学习笔记|串口通信实战|简易串口控制器|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:

C 复制代码
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;
    }
}

简易的工作原理

先清空标志位,再把数据存入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,如果有,说明接收完成。

处理代码为:

C 复制代码
		if( RX2_Buffer[RX2_Cnt] == '\n' )
		{
			if( RX2_Buffer[RX2_Cnt-1] == '\r' )
				Rec_Flag = 1;	//接收完成标志位,
			RX2_Cnt = 0;		//接收完成清0
		}
		else
			RX2_Cnt++;

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

C 复制代码
//		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;
//        }

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

C 复制代码
		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,防止它反复执行
		}

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

C 复制代码
if((RX2_Buffer[1] >= 48) && (RX2_Buffer[1] <= 55))

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

C 复制代码
            	case 'A':
					if((RX2_Buffer[1] >= 48) && (RX2_Buffer[1] <= 55))
					{
						LED = (1<<(RX2_Buffer[1] - 48));
					}
            		break;

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

再来看第二个:

C 复制代码
				case 'B':
					SEG0 = RX2_Buffer[1] - 48;
					SEG1 = RX2_Buffer[2] - 48;
					SEG2 = RX2_Buffer[3] - 48;
					SEG3 = RX2_Buffer[4] - 48;
            		break;

编译下载,选择正确的串口号,发送B1234,数码管上显示了1234。

接下来第三个,选项C,如果RX2_Buffer[1]==0,直接控制蜂鸣器的引脚。

C 复制代码
				case 'C':
					if(RX2_Buffer[1] == 48)
						BEEP = 0;
					else
						BEEP = 1;
            		break;

选项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");

完整核心代码为:

C 复制代码
		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,防止它反复执行
		}

实际场景下可以做相应的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

相关推荐
limengshi1383921 小时前
通信工程学习:什么是IP网际协议
网络·网络协议·学习·tcp/ip·信息与通信
IM_DALLA3 小时前
【Verilog学习日常】—牛客网刷题—Verilog快速入门—VL70
学习·fpga开发·verilog学习
布丁不叮早起枣祈3 小时前
10.6学习
学习
望森FPGA3 小时前
HDLBits中文版,标准参考答案 | 3.1.2 Multiplexers | 多路复用器
学习·fpga开发
985小水博一枚呀3 小时前
【深度学习基础模型】稀疏自编码器 (Sparse Autoencoders, SAE)详细理解并附实现代码。
人工智能·python·深度学习·学习·sae·autoencoder
小魏冬琅3 小时前
命题逻辑与谓词逻辑 - 离散数学系列(二)
学习
9毫米的幻想4 小时前
【C++】—— 继承(上)
c语言·开发语言·jvm·c++·学习
mljy.4 小时前
C++《string》
c++·学习
gongyuandaye4 小时前
《数据密集型应用系统设计》笔记——第二部分 分布式数据系统(ch5-9)
笔记·分布式·ddia
2301_815389375 小时前
【笔记】Day1.1.24测试
笔记