Engineering Design
Lab exercise 2
Nios II Processor Software Development
Note: To do this lab exercise you need the textbook and it’s CD.
Part 1
Designing systems with embedded processors requires both hardware and software design elements. In this lab you will follow Tutorial III of the text book (Chapter 16) and step through the software development for a Nios II processor executing on the UP3 board.
- Follow the steps in Section 16.2 of the text book and start a Nios II software project
- Follow the steps in Section 16.4 of the text book and generate the Nios II system library
- Follow the steps in Section 16.6 of the text book and write the “Hello World” C program. Instead of C program given in Figure 16.8 use the following simplified version:
#include “rpds_software.h”
int main (void) {
printf(“Hello world\n”);
return (0);
}
- Follow the steps in Section 16.7 and download the Nios II hardware and software projects.
- Follow the steps in Section 16.8 of the text book and execute the software.
Part 2
In many occasions (probably in some of your projects), the Nios processor should be able to communicate with components such as LEDs, dipswitches, LCD display, SRAM, Flash memory and etc. Peripheral devices are memory mapped in Nios II. That is, each device is mapped to a portion of memory. The device’s data and control registers can be accessed by writing to (or reading from) the corresponding memory locations. The system.h file indicates which devices are available for a particular Nios II processor implementation. In fact, the system.h file describes each peripheral in the system and provides the following details:
•The hardware configuration of the peripheral
•The base address
•The IRQ (interrupt request) priority (if any)
•A symbolic name for the peripheral
Accessing and communicating with Nios II peripherals can be accomplished in three general methods: direct register access, hardware abstraction layer (HAL) interface and standard ANSI C library functions. In this lab we review HAL approach. The HAL provides the C-language macros IORD and IOWR that expandto the appropriate assembly instructions. These macros should be used to access device registers. See the following table for a short explanation on IORD and IOWR. In the following we use HAL instructions to access and communicate with dipswitches, LEDs, LCD and pushbuttons on the UP3 board.
LED and Dipswitches
There are four dipswitches on the UP3 board. The base address for dipswitches is SWITCHES_BASE which is defined in system.h. The status of each switch is represented by one of the four least significant bits of the content of SWITCHES-BASE.
There are four user definable LEDs on UP3 board. Base address for LED is LED_BASE which is defined in system.h. The LEDs can be turned on and off by writing to the four least significant bits of the content of LED_BASE.
Edit your c file and enter the following code. It will read the status of dipswitches and turn the LEDs on or off accordingly.
#include "rpds_software.h";
int main(void){
unsigned char led_val, swi;
while(1){
swi = IORD(SWITCHES_BASE,0);
led_val = swi;
IOWR(LEDS_BASE,0, (led_val & 0xF));
}
return(0);
}
Build the project and execute it on the Nios. Change position of dipswitches and make sure the LEDs reflect the status of dipswitches.
LCD
In order to use the LCD, it should be first initialized. This can be done by calling the lcd_init function. To write amessage on LCD the following instruction should be used
IOWR( LCD_BASE, LCD_WR_DATA_REG, MSSG) where MSSG is a character or part of a character string.
Add the following two lines to your rpds_software.h header file:
# define LCD_WR_COMMAND_REG 0
# define LCD_WR_DATA_REG 2
Modify your C code to the following:
#include "rpds_software.h";
void lcd_init( void );
int main(void){
int i;
char message[18]="CoE4OI5,Lab 02";
lcd_init();
for (i=0; i<17; i++){
IOWR(LCD_BASE, LCD_WR_DATA_REG,message[i]);
usleep(100);
}
return(0);
}
void lcd_init( void ) {
/* Set Function Code Four Times -- 8-bit, 2 line, 5x7 mode */
IOWR( LCD_BASE, LCD_WR_COMMAND_REG, 0x38 );
usleep(4100); /* Wait 4.1 ms */
IOWR( LCD_BASE, LCD_WR_COMMAND_REG, 0x38 );
usleep(100); /* Wait 100 us */
IOWR( LCD_BASE, LCD_WR_COMMAND_REG, 0x38 );
usleep(5000); /* Wait 5.0 ms */
IOWR( LCD_BASE, LCD_WR_COMMAND_REG, 0x38 );
usleep(100);
/* Set Display to OFF */
IOWR( LCD_BASE, LCD_WR_COMMAND_REG, 0x08 );
usleep(100);
/* Set Display to ON */
IOWR( LCD_BASE, LCD_WR_COMMAND_REG, 0x0C );
usleep(100);
/* Set Entry Mode -- Cursor increment, display doesn't shift */
IOWR( LCD_BASE, LCD_WR_COMMAND_REG, 0x06 );
usleep(100);
/* Set the cursor to the home position */
IOWR( LCD_BASE, LCD_WR_COMMAND_REG, 0x02 );
usleep(2000);
/* Clear the display */
IOWR( LCD_BASE, LCD_WR_COMMAND_REG, 0x01 );
usleep(2000);
}
Build your project and execute it on Nios II processor.
Pushbuttons and Interrupts
In the Nios reference design used in this lab, the four pushbuttons on the UP3 board can interrupt the Nios CPU. In order to service the interrupt, an interrupt service routine (ISR) should be written and we also have to tell the CPU that this ISR correspond to which interrupt source. Altera has provided the following instruction for doing this:
int alt_irq_register (alt_u32 id, void* context, void (*isr)(void*, alt_u32)). This function is part of the sys/alt_irq.h header file, so you have to include this header file if you are using this function. The alt_irq_register() function assigns an ISR to a source of interrupt. The function has the following arguments:
- id is the hardware interrupt number for the device, as defined insystem.h. Interrupt priority corresponds inversely to the IRQnumber. Therefore, IRQ0 represents the highest priority interruptand IRQ31 is the lowest.
- context is a pointer used to pass context-specific information to theISR, and can point to any ISR-specific information. The context valueis opaque to the HAL; it is provided entirely for the benefit of theuser-defined ISR.
- isr is a pointer to the function that is called in response to IRQnumber id. The two input arguments provided to this function arethe context pointer and id. Registering a null pointer for isrresults in the interrupt being disabled.
The pushbuttons on the UP3 are represented by a 4-bit parallel I/O (PIO) peripheral called buttons in the Nios II processor design you are using for this lab experiment. Macros are included in the altera_avalon_pio_regs.h file that read and write from the control and data registers in PIO. Since there are 4 pushbuttons, we have to read the content of a register called edgecapture register to determine which pushbutton was pressed. Bit n in the edgecapture register is set to 1 whenever an edge is detected on input port n. An Avalon macro can read the edgecapture register to determine if an edge has occurred on any of the PIO input ports. You have to clear edgecapture after reading it. The type of edge(s) to detect is fixed in hardware at system generation time. The edgecapture register only exists when the hardware is configured to capture edges. If the core is not configured to capture edges, reading from edgecapture returns an undefined value, and writing to edgecapture has no effect.
Let’s assume we have written an ISR for pushbutton. Let’s call it handle_button_interrupts(we will write this function shortly). We have to assign this ISR to the pushbuttons. This is done like this:
alt_irq_register(BUTTONS_IRQ, edge_capture_ptr, handle_button_interrupts);
Add the following two lines to your rpds_software.h header file:
#include "sys/alt_irq.h"
#include "altera_avalon_pio_regs.h"
Modify your C code as follows:
#include “rpds_software.h”
staticvoid handle_button_interrupts(void* context, alt_u32 id);
/* Declare a global variable to hold the edge capture value. */
volatileint edge_capture;
int main(void){
/* Recast the edge_capture pointer to match the
alt_irq_register() function prototype. */
void* edge_capture_ptr = (void*) &edge_capture;
/* Enable all 4 button interrupts. */
IOWR_ALTERA_AVALON_PIO_IRQ_MASK(BUTTONS_BASE, 0xf);
/* Reset the edge capture register. */
IOWR_ALTERA_AVALON_PIO_EDGE_CAP(BUTTONS_BASE, 0x0);
/* Register the ISR. */
alt_irq_register( BUTTONS_IRQ, edge_capture_ptr, handle_button_interrupts);
}
staticvoid handle_button_interrupts(void* context, alt_u32 id)
{
/* cast the context pointer to an integer pointer. */
volatileint* edge_capture_ptr = (volatileint*) context;
/*
* Read the edge capture register on the button PIO.
* Store value.
*/
*edge_capture_ptr= IORD_ALTERA_AVALON_PIO_EDGE_CAP(BUTTONS_BASE);
/* Write to the edge capture register to reset it. */
IOWR_ALTERA_AVALON_PIO_EDGE_CAP(BUTTONS_BASE, 0);
/* reset interrupt capability for the Button PIO. */
IOWR_ALTERA_AVALON_PIO_IRQ_MASK(BUTTONS_BASE, 0xf);
if ( *edge_capture_ptr == 1)
printf("hello world\n");
if ( *edge_capture_ptr == 4)
{
IOWR(LEDS_BASE,0, (1 & 0xF));
usleep(1000000);
IOWR(LEDS_BASE,0, (0 & 0xF));
}
}
This ISR prints “hello world” when one of the pushbuttons is pressed and turns on one of the LEDs for 1000000 micro seconds (1 second) when one of the other pushbuttons is pressed. Build your project and run it on Nios processor. Press the first pushbuttons. You should see hello world on your monitor. Press the last one. LED will come on for 1 second.