3. 串口发送字符串的分层封装

串口发送字符串的分层封装 学习卡片

任务定义

在单片机裸机开发中,上层业务代码(如日志输出、命令解析)需要发送字符串,但不应直接操作UART寄存器。本案例要求封装一个串口发送模块,对外仅暴露两个接口:uart_send_string()和uart_send_byte(),上层调用者无需了解任何寄存器细节。底层基于STM32F103的USART1实现,通过查询方式发送(非中断),以最小代码量完成分层隔离。

分层结构

整个设计分为两层:驱动层(uart_driver.c/h)负责寄存器操作;服务层(uart_service.c/h)负责字符串处理逻辑。上层业务只包含uart_service.h,不直接包含驱动头文件,从而在编译层面强制隔离寄存器访问。

驱动层实现

// uart_driver.h
#ifndef UART_DRIVER_H
#define UART_DRIVER_H

#include "stm32f1xx.h"

void uart_driver_init(void);
void uart_driver_send_byte(uint8_t ch);

#endif
// uart_driver.c
#include "uart_driver.h"

void uart_driver_init(void)
{
    // 使能USART1和GPIOA时钟
    RCC->APB2ENR |= RCC_APB2ENR_USART1EN | RCC_APB2ENR_IOPAEN;
    
    // PA9=TX(推挽复用), PA10=RX(浮空输入)
    GPIOA->CRH &= ~(GPIO_CRH_CNF9_Msk | GPIO_CRH_MODE9_Msk);
    GPIOA->CRH |= (GPIO_CRH_CNF9_1 | GPIO_CRH_MODE9_1 | GPIO_CRH_MODE9_0); // 50MHz复用推挽
    GPIOA->CRH &= ~(GPIO_CRH_CNF10_Msk | GPIO_CRH_MODE10_Msk);
    GPIOA->CRH |= GPIO_CRH_CNF10_0;  // 浮空输入
    
    // 115200-8-N-1 (PCLK2=72MHz)
    USART1->BRR = 0x2710;  // 72MHz/115200 = 625 = 0x271
    USART1->CR1 = USART_CR1_UE | USART_CR1_TE;
}

void uart_driver_send_byte(uint8_t ch)
{
    // 等待发送数据寄存器空
    while (!(USART1->SR & USART_SR_TXE));
    USART1->DR = ch;
}

服务层实现

// uart_service.h
#ifndef UART_SERVICE_H
#define UART_SERVICE_H

#include <stdint.h>

void uart_service_init(void);
void uart_send_byte(uint8_t ch);
void uart_send_string(const char *str);

#endif
// uart_service.c
#include "uart_service.h"
#include "uart_driver.h"

void uart_service_init(void)
{
    uart_driver_init();
}

void uart_send_byte(uint8_t ch)
{
    uart_driver_send_byte(ch);
}

void uart_send_string(const char *str)
{
    if (str == 0) return;
    while (*str != '\0') {
        uart_driver_send_byte((uint8_t)(*str));
        str++;
    }
}

上层调用示例

// main.c 片段
#include "uart_service.h"

int main(void)
{
    uart_service_init();
    
    while (1) {
        uart_send_string("Hello from UART layer\r\n");
        for (volatile uint32_t i = 0; i < 2000000; i++); // 简单延时
    }
}

接口隔离验证

上层main.c只包含uart_service.h,不包含uart_driver.h。若上层代码尝试直接访问USART1->DR或调用uart_driver_send_byte(),编译器将报错(未声明标识符),从编译层面保证分层不被破坏。

工程文件与编译

需要添加的文件:uart_driver.c、uart_service.c、main.c。头文件路径需包含上述三个.h所在目录。使用STM32标准外设库或直接寄存器方式均可,本实现不依赖HAL库,仅需CMSIS头文件stm32f1xx.h。

验证方法与判定标准

硬件验证:将PA9(TX)连接至USB转TTL模块的RX,PC端打开串口助手(115200-8-N-1)。上电后应持续收到字符串Hello from UART layer,每行以回车换行结尾。

判定标准:

  1. 串口助手能稳定收到预期字符串,无乱码、无丢失;
  2. 在main.c中尝试包含uart_driver.h并直接操作USART1->DR,编译应报错,证明接口隔离有效;
  3. 将uart_send_string()的参数改为NULL,程序不崩溃(空指针保护生效)。
串口发送分层调用关系与数据流 上层业务 (main.c) 调用 uart_send_string() 不包含驱动头文件 服务层 (uart_service.c) 字符串遍历/空指针保护 调用 uart_driver_send_byte() 驱动层 (uart_driver.c) 寄存器操作 USART1->DR 等待TXE标志 ① ② ③ USART1 硬件 PA9 TX → 串口助手 编译期隔离验证 main.c 只 include "uart_service.h" 直接访问寄存器 → 编译报错 数据流:字符串指针 → 逐字节遍历 → 寄存器写入 → TX引脚输出