- 问题描述:
系统随机运行一段时间后,PA9的按键失效,测量IO一直为一个低电平。 - 硬件IO配置如下:
按键输入使用了PA9,PA10,PA11,PA12,低电平有效
模拟i2c使用了PA5,PA6.
检测硬件无任何虚焊,短路问题。
检测代码无错误操作IO的问题。
下调试器进行跟踪,在按键扫描的地方放断点,在该按键失效后,查看相关寄存器的状态。
发现读回来的值确实和io状态的低电平一致,也就是说读本身没有问题,但是这个io在按键没有按下的时候,为什么会被拉低了呢?
百思不得其解!!!!!!!
我想,一定是有什么地方错误的修改了GPIO的配置或者IDR的值或者ODR的值。
我进入调试状态,一个一个核对GPIO的寄存器,Cortex-M0的GPIO寄存器如下:
c
typedef struct
{
__IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
__IO uint16_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */
uint16_t RESERVED0; /*!< Reserved, 0x06 */
__IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */
__IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
__IO uint16_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
uint16_t RESERVED1; /*!< Reserved, 0x12 */
__IO uint16_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
uint16_t RESERVED2; /*!< Reserved, 0x16 */
__IO uint32_t BSRR; /*!< GPIO port bit set/reset registerBSRR, Address offset: 0x18 */
__IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */
__IO uint32_t AFR[2]; /*!< GPIO alternate function low register, Address offset: 0x20-0x24 */
__IO uint16_t BRR; /*!< GPIO bit reset register, Address offset: 0x28 */
uint32_t RESERVED3[49]; /*!< Reserved, 0x2A */
__IO uint32_t DS;
}GPIO_TypeDef;
经过逐一的确认和对比,在发生这个错误状态以后,我发现了一个问题,PA9的上下拉寄存器PUPDR被修改为了非法的数值11,找到原因,那接下来我就只需要找到是谁修改了它就可以了。
我们看看,全部代码里面只有GPIO_Init初始化函数会对GPIOx->PUPDR寄存器进行读写:
c
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
uint32_t pinpos = 0x00, pos = 0x00 , currentpin = 0x00;
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));
assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
assert_param(IS_GPIO_PUPD(GPIO_InitStruct->GPIO_PuPd));
/*-------------------------- Configure the port pins -----------------------*/
/*-- GPIO Mode Configuration --*/
for (pinpos = 0x00; pinpos < 0x10; pinpos++)
{
pos = ((uint32_t)0x01) << pinpos;
/* Get the port pins position */
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
if (currentpin == pos)
{
if ((GPIO_InitStruct->GPIO_Mode == GPIO_Mode_OUT) || (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_AF))
{
/* Check Speed mode parameters */
assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
/* Speed mode configuration */
GPIOx->OSPEEDR &= ~(GPIO_OSPEEDER_OSPEEDR0 << (pinpos * 2));
GPIOx->OSPEEDR |= ((uint32_t)(GPIO_InitStruct->GPIO_Speed) << (pinpos * 2));
/* Check Output mode parameters */
assert_param(IS_GPIO_OTYPE(GPIO_InitStruct->GPIO_OType));
/* Output mode configuration */
GPIOx->OTYPER &= ~((GPIO_OTYPER_OT_0) << ((uint16_t)pinpos));
GPIOx->OTYPER |= (uint16_t)(((uint16_t)GPIO_InitStruct->GPIO_OType) << ((uint16_t)pinpos));
}
GPIOx->MODER &= ~(GPIO_MODER_MODER0 << (pinpos * 2));
GPIOx->MODER |= (((uint32_t)GPIO_InitStruct->GPIO_Mode) << (pinpos * 2));
/* Pull-up Pull down resistor configuration */
GPIOx->PUPDR &= ~(GPIO_PUPDR_PUPDR0 << ((uint16_t)pinpos * 2));
GPIOx->PUPDR |= (((uint32_t)GPIO_InitStruct->GPIO_PuPd) << (pinpos * 2));
}
}
}
好的,那使出绝招,下一个断点来抓住它就可以(**技巧:**我们用__BKPT();插入一个软件断点,在if条件满足的时候代码会自动停止到这个位置):
因为我发现在出现这个问题的时候,我的PA9的上下拉配置位会被修改,对应的那个字节会变成0x5C(正确应该是0x54)
c
GPIOx->PUPDR &= ~(GPIO_PUPDR_PUPDR0 << ((uint16_t)pinpos * 2));
GPIOx->PUPDR |= (((uint32_t)GPIO_InitStruct->GPIO_PuPd) << (pinpos * 2));
if((GPIOx->PUPDR & 0x00FF0000) == 0x005C0000)
__BKPT();
如我所愿,开机运行几秒后,进入了这个断点。
跟踪发现,确实是我在调用i2c的程序时候,切换sda引脚状态(输入输出互相切换)时候进入了这个断点。但是从逻辑推理和代码来看,它们之间应该互不干涉才对啊:
i2c的输入和输出代码切换的代码如下,在调用I2cSdaSetOuput的时候进入了这个断点,发生了错误修改上下拉寄存器的情况。
c
static void I2cSdaSetInput(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = I2C_SDA_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(I2C_SDA_PORT, &GPIO_InitStructure);
}
static void I2cSdaSetOuput(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = I2C_SDA_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_Init(I2C_SDA_PORT, &GPIO_InitStructure);
}
好的,那至此,我们只需看看这两行代码的问题,为什么会修改到和i2c无关的io bit呢?
c
GPIOx->PUPDR &= ~(GPIO_PUPDR_PUPDR0 << ((uint16_t)pinpos * 2));
GPIOx->PUPDR |= (((uint32_t)GPIO_InitStruct->GPIO_PuPd) << (pinpos * 2));
对照规格书,看起来代码逻辑也没有问题,那继续跟踪一下发生运算的过程,加点代码记录一下状态:
c
{
uint32_t pd,pd1,pd2;
pd = GPIOx->PUPDR;
pd &= ~(GPIO_PUPDR_PUPDR0 << ((uint16_t)pinpos * 2));
pd1 = pd;
pd |= (((uint32_t)GPIO_InitStruct->GPIO_PuPd) << (pinpos * 2));
GPIOx->PUPDR = pd;
// GPIOx->PUPDR &= ~(GPIO_PUPDR_PUPDR0 << ((uint16_t)pinpos * 2));
// GPIOx->PUPDR |= (((uint32_t)GPIO_InitStruct->GPIO_PuPd) << (pinpos * 2));
if((GPIOx->PUPDR & 0x00FF0000) == 0x005C0000)
__BKPT();
}
一步一步的跟踪这个过程,终于发现了问题:
在进入这个断点的时候,我回溯前面的值,发现pd1保存的读取回来的值,寄存器的相关设置位没有任何问题,经过下面的运算和赋值后就错误了,这个时候,我发现GPIO_InitStruct->GPIO_PuPd的值为0x81,pinpos为6,拿起计算器一算,果然,PA9的那个上下拉设置位被修改了。
真相大白,因为GPIO_InitStruct->GPIO_PuPd的值的0x81的那个高位的bit的1(1000 0000)被错误的赋值给了PA9的设置bit,导致了该问题发生。

继续往前追,为什么GPIO_InitStruct->GPIO_PuPd会是一个0x81?
c
static void I2cSdaSetOuput(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = I2C_SDA_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_Init(I2C_SDA_PORT, &GPIO_InitStructure);
}
看到了,这个函数原来没有对它赋值,因为在我是设置为输出,上下拉状态对我没有任何影响,所以忽略了它,导致变量GPIO_InitStruct->GPIO_PuPd是一个随机的值。而这个数值,经过前面初始化函数的移位运算,有很巧的修改了PA9的设置,导致该问题发生。
简单的修改(但是比较治标不治本),对该初始化函数增加一个GPIO_InitStruct->GPIO_PuPd的赋值即可。
测试,也发现问题得以解决。
但是,问题不能到此为止,万一那天其他地方有出现这种情况呢?而且是极有可能的,因为潜意识里面我会认为我设置输出IO。输入IO的相关配置与我无关,我不应该关注它,所以我们要让GPIO_Init函数能正确的处理这非法情况。
好了,我只需对GPIO_Init函数的代码修改如下:

增加红色两个代码来对非法数值进行拦截,就可以彻底的解决这个问题。
这个bug的隐藏非常深,有一定的巧合性,这个遗留问题从stm32F030的库就带来了,今天彻底解决。
原创文章,转载请注明出处!!