STM32 | 基于hal的SPI全双工通信问题

 问题描述

最近在进行两个stm32芯片spi全双工通信时发现网上对于这种通信的解答不是很全面,大部分都是基于中断处理去进行数据的收发,在查询了手册后发现可以使用轮询的方式查询标志位去进行数据的收发处理于是有了下文。

基本介绍

本例子基于STM32F407芯片,实现两个芯片间SPI3与SPI3的主从通信。其中SPI3_!配置为主机,SPI3_2配置为从机,均配置为全双工模式。硬件连接图:

  其中,我们需要注意的是,SPI的从机不能主动发送数据,只能应答数据。

  所以主机使用查询方式发送数据给从机。从机也使用轮询的方式接收并返回自定义的数据,把接收到的数据保存到自定义的接收数据的数组中然后再将自定义的数据发送给主机。从机总是在收到主机的数据时,才会发送数据给从机。即从机被动发送数据,也即主机主动申请数据。

最终效果

下面是实现的效果:

配置和讲解

我的spi配置是自己配置的不是cubmax,当然如果要用cubmax进行配置也不是很难,参考就行

/**
 * @brief       SPI3初始化代码
 *   @note      主机模式,8位数据,禁止硬件片选
 * @param       无
 * @retval      无
 */
void spi3_init(void)
{
    SPI3_SPI_CLK_ENABLE(); /* SPI3时钟使能 */

    g_spi3_handler.Instance = SPI3_SPI;                                /* SPI3 */
    g_spi3_handler.Init.Mode = SPI_MODE_MASTER;                        /* 设置SPI工作模式,设置为主模式 */
    g_spi3_handler.Init.Direction = SPI_DIRECTION_2LINES;              /* 设置SPI单向或者双向的数据模式:SPI设置为双线模式 */
    g_spi3_handler.Init.DataSize = SPI_DATASIZE_8BIT;                  /* 设置SPI的数据大小:SPI发送接收16位帧结构 */
    g_spi3_handler.Init.CLKPolarity = SPI_POLARITY_HIGH;               /* 串行同步时钟的空闲状态为高电平 */
    g_spi3_handler.Init.CLKPhase = SPI_PHASE_1EDGE;                    /* 串行同步时钟的第二个跳变沿(上升或下降)数据被采样 */
    g_spi3_handler.Init.NSS = SPI_NSS_SOFT;                            /* NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制 */
    g_spi3_handler.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; /* 定义波特率预分频的值:波特率预分频值为256 */
    g_spi3_handler.Init.FirstBit = SPI_FIRSTBIT_MSB;                   /* 指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始 */
    g_spi3_handler.Init.TIMode = SPI_TIMODE_DISABLE;                   /* 关闭TI模式 */
    g_spi3_handler.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;   /* 关闭硬件CRC校验 */
    g_spi3_handler.Init.CRCPolynomial = 7;                             /* CRC值计算的多项式 */
    HAL_SPI_Init(&g_spi3_handler);                                     /* 初始化 */

    __HAL_SPI_ENABLE(&g_spi3_handler); /* 使能SPI3 */

    spi3_read_write_byte(0XFF); /* 启动传输, 实际上就是产生8个时钟脉冲, 达到清空DR的作用, 非必需 */
}

怕有些小白不是很懂配置我就稍微讲讲一些比较重要的配置:

  1. 首先配置的是spi的主从模式,我这里配置的是主机的所以mode选项我们选择master同理如果是从机配置为slaver即可
  2. 配置spi的单向双向模式,这里因为我要进行两边的数据交互所以这里选择双线模式
  3. 数据大小很好理解,你要传送接收的数据是十六位还是八位
  4. 因为spi时钟是由主机发出,因此此处的配置我们其实任意选择都行,无论是high还是low都可以只是触发是上升触发还是下降触发的区别而已
  5. 然后看边沿触发是第一个还是第二个触发进行数据交换,也比较简单理解
  6. 注意ti的模式我这里是关闭的也就是不用中断的方式

基础配置好了之后我们要理一下spi收发数据的逻辑

首先我们的主机要将片选信号打开 那么我这里配置的是    SPI3_CS(0);                   //打开片选

然后我们调用一个hal库里spi比较常用的一个函数

    HAL_SPI_TransmitReceive(&g_spi3_handler, txdata, rxdata, len, 2000);//发送接收数据

去将数据进行收发处理,注意此处有个很重要的地方,如果只是单纯的打开片选后两边都调用这个函数进行数据交互会出现乱码等各种意想不到的问题,当时项目就是这里卡了一会,后来看了一下spi的寄存器配置发现其实解决办法很简单,我们后面说。那我们讲到寄存器了那我们先来看看spi的寄存器配置

这个图在手册里面很好找到,我们只要看sr寄存器的TXE和RXNE标志即可,详细的意思我们可以打开hal库内的说明就知道了

#define SPI_FLAG_RXNE                   SPI_SR_RXNE   /* SPI status flag: Rx buffer not empty flag       */
#define SPI_FLAG_TXE                    SPI_SR_TXE    /* SPI status flag: Tx buffer empty flag           */

 那么我们再通过hal库内的标志位获取函数去进行标志位的判断将数据缓存区内的数据进行逻辑判断和处理就能将时序理清了

这里我就简单贴一下主机的程序

/*----------------------------------------------spi主机自定义函数------------------------------------------------- */
/**
*  @brief        SPI3主机发送同时接收一组数据
 * @param        rxdata  : 接收数据的数组的指针
 * @param        txdata  : 要发送的数据数组指针
 * @param        len     : 发送接收数据的长度
 * @retval       None
 * @说明:本函数是spi主机发送接收数据函数,具备全双工功能
          打开片选功能后进行主从通信,通过标志位进行数据缓存的判断与交互         
 */

void SPI3_Master_TransmitReceive_Byte(uint8_t *txdata,uint8_t *rxdata, uint8_t len)
{
    SPI3_CS(0);                                                         //打开片选
    HAL_SPI_TransmitReceive(&SPI3_Handler, txdata, rxdata, len, 2000);//发送接收数据
    if(__HAL_SPI_GET_FLAG(&g_spi3_handler, SPI_FLAG_TXE))               //发送缓存区为空
    {            
        if(__HAL_SPI_GET_FLAG(&g_spi3_handler, SPI_FLAG_RXNE))          //接收缓存区不为空时
        {
            /* 当接收缓存区不为空时的操作 */
//            HAL_SPI_Receive(&g_spi3_handler, rxdata, 4, 2000);         // 通过SPI3接收数据
        }
        else
        {
            SPI3_CS(1);                                                  //关闭片选
            printf("%s%s",txdata,rxdata);                                //将发送和接收的数据打印
        }
    }
}

里面都有比较详细的注释这里就不做讲解

从机的代码这里就不贴出来了,实现也是比较简单的

需要注意的是我这里主机是主动发送数据从机才会触发,因此笔者在主函数里面使用了外部触发的方式将自定义的数据通过mosi发送至从机从机收到数据同步发送回传数据到主机。还有个注意的地方由于数据传输并不是简单的直接互传,而是数据交换,因此如果要从从机拿到数据主机就要发一个数据过去才可以,就算是空的也行。

由于笔者自身能力也有限,如果有其他问题也可以直接联系笔者进行交流^^

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以帮你解答关于STM32HAL库的SPI通信问题SPI通信是一种同步的串行通信协议,它使用4根线实现通信,分别是时钟线(SCLK)、主设备输出线(MOSI)、主设备输入线(MISO)和片选线(CS)。下面是一份使用STM32HAL库进行SPI通信的示例代码,你可以参考一下: ```c #include "stm32f4xx_hal.h" // 定义SPI句柄 SPI_HandleTypeDef hspi1; // 定义数据缓冲区 uint8_t tx_buf[10]; uint8_t rx_buf[10]; int main(void) { // 初始化HALHAL_Init(); // 初始化SPI口 hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; // 主机模式 hspi1.Init.Direction = SPI_DIRECTION_2LINES; // 双线全双工 hspi1.Init.DataSize = SPI_DATASIZE_8BIT; // 8位数据位 hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // 时钟极性为低电平 hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // 时钟相位为第一边沿 hspi1.Init.NSS = SPI_NSS_SOFT; // 软件控制片选信号 hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; // 波特率预分频 hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; // MSB优先 hspi1.Init.TIMode = SPI_TIMODE_DISABLE; // 禁用TI模式 hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; // 禁用CRC校验 HAL_SPI_Init(&hspi1); // 发送数据 HAL_SPI_Transmit(&hspi1, tx_buf, 10, 100); // 接收数据 HAL_SPI_Receive(&hspi1, rx_buf, 10, 100); // 同时发送和接收数据 HAL_SPI_TransmitReceive(&hspi1, tx_buf, rx_buf, 10, 100); while (1) { } } ``` 在使用HAL库进行SPI通信时,需要先初始化SPI口并定义一个SPI句柄。然后可以使用HAL_SPI_Transmit、HAL_SPI_Receive和HAL_SPI_TransmitReceive函数分别进行发送、接收和同时发送和接收数据。参数说明如下: - 第一个参数:SPI句柄 - 第二个参数:待发送数据的缓冲区指针 - 第三个参数:待发送/接收数据的长度 - 第四个参数:超时时间(单位为ms) 希望这些信息可以帮助你解决问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值