上期我们学习了如何通过定时器产生PWM波控制LED的亮度,现在我们开始学习串口通信
UART:通用异步收发器(Universal Asynchronous Receiver/Transmitter:UART),一种异步串行通信协议,原理是通过信号线将需要发送的数据以二进制的形式一位一位的发送出去,在传输的过程中高电平表示发送的数据为‘1’,低电平表示数据‘0’,
在串口通信的过程中,数据往往以数据帧的格式进行传输,数据帧的一般格式如下:
一个完整的数据帧应该包含:起始位、数据位、停止位。
无校验:无校验的情况下数据位为8位,不对数据通信过程中的数据正确与否进行检验
奇校验:奇校验的情况下数据位为9位,第九位根据前八位数据计算而来,当前八位数据中1的个数的偶数个,则第九位为1,反之如果为奇数个,则为0.
偶校验:偶校验则是在第九位补0或1,使得这九位数字中1的总数始终为偶数个
1.串口1的控制寄存器SCON(可位寻址)和PCON(不可位寻址)
2. 串行口数据缓存数据寄存器SUBF:
在串口通信的过程中,发送/接收的数据都会存入SBUF寄存器,通过对TI/RI的判断,可以进行对SBUF寄存器的数据写入/读出.
3.辅助寄存器AUXR(不可位寻址):
- S1ST2:为0时通过定时器1为串口1提供波特率,为1时通过定时器2为串口1提供波特率,在串口初始化的时候应该将该位置0
根据SM0/SM1的值,串口的工作模式有以下四种:
SM0 | SM1 | 工作模式 |
---|---|---|
0 | 0 | 工作模式0:同步移位寄存器 |
0 | 1 | 工作模式1:8位UART,无校验位,波特率可调 |
1 | 0 | 工作模式2:9位UART,波特率固定,可使用奇偶校验 |
1 | 1 | 工作模式3:9位UART,波特率可调,可使用奇偶校验 |
常见波特率与定时器1各参数关系图:
举个栗子:
在工作模式1的情况下,波特率由以下公式计算:
波特率 = (2^SOMD /32) * SYSclk / 12 / (256_TH1) ----(定时器1工作模式为8位自动重装载)
将上述公式化简: 256-TH1 = (波特率 * 12 * 32)/ (2^SMOD * SYSclk)
假设波特率为 9600,SMOD = 0 则: 256 - TH1 = (96001232) / 12MHZ = 3.072
TH1 = 256-3 = 253 = 0xFD
串口初始化函数:
1.设置定时器1工作模式2八位自动重装
2.代开定时器1 ,设置波特率(设置TH1和TL1的初始值)
3.设置串口工作模式,是否允许接收(设置SCON寄存器)
4.打开串口中断和总中断允许
5.设置定时器1为串口1提供波特率
具体函数如下:
//串口初始化函数,设置模式为波特率可调,八位数据位,通过定时器1提供波特率
void Uart_Init(void)
{
TMOD |= 0x20; //定时器1工作模式为八位自动重装载,
TL1 = TH1 = 0xFD; //设置波特率为9600
TR1 = 1; //打开定时器1
SCON = 0x50; //设置串口工作模式为波特率可调,八位数据位,无校验,允许接受
ES = 1; //打开串口中断允许
EA = 1; //打开中断总允许
AUXR = 0x00; //设置定时器1为串口1提供波特率,
}
串口中断服务函数:
//串口中断服务函数
void Uart_Hander() interrupt 4
{
}
高四位 | 低四位 | 说明 | |
---|---|---|---|
控制灯光指令1 | A | L4 L3 L2 L1 | 第四位每位控制一个LED灯,0表示关灯,1表示开灯 |
控制灯光指令2 | B | L8 L7 L6 L5 | 例如: 0xA3,打开L1、L2,关闭L3、L4 |
读取信息指令 | C | 0 | 发送字符串:“it is OK!”,回车换行。 |
1.main.c
#include
#include "Interrupt.h"
#include "LS138.h"
unsigned char Uart_arr[8] = {'1','2','3','4',5,6,7,8}; //用于存放串口接受的内容
//更具指令改变LED的状态,
void Uart_ControlLED(unsigned char dat)
{
switch(dat & 0xF0)
{
case 0xA0: LED_Write((P0 |0x0F)&(~dat | 0xF0)); //改变低四位LED的亮起情况
break;
case 0xB0: LED_Write((P0 |0xF0)&((~dat << 4)| 0x0F)); //改变高四位LED的亮起情况
break;
case 0xC0: Uart_SerdData("it is OK! \r\n"); //发送字符串"it is OK!"
break;
}
}
void main()
{
Uart_Init(); //串口初始化
Uart_SerdData("Hello world! \r\n"); //发送字符串"HEello world!"
while(1)
{
}
}
//串口中断服务函数
void Uart_Hander() interrupt 4
{
static unsigned char i =0;
if(RI)
{
Uart_arr[i%8] = SBUF;
RI = 0;
Uart_SendByte(Uart_arr[i%8]); //将接受到的内容发送回去
Uart_ControlLED(Uart_arr[i%8]); //通过接受到的指令,改变LED的状态
i++;
}
}
2.串口初始化函数:Uart_Init()
//串口初始化函数,设置模式为波特率可调,八位数据位,通过定时器1提供波特率
void Uart_Init(void)
{
TMOD |= 0x20; //定时器1工作模式为八位自动重装载,
TL1 = TH1 = 0xFD; //设置波特率为9600
TR1 = 1; //打开定时器1
SCON = 0x50; //设置串口工作模式为波特率可调,八位数据位,无校验,允许接受
ES = 1; //打开串口中断允许
EA = 1; //打开中断总允许
AUXR = 0x00; //设置定时器1为串口1提供波特率,
}
3.LED控制函数:LED_Write()
//控制对应的LED亮起,亮起的情况由dat决定,例如: dat = 0xF0 则对应:L1到L4亮起,L5到L8熄灭
void LED_Write(unsigned char dat)
{
LS138_Clear();
LS138_Set(4);
P0 = dat;
}
这里重点讲一下Uart_ControlLED()函数的实现原理:
同样 假设 P0 = 0011 1000 , dat = 1010 1001 (L1和L4亮起)
那么我们需要P0 的值是 : 0011 0110(同样是L1和L4亮起)
则可以进行如下操作: (P0 | 0x0F)&(~dat | 0xF0)
P0 | 0x0F = 0011 1111 , ~dat | 0xF0 = 1111 0110
两者按位与完之后就是 0011 0110 刚好是我们希望的。