SAM D21 - Firmware upgrade over i2c

The SAM D21 doesn't have two banks, and so doing a firmware upgrade over i2c isn't as simple as writing to the other bank and then flipping bank.

I had considered splitting the single bank into two halves, using the first half as the 'main' half, and using the second half to store the bytes of the firmware upgrade received over i2c. This isn't an option though because the firmware takes up 78% of the available Flash.

So instead here's what I'll have to do:

  • Write a function called 'Upgrade' that reads bytes from i2c and then writes those bytes to the Flash (of course I need to take into account that the Flash can only be written in full pages).
  • Copy the machine code of 'Upgrade' into SRAM, and execute it from SRAM.

I would have to make sure that my function 'Upgrade' does not jump to a code location in the Flash, and so I would want all function calls to be expanded inline. Normally when programming on desktop PC for x86_64, I would use attribute((__flatten)) for this purpose.

Do you reckon I'll be able to pull this off?

you can use InternalStorage from the WiFi101OTA or my ArduinoOTA library to store and apply the update. Or with SD card you can use the SDU library.
https://github.com/JAndrassy/ArduinoOTA/tree/master/examples/Advanced

Here's what I've got so far for the 'Upgrade' function:

void Upgrade(void)
{
    /* Disable all interrupts -- primarily because we don't want
       to jump to a code location in the Flash while it's being
       overwritten.                                              */

    __disable_irq();

    /* Now we're expecting 262144 bytes of firmware over i2c */

    unsigned bytes_read = 0u;

    constexpr unsigned page_size = 128u;
    static alignas(std::uint32_t) char unsigned page_in_sram[page_size];
    char unsigned *p = page_in_sram;

    for ( bytes_read = 0u; bytes_read < 262144u; )  // A firmware upgrade is 256 kilobytes
    {
        while ( 0 == Wire.available() ) delay(2);

        *p++ = Wire.read();
        ++bytes_read;

        if ( p < &page_in_sram[page_size] ) continue;

        p = page_in_sram;  // Reset it for the next iteration

        // ============= Now we write a page ========================

        // Calculate data boundaries
        unsigned size = (page_size + 3u) / 4u;
        std::uint32_t volatile *dst_addr = (std::uint32_t volatile*)(0x00000 + bytes_read - page_size);  // might be off by 1 -- REVISIT - FIX
        uint8_t const *src_addr = page_in_sram;

        // Disable automatic page write
        NVMCTRL->CTRLB.bit.MANW = 1;

        // Do writes in pages
        while ( size )
        {
            // Execute "PBC" Page Buffer Clear
            NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_PBC;
            while ( 0 == NVMCTRL->INTFLAG.bit.READY ) /* Keep Looping */;

            // Fill page buffer
            for ( std::uint32_t i = 0u; size && (i < (page_size/4)); ++i )
            {
                *dst_addr = *static_cast<std::uint32_t const*>(static_cast<void const*>(src_addr));
                src_addr += sizeof(std::uint32_t);
                ++dst_addr;
                --size;
            }

            // Execute "WP" Write Page
            NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_WP;
            while ( 0 == NVMCTRL->INTFLAG.bit.READY ) /* Keep Looping */;
        }
    }
}

So in the Arduino IDE, I clicked 'Export Compiled Binary', and then I disassembled the ELF file. Here's the disassembly of the 'Upgrade' function:

00004a7c <_Z7Upgradev>:
    4a7c:	b5f8      	push	{r3, r4, r5, r6, r7, lr}
    4a7e:	b672      	cpsid	i
    4a80:	4d1e      	ldr	r5, [pc, #120]	; (4afc <_Z7Upgradev+0x80>)
    4a82:	247f      	movs	r4, #127	; 0x7f
    4a84:	002e      	movs	r6, r5
    4a86:	4264      	negs	r4, r4
    4a88:	4f1d      	ldr	r7, [pc, #116]	; (4b00 <_Z7Upgradev+0x84>)
    4a8a:	0038      	movs	r0, r7
    4a8c:	f001 f9c0 	bl	5e10 <_ZN7arduino7TwoWire9availableEv>
    4a90:	2800      	cmp	r0, #0
    4a92:	d103      	bne.n	4a9c <_Z7Upgradev+0x20>
    4a94:	3002      	adds	r0, #2
    4a96:	f003 fd29 	bl	84ec <delay>
    4a9a:	e7f5      	b.n	4a88 <_Z7Upgradev+0xc>
    4a9c:	0038      	movs	r0, r7
    4a9e:	f001 fb09 	bl	60b4 <_ZN7arduino7TwoWire4readEv>
    4aa2:	4a18      	ldr	r2, [pc, #96]	; (4b04 <_Z7Upgradev+0x88>)
    4aa4:	1c6b      	adds	r3, r5, #1
    4aa6:	7028      	strb	r0, [r5, #0]
    4aa8:	4293      	cmp	r3, r2
    4aaa:	d31f      	bcc.n	4aec <_Z7Upgradev+0x70>
    4aac:	2180      	movs	r1, #128	; 0x80
    4aae:	4b16      	ldr	r3, [pc, #88]	; (4b08 <_Z7Upgradev+0x8c>)
    4ab0:	4d16      	ldr	r5, [pc, #88]	; (4b0c <_Z7Upgradev+0x90>)
    4ab2:	685a      	ldr	r2, [r3, #4]
    4ab4:	0020      	movs	r0, r4
    4ab6:	430a      	orrs	r2, r1
    4ab8:	605a      	str	r2, [r3, #4]
    4aba:	0031      	movs	r1, r6
    4abc:	2220      	movs	r2, #32
    4abe:	46ac      	mov	ip, r5
    4ac0:	4d13      	ldr	r5, [pc, #76]	; (4b10 <_Z7Upgradev+0x94>)
    4ac2:	801d      	strh	r5, [r3, #0]
    4ac4:	7d1d      	ldrb	r5, [r3, #20]
    4ac6:	07ed      	lsls	r5, r5, #31
    4ac8:	d5fc      	bpl.n	4ac4 <_Z7Upgradev+0x48>
    4aca:	000f      	movs	r7, r1
    4acc:	3780      	adds	r7, #128	; 0x80
    4ace:	c920      	ldmia	r1!, {r5}
    4ad0:	3a01      	subs	r2, #1
    4ad2:	c020      	stmia	r0!, {r5}
    4ad4:	2a00      	cmp	r2, #0
    4ad6:	d001      	beq.n	4adc <_Z7Upgradev+0x60>
    4ad8:	42b9      	cmp	r1, r7
    4ada:	d1f8      	bne.n	4ace <_Z7Upgradev+0x52>
    4adc:	4665      	mov	r5, ip
    4ade:	801d      	strh	r5, [r3, #0]
    4ae0:	7d1d      	ldrb	r5, [r3, #20]
    4ae2:	07ed      	lsls	r5, r5, #31
    4ae4:	d5fc      	bpl.n	4ae0 <_Z7Upgradev+0x64>
    4ae6:	2a00      	cmp	r2, #0
    4ae8:	d1ea      	bne.n	4ac0 <_Z7Upgradev+0x44>
    4aea:	0033      	movs	r3, r6
    4aec:	4a09      	ldr	r2, [pc, #36]	; (4b14 <_Z7Upgradev+0x98>)
    4aee:	3401      	adds	r4, #1
    4af0:	4294      	cmp	r4, r2
    4af2:	d001      	beq.n	4af8 <_Z7Upgradev+0x7c>
    4af4:	001d      	movs	r5, r3
    4af6:	e7c7      	b.n	4a88 <_Z7Upgradev+0xc>
    4af8:	bdf8      	pop	{r3, r4, r5, r6, r7, pc}
    4afa:	46c0      	nop			; (mov r8, r8)
    4afc:	200040c5 	andcs	r4, r0, r5, asr #1
    4b00:	20004e40 	andcs	r4, r0, r0, asr #28
    4b04:	20004145 	andcs	r4, r0, r5, asr #2
    4b08:	41004000 	mrsmi	r4, (UNDEF: 0)
    4b0c:	ffffa504 			; <UNDEFINED> instruction: 0xffffa504
    4b10:	ffffa544 			; <UNDEFINED> instruction: 0xffffa544
    4b14:	0003ff81 	andeq	pc, r3, r1, lsl #31

I'm a lot more familiar with x86_64 than ARM32, but anyway I'll try talk about what's happening in this assembler. So first of all, I need to make sure that all the 'jumps' and 'branches' are to relative addresses inside the same function, and I therefore see the following problematic lines:

    4a8c:	f001 f9c0 	bl	5e10 <_ZN7arduino7TwoWire9availableEv>
    4a96:	f003 fd29 	bl	84ec <delay>
    4a9e:	f001 fb09 	bl	60b4 <_ZN7arduino7TwoWire4readEv>

So even though I told the compiler to 'flatten' my function, it still performs these three function calls. The delay isn't needed so I'll take it out, leaving me with just two functions that I need to expand inline:

_ZN7arduino7TwoWire9availableEv
_ZN7arduino7TwoWire4readEv

The assembler for the 'available' method is:

00005e10 <_ZN7arduino7TwoWire9availableEv>:
    5e10:	30fc      	adds	r0, #252	; 0xfc
    5e12:	6a40      	ldr	r0, [r0, #36]	; 0x24
    5e14:	4770      	bx	lr

So I can re-write that in C++ as follows:

inline uint32_t Wire_available( decltype(Wire) const *const parg )
{
    char unsigned const *p = static_cast<char unsigned const*>(static_cast<void const*>(parg));

    p += 252u;
    p += 36u;

    return *static_cast<uint32_t const*>(static_cast<void const*>(p));
}

Next if I look at the assembler for the 'read' method, I get:

000060b4 <_ZN7arduino7TwoWire4readEv>:
    60b4:	b510      	push	{r4, lr}
    60b6:	3018      	adds	r0, #24
    60b8:	f7ff ffb7 	bl	602a <_ZN7arduino11RingBufferNILi256EE9read_charEv>
    60bc:	bd10      	pop	{r4, pc}

So this written in C++ would be:

inline char Wire_read( decltype(Wire) *const parg )
{
    char unsigned *const p = static_cast<char unsigned*>(static_cast<void*>(parg));
    return Wire_read_detail( p + 24u );
}

And so then I sought the assembler for '_ZN7arduino11RingBufferNILi256EE9read_charEv':

0000602a <_ZN7arduino11RingBufferNILi256EE9read_charEv>:
    602a:	0003      	movs	r3, r0
    602c:	33fc      	adds	r3, #252	; 0xfc
    602e:	68da      	ldr	r2, [r3, #12]
    6030:	2a00      	cmp	r2, #0
    6032:	d009      	beq.n	6048 <_ZN7arduino11RingBufferNILi256EE9read_charEv+0x1e>
    6034:	689a      	ldr	r2, [r3, #8]
    6036:	5c80      	ldrb	r0, [r0, r2]
    6038:	689a      	ldr	r2, [r3, #8]
    603a:	3201      	adds	r2, #1
    603c:	b2d2      	uxtb	r2, r2
    603e:	609a      	str	r2, [r3, #8]
    6040:	68da      	ldr	r2, [r3, #12]
    6042:	3a01      	subs	r2, #1
    6044:	60da      	str	r2, [r3, #12]
    6046:	4770      	bx	lr
    6048:	2001      	movs	r0, #1
    604a:	4240      	negs	r0, r0
    604c:	e7fb      	b.n	6046 <_ZN7arduino11RingBufferNILi256EE9read_charEv+0x1c>

It looks like the above assembler doesn't jump to any code locations outside the function, so let's convert it to C++:

inline char Wire_read_detail(void *const parg)
{
    uint8_t *const p = (uint8_t *)parg + 252u;
    uint32_t tmp = *((uint32_t *)(p + 12));
    if ( 0 == tmp ) return 1;
    tmp = *((uint32_t *)(p + 8));
    uint8_t retval = *(uint8_t *)(parg + tmp);
    tmp = *((uint32_t *)(p + 8));
    ++tmp;
    *((uint32_t *)(p + 8)) = tmp;
    tmp = *((uint32_t *)(p + 12));
    --tmp;
    *((uint32_t *)(p + 12)) = tmp;
    return retval;
}

So now if I copy-paste these three inline functions into my code, I get:

inline uint32_t Wire_available( decltype(Wire) const *const parg )
{
    char unsigned const *p = static_cast<char unsigned const*>(static_cast<void const*>(parg));

    p += 252u;
    p += 36u;

    return *static_cast<uint32_t const*>(static_cast<void const*>(p));
}

inline char Wire_read_detail(void *const parg)
{
    uint8_t *const p = (uint8_t *)parg + 252u;
    uint32_t tmp = *((uint32_t *)(p + 12));
    if ( 0 == tmp ) return 1;
    tmp = *((uint32_t *)(p + 8));
    uint8_t retval = *(uint8_t *)(parg + tmp);
    tmp = *((uint32_t *)(p + 8));
    ++tmp;
    *((uint32_t *)(p + 8)) = tmp;
    tmp = *((uint32_t *)(p + 12));
    --tmp;
    *((uint32_t *)(p + 12)) = tmp;
    return retval;
}

inline char Wire_read( decltype(Wire) *const parg )
{
    char unsigned *const p = static_cast<char unsigned*>(static_cast<void*>(parg));
    return Wire_read_detail( p + 24u );
}

void Upgrade(void)
{
    /* Disable all interrupts -- primarily because we don't want
       to jump to a code location in the Flash while it's being
       overwritten.                                              */

    __disable_irq();

    /* Now we're expecting 262144 bytes of firmware over i2c */

    unsigned bytes_read = 0u;

    constexpr unsigned page_size = 128u;
    static alignas(std::uint32_t) char unsigned page_in_sram[page_size];
    char unsigned *p = page_in_sram;

    for ( bytes_read = 0u; bytes_read < 262144u; )  // A firmware upgrade is 256 kilobytes
    {
        while ( 0 == Wire_available(&Wire) ) /* Keep Looping */;

        *p++ = Wire_read(&Wire);
        ++bytes_read;

        if ( p < &page_in_sram[page_size] ) continue;

        p = page_in_sram;  // Reset it for the next iteration

        // ============= Now we write a page ========================

        // Calculate data boundaries
        unsigned size = (page_size + 3u) / 4u;
        std::uint32_t volatile *dst_addr = (std::uint32_t volatile*)(0x00000 + bytes_read - page_size);  // might be off by 1 -- REVISIT - FIX
        uint8_t const *src_addr = page_in_sram;

        // Disable automatic page write
        NVMCTRL->CTRLB.bit.MANW = 1;

        // Do writes in pages
        while ( size )
        {
            // Execute "PBC" Page Buffer Clear
            NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_PBC;
            while ( 0 == NVMCTRL->INTFLAG.bit.READY ) /* Keep Looping */;

            // Fill page buffer
            for ( std::uint32_t i = 0u; size && (i < (page_size/4)); ++i )
            {
                *dst_addr = *static_cast<std::uint32_t const*>(static_cast<void const*>(src_addr));
                src_addr += sizeof(std::uint32_t);
                ++dst_addr;
                --size;
            }

            // Execute "WP" Write Page
            NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_WP;
            while ( 0 == NVMCTRL->INTFLAG.bit.READY ) /* Keep Looping */;
        }
    }

    /* Now we just loop forever until microcontroller is reset */
    for (;;);
}

If I compile this code and then disassemble the 'Upgrade' function, I get:

0000386c <_Z7Upgradev>:
    386c:	b5f7      	push	{r0, r1, r2, r4, r5, r6, r7, lr}
    386e:	b672      	cpsid	i
    3870:	491c      	ldr	r1, [pc, #112]	; (38e4 <_Z7Upgradev+0x78>)
    3872:	4b1d      	ldr	r3, [pc, #116]	; (38e8 <_Z7Upgradev+0x7c>)
    3874:	31fc      	adds	r1, #252	; 0xfc
    3876:	227f      	movs	r2, #127	; 0x7f
    3878:	469c      	mov	ip, r3
    387a:	0008      	movs	r0, r1
    387c:	4252      	negs	r2, r2
    387e:	9101      	str	r1, [sp, #4]
    3880:	9901      	ldr	r1, [sp, #4]
    3882:	6a4c      	ldr	r4, [r1, #36]	; 0x24
    3884:	2c00      	cmp	r4, #0
    3886:	d100      	bne.n	388a <_Z7Upgradev+0x1e>
    3888:	e7fe      	b.n	3888 <_Z7Upgradev+0x1c>
    388a:	6a05      	ldr	r5, [r0, #32]
    388c:	4f17      	ldr	r7, [pc, #92]	; (38ec <_Z7Upgradev+0x80>)
    388e:	1c5e      	adds	r6, r3, #1
    3890:	5d7f      	ldrb	r7, [r7, r5]
    3892:	3c01      	subs	r4, #1
    3894:	701f      	strb	r7, [r3, #0]
    3896:	4b16      	ldr	r3, [pc, #88]	; (38f0 <_Z7Upgradev+0x84>)
    3898:	3501      	adds	r5, #1
    389a:	6205      	str	r5, [r0, #32]
    389c:	6244      	str	r4, [r0, #36]	; 0x24
    389e:	429e      	cmp	r6, r3
    38a0:	d318      	bcc.n	38d4 <_Z7Upgradev+0x68>
    38a2:	2580      	movs	r5, #128	; 0x80
    38a4:	4b13      	ldr	r3, [pc, #76]	; (38f4 <_Z7Upgradev+0x88>)
    38a6:	0016      	movs	r6, r2
    38a8:	685c      	ldr	r4, [r3, #4]
    38aa:	432c      	orrs	r4, r5
    38ac:	001d      	movs	r5, r3
    38ae:	605c      	str	r4, [r3, #4]
    38b0:	4c11      	ldr	r4, [pc, #68]	; (38f8 <_Z7Upgradev+0x8c>)
    38b2:	801c      	strh	r4, [r3, #0]
    38b4:	7d2c      	ldrb	r4, [r5, #20]
    38b6:	07e1      	lsls	r1, r4, #31
    38b8:	d5fc      	bpl.n	38b4 <_Z7Upgradev+0x48>
    38ba:	4667      	mov	r7, ip
    38bc:	2420      	movs	r4, #32
    38be:	cf02      	ldmia	r7!, {r1}
    38c0:	3c01      	subs	r4, #1
    38c2:	c602      	stmia	r6!, {r1}
    38c4:	2c00      	cmp	r4, #0
    38c6:	d1fa      	bne.n	38be <_Z7Upgradev+0x52>
    38c8:	4c0c      	ldr	r4, [pc, #48]	; (38fc <_Z7Upgradev+0x90>)
    38ca:	801c      	strh	r4, [r3, #0]
    38cc:	7d2b      	ldrb	r3, [r5, #20]
    38ce:	07db      	lsls	r3, r3, #31
    38d0:	d5fc      	bpl.n	38cc <_Z7Upgradev+0x60>
    38d2:	4666      	mov	r6, ip
    38d4:	4b0a      	ldr	r3, [pc, #40]	; (3900 <_Z7Upgradev+0x94>)
    38d6:	3201      	adds	r2, #1
    38d8:	429a      	cmp	r2, r3
    38da:	d001      	beq.n	38e0 <_Z7Upgradev+0x74>
    38dc:	0033      	movs	r3, r6
    38de:	e7cf      	b.n	3880 <_Z7Upgradev+0x14>
    38e0:	e7fe      	b.n	38e0 <_Z7Upgradev+0x74>
    38e2:	46c0      	nop			; (mov r8, r8)
    38e4:	20004e40 	andcs	r4, r0, r0, asr #28
    38e8:	200040c5 	andcs	r4, r0, r5, asr #1
    38ec:	20004e58 	andcs	r4, r0, r8, asr lr
    38f0:	20004145 	andcs	r4, r0, r5, asr #2
    38f4:	41004000 	mrsmi	r4, (UNDEF: 0)
    38f8:	ffffa544 			; <UNDEFINED> instruction: 0xffffa544
    38fc:	ffffa504 			; <UNDEFINED> instruction: 0xffffa504
    3900:	0003ff81 	andeq	pc, r3, r1, lsl #31

Now I'm no expert on arm32 assembler, but at first glance it seems that this is position-independent code that doesn't jump to anywhere outside the function.

Next I would have to copy the machine code for this function into SRAM and execute it from SRAM, something like:

void Invoke_Upgrade(void)
{
    static alignas(std::uint64_t) char unsigned buf[148u];  // 148 bytes of machine code

    memcpy( buf, reinterpret_cast<void const*>(&Upgrade), sizeof buf );

    void (*pUpgrade)(void) = reinterpret_cast<void(*)(void)>(static_cast<void const*>(buf));

    pUpgrade();  // This function cannot return
}

Might I have a working solution here to do a 256 kB firmware upgrade over i2c?

Can anyone think if there's anything I'm forgetting about here? If I disable all interrupts, might that stop the i2c communication from working?

By the way . . . I needn't have bothered to analyse the assembler for Wire::available and Wire::read, because the implementations are inside Wire.cpp on my hard disk:

int TwoWire::available(void)
{
  return rxBuffer.available();
}

int TwoWire::read(void)
{
  return rxBuffer.read_char();
}

The variable, rxBuffer, is of type 'arduino::RingBufferN<256>', so the two methods I need are:

template<int N>
int RingBufferN<N>::available()
{
  int delta = _iHead - _iTail;

  if(delta < 0)
    return N + delta;
  else
    return delta;
}

template<int N>
int RingBufferN<N>::read_char()
{
  if(_iTail == _iHead)
    return -1;

  uint8_t value = _aucBuffer[_iTail];
  _iTail = (uint32_t)(_iTail + 1) % N;

  return value;
}

Tomorrow I'll copy-paste these implementations into my Arduino sketch.

It's starting to look like I might get this working . . . I mean all I need to make sure of is two things:
(1) I don't break the i2c communication by disabling interrupts
(2) My function 'Upgrade' executes entirely from SRAM and doesn't jump to any code location in the Flash
(3) I pause for a few milliseconds after sending every complete page over i2c, to give the Arduino some time to write it to the Flash before reading from the i2c again

Anyone got any thoughts?

One other thing, I noticed that the assembler instructions at the end of the function were garbage:

    4b02:	e7fe      	b.n	4b02 <_Z7Upgradev+0x7e>
    4b04:	200040c5 	andcs	r4, r0, r5, asr #1
    4b08:	20004e40 	andcs	r4, r0, r0, asr #28
    4b0c:	20004e58 	andcs	r4, r0, r8, asr lr
    4b10:	0002e270 	andeq	lr, r2, r0, ror r2
    4b14:	20005338 	andcs	r5, r0, r8, lsr r3
    4b18:	20004145 	andcs	r4, r0, r5, asr #2
    4b1c:	41004000 	mrsmi	r4, (UNDEF: 0)
    4b20:	ffffa544 			; <UNDEFINED> instruction: 0xffffa544
    4b24:	ffffa504 			; <UNDEFINED> instruction: 0xffffa504
    4b28:	0003ff81 	andeq	pc, r3, r1, lsl #31

That first instruction is an infinite loop, it just keeps jumping back to itself. All of the instructions following it aren't real instructions at all -- they're data. These pieces of data are used by the preceding LDR instructions that add a constant to the program counter, such as:

    4a88:	4b1e      	ldr	r3, [pc, #120]	; (4b04 <_Z7Upgradev+0x80>)
    4a8e:	4d1e      	ldr	r5, [pc, #120]	; (4b08 <_Z7Upgradev+0x84>)
    4aa2:	481a      	ldr	r0, [pc, #104]	; (4b0c <_Z7Upgradev+0x88>)

So it seems that I truly have position-independent code here.
I just wonder if the i2c will continue to work after I disable interrupts.

Those are "immediate constants", loaded into registers via pc-relative addressing modes. pc-relative addresing has a relatively small range, so the compiler will typically put the constants used by a function immediately following the function. It's not very good at aggregating multiple uses of the same constant :frowning:

I think you can tell the compiler to produce "position independetb code", but not from within Arduino (which makes it difficult to use customer compiler options.)

You're not going to get "flat" or position-independent code if it uses functions from other libraries, without something like "link time optimization", which apparently has enough bugs in gcc-arm that it's not used.


Normally one would modify the bootloader to support I2C, and figure out some way to tell it that that's what it should do, rather than implementing self-updating applications.

Add -C to your objdump command, and it will de-mangle those C++ function names for you.

There's a pre-processor symbol RAMFUNC that will cause a function to be located in RAM rather than flash (RAM is faster, so this is sometimes done for performance or determinism reasons.) Unless you're also short of RAM, this is probably a better idea than copying the function to RAM yourself.

If the read/available commands are calling ringbuffer functions, that's a strong sign that I2C is using interrupts, and won't work without them. You'll probably have to reproduce or replace all of the WIRE library from Arduino, including code to stop whatever your application is doing with I2C.

I just need to make sure that the interrupt routine is in RAM. If I search through my sketch for uses of the variable named 'Wire', I see that four methods are invoked:

begin
onReceive
available
read

If I look inside the file 'Wire.h', I see that TwoWire inherits from HardwareI2C, and all four of these methods are virtual. This is good because it means I can either make my own new derived class, or edit the v-table of pre-existing object.

I'm trying to find where in the code the interrupt gets attached, but I haven't found it yet.

Found the interrupt routine inside 'Wire.cpp':

  void WIRE_IT_HANDLER(void) {
    Wire.onService();
  }

And I found the assembler:

000061e8 <SERCOM2_Handler>:
    61e8:	b510      	push	{r4, lr}
    61ea:	4802      	ldr	r0, [pc, #8]	; (61f4 <SERCOM2_Handler+0xc>)
    61ec:	f7ff ff71 	bl	60d2 <_ZN7arduino7TwoWire9onServiceEv>
    61f0:	bd10      	pop	{r4, pc}
    61f2:	46c0      	nop			; (mov r8, r8)
    61f4:	20004e40 	andcs	r4, r0, r0, asr #28

That last instruction isn't a real instruction, it's the data needed by the line 4 lines before it.
So now I need to find the assembler for '_ZN7arduino7TwoWire9onServiceEv':

000060d2 <_ZN7arduino7TwoWire9onServiceEv>:
    60d2:	b570      	push	{r4, r5, r6, lr}
    60d4:	0004      	movs	r4, r0
    60d6:	6900      	ldr	r0, [r0, #16]
    60d8:	f000 fbf0 	bl	68bc <_ZN6SERCOM11isSlaveWIREEv>
    60dc:	2800      	cmp	r0, #0
    60de:	d015      	beq.n	610c <_ZN7arduino7TwoWire9onServiceEv+0x3a>
    60e0:	6920      	ldr	r0, [r4, #16]
    60e2:	f000 fc02 	bl	68ea <_ZN6SERCOM18isStopDetectedWIREEv>
    60e6:	2800      	cmp	r0, #0
    60e8:	d011      	beq.n	610e <_ZN7arduino7TwoWire9onServiceEv+0x3c>
    60ea:	6920      	ldr	r0, [r4, #16]
    60ec:	f000 fb5e 	bl	67ac <_ZN6SERCOM17prepareAckBitWIREEv>
    60f0:	2103      	movs	r1, #3
    60f2:	6920      	ldr	r0, [r4, #16]
    60f4:	f000 fb64 	bl	67c0 <_ZN6SERCOM22prepareCommandBitsWireEh>
    60f8:	238e      	movs	r3, #142	; 0x8e
    60fa:	009b      	lsls	r3, r3, #2
    60fc:	58e5      	ldr	r5, [r4, r3]
    60fe:	2d00      	cmp	r5, #0
    6100:	d135      	bne.n	616e <_ZN7arduino7TwoWire9onServiceEv+0x9c>
    6102:	2300      	movs	r3, #0
    6104:	34fc      	adds	r4, #252	; 0xfc
    6106:	61e3      	str	r3, [r4, #28]
    6108:	6223      	str	r3, [r4, #32]
    610a:	6263      	str	r3, [r4, #36]	; 0x24
    610c:	bd70      	pop	{r4, r5, r6, pc}
    610e:	6920      	ldr	r0, [r4, #16]
    6110:	f000 fbf5 	bl	68fe <_ZN6SERCOM14isAddressMatchEv>
    6114:	2800      	cmp	r0, #0
    6116:	d009      	beq.n	612c <_ZN7arduino7TwoWire9onServiceEv+0x5a>
    6118:	6920      	ldr	r0, [r4, #16]
    611a:	f000 fbeb 	bl	68f4 <_ZN6SERCOM21isRestartDetectedWIREEv>
    611e:	2800      	cmp	r0, #0
    6120:	d004      	beq.n	612c <_ZN7arduino7TwoWire9onServiceEv+0x5a>
    6122:	6920      	ldr	r0, [r4, #16]
    6124:	f000 fbf0 	bl	6908 <_ZN6SERCOM25isMasterReadOperationWIREEv>
    6128:	2800      	cmp	r0, #0
    612a:	d0de      	beq.n	60ea <_ZN7arduino7TwoWire9onServiceEv+0x18>
    612c:	6920      	ldr	r0, [r4, #16]
    612e:	f000 fbe6 	bl	68fe <_ZN6SERCOM14isAddressMatchEv>
    6132:	2800      	cmp	r0, #0
    6134:	d121      	bne.n	617a <_ZN7arduino7TwoWire9onServiceEv+0xa8>
    6136:	6920      	ldr	r0, [r4, #16]
    6138:	f000 fbd2 	bl	68e0 <_ZN6SERCOM15isDataReadyWIREEv>
    613c:	2800      	cmp	r0, #0
    613e:	d0e5      	beq.n	610c <_ZN7arduino7TwoWire9onServiceEv+0x3a>
    6140:	6920      	ldr	r0, [r4, #16]
    6142:	f000 fbe1 	bl	6908 <_ZN6SERCOM25isMasterReadOperationWIREEv>
    6146:	2800      	cmp	r0, #0
    6148:	d033      	beq.n	61b2 <_ZN7arduino7TwoWire9onServiceEv+0xe0>
    614a:	2388      	movs	r3, #136	; 0x88
    614c:	009b      	lsls	r3, r3, #2
    614e:	18e3      	adds	r3, r4, r3
    6150:	68db      	ldr	r3, [r3, #12]
    6152:	21ff      	movs	r1, #255	; 0xff
    6154:	2b00      	cmp	r3, #0
    6156:	d005      	beq.n	6164 <_ZN7arduino7TwoWire9onServiceEv+0x92>
    6158:	0020      	movs	r0, r4
    615a:	3025      	adds	r0, #37	; 0x25
    615c:	30ff      	adds	r0, #255	; 0xff
    615e:	f7ff ff6e 	bl	603e <_ZN7arduino11RingBufferNILi256EE9read_charEv>
    6162:	b2c1      	uxtb	r1, r0
    6164:	6920      	ldr	r0, [r4, #16]
    6166:	f000 fb9b 	bl	68a0 <_ZN6SERCOM17sendDataSlaveWIREEh>
    616a:	75a0      	strb	r0, [r4, #22]
    616c:	e7ce      	b.n	610c <_ZN7arduino7TwoWire9onServiceEv+0x3a>
    616e:	6823      	ldr	r3, [r4, #0]
    6170:	0020      	movs	r0, r4
    6172:	691b      	ldr	r3, [r3, #16]
    6174:	4798      	blx	r3
    6176:	47a8      	blx	r5
    6178:	e7c3      	b.n	6102 <_ZN7arduino7TwoWire9onServiceEv+0x30>
    617a:	6920      	ldr	r0, [r4, #16]
    617c:	f000 fb16 	bl	67ac <_ZN6SERCOM17prepareAckBitWIREEv>
    6180:	6920      	ldr	r0, [r4, #16]
    6182:	2103      	movs	r1, #3
    6184:	f000 fb1c 	bl	67c0 <_ZN6SERCOM22prepareCommandBitsWireEh>
    6188:	6920      	ldr	r0, [r4, #16]
    618a:	f000 fbbd 	bl	6908 <_ZN6SERCOM25isMasterReadOperationWIREEv>
    618e:	2800      	cmp	r0, #0
    6190:	d0bc      	beq.n	610c <_ZN7arduino7TwoWire9onServiceEv+0x3a>
    6192:	2388      	movs	r3, #136	; 0x88
    6194:	2200      	movs	r2, #0
    6196:	009b      	lsls	r3, r3, #2
    6198:	18e3      	adds	r3, r4, r3
    619a:	605a      	str	r2, [r3, #4]
    619c:	609a      	str	r2, [r3, #8]
    619e:	60da      	str	r2, [r3, #12]
    61a0:	2301      	movs	r3, #1
    61a2:	75a3      	strb	r3, [r4, #22]
    61a4:	238d      	movs	r3, #141	; 0x8d
    61a6:	009b      	lsls	r3, r3, #2
    61a8:	58e3      	ldr	r3, [r4, r3]
    61aa:	4293      	cmp	r3, r2
    61ac:	d0ae      	beq.n	610c <_ZN7arduino7TwoWire9onServiceEv+0x3a>
    61ae:	4798      	blx	r3
    61b0:	e7ac      	b.n	610c <_ZN7arduino7TwoWire9onServiceEv+0x3a>
    61b2:	0023      	movs	r3, r4
    61b4:	33fc      	adds	r3, #252	; 0xfc
    61b6:	6a5a      	ldr	r2, [r3, #36]	; 0x24
    61b8:	2380      	movs	r3, #128	; 0x80
    61ba:	6920      	ldr	r0, [r4, #16]
    61bc:	005b      	lsls	r3, r3, #1
    61be:	429a      	cmp	r2, r3
    61c0:	d106      	bne.n	61d0 <_ZN7arduino7TwoWire9onServiceEv+0xfe>
    61c2:	f000 faeb 	bl	679c <_ZN6SERCOM18prepareNackBitWIREEv>
    61c6:	2103      	movs	r1, #3
    61c8:	6920      	ldr	r0, [r4, #16]
    61ca:	f000 faf9 	bl	67c0 <_ZN6SERCOM22prepareCommandBitsWireEh>
    61ce:	e79d      	b.n	610c <_ZN7arduino7TwoWire9onServiceEv+0x3a>
    61d0:	f000 fb9f 	bl	6912 <_ZN6SERCOM12readDataWIREEv>
    61d4:	0001      	movs	r1, r0
    61d6:	0020      	movs	r0, r4
    61d8:	3018      	adds	r0, #24
    61da:	f7ff fec2 	bl	5f62 <_ZN7arduino11RingBufferNILi256EE10store_charEh>
    61de:	6920      	ldr	r0, [r4, #16]
    61e0:	f000 fae4 	bl	67ac <_ZN6SERCOM17prepareAckBitWIREEv>
    61e4:	e7ef      	b.n	61c6 <_ZN7arduino7TwoWire9onServiceEv+0xf4>
	...

We've got lots of function calls in here, specifically we have jumps to code addresses less than 0x40000, so I somehow need to put these methods in SRAM.

The interrupt routine just calls 'Wire.onService()', so if I disable the interrupt then I can just invoke 'onService' by myself in a loop (assuming this won't cause any timing issues).

So here's the C++ code for 'Wire::onService' taken from 'Wire.cpp':


void TwoWire::onService(void)
{
  if ( sercom->isSlaveWIRE() )
  {
    if(sercom->isStopDetectedWIRE() || 
        (sercom->isAddressMatch() && sercom->isRestartDetectedWIRE() && !sercom->isMasterReadOperationWIRE())) //Stop or Restart detected
    {
      sercom->prepareAckBitWIRE();
      sercom->prepareCommandBitsWire(0x03);

      //Calling onReceiveCallback, if exists
      if(onReceiveCallback)
      {
        onReceiveCallback(available());
      }
      
      rxBuffer.clear();
    }
    else if(sercom->isAddressMatch())  //Address Match
    {
      sercom->prepareAckBitWIRE();
      sercom->prepareCommandBitsWire(0x03);

      if(sercom->isMasterReadOperationWIRE()) //Is a request ?
      {
        txBuffer.clear();

        transmissionBegun = true;

        //Calling onRequestCallback, if exists
        if(onRequestCallback)
        {
          onRequestCallback();
        }
      }
    }
    else if(sercom->isDataReadyWIRE())
    {
      if (sercom->isMasterReadOperationWIRE())
      {
        uint8_t c = 0xff;

        if( txBuffer.available() ) {
          c = txBuffer.read_char();
        }

        transmissionBegun = sercom->sendDataSlaveWIRE(c);
      } else { //Received data
        if (rxBuffer.isFull()) {
          sercom->prepareNackBitWIRE(); 
        } else {
          //Store data
          rxBuffer.store_char(sercom->readDataWIRE());

          sercom->prepareAckBitWIRE(); 
        }

        sercom->prepareCommandBitsWire(0x03);
      }
    }
  }
}

If I replace all the invocations of 'sercom' methods with inline code (copy-pasted from SERCOM.cpp), then it looks like this:

struct WireDerived : decltype(Wire) { void onService(void); };

void WireDerived::onService(void)
{
    decltype(this->sercom->sercom) &srcm = this->sercom->sercom;

    if ( I2C_SLAVE_OPERATION != srcm->I2CS.CTRLA.bit.MODE ) return;

    if ( (srcm->I2CS.INTFLAG.bit.PREC) || ((srcm->I2CS.STATUS.bit.SR) && !(srcm->I2CS.STATUS.bit.DIR))) //Stop or Restart detected
    {
        if ( I2C_MASTER_OPERATION == srcm->I2CS.CTRLA.bit.MODE ) srcm->I2CM.CTRLB.bit.ACKACT = 0;
                                                              else srcm->I2CS.CTRLB.bit.ACKACT = 0;

        if( I2C_MASTER_OPERATION == srcm->I2CS.CTRLA.bit.MODE )
        {
            srcm->I2CM.CTRLB.bit.CMD = 0x03;

            while( srcm->I2CM.SYNCBUSY.bit.SYSOP );  // Waiting for synchronization
        }
        else
        {
            srcm->I2CS.CTRLB.bit.CMD = 0x03;
        }

        //Calling onReceiveCallback, if exists
        if ( onReceiveCallback )
        {
            onReceiveCallback(available());
        }

        rxBuffer.clear();
    }
    else if ( srcm->I2CS.INTFLAG.bit.AMATCH )  //Address Match
    {
        if ( I2C_MASTER_OPERATION == srcm->I2CS.CTRLA.bit.MODE  ) srcm->I2CM.CTRLB.bit.ACKACT = 0;
                                                               else srcm->I2CS.CTRLB.bit.ACKACT = 0;

        if ( I2C_MASTER_OPERATION == srcm->I2CS.CTRLA.bit.MODE )
        {
            srcm->I2CM.CTRLB.bit.CMD = 0x03;
            while ( srcm->I2CM.SYNCBUSY.bit.SYSOP ); // Waiting for synchronization
        }
        else
        {
            srcm->I2CS.CTRLB.bit.CMD = 0x03;
        }

        if ( srcm->I2CS.STATUS.bit.DIR ) //Is a request ?
        {
            txBuffer.clear();

            transmissionBegun = true;

            //Calling onRequestCallback, if exists
            if(onRequestCallback)
            {
                onRequestCallback();
            }
        }
    }
    else if( srcm->I2CS.INTFLAG.bit.DRDY )  // Data is ready
    {
        if ( I2C_MASTER_OPERATION == srcm->I2CS.CTRLA.bit.MODE )
        {
            uint8_t c = 0xff;

            if( txBuffer.available() ) c = txBuffer.read_char();

            //Send data
            srcm->I2CS.DATA.bit.DATA = c;

            //Problems on line? nack received?
            if( !srcm->I2CS.INTFLAG.bit.DRDY || srcm->I2CS.STATUS.bit.RXNACK )
                transmissionBegun = false;
            else
                transmissionBegun = true;
        }
        else
        {
            //Received data
            if ( rxBuffer.isFull() )
            {
                if( I2C_MASTER_OPERATION == srcm->I2CS.CTRLA.bit.MODE ) srcm->I2CM.CTRLB.bit.ACKACT = 1;
                                                                     else srcm->I2CS.CTRLB.bit.ACKACT = 1;
            }
            else
            {
                //Store data

                char retval;

                if( I2C_MASTER_OPERATION == srcm->I2CS.CTRLA.bit.MODE )
                {
                    while( srcm->I2CM.INTFLAG.bit.SB == 0 && srcm->I2CM.INTFLAG.bit.MB == 0 ); // Waiting complete receive
                    retval = srcm->I2CM.DATA.bit.DATA;
                }
                else
                {
                    retval = srcm->I2CS.DATA.reg;
                }

                rxBuffer.store_char(retval);

                if( I2C_MASTER_OPERATION == srcm->I2CS.CTRLA.bit.MODE ) srcm->I2CM.CTRLB.bit.ACKACT = 0;
                                                                     else srcm->I2CS.CTRLB.bit.ACKACT = 0;
            }

            if ( I2C_MASTER_OPERATION == srcm->I2CS.CTRLA.bit.MODE )
            {
                srcm->I2CM.CTRLB.bit.CMD = 0x03;
                while(srcm->I2CM.SYNCBUSY.bit.SYSOP);  // Waiting for synchronization
            }
            else
            {
                srcm->I2CS.CTRLB.bit.CMD = 0x03;
            }
        }
    }
}

If I compile this, I get the following assembler:

0000237c <WireDerived::onService()>:
    237c:	b570      	push	{r4, r5, r6, lr}
    237e:	6902      	ldr	r2, [r0, #16]
    2380:	0004      	movs	r4, r0
    2382:	6813      	ldr	r3, [r2, #0]
    2384:	6819      	ldr	r1, [r3, #0]
    2386:	06c9      	lsls	r1, r1, #27
    2388:	0f49      	lsrs	r1, r1, #29
    238a:	2904      	cmp	r1, #4
    238c:	d15c      	bne.n	2448 <WireDerived::onService()+0xcc>
    238e:	7e19      	ldrb	r1, [r3, #24]
    2390:	07c9      	lsls	r1, r1, #31
    2392:	d436      	bmi.n	2402 <WireDerived::onService()+0x86>
    2394:	8b59      	ldrh	r1, [r3, #26]
    2396:	06c9      	lsls	r1, r1, #27
    2398:	d502      	bpl.n	23a0 <WireDerived::onService()+0x24>
    239a:	8b59      	ldrh	r1, [r3, #26]
    239c:	0709      	lsls	r1, r1, #28
    239e:	d530      	bpl.n	2402 <WireDerived::onService()+0x86>
    23a0:	7e19      	ldrb	r1, [r3, #24]
    23a2:	0789      	lsls	r1, r1, #30
    23a4:	d455      	bmi.n	2452 <WireDerived::onService()+0xd6>
    23a6:	7e19      	ldrb	r1, [r3, #24]
    23a8:	0749      	lsls	r1, r1, #29
    23aa:	d54d      	bpl.n	2448 <WireDerived::onService()+0xcc>
    23ac:	6819      	ldr	r1, [r3, #0]
    23ae:	06c9      	lsls	r1, r1, #27
    23b0:	0f49      	lsrs	r1, r1, #29
    23b2:	2905      	cmp	r1, #5
    23b4:	d000      	beq.n	23b8 <WireDerived::onService()+0x3c>
    23b6:	e07d      	b.n	24b4 <WireDerived::onService()+0x138>
    23b8:	2388      	movs	r3, #136	; 0x88
    23ba:	009b      	lsls	r3, r3, #2
    23bc:	18e3      	adds	r3, r4, r3
    23be:	68d9      	ldr	r1, [r3, #12]
    23c0:	25ff      	movs	r5, #255	; 0xff
    23c2:	2900      	cmp	r1, #0
    23c4:	d010      	beq.n	23e8 <WireDerived::onService()+0x6c>
    23c6:	68d9      	ldr	r1, [r3, #12]
    23c8:	2900      	cmp	r1, #0
    23ca:	d100      	bne.n	23ce <WireDerived::onService()+0x52>
    23cc:	e06d      	b.n	24aa <WireDerived::onService()+0x12e>
    23ce:	2092      	movs	r0, #146	; 0x92
    23d0:	6899      	ldr	r1, [r3, #8]
    23d2:	0040      	lsls	r0, r0, #1
    23d4:	1861      	adds	r1, r4, r1
    23d6:	5c08      	ldrb	r0, [r1, r0]
    23d8:	6899      	ldr	r1, [r3, #8]
    23da:	3101      	adds	r1, #1
    23dc:	4029      	ands	r1, r5
    23de:	6099      	str	r1, [r3, #8]
    23e0:	68d9      	ldr	r1, [r3, #12]
    23e2:	3901      	subs	r1, #1
    23e4:	60d9      	str	r1, [r3, #12]
    23e6:	b2c5      	uxtb	r5, r0
    23e8:	6813      	ldr	r3, [r2, #0]
    23ea:	3328      	adds	r3, #40	; 0x28
    23ec:	701d      	strb	r5, [r3, #0]
    23ee:	6813      	ldr	r3, [r2, #0]
    23f0:	7e1a      	ldrb	r2, [r3, #24]
    23f2:	0752      	lsls	r2, r2, #29
    23f4:	d55c      	bpl.n	24b0 <WireDerived::onService()+0x134>
    23f6:	8b5b      	ldrh	r3, [r3, #26]
    23f8:	075b      	lsls	r3, r3, #29
    23fa:	d459      	bmi.n	24b0 <WireDerived::onService()+0x134>
    23fc:	2301      	movs	r3, #1
    23fe:	75a3      	strb	r3, [r4, #22]
    2400:	e022      	b.n	2448 <WireDerived::onService()+0xcc>
    2402:	6819      	ldr	r1, [r3, #0]
    2404:	484d      	ldr	r0, [pc, #308]	; (253c <WireDerived::onService()+0x1c0>)
    2406:	6859      	ldr	r1, [r3, #4]
    2408:	4001      	ands	r1, r0
    240a:	6059      	str	r1, [r3, #4]
    240c:	23c0      	movs	r3, #192	; 0xc0
    240e:	6811      	ldr	r1, [r2, #0]
    2410:	029b      	lsls	r3, r3, #10
    2412:	6808      	ldr	r0, [r1, #0]
    2414:	06c0      	lsls	r0, r0, #27
    2416:	0f40      	lsrs	r0, r0, #29
    2418:	2805      	cmp	r0, #5
    241a:	d116      	bne.n	244a <WireDerived::onService()+0xce>
    241c:	6848      	ldr	r0, [r1, #4]
    241e:	4303      	orrs	r3, r0
    2420:	604b      	str	r3, [r1, #4]
    2422:	6812      	ldr	r2, [r2, #0]
    2424:	69d3      	ldr	r3, [r2, #28]
    2426:	075b      	lsls	r3, r3, #29
    2428:	d4fc      	bmi.n	2424 <WireDerived::onService()+0xa8>
    242a:	238e      	movs	r3, #142	; 0x8e
    242c:	009b      	lsls	r3, r3, #2
    242e:	58e5      	ldr	r5, [r4, r3]
    2430:	2d00      	cmp	r5, #0
    2432:	d004      	beq.n	243e <WireDerived::onService()+0xc2>
    2434:	6823      	ldr	r3, [r4, #0]
    2436:	0020      	movs	r0, r4
    2438:	691b      	ldr	r3, [r3, #16]
    243a:	4798      	blx	r3
    243c:	47a8      	blx	r5
    243e:	2300      	movs	r3, #0
    2440:	34fc      	adds	r4, #252	; 0xfc
    2442:	61e3      	str	r3, [r4, #28]
    2444:	6223      	str	r3, [r4, #32]
    2446:	6263      	str	r3, [r4, #36]	; 0x24
    2448:	bd70      	pop	{r4, r5, r6, pc}
    244a:	684a      	ldr	r2, [r1, #4]
    244c:	4313      	orrs	r3, r2
    244e:	604b      	str	r3, [r1, #4]
    2450:	e7eb      	b.n	242a <WireDerived::onService()+0xae>
    2452:	6819      	ldr	r1, [r3, #0]
    2454:	4839      	ldr	r0, [pc, #228]	; (253c <WireDerived::onService()+0x1c0>)
    2456:	6859      	ldr	r1, [r3, #4]
    2458:	4001      	ands	r1, r0
    245a:	6059      	str	r1, [r3, #4]
    245c:	23c0      	movs	r3, #192	; 0xc0
    245e:	6811      	ldr	r1, [r2, #0]
    2460:	029b      	lsls	r3, r3, #10
    2462:	6808      	ldr	r0, [r1, #0]
    2464:	06c0      	lsls	r0, r0, #27
    2466:	0f40      	lsrs	r0, r0, #29
    2468:	2805      	cmp	r0, #5
    246a:	d11a      	bne.n	24a2 <WireDerived::onService()+0x126>
    246c:	6848      	ldr	r0, [r1, #4]
    246e:	4303      	orrs	r3, r0
    2470:	604b      	str	r3, [r1, #4]
    2472:	6811      	ldr	r1, [r2, #0]
    2474:	69cb      	ldr	r3, [r1, #28]
    2476:	075b      	lsls	r3, r3, #29
    2478:	d4fc      	bmi.n	2474 <WireDerived::onService()+0xf8>
    247a:	6813      	ldr	r3, [r2, #0]
    247c:	8b5b      	ldrh	r3, [r3, #26]
    247e:	071b      	lsls	r3, r3, #28
    2480:	d5e2      	bpl.n	2448 <WireDerived::onService()+0xcc>
    2482:	2388      	movs	r3, #136	; 0x88
    2484:	2200      	movs	r2, #0
    2486:	009b      	lsls	r3, r3, #2
    2488:	18e3      	adds	r3, r4, r3
    248a:	605a      	str	r2, [r3, #4]
    248c:	609a      	str	r2, [r3, #8]
    248e:	60da      	str	r2, [r3, #12]
    2490:	2301      	movs	r3, #1
    2492:	75a3      	strb	r3, [r4, #22]
    2494:	238d      	movs	r3, #141	; 0x8d
    2496:	009b      	lsls	r3, r3, #2
    2498:	58e3      	ldr	r3, [r4, r3]
    249a:	4293      	cmp	r3, r2
    249c:	d0d4      	beq.n	2448 <WireDerived::onService()+0xcc>
    249e:	4798      	blx	r3
    24a0:	e7d2      	b.n	2448 <WireDerived::onService()+0xcc>
    24a2:	6848      	ldr	r0, [r1, #4]
    24a4:	4303      	orrs	r3, r0
    24a6:	604b      	str	r3, [r1, #4]
    24a8:	e7e7      	b.n	247a <WireDerived::onService()+0xfe>
    24aa:	2001      	movs	r0, #1
    24ac:	4240      	negs	r0, r0
    24ae:	e79a      	b.n	23e6 <WireDerived::onService()+0x6a>
    24b0:	2300      	movs	r3, #0
    24b2:	e7a4      	b.n	23fe <WireDerived::onService()+0x82>
    24b4:	0021      	movs	r1, r4
    24b6:	2080      	movs	r0, #128	; 0x80
    24b8:	31fc      	adds	r1, #252	; 0xfc
    24ba:	6a4d      	ldr	r5, [r1, #36]	; 0x24
    24bc:	0040      	lsls	r0, r0, #1
    24be:	4285      	cmp	r5, r0
    24c0:	d115      	bne.n	24ee <WireDerived::onService()+0x172>
    24c2:	2180      	movs	r1, #128	; 0x80
    24c4:	6818      	ldr	r0, [r3, #0]
    24c6:	6858      	ldr	r0, [r3, #4]
    24c8:	02c9      	lsls	r1, r1, #11
    24ca:	4301      	orrs	r1, r0
    24cc:	6059      	str	r1, [r3, #4]
    24ce:	23c0      	movs	r3, #192	; 0xc0
    24d0:	6811      	ldr	r1, [r2, #0]
    24d2:	029b      	lsls	r3, r3, #10
    24d4:	6808      	ldr	r0, [r1, #0]
    24d6:	06c0      	lsls	r0, r0, #27
    24d8:	0f40      	lsrs	r0, r0, #29
    24da:	2805      	cmp	r0, #5
    24dc:	d12a      	bne.n	2534 <WireDerived::onService()+0x1b8>
    24de:	6848      	ldr	r0, [r1, #4]
    24e0:	4303      	orrs	r3, r0
    24e2:	604b      	str	r3, [r1, #4]
    24e4:	6812      	ldr	r2, [r2, #0]
    24e6:	69d3      	ldr	r3, [r2, #28]
    24e8:	075b      	lsls	r3, r3, #29
    24ea:	d4fc      	bmi.n	24e6 <WireDerived::onService()+0x16a>
    24ec:	e7ac      	b.n	2448 <WireDerived::onService()+0xcc>
    24ee:	6818      	ldr	r0, [r3, #0]
    24f0:	06c0      	lsls	r0, r0, #27
    24f2:	0f40      	lsrs	r0, r0, #29
    24f4:	2805      	cmp	r0, #5
    24f6:	d105      	bne.n	2504 <WireDerived::onService()+0x188>
    24f8:	7e18      	ldrb	r0, [r3, #24]
    24fa:	0780      	lsls	r0, r0, #30
    24fc:	d402      	bmi.n	2504 <WireDerived::onService()+0x188>
    24fe:	7e18      	ldrb	r0, [r3, #24]
    2500:	07c0      	lsls	r0, r0, #31
    2502:	d5f9      	bpl.n	24f8 <WireDerived::onService()+0x17c>
    2504:	2080      	movs	r0, #128	; 0x80
    2506:	3328      	adds	r3, #40	; 0x28
    2508:	781b      	ldrb	r3, [r3, #0]
    250a:	6a4d      	ldr	r5, [r1, #36]	; 0x24
    250c:	b2db      	uxtb	r3, r3
    250e:	0040      	lsls	r0, r0, #1
    2510:	4285      	cmp	r5, r0
    2512:	d009      	beq.n	2528 <WireDerived::onService()+0x1ac>
    2514:	69c8      	ldr	r0, [r1, #28]
    2516:	1824      	adds	r4, r4, r0
    2518:	7623      	strb	r3, [r4, #24]
    251a:	69cb      	ldr	r3, [r1, #28]
    251c:	3301      	adds	r3, #1
    251e:	b2db      	uxtb	r3, r3
    2520:	61cb      	str	r3, [r1, #28]
    2522:	6a4b      	ldr	r3, [r1, #36]	; 0x24
    2524:	3301      	adds	r3, #1
    2526:	624b      	str	r3, [r1, #36]	; 0x24
    2528:	6813      	ldr	r3, [r2, #0]
    252a:	4804      	ldr	r0, [pc, #16]	; (253c <WireDerived::onService()+0x1c0>)
    252c:	6819      	ldr	r1, [r3, #0]
    252e:	6859      	ldr	r1, [r3, #4]
    2530:	4001      	ands	r1, r0
    2532:	e7cb      	b.n	24cc <WireDerived::onService()+0x150>
    2534:	684a      	ldr	r2, [r1, #4]
    2536:	4313      	orrs	r3, r2
    2538:	604b      	str	r3, [r1, #4]
    253a:	e785      	b.n	2448 <WireDerived::onService()+0xcc>
    253c:	fffbffff 			; <UNDEFINED> instruction: 0xfffbffff

It looks like I've converted 'TwoWire::onService' into position-independent code, so now in my own sketch, after I've disabled the interrupts, I just call 'onService' as follows inside a loop:

    static_cast<WireDerived&>(Wire).onService();

This will work fine because the 'onService' method is not virtual -- otherwise I would have to manually edit the v-table for the 'Wire' variable.

I'm getting closer to getting this working.

You're going to a lot of trouble to move existing interrupt-driven I2C code into RAM, when it might be easier to write entirely new polled I2C code that could be put into RAM with the RAMFUNC macro I mentioned.

I can't use 'RAMFUNC' because I don't have enough SRAM to spare.
So instead, I have all of the mutable global variables placed inside a struct, like:

struct GlobalsStruct {
    array< array< bitset<32u>, 256u >, 2u > bitmap{};
    char status[128u];
    /* other stuff in here */
} globals;

And then I have a second struct with the stuff I need for the firmware upgrade:

struct UpgradeStruct {
    alignas(std::uint32_t) char unsigned page_in_sram[page_size];
    alignas(std::uint32_t) char unsigned machine_code[     148u];  // 148 bytes of machine code
};

And then when I go to perform the firmware upgrade, I do:

UpgradeStruct &us = *static_cast<UpgradeStruct*>(static_cast<void*>(&globals));

And then I use 'memcpy' to copy the function from Flash into 'us.machine_code'. So I'm overwriting all the global variables with the position-independent function I need. (By the way I tried using a 'union' at first for this purpose, but the compiler wouldn't allow it -- I think because some of the globals have a user-defined constructor).

Just doing some timing testing here.... it looks like I'll be able to send a 152 kB firmware file over i2c to the Arduino in about 76 seconds.

Let me give a little more context. So I have a desktop PC running a program that loads the Arduino firmware file and sends it out the COM port to a Texas Instruments microcontroller.

So the Texas Instruments microcontroller is receiving these bytes over the RS232 connection, and it's forwarding these bytes out on an I2C line to the Arduino. The Arduino is receiving bytes over I2C and writing them to the Flash one page at a time.

So in this setup, I was able to send the entire firmware file from the PC in 76 seconds. I might get it faster than that though.

Okay I will be able to check that the firmware upgrade went okay by taking the MD5 hash of the entire firmware, as described here:

When the i2c interrupt fires, it calls Sercom2_Handler. Does anyone know how I can change the address of the interrupt routine? I need to change it to an address in SRAM.

I've tried reading this document but I don't see how I can set the address of the interrupt routine:

The interrupt vectors are normally off in flash (and thus unchangeable.)
In theory, you can allocate a new vector table in RAM, and set SCB->VTOR to point there, and get changeable vectors. There are rather fierce alignment requirements for the vector table (at least 32words for Cortex-M0; I think 64 words for SAMD.)

I need to see the bigger picture here. . . do you know where I can get the source code for this bootloader:

https://github.com/arduino/ArduinoCore-samd/tree/master/bootloaders/mkrzero

Even if it's written in Assembler, I still wanna see it. (Yes I realise I can disassemble the bin file but I want to see how it was written with loop labels and so forth).

in the zero folder

I just had a new idea. Previously I said that I can't just store the firmware upgrade on the second half of the Flash (i.e. from 128k to 256k), because the firmware takes up more than 50% of the Flash.

But here's what I can do:

  • Write a very small 'Upgrader' sketch that can perform the firmware upgrade over i2c (let's say it's 16 kB).
  • Copy the 'Upgrader' sketch into the Flash at address 0x3c000 (near the end)
  • Edit the bytes located at address 0x2000 so that there's an immediate jump to 0x3c000
  • Reset the microcontroller
  • The microcontroller is now running the 'Upgrader' sketch located at 0x3c000
  • Read the firmware in over i2c and store it at address 0x2000
  • Reset the microcontroller

This will work, right? Will I need to power-cycle the microcontroller to get it to reset, or is there a CPU instruction I can issue to force a reset?