Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Multidrop communication using PIC

Status
Not open for further replies.

haxan

New Member
Hi, can anyone please help me with multidrop communication using several PIC chips on one single bus with RS-485 transceiver.

I dont know how to send address byte using 9 bit UART. I am using the UART C18 library.

I want two function like

sendaddress(adr)

and

senddata(data)

sendaddress should also check if the addressee was found on the BUS or not.

Can anyone please help me.
 
There are a few ECAN routines available on microchip website but i am unable to get a hold of them. Just too complex for me.

I am trying to make my own controller area network using RS-485.
I am able to send data to the controller i want to communicate with, however am having trouble receiving data back from it.

that means communication from Master to Slave is perfect now. The only problem is getting data back from Slave towards master in which case i have to revert the Slave to Master and Master to Slave. While swapping these nodes, i get data logic error in simulation which means at some point when i swap the nodes, there comes a time when either both are in reception or transmission mode. This is reponsible for the corruption of data :(
 
Assuming you have 1 master then this is not difficult. You setup the pics in address mode and an interrupt will happen whenever a byte is received with the address bit set. Your interrupt checks the address and decides whether to keep listening or ignore until another address byte comes along.

Mike.
 
Thank you pommie but the problem i am facing is mostly when i want to receive data from Slave to Master.
Transmission from Master to Slave has been done. I have also done reception but i get too many glitches in data.

The problem mostly occurs when i make the Master go into reception mode and Slave in transmission mode. In this conversion, i usually see glitches of data on oscilloscope.
I cannot figure not how to syncronize the RS-485 transceivers together.

To check i tied one transceiver's enable pin with another transceiver's enable pin via a NOT gate. This works fine but i do not want to put a NOT gate in between as it will not work when more than two transceivers are connected.
 
Last edited:
This is from dim memory so anyone should feel free to correct me if I get anything wrong.

The RW control pin on the driver chip is connected to each pic along with the RX and TX lines. RW should be set to write prior to sending a packet and to read after the packet is sent. That is the often the only synchronization you need. Lets keep it simple for now.

You may be getting glitches because you have wired the R/W lines from two driver chips together. They should be under the control of the PIC associated with the driver chip.

Circuit cellar has a nice article on basic RS485 communication. I may have shown you this earlier but take another look.
https://www.electro-tech-online.com/custompdfs/2009/12/microsys_art_RS485.pdf

Most importantly this.
rs485-png.35978


3v0
 

Attachments

  • rs485.png
    rs485.png
    22.5 KB · Views: 2,248
  • rs485.gif
    rs485.gif
    13.3 KB · Views: 339
Last edited:
How can one make a timeout for serial communication, meaning that once the serial communication starts, also enable the timer. When the timer reaches a certain timeout value, the system should flag a timeout and go back to default running.

doing this i am having two problem:
Timer0 is not getting higher priority than serial interrupt so its not working
How can i unload values from STACK register (to waste the return value of function).

I have done all these kinda things in 8051 but am new to PIC and cannot find much info on this. Please help me.
 
...Timer0 is not getting higher priority than serial interrupt so its not working...

In general ISR code needs to be as short as possible.

If you are not in an ISR the high level RX interrupt will not prevent the low level timer interrupt.

How can i unload values from STACK register (to waste the return value of function).

The return value is removed from the stack when the function return is executed.

It seems like you may have poorly written ISR code.

The RX ISR should move the received character(s) to your input buffer. The ISR should not wait for characters to arrive. When it has no more characters in the RX buffer it returns. If the ISR has received a full packet/msg it should set a flag to be read by the non-ISR code which processes the packet.

The timeout can be polled or interrupt driven. If the RX ISR (polled) or timer ISR (interrupt) determines the timer has expired discard the current partial packet. Polling the timer in the RX ISR may simplify the code.

I hope this helps. If not post the code we may be able to help you with it. We at least need to see the initialization code and ISR code. Post the entire source if it is not too long.

3v0
 
Below is the entire code i have written for the Slave Node. The problem with my program is that the Master Sends the Address and Command (Instruction). This instruction tells the Slave the number of bytes to be received further (that the master will transmit) and to transmit (if any) certain data back to master.


#include <p18cxxx.h>
#include <delays.h>
#include <stdlib.h>
#include <stdio.h>
#include <usart.h>
#include <timers.h>
#define MAX_EN PORTAbits.RA0
#define TX_MODE 1
#define RX_MODE 0
#define MY_ADDRESS 'a'


#define CM_CHANGE_POWER 100 // Change Power Source [0, 1, 2 ... ]
#define CM_UPDATE_TOGGLES 101 // Change Toggles to ones sent from Master
#define CM_UPDATE_DIMMERS 102 // Change Dimmers to ones sent from Master
#define CM_SEND_TOGGLES 103 // Send Toggles to Master
#define CM_SEND_DIMMERS 104 // Send Dimmers to Master
#define CM_WRITE_EEPROM 105 // Write to EEPROM sent from Master
#define CM_READ_EEPROM 106 // Send EEPROM to Master
#define CM_MASTER 107 // Master On/Off
#define ACK 200 // Acknowlegdment
#define TEST 201 // Acknowlegdment
#define RX_MODE 0 // RA 484 En pin
#define TX_MODE 1 // RA 484 En pin

#define B1 PORTDbits.RD0
#define B2 PORTDbits.RD1
#define B3 PORTDbits.RD2
#define B4 PORTDbits.RD4
#define B5 PORTDbits.RD5
#define B6 PORTDbits.RD6

#pragma config WDT = OFF, LVP = OFF, OSC = HS, DEBUG = OFF

unsigned char receive_count = 0; // Num of Bytes to receive from MASTER
unsigned char transmit_count = 0; // Num of Bytes to transmit from MASTER
unsigned char receive_bytes[5] = {0};
unsigned char transmit_bytes[5] = {0};

void ParseInstruction(unsigned char);
void senddata(unsigned char);
void SlaveSetting(void);

unsigned long timercount = 0;

void T0_handler (void);
#pragma code T0_interrupt = 0x18
void T0_int (void)
{
_asm goto T0_handler _endasm
}
#pragma code
#pragma interrupt T0_handler
void T0_handler (void)
{

}


/* ~~~~ Interrupt Handler ~~~~ */
void rx_handler (void);
#pragma code rx_interrupt = 0x08
void rx_int (void)
{
_asm goto rx_handler _endasm
}
#pragma code
#pragma interrupt rx_handler
void rx_handler (void)
{
unsigned char c;
unsigned char i,j = 0;
if(PIR1bits.RCIF == 1)
{
c = ReadUSART();
if(c != 68){return;} // Compare Address in NINE Bit mode
TMR0H = 0; // Reset Timer0 to 0x0000
TMR0L = 0;
PIE1bits.RCIE = 0; // Disable Serial Interrupt
INTCONbits.T0IF = 0;
INTCONbits.T0IE = 1; // Enable Timer0 Interrupt for timeouts
RCSTAbits.RX9 = 0; // Read data byte now (without Nine bit)
while(!DataRdyUSART());
c = ReadUSART(); // Read Instruction

ParseInstruction(c);

for(i = 0; i < receive_count; i++)
{
while(!DataRdyUSART());
receive_bytes = ReadUSART(); // Fill received bytes
}
MAX_EN = TX_MODE;
TXSTAbits.TX9 = 0;
//Delay10KTCYx(254);
Delay10KTCYx(254);
for(j = 0; j < transmit_count; j++)
{
senddata(0x11); // Transmit required bytes
Delay1KTCYx(1);
}
senddata(ACK);
Delay1KTCYx(1);
//while(!DataRdyUSART());
//putrsUSART((const far rom char *)"SLAVE 1!");

}
if(INTCONbits.T0IF == 1 && INTCONbits.T0IE == 1)
{
timercount++;
if(timercount > 3){SlaveSetting();PORTAbits.RA3 = ~PORTAbits.RA3;}
PORTAbits.RA2 = ~PORTAbits.RA2;
INTCONbits.T0IF = 0;
}
//else{return;}
SlaveSetting(); // Go back to becoming Slave
}

unsigned char button = 0;

void senddata(unsigned char data)
{
//while(BusyUSART());
USART_Status.TX_NINE = 0;
WriteUSART(data);
while(BusyUSART());
}

void sendaddress(unsigned char data)
{
//while(BusyUSART());
USART_Status.TX_NINE = 1;
WriteUSART(data);
while(BusyUSART());
}

void process(void)
{
switch(button)
{
case 1:
sendaddress(68);
break;
case 2:
sendaddress(66);
break;
case 3:
sendaddress(70);
break;
case 4:
senddata(68);
break;
case 5:
senddata(66);
break;
case 6:
senddata(70);
break;
}
button = 0;
}


void main(void)
{
unsigned char i;
//OSCCON = 0x70;
ADCON1 = 0x0F; // make all AN (analog) pins digital (for bit ref)
TRISB = 0b00000000;
TRISA = 0b00000000; // First 5 bits (RA0-RA4) are inputs
TRISC = 0b10000000; // Tx and Rx to input (Tx input as per datasheet)
TRISD = 0xFF;
PORTA = 0;
PORTB = 0; // BUS to be set to 0 before PORTC to avoid junk to latch
PORTC = 0b00000000;
PORTD = 0x00;


// Assumes FOSC = 4 Mhz, 9600 bps.
OpenUSART (
USART_TX_INT_OFF &
USART_RX_INT_ON &
USART_ASYNCH_MODE &
USART_NINE_BIT &
USART_CONT_RX &
USART_BRGH_HIGH, 25
);

OpenTimer0 (TIMER_INT_OFF & T0_SOURCE_INT & T0_16BIT & T0_PS_1_1);

MAX_EN = RX_MODE;
RCONbits.IPEN = 1; // Enable interrupt priority
//IPR1bits.RCIP = 1; // Enable all high priority interrupts
INTCONbits.GIEH = 1;
PIR1bits.RCIF = 0; // Clear Receive Interrupt Flag

while(1){
if(B1 == 1){button = 1;while(B1);}
if(B2 == 1){button = 2;while(B2);}
if(B3 == 1){button = 3;while(B3);}
if(B4 == 1){button = 4;while(B4);}
if(B5 == 1){button = 5;while(B5);}
if(B6 == 1){button = 6;while(B6);}
process();
//USART_Status.RX_NINE = 0;
}
}

void ParseInstruction(unsigned char ins)
{
receive_count = 0;
transmit_count = 0;

switch(ins)
{
case CM_CHANGE_POWER:
receive_count = 1;
break;
case CM_UPDATE_TOGGLES:
receive_count = 1;
break;
case CM_UPDATE_DIMMERS:
receive_count = 1;
break;
case CM_SEND_TOGGLES:
transmit_count = 1;
break;
case CM_SEND_DIMMERS:
transmit_count = 1;
transmit_bytes[0] = 0x10;
break;
case CM_WRITE_EEPROM:
receive_count = 2; // Address and Data
break;
case CM_READ_EEPROM:
receive_count = 1; // Address
transmit_count = 1; // Data
break;
case CM_MASTER:
receive_count = 1;
break;
}
}


void SlaveSetting(void)
{
timercount = 0;
INTCONbits.T0IE = 0; // Disable Timer0 Interrupt for timeouts
RCSTAbits.RX9 = 1;
TXSTAbits.TX9 = 1;
PIR1bits.RCIF = 0;
MAX_EN = RX_MODE;
PIE1bits.RCIE = 1; // Enable Serial Interrupt
}
 
The main idea behind interrupts is that they keep the processor from waiting for various things. Yet you have written an ISR function full of waits and delays.

The processor is much much faster then the RS495 signal. The most you want to do here, is use the ISR to collect data to make a packet. You must not block waiting for characters to arrive. When you get a RX interrupt you move the character to your buffer. If there are no other characters recieved you return from the ISR. In general you do not wait for anything in an ISR.

while(!DataRdyUSART());
I do not want to see any delays in an ISR

Code:
 Delay10KTCYx(254);
This is bad too

Code:
        for(j = 0; j < transmit_count; j++)
        {
            senddata(0x11); // Transmit required bytes
            Delay1KTCYx(1);
        }
You need to rewrite this.

Code:
void rx_handler (void)
{
    unsigned char c;
    unsigned char i,j = 0;
    if(PIR1bits.RCIF == 1)
    {
        c = ReadUSART();
        if(c != 68){return;} // Compare Address in NINE Bit mode
        TMR0H = 0; // Reset Timer0 to 0x0000
        TMR0L = 0;
        PIE1bits.RCIE = 0; // Disable Serial Interrupt
        INTCONbits.T0IF = 0;
        INTCONbits.T0IE = 1; // Enable Timer0 Interrupt for timeouts
        RCSTAbits.RX9 = 0; // Read data byte now (without Nine bit)
        while(!DataRdyUSART());
        c = ReadUSART(); // Read Instruction

        ParseInstruction(c);

        for(i = 0; i < receive_count; i++)
        {
            while(!DataRdyUSART());
            receive_bytes[i] = ReadUSART(); // Fill received bytes
        }
        MAX_EN = TX_MODE;
        TXSTAbits.TX9 = 0;
        //Delay10KTCYx(254);
        Delay10KTCYx(254);
        for(j = 0; j < transmit_count; j++)
        {
            senddata(0x11); // Transmit required bytes
            Delay1KTCYx(1);
        }
        senddata(ACK);
        Delay1KTCYx(1);
        //while(!DataRdyUSART());
        //putrsUSART((const far rom char *)"SLAVE 1!");

    }
    if(INTCONbits.T0IF == 1 && INTCONbits.T0IE == 1)
    {
        timercount++;
        if(timercount > 3){SlaveSetting();PORTAbits.RA3 = ~PORTAbits.RA3;}
        PORTAbits.RA2 = ~PORTAbits.RA2;
        INTCONbits.T0IF = 0;
    }
    //else{return;}
    SlaveSetting(); // Go back to becoming Slave
}

Function senddata is weird too. I would use this. It allows the processor to continue up to the time that it needs to wait for a busy TX.

void senddata(unsigned char data)
{
while(BusyUSART()); /// uncomment this
USART_Status.TX_NINE = 0;
WriteUSART(data);
}[/CODE]

There is so much wrong with this code that I can not help but think you may be in over your head. At the very least you need to think about what you are doing.
3v0
 
Thank you 3v0 for all the feedback. Hopefully in my next post here i will have made the communication system without bugs. I have changed my entire code :) So far no problem. Will let you know if i hit some rock.
 
Is there any way to check that a node with a certain address is on the BUS WITHOUT timeout?

Timeout does not tell you that. Once a packet has started to arrive we start a timer. If that timer times out prior to the end of the packet we reject that packat.

It is possible to poll the timer instead of turning on the interrupt. Each time you enter the RX ISR you check the timer to see if it has timed out. If it has you discard the current packet.

If a packet is arriving we know which node it is from by looking at the TO address in the partial packet.


3v0
 
No sorry, i think you didn't understand my question.

What i am asking is that there can be like 50 nodes connected on BUS. Is there any way for the MASTER to know all their addresses and in case a new Node is connected, Master should scan again for new Nodes and detect it (Like in network discovery).

How do i detect if the Node i am trying to send data to exists or not (otherthan waiting for ACK signal and hitting timeout)
 
...
What i am asking is that there can be like 50 nodes connected on BUS. Is there any way for the MASTER to know all their addresses and in case a new Node is connected, Master should scan again for new Nodes and detect it (Like in network discovery).
In general you must scan to determine which nodes are present.

You could play this game. Setup all nodes to listen to a broadcast addr as well as their own. Send a broadcast ping msg telling all nodes to reply. Have each node on the net reply after a delay assigned to its address. After sending the ping the master watches the bus and records the replies from each node. It may not be worth the trouble.

...
How do i detect if the Node i am trying to send data to exists or not (otherthan waiting for ACK signal and hitting timeout)
You can not. You can keep a table of known active nodes. It would take some sort of magic to know a node is there without trying to communicate with it. But it pasys to reduce the timeout period to the shortest that works well for the network. You could read the value from the timeout counter when each packet is complete. Write a few lines of code to save the value if it is the greatest seen yet. Run the network for a while and then adjust your timeout accordingly. Or juse a scope to get a ballpark number.

There a other games you can play but in general simple is better.

3v0
 
okay thank you, i will try adding this ping to my broadcast command set and check with you again.

Also another problem that i found out is more of hardware. On the data BUS, if any of the controller is powered off and the RS-485 transceiver is still On, the complete BUS gets corrupted (data of other nodes stop working). Is there something i can do so that when a node is not active, it doesn't affect the overall data BUS. I have tried putting a pull down resistor with the EN pin of RS-485 transceiver but that doesn't help either. However it corrupts the signal if the controller of that node is switched on. Have also tried adding termination resistors (220 ohm) between A and B on transceiver of node which i switch off, still nothing.
 
Last edited:
. On the data BUS, if any of the controller is powered off and the RS-485 transceiver is still On, the complete BUS gets corrupted (data of other nodes stop working).
You are now outside of what I have done. Adding or removing a node from a working network is called Hot-Plugging. Not all transeceivers are able to do this cleanly. Check the datasheets.

The only connections between the transeceiver and the buss are the A and B pins and Gnd. It would be great if A and B go cleanly to high impedance when the transeceiver is powered on or off. I do not think this is the general case as TI talks about this as a feature of one of its RS485 transceivers.

It could be that you need to power up a node then connect the GND-A-B in that order. To disconnect unhook B-A-GND then power down. But the transceivers must be designed to do this.

3v0
 
Done :)

The library now contains broadcast commands, controller specific commands, network discovery of all nodes. Can send n bytes of data and also receive m bytes of data.
  1. Invalid command detection
  2. Timeout detection
  3. ACK successful/unsuccessful detection

Drawbacks:
  1. The only thing that is left is error detection in data bytes. Command bytes can be checked by invalid command detection. However if a bit flips and the command exists for even that pair, no correction for that yet.
  2. Will work for controllers with one USART only. (Tx RC6 and Rx RC7)

Well its enough for now i guess. Don't want to get messed up in CRC and other such stuff.

Thank you for your help 3v0 :D
 
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top