Nuoeriris · 2020年06月22日

MM32 USB功能学习笔记 —— 虚拟串口CDC

在上一节我们介绍了MM32 MCUWINUSB功能,对于WINUSB来说,还是需要开发对应的上位机来测试收发数据,相对来说较麻烦,如果仅做MCUPC的数据通信功能,开发USB CDC功能可以直接用串口助手等工具即可测试数据收发。

本节我们来讲解如何在MM32 MCU实现CDC功能。

USB CDC类是USB通信设备类 Communication Device Class)的简称。CDC类是USB组织定义的一类专门给各种通信设备(电信通信设备和中速网络通信设备)使用的USB子类。根据CDC类所针对通信设备的不同,CDC类又被分成以下不同的模型:USB传统纯电话业务(POTS)模型,USB ISDN模型和USB网络模型。其中,USB传统纯电话业务模型,有可分为直接线控制模型(Direct Line Control Model)、抽象控制模型(Abstract Control Model)和USB电话模型(USB Telephone Model),本节我们所讨论的虚拟串口就属于USB传统纯电话业务模型下的抽象控制模型。

通常一个CDC类又是由两个接口子类组成通信接口类(Communication Interface Class)和数据接口类(Data Interface Class)。主要通过通信接口类对设备进行管理和控制,而通过数据接口类传送数据。这两个接口子类占有不同数量和类型的终端点(Endpoints),对于前面所述的不同CDC类模型,其所对应的接口的终端点需求也是不同的。如所需要讨论的抽象控制模型对终端点的需求,通信接口类需要一个控制终端点(Control Endpoint)和一个可选的中断(Interrupt)型终端点,数据接口子类需要一个方向为输入(IN)的周期性(Isochronous)型终端 点和一个方向为输出(OUT)的周期性型终端点。其中控制终端点主要用于USB设备的枚举和虚拟串口的波特率和数据类型(数据位数、停止位和起始位)设置 的通信。输出方向的非同步终端点用于主机(Host)向从设备(Slave)发送数据,相当于传统物理串口中的TXD线(如果从单片机的角度看),输入方向的非同步终端点用于从设备向主机发送数据,相当于传统物理串口中的RXD线。

USB CDC类的通信部分主要包含三部分:枚举过程、虚拟串口操作和数据通信。其中虚拟串口操作部分并不一定强制需要,因为若跳过这些虚拟串口的操作,实际上USB依然是可以通信的,因为在操作虚拟串口之前会有两条数据通信的数据。之所以会有虚拟串口操作,主要是我们通常使用PC作为Host端,在PC端使用一个串口工具来与其进行通信,PC端的对应驱动将其虚拟成一个普通串口,这样一来,可以方便PC端软件通过操作串口的方式来与其进行通信,但实际上,Host端与Device端物理上是通过USB总线来进行通信的,与串口没有关系,这一虚拟化过程,起决定性作用的是对应驱动,包含如何将每一条具体的虚拟串口操作对应到实际上的USB操作。这里需要注意地是,Host端与Device端的USB通信速率并不受所谓的串口波特率影响,它就是标准的USB2.0全速(12Mbps)速度,实际速率取决于总线的实际使用率、驱动访问USB外设有效速率(两边)以及外部环境对通信本身造成的干扰率等等因素组成。

本次我们采用MM32L373 miniboard作为测试开发板。为了方便大家使用MM32 MCUCDC功能,我们已经封装好全部代码,用户不需要自己配置以上的那些描述符等参数,只需要知道如何处理CDC的数据接收和发送即可。

软件资源如下:

函数初始化配置及相关全局变量定义内容,参考之前文章代码即可,在此不作过多赘述。

对于MM32 MCUCDC功能来说,在使用CDC功能之前先调用USB初始化函数来初始化USB协议栈。

int main(void)
{
usbd_init();  // USB Device Initialization and connect
usbd_connect(__TRUE);
while (!usbd_configured())   // Wait for USB Device to configure
{
}
while (1)
{
}
}

然后就是CDC数据收发处理函数,USB数据处理函数如下:

int main(void)
{
usbd_init();  // USB Device Initialization and connect
usbd_connect(__TRUE);
while (!usbd_configured())   // Wait for USB Device to configure
{
}
while (1)
{
}
}

然后就是CDC数据收发处理函数,USB数据处理函数如下:

// For usart
#define  CDC_UART UART1
#define CDC_UART_ENABLE()       RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE)
#define CDC_UART_DISABLE()           RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, DISABLE)
#define CDC_UART_IRQn                UART1_IRQn
#define CDC_UART_IRQn_Handler        UART1_IRQHandler
#define UART_PINS_PORT_ENABLE()      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE)
#define UART_PINS_PORT_DISABLE()     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , DISABLE)
#define UART_TX_PORT                GPIOA
#define UART_TX_PIN                  GPIO_Pin_9
#define UART_TX_PIN_SOURCE        GPIO_PinSource9
#define UART_RX_PORT                GPIOA
#define UART_RX_PIN                  GPIO_Pin_10
#define UART_RX_PIN_SOURCE        GPIO_PinSource10
#define USART_BUFFER_SIZE  (256)
static struct {
volatile uint16_t     idx_in;
volatile uint16_t     idx_out;
volatile int16_t       cnt_in;
volatile int16_t       cnt_out;
uint8_t    data[USART_BUFFER_SIZE];
} WrBuffer, RdBuffer;
/* Control Lines */
#define UART_CONTROL_LINE_RTS_Pos   0   /* request to send  control line      */
#define UART_CONTROL_LINE_RTS_Msk  (1 << UART_CONTROL_LINE_RTS_Pos)
#define UART_CONTROL_LINE_DTR_Pos   0   /* request to send  control line      */
#define UART_CONTROL_LINE_DTR_Msk  (1 << UART_CONTROL_LINE_DTR_Pos)
/* Status Lines */
#define UART_STATUS_LINE_CTS_Pos    0   /* clear to send control line         */
#define UART_STATUS_LINE_CTS_Msk   (1 << UART_STATUS_LINE_CTS_Pos)
#define UART_STATUS_LINE_DCD_Pos    1   /* data carrier detect                */
#define UART_STATUS_LINE_DCD_Msk   (1 << UART_STATUS_LINE_DCD_Pos)
#define UART_STATUS_LINE_DSR_Pos    2   /* data set ready                     */
#define UART_STATUS_LINE_DSR_Msk   (1 << UART_STATUS_LINE_DSR_Pos)
#define UART_STATUS_LINE_RI_Pos     3   /* ring indicator                     */
#define UART_STATUS_LINE_RI_Msk    (1 << UART_STATUS_LINE_RI_Pos)
/* Communication Errors */
#define UART_FRAMING_ERROR_Pos      0
#define UART_FRAMING_ERROR_Msk     (1 << UART_FRAMING_ERROR_Pos)
#define UART_PARITY_ERROR_Pos       1
#define UART_PARITY_ERROR_Msk      (1 << UART_PARITY_ERROR_Pos)
#define UART_OVERRUN_ERROR_Pos      2
#define UART_OVERRUN_ERROR_Msk     (1 << UART_OVERRUN_ERROR_Pos)
static uint32_t StatusRegister;
static uint32_t BreakFlag;
extern uint8_t EP2ReceiveFlag;
extern uint8_t EP2TransferFlag;
extern uint8_t RxBufLen;
extern uint8_t TxBufLen;
extern uint8_t usb_buf_busy_flag;
extern uint8_t EP2RXBuff[64];
extern uint8_t EP2TXBuff[1024];
#define RX_OVRF_MSG         "\n"
#define RX_OVRF_MSG_SIZE    (sizeof(RX_OVRF_MSG) - 1)
#define  BUFFER_SIZE (512)
circ_buf_t write_buffer;
uint8_t write_buffer_data[BUFFER_SIZE];
static uint32_t tx_in_progress = 0;
circ_buf_t read_buffer;
uint8_t read_buffer_data[BUFFER_SIZE];
static UART_Configuration configuration = {
.Baudrate = 9600,
.DataBits = UART_DATA_BITS_8,
.Parity = UART_PARITY_NONE,
.StopBits = UART_STOP_BITS_1,
.FlowControl = UART_FLOW_CONTROL_NONE,
};
extern uint32_t SystemCoreClock;
void CDC_UART_CallBack(void);
static void clear_buffers(void)
{
circ_buf_init(&write_buffer, write_buffer_data, sizeof(write_buffer_data));
circ_buf_init(&read_buffer, read_buffer_data, sizeof(read_buffer_data));
}
 
int32_t uart_initialize(void)
{
uint16_t data_bits;
uint16_t parity;
uint16_t stop_bits;
GPIO_InitTypeDef GPIO_InitStructure;
UART_InitTypeDef UART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
UART_ITConfig(CDC_UART, UART_IT_RXIEN|UART_IT_TXIEN, DISABLE);
clear_buffers();
CDC_UART_ENABLE();
UART_PINS_PORT_ENABLE();
//TX pin
GPIO_InitStructure.GPIO_Pin = UART_TX_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(UART_TX_PORT, &GPIO_InitStructure);
//RX pin
GPIO_InitStructure.GPIO_Pin = UART_RX_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(UART_RX_PORT, &GPIO_InitStructure);
//Only 8 bit support
data_bits = UART_WordLength_8b;
// parity
if(configuration.Parity == UART_PARITY_ODD)
parity = UART_Parity_Odd;
else if(configuration.Parity == UART_PARITY_EVEN)
parity = UART_Parity_Even;
else
parity = UART_Parity_No;
// stop bits
if(configuration.StopBits == UART_STOP_BITS_2)
stop_bits = UART_StopBits_2;
else if(configuration.StopBits == UART_STOP_BITS_1)
stop_bits = UART_StopBits_1;
else
stop_bits = UART_StopBits_1;
//
UART_InitStructure.UART_BaudRate = configuration.Baudrate;
UART_InitStructure.UART_WordLength = data_bits;
UART_InitStructure.UART_StopBits = stop_bits;
UART_InitStructure.UART_Parity = parity;
UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;
UART_InitStructure.UART_Mode = UART_Mode_Rx | UART_Mode_Tx;
UART_Init(CDC_UART, &UART_InitStructure);
//
NVIC_InitStructure.NVIC_IRQChannel = CDC_UART_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// Enable RX interrupt
UART_ITConfig(CDC_UART, UART_IT_RXIEN, ENABLE);
// Initially disable TxEmpty Interrupt
UART_ITConfig(CDC_UART, UART_IT_TXIEN, DISABLE);
UART_ITConfig(CDC_UART, UART_OVER_ERR|UART_IT_ERR|UART_IT_PE|0x0040, ENABLE);
NVIC_ClearPendingIRQ(CDC_UART_IRQn);
UART_Cmd(CDC_UART, ENABLE);
return 1;
}
int32_t uart_uninitialize(void)
{
UART_Cmd(CDC_UART, DISABLE);
UART_ITConfig(CDC_UART, UART_IT_RXIEN|UART_IT_TXIEN, DISABLE);
clear_buffers();
return 1;
}
int32_t uart_reset(void)
{
uart_initialize();
tx_in_progress = 0;
clear_buffers();
return 1;
}

int32_t uart_set_configuration(UART_Configuration *config)
{
uint16_t data_bits;
uint16_t parity;
uint16_t stop_bits;
UART_InitTypeDef UART_InitStructure;
// Disable uart and tx/rx interrupter
UART_Cmd(CDC_UART, DISABLE);
UART_ITConfig(CDC_UART, UART_IT_RXIEN|UART_IT_TXIEN, DISABLE);
clear_buffers();
//Only 8 bit support
data_bits = UART_WordLength_8b;
configuration.DataBits = UART_DATA_BITS_8;
// parity
configuration.Parity = config->Parity;
if(config->Parity == UART_PARITY_ODD)
parity = UART_Parity_Odd;
else if(config->Parity == UART_PARITY_EVEN)
parity = UART_Parity_Even;
else if(config->Parity == UART_PARITY_NONE)
parity = UART_Parity_No;
else {   //Other not support
parity = UART_Parity_No;
configuration.Parity = UART_PARITY_NONE;
}
// stop bits
configuration.StopBits = config->StopBits;
if(config->StopBits == UART_STOP_BITS_2)
stop_bits = UART_StopBits_2;
else if(config->StopBits == UART_STOP_BITS_1)
stop_bits = UART_StopBits_1;
else {
stop_bits = UART_StopBits_1;
configuration.StopBits = UART_STOP_BITS_1;
}
configuration.Baudrate = config->Baudrate;
configuration.FlowControl = UART_FLOW_CONTROL_NONE;
//
UART_InitStructure.UART_BaudRate = config->Baudrate;
UART_InitStructure.UART_WordLength = data_bits;
UART_InitStructure.UART_StopBits = stop_bits;
UART_InitStructure.UART_Parity = parity;
UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;
UART_InitStructure.UART_Mode = UART_Mode_Rx | UART_Mode_Tx;
UART_Init(CDC_UART, &UART_InitStructure);
// Enable RX interrupt
UART_ITConfig(CDC_UART, UART_IT_RXIEN, ENABLE);
// Initially disable TxEmpty Interrupt
UART_ITConfig(CDC_UART, UART_IT_TXIEN, DISABLE);
UART_Cmd(CDC_UART, ENABLE);
return 1;
}
int32_t uart_get_configuration(UART_Configuration *config)
{
config->Baudrate = configuration.Baudrate;
config->DataBits = configuration.DataBits;
config->Parity   = configuration.Parity;
config->StopBits = configuration.StopBits;
config->FlowControl = UART_FLOW_CONTROL_NONE;
return 1;
}
int32_t uart_write_free(void)
{
return circ_buf_count_free(&write_buffer);
}
int32_t uart_write_data(uint8_t *data, uint16_t size)
{
uint32_t cnt;
cnt = circ_buf_write(&write_buffer, data, size);
// Atomically enable TX
if(!tx_in_progress)
{
// Wait for tx is free
//while(USART_GetITStatus(CDC_UART, UART_IT_TXIEN) == RESET);
tx_in_progress = 1;
UART_SendData(CDC_UART, circ_buf_pop(&write_buffer));
// Enale tx interrupt
UART_ITConfig(CDC_UART, UART_IT_TXIEN, ENABLE);
}
return cnt;
}
int32_t uart_read_data(uint8_t *data, uint16_t size)
{
return circ_buf_read(&read_buffer, data, size);
}
void uart_enable_flow_control(bool enabled)
{
// Flow control not implemented for this platform
}
void CDC_UART_IRQn_Handler(void)
{
CDC_UART_CallBack();
}
int32_t UART_GetCommunicationErrorStatus (void)
{
int32_t err = 0;
if (StatusRegister & UART_IT_PE)
err |= UART_PARITY_ERROR_Msk;
if (StatusRegister & UART_OVER_ERR)
err |= UART_OVERRUN_ERROR_Msk;
if (BreakFlag == 0 && (StatusRegister & UART_IT_ERR))
err |= UART_PARITY_ERROR_Msk;
return (err);
}
int32_t UART_GetStatusLineState (void)
{
return (0);
}
int32_t UART_GetBreak (void)
{
return (BreakFlag);
}
/* Check if status has changed and if so, send notify to USB Host on Int EP   */
void NotifyOnStatusChange (void)
{
static int32_t old_notify = -1;
int32_t status, notify = 0;
status = UART_GetCommunicationErrorStatus();
if (status & UART_OVERRUN_ERROR_Msk)
notify |= CDC_SERIAL_STATE_OVERRUN;
if (status & UART_PARITY_ERROR_Msk )
notify |= CDC_SERIAL_STATE_OVERRUN;
if (status & UART_FRAMING_ERROR_Msk)
notify |= CDC_SERIAL_STATE_FRAMING;
status     = UART_GetStatusLineState();
if (status & UART_STATUS_LINE_RI_Msk )
notify |= CDC_SERIAL_STATE_RING;
if (status & UART_STATUS_LINE_DSR_Msk)
notify |= CDC_SERIAL_STATE_TX_CARRIER;
if (status & UART_STATUS_LINE_DCD_Msk)
notify |= CDC_SERIAL_STATE_RX_CARRIER;
if (UART_GetBreak())
notify |= CDC_SERIAL_STATE_BREAK;
#ifdef CDC_ENDPOINT
if (notify ^ old_notify)                          // If notify changed
{
if (USBD_CDC_ACM_Notify (notify))   // Send new notification
old_notify = notify;
}
#endif
}
int32_t UART_ReadData (uint8_t *data, uint16_t size)
{
int32_t cnt = 0;
while (size != 0)
{
--size;
if (RdBuffer.cnt_in != RdBuffer.cnt_out)
{
*data++ = RdBuffer.data[RdBuffer.idx_out++];
RdBuffer.idx_out &= (USART_BUFFER_SIZE - 1);
RdBuffer.cnt_out++;
cnt++;
}
}
return (cnt);
}
void Uart_Put_Char (char ch)
{
while((CDC_UART->CSR&UART_IT_TXIEN)==0);
CDC_UART->TDR = (ch & (uint16_t)0x00FF);
}
void Uart_PutBuff (uint8_t *buff, uint32_t len)
{
while(len--)
{
Uart_Put_Char(*buff);
buff++;
}
}
void  USBD_CDC_TASK(void)
{
uint8_t i,count;
NotifyOnStatusChange();
if(CDC_UART ->ISR &0x08)
{
CDC_UART ->GCR &= ~(3 << 3);
CDC_UART ->GCR = 3 << 3;
UART_ClearITPendingBit(CDC_UART,UART_OVER_ERR);
}
// USB -> UART
if(EP2ReceiveFlag == 1)
{
EP2ReceiveFlag = 0;
Uart_PutBuff(EP2RXBuff,RxBufLen);
}
// UART -> USB
if(EP2TransferFlag == 1)
{
if(TxBufLen > 0)
{
while(USB->rEP2_CTRL & 0x80);
if(TxBufLen > 64)
{
UART_ReadData(EP2TXBuff,64);
count = 64;
TxBufLen -= 64;
}
else
{
UART_ReadData(EP2TXBuff,TxBufLen);
count = TxBufLen;
TxBufLen = 0;
}
usb_buf_busy_flag = 1;
for(i = 0;i < count;i++)
{
USB->rEP2_FIFO = *(EP2TXBuff + i);
}
if((USB ->rEP2_AVIL&0x3f) == count)
{
USB->rEP2_CTRL = 0x80|count;
}
else
{
USB->rTOP |= 1<<3;
USB->rTOP &= ~(1<<3);
}
USB->rEP2_CTRL = 0x80|count;
if(0 == TxBufLen)
EP2TransferFlag = 0;
}
}
}
void CDC_UART_CallBack(void)
{
uint8_t  ch;
int16_t  len_in_buf;
StatusRegister = CDC_UART->ISR;
if(StatusRegister&0x78)
{
CDC_UART->ICR = 0xFF;
CDC_UART->ICR;
GPIOA ->ODR ^= 1;
CDC_UART->GCR &= 0xFFE7;
CDC_UART->GCR |= 0x18;
}
if (UART_GetITStatus(CDC_UART, UART_IT_RXIEN) != RESET)
{
UART_ClearITPendingBit(CDC_UART,UART_IT_RXIEN);
len_in_buf = RdBuffer.cnt_in - RdBuffer.cnt_out;
if (len_in_buf < USART_BUFFER_SIZE)
{
ch = (uint8_t)UART_ReceiveData(CDC_UART);
TxBufLen ++;
RdBuffer.data[RdBuffer.idx_in++] = ch;
if ((ch == 0)&&      UART_GetFlagStatus(CDC_UART, UART_CSR_RXAVL))
{
BreakFlag = 1;
}
else
{
BreakFlag = 0;
}
RdBuffer.idx_in &= (USART_BUFFER_SIZE - 1);
RdBuffer.cnt_in++;
}
else
{
RdBuffer.cnt_in =0;
RdBuffer.cnt_out=0;
UART_ReceiveData(CDC_UART);
}
}
}

如上,我们就完成MM32 MCU的CDC功能,将程序下载到MCU中,插上USB线,然后在电脑的设备管理器的端口栏就可以找到对应的USB CDC枚举模拟串口设备。
1.jpg
图1  PC设备管理器列表

用串口助手打开虚拟串口,TX接RX测试发送数据,结果如下:2.jpg
图2  串口收发数据通信

以上就是MM32 MCU USBCDC功能,下一节我们介绍MM32 MCU USBMSC功能。

推荐阅读
关注数
6100
内容数
272
灵动MM32 MCU相关技术知识,欢迎关注~
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息