良许Linux教程网 干货合集 STM32硬件SPI主从通信(附代码)

STM32硬件SPI主从通信(附代码)

例子说明及框图

本例子基于STM32F103ZET6芯片(代码工程可在文末获取),实现SPI1与SPI2的主从通信。其中SPI1配置为主机,SPI2配置为从机,均配置为全双工模式。硬件连接图:

image-20231009212921706
image-20231009212921706

其中需要注意的是,SPI协议中的从机(Slave)不能主动地发送数据,而只能被动地应答数据。在本例中,数据的交互过程如下:

  1. 主机(Master)使用查询方式向从机发送数据。
  2. 从机使用中断接收方式接收数据,并将接收到的数据加上0x05后再发送给主机。

从机只有在收到主机发送的数据时,才会发送数据给主机。换句话说,从机是被动地发送数据,也就是主机主动地请求数据。

代码细节

主函数:

int main(void)
{   
    uint8_t i = 0;

    //----------------------------------------------------------------------------------------------- 
    // 上电初始化函数
    SysInit();

    //----------------------------------------------------------------------------------------------- 
    // 主程序
    while (1)
    {
        /* 主机发、收数据 */
        for (i = 0; i return 0;
}

其中,ucSPI1_RxBufucSPI1_TxBuf的定义为:

uint8_t ucSPI1_RxBuf[SPI_BUF_LEN] = {0};
uint8_t ucSPI1_TxBuf[SPI_BUF_LEN] = {0x01, 0x02, 0x03, 0x04, 0x05};

SPI1_ReadWriteByte函数为SPI1的读写函数,其作用是往SPI1发送缓冲区写入数据的同时可以读取SPI1接收缓冲区中的数据,其内部实现为:

uint8_t SPI1_ReadWriteByte(uint8_t TxData)
{                     
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); // 等待发送区空  
    SPI_I2S_SendData(SPI1, TxData);                                 // 通过外设SPI1发送一个

byte数据
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);// 等待接收完一个byte  
    return SPI_I2S_ReceiveData(SPI1);                               // 返回通过SPIx最近接收的

数据          
}

为什么可以这么写呢?看一下SPI的框图:

image-20231009212925276
image-20231009212925276

从框图可看出SPI有 2 个缓冲区,一个用于写入(发送缓冲区),一个用于读取(接收缓冲区)。对数据寄存器执行写操作时,数据将写入发送缓冲区,从数据寄存器执行读取时,将返回接收缓冲区中的值。这样写并不会出现读到的数据等于发送的数据。

SPI2中断函数:

void SPI2_IRQHandler(void)
{
    /* 判断接收缓冲区是否为非空 */
    if (SET == SPI_I2S_GetITStatus(SPI2, SPI_I2S_IT_RXNE))
    {
        ucSPI2_RxBuf[ucSPI2_RxCount] = SPI2->DR;                         /* 读取接收缓冲区数

据 */

        while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);  /* 等待发送区空 */ 
        SPI2->DR = ucSPI2_RxBuf[ucSPI2_RxCount] + 0x05;                  /* 往发送缓冲区填数

据 */

        /* 计数器处理 */
        ucSPI2_RxCount++;
        if (ucSPI2_RxCount > SPI
_BUF_LEN - 1)
        {
            ucSPI2_RxCount = 0;
        }

        /* 清中断标志 */
        SPI_I2S_ClearITPendingBit(SPI2, SPI_I2S_IT_RXNE);
    }
}

从机接收到主机数据后,会加上0x05,再返还给主机。

SPI1初始化函数:

void bsp_SPI1_Init(void)
{
    /* 定义SPI结构体变量 */
    GPIO_InitTypeDef  GPIO_InitStructure;
    SPI_InitTypeDef   SPI_InitStructure;

    /* SPI的IO口和SPI外设打开时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

    /* SPI的IO口设置 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;     // 复用输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* SPI的基本配置 */
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  // 设置SPI单向或者双

向的数据模式:SPI设置为双线双向全双工
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;     // 设置SPI工作模式:设置为主SPI
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 设置SPI的数据大小:SPI发送接收8位帧结构
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;       // 串行同步时钟的空闲状态为高电平
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;      // 串行同步时钟的第二个跳变沿(上升或下降)

数据被采样

    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;         // NSS信号由硬件(NSS管脚)还是软件(使用

SSI位)管理:内部NSS信号有SSI位控制
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;// 定义波特率预分频的

值:波特率预分频值为256
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;// 指定数据传输从MSB位还是LSB位开始:数据传

输从MSB位开始
    SPI_InitStructure.SPI_CRCPolynomial = 7;          // CRC值计算的多项式
    SPI_Init(SPI1, &SPI_InitStructure);               // 根据SPI_InitStruct中指定的参数初始化

外设SPIx寄存器

    SPI_Cmd(SPI1, ENABLE);    // 使能SPI外设
}

SPI1配置为主模式,全双工。

SPI2初始化函数:

void bsp_SPI2_Init(void)
{
    /* 定义SPI结构体变量 */
    GPIO_InitTypeDef  GPIO_InitStructure;
    SPI_InitTypeDef  SPI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    /* SPI的IO口和SPI外设打开时钟 */
    RCC_APB2PeriphClockCmd(    RCC_APB2Periph_GPIOB, ENABLE );
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);

    /* SPI的IO口设置 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //PB13/14/15复用推挽输出 
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    /* SPI的基本配置 */
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  // 设置SPI单向或者双

向的数据模式:SPI设置为双线双向全双工
    SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;      // 设置SPI工作模式:设置为从SPI
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 设置SPI的数据大小:SPI发送接收8位帧结构
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;       // 串行同步时钟的空闲状态为高电平
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;      // 串行同步时钟的第二个跳变沿(上升或下降)

数据被采样
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;         // NSS信号由硬件(NSS管脚)还是软件(使用

SSI位)管理:内部NSS信号有SSI位控制
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;// 定义波特率预分频的值:波特率预分频值为256
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;// 指定数据传输从MSB位还是LSB位开始:数据传

输从MSB位开始
    SPI_InitStructure.SPI_CRCPolynomial = 7;          // CRC值计算的多项式
    SPI_Init(SPI2, &SPI_InitStructure);               // 根据SPI_InitStruct中指定的参数初始化

外设SPIx寄存器

    SPI_I2S_ITConfig(SPI2, SPI_I2S_IT_RXNE, ENABLE);  // 使能接收中断

    SPI_Cmd(SPI2, ENABLE);    // 使能SPI2外设

    /* NVIC中断控制器配置 */
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);         // 中断优先级分组2

    NVIC_InitStructure.NVIC_IRQChannel = SPI2_IRQn;         // SPI2中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1; // 抢占优先级3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;       // 子优先级3
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;         // IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);                         // 根据指定的参数初始化VIC寄存器
}

SPI2配置为从模式,全双工,使能接收中断。

验证情况

image-20231009212930185
image-20231009212930185

可见,与我们前面分析的一致,ucSPI2_RxBuf为从机接收自主机的数据;ucSPI1_RxBuf为主机接收自从机的数据。这里发现ucSPI1_RxBuf的所有数组元素都往后移了一个单位,那是因为主机第一次发送数据给从机的时候,从机并没有数据返还给主机,即此时还没有数据存储在ucSPI1_RxBuf[0]中。

以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !

137e00002230ad9f26e78-265x300
本文由 良许Linux教程网 发布,可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。
良许

作者: 良许

良许,世界500强企业Linux开发工程师,公众号【良许Linux】的作者,全网拥有超30W粉丝。个人标签:创业者,CSDN学院讲师,副业达人,流量玩家,摄影爱好者。
上一篇
下一篇

发表评论

联系我们

联系我们

公众号:良许Linux

在线咨询: QQ交谈

邮箱: yychuyu@163.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部