STM32中的选项字节是什么?我如何使用它们?

所有STM32都有选项,尽管不同系列和系列的功能可能不同,但它们都是为了让用户能够自定义微控制器的一般设置。 Option Bytes主要用于在启动Cortex®-M和用户代码之前预先配置芯片上的系统。它们在通电复位后或通过设置FLASH_CR寄存器中的OBL_LAUNCH位请求时自动加载。以下是一些通常可用的示例设置:读出保护级别(RDP)、低功耗模式期间的看门狗设置、引导配置模式、耗尽阈值级别以及更多与安全相关的设置,如专有代码读取保护(PCROP)和写入保护区(WRP)。您可以想象,这些选项可以改变STM32在开机、执行甚至关机期间的行为。
选项字节位于与用户闪存不同的存储区域,可以以不同的模式和时间访问,包括:
  • 使用编程工具与SWD或JTAG(只要可用)进行常规编程阶段。
  • 使用具有可用接口(USART、SPI、I2C、CAN、USB等)的系统引导加载程序
  • 在代码执行/运行期间,基于您自己的固件实现。

本文将重点讨论后者,因为它将创建一个针对使用NUCLEO-G071RB板的STM32G071的小代码示例。它显示了使用HAL API编程选项字节所需的步骤,以及一些防止出现问题的提示和技巧,但不用担心,这些步骤可以很容易地针对任何其他STM32设备进行定制。
作为第一步,通常的建议是查看参考手册(在这种情况下为RM0444),以了解所有可能的选项字节和设置。我们文档中的选项字节部分始终在FLASH章节中,并有一个专门的分章,下面是RM0444中的一个表,显示了地址和位名称:

从上表中可以注意到,地址一次以2个字(8个字节)的比例增加,这是因为大多数选项字节都有一个经过验证的互补字,以确保正确的编程。在STM320x1的情况下,其表示如下:

好了,现在我们在文档中查找信息,以及选项字节在内存区域中的大致组织方式,我们应该研究如何对它们进行编程。
复位后,例如通电复位或仅简单的NRST引脚复位,FLASH控制寄存器的选项相关位受到写保护。要在选项字节页上运行任何操作,必须清除选项锁定位。以下顺序用于解锁此寄存器:
1.使用LOCK清除序列解锁FLASH_CR
2.写入FLASH选项密钥寄存器的OPTKEY1
3.写入FLASH选项密钥寄存器的OPTKEY2
请注意,任何不正确的序列都会锁定闪存选项寄存器,直到下一次系统重置。在键序列错误的情况下,检测到总线错误,并生成硬故障中断。一旦执行解锁序列,我们就可以在FLASH选项字节寄存器中写入所需的值——随着过程的完成,我们应该在发出选项启动命令之前检查FLASH繁忙标志。对一个选项值的任何修改都是通过先擦除用户选项字节页,然后用闪存选项寄存器中包含的值编程所有选项字节来自动执行的。在设置OPTSTRT位时,自动计算互补值并将其写入互补选项字节。
在繁忙位被硬件清除后,所有新选项都会在闪存中更新,但不会应用于系统。一个有趣的事实是,如果你从选项寄存器中执行读取,它们仍然会返回最后加载的选项字节值,新选项只有在加载后才会对系统产生影响。有两种方法可以加载选项字节:
–当设置了FLASH控制寄存器(FLASH_CR)的OBL_LAUNCH位时
–电源复位后(BOR复位或退出待机/关机模式);这里需要注意的是,它必须是一个电源重置;软件重置或简单地切换NRST行不会加载选项字节
在我们转向固件实现理论之前,我们必须考虑一个预防措施。在选项字节编程失败时(由于任何原因,例如在选项字节更改序列期间断电或重置),重置后加载选项字节的不匹配值。这些不匹配的值强制使用安全配置,该配置可能会根据STM32系列永久锁定设备。为了防止这种情况发生,只在安全的环境中对选项字节进行编程——安全的电源,没有挂起的看门狗,以及干净的复位线。为了确保这种情况,在我们的固件实现中,我们将在前进之前检查MCU上的电压电平。
固件实施:
如前所述,对于该代码,使用的板为NUCLEO-G071RB,并使用默认硬件分配进行设置,这意味着使用LED、按钮键和USART(115200/8/N/1)引脚。在这些外围设备之上,还添加了ADC和IWDG—下面将解释应用程序代码,所有这些代码都应位于主.c文件中
作为一般指南,固件应用程序将启动除IWDG以外的时钟和外围设备:
int main(void){/*用户代码开始1*/FLASH_OBProgramInitTypeDef选项字节结构;uint16_t adc_value;/*用户编码结束1*//*MCU配置-----------------------------------------------------------*/*重置所有外围设备,初始化闪存接口和系统。*/HAL_Init();/*用户代码开始初始化*/*用户代码结束初始化*/*配置系统时钟*/SystemClock_Config();/*用户代码开始系统初始化*/HAL_FLASHEx_EnableDebugger();/*用户代码结束SysInit*//*初始化所有配置的外围设备*/MX_GPIO_Init();MX_USART2_UART_Init();MX_ADC1_Init();/*用户代码开始2*/
然后,该代码将配置ADC以读取参考电压,并在电压高于某个水平时进行简单检查,如果高于某一水平,则它将向前移动。这部分代码只是一个伪实现:
/*用户代码开始2*///ADC启用-测量Vref。//Vref在内部连接到Vin[13]。Vref用于计算Vpower电平//并使用该信息禁用Option Bytes更新。//ADC在IN RCC上运行->CCIPR |=0x80000000;//选择ADC时钟=HSI16。碾压混凝土->APBENR2 |=0x00100000;//启用ADC时钟ADC->CCR |=0x00400000;//使能Vref-在使能ADC ADC1之前设置->CR |=0x000000001;//启用ADC,同时((ADC1->ISR&0x00000001)!=0x00000001){//当ADC未就绪时=循环…;}ADC1->SMPR=0x00000077;//采样时间(160.5周期)ADC1->CHSELR |=0x00002000;//选择通道13(Vref)ADC1->CR |=0x00000004;//开始转换//在这里,我们将根据电源电压决定是否更新选项字节//,同时((ADC1->ISR&0x00000004)!=0x00000004){//当ADC转换未完成时,循环…..;}ADC_value=ADC1->DR;//如果Vss~2.9V为(ADC_value>1712)//1712,则读取ADC{//Vss过低//进度指示器:ADC错误:Vss过低。//跳过更新!}
基本功能通过后,固件将检查并打印之前设置的任何标志,并确保清除所有重置和时钟控制标志。
如果(__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST;printf(“清除所有RCC标志\r\n”);
下一步是验证RDP级别,如果设置为级别1,代码将等待按下并释放蓝色按钮,然后再进行RDP回归。
HAL_FLASHEx_OBGetConfig(选项字节结构);if(OptionsBytesStruct.RDPLevel==OB_RDP_LEVEL_1){printf(“RDP LVL1\r\n”);print f(“等待BT1被按下”);while(HAL_GPIO_ReadPin(BT1_GPIO_Port,BT1_Pin)==GPIO_Pin_SET){;}printf;while(HAL_GPIO_ReadPin(BT1_GPIO_Port,BT1_Pin)==GPIO_Pin_RESET){;}while(HAL_FLASH_Unlock()!=HAL_OK){printf(“等待Flash解锁\r\n”);}while(HAL_Flash_OB_Unlock()!=HAL_OK){printf(“等待OB解锁\r\n”);}printf;RDP_Progress();}
如果不是RDP级别1,代码也将进入主循环,并等待按键执行更改几个选项字节的代码,包括将RDP更改为级别1和nBOOT选择。
/*无限循环*/*用户代码开始时*/WHILE(1){/*用户代码结束时*/*用户编码开始时3*/printf(“\033[96mPress BT1 to change Option Bytes\033[0m\r\n”);如果(HAL_GPIO_ReadPin(BT1_GPIO_Port,BT1_Pin)==GPIO_Pin_RESET){printf(”BT1 pressed \r\n“);WHILE(HAL_FLASH_Unlock()!=HAL_OK){print f(”Waiting FLASH Unlock \r\n“));}WHILE(HAL_FLASH_OB_Unlock()!=HAL_OK){打印(“等待OB解锁\r\n”);}OptionsBytesStruct.OptionsType=OPTIONBYTE_USER|OPTIONBYTE_RDP//配置USER和RDP选项BytesStruct.USERType=OB_USER_nBOOT_SEL;选项BytesStruct.USERConfig=OB_BOOT0_FROM_PIN://设置为从引脚(UART)启动选项ByteConstruct.RDPLevel=OB_RDP_LEVEL_1;而(HAL_FLASHEx_OBProgram(&OptionsBytestruct)!=HAL_OK){printf(“正在等待OB程序\r\n”);}printf;SET_BIT(FLASH->CR,FLASH _CR_OPTSTRT);而((FLASH->SR&FLASH_SR_BSY1)!=0){;}
为了确保选项字节的编程和加载,实现了两种方法——OBL_LAUNCH和具有备用入口的IWDG,其由于IWDG而唤醒并强制电源重置,因此显示了这两种方法以供演示。
printf(“OBLauch\r\n”);MX_IWDG_Init();而(HAL_FLASH_OB_Launch()!=HAL_OK){printf(“OBLauch Failed..用IWDG和待机模式重试\r\n”);HAL_PWR_EnterSTANDBYMode();}}
如果没有按键,还提供了一个简单的LED和打印消息来指导流程,也在主循环中执行
void切换LED(void){if(HAL_GPIO_ReadPin(LED_GREEN_GPIO_Port,LED_GREEN_Pin)){printf(“\033[5];32mLED\033[0m\r\n”);HAL_GPIO_WritePin(LED _GREEN_GPIO_Port,LED_GREEN_Pin,GPIO_Pin_RESET);}else{printf(”\033[1;32mLED\033[0m\r\n“);HAL_GPIO_WritePin
另一个详细信息是RDP的工作原理,因此请参阅此图以获得快速解释:

正如我们所看到的,在RDP从级别1回归到级别0时,会执行大规模擦除,因此我们在实现RDP_regression函数时需要考虑到这一点,因为如果我们碰巧将其留在FLASH中,该函数将在完成之前停止存在,并且无法按预期工作,这就是我们将该函数放入RAM的原因
void __attribute__((__section__(“.RamFunc”SR&FLASH_SR_BSY1)!=0){;}/*力OB加载*/FLASH->CR |=FLASH_CR_OBL_LAUNCH;}
结论:
选项字节是最终产品中的一个关键因素,因为它提供了一套强大的自定义功能,以确保您的微控制器能够按照您的意愿运行,包括选项字节最常用的功能之一,即读取保护,它允许您锁定STM32以防不必要的写入和读取。
在采取一些预防措施的情况下,可以在任何给定的情况下对选项字节进行编程,但重要的是在更新它们之前确保足够的条件。
希望你喜欢这篇文章!