Continue to Site

Welcome to EDAboard.com

Welcome to our site! EDAboard.com is an international Electronics Discussion Forum focused on EDA software, circuits, schematics, books, theory, papers, asic, pld, 8051, DSP, Network, RF, Analog Design, PCB, Service Manuals... and a whole lot more! To participate you need to register. Registration is free. Click here to register now.

[PIC] Software Serial RS232 interface for 16F877a

Status
Not open for further replies.

bar_

Newbie level 6
Joined
Jun 22, 2016
Messages
11
Helped
0
Reputation
0
Reaction score
0
Trophy points
1
Activity points
98
Dear All,

I'm trying to create a software UART for my 16f877a controller for interfacing serial bluetooth and gsm modem for a home automation purpose.

But i got stuck in the first step itself. First i tried FORCE_SW function on the CCS C complier but it is not working properly. i mean, the data displayed on the terminal is full of junk. the code is below

Code:
#include "16F877a.h"
#use delay(clock = 4000000)
#use rs232(baud = 9600, xmit = pin_c0, rcv = pin_c1, force_sw)
void main()
   {
   set_tris_c(0b10000010);        // UART RX pin direction register
      while(1)
         {
            printf("e\n\r");
            delay_ms(100);
         }
   }

Then i tried to write my own code to just transmit the character "e" at a interval of 100ms continously but it is also not working. i mean not displaying any junk data also. below is the code

#include "16f877a.h"
#use delay(clock=40000000)
#define Baudrate              9600                    //bps
#define one_bit_delay (1000000/Baudrate)              //one bit delay
#define DataBitCount 8                                // no parity, no flow control
#define set_rx_tx_direc set_tris_c(0b10000010)        // UART RX pin direction register
#byte port_c = 0x07
#bit UART_RX = port_c.1
#bit UART_TX = port_c.0
void main()
{      
   set_rx_tx_direc;  
   UART_TX = 1;
   delay_ms(3);
   while(1)
   {
    UART_TX = 0; //start bit to transmit "e" whose ascii value = 0d101 or 0b01100101
    delay_us(one_bit_delay);
    UART_TX = 1; //0th bit
    delay_us(one_bit_delay);
    UART_TX = 0; //1st bit
    delay_us(one_bit_delay);
    UART_TX = 1; //2nd bit
    delay_us(one_bit_delay);
    UART_TX = 0; //3rd
    delay_us(one_bit_delay);
    UART_TX = 0; //4th
    delay_us(one_bit_delay);
    UART_TX = 1; //5th
    delay_us(one_bit_delay);
    UART_TX = 1; //6th
    delay_us(one_bit_delay);    
    UART_TX = 0; //7th
    delay_us(one_bit_delay);    
    UART_TX = 1;  //stop bit
    delay_us(one_bit_delay);    

    delay_ms(100); // delay_to_next_character

    }
}

please anybody help me solving this problem. thanks in advance.
 

I think that code should work although it would make more sense to define the bit rate in terms of the clock instead of a constant and to use a loop to shift bits from a byte instead of telling TX to go low or high in lots of instructions.

How do you have it interfaced? Bear in mind the data stream will be positve logic but if the other devices are expecting RS232 data, it will be inverted (including the start and stop bits so it won't sync properly) and at the wrong voltage levels.

Brian.
 

Hi,

I'd go for an interrupt solution.

Isn't there a hardware UART?

Klaus
 

Thanks betwixt and KlausST for the reply.

betwixt, if i try to define the bit rate using delay_cycles(), the minimum delay i can get will be 5uS in case of 20Mhz crystal.
Thats why i used the delay_us() function it will give me as minimum as 1uS delay. regarding the function based transmission i had tried that also but no results. the code is below

Code:
#include "16f877a.h"
#use delay(clock=40000000)
#define Baudrate              1200                    //bps
#define OneBitDelay           (1000000/Baudrate)
#define DataBitCount          8                       // no parity, no flow control
#define set_rx_tx_direc       set_tris_c(0b10000010)  // UART RX pin direction register
#byte  port_a = 0x05
#byte  port_b = 0x06
#byte  port_c = 0x07
#byte  port_d = 0x08
#bit   UART_RX = port_c.1
#bit   UART_TX = port_c.0
void InitSoftUART(void);
char UART_Receive(void);
void UART_Transmit(char charc);


void InitSoftUART(void)      // Initialize UART pins to proper values
{           
   output_high(pin_c0);      // TX pin is high in idle state
   set_rx_tx_direc;
}
char UART_Receive(void)
{
   char DataValue = 0;
   char i = 0;
   //wait for start bit
   while(UART_RX==1);
   delay_us(833);
   delay_us(416);   // Take sample value in the mid of bit duration
   for ( i = 0; i < DataBitCount; i++ )
   {
      if ( UART_RX == 1 )   //if received bit is high
      {
         //DataValue += (1<<i);
         DataValue = (DataValue | (DataValue << i));
      }
      delay_us(833);
   }
   // Check for stop bit
   if ( UART_RX == 1 )       //Stop bit should be high
   {
      //delay_us(OneBitDelay/2);
      delay_us(416);
      return DataValue;
   }
   else                      //some error occurred !
   {
      //delay_us(OneBitDelay/2);
      delay_us(416);
      return 0x00;
   }
}
void UART_Transmit(char DataValue)
{
   char i = 0;
   UART_TX = 0; // Send Start Bit
   delay_us(833); // Send Start Bit
   for(i = 0; i < DataBitCount; i++)
   {
      //Set Data pin according to the DataValue
      //DataValue = (DataValue | (DataValue << i));
      if(((DataValue >> i) & 0x01) == 0x01)
      //if( ((DataValue>>i)&0x1) == 0x1 )   //if Bit is high
      {
         UART_TX = 1;
      }
      else      //if Bit is low
      {
         UART_TX = 0;
      }
       delay_us(833);
   }   
   UART_TX = 1;  //Send Stop Bit
   delay_us(833);  //Send Stop Bit
}
void main()
{   
   char ch = 0;
   InitSoftUART();       // Intialize Soft UART
   delay_ms(10);
   while(1)
   {
      ch = UART_Receive();   // Receive a character from UART
      if(ch != 0)
      {
      UART_Transmit(ch);      // Echo back that character
      }
   }
}
i think there is no problem in the code. i dont know where the problem occurs.

- - - Updated - - -

How do you have it interfaced? Bear in mind the data stream will be positve logic but if the other devices are expecting RS232 data, it will be inverted (including the start and stop bits so it won't sync properly) and at the wrong voltage levels.
Brian.

betwixt, as of now im just simulating the code in proteus only. i viewed the signal pattern of the hardware based data transmission of the letter "e" and ditto the same pattern in the while(1) in the main function but there is not output

- - - Updated - - -

KlausST, it can be done using interrupt, but i want to make the code simple. so i tried to just make a TX part to work. 16F877a has 1 nos of hardware UART but i want 2 Nos of UART port for interfacing bluetooth and GSM modems simutaneously to the controller
 
Last edited by a moderator:

Hi bar_;
I'm not using this compiler (CCS C) but I think your main problem is the wrong clock definition. This line
#use delay(clock=40000000)
means that the compiler assumes a 40 MHz (??) clock to construct the delay_us(xx) routines, but you are using a 20 MHz crystal. And .. do you defined the 20 MHz also in Proteus, in the PIC's Properties window?
I advice that use the "Virtual Terminal" in the Proteus to examine the in-out signals.
When you are using the COF file (generated by the compiler) instead of the HEX, you'll able to debug step-by-step the code at source level. In this case you will see the 'real' timing values too.
 
  • Like
Reactions: bar_

    bar_

    Points: 2
    Helpful Answer Positive Rating
Good catch Zuisti, I did't notice the extra zero.

I don't use CCS either but what I meant by defining in terms of the clock is to calculate the bit delay from the clock frequency setting instead of explicity 1,000,000 so it still works if you change crystal. I can't confirm the code without CCS but something like this:

#define clock_frequency 20000000
#define baud_rate 9600
#define bit_period clock_frequency/(baud_rate * 4)

now, if your change clock frequency or baud rate it will recalculate the correct bit period.

Brian.
 
  • Like
Reactions: bar_

    bar_

    Points: 2
    Helpful Answer Positive Rating
Hi Zuisti, big thanks to you, after editing of the clock speed from 40Mhz to 4Mhz, the code worked fine. i got the result. thank you very much.

Thanks to betwixt and klausst also.
 

Hi,

KlausST, it can be done using interrupt, but i want to make the code simple.

It´s a point of view. I like using interrupts BECAUSE it makes the code SIMPLE.
Additionally it generates a fixed UART timing. And in your case I assume it safes 99% processing power. (In other words: it needs only 1% processing power)
In main loop I just write the bytes to be transmitted in a software buffer. The rest of transmitting is made by the interrupt routine. But I don´t have to take care in main loop.
It´s as simple as writing bytes into SRAM. (That´s why I call it simple)

In the remaining 99% of processing power there is plenty of time to react to other events. Like incoming data. So you can run both UARTs full duplex in the same time without missing a byte.

Klaus
 
  • Like
Reactions: bar_

    bar_

    Points: 2
    Helpful Answer Positive Rating
Hi,



It´s a point of view. I like using interrupts BECAUSE it makes the code SIMPLE.
Additionally it generates a fixed UART timing. And in your case I assume it safes 99% processing power. (In other words: it needs only 1% processing power)
In main loop I just write the bytes to be transmitted in a software buffer. The rest of transmitting is made by the interrupt routine. But I don´t have to take care in main loop.
It´s as simple as writing bytes into SRAM. (That´s why I call it simple)

In the remaining 99% of processing power there is plenty of time to react to other events. Like incoming data. So you can run both UARTs full duplex in the same time without missing a byte.

Klaus

I agree Mr.klaus , it depends on point of view. i try to avoid interrupts most of the time, but it is very useful in saving processor power as you say. actually i'm not familiar with interrupts that's the actual reason. i will try to do the same by using interrupts on the later versions of this code.
 

Dear all,

I have managed to run the software uart without using interrupt successfully.
Now i'm trying to write the same using timer 2 interrupt on 16F877a controller.
but the code outputs junk result on proteus. can anybody help to solve the problem? below is the code

Code:
#include "16f877a.h"
#use delay(clock=20000000)
#define Baudrate 9600                    //bps
#define set_rx_tx_direc set_tris_c(0b10000010)        // UART RX pin direction register
#byte port_c = 0x07
#bit UART_RX = port_c.1
#bit UART_TX = port_c.0
short stop_bit = 0;
char bit_transmitted = 0;
char DataValue = 0;
#int_timer2
void tmr2_isr()   // timer 2 ISR
{   
   if((DataValue != 0) && (bit_transmitted < 8))
   {      
      if(((DataValue >> bit_transmitted) & 0x01) == 0x01) //if bit is high
         {
            UART_TX = 1;
         }
         else      //if Bit is low
         {
            UART_TX = 0;
         } 
         bit_transmitted++;
   }   
   if(stop_bit == 1){UART_TX = 1; stop_bit = 0;}
   if(bit_transmitted == 8)
   {
   DataValue = 0; 
   bit_transmitted = 0;   
   stop_bit = 1;
   }   
set_timer2(0); // zero the timer 2 register to minimize error
}

void main()
{         
   set_rx_tx_direc;  //set direction of C0 as tx, C1 as rx
   enable_interrupts(GLOBAL);
   enable_interrupts(INT_TIMER2);
   setup_timer_2(T2_DIV_BY_4, 130, 1);  // timer 2 will overflow at period register value 130, i.e at 104uS
   UART_TX = 1;  // tx always ON
   delay_ms(3);
   while(1)
   { 
   DataValue = 'k';  //chr to be sent
   set_timer2(0);    // set timer 2 register to zero
   UART_TX = 0;    // set start pulse
   delay_ms(100);
   }
}

i'm trying to send the character 'k' on pin C0. but the result is shown in below image Capture.JPG

note the timing in the CRO. it is 104uS only and it seems it is transmitting the signal correctly. i had changed the bps to 1200 also but there is no valid data displayed on the terminal.

somebody help ..thanks in advance

Bar_
 

Hi,

I see about 120..125us / bit, not the expected 104us.


Klaus
 
  • Like
Reactions: bar_

    bar_

    Points: 2
    Helpful Answer Positive Rating
Mr. Klaus, jus now i had verified the timing using the graph ...its 115uS/bit.

But actually the problem is in the line set_timer2(0) in the ISR.

i removed that line and the code now works fine.

right now transmitting using interrupt working fine, but when i try to include the receiving code without interrupt it is not working. i had written to receive a character from virtual terminal and transmit the same to terminal in proteus. but when i press any character i receive junk data. ex: i pressed 'k' and i received '.' the timing diagram and code is shown below. what might be the mistake. ?
Capture.JPG
the yellow line is transmitting and blue line is receiving.
the code is below:

Code:
#include "16f877a.h"
#use delay(clock=20000000)
#define Baudrate 9600                    //bps
#define one_bit_delay (1000000/Baudrate)              //one bit delay
#define DataBitCount 8                                // no parity, no flow control
#define set_rx_tx_direc set_tris_c(0b10000010)        // UART RX pin direction register
#byte port_c = 0x07
#bit UART_RX = port_c.1
#bit UART_TX = port_c.0
short stop_bit = 0;
char bit_transmitted = 0;
char DataValue = 0;
char rcv_byte = 0;
short byte_sent = 0;
char rcv_data = 0;
char i = 0;   
#int_timer2
void tmr2_isr()
{   
   if((DataValue != 0) && (bit_transmitted < 8))
   {      
      if(((DataValue >> bit_transmitted) & 0x01) == 0x01) //if bit is high h = 0b01101000
         {
            UART_TX = 1;
         }
      else      //if Bit is low
         {
            UART_TX = 0;
         } 
      bit_transmitted++;
   }   
   if(stop_bit == 1){UART_TX = 1; stop_bit = 0; byte_sent = 1;}
   if(bit_transmitted == 8)
   {
   DataValue = 0; 
   bit_transmitted = 0;   
   stop_bit = 1;
   }   
//set_timer2(0);  //this line makes the code emits junk data on the serial port - 
//timer functions used inside the timer ISR causes junk data and code crash
}
char rx_data(void)
{
   while(UART_RX==1); //wait for start bit
   delay_us(one_bit_delay);  //start bit
   delay_us(one_bit_delay/2);   // Take sample value in the mid of bit duration
   for(i = 0; i < DataBitCount; i++)
   {
      if(UART_RX == 1)   //if received bit is high
      {
         rcv_data = ((rcv_data >> 1) | 0x80);
      }
      if(UART_RX == 0) //if received bit is low
      {
         rcv_data = ((rcv_data >> 1) | 0x00);
      }
      delay_us(one_bit_delay);
   }   
   if(UART_RX == 1)  // Check for stop bit     //Stop bit should be high
   {
      delay_us(one_bit_delay/2);
      return rcv_data;
   }
   else //some error occurred !
   {
      delay_us(one_bit_delay/2);
      return 'r';      
   }
}

void tx_data(char ch)
{
   DataValue = ch;
   set_timer2(0);
   UART_TX = 0;
   while(!byte_sent);
   byte_sent = 0;
}
void main()
{         
   set_rx_tx_direc;  
   enable_interrupts(GLOBAL);
   enable_interrupts(INT_TIMER2);
   setup_timer_2(T2_DIV_BY_4, 130, 1);
   UART_TX = 1;
   delay_ms(3);
   while(1)
   { 
   //tx_data("hello_world\n\r");
   rcv_byte = rx_data();
   delay_ms(100);
   tx_data(rcv_byte);
   }
   
   
}
 

I'm not certain of the CCS syntax but my calculation for timer 2 to give 104uS interrupts is:
Prescaler = 1:1
Postscaler = 1:3
Timer start value = 173

which should cause an interrupt every 103.8us (9634 bauds which is well within tolerance).

As that is one bit period and 10 bits have to be sent, I would expect to re-load the starting value for each bit rather than setting it to zero after the first bit. Don't forget the timer counts up and the interrupt is generated as it increments from 255 to 256 (zero in reality as it rolls over).

Brian.
 

hello Mr.Brian,

i dint understand how you got the value 103.8uS using your settings.

For 20Mhz crystal the clock value to timer will be 5Mhz(20/4 = 5) so timer 2 will increment on every 5uS. starting value of timer 2 is 173 , so count before interrupt is 83, i.e 256-173 = 83. so timer 2 will interrupt on every 83uS ,since postscalar value is 1:3 the interrupt time would be 83us X 3 = 249uS
can explain it please.

Bar_

sorry Mr. Brian,

timer 2 interrupt will occur on every 415uS(83uS X 5us) since post scalar is 1:3 the interrupt will occur every 1245uS (83 X 5 X 3)
am i right??

Bar_
 
Last edited by a moderator:

The timer2 not how it works, see the datasheet:

The Timer2 module has an 8-bit period register PR2. The TMR2 register value increments from 00h until it matches the PR2 register value and then resets to 00h on the next increment cycle.
....
The match output of Timer2 goes through a 4-bit postscaler (which gives a 1:1 to 1:16 scaling inclusive) to generate a Timer2 interrupt.


So the right values for timer2 to give 104uS interrupts are:
Prescaler = 1:1
Postscaler = 1:3
PR2 value = 173

Explanation:
For 20Mhz crystal the clock value to timer will be 5Mhz (20/4 = 5) so timer2 increments on every 0.2uS (!), with PR2=173 a match is occured on every 34.6 uS (173*0.2), so the interrupt (using 1:3 postscaler) is generated on every 103.8 uS (34.6*3).
 
  • Like
Reactions: betwixt

    betwixt

    Points: 2
    Helpful Answer Positive Rating
Sorry, but I was a little sleepy last night.
To be more precise:
When TMR2 match to PR2 then the next clock cycle will miscarry (for TMR2 reset). Decrement by one the PR2 value to compensate this loss, ie use 172, not 173.
The calculation remains correct:
with PR2=172 the postscaler is incremented on every 34.6 uS (172*0.2 + 0.2),
 
Last edited:

Hello Mr. Zuisti,

i used 20Mhz crystal, Pre scalar 1:4, period register value 130, Post scalar value 1:1 and that gives an interrupt on every 104uS right??

now the code transmits fine using timer 2 interrupt but it outputs junk data when i receive and re-transmit the same byte.
I'm receiving the byte in main loop not using interrupt. where may be the problem?
below is the code
Code:
#include "16f877a.h"
#use delay(clock=20000000)
#define Baudrate 9600                    //bps
#define one_bit_delay (1000000/Baudrate)              //one bit delay
#define DataBitCount 8                                // no parity, no flow control
#define set_rx_tx_direc set_tris_c(0b10000010)        // UART RX pin direction register
#byte port_c = 0x07
#bit UART_RX = port_c.1
#bit UART_TX = port_c.0
short stop_bit = 0;
char bit_transmitted = 0;
char DataValue = 0;
char rcv_byte = 0;
short byte_sent = 0;
char rcv_data = 0;
char i = 0;   
#int_timer2
void tmr2_isr()
{   
   if((DataValue != 0) && (bit_transmitted < 8))
   {      
      if(((DataValue >> bit_transmitted) & 0x01) == 0x01) //if bit is high h = 0b01101000
         {
            UART_TX = 1;
         }
      else      //if Bit is low
         {
            UART_TX = 0;
         } 
      bit_transmitted++;
   }   
   if(stop_bit == 1){UART_TX = 1; stop_bit = 0; byte_sent = 1;}
   if(bit_transmitted == 8)
   {
   DataValue = 0; 
   bit_transmitted = 0;   
   stop_bit = 1;
   }   
//set_timer2(0);  //this line makes the code emits junk data on the serial port - 
//timer functions used inside the timer ISR causes junk data and code crash
}
char rx_data(void)
{
   while(UART_RX==1); //wait for start bit
   delay_us(one_bit_delay);  //start bit
   delay_us(one_bit_delay/2);   // Take sample value in the mid of bit duration
   for(i = 0; i < DataBitCount; i++)
   {
      if(UART_RX == 1)   //if received bit is high
      {
         rcv_data = ((rcv_data >> 1) | 0x80);
      }
      if(UART_RX == 0) //if received bit is low
      {
         rcv_data = ((rcv_data >> 1) | 0x00);
      }
      delay_us(one_bit_delay);
   }   
   if(UART_RX == 1)  // Check for stop bit     //Stop bit should be high
   {
      delay_us(one_bit_delay/2);
      return rcv_data;
   }
   else //some error occurred !
   {
      delay_us(one_bit_delay/2);
      return 'r';      
   }
}

void tx_data(char ch)
{
   DataValue = ch;
   set_timer2(0);
   UART_TX = 0;
   while(!byte_sent);
   byte_sent = 0;
}
void main()
{         
   set_rx_tx_direc;  
   enable_interrupts(GLOBAL);
   enable_interrupts(INT_TIMER2);
   setup_timer_2(T2_DIV_BY_4, 130, 1);
   UART_TX = 1;
   delay_ms(3);
   while(1)
   { 
   //tx_data("hello_world\n\r");
   rcv_byte = rx_data();
   delay_ms(100);
   tx_data(rcv_byte);
   }
   
   
}

below is the simulation screen shot

Capture.JPG

i'm sending the character 'k' but i receive '.'

also is it possible to create a full duplex software uart ??


thanks in advance

Bar_
 
Last edited:

That's another setting that gives the same time period but use 131 to account for the extra reset time.

Of course a full-duplex UART is possible but it takes some thought as to how you check the incoming bits. You have two options, to monitor for the receive start bit, wait 1.5 bit periods then sample the next 8 single bit periods, optionally checking if the 9th bit is the stop bit -or- you can oversample at several times the bit rate and work on majority sampling. The over-sampling method is considerably more complicated to code but is more resilient to interference and baud rate differences. Over-sampling is the method used in hardware UART revceivers but they don't have to do something else at the same time!

The 1.5 bit method is easiest to code and normally adequate for wired connections. The idea is to sense the beginning of the start bit and call that "time zero", you then wait for a period equal to 1.5 bit lengths. The sets your next samplng point in the middle of the first data bit. You repeat 8 more times using 1 bit length so in theory you sample in the middle of bit 1, 2 3 and so on. The final sample will be in the middle of the stop bit so if you want to, you can confirm it is the correct state as a verification that the framing was successful. It is far easier to find the beginning of the start bit if you use the INT pin or IOC feature but that isn't available on PORTC.


Brian.
 
  • Like
Reactions: bar_

    bar_

    Points: 2
    Helpful Answer Positive Rating
i'm sending the character 'k' but i receive '.'

Have you really copied/pasted these characters from terminal to here? The binary value of both is quite different, therefore, if your code was really working, I would expect that at least the bits situed at the middle of the byte received were consistent - assuming a loss either at the beginning or at the end of the bit stream.

01101011 k
00101110 .
 

Status
Not open for further replies.

Similar threads

Part and Inventory Search

Welcome to EDABoard.com

Sponsor

Back
Top