CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

Software RS232 that is not disturbed by Interrupt

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
chaertl1



Joined: 17 Aug 2009
Posts: 9

View user's profile Send private message

Software RS232 that is not disturbed by Interrupt
PostPosted: Fri Aug 21, 2009 1:30 am     Reply with quote

Hi,

Does anyone have an example for using software RS232 where an interrupt does not affect the getchar() function.

CLOCK = 8Mhz
My TIMER0 interrupt fires at 6400 Hz for pwm generation. every (156us)
My desired Baudrate is 2400bps (bit time 416us)

This works fine but only with the DISABLE_INTS statement in the #use RS232 command.

The disadvantage of DISABLE_INTS statement is that while receiving a char with getchar() the pwm generation done in the interrupt routine is not done anymore what results in a flickering output.

When I enable the interrupts while receiving and the timer0 interrupt fires
the getchar() routine is disturbed in timing.

Is there a possibility to tell getchar() how much time was spent in the interrupt to correct the bit time?
Wayne_



Joined: 10 Oct 2007
Posts: 681

View user's profile Send private message

PostPosted: Fri Aug 21, 2009 1:38 am     Reply with quote

You must be using software RS232, use a hardware UART and you shouldn't get the problem as getc just reads the whole char from the uarts buffer.

Post fuses and #use statements. Preferably post your isrs as well.
chaertl1



Joined: 17 Aug 2009
Posts: 9

View user's profile Send private message

PostPosted: Fri Aug 21, 2009 2:05 am     Reply with quote

I'm using a 12F629.

The 16F877 is just my debug device.
and the desired clock for the 12f629 is the internal 4mhz.

No HW UART.

My code contains some german comments:
The "accurateinterrupt.h" is a package written by me that calculates
the right values for timer preinit and timer divider.
Code:

#include <16F877.h>
#case
#use delay(CLOCK=8000000)
#use rs232(BAUD=2400,RCV=PIN_B3,DISABLE_INTS,PARITY=N,BITS=8,STOP=1)  // bit_time = 416us
#use fixed_io(b_outputs=PIN_B0,PIN_B1,PIN_B2)
#fuses HS,NOWDT,NOPROTECT,NOPUT,NOBROWNOUT


/* --------------------- INTERRUPT HANDLING -------------------------------------- */
#define CLK              8000000   // External Clock 4Mhz
#define INT_FREQU        6400      // Interrupt frequency 6400/sec
#include "accurateinterrupt.h"
/* ------------------------------------------------------------------------------- */

#define PWM_RESOLUTION 64

#define TELEGRAM_START '#'
#define TELEGRAM_END   '+' 
#define TELEGRAM_LEN   5 // #ARGBF*

int portVal;
int datArr[2][TELEGRAM_LEN];
int1 rdPtr=1;
int1 wrPtr=0;
int pwmPos = 0;
int data[3]={0,0,0};
       
#int_rtcc
isr() {   
  set_timer0(RTCC_PRESET);  //from accurateInterrupt.h
 
  portVal=0;
  if(pwmPos < data[0]) bit_set(portVal,0);
  if(pwmPos < data[1]) bit_set(portVal,1);
  if(pwmPos < data[2]) bit_set(portVal,2);           
   output_b(portVal);
   
   if (pwmPos<PWM_RESOLUTION-1){
     pwmPos++;
   } else {
      pwmPos=0;
   }
   
}

typedef enum {
   wsInit     = 0,
   wsCalc        ,  //
   wsRcv         ,  //Startzeichen empfangen '#'
   wsRcvData     ,  //Daten empfangen        'RGBT'
   wsRcvEnd         //Ende Zeichen           'ASCII(13)'
} workState_t;






void main(){
 
  workState_t wst = wsInit;           
  //char        telegram[TELEGRAM_LEN];  //Puffer für vom Bus gelesene Zeichen
  int         rcvPos  = 0;                   //Schreibzeiger für Telegramm           
  int         rcv = 0;
  int1        start = FALSE;
  int         *ptr;
 
  while(TRUE){
    switch(wst){
    case wsInit:
      setup_timer_0(RTCC_INTERNAL|RTCC_DEVIDER); //from accurateInterrupt.h 
      set_timer0   (RTCC_PRESET);                //from accurateInterrupt.h 
      enable_interrupts(GLOBAL|INT_RTCC);
      wst = wsRcv;
     
      output_b(0);
       break;   
    case wsRcv:
       
       if(kbhit()){
               
         rcv=getch();          // Ein Zeichen empfangen
         
         
         switch (rcv){
         case TELEGRAM_START:           // Ist das empfangene Zeichen ein START zeichen          
           start=TRUE;
           rcvPos=0;                    // Schreibzeiger auf erste Datenposition setzen
            break;   
         case TELEGRAM_END:            
            if(start &&
                rcvPos==TELEGRAM_LEN){  // Telegramm OK Start- und Endezeichen an richtiger Position vorhanden
           ptr = datArr[wrPtr];
           data[0] = ptr[1];
           data[1] = ptr[2];
           data[2] = ptr[3];
           
           rdPtr=!rdPtr;
           wrPtr=!wrPtr;
           //rdPtr==1 ? output_high(PIN_B1):output_low(PIN_B1);
           
            } else {
               rcvPos=0;
              start=FALSE;
            }
           break;
         
         default:
           if (start && rcvPos < TELEGRAM_LEN){              
              datArr[wrPtr][rcvPos]=rcv;
              rcvPos++;
           } else {
              rcvPos=0;
              start=FALSE;
           }
         }
       }
    case wsCalc:
       break;    
       
    }          
  }
   
}
Ttelmah
Guest







PostPosted: Fri Aug 21, 2009 3:00 am     Reply with quote

What you want to do, can be done, but only by you writing a lot more complex code, and not using the CCS functions.
What you would need to do, is make your timer interrupt synchronous to a sub division of the bit time of the serial. Ideally about 1/4 bit time. So (say), 104uSec.
Then your timer interrupt, has to implement your required PWM code, and also a receive state machine for the serial. The first state checks simply for the incoming line being low. If it is, advance to the next state. Otherwise stay in the first state. The second state (1/4 bit time later), re-checks that the serial is low. If so, we consider ourself to now be in the 'start' bit, and advance to the next state. If not, return to the first state.
Then the code in each timer interrupt, performs one function, and advances to the next state. Either just advancing a time slot, or receiving a bit.

Something like:
Code:

int8 rx_char;
int1 have_data=FALSE;

#int_timerX //For whatever timer you are using
void timer_int(void) {
   static int8 state=0;
   static int8 incoming=0;
   static int8 bitno=0;

   //Other timer code here

   switch (state) {
   case 0:
       if (input(RX_INPUT_LINE)==0) state=1;
       break;
   case 1:
   case 2:
       if (input(RX_INPUT_LINE)==0) state++;
       else state=0;
       break;
   case 3: //since bit time is four interrupts, for three out of four
   case 4: //loops, simply advance one state
   case 5: //This keeps work in the interrupt small
   case 7:
   case 8:
   case 9:
   case 11:
   case 12:
   case 13:
   case 15:
   case 16:
   case 17:
   case 19:
   case 20:
   case 21:
   case 23:
   case 24:
   case 25:
   case 27:
   case 28:
   case 29:
   case 31:
   case 32:
   case 33:
   case 35: //These last three, are delaying to the middle of the stop
   case 36: //bit
   case 37:
       state++;
       break;
   //Now sample at four interrupt intervals from the 'double checked'
   //start bit edge
   case 6:
   case 10:
   case 14:
   case 18:
   case 22:
   case 26:
   case 30:
   case 34:
       if (input(RX_INPUT_LINE)==1) bit_set(incoming,bitno);
       bitno++;
       state++;
       break;
   case 38:
       //Here should be in the stop bit
       rx_char=incoming;
       bitno=state=incoming=0;
       have_data=TRUE;
       break;
   }
}

If the interrupt is at 104uSec, this will receive 2400bps serial, arriving on 'RX_INPUT_LINE", and set the bit 'have_data', when a character is received. I your main, you can just test for 'have_data' (and clear it once you have seen it), and read the character.

You need a sample interval that is a reasonable 'sub division' of the total bit time. In this case, if the code arrived in the interrupt exactly at the moment the first falling edge was seen, it would try twice more, then sample the data at 416uSec intervals from this point. So sample at 208uSec for the start bit centre. If the edge occured immediately after the test in the interrupt, sampling would be up to 1/4 bit time later.

The 'longest' interval you can use, is 1/3rd bit time, sampling just twice for the start bit, and reading the data every third interrupt (hopefully you can see how the switch would need to change for this). Beyond this, you start to run the risk of missing a bit.

As a 'caveat', do not add a 'default' entry to the switch. A written, the compiler will implement a jump table for the state machine. Making it take the same time for each state. If a default is added,the compiler switches mode, and uses effectively 'if' tests for each state, increasing the time taken in the interrupt.

Though the table is long, the work done in each state is tiny, and in fact this is an efficent way to receive serial on chips that don't have a UART. You can use exactly the same appoach to send data as well, with another similar state machine.

Best Wishes
John P



Joined: 17 Sep 2003
Posts: 331

View user's profile Send private message

PostPosted: Fri Aug 21, 2009 7:58 am     Reply with quote

I think Ttelmah has suggested the only method which has any chance of success. You might additionally need to make the interrupt into the #INT_GLOBAL style, which would allow much faster entry and exit.

One quibble I have is with this line:
Code:

if (input(RX_INPUT_LINE)==1) bit_set(incoming,bitno);


That works out to a complicated bit of assembly language involving a loop, and it forces the compiler (my version, at least) to use one of its "scratchpad" registers, 0x77. If you use INT_GLOBAL, you need to make sure the compiler doesn't do this, as then you probably have to save/restore it. It wouldn't be necessary if the program were written to store the correct bit without calculation, which could easily be done with individual lines in the
Code:

   case 6:
   case 10:
   case 14:
   case 18:
   case 22:
   case 26:
   case 30:
   case 34:

part of the program. Alternatively, you could left-shift the 'incoming' register and then do the bit_test and set bit 0 if needed. Actually that sounds a lot better--reducing program size is always good!

Edited a few minutes later to say oops, right shift not left shift, and set bit 7 not bit 0! Serial ports send lowest bit first, so it gets shifted the farthest.

Damn, it's hard to get this stuff right.
chaertl1



Joined: 17 Aug 2009
Posts: 9

View user's profile Send private message

PostPosted: Mon Aug 24, 2009 1:36 am     Reply with quote

Did anybody try this on a 12F629.

i tried to do this with the #int_global bt then i'm out of rom
Ttelmah
Guest







PostPosted: Mon Aug 24, 2009 4:20 am     Reply with quote

Ah. it is the bit set that uses the loop. Re-code as:
Code:

#define RX_INPUT_LINE (PIN_A0)
#byte TIMER0=01

int8 rx_char;
int1 have_data=FALSE;
#use fast_io(a)

#int_timer0 //For whatever timer you are using
void timer_int(void) {
   static int8 state=0;
   static int8 incoming=0;
   static int8 bitno=1;
   //Timer is ticking at 1uSec/instruction
   TIMER0+=152;

   //Other timer code here

   switch (state) {
   case 0:
       if (input(RX_INPUT_LINE)==0) state=1;
       break;
   case 1:
   case 2:
       if (input(RX_INPUT_LINE)==0) state++;
       else state=0;
       break;
   case 3: //since bit time is four interrupts, for three out of four
   case 4: //loops, simply advance one state
   case 5: //This keeps work in the interrupt small
   case 7:
   case 8:
   case 9:
   case 11:
   case 12:
   case 13:
   case 15:
   case 16:
   case 17:
   case 19:
   case 20:
   case 21:
   case 23:
   case 24:
   case 25:
   case 27:
   case 28:
   case 29:
   case 31:
   case 32:
   case 33:
   case 35: //These last three, are delaying to the middle of the stop
   case 36: //bit
   case 37:
       state++;
       break;
   //Now sample at four interrupt intervals from the 'double checked'
   //start bit edge
   case 6:
   case 10:
   case 14:
   case 18:
   case 22:
   case 26:
   case 30:
   case 34:
       if (input(RX_INPUT_LINE)==1) incoming|=bitno;
       bitno*=2;
       state++;
       break;
   case 38:
       //Here should be in the stop bit
       rx_char=incoming;
       bitno=1;
       state=incoming=0;
       have_data=TRUE;
       break;
   }
}

Switched to using fast_io, and a quick test suggests it does work.
Using 'bitno', as a mask, rather than a number.

Using INT_GLOBAL, will be worthwhile if the interrupt has to do much else...

You also have to remember that doing it with #int_global, you are going to have to add register saves for every register used inside the routine.
Size wise, I'd have expected problems on something like a 508, but would have expected it to work OK, (in fact quite easily), of the 629.
Have just put the code together, with basic initialisation, using the normal interrupt handler, and it uses less than 20% of the ROM, on a 629.

Best Wishes
John P



Joined: 17 Sep 2003
Posts: 331

View user's profile Send private message

PostPosted: Mon Aug 24, 2009 9:14 am     Reply with quote

I think that'll work, but what I had in mind was:

Code:

   case 6:
   case 10:
   case 14:
   case 18:
   case 22:
   case 26:
   case 30:
   case 34:
       incoming >>= 1;
       if (input(RX_INPUT_LINE)==1)
          bit_set(incoming, 7);
       state++;
       break;
   case 38:
       //Here should be in the stop bit
       rx_char=incoming;
       state=incoming=0;
       have_data=TRUE;
       break;
Ttelmah
Guest







PostPosted: Mon Aug 24, 2009 9:27 am     Reply with quote

Saves one byte of RAM.

Seriously to the poster running out of ROM, when using INT_GLOBAL, suggests you are either doing a lot more than this, or there is a syntax error somewhere resulting in unexpected ROM use.

I can confirm it does work. Simulated a received byte, in MPLAB, and it correctly picks it up, and gets the right value.

Best Wishes
chaertl1



Joined: 17 Aug 2009
Posts: 9

View user's profile Send private message

PostPosted: Mon Aug 24, 2009 12:10 pm     Reply with quote

OK,

thanks,

I got it working with 1200 baud in my simulator.

At 2400 baud I had a problem with the length of the interrupt routine.
I missed 3 us to get in sync.

Maybe I can optimize the code using fast io.

I'm aware of using the int_global because of saving all the registers.
Code:

#include <16F877.h>
#case
//#use delay(CLOCK=4000000)
//#use rs232(BAUD=2400,RCV=PIN_A0,DISABLE_INTS,PARITY=N,BITS=8,STOP=1)  // bit_time = 416us
#use fixed_io(a_outputs=PIN_A2,PIN_A4,PIN_A5)
//#use fast_io(a)

//#fuses INTRC_IO,NOWDT,NOPROTECT,NOPUT,NOBROWNOUT
#fuses HS,NOWDT,NOPROTECT,NOPUT,NOBROWNOUT

/* --------------------- INTERRUPT HANDLING -------------------------------------- */
//#define INTER_CLOCK_CORR 35
#define CLK              4000000   // External Clock 4Mhz
#define INT_FREQU        4800      // Interrupt frequency 6400/sec
#include "accurateinterrupt.h"
/* ------------------------------------------------------------------------------- */

#define RX_INPUT_LINE PIN_A0
#define PWM_RESOLUTION 64

#define TELEGRAM_START '#'
#define TELEGRAM_END   '+' 
#define TELEGRAM_LEN   5 // #ARGBF*

int  portVal;
int  datArr[2][TELEGRAM_LEN];
int1 rdPtr     = 1;
int1 wrPtr     = 0;
int1 have_data = 0;
int  pwmPos    = 0;
int  data[3]   = {0,0,0};


int  state    = 0;
int  incoming = 0;
int  rx_char  = 0; 
int  bitno    = 1;
          
#int_rtcc
isr() {   
  set_timer0(RTCC_PRESET);  //from accurateInterrupt.h
 
  // PWM HANDLING START ---------------------------------------------------------
 
  portVal=0;
  if(pwmPos < data[2]) bit_set(portVal,4); //BLAU
  if(pwmPos < data[0]) bit_set(portVal,5); //Rot   2
  if(pwmPos < data[1]) bit_set(portVal,2); //GRÜN  4         
   output_a(portVal);
   
   if (pwmPos<PWM_RESOLUTION-1){
     pwmPos++;
   } else {
      pwmPos=0;
   }
   
   // PWM HANDLING END ---------------------------------------------------------
   

   //Other timer code here

   switch (state) {
   case 0:
       if (input(RX_INPUT_LINE)==0) state=1;
       break;
   case 1:
   case 2:
       if (input(RX_INPUT_LINE)==0) state++;
       else state=0;
       break;
   case 3: //since bit time is four interrupts, for three out of four
   case 4: //loops, simply advance one state
   case 5: //This keeps work in the interrupt small
   case 7:
   case 8:
   case 9:
   case 11:
   case 12:
   case 13:
   case 15:
   case 16:
   case 17:
   case 19:
   case 20:
   case 21:
   case 23:
   case 24:
   case 25:
   case 27:
   case 28:
   case 29:
   case 31:
   case 32:
   case 33:
   case 35: //These last three, are delaying to the middle of the stop
   case 36: //bit
   case 37:
       state++;
       break;
   //Now sample at four interrupt intervals from the 'double checked'
   //start bit edge
   case 6:
   case 10:
   case 14:
   case 18:
   case 22:
   case 26:
   case 30:
   case 34:
       if (input(RX_INPUT_LINE)==1) incoming|=bitno;
       bitno*=2;
       state++;
       break;
   case 38:
       rx_char=incoming;
       bitno=1;
       state=incoming=0;
       have_data=TRUE;
       break;
   }
   
}

typedef enum {
   wsInit     = 0,
   wsCalc        ,  //
   wsRcv         ,  //Startzeichen empfangen '#'
   wsRcvData     ,  //Daten empfangen        'RGBT'
   wsRcvEnd         //Ende Zeichen           'ASCII(13)'
} workState_t;






void main(){
 
  workState_t wst     = wsInit;              // Current workstate
  int         rcvPos  = 0;                   // Write pointer of telegram
  int         rcv     = 0;                   // Buffer for received char
  int1        start   = FALSE;               // Start char received
  int         *ptr;                          // Pointer to save rom using multi dimensional arrays
 
  while(TRUE){
    switch(wst){
    case wsInit:
      setup_timer_0(RTCC_INTERNAL|RTCC_DEVIDER); //from accurateInterrupt.h 
      set_timer0   (RTCC_PRESET);                //from accurateInterrupt.h 
      enable_interrupts(GLOBAL|INT_RTCC);
      wst = wsRcv;
     
      output_a(0);
       break;   
    case wsRcv:
       
       if(have_data){//if(kbhit()){           // is there anything in the receive buffer
               
         rcv = rx_char;//rcv=getch();         // copy receive buffer
         have_data = 0;
         switch (rcv){               
         case TELEGRAM_START:                 // is the received char a telegram start
           start=TRUE;
           rcvPos=0;                          // set write pointer to first position
            break;   
         case TELEGRAM_END:            
            if(start &&
                rcvPos==TELEGRAM_LEN){          // is telegram complete (start char received and end char at right position)
           ptr = datArr[wrPtr];              // use pointer to save rom (multi dimensional array)
           data[0] = ptr[1];                 // copy received data to work environment
           data[1] = ptr[2];
           data[2] = ptr[3];
           
           rdPtr=!rdPtr;
           wrPtr=!wrPtr;
         
           
            } else {
               rcvPos=0;
              start=FALSE;
            }
           break;
         
         default:
           if (start && rcvPos < TELEGRAM_LEN){              
              datArr[wrPtr][rcvPos]=rcv;
              rcvPos++;
           } else {
              rcvPos=0;
              start=FALSE;
           }
         }
       }
       
    case wsCalc:
       break;    
       
    }          
  }
   
}




Code:

#ifndef __ACCURATEINTERRUPT_H__
#define __ACCURATEINTERRUPT_H__

// Clock frequency
#ifndef CLK 
  #error "Please define CLK with your clock frequency in HZ e.g.(#define CLK 4000000 //4Mhz Clock) "
#endif
// Interrupt frequency
#ifndef INT_FREQU                   
  #error "Please define INT_FREQU with your desired interrupt frequency in HZ e.g.(#define INT_FREQU 6400 //6400 ints/sec) "
#endif
#ifndef INTER_CLOCK_CORR
  #define INTER_CLOCK_CORR 35     // Number of clocks the compiler generated code needs
#endif                                   // from the timer overflow to the set_timer_X() instruction
                                   // in your interrupt service routine
                                   

#define CLK_EFF           ((float32)CLK/4)               // Effective Clock
#define CLK_PERINTERRUPT  ((float32)(CLK_EFF/INT_FREQU)) // Number of clocks between interrupt
#define TMR_DEVIDER       ( (int)(CLK_PERINTERRUPT/256)) // Calculated timer prescaler

#if (TMR_DEVIDER < 1)
  #define RTCC_DEVIDER  RTCC_DIV_1  // 8 
  #define DV 0
#elif (TMR_DEVIDER < 2)
  #define RTCC_DEVIDER  RTCC_DIV_2  // 0 
  #define DV 1
#elif (TMR_DEVIDER < 4)                 
  #define RTCC_DEVIDER  RTCC_DIV_4  // 1   
  #define DV 2
#elif (TMR_DEVIDER < 8)
  #define RTCC_DEVIDER  RTCC_DIV_8  // 2
  #define DV 3
#elif (TMR_DEVIDER < 16)
  #define RTCC_DEVIDER  RTCC_DIV_16 // 3
  #define DV 4
#elif (TMR_DEVIDER < 32)
  #define RTCC_DEVIDER  RTCC_DIV_32 // 4
  #define DV 5
#elif (TMR_DEVIDER < 64)
  #define RTCC_DEVIDER  RTCC_DIV_64 // 4
  #define DV 6
#elif (TMR_DEVIDER < 128)
  #define RTCC_DEVIDER  RTCC_DIV_128 // 4
  #define DV 7
#elif (TMR_DEVIDER < 256)
  #define RTCC_DEVIDER  RTCC_DIV_256 // 4
  #define DV 8
#else
  #error
#endif

#define REST (((int)(CLK_PERINTERRUPT/(1<<DV))) % 256)
#define RTCC_PRESET (256 - (REST - (INTER_CLOCK_CORR/(1<<DV))))

#endif //__ACCURATEINTERRUPT_H__
chaertl1



Joined: 17 Aug 2009
Posts: 9

View user's profile Send private message

PostPosted: Mon Aug 24, 2009 12:45 pm     Reply with quote

I've tested it with the real hardware and it works very good.
chaertl1



Joined: 17 Aug 2009
Posts: 9

View user's profile Send private message

PostPosted: Mon Aug 24, 2009 1:27 pm     Reply with quote

Got it working with 2400baud using fast io
John P



Joined: 17 Sep 2003
Posts: 331

View user's profile Send private message

PostPosted: Mon Aug 24, 2009 2:48 pm     Reply with quote

My code saves an instruction or two. If "time is of the essence" then it might count for something. The global interrupt is the really big time saver, though--if you can be sure that it won't get you into trouble! I always end up looking at the assembly code to see which registers the compiler uses in the interrupt, to be sure that I've saved them. But if it's working without drastic measures, great.
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group