3

这是一个有点奇怪的用例,因此很难搜索现有的讨论。我正在为嵌入式系统(使用 XC16 编译器的 Microchip PIC24)编程,目前正在跨 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
    }
}

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

我想了很多,尽管知道从不将函数放在头文件中的规则,但还是决定尝试使用包含重复代码的特定头文件,并将引用作为#defined 值:

协议.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 中的代码时要做什么。它只是在代码执行时停留在调用指令处,因此调试会有点困难。

那么这个解决方案有多hacky?C神会因为我考虑这个而惩罚我吗?有没有我忽略的更好的选择?

4

2 回答 2

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)
于 2014-10-02T02:37:55.293 回答
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;
}

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

于 2014-10-30T08:39:27.557 回答