前言
在 STM32、单片机、机器人和嵌入式项目中,最常见的通信方式主要有 UART 串口、I2C、SPI 和 CAN。它们都属于串行通信,但适用场景差别很大。
本文从通信特点、接线方式、优缺点、典型应用和 STM32 HAL 模板代码几个方面进行总结,适合用于快速复习和项目选型。
一、四种通信方式总览
| 通信方式 | 常用引脚 | 通信类型 | 主从关系 | 典型速率 | 通信距离 | 典型应用 |
|---|---|---|---|---|---|---|
| UART 串口 | TX、RX、GND | 异步串行 | 点对点为主 | 9600、115200、1Mbps | 短到中等 | 调试、蓝牙、GPS、串口屏、上位机 |
| I2C / IIC | SCL、SDA、GND | 同步串行 | 一主多从 | 100k、400k、1MHz | 短距离 | OLED、EEPROM、RTC、低速传感器 |
| SPI | SCK、MOSI、MISO、CS、GND | 同步串行 | 一主多从 | 几 MHz 到几十 MHz | 短距离 | Flash、屏幕、ADC、IMU |
| CAN | CAN_H、CAN_L、GND | 差分总线 | 多主通信 | 125k、250k、500k、1Mbps | 中长距离 | 电机控制、汽车电子、机器人、工业控制 |
简单选择原则:
- 调试、上位机、模块通信:优先选 UART。
- 多个低速传感器、节省引脚:优先选 I2C。
- 高速外设、大量数据传输:优先选 SPI。
- 多节点、强干扰、远距离、可靠通信:优先选 CAN。
二、UART 串口通信
1. 基本特点
UART 是异步串行通信,不需要时钟线,通信双方只需要约定相同的波特率、数据位、停止位和校验位。
常见参数:
- 波特率:9600、115200、500000、1000000
- 数据位:8 bit
- 停止位:1 bit
- 校验位:None / Even / Odd
2. 接线方式
text
设备A TX -> 设备B RX
设备A RX -> 设备B TX
设备A GND -> 设备B GND
注意:TX 和 RX 要交叉连接,GND 必须共地。
3. STM32 HAL 模板代码
c
#include "main.h"
#include <string.h>
extern UART_HandleTypeDef huart1;
void UART_SendString(const char *str)
{
HAL_UART_Transmit(&huart1, (uint8_t *)str, strlen(str), 100);
}
void UART_ReceiveExample(void)
{
uint8_t rx_data = 0;
if (HAL_UART_Receive(&huart1, &rx_data, 1, 100) == HAL_OK)
{
HAL_UART_Transmit(&huart1, &rx_data, 1, 100);
}
}
中断接收模板:
c
#include "main.h"
extern UART_HandleTypeDef huart1;
uint8_t uart_rx_byte;
void UART_StartReceiveIT(void)
{
HAL_UART_Receive_IT(&huart1, &uart_rx_byte, 1);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
HAL_UART_Transmit(&huart1, &uart_rx_byte, 1, 100);
HAL_UART_Receive_IT(&huart1, &uart_rx_byte, 1);
}
}
4. 优缺点
优点:
- 使用简单,调试方便。
- 适合和上位机、串口模块通信。
- 硬件资源占用少。
缺点:
- 常见情况下主要是点对点通信。
- 抗干扰能力一般。
- 双方波特率不一致会乱码。
三、I2C / IIC 通信
1. 基本特点
I2C 是两线同步通信,由 SCL 时钟线和 SDA 数据线组成。一个主机可以挂多个从机,每个从机通过地址区分。
I2C 常见速率:
- 标准模式:100 kHz
- 快速模式:400 kHz
- 高速模式:1 MHz 及以上,具体取决于芯片支持
2. 接线方式
text
主机 SCL -> 从机 SCL
主机 SDA -> 从机 SDA
GND -> GND
SCL 和 SDA 通常需要上拉电阻,一般上拉到 3.3V 或 5V,取决于芯片电平。
3. STM32 HAL 模板代码
以向 I2C 设备寄存器写入和读取为例:
c
#include "main.h"
extern I2C_HandleTypeDef hi2c1;
#define DEV_ADDR (0x68 << 1)
#define REG_ADDR 0x75
void I2C_WriteReg(uint8_t reg, uint8_t data)
{
HAL_I2C_Mem_Write(&hi2c1,
DEV_ADDR,
reg,
I2C_MEMADD_SIZE_8BIT,
&data,
1,
100);
}
uint8_t I2C_ReadReg(uint8_t reg)
{
uint8_t data = 0;
HAL_I2C_Mem_Read(&hi2c1,
DEV_ADDR,
reg,
I2C_MEMADD_SIZE_8BIT,
&data,
1,
100);
return data;
}
扫描 I2C 地址模板:
c
#include "main.h"
#include <stdio.h>
extern I2C_HandleTypeDef hi2c1;
extern UART_HandleTypeDef huart1;
void I2C_Scan(void)
{
char msg[64];
for (uint8_t addr = 1; addr < 127; addr++)
{
if (HAL_I2C_IsDeviceReady(&hi2c1, addr << 1, 2, 10) == HAL_OK)
{
snprintf(msg, sizeof(msg), "I2C device found: 0x%02X\r\n", addr);
HAL_UART_Transmit(&huart1, (uint8_t *)msg, strlen(msg), 100);
}
}
}
4. 优缺点
优点:
- 只占用两根信号线。
- 一个总线上可以挂多个设备。
- 很适合低速传感器。
缺点:
- 距离短。
- 总线速度不高。
- 需要上拉电阻。
- 某个设备异常时可能导致总线被拉死。
四、SPI 通信
1. 基本特点
SPI 是高速同步串行通信,通常由主机提供时钟,从机通过片选信号 CS 选择。
常用信号线:
- SCK:时钟线
- MOSI:主机输出,从机输入
- MISO:主机输入,从机输出
- CS / NSS:片选线
- GND:共地
2. 接线方式
text
主机 SCK -> 从机 SCK
主机 MOSI -> 从机 MOSI
主机 MISO -> 从机 MISO
主机 CS -> 从机 CS
GND -> GND
多个 SPI 从设备通常共用 SCK、MOSI、MISO,每个从设备单独使用一个 CS。
3. STM32 HAL 模板代码
c
#include "main.h"
extern SPI_HandleTypeDef hspi1;
#define SPI_CS_GPIO_Port GPIOA
#define SPI_CS_Pin GPIO_PIN_4
static void SPI_CS_LOW(void)
{
HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, GPIO_PIN_RESET);
}
static void SPI_CS_HIGH(void)
{
HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, GPIO_PIN_SET);
}
void SPI_WriteRead(uint8_t *tx_buf, uint8_t *rx_buf, uint16_t len)
{
SPI_CS_LOW();
HAL_SPI_TransmitReceive(&hspi1, tx_buf, rx_buf, len, 100);
SPI_CS_HIGH();
}
SPI 写寄存器模板:
c
void SPI_WriteReg(uint8_t reg, uint8_t data)
{
uint8_t tx_buf[2];
tx_buf[0] = reg;
tx_buf[1] = data;
SPI_CS_LOW();
HAL_SPI_Transmit(&hspi1, tx_buf, 2, 100);
SPI_CS_HIGH();
}
SPI 读寄存器模板:
c
uint8_t SPI_ReadReg(uint8_t reg)
{
uint8_t tx_buf[2];
uint8_t rx_buf[2];
tx_buf[0] = reg | 0x80;
tx_buf[1] = 0xFF;
SPI_CS_LOW();
HAL_SPI_TransmitReceive(&hspi1, tx_buf, rx_buf, 2, 100);
SPI_CS_HIGH();
return rx_buf[1];
}
说明:不同 SPI 设备的读写位定义不同,reg | 0x80 只是常见写法,具体要看芯片手册。
4. 优缺点
优点:
- 速度快。
- 协议简单。
- 支持全双工通信。
缺点:
- 占用引脚较多。
- 多个从机需要多个 CS。
- 不适合远距离通信。
五、CAN 通信
1. 基本特点
CAN 是一种差分总线通信方式,常用于汽车电子、机器人底盘、电机控制和工业控制。它不是通过设备地址通信,而是通过消息 ID 区分数据类型和优先级。
CAN 的核心特点:
- 多主通信。
- 差分传输,抗干扰能力强。
- 支持仲裁,ID 越小优先级越高。
- 适合多节点总线。
2. 接线方式
text
CAN_H -> CAN_H
CAN_L -> CAN_L
GND -> GND
注意事项:
- MCU 通常需要外接 CAN 收发器,例如 TJA1050、SN65HVD230。
- 总线两端需要各接一个 120Ω 终端电阻。
- CAN_H 和 CAN_L 不要接反。
3. STM32 HAL 模板代码
CAN 启动和滤波器配置:
c
#include "main.h"
extern CAN_HandleTypeDef hcan1;
void CAN_FilterInit(void)
{
CAN_FilterTypeDef filter;
filter.FilterBank = 0;
filter.FilterMode = CAN_FILTERMODE_IDMASK;
filter.FilterScale = CAN_FILTERSCALE_32BIT;
filter.FilterIdHigh = 0x0000;
filter.FilterIdLow = 0x0000;
filter.FilterMaskIdHigh = 0x0000;
filter.FilterMaskIdLow = 0x0000;
filter.FilterFIFOAssignment = CAN_RX_FIFO0;
filter.FilterActivation = ENABLE;
filter.SlaveStartFilterBank = 14;
HAL_CAN_ConfigFilter(&hcan1, &filter);
HAL_CAN_Start(&hcan1);
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
}
CAN 发送模板:
c
void CAN_SendData(uint32_t std_id, uint8_t *data, uint8_t len)
{
CAN_TxHeaderTypeDef tx_header;
uint32_t tx_mailbox;
tx_header.StdId = std_id;
tx_header.ExtId = 0;
tx_header.IDE = CAN_ID_STD;
tx_header.RTR = CAN_RTR_DATA;
tx_header.DLC = len;
tx_header.TransmitGlobalTime = DISABLE;
HAL_CAN_AddTxMessage(&hcan1, &tx_header, data, &tx_mailbox);
}
CAN 接收中断模板:
c
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
CAN_RxHeaderTypeDef rx_header;
uint8_t rx_data[8];
if (hcan->Instance == CAN1)
{
HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, rx_data);
switch (rx_header.StdId)
{
case 0x201:
/* 处理 ID 为 0x201 的数据 */
break;
case 0x202:
/* 处理 ID 为 0x202 的数据 */
break;
default:
break;
}
}
}
4. 优缺点
优点:
- 抗干扰强。
- 适合多节点通信。
- 总线仲裁机制可靠。
- 非常适合电机、车载、机器人底盘等场景。
缺点:
- 配置比 UART、I2C、SPI 复杂。
- 需要 CAN 收发器。
- 波特率、采样点、终端电阻配置错误时容易通信失败。
六、实际项目中的选型建议
1. 连接上位机调试
推荐 UART。
原因是简单、直观,配合 USB 转 TTL 和串口助手即可快速调试。
2. 连接 OLED、EEPROM、RTC、气压计等低速外设
推荐 I2C。
原因是省引脚,一个总线可以挂多个设备。
3. 连接 Flash、TFT 屏幕、高速 ADC、IMU
推荐 SPI。
原因是速度高,时序简单,适合连续读写大量数据。
4. 连接电机控制器、机器人底盘、多个控制节点
推荐 CAN。
原因是抗干扰强,支持多节点,可靠性高。
七、常见问题总结
1. UART 收到乱码
常见原因:
- 波特率不一致。
- TX/RX 接反或没有共地。
- 电平不匹配,例如 3.3V 和 RS232 电平直接连接。
2. I2C 找不到设备
常见原因:
- SDA/SCL 没有上拉电阻。
- 地址写错,注意 HAL 库中地址通常需要左移一位。
- 设备没有供电。
- SDA/SCL 接反。
3. SPI 读出来全是 0x00 或 0xFF
常见原因:
- CS 没有正确拉低。
- SPI 模式 CPOL/CPHA 配错。
- MOSI/MISO 接反。
- 设备读写命令格式不对。
4. CAN 无法通信
常见原因:
- CAN_H 和 CAN_L 接反。
- 没有 120Ω 终端电阻。
- 波特率不一致。
- CAN 收发器没有供电。
- 滤波器配置过严,导致消息被过滤。
八、总结
UART、I2C、SPI、CAN 都是嵌入式开发中非常重要的通信方式:
- UART:简单、常用于调试和模块通信。
- I2C:省引脚、适合低速多外设。
- SPI:速度快、适合高速外设。
- CAN:可靠、抗干扰、适合多节点控制系统。
项目中不要只看"能不能通信",还要考虑通信距离、速率、抗干扰能力、节点数量和硬件成本。掌握这四种通信方式,基本可以覆盖大多数 STM32 和嵌入式项目的外设通信需求。