FireBeetle的USB Host Shield

目前市面上最成熟的 USB Host 库当属Oleg Mazurov的USB Host Shield Library 2.0(项目地址 https://github.com/felis/USB_Host_Shield_2.0),几乎可以兼容市面上的所有 Arduino 板,从 Atmel 328P/32U4到 ATMage 2560 再到 EP32都能够使用这个库来驱动 USB 设备。这次我专门为 FireBeetle设计了一个Shield 使得FireBeetle能够驱动一些USB 设备。

首先,依然是硬件设计部分。USB Host Shield Library是基于 MAX3421e实现的,下面可以看作是这个芯片的一个最小系统,需要外接一颗12Mhz 的晶振。此外可以看到这个芯片本身带有GPIO引脚,可以用来做 GPI或者 GPO ,在一些情况下可以弥补主控 GPIO 不足的缺点,但是本次设计并未使用这些引脚。

这个可以看作时 Max4321e 的最小系统了

上述芯片和 FireBeetle接口如下图,二者是通过 SPI 来进行通讯的。下图中IO18/23/19/13 就是 SPI 接口。特别注意,我在板子上预留了 JP3/JP2 这是为以后堆叠使用可能发生的引脚冲突预留的,正常使用的时候 JP1和JP3(称作短接电阻) 是需要焊接起来的;JP2 的功能是预留给某些情况下 USB 设备需要功耗较高,FireBeetle 无法提供时就需要考虑使用外部5V进行供电,此时如果你还使用FIreBeetle 上的 USB端口调试,既有可能出现外部5V 和USB 上面的5V 电压不同的情况。因此,这种情况下需要断开 JP2。

Fire Beetle 端

接下来继续进行硬件设计,绘制PCB。特别注意,下面电路图时有缺陷的,再晶振下面走线,有可能对信号有影响。建议需要的朋友重新设计 PCB。

3D 预览结果:

之后,打板,焊接成品如下,黑色PCB 很 DFRobot 出品的感觉。

接下来就用这个板子配合 FireBeetle来实现一个 USB 键盘转蓝牙的作品。

  1. 为了驱动这个 USB Host Shield 需要对 USB Host Library 做一点修改。
    1. 1 UsbCore.h 中修改为我们使用的SS和INT Pin
#elif defined(ESP32)
//LABZ typedef MAX3421e<P5, P17> MAX3421E; // ESP32 boards
typedef MAX3421e<P13, P26> MAX3421E; // ESP32 boards  // LABZ
#else
  1. 2 usbhost.h 中修改为我们使用的 SPI
#elif defined(ESP32)
//LABZ typedef SPi< P18, P23, P19, P5 > spi;
typedef SPi< P18, P23, P19, P13 > spi; // LABZ
#else

2. 蓝牙键盘功能的实现。这里我们需要用到 ESP32 BLE Keyboard library,在 https://github.com/T-vK/ESP32-BLE-Keyboard 可以找到这个项目。

3.最终的代码如下:

#include <hidboot.h>
#include <SPI.h>

#include <BleKeyboard.h>

BleKeyboard bleKeyboard;

class KbdRptParser : public KeyboardReportParser
{
    void Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf);
};

void KbdRptParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf)
{

  if (bleKeyboard.isConnected()) {
    KeyReport _keyReport;
    _keyReport.modifiers=buf[0];
    for (int i = 2; i < 8; i++) {
      _keyReport.keys[i-2] = buf[i];
    }

    bleKeyboard.sendReport(&_keyReport);
  }
  Serial.print("Received ");  Serial.println(len);
  for (int i = 0; i < len; i++) {
    Serial.print(buf[i]);
    Serial.print("  ");
  }
  Serial.println("  ");
}

USB     Usb;

HIDBoot<USB_HID_PROTOCOL_KEYBOARD>    HidKeyboard(&Usb);

KbdRptParser Prs;

void setup()
{
  Serial.begin( 115200 );
  Serial.println("Start");

  if (Usb.Init() == -1)
    Serial.println("OSC did not start.");

  bleKeyboard.begin();
  delay( 200 );

  HidKeyboard.SetReportParser(0, &Prs);
}

void loop()
{
  Usb.Task();
}

简单的说上电之后,在PC上搜索蓝牙设备,配对之后就可以通过 USB Host Shield 解析键盘数据,然后将这个数据直接填写到蓝牙键盘的结构体中,FIreBeetle 随即将这个信息传递给 PC,PC上就能收到你的按键信息了。

使用一个小键盘测试

电路图文件和PCB 在这里下载:

工作的视频可以在 B站看到

https://www.bilibili.com/video/BV1zy4y1W7oh

VC 运行 PowerShell 命令的方法

有时候,我们需要执行 PowerShell 命令来取得一些信息,通过下面的代码能在 VC 中执行命令并且获得返回值:

#include "stdafx.h"
#include <stdio.h>
#include <string.h>

// 描述:execmd函数执行命令,并将结果存储到result字符串数组中
// 参数:cmd表示要执行的命令,  result是执行的结果存储的字符串数组
// 函数执行成功返回1,失败返回0
#pragma warning(disable:4996)
int execmd(char* cmd, char* result) {
	char buffer[128]; //定义缓冲区
	FILE* pipe = _popen(cmd, "r"); //打开管道,并执行命令
	if (!pipe)
		return 0; //返回0表示运行失败
	while (!feof(pipe)) {
		if (fgets(buffer, 128, pipe)) { //将管道输出到result中
			strcat(result, buffer);
		}
	}
	_pclose(pipe); //关闭管道
	return 1; //返回1表示运行成功
}

int main(void)
{
	char res[2048] = { 0 };
	execmd("powershell $psversiontable", res);
	printf("Result [%s]\r\n",res);
	getchar();
}

下面是运行结果,可以看到二者完全相同。

代码运行结果和 Power Shell 运行结果相同

特别注意:如果遇到下面这样的错误提示,那么需要扩大 res[] 。

Run-Time Check Failure #2 – Stack around the variable ‘a’ was corrupted.

参考:

1.https://www.cnblogs.com/htj10/p/13830785.html VC执行Cmd命令,并获取结果

2.https://blog.csdn.net/weixin_42395980/article/details/112123690 c++ 调用 powershell_十九,Powershell基础入门及常见用法(一)

ESP32/S2 不推荐使用的GPIO Pin

ESP32 不建议使用的 GPIO 如下

  1. IO0 Strapping,用于选择 SPI 启动还是下载启动
  2. IO1 TXD , 用于串口下载
  3. IO2 Strapping,下载启动需要用
  4. IO3 RXD,用于串口下载
  5. IO5 Strapping, SDIO 从机信号输入输出时序
  6. IO6-11 内部 SPI FLASH
  7. IO12 Strapping, MTDI信号
  8. IO15 Strapping MTDO信号

ESP32S2 不建议使用的 GPIO 如下(这个是我自己总结的,缺少佐证):

  1. GPIO0 系统启动模式选择
  2. GPIO1 TXD 用于串口下载
  3. GPIO3 RXD 用于串口下载
  4. GPIO43 TXD 用于串口下载
  5. GPIO44 RXD 用于串口下载
  6. GPIO37 SPI NOR/PSRAM 的SPIHD
  7. IO39 SPI NOR的SPICS0
  8. 6.IO40 SPI NOR/PSRAM 的SPICLK
  9. IO41 SPI NOR/PSRAM 的SPIQ
  10. IO42 SPI NOR/PSRAM 的SPID
  11. IO38 SPI NOR/PSRAM 的 SPIWP
  12. IO26 用作PSRAM SPICS1
  13. IO27 用作PSRAM SPIHD
  14. IO28用作PSRAM SPIWP
  15. IO29 用作PSRAM SPICS0
  16. IO30 用作PSRAM SPICLK
  17. IO31 用作PSRAM SPIQ
  18. IO32 用作PSRAM SPID
  19. GPIO45 VDD_SPI 电压选择
  20. GPIO46 系统启动是否打印 ROM Code

关于 S2 的 PSRAM 在【参考3】有描述:

特别提醒:如果你使用开发板,例如 S2 Saola 这种, IO18 上有LED,如果直接使用这个pin作为SPI CLOCK可能会导致不断重启的问题。

参考:

1.http://www.360doc.com/content/20/0312/20/42387867_898729516.shtml

2.ESP32­WROOM­32D &ESP32­WROOM­32U 技术规格书

3.https://www.espressif.com/sites/default/files/documentation/esp32-s2_datasheet_cn.pdf

====================================================

2023年1月17日更新

ESP32S3不建议使用的 GPIO 如下:

1.GPIO0 Strapping
2.GPIO3 Strapping
3.GPIO19 USB-JTAG
4.GPIO20 USB-JTAG
5.GPIO26-37 SPI0/1
6.GPIO45 Strapping
7.GPIO46 Strapping

  • Strapping 管脚:GPIO2、GPIO3、GPIO45 和 GPIO46 是 Strapping 管脚。更多信息请参考 ESP32-S3 技术规格书。
  • SPI0/1:GPIO26-32 通常用于 SPI flash 和 PSRAM,不推荐用于其他用途。当使用八线 flash 或八线 PSRAM 或同时使用两者时,GPIO33~37 会连接到 SPIIO4 ~ SPIIO7 和 SPIDQS。因此,对于内嵌 ESP32-S3R8 或 ESP32-S3R8V 芯片的开发板,GPIO33~37 也不推荐用于其他用途。
  • USB-JTAG:GPIO19 和 GPIO20 默认用于 USB-JTAG。用做 GPIO 时驱动程序将禁用 USB-JTAG。

上述来自:https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32s3/api-reference/peripherals/gpio.html

联宝招聘(合肥,昆山,台北内部推荐,20210705)

职位部门级别位置
软件工程师研发部工程师,高级工程师合肥
嵌入式开发工程师研发部工程师,高级工程师合肥,昆山
计算机视觉博士研究员研发部高级工程师合肥
数据挖掘博士研究员研发部高级工程师合肥
EDA应用软件开发工程师研发部工程师,高级工程师合肥,昆山
软件BIOS高级工程师研发部高级工程师台北
IT PLM方案工程师研发部工程师台北
软件应用高级工程师(driver)研发部高级工程师合肥,昆山
软件项目管理研发部工程师合肥

*合肥地址:中国安徽省合肥市经济技术开发习友路5899号联想科技港

*昆山地址:江苏省昆山市高新区登云路288号海创大厦C座

有兴趣或者想进一步咨询的朋友可以通过如下邮件地址接洽:

372790124@qq.com

YModem 串口测试软件

最近研究 YModem 协议需要有对应的软件进行测试,开始使用的是 WinXP 的超级终端,但是发现它无法支持超过 1Mhz 的频率,之后使用 ExtraPutty 发现它在无法工作在 2Mhz 的情况下。最终找到了一个开源的 YModem 串口测试软件:SerialPortYmodem,这是一个开源的工具在:

https://github.com/1021256354/SerialPortYmodem

它提供了源代码,但是没有提供 Windows 的可执行程序,只好手工编译,编译过程又发现它使用的是 QT 5.7 ,于是又研究如何安全QT 最终得到的编译结果。有同样需求的朋友可以在这里下载到编译结果。编译出来的可执行程序真不大160K 左右,但是如果想让这个程序在Windows下执行起来需要 48MB 的DLL 。

我在源代码的基础上还做了如下修改:

1.将中文提示信息替换为英文;

2.增加了一些波特率,比如:1,500,000和 2,000,000

3.增加了速度的显示,在 Send 和 Receive 之间可以看到当前速度

2,000,000 波特率通讯测试

编译好的Windows 可执行文件在下面(QT 框架编译出来的程序160K, 支持这个文件的 DLL 32MB):

链接: https://pan.baidu.com/s/1qPJT_jFvkZW1LO02BFjOWA 提取码: b9vz 复

修改后的源代码下载

FireBeetle 播放音频的更大存储空间

前面的文章介绍了 DAC 方式播放和用更高精度的 PWM 方式直接播放音频文件,但是很明显我们遇到了的2个问题:

  1. 存储空间有限,APP 中最大只能存放2.7MB的音频;
  2. 每次都需要手工将数据转化为 .h ,比较麻烦。

这次就介绍如何在 FireBeetle上使用更大的空间。从介绍中可以看到FireBeetle Flash 为16MB,但是 APP 最大只能用到3MB,余下的空间要么分配给 SPIFFS,要么分给 FATFS。

SPIFFS 和 FATFS 都是一种文件系统。其中SPIFFS 是一个用于 SPI NOR flash 设备的嵌入式文件系统,支持磨损均衡、文件系统一致性检查等功能【参考1】。同样的 FATFS也是一种文件系统【参考2】。很明显 ESP32 上我们可以使用更大的空间,因此这里尝试使用 FATSFS来存储音频文件。

对于 ESP32 来说,每次上传的 APP 和 FATFS 是分开的。比如,编译之后生成了一个 3MB 的APP, 那么上传时只会烧写更新 3MB APP那一段的SPI NOR中的内容,余下的部分不会改变(加上压缩以及快速的串口通讯,我们并不会感觉上传太慢)。因此,我们还需要一个额外的工具来完成上传 FATFS 的部分。这个工具就是arduino esp32fs 插件,这个插件能将文件传输到ESP32 的SPIFFS, LittleFS 或者FatFS分区上,项目地址如下:

https://github.com/lorol/arduino-esp32fs-plugin

下载到编译好的文件是一个 JAR文件。

安装方法是在你 Arduino.exe 的目录中,Tools 下创建 ESP32FS/Tool 目录,然后将上面这个文件放置进去:

重启 Arduino 之后在 Tools 菜单中会出现 “ESP32 Sketch Data Upload”的选项。

为了给 FATFS 分区上传,我们还需要2个额外的工具 mklittlefs.exe 和 mkfatfs.exe。这两个工具需要放在C:\Users\用户名\AppData\Local\Arduino15\packages\firebeetle32\hardware\esp32\0.1.1\tools 目录下。

上面准备妥当之后,先编写一个测试程序。这个程序会列出当前 FATFS 上面的文件名称。

#include "FS.h"
#include "FFat.h"

void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
    Serial.printf("Listing directory: %s\r\n", dirname);

    File root = fs.open(dirname);
    if(!root){
        Serial.println("- failed to open directory");
        return;
    }
    if(!root.isDirectory()){
        Serial.println(" - not a directory");
        return;
    }

    File file = root.openNextFile();
    while(file){
        if(file.isDirectory()){
            Serial.print("  DIR : ");
            Serial.println(file.name());
            if(levels){
                listDir(fs, file.name(), levels -1);
            }
        } else {
            Serial.print("  FILE: ");
            Serial.print(file.name());
            Serial.print("\tSIZE: ");
            Serial.println(file.size());
        }
        file = root.openNextFile();
    }

    
}

void setup() {
  Serial.begin(115200);
  if(!FFat.begin()){
        Serial.println("FFat Mount Failed");
        return;
  }  
    Serial.printf("Total space: %10u\n", FFat.totalBytes());
    Serial.printf("Free space: %10u\n", FFat.freeBytes());  
    
}

void loop() {
    listDir(FFat, "/", 0);
    delay(5000);
}

此外,我们还要在程序目录下创建一个 data 目录,然后将3支歌曲的文件放在里面。

直接使用菜单上传FATFS分区。

选择 FATFS:

看到下面的字样就表示已经成功:

接下来,像普通 Arduino 代码一样上传我们的程序,打开串口就能看到结果:

有了上面的代码,我们可以很容易编写出来播放代码:

#include "FS.h"
#include "FFat.h"

#include "data\1990.h"
#include "soc/sens_reg.h"  // For dacWrite() patch, TEB Sep-16-2019

void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
    Serial.printf("Listing directory: %s\r\n", dirname);

    File root = fs.open(dirname);
    if(!root){
        Serial.println("- failed to open directory");
        return;
    }
    if(!root.isDirectory()){
        Serial.println(" - not a directory");
        return;
    }

    File file = root.openNextFile();
    while(file){
        if(file.isDirectory()){
            Serial.print("  DIR : ");
            Serial.println(file.name());
            if(levels){
                listDir(fs, file.name(), levels -1);
            }
        } else {
            Serial.print("  FILE: ");
            Serial.print(file.name());
            Serial.print("\tSIZE: ");
            Serial.println(file.size());
        }
        file = root.openNextFile();
    }

    
}

void playFile(fs::FS &fs, const char * path){
    Serial.printf("Playing file: %s\r\n", path);

    File file = fs.open(path);
    if(!file){
        Serial.println("- failed to open file for reading");
        return;
    }

    uint8_t buffer[1024*4];
    size_t bytessend;
    while(file.available()){
        bytessend=file.read(buffer, 1024*4);
        for (int i=0;i<1024*4;i++) {
            CLEAR_PERI_REG_MASK(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_CW_EN1_M);
            SET_PERI_REG_BITS(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_DAC, buffer[i], RTC_IO_PDAC1_DAC_S);
            SET_PERI_REG_MASK(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_XPD_DAC | RTC_IO_PDAC1_DAC_XPD_FORCE);
            ets_delay_us(125);
        }
    }
    file.close();
}
void setup() {
  Serial.begin(115200);
  if(!FFat.begin()){
        Serial.println("FFat Mount Failed");
        return;
  }  
    Serial.printf("Total space: %10u\n", FFat.totalBytes());
    Serial.printf("Free space: %10u\n", FFat.freeBytes());  
    
    listDir(FFat, "/", 0);
}

void loop() {
    playFile(FFat, "/1st.wav");
    playFile(FFat, "/10years.wav");
    playFile(FFat, "/1990.wav");
}

参考:

  1. https://docs.espressif.com/projects/esp-idf/zh_CN/release-v4.1/api-reference/storage/spiffs.html
  2. https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/storage/fatfs.html

VS2015 生成后事件

有些情况下,我们希望在编译完成后针对生成的 EXE 多做一些事情,比如:重命名,加入校验功能等等。为此,需要使用 VS 的“生成后事件”功能。

举例如下:

1.在你的 VC 项目上,点击右键打开属性

2. “生成后事件”位置如下图所示。特别注意:在上方的 Configuration 中需要选择这个动作的条件。比如,你设置了 X64 Release的动作,但是如果当前是 X64 Debug,那么编译过程中不会执行该动作。

3. 这里我们使用一个 dir 和 echo 命令(使用“&” 可以连接2个DOS 命令)

4.运行结果,可以看到编译结束后运行了上面的2个命令

参考:

  1. https://developer.aliyun.com/article/260394 VS中的预先生成事件和后期生成事件
  2. https://www.cnblogs.com/panmy/p/5831146.html VS中的生成事件
  3. https://blog.csdn.net/face_to/article/details/99968278 VS2015编译生成后事件处理

ESP32 YModem 的测试例子

根据 https://github.com/loboris/ESP32_ymodem_example 修改的 Arduino 版本的 ESP32 代码,在 FireBeelte 上测试通过。通过USB 串口进行测试,接收到的数据并不会存放在任何地方。

//https://github.com/loboris/ESP32_ymodem_example/blob/master/components/ymodem/ymodem.c
#include "ymodem.h"

//------------------------------------------------------------------------
static unsigned short crc16(const unsigned char *buf, unsigned long count)
{
  unsigned short crc = 0;
  int i;

  while(count--) {
    crc = crc ^ *buf++ << 8;

    for (i=0; i<8; i++) {
      if (crc & 0x8000) crc = crc << 1 ^ 0x1021;
      else crc = crc << 1;
    }
  }
  return crc;
}

//--------------------------------------------------------------
static int32_t Receive_Byte (unsigned char *c, uint32_t timeout)
{
    unsigned char ch;
    //ZivDebug int len = uart_read_bytes(EX_UART_NUM, &ch, 1, timeout / portTICK_RATE_MS);
    //ZivDebug_Start
    int len=0;
    unsigned int Elsp=millis();
    while ((millis()-Elsp<timeout / portTICK_RATE_MS)&&(len==0)) {
            if (Serial.available()) {
                    ch=Serial.read();
                    len=1;    
                    #ifdef ENDEBUG
                      Serial2.print("ESP32 RCV1:");
                      Serial2.print(ch,HEX);
                      Serial2.println(" ");                    
                    #endif
            }
    }
    //ZivDebug_End
    if (len <= 0) return -1;

    *c = ch;
    return 0;
}

//------------------------
static void uart_consume()
{
  uint8_t ch[64];
    //ZivDebug while (uart_read_bytes(EX_UART_NUM, ch, 64, 100 / portTICK_RATE_MS) > 0) ;
    //ZivDebug_Start
    int len=0;
    unsigned int Elsp=millis();
    while ((millis()-Elsp<100 / portTICK_RATE_MS)||(len<64)) {
            if (Serial.available()) {

                    ch[len]=Serial.read();
                    #ifdef ENDEBUG
                        Serial2.print("ESP32 RCV2:");
                        Serial2.print(ch[len],HEX);
                        Serial2.println(" ");
                    #endif  
                    len++;
            }
    }
    //ZivDebug_End
}

//--------------------------------
static uint32_t Send_Byte (char c)
{
  //ZivDebug uart_write_bytes(EX_UART_NUM, &c, 1);
#ifdef ENDEBUG
        Serial2.print("ESP32 send:");
        Serial2.print(c,HEX);
        Serial2.println(" ");
#endif          
  Serial.write(c); //ZivDebug
  return 0;
}

//----------------------------
static void send_CA ( void ) {
  Send_Byte(CA);
  Send_Byte(CA);
}

//-----------------------------
static void send_ACK ( void ) {
  Send_Byte(ACK);
}

//----------------------------------
static void send_ACKCRC16 ( void ) {
  Send_Byte(ACK);
  Send_Byte(CRC16);
}

//-----------------------------
static void send_NAK ( void ) {
  Send_Byte(NAK);
}

//-------------------------------
static void send_CRC16 ( void ) {
  //Serial2.print("SNDCRC16");
  Send_Byte(CRC16);
}


/**
  * @brief  Receive a packet from sender
  * @param  data
  * @param  timeout
  * @param  length
  *    >0: packet length
  *     0: end of transmission
  *    -1: abort by sender
  *    -2: error or crc error
  * @retval 0: normally return
  *        -1: timeout
  *        -2: abort by user
  */
//--------------------------------------------------------------------------
static int32_t Receive_Packet (uint8_t *data, int *length, uint32_t timeout)
{
  int count, packet_size, i;
  unsigned char ch;
  *length = 0;
  //Serial2.print("Receive_Packet:");
  // receive 1st byte
  if (Receive_Byte(&ch, timeout) < 0) {
    return -1;
  }
  //Serial2.print("Rcv5:");
  //Serial2.println(ch,HEX);
  switch (ch) {
    case SOH:
    packet_size = PACKET_SIZE;
    break;
    case STX:
    packet_size = PACKET_1K_SIZE;
    break;
    case EOT:
        *length = 0;
        return 0;
    case CA:
      //Serial2.print("CA:");
      if (Receive_Byte(&ch, timeout) < 0) {
        return -2;
      }
      if (ch == CA) {
        *length = -1;
        return 0;
      }
      else return -1;
    case ABORT1:
    case ABORT2:
      return -2;
    default:
      vTaskDelay(100 / portTICK_RATE_MS);
      uart_consume();
      return -1;
  }

  *data = (uint8_t)ch;
  uint8_t *dptr = data+1;
  count = packet_size + PACKET_OVERHEAD-1;
  //Serial2.print("Rcv3:");
  //Serial2.println(count);
  for (i=0; i<count; i++) {
    if (Receive_Byte(&ch, timeout) < 0) {
      return -1;
    }
    *dptr++ = (uint8_t)ch;;
  }
  //Serial2.print("Rcv4:");
  //Serial2.println(i);
  if (data[PACKET_SEQNO_INDEX] != ((data[PACKET_SEQNO_COMP_INDEX] ^ 0xff) & 0xff)) {
      *length = -2;
      return 0;
  }
  if (crc16(&data[PACKET_HEADER], packet_size + PACKET_TRAILER) != 0) {
      *length = -2;
      return 0;
  }

  *length = packet_size;
  //Serial2.print("Rcv2:");
  //Serial2.println(packet_size);
  return 0;
}

// Receive a file using the ymodem protocol.
//-----------------------------------------------------------------
int Ymodem_Receive (FILE *ffd, unsigned int maxsize, char* getname)
{
  uint8_t packet_data[PACKET_1K_SIZE + PACKET_OVERHEAD];
  uint8_t *file_ptr;
  char file_size[128];
  unsigned int i, file_len, write_len, session_done, file_done, packets_received, errors, size = 0;
  int packet_length = 0;
  file_len = 0;
  int eof_cnt = 0;
  
  for (session_done = 0, errors = 0; ;) {
    for (packets_received = 0, file_done = 0; ;) {
      //LED_toggle();
      switch (Receive_Packet(packet_data, &packet_length, NAK_TIMEOUT)) {
        case 0:  // normal return
          switch (packet_length) {
            case -1:
                // Abort by sender
                send_ACK();
                size = -1;
                goto exit;
            case -2:
                // error
                errors ++;
                if (errors > 5) {
                  send_CA();
                  size = -2;
                  goto exit;
                }
                send_NAK();
                break;
            case 0:
                // End of transmission
              eof_cnt++;
              if (eof_cnt == 1) {
                send_NAK();
              }
              else {
                send_ACKCRC16();
              }
                break;
            default:
              // ** Normal packet **
              if (eof_cnt > 1) {
              send_ACK();
              }
              else if ((packet_data[PACKET_SEQNO_INDEX] & 0xff) != (packets_received & 0x000000ff)) {
                errors ++;
                if (errors > 5) {
                  send_CA();
                  size = -3;
                  goto exit;
                }
                send_NAK();
              }
              else {
                if (packets_received == 0) {
                  // ** First packet, Filename packet **
                  if (packet_data[PACKET_HEADER] != 0) {
                    errors = 0;
                    // ** Filename packet has valid data
                    if (getname) {
                      for (i = 0, file_ptr = packet_data + PACKET_HEADER; ((*file_ptr != 0) && (i < 64));) {
                        *getname = *file_ptr++;
                        getname++;
                      }
                      *getname = '\0';
                    }
                    for (i = 0, file_ptr = packet_data + PACKET_HEADER; (*file_ptr != 0) && (i < packet_length);) {
                      file_ptr++;
                    }
                    for (i = 0, file_ptr ++; (*file_ptr != ' ') && (i < FILE_SIZE_LENGTH);) {
                      file_size[i++] = *file_ptr++;
                    }
                    file_size[i++] = '\0';
                    if (strlen(file_size) > 0) size = strtol(file_size, NULL, 10);
                    else size = 0;

                    // Test the size of the file
                    if ((size < 1) || (size > maxsize)) {
                      // End session
                      send_CA();
                      if (size > maxsize) size = -9;
                      else size = -4;
                      goto exit;
                    }

                    file_len = 0;
                    send_ACKCRC16();
                  }
                  // Filename packet is empty, end session
                  else {
                      errors ++;
                      if (errors > 5) {
                        send_CA();
                        size = -5;
                        goto exit;
                      }
                      send_NAK();
                  }
                }
                else {
                  // ** Data packet **
                  // Write received data to file
                  if (file_len < size) {
                    file_len += packet_length;  // total bytes received
                    if (file_len > size) {
                      write_len = packet_length - (file_len - size);
                      file_len = size;
                    }
                    else write_len = packet_length;

                    //ZivDebug int written_bytes = fwrite((char*)(packet_data + PACKET_HEADER), 1, write_len, ffd);
                    int written_bytes=write_len;
                    if (written_bytes != write_len) { //failed
                      /* End session */
                      send_CA();
                      size = -6;
                      goto exit;
                    }
                    //LED_toggle();
                  }
                  //success
                  errors = 0;
                  send_ACK();
                }
                packets_received++;
              }
          }
          break;
        case -2:  // user abort
          send_CA();
          size = -7;
          goto exit;
        default: // timeout
          if (eof_cnt > 1) {
          file_done = 1;
          }
          else {
        errors ++;
        if (errors > MAX_ERRORS) {
        send_CA();
        size = -8;
        goto exit;
        }
        send_CRC16();
          }
      }
      if (file_done != 0) {
        session_done = 1;
        break;
      }
    }
    if (session_done != 0) break;
  }
exit:
  #if YMODEM_LED_ACT
  gpio_set_level(YMODEM_LED_ACT, YMODEM_LED_ACT_ON ^ 1);
  #endif
  return size;
}

//------------------------------------------------------------------------------------
static void Ymodem_PrepareIntialPacket(uint8_t *data, char *fileName, uint32_t length)
{
  uint16_t tempCRC;

  memset(data, 0, PACKET_SIZE + PACKET_HEADER);
  // Make first three packet
  data[0] = SOH;
  data[1] = 0x00;
  data[2] = 0xff;
  
  // add filename
  sprintf((char *)(data+PACKET_HEADER), "%s", fileName);

  //add file site
  sprintf((char *)(data + PACKET_HEADER + strlen((char *)(data+PACKET_HEADER)) + 1), "%d", length);
  data[PACKET_HEADER + strlen((char *)(data+PACKET_HEADER)) +
     1 + strlen((char *)(data + PACKET_HEADER + strlen((char *)(data+PACKET_HEADER)) + 1))] = ' ';
  
  // add crc
  tempCRC = crc16(&data[PACKET_HEADER], PACKET_SIZE);
  data[PACKET_SIZE + PACKET_HEADER] = tempCRC >> 8;
  data[PACKET_SIZE + PACKET_HEADER + 1] = tempCRC & 0xFF;
}

//-------------------------------------------------
static void Ymodem_PrepareLastPacket(uint8_t *data)
{
  uint16_t tempCRC;
  
  memset(data, 0, PACKET_SIZE + PACKET_HEADER);
  data[0] = SOH;
  data[1] = 0x00;
  data[2] = 0xff;
  tempCRC = crc16(&data[PACKET_HEADER], PACKET_SIZE);
  //tempCRC = crc16_le(0, &data[PACKET_HEADER], PACKET_SIZE);
  data[PACKET_SIZE + PACKET_HEADER] = tempCRC >> 8;
  data[PACKET_SIZE + PACKET_HEADER + 1] = tempCRC & 0xFF;
}

//-----------------------------------------------------------------------------------------
static void Ymodem_PreparePacket(uint8_t *data, uint8_t pktNo, uint32_t sizeBlk, FILE *ffd)
{
  uint16_t i, size;
  uint16_t tempCRC;
  
  data[0] = STX;
  data[1] = (pktNo & 0x000000ff);
  data[2] = (~(pktNo & 0x000000ff));

  size = sizeBlk < PACKET_1K_SIZE ? sizeBlk :PACKET_1K_SIZE;
  // Read block from file
  if (size > 0) {
    //ZivDebug size = fread(data + PACKET_HEADER, 1, size, ffd);
    //ZivDebug_Start
    for (i=0;i<size;i++){data[PACKET_HEADER+i]=i;}
    //ZivDebug_End
  }

  if ( size  < PACKET_1K_SIZE) {
    for (i = size + PACKET_HEADER; i < PACKET_1K_SIZE + PACKET_HEADER; i++) {
      data[i] = 0x00; // EOF (0x1A) or 0x00
    }
  }
  tempCRC = crc16(&data[PACKET_HEADER], PACKET_1K_SIZE);
  //tempCRC = crc16_le(0, &data[PACKET_HEADER], PACKET_1K_SIZE);
  data[PACKET_1K_SIZE + PACKET_HEADER] = tempCRC >> 8;
  data[PACKET_1K_SIZE + PACKET_HEADER + 1] = tempCRC & 0xFF;
}

//-------------------------------------------------------------
static uint8_t Ymodem_WaitResponse(uint8_t ackchr, uint8_t tmo)
{
  unsigned char receivedC;
  uint32_t errors = 0;

  do {
    if (Receive_Byte(&receivedC, NAK_TIMEOUT) == 0) {
      if (receivedC == ackchr) {
        return 1;
      }
      else if (receivedC == CA) {
        send_CA();
        return 2; // CA received, Sender abort
      }
      else if (receivedC == NAK) {
        return 3;
      }
      else {
        return 4;
      }
    }
    else {
      errors++;
    }
  }while (errors < tmo);
  return 0;
}


//------------------------------------------------------------------------
int Ymodem_Transmit (char* sendFileName, unsigned int sizeFile, FILE *ffd)
{
  uint8_t packet_data[PACKET_1K_SIZE + PACKET_OVERHEAD];
  uint16_t blkNumber;
  unsigned char receivedC;
  int i, err;
  uint32_t size = 0;

  // Wait for response from receiver
  err = 0;
  do {
    Send_Byte(CRC16);
    //LED_toggle();
  } while (Receive_Byte(&receivedC, NAK_TIMEOUT) < 0 && err++ < 45);

  if (err >= 45 || receivedC != CRC16) {
    send_CA();
    return -1;
  }
  
  // === Prepare first block and send it =======================================
  /* When the receiving program receives this block and successfully
   * opened the output file, it shall acknowledge this block with an ACK
   * character and then proceed with a normal YMODEM file transfer
   * beginning with a "C" or NAK tranmsitted by the receiver.
   */
  Ymodem_PrepareIntialPacket(packet_data, sendFileName, sizeFile);
  do 
  {
    // Send Packet
  //ZivDebug uart_write_bytes(EX_UART_NUM, (char *)packet_data, PACKET_SIZE + PACKET_OVERHEAD);
        //ZivDebug_Start
        //Serial2.print("ESP32 send:");
       // for (int i=0;i<PACKET_SIZE + PACKET_OVERHEAD;i++) {
       //         Serial2.print(packet_data[i],HEX);
       //         Serial2.print(" ");
       // }        
        for (int i=0;i<PACKET_SIZE + PACKET_OVERHEAD;i++) {
                Serial.write(packet_data[i]);
        }
        //ZivDebug_End
  // Wait for Ack
    err = Ymodem_WaitResponse(ACK, 10);
    if (err == 0 || err == 4) {
      send_CA();
      return -2;                  // timeout or wrong response
    }
    else if (err == 2) return 98; // abort
    //LED_toggle();
  }while (err != 1);

  // After initial block the receiver sends 'C' after ACK
  if (Ymodem_WaitResponse(CRC16, 10) != 1) {
    send_CA();
    return -3;
  }
  
  // === Send file blocks ======================================================
  size = sizeFile;
  blkNumber = 0x01;
  
  // Resend packet if NAK  for a count of 10 else end of communication
  while (size)
  {
    // Prepare and send next packet
    Ymodem_PreparePacket(packet_data, blkNumber, size, ffd);
    do
    {
        //uart_write_bytes(EX_UART_NUM, (char *)packet_data, PACKET_1K_SIZE + PACKET_OVERHEAD);
        //ZivDebug_Start
        //Serial2.print("ESP32 send:");
        //for (int i=0;i<PACKET_1K_SIZE + PACKET_OVERHEAD;i++) {
        //        Serial2.print(packet_data[i],HEX);
        //        Serial2.print(" ");
        //}
        //Serial2.println(" ");
        for (int i=0;i<PACKET_1K_SIZE + PACKET_OVERHEAD;i++) {
                Serial.write(packet_data[i]);
        }
        //ZivDebug_End

      // Wait for Ack
      err = Ymodem_WaitResponse(ACK, 10);
      if (err == 1) {
        blkNumber++;
        if (size > PACKET_1K_SIZE) size -= PACKET_1K_SIZE; // Next packet
        else size = 0; // Last packet sent
      }
      else if (err == 0 || err == 4) {
        send_CA();
        return -4;                  // timeout or wrong response
      }
      else if (err == 2) return -5; // abort
    }while(err != 1);
    //LED_toggle();
  }
  
  // === Send EOT ==============================================================
  Send_Byte(EOT); // Send (EOT)
  // Wait for Ack
  do 
  {
    // Wait for Ack
    err = Ymodem_WaitResponse(ACK, 10);
    if (err == 3) {   // NAK
      Send_Byte(EOT); // Send (EOT)
    }
    else if (err == 0 || err == 4) {
      send_CA();
      return -6;                  // timeout or wrong response
    }
    else if (err == 2) return -7; // abort
  }while (err != 1);
  
  // === Receiver requests next file, prepare and send last packet =============
  if (Ymodem_WaitResponse(CRC16, 10) != 1) {
    send_CA();
    return -8;
  }

  //LED_toggle();
  Ymodem_PrepareLastPacket(packet_data);
  do 
  {
  // Send Packet
  //ZivDebug uart_write_bytes(EX_UART_NUM, (char *)packet_data, PACKET_SIZE + PACKET_OVERHEAD);
        //ZivDebug_Start
        //Serial2.print("ESP32 send:");
        //for (int i=0;i<PACKET_SIZE + PACKET_OVERHEAD;i++) {
        //        Serial2.print(packet_data[i],HEX);
        //        Serial2.print(" ");
        //}
        //Serial2.println("");
        for (int i=0;i<PACKET_SIZE + PACKET_OVERHEAD;i++) {
                Serial.write(packet_data[i]);
        }
        //ZivDebug_End
  // Wait for Ack
    err = Ymodem_WaitResponse(ACK, 10);
    if (err == 0 || err == 4) {
      send_CA();
      return -9;                  // timeout or wrong response
    }
    else if (err == 2) return -10; // abort
  }while (err != 1);
  
  #if YMODEM_LED_ACT
  gpio_set_level(YMODEM_LED_ACT, YMODEM_LED_ACT_ON ^ 1);
  #endif
  return 0; // file transmitted successfully
}

void setup() {
  Serial.begin(921600);
  Serial2.begin(115200);
}

void loop() {
  char Filename[20];
  int sizesnd=Ymodem_Receive (NULL, 60*1024*1024, Filename);
  Serial2.print("Send bytes=");
  Serial2.println(sizesnd);
}

使用 Windows XP 的超级终端测试,在 921600 波特率情况下(超级终端支持的最高频率),传输速度可以达到 34KBytes/s。

超级终端传输

DFRobot 的 FireBeetle 上面使用的是 CH340C,最高可以支持 2,000,000的波特率。但是

Acpica 工具的重新编译方法

我们编译使用的 ACPI 工具iASL .exe通常来自Acpica。这个工具是开源的,本文将介绍如何在 Window 下编译。

首先,源代码可以来自https://github.com/acpica/acpica/releases 或者 https://acpica.org/downloads/windows-source。个人更推荐前者,后者在很多时候会有奇怪的问题。

接下来准备编译环境和工具。这次我使用 VS2019, 有兴趣的朋友可以使用这个 VS2019 离线安装包,安装方法很简单的,默认选项不需要联网即可完成安装。接下来需要安装3个工具:

1.GnuWin32  安装界面如下:

上面安装的是 GnuWin32 的安装包,上面的跑完了还要运行一下 install.bat. 特别注意,必须安装到 GnuWin32 目录下

2.接下来安装 Bison,特别注意需要安装到 Gnu32Win 目录下

3.安装 Flex,同样要特别安装到 GnuWin32 目录下。

4.上述安装好了之后需要将 c:\GnuWin32\bin 加入 Path 中。检查方法是设置之后,打开 CMD 窗口,输入 bison 和 flex,如果没有无法找到这个命令的错误,那就是正确的。

5.解压 acpica-R06_04_21.zip c:\apica 目录(必须是这个名字)。然后打开 generate\msvc2017目录下的 AslCompiler.dsw 文件。之后因为这个项目默认使用 VS2017,所以还要改动一下项目属性:

之后即可编译通过,比如我修改代码加入下面的字符串:

为了更加方便使用,这里提供了上面提到的工具。

  1. acpica-R06_04_21.zip 源代码
  2. GetGnuWin32-0.6.3.exe
  3. flex-2.5.4a-1.exe
  4. bison-2.4.1-setup.exe

如果不愿意进行安装,可以直接使用gnu4acpica这个压缩包,解压到 c:\GnuWin32 ,其中包括了 GnuWin32 Flex 和 Bison 无需额外安装即可编译通过。


链接: https://pan.baidu.com/s/1GSmC5BmDOHhx1QwV1a4HRQ?pwd=LABZ

提取码: LABZ

VC 显示设备管理器中有错误的设备

VS2015 编译通过,显示设备管理器中有错误的设备,代码如下:

// PrintDeviceInfo.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <Windows.h>
#include <setupapi.h>
#include <cfgmgr32.h>

#pragma comment(lib, "setupapi.lib")

int  _tmain(int  argc, _TCHAR* argv[])
{

	HDEVINFO hDevInfo;
	SP_DEVINFO_DATA DeviceInfoData;
	DWORD  i;

	// 得到所有设备 HDEVINFO      
	hDevInfo = SetupDiGetClassDevs(NULL, 0, 0, DIGCF_PRESENT | DIGCF_ALLCLASSES);

	if (hDevInfo == INVALID_HANDLE_VALUE)
		return  0;

	// 循环列举     
	DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
	for (i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &DeviceInfoData); i++)
	{
		char  szClassBuf[MAX_PATH] = { 0 };
		char  szDescBuf[MAX_PATH] = { 0 };
		DWORD dwDevStatus, dwProblem;

		// 获取类名  
		if (!SetupDiGetDeviceRegistryProperty(hDevInfo, &DeviceInfoData, SPDRP_CLASS, NULL, (PBYTE)szClassBuf, MAX_PATH - 1, NULL))
			continue;

		//获取设备描述信息
		if (!SetupDiGetDeviceRegistryProperty(hDevInfo, &DeviceInfoData, SPDRP_DEVICEDESC, NULL, (PBYTE)szDescBuf, MAX_PATH - 1, NULL))
			continue;

		CM_Get_DevNode_Status(&dwDevStatus, &dwProblem, DeviceInfoData.DevInst, 0);
		
		if (dwProblem != 0) {
			wprintf(L"Below device has a error:\r\n   Class:%s\r\n   Desc:%s\r\n\r\n", szClassBuf, szDescBuf);
		}

	}

	//  释放     
	SetupDiDestroyDeviceInfoList(hDevInfo);

	wprintf(L"Press anykey to exit.");
	getchar();

	return  0;
}

参考:

1.https://blog.csdn.net/flyingleo1981/article/details/53525060 获取设备管理器的信息 – VC

2.https://blog.csdn.net/d2262272d/article/details/105047066 判断usb硬件的驱动是否已安装