我正在使用Atmega328。我的输出引脚沿引脚排列"随机"分布,即它们不属于相同的端口。例如,我的输出引脚可以是PB0,PB4,PC1,PC3和PD1。
我一直使用汇编来编程微控制器,所以这是我第一次使用C。我想知道的是,是否有办法避免为每个引脚使用 DDRx 和 Px 来设置或清除它们。
例如,我想使用这样的东西来设置端口 B 的第一个位:
#define NAME_1 DDRB,0
sbi NAME_1;
这可能吗?
编辑:
也许我没有清楚地表达自己。我想要的是能够用一些有意义的名称引用某些 I/O 端口引脚。例如,将PD3命名为"blue_LED",这样代码更具可读性,并且如果以后更改蓝色LED的位置,则可以轻松修改代码。换句话说,我希望能够打开和关闭某些引脚,而它们的名称不是硬编码的。有办法吗?
sbi
指令的特殊之处在于它直接在 AVR 平台上的 I/O 端口中操作一个位。I/O端口的正常过程是您必须使用其他指令(如out
)在I/O端口和寄存器之间复制整个单词。
也就是说,C中没有sbi
,只是不知道一个特定平台的这些特殊功能。对于您作为示例给出的程序集,您将用 C 编写:
DDRB |= 1<<0;
我个人认为这看起来很简洁,但当然你可以定义一个宏
#define sbi(x,b) (x) |= 1<<(b)
sbi(DDRB, 0);
(其中b
是">位号"),也许反过来
#define cbi(x,b) (x) &= ~(1<<(b))
cbi(DDRB, 0)
这将起作用,但我建议不要使用它。虽然第一个符号DDRB |= 1<<0;
对任何 C 程序员来说都是显而易见的,但使用这样的宏可能不是。
最后,如果您担心性能:我还没有验证这一点,但我很确定avr-gcc
足够聪明,可以在 I/O 端口上的位掩码操作有效地更改一位时发出sbi
和cbi
指令。编辑:参见JLH的答案,以获得实验结果,gcc-avr
确实足够聪明,可以发出这些sbi
/cbi
指令。
我使用 avg-gcc 对 Atmel AVR 进行编程,支持包绝对了解 cbi、sbi 以及与此相关的所有说明。 因此,除非您愿意,否则无需诉诸组装。这里有一些反汇编的C来证明这一点。
PORTD |= (1 << PD0);
d8: 58 9a sbi 0x0b, 0 ; 11
现在我将向您展示如何操作。 您可以设置每个端口的每个位,以便非常轻松地进行输入或输出。
DDRA |= (1<<PA0);
具有使端口 A 上的引脚 0 成为输出的效果,如下所示:
bit76543210
00000001 // or-ing the 1 adds 1 leaving the other bits alone.
要使引脚 A3 也成为输出,请执行以下操作:
DDRA |= (1<<PA3);
bit76543210
00001001 // now the DDRA register looks like this, pins 0 and 3 set as outputs
很乏味,对吧? 好吧,这些右侧表达式的计算结果为常量,因此您可以将这些表达式合并到一个语句中:
DDRA |= (1<<PA0) | (1<<PA3);
编译器会将右侧折叠成一个常量并将其|=
到寄存器中。 因此,每个端口实际上只需要一个这样的语句,编译器使其非常高效。
这照顾方向 - 输入或输出。 接下来是设置和清除输出。
要设置输出:
PORTD |= (1<<PD); // turns pin 0 on port D on (high) as an output.
PORTD &= ~(1<<PD); // turns pin 0 on port D off (low) as an output.
PORTD ^= (1<<PD); // toggles it (high-> low) or (low->high as an output.
编辑:
对于您提到的关于使用有意义名称的额外要求,您当然可以这样做。 我经常这样做是为了避免记住什么与什么有关。 例如,来自同一项目:
#define LED_INDICATOR_PIN PA0
#define LED_INDICATOR_PORT PORTA
#define LED_INDICATOR_DDR DDRA
我在代码中引用"友好"名称:
void initialize(void)
{
// Set up an output pin to blink an LED to show sign of life.
LED_INDICATOR_DDR |= (1 << LED_INDICATOR_PIN);
// Set up TTY port.
init_uart();
}
void toggleLED(void)
{
LED_INDICATOR_PORT ^= (1 << LED_INDICATOR_PIN);
}
更容易阅读。 这就是你所追求的吗? 我知道这就是我每次都会做的事情。
我用C++写了这样的东西:
//IO = I/O Subsystem
inline void Io_LEDred_Configure() {DDRB |= 1<<0; } //Port B0
inline void Io_LEDred_On() {PORTB |= 1<<0; }
inline void Io_LEDred_Off() {PORTB &= ~(1<<0);}
inline void Io_LEDgreen_Configure() {DDRD |= 1<<3; } //Port D3
inline void Io_LEDgreen_On() {PORTD |= 1<<3; }
inline void Io_LEDgreen_Off() {PORTD &= ~(1<<3);}
如果必须将IO切换到其他端口,则只需更改这三行即可完成。
在C++编译器发出的代码与您作为汇编程序员编码人员编写的代码完全相同。在 C 中,您必须使用常规函数。但是这些电话有一些开销。如果你想避免这种情况,你必须使用宏:
#define IO_LEDRED_CONFIGURE DDRB |= 1<<0
#define IO_LEDRED_ON PORTB |= 1<<0
...