by limor » Sun Nov 12, 2006 1:59 pm
by limor
Sun Nov 12, 2006 1:59 pm
Hello freeforall and welcome to the forum!
I've uploaded some code for the Bioloid.
It is a rewrite of the example.c that comes with the kit.
http://robosavvy.com/Builders/limor/Bioloid_example.zip
The pnproj file is a Programmers Notepad Project file. WinAVR (gcc for Atmel) comes with PN as the default IDE. It uses gnu make to compile the project. The makefile only needs a couple of lines modified to fit your project files.
This code is not yet fully functional but it demonstrates how Atmega128 is programmed and how the Robotis servo protocol works.
It should be compatible eventually with both the CM5 and with the Bioloid sensor board by Pepperm (search the Bioloid forum).
For example, here is a routines that initialize the RS485 serial interface
- Code: Select all
/*
* InitSerialRS485 - set the i/o pins to talk to servos
* Interrupt - if 1 then input will be interrupt driven
* gInterruptBuffer[gInterruptBufferPointer++] will continously take new read values
* Baudrate - used in conjunction with prescaler...
*/
void InitSerialRS485(byte Baudrate, byte Interrupt)
{
UBRR0H = 0;
UBRR0L = Baudrate;
UCSR0A = 0x02; // U2X: Double the USART transmission speed
UCSR0B = 0x18; // Receiver and Transmitter enable
if(Interrupt) sbi(UCSR0B,7); // RxD interrupt enable
UCSR0C = 0x06; // 8 bit data, no parity
UDR0 = 0xFF; // this will be broadcast (may be required by servos?)
SET_TXRS485_FINISH; // Note. set 1, then 0 is read
}
As you may notice, it is indeed C but 99% of the logic of what this code does is in the Atmega128 documentation. There is no object-oriented design patterns. This is back to basics, like writing a device driver. No operating system no JIT or garbage-collection or simulated threads or any other hidden agenda.
Here's another example, this time it is the actual implementation of the Robotis protocol
- Code: Select all
volatile byte Buffer[256]; // in interrupt UART read mode, gBuffer[gBufferPointer++] will continously take new read values
volatile byte BufferPointer=0;
/*
* SIGNAL() Rx Interrupt - write data to buffer (used with CM5's RS485)
* When a byte (8bit + parity + stop) has been read by the Atmega, it issues an interrupt
* RxBufferPointer goes back to 0 after 255, RxPacket doesn't need it to reset it to 0
*
* The reason for using an interrupt driven Rx is because the CM5 has 2 serials.
* you can't poll two Rx because the other one may need attention during poll.
*/
SIGNAL (SIG_UART0_RECV)
{
Buffer[BufferPointer++] = RXRS485_DATA;
}
/*
* TxRS485() write byte to Robotis bus
*/
static void TxRS485(byte Data)
{
while(!CHECK_TXRS485_READY);
TXRS485_DATA = Data;
}
/*
* RxRS485() read byte from Robotis bus (CM5 is normally interrupt Rx so doesnt use this routine)
*/
static byte RxRS485(void)
{
while(!CHECK_RXRS485_READY);
return(RXRS485_DATA);
}
/*
TxRS485Packet() send Robotis package to RS485.
Similar format packet is sent by master and by slave (5th byte differs)
ID - My ID
Instruction - Master: PING, READ etc. Slave: Error bitmask
ParameterLength - number of parameters sent in Buffer
Parameter - will be coppied to Buffer[5]..Buffer[5+ParameterLength-1]
*/
void TxRS485Packet(byte ID, byte Instruction, byte ParameterLength, byte *Parameter)
{
byte i, CheckSum, PacketLength;
Buffer[0] = 0xff;
Buffer[1] = 0xff;
Buffer[2] = ID;
Buffer[3] = ParameterLength+2; //Length(Paramter,Instruction,Checksum)
Buffer[4] = Instruction;
for (i=0; i< ParameterLength;i++)
Buffer[5+i] = Parameter[i];
CheckSum = 0;
PacketLength = ParameterLength+4+2;
for(i = 2; i < PacketLength-1; i++) //except 0xff,checksum
{
CheckSum += Buffer[i];
}
Buffer[i] = ~CheckSum; //Writing Checksum with Bit Inversion
SET_RS485_TXD;
for(i = 0; i < PacketLength; i++)
{
SET_TXRS485_FINISH; // ? this only clears bit6. triggers interrupt if interrupt flag is set
TxRS485(Buffer[i]);
}
while(!CHECK_TXRS485_FINISH); // Wait until (bit6) TXD Shift register empty
SET_RS485_RXD;
BufferPointer=0; // Response packet instantanous. Interrupt Rx relies on buffer being filled from 0
}
/*
* RxRS485Packet() receive Robotis instruction packet through RS485.
* This is is the non-interrupt version of the read-packet designed for a sensor board.
* The full protocol implementation of the protocol is needed only by Robotis servos
* returns the packet pointer if the packet is intended for me otherwise null
*/
byte *RxRS485Packet(byte ID)
{
byte i;
Buffer[0] = RxRS485(); // 0xff;
Buffer[1] = RxRS485(); // 0xff;
Buffer[2] = RxRS485(); // ID;
Buffer[3] = RxRS485(); // ParameterLength+2; // Length(Paramter,Instruction,Checksum)
Buffer[4] = RxRS485(); // Instruction;
for (i=0; i<Buffer[3]-2; i++)
Buffer[5+i] = RxRS485(); // Parameters..
Buffer[5+i] = RxRS485(); // Checksum;
// not performing any error checking. assumning that robotis cables are ok.
return (Buffer[2] == ID)? Buffer: (byte *)0;
}
/*
* RxRS485PacketIntr() receive Robotis (response) packet through RS485.
* This is is the interrupt-driven version of the read-packet designed for CM5
* Just waits for the buffer to fill up by the interrupt routine
*
* The reason for using an interrupt driven Rx is because the CM5 has 2 serials.
* you can't poll two Rx because the other one may need attention during poll.
*/
byte *RxRS485PacketIntr()
{
byte i;
long r=0;
while (BufferPointer < 4) if (r++ > 100000) return 0;
while (BufferPointer < 4+Buffer[3]) if (r++ > 200000) return 0;
return Buffer;
}
Finally here is the actual main() program. Hopefully when this library will be complete, you wont have to care about the details of the above implementation of the protocol and initialization, and focus only on getting servo position and other sensor data packets and sending back servo position and torque packets and you AI algorithms.
- Code: Select all
#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include "robotis.h"
int main (void)
{
unsigned short popo=300;
byte i;
byte *packet;
byte buf[256];
InitPorts();
InitSerialRS485(BAUD_1000000, 1); // Rx is interrupt driven
InitSerialPC(BAUD_57600, 0); // Rx is not interrupt driven
sei(); // enable interrupts
while (1) {
TxPCString("Press Enter ->");
RxPC(); // wait for PC keypress
popo += 30;
buf[0] = P_GOAL_POSITION_L;
buf[1] = 0; // Data P_GOAL_POSITION_L
buf[2] = 2; // Data P_GOAL_POSITION_H
buf[3] = 0x00; // Data P_GOAL_SPEED_L
buf[4] = 0x01; // Data P_GOAL_SPEED_H
TxRS485Packet(6,INST_WRITE,5, buf);
if (packet = RxRS485PacketIntr()) TxPCString("Bingo!\r\n");
for (i=0; i <20; i++) {
TxPCString("Testing [");
TxPC32Dec(i);
TxPCString("] ");
TxRS485Packet(i, INST_PING, 0, 0);
if (packet = RxRS485PacketIntr()) {
TxPCString("Bingo!");
}
TxPCString("\r\n");
}
}
}
Hello freeforall and welcome to the forum!
I've uploaded some code for the Bioloid.
It is a rewrite of the example.c that comes with the kit.
http://robosavvy.com/Builders/limor/Bioloid_example.zip
The pnproj file is a Programmers Notepad Project file. WinAVR (gcc for Atmel) comes with PN as the default IDE. It uses gnu make to compile the project. The makefile only needs a couple of lines modified to fit your project files.
This code is not yet fully functional but it demonstrates how Atmega128 is programmed and how the Robotis servo protocol works.
It should be compatible eventually with both the CM5 and with the Bioloid sensor board by Pepperm (search the Bioloid forum).
For example, here is a routines that initialize the RS485 serial interface
- Code: Select all
/*
* InitSerialRS485 - set the i/o pins to talk to servos
* Interrupt - if 1 then input will be interrupt driven
* gInterruptBuffer[gInterruptBufferPointer++] will continously take new read values
* Baudrate - used in conjunction with prescaler...
*/
void InitSerialRS485(byte Baudrate, byte Interrupt)
{
UBRR0H = 0;
UBRR0L = Baudrate;
UCSR0A = 0x02; // U2X: Double the USART transmission speed
UCSR0B = 0x18; // Receiver and Transmitter enable
if(Interrupt) sbi(UCSR0B,7); // RxD interrupt enable
UCSR0C = 0x06; // 8 bit data, no parity
UDR0 = 0xFF; // this will be broadcast (may be required by servos?)
SET_TXRS485_FINISH; // Note. set 1, then 0 is read
}
As you may notice, it is indeed C but 99% of the logic of what this code does is in the Atmega128 documentation. There is no object-oriented design patterns. This is back to basics, like writing a device driver. No operating system no JIT or garbage-collection or simulated threads or any other hidden agenda.
Here's another example, this time it is the actual implementation of the Robotis protocol
- Code: Select all
volatile byte Buffer[256]; // in interrupt UART read mode, gBuffer[gBufferPointer++] will continously take new read values
volatile byte BufferPointer=0;
/*
* SIGNAL() Rx Interrupt - write data to buffer (used with CM5's RS485)
* When a byte (8bit + parity + stop) has been read by the Atmega, it issues an interrupt
* RxBufferPointer goes back to 0 after 255, RxPacket doesn't need it to reset it to 0
*
* The reason for using an interrupt driven Rx is because the CM5 has 2 serials.
* you can't poll two Rx because the other one may need attention during poll.
*/
SIGNAL (SIG_UART0_RECV)
{
Buffer[BufferPointer++] = RXRS485_DATA;
}
/*
* TxRS485() write byte to Robotis bus
*/
static void TxRS485(byte Data)
{
while(!CHECK_TXRS485_READY);
TXRS485_DATA = Data;
}
/*
* RxRS485() read byte from Robotis bus (CM5 is normally interrupt Rx so doesnt use this routine)
*/
static byte RxRS485(void)
{
while(!CHECK_RXRS485_READY);
return(RXRS485_DATA);
}
/*
TxRS485Packet() send Robotis package to RS485.
Similar format packet is sent by master and by slave (5th byte differs)
ID - My ID
Instruction - Master: PING, READ etc. Slave: Error bitmask
ParameterLength - number of parameters sent in Buffer
Parameter - will be coppied to Buffer[5]..Buffer[5+ParameterLength-1]
*/
void TxRS485Packet(byte ID, byte Instruction, byte ParameterLength, byte *Parameter)
{
byte i, CheckSum, PacketLength;
Buffer[0] = 0xff;
Buffer[1] = 0xff;
Buffer[2] = ID;
Buffer[3] = ParameterLength+2; //Length(Paramter,Instruction,Checksum)
Buffer[4] = Instruction;
for (i=0; i< ParameterLength;i++)
Buffer[5+i] = Parameter[i];
CheckSum = 0;
PacketLength = ParameterLength+4+2;
for(i = 2; i < PacketLength-1; i++) //except 0xff,checksum
{
CheckSum += Buffer[i];
}
Buffer[i] = ~CheckSum; //Writing Checksum with Bit Inversion
SET_RS485_TXD;
for(i = 0; i < PacketLength; i++)
{
SET_TXRS485_FINISH; // ? this only clears bit6. triggers interrupt if interrupt flag is set
TxRS485(Buffer[i]);
}
while(!CHECK_TXRS485_FINISH); // Wait until (bit6) TXD Shift register empty
SET_RS485_RXD;
BufferPointer=0; // Response packet instantanous. Interrupt Rx relies on buffer being filled from 0
}
/*
* RxRS485Packet() receive Robotis instruction packet through RS485.
* This is is the non-interrupt version of the read-packet designed for a sensor board.
* The full protocol implementation of the protocol is needed only by Robotis servos
* returns the packet pointer if the packet is intended for me otherwise null
*/
byte *RxRS485Packet(byte ID)
{
byte i;
Buffer[0] = RxRS485(); // 0xff;
Buffer[1] = RxRS485(); // 0xff;
Buffer[2] = RxRS485(); // ID;
Buffer[3] = RxRS485(); // ParameterLength+2; // Length(Paramter,Instruction,Checksum)
Buffer[4] = RxRS485(); // Instruction;
for (i=0; i<Buffer[3]-2; i++)
Buffer[5+i] = RxRS485(); // Parameters..
Buffer[5+i] = RxRS485(); // Checksum;
// not performing any error checking. assumning that robotis cables are ok.
return (Buffer[2] == ID)? Buffer: (byte *)0;
}
/*
* RxRS485PacketIntr() receive Robotis (response) packet through RS485.
* This is is the interrupt-driven version of the read-packet designed for CM5
* Just waits for the buffer to fill up by the interrupt routine
*
* The reason for using an interrupt driven Rx is because the CM5 has 2 serials.
* you can't poll two Rx because the other one may need attention during poll.
*/
byte *RxRS485PacketIntr()
{
byte i;
long r=0;
while (BufferPointer < 4) if (r++ > 100000) return 0;
while (BufferPointer < 4+Buffer[3]) if (r++ > 200000) return 0;
return Buffer;
}
Finally here is the actual main() program. Hopefully when this library will be complete, you wont have to care about the details of the above implementation of the protocol and initialization, and focus only on getting servo position and other sensor data packets and sending back servo position and torque packets and you AI algorithms.
- Code: Select all
#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include "robotis.h"
int main (void)
{
unsigned short popo=300;
byte i;
byte *packet;
byte buf[256];
InitPorts();
InitSerialRS485(BAUD_1000000, 1); // Rx is interrupt driven
InitSerialPC(BAUD_57600, 0); // Rx is not interrupt driven
sei(); // enable interrupts
while (1) {
TxPCString("Press Enter ->");
RxPC(); // wait for PC keypress
popo += 30;
buf[0] = P_GOAL_POSITION_L;
buf[1] = 0; // Data P_GOAL_POSITION_L
buf[2] = 2; // Data P_GOAL_POSITION_H
buf[3] = 0x00; // Data P_GOAL_SPEED_L
buf[4] = 0x01; // Data P_GOAL_SPEED_H
TxRS485Packet(6,INST_WRITE,5, buf);
if (packet = RxRS485PacketIntr()) TxPCString("Bingo!\r\n");
for (i=0; i <20; i++) {
TxPCString("Testing [");
TxPC32Dec(i);
TxPCString("] ");
TxRS485Packet(i, INST_PING, 0, 0);
if (packet = RxRS485PacketIntr()) {
TxPCString("Bingo!");
}
TxPCString("\r\n");
}
}
}