任务介绍
本来设计是用来实现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插座。
原理图
PCB
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生成时钟的功能。
主要代码有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总线数据。
这是USB转UART/SPI/CLOCK的命令解析过程
这是串口回环调试过程和回显结果
这是收到命令后SPI的发送波形
这是收到上位机生成10M时钟信号的波形
心得体会
通过本次活动学习了Kicad/VSCODE的使用,加深了stm32的理解,收获颇丰,感谢电子森林和digikey的赞助。