| /* |
| * This file is part of the Micro Python project, http://micropython.org/ |
| * |
| * Taken from ST Cube library and heavily modified. See below for original |
| * copyright header. |
| */ |
| |
| /** |
| ****************************************************************************** |
| * @file USB_Device/CDC_Standalone/Src/usbd_cdc_interface.c |
| * @author MCD Application Team |
| * @version V1.0.1 |
| * @date 26-February-2014 |
| * @brief Source file for USBD CDC interface |
| ****************************************************************************** |
| * @attention |
| * |
| * <h2><center>© COPYRIGHT(c) 2014 STMicroelectronics</center></h2> |
| * |
| * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); |
| * You may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at: |
| * |
| * http://www.st.com/software_license_agreement_liberty_v2 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| ****************************************************************************** |
| */ |
| |
| /* Includes ------------------------------------------------------------------*/ |
| #include <stdbool.h> |
| #include "stm32f4xx_hal.h" |
| #include "usbd_cdc_msc_hid.h" |
| #include "usbd_cdc_interface.h" |
| #include "pendsv.h" |
| #include "usb.h" |
| |
| // CDC control commands |
| #define CDC_SEND_ENCAPSULATED_COMMAND 0x00 |
| #define CDC_GET_ENCAPSULATED_RESPONSE 0x01 |
| #define CDC_SET_COMM_FEATURE 0x02 |
| #define CDC_GET_COMM_FEATURE 0x03 |
| #define CDC_CLEAR_COMM_FEATURE 0x04 |
| #define CDC_SET_LINE_CODING 0x20 |
| #define CDC_GET_LINE_CODING 0x21 |
| #define CDC_SET_CONTROL_LINE_STATE 0x22 |
| #define CDC_SEND_BREAK 0x23 |
| |
| /* Private typedef -----------------------------------------------------------*/ |
| /* Private define ------------------------------------------------------------*/ |
| #define APP_RX_DATA_SIZE 1024 // I think this must be at least CDC_DATA_FS_OUT_PACKET_SIZE=64 (APP_RX_DATA_SIZE was 2048) |
| #define APP_TX_DATA_SIZE 1024 // I think this can be any value (was 2048) |
| |
| /* Private macro -------------------------------------------------------------*/ |
| /* Private variables ---------------------------------------------------------*/ |
| |
| static uint8_t dev_is_connected = 0; // indicates if we are connected |
| |
| static uint8_t UserRxBuffer[APP_RX_DATA_SIZE]; // received data from USB OUT endpoint is stored in this buffer |
| static uint16_t UserRxBufCur = 0; // points to next available character in UserRxBuffer |
| static uint16_t UserRxBufLen = 0; // counts number of valid characters in UserRxBuffer |
| |
| static uint8_t UserTxBuffer[APP_TX_DATA_SIZE]; // data for USB IN endpoind is stored in this buffer |
| static uint16_t UserTxBufPtrIn = 0; // increment this pointer modulo APP_TX_DATA_SIZE when new data is available |
| static __IO uint16_t UserTxBufPtrOut = 0; // increment this pointer modulo APP_TX_DATA_SIZE when data is drained |
| static uint16_t UserTxBufPtrOutShadow = 0; // shadow of above |
| static uint8_t UserTxBufPtrWaitCount = 0; // used to implement a timeout waiting for low-level USB driver |
| static uint8_t UserTxNeedEmptyPacket = 0; // used to flush the USB IN endpoint if the last packet was exactly the endpoint packet size |
| |
| static int user_interrupt_char = VCP_CHAR_NONE; |
| static void *user_interrupt_data = NULL; |
| |
| /* USB handler declaration */ |
| extern USBD_HandleTypeDef hUSBDDevice; |
| |
| /* Private function prototypes -----------------------------------------------*/ |
| static int8_t CDC_Itf_Init (void); |
| static int8_t CDC_Itf_DeInit (void); |
| static int8_t CDC_Itf_Control (uint8_t cmd, uint8_t* pbuf, uint16_t length); |
| static int8_t CDC_Itf_Receive (uint8_t* pbuf, uint32_t *Len); |
| |
| const USBD_CDC_ItfTypeDef USBD_CDC_fops = { |
| CDC_Itf_Init, |
| CDC_Itf_DeInit, |
| CDC_Itf_Control, |
| CDC_Itf_Receive |
| }; |
| |
| /* Private functions ---------------------------------------------------------*/ |
| |
| /** |
| * @brief CDC_Itf_Init |
| * Initializes the CDC media low layer |
| * @param None |
| * @retval Result of the opeartion: USBD_OK if all operations are OK else USBD_FAIL |
| */ |
| static int8_t CDC_Itf_Init(void) |
| { |
| #if 0 |
| /*##-1- Configure the UART peripheral ######################################*/ |
| /* Put the USART peripheral in the Asynchronous mode (UART Mode) */ |
| /* USART configured as follow: |
| - Word Length = 8 Bits |
| - Stop Bit = One Stop bit |
| - Parity = No parity |
| - BaudRate = 115200 baud |
| - Hardware flow control disabled (RTS and CTS signals) */ |
| UartHandle.Instance = USARTx; |
| UartHandle.Init.BaudRate = 115200; |
| UartHandle.Init.WordLength = UART_WORDLENGTH_8B; |
| UartHandle.Init.StopBits = UART_STOPBITS_1; |
| UartHandle.Init.Parity = UART_PARITY_NONE; |
| UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE; |
| UartHandle.Init.Mode = UART_MODE_TX_RX; |
| |
| if(HAL_UART_Init(&UartHandle) != HAL_OK) |
| { |
| /* Initialization Error */ |
| Error_Handler(); |
| } |
| |
| /*##-2- Put UART peripheral in IT reception process ########################*/ |
| /* Any data received will be stored in "UserTxBuffer" buffer */ |
| if(HAL_UART_Receive_IT(&UartHandle, (uint8_t *)UserTxBuffer, 1) != HAL_OK) |
| { |
| /* Transfer error in reception process */ |
| Error_Handler(); |
| } |
| |
| /*##-3- Configure the TIM Base generation #################################*/ |
| now done in HAL_MspInit |
| TIM_Config(); |
| #endif |
| |
| /*##-4- Start the TIM Base generation in interrupt mode ####################*/ |
| /* Start Channel1 */ |
| __HAL_TIM_ENABLE_IT(&TIM3_Handle, TIM_IT_UPDATE); |
| |
| /*##-5- Set Application Buffers ############################################*/ |
| USBD_CDC_SetTxBuffer(&hUSBDDevice, UserTxBuffer, 0); |
| USBD_CDC_SetRxBuffer(&hUSBDDevice, UserRxBuffer); |
| |
| UserRxBufCur = 0; |
| UserRxBufLen = 0; |
| |
| /* NOTE: we cannot reset these here, because USBD_CDC_SetInterrupt |
| * may be called before this init function to set these values. |
| * This can happen if the USB enumeration occurs after the call to |
| * USBD_CDC_SetInterrupt. |
| user_interrupt_char = VCP_CHAR_NONE; |
| user_interrupt_data = NULL; |
| */ |
| |
| return (USBD_OK); |
| } |
| |
| /** |
| * @brief CDC_Itf_DeInit |
| * DeInitializes the CDC media low layer |
| * @param None |
| * @retval Result of the opeartion: USBD_OK if all operations are OK else USBD_FAIL |
| */ |
| static int8_t CDC_Itf_DeInit(void) |
| { |
| #if 0 |
| /* DeInitialize the UART peripheral */ |
| if(HAL_UART_DeInit(&UartHandle) != HAL_OK) |
| { |
| /* Initialization Error */ |
| } |
| #endif |
| return (USBD_OK); |
| } |
| |
| /** |
| * @brief CDC_Itf_Control |
| * Manage the CDC class requests |
| * @param Cmd: Command code |
| * @param Buf: Buffer containing command data (request parameters) |
| * @param Len: Number of data to be sent (in bytes) |
| * @retval Result of the opeartion: USBD_OK if all operations are OK else USBD_FAIL |
| */ |
| static int8_t CDC_Itf_Control(uint8_t cmd, uint8_t* pbuf, uint16_t length) { |
| switch (cmd) { |
| case CDC_SEND_ENCAPSULATED_COMMAND: |
| /* Add your code here */ |
| break; |
| |
| case CDC_GET_ENCAPSULATED_RESPONSE: |
| /* Add your code here */ |
| break; |
| |
| case CDC_SET_COMM_FEATURE: |
| /* Add your code here */ |
| break; |
| |
| case CDC_GET_COMM_FEATURE: |
| /* Add your code here */ |
| break; |
| |
| case CDC_CLEAR_COMM_FEATURE: |
| /* Add your code here */ |
| break; |
| |
| case CDC_SET_LINE_CODING: |
| #if 0 |
| LineCoding.bitrate = (uint32_t)(pbuf[0] | (pbuf[1] << 8) |\ |
| (pbuf[2] << 16) | (pbuf[3] << 24)); |
| LineCoding.format = pbuf[4]; |
| LineCoding.paritytype = pbuf[5]; |
| LineCoding.datatype = pbuf[6]; |
| /* Set the new configuration */ |
| #endif |
| break; |
| |
| case CDC_GET_LINE_CODING: |
| #if 0 |
| pbuf[0] = (uint8_t)(LineCoding.bitrate); |
| pbuf[1] = (uint8_t)(LineCoding.bitrate >> 8); |
| pbuf[2] = (uint8_t)(LineCoding.bitrate >> 16); |
| pbuf[3] = (uint8_t)(LineCoding.bitrate >> 24); |
| pbuf[4] = LineCoding.format; |
| pbuf[5] = LineCoding.paritytype; |
| pbuf[6] = LineCoding.datatype; |
| #endif |
| |
| /* Add your code here */ |
| pbuf[0] = (uint8_t)(115200); |
| pbuf[1] = (uint8_t)(115200 >> 8); |
| pbuf[2] = (uint8_t)(115200 >> 16); |
| pbuf[3] = (uint8_t)(115200 >> 24); |
| pbuf[4] = 0; // stop bits (1) |
| pbuf[5] = 0; // parity (none) |
| pbuf[6] = 8; // number of bits (8) |
| break; |
| |
| case CDC_SET_CONTROL_LINE_STATE: |
| dev_is_connected = length & 1; // wValue is passed in Len (bit of a hack) |
| break; |
| |
| case CDC_SEND_BREAK: |
| /* Add your code here */ |
| break; |
| |
| default: |
| break; |
| } |
| |
| return USBD_OK; |
| } |
| |
| /** |
| * @brief TIM period elapsed callback |
| * @param htim: TIM handle |
| * @retval None |
| */ |
| void USBD_CDC_HAL_TIM_PeriodElapsedCallback(void) { |
| if (!dev_is_connected) { |
| // CDC device is not connected to a host, so we are unable to send any data |
| return; |
| } |
| |
| if (UserTxBufPtrOut == UserTxBufPtrIn && !UserTxNeedEmptyPacket) { |
| // No outstanding data to send |
| return; |
| } |
| |
| if (UserTxBufPtrOut != UserTxBufPtrOutShadow) { |
| // We have sent data and are waiting for the low-level USB driver to |
| // finish sending it over the USB in-endpoint. |
| // We have a 15 * 10ms = 150ms timeout |
| if (UserTxBufPtrWaitCount < 15) { |
| PCD_HandleTypeDef *hpcd = hUSBDDevice.pData; |
| USB_OTG_GlobalTypeDef *USBx = hpcd->Instance; |
| if (USBx_INEP(CDC_IN_EP & 0x7f)->DIEPTSIZ & USB_OTG_DIEPTSIZ_XFRSIZ) { |
| // USB in-endpoint is still reading the data |
| UserTxBufPtrWaitCount++; |
| return; |
| } |
| } |
| UserTxBufPtrOut = UserTxBufPtrOutShadow; |
| } |
| |
| if (UserTxBufPtrOutShadow != UserTxBufPtrIn || UserTxNeedEmptyPacket) { |
| uint32_t buffptr; |
| uint32_t buffsize; |
| |
| if (UserTxBufPtrOutShadow > UserTxBufPtrIn) { // rollback |
| buffsize = APP_TX_DATA_SIZE - UserTxBufPtrOutShadow; |
| } else { |
| buffsize = UserTxBufPtrIn - UserTxBufPtrOutShadow; |
| } |
| |
| buffptr = UserTxBufPtrOutShadow; |
| |
| USBD_CDC_SetTxBuffer(&hUSBDDevice, (uint8_t*)&UserTxBuffer[buffptr], buffsize); |
| |
| if (USBD_CDC_TransmitPacket(&hUSBDDevice) == USBD_OK) { |
| UserTxBufPtrOutShadow += buffsize; |
| if (UserTxBufPtrOutShadow == APP_TX_DATA_SIZE) { |
| UserTxBufPtrOutShadow = 0; |
| } |
| UserTxBufPtrWaitCount = 0; |
| |
| // According to the USB specification, a packet size of 64 bytes (CDC_DATA_FS_MAX_PACKET_SIZE) |
| // gets held at the USB host until the next packet is sent. This is because a |
| // packet of maximum size is considered to be part of a longer chunk of data, and |
| // the host waits for all data to arrive (ie, waits for a packet < max packet size). |
| // To flush a packet of exactly max packet size, we need to send a zero-size packet. |
| // See eg http://www.cypress.com/?id=4&rID=92719 |
| UserTxNeedEmptyPacket = (buffsize == CDC_DATA_FS_MAX_PACKET_SIZE && UserTxBufPtrOutShadow == UserTxBufPtrIn); |
| } |
| } |
| } |
| |
| /** |
| * @brief CDC_Itf_DataRx |
| * Data received over USB OUT endpoint is processed here. |
| * @param Buf: Buffer of data received |
| * @param Len: Number of data received (in bytes) |
| * @retval Result of the opeartion: USBD_OK if all operations are OK else USBD_FAIL |
| * @note The buffer we are passed here is just UserRxBuffer, so we are |
| * free to modify it. |
| */ |
| static int8_t CDC_Itf_Receive(uint8_t* Buf, uint32_t *Len) { |
| #if 0 |
| // this sends the data over the UART using DMA |
| HAL_UART_Transmit_DMA(&UartHandle, Buf, *Len); |
| #endif |
| |
| // TODO improve this function to implement a circular buffer |
| |
| // if we have processed all the characters, reset the buffer counters |
| if (UserRxBufCur > 0 && UserRxBufCur >= UserRxBufLen) { |
| memmove(UserRxBuffer, UserRxBuffer + UserRxBufLen, *Len); |
| UserRxBufCur = 0; |
| UserRxBufLen = 0; |
| } |
| |
| uint32_t delta_len; |
| |
| if (user_interrupt_char == VCP_CHAR_NONE) { |
| // no special interrupt character |
| delta_len = *Len; |
| |
| } else { |
| // filter out sepcial interrupt character from the buffer |
| bool char_found = false; |
| uint8_t *dest = Buf; |
| uint8_t *src = Buf; |
| uint8_t *buf_top = Buf + *Len; |
| for (; src < buf_top; src++) { |
| if (*src == user_interrupt_char) { |
| char_found = true; |
| } else { |
| if (char_found) { |
| *dest = *src; |
| } |
| dest++; |
| } |
| } |
| |
| if (char_found) { |
| // raise exception when interrupts are finished |
| user_interrupt_char = VCP_CHAR_NONE; |
| pendsv_nlr_jump(user_interrupt_data); |
| } |
| |
| // length of remaining characters |
| delta_len = dest - Buf; |
| } |
| |
| if (UserRxBufLen + delta_len + CDC_DATA_FS_MAX_PACKET_SIZE > APP_RX_DATA_SIZE) { |
| // if we keep this data then the buffer can overflow on the next USB rx |
| // so we don't increment the length, and throw this data away |
| } else { |
| // data fits, leaving room for another CDC_DATA_FS_OUT_PACKET_SIZE |
| UserRxBufLen += delta_len; |
| } |
| |
| // initiate next USB packet transfer, to append to existing data in buffer |
| USBD_CDC_SetRxBuffer(&hUSBDDevice, UserRxBuffer + UserRxBufLen); |
| USBD_CDC_ReceivePacket(&hUSBDDevice); |
| |
| return USBD_OK; |
| } |
| |
| int USBD_CDC_IsConnected(void) { |
| return dev_is_connected; |
| } |
| |
| void USBD_CDC_SetInterrupt(int chr, void *data) { |
| user_interrupt_char = chr; |
| user_interrupt_data = data; |
| } |
| |
| void USBD_CDC_Tx(const char *str, uint32_t len) { |
| for (int i = 0; i < len; i++) { |
| // If the CDC device is not connected to the host then we don't have anyone to receive our data. |
| // The device may become connected in the future, so we should at least try to fill the buffer |
| // and hope that it doesn't overflow by the time the device connects. |
| // If the device is not connected then we should go ahead and fill the buffer straight away, |
| // ignoring overflow. Otherwise, we should make sure that we have enough room in the buffer. |
| if (dev_is_connected) { |
| // If the buffer is full, wait until it gets drained, with a timeout of 500ms |
| // (wraparound of tick is taken care of by 2's complement arithmetic). |
| uint32_t start = HAL_GetTick(); |
| while (((UserTxBufPtrIn + 1) & (APP_TX_DATA_SIZE - 1)) == UserTxBufPtrOut && HAL_GetTick() - start <= 500) { |
| __WFI(); // enter sleep mode, waiting for interrupt |
| } |
| |
| // Some unused code that makes sure the low-level USB buffer is drained. |
| // Waiting for low-level is handled in USBD_CDC_HAL_TIM_PeriodElapsedCallback. |
| /* |
| start = HAL_GetTick(); |
| PCD_HandleTypeDef *hpcd = hUSBDDevice.pData; |
| if (hpcd->IN_ep[0x83 & 0x7f].is_in) { |
| //volatile uint32_t *xfer_count = &hpcd->IN_ep[0x83 & 0x7f].xfer_count; |
| //volatile uint32_t *xfer_len = &hpcd->IN_ep[0x83 & 0x7f].xfer_len; |
| USB_OTG_GlobalTypeDef *USBx = hpcd->Instance; |
| while ( |
| // *xfer_count < *xfer_len // using this works |
| // (USBx_INEP(3)->DIEPTSIZ & USB_OTG_DIEPTSIZ_XFRSIZ) // using this works |
| && HAL_GetTick() - start <= 2000) { |
| __WFI(); // enter sleep mode, waiting for interrupt |
| } |
| } |
| */ |
| } |
| |
| UserTxBuffer[UserTxBufPtrIn] = str[i]; |
| UserTxBufPtrIn = (UserTxBufPtrIn + 1) & (APP_TX_DATA_SIZE - 1); |
| } |
| } |
| |
| int USBD_CDC_RxNum(void) { |
| return UserRxBufLen - UserRxBufCur; |
| } |
| |
| int USBD_CDC_RxGet(void) { |
| // wait for buffer to have at least 1 character in it |
| while (USBD_CDC_RxNum() == 0) { |
| __WFI(); |
| } |
| |
| // get next character |
| int c = UserRxBuffer[UserRxBufCur++]; |
| |
| return c; |
| } |