C: минимизация дублирования кода с использованием функций в заголовочном файле
Это немного странный случай использования, поэтому поиск существующего обсуждения затруднен. Я программирую для встраиваемых систем (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 values:
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 (и соответственно обновить #include), но это не имеет большого значения.
Есть только один недостаток: IDE не знает, что делать при пошаговом выполнении кода, включенного в protocol.h, во время симуляции или отладки. Он просто остается в команде вызова во время выполнения кода, поэтому отладка будет немного сложнее.
Итак, насколько хакерское решение? Будут ли боги Си поразить меня за то, что я обдумал это? Есть ли лучшие альтернативы, которые я пропустил?
2 ответа
Альтернативой является определение макроса функции, который содержит тело кода. Некоторые операторы вставки токенов могут автоматически генерировать необходимые имена символов. Многострочные макросы могут быть созданы с помощью \
в конце всего, кроме последней строки.
#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)
Использование макросов для этой цели кажется мне плохой идеей. Невозможность отладки является лишь одним недостатком. Это также затрудняет понимание, скрывая реальное значение символов. А подпрограммы прерывания должны быть независимыми и короткими, а общие функции скрыты в функциях-обработчиках.
Первое, что я хотел бы сделать, это определить общую структуру буфера для каждого 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;
}
Полученный код очень гибкий и может быть легко изменен. Одноступенчатый код не проблема.