【Arduino环境下驱动合宙esp32c3单片机基本外设】

简介: 【Arduino环境下驱动合宙esp32c3单片机基本外设】

本教程是参加FastBond2活动主题4 - 测量仪器中的【Arduino环境下驱动合宙esp32c3单片机基本外设】。

围绕FastBond2阶段1——基于ESP32C3开发的简易IO调试设备项目需求开发。

设计目标:

  1. 多种数字和模拟信号的输入输出:用户可以选择不同的输入输出模式,并通过设备的操作界面进行设置。例如,用户可以选择某个GPIO口作为模拟输入引脚,然后通过设备的操作界面设置输入的电压值,以模拟外部信号的输入,达到调试简易传感器读取和执行器输出功能。
  2. 支持PWM输出、舵机控制特性:用户可以选择某个GPIO口作为PWM输出引脚,并通过设备的操作界面设置PWM输出的频率和占空比。用户还可以选择某个GPIO口作为舵机控制引脚,并通过设备的操作界面设置舵机的角度。
  3. 因此系统具有一定的电流输出能力、信号辨识能力和显示交互功能。

1. GPIO调试

查看对应D5LED匹配IO13和蜂鸣器,源码修改如下,即可实现呼吸灯效果

1.1 源码分享

/*
  Fade
  This example shows how to fade an LED on pin 9 using the analogWrite()
  function.
  The analogWrite() function uses PWM, so if you want to change the pin you're
  using, be sure to use another PWM capable pin. On most Arduino, the PWM pins
  are identified with a "~" sign, like ~3, ~5, ~6, ~9, ~10 and ~11.
  This example code is in the public domain.
  https://www.arduino.cc/en/Tutorial/BuiltInExamples/Fade
*/
int led = 13;         // the PWM pin the LED is attached to
int brightness = 0;  // how bright the LED is
int fadeAmount = 5;  // how many points to fade the LED by
// the setup routine runs once when you press reset:
void setup() {
  // declare pin 9 to be an output:
  pinMode(led, OUTPUT);
}
// the loop routine runs over and over again forever:
void loop() {
  // set the brightness of pin 9:
  analogWrite(led, brightness);
  // change the brightness for next time through the loop:
  brightness = brightness + fadeAmount;
  // reverse the direction of the fading at the ends of the fade:
  if (brightness <= 0 || brightness >= 255) {
    fadeAmount = -fadeAmount;
  }
  // wait for 30 milliseconds to see the dimming effect
  delay(30);
}

它使用analogWrite()函数来控制连接到引脚9上的LED的亮度变化。代码使用了一个for循环来逐步改变LED的亮度,直到达到最大或最小亮度为止,然后反转亮度的变化方向。该程序设置了一个延时来允许人眼看到LED逐渐变亮或变暗的效果。

2.2 实验效果

板载LED灯会呈现呼吸效果

2. ADC调试

在文件实例基础中选择AnalogRead

查看对应模拟量端口匹配IO00~01,源码修改如下,即可实现A0、A1端口读取模拟量并打印效果

2.1 源码分享

void setup() {
  // initialize serial communication at 115200 bits per second:
  Serial.begin(115200);
  //set the resolution to 12 bits (0-4096)
  analogReadResolution(12);
}
void loop() {
  // read the analog / millivolts value for pin 2:
  int analogValue = analogRead(0);
  int analogVolts = analogReadMilliVolts(1);
  // print out the values you read:
  Serial.printf("ADC analog value = %d\n",analogValue);
  Serial.printf("ADC millivolts value = %d\n",analogVolts);
  delay(100);  // delay in between reads for clear read from serial
}

在setup()函数中,初始化串口通信模块,设置波特率为115200bps,同时设置模数转换器(Analog-to-Digital Converter,ADC)的分辨率到12位。

在loop()函数中:

  1. 读取从板子的第1个模拟端口(analog pin 1)获得模拟值,并存储在变量analogValue中;
  2. 将该模拟值转换成mV,并存储在变量analogVolts中;
  3. 输出两个值到串口中;
  4. 延时100ms,以防止误读。

2.2 实验效果

IO00接光敏电阻,手掌适当遮罩可发现其变化,可用铅笔触碰IO01,看其变化,打印效果如下

3. WS2812驱动

安装WS2812FX和 FastLED库,打开WS2812FX里面的ws2812fx_custom_FastLED例程,我连接IO18,选择合适数量这里是4

3.1 源码分享

/*
  Demo sketch showing how to use the functions and features of the FastLED
  library to create custom effects. For this example we're going to implement
  Mark Kriegsman's Fire2012 effect from the FastLED examples folder here:
  https://github.com/FastLED/FastLED/tree/master/examples/Fire2012
  The basic idea is to use FastLED to create the LED color data, then
  copy the data to the ws2812fx instance for display.
  Keith Lord - 2018
  LICENSE
  The MIT License (MIT)
  Copyright (c) 2018  Keith Lord 
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:
  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  CHANGELOG
  2018-05-13 initial version
*/
#include "FastLED.h" // be sure to install and include the FastLED lib
#include <WS2812FX.h>
#define NUM_LEDS 4
#define LED_PIN 18
WS2812FX ws2812fx = WS2812FX(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
// declare global parameters used by Fire2012
bool gReverseDirection = false;
void setup() {
  Serial.begin(115200);
  // init WS2812FX to use a custom effect
  ws2812fx.init();
  ws2812fx.setBrightness(255);
  ws2812fx.setColor(BLUE);
  ws2812fx.setSpeed(1000);
  ws2812fx.setMode(FX_MODE_CUSTOM);
  ws2812fx.setCustomMode(myCustomEffect);
  ws2812fx.start();
}
void loop() {
  ws2812fx.service();
}
// in the custom effect run the Fire2012 algorithm
uint16_t myCustomEffect() {
  Fire2012();
  // return the animation speed based on the ws2812fx speed setting
  return (ws2812fx.getSpeed() / NUM_LEDS);
}
/*
 * paste in the Fire2012 code with a small edit at the end which uses the
 * setPixelColor() function to copy the color data to the ws2812fx instance. 
*/
// Fire2012 by Mark Kriegsman, July 2012
// as part of "Five Elements" shown here: http://youtu.be/knWiGsmgycY
// This basic one-dimensional 'fire' simulation works roughly as follows:
// There's a underlying array of 'heat' cells, that model the temperature
// at each point along the line.  Every cycle through the simulation, 
// four steps are performed:
//  1) All cells cool down a little bit, losing heat to the air
//  2) The heat from each cell drifts 'up' and diffuses a little
//  3) Sometimes randomly new 'sparks' of heat are added at the bottom
//  4) The heat from each cell is rendered as a color into the leds array
//     The heat-to-color mapping uses a black-body radiation approximation.
//
// Temperature is in arbitrary units from 0 (cold black) to 255 (white hot).
//
// This simulation scales it self a bit depending on NUM_LEDS; it should look
// "OK" on anywhere from 20 to 100 LEDs without too much tweaking. 
//
// I recommend running this simulation at anywhere from 30-100 frames per second,
// meaning an interframe delay of about 10-35 milliseconds.
//
// Looks best on a high-density LED setup (60+ pixels/meter).
//
//
// There are two main parameters you can play with to control the look and
// feel of your fire: COOLING (used in step 1 above), and SPARKING (used
// in step 3 above).
//
// COOLING: How much does the air cool as it rises?
// Less cooling = taller flames.  More cooling = shorter flames.
// Default 50, suggested range 20-100 
#define COOLING  55
// SPARKING: What chance (out of 255) is there that a new spark will be lit?
// Higher chance = more roaring fire.  Lower chance = more flickery fire.
// Default 120, suggested range 50-200.
#define SPARKING 120
void Fire2012()
{
// Array of temperature readings at each simulation cell
  static byte heat[NUM_LEDS];
  // Step 1.  Cool down every cell a little
    for( int i = 0; i < NUM_LEDS; i++) {
      heat[i] = qsub8( heat[i],  random8(0, ((COOLING * 10) / NUM_LEDS) + 2));
    }
    // Step 2.  Heat from each cell drifts 'up' and diffuses a little
    for( int k= NUM_LEDS - 1; k >= 2; k--) {
      heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3;
    }
    // Step 3.  Randomly ignite new 'sparks' of heat near the bottom
    if( random8() < SPARKING ) {
      int y = random8(7);
      heat[y] = qadd8( heat[y], random8(160,255) );
    }
    // Step 4.  Map from heat cells to LED colors
    for( int j = 0; j < NUM_LEDS; j++) {
      CRGB color = HeatColor( heat[j]);
      int pixelnumber;
      if( gReverseDirection ) {
        pixelnumber = (NUM_LEDS-1) - j;
      } else {
        pixelnumber = j;
      }
// **** modified for use with WS2812FX ****
//    leds[pixelnumber] = color;
      ws2812fx.setPixelColor(pixelnumber, color.red, color.green, color.blue);
// **** modified for use with WS2812FX ****
    }
}

定义了数字灯的数量NUM_LEDS和LED引脚编号LED_PIN。

使用FastLED库中的WS2812FX初始化led控制器,配置颜色模式、亮度、速度等参数。

在loop()函数中,调用service()函数更新led状态。

在myCustomEffect()函数中,运行Fire2012算法并返回动画速度,该速度基于ws2812fx的速度设置。

Fire2012是一个简单的模拟火焰燃烧效果的算法,它包括四个步骤:冷却、上升和扩散热量、随机产生火花和渲染温度到颜色。

在Fire2012()函数中:

  1. 对每个像素点进行降温操作;
  2. 让每个像素点的热量向上传递并扩散;
  3. 随机产生新火花;
  4. 根据热量计算像素点的颜色,并修改ws2812fx的状态。

3.2 实验效果

ws2812会来回显示不同灯光效果

4. 旋转编码器

安装Encoder库,点击例程NoInterrupts

修改源码,端口为IO06/IO07

4.1 源码分享

#define ENCODER_DO_NOT_USE_INTERRUPTS
#include <Encoder.h>
Encoder myEnc(7, 6);
//   avoid using pins with LEDs attached
void setup() {
  Serial.begin(9600);
  Serial.println("Basic NoInterrupts Test:");
}
long position  = -999;
void loop() {
  long newPos = myEnc.read();
  if (newPos != position) {
    position = newPos;
    Serial.println(position);
  }
  // With any substantial delay added, Encoder can only track
  // very slow motion.  You may uncomment this line to see
  // how badly a delay affects your encoder.
  //delay(50);
}

设置串口波特率为9600bps。

在setup()函数中,初始化Serial通信模块,准备用于输出调试信息。

在loop()函数中:

  1. 调用myEnc.read()方法读取编码器的当前位置,得到新的位置值newPos;
  2. 如果newPos与上一时刻的位置值position不同,则更新position为newPos; 输出更新后的position值;
  3. 如果对程序进行任何形式的延迟处理,编码器只能跟踪非常慢的动作。您可以取消此行的注释来查看延迟对您的编码器的影响程度。

4.2 测试效果

打开串口旋转编码器,有打印数值变化

5. SSD1306屏幕驱动

安装 U8g2库,打开FontTest例程,修改SCL为IO05,SDA为IO04

5.1 源码分享

/*
  UpdateArea.ino
  Demonstration for the UpdateDisplayArea command
  Universal 8bit Graphics Library (https://github.com/olikraus/u8g2/)
  Copyright (c) 2016, olikraus@gmail.com
  All rights reserved.
  Redistribution and use in source and binary forms, with or without modification, 
  are permitted provided that the following conditions are met:
  * Redistributions of source code must retain the above copyright notice, this list 
    of conditions and the following disclaimer.
  * Redistributions in binary form must reproduce the above copyright notice, this 
    list of conditions and the following disclaimer in the documentation and/or other 
    materials provided with the distribution.
  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 
  CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 
  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  
*/
#include <Arduino.h>
#include <U8g2lib.h>
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif
/*
  U8g2lib Example Overview:
    Frame Buffer Examples: clearBuffer/sendBuffer. Fast, but may not work with all Arduino boards because of RAM consumption
    Page Buffer Examples: firstPage/nextPage. Less RAM usage, should work with all Arduino boards.
    U8x8 Text Only Example: No RAM usage, direct communication with display controller. No graphics, 8x8 Text only.
*/
// Please UNCOMMENT one of the contructor lines below
// U8g2 Contructor List (Frame Buffer)
// The complete list is available here: https://github.com/olikraus/u8g2/wiki/u8g2setupcpp
// Please update the pin numbers according to your setup. Use U8X8_PIN_NONE if the reset pin is not connected
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ 5, /* data=*/ 4, /* reset=*/ U8X8_PIN_NONE);   // ESP32 Thing, pure SW emulated I2C
// End of constructor list
int16_t offset;       // current offset for the scrolling text
u8g2_uint_t width;      // pixel width of the scrolling text (must be lesser than 128 unless U8G2_16BIT is defined
const char *text = "U8g2";  // scroll this text from right to left
const uint8_t tile_area_x_pos = 2;  // Update area left position (in tiles)
const uint8_t tile_area_y_pos = 3;  // Update area upper position (distance from top in tiles)
const uint8_t tile_area_width = 12;
const uint8_t tile_area_height = 3; // this will allow cour18 chars to fit into the area
const u8g2_uint_t pixel_area_x_pos = tile_area_x_pos*8;
const u8g2_uint_t pixel_area_y_pos = tile_area_y_pos*8;
const u8g2_uint_t pixel_area_width = tile_area_width*8;
const u8g2_uint_t pixel_area_height = tile_area_height*8;
void setup(void) {
  u8g2.begin();
  u8g2.clearBuffer();         // clear the internal memory
  u8g2.setFont(u8g2_font_helvR10_tr); // choose a suitable font
  u8g2.drawStr(0,12,"UpdateDisplayArea"); // write something to the internal memory
  // draw a frame, only the content within the frame will be updated
  // the frame is never drawn again, but will stay on the display
  u8g2.drawBox(pixel_area_x_pos-1, pixel_area_y_pos-1, pixel_area_width+2, pixel_area_height+2);
  u8g2.sendBuffer();          // transfer internal memory to the display
  u8g2.setFont(u8g2_font_courB18_tr); // set the target font for the text width calculation
  width = u8g2.getUTF8Width(text);    // calculate the pixel width of the text
  offset = width+pixel_area_width;
}
void loop(void) {
  u8g2.clearBuffer();           // clear the complete internal memory
  // draw the scrolling text at current offset
  u8g2.setFont(u8g2_font_courB18_tr);   // set the target font
  u8g2.drawUTF8(
    pixel_area_x_pos-width+offset, 
    pixel_area_y_pos+pixel_area_height+u8g2.getDescent()-1, 
    text);                // draw the scolling text
  // now only update the selected area, the rest of the display content is not changed
  u8g2.updateDisplayArea(tile_area_x_pos, tile_area_y_pos, tile_area_width, tile_area_height);
  offset--;               // scroll by one pixel
  if ( offset == 0 )  
    offset = width+pixel_area_width;      // start over again
  delay(10);              // do some small delay
}

5.2 测试效果

上传代码,滑动显示U8g2

6. 双cpu同时工作测试

ESP32-C3 系列芯片,搭载 RISC-V 32 位单核处理器。不过可以跑多线程,下面就是两个线程。程序中避免有过多的延迟函数😘😘😘

6.1 源码分享

int testdata0 = 10;
int testdata1 = 0;
void CpuLoop(void *pvParameters){
  while(1){
    Serial.println("cpu1 "+String(testdata0));
    testdata1 ++;
    if(testdata1 == 10)
    testdata1 = 0;
    delay(2000);
  }
  vTaskDelete(NULL);
}
void setup() {
    Serial.begin(9600);
    xTaskCreatePinnedToCore(CpuLoop,    //具体实现的函数
                            "CPU_LOOP",  //任务名称
                            8192,       //堆栈大小
                            NULL,       //输入参数
                            1,          //任务优先级
                            NULL,       //
                            1           //核心  0\1
                            );
}
void loop() {
  Serial.println("cpu0 "+String(testdata1));
  testdata0 --;
  if(testdata0 == 0)
    testdata0 = 10;
  delay(1000);
}

设置串口波特率为9600bps。

在setup()函数中创建一个名为“CPU_LOOP”的任务,并将其实现的函数appCpuLoop()、堆栈大小为8192、输入参数为空、优先级为1、绑定到核心1上进行运行。

在CpuLoop()函数中,当while循环条件为真时,持续执行循环体内的内容。首先打印出字符串“Cpu1”和变量testdata0相加的结果,然后让testdata1自增1,如果testdata1等于10,则将其值设为0;最后延时2000毫秒后继续下一次循环。

在loop()函数中,打印出字符串“cpu0”和变量testdata1相加的结果,然后让testdata0自减1,如果testdata0等于0,则将其值设为10;最后延时1000毫秒后继续下一次循环。

6.2 测试效果

打开串口显示两个内核运行效果

7. BLE蓝牙和安卓端蓝牙数据交互实验

🔖ESP32低功耗(BLE)蓝牙实验.

🛠调试工具

🔧安卓系统BLE调试工具:BLEAssist

📍APP下载地址:http://www.wch.cn/downloads/BLEAssist_ZIP.html

首先安装BLE调试助手

安装后打开,下拉刷新蓝牙,连接对应蓝牙

在第三项中选择查看和发送数据

7.1 源码分享

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
BLECharacteristic *pCharacteristic;
bool deviceConnected = false;
char BLEbuf[32] = { 0 };
uint32_t cnt = 0;
String message_c;
int i = 0;
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"  // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
class MyServerCallbacks : public BLEServerCallbacks {
  void onConnect(BLEServer *pServer) {
    deviceConnected = true;
  };
  void onDisconnect(BLEServer *pServer) {
    deviceConnected = false;
  }
};
class MyCallbacks : public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic *pCharacteristic) {
    std::string rxValue = pCharacteristic->getValue();
    if (rxValue.length() > 0) {
      Serial.print("------>Received Value: ");
      for (int i = 0; i < rxValue.length(); i++) {
        Serial.print(rxValue[i]);
      }
      Serial.println();
    }
  }
};
void setup() {
  Serial.begin(115200);
  // Create the BLE Device
  BLEDevice::init("ESP32 BLE Test");
  // 创建蓝牙服务器
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());
  // // 创建广播服务的UUID
  BLEService *pService = pServer->createService(SERVICE_UUID);
  // 创建广播服务的UUID
  pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY);
  pCharacteristic->addDescriptor(new BLE2902());
  BLECharacteristic *pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);
  pCharacteristic->setCallbacks(new MyCallbacks());
  // 开始蓝牙服务
  pService->start();
  // 开始广播
  pServer->getAdvertising()->start();
  Serial.println("Waiting a client connection to notify...");
}
void loop() {
  if (deviceConnected) {  //设备连接后,每秒钟发送txValue。
    memset(BLEbuf, 0, 32);
    message_c = "s  " + String(i) + "\r\n";
    i++;
    char *p = const_cast<char *>(message_c.c_str());
    memcpy(BLEbuf, p, 32);
    pCharacteristic->setValue(BLEbuf);
    pCharacteristic->notify();  // Send the value to the app!
    Serial.print("*** Sent Value: ");
    Serial.print(BLEbuf);
    Serial.println(" ***");
  }
  delay(1000);
}

设置串口波特率为115200bps。

在setup()函数中:

  1. 使用BLEDevice类初始化Bluetooth Low Energy设备,并设定其名称为"ESP32 BLE Test";
  2. 创建并返回一个服务器对象,并将其回调函数设置为MyServerCallbacks类型的实例;
  3. 使用BLEServer类创建一个服务,并使用SERVICE_UUID定义该服务的唯一标识符;
  4. 在创建的服务中添加一个特性,该特性具有可写属性,其唯一标识符为CHARACTERISTIC_UUID_RX;
  5. 将可写特性的回调函数设置为MyCallbacks类型的实例; 启动创建的服务; 启动广告活动。

在loop()函数中:

  1. 检查设备是否已连接。如果设备已连接,则按照以下步骤执行: 清空BLEbuf数组;
  2. 定义一个字符串message_c,其中包含字符’s’和变量i的当前值,两者之间用空格分隔,并在其末尾添加换行符;
  3. 将message_c转换为字符指针类型,并复制到BLEbuf数组中; 设置特性值为BLEbuf数组的内容;
  4. 调用notify()方法向应用程序发送数据; 打印已经发送的数据;

延迟1000ms后继续下一次循环。

7.2 实验效果

  1. esp32c3发送数据,手机接收数据


  2. esp32c3接收数据,手机发送数据


8. Servo调试

安装库《ESP32Servo》

打开库的Sweep例程,修改端口为19

8.1 源码分享

/* Sweep
 by BARRAGAN <http://barraganstudio.com>
 This example code is in the public domain.
 modified 8 Nov 2013
 by Scott Fitzgerald
 modified for the ESP32 on March 2017
 by John Bennett
 see http://www.arduino.cc/en/Tutorial/Sweep for a description of the original code
 * Different servos require different pulse widths to vary servo angle, but the range is 
 * an approximately 500-2500 microsecond pulse every 20ms (50Hz). In general, hobbyist servos
 * sweep 180 degrees, so the lowest number in the published range for a particular servo
 * represents an angle of 0 degrees, the middle of the range represents 90 degrees, and the top
 * of the range represents 180 degrees. So for example, if the range is 1000us to 2000us,
 * 1000us would equal an angle of 0, 1500us would equal 90 degrees, and 2000us would equal 1800
 * degrees.
 * 
 * Circuit: (using an ESP32 Thing from Sparkfun)
 * Servo motors have three wires: power, ground, and signal. The power wire is typically red,
 * the ground wire is typically black or brown, and the signal wire is typically yellow,
 * orange or white. Since the ESP32 can supply limited current at only 3.3V, and servos draw
 * considerable power, we will connect servo power to the VBat pin of the ESP32 (located
 * near the USB connector). THIS IS ONLY APPROPRIATE FOR SMALL SERVOS. 
 * 
 * We could also connect servo power to a separate external
 * power source (as long as we connect all of the grounds (ESP32, servo, and external power).
 * In this example, we just connect ESP32 ground to servo ground. The servo signal pins
 * connect to any available GPIO pins on the ESP32 (in this example, we use pin 18.
 * 
 * In this example, we assume a Tower Pro MG995 large servo connected to an external power source.
 * The published min and max for this servo is 1000 and 2000, respectively, so the defaults are fine.
 * These values actually drive the servos a little past 0 and 180, so
 * if you are particular, adjust the min and max values to match your needs.
 */
#include <ESP32Servo.h>
Servo myservo;  // create servo object to control a servo
// 16 servo objects can be created on the ESP32
int pos = 0;    // variable to store the servo position
// Recommended PWM GPIO pins on the ESP32 include 2,4,12-19,21-23,25-27,32-33 
// Possible PWM GPIO pins on the ESP32-S2: 0(used by on-board button),1-17,18(used by on-board LED),19-21,26,33-42
// Possible PWM GPIO pins on the ESP32-S3: 0(used by on-board button),1-21,35-45,47,48(used by on-board LED)
// Possible PWM GPIO pins on the ESP32-C3: 0(used by on-board button),1-7,8(used by on-board LED),9-10,18-21
// #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)
int servoPin = 19;
// #elif defined(CONFIG_IDF_TARGET_ESP32C3)
// int servoPin = 7;
// #else
// int servoPin = 18;
// #endif
void setup() {
  // Allow allocation of all timers
  ESP32PWM::allocateTimer(0);
  ESP32PWM::allocateTimer(1);
  ESP32PWM::allocateTimer(2);
  ESP32PWM::allocateTimer(3);
  myservo.setPeriodHertz(50);    // standard 50 hz servo
  myservo.attach(servoPin, 1000, 2000); // attaches the servo on pin 18 to the servo object
  // using default min/max of 1000us and 2000us
  // different servos may require different min/max settings
  // for an accurate 0 to 180 sweep
}
void loop() {
  for (pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees
    // in steps of 1 degree
    myservo.write(pos);    // tell servo to go to position in variable 'pos'
    delay(15);             // waits 15ms for the servo to reach the position
  }
  for (pos = 180; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
    myservo.write(pos);    // tell servo to go to position in variable 'pos'
    delay(15);             // waits 15ms for the servo to reach the position
  }
}

这个程序是用来控制servo电机转动的一个示例程序,在ESP32上可以工作。在代码中,servoPin被定义为ESP32-S2和ESP32-S3的19号引脚,以及ESP32-C3的7号引脚。这可以通过条件编译选项CONFIG_IDF_TARGET_ESP32S2或CONFIG_IDF_TARGET_ESP32S3选择要使用的硬件平台,然后定义对应的引脚号。如果没有这些选项,那么就使用默认的18号引脚。

在setup函数中,我们调用了ESP32PWM::allocateTimer()函数分配了所有可用的定时器资源,并设置了伺服电机的频率为标准的50Hz。接着通过myservo.attach(servoPin, 1000, 2000)命令把电机连接到指定的引脚上,并且设置了最小和最大周期分别为1000和2000微秒。这样可以让电机在一个准确的0~180度范围内转动。

在主循环中,pos变量被用来记录电机的位置角度。外层for循环会让电机从0度转到180度,而内层for循环则让电机再从180度转回0度,每次移动1度,并等待15ms的时间,让电机有足够的时间到达指定的角度位置。

8.2 实验效果

esp32c3会控制舵机来回转动180度

9. 总结

本教程是参加FastBond2活动主题4 - 测量仪器中的【Arduino环境下驱动合宙esp32c3单片机基本外设】。

围绕FastBond2阶段1——基于ESP32C3开发的简易IO调试设备项目需求开发。

这次fastbond2活动提供了How to make自主设计的机会,而且给到各种技术支持,非常值得大学生们来参加活动。现在是阶段二实物验证阶段,我会用博客和视频慢慢记录这一次次有趣的开发过程。


目录
相关文章
|
6月前
|
传感器 存储 内存技术
单片机的接口与外设
单片机的接口与外设
120 1
|
芯片
如何来看单片机外设A/D转换器ADC0804时序图
如图,为单片机AD转换器的一种: ADC0804单片集成A/D转换器。它采用CMOS工艺20引脚集成芯片,分辩率为8位,转换时间为100µs,输入电压范围为0~5V。芯片内具有三态输出数据锁存器,可直接接在数据总线上。
1502 0
|
6月前
|
编译器 C语言 开发者
单片机原理与应用:探索微型计算机世界
单片机原理与应用:探索微型计算机世界
57 1
|
6月前
|
数据采集 数据处理 C语言
单片机:探索其原理、应用与编程实践
单片机:探索其原理、应用与编程实践
95 1
|
6月前
|
物联网
STC51单片机-实验开发装置仿真-物联网应用系统设计
STC51单片机-实验开发装置仿真-物联网应用系统设计
141 0
|
6月前
|
物联网
STC51单片机-控制LED闪亮的仿真-物联网应用系统设计
STC51单片机-控制LED闪亮的仿真-物联网应用系统设计
84 0
|
1月前
|
传感器 存储 物联网
单片机的原理与应用
单片机是一种将计算机的CPU、存储器、输入输出接口等功能集成在一块芯片上的微型计算机,被广泛应用于各类控制系统和智能设备中。
65 5
|
4月前
|
传感器 存储 程序员
《单片机原理与应用及C51编程技术》期末复习笔记
《单片机原理与应用及C51编程技术》期末复习笔记
|
6月前
|
传感器 人工智能 物联网
单片机:编程实践与应用探索
单片机:编程实践与应用探索
69 4
|
6月前
|
存储 芯片
【期末不挂科-单片机考前速过系列P11】(第十一章:15题速过串行口的工作原理和应用)经典例题盘点(带图解析)
【期末不挂科-单片机考前速过系列P11】(第十一章:15题速过串行口的工作原理和应用)经典例题盘点(带图解析)