当前位置:首页 > 嵌入式 > 嵌入式分享
[导读]裸机或RTOS项目里,最容易出现“驱动调业务”的硬耦合:UART中断里直接ParseModbus、按键扫描里直接ControlRelay、定时器回调里直接BlinkLED。换协议、换板卡就要改驱动源文件,维护成本陡增。用函数指针做回调注册,把“事件发生”和“事件处理”拆开,驱动只负责收数据、置标志、调指针,应用层注册具体函数,复用性会明显提升。


裸机或RTOS项目里,最容易出现“驱动调业务”的硬耦合:UART中断里直接ParseModbus、按键扫描里直接ControlRelay、定时器回调里直接BlinkLED。换协议、换板卡就要改驱动源文件,维护成本陡增。用函数指针做回调注册,把“事件发生”和“事件处理”拆开,驱动只负责收数据、置标志、调指针,应用层注册具体函数,复用性会明显提升。

一、定义统一回调类型

按外设事件抽象参数,避免每个驱动写一套怪异签名:

typedef enum {

   EVT_UART_RX,

   EVT_GPIO_FALL,

   EVT_TIMER_PERIOD

} drv_event_t;


typedef void (*drv_callback_t)(drv_event_t evt,

                             uint8_t inst,

                             void *data,

                             uint16_t len,

                             void *ctx);

inst区分多实例端口/引脚,ctx传应用私有对象,避免用全局变量;data/len用于UART、DMA等带载荷事件。

二、驱动内部用注册表管理多实例

以UART为例,不在中断里写业务,只存回调并转发:

#define UART_MAX 3

static struct {

   drv_callback_t cb;

   void *ctx;

   uint8_t buf[128];

   uint16_t len;

} uart_slot[UART_MAX];


void uart_register(uint8_t port, drv_callback_t cb, void *ctx){

   if(port < UART_MAX){

       uart_slot[port].cb = cb;

       uart_slot[port].ctx = ctx;

   }

}


static void uart_irq(uint8_t port){

   /* 硬件收数据入buf,收完一帧后 */

   struct uart_slot *s = &uart_slot[port];

   if(s->cb)

       s->cb(EVT_UART_RX, port, s->buf, s->len, s->ctx);

}

应用层初始化时注册不同协议解析函数,驱动文件不动;同一份UART驱动可同时接Modbus、AT、私有协议。

三、GPIO与定时器同样解耦

按键/外部中断不直控业务,用边沿回调:

typedef void (*gpio_cb_t)(uint8_t pin, void *ctx);

static gpio_cb_t gpiocb[16];


void gpio_register(uint8_t pin, gpio_cb_t cb){

   if(pin < 16) gpiocb[pin] = cb;

}


void EXTI_IRQHandler(void){

   uint8_t pin = get_pending_pin();

   if(gpiocb[pin]) gpiocb[pin](pin, NULL);

}

定时器周期事件可用函数指针数组做轻量调度:

typedef void (*timer_cb_t)(void);

timer_cb_t timer_tbl[4];


void timer_period_isr(void){

   for(int i=0;i<4;i++)

       if(timer_tbl[i]) timer_tbl[i]();

}

CMSIS驱动体系也采用类似思路:Initialize时注册SignalEvent回调,中断只发事件、不处理业务。

四、中断上下文:短回调或事件入队

ISR里直接调复杂业务会阻塞其他中断。两点原则:

高速、低延迟场景可在ISR直接调回调,但只做置标志、入环形缓冲、发轻量通知;

解析、打印、写Flash等重活抛到主循环/RTOS任务。配合事件队列:

typedef struct { drv_event_t evt; uint8_t inst; void *ctx; } eq_t;

eq_t eq[16]; volatile uint8_t qh,qt;


void isr_post(drv_event_t e, uint8_t i, void *c){

   eq[qh].evt=e; eq[qh].inst=i; eq[qh].ctx=c;

   qh=(qh+1)%16;

}

主循环出队后调用应用回调,既解耦又保实时。

五、避坑要点

所有函数指针初始化为NULL,调用前判空,防止未注册跑飞;

多实例别用单全局回调,用数组/句柄结构体;

回调签名全程统一,避免强制转换导致栈破坏;

RTOS中回调若操作队列/信号量,确认所用API可在ISR调用;

注销设备时把槽位清NULL,防止悬空指针。

把“驱动只发事件、应用注册处理”作为规矩,外设驱动就能跨项目原样拷贝:换协议换回调,换板子换底层寄存器,业务层几乎不动。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭