矿石收音机论坛

 找回密码
 加入会员

QQ登录

只需一步,快速开始

搜索
查看: 3447|回复: 27

【求助】51单片机C语言编程串口通讯疑惑

[复制链接]
     
发表于 2021-5-22 12:11:48 | 显示全部楼层 |阅读模式
本帖最后由 Fireflying 于 2021-5-22 12:33 编辑

学习51单片机C语言编程,最近在跟串口通讯死磕,经历了无数次失败,今天终于把串口发送数据调试成功了,但是仍然有疑问。
可以参阅这个关联帖子:
http://www.crystalradio.cn/forum ... ead&tid=1942403

在上面那个实验失败之后,我就先换了一个思路,打算先用单片机向电脑(上位机)用串口发送固定的数值,看电脑能不能正确收到。
中间的失败过程就不说了,只说关键。程序设置的循环发送一个固定的十六进制值0x66,当我用STC-ISP的串口助手打开串口,电脑能收到数据,但是数据并不是单片机发送的0x66,一开始我以为是波特率不匹配导致的乱码,于是我就调整程序串口初始化那部分的语句,改变初始化波特率;也有调整串口助手的波特率,但始终没有收到正确的数据0x66,有时候收到的是0xee,有时候是0x00,有时候是0x0e,总之就不是单片机发送的数。

查询了手头的教材,没有找到答案。于是祭起万能的百度一顿搜索,搜到了一个帖子,说发送和接收之前插入延时,原本的乱码就正常了。

  1. /*********************************************************************************************
  2. 程序名:                STC12C4052AD单片机串口编程实验   
  3. 编写人:                Fireflying     
  4. 编写时间: 2021年05月22日
  5. 硬件支持: 采用STC12C4052AD(DIP20封装)
  6. 接口说明:    
  7. 修改日志:        发送前和后都加上10毫秒延时后,发送才正常。不加延时则乱码  
  8.   NO.1-                                                               
  9. /*********************************************************************************************
  10. 说明:串口初始化,固定波特率,由单片机向串口循环发送特定数据,用上位机接收数据。
  11. /*********************************************************************************************/

  12. #include <STC12C2052AD.H> //STC12Cx052AD系列单片机头文件

  13. /*********************************************************************************************
  14. 函数名:UART串口初始化函数
  15. 调  用:UART_init();
  16. 参  数:无
  17. 返回值:无
  18. 结  果:启动UART串口接收中断,允许串口接收,启动T/C1产生波特率(占用)
  19. 备  注:振荡晶体为12MHz,初始化波特率9600
  20. /**********************************************************************************************/
  21. void UART_init (void)        ////9600bps@12.000MHz
  22. {
  23.         PCON &= 0x7F;                //波特率不倍速
  24.         SCON = 0x50;                //8位数据,可变波特率
  25.         AUXR |= 0x40;                //定时器时钟1T模式
  26.         AUXR &= 0xFE;                //串口1选择定时器1为波特率发生器
  27.         TMOD &= 0x0F;                //设置定时器模式
  28.         TMOD |= 0x20;                //设置定时器模式
  29.         TL1 = 0xD9;                //设置定时初始值
  30.         TH1 = 0xD9;                //设置定时重载值
  31.         ET1 = 0;                //禁止定时器%d中断
  32.         TR1 = 1;                //定时器1开始计时
  33. }

  34. /**********************************************************************************************/


  35. /*********************************************************************************************
  36. 函数名:毫秒级CPU延时函数
  37. 调  用:DELAY_MS (?);
  38. 参  数:1~65535(参数不可为0)
  39. 返回值:无
  40. 结  果:占用CPU方式延时与参数数值相同的毫秒时间
  41. 备  注:应用于1T单片机时i<600,应用于12T单片机时i<125
  42. /*********************************************************************************************/
  43. void DELAY_MS (unsigned int a){
  44.         unsigned int i;
  45.         while( a-- != 0){
  46.                 for(i = 0; i < 600; i++);
  47.         }
  48. }
  49. /*********************************************************************************************/


  50. /*********************************************************************************************
  51. 函数名:主函数
  52. 调  用:无
  53. 参  数:无
  54. 返回值:无
  55. 结  果:程序开始处,无限循环
  56. 备  注:从串口循环发送数据0x66
  57. /**********************************************************************************************/
  58. void main (void)
  59. {
  60.         UART_init();                //串口初始化
  61.         while(1)        //主循环
  62.         {
  63.                 DELAY_MS (10);        //延时10毫秒
  64.                 SBUF = 0x66;        //向发送寄存器赋值(发送)
  65.                 TI = 0;                        //令发送中断标志位为0(软件清零)
  66.                 DELAY_MS (10);        //延时10毫秒
  67.         }
  68. }
  69. /**********************************************************************************************/
复制代码


好吧,我就在我的程序里面发送之前和之后各加入10毫秒延时,再试,豁然贯通矣!我把程序里面的发送数据改成啥,电脑收到的就是啥。
然后新问题就来了,为什么不加延时就乱码,加上延时就正常了?这里面的因果关系是甚么?前面百度的那个帖子,帖主自己找到的解决方法,但是帖主自己也对这个解决方法的原因无法理解。
如果正确发送都需要延时的话,哪怕延时只有10毫秒,对发送速率的影响也是很可观的。每次发送数据白白消耗10毫秒,这样哪怕实际发送的数据不消耗时间,一秒钟(1000毫秒)最多发送100次数据,这个速率也太慢了吧?难不成所有的串口通讯都要做延时处理?这从逻辑上是不对的啊?那这个问题的根源是在哪里?
     
 楼主| 发表于 2021-5-22 12:53:22 | 显示全部楼层
刚才继续实验,把延时调整到1毫秒,都能正常工作了,但是不延时就乱码。
回复 支持 反对

使用道具 举报

     
发表于 2021-5-22 12:57:07 | 显示全部楼层
你的波特率9600, 算一下发送一个字节要多少时间,

再算一下,执行   SBUF = 0x66;      TI = 0; 这两个指令要多少时间.

评分

1

查看全部评分

回复 支持 反对

使用道具 举报

     
发表于 2021-5-22 12:58:40 | 显示全部楼层
可以在SBUF = 0x66; 后加while(!TI); 试试.

评分

1

查看全部评分

回复 支持 反对

使用道具 举报

     
 楼主| 发表于 2021-5-22 12:59:47 | 显示全部楼层
继续实验,这次是先读取串口数据,然后再把读到的数据从串口发出去。串口操作之前加上1毫秒延时。

  1. /*********************************************************************************************
  2. 程序名:                STC12C4052AD单片机串口编程实验   
  3. 编写人:                Fireflying     
  4. 编写时间: 2021年05月22日
  5. 硬件支持: 采用STC12C4052AD(DIP20封装)
  6. 接口说明:    
  7. 修改日志:        发送前和后都加上1毫秒延时后,串口发送接收都正常。不加延时则乱码  
  8.   NO.1-                                                               
  9. /*********************************************************************************************
  10. 说明:串口初始化,固定波特率,读取来自串口的数据(由上位机通过串口助手发送),将串口收到的数据再从串口发出去。
  11. /*********************************************************************************************/

  12. #include <STC12C2052AD.H> //STC12Cx052AD系列单片机头文件
  13. unsigned char UART_data; //定义串口接收数据变量

  14. /*********************************************************************************************
  15. 函数名:UART串口初始化函数
  16. 调  用:UART_init();
  17. 参  数:无
  18. 返回值:无
  19. 结  果:启动UART串口接收中断,允许串口接收,启动T/C1产生波特率(占用)
  20. 备  注:振荡晶体为12MHz,初始化波特率9600
  21. /**********************************************************************************************/
  22. void UART_init (void)        //9600bps@12.000MHz
  23. {
  24.         PCON &= 0x7F;                //波特率不倍速
  25.         SCON = 0x50;                //8位数据,可变波特率
  26.         AUXR |= 0x40;                //定时器时钟1T模式
  27.         AUXR &= 0xFE;                //串口1选择定时器1为波特率发生器
  28.         TMOD &= 0x0F;                //设置定时器模式
  29.         TMOD |= 0x20;                //设置定时器模式
  30.         TL1 = 0xD9;                //设置定时初始值
  31.         TH1 = 0xD9;                //设置定时重载值
  32.         ET1 = 0;                //禁止定时器%d中断
  33.         TR1 = 1;                //定时器1开始计时
  34. }

  35. /**********************************************************************************************/


  36. /*********************************************************************************************
  37. 函数名:毫秒级CPU延时函数
  38. 调  用:DELAY_MS (?);
  39. 参  数:1~65535(参数不可为0)
  40. 返回值:无
  41. 结  果:占用CPU方式延时与参数数值相同的毫秒时间
  42. 备  注:应用于1T单片机时i<600,应用于12T单片机时i<125
  43. /*********************************************************************************************/
  44. void DELAY_MS (unsigned int a){
  45.         unsigned int i;
  46.         while( a-- != 0){
  47.                 for(i = 0; i < 600; i++);
  48.         }
  49. }
  50. /*********************************************************************************************/


  51. /*********************************************************************************************
  52. 函数名:主函数
  53. 调  用:无
  54. 参  数:无
  55. 返回值:无
  56. 结  果:程序开始处,无限循环
  57. 备  注:从串口循环发送数据0x66
  58. /**********************************************************************************************/
  59. void main (void)
  60. {
  61.         UART_init();                //串口初始化
  62.         while(1)        //主循环
  63.         {
  64.                 DELAY_MS (5);        //延时1毫秒
  65.                 UART_data = SBUF;        //读接收缓冲区数据到变量UART_data
  66.                 RI = 0;         //接收标志位软件清零
  67.                 if(TI==0)                //发送标志位等于0
  68.                         {
  69.                                 DELAY_MS (5);        //延时1毫秒
  70.                                 SBUF = UART_data;        //把前面收到的数据赋值到发送缓冲区(发出去)
  71.                         }
  72.                 else                //发送标志位不等于0
  73.                         {       
  74.                                 TI = 0;                //令发送中断标志位为0(软件清零)
  75.                         }
  76.         }
  77. }
  78. /**********************************************************************************************/

复制代码


实验结果,在上位机串口助手里面打开串口,发送一个十六进制数例如“7a”,上位机就源源不断的收到“7a”,直到发送另外一个数,收到的数也随之改变。
回复 支持 反对

使用道具 举报

     
 楼主| 发表于 2021-5-22 13:03:54 | 显示全部楼层
本帖最后由 Fireflying 于 2021-5-22 13:07 编辑
diy1997 发表于 2021-5-22 12:58
可以在SBUF = 0x66; 后加while(!TI); 试试.


啥意思?把TI取反的值作为while循环的判断条件?意思是如果TI的值是0,就一直往SBUF里面塞“0x66",直到TI变1串口发送成功了再往后执行?
意思是不是说单片机执行速度太快了,我原来的程序数据还没发送完成,就执行TI清零然后到下一个循环了,这样造成每次发送实际上都没成功?
回复 支持 反对

使用道具 举报

     
 楼主| 发表于 2021-5-22 13:17:41 | 显示全部楼层
diy1997 发表于 2021-5-22 12:58
可以在SBUF = 0x66; 后加while(!TI); 试试.

果然有效。
回复 支持 反对

使用道具 举报

     
发表于 2021-5-22 13:24:16 | 显示全部楼层
看一下器件的规格书,可能在硬件上有一定的限制,或者某些参数设置有问题。
回复 支持 反对

使用道具 举报

     
发表于 2021-5-22 13:28:48 | 显示全部楼层
你用的是STC的单片机,它的串口发送机制就是,

往SBUF 写入数据就启动发送,

发送完一个字节后,TI就置1表示发送完成.

while(!TI) 意思是,如果TI一直为0程序就会一直在这里执行查询TI,

直到发送完成TI置1才会往下执行,并不会“一直往SBUF里面塞“0x66"。



评分

2

查看全部评分

回复 支持 反对

使用道具 举报

     
 楼主| 发表于 2021-5-22 13:30:47 | 显示全部楼层
diy1997 发表于 2021-5-22 12:57
你的波特率9600, 算一下发送一个字节要多少时间,

再算一下,执行   SBUF = 0x66;      TI = 0; 这两个指 ...

波特率9600的时候,发送一个字节需要的时间是:
1÷9600秒,大约是0.1毫秒。
12M晶振1T单片机的一个指令周期是:
1÷12000000秒,大约是0.0008毫秒,两条指令大约是0.0016毫秒。
这样计算对不对?
就是说单片机的操作速度太快,按照串口波特率,一次还没发送完成,单片机已经循环操作百十次了?
回复 支持 反对

使用道具 举报

     
 楼主| 发表于 2021-5-22 13:33:36 | 显示全部楼层
diy1997 发表于 2021-5-22 13:28
你用的是STC的单片机,它的串口发送机制就是,

往SBUF 写入数据就启动发送,

哦,这一行是明白了。感谢老师点拨!
回复 支持 反对

使用道具 举报

     
发表于 2021-5-22 18:10:10 | 显示全部楼层
Fireflying 发表于 2021-5-22 13:30
波特率9600的时候,发送一个字节需要的时间是:
1÷9600秒,大约是0.1毫秒。
12M晶振1T单片机的一个指 ...

对,你用的是硬件 UART,把数据装载入 UART 数据寄存器后(一个或两个时钟周期),CPU 就没事了,可以做这句代码后边的事情了,但 UART 发送器才开始慢吞吞发送数据呢,所以下一次数据发送,要等待 UART 数据寄存器空(这一字节数据发送完毕)才行。

不过通常的写法是先等待 UART 数据寄存器空标志,然后才把数据装载入 UART 数据寄存器,而不是先把数据装载入 UART 数据寄存器,再等待 UART 数据寄存器空标志,各大 MCU 厂家给出的示例代码都是这样写的,你的巫妖难道参加没给你示例代码?

无标题utt.png

评分

1

查看全部评分

回复 支持 反对

使用道具 举报

     
发表于 2021-5-22 19:34:26 | 显示全部楼层
Fireflying 发表于 2021-5-22 13:30
波特率9600的时候,发送一个字节需要的时间是:
1÷9600秒,大约是0.1毫秒。
12M晶振1T单片机的一个指 ...

波特率,你算的时间是发送一个比特的时间(两相调制),

而一个字节有8比特.

指令周期, 一般由若干个机器周期组成,

而机器周期则由12个时钟周期组成,

你算的是时钟周期.

而1T单片机是指1个机器周期=1个时钟周期.

评分

1

查看全部评分

回复 支持 反对

使用道具 举报

     
发表于 2021-5-22 19:53:10 | 显示全部楼层
我现在的STC15W404AS 串口用921600kps每秒发一个值到上位机还是很稳定,SBUF发送要等待, TI寄存器要好好利用.我也经常遇到这类问题,就是CPU程序运行在外设前头导致崩溃,记得最严重一次是STM32驱动伺服电机,我调试调速底层代码时一下没有做设置周期,CPU直接以机器周期发送调速命令导致调速命令比伺服脉冲还要快一下子伺服电机打头保护,还好损失不是很严重.

评分

1

查看全部评分

回复 支持 反对

使用道具 举报

     
发表于 2021-5-22 21:27:35 | 显示全部楼层
washu 发表于 2021-5-22 18:10
对,你用的是硬件 UART,把数据装载入 UART 数据寄存器后(一个或两个时钟周期),CPU 就没事了,可以做 ...

南通老妖的例程里有详细的例子,都是要先把接口状态判断清楚再操作的,这个其实是基本操作,哪有先干了再说的道理,问题是需要看那。

评分

1

查看全部评分

回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 加入会员

本版积分规则

小黑屋|手机版|矿石收音机 ( 蒙ICP备05000029号-1 )

蒙公网安备 15040402000005号

GMT+8, 2025-4-28 06:09

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表