我总是班门弄斧,前两天还在问虚拟串口的问题,今天初步鼓捣出来了,就急忙写了稿件,与大家分享。
由于现在的PC机大多都没有串口了,但PC机上的很多应用程序却使用串口,为了让PC机与STM32处理器进行通信,可以让STM32处理器,以串行方式与外界进行通信,再用CH340等芯片,实现USB转串口的功能,实现虚拟串口。不过这样做需要额外的芯片和相关电路,不很理想。 利用STM32处理器自身的USB功能,就可以实现虚拟串口。由于USB是一个很复杂的东西,所对应的代码很多,如果采用操作寄存器或标准库的方式,都要编写很多代码,也容易出错,而采用STM32CUBE就方便多了。 首先要利用STM32CUBE,选择芯片,比如芯片型号为STM32F103ZET6,如下图所示: file:///C:\Users\lvy\AppData\Local\Temp\ksohtml\wpsA2D8.tmp.jpg 然后在Pinout选项页中,先选择“RCC”项,按下图设置: file:///C:\Users\lvy\AppData\Local\Temp\ksohtml\wpsA2E9.tmp.jpg 再选择“USB”选项,按下图设置: file:///C:\Users\lvy\AppData\Local\Temp\ksohtml\wpsA2EA.tmp.jpg 这一步的最后再选择“USB_DEBICE”,按下图设置: file:///C:\Users\lvy\AppData\Local\Temp\ksohtml\wpsA2FA.tmp.jpg 下面要设置时钟,进入“Clock Configuration”选项页,按下图设置: file:///C:\Users\lvy\AppData\Local\Temp\ksohtml\wpsA2FB.tmp.jpg 请注意:上图的设置必须保障给USB模块的时钟信号是精准的48MHz,不允许有偏差。 然后进行工程设置,点击菜单项“Porject→Settings...”,这时将弹出一个对话框窗口如下: file:///C:\Users\lvy\AppData\Local\Temp\ksohtml\wpsA31B.tmp.jpg 在该对话框窗口上,输入工程名、工程存放的文件夹、所用的开发工具之后,点击“OK”按键,关闭该对话框。 最后点击菜单项“Porject→Generate Code”,生成工程。本人用的开发工具是IAR,打开该工程,如下图所示: file:///C:\Users\lvy\AppData\Local\Temp\ksohtml\wpsA32C.tmp.jpg 这个工程已经包含了不少文件,建立了基本的程序框架和初始化代码,只要进一步添加应用程序代码就可以了。下面将围绕虚拟串口进一步编写程序: 在这个工程中,有一个“usbd_cdc_if.c”文件,进行USB虚拟串口进行数据的发送和接收时,首先要在usbd_cdc_if.c文件中修改“APP_RX_DATA_SIZE”、“APP_TX_DATA_SIZE”两个宏,它们用于数据的接收和发送缓冲区的大小;然后要声明一个结构体(并初始化),其代码如下: USBD_CDC_LineCodingTypeDef LineCoding = { 115200, /*baud rate*/ 0x00, /*stop bits - 1*/ 0x00, /*parity - none*/ 0x08, /*nb. of bits 8*/ }; 这段代码用于指示串口的基本设置。 在该文件的“CDC_Control_FS”函数中,在“CDC_SET_LINE_CODING:”下面,添加如下代码: 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; 这样之后,就可以使用虚拟串口了! 当需要发送数据时,先将要发送的数据放入一个数组中(假设该数组名为a,要发送的n个字节),然后用如下代码就可以发送数据: CDC_Transmit_FS(a, n); 如果还需要读取上位机传来的数据,要修改usbd_cdc_if.c文件中的CDC_Receive_FS函数,该函数原本为: static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len) { /* USER CODE BEGIN 6 */ USBD_CDC_SetRxBuffer(hUsbDevice_0, &Buf[0]); USBD_CDC_ReceivePacket(hUsbDevice_0); return (USBD_OK); /* USER CODE END 6 */ } 该函数在收到数据时,将被系统调用,它的两个参数分别指向接收缓冲区和接收到的数据字节长度。显然我们可以编写一段代码,将接收到的数据送到应用程序指定的内存区,比如进行如下修改: static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len) { /* USER CODE BEGIN 6 */ uint8_t i; USBD_CDC_SetRxBuffer(hUsbDevice_0, &Buf[0]); USBD_CDC_ReceivePacket(hUsbDevice_0); for(i = 0; i < *Len; i++) app_rx_buf = Buf;//将收到的数据转移到app_rx_buf数组中 rx_f = TRUE;//将收到数据标志位置位,用以通知应用程序 return (USBD_OK); /* USER CODE END 6 */ } 这样就可以进行数据的上传和下传了。
|