ME32x系列是内嵌ARM Cortex™ M0/M3核的32位微控制器。该系列控制器由敏矽微电子有限公司自主开发,并具有自主知识产权。敏矽微电子的微控制器包括有通用MCU和专用SOC系列,具有非常高的性价比,是MCU产品升级换代和国外产品替代的最佳选择。通用功能有高精度ADC,CAN接口,I2S音频接口,UART串口,SPI接口,I2C总线接口,看门狗定时器(WDT),通用计数器/定时器。特殊接口包括人机界面控制器(LCD驱动,电容触摸按键)和马达控制功能模块。
GPIO模块
ME32x系列所有MCU都含有多个GPIO模块,每个GPIO模块可以对16个IO通道的输入输出进行控制。GPIO模块主要实现以下功能:
- 数字管脚可以用软件定义为输入或输出
- 管脚读写可以被屏蔽
- 多个管脚的置位、清零位用一条指令实现
- 管脚的输出取反
- 每一个管脚可作为外部中断信号
- 可编程的中断触发条件
其中,实现IO端口输出的寄存器一般有OUT、SET、CLR和NOT。下面我们将对这几个寄存器如何使用做一些讨论。
GPIO输出寄存器
OUT寄存器可以实现IO的0/1输出;
SET寄存器只能实现IO的1输出,并且寄存器写1有效,读为0;
CLR寄存器只能实现IO的0输出,并且寄存器写1有效,读为0;
NOT寄存器只能实现IO的取反输出,并且寄存器写1有效,读为0;
从上面的描述,可以看出:
- 当我们要IO输出为1时,可以使用OUT或者SET寄存器
- 当我们要IO输出为0时,可以使用OUT或者CLR寄存器
- 当我们要IO当前输出取反时,可以简单的使用NOT,当然也可通过软件对当前的状态进行判断后使用OUT/SET/CLR寄存器输出对应的值。
然而由于ARM CPU不支持BIT(位)操作,我们对单个IO的输出不直接操作,只能通过软件结构的变量模式进行操作。这种结构模式可以让软件看起来简单易懂,有的时候甚至给我们带来错觉,认为ARM CPU可以实现单指令的BIT(位)操作,以至于程序员在编程过程中忽略结构的变量BIT(位)操作的真正含义,从而给以中断驱动的嵌入式软件带来不可预知的执行结果。
结构变量定义:
…
typedef struct {
union {
__IO uint32_t OUT; /*!< Pin output value register */
struct {
__IO uint32_t OUT0 : 1; /*!< GPIO pin PIOn pin PIOn_0 output value */
__IO uint32_t OUT1 : 1; /*!< GPIO pin PIOn pin PIOn_1 output value */
__IO uint32_t OUT2 : 1; /*!< GPIO pin PIOn pin PIOn_2 output value */
__IO uint32_t OUT3 : 1; /*!< GPIO pin PIOn pin PIOn_3 output value */
__IO uint32_t OUT4 : 1; /*!< GPIO pin PIOn pin PIOn_4 output value */
__IO uint32_t OUT5 : 1; /*!< GPIO pin PIOn pin PIOn_5 output value */
__IO uint32_t OUT6 : 1; /*!< GPIO pin PIOn pin PIOn_6 output value */
__IO uint32_t OUT7 : 1; /*!< GPIO pin PIOn pin PIOn_7 output value */
__IO uint32_t OUT8 : 1; /*!< GPIO pin PIOn pin PIOn_8 output value */
__IO uint32_t OUT9 : 1; /*!< GPIO pin PIOn pin PIOn_9 output value */
__IO uint32_t OUT10 : 1; /*!< GPIO pin PIOn pin PIOn_10 output value */
__IO uint32_t OUT11 : 1; /*!< GPIO pin PIOn pin PIOn_11 output value */
__IO uint32_t OUT12 : 1; /*!< GPIO pin PIOn pin PIOn_12 output value */
__IO uint32_t OUT13 : 1; /*!< GPIO pin PIOn pin PIOn_13 output value */
__IO uint32_t OUT14 : 1; /*!< GPIO pin PIOn pin PIOn_14 output value */
__IO uint32_t OUT15 : 1; /*!< GPIO pin PIOn pin PIOn_15 output value */
} OUT_b; /*!< BitSize */
};
union {
__O uint32_t SET; /*!< Pin output value set register */
struct {
__O uint32_t SET0 : 1; /*!< Set GPIO pin PIOn_0 output value */
__O uint32_t SET1 : 1; /*!< Set GPIO pin PIOn_1 output value */
__O uint32_t SET2 : 1; /*!< Set GPIO pin PIOn_2 output value */
__O uint32_t SET3 : 1; /*!< Set GPIO pin PIOn_3 output value */
__O uint32_t SET4 : 1; /*!< Set GPIO pin PIOn_4 output value */
__O uint32_t SET5 : 1; /*!< Set GPIO pin PIOn_5 output value */
__O uint32_t SET6 : 1; /*!< Set GPIO pin PIOn_6 output value */
__O uint32_t SET7 : 1; /*!< Set GPIO pin PIOn_7 output value */
__O uint32_t SET8 : 1; /*!< Set GPIO pin PIOn_8 output value */
__O uint32_t SET9 : 1; /*!< Set GPIO pin PIOn_9 output value */
__O uint32_t SET10 : 1; /*!< Set GPIO pin PIOn_10 output value */
__O uint32_t SET11 : 1; /*!< Set GPIO pin PIOn_11 output value */
__O uint32_t SET12 : 1; /*!< Set GPIO pin PIOn_12 output value */
__O uint32_t SET13 : 1; /*!< Set GPIO pin PIOn_13 output value */
__O uint32_t SET14 : 1; /*!< Set GPIO pin PIOn_14 output value */
__O uint32_t SET15 : 1; /*!< Set GPIO pin PIOn_15 output value */
} SET_b; /*!< BitSize */
};
union {
__O uint32_t CLR; /*!< Pin output value clear register */
struct {
__O uint32_t CLR0 : 1; /*!< Clear GPIO pin PIOn_0 output value */
__O uint32_t CLR1 : 1; /*!< Clear GPIO pin PIOn_1 output value */
__O uint32_t CLR2 : 1; /*!< Clear GPIO pin PIOn_2 output value */
__O uint32_t CLR3 : 1; /*!< Clear GPIO pin PIOn_3 output value */
__O uint32_t CLR4 : 1; /*!< Clear GPIO pin PIOn_4 output value */
__O uint32_t CLR5 : 1; /*!< Clear GPIO pin PIOn_5 output value */
__O uint32_t CLR6 : 1; /*!< Clear GPIO pin PIOn_6 output value */
__O uint32_t CLR7 : 1; /*!< Clear GPIO pin PIOn_7 output value */
__O uint32_t CLR8 : 1; /*!< Clear GPIO pin PIOn_8 output value */
__O uint32_t CLR9 : 1; /*!< Clear GPIO pin PIOn_9 output value */
__O uint32_t CLR10 : 1; /*!< Clear GPIO pin PIOn_10 output value */
__O uint32_t CLR11 : 1; /*!< Clear GPIO pin PIOn_11 output value */
__O uint32_t CLR12 : 1; /*!< Clear GPIO pin PIOn_12 output value */
__O uint32_t CLR13 : 1; /*!< Clear GPIO pin PIOn_13 output value */
__O uint32_t CLR14 : 1; /*!< Clear GPIO pin PIOn_14 output value */
__O uint32_t CLR15 : 1; /*!< Clear GPIO pin PIOn_13 output value */
} CLR_b; /*!< BitSize */
};
union {
__O uint32_t NOT; /*!< Pin output value invert register */
struct {
__O uint32_t NOT0 : 1; /*!< Invert GPIO pin PIOn_0 output value */
__O uint32_t NOT1 : 1; /*!< Invert GPIO pin PIOn_1 output value */
__O uint32_t NOT2 : 1; /*!< Invert GPIO pin PIOn_2 output value */
__O uint32_t NOT3 : 1; /*!< Invert GPIO pin PIOn_3 output value */
__O uint32_t NOT4 : 1; /*!< Invert GPIO pin PIOn_4 output value */
__O uint32_t NOT5 : 1; /*!< Invert GPIO pin PIOn_5 output value */
__O uint32_t NOT6 : 1; /*!< Invert GPIO pin PIOn_6 output value */
__O uint32_t NOT7 : 1; /*!< Invert GPIO pin PIOn_7 output value */
__O uint32_t NOT8 : 1; /*!< Invert GPIO pin PIOn_8 output value */
__O uint32_t NOT9 : 1; /*!< Invert GPIO pin PIOn_9 output value */
__O uint32_t NOT10 : 1; /*!< Invert GPIO pin PIOn_10 output value */
__O uint32_t NOT11 : 1; /*!< Invert GPIO pin PIOn_11 output value */
__O uint32_t NOT12 : 1; /*!< Invert GPIO pin PIOn_12 output value */
__O uint32_t NOT13 : 1; /*!< Invert GPIO pin PIOn_13 output value */
__O uint32_t NOT14 : 1; /*!< Invert GPIO pin PIOn_14 output value */
__O uint32_t NOT15 : 1; /*!< Invert GPIO pin PIOn_15 output value */
} NOT_b; /*!< BitSize */
};
…
}PA_Type;
…
#define PA_BASE 0x50060000UL
#define PA ((PA_Type *) PA_BASE)
调用:
PA->OUT_b.OUT0=0/1;
这个调用将对PA0 IO口实现输出0或者1。如果我们看这条程序的汇编,就会发现,实现这次对PA0的端口操作,实际上经过3个步骤:
- 读回PA OUT寄存器值到一个变量
- 对变量的BIT0进行逻辑运算实现BIT0的0/1操作
- 把变量的值重新写回到PA的OUT寄存器
在如果步骤1/2/3连续执行没有被中断打断,没有问题;如果1/2/3被中断打断,并且在这个中断里也对PA的OUT寄存器进行了操作,那么这个中断对PA OUT寄存器操作将在2/3执行后被覆盖掉,也就意味着在中断里的输出操作结果失效。如果同时有多个中断在执行类似的操作,整个系统会变得无法预知。这就是有人经常讲的输出不工作。。。
如果我们把OUT寄存器换成SET、CLR去执行输出1/0会怎么样呢?
PA->SET_b.SET0=1; //输出1
PA->CLR_b.CLR0=1; //输出0
虽然它也经历了寄存器读回、修改,然后写回操作,由于它只有写1有效,读为0,它无法对除PA0 IO口外其它IO造成任何影响,即使在执行读回、修改、写回操作时被其它中断打断。
总结
在对GPIO整个端口16个IO输出初始化时,可以采用OUT寄存器直接赋值
在对单个IO进行操作时,不论什么情况,都不要使用OUT寄存器,取而代之的是采用SET/CLR输出1/0