一.前情提要

早在大一就想要有一个PWM调试器,可以快速的生成PWM波,用于快速驱动舵机或电调,市面上的PWM调试器要不就是供电麻烦,需要2S电池,要不就是PWM频率不可调,所以想要一个可以直接插typec供电诱骗出9V或者12V可调,舵机直接插上去就可以通过旋钮控制进行测试的工具,有刷电机也可以直接通过接线端子直接拧螺丝接到这个工具上。

之前对PY32的性价比就已经略有耳闻了,优信电子0.6一片,带8个ADC,5个PWM口,还有I2C和SPI接口,封装还特别小,除了没有CAN接口,其他的基本都完美了,CAN通信也可以用I2C平替。简直就是梦中情芯。正好借这个舵机调试小工具的契机玩一下这个芯片

要实现的功能:

1.可以通过按键或者旋钮调整输出PWM的占空比和频率//什么逻辑

2.有屏幕可以看到当前的参数,最好搞个小UI一样的

3.USB直插可以对舵机/电机进行供电

4.搞个上位机,最好兼容现成的上位机,或者自己做个轻量化的?

二.开发环境配置

普冉官方提供了三种开发环境,分别是keil的MDK-ARM,EWARM(没用过)和EIDE,本人对编译器的偏好Clion>VScode>>keil

本来在GITHUB看到了有人打包好了PY32库的Cmake版本,这样就可以美美用clion了,但是折腾了好久没折腾明白,好不容易cmake和编译都过了但是烧录还是会出问题,下次再折腾吧,只能转头用VSCode的EIDE了

配置过程如下

待补充

三.PY32库开发

PY32的HAL库和STM32的HAL库特别像,但是没有CUBEMX这样的可视化界面,只能自己去完成HAL库的调用,也算是从CUBEMX进阶到HAL库级别了。下面是各外设调用需要进行的流程和调用的函数示例

PY32F002BF15U6_PY32F002B系列_普冉半导体(上海)股份有限公司

1.GPIO点灯

每个新人的嵌入式第一课就是点亮一颗电容 LED,GPIO的使用只要HAL_GPIO_Init和使能GPIOA时钟就可以,虽然我不知道为什么我没有用到PWM和串口这样的外设还需要使能GPIO的时钟()

__HAL_RCC_GPIOA_CLK_ENABLE();//使能GPIOA时钟

GPIO_InitTypeDef GPIO_InitTypeDef={0};//对GPIO进行配置
GPIO_InitTypeDef.Pin = GPIO_PIN_1;//指定GPIO的1号脚
GPIO_InitTypeDef.Mode = GPIO_MODE_OUTPUT_PP;//推挽
GPIO_InitTypeDef.Pull = GPIO_PULLUP;//上拉
GPIO_InitTypeDef.Speed = GPIO_SPEED_FREQ_LOW;//速度为低
HAL_GPIO_Init(GPIOA, &GPIO_InitTypeDef);//对GPIOA组进行上面结构体信息的初始化


  while (1)//在循环翻转接口即可
  {

    
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1);
    HAL_Delay(500);
  }

效果:

2.PWM

PWM驱动需要三步,完成时基的启动,GPIO初始化,PWM参数输入与启动。

TIM_HandleTypeDef htim1; // 定义 TIM1 定时器句柄结构体; 
htim1.Instance = TIM1; // 指定定时器实例为 TIM1
htim1.Init.Period = 2000-1; // 设置自动重装载值(周期)为 1999
htim1.Init.Prescaler = 1200 - 1; // 设置预分频值为 1199
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // 设置时钟分频为不分频
htim1.Init.CounterMode = TIM_COUNTERMODE_UP; // 设置计数器模式为向上计数
htim1.Init.RepetitionCounter = 1 - 1; // 设置重复计数器值为 0
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; // 禁用自动重装载寄存器预加载功能
HAL_TIM_PWM_Init (&htim1); // 初始化 TIM1 基础配置


TIM_OC_InitTypeDef sConfig;// 定义输出比较(PWM)配置结构体
sConfig.OCMode = TIM_OCMODE_PWM1; // 设置输出比较模式为 PWM1 模式
sConfig.OCPolarity = TIM_OCPOLARITY_HIGH; // 设置输出通道极性为高电平有效
sConfig.OCFastMode = TIM_OCFAST_DISABLE; // 禁用输出通道快速模式
sConfig.OCNPolarity = TIM_OCNPOLARITY_HIGH; // 设置互补输出通道极性为高电平有效
sConfig.OCNIdleState = TIM_OCNIDLESTATE_RESET; // 设置互补输出通道空闲状态为低电平
sConfig.OCIdleState = TIM_OCIDLESTATE_RESET; // 设置输出通道空闲状态为低电平
sConfig.Pulse = 400; // 设置 PWM 脉冲宽度(比较值)为 400
HAL_TIM_PWM_ConfigChannel (&htim1, &sConfig, TIM_CHANNEL_2); // 配置 TIM1 的通道 2 为 PWM 模式,


HAL_TIM_PWM_Start (&htim1, TIM_CHANNEL_2); // 启动 TIM1 通道 2 的 PWM 输出.

调用HAL_TIM_Base_Init的时候会自动跳到弱定义void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim),我们需要重写这个函数,本来这个函数是cubeMX帮你写好的,所以我们这里要手动写一下,在这个函数中完成时基的启动和GPIO的初始化,然后放到py32f002b_hal_msp.c文件中。

void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
  GPIO_InitTypeDef   GPIO_InitStruct = {0};

  __HAL_RCC_TIM1_CLK_ENABLE(); //使能TIM1时钟                                                      
  __HAL_RCC_GPIOB_CLK_ENABLE();//使能GPIOB的时钟                             


  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;//复用推挽模式                   
  GPIO_InitStruct.Pull = GPIO_PULLUP;//默认上拉                       
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;//高速模式
  
  //GPIO PB0(TIM1_CH2)初始化
  GPIO_InitStruct.Pin = GPIO_PIN_0;
  GPIO_InitStruct.Alternate = GPIO_AF2_TIM1;//AF2代表第二种复用模式(TIM1/2)
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  
}

但是按照普冉的库函数的说法,你用什么就是什么的MspInit。这个注释写的还是可以的,夸夸普冉

效果:这里可以算出官方默认配置的内部时钟为24Mhz

3.串口UART收发数据

这里采用不同于官方示例库中的写法,我们用串口空闲中断,可以接收不定长数据,且比官方直接操作寄存器的方式更加规范。串口由于用到了中断比之前的外设稍微麻烦一些,但是也不是很难,还是配置老三样完成时基的启动,GPIO初始化、其他的就是这个专属外设的配置了。

/ UART_HandleTypeDef huart1;
  huart1.Instance          = USART1;
  huart1.Init.BaudRate     = 115200;
  huart1.Init.WordLength   = UART_WORDLENGTH_8B;
  huart1.Init.StopBits     = UART_STOPBITS_1;
  huart1.Init.Parity       = UART_PARITY_NONE;
  huart1.Init.HwFlowCtl    = UART_HWCONTROL_NONE;
  huart1.Init.Mode         = UART_MODE_TX_RX;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if(HAL_UART_Init(&huart1) != HAL_OK)
{
    APP_ErrorHandler();
}
  __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);



在py32f002b_hal_msp.c一样配置好IO和时基

 void HAL_UART_MspInit(UART_HandleTypeDef *huart){


  GPIO_InitTypeDef  GPIO_InitStruct = {0};

  /* Clock Enable */
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_USART1_CLK_ENABLE();
  
  /* GPIO initialization
  PB04:TX,
  PB05:RX
  */
  GPIO_InitStruct.Pin       = GPIO_PIN_4;
  GPIO_InitStruct.Mode      = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull      = GPIO_PULLUP;
  GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_HIGH;
  GPIO_InitStruct.Alternate = GPIO_AF1_USART1;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = GPIO_PIN_5;
  GPIO_InitStruct.Alternate = GPIO_AF1_USART1;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  
  /* USART1 interrupt enable */
  HAL_NVIC_SetPriority(USART1_IRQn, 0, 1);
  HAL_NVIC_EnableIRQ(USART1_IRQn);




 }

接下来配置中断,在py32f002b_it.c中加上我们的中断回调函数

注意这些中断的调用关系,

1.USART1_IRQHandler(void) 只要是串口产生的一切中断都会到这个函数中来,这是由启动文件决定的

-> HAL_UART_IRQHandler(&huart1);由上面的函数调用,这里是手动放进去的,理论上可以完全不用他的函数直接在USART1_IRQHandler里写判断是什么中断,以及要做什么,对于中断向量不直接指向HAL_UART_IRQHandler而是要这样套一层娃娃的原因AI已经做出了很好的回答

-> HAL_UART_IdleFrameDetectCpltCallback(huart); 以前觉得中断还有点玄学,不知道是为什么找到对应的回调函数的。现在理解了,其实就是对回调的参数\当初设定的参数进行判断,到这一层要记得完成回调要清除标志位,这里库里已经帮我清除掉了

我们既然搞清楚了中断的调用流程,那我们把HAL_UART_IdleFrameDetectCpltCallback弱函数实例化

void HAL_UART_IdleFrameDetectCpltCallback(UART_HandleTypeDef *huart)
{
  uint8_t rx_data;
  if(HAL_UART_Receive_IT(huart, &rx_data, 1) != HAL_OK)
  {
    APP_ErrorHandler();
  }
  if(rx_data == '1')
  {
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
  }
  else if(rx_data == '0')
  {
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
  }
}

这里简单写了一个对串口接收到的消息进行解析并点灯的功能,

四、PCB及原理图设计