STM32 | 硬件SPI主从通信

发布者:cwm6269310最新更新时间:2021-08-09 来源: eefocus关键字:STM32  硬件SPI  主从通信 手机看文章 扫描二维码
随时随地手机看文章

例子说明及框图

本例子基于STM32F103ZET6芯片,实现SPI1与SPI2的主从通信。其中SPI1配置为主机,SPI2配置为从机,均配置为全双工模式。硬件连接图:

======001

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


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

从机使用中断接收方式接收数据,把接收到的数据加上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_RxBuf与ucSPI1_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的框图:

======002

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


验证情况

======003

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


关键字:STM32  硬件SPI  主从通信 引用地址:STM32 | 硬件SPI主从通信

上一篇:STM32 | STM32的复用时钟何时开启?
下一篇:STM32 | 两块STM32之间的SPI主从通信实例

推荐阅读最新更新时间:2024-11-08 06:51

STM32入门系列-学习STM32要掌握的内容
STM32芯片架构 STM32F103系列芯片的系统架构如下: STM32芯片基于ARM公司的Cortex-M3内核,由ST公司设计生产,内核与总线矩阵之间有I(指令)、S(系统)、D(数据)三条信号线。内核通过总线矩阵与FLASH、SRAM、外设连接。而外设包括GPIO、USART、I2C、SPI等。 STM32芯片系统结构 STM32F103 系列芯片(不包含互联网型)的系统结构如下: 从上图可以看出,在小容量、中容量和大容量产品中,主系统由以下部分构 成: 四个驱动单元: Cortex-M3 内核 DCode 总线(D-bus) Cortex-M3 内核系统总线(S-bus) 通用 DMA1
[单片机]
<font color='red'>STM32</font>入门系列-学习<font color='red'>STM32</font>要掌握的内容
STM32基础13--直接存储器访问(DMA)
前言 DMA(Direct Memory Access,直接存储器访问) ,它就是字面的意思,直接的内存访问,不需要通过CPU即可对相关地址的内存进行直接访问。 这样子说有点抽象,不太容易理解,但是如果在学51单片机汇编,就知道当我们对某个值进行赋值操作时,是CPU使用MOV指令对某个地址赋值(MOV direct, XXX ,将XXX送入地址中的内存)。DMA的意思就是我们可以不通过CPU执行指令,直接通过DMA硬件进行数据的交互。 咋一看,好像也没啥作用,如果没有DMA在传输大量的数据时,CPU会忙碌的处理数据,没有执行其他指令,就会有系统被卡住了的感觉,所以在传输大量数据时,DMA可以减轻大大CPU的负担。 DM
[单片机]
<font color='red'>STM32</font>基础13--直接存储器访问(DMA)
STM32之六独立看门狗
单片机系统在外界的干扰下会出现程序跑飞的现象导致出现死循环,看门狗电路就是为了避免这种情况的发生,在一定的时间内(通过计数器)没有喂狗信号输入给看门狗则表示MCU出现问题,自动会给处理器发送复位信号,是MCU重新启动,是系统正常运转。 STM32的独立看门狗有内部的专门40KHz低速时钟驱动,即使主时钟发生故障,它仍然有效。看门狗时钟十一个内部RC时钟,并不是准确的40KHz,而是在30~60KHz之间的变化时钟,估算时间的时候以40KHz来计算。 第一步,首先取消寄存器写保护,我们利用库函数的IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);来实现,
[单片机]
<font color='red'>STM32</font>之六独立看门狗
STM32】NVIC中断优先级管理(中断向量表)
STM32F1xx官方资料: 《STM32中文参考手册V10》-第9章 中断和事件 Cortex-M3内核支持256个中断,其中包含了16个内核中断(异常)和240个外部中断,并且具有256级的可编程中断设置。但是,STM32并没有使用CM3内核的全部东西,而是只用了它的一部分。STM32有84个中断,包括16个内核中断(异常)和68个可屏蔽中断,具有16级可编程的中断优先级。而STM32F103系列上面,16个内核中断(异常)不变,而可屏蔽中断只有60个(在107系列才有68个)。 注意一下:CM3的外部中断和STM32的外部中断不是一个概念。CM3:除了内核异常之外的都是外部中断;STM32:外部中断EXTI只有6个
[单片机]
【<font color='red'>STM32</font>】NVIC中断优先级管理(中断向量表)
stm32printf函数调用
一、对工程属性进行配置,详细步骤如下 1、首先要在你的main 文件中 包含“stdio.h” (标准输入输出头文件)。 2、在main文件中重定义 fputc 函数 如下: // 发送数据 int fputc(int ch, FILE *f) { USART_SendData(USART1, (unsigned char) ch);// USART1 可以换成 USART2 等 while (!(USART1- SR & USART_FLAG_TXE)); return (ch); } 这样在使用printf时就会调用自定义的fputc函数,来发送字符。 3、在工程属性的 “Targe
[单片机]
远程修改STM32 TIMER占空比的方案
现在有人有这样一个需求,他使用STM32F429芯片做开发,其中用到32位的TIM2实现3路PWM输出。 另外有上位机跟STM32的UART接口相连,上位机可能不定期地需要通过UART接口给STM32发送新的占空比参数,而且每次都发送3个比较输出通道的参数【12个字节】。 如何快捷地实现这个功能呢?要求收到数据后尽快修改3个比较通道的参数。 前一篇重点介绍了利用DMAFIFO和UART接收事件触发DMA传输实现了3个CCR寄存器的批量修改。 我们不妨就该话题稍作拓展,不再局限于某个STM32系列,而是从整个STM32的资源上考虑当前需求。上次提到3种实现方案,我想借此机会再给大家介绍另外一种实现方案,以拓宽未来解决
[单片机]
远程修改<font color='red'>STM32</font> TIMER占空比的方案
STM32的CRH、CRL、ODR和IDR寄存器的使用总结
一、下载: STM32F103中文参考手册 百度网盘:链接: http://pan.baidu.com/s/1boYtx0b 密码:lwcg 二、CRH和CRL的介绍: CRH和CRL的使用基本相同,CRH用于控制GPIOX(X表示A---G)的高8位(Pin15---Pin8),而CRL用于控制GPIOX(X表示A---G)的低8位(Pin7----Pin0)。 三.CRH、CRL和ODR的使用: 1、 RCC- APB2ENR|=1 2; //使能PORTA时钟 GPIOA- CRH&=0XFFFFFFF0;//清除PA8该位原来的设置 GPIOA- CRH|=0X00000003;//PA8
[单片机]
STM32堆栈空间大小设置
1. 设置堆栈空间大小 在使用STM32编程时,一般情况下我们不会关注堆栈空间的大小,因为在STM32的启动文件中,已经帮我们预先设置好了堆栈空间的大小。如下图所示的启动代码中,Stack栈的大小为:0x400(1024Byte),Heap堆的大小为:0x200(512Byte)。 这也是为什么一个基础的工程编译后,RAM的空间也占用了1.6K左右的原因,因为堆栈的空间均分配在RAM中,可在编译的map文件中查看RAM资源占用的情况。 若工程中使用的局部变量较多,定义的数据长度较大时,若不调整栈的空间大小,则会导致程序出现栈溢出,程序运行结果与预期的不符或程序跑飞。这时我们就需要手动的调整栈的大小。 当工程中使用了
[单片机]
<font color='red'>STM32</font>堆栈空间大小设置
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved