[MCU开发工具资源区]

(分享)CW32的SPI单工模式主从通信介绍

[复制链接]
124|7
手机看帖
扫描二维码
随时随地手机跟帖
豌豆爹|  楼主 | 2023-3-13 10:32 | 显示全部楼层 |阅读模式
串行外设接口(SPI)是一种同步串行数据通信接口,常用于 MCU 与外部设备之间进行同步串行通信。SPI是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间。CW32L083 内部集成 2 个串行外设 SPI 接口,支持双向全双工、单线半双工和单工通信模式,可配置 MCU 作为 主机或从机,支持多主机通信模式,支持直接内存访问(DMA)。

单工通信模式
SPI 支持单工通信模式,主机和从机通过一根单向数据线进行单发或单收通信。主机使用 MOSI 信号线进行单发通信,使用 MISO 信号线进行单收通信;从机使用 MOSI 信号线进行单收通信, 使用 MISO 信号线进行单发通信,未使用的信号线可供其它功能使用。

主机单发,从机单收应用场景下,连接框图如下:
3.png
主机单发配置:
设置 SPIx_CR1.MODE 为 0x1,SPI 工作于单工单发通信模式;
设置 SPIx_CR1.MSTR 为 1,SPI 工作于主机模式。
设置 SPIx_SSI.SSI 为 0,在从机选择 CS 引脚输出低电平,作为通信起始信号。
当发送缓冲器为空时,即 SPIx_ISR.TXE 标志位为 1,将待发送的一帧数据写入 SPIx_DR 寄存器,数据在同步移位时钟信号的控制下从 MOSI 引脚输出。
当写入最后一帧数据后,必须等待发送缓冲空标志位 SPIx_ISR.TXE 变为 1,同时 SPI 总线忙标志位 SPIx_ISR. BUSY 变为 0,以确保数据发送完毕。然后设置 SPIx_SSI.SSI 为 1,使从机选择 CS 引脚输出高电平,结束本次通信。
从机单收配置:
设置 SPIx_CR1.MODE 为 0x2,SPI 工作于单工单收通信模式;
设置 SPIx_CR1.MSTR 为 0,SPI 工作于从机模式。
当检测到 CS 引脚变为低电平时,从机开始与主机通信。当接收缓冲器非空时,即 SPIx_ISR.RXNE 标志位为 1,表示已经接收完成一帧数据,此时可以读取 SPIx_DR 寄存器。当检测到 CS 引脚变为高电平时,本次通信结束。

主机单收,从机单发应用场景下,连接框图如下:
微信图片_20230313103054.png

具体设置与主机单发和从机单收类似,详情可查看用户手册。

SPI中断
SPI 控制器支持 8 个中断源,当 SPI 中断触发事件发生时,中断标志位会被硬件置位,如果设置了对应的中断使能控制位,将产生中断请求。
2.png

在用户 SPI 中断服务程序中,应查询相关 SPI 中断标志位,以进行相应的处理,在退出中断服务程序之前,要清除该中断标志位,避免重复进入中断程序。

实例演示: SPI单工模式进行主从机通信,主机单发,从机单收。
SPIy(主机)采用中断方式发送TxBuffer缓冲区中的数据,SPIz(从机)采用中断方式接收数据并存储到RxBuffer缓冲区,比较TxBuffer和RxBuffer,如果数据一致LED1亮,否则LED2亮。

1. 配置RCC
void RCC_Configuration(void)
{
    RCC_HSI_Enable(RCC_HSIOSC_DIV2);  //SYSCLK = HSI = 24MHz = HCLK = PCLK
    RCC_AHBPeriphClk_Enable(SPIy_GPIO_CLK | SPIz_GPIO_CLK | RCC_AHB_PERIPH_GPIOC, ENABLE); //外设时钟使能
    SPIy_APBClkENx(SPIy_CLK, ENABLE);
    SPIz_APBClkENx(SPIz_CLK, ENABLE);
}‍

2. 配置GPIO
void GPIO_Configuration(void)
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};
    SPIy_AF_SCK; //SPI SCK MOSI 复用
    SPIy_AF_MOSI;
    SPIz_AF_SCK;
    SPIz_AF_MOSI;
    GPIO_InitStructure.Pins = SPIy_SCK_PIN; //推挽输出
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_Init(SPIy_GPIO, &GPIO_InitStructure);
    GPIO_InitStructure.Pins = SPIy_MOSI_PIN;
    GPIO_Init(SPIy_GPIO, &GPIO_InitStructure);
    GPIO_InitStructure.Pins = SPIz_SCK_PIN; //浮空输入
    GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
    GPIO_Init(SPIz_GPIO, &GPIO_InitStructure);
    GPIO_InitStructure.Pins = SPIz_MOSI_PIN;
    GPIO_Init(SPIz_GPIO, &GPIO_InitStructure);
    GPIO_InitStructure.Pins = GPIO_PIN_3 | GPIO_PIN_2; //PC3 LED1 / PC2 LED2
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_Init(CW_GPIOC, &GPIO_InitStructure);
    PC03_SETLOW();//LED灭
    PC02_SETLOW();
}‍

3. 配置SPI
void SPI_Configuration()
{
    SPI_InitTypeDef SPI_InitStructure = {0};
    SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_TxOnly; // 单工单发
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // 主机模式
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 帧数据长度为8bit
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // 时钟空闲电平为低
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // 第一个边沿采样
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 片选信号由SSI寄存器控制
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; // 波特率为PCLK的8分频
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // 最高有效位 MSB 收发在前
    SPI_InitStructure.SPI_Speed = SPI_Speed_Low; // 低速SPI
    SPI_Init(SPIy, &SPI_InitStructure);
    SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_RxOnly; // 单工单收
    SPI_InitStructure.SPI_Mode = SPI_Mode_Slave; // 从机模式
    SPI_Init(SPIz, &SPI_InitStructure);
    SPI_Cmd(SPIy, ENABLE);
    SPI_Cmd(SPIz, ENABLE);
}‍

4. 配置NVIC中断函数
void NVIC_Configuration(void)
{
    NVIC_SetPriority(SPIy_IRQ, 1); //优先级,无优先级分组
    NVIC_SetPriority(SPIz_IRQ, 0);
    NVIC_EnableIRQ(SPIy_IRQ); //SPI中断使能
    NVIC_EnableIRQ(SPIz_IRQ);
}
void SPI1_IRQHandler(void)//SPI1中断函数
{
    if(SPI_GetITStatus(CW_SPI1, SPI_IT_TXE) != RESET)
    {
        if(TxCounter == BufferSize - 1)
        {
            SPI_ITConfig(CW_SPI1, SPI_IT_TXE, DISABLE);
        }
        SPI_SendData(CW_SPI1, TxBuffer[TxCounter++]);
    }
}
void SPI2_IRQHandler(void) //SPI2中断函数
{
      if(SPI_GetITStatus(CW_SPI2, SPI_IT_RXNE) != RESET)
    {
        RxBuffer[RxCounter++] = SPI_ReceiveData(CW_SPI2);
    }
}‍

5. 比较两个buffers区
TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength)
{
    while(BufferLength--)
    {
        if(*pBuffer1 != *pBuffer2)
        {
            return FAILED;
        }
        pBuffer1++;
        pBuffer2++;
    }
    return PASSED;
}‍

6. 主程序
int32_t main(void)
{
    RCC_Configuration();//配置RCC
    GPIO_Configuration();//配置GPIO
    SPI_Configuration();//配置SPI
    NVIC_Configuration();//配置NVIC
    SPI_ITConfig(SPIz, SPI_IT_RXNE, ENABLE); //使能 SPIz RXNE 中断
    SPI_NSSInternalSoftwareConfig(SPIz, SPI_NSSInternalSoft_Reset); //软件NSS,选中SPIz
    SPI_ITConfig(SPIy, SPI_IT_TXE, ENABLE); //使能 SPIy TXE 中断
    while(RxCounter < BufferSize); //等待收发完成
    SPI_NSSInternalSoftwareConfig(SPIz, SPI_NSSInternalSoft_Set); //释放SPIz
    TransferStatus = Buffercmp(TxBuffer, RxBuffer, BufferSize); //检查收发数据一致性
    if(TransferStatus == PASSED) //PASSED
    {
        PC03_SETHIGH();//LED1亮
    }
    else //FAILED
    {
        PC02_SETHIGH();//LED2亮
    }
    while(1)
    {
    }
}‍

7.实验结果显示:LED1亮起,buffers区数据相等,SPI单工模式主从通讯(中断方式)功能实现。

使用特权

评论回复
tpgf| | 2023-4-10 14:28 | 显示全部楼层
SPI 单工通信模式最大的优点就是节省引脚

使用特权

评论回复
nawu| | 2023-4-10 15:05 | 显示全部楼层
SPI单工模式需要时钟信号吗

使用特权

评论回复
aoyi| | 2023-4-10 15:27 | 显示全部楼层
nawu 发表于 2023-4-10 15:05
SPI单工模式需要时钟信号吗

应该不需要额外的 只要波特率一致就行

使用特权

评论回复
szt1993| | 2023-4-10 15:47 | 显示全部楼层
在SPI主模式下,主设备控制数据的发送和接收,并且还负责时钟信号的生成

使用特权

评论回复
zljiu| | 2023-4-10 15:54 | 显示全部楼层
spi单工模式会不会极大的限制了数据的传输速度啊

使用特权

评论回复
gwsan| | 2023-4-10 16:21 | 显示全部楼层
用io也是可以模拟的吧 只要时序满足要求就行吧

使用特权

评论回复
tfqi| | 2023-4-10 16:31 | 显示全部楼层
gwsan 发表于 2023-4-10 16:21
用io也是可以模拟的吧 只要时序满足要求就行吧

个人认为是可以的额但是确实不如现成的模块省事

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

497

主题

1869

帖子

4

粉丝