# 使用虚拟终端模拟串口控制LED灯
## 项目简述
### 题目选择
个人选择的题目的是第五题,即
要求:利用现有电路,使用串口模拟终端,向单片机发送命令,用于控制LED灯。不同命令控制不同的显示效果可自行设计。
选择第五题的理由是题目包含的信息量看起来更多,也看起来更加有趣一点(笑
### 项目整体结构
1. 电路部分
电路部分的设计并未有变化,即还是使用原先的8052.dsn。
<img src="./8052.PNG">
2. C语言代码部分
代码部分分为主要分为两个部分,负责主体逻辑的test.c文件(包含main函数)和负责配置串口,发送数据的UART.c 和UART.h文件。
<img src="./代码部分.PNG">
在UART.h 中,定义了uart_init,uart_sendByte,UART_Routine这几个函数,uart_init函数负责在最开始时配置串口相关的寄存器参数,uart_sendByte函数负责通过发送单个字符至终端,UART_Routine为中断函数,负责接受终端输入并控制LED的亮灭,而这几个函数的具体实现则在UART.c中。
在test.c中,首先引用reg52.h和UART.h这两个头文件,之后在main函数中调用uart_init进行初始化,并调用uart_sendByte函数发送0x4F,0x4B(即OK)。
## 细节分析
### 串口
UART(Universal Asynchronous Receiver/Transmitter,即通用异步收发器)串行通信是单片机最常用的一种通信技术,通常用于单片机和其他设备之间进行通信。其相关寄存器如图所示:
<img src="寄存器.PNG">
即我们在使用串口进行通讯之前,首先要配置相关的寄存器,本程序的配置在uart_init函数中实现
```c
void uart_init()//9600bps@11.0592MHz
{
SCON = 0x50;
TMOD = 0x20;
TH1 = 0xfd;
TL1 = TH1;
PCON = 0x00;
EA = 1;
ES = 1;
TR1 = 1;
}
```
这里可以简单讲一下赋值的依据,首先可以看见我们将SCON寄存器赋为0x50,即(01010000),依据如下:
<img src="SCON.PNG">
图中可以看见SM0、SM1的值对应四种工作方式,我们选择的工作方式是方式1,故SM0=0,SM1=1。即最开头的01,之后可以看见我们又将REN的值也调为1,其作用如下图所示:
<img src="REN.PNG">
在SCON其余的值对本程序不影响,故设置为0。
PCON寄存器主要负责电源控制,作用如图:
<img src="PCON.PNG">
图中有点错误在于SMOD=0是各工作方式波特率正常,故赋位0,其余各位也暂无影响,故也赋值为0,即PCON=0x00。
### 定时器
说完了SCON和PCON,可以发现还有几个寄存器没有提到,即TMOD,TH1,TL1,TR1(负责时钟配置),和EA,ES(负责中断配置)。
在串口通讯的过程中,定时器被用作通信的时钟源。因为在全双工的uart串口通讯过程中必定存在与外界设备沟通的时序问题,所以也应当会存在一个时钟源(个人猜测)。相关寄存器如图所示:
<img src="定时器.PNG">
其中,TMOD寄存器的作用如下图所示,
<img src="TMOD.PNG">
我们需要修改的部分只有定时器1的控制模式
<img src="TMOD45.PNG">
选择8位自动重装定时器模式,则TMOD=0x20(00100000)。
在设置完定时器1的模式后,我们需要规定串口的波特率。在使用串口做通讯时,一个很重要的参数就是波特率,只有上下位机的波特率一样时才可以进行正常通讯。而波特率是指串行端口每秒内可以传输的波特位数,具体类似于串口的传输速率。
波特率的计算公式如图:
<img src="波特率.PNG">
<img src="溢出速率.PNG">
而T1即定时器1在模式2下的溢出率由TL1值决定,TH1与TL1相同,负责设定定时器1的重装值。这里我们采用的参数是较为经典的9600波特率,晶振11.0592M。整体的计算过程较为复杂,有个偷懒的办法,交给软件去算,以下截图为stc-isp软件自动生成的结果。
<img src="stc.PNG">
多了一些其他参数,但无伤大雅(*^_^*)。
### 中断
在前面提到过配置部分除了定时器的配置外,还配置了相关的中断设置,那么中断的具体定义如图:
<img src="中断.PNG">
那么本次使用的中断请求为串口(uart)中断,可以理解为当串口收到数据时,cpu会暂停当前的工作(main函数),进而去处理中断函数所规定的内容。(虽然个人有点不太明白为什么不能写成在main函数中轮询SBUF寄存器,可能是模块化?),那么首先应当允许使用该中断,即:
<img src="EA.PNG">
```c
EA = 1;
ES = 1;
```
接下来需要编写对应的中断函数,因为中断函数不在main函数中调用,所以需要知道其中断号以标识其为中断函数。而串口中断号在下图中显示
<img src ="中断号.PNG">
那么以此编写中断函数代码
```c
void UART_Routine() interrupt 4
{
if(RI==1) //如果接收标志位为1,接收到了数据
{
if(SBUF>0x2f&&SBUF<0x3a)
{
int k=SBUF-0x30;
P0 = ~(1<<k);//点亮对应LED灯
};
uart_sendByte(0x0D); //换行符
uart_sendByte(SBUF); //将受到的数据发回串口
uart_sendByte(0x0D);
RI=0; //接收标志位清0
}
}
```
在该函数中,我们首先检验是否收到了数据,接下来从SBUF寄存器中读出该数据,如果该数据在0~7范围内(对应acsii码为0x30-0x37),则点亮该对应的led灯,最后将收到的数据再返回串口,接受标志位置0。
同时将数据发回串口的功能由uart_sendByte函数实现,代码如下
```c
void uart_sendByte(unsigned char byte)
{
SBUF=byte;//赋值缓冲区寄存器
while(TI==0);//判断发送中断请求后标志位是否归位
TI=0;//若未归位则置标志位为0
}
```
依据如图:
<img src="TI.PNG">
## 运行结果
按下运行键后可得如图所示,首先先向终端发送“OK”字符。
<img src="运行结果1.PNG">
接着向终端输入1,可以看见第二个LED亮起
<img src="输入1.PNG">
再输入6,可以看见第七个LED亮起
<img src="输入6.PNG">