速通串口通信

  • 串口通信可以说是蓝桥杯单片机考察的最难的一个模块了,如果你是参加第十六届蓝桥杯的选手,可以在比赛前花上一点时间学习以下串口通信的函数实现,以防省赛突然蹦出串口通信来,机会是留给有准备的人的。
  • 直接点击下方链接跳转到串口初始化函数开始学习即可,前面的寄存器不学对比赛没有影响。
    串口初始化

一、串行口控制寄存器SCON

在这里插入图片描述

1.RI

RI的功能简单来说就是接收中断请求标志位:数据接收完成时由硬件置为1,需手动清零

2.TI

TI的功能是发送中断请求标志位:数据发送完成时由硬件置为1,需手动清零

3.SM0、SM1

对于SM0、SM1只需要知道当SM0、SM1=01时,串口工作在方式一(8位UART,波特率可变)。

4.REN

允许/禁止串行中断接收控制位,REN置1时允许接收中断,置0时则反之。

5.其余位

其余位与串口中断没有直接关系,统一置为0即可。

综上所述,对于SCON的配置为:SCON = 0x50;

二、电源控制器PCON

在这里插入图片描述
只需知道SMOD置0即可,其他不用管。

三、数据缓存器SBUF

如果读者有学过计算机组成原理这门课,那肯定对SBUF寄存器不陌生吧,在此只需知道SBUF为数据的临时中转站即可,无需过多了解。
在这里插入图片描述

  • 串行发送时,CPU向SBUF写入数据,此时99H表示发送缓存SBUF。
  • 串行接收时:CPU从SBUF读取数据,此时99H表示接收缓存SBUF。
  • 数据发送:把数据扔进SBUF后,内核会自动将数据发送,内容发送完毕,TI标志位置1。
  • 数据接收:内核从串口接收到一个完整数据后,会将RI标志置1,用户用SBUF读取即可。

四、中断允许寄存器IE

在这里插入图片描述
只需知道EA、ES的功能即可。

五、串口初始化函数

如果你还没理解前面的寄存器功能是没有关系的,速成只需要会使用ISP对串口进行配置即可,配置如下图所示:
在这里插入图片描述

定时器时钟配置1T还是12T目测没有太大影响,最好设置12T模式

再将复制后的代码进行以下处理:

void Uart1_Init(void)	//9600bps@12.000MHz
{
	SCON = 0x50;		//8位数据,可变波特率
	AUXR |= 0x01;		//串口1选择定时器2为波特率发生器
	AUXR |= 0x04;		//定时器时钟1T模式
	T2L = 0xC7;			//设置定时初始值
	T2H = 0xFE;			//设置定时初始值
	AUXR |= 0x10;		//定时器2开始计时
	ES = 1;
	EA = 1;
}

六、字符串发送函数

1.方法一:不引用头文件(不推荐)

//单片机回复上位机(单个字节)
void SendByte(unsigned char dat)
{
	SBUF = dat;
	while(!TI);//死循环,等待字节发送
	TI = 0;//字节发送完成后TI会置1,计时清0
}

//单片机回复上位机(字符串)
//写法一:如果你刚开始学习C语言,写法一比较好理解
void SendString(unsigned char *dat, unsigned char i)
{
	unsigned char j;
    for (j = 0; j < i; j++) // 遍历数组中的所有元素
    {
        SendByte(dat[j]); // 发送当前字节
    }
}
//写法二
void SendString(unsigned char *dat)
{
	while(*dat != '\0')//当发送数组未到达终止符时
		SendByte(*dat++);//发送该字节,发送完成后数组往后移位
}

2.方法二:引用头文件

stdio.h头文件中,已经声明了putcharprintf这两个函数。
在这里插入图片描述
putchar函数需要用户自己定义,在底层函数中格式如下:

extern char putchar(char ch)
{
	SBUF = ch; // 将ch写入SBUF,发出数据
	while (TI == 0);// 等待发送完成
	TI = 0; // 清除发送完成标志
	return ch;
}

该函数是标准库函数 printf 的底层实现依赖。在嵌入式系统中,printf 会调用 putchar 将字符逐个发送到串口。
所以你要单片机回复上位机某个字符串时,只需按照以下方式编写代码即可。

//串口处理函数
void UartProc() 
{
	//处理数据溢出代码
	if(//上位机发送数据)
		printf("HELLO!");
}

七、串口中断函数

//全局变量定义
idata unsigned char uart_rec[10];//数据缓存数组
idata unsigned char uart_rec_index; //数据缓存数组索引

void Uart1Server() interrupt 4
{
	if(RI == 1)//串口数据接收完成
	{
		uart_rec[uart_rec_index] = SBUF;//读取数据缓存区的数据
		uart_rec_index++;//移位
		RI = 0;//重新接收
	}
}

八、防止数据溢出判断

上面写的代码存在一个致命的缺点就是没有考虑数据溢出,也就是说,当数据接收数组存满后,再发送串口数据会导致溢出,所以还需要加入溢出判断。

memset函数
调用该函数需要引入头文件#include <string.h>
函数调用格式:memset(*ptr,value,size)
*ptr:要改变的数组
value:改变后的值
size:设置的字节数
例如:memset(arr,0,3);表示将数组arr的第0位到第2位的数据赋值为0
也就是说,该函数等于以下代码:

unsigned char i;
for(i = 0; i < 3; i++)
	arr[i] = 0;
/*全局变量声明区*/
idata unsigned char uart_rec[10];//数据缓存存放数组
idata unsigned char uart_rec_index;//数据缓存存放数组索引
idata unsigned char systick;//滴答计时器
idata bit uart_flag;//处理数据标志位

//Uart初始化以及发送字节函数省略

void UartProc()//串口处理数据
{
	//当未开始串口通信时,不执行该函数
	if(!uart_rec_index)
		return;
	if(systick >= 10)//当滴答计时器计时10ms时,进行一次数据处理
	{
		systick = uart_flag = 0;
		
		//串口执行操作(自行编写)

		//执行完操作后,开始清除缓存区数据
		memset(uart_rec,0,uart_rec_index);//清空缓存区
		uart_rec_index = 0;//重置索引值
		
	}
}

void Uart1_Server() interrupt 4
{
	if(RI == 1)//接收到数据时
	{
		uart_flag = 1;//有数据传入,准备处理数据
		uart_rec[uart_rec_index] = SBUF;
		uart_rec_index++;
		RI = 0;
		if(uart_rec_index > 10)
		{
			memset(uart_rec,0,10);
			uart_rec_index = 0;//防止数据溢出
		}
	}
}

void Timer1_Init(void)		//1毫秒@12.000MHz
{
	AUXR &= 0xBF;			//定时器时钟12T模式
	TMOD &= 0x0F;			//设置定时器模式
	TL1 = 0x18;				//设置定时初始值
	TH1 = 0xFC;				//设置定时初始值
	TF1 = 0;				//清除TF1标志
	TR1 = 1;				//定时器1开始计时
	ET1 = 1;				//使能定时器1中断
	EA = 1;
}

void Timer1_Isr(void) interrupt 3
{
	if(uart_flag)//数据接收时,滴答计时器开始计时
		systick++;
}

九、完整代码及应用

模块化编程,将UartInit()putchar()函数放进Uart.c文件中,并在Uart.h中声明,在main.c中引入头文件Uart.h

1.Uart.h

#ifndef __Uart_H__
#define __Uart_H__

void UartInit(void);
extern char putchar(char ch);

#endif

2.Uart.c

#include <STC15F2K60S2.H>

void Uart1_Init(void)	//9600bps@12.000MHz
{
	SCON = 0x50;		//8位数据,可变波特率
	AUXR |= 0x01;		//串口1选择定时器2为波特率发生器
	AUXR |= 0x04;		//定时器时钟1T模式
	T2L = 0xC7;			//设置定时初始值
	T2H = 0xFE;			//设置定时初始值
	AUXR |= 0x10;		//定时器2开始计时
	EA = 1;
	ES = 1;
}

extern char putchar(char ch)
{
	SBUF = ch;
	while(!TI);//等待数据发送
	TI = 0;
	return ch;
}

3.main.c

#include <STC15F2K60S2.H> // 单片机寄存器专用头文件
#include <string.h>
#include <stdio.h>
#include "Init.h"
#include "Uart.h"	  // 串口底层驱动专用头文件
#include <intrins.h>

idata unsigned char uart_rec[10];
idata unsigned char uart_rec_index;
idata unsigned char systick;

idata bit uart_flag;

void Timer1_Init(void)		//1毫秒@12.000MHz
{
	AUXR &= 0xBF;			//定时器时钟12T模式
	TMOD &= 0x0F;			//设置定时器模式
	TL1 = 0x18;				//设置定时初始值
	TH1 = 0xFC;				//设置定时初始值
	TF1 = 0;				//清除TF1标志
	TR1 = 1;				//定时器1开始计时
	ET1 = 1;				//使能定时器1中断
}

void Timer1_Isr(void) interrupt 3
{
	if(uart_flag)
		systick++;
}


void UartProc()
{
	if(!uart_rec_index)
		return;
	if(systick >= 10)
	{
		systick = uart_flag = 0;

		//逻辑函数
		
		//清除数据
		memset(uart_rec,0,uart_rec_index);
		uart_rec_index = 0;
	}
}

void UartServer() interrupt 4
{
	if(RI == 1)
	{
		uart_flag = 1;
		uart_rec[uart_rec_index] = SBUF;
		uart_rec_index++;
		RI = 0;
		if(uart_rec_index > 10)
		{
			memset(uart_rec,0,10);
			uart_rec_index = 0;//防止数据溢出
		}
	}
}

void main()
{
	SystemInit();
	Timer1_Init();
	Uart1_Init();
	while(1)
	{
		UartProc();
	}
}

4.应用

应用1:上位机发送指定内容时单片机才回复

上位机向串口发送字符串ok,单片机收到后向上位机回复HELLO
只需在串口数据处理函数更改即可

void UartProc()
{
	if(!uart_rec_index)
		return;
	if(systick >= 10)
	{
		systick = uart_flag = 0;

		//逻辑函数
		if(uart_rec[0] == 'o' && uart_rec[1] == 'k')
		{
			printf("HELLO");
		}
		//清除数据
		memset(uart_rec,0,uart_rec_index);
		uart_rec_index = 0;
	}
}

串口现象:
在这里插入图片描述

应用2:单片机回复完成后换行

上位机向串口发送字符串ok,单片机收到后向上位机回复HELLO,并自动换行回复WORLD!
在末尾加上换行符\n即可

void UartProc()
{
	if(!uart_rec_index)
		return;
	if(systick >= 10)
	{
		systick = uart_flag = 0;

		//逻辑函数
		if(uart_rec[0] == 'o' && uart_rec[1] == 'k')
		{
			printf("HELLO\n");
			printf("WORLD!\n");
		}
		//清除数据
		memset(uart_rec,0,uart_rec_index);
		uart_rec_index = 0;
	}
}

串口现象:
在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

星光始终闪耀

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

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

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

打赏作者

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

抵扣说明:

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

余额充值