参考链接:分享一个通用的嵌入式驱动层
以下为我阅读这篇博客的理解,如果错误请帮忙指正。
1.首先创建一个链表控制的文件,此文件将会把每一个硬件的相关信息(cola_device,name用于查找设备,dops对硬件进行操作的相关函数)插入链表。
struct cola_device_ops
{
int (*init) (cola_device_t *dev);
int (*open) (cola_device_t *dev, int oflag);
int (*close) (cola_device_t *dev);
int (*read) (cola_device_t *dev, int pos, void *buffer, int size);
int (*write) (cola_device_t *dev, int pos, const void *buffer, int size);
int (*control)(cola_device_t *dev, int cmd, void *args);
};
struct cola_device
{
const char * name;
struct cola_device_ops *dops;
struct cola_device *next;
};
2.在cola_device_ops这个结构体中涉及所有关于硬件的操作(初始化,打开,关闭,读,写,控制),实际上大多数硬件只需要实现部分操作。然后通过在main函数中调用led_register()这个函数就将其注入到链表中了(并且这一步还初始化了硬件)
static cola_device_t led_dev;
static void led_gpio_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Pin = PIN_GREENLED;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(PORT_GREEN_LED, &GPIO_InitStructure);
LED_GREEN_OFF;
}
static int led_ctrl(cola_device_t *dev, int cmd, void *args)
{
if(LED_TOGGLE == cmd)
{
LED_GREEN_TOGGLE;
}
else {
}
return 1;
}
static struct cola_device_ops ops =
{
.control = led_ctrl,
};
void led_register(void)
{
led_gpio_init();
led_dev.dops = &ops;
led_dev.name = "led";
cola_device_register(&led_dev);
}
3.当需要操作硬件的时候,先用名字查找这个硬件设备,然后就可以对其进行控制了
void app_init(void)
{
app_led_dev = cola_device_find("led");
app_led_dev.control(app_led_dev, LED_TOGGLE, 0);
}
4.既然这里没有设计到硬件层的删除,那么也就意味着这里没有必要使用链表。我们完全可以使用数组进行操作。不管是上面的链表还是这个用数组,在我看来我都不会去使用它,即使我理解了用法,这个实在是对单片机那小的可怜内存的高看(在实际开发中)。
struct cola_device
{
const char * name;
struct cola_device_ops *dops;
struct cola_device *next;
};
改为:
struct cola_device
{
const char dev_num;//用枚举来代替设备,或者你也可以不用
struct cola_device_ops *dops;
};
//注册函数
//在这里搞一个用于存储所有设备信息的数组
cola_device_t dev_arr[10] = {0};
void cola_device_register(cola_device_t dev)
{
dev_arr[dev.dev_num] = dev;
}
//如何调用
//1.直接extern cola_device_t dev_arr[10]
//2.第一中方法也许会让你觉得很low,那我们再写个函数
cola_device_t* find_dev(const char dev_num)
{
return &dev_arr[dev.dev_num];
}
总结:这些所有的操作其实就是用同一个函数名,来掩盖不同的设备,不同的操作方法。
以上写于2022年11月, 现在(2024年1月)对以上的说法不合理的地方解答:
1. 抽象是没毛病的, 这种抽象肯定不能用于8位单片机中, 并且要对功能有大概了解, 太复杂, 而老板给的芯片太小, 肯定也不能取用抽象.
2. 可以将struct cola_device_ops用const声明, 这样可以将内存占用降下去.
3. 使用数组太鸡肋, 在模块化中不灵活, 可能我取消某个模块, 还要去修改其他地方的代码, 如果用链表, 那我就不需要去修改代码.链表在模块化, 动态化编程必定是必不可少的.