STM32F407ZG开发板学习(4)

  • 串口简介
    • 通信接口
    • USART
      • 接线
      • 电平标准
      • 数据帧
  • 实验:固定帧头帧尾数据传输
    • 需求
    • 最终思路以及思考过程
      • 思路
      • 中断函数程序段长度的问题
      • 缓冲区数据结构的决定
    • 初始化配置
    • 中断服务函数
      • 队列溢出处理
    • 实验结果

串口简介

串口是MCU的重要外部接口,可以实现MCU与外部设备的互相通信,同时在软件开发中可以通过将MCU的数据传输到PC上查看以供程序员进行调试。STM32F407ZGT6最多提供六路串口,有分数波特率发生器、支持 同步 单线通信和半双工单线通讯、支持 LIN 、 支持调制解调器操作、 智能卡协议和 IrDA SIR ENDEC 规范 、具有 DMA 等。
官方文档的介绍:

通信接口


USART:一个全双工通用同步/异步串行收发模块,该接口是一个高度灵活的串行通信设备。
I2C:一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。
SPI:串行外设接口(Serial Peripheral Interface)是一种同步外设接口,它可以使单片机与各种外围设备以串行方式进行通信以交换信息。外围设备包括Flash RAM,网络控制器、LCD显示驱动器、A/D转换器和MCU等。
CAN:控制器域网 (Controller Area Network, CAN),属于总线式串行通信网络。
USB:Universal Serial Bus(通用串行总线)是一个外部总线标准,用于规范电脑与外部设备的连接和通讯。是应用在PC领域的接口技术。

USART

接线

接口通过三个引脚从外部连接到其它设备。
任何USART双向通信均需要至少两个引脚:接收数据输入引脚 (RX) 和发送数据引脚输出 (TX):
RX:接收数据输入引脚就是串行数据输入引脚。过采样技术可区分有效输入数据和噪声,从而用于恢复数据。
TX:发送数据输出引脚。如果关闭发送器,该输出引脚模式由其 I/O 端口配置决定。如果使 能了发送器但没有待发送的数据,则 TX 引脚处于高电平。在单线和智能卡模式下,该 I/O 用于发送和接收数据(USART 电平下,随后在 SW_RX 上接收数据)。


TX和RX两个设备间交叉连接,这两条线的电平是相对于GND的,因此GND也是必须连接的。而VCC可以视情况连接,主要看各设备有无单独电源。若只连接设备1到设备2的TX -> RX,那么通信方式将从全双工变为单工,仅允许设备1发送设备2接收。

电平标准

电平标准用于规定数据1和0的表达方式,主要有以下几种:
TTL电平:3.3/5V 表示1,0V 表示0。
RS232:-3 ~ -15V 表示1,+3 ~ +15V 表示0。
RS485:两线压差 +2 ~ +6V 表示1,-2 ~ -6V 表示0。(差分信号)

数据帧

USART为异步通信,数据帧在传输时需要添加帧头帧尾以检测区分,也就是启动位和停止位,同时需要规定波特率来确定检测电平的时间点以得到正确的数据,既不重复采样也不丢弃数据。
如图,空闲时为高电平,启动位为低电平,检测到下降沿即开始准备接收一帧数据接收完一帧后会有一个停止位,即将电平置回空闲时的高电平。
数据帧有9位长和8位长两种,9位长通常是8bit的数据加上1bit的奇偶校验位,而8位长通常不加校验位。

实验:固定帧头帧尾数据传输

需求

  1. 从PC输入数据,串口识别相应数据帧接收后发送回PC打印查看。
  2. 数据帧帧头0xEE,帧尾0xFF。

最终思路以及思考过程

思路

接收时中断,判断是否有效,有效则存入缓冲区,存入完整一帧后,在主循环打印。

中断函数程序段长度的问题

在进行最开始的中断函数的设计时,我将打印数据的操作放在了中断函数之中,这意味着当接收到0xFF时,需要将缓冲区的完整一帧数据在此次中断完成打印,并清空缓冲区。这将导致中断函数运行的时间大大增加且分配不均,而在调试过程中还出现了在一次性输入多条数据帧时会有数据丢失的诡异现象。
至今我也没有找到数据丢失这个问题原因,先认为是中断函数不宜有过长的程序段以及复杂的操作。
于是转而把打印与存数据的操作分开来,中断函数中仅完成存数的操作。

缓冲区数据结构的决定

  1. 一个数组,这种方式最为简单,将接收到的所有数据都存入缓冲区,这样中断函数甚至不需要判断帧头帧尾,但个人认为,当一条长数据帧的帧头不幸丢失,这会导致缓冲区极大的存储空间浪费。
  2. 一个数组、一个数据帧计数量、一个打印游标,在中断函数中实现帧头帧尾检测,只存入有用的数据,然后以0xFF作为每帧结尾存入缓冲区,这就解决了上面那种缓冲区浪费的情况。而且帧头帧尾判断也不过是对接收到的数据的if else判断是否存入缓冲区,实际上每次进入中断函数并不会有大量的代码段操作,它只会根据情况执行一些设置标志位和存数据的操作。
  3. 上一点的升级版——循环队列。 根据前两点的思考,又考虑到了新的一点。就是当数据帧计数量减小,打印游标往后移动,前面的数据不断打印出后,实际上就已经不再需要了,但是前面的空间却还是在被占用着。想要循环利用前面的这片空间,不由得想到循环队列这种数据结构。因为入队是在队尾,出队是在队头,这恰好与我们打印与接收的时序是一致的,在打印时只需要移动Front,在存数据时也只需要移动Rear,打印时的pop操作也将前面的空间“释放”出来以循环利用。

  1. 歪想法——链表。 循环队列的想法已经让我认为比较合理了,后续就是考虑溢出时的操作。而我个人又比较怕麻烦,就想能不能考虑一种不需要考虑溢出,即动态分配内存的方式来存数据呢。因为从需求来看,根本不需要随机访问,那么链表的数据结构看起来是一个不错的选择,它可以动态分配内存,数据帧有多少我就分多少来存,打印时只需要判断链表是否为空依次打印。然而,STM32的MCU是32位的,这代表一个地址需要4B的存储空间,链表中next指针的存在使得我存一个1B的数据反而多用了4B的开销!!! 再者,在STM32使用C标准库的malloc等动态分配内存,将导致本就不富裕的内存多了很多无法使用的碎片,这是很恐怖的。因此,存数据这种操作,尽量别用链表,别动态分配内存。

因此,最后选择了循环队列的方式来作为缓冲区。

初始化配置

配置串口的主要步骤如下:

  1. 使能相应引脚所在GPIO的时钟和串口时钟。
  2. GPIO相应引脚复用为USART,使用 GPIO_PinAF_Config 函数。
  3. 用 GPIO_InitTypeDef 初始化GPIO。
  4. 用USART_InitTypeDef 初始化相应串口,包括波特率、流控制、校验位、数据长度、停止位的大小(0.5 / 1 / 1.5 / 2)等。
  5. 开启需要的中断并配置中断等级(NVIC_InitTypeDef),仅需要中断才配置。
  6. 使能串口,编写中断服务函数 USARTx_IQRHandler 。

如图选择USART3,我的开发板是复用GPIOB的PB10和PB11

 GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;//使能USART3和GPIOB的时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//复用引脚设置GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_USART3); //TXGPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_USART3); //RX//USART3 TX RXGPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //复用推挽输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; //PB10 PB11GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //50MHzGPIO_Init(GPIOB, &GPIO_InitStructure);//USART1 InitUSART_InitStructure.USART_BaudRate = 115200; //波特率USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //流控制USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式:发送/接收,可以用|连接USART_InitStructure.USART_Parity = USART_Parity_No; //校验USART_InitStructure.USART_StopBits = USART_StopBits_1; //终止符位数USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长,未选校验取8位USART_Init(USART3, &USART_InitStructure);USART_Cmd(USART3, ENABLE);USART_ClearFlag(USART3, USART_FLAG_TC);//配置中断USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); //接收中断//NVIC配置NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStructure);

中断服务函数

首先贴一下在全局设置的缓冲区和标志位。
缓冲区设置为循环队列,方便循环使用存储空间无需等存满后再整个清除。

uint8_t USART_RX_BUF[USART_REC_LEN]; //缓冲区,循环队列
uint8_t FRAME_NUM = 0; //有效帧计数
uint8_t Front = 0;
uint8_t Rear = 0;

标志位,主要用于标记是否检测到0xEE,即是否进入一帧。

//接收状态
//bit 1,      是否已经接收到0xEE开头符
//bit 0,      队列溢出的标志
uint8_t USART_RX_STA = 0; //接收状态标记

函数中逻辑比较简单,每当DR中接收到数据,RXNE置1代表数据接收器中有数据了,需要中断处理。

代码如下,add_buf 函数是循环队列基本的入队操作,不再赘述。

     if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET){Res = USART_ReceiveData(USART3);//(USART3->DR);  //读取接收到的数据if(Res != 0xEE) //检测到非开头符{if(USART_RX_STA & 0x02) //接收过0xEE{if(Res != 0xFF){add_buf(Res);}else //检测到结束符{add_buf(0xFF);USART_RX_STA &= 0xFD; //高标志位置0FRAME_NUM++;}}//没接收过0xEE不用操作}else //检测到开头符{if(USART_RX_STA & 0x02) //非首次检测到0xEE{add_buf(Res);}else //首次则置1标志位{USART_RX_STA |= 0x02; //高标志位置1}}}

如此一来,主循环的逻辑显而易见,即当 FRAME_NUM > 0 时打印数据直到遇到0xFF,最后再使FRAME_NUM自减,即可打印完整的一帧。代码较简单,用到 pop_buf 出队函数。

     if(FRAME_NUM > 0) //pop操作仅出现在此处{temp_data = pop_buf();while(temp_data != 0xFF){USART_SendData(USART3, temp_data);while(USART_GetFlagStatus(USART3,USART_FLAG_TC)!=SET);temp_data = pop_buf();}printf("\r\n\r\n");FRAME_NUM--;}

队列溢出处理

队列溢出处理一开始想直接清空队列,令Front = Rear,但是由于FRAME_NUM的存在,这会导致主循环仍然继续打印之前存入的完整的帧并出队数据,导致Front的错乱。
因此最后选择的方法是在中断函数判断是否溢出,溢出则代表缓冲区最后面存入了半帧数据,没有0xFF结尾符,则清掉这一半帧即可。

     //判断队列是否溢出if(full_buf()){//循环队列牺牲一个字节用于队满判断printf("溢出,清除最后一帧的数据,请检查数据长度");printf("\r\n\r\n");/** 在一次性输入多条数据帧且最后一条导致缓冲区溢出时,* 直接清空缓冲区,会使得FRAME_NUM非零导致主循环打* 印一些不期望的数据,且令Front和Rear错乱。*///Rear = Front;//只清掉未发完的那一帧cpy_rear = (Rear - 1) % USART_REC_LEN;while(cpy_rear != Front && USART_RX_BUF[cpy_rear] != 0xFF){cpy_rear = (cpy_rear - 1) % USART_REC_LEN;}//改变rearif(USART_RX_BUF[cpy_rear] == 0xFF)Rear = (cpy_rear + 1) % USART_REC_LEN;if(cpy_rear == Front)Rear = Front;USART_RX_STA = 0x00;}

实验结果

由实验结果来看,一次性输入多条数据帧也是可以正确打印的。
输入:ff 41 ee 41 ff ee 42 43 ff 44 ee 45 46 ff 47 48 ee 49 ff 50 51
期望输出:(16进制下GBK编码,0x41表示A)
(帧1)41
(帧2)42 43
(帧3)45 46
(帧4)49

溢出时的结果:(为了方便缓冲区大小设置为了5)
即使溢出,也还是可以把已经存入的完整帧打印出来。

。。。刚刚调试的时候又发现了溢出相关新的问题,就是如果上面的输入再多几个字节,那么有可能会触发多次溢出处理,导致多删掉几帧。那么就应该再设置一个是否处理过溢出的标志位予以识别。累了,具体后面再更新吧。
更新:目前想的新策略是在一段时间内只丢弃数据,等待一部分帧打印后,这时缓冲区要么被一个半帧填满,要么并没有满,但是有半帧在最后的位置没有清除,这时再清除这个半帧即可。

STM32F407ZG 串口通信+固定帧头帧尾传输数据帧相关推荐

  1. 移动端布局规范-固定页头页尾-中间随高度滑动

    <!DOCTYPE html> <html> <head><title>移动端上下固定中间滑动</title><meta charse ...

  2. 7.STM32F407ZG串口通信配置流程

    步骤: 1.时钟使能:GPIO时钟使能,串口时钟使能. RCC_AHB1PeriphResetCmd(RCC_AHB1Periph_GPIOA, ENABLE);RCC_APB2PeriphClock ...

  3. (一)串口通信:同步通信与异步通信的区别;异步通信:握手、收发过程、心跳包、定时器、粘包、拆包、丢包、误包(或误码)、帧结构、奇偶校验、CRC校验等等

    目录 第一章.简介 第二章.并行通信 第三章.串行通信 3.1.同步通信 3.1.1.同步通信的原理 3.1.2.同步通信的数据格式 3.1.3.同步通信特点 3.2.异步通信 3.2.1.异步通信的 ...

  4. js 串口通信mscomm接收undefined_串口通信帧的同步方法(识别一帧数据的起始结束)42...

    串口通信是单片机和DSP等嵌入式系统之间,以及嵌入式系统与PC机或无线模块之间的一种非常重要且普遍使用的通信方式.在嵌入式系统的硬件结构中,通常只有一个8位或16位的CPU,不仅要完成主流程的工作,同 ...

  5. 嵌入式开发—串口通信

    文章目录 1 概述 1.1 串口通信是什么 1.2 波特率 1.3 串口通信的用途 2 串口收发 2.1 波特率发生器 2.2 收发FIFO 2.3 DMA 3 串口收发程序设计 3.1 串口发送数据 ...

  6. 【嵌入式学习-STM32F103-USART串口通信】

    目录 1.串口通信协议(简介+软硬件规则) 2.STM32内部的USART外设 3.USART基本结构(江科大简化) 4.串口发送代码 4-1 基本流程 4-2 整体代码 4-2-1 main.c 4 ...

  7. 实现串口通信数据帧打包与解析,串口通信可靠传输,屡试不爽的数据封包与状态机数据解析程序

    提示:本文所述内容为实际项目中多次实践的成果,稳定可靠,且方便移植,适合多种通信场景. 文章目录 前言 一.实现思路 一.发送端 1.1 实现过程 1.2 实现代码 1.2.1 定义数据发送函数 1. ...

  8. STM32串口通信详解以及通信异常或者卡死常见问题分析

    STM32串口通信详解以及通信异常或者卡死常见问题分析 目录 STM32串口通信详解以及通信异常或者卡死常见问题分析 一.常见的异常问题 二.STM32的串口简介 1.串口的通讯方式 ①按数据传输方向 ...

  9. 【STM32串口通信】

    STM32串口通信 学习计划 一.串口通信知识点 二.硬件部分 1.所需硬件 2.部分硬件连接 三.阻塞式 0.串口阻塞式发送和接收概念 1.STM32CUBEMX配置 2.编写阻塞式串口发送与接收代 ...

最新文章

  1. QT+VS打包发布流程该怎么做?
  2. linux 子网和广播地址异常
  3. 20个数据库设计的最佳实践
  4. SheevaPlug是什么,有什么用途
  5. 《从零开始学Swift》学习笔记(Day 40)——析构函数
  6. ASP.NET登录控件login。
  7. JVM超神之路:年后跳槽需要的JVM知识点,周末给你整理了一份!!!
  8. arduino舵机代码_Arduino如何同时使用多个串口
  9. 小程序Git版本管理
  10. 固件编辑器android,定制 Android 固件
  11. IE浏览器F12无法使用
  12. Unity 多人签名
  13. 【日常点滴015】微信小程序直接下载文件到PC端
  14. IT开发资料大全 转
  15. 碰到spoolsv.exe-应用程序错误怎么办?
  16. 如何使用powertoys全盘搜索文件
  17. 运行错误 terminate called without an active exception
  18. 软件项目管理——谈谈软件定价
  19. 前端笔记5 JQuery 语法及应用
  20. 如何合并多个excel文件图文步骤

热门文章

  1. (4.0.15.5)Android开发:最详细的 Toolbar 开发实践总结
  2. 不同材质皮革皮衣皮裤应对西南盆地地区冬季低温阴冷湿冷气候会有什么样的效果?
  3. 自媒体运营主要做些什么
  4. python shape函数的用法
  5. 计算机科学MCS,计算机专业顶级期刊_mcs期刊_计算机科学期刊
  6. 如何用php做商品价格计算表,利用ajax+php实现商品价格计算
  7. 微信公众号jssdk 分享/App原生应用接入分享开发及应用场景
  8. Javaweb登录页面使用JQuery循环判断输入框不能为空
  9. java 静态初始化数据_Java静态数据初始化
  10. 计算机图形学用户坐标系,计算机图形学01——坐标系