解决SPI1在发送和接收8位数据时出现接收不同步的问题

news2024/11/15 9:21:23

STM32G474的SPI1工作在主机模式,将SPI1_MISO和SPI1_MOSI连接一起,实现自发自收测试。但是在“使用8位数据自发自收时”时,发现接收数据不是同步的。虽然SPI1初始化正确,但是还需要对SPI1_DR寄存器进行强制转换,否则,就会出现接收不同步。我们知道SPI是一个硬件接口,虽然SPI1_DR寄存器是一个16位寄存器,但是触发事件配置的是1/4个8位就可以触发接收了,应该无需软件进行强制转换才是正确的,但实际使用中,使用强制转换,确实令人无法理解。

如果写成下面这样,总是有点问题:
while ( _HAL_SPI_GET_FLAG( SPI1, SPI_FLAG_TXE ) == RESET  ){ if(delay++>200) return 0; }
//检查指定的SPI标志位设置与否:发送缓存空标志位
delay=0;
printf("TxData=0x%02X   ",TxData);
SPI1->DR=TxData; //通过外设SPIx发送一个数据,写SPI1_DR

while (_HAL_SPI_GET_FLAG(SPI1, SPI_FLAG_RXNE) == RESET){ if(delay++>200)return 0; }
//检查指定的SPI标志位设置与否:接受缓存非空标志位
RxData=SPI1->DR; //返回通过SPIx最近接收的数据,写读SPI1_DR
printf("RxData=0x%02X\r\n",RxData);

这样就会发现接收不同步:
TxData=0x80   RxData=0x80
TxData=0x81  
RxData=0x00
TxData=0x82   RxData=0x81
TxData=0x83  
RxData=0x00
TxData=0x84   RxData=0x82
TxData=0x85  
RxData=0x00
TxData=0x86   RxData=0x83
TxData=0x87   RxData=0x84
TxData=0x88   RxData=0x85
TxData=0x89   RxData=0x86

如果写成下面这样,就没有问题:
while ( _HAL_SPI_GET_FLAG( SPI1, SPI_FLAG_TXE ) == RESET  ){ if(delay++>200) return 0; }
//检查指定的SPI标志位设置与否:发送缓存空标志位
delay=0;
printf("TxData=0x%02X   ",TxData);
*(__IO uint8_t *)&SPI1->DR=TxData; //通过外设SPIx发送一个数据,写SPI1_DR

while (_HAL_SPI_GET_FLAG(SPI1, SPI_FLAG_RXNE) == RESET){ if(delay++>200)return 0; }
//检查指定的SPI标志位设置与否:接受缓存非空标志位
RxData=*(__IO uint8_t *)&SPI1->DR; //返回通过SPIx最近接收的数据,写读SPI1_DR
printf("RxData=0x%02X\r\n",RxData);

这样就可以打印如下:
TxData=0x80   RxData=0x80
TxData=0x81   RxData=0x81
TxData=0x82   RxData=0x82
TxData=0x83   RxData=0x83
TxData=0x84   RxData=0x84
TxData=0x85   RxData=0x85
TxData=0x86   RxData=0x86
TxData=0x87   RxData=0x87
TxData=0x88   RxData=0x88
TxData=0x89   RxData=0x89

 SPI初始化程序如下:

#define HalfWord  0

/** @brief  Check whether the specified SPI flag is set or not.
  * @param  __Instance__ specifies the SPI Handle.
  *         This parameter can be SPI where x: 1, 2, or 3 to select the SPI peripheral.
  * @param  __FLAG__ specifies the flag to check.
  *         This parameter can be one of the following values:
  *            @arg SPI_FLAG_RXNE: Receive buffer not empty flag
  *            @arg SPI_FLAG_TXE: Transmit buffer empty flag
  *            @arg SPI_FLAG_CRCERR: CRC error flag
  *            @arg SPI_FLAG_MODF: Mode fault flag
  *            @arg SPI_FLAG_OVR: Overrun flag
  *            @arg SPI_FLAG_BSY: Busy flag
  *            @arg SPI_FLAG_FRE: Frame format error flag
  *            @arg SPI_FLAG_FTLVL: SPI fifo transmission level
  *            @arg SPI_FLAG_FRLVL: SPI fifo reception level
  * @retval The new state of __FLAG__ (TRUE or FALSE).
  */

#define _HAL_SPI_GET_FLAG(__Instance__, __FLAG__) (( ( (__Instance__)->SR ) & (__FLAG__) ) == (__FLAG__))

#define _HAL_SPI_ENABLE(__Instance__)  SET_BIT((__Instance__)->CR1, SPI_CR1_SPE)
#define _HAL_SPI_DISABLE(__Instance__) CLEAR_BIT((__Instance__)->CR1, SPI_CR1_SPE)

void SPI1_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    SPI_HandleTypeDef hspi1;

    __HAL_RCC_SPI1_CLK_ENABLE();  //使能SPI1外设时钟
    __HAL_RCC_GPIOB_CLK_ENABLE(); //GPIOB时钟使能

    GPIO_InitStruct.Pin = GPIO_PIN_3;            //选择引脚编号为3
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;      //复用推挽模式
//    GPIO_InitStruct.Pull = GPIO_NOPULL;          //引脚上拉和下拉都没有被激活
    GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHz
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;   //PB3映射到SPI1_SCK
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    //根据GPIO_InitStruct结构变量指定的参数初始化GPIOB的外设寄存器
    //配置“SPI1_SCK引脚”

    GPIO_InitStruct.Pin = GPIO_PIN_4;            //选择引脚编号为4
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;      //复用推挽模式
    GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉
//    GPIO_InitStruct.Pull = GPIO_NOPULL;          //引脚上拉和下拉都没有被激活
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHz
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;   //PB4映射到SPI1_MISO
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    //根据GPIO_InitStruct结构变量指定的参数初始化GPIOB的外设寄存器
    //配置“SPI1_MISO引脚”

    GPIO_InitStruct.Pin = GPIO_PIN_5;            //选择引脚编号为5
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;      //复用推挽模式
    GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉
//    GPIO_InitStruct.Pull = GPIO_PULLDOWN;        //设置下拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHz
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;   //PB5映射到SPI1_MOSI
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    //根据GPIO_InitStruct结构变量指定的参数初始化GPIOB的外设寄存器
    //配置“SPI1_MOSI引脚”

  hspi1.Instance = SPI1;   //选择SPI1
    hspi1.Init.Mode = SPI_MODE_MASTER;
    //SPIx_CR1寄存器bit2(MSTR),MSTR=1配置SPI外设为主机
    hspi1.Init.Direction = SPI_DIRECTION_2LINES;
    //SPIx_CR1寄存器bit15(BIDIMODE),BIDIMODE=0选择“双线单向数据模式” 
    hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
    //SPIx_CR1寄存器bit0(CPHA)
    //CPHA=0,表示从SPI_SCK的空闲位开始,第1个边沿用来采集第1个位
    //CPHA=1,表示从SPI_SCK的空闲位开始,第2个边沿用来采集第1个位

    hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
    //SPIx_CR1寄存器bit1(CPOL)
    //CPOL=0,表示SPI_SCK的空闲位为低电平
    //CPOL=1,表示SPI_SCK的空闲位为高电平

    hspi1.Init.NSS = SPI_NSS_SOFT;
    //SPIx_CR1寄存器bit9(SSM),SSM=1,NSS引脚的输入将被替换为来自SSI位的值
    hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
    //SPIx_CR1寄存器bit5(BR[2:0])
    //BR[2:0]=000b,SCK的时钟频率为fPCLK/2
    //BR[2:0]=001b,SCK的时钟频率为fPCLK/4
    //BR[2:0]=010b,SCK的时钟频率为fPCLK/8
    //BR[2:0]=011b,SCK的时钟频率为fPCLK/16
    //BR[2:0]=100b,SCK的时钟频率为fPCLK/32
    //BR[2:0]=101b,SCK的时钟频率为fPCLK/64
    //BR[2:0]=110b,SCK的时钟频率为fPCLK/128
    //BR[2:0]=111b,SCK的时钟频率为fPCLK/256

    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
    //SPIx_CR1寄存器bit7(LSBFIRST)
    //LSBFIRST=0,表示先传送最高位
    //LSBFIRST=1,表示先传送最低位

    hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
    //SPIx_CR1寄存器bit13(CRCEN),CRCEN=0不使能CRC校验
    hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
    //SPIx_CR1寄存器bit11(CRCL)
    //CRCL=0表示CRC的长度为8位
    //CRCL=1表示CRC的长度为16位

    hspi1.Init.CRCPolynomial = 7;
    //SPIx_CRCPR,这个寄存器包含了CRC计算的多项式
    //CRC多项式(0x0007)是该寄存器的默认值。可以根据需要,配置自己的“CRC多项式”。
 

#if (HalfWord == 0U)
    hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
    //SPIx_CR2寄存器bit11:8(DS[3:0])
    //DS[3:0]=0011b,表示SPI传输的一帧的数据长度为4位
    //DS[3:0]=0100b,表示SPI传输的一帧的数据长度为5位
    //DS[3:0]=0101b,表示SPI传输的一帧的数据长度为6位
    //DS[3:0]=0110b,表示SPI传输的一帧的数据长度为7位
    //DS[3:0]=0111b,表示SPI传输的一帧的数据长度为8位,这里读写8位数据
    //DS[3:0]=1000b,表示SPI传输的一帧的数据长度为9位
    //DS[3:0]=1001b,表示SPI传输的一帧的数据长度为10位
    //DS[3:0]=1010b,表示SPI传输的一帧的数据长度为11位
    //DS[3:0]=1011b,表示SPI传输的一帧的数据长度为12位
    //DS[3:0]=1100b,表示SPI传输的一帧的数据长度为13位
    //DS[3:0]=1101b,表示SPI传输的一帧的数据长度为14位
    //DS[3:0]=1110b,表示SPI传输的一帧的数据长度为15位
    //DS[3:0]=1111b,表示SPI传输的一帧的数据长度为16位

#else
  hspi1.Init.DataSize = SPI_DATASIZE_16BIT;//这里读写16位数据
#endif

    hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
    //SPIx_CR2寄存器bit4(FRF)
    //FRF=0,帧格式为SPI Motorola mode,这里使用“Motorola模式”
    //FRF=1,帧格式为SPI TI mode

    hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
    //SPIx_CR2寄存器bit3(NSSP)
    //NSSP=0,NSS引脚无脉冲,这里不使用NSS引脚
    //NSSP=1,NSS引脚能产生脉冲

    HAL_SPI_Init(&hspi1);

    _HAL_SPI_ENABLE(SPI1);
}

//函数功能:SPI1总线读写一个字节
uint8_t SPI1_ReadWriteByte(uint8_t TxData)
{
    uint8_t delay=0;
    uint8_t RxData = 0;
    
  IWDG_Counter_Reload();

 //喂狗,按照IWDG重装载寄存器IWDG_RLR的值重装载IWDG计数器,独立看门狗的溢出周期为6400ms
    while ( _HAL_SPI_GET_FLAG( SPI1, SPI_FLAG_TXE ) == RESET  ){ if(delay++>200) return 0; }
//检查指定的SPI标志位设置与否:发送缓存空标志位
    
  delay=0;
  printf("TxData=0x%02X   ",TxData);
    *( uint8_t *)&SPI1->DR=TxData; //通过外设SPIx发送一个数据,写SPI1_DR

     while (_HAL_SPI_GET_FLAG(SPI1, SPI_FLAG_RXNE) == RESET){ if(delay++>200)return 0; }
//检查指定的SPI标志位设置与否:接受缓存非空标志位

  RxData=*( uint8_t *)&SPI1->DR; //返回通过SPIx最近接收的数据,写读SPI1_DR
  printf("RxData=0x%02X\r\n",RxData);
  return RxData;
}

去掉“__IO修饰符”也可以,说明不是优化的问题。

#define __IO   volatile /*!< defines 'read / write' permissions */

volatile 的作用就是指示编译器不要因优化而省略此指令,必须每次都直接读写其值

问题是解决了,但是为什么这么做,确实有点懵逼。HAL库是这么干的,不参考官方的库,还真不知道怎么搞。若你能解释,请给我留言,或者你有更好的解决方法。若使用16位数据传输,就不要留言,那个是可以的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2146700.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024年网络安全人才发展报告一览

前不久&#xff0c;由信通院、工业和信息化部教育与考试中心、中国网络空间新兴技术安全创新论坛等单位联合发布了《2024网络安全产业人才发展报告》&#xff0c;长达87页的调研报告深度剖析了目前中国网络安全产业人才供需、分布、就业等现状&#xff0c;这里&#xff0c;星尘…

C++ 第三讲:内存管理

C 第三讲&#xff1a;内存管理 1.C内存分布2.内存管理方式2.1C语言内存管理方式2.2C内存管理方式2.2.1new\delete操作内置类型2.2.2new\delete操作自定义类型 3.operator new与operator delete函数4.new和delete实现原理4.1内置类型4.2自定义类型 5.定位new5.1内存池的基本了解…

基于Java的学生档案管理系统的设计与实现

基于springbootvue实现的学生档案管理系统 &#xff08;源码L文ppt&#xff09;4-065 第4章 系统设计 4.1 总体功能设计 学生档案管理系统的总体功能设计包括学生信息管理、课程管理、教师信息管理、成绩管理和系统配置管理。系统将提供用户友好的界面&#xff0c;支…

【网络】传输层协议TCP

TCP协议 TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;是一种面向连接的、可靠的、基于字节流的传输层通信协议&#xff0c;由IETF的RFC 793定义。TCP在IP&#xff08;Internet Protocol&#xff0c;互联网协议&#xff09;网络层上提供…

【Linux】—— muduo网络库的安装配置与使用

muduo网络库编程 Linux环境下Epollpthread线程库 Boost库安装与使用 安装Boost库 下载boost库源码&#xff0c;linux环境解压 tar -zxvf boost_1_69_0.tar.gz 解压完成后&#xff0c;进入该目录&#xff0c;查看内容 运行bootstrap.sh工程编译构建程序 ./bootstrap.sh …

javascript-代码执行原理

js 是解释型语言 js 引擎执行流程 分为两个阶段: 语法分析执行阶段执行阶段涉及的数据结构: 调用栈。处理执行上下文和执行代码内存堆。给对象分配内存任务队列。暂存待执行的任务,分为宏任务队列和微任务队列语法分析 词法分析 > 语法分析 > 代码生成(字节码) …

封装svg图片

前言 项目中有大量svg图片&#xff0c;为了方便引入&#xff0c;所以对svg进行了处理 一、svg是什么&#xff1f; svg是可缩放矢量图形&#xff0c;是一种图片格式 二、使用步骤 1.创建icons文件夹 将icons文件夹放进src中&#xff0c;并创建一个svg文件夹和index.js&…

深入探索迭代器模式的原理与应用

&#x1f3af; 设计模式专栏&#xff0c;持续更新中 欢迎订阅&#xff1a;JAVA实现设计模式 &#x1f6e0;️ 希望小伙伴们一键三连&#xff0c;有问题私信都会回复&#xff0c;或者在评论区直接发言 迭代器模式 &#x1f4bb; 迭代器模式 (Iterator Pattern) 是一种行为设计模…

【LeetCode】每日一题 2024_9_19 最长的字母序连续子字符串的长度(字符串,双指针)

前言 每天和你一起刷 LeetCode 每日一题~ LeetCode 启动&#xff01; 题目&#xff1a;坐上公交的最晚时间 代码与解题思路 func longestContinuousSubstring(s string) (ans int) { // 题目要求&#xff1a; 最长 的 字母序连续子字符串 的长度// 双指针&#xff0c;start …

【学习笔记】线段树分裂

前言 有线段树合并就应该有线段树分裂。它是线段树合并的逆过程。具体的&#xff0c;你需要以权值线段树中第 k 小的数为分界线&#xff0c;把线段树分成两半。 算法流程 和线段树上二分类似。假设原来的线段树为 u&#xff0c;要分裂出线段树 v 记左子树的权值为 val。如果…

CodeMeter助力软件授权与IP保护,保障工业自动化与物联网安全

随着工业自动化的飞速发展&#xff0c;Hilscher的开放工业4.0联盟&#xff08;OI4&#xff09;旗舰店应运而生&#xff0c;将应用商店模式引入工业领域。凭借CodeMeter授权和加密技术的支持&#xff0c;该商店为工业用户提供了一个安全且开放的应用程序和解决方案平台。该平台不…

超声波清洗机哪个品牌好用又实惠?精选业内四款优质清洗机推荐

超声波清洗机作为一种创新的清洁解决方案&#xff0c;凭借其深入微观的清洁效能、简便的操作方式以及对物品的细腻呵护&#xff0c;正逐渐成为广受喜爱的清洁良品。不过&#xff0c;市面上品牌林立、型号多样&#xff0c;价格亦波动不一&#xff0c;这无疑为消费者选购时平添了…

ATT&CK靶机实战系列之vulnstack2

声明: 本文章只是用于网络安全交流与学习&#xff0c;若学者用学到的东西做一些与网络安全不相关的事情&#xff0c;结果均与本人无关&#xff01;&#xff01;&#xff01; 靶场环境: 使用kali作为hacker的攻击机器&#xff0c;来对web pc dc进行攻击。 这里声明一下: 关于…

Cesium billboard 自定义shader实现描边效果

Cesium billboard 自定义shader实现描边效果 uniform sampler2D u_atlas;uniform vec2 dimensions;in vec2 v_textureCoordinates;in vec4 v_pickColor;in vec4 v_color;in float v_splitDirection;void main(){if (v_splitDirection < 0.0 && gl_FragCoord.x > …

一文详解可视化大屏技术在地震监测中的作用!

昨天&#xff0c;安徽合肥市肥东县发生地震&#xff0c;震级达到4.7级&#xff0c;震源深度12千米。这一事件再次提醒我们&#xff0c;地震的威胁无处不在&#xff0c;及时有效的地震预警对于减少灾害损失至关重要。四川省作为地震活动频繁的地区&#xff0c;近年来在地震监测和…

配置环境-keil

配置keil -- 先将keil安装配置好&#xff0c;包括库 一、STM32 -- STM32是意法半导体&#xff08;意大利&#xff09;采用ARM公司设计的内核&#xff0c;设计一系列32位单片机芯片。 1、STM32开发的几种方式 2、STM32寄存器和库函数版本的工程创建 新建文件夹 复制相关文件…

【机器学习(八)】分类和回归任务-因子分解机(Factorization Machines,FM)-Sentosa_DSML社区版

文章目录 一、算法概念二、算法原理&#xff08;一&#xff09; FM表达式&#xff08;二&#xff09;时间复杂度&#xff08;三&#xff09;回归和分类 三、算法优缺点&#xff08;一&#xff09;优点&#xff08;二&#xff09;缺点 四、FM分类任务实现对比&#xff08;一&…

【2024华为杯研究生数学建模竞赛】比赛思路、代码、论文更新中.....

目录 赛中助攻华为杯常用建模算法&#x1f5d2;️&#x1f5d2;️历年优秀论文⭐⭐论文模板1&#xff09;论文模板2&#xff09;基础画图能力 绘图与数据分析软件SPSSPRO 2024研究生数学建模竞赛时间为9月21日&#xff08;周六&#xff09;8:00至9月25日&#xff08;周三&#…

互联网前端之 CSS 常见属性与三层结构

目录 现在互联网前端三层 CSS 常见属性 关注作者微信公众号&#xff0c;开启探索更多 CSS 知识的精彩之旅。在这里&#xff0c;你将收获丰富的CSS专业内容&#xff0c;深入了解这一网页开发语言的奥秘&#xff0c;不断拓展你的知识边界&#xff0c;提升技能水平。快来关注吧&…

对想学习人工智能或者大模型技术从业者的建议,零基础入门到精通,收藏这一篇就够了

“ 技术的价值在于应用&#xff0c;理论与实践相结合才能事半功倍**”** 写这个关于AI技术的公众号也有差不多五个月的时间了&#xff0c;最近一段时间基本上都在保持日更状态&#xff0c;而且写的大部分都是关于大模型技术理论和技术方面的东西。‍‍‍‍‍‍‍‍‍ 然后最近…