·44 分钟
嵌入式通信协议详解:I2C、SPI、UART 与 CAN
嵌入式I2CSPIUARTCAN通信协议嵌入式
通信协议概述
嵌入式系统中,MCU 需要与各种外设进行通信。不同的通信协议适用于不同的应用场景。
1. 协议对比
| 协议 | 线数 | 速率 | 距离 | 特点 | 典型应用 |
|---|---|---|---|---|---|
| UART | 2 | 115200bps | 15m | 异步、简单 | 调试、GPS |
| I2C | 2 | 400Kbps | 1m | 多设备、地址 | 传感器、EEPROM |
| SPI | 4 | 10Mbps | 1m | 高速、全双工 | Flash、LCD |
| CAN | 2 | 1Mbps | 1km | 多主、可靠 | 汽车、工业 |
UART 串口通信
1. UART 原理
code
UART 数据帧格式:
┌─────┬─────────────────────┬─────┬─────┐
│起始位│ 数据位 (8位) │校验位│停止位│
│ 0 │ D0 D1 D2 D3 D4 D5 D6 D7 │ X │ 1 │
└─────┴─────────────────────┴─────┴─────┘
波特率:每秒传输的位数
常用波特率:9600, 19200, 38400, 115200
2. UART 配置
c
// UART 初始化
void UART_Init(uint32_t baudrate) {
// 1. 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2. 配置 GPIO
GPIO_InitTypeDef GPIO_InitStruct;
// TX - PA9
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// RX - PA10
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 3. 配置 UART
USART_InitTypeDef USART_InitStruct;
USART_InitStruct.USART_BaudRate = baudrate;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStruct);
// 4. 使能 UART
USART_Cmd(USART1, ENABLE);
}
3. UART 数据收发
c
// 发送单个字节
void UART_SendByte(uint8_t data) {
USART_SendData(USART1, data);
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
// 接收单个字节
uint8_t UART_ReceiveByte(void) {
while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
return USART_ReceiveData(USART1);
}
// 发送字符串
void UART_SendString(const char *str) {
while(*str) {
UART_SendByte(*str++);
}
}
// printf 重定向
int fputc(int ch, FILE *f) {
UART_SendByte(ch);
return ch;
}
4. UART 中断接收
c
// 中断接收配置
void UART_Interrupt_Init(void) {
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
}
// 接收缓冲区
#define RX_BUFFER_SIZE 256
uint8_t rx_buffer[RX_BUFFER_SIZE];
volatile uint16_t rx_count = 0;
// 中断服务函数
void USART1_IRQHandler(void) {
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
uint8_t data = USART_ReceiveData(USART1);
if(rx_count < RX_BUFFER_SIZE) {
rx_buffer[rx_count++] = data;
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
I2C 通信
1. I2C 原理
code
I2C 总线结构:
VCC
│
┌┴┐
│ │ 4.7KΩ 上拉电阻
└┬┘
│
┌────────┴────────┐
│ SDA (数据线) │
├─────────────────┤
│ SCL (时钟线) │
└─────────────────┘
│
┌────┴────┐
│ 主设备 │
└────┬────┘
│
┌────┴────┐
│ 从设备 │
└─────────┘
I2C 时序:
┌───┐ ┌───┐ ┌───┐ ┌───┐
│ S │ │ A │ │ D │ │ P │
└─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘
│ │ │ │
↓ ↓ ↓ ↓
开始 地址 数据 停止
2. I2C 配置
c
// I2C 初始化
void I2C_Init(void) {
// 1. 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// 2. 配置 GPIO
GPIO_InitTypeDef GPIO_InitStruct;
// SCL - PB6
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
// SDA - PB7
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
// 3. 配置 I2C
I2C_InitTypeDef I2C_InitStruct;
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStruct.I2C_OwnAddress1 = 0x00;
I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStruct.I2C_ClockSpeed = 400000; // 400KHz
I2C_Init(I2C1, &I2C_InitStruct);
// 4. 使能 I2C
I2C_Cmd(I2C1, ENABLE);
}
3. I2C 读写操作
c
// I2C 写单个字节
void I2C_WriteByte(uint8_t addr, uint8_t reg, uint8_t data) {
// 等待总线空闲
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
// 发送起始信号
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
// 发送设备地址
I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
// 发送寄存器地址
I2C_SendData(I2C1, reg);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
// 发送数据
I2C_SendData(I2C1, data);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
// 发送停止信号
I2C_GenerateSTOP(I2C1, ENABLE);
}
// I2C 读单个字节
uint8_t I2C_ReadByte(uint8_t addr, uint8_t reg) {
uint8_t data;
// 等待总线空闲
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
// 发送起始信号
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
// 发送设备地址(写)
I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
// 发送寄存器地址
I2C_SendData(I2C1, reg);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
// 重新发送起始信号
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
// 发送设备地址(读)
I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Receiver);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
// 禁用应答
I2C_AcknowledgeConfig(I2C1, DISABLE);
// 发送停止信号
I2C_GenerateSTOP(I2C1, ENABLE);
// 接收数据
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
data = I2C_ReceiveData(I2C1);
// 重新使能应答
I2C_AcknowledgeConfig(I2C1, ENABLE);
return data;
}
4. I2C 设备驱动示例
c
// MPU6050 陀螺仪驱动
#define MPU6050_ADDR 0xD0
void MPU6050_Init(void) {
// 唤醒 MPU6050
I2C_WriteByte(MPU6050_ADDR, 0x6B, 0x00);
// 配置采样率
I2C_WriteByte(MPU6050_ADDR, 0x19, 0x07); // 1KHz
// 配置低通滤波
I2C_WriteByte(MPU6050_ADDR, 0x1A, 0x06);
// 配置陀螺仪量程
I2C_WriteByte(MPU6050_ADDR, 0x1B, 0x18); // ±2000°/s
// 配置加速度计量程
I2C_WriteByte(MPU6050_ADDR, 0x1C, 0x01); // ±4g
}
// 读取加速度数据
void MPU6050_ReadAccel(int16_t *ax, int16_t *ay, int16_t *az) {
uint8_t data[6];
// 读取 6 个字节
for(int i = 0; i < 6; i++) {
data[i] = I2C_ReadByte(MPU6050_ADDR, 0x3B + i);
}
// 组合数据
*ax = (data[0] << 8) | data[1];
*ay = (data[2] << 8) | data[3];
*az = (data[4] << 8) | data[5];
}
SPI 通信
1. SPI 原理
code
SPI 总线结构:
┌─────────┐ ┌─────────┐
│ 主设备 │ │ 从设备 │
├─────────┤ ├─────────┤
│ MOSI ──┼──────┼── DI │
│ MISO ──┼──────┼── DO │
│ SCK ──┼──────┼── CLK │
│ CS ──┼──────┼── CS │
└─────────┘ └─────────┘
SPI 时钟模式:
CPOL = 0: 空闲时 SCL 为低
CPOL = 1: 空闲时 SCL 为高
CPHA = 0: 第一个边沿采样
CPHA = 1: 第二个边沿采样
2. SPI 配置
c
// SPI 初始化
void SPI_Init(void) {
// 1. 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2. 配置 GPIO
GPIO_InitTypeDef GPIO_InitStruct;
// SCK - PA5
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// MOSI - PA7
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// MISO - PA6
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// CS - PA4
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 3. 配置 SPI
SPI_InitTypeDef SPI_InitStruct;
SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStruct.SPI_CPOL = SPI_CPOL_High;
SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;
SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStruct.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStruct);
// 4. 使能 SPI
SPI_Cmd(SPI1, ENABLE);
}
3. SPI 读写操作
c
// SPI 发送接收单个字节
uint8_t SPI_TransferByte(uint8_t data) {
// 等待发送缓冲区空
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
// 发送数据
SPI_I2S_SendData(SPI1, data);
// 等待接收缓冲区非空
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
// 接收数据
return SPI_I2S_ReceiveData(SPI1);
}
// SPI 发送多个字节
void SPI_SendBuffer(const uint8_t *data, uint16_t len) {
for(uint16_t i = 0; i < len; i++) {
SPI_TransferByte(data[i]);
}
}
// SPI 接收多个字节
void SPI_ReceiveBuffer(uint8_t *data, uint16_t len) {
for(uint16_t i = 0; i < len; i++) {
data[i] = SPI_TransferByte(0xFF);
}
}
4. SPI 设备驱动示例
c
// W25Q128 Flash 驱动
#define W25Q128_CS_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_4)
#define W25Q128_CS_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_4)
// 读取设备 ID
uint16_t W25Q128_ReadID(void) {
uint16_t id;
W25Q128_CS_LOW();
SPI_TransferByte(0x90); // 读取 ID 命令
SPI_TransferByte(0x00);
SPI_TransferByte(0x00);
SPI_TransferByte(0x00);
id = SPI_TransferByte(0xFF) << 8;
id |= SPI_TransferByte(0xFF);
W25Q128_CS_HIGH();
return id;
}
// 读取数据
void W25Q128_ReadData(uint32_t addr, uint8_t *data, uint32_t len) {
W25Q128_CS_LOW();
SPI_TransferByte(0x03); // 读取数据命令
SPI_TransferByte((addr >> 16) & 0xFF);
SPI_TransferByte((addr >> 8) & 0xFF);
SPI_TransferByte(addr & 0xFF);
for(uint32_t i = 0; i < len; i++) {
data[i] = SPI_TransferByte(0xFF);
}
W25Q128_CS_HIGH();
}
// 写入数据(页编程)
void W25Q128_WritePage(uint32_t addr, const uint8_t *data, uint16_t len) {
// 写使能
W25Q128_CS_LOW();
SPI_TransferByte(0x06);
W25Q128_CS_HIGH();
// 页编程
W25Q128_CS_LOW();
SPI_TransferByte(0x02); // 页编程命令
SPI_TransferByte((addr >> 16) & 0xFF);
SPI_TransferByte((addr >> 8) & 0xFF);
SPI_TransferByte(addr & 0xFF);
for(uint16_t i = 0; i < len; i++) {
SPI_TransferByte(data[i]);
}
W25Q128_CS_HIGH();
// 等待写入完成
while(W25Q128_ReadStatus() & 0x01);
}
CAN 通信
1. CAN 原理
code
CAN 总线结构:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 节点1 │ │ 节点2 │ │ 节点3 │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└───────────────┼───────────────┘
│
┌──────┴──────┐
│ 120Ω 终端 │
└─────────────┘
CAN 数据帧格式:
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│ SOF │ ID │ RTR │ IDE │ DLC │数据 │ CRC │ ACK │ EOF │
│ 1位 │11位 │ 1位 │ 1位 │ 4位 │0-8字│15位 │ 2位 │ 7位 │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
2. CAN 配置
c
// CAN 初始化
void CAN_Init(void) {
// 1. 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2. 配置 GPIO
GPIO_InitTypeDef GPIO_InitStruct;
// CAN_TX - PA12
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// CAN_RX - PA11
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 3. 配置 CAN
CAN_InitTypeDef CAN_InitStruct;
CAN_InitStruct.CAN_TTCM = DISABLE;
CAN_InitStruct.CAN_ABOM = DISABLE;
CAN_InitStruct.CAN_AWUM = DISABLE;
CAN_InitStruct.CAN_NART = DISABLE;
CAN_InitStruct.CAN_RFLM = DISABLE;
CAN_InitStruct.CAN_TXFP = DISABLE;
CAN_InitStruct.CAN_Mode = CAN_Mode_Normal;
CAN_InitStruct.CAN_SJW = CAN_SJW_1tq;
CAN_InitStruct.CAN_BS1 = CAN_BS1_3tq;
CAN_InitStruct.CAN_BS2 = CAN_BS2_2tq;
CAN_InitStruct.CAN_Prescaler = 6; // 36MHz / (1+3+2) / 6 = 1Mbps
CAN_Init(CAN1, &CAN_InitStruct);
// 4. 配置过滤器
CAN_FilterInitTypeDef CAN_FilterInitStruct;
CAN_FilterInitStruct.CAN_FilterNumber = 0;
CAN_FilterInitStruct.CAN_FilterMode = CAN_FilterMode_IdMask;
CAN_FilterInitStruct.CAN_FilterScale = CAN_FilterScale_32bit;
CAN_FilterInitStruct.CAN_FilterIdHigh = 0x0000;
CAN_FilterInitStruct.CAN_FilterIdLow = 0x0000;
CAN_FilterInitStruct.CAN_FilterMaskIdHigh = 0x0000;
CAN_FilterInitStruct.CAN_FilterMaskIdLow = 0x0000;
CAN_FilterInitStruct.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
CAN_FilterInitStruct.CAN_FilterActivation = ENABLE;
CAN_FilterInit(&CAN_FilterInitStruct);
}
3. CAN 数据收发
c
// CAN 发送数据
void CAN_SendMessage(uint32_t id, uint8_t *data, uint8_t len) {
CanTxMsg TxMessage;
TxMessage.StdId = id;
TxMessage.ExtId = 0x00;
TxMessage.RTR = CAN_RTR_Data;
TxMessage.IDE = CAN_ID_STD;
TxMessage.DLC = len;
for(uint8_t i = 0; i < len; i++) {
TxMessage.Data[i] = data[i];
}
CAN_Transmit(CAN1, &TxMessage);
}
// CAN 接收数据
void CAN_ReceiveMessage(uint32_t *id, uint8_t *data, uint8_t *len) {
CanRxMsg RxMessage;
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
*id = RxMessage.StdId;
*len = RxMessage.DLC;
for(uint8_t i = 0; i < *len; i++) {
data[i] = RxMessage.Data[i];
}
}
4. CAN 中断接收
c
// CAN 中断配置
void CAN_Interrupt_Init(void) {
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);
}
// 中断服务函数
void USB_LP_CAN1_RX0_IRQHandler(void) {
if(CAN_GetITStatus(CAN1, CAN_IT_FMP0) != RESET) {
uint32_t id;
uint8_t data[8];
uint8_t len;
CAN_ReceiveMessage(&id, data, &len);
// 处理接收到的数据
CAN_ProcessMessage(id, data, len);
CAN_ClearITPendingBit(CAN1, CAN_IT_FMP0);
}
}
协议选择指南
1. 选择标准
code
选择通信协议的考虑因素:
1. 速度需求
- 低速:UART, I2C
- 高速:SPI, CAN
2. 距离需求
- 短距离:I2C, SPI
- 长距离:UART, CAN
3. 设备数量
- 单设备:UART, SPI
- 多设备:I2C, CAN
4. 可靠性需求
- 一般:UART, I2C, SPI
- 高可靠性:CAN
2. 应用场景
| 场景 | 推荐协议 | 原因 |
|---|---|---|
| 调试串口 | UART | 简单、通用 |
| 传感器数据 | I2C | 多设备、低功耗 |
| 高速数据采集 | SPI | 高速、全双工 |
| 汽车电子 | CAN | 可靠、多主 |
| 显示屏 | SPI | 高速、大数据量 |
| 存储器 | SPI | 高速、简单 |
下一篇
接下来我们将深入探讨嵌入式实时操作系统(RTOS),包括任务管理、同步机制和内存管理等内容。