User Tools

Site Tools


archive:pgt302lab01

Lab Work 2 - Bare-metal Programming

The things I have here are now available at GitHub under project my1barepi. I want to show that we do not necessarily need an OS - sometimes a 'simple' bare-metal code is good enough for simple applications.

The things I put here are mostly based on what I gather from the internet. Among the notable source of information are:

Compiling baremetal codes for Raspberry Pi

If you really want to start from the beginning, start from ACT I. But, if you are only interested in getting things running a.s.a.p., go straight ahead to ACT III, where we use example codes from my1barepi.

ACT I: Blinking LED

The Hello world! of embedded systems (which usually has no display by default) is possible with the availability of the ACT LED which is generally used to indicate (micro-)SD card access by an OS. Since this is bare-metal, all your base are belong to us! 8-)

The GPIO that is used for the ACT LED on R-Pi B+ is GPIO47 (was on GPIO16 previously). To access the GPIO, some GPIO register information from the peripherals documentation introduced earlier:

BCM2835 GPIO-related registers

Notice that the addresses shown are the ones assigned by the MMU. Since we will not be writing any codes to communicate with the MMU (at least for now), we need to access them using physical memory. So, instead of accessing 0x7E200000 for GPFSEL0, we will use address 0x20200000 (from the memory map introduced earlier).

A look into GPFSELx registers:

GPFSELx Registers

These should be enough for us to write a software to blink that LED!

Doing it in Assembly

Note: All codes/files in this section is available in my1barepi repository

A code to blink the ACT LED in assembly:

main.s
.section .boot
boot:
	ldr r0,=0x20200000
@set gpio as output
	mov r1,#1
	lsl r1,#21
	str r1,[r0,#16]
loop:
@clr gpio
	mov r1,#1
	lsl r1,#15
	str r1,[r0,#44]
@loop delay
	mov r2,#0x3F0000
wait1:
	sub r2,#1
	cmp r2,#0
	bne wait1
@set gpio
	mov r1,#1
	lsl r1,#15
	str r1,[r0,#32]
@loop delay
	mov r2,#0x3F0000
wait2:
	sub r2,#1
	cmp r2,#0
	bne wait2
@infinite loop
	b loop

We also need a linker:

kernel.ld
SECTIONS {
	. = 0x8000;
	.text : { /** code segment */
		*(.boot)
		*(.text)
	}
	.data : { /** data segment */
		*(.data)
	}
	/DISCARD/ : { /** discard all other... */
		*(*)
	}
}

A makefile to build bare-metal codes (assembly) for R-Pi:

Makefile
# to generate raspberry pi bare-metal code (kernel.img)
 
ifeq ($(OS),Windows_NT)
TOOLPATH ?= /c/users/public/tool/xtool-arm/bin/
else
TOOLPATH ?= /home/share/tool/xtool-arm/bin/
endif
TOOLPFIX ?= $(TOOLPATH)arm-none-eabi-
 
LINKER = kernel.ld
TARGET = kernel.img
LST = kernel.lst
MAP = kernel.map
 
AFLAGS +=
LFLAGS += --no-undefined
 
help:
	@echo "Targets: help clean pi <filename>.img"
 
pi: main.img
 
clean:
	rm -rf *.img *.lst *.map
# *.elf *.o deleted by compiler once done with
 
new: clean pi
 
%.img: %.elf
	$(TOOLPFIX)objcopy $< -O binary $@
	$(TOOLPFIX)objcopy $< -O binary $(TARGET)
 
%.elf: %.o $(LINKER)
	$(TOOLPFIX)ld $(LFLAGS) $< -Map $(MAP) -o $@ -T $(LINKER)
	$(TOOLPFIX)objdump -d $@ > $(LST)
 
%.o: %.s
	$(TOOLPFIX)as $(AFLAGS) $< -o $@

Using this makefile, all you have to do is type make and a kernel.img file will be created. Simply copy that file to the (micro-)SD card and you get yourself a system that blinks an LED! Cool, right?!

Doing it in C

Note: All codes/files in this section is available in my1barepi repository

The equivalent code to blink the ACT LED in C:

main.c
#define GPIO_BASE 0x20200000
#define GPIO_FSEL 0x00
#define GPIO_FSET 0x07
#define GPIO_FCLR 0x0A
#define GPIO_ACT_LED 47
 
/**  needs to be global, coz local needs stack => stack pointer! */
unsigned int *gpio, loop;
 
void main(void)
{
	/** point to gpio access register */
	gpio = (unsigned int*) GPIO_BASE;
	/** configure gpio as output */
	gpio[GPIO_FSEL+(GPIO_ACT_LED/10)] = 1 << (GPIO_ACT_LED%10)*3;
	/** main loop */
	while(1)
	{
		/** clear pin - on led! */
		gpio[GPIO_FCLR+(GPIO_ACT_LED/32)] = 1 << (GPIO_ACT_LED%32);
		/** delay a bit to allow us see the light! */
		for(loop=0;loop<0x3F0000;loop++);
		/** set pin - off led! */
		gpio[GPIO_FSET+(GPIO_ACT_LED/32)] = 1 << (GPIO_ACT_LED%32);
		/** delay a bit to allow us see the blink! */
		for(loop=0;loop<0x3F0000;loop++);
	}
}

A slightly modified linker file:

kernel.ld
SECTIONS {
	. = 0x8000;
	.text : { /** code segment */
		KEEP(*(.text.startup))
		*(.text)
	}
	.data : { /** data segment */
		*(COMMON)
		*(.data)
	}
	/DISCARD/ : { /** discard all other... */
		*(*)
	}
}

And, a slightly modified makefile:

Makefile
# to generate raspberry pi bare-metal code (kernel.img)
 
ifeq ($(OS),Windows_NT)
TOOLPATH ?= /c/users/public/tool/xtool-arm/bin/
else
TOOLPATH ?= /home/share/tool/xtool-arm/bin/
endif
TOOLPFIX ?= $(TOOLPATH)arm-none-eabi-
 
LINKER = kernel.ld
TARGET = kernel.img
LST = kernel.lst
MAP = kernel.map
 
CFLAGS += -mfpu=vfp -mfloat-abi=hard -march=armv6zk -mtune=arm1176jzf-s
CFLAGS += -nostdlib -nostartfiles -ffreestanding -Wall
LFLAGS += --no-undefined
 
help:
	@echo "Targets: help clean pi <filename>.img"
 
pi: main.img
 
clean:
	rm -rf *.img *.lst *.map
# *.elf *.o deleted by compiler once done with
 
new: clean pi
 
%.img: %.elf
	$(TOOLPFIX)objcopy $< -O binary $@
	$(TOOLPFIX)objcopy $< -O binary $(TARGET)
 
%.elf: %.o $(LINKER)
	$(TOOLPFIX)ld $(LFLAGS) $< -Map $(MAP) -o $@ -T $(LINKER)
	$(TOOLPFIX)objdump -d $@ > $(LST)
 
%.o: %.c
	$(TOOLPFIX)gcc $(CFLAGS) -c $< -o $@

And you get a bigger binary of the same thing (as discussed in lecture)! Also, you should notice that even though both programs (the main.s and main.c uses the same loop value (0x3f0000), the kernel.img created from main.c will result in a slower blinking LED.

Utilizing C Macro Definition

The C code can be rewritten to utilize C macro (function macro).

main.c
/*----------------------------------------------------------------------------*/
#define GPIO_BASE 0x20200000
#define GPIO_FSEL 0x00
#define GPIO_FSET 0x07
#define GPIO_FCLR 0x0A
#define GPIO_ACT_LED 47
/*----------------------------------------------------------------------------*/
/** using macros :p */
#define gpio_output(x) gpio[GPIO_FSEL+(x/10)]=1<<(x%10)*3
#define gpio_clr(x) gpio[GPIO_FCLR+(x/32)]=1<<(x%32)
#define gpio_set(x) gpio[GPIO_FSET+(x/32)]=1<<(x%32)
/*----------------------------------------------------------------------------*/
/** volatile coz -O2 compiler option would otherwise kill them? */
volatile unsigned int *gpio, loop;
/*----------------------------------------------------------------------------*/
void main(void)
{
	/** point to gpio access register */
	gpio = (unsigned int*) GPIO_BASE;
	/** configure gpio as output */
	gpio_output(GPIO_ACT_LED);
	/** main loop */
	while(1)
	{
		/** clear pin! */
		gpio_clr(GPIO_ACT_LED);
		/** delay a bit to allow us see the blink! */
		for(loop=0;loop<0x200000;loop++);
		/** set pin! */
		gpio_set(GPIO_ACT_LED);
		/** delay a bit to allow us see the blink! */
		for(loop=0;loop<0x200000;loop++);
	}
}
/*----------------------------------------------------------------------------*/

ACT II: From Switch to LED

A system is rarely complete without an input. Let us now try to write a software that reads the status of a button (a.k.a. reset switch) and drive an LED accordingly.

Something About Input

Here are some information that is needed to read GPIO pin status.

Some relevant registers

Related Registers to use GPIO as Input

So, to actually check the pin status we need to check the following.

GPIO Level Register

This is a simple program that detects an input level and updates an LED accordingly.

main.c
/*----------------------------------------------------------------------------*/
#define GPIO_BASE 0x20200000
#define GPIO_FSEL 0x00
#define GPIO_FSET 0x07
#define GPIO_FCLR 0x0A
#define GPIO_FGET 0x0D
//*----------------------------------------------------------------------------*/
#define MY_LED 47
#define MY_SWITCH 3
/*----------------------------------------------------------------------------*/
/** using macros :p */
#define gpio_output(x) gpio[GPIO_FSEL+(x/10)]=1<<(x%10)*3
#define gpio_clr(x) gpio[GPIO_FCLR+(x/32)]=1<<(x%32)
#define gpio_set(x) gpio[GPIO_FSET+(x/32)]=1<<(x%32)
#define gpio_get(x) (gpio[GPIO_FGET+(x/32)]&(1<<(x%32)))
/*----------------------------------------------------------------------------*/
volatile unsigned int *gpio;
/*----------------------------------------------------------------------------*/
void main(void)
{
	/** base register address */
	gpio = (unsigned int*) GPIO_BASE;
	/** configure gpio as output, by default it is an input pin */
	gpio_output(MY_LED);
	/** main loop */
	while(1)
	{
		if(gpio_get(MY_SWITCH)) gpio_set(MY_LED);
		else gpio_clr(MY_LED);
	}
}
/*----------------------------------------------------------------------------*/

Something About Input (Events)

Notice that we can also detect events (and edges)!

More on event-related thingy...

Info on event detect status registers and edge detect enable registers

GPIO Event Detect Status

GPIO Asynchronous Rising Edge Enable

The event(s) status would be very useful, but we will pin this up and revisit the topic later.

ACT III : Using my1barepi

Using my1barepi makes things easier.

Starting with the basics…

Revisiting Basic Digital Interfacing

Unlike boolean logic, digital electronics has a third-state condition (aptly named tri-state condition) which cannot be fit into the common VDD = logic 1 and GND = logic 0 presumptions. A more suitable definition for logic 1 in digital electronics is node charging/discharging capabilities: logic 1 is the ability to charge a node to voltage VDD (actually over a certain threshold, but that is for another discussion), and logic 0 is the ability to discharge a node to GROUND reference voltage.

Logic 1 - Node charged to VDD Logic 0 - Node discharged to GND
Node being charged Node being discharged
Also known as sourcing current Also known as sinking current

Using this, the tri-state condition is when a node is NOT being charged or discharged (i.e. floating, left for other device to pull the node to VDD or GND). This is very useful since there are times when a tri-state condition is required (e.g. I2C interface, where open collector/drain circuit is commonly used).

Things To Tinker

Thing 1 Using the given example code, try to blink an external LED (off the Pi, on a breadboard). You will have to select a GPIO pin for that. Then, try to do 2 alternate-blinking LEDs. Investigate the issue when the two selected GPIO number have the same digit on the 10s (e.g. 11 and 16, 24 and 22).

Thing 2 Based on the GPIO input example, try to write a code that blinks an external LED ONLY when the input is at logic 'LO'. Otherwise, the LED should be turned OFF.

Thing 3 Use a seven segment. Create a simple up (OR down) counter.

Thing 4 Upgrade the previous Thingy - use reset switches to start/stop the counting and control the direction.

archive/pgt302lab01.txt · Last modified: 2020/10/21 09:26 by 127.0.0.1