• 学习笔记|串口通信实战|简易串口控制器|sprintf函数|STC32G单片机视频开发教程(冲哥)|第二十一集(下):串口与PC通信


    3.串口通信实战

    做一个简易串口控制器。发送对应指令,让板子做相应的事情,或者传输数据(文本模式下发送,不要选择HEX)。
    1.串口发送字符Ax\r\n,(x表示0-7)板子点亮对应LED.\r\n也可以在串口软件中设置自动发送。
    2.串口发送Bxxxx\r\n,xxxx表示一个四位数,四位数码管显示这个4位数
    2.串口发送Z\r\n,板子给电脑发送“Hello STC”;
    3.串口发送字符Cx\r\n,(x表示0-1)板子打开/关闭蜂鸣
    4.串口发送字符D\r\n,板子通过串口发送当前温度给电脑。

    实操

    先把需求复制到demo.c顶部。
    为实现功能1,首先要对串口接收进行处理。查看void UART2_int (void) interrupt UART2_VECTOR:

    void UART2_int (void) interrupt 8
    {
        if(S2RI)		//如果接收到数据,
        {
            S2RI = 0;    //Clear Rx flag
            RX2_Buffer[RX2_Cnt] = S2BUF;
            if(++RX2_Cnt >= UART2_BUF_LENGTH)   RX2_Cnt = 0;
        }
    
        if(S2TI)
        {
            S2TI = 0;    //Clear Tx flag
            B_TX2_Busy = 0;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    简易的工作原理

    先清空标志位,再把数据存入RX2_Buffer[RX2_Cnt]。S2BUF(写入的数据)不断的存到RX2_Buffer中,这里用到了循环写入的方式,刚刚上电的时候RX2_Cnt是0,
    写完后变成了1,变成了2,…,总长度是#define UART2_BUF_LENGTH 128,数组RX2_Buffer的最大长度是128,也就是说写入值超过127以后下一次会重新开始写。
    覆盖掉0的参数,再往下写,一个一个的覆盖下去,直到覆盖到最后一个,然后又从头开始。
    再去看一下串口发送,在demo.c中,if((TX2_Cnt != RX2_Cnt) && (!B_TX2_Busy)) //收到数据, 发送空闲
    如果TX2_Cnt != RX2_Cnt,假设接收的数值是4,则已经写入了4个数据,如果串口发送和串口接收的数值不相等,并且不为忙碌的时候,就可以开始发送数据。
    把数据写入 S2BUF,然后他也是跟着跑,每次写入一个数据,RX2CNT是每接收到一个数据,RX2CNT数值加1,加1以后,TX就不等于RX2CNT了,这种情况下,先往上写一个数据,TX2CNT也就可以开始+1,
    比如说写入的是4个数据,假设TX2CNT刚上电,初始是0,即满足(TX2_Cnt != RX2_Cnt) && (!B_TX2_Busy)的条件,则先将数据(写入0)先传送出去,写完以后这里还是不等于他,把写入1也写出去,如果说还是不等于,
    再接着写出去,这里其实是一个循环的队列,串口在空闲的时候就可以跟着他走,这里就是一个循环队列的演示。
    本次只要接收到一个指令就可以。从指令集分析,每次接收到\r\n以后,就可以重新开始计数。
    在中断函数void UART2_int (void) interrupt 8中开始改写,如果先接收到了数据,先把接收到的数据存进去,初始化的时候RX2_Cnt = 0;(刚上电的时候这个数值为0)。
    添加变量bit Rec_Flag =0; //接收完成标志位。还需要在.h文件中定义一下:extern bit Rec_Flag; 增加extern关键字,主函数中也可以调用。
    假设接收到4个字符:
    在这里插入图片描述
    先接收到A以后,没有检测到\r\n,先接收到O以后他也是没有检测到\r\n,直到检测到\n再去判断前一个数值是不是\r,如果有,说明接收完成。
    处理代码为:

    		if( RX2_Buffer[RX2_Cnt] == '\n' )
    		{
    			if( RX2_Buffer[RX2_Cnt-1] == '\r' )
    				Rec_Flag = 1;	//接收完成标志位,
    			RX2_Cnt = 0;		//接收完成清0
    		}
    		else
    			RX2_Cnt++;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    接收完成后,在主函数里做处理。这里不需要把参数打印出来了,将接收数据处理代码注释掉或者删除。

    //		if((TX2_Cnt != RX2_Cnt) && (!B_TX2_Busy))   //收到数据, 发送空闲
    //        {
    //            S2BUF = RX2_Buffer[TX2_Cnt];
    //            B_TX2_Busy = 1;
    //            if(++TX2_Cnt >= UART2_BUF_LENGTH)   TX2_Cnt = 0;
    //        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    通过检测Rec_Flag位,它已经检测到了最末尾的\r\n符号。可以用switch语句,根据RX2_Buffer[0]分情况处理,比较的时候只能是单个变量或者字符:

    		if(Rec_Flag == 1)	//它已经检测到了最末尾的\r\n符号
    		{
    			switch (RX2_Buffer[0])
                {
                	case 'A':
    					if()
                		break;
    				case 'B':
                		break;
    				case 'C':
                		break;
    				case 'D':
                		break;
                	case 'Z':
                		break;
                	default:
                		break;
                }
    			Rec_Flag = 0;	//执行完后Rec_Flag 清0,防止它反复执行
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    下一步,判断第2个字符,根据ASCII码表,第二个数需要大于等于48,小于等于55,则这个数据有效:

    if((RX2_Buffer[1] >= 48) && (RX2_Buffer[1] <= 55))
    
    • 1

    点亮灯执行,LED = (1<<(RX2_Buffer[1] - 48)) ,RX2_Buffer[1] - 48则取至范围变为0-7,如果0左移1位就是点亮LED0,左移7位就是点亮LED1.

                	case 'A':
    					if((RX2_Buffer[1] >= 48) && (RX2_Buffer[1] <= 55))
    					{
    						LED = (1<<(RX2_Buffer[1] - 48));
    					}
                		break;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    编译完,准备去下载。下载完成打开串口助手,发送A0,发现状态反了,很好处理,取反。一定要用全部取反(~):LED = ~(1<<(RX2_Buffer[1] - 48));,不是感叹号!(位取反)。
    再来看第二个:

    				case 'B':
    					SEG0 = RX2_Buffer[1] - 48;
    					SEG1 = RX2_Buffer[2] - 48;
    					SEG2 = RX2_Buffer[3] - 48;
    					SEG3 = RX2_Buffer[4] - 48;
                		break;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    编译下载,选择正确的串口号,发送B1234,数码管上显示了1234。
    接下来第三个,选项C,如果RX2_Buffer[1]==0,直接控制蜂鸣器的引脚。

    				case 'C':
    					if(RX2_Buffer[1] == 48)
    						BEEP = 0;
    					else
    						BEEP = 1;
                		break;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    选项D,这里要新学一个函数sprintf。

    Tips:sprintf函数简介

    详细可参考:sprintf函数用法详解
    sprintf函数的原型如下:
    int sprintf(char *str, const char *format, …);
    其中,str参数是指向存储输出结果的缓存区的指针,必须具有足够的容量来存储输出结果;format参数是格式控制字符串,定义了输出的格式等;其余的…参数是输出结果。
    sprintf函数的返回值为输出到缓存区中的字符数量,这个值不包括字符串结尾的’\0’。
    本工程中的应用,首先需要引用头文件:#include “stdio.h”。
    sprintf函数与printf相比,里面的内容和后面的内容都是不变的,只是前面加了一个,把生成的字符保存到了前面,比如定义数组char str[30];将最终要显示的字符串保存在了之前定义的数组里,
    int temp = 26; //这里仅做模拟,每执行一次加1,方便区分。下载执行,输入D点击发送,显示温度:0,温度1,…
    再实现命令Z,代码为:PrintString2(“Hello STC!\r\n”);
    完整核心代码为:

    		if(Rec_Flag == 1)	//它已经检测到了最末尾的\r\n符号
    		{
    			switch (RX2_Buffer[0])
                {
                	case 'A':
    					if((RX2_Buffer[1] >= 48) && (RX2_Buffer[1] <= 55))
    					{
    						LED = ~(1<<(RX2_Buffer[1] - 48));
    					}
                		break;
    				case 'B':
    					SEG0 = RX2_Buffer[1] - 48;
    					SEG1 = RX2_Buffer[2] - 48;
    					SEG2 = RX2_Buffer[3] - 48;
    					SEG3 = RX2_Buffer[4] - 48;
                		break;
    				case 'C':
    					if(RX2_Buffer[1] == 48)
    						BEEP = 0;
    					else
    						BEEP = 1;
                		break;
    				case 'D':
    					sprintf(str,"温度:%d\r\n",temp);
    					PrintString2(str);
    					temp++;
                		break;
                	case 'Z':
    					PrintString2("Hello STC!\r\n");
                		break;
                	default:
                		break;
                }
    			Rec_Flag = 0;	//执行完后Rec_Flag 清0,防止它反复执行
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    实际场景下可以做相应的UI界面规划设计,发送相应指令,执行对应程序。下位机做好指令的接收和处理。如果担心数据乱码,可以加入数据校验,判断末尾的值是否和要求的相等,相等说明命令有效。

    总结

    1.了解串口的接线(TX和RX相连)和扩展(232,485等硬件)
    2.学会分析和移植驱动代码。
    3.拓展一下sprintf的用法(变量转字符串操作很有用)
    4.课外可以自己买几个串口的模块体验一下~

    课后练习

    用试验箱实现简易串口控制器主机。(可以用本实验性的第二组串口/另外的核心板)
    1.按下按钮0-7发送字符Ax\r\n(x表示0-7)
    2.按下按钮8发送B0000\r\n
    3.按下按钮9发送Z\r\n
    4.按下按钮A串口发送字符C0\r\n
    4.按下按钮B串口发送字符C1\r\n
    4.按下按钮C发送字符Dx\r\n

  • 相关阅读:
    洛谷P6669 组合数问题
    在云服务器上打开ftp服务-踩坑及心得
    P1547 [USACO05MAR] Out of Hay S 题解
    嬴图Ultipa | 实时图计算如何将反洗钱进行到底?
    蓝桥杯嵌入式基础模块——定时器输入捕获功能(新板)STM32G431(HAL库开发)
    Stream.toList()和Collectors.toList()的性能比较
    【机器学习Q&A】数据抽样和模型验证方法、超参数调优以及过拟合和欠拟合问题
    (附源码)ssm高校运动会管理系统 毕业设计 020419
    基于BP神经网络算法鸢尾花数据集的分类
    11月外贸新规
  • 原文地址:https://blog.csdn.net/Medlar_CN/article/details/133886823