发新帖本帖赏金 1.00元(功能说明)我要提问
返回列表
打印

一些无关紧要的编程经验

[复制链接]
1202|5
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
红蛋大叔|  楼主 | 2017-10-21 22:04 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
回调函数之 -  增加程序的复用性

     上次说了采用回调函数隔离第三方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中的任何实现,从而实现程序的复用性。
      当然,串口打印调试信息的方法有很多,这里只是说明一个通过回调函数构建可复用的软件的一个方法。如果你有更好的方法,欢迎交流。

最后总结一下:
程序的可复用性关键在于把变化隔离出去而将其本质特征复用。






打赏榜单

SmartEnergy 打赏了 1.00 元 2017-10-22
理由:抽象思维好

相关帖子

沙发
红蛋大叔|  楼主 | 2017-10-21 22:05 | 只看该作者

使用特权

评论回复
板凳
红蛋大叔|  楼主 | 2017-10-22 09:37 | 只看该作者
几点错误:
(1)文中INT8U  对应的定义是 unsigned char
(2)文中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++]= ASC_II[p_src[i] & 0x0f]以及debug_info.tx_buf[debug_info.tx_end_pos++]= ASC_II[p_src[i] & 0x0f]

使用特权

评论回复
地板
qinxingtech| | 2018-5-15 09:00 | 只看该作者
红蛋大叔 发表于 2017-10-22 09:37
几点错误:
(1)文中INT8U  对应的定义是 unsigned char
(2)文中debug_info.tx_buf[debug_info.tx_end_p ...

楼主很厉害

使用特权

评论回复
5
dukedz| | 2018-5-15 14:27 | 只看该作者
本帖最后由 dukedz 于 2018-5-15 14:28 编辑

如果只是想通用打印接口,用 printf 不就好了,某些情況下標準 printf 太大的話也可以自己寫精簡版的 printf...

調試打印最主要的是不希望它影響代碼運行,譬如在中斷函數裏面加了打印,那麼就會影響後續中斷丟失,所以希望把打印輸出到緩存,然後在主循環中發送,或是直接後臺 DMA 或中斷自動發送串口打印數據。。。

貼一點我寫的庫出來,完整代碼下載:https://github.com/dukelec/cdnet/tree/master/utils先是頭文件 debug.h:
#ifndef __DEBUG_H__
#define __DEBUG_H__

#ifndef dprintf
#define dprintf(fmt, ...) _dprintf(fmt, ## __VA_ARGS__)
#endif

#define d_info(fmt, ...) dprintf("I: " fmt, ## __VA_ARGS__)
#define d_warn(fmt, ...) dprintf("W: " fmt, ## __VA_ARGS__)
#define d_error(fmt, ...) dprintf("E: " fmt, ## __VA_ARGS__)

#define dd_info(name, fmt, ...) dprintf("I: %s: " fmt, name, ## __VA_ARGS__)
#define dd_warn(name, fmt, ...) dprintf("W: %s: " fmt, name, ## __VA_ARGS__)
#define dd_error(name, fmt, ...) dprintf("E: %s: " fmt, name, ## __VA_ARGS__)

#ifdef VERBOSE
#define d_verbose(fmt, ...) dprintf("V: " fmt, ## __VA_ARGS__)
#define dd_verbose(name, fmt, ...) dprintf("V: %s: " fmt, name, ## __VA_ARGS__)
#ifndef DEBUG
#define DEBUG
#endif // DEBUG
#else
#define d_verbose(fmt, ...) do {} while (0)
#define dd_verbose(name, ...) do {} while (0)
#endif

#ifdef DEBUG
#define d_debug(fmt, ...) dprintf("D: " fmt, ## __VA_ARGS__)
#define dd_debug(name, fmt, ...) dprintf("D: %s: " fmt, name, ## __VA_ARGS__)
#else
#define d_debug(fmt, ...) do {} while (0)
#define dd_debug(name, ...) do {} while (0)
#endif

void _dprintf(char *format, ...);
void dputs(char *str);
void dhtoa(uint32_t val, char *buf);
void debug_init(void);
void debug_flush(void);

void hex_dump_small(char *pbuf, const void *addr, int len, int max);
void hex_dump(const void *addr, int len);

#endif


debug.c
#include "common.h"

extern uart_t debug_uart;

#ifndef LINE_LEN
    #define LINE_LEN    80
#endif
#ifndef DBG_LEN
    #define DBG_LEN     60
#endif

typedef struct {
    list_node_t node;
    uint8_t data[LINE_LEN];
    int len;
} dbg_node_t;


static dbg_node_t dbg_alloc[DBG_LEN];

static list_head_t dbg_free = {0};
static list_head_t dbg_tx = {0};
static int dbg_lost_cnt = 0;


// for dprintf

void _dprintf(char* format, ...)
{
    dbg_node_t *buf = list_get_entry_it(&dbg_free, dbg_node_t);
    if (buf) {
        va_list arg;
        va_start (arg, format);
        // WARN: stack may not enough for reentrant
        buf->len = vsnprintf((char *)buf->data, LINE_LEN, format, arg);
        va_end (arg);
        list_put_it(&dbg_tx, &buf->node);
    } else {
        uint32_t flags;
        local_irq_save(flags);
        dbg_lost_cnt++;
        local_irq_restore(flags);
    }
}

......

void debug_flush(void)
{
    static int dbg_lost_last = 0;

    while (true) {
#ifdef DBG_TX_IT
        static dbg_node_t *cur_buf = NULL;
        if (!dbg_transmit_is_ready(&debug_uart))
            return;
        if (cur_buf) {
            list_put_it(&dbg_free, &cur_buf->node);
            cur_buf = NULL;
        }
#endif

        dbg_node_t *buf = list_get_entry_it(&dbg_tx, dbg_node_t);
        if (!buf) {
            if (dbg_lost_last != dbg_lost_cnt) {
                _dprintf("#: dbg lost: %d -> %d\n", dbg_lost_last, dbg_lost_cnt);
                dbg_lost_last = dbg_lost_cnt;
            }
            return;
        }
#ifdef DBG_TX_IT
        dbg_transmit_it(&debug_uart, buf->data, buf->len);
        cur_buf = buf;
#else
        dbg_transmit(&debug_uart, buf->data, buf->len);
        list_put_it(&dbg_free, &buf->node);
#endif
    }
}

// for printf

int _write(int file, char *data, int len)
{
   if (file != STDOUT_FILENO && file != STDERR_FILENO) {
      errno = EBADF;
      return -1;
   }
   dbg_transmit(&debug_uart, (uint8_t *)data, len);
   return len;
}

使用特权

评论回复
6
dukedz| | 2018-5-15 14:34 | 只看该作者
本帖最后由 dukedz 于 2018-5-15 14:36 编辑

再補充一下在應用程序中 dbg_transmit 相關的定義參考,此爲 STM32 平臺,完整代碼:https://github.com/dukelec/cdbus_bridge/blob/master/fw/usr/common_append.h

static inline
void dbg_transmit(uart_t *uart, const uint8_t *buf, uint16_t len)
{
#if 1 // avoid hal check
    uint16_t i;
    for (i = 0; i < len; i++) {
        while (!__HAL_UART_GET_FLAG(uart->huart, UART_FLAG_TXE));
        uart->huart->Instance->DR = *(buf + i);
    }
#else
    HAL_UART_Transmit(uart->huart, (uint8_t *)buf, len, HAL_MAX_DELAY);
#endif
}

static inline
void dbg_transmit_it(uart_t *uart, const uint8_t *buf, uint16_t len)
{
    HAL_UART_Transmit_DMA(uart->huart, (uint8_t *)buf, len);
}

static inline bool dbg_transmit_is_ready(uart_t *uart)
{
#if 1 // DMA
    if (uart->huart->TxXferCount == 0) {
        uart->huart->gState = HAL_UART_STATE_READY;
        return true;
    } else {
        return false;
    }
#else
    return uart->huart->gState == HAL_UART_STATE_READY;
#endif
}

使用特权

评论回复
发新帖 本帖赏金 1.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

25

主题

69

帖子

3

粉丝