Software interrupts

Software interrupts can be quite useful. In kernel drivers (under Linux), a hardware interrupt may be generated by a peripheral. Since these interrupts are asynchronous, the ISR must be fast so it does not miss other hardware interrupts. The bulk of the work is actually carried out in software interrupts, which the hardware interrupt triggers when it exits. Software interrupts are also available on several microcontrollers. You can make a software interrupt on the ATmega. All you need to use a GPIO that is either an external or pin change interrupt, enable it, and then set it as an output. Setting the pin in software will then generate an interrupt. The SAM7 has provision for software interrupts. This page gives the idea of the solution I ended up implementing that made use of a software interrupt on a SAM7 microcontroller.

The problem
A problem I was tasked to fix was to increase the performance of an existing embedded device. It used the infamous SAM7. Most of the software had been written and the GPIO and peripherals had already assigned. The challenge was to increase the sampling rate of the device to improve the resolution of a sensor it was reading. Since timing was highly important, it made samples synchronous to a RTC's programmable timing output. As it turned out, the solution was not as simple as increasing the frequency of the RTC output. The issue was that the existing interrupt handler took too long to execute: if the timing frequency was increased the SAM7 would miss interrupts. 
Investigation
The first step in fixing someone else's creation is to profile the system (after you have familiarised yourself with the existing hardware and software). I did this by adding code to toggle a spare GPIO at the start and end of the ISR of interest. Hopefully the PCB has some unused GPIO brought out to pads you can probe (or better yet, solder wires to). Otherwise, you might need to either solder wires to the microcontroller pins or temporarily free up some pins. I found that the ISR took close to four times the period of desired new sampling frequency (which is eight times higher than it previously was). Obviously the ISR needs to execute faster than the period of the interrupts. Otherwise, your system will not be able to service all interrupts (if they are blocking interrupts) or overflow the stack (if the interrupts are nested and reentrant).  
After you have found the execution time of an interrupt handler, the next stage is to understand what is hogging the CPU. In my case there were several calls to read an I2C sensor data. Although the bus speed was already 400kHz (fast mode), it will still take tens of microseconds to do a transfer. To read n values, it will likely take 75 + 25n microseconds (2.5us/bit, about 10 bits per transaction, address, register, address with read bit, n reads). Note that you may be able to readily speed up the I2C transfers by cleverly scheduling it. Also the slave device probably allows you to read more than one byte at a time. For instance, you can read an entire set of sensor data from an MPU-6050 at a time, rather than having to read each axis separately (or worse yet, reading each byte of an axis separately). This saves you from sending the I2C preamable (address, register, address with read bit) often and does save time. 
A solution
The solution that ended up working for me was to shift the slow and less critical code to another function. I also removed redundant code what was left of the original ISR and got it executing in a fraction of the required period. The slow code was then assigned to an unused interrupt handler. In my case, the fast RTC handler used the PIO pin change interrupt while the slow code was assigned to the timer counter 0 interrupt. In order to generate a software interrupt, you need to set the appropriate bit in the AIC_ISCR register. Note that the software interrupt needs to be designated a lower priority and needs to programmed to be edge triggered.
The above was implemented and it showed signs that it was working - the software ISR was executed after the hardware interrupt had finished. However, the software interrupt would block and prevent the hardware interrupt from triggering. Samples were being dropped which was not acceptable. After meticulously checking the software I had added, it suggested that the interrupts were not being nested.

Software interrupts can be quite useful. In kernel drivers (under Linux), a hardware interrupt may be generated by a peripheral. Since these interrupts are asynchronous, the ISR must be fast so it does not miss other hardware interrupts. The bulk of the work is actually carried out in software interrupts, which the hardware interrupt triggers when it exits. Software interrupts are also available on several microcontrollers. You can make a software interrupt on the ATmega. All you need to use a GPIO that is either an external or pin change interrupt, enable it, and then set it as an output. Setting the pin in software will then generate an interrupt. The SAM7 has provision for software interrupts. This page gives the idea of the solution I ended up implementing that made use of a software interrupt on a SAM7 microcontroller.

The problem

A problem I was tasked to fix was to increase the performance of an existing embedded device. It used the infamous SAM7. Most of the software had been written and the GPIO and peripherals had already assigned. The challenge was to increase the sampling rate of the device to improve the resolution of a sensor it was reading. Since timing was highly important, it made samples synchronous to a RTC's programmable timing output. As it turned out, the solution was not as simple as increasing the frequency of the RTC output. The issue was that the existing interrupt handler took too long to execute: if the timing frequency was increased the SAM7 would miss interrupts. 

Investigation

The first step in fixing someone else's creation is to profile the system (after you have familiarised yourself with the existing hardware and software). I did this by adding code to toggle a spare GPIO at the start and end of the ISR of interest. Hopefully the PCB has some unused GPIO brought out to pads you can probe (or better yet, solder wires to). Otherwise, you might need to either solder wires to the microcontroller pins or temporarily free up some pins. I found that the ISR took close to four times the period of desired new sampling frequency (which is eight times higher than it previously was). Obviously the ISR needs to execute faster than the period of the interrupts. Otherwise, your system will not be able to service all interrupts (if they are blocking interrupts) or overflow the stack (if the interrupts are nested and reentrant).  

After you have found the execution time of an interrupt handler, the next stage is to understand what is hogging the CPU. In my case there were several calls to read an I2C sensor data. Although the bus speed was already 400kHz (fast mode), it will still take tens of microseconds to do a transfer. To read n values, it will likely take 75 + 25n microseconds (2.5us/bit, about 10 bits per transaction, address, register, address with read bit, n reads). Note that you may be able to readily speed up the I2C transfers by cleverly scheduling it. Also the slave device probably allows you to read more than one byte at a time. For instance, you can read an entire set of sensor data from an MPU-6050 at a time, rather than having to read each axis separately (or worse yet, reading each byte of an axis separately). This saves you from sending the I2C preamable (address, register, address with read bit) often and does save time. 

A solution

The solution that ended up working for me was to shift the slow and less critical code to another function. I also removed redundant code what was left of the original ISR and got it executing in a fraction of the required period. The slow code was then assigned to an unused interrupt handler. In my case, the fast RTC handler used the PIO pin change interrupt while the slow code was assigned to the timer counter 0 interrupt. In order to generate a software interrupt, you need to set the appropriate bit in the AIC_ISCR register. Note that the software interrupt needs to be designated a lower priority and needs to programmed to be edge triggered.

The above was implemented and it showed signs that it was working - the software ISR was executed after the hardware interrupt had finished. However, the software interrupt would block and prevent the hardware interrupt from triggering. Samples were being dropped which was not acceptable. After meticulously checking the software I had added, it suggested that the interrupts were not being nested.

I did an internet search on how to enable nested interrupts, and found exactly what I was after. The website by Martin Thomas provides an example on how to enable nested interrupts on the SAM7. Basically you need to make your interrupt handler naked and add some assembly at the beginning and end of the function. I reworked my software and voila, nested interrupts!

Resources

Below is a skeleton project for the SAM7 that does the following:

1. Demostrates how to create a software interrupt.

2. Shows how to enable nested interrupts.

sam7_software_interrupt.zip

Share |