SPI-SPI单线半双工数据收发应用笔记
SPI单线半双工数据收发应用笔记
SPI 接口可以工作在单线半双工模式,即主设备使用 MOSI 引脚,从设备使用 MISO 引脚进行通讯。CH32V203C8T6 芯片内置两路 SPI,使用 SPI1 作为主机,SPI2 作为从机,配合 DMA 完成 SPI 接口的单线半双工通信测试。
查阅应用手册 SPI 章节的寄存器描述,不难发现其关键在于通信过程中正确切换控制寄存器1中 BIDIOE 位。当 BIDIOE 置位时,主机处于发送状态,此时通过 DMA 将所需发送的数据搬运到数据寄存器中,即可完成发送过程。当 BIDIOE 复位时,主机处于接收状态,此时,主机仅通过时钟线持续输出既定频率的时钟信号。
1. SPI_InitTypeDef SPI_InitStructure = {0};
2.
3. RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
4. RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
5.
6. // SPI1 HOST
7. SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx; // 单线半双工发送状态
8. SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // 主机
9. SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
10. SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
11. SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
12. SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 软件片选
13. SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
14. SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_LSB;
15. SPI_InitStructure.SPI_CRCPolynomial = 7;
16. SPI_Init(SPI1, &SPI_InitStructure);
17.
18. // SPI2 SLAVE
19. SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Rx; // 单线半双工接收状态
20. SPI_InitStructure.SPI_Mode = SPI_Mode_Slave; // 从机
21. SPI_InitStructure.SPI_NSS = SPI_NSS_Hard; // 硬件片选
22. SPI_Init(SPI2, &SPI_InitStructure);
23.
24. SPI_Cmd(SPI1, ENABLE);
25. SPI_Cmd(SPI2, ENABLE);
为了能够实现数据正常的接收,在初始化时,先将 SPI1 主机配置为单线发送状态,将 SPI2 从机配置为单线接收状态。
SPI 作为从机时,处于完全被动状态,在片选状态下,只要主机向外输出时钟信号,从机将持续的把数据寄存器中的数据向外移出。这种情况下需要特殊的处理,以保证 SPI 不会开始一次新的传输。为了简化这一操作,SPI2 配置时使用了硬件片选进行控制。
在初始化 DMA 时,应注意不使能 SPI1&2 的发送通道,避免 SPI 从机在未切换完成前进行发送操作。实际测试 DMA 接收通道,并发现存在接收异常的问题。
1. printf("SPI1 Tx...\r\n");
2. GPIO_ResetBits(GPIOA, GPIO_Pin_4);
3. DMA_Cmd(DMA1_Channel3, ENABLE); // 主机(SPI1)数据发送
4. while(DMA_GetFlagStatus(DMA1_FLAG_TC3) == 0); // 等待主机DMA(SPI1)操作完成
5. while(DMA_GetFlagStatus(DMA1_FLAG_TC4) == 0); // 等待从机DMA(SPI2)操作完成
6. GPIO_SetBits(GPIOA, GPIO_Pin_4); // 主机释放片选信号
7.
8. printf("SPI2 Tx...\r\n");
9. SPI2->CTLR1 |= 1<<14; // 从机(SPI2)切换为发送状态
10. /* 先准备好第一个需要发送的数据,等待片选及时钟信号
11. * 避免出现在SPI时钟较高时,数据寄存器未完成更新的问题
12. * */
13. DMA_Cmd(DMA1_Channel5, ENABLE); // 从机(SPI2)数据发送
14. GPIO_ResetBits(GPIOA, GPIO_Pin_4); // 片选
15. SPI1->CTLR1 &= ~(1<<14); // 主机(SPI1)切换为接收状态
16.
17. while(DMA_GetFlagStatus(DMA1_FLAG_TC5) == 0); // 等待从机DMA(SPI2)操作完成
18. // 等待SPI2发送完成
19. __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
20. __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
21. __NOP();__NOP();__NOP();__NOP();
22. GPIO_SetBits(GPIOA, GPIO_Pin_4);
23. while(DMA_GetFlagStatus(DMA1_FLAG_TC2) == 0); // 等待主机DMA(SPI1)操作完成
24. SPI1->CTLR1 |= 1<<14; // 主机(SPI1)切换为发送状态
25. SPI2->CTLR1 &= ~(1<<14); // 从机(SPI2)切换为接收状态
测试过程中,还需要注意 SPI2 作为从机发送数据的过程。应首先将 SPI 从机切换至发送状态,以留出充足的时间,给 DMA 将所需发送的数据搬运到数据寄存器中。
在测试时还使用了 NOP 函数,用于解决从机发送完成最后一包数据后,依然有数据输出的问题,通过在从机 DMA 将数据搬运完成后,添加一段延时,等待 SPI 从机将数据发送完成,此时主机主动释放片选信号,停止从机数据的发送。延时所需的时间与系统主频和 SPI 时钟频率有关,需在开发过程中根据实际情况进行调整。