diff --git a/libraries/Examples/examples/Advanced/Artemis_Bootloader/Artemis_Bootloader.ino b/libraries/Examples/examples/Advanced/Artemis_Bootloader/Artemis_Bootloader.ino index 9010d2fa..0c65d6bd 100644 --- a/libraries/Examples/examples/Advanced/Artemis_Bootloader/Artemis_Bootloader.ino +++ b/libraries/Examples/examples/Advanced/Artemis_Bootloader/Artemis_Bootloader.ino @@ -1,8 +1,8 @@ /* Artemis Bootloader - By: Nathan Seidle + By: Nathan Seidle, Owen Lyke SparkFun Electronics - Date: May 2nd, 2019 + Date: July 15, 2019 License: MIT. See license file for more information but you can basically do whatever you want with this code. @@ -35,8 +35,47 @@ If our bootloader completes or times out, we jump to 0x10000+4 to begin running user code */ -#define BOOTLOADER_VERSION 0x01 +#define BOOTLOADER_VERSION 0x02 + +// **************************************** +// +// Bootloader options +// +// **************************************** +#define SERIAL_PORT Serial // Which UART port to bootload on +#define USERCODE_OFFSET (0xC000 + 0x4000) // Location in flash to begin storing user's code (Linker script needs to be adjusted to offset user's flash to this address) +#define NUM_BAUDS 5 // Define the list of baud rates to check +uint32_t bauds[NUM_BAUDS] = { // Baud rates are checked in order, so 921600 is listed first (for fastest experience) + 921600, + 460800, + 230400, + 115200, + 57600, +}; + + +// **************************************** +// +// Debug Options +// +// **************************************** + +//#define DEBUG 1 // Uncomment to see debug print statements + +#ifdef DEBUG +#define DEBUG_SERIAL_PORT Serial1 +#define DEBUG_BAUD_RATE 921600 +#define DEBUG_BUFFER_LEN 256 +char debug_buffer[DEBUG_BUFFER_LEN]; +#endif DEBUG + + +// **************************************** +// +// Globals +// +// **************************************** static uint32_t ui32Source[512]; #define MAX_WAIT_IN_MS 50 //Millisecond wait before bootload timeout and begin user code @@ -57,61 +96,18 @@ uint32_t crc32 = 0; uint32_t frame_address = 0; uint16_t last_page_erased = 0; -// Location in flash to begin storing user's code -// Linker script needs to be adjusted to offset user's flash to this address -#define USERCODE_OFFSET 0xC000 + 0x4000 - -//Comment out this line to turn off debug statements on Serial1 -//#define DEBUG 1 - -void setup() { - Serial.begin(9600); //Start comm with computer at low baud rate. We'll go faster later. -#ifdef DEBUG - Serial1.begin(921600); - Serial1.println(); - Serial1.println(); - Serial1.println("Debug"); -#endif +#define MAX_WAIT_IN_US (MAX_WAIT_IN_MS*1000) - delay(3); //Necessary to separate a blip on TX line when port opens from annouce command - Serial.write(BL_COMMAND_ANNOUNCE); //Tell the world we can be bootloaded - //Check to see if the computer responded - uint16_t count = 0; - while (Serial.available() == 0) - { - if (count++ > MAX_WAIT_IN_MS) - { -#ifdef DEBUG - Serial1.println("No response from computer"); -#endif - app_start(); - } - delay(1); - } - if (Serial.read() != BL_COMMAND_AOK) - { +void setup() { #ifdef DEBUG - Serial1.println("Invalid response from computer"); + DEBUG_SERIAL_PORT.begin(DEBUG_BAUD_RATE); + debug_printf("\n\nDebug\n"); #endif - app_start(); //If the computer did not respond correctly with a AOK, we jump to user's program - } - - //Now get upload baud rate from caller - uint32_t uploadRate = get4Bytes(); - - Serial.write(BOOTLOADER_VERSION); - delay(5); //Small delay before baud change. Otherwise baud fails to change. - - Serial.begin(uploadRate); //Go to new baud rate - delay(10); //Give the computer some time to switch baud rates - -#ifdef DEBUG - Serial1.print("Bootloading @ "); - Serial1.println(uploadRate); -#endif + phase_set_baud_rate(); + phase_send_bl_info(); } //This is the main bootloader @@ -121,18 +117,13 @@ void loop() { if (crc32 != incoming_crc32) { RESTART: - Serial.write(BL_COMMAND_BAD_CRC); //Tell client to resend last frame -#ifdef DEBUG - Serial1.println("RESTART!"); -#endif + SERIAL_PORT.write(BL_COMMAND_BAD_CRC); //Tell client to resend last frame + debug_printf("RESTART!\n"); } else { - Serial.write(BL_COMMAND_NEXT_FRAME); //Get next frame -#ifdef DEBUG - Serial1.println(); - Serial1.println("Getting next frame"); -#endif + SERIAL_PORT.write(BL_COMMAND_NEXT_FRAME); //Get next frame + debug_printf("\nGetting next frame\n"); } while (1) //Wait for the computer to tell us its ready to chat @@ -146,30 +137,23 @@ RESTART: if (frame_size == BL_COMMAND_ALL_DONE) //Check to see if we are done { -#ifdef DEBUG - Serial1.println("Done!"); -#endif + debug_printf("Done!\n"); am_hal_reset_control(AM_HAL_RESET_CONTROL_SWPOI, 0); //Cause a system Power On Init to release as much of the stack as possible } -#ifdef DEBUG - Serial1.print("frame_size: "); - Serial1.println(frame_size); -#endif + debug_printf("frame_size: %d\n", frame_size); //Get the memory address at which to store this block of data frame_address = get4Bytes(); if (retransmit_flag == true) goto RESTART; - //Serial1.print("frame_address: "); - //Serial1.println(frame_address); + //debug_printf("frame_address: %d\n", frame_address); //Get CRC from client incoming_crc32 = get4Bytes(); if (retransmit_flag == true) goto RESTART; - //Serial1.print("incoming_crc32: "); - //Serial1.println(incoming_crc32); + //debug_printf("incoming_crc32: %d\n", incoming_crc32); //Receive this frame crc32 = 0; @@ -181,9 +165,7 @@ RESTART: if (incoming_crc32 == crc32) { -#ifdef DEBUG - Serial1.println("CRC good."); -#endif + debug_printf("CRC good.\n"); int32_t i32ReturnCode = 0; @@ -191,9 +173,7 @@ RESTART: //Only erase a page if we haven't erased it before if (last_page_erased < AM_HAL_FLASH_ADDR2PAGE(frame_address + USERCODE_OFFSET)) { -#ifdef DEBUG - Serial1.printf("Erasing instance %d, page %d\n\r", AM_HAL_FLASH_ADDR2INST(frame_address + USERCODE_OFFSET), AM_HAL_FLASH_ADDR2PAGE(frame_address + USERCODE_OFFSET)); -#endif + debug_printf("Erasing instance %d, page %d\n\r", AM_HAL_FLASH_ADDR2INST(frame_address + USERCODE_OFFSET), AM_HAL_FLASH_ADDR2PAGE(frame_address + USERCODE_OFFSET)); //Erase the 8k page for this address i32ReturnCode = am_hal_flash_page_erase(AM_HAL_FLASH_PROGRAM_KEY, @@ -202,40 +182,240 @@ RESTART: last_page_erased = AM_HAL_FLASH_ADDR2PAGE(frame_address + USERCODE_OFFSET); } -#ifdef DEBUG if (i32ReturnCode) { - Serial1.printf("FLASH_MASS_ERASE i32ReturnCode = 0x%x.\n\r", i32ReturnCode); + debug_printf("FLASH_MASS_ERASE i32ReturnCode = 0x%x.\n\r", i32ReturnCode); } -#endif //Record the array i32ReturnCode = program_array(frame_address + USERCODE_OFFSET); -#ifdef DEBUG - if (i32ReturnCode) - Serial1.printf("FLASH_WRITE error = 0x%x.\n\r", i32ReturnCode); - else - Serial1.println("Array recorded to flash"); -#endif + if (i32ReturnCode){ + debug_printf("FLASH_WRITE error = 0x%x.\n\r", i32ReturnCode); + }else{ + debug_printf("Array recorded to flash\n"); + } } else { -#ifdef DEBUG - Serial1.println("Bad CRC:"); - Serial1.printf("incoming_crc32: 0x%04X\n\r", incoming_crc32); - Serial1.printf("CRC: 0x%04X\n\r", crc32); -#endif + debug_printf("Bad CRC:\n"); + debug_printf("incoming_crc32: 0x%04X\n\r", incoming_crc32); + debug_printf("CRC: 0x%04X\n\r", crc32); + } +} + + + + + + + + +// **************************************** +// +// Jump to the application +// +// **************************************** +void app_start() +{ + // Print a section of flash + uint32_t start_address = USERCODE_OFFSET; + debug_printf("Printing page starting at offset 0x%04X", start_address); + for (uint16_t x = 0; x < 512; x++) + { + if (x % 8 == 0) + { + debug_printf("\nAdr: 0x%04X", start_address + (x * 4)); + } + debug_printf(" 0x%08X", *(uint32_t *)(start_address + (x * 4))); + } + debug_printf("\n"); + + uint32_t entryPoint = *(uint32_t *)(USERCODE_OFFSET + 4); + + debug_printf("Jump to 0x%08X", entryPoint); + delay(10); //Wait for prints to complete + + goto *entryPoint; //Jump to start of user code + + debug_printf("Hould not be here\n"); + while (1){}; +} + + + +// **************************************** +// +// Commit whatever is in the ui32Source to the given location +// +// **************************************** +int32_t program_array(uint32_t ui32PrgmAddr) +{ + uint32_t *pui32Dst; + int32_t i32ReturnCode; + // + // Program a few words in a page in the main block of instance 1. + // + pui32Dst = (uint32_t *) ui32PrgmAddr; + i32ReturnCode = am_hal_flash_program_main(AM_HAL_FLASH_PROGRAM_KEY, + ui32Source, + pui32Dst, + 512); + + return (i32ReturnCode); +} + + + +// **************************************** +// +// Baud determination phase +// +// **************************************** +int8_t phase_set_baud_rate( void ){ + + bool set_baud_success = false; + uint8_t baud_index = 0; + const uint8_t BAUD_RATE_CMD_LEN = 7; + char recvd[BAUD_RATE_CMD_LEN] = {0}; + const uint32_t send_baud_timeout_ms = 40; + int8_t timed_out = 0x00; + + // Loop through all baud rates to check + while(baud_index < NUM_BAUDS){ + + SERIAL_PORT.begin(bauds[baud_index]); + + // Wait for at least one byte to be available: + uint32_t wait_bytes = 1; + wait_until( &wait_bytes, send_baud_timeout_ms ); + + // When at least one byte is available either read a full command or time out + uint32_t num_2_recv = BAUD_RATE_CMD_LEN; + memset( (void*)recvd, 0x00, sizeof(recvd)/sizeof(char)); + timed_out = read_until( recvd, &num_2_recv, send_baud_timeout_ms ); + debug_printf("Received: {%d, %d, %d, %d, %d, %d, %d} (timed out? %d) (recieved: %d)\n", recvd[0], recvd[1], recvd[2], recvd[3], recvd[4], recvd[5], recvd[6], timed_out, num_2_recv); + + if((timed_out == 0x00) && (num_2_recv == BAUD_RATE_CMD_LEN)){ // Looks like we got a message, now check baud match + uint32_t got_baud = ( (((uint32_t)recvd[3]) << 24) | (((uint32_t)recvd[4]) << 16) | (((uint32_t)recvd[5]) << 8) | (((uint32_t)recvd[6]) << 0) ); + debug_printf("Got baud: %d, at baud: %d\n", got_baud, bauds[baud_index]); + if( got_baud == bauds[baud_index] ){ + set_baud_success = true; + break; + } + } + + SERIAL_PORT.end(); + baud_index++; + } + + // report success or failure + if(set_baud_success){ + // Indicate to the host that the baud rate is set correctly + send_bytes(recvd, BAUD_RATE_CMD_LEN); + return 0; + }else{ + return -1; + } +} + + + +// **************************************** +// +// Send Bootloader Information Phase +// +// **************************************** +void phase_send_bl_info( void ){ + +// delay(3); //Necessary to separate a blip on TX line when port opens from annouce command + + delayMicroseconds(10); + + SERIAL_PORT.write(BL_COMMAND_ANNOUNCE); //Tell the world we can be bootloaded + + // ToDo: we will need to make sure that no more baud rate negotiation messages are coming in at this point... + + //Check to see if the computer responded + uint32_t count = 0; + while (SERIAL_PORT.available() == 0) + { + if (count++ > MAX_WAIT_IN_US) + { + debug_printf("No response from computer\n"); + app_start(); + } + delayMicroseconds(1); + } + if (SERIAL_PORT.read() != BL_COMMAND_AOK) + { + debug_printf("Invalid response from computer\n"); + app_start(); //If the computer did not respond correctly with a AOK, we jump to user's program + } + + SERIAL_PORT.write(BOOTLOADER_VERSION); + + debug_printf("Bootloading!\n"); +} + + + + + +// **************************************** +// +// Convenience UART functions +// +// **************************************** +int8_t read_until( char* buff, uint32_t* num_io, uint32_t timeout_ms ){ + uint32_t start = millis(); + uint32_t desired = *(num_io); + uint32_t received = 0; + while( (millis() - start) < timeout_ms ){ + if( received >= desired ){ // Check if all bytes received + break; + } + if(SERIAL_PORT.available()){ // Store the byte + *(buff + received) = SERIAL_PORT.read(); + received++; + } + } + *(num_io) = received; // Provide the number of bytes received within the timeout + if(!(received >= desired)){ // Warn that timeout happened b4 bytes received + return -1; + } + return 0; +} + +int8_t wait_until( uint32_t* num_io, uint32_t timeout_ms ){ + uint32_t start = millis(); + uint32_t desired = *(num_io); + uint32_t avail = 0; + while( (millis() - start) < timeout_ms ){ + avail = SERIAL_PORT.available(); // How many bytes available? + if( avail >= desired ){ // Check if all bytes available + break; + } + } + *(num_io) = avail; // Provide the number of bytes received within the timeout + if(!(avail >= desired)){ // Warn that timeout happened b4 bytes received + return -1; + } + return 0; +} + +void send_bytes( const char* buff, uint32_t num ){ + for(uint32_t indi = 0; indi < num; indi++){ + SERIAL_PORT.write(buff[indi]); } } -//Gets a character from Serial uint8_t getch() { retransmit_flag = false; uint16_t counter = 0; - while (Serial.available() == 0) + while (SERIAL_PORT.available() == 0) { delayMicroseconds(10); if (counter++ > 10000) @@ -244,7 +424,7 @@ uint8_t getch() return (0); //Timeout } } - return (Serial.read()); + return (SERIAL_PORT.read()); } uint32_t get4Bytes(void) @@ -269,52 +449,19 @@ uint32_t get4BytesReversed(void) return (incoming); } -void app_start() -{ -#ifdef DEBUG - // Print a section of flash - uint32_t start_address = USERCODE_OFFSET; - Serial1.printf("Printing page starting at offset 0x%04X", start_address); - for (uint16_t x = 0; x < 512; x++) - { - if (x % 8 == 0) - { - Serial1.println(); - Serial1.printf("Adr: 0x%04X", start_address + (x * 4)); - } - Serial1.printf(" 0x%08X", *(uint32_t *)(start_address + (x * 4))); - } - Serial1.println(); -#endif - - uint32_t entryPoint = *(uint32_t *)(USERCODE_OFFSET + 4); - -#ifdef DEBUG - Serial1.printf("Jump to 0x%08X", entryPoint); -#endif - delay(10); //Wait for prints to complete - goto *entryPoint; //Jump to start of user code +// **************************************** +// +// A debug print function +// +// **************************************** +void debug_printf(char* fmt, ...){ #ifdef DEBUG - Serial1.println("Not here"); -#endif - while (1); -} - -//Commits whatever is in the ui32Source to the given location -int32_t program_array(uint32_t ui32PrgmAddr) -{ - uint32_t *pui32Dst; - int32_t i32ReturnCode; - // - // Program a few words in a page in the main block of instance 1. - // - pui32Dst = (uint32_t *) ui32PrgmAddr; - i32ReturnCode = am_hal_flash_program_main(AM_HAL_FLASH_PROGRAM_KEY, - ui32Source, - pui32Dst, - 512); - - return (i32ReturnCode); + va_list args; + va_start (args, fmt); + vsnprintf(debug_buffer, DEBUG_BUFFER_LEN, (const char*)fmt, args); + va_end (args); + DEBUG_SERIAL_PORT.print(debug_buffer); +#endif //DEBUG } diff --git a/tools/artemis/artemis_svl.py b/tools/artemis/artemis_svl.py index 2390d497..8ff59e4e 100644 --- a/tools/artemis/artemis_svl.py +++ b/tools/artemis/artemis_svl.py @@ -11,28 +11,133 @@ import serial.tools.list_ports as list_ports import sys import time +import re from sys import exit -# Bootloader command constants +# ****************************************************************************** +# +# Define Commands +# +# ****************************************************************************** BL_COMMAND_ANNOUNCE = 127 BL_COMMAND_AOK = 6 BL_COMMAND_BAD_CRC = 7 BL_COMMAND_NEXT_FRAME = 8 BL_COMMAND_ALL_DONE = 9 BL_COMMAND_COMPUTER_READY = 10 +BL_COMMAND_SET_BAUD = 0x01 # ****************************************************************************** # -# Main function +# Return byte arrays for given commands +# +# ****************************************************************************** +def svl2_cmd_set_baud(baud): + cmd_bytes = BL_COMMAND_SET_BAUD.to_bytes(1, byteorder = 'big') + baud_bytes = baud.to_bytes(4, byteorder = 'big') + return (cmd_bytes + baud_bytes) + +def svl2_cmd_host_rdy(start, size, crc32, total_size): + cmd_bytes = SVL2_CMD_HOST_RDY.to_bytes(1, byteorder = 'big') + start_bytes = start.to_bytes(4, byteorder = 'big') + size_bytes = size.to_bytes(4, byteorder = 'big') + crc32_bytes = crc32.to_bytes(4, byteorder = 'big') + total_size_bytes = total_size.to_bytes(4, byteorder = 'big') + return (cmd_bytes + start_bytes + size_bytes + crc32_bytes + total_size_bytes) + +# ****************************************************************************** +# +# Send commands in the specified format +# +# ****************************************************************************** +def send_package(byter, ser): + num = len(byter) + num_bytes = num.to_bytes(2, byteorder = 'big') + ser.write(num_bytes + byter) + # ser.write() + +# ****************************************************************************** +# +# Wait for matching command using current port timeout settings # # ****************************************************************************** +def wait_for_package(byter, ser): + retval = {'status':-1, 'data':''} # Create return dictionary + data = ser.read_until(byter, len(byter) + 2 + 1) # Wait for incoming data or timeout (+2 accounts for length bytes, +1 accounts for Apollo3 Serial.begin() blip) + reres = re.search(byter,data) # Use RE to search for the desired sequence (necessary on Mac/Linux b/c of handling of the startup blip) + if(reres): + retval['status'] = 0 # indicate success + retval['data'] = reres.group(0) # return the found data + + return retval + +# ****************************************************************************** +# +# Get a single byte command +# +# ****************************************************************************** +def wait_for_command(ser): + data = ser.read(size = 3) # Wait for incoming data or timeout + data = data[2:] + return data + + +# ****************************************************************************** +# +# Agree on a baud rate for the target +# +# ****************************************************************************** +def phase_set_baud(ser): + verboseprint("Starting Communications at " + str(args.baud) + " baud") + + baud_set_tries = args.tries + baud_set_timeout = global_timeout # Timeout should be no shorter than (10*len(baud_set_command)/args.baud) + baud_set_command = svl2_cmd_set_baud(args.baud) + + for tri in range(baud_set_tries): + send_package(baud_set_command, ser) # Send the baud set command + baud_set_start = time.time() # Mark the start + ret = wait_for_package(baud_set_command, ser) # Try to get the confirmation + result = ret['data'] + if(result == baud_set_command): # If there is a match then the target is at the right baud rate + break + + while((time.time() - baud_set_start) < baud_set_timeout): # Wait for timeout in case target is transmitting slowly + time.sleep(0.0005) # Mismatch cases: + # Target baud is low, host baud is high: + # Target begins sending, then host reads 'len(baud_set_command)' bytes (incorrectly) and returns. While loop gives target time to finish transmitting + # Target baud is high, host baud is low: + # Target begins sending, host reads past the end of target transmission, then ready to go again. While loop not needed, but does not hurt + + verboseprint('Try #' + str(tri) + '. Result: ' + str(result)) + if(len(result) != len(baud_set_command)): + print("Baud Set: lengths did not match") + return 1 + + if( result != baud_set_command ): + print("Baud Set: messages did not match") + return 1 + + verboseprint("Received confirmation from target!") + return 0 + + + + +# ****************************************************************************** +# +# Main function +# +# ****************************************************************************** +global_timeout = 0 + def main(): # Open a serial port, and communicate with Device - # + # Max flashing time depends on the amount of SRAM available. # For very large images, the flashing happens page by page. # However if the image can fit in the free SRAM, it could take a long time @@ -43,7 +148,7 @@ def main(): # Check to see if the com port is available try: - with serial.Serial(args.port, args.baud, timeout=1) as ser: + with serial.Serial(args.port, args.baud, timeout=0.05) as ser: pass except: @@ -78,13 +183,22 @@ def main(): # Begin talking over com port print('Connecting over serial port {}...'.format(args.port), flush=True) - # Initially we communicate at 9600 - with serial.Serial(args.port, 9600, timeout=0.100) as ser: + # # compute an acceptable timeout for the given baud rate + # global_timeout = 10*(25)/args.baud + global_timeout = 0.05 + verboseprint('Using Serial timeout: ' + str(global_timeout)) + + # Now open the port for bootloading + with serial.Serial(args.port, args.baud, timeout=global_timeout) as ser: # DTR is driven low when serial port open. DTR has now pulled RST low causing board reset. # If we do not set DTR high again, the Ambiq SBL will not activate, but the SparkFun bootloader will. - verboseprint("Waiting for command from bootloader") + if(phase_set_baud(ser) != 0): + exit() + + print('Connected!') + print('Bootloading...') # Wait for incoming BL_COMMAND_ANNOUNCE i = 0 @@ -109,10 +223,6 @@ def main(): verboseprint("Unkown response: " + str(ord(response))) response = '' - # Send upload baud rate - baud_in_bytes = args.baud.to_bytes(4, byteorder='big') - ser.write(baud_in_bytes) - # Wait for incoming char indicating bootloader version i = 0 response = '' @@ -126,13 +236,6 @@ def main(): verboseprint("Bootloader version: " + str(ord(response))) - ser.flush() - # Wait for all previous bytes to transmit before changing bauds - time.sleep(0.010) - - # Go to new baud rate - ser.baudrate = args.baud - # Read the binary file from the command line. with open(args.binfile, mode='rb') as binfile: application = binfile.read() @@ -141,7 +244,6 @@ def main(): verboseprint("Length to send: " + str(totalLen)) - frame_address = 0 start = 0 end = start + 512*4 @@ -175,7 +277,7 @@ def main(): while len(response) == 0: i = i + 1 if(i == 10): - print("No announcement from Artemis bootloader") + print("Bootloader did not request next frame") exit() response = ser.read() @@ -240,6 +342,9 @@ def main(): parser.add_argument("-v", "--verbose", default=0, help="Enable verbose output", action="store_true") + parser.add_argument("-t", "--tries", default=20, help="How many baud rate negotiation messages to send before failing", + type=int) + if len(sys.argv) < 2: print("No port selected. Detected Serial Ports:") devices = list_ports.comports() @@ -259,4 +364,4 @@ def verboseprint(*args): else: verboseprint = lambda *a: None # do-nothing function - main() + main() \ No newline at end of file diff --git a/tools/artemis/linux/artemis_svl b/tools/artemis/linux/artemis_svl index 9757cf10..d04c2821 100755 Binary files a/tools/artemis/linux/artemis_svl and b/tools/artemis/linux/artemis_svl differ diff --git a/tools/artemis/macosx/artemis_svl b/tools/artemis/macosx/artemis_svl index b9ef0f54..9787d2f9 100755 Binary files a/tools/artemis/macosx/artemis_svl and b/tools/artemis/macosx/artemis_svl differ diff --git a/tools/artemis/windows/artemis_svl.exe b/tools/artemis/windows/artemis_svl.exe index a7f079b1..0822274f 100644 Binary files a/tools/artemis/windows/artemis_svl.exe and b/tools/artemis/windows/artemis_svl.exe differ