虚拟串口CDC

在前面的章节中我们介绍了MM32 USB各种功能类型,也介绍了如何通过串口或者J-Link RTT方式实现shell辅助调试方式,但是其都需要依赖额外的工具,比如串口方式就需要USB转TTL,J-Link RTT需要使用J-Link下载器,所以希望有新的方法实现shell,本次我们介绍USB CDC的方式来实现shell功能。

本次我们采用MM32L373 MiniBoard作为测试开发板,验证USB CDC的方式来实现shell功能。

前面已经为大家讲解了shell的串口方法,其实原理一样,只是用MM32 USB枚举成串口设备替代USB转TTL,直接从USB获取数据到MCU,也不需要额外占用MCU的串口,节省资源和硬件,相关的代码都可以从之前的文章获取,本次只是融合两者,改变实现接口,具体代码参考如下:

对于CDC部分,其函数初始化配置及相关全局变量定义内容,代码如下:

#define USBD_POWER 0

#define USBD_MAX_PACKET0 64

#define USBD_DEVDESC_IDVENDOR 0x2F81 //0x0D28

#define USBD_DEVDESC_IDPRODUCT 0x0001 //0x0204

以上是定义的MM32 MCU CDC设备VID和PID,灵动微电子已经获得USB组织授权的VID和PID。当设备插入电脑上,可以查看到如上标识的CDC设备,如图1所示:

图1 PC设备管理器列表

对于MM32 MCU的CDC功能来说,

在使用CDC功能之前先调用USB初始化函数来初始化USB协议栈。

int main(void)

{

// USB Device Initialization and connect

usbd_init();

usbd_connect(__TRUE);

while (!usbd_configured()) // Wait for USB Device to configure
{
}
while (1)
{
}
}

对于shell部分其函数初始化配置及相关全局变量定义内容,代码如下:

typedef struct

{

char *command; // shell命令提示符

char buffer[SHELL_COMMAND_MAX_LENGTH]; // shell命令缓冲buffer

unsigned short length; // shell命令长度大小

unsigned short cursor; // shell光标位置偏移

char *param[SHELL_PARAMETER_MAX_NUMBER]; // shell参数变量

char history[SHELL_HISTORY_MAX_NUMBER][SHELL_COMMAND_MAX_LENGTH]; // 历史记录区域

unsigned short historyCount; // 历史记录数量

short historyFlag; // 当前记录偏移位置

short historyOffset; // 历史记录偏移大小

SHELL_CommandTypeDef *commandBase; // 命令表基地址

unsigned short commandNumber; // 命令数量

int keyFuncBase; // 按键响应表基地址

unsigned short keyFuncNumber; // 按键响应数量

SHELL_InputMode status; // shell输入状态

unsigned char isActive; //是不是当前激活的shell

shellRead read; // shell读函数接口

shellWrite write; // shell写函数接口

}SHELL_TypeDef;

如上所示,为对象的定义接口,移植的步骤先定义一个shell对象,即:SHELL_TypeDef cdc_shell,然后实例化对象的操作接口,具体说明看注释,对于其中我们需要关注的是shell的读写接口。由于本次我们使用USB CDC接收和发送数据,所以我们只需要在USB CDC的函数中处理接收到的数据即可,我们使用shellHandler(&cdc_shell, EP2RXBuff[i]);来处理数据的交互,具体函数代码参考串口shell代码。

shell的发送接口,只需要把数据拷贝到buffer即可。

shell的读写接口移植到CDC上,代码如下:

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;

for (i = 0; i < RxBufLen; i++)

shellHandler(&cdc_shell, EP2RXBuff[i]);

}

// 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;

}

}

}

如上,我们就完成通过MM32 MCU的CDC实现shell调试功能,用串口助手打开虚拟串口,用CDC shell测试发送数据,结果如下:

图2 功能演示

以上就是MM32 MCU USB的CDC shell功能。

本文转自:灵动MM32MCU

围观 218

在前面的章节中我们介绍了MM32 USB各种功能类型,也介绍了如何通过串口或者J-Link RTT方式实现shell辅助调试方式,但是其都需要依赖额外的工具,比如串口方式就需要USB转TTL,J-Link RTT需要使用J-Link下载器,所以希望有新的方法实现shell,本次我们介绍USB CDC的方式来实现shell功能。

本次我们采用MM32L373 MiniBoard作为测试开发板,验证USB CDC的方式来实现shell功能。

前面已经为大家讲解了shell的串口方法,其实原理一样,只是用MM32 USB枚举成串口设备替代USB转TTL,直接从USB获取数据到MCU,也不需要额外占用MCU的串口,节省资源和硬件,相关的代码都可以从之前的文章获取,本次只是融合两者,改变实现接口,具体代码参考如下:

对于CDC部分,其函数初始化配置及相关全局变量定义内容,代码如下:

#define USBD_POWER                    0

#define USBD_MAX_PACKET0             64

#define USBD_DEVDESC_IDVENDOR      0x2F81 //0x0D28

#define USBD_DEVDESC_IDPRODUCT     0x0001 //0x0204

以上是定义的MM32 MCU CDC设备VID和PID,灵动微电子已经获得USB组织授权的VID和PID。当设备插入电脑上,可以查看到如上标识的CDC设备,如图1所示:

MM32 USB学习笔记——虚拟串口CDC SHELL调试
图1 PC设备管理器列表

对于MM32 MCU的CDC功能来说,

在使用CDC功能之前先调用USB初始化函数来初始化USB协议栈。

int main(void)

{

// USB Device Initialization and connect

usbd_init();

usbd_connect(__TRUE);

while (!usbd_configured())     // Wait for USB Device to configure

{

}

while (1)

{     

}

}

对于shell部分其函数初始化配置及相关全局变量定义内容,代码如下:

typedef struct

{

char *command; // shell命令提示符

char buffer[SHELL_COMMAND_MAX_LENGTH]; // shell命令缓冲buffer

unsigned short length; // shell命令长度大小

unsigned short cursor; // shell光标位置偏移

char *param[SHELL_PARAMETER_MAX_NUMBER]; // shell参数变量

char history[SHELL_HISTORY_MAX_NUMBER][SHELL_COMMAND_MAX_LENGTH]; // 历史记录区域

unsigned short historyCount; // 历史记录数量

short historyFlag; // 当前记录偏移位置

short historyOffset; // 历史记录偏移大小

SHELL_CommandTypeDef *commandBase; // 命令表基地址

unsigned short commandNumber; // 命令数量

int keyFuncBase; // 按键响应表基地址

unsigned short keyFuncNumber; // 按键响应数量

SHELL_InputMode status; // shell输入状态

unsigned char isActive; //是不是当前激活的shell

shellRead read; // shell读函数接口

shellWrite write; // shell写函数接口

}SHELL_TypeDef;

如上所示,为对象的定义接口,移植的步骤先定义一个shell对象,即:SHELL_TypeDef cdc_shell,然后实例化对象的操作接口,具体说明看注释,对于其中我们需要关注的是shell的读写接口。由于本次我们使用USB CDC接收和发送数据,所以我们只需要在USB CDC的函数中处理接收到的数据即可,我们使用shellHandler(&cdc_shell, EP2RXBuff[i]);来处理数据的交互,具体函数代码参考串口shell代码。

shell的发送接口,只需要把数据拷贝到buffer即可。

shell的读写接口移植到CDC上,代码如下:

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;
for (i = 0; i < RxBufLen; i++)
shellHandler(&cdc_shell, EP2RXBuff[i]);
}

// 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;
}
}
}

如上,我们就完成通过MM32 MCU的CDC实现shell调试功能,用串口助手打开虚拟串口,用CDC shell测试发送数据,结果如下:

MM32 USB学习笔记——虚拟串口CDC SHELL调试
图2 功能演示

以上就是MM32 MCU USB的CDC shell功能。

本文转自:灵动MM32MCU,转载此文目的在于传递更多信息,版权归原作者所有。

围观 124

在上一节我们介绍了MM32 MCU的WINUSB功能,对于WINUSB来说,还是需要开发对应的上位机来测试收发数据,相对来说较麻烦,如果仅做MCU与PC的数据通信功能,开发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 MCU的CDC功能,我们已经封装好全部代码,用户不需要自己配置以上的那些描述符等参数,只需要知道如何处理CDC的数据接收和发送即可。

软件资源如下:

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

对于MM32 MCU的CDC功能来说,在使用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数据处理函数如下:

// 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 PC设备管理器列表

用串口助手打开虚拟串口,TX接RX测试发送数据,结果如下:


图2 串口收发数据通信

以上就是MM32 MCU USB的CDC功能,下一节我们介绍MM32 MCU USB的MSC功能。

来源: 灵动MM32MCU

围观 623
订阅 RSS - 虚拟串口CDC