查看: 3145|回复: 0

使用ARM Cortex-M USB CDC网关对ESP32进行编程

[复制链接]
  • TA的每日心情
    开心
    2024-3-26 15:16
  • 签到天数: 266 天

    [LV.8]以坛为家I

    3299

    主题

    6546

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    32024
    最后登录
    2024-4-25
    发表于 2020-2-24 11:35:38 | 显示全部楼层 |阅读模式
    使用ARM Cortex-M USB CDC网关对ESP32进行编程




    Espressif ESP32设备无处不在:它们价格便宜,易于使用,并且Espressif IDF环境和构建系统实际上非常好,并且对包括Eclipse在内的我来说都运行良好。对ESP32进行编程的默认方法是:a)按下某些按钮进入UART引导加载程序,以及b)使用USB电缆用ESP-IDF烧写应用程序。

    如果ESP32直接连接到主机PC,则可以正常工作。但就我而言,它位于NXP Kinetis K22FX512 ARM Cortex-M4F微控制器的后面,并且不能由主机PC直接访问。因此,我必须找到一种方法来允许通过ARM Cortex-M引导加载ESP32,这是本文的主题。


    TGO ESP32 MICRO-D4模块
    16.png
    ESP32模组


    我选择TTGO TTGO Micro-32模块是因为它价格便宜,可用的教程和文档很多,并且比通常的ESP32小得多。

    下面比较一下“常规” ESP32模块和带有ESP32 PICO-D4的Micro-32:
    17.png
    带有常规ESP32模块的TTGO Micro-32

    上述开发板的左侧包括UART-2-USB CDC接口,用于对设备进行编程。有两个按钮:EN /复位(低电平有效)和启动选择开关(IO0)。

    在GitHub上可用的模块引脚下方:
    18.png
    首先,将模块焊接在插针插座上,然后与面包板一起使用:
    19.png
    要使用该模块,只需要几个引脚即可:


    ●    GND和3.3V


    ●    EN,它是一个复位(低电平有效)


    ●    IO0(低有效),用于在复位期间将模块置于串行引导加载程序模式


    ●    UART Tx和Rx用于串行连接和串行引导程序


    下一步是在机器人顶部构建第一个原型板作为基础板:
    20.png
    21.png

    由于机器人的3.3V DC-DC转换器无法在所有模式下提供所需的mA,因此添加了一个额外的5V DC-DC转换器。该版本中缺少的其他东西是GND和EN信号之间为100 nF。

    编程模块


    机器人上的主处理器是NXP K22FX512。已使用基于Eclipse的MCUXpresso IDE和MCUXpresso SDK开发了该软件。


    想法是,K22充当主机PC和ESP32之间的网关。
    23.png
    要进入ESP的自举程序模式,在释放复位信号时,IO0信号需要保持低电平。典型的ESP32板带有UART-2-USB转换器,并且使用带有流控制信号的USB CDC来完成EN和IO0的切换。因此,基本上,在K22上,我必须运行USB CDC堆栈并正确处理流控制信号。

    机械手上的命令行外壳可直接访问ESP32模块:
    24.png
    USB CDC


    恩智浦MCUXpresso SDK中有一个基本的USB CDC示例项目。但这仅适用于非常简单的情况,不能重入。这个问题并不是那么容易找到:基本上使用启用了流控制的终端,USB堆栈没有发回ACK包,从而导致主机上的终端阻塞。使用LeCroy USB分析仪来发现问题非常有帮助:
    25.png
    要更改的第一件事是对virtual_com.c中的接收数据使用可重入环形缓冲区,并计划下一个接收事件。所做的更改在下面用“ << EST”标记。
    1. <font size="3" face="微软雅黑">        case kUSB_DeviceCdcEventRecvResponse:
    2.         {
    3.             if ((1 == s_cdcVcom.attach) && (1 == s_cdcVcom.startTransactions))
    4.             {
    5. #if 1 /* << EST */ size_t i, dataSize; dataSize = epCbParam->length;
    6.                 if (dataSize!=0 && dataSize!=(size_t)-1) {
    7.                   i = 0;
    8.                   while(i<dataSize) { McuRB_Put(usb_rxBuf, &s_currRecvBuf[i]); i++; } } #endif #if defined(FSL_FEATURE_USB_KHCI_KEEP_ALIVE_ENABLED) && (FSL_FEATURE_USB_KHCI_KEEP_ALIVE_ENABLED > 0U) && \
    9.     defined(USB_DEVICE_CONFIG_KEEP_ALIVE_MODE) && (USB_DEVICE_CONFIG_KEEP_ALIVE_MODE > 0U) &&             \
    10.     defined(FSL_FEATURE_USB_KHCI_USB_RAM) && (FSL_FEATURE_USB_KHCI_USB_RAM > 0U)
    11.                 s_waitForDataReceive = 0;
    12.                 USB0->INTEN |= USB_INTEN_SOFTOKEN_MASK;
    13. #endif
    14.                 //if (!s_recvSize) /* << EST */ { /* Schedule buffer for next receive event */ error = USB_DeviceCdcAcmRecv(handle, USB_CDC_VCOM_BULK_OUT_ENDPOINT, s_currRecvBuf, g_UsbDeviceCdcVcomDicEndpoints[0].maxPacketSize); #if defined(FSL_FEATURE_USB_KHCI_KEEP_ALIVE_ENABLED) && (FSL_FEATURE_USB_KHCI_KEEP_ALIVE_ENABLED > 0U) && \
    15.     defined(USB_DEVICE_CONFIG_KEEP_ALIVE_MODE) && (USB_DEVICE_CONFIG_KEEP_ALIVE_MODE > 0U) &&             \
    16.     defined(FSL_FEATURE_USB_KHCI_USB_RAM) && (FSL_FEATURE_USB_KHCI_USB_RAM > 0U)
    17.                     s_waitForDataReceive = 1;
    18.                     USB0->INTEN &= ~USB_INTEN_SOFTOKEN_MASK;
    19. #endif
    20.                 }
    21.             }
    22.         }
    23.         break;</font>
    复制代码
    接下来的事情是正确处理kUSB_DeviceCdcEventSetControlLineState并调用应用程序回调:
    1. <font size="3" face="微软雅黑">        case kUSB_DeviceCdcEventSetControlLineState:
    2.         {
    3.             s_usbCdcAcmInfo.dteStatus = acmReqParam->setupValue;
    4.             /* activate/deactivate Tx carrier */
    5.             if (acmInfo->dteStatus & USB_DEVICE_CDC_CONTROL_SIG_BITMAP_CARRIER_ACTIVATION)
    6.             {
    7.                 acmInfo->uartState |= USB_DEVICE_CDC_UART_STATE_TX_CARRIER;
    8.             }
    9.             else
    10.             {
    11.                 acmInfo->uartState &= (uint16_t)~USB_DEVICE_CDC_UART_STATE_TX_CARRIER;
    12.             }

    13.             /* activate carrier and DTE. Com port of terminal tool running on PC is open now */
    14.             if (acmInfo->dteStatus & USB_DEVICE_CDC_CONTROL_SIG_BITMAP_DTE_PRESENCE)
    15.             {
    16.                 acmInfo->uartState |= USB_DEVICE_CDC_UART_STATE_RX_CARRIER;
    17.            }
    18.             /* Com port of terminal tool running on PC is closed now */
    19.             else
    20.             {
    21.                 acmInfo->uartState &= (uint16_t)~USB_DEVICE_CDC_UART_STATE_RX_CARRIER;
    22.             }
    23.             /* Indicates to DCE if DTE is present or not */
    24.             acmInfo->dtePresent = (acmInfo->dteStatus & USB_DEVICE_CDC_CONTROL_SIG_BITMAP_DTE_PRESENCE) ? true : false;
    25.     #if 1 /* << EST */ // http://markdingst.blogspot.com/2014/06/implementing-usb-communication-device.html // bit 0: Indicates to DCE if DTE is present or not. This signal corresponds to V.24 signal 108/2 and RS232 signal DTR. // 0: DTE is not present. // 1: DTE is present // bit 1: Carrier control for half duplex modems. This signal corresponds to V.24 signal 105 and RS232 signal RTS. // 0: Deactivate carrier. // 1: Activate carrier. // The device ignores the value of this bit when operating in full duplex mode. McuESP32_UartState_Callback(acmInfo->uartState);
    26.         #if ENABLED_USB_CDC_LOGGING
    27.             McuRTT_printf(0, "CDC: set control dteStatus: %d, uartState: %d, dtePresent: %d, attach: %d, startTransaction: %d\r\n", s_usbCdcAcmInfo.dteStatus, acmInfo->uartState, acmInfo->dtePresent, s_cdcVcom.attach, s_cdcVcom.startTransactions);
    28.         #endif
    29.     #endif

    30.             /* Initialize the serial state buffer */
    31.             acmInfo->serialStateBuf[0] = NOTIF_REQUEST_TYPE;                /* bmRequestType */
    32.             acmInfo->serialStateBuf[1] = USB_DEVICE_CDC_NOTIF_SERIAL_STATE; /* bNotification */
    33.             acmInfo->serialStateBuf[2] = 0x00;                              /* wValue */
    34.             acmInfo->serialStateBuf[3] = 0x00;
    35.             acmInfo->serialStateBuf[4] = 0x00; /* wIndex */
    36.             acmInfo->serialStateBuf[5] = 0x00;
    37.             acmInfo->serialStateBuf[6] = UART_BITMAP_SIZE; /* wLength */
    38.             acmInfo->serialStateBuf[7] = 0x00;
    39.             /* Notify to host the line state */
    40.             acmInfo->serialStateBuf[4] = acmReqParam->interfaceIndex;
    41.             /* Lower byte of UART BITMAP */
    42.             uartBitmap = (uint8_t *)&acmInfo->serialStateBuf[NOTIF_PACKET_SIZE + UART_BITMAP_SIZE - 2];
    43.             uartBitmap[0] = acmInfo->uartState & 0xFFu;
    44.             uartBitmap[1] = (acmInfo->uartState >> 8) & 0xFFu;
    45.             len = (uint32_t)(NOTIF_PACKET_SIZE + UART_BITMAP_SIZE);
    46.             if (0 == ((usb_device_cdc_acm_struct_t *)handle)->hasSentState)
    47.             {
    48.                 error = USB_DeviceCdcAcmSend(handle, USB_CDC_VCOM_INTERRUPT_IN_ENDPOINT, acmInfo->serialStateBuf, len);
    49.                 if (kStatus_USB_Success != error)
    50.                 {
    51.                   usb_echo("kUSB_DeviceCdcEventSetControlLineState error!");
    52.                 }
    53.                 ((usb_device_cdc_acm_struct_t *)handle)->hasSentState = 1;
    54.             }

    55.             /* Update status */
    56.             if (acmInfo->dteStatus & USB_DEVICE_CDC_CONTROL_SIG_BITMAP_CARRIER_ACTIVATION)
    57.             {
    58.                 /*  To do: CARRIER_ACTIVATED */
    59. #if ENABLED_USB_CDC_LOGGING
    60.               McuRTT_printf(0, "CARRIER_ACTIVATED\r\n");
    61. #endif
    62.             }
    63.             else
    64.             {
    65.                 /* To do: CARRIER_DEACTIVATED */
    66. #if ENABLED_USB_CDC_LOGGING
    67.               McuRTT_printf(0, "CARRIER_DEACTIVATED\r\n");
    68. #endif
    69.             }
    70. #if 0
    71.             if (acmInfo->dteStatus & USB_DEVICE_CDC_CONTROL_SIG_BITMAP_DTE_PRESENCE)
    72. #else /* << EST */ if ( (acmInfo->dteStatus & USB_DEVICE_CDC_CONTROL_SIG_BITMAP_DTE_PRESENCE)
    73.                 || (s_cdcVcom.attach && (acmInfo->dteStatus==USB_DEVICE_CDC_CONTROL_SIG_BITMAP_CARRIER_ACTIVATION) /* && !s_cdcVcom.startTransactions*/) /* << EST */ ) #endif { /* DTE_ACTIVATED */ if (1 == s_cdcVcom.attach) { s_cdcVcom.startTransactions = 1; #if ENABLED_USB_CDC_LOGGING McuRTT_printf(0, "startTransactions=1\r\n"); #endif #if defined(FSL_FEATURE_USB_KHCI_KEEP_ALIVE_ENABLED) && (FSL_FEATURE_USB_KHCI_KEEP_ALIVE_ENABLED > 0U) && \
    74.     defined(USB_DEVICE_CONFIG_KEEP_ALIVE_MODE) && (USB_DEVICE_CONFIG_KEEP_ALIVE_MODE > 0U) &&             \
    75.     defined(FSL_FEATURE_USB_KHCI_USB_RAM) && (FSL_FEATURE_USB_KHCI_USB_RAM > 0U)
    76.                     s_waitForDataReceive = 1;
    77.                     USB0->INTEN &= ~USB_INTEN_SOFTOKEN_MASK;
    78.                     s_comOpen = 1;
    79.                     usb_echo("USB_APP_CDC_DTE_ACTIVATED\r\n");
    80. #endif
    81.                 }
    82.             }
    83.             else
    84.             {
    85.                 /* DTE_DEACTIVATED */
    86.                 if (1 == s_cdcVcom.attach)
    87.                 {
    88.               //      s_cdcVcom.startTransactions = 0;
    89. #if ENABLED_USB_CDC_LOGGING
    90.                    McuRTT_printf(0, "startTransactions=0\r\n");
    91. #endif
    92.                 }
    93.             }</font>
    复制代码
    辅助程序处理GPIO引脚切换:
    1. <font size="3" face="微软雅黑">static void AssertReset(void) {
    2.   McuGPIO_SetAsOutput(McuESP32_RF_EN_Pin, false); /* output, LOW */
    3. }

    4. static void DeassertReset(void) {
    5.   McuGPIO_SetAsInput(McuESP32_RF_EN_Pin);
    6. }

    7. static void DoReset(void) {
    8.   AssertReset();
    9.   vTaskDelay(pdMS_TO_TICKS(1));
    10.   DeassertReset();
    11. }

    12. static void AssertBootloaderMode(void) {
    13.   McuGPIO_SetAsOutput(McuESP32_RF_IO0_Pin, false); /* output, LOW */
    14. }

    15. static void DeassertBootloaderMode(void) {
    16.   McuGPIO_SetAsInput(McuESP32_RF_IO0_Pin);
    17. }</font>
    复制代码
    现在介绍控制状态序列。 我已经使用不同的终端程序以及在连接,编程和最后复位期间ESP-IDF如何使用DTR / RTS信号验证了它:
    1. <font size="3" face="微软雅黑">/* idf.py flash sequence:
    2. *
    3. * 00> State: 3, DtrRts: 3

    4. * 00> State: 2, DtrRts: 1
    5. * 00> State: 3, DtrRts: 3
    6. * 00> State: 1, DtrRts: 2
    7. * 00> State: 0, DtrRts: 0

    8. * 00> State: 2, DtrRts: 1
    9. * 00> State: 3, DtrRts: 3
    10. * 00> State: 1, DtrRts: 2
    11. * 00> State: 0, DtrRts: 0
    12. *
    13. * reset at the end:
    14. * 00> State: 2, DtrRts: 1
    15. * 00> State: 0, DtrRts: 0
    16. */</font>
    复制代码
    “魔术”是在回调本身中完成的:它会记住控制线信号的先前状态,并自动切换到编程模式并返回正常模式:
    1. <font size="3" face="微软雅黑">void McuESP32_UartState_Callback(uint8_t state) { /* callback for DTR and RTS lines */
    2.   static uint8_t prevState = -1;
    3.   static uint8_t prevPrevState = -1;
    4.   uint8_t DtrRts;

    5. #if McuESP32_VERBOSE_CONTROL_SIGNALS
    6.   McuRTT_printf(0, "state: %d, prev: %d, prevprev: %d\r\n", state, prevState, prevPrevState);
    7. #endif
    8.   if (state != prevState) {
    9.     if (McuESP32_UsbPrgMode==McuESP32_USB_PRG_MODE_AUTO || McuESP32_UsbPrgMode==McuESP32_USB_PRG_MODE_ON) {
    10.       /*
    11.        * DTR  RTS  EN  GPIO0
    12.        * 1    1    1   1
    13.        * 0    0    1   1
    14.        * 1    0    0   0
    15.        * 0    1    1   0
    16.        */
    17.       DtrRts = 0;
    18.       if ((state&1)==1) { /* DTR */
    19.         DtrRts |= 2; /* DTR set */
    20.       }
    21.       if ((state&2)==2) { /* DTR */
    22.         DtrRts |= 1; /* RTS set */
    23.       }
    24.     #if McuESP32_VERBOSE_CONTROL_SIGNALS
    25.       McuRTT_printf(0, "State: %d, DtrRts: %d\r\n", state, DtrRts);
    26.     #endif
    27.       switch(DtrRts) {
    28.         default:
    29.         case 0:
    30.           DeassertReset();
    31.           McuWait_Waitus(100); /* block for a short time (in the ISR!!!) ==> should have a 100 uF added to the reset line */
    32.           DeassertBootloaderMode();
    33.           //McuRTT_printf(0, "Release both: %d\r\n", DtrRts);
    34.           break;
    35.         case 1:
    36.           AssertBootloaderMode();
    37.           //McuRTT_printf(0, "assert BL: %d\r\n", DtrRts);
    38.           break;
    39.         case 2:
    40.           if (McuGPIO_IsLow(McuESP32_RF_EN_Pin)) {
    41.             if (McuGPIO_IsLow(McuESP32_RF_IO0_Pin)) {
    42.               McuESP32_IsProgramming = true; /* the DeassertReset() below will enter bootloader mode */
    43.               McuRTT_printf(0, "Enter Bootloader Mode\r\n");
    44.             } else {
    45.               McuESP32_IsProgramming = false; /* the DeassertReset() below will do a reset without bootloader */
    46.               McuRTT_printf(0, "Reset\r\n");
    47.             }
    48.           }
    49.           DeassertReset();
    50.           McuWait_Waitus(100); /* block for a short time (in the ISR!!!) ==> should have a 100 uF added to the reset line */
    51.           //McuRTT_printf(0, "release reset: %d\r\n", DtrRts);
    52.           break;
    53.         case 3:
    54.           AssertReset();
    55.           //McuRTT_printf(0, "assert reset: %d\r\n", DtrRts);
    56.           break;
    57.       } /* switch */
    58.       if (state==0 && prevState==2 && prevPrevState==0) {
    59.         // reset sequence with idf.py and Arduino IDE:
    60.         // State: 0 DtrRts: 0 Release both: 0
    61.         // State: 2 DtrRts: 1 assert BL: 1
    62.         // State: 0 DtrRts: 0 Release both: 0
    63.         McuRTT_printf(0, "Request Reset\r\n");
    64.         McuESP32_ScheduleReset = true; /* cannot do reset sequence here, as called from an interrupt, so we cannot block */
    65.         McuESP32_IsProgramming = false;
    66.       }
    67.     }
    68.     prevPrevState = prevState;
    69.     prevState = state;
    70.   } /* if state!=prevState */
    71. }</font>
    复制代码
    UART任务


    ESP32的Tx和Rx由K22上的两个简单FreeRTOS任务处理。 以下是在接收方的代码:
    1. <font size="3" face="微软雅黑">static void UartRxTask(void *pv) { /* task handling characters sent by the ESP32 module */
    2.   unsigned char ch;
    3.   BaseType_t res;

    4.   for(;;) {
    5.     res = xQueueReceive(uartRxQueue, &ch, portMAX_DELAY);
    6.     if (res==pdPASS) {
    7. #if PL_CONFIG_USE_USB_CDC_ESP32
    8.       if (McuESP32_IsProgramming && USB_CdcIsConnected()) { /* send directly to programmer attached on the USB */
    9.         USB_CdcStdio.stdOut(ch); /* forward to USB CDC and the programmer on the host */
    10.       }
    11.       if (McuESP32_CopyUartToShell && !McuESP32_IsProgramming) { /* only write to shell if not in programming mode. Programming mode might crash RTT */
    12.         SHELL_SendChar(ch); /* write on console output */
    13.       }
    14. #else
    15.     SHELL_SendChar(ch); /* write on console output */
    16. #endif
    17.     }
    18.   }
    19. }</font>
    复制代码
    以下是发送的任务代码:
    1. <font size="3" face="微软雅黑">static void UartTxTask(void *pv) { /* task handling sending data to the ESP32 module */
    2.   unsigned char ch;
    3.   BaseType_t res;
    4.   bool workToDo;

    5.   for(;;) {
    6.     if (McuESP32_ScheduleReset) {
    7.       McuESP32_ScheduleReset = false;
    8.       McuRTT_printf(0, "Performing reset\r\n");
    9.       DoReset();
    10.     }
    11.     workToDo = false;
    12.     do {
    13.       res = xQueueReceive(uartTxQueue, &ch, 0); /* poll queue */
    14.       if (res==pdPASS) {
    15.         workToDo = true;
    16.         McuESP32_CONFIG_UART_WRITE_BLOCKING(McuESP32_CONFIG_UART_DEVICE, &ch, 1);
    17.       }
    18.     } while (res==pdPASS);
    19. #if PL_CONFIG_USE_USB_CDC_ESP32
    20.     while (USB_CdcStdio.keyPressed()) {
    21.       workToDo = true;
    22.       USB_CdcStdio.stdIn(&ch); /* read byte */
    23.       McuESP32_CONFIG_UART_WRITE_BLOCKING(McuESP32_CONFIG_UART_DEVICE, &ch, 1); /* send to the module */
    24.       if (McuESP32_CopyUartToShell && !McuESP32_IsProgramming) {
    25.         SHELL_SendChar(ch);  /* copy to console */
    26.       }
    27.     }
    28. #endif
    29.     if (!workToDo) {
    30.       vTaskDelay(pdMS_TO_TICKS(5));
    31.     }
    32.   }
    33. }</font>
    复制代码
    这样,我可以连接到NXPK22的USB端口,并使用Eclipse编程ESP32:
    27.png
    总结


    可以将NXP Kinetis K22用作ESP32模块的网关:可以对其进行编程,也可以用于串行/ UART /终端连接。 还未进行优化,因此编程速度目前限制为115200波特率。 由于idf.py正在压缩图像,因此编程速度约为150 kBits / s。




    作者:阿哲               文章出处:点击

    签到签到
    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /4 下一条

    Archiver|手机版|小黑屋|恩智浦技术社区

    GMT+8, 2024-4-26 03:35 , Processed in 0.113718 second(s), 20 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.

    快速回复 返回顶部 返回列表