上一讲我们使用Arduino DUE进行CAN通信,Arduino DUE进行CAN通信的优势在于,它本身集成了两个CAN控制器模块,可以在板内实现互发。本讲使用另外一个开发板,ESP32,其内置了总线控制器,同样可以实现CAN总线通信。
ESP32开发板
ESP32是乐鑫Espressif旗下的一款集成了32位MCU、2.4Ghz Wi-Fi、蓝牙Bluetooth 5 (LE)的开发板,其系列分为ESP32-S2、ESP32-C3、ESP32-C6、ESP32-H2、ESP32-S3等系列。推荐使用ESP32-S3,理论上其他ESP32系列的开发也可以。
ESP32-S3配置
以ESP32-S3为例,其主要配置如下,
系列名称 | ESP32-S3 |
---|---|
微控制器MCU | Xtensa® 32 位 LX7 双核处理器,主频高达 240 MHz。 |
存储器 | 512KB SRAM、384KB ROM存储空间。并支持外部SPI、Dual SPI、Quad SPI、Octal SPI、QPI、OPI Flash和片外RAM。 |
接口 | 45个可编程GPIO,支持常见外设接口,如SPI、I2S、I2C、PWM、RMT、ADC、UART、SD/MMC主机控制器和TWAIᐪᔿ控制器等。 |
安全 | 基于AES-XTS算法Flash加密和基于RSA算法的安全启动,数字签名和HMAC模块,“世界控制器(World Controller)“莫模块。 |
开发板 | ESP32-S3-DevKitM-1、ESP32-S3-DevKitM-1等 |
针脚定义
作为开发板,开发者最为关心的是针脚定义。ESP32-S3提供了非常丰富的接口。具体如下,
ESP32 CAN总线模块
上一讲中,Arduino Due本身集成了CAN控制器模块,这点反映在Arduino Due的官方文档里,同时,MCU也从底层角度提供了支持。也许您会有疑问,上一节关于ESP32的介绍中没有提到ESP32对CAN总线的支持。
注意在ESP32配置表里,有“TWAIᐪᔿ控制器”,TWAI全称Two-Wire Automobile Interface,这是乐鑫研发的通信协议,对标Bosch的CAN Bus协议。据我阅读两者的文档,后来者“借鉴”先驱颇多。
不管如何,从协议角度来看,TWAI与CAN Bus可以互通。但与Arduino Due不同,ESP32仅仅支持一个TWAI控制器,也就是说,该开发板只能外接一个CAN收发器。我们可以使用上一讲Arduino Due里配置的CAN Bus节点,只要将Arduino Due的CAN收发器(至少一个)的CAN High、CAN Low分别连接起来形成一条”双绞线“就可以完成多者互通。
如何使用Two-Wire Automobile Interface
如前面所述,通过TWAI控制器实现了TWAI协议,但ESP32 TWAI控制器和Arduino Due一样,仅仅是控制器,而不包含收发器,至于两者的关系可以参考上一讲。也就是说,还要额外使用一个TWAI收发器。还行,TWAI协议和CAN Bus本身可以互通,所以, 上一讲中提到的CAN收发器也可以在ESP32开发板里使用。
另外,Arduino Due的CAN总线针脚固定,这个可以理解,毕竟MCU引出的针脚“透传”到外设,而开发板本身不会增加额外的处理逻辑。但ESP32不同,毕竟芯片和TWAI出自同一家厂商,比Bosch仅仅定义协议可操作空间要大,其中一点就是其TWAI可以自定义GPIO针脚。下面通过Arduino IDE实现TWAI消息传送。
具体针脚连线如下,
ESP32-S3(或其他ESP32系列) | CAN收发器 | CAN总线“双绞线(可使用面包板)” | 其他CAN收发器 |
---|---|---|---|
GND | GND | ||
3V3 | 3.3V | ||
CAN RX | GPIO4或其他未使用IO针脚 | ||
CAN TX | GPIO4或其他未使用IO针脚 | ||
CAN H | CAN High | CAN H | |
CAN L | CAN Low | CAN L |
为ESP32配置Arduino IDE开发环境
在安装了Arduino IDE后,通过以下步骤添加ESP32开发板,
依次打开文件(File)、首选项(Preferences),打开首选项对话框。
其他开发板管理器地址(Additional boards manager URLS),输入https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
点击确定,等待更新配置文件更新。
-
打开开发板管理器(Boards Manager),搜索esp32,点击安装(Install)。(我的Arduino IDE已经安装ESP32开发板,显示REMOVE)
图4 在Arduino IDE里添加ESP32开发板 等待几分钟,直到安装完成。
TWAI消息传送的代码实现
完成了环境配置,下面着手编写代码, 在Arduino IDE里新建Sketch,可以命名为CANMessager,添加一个MessageManager.hpp的c++类文件,具体代码如下,
CANMessager.ino
#include "MessageManager.hpp"
MessageManager messageManager;
void setup()
{
// put your setup code here, to run once:
Serial.begin(115200);
while(!Serial);
messageManager.setup();
}
void loop()
{
// put your main code here, to run repeatedly:
messageManager.loop();
}
MessageManager.hpp
#include <driver/twai.h>
// GPIOs utilized to connect to the CAN Bus Transceivers
#define RX_GPIO_PIN GPIO_NUM_4
#define TX_GPIO_PIN GPIO_NUM_5
#define INTERVAL 1000
typedef struct MessageManager
{
public:
typedef enum MessageType
{
normal = 0,
loopback
} MessageType;
private:
bool driver_installed = false;
MessageType messageType = normal;
unsigned long long lastTime = 0;
unsigned long long now = 0;
protected:
public:
void setup()
{
twai_general_config_t general_config = TWAI_GENERAL_CONFIG_DEFAULT(TX_GPIO_PIN, RX_GPIO_PIN, TWAI_MODE_NO_ACK);
general_config.tx_queue_len = 0;
general_config.rx_queue_len = 1000;
// Baud Rate
twai_timing_config_t timing_config = TWAI_TIMING_CONFIG_500KBITS();
// Transparent transmission
twai_filter_config_t filter_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
// Install the TWAI driver
if (twai_driver_install(&general_config, &timing_config, &filter_config) == ESP_OK)
{
printf("TWAI driver installed.");
// Start the TWAI driver
if (twai_start() == ESP_OK)
{
printf("Driver started.");
// Reconfigure the alerts to detect the arrival of new message, errors of frames received, Bus errors and Received Queue Full errors.
uint32_t alerts_to_enable = TWAI_ALERT_RX_DATA | TWAI_ALERT_ERR_PASS | TWAI_ALERT_BUS_ERROR | TWAI_ALERT_RX_QUEUE_FULL;
if (twai_reconfigure_alerts(alerts_to_enable, NULL) == ESP_OK)
{
printf("TWAI alerts reconfigured.");
// All set
driver_installed = true;
}
else
printf("Failed to reconfigure the TWAI alerts.");
}
else
printf("Failed to start the TWAI driver.");
}
else
printf("Failed to install the TWAI driver.");
}
void loop()
{
if (driver_installed)
{
uint32_t alert_triggered;
twai_status_info_t status_info;
// Check if new alert available.
twai_read_alerts(&alert_triggered, portMAX_DELAY);
twai_get_status_info(&status_info);
// Tackle with the alerts.
// New message arrived.
if (alert_triggered & TWAI_ALERT_ERR_PASS)
{
Serial.println("Alert: An (Bit, Stuff, CRC, Form, ACK) error has occurred on the bus.");
Serial.print("Bus error count: ");
Serial.println(status_info.bus_error_count);
}
else if (alert_triggered & TWAI_ALERT_RX_QUEUE_FULL)
{
Serial.println("Alert: the RX queue is full causing the received frames t be lost.");
Serial.printf("RX buffered: %d\n", status_info.msgs_to_rx);
Serial.printf("RX misused: %d\n", status_info.rx_missed_count);
Serial.printf("RX overrun: %d\n", status_info.rx_overrun_count);
}
else if (alert_triggered & TWAI_ALERT_RX_DATA)
{
// Upon the new message arrival.
twai_message_t message;
while(twai_receive(&message, 0) == ESP_OK)
handle_received_message(message);
}
else
{
now = millis();
if (now - lastTime > INTERVAL)
{
// No errors and messages, simulate a loopback message.
switch (messageType)
{
case loopback:
{
uint8_t data[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
transmit_loopback_message(0x01, data);
}
break;
default:
break;
}
lastTime = now;
}
}
}
}
void handle_received_message(const twai_message_t message)
{
Serial.print("Received new ");
Serial.print(message.extd ? "extended ": "standard ");
Serial.print("message, ");
Serial.printf("Identifier: %x, ", message.identifier);
if (!message.rtr)
{
// Not a Remote Transmission Request message.
for (uint8_t i = 0; i < message.data_length_code; i ++)
{
if (i < message.data_length_code - 1)
Serial.printf("Data[%d] = %02x, ", i, message.data[I]);
else
Serial.printf("Data[%d] = %02x", i, message.data[I]);
}
Serial.println();
}
}
void transmit_loopback_message(const uint32_t identifier, const uint8_t *data, const uint8_t data_length_code = TWAI_FRAME_MAX_DLC)
{
// Configure the loopback message
twai_message_t message = {
{
{
.self = 1,
}
},
.identifier = identifier,
.data_length_code = data_length_code,
};
memcpy(message.data, data, data_length_code);
// Transmit the loopback message
transmit_message(message);
}
void transmit_message(const twai_message_t message)
{
String string = "Data with ID [###] will be transmitted: ";
string.replace("###", String(message.identifier, HEX));
Serial.print(string);
for (uint8_t i = 0; i < message.data_length_code; i ++)
{
Serial.printf("Data[%d] = %#02x", i, message.data[I]);
if (i < message.data_length_code - 1)
Serial.print(", ");
}
Serial.println();
// Queue the message for transmission.
esp_err_t result = twai_transmit(&message, portMAX_DELAY);
if(result == ESP_OK)
Serial.printf("%s: Message queued for transmission.\n", esp_err_to_name(result));
else
Serial.printf("%s: Failed to queue the message for transmission.\n", esp_err_to_name(result));
Serial.println();
}
} MessageManager;
简单解释一下,
- MessageManager.hpp存在的目的就是为了接管CANMessager.ino的代理方法
setup()
和loop()
,起到分散代码的目的。 -
#define RX_GPIO_PIN GPIO_NUM_4#define TX_GPIO_PIN GPIO_NUM_5
定义TWAI使用的IO针脚
*twai_general_config_t general_config
,TWAI的主要配置信息,比如针脚,消息模式、接收和发送最大队列。 -
twai_timing_config_t timing_config
,主要配置CAN通信的波特率,背后有系列通过各种段(Segment)、时钟频率计算波特率的算法,后续章节会解释。 -
twai_filter_config_t filter_config
,消息过滤设置。 -
twai_driver_install()
安装驱动。 -
twai_start()
开始服务。 -
twai_reconfigure_alerts
信息启用机制,比如启用某种类型的消息,后续只会收到启用的消息。 -
twai_read_alerts
读取消息到队列;twai_get_status_info
总线状态信息。 -
alert_triggered & TWAI_ALERT_ERR_PASS、alert_triggered & TWAI_ALERT_RX_QUEUE_FULL、alert_triggered & TWAI_ALERT_RX_DATA
各种新消息处理方法。 -
twai_receive(&message, 0)
,接收消息;handle_received_message
处理接收到的消息。 -
transmit_message
,传输消息。
上传运行程序
完成了代码书写,下面开始烧录到开发板,运行程序。注意,Arduino Due端的CAN收发器确保连接到总线,并且也要运行起来。
运行结果打印输出如下,
发送端
Data with ID [18] will be transmitted: Data: [0] = 00, [1] = 0x1, [2] = 0x2, [3] = 0x3, [4] = 0x4, [5] = 0x5, [6] = 0x6, [7] = 0x7
ESP_OK: Message queued for transmission.
Received new standard message, Identifier: 18, Data: [0] = 00, [1] = 01, [2] = 02, [3] = 03, [4] = 04, [5] = 05, [6] = 06, [7] = 07
Data with ID [2c] will be transmitted: Data: [0] = 00, [1] = 0x1, [2] = 0x2, [3] = 0x3, [4] = 0x4, [5] = 0x5, [6] = 0x6, [7] = 0x7
ESP_OK: Message queued for transmission.
Received new standard message, Identifier: 2c, Data: [0] = 00, [1] = 01, [2] = 02, [3] = 03, [4] = 04, [5] = 05, [6] = 06, [7] = 07
Data with ID [ee] will be transmitted: Data: [0] = 00, [1] = 0x1, [2] = 0x2, [3] = 0x3, [4] = 0x4, [5] = 0x5, [6] = 0x6, [7] = 0x7
ESP_OK: Message queued for transmission.
Received new standard message, Identifier: ee, Data: [0] = 00, [1] = 01, [2] = 02, [3] = 03, [4] = 04, [5] = 05, [6] = 06, [7] = 07
接收端
can0 018 [8] 00 01 02 03 04 05 06 07
can0 02C [8] 00 01 02 03 04 05 06 07
can0 0EE [8] 00 01 02 03 04 05 06 07
小结
本讲通过ESP32的TWAI协议,实现CAN总线通信的应用层传输,与上一讲Arduino Due的互通,接近于CAN总线在实际使用的场景。相信通过这两讲的介绍,特别亲手操刀,让您真切体会到了CAN通信没那么复杂。