CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

migrating serial communication code from 16F877A to 18F4620

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
Robotan



Joined: 12 Jul 2008
Posts: 18
Location: Canada

View user's profile Send private message

migrating serial communication code from 16F877A to 18F4620
PostPosted: Sat Feb 21, 2009 9:09 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Sat Feb 21, 2009 11:07 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Sun Feb 22, 2009 3:16 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Sun Feb 22, 2009 3:28 pm     Reply with quote

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 ?

Quote:
#FUSES PBADEN

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

View user's profile Send private message

PostPosted: Sun Feb 22, 2009 10:01 pm     Reply with quote

Nope, I'm not doing any low level stuff simply because I don't know how Smile
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

View user's profile Send private message

PostPosted: Sun Feb 22, 2009 10:47 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Mon Feb 23, 2009 10:09 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Tue Feb 24, 2009 1:55 am     Reply with quote

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

View user's profile Send private message

PostPosted: Tue Feb 24, 2009 7:22 am     Reply with quote

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

View user's profile Send private message

PostPosted: Tue Feb 24, 2009 12:51 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Tue Feb 24, 2009 5:43 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Tue Feb 24, 2009 7:18 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Wed Feb 25, 2009 7:22 am     Reply with quote

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?
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
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