CH32 - SPI自定义协议的MCU间通信
@
在很多项目应用中,会需要MCU间的通信交互;而SPI作为一种高速、全双工的同步通信总线,是最常用的选择之一;接下来我们就以CH32V305为例,实现一种我们自定义SPI协议的主从机通信;
SPI模式
根据SPI极性(CPOL)和相位(CPHA)的配置,SPI共有四种工作i模式;
CPOL = 0; 表示空闲时时钟信号是低电平
= 1; 表示空闲时时钟信号是高电平
CPHA = 0; 表示从第一个跳变沿开始采样数据
= 1; 表示从第二个跳变沿开始采样数据
自定义SPI协议格式
我们选择SPI模式3(CPOL = 1,CPHA = 1),帧格式选择MSB,通信协议格式如下
命令包
主机先发送两字节的命令包,命令包包括1字节的读写控制指令和1字节的数据包长度;
byte0: 0x77 / 0x99, 写指令/ 读指令
byte1: 0x08, 告诉设备接下来的数据包长度为8字节
数据包
在发送命令包1ms后,主机开时发送写的数据或提供读的时钟,从机开始接收写的数据或发送读的数据;
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);
}
测试验证
总结及程序下载
SPI+DMA的方式能够非常高效灵活地完成MCU间的通信,上面我们分析了主机对从机写数据的过程,其实读数据过程逻辑也是同理的,完整参考程序链接如下: