Tests/sys/conn_can produces "Stack pointer corrupted" on custom board

Hi,

I’m pretty new to RIOT and am in the process of doing a bringup for a custom stm32f103cb based board with support for CAN. When I compile the tests/sys/conn_can test for my board I get a hard fault when trying to send can packets. I’m not sure if this is an issue with my board definition or the tests/sys/conn_can example. I’m not sure how best to proceed.

The issue

I’m using the tests/sys/conn_can as an example. Which I have correctly compiled and flashed to my custom board.

I connect to my board over uart and enter the following in the riot shell:

test_can send 0 12 abcd

My oscilloscope decodes a can frame id=0x012, DLC=1, data=cd. Not DLC=2 data=abcd as I would expect, however this isn’t the main issue.

I’ve set DEBUG=1 in most of the can related header files which gives me the following output on the console:

_send: candev=0x20001b58, frame=0x200032c8
tx irq
_can_event: dev=0Stack pointer corrupted, reset to top of stack
active thread: 1
FSR/FAR:
 CFSR: 0x00009200
 HFSR: 0x40000000
 DFSR: 0x00000008
 AFSR: 0x00000000
 BFAR: 0x1ffffff8
Misc
EXC_RET: 0xfffffff1
*** RIOT kernel panic:
HARD FAULT HANDLER

Other verification steps I’ve taken:

  • I don’t believe there are any hardware or electrical issues. I’ve compiled and run different firmware generated with CubeMX on this exact same setup and had no issues. I’ve been able to correctly send and receive can packets.
  • I’ve verified that all the bit-timing parameters are the same as the CubeMx known working example: bitrate=500000, sample_point=875, brp=9, prop_seg=3, phase_seg1=3, phase_seg2=1.
  • The same behaviour occurs when only transmitting one byte.
  • I have another device on the bus which can ACK transmitted packets.

My setup:

RIOT: (Version: 2025.01-devel-13-g88e6a)

I’m using a STM32F103CB MCU. I’m using the non default can pins PB8 and PB9.

My board folder:

custom_board:
├── board.c
├── dist
│   └── openocd.cfg
├── doc.txt
├── include
│   ├── board.h
│   ├── can_params.h
│   ├── gpio_params.h
│   └── periph_conf.h
├── Kconfig
├── Makefile
├── Makefile.features
└── Makefile.include

My periph_conf.h:

  #ifndef PERIPH_CONF_H
  #define PERIPH_CONF_H

  /* This board provides an HSE */
  #include "stm32f103xb.h"
  #ifndef CONFIG_BOARD_HAS_HSE
  #define CONFIG_BOARD_HAS_HSE    1
  #endif

  #include "periph_cpu.h"
  #include "clk_conf.h"
  // #include "candev_stm32.h"
  // #include "periph/can.h"
  // #include "can/device.h"

  #ifdef __cplusplusq
  extern "C" {
  #endif


  /**
  * @name   UART configuration
  * @{
  */
  static const uart_conf_t uart_config[] = {
      {
          .dev        = USART1,
          .rcc_mask   = RCC_APB2ENR_USART1EN, // provide USE non default pins for UART1
          .rx_pin     = GPIO_PIN(PORT_B, 7),
          .tx_pin     = GPIO_PIN(PORT_B, 6),
          .bus        = APB2,
          .irqn       = USART1_IRQn
      },
  };

  #define UART_0_ISR          isr_usart1
  #define UART_NUMOF          ARRAY_SIZE(uart_config)
  /** @} */


  /**
  * @name   Timer configuration
  * @{
  */
  static const timer_conf_t timer_config[] = {  
      {
          .dev      = TIM4,                   // provide TIM4 for ztimer usec
          .max      = 0x0000ffff,
          .rcc_mask = RCC_APB1ENR_TIM4EN,
          .bus      = APB1,
          .irqn     = TIM4_IRQn
      },
  };

  #define TIMER_0_ISR         isr_tim4
  #define TIMER_NUMOF         ARRAY_SIZE(timer_config)

  /** @} */


  #ifdef __cplusplus
  }
  #endif

  #endif /* PERIPH_CONF_H */
  /** @} */

My can_params.h: I’m just hiding the default can params defined under:

cpu/stm32/include/can_params.h

  #ifndef CAN_PARAMS_H
  #define CAN_PARAMS_H

  #include "can/device.h"
  #include "periph/can.h"

  #ifdef __cplusplus
  extern "C" {
  #endif

  static const can_conf_t candev_conf[] = {
      {
          .can = CAN1,
          .rcc_mask = RCC_APB1ENR_CAN1EN,
          .rx_pin = GPIO_PIN(PORT_B, 8),
          .tx_pin = GPIO_PIN(PORT_B, 9),
          .tx_irqn = CAN1_TX_IRQn,
          .rx0_irqn = CAN1_RX0_IRQn,
          .rx1_irqn = CAN1_RX1_IRQn,
          .sce_irqn = CAN1_SCE_IRQn,
          .en_deep_sleep_wake_up = true,
          .ttcm = 0,
          .abom = 1,
          .awum = 1,
          .nart = 1,
          .rflm = 0,
          .txfp = 0,
      },
  };

  static const candev_params_t candev_params[] = {
      {
          .name = "can_stm32_0",
      },
  };
  #ifdef __cplusplus
  }
  #endif

  #endif /* CAN_PARAMS_H */

In my board.c I’m taking care of non default pin remapping using the AFIO-MAPR register to remap can and uart to non default pins

#include "board.h"
#include "periph/cpu_gpio.h"
#include "periph/gpio.h"
#include "log.h"
// #include "periph_conf.h"

void board_init(void)
{

    /* remap USART1 to PB7 and PB6 */
    AFIO->MAPR |= AFIO_MAPR_USART1_REMAP;
    
    RCC->APB1ENR |= RCC_APB1ENR_CAN1EN; // enable CAN clock on APB1
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // enable port B GPIO for can

    /* remap CAN1 to PB8 and PB9 */
    uint32_t tmpreg = AFIO->MAPR;
    tmpreg &= ~AFIO_MAPR_CAN_REMAP;
    tmpreg |= AFIO_MAPR_SWJ_CFG;
    tmpreg |= AFIO_MAPR_CAN_REMAP_REMAP2;
    AFIO->MAPR = tmpreg;

    printf("Initialising board, AFIO->MAPR: 0x%04lx!\r\n", AFIO->MAPR);
}

In my Makefile.features I provide:

  CPU = stm32
  CPU_MODEL = stm32f103cb

  # Put defined MCU peripherals here (in alphabetical order)
  FEATURES_PROVIDED += periph_can
  FEATURES_PROVIDED += periph_timer
  FEATURES_PROVIDED += periph_uart

I’m not sure how best to proceed.

Try increasing stack sizes.

To elaborate on @Kaspar’s answer:

The test application you are using sets the stack sizes of every CAN thread to THREAD_STACKSIZE_MAIN (you can see that in the main.c). The error indeed sounds like this size is set too small, so you can try building the test with CFLAGS='-DTHREAD_STACKSIZE_MAIN=THREAD_STACKSIZE_BIG' make -C tests/sys/conn_can BOARD=<your-board> all .

Documentation for further stack sizes can be found here.

Did you follow the steps of the test application’s README, i.e., initialized the CAN transceiver with init 0 before trying to send?

You could also try to test the CAN support on your board with tests/drivers/candev (I’m not very familiar with CAN support in RIOT in general, though).