从会写代码,到想要写好代码,这个过程是很难受的。
由于做的嵌入式软件,主要是MCU方面,都是要跟硬件底层打交道的软件设计,接手的别人的软件给人影响最深刻的就是典型的面向过程式编程,高层模块大量依赖低层模块,特别是高层模块依赖底层硬件。
缺点: 修改底层模块,将影响高层模块。在实际应用中,底层模块又是经常要被修改的。
怎么解决?依赖反转,低层模块依赖高层。
怎么实现依赖反转?面向对象编程中有一个很重要的概念 —— 面向抽象接口编程。在C++中使用虚函数实现多态、抽象接口,C语言没有虚函数,对于OOPC来说只能使用函数指针来实现多态、抽象接口。
了解很多理论后尝试了在STM32上使用。
数据结构:
struct gpio {
void (*gpio_init)(unsigned char port, unsigned short pin, unsigned char mode);
void (*set_gpio)(unsigned char port, unsigned short pin, unsigned char level));
void (*set_gpio_port)(unsigned char port, unsigned short portval);
unsigned char (*get_gpio)(unsigned char port, unsigned short pin);
unsigned short (*get_gpio_port)(unsigned char port);
};
MCU的GPIO虽然有很多模式,实际上只有一种,感觉有点类似设计模式中的单例模式
实现抽象接口:
#include "hal_gpio.h"
#include "stm32f10x.h"
#define STM32_PIN(X) (2 << (X-1))
#define ARR(A) (sizeof(A)/sizeof(A[0]))
static GPIO_TypeDef* stm32_port[] = {
GPIOA, GPIOB, GPIOC, GPIOD, GPIOE, GPIOF, GPIOG};
static void hal_gpio_init(unsigned char port, unsigned short pin, unsigned char mode)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = STM32_PIN(pin); //LED0-->PB.5 端口配置
GPIO_InitStructure.GPIO_Mode = mode; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(stm32_port[port], &GPIO_InitStructure); //根据设定参数初始化GPIOB.5
}
static void hal_set_gpio(unsigned char port, unsigned short pin, unsigned char level)
{
if (level == SET)
{
GPIO_SetBits(stm32_port[port], (uint16_t)STM32_PIN(pin));
}
else
{
GPIO_ResetBits(stm32_port[port], (uint16_t)STM32_PIN(pin));
}
}
static void hal_set_gpio_port(unsigned char port, unsigned short portval)
{
GPIO_Write(stm32_port[port], (uint16_t )portval);
}
static unsigned char hal_get_gpio(unsigned char port, unsigned short pin)
{
return (unsigned char)GPIO_ReadInputDataBit(stm32_port[port], STM32_PIN(pin));
}
static unsigned short hal_get_gpio_port(unsigned char port)
{
return (int)GPIO_ReadOutputData(stm32_port[port]);
}
struct gpio gpio;
void init_gpio()
{
gpio.get_gpio = hal_get_gpio;
gpio.get_gpio_port = hal_get_gpio_port;
gpio.set_gpio = hal_set_gpio;
gpio.set_gpio_port = hal_set_gpio_port;
gpio.gpio_init = hal_gpio_init;
}
实现了STM32的GPIO硬件抽象接口,之后的GPIO操作使用gpio
对象进行操作。这样做之后,上层使用到GPIO功能,当要换其他MCU,只需要实现这些抽象接口即可,上一层的程序不要修改。
知道抽象接口这玩意后考虑使用它来给软件分层。
关于嵌入式软件分层设计的文章:
https://www.cnblogs.com/kmust/p/9250263.html
实现了GPIO硬件适配,假设现在有一个LED模块,一个开发板总是要点灯的嘛。
抽象led:
led无非就是点亮、熄灭。
struct led{
void (*led_on)();
void (*led_off)();
};
void led_init(struct led *led, void (*led_on)(), void (*led_off)())
{
led->led_on = led_on;
led->led_off = led_off;
}
void set_led_on(struct led *led)
{
if (led->led_on != NULL)
{
led->led_on();
}
}
void set_led_off(struct led *led)
{
if (led->led_off != NULL)
{
led->led_off();
}
}
led的功能模块:
struct led led1;
struct led led2;
void led1_on()
{
gpio.set_gpio(GPIO_E, 5, 0);
}
void led1_off()
{
gpio.set_gpio(GPIO_E, 5, 1);
}
void led2_on()
{
gpio.set_gpio(GPIO_B, 5, 0);
}
void led2_off()
{
gpio.set_gpio(GPIO_B, 5, 1);
}
led_init(&led1, led1_on, led1_off);
led_init(&led2, led2_on, led1_off);
更高一层的业务逻辑层可能调用led模块去实现闪烁等功能。假如现在由于硬件变更,某个led控制引脚变更,用了抽象接口后业务逻辑层关于led闪烁的代码不需要修改,只需要修改led功能模块给这个led换一个引脚即可。