2025 Make Blocks阶段2 - 基于stm32f103cb实现USB转SPI/UART/并生成时钟信号
该项目使用了Kicad软件和stm32f103cb微控制器,实现了USB转换器的设计,它的主要功能为:USB转SPI/IIC/UART/485/SWD/CAN并生成时钟信号。
标签
USB
MakeBlocks
stm32f103cb
wswsr
更新2025-09-22
11
KiCad文件
全屏

任务介绍

本来设计是用来实现USB转SPI/UART/485/CAN/SWD并生成时钟信号,软件框架已经写好,但由于时间关系,只实现了USB转SPI/UART和驱动SI5351A时钟芯片生成时钟信号。

模块介绍

根据任务要求本次我选用的是STM32F103CBT6芯片设计了一款USB转换器,此板卡主要用于USB转换成IIC/SPI/UART/485/CAN/SWD信号,并可生成3路时钟信号,常用于电路模块的采集和调试。

STM32f103CBT6芯片DigiKey链接:STM32F103CBT6 STMicroelectronics | 集成电路(IC) | DigiKey

XC6206P332MR芯片DigiKey链接:XC6206P332MR-G Torex Semiconductor Ltd | 集成电路(IC) | DigiKey

SI5351A-B-GT芯片DigiKey链接:SI5351A-B-GT Skyworks Solutions Inc. | 集成电路(IC) | DigiKey

SN65HVD3088EDR芯片DigiKey链接:SN65HVD3088EDR Texas Instruments | 集成电路(IC) | DigiKey

原理图/PCB和3D图

这块板卡想做成一个常用的调试和测试板,能兼容常见的一些外设接口,这里考虑常用调试场景,保留的常见的UART/485/CAN/SPI/IIC//SWD接口,并留出3路时钟信号。电源采用USB供电,每个接口做了ESD防护,主要是防止用手接触时静电损伤芯片。采用了SI5351芯片生成3路时钟,时钟电平默认3.3V,如果需要调整,可调节电阻适配不同电压。时钟信号采用SMA插座。

image.png

原理图

image.png

PCB


image.png

3D效果图

实物图

因为疏于检测,用于USB设备识别的两个上拉电阻忘记了,只能用飞线大法解决了...尴尬。还好都是用的0603电阻,焊起来好焊一些。

模块主要性能指标和管脚定义

主要性能指标

核心芯片:STM32F103CBT6

内核:CM-3

主频:72M

板卡尺寸:40mm*38mm

供电方式:USB

输出接口:IIC/SPI/SWD/CAN/UART/485,时钟*3

主控芯片管脚定义

IIC接口:

IIC_SCL--PB6

IIC_SDA--PB7

SPI接口:

SPI_NSS(软)--PA4

SPI_SCK--PA5

SPI_MISO--PA6

SPI_MOSI--PA7

模拟SWD接口:

SWDIO_T--PB14

SWCLK_T--PB13

CAN接口:

CAN_TX--PB9

CAN_RX--PB8

UART接口:

UART_TX--PA9

UART_RX--PA10

485接口:

485_TX--PA2

485_RX--PA3

USB接口:

USB_DP--PA12

USB_DM--PA11

单片机调试接口SWD

SWDIO--PA13

SWCLK--PA14

SWO--PB3

调试软件和代码

本来想用KEIL进行编码和调试的,因为这个用的比较熟。后来想挑战一下,用了B站流行的VSCode开发,摸索了很久才搭建好了VSCode+Cortex-Debug+stm32 VS Code Extension的开源环境。目前也仅仅会连接和调试,里面很复杂的功能还不知道怎么用,比如不知道怎么看外设寄存器的值。

stm32f103cb的初始化配置是通过Cubemx软件配置的,目前只实现了USB转SPI/UART,通过IIC驱动SI5351A生成时钟的功能。

image.png

主要代码有4大块,1是实现USB虚拟串口,能接收上位机发的串口指令。这部分网上很多内容,不在描述。

第二部分,是对串口指令的解析。因为这个模块是平常调试用的,所以约束定义了几个简单的指令:

(1)串口参数指令:UART param:波特率,数据长度,停止位,校验(0,1,2-none,odd,even)

(2)串口发送指令:UART sent:要发送的内容。

(3)SPI参数指令:SPI param:数据大小,极性,相位。

(4)SPI发送指令:SPI sent:要发送的内容。

(5)时钟参数指令:CLOCK param:频率,通道。

这部分是根据上位机下发的指令,解析指令内容,如果是参数,就保存到对应的参数结构体中,如果是发送内容就将内容缓存起来,供第三部分代码处理。

第三部分是根据上位机内容通过对应接口发送出去,实现USB与各种接口的转换。这里已经把所以的指令解析框架搭好了,但由于时间关系没弄完,只实现了SPI/UART和SI5351A时钟生成功能。

第四部分是中断处理,中断处理实现了各个接口的中断功能,主要用来接收数据后转发给USB传给上位机。

/**************************************************
*简介:解析命令
*参数:
*****参数1: USB接收数据缓冲区
*****参数2:命令结构体指针
*返回值:无
**************************************************/
ErrorStatus ParseCMD(uint8_t *buf,CommandInfoDef *command)
{
  char* pToken = NULL;
  uint32_t param[4]={0,0,0,0};//存储参数数组
  char* pCommand = NULL;
  char i=0;


 
  command->CmdNum = 0;
 
  //判断是否有串口命令
  pCommand = strstr(buf,"UART");
  if( pCommand!= NULL)
  {
    //判断是否为串口参数定义
    pCommand = strstr(buf,"param:");
    if(pCommand != NULL)
    {
        //更新串口参数
        //按照波特率,数据长度,停止位,校验(0,1,2-none,odd,even)的方式进行参数提取
        pCommand += strlen("param:"); //跳过串口发送命令
        pToken = strtok(pCommand,","); //提取第一个参数UART
        i=0;
        while(pToken != NULL && i < 4)
        {
            param[i++] = atoi(pToken); //保存参数
            pToken = strtok(NULL,","); //提取下一个参数
        }


        UART_Info.BaudRate = param[0]; //波特率
        UART_Info.WordLength = param[1]; //数据长度
        UART_Info.StopBits = param[2];// 停止位
        UART_Info.Parity = param[3];//校验(0,1,2-none,odd,even)


        return SUCCESS;
    }
    //判断是否为串口发送命令  
    pCommand = strstr(buf,"sent:");    
    if(pCommand != NULL)
    {
      memset(command->CmdBuf,0,CMD_BUF_SIZE);  //提取发送内容
      pCommand += strlen("sent:"); //跳过串口发送命令
      command->CmdLen = strlen(pCommand);//计算命令长度
      strcpy(command->CmdBuf,pCommand);//拷贝发送内容
    }
    command->CmdNum = UART_CMD;
    return SUCCESS;
  }
 
  pCommand = strstr(buf,"IIC");//找到恢复控制命令并返回所在位置指针
  if(pCommand != NULL)
  {
    command->CmdNum = IIC_CMD;
    return SUCCESS;
  }
 
  pCommand = strstr(buf,"SPI");//找到运行并返回所在位置指针
  if(pCommand != NULL)
  {
    //判断是否为SPI参数定义
    pCommand = strstr(buf,"param:");
    if(pCommand != NULL)
    {
        //更新SPI参数
        pCommand += strlen("param:"); //跳过
        //按照波特率,数据长度,停止位,校验(0,1,2-none,odd,even)的方式进行参数提取
        pToken = strtok(pCommand,","); //提取第一个参数UART
        i=0;
        while(pToken != NULL && i < 3)
        {
            param[i++] = atoi(pToken); //保存参数
            pToken = strtok(NULL,","); //提取下一个参数
        }


        SPI_Info.DataSize = param[0]; //数据大小,8/16位
        SPI_Info.CLKPolarity = param[1]; //时钟极性
        SPI_Info.CLKPhase = param[2];// 时钟相位
       
        return SUCCESS;
    }
    //判断是否为SPI发送命令  
    pCommand = strstr(buf,"sent:");    
    if(pCommand != NULL)
    {
      memset(command->CmdBuf,0,CMD_BUF_SIZE);  //提取发送内容
      pCommand += strlen("sent:"); //跳过串口发送命令
      command->CmdLen = strlen(pCommand);//计算命令长度
      strcpy(command->CmdBuf,pCommand);//拷贝发送内容
    }
    command->CmdNum = SPI_CMD;
    return SUCCESS;
  }
 
  pCommand = strstr(buf,"RS485");
  if(pCommand != NULL)
  {
    command->CmdNum = RS485_CMD;
    return SUCCESS;
  }
 
  pCommand = strstr(buf,"CAN");
  if(pCommand != NULL)
  {
    command->CmdNum = CAN_CMD;
    return SUCCESS;
  }
 
  pCommand = strstr(buf,"SWD");
  if(pCommand != NULL)
  {
    command->CmdNum = SWD_CMD;
    return SUCCESS;
  }


  pCommand = strstr(buf,"CLOCK");
  if(pCommand != NULL)
  {
    //判断是否为CLOCK参数定义
    pCommand = strstr(buf,"param:");
    if(pCommand != NULL)
    {
        //更新CLOCK参数
        pCommand += strlen("param:"); //跳过
        //按照频率,通道的方式进行参数提取
        pToken = strtok(pCommand,","); //提取第一个参数UART
        i=0;
        while(pToken != NULL && i < 2)
        {
            param[i++] = atoi(pToken); //保存参数
            pToken = strtok(NULL,","); //提取下一个参数
        }


        SI5351A_Info.frequency = param[0]; //频率,以MHz为单位
        SI5351A_Info.Chanal = param[1]; //通道 0,1,2
     
        command->CmdNum = CLOCK_CMD;
        return SUCCESS;
    }


  }
 
  return ERROR;
}
/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{


  /* USER CODE BEGIN 1 */
  unsigned char buf[32]={"USB CDC TEST\r\n"};
 
  /* USER CODE END 1 */


  /* MCU Configuration--------------------------------------------------------*/


  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();


  /* USER CODE BEGIN Init */


  /* USER CODE END Init */


  /* Configure the system clock */
  SystemClock_Config();


  /* USER CODE BEGIN SysInit */


  /* USER CODE END SysInit */


  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SPI1_Init();
  MX_USART1_UART_Init();
  MX_USB_DEVICE_Init();
  MX_I2C1_Init();
  /* USER CODE BEGIN 2 */
  HAL_UART_Receive_IT(&huart1, uart_rx_buffer, 1); //启动串口接收中断
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET);//点灯
  CDC_Transmit_FS(buf,sizeof(buf));


  if(si5351aSoftInit())
  {
    CDC_Printf("si5351a init success\r\n");


  }
  else
  {
    CDC_Printf("si5351a init fail\r\n");
    while(1);
  }
  //si5351aSetFrequency(10000000, 0);//CLK0


  /* USER CODE END 2 */


  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
   
    HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_15);
    HAL_Delay(500);
 
    if(usb_rx_len != 0 ) //收到数据
    {
      CDC_Transmit_FS(usb_rx_buf,usb_rx_len);
     
      if(ParseCMD(usb_rx_buf,&CommandInfo) != ERROR)
        {
          switch(CommandInfo.CmdNum)
          {
            case UART_CMD :
            {
              //已经吧命令缓存,串口指令解析并生成波形
              HAL_UART_Transmit(&huart1,CommandInfo.CmdBuf,CommandInfo.CmdLen,1000);
              CDC_Printf("UART_CMD\r\n");
              HAL_Delay(1);
              CDC_Printf("UART_CMDLEN:%d\r\n",CommandInfo.CmdLen);
              //调用串口处理函数
             
              break;
            }
            case IIC_CMD:
            {
              CDC_Printf("IIC_CMD\r\n");
              break;
            }
            case SPI_CMD:
            {
              //已经吧命令缓存,串口指令解析并生成波形
              HAL_GPIO_WritePin(SPI_NSS_GPIO_Port,SPI_NSS_Pin,GPIO_PIN_RESET);//片选拉低
              HAL_SPI_Transmit(&hspi1,CommandInfo.CmdBuf,CommandInfo.CmdLen,1000);
              HAL_SPI_TransmitReceive(&hspi1, &spi_dummy_tx, spi_rx_buffer, 2,1000);//启动SPI接收
              HAL_SPI_TransmitReceive_IT(&hspi1, &spi_dummy_tx, spi_rx_buffer, 2);
              HAL_GPIO_WritePin(SPI_NSS_GPIO_Port,SPI_NSS_Pin,GPIO_PIN_SET);//片选拉高
              CDC_Printf("SPI_CMD\r\n");
              HAL_Delay(1);
              CDC_Printf("SPI_CMDLEN:%d\r\n",CommandInfo.CmdLen);
              break;
            }
            case RS485_CMD:
            {
              CDC_Printf("RS485_CMD\r\n");
              break;
            }
            case CAN_CMD:
            {
              CDC_Printf("CAN_CMD\r\n");


              break;  
            }
            case SWD_CMD:
            {
              CDC_Printf("SWD_CMD\r\n");


              break;
            }
            case CLOCK_CMD:
            {
              CDC_Printf("CLOCK_CMD\r\n");
              si5351aSetFrequency(SI5351A_Info.frequency*1000000, SI5351A_Info.Chanal);//CLK0
              HAL_Delay(1);
              CDC_Printf("CLOCK_Fre:%d\r\n",SI5351A_Info.frequency);
              break;
            }
            default:
            {
              CDC_Printf("NoneCMD\r\n");
              ;
              break;
            }
          }
             
        }
      /* USER CODE BEGIN 3 */
      memset(usb_rx_buf, 0,USB_BUF_LEN);//清空接收缓冲区
      usb_rx_len = 0;//清空接收数据长度
      /* USER CODE END 3 */
    }
  }  /* USER CODE END WHILE */
}

功能演示

这是调5351A时抓取的IIC总线数据。

image.png

这是USB转UART/SPI/CLOCK的命令解析过程

image.png

这是串口回环调试过程和回显结果

IMG_20250911_213344.jpg

image.png

这是收到命令后SPI的发送波形

这是收到上位机生成10M时钟信号的波形

IMG_20250911_213822.jpg

心得体会

通过本次活动学习了Kicad/VSCODE的使用,加深了stm32的理解,收获颇丰,感谢电子森林和digikey的赞助。

附件下载
USB_Converter.zip
ClockGen.zip
团队介绍
王世瑞
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号