Part 5: Programming for CAN Communications
The complete source code for this tutorial can be downloaded here.
The HUBO Lab Motor Controller is designed to be connected to a CAN-bus and receive commands from a main computer. These commands are sent via a standard CAN message, which consists of an 11-bit identifier and up to eight bytes of data. This tutorial uses a command format where the message identifier refers to the unique Joint Motor Controller (JMC) number of the board (needed in a practical application where multiple MC boards are connected on a single CAN-bus) and the first byte of data contains a value corresponding to a specific command. The function of the remaining bytes then varies depending on the command.
HMC_CommonDefinitions.h
#define CMD_MBOX 1
#define CANCMD_ZEROENC 0x0A
#define CANCMD_MOTORENABLE 0x0B
#define CANCMD_PWMRUN 0x0D
#define CANCMD_POSCONTROL 0x0E
The above constants are defined in the CAN section of HMC_CommonDefinitions.h. The first constant, CMD_MBOX, represents the mailbox where CAN messages will be received. The other constants correspond to the values that need to be present in the first byte of a CAN message for a specific command to be processed.
HMC_CAN.h
extern void InitializeCAN(void);
extern void ConfigureCAN(UINT8 JMCNum);
extern void TransmitCAN(UINT8 buffer_No, UINT8 *Tx_Data, UINT8 *Tx_DLC);
extern UINT8 ReceiveCAN(UINT8 buffer_No, UINT8 *Rx_Data, UINT8 *Rx_DLC);
extern void ParseCAN(void);
The above declarations found in HMC_CAN.h are for functions defined in HMC_CAN.c that will be called from other source files.
HMC_CAN.c
void InitializeCAN(void)
{
// Configure the eCAN RX and TX pins for eCAN transmissions
EALLOW;
ECanaShadow.CANTIOC.all = ECanaRegs.CANTIOC.all;
ECanaShadow.CANTIOC.bit.TXFUNC = 1;
ECanaRegs.CANTIOC.all = ECanaShadow.CANTIOC.all;
ECanaShadow.CANRIOC.all = ECanaRegs.CANRIOC.all;
ECanaShadow.CANRIOC.bit.RXFUNC = 1;
ECanaRegs.CANRIOC.all = ECanaShadow.CANRIOC.all;
EDIS;
// Disable all Mailboxes
ECanaRegs.CANME.all = 0;
// Set MSG IDs for Mailboxes
ECanaMboxes.MBOX0.MSGID.all = 0;
.
.
.
ECanaMboxes.MBOX15.MSGID.all = 0;
// Set Direction for Mailboxes
ECanaRegs.CANMD.all = 0x00000000; // TX 0, RX 1
// Specify that 8 bytes will be sent/received
ECanaMboxes.MBOX0.MSGCTRL.bit.DLC = 8;
.
.
.
ECanaMboxes.MBOX15.MSGCTRL.bit.DLC = 8;
// No remote frame is requested
ECanaMboxes.MBOX0.MSGCTRL.bit.RTR = 0;
.
.
.
ECanaMboxes.MBOX15.MSGCTRL.bit.RTR = 0;
EALLOW;
ECanaRegs.CANMIM.all = 0x00FF;
The first function defined in HMC_CAN.c is InitializeCAN(). It starts by putting all the mailbox registers into a known state, which is important because their state is unknown on power up. All mailboxes are disabled to begin and setup to transmit/receive 8 bytes of data. They are also set to not use RTR. Note the need to use EALLOW and EDIS to access some registers and the fact that a shadow structure must be used when modifying a single bit because registers require all their bits to be written.
// Request permission to change the configuration registers
ECanaShadow.CANMC.all = ECanaRegs.CANMC.all;
ECanaShadow.CANMC.bit.CCR = 1;
ECanaShadow.CANMC.bit.DBO = 1; // LSB first
ECanaRegs.CANMC.all = ECanaShadow.CANMC.all;
EDIS;
// Wait for CCE bit to be set...
do
{
ECanaShadow.CANES.all = ECanaRegs.CANES.all;
} while(ECanaShadow.CANES.bit.CCE != 1 );
// Configure the eCAN timing
EALLOW;
ECanaShadow.CANBTC.all = ECanaRegs.CANBTC.all;
ECanaShadow.CANBTC.bit.BRPREG = 9; // (BRPREG + 1) = 10 feeds a 15 MHz CAN clock
ECanaShadow.CANBTC.bit.TSEG2REG = 2 ; // to the CAN module. (150 / 10 = 15)
ECanaShadow.CANBTC.bit.TSEG1REG = 10; // Bit time = 15
ECanaShadow.CANBTC.bit.SAM = 1; // sample 3 times
ECanaRegs.CANBTC.all = ECanaShadow.CANBTC.all;
ECanaShadow.CANMC.all = ECanaRegs.CANMC.all;
ECanaShadow.CANMC.bit.CCR = 0;
ECanaRegs.CANMC.all = ECanaShadow.CANMC.all;
EDIS;
// Wait for CCE bit to be set...
do
{
ECanaShadow.CANES.all = ECanaRegs.CANES.all;
} while(ECanaShadow.CANES.bit.CCE != 0 );
// Configure the eCAN for normal mode
// Disable the enhanced features of the eCAN.
EALLOW;
ECanaShadow.CANMC.all = ECanaRegs.CANMC.all;
ECanaShadow.CANMC.bit.STM = 0;
ECanaShadow.CANMC.bit.SCB = 0;
ECanaRegs.CANMC.all = ECanaShadow.CANMC.all;
EDIS;
EALLOW;
ECanaShadow.CANGIM.all = ECanaRegs.CANGIM.all;
ECanaShadow.CANGIM.bit.I0EN = 1;
ECanaRegs.CANGIM.all = ECanaShadow.CANGIM.all;
EDIS;
}
After the mailboxes, InitializeCAN() handles setting up the CAN peripheral so it can actually communicate on the bus. In addition to EALLOW, some registers require an extra level of permission from the CPU in order to be modified. To obtain this permission, the CCR bit is set. The program must then wait until the CCE bit is brought high by the CPU, indicating permission is granted, before continuing.
Perhaps the most important bits being set in this part of the function are those in the CANBTC register, which configures the baud rate that will be used for bus communication. It is very important that all devices on a CAN-bus use the same baud rate. If the baud rates are different, the devices cannot communicate. In this tutorial, a baud rate used is 1Mb/s. Another important bit being set is I0EN, which enables Interrupt Line 0 and will allow messages to be received asynchronously.
void ConfigureCAN(UINT8 JMCNum)
{
// Configure Mailbox 1 to receive msgs
ECanaMboxes.MBOX1.MSGID.bit.STDMSGID=JMCNum;
ECanaRegs.CANMD.bit.MD1 = 1;
// Enable Mailbox
ECanaRegs.CANME.all = 0x02;
}
The next function, ConfigureCAN(), is very simple. Its only job is to configure Mailbox 1 to receive messages and enable it. Where the JMC number used as the message identifier comes from will be shown shortly.
UINT8 ReceiveCAN(UINT8 mboxNum, UINT8 *Rx_Data, UINT8 *Rx_DLC)
{
volatile struct MBOX *Mailbox;
Uint32 lowMboxData = 0;
Uint32 highMboxData = 0;
if(ECanaRegs.CANRMP.all == (ECanaRegs.CANRMP.all |(0x00000001<< mboxNum) ) )
{
Mailbox = &ECanaMboxes.MBOX0 + mboxNum;
lowMboxData = Mailbox->MDL.all;
highMboxData = Mailbox->MDH.all;
*Rx_DLC = (int)Mailbox->MSGCTRL.bit.DLC;
switch(*Rx_DLC)
{
case 8:
Rx_Data[7]= (highMboxData >>24) & 0x000000FF;
case 7:
Rx_Data[6]= (highMboxData >>16) & 0x000000FF;
case 6:
Rx_Data[5]= (highMboxData >>8) & 0x000000FF;
case 5:
Rx_Data[4]= highMboxData & 0x000000FF;
case 4:
Rx_Data[3]= (lowMboxData >>24) & 0x000000FF;
case 3:
Rx_Data[2]= (lowMboxData >>16) & 0x000000FF;
case 2:
Rx_Data[1]= (lowMboxData >>8) & 0x000000FF;
case 1:
Rx_Data[0]= lowMboxData & 0x000000FF;
}
ECanaRegs.CANRMP.all =(ECanaRegs.CANRMP.all |(0x00000001 << mboxNum) );
return(TRUE);
}
else
return(FALSE);
}
ReceiveCAN() is another simple yet important function. When called, it checks the desired Mailbox to determine if there is a message waiting. If so, it uses two 4-byte variables (lowMboxData and highMboxData) to store the entire 8-byte message. Depending on how many of these bytes actually contain data, the bytes are separated and put into an array via a pointer. (Note: There is also a function defined called TransmitCAN(); however it is never used in this tutorial, nor are any mailboxes configured to transmit messages.)
void ParseCAN(void)
{
if(ReceiveCAN(CMD_MBOX, RxData, &RxDLC) == TRUE)
{
switch(RxData[0])
{
.
.
.
case CANCMD_MOTORENABLE: // Enable/Disable IR2131 Driver
if(RxData[1] == ENABLE){
//******** to prevent sudden move
ReadEncoder();
MotorParams.NextRefPos = MotorParams.PrevPos = MotorParams.Pos;
MotorParams.PosControlRun = DISABLE;
MotorParams.PWMRun = DISABLE;
MotorParams.PWMSignal = 0;
//******** to prevent sudden move
StartupIR2131();
}
else if(RxData[1] == DISABLE){
ShutdownIR2131();
}
break;
.
.
.
}
}
}
Finally, the last function in HMC_CAN.c also has the largest task. ParseCAN() takes incoming messages, decides which command, if any, should be executed and then carries it out. It does this by first checking to see if a message has been received. If so, it tries to match the first byte of data to one of the command identifiers and then proceeds from there. At this point, the only command that will be discussed is the command to turn the motor on or off. It is worth noting that what is actually being turned on or off is the IR2131 MOSFET driver; although without turning it on, the motor will not move. Whether the IR2131 is enabled or disabled is determined by the second byte of data in the message. For the moment, the lines of code referring to MotorParams can be ignored.
HMC_Main.c
void main(void)
{
.
.
.
InitializeCAN();
EALLOW;
PieVectTable.ECAN0INTA = &can_isr;
EDIS;
ConfigureCAN(GetJMCNum());
.
.
.
}
.
.
.
UINT8 GetJMCNum(void)
{
UINT8 JMCNum;
JMCNum = GpioDataRegs.GPFDAT.bit.GPIOF11;
JMCNum = (JMCNum << 1)|GpioDataRegs.GPFDAT.bit.GPIOF10;
JMCNum = (JMCNum << 1)|GpioDataRegs.GPFDAT.bit.GPIOF9;
JMCNum = (JMCNum << 1)|GpioDataRegs.GPFDAT.bit.GPIOF8;
JMCNum = (JMCNum << 1)|GpioDataRegs.GPEDAT.bit.GPIOE2;
return JMCNum;
}
interrupt void can_isr(void)
{
ParseCAN();
PieCtrlRegs.PIEACK.all |= PIEACK_GROUP9;
}
.
.
.
The first CAN related function run by the program is InitializeCAN(), called by main(). Next, a pointer to can_isr(), which is an interrupt service routine, is added to the PIE vector table. Whenever a message is received by the motor controller, this function will be triggered and call ParseCAN(). It will also reset the interrupt allowing it to be triggered again. The last CAN related function called is ConfigureCAN(). As explained previously, the JMCNum used as the message identifier by ConfigureCAN() refers to a unique number given to each motor controller on the bus. This number is assigned via a DIP switch on the board (near the CAN and Limit Switch connectors) and returned by the function GetJMCNum(), which takes care of polling the GPIO pins connected to the DIP switch.
Testing the Results:
With the CAN code and motor enable command implemented, a simple test of the motor controller can be conducted. With the board properly connected, powered up, and flashed, notice the LED D6 is lit (it is near the IR2131 component, which has the letters ‘IOR’ on it). This indicates that the IR2131 is disabled. Now, making sure the other device is set to a baud of 1Mb/s, send a CAN message with the JMC number as the identifier and containing the value 0x0B in the first byte of data and 0x01 in the second. If all goes well, the light should go off. Send the same command again, this time replacing the second byte of data with 0x00. The light should turn back on.
Continue to Part 6, Programming for the Hall Effect Sensor and PWM Control...