Option Bytes
所有 STM32 都有option,尽管不同系列和系列的功能可能有所不同,但它们都是为了让用户能够自定义微控制器的常规设置。option bytes主要用于在启动 Cortex®-M 和用户代码之前预配置片上系统。它们会在上电(Power-On)复位后自动加载,或者根据请求通过设置 FLASH_CR 寄存器中的 OBL_LAUNCH 位来加载。
以下是一些通常可用的示例设置:
- 读出保护级别 ( read-out protection level,RDP)
- 低功耗模式下的看门狗设置
- 启动配置模式
- 掉电阈值级别
- 与安全相关的更多设置,例如专有代码读取保护 (proprietary code read protection,PCROP) 和写保护区域(write protection areas,WRP)
你可以想象,这些选项可以改变STM32在开机、执行、甚至关机时的行为方式。
Option Bytes驻留在与用户Flash不同的存储区域中,并且可以以不同的模式和时间访问,包括:
- 使用编程工具通过 SWD 或 JTAG(只要可用)进行常规编程阶段。
- 使用具有可用接口(USART、SPI、I2C、CAN、USB 等)的系统引导加载程序
- 在代码执行/运行时基于您自己的固件实现。
Firmware implementation
本文将重点关注后者,因为它将使用 NUCLEO-G071RB 板创建一个针对 STM32G071 的小型代码示例。它显示了使用 HAL API 对Option Bytes进行编程所需的步骤以及防止出现问题的一些提示和技巧,但不用担心,这些步骤可以轻松地针对任何其他 STM32 设备进行定制。
第一步,通常的建议是查看参考手册(本例中为 RM0444)以熟悉所有可能的Option Bytes和设置。我们文档中的Option Bytes部分始终位于 FLASH 章节中,并有专门的子章节,下面是 RM0444 中的表格,显示了地址和位名称:
从上表中人们可能会注意到,地址一次以 2 个字(8 字节)的比例增加,这是因为大多数Option Bytes都有一个互补字(complementary word),经过验证以确保正确编程。对于 STM32G0x1,其表示方式如下:
既然我们现在在文档中查找信息以及option bytes在内存区域中的大致组织方式,我们应该研究如何对它们进行编程。
复位后,例如上电复位或只是简单的 NRST 引脚复位,FLASH 控制寄存器的选项相关位将被写保护.要在option bytes页上运行任何操作,必须清除选项锁定位。以下序列用于解锁该寄存器:
- 使用LOCK清除序列解锁FLASH_CR
- 写入FLASH选项密钥寄存器的OPTKEY1
- 写入FLASH选项密钥寄存器的OPTKEY2
请注意,任何不正确的序列都会锁定闪存选项寄存器,直到下一次系统复位。如果按键顺序错误,则会检测到总线错误并生成硬故障中断.一旦执行解锁序列,我们就可以在闪存选项字节寄存器中写入所需的值 - 当该过程完成时,我们应该在发出选项启动命令之前检查闪存忙标志(FLASH busy flag)。对一个选项值的任何修改都是通过首先擦除用户选项字节页自动执行的,然后使用闪存选项寄存器中包含的值对所有Option Bytes进行编程。设置 OPTSTRT 位后,将自动计算互补值并将其写入互补选项字节中。
硬件清除忙位(busy bit)后,所有新选项都会更新到 Flash 存储器中,但不会应用于系统。一个有趣的事实是,如果您从选项寄存器执行读取,它们仍然会返回最后加载的选项字节值,新选项仅在加载后才会对系统产生影响。这里有两种加载Option Bytes的方法
- 当 FLASH 控制寄存器 (FLASH_CR) 的 OBL_LAUNCH 位被置位时
- 电源复位后(BOR 复位或退出待机/关机模式);这里需要注意的是,一定是电源重置;软件重置或简单地切换 NRST 线不会加载Option Bytes
预防措施
在我们转向理论的固件实现之前,我们必须考虑一项预防措施。当Option Bytes编程失败时(由于任何原因,例如在Option Bytes更改序列期间断电或复位),复位后会加载不匹配的Option Bytes值。这些不匹配的值会强制执行安全配置,可能会永久锁定设备,具体取决于 STM32 系列。为了防止这种情况发生,只能在安全环境中对Option Bytes进行编程——安全供电、无挂起的看门狗和干净的复位线。为了尽量确保这种情况,在我们的固件实现中,我们将在继续之前检查 MCU 上的电压电平。
固件实现:
对于此代码,如前所述,使用的板是 NUCLEO-G071RB,并且使用默认硬件分配进行设置,这意味着使用 LED、按钮键和 USART (115200/8/N/1) 引脚。在这些外设之上,还添加了 ADC 和 IWDG - 应用程序代码如下所述,所有代码都应驻留在 main.c 文件中
int main(void)
{
/* USER CODE BEGIN 1 */
FLASH_OBProgramInitTypeDef OptionsBytesStruct;
uint16_t adc_value;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
HAL_FLASHEx_EnableDebugger();
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART2_UART_Init();
MX_ADC1_Init();
/* USER CODE BEGIN 2 */
然后,代码将配置 ADC 来读取电压参考,并应用简单的检查电压是否高于特定水平,如果是,则它将继续前进。这部分代码只是一个示例实现:
/* USER CODE BEGIN 2 */
// ADC enable - to measure Vref.
// Vref is internally connected to Vin[13]. Vref is used to calculate the Vpower level
// and use that information to disable Option Bytes update.
// ADC runs on IN
RCC->CCIPR |= 0x80000000; // Select ADC clock = HSI16.
RCC->APBENR2 |= 0x00100000; // Enable ADC clock
ADC->CCR |= 0x00400000; // Enable Vref - set BEFORE enabling ADC
ADC1->CR |= 0x000000001; // Enable ADC
while ((ADC1->ISR & 0x00000001) != 0x00000001)
{ // While ADC is NOT ready = loop.....
;
}
ADC1->SMPR = 0x00000077; // Sample time (160.5 cycles)
ADC1->CHSELR |= 0x00002000; // Select Channel 13 (Vref)
ADC1->CR |= 0x00000004; // Start conversion
// Here we are about to make a decision whether to update the Option Byte
// Based on the Power Supply Voltage
while ((ADC1->ISR & 0x00000004) != 0x00000004)
{ // While ADC conversion is NOT completed loop.....
;
}
adc_value = ADC1->DR; // Read ADC
if (adc_value > 1712) // 1712 for Vss ~2.9V
{ // Vss too low
// Progress indicator: ADC Error: Vss too low.
// Skip the update!
}
一旦通过了基本功能,固件就会检查并打印之前设置的任何标志,并确保清除所有复位和时钟控制标志。
if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST))
{
printf("IWDG flag\r\n");
}
if (__HAL_RCC_GET_FLAG(RCC_FLAG_OBLRST))
{
printf("OBL flag\r\n");
}
__HAL_RCC_CLEAR_RESET_FLAGS();
printf("clear all RCC flags\r\n");
下一步是验证 RDP 级别,如果设置为级别 1,代码将等待按下并释放蓝色按钮,然后再进行 RDP 回归。
HAL_FLASHEx_OBGetConfig(&OptionsBytesStruct);
if(OptionsBytesStruct.RDPLevel == OB_RDP_LEVEL_1)
{
printf("RDP LVL1 \r\n");
printf("wait BT1 to be pressed\r\n");
while(HAL_GPIO_ReadPin(BT1_GPIO_Port, BT1_Pin) == GPIO_PIN_SET)
{
;
}
printf("wait BT1 to be released\r\n");
while(HAL_GPIO_ReadPin(BT1_GPIO_Port, BT1_Pin) == GPIO_PIN_RESET)
{
;
}
while(HAL_FLASH_Unlock() != HAL_OK)
{
printf("Waiting Flash Unlock\r\n");
}
while(HAL_FLASH_OB_Unlock() != HAL_OK)
{
printf("Waiting OB Unlock\r\n");
}
printf("RDP regression LV0 init\r\n");
RDP_Regression();
}
如果不是 RDP level 1,代码也会进入主循环并等待按键,执行更改一些选项字节的代码,包括 RDP 为 level 1 和 nBOOT 选择。
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
printf("\033[96mPress BT1 to change Option Bytes\033[0m \r\n");
if(HAL_GPIO_ReadPin(BT1_GPIO_Port, BT1_Pin) == GPIO_PIN_RESET)
{
printf("BT1 pressed\r\n");
while(HAL_FLASH_Unlock() != HAL_OK)
{
printf("Waiting Flash Unlock\r\n");
}
while(HAL_FLASH_OB_Unlock() != HAL_OK)
{
printf("Waiting OB Unlock\r\n");
}
OptionsBytesStruct.OptionType = OPTIONBYTE_USER | OPTIONBYTE_RDP ; //Configure USER and RDP
OptionsBytesStruct.USERType = OB_USER_nBOOT_SEL;
OptionsBytesStruct.USERConfig = OB_BOOT0_FROM_PIN;//Set to boot from pin (UART)
OptionsBytesStruct.RDPLevel = OB_RDP_LEVEL_1;
while(HAL_FLASHEx_OBProgram(&OptionsBytesStruct) != HAL_OK)
{
printf("Waiting OB Program\r\n");
}
printf("OB Boot0 is now Boot From Pin and RDP 1\r\n");
SET_BIT(FLASH->CR, FLASH_CR_OPTSTRT);
while((FLASH->SR & FLASH_SR_BSY1) != 0)
{
;
}
为了确保选项字节的编程和加载,实现了两种方法——OBL_LAUNCH 以及具有待机条目的 IWDG,它会由于 IWDG 而唤醒并强制电源复位,因此两种方法均出于演示目的而显示。
printf("OBLauch\r\n");
MX_IWDG_Init();
while( HAL_FLASH_OB_Launch()!= HAL_OK)
{
printf("OBLauch Failed..retry with IWDG and StandBy Mode\r\n");
HAL_PWR_EnterSTANDBYMode();
}
}
如果未按下按键,还会出现一个简单的 LED 和打印消息来指导该过程,并在主循环中执行
void ToggleLED(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(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_SET);
}
HAL_Delay(2000);
}
其他详细信息是 RDP 的工作原理,因此请参阅此图片以获得快速说明:
正如我们所看到的,在 RDP 从级别 1 回归到级别 0 时,会执行批量擦除,因此我们在实现 RDP_Regression 函数时需要考虑到这一点,因为如果我们碰巧将其留在 FLASH 中,该函数将 在完成之前就不再存在,并且无法按预期工作,这就是我们将此函数放入 RAM 的原因
void __attribute__((__section__(".RamFunc"))) RDP_Regression(void)
{
__disable_irq();
printf("Mass Erase Start\r\n");
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
FLASH->OPTKEYR = FLASH_OPTKEY1;
FLASH->OPTKEYR = FLASH_OPTKEY2;
/* Force readout protection level 0 */
FLASH->OPTR = OPTION_BYTE_TARGET_VALUE;
FLASH->CR |= FLASH_CR_OPTSTRT;
while((FLASH->SR & FLASH_SR_BSY1) != 0)
{
;
}
/* Force OB Load */
FLASH->CR |= FLASH_CR_OBL_LAUNCH;
}
结论:
Options Bytes是最终产品的关键因素,因为它提供了一组很好的自定义功能,以确保您的微控制器按照您想要的方式运行,包括Options Bytes最常用的功能之一,即读保护,它允许 您可以锁定 STM32,防止不必要的写入和读取。 采取一些预防措施后,可以在任何给定情况下对Options Bytes进行编程,但重要的是在更新它们之前确保适当的条件。
Source:https://community.st.com/t5/stm32-mcus/what-are-option-bytes-in-stm32-and-how-do-i-use-them/ta-p/49451