历史上的今天
返回首页

历史上的今天

今天是:2024年11月05日(星期二)

正在发生

2020年11月05日 | STM32 USB Virtual COM USB转串口的功能实现

2020-11-05 来源:eefocus

这次讲的是如何实现USB转串口功能的实现。首先看看工程的布局吧:

我们主要要介绍的文件的在USB_User这个组文件。从上面的截图可以看到USB_User这个文件由hw_config.c、usb_desc.c、usb_endp.c、usb_istr.c、usb_prop.c、usb_pwr.c几个文件组成。其中usb_istr.c和usb_pwr.c整两个文件不用修改,其他的文件都需要修改。下面接慢慢将来。


首先讲讲hw_config.c这个文件。由于我们用到串口,所以这个文件需要添加串口相关代码。在这个文件的开始就需要定义一下串口的相关变量:

uint8_t  USART_Rx_Buffer [USART_RX_DATA_SIZE]; //串口接收缓冲

uint32_t USART_Rx_ptr_in = 0; //这里采用的是一个环形缓冲,串口数据输入起始位置

uint32_t USART_Rx_ptr_out = 0; //环形缓冲的数据结束位置

uint32_t USART_Rx_length  = 0; //接收数据的长度

uint8_t  USB_Tx_State = 0; //USB发送标志,当串口缓冲有数据没有发送,该位置1


这里开了一个2K的环形缓冲如下图所示:

STM32 USB Virtual COM USB转串口的功能实现 - ziye334 - ziye334的博客其中 USART_Rx_ptr_in指向的就是图中read position处, USART_Rx_ptr_out指向write position处,  USART_Rx_length就是数据的长度,就是图中橙色的圆弧。当没有数据的时候, USART_Rx_ptr_in= USART_Rx_ptr_out,有数据收到的时候 USART_Rx_ptr_in就向后偏移,当数据被读出去的时候 USART_Rx_ptr_out也会向后偏移。

这里需要定义一个串口默认配置:波特率为9600,数据长度为8位,停止位为1位,奇校验,没有数据流控制,代码如下:

/*******************************************************************************

* Function Name  :  USART_Config_Default.

* Description    :  串口的默认配置值

* Input          :  None.

* Return         :  None.

*******************************************************************************/

void USART_Config_Default(void)

{

GPIO_InitTypeDef GPIO_InitStructure;


/* 使能 UART2 时钟 */

RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);


/* 配置 USART2 的Tx 引脚类型为推挽式的 */

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOA, &GPIO_InitStructure);


/* 配置 USART2 的Rx 为输入悬空 */

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;

    GPIO_Init(GPIOA, &GPIO_InitStructure);

/* 串口默认配置*/

/* 串口配置值如下:

    - 波特率 = 9600 baud  

    - 数据长度 = 8 Bits

    - 1位停止位

    - 奇校验

    - 不使能硬件流控制

    - 接收传输使能

*/

USART_InitStructure.USART_BaudRate = 9600; //波特率9600

USART_InitStructure.USART_WordLength = USART_WordLength_8b; //8位数据位

USART_InitStructure.USART_StopBits = USART_StopBits_1; //1位停止位

USART_InitStructure.USART_Parity = USART_Parity_Odd; //奇校验

USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//没有数据流控制

USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //接收、发送使能

USART_Init(USART2, &USART_InitStructure); //配置串口

USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); //使能串口接收中断

USART_Cmd(USART2,ENABLE); //串口使能

}


这里还要定义一个串口配置函数,这个函数会根据linecoding这个结构体来配置串口设置:

/*******************************************************************************

* Function Name  :  USART_Config.

* Description    :  根据line coding 结构体配置串口.

* Input          :  None.

* Return         :  配置状态

                    TRUE : 配置成功

                    FALSE: 配置中断

*******************************************************************************/

bool USART_Config(void)

{

  /*设置停止位*/

switch (linecoding.format)

{

case 0:

USART_InitStructure.USART_StopBits = USART_StopBits_1;   //1位停止位

break;

case 1:

USART_InitStructure.USART_StopBits = USART_StopBits_1_5;  //1.5为停止位

break;

case 2:

USART_InitStructure.USART_StopBits = USART_StopBits_2;   //2位停止位

break;

default :

{

USART_Config_Default(); //默认配置

return (FALSE);

}

}


/* 设置校验位*/

switch (linecoding.paritytype)

{

case 0:

USART_InitStructure.USART_Parity = USART_Parity_No; //没有校验

break;;

case 1:

USART_InitStructure.USART_Parity = USART_Parity_Even; //偶校验

break;

case 2:

USART_InitStructure.USART_Parity = USART_Parity_Odd; //奇校验

break;

default :

{

USART_Config_Default(); //默认配置

return (FALSE);

}

}


/*设置数据位: 8位或9位*/

switch (linecoding.datatype)

{

case 0x07:

USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8为数据位,这个选项就校验位必须设置(奇校验/偶校验)

break;

case 0x08:

if (USART_InitStructure.USART_Parity == USART_Parity_No)//没有设置校验位时

{

      USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位数据位

}

else 

{

      USART_InitStructure.USART_WordLength = USART_WordLength_9b;//9位数据位

}

break;

default :

{

USART_Config_Default(); //默认配置

return (FALSE);

}

}


USART_InitStructure.USART_BaudRate = linecoding.bitrate; //设置波特率

USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//设置没有硬件数据流控制

USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //使能接收、发送

USART_Init(USART2, &USART_InitStructure); //初始化串口

return (TRUE);

}


还要定义一个USB_To_USART_Send_Data()函数,将USB接收到的数据从串口发送数据:

/*******************************************************************************

* Function Name  : USB_To_USART_Send_Data.

* Description    : 将USB接收到的数据到URAT.

* Input          : data_buffer: data address.

                   Nb_bytes: number of bytes to send.

* Return         : none.

*******************************************************************************/

void USB_To_USART_Send_Data(uint8_t* data_buffer, uint8_t Nb_bytes)

{  

uint32_t i;

for (i = 0; i < Nb_bytes; i++) //串口发送数据

{

USART_SendData(USART2, *(data_buffer + i));

while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET); 

}  

}


接下去要定义Handle_USBAsynchXfer();函数,这个函数将串口收到的数据通过USB发送给PC机:

/*******************************************************************************

* Function Name  : Handle_USBAsynchXfer.

* Description    : 给USB发送数据

* Input          : None.

* Return         : none.

*******************************************************************************/

void Handle_USBAsynchXfer (void)

uint16_t USB_Tx_ptr;   //USB发送数据位置

uint16_t USB_Tx_length;

if(USB_Tx_State != 1)   //USB没有在发送

{

if (USART_Rx_ptr_out == USART_RX_DATA_SIZE)//环形接收缓冲输出位置到了缓冲区的最后

{

USART_Rx_ptr_out = 0; //设置接收缓冲位置为从0开始

}

if(USART_Rx_ptr_out == USART_Rx_ptr_in) //环形接收缓冲输出位置等于输入位置,说明USB数据发送完毕了

{

USB_Tx_State = 0; //设置USB发送标志位为0

return;

}

if(USART_Rx_ptr_out > USART_Rx_ptr_in) //环形接收缓冲输出位置大于输入位置,说明已经串口数如数据已经越过界了

USART_Rx_length = USART_RX_DATA_SIZE - USART_Rx_ptr_out;//计算缓冲数据长度

}

else 

{

USART_Rx_length= USART_Rx_ptr_in - USART_Rx_ptr_out; //计算串口缓冲的数据长度

}

if (USART_Rx_length > VIRTUAL_COM_PORT_DATA_SIZE)//如果数据的长度大于端点的最大长度

{

USB_Tx_ptr = USART_Rx_ptr_out; //USB发送数据起始位置为串口缓冲输出位置

USB_Tx_length=VIRTUAL_COM_PORT_DATA_SIZE;//USB的发送长度为 端点的最大传输长度

USART_Rx_ptr_out += VIRTUAL_COM_PORT_DATA_SIZE;//移动串口缓冲输出位置

USART_Rx_length -= VIRTUAL_COM_PORT_DATA_SIZE;//计算剩余数据长度

}

else //如果数据的长度小于端点的最大长度

{

USB_Tx_ptr = USART_Rx_ptr_out; //USB发送数据起始位置为串口缓冲输出位置

USB_Tx_length = USART_Rx_length;//USB发送数据长度为 串口缓冲数据的长度

USART_Rx_ptr_out += USART_Rx_length;//移动串口缓冲输出位置

USART_Rx_length = 0; //没有剩余的数据要发送了

}

USB_Tx_State = 1; //设置USB发送状态为正在发送

UserToPMABufferCopy(&USART_Rx_Buffer[USB_Tx_ptr], ENDP1_TXADDR, USB_Tx_length);//从缓冲数据区拷贝数据到端点发送数据

SetEPTxCount(ENDP1, USB_Tx_length); //设置端点1发送计数

SetEPTxValid(ENDP1); //使能端点1

}


这里还要定义一个函数用来调整串口接收到的串口环形缓冲的写入读出数据的位子 USART_To_USB_Send_Data() :

/*******************************************************************************

* Function Name  : UART_To_USB_Send_Data.

* Description    : 发送串口接收到的数据大USB

* Input          : None.

* Return         : none.

*******************************************************************************/

void USART_To_USB_Send_Data(void)

if (linecoding.datatype == 7)   //数据长度为7

{

USART_Rx_Buffer[USART_Rx_ptr_in] = USART_ReceiveData(USART2)&0x7F; //保存接收到的数据

}

else if (linecoding.datatype == 8)   //数据长度为8

{

USART_Rx_Buffer[USART_Rx_ptr_in] = USART_ReceiveData(USART2);//保存接收到的数据

}

USART_Rx_ptr_in++; //串口缓冲输入位置递增

/* To avoid buffer overflow */

if(USART_Rx_ptr_in == USART_RX_DATA_SIZE) //如果串口缓冲数据位置到达了缓冲的最后

{

USART_Rx_ptr_in = 0; //重新开始

}

}


接下去要将的是usb_desc.c真个文件。先讲讲设备描述符,设备描述符跟之前讲述的没有多少出入,只是厂商ID要独享为0483,否则上位机无法安装相应的驱动。

/* USB标准设备描述符*/

const uint8_t Virtual_Com_Port_DeviceDescriptor[VIRTUAL_COM_PORT_SIZ_DEVICE_DESC] =

{

    0x12,                       /*bLength:长度,设备描述符的长度为18字节*/

    USB_DEVICE_DESCRIPTOR_TYPE, /*bDescriptorType:类型,设备描述符的编号是0x01*/

    0x00,                       /*bcdUSB:所使用的USB版本为2.0*/

    0x02,

    0x02,                       /*bDeviceClass:设备所使用的类代码*/

    0x00,                       /*bDeviceSubClass:设备所使用的子类代码*/

    0x00,                       /*bDeviceProtocol:设备所使用的协议*/

    0x40,                       /*bMaxPacketSize:最大包长度为64字节*/

    0x83,                       /*idVendor:厂商ID为0x0483*/

    0x04,

    0x40,                       /*idProduct:产品ID为0x5740*/

    0x57,

    0x00,                       /*bcdDevice:设备的版本号为2.00*/

    0x02,

    1,                          /*iManufacturer:厂商字符串的索引*/

    2,                          /*iProduct:产品字符串的索引*/

推荐阅读

史海拾趣

ADATA公司的发展小趣事

ADATA科技成立于2001年,是一家专注于提供存储解决方案的公司,以下是该公司发展的五个相关故事:

  1. 公司成立与初期发展: ADATA科技成立于2001年,总部位于台湾新北市,最初致力于生产和销售DRAM模块。随着存储技术的不断发展,公司逐渐扩展了业务范围,涵盖了闪存产品、固态硬盘、移动存储设备等多个领域。

  2. 技术创新与产品推出: ADATA科技在存储领域进行了持续的技术创新,并推出了一系列具有竞争力的产品。公司不断提升产品性能、降低成本,并注重产品的设计和用户体验。除了传统的DRAM模块,公司还推出了闪存卡、固态硬盘、移动硬盘等产品,满足了不同客户和市场的需求。

  3. 市场拓展与国际化发展: ADATA科技积极拓展国内外市场,并逐步实现了国际化发展。公司产品远销全球各地,与全球范围内的主要零售商、电子产品制造商建立了合作关系。通过与合作伙伴的紧密合作,公司产品在国际市场上得到了广泛认可和好评。

  4. 品牌建设与市场影响力: ADATA科技通过持续的品牌建设活动,不断提升了在存储领域的市场影响力。公司参加各类行业展会、展示活动,并投入大量资源进行市场推广和宣传。同时,公司还与体育、文化等领域开展赞助活动,提升品牌知名度和美誉度。

  5. 未来展望与持续发展: 作为一家专注于存储解决方案的企业,ADATA科技将继续致力于技术创新和产品开发。公司将不断改进现有产品,推出更多性能更好、功能更丰富的存储产品,以满足不断变化的市场需求。同时,公司还将继续拓展国际市场,加强与合作伙伴的合作,实现业务的持续增长和发展。

功得(CONQUER)公司的发展小趣事

功得公司最初成立时,只是一家专注于电子元器件代理的小公司。创始人李明看准了电子行业快速发展的趋势,决定投身于这一领域。他带领团队深入市场调研,发现了一种新型的集成电路芯片在市场上有着巨大的潜力。于是,功得公司投入大量资金研发这种芯片,并通过不断改进和优化,最终成功推出了具有竞争力的产品。凭借这一创新产品,功得公司在市场上获得了初步的成功,为后续发展奠定了基础。

HELUKABEL公司的发展小趣事

在追求经济效益的同时,功得公司也积极履行社会责任。他们关注环保问题,采用环保材料和工艺生产产品;关注员工福利,为员工提供良好的工作环境和福利待遇;关注社会公益事业,积极参与各种慈善活动。这些举措使得功得公司在社会上树立了良好的形象,也为公司的长远发展提供了有力保障。

CHENMKO公司的发展小趣事

在市场竞争日益激烈的环境下,功得公司意识到仅仅依靠创新是不够的,还需要有高品质的产品来赢得客户的信任。因此,公司开始注重产品质量管理,建立了完善的质量控制体系。功得公司严格把控原材料采购、生产工艺和成品检验等环节,确保每一件产品都符合高标准的质量要求。这种对品质的执着追求,使得功得公司的产品在市场上赢得了良好的口碑,品牌知名度也逐渐提升。

Golledge Electronics公司的发展小趣事

在追求经济效益的同时,功得公司也积极履行社会责任。他们关注环保问题,采用环保材料和工艺生产产品;关注员工福利,为员工提供良好的工作环境和福利待遇;关注社会公益事业,积极参与各种慈善活动。这些举措使得功得公司在社会上树立了良好的形象,也为公司的长远发展提供了有力保障。

Crane Co.公司的发展小趣事

在1870年代,Crane Co.在制造业改善方面争当先驱。公司引入了由R. T. Crane发明的多用途机器,以及移动模具和浇铸金属的传送系统。这些创新技术不仅提高了生产效率,还标志着铸造领域流水线生产的开端。这一时期的变革为Crane Co.在电子行业的发展奠定了技术基础。

问答坊 | AI 解惑

Cadence MMsim v7.01 Linux 3CD模拟/数模混合电路加速仿真技术

Cadence MMsim v7.01 Linux 3CD模拟/数模混合电路加速仿真技术 MMsim v7.01软件下载 license 授权 模拟/数模混合电路加速仿真技术 需要的话,请联系QQ 3385251   tel:13017525669 对今天的混合信号SoC设计,往往包括模拟、射频、数字 ...…

查看全部问答>

C++学习的门厅---控制台应用程序

C++学习的门厅---控制台应用程序    //WIN程序开发的绚丽舞台,引无数手握空拳者纷至沓来,兴奋之余,竟然很容易忘记WIN开发的基础是C++:( //看中的VC++2005的也好,看中的BCB6.0也罢,均有可以编写、编译、测试的控制台应用程 ...…

查看全部问答>

USB单片DAC PCM2702及其应用

本帖最后由 dontium 于 2015-1-23 13:07 编辑 USB单片DAC PCM2702及其应用文/吴玥  编者按:PCM2702是由德州仪器推出的一款带有USB接口的16位立体声数模转换芯片,它可以为实现电脑音源的一系列外围设计提供帮助。《无线电》杂志特准备50片PCM270 ...…

查看全部问答>

瞬联软件招聘,内部推荐,高薪,上海职位。顶者有分。

瞬联软件是国内比较知名的软件外包公司(http://www.cienet.com.cn),总部在北京,在上海,杭州,成都均有分布,客户几乎包含了所有知名的通信厂商,比如诺基亚西门子网络(NSN),摩托罗拉,爱立信等。在程序员中有很好的口碑,提供的待遇和福利也 ...…

查看全部问答>

高分在线等:用ARM汇编指令如何访问I/O口数据寄存器

如题? 我这样,总是编不过 ...... LDR R0 GPGDAT ...... 该如何写呢? …

查看全部问答>

电源正负极之间是否短路的测量问题

请问各位大侠:     我用万用表的通断档测量电路板的正负极之间是否短路时,(电路板未加电时测量)     万用表通断档有时显示的是.548,用电阻档测量时阻值为1.5K;有时通断档显示为1.254,电阻档测得阻值为5.2K.   &nb ...…

查看全部问答>

Altium 6

有谁用过Altium 6的,整体性能怎么样?…

查看全部问答>

关于SWIM问题

各位大虾,我在用SWIM DEBUG 过程中,出现这样的问题 error:swim prog error[42004]:memory write error 怎么解决?????…

查看全部问答>

那个组长的传言是真的么?

本帖最后由 paulhyde 于 2014-9-15 09:00 编辑 那个组长的传言是真的么?。。。表示怀疑,,TI赞助呀,能不推荐DSP么  …

查看全部问答>

THS7001

本帖最后由 paulhyde 于 2014-9-15 09:01 编辑 THS7001应用设计  …

查看全部问答>