上一講我們使用Arduino DUE進行CAN通信,Arduino DUE進行CAN通信的優(yōu)勢在于,它本身集成了兩個CAN控制器模塊,可以在板內(nèi)實現(xiàn)互發(fā)。本講使用另外一個開發(fā)板,ESP32,其內(nèi)置了總線控制器,同樣可以實現(xiàn)CAN總線通信。
ESP32開發(fā)板
ESP32是樂鑫Espressif旗下的一款集成了32位MCU、2.4Ghz Wi-Fi、藍牙Bluetooth 5 (LE)的開發(fā)板,其系列分為ESP32-S2、ESP32-C3、ESP32-C6、ESP32-H2、ESP32-S3等系列。推薦使用ESP32-S3,理論上其他ESP32系列的開發(fā)也可以。
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算法的安全啟動,數(shù)字簽名和HMAC模塊,“世界控制器(World Controller)“莫模塊。 |
開發(fā)板 | ESP32-S3-DevKitM-1、ESP32-S3-DevKitM-1等 |
針腳定義
作為開發(fā)板,開發(fā)者最為關心的是針腳定義。ESP32-S3提供了非常豐富的接口。具體如下,
ESP32 CAN總線模塊
上一講中,Arduino Due本身集成了CAN控制器模塊,這點反映在Arduino Due的官方文檔里,同時,MCU也從底層角度提供了支持。也許您會有疑問,上一節(jié)關于ESP32的介紹中沒有提到ESP32對CAN總線的支持。
注意在ESP32配置表里,有“TWAI??控制器”,TWAI全稱Two-Wire Automobile Interface,這是樂鑫研發(fā)的通信協(xié)議,對標Bosch的CAN Bus協(xié)議。據(jù)我閱讀兩者的文檔,后來者“借鑒”先驅(qū)頗多。
不管如何,從協(xié)議角度來看,TWAI與CAN Bus可以互通。但與Arduino Due不同,ESP32僅僅支持一個TWAI控制器,也就是說,該開發(fā)板只能外接一個CAN收發(fā)器。我們可以使用上一講Arduino Due里配置的CAN Bus節(jié)點,只要將Arduino Due的CAN收發(fā)器(至少一個)的CAN High、CAN Low分別連接起來形成一條”雙絞線“就可以完成多者互通。
如何使用Two-Wire Automobile Interface
如前面所述,通過TWAI控制器實現(xiàn)了TWAI協(xié)議,但ESP32 TWAI控制器和Arduino Due一樣,僅僅是控制器,而不包含收發(fā)器,至于兩者的關系可以參考上一講。也就是說,還要額外使用一個TWAI收發(fā)器。還行,TWAI協(xié)議和CAN Bus本身可以互通,所以, 上一講中提到的CAN收發(fā)器也可以在ESP32開發(fā)板里使用。
另外,Arduino Due的CAN總線針腳固定,這個可以理解,畢竟MCU引出的針腳“透傳”到外設,而開發(fā)板本身不會增加額外的處理邏輯。但ESP32不同,畢竟芯片和TWAI出自同一家廠商,比Bosch僅僅定義協(xié)議可操作空間要大,其中一點就是其TWAI可以自定義GPIO針腳。下面通過Arduino IDE實現(xiàn)TWAI消息傳送。
具體針腳連線如下,
ESP32-S3(或其他ESP32系列) | CAN收發(fā)器 | CAN總線“雙絞線(可使用面包板)” | 其他CAN收發(fā)器 |
---|---|---|---|
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開發(fā)環(huán)境
在安裝了Arduino IDE后,通過以下步驟添加ESP32開發(fā)板,
依次打開文件(File)、首選項(Preferences),打開首選項對話框。
其他開發(fā)板管理器地址(Additional boards manager URLS),輸入https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
點擊確定,等待更新配置文件更新。
-
打開開發(fā)板管理器(Boards Manager),搜索esp32,點擊安裝(Install)。(我的Arduino IDE已經(jīng)安裝ESP32開發(fā)板,顯示REMOVE)
圖4 在Arduino IDE里添加ESP32開發(fā)板 等待幾分鐘,直到安裝完成。
TWAI消息傳送的代碼實現(xiàn)
完成了環(huán)境配置,下面著手編寫代碼, 在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的主要配置信息,比如針腳,消息模式、接收和發(fā)送最大隊列。 -
twai_timing_config_t timing_config
,主要配置CAN通信的波特率,背后有系列通過各種段(Segment)、時鐘頻率計算波特率的算法,后續(xù)章節(jié)會解釋。 -
twai_filter_config_t filter_config
,消息過濾設置。 -
twai_driver_install()
安裝驅(qū)動。 -
twai_start()
開始服務。 -
twai_reconfigure_alerts
信息啟用機制,比如啟用某種類型的消息,后續(xù)只會收到啟用的消息。 -
twai_read_alerts
讀取消息到隊列;twai_get_status_info
總線狀態(tài)信息。 -
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
,傳輸消息。
上傳運行程序
完成了代碼書寫,下面開始燒錄到開發(fā)板,運行程序。注意,Arduino Due端的CAN收發(fā)器確保連接到總線,并且也要運行起來。
運行結(jié)果打印輸出如下,
發(fā)送端
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
小結(jié)
本講通過ESP32的TWAI協(xié)議,實現(xiàn)CAN總線通信的應用層傳輸,與上一講Arduino Due的互通,接近于CAN總線在實際使用的場景。相信通過這兩講的介紹,特別親手操刀,讓您真切體會到了CAN通信沒那么復雜。