C:使用头文件中的函数最小化代码重复

时间:2014-10-02 02:20:16

标签: c microchip mplab xc16

这是一个奇怪的用例,因此搜索现有的讨论很困难。我正在为嵌入式系统编程(Microchip PIC24使用XC16编译器),目前我正在通过3个独立的UART通道实现通信协议(每个UART都将从主数据表中获取数据)。

我开始编写项目的方式是让每个UART都由一个单独的模块处理,并且有很多代码重复,沿着以下伪代码行:

UART1.c:

static unsigned char buffer[128];
static unsigned char pointer = 0;
static unsigned char packet_received = 0;

void interrupt UART1Receive (void) {
    buffer[pointer++] = UART1RX_REG;
    if (end of packet condition) packet_received = 1;
}

void processUART1(void) {    // This is called regularly from main loop
    if (packet_received) {
        // Process packet
    }
}

UART2.c:

static unsigned char buffer[128];
static unsigned char pointer = 0;
static unsigned char packet_received = 0;

void interrupt UART2Receive (void) {
    buffer[pointer++] = UART2RX_REG;
    if (end of packet condition) packet_received = 1;
}

void processUART2(void) {    // This is called regularly from main loop
    if (packet_received) {
        // Process packet
    }
}

虽然上述内容很好并且运行良好,但实际上通信协议本身非常复杂,因此将其复制三次(只需更改对UART寄存器的引用)就会增加引入错误的机会。拥有单个函数并将指针传递给它不是一种选择,因为这会对速度产生太大的影响。代码需要在内存中为每个UART进行物理复制。

我给了它很多想法,尽管知道永远不会将函数放在头文件中的规则,但是决定尝试一个包含重复代码的特定头文件,引用为#defined值:

protocol.h:

// UART_RECEIVE_NAME and UART_RX_REG are just macros to be defined 
// in calling file
void interrupt UART_RECEIVE_NAME (void) {
    buffer[pointer++] = UART_RX_REG;
    if (end of packet condition) packet_received = 1;
}

UART1.c:

static unsigned char buffer[128];
static unsigned char pointer = 0;
static unsigned char packet_received = 0;

#define UART_RECEIVE_NAME UART1Receive
#define UART_RX_REG       UART1RX_REG

#include "protocol.h"

void processUART1(void) {    // This is called regularly from main loop
    if (packet_received) {
        // Process packet
    }
}

UART2.c:

static unsigned char buffer[128];
static unsigned char pointer = 0;
static unsigned char packet_received = 0;

#define UART_RECEIVE_NAME UART2Receive
#define UART_RX_REG       UART2RX_REG

#include "protocol.h"

void processUART2(void) {    // This is called regularly from main loop
    if (packet_received) {
        // Process packet
    }
}

编译时代码没有任何错误,我感到有些惊讶!它似乎确实有效,并且编译后MPLAB X甚至可以计算出所有符号引用,这样UART1.c和UART2.c中的每个宏引用都不会被识别为不可解析的标识符。然后我意识到我应该将protocol.h文件重命名为protocol.c(并相应地更新#includes),但这实际上并不是什么大问题。

只有一个缺点:IDE在模拟或调试时逐步执行protocol.h中包含的代码时不知道该怎么做。它只是在代码执行时停留在调用指令,因此调试会更困难。

这个解决方案有多糟糕?即使考虑到这一点,C众神也会打击我吗?有没有更好的选择我忽略了?

2 个答案:

答案 0 :(得分:4)

另一种方法是定义一个包含代码体的函数宏。一些令牌粘贴操作符可以自动生成所需的符号名称。可以通过在除最后一行之外的所有末尾使用\来生成多行宏。

#define UART_RECEIVE(n) \
void interrupt UART##n##Receive (void) { \
    buffer[pointer++] = UART##n##RX_REG; \
    if (end of packet condition) packet_received = 1; \
}

UART_RECEIVE(1)
UART_RECEIVE(2)

答案 1 :(得分:2)

为此目的使用宏似乎是一个坏主意。调试不可能只是一个缺点。通过隐藏符号的真实含义,它也很难理解。并且中断例程应该保持独立和简短,并且在处理函数中隐藏了常见函数。

我要做的第一件事是为每个UART定义一个公共缓冲区结构。这使得同步通信成为可能。如果每个uart需要一个单独的处理函数用于消息,它可以作为函数指针包含在内。语法有点 复杂,但它会产生有效的代码。

typedef struct uart_buf uart_buf_t;
struct uart_buf {
    uint8_t* buffer;
    int16_t  inptr;
    bool packet_received;
    void (*handler_func)(uart_buf_t*);
};

uart_buf_t uart_buf_1;
uart_buf_t uart_buf_2;

然后每个中断处理程序都是这样的:

void interrupt UART1Receive (void) {
  handle_input(UART1RX_REG, &uart_buf_1);
}

void interrupt UART2Receive (void) {
  handle_input(UART2RX_REG, &uart_buf_2);
}

公共处理程序将是:

void handle_input(uint8_t in_char, *buff) {
   buf->buffer[buf->inptr++] = in_char; 
   if (in_char=LF) 
       buf->packet_received = true;
       buf->handler_func(buf);
   }
}

消息处理程序是:

void hadle_packet(uart_buf_t* buf) {
    ... code to handle message
    buf->packet_received=0;
}

必须初始化函数指针:

void init() {
    uart_buf_1.handler_func=handler1;
    uart_buf_2.handler_func=handler1;
}

生成的代码非常灵活,可以轻松更改。单步代码没问题。