2020年9月4日 星期五

Bring TI Tiva C MCUs Able to Send and Receive CAN Bus Data in One Unit

 

※ The code you could get from here.

    The TI Tiva series are very powerful microcontrollers(MCU) : support with floating point. high frequency, abundant peripherals, large size flash and RAM .. and so forth. The series is very high-end, eligible to be adopted in the complex applications. 

     One of the features is, these MCUs support with CAN(Controller Area Network) bus natively : thus it is not necessary to use an external (SPI) CAN bus controller to deal with the communication. However, I found there are some knacks do not denoted explicitly in the example codes or the website. So I write down this post to the people interested in this issue.

    I use EK-TM4C1294XL with Keil C. 

    If you work in the other board (EK-TM4C123GXL), the project setting to the target CPU and the GPIO pins must be modified.


零. The necessary components.

      Two Tiva board (mine both are EK-TM4C1294XL) and  two CAN bus transceivers (and some female-female dupont Line).  The transceiver modules I used are SN65HVD230.

一. Initialize CAN bus and register the interrupt callback function :

    As the CANbus example, we initialize CAN-bus peripheral for CAN1 with bitrate CANBUS_BITRATE (1*1000*1000, 1MHz) :

	{
		SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
		GPIOPinConfigure(GPIO_PB0_CAN1RX);
		GPIOPinConfigure(GPIO_PB1_CAN1TX);
		GPIOPinTypeCAN(GPIO_PORTB_BASE, GPIO_PIN_0 | GPIO_PIN_1);

		SysCtlPeripheralEnable(SYSCTL_PERIPH_CAN1);
		CANInit(CAN1_BASE);

		CANBitRateSet(CAN1_BASE, g_ui32SysClock, CANBUS_BITRATE);
		CANIntRegister(CAN1_BASE, canbus1_interrupt_handler);
		CANIntEnable(CAN1_BASE, CAN_INT_MASTER | CAN_INT_ERROR | CAN_INT_STATUS);
		IntEnable(INT_CAN1);

		CANEnable(CAN1_BASE);
	}

Beware, for CAN1, RX is GPIO PB0 , and TX is PB1, so we need to solder the pin header on the two pins.

    And, of course, modify the startup_rvmdk.S file to register the interrupt handle for CAN1 :

;******************************************************************************
;
; External declarations for the interrupt handlers used by the application.
;
;******************************************************************************
:
:
	EXTERN	canbus1_interrupt_handler
;******************************************************************************
;******************************************************************************
;
; The vector table.
;
;******************************************************************************
        EXPORT  __Vectors
__Vectors
:
:
        DCD     IntDefaultHandler           ; CAN0
        DCD     canbus1_interrupt_handler   ; CAN1
:
:

The interrupt callback function name as canbus1_interrupt_handler, its implementation is : 

unsigned int g_is_canbus_error_occurred = 0;
unsigned int g_controller_status = 0;

unsigned int g_is_canbus_sent = 0;
unsigned int g_sent_canbus_message_count = 0;

unsigned int g_received_canbus_message_count = 0;
unsigned int g_is_canbus_received = 0;


#define SEND_MESSAGE_OBJ							(2)
#define RECEIVE_MESSAGE_OBJ							(1)

void canbus1_interrupt_handler(void)
{
	uint32_t interrupt_cause;

	interrupt_cause = CANIntStatus(CAN1_BASE, CAN_INT_STS_CAUSE);

	if(CAN_INT_INTID_STATUS == interrupt_cause)
	{
		g_controller_status = CANStatusGet(CAN1_BASE, CAN_STS_CONTROL);

		/*avoid the message which should be filtered out triggering the interrupt*/
		g_controller_status &= ~CAN_STATUS_TXOK;
		g_controller_status &= ~CAN_STATUS_RXOK;
		
		if(CAN_STATUS_LEC_NONE != g_controller_status)
			g_is_canbus_error_occurred = 1;
	}


	if(0 == g_is_canbus_error_occurred)
	{
		switch(interrupt_cause)
		{
		case SEND_MESSAGE_OBJ:
			g_sent_canbus_message_count++;
			g_is_canbus_sent = 1;
			CANIntClear(CAN1_BASE, SEND_MESSAGE_OBJ);
			break;
		case RECEIVE_MESSAGE_OBJ:
			g_received_canbus_message_count++;
			g_is_canbus_received = 1;
			CANIntClear(CAN1_BASE, RECEIVE_MESSAGE_OBJ);
			break;
		}
	}

}

     The most importance of this post is here : the highlight lines will filter-out the interrupts from the unwanted message id. If we do not filter them out, the interrupt would be triggered by every message transmission( even the message is sent by the device itself), that leads the false error. 


二. The send and receive processing.

The CAN bus communication rules are :

甲. For one frame, the max frame size is 8 bytes.

乙. Send and receive mechanism, is based on message id and mask. It matches while <coming data message id > & ui32MsgIDMask ==  ui32MsgID & ui32MsgIDMask.

丙. The message id size is 7 11 bits.

※ It is possible to use the mask and id mechanism to group the devices, for example, there is a group with receiving id 0x01X (0x011, 0x012, 0x013...), and filter 0x010 and 0x01X (like 0x011). While the receiving message id is 0x010, it is the group message; on the other hand, if the id is 0x01X, the message is only specified for the device.

So our initialization of the message objects code is as below:

#if(1)
	#define SEND_MESSAGE_ID							(0x0A0)
	#define RECEIVE_MESSAGE_ID						(0x0B0)
#else
	#define SEND_MESSAGE_ID							(0x0B0)
	#define RECEIVE_MESSAGE_ID						(0x0A0)
#endif
:
:
#define MAX_DATA_FRAME_SIZE							(8)
	{

		tCANMsgObject canbus_send_message;
		unsigned char can_send_buffer[MAX_DATA_FRAME_SIZE];

		tCANMsgObject canbus_receive_message;
		unsigned char can_receive_buffer[MAX_DATA_FRAME_SIZE];

		{
			canbus_send_message.ui32MsgID = SEND_MESSAGE_ID;
			canbus_send_message.ui32MsgIDMask = 0;
			canbus_send_message.ui32Flags = MSG_OBJ_TX_INT_ENABLE;
			canbus_send_message.pui8MsgData = &can_send_buffer[0];
		}


#define CAN_SFF_MASK 								(0x000007FFU) /* standard frame format (SFF) */
		{
			canbus_receive_message.ui32MsgID = RECEIVE_MESSAGE_ID;
			canbus_receive_message.ui32MsgIDMask = CAN_SFF_MASK;
			canbus_receive_message.ui32Flags = MSG_OBJ_RX_INT_ENABLE | MSG_OBJ_USE_ID_FILTER;
			canbus_receive_message.ui32MsgLen = sizeof(can_receive_buffer);
			canbus_receive_message.pui8MsgData = &can_receive_buffer[0];
		}

		CANMessageSet(CAN1_BASE, RECEIVE_MESSAGE_OBJ, &canbus_receive_message, MSG_OBJ_TYPE_RX);
		:
		:
	}

    The code is : Device 甲 sends the data to message id 0x0A0, and receives the data from message id 0x0B0 ;  on the contrary, device 乙 receives from id 0x0A0, and send to id 0x0B0. To filter out the other message (including the message sent by the device itself), here we apply the mask CAN_SFF_MASK, which make the device only receive the data from the id equaling to canbus_receive_message.ui32MsgID.

    For the send and receive implementation, they are very straight forward :

		while(1)
		{
			:
			:
			if(0 != g_is_canbus_error_occurred)
			{
				printf("canbus_error_occurred, controller_status = 0x%02x\r\n", g_controller_status);
				g_is_canbus_error_occurred = 0;
			}

			if(0 != g_is_canbus_sent)
			{
				printf("msg sent, count = %u\r\n", g_sent_canbus_message_count);
				g_is_canbus_sent = 0;
			}

			/*send message*/
			{
				:
				CANMessageSet(CAN1_BASE, SEND_MESSAGE_OBJ, &canbus_send_message, MSG_OBJ_TYPE_TX);
				:
			}
			
			/*receive message*/
			if(0 != g_is_canbus_received)
			{
				memset(&can_receive_buffer[0], 0, sizeof(can_receive_buffer));
				CANMessageGet(CAN1_BASE, RECEIVE_MESSAGE_OBJ, &canbus_receive_message, 0);
				g_is_canbus_received = 0;

				:
				if(canbus_receive_message.ui32Flags & MSG_OBJ_DATA_LOST)
					printf("CAN message loss detected\r\n");
				:
			}
		}/*while 1*/

※ In my GitHub code, the sending is uptime data, and the receiving prints that. Those make the code a little bit tedious, I leave them out.



三. Connect the wire and run the code.

Connect the 2 boards with 2 CAN bus transceivers.

One sends to id 0x0A0, receives from id 0x0B0 ; the other is reverse. it is about main.c line 31, to set #if(0) or #if(1).

#if(0)
	#define SEND_MESSAGE_ID							(0x0A0)
	#define RECEIVE_MESSAGE_ID						(0x0B0)
#else
	#define SEND_MESSAGE_ID							(0x0B0)
	#define RECEIVE_MESSAGE_ID						(0x0A0)
#endif


And the UART outputs are :



To here, the work is done.


Summary :  
   甲. For TI Tiva C, the CAN bus interrupt handler will be called by any message transmission. It is necessary to filter out the unwanted triggering.
   乙. CAN bus transmission is based on message id, not the sender and recipient.

Reference :

1. https://ohm.ninja/tiva-c-series-can-bus-with-mcp2551/

2. The CAN bus examples of TivaWare for TM4C Series Peripheral Driver Library, which could be downloaded from here.

沒有留言:

張貼留言