Есть два решения, которые я использовал. Помните, что xc8 в облегченной версии является ужасным компилятором и производит странный, длинный, неоптимизированный, заполненный кодом сборки ужасов, поэтому сразу переключитесь на gcc + stm32 и бросьте xc8 в корзину.
Вы можете создать собственный контейнер / класс для взаимодействия с выводом так же, как это делает arduino с классами DigitalIn. Мы можем сохранить дескриптор на выводе, используя указатель на регистр PORTA или PORTB или PORTX и битовую позицию внутри этого регистра. Затем мы можем вычислить необходимую битовую маску, чтобы установить вывод, используя простые битовые операции. В мои первые дни программирования я делал это, и вы можете просматривать исходные тексты pinpointer.h . В основном это работает так:
pinpointer_t pp = PP_RB7; // this is PORTBbits.RB7
PP_SET_PIN_AS_OUTPUT(pp); // set RB7 as output, equal to TRISBbits.RB7 = 0
PP_WRITE_PIN(pp, 1); // output 1 on this pin, equal to PORTBbits.RB7 = 1
A pinpointer_t
имеет 2 байта. Первый байт - это позиция {PORT,LAT,TRIS}{A,B,C,...}
. Теперь, когда микрочип производит микроконтроллеры, так что регистры портов располагаются один за другим PORTB - PORTA = 1
, первый байт хранит номер, который необходимо добавить в PORTA, чтобы получить адрес порта. Мы могли бы хранить указатель здесь, но это сделало бы pinpointer_t 4 или, может быть, больше байтов.
Второй байт хранит положение булавки, используя битовую маску. Теперь PP_RB7
равно o 0x180
, т.е. второй порт PORTA + 1 = PORTB
с битовой маской 0x80, то есть 7-й бит. PP_SET_PIN_AS_OUTPUT(PP_RB7)
переводится примерно как что-то вроде ((TRISA) + (PP_RB7>>8)) &= ~PP_RB7 )
.
Зная, что число выводов никогда не превышает 7 и что никогда не бывает более 7 портов, мы можем хранить ту же информацию, используя один байт (первые 4 бита для порта, вторые 4 бита для контакта), но я обнаружил, что XC8 свободен Компилятор версии генерирует код **** для операций сдвига, например PP_RB7>>4
, и обычно с использованием 2 байтов приводит к уменьшению кода.
Так что ваша библиотека foo может выглядеть так:
// foo.c
void foo_init(void) {
PP_SET_PIN_AS_OUTPUT(foo_pin);
}
// foo.h
extern pinpointer_t foo_pin;
void foo_init(void);
// main.c
pinpointer_t foo_pin = PP_RB7;
void main() {
foo_init(void);
}
Но лучше без глобальных переменных:
// foo.c
void foo_init(struct foo_s *foo, pinpointer_t pin) {
foo->pin = pin;
PP_SET_PIN_AS_OUTPUT(foo->pin);
}
// foo.h
struct foo_s {
pinpointer_t pin;
};
void foo_init(struct foo_s *foo, pinpointer_t pin);
// main.c
void main() {
struct foo_s foo;
foo_init(&foo, PP_RB7);
}
Другое решение - использовать макросы. Макросы создают значительно меньший и более быстрый код, но его обслуживание ужасно. Я бы создал общий файл *-config
, так как макросы определяются не компилятором, а обработчиком. Ошибки распространены. Я бы сделал это так:
// foo.c
#include <foo.h>
#ifndef FOO_CALLBACK_SET_PIN_AS_OUTPUT
#error You need to define FOO_CALLBACK_SET_PIN_AS_OUTPUT
#endif
void foo_init(void) {
FOO_CALLBACK_SET_PIN_AS_OUTPUT();
}
// foo.h
#include <foo-config.h>
void foo_init(void);
// foo-config.h
#define FOO_CALLBACK_SET_PIN_AS_OUTPUT do{ TRISBbits.RB7 = 1; }while(0)
// main.c
#include <foo.h>
int main() {
foo_init();
}
foo-config.h
- это файл, созданный в вашем проекте, который содержит определения макросов, используемых библиотекой foo. Однажды я написал библиотеку для hd44780 , которая для всех операций gpio / pin использовала обратные вызовы макросов. Приложению необходимо добавить включаемый путь к процессу компиляции с конфигурацией .