Sprocket Boards
This is the documentation repo for sprocket boards. Sprocket boards are not commercially available at the moment.
See the GitHub Organization for more information and source.
The Sprocket-G031
This is a breakout board for the STM32G031. Each board is 15.0x47.0mm.
Each board costs under 2.00 EUR/ea at 100 units.
I documented the design process here:
A design process story in 4 pictures, from concept to board. I'm still waiting on the boards to arrive (looks like they'll be done being built tomorrow hopefully), but I thought I would wax nostalgic about how my process worked for this.
— James Munns (@bitshiftmask) March 15, 2021
Read along for more details. 1/? pic.twitter.com/6kHYOADmBB
And streamed most of the original design on YouTube here:
The Board
It has:
- A 28-pin STM32G031
- 64MHz Cortex M0+
- 8KiB RAM
- 64KiB Flash
- Two WS2812b-style Smartleds
- Two User buttons
- Reverse Protection Diode
- One Power LED (always on)
- Two User LEDs
- 3.3v LDO
- Four QWIIC connectors
- Three main connectors:
- One with 8 GPIOs, all with PWM, six with ADC channels and Power
- One with I2C, SPI, UART, and Power
- One with SWD connectors, reset pin, and power
The board is meant to be brick mount compatible, and is the same size as a LEGO 2x6 Plate.
The four mounting pins each electrically connect to:
- +5V0
- Ground
- SDA (3v3)
- SCL (3v3)
These connectors are intended for use with a testing, programming, or configuration jig.
Compatible Add-On Boards
I don't know if I'll make a lot of accessories, but I'll certainly make some.
Clink board
This board is intended to take PWM as inputs (on up to eight channels). The PWM signal is buffered by a TI 74HC244D, and sent through an RC circuit, taken from the Raspberry Pi 3B+ Audio.
I don't expect Hi-Fi audio, but might be useful to make appropriate bleep-bloops.
The board also contains it's own 5V LDO to reduce signal noise.
Hardware
This is where I put information about the hardware
Image overview
TODO: Do an ifixit/adafruit style highlighted picture
Pins by Package Number
UQFPN-28 Number | Pin Name | SPROCKET USE | PWM | ADC |
---|---|---|---|---|
1 | PC14-OSC32_IN | BUTTON1 | No | No |
2 | PC15-OSC32_OUT | BUTTON2 | No | No |
3 | VDD/VDDA | Supply | No | No |
4 | VSS/VSSA | Supply | No | No |
5 | PF2-NRST | SWD | No | No |
6 | PA0 | LED1 | Yes | ADC0 |
7 | PA1 | GPIO1 | Yes | ADC1 |
8 | PA2 | UART-TX | Yes | ADC2 |
9 | PA3 | UART-RX | Yes | ADC3 |
10 | PA4 | SmartLED (SPI2_MOSI) | Yes | ADC4 |
11 | PA5 | GPIO2 | Yes | ADC5 |
12 | PA6 | GPIO3 | Yes | ADC6 |
13 | PA7 | GPIO4 | Yes | ADC7 |
14 | PB0 | GPIO5 | Yes | ADC8 |
15 | PB1 | GPIO6 | Yes | ADC9 |
16 | PA8 | GPIO7 | Yes | No |
17 | PC6 | GPIO | Yes | No |
18 | PA11/PA9 | I2C2-SCL | Yes | ADC15 |
19 | PA12/PA10 | I2C2-SDA | Maybe? | ADC16 |
20 | PA13 | SWDIO | No | ADC17 |
21 | PA14-BOOT0 | SWCLK | Maybe? | ADC18 |
22 | PA15 | SPI-CSn | Maybe? | No |
23 | PB3 | SPI-SCK | Yes | No |
24 | PB4 | SPI-CIPO | Yes | No |
25 | PB5 | SPI-COPI | Yes | No |
26 | PB6 | I2C1-SCL | Yes | No |
27 | PB7 | I2C1-SDA | Maybe? | ADC11 |
28 | PB8 | LED2 | Yes | No |
Complete Pinout Table
PIN Number | Pin Name | PIN TYPE | IO Capabilities | NOTES | SPROCKET USE | ALT FUNCS | ADD'L FUNC |
---|---|---|---|---|---|---|---|
1 | PC14-OSC32_IN | I/O | FT | 1, 2 | BUTTON1 | TIM1_BK2 | OSC32_IN, OSC_IN |
2 | PC15-OSC32_OUT | I/O | FT | 1, 2 | BUTTON2 | OSC32_EN, OSC_EN | OSC32_OUT |
3 | VDD/VDDA | S | Supply | ||||
4 | VSS/VSSA | S | Supply | ||||
5 | PF2-NRST | I/O | - | - | SWD | MCO | NRST |
6 | PA0 | I/O | FT_a | - | LED1 | SPI2_SCK, USART2_CTS, TIM2_CH1_ETR, LPTIM1_OUT | ADC_IN0, TAMP_IN2, WKUP1 |
7 | PA1 | I/O | FT_ea | - | GPIO1 | SPI1_SCK, I2S1_CK, USART2_RTS_DE_CK, TIM2_CH2, I2C1_SMBA, EVENTOUT | ADC_IN1 |
8 | PA2 | I/O | FT_a | - | UART-TX | SPI1_MOSI, I2S1_SD, USART2_TX, TIM2_CH3, LPUART1_TX | ADC_IN2, WKUP4, LSCO |
9 | PA3 | I/O | FT_ea | - | UART-RX | SPI2_MISO, USART2_RX, TIM2_CH4, LPUART1_RX, EVENTOUT | ADC_IN3 |
10 | PA4 | I/O | FT_a | - | SmartLED (SPI2_MOSI) | SPI1_NSS, I2S1_WS, SPI2_MOSI, TIM14_CH1, LPTIM2_OUT, EVENTOUT | ADC_IN4, TAMP_IN1, RTC_TS, RTC_OUT1, WKUP2 |
11 | PA5 | I/O | FT_ea | - | GPIO2 | SPI1_SCK, I2S1_CK, TIM2_CH1_ETR, LPTIM2_ETR, EVENTOUT | ADC_IN5 |
12 | PA6 | I/O | FT_ea | - | GPIO3 | SPI1_MISO, I2S1_MCK, TIM3_CH1, TIM1_BK, TIM16_CH1, LPUART1_CTS | ADC_IN6 |
13 | PA7 | I/O | FT_a | - | GPIO4 | SPI1_MOSI, I2S1_SD, TIM3_CH2, TIM1_CH1N, TIM14_CH1, TIM17_CH1 | ADC_IN7 |
14 | PB0 | I/O | FT_ea | - | GPIO5 | SPI1_NSS, I2S1_WS, TIM3_CH3, TIM1_CH2N, LPTIM1_OUT | ADC_IN8 |
15 | PB1 | I/O | FT_ea | - | GPIO6 | TIM14_CH1, TIM3_CH4, TIM1_CH3N, LPTIM2_IN1, LPUART1_RTS_DE, EVENTOUT | ADC_IN9 |
16 | PA8 | I/O | FT | - | GPIO7 | MCO, SPI2_NSS, TIM1_CH1, LPTIM2_OUT, EVENTOUT | - |
17 | PC6 | I/O | FT | - | GPIO | TIM3_CH1, TIM2_CH3 | - |
18 | PA11/PA9 | I/O | FT_fa | 3 | I2C2-SCL | SPI1_MISO, I2S1_MCK, USART1_CTS, TIM1_CH4, TIM1_BK2, I2C2_SCL | ADC_IN15 |
19 | PA12/PA10 | I/O | FT_fa | 3 | I2C2-SDA | SPI1_MOSI, I2S1_SD, USART1_RTS_DE_CK, TIM1_ETR, I2S_CKIN, I2C2_SDA | ADC_IN16 |
20 | PA13 | I/O | FT_ea | 4 | SWDIO | SWDIO, IR_OUT, EVENTOUT | ADC_IN17 |
21 | PA14-BOOT0 | I/O | FT_a | 4 | SWCLK | SWCLK, USART2_TX, EVENTOUT | ADC_IN18, BOOT0 |
22 | PA15 | I/O | FT | - | SPI-CSn | SPI1_NSS, I2S1_WS, USART2_RX, TIM2_CH1_ETR, EVENTOUT | - |
23 | PB3 | I/O | FT | - | SPI-SCK | SPI1_SCK, I2S1_CK, TIM1_CH2, TIM2_CH2, USART1_RTS_DE_CK, EVENTOUT | - |
24 | PB4 | I/O | FT | - | SPI-CIPO | SPI1_MISO, I2S1_MCK, TIM3_CH1, USART1_CTS, TIM17_BK, EVENTOUT | - |
25 | PB5 | I/O | FT | - | SPI-COPI | SPI1_MOSI, I2S1_SD, TIM3_CH2, TIM16_BK, LPTIM1_IN1, I2C1_SMBA | WKP6 |
26 | PB6 | I/O | FT_f | - | I2C1-SCL | USART1_TX, TIM1_CH3, TIM16_CH1N, SPI2_MISO, LPTIM1_ETR, I2C1_SCL, EVENTOUT | - |
27 | PB7 | I/O | FT_fa | - | I2C1-SDA | USART1_RX, SPI2_MOSI, TIM17_CH1N, LPTIM1_IN2, I2C1_SDA, EVENTOUT | ADC_IN11, PVD_IN |
28 | PB8 | I/O | FT_f | - | LED2 | SPI2_SCK, TIM16_CH1, I2C1_SCL, EVENTOUT | - |
NOTES | Meaning |
---|---|
1 | <= 2MHz, max load 30pF, only sinks 3mA (collectively?) |
2 | RTC domain relevant, see RM0444 |
3 | pins are remappable to swap between IOs using SYSCFG_CFGR1 |
4 | SWD on reset, PA13 Pull-Up, PA14 Pull-down internally |
FT | 5V tolerant I/O |
_f | Fm+ capable |
_a | analog switch function |
_e | switchable diode to Vdd |
PVD | Programmable Voltage Detector |
MCO | Microcontroller Clock Output |
LSCO | Low Speed Clock Output |
Board Pinout
Col 1 | Col 2 | Col 3 | Col 4 | Col 5 | ||||
---|---|---|---|---|---|---|---|---|
Row 1 | GPIO1 | GPIO5 | SWDIO | COPI | CSn | |||
Row 2 | GPIO2 | GPIO6 | GND | SCK | CIPO | |||
Row 3 | GPIO3 | GPIO7 | SWCLK | SDA | SCL | |||
Row 4 | GPIO4 | GPIO8 | 3v3 | 3v3 | 5v0 | |||
Row 5 | GND | GND | NRST | GND | GND | |||
Row 6 | 3v3 | 5v0 | 5v0+ | RX | TX |
"5v0" pins are post-protection diode. "5v0+" are pre-protection diode.
Bootloader
The bootloader for the sprocket is sprocket-boot
. It is written in Rust.
Installing
Build sprocket-boot
with a nightly compiler, and flash with probe-run
or
however you flash binaries using an SWD adapter. In the future, I plan to build an
application that can be used to update the bootloader without a debugger.
Memory Layout
NOTE: Both the application and the bootloader must respect these regions.
Region | Device | Start Addr | Size | Usage |
---|---|---|---|---|
Application | FLASH | 0x0800_0000 | 58KiB | User Application and Vector Table |
Settings | FLASH | 0x0800_E800 | 2KiB | Bootloader/Application Settings |
Bootloader | FLASH | 0x0800_F000 | 4KiB | Bootloader Code |
RAM Flags | RAM | 0x2000_0000 | 128 Bytes | Message passing between BL/APP |
User Memory | RAM | 0x2000_0080 | 8064 Bytes | General Purpose RAM |
How it works
Sprocket boot doesn't actually relocate the vector table, or own the vector table itself. It depends on the reset vector and MSP of the application to contain the reset address and stack address of the bootloader.
This is achieved by hot-patching the application image when bootloading with the proper bootloader information. It then stores the Application's reset vector and MSP in the Settings page.
This means that if you use SWD (or some other means, like DFU), you will likely break the bootloader, unless you manually apply these changes. It also means that the bootloader cannot use interrupts (or frameworks like RTIC) at all.
The STM32G031 does (I think?) support the use of the VTOR, so I may remove this hot-patching limitation/innovation/hack in the future.
The boot sequence
This is a distilled version of how the boot sequence works:
- The system boots directly to the bootloader
- The bootloader checks:
- The Settings Page
- The RAM Flags Segment
- The Reset Vector/MSP of the Application
- The state of the buttons
- If the buttons are pressed, the system stays in bootloader
- If the "stay in bootloader" RAM flag has been set, the system stays in bootloader
- If the Settings or Application data looks bad, the system stays in bootloader
- Otherwise the bootloader jumps to the application
- If the system is stayig in bootloader, it starts listening as an I2C Peripheral
Pages and Subpages
The FLASH is broken up into two main chunks:
- PAGES, which are always 2KiB, and are the minimum erasable size on the STM32G031.
- SUBPAGES, which are currently defined as 256 bytes. This number was chosen arbitrarily
- Typically, a PAGE is erased, and then each SUBPAGE is written with new data.
Bootloader I2C Commands
The Bootloader acts as an I2C peripheral, and can be clocked (theoretically) up to 1mbps with appropriate pullups and cable length, though clock rates higher than 400kHz are not currently reliable. A clock rate of 100kHz-400kHz is recommended. Clock stretching is probably required at the moment, but I am open to removing that requirement.
There are two kinds of I2C communications from a Controller perspective:
-
Writes
-
Writes-then-Reads
-
For Writes:
- Send the I2C address - Write
- then a 1-byte register ID
- then write the contents of the register
- then a STOP
-
For Writes-then-Reads:
- Send the I2C Address - Write
- Then a 1-byte register ID
- Then a STOP
- Send the I2C Address - Read
- Then read the contents of the register
- Then a stop
Register | Direction | Length | Usage |
---|---|---|---|
0x10 | WR-TH-RD | 16 | Bootloader Image Name |
0x40 | WRITE | 5 | Start Bootload |
0x41 | WRITE | 261 | Write Subpage |
0x42 | WRITE | 0 | Complete and Reboot |
0x45 | WRITE | 1 | Set I2C Address |
At the moment, any other read/write will cause the bootloader to panic. Also Note: The Length in this table does not count the 1 byte register address.
0x10
- Bootloader Image Name
At the moment, the device responds with a constant b"sprocket boot!!!"
.
This register can be used to verify bootloader communications.
0x40
- Start Bootload
Before writing subpages, a bootload must be started. This message should contain:
- 4 bytes - total checksum (later crc32), little endian
- 1 byte - total subpages to write
0x41
- Write Subpage
Before writing subpages, a bootload must be started. On the first write of each page, the page will be erased.
For now, Page 0 + Subpage 0 must be the LAST subpage written. When writing this subpage, the reset vector and stack pointer will be overwritten and stored to the Settings page.
Write Subpage messages contain:
- 1 byte: Page/Subpage - 0bPPPPP_SSS
- max 32 pages
- 8 sub-pages per page
- 256: subpage contents
- 4 byte: For now: 32-bit checksum. Later, CRC32 or similar
0x42
- Complete and Reboot
This command will reboot to the application if:
- No subpages have been written, or a bootload was never started
- ALL subpages have been written, after a bootload was started
The device will wait for the I2C STOP command, then reboot.
0x45
- Set I2C address
After starting a bootload, a new I2C address can be set. This address will always be used by the bootloader, and may be used by the application as well.
This I2C address will only be used after a reboot, and is written to the settings page when a "Complete and Reboot" command is sent.
Board Support Crate
I plan to write a board support crate for the sprocket. TODO.
I2C Protocol
TODO: Document. For now, see the Bootloader Section for more detail on currently implemented I2C commands.