STM32G0 FLASH驱动要点

项目场景:

        在使用STM32G0B1这款MCU时,使用FLASH模拟片内EEPROM的功能,选择了一个2k的页存储一些系统参数。


问题描述

        在调试功能时,操作FLASH经常失败,更有时会导致HardFault。废话不说直接上代码。

擦除代码:

/**
 * @brief 擦除页
 * 
 * @param address 起始地址
 * @param addr_len 地址长度,注:单位为字节
 * @return uint8_t FLASH_RTN_OK:擦除成功; 
 *                 其他值:擦除失败
 */
uint8_t BSP_EraseFlashPages(uint32_t address, uint32_t addr_len)
{
    FLASH_EraseInitTypeDef FlashEraseInit;
    uint32_t SectorError, addr_end;
    uint8_t rtn = FLASH_RTN_OK;

    addr_end = address + addr_len - 1;
    if (address < ADDR_FLASH_BANK_1 || address > ADDR_FLASH_END || 
        addr_end < ADDR_FLASH_BANK_1 || addr_end > ADDR_FLASH_END)
    {
        return FLASH_RTN_ERROR; // 地址不在规定范围内
    }
    
    if(address < ADDR_FLASH_BANK_2 && addr_end >= ADDR_FLASH_BANK_2) // 跨bank擦除
    {
        FlashEraseInit.Banks        = FLASH_BANK_1;
        FlashEraseInit.TypeErase    = FLASH_TYPEERASE_PAGES;     // 页擦除
        FlashEraseInit.Page       	= ((address - ADDR_FLASH_BANK_1) / 2048); // 起始页
        FlashEraseInit.NbPages    	= 127 - FlashEraseInit.Page + 1;
        
        HAL_FLASH_Unlock();	//解锁
        if (HAL_FLASHEx_Erase(&FlashEraseInit, &SectorError) != HAL_OK)
        {
            rtn = FLASH_RTN_ERASE_ERROR;
            //发生错误了
            HAL_FLASH_Lock();           //上锁
            return rtn;
        }
        FLASH_WaitForLastOperation(FLASH_WAITETIME);                //等待上次操作完成

        FlashEraseInit.Banks        = FLASH_BANK_2;
        FlashEraseInit.TypeErase    = FLASH_TYPEERASE_PAGES;     // 页擦除
        FlashEraseInit.Page       	= 0; // 起始页
        FlashEraseInit.NbPages    	= ((addr_end - ADDR_FLASH_BANK_1) / 2048) - 127;
        if (HAL_FLASHEx_Erase(&FlashEraseInit, &SectorError) != HAL_OK)
        {
            rtn = FLASH_RTN_ERASE_ERROR;
            //发生错误了
        }
        FLASH_WaitForLastOperation(FLASH_WAITETIME);                //等待上次操作完成

        HAL_FLASH_Lock();           //上锁
        return rtn;
    }
    else // 同BANK擦除
    {
        // 判断地址所在bank
        if(address >= ADDR_FLASH_BANK_2) 
        {
            FlashEraseInit.Banks    = FLASH_BANK_2;	// 要擦除的块
        } 
        else
        {
            FlashEraseInit.Banks    = FLASH_BANK_1;
        } 

        FlashEraseInit.TypeErase    = FLASH_TYPEERASE_PAGES;     // 页擦除
        FlashEraseInit.Page       	= ((address - ADDR_FLASH_BANK_1) / 2048); // 起始页
        FlashEraseInit.NbPages    	= ((addr_end - ADDR_FLASH_BANK_1) / 2048) - FlashEraseInit.Page + 1;
        
        HAL_FLASH_Unlock();	//解锁
        if (HAL_FLASHEx_Erase(&FlashEraseInit, &SectorError) != HAL_OK)
        {
            rtn = FLASH_RTN_ERASE_ERROR;
            //发生错误了
        }

        FLASH_WaitForLastOperation(FLASH_WAITETIME);                //等待上次操作完成
        HAL_FLASH_Lock();           //上锁
        return rtn;
    }
	
}

写入代码:

/**
 * @brief 从指定的Flash地址写入数据,按双字写入
 * 
 * @param writeAddr Flash写入起始地址
 * @param pBuffer 写入缓冲区首指针,内存地址必须8字节对其
 * @param ByteToWrite 要写的字节个数,注意是字节个数
 * @return uint8_t FLASH_RTN_OK:写入成功; 其他值:写入失败
 */
uint8_t BSP_WriteFlashDoubleWord(uint32_t writeAddr, uint64_t *pBuffer, uint32_t ByteToWrite)
{
    HAL_StatusTypeDef FlashStatus = HAL_OK;
    uint32_t          addrx = 0;
    uint32_t          endaddr = 0;
    uint8_t           rtn = FLASH_RTN_OK;

    //非法地址
    if (writeAddr < STM32_FLASH_BASE || writeAddr % 4)
    {
        return FLASH_RTN_ERROR;
    }
    if (((uint32_t)pBuffer & 7) != 0)
    {
        return FLASH_RTN_ERROR;
    }
    addrx   = writeAddr;                 //写入的起始地址
    endaddr = writeAddr + ByteToWrite;   //写入的结束地址

    FlashStatus = FLASH_WaitForLastOperation(FLASH_WAITETIME);          //等待上次操作完成

    HAL_FLASH_Unlock();                  //解锁
    if (FlashStatus == HAL_OK)
    {
        addrx = writeAddr;
        while (addrx < endaddr) //写数据
        {
            if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addrx, *pBuffer) != HAL_OK) //写入数据
            {
                HAL_FLASH_Lock();           //上锁
                return FLASH_RTN_WRITE_ERROR;//写入异常
            }
            addrx   += sizeof(typeof(*pBuffer));
            pBuffer += 1;
        }
    }
    HAL_FLASH_Lock();           //上锁
    return rtn;
}

原因分析:

        这段代码是参考之前的STM32F4的代码,怀疑是F4和G0的flash操作有差异,可能有些时序有误,导致了hardfault。于是翻出了G0的手册阅读了flash章节,发现确实是由于操作差异导致了问题,下面就详细展开说明。

  1. G0只提供了两种写入方式:双字写入和快速写入(32 double words = 256 bytes),除此之外的写入都被视为非法操作,会引起SR寄存器错误标志位置位。
  2. 由于写入方式限制,导致SR寄存器可能在当前操作前已经出现错误标志位,手册里也对时序做出明确规定,先清除由于之前操作引起的错误标志位,再执行本次flash操作。

        基于以上两点,手册对flash操作进行了详细说明:

页擦除:

        擦除步骤:

        1)解锁FLASH

        2)等待SR->BSY1 和 SR->BSY2为0

        3)检查并清除所有的错误标志位,尤其是SR->PGSERR

        4)以上步骤都通过后,才可以进行擦除操作

        HAL库的源码已经对以上的操作进行了一些封装:

 

        1、擦除前先判断BSY1和BSY2, 如果置位,直接返回HAL_TIMEOUT。程序应对返回值进行判断,返回值是HAL_TIMEOUT并且*PageError未被修改,表示flash正在操作,需等待。

        2、判断错误标志位,有则清除并返回HAL_ERROR,程序应对返回值进行判断,返回值是HAL_ERROR并且*PageError未被修改,表示之前flash操作有误,但已经将错误标志位清除,可以重新进行擦除操作。

          前面判断无问题,flash空闲并且也无错误发生。执行擦除操作,返回值是HAL_ERROR或者HAL_TIMEOUT,并且*PageError不是0xFFFFFFFF则代表擦除失败的页标号,反之则擦除成功。

写入:

        写入步骤:

        1)写过的地址不可以写入非0数据。

           (全写0代表此区域废除->flash均衡算法,写满一页再擦除)

        2)字节或半字写入会导致SIZERR,地址未8字节对齐导致PGAERR

        3)等待SR->BSY1 和 SR->BSY2为0

        4)检查并清除所有的SR寄存器内的错误标志位

        5)执行写操作

        源码:

        1、先判断BSY1和BSY2, 如果置位,直接返回HAL_TIMEOUT。程序应对返回值进行判断,返回值是HAL_TIMEOUT,表示flash正在操作,需等待。

        2、判断错误标志位,有则清除并返回HAL_ERROR,程序应对返回值进行判断,返回值是HAL_ERROR,表示之前flash操作有误,但已经将错误标志位清除,可以重新进行写入操作。

FLASH操作时间

        另外查看datasheet还可以知道页擦除的时间最长可以去到40ms,时间上也要注意。

        由此已经解释清除了G0片内flash的操作时序,参照正确的时序修改代码应该就可以解决问题。


解决方案:

按照上述时序优化flash驱动代码。

 定义返回值,可以精准分析具体的出错原因。

// 返回值
#define FLASH_RTN_OK             	0x00	// OK
#define FLASH_RTN_PARA_ERROR        0x01    // 参数错误
#define FLASH_RTN_UNLOCK_ERROR	    0x02	// 解锁失败
#define FLASH_RTN_LOCK_ERROR		0x04	// 上锁失败
#define FLASH_RTN_ERASE_ERROR    	0x08	// 擦除错误
#define FLASH_RTN_WRITE_ERROR    	0x10	// 写错误
#define FLASH_RTN_BSY			    0x20	// flash busy
#define FLASH_RTN_SR_ERROR		    0x40	// SR错误标志置位

擦除:


/**
 * @brief 锁定flash
 * 
 */
static uint8_t flash_lock_private(void)
{
	if(HAL_FLASH_Lock() != HAL_OK)          //上锁
	{
		return FLASH_RTN_LOCK_ERROR;
	}
	return FLASH_RTN_OK;
}

/**
 * @brief 擦除flash
 * 
 */
static uint8_t flash_erase_private(FLASH_EraseInitTypeDef *herase, uint32_t *hpage)
{
	uint8_t i, rtn = FLASH_RTN_OK;
	HAL_StatusTypeDef ret;
	
	*hpage = 0xFFFFFFFF;
	for(i = 0; i < 2; i++) // 操作两次,防止第一次由于之前的flash操作失败导致报错
	{
		ret = HAL_FLASHEx_Erase(herase, hpage);
		if(ret == HAL_OK)
		{
			rtn |= FLASH_RTN_OK;
			break;
		}
		else if(i == 0 && ret == HAL_ERROR && *hpage == 0xFFFFFFFF)
		{
			// 首次出现ERROR,由于之前的操作失败导致报错
			// HAL_FLASHEx_Erase()会清除SR寄存器的错误标志,重试第二次
			continue; 
		}
		else
		{
			if(ret == HAL_TIMEOUT)
			{
				rtn |= FLASH_RTN_BSY;
			}
			else if(ret == HAL_ERROR)
			{
				rtn |= FLASH_RTN_SR_ERROR;
			}
			
			if(*hpage != 0xFFFFFFFF)
				rtn |= FLASH_RTN_ERASE_ERROR;

			break;
		}
	}
	
	return rtn;
}
/**
 * @brief 擦除页
 * 
 * @param address 起始地址
 * @param addr_len 地址长度,注:单位为字节
 * @return uint8_t FLASH_RTN_OK:擦除成功; 
 *                 其他值:擦除失败
 */
uint8_t BSP_EraseFlashPages(uint32_t address, uint32_t addr_len)
{
    FLASH_EraseInitTypeDef FlashEraseInit;
    uint32_t SectorError, addr_end;
    uint8_t rtn = FLASH_RTN_OK;

    addr_end = address + addr_len - 1;
    if (address < ADDR_FLASH_BANK_1 || address > ADDR_FLASH_END || 
        addr_end < ADDR_FLASH_BANK_1 || addr_end > ADDR_FLASH_END)
    {
        return FLASH_RTN_PARA_ERROR; // 地址不在规定范围内
    }
    
	if(HAL_FLASH_Unlock() != HAL_OK)	//解锁
	{
		return FLASH_RTN_UNLOCK_ERROR;
	}
	
    if(address < ADDR_FLASH_BANK_2 && addr_end >= ADDR_FLASH_BANK_2) // 跨bank擦除
    {
        FlashEraseInit.Banks        = FLASH_BANK_1;
        FlashEraseInit.TypeErase    = FLASH_TYPEERASE_PAGES;     // 页擦除
        FlashEraseInit.Page       	= ((address - ADDR_FLASH_BANK_1) / 2048); // 起始页
        FlashEraseInit.NbPages    	= 127 - FlashEraseInit.Page + 1;
        if((rtn = flash_erase_private(&FlashEraseInit, &SectorError)) != FLASH_RTN_OK)
		{
			rtn |= flash_lock_private(); // 上锁
			return rtn;
		}
        
        FlashEraseInit.Banks        = FLASH_BANK_2;
        FlashEraseInit.TypeErase    = FLASH_TYPEERASE_PAGES;     // 页擦除
        FlashEraseInit.Page       	= 0; // 起始页
        FlashEraseInit.NbPages    	= ((addr_end - ADDR_FLASH_BANK_1) / 2048) - 127;
		rtn = flash_erase_private(&FlashEraseInit, &SectorError);
    }
    else // 同BANK擦除
    {
        // 判断地址所在bank
        if(address >= ADDR_FLASH_BANK_2) 
        {
            FlashEraseInit.Banks    = FLASH_BANK_2;	// 要擦除的块
        } 
        else
        {
            FlashEraseInit.Banks    = FLASH_BANK_1;
        } 

        FlashEraseInit.TypeErase    = FLASH_TYPEERASE_PAGES;     // 页擦除
        FlashEraseInit.Page       	= ((address - ADDR_FLASH_BANK_1) / 2048); // 起始页
        FlashEraseInit.NbPages    	= ((addr_end - ADDR_FLASH_BANK_1) / 2048) - FlashEraseInit.Page + 1;
		rtn = flash_erase_private(&FlashEraseInit, &SectorError);
    }
	
	rtn |= flash_lock_private(); // 上锁
	return rtn;
}

写入:


/**
 * @brief 从指定的Flash地址写入数据,按双字写入
 * 
 * @param writeAddr Flash写入起始地址,写入起始地址必须8字节对齐
 * @param pBuffer 写入缓冲区首指针
 * @param ByteToWrite 要写的字节个数,注意是字节个数,必须是8的整倍数
 * @return uint8_t FLASH_RTN_OK:写入成功; 其他值:写入失败
 */
uint8_t BSP_WriteFlashDoubleWord(uint32_t writeAddr, uint64_t *pBuffer, uint32_t ByteToWrite)
{
	HAL_StatusTypeDef ret;
    uint32_t addrx = 0, endaddr = 0, cnt = 0;
    uint8_t rtn = FLASH_RTN_OK;

    //非法地址
    if (writeAddr < STM32_FLASH_BASE || writeAddr % 8)
    {
        return FLASH_RTN_PARA_ERROR;
    }
//    if (((uint32_t)pBuffer & 7) != 0)
//    {
//        return FLASH_RTN_PARA_ERROR;
//    }
    addrx   = writeAddr;                 //写入的起始地址
    endaddr = writeAddr + ByteToWrite;   //写入的结束地址

	if(HAL_FLASH_Unlock() != HAL_OK)	//解锁
	{
		return FLASH_RTN_UNLOCK_ERROR;
	}
	
	addrx = writeAddr;
	while (addrx < endaddr) //写数据
	{
		if((ret = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addrx, *pBuffer)) != HAL_OK) //写入数据
		{
			if(cnt == 0 && ret == HAL_ERROR)
			{
				// 首次出现ERROR,由于之前的操作失败导致报错
				// HAL_FLASH_Program()会清除SR寄存器的错误标志,重试第二次
				cnt++;
				continue;
			}
			else
			{
				rtn |= FLASH_RTN_WRITE_ERROR;
				break;
			}
		}
		addrx   += sizeof(typeof(*pBuffer));
		pBuffer += 1;
		cnt++;
	}

    rtn |= flash_lock_private(); // 上锁
	return rtn;
}

完善驱动后,问题得到解决,flash操作不再失败,也不会进入hardfault。


总结:        

        总的来说,在使用MCU的时候,还是得查看官方的手册,一切操作以手册为准。本以为ST的外设驱动兼容性会很高,一开始就图省事直接把F4的代码搬过来用了,后面还是得老老实实地读手册扣代码。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值