前言

这篇文章是关于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
#include
#include
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
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
#include
#include
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仓库:
ESP32代码记录:ESP32基础教程 – Echo (liveout.cn)
PS:写程序难免会有问题,而且国内的ChatGPT访问受限
这里就给个朋友搭建的链接吧:chuanwen智能

标签: 单片机

已有 7 条评论

  1. ztrztr ztrztr

    突然发现我在学校的单片机课的期末作业就是这个,只是简单很多。
    已经 star 了。

    1. 感谢star~这个使用了一些操作系统特性,看起来复杂点

  2. 太棒啦,已经 star 啦~

    1. 哇,感谢豆豆! :flower-flower:

  3. 一岁 一岁

    :dinosaur-agree:

  4. […] 其相关流程及代码部分见此篇文章:ESP32Demo:智能垃圾桶 – Echo (liveout.cn) […]

添加新评论