So far we have blinked LEDs, rotated them with a timer, caught a button interrupt, and sent text over UART.
Time to talk to an external chip.
In this tutorial we are going to read angular velocity on X, Y, Z axes from the L3GD20 gyroscope soldered on the STM32F3Discovery board.
We'll use the SPI1 peripheral in bare metal C in order to decode a live transaction on a logic analyzer but also directly from the OpenOCD console.
Let's get started.
The L3GD20 is a separate chip physically soldered on the PCB next to the MCU.
The two chips are connected by 4 copper traces on the board, that is the SPI bus.
That's why we need to set this SPI bus.
On STM32, every peripheral is off by default to save power.
Before using any peripheral, you must enable its clock via RCC.
Without the clock, writing to peripheral registers does nothing.
We then enable them:
Three peripherals, three clock enables.
Miss one and nothing works.
Of course, we enable clocks only on the peripherals used in our project (GPIOA, GPIOE and SPI1).
But before going further let's have a look at some glossary explanation.
SPI uses 4 wires:
NSS is also called CS (Chip Select). "Negative" means active LOW:
SPI is full-duplex: MOSI and MISO transfer simultaneously on every clock edge.
When we send a dummy 0x00 byte to read data, a real byte is being sent at the same time on MOSI, that is just how the protocol works.
The STM32 SPI peripheral has a dedicated NSS pin.
In this exercise we manage CS manually via PE3, a plain GPIO output.
This gives full control over CS timing and avoids the constraints of hardware NSS management.
Since we use software CS, we set SSM=1 and SSI=1 in CR1.
SSM tells the hardware to ignore the NSS pin.
SSI forces the internal NSS signal HIGH to prevent MODF (mode fault) errors that would trigger if the hardware saw NSS as LOW while in master mode.
The L3GD20 requires SPI Mode 3, defined by two parameters:
SCK : ```\___/```\___/``` (idles HIGH, CPOL=1) MOSI : ====X valid X==== (data changes on rising edge) MISO : ====X valid X==== (data sampled on falling edge, CPHA=1) NSS : ```\_______________/ (active LOW during transfer)
Example captured on logic analyzer:
MISO: 0x00 0x1D 0xFC 0x7B 0x02 0x7C 0x00
X = (0xFC << 8) | 0x1D = 0xFC1D = -995 Y = (0x02 << 8) | 0x7B = 0x027B = 635 Z = (0x00 << 8) | 0x7C = 0x007C = 124
Terminal output for the same frame:
-995 635 124
Exact match, SPI communication validated.
Add a getchar() in the main loop to step through one frame at a time:
while (1) {
l3gd20_read_xyz(&x, &y, &z);
printf("%6d %6d %6d\r\n", (int)x, (int)y, (int)z);
getchar(); // press Enter to advance one frame
leds_show_rotation(z);
delay_ms(200);
}
Capture on the logic analyzer, press Enter in the OpenOCD console, then compare the decoded bytes with the printed values.
To build, flash and monitor in one command, use the run.sh file provided in this tutorial.
Simply do from the firmware/stm32f3discovery folder:
$ ./run.sh all
We have configured SPI1 from scratch, read raw gyroscope data from the L3GD20, and verified every byte on a logic analyzer.
The data matches, the protocol is understood.
Good job, you did it ![]()
Add new comment