/** ****************************************************************************** * @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 * *

© COPYRIGHT(c) 2014 STMicroelectronics

* * 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 #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 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) { // 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. if (UserTxBufPtrWaitCount < 10) { PCD_HandleTypeDef *hpcd = hUSBDDevice.pData; USB_OTG_GlobalTypeDef *USBx = hpcd->Instance; if (USBx_INEP(3)->DIEPTSIZ & USB_OTG_DIEPTSIZ_XFRSIZ) { // USB in-endpoint is still reading the data UserTxBufPtrWaitCount++; return; } } UserTxBufPtrOut = UserTxBufPtrOutShadow; } if (UserTxBufPtrOutShadow != UserTxBufPtrIn) { uint32_t buffptr; uint32_t buffsize; if (UserTxBufPtrOutShadow > UserTxBufPtrIn) { // rollback buffsize = APP_TX_DATA_SIZE - UserTxBufPtrOutShadow; } else { buffsize = UserTxBufPtrIn - UserTxBufPtrOutShadow; } buffptr = UserTxBufPtrOutShadow; // dpgeorge: For some reason that I don't understand, a packet size of 64 bytes // (CDC_DATA_FS_MAX_PACKET_SIZE) does not get through to the USB host until the // next packet is sent. To work around this, we just make sure that we never // send a packet 64 bytes in length. if (buffsize == CDC_DATA_FS_MAX_PACKET_SIZE) { buffsize -= 1; } 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; } } } /** * @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; }