|
|
View previous topic :: View next topic |
Author |
Message |
kd5uzz
Joined: 28 May 2006 Posts: 56
|
Canbus vs. I2C vs. Serial via shared TX/RX and enable line |
Posted: Sat Apr 26, 2008 4:11 pm |
|
|
I designed a board that talks to a master via the built in UART. It has worked very well for many months. I need to add a few more boards to the system, and I want them to all be able to talk to one another. I am unsure of what technology to use for this purpous. CanBus looks good because it is a well known and tested technology. It would allow expansion for many years. The downside is that it needs extra hardware (I am aware there are a few 18 series pics w/ CAN hardware built in, but would rather use what I already have). I2C would be ok, except there seem to be issues when used in a multimaster enviroment. I have concidered designing my own solution where the PICs would use the same pin for TX and RX. When a pic wants to send a message it would bring a pin low (A4, open collector output?). If the pin was already low it would know the line was busy. The problem is that I wouldn't be able to use the HW UART (and the interrupts it generates). Maybe I could use RB0 and the B0 change int to detect inputs.
I should mention that I will also be using an XBee radio for remote serial communications, although I could have a PIC perform the conversion.
Cost and complexity to impliment is also an issue (and another shot against CanBus)
Thoughts? Suggestions? What have you done before? |
|
|
RLScott
Joined: 10 Jul 2007 Posts: 465
|
Re: Canbus vs. I2C vs. Serial via shared TX/RX and enable li |
Posted: Sat Apr 26, 2008 7:06 pm |
|
|
You could use the hardware UART and a shared TX/RX line as long as you use some separate method of collision avoidance. The TX pin can be converted to open collector by adding a transisitor buffer (or an open collector gate that is external to the PIC). Make the buffer non-inverting and it can be connected directly to all the RX pins without any additional buffering.
As for collision avoidance, use a separate GPIO pin. Before any node is allowed to send, it must first "acquire" the bus by pulsing the GPIO pin in a pattern that is unique to that node. The pulses are not done by writing to the port but rather to the TRIS register - to switch between input and output. Whenever you switch to input, check that the pin is high. If it passes this test for the entire pattern, and if no RX characters have been received during that time, then you have acquired the right to transmit. That right extends until there has been no activity on the TX bus for a certain time-out period. That time-out period must be shorter than the time to send your unique pattern on the GPIO pin or else the protocol can fail. Therefore the unique pattern must be longer than one Serial character time. I know this is something like a software UART, but it does not require writing any RX code.
Robert Scott
Real-Time Specialties |
|
|
kd5uzz
Joined: 28 May 2006 Posts: 56
|
|
Posted: Sat Apr 26, 2008 7:20 pm |
|
|
Thank you for the indepth response. I will need to give some more thought to your idea of sending a pulse pattern to request transmit rights. I don't think I would have ever though of that.
Just to clarify, you say the UART can use a shared TX/RX line, is that to say I can specify the same pin in the #use rs232 statement, or that I am able to connect them to the same wire/trace on the board?
It seems I have some experimenting to do. I recently gave away all of my development boards, I'll need to make a few more to test this. |
|
|
foodwatch
Joined: 18 Apr 2006 Posts: 66
|
|
Posted: Sat Apr 26, 2008 7:39 pm |
|
|
I do this all the time. I recommend using the RS485 bus along with the built in uart. You can use a max1487 to do the communicating and you can turn them all open state except when one needs to talk. I use master slave polling and poll the slaves as often as necessary. You can use the built in uart along with two enable lines..one for tx and one for rx. This way you can do it all on 2 wires. I use cat 5 cable and carry the supply voltage along the cable also. Hope this helps. |
|
|
RLScott
Joined: 10 Jul 2007 Posts: 465
|
|
Posted: Sat Apr 26, 2008 9:07 pm |
|
|
kd5uzz wrote: |
Just to clarify, you say the UART can use a shared TX/RX line, is that to say I can specify the same pin in the #use rs232 statement, or that I am able to connect them to the same wire/trace on the board?
|
No, if you want to use the hardware UART you have to use the TX and RX pins that the particular PIC requires for them. Each TX pin needs its own external open-collector non-inverting buffer. Then on the other side of all those buffers you connect them all to a single wire, which also goes to all the RX pins on all the PICs.
Or you can do what foodwatch suggested and use standard RS-485. Then collision avoidance is accomplished by a strict high-level protocol whereby every node knows who the master node is and only the master node can initiate a transmission. It might be possible to transfer the title of "master" to a different node, but only by the current master declaring it so. Normally RS-485 has one master and a number of slaves that speak only when spoken to. Then there won't be any collisions.
Also you must provide an additional control output to enable the RS-485 buffers. Fortunately the CCS library contains functions to do this automatically (I think).
Robert Scott
Real-Time Specialties |
|
|
Guest
|
|
Posted: Sun Apr 27, 2008 1:37 am |
|
|
I don't know the max1487 but usually the RS485 drivers have one Tx and one Rx drivers that can be activated independently.
In order to create a communication bus, the TXenable and RXenable pins are connected together as well as the TX and RX pairs (a lot of drivers have these connections made inside ) Then a master-slave or multi-master protocol must be added.
Dimmu |
|
|
foodwatch
Joined: 18 Apr 2006 Posts: 66
|
|
Posted: Sun Apr 27, 2008 8:15 am |
|
|
When I made my suggestions, I failed to ask one critical question... What is the maximum distance between the two furthest boards that must talk to each other? I2C is designed for communications chip to chip over very short and controlled distances. If your boards are in the same box, I2C would be a good choice but you will need to follow a protocol. RS485 can be implemented the same way as RS232 but it will work reliably out to several thousand feet. The Max1487 is a more rugged and lower current version of the old 75176. You don't need to follow any rigid protocols. Simply control the transmit and receive enable separately rather than together. This allows you to mute the sending PIC's receive line so you don't have to worry about interrupts caused by the sending PIC's hearing its own transmission. Collision detection methology is all according to how often you use the bus and whether one pic can be made to be the host. In my system. I use one as the host and give each of the others an address. Then write "rs232" code to have the slaves respond to a simple address. A very effective trick is to encode all your data as ascii hex bytes then you have all the rest of the binary values to use as control functions. I use SOH (dec001) to begin all transmissions, then EOT(dec004) to end. Since the data is all in the form 00 to FF the code to detect messages on the bus is very simple. It all depends on the data volume and frequency on the bus. With this method, you don't need to follow any rigid protocol specifications... |
|
|
kd5uzz
Joined: 28 May 2006 Posts: 56
|
|
Posted: Sun Apr 27, 2008 12:33 pm |
|
|
In my current project they would be no more than a foot or two apart. Even in future projects I can't imagine them ever being more than 5 or 10 feet apart. Anything more than that and I would switch to wireless or something better suited to long distances. My main requirement right now is that they are all able to talk to one another, at any time.
Thank you all for your input. It has been enlightening.
Much of the system has yet to be designed, as I expected the design to be dependent on this. I envision a system where there is a 'master' device that does much of the command and control of other devices, but I want to be able to short circuit the 'master' when conditions warrent it. An example would be when a physical world condition exists that must be instantly reacted to to prevent damage. In this case the board that reads sensors would send an estop command to the motor controller to stop everything. It would then notify the 'master' of the condition. I also have some PC software that I use to sniff the data on the bus and I want to be able to inject commands as needed. |
|
|
newguy
Joined: 24 Jun 2004 Posts: 1909
|
|
Posted: Sun Apr 27, 2008 1:19 pm |
|
|
I've been using CAN for quite a while and I really love it. It sounds like it may be overkill for your situation, but the implementation is very straightforward. It's also robust - very, very robust. The CAN h/w inside the PIC takes care of most fault conditions, and will automatically retry failed transmissions. I absolutely love CAN.
With CAN there is no bus master - they're all peers. The way they communicate is via the message ID. The message ID, in extended addressing mode, is 31 bits long. I've divided those 31 bits into various fields, one for message originator, one for message destination (either a specific processor or a broadcast to all), one for message purpose (mainly for acking/handshaking during multi-message transfers), and another field for the actual message purpose. You then set up the CAN receive filters in your various processors to look for messages that are only destined for that processor, and for the broadcast messages. Communication is then very straightforward. |
|
|
jma_1
Joined: 08 Feb 2005 Posts: 147 Location: Wisconsin
|
|
Posted: Sun Apr 27, 2008 9:02 pm |
|
|
kd5uzz,
I'm with newguy on using a CAN bus.
If you could modify your hardware/software slightly to use SPI, you can interface a standalone chip like the MCP2515 from Microchip. You would still need a transceiver chip, but the processing of the CAN could be offloaded to the chip while retaining the functionality of your present setup. This assumes you could switch from I2C to SPI -> big assumption. The built in hardware collision detection, retransmission, and message priority make CAN very attractive and easy to implement.
JMA |
|
|
kd5uzz
Joined: 28 May 2006 Posts: 56
|
Home grown solution |
Posted: Thu May 01, 2008 8:54 am |
|
|
Thanks for all the input. It has given me much to think about. After some consideration I decided to see if I could 'build my own.'
I would like input on the concept, and if I am being unrealistic on timing requirements. Specifically, I am wondering if I am giving the other devices the time needed to process the data.
The image below is what ENABLE should look like while sending a packet. The 32us delay near the beginning is variable, it is determined by the device address and the number of failed transmissions. It can never be more than 32, plus 2 failures (34us).
ENABLE is bin B0.
Data is transmitted via a software serial port on pin B1 (TX and RX use the same pin).
On the 16f876a VCC, VDD, ENABLE (B0), and Data (B1) are all next to one another. It is my intention to use that to my advantage by creating two pin headers to allow me to daisy chain devices.
Below is my pseudo code, as well as an image showing the waveform of ENABLE.
I have written code that should perform these functions, but have not been able to test it. It is included below.
Any input is welcome.
Code: |
ENABLE is Pin RB0. RB0 has a single pullup resistor.
TX
--
There is an array of 9 chars to be sent.
loop for 40us
is ENABLE high for the entire time?
NO://bus is busy, start over
Yes: //request control of bus
disable INT_EXT //don't want to trip our own RX routine
ENABLE = Low
Wait MyDelay
ENABLE = Float
Loop for 10us
is ENABLE high for the entire time?
NO: //someone w/ a higher priority wants it
// increase MyDelay, start over
Yes: //The bus is mine
ENABLE = low //assert control over bus
Wait 35us //get everyone's attention
loop 9 times (T)
Toggle ENABLE
Send Byte (T)
wait 25us
//I'm done
ENABLE = float
return
enable INT_EXT //reenable RX routine
RX
--
Stage starts as START_WAIT, and is static
Count is static
This is executed by #INT_EXT, every time RB0 changes state.
Switch Stage
case START_WAIT
if ENABLE is low
loop for 35us
is ENABLE low for the entire time?
NO: //then this isn't the start of a packet, return
YES: stage = RX_MODE, return
case RX_MODE
if kbhit()
RX Byte
Buffer[++Count] = Byte
If Count = 9 //end of packet
DoParse = TRUE
Count = 0
Stage = START_WAIT
return
|
Code: |
#include "16f876a.h"
#fuses HS,NOWDT,NOPROTECT,NOBROWNOUT,NOLVP
#use delay(clock=20000000)
#use rs232(stream=Bus,baud=57600, xmit=PIN_B1, rcv=PIN_B1)
#use rs232(stream=COM1,baud=19200, xmit=PIN_C6, rcv=PIN_C7)
#define ENABLE PIN_B0
#define START_WAIT 2
#define RX_MODE 4
#define LISTENFORACTIVITY 40
#define ATTENTIONDEVICES 35
#define CHARTIME 25
#define ENABLEisLOW !input(ENABLE)
#define ENABLEisHIGH input(ENABLE)
#define ENABLEis input(ENABLE)
#define VERIFYTIME 10
#define NUMBYTES 9
#define MYIDADDRESS 1
#define STARTBYTE '$'
#define ENDBYTE '\n'
#define PARSEME 0
int MyID,MyDelay,ParsePacket;
char RXBuffer[NUMBYTES];
char TXBuffer[NUMBYTES];
void Main(){
int EnableWas;
MyID = read_eeprom(MYIDADDRESS);
MyDelay = MyID;
fprintf(COM1,"\n\rJechBus Access Control 2.1 proof of concept\n\r");
fprintf(COM1,"MyID: %U\n\r", MyID);
//enable_interrupts(INT_EXT);
//enable_interrupts(INT_RDA);
enable_interrupts(GLOBAL);
while(TRUE){
if (ENABLEis == EnableWas){
//hasn't changed
} else {
EnableWas = ENABLEis;
fprintf(COM1,"ENABLE changed\n\r");
}
}
}
//rx
#int_ext
void RB0_ISR(){
static int Stage, Count;
int Counter;
switch (Stage){
case START_WAIT: //havn't seen an attention pulse
for (Counter=1;Counter<=ATTENTIONDEVICES;++Counter){
if (ENABLEisHIGH){ //not start of packet
return;
}
delay_us(1);
}
//if we get here then ENABLE has been low for ATTENTIONDEVICES microseconds
//must be about to start a packet
Stage = RX_MODE;
return;
break;
case RX_MODE: //we've seen an attention pulse
//time to start RXing data
Count++; //we increase this so that if we get out of sync we will eventually reset (would miss current and next packet)
if (kbhit()){
RXBuffer[Count] = fgetc(Bus);
if (Count == 9){
Count = 0;
Stage = START_WAIT;
if ((RXBuffer[1] == STARTBYTE) && (RXBuffer[NUMBYTES] == ENDBYTE)){
RXBuffer[PARSEME] = TRUE;
}
}
}
return;
break;
}
}
//tx
int SendPacket(){
int Counter = 0;
int EnableState = 0;
for (Counter=1;Counter<=LISTENFORACTIVITY;++Counter){
if (ENABLEisLOW){ //someone is using the bus
return FALSE;
}
delay_us(1);
}
//ENABLE has been High for LISTENFORACTIVY microseconds
//that means no one else has the bus
disable_interrupts(INT_EXT); //don't want to trigger our own rx routine
//ask for control of the bus
output_low(ENABLE);
delay_us(MyDelay);
output_float(ENABLE);
//see if we have it
for (Counter=1;Counter<=VERIFYTIME;++Counter){
if (ENABLEisLOW){
enable_interrupts(INT_EXT);
MyDelay++; // increase my priority, eventually I'll be able to TX because I'll have the highest priority
return FALSE; //someone is using the bus
}
delay_us(1);
}
// noone has tried to take control, we have control
// time to get everyone attention
output_low(ENABLE);
delay_us(35);
//35us of low means that someone is sending data
for (Counter=1;Counter<=NUMBYTES;++Counter){ //loop through the buffer
if (ENABLESTATE == 0){ //toggle ENABLE (RX is triggered on state change)
output_high(ENABLE);
} else {
output_low(ENABLE);
}
EnableState = !EnableState;
fputc(TXBuffer[Counter],Bus); //send the byte
delay_us(CHARTIME); //allow other devices time to proccess data
}
output_float(ENABLE);
MyDelay = MyID;
enable_interrupts(INT_EXT);
return TRUE;
}
|
|
|
|
RLScott
Joined: 10 Jul 2007 Posts: 465
|
Re: Home grown solution |
Posted: Thu May 01, 2008 9:11 am |
|
|
It looks like you capture the bus by pulling B0 low. Then, some time later, you check to see if you are alone in capturing the bus by letting B0 go high for a short time and then seeing if it does go high. What if two devices do that at the same time and both let B0 go high at the same time? It will appear to both of them that they both have the bus to themselves. If you want to do something like that, then perhaps you could assign a unique address to each node on the bus. Then when a device wants to capture the bus, it could let B0 go high not just once, but several times, in time with a pattern determined by the unique node address. That way it would be impossible for two devices to both pass the "verification" stage, because at some point in time, one device will let B0 go high but another device will not.
After re-reading your posting, I see that you have already done that by making the initial pull-down variable in length. That could work, provided you have allowed for the worst-case timing for when a competing device samples the enable.
Robert Scott
Real-Time Specialties |
|
|
kd5uzz
Joined: 28 May 2006 Posts: 56
|
|
Posted: Thu May 01, 2008 12:24 pm |
|
|
There is an image in this post that is hosted on a machine on campus. Is anyone able to see it? |
|
|
foodwatch
Joined: 18 Apr 2006 Posts: 66
|
|
Posted: Thu May 01, 2008 12:46 pm |
|
|
why not just do straight master->slaves but poll many times each second for changes? There is no chance for collision and you won't have to follow any exotic overhead intensive architecture any you will never miss an event. If you are controlling a machine and there is danger of a data collision causing a missed packet, master slave may be the safest. Use a simple architecture like rs232 via rs422 or rs485 hardware. |
|
|
kd5uzz
Joined: 28 May 2006 Posts: 56
|
I give in |
Posted: Tue May 06, 2008 3:24 pm |
|
|
After two promising starts I've reexamined CANBUS. I'll be making the switch to CANBUS via 18 series PICs. Thanks for all the help everyone. Now I'm off to research CCS + CANBUS. This will allow me to put this part of this project down and move on to a few other things. |
|
|
|
|
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
|