2
\$\begingroup\$

I am working with an STM32 L476RG evaluation board on a project. The goal currently is to utilize STM's mass storage library to write data to flash over USB. I've got the USB library working properly (simply reading and writing data from/to RAM as a start), but I've encountered a strange problem when using flash.

If I write to the flash in the STORAGE_Init_FS function (Using HAL_FLASH_Program(....)), I can write to the flash just fine. I clear the page, write arbitrary data to it, and it appears in the memory view of the debugger.

However, if I try to do the exact same thing in STORAGE_Write_FS (tried with line-for-line, the same code), then the flash operation returns an error and doesn't ever write any data.

Here are the relevant code portions:

{
  /* USER CODE BEGIN 2 */
    USB_Flash_Init();
  return (USBD_OK);
  /* USER CODE END 2 */
}

HAL_StatusTypeDef USB_Flash_Init() {
    __HAL_RCC_SYSCFG_CLK_ENABLE();
    __HAL_RCC_FLASH_CLK_ENABLE();
    /* Clear flash flags */
    HAL_FLASH_Unlock();
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
    HAL_FLASH_Lock();

    HAL_FLASH_Unlock();
    HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, 0x8080000, 0x0001);
    HAL_FLASH_Lock();
    return HAL_OK;
}

In the above code, the data 0x0001 is properly written to memory address 0x8080000. (The erase call is not there, but the page has been erased).

However, in the below function,

int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
  /* USER CODE BEGIN 7 */
    __HAL_RCC_SYSCFG_CLK_ENABLE();
    __HAL_RCC_FLASH_CLK_ENABLE();
    HAL_FLASH_Unlock();
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
    HAL_FLASH_Lock();
    HAL_FLASH_Unlock();
    FLASH_PageErase(0, FLASH_BANK_2);
    HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, 0x8080000, 0x0001);
    FLASH_PageErase(0, FLASH_BANK_2);
    HAL_FLASH_Lock();
  return (USBD_OK);
  /* USER CODE END 7 */
}

The data gets properly wiped (viewing the memory address through the debugger), but then never written, it remains 0xFFFFFFFF. I know some of the init stuff had already been done, but I wanted to be certain it wasn't the cause so I threw it in there as well. The only thing I can really think of would be the stack potentially not having enough space (unlikely with the massive amount of memory this thing has). In order to verify this wasn't the cause, I tried both taking my 2k buffer (not shown here) out of the function to no longer be a local variable, and increasing the stack and heap sizes to 0x4096 (excessive) in the RAM.ld.

Neither changed the behavior. Anyone I've spoken to has been at a loss as to the cause of this. What could I be doing wrong?

I do get two errors when I check HAL_FLASH_CheckError() (might not be the exact function name), 0xA0, which corresponds to HAL_FLASH_ERROR_PGS and HAL_FLASH_ERROR_PGA, programming sequence and programming alignment errors respectively. I'm not certain these errors are relevant, considering the actual function calls and setup are identical, only the scope is different.

If anyone has any advice, I'd be glad to hear it! Hopefully I'm just missing some control register or bit somewhere.

\$\endgroup\$
  • \$\begingroup\$ What is the path by which execution reaches each point? Try putting the function in its own file, and calling it both early in main() and also at whatever point you are dealing with the actual data. Hopefully it goes without saying that you can't do this in an interrupt context. Make sure your flash timing is appropriate to whatever system clock you need for USB... \$\endgroup\$ – Chris Stratton Dec 2 '19 at 23:18
  • \$\begingroup\$ You erase the page (FLASH_PageErase) after programming (HAL_FLASH_Program)... If it's "code extraction" mistake, take look of your flash latency and prefetch and co. \$\endgroup\$ – rom1nux Dec 3 '19 at 0:18
  • \$\begingroup\$ @ChrisStratton The path it takes to the call is through a few layers of STM libraries to reach each function. I believe it has something to do with where it's being called from, considering it works in one function but not another. Flash is writing fine in main() and wherever else I try, it's just in this USB write function that it fails. Edit: Sorry hitting enter submitted the comment. Why can this not be done in an interrupt context? Both functions I've demonstrated are called in a USB interrupt managed by STM's libraries. I have no control over when this gets called. \$\endgroup\$ – kbrown323 Dec 4 '19 at 17:14
  • \$\begingroup\$ Just to be certain, it's not a clock error since I got the entire USB communication working perfectly when writing to RAM. @rom1nux I do indeed erase it afterwards in that example, but the actual code dealing with the USB data blocks and all did not, and I'm stepping through it with the debugger and watching the memory address change (or not, in the problematic one). Taking out this extra erase doesn't cause a difference in behavior. \$\endgroup\$ – kbrown323 Dec 4 '19 at 17:18
  • \$\begingroup\$ Just to be clear, I am aware that it's generally bad practice to do "too much" in an interrupt and you want to keep interrupt logic simple to prevent disruption of main program flow. Flash programming certainly qualifies. But at this point, there is literally nothing else executing (empty while loop), and when in actual use for USB Mass Storage the device will be in a download firmware mode where normal execution is stopped. \$\endgroup\$ – kbrown323 Dec 4 '19 at 17:25
3
\$\begingroup\$

It appears that the actual issue is that the FLASH_PECR_ERASE (or at least that is what is called on the L0) is left set by the typical flash erase routines. This causes subsequent program attempts to be some odd and perhaps invalid mix of a program and erase operation. As mentioned deep in comments below I've previously seen this on an STM32L0, and it seems to be an issue with the STM32L4 here as well.

Original and apparently irrelevant ideas follow

Flash is writing fine in main() and wherever else I try, it's just in this USB write function that it fails.

Why can this not be done in an interrupt context? Both functions I've demonstrated are called in a USB interrupt managed by STM's libraries.

Taking at face value the (perhaps incorrect) belief that this is being called from an interrupt context, you most definitely cannot write to flash from an interrupt by ordinary means. Writing to flash is a very complex process, a real departure from ordinary chip operation that chips are supposed to hardly ever perform, and very time consuming too.

You'll need to cache the intention to perform a write (save the desire and the data), perform it later from the main loop, and then queue a success or failure response to send back through USB (ie, accept the data immediately, but don't reply if the write worked or not until you have completed it). Remember to make communication between the interrupt and main loop via safely atomic flags, or use an appropriately brief critical section to make a copy of anything non-atomic. You will probably need to lock out further modification of the data buffer from the interrupt until you have finished the write, if you cannot do that at the level of the USB buffer itself you may need to DMA from a USB buffer elsewhere in RAM and then write from there.

USB Mass Storage examples are very common. You can probably learn useful things about how to do the needed decoupling from an example backed by SPI flash rather than internal or from one for a different STM32 if the USB functional block has meaningful similarities in it's architecture with regard to buffering, etc.

If STORAGE_Write_FS() is really being called from an interrupt that is an unfortunate piece of example code. It may be that there are parts of the architecture of the example which are not yet sufficiently understood - if they give any example backed by actual non-volatile storage (vs faking it with a tiny bit of RAM) a detailed examination of that would be key.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ > You most definitely cannot write to flash from an interrupt by ordinary means. Writing to flash is a very complex process, a real departure from ordinary chip operation that chips are supposed to hardly ever perform, and very time consuming too. So then why is it that it works in one interrupt call and not the other? I'm having trouble seeing why it can't be done from an interrupt, an interrupt is simply another function with a nonstandard access point no? I'd understand if there was some time limit placed on the interrupt, or if there was other critical code executing. \$\endgroup\$ – kbrown323 Dec 4 '19 at 19:08
  • \$\begingroup\$ Particularly this is puzzling when it works in one interrupt and not another. I could buffer the data and perform the write in main(), but I'd need to buffer enough memory for the maximum write request the host could perform, which would result in a buffer of at least kilobytes (I don't know the exact maximum here) being held constantly in memory and not being used except in this special case. I'll check around for examples with similar boards (there appeared to be some for the F4 series, but they use a slightly different library from the L4, I've been working off a USB training STM video \$\endgroup\$ – kbrown323 Dec 4 '19 at 19:14
  • \$\begingroup\$ Update: I've found a few examples of people doing the same thing, utilizing the internal flash as a USB MSC. They are doing what I am, simply calling the flash programming function in the Storage_Write_FS function. Also I went ahead and changed Storage_Write_FS to set a flag, which tells main() to perform the desired write (not actually dealing with USB data yet, still writing 1 to a hardcoded address), and surprisingly it's yielding the same problem. Same error code when stepping through the flash programming, and the flash is never updated! Very curious. \$\endgroup\$ – kbrown323 Dec 4 '19 at 19:42
  • \$\begingroup\$ I'm quite stumped as to how it would program the flash fine in USB_Flash_Init() (simply called by STORAGE_Init_FS, triggered by interrupt from OS command), but is failing not just in STORAGE_Write_FS() but also in main(). Any advice or ideas would be greatly appreciated! \$\endgroup\$ – kbrown323 Dec 4 '19 at 19:45
  • \$\begingroup\$ It's not clear that your belief that any of this is being called in an interrupt context is actually correct - it would be quite unfortunate if it is. It may be however that the flash writing needs to be protected against other interrupts firing. Before you should have a look at sources for USB-based bootloaders. In some past hardware architectures you could not write to a flash bank while running from it (you used to have to load a stub in RAM to do the actual write or place it in another flash bank), I got the sense that issue is overcome in current ones but perhaps imperfectly. \$\endgroup\$ – Chris Stratton Dec 4 '19 at 20:02
2
\$\begingroup\$

Update: The problem was the usage of the FLASH_PageErase() function, which does not reset the proper control bits for the flash to allow writing again. It just coincided with the order of the function calls that I wasn't erasing the page until the second call.

I'm sure scouring the documentation will yield the relevant bits that need to be reset, but in my case I simply switched to the HAL_FLASHEx_Erase function. Leaving this here in case anyone runs into a similar issue.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.