基于维特6轴IMU模组和ESP32来控制2维舵机云台

目录

前言

硬件准备

维特智能6轴IMU:型号JY62

ESP32S3开发板

二维舵机云台

杜邦线若干

软件环境

esp-idf+vscode开发环境

运行维特官方的ESP32例程

ESP32的PWM功能介绍

LED控制PWM(LEDC)

电机控制PWM(MCPWM)

MCPWM外设相关API

mcpwm_gpio_init

mcpwm_init

mcpwm_set_duty_in_us

核心代码解读

将imu输出的角度信息映射到PWM波的脉宽

初始化两个舵机的PWM配置

 main函数中添加的部分

最终效果


前言

维特智能的6轴IMU模组可以自动输出角加速度、陀螺仪、角度等信息,并且可以通过串口或者I2C读取,因此我们可以直接将读取到的IMU姿态角映射到PWM波的脉宽,从而实现2维舵机云台的简单控制。本文使用ESP32S3通过串口读取IMU数据并生成对应的PWM波控制2维舵机云台

硬件准备

维特智能6轴IMU:型号JY62

这款imu模组在维特的官方淘宝店就有卖,模组内部集成了TDK家的高精度imu芯片以及自研的高精度算法,输出速率快且稳定,唯一缺点就是小贵(开玩笑啦 其实是主播穷~)。

官网资料:JY62产品资料

购买链接:维特智能六轴加速度电子陀螺仪传感器姿态倾角温补传感器模块JY62-淘宝网

ESP32S3开发板

就是普通的esp32s3小系统板。

二维舵机云台

常规的2DoF舵机云台,一般由结构件和2个舵机(水平和垂直)组成,很容易买到。

杜邦线若干

软件环境

esp-idf+vscode开发环境

这里不涉及配环境教程,请读者参看其他教程。

运行维特官方的ESP32例程

打开维特官方的JY62产品资料文档,可以看到SDK一栏,点进去就能看到各种版本的SDK。

  然后再点击下图所示的ESP32_SDK-UART快速上手。

 

点进去之后下载对应的ESP32工程压缩包,如下图:

之后将工程在vscode中打开。在main.c文件中可以看到如下引脚分配:

#define UART_RX_PIN 5
#define UART_TX_PIN 4

 即imu模组的TX连ESP32的引脚5,RX连ESP32的引脚4。之后将imu模组的GND连上ESP32的GND,模组VCC连ESP32的3v3或5v。接着就是编译并烧录代码,并打开esp-idf的串口监视窗口,应该能看到如下imu模组不断输出的信息,如果晃动imu模组,相应的数据会发生改变。

ESP32的PWM功能介绍

以上已经通过ESP32的串口功能成功读取到imu模组输出的信息,但是要驱动舵机的话,需要PWM波,这里就不具体介绍PWM的相关理论了,而是简单介绍一下ESP32的PWM资源。本文使用的是ESP32S3,其PWM资源如下。本文使用的是MCPWM外设来生成PWM波驱动舵机

LED控制PWM(LEDC)

  1. 通道数量:LEDC支持8个独立通道。

  2. 特性

    • 可配置周期和占空比,占空比分辨率可达14位。

    • 支持多种时钟源,包括APB时钟和外部主晶振时钟。

    • 在CPU处于轻睡眠模式时仍可运行。

    • 支持占空比的渐进增加或减少,适用于LED RGB颜色渐变生成器。

  3. 引脚配置:LEDC的信号可以通过GPIO矩阵连接到任意GPIO引脚。

电机控制PWM(MCPWM)

  1. 通道数量:MCPWM包含两个MCPWM外设,每个外设支持3个PWM运算符。

  2. 特性

    • 每个MCPWM外设包含一个时钟分频器(预分频器)、三个PWM定时器、三个PWM运算符和一个捕获模块。

    • PWM定时器用于生成定时参考,PWM运算符根据这些参考生成所需的波形。

    • 任何PWM运算符都可以配置为使用任何PWM定时器的定时参考,不同的PWM运算符可以使用同一个PWM定时器的定时参考来产生相关的PWM信号。

    • 不同的PWM定时器也可以同步在一起。

  3. 引脚配置:MCPWM的信号可以通过GPIO矩阵连接到任意GPIO引脚。

MCPWM外设相关API

mcpwm_gpio_init

功能:将指定的GPIO初始化为MCPWM信号输出引脚。

函数原型:

esp_err_t mcpwm_gpio_init(mcpwm_unit_t mcpwm_num, mcpwm_io_signals_t io_signal, int gpio_num);

参数解读:

  • mcpwm_num:MCPWM单元编号(如MCPWM_UNIT_0MCPWM_UNIT_1)。

  • io_signal:指定的MCPWM信号(如MCPWM0AMCPWM0B等,具体信号见mcpwm_io_signals_t)。

  • gpio_num:要绑定的GPIO编号。

mcpwm_init

功能:初始化MCPWM单元和定时器。

函数原型:

esp_err_t mcpwm_init(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num, const mcpwm_config_t *mcpwm_conf);

参数解读:

  • mcpwm_num:MCPWM单元编号。

  • timer_num:MCPWM定时器编号(如MCPWM_TIMER_0MCPWM_TIMER_1等)。

  • mcpwm_config:指向mcpwm_config_t结构体的指针,包含定时器的配置信息(如频率、占空比等)。

mcpwm_set_duty_in_us

功能:设置MCPWM的占空比,以微秒为单位。

函数原型:

esp_err_t mcpwm_set_duty_in_us(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num, mcpwm_generator_t gen, uint32_t duty_in_us);

参数解读:

  • mcpwm_num:MCPWM单元编号。

  • timer_num:MCPWM定时器编号。

  • gen:PWM信号生成器编号(如MCPWM_GEN_AMCPWM_GEN_B)。

  • duty_in_us:高电平的持续时间(单位为微秒),其实就是脉宽。

核心代码解读

将imu输出的角度信息映射到PWM波的脉宽

博主所使用的舵机是270度舵机,即正负可以达到135度,而驱动舵机一般是用50HZ即周期为20ms的PWM波,脉宽范围即高电平的持续时间为500us到2500us。由于PWM控制舵机是线性的,因此可以用一个简单的公式将任意角度映射到其对应的脉宽,代码如下,用手写一下就能理解啦~

#define SERVO_MIN_PULSEWIDTH_US 500   // 最小脉冲宽度(微秒)
#define SERVO_MAX_PULSEWIDTH_US 2500  // 最大脉冲宽度(微秒)
#define SERVO_MIN_DEGREE -135.0f      // 最小角度(270度范围)
#define SERVO_MAX_DEGREE 135.0f       // 最大角度(270度范围)

// 角度映射到脉宽
static inline uint32_t angle_2_pulsewidth(float angle)
{
    return (uint32_t)((angle - SERVO_MIN_DEGREE) * (SERVO_MAX_PULSEWIDTH_US - SERVO_MIN_PULSEWIDTH_US) / (SERVO_MAX_DEGREE - SERVO_MIN_DEGREE) + SERVO_MIN_PULSEWIDTH_US);
}

初始化两个舵机的PWM配置

其次就是初始化水平舵机和垂直舵机的PWM配置,相关函数上面已经介绍过了。本文ESP32输出用于驱动水平和垂直舵机PWM信号的引脚分别为引脚18和17

#include "driver/mcpwm.h"
#define SERVO_HORIZONTAL_GPIO 18      // 水平舵机连接的 GPIO 引脚
#define SERVO_VERTICAL_GPIO 17        // 垂直舵机连接的 GPIO 引脚

/**
 * @brief PWM初始化
 */
void Servo_Init(void){
	// 初始化水平舵机 MCPWM
    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A,SERVO_HORIZONTAL_GPIO);
    mcpwm_config_t servo_config = {
        .frequency = 50, // 舵机的频率为 50Hz
        .cmpr_a = 0,     // 初始占空比为 0
        .duty_mode = MCPWM_DUTY_MODE_0,
        .counter_mode = MCPWM_UP_COUNTER,
    };
    ESP_ERROR_CHECK(mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &servo_config));
    ESP_LOGI(TAG, "水平舵机 MCPWM 初始化成功");

    // 初始化垂直舵机 MCPWM
    mcpwm_gpio_init(MCPWM_UNIT_1, MCPWM1A, SERVO_VERTICAL_GPIO);
    ESP_ERROR_CHECK(mcpwm_init(MCPWM_UNIT_1, MCPWM_TIMER_1, &servo_config));
    ESP_LOGI(TAG, "垂直舵机 MCPWM 初始化成功");

    // 设置舵机到初始位置(0 度)
    ESP_ERROR_CHECK(mcpwm_set_duty_in_us(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, angle_2_pulsewidth(0.0f)));
    ESP_ERROR_CHECK(mcpwm_set_duty_in_us(MCPWM_UNIT_1, MCPWM_TIMER_1, MCPWM_OPR_A, angle_2_pulsewidth(0.0f)));
    ESP_LOGI(TAG, "舵机设置到 0 度");
}

 main函数

基于维特官方的工程,在while(1)循环里,ESP32一旦获得imu模组输出的angle信息,就将其转换成脉宽,然后通过mcpwm_set_duty_in_us函数设置占空比,就能实时用imu模组来控制2维舵机云台啦~此外还添加了打印日志,实时输出2个舵机的PWM脉宽和角度,不过这里偏航角是有累计误差的,因为6轴IMU没有地磁来修正其积分累计误差。另外,由于舵机云台是2DoF的,而imu模组会输出3个角度,因此其中有一个用不上(滚转角)

void app_main(void)
{
	float fAcc[3], fGyro[3], fAngle[3];
	int i;

	xTaskCreate(Usart0_task, "Usart0_task", 4096, NULL, 5, NULL);
	xTaskCreate(Usart1_task, "Usart1_task", 4096, NULL, 5, NULL);

	WitInit(WIT_PROTOCOL_NORMAL, 0x50);
	WitSerialWriteRegister(SensorUartSend);
	WitRegisterCallBack(SensorDataUpdata);
	WitDelayMsRegister(Delayms);
	printf("\r\n********************** wit-motion normal example  ************************\r\n");
	AutoScanSensor();
    /******************************舵机********************************* */
    Servo_Init();
    vTaskDelay(pdMS_TO_TICKS(1000)); // 等待舵机就位
	while (1)
	{
		if(s_cDataUpdate)
		{
			for(i = 0; i < 3; i++)
			{
				fAcc[i] = sReg[AX+i] / 32768.0f * 16.0f;
				fGyro[i] = sReg[GX+i] / 32768.0f * 2000.0f;
				fAngle[i] = sReg[Roll+i] / 32768.0f * 180.0f;
			}
			if(s_cDataUpdate & ACC_UPDATE)
			{
				printf("acc:%.3f %.3f %.3f\r\n", fAcc[0], fAcc[1], fAcc[2]);
				s_cDataUpdate &= ~ACC_UPDATE;
			}
			if(s_cDataUpdate & GYRO_UPDATE)
			{
				printf("gyro:%.3f %.3f %.3f\r\n", fGyro[0], fGyro[1], fGyro[2]);
				s_cDataUpdate &= ~GYRO_UPDATE;
			}
			if(s_cDataUpdate & ANGLE_UPDATE)
			{
				printf("angle:%.3f %.3f %.3f\r\n", fAngle[0], fAngle[1], fAngle[2]);
				/******************************************************添加的控制代码*******************************************************************/
                // 设置舵机角度
                uint32_t horizontal_pulsewidth = angle_2_pulsewidth(fAngle[2]);
                uint32_t vertical_pulsewidth = angle_2_pulsewidth(fAngle[0]);
                ESP_ERROR_CHECK(mcpwm_set_duty_in_us(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, horizontal_pulsewidth));//水平舵机,对应云台偏航角
                ESP_ERROR_CHECK(mcpwm_set_duty_in_us(MCPWM_UNIT_1, MCPWM_TIMER_1, MCPWM_OPR_A, vertical_pulsewidth));//垂直舵机,对应云台俯仰角
				
                // 打印日志
                ESP_LOGI(TAG, "水平舵机脉冲宽度: %lu us", horizontal_pulsewidth);
                ESP_LOGI(TAG, "垂直舵机脉冲宽度: %lu us", vertical_pulsewidth);
                ESP_LOGI(TAG, "偏航角: %.2f 度,俯仰角: %.2f 度", fAngle[2], fAngle[0]);
				
				/*************************************************************************************************************************/
				s_cDataUpdate &= ~ANGLE_UPDATE;
			}
			if(s_cDataUpdate & MAG_UPDATE)
			{
				printf("mag:%d %d %d\r\n", sReg[HX], sReg[HY], sReg[HZ]);
				s_cDataUpdate &= ~MAG_UPDATE;
			}
			if(s_cDataUpdate & TEMP_UPDATE)
			{
				printf("temp:%f\r\n", sReg[TEMP]/100.0);
				s_cDataUpdate &= ~TEMP_UPDATE;
			}
		}
        

	}
}

最终效果

连线示意图如下所示。这里主播踩了一个坑,就是2个舵机的VCC跟GND最好用另一个板子来供,然后ESP32跟那个板子共地,因为ESP32数据线是连的电脑,舵机断电和上电瞬间可能会产生反电动势通过ESP32的数据线打在电脑上,触发电脑的过流保护而导致电脑关机,这个其实可能也跟舵机有关,可能是主播买的舵机的工作电流比较大的缘故

结果演示

下面是esp-idf的串口监视窗口,可以看到除了原先的imu信息,还多了新加的打印日志。当然博主为了方便演示,代码中直接用的绝对量来控制,正常工程应用的话应该用增量控制。

 

PS:完整工程还请读者移步至我的gitee仓库:esp_imuServo: 基于维特6轴imu模组和ESP32控制2维舵机云台 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值