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

例子说明及框图

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

其中,我们需要注意的是,SPI的从机不能主动发送数据,只能被动应答数据。本例子的数据交互过程:

  1. 主机使用查询方式发送数据给从机。

  2. 从机使用中断接收方式接收数据,把接收到的数据加上0x05再发送给主机。

从机总是在收到主机的数据时,才会发送数据给从机。即从机被动发送数据,也即主机主动申请数据。

代码细节

主函数:

int main(void)
{   
    uint8_t i = 0;

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

//----------------------------------------------------------------------------------------------- 
    // 主程序
    while (1)
    {
        /* 主机发、收数据 */
        for (i = 0; i < SPI_BUF_LEN; i++)
        {
            ucSPI1_RxBuf[i] = SPI1_ReadWriteByte(ucSPI1_TxBuf[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的框图:

从框图可看出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配置为从模式,全双工,使能接收中断。

验证情况

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

代码获取

(0)

相关推荐

  • STM32系统学习——USART(串口通信)

    串口通信是一种设备间非常常用的串行通行方式,其简单便捷,大部分电子设备都支持. 一.物理层 常用RS-232标准,主要规定了信号的用途.通信接口以及信号的电平标准. "DB9接口" ...

  • 单片机数据通信怎么学?这个工具要用好:串口通信

    刚开始学单片机的你,是不是会因用程序把LED点亮而感到高兴,会因用程序把数码管点亮而感到高兴.这是好事,这也是想继续学习下去的动力. 但是到了与数据相关的实验时,却感觉很难有所进步.有时候,把驱动写好 ...

  • 看完这篇,不要说不懂串口通信!

    一.什么是串口通讯? 串行通讯是指仅用一根接收线和一根发送线就能将数据以位进行传输的一种通讯方式.尽管串行通讯的比按字节传输的并行通信慢,但是串口可以在仅仅使用两根线的情况下就能实现数据的传输. 典型 ...

  • STM32F1系列之常用外设说明

    STM32F1系列之常用外设说明

  • STM32无线通信——nRF24L01通信模块

    不同型号STM32的无线通信--基于一样的nRF24L01芯片模块 在此声明一下全部代码均不允许转发以及在商业上的行为等,-Mannix声明. 本次讲解主要内容 1.实验目的 2.实验硬件 3.芯片模 ...

  • STM32串口通信基本原理

    通信方式 并行通信 传输原理:数据各个位同时传输 优点:速度快(一个引脚传输一个位) 缺点:占用引脚资源多 串行通信 传输原理:数据按位顺序传输 优点:占用引脚资源少(一个引脚都可以) 缺点:速度相对 ...

  • STM32驱动NRF24L01

    前言: 为了方便查看博客,特意申请了一个公众号,附上二维码,有兴趣的朋友可以关注,和我一起讨论学习,一起享受技术,一起成长. 1. 简介 NRF24L01是 nordic 的无线通信芯片,它具有以下特 ...

  • 附源码-终极串口接收(二)

    来源:公众号[鱼鹰谈单片机] 作者:鱼鹰Osprey ID   :emOsprey 前段时间需要写个串口接收程序,一时没找到源码,就想着自己写过一篇文章<终极串口接收方式,极致效率>,看看 ...

  • STM32与串口屏交互(USART HMI)

    一.前期准备 二.串口屏上位机使用方法以及界面设计 三.STM32软件编程 四.单片机发送数据的字符串指令汇总 五.总结 不管是备战电赛还是准备毕设,一块能与单片机交互的屏幕显得尤为重要,相较于传统的 ...

  • 两块STM32之间的SPI主从通信实例(附代码工程)

    最近因为工作需要,要实现控制板之间的 SPI 通讯.两块 STM32 之间的 SPI 通讯平时用的比较少,之前我也没有用过,网上也查了很多资料,没有找到现成的.直接能用的例子.做软件的不就是ctrl+ ...

  • STM32内部FLASH读写操作详解(附代码)

    STM32 芯片内部的 FLASH 存储器,主要用于存储我们代码. 如果内部FLASH存储完我们的代码还有剩余的空间,那么这些剩余的空间我们就可以利用起来,存储一些需要掉电保存的数据. 本文以STM3 ...

  • (8条消息) 9、STM32的PWM的原理与使用(内附代码)

    (8条消息) 9、STM32的PWM的原理与使用(内附代码)

  • 【精品博文】详细解析FPGA与STM32的SPI通信(二)

    一个双肩背包 有多难? 戳一下试试看! →_→ 长摁识别 [主题]:详细解析FPGA与STM32的SPI通信(二) [作者]:LinCoding [声明]:转载.引用,请注明出处 本篇文章承接--详细 ...

  • STM32学习笔记:读写内部Flash(介绍 附代码)

    一.介绍 首先我们需要了解一个内存映射: stm32的flash地址起始于0x0800 0000,结束地址是0x0800 0000加上芯片实际的flash大小,不同的芯片flash大小不同. RAM起 ...

  • 系统设计精选 | 基于FPGA的CAN总线控制器的设计(附代码)

    导读 CAN 总线(Controller Area Network)是控制器局域网的简称,是 20 世纪 80 年代初德国 BOSCH 公司为解决现代汽车中众多的控制与测试仪器之间的数据交换而开发的一 ...

  • 使用VBA,1分钟汇总几百个工作簿的数据,效率就是高(附代码)

    昨天发了一个汇总工作簿数据的PowerQuery教程,有同学就反应,是旧版本的Excel用不了,也有同学反应是WPS,没有这功能,今天我们就来个通用的方法.如果你经常我们需要合并N多工作簿中的数据到一 ...

  • 浅谈踢人下线的设计思路!(附代码实现方案)

    前言 前两天写了一篇文章,主要讲了下java中如何实现踢人下线,原文链接:java中如何踢人下线?封禁某个账号后使其会话立即掉线! 本来只是简单阐述一下踢人下线的业务场景和实现方案,没想到引出那么多大 ...

  • 基于OpenCV的实战:轮廓检测(附代码解析)

    重磅干货,第一时间送达 利用轮廓检测物体可以看到物体的各种颜色,在这种情况下放置在静态和动态物体上.如果是统计图像,则需要将图像加载到程序中,然后使用OpenCV库,以便跟踪对象. 每当在框架中检测到 ...