一.前情提要
早在大一就想要有一个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);
}
}
这里简单写了一个对串口接收到的消息进行解析并点灯的功能,
评论区