人生的第一次串口打印
双机通讯是不是很神奇,特别是自己实现的,今天就实现一个最经典的“hello.word!”的打印
在上一篇文章中,学会了串口的配置,如果还有不懂得,可以去看看
第一步,串口是如何工作的
还是以8位UART格式为例,一帧信息是10位,也就是10bit:一个起始位,八个数据位(低位在先),一个停止位。所以串口每执行一次写或者读SUBF时,都是传输一帧10位数据,波特率的意思就是一秒钟可以发送多少帧数据,比如9600bsp,就是1秒钟可以接受9600/10=960帧的数据。
执行过程
以模式一8位UART为例,串口发送数据时,数据由串行发送端TXD输出,当主机执行一条写SBUF指令,就启动串行通信发送,当数据全部通过移位寄存器,打开控制器开关TI = 1,将数据发送出去,一般这样简单理解就可以,没必要深究。
所以第一步就是配置串口,上一篇文章已经配置好了
void Uart1_Init(void) // 9600bps@22.1184MHz
{
SCON = 0x50; // 8位数据,可变波特率
AUXR |= 0x40; // 定时器时钟1T模式
AUXR &= 0xFE; // 串口1选择定时器1为波特率发生器
TMOD &= 0x0F; // 设置定时器模式
TL1 = 0xC0; // 设置定时初始值
TH1 = 0xFD; // 设置定时初始值
ET1 = 0; // 禁止定时器中断
TR1 = 1; // 定时器1开始计时
ES = 1; //串口中断打开
EA = 1; //总中断打开
}
然后就是发送一个字节
void Uart1_SendByte(uint8_t b)
{
// Uart1.tx_flag = 0;
SBUF = b; // 发送一个字节,将其值赋给SBUF
// while(!Uart1.tx_flag);
while (TI == 0);
TI = 0; // 数据发送完后TI自动置一,需要软件置零
}
一共两种方式,都可以,任选一种,不过更建议采用有标志位的,可以做一些判断操作。比如
if(Uart1_tx_flag == 1)
{
/******写自己要做的操作****/
}
有时候不仅仅发送一个字节,可能会是一个字符串,就需要重复写一个字节的过程
void Uart1_SendStr(uint8_t *str)
{
while (*str) {
Uart1_SendByte(*str++); // 将串口传进来的值取地址,并且将地址上的值依次送给SBUF
}
}
我们一直说想通过printf打印一些关键节点,这就需要进行串口重定向
什么意思呢,就是51单片机没办法直接将printf的值通过打印出来,需要借助putchar()来实现,相当于是printf和单片机串口之间的翻译或者说桥梁
char putchar(char c)
{
Uart1_SendByte(c);
return c;
}
有时候我们会对传回来的值进行校验,判断是否是我们想要的,有时候会对传输值进行判断。比如起始值,控制位。校验码,结束符等等关键字符,这个怎么做呢,我的思路比较简单,就是将传回来的值,放到一个数组里面,然后通过提取数组中的值进行判断,符合要求的,输出数据段,不符合要求的,就舍弃这段数据,通过printf可以判断哪一步产生了哪些值。
言过正传,先写串口的打印字符串,
前面的写了这么多,会有人问,那RI怎么处理
接下来就是RI的判断处理了,一般是在中断函数中进行的
void Uart1_Isr(void) interrupt 4
{
if (TI == 1) {
TI = 0;
Uart1.tx_flag = 1;
}
if (RI == 1) // 数据接收完成后自动置一
{
Uart1.tx_flag = 1;
RI = 0; // 数据接收完成后由软件自动清零
}
}
写完串口函数,接下来就是主函数的打印,首先我们测试打印一个字符串
void main(void)
{
Uart1_SendStr("Hello,word!");
while(1)
{
///
}
}
打开串口助手,然后按复位键,就显示了hello,word!
,这种是最简单的,是写在程序里面的,我们要通过串口给他写值,让他打印该怎么做呢,继续往下看:
我们可以在终端中做一些处理
首先,我们先定义一个结构体,用来存放这些接收的数据值
#define RX_MAX (64U)
typedef struct
{
uint8_t tx_flag;
uint8_t rx_flag;
uint8_t rx_len;
uint8_t rx_buffer[RX_MAX];
}TsUart;
然后在中断函数中,做一些处理,当传回来的值大于6,小于64的时候,我们认为其有效,将这部分值提取出来,放到我们的结构体数组中,进行处理
void Uart1_Isr(void) interrupt 4
{
uint8_t temp;
if (TI == 1) {
TI = 0;
Uart1.tx_flag = 1;
}
if (RI == 1) // 数据接收完成后自动置一
{
if (Uart1.rx_len < RX_MAX)
{
Uart1.rx_buffer[Uart1.rx_len++] = SBUF; // 将值放在Uart1.rx_buffer[]中
}
if (Uart1.rx_len >= 6)
{
Uart1.rx_flag = 1;
}
if (Uart1.rx_len >= RX_MAX) {
temp = SBUF;
Uart1.rx_len = RX_MAX;
Uart1.rx_flag = 1;
}
RI = 0; // 数据接收完成后由软件自动清零
}
}
然后我们可以声明一个检查函数,用来查看和检验,比如我们要看传回来几位数,里面的内容是什么
void Uart1_ReceiveCheck(unsigned char d)
{
uint8_t i;
printf("value: ");
for (i = 0; i < d; i++)
{
// printf("0x%02bX ", Uart1.rx_buffer[i]);
Uart1_SendByte(Uart1.rx_buffer[i]);
// printf("%c ", Uart1.rx_buffer[i]);
}
printf("\r\n");
}
一共举了三个例子,第一个是以16进制查看接受的值,第二个就是直接将接收的值打印出来,第三个是以字符方式打印出来
具体什么格式输出可以参考这篇link
主函数中的while用作处理就行
while (1)
{
if (Uart1.rx_flag == 1)
{
Uart1.rx_flag = 0;
printf("Uart1.rx_len = %02bd\r\n", Uart1.rx_len);
Uart1_ReceiveCheck(Uart1.rx_len);
memset(Uart1.rx_buffer, 0, Uart1.rx_len); // 清除緩存值
Uart1.rx_flag = 0;
Uart1.rx_len = 0;
}
}
这篇有点长了,那天有时间,将前面说的校验值,控制位讲一下,因为有的需要这种判断
例如
写这个纯脆为了自学巩固,哪里错里很正常,欢迎大佬指正!