Implementing the SPI (Serial Peripheral Interface) driver on the STM32 microcontroller for communication with sensors. The content includes SPI initialization, data transmission, error handling, debugging techniques, and the use of logic analyzers. The code is based on the STM32Cube HAL library, suitable for the STM32F4 series (such as STM32F407), and uses a generic sensor (like MPU6050) as an example.1. Basics of SPI and Hardware Preparation
1.1 Introduction to SPI Protocol
SPI is a high-speed, full-duplex, synchronous serial communication protocol widely used for communication between MCUs and peripherals such as sensors and memory. Key features include:
-
Master-Slave Mode: STM32 acts as the master, and the sensor acts as the slave.
-
Four-Wire System: MOSI (Master Out Slave In), MISO (Master In Slave Out), SCLK (Clock Line), CS (Chip Select Line).
-
Clock Polarity and Phase: CPOL (Clock Polarity) and CPHA (Clock Phase) determine the timing of data sampling.
-
Data Frame: Typically 8 or 16 bits, depending on the slave device.
1.2 Hardware Connections
Taking the STM32F407 and MPU6050 sensor as an example, the typical connections are as follows:
-
SPI1 Pins (can be adjusted based on actual hardware):
-
PA5: SCLK
-
PA6: MISO
-
PA7: MOSI
-
PA4: CS (Chip Select, GPIO controlled)
-
MPU6050:
-
SCK → PA5
-
SDA/SDI → PA7 (MOSI)
-
SDO → PA6 (MISO)
-
CS → PA4
-
VCC: 3.3V, GND: Ground
1.3 Development Environment
-
Hardware: STM32F407 development board, MPU6050 module, logic analyzer (e.g., Saleae Logic).
-
Software: STM32CubeIDE, STM32CubeMX, logic analyzer software.
-
Library: STM32Cube HAL library.
2. SPI Initialization
2.1 Configuring SPI with STM32CubeMX
STM32CubeMX can quickly generate initialization code, with the following steps:
-
Create Project:
-
Select the STM32F407 chip and create a new project.
Configure Clock:
-
Set HSE (external crystal) as the system clock source and configure PLL to achieve a system clock of 168 MHz.
Configure SPI1:
-
Baud Rate: Set to 1 Mbps (according to the sensor manual, MPU6050 supports up to 20 MHz).
-
CPOL: Low (idle clock low level).
-
CPHA: 1 Edge (sample on the first clock edge).
-
Data Size: 8 bits.
-
NSS: Software (chip select controlled by GPIO).
-
Enable SPI1, select mode as “Full-Duplex Master”.
-
Parameter settings:
Configure GPIO:
-
PA4 configured as GPIO output for manual CS control.
Generate Code:
-
Set project name and path, generate initialization code.
The generated code is located in the main.c file in the MX_SPI1_Init function, with the example code as follows:
static void MX_SPI1_Init(void){ hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; // Adjust to 1 Mbps hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; if (HAL_SPI_Init(&hspi1) != HAL_OK) { Error_Handler(); }}
2.2 Manually Configuring the CS Pin
The CS pin is controlled via GPIO, with the initialization code as follows:
static void MX_GPIO_Init(void){ GPIO_InitTypeDef GPIO_InitStruct = {0}; // Enable GPIOA clock __HAL_RCC_GPIOA_CLK_ENABLE(); // Configure PA4 as output (CS) GPIO_InitStruct.Pin = GPIO_PIN_4; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // Default CS high (not selected) HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);}
3. Data Transmission
3.1 SPI Communication Process
SPI communication typically includes:
-
Pulling CS Low: Selecting the slave device.
-
Sending Command/Address: For example, reading the MPU6050 register.
-
Sending/Receiving Data: The master sends data while receiving data from the slave.
-
Pulling CS High: Ending communication.
3.2 Implementing Read/Write Functions
The following are the SPI read/write functions based on the HAL library, suitable for MPU6050:
// SPI handle (defined in main.c)extern SPI_HandleTypeDef hspi1; // CS control macros#define CS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET)#define CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET) // Write RegisterHAL_StatusTypeDef MPU6050_WriteReg(uint8_t reg, uint8_t data){ uint8_t txData[2] = {reg & 0x7F, data}; // Register address write operation (bit7=0) CS_LOW(); HAL_StatusTypeDef status = HAL_SPI_Transmit(&hspi1, txData, 2, HAL_MAX_DELAY); CS_HIGH(); return status;} // Read RegisterHAL_StatusTypeDef MPU6050_ReadReg(uint8_t reg, uint8_t *data, uint16_t len){ uint8_t txData = reg | 0x80; // Register address read operation (bit7=1) CS_LOW(); HAL_StatusTypeDef status = HAL_SPI_Transmit(&hspi1, &txData, 1, HAL_MAX_DELAY); if (status == HAL_OK) { status = HAL_SPI_Receive(&hspi1, data, len, HAL_MAX_DELAY); } CS_HIGH(); return status;}
3.3 Initializing MPU6050
Taking MPU6050 as an example, the initialization code is as follows:
void MPU6050_Init(void){ // Wake up MPU6050 (exit sleep mode) MPU6050_WriteReg(0x6B, 0x00); // PWR_MGMT_1 register // Configure sample rate MPU6050_WriteReg(0x19, 0x07); // SMPLRT_DIV register // Configure gyroscope and accelerometer MPU6050_WriteReg(0x1B, 0x00); // GYRO_CONFIG register MPU6050_WriteReg(0x1C, 0x00); // ACCEL_CONFIG register}
3.4 Reading Sensor Data
Reading accelerometer data (registers 0x3B~0x40):
void MPU6050_ReadAccel(int16_t *accel){ uint8_t data[6]; MPU6050_ReadReg(0x3B, data, 6); // Read 6 bytes (X/Y/Z axis high and low bytes) accel[0] = (int16_t)((data[0] << 8) | data[1]); // X axis accel[1] = (int16_t)((data[2] << 8) | data[3]); // Y axis accel[2] = (int16_t)((data[4] << 8) | data[5]); // Z axis}
4. Error Handling
4.1 Common Errors
-
Timeout: HAL_SPI_Transmit/Receive returns HAL_TIMEOUT.
-
Hardware Connection Error: Pins not properly connected or poorly soldered.
-
Timing Mismatch: CPOL/CPHA settings do not match the slave device.
-
Chip Select Error: CS not properly pulled low/high.
4.2 Error Handling Code
Add error checking in the read/write functions:
HAL_StatusTypeDef MPU6050_WriteReg(uint8_t reg, uint8_t data){ uint8_t txData[2] = {reg & 0x7F, data}; CS_LOW(); HAL_StatusTypeDef status = HAL_SPI_Transmit(&hspi1, txData, 2, 100); // 100ms timeout CS_HIGH(); if (status != HAL_OK) { // Log error (e.g., via UART output) printf("SPI Write Error: %d\n", status); } return status;}
4.3 Retry Mechanism
For timeouts or communication errors, a retry logic can be added:
HAL_StatusTypeDef MPU6050_WriteReg_Retry(uint8_t reg, uint8_t data, uint8_t retries){ HAL_StatusTypeDef status; for (uint8_t i = 0; i < retries; i++) { status = MPU6050_WriteReg(reg, data); if (status == HAL_OK) return HAL_OK; HAL_Delay(10); // Wait 10ms before retrying } printf("SPI Write Failed after %d retries\n", retries); return status;}
5. Debugging Techniques
5.1 Using UART to Print Debug Information
Output register values or error statuses via serial:
void MPU6050_Debug(void){ uint8_t who_am_i; MPU6050_ReadReg(0x75, &who_am_i, 1); // WHO_AM_I register printf("MPU6050 WHO_AM_I: 0x%02X\n", who_am_i); // Should be 0x68}
5.2 Using Logic Analyzer
A logic analyzer (e.g., Saleae Logic) can capture SPI signals and analyze timing issues:
-
Connect Logic Analyzer:
-
Channel 0: SCLK
-
Channel 1: MOSI
-
Channel 2: MISO
-
Channel 3: CS
Set Sampling Rate: Recommended above 10 MHz to ensure high-frequency signal capture.
Trigger Condition: Trigger on CS falling edge.
Analyze Signals:
-
Check if SCLK frequency meets expectations (1 Mbps).
-
Verify CPOL/CPHA settings: SCLK idle level (low) and sampling edge (first edge).
-
Confirm if MOSI/MISO data matches expectations (e.g., MPU6050’s WHO_AM_I register should return 0x68).
Common Issues:
-
Irregular SCLK: Check clock configuration or SPI initialization.
-
Data Misalignment: Check CPHA settings or CS timing.
5.3 Using Debugger
Using the debugging mode of STM32CubeIDE:
-
Set breakpoints in read/write functions to observe variable values.
-
Check SPI register status (e.g., SPI_SR register).
6. Advanced Logic Analyzer Usage
6.1 Capturing Long Timings
For periodic data transmission, set the logic analyzer to “Continuous Capture” mode to analyze multiple frames of data:
-
Check if CS is correctly pulled low/high each time.
-
Verify if data transmission intervals are stable.
6.2 Automatic Decoding
Saleae Logic supports SPI protocol decoding:
-
Select “SPI” analyzer in the software.
-
Configure channels (SCLK/MOSI/MISO/CS).
-
Set CPOL/CPHA and data size (8 bits).
-
View decoding results to quickly locate erroneous bytes.
6.3 Saving and Sharing
Save captured waveforms as .sal files or export as CSV for further analysis.
7. Complete Example Code
The following is the complete main function, integrating initialization and data reading:
#include "main.h"#include <stdio.h>extern SPI_HandleTypeDef hspi1;int main(void){ HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_SPI1_Init(); MX_USART2_UART_Init(); // For debugging output MPU6050_Init(); int16_t accel[3]; while (1) { MPU6050_ReadAccel(accel); printf("Accel: X=%d, Y=%d, Z=%d\n", accel[0], accel[1], accel[2]); HAL_Delay(1000); // Read once per second }}
8. Common Debugging Issues
8.1 Sensor Not Responding
-
Check: Does the WHO_AM_I register (0x75) return 0x68?
-
Solution:
-
Confirm hardware connections (soldering, pin multiplexing).
-
Check power supply (MPU6050 requires 3.3V).
-
Verify SPI clock frequency (too high may cause failure).
8.2 Inaccurate Data
-
Check: Are the accelerometer data abnormal (e.g., all zeros or fixed values)?
-
Solution:
-
Confirm MPU6050 initialization (sleep mode is off).
-
Check data merging logic (high and low byte order).
8.3 SPI Communication Failure
-
Check: Does HAL_SPI_Transmit return HAL_ERROR or HAL_TIMEOUT?
-
Solution:
-
Use a logic analyzer to check timing.
-
Lower SPI baud rate (e.g., 500 kbps) and retry.