TLDW logo

Lowlevel Embedded Programming with RP2040 (no SDK)

By dr Jonas Birch

Summary

## Key takeaways - **RP2040 Boot Sequence**: First stage bootloader in ROM initializes internal hardware, then calls second stage bootloader in first 256 bytes of flash to initialize storage before loading the program. [05:28], [06:53] - **ARM Thumb Assembly Setup**: Use syntax unified, CPU Cortex-M0+, thumb; define sections with .section .boot,"ax",%progbits for boot and code, aligning to 4-byte boundaries for functions. [08:23], [12:39] - **Vectors Define Entry Point**: Vectors section provides stack address first, then main function pointer as reset vector, followed by ISR vectors filled with zeros; export/import labels across files. [15:02], [18:10] - **Memory Map Bare-Metal**: Boot at 0x10000000 for 256 bytes, flash at 0x10000100 for 2MB, RAM at 0x20000000 for 264KB; linker places boot, code, data, BSS, stack accordingly. [21:35], [23:43] - **Light Pin 7 via Registers**: Set GPIO7_CONTROL to 5 for SIO function, reset PADS_BANK0 and IO_BANK0, set GPIO_OUT to 1<<7 and GPIO_OE to 1<<7 using LDR/STR on SIO base 0xD0000000. [01:18:25], [01:36:11] - **UF2 for Pico Deployment**: Compile ELF with arm-none-eabi-as/ld, convert to UF2 using Pico tool (requires SDK build), copy to Pico in bootloader mode by holding BOOTSEL. [38:04], [01:39:11]

Topics Covered

  • ARM Boot Sequence Differs Radically
  • Memory Addresses Control Hardware Directly
  • Datasheets Hide Critical Register Offsets
  • Bare-Metal GPIO Needs Precise Bit Shifting

Full Transcript

So, this is a big pack of matches.

This is a small pack of matches. And

this is a microprocessor, a CPU. It's an

RP 2040, an ARM Cortex CPU from the company behind Raspberry Pi.

However, as you can imag imagine, it's a little bit difficult to connect stuff to these small pins right here. So,

instead, I'm going to use this little baby.

So, as you can see, it's the same CPU, but there's some additional stuff added here. So all of the connectors, almost

here. So all of the connectors, almost all of the connectors on this CPU are connected to these pads on the sides here. So we can just add some uh

here. So we can just add some uh connectors here and put this on the breadboard or whatever.

So this is a microcontroller board. So

this is what we're going to use for our project today. We're going to learn how

project today. We're going to learn how to program this thing in a really low level.

By the way, before we begin, we're going to have a little quiz.

So, this is a project that I'm working on. It's not entirely finished yet,

on. It's not entirely finished yet, but I want to dare you to figure out

what this is by just looking at this thing. If anyone of you can figure out

thing. If anyone of you can figure out what it might be, write it down there in the comments. And uh if you manage to

the comments. And uh if you manage to figure out, the first one who manages to figure out what it is will get some kind

of small token of my appreciation.

Sup guys.

All right, so let's get this project started. It's quite a lot of setup

started. It's quite a lot of setup things to do in order to get it to work.

But once you have done it uh one time, you can reuse the same project. So you

don't have to do it all over again. All

right. So I'm going to use my Linux machine to compile everything and then I'm going to download the image and

write it to to the Raspberry. Um,

so let's start by creating a directory. I'm going to call this ARM.

a directory. I'm going to call this ARM.

And since we're going to have several projects, I'm going to call this first one light up because

the purpose is to uh put electricity on one of the pins so our LED lights up.

So I'm going to set this to my home.

And we need to install some dependencies because we need to be able to assemble this uh this code and link everything.

So the package in Linux you can do let's see here if you do apppt cache

search and you search for ARM and you grap for AI then you're going to get some

interesting things.

Okay, so we need the bin utils.

This one right here. This contains uh the assembler and the linker.

And maybe this is all we need actually at least for now. When we do more advanced projects, we need to be able to

debug and such things. And when we do that, I recommend using a Raspberry Pi, but uh it's not strictly necessary.

There are other ways.

Let's see here if there's anything else we might need.

No, I think this is the only one. So

after you have installed that um we can open up this project folder.

So let's see here. I put it inside of tmp arm light up.

I'm going to open it in VS Code and I'm using the sociopath SCP client here in order to code remotely but you can just open it up. locally.

All right. So, what do we need to do here?

Well, first of all, we need to have a file for the second stage boot loader

because the booting process on this ARM CPU works a little bit different than a regular personal computer x86.

Um so first there is the first stage bootloadader and this is inside of ROM memory. So uh

it is written physically inside of the part of the CPU and this has uh the purpose of initializing

all the internal hardware.

When this is done, it's going to call the second stage bootloadader and that is located inside of the first

256 bytes of flash. So basically on the hard drive and this we need to put there. We

don't need to code it ourselves. Um

but we do need to um to put the code there.

Um and this has uh one purpose basically and that is to initialize

u everything regarding to um uh storage. So the rest of the process

uh storage. So the rest of the process can continue on and then it loads our program.

So this we don't need to worry about it happens uh automatically. But we need to store this uh second stage boot loader.

So I have put the code inside of the repo. I will link to this in the video

repo. I will link to this in the video description. And I've created the ARM

description. And I've created the ARM directory where I have the boot.asm.

So you can just uh take this code create the file boot.asm.

boot.asm.

We can paste it in.

And as you can see, my VS Code defaults to NASA x86.

So, we need to change this to ARM. And

if you don't have this, you need to uh download it as one of these extensions for VS Code.

So now it changed color and uh it recognizes that this is a comment. So

comments have an at sign in front and these things right here. We need to start all our

assembly files with this.

So, syntax unified just tells uh the assembler what kind of variation of the language that we're using. And that's

this one. And this tells it what CPU we're on. And that is an Cortex M0 Plus.

we're on. And that is an Cortex M0 Plus.

But it's not the regular big version.

It's the thumb version. So, we need to say thumb as well. And then the assembler will be able to create the

correct machine code based on that. Uh

and then we need to define a section and the in this case the boot which we will define as the first 256

bytes of flash and ax just means that we have uh um that it's going to be allocated and that

it's executable code that's not strictly necessary I don't think uh and word means that we are

defining signing 32bit words like this and this is just the static machine code

for the second stage bootloadader.

Then we need another file um which is our actual code. So let's

call this light up. Azen.

So this is our live light up.

And I need to specify that this is ARM.

So, same as before, we need to say that the syntax is unified. We need to say

that the CPU is a Cortex M0 plus and that we're using thumb.

And then we can define a section. Let's

call this code like this.

The code needs to be allocated and executable same as the other stuff. And here we will put our

main function.

So we can define our main label. We can call it whatever we

main label. We can call it whatever we want.

I'll call it main here. And um we need to tell we need to tell uh the assembler that this is a function.

So let's see here.

need to look at my cheat sheet, right? So, we need to say that the type

right? So, we need to say that the type of main is a function and also that it's a thumb function.

So this we need to do for all the functions otherwise it will not be correctly aligned and stuff. And we also need to say that this section code is

going to be aligned as a four byte B boundary.

And yeah, so that's the main syntax of this thing.

And then we need one additional ASOM file where we're going to store the reset vector and our pointers.

We can call this vectors.

Let's add as same here the syntax is unified.

The CPU is ARM Cortex.

Do we say Cortex M? Okay.

Cortex M0 plus and thumb.

So then we're going to have the reset vector and let's create a section for this.

Let's say we have a sector called vectors and this is going to be allocated and

well let's do this.

And this needs to be aligned for So, we're going to have two things.

We're going to have the vectors and we're going to have the pointers.

So, what is the vectors? Well, that is where we're telling the CPU where it's going to start executing the code and

some other stuff like interrupts and things like that, but we'll get to that. So let's say we have a

label called stack. So the first thing is we need to provide the address of the stack.

Um and the next thing is our start function. So let's say it's main.

function. So let's say it's main.

And then we're going to have the ISR the interrupt service routines.

But we are not going to use this in this first project. So I'm just going to say

first project. So I'm just going to say ISR and I'm going to do BXLR

which means no not BX.

Let's see.

Yes, it's correct because when we call a function um we do something like this.

We say BL ISR for instance. This means

that we're going to branch uh so jump in the code to the ISR co

label and start executing code. But it

also means that we're going to save the current address that we're at in the LR register.

So when we are done with the ISR, we can return back there, which this BXLR does.

All right. And since the um since the main here and the ISR are labels that we're using in another file,

we're using it here. We need to export them in this file and import them in the other file.

And in order to export something, we just say global main

global ISR. And by the way, ISR is also a function.

And it's also a thumb function like so. And inside of vectors, we need

like so. And inside of vectors, we need to import it and we use the extern for that. So we're going to

that. So we're going to import the stack.

I'm going to import.

No, not the stack.

Let's see.

How do I do this?

Yes, let's do that. Let's uh import the stack, the main, the ISR. And there are a lot of different other

things we can put after here, but um we're not going to use it at this point.

So, we can just fill it out with a lot of zeros.

I don't remember exactly how many, but it's quite a lot.

Let's say 0x050.

Right.

The next thing we need to set up is the memory the memory file. So let's say memory LD since this is a linker script.

So we can say that this is a linker map file. And you need to have a

file. And you need to have a uh extension to get this linker map file thing also if you if you want it. It's

just for syntax highlighting. It's not

strictly necessary.

And I am cheating a little bit with my sheet cheat sheet sheet here because

I don't have all this in heart. Um, and

it's actually pretty difficult to obtain all this information needed to create this without the um, without the SDK

because there's almost no information on Google. I found some blog posts where

Google. I found some blog posts where they try to do the same thing as I do here, code bare metal on the ARM processor,

but uh, they failed at it. So I used some of their information and I tried until it worked.

So we need to define the entry which is our main and then we're going to do the memory map.

We're going to have three different sections. So we're going to have the

sections. So we're going to have the boot section which is going to be readrite.

It's going to need to be executable allocated and I I don't remember what I is. I

think it has to do with filling out with zeros if we don't use all the space.

And then we need to define the origin.

So where the memory uh starts and this is 0x1 1 2 3 4 5 6 7 zeros.

And so this is where we put our boot loader. And this is 256

loader. And this is 256 uh bytes.

Then we have the flash which is the hard disk. So the hardest drive so to speak

disk. So the hardest drive so to speak where we put our code.

It has the same permissions.

The origin is same as this one, but we add 100 to it, which is 256

bytes.

And the length of this one is uh 2 megabytes.

And finally, we have the RAM.

This is read right x a. We don't need to fill this out with

x a. We don't need to fill this out with zeros.

The origin is 0x2 and all zeros.

And the length is 264k.

All right. So this is the memory map.

The next is the actual sections that we want to put inside of the memory uh locations.

So, we're going to have a boot section and we want to import

the boot section.

and we want to put this inside of our boot memory space.

Then we're going to have the code section and inside of here we want to put the the star here by the way

is the file. So we will import the vectors section from all the files which is just one in this case.

And we want to import the code section.

And this we want to put inside of flash.

And we're also going to have a data section.

Same. We're going to import the data and put this inside of Flash.

And then we're going to have a specific label for the stack or a specific section.

And here I want to import the BSS, the unallocated static data and the stack section itself.

And then I want to define this stack label.

So let's put it at the current memory position and add 0x400 so we have some

room for the stack.

And this we put inside a RAM.

And this is the whole file.

All right. So, this is how I'm going to organize everything. And most of this is

organize everything. And most of this is based off of the data sheet for the RP 2040 CPU.

Okay.

And now we only need one more file.

And that is our make file. So we can compile all this all this stuff.

So let's create a make file and let's define some variables. So the

assembler we want to use is called arm none.

EABA EABIS.

So this is the file name of the executable for the assembler.

And then we have the linker ARM non EABA LD.

I'm going to have an include directory.

So let's say it's this going to have some flags.

So here we are just defining different variables and I'm going to tell it dash I and um the include directory.

So this just says that we can include stuff from there and then some linker flags as well.

So, we're going to use dash M.

And I'm not sure what does M does. Let's check.

So, we can run ARM nonabd-help grip dash M.

uh it shows output.

So when we assemble something, it's going to tell us what we have done and how the memory looks. And I'm also going

to specify dash t which is a linker file and we call that um memory.ld

memory.ld LD.

All right. So, how do we do the actual compilation? We can do it in a couple of

compilation? We can do it in a couple of different ways. I like to

different ways. I like to create an LL file and use that as a basis

um for um creating the output binary file.

So let's start by compiling our boot.asm ASM into boot

boot.asm ASM into boot and we compile simply by running the assembler with the flags

and we take the input file and we pro create an output file. So,

this is the same way that we're going to do all three of these files.

This is going to be vectors vectors and

the actual code file which is light up light up. So now we have O files.

light up. So now we have O files.

Now we need to create um the elf file.

So let's say we want to create something called light up dot

elf.

And we're going to base this on all our files.

So we need boot, we need uh light up. Oo, and we need vectors. Oo.

vectors. Oo.

And how do we create this um this L file? Well, we'll run the linker LD.

file? Well, we'll run the linker LD.

And we provide our linker flags.

And then we take all of the inputs which this means. And we provide an output

this means. And we provide an output file.

And I don't want it executable. So I'm

going to change mod to regular read. write permissions.

All right. So,

let's start there.

The only thing we also need to do is create this weird um UF2 file.

But let's start here. So, we at least have something.

I also want to have the regular stuff like a clean directive which will just remove all the O and the elf files

and let's say the UF2 files as well since we will have these later on.

And all we're going to run is the light up dot elf

and we need to provide what's phony and that is clean since it doesn't create anything.

Okay.

Let's see what we have gotten wrong.

So, let's just run make and see what happens.

Syntax error in the LD file on line seven.

Ah, this should be sections.

So, let's run make clean and try again.

Okay, so we got this output where it tells us how it is all set up.

So, we have the boot at this address and the flash and the RAM, the different memory configurations.

And then we have the boot uh section and we import um everything from boot.

And we also take the code and we fill it out as we can see here with zeros.

And then it adds some other stuff by default.

And it has created this um light up.

5K file.

So how can we check this file? Well, we can run object dump.

this file? Well, we can run object dump.

Let me see if I have an alias here.

I don't Maybe. Let's see

Maybe. Let's see if we run ARM none obump dash d

on this elf file.

Here we go.

So here we can see what it contains. So

this is the code for the boot loader.

A lot of different weird stuff which we don't need to care about.

And here we can see the values of the vectors.

So the first thing inside of the vectors is the stack. So our stack pointer will be this.

And then we have the main and the ISR which are these addresses inside of the flash.

And then we have the actual code.

So the only thing we need have here is um blisr. Let's see what we did here. Yeah, we put

this temporarily, but we're not going to have this. But as we can see, this is

have this. But as we can see, this is this turned into this. So, it calls the ISR at this address.

Um, and here we just have the BX the return statement.

All right. So, that's good. Now we know that we can create uh these things.

Now we come to the little tricky part because it's not enough to create an L file. We need to create this UF2 file

file. We need to create this UF2 file which is a we very weird format. It's

like a container like a um a zip file or an ex file but it has some additional information. And it it stores the data

information. And it it stores the data and the code in different sections. It

has some metadata which tells it how to create the flash image and so on and so forth.

And so we need a program that can um

that can uh create those U2 files. And

it's difficult to find uh something which does that. I have

found one uh open-source tool. But the

problem is it does not work for the Raspberry Nano. At least I haven't got

Raspberry Nano. At least I haven't got it to work. So this is the only time we need to use something from the

SDK which is called the Pico tool.

And the pickle tool um requires the SD SDK to compile.

And the pickle tool can create a a UF2 image. So basically you just do

pickle tool UF2 convert and you provide the input file which is the elf and the output file which can be lightup. UF2

for instance.

But we need to compile this thing and find it.

So let's see if we can find it.

So we'll go to Google and search for PO tool.

And we find it at this GitHub repo.

So we will download the zip file. We can rightclick and copy link.

And let's go to a temporary directory.

So let's create an ARM directory here.

And let's w get this file and just unzip it.

And in order for this to compile, we also need to have the SDK just for this

um step. So let's see Raspberry

um step. So let's see Raspberry Nano SDK which we have here.

So let's do the same here. Copy this

link.

W get it.

Unzip and it's quite a lot of files and we can we could use this in order to create a C

program.

um but we will not learn anything about the actual platform if we use those things. So let's go to this SDK folder

things. So let's go to this SDK folder and let's see how we can compile this thing.

Let's check the readme file.

So make your target from the build directory you created.

I guess we only need to run this.

All right. So

now it is compiling this thing and we will just wait until it's done.

Okay, it didn't compile it. It just

generated all the make files and stuff.

So, we go to the build directory and I guess we just run make.

Yeah. So, now we just wait until this is done. We'll be right back after this.

done. We'll be right back after this.

All right, guys. Welcome back. So, now

the SDK has compiled.

So let's save the path that you install this on.

So let's just create some temporary variable. I don't know SDK is equal to

variable. I don't know SDK is equal to this.

Then we go to the next one, the PO tool.

And let's see what it says regarding installation.

Let's do more. Read me

building Okay. So, we need some dependencies.

Okay. So, we need some dependencies.

So, we need to do this.

Unless you already have them installed.

I did.

Then we create a build directory.

We go into it. We run cmake dot dot.

And I wonder if we need to provide the SDK path somehow. But let's see what it says. If we just run make

says. If we just run make So far so good.

If you do get an error, however, in that case, you need to specify the path of the SDK.

Maybe I have already done that and that's why it doesn't whine.

Going to try to remember to check that when this is done.

If it's ever done, this mainc must be quite big.

Okay, looks like it's worked. Um, let's

do set and grip for SDK.

Yeah, that's right. I have already specified the path. So if you get an error when

the path. So if you get an error when you try to compile Pico to set this variable. So you do export this is equal

variable. So you do export this is equal to well the variable that you set before what did we call it SDK

and now it points to the right thing.

Um okay that's good. So now we have the Pico tool right here and I think we can run sudo make install to install it

globally.

And now we can run pick tool like this.

Pretty nice. Pretty nice.

So now we can go into the make file.

and create a new target light upf2 which is dependent on light upf

and all we're going to run is um let's do it like this let's say we have

a variable called ple to PT and we run convert uh UF2 convert

verbose and we provide the source file

and the output file and then we can set PL tool to be equal to PL tool like so

let's see if this works.

So, we'll make clean and run make.

We did not get an error. That's a good sign.

We did not get any F2 either.

Do I forget to save perhaps?

Ah, I need to change the all now to the UF2 version. That's right.

UF2 version. That's right.

All right. So, this part right here is the pickle tool output.

And this is the output when everything worked.

Which means we have this light up. Q2

right here.

1K big.

Okay. So, let's see here.

Now, I want to download this in some easy fashion.

Uh, I think I'm going to go to my root directory and create

folder. Let's call this um ARM Inside

folder. Let's call this um ARM Inside of here, I'm going to create a batch file.

So let's say this is um um UF2 dotbot and I'm going to edit this

And let's see what I'm going to do. I'm

going to run PSP.

And this uh step you probably don't need if you have compiled everything locally.

But since I have done it on a remote Linux box, I need to download this to my Windows machine so I can transfer it to the device.

So let's see here. I want a PSP username at 10.1.2.5 2.5

and the directory is tmp arm light up

light upuf2 and I want to put it inside of my UF2 directory

my ARM directory like so.

So now hopefully I should just be able to run slasharmuf2.bot.

No, that did not work. Am I connected to the VPN?

I am. So why didn't it work?

Maybe I should run the regular SCP.

Okay.

And there we have it. Lightup UF2. We

have successfully created a UF2. But

this is the empty one, so it doesn't do much.

But the next step is to create the code for actually initializing the pins and putting electricity onto it. So that's

what we'll do just after this.

Did you learn anything useful today and you want to support this channel? Go to

dr.com/support.

You can also click the button become a member. Thank you for your support.

member. Thank you for your support.

All right guys, welcome back.

So before we can start developing software for our processor,

we need to learn a little bit about how it works.

So let's see here.

I will assume for this part that you have at least a basic understanding of the assembly language.

If you don't, you can watch my assembly school. It's for a different platform,

school. It's for a different platform, but the general concepts are the same.

So we are dealing with a CPU which is an ARM Cortex 7

and this is the so-called thumb two version which means that it has a a smaller amount of instructions and most of the instructions are 8bit in

nature.

The pro CPU in itself is made up of a lot of registers.

So we have R0, R1 R2 all the way down to

I think R 13 or something like that.

And each of these can store 32 bits of data.

So this is like a small fast memory cell on the processor itself.

Uh and these are used for storing intermediate values. There's also one

intermediate values. There's also one two other registers which you need to know about. It's the SP which is the

know about. It's the SP which is the stack pointer uh which points to the top of the stack.

if we need to store some additional information in the RAM.

And we also have a register called LR which are used when we call functions or sub routines and we need to go back

after we have returned from that function. The return address will be

function. The return address will be stored in the LR register instead of the stack. So it's good to know about.

stack. So it's good to know about.

All right. So how do we put things in these registers? There is an instruction

these registers? There is an instruction called move.

It doesn't move anything but it copies data into the registers. So let's say we want to put the number seven into the

R0.

Then we just do move our zero seven. Easy as pancake.

seven. Easy as pancake.

One problem though, this is an 8 bit instruction, which means that this right here needs

to be 8 bits. So from 0 to 255 only.

But what if we need to deal with memory addresses? Because the

ARM cortex has a memory space which is 32 bit. So it goes from 0x 0 0 0

32 bit. So it goes from 0x 0 0 0 all the way to all fs.

In a regular personal computer, an x86 32-bit system, this is all memory addresses in RAM. But that's not the

case here. Only a small portion of it

case here. Only a small portion of it are RAM. So the RAM which is 264K,

are RAM. So the RAM which is 264K, it goes from 0x 2 and all zeros

and 264K from there up.

We also have 0x1 all zeros.

This is two megabytes and this is the flash. So this is the hard disk so to speak.

Uh all other addresses are used to communicate with different hardware components.

So, let's say for example that we have the card right here and we have the CPU and we have a couple of connectors at

the end here.

And let's say we connect a um an LED light for instance through a resistor

to ground like so.

So this connector right here, it has a specific memory address and we need to write to that memory address in order to light this uh thing up.

And there are a lot of different instructions for like doing math and stuff and all a lot of other things, but

we will only need to use maybe five different instructions because we only need to write to a memory address to activate a specific

pin. And we might need to read from a

pin. And we might need to read from a memory address to see if that pin has electricity.

All right. So, how do we do this when we only we're when we're only able to deal with 8 bit numbers with a move

instruction? Well, there is another

instruction? Well, there is another instruction which is called LDR and it stands for load.

Um, and it works like this. First, we

create a label somewhere.

a label. Let's say it's called GPIO which is the name of the pins the general purpose input output pins. So

let's say we have a GPIO1 a label like this and we do word

which will put a memory address. So let's say 0x I'm just taking an example here. So

0x ow take it easy. FB00 0 1 0 or something.

So now this GPIO1 label here act as a pointer and let's say we want to put the

value of 0 1 0 0 uh and we specify

we can specify this in a diff in two different ways. Uh first we need to

different ways. Uh first we need to create a constant which we use the EQ uh thing

semi instruction and let's say we call this um I don't know activate or something

and we can either do 0 B 0 1 0 0 to specify a specific

uh address like this. But we can also do a one uh shifted left by

one two positions.

So these are equivalent.

If you're only going to set one bit, this is a good way. But if you're going to set like 01 01 or something, you can

do the 0 B uh version.

All right. So now we have the pointer, we have the value.

Now we want to load that.

So what we do is we select a register let's say R0 and we do LDR R0

um an equal sign to specify that we are dealing uh with a constant and we put the value here.

Um then we load another register let's say R1.

Um and here we are going to put our pointer.

So the GP IO1 but this R1 now contains the address of

this pointer GPIO1.

So we need to um dreference this and we can do it in two ways or actually it's a variation of the same way but we

can load it into a second register by just do LDR and let's say we want to put it in R2

and then we take the address pointed to by R1 by putting it into

um these brackets same as in x86.

So now R2 will contain this.

We can also use the same register which is kind of neat. So we can do LDR R1

R1 and that does the same thing.

And in order to store a value, so we want to store this activate value here.

Then we can use the store str command.

Um let's see here. We want to take the value. So we begin with a value here.

value. So we begin with a value here.

It's a little bit uh in the wrong order, but that's how it's done.

So we want to store the value that we have in R0 and we want to put it into the pointer in R1 like this. So now we

have written this value uh I mean uh this value to this address here and this will light up.

All right, you guys. Welcome back. So,

time to let us uh let our code do some stuff.

Um so, our goal is to light up pin number seven, let's say.

Uh okay. So, what are the steps? There are

okay. So, what are the steps? There are

three steps.

First we need to initialize the stack so we can uh use functions and deal with uh

well local variables and stuff like that.

Then we will do the actual steps for lighting up the thing. So we need to

select the function of pin 7. So all the pins have different

functions that they can perform and uh I'm going to show you details in a little bit but that's one of the steps

to choose what function to perform.

Uh and when we have selected that we need to activate power

on pin 7.

So this is what we need to do with our code and we need to do all of this in the blind because we cannot troubleshoot or

debug uh at the moment. So the only um the only feedback that we get is

is the pin number seven lighting up the if we connect the LED to it. Is it

lighting up or not? That's the only uh feedback we're going to get at this point. We'll set up debugging and the

point. We'll set up debugging and the kind later on.

All right. So initializing the stack.

That's pretty simple.

Um if you remember our code we have this vectors thing here and here we mention the stack. So this is uh the address of the

stack. So this is uh the address of the stack and inside of our

memory map uh linker script we define the stack address here. So it

will take the current address um in RAM and add 400 to it

which gives us plenty of room. I think

that's around half a what is it half a kilobyte 512 bytes I think.

Okie dos. So how do we do that? Well, we

need to load it this address into a register and then move it or

uh copy it into the stack pointer which is a special uh register.

So that's pretty simple. Uh if we go to our light up ASM, we can just do we can just do it here

inside of main.

And let's remove this blisr because don't need that.

Um so we want to load into

the let's say our zero we want to load the address of

the stack. So this means

the stack. So this means if we go back to our vectors

um it means the actual address of the stack pointer.

So then we'll only need to move this um so move into SP from R0.

So now we have initialized the stack.

Maybe we can put a sub label here and call this in it or something.

So we can move this out which means we need to move this out.

All right, step number one done. Easy

peasy.

Um, so now we need to select the function of pin 7.

So now we need to look at the data sheet because we need to learn how to find the important information inside of the data

sheet. There is one problem though

sheet. There is one problem though and that is there are three data sheets.

So we have one which is called getting started with Pico which has some basic information about the microcontroller

card as a whole and not a lot of interesting things in this

one. The only thing that I found useful

one. The only thing that I found useful is um let's see where is it

this the debug probe. It shows how to build one of these debug probes and that we can use to

uh connect a debugger, but that's for later. Um, and then we have the Pico data sheet. That's also

for the entire card as a whole. Here you

can find quite interesting things actually.

Um, so I recommend reading it through.

It's not that big, but the big one is the RP 2040 data sheet. So this is a

whole data sheet with a lot of pages 642 pages and everything is about the processor the CPU.

So how do we find the important information? Well, let's first go to one

information? Well, let's first go to one of the beginning things here. I think

it's pino. No p not this. Um,

let's see.

I want to find a an image of this thing.

Maybe it's in the other data sheet.

Yes, probably it is. Uh so let's check this uh Raspberry Pi Pico data sheet instead and go to the beginning. So here is the

card and on this uh display here this is the back of the card and here we can see all the different

uh pins. It's a little difficult to see

uh pins. It's a little difficult to see because it's flipped 90 degrees to the right, but um maybe we can do something

like this.

And then if we just take this thing, we crop.

and we rotate to the left. All right.

So, here we have the underbelly of the card. Um, and the interesting things

card. Um, and the interesting things that you should know about is Vbus

and VCS, which is uh power and ground is of course the minus side.

Um but then we have a lot of these GP 22 GP21 etc. that is the GPIO ports, the general purpose ones that we can use for

whatever we want.

But even those have some specific functions as well and that's why we need to select um

a function. So now I am at the right

a function. So now I am at the right place. But let's go to the absolute

place. But let's go to the absolute beginning so I can show you how to find it. And if you go to

it. And if you go to this one, GPIO functions. Here you can see the different functions. So we have

one, two, three, four, up to nine different uh functions. And then we have a list of all the ports here.

So if we take our port the one that I selected randomly number seven we can use it as an uh SPI or a UART. UART by

the way is like a serial connection. So

you send one bite to the UART and it will send it uh over the console cable uh at the right clock length and

so on. And I square C is also for

so on. And I square C is also for communicating.

Um PIO is like a special assembly language

that controls the uh the port.

Then you can use it at USB.

But we are interested in this function number five SIO

and it stands for uh soft no single cycle IO.

So single cycle input output. But what

it really is is we let the CPU the processor handle the pins.

So if we enable SCIO, we can choose uh from with the code in the processor what um part of the thing that we are

going to which uh port that we want to activate and so on.

So that's where we need to start. So how

do we select a function? We need to select function number five as I showed you.

Um but how do we do that? We need to write to a specific register. So if we look at the

contents here on the left, we scroll down to GPIO and it's a bit

longer. Let's see here. Clocks and we

longer. Let's see here. Clocks and we have crystal pl GPIO. Here it is.

pl GPIO. Here it is.

So here is a lot of information about the GPIO ports and here is another copy of the function

list. But we want to know how to

list. But we want to know how to actually set the function. So we want to scroll down to list of registers.

So here is some important thing.

This is important. the user bank IO registers start at base address of this.

So this we should copy.

Let's go to the vectors and put it as a comment. And a comment is with an at

comment. And a comment is with an at sign in this language.

So this is the base. We want to remember that.

Um and then we have a lot of the different registers.

All the all these uh different GPIO ports has two registers each. As you can

see, GPIO status and GPIO control for number zero, number one, number two, and so on and so forth. And as you can

imagine, you read from this status to learn about things. And you can write to the control to decide things. So we need

to find our control register for number seven. And here it is. And to the left

seven. And here it is. And to the left we see the offset.

So we need to copy this.

So now we know in order to get the actual address of the register that we need to write to, we just plus these

together. But we need to know one more

together. But we need to know one more thing. We need to know what to write to

thing. We need to know what to write to that register. So if we take this GPIO7

that register. So if we take this GPIO7 control and we click on it here, we can

see what we can can write to it.

So it's a 32bit register goes from bit number zero to bit number 31.

And this means that it is two bits number 30 and 31 is reserved. And then

we have bit number 28 and 29.

So this 31 bit is the one to the left.

And then we move on right here.

So what we're interested in is at the end here this function select.

So we have um 01 2 3 4. We have up to five bits of

function select.

um and we want to write the value five to this.

So since it starts at the first bit, we can simply

write a five to this register.

So we write five to this register. But if

we needed to do one of the others here, um let's say we want to write something to

this part, then we need to do some bit shifting and stuff to find this specific bit. But we don't need to do that now.

bit. But we don't need to do that now.

So I won't get into it. But let's start by placing these two together.

So let's copy this and go to my shell.

And I have an alias which is called hex which looks like this. So you can just copy this and paste it in and you have it too.

Um so first I'm going to echo and I do a dollar with double parentheses which means a mathematical

uh calculation. And I take this

uh calculation. And I take this and I plus it to this.

And the answer I will get in decimal format. So I'll run my hex alias on

format. So I'll run my hex alias on this. And then I get the address of the

this. And then I get the address of the register I will use.

So let's create a pointer which contains this thing.

So let's say we have a um GPIO 7 control

um and this contains the word this address. So a word in this language

this address. So a word in this language is a 32bit integer like this.

And then we need to uh export this.

So we run extern GPIO 7 control.

And now we can go to the light up and write to that register.

So what we do is we load R0 let's say with the GPIO7 control

and let's um import it. No, I'm going to the wrong direction. You should write it extern

direction. You should write it extern here to import it. So extern GPIO 7

control and inside of vectors you should run global.

You use global to export.

All right. So now we have loaded um the address to this part. So basically

we have loaded the pointer inside of our zero. So we need to dreference that

zero. So we need to dreference that pointer and we can do that by running load and

into our zero we want to load the contents of our zero which means we go into this pointer and

obtain this address.

And we can move into R1 the number five.

Maybe we need to do this.

I'm not 100% sure about these equal signs, but I think it's when we specify a constant value

and with this brackets, it's when we reference a pointer.

Um, all right. So, now we have the address

all right. So, now we have the address we want to write to inside of our zero.

We have the value inside of R1.

And now we run a store operation.

And we want to store the value into the address of our zero.

And now we use the brackets again. So

basically f the first time we reference this and obtain this and now we write into this pointer.

I think this is right.

Let me just double check my cheat sheet.

H yeah looks right and that's all for that point. So now we h have selected the function

and now we need to activate power and how do we do that? Well we need to

go into that specific section. So we

have enabled SCIO that's the function that we have just now enabled.

So we need to look at the SCIO registers in order to control this stuff.

Um so how do we find these? We need to go into the right data sheet to begin with.

It should be the RP 2040 data sheet.

Let's see. I think it's somewhere in the beginning.

Let's see here.

Maybe we can go to the start here and search.

This search function doesn't seem very good.

Let's see. It's weird.

Let's see. Maybe here list the registers.

Why doesn't the search function work?

Very weird.

Well, let's see if we can't find it. Um

ah here. Scio.

All right. So we can see here that every SCIO register begins with uh 0xd and it goes through the whole range

here.

And here is some general information.

But we are interested in the registers which usually is at the end of the chapter

here. All right. So here are all the

here. All right. So here are all the registers which corresponds to GPIO uh I

mean SIO. So I copy this base address

mean SIO. So I copy this base address and let's go here. We can delete this.

So the base in this case is this.

And the register we want to use is this GPIO

output enable.

So if we we can copy this offset first and then we click it.

Um and as we can see from zero all the way to 29

there is one bit per GPIO and as we can see it's GP output enable.

Let me just check one thing.

Yeah, that's the right one. So we we just want to write a seven to output the output enable register and that will uh activate our

uh pin.

Easy as pizza.

So we'll create a new label. Let's call

this um scio power on.

Um, and we'll have a word which is here. We It's so easy to just

copy because we can take this paste it here and we see that it should end in zero 2 0.

So 0 2 0.

So this is the address that we want to write a7 to.

So let's do as we did before.

We export our SCIO power on.

We go to our main code file and we import the same thing.

power on and then we repeat this exact same steps.

So we load into our zero the value of um sio power on. So this is the destination address

and we dreference it into the same variable the same register like before.

Then we move into R1 the value seven and we store the value in R1 to

the address in R0.

And now hopefully our light will light up.

But we don't want our code to crash here. So let's add an endless loop here

here. So let's add an endless loop here by the end. So what we can do is we can create a loop label

and let's run a no up and then we jump to that loop label. We can use the branch always

which is the same as jumping to this. So

this will be an endless loop. So if

everything is correct, this uh code will set the correct function.

it will uh initialize the stack before that and it will activate power on pin 7 and then go to an endless loop.

So let's save and let's see if it compiles.

Nope.

Bad expression. Move R15.

Okay, maybe we should not have the equal signs there cannot honor with suffix. Okay, so it the move instruction defaults to 32bit

but as I mentioned our CPU can only handle 8 bits moves.

So we need to specify that it is an 8 bit move by adding an s to the end here moves.

And now it compiled.

So let's close this one.

And let's go to my directory slasharm and let's run this. So arm uf2.

And now we just need to copy this file to the microcontroller card and hopefully it

will light up. Let's try it.

All right. So, make sure to hold down this boot select button and then you connect this right here

to the USB port.

All right. So, the microcontroller card identifies itself as if it were a regular thumb drive.

So you just uh take the u image file, the UF2 file and you copy it over to

this disc like this. And uh then the uh Raspberry uh install that software and reboot and start executing it.

Uh sorry guys, I messed it up. Um

if um I mixed the two different things the GPIO 7 control register we want to send the the

set the value of five but we don't want to set the value of seven when we power it on we want to set bit number seven

right Um so we need to create a constant

which we can do with this EQ and we give it a name let's say um SCO GP7

um and we want to set the we want to set bit number seven.

So we can do a parentheses and we take a one which we push to the left seven positions

because if we were to push it zero positions, we would end up at bit number zero. And if we push it seven positions,

zero. And if we push it seven positions, we'll end up with um

seven. So if we have a um let's say a

seven. So if we have a um let's say a one byte binary

digit like this um and we set the value to one which means this

and then we put push it seven positions to the left.

So 1 2 3 4 5 6 7. So we end up here and then it

automatically fills up with zeros on the right of it. So this is the value that we end up with.

Um all right. So let's change this seven right there. And we can't use the move s

right there. And we can't use the move s instruction because now we're at uh a bigger.

Maybe we can actually let's try GP7

like this.

I'm not entirely sure, but let's see.

Nope. Uh, so let's do a load instead.

Like this.

All right. So, let's update this file.

It's inside of ARM.

Let's delete the old one.

Update.

And let's try this again. All right. I

did one mistake and I forgot two things.

So the first step was to initialize the stack and we almost did that. We load the

stack pointer into the R0 and we move it into the SP. But this uh stack label

here refers to the the address of this pointer. we need

to uh use the value of this pointer which means we need to

uh dreference R0 and then put it into SP. So that's

the bug. But I forgot two things.

So let's do this.

So after we have initialized the stack, we need to reset the pins

um and pads.

So the pads is a hardware unit which controls the pins.

And four we need to um change pin 7 to output because all the pins are in input mode by default and we

need to change it to output.

So how do we do these two things?

Um if we go into the data sheet, we need to look at one register which is called the reset register

and it's found on this address right here with an offset of zero. So it's

exactly at this base address.

So, let's go into the vectors.

And this was just while I was uh troubleshooting a little bit, but

we're going to add uh the reset register at this address.

And export it.

And inside of our code file, we are going to import the reset rig.

See, did I do the right thing? Yes.

And what do we want to write to this thing? Well, here is a long list. So

thing? Well, here is a long list. So

every bit basically refers to one part of the hardware and there are two different things. We need to re reset

different things. We need to re reset the pads and we need to reset the SPI which controls the actual pins.

So let's create two uh a constants. So let's call this reset

pads.

And as we can see the pads is bit number eight.

So we'll do this trick shifted eight positions and we'll create one for the reset of SPI

and SPI is bit 16.

But if but we are writing to the same register.

So we need to add those two together so we can write it at once. So I'm going to create a third which I'll call reset

both where I take the reset pads and I or it uh together with reset

SPI like this.

Then we add a section here where we load the R0 with the address of the reset register.

And as usual, we dreference that pointer.

Load into R1 the value of reset both.

And then we store uh that value inside of our zero like this.

So now we have initialized the pads and the pins.

And the second thing we need to add is to change pin number seven to output.

So let's go back to the data sheet and let's go back to the part where we talk about SPI. So the

about SPI. So the um the processor's control over the pins that we did from the beginning when we

activate the power. So, we go to SPI list of registers and

let's see where we're at.

Let's see what did we do. Last time we used this SCIO power on at D blah blah blah blah 20.

This is not the place.

Let's see.

It's not the SPI. Ah, the SCIO. I mean,

not the SPI.

Where did we find Sio?

We can search for that address 0xd00 here. IO port registers.

here. IO port registers.

Let's go down a bit. So we find the registers which always is on the end of each chapter.

So let's see here. So the one we used last time is this GPIO output enable.

But we also need to change GPIO out.

this one output value.

So if we look at this set output level and um every bit

corresponds to one of the pins. So we

want to change bit seven same as before.

So we want to last time we changed bit seven of the this register right here.

So we can use the same SCIO GP7 that we used before, but we need to write it to another register,

namely the one with a 10 instead of a 20.

So let's go into the vectors and we can copy this.

Let's call it SCIO output.

And this should be 10 not 20.

And we need to export this reio output like so.

So now we go here.

So before we write the output enable, we are going to we can move this part up a bit because

we're going to use the same value.

So let's load into since we're using R0 and R1 here, we can load this into R2.

And that is the SCIO output output which we need to import.

And as usual, we need to dreference this pointer.

So now before we write uh R1 to R0, we're going to write R1 to R2

like this.

And now we have done all the necessary steps. It was a lot of work, but now we

steps. It was a lot of work, but now we can just use the same code for any pin in the future.

So let's recompile this thing and we'll go to the correct folder.

We are going to download the new code and we are going to connect our machine.

And don't forget to push the boot select button.

And now we will transfer this thing and hope it works.

And it does. Beautiful.

Beautiful.

All right. So, we have successfully uh been able to put electricity on a pin and

uh we can do a lot of things controlling external hardware using that uh technique.

So, that would be all for today. I

really hope you like this episode because we're going to build incredible things using this chip.

So, I hope you will continue watching this series and that you like and subscribe and maybe even hit the bell icon so you will get a notification when

the next episode drops. And thank you for watching. Thanks for today.

for watching. Thanks for today.

Loading...

Loading video analysis...