ESP32-S2是繼ESP32之后新出的一款的MCU,而USB接口是ESP32-S2的一大特色,雖然使用的只是USB1.1協(xié)議,但是相比于串口而言傳輸速度還是要快很多的。對于音頻或者視頻等數(shù)據(jù)的傳輸,使用usb明顯是比串口有優(yōu)勢的。
因為前段時間項目需求,需要用到ESP32-S2的USB,于是就花了些時間研究了一下,發(fā)現(xiàn)網(wǎng)上關(guān)于ESP32-S2 USB的介紹很少而且大多資料都過時了,于是就有了這篇博客。
好了,廢話不多說了,馬上開始講解。
1 硬件介紹
本文的硬件配置如下:
模塊 | 型號 | 說明 |
---|---|---|
ESP32-S2 | ESP32-S2-WROVER | 這是樂鑫的一款模組,內(nèi)部主要是用樂鑫的ESP32-S2再加上一個4M FLASH和2M PSRAM組成,開發(fā)板用的是樂鑫的ESP32-S2-SAOLA |
ESP32-S2的引腳很多我就不一一介紹了,這一講主要用到的UART0和USB(GPIO19,GPIO20)。
1.1 硬件連接
我這里用的是開發(fā)板,硬件連接比較簡單。
UART0通過USB轉(zhuǎn)TTL芯片連接到PC端。
USB通過GPIO19和GPIO20直連PC端的USB接口。
引腳 | 描述 | 說明 |
---|---|---|
GPIO19 | USB D- | USB信號線,直連PC端即可,不需要接轉(zhuǎn)換芯片 |
GPIO20 | USB D+ | USB信號線,直連PC端即可,不需要接轉(zhuǎn)換芯片 |
U0TXD | 串口TX | 方便調(diào)試使用,需要接USB轉(zhuǎn)換TTL才能連接到PC端 |
U0RXD | 串口RX | 方便調(diào)試使用,需要接USB轉(zhuǎn)換TTL才能連接到PC端 |
2 軟件開發(fā)
2.1 安裝開發(fā)板
關(guān)于ESP32-S2 Arduino的環(huán)境搭建我之前出過教程了,這里就不多說了,不懂的同學(xué)可以先看下我之前的博客。
2.2 安裝庫
打開Arduino IDE,依次打開 工具 -> 管理庫…
在搜索框輸入需要安裝的庫名稱,找到對應(yīng)的庫,點擊安裝即可。
本文需要使用的Arduino庫如下:
Arduino庫 | 版本 | 說明 |
---|---|---|
ESP32TinyUSB | 1.3.4 | USB相關(guān)庫,使用該庫要確保ESP32庫版本在2.0.0以上 |
esp32 | 2.0.1 | 建議使用該版本,v2.0.2有個usb相關(guān)的結(jié)構(gòu)體定義改了,跟ESP32TinyUSB庫不兼容。 如果非要用2.0.2以上版本就需要在ESP32TinyUSB和esp32兩者之前選擇一個把client_event_callback的定義改掉 |
2.3 運(yùn)行示例代碼
ESP32TinyUSB庫自帶很多examples,我們打開一個cdc的示例代碼先測試一下USB通訊。
示例代碼如下:
/**
* Simple CDC device connect with putty to use it
* author: chegewara
* Serial - used only for logging
* Serial1 - can be used to control GPS or any other device, may be replaced with Serial
*/
#include "cdcusb.h"
#if CFG_TUD_CDC
CDCusb USBSerial;
class MyUSBCallbacks : public CDCCallbacks {
void onCodingChange(cdc_line_coding_t const* p_line_coding)
{
int bitrate = USBSerial.getBitrate();
Serial.printf("new bitrate: %dn", bitrate);
}
bool onConnect(bool dtr, bool rts)
{
Serial.printf("connection state changed, dtr: %d, rts: %dn", dtr, rts);
return true; // allow to persist reset, when Arduino IDE is trying to enter bootloader mode
}
void onData()
{
int len = USBSerial.available();
Serial.printf("nnew data, len %dn", len);
uint8_t buf[len] = {};
USBSerial.read(buf, len);
Serial.write(buf, len);
}
void onWantedChar(char c)
{
Serial.printf("wanted char: %cn", c);
}
};
void setup()
{
Serial.begin(115200);
USBSerial.setCallbacks(new MyUSBCallbacks());
USBSerial.setWantedChar('x');
if (!USBSerial.begin())
Serial.println("Failed to start CDC USB stack");
}
void loop()
{
while (Serial.available())
{
int len = Serial.available();
char buf1[len];
Serial.read(buf1, len);
int a = USBSerial.write((uint8_t*)buf1, len);
}
}
#endif
運(yùn)行結(jié)果如下:
設(shè)備管理器能看到兩個com口(一個是串口轉(zhuǎn)換芯片,一個是ESP32-S2的USB)。
用串口助手先打開UART對應(yīng)的端口,波特率115200。再打開一個串口助手,連接USB虛擬串口對應(yīng)的com口,此時能看到UART會出現(xiàn)一些log。
USB連接上之后,兩個串口助手之間可以互發(fā)數(shù)據(jù),說明USB通訊是沒問題的。
提示:如果燒錄程序之后出現(xiàn)一直重啟的現(xiàn)象,可能是因為MCU原本出廠的固件有一部分沒有被擦除導(dǎo)致的,可以使用樂鑫的flash燒錄工具對整個MCU進(jìn)行擦除之后再燒錄Arduino的程序。
2.4 USB傳輸速度測試
簡單寫一個測試代碼用來測試USB數(shù)據(jù)傳輸的速度。
示例代碼如下:
#include "cdcusb.h"
#include "Arduino.h"
#include <esp_heap_caps.h>
#define FILE_SIZE 971240 // 測試文件的大小
uint8_t *rx_buf;
uint32_t rx_num = 0;
uint8_t first_time_flag = 1;
long lTime;
CDCusb CDCUSBSerial;
class MyCDCCallbacks : public CDCCallbacks {
void onCodingChange(cdc_line_coding_t const* p_line_coding)
{
int bitrate = CDCUSBSerial.getBitrate();
Serial.printf("new bitrate: %dn", bitrate);
}
bool onConnect(bool dtr, bool rts)
{
Serial.printf("connection state changed, dtr: %d, rts: %dn", dtr, rts);
return true; // allow to persist reset, when Arduino IDE is trying to enter bootloader mode
}
void onData()
{
if(first_time_flag)
{
first_time_flag = 0;
lTime = micros();
}
int len = CDCUSBSerial.available();
CDCUSBSerial.read(&rx_buf[rx_num], len);
rx_num += len;
if(rx_num >= FILE_SIZE)
{
lTime = micros() - lTime;
Serial.printf("time: %f s n", lTime / 1000000.0);
Serial.printf("speed:%f kb/s", ((float)FILE_SIZE / 1024.0) / (lTime / 1000000.0));
first_time_flag = 1;
rx_num = 0;
}
}
};
void setup()
{
Serial.begin(115200);
if (!CDCUSBSerial.begin())
Serial.println("Failed to start CDC USB stack");
CDCUSBSerial.setCallbacks(new MyCDCCallbacks());
rx_buf = (uint8_t*)ps_malloc(FILE_SIZE);
}
void loop()
{
}
通過串口助手往USB發(fā)送一個大文件(971240字節(jié),約948.5kb)。
提示:這里串口助手打開文件時顯示的時間是按當(dāng)前波特率估算出來的,但是實際上我們用的是虛擬串口,USB傳輸是沒有波特率這個參數(shù)的,這里波特率不管設(shè)置為多少,實際的速度都一樣,都是以USB傳輸速度為準(zhǔn)。
通過ESP32-S2的串口0打印實際的傳輸?shù)臅r間和速度。
經(jīng)過sscom這個串口助手傳輸文件測試,ESP32-S2 USB的最大傳輸速度在190kb/s左右,實際上加上一些應(yīng)用代碼之后,速度會有所下降,約160kb/s左右(這個速度跟具體的應(yīng)用有關(guān))。
因為USB接收是中斷處理的,MCU如果一直處于閑置狀態(tài),那USB的數(shù)據(jù)傳輸速度可以達(dá)到最大。反之,MCU如果一直在運(yùn)行其他應(yīng)用代碼,那么在接收USB數(shù)據(jù)時只能通過頻繁的中斷來完成數(shù)據(jù)的讀取,此時接收的效率明顯是要下降一些的。另外,傳輸速度跟MCU的接收方式也有關(guān)系,USB1.1最大支持一次接收64字節(jié),所以MCU在進(jìn)入回調(diào)函數(shù)時,應(yīng)該根據(jù)把當(dāng)前收到的所有數(shù)據(jù)一次性讀取完,而不是每次回調(diào)只讀一個字節(jié)。
后期測試補(bǔ)充:
在使用sscom這個串口助手時,文件的傳輸速度跟設(shè)置的波特率無關(guān),但是后來用另外一個串口助手(UartAssist)時發(fā)現(xiàn)用這個工具設(shè)置的波特率跟實際傳輸速度有關(guān)聯(lián),這就很奇怪了,因為這個只是虛擬串口,實際上是按照USB1.1協(xié)議來傳輸數(shù)據(jù)的,理論上不應(yīng)該出現(xiàn)這種情況的。然后我測試了多種不同的波特率,發(fā)現(xiàn)波特率較小時,實際傳輸速度與波特率基本一致,波特率越大速度越快,當(dāng)波特率增大到2M時,速度與之前sscom串口助手測試的速度接近。繼續(xù)增大波特率到某個值之后,不管波特率設(shè)置多少,速度都不再增大了。最大傳輸速度在270kb/s左右。
從目前的現(xiàn)象來看,USB的傳輸速度跟串口工具本身也有關(guān)系,具體的原因還沒搞清楚,有懂的老哥解答一下嗎?
結(jié)束語
好了,關(guān)于ESP32-S2 USB的使用就介紹到這里。如果這篇文章對你有幫助,可以點贊收藏,如果還有什么問題,歡迎在評論區(qū)留言或者私信給我。
補(bǔ)充說明:
最近我經(jīng)常收到一些私信,這是不是真的USB?這是串口轉(zhuǎn)USB吧?
所以我這里統(tǒng)一補(bǔ)充說明一下,如何區(qū)分USB CDC和串口。
CDC類USB和HID不同,它枚舉出來的設(shè)備確實是一個串行設(shè)備,看著是很像串口,但實際上是不一樣的。
主要有以下幾個區(qū)別:
區(qū)別 | USB | 串口 |
---|---|---|
1 | 兩個USB之間是直連的,不需要轉(zhuǎn)換電平 | 串口是TTL電平的,如果沒有轉(zhuǎn)換芯片,是沒法直接連接PC端的USB接口的。常用的轉(zhuǎn)換IC如:CH340、CP2102、PL2303、FT232、MAX232等 |
2 | 傳輸速度快 | 傳輸速度慢 |
3 | 遵循USB協(xié)議(可通過邏輯分析儀抓取和解析) | 遵循串口協(xié)議(可通過邏輯分析儀抓取和解析) |
4 | USB有枚舉過程(可通過BusHound抓取通訊過程) | 串口不存在枚舉過程 |