I've been experiencing some unexpected behaviour while using interrupts in PIO to synchronize the operation of two state machines.
Code explanation:
Although the project itself is quite complicated, the relevant parts of the code are the get_cal() function in the main C code, and the two PIO programs 'calibration' and 'residue'. 'calibration' waits for the main C code to send a value to the OSR via jmp !OSRE, while being in a dithering loop that toggles two GPIO pins (Autopull enabled with a threshold of 32 bits). Once a value has been sent to the OSR (the value itself not being important, it's just being used to tell the state machine that it's supposed to do something), it breaks out of the dither loop to execute the rest of the program, which involves setting the state of a couple of GPIO pins and firing an interrupt. Once a certain state is set, irq 0 is fired by irq wait 0. This should wait for the second state machine running the 'residue' part of the code. 'residue' first waits for irq 0 via wait 0 irq 0, which should wait for interrupt 0, and after receiving it, not clear it immediately. It then executes a loop to read an SPI ADC using in pins 1. The loop should run 15 times to read 15 bits. Autopull is enabled with a threshold of 15 bits. After that, control is handed back to the 'calibration' state machine by irq clear 0. 'calibration' sets the GPIO states a couple more times and calls an interrupt afterwards. In total, 'calibration' sets irq 0 three times. After that, it should go back to the dithering loop and wait for the C code to put another value in the OSR.
Relevant code snippets:
get_cal():
calibration:
residue:
Actual code behaviour:
Depending on exactly what code I was using and what I had commented out, I got a range of different unexpected behaviour. I am monitoring the relevant GPIO pins with an oscilloscope.
With the code as posted, upon startup, the calibration state machine starts up correctly and enters the dithering loop. the residue state machine, on the other hand, runs its loop five times, even though it should be waiting for an interrupt from the calibration state machine. After the C code in the get_cal() function sends a value (in this case, just a 1) to the calibration state machine's OSR, the PIO code correctly breaks out of the dithering loop, sets GPIO states and fires irq 0 correctly thrice, as expected. The residue state machine also responds correctly.
After this, however, the calibration state machine does not go back to the dithering loop as it should. It does, however, respond to further data in the OSR and sets the pins and fires the interrupts, to which the residue state machine responds. I am not sure why it does not go back to the dithering loop, despite there being a jmp dither_start at the end.
I wanted to see what happened if I cleared the FIFOs after each cycle of sending and reading values from both state machines, so I added two pio_sm_clear_fifos() to clear each state machine's FIFOs.
What happens now is that the 'residue' state machine runs 4 ro 5 every time the get_cal() function is called, but there is no associated activity from the 'calibration' state machine every time, it only runs intermittently. This is the part that makes least sense to me, since the 'residue' state machine should totally depend on the 'calibration' state machine firing irq 0. Since this change in behaviour happened because I am clearing the FIFOs, I assume the problem lies somewhere with the in pins 1 in the 'residue' program and autopush that makes the code run regardless of if irq 0 is fired or not.
Next, I tried changing all the irq wait 0 in the calibration program to irq 0, and in the residue program changed wait 0 irq 0 to wait 1 irq 0 and removed the irq clear 0 line. Dithering happens normally, but the 'calibration' state machine does not respond to a value being sent to OSR.
I understand that debugging RP2040 code that makes use of PIO can be a little tricky and that more context might be needed, so here's the whole code: https://github.com/NNNILabs/Multislope-3I/tree/main/SW. I apologize for the mess, since this project is still a work-in-progress.
I'm not sure how to start making sense of what is happening. I don't know if it is 'in' and autopush messing with the PIO code, or if I'm using interrupts wrong. I have read in the RP2040 datasheets that irq rel is used to sync two state machines, but I was not able to find example code. I would really appreciate it if someone could provide some insight into this problem.
Code explanation:
Although the project itself is quite complicated, the relevant parts of the code are the get_cal() function in the main C code, and the two PIO programs 'calibration' and 'residue'. 'calibration' waits for the main C code to send a value to the OSR via jmp !OSRE, while being in a dithering loop that toggles two GPIO pins (Autopull enabled with a threshold of 32 bits). Once a value has been sent to the OSR (the value itself not being important, it's just being used to tell the state machine that it's supposed to do something), it breaks out of the dither loop to execute the rest of the program, which involves setting the state of a couple of GPIO pins and firing an interrupt. Once a certain state is set, irq 0 is fired by irq wait 0. This should wait for the second state machine running the 'residue' part of the code. 'residue' first waits for irq 0 via wait 0 irq 0, which should wait for interrupt 0, and after receiving it, not clear it immediately. It then executes a loop to read an SPI ADC using in pins 1. The loop should run 15 times to read 15 bits. Autopull is enabled with a threshold of 15 bits. After that, control is handed back to the 'calibration' state machine by irq clear 0. 'calibration' sets the GPIO states a couple more times and calls an interrupt afterwards. In total, 'calibration' sets irq 0 three times. After that, it should go back to the dithering loop and wait for the C code to put another value in the OSR.
Relevant code snippets:
get_cal():
Code:
void get_cal(){ // newInput = scanf("%s", &inputBuffer, 31); // Read input from serial port sleep_ms(100); gpio_put(LED, true); uint32_t countOne = 0; uint32_t countTwo = 0; uint32_t countThree = 0; uint32_t countFour = 0; pio_sm_put_blocking(pio, calibrationSM, 1); countOne = pio_sm_get_blocking(pio, residueSM); countTwo = pio_sm_get_blocking(pio, residueSM); countThree = pio_sm_get_blocking(pio, residueSM); // pio_sm_clear_fifos(pio, calibrationSM); // pio_sm_clear_fifos(pio, residueSM); uint32_t R1 = (countOne - countTwo); uint32_t R2 = (countThree - countTwo); // uint32_t R3 = (countThree - countFour); // RUD = (R2 > R1)? (R2 - R1) : (R1 - R2); // RUU = (R3 > R2)? (R3 - R2) : (R2 - R3); RUU = R1; RUD = R2; // printf("%d, %d\n", R1, R2); gpio_put(LED, false);}
Code:
.program calibration.wrap_targetdither_start: jmp !OSRE start set pins 0b001dither_high: jmp pin dither_high set pins 0b010 [1].wrapstart: out X 32 set pins 0b000 irq wait 0runup_u: ; set pins 0b001 [13] ; set pins 0b010 [1] set pins 0b010 [1] set pins 0b000 irq wait 0runup_ud: ; set pins 0b001 [1] ; set pins 0b010 [13] set pins 0b001 [1] set pins 0b000 irq wait 0 jmp dither_start
Code:
.program residue.side_set 2.wrap_target wait 0 irq 0 side 0b10 [7] set X 14 side 0b00 [7]loop: nop side 0b01 [1] ; nop side 0b00 in pins 1 side 0b00 jmp X-- loop side 0b00 irq clear 0 side 0b00.wrap
Depending on exactly what code I was using and what I had commented out, I got a range of different unexpected behaviour. I am monitoring the relevant GPIO pins with an oscilloscope.
With the code as posted, upon startup, the calibration state machine starts up correctly and enters the dithering loop. the residue state machine, on the other hand, runs its loop five times, even though it should be waiting for an interrupt from the calibration state machine. After the C code in the get_cal() function sends a value (in this case, just a 1) to the calibration state machine's OSR, the PIO code correctly breaks out of the dithering loop, sets GPIO states and fires irq 0 correctly thrice, as expected. The residue state machine also responds correctly.
After this, however, the calibration state machine does not go back to the dithering loop as it should. It does, however, respond to further data in the OSR and sets the pins and fires the interrupts, to which the residue state machine responds. I am not sure why it does not go back to the dithering loop, despite there being a jmp dither_start at the end.
I wanted to see what happened if I cleared the FIFOs after each cycle of sending and reading values from both state machines, so I added two pio_sm_clear_fifos() to clear each state machine's FIFOs.
What happens now is that the 'residue' state machine runs 4 ro 5 every time the get_cal() function is called, but there is no associated activity from the 'calibration' state machine every time, it only runs intermittently. This is the part that makes least sense to me, since the 'residue' state machine should totally depend on the 'calibration' state machine firing irq 0. Since this change in behaviour happened because I am clearing the FIFOs, I assume the problem lies somewhere with the in pins 1 in the 'residue' program and autopush that makes the code run regardless of if irq 0 is fired or not.
Next, I tried changing all the irq wait 0 in the calibration program to irq 0, and in the residue program changed wait 0 irq 0 to wait 1 irq 0 and removed the irq clear 0 line. Dithering happens normally, but the 'calibration' state machine does not respond to a value being sent to OSR.
I understand that debugging RP2040 code that makes use of PIO can be a little tricky and that more context might be needed, so here's the whole code: https://github.com/NNNILabs/Multislope-3I/tree/main/SW. I apologize for the mess, since this project is still a work-in-progress.
I'm not sure how to start making sense of what is happening. I don't know if it is 'in' and autopush messing with the PIO code, or if I'm using interrupts wrong. I have read in the RP2040 datasheets that irq rel is used to sync two state machines, but I was not able to find example code. I would really appreciate it if someone could provide some insight into this problem.
Statistics: Posted by NNNILabs — Sun Jul 21, 2024 12:10 pm