MSPM0L1306 从零到入门:第六章 UART —— 让单片机与世界“对话”

第六章 UART ------ 让单片机与世界"对话"

到目前为止,我们的单片机还像一个"独行侠",只能通过闪烁的LED来表达自己。本章,我们将为它开启一扇与外部世界沟通的窗户------UART (通用异步收发器)。通过它,我们可以让单片机向电脑发送调试信息,也可以通过电脑向单片机发送控制指令,实现真正的双向交互。

1. 什么是UART?------ 两个设备间的"电话线"

UART是一种串行(Serial)、**异步(Asynchronous)**的通信协议。

  • 串行:想象一条单车道,所有数据(0和1)像一列自行车一样,一个接一个地在一条线上进行传输。这与一次能走8辆车的"并行"通信相比,大大节省了引脚资源。
  • 异步 :通信的双方没有共享一根时钟线。它们就像两个约定好语速的人打电话,只要双方的"语速"(即波特率 Baud Rate )一致,就能正确理解对方。为了在没有时钟的情况下同步,UART在每个数据包前后都加上了特殊的信号:
    • 起始位 (Start Bit):一个固定的低电平,通知接收方:"嘿,我要开始说话了!"
    • 数据位 (Data Bits):通常是8位,是我们真正要传输的数据。
    • 停止位 (Stop Bit):一个或多个固定的高电平,表示:"这句话说完了。"

(文字时序图: 空闲(高) -> 起始位(低) -> [D0,D1,D2,D3,D4,D5,D6,D7] -> 停止位(高) -> 空闲(高))

2. 硬件连接:从MCU引脚到PC的"翻译官"

如您所述,我们的开发板上集成了一颗CH340E芯片。这颗芯片扮演着"翻译官"的角色:

  • 它将PC的USB信号,翻译成MCU能听懂的TTL电平串口信号
  • 它将MCU的TTL电平串口信号,翻译成PC能识别的USB信号。

在我们的原理图中:

  • MCU的PA22 (UART0_RX)引脚连接到CH340E的发送端。
  • MCU的PA23 (UART0_TX)引脚连接到CH340E的接收端。

注意 :通信双方的接线永远是 TX (发送) → RX (接收)RX (接收) → TX (发送)

3. 软件设计:从SysConfig到健壮的应用

3.1 SysConfig:图形化配置UART

和之前一样,我们优先使用SysConfig来完成底层的初始化配置,而不是手写这些复杂的结构体。

  1. 打开.syscfg文件,在"SOFTWARE"中添加并选择 UART 模块。
  2. 基本配置 (Basic Configuration)
  • Mode : Normal
  • Direction : TX and RX
  • Parity : None
  • Desired Baud Rate : 输入 9600。SysConfig会自动计算出下方的IBRDFBRD值(也就是您代码中的263)。
  1. 引脚配置 (Pin Configuration)
  • RX Pin : 选择 PA22
  • TX Pin : 选择 PA23
  1. 中断配置 (Interrupt Configuration)
  • 勾选 Enable RX Interrupt,允许接收中断。
  • 在NVIC配置中,确保UART0的中断是使能的,并可以为其分配合理的优先级。
  1. 保存 。SysConfig会自动生成您草稿中展示的SYSCFG_DL_UART_0_init()函数及相关的 gUART_0ClockConfiggUART_0Config 结构体。

3.2 发送数据:让单片机"开口说话"

3.2.1 阻塞式发送:最简单但需谨慎

您的草稿中提供了一个基础的发送函数:

c 复制代码
// 发送单个字节
void uart_send_char(uint8_t ch)
{
    // 等待,直到发送缓冲区为空(硬件发送完上一个字节)
    while(DL_UART_Main_isBusy(UART_0_INST));
    DL_UART_Main_transmitData(UART_0_INST, ch);
}

// 发送字符串
void uart_send_string(char* str)
{
    while(*str != '\0')
    {
        uart_send_char(*str++);
    }
}

【深度剖析】 : while(DL_UART_Main_isBusy(...)) 是一种阻塞式操作。如果UART因为某些原因一直处于忙碌状态,程序就会卡死在这里。在简单的应用中尚可接受,但在需要高实时性的多任务系统中,应尽量避免或为其增加超时退出机制。

3.2.2 printf重定向:开发者的福音

重定向printf函数,能让我们像在PC上编程一样,方便地打印变量和格式化字符串。您的代码已非常标准。

c 复制代码
// 在你的UART驱动文件中 (e.g., UART.c)
#include <stdio.h>

/* ... Keil MDK的兼容性代码 ... */
// #if !defined(__MICROLIB) ... #endif

// 重定向fputc函数,将printf的输出导向到UART
int fputc(int ch, FILE *stream)
{
    // 等待发送缓冲区空闲
    while(DL_UART_Main_isBusy(UART_0_INST));
    // 将字符写入UART的发送数据寄存器
    DL_UART_Main_transmitData(UART_0_INST, (uint8_t)ch);
    return ch;
}

现在,我们可以在main.c中愉快地使用printf了!

3.3 接收数据:从"简单回显"到"环形缓冲"

您的草稿中,中断服务函数只是简单地将收到的数据再发回去:

c 复制代码
// 简单的回显式中断
void UART0_IRQHandler(void)
{
    ...
    uart_data = DL_UART_Main_receiveData(UART_0_INST);
    uart_send_char(uart_data);
    ...
}

这种方式只能用于测试,无法构建复杂的应用。因为它有一个致命缺陷:中断处理快,主循环处理慢 。如果PC快速发送一串字符"LED_ON",中断会快速地将这6个字符放入uart_data变量中,但由于uart_data只能存一个字符,前5个字符都会被覆盖丢失!

【解决方案】引入环形缓冲区 (Ring Buffer)

环形缓冲区是一个先进先出(FIFO)的定长数组,它利用两个指针(读指针tail和写指针head)来巧妙地实现"循环"写入和读取,是处理异步数据流的标准数据结构。

  1. 定义环形缓冲区 (在UART.c中)
c 复制代码
#define RX_BUFFER_SIZE 64 // 定义缓冲区大小
volatile uint8_t g_rxBuffer[RX_BUFFER_SIZE];
volatile uint32_t g_rxHead = 0; // 写指针,由中断操作
volatile uint32_t g_rxTail = 0; // 读指针,由主循环操作
  1. 重写中断服务函数 (在UART.c中)
c 复制代码
void UART0_IRQHandler(void)
{
    switch (DL_UART_Main_getPendingInterrupt(UART_0_INST)) {
        case DL_UART_MAIN_IIDX_RX:
        {
            // 从硬件接收数据
            uint8_t data = DL_UART_Main_receiveData(UART_0_INST);

            // 计算下一个写指针位置
            uint32_t next_head = (g_rxHead + 1) % RX_BUFFER_SIZE;

            // 如果缓冲区未满,则存入数据
            if (next_head != g_rxTail) {
                g_rxBuffer[g_rxHead] = data;
                g_rxHead = next_head;
            }
            // 如果缓冲区满了,则丢弃数据(或进行其他错误处理)

            // (可选) 为了调试,可以回显刚收到的字符
            //uart_send_char(data); 
            break;
        }
        default:
            break;
    }
}
  1. 提供读取函数 (供main函数调用)
c 复制代码
// 从环形缓冲区读取一个字节
bool uart_get_char(uint8_t *ch)
{
    // 如果读写指针相同,说明缓冲区为空
    if (g_rxHead == g_rxTail) {
        return false;
    }

    *ch = g_rxBuffer[g_rxTail];
    g_rxTail = (g_rxTail + 1) % RX_BUFFER_SIZE;
    return true;
}

3.4 主函数测试:打造一个交互式LED控制器

现在,主函数不再是盲目地闪烁LED,而是等待并解析来自PC的命令。

c 复制代码
#include "ti_msp_dl_config.h"
#include <stdio.h>
#include <string.h> // 用于字符串比较
#include "LED.h"
#include "SYSTICK.h"
#include "UART.h" // 包含我们新加的 uart_get_char

// 命令缓冲区
#define CMD_BUFFER_SIZE 32
char g_cmdBuffer[CMD_BUFFER_SIZE];
uint8_t g_cmdIndex = 0;

// 解析并执行命令
void process_command(void)
{
    if (strcmp(g_cmdBuffer, "LED_ON") == 0) {
        LED_ON();
        printf("Command accepted: LED is now ON.\r\n");
    } else if (strcmp(g_cmdBuffer, "LED_OFF") == 0) {
        LED_OFF();
        printf("Command accepted: LED is now OFF.\r\n");
    } else {
        printf("Unknown command: %s\r\n", g_cmdBuffer);
    }
    // 清空命令缓冲区
    g_cmdIndex = 0;
    memset(g_cmdBuffer, 0, CMD_BUFFER_SIZE);
}

int main(void)
{
    SYSCFG_DL_init();
    // LED_Init(); // 已被SYSCFG_DL_init()包含
    // SysTick_init(); // 如需延时则保留
    // UART初始化和中断使能也由 SYSCFG_DL_init() 完成
    __enable_irq();

    printf("\r\n--- MSPM0 UART Interactive LED Control ---\r\n");
    printf("Send 'LED_ON' or 'LED_OFF' to control the LED.\r\n");

    while (1) 
    {
        uint8_t received_char;
        // 检查环形缓冲区是否有新数据
        if (uart_get_char(&received_char)) {
            // 如果收到的是回车符或换行符,认为一条命令结束
            if (received_char == '\r' || received_char == '\n') {
                if (g_cmdIndex > 0) {
                    process_command();
                }
            } else {
                // 将字符存入命令缓冲区
                if (g_cmdIndex < CMD_BUFFER_SIZE - 1) {
                    g_cmdBuffer[g_cmdIndex++] = received_char;
                }
            }
        }
    }
}

测试步骤

  1. 编译并烧录程序。
  2. 打开一个串口调试助手(如SSCOM、MobaXterm)。
  3. 设置波特率为9600,8-N-1。
  4. 在发送框中输入LED_ON,然后点击发送。
  5. 观察:开发板上的LED会点亮,同时串口助手会收到回复 "Command accepted: LED is now ON."
  6. 输入LED_OFF并发送,LED会熄灭。

通过环形缓冲区,我们成功构建了一个健壮的、非阻塞的串口通信框架,将中断与主循环的职责清晰地分离开来,这是嵌入式系统编程中一个极其重要的设计模式。


相关推荐
qq_7391753692 小时前
开源基于STC8的智能浇花与温湿度报警系统
c语言·stm32·单片机·嵌入式硬件
清月电子2 小时前
充电宝新规适配方案:KT6368A 蓝牙芯片应用技术说明
人工智能·单片机·嵌入式硬件·物联网
GesLuck2 小时前
Beaglebone BB Black C版 AM3358(一)
c语言·开发语言·物联网·硬件架构
Lay_鑫辰2 小时前
西门子1200PLC控制禾川X5ER伺服配置AC4模式全流程
运维·人工智能·单片机·嵌入式硬件·自动化
就是蠢啊2 小时前
51单片机——多文件结构模板
单片机·嵌入式硬件·51单片机
Bona Sun3 小时前
单片机手搓掌上游戏机(二十一)—pico运行doom之修改编译
c语言·c++·单片机·游戏机
松涛和鸣3 小时前
23、链式栈(LinkStack)的实现与多场景应用
linux·c语言·c++·嵌入式硬件·ubuntu
GesLuck3 小时前
Function函数
开发语言·物联网
意法半导体STM323 小时前
STM32N6 如何配置EMMC启动 LAT1581
stm32·单片机·嵌入式硬件·mcu·嵌入式ai·stm32n6·stm32开发