UART(三)发送单字节命令
使用UART
与PC
进行通信,通过PC
向单片机发送命令,控制LED
的亮灭
当前需求是实现PC
单片机的串口通讯,但是现在的PC
基本都不再提供串口,因此需要使用一个USB
转串口的芯片来实现PC
与单片机的通讯,如下图所示。
1.1 具体要求
- 当
PC
向单片机发送字符A时,单片机需要令LED
亮起,并向PC
回复:Ok: LED is on。 - 当
PC
向单片机发送字符B时,单片机需要令LED
熄灭,并向PC
回复:Ok: LED is off。 - 当
PC
向单片机发送其他字符时,单片机不做任何操作,只需向PC
回复:Error:Unknown command。
1.2 实现思路
实现当前需求,需要先对串口进行初始化,具体操作如下。
1.2.1 选择串口工作模式
本案例选择模式1,因此需要将SM0
和SM1
做出如下配置。
SM0 = 0;
SM1 = 1;
1.2.2 设置波特率
本案例波特率选用9600
,需要做如下配置。
(1)SMOD控制位
按照前文的计算,将SMOD
设置为0
即可,由于SMOD
不可进行位寻址,因此我们需要对其所在的寄存器PCON
进行整体赋值,如下。
PCON &= 0x7F;
(2)定时器1
按照前文的计算,定时器1应工作在模式2(8位自动重装载),每次重装载的初始值应为253
。具体设置如下。
// 定时器1工作模式
TMOD &= 0x0F;
TMOD |= 0x20;
// 定时器1的初值
TH1 = 0xFD;
TL1 = 0xFD;
// 启动定时器1
TR1 = 1;
(3)串口接收相关配置
串口默认不接收数据,因此需要先使能接受,另外还需将SCON
寄存器中的SM2
控制位设置为0,表示接受数据时不校验数据帧的停止位。
REN = 1;
SM2 = 0;
(4)启动串口中断
// 开启中断
EA = 1;
// 开启串口中断
ES = 1;
// 复位中断标志位
RI = 0;
TI = 0;
完成串口的初始化之后,根据需求编写响应的业务逻辑即可。
1.3 代码
1.3.1 初始化
void Dri_UART_Init()
{
// 1.设置串口工作模式
SM0 = 0;
SM1 = 1;
// 2.设置波特率
// 2.1 设置SMOD控制位
PCON &= ~0x80;
// 2.2设置定时器1工作模式
TMOD &= 0x0F;
TMOD |= 0x20;
// 2.3 设置定时器1初值
TH1 = 0xFD;
TL1 = 0xFD;
// 2.4 启动定时器1
TR1 = 1;
// 3.串口接收相关配置
REN = 1;
SM2 = 0;
// 打开中断总开关和串口中断开关
EA = 1;
ES = 1;
TI = 0;
RI = 0;
}
1.3.2 串口中断函数
当收到上位机发送的'A'命令后,打开led,收到'B'命令后,关闭led:
void Dri_UART_Func() interrupt 4
{
// 收到命令,将数据放入缓冲区
if (RI == 1) {
if(SBUF == 'A'){
P0 = 0x00;
}else if(SBUF == 'B'){
P0 = 0xFF;
}
RI = 0; // 清除接收中断标志
}
if (TI == 1) {
TI = 0; // 清除发送中断标志
}
}
1.3.3 发送一个字符数据
void Dri_UART_SendChar(char ch)
{
SBUF = ch;
}
1.3.4 发送一个字符串
void Dri_UART_SendStr(char *str)
{
while (*str) {
Dri_UART_SendChar(*str);
str++;
}
}
循环调用发送一个字符的函数,直接写 SBUF
,但没有检查上一个字符是否已经发完,这样可能会丢字符!
- 修改发送单个字符函数,添加全局变量
is_sending
避免了数据在未发送完成时被覆盖;
// 发送状态,1:正在发送,0:未在发送
static bit is_sending;
void Dri_UART_SendChar(char ch)
{
// 如果有数据在发送,等待发送完成
while (is_sending);
is_sending = 1;
SBUF = ch;
}
// 初始化函数
void Dri_UART_Init()
{
...
// 初始化函数添加
is_sending = 0;
}
// 中断函数
void Dri_UART_Func() interrupt 4
{
...
// 发送完成,将发送状态置为0
if (TI == 1) {
is_sending = 0;
...
}
}
1.3.5 收到指令后回复字符串
中断函数添加逻辑:
void Dri_UART_Func() interrupt 4
{
// 收到命令,将数据放入缓冲区
if (RI == 1) {
if(SBUF == 'A'){
...
Dri_UART_SendStr("LED is on")
}else if(SBUF == 'B'){
...
Dri_UART_SendStr("LED is off")
}else{
Dri_UART_SendStr("Error:Unknown command")
}
RI = 0; // 清除接收中断标志
}
...
}
结果会发生什么?
- 调用
Dri_UART_SendStr()
,第一个字符调用SendChar
; is_sending == 0
,于是 SBUF 写入第一个字节;- 等待发送完成 —— 但 你在中断里,
TI == 1
不能触发新的中断; - 所以 中断函数死等
is_sending
== 0 永远不会成功; - 程序卡死在中断里,后面所有中断(包括接收)都不来了,系统死锁。
- 修改代码
// 接收到的数据存放在buffer中
char buffer;
// 调用 Dri_UART_RecvChar() 从 buffer 中取出接收到的数据;
bit Dri_UART_RecvChar(char *p_ch)
{
// 如果缓冲区有数据,则将值符传给p_ch,并清空缓冲区
if (buffer) {
*p_ch = buffer;
buffer = 0;
return 1;
} else {
return 0;
}
}
// 中断函数
void Dri_UART_Func() interrupt 4
{
if (RI == 1) {
buffer = SBUF; // 收到命令,将数据放入缓冲区
...
}
...
}
// 初始化函数
void Dri_UART_Init()
{
...
// 初始化函数添加
buffer = 0;
}
函数使用
void main()
{
char command;
Dri_UART_Init();
while (1) {
if (Dri_UART_RecvChar(&command)) {
if (command == 'A') {
// 点亮LED
P0 = 0x00;
Dri_UART_SendStr("Ok:LED is on");
} else if (command == 'B') {
// 熄灭LED
P0 = 0xFF;
Dri_UART_SendStr("Ok:LED is off");
} else {
// 报错
Dri_UART_SendStr("Error:Unknown command");
}
}
}
}
1.3.6 单字节接收完整代码
Dri_UART.h
#ifndef __DRI_UART_H__
#define __DRI_UART_H__
#include <STC89C5xRC.H>
#include "Util.h"
/**
* @brief 串口初始化方法,需要先调用
*
*/
void Dri_UART_Init();
/**
* @brief 通过串口发送一个字符串
*
* @param ch 要发送的字符串
*/
void Dri_UART_SendStr(char *str);
/**
* @brief 通过串口接收一个字符
*
* @param p_ch 要接收的字符指针
*
* @return 0为读取失败,1为读取成功
*/
bit Dri_UART_RecvChar(char *p_ch);
#endif
Dri_UART.c
#include "Dri_UART.h"
#define BAUD_RATE 9600
#define T2TEMP 256 - (FOSC / NT / 32 / BAUD_RATE)
char buffer;
// 发送状态,1:正在发送,0:未在发送
static bit is_sending;
void Dri_UART_Init()
{
// 1.设置串口工作模式
SM0 = 0;
SM1 = 1;
// 2.设置波特率
// 2.1 设置SMOD控制位
PCON &= ~0x80;
// 2.2设置定时器1工作模式
TMOD &= 0x0F;
TMOD |= 0x20;
// 2.3 设置定时器1初值
TH1 = 0xFD;
TL1 = 0xFD;
// 2.4 启动定时器1
TR1 = 1;
// 3.串口接收相关配置
REN = 1;
SM2 = 0;
// 打开中断总开关和串口中断开关
EA = 1;
ES = 1;
TI = 0;
RI = 0;
is_sending = 0;
buffer = 0;
}
void Dri_UART_SendChar(char ch)
{
// 如果有数据在发送,等待发送完成
while (is_sending);
is_sending = 1;
SBUF = ch;
}
void Dri_UART_SendStr(char *str)
{
while (*str) {
Dri_UART_SendChar(*str);
str++;
}
}
bit Dri_UART_RecvChar(char *p_ch)
{
// 如果缓冲区有数据,则将值符传给p_ch,并清空缓冲区
if (buffer) {
*p_ch = buffer;
buffer = 0;
return 1;
} else {
return 0;
}
}
/**
* @brief 串口中断函数,进入这个函数有两个触发条件:发送完成和接收完成
*
*/
void Dri_UART_Func() interrupt 4
{
// 收到命令,将数据放入缓冲区
if (RI == 1) {
buffer = SBUF;
RI = 0;
}
// 发送完成,将发送状态置为0
if (TI == 1) {
is_sending = 0;
TI = 0;
}
}
main.c
#include <STC89C5xRC.H>
#include "Dri_UART.h"
void main()
{
char command;
Dri_UART_Init();
while (1) {
if (Dri_UART_RecvChar(&command)) {
if (command == 'A') {
// 点亮LED
P0 = 0x00;
Dri_UART_SendStr("Ok:LED is on");
} else if (command == 'B') {
// 熄灭LED
P0 = 0xFF;
Dri_UART_SendStr("Ok:LED is off");
} else {
// 报错
Dri_UART_SendStr("Error:Unknown command");
}
}
}
}
1
1
1
1
1
1
1
1
1
1