How to Use Interrupts in OpenXLR8
An Advanced Technique Tutorial
Jason Pecor: Is it time to add microcontroller Interrupts to your custom Xcelerator Block, but you’re not sure where to start? Well then, this is your video.
In this second installment of the Advanced Techniques Tutorial series by Alorium Technology, you will learn how to use interrupts as part of the OpenXLR8 development flow. Your hosts for this tutorial are Steve Phillips, Chief Architect for the XLR8 platform, and self-proclaimed “Master Craftsman”, along with Bryan Craker, Lead Software Developer. And here are Steve and Brian.
(00:22) Steve Phillips: Hello, and welcome to the Alorium Technology webinar. I’m using interrupts in OpenXLR8. My name is Steve Philips, Master Craftsman, and joining me today is Bryan Craker.
Today in our webinar, we’ll be walking through the interrupt logic. Then Bryan will take us through the interrupt service routines. And then we’ll have a demo of using interrupts in OpenXLR8 using our I2C SPI UART Demo.
Prerequisites for today’s webinar include a familiarity with Arduino Uno interrupts. Our XLR8 interrupts are an extension of those, so it’s useful to have an understanding of how you use the interrupts in the Uno. You also need a familiarity with XLR8 and OpenXLR8. So if you haven’t already, please go and watch the Intro to OpenXLR8 webinar available on our YouTube channel.
If you wanna work through the demo with us, you’ll need a laptop with Windows or Linux installed. And you’ll also need the tools involved in OpenXLR8, which include Arduino IDE, and the Intel Quartus Prime Lite Edition. Please download and install version 17.1 as that’s the version we currently support.You’ll also need an XLR8 board and a mini cable to connect it up to your laptop.
So let’s dig into the logic that’s implemented for interrupts in XLR8. As I mentioned, XLR8 uses the standard Arduino Uno or AVR interrupt model. Most of the complexity you see today is the complexity inherent in that methodology. We do a minor extension on top of that to implement our own custom interrupts, but most of the complexity is inherent in AVR. We’re gonna use two of the existing interrupt vectors to implement our XLR8 interrupts. And we’ll look at the logic we’ve added to implement interrupts in the XLR8.
(02:24)Here we see the interrupt vector table for the ATmega328P AVR core. This is what’s used in the Arduino Uno, and also what’s implements inside of the XLR8. Basically what we see here is for each of the possible interrupts, there is an entry in the table for that. And the entry is essentially a branch instruction that takes you to the ISR, or Interrupt Service Routine, for that interrupt. What we’re doing is we’re using two of these entries that are not currently used as ours. The first is number 23, EEPROM Ready, which we’ll use for our built-in interrupts, the GPIO PC interrupts. The second is number 24, Analog Comparator, which we’re going to use for our OpenXLR8 interrupts.
Here we see a high level block diagram of just the interrupt connections in OpenXLR8. At the lowest level, we have Interrupt sources, either from the ports, where we have pin change interrupts being generated, or from custom modules inside of the OpenXLR8. Those Interrupts are then forwarded up into the pin change interrupt, or PCINT blocks, which store the state of those interrupts. And those then generate IRQs which are sent to the IRQ module, which interfaces to the AVR core. We basically have two branches to this hierarchy, one is for pin change interrupts that generate the built-in IRQ, the other is for interrupts coming from inside the OpenXLR8 module, which generates the OpenXLR8 IRQ.
Pin change interrupts come from the pins themselves. The user can configure any GPIO pin to generate pin change interrupts, such that whenever the pin transitions from a zero to a one, or a one to a zero, it generates an interrupt signal that’s then forwarded up the pcint module. It should be noted that the state of that pin when it changes is not saved, so let’s assume that software knows what a change on that pin would mean.
OpenXLR8 interrupts come from custom blocks implemented within OpenXLR8 module. These interrupts can be whatever the user wants, and could be a steady state signal that’s held high. Those interrupts are forwarded up into the pin change interrupt module, or pcint module. Inside of the pcint module, we have a flag register with a bit for each of the interrupt sources. Associated with that is a mask register that controls whether an incoming interrupt will set a flag register bit. If the mass bit is zero, then the associated flag register bit will not get set. If it’s a one, then it will be set.
In a similar manner, there’s a control register that controls whether a bit that’s already set in the flag register will generate an IRQ signal. If the control register is a zero, it’ll prevent that bit in the flag register from causing an IRQ. If it’s a one, it will allow it through. Bits set in the flag register will generate IRQs, which are sent to the XLR8 IRQ module. This module is very much like the PCINT module in that it has a flag register with a mask and a control register associated with it.
(05:58) But it also implements that acknowledge register. This is used to prevent a singular IRQ from generating multiple interrupts. If a bit in the knowledge register is set, then it will block an IRQ from being sent to the AVR core. So how does all this work? Let’s walk through how we would generate and handle interrupts in a typical case. So assuming that software has already configured the mask and control registers properly and such that interrupts will work will first have interrupts of some type generated either as a pcint interrupt or as a custom interrupt and OpenXLR8.
Those intervals will then cause a flag register to get set within the pcint module. In the case of the pcint interrupts those interrupts are momentary and go away after setting the bit in the flight register, whereas, the interrupts coming from OpenXLR8 modules could be steady state. Those bits said in the pcint modules will generate IRQ that set flag registers up in the OpenXLR8 IRQ module. And then those set will cause an IRQ into the AVR core itself.
Once that IRQ is selected to be serviced, the AVR core will send any knowledge back to the OpenXLR8 IRQ module, which will then block the IRQ being sent to the AVR core, to prevent multiple interrupts from a single source. At this point, software is in the interrupt service routine, it will first inspect the flag registers in the pcint modules to determine what was the source of that interrupt will then take whatever action is necessary to handle that interrupt and clear the bits.
At this point, enough sources are now gone, we can clear the flag registers in the pcint modules, and then clear the flag and acknowledge registers in the IRQ module, which will create a clean interrupt hierarchy. We’re now ready to return to normal operation and handle any new interrupts that may occur. I’ll hand it over to Bryan so that he can walk us through the interrupt service routine.
(08:22) Bryan Craker: Hey everyone, this is Bryan Craker and I’m going to go over developing the software side of an ISR. So, this will be how you connect to our OpenXLR8 modules and handle interrupts from it from the software side. So just in general, there are some defines and includes that you can set up for an OpenXLR8 ISR that could be included in just about any project that is using our normal recommended OpenXLR8 flow to create interrupts.
You can include the XLR8 address pack which is a library that we provide that has registered addresses defined that are used in OpenXLR8. So, that includes the controlled mask flag and act registers for ISR’s and you also want to define the vectors that the ISR’s will trigger off if you have ever seen it ISR in Arduino before, it’s always triggering off of some defined vector.
And as we said an earlier slides those vectors are vector 23 and 24 that we’ve taken for the built-in XP and the OpenXLR8 interrupt, use. It’s also useful to define the built-in XLR8 interrupt bit and the OpenXLR8 interrupt bit, which would be bits one and two. And then those address registers are defined from the OpenXLR8 address pack. Next, it’s useful to define your interrupt bits. So in the ISR that you create you will service interrupts based on flags being set and then you’ll clear those flags. The bit position for a specific interrupted will always be the same.
So it’s helpful to set up a define for that bit. So for example, if you had two different interrupts that you wanted to monitor in OpenXLR8, you could have two different defines and just have those defines set to the two bits that you want. For example, bits one and two. At the start of the program, you need to enable the interrupts you want to monitor, it’s useful to put the enables and the function that you run in setup of your Arduino sketch.
You set the global flag for OpenXLR8 interrupts first, and then the individual interrupts that you want to monitor. And this is why we defined those bits previously. So you can see here I’m reusing those example defines to set the OpenXLR8 control and mask registers. When ISR triggers, you need to check the interrupt flags to see what or possibly which interrupts have been triggered. You then need to do whatever the particular interrupt requires and then clear the bit and then recheck the interrupt flags. You’ll repeat this process until the interrupt flags are clear and then clear the act registers.
(11:45) So, if you’ve ever seen an ISR in Arduino before, this will look very familiar. You’re just creating an ISR and having a trigger off of a specific vector. If you haven’t ever built your own ISR before, this is what Arduino uses for any of its hardware interrupts. It has a vector defined for a specific ISR and the contents of that ISR are the function that is called when that hardware interrupt is triggered. So the difference with an OpenXLR8 vector is that any OpenXLR8 interrupts have to be handled in this same vector. So it’s one vector that’s handling possibly multiple interrupts. So we have this method where you check the flags at the top of the ISR, and then enter a while loop that will repeat until that flags register comes back clear, and then you want to examine each bit that you care about in that flag register.
So for example, bits one and two that I’ve defined in the previous slides, you’ll check the first one and do whatever is demanded by that bit being set, and then you check the second one and if that’s set, you do whatever is demanded by that. And at the end of the loop, you’ll reject the flags to see if any new interrupt has been triggered while you were handling other interrupts. If the flag register comes back clear, we’re done with the ISR. We’ll want to clear the flag and act registers. Okay, next I’m going to pass it back to Steve to talk about what our specific XLR8 demo is going to look like here.
(13:47) Steve Phillips: Now let’s walk through our demo, we’re going to use the XLR8 I2C SPI UART block to do our demo. Here’s our old friend of a block diagram. We’ve highlighted the OpenXLR8 module. You can see here that inside of that we’ve instantiated UART module, I2C module, and a SPI module. And the UART and I2C have custom interrupts that we need to wire into the pcint module that’s inside of the OpenXLR8. That’s done by going to our OpenXLR8.v file. Down to the bottom of that file will find the pcint module instantiation. It’s typically commented out in OpenXLR8.v.
So we first need to uncomment that, then we need to set the parameter NUM_INTS. The value of this should be equal to the number of interrupts that we’re going to handle. In this particular case, we have UART with three interrupts, and we have the I2C with one, so we’re going to set it to four. Then we need to wire our interrupts into the module itself. Here you can see our four interrupt signals being wired into the x_int_in bus of the pcint module. Now Bryan will walk you through the software side of UART demo.
(15:11) Bryan Craker: All right, now that we’ve talked about how to set up an ISR in general, and we’ve talked about what this demo case is going to look like, I’m going to go through actually setting up an ISR to connect to the module that we have set up here. So in this demo, I’m only going to be connecting to the interrupts. So first you want to define UART interrupt bits and define the interrupt with bits for the RX and TX interrupt. In the OpenXLR8 build that we have the RX is tied to interrupt bit one and the TX is tied to interrupt bit two. So down here, UART interrupted defines are going to just look like this. I’ve just set up an OpenXLR8 RX interrupt on bit one and OpenXLR8 TX interrupt on bit two.
Next, you need to initialize the UART and you’ll need to know the addresses defined by your OpenXLR8 instantiation, and then we’ll use those addresses to initialize an instance of XLR8HardwareS erial in software. So these addresses over on the right side are all just matching exactly to the addresses that have been defined in this particular OpenXLR8 build, and those addresses are then being used to call the instantiation. Next you want to enable the interrupts for the UART XP and once again, it’s useful to put the enables in a function that you run in setup of the Arduino sketch. First, you set the global flag for all OpenXLR8 interrupt bits and then you set the individual interrupt bits that you wish to monitor. I’m using the defines that I set up previously.
Just like in the example before, I have a function called enableSerialInterrupts and I’m setting the global control and mask register. And then I’m using the OpenXLR8 RX interrupted bit, and the OpenXLR8 TX interrupted bit to set the control and mask registers for those bits. And that will be what actually allows us to monitor the RX and TX interrupts in software. Next, we want to set up the ISR. This is just like the example that I showed before, just handling the RX and TX specifically, I’ve added in a lot of serial print lines for the demo, just so that we can see what the XLR8 board is doing as it’s stepping through the ISR. Usually you would, of course, not want a bunch of serial outputs in your ISR as that creates an awful lot of overhead. But for the demo purposes, that it makes it a lot easier to understand.
So just like before, at the top of the ISR we’re checking the flag register and then we are entering the loop and first of all check the RX interrupt and if the RX interrupt flag has been set, we will run the XLR8Serial._rx_complete_irq function which is just the function that handles the RX control. Then we’ll clear that RX bit in the OpenXLR8 flag register. Next we’ll check for a TX interrupt and if that’s been set with we will call the tx_udr_empty_irq which is the function that handles TX interrupts in the XLR8 serial class. Then we clear the OpenXLR8 TX flag bit then we will reject the flags again. If the flag register comes back clear. We will exit the loop and clear the global XLR8 flag and accurate resistors. The demo today is a sketch I’m calling XLR8 Serial Pass Through. It’s based on the serial pass through demo by Eric Nyquist.
(19:45) If you’ve ever seen this demo before, this will look very familiar. If you’ve never seen this demo before, the demo is pre-installed with the Arduino IDE. You can open up the examples and this is one of the sketches available through the IDE. So, very simply, the input to the built in serial will print out to the XLR8Serial that we’ve just instantiated, and input to the XLR8Serial will print out to the built-in Serial, which we can monitor through the Arduino serial monitor.
So, stepping through the sketch all of the defines and ISR have put in a header file called XLR8SerialDemo.h, so I include that and then in the setup function I’m calling that enable serial interrupts function which will turn on all of the interrupts and control registers. Then I’m beginning the two serial interfaces. So it’s the built-in serial interface that everybody that uses Arduino is familiar with. And I’m also beginning the XLR8Serial interface, which we instantiated in our header file earlier. And then just to show what the sketch is doing, I have a debugging line that reports that we’ve become a demo.
And then we’re going to enter the loop. The loop is going to monitor any activity on the serial or XLR8Serial lines. So if any data comes in on the serial interface, the data will be read and then written out straight to the XLR8Serial interface. And vice versa if any data comes into the XLR8Serial interface, that data will be printed out to the console output via the built-in serial interface. So for my hardware setup here, what I’ve done is taken two XLR8 boards and connected them together. You can see the board on the left is connected to the normal built-in serial interface. This would be the same on board, it’s pins zero and one and that serial interface is connected to the board on the right, which has our XLR8 UART module on it, and that module happens to be attached to pins 10 and 11.
So the the board on the right has built-in serial interface for free on that device, so we will be able to still connect to the serial monitor. The board on the left has a very simple sketch on it. It’s simply going to wait for any input to come into its serial interface. And as soon as some sort of input comes to it, it’s just going to respond by writing back the letter “A”. So here’s the program output from the board with XLR8Serial module on it.
(23:05) So we can see what’s happening here. The sketches going to say that its beginning and then I’m going to send some data out of the XLR8Serial module so you will see an TX interrupt. And then when the second board responds, we trigger the ISR again. So we check the flags, we enter the loop, we’re checking for an RX interrupt, and this time we do find an RX interrupt so we handle that. We clear the RX flag, we check for a TX interrupt, this time there is no TX interrupt, we check the flags again they come back clean and there’s no interrupts left to handle.
So we cleared the global flag and write the ACK again, and we print out to the screen that we’ve received the letter “A”. Alright, that’ll do it for today. I hope that you enjoyed the presentation. You can find out more about us at a aloriumtech.com. You can find more about our products on our product pages. Find out more about OpenXLR8 at our OpenXLR8 page. It has demos and more explanations there. We have more videos like this on YouTube, and you can contact us with any questions you might have at our support email firstname.lastname@example.org. Thank you.