MH2030B 一个输入IO失效故障分析(stm32F030系列有类似问题)

  • 问题描述:
    系统随机运行一段时间后,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的库就带来了,今天彻底解决。

原创文章,转载请注明出处!!

相关推荐
LCG元2 小时前
STM32实战:基于STM32F103的智能饮水机(温度控制+流量计费)
stm32·单片机·嵌入式硬件
m0_377108142 小时前
stm32-DMA
stm32·单片机·嵌入式硬件
嵌入式小站2 小时前
STM32 零基础可移植教程 11:PWM 输出,让 LED 呼吸起来
stm32·单片机·嵌入式硬件
sramdram2 小时前
Cascadeteq国产替代psram芯片,国产psram芯片CSS1604S系列
单片机·嵌入式硬件·psram·cascadeteq·国产替代psram·国产psram芯片
南檐巷上学2 小时前
基于Zynq-7020的带有正弦波发生器的8051软核设计
单片机·嵌入式硬件·fpga开发·fpga
崇山峻岭之间3 小时前
单片机低功耗实验
单片机·嵌入式硬件
周周记笔记3 小时前
【元器件专题】PNP三极管如何搭建开关电路
单片机·嵌入式硬件
不脱发的程序猿3 小时前
如何创建一个标准Skill,让嵌入式经验真正复用起来
人工智能·单片机·嵌入式硬件·嵌入式·skill
czhaii3 小时前
STC8H8K32U工控板运行程序标志位显示
单片机·嵌入式硬件