我使用了两种解决方案。请记住,精简版中的 xc8 是一个糟糕的编译器,它会生成奇怪的、冗长的、未优化的、充满恐怖的汇编代码,因此请立即切换到 gcc+stm32 并将 xc8 扔进垃圾箱。
您可以创建一个自定义容器/类来与 pin 交互,就像 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
Apinpointer_t
有 2 个字节。第一个字节是{PORT,LAT,TRIS}{A,B,C,...}
. 现在微芯片生产微控制器,因此端口寄存器一个接一个PORTB - PORTA = 1
,第一个字节存储需要添加到 PORTA 以获得端口地址的数字。我们可以在这里存储一个指针,但这会使 pinpointer_t 有 4 个或更多字节。第二个字节使用位掩码存储引脚位置。现在PP_RB7
等于 o 0x180
,即。位掩码为 0x80的第二个端口PORTA + 1 = PORTB
,即第 7 位。PP_SET_PIN_AS_OUTPUT(PP_RB7)
大致翻译为 smth like ((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 操作都使用宏回调。应用程序需要在配置中添加一个包含路径到编译过程。