STM32 模块解耦的一些方法和要点:
一、使用函数接口
- 封装功能模块
- 将每个功能模块的操作封装在独立的函数中。例如,对于一个与传感器交互的模块,创建专门的函数如
readSensorData()
、configureSensor()
等。这些函数作为模块与外部代码交互的接口,隐藏了模块内部实现的细节。 - 例如,在一个使用 ADC(模数转换器)的模块中:
- 将每个功能模块的操作封装在独立的函数中。例如,对于一个与传感器交互的模块,创建专门的函数如
uint16_t readADCValue(void) {
// 启动 ADC 转换
// 等待转换完成
// 返回转换结果
return adcValue;
}
- 避免全局变量共享
- 尽量减少模块之间通过全局变量共享数据,而是通过函数参数和返回值来传递数据。例如,一个定时器模块不应该直接操作其他模块的全局变量来控制其定时行为,而是通过提供设置定时参数的函数接口。
- 比如,在一个定时器模块中:
void setTimerPeriod(uint32_t period) {
timerPeriod = period;
// 根据新的周期重新配置定时器
}
二、分层架构设计
- 硬件抽象层(HAL)
- 在 STM32 开发中,创建硬件抽象层,将与硬件直接相关的操作封装在这一层。例如,对于 GPIO(通用输入 / 输出)操作,可以定义一组函数如
HAL_GPIO_WritePin()
、HAL_GPIO_ReadPin()
等。这些函数在底层直接操作硬件寄存器,但在高层的应用逻辑中,只需要调用这些抽象函数,而不需要关心硬件的具体细节。 - 例如:
- 在 STM32 开发中,创建硬件抽象层,将与硬件直接相关的操作封装在这一层。例如,对于 GPIO(通用输入 / 输出)操作,可以定义一组函数如
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState) {
// 根据 PinState 设置 GPIO 寄存器的值
}
- 应用层和驱动层分离
- 将应用层逻辑和硬件驱动层分开。应用层专注于业务逻辑的实现,而驱动层负责与硬件设备的通信和控制。例如,在一个温度控制系统中,应用层负责根据温度设定值和当前温度值进行控制算法的计算,而驱动层则负责与温度传感器和加热元件等硬件设备的交互。
- 例如,应用层代码:
if (currentTemperature < setTemperature) {
heaterDriver.turnOn();
} else {
heaterDriver.turnOff();
}
- 其中
heaterDriver
是一个硬件驱动对象,turnOn()
和turnOff()
是驱动层提供的操作硬件的函数。
三、事件驱动架构
- 事件触发机制
- 在 STM32 中,可以利用中断机制或者定时器来实现事件驱动。例如,当一个外部按键按下时,触发一个外部中断,在中断服务函数中发布一个按键按下的事件;其他模块可以注册对这个事件的处理函数,当事件发生时,相应的处理函数被调用。
- 例如,在一个按键处理模块中:
void EXTI0_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line0)!= RESET) {
// 清除中断标志位
EXTI_ClearITPendingBit(EXTI_Line0);
// 发布按键按下事件
postEvent(KEY_PRESS_EVENT);
}
}
- 事件注册与处理
- 其他模块可以通过注册事件处理函数来响应特定的事件。例如,一个显示模块可以注册对按键按下事件的处理函数,当按键按下事件发生时,更新显示内容。
- 例如,在事件处理系统中:
typedef void (*EventHandler)(void);
void registerEventHandler(EventType eventType, EventHandler handler) {
eventHandlers[eventType] = handler;
}
- 当事件发生时:
void dispatchEvent(EventType eventType) {
if (eventHandlers[eventType]) {
eventHandlers[eventType]();
}
}