Atmel SAMC21 Canbus callback confusion, only works when single step debugging - Page 1
Forum section: electronics / general

author: moonie223
author_rank: Contributor
timestamp: January 14, 2024, 12:51:39 am
content: Hello,I am trying to get canbus working on a SAMC21E15 using the ASF4/Atmel START. I also have a SAME54 Xplained board, so I've got something to communicate with. The SAME54 board plus the CANFD example code works like expected, well, it does after correctly re configuring the clock system so it doesn't just hang on a DFLL lock wait. My custom SAMC21 board does not.I am trying to get the C21 board to transmit to the E54 board, but if I just let it run the code always ends up in the dummy handler. I eventually traced the problem to the can_async_register_callback function, and found that if I comment out the code which actually enables the transmit complete interrupt I no longer hit the dummy handler and the E54 board receives the canbus message like it should.So I single step through the relevant section of code to figure out why it's registering a callback to the exception table instead of the function it should be using and it works like it should. It transmits the message and calls the CAN_0_tx_callback function like it should when it is done. If I disable the breakpoint in the can_async_register_callback function and try retransmitting the same exact message, it runs into the dummy handler again. I can single step through the same function and it works like it should every time.I am at a total loss on this one and don't really know where to go from here and would appreciate any advice.Thank you!Relevant bits of code...Code:static void CAN_0_tx_callback(struct can_async_descriptor *const descr){(void)descr;printf(" CAN Transmission done \r\n");}...case '2':printf(" 2: Send standard message with ID: 0x45A and 64 byte data 0 to 63. \r\n");msg.id = 0x45A;msg.type = CAN_TYPE_DATA;msg.data = tx_message_0;msg.len = 64;msg.fmt = CAN_FMT_STDID;can_async_register_callback(&CAN_0, CAN_ASYNC_TX_CB, (FUNC_PTR)CAN_0_tx_callback);can_async_enable(&CAN_0);can_async_write(&CAN_0, &msg);break;...int32_t can_async_register_callback(struct can_async_descriptor *const descr, enum can_async_callback_type type,FUNC_PTR cb){ASSERT(descr);switch (type) {case CAN_ASYNC_RX_CB:descr->cb.rx_done = (cb != NULL) ? (can_cb_t)cb : NULL;break;case CAN_ASYNC_TX_CB:descr->cb.tx_done = (cb != NULL) ? (can_cb_t)cb : NULL;break;case CAN_ASYNC_IRQ_CB:descr->cb.irq_handler= (cb != NULL) ? (void (*)(struct can_async_descriptor *const, enum can_async_interrupt_type))cb : NULL;break;default:return ERR_INVALID_ARG;}_can_async_set_irq_state(&descr->dev, type, NULL != cb);return ERR_NONE;}

author: ataradov
author_rank: Super Contributor
timestamp: January 14, 2024, 01:16:54 am
content: Stock CAN examples should work without correcting anything. So, if you needed to correct something, I would look into that first.Other than this - in the dummy handler look at the IPSR register. Lower byte would indicate what interrupt handler was executed. All unused interrupts are aliased to the dummy handler, so it can be a lot of things. If the value is 15 or less, it would be the core exception. 3 is a Hard Fault, for example. If that happens, look at the stack contents and see where it faults. Word 7 from the top of the stack is the saved PC at the time of the fault. Match that to the code.If the value is > 15, then you don't have correct interrupt handler defined.

author: moonie223
author_rank: Contributor
timestamp: January 14, 2024, 01:58:43 am
content: Have you tried the examples lately? I remember them working when I first got the E54 xplained board, but they do not work now. If you check now it sets target IO clock to 120MHz, which is a 20MHz overclock for sercom. I think things have changed since it's been depreciated. I just loaded it up again and it hangs here.Code:while (!(hri_oscctrl_get_DPLLSTATUS_LOCK_bit(hw, 0) || hri_oscctrl_get_DPLLSTATUS_CLKRDY_bit(hw, 0)));Regardless, it should not matter. All I change are the clock settings and the bit timings. My C21 board uses a 16MHz crystal, I set up the E54 to generate a 94MHz clock and divide that down to 16MHz for canbus. I have no problems at all with this setup, it configures callbacks just fine and can receive messages from the C21 board like it should. It is only the SAMC21E15 that I am having problems with. I also have a SAME51G19 custom board which also works as expected...The IPSR is set to 0x3 when in the dummy handler, but that doesn't explain why it doesn't happen when I am stepping through the code line by line. Do you have any idea why that might happen?

author: ataradov
author_rank: Super Contributor
timestamp: January 14, 2024, 02:13:13 am
content: I have tried reasonably recently, may be a few months ago. I don't think there are a lot of changes happening there.EDIT: Oh, wait, I have not tried Start CAN code at all. I've only tried old ASF3 code. I think it is mostly the same, and if I was going to use outdated frameworks, it would be ASF3, since you can have a full archive.There is a silicon errata about DPLL on SAM E5x/D5x potentially not locking, so this may be a hardware issue. And SERCOM should be fine at 120 MHz, at least for the testing.Well, you can check where it fails and there may be an obvious explanation.One possible thing is incorrect flash RWS setting, which causes fetch failures. And stepping changes timing, so those failures don't happen.

author: moonie223
author_rank: Contributor
timestamp: January 14, 2024, 02:30:57 am
content: What is going on with the E54 and the examples not working exactly as delivered doesn't really matter to me. I don't think it's relevant to my issue at all.On the SAMC21 I've configured wait states to 2 which should be correct for 48MHz, start doesn't automatically configure that like it should. It will not work at all if you don't, I already crossed that bridge. I guess I can try setting it even higher?My single stepping through the code was my attempt at figuring out where it fails, except then it doesn't fail. I'm not really an expert at any of this, and I don't know where to go from here...

author: ataradov
author_rank: Super Contributor
timestamp: January 14, 2024, 02:39:16 am
content: 2 should be good enough.As I said, when you are in the dummy exception handler, look at the memory at the stack pointer address. 7th word from the SP is the saved PC. This is the code that was executed when it failed. Then look in the disassembly at where this code belongs. After that you can try to determine why it fails.This is the way you start any investigation into the faults.

author: moonie223
author_rank: Contributor
timestamp: January 14, 2024, 03:16:14 am
content: What is PC? Process command? Is all that not the same as the call stack in atmel studio? My linker seems to set the stack at 0x400, and the code crashes at 0x248. So I assume I need to add 0x1C to get 7 words from the pointer?Sorry, like I said I have no real idea what I am doing else I'd probably not use any framework...

author: ataradov
author_rank: Super Contributor
timestamp: January 14, 2024, 03:21:30 am
content: Program Counter. I don't know if it can accurately get call stack on exception.How do you know it crashes at 0x248? If you just look at the program counter, then it is the address of the HF handler, not the crash location.You look at the value of the SP register (it would be 0x20000000 + some value). Then go to that address in the memory view and look at the 7th word there.

author: moonie223
author_rank: Contributor
timestamp: January 14, 2024, 03:37:59 am
content: Bad wording, and I overlooked the 0x2 for sram. When sitting in the dummy the SP is 0x20000248 and the PC is 0x162. So I would go to 0x20000256? Then go to that address in flash? That address is 0xffff, not anything in flash?HF handler? Hardfault? If I didn't know any better, which I don't, I think for some reason it's doing exactly what I tell it. I pass it a null pointer instead of a function pointer and then it goes to the exception table, which is empty, and then I get the dummy handler? I can see the code setting it to the pointer to the correct function when I single step it though...

author: ataradov
author_rank: Super Contributor
timestamp: January 14, 2024, 03:47:45 am
content: Yes, you need to look at the word at address 0x20000256. If that word is not in the flash, then you have some pointer not set correctly, and the code tried to call that address, which obviously failed.If that happens, the next best thing to look at is the 8th word, which contains saved LR register. This register would contain an address after the last "bl" instruction, so you will at least know what code 100% ran successfully last.You can also look at the way those callback pointers set after the Hard Fault happens. Do they still look correct? Are they damaged by something?

author: moonie223
author_rank: Contributor
timestamp: January 14, 2024, 04:28:07 am
content: None of this makes any sense. I have to be missing something obvious.Thanks for trying to help, but I'm going to call it a night.

author: IDEngineer
author_rank: Super Contributor
timestamp: January 14, 2024, 02:06:54 pm
content: I believe you're working with off the shelf hardware but thought I'd share this just in case.I actually use the "it only works during debugging" as a challenge question for people. One of the hardware causes for this can be excessive capacitance on a changing signal, such as a clocking edge. If you're single stepping there's plenty of time for the capacitance to (dis)charge, but when you turn it loose to run at full speed the RC constant can prevent reaching threshold voltages.Long ago I painfully learned to presume *nothing* about my signal levels and scope everything while running flat out. It's a lot easier to debug when you know your hardware is "clean".

author: moonie223
author_rank: Contributor
timestamp: January 14, 2024, 07:31:35 pm
content: I am not working with off the shelf hardware. I've read through the entire schematic checklist section of the datasheet and have the recommended capacitors where they are recommended. The custom board does work well with other code I've ran it on, toggling GPIO and UART echo. I can see it transmitting canbus messages, and I can even see it receiving them by looking directly at message ram (haven't wrote polling code to extract that.) If I disable interrupts and just run it "polling" style, it works at full speed. I appreciate the advise but I do not think that is the issue here.Something is either not setting pointers to callbacks like it should, (except when I watch them, and then it DOES work!?) or they are being taken out like ataradov suggests while running at full speed.This is a 4K SRAM device, and digging through the MAP file I don't really have much left. I am too much of a novice to really figure out why, so now I'll be trying to make sense of the vector table, handlers, how the ASF4 code uses them and the debugger tools to try and see when or if they change while running full speed. I've never used data breakpoints before, and don't even know where to set them...All I really want is a uart to canbus bridge, I have one working great on a 328PB and MCP2515. I figured 4K was massive. Probably is, if I don't use ASF4.

author: ataradov
author_rank: Super Contributor
timestamp: January 14, 2024, 07:49:18 pm
content: The first thing you need to verify if any of the structures containing pointers are damaged after the fault handler is called.If they are, then it is likely that you have stack overflow and stack overwrites the data. And easy way to check this is to fill the stack area with a known value (0x55 or something like this) and then look at it after some time running or at the fault. If you don't have any 0x55's, then you do have a stack overflow.And there are many ways single stepping may not lead to an immediate failure, since it does change timings relative to the external events. Even if your application is paused, the CAN controller still sends the data and sets the interrupt flags. You just don't have any extra code that would run before the interrupt handler.

author: moonie223
author_rank: Contributor
timestamp: January 14, 2024, 08:15:52 pm
content: What is the easiest way to fill the stack with data? I am pretty sure the struct inside a struct with all my pointers is getting overwritten, and whatever is doing that doesn't run while single stepping.The only other thing running is sercom usart, and it's supposed to be running while halted from debugging.

author: ataradov
author_rank: Super Contributor
timestamp: January 14, 2024, 09:24:27 pm
content: Quote from: moonie223 on January 14, 2024, 08:15:52 pmManually from the IDE memory view window. You don't need to fill a lot, just the very top of the stack. Or the bottom, depending on your perspective. The lowest address, basically.Or in the startup code.

author: IDEngineer
author_rank: Super Contributor
timestamp: January 14, 2024, 11:15:33 pm
content: Quote from: moonie223 on January 14, 2024, 07:31:35 pmThose are probably power supply bypass caps. I'm talking about unintentional, parasitic capacitance on (relatively) high speed lines.But no matter. Just an idea that would explain the speed sensitivity. If you're convinced it's firmware, then no reason to look at the hardware.

author: moonie223
author_rank: Contributor
timestamp: January 15, 2024, 03:03:26 am
content: Quote from: ataradov on January 14, 2024, 09:24:27 pmI see, the last time I was trying to make sure a set of variables sat in a very specific location in RAM I had to modify the linker script (first 32K message ram requirement for M_CAN on the E51) I assume I'd have to do that here, too? If I wanted to let the code do that, instead of doing it manually?Either way, I am sure now that the pointers are being corrupted and the code ends up in the dummy after trying to run invalid pointers. I guess from here I should just rip the bandaid off and try to work without any framework. At least I can copy relevant bits if I need to.I'm trying to make sense out of linkers, again, and I notice that the ones included with Atmel/microchip DFP packs predefine the stack size and just stick it (_sstack - _estack) right in the middle of RAM somewhere, but other code (like arduino zero, or your mcu-starter-projects) just look to put it at the top of RAM. I guess you just have to know what you are doing, there's no way to generate these files + the associated startup files? Either way, how you do it makes more sense. I'm wasting a good deal of RAM for no reason using the DFP included linkers...

author: ataradov
author_rank: Super Contributor
timestamp: January 15, 2024, 03:23:50 am
content: No, you don't need to modify the linker script. Your linker script will already export the symbols for the stack start and end address. You can use those symbols the same way BSS and data symbols are already used in the startup code.Placing a fixed size stack naturally at the end of the variables has advantage that the linker will warn you if your variables are too big. But it wastes SRAM at the end,I personally like placing the stack at the end of the SRAM, leaving all remaining memory for the stack, but then you need to keep in mind that stack may overflow if you add more variables.If you have some RAM remaining, you can increase the stack size and see if that helps. You can also look at what is using all that stack.

author: moonie223
author_rank: Contributor
timestamp: January 15, 2024, 04:22:03 am
content: Increasing the size of the stack seems to fix it, the example code plus the default 0x400 stack only takes 0x4B0 bytes. If I completely fill out the rest of the RAM with the stack, I hit a hardfault again when STDIO tries to initialize. If I back off the end of RAM 0x200 bytes, it will initialize and adds something past the stack. I think STDIO is supposed to be unbuffered, 1 byte at a time, I'm not sure what it's doing with that memory.I don't care for this middle of the memory stack stuff, and dealing with this depreciated framework is probably more trouble than it's worth. I can't stand looking at MPLAB X. I think I'm going to start from scratch, between what I can gather for examples from the ASF4 code and arduino zero code, I should be able to do what I need.Thank you for the help!