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);
#endifDri_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");
}
}
}
}
Ценники на вывоз мусора сейчас космос, любые лайфхаки по экономии в тему.
1xbet onlayn kazino platforması ilə vaxtınızı daha səmərəli və maraqlı keçirin. Saytın canlı kazino bölməsi istifadəçilərə fərqli bir dünya təklif edir. Burada canlı dilerli kazino oyunlarının hər bir növünü sınaqdan keçirə bilərsiniz. Krupyelərin peşəkarlığı oyunun dinamikasını və marağını artırır. Müntəzəm olaraq yenilənən oyun siyahısı cansıxıcılığa yol vermir. Hesabınızı artırmaq üçün onlarla fərqli və etibarlı üsul mövcuddur. Qazanılan pullar cəmi bir neçə dəqiqə ərzində şəxsi hesabınıza köçürülür. Aktiv fəaliyyətinizə görə fərdi bonuslar və hədiyyələr qazana bilərsiniz. Dəstək xidməti həm çat, həm də e-poçt vasitəsilə kömək göstərir. 1xbet ailəsinə qoşulun və böyük uduşlar üçün ilk addımı atın. onlayn kazino
我一直梦想, 看到你们相册那样的地方。真棒。
我喜欢, 这里分享真实经验。你的项目 就是 属于这里的。请继续。
我热爱这样的想法, 去那么多国家。真的很鼓舞。
能感受到热爱。继续保持 感受。
我关注这样的资源, 写得很实在。你的网站 就是 属于这里的。加油。
1
1
1