2

Assume there are two boards with stm32 micro-controllers which are connected to each other with rs-485 (each board has a uart-to-rs485 transceiver). This is the connection diagram and the accessible ports:

connection diagram

I want to be able to re-program each board separately using rs-485 wires that are available. Using st system bootloader and boot0 pin is not an option because it requires changing the PCB and re-wiring the system.

So I need to write my own custom bootloader. What I intend to do is to separate the flash memory of the B-1 MCU into three parts:

  1. 20KB for bootloader
  2. 120KB for B-2 application (as kind of a buffer)
  3. 360KB for B-1 application (bootloader jumps to this part after finishing boot mode)

and for B-2, two partitions:

  1. 20KB for bootloader
  2. 100KB for main application

and using the UART interface of B-1, I can load the .hex files to the specified flash area and tell the MCU what to do with it (either use it as it's own main app or send it to B-2). Here is a brief algorithm for the bootloader:

// B1: starts from bootloader 
for (5 seconds) {
    // check for the boot command from UART 
    if (command received) {
        // send boot and reset command to B-2 and receive ack
        // start the boot mode
        while (boot mode not aborted) {
            // receive command packet containing address
            if (command == header && address == B1) {
                // prompt for the main .hex file and store it in B-1 partition
            } else if (command == header && address == B2) {
                // prompt for the main .hex file and store it in B-2 partition
                // send header and the app to B-2 using rs-485 port
            } else if (command == abort) {
                break from while loop
            }
        }
    } else {
        // do nothing
    }
}
// jump to main application

Now I have 2 concerns:

  1. Since there is no gpio connection between B-1 and B-2 to activate boot mode for B-2, is it possible for B-2 board to set a flag in flash memory outside of it's main application area to check for it and use it as a boot mode activation flag?
  2. Is it possible to write the stream of data directly from uart input to the flash memory area of each application? like this:
// Obviously this address is outside the flash area of the current bootloader running
uint8_t appAddress = B2_APP_FLASH_MEMORY_PARTITION_START_ADDRESS;
for (i from 0 to size_of_app) {
    hal_uart_receive(&uartPort, appAddress + i, 1, timeout);
}

0andriy
  • 4,183
  • 1
  • 24
  • 37
mehdi
  • 167
  • 11
  • Is the ".hex file" you are planning to use a standard Intel hex format file? – tinman Dec 18 '22 at 12:47
  • @tinman Yes it is. `ASCII text, with CRLF line terminators` It's compiled from source code using `gcc-arm-none-eabi` – mehdi Dec 18 '22 at 13:23
  • In "concern" #2 you say "like this", followed by an example that does not seem to be an exemplar of the preceding statement - i.e. there is no flash write. For #1 I am not sure what you are asking. Bit it seems like an X-Y problem. You have a requirement, you have invented a solution and you are asking about that solution. It might be better to leave it somewhat more open. – Clifford Dec 18 '22 at 16:01
  • Don't forget about situation when sending the firmware goes wrong. Typical approach for this would be to have two partitions: active, and a fall-back copy. – Andrejs Cainikovs Dec 18 '22 at 17:09
  • @Clifford In number 1 concern what I meant was that if I declare a pointer, pointing to flash memory location dedicated to the main B-1 application (or B-2 application), can I write incoming data from uart directly to that address? so the flash write is done **during** data receive process. and in number one I want to have a byte in flash memory of B-2, which upon receiving `reset-and-boot` command from B-1 I set that byte to 1, and after soft-reset of B-2, check that byte to see whether the bootloader should run or jump to main application – mehdi Dec 18 '22 at 21:37
  • @AndrejsCainikovs Yes that's right. But Because of some memory limitations, I don't want to "waste" memory to store a fall-back copy. I want to have only 1 partition and if the CRC check is correct after loading the app, run it. if not, erase the same partition and flash it again. – mehdi Dec 18 '22 at 21:40
  • Flash memory is byte/word programmable, but you must erase an entire page. That probably makes your proposal impractical. – Clifford Dec 18 '22 at 23:16
  • What's the reason you need to use bootloaders to begin with? – Lundin Dec 19 '22 at 07:44
  • @Lundin I don't have direct access to the B-2 board after mounting it on the project site. So it's not possible to update the firmware directly with st-link or etc. – mehdi Dec 19 '22 at 08:00

1 Answers1

2

Since there is no gpio connection between B-1 and B-2 to activate boot mode for B-2, is it possible for B-2 board to set a flag in flash memory outside of it's main application area to check for it and use it as a boot mode activation flag?

I am not sure that you are suggesting, or how your suggestion will solve the problem. Ideally you would have a means form B1 of directly resetting B2 via its /RESET line, but failing that if it is loaded with an application that accepts a reset command or signal over the RS-485, then you can then have it issue a soft-reset to start the bootloader. On Cortex-M devices you can do that through the NVIC.

If you need to communicate information to the B2 bootloader - perhaps to either invoke an update or to bypass that and boot the application, you need not program flash memory for that, you can simply write a boot command or signature via a reserved area of SRAM (best right at the top) that is not initialised by the runtime start-up (or the content of which you capture before such initialisation). Content in SRAM will survive a reset so long as power is maintained, so it can be used to communicate between the application and the bootloader - both ways.

This is of course a bootstrap issue - what if there is no application loaded to accept a reset command, or the application is not valid/complete (programming interruption). Well the relocated application area will have its vector table including its initial-SP and reset vector right at the start. In your bootloader when the first 8 bytes of the image are received, you hold them back and do not program that area until the rest of the image is written. By programming the reset vector last, if the programming is interrupted, that location will not be a valid address. The bootloader can validate it (or check if it is in the erase state) and if not valid/written, it can wait indefinitely for an update or simply reset to repeat the update polling. Be aware of a bit of an STM32 gotcha here though - most parts erase flash to "all-ones" (0xFF) state, some however (STM32Lxx parts) erase to "all-zeroes". Of course you couls simply check for 0x00000000 or 0xffffffff since neither would be a valid start address, or explicitly check the range.

Is it possible to write the stream of data directly from uart input to the flash memory area of each application? like this:

Yes, but remember that on STM32, normally the code is executing from the same flash memory you are trying to program and that the bus stalls during flash write and erase, such that if you are executing from flash, execution halts. For page erase, that can be several milliseconds - for parts with large pages, several hundred milliseconds even. That may mean that you fail to read characters on the UART if you are using polling or interrupt.

You can overcome this issue by protocol design. For example if you use a packet protocol where the last packet must be acknowledged before the next one is sent, you can use that as a flow control. Once you have collated a block of data to be written, you simply delay the acknowledgement of the last packet until after you have erased and/or written the data to flash. XMODEM-1K is a suitable protocol for that and despite its flaws its simplicity and support in common terminal emulator applications make it a good choice for this application.

Now all that said, you can increase the flash available to B1 by not buffering the image for B2 on B1 at all and simply implement a bi-directional pass-through such that the input on the UART of B1 is passed directly to the B1 RS-485 output, (surely also a UART so your port naming is ambiguous), and B1 RS-485 input passed directly to the UART output. That way B1 becomes "transparent" and the update tool will appear to be communicating directly with B2. That is perhaps far simpler and faster, and if the bootloader is "fail-safe" as described above, will still allow retries following interruption.

The pass-through function might be part of B1's application or a "mode" of the bootloader. The pass-through mode might be invoked by a particular boot command or you might have the application pass a "boot mode" command via the SRAM mechanism described earlier.

In either case ideally you would have identical bootloader code on both B1 and B2 for simplicity and flexibility. There is no reason why that should not be the case; they are both receiving the updates over UART.

Clifford
  • 88,407
  • 13
  • 85
  • 165
  • Nice answer. A small nitpick though. While you can write to flash from the incoming UART data it reads like @mehdi wanted to update the firmware directly with the incoming data but if they are transmitting an ASCII text file with CR/LF line endings then that will not work for their scenario. It would require storing into RAM and converting to bytes. – tinman Dec 18 '22 at 17:28
  • @tinman Yes, I have was unsure what he was asking, and asked for clarification in a comment on the question. However the answer to your interpretation is implicit in the need to have some sort flow control protocol because of the bus stalling and erase time issue. – Clifford Dec 18 '22 at 17:57
  • Thank you for your detailed answer. paragraph1: yes I have a `reset` packet in my protocol and can reset B2 from B1. p2: I had this mentality that the flash is more "safe" than sram during reboots. p3: Is it not enough to implement CRC check and also send the app size initially in a header packet and in case of CRC mismatch or not receiving full size data, reset the bootloader? p4,5: didn't know about the stalling. I will read more about the protocol. does a programmer like stlink also use this type of protocols? p6-8: That's a good idea and it's also easier to implement. – mehdi Dec 18 '22 at 22:54
  • STM32 flash cells have relatively low endurance (10000 cycles), and require page erase. Lower end parts have 2k byte pages, but the larger devices have 16, 32, 64 or 128k pages. Using a whole page for this, even at 2K is rather wasteful and impractical, and not power fail safe. – Clifford Dec 18 '22 at 23:04
  • Re CRC, possible, but not simpler. After booting a partially programmed flash, where is it going to get the size information or the CRC from. Musc simpler to just program the reset vector last. – Clifford Dec 18 '22 at 23:07
  • STLink is a JTAG/SWD programmer, no bootloader is involved, it directly accesses memory via the CoreSight on-chip debugger. The PC app decides the hex file, and just the binary data is sent. – Clifford Dec 18 '22 at 23:11
  • This answer is by no means definitive, just some ideas. But I wrote my first STM32 bootloader 12 years ago and have implemented many since. – Clifford Dec 18 '22 at 23:14
  • @tinman does it make a difference if I send the .bin file instead of .hex? Regarding direct write to flash and bypassing the RAM – mehdi Dec 20 '22 at 05:24
  • @mehdi. All things are possible, not all things are advisable. The raw binary lacks location information and record checksums. – Clifford Dec 20 '22 at 09:07