ESP32网络摄像头平台

组装带有网络摄像头的移动平台的想法几乎是自发出现的。我希望在不起眼的家庭自动化设施中使用IP摄像机之类的东西。在这里,与其说价格或质量,不如说它是一个创造性的实验。这样的各种DIY文章和项目都得到了启发。



组装的结构看起来像这样



图片



组件



底座是可移动的两层机器人平台 汽车底盘2WD迷你套件



图片



平台尺寸:135 mm x 135 mm x 80 mm



驱动器是两个带变速箱的标准摩托轮和一个带光栅盘的直流电动机,用于速度传感器:



  • 额定电流:最大250mA 在3.6 V电压下
  • 扭矩800克/厘米(在6V电压下)
  • 电源电压:6-8 V
  • 空载速度:170 rpm(在3.6 V下)
  • 齿轮比:1:48
  • 车轴两侧都伸出
  • 轮轴直径:5毫米
  • 尺寸:64x20x20毫米
  • 重量:26g




MX1508模块被选电机驱动器。

可以在此处阅读有关模块的



图片



技术参数:



  • 电源电压:2-10 V
  • 每个通道的工作驱动器:1.5 A(峰值电流2.5 A,不超过10秒)
  • 逻辑输入:5V
  • 尺寸:24.7 x 21 x 0.5mm




对于IP摄像机的水平和垂直移动,已经选择了流行的SG90 2kg伺服电机。 以下规格在制造商的网站上提供:



图片







  • 重量:9g
  • 尺寸:23×12.2x29mm
  • 失速扭矩:1.8kg / cm(4.8v)
  • 齿轮类型:POM齿轮组
  • 工作速度:0.1秒/ 60度(4.8v)
  • 工作电压:4.8v
  • 温度范围:0℃_ 55℃
  • 死区宽度:1us
  • 电源:通过外部适配器
  • 伺服线长:25厘米
  • 伺服插头:JR(适合JR和Futaba)




已为网络摄像头选择了FPV支架套件



图片



在线商店中的支架说明:“ FPV允许您将FPV摄像头定向在3个平面上。简单的连接和操作将使您能够快速组装平台并将其连接到控制器或飞行控制器。与EMAX 9g ES08A小型伺服或SG90伺服一起使用(进行了一些修改)。

“进行一些修改”-应该考虑到这一点,必须使用字面意义上的文件来修改该集合。但是对于一个3美元的DIY来说,那算什么了。有些人抱怨说,即使是修订版也无济于事,在我看来,舵机的尺寸也不适合所有规则。两个SG90滑轨用于水平和垂直移动摄像机。还考虑了在3D打印机上进行设计和打印的选项,但到目前为止,我止步于此支架。



基于ESP32 CAM的IP摄像机



图片



如前所述:“ ESP32中的I2S子系统还提供了一条直接连接到RAM的高速总线,用于直接内存访问。简而言之,您可以配置ESP32 I2S子系统在硬件控制下发送或接收并行数据。”

那些。您可以将I2S ESP32接口配置为在硬件控制下发送或接收并行数据,该控制是为连接相机而实现的。该板由Seeed Studio开发在这里的价格为9.90美元,但在我们的广播商店中,它们的售价为8美元,显然不仅Seeed Studio可以生产它们。



技术数据:



  • 最小的802.11b / g / n Wi-Fi BT SoC模块
  • 低功耗32位CPU,也可以为应用处理器服务
  • 时钟速度高达160MHz,摘要计算能力高达600 DMIPS
  • 内置520 KB SRAM,外部4MPSRAM
  • 支持UART / SPI / I2C / PWM / ADC / DAC
  • 支持OV2640和OV7670相机,内置闪光灯。
  • 支持图片WiFI上传
  • 支持TF卡
  • 支持多种睡眠模式。
  • 嵌入式Lwip和FreeRTOS
  • 支持STA / AP / STA + AP操作模式
  • 支持Smart Config / AirKiss技术
  • 支持串行端口本地和远程固件升级(FOTA)




动力源



很长时间以来,该平台一直无法通过自主电源进行控制,而无需充电。因此,选择了具有USB输出和一个插槽的2A 18650电源模块作为源。



图片



规格:

  • 电池类型:18650锂离子电池(无保护)
  • 充电器电压:5V至8V
  • 输出电压
  • 3V-直接从电池通过保护装置
  • 5V-通过升压转换器。
  • 最大输出电流:
  • 输出3V-1A
  • 输出5V-2A
  • 最大充电电流:1A
  • 输入连接器类型:微型USB
  • 输出连接器类型:USB-A
  • 5V消耗器-来自过载和短路
  • 尺寸图
  • PCB:29.5 x 99.5 x 19毫米
  • 整个装置:30 x 116 x 20mm




选择ESP-WROOM-32作为主要控制器,



图片



在前面我已经详细介绍了ESP32的特性。以下是该模块的基本特征:

  • Xtensa LX6 32位双核微处理器,最高240 MHz
  • 闪存:4 MB
  • 无线通信Wi-Fi 802.11b / g / n高达150 Mb / s,蓝牙4.2 BR / EDR / BLE
  • 支持STA / AP / STA + AP模式,内置TCP / IP堆栈
  • GPIO 32(UART,SPI,I2C,I2S接口,PWM,SD控制器,电容式触摸,ADC,DAC等
  • 电源:通过microUSB连接器(CP2102转换器)或输出
  • 引脚间距:2.54mm(可插入面包板)
  • 面板尺寸:5.2 x 2.8厘米




两个光学编码器“NONAME”用于 作为速度传感器用于计数马达轮的光栅盘的旋转脉冲。



图片



特性:

  • 电源电压:3.3V-5V
  • 传感器凹槽宽度:6毫米;
  • 输出类型:模拟和数字
  • 指示灯:输出状态




流行的超声波测距仪HC-SR04采用测量距离。



图片



特点:

  • 供电电压:5V
  • 静音模式下的功耗:2mA
  • 运行时消耗:15 mA
  • 距离范围:2-400厘米
  • 有效视角:15
  • 工作视角:30°




软件实施



第一步是了解并刷新ESP32 CAM模块。

Harba, 此处, 此处和其他资源上介绍了使用该模块的说明

大多数文章描述了使用Arduino IDE的简单刷新过程在大多数情况下,这已经足够,并且在开始时,此选项也很好。



图片



在无线电商店中,ESP32-CAM模块与OV2640相机一起出售,因此需要在草图中进行一些小的更改:



// Select camera model
//#define CAMERA_MODEL_WROVER_KIT
//#define CAMERA_MODEL_ESP_EYE
//#define CAMERA_MODEL_M5STACK_PSRAM
//#define CAMERA_MODEL_M5STACK_WIDE
#define CAMERA_MODEL_AI_THINKER




并指定Wi-Fi接入点的SSID和密码



const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";




在我的情况下,网络摄像机可以工作的条件之一是能够通过Keenetic代理服务器传输视频流。我正在使用Keenetik Viva家用路由器。 KeenDNS服务将域名提供给家庭Web资源。但是令我惊讶的是,第一次尝试失败了。尝试通过Internet进行远程访问时,收到错误消息“标头字段太长,服务器无法解释”。随着这个问题我第一次遇到。解决此问题的方法是更改​​CONFIG_HTTPD_MAX_REQ_HDR_LEN配置,例如



#define CONFIG_HTTPD_MAX_REQ_HDR_LEN 2048




在Arduino IDE ESP32的情况下,模块已被编译并显示为静态库,它们位于Windows沿路径-%userprofile%\ AppData \ Local \ Arduino15 \ packages \ esp32 \ hardware \ esp32 \ 1.0.4 \ tools \ sdk \

Just更改标题中的参数将无济于事。

也就是说,要更改配置,我们需要重新编译ESP-IDF库。

解决方案是克隆github.com/espressif/esp-who项目。在包含示例的目录中,我们找到了camera_web_server项目,更改了最大标头长度的参数,并且不要忘记指定Wi-Fi连接设置



图片



。要编译该项目,我们必须安装另一个复选框-ESP32支持数组'rtc_gpio_desc'



图片



成功编译和加载项目后,在浏览器中转到相应的IP地址,并使用网络摄像头界面转到该页面。



图片



该接口与Arduino示例类似,但是增加了一些功能。



我对原始的app_httpd.c文件做了一些小的更改,以控制来自Web界面的GPIO_NUM_2引脚信号。尽管模块的描述讨论了为SD卡的需要使用引脚,但是我不使用它,因此可以使用这些引脚。



void app_httpd_main()
{
	gpio_set_direction(GPIO_NUM_2, GPIO_MODE_OUTPUT);

static esp_err_t cmd_handler(httpd_req_t *req)
{
.......
// 736
else if(!strcmp(variable, "gpio2")) {
    		if (val == 0)
                gpio_set_level(GPIO_NUM_2, 0);
            else
                gpio_set_level(GPIO_NUM_2, 1);
    	}




对于远程控制,我在运行于Raspberry pi上的Node-Red上做了一个简单的面板。



图片



我们设法将视频流图像嵌入到模板节点中:



<iframe 
    src="http://192.168.1.61"
    width="300" height="300">
</iframe>




这里有一点很重要:必须嵌入http,对于https,Content-Security-Policy会出现问题。如果在这种情况下出现问题,则可以尝试添加标题:



<script>
    var meta = document.createElement('meta');
    meta.httpEquiv = "Content-Security-Policy";
    meta.content = "default-src * 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline';";
document.getElementsByTagName('head')[0].appendChild(meta);
</script>




更改固件后,要控制ESP32-CAM模块的GPIO_NUM_2管脚,应执行以下http GET请求:



http://192.168.1.61/control?var=gpio2&val=1 // 0




在面板界面上,这是唤醒开关,在工作线程



图片



中,请求功能如下所示:



var newMsg = {}
var i = msg.payload ? 1 : 0;
newMsg.query = "control?var=gpio2&val=" + i
node.send(newMsg)




http请求节点的设置:



图片



其他参数和状态通过MQTT传输



Wi-Fi和MQTT连接



我也将使用Arduino框架作为示例,因为我也对此进行了实验。但最后,我在ESP-IDF上有一个有效的应用程序。



#include <WiFi.h>标头



Arduino框架的Wi-Fi连接功能
void setup_wifi()
{
  Serial.println("Starting connecting WiFi.");
  delay(1000);
  for (int8_t i = 0; i < 3; i++)
  {
    WiFi.begin(ssid, password);
    uint32_t start = millis();
    while (WiFi.status() != WL_CONNECTED && ((millis() - start) < 4000))
    {
      Serial.print(".");
      delay(500);
    }
    if (WiFi.status() == WL_CONNECTED)
    {
      Serial.println("WiFi connected");
      Serial.println("IP address: ");
      Serial.println(WiFi.localIP());
      return;
    }
    else
    {
      Serial.println("Connecting Failed");
      //WiFi.reconnect(); // this reconnects the AP so the user can stay on the page
    }
  }
}






该函数包含一个用于三个迭代的循环,因为 通常在第一次尝试时连接失败,然后无休止地等待WL_CONNECTED状态。也许您仍然可以用另一种方法解决它,但是它可以那样工作。



使用github.com/knolleary/pubsubclient.git库完成了针对Arduino框架的MQTT连接



要使用该库,您需要包含标题#include <PubSubClient.h>



MQTT连接功能
bool setup_mqtt()
{
  if (WiFi.status() == WL_CONNECTED)
  {
    if (!client.connected())
    {
      client.setServer(mqtt_server, 1883);
      client.setCallback(callback);
    }
    Serial.print("Connecting to MQTT server ");
    Serial.print(mqtt_server);
    Serial.println("...");
    String clientId = "ESP32_car_client";
    if (client.connect(clientId.c_str()))
    {
      Serial.println("Connected to MQTT server ");
      //subscribing to topics
      client.subscribe("esp32/car/#");
      client.subscribe("esp32/camera/#");
      return true;
    }
    else
    {
      Serial.println("Could not connect to MQTT server");
      return false;
    }
  }
  return false;
}






首先,我们检查是否已连接到Wi-Fi,然后连接到代理client.setServer(mqtt_server,1883);



并设置回调函数client.setCallback(callback);



MQTT回调函数
void callback(char *topic, byte *payload, unsigned int length)
{
  Serial.println("Message arrived ");
  memset(payload_buf, 0, 10);
  for (int i = 0; i < length; i++)
  {
    payload_buf[i] = (char)payload[i];
  }

  command_t mqtt_command = {
      .topic = topic,
      .message = payload_buf};
  xQueueSend(messageQueue, (void *)&mqtt_command, 0);
}






如果连接成功,请订阅主题



client.subscribe("esp32/car/#");
client.subscribe("esp32/camera/#");




在某些情况下,MQTT连接被丢弃,因此在定期轮询任务中添加了检查。



定期轮询任务
void pollingTask(void *parameter)
{
  int32_t start = 0;

  while (true) {
    if (!client.connected()) {
      long now = millis();
      if (now - start > 5000) {
        start = now;
        // Attempt to reconnect
        if (setup_mqtt()) {
          start = 0;
        }
      }
    }
    else {
      client.loop();
      int val = digitalRead(WAKEUP_PIN);
      if (val == LOW) {
        Serial.println("Going to sleep now");
        esp_deep_sleep_start();
      }
    }
    vTaskDelay(100 / portTICK_PERIOD_MS);
  }
  vTaskDelete(NULL);
}






上一篇文章介绍了使用ESP-IDF连接到Wi-FI和MQTT的示例。在使用ESP-IDF



情况下,连接Wi-Fi和MQTT时没有任何故障。在esp_err_t mqtt_event_handler函数(esp_mqtt_event_handle_t事件)中处理来自MQTT主题的数据时,有一个细微差别:当事件类型为MQTT_EVENT_DATA时,您应考虑event-> topic_len和event-> data_len参数,并获取主题名称和正确长度的数据,否则我们将获得垃圾回收名称和数据。为此,我们可以创建缓冲区数组或动态分配内存(然后释放它),然后复制数据,例如



strncpy(topic, event->topic, event->topic_len);
strncpy(data, event->data, event→data_len);




使用esp_mqtt_client_publish函数将数据发送到主题



esp_mqtt_client_publish(client, topics[i], topic_buff[i], 0,0,0);




HC-SR04超声波传感器数据处理



HC-SR04是用于设计微控制器设备的廉价且流行的传感器。像往常一样,Internet上有很多有关此主题的材料:此处此处。也可以在此处查看说明,在此处查看简短的数据表

简而言之,要开始测量距离,您需要向Trig引脚施加持续时间为10μs的高信号。这将启动传感器以发送40 kHz超声脉冲的8个周期,并等待反射的超声脉冲。当换能器检测到来自接收器的超声信号时,它将回声输出设置为高电平并延迟与距离成比例的周期(宽度)。要计算距离,您需要计算公式:



distance = duration * 340 / = duration * 0.034 /,



340 m / s-声音在空气中传播的速度。



图片



在Arduino框架中,使用pulseIn函数可以找到以μs

为单位的脉冲宽度,对于ESP-IDF,有一个ESP-IDF组件库项目其中也包含用于HC-SR04的超声波组件。



样例代码
esp_err_t ultrasonic_measure_cm(const ultrasonic_sensor_t *dev, uint32_t max_distance, uint32_t *distance)
{
    CHECK_ARG(dev && distance);

    PORT_ENTER_CRITICAL;

    // Ping: Low for 2..4 us, then high 10 us
    CHECK(gpio_set_level(dev->trigger_pin, 0));
    ets_delay_us(TRIGGER_LOW_DELAY);
    CHECK(gpio_set_level(dev->trigger_pin, 1));
    ets_delay_us(TRIGGER_HIGH_DELAY);
    CHECK(gpio_set_level(dev->trigger_pin, 0));

    // Previous ping isn't ended
    if (gpio_get_level(dev->echo_pin))
        RETURN_CRITICAL(ESP_ERR_ULTRASONIC_PING);

    // Wait for echo
    int64_t start = esp_timer_get_time();
    while (!gpio_get_level(dev->echo_pin))
    {
        if (timeout_expired(start, PING_TIMEOUT))
            RETURN_CRITICAL(ESP_ERR_ULTRASONIC_PING_TIMEOUT);
    }

    // got echo, measuring
    int64_t echo_start = esp_timer_get_time();
    int64_t time = echo_start;
    int64_t meas_timeout = echo_start + max_distance * ROUNDTRIP;
    while (gpio_get_level(dev->echo_pin))
    {
        time = esp_timer_get_time();
        if (timeout_expired(echo_start, meas_timeout))
            RETURN_CRITICAL(ESP_ERR_ULTRASONIC_ECHO_TIMEOUT);
    }
    PORT_EXIT_CRITICAL;

    *distance = (time - echo_start) / ROUNDTRIP;

    return ESP_OK;
}






注释中有对该算法的解释。在while循环中测量脉冲持续时间,同时Echo引脚上的信号电平为高电平(//得到回波后,进行测量),然后测量距离



*distance = (time - echo_start) / ROUNDTRIP


用于获取距离的系数,以厘米为单位ROUNDTRIP =58。



在Arduino框架中,它看起来更容易



样例代码
#include "ultrasonic.h"

portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
#define PORT_ENTER_CRITICAL portENTER_CRITICAL(&mux)
#define PORT_EXIT_CRITICAL portEXIT_CRITICAL(&mux)

Ultrasonic::Ultrasonic() {
  pinMode(GPIO_NUM_33, OUTPUT);
  pinMode(GPIO_NUM_26, INPUT);
}

uint32_t Ultrasonic::calculateDistance() {
    PORT_ENTER_CRITICAL;
    digitalWrite(GPIO_NUM_33, LOW);
    delayMicroseconds(2);
    digitalWrite(GPIO_NUM_33, HIGH);
    delayMicroseconds(10);
    digitalWrite(GPIO_NUM_33, LOW);
    duration = pulseIn(GPIO_NUM_26, HIGH);
    PORT_EXIT_CRITICAL;
    distance = duration / 58;
    return distance;
}

uint32_t Ultrasonic::getDistance() {
    return distance;
}






曾尝试对ESP32 Arduino项目使用超声波ESP-IDF库,但这种情况在第一个传感器故障之前一直有效。为什么这样,却无法准确找出。传感器故障是脉冲和错误读数的周期性错误计算,在计算得出的数字中,距离似乎超过20,000 cm,在论坛上,他们写道这是由于传感器质量低下造成的。



光学传感器进行速度测量



用于读取脉冲的光学模块基于LM393比较器和插槽传感器。设计用于可装在齿轮箱或电动机轴上的光栅盘。



像往常一样,已经有关于该主题的文章:digitrode.rumirrobo.ruarduino-kit.ru



传感器电路:



图片



在Arduino框架中,我们按以下方式计算速度:

-定义计数器的变量(结构),例如

typedef struct {
  int encoder_pin = ENCODER_PIN; // pulse output from the module
  unsigned int rpm = 0; // rpm reading
  volatile byte pulses = 0; // number of pulses
  unsigned long timeold = 0;
  unsigned int pulsesperturn = 20;
} pulse_t;




然后,在设置功能中,我们必须注册输入引脚并对其进行中断。



pinMode(pulse_struct.encoder_pin, INPUT);
attachInterrupt(pulse_struct.encoder_pin, counter, FALLING);




接下来,计算每分钟的转数



pulse_struct.rpm = 
        (60 * 1000 / pulse_struct.pulsesperturn )/ 
        (1000)* pulse_struct.pulses;




样例代码
void pulseTask(void *parameters) {
  sensor_data_t data;
  data.sensor = OPTICAL_SENSOR;
  portBASE_TYPE xStatus;

   while (true) {
      //Don't process interrupts during calculations
      detachInterrupt(0);
      pulse_struct.rpm = 
        (60 * 1000 / pulse_struct.pulsesperturn )/ 
        (1000)* pulse_struct.pulses;
      pulse_struct.pulses = 0;
      data.value = pulse_struct.rpm;
      //Restart the interrupt processing
      attachInterrupt(0, counter, FALLING);
      Serial.print("optical: ");
      Serial.println(data.value);
     //Sending data to sensors queue
    xStatus = xQueueSend(sensorQueue, (void *)&data, 0);
    if( xStatus != pdPASS ) {
     printf("Could not send optical to the queue.\r\n");
    }
    taskYIELD();
    vTaskDelay(1000 / portTICK_PERIOD_MS);
   }
}






为此,可以在ESP-IDF中使用一篇文章中描述的硬件计数器PCNT



正在处理的任务的样本代码
typedef struct {
      uint16_t delay; //delay im ms
      int pin;
      int ctrl_pin;
      pcnt_channel_t channel;
      pcnt_unit_t unit;
      int16_t count;
} speed_sensor_params_t;

void pulseTask(void *parameters) {
  sensor_data_t data_1;
  sensor_data_t data_2;
  data_1.sensor = OPTICAL_SENSOR_1;
  data_2.sensor = OPTICAL_SENSOR_2;
  portBASE_TYPE xStatus;

  speed_sensor_params_t params_1 = {
      .delay = 100,
      .pin = ENCODER_1_PIN,
      .ctrl_pin = GPIO_NUM_0,
      .channel = PCNT_CHANNEL_0,
      .unit = PCNT_UNIT_0,
      .count = 0,
  };
    ESP_ERROR_CHECK(init_speed_sensor(&params_1));

    speed_sensor_params_t params_2 = {
      .delay = 100,
      .pin = ENCODER_2_PIN,
      .ctrl_pin = GPIO_NUM_1,
      .channel = PCNT_CHANNEL_0,
      .unit = PCNT_UNIT_1,
      .count = 0,
  };
    ESP_ERROR_CHECK(init_speed_sensor(&params_2));

    while(true) {
        data_1.value = calculateRpm(&params_1);
        data_2.value = calculateRpm(&params_2);
        sensor_array[OPTICAL_SENSOR_1] = data_1.value;
        sensor_array[OPTICAL_SENSOR_2] = data_2.value;
        printf("speed 1 = %d\n", data_1.value);
        printf("speed 2 = %d\n", data_2.value);
        xStatus = xQueueSend(sensorQueue, (void *)&data_1, 0);
        xStatus = xQueueSend(sensorQueue, (void *)&data_2, 0);
        if( xStatus != pdPASS ) {
        printf("Could not send optical to the queue.\r\n");
        }
        vTaskDelay(100 / portTICK_PERIOD_MS);
}
}






PWM控制



您可以在developer.alexanderklimovwiki.amperka.ru上了解有关在Arduino上控制伺服驱动器的信息

正如上面的资料所述:“伺服器是一种带有电动机的机构,该电动机可以旋转到给定角度并保持其当前位置。” 实际上,我们正在处理脉冲宽度调制,其中执行器的旋转角度取决于信号脉冲宽度。



图片



对于Arduino框架上的ESP32,您可以使用ESP32Servo



。为此,我们连接头文件



#include <ESP32Servo.h>




创建一个对象



Servo servo_horisontal;




我们指示输出引脚



 servo_horisontal.attach(SERVO_CAM_HOR_PIN);




之后,我们可以写下所需的旋转量值



servo_horisontal.write(value);




使用esp32-hal-ledc.h库完成了Arduino框架上其他类型设备的PWM控制ESP32

微控制器不支持PWM的标准Arduino AnalogWrite()函数。而不是将它们的被功能

提供ledcSetup(信道,频率,resolution_bits) -表示的信道,频率和分辨率

ledcAttachPin(GPIO,信道) -表示的端口和通道

ledcWrite(信道,占空比) -表示PWM信号的信道和占空比

的例子可以看出

如何顾名思义,这些功能最初是为控制LED模块而设计的,但也可用于其他目的。



在ESP-IDF框架中,伺服驱动器的控制与使用MCPWM模块的换向器控制相同,如前一篇文章所述。可以在此处查看MCPWM伺服电机控制的示例



样例代码
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 mcpwm_example_servo_control(void *arg)
{
    uint32_t angle, count;
    //1. mcpwm gpio initialization
    mcpwm_example_gpio_initialize();

    //2. initial mcpwm configuration
    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);    //Configure PWM0A & PWM0B with above settings
    while (1) {
        for (count = 0; count < SERVO_MAX_DEGREE; count++) {
            printf("Angle of rotation: %d\n", count);
            angle = servo_per_degree_init(count);
            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
        }
    }
}






那些。我们需要使用mcpwm_init函数(MCPWM_UNIT_0,MCPWM_TIMER_0和pwm_config)初始化模块;

然后设置角度值

mcpwm_set_duty_in_us(MCPWM_UNIT_0,MCPWM_TIMER_0,MCPWM_OPR_A,角度);



可以在github上找到将MCPWM模块用于不同驱动器类型的示例一篇文章中

还提供了有刷电机控制的示例 应当指出的是,这样的平台是差分控制的非完整的



系统。电动机的性能存在差异,因此必须为其中之一设置软件偏移,以确保速度均匀。您可以通过robotosha.ru robotosha.ru/robotics/robot-motion.html熟悉该理论为了对齿轮电动机进行最佳控制,使用了带有光学传感器形式反馈的PID算法。此处此处介绍了算法的说明

运动方程以及控制算法的描述不在本文讨论范围之内。差分运动学尚未在代码中实现。



睡眠模式



根据文档以及本文中的描述 ESP32可以在不同的电源模式之间切换:

  • 主动模式
  • 调制解调器睡眠模式
  • 浅睡眠模式
  • 深度睡眠模式
  • 休眠方式




下表显示了不同模式下电流消耗的差异。



图片



在GPIO_NUM_13引脚上没有高信号的情况下,我使用了深度睡眠模式



gpio_set_direction(WAKEUP_PIN, GPIO_MODE_INPUT);
esp_sleep_enable_ext0_wakeup(WAKEUP_PIN,1); //1 = High, 0 = Low




在没有外部影响的情况下,我用一个电阻将10k输入上拉至3.3V,尽管可以通过软件实现。在定期轮询的任务中,我检查输入信号的状态



if(!gpio_get_level(WAKEUP_PIN)) {
         printf("Going to sleep now\n");
        esp_deep_sleep_start();
    }




这将完成描述。展示了将ESP32模块与各种外设结合使用的实际示例。还涉及一些软件实现问题以及ESP-IDF和Arduino方法的比较。



All Articles