• 正文
  • 相关推荐
申请入驻 产业图谱

CW32L012的舵机云台控制案例分享

7小时前
106
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

前言:本实验用CW32L012作为主控,本实验用CW32L012作为主控,两个舵机组成上下层云台实现左右上下摆的控制。

一、舵机云台介绍:

本实验使用两个SG90舵机,上下两侧各一个组成一个两个自由度的云台。SG90是最经典、最便宜的9g 微型模拟舵机,广泛用于单片机、机器人、航模、云台等项目。上下两个电机分别用两个按键控制实现动作。舵机一共有三根线,黄色是信号线(接引脚),红色接正,灰色接地。下附SG90舵机和云台图片。

舵机云台

SG90舵机

二、舵机驱动原理:定时器+pwm

1.舵机靠什么控制角度?

答:靠高电平的持续时间,SG90规定PWM频率固定50Hz,一个周期固定20ms。角度只看高电平宽度:1.0ms 高电平 → 转到 0°;1.5ms 高电平 → 转到 90°(中间);2.0ms 高电平 → 转到 180°

2.什么是PWM

答:PWM=脉冲宽度调制。也就是固定周期里,高电平占多久、低电平占多久。在舵机上面就是20ms的周期内,改变高电平的宽度等于改变了舵机转的角度。

3.为什么一定要用定时器

答:不用定时器行不行,答案可以但不建议,因为用普通 IO + 延时函数软件模拟 PWM有个致命的缺点那就是:延时卡死 CPU,程序干不了别的;延时不准,波形歪 → 舵机抖动、角度跑偏主程序一跑别的任务,PWM 就乱了。用定时器的好处:单片机定时器外设自己独立运行自动生成 20ms 周期;自动控制高电平 1ms、1.5ms、2ms;完全不占用 CPU;你主程序该干嘛干嘛;波形极其标准 → 舵机不抖、定位准

4.定时器是怎么生成PWM的?

答:定时器就像一个精准秒表。先设定好计时频率(分频),设定好一个完整周期一共多少个数(自动重装载值)。再设置一个比较值,计数小于比较值就输出高电平;计数超过比较值就输出低电平。改比较值就是改高电平时间也就是直接改舵机角度。这就是定时器 PWM 输出原理。

下面具体展示出在Keil工程的代码:

三、软件讲解:

Gimbal.c:#include "Gimbal.h"void Gimbal_Init(void){    __SYSCTRL_GPIOA_CLK_ENABLE();    __SYSCTRL_GTIM2_CLK_ENABLE();    // 1. 配置复用功能    PA00_AFx_GTIM2CH1(); // 摇头 CH1    PA01_AFx_GTIM2CH2(); // 点头 CH2    //2. 配置 GPIO 为推挽输出    GPIO_InitTypeDef GPIO_InitStruct;    GPIO_InitStruct.IT   = GPIO_IT_NONE;    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;    GPIO_InitStruct.Pins = GPIO_PIN_0 | GPIO_PIN_1;    GPIO_Init(CW_GPIOA, &GPIO_InitStruct);    // 3. 定时器基础配置(完全对齐能动的代码)    GTIM_InitTypeDef GTIM_InitStruct = {0};    GTIM_InitStruct.AlignMode    = GTIM_ALIGN_MODE_EDGE;    GTIM_InitStruct.ARRBuffState = GTIM_ARR_BUFF_EN;    GTIM_InitStruct.Direction    = GTIM_DIRECTION_UP;    GTIM_InitStruct.EventOption  = GTIM_EVENT_NORMAL;    GTIM_InitStruct.Prescaler    = 95;    GTIM_InitStruct.PulseMode    = GTIM_PULSE_MODE_DIS;    GTIM_InitStruct.ReloadValue  = 20000 - 1;    GTIM_InitStruct.UpdateOption = GTIM_UPDATE_DIS;    GTIM_TimeBaseInit(CW_GTIM2, &GTIM_InitStruct);    // 4. PWM 输出模式配置(完全对齐能动的代码)    GTIM_OCModeCfgTypeDef GTIM_OCModeCfgStruct = {DISABLE, DISABLE, 0};    GTIM_OCModeCfgStruct.FastMode     = DISABLE;    GTIM_OCModeCfgStruct.OCMode       = GTIM_OC_MODE_PWM1;    GTIM_OCModeCfgStruct.OCPolarity   = GTIM_OC_POLAR_NONINVERT;    GTIM_OCModeCfgStruct.PreloadState = DISABLE;    GTIM_OC1ModeCfg(CW_GTIM2, &GTIM_OCModeCfgStruct);    GTIM_OC2ModeCfg(CW_GTIM2, &GTIM_OCModeCfgStruct);    // 5. 初始比较值设 0    GTIM_SetCompare1(CW_GTIM2, 0);    GTIM_SetCompare2(CW_GTIM2, 0);    // 6. 使能通道输出    GTIM_OC1Cmd(CW_GTIM2, ENABLE);    GTIM_OC2Cmd(CW_GTIM2, ENABLE);    // 7. 启动定时器    GTIM_Cmd(CW_GTIM2, ENABLE);}void Set_Yaw(float Angle){    if(Angle < 0)   Angle = 0;    if(Angle > 180) Angle = 180;    CW_GTIM2->CCR1 = (uint32_t)(Angle * 2000.0f / 180.0f + 500);}void Set_Pitch(float Angle){    if(Angle < 0)   Angle = 0;    if(Angle > 180) Angle = 180;    CW_GTIM2->CCR2 = (uint32_t)(Angle * 2000.0f / 180.0f + 500);}Gimbal.h:#ifndef __GIMBAL_H  #define __GIMBAL_H#include "cw32l012_gpio.h"#include "cw32l012_gtim.h"#include "cw32l012_sysctrl.h"void Gimbal_Init(void);void Set_Yaw(float Angle);void Set_Pitch(float Angle);#endifKEY.c:#include "key.h"#include "cw32l012_gpio.h"#include "cw32l012_sysctrl.h"static uint8_t key_val  = 0;static uint8_t key_down = 0;static uint8_t key_old  = 0;// 简单延时消抖(粗略,约10ms @96MHz)static void Key_Delay(uint32_t ms){    uint32_t i;    while(ms--)    {        i = 12000;        while(i--);    }}void key_Init(void){    __SYSCTRL_GPIOB_CLK_ENABLE();    GPIO_InitTypeDef GPIO_Initstructure;    GPIO_Initstructure.IT   = GPIO_IT_NONE;    GPIO_Initstructure.Mode = GPIO_MODE_INPUT_PULLUP; // 上拉输入    GPIO_Initstructure.Pins = GPIO_PIN_11 | GPIO_PIN_13;    GPIO_Init(CW_GPIOB, &GPIO_Initstructure);}// 读取当前按键状态(带消抖)static uint8_t readpin(void){    uint8_t temp = 0;    if(GPIO_ReadPin(CW_GPIOB, GPIO_PIN_11) == 0)    {        Key_Delay(10); // 消抖等待        if(GPIO_ReadPin(CW_GPIOB, GPIO_PIN_11) == 0)            temp |= 0x01; // bit0 表示 PB11 按下    }    if(GPIO_ReadPin(CW_GPIOB, GPIO_PIN_13) == 0)    {        Key_Delay(10);        if(GPIO_ReadPin(CW_GPIOB, GPIO_PIN_13) == 0)            temp |= 0x02; // bit1 表示 PB13 按下    }    return temp;}// 按键扫描:检测下降沿(按下瞬间)void key_scan(void){    key_val = readpin();    uint8_t changed = key_old ^ key_val; // 哪些位发生了变化    if((changed & 0x01) && (key_val & 0x01)) // PB11 刚按下        key_down = 1;    if((changed & 0x02) && (key_val & 0x02)) // PB13 刚按下        key_down = 2;    key_old = key_val;}// 获取按键值(读一次后清零,防止重复触发)uint8_t get_keyval(void){    uint8_t temp = key_down;    key_down = 0;    return temp;}KEY.h:#ifndef __KEY_H#define __KEY_H#include <stdint.h>void    key_Init(void);void    key_scan(void);uint8_t get_keyval(void);#endifmain:#include "cw32l012.h"#include "main.h"#include "Gimbal.h"#include "key.h"#include "oled.h"// 粗略延时(约1ms @96MHz)void Delay_ms(uint32_t ms){    uint32_t i;    while(ms--)    {        i = 12000;        while(i--);    }}// 云台当前角度float current_yaw   = 90.0f; // 摇头初始90度(居中)float current_pitch = 90.0f; // 点头初始90度(居中)int main(void){    // 换掉 SystemCoreClockUpdate(),改成这个    SYSCTRL_HSI_Enable(SYSCTRL_HSIOSC_DIV1);    SYSCTRL_HCLKPRS_Config(SYSCTRL_HCLK_DIV1);    SYSCTRL_PCLKPRS_Config(SYSCTRL_PCLK_DIV1);    SYSCTRL_SystemCoreClockUpdate(96000000);    key_Init();    OLED_Init();    OLED_Clear();    OLED_Printf(0, 0, OLED_6X8, "Yaw  : %.1f", current_yaw);    OLED_Printf(0, 10, OLED_6X8, "Pitch: %.1f", current_pitch);    OLED_Update();    Gimbal_Init();    Set_Yaw(current_yaw);    Set_Pitch(current_pitch);    Delay_ms(500);    while(1){    key_scan();    uint8_t key = get_keyval();    if(key == 1)    {        current_yaw += 10.0f;        if(current_yaw > 180.0f) current_yaw = 0.0f;        Set_Yaw(current_yaw);        // 刷新显示        OLED_Printf(0, 0, OLED_6X8, "Yaw  : %.1f  ", current_yaw);        OLED_Update();    }    else if(key == 2)    {        current_pitch += 10.0f;        if(current_pitch > 150.0f) current_pitch = 30.0f;        Set_Pitch(current_pitch);        //刷新显示        OLED_Printf(0, 10, OLED_6X8, "Pitch: %.1f  ", current_pitch);        OLED_Update();    }    Delay_ms(20);}}void assert_failed(uint8_t *file, uint32_t line) { while(1); }

四、最后总结注意事项:

1.必须做按键消抖硬件加下拉 / 上拉电阻,软件加简单延时消抖或状态机消抖,避免误触发。

2.优先用定时器硬件 PWM别用普通 IO 延时模拟 PWM,容易抖、占用 CPU、按键响应变慢,硬件 PWM 最稳。

3.舵机单独供电不要靠单片机开发板 3.3V/5V 小电流给舵机供电,容易电压跌落,导致抖动、卡死,最好外部 5V 独立供电、共地。

4.角度不要一次性跳太大程序里设置逐级缓转,不要从 0° 直接跳到 180°,保护齿轮不易扫齿,运行更顺滑。

5.程序逻辑写简单分层分开写:按键扫描函数、舵机角度设置函数,不要全部揉在主循环里,方便后期改功能、加模式。

6.预留角度限位程序里限定最小 0°、最大 180°,防止赋值超范围导致舵机卡死烧机。

7.区分 180° 和 360° 舵机实验用180° 角度舵机;买到 360° 连续旋转款是不能定点停角度的,怎么按都只会转圈,注意别买错。

扫码加入QQ群3群| 610403240

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录

以开放、共享、互助为理念,致力于构建武汉芯源半导体CW32系列MCU生态社区。无论是嵌入式MCU小自还是想要攻破技术难题的工程师,亦或是需求解决方案的产品经理都可在CW32生态社区汲取营养、共同成长。