Teach lesson
Nios V on DE1-SoC (4/5): HEX display and timer
Students build and upload Nios V C examples for HEX0 and the interval timer, then observe HEX0 and LEDR0 behavior on DE1-SoC hardware.
Learning Outcomes
Use validated memory-mapped display and timer peripherals in the fixed Nios V system.
Explain why seven-segment displays need encoded segment patterns.
Configure and poll the FPGA interval timer at
0xFF202000.Distinguish validated logical
NB0..NB3JP1/GPIO inputs from the unsupported FPGAcademy KEY-port browser path.Collect live evidence for peripheral behavior on a real DE1-SoC board.
Student activity preview
Activity Content
Preview only. In a class session, students can fill in responses and submit their work to the teacher.
From GPIO to peripherals
5 min
In the previous lessons, you used memory-mapped I/O for simple controls: browser NSW inputs, JP1 GPIO data, and LEDR outputs. This lesson keeps the same idea but uses two richer peripherals already built into the fixed FPGA system.
A peripheral is a hardware block that the Nios V processor controls by reading or writing a specific address. You are not changing the FPGA design in this lesson. You are replacing main.c with small C examples, building an ELF, uploading it, and checking whether the board behavior matches the register-level explanation.
The two required examples are:
- hex_counter, which writes encoded patterns to HEX0.
- timer_blink, which configures the interval timer and toggles LEDR0.
For a complete register map, use the FPGAcademy DE1-SoC Computer with Nios V user manual. This activity gives you the subset you need: HEX3_HEX0 at 0xFF200020 and the interval timer at 0xFF202000.
This lesson uses two validated register paths from the fixed Nios V system: HEX display output and interval-timer-driven LED timing.
The browser shows Nios-specific logical button controls labelled NB0..NB3. These are validated JP1/GPIO inputs, not the FPGAcademy KEY register at 0xFF200050.
For the required work in this lesson, use the HEX and timer examples below. The optional button section is self-contained if it is assigned: use NB0..NB3 as logical JP1 inputs and avoid KEY0..KEY3 wording.
Only treat a peripheral as course-ready when there is live evidence for the current LabsLand lab profile. A register can exist in the reference system and still be unsuitable for a remote activity if the browser control path is not wired to it.
HEX output register
22 min
The HEX displays are seven-segment displays. A digit is not written as the number 3; it is written as a bit pattern that turns individual segments on or off.
The hex_counter example writes segment patterns to HEX3_HEX0 at 0xFF200020.
Each byte in HEX3_HEX0 controls one display. In this starter, only the low byte is used, so only HEX0 changes. A value such as 0x3F is not the number zero in the usual sense; it is the segment pattern that lights the segments needed to draw 0.
Examples:
- Digit 0 uses pattern 0x3F.
- Digit 1 uses pattern 0x06.
- Digit A uses pattern 0x77.
Open
Nios V C IDE for DE1-SoC.Replace the starter source with the
hex_countersource below.Click
Build ELF, then upload it.Observe HEX0 on the board view for at least two different digits.
Record whether HEX0 changes through hexadecimal values.
Use this source for hex_counter:
Select the full code block before copying it into main.c; the whole starter
source should be replaced.
/* DE1-SoC Computer with Nios V -- memory-mapped I/O */
volatile int *HEX3_HEX0 = (int *) 0xFF200020; /* 7-seg HEX3..HEX0 */
static const unsigned char HEX_CODE[16] = {
0x3F, 0x06, 0x5B, 0x4F,
0x66, 0x6D, 0x7D, 0x07,
0x7F, 0x67, 0x77, 0x7C,
0x39, 0x5E, 0x79, 0x71
};
static void delay(void)
{
volatile int i;
for (i = 0; i < 2000000; i++) {
}
}
int main(void)
{
int count = 0;
while (1) {
*HEX3_HEX0 = HEX_CODE[count & 0xF];
count++;
delay();
}
return 0;
}
Use the table to record what you saw after uploading hex_counter. The expected behavior is already filled in. Add two different HEX0 digits or segment patterns you saw, then enter pass, fail, or uncertain in the result column.
Record one HEX0 check from the live board. Leave the expected behavior unless your teacher asks for a different test, write two observed digits or patterns, then type pass, fail, or uncertain.
| Expected behavior | First digit or pattern seen | Second digit or pattern seen | Result (pass/fail/uncertain) |
|---|---|---|---|
Why does the example use a lookup table of segment patterns instead of writing the integer values 0, 1, 2, and so on directly to the HEX register?
Interval timer
22 min
The timer_blink example programs the FPGA interval timer at base address 0xFF202000. It sets a period, starts the timer, polls the timeout bit, clears it, and toggles LEDR0.
The timer clock is 100 MHz, so 50,000,000 ticks is about half a second. The 32-bit period is split into two 16-bit registers because the timer interface stores the low and high halves separately.
Timer registers used here:
- STATUS at offset +0x0: bit 0, TO, becomes 1 when the period expires.
- CONTROL at offset +0x4: STOP, START, and CONT control whether the timer runs continuously.
- PERIOD_LO at offset +0x8: low 16 bits of the period.
- PERIOD_HI at offset +0xC: high 16 bits of the period.
Replace the C source with the
timer_blinksource below.Click
Build ELF, then upload it.Observe
LEDR0for at least 10 seconds.Record at least two moments where the LED state changes.
Estimate whether the blink is stable enough to use as a timing source.
Use this source for timer_blink:
Select the full code block before copying it into main.c; the whole previous
source should be replaced.
/* DE1-SoC Computer with Nios V -- memory-mapped I/O */
volatile int *LEDS = (int *) 0xFF200000; /* red LEDs */
#define TIMER_BASE 0xFF202000
#define TIMER_STATUS (*(volatile short *)(TIMER_BASE + 0x0))
#define TIMER_CONTROL (*(volatile short *)(TIMER_BASE + 0x4))
#define TIMER_PERIOD_LO (*(volatile short *)(TIMER_BASE + 0x8))
#define TIMER_PERIOD_HI (*(volatile short *)(TIMER_BASE + 0xC))
#define TIMER_TO 0x1
#define TIMER_CONT 0x2
#define TIMER_START 0x4
#define TIMER_STOP 0x8
int main(void)
{
const unsigned int half_second = 50000000u; /* 100 MHz timer clock */
int led = 0;
TIMER_CONTROL = TIMER_STOP;
TIMER_PERIOD_LO = (short)(half_second & 0xFFFFu);
TIMER_PERIOD_HI = (short)((half_second >> 16) & 0xFFFFu);
TIMER_STATUS = 0; /* clear TO */
TIMER_CONTROL = TIMER_CONT | TIMER_START;
while (1) {
if ((TIMER_STATUS & TIMER_TO) != 0) {
TIMER_STATUS = 0; /* clear TO */
led ^= 1;
*LEDS = led;
}
}
return 0;
}
Use the table to record the timer-driven blink. Keep the 10-second window unless your teacher asks for a longer test. In the evidence column, write what changed, for example off -> on -> off, then enter pass, fail, or uncertain.
Record one timer observation. Use a 10-second window, describe the LEDR0 state changes you saw, then type pass, fail, or uncertain.
| Observation window (s) | Observed LEDR0 state changes | Expected behavior | Result (pass/fail/uncertain) |
|---|---|---|---|
How is this timer-driven blink different from a software delay loop? Mention the timer period registers, the timeout bit, and one reason a hardware timer can be useful in embedded software.
Small extension modification
18 min
Choose one validated example and make a small, observable change.
Examples:
- In hex_counter, change the display sequence so it counts only even hexadecimal values.
- In hex_counter, drive two HEX digits instead of only HEX0.
- In timer_blink, change the timer period so LEDR0 blinks faster or slower.
- In timer_blink, toggle a different LED bit.
- Optional button work: read JP1 bits 0 through 3 from NB0..NB3 and show them on LEDR0..LEDR3.
Choose either the HEX or timer example.
Make one small change to the source.
Build and upload the ELF.
Observe the board and record how the behavior changed.
Submit the changed lines of code and identify which memory-mapped register each changed line affects.
Keep the modification small enough to validate in one board session. A good submission changes one behavior and can be checked with one or two observations.
Submit a concise observation note: what changed, what you expected to see, and what the live board showed.
Optional: logical NB buttons
The logical NB0..NB3 buttons are validated through the JP1 data register bits 0 through 3. They are useful for a quick polling exercise, but they are not the FPGAcademy KEY3..KEY0 port.
Minimal C pattern:
volatile int *LEDS = (int *) 0xFF200000;
volatile int *JP1 = (int *) 0xFF200060;
volatile int *JP1_DIR = (int *) 0xFF200064;
*JP1_DIR = 0;
while (1) {
*LEDS = (*JP1) & 0xF; // NB0..NB3 -> LEDR0..LEDR3
}Why does this optional exercise read JP1 bits 0 through 3 instead of reading the FPGAcademy KEY register at 0xFF200050?
If you run the optional button exercise, record one NB button you clicked and the LED bit that changed.
Prepare for the capstone
8 min
The capstone must use NSW0..NSW9 and LEDR0..LEDR9 plus at least one validated extension peripheral from this lesson.
Which extension peripheral are you most likely to use in the capstone?
Describe one capstone behavior that combines NSW/LEDR with your selected extension peripheral. Include the register address or addresses you expect to use.