2023-05-16 06:33 PM - edited 2023-11-20 05:18 AM
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.
2023-05-17 12:07 AM - edited 2023-11-20 05:18 AM
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
2023-05-17 12:15 AM - edited 2023-11-20 05:18 AM
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.
2023-05-17 04:25 AM
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
2023-05-17 03:28 PM
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.
2023-05-18 12:41 PM
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
2023-05-18 04:40 PM
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.