cancel
Showing results for 
Search instead for 
Did you mean: 

STM32 F3 Option Byte Retention

GeoffF
Associate II

We have a product with a STM32F373CC where we calibrate the RTC in Production and store the calibration factor in Data0 and Data1 of the Option Bytes (OBR).

Tracing through the (legacy) code via my debugger, I can see the OBR values being written correctly. However, when I restart the micro, those values seem to be reset. The reference manual says it's meant to be non-volatile, but this doesn't seem to be the case.

What might we be doing wrong?

Here's our function that writes the code. At the end of the function, the OBR Data0 and Data1 fields show the correct values, and the nData0 and nData1 fields show the inverted values. We deliberately don't re-load the FLASH_OBR register because we don't want to cause a reset.

FLASHteResult FLASH_eWriteOptionBytes( tuFLASH_OBR uFLASH_OBR, uint32 lwFLASH_WRPR )
{
   tuOBR uOBR;
   FLASHteResult eResult;
   uint32 lwIRQ;
   uint16 *pwDst;
   uint16 *pwSrc;
   //erase OBR
   memset( (void *)&uOBR, 0xFF, sizeof( uOBR ) );
   //set the read protection option
   if( uFLASH_OBR.LEVEL_2_PROT)
   {
      //uOBR.OB_RDP = OB_RDP_Level2; //DANGER - THIS CAN'T BE UNDONE
      return( FLASHkeR_Failed );
   }
   else if( uFLASH_OBR.LEVEL_1_PROT )
   {
      uOBR.OB_RDP = OB_RDP_Level1;
   }
   else
   {
      uOBR.OB_RDP = OB_RDP_Level0;
   }
   //copy the USER flags
   uOBR.abReg[2] = uFLASH_OBR.abReg[1];
   //copy the data
   uOBR.Data0 = uFLASH_OBR.Data0;
   uOBR.Data1 = uFLASH_OBR.Data1;
   //write protection
   uOBR.WRP0 = lwFLASH_WRPR;
   //disable interrupts (flash can't be used during erase anyway)
   lwIRQ = ARM_lwPushInterrupts();
   //unlock the flash
   eResult = nFLASH_eUnlock();
   if( eResult != FLASHkeR_Ok )
      goto END;
   //now unlock the option register
   FLASH_OPTKEYR = 0x45670123;
   FLASH_OPTKEYR = 0xCDEF89AB;
   if( !FLASH_CR.OPTWRE )
   {
      eResult = FLASHkeR_Failed;
      goto END; //jump if failed to unlock
   }
   //erase the option area
   {
      static const tuFLASH_CR kuFLASH_CR =
      {{
         .OPTER = TRUE,
         .OPTWRE = TRUE
      }};
      FLASH_CR.lwReg = kuFLASH_CR.lwReg;
   }
   //request erase
   FLASH_CR.STRT = 1;
   //waf completion
   while( FLASH_SR.BSY );
   //finished erasing
   FLASH_CR.lwReg = 0;
   //unlock the option register again
   FLASH_OPTKEYR = 0x45670123;
   FLASH_OPTKEYR = 0xCDEF89AB;
   if( !FLASH_CR.OPTWRE )
   {
      eResult = FLASHkeR_Failed;
      goto END; //jump if failed to unlock
   }
   //write the flash
   {
      static const tuFLASH_CR kuFLASH_CR =
      {{
         .OPTPG = TRUE,
         .OPTWRE = TRUE
      }};
      FLASH_CR.lwReg = kuFLASH_CR.lwReg;
   }
   //unlock the option register again
   FLASH_OPTKEYR = 0x45670123;
   FLASH_OPTKEYR = 0xCDEF89AB;
   if( !FLASH_CR.OPTWRE )
   {
      eResult = FLASHkeR_Failed;
      goto END; //jump if failed to unlock
   }
   //write the option bytes
   for( pwDst = (uint16*)&OBR, pwSrc = (void*)&uOBR; pwDst < (uint16*)(&OBR + 1); pwDst++, pwSrc++)
   {
      //16 bits at a time
      *pwDst = *pwSrc;
      //waf completion
      while( FLASH_SR.BSY );
   }
   //finished writing
   FLASH_CR.lwReg = 0;
   //relock
   nFLASH_vLock();
 
   #if 0 //avoid doing a reload as this causes a reset (see section 3.5.5 of the F3 Reference Manual)
   //force reload
   {
      static const tuFLASH_CR kuFLASH_CR =
      {{
         .FORCE_OPTLOAD = TRUE
      }};
      FLASH_CR.lwReg = kuFLASH_CR.lwReg;
   }
   #endif
 
   END:
   //no more flash activity
   FLASH_CR.lwReg = 0;
   //restore interrupts
   ARM_vPopInterrupts( lwIRQ );
   return( FLASHkeR_Ok );
}

As things stand, I'm going to re-write to NOT use the options bytes, as we need to get product out the door.

Here's the relevant section of the schematic. The battery is currently sitting at 3.13V.


_legacyfs_online_stmicro_images_0693W00000bkERaQAM.png

6 REPLIES 6
Kamil Duljas
Senior III

In line 86 what's the OBR? you mean FLASH->OBR? How you call it?

You have statement: pwDst < (uint16*)(&OBR + 1), but FLASH->OBR is uint32_t so you adding "1" you increment by 32 bits, is it expected on you?

Notice that you should not copy whole OBR because some bits in FLASH->OBR are reserved


_legacyfs_online_stmicro_images_0693W00000bkFqvQAE.png

Dudo
GeoffF
Associate II

OBR is actually the Option Byte register at address 0x1FFFF800, as described in Section 4 of the reference manual. It's written 16 bits at a time, but the CPU automatically fills in the inverted side. This is, apparently, how you program the FLASH_OBR, because a reset fills in FLASH_OBR from the Option Byte Register.


_legacyfs_online_stmicro_images_0693W00000bkFt6QAE.png

What Kamil meant was, how exactly do you declare OBR? You perform pointer arithmetics in both (&OBR + 1) and pwDst++. The latter is certainly wrong, as you want to skip nDatax; the former may or may not be wrong depending on what is OBR's type and what did you exactly intended to do in the loop.

> At the end of the function, the OBR Data0 and Data1 fields show the correct values, and the nData0 and nData1 fields show the inverted values.

How exactly do you check that?

Can you show in debugger content of 0x1FFF'F800... area?

JW

Thanks Jan and Kamil,

Here's our daefinition of the OBR register:

typedef union
{
   struct
   {
      unsigned long /* teOB_RDP */ OB_RDP : 8;
      unsigned long /* teOB_RDP */ ***_RDP : 8; //this is filled in automatically by the CPU
      tsOB_USER OB_USER;
      tsOB_USER ***_USER; //this is filled in automatically by the CPU
      unsigned long Data0 : 8;
      unsigned long nData0 : 8; //this is filled in automatically by the CPU
      unsigned long Data1 : 8;
      unsigned long nData1 : 8; //this is filled in automatically by the CPU
      unsigned long WRP0 : 8;
      unsigned long nWRP0 : 8; //this is filled in automatically by the CPU
      unsigned long WRP1 : 8;
      unsigned long nWRP1 : 8; //this is filled in automatically by the CPU
      unsigned long WRP2 : 8;
      unsigned long nWRP2 : 8; //this is filled in automatically by the CPU
      unsigned long WRP3 : 8;
      unsigned long nWRP3 : 8; //this is filled in automatically by the CPU
   };
   unsigned short awReg[8];
   unsigned char abReg[16];
} tuOBR;
extern const volatile tuOBR OBR;

This is then placed in a segment defined as follows:

<MemorySegment name="Option_bytes">

 <ProgramSection start="0x1FFFF800" size="16" load="No" name=".OBR"/>

</MemorySegment>

And yes, I was watching it being filled in via the debugger's memory window.

As an experiment, I tried changing the type of pwDst, and got a memory error.

Note that this is code written some years ago by my long-departed predecessor at this company. It's been used in some of our products in the past, apparently.

To get product out the door, I was able to change the strategy to use an external EEPROM device to store the calibration values.

However, I'd like to see if I could make this code actually do what it's meant to do.

Okay, but still you write into both the "inverted" and "noninverted" portions, I don't think that's how it's supposed to work.

But, nonetheless, could you please show us example readouts of given area, before and after calling the function (plus content of the array to be written), and then after reset - or whenewer it changes, according to your initial post?

JW

GeoffF
Associate II

Thanks Jan,

I've moved on from there - to the point of removing that chunk of code - so I'm not sure I can give you a good image without some work. To make it harder, my IDE doesn't like staying connected during a reset, to the point where I have to shut-down and restart the IDE when the target is reset. This makes it harder to follow the steps.

If I load - for example "e4" into Data0 and "03" into Data1 without changing nData0 or nData1, I've watched the memory area get loaded with "e4" in Data0 and "1B" into nData0, then "03" into Data1 and "FC" into nData1. It seems the "n" fields are read-only.

I have (once) seen the OBR start up showing the correct values, but FLASH_OBR is incorrect.

I have another project active now using that chunk of code and an F3, so I'll see what's going on there.