# uart bit banging

Status
Not open for further replies.

#### Dr_Doggy

##### Well-Known Member
trying to get this working on non-uart pin(RC1) for LCD NHD-0216K3Z-NSW-BBW-V3
with RS-232 (TTL) protocol, 8-bit data, 1 Stop bit, no parity, no hand-shaking. BAUD rate is 9600

I am getting results but they are a "bit" or 2 off, I think my problem is with delay time, or maybe inversion issue.
Maybe i need to tweak my clock? or set clock up wrong? Maybe im not factoring in properly the time it takes for all the if checking?
tried lots of small adjustments but just not getting it..
thanks!

Code:
void sendUart(unsigned char datdat)
{
PORTC |= 0b00000010;        // start bit
__delay_us(99);
nextstep:
if (datdat&1){PORTC |= 0b00000010; }else{PORTC &= 0b11111101;}   set output
__delay_us(99);
datdat = datdat>>1;
if (cnt<8){cnt++;goto nextstep;}                  // do 8 times
PORTC &= 0b11111101;    // stop bit
__delay_us(99);
}
setup code:

Code:
#include <xc.h>
#define _XTAL_FREQ 8000000

main(){
OSCCONbits.SCS = 0;        // Clock source defined by FOSC<2:0> of the Configuration Word
OSCCONbits.IRCF0 = 1;   // 8 Mhz pls
OSCCONbits.IRCF1 = 1;
OSCCONbits.IRCF2 = 1;
OSCTUNE= 0b00000000; // no drift
INTE = 1; // interrupt used
while(1){}
}

void interrupt  INTERRUPT()
{
if(INTF == 1 & INTE == 1)     // on interrupt
{
unsigned char  dat = 0xAA;    // test byte to send to LCD
unsigned char  cnt=0;
sendUart(dat);
delayMS(1000);                       // long pause
INTF = 0;
}

}

Last edited:

#### NorthGuy

##### Well-Known Member
You need about 2.5% accuracy on your clock, but if the receiver has a good clock you can get away with as bad as 4%. With the internal oscillator being somehow inaccurate plus inaccuracies introduced by C, you probably just not getting enough accuracy.

You certainly can tune it up with long series of "NOPs", equalizing paths (so that setting "one" takes exact the same time as setting "zero") and so on, but it still may fall apart the next time you re-compile.

#### atferrari

##### Well-Known Member
You certainly can tune it up with long series of "NOPs", equalizing paths (so that setting "one" takes exact the same time as setting "zero") and so on, but it still may fall apart the next time you re-compile.
Do you mean, next time he changes the code somehow?

#### Nigel Goodwin

##### Super Moderator
Check the exact timings on a scope, and adjust your delay routines accordingly - bear in mind that if you're using the free version of an XC compiler they add loads of spurious code to make it really large and slow.

#### Dr_Doggy

##### Well-Known Member
yup free .. I have been thinking about purchasing, just for the fact of freeing up program space but havn't made that 50\$ leap just yet...
thought about switching to asm for this one function, that would cut out compiler adding extra spurious code?
any ideas about how/when spurious code occurs are there instructions I should avoid or change, for example i often change for statements to if's and goto's.
also how to i find out how long each instruction takes, i figure a nop time period would take 1 clock tick, and a=b would be closer to 3 or 4 ticks..? would a asm conversion reveal more into this?

I have been thinking about the clock inaccuracy problem, i may have made a mistake when i was thinking faster=more accurate, but now that i realize problem is in the clock itself, maybe a lower frequency would be more stable?

I am using LCD just for debugging, but since i cant change LCD internal baud rate without first achieving current baud rate I decided to use pickit uart tool in place of LCD where i can slow PICKIT down to 300 baud... interesting note after getting on a scope, these instructions are for a 2ms delay:__delay_ms(1);__delay_us(980); .... is this where i should use osctune to compensate?

Thanks for the tolerance notes, I now realize that sending 5 chars results in the first and last ones outputting wrong, but the middle 3 are ok, i though at first that it was an error with a polarity or delay time with the start/stop bits ... after getting scope on it, it turns out that there is another function clearing RC1 ... I try using that trick with a dummy byte for port C that we just talked about the other month.. but it is maybe not working so well this time...

another interesting note to my problem is that it lies in BOTH of these next functions , ie leaving one out is not enough to keep the RC1 bit from clearing to 0... I will highlight the lines in RED to show exactly which lines i had to remove to keep RC1 at a logic 1 value... runLED is on timer0 interrupt and sendIR is called by main()... (just mentioning in case...)

EDIT: code tags dont highlight red so i will leave the 4 //'s in a clearly visible location

Code:
void runLED(unsigned char R, unsigned char G, unsigned char B)
{
unsigned char dummy = PORTC;
if(B > dutyCounter){dummy &= 0b11101111;}else{dummy |= 0b00010000; }
if(R > dutyCounter){dummy &= 0b11111011;}else{dummy |= 0b00000100; }
if(G > dutyCounter){dummy &= 0b11110111;}else{dummy |= 0b00001000; }
if(dutyCounter > 250){dutyCounter = 0;}else{dutyCounter++;}
//    PORTC = dummy;
}

void sendIR(unsigned int Rem)
{
unsigned char  counter;
unsigned char  dummy = PORTC;
INTE = 0;
delayMS(1);
// start bit
for (counter = 0;counter<16;counter++)
{
dummy|= 0b00100000;
delayMS(1);
dummy&= 0b11011111;
if (Rem & 1){delayMS(1);}
else {delayMS(2);}
Rem = Rem >> 1;
}
counter = PORTA;
//    PORTC = dummy;
delayMS(1);
INTF = 0;
INTE = 1;

}

Last edited:

#### NorthGuy

##### Well-Known Member
Do you mean, next time he changes the code somehow?
Yes.

It is not only impossible to predict C timing, it is also no guarantee it stays the same when your program changes.

You can get better timing with assembler or with interrupts.

#### Nigel Goodwin

##### Super Moderator
.... is this where i should use osctune to compensate?
No, certainly not - osctune is to set your clock as accurately as possible, not to try and overcome programming errors.

As I said before, measure the exact timing with a scope, and then adjust your delays accordingly - once you've got it accurate, then don't alter that part of the code in any way whatsoever (or the compiler might add more spurious and unwanted instructions). Good values to send for the scope are 0x55 or 0xAA (or alternate both).

You might also consider doing that part of the code as in-line assembler, so you have accurate control of your timing.

As for your RS232 speed?, I've always found 9600 baud a good compromise, relatively fast but still simple to do in software.

#### Pommie

##### Well-Known Member
I always use two stop bits so the timing isn't carried over between bytes. Just change your delay(99) to delay(200). If your first 3 are getting through then this should fix your problem.

BTW, using goto in C is really not encouraged. Why not use a for loop like most people do,
Code:
void sendUart(unsigned char datdat)
{
unsigned char i=0;
PORTC |= 0b00000010;        // start bit
__delay_us(99);
for(i=0;i<8;i++){
if(datdat&1)
PORTC |= 0b00000010;
else
PORTC &= 0b11111101;//   set output
__delay_us(99);
datdat>>=1;
}
PORTC &= 0b11111101;    // stop bit
__delay_us(99);
}
BTW, if you want to see the generated code, menu->window->debugging->disassembly will show it if you set a breakpoint near the questionable code.

Mike.

#### Dr_Doggy

##### Well-Known Member
thanks , I am used to calibrating delay values with scope since in my earlier years as i never set up the clock properly, but this time i think i have osc settings right!... but when i use code: __delay__us(1980) ... scope shows 2ms delay(baud ok now in real time @300bps)... if osctune is not ideal fix than maybe another problem/solution somewhere else in my code? (just wondering, it is not a real problem, results are consistent )

IT turns out my problem is not timing or inversion... and it is the first and last bytes getting error, the middle 3 are the ones displaying OK..
When uart is idle the bus should be logic 1 but check out the code in post 5, as it is... it works, but if i UN-comment either one of the lines in either function the uart idling [email protected] 1 drops to logic 0.... I have confirmed that it is those 2 commented lines that are clearing RC1 but they should not be doing that...??? .... because i am using dummy bytes...

ie.. this code should not clear RC1, but it does:
unsigned char dummy = PORTC;
dummy|= 0b00100000;
delayMS(1);
dummy&= 0b11011111;
PORTC = dummy;

#### Dr_Doggy

##### Well-Known Member
Yes.

It is not only impossible to predict C timing, it is also no guarantee it stays the same when your program changes.

You can get better timing with assembler or with interrupts.
That is one thing I like about the more complex SPI/I2C encoding.. time is not as critical but for now I need way to debug some other code till i get proper pickit and pic's and with little program space available... there is R1/R2 jumper that would switch it over... maybe i should use it..

Pls elaborate!
Im rather surprised at this, doesnt compiler basically look at the line and switch it to asm, what kind of change would do this? Even when im in an interrupt loop already, mostly i still keep BOR and other background functions off.... ? or does compiler work with code to compress like we would do with boolean truth tables? ..... I assumed free mode would not compress and purchased would include compressor?

#### Nigel Goodwin

##### Super Moderator
..... I assumed free mode would not compress and purchased would include compressor?
As I understand it the free mode does give some optimization, just less than the paid for ones - but that's not the real problem, the problem is that the free version deliberately adds loads of spurious code making it much larger and slower than it should be.

#### jjw

##### Member
Maybe I am missing something, but shouldn't start bit be 0 and stop bit 1 ?

#### jjw

##### Member
In post #1 start and stop bits seem to be inverted, start bit is 1, stop bit is 0

#### Dr_Doggy

##### Well-Known Member
you are quite rite JJW, good eye, in all the previous examples it seems we have all made that mistake (me because im snipping alot of code to simplify for easier assistance here), however as i change code to find errors I have since corrected this issue....
But check out post #9 & #5, the sendIR is a different function than sendUart and sendLCD...

... BUT... after testing and commenting out alot of other code!
I have found it is the sendIR() function that is clearing portRC1 when im clearly instructing RC5 only!!
... the same thing happens in function runLED(post#5)<-- this is for a single RGB LED on port RC 2,3,4
... sendUart uses the uart protocol which im debugging on portRC1 to use for more complex debugging somewhere else! which is reason for the LCD

ie.. this code should not clear RC1, but it does, it should only be changing RC5:
unsigned char dummy = PORTC;
dummy|= 0b00100000;
PORTC = dummy;
delayMS(1);
dummy&= 0b11011111;
PORTC = dummy;

#### Pommie

##### Well-Known Member
Regarding the clearing of RC1, there are a couple of reasons why RC1 may always read as zero,
1. It's set as an analogue pin.
2. It's heavily loaded and so is being pulled low enough to register as a zero.

As you are using a dummy variable, why not always modify the dummy and write it to port c and never read port C.

Mike.

#### Nigel Goodwin

##### Super Moderator
ie.. this code should not clear RC1, but it does, it should only be changing RC5:
unsigned char dummy = PORTC;
dummy|= 0b00100000;
PORTC = dummy;
delayMS(1);
dummy&= 0b11011111;
PORTC = dummy;
Why are you doing all this just to set or clear a bit?, PIC's have specific instructions for doing that.

#### NorthGuy

##### Well-Known Member
Im rather surprised at this, doesnt compiler basically look at the line and switch it to asm, what kind of change would do this? Even when im in an interrupt loop already, mostly i still keep BOR and other background functions off.... ? or does compiler work with code to compress like we would do with boolean truth tables? ..... I assumed free mode would not compress and purchased would include compressor?
There are numbers of ways to convert C to the Assembler. You cannot predict what exactly the compiler does. At any rate, it's easier to write your own Assembler than to predict the complier behaviour. The generated code also depends on where variables are, banking etc. If you add a variable in the future, banking assignment may change, and voila - you get lots of extra "movlb" instructions here and there. By this time you forgot about the UART and you assume it is working and you cannot make sense of the garbage it is transmitting.

#### Dr_Doggy

##### Well-Known Member
pommie was very close comment #2) It's heavily loaded and so is being pulled low enough to register as a zero.
... we have established similar problems before when using RC1= which is why I started using bytes for portc operation

actually, RC1 is the only pin i can confirm that isn't overloaded... no brownouts though .... but it is the one that is being read as 0 so any operators &=, |= was clearing it....

more interesting is that even eliminating all the read operations did not fully work... to get it to work fully, anywhere I had portc=dummy; I also had to add the line dummy|=0b00000010; prior to it which is strange since there are few calls to the public variable dummy and none of them change rc1!

anyway, good that im over that last hump, code here is almost done! Maybe though I should attempt to learn this asm, any nice simple tutorials?.. or where to start!?

Forum Supporter