Documentation

https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/twai.html

Initialise CAN Bus

#include "driver/gpio.h"
#include "driver/twai.h"


#define GPIO_CANBUS_TX			GPIO_NUM_15
#define GPIO_CANBUS_RX			GPIO_NUM_16


//***************************************
//***************************************
//********** INITIALISE CANBUS **********
//***************************************
//***************************************
void CanbusInitialise (void)
{
	//Initialize configuration structures using macro initializers
	twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_CANBUS_TX, GPIO_CANBUS_RX, TWAI_MODE_NORMAL);
	//g_config.intr_flags = ESP_INTR_FLAG_LOWMED;		//Optional - move canbus irq to free up the default level 1 IRQ it will take up
	//g_config.tx_queue_len = 16;		//<TWAI_GENERAL_CONFIG_DEFAULT default is 5, use this to increase if needed
	//g_config.rx_queue_len = 10;		//<TWAI_GENERAL_CONFIG_DEFAULT default is 5, use this to increase if needed

	twai_timing_config_t t_config = TWAI_TIMING_CONFIG_250KBITS();		//<<<Set CAN BUS Speed

	//MESSAGE ACCEPTANCE CONFIG
	//ACCEPT ALL CAN MESSAGES:
	//twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();

	//ACCEPT CAN MESSAGES USING A MASK:
	//	ID bits that must match.  SID can bus: 0x0-0x7FF (11 bits), EID can bus: 0x0-0x1FFFFFFF (29 bits)
	//	acceptance_code
	//		The value a message needs to match to be accepted, after the acceptance_mask is applied
	//
	//	acceptance_mask
	//		high bit = that bit position is ignored (doesn't have to match acceptance_code)
	//		low bit = that bit position must match the acceptance_code for the message to be accepted
	//
	//	single_filter
	//		true = Single Filter Mode
	//			The acceptance code and mask to define a single filter. This allows for the first two data bytes of a standard frame to be filtered, or the entirety of an extended frame’s 29-bit ID.
	//		false = Dual Filter Mode
	//			The acceptance code and mask to define two separate filters allowing for increased flexibility of ID’s to accept, but does not allow for all 29-bits of an extended ID to be filtered.

	//Accept EID messages with ID 0x14ff0000 and 0x14ff0100
	twai_filter_config_t f_config = {
				.acceptance_code = (0x14ff0000 << 3),		// <<3 for 29 bit EDID (bottom 3 bits of this field are not used)
				.acceptance_mask = ~(0x1ffffeff << 3),		// <<3 for 29 bit EDID (bottom 3 bits of this field are not used)
				.single_filter = true
	};

	//Accept SID messages with ID 0x0700 and 0x0701
	/*
	twai_filter_config_t f_config = {
				.acceptance_code = (0x0700 << 21),			// <<21 for 11 bit SID (bits 20:16=unused 15:8=DataByte1 7:0=DataByte2)
				.acceptance_mask = ~(0x07fe << 21),			// <<21 for 11 bit SID (bits 20:16=unused 15:8=DataByte1 7:0=DataByte2)
				.single_filter = true
	};
	*/


	//Install TWAI driver
	if (twai_driver_install(&g_config, &t_config, &f_config) != ESP_OK)
	{
		printf("CAN ERROR - Failed to install driver\n");
		return;
	}

	//Start TWAI driver
	if (twai_start() != ESP_OK)
	{
		printf("CAN ERROR - Failed to start driver\n");
		return;
	}


	//----- CONFIGURE CAN BUS ALERTS -----
	//Available alerts:
	//	TWAI_ALERT_TX_IDLE (No more messages queued for transmission)
	//	TWAI_ALERT_TX_SUCCESS (The previous transmission was successful)
	//	TWAI_ALERT_RX_DATA (A frame has been received and added to the RX queue)
	//	TWAI_ALERT_BELOW_ERR_WARN (Both error counters have dropped below error warning limit)
	//	TWAI_ALERT_ERR_ACTIVE (TWAI controller has become error active)
	//	TWAI_ALERT_RECOVERY_IN_PROGRESS (TWAI controller is undergoing bus recovery)
	//	TWAI_ALERT_BUS_RECOVERED (TWAI controller has successfully completed bus recovery)
	//	TWAI_ALERT_ARB_LOST (The previous transmission lost arbitration)
	//	TWAI_ALERT_ABOVE_ERR_WARN (One of the error counters have exceeded the error warning limit)
	//	TWAI_ALERT_BUS_ERROR (A (Bit, Stuff, CRC, Form, ACK) error has occurred on the bus)
	//	TWAI_ALERT_TX_FAILED (The previous transmission has failed)
	//	TWAI_ALERT_RX_QUEUE_FULL (The RX queue is full causing a received frame to be lost)
	//	TWAI_ALERT_ERR_PASS (TWAI controller has become error passive)
	//	TWAI_ALERT_BUS_OFF (Bus-off condition occurred. TWAI controller can no longer influence bus)

	//CONFIGURE ALERTS
	if (twai_reconfigure_alerts((TWAI_ALERT_BUS_OFF | TWAI_ALERT_BUS_RECOVERED | TWAI_ALERT_TX_FAILED), NULL) != ESP_OK)
	{
		printf("CAN ERROR - Failed to reconfigure alerts\n");
	}

}

Handling Alerts

You need to check for and handle the TWAI_ALERT_BUS_OFF and TWAI_ALERT_BUS_RECOVERED to protect against the CAN bus ceasing to function if there are bus issues.

Polling for alerts

twai_read_alerts() uses a ticks_to_wait value, whereby if there is no alert it will wait the specified number of FreeRTOS ticks before returning. The behaviour of using a zero value is not specified, however in our tests of a polling based approach, the following devices returned immediately using pdMS_TO_TICKS(0):

  • ESP32 S3
	//------------------------------------
	//----- CHECK FOR CAN BUS ALERTS -----
	//------------------------------------

	//##### Test code to see if twai_receive() returns without stalling #####
	//gpio_set_direction(GPIO_NUM_3, GPIO_MODE_OUTPUT);
	//gpio_set_level(GPIO_NUM_3, 1);

	uint32_t alerts_triggered;
	twai_read_alerts(&alerts_triggered, pdMS_TO_TICKS(0));		//<<<This stalls for the time period set if there are no alerts
	if (alerts_triggered & TWAI_ALERT_BUS_OFF)
	{
		//----- CAN BUS HAS ENTERED OFF STATE -----
		//The bus-off state is automatically entered when the TWAI controller’s Transmit Error Counter becomes >= 256.
		//Indicates the occurrence of severe errors on the bus or in the TWAI controller.
		//To exit the bus-off state, the TWAI controller must undergo the bus recovery process.
		printf("CAN BUS - TWAI_ALERT_BUS_OFF\n");

		twai_initiate_recovery();	//Start the bus recovery process. The TWAI driver will enter the recovering state and wait for 128 occurrences of the bus-free signal on the TWAI bus before returning to the stopped state. This function will reset the TX queue, clearing any messages pending transmission.
	}

	if (alerts_triggered & TWAI_ALERT_BUS_RECOVERED)
	{
		//----- THE CAN BUS HAS RECOVERED -----
		//TWAI controller has successfully completed bus recovery. The driver is now in the stopped state. We need to call:
		//	twai_driver_install()
		//	twai_start()
		printf("CAN BUS - BUS_RECOVERED\n");

		CanbusInitialise();
	}

	if (alerts_triggered & TWAI_ALERT_TX_FAILED)
	{
		printf("CAN BUS - TWAI_ALERT_TX_FAILED\n");
	}

	//##### Test code to see if twai_receive() returns without stalling #####
	//gpio_set_level(GPIO_NUM_3, 0);

Transmit CAN message

When you transmit a message on CAN, the CAN driver will try continuously forever until it gets an ACK from at least one other CAN bus device that the message was accepted. If there are no other devices it will simply retry until it succeeds. You don’t get a TWAI_ALERT_TX_FAILED alert unless there is an error that causes the transmission to fail (e.g. a bus hardware error that causes TWAI_ALERT_BUS_OFF to happen). If you try and send successive messages, you will get a ESP_ERR_TIMEOUT (0x107) response from calls to twai_transmit() once the CAN tx queue is full (6 messages by default).

	//----------------------------
	//----- TRANSMIT MESSAGE -----
	//----------------------------
	if (CanDoMessageTx)
	{
		CanDoMessageTx = 0;

		printf("CAN BUS - Sending message...\n");

		//Setup message
		twai_message_t CanTxMessage;
		CanTxMessage.identifier = 0x01ff;		//11 or 29 bit identifier
		CanTxMessage.extd = 1;	
						//extd = Extended Frame Format (29bit ID)
		CanTxMessage.rtr = 0;		//Not a Remote Transmission Request (this needs to be set to 0 to ensure this)
		CanTxMessage.data_length_code = 4;

		//The data to send
		for (int i = 0; i < 4; i++)
			CanTxMessage.data[i] = i;

		//SEND MESSAGE
		esp_err_t CanTxErr;
		if ((CanTxErr = twai_transmit(&CanTxMessage, pdMS_TO_TICKS(0))) != ESP_OK)		//ESP_OK: Transmission successfully queued/initiated. <<<If the TX queue is full function will block until more space becomes available or until it times out
		{
			printf("CAN BUS - Failed to queue message for tx 0x%04X\n", CanTxErr);
		}
	} //if (CanDoMessageTx)

Useful things for TX

TX only once

Including the following will cause a TX to be attempted only once instead of continuously until it is acknowledged. Can be useful for debugging:

	CanTxMessage.ss = 1;		//<<<Special mode - Single shot - TX will be attempted only once

Receive CAN messages

Polling for new messages

twai_receive() uses a ticks_to_wait value, whereby if there is nothing received it will wait the specified number of FreeRTOS ticks before returning. The behaviour of using a zero value is not specified, however in our tests of a polling based approach, the following devices returned immediately using pdMS_TO_TICKS(0):

  • ESP32 S3
	int Index;

	//--------------------------------------
	//----- CHECK FOR MESSAGE RECEIVED -----
	//--------------------------------------
	
	//##### Test code to see if twai_receive() returns without stalling #####
	//gpio_set_direction(GPIO_NUM_3, GPIO_MODE_OUTPUT);
	//gpio_set_level(GPIO_NUM_3, 1);

	//HAS A NEW MESSAGE BEEN RECEIVED?
	twai_message_t CanRxMessage;
	if (twai_receive(&CanRxMessage, pdMS_TO_TICKS(0)) == ESP_OK)		//ESP_OK: Message successfully received from RX queue
	{
		//----- NEW CAN BUS MESSAGE RECEIVED -----
		if (CanRxMessage.extd)
			printf("CAN BUS - Message received - Extended Format\n");
		else
			printf("CAN BUS - Message received - Standard Format\n");

		printf("CAN BUS message ID: %ld\n", CanRxMessage.identifier);

		if (!(CanRxMessage.rtr))		//rtr = Message is a Remote Frame
		{
			printf("Data bytes: ");
			for (int Index = 0; Index < CanRxMessage.data_length_code; Index++)
				printf("0x%02X ", CanRxMessage.data[Index]);
			printf("\n");
		}

	} //if (twai_receive(&CanRxMessage, pdMS_TO_TICKS(0)) == ESP_OK)

	
	//##### Test code to see if twai_receive() returns without stalling #####
	//gpio_set_level(GPIO_NUM_3, 0);

ACK’ing of messages

A node that filters out a message using acceptance_code and acceptance_mask will not receive the message, but will still acknowledge it on the bus.

USEFUL?
We benefit hugely from resources on the web so we decided we should try and give back some of our knowledge and resources to the community by opening up many of our company’s internal notes and libraries through mini sites like this. We hope you find the site helpful.
Please feel free to comment if you can add help to this page or point out issues and solutions you have found, but please note that we do not provide support on this site. If you need help with a problem please use one of the many online forums.

Comments

  1. Celio dos Santos Kiotheka

    1 month ago

    Hi,
    these “ifs” are wrong
    if (alerts_triggered && TWAI_ALERT_BUS_OFF)…
    if (alerts_triggered && TWAI_ALERT_BUS_RECOVERED)…
    if (alerts_triggered && TWAI_ALERT_TX_FAILED)…

    So this is correct
    if (alerts_triggered & TWAI_ALERT_BUS_OFF)…
    if (alerts_triggered & TWAI_ALERT_BUS_RECOVERED)…
    if (alerts_triggered & TWAI_ALERT_TX_FAILED)…

    1. admin Article Author

      1 month ago

      Thank you, corrected 🙂

Comments

Your email address will not be published. Required fields are marked *