了解 GPIO 如何耦合在一起的最佳方法是访问数据表。 TI 的 MSP430 数据表就是一个很好的例子。
输入/输出部分非常明确地说明了 GPIO 寄存器如何与 IO Pad 本身交互。这是端口 P1.0-P.17(来自上述数据表的第 80 页)。
输出
这里的基本要点是:
- 如果 IO 焊盘处于输出模式,则将“0”写入 P1OUT.x(例如 bit0 为 P1OUT.0)将驱动输出低电平。
这是一个更简单的简化版本:
输入
同样,这里是在“输入”模式下使用 IO 板的检查
这里的基本要点是,这里写入 P1OUT.x 对输出没有影响;输出缓冲区是三态的!
危险
如果您的程序在多个任务之间共享一个 GPIO 端口寄存器,那么您必须确保对该寄存器的访问是线程安全的。对于嵌入式系统,这通常意味着“确保 ISR 不会搞砸”。考虑 -
//... (from main thread)
P1OUT = P1OUT | 0x02;
//... (from some ISR)
P1OUT = P1OUT | 0x01;
这一般翻译成下面的反汇编
//... (from main thread)
mov P1OUT, %some_reg
operate on %some_reg
mov %some_reg, P1OUT
//... (from some ISR)
mov P1OUT, %some_reg
operate on %some_reg
mov %some_reg, P1OUT
//and then the time series looks like (P1OUT = 0 initially)
[0]: mov P1OUT, %some_reg_main_thread (%reg_m = 0)
[1]: or 0x02, %some_reg_main_thread (%reg_m = 2)
[2]: ISR fires!
[3]: mov P1OUT, %some_reg_isr_thread (%reg_i = 0)
[4]: or 0x01, %some_reg_isr_thread (%reg_i = 1)
[5]: mov %some_reg_isr_thread, P1OUT (P1OUT = 1)
[6]: ISR exits
[7]: mov %some_reg_main_thread, P1OUT (P1OUT = 2)
(end result: P1OUT=2, but should be 3 (0x01 | 0x02))
此外,IO Pad 并不像看起来那么简单,您应该经常访问数据表以了解它有多糟糕/精致/令人困惑/等等。
例如在上面的 TI 示例中,以下危害适用于您的原始问题:
(当 P1DIR.x = 0)
-
P1REN.x + P1OUT.x
- 如果 P1REN.x 为“1”,并且您切换 P1OUT.x,也会切换上拉电阻的开启和关闭
当然,IO pad 拓扑是 MCU 特定的。我建议在继续您的项目之前查看数据表中的 IO-Pad 拓扑。它应该是你最好的朋友。
代码实践
尽可能考虑使用 |=、&= 和 ^= 运算符,例如:
P1OUT |= (1<<2)+(1<<3) //assert pin2 & pin3 high
如果不能,请考虑使用打包结构或单元测试封装例程:
struct myporta {
uint b3_0 : 4; //use unsigned int-type
uint b4 : 1;
uint b5_10 : 6;
uint b25_b11 : 15;
uint b26 : 1;
uint b31_27 : 5;
} __attribute__ ((packed)); //gcc syntax
...
//write to my LED
myporta *porta = &PORTA; //if you w
porta->b26 = 1; //turn the led on
没有什么比 gpio 互相破坏更糟糕的了!