analogdevicesinc / msdk

Software Development Kit for Analog Device's MAX-series microcontrollers
Apache License 2.0
61 stars 82 forks source link

Bootload the program through serial UART/I2C from external MCU #709

Closed fcenciarelli closed 1 year ago

fcenciarelli commented 1 year ago

Hi, I am trying to use an external MCU to load code to MAX78000 through UART.

Following up from: https://github.com/Analog-Devices-MSDK/msdk/issues/485 #485

I can reliably activate the bootloader through UART. I can send the command "i\r\n" to get back "USN:..". I can also send the load command "l\r\n" and get back the "Ready to load SREC", however I am not sure in what format and timing should I send the data. I get a "Load success..." straight away. On the PC to MAX78000 Python implementation you shared it seems like the pages must be erased before loading the code?

It would be great to have more documentation on how to reliably load the file, which format to use when loading, and the timing of the communication between MAX78000 and an host MCU, or if you have any implementation in C of the python code you shared in issue #485 .

Thank you in advance!

Jake-Carter commented 1 year ago

Hi @fcenciarelli

The format that the bootloader expects is the .srec format. We actually have an implicit rule in our Makefiles for building with this format, but it's not very well documented. So you have two options:

  1. make release - this will build a .srec file as well as a .bin, .hex, and .dasm disassembly file. Note that this also strips debug symbols and increases the default optimization level.

    image

  2. You can drive the implicit rule manually by targeting the .srec file. make build/max78000.srec. Note you can use the PROJECT option to change the filename, otherwise it defaults to the name of the micro.

    image

Either option should give you a .srec file that you can use with the built-in non-secure bootloader.

I think the only major timing requirements are:

As soon as you stop sending bytes, the "load success" message is sent if everything went as expected.

image (https://www.analog.com/media/en/technical-documentation/user-guides/max78000-user-guide.pdf)

Our documentation does not mention erasing pages first as a requirement. I think the Python script may just do this as a precaution, but I'm not its author. Based on the documentation you should not need to.

Jake-Carter commented 1 year ago

We don't have any example code specific to driving this bootloader in C, but the UART Example example shows how to transmit bytes.

The most difficult thing I can anticipate with this is the requirement for a continuous byte stream for the file data. If your host MCU doesn't have enough memory to store the file, then you'll need to stream it from a host PC. So your micro turns into a "passthrough" device for the UART. You'll want to ensure that no gaps make it into the file data, otherwise the bootloader will exit early. DMA will probably be necessary.

fcenciarelli commented 1 year ago

Hi @Jake-Carter, Thank you for your detailed answer. I followed the detailed steps. So I wrote three bytes "L\r\n" , read 30 bytes until received "Ready to load SREC\n" and wrote the first line of the SREC file created using the "make release" (S3851000000000000220B9040010ED0200...) sent as a string. However as you can see from the logs below, even if I write continuously the bytes after receiving the command, the bootloader writes "End of file\nLoad sucess...." straight away. I tried to time it in multiple ways but I cannot get it to work, the problem is really to make the bootloader understand what is the start of the file. Otherwise it basically sends back the SREC string I previously sent.

I (10345) TX_TASK: Wrote 3 bytes I (10355) RX_TASK: Read 30 bytes: ' ULDR> L Ready to load SREC ' I (10355) RX_TASK: Wrote 587 bytes I (19405) RX_TASK: Read 763 bytes: 'End of file Load success, image loaded with the following parameters: Base address: 0x00000000 Length: 0x00000000 ULDR> S3851000500100D0500100F05001011050010130500101505001017050010190500101B0500101D0500101F050010210500L Invalid command: [S3851000500100D0500100F05001011050010130500101505001017050010190500101B0500101D0500101F050010210500L]

Is there anything I can try to fix this? Or is it possible to have additional documentation for the non-secure ROM bootloader inside the MAX78000?

Thank you for your help.

Jake-Carter commented 1 year ago

Hmm, ok thanks. I'll take a deeper look at this and collect some logic analyzer captures of the UART.

I spoke with the current maintainers of the documentation last week to confirm the existing instructions. If I see the same behavior I'll get these findings back to them.

Stand by!

Jake-Carter commented 1 year ago

MAX78000 Bootloader Trace.zip

@fcenciarelli I've attached a UART trace for a successful load on the MAX78000EVKIT. You can open it with the Saleae Logic software.

The .srec file I used is included, as well as the Python script. The Python script is slightly modified from the previous version to remove the dependency on bincopy. In general, the script could use some more development. So consider this a functional pre-release for testing.

I also found that the page erases are necessary. Otherwise, the bootloader fails with "Flash write failure at address: 0x10008CF0" with the Hello_World .srec. This isn't mentioned anywhere in the documentation, so I've requested an update to the instructions.

Note there is also about a 124ms delay between the "Ready to load SREC" and the start of the SREC file. So the timing of the response to "Ready to load SREC" does not seem critical.

I also experimented with introducing delays between each page of the binary, and even between the pages themselves. The bootloader did not complain even with 100ms delay between each page, so the timing of the file data transmission also does not seem critical.

Accuracy is the most important thing over timing. I believe the bootloader uses the special srec header/footer alone to determine when it should exit. Note that it also expects ASCII-encoded data.

I hope this helps you continue development. Let me know if you get stuck.

fcenciarelli commented 1 year ago

Hi @Jake-Carter, thank you for the detailed answer and analysis.

I understood why it was not working by analysing the UART trace. What is sent as load message is "L\r", then the "Ready to load SREC" is received back. I was sending the command "L\r\n" and the final "\n" made the board understand that the loading was already finished. This is why I was getting the "End of..." message straight away.

In the documentation it is written as L<0x0D><0x0A> ("L\r\n") / Ready to load SREC<0x0D><0x0A> / [Motorola SREC File], but it should really be L<0x0D> ("L\r") / Ready to load SREC<0x0D><0x0A> / [Motorola SREC File], in this way the "End of file" does not get triggered.
I agree that timing is not really important in this sense, just that "\n" is interpreted as end of file.

Thank you for your support!

khavernathy commented 8 months ago

Is there a way to do these required operations with C code from the MAX78000 library code? I already have a way for the host to command a "reset" and placeholder code to run on the MAX78000 to begin the bootloader process.

1. The host asserts the UART0 receive pin and SWDCLK pins low, as shown in Table 26-1.
2. The host asserts RSTN pin low.
3. The host deasserts the RSTN pin

I think UART0 is MXC_GPIO_PIN_0 and SWDCLK is MXC_GPIO_PIN_29, but I don't know which pin RSTN is, and I don't know how to assert pins low or high.

Alternatively, any advice on doing these asserts from the host side instead of on the MAX78000 would be good.

Jake-Carter commented 8 months ago

Is there a way to do these required operations with C code from the MAX78000 library code? I already have a way for the host to command a "reset" and placeholder code to run on the MAX78000 to begin the bootloader process.

1. The host asserts the UART0 receive pin and SWDCLK pins low, as shown in Table 26-1.
2. The host asserts RSTN pin low.
3. The host deasserts the RSTN pin

I think UART0 is MXC_GPIO_PIN_0 and SWDCLK is MXC_GPIO_PIN_29, but I don't know which pin RSTN is, and I don't know how to assert pins low or high.

Alternatively, any advice on doing these asserts from the host side instead of on the MAX78000 would be good.

@khavernathy typically the "host" in these scenarios is a microcontroller with GPIOs routed to the relevant pins on the "slave" MAX78000 that will be flashed. The host micro will use the GPIO APIs to drive the signals correctly. See the GPIO example and GPIO API Documentation for how to set pins high/low.

Pin descriptions can always be found in our datasheets. See pg 27 & 28.

Are you trying to have the MAX78000 self-initiate its own bootloader sequence?

khavernathy commented 8 months ago

Thanks Jake

Not trying to self-initiate on the MAX78000, no. Thanks for the sending those docs. I've managed to figure this out based on the docs:

UART0 receive pin: J9 on 9x9 GPIO chip. ? on FTHR RSTN pin: B4 on 9x9 GPIO chip. ? on FTHR SWDCLK pin: ? on 9x9 GPIO chip. ? on FTHR

When you say GPIOs routed to pins on the slave (MAX78000), do you mean I'd have to connect jumpers or solder pins on my MCU to the MAX78000 9x9 chip? That chip is extremely small and we've got hardware folks working with it. Is there a way for me to control high/low states of the pins with the Feather board?

In gpio.h (https://github.com/Analog-Devices-MSDK/msdk/blob/f9c0dd1cbe0980c937a0a0185fe596b7236c9829/Libraries/PeriphDrivers/Include/MAX78000/gpio.h#L63) I see GPIO pins 0->31, but not up to 81.

Could I assert GPIO pins high/low through python, like how the bootloader sample code uploads the .srec over UART with python?

I am but a humble software engineer trying to do embedded engineering semi-remotely. Thanks for all your help.

Jake-Carter commented 8 months ago

@khavernathy

I think what you're describing is using a MAX78000FTHR to flash the bootloader of a MAX78000 on a custom board. Correct me if I'm wrong.

If so, here are the connections and activation sequence. Note timing info is an inexact estimate based on my previous experience.

MAX78000_Bootloader_Activation

If the MAX78000FTHR is the host, you can pick any of the GPIOs to use to drive the signals. For convenience, I selected the pins above because they're next to each other.

image

Using these pins, here is some code for driving the signals from the MAX78000FTHR host. (Note IMPLEMENT ME for reading the status prompt)

/******************************************************************************
 *
 * Copyright (C) 2024 Analog Devices, Inc. All Rights Reserved. This software
 * is proprietary to Analog Devices, Inc. and its licensors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ******************************************************************************/

/***** Includes *****/
#include <stdio.h>
#include <stdint.h>
#include "mxc_device.h"
#include "board.h"
#include "mxc_delay.h"
#include "gpio.h"
#include "mxc_pins.h"
#include "uart.h"

// *****************************************************************************
int main(void)
{
    // Initialize UART2.  This will also configure UART2 pins P1_1 & P1_0 as UART pins.
    MXC_UART_Init(MXC_UART2, 115200, MXC_UART_APB_CLK);

    // Set up GPIOs.  Variables are named after the target pin on the _slave_ device.  
    mxc_gpio_cfg_t SWCLK = {
        // P2_3 on host
        .port = MXC_GPIO2,
        .mask = MXC_GPIO_PIN_3,
        .func = MXC_GPIO_FUNC_OUT,
        .vssel = MXC_GPIO_VSSEL_VDDIO // Note VDDIO here (1.8V on FTHR)
    };
    MXC_GPIO_OutSet(SWCLK.port, SWCLK.mask);  // Set pin initial state HIGH before configuration.  Avoids any glitches on transition.
    MXC_GPIO_Config(&SWCLK);

    mxc_gpio_cfg_t RSTN = {
        // P2_4 on host
        .port = MXC_GPIO2,
        .mask = MXC_GPIO_PIN_4,
        .func = MXC_GPIO_FUNC_OUT,
        .vssel = MXC_GPIO_VSSEL_VDDIOH, // Note RSTN is pulled up to VDDIOH on the slave.
                                        // Therefore, it should also use VDDIOH on the host (3.3V by default on FTHR)
    };
    MXC_GPIO_OutSet(RSTN.port, RSTN.mask);
    MXC_GPIO_Config(&RSTN);

    mxc_gpio_cfg_t UART0_RX = {
        // P1_1 (UART_TX) on host.  This will have been initialized by MXC_UART_Init as a UART2 TX Pin.
        // We need to reconfigure this for temporary manual control.
        .port = MXC_GPIO1,
        .mask = MXC_GPIO_PIN_1,
        .func = MXC_GPIO_FUNC_OUT,
        .vssel = MXC_GPIO_VSSEL_VDDIO, // Note VDDIO here (1.8V on FTHR)
    };
    MXC_GPIO_OutSet(UART0_RX.port, UART0_RX.mask);
    MXC_GPIO_Config(&UART0_RX);

    MXC_Delay(MXC_DELAY_MSEC(50)); // Let pin states settle.

    // Pins are now configured and in initial state.  Ready to drive bootloader.

    // Drive SWCLK and UART0_RX LOW.
    MXC_GPIO_OutClr(SWCLK.port, SWCLK.mask);
    MXC_GPIO_OutClr(UART0_RX.port, UART0_RX.mask);

    MXC_Delay(MXC_DELAY_MSEC(10));

    MXC_GPIO_OutClr(RSTN.port, RSTN.mask); // RSTN LOW
    MXC_Delay(MXC_DELAY_MSEC(50));
    MXC_GPIO_OutSet(RSTN.port, RSTN.mask); // RSTN High

    // (IMPLEMENT ME!) Read on UART2 and wait for bootloader status prompt...

    MXC_GPIO_Config(&gpio_cfg_uart2); // Reconfigure UART2 pins so we can use it as normal
}

Hope this helps!

I think this is useful info for this ticket, but feel free to email me direct from here.