这是一个奇怪的用例,因此搜索现有的讨论很困难。我正在为嵌入式系统编程(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众神也会打击我吗?有没有更好的选择我忽略了?
答案 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;
}
生成的代码非常灵活,可以轻松更改。单步代码没问题。