Wednesday, 30 November 2011

Serial/UART communication between two PICs (18F and 16F)

It's taken a long time and a lot of different approaches to get even simple serial communication between two PICs working. Perhaps hampered a little by using two different PICs (a 16F and an 18F series chip) but that really shouldn't make any difference. Serial/UART communication shouldn't be platform dependent!

We've tried serial/UART, SPI, I2C and even bit-banging and each time got different (non-working) results. It's too much to go into in this blog post, so we're just going to describe how we actually got a working prototype:

The first thing to note is that although we're using one of our favoured 18F4550 PICs, we couldn't use the USB  and UART together. So for this experiment, we're reading the data back off our 18F (master) chip via serial, using the excellent PICKit2 programmer and software.

In our first (failed) attempt at creating pic-to-pic communication, we had our 16F (slave) device monitoring it's input pins and simply broadcasting the results as an 8-byte message over UART. The idea was for the 18F (master) chip to read this serial stream at intervals, find the start of the message and decode the rest of it, so that it knows the state of the slave chip.

In theory this sounds fine. In practice, sending more than about 4 bytes to the 18F (master) chip caused it to lock up on the Hserin command. We even tried connecting the PICKit2 to the RX pin on the 18F and removed our slave chip altogether. Our tests confirmed the same thing happening - if we sent one or two bytes at a time to the 18F from the PICKit2 terminal, it successfully received them and sent a response message back. If we sent more than about five bytes at a time, the 18F simply stopped responding - apparently locking up at the point where it reads in the serial data
(we proved this by switching an LED on just before the Hserin command, and turning it off again immediately after).

So it appears that we have a set-up whereby we can receive data from our slave, one byte at a time, but the master can't simply "dip in" to a serial stream being "broadcast" by the slave. After reading through the datasheet, it seems that the 18F has only a two (or was it four) byte buffer so blasting more bytes into the buffer than it can handle is causing some sort of buffer overrun error.

We need some way of getting the master chip to ask for data, one byte at a time.
This is what we came up with:

The master chip sends a pin high then requests a byte from the serial buffer.
The slave chip monitors the state of the "handshaking" line. When the line goes high, and the previous line state is low, this represents a request from the master, so send one byte.
Because we're using hardware UART on the master chip, after the line goes high, the code waits until a byte has arrived in the serial buffer. Because the slave only sends a byte on a low-to-high transition on the handshaking pin, we don't have to worry about buffer under-runs or errors in the master chip.



Slave code
'4Mhz external clock
Define CONF_WORD = 0x3f31
Define CLOCK_FREQUENCY = 4

AllDigital
Config PORTB = Input
Config PORTD = Input
Config PORTC = Input
Config PORTA = Input

declarations:
       Dim linestate As Bit
       Dim lastlinestate As Bit
     
       Dim a1 As Byte
       Dim a2 As Byte
       Dim a3 As Byte
       Dim a4 As Byte
       Dim a5 As Byte
       Dim xorval As Byte
       Dim bytecounter As Byte
            
init:
       WaitMs 50
       Hseropen 9600
       WaitMs 50
            
       linestate = 0
       lastlinestate = 0
       bytecounter = 1
     
loop:
       'all the slave does is read a whole load of input pins
       'and write the response back to the master. The master chip
       'will decide which "row" is live, this chip just takes all
       'the "column" data and sends it back to the master.
     
       'RA0-5
       'RB1-7
       'RC0-5 (6&7 are TX/RX)
       'RD0-7
       'RE0-2
            
       'this should give us 6+7+6+8+3 = 30 inputs
       a1 = PORTA And 63 '(mask off bits 7+6)
       a2 = PORTB And 254 '(make off bit 0, handshake line)
       a3 = PORTC And 63
       a4 = PORTD
       a5 = PORTE And 7
                   
       'now stream the values as a response back to the slave
       'all messages begin 255,255 (since this sequence is not
       'possible in the input stream because we bitmask bytes 1,3,5)
     
       linestate = PORTB.0
       If linestate = 1 And lastlinestate = 0 Then
              'the host chip has asked for another byte of data
            
              'create the checksum value
              xorval = 0
              xorval = xorval Xor a1
              xorval = xorval Xor a2
              xorval = xorval Xor a3
              xorval = xorval Xor a4
              xorval = xorval Xor a5
              xorval = xorval And 127

              'decide which byte of the message to send
              '(the host has to ask for each message eight times for all 8 bytes)
              Select Case bytecounter
                     Case 1
                     Hserout 255
                     Case 2
                     Hserout 255
                     Case 3
                     Hserout a1
                     Case 4
                     Hserout a2
                     Case 5
                     Hserout a3
                     Case 6
                     Hserout a4
                     Case 7
                     Hserout a5
                     Case 8
                     Hserout xorval
                     bytecounter = 0
              EndSelect
            
              bytecounter = bytecounter + 1
       Endif
       lastlinestate = linestate
     
Goto loop
End




Master code

AllDigital

declarations:
       Dim a1 As Byte
       Dim a2 As Byte
       Symbol lineshake = PORTA.0
       Config PORTA.0 = Output
     
init:
       Low PORTA.0
       WaitMs 200
       Hseropen 9600
       WaitMs 200
     
loop:

       Gosub getnextbyte
       'two 255 = start of message
       Hserout 255
       Hserout 255
       Hserout a1
            
Goto loop
End

getnextbyte:
       'to get the next byte, we send the lineshaking
       'line high - this tells the slave to "release"
       'another byte. Once we have this byte, we drive
       'the lineshaking pin low to say we're done
       High lineshake
       Hserin a1
       Low lineshake
     
Return


After connecting up the slave to the master (the TX pin of the slave into the RX pin of the master, and RA.0 on the master to RB.0 on the slave) we did actually start receiving intelligible data from the master. So it looks like we've (finally) cracked pic-to-pic communication over serial.

It's probably a bit slower than it needs to be (since the master has to "poll" each byte from the slave) but it's fast enough for what we want - and once we crank the baud rate up from 9600 to about 57600, or even 115200, it should be more than quick enough to scan an entire game board (of not more than 16 x 28 = 448 squares) at a reasonable rate.

11 comments:

  1. Looks like more engineering than ought to be necessary for serial communications. The master shouldn't need to poll for bytes. But it looks like there's a basic misunderstanding here as to how serial I/O works generally. A receiver needs to be paying attention to the serial stream. It can't just "dip in" as you say. What is usually done is to write a short interrupt service routine on the receiving chip which runs "in the background" collecting bytes from the UART when they arrive and storing them in some sort of RAM buffer for the mainline code to examine (or I suppose ignore) at leisure.

    That way you don't lock up, overrun, or slow down to poll the other chip, and serial I/O works as you expect it to here. Managing the buffer is of course a separate detail.

    On the sending side, of course you need to have similar timing control to make sure the UART there is ready for each outgoing byte you're feeding it, but you can either do that manually (if the chip has nothing better to do during the time it's sending) or queue up the data in a buffer and have an interrupt routine feed that out to the port as it's ready.

    ReplyDelete
  2. You're quite right - someone on the PIC sim ide forum suggested using an interrupt on the receiver then some logic to decide whether to store the incoming data (in a buffer) or ignore it depending on the current "state" of the master device.

    I did actually try this out and get it working too. I've just not had time to write it up fully. But this method (above, with handshaking) also works so I'll leave it here as it could be amended for future development (e.g. having multiple handshake lines would allow multiple slave chips to be queried.)

    Thanks for your comments!

    ReplyDelete
  3. hello, I'm doing a RS232 communication between a pic and a pic 16f877a 18F4550, the 4550 pic is a master and a slave 877 pic, but in communicating data be checked, if I try to 16f 16f rs232 working properly and if I try 18f to 18f rs232 also works correctly, but if I try 16f to 18f is when the data is wrong, I change the speed of the slave but the error continues. I advise you to try. I'm doing my program in c + + with css

    thanks

    ReplyDelete
    Replies
    1. Are you using USB on the 4550 PIC?
      I don't think it's possible to (accurately) use both UART and USB on the same 18F4550 pic. If you're just using serial (TTL level, not RS232 which uses +/-12v ) and it works between two PIC devices in the same 16F family, there should be no reason why a 16F and an 18F can't talk to each other!

      I have, in the past, used internal oscillators in 16F PICs and this has messed up the serial data (it gets bitshifted by one or two places over time) and putting an external crystal on it solved the problem; but that wouldn't explain why 16F to 16F works and 16F to 18F doesn't!

      Are you using software or hardware UART?
      Obviously both chips need to be running the same baud rate. If you're using hardware UART on both chips, it should take care of framing and buffering and so on... drop me a mail and I might be able to look at your code (I use SourceBoost C and Oshonsoft BASIC)

      Delete
    2. This is my code, the 18F pic rs232 sends a number to the pic for 16F it receives and publishes on a lcd


      ///////////////////////////////////////////////////////////////////////////18F4550////////////////////////////
      #include <18F4550.h>
      #fuses HS,NOWDT
      #use delay(clock=20000000)
      #use rs232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8)
      #use fast_io(A)
      #include
      char ver[3];
      const int y=109;
      void main()
      {

      enable_interrupts(INT_RDA);
      enable_interrupts(GLOBAL);
      for(;;)
      {

      if (input(pin_a0)==1){
      itoa(y,10, ver);
      printf("%s#\r",ver);
      delay_ms(100);
      }
      }}

      /////////////////////////////////////////////////////////////////////pic 16F877A//////////////////////////
      #include <16F877a.h>
      #FUSES HS,NOWDT
      #use delay(clock=20000000)
      #use rs232(baud=9600,xmit=PIN_C6, rcv=PIN_C7,bits=8, stop=1)
      #include
      int vec[5],a;
      char version[];
      char x;
      #INT_RDA
      void RDA_isr()
      {
      output_low(pin_b1);
      output_high(pin_b2);
      output_high(pin_b3);
      x=' ';
      x=fgetc();
      while(x!='#')
      {
      x=fgetc();

      lcd_putc(x);
      vec[a]=x;
      a++;
      }
      }


      void main() {

      setup_adc(ADC_OFF);
      setup_psp(PSP_DISABLED);
      setup_spi(SPI_SS_DISABLED);
      setup_timer_0(RTCC_INTERNAL);
      setup_timer_1(T1_DISABLED);
      setup_timer_2(T2_DISABLED,0,1);
      setup_comparator(NC_NC_NC_NC);
      setup_vref(FALSE);

      enable_interrupts(INT_RDA);
      enable_interrupts(GLOBAL);
      lcd_init();
      for (;;) {
      output_high(pin_b1);
      output_low(pin_b2);
      output_low(pin_b3);

      }

      }

      Delete
    3. This comment has been removed by the author.

      Delete
  4. Are you really sure you're using RS-232? Or just making a TTL-level serial I/O connection between the chips? Is there a chance that you've mismatched the signal voltages between the two? Or allowed RF interference in the line? Not knowing the hardware involved makes this far harder to make suggestions about.

    What sort of errors are you getting? Framing errors? Properly framed data but just incorrect? Any pattern to what's wrong? How certain are you that the UARTS in each unit are truly at the same speed? Based on your crystal frequency, how accurate does that make the UART at your selected speed? (See the datasheets for that info).

    Those are some questions to consider which should help you narrow down the problem.

    HTH HAND

    ReplyDelete
  5. Hi, I could solve my problem and I am trying everything in hadware and turns to make a serial communication between pic 18f and 16f one, working with 20MHz, set it to
    the 18f
      # use rs232 (baud = 9600, parity = N, xmit = PIN_C6, rcv = PIN_C7, bits = 8)
    and 16f # use rs232 (baud = 2400, parity = N, xmit = PIN_C6, rcv = PIN_C7, bits = 8)
    this way everything works properly

    ReplyDelete
  6. I've not had chance to look over your code, but I'd be amazed if it does work as you suggested in your last comment.
    You absolutely must use the same baud rate on both chips - on the 18F you said you're using 9600 baud (this is the default for most PCs too) and on the 16F only 2400 baud.

    I'd recommend using 9600 baud on both.
    Then plug each individually into the PC (using the PICKit programmer in serial/UART mode) and check that they're both working at 9600bps. Finally see if they can communicate with each other.

    I can't believe you're getting accurate data transfer in both directions, on both PICs, with one running at 2400 and one at 9600bps!

    ReplyDelete
    Replies
    1. Chris, actually i've been through 18f programming recently and setting up the oscillator for 18f is little tricky. I believe he must have setup 18f 4x lower oscillator and the compiler unknowingly generates the 2400 bps on 18F.!!!

      Delete
  7. #include<16f877a.h>
    #device ADC=10
    #fuses HS,NOWDT
    #use delay(clock=4000000)
    #use rs232(baud=57600,parity=N,xmit=PIN_C6)
    #bit LCD_RS=0x8.2 //0x5.3
    #bit LCD_RW=0x8.0 //0x5.2
    #bit LCD_EN=0x8.3 //0x5.1

    #byte LCD_DATA=8

    #define LCD_STROBE() ((LCD_EN=1),(LCD_EN=0))

    void lcd_char(unsigned char c);
    int16 value,value1;
    char w,x,y,z,p,q;
    unsigned char str1[]="L:";
    void main()
    {
    set_tris_d(0x00);
    set_tris_a(0x0F);
    while(TRUE)
    [
    set_tris_a(0x0F);


    setup_adc(ADC_CLOCK_INTERNAL);
    setup_adc_ports (ALL_ANALOG);
    set_adc_channel(0);

    delay_ms(1000);
    value=read_adc();
    value1=value;
    z=value1%10;
    value1=value1/10;
    y=value1%10;
    value1=value1/10;
    x=value1%10;

    x

    if(x!=0)
    {
    lcd_char(x+0x30);
    }
    lcd_char(y+0x30);
    lcd_char('.');
    lcd_char(z+0x30);
    lcd_char('L');


    delay_ms(1000);



    this is my code for light intensity. by using this code i saw the result on LCD. can any one tell me by adding what i saw this result on pc. i am using rs232 for communication. i have max232 i know how to connect but what modification are needed in this code so i saw on pc.the LCD function have properly work

    ReplyDelete