从一开始学IO操作时就直接调库,对怎么实现的就没怎么关心。但最近用IO管脚模拟Intel的8080时序,特别是数据线和IO端口不一致时,总感觉特别的别扭,就索性去查查下stm32参考手册和别人实现对IO操作的写法。
第一种 端口/清除寄存器(GPIOx_BSRR、GPIOx_BRR)
标准库和HAL库实现也是对这两个寄存器进行封装,用寄存器的人也一般用这两个寄存器。
第二种 位带别名区(ARM M3)
把IO管脚的映射到一片内存,操作内存来间接操作IO输出。
上述两种方法也有试用过,但还是感觉不好用,下面介绍本人最近的一种写法。
使用端口输出数据寄存器(GPIOx_ODR),用联合体嵌套结构体的位域指向指定IO口输出,感觉有点绕口,emmmm,还是直接上写法:
typedef union
{
struct
{
uint32_t _L :8;
uint32_t _H :8;
}y8;
struct
{
uint32_t _0 :1;
uint32_t _1 :1;
uint32_t _2 :1;
uint32_t _3 :1;
uint32_t _4 :1;
uint32_t _5 :1;
uint32_t _6 :1;
uint32_t _7 :1;
uint32_t _8 :1;
uint32_t _9 :1;
uint32_t _10 :1;
uint32_t _11 :1;
uint32_t _12 :1;
uint32_t _13 :1;
uint32_t _14 :1;
uint32_t _15 :1;
}y;
}GPIOx_ODR;
//#define PA0 (((GPIOx_ODR *)&GPIOA->ODR)->y._0 )
//#define PA1 (((GPIOx_ODR *)&GPIOA->ODR)->y._1 )
//#define PA2 (((GPIOx_ODR *)&GPIOA->ODR)->y._2 )
//#define PA3 (((GPIOx_ODR *)&GPIOA->ODR)->y._3 )
//#define PA4 (((GPIOx_ODR *)&GPIOA->ODR)->y._4 )
//#define PA5 (((GPIOx_ODR *)&GPIOA->ODR)->y._5 )
//#define PA6 (((GPIOx_ODR *)&GPIOA->ODR)->y._6 )
//#define PA7 (((GPIOx_ODR *)&GPIOA->ODR)->y._7 )
//#define PA8 (((GPIOx_ODR *)&GPIOA->ODR)->y._8 )
//#define PA9 (((GPIOx_ODR *)&GPIOA->ODR)->y._9 )
//#define PA10 (((GPIOx_ODR *)&GPIOA->ODR)->y._10)
//#define PA11 (((GPIOx_ODR *)&GPIOA->ODR)->y._11)
//#define PA12 (((GPIOx_ODR *)&GPIOA->ODR)->y._12)
//#define PA13 (((GPIOx_ODR *)&GPIOA->ODR)->y._13)
//#define PA14 (((GPIOx_ODR *)&GPIOA->ODR)->y._14)
//#define PA15 (((GPIOx_ODR *)&GPIOA->ODR)->y._15)
PA0 = 1;PA0 = 0;就可以实现类似51的P01 = 1;P01 = 0;
需要注意的是如果是对PA0 = 0x02这样写,取位域时是取右值的最低位,
等同于PA0 = 0,这种情况要对右值进行布尔类型转换,即PA0 = (bool)0x02;
上述封装唯一缺点就是需要定义太多了。。。有兴趣的朋友可以想利用各种方法进行封装,变得更加好用和快速,这里就不多说。
最后由此思路扩展,输出寄存器可以这样,理论上自然而然输入寄存器也能这样使用。