August 5, 2023

RP2040 - first blink the LED using C code

Hey, wait! At least that is what you should say. Aren't we supposed to be doing an assembly language thing here?

This is a step along the way. For me, it is about getting comfortable with the RP2040 and getting my toolchain set up. You could also view the C code I am about to show you as "pseudo-code" for assembly language code we will soon be writing. And, to boot, we can let the C compiler show us how to write assembly code, which is never a bad idea.

Really what we are doing is avoiding tackling two problems at once. One is figuring out the RP2040 and our toolchain, the other is figuring out assembly language.

The whole shebang is on my Github here:

I owe a debt to Dwelch67, as I studied his blink00 demo to learn how to do this. I coded it with a very different style than he did, just because I have my own way of doing things. Here is the heart of the C code:
void
blink ( void )
{
        struct resets *rp;
        struct sio *sp;
        struct io_bank0 *iop;

        /* reset IO Bank 0 */
        rp = (struct resets *) RESET_BASE_CLR;
        rp->reset = R_IO_BANK0;

        rp = (struct resets *) RESET_BASE_RW;
        while ( (rp->done & R_IO_BANK0) == 0 )
            ;

        sp = (struct sio *) SIO_BASE;

        // sp->gpio_oe_clr = GPIO_25;
        // sp->gpio_out_clr = GPIO_25;

        /* Set function select to software IO */
        iop = (struct io_bank0 *) IO_BANK0_BASE_RW;
        iop->io[25].ctrl = 5;

        sp->gpio_oe_set = GPIO_25;
        // sp->gpio_out_set = GPIO_25;  // led on
        // sp->gpio_out_clr = GPIO_25;  // led off

        for ( ;; ) {
            // on
            sp->gpio_out_set = GPIO_25;
            blink_delay ();
            // off
            sp->gpio_out_clr = GPIO_25;
            blink_delay ();
        }
}
So, let me go step by step through what we are doing here. As you may guess, IO_BANK0 contains the user GPIO. On the RP2040 there are 30 bits of user GPIO (0-29). As you might also have guessed, the onboard LED is driven by bit 25.

To save space, I am not showing all the code here, but you can either go to Github to look at it all, or even better, clone and then build and run it yourself.

What is this IO bank business

We could go deep into explaining all the hardware detail of the RP2040. We won't, but we will at least briefly discuss this. The RP2040 has 36 pio pins in 2 banks. 30 are in one (the user bank) and 6 are in the other (to talk to the flash chip). So bank 0 (that we set up here) is the user bank with 30 pins. Bank 1 is the bank with the flash chip signals, and we don't mess with it.

You may also wonder about why we have to fiddle with reset at all. This is common in microcontrollers and SoC in general. After power up, most peripherals are held in reset and you have to release the reset (and sometimes enable a clock) to get them to run. With some chips this is done to save power -- you don't run any part of the chip unless you actually need it.

The Makefile

Some people are afraid of Makefiles. They shouldn't be. I think part of the trouble is that some people write crazy complicated Makefiles that would terrify anyone and that even they probably don't understand. My makefiles are minimalist and serve me. If you look at mine (and you should) you will find that it generates and/or allows you to generate a couple of interesting things: These are the same thing of course, but from different angles.
The first (blink.ss) has lots of automatically generated boilerplate that gets in the way of reading the code and takes some getting used to.
The second (blink.dump) is (to my eye) much easier to read and also shows the addresses and generated binary opcodes.
Feedback? Questions? Drop me a line!

Tom's Computer Info / [email protected]