|
|
View previous topic :: View next topic |
Author |
Message |
Robotan
Joined: 12 Jul 2008 Posts: 18 Location: Canada
|
migrating serial communication code from 16F877A to 18F4620 |
Posted: Sat Feb 21, 2009 9:09 pm |
|
|
Hello,
I am working on an application that controls dynamixel ax-12 servo motors. These motors are addressable on a serial bus and can be sent command positions, speeds etc. Anyway, I had the code to communicate with these motors working on a 16F877A, but am having trouble migrating to an 18F4620. I made sure the clock and baud rate were correct, and the motors don't seem to respond. I checked the hardware by connecting a serial LCD to the 18F4620, and it worked fine. I'm wondering if there is a difference in the way the UART works on the newer chip and I'm not setting it up properly?
Here is the header file:
Code: |
#include <18F4620.h>
#device adc=10
#FUSES NOWDT //No Watch Dog Timer
#FUSES WDT128 //Watch Dog Timer uses 1:128 Postscale
#FUSES H4 //High speed osc with HW enabled 4X PLL
#FUSES NOPROTECT //Code not protected from reading
#FUSES IESO //Internal External Switch Over mode enabled
#FUSES NOBROWNOUT //No brownout reset
#FUSES BORV21 //Brownout reset at 2.1V
#FUSES NOPUT //No Power Up Timer
#FUSES NOCPD //No EE protection
#FUSES STVREN //Stack full/underflow will cause reset
#FUSES NODEBUG //No Debug mode for ICD
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOWRT //Program memory not write protected
#FUSES NOWRTD //Data EEPROM not write protected
#FUSES NOEBTR //Memory not protected from table reads
#FUSES NOCPB //No Boot Block code protection
#FUSES NOEBTRB //Boot block not protected from table reads
#FUSES NOWRTC //configuration not registers write protected
#FUSES NOWRTB //Boot block not write protected
#FUSES FCMEN //Fail-safe clock monitor enabled
#FUSES NOXINST //Extended set extension and Indexed Addressing mode disabled (Legacy mode)
#FUSES PBADEN //PORTB pins are configured as analog input channels on RESET
#FUSES LPT1OSC //Timer1 configured for low-power operation
#FUSES MCLR //Master Clear pin enabled
#use delay(clock=40M, oscillator=10M)
#use rs232(baud=250000,xmit=PIN_C6,rcv=PIN_C7,enable=PIN_C5,PARITY = N,bits=8)
|
And here is part of the main code:
Code: | short TX_flag;
char rpacket[16];
int id_num;
#int_TBE
void TBE_isr(void)
{
TX_flag = 1; //transmit buffer is empty
disable_interrupts(INT_TBE);
}
void main()
{
setup_adc_ports(AN0_TO_AN7|VSS_VREF);
setup_adc(ADC_CLOCK_DIV_16|ADC_TAD_MUL_12);
setup_psp(PSP_DISABLED);
setup_wdt(WDT_OFF);
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_TBE);
enable_interrupts(GLOBAL);
//send commands here...
|
The interrupt is used to determine when the buffer is empty and the next byte can be sent. Basically, the motors are sent a series of bytes with this function:
Code: |
void AX_SendByte(int toSend)
{
while(TX_flag == 0){ //wait for transmit register to be ready
}
putc(toSend);
TX_flag = 0;
enable_interrupts(INT_TBE);
} |
Since the code works with the 16F877A, I'm fairly certain the problem lies with the initialization of the 18F4620 somewhere. Any ideas of where the problem lies?
Rob |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Sat Feb 21, 2009 11:07 pm |
|
|
My advice is, make very few changes when initially "porting" the code
from one PIC family to a new one. You are running the new PIC at
40 MHz. Don't do that. Run it at the same speed as the old PIC.
If you have made any other important changes, put them back so the
code is similar to the 16F877A code. |
|
|
Robotan
Joined: 12 Jul 2008 Posts: 18 Location: Canada
|
|
Posted: Sun Feb 22, 2009 3:16 pm |
|
|
Well, I tried installing the 18F4620 chip on the old board to replace the 16F877A. This means all of the hardware, including the crystal is the same. I loaded an essentially identical program that was working on the 16F877A, and it doesn't work with the 18F4620. Just to make sure the timing with the new chip is correct, I made a simple program to blink an led at 1 hz, and it worked fine. So I'm kind of stumped as to what is preventing the new PIC from communication properly with the servo motors. Any help is appreciated. |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Sun Feb 22, 2009 3:28 pm |
|
|
Are you doing anything that is low-level hardware dependent, such as:
1. Does your program do any direct access to SFR registers in the PIC ?
The register addresses are different between 16F and 18F.
Do you have any #byte, #locate, or #bit statements in your program ?
2. Are you using #org statements ?
3. In your #fuses statement above, you are configuring all of Port B
as analog input pins. That's not compatible with the 16F877A. On it,
Port B is always all digital. You need to change the fuse to NOPBADEN. |
|
|
Robotan
Joined: 12 Jul 2008 Posts: 18 Location: Canada
|
|
Posted: Sun Feb 22, 2009 10:01 pm |
|
|
Nope, I'm not doing any low level stuff simply because I don't know how
So there aren't any #byte, #locate, #bit or #org statements in the program. The one thing that may be suspect is the interrupt that fires when the transmit buffer is empty. This is shown in the code snippets above. All I do is check a flag to make sure the buffer is empty before sending the next character. This may be overkill, but it worked on the 16F877A. Speaking of which, the #fuses statement you asked about is in the code for the 18F4620, so it should be fine. The only way I could think of to verify the serial communication is working properly is to test it with another device. As I mentioned before, I connected a serial lcd sceen, and it works fine. Do you have any other ideas of what might be wrong? |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Sun Feb 22, 2009 10:47 pm |
|
|
Post your compiler version for the 16F877A, and also the version for
the 18F4620.
Also, regarding your comment about the NOPBADEN fuse. It's not in
your posted code. So apparently you are testing a different program
than what you posted. |
|
|
Robotan
Joined: 12 Jul 2008 Posts: 18 Location: Canada
|
|
Posted: Mon Feb 23, 2009 10:09 pm |
|
|
Ok, I have two different student versions of the compiler. For the 16F877A, it is version 3.212, and for the 18F4620, version 4.084.
Just to be 100% sure it was not a harware problem, I put the old chip in the new board. Here is the code that works well with the 16F877A:
Code: |
#include <16F877A.h>
#device ICD=TRUE
#fuses HS,NOWDT,PUT,NOLVP,NOBROWNOUT
#use delay (clock=10000000)
#use rs232(stream=AX12,baud=19200,xmit=PIN_C6,rcv=PIN_C7,enable=PIN_C5,Parity=N,Bits=8)
#use rs232(stream=LCD,baud=9600,xmit=PIN_C2)
//#defines and function prototypes go here
short TX_flag;
char rpacket[16];
int id_num;
#INT_TBE
void TX_ready()
{
TX_flag = 1; //transmit buffer is empty
disable_interrupts(INT_TBE);
}
void main()
{
int x,y;
enable_interrupts(GLOBAL);
delay_ms(2000);
fprintf(LCD,"Servo Test");
delay_ms(2000);
do
{
fprintf(LCD,"?f-90 degrees");
AX_position(1,-90,6);
while(AX_moving(1));
delay_ms(20);
fprintf(LCD,"?f0 degrees");
AX_position(1,0,6);
while(AX_moving(1));
delay_ms(20);
}
while(true);
|
With this code working, I then took the 16F877A chip out of the socket, and replaced it with the 18F4620. The only change I made to the code was in the first line, to include the 18F4620 header. And with this code loaded onto the new chip, it no longer works. The servo motor does not respond.
Ideas? |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Tue Feb 24, 2009 1:55 am |
|
|
Quote: |
AX_position()
AX_moving()
|
Can you post these two routines (so the program will be complete and
compilable) ?
Quote: |
For the 16F877A, it is version 3.212, and for the 18F4620, version 4.084. |
If these two compiler versions are installed in the same directory, it's
possible that installing one can disrupt the other. This problem might
not occur, because CCS has a separate device data file for vs. 4, called
Devices4.dat, while the vs. 3 file is called Devices.dat. But just to be sure,
re-install the PCH compiler, re-compile the 18F4620 code, and test it. |
|
|
Robotan
Joined: 12 Jul 2008 Posts: 18 Location: Canada
|
|
Posted: Tue Feb 24, 2009 7:22 am |
|
|
Here is the complete program:
Code: |
#include <18F4620.h>
#device ICD=TRUE
#fuses HS,NOWDT,PUT,NOLVP,NOBROWNOUT
#use delay (clock=10000000)
#use rs232(stream=AX12,baud=19200,xmit=PIN_C6,rcv=PIN_C7,enable=PIN_C5,Parity=N,Bits=8)
#use rs232(stream=LCD,baud=9600,xmit=PIN_C2)
//Servo motor control table addresses
//EEPROM
#define CT_ID 0x03
#define CT_BAUD_RATE 0x04
#define CT_RETURN_DELAY 0x05
#define CT_CW_ANGLELIMIT_L 0x06
#define CT_CW_ANGLELIMIT_H 0x07
#define CT_CCW_ANGLELIMIT_L 0x08
#define CT_CCW_ANGLELIMIT_H 0x09
#define CT_MAX_TORQUE_L 0x0E
#define CT_MAX_TORQUE_H 0x0F
#define CT_STATUS_RETURN 0x10
#define CT_ALARM_LED 0x11
//RAM
#define CT_TORQUE_ENABLE 0x18
#define CT_LED 0X19
#define CT_GOAL_POSITION_L 0x1E
#define CT_GOAL_POSITION_H 0x1F
#define CT_MOVING_SPEED_L 0x20
#define CT_MOVING_SPEED_H 0x21
#define CT_PRESENT_POS_L 0x24
#define CT_PRESENT_POS_H 0x25
#define CT_PRESENT_SPEED_L 0x26
#define CT_PRESENT_SPEED_H 0x27
#define CT_PRESENT_LOAD_L 0x28
#define CT_PRESENT_LOAD_H 0x29
#define CT_PRESENT_VOLTAGE 0x2A
#define CT_REGISTERED_INST 0x2C
#define CT_MOVING 0x2E
//Servo Motor Instruction Set
#define I_PING 0x01
#define I_READ_DATA 0x02
#define I_WRITE_DATA 0x03
#define I_REG_WRITE 0x04
#define I_ACTION 0x05
#define I_RESET 0x06
#define I_SYNC_WRITE 0x07
void AX_setup(int oldid, int newid);
void AX_status_return(int id);
int AX_Ping(int id);
int AX_moving(int id);
void AX_RxPacket(void);
void AX_SendByte(int toSend);
void AX_position(int id, signed int16 angle, int16 speed);
void AX_sync_move(signed int16 angle, int16 speed);
short TX_flag;
char rpacket[16];
int id_num;
#INT_TBE
void TX_ready()
{
TX_flag = 1; //transmit buffer is empty
disable_interrupts(INT_TBE);
}
void main()
{
enable_interrupts(GLOBAL);
delay_ms(2000);
fprintf(LCD,"Servo Test");
delay_ms(2000);
do
{
fprintf(LCD,"?f-90 degrees");
AX_position(1,-90,6);
while(AX_moving(1));
delay_ms(20);
fprintf(LCD,"?f0 degrees");
AX_position(1,0,6);
while(AX_moving(1));
delay_ms(20);
}
while(true);
}
void AX_position(int id, signed int16 angle, int16 speed)//angle -150 to 150 deg, speed to 114 rpm
{
int checksum =0;
angle = angle*34/10+512; //convert from degrees (could be more accurate)
speed = speed*90/10; //convert from rpm
checksum = 0;
AX_SendByte(0xFF); //initiate a packet
AX_SendByte(0xFF);
AX_SendByte(id);
checksum += id;
AX_SendByte(7); //length of packet
checksum += 7;
AX_SendByte(I_WRITE_DATA);
checksum += I_WRITE_DATA;
AX_SendByte(CT_GOAL_POSITION_L);
checksum += CT_GOAL_POSITION_L;
AX_SendByte(*(&angle));//low byte of angle
checksum += *(&angle);
AX_SendByte(*(&angle+1));//high byte of angle
checksum += *(&angle+1);
AX_SendByte(*(&speed));//low byte of speed
checksum += *(&speed);
AX_SendByte(*(&speed+1));//high byte of speed
checksum += *(&speed+1);
checksum = ~checksum;
AX_SendByte(checksum);
}
int AX_Ping(int id)
{
int checksum = 0;
int error;
AX_SendByte(0xFF); //initiate a packet
AX_SendByte(0xFF);
AX_SendByte(id);
checksum += id;
AX_SendByte(2); //length of packet
checksum += 2;
AX_SendByte(I_PING);//ping instruction
checksum += I_PING;
checksum = ~checksum;
AX_SendByte(checksum);
fgetc(AX12); //0xFF
fgetc(AX12); //0xFF
id_num = fgetc(AX12); //ID
fgetc(AX12); //length
error = fgetc(AX12); //error
fgetc(AX12); //checksum
return (error);
}
int AX_moving(int id)
{
int checksum = 0;
int moving;
delay_ms(5);
AX_SendByte(0xFF); //initiate a packet
AX_SendByte(0xFF);
AX_SendByte(id);
checksum += id;
AX_SendByte(4); //length of packet
checksum += 4;
AX_SendByte(I_READ_DATA);//read data instruction
checksum += I_READ_DATA;
AX_SendByte(CT_MOVING);
checksum += CT_MOVING;
AX_SendByte(1);//length of data to be read
checksum += 1;
checksum = ~checksum;
AX_SendByte(checksum);
fgetc(AX12); //0xFF
fgetc(AX12); //0xFF
fgetc(AX12); //ID
fgetc(AX12); //length
fgetc(AX12); //error
moving = fgetc(AX12);
fgetc(AX12); //checksum
return (moving);
}
void AX_SendByte(int toSend)
{
while(TX_flag == 0); //wait for transmit register to be ready
fputc(toSend,AX12);
TX_flag = 0;
enable_interrupts(INT_TBE);
} |
Also, the different compilers are on different computers. I never had the old version installed on my desktop since reformatting, so there shouldn't be a problem. |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Tue Feb 24, 2009 12:51 pm |
|
|
To trouble-shoot this, the first thing I would do is to get rid of the INT_TBE
stuff. You can keep the sendbyte routine, just strip it down to an fputc().
Comment out the lines shown in bold:
Quote: |
//#INT_TBE
//void TX_ready()
//{
// TX_flag = 1; //transmit buffer is empty
// disable_interrupts(INT_TBE);
//}
void AX_SendByte(int toSend)
{
// while(TX_flag == 0); //wait for transmit register to be ready
fputc(toSend,AX12);
// TX_flag = 0;
// enable_interrupts(INT_TBE);
}
void main()
{
// enable_interrupts(GLOBAL);
delay_ms(2000);
fprintf(LCD,"Servo Test");
delay_ms(2000); |
Also, by using the Picbaud program, I see that the best error is -1.36%
on the baud rate. Were you also using a 10 MHz crystal on the
16F877A ? I'm asking, because if the 16F877A baud rate was more
exact, then this could possibly be a problem (on the 18F4620). |
|
|
Robotan
Joined: 12 Jul 2008 Posts: 18 Location: Canada
|
|
Posted: Tue Feb 24, 2009 5:43 pm |
|
|
I just tried the program without the interrupt and it still doesn't work. Both chips were tested with the same 10MHz crystal. With the picbaud program I noticed the error in the 19200 baud rate is much lower at 40MHz, so I tried running the 18F4620 at that speed. But no luck, the new PIC still refuses to talk to the servo motors. |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Tue Feb 24, 2009 7:18 pm |
|
|
I finally figured out what your problem is. It's because starting
with vs. 4.021, CCS decided to do pointer arithmetic correctly.
Here's the note from the old versions page:
Quote: |
4.021 The & unary operator by default no longer returns a generic (int8 *) |
Quote: | int16 angle;
AX_SendByte(*(&angle+1)); //high byte of angle |
This code is actually increasing the address by 2 bytes, because 'angle'
is declared as an 'int16'. This is how it works starting with vs. 4.021.
But you just want to get the MSB of 'angle', so you need to cast it to
an int8 pointer before you add 1 to it. Example:
Quote: | AX_SendByte(*((int8 *)&angle+1)); |
You need to do this everywhere in your code where you're adding 1
to a 16-bit pointer. Be careful, because it's easy to make a mistake
when doing this mod. |
|
|
Robotan
Joined: 12 Jul 2008 Posts: 18 Location: Canada
|
|
Posted: Wed Feb 25, 2009 7:22 am |
|
|
Wow, thanks! I being an amateur programmer would never have caught that. I don't fully understand pointer arithmetic, but that method of accessing the high or low byte of an integer was lifted from a CCS code example. It seemed like a more direct way of doing things rather than copying the high and low bytes to new variables before sending them. I use several serial devices, and in each case I find myself having to split up multi byte variables before sending them, and then re-assembling them on the other side. Are there other ways of doing this that I should be aware of? |
|
|
|
|
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
|