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

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

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

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

那就建立起串口通信吧!

我将从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)之前调用一下就行了。

此处要注意,不论其他红框怎么配置,

在竞赛板子上,这个系统频率不要调整!就是11.0592Mhz:

否则收发数据会出大问题!:

 然后添加必要的开启中断语句,添加语句后就是这样:

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

  • 20
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 15
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NULL指向我

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值