用Arduino做一个自动割草机

这个项目中,我们将用Arduino制作一个自动割草机。该机器可以自动修剪院子里长高的草。如果有障碍物,它会自动改变方向,有助于减少人力。

本文提供了项目的基本概述,以及制作Arduino割草机器人所需的组件。提供了电路原理图和Arduino源代码,以便简化组装和编程过程。

注意:这个项目不是玩具,它包含锋利的刀片。如果不小心使用,可能会造成严重的伤害。不要让它无人看管,刀片应正确固定。操作前检查一下。

材料清单

我们需要以下组件:

1、 Arduino UNO
2、 L293D电机驱动盾
3、 超声波传感器HC-SR04
4、 超声波传感器外壳/支架
6、 直流减速电机 x4
7、 BLDC电机100KV
8、 舵机SG-90
9、 ESC模块
10、舵机测试仪
11、3针滑动开关
12、X型十字支架
13、机器人底盘
14、11.1V锂电池

什么是割草机器人(割草机)?

割草机器人是一种用于自动修剪和维护草坪的机器人设备。这些机器人使用传感器和算法来导航和修剪草坪,并且可以根据草坪的生长速度或特定的时间表来编程修剪草坪。一些割草机器人还配备了诸如障碍物检测、防盗保护和通过智能手机应用程序远程控制等功能。它们近年来越来越受欢迎,因为可以节省割草的时间和精力。

电路和连接

源代码/程序

AFMotor Library:
https://github.com/adafruit/Adafruit-Motor-Shield-library

NewPing Library:
https://github.com/microflo/NewPing

需要编译到Arduino UNO开发板中的代码:

#include <AFMotor.h>  
#include <NewPing.h>
#include <Servo.h> 
 
#define TRIG_PIN A0 
#define ECHO_PIN A1 
#define MAX_DISTANCE 200 
#define MAX_SPEED 190 
#define MAX_SPEED_OFFSET 20
 
NewPing sonar(TRIG_PIN, ECHO_PIN, MAX_DISTANCE); 
 
AF_DCMotor motor1(1, MOTOR12_1KHZ); 
AF_DCMotor motor2(2, MOTOR12_1KHZ);
AF_DCMotor motor3(3, MOTOR34_1KHZ);
AF_DCMotor motor4(4, MOTOR34_1KHZ);
Servo myservo;   
 
boolean goesForward=false;
int distance = 100;
int speedSet = 0;
 
void setup() {
 
  myservo.attach(10);  
  myservo.write(115); 
  delay(2000);
  distance = readPing();
  delay(100);
  distance = readPing();
  delay(100);
  distance = readPing();
  delay(100);
  distance = readPing();
  delay(100);
}
 
void loop() {
 int distanceR = 0;
 int distanceL =  0;
 delay(40);
 
 if(distance<=15)
 {
  moveStop();
  delay(100);
  moveBackward();
  delay(300);
  moveStop();
  delay(200);
  distanceR = lookRight();
  delay(200);
  distanceL = lookLeft();
  delay(200);
 
  if(distanceR>=distanceL)
  {
    turnRight();
    moveStop();
  }else
  {
    turnLeft();
    moveStop();
  }
 }else
 {
  moveForward();
 }
 distance = readPing();
}
 
int lookRight()
{
    myservo.write(50); 
    delay(500);
    int distance = readPing();
    delay(100);
    myservo.write(115); 
    return distance;
}
 
int lookLeft()
{
    myservo.write(170); 
    delay(500);
    int distance = readPing();
    delay(100);
    myservo.write(115); 
    return distance;
    delay(100);
}
 
int readPing() { 
  delay(70);
  int cm = sonar.ping_cm();
  if(cm==0)
  {
    cm = 250;
  }
  return cm;
}
 
void moveStop() {
  motor1.run(RELEASE); 
  motor2.run(RELEASE);
  motor3.run(RELEASE);
  motor4.run(RELEASE);
  } 
  
void moveForward() {
 
 if(!goesForward)
  {
    goesForward=true;
    motor1.run(FORWARD);      
    motor2.run(FORWARD);
    motor3.run(FORWARD); 
    motor4.run(FORWARD);     
   for (speedSet = 0; speedSet < MAX_SPEED; speedSet +=2) 
   {
    motor1.setSpeed(speedSet);
    motor2.setSpeed(speedSet);
    motor3.setSpeed(speedSet);
    motor4.setSpeed(speedSet);
    delay(5);
   }
  }
}
 
void moveBackward() {
    goesForward=false;
    motor1.run(BACKWARD);      
    motor2.run(BACKWARD);
    motor3.run(BACKWARD);
    motor4.run(BACKWARD);  
  for (speedSet = 0; speedSet < MAX_SPEED; speedSet +=2) 
  {
    motor1.setSpeed(speedSet);
    motor2.setSpeed(speedSet);
    motor3.setSpeed(speedSet);
    motor4.setSpeed(speedSet);
    delay(5);
  }
}  
 
void turnRight() {
  motor1.run(FORWARD);
  motor2.run(FORWARD);
  motor3.run(BACKWARD);
  motor4.run(BACKWARD);     
  delay(500);
  motor1.run(FORWARD);      
  motor2.run(FORWARD);
  motor3.run(FORWARD);
  motor4.run(FORWARD);      
} 
 
void turnLeft() {
  motor1.run(BACKWARD);     
  motor2.run(BACKWARD);  
  motor3.run(FORWARD);
  motor4.run(FORWARD);   
  delay(500);
  motor1.run(FORWARD);     
  motor2.run(FORWARD);
  motor3.run(FORWARD);
  motor4.run(FORWARD);
}  

测试

上传代码后,你可以把机器人带到野外,也许在高草地区。高草地区可以是一个很好的测试选择。

打开机器人上的开关,确保电源为Arduino板提供正确的电压,并且所有组件都正确连接。通过手动控制机器人的运动来测试机器人的电机,确保机器人运动平稳准确。

测试机器人的传感器,在其路径上放置障碍物,并确保能避开它们。此外,检查传感器的范围和灵敏度,以确保机器人可以检测到草的存在。

Arduino Uno R4发售:配置飙升,芯片大换

Arduino 在 2010 年推出了 Uno R3之后,一直没有对这个型号做更新。而这个型号也历久弥坚,流行了很久。

十三年过去了,他们突然发了一个升级型号 —— Arduino Uno R4。和老款相比,新款在算力、内存、网络连接方面都做了大幅改善。

Arduino UNO R4 具有与R3相似的设计。不过UNO R4没再用ATmega328P的芯片。而是改成了ARM Cortex-M4,48兆赫工作频率,有32 KB的RAM和256 KB的闪存。可以用5V的电源供电。

该型号共有两个版本,分别是Minima版和Wi-Fi版。两个版本芯片都是ARM Cortex-M4,不同点主要在于:

  • 无线连接:Arduino UNO R4 WiFi 集成了一个 ESP32-S3 模块,可以支持 Wi-Fi 和蓝牙连接。Arduino UNO R4 Minima 没有无线连接功能,但是可以通过 Qwiic 连接器接入其他的无线模块。
  • LED 矩阵:Arduino UNO R4 WiFi 有一个内置的 12×8 LED 矩阵,可以用来显示图形或文字。Arduino UNO R4 Minima 没有 LED 矩阵。
  • VRTC 和 OFF 引脚:Arduino UNO R4 WiFi 有一个 VRTC 引脚,可以用来给 ESP32-S3 模块提供电源,以便在低功耗模式下保持 Wi-Fi 连接。它还有一个 OFF 引脚,可以用来关闭 ESP32-S3 模块,以节省电量。Arduino UNO R4 Minima 没有这两个引脚。

WI-FI版本外观

Wi-Fi版本售价信息

Minima版本外观

Minima版本售价信息

UNO R4和UNO R3参数对比

由于其强大的设计和可靠的性能,Arduino UNO R4会是物联网爱好者不容错过的一款开发板,也很适合Arduino老玩家用来升级他们的项目。

触碰即盛放的“宝莲灯”——Arduino创意作品

编者注:这是一个名叫JiříPraus的外国小哥实现的Arduino创意作品,实际上是“郁金香”,但看着太像中国神话故事里的“宝莲灯”了。

轻轻抚摸就会绽放,它的六个花瓣将缓慢打开并照耀出彩虹般的光。当花瓣闭合时,它们会产生令人难以置信的带有叶子图案的光。

所需材料:

Arduino Nano R3
SG90微型伺服电机
TTP223触摸传感器
1mm黄铜丝
2mm黄铜管
0.3mm绝缘铜线
WS2812 5050 NeoPixel LED灯 x 7
白色SMD 1206 LED x 30

附注:如何焊接黄铜

https://davidneat.wordpress.com/2015/05/03/a-quick-guide-to-soldering-brass/amp/

推杆如何与花瓣一起运动

当推杆向上移动时,它将连杆和花瓣向下拉。当它向下移动时,它拖着连杆,将花瓣闭合。

单片花瓣的构成:

花瓣由黄铜细条组成,花瓣内有5个白色LED和同一根导线构成的“静脉”结构。

相同的花瓣,一共要做6个。否则它们在关闭时不会构成漂亮的郁金香形状,甚至会卡住。

相关代码:


#include <Adafruit_TiCoServo.h>
#include "SoftPWM.h"

#define NEOPIXEL_PIN A0
#define TOUCH_SENSOR_PIN 2

#define SERVO_PIN 9
//#define SERVO_OPEN 1750
#define SERVO_OPEN 1650
#define SERVO_SAFE_MIDDLE 1000
#define SERVO_CLOSED 775

#define RED 0
#define GREEN 1
#define BLUE 2

float currentRGB[] = {0, 0, 0};
float changeRGB[] = {0, 0, 0};
byte newRGB[] = {0, 0, 0};

#define MODE_SLEEPING 0
#define MODE_BLOOM 3
#define MODE_BLOOMING 4
#define MODE_BLOOMED 5
#define MODE_FADE 6
#define MODE_FADING 7
#define MODE_FADED 8
#define MODE_FALLINGASLEEP 9

#define MODE_RAINBOW 90

byte mode = MODE_FADED;

byte petalPins[] = {3, 4, 5, 6, 10, 11};

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(7, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ400);
Adafruit_TiCoServo servo;

int servoChange = 1; // open
int servoPosition = SERVO_SAFE_MIDDLE;

void setup() {
  Serial.begin(115200);
  pixels.begin();
  servo.attach(SERVO_PIN, SERVO_CLOSED, SERVO_OPEN);

  pinMode(TOUCH_SENSOR_PIN, INPUT);
  attachInterrupt(digitalPinToInterrupt(TOUCH_SENSOR_PIN), _touchISR, RISING);

  randomSeed(analogRead(A7));
  SoftPWMBegin();

  pixelsUnifiedColor(pixels.Color(0, 0, 0));
  //pixelsUnifiedColor(pixels.Color(255, 70, 0));

  prepareCrossFade(140, 70, 0, 140);
  servo.write(servoPosition);
}

int counter = 0;
byte speed = 15;

void loop() {
  boolean done = true;
  switch (mode) {
    case MODE_BLOOM:
      prepareCrossFadeBloom(500);
      changeMode(MODE_BLOOMING);
      break;

    case MODE_BLOOMING:
      done = crossFade() && done;
      done = openPetals() && done;
      done = petalsBloom(counter) && done;
      if (done) {
        changeMode(MODE_BLOOMED);
      }
      break;

    case MODE_FADE:
      //prepareCrossFade(0, 0, 0, 800);
      changeMode(MODE_FADING);
      break;

    case MODE_FADING:
      done = crossFade() && done;
      done = closePetals() && done;
      done = petalsFade(counter) && done;
      if (done) {
        changeMode(MODE_FADED);
      }
      break;

    case MODE_FADED:
      //prepareCrossFade(140, 70, 0, 140);
      changeMode(MODE_FALLINGASLEEP);
      break;

    case MODE_FALLINGASLEEP:
      done = crossFade() && done;
      done = closePetals() && done;
      if (done) {
        changeMode(MODE_SLEEPING);
      }
      break;

    case MODE_RAINBOW:
      rainbow(counter);
      break;
  }

  counter++;
  delay(speed);
}

void changeMode(byte newMode) {
  if (mode != newMode) {
    mode = newMode;
    counter = 0;
  }
}

void _touchISR() {
  if (mode == MODE_SLEEPING) {
    changeMode(MODE_BLOOM);
  }
  else if (mode == MODE_BLOOMED) {
    changeMode(MODE_FADE);
  }
}

// petals animations

boolean petalsBloom(int j) {
  if (j < 250) {
    return false; // delay
  }
  if (j > 750) {
    return true;
  }
  int val = (j - 250) / 2;
  for (int i = 0; i < 6; i++) {
    SoftPWMSet(petalPins[i], val);
  }
  return false;
}

boolean petalsFade(int j) {
  if (j > 510) {
    return true;
  }
  for (int i = 0; i < 6; i++) {
    SoftPWMSet(petalPins[i], (510 - j) / 2);
  }
  return false;
}

// animations

void prepareCrossFadeBloom(unsigned int duration) {
  byte color = random(0, 5);
  switch (color) {
    case 0: // white
      prepareCrossFade(140, 140, 140, duration);
      break;
    case 1: // red
      prepareCrossFade(140, 5, 0, duration);
      break;
    case 2: // blue
      prepareCrossFade(30, 70, 170, duration);
      break;
    case 3: // pink
      prepareCrossFade(140, 0, 70, duration);
      break;
    case 4: // orange
      prepareCrossFade(255, 70, 0, duration);
      break;
  }
}

void rainbow(int j) {
  uint16_t i;
  byte num = pixels.numPixels() - 1;
  pixels.setPixelColor(pixels.numPixels() - 1, 100, 100, 100);

  for (i = 0; i < num; i++) {
    pixels.setPixelColor(i, colorWheel(((i * 256 / num) + j) & 255));
  }
  pixels.show();
}

// servo function

boolean openPetals() {
  if (servoPosition >= SERVO_OPEN) {
    return true;
  }
  servoPosition ++;
  servo.write(servoPosition);
  return false;
}

boolean closePetals() {
  if (servoPosition <= SERVO_CLOSED) {
    return true;
  }
  servoPosition --;
  servo.write(servoPosition);
  return false;
}

// utility function

void pixelsUnifiedColor(uint32_t color) {
  for (unsigned int i = 0; i < pixels.numPixels(); i++) {
    pixels.setPixelColor(i, color);
  }
  pixels.show();
}

void prepareCrossFade(byte red, byte green, byte blue, unsigned int duration) {
  float rchange = red - currentRGB[RED];
  float gchange = green - currentRGB[GREEN];
  float bchange = blue - currentRGB[BLUE];

  changeRGB[RED] = rchange / (float) duration;
  changeRGB[GREEN] = gchange / (float) duration;
  changeRGB[BLUE] = bchange / (float) duration;

  newRGB[RED] = red;
  newRGB[GREEN] = green;
  newRGB[BLUE] = blue;

  Serial.print(newRGB[RED]);
  Serial.print(" ");
  Serial.print(newRGB[GREEN]);
  Serial.print(" ");
  Serial.print(newRGB[BLUE]);
  Serial.print(" (");
  Serial.print(changeRGB[RED]);
  Serial.print(" ");
  Serial.print(changeRGB[GREEN]);
  Serial.print(" ");
  Serial.print(changeRGB[BLUE]);
  Serial.println(")");
}

boolean crossFade() {
  if (currentRGB[RED] == newRGB[RED] && currentRGB[GREEN] == newRGB[GREEN] && currentRGB[BLUE] == newRGB[BLUE]) {
    return true;
  }
  for (byte i = 0; i < 3; i++) {
    if (changeRGB[i] > 0 && currentRGB[i] < newRGB[i]) {
      currentRGB[i] = currentRGB[i] + changeRGB[i];
    }
    else if (changeRGB[i] < 0 && currentRGB[i] > newRGB[i]) {
      currentRGB[i] = currentRGB[i] + changeRGB[i];
    }
    else {
      currentRGB[i] = newRGB[i];
    }
  }
  pixelsUnifiedColor(pixels.Color(currentRGB[RED], currentRGB[GREEN], currentRGB[BLUE]));
  /*
    Serial.print(currentRGB[RED]);
    Serial.print(" ");
    Serial.print(currentRGB[GREEN]);
    Serial.print(" ");
    Serial.print(currentRGB[BLUE]);
    Serial.println();
  */
  return false;
}

uint32_t colorWheel(byte wheelPos) {
  // Input a value 0 to 255 to get a color value.
  // The colours are a transition r - g - b - back to r.
  wheelPos = 255 - wheelPos;
  if (wheelPos < 85) {
    return pixels.Color(255 - wheelPos * 3, 0, wheelPos * 3);
  }
  if (wheelPos < 170) {
    wheelPos -= 85;
    return pixels.Color(0, wheelPos * 3, 255 - wheelPos * 3);
  }
  wheelPos -= 170;
  return pixels.Color(wheelPos * 3, 255 - wheelPos * 3, 0);
}
Credits

八年无人干预的蜂巢健康监测系统

用技术更好的感知世界,一直是大家追求的目标。

大学毕业后,Glyn Hudson一直忙着创业,成立了一家名叫OpenEnergyMonitor的能源监测公司。

在威尔士的斯诺登尼亚,他接触到几个蜂箱,每年可生产12至15公斤蜂蜜。

作为变温动物,蜜蜂的体温会随着气温而变化。因此,温度是影响蜜蜂生活的重要因素之一。

怎样才能时刻获知蜜蜂当前的生活环境是否适宜?蜂箱里会不会太潮?温度会不会太热?

Glyn打算通过获悉蜂箱内外的温度和湿度,构建一个监测蜂群健康的系统 —— BeeMonitor。

格林(Glyn)在检查BeeMonitor装置
既要监测蜂巢内部的温湿度,也要监测核心温度、周边温湿度、蜂巢外部的温湿度,然后进行比较。
电子组件连接示意图

Glyn解释说:“蜜蜂需要获得更好的帮助和关爱,如果没有它们给植物授粉,我们将很难种庄稼。

为了保持健康的育雏温度,我们要让蜂巢保持33.5-34.5度,而这一温度也是菌落健康的关键指标。”

没有Wi-Fi怎么办

BeeMonitor这套系统在2012年就开始跟踪蜂群的状态,也是Raspberry Pi最早的成功案例子之一。

Glyn自己为BeeMonitor做了大部分工作,而之前为OpenEnergyMonitor项目开发的软件提供了可以在线查看数据记录和图表的平台。

穿着防护服的观众参观 BeeMonitor 蜂箱

蜂箱距离房屋太远,无法通过Wi-Fi传输。因此Glyn装了一个低功耗的RF发射器,该发射器连到蜂巢内部的Arduino,以便进行读数。然后再由远端的Raspberry Pi和另外一个Arduino接收。

该装置的远端部分包括了Arduino,RF接收器,USB电缆和树莓派

这些数据被保存到SD卡,明显的缺点是它不显示实时数据读数。在最初的设定中,Glyn还必须亲自提取和分析CSV数据。

他说:“这非常耗时,但确实产生了一些有趣的数据。”

传感器的取舍

BeeMonitor成功运转后,Glyn意识到数据在Internet上实时传输会更好。这样一来,他就可以从任何地方查看实时的蜂箱数据,还可以让其他人参与其中。

最初,蜂箱内部有一个DS18B20温度传感器和一个DHT22湿度传感器。但Glyn后来决定放弃DHT22湿度传感器。

相关设置方式:

https://learn.openenergymonitor.org/electricity-monitoring/temperature/DS18B20-temperature-sensing

“它消耗了很多电量,而蜜蜂还不喜欢它,一直在用蜂蜡覆盖湿度传感器!奇怪的是,蜜蜂似乎并不介意DS218B20温度传感器,大概是因为与DHT22的塑料格栅相比,温度传感器是圆形的金属物体,” Glyn指出。

与湿度传感器不同,蜜蜂似乎不在乎温度传感器的探头

该系统由一个旧的汽车电池和一个小的太阳能电池板供电,运营成本可以忽略不计。

目前已稳定运行八年,几乎不用人工干预。

“Raspberry Pi非常适合在低功耗状态下可靠地运行此类项目。”Glyn说。

他之所以选择树莓派,是因为硬件背后繁荣的社区。以及有竞争力的价格 —— 整个装置花了他大约50英镑。

相关代码地址:

https://github.com/glynhudson/BeeMonV2

Glyn告诉我们,(如果不考虑联网)仅用树莓派,DS28B20温度传感器,电池组和太阳能电池板结合,就足以打造一个基本的蜂巢监测器。

来源:Raspberrypi.org

编译:王文文