前言
上一篇文章中已经熟悉了如何烧录程序到ESP32,并且成功的点亮了一盏LED灯,如果你动手能力强的话。 那么上一节就是教会了你做了一个支持HomeKit的灯。
本篇会将上一篇中的LED替换成一个舵机。至于为什么使用舵机,是因为我不想破坏开关的原有结构,想使用一种非侵入式的方法去拓展现有的灯开关。
后半部分会继续把 HomeKit 的开关回调和舵机控制代码融合起来,让 Siri 的“开灯/关灯”指令真正推动物理开关。
重新启用环境变量
如果你看完上一篇文章后重启了电脑,或者关闭了子系统,那么就得重新配置一下ESP-IDF的环境变量,也可能还需要重新将设备挂载到子系统上。 使用命令
. $HOME/esp/esp-idf/export.sh
即可
控制舵机
0x01 控制信号
要想控制舵机,就得发送正确的指令。我使用的舵机长这个样子:
信号频率为50Hz.所谓的指令就是每个周期内,高电平脉冲的持续时间(宽度/长度)。
视频中红色为实际信号,可以看到,随着高电平的持续时间变长,舵机的角度也越来越大。
| 持续时间 | 对应角度 |
|---|---|
| 0.50ms | 0° |
| 1.50ms | 90° |
| 2.50ms | 180° |
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 看看效果吧
由于烧录进去的代码只是为了控制舵机,所以是无法被添加到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
来瞅一瞅效果吧
现在舵机就听我们的指令了哦!
再配合上一个简陋的结构,就可以控制开关了。

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