|
|
View previous topic :: View next topic |
Author |
Message |
chaertl1
Joined: 17 Aug 2009 Posts: 9
|
Software RS232 that is not disturbed by Interrupt |
Posted: Fri Aug 21, 2009 1:30 am |
|
|
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
|
|
Posted: Fri Aug 21, 2009 1:38 am |
|
|
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
|
|
Posted: Fri Aug 21, 2009 2:05 am |
|
|
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
|
|
Posted: Fri Aug 21, 2009 3:00 am |
|
|
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
|
|
Posted: Fri Aug 21, 2009 7:58 am |
|
|
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
|
|
Posted: Mon Aug 24, 2009 1:36 am |
|
|
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
|
|
Posted: Mon Aug 24, 2009 4:20 am |
|
|
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
|
|
Posted: Mon Aug 24, 2009 9:14 am |
|
|
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
|
|
Posted: Mon Aug 24, 2009 9:27 am |
|
|
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
|
|
Posted: Mon Aug 24, 2009 12:10 pm |
|
|
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
|
|
Posted: Mon Aug 24, 2009 12:45 pm |
|
|
I've tested it with the real hardware and it works very good. |
|
|
chaertl1
Joined: 17 Aug 2009 Posts: 9
|
|
Posted: Mon Aug 24, 2009 1:27 pm |
|
|
Got it working with 2400baud using fast io |
|
|
John P
Joined: 17 Sep 2003 Posts: 331
|
|
Posted: Mon Aug 24, 2009 2:48 pm |
|
|
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. |
|
|
|
|
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
|