[SAMC] Interrupts, have I got this right? - Page 1
Forum section: electronics / general

author: Simon
author_rank: Unknown
timestamp: February 15, 2020, 11:32:20 am
content: For some reason the SAMC range is not documented very extensively but I take it that the very similar SAMD information is equally valid generically speaking.So having found this:https://microchipdeveloper.com/32arm:samd21-nvic-configurationI take it that I can follow a similar procedure to AVR (the only other micro controller i have experience of) and enable global interrupts and the specific interrupt. So the global interrupt function as shown in that document is accepted during a compile and i take it i can just write "1's" to the bits in the registers that set the interrupts I want and then use the handlers listed in startup_samc21.c that is automatically included in every AS7 project. The weak dummy code confuses me a bit but apparently that is overridden by my code. From what I can tell the dummy code is just an infinite while loop which i do not understand the reason for as if this code were ever to run it would just hang the CPU for ever....

author: ejeffrey
author_rank: Super Contributor
timestamp: February 15, 2020, 06:47:11 pm
content: Weak symbols are flags to the link process. A weak symbol will be compiled and included in the binary as normal, but if there is a second definition with the same name in another object file that will silently override the weak version.Weak symbols are often used for interrupt handlers in this way because without the weak version compilation would fail with an undefined symbol. Somewhere else in the code there is a definition of the default interrupt table that references the name of the interrupt handler. That will fail at link time if there is no function defined. If it wasn't defined weak it would either cause a link error or the version of the symbol that made it to the final output would be ambiguous.Sometimes weak symbols for interrupt tables default to null or an immediate return rather than an infinite loop. However, null is not great because that will probably result in a hard fault if it is ever invoked. That makes debugging more difficult since you won't necessarily see which interrupt caused the fault. A bare return is OK if the IRQ might sometimes be enabled even when not used but that is normally a bug. An infinite loop hangs the CPU in a well defined state for debugging or trigger the watchdog timer if enabled.

author: hans
author_rank: Super Contributor
timestamp: February 15, 2020, 07:04:07 pm
content: The NVIC peripheral is part of the ARM core, so it's pretty similar among chips. Differences may be present in priority grouping though of some chips/vendors.In terms of programming: an interrupt is handled by the ARM processor, by jumping to a particular location in the interrupt vector table, which is usually present at the start of the FLASH memory.This table contains the interrupt vector program location that the CPU must jump to for a particular interrupt to be executed. This table is usually defined in some assembly or C start up code, which refers all interrupt functions to logical names.The library may define a "weak" function such that the implemented application can easily override it. The while(1) dummy code is usually used such that an unhandled interrupt will appear as an obvious programming error, as the program crashes, and these faults need to be fixed. You can figure out which interrupt vector is unhandled byinspecting the NVIC or core registers of the ARM CPU, for example with a debugger.There are some reasons why the weak function definition to an infinite loop is quite sane from a programming perspective:If the default library implementation would have no interrupt functions defined at all, you would have to manually fill the interrupt vector table, which is error prone considering some modern ARM chips can have hundred(s) of interrupt IDs.If the default library didn't have a weak definition, you have to modify the HAL/CMSIS code with stuff that should be in your application directory. Then if a new CMSIS library is needed, you would have to backport or merge all changes again..Finally, if the default interrupt function was to exit without handling the interrupt, you may get undefined or weird behaviour. Usually this is still some kind of infinite loop - as the CPU is still requested to handle the interrupt, but the code doesn't take care of it. However, these errors may be more confusing, as you may think your code is working fine until you fully step through it.

author: Simon
author_rank: Unknown
timestamp: February 15, 2020, 07:16:40 pm
content: I see, yes that makes sense.

author: ataradov
author_rank: Super Contributor
timestamp: February 15, 2020, 07:37:55 pm
content: Just one addition to whathans. The vector table does not have to be located in the flash. Only first two entries are necessary for startup (initial stack pointer and the reset handler). The rest of the table may be located anywhere and the location is defined by the SCB->VTOR register (which reset handier can set before any other code runs).This is convenient if you want to place interrupt handlers in the RAM to speed up execution. It makes sense to put the table itself in the RAM as well in that case.

author: Simon
author_rank: Unknown
timestamp: February 15, 2020, 07:53:06 pm
content: Well I'll start with the basics. I guess that if I am running 48MHz with a ROM that is running at 16MHz and can't even predict that the interrupt handling instructions will need loading next having the table in RAM will increase the response time 3 fold?

author: ataradov
author_rank: Super Contributor
timestamp: February 15, 2020, 07:58:28 pm
content: Quote from: Simon on February 15, 2020, 07:53:06 pmNot really. The response time (latency) is combined from fetching the vector from the table. And this part will be faster. But majority of time entering/exiting the interrupt is spent saving/restoring registers. And this part will be as fast.Normal interrupt entry time on Cortex-M0+ is 15 clock cycles. Having vector table in the flash would increase this time by 2 cycles. Having interrupt handler itself in the flash would add 2 more cycles for the initial instruction fetch.

author: Simon
author_rank: Unknown
timestamp: February 15, 2020, 08:09:51 pm
content: Yes I guess so, the registers are already going to be fast and the bulk of the code is in ROM anyway.

author: westfw
author_rank: Super Contributor
timestamp: February 15, 2020, 09:10:11 pm
content: QuoteMost of the ARMs have an additional layer of "enable-ment."You have global interrupt enable, which is a function of the CPU. (PRIMASK special register, set with __set_PRIMASK())You can enable each "external interrupt" by manipulating the NVIC. In this case, "external" means "external to the ARM core", and it includes all of the SAMC peripherals. (NVIC_EnableIRQ())Each peripheral probably has it's own registers controlling which states/events will cause it to generate an interrupt. (for SAMC, it looks like this register is always (peripheral).INTFLAG, and has set/clear registers as well (xx.INTENSET, xx.INTENCLR)(1) and (2) are part of the ARM Core, and should be the same for almost any ARM Cortex chip (different architectures have different numbers of external interrupts, and the actual number of priority levels gets to be vendor-defined. But "basically the same.")(3) is 100% Vendor and chip dependent. One would like to assume that the SERCOM on a SAMC21 is the same as the SERCOM on a SAMD21 or SAMD51, and it's "somewhat true." :-(It's (2) that is missing from AVRs - there's no way to tell an AVR "disable all the interrupts from UART0" in a way that isn't intimately familiar with the UART internals and how you have it configured.(and yeah, coming from an 8bit world, ARM interrupts are annoyingly "expensive.")

author: Simon
author_rank: Unknown
timestamp: February 15, 2020, 09:16:15 pm
content: i see so there will be an enable for each interrupt line (peripheral) apart from the peripheral internal interrupt enables.

author: mikerj
author_rank: Super Contributor
timestamp: February 15, 2020, 09:39:49 pm
content: Quote from: Simon on February 15, 2020, 11:32:20 amThe point is that the code should never run, if it does you've enabled an interrupt that you shouldn't have. Hanging the CPU at that point is a minimum code solution and allows you to find out what's happening with a debugger attached. If you want a different behaviour, then it's easy enough to provide it.

author: Simon
author_rank: Unknown
timestamp: February 16, 2020, 10:33:06 am
content: OK, so i have the ARM document with it's NVIC register description. All looks fine but the interrupt priorities are a bit confusing.0xE000E400-0xE000E41CNVIC_IPRn RW 0x00000000 Interrupt Priority Registers, NVIC_IPR0 - NVIC_IPR7 onpage B3-288So that is 0x1D bytes of address space or 29 bytes. 29 does not work for the address architecture but 28 is 7 words which cover 4 interrupts each or again 28 interrupts. What about the other 4 to make the 32 lines?

author: Simon
author_rank: Unknown
timestamp: February 16, 2020, 10:34:23 am
content: My bad i get it 0x1C is the start of the next 4 bytes of address space taking it to 8 full registers.

author: Simon
author_rank: Unknown
timestamp: February 16, 2020, 02:09:43 pm
content: i take it that i have to reset the interrupt myself in the "Interrupt Clear-Pending Register, NVIC_ICPR" register

author: hans
author_rank: Super Contributor
timestamp: February 16, 2020, 03:07:05 pm
content: Usually writing to the interrupt status flag register of the peripheral itself, and then leaving the interrupt context, is enough to clear it.Do note that some registers are "write 1 to clear", and not intended to be used with a bit mask/clear operation.

author: Simon
author_rank: Unknown
timestamp: February 16, 2020, 04:17:55 pm
content: all interrupt registers I have seen so far are "write 1" to disable. Saves a read-modify-write. There are two levels of registers here. The CPU interrupt controller and the peripheral interrupt. The TC for example requires the interrupt to be cleared manually, servicing the interrupt routine is not enough. There is no specific information about the NVIC interrupt handling in the register description. Safest thing is to assume that I need to clear it myself. I'ts easy enough to find out of course.I suppose the peripheral register does not reset because it's not interrupt hardware really.

author: ataradov
author_rank: Super Contributor
timestamp: February 16, 2020, 05:44:14 pm
content: NVIC_ICPR is cleared automatically once interrupt is called by the core.Have a look at literately any other interrupt handler code, including from the ASF. They do not clear pending interrupts in the core them.

author: Simon
author_rank: Unknown
timestamp: February 17, 2020, 07:36:29 am
content: i see. Yea this is what i don't get. Why the documentation is so poor from anyone. It's like a secret circle..... The only way of learning is to look at what someone else didAnyway easy enough to discover it by simple experiment. Setup an interrupt and put a random pin toggle in it. If it pulses like mad faster than the interrupt trigger event it's locked in a loop because the interrupt has to be manually cleared.I suppose the reason the peripheral does not clear it's registers is that they are not interrupt hardware registers. The TC compare match will clear only if the CCx register is read in capture mode. Any other mode there is nothing toa trigger a reset.

author: ataradov
author_rank: Super Contributor
timestamp: February 17, 2020, 07:39:16 am
content: Documentation is fine. It is just there is a lot of it and it comes from different sources. And in that case it is sometimes easier to look at the source to get the general idea.What you need is an actual book, not datasheets. I don't have any recommendations though.

author: Simon
author_rank: Unknown
timestamp: February 17, 2020, 07:42:46 am
content: Well books will never be architecture specific. Most books are written with PC programming in mind so at the best of times you have to ignore all the silly examples about keyboard inputs and display outputs etc.Maybe I should write my own 😎

author: ataradov
author_rank: Super Contributor
timestamp: February 17, 2020, 07:43:52 am
content: There are books for ARM specifically. There is one everyone recommends, but I don't remember the name. I'll try to find it.I think it is this onehttps://www.amazon.com/Definitive-Guide-ARM-Cortex-M0/dp/0123854776(The Definitive Guide to the ARM Cortex-M0 by Joseph Yiu).I have not read it, so I can't personally recommend it, but I've heard good feedback.And may be look up other books by the same author.Yeah, there are dozens of books specifically about ARM.

author: westfw
author_rank: Super Contributor
timestamp: February 17, 2020, 05:43:40 pm
content: QuoteAlso note that at least on Cortex-M0 chips, bitfields can only be implemented by read/modify/write of entire words in the memory space.A correct-looking statement like:Code:SERCOM0.USART.INTFLAG.bit.TXC = 1; // Clear TXC Interruptwill end up clearing a lot more bits than you expect, since the compiler had no understanding of "writing ones will clear bits."

author: Simon
author_rank: Unknown
timestamp: February 17, 2020, 05:53:33 pm
content: Err, really? then why do i have set and clear registers so that I can send a 1 to the bits I want to set or clear particularly the ones where writing a 0 has no effect?

author: ataradov
author_rank: Super Contributor
timestamp: February 17, 2020, 06:04:58 pm
content: The issue with this "SERCOM0.USART.INTFLAG.bit.TXC = 1; " is that it is actually equivalent ofCode:int tmp = SERCOM0.USART.INTFLAG.regtmp |= SERCOM_USART_INTFLAG_TXC;SERCOM0.USART.INTFLAG.reg = tmp"So it will write all the set bits at the same time,. The correct way to clear a single bit isSERCOM0.USART.INTFLAG.reg = SERCOM_USART_INTFLAG_TXC;This is C, it has noting to do with MCUs specifically.

author: Simon
author_rank: Unknown
timestamp: February 17, 2020, 06:16:24 pm
content: Uh, this is why i prefer my own simple as possible code.

author: ataradov
author_rank: Super Contributor
timestamp: February 17, 2020, 06:19:23 pm
content: I see people make this mistake with their own code all the time.People write something like "SERCOM0.USART.INTFLAG.reg |= SERCOM_USART_INTFLAG_TXC;", which has the same exact problem.

author: Simon
author_rank: Unknown
timestamp: February 17, 2020, 06:21:52 pm
content: You mean the read modify write operation is a problem? As I don't know what SERCOM_USART_INTFLAG_TXC is without delving into layers and layers of header files I don't get all of the context.

author: ataradov
author_rank: Super Contributor
timestamp: February 17, 2020, 06:26:20 pm
content: SERCOM_USART_INTFLAG_TXC isCode:#define SERCOM_USART_INTFLAG_TXC_Pos 1 /**< \brief (SERCOM_USART_INTFLAG) Transmit Complete Interrupt */#define SERCOM_USART_INTFLAG_TXC (0x1ul << SERCOM_USART_INTFLAG_TXC_Pos)And it does not take nearly as long to look up as you think.

author: cv007
author_rank: Frequent Contributor
timestamp: February 18, 2020, 12:40:38 am
content: The headers are supposed to help a little by making the flags in the structs 'read-only'-https://godbolt.org/z/c_UXW-(parts from some SAMD10 headers)but only produces a warning in this case (default compiler flags, I assume), and still doesn't prevent using |= incorrectly. You just make all warnings go away, and anytime you are dealing with flags, and see anything other than =, an internal flag should go off (don't know how to clear those, though).(if you modify those struct members to a bool, then you get an error instead of a warning when trying to write those bits- don't know the laws that apply to bitfield types, but gcc seems to be happy)

author: westfw
author_rank: Super Contributor
timestamp: February 18, 2020, 08:13:23 am
content: Perhaps I should mention that this will also not do what you want, andCode:SERCOM0.USART.INTENCLR.bit.TXC = 1; // Clear TXC InterruptAnd of course INTENCLR is declared as a R/W register...

author: Simon
author_rank: Unknown
timestamp: February 18, 2020, 08:18:24 am
content: Well that is why i prefer to read the datasheet, understand exactly how the hardware works and write my own code.

author: Simon
author_rank: Unknown
timestamp: February 20, 2020, 08:03:13 pm
content: pwah! interrupts hard? worked first time. OK I had a few minute head scratching as i thought i had set my test pin as an output when I had not.

author: ataradov
author_rank: Super Contributor
timestamp: February 20, 2020, 08:05:57 pm
content: Nobody said they were hard. Especially for blinking and LED.The hard part is when interrupts are complex, there are many of them, and they need to interact with the main code.

author: Simon
author_rank: Unknown
timestamp: February 20, 2020, 08:27:37 pm
content: Yes I guess so. I am just happy that I am making progress in what I once thought insurmountable.

author: westfw
author_rank: Super Contributor
timestamp: February 21, 2020, 07:27:30 pm
content: The hard part is when you decide that you want multiple priority levels, and are trying to keep the different levels from stepping on each other's data structures.

author: Simon
author_rank: Unknown
timestamp: February 21, 2020, 07:43:54 pm
content: Yea I guess so. I do relatively simple things. I just need an interrupt to fire when a counter overflows so that I can run periodic code or a serial port interrupt to fire to tell me new data has arrived or data has finished sending.