我目前正致力于在C中设置一个框架,以便在多个微控制器之间使用。 框架必须携带所有特定于设备的代码,因此应用程序仅包含外设的抽象用法(如SerialPort_Read,write,SetBaudRate等)。
我在C中寻找解决方案时遇到的一个问题是I / O引脚映射。我已经看过项目(比如非常流行的Arduino),其中pin地图被放置在运行时使用的LUT(查找表)中。但是,在运行时期间永远不会修改此LUT,因此在内存中使用它是没有用的。 例如,此函数从一些'const uint'表中解析一些位索引和寄存器,并设置或清除一点:
void pinMode(uint8_t pin, uint8_t mode)
{
uint8_t bit = digitalPinToBitMask(pin);
uint8_t port = digitalPinToPort(pin);
volatile uint8_t *reg;
if (port == NOT_A_PIN) return;
// JWS: can I let the optimizer do this?
reg = portModeRegister(port);
if (mode == INPUT) {
uint8_t oldSREG = SREG;
cli();
*reg &= ~bit;
SREG = oldSREG;
} else {
uint8_t oldSREG = SREG;
cli();
*reg |= bit;
SREG = oldSREG;
}
}
因为这是在控制器上运行的实际C代码,所以会降低效率和速度。我宁愿定义一些做同样事情的宏,但是在编译过程中已经解决了可以更有效地编译的“单行”:
GPIO_Write(PORTA, 5, 1); // Write '1' to pin 5 on PORTA
> LATA |= 1<<5; // Sets bit 5 high
GPIO_Tris(PORTA, 4, OUTPUT); // Set pin 4 on PORTA to output
> PORTA &= ~(1<<4); // sets pin 4 as output I/O type
有人知道在C中定义和使用带宏的查找表是否可能(以及如何)?
目前我正在使用MicroChip C30编译器,我相信它是基于GCC的。它应该可以在不同的编译器之间移植,包括MicroChip C18,C32以及ARM和AVR。
答案 0 :(得分:2)
对于您的具体示例,这些内容将起作用:
#define WRITE_PORTA LATA
#define GPIO_Write(port, pin, value) \
(value ? WRITE_##port |= (1U << (pin)) \
: WRITE_##port &= ~(1U << (pin)))
#define INPUT 0
#define OUTPUT 1
#define GPIO_Tris(port, pin, direction) \
((direction) == INPUT ? port |= (1U << (pin)) \
: port &= ~(1U << (pin)))
您必须确保以系统将理解的方式定义LATA
和PORTA
- 特别是尝试以其在您的示例中的方式重载其含义可能很难解决。
答案 1 :(得分:1)
您定位的是哪个处理器或微控制器? 你可能低估了LUT的用处。
对于许多处理器,LUT不只是将“逻辑”引脚编号映射到单个值,即“物理”引脚编号。 LUT将“逻辑”引脚号映射到几条信息。
通常,“逻辑”引脚映射到相应读/输入或写/输出寄存器的端口地址,以及读或写寄存器中的位偏移。因此,许多MCU上的引脚值实际上映射到结构。它还可能包括映射到数据方向寄存器及其中的字段,以及设置上拉或下拉电阻状态的寄存器。
例如,我有代码来复用8x8显示器。在运行时,我需要使用pinMode将引脚从输出转换为高阻抗输入,因此需要以某种方式对信息进行编码。
在一些MCU上可以做一些聪明才智的事情。 ARM MCU(我相信8051,虽然我从未使用过)使用“位带寻址”http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0179b/CHDJHIDF.html
这为每个端口引脚分配一个唯一的存储器地址,固定的偏移量可以导出另一个数据寄存器的引脚地址和其他功能。这不是魔术,代码对经常存储在LUT中的信息进行编码。
对于其他MCU,它们确实需要端口和位位置,因此每个引脚编号都有两个值。
如果你愿意放弃使用整数作为引脚的想法,而是使用名称,比如P0,P1,那么你可以初始化很多const struct
,每个引脚名称和你的函数将采用const结构值。该结构将包含初始化的端口和位偏移或位掩码值。编译器可能能够针对速度进行优化。这将避免使用LUT,但仍会使用相似数量的空间用于所使用的引脚。您可以对其进行排列,以便不需要将未使用的引脚包含在代码中,从而节省空间。
编辑:如果您愿意使用C ++,我建议使用C ++模板,它可以提供比宏更好的解决方案。它们可以是类型安全的,并且通常更容易调试(如果您有硬件调试,例如JTAG和gdb)
答案 2 :(得分:1)
考虑以下宏:
#define write(port, pin, value) do { \
if (value) \
LAT##port |= 1 << pin; \
else \
LAT##port &= ~(1 << pin); \
} while (0)
用法:
write(A, 3, 1); // compiles to LATA |= 1 << 3;
write(B, 2, 0); // compiles to LATB &= ~(1 << 2);
这是你追求的那种东西吗?
答案 3 :(得分:0)
我已经看过它(https://github.com/triffid/Teacup_Firmware/blob/Gen7/arduino.h)有几个宏:
/// Read a pin
#define _READ(IO) (IO ## _RPORT & MASK(IO ## _PIN))
/// write to a pin
#define _WRITE(IO, v) do { if (v) { IO ## _WPORT |= MASK(IO ## _PIN); } else { IO ## _WPORT &= ~MASK(IO ## _PIN); }; } while (0)
/// set pin as input
#define _SET_INPUT(IO) do { IO ## _DDR &= ~MASK(IO ## _PIN); } while (0)
/// set pin as output
#define _SET_OUTPUT(IO) do { IO ## _DDR |= MASK(IO ## _PIN); } while (0)
// why double up on these macros? see http://gcc.gnu.org/onlinedocs/cpp/Stringification.html
/// Read a pin wrapper
#define READ(IO) _READ(IO)
/// Write to a pin wrapper
#define WRITE(IO, v) _WRITE(IO, v)
/// set pin as input wrapper
#define SET_INPUT(IO) _SET_INPUT(IO)
/// set pin as output wrapper
#define SET_OUTPUT(IO) _SET_OUTPUT(IO)
使用:
#define DIO0_PIN PIND0
#define DIO0_RPORT PIND
#define DIO0_WPORT PORTD
#define DIO0_PWM &OCR0B
#define DIO0_DDR DDRD
#define DIO1_PIN PIND1
#define DIO1_RPORT PIND
#define DIO1_WPORT PORTD
#define DIO1_PWM &OCR2B
#define DIO1_DDR DDRD
...
您可以修改宏以采用直接整数而不是DIOn