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

#本文章来源于韦东山老师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.对象封装上见仁见智,此种封装方法简洁高效,可以借鉴。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值