M0AGX / LB9MG

Amateur radio and embedded systems

Wiznet W7500 flash programming

W7500 is a nice little MCU from Wiznet with a built-in hardware TCP/IP stack and Ethernet MAC. You can get quite far with your IoT application on a Cortex-M0 running at 48 MHz when the networking is offloaded in hardware. The device begins to show its age in 2023 (it was released around 2016). There is no crypto acceleration whatsoever, no AES. Only an RNG is available. I think that modern TLS is practically impossible but some lighter cryptography should be possible. Nevertheless, if you can put the device on an isolated VLAN the chip can still be useful today.

There is also a companion device W7500P with also the PHY built-in so all that you need to implement networking on your board is this chip and an Ethernet jack with magnetics (plus the passives and sensors/actuators that would be needed anyway).

The documentation could always be better but the examples really hit a sweet spot. You don't have to download gigabytes of IDEs and SDKs to build a simple project with make.

The only place I hit a snag was programming of the flash. The device has some extra sectors that can be used for example to store settings. Part of the flash handling functions are in ROM and all that you get is this piece of code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void FLASH_IAP(uint32_t id, uint32_t dst_addr, uint8_t* src_addr, uint32_t size)
{
    uint32_t temp_interrupt;

    /* Backup Interrupt Set Pending Register */
    temp_interrupt = (NVIC->ISPR[0]);
    (NVIC->ISPR[0]) = (uint32_t) 0xFFFFFFFF;

    /* Call IAP Function */
    ((void (*)(uint32_t, uint32_t, uint8_t*, uint32_t)) IAP_ENTRY)(id, dst_addr, src_addr, size);

    /* Restore Interrupt Set Pending Register */
    (NVIC->ISPR[0]) = temp_interrupt;
}

What does it do?! It is just a function call to something magical at the address IAP_ENTRY. In my case the CPU crashed with a hard fault when this code was called (or shortly after...). Additionally, there is an erratum for the flash that requires clocking from the internal RC oscillator when executing flash operations.

A call to ROM... is not the easiest thing to debug. I started by switching to the internal clock but the problem persisted. Then I had a look around the lines in FLASH_IAP() that modify the NVIC. I think that the original author tried to block the interrupts when the ROM function is called. In Cortex-M0 the ISPR is the interrupt set pending register. It can be read to find out pending interrupts but it can only be written with ones to set a particular interrupt. There is a companion ICPR clear register. I don't think the intent was to make all interrupts active and then call the ROM.

I hoped that the ROM function did not do anything with the interrupts so I just wrapped the call with __disable_irq() and __enable_irq() like this:

1
2
3
4
5
6
7
8
9
void FLASH_IAP(uint32_t id, uint32_t dst_addr, uint8_t* src_addr, uint32_t size)
{
    __disable_irq();

    /* Call IAP Function */
    ((void (*)(uint32_t, uint32_t, uint8_t*, uint32_t)) IAP_ENTRY)(id, dst_addr, src_addr, size);

    __enable_irq();
}

To my (not) surprise the code worked just fine. Flash was modified as I expected it to be. The last step was to add the clock switch erratum. I decided to switch the core clock to internal RC without reconfiguring the PLL. The final code is:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void FLASH_IAP(uint32_t id, uint32_t dst_addr, uint8_t* src_addr, uint32_t size)
{
    __disable_irq();
    CRG_FCLK_SourceSelect(CRG_RCLK); // Erratum #3 core clock is internal RC

    /* Call IAP Function */
    ((void (*)(uint32_t, uint32_t, uint8_t*, uint32_t)) IAP_ENTRY)(id, dst_addr, src_addr, size);

    CRG_FCLK_SourceSelect(CRG_MCLK); // Erratum #3 core clock is PLL
    __enable_irq();
}