硬件通信协议

I²C、SPI、UART、USB 是四种常用的硬件通信协议,它们在不同场景下负责计算机(上位机)芯片(下位机)之间的数据传输与控制。

UART

UART通信基于异步传输无需共享时钟信号,双方通过预定义的波特率(每秒比特数(bps))同步数据时序。

引脚

发送端(TX):将并行数据转换为串行比特流,按协议格式发送。
接收端(RX):检测起始位按波特率采样数据位,恢复原始数据。

关键特点

全双工:TX 和 RX 可同时工作(需两线)。
异步:无时钟线,依赖波特率同步。
灵活性:支持多种数据格式和波特率。

代码

import serial

# 配置串口
ser = serial.Serial(
    port='COM3',          # 端口号(Windows)或 '/dev/ttyUSB0'(Linux)
    baudrate=115200,      # 波特率
    bytesize=serial.EIGHTBITS,  # 数据位
    parity=serial.PARITY_NONE,  # 校验位
    stopbits=serial.STOPBITS_ONE,  # 停止位
    timeout=1             # 读取超时(秒)
)

# 发送数据
ser.write(b'Hello from PC!\n')

# 接收数据
while True:
    data = ser.read(10)   # 读取 10 字节
    if data:
        print("Received:", data)

疑问

  1. 停止位的作用?
    • 同步时序、标识帧结束、抗干扰。
    • 异步通信容错:UART双方使用独立时钟源,可能存在微小频率偏差。停止位提供额外时间窗口,允许接收端调整采样点位置,确保正确采样数据位。
    • 恢复空闲电平:停止位强制线路回到高电平(逻辑1),为下一次传输的起始位下降沿提供明确的触发信号。
  2. UART数据位长度为什么是8位?
    UART的数据位长度是可靠性、效率、兼容性、成本 多方博弈的结果。对于大数据量需求,更优方案是分帧传输或换用同步通信协议(如SPI、USB)。
    | 因素 | 过短数据位(如5位) | 适中数据位(如8位) | 过长数据位(如16位) |
    |---------------------|--------------------------|-----------------------------|-----------------------------|
    | 传输效率 | 固定开销占比高 | 平衡开销与容错性 | 单帧效率高,但重传成本增加 |
    | 可靠性 | 时钟偏差影响小 | 误差容忍度适中 | 时钟累积偏差风险显著上升 |
    | 硬件成本 | 简单易实现 | 兼容主流硬件 | 需更大缓冲区、复杂逻辑 |
    | 协议兼容性 | 需适配旧协议 | 行业标准支持 | 破坏现有生态 |
    | 应用场景 | 适合短指令(如电传打字机)| 覆盖绝大多数需求 | 仅少数场景需要,可用分帧替代 |

SPI

SPI(Serial Peripheral Interface,串行外设接口) 是一种高速、全双工、同步的串行通信协议,广泛用于嵌入式系统中微控制器(MCU)与外围设备(如传感器、存储器、显示屏等)之间的短距离通信。

引脚

SPI协议通过4根信号线实现通信:
信号线 方向 作用
SCLK 主 → 从 主设备生成的同步时钟信号。
MOSI 主 → 从 主设备输出数据,从设备输入(Master Out Slave In)。
MISO 从 → 主 从设备输出数据,主设备输入(Master In Slave Out)。
CS/SS 主 → 从 主设备控制从设备的片选信号(低电平有效)。

关键特点

全双工通信。
同步传输:依赖主设备提供的时钟信号(SCLK)同步数据。
主从架构:一个主设备(Master)控制通信,可连接多个从设备(Slave)。
硬件片选:每个从设备需独立的片选信号(CS/SS)进行寻址。
无固定协议格式:数据帧格式灵活(数据位宽、传输顺序可配置)。

代码

// SPI初始化配置(模式0,8位数据,MSB First)
SPI_HandleTypeDef hspi1;

void SPI_Init() {
  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=0
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;        // CPHA=0
  hspi1.Init.NSS = SPI_NSS_SOFT;                // 软件控制CS
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; // 时钟分频
  HAL_SPI_Init(&hspi1);
}

// 发送并接收数据
uint8_t tx_data[] = {0xAA, 0x55};
uint8_t rx_data[2];
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // 拉低CS
HAL_SPI_TransmitReceive(&hspi1, tx_data, rx_data, 2, 100);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);   // 拉高CS

疑问

  1. 时钟信号如何使用的?
    时钟信号定义什么时候采集数据
    时钟极性(CPOL):定义时钟空闲状态的电平。
    CPOL=0:SCLK空闲时为低电平。
    CPOL=1:SCLK空闲时为高电平。
    时钟相位(CPHA):定义数据采样的边沿。
    CPHA=0:在时钟的第一个边沿(上升沿或下降沿)采样数据。
    CPHA=1:在时钟的第二个边沿采样数据。
    模式0(CPOL=0, CPHA=0)
    SCLK: __/¯¯_/¯¯_/¯¯__...
    MOSI: D0 D1 D2 ...
    MISO: D0 D1 D2 ...
    ↑ ↑ ↑ (采样时刻)

  2. 片选信号如何使用的?

  3. 没有固定帧格式吗?为什么不需要起始停止位?

posted @ 2025-03-19 18:38  3yearleft  阅读(163)  评论(0)    收藏  举报