Rob

External Interrupt Controller for AVR8

Recommended Posts

Hi again,

 

In a previous thread Jack and I discovered that there was no interrupt controller in the AVR8 Soft Core. I thought it would be an interesting learning experience to write one. I have finally managed to make it work. So I'm going to share it here step by step. This will be based on the AVR8 Softcore V1.6. I'm still learning vhdl myself so if I make any mistakes, please correct me.

 

Here goes:

 

Part 1: Modify Portx.vhdl, so that we can access the port pins without breaking the normal operation of the port

 

Step 1: Add the following line to the port definition at the top of Portx.vhd:   

irqlines   : out std_logic_vector(7 downto 0)

 

 

    Your port definition should look like this now:    

entity pport is generic(PPortNum : natural); 	               port(	                    -- AVR Control                        ireset     : in std_logic;                        cp2	       : in std_logic;                        adr        : in std_logic_vector(15 downto 0);                        dbus_in    : in std_logic_vector(7 downto 0);                        dbus_out   : out std_logic_vector(7 downto 0);                        iore       : in std_logic;                        iowe       : in std_logic;                        out_en     : out std_logic; 		        -- External connection			portx      : out std_logic_vector(7 downto 0);			ddrx       : out std_logic_vector(7 downto 0);			pinx       : in  std_logic_vector(7 downto 0);			irqlines   : out std_logic_vector(7 downto 0));end pport;

 

 

 

Step 2: Add this line to the DBusOutMux generator in Portx.vhd:    

irqlines(i) <= PINx_Resync(i);

 

   Your DBusOutMux generator should now look like this:   

DBusOutMux:for i in pinx'range generatedbus_out(i) <= (PORTx_Int(i) and PORTx_Sel)or(DDRx_Int(i) and DDRx_Sel)or(PINx_Resync(i) and PINx_Sel);irqlines(i) <= PINx_Resync(i);end generate;	

 

 

Ok, that's it for part 1. I'm going to separate each part into its own post to keep the size manageable.

 

Rob

 

P.S. I tried to attach the finished portx.vhd file to this post but got this message:

 

portx.vhd You aren't permitted to upload this kind of file

 

Is there a better place to share a complete file?

Share this post


Link to post
Share on other sites

Part 2: Modify the AvR_uC_CompPack.vhd file to match our component definition in portx.vhd. Also, add component declaration for our new External interrupt controller.

 

Step 1: Add this line to the port's component declaration:

irqlines   : out std_logic_vector(7 downto 0)

 

   Your declaration should look like this now:

component pport is generic(PPortNum : natural); 	               port(	                   -- AVR Control               ireset     : in std_logic;               cp2	      : in std_logic;               adr        : in std_logic_vector(15 downto 0);               dbus_in    : in std_logic_vector(7 downto 0);               dbus_out   : out std_logic_vector(7 downto 0);               iore       : in std_logic;               iowe       : in std_logic;               out_en     : out std_logic;                -- External connection               portx      : out std_logic_vector(7 downto 0);	       ddrx       : out std_logic_vector(7 downto 0);	       pinx       : in  std_logic_vector(7 downto 0);	       irqlines   : out std_logic_vector(7 downto 0));end component;

 

   

Step 2: Add the following lines to the  AvR_uC_CompPack.vhd file, right after the timer counter, and before the UART. Start around line 96 or so.

    

COMPONENT ExtIRQ_ControllerPORT(			-- begin Signals required by AVR8 for this core, do not modify.			nReset 		: in  STD_LOGIC;			clk 			: in  STD_LOGIC;			adr 			: in  STD_LOGIC_VECTOR (15 downto 0);			dbus_in 		: in  STD_LOGIC_VECTOR (7 downto 0);			dbus_out 	: out STD_LOGIC_VECTOR (7 downto 0);			iore 			: in  STD_LOGIC;			iowe 			: in  STD_LOGIC;			out_en		: out STD_LOGIC;			-- end Signals required by AVR8 for this core, do not modify.			clken			: in  STD_LOGIC;			irq_clken	: in	STD_LOGIC;			extpins		: in  STD_LOGIC_VECTOR(7 downto 0);			INTx			: out STD_LOGIC_VECTOR(7 downto 0)	);END COMPONENT;

 

 

Save your work. That's it for part 2.

Share this post


Link to post
Share on other sites

Part 3: Add the new Interrupt controller vhd file:

 

Step 1: Right click on your project and click add new source. I called the File ExtIRQ_Controller.vhd. 

 

Step 2: Add the following code to your new file.

    

--**********************************************************************************************-- External Interrupt Controller Block Peripheral for the AVR Core-- Version 1.00-- Created 01.26.2013-- Designed by Rob Brown--**********************************************************************************************library IEEE;use IEEE.std_logic_1164.all;use IEEE.std_logic_unsigned.all;use WORK.AVRuCPackage.all;entity ExtIRQ_Controller is Port(			-- begin Signals required by AVR8 for this core, do not modify.			nReset 		: in  STD_LOGIC;			clk 			: in  STD_LOGIC;			adr 			: in  STD_LOGIC_VECTOR (15 downto 0);			dbus_in 		: in  STD_LOGIC_VECTOR (7 downto 0);			dbus_out 	: out STD_LOGIC_VECTOR (7 downto 0);			iore 			: in  STD_LOGIC;			iowe 			: in  STD_LOGIC;			out_en		: out STD_LOGIC;			-- end Signals required by AVR8 for this core, do not modify.						clken			: in  STD_LOGIC;			irq_clken	: in	STD_LOGIC;			extpins		: in  STD_LOGIC_VECTOR(7 downto 0);			INTx			: out STD_LOGIC_VECTOR(7 downto 0)			);end ExtIRQ_Controller;-----------------------------------------------------------------------architecture Behavioral of ExtIRQ_Controller is-- Registerssignal EIMSK  : std_logic_vector(7 downto 0);signal EIFR	  : std_logic_vector(7 downto 0);signal EICR	  : std_logic_vector(7 downto 0);-- EIMSK Bitsalias INT0  : std_logic is EIMSK(0);alias INT1  : std_logic is EIMSK(1);alias INT2  : std_logic is EIMSK(2);alias INT3  : std_logic is EIMSK(3);alias INT4  : std_logic is EIMSK(4);alias INT5  : std_logic is EIMSK(5);alias INT6  : std_logic is EIMSK(6);alias INT7  : std_logic is EIMSK(7);-- EIFR Bitsalias INTF0  : std_logic is EIFR(0);alias INTF1  : std_logic is EIFR(1);alias INTF2  : std_logic is EIFR(2);alias INTF3  : std_logic is EIFR(3);alias INTF4  : std_logic is EIFR(4);alias INTF5  : std_logic is EIFR(5);alias INTF6  : std_logic is EIFR(6);alias INTF7  : std_logic is EIFR(7); -- EICR Bitsalias ISC40  : std_logic is EICR(0);alias ISC41  : std_logic is EICR(1);alias ISC50  : std_logic is EICR(2);alias ISC51  : std_logic is EICR(3);alias ISC60  : std_logic is EICR(4);alias ISC61  : std_logic is EICR(5);alias ISC70  : std_logic is EICR(6);alias ISC71  : std_logic is EICR(7); -- Risign/falling edge detectors	signal INTxRE : std_logic_vector (7 downto 0); -- Rising edge of external input INTxsignal INTxFE : std_logic_vector (7 downto 0); -- Falling edge of external input INT0signal INTxLatched : std_logic_vector (7 downto 0);	-- Synchronizer signalssignal INTxSA  : std_logic_vector (7 downto 0);signal INTxSB  : std_logic_vector (7 downto 0); -- Output of the synchronizer for INTxsignal TRGR : std_logic_vector(7 downto 0);--constant EIMSK_Address : std_logic_vector(IOAdrWidth-1 downto 0) := CAVRIOAdr(16#39#);--constant EIFR_Address  : std_logic_vector(IOAdrWidth-1 downto 0) := CAVRIOAdr(16#38#);--constant EICR_Address  : std_logic_vector(IOAdrWidth-1 downto 0) := CAVRIOAdr(16#3A#);begin-- SynchronizersSyncDFFs:process(clk,nReset)	begin	 if (nReset='0') then      -- Reset	INTxSA <= (others => '0');  	INTxSB <= (others => '0');    elsif (clk='1' and clk'event) then -- Clock  if (irq_clken='1') then       -- Clock Enable(Note 2)	 		INTxSA <= extpins;  		INTxSB <= INTxSA;  end if;	 	 end if;	 end process;-- Edge Detectoredge_detect:for i in INTxSB'range generate	INTxRE(i) <= (not INTxSB(i) and extpins(i));	INTxFE(i) <= (INTxSB(i) and not extpins(i));   	INTxLatched(i) <= INTxSB(i);end generate;-- Interrupt ProcessINT_Proc:process(clk,nReset)begin if (nReset='0') then   INTx(3 downto 0) <= (others => '0'); elsif  (clk='1' and clk'event)  then  if (clken='1') then       -- Clock Enable	   	-- interrupts 0 through 3 do not set their corresponding EIFR flag! See Atmega103 Data Sheet top of page 31.		-- these four interrupts also do not use EICR bits. They are always level triggered, active low.    	TRGR(0) <= (INT0 and not INTxLatched(0));		TRGR(1) <= (INT1 and not INTxLatched(1));		TRGR(2) <= (INT2 and not INTxLatched(2));		TRGR(3) <= (INT3 and not INTxLatched(3));				-- interrupts 4 through 7's triggers are sensitive to EICR bits		-- 00 low level trigger, 10 falling edge trigger, 11 rising edge trigger, 01 is reserved.		-- See Atmega103 Data Sheet page 30 and 31.		TRGR(4) <= (INT4 and ((not ISC41 and not ISC40 and not INTxLatched(4)) or (ISC41 and not ISC40 and INTxFE(4)) or (ISC41 and ISC40 and INTxRE(4))));		TRGR(5) <= (INT5 and ((not ISC51 and not ISC50 and not INTxLatched(5)) or (ISC51 and not ISC50 and INTxFE(5)) or (ISC51 and ISC50 and INTxRE(5))));		TRGR(6) <= (INT6 and ((not ISC61 and not ISC60 and not INTxLatched(6)) or (ISC61 and not ISC60 and INTxFE(6)) or (ISC61 and ISC60 and INTxRE(6))));		TRGR(7) <= (INT7 and ((not ISC71 and not ISC70 and not INTxLatched(7)) or (ISC71 and not ISC70 and INTxFE(7)) or (ISC71 and ISC70 and INTxRE(7))));					INTx <= TRGR;  end if;	  end if;	end process;-- Write EIMSK bitsEIMSK_Bits:process(clk,nReset)begin if (nReset='0') then   EIMSK <= (others => '0'); elsif  (clk='1' and clk'event)  then  if (clken='1') then       -- Clock Enable	   if (adr=EIMSK_Address and iowe='1') then    EIMSK <= dbus_in;	   end if;  end if;	  end if;	end process;-- Write EIFR bitsEIFR_Bits:process(clk,nReset)begin if (nReset='0') then   EIFR <= (others => '0'); elsif  (clk='1' and clk'event)  then  if (clken='1') then       -- Clock Enable	   if (adr=EIFR_Address and iowe='1') then    EIFR(7 downto 4) <= dbus_in(7 downto 4);		 	 EIFR(3 downto 0) <= "0000"; -- in Atmega103, these bits are reserved and always read '0', See Atmega103 Data Sheet top of page 31. 		else	 EIFR(7 downto 4) <= TRGR(7 downto 4);	 EIFR(3 downto 0) <= "0000"; -- in Atmega103, these bits are reserved and always read '0', See Atmega103 Data Sheet top of page 31. 	   end if;  end if;	  end if;	end process;-- Write EICR bitsEICR_Bits:process(clk,nReset)begin if (nReset='0') then   EICR <= (others => '0'); elsif  (clk='1' and clk'event)  then  if (clken='1') then       -- Clock Enable	   if (adr=EICR_Address and iowe='1') then    EICR <= dbus_in;	   end if;  end if;	  end if;	end process;-- Output Enable out_en <= '1' when ((adr=EIMSK_Address or adr=EIFR_Address or adr=EICR_Address) and iore='1') else '0'; -- Read register dbus_out <= EIMSK when (adr=EIMSK_Address) else             EIFR when (adr=EIFR_Address) else             EICR when (adr=EICR_Address) else	    "ZZZZZZZZ";					end Behavioral;

 

 

Save your work, that's it for part 3.

Share this post


Link to post
Share on other sites

Part 4, pulling it all together. This part has a lot of small steps, I'm not going to try to explain them all in this post. If you have any questions just let me know.

 

Step 1. Add the following line to the peripherial control settings in Papilio_AVR8.vhd:    

constant CImplExtIRQ				: boolean := TRUE;	--AVR8 Interrupt Unit

 

 You should have settings that look like this now:

-- Use these setting to control which peripherals you want to include with your custom AVR8 implementation.constant CImplPORTA			: boolean := TRUE; constant CImplPORTB			: boolean := TRUE;constant CImplPORTC			: boolean := TRUE;constant CImplPORTD    			: boolean := TRUE;constant CImplPORTE      		: boolean := TRUE;constant CImplPORTF           		: boolean := TRUE;constant CImplUART      		: boolean := TRUE;	--AVR8 UART peripheralconstant CImplTmrCnt     		: boolean := TRUE;	--AVR8 Timerconstant CImplExtIRQ		        : boolean := TRUE;	--AVR8 Interrupt Unitconstant CImplpapilio_core_template     : boolean := FALSE;	--An example User Core, use this template to make your own custom peripherals. 

  You DO NOT have to turn off any of the GPIO ports. the interrupt controller can be connected to any port and still have normal port operation.

 

Step 2, Add some signal definitions to Papilio_AVR8.vhd (same file):    

-- Ext IRQ Controllersignal extirq_dbusout    : std_logic_vector (7 downto 0);signal extirq_out_en     : std_logic;signal ext_irqlines      : std_logic_vector(7 downto 0);

 

     I added them around line 308, after the signals for the timer counter, but before signals for uart.   

-- Timer/Countersignal tc_dbusout    : std_logic_vector (7 downto 0);signal tc_out_en     : std_logic;-- Ext IRQ Controllersignal extirq_dbusout    : std_logic_vector (7 downto 0);signal extirq_out_en     : std_logic;signal ext_irqlines	 : std_logic_vector(7 downto 0);-- UARTsignal uart_dbusout  : std_logic_vector (7 downto 0);signal uart_out_en   : std_logic; 

 

 Step 3:  Free up interrupt lines. around line 379, after the user core comment the following two lines:

core_irqlines(7 downto 4) <= ( others => '0');core_irqlines(3 downto 0) <= ( others => '0');

   you should have this now:    

-- Unused IRQ lines--core_irqlines(7 downto 4) <= ( others => '0');--core_irqlines(3 downto 0) <= ( others => '0');core_irqlines(13 downto 10) <= ( others => '0');core_irqlines(16) <= '0';core_irqlines(22 downto 20) <= ( others => '0');-- ************************

 

Step 4: Free up output enable lines just under the IRQ lines are the enable lines you need to just change the 10 to an 11 in both lines.

-- Unused out_enio_port_out_en(10 to 15) <= (others => '0');io_port_out(10 to 15) <= (others => (others => '0'));

    You should have this now:    

-- Unused out_enio_port_out_en(11 to 15) <= (others => '0');io_port_out(11 to 15) <= (others => (others => '0'));

Step 5 Connect the IRQ controller to porta. Add this to the bottom of porta's port map

    

irqlines   => ext_irqlines

   Port A should look like this now:

PORTA_COMP:component pport  	generic map(PPortNum => 0)	port map(	                   -- AVR Control               ireset     => core_ireset,               cp2	      => clk16M, -- clk,               adr        => core_adr,               dbus_in    => core_dbusout,               dbus_out   => porta_dbusout,               iore       => core_iore,               iowe       => core_iowe,               out_en     => porta_out_en,	       -- External connection	       portx      => PortAReg,	       ddrx       => DDRAReg,	       pinx       => porta,	       irqlines   => ext_irqlines);

Step 6 clean up dangling lines on other ports. Add the following to the other port maps (all of the remaining ones, B - F)    

irqlines   => open

   Port B - F should look like this now (except for the port letter)    

PORTB_Impl:if CImplPORTB generatePORTB_COMP:component pport 	generic map (PPortNum => 1)	port map(	                   -- AVR Control               ireset     => core_ireset,               cp2	      => clk16M, -- clk,                adr        => core_adr,               dbus_in    => core_dbusout,               dbus_out   => portb_dbusout,               iore       => core_iore,               iowe       => core_iowe,               out_en     => portb_out_en,	       -- External connection	       portx      => PortBReg,	       ddrx       => DDRBReg,               pinx       => portb,	       irqlines   => open);

Step 7: Add the port map for the interrupt controller. I added it after the last port and before the timer counter, around line 668.

   

--****************** External IRQ Controller**************************ExtIRQ_Impl:if CImplExtIRQ generateExtIRQ_Inst:component ExtIRQ_Controller port map(	           -- AVR Control               nReset     => core_ireset,               clk	  => clk16M, -- clk,	       clken	  => vcc,	       irq_clken  => vcc,	       adr        => core_adr,               dbus_in    => core_dbusout,                dbus_out   => extirq_dbusout,                iore       => core_iore,               iowe       => core_iowe,               out_en     => extirq_out_en,	       ------------------------------------------------	       extpins	  => ext_irqlines,			               INTx      => core_irqlines(7 downto 0));			   -- ExtIRQ connection to the external multiplexer							  io_port_out(10)    <= extirq_dbusout;io_port_out_en(10) <= extirq_out_en; end generate;

That's it. save your work. Synthesize it, and copy it to the custom bit files. I'll upload some example sketches to run in a few, I've gotta take a break first though.

Share this post


Link to post
Share on other sites

Ok, back at it. One last change to the papilio-arduino code before we can run a sketch. In WInterrupts.c, find ATmega 103 section of the attachInterrupt function and change it to look like this:

#elif defined(__AVR_ATmega103__)       //interrupts 0 to 3 are level triggered (active low) and do not use the sense bits in EICR    case 0:      //EICRA = (EICRA & ~((1 << ISC00) | (1 << ISC01))) | (mode << ISC00);      EIMSK |= (1 << INT0);      break;    case 1:      //EICRA = (EICRA & ~((1 << ISC10) | (1 << ISC11))) | (mode << ISC10);      EIMSK |= (1 << INT1);      break;	case 2:      //EICRA = (EICRA & ~((1 << ISC00) | (1 << ISC01))) | (mode << ISC00);      EIMSK |= (1 << INT2);      break;    case 3:      //EICRA = (EICRA & ~((1 << ISC10) | (1 << ISC11))) | (mode << ISC10);      EIMSK |= (1 << INT3);      break;    case 4:      EICRA = (EICRA & ~((1 << ISC40) | (1 << ISC41))) | (mode << ISC40);      EIMSK |= (1 << INT4);      break;    case 5:      EICRA = (EICRA & ~((1 << ISC50) | (1 << ISC51))) | (mode << ISC50);      EIMSK |= (1 << INT5);      break;    case 6:      EICRA = (EICRA & ~((1 << ISC60) | (1 << ISC61))) | (mode << ISC60);      EIMSK |= (1 << INT6);      break;    case 7:      EICRA = (EICRA & ~((1 << ISC70) | (1 << ISC71))) | (mode << ISC70);      EIMSK |= (1 << INT7);      break;	  #else

 

 

  That will allow us to use the attachInterrupt function for our sketchs. I should explain that there are 8 total interrupts numbered 0 to 7. However, interrupts 0 to 3 do not have EICR bits and are therefore low level triggered only. Interrupts 4 to 7 do have EICR bits and can be triggered by low level, falling edge, or rising edge. 

 

Sketches soon...

Share this post


Link to post
Share on other sites

Here is a debug sketch, for the irq controller. don't forget to set the board to the custom board.

#define PIN_IRQ_IN  A4volatile unsigned int count = 0;void setup() {  pinMode(PIN_IRQ_IN, INPUT);  Serial.begin(9600);  attachInterrupt(4, irqcount, LOW);}void loop() {  // get registers from core  unsigned int EIMSK_TMP = EIMSK;  unsigned int EIFR_TMP = EIFR;  unsigned int EICR_TMP = EICR;    Serial.print("EIMSK =");  Serial.print(EIMSK_TMP);  Serial.print("\r\n");    Serial.print("EIFR =");  Serial.print(EIFR_TMP);  Serial.print("\r\n");    Serial.print("EICR =");  Serial.print(EICR_TMP);  Serial.print("\r\n");    Serial.print("Count =");  Serial.print(count);  Serial.print("\r\n");}void irqcount(void) {  count++;}

 

this sketch is using interrupt 4 on pin 4 of port a. 

 

more sketches soon.

Share this post


Link to post
Share on other sites

An example of using attachInterrupt function. Probably the minimal sketch to make interrupts work.

 

#define PIN_IRQ_IN  A4volatile unsigned int count = 0;void setup() {  pinMode(PIN_IRQ_IN, INPUT);  Serial.begin(9600);  attachInterrupt(4, irqcount, LOW);}void loop() {    Serial.print("Count =");  Serial.print(count);  Serial.print("\r\n");}void irqcount(void) {  count++;}

Share this post


Link to post
Share on other sites

An example of directly accessing the interrupt registers.

#define PIN_IRQ_IN  A4volatile unsigned int count = 0;void setup() {  pinMode(PIN_IRQ_IN, INPUT);  Serial.begin(9600);  EIMSK |= (1 << INT4);     // Enable external interrupt INT0 GICR   //EICR |= (0 << ISC41) | (0 << ISC40);   // Trigger INT4 on low level  EICR |= (1 << ISC41) | (0 << ISC40);   // Trigger INT4 on falling egde  //EICR |= (1 << ISC41) | (1 << ISC40);   // Trigger INT4 on rising edge}void loop() {    Serial.print("Count =");  Serial.print(count);  Serial.print("\r\n");}ISR(INT4_vect) {    count++;}

 

Ok, I think I'm done for the night. 

Share this post


Link to post
Share on other sites

Rob, this is awesome work! Thank you for sharing it with us.

 

I just added the vhd and vhdl extensions to the system, if there is a missing extension it is always possible to add them to a zip file and upload that. Or just mention they are not allowed and I'll update the system.

 

It's amazing how quickly you put this together. :) If you want to me to include this in the Github branch then the easiest thing to do is create a pull request with the changes on github. I put together a short tutorial about doing this here:

http://retrocade.gadgetfactory.net/index.php?n=Main.Contribute

 

Thanks, this is a really great walk through, we will get this posted to the showcase and the blog.

 

Jack.

Share this post


Link to post
Share on other sites

Thank you for the kind words Jack. I'm Happy to share. The time you spent helping me out last week was very helpful, and really helped solidify the concepts I needed to do this.  That small bit of time was very productive for me :)

 

I'd be honored to have this included in the Github branch. I'm going to go read the tutorial now. I'm also thinking about porting this into the "shifty" branch. I need to study it a bit more first though.

 

Rob

 

 

P.S. I feel silly that I didn't think to zip the files up!  

Share this post


Link to post
Share on other sites

On two of your last three code examples you use attachInterrupt, on your last, there is nothing that connects ISR method to anything..

 

Github's gist is an good way of sharing codesnippets also, some forums underrstand gists so you can view with linenumbers, formatting and whatnot, not sure how it works on this though?

Share this post


Link to post
Share on other sites

Ya, that last example uses the default interrupt method (it's defined in the ardiuno enviroment), It bypasses the attachInterrupt function and accesses the AVR registers directly. Give it a spin, it should work, I tested before uploading.

 

Thanks for the tips on gists. I'll go read up on that.

 

Jack, I forked this version from github:

https://github.com/GadgetFactory/Arduino-Soft-Core

 

It has some files missing and won't build synthesize, is there a better version to fork for inclusion into the main branch?

Share this post


Link to post
Share on other sites

I didn't know either! I just spent the last week or so digging through the ATmega103 datasheet, the vhdl for the softcore and the papilio-arduino interface code. It was fun, and I learned a lot. Oh, can't forget the help from Jack, that put it over the top. :)

Share this post


Link to post
Share on other sites

After I read my last post again this morning, I realized that may have come across wrong. My apologies if it did. 

 

What I meant to say was: I didn't know either *_until_* I spent the last week or so digging through those documents. 

  • Like 1

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now