ESP32 CAN Bus Arduino Library Gotcha Part II

Generally I have found library support for the Arduino environment to be excellent. I’ve used a lot of different available libraries to support everything from sensors to networking protocols. I am currently using the ESP32 built-in CAN Bus support (known as the Two-wire Automotive Interface(TWAI)) for a distributed hardware system being developed for virtual pipe organs. My last post found a small bug in the Arduino CAN Bus library by Sandeep Mistry when setting the bit rate. Different ESP32 revisions ran at different rates because a configuration register previous reserve bit was being set. Recently I have found another bug when attempting to configure the extended ID acceptance filter.

The acceptance filter is used to only pass wanted received CAN Bus messages. Only accepted messages are stored in the receive FIFO. Acceptance filters are based on an CAN Bus ID and a mask. Filtering is ideal for reducing processor load by only passing desired CAN Bus messages.

The acceptance filter has two, 32-bit registers, acceptance code value and acceptance mask value. The code value specifies the ID bit pattern to match and the mask value allows the user to select the bits they want to match. The ESP32 Technical Manual Version 5.2 acceptance filter algorithm is shown below.

In the algorithm when a message bit and acceptance code bit matches then the XNOR output is true (1) and that bit is accepted. For bits the user wants to ignore the acceptance mask bit should be true (1) and that bit is accepted. For an extended CAN Bus ID, if all 29-bits are accepted, then the message is passed to the receive FIFO.

Previously with this library I used acceptance filtering on the 11-bit ID, which worked well. For this VPO system I’ve decided to used the CAN Bus extended 29-bit ID instead. For the Arduino CAN Bus library the method to setup the acceptance filtering is filterExtended() where the 29-bit ID and mask values are supplied. The Sandeep Mistry library version 0.3.1 filterExtended method is given below.

The first issue is the forming of the 29-bit mask value. The mask value is AND’d with the inverted mask value resulting in a value of 0. This requires that all ID bits must matched for the message to be accepted. The mask value result should have 1’s in the bits you are not interested in. Changing the ‘&=’ to just ‘=’ solves this problem.

The second problem is writing the acceptance mask register(3). The current library puts 1’s in the lowest 5 bits. These five bits are:

  • Bit 0 – unused
  • Bit 1 – unused
  • Bit 2 – RTR
  • Bit 3 – ID0
  • Bit 4 – ID1

By setting the lowest 5 bits to 1, the acceptance filter is now ignoring ID[1:0] and RTR. The code should only set RTR = 1, leaving ID[1:0] to the inverted mask bits values, and always use 0 for unused register bits. The updated code is shown below.

Overall this library works well for my application. There have been just a few surprises along the way. With these changes I was successful in filtering extended ID CAN Bus messages.

ESP32 CAN Bus Arduino Library Gotcha

I am building an ESP32 based system that uses CAN bus communication to transfer data between processors. The ESP32 firmware is being developed using the Arduino programming environment. My plan is to purchase ESP32 modules and plug them on interface boards that are interconnected using the CAN bus. The maximum system is about 16 processors.

Controller Area Network (CAN) bus was officially released in 1986 at the Society of Automotive Engineers conference. It was designed as an in-vehicle communications backbone between engine control unit processor, sensors, actuators, indicators, displays, and other vehicle components. CAN bus defines the OSI network model physical and data link layers.

The physical network is a multi-drop bus that uses a single twisted pair wire with approximately one twist per inch and 120 ohm terminations at the ends. The Digikey blog CAN bus image below shows four CAN bus nodes (controller and transceiver), cabling, and terminations.

The CAN bus 2.0 has two frame versions, standard 2.0A and extended 2.0B. The difference between these frame types is the arbitration field with the length of the identifier (11-bits vs. 29-bits) and the control field. The picture from Typhoon HIL Documentation shows the two frame formats.

CAN bus operates on a decentralized networking principle where all nodes are equal. Any node can transmit a data frame when the bus is free. Each frame contains identification information and supports variable data lengths from 0 up to 8 bytes. CAN bus supports a multitude of bits rates from 25kb/s to 1000kb/s and up to 5.0Mb/s for CAN FD. Because it is a decentralized network, there exists bus arbitration and robust error detection used to create a reliable network.

I selected the ESP32 because it has a built in CAN bus 2.0A/B controller (called two-wire automotive interface (TWAI)) that supports bit rates from 12.5kb/s to 1000kb/s and there are available Arduino libraries. The ESP32 does not support CAN FD protocol. The lowest rates 12.5kb/s to 20kb/s are only available on the ESP32 revision 2 or later. To use the ESP32 you must add a CAN bus physical layer transceiver interface.

I setup a ESP32 CAN bus test with two devices. This simple setup should have been straight forward. There are examples with the Sandeep Mistry Arduino CAN bus library (version 0.3.1) I am using and multiple examples on the Internet. I ran into two problems using this library. The first was solved quickly while the second took some digging.

The first problem was with an include file. I’m using the latest Arduino IDE (2.3.2) with ESP32 support. I got a complier error stating that “esp_intr.h” could not be found. A quick search showed a change was needed in the ESP32SJA1000.cpp file changing the reference to #include “esp_intr_alloc.h.” This allowed the code to compile.

In my test I had two ESP32 connected via CAN bus transceivers with the correct terminations. The two devices would not communicate even after testing several different bit rates. I used two different manufacturer’s modules, purchased at different times so I tried two devices from the same manufacturer’s and that worked! Then I tried the other manufacturer’s modules and that worked! But the two different manufacturers modules would not work together.

Finally I hooked up my oscilloscope and measured the bit times. I configured the system to run at 1Mb/s. On the newer ESP32 modules the bit rate was only 0.5Mb/s. The bit rate was correct on the older ESP32 modules.

Espressif Systems in newer ESP32 revisions support CAN bus bit rates less than 25kb/s. This update has caused an issue with the Sandeep Mistry Arduino CAN bus library (and I suspect other libraries as well). The library writes all bits to the REG_IER register that in newer ESP32 revisions has a bit that enables a divide by 2 CAN bit rate. The fix is to modify the REG_IER bit for ESP32 revisions 2 or greater. The modification is needed in the ESP32SJA1000.cpp begin() method after REG_IER write as shown below. After making these changes the CAN bus is now interoperable between different ESP revisions and manufacturer’s modules.

Besides learning about the CAN bus library capabilities I wanted to stress test the CAN bus to determine how well it will work in my application. The stress test uses four senders and one receiver (I only had 5 CAN bus transceivers for the test) to pass messages.

The receiver tracks each individual sender identified by the unique 11-bit ID and verifies all messages are received by checking a message counter. When an error occurs a serial message is printed and the new message counter is used.

Each sender transmits data at random times between 1 and 50ms and each frame has random number of bytes between 1 (message counter) and 8. The message counter, which is always present, is used to track message delivery by the receiver.

Overall I’m pleased with CAN bus performance and reliability. I connected an oscilloscope and observe random message timing and message collision events. I can disconnect/reconnect/reset senders and the receiver properly detects a node message error. I have run the test for hours without any dropped messages at 1Mb/s bit rate on a very short bus (<2feet). Future testing includes adding more nodes and increasing the bus wire length.

ESP32 deep Sleep (RTC) GPIO Output

In the course of developing an ESP32 battery based application I was stumped along with others, on how to maintain a GPIO output in a known state while in deep sleep. My plan was to hold an external peripheral in a reset state to minimize it’s power. The reset signal was an active low signal and the external peripheral had a 10K pullup resistor on the signal. I program the ESP32 in the Arduino environment so I had to find a solution that supported this IDE. I performed a bunch of searches and finally found a working example by Xabi Alaez.

The ESP32 has several “sleep” modes that are listed below. A great article about ESP32 sleep modes is found on the Last Minute Engineers web page.

  • Active keeps everything running including WiFi, Bluetooth, and processing core at all times. This mode consumes more than 240mA with some power spikes as high as 790mA.
  • Modem Sleep is Active minus the WiFi, Bluetooth, and radio. Dependent on the clock speed, which is programmable, consumes between 3mA (low clock speed) to 20mA (high clock speed).
  • Light Sleep the CPU(s) clock is gated while the RTC and ULP co-processor are active. This sleep mode has full RAM retention. Once entering Light Sleep a wake-up source is needed to resume processing. This power consumption is less than 1mA.
  • Deep Sleep the CPU(s), most RAM, and all digital peripherals are powered off while the ULP co-processor and RTC components remain operational. The power consumption is about 0.15mA with the ULP co-processor running.
  • Hibernation only has one RTC timer running and a few RTC GPIOs needed to wake up the chip from hibernation mode. Power consumption is about 2.5uA.

I was most interested in Deep Sleep and Hibernation modes. It turns out you control the power domains before entering Deep Sleep to create Hibernation mode (no API call for hibernation sleep). This also means you can be selective in which power domains are enabled to create a “custom” sleep mode that has less current consumption than Deep Sleep, but not as good as Hibernation.

The ESP power domains are controlled by calling esp_deep_sleep_pd_config() with the power domain and options that include ESP_PD_OPTION_OFF, ESP_PD_OPTION_ON, and ESP_PD_OPTION_AUTO. The auto option powers down the power domain unless it is needed by one of the wakeup options. The different ESP power domains are:

  • ESP_PD_DOMAIN_RTC_PERIPH for RTC IO, sensors and ULP co-processor.
  • ESP_PD_DOMAIN_RTC_SLOW_MEM for RTC slow memory.
  • ESP_PD_DOMAIN_RTC_FAST_MEM for RTC fast memory.
  • ESP_PD_DOMAIN_XTAL for XTAL oscillator.
  • ESP_PD_DOMAIN_RTC8M for internal 8M oscillator.
  • ESP_PD_DOMAIN_VDDSDIO for VDD_SDIO.

During Deep Sleep the only GPIO pins that are available are the RTC IO (RTC_GPIO), which are part of the ESP_PD_DOMAIN_RTC_PERIPH power domain. The ULP co-processor is also active in this power domain. The table below shows the mapping of GPIO pins to analog and RTC functions available through the RTC Mux. The table information is from the Espressif Systems ESP32 Technical Reference Manual Version 4.2, section 5.11, table 21.

GPIO NumRTC GPIO NumAnalog
1
Analog
2
Analog
3
RTC
1
RTC
2
360ADC_HADC1_CH0RTC_GPIO0
371ADC_HADC1_CH1RTC_GPIO1
382ADC_HADC1_CH2RTC_GPIO2
393ADC_HADC1_CH3RTC_GPIO3
344ADC1_CH6RTC_GPIO4I2C_SCL
355ADC1_CH7RTC_GPIO5I2C_SDA
256DAC_1ADC2_CH8RTC_GPIO6I2C_SCL
267DAC_2ADC2_CH9RTC_GPIO7I2C_SDA
338XTAL_32K_NADC1_CH5TOUCH8RTC_GPIO8
329XTAL_32K_PADC1_CH4TOUCH9RTC_GPIO9
410ADC2_CH0TOUCH0RTC_GPIO10
011ADC2_CH1TOUCH1RTC_GPIO11
212ADC2_CH2TOUCH2RTC_GPIO12
1513ADC2_CH3TOUCH3RTC_GPIO13
1314ADC2_CH4TOUCH4RTC_GPIO14
1215ADC2_CH5TOUCH5RTC_GPIO15
1416ADC2_CH6TOUCH6RTC_GPIO16
2717ADC2_CH7TOUCH7RTC_GPIO17
GPIO Pin Function via RTC Mux Selection

To setup the RTC Mux and control the RTC GPIOs in the Arduino environment, calls are made to rtc_gpio_* functions. These functions allow you to initialize the pin as RTC GPIO and set it’s direction and level. My test code adapted from Xabi Alaez’s is shown below.

#include "driver/rtc_io.h"
gpio_num_t RST_PIN = GPIO_NUM_14;

void setup() {
  rtc_gpio_init(RST_PIN);
  rtc_gpio_set_direction(RST_PIN,RTC_GPIO_MODE_OUTPUT_ONLY);
  // toggle reset pin
  rtc_gpio_set_level(RST_PIN,1); //GPIO high
  rtc_gpio_set_level(RST_PIN,0); //GPIO LOW
  rtc_gpio_set_level(RST_PIN,1); //GPIO high
  delay(5000);  // wait
  rtc_gpio_set_level(RST_PIN,0); //GPIO hold low
  esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
  esp_sleep_enable_timer_wakeup(60*1000*1000);  // 60 seconds
  esp_deep_sleep_start();
}

void loop() {
  // none
}

The first step calls rtc_gpio_init(gpio_num_t gpio_num) with the GPIO pin number to the initialize the pad for an analog/RTC function. Next, rtc_gpio_set_direction(gpio_num_t gpio_num, rtc_gpio_mode_t mode) is used to set the pin as output. Finally rtc_gpio_set_level(gpio_num_t gpio_num, uint32_t level level) is used to set the pin output as either HIGH (1) or LOW (0).

After the pin setup and toggled, a delay of 5 seconds is introduced before setting the pin to the value needed during Deep Sleep. Note that the ESP_PD_DOMAIN_RTC_PERIPH is enabled prior to setting the wake up timer and entering Deep Sleep. After waking from Deep Sleep, setup() is repeated again.

Although this approach worked well, I was trying to save 5uA on an external peripheral. By enabling the RTC peripheral that includes the ULP co-processor, I added about 100uA+ to the ESP power. So in the end I didn’t use this approach.

The key to setting an ESP32 output to a known state during Deep Sleep is to select an RTC_GPIO, initialize and operate the pin using RTC_GPIO_* calls, and enabling the RTC peripheral power domain prior to entering Deep Sleep.