ch32v307 dev board, part 2
I'm continuing to build a development environment for my ch32v307 based projects. In this post, I'll explore a problem I ran into.
This is part of a series on the ch32v307 dev board
Interrupts
First problem: interrupts.
Below is an interrupt handler, which has some special needs. This processor has the ability to atomically swap register and stack state on interrupt, which saves a bunch of work. But it needs a signal to tell it when the interrupt handler is done, so the previous state and stack are restored.
The compiler also needs to know about which functions are interrupt handlers, so it can properly handle this special case. For WCH's special compiler, they've created a "WCH-Interrupt-fast" interrupt attribute. But I'm using xpack's riscv gcc 12.2.0, which does not have support for it.
Code (reformatted to be shorter):
__attribute__((interrupt("WCH-Interrupt-fast"))) void SysTick_Handler(void) {
time=SysTick->CNT;
SysTick->CTLR=0;
SysTick->SR=0;
printf("delta time:%d\r\n",time-0x20);
}
Compiler output:
src/main.c:36:1: warning: argument to 'interrupt' attribute is not '"user"', '"supervisor"', or '"machine"' [-Wattributes]
36 | void SysTick_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
| ^~~~
Assembly:
00000210 <SysTick_Handler>:
210: e000f7b7 lui a5,0xe000f
214: 4790 lw a2,8(a5)
216: 47d4 lw a3,12(a5)
218: 80c1aa23 sw a2,-2028(gp) # 20000094 <time>
21c: 0007a023 sw zero,0(a5) # e000f000 <_eusrstack+0xc0007000>
220: 6509 lui a0,0x2
222: 0007a223 sw zero,4(a5)
226: fe060593 addi a1,a2,-32
22a: c8850513 addi a0,a0,-888 # 1c88 <_read_r+0x2e>
22e: afb1 j 98a <iprintf>
Relevant bug report:
Since the compiler didn't understand the "WCH-Interrupt-fast" flag, it didn't emit a mret
instruction. The last instruction relies on iprintf
to return to the caller, which it does with an ret
instruction. This breaks future interrupts, so your interrupt handler gets called once and that's it.
The benefit from this design is it takes 14 cycles between the interrupt happening and when the SysTick counter is read at offset 214 above (SysTick->CNT
is at 0xE000F000 +8
).
Interrupt workaround: option 1
Code:
__attribute__((interrupt)) void SysTick_Handler(void) {
time=SysTick->CNT;
SysTick->CTLR=0;
SysTick->SR=0;
printf("delta time:%d\r\n",time-0x20);
}
This just uses a plain interrupt attribute, which assumes it has to preserve all the registers and stack, so it has to do a whole lot more work.
Assembly:
00000210 <SysTick_Handler>:
210: 7175 addi sp,sp,-144
212: c706 sw ra,140(sp)
[lots more sw statements]
232: e682 fsw ft0,76(sp)
[lots more fsw statements]
252: e000f7b7 lui a5,0xe000f
256: e672 fsw ft8,12(sp)
258: 4790 lw a2,8(a5)
25a: e476 fsw ft9,8(sp)
25c: e27a fsw ft10,4(sp)
25e: e07e fsw ft11,0(sp)
260: 47d4 lw a3,12(a5)
262: 80c1aa23 sw a2,-2028(gp) # 20000094 <time>
266: 0007a023 sw zero,0(a5) # e000f000 <_eusrstack+0xc0007000>
26a: 6509 lui a0,0x2
26c: fe060593 addi a1,a2,-32
270: 0007a223 sw zero,4(a5)
274: d2050513 addi a0,a0,-736 # 1d20 <_read_r+0x2c>
278: 7ac000ef jal ra,a24 <iprintf>
27c: 40ba lw ra,140(sp)
[lots more lw statements]
29c: 6036 flw ft0,76(sp)
[lots more flw statements]
2c4: 6149 addi sp,sp,144
2c6: 30200073 mret
All this extra work takes time, and it totals 48 cycles before it reads the counter at offset 258. But it properly returns with mret
and my interrupts work again.
Interrupt workaround: option 2
From the github bug report above, another workaround is offered:
__attribute__((naked)) void SysTick_Handler() {
__asm volatile ("call SysTick_Handler_real; mret");
}
void SysTick_Handler_real(void) {
time=SysTick->CNT;
SysTick->CTLR=0;
SysTick->SR=0;
printf("delta time:%d\r\n",time-0x20);
}
This splits the interrupt handler into two pieces, one that generates the needed assembly, and one that does the regular C work.
Disassembly:
00000210 <SysTick_Handler>:
210: 20b5 jal 27c <SysTick_Handler_real>
212: 30200073 mret
0000027c <SysTick_Handler_real>:
27c: e000f7b7 lui a5,0xe000f
280: 4790 lw a2,8(a5)
282: 47d4 lw a3,12(a5)
284: 80c1aa23 sw a2,-2028(gp) # 20000094 <time>
288: 0007a023 sw zero,0(a5) # e000f000 <_eusrstack+0xc0007000>
28c: 6509 lui a0,0x2
28e: 0007a223 sw zero,4(a5)
292: fe060593 addi a1,a2,-32
296: cb050513 addi a0,a0,-848 # 1cb0 <_read_r+0x50>
29a: addd j 990 <iprintf>
Because SysTick_Handler
is marked with the naked attribute, it doesn't generate a ret
instruction after the mret
(which wouldn't hurt anything, but it's not needed).
This is a little slower than the buggy interrupt handler, with the counter read at offset 280 taking 16 cycles after the interrupt fires. But it has the benefit of not breaking future interrupts.
Working Interrupts
Now that I have interrupts again, the example USB High Speed Serial (CDC) code works. Someday, I'd like to turn that example code into a better GPS PPS over USB device. My previous project used Full Speed USB, which is limited to 1 ms polling. High Speed USB should be able to poll at 0.125 ms, which I expect to improve the timing results.