回调函数之 - 增加程序的复用性
上次说了采用回调函数隔离第三方SDK的方法。这次来说说通过回调函数增加程序的可复用性,尽量避免重复的编程劳动,以加快项目的开发速度以及把更多的时间拿去打磨程序的核心功能。 程序的可复用性关键在于把变化隔离出去而复用其本质特征。比如在编程时有时需要一个串口打印信息模块打印调试信息以跟踪程序关键变化。当程序出现bug时能帮助我们迅速找到问题所在。那么,在我写好打印调试信息的模块时为了增加这个模块的复用性我怎么样才能毫不费力的在其他MCU中或者直接给其他人使用呢? 我们知道,串口打印信息模块有两点不同。一个是不同的MCU硬件串口初始化可能是不一样的;另一个是不同MCU串口的数据寄存器可能是不一样的。而相同的部分是串口的发送数据的接口以及串口数据的发送维护部分是一样的。那么,我们完全可以把串口初始化以及MCU的串口数据寄存器通过回调函数隔离,这部分交给应用者完成,数据发送以及数据发送维护则交给复用模块中的函数完成。
我们用一个打印串口调试信息的例子来说明一下
串口打印信息模块debug.h
#ifndef_DEBUG_DEF_ #define_DEBUG_DEF_
#defineTX_BUF_COUNT 256 //串口缓存字节数量 /*#defineTX_BUF_COUNT 128*/ /*#defineTX_BUF_COUNT 64*/ /*#defineTX_BUF_COUNT 32*/
typedef struct { void (*tx_register_callback)(unsigned chartx_data); }tx_callback_t;
extern voiddebug_send_callback_init(tx_callback_t callback); extern voiddebug_send_manege(void ); extern voiddebug_send_str(const unsigned char *p_src); extern voiddebug_send_num(const INT8U *p_src,INT8U len); extern voiddebug_send_nl(void );
#endif
以下是复用模块debug.c中定义的函数
#include"debug.h" #if (TX_BUF_COUNT == 256) #defineTX_BYTE_BIT 8 #elif(TX_BUF_COUNT == 128) #define TX_BYTE_BIT 7 #elif(TX_BUF_COUNT == 64) #define TX_BYTE_BIT 6 #elif(TX_BUF_COUNT == 32) #define TX_BYTE_BIT 5 #else #define TX_BYTE_BIT 5 #endif
struct { unsigned int tx_len; //要发送的数据总长度 unsigned char tx_end_pos : TX_BYTE_BIT; //表示缓存中最后写入数据的位置 unsigned char tx_cur_pos : TX_BYTE_BIT; //表示缓存中哪个数据要发送了 unsigned char tx_buf[TX_BUF_COUNT];
}debug_info;
/////////////////////////////////////////////////////////////////////////////////// voiddebug_send_callback_init(tx_callback_t callback) { tx_calback.tx_register_callback = callback; }
voiddebug_send_start(void ) { if(debug_info.len != 0) return; debug_info.tx_cur_pos =debug_info.tx_end_pos; }
voiddebug_send_str(const unsigned char *p_src) { INT8U i;
if(p_src == 0) return;
debug_send_start(); for( ; *p_src != '\0'; p_src++) { debug_info.tx_buf[debug_info.tx_end_pos++]= *p_src; debug_info.tx_len++; } }
voiddebug_send_num(const INT8U *p_src,INT8U len) { unsigned char i;
debug_send_start(); for(i = 0 ; i < len; i++) { debug_info.tx_buf[debug_info.tx_end_pos++]= ASC_II[p_src >> 4]; debug_info.tx_buf[debug_info.tx_end_pos++]= ASC_II[p_src&0x0f]; debug_info.tx_buf[debug_info.tx_end_pos++]= ' '; debug_info.tx_len += 3; } } voiddebug_send_nl(void ) { debug_info.tx_buf[debug_info.tx_end_pos++]= '\r'; debug_info.tx_buf[debug_info.tx_end_pos++]= '\n'; debug_info.tx_len += 2; } //注意 : (1)这里debug的数据发送是以查询方式进行的,这样这个debug.c更具复用性 // (2)debug_send_manege应该放置于软件或者硬件定时器中,定时器的周期则 // 根据串口的波特率来定.比如9600,定时器的周期 >= 1ms就可以 . 如果 // 115200则定时器的周期 >= 100us就可以. voiddebug_send_manege(void ) { if(debug_info.tx_len) { if(tx_calback.tx_register_callback !=0) { uart_tx_callback(debug_info.tx_buf[debug_info.tx_cur_pos++]); //隔离变化 } //UART_DR =debug_info.tx_buf[debug_info.tx_cur_pos++]; //当然你也可以这样,UART_DR为对应MCU的串口数据寄存器 debug_info.tx_len--; } } /////////////////////////////////////////////////////////////////////////////////////////////// 以下是应用者的文件app.c #include"debug.h" unsigned char test[10] = {0x01,0x02,0x03,0x04,0x05}; voidapp_uart_register_init(unsigned long baud) { //MCU相关的寄存器初始化,假设波特率是115200 } voidapp_uart_tx_register(unsigned char tx_data) { UART_DR = tx_data; } voidapp_debug_tx_init(unsigned long baud) { app_uart_register_init(baud); debug_send_callback_init(app_uart_tx_register); } //1ms的软件定时器 voidapp_debug_1ms_clock(void ) { debug_send_manege(); } main() { app_debug_tx_init(115200); while(1) { debug_send_str("debug_test->");//发送字符串 debug_send_num(test,5); //发送数据的字符格式 debug_send_nl(); //换行 } } 由上可以看到,debug.c通过回调函数uart_tx_callback(debug_info.tx_buf[debug_info.tx_cur_pos++])隔离寄存器的变化,同时把串口硬件的寄存器初始化交给了应用者处理。当更换MCU或者把文件给别人使用时应用者只需要知道debug.h中声明的几个接口函数以及根据需要调整缓存大小即可使用这个debug模块而不需要改动debug.c中的任何实现,从而实现程序的复用性。 当然,串口打印调试信息的方法有很多,这里只是说明一个通过回调函数构建可复用的软件的一个方法。如果你有更好的方法,欢迎交流。
最后总结一下: 程序的可复用性关键在于把变化隔离出去而将其本质特征复用。
|