Arduino has a way to do this. the SDU and the SFU library show it
I had to write a batch file to build my sketch with '.text' starting at 0x3a000:
@echo off
set uuid=D31097C5B3C3230B1B8AC2B5E2D38822
set path_arduino=C:\\Users\\admin\\AppData\\Local\\Arduino15\\packages\\arduino
set path_tmp=C:\\Users\\admin\\AppData\\Local\\Temp\\arduino\\sketches\\%uuid%
echo path_arduino = %path_arduino%
echo path_tmp = %path_tmp%
pause
%path_arduino%\\tools\\arm-none-eabi-gcc\\7-2017q4\\bin\\arm-none-eabi-g++ ^
-o ArduinoBC43_UpdaterSketch_moved.ino.elf ^
"%path_tmp%\\sketch\\ArduinoBC43_UpdaterSketch.ino.cpp.o" ^
"%path_tmp%\\libraries\\Wire\\Wire.cpp.o" ^
"%path_tmp%\\core\\variant.cpp.o" ^
"-L%path_tmp%" ^
"-T%path_arduino%\\hardware\\samd\\1.8.13\\variants\arduino_zero\linker_scripts\gcc\flash_with_bootloader.ld" ^
-Os ^
-Wl,--gc-sections -save-temps "-Wl,-Map,%path_tmp%\\ArduinoBC43_UpdaterSketch.ino.map" ^
--specs=nano.specs --specs=nosys.specs -mcpu=cortex-m0plus -mthumb -Wl,--cref -Wl,--check-sections -Wl,--gc-sections ^
-Wl,--unresolved-symbols=report-all -Wl,--warn-common -Wl,--warn-section-align ^
-Wl,--start-group ^
"-L%path_arduino%\\tools\\CMSIS\\4.5.0\CMSIS\Lib\GCC" ^
-larm_cortexM0l_math -lm ^
"%path_tmp%\\..\\..\\cores\\arduino_samd_arduino_zero_edbg_27e9adf56847ad6d10a7b91f750de76b\\core.a" ^
-Wl,--end-group -Wl,-section-start=.text=0x3a000
"%path_arduino%\\tools\\arm-none-eabi-gcc\\7-2017q4\bin\arm-none-eabi-objcopy" -O binary ArduinoBC43_UpdaterSketch_moved.ino.elf ArduinoBC43_UpdaterSketch_moved.ino.bin
"%path_arduino%\\tools\\arm-none-eabi-gcc\\7-2017q4\bin\arm-none-eabi-objdump" -D ArduinoBC43_UpdaterSketch_moved.ino.elf | head -512 >> assembler_moved.txt
I need to execute the function 'WriteFlashPage' from SRAM, and so I've renamed it to 'WriteFlashPage_detail', and then I wrote a wrapper function around it that copies the machine code from Flash into SRAM. The machine code is only about 150 bytes but I copy an entire kilobyte:
void WriteFlashPage(long unsigned const addr, char unsigned const (&bytes)[64u])
{
char unsigned const volatile *const pFlash = (char unsigned const volatile*)&WriteFlashPage_detail;
cout << "WriteFlashPage_detail resides in Flash at address 0x" << (long unsigned)pFlash << endl;
static alignas(long double) char unsigned volatile func_in_ram[1024u];
cout << "Now copying 1 kilobyte of machine code from 0x" << hex << (long unsigned)pFlash << " to 0x" << hex << (long unsigned)&func_in_ram << endl;
for ( unsigned i = 0u; i < 1024u; ++i ) func_in_ram[i] = pFlash[i];
void (*const f)(long unsigned, char unsigned const(&)[64u]) = (void (*)(long unsigned, char unsigned const(&)[64u]))&func_in_ram;
cout << "About to invoke 'WriteFlashPage_detail' which resides in SRAM at address 0x" << hex << (long unsigned)f << endl;
delay(1000u); // Don't disable interrupts before calling 'delay' as the millisecond counter is incremented inside an interrupt routine!
__disable_irq();
f(addr, bytes);
__enable_irq();
}
The assembler for 'WriteFlashPage_detail' seems to be position-independent:
0000212c <WriteFlashPage_detail(unsigned long, unsigned char const (&) [64])>:
212c: 4a1f ldr r2, [pc, #124] ; (21ac <WriteFlashPage_detail(unsigned long, unsigned char const (&) [64])+0x80>)
212e: b5f0 push {r4, r5, r6, r7, lr}
2130: 0844 lsrs r4, r0, #1
2132: 61d4 str r4, [r2, #28]
2134: 4c1e ldr r4, [pc, #120] ; (21b0 <WriteFlashPage_detail(unsigned long, unsigned char const (&) [64])+0x84>)
2136: 8014 strh r4, [r2, #0]
2138: 7d14 ldrb r4, [r2, #20]
213a: 07e4 lsls r4, r4, #31
213c: d5fc bpl.n 2138 <WriteFlashPage_detail(unsigned long, unsigned char const (&) [64])+0xc>
213e: 2480 movs r4, #128 ; 0x80
2140: 6855 ldr r5, [r2, #4]
2142: 4f1c ldr r7, [pc, #112] ; (21b4 <WriteFlashPage_detail(unsigned long, unsigned char const (&) [64])+0x88>)
2144: 432c orrs r4, r5
2146: 2510 movs r5, #16
2148: 6054 str r4, [r2, #4]
214a: 4c1b ldr r4, [pc, #108] ; (21b8 <WriteFlashPage_detail(unsigned long, unsigned char const (&) [64])+0x8c>)
214c: 8014 strh r4, [r2, #0]
214e: 7d14 ldrb r4, [r2, #20]
2150: 07e4 lsls r4, r4, #31
2152: d5fc bpl.n 214e <WriteFlashPage_detail(unsigned long, unsigned char const (&) [64])+0x22>
2154: 2440 movs r4, #64 ; 0x40
2156: 46a4 mov ip, r4
2158: 4484 add ip, r0
215a: 26ff movs r6, #255 ; 0xff
215c: 780c ldrb r4, [r1, #0]
215e: 43b3 bics r3, r6
2160: 4323 orrs r3, r4
2162: 4e16 ldr r6, [pc, #88] ; (21bc <WriteFlashPage_detail(unsigned long, unsigned char const (&) [64])+0x90>)
2164: 784c ldrb r4, [r1, #1]
2166: 4033 ands r3, r6
2168: 0224 lsls r4, r4, #8
216a: 4323 orrs r3, r4
216c: 403b ands r3, r7
216e: 001c movs r4, r3
2170: 788e ldrb r6, [r1, #2]
2172: 78cb ldrb r3, [r1, #3]
2174: 0436 lsls r6, r6, #16
2176: b2a4 uxth r4, r4
2178: 061b lsls r3, r3, #24
217a: 4334 orrs r4, r6
217c: 4323 orrs r3, r4
217e: c008 stmia r0!, {r3}
2180: 3104 adds r1, #4
2182: 3d01 subs r5, #1
2184: 4584 cmp ip, r0
2186: d001 beq.n 218c <WriteFlashPage_detail(unsigned long, unsigned char const (&) [64])+0x60>
2188: 2d00 cmp r5, #0
218a: d1e6 bne.n 215a <WriteFlashPage_detail(unsigned long, unsigned char const (&) [64])+0x2e>
218c: 4c0c ldr r4, [pc, #48] ; (21c0 <WriteFlashPage_detail(unsigned long, unsigned char const (&) [64])+0x94>)
218e: 8014 strh r4, [r2, #0]
2190: 7d14 ldrb r4, [r2, #20]
2192: 07e4 lsls r4, r4, #31
2194: d5fc bpl.n 2190 <WriteFlashPage_detail(unsigned long, unsigned char const (&) [64])+0x64>
2196: 2d00 cmp r5, #0
2198: d1d7 bne.n 214a <WriteFlashPage_detail(unsigned long, unsigned char const (&) [64])+0x1e>
219a: 4b0a ldr r3, [pc, #40] ; (21c4 <WriteFlashPage_detail(unsigned long, unsigned char const (&) [64])+0x98>)
219c: 3b01 subs r3, #1
219e: 2b00 cmp r3, #0
21a0: d1fc bne.n 219c <WriteFlashPage_detail(unsigned long, unsigned char const (&) [64])+0x70>
21a2: 4b08 ldr r3, [pc, #32] ; (21c4 <WriteFlashPage_detail(unsigned long, unsigned char const (&) [64])+0x98>)
21a4: 3b01 subs r3, #1
21a6: 2b00 cmp r3, #0
21a8: d1fc bne.n 21a4 <WriteFlashPage_detail(unsigned long, unsigned char const (&) [64])+0x78>
21aa: bdf0 pop {r4, r5, r6, r7, pc}
21ac: 41004000 mrsmi r4, (UNDEF: 0)
21b0: ffffa502 ; <UNDEFINED> instruction: 0xffffa502
21b4: ff00ffff ; <UNDEFINED> instruction: 0xff00ffff
21b8: ffffa544 ; <UNDEFINED> instruction: 0xffffa544
21bc: ffff00ff ; <UNDEFINED> instruction: 0xffff00ff
21c0: ffffa504 ; <UNDEFINED> instruction: 0xffffa504
21c4: 0001e848 andeq lr, r1, r8, asr #16
This all looks fine to me, but my program freezes when I invoke the function pointer named 'f' inside the function 'WriteFlashPage'. Can anyone see what I'm doing wrong?
Oh crap I just realised now that the assembler for WriteFlashPage_detail contains hard-coded addresses such as 0x2138 -- so I need to somehow compile that function as position-independent code.
By the way I've tried using the 'ramfunc' attribute as follows:
void WriteFlashPage_detail(long unsigned const addr, char unsigned const (&bytes)[64u])
__attribute__((flatten)) __attribute__((section(".ramfunc")));
but it doesn't work. If it worked properly then the address of the function would be greater than 0x200000 because that's where SRAM starts -- but its address is 0x1066d, and that's in the Flash.
So 'ramfunc' doesn't work for me with the SAM D21 in Arduino IDE.
When I get home later I'll try compile with -fPIC and then memcpy the function from Flash into SRAM.
I've already 'flattened' the function -- it doesn't contain any function calls.
If I can't get it to compile with -fPIC then I'll ask ChatGPT to convert all the absolute jumps to relative jumps, then use inline assembler to put the assembler in my sketch.
To be honest though, even with the absolute jumps, the function in SRAM should simply jump to its copy residing in Flash, so I don't know why my program is freezing.
Those are relative jumps; the disassembler is just doing the calculations for you.
In fact, I think all of the "label-based" CM0+ branch instructions are PC-relative. To do an absolute jump, you have to load your 32bit address as a constant, into a register, and then do a register-based branch (BX or BLX)
The range of conditional branches (bpl.n
, etc) is pretty small on a CM0+ (+/-2k)
The subroutine call (BL
) has a much more substantial range (+/-16M), but I don't think it will get you from flash to RAM.
Of course, if you copy code to a new memory location, any PC-relative branch that jumps outside of the code that you've copied will fail.
You don't think it's possible to execute a function in RAM as follows?
void (*f)(void) = (void (*)(void))0x260000;
f();
You reckon that the caller function (located in Flash), might fail to do a 'BL' instruction to an SRAM address?
That should work fine. The C compiler is smart enough to use the register form of branch when needed.
Okay let me tell you where I'm at now.
I downloaded from Github the C++ source code for the bootloader for the SAM D21 for the Arduino Zero board. I built that code to a binary. I then used Microchip Studio to upload this bootloader to my chip.
Next I made an edit to the bootloader C++ code, changing the jump from 0x2004 to 0x3a004, and then I built a new bootloader binary.
I compared these two binaries in a hex editor, and was able to clearly see which bytes were changed from:
00 20 00 00
to:
00 a0 03 00
So then I was able to write a function in C++ in my Arduino sketch which would changes these specific bytes in the bootloader.
I used the command line program 'openocd' to upload the two sketches to my chip (one sketch at 0x2000, and another sketch at 0x3a004).
So now I have a SAM D21 with the following on it:
- A bootloader at 0x00000 configured to jump to 0x2000
- My main sketch at 0x2000
- My 'Uploader Sketch' at 0x3a000
So if I then power my chip on, I can see in the Serial Monitor:
Hello from the main sketch every 2 seconds
I then send a message over i2c to the chip telling it to change the bytes in the bootloader so that it will jump to 0x3a004 instead of 0x02000. I then cut the power to my chip and then re-apply the power, and next I see:
Hello from the Updater Sketch every 2 seconds
so I'm making good progress, I nearly have this working the way I need it.
I want to be able to send a message over i2c to my chip telling it to reboot (so that I don't have to cut the power and re-apply the power). I have tried three strategies to get this working. First thing I do is disable all interrupts and then clear all pending interrupts:
cout << "\n==== Rebooting ====\n";
delay(1000u);
__disable_irq();
// Next line should clear pending interrupts
for ( uint32_t IRQn = 0u; IRQn < 28u; ++IRQn ) NVIC->ICPR[0] = (1 << (IRQn & 0x1F));
Here's the 1st strategy using the watchdog timer. I've tried this with interrupts disabled and also with interrupts enabled, and it doesn't work:
WDT->CTRL.reg = 0; // disable watchdog
while ( 1 == WDT->STATUS.bit.SYNCBUSY ); // Just wait till WDT is free
WDT->CONFIG.reg = 2; // Timeout Period (valid values 0-11)
WDT->CTRL.reg = WDT_CTRL_ENABLE; // enable watchdog
while ( 1 == WDT->STATUS.bit.SYNCBUSY ); // Just wait till WDT is free
Strategy No. 2 was simply just to jump to 0x00000 as follows:
#define APP_START 0x00000000
uint32_t app_start_address = *(uint32_t *)(APP_START + 4);
/* Rebase the Stack Pointer */
__set_MSP(*(uint32_t*)APP_START);
/* Rebase the vector table base address */
SCB->VTOR = ((uint32_t)APP_START & SCB_VTOR_TBLOFF_Msk);
/* Jump to application Reset Handler in the application */
asm("bx %0"::"r"(app_start_address));
And then the third strategy I tried was:
#define SCB_AIRCR_PRIGROUP_Pos 8 /*!< SCB AIRCR: PRIGROUP Position */
#define SCB_AIRCR_PRIGROUP_Msk (7UL << SCB_AIRCR_PRIGROUP_Pos) /*!< SCB AIRCR: PRIGROUP Mask */
__DSB(); /* Ensure all outstanding memory accesses included
buffered write are completed before reset */
SCB->AIRCR = ((0x5FA << SCB_AIRCR_VECTKEY_Pos) |
(SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) |
SCB_AIRCR_SYSRESETREQ_Msk); /* Keep priority group unchanged */
__DSB(); /* Ensure completion of memory access */
//while(1); /* wait until reset */
All three of these strategies just make the microcontroller freeze -- it doesn't reboot. I have to cut the power and re-apply the power to get it to reboot. Does anyone know if it's possible to eradicate the need for this power cut?
I don't know if this gives any clue, but when I use 'openocd' to upload the 'Updater Sketch' to address 0x3a004, I get:
** Programming Finished **
** Verify Started **
verified 18932 bytes in 1.568529s (11.787 KiB/s)
** Verified OK **
** Resetting Target **
target halted due to breakpoint, current mode: Handler HardFault
xPSR: 0x81000003 pc: 0x00000480 msp: 0x20007ba8
shutdown command invoked
And then I need to power-cycle the microcontroller. So what's the problem here? Do I need to reset a breakpoint or reset a hardware fault or something?
reset of any ARM is NVIC_SystemReset();
so I try it with an "I am an expert listen to me". I am the author of ArduinoOTA library for classic AVR Arduino with more than 64 kB flash, Arduino Uno R4, nRF5, RP2040 and the STM32F family. The ArduinoOTA library inherited SAMD support from the WiFi101OTA library. Additionally I adopted the SAMD SDU libray for Arduino M0, created an SDU library for nRF51 and the SDU library for Uno R4.
And I say that the best way for you is to create a second stage bootloader like the SDU library.
I have the firmware upgrade over i2c working now, with the new firmware being sent from a desktop PC program:
The desktop PC has an RS232 connection to a Texas Instruments microcontroller, which has an i2c connection to the Arduino, and so the desktop PC sends one page (i.e. 64 bytes) per packet out over the RS232 line, and it makes its way to the Arduino.
I just need to figure out now how to reset the Arduino without power-cycling it. It's actually not that big a deal if it needs to be power-cycled, but it would be convenient if I could find a way of resetting the SAM D21 from inside my Sketch. By the way @Juraj , I found the source code for 'NVIC_SystemReset':
/** \brief System Reset
This function initiate a system reset request to reset the MCU.
*/
static __INLINE void NVIC_SystemReset(void)
{
__DSB(); /* Ensure all outstanding memory accesses included
buffered write are completed before reset */
SCB->AIRCR = ((0x5FA << SCB_AIRCR_VECTKEY_Pos) |
SCB_AIRCR_SYSRESETREQ_Msk);
__DSB(); /* Ensure completion of memory access */
while(1); /* wait until reset */
}
That's the same code I showed as my 'third strategy' a few posts above this one.
I have a quick question about writing to the Flash in the SAM D21. My understanding is as follows:
- Each page is 64 bytes
- You must erase a page before writing to it
- You can't erase one page on its own -- you need to erase 4 pages
Does that sound right? So if you need to write to one page, then you need to save the three pages beside in SRAM before erasing the 4 pages at once, and then you have to write all four pages, right? Have I got that right?
This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.