主控芯片:STM32F103C8T6
舵机:MG996R
关于舵机的工作原理请看其他博主
1. RCC时钟配置
2. CubeMx定时器配置(注意这里只提供关键设置,关于RCC,SYS配置过程都一样):
主控频率72M,定时器预分频设置为72-1,自动重装值为20000-1(这里可以根据自己需要自行修改),只要保证输出的PWM波是50Hz即可,为什么是50Hz请看舵机工作原理。
直接上手写了,下面就是舵机角度占空比的转换关系(二元一次方程组求出角度和系数的关系)
3. 定义一个PA10口用于模拟PWM波
4. 生成LL库
5. 接下来就是生成的代码了,这里只放关键代码
(1)tim.c 生成的代码不能直接用,需要添加相关使能来是定时器通过工作;
void MX_TIM1_Init(void)
{
/* USER CODE BEGIN TIM1_Init 0 */
/* USER CODE END TIM1_Init 0 */
LL_TIM_InitTypeDef TIM_InitStruct = {0};
LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0};
LL_TIM_BDTR_InitTypeDef TIM_BDTRInitStruct = {0};
LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
/* Peripheral clock enable */
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM1);
/* USER CODE BEGIN TIM1_Init 1 */
/* USER CODE END TIM1_Init 1 */
TIM_InitStruct.Prescaler = 71;
TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;
TIM_InitStruct.Autoreload = 19999;
TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
TIM_InitStruct.RepetitionCounter = 0;
LL_TIM_Init(TIM1, &TIM_InitStruct);
LL_TIM_EnableARRPreload(TIM1);
LL_TIM_SetClockSource(TIM1, LL_TIM_CLOCKSOURCE_INTERNAL);
LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH4);
TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1;
TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_DISABLE;
TIM_OC_InitStruct.OCNState = LL_TIM_OCSTATE_DISABLE;
TIM_OC_InitStruct.CompareValue = 0;
TIM_OC_InitStruct.OCPolarity = LL_TIM_OCPOLARITY_HIGH;
TIM_OC_InitStruct.OCIdleState = LL_TIM_OCIDLESTATE_LOW;
TIM_OC_InitStruct.OCNIdleState = LL_TIM_OCIDLESTATE_LOW;
LL_TIM_OC_Init(TIM1, LL_TIM_CHANNEL_CH4, &TIM_OC_InitStruct);
LL_TIM_OC_DisableFast(TIM1, LL_TIM_CHANNEL_CH4);
LL_TIM_SetTriggerOutput(TIM1, LL_TIM_TRGO_RESET);
LL_TIM_DisableMasterSlaveMode(TIM1);
TIM_BDTRInitStruct.OSSRState = LL_TIM_OSSR_DISABLE;
TIM_BDTRInitStruct.OSSIState = LL_TIM_OSSI_DISABLE;
TIM_BDTRInitStruct.LockLevel = LL_TIM_LOCKLEVEL_OFF;
TIM_BDTRInitStruct.DeadTime = 0;
TIM_BDTRInitStruct.BreakState = LL_TIM_BREAK_DISABLE;
TIM_BDTRInitStruct.BreakPolarity = LL_TIM_BREAK_POLARITY_HIGH;
TIM_BDTRInitStruct.AutomaticOutput = LL_TIM_AUTOMATICOUTPUT_DISABLE;
LL_TIM_BDTR_Init(TIM1, &TIM_BDTRInitStruct);
/* USER CODE BEGIN TIM1_Init 2 */
/* USER CODE END TIM1_Init 2 */
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOA);
/**TIM1 GPIO Configuration
PA11 ------> TIM1_CH4
*/
GPIO_InitStruct.Pin = LL_GPIO_PIN_11;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
/* USER CODE BEGIN 1 */
void PWM_Iint(void) //该函数是初始化使能,需要再生成的代码后面加上
{
LL_TIM_CC_EnableChannel(TIM1,LL_TIM_CHANNEL_CH4);
LL_TIM_EnableCounter(TIM1);
LL_TIM_EnableAllOutputs(TIM1);
}
void Servo_Control(uint16_t angle) //该函数是用来控制舵机的角度的,其实也就是占空比之间的转换,我这里自动重装值是20000-1
{
float temp;
temp = 100*((1.0 / 9.0) * angle + 5.0);//寄存器值 = 1/9 * 角度 + 5
LL_TIM_OC_SetCompareCH4(TIM1, (uint16_t)temp);//将角度值写入比较寄存器
}
(2)main主函数里面,首先调用PWM_Iint(void) ,使能相关寄存器;下面用了三种方法来控制PWM输出,角度控制,占空比控制,模拟PWM控制(暂时无法自己控制旋转速度,希望指正)
a. 下面的代码是实现舵机正反向旋转90度的代码,
/*角度控制*/
for(i=10;i<90;i++)
{
Servo_Control(i);
LL_mDelay(10); //用来控制旋转速度
}
for(i=90;i>10;i--)
{
Servo_Control(i);
LL_mDelay(10); //用来控制旋转速度
}
b. 以下方法可以稳定控制舵机旋转,也就是旋转精度,同样实现正反正反90度旋转
/*占空比控制*/
for(i=500;i<1500;i++)
{
LL_TIM_OC_SetCompareCH4(TIM1, (uint16_t)i);//将角度值写入比较寄存器
LL_mDelay(1);
}
for(i=1500;i>500;i--)
{
LL_TIM_OC_SetCompareCH4(TIM1, (uint16_t)i);//将角度值写入比较寄存器
LL_mDelay(1);
}
c. 以下方法是通过任意IO模拟PWM占空比变换的波形,但是该方法暂时无法实现舵机转速的控制,只用于学习PWM原理
/*模拟PWM PA10*/
for(i=500;i<1500;i++)
{
LL_GPIO_SetOutputPin(PWM_Servo_GPIO_Port,PWM_Servo_Pin);
delay_us(i);
LL_GPIO_ResetOutputPin(PWM_Servo_GPIO_Port,PWM_Servo_Pin);
delay_us((20000-i));
}
for(i=1500;i>500;i--)
{
LL_GPIO_SetOutputPin(PWM_Servo_GPIO_Port,PWM_Servo_Pin);
delay_us(i);
LL_GPIO_ResetOutputPin(PWM_Servo_GPIO_Port,PWM_Servo_Pin);
delay_us(20000-i);
}
void delay_us(uint16_t time) //这里的延时函数一并给出
{
uint16_t i=0;
while(time--)
{
i=10;
while(i--) ;
}
}
测试舵机之前可以用示波器观察波形运行时间,占空比变化,便于理解。关于舵机的工作原理有时间再补存。
代码链接:STM32 CubeMx LL PWM波控制舵机