前言
这篇文章是关于ESP32的一个小Demo,名称为 智能垃圾桶
涉及到的模块为 SG90舵机和HC-SR04超声波
, 使用的编程烧录软件为 Arduino
。
一开始打算直接扔在ESP32基础教程 – Echo (liveout.cn)这篇文章里,不做过多介绍,因为这个小Demo只用到了舵机和超声波模块。
后来感觉模块虽少,但是涉及到的知识其实挺多的,有PWM、外部中断(硬件)、硬件定时器和二值信号量,所以就单独写了一篇文章。
如果想要再进化一下,可以尝试添加摄像头模块,进行深度学习,分别垃圾种类,这方面还没涉及到,就不多说了。
如果十分感兴趣,可以参考此篇文章:STEAM案例 | 《智能垃圾桶》项目的设计 – 知乎 (zhihu.com)
PS:这个Demo非本人原创,我加了些注释额外代码,并且整理优化成了此篇文章,相关链接会在文章结尾部分给出。
接线图
Demo流程
超声波模块每隔200ms发出一次信号进行测距,如果测量到的物体距离在范围内,则信号为 open_semaphore
。
舵机旋转打开盖子,板载灯变亮,串口打印相关信息。
当打开盖子时,记录打开时间,并启动计时器进行定时检测,即每隔500ms进行检测。
如果检测到盖子关闭时间超过了阈值,则重置打开时间,并设置二值信号量状态为关闭。
得到关闭 close_semaphore
信号后,舵机转动进行关盖。
代码部分
代码需要用到的库: ESP32Servo
主体部分
#include "sonar.h"
#include "cover.h"
#include "servo.h"
int ledPin = 2;//板载灯
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; // 自旋锁
// 打开盖子
void open_cover()
{
bool shouldAct = false; // 开盖行为
/*
临界区是一段代码片段,用于在多任务环境下保护共享资源,以确保对资源的访问不会被并发任务中断或干扰。
临界区的作用是提供一种互斥机制,使得同一时间只有一个任务可以访问共享资源,避免并发访问导致的数据竞争和不一致性。
*/
portENTER_CRITICAL(&mux); // 进入临界区
if (openTime == 0) // 打开时间为0时
shouldAct = true;
openTime = micros(); // 记录当前打开盖子的时间
portEXIT_CRITICAL(&mux); // 离开临界区
if (shouldAct)
servo.write(145); // 舵机旋转145,即开盖°
}
void close_cover()
{
servo.write(0); // 舵机为0°,即关盖
}
void setup() {
pinMode(ledPin,OUTPUT); //输出模式
Serial.begin(115200);
sonar_init(&mux); // 超声波模块初始化
cover_detect_init(&mux); // 初始化盖子检测相关的设置
servo_init(); // 舵机初始化
close_cover(); // 刚开始为盖上盖子状态
}
void loop() {
// 超声波有信号回来
if (xSemaphoreTake(open_semaphore, 0) == pdTRUE) // 打开信号为true
{
Serial.println("open"); // 串口打印信息
open_cover();
digitalWrite(ledPin, HIGH); // 灯亮
}
if (xSemaphoreTake(close_semaphore, 0) == pdTRUE) // 关闭信号为true
{
Serial.println("close");
close_cover();
digitalWrite(ledPin, LOW); // 灯灭
}
}
关盖处理
cover.h
#pragma once //预处理指令,用于确保头文件只被编译一次
#include <freertos/FreeRTOS.h>
#include <esp32-hal-timer.h>
#include <freertos/semphr.h>
extern volatile unsigned long openTime;
extern volatile SemaphoreHandle_t close_semaphore;
void cover_detect_init(portMUX_TYPE *mux);
cover.cpp
#include "cover.h"
hw_timer_t *cover_timer = NULL; // 定时器
static portMUX_TYPE *_mux = NULL;
volatile SemaphoreHandle_t close_semaphore; // 关盖信号量
volatile unsigned long openTime = 0; // 打开盖子的时间
// 中断服务程序ISR:检测盖子是否关闭
void IRAM_ATTR close_detect()
{
portENTER_CRITICAL_ISR(_mux);
auto now = micros();
if (openTime != 0 && (now - openTime) >= 4000000) // 打开盖子时间大于等于4s则关闭
{
openTime = 0;
xSemaphoreGiveFromISR(close_semaphore, NULL);
}
portEXIT_CRITICAL_ISR(_mux);
}
void cover_detect_init(portMUX_TYPE *mux)
{
_mux = mux;
close_semaphore = xSemaphoreCreateBinary();
// 检测到关闭部分,0.5秒检测一次
cover_timer = timerBegin(2, 80, true); // 初始化计时器2,分频系数80,使能中断
timerAttachInterrupt(cover_timer, close_detect, true); // 附加中断处理函数 close_detect 到计时器
timerAlarmWrite(cover_timer, 500000, true); // 设置计时器的定时时间为500000微秒(0.5秒),并使能重复触发
timerAlarmEnable(cover_timer); // 启动计时器
}
舵机模块
servo.h
#pragma
#include <ESP32Servo.h>
extern Servo servo;
void servo_init();
servo.cpp
#include "myservo.h"
// 舵机部分
Servo servo;
int minUs = 500;
int maxUs = 2500;
int servoPin = 13;
void servo_init()
{
// 舵机
ESP32PWM::allocateTimer(1);
servo.setPeriodHertz(50);
servo.attach(servoPin, minUs, maxUs);
}
超声波模块
sonar.h
#pragma once
#include <freertos/FreeRTOS.h>
#include <esp32-hal-timer.h>
#include <freertos/semphr.h>
extern volatile SemaphoreHandle_t open_semaphore; // 信号量
void sonar_init(portMUX_TYPE *mux);
sonar.cpp
#include "sonar.h"
volatile SemaphoreHandle_t open_semaphore; // 信号量
// 超声波测距部分
const int trigPin = 17;
const int echoPin = 18;
int distance = 0;
static portMUX_TYPE *_mux = NULL;
hw_timer_t *sonar_timer = NULL; // 定时器
volatile unsigned long startTime = 0; // 发出超声波时间
volatile unsigned long endTime = 0; // 收到超声波时间
// 硬件定时器ISR
void IRAM_ATTR ping()
{
digitalWrite(trigPin, HIGH);
delayMicroseconds(15);
digitalWrite(trigPin, LOW);
}
// ECHO 引脚ISR
void IRAM_ATTR changeISR()
{
auto now = micros(); // 当前时间
auto state = digitalRead(echoPin);
portENTER_CRITICAL_ISR(_mux);
if (state) // 高电平,即刚发出超声波
startTime = now;
else
endTime = now;
// 变成低电平时表示已经收到回声
// 如果 < 10cm 就发信号开盖
if (!state) {
auto t = endTime - startTime;
auto dis = t * 0.01715;
if (dis <= 10)
{
xSemaphoreGiveFromISR(open_semaphore, NULL); // 给一个开盖信号量发送信号
}
}
portEXIT_CRITICAL_ISR(_mux);
}
void sonar_init(portMUX_TYPE* mux)
{
_mux = mux;
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
open_semaphore = xSemaphoreCreateBinary();
//测距定时器部分
sonar_timer = timerBegin(0, 80, true);
timerAttachInterrupt(sonar_timer, ping, true);
timerAlarmWrite(sonar_timer, 200000, true); // 定时时间为 0.2s
// echo引脚的中断
attachInterrupt(digitalPinToInterrupt(echoPin), changeISR, CHANGE);
// 开始周期测量
timerAlarmEnable(sonar_timer);
}
相关链接
Demo教程视频: ESP32之智能垃圾桶制作讲解—哔哩哔哩_bilibili
GitHub仓库:https://github.com/PGwind/esp32project
ESP32代码记录:ESP32基础教程 – Echo (liveout.cn)
PS:写程序难免会有问题,而且国内的ChatGPT访问受限
这里就给个朋友搭建的链接吧:chuanwen智能
突然发现我在学校的单片机课的期末作业就是这个,只是简单很多。
已经 star 了。
感谢star~这个使用了一些操作系统特性,看起来复杂点
太棒啦,已经 star 啦~
哇,感谢豆豆!![](data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iZW1vdGlvbi1sb2FkaW5nIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9Ii04IC04IDQwIDQwIiBzdHJva2U9IiM4ODgiIG9wYWNpdHk9Ii41IiB3aWR0aD0iNjAiIGhlaWdodD0iNjAiPgogIDxwYXRoIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIxLjUiIGQ9Ik0xNC44MjggMTQuODI4YTQgNCAwIDAxLTUuNjU2IDBNOSAxMGguMDFNMTUgMTBoLjAxTTIxIDEyYTkgOSAwIDExLTE4IDAgOSA5IDAgMDExOCAweiIvPgo8L3N2Zz4=)