Hello,
I’m trying to set up an interrupt in RISC-V using an external pushbutton called S1 connected to a GPIO pin. When S1 is pressed, it should trigger an interrupt, go to an ISR called S1_interrupt and turn on an LED at GPIO pin 10 for 2 seconds. Once this is done, the interrupt should clear, and the program return to waiting for another press of S1, where it enters the same ISR, and so on.
The problem I’m having is that pressing S1 does not provide an interrupt.
Some data:
S1 pushbutton address: GPIO pin 12
LED address: GPIO pin 10
S1 is active low
I would like to see an interrupt when S1 is pressed (GPIO input value on pin 12 is 0).
I know the S1 and LED work as I have used it in other projects (S1 being polled).
I can also see from the program when in Debug mode that when I press S1, bit 12 in the GPIO Input Register (0x10012000) changes from a 1 to a 0).
So basically, I can single-step with the debugger through to the section where the Main loop is. However, when I press (and hold) S1, and continue single-stepping, it just cycles through the NOP instruction, never recognizing that an interrupt has occurred.
I have included the code here:
.section .text
.globl _start
// Constants
GPIO_BASE = 0x10012000 // GPIO base address
GPIO_OUTPUT = 0x0C // GPIO output register offset
GPIO_OUPUT_ENABLE = 0x08 // GPIO output enablr register offset
GPIO_INPUT = 0x00 // GPIO input register offset
GPIO_INPUT_ENABLE = 0x04 // GPIO input enable register offset
GPIO_LOW_INTERRUPT_ENABLE = 0x030 // Low interrupt enable register offset
GPIO_LOW_INTERRUPT_PENDING = 0x034 // Low interrupt pending register offset
GPIO_IO_FUNCTION_ENABLE = 0x038 // I/O function enable register offset
GPIO_IO_FUNCTION_SELECT = 0x03C // I/O function select register offset
PLIC_BASE = 0x0C000000 // PLIC base address
PLIC_ENABLE = 0x2000 // PLIC interrupt enable register offset
PLIC_PRIORITY = 0x0004 // PLIC priority register offset
PLIC_THRESHOLD = 0x200000 // PLIC threshold register offset
PLIC_CLAIM = 0x200004 // PLIC claim/complete register offset
S1_GPIO_PIN = 12 // S1 pushbutton pin number (active low)
LED_GPIO_PIN = 10 // LED pin number
S1_IRQ = 12 // IRQ number for GPIO pin 12
S1_PRIORITY = 7 // Interrupt priority for S1
TIME_DELAY = 64000000 // Delay for 2 seconds (assuming 32MHz clock)
// Entry point
_start:
// Set up mtvec with the address of the interrupt vector (S1_interrupt)
la t0, S1_interrupt
csrw mtvec, t0
// Enable external machine interrupts in the mie register
li t0, (1 << 11) // Enable machine external interrupts (MEIE)
csrs mie, t0
// Enable global interrupts in the mstatus register
li t0, (1 << 3) // Set MIE (Machine Interrupt Enable) in mstatus
csrs mstatus, t0
// Enable GPIO input for S1 (GPIO pin 12)
li t1, GPIO_BASE // Load base address of GPIO
lw t2, GPIO_INPUT_ENABLE(t1) // Read current input enable settings
li t3, (1 << S1_GPIO_PIN) // Create mask for GPIO pin 12
or t2, t2, t3 // Enable input for pin 12
sw t2, GPIO_INPUT_ENABLE(t1) // Write back to input enable register
// Enable GPIO output for LED (GPIO pin 10)
// li t1, GPIO_BASE // Load base address of GPIO
lw t2, GPIO_OUPUT_ENABLE(t1) // Read current input enable settings
li t3, (1 << LED_GPIO_PIN) // Create mask for GPIO pin 10
or t2, t2, t3 // Enable output for pin 10
sw t2, GPIO_OUPUT_ENABLE(t1) // Write back to input enable register
// Enable I/O function for S1 (GPIO pin 12)
// lw t2, GPIO_IO_FUNCTION_ENABLE(t1) // Read current I/O function enable settings
// or t2, t2, t3 // Enable I/O function for pin 12
// sw t2, GPIO_IO_FUNCTION_ENABLE(t1) // Write back to I/O function enable register
// Select I/O function for S1 (GPIO pin 12)
// lw t2, GPIO_IO_FUNCTION_SELECT(t1) // Read current I/O function select settings
// or t2, t2, t3 // Select the I/O function for pin 12
// sw t2, GPIO_IO_FUNCTION_SELECT(t1) // Write back to I/O function select register
// Configure GPIO interrupt to trigger when S1 (GPIO pin 12) is low
li t1, GPIO_BASE + GPIO_LOW_INTERRUPT_ENABLE // GPIO low interrupt enable register
lw t2, 0(t1) // Read current interrupt enable settings
li t3, (1 << S1_GPIO_PIN) // Mask for GPIO pin 12
or t2, t2, t3 // Enable interrupt for pin 12
sw t2, 0(t1) // Write back to interrupt enable register
// Set PLIC priority for S1 interrupt
li t1, PLIC_BASE // Load base address of PLIC
li t2, S1_IRQ // Load S1_IRQ into register t2
slli t2, t2, 2 // Shift IRQ number left by 2 bits to calculate offset
add t2, t1, t2
li t3, S1_PRIORITY // Set interrupt priority level to 7
sw t3, PLIC_PRIORITY(t2) // Write priority to PLIC
// Enable S1 interrupt in PLIC
li t1, PLIC_BASE + PLIC_ENABLE // Calculate address of PLIC interrupt enable register
li t2, (1 << S1_IRQ) // Enable interrupt for S1
sw t2, 0(t1) // Write to PLIC enable register
// Set PLIC threshold to 0 (allow all interrupts)
li t1, PLIC_BASE + PLIC_THRESHOLD // Calculate address of PLIC threshold register
li t2, 0 // Set threshold to 0
sw t2, 0(t1)
// Main loop: Just keep looping, let the interrupt handle events
main_loop:
nop // Do nothing (optional, can insert code here)
j main_loop // Infinite loop
// Interrupt Service Routine (ISR) for S1 interrupt
S1_interrupt:
// Check if the interrupt was caused by S1 (GPIO pin 12)
li t1, PLIC_BASE + PLIC_CLAIM // Calculate address of PLIC claim/complete register
lw t0, 0(t1) // Read the interrupt claim register
li t2, S1_IRQ
bne t0, t2, end_interrupt // If not S1, return
// Turn on LED (set GPIO pin 10 high)
li t1, GPIO_BASE + GPIO_OUTPUT // Load base address of GPIO output
lw t2, 0(t1) // Read current GPIO output value
li t3, (1 << LED_GPIO_PIN) // Mask for GPIO pin 10
or t2, t2, t3 // Set pin 10 to high (turn on LED)
sw t2, 0(t1) // Write back to GPIO output register
// Delay for 2 seconds (time delay loop)
li t0, TIME_DELAY
delay_loop:
addi t0, t0, -1
bnez t0, delay_loop
// Turn off LED (set GPIO pin 10 low)
lw t2, 0(t1) // Read current GPIO output value
li t3, (1 << LED_GPIO_PIN) // Mask for GPIO pin 10
not t3, t3 // Invert mask to clear bit
and t2, t2, t3 // Set pin 10 to low (turn off LED)
sw t2, 0(t1) // Write back to GPIO output register
// Clear the interrupt pending flag for S1 (GPIO pin 12)
li t1, GPIO_BASE + GPIO_LOW_INTERRUPT_PENDING // Load base address of interrupt pending register
li t0, (1 << S1_GPIO_PIN) // Create mask for GPIO pin 12
sw t0, 0(t1) // Clear the pending bit for pin 12
// Acknowledge the interrupt in the PLIC (write the IRQ number to PLIC claim/complete register)
li t1, PLIC_BASE + PLIC_CLAIM // Calculate address of PLIC claim/complete register
li t0, S1_IRQ
sw t0, 0(t1)
end_interrupt:
mret // Return from interrupt
Any help would be greatly appreciated.
Julius Olajos