1 February 2025
And the i8 battery is still running today. I’ve forgotten how many days it is since I started the experiment! So I guess the battery life is ‘long enough’ at this point.
Removing the USB dongle from the development board allows me to use the PC17 input and the attached push button just like a standard GPIO pin. I wrote a short program to check the status of the pin whenever a character was received from the USART, then print a 1 or 0 depending on if the button was pushed or not, and it delivers the expected results:
# read the status of the 'Download' push button connected to PC17
la s0, GPIOC_BASE
lw s1, GPIO_INDR(s0) # read input pins
PC17 = (1 << 17)
li s2, PC17
and s1, s1, s2 # isolate PC17 input
snez a0, s1
addi a0, a0, '0' # convert to ASCII
call usart_putc # *** debug *** print status of download push button
Note that I was unable to use the ‘andi’ immediate form of the logical AND instruction, because the constant value assigned to PC17 (1 << 17) was too large. So I loaded it into s2 and used that as the other source register for the instruction. I’m using the snez instruciton again to give me a one or zero result, then adding the constant value of the ASCII character ‘0’, which is 0x30, so that either an ASCII 1 or 0 is typed out to the console, in addition to whatever character is echoed.
Since the over-all goal of the project is to operate as a USB host, I’m thinking that I am just going to have to give up on my dream of using the ‘Download’ button as a reset button. However, I still think that it would still be a useful facility to add a reset button of some sort to this board. I will use PB0 as the alternate reset pin, as it is brought out next to a ground pin on the board headers. This allows me to easily add a pushbuttoon already attached to a two pin header. In a previous life, it was the power button for a PC case.
So I should go back to the GPIO initiailization code and add PB0 as an input with a pullup resistor enabled. In fact, I should go ahead and fill out all the rest of the GPIO initialization, as we’ve already touched all three of the available GPIO ports in some way. This will also include all the ‘extended’ configuration registers:
# configure GPIO
# GPIOA: 0 = LED output, active low
la s0, GPIOA_BASE
sw zero, GPIO_OUTDR(s0) # clear all outputs
li s1, 0x88888882
sw s1, GPIO_CFGLR(s0)
li s1, 0x88888888
sw s1, GPIO_CFGHR(s0)
li s1, 0x88888888
sw s1, GPIO_CFGXR(s0)
# GPIOB: 0 = reset button, 9 = MCO, 10 = USART1_TX, 11 = USART1_RX
la s0, GPIOB_BASE
#sw zero, GPIO_OUTDR(s0) # clear all outputs
PB0 = (1 << 0)
li s1, PB0
sw s1, GPIO_OUTDR(s0) # clear outputs, enable PB0 pull-up resistor
li s1, 0x88888888
sw s1, GPIO_CFGLR(s0)
li s1, 0x88888AB8
sw s1, GPIO_CFGHR(s0)
li s1, 0x88888888
sw s1, GPIO_CFGXR(s0)
# GPIOC: 17 = Download button input
la s0, GPIOC_BASE
sw zero, GPIO_OUTDR(s0) # clear all outputs
li s1, 0x88888888
sw s1, GPIO_CFGLR(s0)
li s1, 0x88888888
sw s1, GPIO_CFGHR(s0)
li s1, 0x88888888
sw s1, GPIO_CFGXR(s0)
I’m also going to shut down the MCO output for now, as it tends to bleed over into all the other signals. This makes the system clock initialization pretty simple: write a zero to RCC_CFGR0:
la s0, RCC_BASE
sw zero, RCC_CFGR0(s0) # 48 MHz, no MCO
Then I modified the code to read and report on the push button status:
# read the status of the 'reset' push button connected to PB0
la s0, GPIOB_BASE
lw s1, GPIO_INDR(s0) # read input pins
andi s1, s1, PB0 # isolate PB0 signal
snez a0, s1
addi a0, a0, '0' # convert to ASCII
call usart_putc # *** debug *** print status of download push button
Since the bitmap for PB0 (1 << 0) or 0x00000001, is ‘short enough’ to fit in the immediate field of the andi instruction, I don’t have to load it into a separate register to apply the logical function. Just how short is ‘short enough’? Let’s look at the specification again:
https://github.com/riscv/riscv-isa-manual/releases/tag/riscv-isa-release-6f69218-2025-01-30
The answer to my question is 12 bits, from the specification section 2.4.1. Integer Register-Immediate Instructions, p. 26. So typically any value between 0 and 4,095 will work. Just realize that it will sign-extend the immediate value if bit 11 is a one.
So the button works as expect at this point (just a digital input; not yet a system reset) and prints a 1 when it is not pressed and a 0 when it is pressed. That means my input pull-up resistor is working as expected and all the other configuration so far is behaving as I wish it to.
So now to connect the push button and the system_reset code. This will be done by the External Interrupt and Event Controller (EXTI), which is part of the PFIC and described in Section 7.4 of the RM starting on p. 32.
The first thing we need to do is configure EXTI0 for being triggered on a falling edge by setting bit 0 of EXTI_FTENR. Then we enable EXTI0 by setting bit 0 in the EXTI_INTENR register. Since the reset switch is on PB0, it is channeled to EXTI0, as would any signal on PA0 or PC0.
In the same way that the first function is the most fun, so is the first interrupt. There’s so much to prepare to get htings going. EXTI inputs 0-7 are grouped into one interrupt, EXTI7_0_interrupt_ID = 20. I’m planning to use the ‘shortcut’ of the vector-table free interrupt mechanism on this device, so we need to put the interrupt ID and the address of the interrupt handler routine in the VTF registers of the PFIC. Then there’s some mumbo-jumbo dealing with CSRs to both configure the chip to use the VTF system as well as enable interrupts on a global basis.
I forgot that you have to also add in the ‘enable’ bit to the handler address when setting up the VTF interrupts.
Now something I’ve done has upset the chip. It starts out OK, but after you type the second character, the system resets. What’s even more strange is that it is setting the ‘illegal instructions’ exception in the mcause CSR. So now I have to figure out what I did and undo it.
Perhaps you recall the short list of possible interrupts I wanted to handle in this application? Well, if not, it’s in the log somewhere, but if you do go back and find it, you’ll see that the very first interrupt in the list is the ‘HardFault’ handler. I have found, from my various tinkering, that when things go too far off the rails, the system usually throws an exception and lets you know what the problem is, if you only know where to look for the clues it’s leaving you. Having an interrupt handler that deals with a HardFault exception is a good way to examine the mcause CSR and print out the interrupt or exception number, as well as the address where it happened. This is not hard to implement, but I’ll need to write a little more code and at this point I think it would be best to do it tomorrow.