Mostly as a learning exercise, I've been messing around with installing a fault handler in the teensy 3 interrupt vector table. This interrupt routine gets called whenever a processor fault occurs (such as a memory or instruction error). This may help with debugging. This was mostly based on the "Definitive Guide to the Arm Cortex M3", but this blog was also somewhat helpful.
In Adruino.app/Contents/Resources/Java/hardware/teensy/cores/teensy3/mk20dx128.c, modify fault_isr so that it's a weak symbol:
(Perhaps Paul can make this change permanent? It might also be useful to have a separate hard_fault_isr handler.)
Use this main.cpp:
When run, this outputs:
You can then use addr2line to figure out where these addresses are.
(The Makefile seems to like to delete the .elf file so you may need to do: make main.elf)
The first address (pc) is the currently executing instruction, and the second address (lr) is the previously called function.
As you can see by the fact that current PC is inside the next Serial.println() function, certain faults are "imprecise" in that they don't occur for some time after the offending code is executed. So you may see that you have continued somewhat past where the mistake occurred.
The part used on the Teensy3 unfortunately lacks a memory protection unit (MPU), so there are classes of errors that you could catch on a desktop CPU that will go unnoticed (and silently corrupt your program's execution) on the Teensy.
I've uploaded this to my teensy mercurial repo. Type make main_crash.hex to build and upload.
Next, I'd like to try to turn this fault ISR into a mini debug console (or even GDB stub).
Hope this is useful,
-c
In Adruino.app/Contents/Resources/Java/hardware/teensy/cores/teensy3/mk20dx128.c, modify fault_isr so that it's a weak symbol:
Code:
void __attribute__((weak)) fault_isr(void)
{
while (1); // die
}
(Perhaps Paul can make this change permanent? It might also be useful to have a separate hard_fault_isr handler.)
Use this main.cpp:
Code:
#include <unistd.h>
#include "usb_serial.h"
#include "core_pins.h"
// These could go in mk20dx128.h
#define SCB_SHCSR_USGFAULTENA (uint32_t)1<<18
#define SCB_SHCSR_BUSFAULTENA (uint32_t)1<<17
#define SCB_SHCSR_MEMFAULTENA (uint32_t)1<<16
#define SCB_SHPR1_USGFAULTPRI *(volatile uint8_t *)0xE000ED20
#define SCB_SHPR1_BUSFAULTPRI *(volatile uint8_t *)0xE000ED19
#define SCB_SHPR1_MEMFAULTPRI *(volatile uint8_t *)0xE000ED18
usb_serial_class Serial;
void flash() {
digitalWrite(13, HIGH);
delay(100);
digitalWrite(13, LOW);
delay(100);
}
extern "C" {
void __attribute__((naked)) fault_isr () {
uint32_t* sp=0;
// this is from "Definitive Guide to the Cortex M3" pg 423
asm volatile ( "TST LR, #0x4\n\t" // Test EXC_RETURN number in LR bit 2
"ITE EQ\n\t" // if zero (equal) then
"MRSEQ %0, MSP\n\t" // Main Stack was used, put MSP in sp
"MRSNE %0, PSP\n\t" // else Process stack was used, put PSP in sp
: "=r" (sp) : : "cc");
Serial.print("!!!! Crashed at pc=0x");
Serial.print(sp[6], 16);
Serial.print(", lr=0x");
Serial.print(sp[5], 16);
Serial.println(".");
Serial.flush();
// allow USB interrupts to preempt us:
SCB_SHPR1_BUSFAULTPRI = (uint8_t)255;
SCB_SHPR1_USGFAULTPRI = (uint8_t)255;
SCB_SHPR1_MEMFAULTPRI = (uint8_t)255;
while (1) {
flash();
asm volatile (
"WFI" // Wait For Interrupt.
);
}
}
}
int main() {
pinMode(13, OUTPUT);
while(!usb_configuration) {
flash();
}
// enable bus, usage, and mem fault handlers.
SCB_SHCSR |= SCB_SHCSR_BUSFAULTENA | SCB_SHCSR_USGFAULTENA | SCB_SHCSR_MEMFAULTENA;
Serial.println("Hello, world.");
Serial.flush();
digitalWrite(13, HIGH);
delay(2000);
digitalWrite(13, LOW);
// crash
*((int*)0x0) = 1;
// shouldn't get here:
Serial.println("Success.");
}
When run, this outputs:
Hello, world.
!!!! Crashed at pc=0x490, lr=0x55B.
You can then use addr2line to figure out where these addresses are.
arm-none-eabi-addr2line -s -f -C -e main.elf 0x490 0x55B
Print:: println(char const*)
Print.h:47
main
main_crash.cpp:86
(The Makefile seems to like to delete the .elf file so you may need to do: make main.elf)
The first address (pc) is the currently executing instruction, and the second address (lr) is the previously called function.
As you can see by the fact that current PC is inside the next Serial.println() function, certain faults are "imprecise" in that they don't occur for some time after the offending code is executed. So you may see that you have continued somewhat past where the mistake occurred.
The part used on the Teensy3 unfortunately lacks a memory protection unit (MPU), so there are classes of errors that you could catch on a desktop CPU that will go unnoticed (and silently corrupt your program's execution) on the Teensy.
I've uploaded this to my teensy mercurial repo. Type make main_crash.hex to build and upload.
Next, I'd like to try to turn this fault ISR into a mini debug console (or even GDB stub).
Hope this is useful,
-c
Last edited: