学习笔记-EBD-韦东山老师-FreeRTOS下UART的封装

本文介绍了在FreeRTOS环境下如何对UART进行驱动层的抽象封装,通过面向对象的方式创建UART_Device对象,使用中断和信号量管理串口通信,并讨论了现有程序的优化点,如内存使用和对象定位方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#本文章来源于韦东山老师B站视频课程《FreeRTOS下UART的封装》#

- 这里仅为个人对原作者视频内容的记录和自己的一些体会

为什么使用架构

在嵌入式程序中,有几种类型的程序层级,最初级的是完全耦合,应用程序直接操作寄存器,第二类是对驱动层进行抽象,抽象出类似于HAL、LL库的代码,app直接操作驱动层,第三类是在驱动之上再进行封装,支持用户库和应用。更高层级的抽象可以为用户层提供稳定的接口,避免了硬件调整造成的额外工作。

本文所重点讲述的就是如何在驱动层进行进一步抽象

路线选择:面向过程or面向对象

从左侧依赖hal、依赖硬件了解(IT硬件中断、huart1串口号)知识的软件进行抽象有两种思路,分别是函数式、对象式。下面分别是两种实现的例子。我用自己的话总结了以下他们的本质区别:函数式或者过程式,需要将差异紧耦合在过程内部,导致串口号遍历、接口函数遍历等各种扩展性问题;对象式编程将差异封装在对象内部,可以独立设计、使用。

外设对象抽象(基于rtos&hal库进行Device封装)

定义设备对象

定义串口设备对象UART_Device,其中void *指针用于指向一个私有数据类型UART_Data(不对外暴露,每一类串口都要实现的一个数据对象,因操作系统、驱动函数而变化,包括串口设备句柄、信号量、队列、接受数据缓存字符)

struct UART_Device {
    char *name;
    int (*Init)(struct UART_Device *pDev, int baud, int datas, char parity, int stop);
    int (*Send)(struct UART_Device *pDev, uint8_t *datas, int len, int timeout_ms);
    int (*Recv)(struct UART_Device *pDev, uint8_t *data, int timeout_ms);
    void *priv_data;
};

// 以下为私有类型,针对驱动库,操作系统可以进行调整
struct UART_Data {
    UART_HandleTypeDef *handle;
    SemaphoreHandle_t xTxSem;
    QueueHandle_t xRxQueue;
    uint8_t rxdata;
};

// 创建静态数据
#define UART_RX_QUEUE_LEN 100      // dev 数据队列长度
struct UART_Device g_stm32_uart1;  // dev 对象
static struct UART_Data g_stm32_uart1_data = { // dev 对象私有数据
    &huart1,
};

定义设备操作

本实例中以freertos操作系统为例,基于hal库进行外设操作,串口收发使用中断,除封装收发函数外还需要再对应中断处理函数中进行数据处理,同步使用信号量和队列,均由操作系统提供。

extern UART_HandleTypeDef huart1;  // dev 对应串口号

static int stm32_uart_init(struct UART_Device *pDev, int baud, int datas, char parity, int stop)
{
    struct UART_Data *data = pDev->priv_data;

    data->xTxSem = xSemaphoreCreateBinary();
    data->xRxQueue = xQueueCreate(UART_RX_QUEUE_LEN, 1);

    /* 启动第1次数据的接收 */
    HAL_UART_Receive_IT(data->handle, &data->rxdata, 1);
    
    return 0;
}

static int stm32_uart_send(struct UART_Device *pDev, uint8_t *datas, int len, int timeout_ms)
{
    struct UART_Data *data = pDev->priv_data;
    
    /* 仅仅是触发中断而已 */
    HAL_UART_Transmit_IT(data->handle, datas, len);

    /* 等待发送完毕:等待信号量 */
    if (pdTRUE == xSemaphoreTake(data->xTxSem, timeout_ms))
        return 0;
    else
        return -1;
}

static int stm32_uart_recv(struct UART_Device *pDev, uint8_t *data, int timeout_ms)
{
    struct UART_Data *uart_data = pDev->priv_data;
    
    /* 读取队列得到数据, 问题:谁写队列?中断:写队列 */
    if (pdPASS == xQueueReceive(uart_data->xRxQueue, data,timeout_ms))
        return 0;
    else
        return -1;
}

// 中断处理程序
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
    struct UART_Data *data;
    
    if (huart == &huart1)
    {
        data = g_stm32_uart1.priv_data;
        
        /* 释放信号量 */
        xSemaphoreGiveFromISR(data->xTxSem, NULL);
    }
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    struct UART_Data *data;
    
    if (huart == &huart1)
    {
        data = g_stm32_uart1.priv_data;
        
        /* 写队列 */
        xQueueSendFromISR(data->xRxQueue, &data->rxdata, NULL);

        /* 再次启动数据的接收 */
        HAL_UART_Receive_IT(data->handle, &data->rxdata, 1);
    }
}

接口暴露

struct UART_Device *g_uart_devs[] = {&g_stm32_uart1};

struct UART_Device *GetUARTDevice(char *name)
{
    int i = 0;
    for (i = 0; i < sizeof(g_uart_devs)/sizeof(g_uart_devs[0]); i++)
    {
        if (0 == strcmp(name, g_uart_devs[i]->name))
            return g_uart_devs[i];
    }

    return NULL;
}

// 整个文件暴露了一个UART_Device类型和GetUARTDevice函数
// 调用:
void StartDefaultTask(void *argument)
{
  /* USER CODE BEGIN 5 */
    char c;
    struct UART_Device *pUARTDev = GetUARTDevice("stm32_uart1");
    pUARTDev->Init(pUARTDev, 115200, 8, 'N', 1);
    
  /* Infinite loop */
  for(;;)
  {
      //HAL_UART_Transmit_IT(&huart1, "100ask\r\n", 8);
      //HAL_Delay(100);
      pUARTDev->Send(pUARTDev, "100ask\r\n", 8, 100);

      //while (HAL_OK != HAL_UART_Receive(&huart1, &c, 1, 100));
      while (0 != pUARTDev->Recv(pUARTDev, &c, 100));
      c++;
      //HAL_UART_Transmit_IT(&huart1, &c, 1);
      //HAL_Delay(1);
      
      pUARTDev->Send(pUARTDev, &c, 1, 1);
  }
  /* USER CODE END 5 */
}

优化

有没有可能进一步优化程序呢?个人觉得现阶段的程序有如下问题:1.使用字符串定位串口对象,占用资源且存在不确定性 2.存在静态内存开销,也就是没有用驱动功能的时候就存在内存开销了 3.对象封装上见仁见智,此种封装方法简洁高效,可以借鉴。

<think>嗯,用户想学习FreeRTOS的入门方法。首先,我需要确定用户是否有嵌入式系统的基础,因为FreeRTOS通常用在微控制器上。可能用户是刚开始接触RTOS,或者有一定编程经验但没接触过实时操作系统。 我应该先介绍FreeRTOS是什么,简要说明它的应用场景,比如物联网、嵌入式设备等。然后,分步骤来规划学习路径,可能需要从基础概念开始,比如任务、队列、信号量这些核心组件。 用户可能需要实践指导,比如推荐具体的开发板,比如STM32或者ESP32,因为这些硬件支持FreeRTOS,资料也多。还要提到开发环境,比如使用Keil、IAR或者VS Code加插件,这对新手来说可能比较重要。 另外,官方文档和示例代码是学习的关键资源,应该强调阅读文档的重要性。可能用户不知道从哪里获取资料,需要给出链接或者资源名称。 还要考虑用户的学习步骤,比如先掌握任务调度,再逐步学习任务间通信、内存管理、中断处理等。每个部分需要简要说明重点,避免信息过载。 可能用户会遇到调试问题,可以建议使用调试工具和日志输出,或者社区支持如论坛、Stack Overflow。最后,推荐一些进阶内容,如源码分析和移植,帮助用户深入理解。 需要注意的是,避免使用复杂术语,用简单的中文解释概念,保持回答结构清晰,分点列出,方便用户一步步学习。同时检查是否有遗漏的重要点,比如实践项目的重要性,或者常见的错误避免方法。</think>学习FreeRTOS的入门路径可以按照以下步骤展开,结合理论与实践逐步掌握: --- ### **一、基础准备** 1. **理解RTOS概念** - 明确实时操作系统(RTOS)与裸机编程的区别,了解**任务调度**、**优先级**、**抢占式多任务**等核心概念。 - 推荐阅读:《嵌入式实时操作系统原理与最佳实践》 2. **掌握硬件基础** - 熟悉一种微控制器(如STM32、ESP32),需了解GPIO、UART、定时器等外设驱动。 - 开发环境建议:Keil、IAR、VS Code(配合PlatformIO插件)。 --- ### **二、FreeRTOS核心知识** 1. **基础框架** - 任务(Task):学习`xTaskCreate()`创建任务,理解任务状态(运行、就绪、阻塞)。 - 调度器:掌握优先级调度和时间片轮转机制。 ```c void vTaskExample(void *pvParameters) { while(1) { // 任务代码 vTaskDelay(1000 / portTICK_PERIOD_MS); // 延时1秒 } } xTaskCreate(vTaskExample, "Task1", 128, NULL, 1, NULL); ``` 2. **任务通信与同步** - **队列(Queue)**:跨任务传递数据,使用`xQueueSend()`和`xQueueReceive()`。 - **信号量(Semaphore)**:二进制/计数信号量,管理资源共享。 - **互斥量(Mutex)**:解决优先级反转问题。 3. **内存与时间管理** - 理解FreeRTOS的`heap_1`至`heap_5`内存分配方案。 - 使用`vTaskDelay()`和定时器(Software Timer)管理时间。 --- ### **三、实践路线** 1. **官方资源** - 下载[FreeRTOS官方源码](https://www.freertos.org/),阅读`Demo`目录下的示例(如`STM32Cube_FW_F4`中的项目)。 - 参考[FreeRTOS官方文档](https://www.freertos.org/documentation),重点关注《Mastering the FreeRTOS Real Time Kernel》。 2. **开发板实战** - **入门项目**:LED闪烁+串口打印(验证任务调度)。 - **进阶项目**: - 多任务传感器数据采集(如温湿度+光照)。 - 通过队列实现任务间数据传输(如UART接收后转发)。 3. **调试技巧** - 使用`printf`打印任务状态,或借助Tracealyzer可视化任务调度。 - 常见错误:堆栈溢出(通过`uxTaskGetStackHighWaterMark()`检测)。 --- ### **四、学习资源推荐** - **书籍**: 《FreeRTOS实时内核使用指南》 《ARM Cortex-M3与FreeRTOS的应用开发》 - **视频教程**: B站“东山FreeRTOS系列”、Coursera嵌入式专项课程。 - **社区**: FreeRTOS官方论坛、Stack Overflow、电子工程世界论坛。 --- ### **五、进阶方向** 1. 研究FreeRTOS内核源码(如`tasks.c`和`queue.c`)。 2. 移植FreeRTOS到新硬件平台(需修改`port.c`)。 3. 集成中间件(如FreeRTOS+TCP、FreeRTOS-FAT)。 --- **关键点**:从简单任务调度入手,逐步添加通信机制,通过实际项目加深理解。初期可复制官方Demo修改,后期尝试独立设计多任务系统。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值