|
|
View previous topic :: View next topic |
Author |
Message |
Barney
Joined: 18 Oct 2004 Posts: 41 Location: Newark, CA
|
115200 to 57600 conversion |
Posted: Thu Aug 25, 2005 6:53 pm |
|
|
I am using a 16F88 to buffer the 115200 baud output of a device. The PIC receives data at 115200 baud and then transmits it to a PC at 57600 baud. HW details: PIC running at 18.432Mhz, the 115200 is going into the hardware UART, and I am using a software UART at 57600 to send to the PC.
I am using INT_RDA to store the incoming bytes into a 64 byte circular buffer. There is a loop in main() that checks for new data in the buffer and transmits it if found at the slower rate.
Since the hardware UART has a 2 byte FIFO plus the RSR this gives almost 3 bytes of hardware buffering. I figured that would be enough to cover the slower xmit rate, but I knew the code would have to be very tight.
In my test case, I send 20 bytes with no delays and the PIC is dropping 3 bytes at 57600. So just for grins I raised the xmit rate and at 65000 baud no bytes were dropped. (I don't plan on using 65000 baud, just wanted to see how high I had to go to not drop bytes.)
I instrumented the interrupt handler and the fputc() command to toggle output pins and looked at the timing on a DSO. PIN_B4 for the fputc() shows a time of 213uS. Transmitting a byte at 57600 should take only 174 uS, so I am loosing 39 uS somewhere.
Questions:
1. Is 115200 to 57600 feasible? There seems to be more overhead than I expected. Would a hand written ASM output routine be efficient enough?
2. I can go to 20Mhz and gain another 8.5% of clock speed. Worthwhile? An extra 8.5% gain in clock speed dosen't seem enough.
3. I am using the 16F88 because I am familiar with it, but would consider going to a 40Mhz 18F if that would work.
Code: | include <16F88.h>
#fuses NOWDT, NOPROTECT, NOLVP, HS
#use standard_io(A)
#use standard_io(B)
#use delay(clock=18432000)
#use rs232(FORCE_SW, INVERT, BAUD=57600, XMIT=PIN_A0, RCV=PIN_A1, DISABLE_INTS, PARITY=N, BITS=8, STREAM=HOST)
#use rs232(BAUD=115200, XMIT=PIN_B5, RCV=PIN_B2, PARITY=N, BITS=8, ERRORS, STREAM=DUT)
#define BUF_SIZE 64
char b0[BUF_SIZE];
int in_ptr = 0;
int out_ptr = 0;
#int_RDA
void RDA_isr()
{
char inchar;
output_high(PIN_B7);
inchar = fgetc(DUT);
if (inchar) { //ignore nulls
b0[in_ptr] = inchar;
in_ptr = (in_ptr + 1) % 64;
}
output_low(PIN_B7);
}
void main(){
output_low(PIN_B7);
output_low(PIN_B4);
enable_interrupts(INT_RDA);
enable_interrupts(global);
while(TRUE) {
if (out_ptr != in_ptr) {
output_high(PIN_B4);
fputc(b0[out_ptr], HOST);
output_low(PIN_B4);
out_ptr = (out_ptr + 1) % 64;
}
}
}
|
|
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Thu Aug 25, 2005 7:51 pm |
|
|
Quote: | 1. Is 115200 to 57600 feasible? There seems to be more overhead than I expected. Would a hand written ASM output routine be efficient enough?
|
If you are transmitting continously, then the obivous answer would be no.
The amount of time it takes to receive 64 bytes must be larger than the amount of time it takes to transmit the data. That means you will need on average 1 character time of dead time.
I suspect that interrupts have to be disabled to transmit. Since two bytes can be received during the transmission and then account for the interrupt overhead plus the transmit overhead and you've got problems. You would be better off polling the hardware than trying to use the int. Code: |
#fuses NOWDT, NOPROTECT, NOLVP, HS
#use standard_io(A)
#use standard_io(B)
#use delay(clock=18432000)
#use rs232(FORCE_SW, INVERT, BAUD=57600, XMIT=PIN_A0, RCV=PIN_A1, DISABLE_INTS, PARITY=N, BITS=8, STREAM=HOST)
#use rs232(BAUD=115200, XMIT=PIN_B5, RCV=PIN_B2, PARITY=N, BITS=8, ERRORS, STREAM=DUT)
#define BUF_SIZE 64
void main(void)
{
int in_ptr = 0;
int out_ptr = 0;
char inchar;
char b0[BUF_SIZE];
while(TRUE)
{
while (kbhit())
{
inchar = fgetc(DUT);
//ignore nulls
if (inchar)
{
b0[in_ptr] = inchar;
in_ptr++;
// Limit the buffer size to 64
bit_clear(in_ptr,6);
}
}
if (out_ptr != in_ptr)
{
fputc(b0[out_ptr], HOST);
out_ptr++;
// Limit the buffer size to 64
bit_clear(out_ptr,6);
}
}
}
|
|
|
|
ckielstra
Joined: 18 Mar 2004 Posts: 3680 Location: The Netherlands
|
|
Posted: Fri Aug 26, 2005 7:51 am |
|
|
Mark,
Can you explain why you are ignoring the incomming nulls? If the incomming data stream is binary, then I think it is incorrect to throw away incomming null data. |
|
|
bkamen
Joined: 07 Jan 2004 Posts: 1615 Location: Central Illinois, USA
|
Re: 115200 to 57600 conversion |
Posted: Fri Aug 26, 2005 8:19 am |
|
|
Barney wrote: |
3. I am using the 16F88 because I am familiar with it, but would consider going to a 40Mhz 18F if that would work.
|
An 18F would certainly work better as the 18F6520 and 8520 both come with
(2) AUSART's. HOWEVER, because you are converting a faster rate to a slower one, you MUST have some sort of handshaking going on with the faster host or you will lose bytes if the transmit is sending for a period longer than the buffer can hold while sending at the 1/2 speed on the "output" port at 57600.
But and 18F w/2 hardware serial ports would be a better fit.
-Ben |
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Fri Aug 26, 2005 8:37 am |
|
|
ckielstra wrote: | Mark,
Can you explain why you are ignoring the incomming nulls? If the incomming data stream is binary, then I think it is incorrect to throw away incomming null data. |
Look at his code. He was doing that and had a comment about it thus I did as well for him. Normally I wouldn't have and indeed it takes a couple of extra instructions. |
|
|
Barney
Joined: 18 Oct 2004 Posts: 41 Location: Newark, CA
|
Amplifications |
Posted: Fri Aug 26, 2005 10:17 am |
|
|
1. The data comes in bursts so the 64 byte buffer should be long enough to handle the situation. I agree that the INT_RDA must be disabled while using a software UART to transmit so I am using the DISABLE_INT parameter in the #use rs232(). I also tried bracketing the fputc() with disable, enable interrupt commands and it worked about the same.
2. I am ignoring nulls because the cretin that designed the external device uses the TX pin for other things, like an index pulse. The index pulse causes the PIC UART to see <NULL> characters. The real data is ASCII text so throwing away the <NULL>s is OK.
3. I tried polling with kbhit() and it was even worse for dropping characters.
Using the bit_clear() function to implement the circular buffer is cute. Any idea on how much time it saves?
4. No handshaking available. The PIC has to be able to keep up with 115200 baud characters. Yeah the design bites, but that is what I have to deal with. |
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Fri Aug 26, 2005 10:42 am |
|
|
So you tried the code that I posted and it dropped more chars? |
|
|
Barney
Joined: 18 Oct 2004 Posts: 41 Location: Newark, CA
|
|
Posted: Fri Aug 26, 2005 2:08 pm |
|
|
Yes I tried your code. It was very similar to a polling routine I had tried earlier and both were signifcantly worse than the interrupt version.
I think this mid-line CPU just doesn't have the horsepower to do the job reliably so I am moving up the food chain. I found some 18F6227 with dual EUSARTs that should give me a lot more head room.
I am not familiary with the 18F6XXX series, in particular the data addressing. How big a byte array will the CCS compiler support on this chip? |
|
|
newguy
Joined: 24 Jun 2004 Posts: 1908
|
|
Posted: Fri Aug 26, 2005 2:33 pm |
|
|
Barney wrote: | I am not familiary with the 18F6XXX series, in particular the data addressing. How big a byte array will the CCS compiler support on this chip? |
Can't comment on the 18F6xxx series, but I have a buffer of 500 integers running on a 18F4610 with no issues. |
|
|
Ttelmah Guest
|
|
Posted: Sat Aug 27, 2005 5:20 am |
|
|
The dual UART chip, will get rid of the problem, provided the data really can be shifted out in the time. However looking at it, I'd suspect you might actually get better results using the two UARTs the other way round!. This is the reverse to what seems 'good logic', since using the hardware to handle the faster rate makes good sense, but if you are running the 'polling' approach, using the software input for the higher rate, potentially allows the slower data to be sending while the faster data is being received. If you reversed the UARTS, and then used the polling code posted, but modified with:
Code: |
#fuses NOWDT, NOPROTECT, NOLVP, HS
#use standard_io(A)
#use standard_io(B)
#use delay(clock=18432000)
#use rs232(INVERT, BAUD=115200, XMIT=PIN_A0, RCV=PIN_A1, PARITY=N, BITS=8, STREAM=DUT)
#use rs232(BAUD=57600, XMIT=PIN_B5, RCV=PIN_B2, PARITY=N, BITS=8, ERRORS, STREAM=HOST)
#define BUF_SIZE 64
#bit TRMT=0x98.1
#byte TXREG=0x19
void main(void)
{
int in_ptr = 0;
int out_ptr = 0;
char inchar;
char b0[BUF_SIZE];
while(TRUE)
{
if (kbhit(DUT))
{
inchar = fgetc(DUT);
//ignore nulls
if (inchar)
{
b0[in_ptr] = inchar;
in_ptr++;
// Limit the buffer size to 64
bit_clear(in_ptr,6);
}
}
if (TRMT)
if (out_ptr != in_ptr) {
TXREG=b0[out_ptr];
out_ptr++;
// Limit the buffer size to 64
bit_clear(out_ptr,6);
}
}
}
|
The key here, is that you sit in a loop, effectively polling for the start bit. If a start bit is found, you use the sofware UART to retrieve the character. Whenever the start bit is not present, you test for the hardware holding register being empty, then if it is, check if any data is waiting to send, and put it directly into the register if it is. The longest time in this loop, would be in this case. The slowest thing will be the handling of the array access. I'd suspect the loop time would be in the order of 25 instructions. If so, the worst case 'error' on finding the start bit, would only be about 6uSc, and this _might_ just about work. You might make this work reliably, by a really 'brutal' piece of programming (ugly/bulky), using this approach:
Code: |
if (TRMT)
if (out_ptr != in_ptr) {
switch (out_ptr) {
case 0:
TXREG=b0[0];
break
case 1:
TXREG=b0[1];
break;
//repeat for 62 more cases!...
}
out_ptr++;
// Limit the buffer size to 64
bit_clear(out_ptr,6);
}
|
Provided you have no default statement, this should be coded by the compiler as a 'jump table'. Though this takes a while to set up, it means that each access to the array, will be hard coded, rather than using a variable. Potentially this _may_ be quicker than the array access!.
Best Wishes |
|
|
Barney
Joined: 18 Oct 2004 Posts: 41 Location: Newark, CA
|
|
Posted: Mon Aug 29, 2005 8:09 am |
|
|
Interesting out of the box thinking. While I am waiting for the 18F parts, I will give it a whirl and report on the results.
I also realized that the UART TX section is sitting idle. Right now, all of the communication is one way, so I could use the set_uart_speed to switch back and forth between the different receive and xmit rates. Any words of wisdom? |
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Mon Aug 29, 2005 9:12 am |
|
|
Barney wrote: | Interesting out of the box thinking. While I am waiting for the 18F parts, I will give it a whirl and report on the results.
I also realized that the UART TX section is sitting idle. Right now, all of the communication is one way, so I could use the set_uart_speed to switch back and forth between the different receive and xmit rates. Any words of wisdom? |
No you can't unless you receive a "packet" of data and can transmit this "packet" before the next "packet" starts to be received. There are not separate baud rate generators for the rx and tx. If there were, then you could do what you stated. |
|
|
Barney
Joined: 18 Oct 2004 Posts: 41 Location: Newark, CA
|
|
Posted: Mon Aug 29, 2005 9:48 am |
|
|
Right, bad idea. Back to the drawing board. |
|
|
Barney
Joined: 18 Oct 2004 Posts: 41 Location: Newark, CA
|
|
Posted: Tue Aug 30, 2005 10:03 am |
|
|
I tried a couple of other ideas and they didn't work. Sadly, I have to conclude that the 16F88 doesn't have the horsepower to do the job reliabily.
1. The 115200 software UART: Looking at the timing requirements, it should have worked, but didn't. I cut the polling loop to just the kbdhit() and fgetc() and toggled an output bit before and after the fgec(). I didn't even store the data or output it. The oscilloscope shows a lot of data is being missed. Don't know why.
2. External Interrupt: Since the software UART was missing bytes, I thought to use the EI to "kick start" it by triggering on a falling signal to detect the start bit. There is a 8.5 uSec delay between the falling signal and a bit toggle at the start of the interrupt handler. This means the interrupt handler misses the entire start bit at 115200 baud. I could maybe write a custom routine to pick up the 8 data bits, but that seems too hokey.
So now I am waiting for some 18FXXX parts with dual H/W UARTS & a PCH compiler. My new motto is "Do it in hardware".
Thanks for the suggestions. |
|
|
Barney
Joined: 18 Oct 2004 Posts: 41 Location: Newark, CA
|
|
Posted: Fri Sep 16, 2005 10:31 am |
|
|
The 18F6627 with the dual UARTS works like a champ. No more dropped characters. I set it up with a 2KB buffer and just for grins ran some tests to see how much buffer was required for various output bauds. These results depend on the burst timing characteristics of my incoming data (YMMV). When graphed it shows a pretty good quadratic curve. In each case a total of 519 bytes was received at 115KB. Since I can't figure out a way to do tables, the data is ordered pairs of:
Xmit Rate:Max #Chars Buffered
9600:245
14400:218
19200:190
28800:150
38400:127
57600:93
115200:2 |
|
|
|
|
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
|