Grand Central M4 (SAMD51) - PIO Unified Debugger, timers, delays

Sorry for the cross post. I originally posted my message in the “Development Platforms” section of this board. Probably this is is the correct location. If a moderator wants to delete one of the posts, please go ahead (there is no response in the other location).

https://community.platformio.org/t/grand-central-m4-samd51-timers-delays-and-debugging/14946

Cross-posted at the Adafruit forums: Grand Central M4 - timers, delays and debugging - adafruit industries

Hi folks,

I’m sorry if this is a newbie question.

I’ve just purchased an Adafruit Grand Central M4, and Segger J-Link Mini Edu . Using VS Code with PlatformIO as my IDE, I compiled the classic Arduino “blink” program and downloaded it to the GC over the J-Link connection (using SWD). In the loop() function, this blink program turns on the built-in LED, inserts a delay (I tried various numbers, the classic example used 1000 ms), turns off the LED, inserts another delay.

If I run the program by itself (not in the debugger), everything works just fine. I ran this way by simply pressing the reset button on the GC. If I run the program within the debugger, I can step through and see the code executing, but the delay() seems to return immediately. The LED appears to be on continuously (but it is probably blinking very very fast).

I created a similar blink program, but using the millis() function to check the time in the loop() function, rather than using the delay() function. Again, this program runs fine outside of the debugger, but within the debugger millis() always returns 0. I set a breakpoint after the call and checked the return value.

Is there some known interaction between the debugging system and the timer on the GC? And is there a setting or workaround to allow debugging with delays and timers? If this question is answered elsewhere in the forums, a link or a search string to find that answer would be great.

The GCM4 includes a bootloader, which might be setting up some registers when it runs. Is it possible that these settings conflict with the debugger running under PlatformIO?

arduino-blink is the only example provided in the atmel-sam category which supports the Arduino framework. Apparently the GCM4 board only supports this framework. I am hoping that I can get this example running in PlatformIO as a starting point for future projects. So I was surprised when I ran into an issue which was not in the documentation.

Thanks in advance for your help.

Could be a code optimization issue, though extremely weird that the optimized -Os code would work while the debug-code (-Og) not…

I don’t have such a board but I can stll look at the outputted assembly for the millis() function in normal and debug to spot some weirdness.

If the code is fine, aka “reads a global variable in all cases”, then there might by an issue in the peripheral registers for the timer feeding the millis() tick – e.g. not running at all or running weirdly in debug mode. But that again can further be checked by breaking inside the ISRs responsible for this (if they are reached at all).

The compiled code looks good in both Release and Debug mode.

Basically a src\main.cpp

#include <Arduino.h>

void setup() {
  //Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  unsigned long now = millis();
  if(now % 1000 == 0) {
     digitalWrite(LED_BUILTIN, digitalRead(LED_BUILTIN) ^ 1);
  }
}

and a platformio.ini of

[env:adafruit_grandcentral_m4]
platform = atmelsam
board = adafruit_grandcentral_m4
framework = arduino

[env:adafruit_grandcentral_m4_debug]
platform = atmelsam
board = adafruit_grandcentral_m4
framework = arduino
build_type = debug

when compiling and reverse-engineering the firmware again we see that millis() in both configuration does:

uint32_t millis()
{
  return ulTickCount;
}

void SysTick_DefaultHandler()
{
  ++ulTickCount;
  tickReset();
}

In other words, the interrupt service routine SysTick_DefaultHandler() should be executed every millisecond, increasing the ulTickCount variable by one. This value should be returned by the millis() function. The microcode looks fine, which may imply a problem with the peripheral setup.

Can you please:

  • use the above project code and configuration
  • place a breakpoint at the first line in setup()
  • start a debug session and let it hit the breakpoint in setup()
  • follow the mills() defintion in code to get to the C:\Users\<user>\.platformio\packages\framework-arduino-samd-adafruit\cores\arduino\delay.c file
  • place a breakpoint in the SysTick_DefaultHandler() function
  • continue execution
  • –> Does it hit the SysTick_DefaultHandler() breakpoint? Is the global variable _ulTickCount thus changed (variable window on the left)? Is this value correctly returned by millis()?

Hello maxgerhardt,

Thank you for your kind and detailed reply.

I created a project with your provided platformio.ini file and main.cpp

I added the following lines to the [env:adafruit_grandcentral_m4_debug] configuration that you had provided:
debug_tool = jlink
upload_protocol = jlink

I set a breakpoint at the code line containing the call to millis (1st line of the loop fn).

I stepped into millis(), and then found the SysTick_DefaultHandler handler, and set a breakpoint there. Then clicked “continue”. The breakpoint was never hit.

Any suggestions?

Take care and stay safe.

Hm it seems like if the SysTick handler is never invoked then this explains the behavior, as then the millisecond variable is never incremented and stays 0.

One or more things could have happened:

  • Somehow, the debugger disables the systick when debugging the chip…
  • The SYSTICK control registers (SYST_CSR, SYST_RVR, …) are misconfigured in debug-mode compiled code
  • The interrupt for the SysTick was disabled in the NVIC (nested vector interrupt control)
  • The clock generation setup which feeds the SysTick hardware its base clock is setup wrong

I’ll see if I can write some code which prints out some diagnostic output on what’s going on here.

1 Like

That would be excellent. Please let me know if you want or need help in deploying the tests.

Hello Max,

Have you had a moment to think about how to make these tests?

Thanks in advance.

My goodness have I been busy… I’ll look into it after studying some datasheets of the clock tree of this thing.

Hello Max,

Sorry to pester you again… I’m still not able to use the PIO debugger with the Grand Central M4.

Sorry to have to “bump” this topic again. I still don’t have a way to debug the Adafruit Grand Central M4 within Platformio…

Does anybody have advice?

Thanks in advance.

I managed to come up with some checking code.

#include <Arduino.h>

void setup() {
  Serial.begin(9600);
}

void print_systick_info() {
  /* capture values */
  uint32_t systick_ctrl = SysTick->CTRL;
  uint32_t systick_load = SysTick->LOAD;
  uint32_t systick_val = SysTick->VAL;
  /* decode (same as in https://developer.arm.com/documentation/dui0552/a/cortex-m3-peripherals/system-timer--systick/systick-control-and-status-register?lang=en) */
  bool systick_ctrl_countflag = (systick_ctrl & SysTick_CTRL_COUNTFLAG_Msk) != 0;
  int systick_ctrl_clksource = (systick_ctrl & SysTick_CTRL_CLKSOURCE_Msk) >> SysTick_CTRL_CLKSOURCE_Pos;
  int systick_ctrl_tickint = (systick_ctrl & SysTick_CTRL_TICKINT_Msk) >> SysTick_CTRL_TICKINT_Pos;
  bool systick_ctrl_enable = (systick_ctrl & SysTick_CTRL_ENABLE_Msk) != 0;
  /* print */
  Serial.print("Systick: CTRL=0x");
  Serial.print(systick_ctrl, HEX);
  Serial.print(", countflag=");
  Serial.print(systick_ctrl_countflag);
  Serial.print(", clksource=");
  Serial.print(systick_ctrl_clksource);
  Serial.print(", tickint=");
  Serial.print(systick_ctrl_tickint);
  Serial.print(", enable=");
  Serial.println(systick_ctrl_enable);
  Serial.println("Systick: load=" + String(systick_load) +  ", val = " + String(systick_val));
}

extern "C" int sysTickHook(void);

void print_nvic_info() {
  uint32_t vtor = SCB->VTOR;
  Serial.println("Vectortable offset = 0x" + String(vtor, HEX));
  uint32_t systick_handler_addr = (uint32_t) &SysTick_Handler;
  Serial.println("Systick handler func = 0x" + String(systick_handler_addr, HEX));
  /* entry index 15 should be Systick handler.
   * will crash if the pointer is invalid or needs some remapping.. still try.
   * comment out if it crashes.
   */
  uint32_t* table = (uint32_t*) vtor;
  Serial.println("Registered SysTick handler = 0x" + String(table[15], HEX));
  bool systick_interrupt_active = (SCB->SHCSR & SCB_SHCSR_SYSTICKACT_Msk) != 0;
  Serial.println("SysTick interrupt is active: " + String(systick_interrupt_active));
  //call into sysTickHook() if active..
  int isHookActive = sysTickHook();
  Serial.println("Hook is active: " + String(isHookActive));
}

void print_clocktree_info() {
  /* .. complex.. todo. */
  int gclk_output_enabled = GCLK->GENCTRL->bit.OE;
  Serial.println("GCLK OE: " + String(gclk_output_enabled));
  //MCLK->CPUDIV
}

void loop() {
  print_systick_info();
  print_nvic_info();
  print_clocktree_info();
  Serial.println("Current millis value = " + String(millis()));
  delay(1000); /* attempt at delaying, although it doesn't seem to currently work.. */
}

please check what the output is on the two environments

[env:adafruit_grandcentral_m4]
platform = atmelsam
board = adafruit_grandcentral_m4
framework = arduino

[env:adafruit_grandcentral_m4_debug]
platform = atmelsam
board = adafruit_grandcentral_m4
framework = arduino
build_type = debug

when the code is just uploaded and ran normally.

Hello Max,

Sorry this is taking so long. A few other projects have come up.

In VSCode 1.50.1, I created a new workspace for this project.

I had to modify the platformio.ini file a bit to accomodate the jlink, and currently it looks like this:

[env:adafruit_grandcentral_m4]
platform = atmelsam
framework = arduino
board = adafruit_grandcentral_m4

debug_tool = jlink

; SWD interface
upload_protocol = jlink

debug_init_break = 

This is similar to my previous configuration.

For some reason, when I try to run this using the Run button in the left column followed by the “Start debugging” button on the small floating toolbar, I am getting this message in the output window.

Error: The PlatformIO task detection didn't contribute a task for the following configuration:
{
    "type": "PlatformIO",
    "task": "Monitor",
    "problemMatcher": [
        "$platformio"
    ],
    "label": "PlatformIO: Monitor"
}
The task will be ignored.

I searched on this message on the web and found several reports that said I should delete .vscode/tasks.json, but I don’t seem to have that file in my directory.

When I try to connect to a monitor task with “Terminal > Run task…” and select “PlatformIO: Monitor”, I see a prompt asking for a monitor port, rather than getting a direct connection automatically.

Meanwhile, I notice that you have used the Serial object in the code for reporting. The Grand Central M4 uses the USB port on the board for Serial comms. When the jlink is connected via USB, I don’t know if you can access this port any more. I don’t have enough USB ports on my PC in any case, so I could try adding a hub.

There is a Segger library of functions that allows you to output to a monitor port if you add this to the platformio.ini:

monitor_port = socket://localhost:19021

So my next step is probably to add the Segger RTT files to this project and convert the "Serial.println() function calls to the Segger equivalents.

Thanks, and I will report back again.