Search Microcontrollers

Thursday, May 21, 2015

Cortex M3 - SPI / 1

I have been playing with SPI a few times, but never on a Cortex M3 using CMSIS.

That, by itself, it should be a good reason to dig into this topic, however I have a nice project in mind and it will require SPI communicatin, so... let's  get to it!

Some basic stuff first :
SPI uses four pins: MOSI (Master Out Slave In), MISO (you guess it), CLK (clock) , SSEL and as Chip enable/select to activate the slave, normally a simple GPIO pin on the master.

NXP Cortex M3s implement a variation of SPI called SSP (Synchronous Serial Port) which supports the "old" SPI.
In the LPC1768 there are two SSP peripherals called SSP0 and SSP1.

The LPC17xx manual says:

"The two SSP interfaces, SSP0 and SSP1 are configured using the following registers:

  1. Power: In the PCONP register, set bit PCSSP0 to enable SSP0 and bit PCSSP1 to enable SSP1.
    Remark: On reset, both SSP interfaces are enabled (PCSSP0/1 = 1).
  2. Clock: In PCLKSEL0 select PCLK_SSP1; in PCLKSEL1 select PCLK_SSP0. In master mode, the clock must be scaled down.
  3. Pins: Select the SSP pins through the PINSEL registers and pin modes through the PINMODE registers.
  4. Interrupts: Interrupts are enabled in the SSP0IMSC register for SSP0 and SSP1IMSC register for SSP1. Interrupts are enabled in the NVIC using the appropriate Interrupt Set Enable register.
  5. Initialization: There are two control registers for each of the SSP ports to be configured: SSP0CR0 and SSP0CR1 for SSP0, SSP1CR0 and SSP1CR1 for SSP1.
  6. DMA: The Rx and Tx FIFOs of the SSP interfaces can be connected to the GPDMA controller
Remark: SSP0 is intended to be used as an alternative for the SPI interface, which is included as a legacy peripheral. Only one of these peripherals can be used at the any one time" 
(@NXP LPC17xx user manual)

It seems to me this is a pretty good checklist.

1) Power ON


Power CONtrol for Peripherals (PCONP) is the register we use to turn the SSP (or any other) interfaces on:

  LPC_SC->PCONP |= (1 << 21); /* Enable power to SSPI0 block */
  LPC_SC->PCONP |= (1 << 10); /* Enable power to SSPI1 block */

Normally we just need one, I reported the lines for both so you can decide which one to use.

2) Clock in

The Peripheral clock selection is done via the PCLKSEL0 and 1.
Each peripheral uses 2 bits

00 : PCLK_peripheral = CCLK/4 00
01 : PCLK_peripheral = CCLK
10 : PCLK_peripheral = CCLK/2
11 : PCLK_peripheral = CCLK/8

SSP0 uses bits 11:10 of PCLKSEL1 and SSP1 uses bits 21:20 of PCLKSEL0

LPC_SC->PCLKSEL1 &= ~(3<<10);  /* PCLKSP0 = CCLK/4 */
LPC_SC->PCLKSEL1 |=  (1<<10);  /* PCLKSP0 = CCLK */

LPC_SC->PCLKSEL0 &= ~(3<<20);  /* PCLKSP1 = CCLK/4*/
LPC_SC->PCLKSEL0 |=  (1<<20);  /* PCLKSP1 = CCLK */

3) Pins

Here we will set MISO, MOSI and CLK pins, plus you need to remember to set 1 GPIO as output to enable the slave, in this example we will assume you are using GPIO P1.21 as SSEL (Slave Selection / Enable).
Normally SPI slaves are selected active when SSEL is LOW.

A summary of the available configurations for SSP pins :



 /* ----> SSEL : output set to high. */
LPC_PINCON->PINSEL3 &= ~(0<<10);   /* P1.21 SSEL (used as GPIO)   */
LPC_GPIO1->FIODIR   |=  (1<<21);   /* P1.21 is output */
LPC_GPIO1->FIOPIN   |=  (1<<21);   /* set P1.21 high*/
  
 /* ----> SSP0 : SCK, MISO, MOSI  */
LPC_PINCON->PINSEL3 &= ~(3UL<<8);       /* P1.20 cleared  */
LPC_PINCON->PINSEL3 |=  (3UL<<8);       /* P1.20 SCK0  */
   /* P1.23, P1.24 cleared        */ 
LPC_PINCON->PINSEL3 &= ~((3<<14) | (3<<16)); 
   /* P1.23 MISO0, P1.24 MOSI0    */ 
LPC_PINCON->PINSEL3 |=  ((3<<14) | (3<<16)); 

 /* ----> SSP1 : SCK, MISO, MOSI  */
LPC_PINCON->PINSEL0 &= ~(0x3F<<14);  /* P0.7,8,9 cleared  */
    /* ... an then set to function 2 */
LPC_PINCON->PINSEL0 |=  (2UL<<14) | (2UL<<16) | (2UL<<18);

4) Interrupts

As suggested by the checklist, we will use the SSP Interrupt Mask Registers SSPxIMSC.


As you can see, these interrupts are used to identify error conditions or FIFO status to ease buffered communication.
Since SSP is a synchronous communication (unlike the UART), there is no point in having a RX data ready interrupt.
In fact, each time you want a slave device to send you some data, using SPI / SPSS you explicitly have to ask for it (polling), meaning you should also be already listening for it to arrive, no need for an interrupt.

Should you need to implement some more advanced control, feel free to enable those interrupts, in that case you should check the SSPxRIS (Raw Interrupt Status), SSPxMIS (Masked Interrupt Status) and SSPxICR (Interrupt Clear) registers.

5) Initialisation

Ok, we definitely need this one.
What we need here is to accurately set the bit frequency, so that it will match the frequency supported by the slave device we are interfacing.
This is done via the Prescaler Register (CPSR) that divides the peripheral clock (pclk) we initially configured in step 2 (PCLKSEL0 and 1).

So, let's assume our processor is running at 100MHz, we fed the peripheral with the same clock speed (by providing a divider = 1) and now we want to obtain a 400KBit/s rate.
How do we do that?

#define sspKBps 400000
int prescaler = SystemCoreClock / sspKBps; 
/* 100.000.000/400.000 = 250 */

LPC_SSP0->CPSR = prescaler; /* for SSP0 */
LPC_SSP1->CPSR = prescaler; /* for SSP1 */

Now we need to use the SSP Control registers to specify how many bits should be transferred and which protocol -Frame Format- to use (remember, the SSP can do more than SPI, in fact it supprots TI and Microwire formats too). 
Also, we can further reduce the bit rate here by dividing the value of the prescaler, plus we can set clock phase and polarity.

  
/* SSP0 : 8Bit, SPI, CPOL=0, CPHA=0 */                                  
LPC_SSP0->CR0  = 0x0007;   
/* SSP1 : 8Bit, SPI, CPOL=0, CPHA=0 */                                  
LPC_SSP1->CR0  = 0x0007;   

The Control Register 1 (CR1) allows to enable the SSP port and to configure it as master or slave.



LPC_SSP0->CR1  = 0x0002;       /* SSP0 enable, master  */
LPC_SSP1->CR1  = 0x0002;       /* SSP1 enable, master  */

6) DMA

I am sure some day I will feel particularly brave and will have a go at it, for now, let's just say that we can enable DMA transfers for SSP ports using the SSPxDMACR registers.



Ok, with all this our SSP port should be ready for communication, at least on the Master side.

So, where do we put output data and from where do we get input?
There is a Data Register SSPxDR (we can use the low 16 bits) that is used both for RX and TX.
Why is this possible?
SSP (or SPI) is synchronous communication, meaning you cannot write and read at the same time on the line, or better, for every byte you send out, you will get a byte back.

The LPC17xx manual says :

"Write: software can write data to be sent in a future frame to this register whenever the TNF bit in the Status register is 1, indicating that the Tx FIFO is not full. If the Tx FIFO was previously empty and the SSP controller is not busy on the bus, transmission of the data will begin immediately. Otherwise the data written to this register will be sent as soon as all previous data has been sent (and received). If the data length is less than 16 bits, software must right-justify the data written to this register.

Read: software can read data from this register whenever the RNE bit in the Status register is 1, indicating that the Rx FIFO is not empty.
When software reads this register, the SSP controller returns data from the least recent frame in the Rx FIFO. If the data length is less than 16 bits, the data is right-justified in this field with higher order bits filled with 0s"

Looks like we need to check the Status Register (SSPxSR)


So, putting it all together, to exchange 1 byte we need to :

int SSP1_sendbyte(int out)
{
// enable your GPIO used as SSEL
 LPC_GPIO1->FIOCLR = 1<<21; // enable slave
 // while(!(LPC_SSP1->SR & 1)) ; // Wait until TX Empty
 // might need a grace period here, depending on the slave
 LPC_SSP1->DR = out; // output data
 // while(LPC_SSP1->SR & (1 << 4)); // Wait until SSP is busy
  while(LPC_SSP1->SR & (1 << 2)); // Wait until we have data in RX
 LPC_GPIO1->FIOSET = 1<<21; // disable slave
 return LPC_SSP1->DR;
}

In a future post we will test this with some SPI device.

Tuesday, May 19, 2015

Cortex M3 - UART / 2

Interrupts are cool.
No, seriously, if you played with serial communications you know that using an ISR helps a lot because you never know when the other device decides to send a few bytes on the line.
Asynchronous communication in full duplex baby!

So it is good to have an Interrupt Service Routine that gets the data for you from the line while you are busy doing other things.
Typically you are going to have a buffer with two pointers: one knows where to find the first location in the buffer where to write data and the other knows the first location where you did not get data out of the buffer yet.
If the two pointers point to the same lcoation, then there is no data to be extracted from the buffer.

In general this is a convenient way to buy some time and check every now and then if we received some data.


In the image above you can see an example where 3 bytes were received, so the write pointer points to the 4th location and 1 byte was read from the buffer, so the Read Ptr points to the 2nd location.
The two pointers have different values, so there is still data to be read in the buffer.
Obviously while you read and write you increment the relevant pointer, resetting it to zero when it reaches the end of the buffer.

This is one way to manage asynchronous communications, of course you could add some logic in your Interrupt handler routine to make your application react immediately i.e. when a specific character is received.
It is in genreral good practice, tho, to have as little logic as possible in the ISR routines, they should complete in the shortest amount of time possible.

Now, the funny thing is that the buffer we described is also called a FIFO (First In-First Out) buffer... which is already supported via hardware in the Cortex M3.

That's right, if you needed just a few bytes in your buffer, no need to set up an ISR, you could simply enable the internal FIFO and you can still do it even if you decide to use interrupts.

void SER_fifo(int uart,uint32_t enable,uint32_t dma,uint32_t trigger)
{
LPC_UART_TypeDef *pUart;
pUart = __get_uart_base(uart);
pUart->FCR |= (enable&0x1)|((dma&1)<<3)|((trigger&0x3)<<6);
}

The Fifo Control Register (FCR) can be used to enable the Fifo, define how many bytes should be received by it before triggering an IRQ and if it should use a dma channel to automatically copy the data to a specific memory location.
Neat eh?

@NXP

You can also reset the fifos (both the output fifo and the input fifo)... wait.. output fifo?
Yes, serial communication might be a bit slow for MCUs and imagine you need to send a few bytes out: the output fifo will be happy to store some of those bytes for you so you can go have fun doing other interesting tasks while it handles the communication.
You can do the same thing with an ISR routine or even use both at the same time.
Notice that you can activate both (RX and TX) fifos or none of them, but you cannot activate just RX and not TX or the other way round.

Now, let's see the interrupts.


The UARTxIER are the Interrupt Enable Registers for the various UARTS.
There are two interrupts we want to consider there :
- The one generated when a new byte is received (or when the number of bytes in the fifo reaches the trigger threshold, if enabled) and this is the RBR one.
- The other, generated when the line is ready to send out some new data, meet the THRE (Transmit Hold Register Empty) interrupt.
The third one is triggered when the status of the receive line changes.

#define IER_RBR  0x01
#define IER_THRE 0x02
#define IER_RXLS 0x04
pUart->IER = IER_RBR | IER_THRE | IER_RXLS;  

That line enables the uart interrupts... almost.
Actually we also need to enable the Uartx IRQ in the Nested Vector Interrupt Controller (NVIC)  which has a cool name because it does cool stuff.

switch (uart)
{
  case 0: NVIC_EnableIRQ(UART0_IRQn); break;
  case 2: NVIC_EnableIRQ(UART2_IRQn); break;
  case 3: NVIC_EnableIRQ(UART3_IRQn); break;
}

Now interrupts will be fired upon events and we should really do something with them.
To capture them we just need to define a function (one per each uart we use) with a specific name, I am using uart2 so :

void UART2_IRQHandler(void) __irq
{
   ...
}

Now we need to fill it with some good code, because I never had luck in succesfully compiling a statement like "...", so I guess it's not going to work for you either.

First thing, let's just say we want to have an input buffer which will be filled by the IRQHandler procedure.

#define UART_RX_BUFSIZE 32
uint8_t UART_RxBuf[UART_RX_BUFSIZE];
uint8_t bufWptr =0;
uint8_t bufRptr = 0;

Then we need to consider that the IRQHandler will be called for every kind of uart (uart2 in my case) related interrupt.
By "every" we actually mean as many as you can count with two fingers (or two bits), in other words : four.

This is the Interrupt Identification Register (IIR) which can be used to get the IntID, a 2 bit number that identifies which interrupt we are dealing with.



void UART2_IRQHandler(void) __irq
{
uint8_t intId;
intId = ((LPC_UART2->IIR)>>1)& 0x7;

switch (intId)
{
  case INTID_RXLS : break;// whatever
  case INTID_RDA  : 
while((LPC_UART2->LSR) & 0x1) 
                {
        UART_RxBuf[bufWptr++] = LPC_UART2->RBR;
        if (bufWptr >= UART_RX_BUFSIZE)
         bufWptr = 0;
}
break;
  case INTID_CTI  : break; // whatever
  case INTID_THRE : break; // whatever
}
}

This is a simple implementation that should provide RX interrupt handling, notice it does not check if our software buffer is overran.

Let's just add a couple of functions to get the data out of the buffer and check if we should do it.

int SER_hasBufData()
{
 return (bufWptr!=bufRptr);
}

uint8_t SER_getBufData()
{
 if (bufRptr>=UART_RX_BUFSIZE) bufRptr=0;
 return UART_RxBuf[bufRptr++];
}

And finally we can update the test program

int main(void)
{
  SystemInit();
  setpll0(25,2,3);
  SystemCoreClockUpdate();
  SER_init(2,115200);
  SER_fifo(2,1,0,0);
  while (1) 
  {
   delay(10000000);
   SER_putString (2, ".");
   while (SER_hasBufData())
   {
SER_putString (2, "Received : ");
SER_putChar(2,SER_getBufData());
SER_putString (2, "\r\n");
   }  
  }
  return 0;
}

As usual, you can get my full code here.

Sunday, May 17, 2015

Cortex M3 - UART / 1

Hello again, been a bit too busy to blog for quite some time, sorry for that.

I have a nice Cortex M3 board around since some time, it is based on a NXP LPC1768, which is quite a neat processor.
It can run up to 100MHz, has 512K Flash, 64K Sram and a nice set of peripherals.

The board itself is quite nice too, not sure you can still find it around -ebay listings below, to give you an idea on what's available-, mine is called "LandTiger" and I guess it is just some clone of some popular dev board.




That said, I wanted to play with the UART using a standard CMSIS approach.
CMSIS stands for Cortex Microcontroller Software Intertface Standard, it is defined by ARM and supported by various IDEs.

CMSIS deserves a post on its own, if you don't know it, there is quite a lot of info around already, but I might post myself a sort of "CMSIS for dummies" some time.

Regarding the IDE/toolchain there are quite a few options, currently I am using Keil uVision5 (there is a free version, codesize limited), but I also tried IAR and CoIDE (which is open source).
They all work, and all of them require a bit of fight to get the things the way you want them (at least this happened to me), I couldn't  really pick a winner in the end, but decided to stick to Keil uVision.

So, the UART.
Why the UART first of all?
Meh, getting serial communication is pretty key to allow some remote debugging, so it is a good place to start (maybe just after blinking a few LEDs).

Now, CMSIS provides a layer that gives some kind of standard helping you when you have to port your code from a device to another.
For what I can see, you cannot simply change device, recompile and hope everything works.
NXP devices have some peculairities and need to be handled differently from STM devices etc.

The way you normally manage this is to generate your own abstraction layer (or use some available libraries that do just that).

This post is about the LPC1768, but most of the info might eventually apply to other devices too, provided the register addresses will be different, but those are defined in a CMSIS include file usually provided by the vendor.

So, the LPC1768 has 4 UARTs and uarts 0,2 and 3 work pretty much the same way.
Uart1 is a bit special, so it will not be discussed here.

(@NXP)

There are quite a few registers to play with, as you can see from the LPC17xx manual.
Two easy ones : Receive Register (RBR) and Transmit Register (THR), these should not require a lot of explanations.
Can you see the specification (DLAB=0)?
That means that the Divisor LAtch Bit must be set to zero.
We use the DLAB to set the frequency divider (hence the name) to obtain specific baud rates, basically when you set the baud rates, the DLAB bit must be 1, in any other moment it has to be 0 (and in some case it does not matter, but leave it to zero in that case :) ).
The divisor is set via the DLL and DLM registers, containing the low 8 bits and the high 8 bits of a 16 bit unsigned integer, being the divisor.

DLAB is the bit 7 of the LCR register.

But setting the baud rates and the typical 8,N,1 configuration is no all you have to do.

First of all, Cortex Microcontrollers are usually designed with power saving objectives in mind, so it is often the case that by default peripherals are turned OFF.
Before you can configure the UART, it is a good idea to turn it on!

This is quite easy, it is done via the Power CONtrol for Peripherals  (PCONP) register

(@NXP)

LPC_UART_TypeDef * __get_uart_base(int uart)
{
switch (uart)
{
 case 0: LPC_SC->PCONP |= (1<<3); break;
 case 2: LPC_SC->PCONP |= (1<<24); break;
 case 3: LPC_SC->PCONP |= (1<<25); break;
}
}

If you check the simple code and the register, you will notice that depending on which uart we want to use, we turn on one single bit of the register (3, 24 or 25).

So, now the UART is on, but there is another typical feature of cortex microcontrollers : pins are used for different purposes, so there is a multiplexer that has to be configured to make sure we have a RX and a TX pin connected to our uart.

This is done via the PINSELx registers, for the uarts we will need PINSEL0 (uart0 and 2) and PINSEL1 (uart3).


Next comes the peripheral (uartx) clock configuration.
Cortex M3 MCUs can use different clock signals per each peripheral, the LPC1768 allows to divide the system clock by 1,2,4 or 8 times.

These dividers are configured using two bits per each peripheral in the PCLKSELx registers



If we need to have reliable communication at high baud rates, it is advisable to set the value to 0x01, equal to no clock division.

We can expand the previous switch to include both pin and clock configuration and embed everything in a SER_On(int uart) function

void SER_On(int uart)
{
switch (uart)
{
  case 0: 
LPC_PINCON->PINSEL0 &= (0x0F<<4); 
        LPC_PINCON->PINSEL0 |= (1 << 4);             
/* Pin P0.2 used as TXD0 (Com0) */
        LPC_PINCON->PINSEL0 |= (1 << 6);             
/* Pin P0.3 used as RXD0 (Com0) */
        LPC_SC->PCONP |= (1<<3); 
        LPC_SC->PCLKSEL0 &= ~(3<<6);

LPC_SC->PCLKSEL0 |= (1<<6); // = CCLK
  break;
  case 2:
LPC_PINCON->PINSEL0 &= (0x0F<<20); 
        LPC_PINCON->PINSEL0 |= (1 << 20);             
/* Pin P0.10 used as TXD2 (Com2) */
        LPC_PINCON->PINSEL0 |= (1 << 22);             
/* Pin P0.11 used as RXD2 (Com2) */
LPC_SC->PCONP |=(1<<24);
        LPC_SC->PCLKSEL1 &= ~(3<<16);
LPC_SC->PCLKSEL1 |= (1<<16); // = CCLK
  break;
  case 3:
LPC_PINCON->PINSEL1 &= (0x0F<<18); 
        LPC_PINCON->PINSEL1 |= (3 << 18);             
/* Pin P0.25 used as TXD3 (Com3) */
        LPC_PINCON->PINSEL1 |= (3 << 20);             
/* Pin P0.26 used as RXD3 (Com3) */
LPC_SC->PCONP |=(1<<25);
        LPC_SC->PCLKSEL1 &= ~(3<<18);
LPC_SC->PCLKSEL1 |= (1<<18); // = CCLK
  break;
}
}

We can now configure the UARTs, but it is good to define a handy function that returns the uart base address to access the registers, so that each time we don't need to do stuff like "if (uart==0) then ... " etc.

LPC_UART_TypeDef * __get_uart_base(int uart)
{
 switch (uart)
 {
   case 0: return LPC_UART0;
   case 2: return LPC_UART2;
   case 3: return LPC_UART3;
 }
 return LPC_UART0;
}

the LPC_UART_TypeDef structure and the  LPC_UARTx constants are provided in the LPC17xx.h CMSIS include file, other vendors (non NXP) will use different names, but should not be difficult to find the matching structures and constants.

void SER_init (int uart,int baudrate) 
{
  LPC_UART_TypeDef *pUart;
  uint32_t fdiv;

  pUart = __get_uart_base(uart);

  SER_On(uart);

    // set to 8 databits, no parity, and 1 stop bit
  pUart->LCR = 0x83; //DLAB on
     // do baudrate calculation
  fdiv = (SystemCoreClock / 16) / baudrate;   
  pUart->DLM = (fdiv >> 8) & 0xFF;
  pUart->DLL = (fdiv) & 0xFF;
  pUart->LCR = 0x3;
  pUart->FCR = 0x6; //reset read and write fifo
}

First we get the base address with the __get_uart_base(uart) function we defined at the beginning.
Then we turn the UART on, select the clock and configure the pins with the SER_On function.

At this point we are ready to configure the baud rate divider and we know that for this one the DLAB must be on, turns out the DLAB is bit 7 ot the Line Control Register (LCR), the 3 in the lower part of the register sets the 8,N,1 part.


Now we need to calculate the fdiv divider.
There is a formula for that one:

fdiv = (SystemCoreClock / 16) / baudrate;   

However, trap for young players -it tricked me initially- this formula assumes that your uart pclk divider is 1 (!!).
If you set pclk, say to /4 , then also the divider will have to be divided by 4.
fdiv is then copied into DLM and DLL (high part and low part), finally the DLAB is turned off again.

Note : SystemCoreClock is a variable provided by the CMSIS core modules, if you decide to set manually in your code the cpu clock, then you should also call  SystemCoreClockUpdate(); right after to ensure that the variable SystemCoreClock is properly updated. 

The final line resets both the TX and RX fifos and disables them via the Fifo Control Register (FCR).

Ok, so, now we are all set to send out some data, we need a putchar function:

int SER_putChar (int uart, int c) {
  LPC_UART_TypeDef *pUart;

  pUart = __get_uart_base(uart);
  while (!(pUart->LSR & SER_THRE));
  return (pUart->THR = c);

}

Writing a char to the uart is as simple as putting it in the Transmit Hold Register (THR)... well, almost that simple, we just need to wait the uart is redy to send out a new character and we do that by polling the Line Status Register (LSR



I defined a few constants to map to the register bits:
#define SER_RDR 0x1
#define SER_OE  0x2
#define SER_PER 0x4
#define SER_FE  0x8
#define SER_BI  0x10
#define SER_THRE 0x20

#define SER_TEMT 0x40

If you check bit 5, you notice that it tells us if the THR is empty and ready to receive another byte.

Using the same register we can discover if we have incoming data

int SER_hasData(int uart)
{
  LPC_UART_TypeDef *pUart;
  pUart = __get_uart_base(uart);
  return pUart->LSR & SER_RDR;

}

and then we can read from RX (register RBR)

int SER_getChar (int uart) 
{
  LPC_UART_TypeDef *pUart;
  pUart = __get_uart_base(uart);
  while (!(pUart->LSR & SER_RDR));
  return (pUart->RBR);

}

and handle output strings as well

void SER_putString (int uart, unsigned char *s) 
{
  while (*s != 0) {
   SER_putChar(uart, *s++);
  }

}


Ok, now a simple test program:

#include "lpc17xx.h"

void delay(uint32_t count)
{
 while (count>0) count--;

}


int main(void)
{

SystemInit(); //CMSIS standard
SER_init(2,115200);
         while (1) 
{
         delay(10000000); // I am running @100MHz
SER_putString (2, ".");
while (SER_hasData(2))
{
SER_putString (2, "Received : ");
SER_putChar(2,SER_getChar(2));
SER_putString (2, "\r\n");
}  
}
return 0;

}

This is really a basic program, but should give you a basic start to play with the uart.
So far we did not enable interrupts, but if you would like to play with my code, you can find it here.