- 串口通信可以说是蓝桥杯单片机考察的最难的一个模块了,如果你是参加第十六届蓝桥杯的选手,可以在比赛前花上一点时间学习以下串口通信的函数实现,以防省赛突然蹦出串口通信来,机会是留给有准备的人的。
- 直接点击下方链接跳转到串口初始化函数开始学习即可,前面的寄存器不学对比赛没有影响。
串口初始化
一、串行口控制寄存器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
五、串口初始化函数
如果你还没理解前面的寄存器功能是没有关系的,速成只需要会使用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
头文件中,已经声明了putchar
和sprintf
这两个函数。
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;}
}
串口现象: