GD32VF103 Longan Nano interrupts not working
Asked Answered
S

1

6

Introduction:
I bought a Longan Nano evaluation board equipped with a GD32VF103 Risc-V MCU.
I can run the video player demo, and compile and load working code using VS Code, PlatformIO and the DFU tool. Below a video of the board and the demo running.
enter image description here
https://www.youtube.com/watch?v=84_PzcNiJb4

What I want to do
I'm building a robot for a robot competition as an hobby. I used a Microchip 4809 8bit MCU for the motor controller, but I hit the limit of the MCU in running the PID controller at 2KHz and I have yet to add the current loops. I wanted to upgrade the motor controller and I decided to pick up the Longan Nano because of the LCD screen, the vastly superior CPU horsepower and to learn Risc-V.
https://www.youtube.com/watch?v=1dQMktoiuLg

Problem
I can run the pheriperals of the Longan Nano just fine in polling. I tried REALLY hard to make the interrupts work to no avail. I tried to read in polling the interrupt flags and they work that way, so I think its either a linker problem in linking the ISR handler to te Interrupt Vector table in Start.s or a configuration problem with the ECLIC.

This is a polling example that runs correctly. The red led blinks at 2Hz and pressing the boot button will toggle the blue led.

#include <gd32vf103.h>

void init()
{
    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_GPIOC);

    gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_1);
    gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_2);
    gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ,GPIO_PIN_8);

    gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_13);

    gpio_bit_set(GPIOC,GPIO_PIN_13);
    gpio_bit_set(GPIOA,GPIO_PIN_1);
    gpio_bit_set(GPIOA,GPIO_PIN_2);

    rcu_periph_clock_enable(RCU_AF);
    
    eclic_init(ECLIC_NUM_INTERRUPTS);
    
    eclic_priority_group_set(ECLIC_PRIGROUP_LEVEL3_PRIO1);
    eclic_irq_enable(EXTI5_9_IRQn, 1, 1);

    exti_deinit();
    gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA, GPIO_PIN_SOURCE_8);
    exti_init(EXTI_8, EXTI_INTERRUPT, EXTI_TRIG_BOTH);
    
    //Clear interrupt flags
    exti_interrupt_flag_clear(EXTI_8);
    eclic_clear_pending(EXTI5_9_IRQn);
    
    eclic_global_interrupt_enable();    

    return;
}

void EXTI5_9_IRQHandler()
{

}

void delay_us(unsigned int us)
{
    uint64_t start_mtime, delta_mtime;

    // Don't start measuring until we see an mtime tick
    uint64_t tmp = get_timer_value();

    do
    {
        start_mtime = get_timer_value();
    }
    while (start_mtime == tmp);

    do
    {
        delta_mtime = get_timer_value() - start_mtime;
    }
    while(delta_mtime <(SystemCoreClock/4000000.0 *us ));

    return;
}

int main()
{
    init();

    while (true)
    {
        gpio_bit_write(GPIOC, GPIO_PIN_13, (bit_status)(1-gpio_input_bit_get(GPIOC, GPIO_PIN_13)));
        delay_us(250000);

        //This correctly detects the EXTI8. Only a single acquisition at beginning
        if (exti_interrupt_flag_get(EXTI_8) != RESET)
        {
            gpio_bit_write(GPIOA, GPIO_PIN_1, (bit_status)(1-gpio_input_bit_get(GPIOA, GPIO_PIN_1)));
            exti_interrupt_flag_clear(EXTI_8);
            eclic_clear_pending(EXTI5_9_IRQn);
        }
    }

    return 0;
}

This is an interrupt example that doesn't work. And I can't figure out why.

#include <gd32vf103.h>

void init()
{
    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_GPIOC);
    
    gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_1);
    gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_2);
    gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ,GPIO_PIN_8);

    gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_13);
    
    gpio_bit_set(GPIOC,GPIO_PIN_13);
    gpio_bit_set(GPIOA,GPIO_PIN_1);
    gpio_bit_set(GPIOA,GPIO_PIN_2);
    
    rcu_periph_clock_enable(RCU_AF);
    
    eclic_init(ECLIC_NUM_INTERRUPTS);
    
    eclic_priority_group_set(ECLIC_PRIGROUP_LEVEL3_PRIO1);
    eclic_irq_enable(EXTI5_9_IRQn, 1, 1);

    exti_deinit();
    gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA, GPIO_PIN_SOURCE_8);
    exti_init(EXTI_8, EXTI_INTERRUPT, EXTI_TRIG_BOTH);
    
    //Clear interrupt flags
    exti_interrupt_flag_clear(EXTI_8);
    eclic_clear_pending(EXTI5_9_IRQn);
    
    eclic_global_interrupt_enable();    

    return;
}

void exti_5_9_handler( void )
{
    if (exti_interrupt_flag_get(EXTI_8) != RESET)
    {
        exti_interrupt_flag_clear(EXTI_8);
        eclic_clear_pending(EXTI5_9_IRQn);

        gpio_bit_write(GPIOA, GPIO_PIN_1, (bit_status)(1-gpio_input_bit_get(GPIOA, GPIO_PIN_1)));
    }
    else
    {
        gpio_bit_write(GPIOA, GPIO_PIN_2, (bit_status)(1-gpio_input_bit_get(GPIOA, GPIO_PIN_2)));
    }
    
    return;
}

void EXTI5_9_IRQHandler()
{
    exti_5_9_handler();
}

void delay_us(unsigned int us)
{
    uint64_t start_mtime, delta_mtime;

    // Don't start measuring until we see an mtime tick
    uint64_t tmp = get_timer_value();

    do
    {
        start_mtime = get_timer_value();
    }
    while (start_mtime == tmp);

    do
    {
        delta_mtime = get_timer_value() - start_mtime;
    }
    while(delta_mtime <(SystemCoreClock/4000000.0 *us ));

    return;
}

int main()
{
    init();

    while (true)
    {
        gpio_bit_write(GPIOC, GPIO_PIN_13, (bit_status)(1-gpio_input_bit_get(GPIOC, GPIO_PIN_13)));
        delay_us(250000);
    }

    return 0;
}

Question
I'd like help in making the interrupts on the Longan Nano GD32VF103 work

Solution
The problem was that the C++ compiler decided it was a good idea to change the name of the interrupt handler, and the platform.io GD32VF103 toolchain relies on an interrupt vector table with .weak symbols in Start.S that only links the interrupt handlers correctly if they are called with a special name.

#include "riscv_encoding.h"

        .section .init
    
    .weak  eclic_msip_handler
    .weak  eclic_mtip_handler

    ~more interrupt vector table enties~

    .weak  CAN0_EWMC_IRQHandler
    .weak  EXTI5_9_IRQHandler

The solution is to use the keyword extern "C" before the interrupt service routine so that the C++ compiler does not mess up with the name and the linker can automagically use its address in the interrupt vector table entry

#include <gd32vf103.h>
#define EVER (;;)

void init()
{
    //Clock the GPIO banks
    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_GPIOC);
    //Setup the R, G and B LEDs
    gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_1);
    gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_2);
    gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ,GPIO_PIN_8);
    //Setup the boot button
    gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_13);
    //Initialize the LEDs to: OFF
    gpio_bit_set(GPIOC,GPIO_PIN_13);
    gpio_bit_set(GPIOA,GPIO_PIN_1);
    gpio_bit_set(GPIOA,GPIO_PIN_2);
    
    //Clock the alternate functions
    rcu_periph_clock_enable(RCU_AF);
    
    //Initialize the ECLIC IRQ lines
    eclic_priority_group_set(ECLIC_PRIGROUP_LEVEL3_PRIO1);
    eclic_irq_enable(EXTI5_9_IRQn, 1, 1);

    //Initialize the EXTI. IRQ can be generated from GPIO edge detectors
    gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA, GPIO_PIN_SOURCE_8);
    exti_init(EXTI_8, EXTI_INTERRUPT, EXTI_TRIG_BOTH);
    
    //Clear interrupt flag. Ensure no spurious execution at start
    exti_interrupt_flag_clear(EXTI_8);
    
    //Enable the interrupts. From now on interrupt handlers can be executed
    eclic_global_interrupt_enable();    

    return;
}

extern "C"
void EXTI5_9_IRQHandler()
{
    //If: interrupt from PA8 boot button
    if (exti_interrupt_flag_get(EXTI_8) != RESET)
    {
        //Clear the interrupt from PA8 boot button
        exti_interrupt_flag_clear(EXTI_8);
        //Toggle the blue led
        gpio_bit_write(GPIOA, GPIO_PIN_2, (bit_status)(1-gpio_input_bit_get(GPIOA, GPIO_PIN_2)));
    }
    //Default: interrupt from an unhandled GPIO
    else
    {
        //Do nothing (should clear the interrupt flags)
    }
}

void delay_us(unsigned int us)
{
    uint64_t start_mtime, delta_mtime;
    
    uint64_t tmp = get_timer_value();

    do
    {
        start_mtime = get_timer_value();
    }
    while (start_mtime == tmp);

    do
    {
        delta_mtime = get_timer_value() - start_mtime;
    }
    while(delta_mtime <(SystemCoreClock/4000000.0 *us ));

    return;
}

int main()
{
    init();

    for EVER
    {
        //Toggle the RED LED
        gpio_bit_write(GPIOC, GPIO_PIN_13, (bit_status)(1-gpio_input_bit_get(GPIOC, GPIO_PIN_13)));
        //2Hz blink
        delay_us(250000);
    }

    return 0;
}

Video of the example running. Boot button toggles the blue/green LED through an ISR
https://www.youtube.com/watch?v=f135I4lzgCA

Acknowledgement
Thanks for the help! Making the ISR work was driving me mad :)

Samuelson answered 4/7, 2020 at 11:48 Comment(5)
What compiler are you using to build your binary?Billibilliard
riscv-nuclei-elf-gcc.exe -march=rv32imac -mabi=ilp32 -mcmodel=medlowSamuelson
If you disassemble your output ELF file, what is your interrupt handler called? There is a weak symbol defined with the name EXTI5_9_IRQHandler in start.S, but due to name mangling it may not end up being that name after compilation, resulting in a mismatch.Billibilliard
Here's something you can try - add extern "C" in front of void EXTI5_9_IRQHandler(void) and retest. If GCC is mangling your ISR, adding the extern "C" specifier will prevent any name mangling.Billibilliard
It works! extern "C" did the trick. I tried disassembling the .elf with riscv-nuclei-elf-objdump but i need to learn how to display user readable symbols. If you can post it as an answer, I can accept it. Thanks!Samuelson
B
1

I saw a similar thing when building code for an embedded ARM processor.

When inter-operating with assembly code from C/C++, the names of functions called from C/C++ code and implemented in assembly, or called from assembly and implemented in C/C++, must match exactly. Name mangling complicates this process.

Almost every modern compiler performs name mangling, in order to create a unique string identifier for every function compiled into the resulting executable. It's almost like a hash code for that particular function.

This whole process was introduced with the advent of C++, as C++ allows overloading functions, where C did not. Developers at the time utilized name mangling as a method of code reuse; C linkers could handle strange function names, but not multiple definitions, so all C++ functions would be mangled with additional information that specified the type and order of the parameters to the function, allowing the C linker to create an executable with no code rewriting needed.

The assembler, however, does not perform name mangling as that's the responsibility of the C++ compiler, not the assembler or the linker. As a result, the names of functions declared in assembly, or referenced from assembly, must be "pre-mangled" by the developer.

There's two ways to do this - either the name we have in assembly is mangled by default (so for GCC, void EXTI5_9_IRQHandler(void) would be _Z18EXTI5_9_IRQHandlerv in assembly), or we disable name mangling for that particular function.

Under GCC, and likely other compilers, specifying extern "C" tells the C++ compiler that this function should not have any name mangling applied, as it is a function that will be provided by C code that doesn't understand name mangling.

Billibilliard answered 5/7, 2020 at 19:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.