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 时钟频率有关,需在开发过程中根据实际情况进行调整。