|
|
View previous topic :: View next topic |
Author |
Message |
CF__878 Guest
|
I2C Hexapod robot problem |
Posted: Wed Oct 29, 2008 8:03 am |
|
|
Hello guys! I am Javier and I would like to thank everybody in this forum for the information that is available for all of us that need help. But it seems that I need a little bit of more help. I am making an Hexapod robot and I'm trying to control it in a distributed way, it means that every leg is controlled by a PIC and then a "Brain" controls all of them and communicates with the "human" interface. The servos are controlled by a a Timer1 routines that I made and they work well but the problem is when I try to implement the communication between the PICS. I tried two ways, one is a only master bus with slaves and I send them the positions for the servos, and it works well if I only communicate with one PIC, but when it is connected with another one the hole thing crashes and the servos don't move. The second way is using a multimaster system where when the slave has completed the servo's movement switches to master and sends the "Brain" a "ready" flag which means that the servo controller is ready for new movements. And in this case the problem is that the master can send information without a problem but when a slave changes to master the whole thing crashes again. I post the code for the multimaster system, can you help me? What I am doing wrong?
Code: | /////////////////////////////////////////////////////////////////////////
The code programmed in the leg's controller
////////////////////////////////////////////////////////////////////////
#include "16F88.h"
//#device adc=8
#FUSES NOWDT //No Watch Dog Timer
#FUSES HS //High speed Osc (> 4mhz)
#FUSES NOPUT //No Power Up Timer
#FUSES NOPROTECT //Code not protected from reading
#FUSES NODEBUG //No Debug mode for ICD
#FUSES NOBROWNOUT //No brownout reset
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOCPD //No EE protection
#use delay(clock=20000000)
#use i2c(MASTER, sda=PIN_B2, scl=PIN_B3, stream=I2CM)
#use i2c(SLAVE, sda=PIN_B1, scl=PIN_B4, address=0xB0, force_hw, stream=I2CS)
#use fast_io(A)
#priority timer1,SSP
#define servo1 PIN_A0
#define servo2 PIN_A1
const int16 Ticks4Window = 50000; // PWM Window for servo = 10 ms x 2 = 20 ms
const int16 Ticks4Minimum = 3000; // PWM High for Minimum Position = 0.7 ms (-90?)
const int16 Ticks4Center = 7500; // PWM High for Center Position = 1.5 ms (0?)
const int16 Ticks4Maximum = 12000; // PWM High for Maximum Position = 2.3 ms (+90?)
int1 flag_seq=0; //variable to indicate the ISR phase
int1 s_ID=0; //indicates which servo is being operated
int1 move=0; //indicates that the Master enables movement
int16 Ticks4NextISR=1000; // this variable contains the new timer1 prescaler value
int16 servo_duty[2]={7500,7500}; // variable that contains the timer "ticks" to complete the PWM pulse for each servo
unsigned int16 cycles_isr[2]={0,0}; //this is a counter for the numbers of cycles of 20ms a servo have made to compare with cycles[2]
unsigned int16 cycles[2]={0,0}; //this will enable us to calculate the number of cycles to perform a correct movement
unsigned int8 last_position[2]={18,18}; //contains the last position of the servo to calculate the cyles
const int8 table_cycles[37]={0,3,4,4,5,6,7,8,8,9,10,11,12,12,13,14,15,15,16,17,18,19,19,20,21,22,23,23,24,25,26,27,27,28,29,30,31}; //this
//array contains the number of cycles of ISR depending on the number of degrees to move the servo
const int16 table_ticks[37]={3000,3250,3500,3750,4000,4250,4500,4750,5000,5250,5500,5750,6000,6250,6500,6750,7000,7250,7500,7750,8000,8250,8500,8750,9000,9250,9500,9750,10000,10250,10500,10750,
11000,11250,11500,11750,12000};
unsigned int8 address; //this variable will contain the address of the data send by I2C inside the array buffer
unsigned int8 buffer[2]; //this array will contain the servo's positions that the master has send via I2C
unsigned int8 state; //a simple variable to read the state of the I2C bus;
unsigned int8 indicator=0; //the master orders a move_s1_first()[indicator=0]; or a move_s2_first();[indicator=128] function
unsigned int8 ready=0; //a flag to indicate that the movement is finished properly.
unsigned int1 new_poss=0; //falg to indicate that new positions send by the master have been recieved
#int_TIMER1
void TIMER1_isr(void)
{
if(flag_seq==0)
{
if(s_ID==0 && move==1) output_high(servo1);
if(s_ID==1 && move==1) output_high(servo2);
Ticks4NextISR = 65535 - servo_duty[s_ID];
set_timer1(Ticks4NextISR);
}
if(flag_seq==1)
{
if(s_ID==0 && move==1) output_low(servo1);
if(s_ID==1 && move==1) output_low(servo2);
Ticks4NextISR = 65535 - Ticks4Window + servo_duty[s_ID];
set_timer1(Ticks4NextISR);
cycles_isr[s_ID]++;
if(++s_ID>1) s_ID=0;
}
++flag_seq;
if(flag_seq>1)
{
flag_seq=0;
}
}
#int_SSP
void SSP_isr(void)
{
state = i2c_isr_state();
if(state < 0x80) //master is sending data
{
if(state == 0) //master has matched this address device
{
}
if(state == 1) //first received byte is address
{
address = i2c_read(I2CS);
new_poss=1;
}
if(state >= 2) //second received byte is data
{
buffer[address] = i2c_read(I2CS);
address++;
indicator=buffer[0]&128;
}
}
if(state == 0x80) //master is requesting data
{
i2c_write (I2CS,ready); //send requested data
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////
// FUNCIONES DE MOVIMIENTO //
void move_S2_first(unsigned int8 degS1, unsigned int8 degS2 )
{
unsigned int8 rest;
ready=0;
if(last_position[0]>degS1)
{
rest=last_position[0]-degS1;
}
else
{
rest=degS1-last_position[0];
}
cycles[0]=table_cycles[rest];
if(last_position[1]>degS2)
{
rest=last_position[1]-degS2;
}
else
{
rest=degS2-last_position[1];
}
cycles[1]=table_cycles[rest];
servo_duty[1]=table_ticks[degS2]; //we lift servo2 to the postiion indicated by degS"
cycles_isr[1]=0;
while(cycles_isr[1]!=cycles[1]) //servo1 won't move until servo2 is in its position
{}
servo_duty[0]=table_ticks[degS1]; //now servo1 can move to the position indicated by degS1 and servo2 holds on degS2
cycles_isr[0]=0;
while(cycles_isr[0]<cycles[0])
{}
last_position[0]=degS1;
last_position[1]=degS2;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void move_S1_first(unsigned int8 degS1, unsigned int8 degS2 )
{
unsigned int8 rest;
ready=0;
if(last_position[0]>degS1)
{
rest=last_position[0]-degS1;
}
else
{
rest=degS1-last_position[0];
}
cycles[0]=table_cycles[rest];
if(last_position[1]>degS2)
{
rest=last_position[1]-degS2;
}
else
{
rest=degS2-last_position[1];
}
cycles[1]=table_cycles[rest];
servo_duty[0]=table_ticks[degS1]; //we move servo1 to the postiion indicated by degS1
cycles_isr[0]=0;
while(cycles_isr[0]!=cycles[0]) //servo2 won't move until servo1 is in its position
{}
servo_duty[1]=table_ticks[degS2]; //now servo2 can move to the position indicated by degS2 and servo1 holds on degS1
cycles_isr[1]=0;
while(cycles_isr[1]<cycles[1])
{}
last_position[0]=degS1;
last_position[1]=degS2;
}
void main()
{
setup_adc_ports(NO_ANALOGS);
setup_adc(ADC_OFF);
setup_timer_1(T1_INTERNAL|T1_DIV_BY_1);
setup_timer_2(T2_DISABLED,0,1);
setup_comparator(NC_NC_NC_NC);
setup_vref(FALSE);
set_tris_a(0b00000000);
set_tris_b(0b11111111);
// set_tris_c(0b00000000);
output_low(servo1);
output_low(servo2);
set_timer1(Ticks4NextISR);
enable_interrupts(INT_TIMER1);
enable_interrupts(INT_SSP);
enable_interrupts(GLOBAL);
move=1;
ready=2;
i2c_start(I2CM);
i2c_write(I2CM,0xA0); //i2c address of a slave device
i2c_write(I2CM,ready); //1st byte to slave
i2c_stop(I2CM);
//MAIN BUCLE
while(1)
{
while(new_poss!=1)
{}
new_poss=0;
if(indicator==128)
{
move_S2_first(buffer[0],buffer[1]);
ready=2;
i2c_start(I2CM);
i2c_write(I2CM,0xA0); //i2c address of a slave device
i2c_write(I2CM,ready); //1st byte to slave
i2c_stop(I2CM);
}
else if(indicator<128)
{
move_S1_first(buffer[0],buffer[1]);
ready=2;
i2c_start(I2CM);
i2c_write(I2CM,0xA0); //i2c address of a slave device
i2c_write(I2CM,ready); //1st byte to slave
i2c_stop(I2CM);
}
}
}
////////////////////////////////////////////////////////////////////
The code inside the master's controller
/////////////////////////////////////////////////////////////////
#include <16F873A.h>
#fuses HS,NOLVP,NOWDT,NOPROTECT
#use delay(clock=20000000)
#use i2c(MASTER, fast,sda=PIN_C1, scl=PIN_C0,stream=I2CM)
#use i2c(SLAVE,fast, sda=PIN_C4, scl=PIN_C3, address=0xC1, force_hw,stream=I2CS) //using hardware I2C, built into the PIC, make sure to include this line in any master I2C program
#use fast_io(A)
#use fast_io(B)
#use fast_io(C)
unsigned int8 state,aux1;
unsigned int8 ready[2];
unsigned int8 address,s1,s11,i=0;
unsigned int8 buffer1[2]={9,27};
unsigned int8 buffer2[2]={137,155};
unsigned int8 buffer11[2]={30,36};
#int_SSP
void SSP_isr(void)
{
state = i2c_isr_state(I2CS);
if(state < 0x80) //master is sending data
{
if(state == 0) //master has matched this address device
{
}
if(state == 1) //first received byte is address
{
address = i2c_read(I2CS);
}
if(state >= 2) //second received byte is data
{
ready[address] = i2c_read(I2CS);
address++;
}
}
if(state == 0x80) //master is requesting data
{
i2c_write (I2CS,aux1); //send requested data
}
}
void main(void)
{
set_tris_a(0b00000000);
set_tris_b(0b00000000);
set_tris_c(0b11111111);
enable_interrupts(INT_SSP);
enable_interrupts(GLOBAL);
while(TRUE)
{
while(ready[0]==0){}
//we send the positons of the first movement to slave 1
i2c_start(I2CM); //begin transmission
i2c_write(I2CM,0xB0); //select address of device to communicate with
i2c_write(address); //send actual data
i2c_write(I2CM,buffer1[i]); //send data for servo1
i2c_write(I2CM,buffer11[i]); //send data for servo2
i2c_stop(I2CM); //terminate communication
i=i+1;
if(i==2)
{i=0;}
}
} |
Thank you all! |
|
|
Ttelmah Guest
|
|
Posted: Wed Oct 29, 2008 11:28 am |
|
|
First thing to learn about debugging, is work on small parts. Nobody here is going to want to wade through all your motor movement code, to look at a communication problem, and you should not be doing this either. Write _just_ the communication part, and get it working on it's own.
There is no reason not to use the single I2C interface to talk to all the devices. If you do want an acknowledge, use a single line, and 'wire or' it. Have a pull up resistor on it, have a pin on each slave connected to the line. When communication starts (as soon as the slave enters the interrupt handler, have it pull the line down. Then when the slave has finished, it simply sets the pin as an input. The master just reads the line, and if it is low, knows a slave is driving it.
Use Just the one I2C, remember that the slave devices all need different addresses, and these must increment in two's not one's.
KISS. You are making your problem too complex. Get the I2C bus working to the slaves, have them do something simple, like flash an LED, when they see a message for them. Make sure you can talk to all the slaves, before attaching the motor control code.
Best Wishes |
|
|
CF__878 Guest
|
|
Posted: Wed Oct 29, 2008 4:25 pm |
|
|
Hello Ttelmah,
I know what debugging is about, but because of this (and trust me I am working in this project for a while and I started in slow-small steps) I concluded that sometimes it is important to look at the whole program because one thing that we think it is not related with our problem at the end it is, a little example is the interrupt nesting, where a wrong configuration can produce a lot of problems without thinking of it. So because of this and because in this case the servo control is working perfectly, I posted it to in case the Timer1 is making some thing "bad" to the I2C. And I am also "chopping" my code to work only in the I2C but I am having no luck. But yeah probably I am making it too complex.
Can you explain me the solution about the line to make the acknowledge? Because I don't know if I am understanding it correctly. Thats what I understand. Use a third line (SCL, SDA and the ACK line) where all the devices will be "hanging" from. When the master sends information it puts it high and when the slave has finished the movement it puts it low so the master can read it.I am right? If this is the case, how I know which is the slave that has finished moving, because this is a Hexapod robot so sometimes three or four legs are moving at the same exactly time and I need to know individually who has finished.
Thank you in advance! |
|
|
Ttelmah Guest
|
|
Posted: Thu Oct 30, 2008 4:05 am |
|
|
No, the other way round.
A PIC pin, can be set to behave as an input (output_float), or drive a line high/low. If you have a line with a pullup resistor on it (so it goes high when not driven), and the master only reads it as an input, then in the slaves, when they 'see' a command, they use 'output_low', and when they finish, use 'output_float', the master can send it's command, then simply look at this line, and if it is low, the slave is still driving it low. As soon as it goes high, the master 'knows' the slave has finished. Since the slave 'releases' the line, rather than driving it high, _any_ slave can drive the line low to show it is busy, and it will only go high, when all the slaves release it. This is the concept of 'wire-or'. Used to be commonly used with logic outputs that only had a 'pull down' output (open collector drives).
As an 'aside', consider not using I2C at all. Each slave, has a hardware UART available. Even if the master is using it's hardware UART, you can use a pin on the master as a software serial output, and have this drive all the slaves serial inputs. Then implement a 'busy' wire using the 'wire-or' outlined, and the master can just send a packet describing what each slave is to do. Use something like STX 'slave_number' 'position' ETX. The slaves just have a serial receive routine like:
Code: |
int16 position;
int1 action=false;
#define MY_ADDRESS ('1') //different for each slave
//Note _ASCII_, to avoid confusion with STX
#define STX (2)
#define ETX (3)
#define valhex(x) (x<='9')?(x-'0'):((x-'A')+10)
//Note requires upper case hex
//This is assuming a format, sending STX, the address, then 3 hex
// digits (allowing a value from 0 to 4095), then ETX
#int_rda
void serial_rx(void)
static int8 state=0;
static int16 tempval;
int8 temp_chr;
temp_chr=getc();
switch (state) {
case 0:
if (temp_chr==STX) {
//seen a 'start'
state=1;
}
break;
case 1:
if (temp_chr==MY_ADDRESS) {
//Command is for me
state=2;
output_low(BUSY_LINE); //define to suit your chip
}
else state=0; //Ignore otherwise
break;
case 2:
//Send the data in hex, so there is no confusion with control
//characters.
tempval=valhex(temp_chr);
state++;
break;
case 3:
tempval*=16;
tempval+=valhex(temp_chr);
state++;
break;
case 4:
tempval*=16;
tempval+=valhex(temp_chr);
state++;
//Now send signal to the main code in this processor
//that a new value is present.
position=tempval;
action=true;
break;
case 5:
if (temp_chr==ETX) {
state=0; //Go back to looking
//Up to you, whether you float the 'BUSY' line here, or when the
//slave unit has completed it's movement.
output_float(BUSY_LINE);
}
break;
}
}
|
With this, you have the advantage of only needing two wires in all to provide the transmission, and an acknowledge, and (more importantly), two characters of receive buffering.
Best Wishes |
|
|
CF__878 Guest
|
|
Posted: Thu Oct 30, 2008 6:01 am |
|
|
Hi again,
Thank you for the code, I am considering to use the UART solution. But meanwhile I post a small code using the I2C bus that it is intended to communicate a master (16F873A) with two slaves (16F88) and those two drive two LEDs.They have the same code except the address. It is a simple program but it is not working well. The problem is that I can write and read data when i communicate with only one device but when I wirte to two devices, only the first one works the second one does nothing, so I thing the program hangs or something. I am simulating it on Proteus 7.2 and I am compiling it with CCS 4.065 compiler. I am doing something wrong?
Code: | ///////////////////////////////////////////////
Master code
//////////////////////////////////////////////
#include <16F873A.h>
#FUSES NOWDT //No Watch Dog Timer
#FUSES HS //High speed Osc (> 4mhz)
#FUSES NOPUT //No Power Up Timer
#FUSES NOPROTECT //Code not protected from reading
#FUSES NODEBUG //No Debug mode for ICD
#FUSES NOBROWNOUT //No brownout reset
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOCPD //No EE protection
#use delay(clock=20000000)
#use I2C(MASTER, FAST, SCL=PIN_C3, SDA=PIN_C4, FORCE_HW)
void main(void)
{
unsigned int8 address,ready;
set_tris_a(0b00000000);
set_tris_b(0b00000000);
set_tris_c(0b11111111);
address=0;
while(TRUE)
{
//we send the positons of the first movement to slave 1
i2c_start(); //begin transmission
i2c_write(0x06); //select address of device to communicate with
i2c_write(1); //send actual data
i2c_stop();
i2c_start();
i2c_start(); //begin transmission
i2c_write(0x02); //select address of device to communicate with
i2c_write(1); //send actual data
i2c_stop(); //terminate communication
delay_ms(150);
}
} |
Code: |
////////////////////////////////////////////
Slave code
//////////////////////////////////////////
#include "16F88.h"
//#device adc=8
#FUSES NOWDT //No Watch Dog Timer
#FUSES HS //High speed Osc (> 4mhz)
#FUSES NOPUT //No Power Up Timer
#FUSES NOPROTECT //Code not protected from reading
#FUSES NODEBUG //No Debug mode for ICD
#FUSES NOBROWNOUT //No brownout reset
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOCPD //No EE protection
#use delay(clock=20000000)
#use i2c(SLAVE,FAST,SDA=PIN_B1,SCL=PIN_B4,address=0x02, force_hw)
#use fast_io(A)
unsigned int8 state,ready,address,buffer[3];
#int_SSP
void SSP_isr(void)
{
state = i2c_isr_state();
if(state < 0x80) //master is sending data
{
ready=1;
}
if(state == 0x80) //master is requesting data
{
i2c_write (ready); //send requested data
}
}
void main()
{
setup_adc_ports(NO_ANALOGS);
setup_adc(ADC_OFF);
setup_timer_2(T2_DISABLED,0,1);
setup_comparator(NC_NC_NC_NC);
setup_vref(FALSE);
set_tris_a(0b00000000);
enable_interrupts(INT_SSP);
enable_interrupts(GLOBAL);
//MAIN BUCLE
while(1)
{
if(ready==1)
{
ready=0;
output_high(PIN_A0);
}
}
} |
This is the most simple code to drive I2C I think and it does not work.
I tried sending more than one data byte and for one device it works but
the same happens when sending to another device.
Can you help me?
Thank you in advance! |
|
|
Ttelmah Guest
|
|
Posted: Thu Oct 30, 2008 3:23 pm |
|
|
First, get rid of 'fast' in the slave. The slave device has no 'speed' associated with it, and using this can lead to incorrect setups.
Then, you must read the data in the interrupt routine.
Get rid of the double 'start' in the master.
Best Wishes |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Thu Oct 30, 2008 3:33 pm |
|
|
Quote: | #use i2c(SLAVE,FAST,SDA=PIN_B1,SCL=PIN_B4,address=0x02, force_hw) |
On page 16 of the i2c specification, there is a table of reserved slave
addresses. They are shown in 7-bit format. Address 0x02 is assigned
to the CBUS. In the Notes, it says this:
Quote: | The CBUS address has been reserved to enable the
inter-mixing of CBUS compatible and I2C-bus
compatible devices in the same system. I2C-bus
compatible devices are not allowed to respond on
reception of this address. |
Don't use address 0x02 for the slave.
You should use an even address in this range: 0x10 to 0xF6.
Here is a link to the i2c specification:
http://www.nxp.com/acrobat_download/literature/9398/39340011.pdf
Quote: | #use i2c(SLAVE,fast, sda=PIN_C4, scl=PIN_C3, address=0xC1, force_hw,stream=I2CS) |
Don't use an odd address for the slave. Use an even numbered address
such as 0xC0 or 0xC2. |
|
|
CF_878
Joined: 28 Oct 2008 Posts: 2
|
|
Posted: Thu Oct 30, 2008 4:29 pm |
|
|
Hi all,
I've tried what you recommended but the problem persists. I also tried using this code:
http://www.ccsinfo.com/forum/viewtopic.php?t=33950
But the same happens. I can send whatever I want to the first slave but then the second one won't receive nothing. I mean, If as an example I send something to address 0xC4 and then I want to send something to the address 0xC2, 0xC2 won't receive nothing. It is proteus? Do you have any code that you know it is working well?
Thank you all! |
|
|
Ttelmah Guest
|
|
Posted: Fri Oct 31, 2008 3:22 am |
|
|
If you do a search here, for Proteus and I2C, you will find a lot of posts with people having various problems, especially when dealing with slave mode. I'm afraid that Proteus, while 'pretty good', does not handle this type of thing that well, and especially when things start to get a bit more complex, is not the way to find out if things will really work.
Best Wishes |
|
|
CF_878
Joined: 28 Oct 2008 Posts: 2
|
|
Posted: Fri Oct 31, 2008 4:24 am |
|
|
OK I will test it in my hardware. I thought Proteus fixed that problems.
Thank you for your time and help! |
|
|
|
|
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
|