# hood **Repository Path**: embeddedpro/hood ## Basic Information - **Project Name**: hood - **Description**: A non-blocked embedded platform which contains Hardware Abstraction Layer (HAL) and Component Abstraction Layer (CAL). - **Primary Language**: C - **License**: MIT-0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2025-01-29 - **Last Updated**: 2025-07-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Hood ## 起源 `Hood` 的起源可以追溯到本项目的发起人(以下简称“发起人”)在为一家客户提供软件开发效率提升咨询服务时,为了解决 [DW8051](https://www.synopsys.com/dw/doc.php/ds/i/DW8051.pdf) 单片机程序中的模块间耦合问题而设计的 `TaskPump` 模块。通过以下两个简化后的示例程序文件,可以帮助你理解具体的耦合问题。 **uart.c** ```C bool gUartRxInterrupted = false; void UartHandleRxInterrupt() { ...... } void UartIsr(void) using 0 interrupt UART_interrupt { if (IS_UART_RX_IRQ()) { gUartRxInterrupted = true; CLEAR_UART_RX_STAT(); } } ``` **main.c** ```C void main(void) { ...... while (1) { if (gUartRxInterrupted) { UartHandleRxInterrupt(); DISABLE_ALL_IRQ(); gUartRxInterrupted = false; ENABLE_ALL_IRQ(); } } } ``` 这两个文件分别代表了 UART 模块和 main 模块。当 UART 收到数据并产生中断时,中断服务程序 `UartIsr` 会设置全局变量 `gUartRxInterrupted` 作为标识,main 模块则通过检查这个标识来决定何时调用 UART 模块的 `UartHandleRxInterrupt` 函数进行处理。这种实现方式存在以下问题: - **全局变量的使用**:全局变量会导致代码难以阅读和修改,尤其是在代码量达到上千行时,多个全局变量的使用会使代码变得混乱,形成所谓的“面条状代码”。 - **低内聚、高耦合**:main 模块需要了解 UART 模块的全局变量 `gUartRxInterrupted` 和函数 `UartHandleRxInterrupt`,这违背了软件工程中倡导的高内聚、低耦合设计原则。这一原则的核心目的是使代码更易读和易改。 为了解决这一问题,发起人设计了 `TaskPump`(任务泵)模块,用于实现 UART 和 main 两个模块之间的解耦。`TaskPump` 模块的实现代码如下: **task_pump.c** ```C #define QUEUE_CAPACITY 8 typedef struct { void (*do_work)(); } Task; typedef struct { Task tasks[QUEUE_CAPACITY]; uint8_t post_cursor; uint8_t take_cursor; uint8_t buffered_count; } TaskQueue; static TaskQueue gTaskQueue = {0}; int TaskPumpPost(void (*do_work)()) { DISABLE_ALL_IRQ(); if (gTaskQueue.buffered_count >= QUEUE_CAPACITY || do_work == 0) { ENABLE_ALL_IRQ(); // !!! NOTE: handle the error. return -1; } // Put the |do_work| to tail. gTaskQueue.tasks[gTaskQueue.post_cursor].do_work = do_work; gTaskQueue.post_cursor = (gTaskQueue.post_cursor + 1) % QUEUE_CAPACITY; gTaskQueue.buffered_count++; ENABLE_ALL_IRQ(); return 0; } void TaskPumpExecute(void) { DISABLE_ALL_IRQ(); while (gTaskQueue.buffered_count > 0) { void (*do_work)(void) = gTaskQueue.tasks[gTaskQueue.take_cursor].do_work; gTaskQueue.take_cursor = (gTaskQueue.take_cursor + 1) % QUEUE_CAPACITY; gTaskQueue.buffered_count--; ENABLE_ALL_IRQ(); // Enable interrupt and do work. do_work(); DISABLE_ALL_IRQ(); } ENABLE_ALL_IRQ(); } ``` `TaskPump` 模块实现了一个任务队列,通过调用 `TaskPumpPost` 向任务队列中投递任务,通过调用 `TaskPumpExecute` 执行任务队列中的任务。有了 `TaskPump` 模块后,UART 和 main 模块实现了完全解耦,新的代码实现如下: **uart.c** ```C #include "task_pump.h" static void UartHandleRxInterrupt() { ...... } void UartIsr(void) using 0 interrupt UART_interrupt { if (IS_UART_RX_IRQ()) { TaskPumpPost(UartHandleRxInterrupt); CLEAR_UART_RX_STAT(); } } ``` **main.c** ```C #include "task_pump.h" void main(void) { ...... while (1) { TaskPumpExecute(); } } ``` 在新的 UART 和 main 模块的代码中: - UART 和 main 模块都依赖于 `TaskPump` 这一高内聚、低耦合的模块,两者完全不需要知道彼此的存在。 - UART 模块的实现变得更加简单,既省去了定义全局变量,`UartHandleRxInterrupt` 函数也变成了 `static`,因为不再需要暴露给外部。 可以看到,整个代码行数不到 50 行的 `TaskPump` 模块,在解决软件复杂度中的核心依赖问题时发挥了巨大作用,这正是优良软件设计的威力所在。 `TaskPump` 模块最初被应用于 DW8051 单片机程序,随后又被推广到 ARM 处理器上,并成为发起人所服务的公司大部分嵌入式系统的基础模块。后来,为了将 `TaskPump` 应用于代码量超过 70 万行的控制器产品,需要基于复杂场景对其进行增强。在客户总工程师的同意下,`Hood` 开源项目应运而生。我们希望通过开源的方式完成 `Hood` 在复杂场景下的落地应用。 ## 为什么叫 `Hood` `Hood` 这个名字来源于“防护罩”的意思,比如汽车发动机的引擎盖或飞机发动机的引擎罩。`Hood` 项目希望传达以下理念: - **屏蔽底层技术复杂度**:让上层应用开发者能够将更多精力用于理解业务逻辑和实现产品功能,而不是与底层技术的复杂度作斗争。 - **追求设计之美**:我们希望 `Hood` 本身像发动机一样,具有技术含量和设计之美,这代表了 `Hood` 项目的追求。 - **提供更易掌握的异步编程平台**:在嵌入式系统开发中,通常分为不使用 RTOS(实时操作系统)和使用 RTOS 两大类。不使用 RTOS 的主要原因包括资源限制(RTOS 需要更多的存储空间和内存开销)和降低编程复杂度(RTOS 中的锁、任务调度等概念增加了编程复杂度,容易引发死锁、优先级翻转等问题)。使用 RTOS 除了为了实现系统对某些事件的及时响应外,还可以将多任务作为系统模块化设计的一种手段。`Hood` 采用异步编程模式,不仅为不使用 RTOS 的嵌入式系统开发提供了更好的选择,也为使用 RTOS 的嵌入式系统开发提供了另一种途径。 ## 设计原则 `Hood` 遵循以下设计原则: - **存储和内存空间开销最小化**:这意味着硬件成本最低化。在中国成为世界级超级工厂的背景下,硬件成本的最低化将帮助企业更好地实现降本增效。 - **无动态内存分配与释放**:这一原则不仅有助于实现存储和内存空间最小化,还避免了动态内存管理带来的内存泄漏问题,这是工业界常见的痛点。 - **全异步无阻塞编程**:这种编程模式使得整个软件除了中断服务程序和 main 函数外,不存在因调度时的上下文切换(context switch)带来的开销。虽然 `Hood` 中仍有任务泵的概念,且不同的任务泵存在优先级之分,但本质上所有任务泵都在 main 函数中运行,处于同一个运行空间。开发者在编程时,除了需要特别注意预防中断与非中断程序之间的竞争问题外,其他场景完全无需考虑竞争问题,从而大大简化了编程复杂度和查错难度。 ## 软件架构
架构图
`Hood` 由两大部分组成: - **HAL(硬件抽象层)**:HAL 将每个 CPU 外设抽象为一个由路径指向的设备(device)实例。例如,在 CPU 硬件定时器有限的情况下,可以使用一个硬件定时器来实现系统所需的任意数量的软件定时器。这个硬件定时器称为“滴答(tick)”,其路径可以表示为 `/device/timer/tick`。通过 `DeviceOpen`、`DeviceControl` 和 `DeviceClose` 三个函数,可以实现对 tick 的使用。换句话说,HAL 通过 `Device` 这一抽象概念,统一了上层应用对外设的访问方式。外设包括但不限于 UART、Flash、PWM、GPIO、ADC 等。 - **CAL(组件抽象层)**:CAL 包含与硬件无关的编程组件。例如,前面提到的 `TaskPump` 就是 CAL 中的一员。 需要注意的是,CAL 与 HAL 是相互依赖的关系,且两者都依赖于 `C Library`。 ## 工程方法 为了确保项目的开发质量和效率,`Hood` 采用了以下工程方法: - **跨平台开发**:`Hood` 完全沿用了 [Embedded](https://gitee.com/embeddedpro/embedded) 开源项目所打造的 [Windows WSL + Ubuntu](https://learn.microsoft.com/en-us/windows/wsl/install) 跨平台开发方法,并进一步直接支持 [arm µVision](https://developer.arm.com/documentation/101407/latest/?lang=en)。跨平台开发的优势在于,运行于嵌入式系统的软件模块可以在 Windows 开发主机上高效完成功能开发与验证,同时利用 Linux 开源社区的各种质量保证工具确保软件质量。开发环境的搭建请参见[这里](https://gitee.com/embeddedpro/embedded/blob/master/README.md)。值得一提的是,你可以方便地使用与 `arm µVision` 类似的 IDE 来开发 `Hood`。 - **重度依赖单元测试**:正如全球所有重大开源项目都依赖单元测试来确保软件开发质量和效率一样,`Hood` 也是这一工程方法的坚定支持者和实践者。单元测试不仅确保了软件的开发质量和效率,还使 `Hood` 在面对新的应用场景时,能够快速且高质量地完成软件设计的演进。 ## 工程实践 为了帮助开发者更好地运用 `Hood` ,请参考 [工程实践指南](docs/engineering_guide.md)。 ## 参考资料 - 《专业嵌入式软件开发:全面走向高质高效编程》(李云,2012) - 如需购书,请发邮件至 dali_ly@163.com 联系作者。 - 《[全面效能](https://item.jd.com/14779660.html)》(李云、楼建芳,2024)