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


目录

  • STM32串口通信详解以及通信异常或者卡死常见问题分析
  • 一、常见的异常问题
  • 二、STM32的串口简介
    • 1.串口的通讯方式
      • ①按数据传输方向
      • ②串行通讯的通信方式
      • ③UART异步通信方式引脚连接方法
      • ④STM32F103系列串口对应引脚
      • ⑤串口通讯过程
    • 2.串口的部分寄存器以及库函数的应用
      • ①USART_SR状态寄存器
      • ②USART_DR数据寄存器
      • ③重要的标志位
  • 三、实用的代码讲解分析
    • 1.发送功能函数应用详解
      • ①字符发送
      • ②字符串发送
      • ③C语言printf应用
    • 2.接收功能函数应用详解
      • ①解析数据帧头的通讯接收方式
      • ②解析数据帧尾的通讯接收方式
      • ③解析接收超时的通讯接收方式
  • 四、对第一章的问题分析
  • 四、总结

一、常见的异常问题

◉异常一:数据传输中会出现乱码
◉异常二:程序卡在中断函数里面无法跳出执行主函数的逻辑
◉异常三:数据传输中间歇性数据异常
◉异常四:数据发送时会出现漏发的现象

这些问题对小编带了了巨大的困扰,本文也因这些问题应时而生。小编将自己的见解分享大家,也希望各路大神指导改正。欢迎交流


二、STM32的串口简介

1.串口的通讯方式

◉并行通讯
-传输原理:数据各个同时传输
-优点:速度快
-缺点:占用引脚资源多

◉串行通讯
-传输原理:数据安位顺序传输
-优点:占用引脚资源少
-缺点:传输速度较慢

①按数据传输方向

◉单工:
-数据传输只支持数据一个方向传输

◉半双工
-允许数据在两个方向上传输,但是,在某一时刻,只允许数据在一个方向上传输,他是实际是一种切换方向的单工通讯。
◉全双工
-允许数据同时在两个方向上传输,因此,全双工通讯是两单工通讯方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力。

串行三种通讯方式如图所示:

②串行通讯的通信方式

◉同步通信
-带时钟同步信号传输(接口:SPI,IIC,USART(通用同步异步首发器)

◉异步通信

-不带时钟同步信号(接口:UART(通用异步收发器),单总线

-常见的串口通信接口如图:

③UART异步通信方式引脚连接方法

◉RXD: 数据输入引脚,数据接收。

◉TXD: 数据发送引脚,数据接收。

接线如图:

④STM32F103系列串口对应引脚

串口号 TXD RXD 重定义TXD 重定义RXD
USART1 PA9 PA10 PA6 PA7
USART2 PA2 PA3 PD5 PD6
USART3 PB10 PB11 PD8/PC10 PD9 /PC11
USART4 PC10 PC11 / /
USART5 PC12 PD3 / /

⑤串口通讯过程

2.串口的部分寄存器以及库函数的应用

①USART_SR状态寄存器

作用:状态寄存器USART_SR,描述串口寄存器的一些状态

获取状态标志位函数-操作USART_SR寄存器

// 获取状态标志位
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
// 清除状态标志位
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);
// 获取中断状态标志位
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
// 清除中断状态标志位
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);

②USART_DR数据寄存器

USART_DR实际是包含了两个寄存器,一个专门用于发送的TDR,一个专门用于接收的RDR。进行发送数据操作时,往USART_DR写入数据会自动存储在TDR内;当进行读取数据操作时,向USART_DR读取数据会自动提取RDR数据。

接收发送数据函数-操作USART_DR寄存器

// 发送数据到串口(通过写USART_DR寄存器发送数据)
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
// 接收数据(从USART_DR寄存器读取接收到的数据)
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);

③重要的标志位

1、USART_FLAG_RXNE 串口信号接收完成

2、USART_FLAG_TXE 数据预发送准备

3、USART_FLAG_TC数据发送完成

常用的代码应用:


USART_ITConfig(USART1, USART_IT_TXE, ENABLE); //清除溢出中断USART_ClearFlag(USART1, USART_FLAG_TC);//清除传输完成标志位USART_ClearFlag(USART1,USART_FLAG_RXNE); //清空中断标志位

三、实用的代码讲解分析

1.发送功能函数应用详解

①字符发送

u8 Uart1_PutChar(u8 ch) //发送一个字符,输入参数为发送的字符
{USART_SendData(USART1, (u8) ch);//向串口1,发送一个字符while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET){}//等待发送完成return ch;//返回字符
}

②字符串发送

表达①

void Uart1_PutString(u8* buf , u8 len)//发送一个字符串,输入参数为要发送的数组,发送的字符串个数
{ u8 i;for(i=0;i<len;i++){Uart1_PutChar(*buf++);//发送一个字符}
}

表达②

void Uart1_SendStr(u8 *SendBuf)//串口1打印数据
{while(*SendBuf){while((USART1->SR&0X40)==0){};//等待发送完成 USART1->DR = (u8) *SendBuf; SendBuf++;}
}

③C语言printf应用

#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{ int handle;
}; FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
_sys_exit(int x)
{ x = x;
}
//重定义fputc函数 ,重定义到串口1
int fputc(int ch, FILE *f)
{      while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   USART1->DR = (u8) ch;      return ch;
}
#endif

2.接收功能函数应用详解

①解析数据帧头的通讯接收方式

-说明:通过分析帧头来确定一包数据的到来,还需要确认数据的长度。
-例如:如果帧头确定第一个字符是0X5A,第二个字符是0X5A。串口1接收到的数据从串口2发送出去
-代码函数如下:

u8 old_ch;
u8 oold_ch;
u8 send_distant = 0; //发送字符的指针
void USART2_IRQHandler(void)                    //串口2中断服务程序
{u8 ch;if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)  //是否发送中断事件{ch =USART_ReceiveData(USART2);//将接收到的数据存在数组Usart1RecBuf[RxCounter]里if(oold_ch == 0x5A && old_ch == 0x5A){send_distant = 2;}if(send_distant){if(send_distant == 1){ch = JuLi_L;//改变第二个字符}else if(send_distant == 2){ch = JuLi_H;//改变第二个字符}send_distant--;}oold_ch = old_ch;old_ch = ch;USART_SendData(USART1,ch);//将接收到的数据从串口1发送出去} 

②解析数据帧尾的通讯接收方式

-说明:通过分析帧尾来确定一包数据的结束,不需要确认数据的长度。
-例如:如果帧尾确定倒数第一个字符是0X0A,倒数第二个字符是0X0D。串口1接收到的数据从串口2发送出去
-代码函数如下:


void USART1_IRQHandler(void)                    //串口1中断服务程序
{u8 Res;if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)//接收中断(接收到的数据必须是0x0d 0x0a结尾){Res =USART_ReceiveData(USART1); //读取接收到的数据if((USART_RX_STA&0x8000)==0)//接收未完成{if(USART_RX_STA&0x4000)//接收到了0x0d{if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始else USART_RX_STA|=0x8000;  //接收完成了 }else //还没收到0X0D{   if(Res==0x0d)USART_RX_STA|=0x4000;else{USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;USART_RX_STA++;if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收   }      }}          }
} 

③解析接收超时的通讯接收方式

-说明:该方法不需要判断帧头帧尾,也不需要确认好数据的长度。是通过判断接收数据帧的时间间隔来判断一包数据是否接收完成。

-该方法需要开启定时器来计算时间,接收超时的时间需要计算出数据帧的接收时间间隔来判定,不同的波特率数据帧的接收时间间隔不一样,比如:波特率为115200,时间间隔为:10/115200=0.08ms。

串口中断函数代码如下:

extern unsigned char star_time_led ;  //计时开始变量
unsigned char recv_flag = 0;//定义接受标志位
unsigned long recv_cnt = 0;//串口1接收数据缓存
unsigned char recv_buf[MAX_REV_NUM];//串口1接收数据缓存
extern unsigned char star_time;
extern unsigned char recv_time_cnt;void USART1_IRQHandler(void)                 //串口1中断服务程序
{static char ch;if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //是否发送中断事件{ch = USART_ReceiveData(USART1);//将接收到的数据存在数组Usart1RecBuf[RxCounter]里star_time = 1;         //接受到一帧数据的时候,打开软件定时器,去计数if(recv_cnt < MAX_REV_NUM)//数组长度是否超过缓存区{recv_buf[recv_cnt] =ch;//将接收到的数据存在数组Usart1RecBuf[RxCounter]里recv_cnt++;}else{recv_cnt = MAX_REV_NUM;   //限制数组长度,超过缓存区则不再接收}recv_time_cnt = 0; //每接收到一帧数据,把定时计数器清零,相当于喂狗//但是在定时器中断里面会不断的累加USART_ClearFlag(USART1,USART_FLAG_RXNE); //清空中断标志位}
} 

定时器中断函数代码如下:

unsigned char star_time = 0 ;  //计时开始变量
unsigned char recv_time_cnt;  //定时计数器
extern unsigned long recv_cnt;
extern unsigned char recv_flag;
unsigned char star_time_led = 0 ;  //计时开始变量
void TIM4_IRQHandler(void)   //TIM1中断函数
{if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET) //检查指定的TIM1中断发生与否:TIM1中断源 {    star_time_led++;if(star_time == 1){ recv_time_cnt++; //1、累加定时时间计数器if(recv_time_cnt>MAX_REV_TIME)//2、判断时间是否超过了设定的最大的时间阈值,//   超过则说明等待一段时间后没有新的数据到来//   则判断一包数据接收完毕{recv_time_cnt = 0;//3、清除定时计数器,处理数据 清楚nuffer(放在数据处理之后)recv_cnt = 0;recv_flag = 1; //接收完成标志位置1}}TIM_ClearITPendingBit(TIM4, TIM_IT_Update  );  //清除TIM1的中断待处理位:TIM1中断源 }
}

四、对第一章的问题分析

◉异常一:数据传输中会出现乱码
数据传输中会出现乱码,很有可能是数组溢出,或者定义的数组长度不够。或者中断被打断。

◉异常二:程序卡在中断函数里面无法跳出执行主函数的逻辑
中断标志位没有被清除,在这里要注意一点,串口中断标志位自动清空的前提是软件需要先读USART_SR寄存器,然后读USART_DR寄存器来自动清除。即串口中断事件发生后,如果使能的接收中断,而中断函数里面什么都不执行的话,接收中断标志位是无法自动清空的,故而,函数会一直卡在中断函数里面。

比如一下这个函数,该函数没有逻辑问题,但会引发以上问题,代码如下

extern unsigned char star_time_led ;  //计时开始变量
unsigned char recv_flag = 0;//定义接受标志位
unsigned long recv_cnt = 0;//串口1接收数据缓存
unsigned char recv_buf[MAX_REV_NUM];//串口1接收数据缓存
extern unsigned char star_time;
extern unsigned char recv_time_cnt;
/*
以下写法有严重问题
如果没有这句函数→USART_ClearFlag(USART1,USART_FLAG_RXNE); //清空中断标志位
串口接收中断标志位将文法被清空,会导致函数卡在中断函数里面一直循环,无法正常运行主函数原因分析:
中断条件成立后,中断标志位将会标记,程序将会进入中断函数运行,软件自动轻触中断标志位的条件是
先读USART_SR寄存器,再读USART_DR寄存器。
void USART1_IRQHandler(void)                    //串口1中断服务程序
{if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //是否发送中断事件{star_time = 1;         //接受到一帧数据的时候,打开软件定时器,去计数if(recv_cnt < MAX_REV_NUM)//数组长度是否超过缓存区{recv_buf[recv_cnt] =USART_ReceiveData(USART1);//将接收到的数据存在数组Usart1RecBuf[RxCounter]里recv_cnt++;}else{recv_cnt = MAX_REV_NUM;    //限制数组长度,超过缓存区则不再接收}recv_time_cnt = 0; //每接收到一帧数据,把定时计数器清零,相当于喂狗//但是在定时器中断里面会不断的累加USART_ClearFlag(USART1,USART_FLAG_RXNE); //清空中断标志位}
}
*/

上述代码优化后如下

void USART1_IRQHandler(void)                 //串口1中断服务程序
{static char ch;if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //是否发送中断事件{ch = USART_ReceiveData(USART1);//将接收到的数据存在数组Usart1RecBuf[RxCounter]里star_time = 1;         //接受到一帧数据的时候,打开软件定时器,去计数if(recv_cnt < MAX_REV_NUM)//数组长度是否超过缓存区{recv_buf[recv_cnt] =ch;//将接收到的数据存在数组Usart1RecBuf[RxCounter]里recv_cnt++;}else{recv_cnt = MAX_REV_NUM;   //限制数组长度,超过缓存区则不再接收}recv_time_cnt = 0; //每接收到一帧数据,把定时计数器清零,相当于喂狗//但是在定时器中断里面会不断的累加USART_ClearFlag(USART1,USART_FLAG_RXNE); //清空中断标志位}
}

◉异常三:数据发送中间歇性数据异常漏发乱发等

对于这些奇奇怪怪的问题,首先要了解一下发送函数是怎么发送的

USART_DR 包含了已发送的数据或者接收到的数据。 USART_DR 实际是包含了两个寄存器,一个专门用于发送的可写 TDR,一个专门用于接收的可读 RDR。当进行发送操作时,往 USART_DR 写入数据会自动存储在 TDR 内;当进行读取操作时,向 USART_DR读取数据会自动提取 RDR 数据。

TDR 和 RDR 都是介于系统总线和移位寄存器之间。串行通信是一个位一个位传输的,发送时把 TDR 内容转移到发送移位寄存器,然后把移位寄存器数据每一位发送出去,接收时把接收到的每一位顺序保存在接收移位寄存器内然后才转移到 RDR。

当 TDR 内容转移到发送移位寄存器,还没有发送出去的,就再次把TDR 内容转移到发送移位寄存器里,就会出现少发的现象。

什么时候会有这种情况呢?错误操作代码如下:

void USART2_IRQHandler(void)                 //串口2中断服务程序
{if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)  //是否发送中断事件{Usart1RecBuf[RxCounter] =USART_ReceiveData(USART2);//将接收到的数据存在数组Usart1RecBuf[RxCounter]里RxCounter++;//指向数组地址自加if(RxCounter==2) {   USART_SendData(USART1, Usart1RecBuf[0]);//发送Usart1RecBuf[0]USART_SendData(USART1, Usart1RecBuf[1]);//发送Usart1RecBuf[1]USART_SendData(USART1, Usart1RecBuf[2]);//发送Usart1RecBuf[2]}}
}

上述代码连续运行了3次USART_SendData(USART1, Usart1RecBuf);这个函数,这种情况一般都会出现只有最后一个数据发送成功出去。原因可能就是数据还没有发送出去,发送移位寄存器就更新了。

四、总结

◉上述内用的一些程序源码连接如下,包括解析数据帧头的通讯接收方式,解析数据帧尾的通讯接收方式,解析接收超时的通讯接收方式。创作实属不易

链接:
https://download.csdn.net/download/weixin_43281206/19342432


具体源码工程压缩包可在本博客上传资料下载。或者下面QQ群下载,“星云视界”资料共享群:626051465
“星云视界”资料共享群资料二维码如下:

非常感谢大家的观看,希望对大家有所帮助。小编常接外包设计定单哦!非常乐意为大家提供毕设技术指导以及产品设计等服务。

如需要可直接联系小编(添加请备注博客添加哈):

QQ : 3778615131

微信号 : a13428153731

STM32串口通信详解以及通信异常或者卡死常见问题分析相关推荐

  1. STM32—串口通讯详解

    串口通讯目录 物理层 协议层 USART简介 开发板与上位机的连接 代码讲解: 一.初始化结构体 二.NVIC配置中断优先级 三.USART配置函数讲解 四.传输数据的函数: 1.发送一个字节 2.发 ...

  2. STM32串口使用详解

    一.关于串口需要了解的几个知识点: 1.波特率:在串行通讯中,数据是按位进行传送的,因此传送速率用每秒钟传送格式位的数目来表示,称为波特率.    波特率决定了串口传输的速度,1波特=1bps(位/秒 ...

  3. delphi 串口通信发送_STM32第五章串口通讯详解

    点击上方"果果小师弟",选择"置顶/星标公众号"干货福利,第一时间送达! 串口通信是串行通信里面的异步方式.串行通信是相对于并行通信来说的.串口是一个事实存在的 ...

  4. 【STM32】标准库与HAL库对照学习教程八--串口通信详解

    [STM32]标准库与HAL库对照学习教程八--串口通信详解 一.前言 二.准备工作 三.通信的基本概念 1.通信方式 2.串行通信与并行通信 (1)串行通信 (2)并行通信 3.异步通信与同步通信 ...

  5. Java串口通信详解(转)

    Java串口通信详解(转) 作者:denimcc 日期:2007-05-11 序言     说到开源,恐怕很少有人不挑大指称赞.学生通过开源代码学到了知识,程序员通过开源类库获得了别人的成功经验及能够 ...

  6. MATLAB与51单片机进行串口通信详解

    目录 一.51单片机与电脑进行串口通信 二.MATLAB串口通信函数 三.串口属性 四.示例Demo 4.1 MATLAB接收单片机发来的数据 4.2 MATLAB向单片机发送数据控制LED 五.总结 ...

  7. S5PV210串口通信详解

    S5PV210串口通信详解 S5PV210概述: S5PV210有4路独立,异步,串行的输入输出IO口,UART支持的通信速率达到3Mbps. 一个周期数据的组成:1位起始位,8位有效数据位,1位奇偶 ...

  8. (四)裸机s5pv210之串口通信详解

    裸机s5pv210之串口通信详解 文章目录 裸机s5pv210之串口通信详解 前言 一.电子通信概念 1.同步通信和异步通信 2.电平信号和差分信号 3.并行接口和串行接口 二.串口通信的基本概念 1 ...

  9. RS232串口通信详解

    RS232串口通信详解http://www.21ic.com/jichuzhishi/datasheet/RS232/jiekou/184659.html 串口是计算机上一种非常通用的设备通信协议. ...

最新文章

  1. 为什么LED内部不集成限流电阻呢?
  2. 银行核心海量数据无损迁移:TDSQL数据库多源异构迁移方案
  3. struts2配置文件的位置
  4. var result = ![] == []; console.log(result); // 结果是?为什么?
  5. 缺陷的背后---LIMIT M,N 分页查找
  6. 你们还在用8位单片机吗?
  7. 第六计 / Explosive City (2004)
  8. t–sql pl–sql_不正确SQL Server统计信息– SQL查询性能的杀手–基本知识
  9. checkbox取反
  10. iwrite提交不了作业_痛点!为什么开发了那么多软件,还是解决不了教学问题!...
  11. 【讲座预告】Processing Learner Texts: from Annotation to ...
  12. 【Storm总结-6】Twitter Storm: DRPC简介
  13. 你关注过浏览器最小字体为多大吗?
  14. 合肥工业大学计算机课改没,工程力学性能课改分析
  15. mysql怎么卸载干净?
  16. python实现爱心代码
  17. obd协议 混动车_OBD协议
  18. C#大恒相机采集图片时图片上下对称折叠了
  19. 为什么阿里不允许用Executors创建线程池,而是通过ThreadPoolExecutor的方式?
  20. RK3399平台开发系列讲解(内核驱动外设篇)6.17、VOP驱动解析

热门文章

  1. 2021-2027中国共享单车软件市场现状及未来发展趋势
  2. 盘点四种保密电脑照片的方法
  3. 【中科院暑期2021】OpenCT社区学生火热招募中!
  4. 布线品牌纷纷推出机柜的背后
  5. 关闭windows 7 home basic版 internet 连接状态测试
  6. 153.炫酷粒子背景特效
  7. NSOperationQueue与NSOperation感悟(源自白度以及自己项目中)
  8. System_Verilog打印格式
  9. Qt的事件驱动机制与eventfd
  10. Caused by: java.lang.IllegalArgumentException: The handle attribute is must refer to an existing chi