本文最后更新于 714 天前,其中的信息可能已经过时,如有错误请发送邮件到 echobydq@gmail.com
此篇文章为有关 ESP32
的学习期间的代码记录,并且加上了自己的注释,非教学文章。
使用开发板全称 ESP32 DEVKILTv1(devkitv1)
, 搭载芯片为 ESP32D0WDQ6
,使用软件为 Arduino
。
如果是小白并且想要学习单片机相关知识,建议移步此篇文章:51 单片机入门教程 (上篇)(代码+个人理解) – Echo (liveout.cn)
此篇文章参考教程视频:小鱼创意的个人空间哔哩哔哩 bilibili
GitHub 代码样例链接:https://github.com/PGwind/ESP32code
开发板详细讲解:ESP32 DEVKILTv1 (devkitv1) 开发板全解析
| int ledPin = 2; |
| |
| void setup() { |
| |
| pinMode(ledPin,OUTPUT); |
| } |
| |
| void loop() { |
| |
| digitalWrite(ledPin, HIGH); |
| } |
| int ledPin = 2; |
| |
| void setup() { |
| pinMode(ledPin,OUTPUT); |
| |
| } |
| |
| void loop() { |
| digitalWrite(ledPin, HIGH); |
| delay(2000); |
| digitalWrite(ledPin, LOW); |
| delay(2000); |
| } |
| int ledPin2 = 2; |
| int ledStatus2 = 0; |
| unsigned int prevTime2 = 0; |
| |
| int ledPin4 = 4; |
| int ledStatus4 = 0; |
| unsigned int prevTime4 = 0; |
| |
| void setup() { |
| pinMode(ledPin2, OUTPUT); |
| digitalWrite(ledPin2, HIGH); |
| ledStatus2 = HIGH; |
| prevTime2 = millis(); |
| |
| pinMode(ledPin4, OUTPUT); |
| digitalWrite(ledPin4, HIGH); |
| ledStatus4 = HIGH; |
| prevTime4 = millis(); |
| } |
| |
| void loop() { |
| unsigned int now = millis(); |
| |
| if (now - prevTime2 > 3000) |
| { |
| int status = ledStatus2 == HIGH ? LOW: HIGH; |
| digitalWrite(ledPin2, status); |
| ledStatus2 = status; |
| prevTime2 = now; |
| } |
| |
| if (now - prevTime4 > 1000) |
| { |
| int status = ledStatus4 == HIGH ? LOW: HIGH; |
| digitalWrite(ledPin4, status); |
| ledStatus4 = status; |
| prevTime4 = now; |
| } |
| } |
| int switchPin = 25; //按键所接GPIO口 |
| int ledPin = 4; //LED接口 |
| int ledStatus = 0; //LED目前状态 |
| |
| void setup() { |
| pinMode(switchPin, INPUT_PULLUP);//INPUT_PULLUP上拉,低电平有效,检测到低电平表明按键已经按下 |
| pinMode(ledPin, OUTPUT); |
| digitalWrite(ledPin, HIGH); |
| ledStatus = HIGH; |
| } |
| |
| void loop() { |
| int val = digitalRead(switchPin); //读取开关引脚的电平状态 |
| if (val == LOW) //低电平有效 |
| { |
| ledStatus = !ledStatus; |
| digitalWrite(ledPin, ledStatus); |
| } |
| } |
使用 RBD_ Button
库进行消抖,在 库管理
处进行安装
| #include <RBD_Timer.h> |
| #include <RBD_Button.h> |
| |
| int switchPin = 25; |
| int ledPin = 4; |
| int ledStatus = 0; |
| |
| |
| RBD::Button button(switchPin, INPUT_PULLUP); |
| |
| void setup() { |
| pinMode(ledPin, OUTPUT); |
| button.setDebounceTimeout(20); |
| } |
| |
| void loop() { |
| |
| if (button.onPressed()) |
| { |
| ledStatus = !ledStatus; |
| digitalWrite(ledPin, ledStatus); |
| } |
| } |
LED 控制(LEDC)外围设备主要用于控制 LED 的强度,尽管它也可以用于生成 PWM 信号用于其他目的。它具有 16 个通道,可以生成独立的波形,这些波形可以用于驱动 RGB LED 器件。
| void setup() { |
| int ret = 0; |
| Serial.begin(115200); |
| int ch0 = 0; |
| int gpio4 = 4; |
| ret = ledcSetup(ch0, 5000, 12); |
| |
| delay(200); |
| if (ret == 0) |
| Serial.println("Error Setup"); |
| else |
| Serial.println("Success Setup"); |
| |
| ledcAttachPin(gpio4, ch0); |
| ledcWrite(ch0, pow(2, 11)); |
| |
| } |
| |
| void loop() { |
| |
| |
| } |
每秒钟固定调整占空比 50 次。 T
为呼吸周期,光从灭到最亮经过半个周期 T/2。
半个周期进行 50*T/2
调整占空比
count
表示占空比为 100% 时等分的格子
step
为每次调整时要加上的增量 step = count / (50 * T/2) = 2 * count / (50 * T)
3.2.1 使用 delay()
,呼吸周期偏长
| |
| |
| |
| |
| |
| |
| int gpio4 = 4; |
| int ch1 = 1; |
| int duty = 0; |
| int count = 0; |
| int step = 0; |
| int breathTime = 3; |
| |
| void setup() { |
| ledcSetup(ch1, 1000, 12); |
| count = pow(2, 12); |
| step = 2 * count / (50 * breathTime); |
| ledcAttachPin(gpio4, ch1); |
| } |
| |
| void loop() { |
| ledcWrite(ch1, duty); |
| duty += step; |
| if (duty > count) |
| { |
| duty = count; |
| step = -step; |
| } |
| else if (duty < 0) |
| { |
| duty = 0; |
| step = -step; |
| } |
| delay(20); |
| } |
3.2.2 使用 millis
| int prevTime = 0; |
| int gpio4 = 4; |
| int ch1 = 1; |
| int duty = 0; |
| int count = 0; |
| int step = 0; |
| int breathTime = 3; |
| |
| void setup() { |
| ledcSetup(ch1, 1000, 12); |
| count = pow(2, 12); |
| step = 2 * count / (50 * breathTime); |
| ledcAttachPin(gpio4, ch1); |
| } |
| |
| void loop() { |
| int now = millis(); |
| if (now - prevTime >= 20) |
| { |
| ledcWrite(ch1, duty); |
| duty += step; |
| if (duty > count) |
| { |
| duty = count; |
| step = -step; |
| } |
| else if (duty < 0) |
| { |
| duty = 0; |
| step = -step; |
| } |
| prevTime = now; |
| } |
| } |
使用 AsyncTimer
库进行定时操作,在 库管理
处进行安装。
定时器主要模式:
- 等待多长时间触发一个事件
- 每个多久时间触发一个事件
- 到某个时间点触发一个事件
定时器类型:
- 硬件定时器:ESP32 只有 4 个
- 软件定时器:精度低,数量多
串口定时打印信息
| #include <AsyncTimer.h> |
| |
| AsyncTimer t; |
| |
| void myfun() |
| { |
| Serial.println("the second"); |
| } |
| void setup() { |
| Serial.begin(115200); |
| delay(200); |
| |
| |
| auto id = t.setTimeout([](){ |
| Serial.println("the first"); |
| }, 2000); |
| Serial.print("First:"); |
| Serial.println(id); |
| |
| id = t.setTimeout(myfun, 4000); |
| Serial.print("Second:"); |
| Serial.println(id); |
| } |
| |
| void loop() { |
| t.handle(); |
| } |
| First:62510 |
| Second:36048 |
| the first |
| the second |
| #include <AsyncTimer.h> |
| |
| AsyncTimer t; |
| |
| void myfun() |
| { |
| Serial.println("the second"); |
| } |
| |
| void setup() { |
| Serial.begin(115200); |
| delay(200); |
| |
| |
| auto id = t.setInterval([](){ |
| Serial.println("the first"); |
| }, 2000); |
| Serial.print("First:"); |
| Serial.println(id); |
| |
| id = t.setInterval(myfun, 4000); |
| Serial.print("Second:"); |
| Serial.println(id); |
| } |
| |
| void loop() { |
| t.handle(); |
| } |
| the first |
| the first |
| the second |
LED 灯刚启动以 1s 周期进行闪烁,按键按下去后在 1s 和 3s 的周期进行切换
| |
| #include <RBD_Button.h> |
| #include <AsyncTimer.h> |
| |
| int switchPin = 25; |
| int ledPin = 4; |
| int ledStatus = HIGH; |
| int t = 1; |
| |
| |
| RBD::Button button(switchPin, INPUT_PULLUP); |
| |
| AsyncTimer timer; |
| int taskId = 0; |
| |
| void ChangeLedStatus() |
| { |
| ledStatus = !ledStatus; |
| digitalWrite(ledPin, ledStatus); |
| } |
| |
| void setup() { |
| pinMode(ledPin, OUTPUT); |
| digitalWrite(ledPin, HIGH); |
| button.setDebounceTimeout(20); |
| |
| taskId = timer.setInterval(ChangeLedStatus, t*1000); |
| } |
| |
| void loop() { |
| timer.handle(); |
| |
| if (button.onPressed()) |
| { |
| t = t == 1?3:1; |
| timer.changeDelay(taskId, t*1000); |
| } |
| } |
停止单个定时任务:
cancel(intervalOrTimeoutId)
,intervalOrTimeoutid 即定时任务的编号
停止多个定时任务:
cancelAll(includeIntervals)
,参数默认值为 true。
true:取消所有定时任务 fasle:只取消单次定时任务
改变定时任务周期:
changeDelay(intervalOrTimeoutId, delaylnMs)
,参数分别为定时任务的编号 和 新的超时时间 (ms)
重置定时任务:
reset(intervalOrTimeoutId)
,只能重置还没有停止的定时任务,重置完从 0 重新计时
额外延时一个定时任务:
delay(intervalOrTimeoutId, delaylnMs)
,参数分别为定时任务的编号 和 额外延时时间 (ms)
获取定时任务剩余时间:
getRemaining(intervalOrTimeoutId)
,获取指定定时任务本轮还剩多久时间超时
| unsigned long remaining = getRemaining(timeoutId); |
| void setup() { |
| Serial.begin(115200); |
| analogReadsolution(12); |
| |
| |
| |
| |
| |
| |
| } |
| |
| void loop() { |
| int analogValue = analogRead(2); |
| int analogVolts = analogReadMilliVolts(2); |
| |
| Serial.printf("ADC analog value = %d\n", analogValue); |
| Serial.printf("ADC millivolts value = %d\n", analogVlots); |
| |
| delay(100); |
| } |
| |
| |
| |
| #include <AsyncTimer.h> |
| |
| int pmPin = 32; |
| int ledPin = 4; |
| int ch0 = 0; |
| |
| AsyncTimer timer; |
| int taskId = 0; |
| |
| void ChangeLedLightness() |
| { |
| int val = analogRead(pmPin); |
| Serial.printf("%d:", val); |
| |
| auto vol = analogReadMilliVolts(pmPin); |
| Serial.println(vol); |
| |
| int duty = val / 4095.0 * 1024; |
| ledcWrite(ch0, duty); |
| } |
| |
| void setup() { |
| Serial.begin(115200); |
| analogReadResolution(12); |
| analogSetAttenuation(ADC_11db); |
| |
| ledcSetup(ch0, 1000, 10); |
| ledcAttachPin(ledPin, ch0); |
| |
| taskId = timer.setInterval(ChangeLedLightness, 20); |
| } |
| |
| void loop() { |
| timer.handle(); |
| } |
主机
| |
| #include <Wire.h> |
| |
| #define I2C_DEV_ADDR 0x55 |
| |
| uint32_t i = 0; |
| |
| void setup() { |
| Serial.begin(115200); |
| Serial.setDebugOutput(true); |
| Wire.begin(); |
| } |
| |
| void loop() { |
| delay(5000); |
| |
| Wire.beginTransmission(I2C_DEV_ADDR); |
| Wire.printf("Hello World! %u", i++); |
| uint8_t error = Wire.endTransmission(true); |
| Serial.printf("endTransmission:%u\n", error); |
| |
| uint8_t bytesReceived = Wire.requestFrom(I2C_DEV_ADDR, 16); |
| Serial.printf("requestFrom:%u\n", bytesReceived); |
| if ((bool)bytesReceived) { |
| uint8_t temp[bytesReceived]; |
| Wire.readBytes(temp, bytesReceived); |
| log_print_buf(temp, bytesReceived); |
| } |
| } |
从机
| |
| #include "Wire.h" |
| |
| #define I2C_DEV_ADDR 0x55 |
| |
| uint32_t i = 0; |
| |
| |
| |
| |
| |
| void onRequest(){ |
| Wire.print(i++); |
| Wire.print("Packets."); |
| Serial.println("onRequest"); |
| } |
| |
| |
| void onReceive(int len){ |
| Serial.printf("onReceived[%d]: ", len); |
| while (Wire.available()){ |
| Serial.write(Wire.read()); |
| } |
| Serial.println(); |
| } |
| |
| void setup() { |
| Serial.begin(115200); |
| Serial.setDebugOutput(true); |
| Wire.onReceive(onReceive); |
| Wire.onRequest(onRequest); |
| Wire.begin((uint8_t)I2C_DEV_ADDR); |
| |
| |
| #if CONFIG_IDF_TARGET_ESP#@ |
| char message[64]; |
| snprintf(message, 64, "%u Packets.", i++); |
| Wire.slaveWrite((uint8_t *)message, strlen(message)); |
| #endif |
| } |
| |
| void loop() { |
| |
| } |
主机每秒 2 秒向从机发送递增的数字,
从机在收到主机的数据后 LED 闪烁 0.5 秒,并在收到的数字后加上 OK 字符发送给主机
主机收到从机发来的数据后打印在串口上
主机
主机程序使用了 Wire
库进行 I2C 通信。在 setup
函数中,初始化串口并加入 I2C 总线。在 loop
函数中,通过 Wire.beginTransmission
和 Wire.endTransmission
向从机发送数字字符串,并通过 Wire.requestFrom
从从机接收数据。收到数据后,将其打印在串口上。
| |
| |
| |
| |
| |
| |
| |
| #include <Wire.h> |
| |
| int num = 1; |
| int address = 33; |
| |
| void setup() { |
| Serial.begin(115200); |
| |
| if (Wire.begin()) |
| Serial.println("I2C Success"); |
| else |
| Serial.println("I2C Failed"); |
| } |
| |
| void loop() { |
| char tmp[32]; |
| itoa(num++, tmp, 10); |
| |
| Wire.beginTransmission(address); |
| Wire.write(tmp); |
| int ret = Wire.endTransmission(); |
| if (ret != 0) |
| { |
| Serial.printf("Send failed:%d\r\n", ret); |
| return; |
| } |
| |
| delay(100); |
| |
| |
| |
| |
| |
| |
| |
| int len = Wire.requestFrom(address, 32); |
| if (len > 0) |
| { |
| |
| Serial.print("Receive data size:"); |
| Serial.println(len); |
| |
| Wire.readBytes(tmp, 32); |
| Serial.println(tmp); |
| |
| |
| for (int i=0; i<32; i++) |
| { |
| Serial.printf("%2x, ", tmp[i]); |
| if (i % 8 == 7) |
| Serial.println(); |
| } |
| Serial.println(); |
| } |
| delay(1900); |
| } |
从机
从机程序使用了 Wire
库进行 I2C 通信,并使用 AsyncTimer
库来控制 LED 闪烁。在 onReceive
函数中,当接收到数据时,将数据存储到缓冲区 buf
中,并让 LED 闪烁。在 onRequest
函数中,向主机发送带有 "OK" 字符的数据。
| |
| |
| |
| |
| |
| |
| |
| #include <Wire.h> |
| #include <AsyncTimer.h> |
| |
| char buf[32]; |
| int ledPin = 4; |
| |
| AsyncTimer timer; |
| |
| void onReceive(int len) { |
| |
| if (len > 0) |
| { |
| |
| int sz = Wire.readBytes(buf, 32); |
| if (sz > 0) |
| { |
| buf[sz] = 0; |
| digitalWrite(ledPin, HIGH); |
| |
| |
| timer.setTimeout([](){ |
| digitalWrite(ledPin, LOW); |
| }, 500); |
| } |
| } |
| } |
| |
| void onRequest() { |
| |
| strcat(buf, "OK"); |
| Wire.write(buf); |
| Wire.write(0); |
| } |
| void setup() { |
| Serial.begin(115200); |
| pinMode(ledPin, OUTPUT); |
| Wire.onReceive(onReceive); |
| Wire.onRequest(onRequest); |
| Wire.begin(33); |
| } |
| |
| void loop() { |
| timer.handle(); |
| } |
需要下载 LiquidCrystal_I2C
库,地址为:https://github.com/mrkaleArduinoLib/LiquidCrystal_I2C。
主要用到的文件为 LiquidCrystal_I2C.h
和 LiquidCrystal_I2C.cpp
这两个文件
使用时移动到项目文件根目录并调用
| #include <Arduino.h> |
| #include <Wire.h> |
| #include "LiquidCrystal_I2C.h" |
| |
| LiquidCrystal_I2C lcd(0x27, 16, 2); |
| |
| void setup() { |
| lcd.init(); |
| lcd.backlight(); |
| lcd.print("Hello World!"); |
| |
| lcd.setCursor(0, 1); |
| lcd.print("I am a fish, I am a fish, I am a fish."); |
| |
| |
| lcd.setCursor(2, 1); |
| lcd.write('A'); |
| lcd.write('M'); |
| |
| lcd.clear(); |
| |
| |
| for (int i = 0; i < 100; i++) { |
| lcd.scrollDisplayLeft(); |
| delay(1000); |
| } |
| } |
| |
| void loop() { |
| |
| } |
中断服务程序要求:
- 要尽量地短,减少执行时间
- 不要使用
delay()
函数
- 不要使用
Serial
打印
- 和主程序共享的变量要加_上
volatile
关键字
- 不要使用
millis()
函数,它的值将不会增长
- 可以使用
micros
函数来获取时间
- 外部中断最高频率手册没说,但达到几 M 是没有问题的
IRAM_ATTR
是一个 ESP32 的特殊属性,用于指定函数在 IRAM(内部 RAM)中运行,而不是默认的闪存(Flash)中运行。在 ESP32 中,IRAM 是位于处理器内部的高速随机访问存储器,执行速度更快。
使用 IRAM_ATTR
属性可以将函数加载到 IRAM 中,从而提高函数的执行速度和响应性能。在中断服务程序(ISR)中使用 IRAM_ATTR
属性可以确保 ISR 在最短的时间内得到执行,从而更及时地响应中断事件。
因此,IRAM_ATTR
修饰符常常用于将中断服务程序(ISR)函数加载到 IRAM 中,以提高性能。
| const byte LED = 4; |
| const byte BUTTON = 25; |
| |
| |
| IRAM_ATTR void switchPressed() |
| { |
| |
| if (digitalRead(BUTTON) == HIGH) |
| digitalWrite(LED, HIGH); |
| else |
| digitalWrite(LED, LOW); |
| } |
| |
| void setup() { |
| pinMode(LED, OUTPUT); |
| pinMode(BUTTON, INPUT_PULLUP); |
| |
| attachInterrupt(digitalPinToInterrupt(BUTTON), switchPressed, CHANGE); |
| } |
| |
| void loop() { |
| |
| } |
临界区
是一段代码片段,用于在多任务环境下保护共享资源,以确保对资源的访问不会被并发任务中断或干扰。临界区的作用是提供一种互斥机制,使得同一时间只有一个任务可以访问共享资源,避免并发访问导致的数据竞争和不一致性。
| #include "LiquidCrystal_I2C.h" |
| |
| volatile unsigned long raiseTime = 0; |
| volatile unsigned long fallTime = 0; |
| volatile double duty = 0; |
| volatile double fre = 0; |
| |
| int pwmPin = 27; |
| |
| |
| LiquidCrystal_I2C lcd(0x27, 16, 2); |
| |
| |
| portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; |
| |
| |
| void changeISR() |
| { |
| auto now = micros(); |
| if (digitalRead(pwmPin)) |
| { |
| |
| |
| |
| |
| portENTER_CRITICAL_ISR(&mux); |
| auto total = now - raiseTime; |
| fre = 1e6 / (double)total; |
| auto h = fallTime - raiseTime; |
| duty = h / (double)total; |
| portEXIT_CRITICAL_ISR(&mux); |
| raiseTime = now; |
| } |
| else |
| { |
| fallTime = now; |
| } |
| } |
| |
| void setup() { |
| lcd.init(); |
| lcd.backlight(); |
| lcd.setCursor(0, 0); |
| lcd.print("fre: "); |
| lcd.setCursor(0, 1); |
| lcd.print("duty: "); |
| pinMode(pwmPin, INPUT); |
| attachInterrupt(digitalPinToInterrupt(pwmPin), changeISR, CHANGE); |
| } |
| |
| void loop() { |
| delay(1000); |
| |
| portENTER_CRITICAL(&mux); |
| double f = fre; |
| double d = duty; |
| portEXIT_CRITICAL(&mux); |
| |
| lcd.setCursor(5, 0); |
| lcd.print(f); |
| lcd.setCursor(6, 1); |
| lcd.print(d); |
| } |
分频数越大,周期越长,频率越低。分频数最大是 65525
流程
:初始化 -> 绑定 ISR -> 设置触发 ISR 的计数值 -> 启动定时器
| #include <esp32-hal-timer.h> |
| |
| hw_timer_t *timer = NULL; |
| |
| void IRAM_ATTR timerISR() { |
| |
| } |
| |
| void setup() { |
| timer = timerBegin(0, 80, true); |
| timerAttachInterrupt(timer, &timerISR, true); |
| timerAlarmWrite(timer, 1000000, true); |
| timerAlarmEnable(timer); |
| |
| } |
| |
| void loop() { |
| |
| } |
每 1s 打印一次当前迭代数和时间
| |
| #include <esp32-hal-timer.h> |
| volatile int count = 0; |
| volatile unsigned long tim = 0; |
| |
| hw_timer_t *timer1 = NULL; |
| portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; |
| |
| |
| void IRAM_ATTR onTimer1() { |
| portENTER_CRITICAL_ISR(&timerMux); |
| count ++; |
| tim = micros(); |
| portEXIT_CRITICAL_ISR(&timerMux); |
| } |
| |
| void setup() { |
| Serial.begin(115200); |
| |
| timer1 = timerBegin(0, 80, true); |
| |
| timerAttachInterrupt(timer1, onTimer1, true); |
| |
| timerAlarmWrite(timer1, 1000000, true); |
| |
| timerAlarmEnable(timer1); |
| } |
| |
| void loop() { |
| portENTER_CRITICAL(&timerMux); |
| auto c = count; |
| auto t = tim; |
| portEXIT_CRITICAL(&timerMux); |
| |
| Serial.println(c); |
| Serial.println(t); |
| } |
loop() 函数
中的 portENTER_CRITICAL(&timerMux)
会启用自旋锁,并且禁用掉了 CPU 的中断。
loop () 函数执行速度很快,中断被屏蔽时间会非常长,外部如果有两个或以上中断进来无法及时检测到。
想要解决这个问题,这时候就需要 二值信号量
了。
| |
| #include <esp32-hal-timer.h> |
| volatile int count = 0; |
| volatile unsigned long tim = 0; |
| volatile SemaphoreHandle_t timerSemaphore; |
| |
| hw_timer_t *timer1 = NULL; |
| portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; |
| |
| |
| void IRAM_ATTR onTimer1() { |
| portENTER_CRITICAL_ISR(&timerMux); |
| count ++; |
| tim = micros(); |
| portEXIT_CRITICAL_ISR(&timerMux); |
| |
| |
| |
| |
| |
| |
| xSemaphoreGiveFromISR(timerSemaphore, NULL); |
| } |
| |
| void setup() { |
| Serial.begin(115200); |
| |
| timerSemaphore = xSemaphoreCreateBinary(); |
| |
| timer1 = timerBegin(0, 80, true); |
| |
| timerAttachInterrupt(timer1, onTimer1, true); |
| |
| timerAlarmWrite(timer1, 1000000, true); |
| |
| timerAlarmEnable(timer1); |
| } |
| |
| void loop() { |
| if (xSemaphoreTake(timerSemaphore, 0) == pdTRUE) |
| { |
| portENTER_CRITICAL(&timerMux); |
| auto c = count; |
| auto t = tim; |
| portEXIT_CRITICAL(&timerMux); |
| |
| Serial.println(c); |
| Serial.println(t); |
| } |
| |
| } |
HC-SR04
模块测量错误的情况:
- 物体体积太小,无法反射超声波
- 物体在探头 15° 范围之外
- 物体表面材质是吸收超声波的,比如毛绒绒的物体
- 物体与探头的夹角不对
| const int trigPin = 4; |
| const int echoPin = 16; |
| |
| void setup() { |
| Serial.begin(115200); |
| delay(200); |
| pinMode(trigPin, OUTPUT); |
| pinMode(echoPin, INPUT); |
| } |
| |
| void loop() { |
| |
| digitalWrite(trigPin, HIGH); |
| delayMicroseconds(15); |
| digitalWrite(trigPin, LOW); |
| |
| |
| auto t = pulseIn(echoPin, HIGH); |
| double dis = t * 0.01715; |
| Serial.print(dis); |
| Serial.println(" cm"); |
| |
| delay(200); |
| } |
此程序阻塞过长,下面将使用中断方式优化
中断测距原理:
- 将
外部中断(change)
附加到 ECHO
的引脚上
- 使用硬件定时器每
500ms
给 Trigger
一个 15us
的脉冲 (1s 测量 2 次)
- 在上升沿中断的时候记当前时间
t1
- 在下降沿中断的时候记当前时间
t2
,并发 信号(Semaphore)
给任务
- Loop 函数在收到信号后获取
t2和t1
的值,并计算出距离
| |
| |
| |
| |
| |
| |
| |
| |
| const int trigPin = 4; |
| const int echoPin = 16; |
| double distance = 0; |
| |
| hw_timer_t *timer1 = NULL; |
| portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; |
| volatile unsigned long startTime = 0; |
| volatile unsigned long endTime = 0; |
| volatile SemaphoreHandle_t semaphore; |
| |
| |
| void IRAM_ATTR ping() |
| { |
| digitalWrite(trigPin, HIGH); |
| delayMicroseconds(15); |
| digitalWrite(trigPin, LOW); |
| } |
| |
| |
| void IRAM_ATTR changeISR() |
| { |
| auto now = micros(); |
| auto state = digitalRead(echoPin); |
| |
| portENTER_CRITICAL_ISR(&mux); |
| if (state) |
| startTime = now; |
| else |
| endTime = now; |
| portEXIT_CRITICAL_ISR(&mux); |
| |
| |
| if (!state) |
| xSemaphoreGiveFromISR(semaphore, NULL); |
| } |
| |
| |
| void setup() { |
| pinMode(trigPin, OUTPUT); |
| pinMode(echoPin, INPUT); |
| Serial.begin(115200); |
| |
| semaphore = xSemaphoreCreateBinary(); |
| |
| |
| timer1 = timerBegin(0, 80, true); |
| timerAttachInterrupt(timer1, ping, true); |
| timerAlarmWrite(timer1, 500000, true); |
| |
| |
| attachInterrupt(digitalPinToInterrupt(echoPin), changeISR, CHANGE); |
| |
| |
| timerAlarmEnable(timer1); |
| |
| } |
| |
| void loop() { |
| if (xSemaphoreTake(semaphore, 0) == pdTRUE) |
| { |
| |
| portENTER_CRITICAL(&mux); |
| auto t = endTime - startTime; |
| portEXIT_CRITICAL(&mux); |
| |
| double dis = t * 0.01715; |
| if (dis < 350) |
| { |
| distance = dis; |
| Serial.print("Distance: "); |
| Serial.print(distance, 1); |
| Serial.println(" cm"); |
| } |
| } |
| } |
库名称为 ESP32Servo
| #include <ESP32Servo.h> |
| |
| Servo servo1; |
| Servo servo2; |
| |
| int minUs = 500; |
| int maxUs = 2500; |
| |
| int servo1Pin = 15; |
| int servo2Pin = 16; |
| int pos = -1; |
| bool up = true; |
| |
| void setup() { |
| ESP32PWM::allocateTimer(1); |
| |
| servo1.setPeriodHertz(50); |
| servo2.setPeriodHertz(50); |
| |
| servo1.attach(servo1Pin, minUs, maxUs); |
| servo2.attach(servo2Pin, minUs, maxUs); |
| |
| } |
| |
| void loop() { |
| if (pos == 181) |
| up = false; |
| else if (pos == -1) |
| up = true; |
| |
| if (up) |
| pos ++; |
| else |
| pos --; |
| |
| servo1.write(pos); |
| servo2.write(180 - pos); |
| |
| |
| |
| |
| delay(15); |
| } |
使用超声波测距配合舵机实现智能垃圾桶,因为懒得弄模型,所以垃圾桶开闭直接用串口打印信息。
其相关流程及代码部分见此篇文章:ESP32Demo: 智能垃圾桶 – Echo (liveout.cn)
| #include<WiFi.h> |
| const char* ssid = "WiFi名称"; |
| const char* password = "WiFi密码"; |
| void setup() { |
| |
| Serial.begin(115200); |
| delay(10); |
| |
| |
| Serial.println(); |
| Serial.print("Connecting to "); |
| Serial.println(ssid); |
| |
| WiFi.begin(ssid, password); |
| |
| while (WiFi.status() != WL_CONNECTED) { |
| |
| delay(500); |
| Serial.print("."); |
| } |
| Serial.println(""); |
| Serial.println("WiFi connected"); |
| |
| } |
| |
| void loop() { |
| |
| } |
大佬考虑玩一下ESP-IDF吗(
文章先断更了~短短一星期烧了两块开发板
博主,想问问单片机怎样好上手一些呢,我想学嵌入式但是没有一个好的学习路线,感觉一下子上手不了
如果想要就业的话推荐就是B站江协科技的51单片机教程,我文章分类嵌入式中有相关文章。
如果是感兴趣,可以学学ESP系列开发板,也就是此篇文章里的链接。
51单片机是单片机中的基础,高校教的科目基本都是51。模电和数电基础不是很需要,我当时学的时候就没有,只会基础电路。当然想要更深究就得会这些了。其中最重要的是C语言基础。
嗯嗯好嘞,我学的刚好就是江协的,加油