CH32 - SPI自定义协议的MCU间通信

@


在很多项目应用中,会需要MCU间的通信交互;而SPI作为一种高速、全双工的同步通信总线,是最常用的选择之一;接下来我们就以CH32V305为例,实现一种我们自定义SPI协议的主从机通信;

SPI模式

根据SPI极性(CPOL)和相位(CPHA)的配置,SPI共有四种工作i模式;

CPOL = 0; 表示空闲时时钟信号是低电平
     = 1; 表示空闲时时钟信号是高电平
CPHA = 0; 表示从第一个跳变沿开始采样数据
     = 1; 表示从第二个跳变沿开始采样数据

image
image

自定义SPI协议格式

我们选择SPI模式3(CPOL = 1,CPHA = 1),帧格式选择MSB,通信协议格式如下

image

命令包

主机先发送两字节的命令包,命令包包括1字节的读写控制指令和1字节的数据包长度;

byte0: 0x77 / 0x99, 写指令/ 读指令
byte1: 0x08, 告诉设备接下来的数据包长度为8字节

image

数据包

在发送命令包1ms后,主机开时发送写的数据或提供读的时钟,从机开始接收写的数据或发送读的数据;

image

SPI主机端DMA发送

  • 主机发送命令包
uint8_t SPI_Send_CMD(uint8_t cmd, uint8_t len)
{
    Tx_CMD[0] = cmd;
    Tx_CMD[1] = len;

    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE);
    DMA_Rx_Init(DMA1_Channel2, (u32)&SPI1->DATAR, (u32)Rx_CMD, 2);  // 使能接收
    DMA_Cmd(DMA1_Channel2, ENABLE);

    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);
    DMA_Tx_Init(DMA1_Channel3, (u32)&SPI1->DATAR, (u32)Tx_CMD, 2);  // 使能发送
    DMA_Cmd(DMA1_Channel3, ENABLE);

    while(DMA_GetFlagStatus(DMA1_FLAG_TC3) == RESET);
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY)==SET);
    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, DISABLE);
    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, DISABLE);

    return 1;
}
  • 主机发送数据包
uint8_t SPI_Write_Data(uint8_t len)  
{
    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE);
    DMA_Rx_Init(DMA1_Channel2, (u32)&SPI1->DATAR, (u32)Rx_Write_Data, len);  // 使能接收
    DMA_Cmd(DMA1_Channel2, ENABLE);

    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);
    DMA_Tx_Init(DMA1_Channel3, (u32)&SPI1->DATAR, (u32)Tx_Write_Data, len);  // 使能发送
    DMA_Cmd(DMA1_Channel3, ENABLE);

    while(DMA_GetFlagStatus(DMA1_FLAG_TC3) == RESET);
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY)==SET);
    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, DISABLE);
    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, DISABLE);

    return 1;
}

此函数是写过程的数据包发送函数,读过程的数据包时钟发送函数逻辑也一致,先使能SPI的DMA发送和接收,如何配置DMA方向并使能DMA通道,在等待数据发送或接收完成后,关闭SPI的DMA;

SPI从机端DMA接收

  • 从机接收命令包
void SPI_Rev_CMD(void)
{
    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);
    DMA_Tx_Init(DMA1_Channel3, (u32)&SPI1->DATAR, (u32)Tx_CMD, 2);  // 使能发送
    DMA_Cmd(DMA1_Channel3, ENABLE);

    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE);
    DMA_Rx_Init(DMA1_Channel2, (u32)&SPI1->DATAR, (u32)Rx_CMD, 2);  // 使能接收
    DMA_Cmd(DMA1_Channel2, ENABLE);

    while(DMA_GetFlagStatus(DMA1_FLAG_TC2) == RESET);
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY)==SET);
    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, DISABLE);
    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, DISABLE);
}
  • 从机接收数据包
void SPI_Rev_Data(uint8_t len)
{
    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);
    DMA_Tx_Init(DMA1_Channel3, (u32)&SPI1->DATAR, (u32)Tx_Write_Data, len);  // 使能发送
    DMA_Cmd(DMA1_Channel3, ENABLE);

    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE);
    DMA_Rx_Init(DMA1_Channel2, (u32)&SPI1->DATAR, (u32)Rx_Write_Data, len);  // 使能接收
    DMA_Cmd(DMA1_Channel2, ENABLE);

    while(DMA_GetFlagStatus(DMA1_FLAG_TC2) == RESET);
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY)==SET);
    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, DISABLE);
    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, DISABLE);
}

测试验证

image image

image

总结及程序下载

SPI+DMA的方式能够非常高效灵活地完成MCU间的通信,上面我们分析了主机对从机写数据的过程,其实读数据过程逻辑也是同理的,完整参考程序链接如下:

CH32V305RBT6_SPI.zip

posted @ 2024-12-18 17:26  WCH_CH32  阅读(20)  评论(0编辑  收藏  举报