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/1FcOOLI2OR-1tqwPLtYtuwg 提取码: r9ib

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硬件的驱动是否已安装

Step to UEFI (232)UEFI Shell 下控制 USBNotifier

前面的文章介绍了如何使用 CH55X 制作一个 USB 提醒器【参考1】,这次介绍如何在 UEFI Shell 下编写 Application 来控制使用它。

从思路上来说,可以使用加载驱动,然后调用驱动引入的 protocol 来进行控制。比如,FT232 有一个驱动,可以在 UEFI Shell 下直接调用【参考2】.但是,这次的USB 提醒器并没有这样的Driver(估计需要等待 WCH 来进行开发吧)。于是,我们只能尝试直接对其发送数据。

首先使用 USBView 查看一下:

USBViewer 查看结果

这里需要特别关注的是 Endpoint, 可以看到有下面三个 Endpoint, 第一个是 interrupt, 用于传送控制信息;后面两个分别是 Bulk 的输入和输出用于传输真正的串口数据。

          ===>Endpoint Descriptor&lt;===
bLength:                           0x07
bDescriptorType:                   0x05
bEndpointAddress:                  0x81  -> Direction: IN - EndpointID: 1
bmAttributes:                      0x03  -> Interrupt Transfer Type
wMaxPacketSize:                  0x0008 = 0x08 bytes
bInterval:                         0x40

          ===>Endpoint Descriptor&lt;===
bLength:                           0x07
bDescriptorType:                   0x05
bEndpointAddress:                  0x02  -> Direction: OUT - EndpointID: 2
bmAttributes:                      0x02  -> Bulk Transfer Type
wMaxPacketSize:                  0x0040 = 0x40 bytes
bInterval:                         0x00

          ===>Endpoint Descriptor&lt;===
bLength:                           0x07
bDescriptorType:                   0x05
bEndpointAddress:                  0x82  -> Direction: IN - EndpointID: 2
bmAttributes:                      0x02  -> Bulk Transfer Type
wMaxPacketSize:                  0x0040 = 0x40 bytes
bInterval:                         0x00

另外因为 USBNotifier 实际上是一个假串口设备(意思是并非对外转接为串口数据),所以即使初始化时如果没有设置波特率等等这样的参数,仍然能够正确收到数据。所以,我们只需要将数据丢到Bulk 的 Endpoint 就可以正常收到并处理。关键步骤如下:

  1. 枚举当前系统中有 USBIo 的全部 handle
  2. 使用 UsbGetDeviceDescriptor() 取得每个设备的 PID 和 VID
  3. 使用 UsbGetInterfaceDescriptor() 找到 USBNotifier 的 EndPoint
  4. 使用 UsbBulkTransfer() 针对找到的 Endpoint 发送数据

需要特别注意的是:在 UEFI Shell 下, USBNotifier 会被认为两个设备,Interrupt 的Endpoint 为一个设备,另外2个 Bulk 的 Endpoint 被识别为一个设备。

完整的代码:

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/UsbIo.h>

extern EFI_BOOT_SERVICES         *gBS;
#define USB_ENDPOINT_ADDR(EpAddr) ((EpAddr) & 0x7F)

EFI_GUID gEfiUsbIoProtocolGuid =
{ 0x2B2F68D6, 0x0CD2, 0x44CF, { 0x8E, 0x8B, 0xBB, 0xA2, 0x0B, 0x1B, 0x5B, 0x75 }};

int
EFIAPI
main (
        IN int Argc,
        IN CHAR16 **Argv
)
{
        EFI_STATUS    Status;
        UINTN         HandleIndex, HandleCount;
        //UINT8         i,j;
        EFI_HANDLE    *DevicePathHandleBuffer = NULL;
        EFI_USB_IO_PROTOCOL          *USBIO;
        EFI_USB_DEVICE_DESCRIPTOR     DeviceDescriptor;
        EFI_USB_INTERFACE_DESCRIPTOR  IfDesc;
        UINT8                         arr[64];
        UINTN                         LengthInBytes;
        UINT32                        TransferStatus;

        //Get all the Handles that have UsbIO Protocol
        Status = gBS->LocateHandleBuffer(
                ByProtocol,
                &gEfiUsbIoProtocolGuid,
                NULL,
                &HandleCount,
                &DevicePathHandleBuffer);
        if (EFI_ERROR(Status))
        {
                Print(L"ERROR : Get USBIO count fail.\n");
                return 0;
        }

        for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++)
        {
                Status = gBS->HandleProtocol(
                        DevicePathHandleBuffer[HandleIndex],
                        &gEfiUsbIoProtocolGuid,
                        (VOID**)&USBIO);
                if (EFI_ERROR(Status))
                {
                        Print(L"ERROR : Open USBIO fail.\n");
                        gBS->FreePool(DevicePathHandleBuffer);
                        return 0;
                }

                //Get USB Device Descriptor
                Status = USBIO->UsbGetDeviceDescriptor(USBIO, &DeviceDescriptor);
                if (EFI_ERROR(Status))
                {
                        Print(L"ERROR : Usb Get Device Descriptor fail.\n");
                        gBS->FreePool(DevicePathHandleBuffer);
                        return EFI_SUCCESS;
                }

                //Find the device which VID and PID is USB notifier
                if ((0x1209==DeviceDescriptor.IdVendor) && (0xC550==DeviceDescriptor.IdProduct))
                {
                        //Show the PID and VID
                        Print(L"Found a USB Notifier = %04X, ProductID = %04X\n",
                        DeviceDescriptor.IdVendor,
                        DeviceDescriptor.IdProduct);
                        //
                        // Get Interface Descriptor
                        //
                        Status = USBIO->UsbGetInterfaceDescriptor (USBIO, &IfDesc);
                        if (EFI_ERROR (Status))
                        {
                                continue;
                        }
                        
                        if (IfDesc.NumEndpoints==2)
                        {
                                Print(L"Found OUTPUT Endpoint, send the data!\n");
                                
                                arr[0]=0x5B; arr[1]=0x63; arr[2]=0xFF;
                                arr[3]=0x00; arr[4]=0x00; arr[5]=0x5D;
                                LengthInBytes=6;
                                
                                Status = USBIO->UsbBulkTransfer ( 
                                                        USBIO,
                                                        2,
                                                        &arr[0],
                                                        &LengthInBytes,
                                                        3000,
                                                        &TransferStatus );
                                Print(L"Red color\n");
                                gBS->Stall(3000000UL);
                                
                                arr[2]=00; arr[3]=0xFF; arr[4]=0x00;
                                Status = USBIO->UsbBulkTransfer ( 
                                                        USBIO,
                                                        2,
                                                        &arr[0],
                                                        &LengthInBytes,
                                                        3000,
                                                        &TransferStatus );
                                Print(L"Green color\n");
                                gBS->Stall(3000000UL);
                                
                                arr[2]=0x00; arr[3]=0x00; arr[4]=0xFF;
                                Status = USBIO->UsbBulkTransfer ( 
                                                        USBIO,
                                                        2,
                                                        &arr[0],
                                                        &LengthInBytes,
                                                        3000,
                                                        &TransferStatus ); 
                                Print(L"Blue color\n");
                                gBS->Stall(3000000UL);
                                
                                arr[2]=0x00; arr[3]=0x00; arr[4]=0x00;
                                Status = USBIO->UsbBulkTransfer ( 
                                                        USBIO,
                                                        2,
                                                        &arr[0],
                                                        &LengthInBytes,
                                                        3000,
                                                        &TransferStatus );   
                        }
                }
        }
        gBS->FreePool(DevicePathHandleBuffer);

        return EFI_SUCCESS;
}

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

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

完整工程下载:

参考:

  1. https://www.lab-z.com/usbnt/  做一个 USB 提醒器
  2. https://www.lab-z.com/stufdti/ Step to UEFI (93)FTDI 串口驱动

VC 获得系统的 MCFG Table

之前介绍过我们可以从内存来进行 PCI 配置空间的访问,前提是需要找到 PCI Base Address。在现在的系统中,这个地址是放在 ACPI MCFG Table 中来通知系统的。例如,下面就是我电脑上的 Base Address。

RW Everything 读取 MCFG Table

为了完成这个目标,分作两步,第一步是取得 MCFG Table,通过 GetSystemFirmwareTable() 函数来完成;第二步,根据 MCFG Structure Definitions 来解析 MCFG Table,即可获得我们需要的值。最终代码如下:

// GetMCFGTable.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include &lt;windows.h>
#include &lt;sysinfoapi.h>

//
// Common ACPI description table header.  This structure prefaces most ACPI tables.
//
#pragma pack(1)

typedef struct {
	UINT32  Signature;
	UINT32  Length;
	UINT8   Revision;
	UINT8   Checksum;
	UINT8   OemId[6];
	UINT64  OemTableId;
	UINT32  OemRevision;
	UINT32  CreatorId;
	UINT32  CreatorRevision;
} EFI_ACPI_DESCRIPTION_HEADER;

//
// MCFG Structure Definitions
//
//
// Memory Mapped Enhanced Configuration Base Address Allocation
// Structure Definition
//
typedef struct {
	UINT64  BaseAddress;
	UINT16  PciSegmentGroupNumber;
	UINT8   StartBusNumber;
	UINT8   EndBusNumber;
	UINT32  Reserved;
} EFI_ACPI_MEMORY_MAPPED_ENHANCED_CONFIGURATION_BASE_ADDRESS_STRUCTURE;

///
/// MCFG Table header definition.  The rest of the table 
/// must be defined in a platform specific manner.
///
typedef struct {
	EFI_ACPI_DESCRIPTION_HEADER                       Header;
	UINT64                                            Reserved;
} EFI_ACPI_MEMORY_MAPPED_CONFIGURATION_BASE_ADDRESS_TABLE_HEADER;


typedef struct {
	EFI_ACPI_MEMORY_MAPPED_CONFIGURATION_BASE_ADDRESS_TABLE_HEADER        Header;
	EFI_ACPI_MEMORY_MAPPED_ENHANCED_CONFIGURATION_BASE_ADDRESS_STRUCTURE  Segment;
} EFI_ACPI_MEMORY_MAPPED_CONFIGURATION_BASE_ADDRESS_TABLE;

#pragma pack()

EFI_ACPI_MEMORY_MAPPED_CONFIGURATION_BASE_ADDRESS_TABLE  MCFG;

int main()
{
	DWORD MCFGDataSize;

	MCFGDataSize=GetSystemFirmwareTable(
			'ACPI', 
			*(DWORD *)"MCFG", 
			&amp;MCFG,
			sizeof(EFI_ACPI_MEMORY_MAPPED_CONFIGURATION_BASE_ADDRESS_TABLE));

	printf("MCFG Table size = %d bytes\n", MCFGDataSize);
	
	printf("BaseAddress = 0x%X  \n", MCFG.Segment.BaseAddress);

	getchar();
    return 0;
}

运行结果:

国产高速芯片 CH9344 测试板

 CH9344是南京沁恒出品的一款串口芯片。现在市面上常见的 CH340 也是他们家的产品。相比之下,CH9344有下面2个显著的特点:

1.      最高支持 12,000,000串口频率;

2.      一颗芯片带有4个串口。

更具体的信息可以在官方页面【参考1】上看到。

这次我尝试使用这个芯片制作了一个测试板。首先是绘制电路图,基本上就是照搬 Datasheet上提供的参考设计。需要特别注意的是:

1.      因为通讯速度较高(已经属于 USB High Speed了),因此使用的是30Mhz 的晶振;

2.      Pin37 务必预留一个按钮,后面会讲述原因;

3.      USB 口上预留了一个 H3 跳线,用于一些不需要从 USB口取电的情况;

Layout 还是比较狂野的:

3D 预览如下:

然后就做出来了,焊接难度中等,强烈推荐准备助焊锡膏(不是焊锡膏),因为在焊接的时候很容易出现引脚粘连,在助焊锡膏的帮助下才容易分开。

完成之后就开始了测试。测试中我发现当选择为 12Mhz 通讯时,实际只有  6Mhz。

为了解决这个问题首先在技术社区发帖咨询,管理员让我通过电子邮件联系技术支持(tech@wch.cn)。技术支持工程是先后确认过使用的驱动是最新版本,通讯软件正确(他们家有自己的串口软件),之后他们发现我的芯片版本较老:

升级的方法是:对地短接 Pin37 ,将模块插入电脑后运行升级软件即可更新内部Firmware:

之后,设备管理器中的设备信息有变化:

再次测量,可以看到能够实现 12Mhz 的通讯:

电路设计软件是立创 EDA

参考:

1. http://www.wch.cn/products/CH9344.html

VC 宏展开

相信很多人入门时都使用 MASM,这的 MASM 就是 Microsoft‘s Macro Assembler。其中的 Macro 就是宏的意思。相比函数,宏具有更加简洁,运行速度快(编译器会对代码进行“宏展开”,直接修改代码)等等特点。但是,如果需要调试和阅读具有多层宏定义就非常痛苦了。很多年前我接触到的P公司的BIOS代码就是这样,乍一看代码非常规整,每一行就像一个洋葱,追踪起来一层又一层,让人感叹阅读代码是个系统工程。

最近偶然看到 GCC 有展开宏功能,同样的在Microsoft 的Visual C++上也有类似功能,通过编译指令 /p 或者 /ep 即可实现。这两个参数的区别在于生成的” 预编译文件”是否有行号。例如,编写下面的代码,其中定义一个名为 SUM 的宏:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#define SUM(a,b) a+b


/***
  Print a welcoming message.

  Establishes the main structure of the application.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
        int c =3,d=4;

  Print(L"Hello there fellow Programmer.\n");
  Print(L"Welcome to the world of EDK II.\n");

  Print(L"Macro test %d\n",SUM(c,d));
  
  return(0);
}

在 INF 中定义如下:

[BuildOptions]
  MSFT:*_*_X64_CC_FLAGS  = /P

在Build 目录下有生成一个 hello.i 其中有如下代码断,可以看到 SUM 宏已经展开。

INTN
__cdecl
ShellAppMain (
   UINTN Argc,
   CHAR16 **Argv
  )
{
        int c =3,d=4;

  Print(L"Hello there fellow Programmer.\n");
  Print(L"Welcome to the world of EDK II.\n");

  Print(L"Macro test %d\n",c+d);
  
  return(0);
}

如果使用 /EP  /P 参数,那么生成的预编译文件中不会有行号:

[BuildOptions]
  MSFT:*_*_X64_CC_FLAGS  = /EP /P

下图中,左侧是 /EP /P 参数的运行结果,右侧是/P 的结果:

UEFI TIPS: 定义一个注释宏

最近看了一下C语言中 Define 的用法,这个可以看成是C 语言的宏定义,在使用时会进行展开。从这个角度来说,可以用它实现编译过程中自动删除代码的功能。

比如下面的代码中,当定义LAB_APP_DEBUG 1 后,编译过程中 zPrint  会被解释成为 “\\” 这样对应的一行就会被注释掉。

#include  <Uefi.h>
#include  <Library/BaseLib.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

#include  <stdio.h>
#include  <stdlib.h>

#define LAB_APP_DEBUG 1
#if defined(LAB_APP_DEBUG)
        #define        zPrint   Print
#else   
        #define        zPrint   /\
/
#endif

INTN
EFIAPI
main (
  IN UINTN Argc,
  IN CHAR8 **Argv
  )
{
    zPrint(L"StringAAAA\n");
    zPrint(L"StringBBBB\n");
    
    return EFI_SUCCESS;
}

运行结果,第一个是定义了LAB_APP_DEBUG=1的结果,第二个是删除了这定义的结果

此外,EDK2 中一些宏将一些函数定义为空,在编译时通过当前时 DEBUG 还是 REALSE 进行区分,可以做到和上面相同的效果。

Arduino CH376 模块调试指南

去年的时候,介绍过通过串口来和 CH376 通讯【参考1】,最近又将它拿出来玩,和之前不同,这次是和 ESP32 通讯,没想到遇到了奇怪的问题,以此为契机仔细研读 datasheet 总结如下。

  1. 硬件连接:USB 转串口卡,上面的 5V(必须5V),接VCC;GND 接 GND;TX接 RX; RX 接TX; 特别注意,这种板子上有一个5V转3.3V的ASM1117,就是说5V提供给USB 设备,但是芯片是工作在3.3V 下,这种情况下串口一般都能正常工作,但是如果用 SPI 模式,需要特别注意和单片机的电平匹配问题;
  2. 串口发送 CHECK_EXIST (57 AB 06 AA),正确的回复是 0x55 如果没有回复或者是错误的,请检查硬件连接,还有波特率设定;默认情况下波特率为 9600
测试串口参数如图

3.串口发送CMD_SET_USB_MOD (57 AB 15 06), 模式代码为 06H 时切换到已启用的 USB 主机方式,自动产生 SOF 包,正确回答是CMD_RET_SUCCESS 和 USB_INT_CONNECT (0x51 0x15)

  1. 串口发送 DISK_CONNECT(57 AB 30),检查U盘连接,正确回答是 0x14;如果有问题,请更换U盘或者检查格式,在【参考2】给出了一个Bug:如果保留扇区数>255,在老的芯片上会有问题;
  2. 串口发送 DISK_MOUNT (57 AB 31),初始化U盘,并且检测,正确回答是 0x14;
  3. 串口发送 DISK_CAPACITY (57 AB 3E), 查询U盘容量。查询结果需要发送 RD_USB_DATA0查询(57 AB 27),收到返回值 04 FF 7F 7D 00 ,意思是 4个字节,0x7D7FFF= 8224767个扇区电脑上查看如下(感觉这个命令查询的结果是“U盘上最大的扇区号”,所以数量等于这个值加1):

参考:

  1. https://www.lab-z.com/ardch376/
  2. http://www.wch.cn/bbs/thread-63674-1.html 工作人员回复说老版本芯片会有问题