雷达智富

首页 > 内容 > 程序笔记 > 正文

程序笔记

使用STM32 USB转串口功能(自发自收回环测试、USB转TTL串口)

2024-06-20 117

前言

STM32单片机一般都有USB接口,用于实现各种类型的USB通信,典型的USB转串口功能,接下来就介绍一下,USB转串口的使用,先以自发自收回环测试为例,演示USB转串口的使用,然后以此为基础,修改为USB转TTL串口,其中主要代码使用STM32CubeMX生成。

开发环境

  • STM32F103RB
  • STM32CubeMX
  • STM32CUBEIDE

STM32CubeMx配置

新建工程

在STM32CubeMx中新建基于STM32F103RB的Project。

配置时钟

时钟的配置如下图所示:
image-1681703662077

开启USB

  1. 首先,使能USB Device,如下图:
    image-1681703684594

  2. 开启USB设备:虚拟串口,如下图:
    image-1681703703459

  3. 生成工程
    image-1681703743540
    image-1681703779221

修改代码实现回环收发数据测试

在​​usbd_cdc_if.c​​文件中新定义一个结构体:

USBD_CDC_LineCodingTypeDef USBD_CDC_LineCoding =
{
  115200,      // 默认波特率
  0X00,        // 1位停止位
  0X00,        // 无奇偶校
  0X08,        // 无流控,8bit数据位
};

找到​​CDC_Control_FS​​​函数,找到​​CDC_SET_LINE_CODING​​​和​​CDC_GET_LINE_CODING​​​分支,添加以下代码:
(CDC_SET_LINE_CODING:你在用串口助手选择波特率时候,STM32就会调用这个分支进行修改USB波特率)
(CDC_GET_LINE_CODING:获取STM32的USB波特率)

case CDC_SET_LINE_CODING:
        USBD_CDC_LineCoding.bitrate = (pbuf[3]  24) | (pbuf[2]  16) | (pbuf[1]  8) | pbuf[0];
        USBD_CDC_LineCoding.format = pbuf[4];
        USBD_CDC_LineCoding.paritytype = pbuf[5];
        USBD_CDC_LineCoding.datatype = pbuf[6];
    break;

    case CDC_GET_LINE_CODING:
        pbuf[0] = (uint8_t)(USBD_CDC_LineCoding.bitrate);
        pbuf[1] = (uint8_t)(USBD_CDC_LineCoding.bitrate  8);
        pbuf[2] = (uint8_t)(USBD_CDC_LineCoding.bitrate  16);
        pbuf[3] = (uint8_t)(USBD_CDC_LineCoding.bitrate  24);
        pbuf[4] = USBD_CDC_LineCoding.format;
        pbuf[5] = USBD_CDC_LineCoding.paritytype;
        pbuf[6] = USBD_CDC_LineCoding.datatype;
    break;

如下图所示:
image-1681703902673
找到​​CDC_Receive_FS​​​函数,这个函数如果USB虚拟串口数据收到就会被调用,我们在这个函数中将收到的数据在发回去,只需要添加​​CDC_Transmit_FS(Buf, *Len);​​这一句即可,如下图:
image-1681703924076
然后编译工程并下载,接上USB之后,设备管理器COM出现一个新的端口:
image-1681703946483
我们使用串口调试助手给它发数据:
image-1681704018503

实现USB转串口功能

发数据流程:串口调试助手发送数据-STM32的USB数据接收-STM32转发到串口3
收数据流程:STM32的串口3收到数据-转发到USB-STM32的USB发送到串口调试助手

第一步先在这里加入串口3的初始化操作:
image-1681704054068
贴一下串口3的初始化代码,这里我用到了队列,因为实际测试发现串口3接收数据量比较大的话,那么转发到USB虚拟串口的时候会丢数据,所以这里采用了缓存队列,当串口接收到的数据到达一定数据量之后才做一次转发到USB的操作,并且开启了空闲中断,作用是转发最后一包数据:
关于队列的使用可以查看我的另一篇博客:​​C/C++语言实现的一个缓存队列​​

/*
 * myusart.c
 *
 *  Created on: Mar 22, 2021
 *      Author: hello
 */
#include myusart.h
#include myqueue.h

// 队列大小,定义为USB_CDC的发送包大小
#define QUEUE_SIZE APP_TX_DATA_SIZE

// 队列数据空间。请使用宏QALIGN4,目的是为了根据队列大小计算实际需要的队列存储空间大小并对齐4字节
uint8_t QueueBuffer[QALIGN4(QUEUE_SIZE)];

// 队列句柄
Queue Uart3QueueHandle = {0};
#define UART3_QUEUE_Handle (Uart3QueueHandle)

// 串口转发到USB的缓冲,定义为USB包的一半大小
static uint8_t buffer[APP_RX_DATA_SIZE  1];

void UART3_Init(const USBD_CDC_LineCodingTypeDef *USBD_CDC_LineCoding)
{
  __HAL_UART_DISABLE_IT(huart3, UART_IT_RXNE);

  __HAL_UART_DISABLE_IT(huart3, UART_IT_IDLE);

  HAL_UART_DeInit(huart3);

  huart3.Instance = USART3;
  huart3.Init.BaudRate = USBD_CDC_LineCoding-bitrate;
  huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart3.Init.Mode = UART_MODE_TX_RX;
  huart3.Init.OverSampling = UART_OVERSAMPLING_16;

  switch (USBD_CDC_LineCoding-paritytype)
  {
  case 0:
    huart3.Init.Parity = UART_PARITY_NONE;
    break;
  case 1:
    huart3.Init.Parity = UART_PARITY_ODD;
    break;
  case 2:
    huart3.Init.Parity = UART_PARITY_EVEN;
    break;
  default:
    huart3.Init.Parity = UART_PARITY_NONE;
    break;
  }

  switch (USBD_CDC_LineCoding-datatype)
  {
  case 0x07:
    huart3.Init.WordLength = UART_WORDLENGTH_8B;
    break;
  case 0x08:
    if (huart3.Init.Parity == UART_PARITY_NONE)
    {
      huart3.Init.WordLength = UART_WORDLENGTH_8B;
    }
    else
    {
      huart3.Init.WordLength = UART_WORDLENGTH_9B;
    }
    break;
  default:
    huart3.Init.WordLength = UART_WORDLENGTH_8B;
    break;
  }

  switch (USBD_CDC_LineCoding-format)
  {
  case 0:
    huart3.Init.StopBits = UART_STOPBITS_1;
    break;
  case 2:
    huart3.Init.StopBits = UART_STOPBITS_2;
    break;
  default:
    huart3.Init.StopBits = UART_STOPBITS_1;
    break;
  }

  HAL_UART_Init(huart3);

  Queue_Init(UART3_QUEUE_Handle, QUEUE_SIZE, QueueBuffer);

  __HAL_UART_ENABLE_IT(huart3, UART_IT_RXNE);

  __HAL_UART_ENABLE_IT(huart3, UART_IT_IDLE);
}

void USART3_IRQHandler(void)
{
  static uint32_t nsent = 0;
  static uint8_t dat = 0;

  if (__HAL_UART_GET_FLAG(huart3, UART_FLAG_RXNE) != RESET)
  {
    __HAL_UART_CLEAR_FLAG(huart3, UART_FLAG_RXNE);
    dat = huart3.Instance-DR  0XFF;
    Queue_PutByte(UART3_QUEUE_Handle, dat);                     // 入队一个字节的数据
    if (Queue_GetUsed(UART3_QUEUE_Handle) = sizeof(buffer))
    {
      Queue_Read(UART3_QUEUE_Handle, buffer, sizeof(buffer));
      CDC_Transmit_FS(buffer, sizeof(buffer));                // 转发到USB
    }
  }

  if (__HAL_UART_GET_FLAG(huart3, UART_FLAG_IDLE) != RESET)
  {
    nsent = Queue_GetUsed(UART3_QUEUE_Handle);
    if (nsent != 0)
    {
      Queue_Read(UART3_QUEUE_Handle, buffer, nsent);
      CDC_Transmit_FS(buffer, nsent);               // 转发到USB
    }
    __HAL_UART_CLEAR_IDLEFLAG(huart3);
  }
}

void UART3_SendData(const void *buf, uint32_t len)
{
  const uint8_t *p = (const uint8_t*) buf;
  while (len--)
  {
    while (__HAL_UART_GET_FLAG(huart3, UART_FLAG_TXE) != SET);
    huart3.Instance-DR = (uint8_t) (*p++  0XFF);
    while (__HAL_UART_GET_FLAG(huart3, UART_FLAG_TC) != SET);
  }
}

//#ifdef __GNUC__
//#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
//#else
//#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
//#endif
//PUTCHAR_PROTOTYPE
//{
//  while (__HAL_UART_GET_FLAG(huart3, UART_FLAG_TXE) != SET);
//  huart3.Instance-DR = (uint8_t) (ch  0XFF);
//  while (__HAL_UART_GET_FLAG(huart3, UART_FLAG_TC) != SET);
//  return ch;
//}

然后在添加USB转发到串口的操作:
image-1681704166590
这样就实现了一个USB转TTL串口的功能!

原文链接(转载有改动,感谢原作者):https://blog.51cto.com/u_15950551/6032374

更新于:5个月前
赞一波!2

文章评论

评论问答