|
|
View previous topic :: View next topic |
Author |
Message |
levdev
Joined: 19 May 2010 Posts: 36 Location: UK
|
RS232 Receive Problem on UART |
Posted: Mon Jun 27, 2011 5:31 am |
|
|
I am having a problem with receiving a serial input. It works 99.9% of the time, but around 1 in a thousand it misreads two bytes as one. The full program has a fairly complex interface protocol and command set, but I have simplified it down to the bare minimum to demonstrate the problem. Complier version is PCWH 4.119.
Code: |
#include <18F26K22.h>
#device adc=10
#FUSES NOWDT //No Watch Dog Timer
#FUSES WDT128 //Watch Dog Timer uses 1:128 Postscale
#FUSES INTRC_IO //Internal RC Osc, no CLKOUT
#FUSES PUT //Power Up Timer
#FUSES NOBROWNOUT //No brownout reset
#FUSES NOPBADEN //PORTB pins are configured as digital I/O on RESET
#FUSES NOMCLR //Master Clear pin used for I/O
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOXINST //Extended set extension and Indexed Addressing mode disabled (Legacy mode)
#FUSES IESO //Internal External Switch Over mode enabled
#FUSES STVREN //Stack full/underflow will cause reset
#FUSES NOWRT //Program memory not write protected
#FUSES NOWRTD //Data EEPROM not write protected
#FUSES FCMEN //Fail-safe clock monitor enabled
#FUSES PROTECT //Code protected from reads
#FUSES CPB
#FUSES EBTR
#FUSES NODEBUG
#use delay(clock=16Mhz)
#use rs232(uart1,baud=38400,bits=8,parity=N,stop=1,errors)
#define F_OFF PIN_B4
#define LED PIN_A2
static int IN_BUFF[15]; //Stores input string from RS232 input
static int IN_COUNT = 0; //stores count of how many bytes received
void Initialise_uC()
{
setup_oscillator(OSC_16MHZ|OSC_INTRC|OSC_PLL_OFF);
delay_ms(100);
setup_wdt(WDT_OFF);
setup_timer_0(T0_OFF);
setup_timer_1(T1_DISABLED);
setup_timer_2(T2_DISABLED,0,1);
setup_timer_3(T3_DISABLED);
setup_ccp1(CCP_OFF);
setup_comparator(NC_NC_NC_NC);
setup_adc(ADC_OFF);
output_high(F_OFF);
enable_interrupts(INT_RDA);
enable_interrupts(GLOBAL);
}
//Clears the RS232 input buffer and resets the buffer counter
void Clear_In_Buff()
{
IN_COUNT = 0;
}
void Out_Buff_Error()
{
int i;
putc(0xFF);
for (i=0; i<IN_COUNT; i++) putc(IN_BUFF[i]);
putc(0xFF);
}
void Out_Buff_Good()
{
int i;
putc(0xEE);
for (i=0; i<IN_COUNT; i++) putc(IN_BUFF[i]);
putc(0xEE);
}
void Test_In_Buff()
{
switch (IN_COUNT)
{
case 0:
case 1:
break;
case 2:
if (IN_BUFF[0] != 12)
{
output_high(LED);
Out_Buff_Error();
Clear_In_Buff();
}
break;
case 3:
if (IN_BUFF[0] == 12)
{
Out_Buff_Good();
Clear_In_Buff();
}
break;
default:
Out_Buff_Error();
Clear_In_Buff();
break;
}
}
//interrupt routine for RS232 incoming data
#INT_RDA
void serial_isr()
{
IN_BUFF[IN_COUNT] = getc();
IN_COUNT = IN_COUNT + 1;
}
void main()
{
Initialise_uC();
while(1)
{
delay_ms(15);
Test_In_Buff();
}
}
|
This code receives a command string and then sends it back out to verify what was received. I have a comms program (Docklight) that sends a 3 byte command every 75ms, the command (in hex) is '0C 01 83'. A small section of the response where the error occurrs is shown below :
Code: |
27/06/2011 12:15:12.568 [TX] - 0C 01 83
27/06/2011 12:15:12.584 [RX] - 0C 01 83
27/06/2011 12:15:12.646 [TX] - 0C 01 83
27/06/2011 12:15:12.662 [RX] - 0C 01 83
27/06/2011 12:15:12.724 [TX] - 0C 01 83
27/06/2011 12:15:12.740 [RX] - FF 28 83 FF
27/06/2011 12:15:12.803 [TX] - 0C 01 83
27/06/2011 12:15:12.818 [RX] - 0C 01 83
|
You can see that instead of reading the first two bytes as 0x0C and 0x01 is has read them as a single byte 0x28.
To add further to the story, this problem only occurrs at 38400 and 19200 BAUD, at 9600, 57600 and 115200 it doesn't happen. I analysed the actual BAUD rates based on the internal oscillator and the BRG divisor and found the following:
Code: |
Clock 16000000 16000000 16000000 16000000 16000000
Baudrate 115200 57600 38400 19200 9600
Output baud 114286 57971 38462 19231 9592
Error -0.79% 0.64% 0.16% 0.16% -0.08%
|
Based on this, the BAUD rates look ok, certainly the fact that 115.2kbps has a more negative theoretical error, and 57.6kbps has a more positve error and these work has lead me to beleive this is probably not the issue. Anyway, I've run out of ideas, has anyone got any other suggestions as to what could be causing this. The problem is not just linked to my PC either because our customer has seen the same issue when the product is installed with their PLC. |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9229 Location: Greensville,Ontario
|
|
Posted: Mon Jun 27, 2011 7:33 am |
|
|
quick comments
Is the serial data going through a USB<-> RS232 interface ?
Any proof it's -not- a 'Windows /PC' related problem ?
Either of these can cause a 'random glitch'. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19520
|
|
Posted: Mon Jun 27, 2011 7:35 am |
|
|
I disagree with what you think is happening.....
Thing to remember is that there is a start bit (a '0') in front of each byte sent, and a stop bit (a '1') after each byte. The UART does not start clocking in data, till a start bit is seen.
You are getting an extra start bit. Almost certainly from electrical noise, being seen as a '0'. This then starts the clocking in process, and so the serial line (which is sitting 'high' between the bytes), is clocked in, giving you the 'FF'.
Your problem is hardware. Something is generating a low 'spike' on the serial line into the PIC. Now, remember the serial itself is inverted, so a 'high' spike on the RS232. The spike interval, is short enough, so that at lower baud rates it is not seen as a complete bit (remember the UART performs double sampling on later chips like this).
Then the bit pattern you are seeing received, is:
2 8 3 8
00001010010011000011
The bit pattern being sent, is:
0001100001010000001
Now the '1's at the end of the byte (stop bits), are not sampled as part of the byte, so the receive, is missing the first block of 3 zeros (start, and two zero bits), and recording the next 0 as it's start bit. You will get a framing error, since when the second byte completes, the next bit is not a '1', because the receive is out of sync.
Now, missing the start bit, could be a sign that your hardware is not correctly handing the slew rate for the higher baud rates (you are aware of the cable length recommendations for these higher rates - at 56Kbaud for example, the maximum recommended cable length drops to just 10'....).
Also, is your transceiver capable of driving the slew rates needed for these speeds? - A truly RS232 compliant transceiver, _will not_ be able to handle these rates, since the RS232 limit of 30V/uSec on slew rate, has to be exceeded to work this fast....
However this would not explain the extra start bit being seen, which suggests an electrical noise problem.
Best Wishes |
|
|
levdev
Joined: 19 May 2010 Posts: 36 Location: UK
|
|
Posted: Mon Jun 27, 2011 10:17 am |
|
|
Thanks for your replies. The PC output is from a comms port embedded on the motherboard not via a USB converter so I don't think this is the issue.
The 0xFF bytes are put in by me in the code, just to make the issue easier to see on the screen when it occurrs. These are not part of the error.
Code: |
void Out_Buff_Error()
{
int i;
putc(0xFF);
for (i=0; i<IN_COUNT; i++) putc(IN_BUFF[i]);
putc(0xFF);
}
|
The actual reply is 1 byte shorter than it should be, indicating that the uC receives the single byte 0x28 rather than the two bytes 0x0C and 0x01 that was transmitted.
I have tested monitoring the output of the PC in paralell with another PC, and can confirm that the correct command is received by the other PC at the time of the error, which eliminates the PC Windows glitch.
The cable length is short (<3m) and well screened and not in a noisy environment. As I said in the original post, the problem does not exist at 9600, 57600 or 115200 BAUD so I don't think it is a speed related issue. I am using the MAX3221EIPWR which has a maximum specified data rate of 250kbps.
I agree that something is causing a framing error, but I can't align the bit pattern to work out where it is occurring. Below shows the error return values against some different transmitted values:
Code: |
Tx Byte 1 Tx Byte 2 Rec Byte
-----------------------------------------
0C 01 28
00001100 00000001 00101000
6C 01 2B
01101100 00000001 00101011
9C 01 16
10011100 00000001 00010110
0C FF E8
00001100 11111111 11101000
|
|
|
|
levdev
Joined: 19 May 2010 Posts: 36 Location: UK
|
|
Posted: Thu Jun 30, 2011 7:53 am |
|
|
Has anyone got any other ideas, I still can't get past this problem. |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9229 Location: Greensville,Ontario
|
|
Posted: Thu Jun 30, 2011 8:10 am |
|
|
I see you're using the internal oscillator. Any chance you can try with a real crystal ? I know the odds are slim considering the PIC works at other , even higher baud rates, but perhaps there's an 'issue' with the internal osc and baud rate combinations ??
If the problem magically goes away...you know what to do if not...hmm..more head scratching !
Your test of PC to PC being OK is great, but those have real crystals for clocks.
'try and reply', the more info the more 'options' we might think of. |
|
|
RF_Developer
Joined: 07 Feb 2011 Posts: 839
|
Critical sections/Interrupt locking |
Posted: Fri Jul 01, 2011 4:57 am |
|
|
It looks likely that you are suffering from a lack of interrupt locking/critical sections :-(
When you receive characters you fill up your interrupt routine. That's great. BUT you have to make sure that when you read characters from the buffer, or most other buffer operations in main code, you ensure that the routines are atomic, in other words that they cannot be interrupted. The problem is that interrupts can happen at any time, INCLUDING right at a vital moment in your buffer reading code. The interrupt then changes the buffer pointers/counters/whatever, and your reading code gets confused. Reading the same character twice would be quite likely, or simply missing one or more out altogether.
The same happens when sending via buffers filled in main code and emptied in a transmit ISR. In that case characters can get out of order, or not be sent at all. In some cases its possible to deadlock the whole transmission process, with nothing going out at all.
To make a routine atomic, i.e. to "interrupt lock" it aka making it a critical section. You need to disable the relevant interrupt on entry, i.e. before you start using the buffer itself, and re-enable it on exit, i.e. once you've finished all buffer operations. Be careful if you're passing back a pointer to the buffer rather than a copy of the character/s. That's because you've got no guarantee that the data will still be in the buffer once you're out of the routine. Better to copy while interrupts are off, then you can turn them back on and the copy is safe from being messed with. You can turn off all interrupts, but that's a sledgehammer solution, its better to turn off only the relevant interrupt, i.e. INT_RDA for RS232 receive. Temporarily disabling the interrupts like this delays the ISR, you wont lose interrupts. BUT its important the critical section is as short as practical. Just read the buffer and re-enable interrupts asap.
Another possibility is that you are using the internal oscillator, which has relatively poor accuracy compared to a crystal. This will be adding to the inaccuracy of the baud rate and may be causing it to go out of the +/-3% rs232c spec. Baudrate with lower inherent error will work better... |
|
|
|
|
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
|