关于嵌入式软件分层设计

从会写代码,到想要写好代码,这个过程是很难受的。
由于做的嵌入式软件,主要是MCU方面,都是要跟硬件底层打交道的软件设计,接手的别人的软件给人影响最深刻的就是典型的面向过程式编程,高层模块大量依赖低层模块,特别是高层模块依赖底层硬件。在这里插入图片描述

缺点: 修改底层模块,将影响高层模块。在实际应用中,底层模块又是经常要被修改的。
怎么解决?依赖反转,低层模块依赖高层。
怎么实现依赖反转?面向对象编程中有一个很重要的概念 —— 面向抽象接口编程。在C++中使用虚函数实现多态、抽象接口,C语言没有虚函数,对于OOPC来说只能使用函数指针来实现多态、抽象接口。

了解很多理论后尝试了在STM32上使用。

对GPIO进行抽象

数据结构:

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换一个引脚即可。

猜你喜欢

转载自blog.csdn.net/qq_36413982/article/details/108549109