找回密码
 立即注册
查看: 97|回复: 0

学习记录:串口,双环形缓冲+非阻塞

[复制链接]
已绑定手机

20

主题

90

回帖

191

积分

注册会员

积分
191
发表于 2026-8-15 14:45:04 | 显示全部楼层 |阅读模式

#include <STC32G.H>
#include <stdio.h>
#include <intrins.h>

typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long u32;

#define MAIN_Fosc        22118400L
#define Baudrate1   (65536 - MAIN_Fosc / 115200 / 4)

// ==================== 接收环形缓冲区 ====================
#define RBUF_SIZE   128

u8  rbuf[RBUF_SIZE];
u16 rbuf_head = 0;
u16 rbuf_tail = 0;

int RB_IsEmpty(void) { return (rbuf_head == rbuf_tail); }
int RB_IsFull(void)  { return ((rbuf_tail + 1) % (int)RBUF_SIZE == rbuf_head); }

int RB_Push(u8 dat)
{
    if (RB_IsFull()) return -1;
    rbuf[rbuf_head] = dat;
    rbuf_head = (rbuf_head + 1) % (int)RBUF_SIZE;
    return 0;
}

int RB_Pop(u8 *pdat)
{
    if (RB_IsEmpty()) return -1;
    *pdat = rbuf[rbuf_tail];
    rbuf_tail = (rbuf_tail + 1) % (int)RBUF_SIZE;
    return 0;
}

// ==================== 发送环形缓冲区 ====================
#define TBUF_SIZE   128

u8  tbuf[TBUF_SIZE];
u16 tbuf_head = 0;    // 写指针(主循环中修改)
u16 tbuf_tail = 0;    // 读指针(中断中修改)

int TB_IsEmpty(void) { return (tbuf_head == tbuf_tail); }
int TB_IsFull(void)  { return ((tbuf_tail + 1) % (int)TBUF_SIZE == tbuf_head); }

// 向发送缓冲区写入一个字节(主循环中调用)
int TB_Push(u8 dat)
{
    if (TB_IsFull()) return -1;
    tbuf[tbuf_head] = dat;
    tbuf_head = (tbuf_head + 1) % (int)TBUF_SIZE;
    return 0;
}

// 从发送缓冲区读取一个字节(中断中调用)
int TB_Pop(u8 *pdat)
{
    if (TB_IsEmpty()) return -1;
    *pdat = tbuf[tbuf_tail];
    tbuf_tail = (tbuf_tail + 1) % (int)TBUF_SIZE;
    return 0;
}

// ==================== 串口相关变量 ====================
bit B_TX1_Busy = 0;

void UART1_config(u8 brt);
void PrintString1(u8 *puts);
void SetTimer2Baudraye(u32 dat);

// ==================== 主函数 ====================
void main(void)
{
    u8 dat;
   
    WTST = 0;
    EAXFR = 1;
    CKCON = 0;

    // IO口配置
    P0M1 = 0x30;   P0M0 = 0x30;
    P1M1 = 0x30;   P1M0 = 0x30;
    P2M1 = 0x3c;   P2M0 = 0x3c;
    P3M1 = 0x50;   P3M0 = 0x50;
    P4M1 = 0x3c;   P4M0 = 0x3c;
    P5M1 = 0x0c;   P5M0 = 0x0c;
    P6M1 = 0xff;   P6M0 = 0xff;
    P7M1 = 0x00;   P7M0 = 0x00;

    // 初始化缓冲区
    rbuf_head = 0;
    rbuf_tail = 0;
    tbuf_head = 0;
    tbuf_tail = 0;

    UART1_config(2);
    EA = 1;

    PrintString1("STC32G Dual Buffer Non-Blocking UART\r\n");

    while (1)
    {
        // ===== 步骤1:从接收缓冲区取数据,放入发送缓冲区 =====
        if (RB_Pop(&dat) == 0) {
            TB_Push(dat);   // 存入发送缓冲区,非阻塞
        }
        
        // ===== 步骤2:如果串口空闲且发送缓冲区有数据,触发发送 =====
        if (B_TX1_Busy == 0) {
            if (TB_Pop(&dat) == 0) {
                SBUF = dat;
                B_TX1_Busy = 1;   // 发送开始,不在这里等待!
            }
        }
        
        // ===== 步骤3:其他任务可以放在这里 =====
        // 比如:LED闪烁、按键扫描、ADC读取等
        // 注意:这些任务不能阻塞太久
    }
}

// ==================== 串口发送字符串(阻塞方式,仅用于初始化) ====================
void PrintString1(u8 *puts)
{
    for (; *puts != 0; puts++) {
        SBUF = *puts;
        while (!TI);
        TI = 0;
    }
}

// ==================== 定时器2配置 ====================
void SetTimer2Baudraye(u32 dat)
{
    T2R = 0;
    T2_CT = 0;
    T2x12 = 1;
    T2H = (u8)(dat / 256);
    T2L = (u8)(dat % 256);
    ET2 = 0;
    T2R = 1;
}

// ==================== 串口1初始化 ====================
void UART1_config(u8 brt)
{
    if (brt == 2) {
        S1BRT = 1;
        SetTimer2Baudraye(Baudrate1);
    } else {
        TR1 = 0;
        S1BRT = 0;
        T1_CT = 0;
        T1x12 = 1;
        TMOD &= ~0x30;
        TH1 = (u8)(Baudrate1 / 256);
        TL1 = (u8)(Baudrate1 % 256);
        ET1 = 0;
        TR1 = 1;
    }

    SCON = (SCON & 0x3f) | 0x40;
    ES = 1;
    REN = 1;
    P_SW1 &= 0x3f;
    P_SW1 |= 0x00;

    B_TX1_Busy = 0;
}

// ==================== 串口1中断 ====================
void UART1_int(void) interrupt 4
{
    // ===== 接收中断:只管存数据 =====
    if (RI) {
        RI = 0;
        RB_Push(SBUF);
    }
   
    // ===== 发送中断:只管取数据发送 =====
    if (TI) {
        TI = 0;
        B_TX1_Busy = 0;   // 发送完成,通知主循环可以继续发送
    }
}

之前以为单片机就是用C语言写个程序让他动起来。但是从这个环形缓冲加非阻塞来看,并不是那样的简单。首先要对单片机串口部分的工作原理和硬件结构有所了解,其次要有环形缓冲和非阻塞的这种编程思想,这需要很多阅读和工作的积累才行,再其次要清楚环形缓冲和非阻塞具体是怎么实现的,即通过取模运算(head+1) % SIZE 、不直接用TI标志等待导致程序卡死,设置TIbusy忙标志,主程序不等待,设置双环形缓冲区;这是小白在没有帮助的情况下完成不了的内容。
最后才是用C语言把程序写出来编译测试。这样看来小白欠缺的就是最重要的编程思想和经验啊,之前没有认识到。之前看到过这样的写法,当时不理解,不理解为什么有了TI还要再另外设一个标志位,感觉多此一举。B_TX1_Busy = 1;   // 设置忙标志,不在这里死等 这个标志位只是为了告诉程序,SBUF里面有要发送的数据,不要再往里装填了,避免覆盖。主程序继续干别的,串口硬件自动往外发送,所以主程序和串口是同时在继续进行。 嵌入式编程学习四层地图


层级
名称
描述

第一层
认识硬件了解串口怎么工作、寄存器怎么配、引脚怎么用

第二层编程思想知道什么时候该用缓冲、什么时候该用状态机、如何解耦

第三层具体实现取模运算、环形队列、忙标志、原子操作、指针访问

第四层C语言表达把上面的东西用C语言写出来,编译通过,跑起来

理解原理 → 建立模型 → 设计逻辑 → 用C语言表达 → 动起来


回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2026-9-23 16:10 , Processed in 0.062556 second(s), 41 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表