SAM7 and FreeRTOS

alsorichard wrote on Friday, January 11, 2013:

Hello,

I have inherited a project that use AT91SAM7A3, IAR EmbeddedWorkbench v4.7.0 and FreeRTOS v4.02.
Eventually  managed to get 16 ADC Channels sampling every ms, PWM loading new Duty cycle
appx every 300µs and simultaneously receiving 64 bytes in dbgu UART at 115.2kbps
Essentially two tasks are running, one that calculates average ADC results and one that calculate CRC16 on received bytes and returning ACK if CRC was correct.

I noticed that PWM ISR’s and ADC ISR’s were delayed during the task that calculates CRC, even though PWM was set to highest interrupt priority.
Especially PWM is time critical since it produce AC-wave for motors.
These are the Interrupt and Task priority levels we use:

//rk v3.i FreeRTOS Task priority levels
#define STATUS_PRIORITY (tskIDLE_PRIORITY+1)
#define ADCTEST_PRIORITY (tskIDLE_PRIORITY+4)
#define App1int_PRIORITY (tskIDLE_PRIORITY+2)  //this is the task that calculate CRC
#define App1main_PRIORITY (tskIDLE_PRIORITY+1)

//rk v3.i SAM7 Interrupt priority levels
#define ADC_INTERRUPT_LEVEL 4
#define PWM_INTERRUPT_LEVEL 7
#define DBGU_INTERRUPT_LEVEL 3
#define PIT_INTERRUPT_LEVEL 2

Basically I have two questions.
1. Is it at all possible to do nested interrupts with SAM7A3 and FreeRTOS? We’ve tried with no luck so far…
Have read a number of posts on AT91 forum and FreeRTOS forum and concluded that this is not as easy as doing nested interrupts on AVR’s that I’m used to work with.
2. If answer to question #1 is NO, is there some sort of workaround to ensure that PWM (or other time critical code) interrupt the task running (in our case CRC calculation).
Surprised that high interrupt priority IRQ’s such as PWM don’t  take precedence over a task that is running (CRC calc). Can a task be interrupted between RTOS ticks?
PWM run at 3200Hz, at the moment RTOS tick is set to 100(10ms). Have seen code with Tick set to 1000(1ms), this would make PWM latency smaller but also slow down general performance.
Have seen advices to similar questions to move most of code inside ISR to a task but as far as I can see this will not work with 3200Hz PWM and 1000Hz RTOS Tick.
Regards,
Richard

davedoors wrote on Friday, January 11, 2013:

1. Is it at all possible to do nested interrupts with SAM7A3 and FreeRTOS? We’ve tried with no luck so far…
Have read a number of posts on AT91 forum and FreeRTOS forum and concluded that this is not as easy as doing nested interrupts on AVR’s that I’m used to work with.

It is very difficult to nest interrupts on ARM7 and ARM9 devices. There are application notes available on some chip company websites on how to do it, and it is hard, throw in an RTOS and it is very hard.

2. If answer to question #1 is NO, is there some sort of workaround to ensure that PWM (or other time critical code) interrupt the task running (in our case CRC calculation).

Is your PWM code running in an interrupt? That would be the normal way of doing it. An interrupt will always interrupt a task unless the task has interrupts disabled. Does your task disable interrupts either using an FreeRTOS critical section or in any other code?

Surprised that high interrupt priority IRQ’s such as PWM don’t  take precedence over a task that is running (CRC calc). Can a task be interrupted between RTOS ticks?

See answer above.

PWM run at 3200Hz, at the moment RTOS tick is set to 100(10ms). Have seen code with Tick set to 1000(1ms), this would make PWM latency smaller but also slow down general performance.

So the PWM is performed in an interrupt? Or is the PWM interrupt unblocking a task? 3.2KHz is fast for a task if it has to switch in and out each time.

Have seen advices to similar questions to move most of code inside ISR to a task but as far as I can see this will not work with 3200Hz PWM and 1000Hz RTOS Tick.

…so your PWM code is not in the interrupt…now I’m confused.

alsorichard wrote on Friday, January 11, 2013:

Hello davedoors,

Short answer: PWM is entirely handled inside PWM Interrupt routine. Sorry to confuse you.
Since PWM has quite high interrupt frequency my plan was to let any of the two tasks that need some time to do calculations be interrupted by PWM IRQ or ADC IRQ(running at 1000Hz).
I can notice that both PWM and ADC ISR get stretched out in time when the task that collect received serial bytes, do CRC on them, save them if CRC was correct and send back a single byte ACK to confirm that bytes were successfully received runs.
For now, were sending just 64 bytes for testing but our goal is to be able to receive (or send) 255 bytes.
I’ve never used FreeRTOS or SAM7 before, the code I’ve inherited is quite messy and hard to follow.
Lots of commented code and interesting comments such as “this didn’t work” or “crash if I do this”.
Managed to clean up this mess a bit and eventually get PWM ISR short enough to fetch a new duty cycle value from a wavetable and load into PWM_CUPD register at every PWM IRQ.

Seen several posts from Richard Barry suggesting to do as much processing as possible outside ISR, (this is what I always try to do), but that doesn’t really make sence since RTOS tick have to be at least 3200Hz to do processing outside ISR between every PWM IRQ. Just getting a bit desperate and trying to think out of the box to get this going, but unless my thinking is wrong this would just make things worse.

An interrupt will always interrupt a task unless the task has interrupts disabled. Does your task disable interrupts either using an FreeRTOS critical section or in any other code?

.

Not that I’m aware of, but it may well happen anyway… If above statement is correct then the CRC task should be interrupted when PWM IRQ occur.
I’m at my home PC now but I’ll post the code that do CRC processing as soon as I get to my work PC.

Richard

alsorichard wrote on Friday, January 11, 2013:

hello again,

This is the task that do CRC and other stuff with received bytes.

This runs every 50ms:

for( ;; ) Beg
    vTaskDelayUntil(&App1intPreviousWakeTime,App1intTimeInc);
  
    SwTInt();  

and jumps to here:to see if any bytes arrived

void SwTInt(void)
Beg
Byte TestCh;
  if(xdbguGetChar(UniXport,&TestCh,0)) GetRxPacket(TestCh);

and if so jumps to here

/* Get own Address (0xff) and Function, exit with Error if not valid */
void GetRxPacket(Byte Addr)Beg
Byte InData;
  Crc=0xffff; /* Reset CRC register */
  if (Addr==ComAdr) Beg
    CalcCrc(Addr); /* if own Address (0xff) received do CRC */
    if(xdbguGetChar(UniXport,&InData,1))Beg  /* get Function byte */
      if (InData==ComFunc)Beg
        CalcCrc(ComFunc); /* if Function byte valid (0x01) do CRC */
        if(xdbguGetChar(UniXport,&ByteCtr,1))Beg  /* get ByteCtr */
          CalcCrc(ByteCtr); /* do CRC on ByteCtr */
          if(xdbguGetChar(UniXport,&PrmPtr,1))Beg  /* get PrmPtr */
            CalcCrc(PrmPtr); /* do CRC on PrmPtr */
            GetRxData(PrmPtr,ByteCtr); /* get <Data> */

and jumps to here if first 2 bytes were valid

void GetRxData(Byte PrmPtr,Byte ByteCtr)Beg 
Byte RxData,k,i; /* <Data>, RxBuf pointer, ByteCtr */
  k=0; /* reset pointer to RxBuf */
  i=(ByteCtr-1); /* ByteCtr -1 to get it right */
  while (i>0)Beg
  if(xdbguGetChar(UniXport,&RxData,1))Beg
    RxBuf[k++]=RxData; /* save PrmData to RxBuf */
    i--;
    CalcCrc(RxData);  /* do CRC on byte */
    End
  End    
  if(xdbguGetChar(UniXport,&RxData,1)) CrcLsb=RxData;
  if(xdbguGetChar(UniXport,&RxData,1)) CrcMsb=RxData;
End 

and this is the part that do CRC calculation on every byte

void CrcShift(void)Beg
Boolean Carry;
  while(ctr-->0)Beg  /* do 8 times */
    Carry=Crc AndB 0x0001; /* catch LSB prior to shift */
    Crc=Crc Shr 1; /* right shift CRC */
    if(Carry)Beg
      Crc=Crc XorB 0xa001; /* If LSB was 1 */
    End
    CrcShift(); /* do another round */
  End
End  
  
void CalcCrc(Byte CrcData)Beg
  ctr=8; /* set up ctr to process 8 bits */
  
  /* Do initial Xor btw lsb Data and CRC (0xFFFF) */
  Crc=(Word)((Byte)(Crc AndB 0xFF)XorB CrcData)OrB (Crc AndB 0xFF00);
  CrcShift(); /* continue CRC calculation */
End

Looking at the code I’m a bit suspicious to these lines

 if(xdbguGetChar(UniXport,&InData,1))

Last parameter set to 1, will this block from catching ISR’s?
Changing that parameter to 0 anywhere but in the first if statement have made it impossible to collect all bytes correctly so far but I guess this could be the cause for stretched PWM ISR’s.
If so, is there an alternative way to get to all the bytes that are placed in the queue?

Richard

rtel wrote on Saturday, January 12, 2013:

Operating at those speeds you really have to be doing the PWM and the ADC in the interrupt itself.

With regards to the serial receive functions - the SAM7 USART has a Peripheral DMA Controllers that can handle the receiving for you with very little overhead on the CPU. I suggest in a high CPU load application like your you look to use that.  You can find an application note and examples for the SAM3 (the SAM7 replacement).

I would suggest finding out why calculating the CRC is causing disruption to the interrupts.  Only code that either disables the interrupts or places the CPU into a halt mode should do that.  One of the things that can place the CPU into halt mode is printing out characters through a debug port (semi-hosting) so personally I would scrutinise your character input and output functions first.

Regards.

alsorichard wrote on Saturday, January 12, 2013:

hello richardbarry,

After some early morning testing I’ve concluded that the disruptions to ISR’s are caused by the routine that collect characters from the queue.

void GetRxData(Byte PrmPtr,Byte ByteCtr)Beg Byte RxData,k,i; /* <Data>, RxBuf pointer, ByteCtr */ k=0; /* reset pointer to RxBuf */ i=(ByteCtr-1); /* ByteCtr -1 to get it right */ while (i>0)Beg if(xdbguGetChar(UniXport,&RxData,1))Beg // if this also set to 0 cpu crash RxBuf[k++]=RxData; /* save PrmData to RxBuf */ i--; CalcCrc(RxData); /* do CRC on byte */ End End if(xdbguGetChar(UniXport,&RxData,0) CrcLsb=RxData; if(xdbguGetChar(UniXport,&RxData,0)) CrcMsb=RxData; End 

the function that collect chars from queue look like this(not written by me)

signed portBASE_TYPE xdbguGetChar( xdbguPortHandle pxPort, unsigned portCHAR *pcRxedChar, portTickType xBlockTime ) 
Beg
/* The port handle is not required as this driver only supports one port. */
  ( void ) pxPort;
  if (dbgu_OK==pdFALSE) return(pdFALSE);
    if ((((xsneakQueueHandle) xdbguRxedChars)->uxMessagesWaiting) < DBGU_RX_QUEUE_XON)
    Beg
      DBGU_BASE->US_CR = DBGU_BASE->US_CR | AT91C_US_RXEN; //pm turned on again when queue space available
    End
/* Get the next character from the buffer.  Return false if no characters
are available, or arrive before xBlockTime expires. */ //pm more like ...AFTER..?
    if( xQueueReceive( xdbguRxedChars, pcRxedChar, xBlockTime ) )
    Beg
      DBGU_BASE->US_CR = DBGU_BASE->US_CR | AT91C_US_RXEN; //pm turned on again when queue space available, but perhaps not at once like here..?
      return pdTRUE;
    End
End

I’ve noted that block time can be 0 everywhere except in the while loop. When that is 0 CPU crash.

Regarding using dbgu port as UART. For now I’m stuck with a PCB that use UART0 (shared by dbgu)).
Tried to instead send rather large packet of chars from SAM7 including doing same CRC routine for every char and then disruptions disappear.
I can also see on the scope that disruptions does not happen while actually receiving chars, but later when reading them from queue in the task described in earlier posts.

I’m not really a C person (been programming AVR’s in assembler for 15 years) so I’m not sure what to change in code to get rid of this problem.
But now I’ve zoomed in on what’s causing the problem and that’s a large step toward finding a solution.

Richard

alsorichard wrote on Saturday, January 12, 2013:

oops,

Tried to add a comment to code didn’t work to well.
This is how it should look

void GetRxData(Byte PrmPtr,Byte ByteCtr)Beg 
Byte RxData,k,i; /* <Data>, RxBuf pointer, ByteCtr */
  k=0; /* reset pointer to RxBuf */
  i=(ByteCtr-1); /* ByteCtr -1 to get it right */
  while (i>0)Beg
  if(xdbguGetChar(UniXport,&RxData,1))Beg//SETTING THIS TO 0 CRASH CPU
    RxBuf[k++]=RxData; /* save PrmData to RxBuf */
    i--;
    CalcCrc(RxData);  /* do CRC on byte */
    End
  End    
  if(xdbguGetChar(UniXport,&RxData,1)) CrcLsb=RxData;
  if(xdbguGetChar(UniXport,&RxData,1)) CrcMsb=RxData;
End 

Richard

alsorichard wrote on Sunday, January 13, 2013:

Finally found what caused the ISR glitches.
As usual the person behind the keyboard….
In the real app bytes received by SAM7 will be saved to a buffer where one of the bytes in RX packet point to.
While testing we just sent dummy bytes and the size was too large for the buffer, which caused the routine to spend 2ms  in la-la land. Commenting this line makes it all steady as a rock.

 RxBuf[k++]=RxData; /* save PrmData to RxBuf */ 

[

Thanks to those that tried to help, it got me on the right track.