蓝桥杯单片机串口通信学习提升笔记

news2024/11/24 16:00:51

今日得以继续蓝桥杯国赛备赛之旅:

有道是 “不知何事萦怀抱,醒也无聊,醉也无聊,梦也何曾到谢桥。

那我们该如何 让这位诗人纳兰 “再听乐府曲 ,畅解相思苦”呢?

那就建立起串口通信吧!

我将从SCT15F2K61S2单片机串口通信基础出发,逐步缓解此等繁琐愁杂。

附上工程文件下载传送门:

https://download.csdn.net/download/qq_64257614/87800670?spm=1001.2014.3001.5503

本篇文章除了学习基础,还主要研究学习以下问题:

1.在蓝桥杯单片机实训平台上串口通信实现printf()函数

2.蓝桥杯单片机串口通信发送变量乱码问题

3.接收中断数据处理的实现

4.字符串与变量拼接到一起,变成新的字符串。

目录

一、串口通信知识基础:

二、串口通信基础程序设计:

总设计方针:

首先生成我们要用的串口初始化程序:

其次就是编写发送与接受函数:

中断服务函数:(这个要先定义俩变量):

三、串口通信的一些处理操作:

输出的处理:

1.重定义改造printf函数:

2.连续发送单个字节:

3. sprintf 函数可以格式化输出:

输入的处理:

 4.ASCLL码与发送变量值的处理:

5.字符串与变量的拼接:

6.中断接收字符串:

三、实践拓展延伸:

实验要求:

程序设计:

#include "usart_main.h"

#include "Public.h"

#include "uart.h"



一、串口通信知识基础:

串口按位(bit)发送和接收字节的通信方式

IAP15F2k61s2单片机内部有俩个采用UART工作方式的全双工串行通信接口,

全双工就是俩个单工的结合,A与B俩个机能同时收发数据。

此外半双工,单工就不做介绍,大家可以百度,这个了解就可以了。

每个串行口的组成:

      2个数据缓冲器:(SBUF)     串口1的地址是99H,串口2的地址是9BH。 

     1个移位寄存器:

     1个串行控制器:   (SCON)

     1个波特率发生器:

串口1和串口2:

串口1对应硬件部分是TxD和RxD

串口1可以在:

【P30/RXD,P31/TXD】、【P36/RXD_2,P37/TXD_2】、【P1^6/RXD_3,P17/TXD_3】

之间切换。

(1)串口1可以使用定时器1、定时器2为波特率发生器

(2)定时器2默认为16位自动重装载模式

(3)定时器2的时间常数保存在T2H、T2L

串口2对应硬件部分是TxD2和RxD2

串口2可以在:

【P10/TXD2,P11/RXD2】、【P46/RXD2_2,P47/TXD2_2】

之间切换。

二、串口通信基础程序设计:

总设计方针:

步骤1~4不需要我们自己背记,可用软件生成,下面会介绍。

   步骤5~7需要加在软件生成的函数里面。

                1.设定定时器1工作方式(TMOD寄存器)

                2.计算波特率,如果用的是定时器1作为波特率发生器,那就赋值给TH1,TL1

                3.设置AUXR寄存器

                4.设置SCON寄存器

                5.打开定时器1

                6.使能串口中断 ES

                7.开总中断EA

串口通信中需要用到的寄存器SCON——串行通信控制寄存器(一般设置为0x50)

首先生成我们要用的串口初始化程序:

这里不需要我们背寄存器了,直接用STC_isp软件生成:

如图设定好要用的串口1、频率、波特率等

这个初始化代码没有需要改动的地方,但后续有添加语句,

这个void UartInit(void)函数就是串口1的初始化函数了,

一般在主函数while(1)之前调用一下就行了。

 添加语句后就是这样:

void UartInit(void)		//9600bps@11.0592MHz
{
	SCON = 0x50;		 //8位数据,可变波特率
	AUXR |= 0x40;		 //定时器时钟1T模式
	AUXR &= 0xFE;		 //串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;		 //设置定时器模式
	TL1 = 0xE0;		   //设置定时初始值
	TH1 = 0xFE;		   //设置定时初始值
	ET1 = 0;		     //禁止定时器%d中断
	TR1 = 1;		     //定时器1开始计时
	
  REN = 1;         //允许串口接受
  ES = 1;          //开启串口中断
  EA = 1;          //开启总中断
}

其次就是编写发送与接受函数:

发送一个字节函数:

//发送一个字节
void SendData(unsigned char dat)
{
	SBUF=dat;	//把要发送的数据存入写SBUF
	while(TI == 0);  //等待发送标志位置1
	TI = 0;
}

发送字符串函数:


void Uart_Sendstring(unsigned char *pucStr)
{
	while(*pucStr!='\0')
	{
		SBUF=*pucStr;
		while(TI==0);   //等待串口发送完成
			TI=0;					
		 //因为TI在串口发送后,硬件自动置1,
     //我们需要重新发送数据的话,就需要在置1后,软件清零,
		pucStr++;
	}
}

中断服务函数:(这个要先定义俩变量):

这样写可以接收上位机数据进行分析

u8 R_sign;			//接收缓冲区内容完成标志
u8 R_dat;				//用于接收缓冲区内容

//串口1中断服务函数
void Uart_1_serv() interrupt 4
{
    if(RI)    //查询串口接收程序,如果接收标志位为1,说明有数据接收完毕
    {
        RI = 0;        //清除接收标志位
        R_dat = SBUF;  //将接收缓冲区内容转至R_dat变量中
        R_sign = 1;    
    }
}

三、串口通信的一些处理操作:

输出的处理:

1.重定义改造printf函数:

printf()函数的使用需要注意引用头文件 stdio.h

然后需要重定向putchar()函数

这样以后就能用printf()函数向串口打印数据了

这类思想常见于嵌入式的程序设计,在我的另一篇文章有所研究:

使用printf()函数时该注意,会占用大量资源

嵌入式硬件中Printf函数的原理_NULL指向我的博客-CSDN博客

void send_date(unsigned char date){
	SBUF = date;
	while(!TI);
	TI = 0;
}

char putchar (char ch){
	send_date(ch);
	return ch;
}

 printf()用法举例:

unsigned char test=128;


printf("Welcome to stc15f2k60s2\n");
printf("%d",test);

2.连续发送单个字节:

void send_string(unsigned char *date, unsigned char len){

	while(len--)
	{
		send_date(*date);
		date ++;
	}
}

我们有时要输出变量,用这种方式就要把变量转换成字符型:

cent + '0' ; 

3. sprintf 函数可以格式化输出:

	sprintf(string,"num:%d",(int)cent);
	send_string(string,strlen(string));	

输入的处理:

通过将接收到的数据保持到一个数组当中,

就可以之后在循环中对接收到的数据进行判断。

此段代码是接收中断函数的处理更改

	if(RI==1){
	  str[rev] = SBUF;
		RI = 0;		
		rev++;
	}
	else{
		TI = 0;
	}

            if(str[rev-1] == '\n'){
				if(str[0] == 'S' && str[1] == 'T')
				{
						printf("STOK\r\n");
				}
				rev = 0;
				for(i = 0; i<8;i++){
					str[i] = '\0';	
				}

 4.ASCLL码与发送变量值的处理:

单片机与上位机之间的通信出现问题。例如我如下方式发送一个变量test:

会发现接收不是我们要的效果

	u8 test=1;


	printf("%c",test+'0');
	SendData(test+'0');

总所周知,ASCII码是由八位二进制小数进行一映射的,从0-127的数字分别对应了不同字符:

造成这样的结果在于上位机给我们发送的是ASCII码,

并且上位机接收显示的也是ASCII码,而不是数值大小。

也就是说,我们给上位机发 “1”,上位机实际接收到的是0000_0001(B),

对应ASCII为SOH(SOH(start of headline) 标题开始),

因此上位机并不显示1,而是显示SOH,

要想上位机显示1,应该给它发送49(u8)才对。

同理,上位机发送“1“,我们实际接收到的为49(u8)

因此应该减去48才为我们需要得到的数字1。

在单片机中,我们的数字一律是实际的数值,我们定义变量时,

定义为u8,u6,就是代表无符号的整数,单片机串口发送也是发送这些数值。

而在上位机中,则是发ASCII码,显示ASCII码。

以下为ASCLL码对照表:

我们如果把发送的数据改成:

uint8_t Data[10]={'1','2','3','4'};

那么这样上位机就可以完美接收到1234。

单引号代表取这个符号的ASCII值,因此比较字符串,

可以使用Data[x]=='A',这样的操作进行比较。

5.字符串与变量的拼接:

可以使用C语言中的字符串拼接函数strcat()来将字符串和整数变量拼接在一起。

下面是一个示例代码:

#include <stdio.h>
#include <string.h>

int main() {
    char str1[20] = "Hello";
    int num = 123;
    char str2[10];

    // 将整数转换为字符串格式
    sprintf(str2, "%d", num);

    // 使用strcat()函数将两个字符串拼接在一起
    strcat(str1, str2);

    printf("%s", str1);

    return 0;
}

输出结果为:Hello123

在代码中,我们首先定义了一个字符串str1和一个整数num

然后,我们使用sprintf()函数将整数转换为字符串格式并将其保存在另一个字符串str2中。

最后,我们使用strcat()函数将str1str2拼接在一起,并将结果打印到控制台上。

6.中断接收字符串:

像这样编写即可:

//中断接收字符串,根据选择接收字符还是字符串
unsigned char i;
unsigned char string[20];
void Usart_interrupt() interrupt 4  //用与接收字符串
{
  if (RI) {
    RI = 0;
    string[i] = SBUF;
    if (string[i] == '\n')  //回车为接收结束标志
    {
      i = 0;
    } else {
      i++;
    }
  }
}

三、实践拓展延伸:

实验要求:

/*
	本实验练习串口通信:主要练习以下内容:
	1.串口接受命令,串口中断并执行
	  接收到上位机传来0xff后,单片机发送'Hello'
		接收到上位机传来0xfe后,单片机发送'World'
		接收到上位机传来0xfd后,单片机发送'Hello World'
		关于接收到上位机其他数据,单片机都返回发送'Wrong Order'	
		
	2.串口发送字符,数字。
		用Uart_Sendstring()函数做一些1中指定的发送字符串的任务
			
	3.配置Printf函数,
	   开机用Printf函数向上位机发送'Welcome to stc15f2k60s2\n'
		 开机用Printf函数向上位机发送	变量test的值,test=1
*/
/*
	本实验有以下注意点:printf函数需要重定向
*/

程序设计:

具体设计没有特别难的地方,注意发送变量ASCLL码规则即可。

#include "usart_main.h"

#include "usart_main.h"


void main()
{
	u8 test=1;
//	u16 test2=1234;
	
	cls_buzz_led();				//关闭外设
	UartInit();						//初始化串口
	printf("Welcome to stc15f2k60s2\n");
	printf("%c",test+'0');
//	printf("\n");         //发送换行测试
	SendData(test+'0');
//	SendData('\n');       //发送换行测试
//	SendData(test+'0');	  //发送换行测试
	
	while(1)
	{	
		if(R_sign==1)									//如果有数据接收完毕
		{
			R_sign=0;
			switch(R_dat)
			{
				case 0xff:Uart_Sendstring("Hello");       break;
				case 0xfe:Uart_Sendstring("World");       break;
				case 0xfd:Uart_Sendstring("Hello World"); break;
				default :Uart_Sendstring("Wrong Order");break;	
			}
		}
	}
}
#ifndef _usart_main_h_
#define _usart_main_h_

#include "stc15f2k60s2.h"
#include "Public.h"
#include "stdio.h"
#include "uart.h"

#endif

#include "Public.h"

#include "Public.h"

void inint_74HC573(u8 select)             //74HC573片选函数(简记:0x53f0)
{
	switch(select)
	{
	case 4:	P2 = (P2 & 0X1F) | 0X80; break;//开启 Y4 LED端口 装填
	case 5:	P2 = (P2 & 0X1F) | 0XA0; break;//开启 Y5 ULN2003驱动端口 装填
	case 6:	P2 = (P2 & 0X1F) | 0XC0; break;//开启 Y6 数码管位选端口 装填
	case 7:	P2 = (P2 & 0X1F) | 0XE0; break;//开启 Y7 数码管段选端口 装填
	}
}

void cls_buzz_led()
{
	inint_74HC573(5);
	buzz = 0;
	inint_74HC573(4);
	P0 = 0xff;
}
#ifndef _Public_h_
#define _Public_h_

#include "stc15f2k60s2.h"

typedef unsigned int u16;
typedef unsigned char u8;

sbit buzz=P0^6;								//蜂鸣器

void inint_74HC573(u8 select);             //74HC573片选函数(简记:0x53f0)
void delay_xms(u8 xms);                    //传入参数 参数为几就 延时几毫秒

void cls_buzz_led();


#endif

#include "uart.h"

#include "uart.h"

u8 R_sign;			//接收缓冲区内容完成标志
u8 R_dat;				//用于接收缓冲区内容

void UartInit(void)		//9600bps@11.0592MHz
{
	SCON = 0x50;		 //8位数据,可变波特率
	AUXR |= 0x40;		 //定时器时钟1T模式
	AUXR &= 0xFE;		 //串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;		 //设置定时器模式
	TL1 = 0xE0;		   //设置定时初始值
	TH1 = 0xFE;		   //设置定时初始值
	ET1 = 0;		     //禁止定时器%d中断
	TR1 = 1;		     //定时器1开始计时
	
  REN = 1;         //允许串口接受
  ES = 1;          //开启串口中断
  EA = 1;          //开启总中断
}



//发送字符串函数
void Uart_Sendstring(unsigned char *pucStr)
{
	while(*pucStr!='\0')
	{
		SBUF=*pucStr;
		while(TI==0);   //等待串口发送完成
			TI=0;					
		 //因为TI在串口发送后,硬件自动置1,
     //我们需要重新发送数据的话,就需要在置1后,软件清零,
		pucStr++;
	}
}

//发送一个字节
void SendData(unsigned char dat)
{
	SBUF=dat;	//把要发送的数据存入写SBUF
	while(TI == 0);  //等待发送标志位置1
	TI = 0;
}
/*
//发送字符串,以'\0'形式结尾
void SendString(unsigned char *s)
{
    while (*s!='\0')              //检查是否到结尾
    {
        SendData(*s++);     //地址递增进行逐个发送
    }
}
*/
void send_date(unsigned char date){
	SBUF = date;
	while(!TI);
	TI = 0;
}

char putchar (char ch){
	send_date(ch);
	return ch;
}

//串口1中断服务函数
void Uart_1_serv() interrupt 4
{
    if(RI)    //查询串口接收程序,如果接收标志位为1,说明有数据接收完毕
    {
        RI = 0;        //清除接收标志位
			  R_dat = SBUF;  //将接收缓冲区内容转至R_dat变量中
        R_sign = 1;    
    }
}
#ifndef _uart_h_
#define _uart_h_

#include "stc15f2k60s2.h"
#include "Public.h"
#include "stdio.h"


extern u8 R_sign;
extern u8 R_dat;


void Uart_Sendstring(unsigned char *pucStr);
void UartInit(void);		//9600bps@11.0592MHz
void Uart_1_serv();
void SendData(unsigned char dat);


#endif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/552821.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

论文阅读_音频表示_W2V-BERT

信息 number headings: auto, first-level 2, max 4, _.1.1 name_en: w2v-BERT: Combining Contrastive Learning and Masked Language Modeling for Self-Supervised Speech Pre-Training name_ch: W2V-BERT&#xff1a;结合对比学习和Mask语言建模进行自监督语音预训练 pape…

Redis实现全局唯一Id

Redis实现全局唯一Id 全局唯一Id简介二、Redis实现全局唯一Id实践2.1添加RedisIdWorker配置类2.2测试类 全局唯一Id简介 系统当中有些场景如果使用数据库自增ID就存在一些问题&#xff1a; id的规律性太明显受单表数据量的限制 场景分析&#xff1a;如果我们的id具有太明显的…

基于UDP和TCP套接字实现简单的回显客户端服务器程序

目录 1. 套接字 2. 基于UDP 套接字实现的简单客户端 服务器程序 3. 基于TCP套接字实现的简单客户端 服务器程序 1. 套接字 之前我们有分享到协议分层这个概念,其中就讲到上层协议调用下层协议,下层协议给上层协议提供支持,这里支持指的是就是socket套接字,它是操作系统给应用…

宁波市天一杯 --- Crypto wp

文章目录 secretrsa secret 题目&#xff1a; p134261118796789547851478407090640074022214132682000430136383795981942884853000826171189906102866323044078348933419038543719361923320694974970600426450755845839235949167391987970330836004768360774676424958554946…

坦克大战进阶--发射子弹

坦克大战进阶–发射子弹 1. 坦克大战0.3 1.1 分析 利用线程基础的知识&#xff0c;把坦克大战再次进阶一下&#xff1a;当我们按下J键&#xff0c;坦克就能够发射一颗子弹。 1.2 思路 当发射一颗子弹后&#xff0c;就相当于启动一个线程Mytank 有子弹的对象&#xff0c;当…

MSP432笔记5——外部中断

所用单片机型号&#xff1a;MSP432P401r 今日继续我的MSP432电赛速通之路。 外部中断是个很有用的配置 STM32几乎每个I/O口都能配置复用为外部中断 但MSP432并不是这样。 我经过查阅数据手册发现支持中断的引脚为&#xff1a; P1^0~ P1^7 P3^0~ P3^7 P5^0~ P5^…

Gateway服务网关入门

Gateway服务网关 Spring Cloud Gateway 是 Spring Cloud 的一个全新项目&#xff0c;该项目是基于 Spring 5.0&#xff0c;Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关&#xff0c;它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。…

【网络字节序】

网络字节序 我们已经知道&#xff0c;内存中的多字节数据相对于内存地址有大端和小端之分&#xff0c;磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。网络数据流同样有大端小端之分&#xff0c;那么如何定义网络数据流的地址呢&#xff1f;发送主机通常将发送…

【C++】21年精通C++之泛型编程和模板初阶知识

❤️前言 大家好&#xff01;今天和大家一起学习关于C泛型编程和模板初阶的相关知识。 正文 我们之前已经学习了C中非常重要的一个特性——函数重载&#xff0c;函数重载很好地提高了我们代码的可读性。但是对于适配多种参数的某种函数来说&#xff0c;我们如果使用函数重载就…

感知程序从ros切换到cyber_rt框架下,pcl相关问题

1.在ubuntu20.04下&#xff0c;原感知程序需要的是pcl1.8.1&#xff0c;车上其他程序使用的是pcl.1.10.0或者pcl1.10.0&#xff0c;在编译pcl1.10.0时会编译通不过&#xff0c;而pcl1.10.1可以顺利编译通过&#xff0c;安装pcl1.8.1时遇到的问题可能如下&#xff0c;及对应的修…

CTF必看~ PHP反序列化漏洞6:绝妙_wakeup绕过技巧

作者&#xff1a;Eason_LYC 悲观者预言失败&#xff0c;十言九中。 乐观者创造奇迹&#xff0c;一次即可。 一个人的价值&#xff0c;在于他所拥有的。可以不学无术&#xff0c;但不能一无所有&#xff01; 技术领域&#xff1a;WEB安全、网络攻防 关注WEB安全、网络攻防。我的…

iptables防火墙2

iptables防火墙 一&#xff1a;SNAT原理与应用 SNAT 应用环境&#xff1a;局域网主机共享单个公网IP地址接入Internet&#xff08;私有不能早Internet中正常路由&#xff09;SNAT原理&#xff1a;修改数据包的源地址。 SNAT转换前提条件&#xff1a; 1.局域网各主机已正确设…

新星计划 Electron+vue2 桌面应用 2 搭建及运行

基础内容&#xff1a;新星计划 Electronvue2 桌面应用 1 基础_lsswear的博客-CSDN博客 根据使用过的经验和官网的描述&#xff0c;大概可以有四种方式&#xff1a; 自己创建项目&#xff08;仅使用npm&#xff09;用Electron脚手架HBuilder编译为web&#xff0c;再用Electron…

MSP432笔记4:时钟与滴答计时器

所用单片机型号&#xff1a;MSP432P401r 今日继续更新我的MSP432电赛速通笔记&#xff1a; 提示&#xff1a; 本节内容相当于讲述delay_ms&#xff08;&#xff09; 和delay_us&#xff08;&#xff09; 俩延时函数的由来&#xff0c; 所以不需要花费过多时间斟酌 MSP432单…

论文阅读_音频表示_wav2vec_2.0

论文信息 name_en: wav2vec 2.0: A Framework for Self-Supervised Learning of Speech Representations name_ch: wav2vec 2.0&#xff1a;语音表示自监督学习框架 paper_addr: http://arxiv.org/abs/2006.11477 date_read: 2023-04-27 date_publish: 2020-10-22 tags: [‘深…

C++深度解析:虚函数的使用与避免

C深度解析&#xff1a;虚函数的使用与避免 1. 虚函数的基本概念与原理 (Basic Concepts and Principles of Virtual Functions)1.1 虚函数的定义与作用 (Definition and Role of Virtual Functions)1.2 虚函数的底层实现 (Underlying Implementation of Virtual Functions)1.3 …

【CANN训练营0基础赢满分秘籍】进阶班 Atlas 200I DK 智能小车

1 智能小车三维结构设计 1.1 基本模块 坚固酷炫结构模块运动控制模块超声波传感器模块摄像头视觉模块其他传感器模块 1.2 结构设计基本原则 从零开始设计并搭建智能小车&#xff0c;在满足外观要求的基础上&#xff0c;要满足小车运转过程中的运动干涉率为O&#xff0c;并且…

【CANN训练营0基础赢满分秘籍】进阶班 应用开发深入讲解

1 AIPP AIPP (Artificial Intelligence Pre-Processing)人工智能预处理&#xff0c;在AI Corfe上完成数据预处理。 1.1 静态AIPP 构造AIPP配置文件*.cfg使能静态AIPP&#xff0c;将其配置参数保存在模型文件中。 atc --framework3--soc_versionS[soc_version) --model SHOM…

基于51单片机的电子琴Protues仿真设计

一、设计背景 基于51单片机的电子琴是一款由51单片机控制器、音频模块和硬件阵列组成的数字化乐器。它可以模拟各种乐器的音效&#xff0c;同时也具有许多常规电子琴所没有的高级功能。 首先&#xff0c;这种电子琴是以数字信号处理技术为基础的。通过软件编程&#xff0c;将…

【JUC】Java对象内存布局和对象头

【JUC】Java对象内存布局和对象头 文章目录 【JUC】Java对象内存布局和对象头1. 对象的内存布局1.1 对象头1.1.1 对象标记1.1.2 类元信息/类型指针 1.2 实例数据1.3 对齐填充 2. 测试 1. 对象的内存布局 在 HotSpot 虚拟机里&#xff0c;对象在堆内存中的存储布局可以划分为三…