嵌入式软件知识点汇总(day6):常见通信方式(上)
串口通信
1. 串口通信基本概念
串口通信是一种串行通信方式,数据逐位顺序传输,只需要少数几根线即可实现双向通信。
一般四根线(VCC、GND、RX、TX),部分场景下只需要两根线(RX、TX)(RX、GND)(TX、GND)
串行通信:数据逐位依次传输,只需要少数几根线。
并行通信:数据多个位同时传输,需要多根数据线。
全双工:可以同时进行发送和接收。
半双工:可以发送或接收,但不能同时进行。
单工:只能在一个方向上传输。
主要特点
异步通信:不需要时钟信号同步
全双工:可同时发送和接收
点对点:通常两个设备间通信
距离较长:相比I2C/SPI可传输更远距离
串口通信通常是全双工的。
2. 硬件接口
RS-232:常见于计算机串口,使用负逻辑,电压范围较大(-15V~+15V),传输距离较长(可达15米)。
RS-485:差分信号,抗干扰能力强,支持多点通信,传输距离更长(可达1200米)。
TTL UART:单片机常用,电压为0V和3.3V/5V,传输距离短(通常不超过1米)。
3. 串口通信协议
起始位:表示一帧数据的开始,通常为1位低电平。
数据位:实际传输的数据,通常为5、6、7、8位。
校验位:用于错误检测,可选奇校验、偶校验或无校验。
停止位:表示一帧数据的结束,通常为1、1.5或2位高电平。
4. 关键参数
波特率:每秒传输的符号数,常见的波特率有9600、115200等。
数据位:5~8位,通常为8位。
停止位:1、1.5、2位,通常为1位。
校验位:奇校验、偶校验、无校验。
流控制:硬件流控制(RTS/CTS)和软件流控制(XON/XOFF)。
波特率(Baud Rate)计算
// 波特率计算(以STM32为例)
void calculate_baudrate(void) {// 波特率 = 系统时钟 / (USARTDIV × 16)// USARTDIV = 系统时钟 / (波特率 × 16)uint32_t system_clock = 72000000; // 72MHzuint32_t desired_baud = 115200;uint32_t usartdiv = system_clock / (desired_baud * 16);printf("USARTDIV = %lu\n", usartdiv);
}
数据帧格式
串口数据帧格式:
+----------+----------+-------------+----------+----------+----------+
| 起始位 | 数据位 | 校验位 | 停止位 | | |
| (1 bit) | (5-9 bit) | (可选) | (1-2 bit) | | |
+----------+----------+-------------+----------+----------+----------+
示例:8N1格式 = 1起始位 + 8数据位 + 无校验 + 1停止位
5. 流控制
硬件流控制:使用RTS(Request to Send)和CTS(Clear to Send)信号线来控制数据流,避免缓冲区溢出。
软件流控制:使用特殊字符XON(0x11)和XOFF(0x13)来控制数据流。
6. 错误检测
奇偶校验:检测单比特错误。
溢出错误:接收缓冲区已满,又收到新数据。
帧错误:停止位不在预期的时刻出现。
校验错误:奇偶校验失败。
7. 串口通信编程
7.1 初始化步骤
配置GPIO引脚为串口功能。
使能串口时钟。
配置波特率、数据位、停止位、校验位、流控制。
使能串口和中断(如果需要)。
7.2 数据发送和接收
轮询方式:程序不断检查状态寄存器,直到发送完成或接收到数据。
// 简单的轮询收发 void uart_send_byte(USART_TypeDef *uart, uint8_t data) {// 等待发送缓冲区空while(!(uart->ISR & USART_ISR_TXE));// 发送数据uart->TDR = data;// 等待发送完成while(!(uart->ISR & USART_ISR_TC)); }uint8_t uart_receive_byte(USART_TypeDef *uart) {// 等待接收到数据while(!(uart->ISR & USART_ISR_RXNE));// 读取数据return (uint8_t)(uart->RDR); }// 发送字符串 void uart_send_string(USART_TypeDef *uart, const char *str) {while(*str) {uart_send_byte(uart, *str++);} }
中断方式:使用中断处理发送和接收,提高效率。
#include "stm32f1xx_it.h"// 接收缓冲区 #define RX_BUFFER_SIZE 256 static uint8_t uart_rx_buffer[RX_BUFFER_SIZE]; static volatile uint16_t uart_rx_index = 0; static volatile bool uart_rx_complete = false;// 中断初始化 void uart_interrupt_init(USART_TypeDef *uart) {// 使能接收中断uart->CR1 |= USART_CR1_RXNEIE;// 配置NVICIRQn_Type irq;if(uart == USART1) irq = USART1_IRQn;else if(uart == USART2) irq = USART2_IRQn;// 其他USART...HAL_NVIC_SetPriority(irq, 0, 0);HAL_NVIC_EnableIRQ(irq); }// 中断服务函数 void USART1_IRQHandler(void) {// 接收中断if(USART1->ISR & USART_ISR_RXNE) {uint8_t data = (uint8_t)(USART1->RDR);// 简单的协议处理:以换行符结束if(data == '\n' || uart_rx_index >= RX_BUFFER_SIZE - 1) {uart_rx_buffer[uart_rx_index] = '\0';uart_rx_complete = true;uart_rx_index = 0;} else {uart_rx_buffer[uart_rx_index++] = data;}}// 发送中断处理...if(USART1->ISR & USART_ISR_TXE) {// 发送缓冲区空,可以发送下一个字节} }// 获取接收到的数据 bool uart_get_received_data(char *buffer, uint16_t size) {if(uart_rx_complete) {uint16_t len = strlen((char*)uart_rx_buffer);if(len < size) {strcpy(buffer, (char*)uart_rx_buffer);uart_rx_complete = false;return true;}}return false; }
DMA方式:使用DMA传输大量数据,减少CPU占用。
// DMA接收配置 void uart_dma_init(USART_TypeDef *uart, DMA_Channel_TypeDef *dma_ch) {// 配置DMAdma_ch->CCR = 0;dma_ch->CCR |= DMA_CCR_MINC; // 内存地址递增dma_ch->CCR |= DMA_CCR_CIRC; // 循环模式dma_ch->CCR |= DMA_CCR_TCIE; // 传输完成中断// 设置外设地址(UART数据寄存器)dma_ch->CPAR = (uint32_t)&(uart->RDR);// 设置内存地址dma_ch->CMAR = (uint32_t)uart_rx_buffer;// 数据数量dma_ch->CNDTR = RX_BUFFER_SIZE;// 使能DMAdma_ch->CCR |= DMA_CCR_EN;// 使能UART的DMA接收uart->CR3 |= USART_CR3_DMAR; }// DMA发送函数 void uart_dma_send(USART_TypeDef *uart, DMA_Channel_TypeDef *dma_ch, uint8_t *data, uint16_t length) {// 等待上次DMA传输完成while(dma_ch->CNDTR != 0);// 配置DMA发送dma_ch->CCR &= ~DMA_CCR_EN;dma_ch->CMAR = (uint32_t)data;dma_ch->CNDTR = length;dma_ch->CCR |= DMA_CCR_EN;// 使能UART的DMA发送uart->CR3 |= USART_CR3_DMAT; }
8. 常见应用
调试输出:通过串口打印调试信息。
与传感器通信:读取传感器数据。
与计算机通信:实现嵌入式设备与上位机的数据交换。
设备间通信:多个嵌入式设备通过串口进行数据交换。
9. 通信协议设计
简单的文本协议
// 命令行协议示例
typedef enum {CMD_GET_TEMP, // 获取温度CMD_SET_LED, // 设置LEDCMD_GET_STATUS, // 获取状态CMD_RESET, // 复位CMD_UNKNOWN // 未知命令
} uart_command_t;// 协议解析
uart_command_t parse_command(const char *cmd_str) {if(strcmp(cmd_str, "GET_TEMP") == 0) return CMD_GET_TEMP;if(strcmp(cmd_str, "SET_LED") == 0) return CMD_SET_LED;if(strcmp(cmd_str, "GET_STATUS") == 0) return CMD_GET_STATUS;if(strcmp(cmd_str, "RESET") == 0) return CMD_RESET;return CMD_UNKNOWN;
}// 命令处理
void process_uart_command(const char *command) {uart_command_t cmd = parse_command(command);switch(cmd) {case CMD_GET_TEMP:{float temp = read_temperature();char response[32];snprintf(response, sizeof(response), "TEMP:%.2f\r\n", temp);uart_send_string(USART1, response);}break;case CMD_SET_LED:// 解析参数并设置LEDbreak;case CMD_GET_STATUS:{char status[64];get_system_status(status);uart_send_string(USART1, status);}break;default:uart_send_string(USART1, "ERROR: Unknown command\r\n");break;}
}
二进制协议(更高效)
#pragma pack(push, 1)// 协议头
typedef struct {uint8_t start_flag; // 起始标志 0xAAuint8_t command; // 命令字uint16_t length; // 数据长度uint8_t checksum; // 头校验和
} uart_protocol_header_t;// 数据包
typedef struct {uart_protocol_header_t header;uint8_t data[256]; // 数据负载uint8_t crc16[2]; // CRC16校验
} uart_packet_t;#pragma pack(pop)// 协议处理状态机
typedef enum {STATE_WAIT_HEADER,STATE_READ_LENGTH,STATE_READ_DATA,STATE_READ_CRC,STATE_PROCESS_PACKET
} uart_parser_state_t;// 协议解析器
typedef struct {uart_parser_state_t state;uart_packet_t packet;uint16_t data_index;uint16_t expected_length;
} uart_parser_t;uint8_t calculate_checksum(const uint8_t *data, uint16_t length) {uint8_t sum = 0;for(uint16_t i = 0; i < length; i++) {sum += data[i];}return sum;
}// 协议解析状态机
bool uart_parse_byte(uart_parser_t *parser, uint8_t byte) {switch(parser->state) {case STATE_WAIT_HEADER:if(byte == 0xAA) {parser->packet.header.start_flag = byte;parser->state = STATE_READ_LENGTH;parser->data_index = 0;}break;case STATE_READ_LENGTH:if(parser->data_index == 0) {parser->packet.header.command = byte;parser->data_index++;} else {parser->packet.header.length = byte;parser->expected_length = byte;parser->data_index = 0;// 验证头校验和uint8_t checksum = calculate_checksum((uint8_t*)&parser->packet.header, sizeof(uart_protocol_header_t) - 1);if(checksum == parser->packet.header.checksum) {parser->state = (parser->expected_length > 0) ? STATE_READ_DATA : STATE_READ_CRC;} else {parser->state = STATE_WAIT_HEADER; // 校验失败}}break;case STATE_READ_DATA:parser->packet.data[parser->data_index++] = byte;if(parser->data_index >= parser->expected_length) {parser->state = STATE_READ_CRC;parser->data_index = 0;}break;case STATE_READ_CRC:parser->packet.crc16[parser->data_index++] = byte;if(parser->data_index >= 2) {parser->state = STATE_PROCESS_PACKET;return true; // 完整数据包接收完成}break;default:parser->state = STATE_WAIT_HEADER;break;}return false;
}
10. 错误处理和流控制
错误检测和处理
// 错误状态定义
typedef enum {UART_ERROR_NONE = 0,UART_ERROR_OVERRUN, // 溢出错误UART_ERROR_FRAMING, // 帧错误UART_ERROR_PARITY, // 校验错误UART_ERROR_NOISE, // 噪声错误UART_ERROR_TIMEOUT // 超时错误
} uart_error_t;// 错误处理
uart_error_t uart_check_errors(USART_TypeDef *uart) {if(uart->ISR & USART_ISR_ORE) {uart->ICR |= USART_ICR_ORECF; // 清除溢出标志return UART_ERROR_OVERRUN;}if(uart->ISR & USART_ISR_FE) {uart->ICR |= USART_ICR_FECF; // 清除帧错误标志return UART_ERROR_FRAMING;}if(uart->ISR & USART_ISR_PE) {uart->ICR |= USART_ICR_PECF; // 清除校验错误标志return UART_ERROR_PARITY;}if(uart->ISR & USART_ISR_NE) {uart->ICR |= USART_ICR_NECF; // 清除噪声错误标志return UART_ERROR_NOISE;}return UART_ERROR_NONE;
}// 带错误处理的接收函数
bool uart_receive_byte_safe(USART_TypeDef *uart, uint8_t *data, uint32_t timeout) {uint32_t start_time = HAL_GetTick();while(!(uart->ISR & USART_ISR_RXNE)) {// 检查超时if((HAL_GetTick() - start_time) > timeout) {return false;}// 检查错误uart_error_t error = uart_check_errors(uart);if(error != UART_ERROR_NONE) {// 记录错误日志uart_log_error(error);return false;}}*data = (uint8_t)(uart->RDR);return true;
}
硬件流控制
// RTS/CTS流控制实现
void uart_flow_control_init(void) {// 配置RTS为输出,CTS为输入GPIO_InitTypeDef gpio_init = {0};// RTS引脚配置gpio_init.Pin = UART_RTS_PIN;gpio_init.Mode = GPIO_MODE_OUTPUT_PP;gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(UART_RTS_PORT, &gpio_init);// CTS引脚配置gpio_init.Pin = UART_CTS_PIN;gpio_init.Mode = GPIO_MODE_INPUT;gpio_init.Pull = GPIO_NOPULL;HAL_GPIO_Init(UART_CTS_PORT, &gpio_init);// 初始状态:可以接收数据HAL_GPIO_WritePin(UART_RTS_PORT, UART_RTS_PIN, GPIO_PIN_RESET);
}// 检查是否可以发送(CTS信号)
bool uart_can_send(void) {return (HAL_GPIO_ReadPin(UART_CTS_PORT, UART_CTS_PIN) == GPIO_PIN_RESET);
}// 设置是否可以接收(RTS信号)
void uart_set_ready_to_receive(bool ready) {HAL_GPIO_WritePin(UART_RTS_PORT, UART_RTS_PIN, ready ? GPIO_PIN_RESET : GPIO_PIN_SET);
}// 带流控制的发送函数
bool uart_send_with_flow_control(USART_TypeDef *uart, uint8_t *data, uint16_t length) {for(uint16_t i = 0; i < length; i++) {// 等待CTS信号允许发送uint32_t timeout = 1000; // 1秒超时uint32_t start_time = HAL_GetTick();while(!uart_can_send()) {if((HAL_GetTick() - start_time) > timeout) {return false; // 超时}}// 发送字节uart_send_byte(uart, data[i]);}return true;
}
11. 实际应用案例
嵌入式日志系统
// 串口日志系统
typedef enum {LOG_LEVEL_DEBUG,LOG_LEVEL_INFO,LOG_LEVEL_WARNING,LOG_LEVEL_ERROR
} log_level_t;void uart_log(log_level_t level, const char *format, ...) {// 添加日志级别前缀const char *level_str[] = {"DEBUG", "INFO", "WARN", "ERROR"};char buffer[256];int pos = snprintf(buffer, sizeof(buffer), "[%s] ", level_str[level]);// 格式化消息va_list args;va_start(args, format);vsnprintf(buffer + pos, sizeof(buffer) - pos, format, args);va_end(args);// 添加换行strcat(buffer, "\r\n");// 发送日志uart_send_string(USART1, buffer);
}// 使用示例
#define LOG_DEBUG(...) uart_log(LOG_LEVEL_DEBUG, __VA_ARGS__)
#define LOG_ERROR(...) uart_log(LOG_LEVEL_ERROR, __VA_ARGS__)void system_monitor_task(void) {LOG_DEBUG("系统启动完成");LOG_INFO("温度: %.1f°C", read_temperature());if(system_error_detected) {LOG_ERROR("系统错误: %s", error_message);}
}
传感器数据采集
// 通过串口读取传感器数据
typedef struct {float temperature;float humidity;uint16_t pressure;uint8_t battery_level;
} sensor_data_t;bool read_sensor_data_via_uart(sensor_data_t *data) {// 发送读取命令const uint8_t read_cmd[] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x04};uart_send_data(USART2, read_cmd, sizeof(read_cmd));// 等待响应uint8_t response[32];if(!uart_receive_data(USART2, response, sizeof(response), 1000)) {return false;}// 解析响应数据if(response[0] == 0x01 && response[1] == 0x03) {uint16_t temp_raw = (response[3] << 8) | response[4];uint16_t hum_raw = (response[5] << 8) | response[6];data->temperature = temp_raw / 10.0f;data->humidity = hum_raw / 10.0f;return true;}return false;
}
总结
串口通信关键知识点:
基本参数:波特率、数据位、停止位、校验位
硬件接口:TTL、RS232、RS485电平标准
工作模式:轮询、中断、DMA
协议设计:文本协议、二进制协议
错误处理:溢出、帧错误、校验错误
流控制:硬件RTS/CTS、软件XON/XOFF
实际应用:调试日志、传感器通信、设备控制
嵌入式开发建议:
选择合适的波特率:平衡速度和可靠性
使用DMA处理大数据:减少CPU占用
实现完整的错误处理:提高系统稳定性
设计清晰的通信协议:便于调试和维护
考虑硬件流控制:在高速通信时很重要
IIC总线通信
我们通常说的IIC(Inter-Integrated Circuit)总线是一种同步、多主从、半双工的串行通信总线,由Philips公司(现NXP)开发。它只需要两根线:串行数据线(SDA)和串行时钟线(SCL)。IIC总线在嵌入式系统中广泛应用,用于连接微控制器和各种外设,如传感器、存储器、IO扩展器等。
IIC总线特点
总线长度较短,一般用于板内通信
两线制:SCL(时钟线)和SDA(数据线)
多主多从:支持多个主设备和从设备(但同一时刻只能有一个主设备)
地址寻址:每个设备有唯一地址,通过地址选择设备
速度分级:标准模式(100kbps)、快速模式(400kbps)、高速模式(3.4Mbps)
半双工:同一时间只能单向传输
总线拓扑结构
I2C总线拓扑:+-----+ +-----+ +-----+| MCU | | RTC | | EEPROM||主设备| |从设备| |从设备 |+-----+ +-----+ +-----+| | || | | SCL -----+-----------+-----------+ SDA -----+-----------+-----------+| | |+-----+ +-----+ +-----+| 上拉 | | 上拉 | | 上拉 || 电阻 | | 电阻 | | 电阻 |+-----+ +-----+ +-----+
IIC总线信号
起始条件(START):SCL为高电平时,SDA由高变低
停止条件(STOP):SCL为高电平时,SDA由低变高
数据有效性:在SCL高电平期间,SDA必须保持稳定。数据变化发生在SCL低电平期间。
应答(ACK):每传输完一个字节(8位),接收方需要发送一个应答位(低电平为ACK,高电平为NACK)
非应答(NACK):当接收方不想再接收数据或无法接收时,会发送NACK
IIC总线协议
数据传输以起始条件开始,停止条件结束。
数据传输的基本单位是一个字节,高位在前。
每个字节后必须跟一个应答位。
数据帧格式:
起始条件 + 从设备地址(7位或10位) + 读写位(1位,0写1读) + 应答 + 数据字节 + 应答/非应答 + ... + 停止条件
7位地址和10位地址
7位地址:常用,地址范围0x08~0x77(其中0x00~0x07和0x78~0x7F为保留地址)
10位地址:扩展地址范围,但使用较少
IIC总线操作模式
主设备发送模式:主设备向从设备写数据
主设备接收模式:主设备从从设备读数据
组合模式:先写后读,例如先发送要读的寄存器地址,再读取数据
时钟同步和仲裁
时钟同步:多个主设备可以同时拉低SCL,但只有所有主设备都释放SCL后,SCL才变高。因此,时钟由高电平持续时间最长的设备决定。
仲裁:当多个主设备同时发送数据时,通过SDA上的数据仲裁,确保只有一个主设备获胜。仲裁失败的主设备会退出并转为从设备。
常见IIC设备
EEPROM(如24C02)
温度传感器(如LM75)
实时时钟(如DS1307)
IO扩展器(如PCF8574)
数字陀螺仪/加速度计(如MPU6050)
软件模拟IIC和硬件IIC
硬件IIC:使用微控制器自带的IIC控制器,效率高,但不一定兼容所有设备。软件模拟IIC:使用普通GPIO模拟IIC时序,灵活,但占用CPU资源。上拉电阻
IIC总线为开漏输出,因此SDA和SCL都需要上拉电阻,典型值在2.2k~10k之间,具体根据总线速度和总线电容决定。
I2C硬件接口和电气特性
电气特性
// I2C电平标准
#define I2C_VOLTAGE_LEVEL_3V3 33
#define I2C_VOLTAGE_LEVEL_5V 50// 上拉电阻计算
typedef struct {uint32_t vdd; // 电源电压(mV)uint32_t tr_max; // 最大上升时间(ns)uint32_t bus_capacitance; // 总线电容(pF)
} i2c_electrical_params_t;uint32_t calculate_pullup_resistance(i2c_electrical_params_t *params) {// 上拉电阻 = tr_max / (0.8473 × bus_capacitance)return params->tr_max / (0.8473 * params->bus_capacitance);
}// 常用上拉电阻值
#define I2C_PULLUP_1K 1000
#define I2C_PULLUP_2K2 2200
#define I2C_PULLUP_4K7 4700
#define I2C_PULLUP_10K 10000
GPIO配置
// I2C引脚配置结构体
typedef struct {GPIO_TypeDef *scl_port;uint16_t scl_pin;GPIO_TypeDef *sda_port;uint16_t sda_pin;uint32_t pullup_resistor; // 上拉电阻值
} i2c_pin_config_t;// STM32 I2C引脚配置示例
void i2c_gpio_init(i2c_pin_config_t *config) {GPIO_InitTypeDef gpio_init = {0};// SCL引脚配置gpio_init.Pin = config->scl_pin;gpio_init.Mode = GPIO_MODE_AF_OD; // 开漏输出gpio_init.Pull = GPIO_PULLUP; // 内部上拉(可选)gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;gpio_init.Alternate = GPIO_AF4_I2C1; // I2C复用功能HAL_GPIO_Init(config->scl_port, &gpio_init);// SDA引脚配置gpio_init.Pin = config->sda_pin;HAL_GPIO_Init(config->sda_port, &gpio_init);printf("I2C GPIO初始化完成\n");printf("SCL: Port=0x%p, Pin=%d\n", config->scl_port, config->scl_pin);printf("SDA: Port=0x%p, Pin=%d\n", config->sda_port, config->sda_pin);
}
I2C通信协议详解
基本通信时序
// I2C时序定义
typedef struct {uint32_t start_condition; // 起始条件uint32_t stop_condition; // 停止条件uint32_t data_setup; // 数据建立时间uint32_t data_hold; // 数据保持时间uint32_t clock_low; // 时钟低电平时间uint32_t clock_high; // 时钟高电平时间
} i2c_timing_t;// I2C帧格式
void i2c_frame_format_demo(void) {printf("=== I2C通信帧格式 ===\n");printf("1. 起始条件(S)\n");printf("2. 从设备地址(7位) + 读写位(1位)\n");printf("3. 应答位(ACK/NACK)\n");printf("4. 数据字节(8位)\n");printf("5. 应答位(ACK/NACK)\n");printf("6. ... 更多数据字节 ...\n");printf("7. 停止条件(P)\n");
}
软件模拟I2C实现
#include "stm32f1xx_hal.h"// 软件I2C延时函数
void i2c_delay(void) {for(volatile int i = 0; i < 10; i++); // 调整延时时间
}// I2C起始条件
void i2c_start(i2c_pin_config_t *config) {// SDA高电平期间,SCL从高变低HAL_GPIO_WritePin(config->sda_port, config->sda_pin, GPIO_PIN_SET);HAL_GPIO_WritePin(config->scl_port, config->scl_pin, GPIO_PIN_SET);i2c_delay();HAL_GPIO_WritePin(config->sda_port, config->sda_pin, GPIO_PIN_RESET);i2c_delay();HAL_GPIO_WritePin(config->scl_port, config->scl_pin, GPIO_PIN_RESET);i2c_delay();
}// I2C停止条件
void i2c_stop(i2c_pin_config_t *config) {// SDA低电平期间,SCL从低变高HAL_GPIO_WritePin(config->sda_port, config->sda_pin, GPIO_PIN_RESET);HAL_GPIO_WritePin(config->scl_port, config->scl_pin, GPIO_PIN_SET);i2c_delay();HAL_GPIO_WritePin(config->sda_port, config->sda_pin, GPIO_PIN_SET);i2c_delay();
}// 发送一个字节
bool i2c_send_byte(i2c_pin_config_t *config, uint8_t data) {for(int i = 7; i >= 0; i--) {// 设置数据位HAL_GPIO_WritePin(config->sda_port, config->sda_pin, (data & (1 << i)) ? GPIO_PIN_SET : GPIO_PIN_RESET);i2c_delay();// 时钟脉冲HAL_GPIO_WritePin(config->scl_port, config->scl_pin, GPIO_PIN_SET);i2c_delay();HAL_GPIO_WritePin(config->scl_port, config->scl_pin, GPIO_PIN_RESET);i2c_delay();}// 读取应答位HAL_GPIO_WritePin(config->sda_port, config->sda_pin, GPIO_PIN_SET); // 释放SDAi2c_delay();HAL_GPIO_WritePin(config->scl_port, config->scl_pin, GPIO_PIN_SET);i2c_delay();GPIO_PinState ack = HAL_GPIO_ReadPin(config->sda_port, config->sda_pin);HAL_GPIO_WritePin(config->scl_port, config->scl_pin, GPIO_PIN_RESET);i2c_delay();return (ack == GPIO_PIN_RESET); // ACK为低电平
}// 接收一个字节
uint8_t i2c_receive_byte(i2c_pin_config_t *config, bool ack) {uint8_t data = 0;HAL_GPIO_WritePin(config->sda_port, config->sda_pin, GPIO_PIN_SET); // 释放SDAfor(int i = 7; i >= 0; i--) {HAL_GPIO_WritePin(config->scl_port, config->scl_pin, GPIO_PIN_SET);i2c_delay();if(HAL_GPIO_ReadPin(config->sda_port, config->sda_pin) == GPIO_PIN_SET) {data |= (1 << i);}HAL_GPIO_WritePin(config->scl_port, config->scl_pin, GPIO_PIN_RESET);i2c_delay();}// 发送应答位HAL_GPIO_WritePin(config->sda_port, config->sda_pin, ack ? GPIO_PIN_RESET : GPIO_PIN_SET);i2c_delay();HAL_GPIO_WritePin(config->scl_port, config->scl_pin, GPIO_PIN_SET);i2c_delay();HAL_GPIO_WritePin(config->scl_port, config->scl_pin, GPIO_PIN_RESET);i2c_delay();HAL_GPIO_WritePin(config->sda_port, config->sda_pin, GPIO_PIN_SET); // 释放SDAreturn data;
}
I2C设备地址和寻址
设备地址格式
// I2C地址定义
typedef struct {uint8_t address_7bit; // 7位地址uint8_t address_8bit; // 8位地址(包含读写位)bool is_10bit; // 是否为10位地址
} i2c_device_address_t;// 常见I2C设备地址
#define I2C_ADDR_EEPROM_24C02 (0xA0 >> 1) // 1010000
#define I2C_ADDR_EEPROM_24C256 (0xA0 >> 1) // 1010000
#define I2C_ADDR_RTC_DS3231 (0xD0 >> 1) // 1101000
#define I2C_ADDR_MPU6050 (0x68 << 1) // 1101000
#define I2C_ADDR_BMP280 (0x76 << 1) // 1110110
#define I2C_ADDR_OLED_SSD1306 (0x3C << 1) // 0111100// 地址计算工具函数
uint8_t i2c_make_write_address(uint8_t device_addr_7bit) {return (device_addr_7bit << 1) | 0x00; // 写操作
}uint8_t i2c_make_read_address(uint8_t device_addr_7bit) {return (device_addr_7bit << 1) | 0x01; // 读操作
}// 10位地址处理
void i2c_10bit_address_demo(i2c_pin_config_t *config, uint16_t device_addr_10bit) {// 10位地址分两次发送uint8_t first_byte = 0xF0 | ((device_addr_10bit >> 7) & 0x06);uint8_t second_byte = device_addr_10bit & 0xFF;i2c_start(config);i2c_send_byte(config, first_byte); // 11110xx0i2c_send_byte(config, second_byte); // xxxxxxxx// ... 后续数据传输
}
硬件I2C控制器配置
#include "stm32f1xx_hal.h"I2C_HandleTypeDef hi2c1;// I2C初始化配置
void i2c_hardware_init(void) {hi2c1.Instance = I2C1;hi2c1.Init.ClockSpeed = 100000; // 100kHzhi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; // 占空比hi2c1.Init.OwnAddress1 = 0; // 主设备地址(可设为0)hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;hi2c1.Init.OwnAddress2 = 0;hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;if (HAL_I2C_Init(&hi2c1) != HAL_OK) {Error_Handler();}printf("硬件I2C初始化完成,时钟频率: %lu Hz\n", hi2c1.Init.ClockSpeed);
}// I2C MSP初始化(GPIO和时钟配置)
void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c) {GPIO_InitTypeDef gpio_init = {0};if(hi2c->Instance == I2C1) {__HAL_RCC_GPIOB_CLK_ENABLE();__HAL_RCC_I2C1_CLK_ENABLE();// PB6 - I2C1_SCL, PB7 - I2C1_SDAgpio_init.Pin = GPIO_PIN_6 | GPIO_PIN_7;gpio_init.Mode = GPIO_MODE_AF_OD;gpio_init.Pull = GPIO_PULLUP;gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOB, &gpio_init);}
}
常用I2C设备驱动实例
EEPROM 24C02驱动
// 24C02 EEPROM驱动
#define EEPROM_24C02_ADDR (0xA0 >> 1) // 7位地址
#define EEPROM_PAGE_SIZE 8
#define EEPROM_TOTAL_SIZE 256// EEPROM写操作
bool eeprom_write_byte(uint16_t address, uint8_t data) {uint8_t buffer[2];if(address >= EEPROM_TOTAL_SIZE) return false;buffer[0] = (uint8_t)(address & 0xFF); // 地址低8位buffer[1] = data;HAL_StatusTypeDef status = HAL_I2C_Master_Transmit(&hi2c1, EEPROM_24C02_ADDR << 1,buffer, 2, 1000);// EEPROM写周期需要延时if(status == HAL_OK) {HAL_Delay(5); // 24C02写周期最大5msreturn true;}return false;
}// EEPROM读操作
bool eeprom_read_byte(uint16_t address, uint8_t *data) {if(address >= EEPROM_TOTAL_SIZE) return false;// 先发送地址uint8_t addr_byte = (uint8_t)(address & 0xFF);HAL_StatusTypeDef status = HAL_I2C_Master_Transmit(&hi2c1, EEPROM_24C02_ADDR << 1,&addr_byte, 1, 1000);if(status != HAL_OK) return false;// 然后读取数据status = HAL_I2C_Master_Receive(&hi2c1, EEPROM_24C02_ADDR << 1,data, 1, 1000);return (status == HAL_OK);
}// EEPROM页写(更高效)
bool eeprom_write_page(uint16_t start_address, uint8_t *data, uint8_t length) {if(start_address + length > EEPROM_TOTAL_SIZE) return false;if(length > EEPROM_PAGE_SIZE) return false;uint8_t buffer[EEPROM_PAGE_SIZE + 1];buffer[0] = (uint8_t)(start_address & 0xFF);memcpy(&buffer[1], data, length);HAL_StatusTypeDef status = HAL_I2C_Master_Transmit(&hi2c1, EEPROM_24C02_ADDR << 1,buffer, length + 1, 1000);if(status == HAL_OK) {HAL_Delay(5); // 写周期延时return true;}return false;
}
MPU6050陀螺仪驱动
// MPU6050寄存器定义
#define MPU6050_ADDR (0x68 << 1) // 7位地址为0x68
#define MPU6050_WHO_AM_I 0x75
#define MPU6050_PWR_MGMT_1 0x6B
#define MPU6050_ACCEL_XOUT_H 0x3B
#define MPU6050_GYRO_XOUT_H 0x43// MPU6050数据结构
typedef struct {int16_t accel_x;int16_t accel_y;int16_t accel_z;int16_t temp;int16_t gyro_x;int16_t gyro_y;int16_t gyro_z;
} mpu6050_data_t;// MPU6050初始化
bool mpu6050_init(void) {uint8_t data;// 检查设备IDif(HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, MPU6050_WHO_AM_I, I2C_MEMADD_SIZE_8BIT, &data, 1, 1000) != HAL_OK) {return false;}if(data != 0x68) { // MPU6050的WHO_AM_I寄存器值应为0x68printf("MPU6050设备ID错误: 0x%02X\n", data);return false;}// 唤醒设备data = 0x00; // 退出睡眠模式if(HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, MPU6050_PWR_MGMT_1,I2C_MEMADD_SIZE_8BIT, &data, 1, 1000) != HAL_OK) {return false;}printf("MPU6050初始化成功\n");return true;
}// 读取传感器数据
bool mpu6050_read_data(mpu6050_data_t *sensor_data) {uint8_t buffer[14];// 一次性读取所有传感器数据if(HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, MPU6050_ACCEL_XOUT_H,I2C_MEMADD_SIZE_8BIT, buffer, 14, 1000) != HAL_OK) {return false;}// 解析数据(大端序)sensor_data->accel_x = (buffer[0] << 8) | buffer[1];sensor_data->accel_y = (buffer[2] << 8) | buffer[3];sensor_data->accel_z = (buffer[4] << 8) | buffer[5];sensor_data->temp = (buffer[6] << 8) | buffer[7];sensor_data->gyro_x = (buffer[8] << 8) | buffer[9];sensor_data->gyro_y = (buffer[10] << 8) | buffer[11];sensor_data->gyro_z = (buffer[12] << 8) | buffer[13];return true;
}// 温度转换(摄氏度)
float mpu6050_convert_temperature(int16_t temp_raw) {return (temp_raw / 340.0f) + 36.53f;
}
高级特性和错误处理
I2C总线仲裁和时钟同步
// 多主设备冲突检测
typedef enum {I2C_ARBITRATION_WON, // 仲裁获胜I2C_ARBITRATION_LOST, // 仲裁失败I2C_ARBITRATION_ERROR // 仲裁错误
} i2c_arbitration_result_t;// 总线状态监控
typedef struct {uint32_t total_transactions;uint32_t arbitration_lost_count;uint32_t nack_received_count;uint32_t timeout_count;uint32_t bus_error_count;
} i2c_bus_statistics_t;static i2c_bus_statistics_t i2c_stats = {0};// 错误处理函数
void i2c_handle_error(I2C_HandleTypeDef *hi2c) {uint32_t error_code = HAL_I2C_GetError(hi2c);if(error_code & HAL_I2C_ERROR_AF) {i2c_stats.nack_received_count++;printf("I2C错误: 从设备无应答(NACK)\n");}if(error_code & HAL_I2C_ERROR_ARLO) {i2c_stats.arbitration_lost_count++;printf("I2C错误: 仲裁丢失\n");}if(error_code & HAL_I2C_ERROR_BERR) {i2c_stats.bus_error_count++;printf("I2C错误: 总线错误\n");}if(error_code & HAL_I2C_ERROR_TIMEOUT) {i2c_stats.timeout_count++;printf("I2C错误: 超时\n");}// 清除错误标志并重新初始化HAL_I2C_DeInit(hi2c);HAL_Delay(10);HAL_I2C_Init(hi2c);
}
I2C总线扫描工具
// I2C设备扫描
void i2c_scan_devices(void) {printf("开始I2C设备扫描...\n");printf("地址 | 设备\n");printf("-------+-----------------\n");uint8_t found_count = 0;for(uint8_t address = 1; address < 127; address++) {HAL_StatusTypeDef status = HAL_I2C_IsDeviceReady(&hi2c1, address << 1, 3, // 重试次数10); // 超时(ms)if(status == HAL_OK) {printf("0x%02X | ", address);// 常见设备识别switch(address) {case (0x68): printf("MPU6050/MPU9250"); break;case (0x76): printf("BMP280/BME280"); break;case (0x27): printf("LCD/IO扩展器"); break;case (0x50): printf("EEPROM"); break;case (0x57): printf("AT24Cxx EEPROM"); break;case (0x48): printf("PCF8591 ADC"); break;case (0x38): printf("FT6236触摸屏"); break;case (0x1D): printf("MMA8452加速度计"); break;case (0x29): printf("TCS34725颜色传感器"); break;case (0x40): printf("SHT30温湿度"); break;case (0x60): printf("MCP4725 DAC"); break;default: printf("未知设备"); break;}printf("\n");found_count++;}}printf("扫描完成,找到 %d 个设备\n", found_count);
}
实际应用案例
传感器数据采集
// 多传感器数据采集
typedef struct {mpu6050_data_t imu_data;float temperature;float humidity;uint32_t pressure;uint32_t timestamp;
} sensor_fusion_data_t;// 传感器管理器
typedef struct {bool mpu6050_present;bool bmp280_present;bool sht30_present;uint32_t last_read_time;sensor_fusion_data_t current_data;
} sensor_manager_t;bool sensor_manager_init(sensor_manager_t *manager) {// 扫描I2C总线上的传感器i2c_scan_devices();// 初始化各传感器manager->mpu6050_present = mpu6050_init();// manager->bmp280_present = bmp280_init();// manager->sht30_present = sht30_init();printf("传感器初始化状态:\n");printf("MPU6050: %s\n", manager->mpu6050_present ? "就绪" : "未找到");printf("BMP280: %s\n", manager->bmp280_present ? "就绪" : "未找到");printf("SHT30: %s\n", manager->sht30_present ? "就绪" : "未找到");return (manager->mpu6050_present || manager->bmp280_present || manager->sht30_present);
}void sensor_manager_read_all(sensor_manager_t *manager) {manager->timestamp = HAL_GetTick();if(manager->mpu6050_present) {if(mpu6050_read_data(&manager->current_data.imu_data)) {manager->current_data.temperature = mpu6050_convert_temperature(manager->current_data.imu_data.temp);}}// 读取其他传感器...manager->last_read_time = HAL_GetTick();
}void sensor_manager_print_data(sensor_manager_t *manager) {printf("=== 传感器数据 ===\n");printf("时间戳: %lu ms\n", manager->timestamp);if(manager->mpu6050_present) {printf("加速度: X=%6d, Y=%6d, Z=%6d\n", manager->current_data.imu_data.accel_x,manager->current_data.imu_data.accel_y,manager->current_data.imu_data.accel_z);printf("陀螺仪: X=%6d, Y=%6d, Z=%6d\n", manager->current_data.imu_data.gyro_x,manager->current_data.imu_data.gyro_y,manager->current_data.imu_data.gyro_z);printf("温度: %.2f°C\n", manager->current_data.temperature);}
}
总结
I2C通信关键知识点:
总线结构:两线制(SCL、SDA),开漏输出,需要上拉电阻
通信协议:起始条件、停止条件、应答机制
地址寻址:7位地址(常用)和10位地址
速度模式:标准模式(100kbps)、快速模式(400kbps)、高速模式(3.4Mbps)
多主仲裁:时钟同步和总线仲裁机制
嵌入式开发建议:
选择合适的上拉电阻(通常4.7kΩ-10kΩ)
使用硬件I2C提高效率和可靠性
实现完整的错误处理和总线恢复机制
进行设备扫描确认设备连接状态
优化通信时序减少总线占用时间
SPI总线通信
SPI(Serial Peripheral Interface)是一种高速、全双工、同步的串行通信总线,广泛应用于嵌入式系统中连接各种外设。
1. SPI总线基本概念
SPI总线特点
四线制:SCK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)
全双工:同时进行数据发送和接收
同步通信:由主设备提供时钟信号
主从模式:支持一主多从
高速传输:速度可达几十Mbps
无应答机制:没有内置的错误检查机制
总线拓扑结构
SPI总线拓扑:+-----+ +-----+ +-----+| MCU | |设备1 | |设备2 ||(主设备)| |(从设备)| |(从设备)|+-----+ +-----+ +-----+| | |SCK -+-----------+-----------+MOSI -+-----------+-----------+MISO -+-----------+-----------+CS1 -+-----------+CS2 -+-----------------------+
SPI硬件接口和电气特性
电气特性
// SPI电平标准
#define SPI_VOLTAGE_LEVEL_3V3 33
#define SPI_VOLTAGE_LEVEL_5V 50// SPI速度定义
typedef enum {SPI_SPEED_LOW = 0, // < 1MHzSPI_SPEED_STANDARD, // 1-10MHzSPI_SPEED_HIGH, // 10-20MHzSPI_SPEED_VERY_HIGH // > 20MHz
} spi_speed_category_t;// SPI引脚配置结构体
typedef struct {GPIO_TypeDef *sck_port;uint16_t sck_pin;GPIO_TypeDef *miso_port;uint16_t miso_pin;GPIO_TypeDef *mosi_port;uint16_t mosi_pin;GPIO_TypeDef *cs_port;uint16_t cs_pin;
} spi_pin_config_t;
GPIO配置
// STM32 SPI引脚配置
void spi_gpio_init(spi_pin_config_t *config) {GPIO_InitTypeDef gpio_init = {0};// SCK, MOSI, CS 配置为推挽输出gpio_init.Mode = GPIO_MODE_AF_PP;gpio_init.Pull = GPIO_NOPULL;gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;gpio_init.Pin = config->sck_pin;HAL_GPIO_Init(config->sck_port, &gpio_init);gpio_init.Pin = config->mosi_pin;HAL_GPIO_Init(config->mosi_port, &gpio_init);gpio_init.Pin = config->cs_pin;HAL_GPIO_Init(config->cs_port, &gpio_init);// MISO 配置为输入gpio_init.Mode = GPIO_MODE_INPUT;gpio_init.Pull = GPIO_NOPULL;gpio_init.Pin = config->miso_pin;HAL_GPIO_Init(config->miso_port, &gpio_init);// 初始时CS为高电平(不选中)HAL_GPIO_WritePin(config->cs_port, config->cs_pin, GPIO_PIN_SET);printf("SPI GPIO初始化完成\n");
}
3. SPI通信协议详解
时钟极性和相位
SPI有4种工作模式,由CPOL(时钟极性)和CPHA(时钟相位)决定:
// SPI模式定义
typedef enum {SPI_MODE_0 = 0, // CPOL=0, CPHA=0SPI_MODE_1, // CPOL=0, CPHA=1 SPI_MODE_2, // CPOL=1, CPHA=0SPI_MODE_3 // CPOL=1, CPHA=1
} spi_mode_t;// SPI模式详细说明
void spi_mode_explanation(void) {printf("=== SPI工作模式 ===\n");printf("模式0 (CPOL=0, CPHA=0):\n");printf(" - 时钟空闲低电平\n");printf(" - 数据在时钟上升沿采样\n");printf(" - 最常用的模式\n\n");printf("模式1 (CPOL=0, CPHA=1):\n");printf(" - 时钟空闲低电平\n");printf(" - 数据在时钟下降沿采样\n\n");printf("模式2 (CPOL=1, CPHA=0):\n");printf(" - 时钟空闲高电平\n");printf(" - 数据在时钟下降沿采样\n\n");printf("模式3 (CPOL=1, CPHA=1):\n");printf(" - 时钟空闲高电平\n");printf(" - 数据在时钟上升沿采样\n");
}
数据帧格式
// 数据位序定义
typedef enum {SPI_FIRSTBIT_MSB, // 高位在前SPI_FIRSTBIT_LSB // 低位在前
} spi_bit_order_t;// SPI通信时序
typedef struct {uint32_t clock_polarity; // CPOLuint32_t clock_phase; // CPHAuint32_t data_size; // 数据位宽:8位或16位uint32_t bit_order; // 位序:MSB或LSBuint32_t baud_rate_prescaler; // 波特率分频
} spi_timing_config_t;
4. 软件模拟SPI实现
#include "stm32f1xx_hal.h"// SPI延时函数
void spi_delay(void) {for(volatile int i = 0; i < 5; i++); // 调整延时时间控制速度
}// 片选控制
void spi_cs_low(spi_pin_config_t *config) {HAL_GPIO_WritePin(config->cs_port, config->cs_pin, GPIO_PIN_RESET);spi_delay();
}void spi_cs_high(spi_pin_config_t *config) {spi_delay();HAL_GPIO_WritePin(config->cs_port, config->cs_pin, GPIO_PIN_SET);
}// 软件SPI发送接收一个字节(模式0)
uint8_t spi_soft_transfer_mode0(spi_pin_config_t *config, uint8_t data) {uint8_t received = 0;for(int i = 7; i >= 0; i--) {// 设置MOSIHAL_GPIO_WritePin(config->mosi_port, config->mosi_pin, (data & (1 << i)) ? GPIO_PIN_SET : GPIO_PIN_RESET);spi_delay();// 时钟上升沿(从设备采样)HAL_GPIO_WritePin(config->sck_port, config->sck_pin, GPIO_PIN_SET);spi_delay();// 读取MISOreceived <<= 1;if(HAL_GPIO_ReadPin(config->miso_port, config->miso_pin) == GPIO_PIN_SET) {received |= 1;}// 时钟下降沿HAL_GPIO_WritePin(config->sck_port, config->sck_pin, GPIO_PIN_RESET);spi_delay();}return received;
}// 软件SPI发送接收(模式3)
uint8_t spi_soft_transfer_mode3(spi_pin_config_t *config, uint8_t data) {uint8_t received = 0;// 模式3:时钟空闲高电平,上升沿采样HAL_GPIO_WritePin(config->sck_port, config->sck_pin, GPIO_PIN_SET);for(int i = 7; i >= 0; i--) {// 设置MOSIHAL_GPIO_WritePin(config->mosi_port, config->mosi_pin, (data & (1 << i)) ? GPIO_PIN_SET : GPIO_PIN_RESET);spi_delay();// 时钟下降沿HAL_GPIO_WritePin(config->sck_port, config->sck_pin, GPIO_PIN_RESET);spi_delay();// 读取MISO(在下降沿后)received <<= 1;if(HAL_GPIO_ReadPin(config->miso_port, config->miso_pin) == GPIO_PIN_SET) {received |= 1;}// 时钟上升沿(从设备采样)HAL_GPIO_WritePin(config->sck_port, config->sck_pin, GPIO_PIN_SET);spi_delay();}return received;
}// 通用软件SPI传输函数
uint8_t spi_soft_transfer(spi_pin_config_t *config, uint8_t data, spi_mode_t mode) {switch(mode) {case SPI_MODE_0:return spi_soft_transfer_mode0(config, data);case SPI_MODE_3:return spi_soft_transfer_mode3(config, data);// 模式1和2类似实现...default:return spi_soft_transfer_mode0(config, data);}
}
5. 硬件SPI控制器配置
STM32硬件SPI配置
#include "stm32f1xx_hal.h"SPI_HandleTypeDef hspi1;// SPI初始化配置
void spi_hardware_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; // CPOL = 0hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // CPHA = 0hspi1.Init.NSS = SPI_NSS_SOFT; // 软件片选hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64; // 时钟分频hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; // MSB先行hspi1.Init.TIMode = SPI_TIMODE_DISABLE;hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;hspi1.Init.CRCPolynomial = 10;if (HAL_SPI_Init(&hspi1) != HAL_OK) {Error_Handler();}printf("硬件SPI初始化完成\n");
}// SPI MSP初始化(GPIO和时钟配置)
void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi) {GPIO_InitTypeDef gpio_init = {0};if(hspi->Instance == SPI1) {__HAL_RCC_SPI1_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();// PA5-SPI1_SCK, PA6-SPI1_MISO, PA7-SPI1_MOSIgpio_init.Pin = GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7;gpio_init.Mode = GPIO_MODE_AF_PP;gpio_init.Pull = GPIO_NOPULL;gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOA, &gpio_init);}
}// SPI速度配置
void spi_set_speed(SPI_HandleTypeDef *hspi, uint32_t prescaler) {hspi->Init.BaudRatePrescaler = prescaler;HAL_SPI_Init(hspi);
}// 常用波特率分频
void spi_configure_speed(SPI_HandleTypeDef *hspi, spi_speed_category_t speed) {uint32_t prescaler;switch(speed) {case SPI_SPEED_LOW:prescaler = SPI_BAUDRATEPRESCALER_256; // ~280kHz @72MHzbreak;case SPI_SPEED_STANDARD:prescaler = SPI_BAUDRATEPRESCALER_32; // ~2.25MHz @72MHzbreak;case SPI_SPEED_HIGH:prescaler = SPI_BAUDRATEPRESCALER_8; // ~9MHz @72MHzbreak;case SPI_SPEED_VERY_HIGH:prescaler = SPI_BAUDRATEPRESCALER_2; // ~36MHz @72MHzbreak;default:prescaler = SPI_BAUDRATEPRESCALER_64;}spi_set_speed(hspi, prescaler);printf("SPI速度配置为分频: %lu\n", prescaler);
}
6. 常用SPI设备驱动实例
SPI Flash (W25Q128) 驱动
// W25Q128指令集
#define W25Q128_CMD_WRITE_ENABLE 0x06
#define W25Q128_CMD_WRITE_DISABLE 0x04
#define W25Q128_CMD_READ_STATUS_REG1 0x05
#define W25Q128_CMD_PAGE_PROGRAM 0x02
#define W25Q128_CMD_READ_DATA 0x03
#define W25Q128_CMD_SECTOR_ERASE 0x20
#define W25Q128_CMD_CHIP_ERASE 0xC7
#define W25Q128_CMD_POWER_DOWN 0xB9
#define W25Q128_CMD_RELEASE_POWER_DOWN 0xAB
#define W25Q128_CMD_MANUFACTURER_DEVICE_ID 0x90// Flash容量信息
#define W25Q128_PAGE_SIZE 256
#define W25Q128_SECTOR_SIZE 4096
#define W25Q128_BLOCK_SIZE 65536
#define W25Q128_TOTAL_SIZE 16777216 // 16MB// 读取制造商和设备ID
bool w25q128_read_id(uint16_t *manufacturer_id, uint16_t *device_id) {uint8_t cmd = W25Q128_CMD_MANUFACTURER_DEVICE_ID;uint8_t data[4] = {0};spi_cs_low(&spi_config);HAL_SPI_Transmit(&hspi1, &cmd, 1, 1000);HAL_SPI_Receive(&hspi1, data, 4, 1000);spi_cs_high(&spi_config);*manufacturer_id = (data[0] << 8) | data[1];*device_id = (data[2] << 8) | data[3];return true;
}// 写使能
void w25q128_write_enable(void) {uint8_t cmd = W25Q128_CMD_WRITE_ENABLE;spi_cs_low(&spi_config);HAL_SPI_Transmit(&hspi1, &cmd, 1, 1000);spi_cs_high(&spi_config);
}// 读取状态寄存器
uint8_t w25q128_read_status_reg1(void) {uint8_t cmd = W25Q128_CMD_READ_STATUS_REG1;uint8_t status;spi_cs_low(&spi_config);HAL_SPI_Transmit(&hspi1, &cmd, 1, 1000);HAL_SPI_Receive(&hspi1, &status, 1, 1000);spi_cs_high(&spi_config);return status;
}// 等待写操作完成
void w25q128_wait_busy(void) {while(w25q128_read_status_reg1() & 0x01); // 检查BUSY位
}// 页编程(写一页,最多256字节)
bool w25q128_page_program(uint32_t address, uint8_t *data, uint16_t length) {if(length > W25Q128_PAGE_SIZE) return false;uint8_t cmd[4] = {W25Q128_CMD_PAGE_PROGRAM,(address >> 16) & 0xFF,(address >> 8) & 0xFF,address & 0xFF};w25q128_write_enable();spi_cs_low(&spi_config);HAL_SPI_Transmit(&hspi1, cmd, 4, 1000);HAL_SPI_Transmit(&hspi1, data, length, 1000);spi_cs_high(&spi_config);w25q128_wait_busy();return true;
}// 读取数据
bool w25q128_read_data(uint32_t address, uint8_t *data, uint32_t length) {uint8_t cmd[4] = {W25Q128_CMD_READ_DATA,(address >> 16) & 0xFF,(address >> 8) & 0xFF,address & 0xFF};spi_cs_low(&spi_config);HAL_SPI_Transmit(&hspi1, cmd, 4, 1000);HAL_SPI_Receive(&hspi1, data, length, 1000);spi_cs_high(&spi_config);return true;
}// 扇区擦除(4KB)
bool w25q128_sector_erase(uint32_t address) {uint8_t cmd[4] = {W25Q128_CMD_SECTOR_ERASE,(address >> 16) & 0xFF,(address >> 8) & 0xFF,address & 0xFF};w25q128_write_enable();spi_cs_low(&spi_config);HAL_SPI_Transmit(&hspi1, cmd, 4, 1000);spi_cs_high(&spi_config);w25q128_wait_busy();return true;
}// Flash测试函数
void w25q128_test(void) {uint16_t manufacturer_id, device_id;if(w25q128_read_id(&manufacturer_id, &device_id)) {printf("Flash ID: 制造商=0x%04X, 设备=0x%04X\n", manufacturer_id, device_id);}// 测试读写uint8_t write_data[16] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0};uint8_t read_data[16] = {0};// 擦除第一个扇区w25q128_sector_erase(0x000000);// 写入数据if(w25q128_page_program(0x000000, write_data, 8)) {printf("数据写入成功\n");}// 读取数据if(w25q128_read_data(0x000000, read_data, 8)) {printf("读取数据: ");for(int i = 0; i < 8; i++) {printf("%02X ", read_data[i]);}printf("\n");}
}
7. 高级特性和错误处理
SPI DMA传输
// 使用DMA进行SPI传输(提高效率)
void spi_transmit_dma(uint8_t *data, uint16_t size) {spi_cs_low(&spi_config);HAL_SPI_Transmit_DMA(&hspi1, data, size);// 注意:需要在传输完成中断中拉高CS
}void spi_receive_dma(uint8_t *data, uint16_t size) {spi_cs_low(&spi_config);HAL_SPI_Receive_DMA(&hspi1, data, size);
}// DMA传输完成回调
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) {if(hspi->Instance == SPI1) {spi_cs_high(&spi_config);printf("SPI DMA发送完成\n");}
}void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) {if(hspi->Instance == SPI1) {spi_cs_high(&spi_config);printf("SPI DMA接收完成\n");}
}// DMA错误处理
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi) {printf("SPI DMA错误: 0x%08lX\n", HAL_SPI_GetError(hspi));spi_cs_high(&spi_config);
}
SPI错误处理
// SPI错误统计
typedef struct {uint32_t total_transactions;uint32_t dma_errors;uint32_t timeout_errors;uint32_t mode_errors;uint32_t crc_errors;
} spi_error_stats_t;static spi_error_stats_t spi_stats = {0};// 错误处理
void spi_handle_error(SPI_HandleTypeDef *hspi) {uint32_t error_code = HAL_SPI_GetError(hspi);if(error_code & HAL_SPI_ERROR_CRC) {spi_stats.crc_errors++;printf("SPI错误: CRC校验失败\n");}if(error_code & HAL_SPI_ERROR_MODF) {spi_stats.mode_errors++;printf("SPI错误: 模式错误\n");}if(error_code & HAL_SPI_ERROR_OVR) {printf("SPI错误: 溢出错误\n");}if(error_code & HAL_SPI_ERROR_DMA) {spi_stats.dma_errors++;printf("SPI错误: DMA传输错误\n");}if(error_code & HAL_SPI_ERROR_FLAG) {printf("SPI错误: 标志错误\n");}if(error_code & HAL_SPI_ERROR_TIMEOUT) {spi_stats.timeout_errors++;printf("SPI错误: 超时\n");}// 重新初始化SPIHAL_SPI_DeInit(hspi);HAL_Delay(10);HAL_SPI_Init(hspi);
}// SPI性能统计
void spi_performance_report(void) {printf("=== SPI性能统计 ===\n");printf("总事务数: %lu\n", spi_stats.total_transactions);printf("DMA错误: %lu\n", spi_stats.dma_errors);printf("超时错误: %lu\n", spi_stats.timeout_errors);printf("模式错误: %lu\n", spi_stats.mode_errors);printf("CRC错误: %lu\n", spi_stats.crc_errors);if(spi_stats.total_transactions > 0) {float error_rate = (float)(spi_stats.dma_errors + spi_stats.timeout_errors + spi_stats.mode_errors + spi_stats.crc_errors) / spi_stats.total_transactions * 100.0f;printf("错误率: %.2f%%\n", error_rate);}
}
8. 实际应用案例
多从设备SPI系统
// 多从设备SPI管理器
typedef struct {SPI_HandleTypeDef *hspi;GPIO_TypeDef *cs_flash_port;uint16_t cs_flash_pin;GPIO_TypeDef *cs_lcd_port;uint16_t cs_lcd_pin;GPIO_TypeDef *cs_sensor_port;uint16_t cs_sensor_pin;GPIO_TypeDef *cs_sd_port;uint16_t cs_sd_pin;
} spi_multislave_manager_t;// 选择SPI设备
void spi_select_device(spi_multislave_manager_t *manager, uint8_t device) {// 先取消所有片选HAL_GPIO_WritePin(manager->cs_flash_port, manager->cs_flash_pin, GPIO_PIN_SET);HAL_GPIO_WritePin(manager->cs_lcd_port, manager->cs_lcd_pin, GPIO_PIN_SET);HAL_GPIO_WritePin(manager->cs_sensor_port, manager->cs_sensor_pin, GPIO_PIN_SET);HAL_GPIO_WritePin(manager->cs_sd_port, manager->cs_sd_pin, GPIO_PIN_SET);// 选择指定设备switch(device) {case 0: // FlashHAL_GPIO_WritePin(manager->cs_flash_port, manager->cs_flash_pin, GPIO_PIN_RESET);break;case 1: // LCDHAL_GPIO_WritePin(manager->cs_lcd_port, manager->cs_lcd_pin, GPIO_PIN_RESET);break;case 2: // 传感器HAL_GPIO_WritePin(manager->cs_sensor_port, manager->cs_sensor_pin, GPIO_PIN_RESET);break;case 3: // SD卡HAL_GPIO_WritePin(manager->cs_sd_port, manager->cs_sd_pin, GPIO_PIN_RESET);break;}HAL_Delay(1); // 片选稳定时间
}// 多设备数据采集系统
typedef struct {spi_multislave_manager_t spi_manager;bool flash_available;bool lcd_available;bool sensor_available;bool sd_available;
} spi_system_t;bool spi_system_init(spi_system_t *system) {// 初始化SPIspi_hardware_init();// 检测各设备system->flash_available = w25q128_detect();system->lcd_available = lcd_detect();// system->sensor_available = sensor_detect();// system->sd_available = sd_detect();printf("SPI系统初始化完成:\n");printf("Flash: %s\n", system->flash_available ? "就绪" : "未找到");printf("LCD: %s\n", system->lcd_available ? "就绪" : "未找到");printf("传感器: %s\n", system->sensor_available ? "就绪" : "未找到");printf("SD卡: %s\n", system->sd_available ? "就绪" : "未找到");return (system->flash_available || system->lcd_available || system->sensor_available || system->sd_available);
}
总结
SPI通信关键知识点:
总线结构:四线制(SCK、MOSI、MISO、CS),全双工同步通信
工作模式:4种模式(由CPOL和CPHA决定)
通信时序:时钟极性和相位的组合
多从设备:通过片选信号选择从设备
高速传输:相比I2C速度更快,但需要更多引脚
嵌入式开发建议:
根据从设备要求设置正确的SPI模式
使用DMA进行大数据量传输提高效率
注意片选信号的管理,避免总线冲突
实现错误处理和恢复机制
在高速通信时注意信号完整性