zl程序教程

您现在的位置是:首页 >  系统

当前栏目

【蓝桥杯单片机组模块】11、UART 模块

模块单片机 蓝桥 11 uart
2023-09-11 14:20:36 时间

微信搜索ReCclay,也可免费阅读博主蓝桥系列所有文章,后台回复“代码”即可获取蓝桥所有备赛代码!关注博主公众号,还可拥有加入博主粉丝群实时沟通技术难题、免费下载CSDN资源等多项福利,还在等什么呢?快快扫码关注,学习才不会迷路

在这里插入图片描述

这里再向各位同学推荐一个CSDN博主 ReRrain 的蓝桥备赛博客,博主秉持初学者思路,向你讲述自己蓝桥备赛的心路历程,娓娓道来蓝桥备赛经验,个人觉得非常不错,值得细细品读。


导读:《蓝桥杯单片机组》专栏文章是博主2018年参加蓝桥杯的单片机组比赛所做的学习笔记,在当年的比赛中,博主是获得了省赛一等奖,国赛二等奖的成绩。成绩虽谈不上最好,但至少问心无愧。如今2021年回头再看该系列文章,仍然感触颇多。为了能更好地帮助到单片机初学者,今年特地抽出时间对当年的文章逻辑和结构进行重构,以达到初学者快速上手的目的。需要指出的是,由于本人水平有限,如有错误还请读者指出,非常感谢。那么,接下来让我们一起开始愉快的学习吧。

不积跬步无以至千里,不积小流无以成江海。


这年过的,哈哈哈,都忘了这茬事,昨天和二弟三弟一起竟然看了一下午电影,真是够够了。不扯淡了,马上就要开学了,我得抓紧把模块先全搞完了!

一、基础理论

记住重要一点,串口是低位在先,高位在后!

二、动手实验

UART模块,51内集成有硬件模块,直接调用寄存器即可使用。但是嘛,我们既然打着学习的旗号,又岂有不去瞧瞧软件模拟的道理!

/*
*******************************************************************************
* 文件名:main.c
* 描  述:UART的软件模拟
* 作  者:CLAY
* 版本号:v1.0.0
* 日  期: 2018年2月17日
* 备  注:硬件模拟有硬件模拟的便利,软件让我们更知其所以然。
*         串口通信低位在先,高位在后。起始信号低电平,停止信号高电平。
*******************************************************************************
*/

#include <stc15.h>
typedef unsigned char u8;
typedef unsigned int  u16;
typedef unsigned long u32;

sbit PIN_TXD = P2^0;
sbit PIN_RXD = P2^1;

void CloseFucker();
void ConfigUART(u16 baud);
void StartRxd();
void StartTxd(u8 dat);

bit TxdEnd = 0;
bit RxdEnd = 0;
bit TxdOrRxd = 0; // 1-发; 2-接。
u8 TxdBuf = 0;
u8 RxdBuf = 0;

void main()
{
	EA = 1;
	CloseFucker();
	ConfigUART(9600);

	while(1)
	{
	 	while(PIN_RXD);
		StartRxd();
		while(!RxdEnd);
		StartTxd(RxdBuf+1);
		while(!TxdEnd);

	}
}

void CloseFucker()
{
 	P2 = (P2 & 0x1F) | 0xA0;
	P0 = P0 & 0xAF;
	P2 = P2 & 0x1F;
}

void ConfigUART(u16 baud)
{
	TMOD &= 0xF0;
	TMOD |= 0x02;
	TH0 = 256 - (11059200/12)/baud;	
}

void StartRxd()
{
 	TL0 = 256 - ((256-TH0)>>1); //在半个波特率周期开始接受
	ET0 = 1;
	TR0 = 1;
	RxdEnd = 0;
	TxdOrRxd = 0; 
}

void StartTxd(u8 dat)
{
	TxdBuf = dat;
	TL0 = TH0;
	ET0 = 1;
	TR0 = 1;
	PIN_TXD = 0;
	TxdEnd = 0;
	TxdOrRxd = 1;		
}

void InterruptTimer0() interrupt 1
{
	static u8 cnt = 0;

	if(TxdOrRxd)//发送
	{
	 	cnt++; //起始位已经发送完毕

		if(cnt <= 8)
		{
		 	PIN_TXD = TxdBuf & 0x01;
			TxdBuf >>= 1;
		}
		else if(cnt == 9)//注意发送停止位这次过程中只需要操作PIN_TXD,置位Txd_End关闭定时器是在第十次中进行
		{
		 	PIN_TXD = 1;
		}
		else
		{
			cnt = 0;
		 	TR0 = 0;
			TxdEnd = 1;
		}
	}
	else  //接收
	{
	 	if(cnt == 0)
		{
		 	if(!PIN_RXD)
			{
			 	RxdBuf = 0;
				cnt++;
			}
			else
			{
				TR0 = 0; 	
			}
		}
		else if(cnt <= 8)
		{
			RxdBuf >>= 1;			
			if(PIN_RXD)
			{
			 	RxdBuf |= 0x80;//注意是低位在先,所以低位先到缓冲区高位!
			}
			cnt ++;
		}
		else
		{
		 	cnt = 0;
			TR0 = 0;
			if(PIN_RXD)
			{
			 	RxdEnd = 1;	
			}
		}
	}
}

上面的模拟,其实还算是比较简练直观的。
实际的串口模块要比模拟的更复杂和精确点!而且,STC89C52 单片机来讲,这个波特率发生器只能由定时器 T1 或定时器 T2 产生,而不能由定时器 T0 产生。

/*
*******************************************************************************
* 文件名:main.c
* 描  述:硬件UART
* 作  者:CLAY
* 版本号:v1.0.0
* 日  期: 2018年2月17日
* 备  注:注意,15的AUXR寄存器必须配置下!同时PCON寄存器最好也配置下!
*         
*******************************************************************************
*/

#include <stc15.h>
typedef unsigned char u8;
typedef unsigned int  u16;
typedef unsigned long u32;


void ConfigUART(unsigned int baud);

void main()
{
    ConfigUART(9600);  //配置波特率为9600
    
    while (1)
    {
        while (!RI);     //等待接收完成
        RI = 0;          //清零接收中断标志位
        SBUF = SBUF + 1; //接收到的数据+1后,发送回去
        while (!TI);     //等待发送完成
        TI = 0;          //清零发送中断标志位
    }
}

void ConfigUART(unsigned int baud)
{
	PCON &= 0x7F;  //波特率不倍速
	AUXR &= 0xBF;  //定时器1时钟为Fosc/12,即12T
	AUXR &= 0xFE;  //串口1选择定时器1为波特率发生器

    SCON  = 0x50;  //配置串口为模式1
    TMOD &= 0x0F;  //清零T1的控制位
    TMOD |= 0x20;  //配置T1为模式2
    TH1 = 256 - (11059200/12/32)/baud;  //计算T1重载值
//	TH1 = 0xFD;    //这个是上面初值
    TL1 = TH1;     //初值等于重载值
    ET1 = 0;       //禁止T1中断
    TR1 = 1;       //启动T1
}

更进一步

/*
*******************************************************************************
* 文件名:main.c
* 描  述:硬件UART
* 作  者:CLAY
* 版本号:v1.0.1
* 日  期: 2018年2月17日
* 备  注:使用串口中断不放到while(1)里面了!
*         
*******************************************************************************
*/

#include <stc15.h>

void ConfigUART(unsigned int baud);

void main()
{
	EA = 1;
    ConfigUART(9600);  //配置波特率为9600
    
    while (1);
}

void ConfigUART(unsigned int baud)
{
	PCON &= 0x7F;  //波特率不倍速
	AUXR &= 0xBF;  //定时器1时钟为Fosc/12,即12T
	AUXR &= 0xFE;  //串口1选择定时器1为波特率发生器

    SCON  = 0x50;  //配置串口为模式1
    TMOD &= 0x0F;  //清零T1的控制位
    TMOD |= 0x20;  //配置T1为模式2
    TH1 = 256 - (11059200/12/32)/baud;  //计算T1重载值
//	TH1 = 0xFD;    //这个是上面初值
    TL1 = TH1;     //初值等于重载值
    ET1 = 0;       //禁止T1中断
	ES = 1;
    TR1 = 1;       //启动T1
}

void InterruptUART() interrupt 4
{
 	if(RI)
	{
		RI = 0;
	 	SBUF = SBUF + 1;
	}
	if(TI)
	{
	 	TI = 0;
	}
}

再继续深入

/*
*******************************************************************************
* 文件名:main.c
* 描  述:数码管显示ASCII
* 作  者:CLAY
* 版本号:v1.0.0
* 日  期: 2018年2月17日
* 备  注:串口中断发送并回显,数码管显示!
*         
*******************************************************************************
*/
#include <stc15.h>

typedef unsigned char u8;
typedef unsigned int  u16;
typedef unsigned long u32;


u8 code LedChar[] = {
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
u8 LedBuff[] = {
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};

u8 RxdByte = 0;
u8 T0RH = 0;
u8 T0RL = 0;

void ConfigUART(u16 baud);
void ConfigTimer0(u16 ms);

void main()
{
 	EA = 1;
	ConfigTimer0(1);
	ConfigUART(9600);

	while(1)
	{
	 	LedBuff[0] = LedChar[RxdByte & 0x0F];
		LedBuff[1] = LedChar[RxdByte >> 4];
	}
}

void ConfigUART(u16 baud)
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x50;		//8位数据,可变波特率
	AUXR &= 0xBF;		//定时器1时钟为Fosc/12,即12T
	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;
	TMOD |= 0x20;
	TH1 = 256 - (11059200/12/32)/baud;
	TL1 = TH1;
	ET1 = 0;
	ES = 1;
	TR1 = 1;
}

void ConfigTimer0(u16 ms)
{
	u32 tmp = 0;
	
	tmp	= 11059200 / 12;
	tmp = (tmp * ms) / 1000;
	tmp = 65536 - tmp;
	T0RH = (u8)(tmp >> 8);
	T0RL = (u8)tmp;

	TMOD &= 0xF0;
	TMOD |= 0x01;
	TH0 = T0RH;
	TL0 = T0RL;
	ET0 = 1;
	TR0 = 1;
}

void LedScan()
{
 	static u8 index = 0;

	P2 = (P2 & 0x1F) | 0xE0;
	P0 = 0xFF;
	P2 = P2 & 0x1F;

	P2 = (P2 & 0x1F) | 0xC0;
	P0 = 0x80 >> index;
	P2 = P2 & 0x1F;

	P2 = (P2 & 0x1F) | 0xE0;
	P0 = LedBuff[index];
	P2 = P2 & 0x1F;

	if(index < 7)
		index++;
	else
		index = 0;
}

void InterruptTimer0() interrupt 1
{
	TH0 = T0RH;
	TL0 = T0RL;

	LedScan();
}

void InterruptUART() interrupt 4
{
 	if(RI)
	{
	 	RI = 0;
		RxdByte = SBUF;
		SBUF = RxdByte;
	}
	if(TI)
	{
	 	TI = 0;
	}
}

emmm,再看一个升级版的!

/*
*******************************************************************************
* 文件名:main.c
* 描  述:数码管显示ASCII
* 作  者:CLAY
* 版本号:v1.0.1
* 日  期: 2018年2月17日
* 备  注:任意字符触发或关闭流水灯
*         
*******************************************************************************
*/
#include <stc15.h>

typedef unsigned char u8;
typedef unsigned int  u16;
typedef unsigned long u32;

u8 code LedChar[] = {
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
u8 LedBuff[] = {
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};

bit flag200ms = 0;
bit flagLight = 0;
u8 RxdByte = 0;
u8 T0RH = 0;
u8 T0RL = 0;

void ConfigUART(u16 baud);
void ConfigTimer0(u16 ms);
void CloseLed();
void FlowLight();
void CloseFucker();

void main()
{
 	EA = 1;
	ConfigTimer0(1);
	ConfigUART(9600);
	CloseFucker();

	while(1)
	{
		if(flagLight == 0)
		{
		 	CloseLed();	
		}
		else
		{
			if(flag200ms)
			{
				flag200ms = 0;
		 		FlowLight();
			}
		}
	 	LedBuff[0] = LedChar[RxdByte & 0x0F];
		LedBuff[1] = LedChar[RxdByte >> 4];
	}
}

void CloseFucker()
{
 	P2 = (P2 & 0x1F) | 0xA0;
	P0 = P0 & 0xAF;
	P2 = P2 & 0x1F;
}

void FlowLight()
{
	static bit dir = 0;
	static u8 shift = 0x80;

	P2 = (P2 & 0x1F) | 0x80;
	P0 = ~shift;
	P2 = 0x00;

	if(dir == 0)
	{
	 	shift >>= 1;
		if(shift == 0x01)
			dir = 1;
	}
	else
	{
	 	shift <<= 1;
		if(shift == 0x80)
			dir = 0;
	}
			
}

void CloseLed()
{
 	P2 = (P2 & 0x1F) | 0x80;
	P0 = 0xFF;
	P2 = P2 & 0x1F;
}

void ConfigUART(u16 baud)
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x50;		//8位数据,可变波特率
	AUXR &= 0xBF;		//定时器1时钟为Fosc/12,即12T
	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;
	TMOD |= 0x20;
	TH1 = 256 - (11059200/12/32)/baud;
	TL1 = TH1;
	ET1 = 0;
	ES = 1;
	TR1 = 1;
}

void ConfigTimer0(u16 ms)
{
	u32 tmp = 0;
	
	tmp	= 11059200 / 12;
	tmp = (tmp * ms) / 1000;
	tmp = 65536 - tmp;
	T0RH = (u8)(tmp >> 8);
	T0RL = (u8)tmp;

	TMOD &= 0xF0;
	TMOD |= 0x01;
	TH0 = T0RH;
	TL0 = T0RL;
	ET0 = 1;
	TR0 = 1;
}

void LedScan()
{
 	static u8 index = 0;

	P2 = (P2 & 0x1F) | 0xE0;
	P0 = 0xFF;
	P2 = P2 & 0x1F;

	P2 = (P2 & 0x1F) | 0xC0;
	P0 = 0x80 >> index;
	P2 = P2 & 0x1F;

	P2 = (P2 & 0x1F) | 0xE0;
	P0 = LedBuff[index];
	P2 = P2 & 0x1F;

	if(index < 7)
		index++;
	else
		index = 0;
}

void InterruptTimer0() interrupt 1
{
	static u8 tmr200ms = 0;

	TH0 = T0RH;
	TL0 = T0RL;

	LedScan();
	tmr200ms++;
	if(tmr200ms == 200)
	{
	 	tmr200ms = 0;
		flag200ms = 1;
	}
}

void InterruptUART() interrupt 4
{
 	if(RI)
	{
	 	RI = 0;
		RxdByte = SBUF;
		SBUF = RxdByte;

		flagLight = !flagLight;
	}
	if(TI)
	{
	 	TI = 0;
	}
}

三、总结

串口的使用多用在调试中。实际应用中帧模式的空闲串口实现也很有意思,值得研究一下!最后必须祭上一个实用的串口调试程序:

uart.c

/*
*******************************************************************************
* 文件名:uart.c
* 描  述:
* 作  者:CLAY
* 版本号:v1.0.0
* 日  期: 
* 备  注:
*         
*******************************************************************************
*/
#include "config.h"


void UartInit()		//9600bps@11.0592MHz
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x50;		//8位数据,可变波特率
	AUXR &= 0xBF;		//定时器1时钟为Fosc/12,即12T
	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFD;		//设定定时初值
	TH1 = 0xFD;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
}

void SendData(u8 dat)
{
 	SBUF = dat;
	while(!TI);
	TI = 0;
}

void SendString(u8 *str)
{
 	while(*str != '\0')
	{
	 	SendData(*str++);
	}
}

uart.h

/*
*******************************************************************************
* 文件名:uart.h
* 描  述:
* 作  者:CLAY
* 版本号:v1.0.0
* 日  期: 
* 备  注:
*         
*******************************************************************************
*/

#ifndef UART_H
#define UART_H

extern void UartInit();
extern void SendData(u8 dat);
extern void SendString(u8 *str);

#endif

小结:本篇文章主要介绍了单片机学习中的一个进阶模块:串口模块。该模块主要用于频率测量,本文章从基础理论到试验以及试验踩坑,都有涉及。说实话该模块在比赛中用的并不是很多,但是在实际应用中却非常重要,还希望大家可以认真学习一下。

希望大家多多支持我的原创文章。如有错误,请大家及时指正,非常感谢。


微信搜索ReCclay,即可免费阅读博主蓝桥系列所有文章,后台回复“代码”即可获取蓝桥所有备赛代码!关注博主公众号,还可拥有加入博主粉丝群实时沟通技术难题、免费下载CSDN资源等多项福利,还在等什么呢?快快扫码关注,学习才不会迷路

在这里插入图片描述