ARM BlinkLED¶
In this tutorial, we will compile a program for the HPS (Arm Cortex A) that will be able to control the LEDs and read the buttons on the board that are connected to the HPS.
Notice from the previous diagram extracted from the user manual, there are LEDs and buttons directly connected to the HPS, and others connected to the FPGA. There are two possible approaches to programming the HPS:
baremetal¶
We would create a program that would run on the ARM HPS without any operating system. As detailed in the diagram:
In this way, the application must be able to perform all the necessary HW initialization for the processor to run correctly. If the application is executed on an operating system, this entire compilation stage is the responsibility of the OS. For this, it is advisable to use the ARM IDE called DS-5
Operating System¶
There are several alternatives for embedded operating systems, everything will depend on the application specification. It is necessary to know if there are real-time requirements, if so, consider using an RTOS or some operating system with this functionality (there is a patch in the Linux kernel that makes it more or less real-time). If it is an application that requires network, video, data processing, it is worth considering using a Linux (Android), as there are tools that facilitate application development on this platform (there is a lot already done and a gigantic community).
With the use of an operating system, the part referring to HW is the responsibility of the kernel (or the developers who are adapting the kernel to HW, which is our case). There are several gains from using a Linux-type operating system, we can list some:
- Device drivers
- Portability
- Security
- Network
The losses are also significant: greater memory occupation, greater latencies, slow boot...
Blink LED Software¶
We will compile a program and run it on Embedded Linux, this program will be executed in the user space. For this, we will use the toolchain from the previous tutorial. We will use as a basis the example code from Terasic available in the repository: DE10-Standard-v.1.3.0-SystemCD/Demonstration/SoC/my_first_hps. And cross-compile this code for our HPS using the Makefile in the folder.
About the program¶
This program controls an LED that is connected to the ARM part of the chip:
The pins are controlled by the GPIO peripheral of the HPS (ARM), for this it is necessary to access this peripheral from Linux, this is done in a similar way as we did in Embedded Computing, a pointer that points to the memory region of the component and configures its registers:
In baremetal systems, we can simply create a pointer that points to the memory region we want to change, in Linux we cannot do this directly (via userspace) as operating systems work with memory maps where the 'virtual' address does not represent the real address (remember Sys-HW-SW). In Linux, to access real memory we must map real memory to virtual using the mmap
command:
int main(int argc, char **argv) {
void *virtual_base;
int fd;
//...
// map the address space for the LED registers into user space so we can interact with them.
// we'll actually map in the entire CSR span of the HPS since we want to access various registers within that span
if( ( fd = open( "/dev/mem", ( O_RDWR | O_SYNC ) ) ) == -1 ) {
printf( "ERROR: could not open \"/dev/mem\"...\n" );
return( 1 );
}
virtual_base = mmap( NULL, HW_REGS_SPAN, ( PROT_READ | PROT_WRITE ), MAP_SHARED, fd, HW_REGS_BASE );
Now that the virtual_base
pointer points to the GPIO peripheral, and we can manipulate this address just like we did in Embedded Computing:
while(1){
scan_input = alt_read_word( ( virtual_base + ( ( uint32_t )( ALT_GPIO1_EXT_PORTA_ADDR ) & ( uint32_t )( HW_REGS_MASK ) ) ) );
if(~scan_input&BUTTON_MASK)
alt_setbits_word( ( virtual_base + ( ( uint32_t )( ALT_GPIO1_SWPORTA_DR_ADDR ) & ( uint32_t )( HW_REGS_MASK ) ) ), BIT_LED );
else
alt_clrbits_word( ( virtual_base + ( ( uint32_t )( ALT_GPIO1_SWPORTA_DR_ADDR ) & ( uint32_t )( HW_REGS_MASK ) ) ), BIT_LED );
}
Note
This Makefile only works because we configured our bashrc
with the system variables it uses.
For example, the line SOCEDS_ROOT ?= $(SOCEDS_DEST_ROOT)
uses the variable SOCEDS_DEST_ROOT
that was configured in the previous tutorial, as well as the arm-linux-gnueabihf-
...
Task
Running on target
¶
Now just copy the binary created by the compilation to the memory card and test our program on the target
(HPS). With the memory card on the host
(your computer), copy the binary file: hps_gpio
to the folder: /home/root/
on the memory card. Note that there are two partitions, you should copy to the one that has the root
.
Note
You may have to copy using sudo, in my case I run:
$ sudo cp hps_gpio /media/corsi/847f4797-311c-4286-8370-9d5573b201d7/home/root
Note
Whenever you handle an external memory device, it is advisable to flush the cache to force Linux to change the external device, otherwise the change may only stay in the local memory to the PC.
$ sync
The sync function is blocking, it will be locked while Linux flushes the data.
Task
Practicting
Development Flow¶
This development flow isn't the best, right? It's good to program on the host
, but this scheme of having to keep removing and inserting memory cards, waiting for the target's Linux to boot, logging in, and testing is not good for anyone. There are several solutions to improve this, each with its advantage/disadvantage:
- build on the target itself (bad for the programmer, great for dependencies, easy to debug, slow)
- create an ARM VM and compile on it (good for the programmer, great for dependencies, +- easy to debug, fast, hard to configure)
- cross-compile (good for the programmer, bad for dependencies, hard to debug, fast)
In Assigment 1 we will improve our compilation and testing system.