0

0

0

修罗

站点介绍

只有了解事实才能获得真正的自由

UART(三)发送单字节命令

修罗 2025-08-02 51 0条评论 51单片机

首页 / 正文

UART(三)发送单字节命令

​ 使用UARTPC进行通信,通过PC向单片机发送命令,控制LED的亮灭

​ 当前需求是实现PC单片机的串口通讯,但是现在的PC基本都不再提供串口,因此需要使用一个USB转串口的芯片来实现PC与单片机的通讯,如下图所示。

image-20250802145251590.png

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,因此需要将SM0SM1做出如下配置。

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; // 清除接收中断标志
    }
    
    ...
        
}

结果会发生什么?

  1. 调用 Dri_UART_SendStr(),第一个字符调用 SendChar
  2. is_sending == 0,于是 SBUF 写入第一个字节;
  3. 等待发送完成 —— 但 你在中断里TI == 1 不能触发新的中断;
  4. 所以 中断函数死等 is_sending == 0 永远不会成功
  5. 程序卡死在中断里,后面所有中断(包括接收)都不来了,系统死锁
  • 修改代码
// 接收到的数据存放在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");
            }
        }
    }
}

评论(0)


最新评论

  • 1

    1

  • 1

    1

  • -1' OR 2+158-158-1=0+0+0+1 or 'TKCTZnRa'='

    1

  • 1

    1

  • 1

    1

  • 1

    1

  • 1

    1

  • @@5Qa2D

    1

  • 1

    1

  • 1

    1

日历

2025年09月

 123456
78910111213
14151617181920
21222324252627
282930    

文章目录

推荐关键字: Linux webpack js 算法 MongoDB laravel JAVA jquery javase redis