跳至内容
返回

快把你家的灯接入HomeKit吧其二(舵机与整合篇)

更新于:  at  08:50 下午
⏱️ 约 5804 字 预计阅读 15 分钟

前言

上一篇文章中已经熟悉了如何烧录程序到ESP32,并且成功的点亮了一盏LED灯,如果你动手能力强的话。 那么上一节就是教会了你做了一个支持HomeKit的灯。

本篇会将上一篇中的LED替换成一个舵机。至于为什么使用舵机,是因为我不想破坏开关的原有结构,想使用一种非侵入式的方法去拓展现有的灯开关。

后半部分会继续把 HomeKit 的开关回调和舵机控制代码融合起来,让 Siri 的“开灯/关灯”指令真正推动物理开关。

重新启用环境变量

如果你看完上一篇文章后重启了电脑,或者关闭了子系统,那么就得重新配置一下ESP-IDF的环境变量,也可能还需要重新将设备挂载到子系统上。 使用命令

. $HOME/esp/esp-idf/export.sh

即可

控制舵机

0x01 控制信号

要想控制舵机,就得发送正确的指令。我使用的舵机长这个样子: servo 信号频率为50Hz.所谓的指令就是每个周期内,高电平脉冲的持续时间(宽度/长度)。 视频中红色为实际信号,可以看到,随着高电平的持续时间变长,舵机的角度也越来越大。

持续时间对应角度
0.50ms
1.50ms90°
2.50ms180°

0x02控制相关代码

对于这种由脉冲信号控制的舵机,可以使用PWM来进行控制。查阅官方的指南,可以很容易的找到相关例子,甚至直接有现成的Demo可以使用(不得不说官方的例子真的很详细)。 好了,这下子也不用自己写了(还好不用,不然就得用for循环+延时,手动的凑出脉冲信号来控制了) 直接到mcpwm_servo_control_example.c。 复制或者下载即可,为了方便,可以复制到上一篇文章中的led.c中去,不过要记得先把之前所有的内容注释掉,因为以后还要使用到那些代码。 其中

##define SERVO_MIN_PULSEWIDTH 1000 //Minimum pulse width in microsecond
##define SERVO_MAX_PULSEWIDTH 2000 //Maximum pulse width in microsecond
##define SERVO_MAX_DEGREE 90 //Maximum angle in degree upto which servo can rotate

需要修改一下,依据我的舵机参数,将其修改为

##define SERVO_MIN_PULSEWIDTH 500 //Minimum pulse width in microsecond
##define SERVO_MAX_PULSEWIDTH 2500 //Maximum pulse width in microsecond
##define SERVO_MAX_DEGREE 180 //Maximum angle in degree upto which servo can rotate

又因为我的板子没有引出18号引脚,所以还是修改为上篇文章中的2号引脚吧

static void mcpwm_example_gpio_initialize(void)
{
    printf("initializing mcpwm servo control gpio......\n");
    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, 18);    //Set GPIO 18 as PWM0A, to which servo is connected
}

改为

static void mcpwm_example_gpio_initialize(void)
{
    printf("initializing mcpwm servo control gpio......\n");
    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, 2);    //Set GPIO 2 as PWM0A, to which servo is connected
}
至此,代码就修改完成了。

0x03 烧录进去吧

烧录过程没什么好说的,就是和之前大差不差。

编译好项目以后

make -C examples/esp32/led all

先进行earse_flash

make -C examples/esp32/led erase_flash

再进行flash。

make -C examples/esp32/led flash

0x04 看看效果吧

servo 由于烧录进去的代码只是为了控制舵机,所以是无法被添加到HomeKit里面的。 当烧录完成后,舵机就会缓慢的从0度转到180度,然后快速的回到0度,再次从0度转到180度,然后无限循环。

注:

控制具体转到多少度的代码如下,建议眼熟一下,下篇文章会用到。

// 先计算出目标角度对应的高电平持续时间
angle = servo_per_degree_init(count);
// 设置PWM信号
mcpwm_set_duty_in_us(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, angle);

整合 HomeKit 与舵机控制

我的回合,抽卡。发动魔法卡—融合!

前面已经分别跑通了两件事:第一篇文章中介绍了如何控制 LED 的亮灭,本篇前半部分介绍了如何让舵机旋转到指定角度。接下来要做的,就是把 HomeKit Demo 里的开关状态回调和 MCPWM 舵机控制接起来。

融合前的检测

在 LED Demo 中,每次开灯或关灯都会调用 led_write(bool status) 函数,所以我们只需要根据传进来的 status 值,旋转舵机到对应角度即可。

先从 mcpwm_servo_control_example.c 中提取出需要的方法,内容如下:

// 引入的头文件

##include "driver/mcpwm.h"
##include "soc/mcpwm_periph.h"
##include "esp_attr.h"

// 舵机参数的宏定义
##define SERVO_MIN_PULSEWIDTH 500 // 最小脉冲时间ms
##define SERVO_MAX_PULSEWIDTH 2500 // 最大脉冲时间ms
##define SERVO_MAX_DEGREE 180 // 舵机最大可旋转角度

// 初始化2号端口为PWM
static void mcpwm_example_gpio_initialize(void)
{
    printf("initializing mcpwm servo control gpio......\n");
    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, 18);    //Set GPIO 2 as PWM0A, to which servo is connected
}

// 该方法输入目标角度,输出对应高电平脉冲宽度单位us
static uint32_t servo_per_degree_init(uint32_t degree_of_rotation)
{
    uint32_t cal_pulsewidth = 0;
    cal_pulsewidth = (SERVO_MIN_PULSEWIDTH + (((SERVO_MAX_PULSEWIDTH - SERVO_MIN_PULSEWIDTH) * (degree_of_rotation)) / (SERVO_MAX_DEGREE)));
    return cal_pulsewidth;
}

// 开灯函数
void turn_on(void *arg)
{
    uint32_t angle;
    //1. mcpwm gpio 初始化
    mcpwm_example_gpio_initialize();
    //2. 初始化 mcpwm 配置
    printf("Configuring Initial Parameters of mcpwm......\n");
    mcpwm_config_t pwm_config;
    pwm_config.frequency = 50; // 舵机信号频率为50Hz,每个周期时长20ms
    pwm_config.cmpr_a = 0;     //duty cycle of PWMxA = 0
    pwm_config.cmpr_b = 0;     //duty cycle of PWMxb = 0
    pwm_config.counter_mode = MCPWM_UP_COUNTER;
    pwm_config.duty_mode = MCPWM_DUTY_MODE_0;
    mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config); //应用上面配置到 PWM0A & PWM0B
    // 旋转角度
    printf("Angle of rotation: %d\n", 0);
    // 计算60度对应的脉冲宽度
    angle = servo_per_degree_init(60);
    printf("pulse width: %dus\n", angle);
    mcpwm_set_duty_in_us(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, angle);
    vTaskDelay(10); //Add delay, since it takes time for servo to rotate, generally 100ms/60degree rotation at 5V
    vTaskDelete(NULL);
}

// 关灯函数
void turn_off(void *arg)
{
    uint32_t angle;
    //1. mcpwm gpio 初始化
    mcpwm_example_gpio_initialize();
    //2. 初始化 mcpwm 配置
    printf("Configuring Initial Parameters of mcpwm......\n");
    mcpwm_config_t pwm_config;
    pwm_config.frequency = 50; //frequency = 50Hz, i.e. for every servo motor time period should be 20ms
    pwm_config.cmpr_a = 0;     //duty cycle of PWMxA = 0
    pwm_config.cmpr_b = 0;     //duty cycle of PWMxb = 0
    pwm_config.counter_mode = MCPWM_UP_COUNTER;
    pwm_config.duty_mode = MCPWM_DUTY_MODE_0;
    mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config); //ConfigurePWM0A & PWM0B with above settings
    printf("Angle of rotation: %d\n", 0);
    // 计算120度对应的脉冲宽度
    angle = servo_per_degree_init(120);
    printf("pulse width: %dus\n", angle);
    mcpwm_set_duty_in_us(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, angle);
    vTaskDelay(10); //Add delay, since it takes time for servo to rotate,generally 100ms/60degree rotation at 5V
    vTaskDelete(NULL);
}

// 最后修改led_write函数为
void led_write(bool on)
{
    if (on)
    {
        // 接收到开灯命令,执行开灯任务
         xTaskCreate(turn_on, "turn_on", 4096, NULL, 5, NULL);
    }
    else
    {
        // 接收到关灯命令,执行关灯任务
        xTaskCreate(turn_off, "turn_off", 4096, NULL, 5, NULL);
    }
}

完整的文件位于 HomeKitServoLight.c

来瞅一瞅效果吧

现在舵机就听我们的指令了哦!

再配合上一个简陋的结构,就可以控制开关了。

servo_on_light

老实说,我本来还计划了第四部分的“结构设计篇”,但没想到这个项目…居然烂尾了。所以,就让这个“简陋”的结构成为这个系列的最终成品吧。它虽然丑,但它工作得很好!

参考资料:

ESP-IDF 编程指南

Espressif IoT Development Framework


在以下平台分享此文章: