阿哲
发表于: 2020-1-30 16:20:19 | 显示全部楼层

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,这是本文的主题。

ttgo-esp32-micro-d4-module.png

TTGO ESP32 MICRO-D4模块


ESP32模组

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


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

ttgo-micro-32-with-a-normal-esp32-module.png

带有常规ESP32模块的TTGO Micro-32


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


在GitHub上可用的模块引脚下方:

ttgo-esp32.png

TTGO ESP32


首先,将模块焊接在插针插座上,然后与面包板一起使用:

ttgo-esp32-breakout-board.png

TTGO ESP32扩展板


要使用该模块,只需要几个引脚即可:

●    GND和3.3V

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

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

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

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

v1.0-schematics.png

V1.0原理图

robot-with-v1.0-of-the-ttgo-shield.png

带有TTGO Shield V1.0的机器人


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


编程模块

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

sumo-project.png

sumo项目


想法是,K22充当主机PC和ESP32之间的网关。

connection-diagram.png

连接图


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


机械手上的命令行外壳可直接访问ESP32模块:

esp32-shell-commands.png

ESP32 shell命令


USB CDC

恩智浦MCUXpresso SDK中有一个基本的USB CDC示例项目。但这仅适用于非常简单的情况,不能重入。这个问题并不是那么容易找到:基本上使用启用了流控制的终端,USB堆栈没有发回ACK包,从而导致主机上的终端阻塞。使用LeCroy USB分析仪来发现问题非常有帮助:

lecroy-usb-protocol-analyzer.png

LeCroy USB协议分析仪


要更改的第一件事是对virtual_com.c中的接收数据使用可重入环形缓冲区,并计划下一个接收事件。所做的更改在下面用“ << EST”标记。

  1.         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;
复制代码

接下来的事情是正确处理kUSB_DeviceCdcEventSetControlLineState并调用应用程序回调:

  1.         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.             }
  94.         }
  95.         break;
复制代码

辅助程序处理GPIO引脚切换:

  1. 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. }
复制代码

现在介绍控制状态序列。 我已经使用不同的终端程序以及在连接,编程和最后复位期间ESP-IDF如何使用DTR / RTS信号验证了它:

  1. /* 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. */
复制代码

“魔术”是在回调本身中完成的:它会记住控制线信号的先前状态,并自动切换到编程模式并返回正常模式:

  1. 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. }
复制代码

UART任务

ESP32的Tx和Rx由K22上的两个简单FreeRTOS任务处理。 以下是在接收方的代码:

  1. 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. }
复制代码

以下是发送的任务代码:

  1. 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. }
复制代码

这样,我可以连接到NXPK22的USB端口,并使用Eclipse编程ESP32:

programming-esp32-with-eclipse-1.png

使用Eclipse编程ESP32


总结

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

跳转到指定楼层
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

主题 1 | 回复: 1



手机版|

GMT+8, 2024-3-29 20:23 , Processed in 0.094071 second(s), 6 queries , Gzip On, MemCache On. Powered by Discuz! X3.5

YiBoard一板网 © 2015-2022 地址:河北省石家庄市长安区高营大街 ( 冀ICP备18020117号 )

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