您现在的位置是:网站首页> 硬件

Arduino通用研发信息收集

  • 硬件
  • 2025-06-08
  • 1642人已阅读
摘要

Arduino通用研发信息收集

Arduino RTOS


wokwi和tinkercad两个arduino仿真平台

常用经验及模块接口相关编程收集

***Arduino采集信号采集的是电流还是电压***

板子的驱动一般在Ardunio Studio的driver目录下,点击下载

drivers.rar

自定义板子安装后的位置

Arduino IDE 离线安装自定义开发板 / 附加开发板下载时出错 解决方法

Arduino如何运行FreeRTOS

Ardunio使用json

Ardunio的IO口的驱动能力

Arduino常用的三种通信协议UART, I2C和SPI

上拉电阻

Arduino IO口采集高低电平上拉下拉电阻

Arduino 连接开关

Arduino 读取模拟电压

Arduino  SPI 串行外设接口

Arduino IDE 离线安装自定义开发板 

Arduino IDE里添加自定义板子

采用中断采集脉冲信号

Arduino 极客教程

Arduino烧写软件程序

实用,教你用一块arduino板给另一块烧bootloader

Mqtt 客户端多主题订阅

Arduino的IO口驱动力不够解决办法

LVGL Arduino界面开发库

常用经验及模块接口相关编程收集

ESP32开发相关资料收集

Ardunio软重新启动

中断使用注意

Arduino String使用注意不能直接当char *使用

Arduino的String类用法汇总

不用Arduino库控制舵机

ESP32 低功耗蓝牙的例子代码

采用Enerlib程序库设定睡眠模式

Ardunio 命令行编译上传

Arduino引脚介绍和使用方式

Ardunio 中断

外部中断唤醒

各种Arduino外部中断程序

同一个外部中断的Arduino随机睡眠与唤醒

Arduino休眠模式和看门狗以及中断详解

使用Arduino读取水流速传感器的脉冲数

Arduino看门狗的使用方法

Arduino 键盘串口

Arduino 键盘注销

Arduino 时间函数

Arduino 字符串对象

Arduino I/O函数

Arduino 高级I/O函数

Arduino 字符函数

Arduino数学库

Arduino 三角函数

Arduino 随机数

Arduino 中断

Arduino 超声波传感器

Arduino PIR传感器

Arduino 水位检测器/传感器

Arduino 温度传感器

Arduino 湿度传感器

Arduino 网络通信

Arduino 无线通信

Arduino 步进电机

Arduino 伺服电机

Arduino 直流电机

Arduino的Stepper库函数及其控制步进电机(ULN2003)调速

Arduino 步进电机

Arduino 舵机操作

Arduino笔记-定时器中断(MsTimer2库使用)

Arduino定时器中断attachInterrupt()详解

如何使用Arduino和Arduino控制步进电机

全新AVR芯片ATMEGA328P烧录程序的教程

Arduino基础入门篇舵机控制

Ardunio定时器中断

I2C通讯 ARDUINO的I2C通信详解

Ardunio PWM频率库 

PWM原理、频率与占空比

Arduino使用watchdog方法

动手学arduino用mos管驱动马达

Arduino 鼠标按钮控制 模拟为鼠标

Arduino 键盘消息 Arduino模拟为键盘

动手学arduino两款智能灯

动手学arduino程序控制LED

Arduino 烧写bootloader

SPI通讯接口

wokwi和tinkercad两个arduino仿真平台



Arduino如何运行FreeRTOS

在Arduino上运行FreeRTOS,可以实现多任务并行处理,提高程序的复杂性和功能,同时保持代码的可读性和可靠性。以下是具体的实现步骤:

1. 安装FreeRTOS库

在Arduino IDE中,通过“工具”->“管理库...”打开库管理器,搜索并安装“FreeRTOS for Arduino”库

2. 创建任务

使用FreeRTOS的xTaskCreate函数为不同的功能或过程创建独立的任务。每个任务是一个可以异步运行的函数。例如:

#include <Arduino.h>

#include <FreeRTOS.h>

#include <task.h>


void task1(void *pvParameters) {

  while (1) {

    Serial.println("任务1正在运行");

    vTaskDelay(1000 / portTICK_PERIOD_MS);

  }

}


void task2(void *pvParameters) {

  while (1) {

    Serial.println("任务2正在运行");

    vTaskDelay(2000 / portTICK_PERIOD_MS);

  }

}


void setup() {

  Serial.begin(115200);

  xTaskCreate(task1, "Task1", 100, NULL, 1, NULL);

  xTaskCreate(task2, "Task2", 100, NULL, 2, NULL);

  vTaskStartScheduler();

}


void loop() {

  // 主循环保持空,RTOS负责任务调度

}



在上述代码中,task1task2是两个独立的任务,分别打印“任务1正在运行”和“任务2正在运行”,并分别每隔1秒和2秒执行一次

3. 任务调度

FreeRTOS会根据任务的优先级自动调度任务。高优先级的任务会在低优先级任务完成后立即运行。可以通过xTaskCreate函数的优先级参数设置任务的优先级

4. 任务间通信与同步

FreeRTOS提供了多种任务间通信和同步机制,如队列、信号量、互斥锁等。例如,使用互斥锁确保任务在访问共享资源时不会发生冲突:

#include <Arduino.h>

#include <FreeRTOS.h>

#include <task.h>

#include <semphr.h>


SemaphoreHandle_t xMutex;


void task1(void *pvParameters) {

  while (1) {

    if (xSemaphoreTake(xMutex, (TickType_t)10) == pdTRUE) {

      Serial.println("任务1正在访问共享资源");

      vTaskDelay(1000 / portTICK_PERIOD_MS);

      xSemaphoreGive(xMutex);

    }

    vTaskDelay(500 / portTICK_PERIOD_MS);

  }

}


void task2(void *pvParameters) {

  while (1) {

    if (xSemaphoreTake(xMutex, (TickType_t)10) == pdTRUE) {

      Serial.println("任务2正在访问共享资源");

      vTaskDelay(1000 / portTICK_PERIOD_MS);

      xSemaphoreGive(xMutex);

    }

    vTaskDelay(500 / portTICK_PERIOD_MS);

  }

}


void setup() {

  Serial.begin(115200);

  xMutex = xSemaphoreCreateMutex();

  xTaskCreate(task1, "Task1", 100, NULL, 2, NULL);

  xTaskCreate(task2, "Task2", 100, NULL, 1, NULL);

  vTaskStartScheduler();

}


void loop() {

  // 主循环保持空,RTOS负责任务调度

}

在上述代码中,xSemaphoreCreateMutex创建了一个互斥锁,xSemaphoreTakexSemaphoreGive分别用于获取和释放互斥锁,确保任务在访问共享资源时不会发生冲突

5. 动态调整任务优先级

可以通过vTaskPrioritySet函数动态调整任务的优先级,实现任务间的动态优先级切换:

#include <Arduino.h>

#include <FreeRTOS.h>

#include <task.h>


TaskHandle_t xHandle = NULL;


void DynamicPriorityTask(void *pvParameters) {

  uint32_t ulBasePriority = 2;

  while (1) {

    vTaskPrioritySet(NULL, ulBasePriority);

    if (ulBasePriority == 2) {

      ulBasePriority = 1;

    } else {

      ulBasePriority = 2;

    }

    vTaskDelay(pdMS_TO_TICKS(2000));

    Serial.print("Task Priority: ");

    Serial.println(ulBasePriority);

  }

}


void setup() {

  Serial.begin(115200);

  xTaskCreate(DynamicPriorityTask, "DynamicPriority", 128, NULL, 2, &xHandle);

  vTaskStartScheduler();

}


void loop() {

  // 主循环为空,RTOS负责任务调度

}

在上述代码中,vTaskPrioritySet函数用于动态调整任务的优先级,任务在不同优先级之间切换

注意事项

  • 任务堆栈大小:在创建任务时,需要合理设置任务的堆栈大小,以确保任务有足够的内存空间运行。
  • 任务优先级:合理设置任务的优先级,避免高优先级任务长时间占用CPU,导致低优先级任务无法执行。
  • 任务间通信:在任务间传递数据时,确保使用FreeRTOS提供的通信机制(如队列、信号量等),避免数据竞争和不一致问题。
通过以上步骤,可以在Arduino上运行FreeRTOS,实现多任务并行处理,提高系统的实时性和响应能力。




Ardunio使用json

Serial.begin(115200);

  DynamicJsonDocument doc(1024);


  // You can use a String as your JSON input.

  // WARNING: the string in the input  will be duplicated in the JsonDocument.

  String input =

      "{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038],\"array\":[{\"A\":12},{\"A\":14}]}";

  deserializeJson(doc, input);

  JsonObject obj = doc.as<JsonObject>();


  // You can use a String to get an element of a JsonObject

  // No duplication is done.

  long time = obj[String("time")];

  const char *ss=obj["aa"]; //判断aa键是否存在

  if(!ss)

  {

    Serial.printf("aa is null\r\n");

  }

  else

  {

    Serial.printf("aa is not null\r\n");

  }


  JsonArray arr = obj["array"].as<JsonArray>();

  if(arr.isNull())

  {

     Serial.printf("arr is  null\r\n");

  }

  else

  {

    int nSize=arr.size();

    Serial.printf("arr size is  %d\r\n",nSize);

    int A=arr[0]["A"].as<int>();

    Serial.printf("arr[0].A is  %d\r\n",A);

  }




Ardunio的IO口的驱动能力

数字 IO 口驱动能力

对于 Arduino Uno 等常见开发板,其数字 IO 口在输出高电平时,每个引脚能够提供最大 40mA 的电流。但一般不建议让引脚长时间处于最大电流输出状态,因为这可能会损坏芯片。


模拟 IO 口驱动能力

模拟输出引脚(PWM 引脚)的驱动能力在输出高电平时和数字 IO 口类似,也有最大 40mA 左右的电流输出能力。PWM 引脚通过改变脉冲宽度来模拟不同的电压输出,可用于控制一些需要不同功率的设备,如调节 LED 的亮度或者控制小型舵机的角度。不过,在驱动舵机等设备时,由于舵机启动和运行时电流可能会有较大波动,需要考虑是否超过 IO 口的驱动能力。如果超过了,可能需要使用外部电源和驱动电路来为设备供电,同时通过 Arduino 的 IO 口进行控制信号的传输。


外部电路增强驱动能力

如果需要驱动更大功率的设备,如大功率电机或者高亮度的 LED 灯带等,超出了 Arduino IO 口的驱动能力,可以使用外部驱动电路。例如,使用晶体管(如 NPN 型三极管)或者场效应管(MOSFET)来放大电流,以满足设备的驱动要求。以 NPN 三极管为例,Arduino 的 IO 口可以用来控制三极管的基极电流,通过三极管的放大作用,集电极可以输出较大的电流来驱动负载。或者使用专门的电机驱动芯片,如 L298N 来驱动直流电机,这些芯片可以接收 Arduino 的控制信号,同时提供足够的电流来驱动电机运转。



Arduino常用的三种通信协议UART, I2C和SPI

UART协议UART的全称叫 Universal Asynchronous Reception and Transmission通用异步收发。从名字我们就可以知道,UART是异步串行通信的。利用UART协议传输数据时,需要两个数据引脚,一个用于传输数据的TX,另一个用于接收的RX。Arduino板上默认的RX和TX引脚分别是Pin0和Pin1。UART硬件层和软件层的功能都是Arduino集成的,一般我们不需要从底层操作数据。一个UART口(一组RX和TX)只能连接一个UART设备进行通信,我们需要这样就接线:

1.png

注意,一个设备的TX接的是另一设备的RX,反之RX接TX,最后需要将两个设备共地,共地是让它们的参考电压一致。下面,我们用两块Arduino实际实践一下。如图连接两个Arduino:

2.jpg

我们在Arduino A里烧录以下代码:

void setup() {  

// put your setup code here, to run once:  

Serial.begin(9600);

}

void loop() {  // put your main code here, to run repeatedly:   

 char s[]="I am Arduino A";//每隔1s发送一次字符数组s    

 Serial.print(s);  

 delay(1000);

 }

再在Arduino B里烧录以下代码:


String a=""; //定义字符串,接收数据

void setup() {  // put your setup code here, to run once: 

 Serial.begin(9600);

 }

 void loop() {  

  while(Serial.available())//当发现缓存中有数据时,将数据送至字符数组a中  

   {

     a+=char(Serial.read());

 delay(3); 

   } 

   if (a.length() > 0){

    Serial.println(a);

a = ""   

}

}

这样就A就通过UART向B发送了“ I am Arduino A”消息,B接收到以后输出。当然你可以修改A的代码实现双向数据传输,这里我们就点到即止了。UART是最常见的通信协议之一,它可以实现全双工传输,但它的传输速度比较慢,而且只能支持一对一的设备。 I2C协议I2C是Philips公司在1980年代发明的一种协议,全称是Inter-Integrated Circuit。I2C最常被应用于模块或者传感器之间的通信,因为I2C基于二根传输线,同步通信的半双工协议,而且协议也非常简单可靠。 I2C使用两根传输线实现一个主设备与多个从设备,甚至是多个主设备与对应从设备之间的通信。 这两根通讯线一根为控制时钟线,称之为SCL,用于同步设备间的数据传输时钟; 另一根为数据线,称之为SDA,用于携带数据。理论上,一条I2C总线上能支持挂载128台设备。

1.png

Arduino UNO的I2C引脚是A4(SDA), A5(SDL)。Arduino对I2C协议也进行了库封装:

https://www.arduino.cc/en/Reference/Wire

下面我们还是用两块Arduino来实践一下如何利用I2C协议来传输数据。如图连接好两块Arduino:

2.png

3.jpg

一台我们作为主设备(Master),烧录以下代码:



 void setup()

 { 

 Serial.begin(9600); /* begin serial comm. */ 

 Wire.begin(); /* join i2c bus as master */ 

 Serial.println("I am I2C Master");

 }

 void loop() { 

 Wire.beginTransmission(8); /* begin with device address 8 */ 

 Wire.write("Hello Slave");  /* sends hello string */

  Wire.endTransmission();    /* stop transmitting */ 

  Wire.requestFrom(8, 9); /* request & read data of size 9 from slave */ 

  while(Wire.available())

  {    

  char c = Wire.read();/* read data received from slave */  

  Serial.print(c); 

  }

   Serial.println(); delay(1000);

   }

另一块作为从设备(Slave),烧录以下代码:


void setup() 

Wire.begin(8);      /* join i2c bus with address 8 */ 

Wire.onReceive(receiveEvent); /* register receive event */

 Wire.onRequest(requestEvent); /* register request event */ 

 Serial.begin(9600);           /* start serial comm. */ 

 Serial.println("I am I2C Slave");

 }

 void loop() 

 { 

 delay(100);

 }

 // function that executes whenever data is received from master

 void receiveEvent(int howMany) { 

 while (1 < Wire.available()) 

  char c = Wire.read();      /* receive byte as a character */   

   Serial.print(c);           /* print the character */ 

}

Serial.println("ok");     

/* to newline */

}

//function that executes whenever data is requested from master

void requestEvent() 

 Wire.write("Hi Master");  /*send string on request */

}

这样,我们就实现了主从设备的双向传输。打开主机Arduino的串口监视器我们可以看见如下的输出:

1.png

从机Arduino的串口输出:

1.png

I2C虽然只需要两根线,就能支持多主机多从机的数据传输,但由于只有一根用于数据传输,它通过在“接收”和“传输”两种状态之间但切换实现了双向传输,但牺牲了不少传输速率。I2C还有典型的开漏问题,总线需要加上拉电阻。

SPI协议

最后,我们来看一下SPI协议。SPI全称Serial Peripheral Interface(串行外设接口),由摩托罗拉公司提出的一种同步串行数据传输协议。SPI类似I2C也是同步通信的协议,但是全双工,支持数据的同时输出和输入。这两个特征使SPI的传输速率比UART和I2C都高,这对于像SD卡、或者屏幕等数据型模块来说,是非常具有优势的。

1.png

SPI支持一主多从的模式,但SPI也是三种协议中需要线最多的协议,一共需要4条信号线:

11458f5de8c9a6adc57e185a24b592c8.png

但Arduino UNO默认的SPI引脚分别为D13(SCK), D12(MISO), D11(MOSI), D10(SS),其中SS是从机选择引脚,没有强制要求,你也可以选其他的引脚。2.jpg

同样,我们来实践一下用SPI实现数据传输。

3.png

4.jpg

如图连接好两块Arduino UNO。还是一块作为主机(Master), 另一块作为从机(Slave)。Arduino对SPI协议也做了类封装:

https://www.arduino.cc/en/reference/SPI

主机烧录以下代码:

void setup (void){ 

 Serial.begin(115200);  

 digitalWrite(SS, HIGH); 

 SPI.begin (); 

 SPI.setClockDivider(SPI_CLOCK_DIV8);

}

void loop (void){ 

  char c;  

// enable Slave Select  

  digitalWrite(SS, LOW);   

 // SS is pin 10 

 // send test string  

  for (const char * p = "Hello, world!\n" ; c = *p; p++) 

{

    SPI.transfer (c);  

    Serial.print(c); 

    }  

   // disable Slave Select

    digitalWrite(SS, HIGH);

   delay (1000);

}


从机烧录:


char buf [100];

volatile byte pos;

volatile boolean process_it; 

void setup (void){ 

 Serial.begin (115200);   // debugging  

 // turn on SPI in slave mode  

 SPCR |= bit (SPE);  // have to send on master in, *slave out*  

 pinMode(MISO, OUTPUT);  

 // get ready for an interrupt  

 pos = 0;   

 // buffer empty  

 process_it = false;  

 // now turn on interrupts 

 SPI.attachInterrupt();

 } 

  // end of setup 

   // SPI interrupt 

   routineISR (SPI_STC_vect)

   {

     byte c = SPDR;  // grab byte from SPI Data Register  

 // add to buffer if room 

  if (pos < sizeof buf) 

   { 

      buf [pos++] = c;

      // example: newline means time to process buffer

  if (c == '\n') 

     process_it = true;  

 

 

 // end of room available

}  // end of interrupt routine SPI_STC_vect // main loop - wait for flag set in interrupt routinevoid 

loop (void)

{  

if (process_it)  

   buf [pos] = 0; 

   Serial.println(buf);  

   pos = 0;  

   process_it = false;  

 // end of flag set 

}  // end of loop

这样从机就能接受到主机发过来的消息了。

总结

今天,我们粗略地介绍了一下Arduino数据通信中最常用的三种协议:UART、I2C和SPI。

协议                通信方式                通信方向                信号线                传输速率                主从模式                
UART异步全双工

2线

RX、TX

最低一对一
I2C同步半双工

2线

SDA、SCL,以地址选择从机

多主机

多从机

SPI同步全双工

4线

MOSI、MISO、SCLK、CS(或SS),以CS选择从机

一主多从

它们各自都有自己的优缺点和适用的场景,并没有绝对的好坏,这也是这三种协议经久不衰的原因。只有了解并掌握它们,我们才能在具体的应用场景里选择最合适的协议。当然在嵌入式世界里,还有其他很多协议,小编以后再介绍吧。如果对这三种协议的底层感兴趣的朋友,也可以自己再去深入了解。








上拉电阻

1.png


Arduino IO口采集高低电平上拉下拉电阻

   当输入端口当属插入采集高低电平时候记得设置为pinMode(36,INPUT_PULLUP);

  1. 单片机输出,为避免短路可接电阻,如驱动能力不够可接三级管,MOS管或继电器,(驱动能力满足,只是电压不匹配可以接电瓶转换模块)

  2. 上拉电阻是将电位平时强行拉到高电位,当接通后电位变低,也就是电阻一段端接电源电平

    接线图

     1.png        

  3. 下拉电阻,将电位平时强拉到底电位,当接通后电位变高电位,也就是电阻一段接低

    接线

    1.png
           


           


           


           

    内置上拉电阻

    Arduino自带内置上拉电阻,不用像上面那样自己搭建,直接在代码中开启即可

    pinMode(pin,INPUT_PULLUP);        

使用例子

void setup() {

  // put your setup code here, to run once:

  //pinMode(4,INPUT);

  Serial.begin(9600);

  pinMode(4,INPUT_PULLUP); //将按钮4号引脚拉高电平

  pinMode(2,OUTPUT);//设置2号口输出

}


void loop() {

  // put your main code here, to run repeatedly:

  if(digitalRead(4)==HIGH)//读取4号口的电平高低电平

  {

  digitalWrite(2, HIGH);

  Serial.println("HIGH");

  }

  else

  {

    digitalWrite(2, LOW);

    Serial.println("LOW");

  }

  

}

//按钮按下时为低电平

1.png

由于IO口未内置下拉电阻,所以对于采集外结传感器搞电平时候要接下拉电阻

总结:

 下拉电阻就是电阻一端接地,另一端接IO口和传感器,采集传感器高电平

 上拉电阻就是电阻一端接电源正极,另一端接IO口和传感器,采集传感器低电平

采集脉冲数量可以使用中断

digitalPinToInterrupt(pin);//IO口获得终断号



Arduino 连接开关

按钮或开关连接电路中的两个开路端子。当按下连接到引脚8的按钮开关时,此示例打开引脚2上的LED。

1.jpg

下拉电阻

在电子逻辑电路中使用下拉电阻,以确保在外部器件断开连接或处于高阻抗状态时,Arduino的输入信号达到预期的逻辑电平。虽然没有任何东西连接到输入引脚,但这并不意味着它是一个逻辑0。下拉电阻连接在地面和器件上相应的引脚之间。

下图显示了数字电路中下拉电阻的示例。在电源电压和微控制器引脚之间连接了一个按钮开关。在这样的电路中,当开关闭合时,微控制器输入处于逻辑高值;但是当开关打开时,下拉电阻将输入电压下拉到接地(逻辑零值),防止输入处于未定义状态。

下拉电阻的电阻必须大于逻辑电路的阻抗,否则可能会使电压下降太多,而无论开关的位置如何,引脚处的输入电压将保持在恒定的逻辑低值。

2.jpg

必需的组件

你将需要以下组件:

  • 1 × Arduino UNO 板
  • 1 × 330欧姆电阻
  • 1 × 4.7K欧姆电阻(下拉)
  • 1 × LED

程序

按照电路图进行连接,如下图所示。

3.jpg

草图

在计算机上打开Arduino IDE软件。使用Arduino语言进行编码控制你的电路。通过单击“New”打开一个新的草图文件。

4.jpg

Arduino代码

// constants won't change. They're used here to
// set pin numbers:
const int buttonPin = 8; // the number of the pushbutton pin
const int ledPin = 2; // the number of the LED pin
// variables will change:
int buttonState = 0; // variable for reading the pushbutton status

void setup() {
   // initialize the LED pin as an output:
   pinMode(ledPin, OUTPUT);
   // initialize the pushbutton pin as an input:
   pinMode(buttonPin, INPUT);
}

void loop() {
   // read the state of the pushbutton value:
   buttonState = digitalRead(buttonPin);
   // check if the pushbutton is pressed.
   // if it is, the buttonState is HIGH:
   if (buttonState == HIGH) {
      // turn LED on:
      digitalWrite(ledPin, HIGH);
   } else {
      // turn LED off:
      digitalWrite(ledPin, LOW);
   }
}

代码说明

当开关打开时(按钮未被按下),按钮的两个端子之间没有连接,因此引脚接地(通过下拉电阻),我们读取低电平。当开关闭合时(按钮被按下),它在其两个端子之间建立连接,将引脚连接到5伏,这样我们读出高电平。

结果

按下按钮时LED亮灯,松开按钮时LED熄灯。








Arduino 读取模拟电压

此示例将向你展示如何读取模拟引脚0上的模拟输入。输入从analogRead()转换为电压,并打印输出到Arduino软件(IDE)的串口监视器。

必需的组件

你将需要以下组件:

  • 1 × Breadboard 面包板
  • 1 × Arduino Uno R3
  • 1 × 5K可变电阻(电位器)
  • 2 × 跳线

程序

按照电路图连接面包板上的组件,如下图所示。

1.png2.png

电位器

电位器是一种简单的机电传感器。它将来自输入操作器的旋转或线性运动转换为电阻的变化。这种变化是(或可以)用于控制任何东西,从高保真音响系统到巨大的集装箱船的方向。

我们知道电位器最初被称为变阻器(本质上是一个可变的绕线电阻)。现在可用的电阻器的种类现在相当惊人的,而初学者(特别是)可能很难确定哪种类型适合于给定的任务。一些不同的电阻器类型,都可以用于相同的任务,使得确定工作更难。

3.jpg1.png

左边的图像显示电阻器的标准原理图符号。右边的图像是电位器。

草图

在计算机上打开Arduino IDE软件。使用Arduino语言进行编码控制你的电路。通过单击“New”打开一个新的草图文件。

4.jpg

Arduino代码

void setup() {
   // 初始化串行通信速率为9600bit/s:
   Serial.begin(9600);
}

void loop() {
   // 读取模拟引脚A0的输入数据
   int sensorValue = analogRead(A0);
   // 将模拟信号转换成电压
   float voltage = sensorValue * (5.0 / 1023.0);
   // 打印到串口监视器
   Serial.println(voltage);
}

代码说明

在下面给出的程序或草图中,你在设置功能中做的第一件事是在你的电路板和你的电脑之间以9600比特每秒开始串行通信,使用以下代码:

Serial.begin(9600);

在代码的主循环中,你需要建立一个变量来存储来自电位器的电阻值(其范围在0到1023之间,非常适合int数据类型):

int sensorValue = analogRead(A0);

要将值从0-1023更改为与引脚正在读取的电压相对应的范围,你需要创建另一个变量,一个浮点数并进行一些计算。要缩小0.0和5.0之间的数字,将5.0除以1023.0,再乘以sensorValue:

float voltage= sensorValue * (5.0 / 1023.0);

最后,你需要将此信息打印到串行窗口。你可以用最后一行代码中的Serial.println()命令:

Serial.println(voltage)

现在,通过单击顶部绿色栏右侧的图标或按Ctrl+Shift+M打开Arduino IDE中的串口监视器。

5.jpg

结果

你会看到一个从0.0到5.0的稳定的数字流。当你转动电位器时,这些值会改变,对应于引脚A0上的电压。




Arduino SPI 串行外设接口

串行外设接口(SPI)总线是用于串行通信的系统,最多可使用四个导体,通常为三个。一个导体用于数据接收,一个导体用于数据发送,一个导体用于同步,另一个导体用于选择与之通信的设备。它是一个全双工连接,这意味着数据是同时发送和接收的。最大波特率高于I2C通信系统中的波特率。

板的SPI引脚

SPI使用以下四条线:

  • SCK - 这是由主机驱动的串行时钟。        

  • MOSI - 这是由主机驱动的主输出/从输入。        

  • MISO - 这是由主机驱动的主输入/从输出。        

  • SS - 这是从机选择线。        

使用以下函数,必须包括SPI.h.

  • SPI.begin() - 通过将SCK,MOSI和SS设置为输出来初始化SPI总线,将SCK和MOSI拉低,将SS拉高。        

  • SPI.setClockDivider(分频器) - 相对于系统时钟设置SPI时钟分频器。在基于AVR的板上,可用的分频器为2,4,8,16,32,64或128。默认设置为SPI_CLOCK_DIV4,它将SPI时钟设置为系统时钟的四分之一(对于20 MHz的电路板为5 Mhz)。

  • Divider - 它可以是(SPI_CLOCK_DIV2,SPI_CLOCK_DIV4,SPI_CLOCK_DIV8,SPI_CLOCK_DIV16,SPI_CLOCK_DIV32,SPI_CLOCK_DIV64,SPI_CLOCK_DIV128)。        

  • SPI.transfer(val) - SPI传输基于同时发送和接收:接收的数据在receivedVal中返回。        

  • SPI.beginTransaction(SPISettings(speedMaximum,dataOrder,dataMode)) - speedMaximum是时钟,dataOrder(MSBFIRST或LSBFIRST),dataMode(SPI_MODE0,SPI_MODE1,SPI_MODE2或SPI_MODE3)。        

SPI中有四种操作模式,如下所示:

  • 模式0(默认值) - 时钟通常为低电平(CPOL = 0),数据在从低电平到高电平(前沿)(CPHA = 0)的转换时采样。        

  • 模式1 - 时钟通常为低电平(CPOL = 0),数据在从高电平到低电平(后沿)(CPHA = 1)的转换时采样。        

  • 模式2 - 时钟通常为高电平(CPOL = 1),数据在从高电平到低电平(前沿)(CPHA = 0)的转换时采样。        

  • 模式3 - 时钟通常为高电平(CPOL = 1),数据在从低电平到高电平(后沿)(CPHA = 1)的转换时采样。        

  • SPI.attachInterrupt(handler) - 当从设备从主设备接收数据时调用的函数。        

现在,我们将两个Arduino UNO板连接在一起;一个作为主机,另一个作为从机。

  • (SS):引脚10
  • (MOSI):引脚11
  • (MISO):引脚12
  • (SCK):引脚13

接地是常见的。以下是两个电路板之间的连接的图示:

1.jpg

让我们看看SPI作为主机和SPI作为从机的例子。

SPI为主机

例子

#include <SPI.h>

void setup (void) {
   Serial.begin(115200); //set baud rate to 115200 for usart
   digitalWrite(SS, HIGH); // disable Slave Select
   SPI.begin ();
   SPI.setClockDivider(SPI_CLOCK_DIV8);//divide the clock by 8
}

void loop (void) {
   char c;
   digitalWrite(SS, LOW); // enable Slave Select
   // send test string
   for (const char * p = "Hello, world!\r" ; c = *p; p++) {
      SPI.transfer (c);
      Serial.print(c);
   }
   digitalWrite(SS, HIGH); // disable Slave Select
   delay(2000);
}

SPI为从机

例子

#include <SPI.h>
char buff [50];
volatile byte indx;
volatile boolean process;

void setup (void) {
   Serial.begin (115200);
   pinMode(MISO, OUTPUT); // have to send on master in so it set as output
   SPCR |= _BV(SPE); // turn on SPI in slave mode
   indx = 0; // buffer empty
   process = false;
   SPI.attachInterrupt(); // turn on interrupt
}
ISR (SPI_STC_vect) // SPI interrupt routine { 
   byte c = SPDR; // read byte from SPI Data Register
   if (indx < sizeof buff) {
      buff [indx++] = c; // save data in the next index in the array buff
      if (c == '\r') //check for the end of the word
      process = true;
   }
}

void loop (void) {
   if (process) {
      process = false; //reset the process
      Serial.println (buff); //print the array on serial monitor
      indx= 0; //reset button to zero
   }
}





***Arduino采集信号采集的是电流还是电压***

Arduino采集的信号通常是电压信号。在电子项目中,Arduino的模拟输入引脚(如A0到A5)能够读取外部电路提供的电压值。这些引脚通过内部的模数转换器(ADC)将模拟电压信号转换为数字值,以便单片机处理。用户可以通过编写代码来读取这些数字值,进而根据电压与实际参数(如温度、光照强度、声音水平等)之间的关系,计算出相应的物理量。


例如,当你连接一个传感器(如温度传感器DS18B20或光敏电阻)到Arduino的模拟输入时,实际上是将传感器产生的电压变化转换为数字信号。如果需要测量电流,通常需要在电路中串联一个已知的电阻(称为采样电阻),并将这个电阻的两端连接到Arduino,通过测量这个电阻上的电压降,利用欧姆定律计算出电流。


因此,虽然Arduino直接采集的是电压信号,但通过适当的电路设计,可以间接测量电流、温度、光照等其他物理量。


以下是一个简单的Arduino程序示例,用于通过模拟输入端口A0采集电压,并在串口监视器上显示该电压值。这个例子假设你已经连接了一个电位器,其输出电压范围为0到5V,电位器的一端连接到5V,另一端连接到GND,中间的滑动端连接到Arduino的A0模拟输入端口。

void setup() {

  // 初始化串口通信,便于在电脑上查看数据

  Serial.begin(9600); // 设置串口波特率为9600

}


void loop() {

  // 从A0模拟输入端口读取电压值

  int analogValue = analogRead(A0); // 读取的值范围是0到1023

  

  // 将读取的模拟值转换为电压值

  // Arduino的ADC参考电压默认为5V,因此电压计算公式为:

  // 电压 = (模拟值 / 1023) * 5V

  float voltage = (analogValue * 5.0) / 1023.0;

  

  // 在串口监视器上显示电压值

  Serial.print("当前电压: ");

  Serial.print(voltage);

  Serial.println(" V"); // 打印单位

  

  // 延时1秒,避免串口监视器刷新过快

  delay(1000);

}


在Arduino中,设置参考电压是为了改变模拟输入(ADC)的基准值,从而影响到测量精度和范围。以下是几种设置参考电压的方法


1. 使用默认参考电压

默认情况下,Arduino的模拟输入使用的是其工作电压作为参考电压,通常是5V(对于UNO等基于ATmega328P的板子)。


2. 设置内部参考电压

如果你想使用Arduino的内部参考电压,可以使用analogReference()函数。例如,设置为1.1V的内部参考电压(适用于ATmega328P的Arduino板):


void setup() {

  analogReference(INTERNAL); // 设置为1.1V内部参考电压

}

3. 使用外部参考电压

如果你想通过AREF引脚使用外部电压作为参考,需要确保这个电压在0V到工作电压之间(通常是5V),并且在调用analogRead()之前设置:


void setup() {

  analogReference(EXTERNAL); // 设置为外部参考电压,确保AREF引脚已连接合适的电压

}

注意事项:

改变参考电压后,首次读数可能不准确,因为ADC需要时间稳定。

使用外部参考电压时,确保电压稳定且不超过允许范围,以避免损坏Arduino。

不要将低于0V或高于工作电压的电压加到AREF引脚。

对于不同的Arduino板,可用的内部参考电压可能不同,如Arduino Mega的内部参考电压选项就与UNO不同。

通过调整参考电压,你可以根据项目需求提高测量的分辨率或适应特定的传感器电压范围,但同时要注意这可能引入额外的误差来源,特别是如果外部参考电压不稳定时。



Arduino 极客教程

Arduino 极客教程

步进电机控制

A4988电机驱动板

A4988是一款十分普及且价格实惠的步进电机驱动板。尤其是在3D打印以及数控机床领域,A4988十分常见。在使用Arduino通过A4988控制步进电机时,我们还需要一个额外的电子元件,那就是电解电容。该电容作用是为了保护A4988控制板。

首先让我们来看一下A4988电机驱动板的引脚功能:



A4988电机驱动板引脚说明    
A4988电机驱动板引脚说明 以上示意图引自dronebot workshop(www.dronebotworkshop.com)网站



VMOT – 电机电源正极(可用电源电压为8V ~ 35V)注:此引脚用于连接为电机供电的电源
GND – 电机电源接地
2B, 2A – 电机绕组2控制引脚
1A, 1B – 电机绕组1控制引脚
VDD – 逻辑电源正极(3 – 5.5伏)注:此引脚用于为A4988电机驱动板供电
GND – 逻辑电源接地

ENABLE – 使能引脚(低电平有效)
引脚说明:此当此引脚为低电平时,A4988才能进行电机驱动工作,当该引脚为高电平,A4988将不会进行电机驱动工作。如果该引脚悬空,则A4988默认为使能状态。即该引脚没有连接任何电平时,A4988可以正常工作。
MS1, MS2, MS3 – 驱动模式引脚
引脚说明:这三个引脚控制A4988微步细分驱动模式。通过这三个引脚的逻辑电平,我们可以调整A4988驱动电机模式为全、半、1/4、1/8 及 1/16 步进模式。上图中右侧的表格里有具体如何调节这三个引脚电频以及A4988在不同的电平组合下的驱动模式。表格中“L”代表低电平,“H”代表高电平。“FULL”为全步进,“HALF”为半步进,“QUATER”为1/4步进, “EIGHTH”为1/8步进, “SIXTEENTH”为1/16步进。当MS1, MS2, MS3这几个引脚悬空时,A4988默认为全步进电机驱动模式。
RESET – 复位引脚
引脚说明:该引脚为低电平有效,即当该引脚为低电平时,A4988将复位。如果该引脚悬空,则A4988默认为高电平。即该引脚没有连接任何电平时,A4988可以正常工作。
SLEEP – 睡眠引脚
引脚说明:当该引脚连接电平为低电平时,A4988将进入低能耗睡眠状态,即消耗最小的电能。如果无需使用SLeep功能,则可以将SLEEP引脚与RESET引脚连接,则A4988将持续保持正常能耗状态而不会进入低能耗状态。
STEP – 步进引脚
引脚说明:此引脚用于通过Arduino等微控制器向A4988发送脉冲控制信号,A4988接收到此信号后,会根据 MS1, MS2 和 MS3引脚控制电机运转。
DIR – 方向引脚
引脚说明:通过此引脚可以调整A4988控制电机运行方向。当此引脚为低电平,A4988将控制电机顺时针旋转。高电平则逆时针旋转。

Arduino通过A4988电机驱动板控制NEMA电机电路连接(简化版)

A4988电机驱动板只需要两个Arduino引脚即可控制步进电机运行。



Arduino控制Nema-17步进电机    
Arduino控制Nema-17步进电机(简化版)



请留意在以上电路中,A4988电机电源引脚上连接了一个100uF的电解电容(电解电容引脚有正负极之分,电容正极引脚应接在A4988电机电源正极引脚,相反的电解电容负极引脚接在A4988电机电源接地引脚)。该电容可以起到A4988驱动板电源保护的作用。如果没有100uF的电解电容,可以使用任何大于47uF的电解电容来替换。请将该电容尽量安装在靠近VMOT和GND引脚。

其它接线连接相对简单,请根据以上示意图进行相应连接。

此示例为简化版电路连接,在此模式下:
1. A4988模块只能全步进驱动,您无法通过Arduino控制A4988进行半步进或微步模式驱动。
2. A4988模块的SLEEP/ENABLE/RESET功能不可用
如需使用这些功能请参考Arduino通过A4988模块控制步进电机(完整版)

注意:连接好接线后,请不要马上通电我们还要进行一项非常重要的工作:A4988电流调节


A4988 Vref电压调节

所谓Vref电压调节,就是通过A4988上的电位器旋钮调节Vref参考电压。该电压将直接影响到步进电机在工作时流过线圈的电流强度。请注意,这一步操作非常重要,如果忽略这一步有可能会产生电机损坏的后果。

A4988 Vref参考电压计算公式:

Vref = Imax X Rcs X 8

Rcs: 市面上A4988模块上的Rcs电阻值一般有3种类型,0.05 欧姆, 0.1 欧姆或0.2 欧姆。大部分A4988模块顺时针旋转电位器可调大Vref,逆时针旋转电位器调小Vref。

Vref: 电位器金属旋钮和GND之间的电压即为Vref。可使用小改锥旋转电位器旋钮从而改变Vref。(如下图所示)

Imax:步进电机工作时,线圈允许流过的最大电流



使用万用表测量A4988步进电机驱动模块Vref参考电压    
使用万用表测量A4988步进电机驱动模块Vref参考电压



例:Rcs为0.1欧姆,电机Imax为1.5A,通过以上公式计算,Vref参考电压为1.2 V。即:我们将通过调节电位器旋钮 将Vref调节为1.2伏特左右。

测量Vref时注意:

1. A4988模块的VDD引脚需要连接+5V电压(可连接Arduino的+5V引脚)
2. A4988模块的GND引脚必须与电路中的其它设备共地(可连接Arduino的GND引脚)
3. A4988模块的VMOT引脚无需通电,但是通电也不影响Vref的测量。
4. A4988模块的1A/1B/2A/2B引脚可以不与步进电机连接,但连接也不会影响Vref的测量
5. Arduino无需运行任何程序



Arduino通过A4988电机驱动板控制NEMA电机示例程序(简化版)

无法复制示例程序代码?

/*

  Arduino控制NEMA步进电机测试程序

  by 太极创客()

  本示例程序旨在演示如何通过Arduino控制NEMA步进电机。

  如需获得本示例程序详细电路信息以及如何使用Arduino控制电机的更多知识,请参考太极创客网站:

  http://www.taichi-maker.com/homepage/reference-index/motor-reference-index/arduino-a4988-nema-stepper-motor/

*/

 

// 定义电机控制用常量

 

// A4988连接Arduino引脚号

const int dirPin = 2;  // 方向引脚

const int stepPin = 3; // 步进引脚

 

// 电机每圈步数

const int STEPS_PER_REV = 200;

 

void setup() {

  

  // Arduino控制A4988步进和方向的引脚为输出模式

  pinMode(stepPin,OUTPUT); 

  pinMode(dirPin,OUTPUT);

}

void loop() {

  

  // 设置电机顺时针旋转

  digitalWrite(dirPin,LOW); 

  

  // 电机慢速旋转

  for(int x = 0; x < STEPS_PER_REV; x++) {

    digitalWrite(stepPin,HIGH); 

    delayMicroseconds(2000); 

    digitalWrite(stepPin,LOW); 

    delayMicroseconds(2000); 

  }

  

  // 等待一秒

  delay(1000); 

  

  // 设置电机逆时针旋转

  digitalWrite(dirPin,HIGH);

  

  // 电机快速旋转

  for(int x = 0; x < (STEPS_PER_REV * 2); x++) {

    digitalWrite(stepPin,HIGH);

    delayMicroseconds(1000);

    digitalWrite(stepPin,LOW);

    delayMicroseconds(1000);

  }

  

  // 等待一秒

  delay(1000);

}

Arduino通过A4988电机驱动板控制NEMA电机电路连接(完整版)



Arduino通过A4988模块控制步进电机电路连接说明(完整版)    
Arduino通过A4988模块控制步进电机电路连接说明(完整版)




Arduino通过A4988电机驱动板控制NEMA电机示例程序(完整版)

无法复制示例程序代码?

/*

  Arduino控制NEMA步进电机测试程序 (2018-09-25)

  by 太极创客(www.taichi-maker.com)

  本示例程序旨在演示如何通过Arduino控制NEMA步进电机。

  用户可通过串口监视器控制电机的各个功能

  如需获得本示例程序详细电路信息以及如何使用Arduino控制电机的更多知识,请参考太极创客网站:

  http://www.taichi-maker.com/homepage/reference-index/motor-reference-index/arduino-a4988-nema-stepper-motor/

  

  控制指令:

  顺时针旋转/逆时针旋转 - x0/x1

  运行步数 - z100(走100步)

  步进方式 - b1(全步),b2(半步),b4(四分),b8(8分),b16(16分)

  速度 - d2000(转动速度2000)

  允许工作/禁止工作(enable/disable) - g1/g0

  睡眠 - m0(sleep)/m1(awake)  

*/

 

// A4988引脚连接Arduino引脚编号

const int dirPin   = 2;   // Direction

const int stepPin  = 3;   // Step

const int sleepPin = 4;   // Sleep

const int resetPin = 5;   // Reset

const int ms3Pin   = 6;   // Ms3

const int ms2Pin   = 7;   // Ms2

const int ms1Pin   = 8;   // Ms1

const int enPin    = 9;   // Enable

 

// 步进电机旋转一周步数

const int STEPS_PER_REV = 200;

 

char cmd;  //用户指令字符

int data;  //用户指令数据

int motorSpeed = 2000;  //电机转速(数值越小速度越小)

 

void setup() { 

  // 设置引脚模式

  pinMode(stepPin,OUTPUT); 

  pinMode(dirPin,OUTPUT);

  pinMode(sleepPin,OUTPUT); 

  pinMode(resetPin,OUTPUT);  

  pinMode(ms3Pin,OUTPUT);  

  pinMode(ms2Pin,OUTPUT);  

  pinMode(ms1Pin,OUTPUT); 

  pinMode(enPin,OUTPUT);   

 

  // 初始化引脚状态

  digitalWrite(sleepPin, HIGH);  

  digitalWrite(resetPin, HIGH); 

  digitalWrite(enPin, LOW); 

 

  // 初始化电机步进模式为全步进

  digitalWrite(ms1Pin, LOW); 

  digitalWrite(ms2Pin, LOW); 

  digitalWrite(ms3Pin, LOW); 

 

  Serial.begin(9600);

  Serial.println("++++++++++++++++++++++++++++++++++");     

  Serial.println("+ Taichi-Maker A4988 Steper Demo +");   

  Serial.println("+     www.taichi-maker.com       +");  

  Serial.println("++++++++++++++++++++++++++++++++++");  

  Serial.println("");  

  Serial.println("Please input motor command:"); 

}

 

void loop() {    

  if (Serial.available()) {     // 检查串口缓存是否有数据等待传输 

    cmd = Serial.read();        // 获取电机指令中电机编号信息    

    Serial.print("cmd = ");

    Serial.print(cmd);    

    Serial.print(" , "); 

 

    data = Serial.parseInt();

    Serial.print("data = ");

    Serial.print(data);   

    Serial.println("");    

 

    runUsrCmd();

  }

 

//此函数用于运行用户指令

void runUsrCmd(){

  switch(cmd){ 

    case 'x':    // 设置步进电机旋转(顺时针/逆时针)

      Serial.print("Set Rotation To "); 

      if (data == 0){

        digitalWrite(dirPin, 0);

        Serial.println("Clockwise."); 

      } else {

        digitalWrite(dirPin, 1);

        Serial.println("Counter Clockwise."); 

      }

      break;

    

    case 'g':   // 设置A4988 enable功能

      Serial.print("Set Motor To "); 

      if (data == 0){

        digitalWrite(enPin, 1);

        Serial.println("Disable."); 

      } else {

        digitalWrite(enPin, 0);

        Serial.println("Enable."); 

      }

      break;

      

    case 'm':  // 设置A4988 sleep功能

      Serial.print("Set Motor To "); 

      if (data == 0){

        digitalWrite(sleepPin, 0);

        Serial.println("Sleep."); 

      } else {

        digitalWrite(sleepPin, 1);

        Serial.println("Awake."); 

      }

      break;

 

    case 'b':   // 设置步进模式

      if (data == 1 || data == 2  || data == 4  || data == 8 || data == 16){

        Serial.print("Set Motor Step Control To "); 

        setStepMode(data);

      } else {

        Serial.println("Wrong Step Mode Cmd!");

      }

      break;

 

    case 'z': // 设置步进电机运行步数

      runStepper(motorSpeed, data);

      break;

 

    case 'd': // 设置步进电机运行速度      

      motorSpeed = data;

      Serial.print("Set Motor Speed To ");

      Serial.println(data);

      break;

          

    default:  // 未知指令

      Serial.println("Unknown Command");

  }

}

 

//运行步进电机

void runStepper (int rotationSpeed, int stepNum){

  for(int x = 0; x < stepNum; x++) {

    digitalWrite(stepPin,HIGH); 

    delayMicroseconds(rotationSpeed); 

    digitalWrite(stepPin,LOW); 

    delayMicroseconds(rotationSpeed); 

  }  

}

 

 //设置步进模式

void setStepMode(int modeNum){ 

  switch(modeNum){ 

    case 1:   // 全步进

    digitalWrite(ms1Pin, LOW); 

    digitalWrite(ms2Pin, LOW); 

    digitalWrite(ms3Pin, LOW);  

    Serial.println(F("Stepping Mode: Full"));

    break; 

 

    case 2:  // 半步进  

    digitalWrite(ms1Pin, HIGH); 

    digitalWrite(ms2Pin, LOW); 

    digitalWrite(ms3Pin, LOW);  

    Serial.println(F("Stepping Mode: 1/2"));

    break; 

 

    case 4:  // 1/4 步进   

    digitalWrite(ms1Pin, LOW); 

    digitalWrite(ms2Pin, HIGH); 

    digitalWrite(ms3Pin, LOW);  

    Serial.println(F("Stepping Mode: 1/4"));

    break;     

 

    case 8:  // 1/8 步进   

    digitalWrite(ms1Pin, HIGH); 

    digitalWrite(ms2Pin, HIGH); 

    digitalWrite(ms3Pin, LOW);  

    Serial.println(F("Stepping Mode: 1/8"));

    break;  

 

    case 16:  // 1/16 步进   

    digitalWrite(ms1Pin, HIGH); 

    digitalWrite(ms2Pin, HIGH); 

    digitalWrite(ms3Pin, HIGH); 

    Serial.println(F("Stepping Mode: 1/16")); 

    break;    

  }      

}

使用CNC电机扩展板配合A4988驱动步进电机

Arduino CNC 电机扩展板概述

CNC是计算机数字控制(Computerized Numerical Control )的英文缩写。顾名思义,Arduino CNC电机扩展板常用于驱动3D打印机、机械臂或机器人系统中的NEMA17电机(俗称42步进电机)。CNC扩展板可以支持A4988、DRV8825等步进电机驱动板。



Arduino CNC 电机扩展板外观    
Arduino CNC 电机扩展板外观



使用一个CNC电机扩展板配合A4988驱动板可以让Arduuino一次控制1台-4台42步进电机,而且省去了连线的麻烦。如果您想了解如何操作,请点击以下链接进入本站相关教程页面

Arduino CNC电机扩展板详解

A4988电机驱动板常见问题

1)4988驱动板可以驱动多大的电流?
如果4988芯片上没有加散热片,电流最好在1.2A以下。如果加散热片,电流可以达到2A。

2)步进电机的连接方式是什么?
如果你的电机线是标准的红蓝绿黑的颜色,可以按照颜色连接为:红-蓝-绿-黑,或相反:黑-绿-蓝-红。

3)接上电后,电机左右抖动不能正常运行是什么原因?
电机出现抖动一般有两个原因,一是缺相:可能是4988与电机没有接好导致4988的输出端某一相断开,造成电机缺相从而抖动。也有可能是步进电机接线接错:如果步进电机没有正确的进行连接,电机会出现抖动的情况。

4)电机丢步如何解决?
相电流的大小跟步进电机的扭力有直接关系,如果感觉你的步进电机扭力不足、丢步,可能是4988输出的电流太小。您可以调节A4988模块上的电位器调节A4988模块的输出电流强度。具体调节方法请参考本页面中关于Vref电压的调节方法。请注意:如A4988输出电流强度大于电机工作电流强可能会烧毁您的步进电机。请小心调节。

5)4988可以驱动两相六线或两相五线的电机吗?
可以,两相连线按照问题2连接,将中间抽头悬空即可。

6)电机停止转动时会有滋滋的电流声。
这是正常现象。步进电机有一个参数-步距角。通过细分驱动可以缩小步进角度,比如步距角为1.8度的步进电机,采用16细分,最小可以走的角度是1.8/16=0.1125度。由于这个角度非常小,当电机停止时,可能处于非电机物理位置(1.8度为一个物理位置)。因此步进电机在静止时会有电流声,这属于正常现象,不用担心。






Arduino烧写软件程序

用命令行给Arduino烧写软件程序
arduino远程刷新(烧录)固件

使用Arduino开发ESP32:固件更新演示

Arduino 一次性烧录引导程序和用户程序的方法

USB-ISP烧写器

Arduino编译bootloader(示例代码)

Mega2560(Arduino)Bootloader烧录指南

Arduino Mega2560 作为烧录器烧写Mega2560的bootloader(试成功的)

ESP32烧录Arduino生成的bin文件

用Arduino UNO板子烧写Arduino 2560板子脚对应

#define PIN_MOSI        51    //mega2560的51脚对应UNO的MOSI 11

#define PIN_MISO        50   //mega2560的50脚对应UNO的MISO 12

#define PIN_SCK         52   //mega2560的52脚对应UNO的SCK 13


也就是UNO 10脚对应 2560 RESET

也就是UNO 11 对应 2560的51

也就是UNO 12对应 2560的50

也就是UNO 13 对应 2560的52


Arduino烧录程序如何保护知识产权



用命令行给Arduino烧写软件程序

有挺多工具软件可以完成脱离Arduino IDE烧录Hex二进制文件的工作,他们大多是通过GUI前端在后台调用avrdude程序。大多数情况下,他们可以很好的工作,但是要么是不跨平台,只能用于Windows(这类工具Windows版本的比较多,也有些是python编写的,但是又打包为exe文件运行,也是只支持Windows),要么就是工具本身有些限制。


我实验了用命令行的方式来烧写Hex文件,这样的话,只要系统内安装有Arduino IDE,甚至是只要安装了avrdude程序,就可以方便的烧写Hex文件,而且可以跨平台,无论Windows,macOS,或者是Linux(树莓派?)都可以工作。


首先,我的系统里安装了Arduino IDE v1.8.9。 目前,这个版本是最新版。


确定Arduino IDE编译好的Hex文件的位置

在File菜单的Preferences里面,打开preferences.txt文件,在更改之前,关闭Arduino IDE。

新建一个文件夹C:\Users\zz\Documents\Arduino\hex (zz是我的Windows登录用户名)

在preferences.txt文件里加入如下一行, 并保存

build.path=C:\Users\zz\Documents\Arduino\hex

对于macOS的话

build.path=/Users/zz/Documents/Arduino/hex

打开Arduino IDE,并打开Blink示例程序,Ctrl-R编译后,会在新建的文件夹中看到编译好的Blink.ino.hex文件

C:\Users\zz\Documents\Arduino\hex>dir

 驱动器 C 中的卷没有标签。

 卷的序列号是 1234-5678


 C:\Users\zz\Documents\Arduino\hex 的目录


2019/06/08  14:28    <DIR>          .

2019/06/08  14:28    <DIR>          ..

2019/06/08  14:28                13 Blink.ino.eep

2019/06/08  14:28            13,920 Blink.ino.elf

2019/06/08  14:28             2,640 Blink.ino.hex

2019/06/08  14:28             4,000 Blink.ino.with_bootloader.hex

2019/06/08  14:28               968 build.options.json

2019/06/08  14:28    <DIR>          core

2019/06/08  14:28               397 includes.cache

2019/06/08  14:28    <DIR>          libraries

2019/06/08  14:28    <DIR>          preproc

2019/06/08  14:28    <DIR>          sketch

               6 个文件         21,938 字节

               6 个目录 31,183,998,976 可用字节

尝试用Arduino IDE下载程序,并观察编译和烧写过程

在Arduino IDE中的preferences设置对话框中show verbose output during: 选中compilation和upload,保存设置

打开Blink示例程序,连接Arduino Uno并Ctrl-U(Upload)

然后在主界面的信息提示区域,会打印输出编译和烧写过程。输出内容很多,最关键的是avrdude的命令行

C:\arduino-1.8.9\hardware\tools\avr/bin/avrdude -CC:\arduino-1.8.9\hardware\tools\avr/etc/avrdude.conf -v -patmega328p -carduino -PCOM3 -b115200 -D -Uflash:w:C:\Users\zz\Documents\Arduino\hex/Blink.ino.hex:i

我也尝试了Arduino Micro (更换不同的板子后,需要注意Arduino IDE的board和Port的设定变更),命令行是这样的:

C:\arduino-1.8.9\hardware\tools\avr/bin/avrdude -CC:\arduino-1.8.9\hardware\tools\avr/etc/avrdude.conf -v -patmega32u4 -cavr109 -PCOM4 -b57600 -D -Uflash:w:C:\Users\zz\Documents\Arduino\hex/Blink.ino.hex:i

可以清楚的看到Arduino IDE调用avrdude烧写hex文件,以及相关命令行参数

在macOS系统下,我看到的是

/Applications/Arduino.app/Contents/Java/hardware/tools/avr/bin/avrdude -C/Applications/Arduino.app/Contents/Java/hardware/tools/avr/etc/avrdude.conf -v -patmega328p -carduino -P/dev/cu.usbmodem141301 -b115200 -D -Uflash:w:/Users/zz/Documents/Arduino/hex/Blink.ino.hex:i 

avrdude的参数

-C 制定配置文件的位置.

对于Windows系统的例子

-CC:\arduino-1.8.9\hardware\tools\avr/etc/avrdude.conf

对于macOS系统的例子

-C/Applications/Arduino.app/Contents/Java/hardware/tools/avr/etc/avrdude.conf

-v 打印详细的提示信息.

-p 必须项. 指定AVR零件号.

对于Arduino Uno

-patmega328p

对于Arduino Micro

-patmega32u4

-c 指定编程器的类型.

对于Arduino Uno

-carduino

对于Arduino Micro

-cavr109

-P 指定连接的串口.

对于Windows系统的例子

-PCOM3

对于macOS系统的例子

-P/dev/cu.usbmodem141301

-b 指定RS-232串口波特率.

对于Arduino Uno

-b115200

对于Arduino Micro

-b57600

-D 禁止自动擦除闪存空间

-U :r|w|v:[:format]

指定存储类型,操作方式,hex文件名以及文件类型.

对于Windows系统的例子

-Uflash:w:C:\Users\zz\Documents\Arduino\hex/Blink.ino.hex:i

对于macOS系统的例子

-Uflash:w:/Users/zz/Documents/Arduino/hex/Blink.ino.hex:i

命令行烧写hex文件

这样我们就可以用命令行很便捷的烧写hex文件到Arduino里了


Windows

Uno


C:\arduino-1.8.9\hardware\tools\avr\bin\avrdude -CC:\arduino-1.8.9\hardware\tools\avr\etc\avrdude.conf -v -patmega328p -carduino -PCOM3 -b115200 -D -Uflash:w:C:\Users\zz\Documents\Arduino\hex\Blink.ino.hex:i

Micro


C:\arduino-1.8.9\hardware\tools\avr\bin\avrdude -CC:\arduino-1.8.9\hardware\tools\avr\etc\avrdude.conf -v -patmega32u4 -cavr109 -PCOM4 -b57600 -D -Uflash:w:C:\Users\zz\Documents\Arduino\hex\Blink.ino.hex:i

macOS

Uno


/Applications/Arduino.app/Contents/Java/hardware/tools/avr/bin/avrdude -C/Applications/Arduino.app/Contents/Java/hardware/tools/avr/etc/avrdude.conf -v -patmega328p -carduino -P/dev/cu.usbmodem141301 -b115200 -D -Uflash:w:/Users/zz/Documents/Arduino/hex/Blink.ino.hex:i 

Micro


/Applications/Arduino.app/Contents/Java/hardware/tools/avr/bin/avrdude -C/Applications/Arduino.app/Contents/Java/hardware/tools/avr/etc/avrdude.conf -v -patmega32u4 -cavr109 -P/dev/cu.usbmodem141401 -b57600 -D -Uflash:w:/Users/zz/Documents/Arduino/hex/Blink.ino.hex:i 

注意

对于32u4芯片的Arduino板子,必须在烧写前先按一下板子上的Reset按钮,等1-3秒再执行烧写命令

如果有新的芯片类型或者不同板子的Arduino及其兼容版,只要能先尝试用Arduino IDE编译后烧写,得到烧写相关的命令行信息,就可以手动的用命令行烧写

总结

命令行烧写hex文件比用IDE打开源文件烧写要快捷很多

用于烧写的命令行可以在其他程序(比如Python)中调用,非常方便用于二次开发



arduino远程刷新(烧录)固件

在车间部署了十几个网络版的温湿度采集器(基于arduino的),这些采集器分布在不同的地方,现在要更新一下上面的固件。最笨的方法是一个一个地取下来,插到电脑的USB接口上进行固件更新,这样做显然很麻烦。能不能直接通过网络(以太网)进行固件的更新呢?


    我查了一下资料,发现网上只有通过蓝牙更新的固件的,但是没有以太网的。低功耗的商用的蓝牙,其传输距离只有10米,且那个方法必须在板上进行手动复位。我研究了一下,发现了通过以太网刷新固件的方法,现跟大家分享一下。


 


1、原理:


(1)通过串口转wifi模块发送一个指令,让arduino复位。


(2)然后开始传送编译好的二进制文件,arduino在重启的时候,会将这个文件写入到flash中。avr固件的烧录原理请见:http://news.eeworld.com.cn/mcu/2013/0608/article_13291.html


 


2、硬件:


Arduino uno + 串口转wifi模块。


线路连接:2号IO口串联一个550k电阻接到reset上。


 


我所用的Wifi模块是在这家店买的:


http://item.taobao.com/item.htm?spm=a1z09.2.9.41.0CxtvI&id=36815717425&_u=blmt59h45fc


 


3、所需要工具软件:


  (1)avrdude。版本013.9.18,在附件的bin文件夹下。这个程序可用来将编译好的二进制文件刷写(烧录)到arduino中,它需要用到串口。


  (2)VSPM虚拟串口服务器,下载地地址:http://www.kinghwawin.com/DownloadShow.asp?ID=40。


    这个软件的作用是将TCP服务器虚拟成本地的串口,用于跟avrdude配合使用,即提供一个串口给avrdude使用。下载完毕之后,请按默认路径安装。启动该程序,然后新建一个虚拟串口,将串口号设置为“COM256”。如图所示:

1.jpg

(3)TCP/UDP调试工具。此工具的作用是给远端的arduino控制器发送复位指令。


4、arduino代码

void setup()


{


    Serial.begin(115200);


    pinMode(2,OUTPUT);


    digitalWrite(2, HIGH);   // switch off


}


 


void loop()


{


   if (Serial.available() > 0)


   {


       char val;


       val = Serial.read();     // read serial data


       if(val == 'r')


       {


          digitalWrite(2, LOW);    // 将reset管脚的电平拉低50ms,起到复位的作用


           delay(50);


          digitalWrite(2, HIGH); // 


      } 


  }


}


 


注:arduino中必须有以上的这些代码才能被远程刷新。请将这些代码嵌入到你的arduion中,然后用usb线刷新到arduino中。


 


5、在arduino编辑器中找到编译好的固件(.hex文件)


    默认情况下,arduino会在后台将源码进行编译成二进制文件,然后下载到arduino控制器中,这个过程不会有提示。


    可以通过一些方法来显示编译烧录的过程,并且提取hex文件,详细方法见链接:http://see.sl088.com/wiki/Arduino_%E6%8F%90%E5%8F%96hex


    这个步骤完成之后,我们手头上就有了一个后缀名为hex的二进制文件。


    附件中的bin文件夹下,有一个名为的Blink.cpp.hex文件,它是官方提供的例子编译之后的二进制文件,可以拿这个来做测试。


 


6、手工烧录


     手工烧录是直接使用avrdude来进行烧录。avrdude是一个控制台程序,需要在命令行下进行操作。


    (1)cd进附件中的bin文件夹下。


    (2)使用TCP/UDP调试工具连接到TCP服务器,发送指令r,将arduino复位。


    (3)马上执行以下命令:avrdude.exe avrdude.conf -v -v -v -v -patmega328p -carduino -P\.COM256 -b115200 -D -Uflash:w:Blink.cpp.hex:i


     至于为什么知道是这个命令,我是通过分析arduino编辑器的编译及下载输出得出的,以下是arduino的编译下载时的输出(过程),请注意第43行。



7、自动烧录


我用C#写了一个工具,将上面的手工操作给封装起来。功能包括启动VSPM虚拟串口服务器,复位远端的arduino uno,执行avrdude指令。详见的代码见附件。



使用Arduino开发ESP32:固件更新演示

经过SD卡更新固件

从SD卡进行固件升级算是最简单的了,将固件重命名为 update.bin(不是必须的,只要代码中读取的文件名能匹配上就行) ,而后复制到SD卡中,而后按照下文方式链接SD卡:

《使用Arduino开发ESP32(13):SD卡的使用》

完成电路链接后就可使用下面代码测试了:web


#include <SD_MMC.h>

#include <Update.h> // 引用相关库


void setup()

{

    Serial.begin(115200);

    Serial.println();


    if (!SD_MMC.begin()) //挂载SD卡

    {

        Serial.println("存储卡挂载失败");

        return;

    }


    File updateBin = SD_MMC.open("/update.bin"); // 读取update.bin文件

    if (!updateBin)

    {

        Serial.println("读取update.bin失败");

        return;

    }

    if (updateBin.isDirectory())

    {

        Serial.println("update.bin是一个文件夹");

        updateBin.close();

        return;

    }


    size_t updateSize = updateBin.size(); // 获取固件大小

    if (updateSize <= 0)

    {

        Serial.println("文件为空");

        return;

    }

    Serial.println("固件大小为" + String(updateSize) + "字节");


    Serial.println("开始固件升级");


    if (!Update.begin(updateSize)) // 开始固件升级,检查空可用间大小,若是正在升级或空间不够则返回false

    {

        Serial.println("升级不可用");

        return;

    }


    size_t written = Update.writeStream(updateBin); // 将数据写入到OTA区域 // 使用writeStream方法的话前面Update.begin()必须填写要更新的固件大小

    Serial.println("写入" + String(written) + "字节到OTA区域");


    if (!Update.end()) // 完成数据写入,设置在系统重启后自动将OTA区域固件移动到Sketch区域

    {

        Serial.println("升级失败 Error #: " + String(Update.getError()));

        return;

    }


    updateBin.close(); // 关闭update.bin文件

    //SD_MMC.remove("/update.bin"); // 移除update.bin文件


    Serial.println("升级操做完成,模块将在5秒后重启以完成固件升级");

    delay(5000); 

    ESP.restart(); // 重启设备

}


void loop()

{

}

上面演示中代码看着蛮多行,大可能是都是文件读取、检测的代码,真正固件更新的操做就 Update.begin() 、 Update.writeStream() 、 Update.end() 这几步。服务器


经过网页更新固件

经过网页更新也不复杂,和上面经过SD卡升级须要涉及SD卡使用相关内容同样,经过网页更新须要涉及网页和服务器相关内容,能够参考下面文章:

《使用Arduino开发ESP32(09):WebServer使用演示与说明》

使用下面代码能够测试经过网页进行更新固件的操做:网络


#include <WiFi.h>

#include <WebServer.h>

#include <Update.h> // 引用相关库


const char *ssid = "********";

const char *password = "********";


WebServer server(80);


// 如下为网页文件

String indexhtml = String("") +

                   "<!DOCTYPE html>\n" +

                   "<head>\n" +

                   " <meta charset=\"UTF-8\">\n" +

                   " <title>Update Test</title>\n" +

                   "</head>\n" +

                   "<body>\n" +

                   " <form method=\'POST\' action=\'/update\' enctype=\'multipart/form-data\'>\n" +

                   " <input type=\'file\' name=\'firmware\'>\n" +

                   " <input type=\'submit\'>\n" +

                   " </form>\n" +

                   "</body>\n";


bool shouldreboot = false; // 重启标志,固件升级操做完成后设置该标志准备重启设备


void handleResponse() //回调函数

{

    server.sendHeader("Connection", "close");

    server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");

}


void handleFileupload() //回调函数

{

    HTTPUpload &upload = server.upload();   // 文件上传对象

    if (upload.status == UPLOAD_FILE_START) // 文件上传开始

    {

        Serial.printf("开始上传文件: %s\n", upload.filename.c_str());

        if (!Update.begin()) // 开始固件升级,检查空可用间大小,若是正在升级或空间不够则返回false

        {

            Update.printError(Serial);

        }

    }

    else if (upload.status == UPLOAD_FILE_WRITE) // 文件读写中

    {

        if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) // 将文件数据写入到OTA区域

        {

            Update.printError(Serial);

        }

    }

    else if (upload.status == UPLOAD_FILE_END) // 文件上传完成

    {

        Serial.println("写入" + String(upload.totalSize) + "字节到OTA区域");

        if (!Update.end(true)) // 完成数据写入,设置在系统重启后自动将OTA区域固件移动到Sketch区域 // Update.begin不指定大小时这里设置true

        {

            Update.printError(Serial);

        }

        Serial.println("升级操做完成,模块将在5秒后重启以完成固件升级");

        shouldreboot = true;

    }

    else

    {

        Serial.printf("固件上传失败: status=%d\n", upload.status);

    }

}


void setup()

{

    Serial.begin(115200);

    Serial.println();


    WiFi.mode(WIFI_STA);

    WiFi.setSleep(false);

    WiFi.begin(ssid, password);

    while (WiFi.status() != WL_CONNECTED)

    {

        delay(500);

        Serial.print(".");

    }

    Serial.println("Connected");

    Serial.print("IP Address:");

    Serial.println(WiFi.localIP());


    server.on("/", HTTP_GET, []() {

        server.sendHeader("Connection", "close");

        server.send(200, "text/html", indexhtml); // 发送网页

    });


    server.on("/update", HTTP_POST, handleResponse, handleFileupload); // 绑定回调函数


    server.begin(); //启动服务器


    Serial.println("Web server started");

}


void loop()

{

    server.handleClient(); //处理来自客户端的请求


    if (shouldreboot)

    {

        delay(5000);

        ESP.restart(); // 重启设备

    }

}


上面演示中代码看着也蛮多行,大可能是都是网页文本、回调函数、网络链接等代码,真正固件更新的操做就 Update.begin() 、 Update.write() 、 Update.end() 这几步。和上一个例子不一样的是这里使用Update.begin()时未指定需求空间的大小,因此在后面Update.end()中指定了true参数。


ESP32烧录Arduino生成的bin文件

前言

Arduino有着丰富的第三方库,因此开发起来非常的方便快捷,但是在实际的生产中,我们不可能使用Arduino IDE来进行固件的烧录,因为使用Arduino IDE烧录效率很低,而且要先帮工厂把环境搭建好,更重要的是烧录时必须要打开源代码,这可能会导致工厂泄露机密。因此,最好的方案还是将代码转成通用的bin文件烧录。

废话不多说,马上开始讲解。


1 Arduino生成bin文件

关于Arduino的环境搭建,库安装这些我就不介绍了,都走到量产这一步了不会有人还不知道吧。

1.png

打开Arduino IDE,打开 项目 -> 导出已编译的二进制文件,等待编译完成即可在工程目录下生成相应的bin文件。


编译成功后生成的bin文件:

2.png


2 bin文件烧录

2.1 下载烧录工具

烧录bin文件的时候我们需要用到乐鑫的flash烧录工具。

工具可以在乐鑫官网下载:

https://www.espressif.com/zh-hans/support/download/other-tools

3.png


2.2 集齐MCU需要烧录的固件

用Arduino IDE烧录ESP8266、ESP32、ESP32-S2或者其他ESP系列的MCU时给我们的感觉是它仅仅烧录了自己编写的代码以及底层支持库。但实际上除了这些,还包含了启动文件,bootloader,sdk等。

因此,如果我们只烧录Arduino生成的bin文件是无法正常运行的。我们还需要找到另外几个文件。

这几个文件都在你安装的开发板库里面。比如你用的ESP32,那就是在esp32库。


我这里以ESP32-S2为例讲解,其他ESP系列的MCU也是一样的。


首先我们要打开库文件对应的文件夹。

这个路径可以在Arduino IDE的首选项里面看到。点击一下这个路径就可以弹出文件窗口。

4.png

5.png

在packages文件夹可以看到所有已安装的开发板库。比如我这里安装了esp32和esp8266的库。

6.png

进入相应的库,找到partitions和sdk这两个文件夹。

7.png

提示:我这个是esp32 v2.0.1版本的库,其他MCU或者其他版本的库文件框架可能会有区别,但是大体上应该都是类似的。


1)第1个文件:boot_app0

在partitions文件夹里面。

8.png

2)第2个文件:bootloader

在sdk文件夹里面。

特别说明一下:目前esp32是有3个MCU的sdk的,选择自己要用的即可。我这里用的是esp32-s2。

9.png

提示:dio、dout、qio和qout是spi的模式,40m和80m是flash的频率,比较常用的是dio和40m。当然,如果对性能有更高的要求可以选80m,在SPI作为主机时可以达到80MHz,但相应的对硬件的抗干扰能力也有更高的要求。


3)第3个文件:app

这个是你实际的应用代码,也就是用Arduino IDE生成的bin文件。


4)第4个文件:default

和第1个文件一样,也是在partitions文件夹里面。

10.png


2.3 烧录固件

打开乐鑫的烧录工具,选择对应的芯片和烧录方式。

提示:ESP32-S2除了串口以外,还可以通过USB下载。


11.png

依次添加烧录文件,并填写相应的烧录地址,配置好相应的模式。

我这里还是以ESP32-S2为例。

12.png

获取烧录文件地址的方法:这几个地址可以在Arduino IDE烧录时输出的日志中找到。

13.png


连接好硬件设备,并且进入下载模式。

进入下载模块的方法:按住boot键,然后再按reset键,最后松开boot键。或者按住boot键之后再上电。只要保证MCU启动时,boot脚是低电平即可。

14.png


通过串口打印的信息可以确定是否已进入下载模式。

进入下载模式时可以看到以下信息:

15.png


在烧录工具上选择好对应的com口,配置好波特率(波特率越大,下载速度越快)。

提示:ESP32-S2除了串口以外,还可以通过USB下载,要先进入下载模式才能检测到USB对应的com口,或者之前运行的代码有把USB配置成虚拟串口也可以看到对应的com口。


点击START开始烧录。

提示:烧录固件之前可以先全部擦除一遍。如果点击开始烧录之后才发觉MCU没有进入烧录模式,也可以在这里进入,进入之后马上就会开始烧录了。


16.png

等待下载完成后重启一下即可。


结束语

好了,关于Arduino生成bin文件及烧录方法的介绍就到这里。本文只列举了ESP32-S2的情况,ESP8266和ESP32我也试过,也是一样的,大家举一反三即可。如果这篇文章对你有帮助,可以点赞收藏,如果还有什么问题,欢迎在评论区留言或者私信给我。





Mqtt 客户端多主题订阅


前言: mqtt 的主题一个层级的概念. 我们订阅多个主题就需要用到这个技术点

功能是在主题中引入层次。层次又分主题层级分隔符,多层通配符和单层通配符

需要注意的一点是: 这些层级不能用在发布消息的 Publish 接口中


层级分类

主题层级分隔符: /

多层通配符: #

单层通配符: +

主题层级分隔符

"/" 被用来分割主题树的每一层,并给主题空间提供分等级的结构。当两个通配符在一个主题中出现的时候,主题层次分隔符的使用是很重要的。


// 主题Topic1: 分了三层

/test/child/aaa

// 主题Topic2: 分了四层

/test/child/aaa2/bbb2

多层通配符

多层通配符"#"是一个匹配主题中任意层次数的通配符。我们用案例说明


案例1

订阅主题: /test/child/#

我们将收到这些主题发送来的消息:


/test/child 

/test/child/aaa

/test/child/ccc

/test/child/aaa/bbb

/test/child/aaa/bbb/ddd

多层通配符可以理解为大于等于0的层次。

多层通配符只可以确定当前层或者下一层


常见错误 和正确表示案例


#   // Success, 会接收到不以 / 开头的所有主题

/#  // Success

/test/#/child   // Error, #必须是最后一个字符

/test/#           // Success

/test/child#    //Error  无效的通配符

/test/child/#   // Success

单层通配符

单层通配符 "+" 只匹配主题的一层


案例1

订阅主题: /test/child/+

我们将收到这些主题发送来的消息:

注意:接收不到 /test/child 主题发送的消息


/test/child/aaa

/test/child/bbb

/test/child/ccc

常见错误 和正确表示案例


+  //Success

/+  // Success

/test/+/child   // Success, 

/test/+           // Success

/test/child+    //Error  无效的通配符

/test/child/+   // Success

主题语法和用法

当你建立一个应用,设计主题树的时候应该考虑以下的主题名字的语法和语义:


主题至少有一个字符长。

主题名字是大小写敏感的。比如说,ACCOUNTS和Accounts是两个不同的主题。

主题名字可以包含空格。比如,Accounts payable是一个有效的主题。

以/开头会产生一个不同的主题。比如说,/finnace与finance不同。/finance匹配"+/+"和/+,但不匹配+

不要在任何主题中包含null(Unicode \x0000)字符。

以下的原则应用于主题树的建造和内容


在主题树中,长度被限制于64k内但是在这以内没有限制层级的数目 。

可以有任意数目的根节点;也就是说,可以有任意数目的主题树



Arduino的IO口驱动力不够解决办法

使用Arduino驱动模块

当Arduino的IO输出口驱动力不够时,有几种常见的解决方案:


使用晶体管或MOSFET作为开关:

可以使用NPN晶体管(如2N2222)或N沟道MOSFET(如IRLZ44N)来控制更大的电流。

Arduino的IO口用来控制晶体管或MOSFET的开关,而实际的大电流则通过这些器件来控制。


使用继电器:

继电器可以控制更大的电流和电压。

使用Arduino控制继电器的线圈,继电器的触点则用来控制大功率设备。


使用专用驱动器IC:

例如ULN2003(用于控制多个小功率设备)或L298N(用于控制电机)。

这些IC专门设计用于驱动大电流负载。


使用运算放大器:

运算放大器可以用来放大Arduino的输出信号。

这对于需要精确电压控制的模拟设备特别有用。


电平转换器:

如果需要不同电压级别的信号,可以使用电平转换器IC。


使用外部电源:

对于大功率设备,使用单独的电源供电,而Arduino仅用于控制信号。



LVGL Arduino界面开发库

LVGL(Light and Versatile Graphics Library)是一个免费的开源图形库,提供了创建嵌入式GUI所需的一切。它具有高度的可定制性和低内存占用,非常适合资源受限的嵌入式系统。lv_arduino 是 LVGL 在 Arduino 平台上的移植版本,使得 Arduino 用户可以轻松地在他们的项目中集成图形界面。

项目快速启动

安装步骤

下载并安装 Arduino IDE:确保你已经安装了最新版本的 Arduino IDE。

安装 LVGL 库:

打开 Arduino IDE。

进入 工具 -> 库管理。

搜索 lvgl 并安装 lv_arduino 库。

示例代码

以下是一个简单的示例代码,展示如何在 Arduino 上使用 LVGL 创建一个基本的界面:


#include <lvgl.h>

#include <TFT_eSPI.h>

 

TFT_eSPI tft = TFT_eSPI();

 

void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) {

  uint32_t w = (area->x2 - area->x1 + 1);

  uint32_t h = (area->y2 - area->y1 + 1);

 

  tft.startWrite();

  tft.setAddrWindow(area->x1, area->y1, w, h);

  tft.pushColors((uint16_t *)&color_p->full, w * h, true);

  tft.endWrite();

 

  lv_disp_flush_ready(disp);

}

 

void setup() {

  lv_init();

  tft.begin();

  tft.setRotation(1);

 

  static lv_disp_buf_t disp_buf;

  static lv_color_t buf[LV_HOR_RES_MAX * 10];

  lv_disp_buf_init(&disp_buf, buf, NULL, LV_HOR_RES_MAX * 10);

 

  lv_disp_drv_t disp_drv;

  lv_disp_drv_init(&disp_drv);

  disp_drv.flush_cb = my_disp_flush;

  disp_drv.buffer = &disp_buf;

  lv_disp_drv_register(&disp_drv);

 

  lv_obj_t *label = lv_label_create(lv_scr_act(), NULL);

  lv_label_set_text(label, "Hello, LVGL!");

  lv_obj_align(label, NULL, LV_ALIGN_CENTER, 0, 0);

}

 

void loop() {

  lv_task_handler();

  delay(5);

}



Ardunio软重新启动

void(* resetFunc) (void) = 0; //制造重启命令



中断使用注意

在中断中尽量少写过多的逻辑,一般只给状态符号

中断中不要调用别的硬件资源以免引起诡异的问题



Arduino String使用注意不能直接当char *使用

String message;

message += " " + server.argName(i) + ": " + server.arg(i) + "\n";

    if(server.argName(i).startsWith("wifi"))

    {

      sprintf(m_BaseData.WiFiName,"%s",server.arg(i).c_str());

//sprintf(m_BaseData.WiFiName,"%s",server.arg(i));  不能使用

    }

    if(server.argName(i).startsWith("password"))

    {

      sprintf(m_BaseData.WiFiPassword,"%s",server.arg(i).c_str());

    }

    

    if(server.argName(i).startsWith("url"))

    {

      sprintf(m_BaseData.Url,"%s",server.arg(i).c_str());

     

      Serial.println(server.arg(i));

      Serial.println(m_BaseData.Url);


    }


    if(server.argName(i).startsWith("deviceid"))

    {

      sprintf(m_BaseData.deviceid,"%s",server.arg(i).c_str());      

      Serial.println(server.arg(i));

      Serial.println(m_BaseData.deviceid);

    }


Arduino的String类用法汇总

String类主要的就是一些字符串处理函数,比string具有更多更轻大的功能,so用起来很爽!

下面是其主要函数介绍,用例主要参考官方资料。

1、定义,这个就不多说了

1
2
3
4
5
6
7
8
9
String stringOne = "Hello String";                            
String stringOne =  String('a');                            
String stringTwo =  String("This is a string");                            
String stringOne =  String(stringTwo + " with more");                            
String stringOne =  String(13);                            
String stringOne =  String(analogRead(0), DEC);                            
String stringOne =  String(45, HEX);                            
String stringOne =  String(255, BIN);                            
String stringOne =  String(millis(), DEC);                            

2、字符串的连接与追加,官方将两种功能分开了,其实觉得和在一起对比理解会更好一点

1
2
3
4
5
6
7
8
stringThree =  stringOne + 123;                            
stringThree = stringOne + 123456789;                            
stringThree =  stringOne + 'A';                            
stringThree =  stringOne +  "abc";                            
stringThree = stringOne + stringTwo;                            
stringOne += 123456789;                            
//等同于                            
stringTwo.concat(123456789);                            

3、在字符串中中查找字符位置,返回值为给定字符的第一个位置,查找失败返回-1
indexOf(‘?’):从字符串头部开始查找
lastindexOf(‘?’):从尾部开始查找

1
2
3
4
5
6
7
int firstClosingBracket = stringOne.indexOf('>');                            
//默认是从头部开始查找,还可以指定开始位置                            
int secondOpeningBracket = firstClosingBracket + 1;                            
int secondClosingBracket = stringOne.indexOf('>', secondOpeningBracket );                            
lastindexOf()的用法类似                            
int lastParagraph = stringOne.lastIndexOf("<p");                            
int secondLastGraf = stringOne.lastIndexOf("<p", lastParagraph - 1);                            

4、length():字符串长度测量函数,返回值为字符串的长度

1
2
String txtMsg = "This is a test!";                            
int lastStringLength = txtMsg.length();                            

5、trim():将字符串中的字符串去除,包括空格、回车、Tab等

1
2
3
String str = "this is   a                            
test ,do you kown?";                            
stringOne.trim();                            

6、大小写转换
大转小:toLowerCase()
小转大:toUpperCase()

7、替换:用后面的字符串替换前面的字符串

1
2
String stringOne = "<html><head><body>";                            
stringTwo.replace("<", "</");                            

8、查看字符串指定位置的字符:charAt(),返回值为给定位置的字符,如果超出范围会发生意想不到的错误。
改变字符串给定位置的字符:setcharAt,无返回值。

1
2
3
String reportString = "SensorReading: 456";                            
char mostSignificantDigit = reportString.charAt(15);                            
reportString.setCharAt(13, '=');                            

9、判断字符串开头、结尾或者给定位置是否为给定字符串,是返回true,否则返回false

1
2
3
4
5
6
7
String stringOne = "HTTP/1.1 200 OK";                            
if (stringOne.startsWith("HTTP/1.1")) {                            
    Serial.println("Server's using http version 1.1");                            
}                            
if (stringOne.startsWith("200 OK", 9)) {                            
    Serial.println("Got an OK from the server");                            
}                            

10、字符串的各种比较,包括==,!=,>,<,>=,<=。具体的函数有equals()、equalsIgnoreCase()(忽略大小写)、compareTo。字符串的比较式从前往后进行比较,以ASIIC码为标准。

11、字符串转换为整型:toInt(),返回值为转换后的整型值。

12、字符串解析函数:substring(?,…),返回值为指定位置的字符串值

1
2
3
4
5
6
7
String stringOne = "Content-Type: text/html";                            
if (stringOne.substring(19) == "html") {                            
    Serial.println("It's an html file");                            
}                            
if (stringOne.substring(14,18) == "text") {                            
    Serial.println("It's a text-based file");                            
}                            


不用Arduino库控制舵机

#define ServoPin 9


void ServoControl(int servoAngle)

{

  double thisAngle = map(servoAngle, 0, 180, 500, 2500);//等比例角度值范围转换高电平持续时间范围

  unsigned char i = 50;//50Hz 每秒的周期次数(周期/秒) 即1S 50 个周期 每个周期20ms

  while (i--)

  {

    digitalWrite(ServoPin, HIGH); 

    delayMicroseconds(thisAngle); //高电平时间

    digitalWrite(ServoPin, LOW); 

    delayMicroseconds(20000 - thisAngle);//每个周期20ms减去高电平持续时间

  }

}


void setup() {

  Serial.begin(115200);

  pinMode(ServoPin, OUTPUT);

  digitalWrite(ServoPin, LOW);//先保证拉低

  

}


void loop()

{

  for (int i = 0; i <= 180 ; i += 1)

  {

    delay(500);

    Serial.println(i);

    ServoControl(i);

  }

}



ESP32 低功耗蓝牙的例子代码

1.简单的server

/*

    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp

    Ported to Arduino ESP32 by Evandro Copercini

    updates by chegewara

*/


#include <BLEDevice.h>

#include <BLEUtils.h>

#include <BLEServer.h>


// See the following for generating UUIDs:

// https://www.uuidgenerator.net/


#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"

#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"


void setup() {

  Serial.begin(115200);

  Serial.println("Starting BLE work!");


  BLEDevice::init("Long name works now");

  BLEServer *pServer = BLEDevice::createServer();

  BLEService *pService = pServer->createService(SERVICE_UUID);

  BLECharacteristic *pCharacteristic = pService->createCharacteristic(

                                         CHARACTERISTIC_UUID,

                                         BLECharacteristic::PROPERTY_READ |

                                         BLECharacteristic::PROPERTY_WRITE

                                       );


  pCharacteristic->setValue("Hello World says Neil");

  pService->start();

  // BLEAdvertising *pAdvertising = pServer->getAdvertising();  // this still is working for backward compatibility

  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();

  pAdvertising->addServiceUUID(SERVICE_UUID);

  pAdvertising->setScanResponse(true);

  pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issue

  pAdvertising->setMinPreferred(0x12);

  BLEDevice::startAdvertising();

  Serial.println("Characteristic defined! Now you can read it in your phone!");

}


void loop() {

  // put your main code here, to run repeatedly:

  delay(2000);

}

2.简单的client

/**

 * A BLE client example that is rich in capabilities.

 * There is a lot new capabilities implemented.

 * author unknown

 * updated by chegewara

 */


#include "BLEDevice.h"

//#include "BLEScan.h"


// The remote service we wish to connect to.

static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");

// The characteristic of the remote service we are interested in.

static BLEUUID    charUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8");


static boolean doConnect = false;

static boolean connected = false;

static boolean doScan = false;

static BLERemoteCharacteristic* pRemoteCharacteristic;

static BLEAdvertisedDevice* myDevice;


static void notifyCallback(

  BLERemoteCharacteristic* pBLERemoteCharacteristic,

  uint8_t* pData,

  size_t length,

  bool isNotify) {

    Serial.print("Notify callback for characteristic ");

    Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());

    Serial.print(" of data length ");

    Serial.println(length);

    Serial.print("data: ");

    Serial.println((char*)pData);

}


class MyClientCallback : public BLEClientCallbacks {

  void onConnect(BLEClient* pclient) {

  }


  void onDisconnect(BLEClient* pclient) {

    connected = false;

    Serial.println("onDisconnect");

  }

};


bool connectToServer() {

    Serial.print("Forming a connection to ");

    Serial.println(myDevice->getAddress().toString().c_str());

    

    BLEClient*  pClient  = BLEDevice::createClient();

    Serial.println(" - Created client");


    pClient->setClientCallbacks(new MyClientCallback());


    // Connect to the remove BLE Server.

    pClient->connect(myDevice);  // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)

    Serial.println(" - Connected to server");


    // Obtain a reference to the service we are after in the remote BLE server.

    BLERemoteService* pRemoteService = pClient->getService(serviceUUID);

    if (pRemoteService == nullptr) {

      Serial.print("Failed to find our service UUID: ");

      Serial.println(serviceUUID.toString().c_str());

      pClient->disconnect();

      return false;

    }

    Serial.println(" - Found our service");



    // Obtain a reference to the characteristic in the service of the remote BLE server.

    pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);

    if (pRemoteCharacteristic == nullptr) {

      Serial.print("Failed to find our characteristic UUID: ");

      Serial.println(charUUID.toString().c_str());

      pClient->disconnect();

      return false;

    }

    Serial.println(" - Found our characteristic");


    // Read the value of the characteristic.

    if(pRemoteCharacteristic->canRead()) {

      std::string value = pRemoteCharacteristic->readValue();

      Serial.print("The characteristic value was: ");

      Serial.println(value.c_str());

    }


    if(pRemoteCharacteristic->canNotify())

      pRemoteCharacteristic->registerForNotify(notifyCallback);


    connected = true;

    return true;

}

/**

 * Scan for BLE servers and find the first one that advertises the service we are looking for.

 */

class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {

 /**

   * Called for each advertising BLE server.

   */

  void onResult(BLEAdvertisedDevice advertisedDevice) {

    Serial.print("BLE Advertised Device found: ");

    Serial.println(advertisedDevice.toString().c_str());


    // We have found a device, let us now see if it contains the service we are looking for.

    if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {


      BLEDevice::getScan()->stop();

      myDevice = new BLEAdvertisedDevice(advertisedDevice);

      doConnect = true;

      doScan = true;


    } // Found our server

  } // onResult

}; // MyAdvertisedDeviceCallbacks



void setup() {

  Serial.begin(115200);

  Serial.println("Starting Arduino BLE Client application...");

  BLEDevice::init("");


  // Retrieve a Scanner and set the callback we want to use to be informed when we

  // have detected a new device.  Specify that we want active scanning and start the

  // scan to run for 5 seconds.

  BLEScan* pBLEScan = BLEDevice::getScan();

  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());

  pBLEScan->setInterval(1349);

  pBLEScan->setWindow(449);

  pBLEScan->setActiveScan(true);

  pBLEScan->start(5, false);

} // End of setup.



// This is the Arduino main loop function.

void loop() {


  // If the flag "doConnect" is true then we have scanned for and found the desired

  // BLE Server with which we wish to connect.  Now we connect to it.  Once we are 

  // connected we set the connected flag to be true.

  if (doConnect == true) {

    if (connectToServer()) {

      Serial.println("We are now connected to the BLE Server.");

    } else {

      Serial.println("We have failed to connect to the server; there is nothin more we will do.");

    }

    doConnect = false;

  }


  // If we are connected to a peer BLE Server, update the characteristic each time we are reached

  // with the current time since boot.

  if (connected) {

    String newValue = "Time since boot: " + String(millis()/1000);

    Serial.println("Setting new characteristic value to \"" + newValue + "\"");

    

    // Set the characteristic's value to be the array of bytes that is actually a string.

    pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());

  }else if(doScan){

    BLEDevice::getScan()->start(0);  // this is just eample to start scan after disconnect, most likely there is better way to do it in arduino

  }

  

  delay(1000); // Delay a second between loops.

} // End of loop

3.低功耗蓝牙IBeacon


/*

   Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp

   Ported to Arduino ESP32 by pcbreflux

*/


 

/*

   Create a BLE server that will send periodic iBeacon frames.

   The design of creating the BLE server is:

   1. Create a BLE Server

   2. Create advertising data

   3. Start advertising.

   4. wait

   5. Stop advertising.

   6. deep sleep

   

*/

#include "sys/time.h"


#include "BLEDevice.h"

#include "BLEUtils.h"

#include "BLEBeacon.h"

#include "esp_sleep.h"


#define GPIO_DEEP_SLEEP_DURATION     10  // sleep x seconds and then wake up

RTC_DATA_ATTR static time_t last;        // remember last boot in RTC Memory

RTC_DATA_ATTR static uint32_t bootcount; // remember number of boots in RTC Memory


#ifdef __cplusplus

extern "C" {

#endif


uint8_t temprature_sens_read();

//uint8_t g_phyFuns;


#ifdef __cplusplus

}

#endif


// See the following for generating UUIDs:

// https://www.uuidgenerator.net/

BLEAdvertising *pAdvertising;

struct timeval now;


#define BEACON_UUID           "8ec76ea3-6668-48da-9866-75be8bc86f4d" // UUID 1 128-Bit (may use linux tool uuidgen or random numbers via https://www.uuidgenerator.net/)


void setBeacon() {


  BLEBeacon oBeacon = BLEBeacon();

  oBeacon.setManufacturerId(0x4C00); // fake Apple 0x004C LSB (ENDIAN_CHANGE_U16!)

  oBeacon.setProximityUUID(BLEUUID(BEACON_UUID));

  oBeacon.setMajor((bootcount & 0xFFFF0000) >> 16);

  oBeacon.setMinor(bootcount&0xFFFF);

  BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();

  BLEAdvertisementData oScanResponseData = BLEAdvertisementData();

  

  oAdvertisementData.setFlags(0x04); // BR_EDR_NOT_SUPPORTED 0x04

  

  std::string strServiceData = "";

  

  strServiceData += (char)26;     // Len

  strServiceData += (char)0xFF;   // Type

  strServiceData += oBeacon.getData(); 

  oAdvertisementData.addData(strServiceData);

  

  pAdvertising->setAdvertisementData(oAdvertisementData);

  pAdvertising->setScanResponseData(oScanResponseData);


}


void setup() {


    

  Serial.begin(115200);

  gettimeofday(&now, NULL);


  Serial.printf("start ESP32 %d\n",bootcount++);


  Serial.printf("deep sleep (%lds since last reset, %lds since last boot)\n",now.tv_sec,now.tv_sec-last);


  last = now.tv_sec;

  

  // Create the BLE Device

  BLEDevice::init("");


  // Create the BLE Server

  // BLEServer *pServer = BLEDevice::createServer(); // <-- no longer required to instantiate BLEServer, less flash and ram usage


  pAdvertising = BLEDevice::getAdvertising();

  

  setBeacon();

   // Start advertising

  pAdvertising->start();

  Serial.println("Advertizing started...");

  delay(100);

  pAdvertising->stop();

  Serial.printf("enter deep sleep\n");

  esp_deep_sleep(1000000LL * GPIO_DEEP_SLEEP_DURATION);

  Serial.printf("in deep sleep\n");

}


void loop() {

}

4.BLE_nodtify


/*

    Video: https://www.youtube.com/watch?v=oCMOYS71NIU

    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp

    Ported to Arduino ESP32 by Evandro Copercini

    updated by chegewara


   Create a BLE server that, once we receive a connection, will send periodic notifications.

   The service advertises itself as: 4fafc201-1fb5-459e-8fcc-c5c9c331914b

   And has a characteristic of: beb5483e-36e1-4688-b7f5-ea07361b26a8


   The design of creating the BLE server is:

   1. Create a BLE Server

   2. Create a BLE Service

   3. Create a BLE Characteristic on the Service

   4. Create a BLE Descriptor on the characteristic

   5. Start the service.

   6. Start advertising.


   A connect hander associated with the server starts a background task that performs notification

   every couple of seconds.

*/

#include <BLEDevice.h>

#include <BLEServer.h>

#include <BLEUtils.h>

#include <BLE2902.h>


BLEServer* pServer = NULL;

BLECharacteristic* pCharacteristic = NULL;

bool deviceConnected = false;

bool oldDeviceConnected = false;

uint32_t value = 0;


// See the following for generating UUIDs:

// https://www.uuidgenerator.net/


#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"

#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"



class MyServerCallbacks: public BLEServerCallbacks {

    void onConnect(BLEServer* pServer) {

      deviceConnected = true;

    };


    void onDisconnect(BLEServer* pServer) {

      deviceConnected = false;

    }

};




void setup() {

  Serial.begin(115200);


  // Create the BLE Device

  BLEDevice::init("ESP32");


  // Create the BLE Server

  pServer = BLEDevice::createServer();

  pServer->setCallbacks(new MyServerCallbacks());


  // Create the BLE Service

  BLEService *pService = pServer->createService(SERVICE_UUID);


  // Create a BLE Characteristic

  pCharacteristic = pService->createCharacteristic(

                      CHARACTERISTIC_UUID,

                      BLECharacteristic::PROPERTY_READ   |

                      BLECharacteristic::PROPERTY_WRITE  |

                      BLECharacteristic::PROPERTY_NOTIFY |

                      BLECharacteristic::PROPERTY_INDICATE

                    );


  // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml

  // Create a BLE Descriptor

  pCharacteristic->addDescriptor(new BLE2902());


  // Start the service

  pService->start();


  // Start advertising

  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();

  pAdvertising->addServiceUUID(SERVICE_UUID);

  pAdvertising->setScanResponse(false);

  pAdvertising->setMinPreferred(0x0);  // set value to 0x00 to not advertise this parameter

  BLEDevice::startAdvertising();

  Serial.println("Waiting a client connection to notify...");

}


void loop() {

    // notify changed value

    if (deviceConnected) {

        pCharacteristic->setValue((uint8_t*)&value, 4);

        pCharacteristic->notify();

        value++;

        delay(3); // bluetooth stack will go into congestion, if too many packets are sent, in 6 hours test i was able to go as low as 3ms

    }

    // disconnecting

    if (!deviceConnected && oldDeviceConnected) {

        delay(500); // give the bluetooth stack the chance to get things ready

        pServer->startAdvertising(); // restart advertising

        Serial.println("start advertising");

        oldDeviceConnected = deviceConnected;

    }

    // connecting

    if (deviceConnected && !oldDeviceConnected) {

        // do stuff here on connecting

        oldDeviceConnected = deviceConnected;

    }

}

5.BLE_scan

/*

   Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp

   Ported to Arduino ESP32 by Evandro Copercini

*/


#include <BLEDevice.h>

#include <BLEUtils.h>

#include <BLEScan.h>

#include <BLEAdvertisedDevice.h>


int scanTime = 5; //In seconds

BLEScan* pBLEScan;


class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {

    void onResult(BLEAdvertisedDevice advertisedDevice) {

      Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str());

    }

};


void setup() {

  Serial.begin(115200);

  Serial.println("Scanning...");


  BLEDevice::init("");

  pBLEScan = BLEDevice::getScan(); //create new scan

  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());

  pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster

  pBLEScan->setInterval(100);

  pBLEScan->setWindow(99);  // less or equal setInterval value

}


void loop() {

  // put your main code here, to run repeatedly:

  BLEScanResults foundDevices = pBLEScan->start(scanTime, false);

  Serial.print("Devices found: ");

  Serial.println(foundDevices.getCount());

  Serial.println("Scan done!");

  pBLEScan->clearResults();   // delete results fromBLEScan buffer to release memory

  delay(2000);

}

6.BLE_server_multiconnect


/*

    Video: https://www.youtube.com/watch?v=oCMOYS71NIU

    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp

    Ported to Arduino ESP32 by Evandro Copercini

    updated by chegewara


   Create a BLE server that, once we receive a connection, will send periodic notifications.

   The service advertises itself as: 4fafc201-1fb5-459e-8fcc-c5c9c331914b

   And has a characteristic of: beb5483e-36e1-4688-b7f5-ea07361b26a8


   The design of creating the BLE server is:

   1. Create a BLE Server

   2. Create a BLE Service

   3. Create a BLE Characteristic on the Service

   4. Create a BLE Descriptor on the characteristic

   5. Start the service.

   6. Start advertising.


   A connect hander associated with the server starts a background task that performs notification

   every couple of seconds.

*/

#include <BLEDevice.h>

#include <BLEServer.h>

#include <BLEUtils.h>

#include <BLE2902.h>


BLEServer* pServer = NULL;

BLECharacteristic* pCharacteristic = NULL;

bool deviceConnected = false;

bool oldDeviceConnected = false;

uint32_t value = 0;


// See the following for generating UUIDs:

// https://www.uuidgenerator.net/


#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"

#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"



class MyServerCallbacks: public BLEServerCallbacks {

    void onConnect(BLEServer* pServer) {

      deviceConnected = true;

      BLEDevice::startAdvertising();

    };


    void onDisconnect(BLEServer* pServer) {

      deviceConnected = false;

    }

};




void setup() {

  Serial.begin(115200);


  // Create the BLE Device

  BLEDevice::init("ESP32");


  // Create the BLE Server

  pServer = BLEDevice::createServer();

  pServer->setCallbacks(new MyServerCallbacks());


  // Create the BLE Service

  BLEService *pService = pServer->createService(SERVICE_UUID);


  // Create a BLE Characteristic

  pCharacteristic = pService->createCharacteristic(

                      CHARACTERISTIC_UUID,

                      BLECharacteristic::PROPERTY_READ   |

                      BLECharacteristic::PROPERTY_WRITE  |

                      BLECharacteristic::PROPERTY_NOTIFY |

                      BLECharacteristic::PROPERTY_INDICATE

                    );


  // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml

  // Create a BLE Descriptor

  pCharacteristic->addDescriptor(new BLE2902());


  // Start the service

  pService->start();


  // Start advertising

  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();

  pAdvertising->addServiceUUID(SERVICE_UUID);

  pAdvertising->setScanResponse(false);

  pAdvertising->setMinPreferred(0x0);  // set value to 0x00 to not advertise this parameter

  BLEDevice::startAdvertising();

  Serial.println("Waiting a client connection to notify...");

}


void loop() {

    // notify changed value

    if (deviceConnected) {

        pCharacteristic->setValue((uint8_t*)&value, 4);

        pCharacteristic->notify();

        value++;

        delay(10); // bluetooth stack will go into congestion, if too many packets are sent, in 6 hours test i was able to go as low as 3ms

    }

    // disconnecting

    if (!deviceConnected && oldDeviceConnected) {

        delay(500); // give the bluetooth stack the chance to get things ready

        pServer->startAdvertising(); // restart advertising

        Serial.println("start advertising");

        oldDeviceConnected = deviceConnected;

    }

    // connecting

    if (deviceConnected && !oldDeviceConnected) {

        // do stuff here on connecting

        oldDeviceConnected = deviceConnected;

    }

}

7.BLE_uart

/*

    Video: https://www.youtube.com/watch?v=oCMOYS71NIU

    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp

    Ported to Arduino ESP32 by Evandro Copercini


   Create a BLE server that, once we receive a connection, will send periodic notifications.

   The service advertises itself as: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E

   Has a characteristic of: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E - used for receiving data with "WRITE" 

   Has a characteristic of: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E - used to send data with  "NOTIFY"


   The design of creating the BLE server is:

   1. Create a BLE Server

   2. Create a BLE Service

   3. Create a BLE Characteristic on the Service

   4. Create a BLE Descriptor on the characteristic

   5. Start the service.

   6. Start advertising.


   In this example rxValue is the data received (only accessible inside that function).

   And txValue is the data to be sent, in this example just a byte incremented every second. 

*/

#include <BLEDevice.h>

#include <BLEServer.h>

#include <BLEUtils.h>

#include <BLE2902.h>


BLEServer *pServer = NULL;

BLECharacteristic * pTxCharacteristic;

bool deviceConnected = false;

bool oldDeviceConnected = false;

uint8_t txValue = 0;


// See the following for generating UUIDs:

// https://www.uuidgenerator.net/


#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.println("*********");

        Serial.print("Received Value: ");

        for (int i = 0; i < rxValue.length(); i++)

          Serial.print(rxValue[i]);


        Serial.println();

        Serial.println("*********");

      }

    }

};



void setup() {

  Serial.begin(115200);


  // Create the BLE Device

  BLEDevice::init("UART Service");


  // Create the BLE Server

  pServer = BLEDevice::createServer();

  pServer->setCallbacks(new MyServerCallbacks());


  // Create the BLE Service

  BLEService *pService = pServer->createService(SERVICE_UUID);


  // Create a BLE Characteristic

  pTxCharacteristic = pService->createCharacteristic(

CHARACTERISTIC_UUID_TX,

BLECharacteristic::PROPERTY_NOTIFY

);

                      

  pTxCharacteristic->addDescriptor(new BLE2902());


  BLECharacteristic * pRxCharacteristic = pService->createCharacteristic(

 CHARACTERISTIC_UUID_RX,

BLECharacteristic::PROPERTY_WRITE

);


  pRxCharacteristic->setCallbacks(new MyCallbacks());


  // Start the service

  pService->start();


  // Start advertising

  pServer->getAdvertising()->start();

  Serial.println("Waiting a client connection to notify...");

}


void loop() {


    if (deviceConnected) {

        pTxCharacteristic->setValue(&txValue, 1);

        pTxCharacteristic->notify();

        txValue++;

delay(10); // bluetooth stack will go into congestion, if too many packets are sent

}


    // disconnecting

    if (!deviceConnected && oldDeviceConnected) {

        delay(500); // give the bluetooth stack the chance to get things ready

        pServer->startAdvertising(); // restart advertising

        Serial.println("start advertising");

        oldDeviceConnected = deviceConnected;

    }

    // connecting

    if (deviceConnected && !oldDeviceConnected) {

// do stuff here on connecting

        oldDeviceConnected = deviceConnected;

    }

}

8.BLE_write

/*

    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleWrite.cpp

    Ported to Arduino ESP32 by Evandro Copercini

*/


#include <BLEDevice.h>

#include <BLEUtils.h>

#include <BLEServer.h>


// See the following for generating UUIDs:

// https://www.uuidgenerator.net/


#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"

#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"



class MyCallbacks: public BLECharacteristicCallbacks {

    void onWrite(BLECharacteristic *pCharacteristic) {

      std::string value = pCharacteristic->getValue();


      if (value.length() > 0) {

        Serial.println("*********");

        Serial.print("New value: ");

        for (int i = 0; i < value.length(); i++)

          Serial.print(value[i]);


        Serial.println();

        Serial.println("*********");

      }

    }

};


void setup() {

  Serial.begin(115200);


  Serial.println("1- Download and install an BLE scanner app in your phone");

  Serial.println("2- Scan for BLE devices in the app");

  Serial.println("3- Connect to MyESP32");

  Serial.println("4- Go to CUSTOM CHARACTERISTIC in CUSTOM SERVICE and write something");

  Serial.println("5- See the magic =)");


  BLEDevice::init("MyESP32");

  BLEServer *pServer = BLEDevice::createServer();


  BLEService *pService = pServer->createService(SERVICE_UUID);


  BLECharacteristic *pCharacteristic = pService->createCharacteristic(

                                         CHARACTERISTIC_UUID,

                                         BLECharacteristic::PROPERTY_READ |

                                         BLECharacteristic::PROPERTY_WRITE

                                       );


  pCharacteristic->setCallbacks(new MyCallbacks());


  pCharacteristic->setValue("Hello World");

  pService->start();


  BLEAdvertising *pAdvertising = pServer->getAdvertising();

  pAdvertising->start();

}


void loop() {

  // put your main code here, to run repeatedly:

  delay(2000);

}



采用Enerlib程序库设定睡眠模式

Enerlib程序库可简化Arduino睡眠模式的程序设定,请先下载Enerlib程序库并解压缩到Arduino的libraries文件夹

1.png

本实验程序的行为如下:

  1. 启动时,每隔0.5秒点、灭三次位于第13脚的LED。
  2. LED闪烁完毕后,进入“Power-down(断电)”睡眠模式
  3. 当中断0(第2脚)的信号改变时,唤醒Arduino,再次闪烁LED三次,接着再进入睡眠模式。
#include <Enerlib.h>
Energyenergy;// 宣告"Energy"程式物件
constbyteswPin=2;// 開關腳位
constbyteledPin=13;// LED腳位
bytetimes=0;// 記錄執行次數
volatilebytestate=0;// 暫存執行狀態
void wakeISR(){
if(energy.WasSleeping()){
state=1;
}else{
state=2;
}
}
void setup(){
Serial.begin(9600);
pinMode(ledPin,OUTPUT);
pinMode(swPin,INPUT);
digitalWrite(swPin,HIGH);
attachInterrupt(0,wakeISR,CHANGE);// 附加中斷服務常式
Serial.println("Running...");
}
voidloop()
{
if(state==1){
Serial.println("Was sleeping...");
}elseif(state==2){
Serial.println("Was awake...");
}
state=0;
digitalWrite(ledPin,!digitalRead(ledPin));
delay(500);
times++;
Serial.println(times);
if(times>5){
times=0;
Serial.println("Go to sleep...");
energy.PowerDown();
}
}


Ardunio 命令行编译上传

arduino --upload [sketch.ino]

Arduino IDE编译代码下载程序很方便,但是有如下理由我们仍需要用比较方便快捷的方法烧写编译后的二进制文件。


源代码不需要修改,可以用IDE烧写,但是用IDE打开源代码挺耗时

需要给很多个成熟的产品烧录程序

不想把源代码发给他人,但是希望他人可以自己烧录程序(编译后的二进制文件)

需要频繁烧录不同的程序,比如Arduboy等掌机更换内置游戏

有挺多工具软件可以完成脱离Arduino IDE烧录Hex二进制文件的工作,他们大多是通过GUI前端在后台调用avrdude程序。大多数情况下,他们可以很好的工作,但是要么是不跨平台,只能用于Windows(这类工具Windows版本的比较多,也有些是python编写的,但是又打包为exe文件运行,也是只支持Windows),要么就是工具本身有些限制。


我实验了用命令行的方式来烧写Hex文件,这样的话,只要系统内安装有Arduino IDE,甚至是只要安装了avrdude程序,就可以方便的烧写Hex文件,而且可以跨平台,无论Windows,macOS,或者是Linux(树莓派?)都可以工作。


首先,我的系统里安装了Arduino IDE v1.8.9。 目前,这个版本是最新版。


确定Arduino IDE编译好的Hex文件的位置

在File菜单的Preferences里面,打开preferences.txt文件,在更改之前,关闭Arduino IDE。

新建一个文件夹C:\Users\zz\Documents\Arduino\hex (zz是我的Windows登录用户名)

在preferences.txt文件里加入如下一行, 并保存

build.path=C:\Users\zz\Documents\Arduino\hex

对于macOS的话

build.path=/Users/zz/Documents/Arduino/hex

打开Arduino IDE,并打开Blink示例程序,Ctrl-R编译后,会在新建的文件夹中看到编译好的Blink.ino.hex文件




avrdude的参数

-C 制定配置文件的位置.

对于Windows系统的例子

-CC:\arduino-1.8.9\hardware\tools\avr/etc/avrdude.conf

对于macOS系统的例子

-C/Applications/Arduino.app/Contents/Java/hardware/tools/avr/etc/avrdude.conf

-v 打印详细的提示信息.

-p 必须项. 指定AVR零件号.

对于Arduino Uno

-patmega328p

对于Arduino Micro

-patmega32u4

-c 指定编程器的类型.

对于Arduino Uno

-carduino

对于Arduino Micro

-cavr109

-P 指定连接的串口.

对于Windows系统的例子

-PCOM3

对于macOS系统的例子

-P/dev/cu.usbmodem141301

-b 指定RS-232串口波特率.

对于Arduino Uno

-b115200

对于Arduino Micro

-b57600

-D 禁止自动擦除闪存空间

-U :r|w|v:[:format]

指定存储类型,操作方式,hex文件名以及文件类型.

对于Windows系统的例子

-Uflash:w:C:\Users\zz\Documents\Arduino\hex/Blink.ino.hex:i

对于macOS系统的例子

-Uflash:w:/Users/zz/Documents/Arduino/hex/Blink.ino.hex:i


Windows

Uno

C:\arduino-1.8.9\hardware\tools\avr\bin\avrdude -CC:\arduino-1.8.9\hardware\tools\avr\etc\avrdude.conf -v -patmega328p -carduino -PCOM3 -b115200 -D -Uflash:w:C:\Users\zz\Documents\Arduino\hex\Blink.ino.hex:i

Micro

C:\arduino-1.8.9\hardware\tools\avr\bin\avrdude -CC:\arduino-1.8.9\hardware\tools\avr\etc\avrdude.conf -v -patmega32u4 -cavr109 -PCOM4 -b57600 -D -Uflash:w:C:\Users\zz\Documents\Arduino\hex\Blink.ino.hex:i


macOS

Uno

/Applications/Arduino.app/Contents/Java/hardware/tools/avr/bin/avrdude -C/Applications/Arduino.app/Contents/Java/hardware/tools/avr/etc/avrdude.conf -v -patmega328p -carduino -P/dev/cu.usbmodem141301 -b115200 -D -Uflash:w:/Users/zz/Documents/Arduino/hex/Blink.ino.hex:i 

Micro

/Applications/Arduino.app/Contents/Java/hardware/tools/avr/bin/avrdude -C/Applications/Arduino.app/Contents/Java/hardware/tools/avr/etc/avrdude.conf -v -patmega32u4 -cavr109 -P/dev/cu.usbmodem141401 -b57600 -D -Uflash:w:/Users/zz/Documents/Arduino/hex/Blink.ino.hex:i 



Arduino引脚介绍和使用方式

数字引脚和使用

Arduino上的引脚可以配置为输入或输出。


配置为INPUT的引脚的属性

Arduino(Atmega)引脚默认为输入,因此在将它们用作输入时,不需要使用pinMode()将它们明确声明为输入。并且以这种方式配置的引脚处于高阻抗状态。输入引脚对要采样的电路的要求极小,相当于该引脚前面有一个100兆欧的串联电阻。这意味着只需很少的电流就可以将输入引脚从一种状态转移到另一种状态,并且可以使这些引脚用于实现电容式触摸传感器,读取光电二极管或使用读取模拟传感器等任务。


但是,这也意味着配置为pinMode(pin,INPUT)的引脚,即使未连接任何导线,或者未连接到其他电路的引脚,因为有电噪声或电容耦合的影响,将报告看似随机的引脚状态变化。


上拉电阻,引脚配置为INPUT

如果没有输入,将输入引脚配置成已知的状态通常很有用。这可以通过在输入端增加一个上拉电阻(至+ 5V)或一个下拉电阻(接地电阻)来实现。对于上拉或下拉电阻,10K电阻是一个不错的选择。


配置为INPUT_PULLUP的引脚的属性

Atmega芯片内置了20K上拉电阻,可通过软件进行访问。通过将pinMode()设置为INPUT_PULLUP可以访问这些内置的上拉电阻。这有效地反转了INPUT模式的行为,其中HIGH表示传感器关闭,而LOW表示传感器打开。


该上拉的值取决于所使用的微控制器。在大多数基于AVR的板上,该值保证在20kΩ至50kΩ之间。在Arduino Due上,它介于50kΩ和150kΩ之间。有关确切值,请参考板上微控制器的数据表。


将传感器连接到配置有INPUT_PULLUP的引脚时,另一端应接地。在简单开关的情况下,这会导致当开关断开时引脚读为高电平,而当按下开关时引脚读为低电平。


上拉电阻提供足够的电流,以使连接到已配置为输入的引脚的LED暗淡点亮。如果项目中的LED似乎工作正常,但效果非常暗淡,则很可能正在发生这种情况。


上拉电阻器由控制引脚为高电平或低电平的相同寄存器(内部芯片存储位置)控制。因此,如果某个引脚被配置为在输入为INPUT时接通上拉电阻,则该引脚如果随后通过pinMode()切换至OUTPUT,则该引脚将被配置为HIGH。这也可以在另一个方向上起作用,并且如果使用pinMode()切换到输入,则处于HIGH状态的输出引脚将设置上拉电阻。


在Arduino 1.0.1之前,可以通过以下方式配置内部上拉:


pinMode(pin,INPUT); //将引脚设置为输入

digitalWrite(pin,HIGH); //打开上拉电阻


注意:数字引脚13比其他数字引脚更难用作数字输入,因为它有LED连接到其上的电阻器,该LED和电阻器已焊接到大多数板上的板上。如果启用其内部20k上拉电阻,则它将挂在1.7V左右,而不是预期的5V,因为板载LED和串联电阻将电压电平拉低,这意味着它始终返回LOW。如果必须将引脚13用作数字输入,请将其pinMode()设置为INPUT并使用外部下拉电阻。


配置为OUTPUT的引脚的属性

通过pinMode()配置为OUTPUT的引脚被称为处于低阻抗状态。这意味着它们可以为其他电路提供大量电流。Atmega引脚可以向其他设备或电路提供(提供正电流)或吸收(提供负电流)高达40 mA(毫安)的电流。这足以点亮LED(不要忘记和LED灯串联的电阻)或运行许多传感器,但这个电流不够运行大多数的继电器、螺线管或电动机。


Arduino引脚短路或试图使用它们运行大电流设备,可能会损坏或破坏引脚上的输出晶体管,或者损坏整个Atmega芯片。通常,这将导致微控制器中的出现一个“死”引脚,但其余芯片仍将正常工作。因此,最好将输出引脚连接到具有470Ω或1k电阻的其他设备,除非特定应用需要从引脚吸收最大电流。


三、模拟输入引脚介绍和使用

Arduino芯片(ATmega8,ATmega168,ATmega328P或ATmega1280)上的模拟输入引脚介绍。


模数转换器

用于Arduino 的ATmega控制器包含一个板载6通道(在Mini和Nano上为8通道,在Mega上为16通道)模数(A / D)转换器。转换器具有10位分辨率,意味着可以返回0到1023之间的整数。对于大多数Arduino用户,模拟引脚的主要功能是读取模拟传感器,而模拟引脚还具有通用输入/输出(GPIO)引脚的所有功能。 (与数字引脚0-13相同)。


因此,如果用户需要更多通用输入输出引脚,而所有模拟引脚均未使用,则这些模拟引脚可用于GPIO。


引脚编号

使用别名A0(用于模拟输入0),A1等,可以将模拟引脚与数字引脚相同地使用。例如,代码看起来像这样,将模拟引脚0设置为输出,并将其设置为HIGH :


pinMode(A0,OUTPUT);

digitalWrite(A0,HIGH);

上拉电阻

模拟引脚还具有上拉电阻,其作用与数字引脚上的上拉电阻相同。通过使用以下命令来启用它们:


pinMode(A0,INPUT_PULLUP); //在模拟引脚0上设置上拉 

但是请注意,打开上拉电阻会影响analogRead()报告的值。


细节和警告

如果先前已将引脚设置为输出,则AnalogRead命令将无法正常工作,因此,在这种情况下,请在使用AnalogRead之前将其设置回输入。同样,如果该引脚已被设置为高电平作为输出,则当切换回输入时,将设置上拉电阻。


Ardunio 中断

attachInterrupt()

说明

attachInterrupt()函数是用于为Arduino开发板设置和执行ISR(中断服务程序)用的

ISR(中断服务程序)顾名思义就是中断Arduino当前正在处理的事情而优先去执行中断服务程序。当中断服务程序完成以后,再回来继续执行刚才执行的事情。中断服务程序对监测Arduino输入有很大的用处。

我们可以使用attachInterrupt()函数,利用Arduino的引脚触发中断程序。以下列表说明支持中断的引脚有哪些:

Arduino控制板                支持中断的引脚                
Uno, Nano, Mini2, 3
Mega, Mega2560, MegaADK                2, 3, 18, 19, 20, 21
Micro, Leonardo0, 1, 2, 3, 7
Zero除4号引脚以外的所有数字引脚
MKR1000 Rev.10, 1, 4, 5, 6, 7, 8, 9, A1, A2
Due所有数字引脚

注意    

在ISR(中断服务程序)函数中,delay()函数是不工作的,而且millis()函数返回值也不再增长。在ISR(中断服务程序)运行期间Arduino开发板接收到的串口数据也可能丢失。另外ISR函数里所使用的变量应声明为volatile类型。详情请见以下”关于ISR(中断服务程序)”部分。

使用中断    

中断很适合执行那些需要不断检查的工作,比如检查一个引脚上连接的按键开关是否被按下。中断更适用于很快就会消失的信号检查,比如某一个引脚用于检测脉冲信号,这个脉冲信号的持续时间可能十分短暂。如果不使用中断,那么假如Arduino开发板正在执行其它任务时,突然这个脉冲信号来了,还不等Arduino开发板完成正在执行的工作,这个脉冲信号可能就已经消失了。而使用中断,就可以确保这个转瞬即逝的脉冲信号可以很好的被Arduino开发板检测到并执行相应任务。

关于ISR(中断服务程序)    

对于Arduino开发板来说,ISR(中断服务程序)是一种特殊的函数。它的特殊意味着它具有其它类型函数所不具备的限制和特点。

  • ISR函数不能有任何参数。ISR也没有任何返回值。
  • 通常ISR需要越短小精悍越好!另外如果您的代码中有多个ISR函数,那么每次Arduino只能运行一个ISR函数,其它ISR函数只有在当前的ISR函数执行结束以后,才能按照其优先级别顺序执行。
  • millis()函数的运行依赖Arduino开发板的中断功能,因此ISR函数中的millis()函数是无法正常运行的。micros() 也是类似的情况,它只能在初始的1-2毫秒中可以运行,但是过了这1-2毫秒后就开始出现问题了。 delayMicroseconds() 不需要任何计数器就可以运行,所以delayMicroseconds() 运行是不会受到影响的。
  • 一般情况下,ISR函数与主程序之间传递数据是依靠全局变量来实现的。为了确保全局变量在ISR函数中可以正常的工作,应该将可能被ISR函数中使用的全局变量声明为volatile类型。

如需更多有关中断方面的知识,请参考 Nick Gammon’s notes.

语法

attachInterrupt(digitalPinToInterrupt(pin), ISR, mode);    

参数

pin: 中断引脚号
ISR: 中断服务程序名
mode:中断模式

中断模式(mode)有以下几种形式:

LOW: 当引脚为低电平时触发中断服务程序
CHANGE: 当引脚电平发生变化时触发中断服务程序
RISING: 当引脚电平由低电平变为高电平时触发中断服务程序
FALLING: 当引脚电平由高电平变为低电平时触发中断服务程序

返回值    

示例    

const byte ledPin = 13;

 

//用2号引脚作为中断触发引脚

const byte interruptPin = 2;  

 

volatile byte state = LOW;

 

void setup() {

  pinMode(ledPin, OUTPUT);

 

  //将中断触发引脚(2号引脚)设置为INPUT_PULLUP(输入上拉)模式

  pinMode(interruptPin, INPUT_PULLUP); 

 

  //设置中断触发程序

  attachInterrupt(digitalPinToInterrupt(interruptPin), blink, CHANGE);

}

 

void loop() {

  digitalWrite(ledPin, state);

}

 

//中断服务程序

void blink() {

  state = !state;

}


外部中断唤醒

#include <avr/wdt.h>  

#include <avr/sleep.h>

int ledPin = 13;

int bsleep=0;

int nn=0;

void setup() 

{  

   Serial.begin(9600);

 Serial.print("start\r\n");

  pinMode(ledPin, OUTPUT);   

  set_sleep_mode(SLEEP_MODE_PWR_DOWN); //设置休眠模式。

  sleep_enable(); //开启休眠功能。

  digitalWrite(ledPin,HIGH);

  ///将中断触发引脚(2号引脚)设置为INPUT_PULLUP(输入上拉)模式

  pinMode(2, INPUT_PULLUP); 

  attachInterrupt(0,hello,CHANGE);

  sleep_cpu();

  bsleep=0;

}  


void loop()  

{  

  Serial.print("run...\r\n");

 // sleep_cpu();//进入休眠状态,从此处开始进入休眠。这里不需要喂狗。目的就是等狗超时后执行唤醒函数。

  //delay(1000);

  nn++;

  if(nn>500)

  {

    bsleep=0;

    sleep_enable(); //开启休眠功能。

    sleep_cpu();

    

  }


void hello()

{

  Serial.print("up...\r\n");

 

  if(bsleep==0)

  { 

    sleep_disable();

    nn=0;

  }

  bsleep=1;

  

}



各种Arduino外部中断程序

Arduino的外部中断例程1

Uno和Pro Mini有2个引脚(d2和d3),能够中断Arduino正在做的事情。有了这个,我们可以将Arduino推回到清醒状态。

我们把一个按键开关,接在数字管脚上,触发外部中断事件。

不是所有数字管脚都支持外部中断,对于Arduino NANO,  D2, D3两个脚支持外部中断。



1, 准备元器件和器材

     按键开关  一个


     面包板一块

     1K 电阻一个

      杜邦线若干


2 电路原理图

1.jpg

图中:  开关接在5V与GND之间,串接一个电阻R作为限流电阻。D2接到开关上。

当开关闭合, D2的电平为0V(低电平),  当中断有效时,将触发外部中断。

int pinInterrupt = 2; //接中断信号的脚  

  

void onChange()  

{  

   if ( digitalRead(pinInterrupt) == LOW )  

      Serial.println("Key Down");  

   else  

      Serial.println("Key UP");  

}  

  

void setup()  

{  

   Serial.begin(9600); //打开串口  

  

   pinMode( pinInterrupt, INPUT);//设置管脚为输入  

     

   //Enable中断管脚, 中断服务程序为onChange(), 监视引脚变化  

   attachInterrupt( digitalPinToInterrupt(pinInterrupt), onChange, CHANGE);  

}  

  

void loop()  

{  

  // 模拟长时间运行的进程或复杂的任务。  

  for (int i = 0; i < 100; i++)  

  {  

    // 什么都不做,等待10毫秒  

    delay(10);   

  }  

}  



同一个外部中断的Arduino随机睡眠与唤醒

引言

“预定睡眠-随机唤醒”是嵌入式系统设计中常用的方法。例如,在完成规定的数据发送或接收任务后,MCU立即进入休眠以节约电力,这就是“预定睡眠”的含义;当有新的数据发送和接收任务时,通过各种方式唤醒MCU执行前述任务,这就是“随机唤醒”的含义。在实际工程中,完成该功能主要使用软件,首先整合系统功能,执行完功能模块后马上执行预定的睡眠指令,而唤醒功能则直接在中断服务程序中体现。很多教科书、论坛中广泛介绍的也都是这种方法,该方法能够满足一般工程设计的需要。但“预定睡眠”在某些工程中并不适合使用,例如在无线传感网络构成的系统中,某个节点或分系统的睡眠时机不是由自己决定的,而是远程随机控制的。在这种情况下,睡眠指令在节点或分系统执行自己任务的过程中随机发生,同样,唤醒指令发出也是随机的,通常为节省系统的硬件开支并提高其可靠性,发出睡眠和唤醒指令使用同一个信号,所以系统软件的睡眠指令就不能固定在系统功能程序的预定位置,甚至不能在功能主程序及其调用的函数中出现。

1 目前Arduino睡眠与唤醒的方法

在Arduino IDE中控制Arduino睡眠的工具主要有Enerlib和LowPower两种开源类库,前一种已被Arduino开源库收录,这两种类库专门完成 Arduino的睡眠控制。Arduino的睡眠效果和典型应用在参考文献中有详细的介绍,但也未涉及到随机进入睡眠的方法。

1.1 使用Enerlib类库的实现方法

Enerlib类库提供的Example代码说明了如何使用该类库的功能函数,为阅读方便起见,笔者添加了行号标识和中文注释,如下所列:

1.jpg

2.jpg

可以看出,进入睡眠的代码在仅执行一次的初始化模块void setup()中,表明系统启动后做完初始化工作即进入睡眠。代码的19~23行给出了5种睡眠方式,在实际应用中选择一种适合的方式即可。系统的唤醒在 Int0外部中断服务程序中,Enerlib类库提供了一个只能在中断服务程序中使用的WasSleeping()函数,以判定系统目前的“睡眠/醒着” 的状态,该程序执行一次睡眠,然后随机唤醒后不能再次进入睡眠状态。这是一个典型的“预定睡眠一随机唤醒”实例。

1.2 使用LowPower类库的实现方法

LowPower类库提供的实例代码如下(行号和中文注释由笔者添加):

3.jpg

4.jpg

可以看出,进入睡眠的代码在主程序循环体void loop()内,系统启动后即进入休眠状态,一旦有外部中断即唤醒,唤醒后执行完主程序功能后再次进入睡眠,这也是典型的“预定睡眠-随机唤醒”实例。

因ATMega单片机在外部中断模式下会忽略引起中断引脚的数据方向,所以该例程第10行“pinMode(wake UpPin,INPUT);”没有必要。

以上两种睡眠与唤醒实例均实现不了随机进入睡眠的功能要求。

2 随机睡眠与唤醒的方法

如果可以实现在系统工作过程中随时中止工作进入睡眠(如为了节省电力而远程控制终止节点的监测),然后在适当的时机再唤醒系统继续工作,就是典型的“随机睡

眠-随机唤醒”功能。

2.1 随机进入睡眠与唤醒的方法

在以上两个实例中,一个是将睡眠指令放于初始化模块,实现一次性主动睡眠;另一个是将睡眠指令放在主程序循环体中,在执行完预定功能后主动进入睡眠,二者均有外部随机唤醒。如果要实现通过外部指令(中断)随机进入睡眠,睡眠指令首先不能放在初始化模块void setup()中,因为这样仅会引起一次睡眠;如果放到主程序循环体void loop()中,则必须在由“睡眠/唤醒”指令引起的中断服务程序中放置相应的标志,然后再在主程序中判断这个标志,势必增加相应的软件开支,而且程序的结构也不明晰。

因此,实现随机进入睡眠的最简单的方法就是将睡眠指令直接置于中断服务程序当中,当睡眠指令发出后,执行中断服务程序进入睡眠;当唤醒指令发出后,只要进入了中断服务程序即可唤醒。但是在同一个中断服务程序中实现该功能,需要透彻地了解相应单片机的中断处理机制以及所用的库函数对中断的处理方法。

2.2 Arduino的外部中断

构成Arduino的核心处理器主要是Atmel的AVR ATmega系列MCU(如ATmega328),常用的实验平台有UNO、NANO、Pro、Pro Mini、Micro、Lilypad、Leonardo、Duemilanove等,因此Arduino的睡眠与唤醒的控制实质是对MCU的控制,也就是控制ATnaega328睡眠与唤醒。

实现随机睡眠与唤醒的实验平台是Arduino Pro Mini3.3 V、8 MHz/ATInega328,ATrnega328的外部中断机制主要有:①RESET具有最高的优先级,第二个为INT0;②任一中断发生时全局中断使能位被清零,从而禁止了所有其他的中断;③退出中断后,总是回到主程序并至少执行一条指令才可以去执行其他被挂起的中断;④如果选择了边沿触发方式或电平变化触发方式,那么持续时间大于一个时钟周期的脉冲将触发中断,过短的脉冲则不能保证触发中断,如果选择低电平触发方式,那么低电平必须保持到当前指令执行完成;⑤外部中断通过引脚INT0、INT1与INT2触发,只要使能中断且电平发生了合适的变化,即使引脚INT0~2配置为输出,中断也会触发;⑥若要求INT0与INT1在信号下降沿或上升沿触发,则I/O时钟必须工作;⑦通过电平方式触发中断,在将MCU从掉电模式唤醒时,要保证电平保持一定的时间,以降低MCU对噪声的敏感程度;⑧中断响应时间最少为4个时钟周期,若中断发生时MCU处于休眠模式,中断响应时间还需增加4个时钟周期,此外还要考虑到不同的休眠模式所需要的启动时间。

2.3 Enerlib和LowPower类库的中断处理方式

两个类库中,Enerlib的5种睡眠指令为PowerDown()、Standby()、PowerSave()、SleepADC()、Idle(),LowPower的5种睡眠方式为ADC_OFF、BOD_OFF、IDLE_OFF、STANDBY EXT_STANDBY,除了对各种不同设备进行关断外,对系统中断的处理方式均相同,即睡眠前后均不对中断的使能进行处理,而且均提供了打开和关闭终端使能的函数。这样,打开和关闭中断的时机就完全交给了开发者。

3 随机睡眠与唤醒的实现与结果

使用Arduino自带的LED(Pin13驱动)来检验随机睡眠与唤醒,睡眠发生时LED闪烁0.1 s,表示接收到睡眠命令,然后熄灭LED进入睡眠;唤醒发生时LED闪烁0.1 s,表示接收到唤醒命令,然后LED以1 Hz频率闪烁工作。以下代码笔者添加了行号标识和中文注释。

5.jpg

6.jpg

将Arduino Pro Mini的Pin2(D2)通过一个10 KΩ电阻连接到Vcc,然后在Pin2对地连接一个按键开关,下载完程序后上电,系统LED进入1 Hz闪亮模式,按一下按键,LED闪烁0.1 s,然后进入睡眠;再按一下按键,LED闪烁0.1 s,然后系统LED进入1 Hz闪烁模式,如此重复。

结语

Arduino的核心--ATmega系列单片机,以其RISC的可靠结构、主控板、外围模块等硬件的规范设计,具有较高的可靠性。Arduino之所以风靡全球,除了开源的硬件之外,更重要的是配套了开发软件的IDE以及众多的类库,同时IDE集成、定义了编程方法,底层的Bootloader就像是个微型操作系统,而不像使用Keil C开发单片机软件那样自己来组织程序的结构、编写驱动程序,Arduino的软件和硬件设计使得编程、供电、下载一体化。由于其遵循开源共享的宗旨,相关类库会越来越丰富,也正是这个IDE将软件开发工具和实际硬件隔离,使得软件开发变得简单、快速,开发者可以专注于方案的实施。当然也由于软件底层与功能模块硬件的隔离,使得开发者越来越依赖类库,这一循环导致开发者过分依赖类库而不去探究问题的实质,最后导致问题得不到解决或者退而更换主控系统硬件,从而妨碍了Arduino更大范围的应用。本文所叙述的例程可以根据实际工程要求做适当修改而直接应用,具有一定的参考价值。


Arduino休眠模式和看门狗以及中断详解

原文

一、休眠模式

??Arduino睡眠模式也称为Arduino省电模式(Power Save mode)或Arduino待机模式(Standby Mode)。Arduino睡眠模式允许用户停止或关闭微控制器中未使用的模块,从而显着降低功耗。 Arduino UNO、Arduino Nano和Pro-mini配备了ATmega328P,它有一个欠压检测器(BOD),用于监控睡眠模式时的电源电压。

?? ATmega328P有六种睡眠模式,要进入任何睡眠模式,我们需要在睡眠模式控制寄存器(SMCR.SE)中启用睡眠位。然后,睡眠模式选择位选择Idle、ADC noise reduction、Power-Down、Power-Save、Standby和External Standby的睡眠模式。在现实世界中,实际上只有一种模式很有用;掉电模式(SLEEP_MODE_PWR_DOWN)。

??内部或外部Arduino中断或复位可以将Arduino从睡眠模式唤醒。

??Arduino像电脑和手机一样,也具备睡眠/休眠/待机功能。在睡眠状态下,系统几乎完全停止运作,只保留基本的侦测功能,因此只消耗少许电力。以电脑为例,在睡眠状态下,可被键盘按键或者网络信息唤醒。


主要功能函数如下:


  #include <avr/sleep.h>//引用库文件  

  set_sleep_mode (SLEEP_MODE_PWR_DOWN);// 设置休眠模式  

  sleep_mode (); // 进入休眠状态

注意: sleep_mode 为宏指令,它会自动自动开启休眠功能、进入睡眠状态、禁用休眠功能。 按照官方解释,在某些条件下, sleep_mode 宏会导致个别操作步骤开启休眠功能并发出sleep指令进入休眠,所以,另外提供了以下三个指令来分步完成sleep_mode ()工作:

  sleep_enable();  // 开启休眠功能  

  sleep_cpu (); // 进入休眠状态  

  sleep_disable();//关闭休眠功能 

也就是说,貌似使用sleep_mode会出现意外情况,所以,根据情况自己选择吧。


#include <avr/sleep.h>

void setup () 

{  

  set_sleep_mode (SLEEP_MODE_PWR_DOWN); // 采用“Power-down”睡眠模式 

  sleep_enable();// 启动睡眠模式 

  sleep_cpu ();  // 进入睡眠模式

void loop () 

{

}

这段程序在UNO R3控制板上,约消耗32.9 mA电流;但是在精简的「准系统」Arduino板,仅仅消耗0.36mA(360μA)

1.png

微控器内部除了中央处理器(CPU),还有內存、模拟数位转换器、序列通信…等模块。越省电的模式,仍在运作中的模块就越少。

??例如,在”Power-Down”(电源关闭)睡眠模式之下,微控器仅剩下外部中断和看门狗定时器(Watchdog Timer)仍持续运作。而在Idle睡眠模式底下,SPI,UART(也就是序列端口)、定时器、模拟数位转换器等,仍持续运作,只有中央处理器和闪存(Flash)时脉信号被停止。时脉信号就像心跳一样,一旦停止时脉信号,相关的元件也随之暂停。


睡眠中断的触发

中断触发种类:


通过外部中断。只有在发生外部中断时才会唤醒。

通过UART(USB串行接口)。保持睡眠,直到通过串行接口收到数据。

通过一个内部计时器,将定期通过Timer1从睡眠中醒来,执行一个动作并返回到睡眠状态。

通过看门狗定时器。定期通过看门狗定时器从睡眠中醒来,执行一个动作并返回到睡眠状态。请注意使用Watchdog可以提供最长的睡眠时间和最低的功耗。

二、看门狗

??看门狗,又叫 watchdog timer,是一个定时器电路, 一般有一个输入,叫喂狗(kicking the dog or service the dog),一个输出到MCU的RST端,MCU正常工作的时候,每隔一端时间输出一个信号到喂狗端,给 WDT清零,如果超过规定的时间不喂狗(一般在程序跑飞时),WDT 定时超过,就会给出一个复位信号到MCU,是MCU复位,防止MCU死机。看门狗的作用就是防止程序发生死循环,或者说程序跑飞。出于对单片机运行状态进行实时监测的考虑,产生了一种专门用于监测单片机程序运行状态的芯片,俗称"看门狗"(watchdog)。

??看门狗定时器(WDT:Watch Dog Timer)实际上是一个计数器。一般给看门狗一个大数,程序开始运行后看门狗开始倒计数。如果程序运行正常,过一段时间CPU应该发出指令让看门狗复位,令其重新开始倒计数。如果看门狗计数减到0,就认为程序没有正常工作(因为没有及时复位),就强制整个系统复位(单片机重启)。

??所以,当你开启看门狗后,需要在看门狗超时(计数减到0)前,对其进行 喂狗(复位)操作,否则看门狗会强制你的单片机重启,从头运行程序。如果看门狗在休眠或空闲模式下超时,器件将唤醒并从PWRSAV指令执行处继续执行代码,同时“休眠”状态位(RCON< 3>)或“空闲”状态位(RCON< 2>)会置1,表示器件之前处于省电模式。

??功能作用:看门狗可以在你的程序陷入死循环的时候,让单片机复位而不用整个系统断电,从而保护你的硬件电路。


使用看门狗需要引用头文件 【 avr/wdt.h 】,在wdt.h中,提供了3个看门狗API:


wdt_enable(timeout) //看门狗启动,并设置超时时间

wdt_disable() //看门狗停止

wdt_reset() //看门狗复位(喂狗)


wdt_enable(timeout) 中timeout为超时时间,当超过这个时间后没有喂狗,则单片机重启。

这个时间可使用如下常量:

0=15(16)ms, 1=30(32)ms,2=60(64)ms,3=120(128)ms,4=250ms,5=500ms

6=1 sec,7=2 sec, 8=4 sec, 9= 8sec

1.png

使用看门狗很简单,只需要做下面三步即可:


1、引用头文件 #include avr/wdt.h

2、Setup函数中启动看门狗,并设置超时时间为两秒:wdt_enable(WDTO_2S);

3、Loop函数中喂狗,防止饿死(重启): wdt_reset();


代码如下:


#include <avr/wdt.h>  

int ledPin = 13;

void setup() 

{  

  pinMode(ledPin, OUTPUT);       

  wdt_enable(WDTO_2S);//启动看门狗,设置喂狗时间不能超过2秒      

}  

void loop()  

{  

  digitalWrite(ledPin, HIGH);    

  delay(500);     

  digitalWrite(ledPin, LOW);    

  //喂狗。如果超过2S没有喂狗,则单片机重启。 也就是说,如果本循环执行时间超过2S的话,单片机就会自动重启。

  wdt_reset();    


其它应用:

【利用看门狗进行休眠唤醒】

用下面的代码,代替wdt_enable(),并且不要喂狗。

这样就实现了看门狗超时后,执行唤醒函数,而不是重启单片机。


void wdt_setup(int ii) 

{

// ii为看门狗超时时间,支持以下数值:

// 0=16毫秒, 1=32毫秒,2=64毫秒,3=128毫秒,4=250毫秒,5=500毫秒

// 6=1秒 ,7=2秒, 8=4秒, 9=8秒

byte bb;

if (ii > 9 ) ii = 9;

bb = ii & 7;

if (ii > 7) bb |= (1 << 5);

bb |= (1 << WDCE);

//开始设置看门狗中断   

MCUSR &= ~(1<<WDRF);  //清除复位标志

WDTCSR |= (1<<WDCE) | (1<<WDE);

//设置新的看门狗超时时间

WDTCSR = bb;

//设置为定时中断而不是复位

WDTCSR |= _BV(WDIE); 

//别忘了设置【看门狗唤醒执行函数】

}


看门狗唤醒执行函数:


ISR(WDT_vect)

{

    //唤醒后执行的代码

}


实例一

测试代码如下:


#include <avr/wdt.h>  

#include <avr/sleep.h>

int ledPin = 13;

int data=0;


ISR(WDT_vect)

{

  //看门狗唤醒执行函数

  data++;

}


void setup() 

{  

pinMode(ledPin, OUTPUT);   

set_sleep_mode(SLEEP_MODE_PWR_DOWN); //设置休眠模式。

sleep_enable(); //开启休眠功能。

//ACSR |=_BV(ACD);//关掉ACD,据说很省电。不知道唤醒以后要不要重新开,怎么开?

//ADCSRA=0;//关掉ADC,据说很省电。不知道唤醒以后要不要重新开,怎么开?

//按照官方解释,sleep_enable()最好写在中断(attachInterrupt())前,防止中断在开始休眠前就提前释放而造成休眠后无法唤醒。

//开始设置看门狗中断,用来唤醒。   

MCUSR &= ~(1<<WDRF);

WDTCSR |= (1<<WDCE) | (1<<WDE);

WDTCSR = 1<<WDP1 | 1<<WDP2;

WDTCSR |= _BV(WDIE); 

}  


void loop()  

{  

  if (data>=5)

  {

  digitalWrite(ledPin, HIGH);    

  delay(500);     

  digitalWrite(ledPin, LOW);    

  data=0;

  }

  sleep_cpu();//进入休眠状态,从此处开始进入休眠。这里不需要喂狗。目的就是等狗超时后执行唤醒函数。


本实验程序的行为如下:

1.启动时,每隔0.5秒点、灭三次位于第13脚的LED。

2.LED闪烁完毕后,进入“Power-down(断电)”睡眠模式,5秒之后又开始闪烁一次。


实例二

测试代码如下:


#include <avr/wdt.h>  

#include <avr/sleep.h>

int ledPin = 13;

int data=0;


ISR(WDT_vect)

{

  //看门狗唤醒执行函数

  data++;

}

void setup() 

{  

pinMode(ledPin, OUTPUT);   

set_sleep_mode(SLEEP_MODE_PWR_DOWN); //设置休眠模式。

sleep_enable(); //开启休眠功能。

//ACSR |=_BV(ACD);//关掉ACD,据说很省电。不知道唤醒以后要不要重新开,怎么开?

//ADCSRA=0;//关掉ADC,据说很省电。不知道唤醒以后要不要重新开,怎么开?

//按照官方解释,sleep_enable()最好写在中断(attachInterrupt())前,防止中断在开始休眠前就提前释放而造成休眠后无法唤醒。

//开始设置看门狗中断,用来唤醒。   

MCUSR &= ~(1<<WDRF);

WDTCSR |= (1<<WDCE) | (1<<WDE);

WDTCSR = 1<<WDP1 | 1<<WDP2;

WDTCSR |= _BV(WDIE); 

}  

void loop()  

{  

  if (data>=5)

  {

  digitalWrite(ledPin, HIGH);    

  delay(500);     

  digitalWrite(ledPin, LOW);    

  data=0;

  }

  sleep_cpu();//进入休眠状态,从此处开始进入休眠。这里不需要喂狗。目的就是等狗超时后执行唤醒函数。 


或者代码可以如下:


#include <avr/wdt.h>  

#include <avr/sleep.h>

int ledPin = 13;

int data=0;


ISR(WDT_vect)

{

  //看门狗唤醒执行函数

  data++;

}

void setup() 

{  

pinMode(ledPin, OUTPUT);   

set_sleep_mode(SLEEP_MODE_PWR_DOWN); //设置休眠模式。

//开始设置看门狗中断,用来唤醒。   

MCUSR &= ~(1<<WDRF);

WDTCSR |= (1<<WDCE) | (1<<WDE);

WDTCSR = 1<<WDP1 | 1<<WDP2;

WDTCSR |= _BV(WDIE); 

}  


void loop()  

{  

  if (data>=5)

  {

  digitalWrite(ledPin, HIGH);    

  delay(500);     

  digitalWrite(ledPin, LOW);    

  data=0;

  }

  sleep_mode(); //进入休眠状态,从此处开始进入休眠。这里不需要喂狗。目的就是等狗超时后执行唤醒函数。 

三、外部中断

1.为什么需要中断?

??因为没有中断,你不能让你的Arduino进入睡眠状态,并期望它再次唤醒(一般来说,在有限的情况下,有办法在没有中断的情况下从睡眠中唤醒)。如果你不能入睡,一直工作你的能量很快就会消耗殆尽。睡眠模式消耗的功率非常小,但需要特别设置。你必须知道的第一件事是如何编写代码来利用中断,然后你可以使用更强大的技术。


2.不关心功耗还需要中断吗?

??是! 即使你不打算让处理器进入睡眠状态,也可能需要中断!如果您有一个与时间相关的应用,如需要每间隔几毫秒发生一次操作,或者需要在发生外部事件后立即发生操作,这是中断就会至关重要。如果你熟悉Arduino编程的基础知识,你可能会想知道为什么你不能只用一个简单的while循环来检查什么时候执行你的动作。简单的while循环是可以按顺序读取每个引脚的状态,但这种方法并不可靠。如果只简单的应用while循环来检查按钮的轮询的话,你的while循环再次检查按钮状态之前,该按钮是否已经被按下并释放?你会错过按钮按下。如果您正在寻找一个非常快速的事件,例如来自传感器的信号,因为害怕错过关键事件,你必须经常进行轮训,从而导致你的程序无法做任何其他事情。但是有了中断,你100%保证能够赶上事件。使用中断还可以使您不必经常检查状态,从而节省计算能力,并让while循环更快地完成其他任务。

??试想一下,你正在家里吃饭,这时传来了敲门声,虽然你巨饿,虽然面前全是山珍海味,但此时你不得不去开门,同时不得不放停下生命中最重要的事情——吃饭。打开门后,你发现只是一个查水表的,你检查了水表读数并告诉了查水表的人。关上门,你马不停蹄的又投入了于食物的作战中。

??我们来分析一下这个颇具传奇性的故事,在这里人生的主旋律——吃饭,就是你的主程序,而敲门声,就是一个中断信号,它让你不得不去执行你的人生插曲——开门接客这个中断函数。完成这个小插曲后,你又要投入到主线剧情 吃饭这个主程序上。

??现在我想告诉你一个惊天秘密,其实你妈欺骗了你,你根本不是他们亲生的,你是人造人,而你的大脑里装备了一个arduino控制器!你的型号是 Arduino 吃货,之所以叫这么2的名字,是因为你的loop的写法问题。我们来看看你的loop函数。


void loop() 

{

  吃();

}

//吃,是的,你没有看错,你的人生是如此的幸福,就是不断的 吃();循环。

//但实际上,你还有附加功能 开门();

void 开门()

{

打开门;

if(门口的人==女神)

    跪舔();

if(门口的人==查水表的)

    报告水表读数();

}


??为了让你能顺利执行 开门();动作,你的亲生父母还得在Setup函数中设置 开门();这个动作何时启动。具体的方法是attachInterrupt(中断通道, 中断函数, 触发方式); 在这里中断通道就是你的耳朵(不要问为什么不是屁股),触发 开门();这个函数的方式是敲门声。


void setup()

{

      attachInterrupt(耳朵, 开门, 敲门声);

}


??这样设定后,你每次听到敲门声,就不得不去打开门,并执行相应的动作了。也许你对这样的人物设定不太满意,但这就是你的宿命,少年。忘记你蛋碎的屌丝设定吧,我们要开始严肃的讨论问题了!


3.各种板子的中断

UNO、NANO、ProMINI这仨板子都是INT0 (D2针脚:中断编号为0)、INT1(D3针脚:中断编号为1)。 

1

4.中断函数、中断触发模式与设置中断

【中断函数】:就是你要去执行的函数,这个函数不能带任何参数,且没有返回类型。如:


void hello()

{

  Serial.println("hello");


【中断模式】:就是你的中断触发方式。在大多数arduino上有以下四种触发方式:


LOW                  低电平触发

CHANGE            电平变化,高电平变低电平、低电平变高电平

RISING              上升沿触发

FALLING           下降沿触发

HIGH                高电平触发(该中断模式仅适用于Arduino due) 


【设置中断】:在定义中断函数后,要使用外部中断,你只需要在程序的Setup部分配置好中断函数即可,配置函数如下:


attachInterrupt(interrupt, function, mode); 

//interrupt为你中断通道编号,function为中断函数,mode为中断触发模式 

需要注意的是在Arduino Due中,中断设置有点不同: 

attachInterrupt(pin, function, mode);

//due 的每个IO均可以进行外部中断,所以这里第一个参数为pin,即你使用的引脚编号。 

//如果在程序中途,你不需要使用外部中断了,你可以用中断分离函数来取消这一中断设置:detachInterrupt(interrupt); 

同样在Arduino Due上,该函数为detachInterrupt(Pin);。


5.例程

外部中断的使用也是非常简单的,下面我们来看一个官方提供的例程


volatile int state = 0; 

void setup()

{

  pinMode(13, OUTPUT);

  attachInterrupt(0, blink, CHANGE);//当int.0电平改变时,触发中断函数led

void loop()

{

  digitalWrite(13, state);

void led()//中断函数

{

  state = !state;

}


应用:利用外部中断,可以在很多地方提高你程序的运行效率。你可以运用以上知识,做一个简单的监控装置。


使用Arduino读取水流速传感器的脉冲数

这个实验也是我现在在做的一个项目的一部分,就是需要测量水的流速,然后就要用到水流速传感器,然后读取脉冲数换算得到水流速,做法很简单,只是这个测量模型搭建起来,购买材料和设备花了点时间,话不多说,来讲讲具体做法:


所需设备:水流速传感器、Arduino UNO开发板,抽水水泵、水箱,水管若干,杜邦线若干


水流速传感器:这个我在网上找了好久才找到合适的,我用的是这个:


1.png


然后用水管将水泵和流速传感器连接起来,为了不浪费水,让水管的另一头流入水箱,形成一个水流回路,如图所示

2.jpg

3.jpg




之后利用杜邦线延长线将黄色信号线接入Arduino开发板的2号数字引脚,将红色线接入开发板的5V引脚,黑色线接入GND引脚


再将开发板用串口线连接到电脑,打开Arduino开发环境,接下来上代码:




#define PIN 2

volatile long count =0;

void setup() {

  // put your setup code here, to run once:

  pinMode(PIN,INPUT);

  attachInterrupt(0,blinkA,FALLING);

  Serial.begin(9600);

}


void loop() {

  // put your main code here, to run repeatedly:

  Serial.println(count);

  delay(1000);

}

void blinkA()

{

  count++;

}


然后打开水泵让其抽水,在Arduino开发环境的串口监视器中就能看到流速传感器实时的脉冲数,然后根据具体的流速传感器的换算公式就能得到实时的水流速信息。


Arduino看门狗的使用方法

使用Arduino UNO的看门狗:


    介绍:看门狗定时器用避免电子设备出现故障造成死机,例如死循环。为此,看门狗作为一个物理定时器,在程序运行时不断的增加,并驱动一个中断, 如果达到某一个设定的值,就会复位微处理器。在正常操作下,定时器在程序流程中的定期设置为0, 但如果代码进入死循环,定时器复位不会发生,看门狗触发中断并重新启动。


    使用看门狗防止死机:Arduino为用户提供了方便操作的函数和宏,可以直接引入库:


#include <avr/wdt.h>

    这个函数库提供了三个API函数供我们使用:


wdt_enable(timeout) //看门狗使能并设置超时时间

wdt_disable() //关闭

wdt_reset() //复位

timeout可以使用wdt.h文件中定义的宏常量,也可以自定义时间;


通常情况下,需要在setup()函数中使能看门狗定时器,并在loop()中不断的喂狗防止溢出,官方给出的示例代码如下:


#include <avr/wdt.h>

void setup()

{

   pinMode(13,OUTPUT);

   wdt_enable(WDTO_2S); //开启看门狗,并设置溢出时间为两秒

}

 

void loop()

{

   digitalWrite(13,HIGH);

   delay(100);

   digitalWrite(13,LOW);

   delay(100);

   wdt_reset(); //喂狗操作,使看门狗定时器复位

}

注:看门狗也可以唤醒省电模式下的 Arduino, 详细说明可查阅官方文档,特别指出的是,如果使用POWER_MODE_IDLE模式(UART、SPI、timer等处于工作状态),需在进入休眠状态前关闭看门狗功能,休眠结束之后再次启动,否则几秒之后看门狗会强制重启Aruino, 休眠失败。示例代码:


/* Sleep Demo Serial

 * -----------------

 * Example code to demonstrate the sleep functions in a Arduino. Arduino will wake up

 * when new data is received in the serial port USART

 * Based on Sleep Demo Serial from http://www.arduino.cc/playground/Learning/ArduinoSleepCode

 *

 * Copyright ? 2006 MacSimski 2006-12-30

 * Copyright ? 2007 D. Cuartielles 2007-07-08 - Mexico DF

 *

 * With modifications from Ruben Laguna 2008-10-15

 *

 * This program is free software: you can redistribute it and/or modify

 * it under the terms of the GNU General Public License as published by

 * the Free Software Foundation, either version 3 of the License, or

 * (at your option) any later version.

 *

 * This program is distributed in the hope that it will be useful,

 * but WITHOUT ANY WARRANTY; without even the implied warranty of

 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

 * GNU General Public License for more details.

 *

 * You should have received a copy of the GNU General Public License

 * along with this program. If not, see <http://www.gnu.org/licenses/>.

 *

 */

 

#include <avr/power.h>

#include <avr/sleep.h>

 

int sleepStatus = 0; // variable to store a request for sleep

int count = 0; // counter

 

void setup()

{

 

Serial.begin(9600);

}

 

void sleepNow()

{

/* Now is the time to set the sleep mode. In the Atmega8 datasheet

 * http://www.atmel.com/dyn/resources/prod_documents/doc2486.pdf on page 35

 * there is a list of sleep modes which explains which clocks and

 * wake up sources are available in which sleep modus.

 *

 * In the avr/sleep.h file, the call names of these sleep modus are to be found:

 *

 * The 5 different modes are:

 * SLEEP_MODE_IDLE -the least power savings

 * SLEEP_MODE_ADC

 * SLEEP_MODE_PWR_SAVE

 * SLEEP_MODE_STANDBY

 * SLEEP_MODE_PWR_DOWN -the most power savings

 *

 * the power reduction management <avr/power.h> is described in

 * http://www.nongnu.org/avr-libc/user-manual/group_avr_power.html

 */

 

set_sleep_mode(SLEEP_MODE_IDLE); // sleep mode is set here

 

sleep_enable(); // enables the sleep bit in the mcucr register

// so sleep is possible. just a safety pin

 

power_adc_disable();

power_spi_disable();

power_timer0_disable();

power_timer1_disable();

power_timer2_disable();

power_twi_disable();

wdt_disable(); //close wdt before Arduino enter sleep mode

 

sleep_mode(); // here the device is actually put to sleep!!

 

// THE PROGRAM CONTINUES FROM HERE AFTER WAKING UP

sleep_disable(); // first thing after waking from sleep:

// disable sleep...

wdt_enable(timeout); //open wdt after wakine from sleep

power_all_enable();

 

}

 

void loop()

{

// display information about the counter

Serial.print("Awake for ");

Serial.print(count);

Serial.println("sec");

count++;

delay(1000); // waits for a second

 

// compute the serial input

if (Serial.available()) {

int val = Serial.read();

if (val  'S') {

      Serial.println("Serial: Entering Sleep mode");

      delay(100);     // this delay is needed, the sleep

                      //function will provoke a Serial error otherwise!!

      count = 0;

      sleepNow();     // sleep function called here

    }

    if (val  'A') {

Serial.println("Hola Caracola"); // classic dummy message

}

}

 

// check if it should go asleep because of time

if (count >= 10) {

Serial.println("Timer: Entering Sleep mode");

delay(100); // this delay is needed, the sleep

//function will provoke a Serial error otherwise!!

count = 0;

sleepNow(); // sleep function called here

}

}



Arduino 键盘串口

此示例监听来自串口的一个字节。当接收到时,电路板发送一个击键回到计算机。发送的击键比接收的击键高一个,因此如果从串口监视器发送“a”,你将从连接到计算机的电路板接收到“b”。“1”将返回“2”等。

警告 - 当你使用 Keyboard.print()命令时,Leonardo,Micro或Due板会接管你计算机的键盘。为确保在使用此功能运行草图时不会失去对计算机的控制,请在调用Keyboard.print()之前设置可靠的控制系统。这个草图被设计为只在板通过串口接收到一个字节后才发送一个键盘命令。

必需的组件

你将需要以下组件:

  • 1 × Arduino Leonardo, Micro, 或 Due板

程序

只需使用USB线将电路板连接到计算机。

1.jpg

草图

在计算机上打开Arduino IDE软件。使用Arduino语言进行编码控制你的电路。通过单击“New”打开一个新的草图文件。

2.jpg


注意 - 你必须在Arduino库文件中包含键盘库。将键盘库文件复制并粘贴到以下标黄色的名为“libraries”的文件中。

3.jpg

Arduino代码

/*
   Keyboard test
   For the Arduino Leonardo, Micro or Due Reads
      a byte from the serial port, sends a keystroke back. 
   The sent keystroke is one higher than what's received, e.g. if you send a, you get b, send
      A you get B, and so forth.
   The circuit:
   * none
*/

#include "Keyboard.h"

void setup() {
   // open the serial port:
   Serial.begin(9600);
   // initialize control over the keyboard:
   Keyboard.begin();
}

void loop() {
   // check for incoming serial data:
   if (Serial.available() > 0) {
      // read incoming serial data:
      char inChar = Serial.read();
      // Type the next ASCII value from what you received:
      Keyboard.write(inChar + 1);
   }
}

代码说明

一旦开始编程,则打开你的串口监视器并发送一个字节。电路板将回复一个击键,这是一个更高的数字。

结果

当你发送一个字节时,电路板将会在Arduino IDE串口监视器上回复一个更高数字的击键。



Arduino 键盘注销

本示例使用键盘库将你在计算机上的用户会话注销,此时ARDUINO UNO上的引脚2被拉至接地。草图同时按两个或三个按键的顺序模拟按键,并在短暂的延迟之后释放它们。

警告 - 当你使用 Keyboard.print()命令时,Arduino接管你的计算机键盘。为确保在使用此功能运行草图时不会失去对计算机的控制,请在调用Keyboard.print()之前设置可靠的控制系统。此草图旨在在引脚被拉至接地后才发送键盘命令。

必需的组件

你将需要以下组件:

  • 1 × Breadboard 面包板
  • 1 × Arduino Leonardo, Micro, 或Due板
  • 1 × 按钮
  • 1 × 跳线

程序

按照电路图连接面包板上的组件,如下图所示。

1.jpg

草图

在计算机上打开Arduino IDE软件。使用Arduino语言进行编码控制你的电路。通过单击“New”打开一个新的草图文件。

对于本例,你需要使用Arduino IDE 1.6.7

2.jpg

注意 - 你必须在Arduino库文件中包含键盘库。将键盘库文件复制并粘贴到名为libraries的文件(下图高亮显示)中,如以下屏幕截图所示。

3.jpg

Arduino代码

/*
   Keyboard logout
   This sketch demonstrates the Keyboard library.
   When you connect pin 2 to ground, it performs a logout.
   It uses keyboard combinations to do this, as follows:
   On Windows, CTRL-ALT-DEL followed by ALT-l
   On Ubuntu, CTRL-ALT-DEL, and ENTER
   On OSX, CMD-SHIFT-q
   To wake: Spacebar.
   Circuit:
   * Arduino Leonardo or Micro
   * wire to connect D2 to ground.
*/

#define OSX 0
#define WINDOWS 1
#define UBUNTU 2

#include "Keyboard.h"

// change this to match your platform:
int platform = WINDOWS;

void setup() {
   // make pin 2 an input and turn on the
   // pullup resistor so it goes high unless
   // connected to ground:
   
   pinMode(2, INPUT_PULLUP);
   Keyboard.begin();
}

void loop() {
   while (digitalRead(2) == HIGH) {
      // do nothing until pin 2 goes low
      delay(500);
   }
   
   delay(1000);
   
   switch (platform) {
      case OSX:
      Keyboard.press(KEY_LEFT_GUI);
	  
      // Shift-Q logs out:
      Keyboard.press(KEY_LEFT_SHIFT);
      Keyboard.press('Q');
      delay(100);
	  
      // enter:
      Keyboard.write(KEY_RETURN);
      break;
	  
      case WINDOWS:
      // CTRL-ALT-DEL:
      Keyboard.press(KEY_LEFT_CTRL);
      Keyboard.press(KEY_LEFT_ALT);
      Keyboard.press(KEY_DELETE);
      delay(100);
      Keyboard.releaseAll();
	  
      //ALT-l:
      delay(2000);
      Keyboard.press(KEY_LEFT_ALT);
      Keyboard.press('l');
      Keyboard.releaseAll();
      break;
	  
      case UBUNTU:
      // CTRL-ALT-DEL:
      Keyboard.press(KEY_LEFT_CTRL);
      Keyboard.press(KEY_LEFT_ALT);
      Keyboard.press(KEY_DELETE);
	  
      delay(1000);
      Keyboard.releaseAll();
	  
      // Enter to confirm logout:
      Keyboard.write(KEY_RETURN);
      break;
   }
   
   // do nothing:
   while (true);
}

Keyboard.releaseAll();

   // enter:
      Keyboard.write(KEY_RETURN);
      break;
      case WINDOWS:
	  
   // CTRL-ALT-DEL:
      Keyboard.press(KEY_LEFT_CTRL);
      Keyboard.press(KEY_LEFT_ALT);
      Keyboard.press(KEY_DELETE);
      delay(100);
      Keyboard.releaseAll();
	  
   //ALT-l:
      delay(2000);
      Keyboard.press(KEY_LEFT_ALT);
      Keyboard.press('l');
      Keyboard.releaseAll();
      break;
	  
   case UBUNTU:
      // CTRL-ALT-DEL:
      Keyboard.press(KEY_LEFT_CTRL);
      Keyboard.press(KEY_LEFT_ALT);
      Keyboard.press(KEY_DELETE);
      delay(1000);
      Keyboard.releaseAll();
	  
      // Enter to confirm logout:
      Keyboard.write(KEY_RETURN);
      break;
   }
   
// do nothing:
   while (true);
}

代码说明

在将程序上传到你的板之前,请确保将正在使用的正确操作系统分配给平台变量。

在草图运行时,按下按钮将引脚2接地,而板将发送注销序列发送到USB连接的PC。

结果

当将引脚2接地时,它将执行注销操作。

它使用以下键盘组合注销:

  •  Windows 上,按CTRL-ALT-DEL,然后按ALT-l        

  •  Ubuntu ,CTRL-ALT-DEL和ENTER        

  •  OSX 上,CMD-SHIFT-q        


Arduino 时间函数

Arduino提供四种不同的时间操作函数。它们是:


序号            函数和描述            
1

delay() 函数                

delay()函数的工作方式非常简单。它接受单个整数(或数字)参数。此数字表示时间(以毫秒为单位)。                

2

delayMicroseconds() 函数                

delayMicroseconds()函数接受单个整数(或数字)参数。一毫秒内有一千微秒,一秒内有一百万微秒。                

3

millis() 函数                

此函数用于返回Arduino板开始运行当前程序时的毫秒数。                

4

micros() 函数                

micros()函数返回Arduino板开始运行当前程序时的微秒数。该数字在大约70分钟后溢出,即回到零。                


Arduino 字符串对象

在Arduino编程中使用的第二种类型的字符串是字符串对象。

什么是对象?

对象是一个包含数据和函数的构造。字符串对象可以像变量一样被创建并分配一个值或字符串。字符串对象包含函数(在面向对象编程(OOP)中称为“方法”),它们对字符串对象中包含的字符串数据进行操作。

下面的草图和解释将清楚说明对象是什么,以及如何使用字符串对象。

例子

void setup() { 
   String my_str = "This is my string.";
   Serial.begin(9600);

   // (1) print the string
   Serial.println(my_str);

   // (2) change the string to upper-case
   my_str.toUpperCase();
   Serial.println(my_str);

   // (3) overwrite the string
   my_str = "My new string.";
   Serial.println(my_str);

   // (4) replace a word in the string
   my_str.replace("string", "Arduino sketch");
   Serial.println(my_str);

   // (5) get the length of the string
   Serial.print("String length is: ");
   Serial.println(my_str.length());
}

void loop() { 

}

结果

This is my string.
THIS IS MY STRING.
My new string.
My new Arduino sketch.
String length is: 22

创建字符串对象,并在草图顶部分配一个值(或字符串)。

String my_str = "This is my string." ;

这将创建一个名为 my_str 的String对象,并为其赋值“This is my string.”。

这可以与创建变量并为其分配一个值(如整数)相比较:

int my_var = 102;

以上草图以下列方式工作。

(1)打印字符串

字符串可以像字符数组字符串一样打印到串口监视器窗口。

(2)将字符串转换为大写

创建的字符串对象my_str,有多个可以在其上操作的函数或方法。这些方法通过使用对象名称后跟点运算符(.),然后使用函数的名称来调用的。

my_str.toUpperCase();

toUpperCase()函数对包含在类型为String的 my_str 对象中的字符串进行操作,并将对象包含的字符串数据(或文本)转换为大写字符。String类包含的函数列表可以在Arduino字符串参考中找到。从技术上讲,String被称为一个类,用于创建String对象。

(3)覆盖字符串

赋值运算符用于将新字符串分配给 my_str 对象以替换旧字符串。

my_str = "My new string." ;

赋值运算符不能用于字符数组字符串,仅适用于String对象。

(4)替换字符串中的单词

replace()函数用于将传递给它的第二个字符串替换传递给它的第一个字符串。replace()是构建在String类中的另一个函数,因此可以在String对象my_str上使用。

(5)获取字符串的长度

通过使用length()可以很容易地获取字符串的长度。在示例草图中,由length()返回的结果直接传递到Serial.println(),而不使用中间变量。

何时使用字符串对象

字符串对象比字符串字符数组更容易使用。该对象具有内置函数,可以对字符串执行多个操作。

使用String对象的主要缺点是,它使用了大量的内存,可能会很快耗尽Arduino的RAM内存,这可能会导致Arduino挂起,崩溃或行为意外。如果Arduino上的草图很小并限制了对象的使用,那么应该没有问题。

字符数组字符串更难使用,你可能需要编写自己的函数来操作这些类型的字符串。其优点是,你可以控制字符串数组的大小,因此你可以保持数组很小来节省内存。

你需要确保不要超出字符串数组边界的范围,而String对象没有这个问题,只要有足够的内存供它操作,就会照顾到你的字符串边界。在内存不足时,String对象可以尝试在不存在的内存中写入,但绝不会在超出其操作的字符串末尾的地方写入。

在哪里使用字符串

在本章中,我们学习了字符串,它们在内存中的行为及其操作。

字符串的用法将在课程的下一部分进行介绍,届时我们将学习如何从串口监视器窗口获取用户输入并将输入保存为字符串。



Arduino I/O函数

Arduino板上的引脚可以配置为输入或输出。我们将在这些模式下解释引脚的功能。重要的是要注意,大多数Arduino模拟引脚可以按照与数字引脚完全相同的方式进行配置和使用。

引脚配置为INPUT

Arduino引脚默认配置为输入,因此在使用它们作为输入时,不需要使用 pinMode()显式声明为输入。以这种方式配置的引脚被称为处于高阻抗状态。输入引脚对采样电路的要求非常小,相当于引脚前面的100兆欧的串联电阻。

这意味着将输入引脚从一个状态切换到另一个状态所需的电流非常小。这使得引脚可用于诸如实现电容式触摸传感器或读取LED作为光电二极管的任务。

被配置为pinMode(pin,INPUT)的引脚(没有任何东西连接到它们,或者有连接到它们而未连接到其他电路的导线),报告引脚状态看似随机的变化,从环境中拾取电子噪音或电容耦合附近引脚的状态。

上拉电阻

如果没有输入,上拉电阻通常用于将输入引脚引导到已知状态。这可以通过在输入端添加上拉电阻(到5V)或下拉电阻(接地电阻)来实现。10K电阻对于上拉或下拉电阻来说是一个很好的值。

使用内置上拉电阻,引脚配置为输入

Atmega芯片内置了2万个上拉电阻,可通过软件访问。通过将pinMode()设置为INPUT_PULLUP可访问这些内置上拉电阻。这有效地反转了INPUT模式的行为,其中HIGH表示传感器关闭,LOW表示传感器开启。此上拉的值取决于所使用的微控制器。在大多数基于AVR的板上,该值保证在20kΩ和50kΩ之间。在Arduino Due上,它介于50kΩ和150kΩ之间。有关确切的值,请参考板上微控制器的数据表。

当将传感器连接到配置为INPUT_PULLUP的引脚时,另一端应接地。在简单开关的情况下,这会导致当开关打开时引脚变为高电平,当按下开关时引脚为低电平。上拉电阻提供足够的电流来点亮连接到被配置为输入的引脚的LED。如果项目中的LED似乎在工作,但很昏暗,这可能是发生了什么。

控制引脚是高电平还是低电平的相同寄存器(内部芯片存储器单元)控制上拉电阻。因此,当引脚处于INPUT模式时,配置为有上拉电阻导通的引脚将被开启;如果引脚通过pinMode()切换到OUTPUT模式,引脚将配置为高电平。这也适用于另一个方向,如果通过pinMode()切换到输入,则处于高电平状态的输出引脚将设置上拉电阻。

示例

pinMode(3,INPUT) ; // set pin to input without using built in pull up resistor
pinMode(5,INPUT_PULLUP) ; // set pin to input using built in pull up resistor

引脚配置为OUTPUT

通过pinMode()配置为OUTPUT的引脚被认为处于低阻抗状态。这意味着它们可以向其他电路提供大量的电流。Atmega引脚可以向其他器件/电路提供(提供正电流)或吸收(提供负电流)高达40mA(毫安)的电流。这是足以点亮LED或者运行许多传感器的电流(不要忘记串联电阻),但不足以运行继电器,螺线管或电机。

试图从输出引脚运行高电流器件,可能损坏或破坏引脚中的输出晶体管,或损坏整个Atmega芯片。通常,这会导致微控制器中出现“死”引脚,但是剩余的芯片仍然可以正常工作。因此,最好通过470Ω或1k电阻将OUTPUT引脚连接到其他器件,除非特定应用需要从引脚吸取最大电流。

pinMode()函数

pinMode()函数用于将特定引脚配置为输入或输出。可以使用INPUT_PULLUP模式启用内部上拉电阻。此外,INPUT模式显式禁止内部上拉。

pinMode()函数语法

Void setup () {
   pinMode (pin , mode);
}
  • pin - 你希望设置模式的引脚的编号        

  • mode - INPUT,OUTPUT或INPUT_PULLUP。        

示例

int button = 5 ; // button connected to pin 5
int LED = 6; // LED connected to pin 6

void setup () {
   pinMode(button , INPUT_PULLUP); 
   // set the digital pin as input with pull-up resistor
   pinMode(button , OUTPUT); // set the digital pin as output
}

void setup () {
   If (digitalRead(button ) == LOW) // if button pressed {
      digitalWrite(LED,HIGH); // turn on led
      delay(500); // delay for 500 ms
      digitalWrite(LED,LOW); // turn off led
      delay(500); // delay for 500 ms
   }
}

digitalWrite()函数

digitalWrite()函数用于向数字引脚写入HIGH或LOW值。如果该引脚已通过pinMode()配置为OUTPUT,则其电压将被设置为相应的值:HIGH为5V(或3.3V在3.3V板上),LOW为0V(接地)。如果引脚配置为INPUT,则digitalWrite()将启用(HIGH)或禁止(LOW)输入引脚的内部上拉。建议将pinMode()设置为INPUT_PULLUP,以启用 内部上拉电阻。

如果不将pinMode()设置为OUTPUT,而将LED连接到引脚,则在调用digitalWrite(HIGH)时,LED可能会变暗。在没有明确设置pinMode()时,digitalWrite()将启用内部上拉电阻,这就像一个大的限流电阻。

digitalWrite()函数语法

Void loop() {
   digitalWrite (pin ,value);
}
  • pin - 你希望设置模式的引脚的编号        

  • value - HIGH或LOW。        

示例

int LED = 6; // LED connected to pin 6

void setup () {
   pinMode(LED, OUTPUT); // set the digital pin as output
}

void setup() { 
   digitalWrite(LED,HIGH); // turn on led
   delay(500); // delay for 500 ms
   digitalWrite(LED,LOW); // turn off led
   delay(500); // delay for 500 ms
}

analogRead()函数

Arduino能够检测是否有一个电压施加到其引脚,并通过digitalRead()函数报告。开/关传感器(检测物体的存在)和模拟传感器之间存在一个差异,模拟传感器的值连续变化。为了读取这种类型的传感器,我们需要一个不同类型的引脚。

在Arduino板的右下角,你会看到6个标记为“Analog In”的引脚。这些特殊引脚不仅可以告知是否有电压施加给它们,还可以告知它们的值。通过使用analogRead()函数,我们可以读取施加到其中一个引脚的电压。

此函数返回0到1023之间的数字,表示0到5伏特之间的电压。例如,如果施加到编号0的引脚的电压为2.5V,则analogRead(0)返回512。

analogRead()函数语法

analogRead(pin);
  • pin - 要读取的模拟输入引脚的编号(大多数电路板上为0至5,Mini和Nano上为0至7,Mega上为0至15)        

示例

int analogPin = 3;//potentiometer wiper (middle terminal) 
   // connected to analog pin 3 
int val = 0; // variable to store the value read

void setup() {
   Serial.begin(9600); // setup serial
} 

void loop() {
   val = analogRead(analogPin); // read the input pin
   Serial.println(val); // debug value
}


Arduino 高级I/O函数

在本章中,我们将学习一些高级的输入和输出函数。

analogReference()函数

配置用于模拟输入的参考电压(即用作输入范围顶部的值)。选项是:

  • DEFAULT - 5伏(5V Arduino板)或3.3伏(3.3V Arduino板)的默认模拟参考值        

  • INTERNAL - 内置参考,在ATmega168或ATmega328上等于1.1伏特,在ATmega8上等于2.56伏特(不适用于Arduino Mega)        

  • INTERNAL1V1 - 内置1.1V参考(仅限Arduino Mega)        

  • INTERNAL2V56 - 内置2.56V参考(仅限Arduino Mega)        

  • EXTERNAL - 施加到AREF引脚的电压(仅限0到5V)用作参考        

analogReference()函数语法

analogReference (type);

type - 可以使用以下任何类型(DEFAULT,INTERNAL,INTERNAL1V1,INTERNAL2V56,EXTERNAL)

对AREF引脚的外部参考电压,请勿使用小于0V或大于5V的任何值。如果在AREF引脚上使用外部参考,则必须在调用 analogRead()函数之前将模拟参考设置为EXTERNAL。否则,将短路有效参考电压(内部产生的)和AREF引脚,可能会损坏Arduino板上的微控制器。


1.jpg


或者,你可以通过5K电阻将外部参考电压连接到AREF引脚,从而允许在外部和内部参考电压之间切换。

注意,电阻将改变用作参考的电压,因为AREF引脚上有一个内部32K电阻。两者用作分压器。例如,通过电阻器施加的2.5V将在AREF引脚处产生2.5*32/(32+5)=?2.2V电压。

示例

int analogPin = 3;// potentiometer wiper (middle terminal) connected to analog pin 3 
int val = 0; // variable to store the read value

void setup() {
   Serial.begin(9600); // setup serial
   analogReference(EXTERNAL); // the voltage applied to the AREF pin (0 to 5V only) 
      // is used as the reference.
}

void loop() {
   val = analogRead(analogPin); // read the input pin
   Serial.println(val); // debug value
}


Arduino 字符函数

所有数据都以字符形式输入计算机,包括字母,数字和各种特殊符号。在本章节中,我们讨论C++检查和操作单个字符的功能。

字符处理库包括几个函数,执行有用的测试和字符数据的操作。每个函数接收一个字符,表示为int或EOF作为参数。字符通常作为整数操作。

记住,EOF通常具有值-1,而一些硬件架构不允许负值存储在char变量中。因此,字符处理函数将字符作为整数来操作。

下表总结了字符处理库的函数。使用字符处理库中的函数时,请包含<cctype>标题。

序号原型和描述            
1

int isdigit(int c)                

如果c是数字,则返回1,否则返回0。                

2

int isalpha(int c)                

如果c是字母,则返回1,否则返回0。                

3

int isalnum(int c)                

如果c是数字或字母,则返回1,否则返回0。                

4

int isxdigit(int c)                

如果c是十六进制数字字符,则返回1,否则返回0。                

5

int islower(int c)                

如果c是小写字母,则返回1,否则返回0。                

6

int isupper(int c)                

如果c是大写字母,则返回1;否则返回0。                

7

int isspace(int c)                

               

如果c是空白字符:换行符('\n')、空格符(' ')、换页符('\f')、回车符('\r')、水平制表符('\t')或垂直制表符('\v'),则返回1,否则返回0。

               

8

int iscntrl(int c)                

如果c是控制字符,如换行符('\n')、换页符('\f')、回车符('\r')、水平制表符 (\v')、垂直制表符('\v')、alert('\a')或退格('\b'),则返回1,否则返回0。                

9

int ispunct(int c)                

如果c是除空格,数字或字母以外的打印字符,则返回1,否则返回0。                

10

int isprint(int c)                

如果c是包含空格(' ')的打印字符,则返回1,否则返回0。                

11

int isgraph(int c)                

如果c是除空格(' ')之外的打印字符,则返回1,否则返回0。                

例子

以下示例演示如何使用函数 isdigit,isalpha,isalnum  isxdigit 函数 isdigit 确定其参数是否为数字(0-9)。函数 isalpha 确定其参数是大写字母(A-Z)还是小写字母(a-z)。函数 isalnum 确定其参数是大写,小写字母还是数字。函数 isxdigit 确定其参数是否为十六进制数字(A-F,a-f,0-9)。

例1

void setup () {
   Serial.begin (9600);
   Serial.print ("According to isdigit:\r");
   Serial.print (isdigit( '8' ) ? "8 is a": "8 is not a");
   Serial.print (" digit\r" );
   Serial.print (isdigit( '8' ) ?"# is a": "# is not a") ;
   Serial.print (" digit\r");
   Serial.print ("\rAccording to isalpha:\r" );
   Serial.print (isalpha('A' ) ?"A is a": "A is not a");
   Serial.print (" letter\r");
   Serial.print (isalpha('A' ) ?"b is a": "b is not a");
   Serial.print (" letter\r");
   Serial.print (isalpha('A') ?"& is a": "& is not a");
   Serial.print (" letter\r");
   Serial.print (isalpha( 'A' ) ?"4 is a":"4 is not a");
   Serial.print (" letter\r");
   Serial.print ("\rAccording to isalnum:\r");
   Serial.print (isalnum( 'A' ) ?"A is a" : "A is not a" );

   Serial.print (" digit or a letter\r" );
   Serial.print (isalnum( '8' ) ?"8 is a" : "8 is not a" ) ;
   Serial.print (" digit or a letter\r");
   Serial.print (isalnum( '#' ) ?"# is a" : "# is not a" );
   Serial.print (" digit or a letter\r");
   Serial.print ("\rAccording to isxdigit:\r");
   Serial.print (isxdigit( 'F' ) ?"F is a" : "F is not a" );
   Serial.print (" hexadecimal digit\r" );
   Serial.print (isxdigit( 'J' ) ?"J is a" : "J is not a" ) ;
   Serial.print (" hexadecimal digit\r" );
   Serial.print (isxdigit( '7' ) ?"7 is a" : "7 is not a" ) ;

   Serial.print (" hexadecimal digit\r" );
   Serial.print (isxdigit( '$' ) ? "$ is a" : "$ is not a" );
   Serial.print (" hexadecimal digit\r" );
   Serial.print (isxdigit( 'f' ) ? “f is a" : "f is not a");
   
}

void loop () {

}

结果

According to isdigit:
8 is a digit
# is not a digit
According to isalpha:
A is a letter
b is a letter
& is not a letter
4 is not a letter
According to isalnum:
A is a digit or a letter

8 is a digit or a letter
# is not a digit or a letter
According to isxdigit:
F is a hexadecimal digit
J is not a hexadecimal digit
7 is a hexadecimal digit

$ is not a hexadecimal digit
f is a hexadecimal digit

我们对每个函数使用条件运算符(?:)来确定字符串“is a”或字符串“is not a”是否应该打印在每个测试字符的输出中。例如,行a表示如果“8”是数字,即如果isdigit返回真(非零)值,则打印字符串“8 is a”。如果“8”不是数字(即,如果isdigit返回0),则打印字符串“8 is not a”。

例2

以下示例演示了函数 islower  isupper 的使用。函数 islower 确定其参数是否为小写字母(a-z)。函数 isupper 确定其参数是否为大写字母(A-Z)。

int thisChar = 0xA0;

void setup () {
   Serial.begin (9600);
   Serial.print ("According to islower:\r") ;
   Serial.print (islower( 'p' ) ? "p is a" : "p is not a" );
   Serial.print ( " lowercase letter\r" );
   Serial.print ( islower( 'P') ? "P is a" : "P is not a") ;
   Serial.print ("lowercase letter\r");
   Serial.print (islower( '5' ) ? "5 is a" : "5 is not a" );
   Serial.print ( " lowercase letter\r" );
   Serial.print ( islower( '!' )? "! is a" : "! is not a") ;
   Serial.print ("lowercase letter\r");

   Serial.print ("\rAccording to isupper:\r") ;
   Serial.print (isupper ( 'D' ) ? "D is a" : "D is not an" );
   Serial.print ( " uppercase letter\r" );
   Serial.print ( isupper ( 'd' )? "d is a" : "d is not an") ;
   Serial.print ( " uppercase letter\r" );
   Serial.print (isupper ( '8' ) ? "8 is a" : "8 is not an" );
   Serial.print ( " uppercase letter\r" );
   Serial.print ( islower( '$' )? "$ is a" : "$ is not an") ;
   Serial.print ("uppercase letter\r ");
}

void setup () {

}

结果

According to islower:
p is a lowercase letter
P is not a lowercase letter
5 is not a lowercase letter
! is not a lowercase letter

According to isupper:
D is an uppercase letter
d is not an uppercase letter
8 is not an uppercase letter
$ is not an uppercase letter

例3

以下示例演示如何使用函数 isspace,iscntrl,ispunct,isprint  isgraph 

  • 函数 isspace 确定其参数是否为空白字符,例如空格(' '),换页符('\f'),换行符('\n'),回车符('\r'),水平制表符('\t')或垂直制表符('\v')。        

  • 函数 iscntrl 确定其参数是否为控制字符,如水平制表符('\t'),垂直制表符('\v'),换页符('\f'),alert('\a'),退格符('\b'),回车符('\r')或换行符('\n')。        

  • 函数 ispunct 确定其参数是否是除空格,数字或字母以外的打印字符(例如$,#,(,),[,],{,},;,:或%)。        

  • 函数 isprint 确定其参数是否为可以在屏幕上显示的字符(包括空格字符)。        

  • 函数 isgraph 测试与isprint相同的字符,但不包括空格字符。        

void setup () {
   Serial.begin (9600);
   Serial.print ( " According to isspace:\rNewline ") ;
   Serial.print (isspace( '\n' )? " is a" : " is not a" );
   Serial.print ( " whitespace character\rHorizontal tab") ;
   Serial.print (isspace( '\t' )? " is a" : " is not a" );
   Serial.print ( " whitespace character\n") ;
   Serial.print (isspace('%')? " % is a" : " % is not a" );
   
   Serial.print ( " \rAccording to iscntrl:\rNewline") ;
   Serial.print ( iscntrl( '\n' )?"is a" : " is not a" ) ;
   Serial.print (" control character\r");
   Serial.print (iscntrl( '$' ) ? " $ is a" : " $ is not a" );
   Serial.print (" control character\r");
   Serial.print ("\rAccording to ispunct:\r");
   Serial.print (ispunct(';' ) ?"; is a" : "; is not a" ) ;
   Serial.print (" punctuation character\r");
   Serial.print (ispunct('Y' ) ?"Y is a" : "Y is not a" ) ;
   Serial.print ("punctuation character\r");
   Serial.print (ispunct('#' ) ?"# is a" : "# is not a" ) ;
   Serial.print ("punctuation character\r");

   Serial.print ( "\r According to isprint:\r");
   Serial.print (isprint('$' ) ?"$ is a" : "$ is not a" );
   Serial.print (" printing character\rAlert ");
   Serial.print (isprint('\a' ) ?" is a" : " is not a" );
   Serial.print (" printing character\rSpace ");
   Serial.print (isprint(' ' ) ?" is a" : " is not a" );
   Serial.print (" printing character\r");
   
   Serial.print ("\r According to isgraph:\r");
   Serial.print (isgraph ('Q' ) ?"Q is a" : "Q is not a" );
   Serial.print ("printing character other than a space\rSpace ");
   Serial.print (isgraph (' ') ?" is a" : " is not a" );
   Serial.print ("printing character other than a space ");
}

void loop () {

}

结果

According to isspace:
Newline is a whitespace character
Horizontal tab is a whitespace character
% is not a whitespace character
According to iscntrl:
Newline is a control character
$ is not a control character
According to ispunct:
; is a punctuation character
Y is not a punctuation character
# is a punctuation character
According to isprint:
$ is a printing character
Alert is not a printing character
Space is a printing character
According to isgraph:
Q is a printing character other than a space
Space is not a printing character other than a space


Arduino数学库

Arduino 数学库

2018-11-15 17:56 更新

Arduino数学库(math.h)包含了许多用于操作浮点数的有用的数学函数。        

库中的宏        

以下是在标题math.h中定义的宏:        


       

描述
M_E2.7182818284590452354常数e。
M_LOG2E

1.4426950408889634074

/* log_2 e */

e以2为底的对数。
M_1_PI

0.31830988618379067154

/* 1/pi */

常数1/pi。
M_2_PI

0.63661977236758134308

/* 2/pi */

常数2/pi。
M_2_SQRTPI

1.12837916709551257390

/* 2/sqrt(pi) */

常数2/sqrt(pi)。
M_LN10

2.30258509299404568402

/* log_e 10 */

10的自然对数。
M_LN2

0.69314718055994530942

/* log_e 2 */

2的自然对数。
M_LOG10E

0.43429448190325182765

/* log_10 e */

e以10为底的对数。
M_PI

3.14159265358979323846

/* pi */

常数pi。
M_PI_2

3.3V1.57079632679489661923

/* pi/2 */

常数pi/2。
M_PI_4

0.78539816339744830962

/* pi/4 */

常数pi/4。
M_SQRT1_2

0.70710678118654752440

/* 1/sqrt(2) */

常数1/sqrt(2)。
M_SQRT2

1.41421356237309504880

/* sqrt(2) */

2的平方根。
acosf-acos()函数的别名。
asinf-asin()函数的别名。                    
atan2f-atan2()函数的别名。                    
cbrtf-cbrt()函数的别名。                    
ceilf-ceil()函数的别名。                    
copysignf-copysign()函数的别名。                    
coshf-cosh()函数的别名。                    
expf-exp()函数的别名。                    
fabsf-fabs()函数的别名。                    
fdimf-fdim()函数的别名。                    
floorf-floor()函数的别名。                    
fmaxf-fmax()函数的别名。                    
fminf-fmin()函数的别名。                    
fmodf-fmod()函数的别名。                    
frexpf-frexp()函数的别名。                    
hypotf-hypot()函数的别名。                    
INFINITY-无穷大常量。
isfinitef-isfinite()函数的别名。                    
isinff-isinf()函数的别名。                    
isnanf-isnan()函数的别名。                    
ldexpf-ldexp()函数的别名。                    
log10f-log10()函数的别名。                    
logf-log()函数的别名。                    
lrintf-lrint()函数的别名。                    
lroundf-lround()函数的别名。                    


       

库函数        

以下函数在标题 math.h 中定义:        


       

序号库函数和描述
1

double acos (double __x)                        

acos()函数计算x的反余弦的主值。返回值在[0, pi]弧度的范围内。不在[-1, +1]范围内的参数会发生域错误。

2

double asin (double __x)                        

asin()函数计算x的反正弦的主值。返回值在[-pi/2, pi/2]弧度的范围内。不在[-1, +1]范围内的参数会发生域错误。

3

double atan (double __x)                        

atan()函数计算x的反正切的主值。返回值在[-pi/2, pi/2]弧度的范围内。

4

double atan2 (double __y, double __x)                        

atan2()函数计算y/x的反正切的主值,使用两个参数的符号来确定返回值的象限。返回值在[-pi, +pi]弧度的范围内。

5

double cbrt (double __x)                        

cbrt()函数返回x的立方根值。

6

double ceil (double __x)                        

ceil()函数返回大于或等于x的最小整数值,以浮点数表示。

7

static double copysign (double __x, double __y)                        

copysign()函数返回x,但带有y的符号。即使x或y是NaN或零,他们也可以工作。

8

double cos(double __x)                        

cos()函数返回x的余弦,以弧度为单位。

9

double cosh (double __x)                        

cosh()函数返回x的双曲余弦。

10

double exp (double __x)                        

exp()返回e的x次幂的值。

11

double fabs (double __x)                        

fabs()函数计算浮点数x的绝对值。

12

double fdim (double __x, double __y)                        

fdim()函数返回max(x - y, 0)。如果x或y或者两者都是NaN,则返回NaN。

13

double floor (double __x)                        

floor()函数返回小于或等于x的最大整数值,以浮点数表示。

14

double fma (double __x, double __y, double __z)                        

fma()函数执行浮点乘加,即运算(x * y) + z,但是中间结果不会四舍五入到目标类型。这有时可以提高计算的精度。

15

double fmax (double __x, double __y)                        

fmax()函数返回两个值x和y中较大的一个。如果一个参数是NaN,则返回另一个参数。如果两个参数都是NaN,则返回NaN。

16

double fmin (double __x, double __y)                        

fmin()函数返回两个值x和y中较小的一个。如果一个参数是NaN,则返回另一个参数。如果两个参数都是NaN,则返回NaN。

17

double fmod (double __x, double__y)                        

fmod()函数返回x / y的余数。

18

double frexp (double __x, int * __pexp)                        

frexp()函数将浮点数分解为规格化分数和2的整次幂。它将整数存储在pexp指向的int对象中。如果x是一个正常的浮点数,则frexp()函数返回值v,使得v具有区间[1/2, 1)或零的量值,而x等于v乘以2的pexp次幂。如果x是零,那么结果的两个部分都是零。如果x不是有限数字,frexp()将按原样返回x,并通过pexp存储0。

注意 ? 这个实现允许一个零指针作为指令来跳过存储指数。

19

double hypot (double __x, double__y)                        

hypot()函数返回sqrt(x*x + y*y)。这是一个边长为x和y的直角三角形的斜边的长度,或点(x, y)距离原点的距离。使用这个函数而不是直接使用公式是比较明智的,因为误差要小得多。x和y没有下溢。如果结果在范围内,则不会溢出。

20

static int isfinite (double __x)                        

如果x是有限的,isfinite()函数返回一个非零值:不是正或负无穷,也不是NaN

21

int isinf (double __x)                        

如果参数x是正无穷大,则函数isinf()返回1;如果x是负无穷大,则返回-1,否则返回0。

注意 ? GCC 4.3可以用内联代码替换这个函数,这个代码对两个无穷大返回1值(gcc bug #35509)。

22

int isnan (double __x)                        

如果参数x表示“非数字”(NaN)对象,则函数isnan()返回1,否则返回0。

23

double ldexp (double __x, int __exp )                        

ldexp()函数将浮点数乘以2的整数次幂。它返回x乘以2的exp次幂的值。

24

double log (double __x)                        

log()函数返回参数x的自然对数。

25

double log10(double __x)                        

log10()函数返回参数x的对数,以10为基数。

26

long lrint (double __x)                        

lrint()函数将x四舍五入到最近的整数,将中间情况舍入到偶数整数方向(例如,1.5和2.5的值都舍入到2)。这个函数类似于rint()函数,但是它的返回值类型不同,并且有可能溢出。

返回                        

四舍五入的长整数值。如果x不是有限数字或者溢出,则此实现返回LONG_MIN值(0x80000000)。

27

long lround (double __x)                        

lround()将函数将x四舍五入到最近的整数,但中间情况不舍入到0(不是到最近的偶数整数)。这个函数类似于round()函数,但是它的返回值的类型是不同的,并且有可能溢出。

返回                        

四舍五入的长整数值。如果x不是有限数字或者溢出,则此实现返回LONG_MIN值(0x80000000)。                        

28

double modf (double __x, double * __iptr )                        

modf()函数将参数x分解为整数部分和小数部分,每个部分都与参数具有相同的符号。它在iptr指向的对象中将整数部分存储为double。

modf()函数返回x的有符号小数部分。

注意 ? 这个实现跳过零指针的写入。但是,GCC 4.3可以用内联代码替换这个函数,不允许使用NULL地址来避免存储。

29

float modff (float __x, float * __iptr)                        

modf()函数的别名。

30

double pow (double __x, double __y)                        

pow()函数返回x的y次幂。

31

double round (double __x)                        

round()函数将x四舍五入到最近的整数,但中间情况不舍入到0(不是到最近的偶数整数)。不可能会溢出。                        

返回                        

四舍五入的值。如果x是整数或无穷大,则返回x本身。如果x是NaN,则返回NaN

32

int signbit (double __x)                        

如果x的值设置了符号位,signbit()函数将返回一个非零值。这与“x < 0.0”不同,因为IEEE 754浮点允许零署名。比较“-0.0 < 0.0”是错的,但“signbit (-0.0)”会返回一个非零值。

33

double sin (double __x)                        

sin()函数返回x的正弦值,以弧度为单位。

34

double sinh (double __x)                        

sinh()函数返回x的双曲正弦。

35

double sqrt (double __x)                        

sqrt()函数返回x的非负平方根。

36

double square (double __x)                        

square()函数返回x * x。

注意 ? 此函数不属于C标准定义。

37

double tan (double __x)                        

tan()函数返回x的正切值,以弧度为单位。

38

double tanh ( double __x)                        

tanh()函数返回x的双曲正切。

39

double trunc (double __x)                        

trunc()函数将x四舍五入为最近的整数,不大于绝对值。


例子

以下示例显示如何使用最常用的math.h库函数:        

double double__x = 45.45 ;
double double__y = 30.20 ;

void setup() {
   Serial.begin(9600);
   Serial.print("cos num = ");
   Serial.println (cos (double__x) ); // returns cosine of x
   Serial.print("absolute value of num = ");
   Serial.println (fabs (double__x) ); // absolute value of a float
   Serial.print("floating point modulo = ");
   Serial.println (fmod (double__x, double__y)); // floating point modulo
   Serial.print("sine of num = ");
   Serial.println (sin (double__x) ) ;// returns sine of x
   Serial.print("square root of num : ");
   Serial.println ( sqrt (double__x) );// returns square root of x
   Serial.print("tangent of num : ");
   Serial.println ( tan (double__x) ); // returns tangent of x
   Serial.print("exponential value of num : ");
   Serial.println ( exp (double__x) ); // function returns the exponential value of x.
   Serial.print("cos num : ");

   Serial.println (atan (double__x) ); // arc tangent of x
   Serial.print("tangent of num : ");
   Serial.println (atan2 (double__y, double__x) );// arc tangent of y/x
   Serial.print("arc tangent of num : ");
   Serial.println (log (double__x) ) ; // natural logarithm of x
   Serial.print("cos num : ");
   Serial.println ( log10 (double__x)); // logarithm of x to base 10.
   Serial.print("logarithm of num to base 10 : ");
   Serial.println (pow (double__x, double__y) );// x to power of y
   Serial.print("power of num : ");
   Serial.println (square (double__x)); // square of x
}

void loop() {

}
       

结果        

cos num = 0.10
absolute value of num = 45.45
floating point modulo =15.25
sine of num = 0.99
square root of num : 6.74
tangent of num : 9.67
exponential value of num : ovf
cos num : 1.55
tangent of num : 0.59
arc tangent of num : 3.82
cos num : 1.66
logarithm of num to base 10 : inf
power of num : 2065.70
   


Arduino 三角函数

你需要使用三角几何来计算移动物体的距离或角速度。Arduino提供了传统的三角函数(sin,cos,tan,asin,acos,atan),可以通过编写它们的原型来概括。Math.h包含三角函数的原型。

三角函数的精确语法

double sin(double x); //返回x的正弦值
double cos(double y); //返回y的余弦值
double tan(double x); //返回x的正切值
double acos(double x); //返回x的反余弦数
double asin(double x); //返回x的反正弦数
double atan(double x); //返回x的反正切数

例子

double sine = sin(2); // 大约 0.90929737091
double cosine = cos(2); // 大约 -0.41614685058
double tangent = tan(2); // 大约 -2.18503975868


Arduino 随机数

要生成随机数,可以使用Arduino随机数函数。

可能是由于内容开始是网络地址(http://...)完整的英文词截取后不能换行造成 为避免代码造成手机端排版的混乱,可适当增加文字描述,将代码往后推移
我们有两个函数:

  • randomSeed(seed)
  • random()

randomSeed(seed)

randomSeed(seed)函数重置Arduino的伪随机数生成器。虽然random()返回的数字的分布本质上是随机的,但是顺序是可预测的。你应该将发生器重置为某个随机值。如果你有一个未连接的模拟引脚,它可能会从周围环境中拾取随机噪音。这些可能是无线电波,宇宙射线,手机的电磁干扰,荧光灯等。

例子

randomSeed(analogRead(5)); // randomize using noise from analog pin 5

random()

random函数生成伪随机数。以下是语法。

random()语法

long random(max) // it generate random numbers from 0 to max
long random(min, max) // it generate random numbers from min to max

例子

long randNumber;

void setup() {
   Serial.begin(9600);
   // if analog input pin 0 is unconnected, random analog
   // noise will cause the call to randomSeed() to generate
   // different seed numbers each time the sketch runs.
   // randomSeed() will then shuffle the random function.
   randomSeed(analogRead(0));
}

void loop() {
   // print a random number from 0 to 299
   Serial.print("random1=");
   randNumber = random(300);
   Serial.println(randNumber); // print a random number from 0to 299
   Serial.print("random2=");
   randNumber = random(10, 20);// print a random number from 10 to 19
   Serial.println (randNumber);
   delay(50);
}

让我们现在重温我们对一些基本概念的知识,例如位和字节。

Bit(位)

位只是一个二进制数字。

  • 二进制系统使用两个数字,0和1。        

  • 与十进制数字系统类似,数字的位数不具有相同的值,位的“意义"取决于其在二进制数中的位置。例如,十进制数666中的数字相同,但具有不同的值。        

1.jpg

字节

一个字节由8位组成。

  • 如果一个位是一个数字,逻辑上字节表示数字。        

  • 可以对它们执行所有数学运算。        

  • 一个字节中的数字也不具有相同的意义。        

  • 最左边的位具有被称为最高有效位(MSB)的最大值。        

  • 最右边的位具有最小值,因此称为最低有效位(LSB)。        

  • 由于可以以256种不同的方式组合一个字节的八个0和1,所以可以由一个字节表示的最大十进制数是255(一个组合表示零)。        


Arduino 中断

中断(interrupt)停止Arduino的当前工作,以便可以完成一些其他工作。

假设你坐在家里和别人聊天。突然电话响了。停止聊天,拿起电话与来电者通话。当你完成电话交谈后,你回去和电话响之前的那个人聊天。

同样,你可以把主程序想象成是与某人聊天,电话铃声使你停止聊天。中断服务程序是在电话上通话的过程。当通话结束后,你回到你聊天的主程序。这个例子准确地解释了中断如何使处理器执行操作。

主程序在电路中运行并执行一些功能。但是,当发生中断时,主程序在另一个程序执行时停止。当这个程序结束时,处理器再次返回主程序。

1.jpg

重要特征

这里有一些关于中断的重要特征:

  • 中断可以来自各种来源。在这种情况下,我们使用的是由数字引脚上的状态改变触发的硬件中断。        

  • 大多数Arduino设计有两个硬件中断(称为“interrupt0”和“interrupt1”)分别硬连接到数字I/O引脚2和3。        

  • Arduino Mega有六个硬件中断,包括引脚21,20,19和18上的附加中断(“interrupt2”到“interrupt5”)。        

  • 你可以使用称为“中断服务程序”(Interrupt Service Routine,通常称为ISR)的特殊函数来定义程序。        

  • 你可以定义该程序并指定上升沿,下降沿或两者的条件。在这些特定条件下,将处理中断。        

  • 每次在输入引脚上发生事件时,都可以自动执行该函数。        

中断类型

有两种类型的中断:

  • 硬件中断 - 它们响应外部事件而发生,例如外部中断引脚变为高电平或低电平。        

  • 软件中断 - 它们响应于在软件中发送的指令而发生。“Arduino语言”支持的唯一类型的中断是attachInterrupt()函数。        

在Arduino中使用中断

中断在 Arduino 程序中非常有用,因为它有助于解决时序问题。中断的良好应用是读取旋转编码器或观察用户输入。一般情况下,ISR 应尽可能短且快。如果你的草图使用多个 ISR,则一次只能运行一个。其他中断将在当前完成之后执行,其顺序取决于它们的优先级。

通常,全局变量用于在 ISR 和主程序之间传递数据。为了确保在 ISR 和主程序之间共享的变量正确更新,请将它们声明为 volatile。

Arduino 中主要有时钟中断和外部中断,本文所说的中断指的是外部中断。Arduino 中的外部中断通常是由Pin 口(数字 Pin 口,不是模拟口)电平改变触发的。每种型号的 Arduino 版都有数个 Pin 口可以用来注册中断,具体如下:

开发板可以用来注册中断的Pin口
Uno, Nano, Mini, other 328-based2, 3
Uno WiFi Rev.2所有数字口
Mega, Mega2560, MegaADK2, 3, 18, 19, 20, 21
Micro, Leonardo, other 32u4-based0, 1, 2, 3, 7
Zero除了4号口外的所有数字口
MKR Family boards0, 1, 4, 5, 6, 7, 8, 9, A1, A2
Due所有数字口
101所有数字口 (只有 2, 5, 7, 8, 10, 11, 12, 13数字口可以使用 CHANGE 类型中断,中断类型在下文有介绍)

注册中断主要是通过 attachInterrupt() 函数实现的,其原型为:

void attachInterrupt(uint8_t interruptNum, void (*userFunc)(void), int mode);
  1. 第一个参数为中断号,Arduino上每个可以注册中断的Pin口都会被分配一个中断号,这里需要传入的是中断号而不是Pin口号。但是不同的Arduino开发板上面的中断号分配并不完全一样。各个开发板的Pin口号和中断号对应关系如下:
           
     开发板 中断号0 中断号1 中断号2 中断号3 中断号4 中断号5
     Uno, Ethernet PIN 2 PIN 3



     Mega2560 PIN 2 PIN 3 PIN 21 PIN 20 PIN 19 PIN 18
     基于32u4的开发板 如 Leonardo, Micro PIN 3 PIN 2 PIN 0 PIN 1 PIN 7
    从上表中可以看出同一个 Pin 口在不同的开发板上可能会有不同的中断号,这势必会影响程序的可移植性。幸运的是,Arduino 还提供了另一个函数 digitalPinToInterrupt(int)。从名字就能看出,这个函数能输入 Pin 口号并输出对应的中断号。需要注意的是,输入的 Pin 口号需要在上述的支持列表当中。所以,Arduino 官方推荐我们使用         
    attachInterrupt(digitalPinToInterrupt(pin), ISR, mode);
    这种方式来注册中断号。
       
  2. 第二个参数是中断服务例程(ISR)的函数指针,在 C/C++ 中直接写该函数的函数名即可。在触发时,该函数将会被调用。该函数必须没有任何的参数也没有任何的返回值。
  3. 第三个参数是中断触发条件,由几个可选的值:
    LOW 当中断所在 Pin 口处于低电平时触发
    CHANGE 当中断所在 Pin口电平改变时触发
    RISING 当中断所在Pin口从低电平变为高电平(上升沿)时触发
    FALLING 当中断所在Pin口从高电平变为低电平(下降沿)时触发
    对于 Due,Zero 和 MKR1000开发板,还有一个 HIGH 表示当中断所在 Pin 口处于高电平时触发

示例

int pin = 2; //define interrupt pin to 2 volatile int state = LOW; // To make sure variables shared between an ISR //the main program are updated correctly,declare them as volatile. void setup() {    pinMode(13, OUTPUT); //set pin 13 as output    attachInterrupt(digitalPinToInterrupt(pin), blink, CHANGE);    //interrupt at pin 2 blink ISR when pin to change the value }  void loop() {     digitalWrite(13, state); //pin 13 equal the state value }  void blink() {     //ISR function    state = !state; //toggle the state when the interrupt occurs }

attachInterrupt语句语法

attachInterrupt(digitalPinToInterrupt(pin),ISR,mode);//recommended for arduino board attachInterrupt(pin, ISR, mode) ; //recommended Arduino Due, Zero only //argument pin: the pin number //argument ISR: the ISR to call when the interrupt occurs;     //this function must take no parameters and return nothing.     //This function is sometimes referred to as an interrupt service routine. //argument mode: defines when the interrupt should be triggered.

在 Arduino 中使用中断需要注意的问题

  1. 由于中断会打断正常代码的运行,因此 ISR 的应该尽可能快地执行完毕。
  2. 在 ISR 中修改的全局变量要用 volatile 修饰符修饰以防止编译器优化
  3. 在 ISR 中不能使用其他用中断实现的函数,如 millis() delay() 等。延时可以使用  delayMicroseconds(),它不是用中断实现的。


Arduino 超声波传感器

HC-SR04超声波传感器使用声纳来确定物体的距离,就像蝙蝠一样。它提供了非常好的非接触范围检测,准确度高,读数稳定,易于使用,尺寸从2厘米到400厘米或1英寸到13英尺不等。

其操作不受阳光或黑色材料的影响,尽管在声学上,柔软的材料(如布料等)可能难以检测到。它配有超声波发射器和接收器模块。


1.jpg


2.jpg

技术规格

电源 - + 5V DC
静态电流 - <2mA
工作电流 - 15mA
有效角度 - <15°
测距距离 - 2厘米-400厘米/1英寸-13英尺
分辨率 - 0.3厘米
测量角度 - 30度

必需的组件

你将需要以下组件:

  • 1 × Breadboard 面包板
  • 1 × Arduino Uno R3
  • 1 × 超声波传感器(HC-SR04)

程序

按照电路图进行连接,如下图所示。

3.jpg

草图

在计算机上打开Arduino IDE软件。使用Arduino语言进行编码控制你的电路。通过单击“New”打开一个新的草图文件。

4.jpg

Arduino代码

const int pingPin = 7; // Trigger Pin of Ultrasonic Sensor
const int echoPin = 6; // Echo Pin of Ultrasonic Sensor

void setup() {
   Serial.begin(9600); // Starting Serial Terminal
}

void loop() {
   long duration, inches, cm;
   pinMode(pingPin, OUTPUT);
   digitalWrite(pingPin, LOW);
   delayMicroseconds(2);
   digitalWrite(pingPin, HIGH);
   delayMicroseconds(10);
   digitalWrite(pingPin, LOW);
   pinMode(echoPin, INPUT);
   duration = pulseIn(echoPin, HIGH);
   inches = microsecondsToInches(duration);
   cm = microsecondsToCentimeters(duration);
   Serial.print(inches);
   Serial.print("in, ");
   Serial.print(cm);
   Serial.print("cm");
   Serial.println();
   delay(100);
}

long microsecondsToInches(long microseconds) {
   return microseconds / 74 / 2;
}

long microsecondsToCentimeters(long microseconds) {
   return microseconds / 29 / 2;
}

代码说明

超声波传感器有四个端子:+5V,Trigger,Echo和GND,连接如下:

  • 将+5V引脚连接到Arduino板上的+5v。
  • 将Trigger连接到Arduino板上的数字引脚7。
  • 将Echo连接到Arduino板上的数字引脚6。
  • 将GND连接到Arduino上的GND。

在我们的程序中,我们通过串口显示了传感器测量的距离,单位为英寸和厘米。

结果

你将在Arduino串口监视器上看到传感器测量的距离,单位为英寸和厘米。



Arduino PIR传感器

PIR传感器可以让你感知运动。它们用于检测人是否进入或离开传感器的范围。通常出现在家庭或企业使用的电器和小工具中。它们通常被称为PIR,“被动红外”,“热电”或“IR运动”传感器。

以下是PIR传感器的优点:

  • 体积小
  • 镜头范围广
  • 易于界面
  • 廉价
  • 低电量
  • 使用方便
  • 不会磨损

1.jpg


PIR由热电传感器制成,圆形金属罐中央有一个矩形晶体,可以检测红外辐射的水平。所有东西都发射出低水平的辐射,而东西越热发射的辐射就越多。运动检测器中的传感器分成两半。这是为了检测运动(变化)而不是平均IR水平。两个半部分相互连接,以便彼此抵消。如果一半看到比另一半更多或更少的红外辐射,则输出将摆动高或低。

2.jpg


PIR具有可调设置,并在3引脚ground/out/power板上安装了一个接头。

3.jpg


对于许多需要在人们离开或进入该区域进行检测的基础项目或产品,PIR传感器是非常有用的。请注意,PIR不会告诉你周围的人数或与传感器的距离。镜头通常固定在有一定距离的扫描范围内,有时候它们会被房子里的宠物带走

必需的组件

你将需要以下组件:

  • 1 × Breadboard 面包板
  • 1 × Arduino Uno R3
  • 1 × PIR传感器(MQ3)

程序

按照电路图进行连接,如下图所示。


4.jpg


草图

在计算机上打开Arduino IDE软件。使用Arduino语言进行编码控制你的电路。通过单击“New”打开一个新的草图文件。

5.jpg

Arduino代码

#define pirPin 2
int calibrationTime = 30;
long unsigned int lowIn;
long unsigned int pause = 5000;
boolean lockLow = true;
boolean takeLowTime;
int PIRValue = 0;

void setup() {
   Serial.begin(9600);
   pinMode(pirPin, INPUT);
}

void loop() {
   PIRSensor();
}

void PIRSensor() {
   if(digitalRead(pirPin) == HIGH) {
      if(lockLow) {
         PIRValue = 1;
         lockLow = false;
         Serial.println("Motion detected.");
         delay(50);
      }
      takeLowTime = true;
   }
   if(digitalRead(pirPin) == LOW) {
      if(takeLowTime){
         lowIn = millis();takeLowTime = false;
      }
      if(!lockLow && millis() - lowIn > pause) {
         PIRValue = 0;
         lockLow = true;
         Serial.println("Motion ended.");
         delay(50);
      }
   }
}

代码说明

PIR传感器有三个端子:Vcc,OUT和GND。按如下所示连接传感器: 

  • 将+Vcc连接到Arduino板上的+5v。
  • 将OUT连接到Arduino板上的数字引脚2。
  • 将GND连接到Arduino上的GND。

您可以通过位于传感器板底部的两个可变电阻器来调节传感器灵敏度和延迟时间。

6.jpg

一旦传感器检测到任何运动,Arduino将通过串口发送一条消息,表示检测到运动。PIR感测运动将延迟一定时间以检查是否存有新的运动。如果没有检测到运动,Arduino将发送一条新消息,说明运动已经结束。

结果

如果检测到运动,你将在串口上看到一条消息,并在运动停止时看到另一条消息。


Arduino 水位检测器/传感器

水位传感器砖设计用于水位检测,可广泛应用于检测降雨,水位,甚至液体泄漏。

1.jpg

将水位传感器连接到Arduino是检测泄漏,溢出,洪水,雨水等的好方法。它可用于检测水的存在,水位,体积以及是否缺水。当这个用来提醒你给植物浇水时,有一个比较好的Grove传感器。传感器具有一系列暴露的迹线,当检测到水时读取LOW。

在本章中,我们将把水位传感器连接到Arduino上的数字引脚8,并将使用非常方便的LED来帮助识别水位传感器何时与水源接触。

必需的组件

你将需要以下组件:

  • 1 × Breadboard 面包板
  • 1 × Arduino Uno R3
  • 1 × 水位传感器
  • 1 × led
  • 1 × 330欧姆电阻    

程序

按照电路图连接面包板上的组件,如下图所示。

2.jpg

草图

在计算机上打开Arduino IDE软件。使用Arduino语言进行编码控制你的电路。通过单击“New”打开一个新的草图文件。

3.jpg

Arduino代码

#define Grove_Water_Sensor 8 // Attach Water sensor to Arduino Digital Pin 8
#define LED 9 // Attach an LED to Digital Pin 9 (or use onboard LED)

void setup() {
   pinMode(Grove_Water_Sensor, INPUT); // The Water Sensor is an Input
   pinMode(LED, OUTPUT); // The LED is an Output
}

void loop() {
   /* The water sensor will switch LOW when water is detected.
   Get the Arduino to illuminate the LED and activate the buzzer
   when water is detected, and switch both off when no water is present */
   if( digitalRead(Grove_Water_Sensor) == LOW) {
      digitalWrite(LED,HIGH);
   }else {
      digitalWrite(LED,LOW);
   }
}

代码说明

水位传感器具有三个端子:S,Vout(+)和GND(-)。按如下所示连接传感器:

  • 将+Vs连接到Arduino板上的+5v。
  • 将S连接到Arduino板上的数字引脚8。
  • 将GND连接到Arduino上的GND。
  • 将LED连接到Arduino板上的数字引脚9。

当传感器检测到水时,Arduino上的引脚8变为LOW,然后Arduino上的LED亮起。

结果

当传感器检测到水时,你会看到指示LED灯亮起。

Arduino 温度传感器

温度传感器LM35系列是精密集成电路温度器件,输出电压与摄氏温度成线性比例。

LM35器件优于以开尔文校准的线性温度传感器,因为用户不需要从输出中减去大的恒定电压以获得便利的摄氏缩放。LM35器件不需要任何外部校准或调整,即可在室温下提供±1/4°C的典型精度,在-55°C至150°C的温度范围内提供±3°C的典型精度。

1.jpg

技术规格

  • 直接以摄氏度校准
  • 线性 + 10-mV/°C比例因子
  • 0.5°C确保准确度(在25°C)
  • 额定温度范围为-55°C至150°C
  • 适用于远程应用

必需的组件

你将需要以下组件:

  • 1 × Breadboard 面包板
  • 1 × Arduino Uno R3
  • 1 × LM35 传感器

程序

按照电路图连接面包板上的组件,如下图所示。

2.jpg

草图

在计算机上打开Arduino IDE软件。使用Arduino语言进行编码控制你的电路。通过单击“New”打开一个新的草图文件。

3.jpg

Arduino代码

float temp;
int tempPin = 0;

void setup() {
   Serial.begin(9600);
}

void loop() {
   temp = analogRead(tempPin);
   // read analog volt from sensor and save to variable temp
   temp = temp * 0.48828125;
   // convert the analog volt to its temperature equivalent
   Serial.print("TEMPERATURE = ");
   Serial.print(temp); // display temperature value
   Serial.print("*C");
   Serial.println();
   delay(1000); // update sensor reading each one second
}

代码说明

LM35传感器有三个端子:Vs,Vout和GND。我们将按如下方式连接传感器:

  • 将+Vs连接到Arduino板上的+5v电压。
  • 将Vout连接到Arduino板上的模拟0或A0。
  • 将GND连接到Arduino上的GND。

模数转换器(ADC)基于公式ADC值将模拟值转换为数字近似值=样本*1024/参考电压(+5v)。将模拟值转换为数字逼近。 那么用+5v做参考,数字近似值将等于输入电压*205。

结果

你将看到串口监视器上的温度显示,每秒更新一次。


Arduino 湿度传感器

Arduino 湿度传感器

2018-11-21 14:55 更新

在本节中,我们将学习如何使用不同的传感器连接我们的Arduino板。我们将讨论以下传感器:        

  • 湿度传感器(DHT22)
  • 温度传感器(LM35)
  • 水位检测传感器(简单水触发器)
  • PIR传感器
  • 超声波传感器
  • GPS

湿度传感器(DHT22)        

DHT-22(也称为AM2302)是一个数字输出,相对湿度和温度传感器。它使用电容式湿度传感器和热敏电阻来测量周围空气,并在数据引脚上发送数字信号。        

在本例中,你将学习如何将此传感器与Arduino UNO一起使用。室温和湿度将打印到串口监视器上。        

DHT-22传感器

1.jpg        

连接很简单。左边的第一个引脚为3-5V电源,第二个引脚连接到数据输入引脚,最右边的引脚接地。        

技术细节        

  • 电源 - 3-5V                

  • 最大电流 - 2.5mA                

  • 湿度 - 0-100%,精确度为2-5%                

  • 温度 - 40至80°C,精确度为±0.5°C                

必需的组件        

你将需要以下组件:        

  • 1 × Breadboard 面包板
  • 1 × Arduino Uno R3
  • 1 × DHT22
  • 1 × 10K欧姆电阻

程序        

按照电路图连接面包板上的组件,如下图所示。        

2.jpg        


       

1.jpg        

草图        

在计算机上打开Arduino IDE软件。使用Arduino语言进行编码控制你的电路。通过单击“New”打开一个新的草图文件。        

3.jpg        

Arduino代码        

// Example testing sketch for various DHT humidity/temperature sensors

#include "DHT.h"
#define DHTPIN 2 // what digital pin we're connected to
// Uncomment whatever type you're using!
//#define DHTTYPE DHT11 // DHT 11
#define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321
//#define DHTTYPE DHT21 // DHT 21 (AM2301)
// Connect pin 1 (on the left) of the sensor to +5V
// NOTE: If using a board with 3.3V logic like an Arduino Due connect pin 1
// to 3.3V instead of 5V!
// Connect pin 2 of the sensor to whatever your DHTPIN is
// Connect pin 4 (on the right) of the sensor to GROUND
// Connect a 10K resistor from pin 2 (data) to pin 1 (power) of the sensor
// Initialize DHT sensor.
// Note that older versions of this library took an optional third parameter to
// tweak the timings for faster processors. This parameter is no longer needed
// as the current DHT reading algorithm adjusts itself to work on faster procs.
DHT dht(DHTPIN, DHTTYPE);

void setup() {
   Serial.begin(9600);
   Serial.println("DHTxx test!");
   dht.begin();
}

void loop() {
   delay(2000); // Wait a few seconds between measurements
   float h = dht.readHumidity();
   // Reading temperature or humidity takes about 250 milliseconds!
   float t = dht.readTemperature();
   // Read temperature as Celsius (the default)
   float f = dht.readTemperature(true);
   // Read temperature as Fahrenheit (isFahrenheit = true)
   // Check if any reads failed and exit early (to try again).
   if (isnan(h) || isnan(t) || isnan(f)) {
      Serial.println("Failed to read from DHT sensor!");
      return;
   }
   
   // Compute heat index in Fahrenheit (the default)
   float hif = dht.computeHeatIndex(f, h);
   // Compute heat index in Celsius (isFahreheit = false)
   float hic = dht.computeHeatIndex(t, h, false);
   Serial.print ("Humidity: ");
   Serial.print (h);
   Serial.print (" %\t");
   Serial.print ("Temperature: ");
   Serial.print (t);
   Serial.print (" *C ");
   Serial.print (f);
   Serial.print (" *F\t");
   Serial.print ("Heat index: ");
   Serial.print (hic);
   Serial.print (" *C ");
   Serial.print (hif);
   Serial.println (" *F");
}
       

代码说明        

DHT22传感器具有四个端子连接到电路板的端子(Vcc,DATA,NC,GND),如下:        

  • DATA引脚连接到Arduino的2号引脚号
  • Vcc引脚连接到Arduino板的5伏电压
  • GND引脚连接到Arduino板的接地
  • 我们需要在DATA和Vcc引脚之间连接10k欧姆电阻(上拉电阻)

一旦硬件连接完成,你需要添加DHT22库到你的Arduino库文件,如前所述。        

结果        

你将看到串口监视器上的温度和湿度显示,每2秒更新一次。        


Arduino 网络通信

德州仪器的CC3000 WiFi模块是一个小型银包,最终为你的Arduino项目带来了易用,经济实惠的WiFi功能。

它使用SPI进行通信(而不是UART),因此你可以根据需要尽可能快或尽可能慢地推送数据。它有一个合适的IRQ引脚中断系统,因此你可以有异步连接。它支持802.11b/g,open/WEP/WPA/WPA2安全,TKIP及AES。具有“BSD socket”接口的内置TCP/IP堆栈支持客户端和服务器模式下的TCP和UDP。


1.jpg

必需的组件

你将需要以下组件:

  • 1 × Arduino Uno
  • 1 × Adafruit CC3000分线板
  • 1 × 5V继电器
  • 1 × 整流二极管
  • 1 × LED
  • 1 × 220欧姆电阻
  • 1 × 面包板和一些跳线

对于这个项目,你只需要通常的Arduino IDE,Adafruit的CC3000库以及CC3000 MDNS库。我们也将使用aREST库通过WiFi向中继发送命令。

程序

按照电路图进行连接,如下图所示。

2.jpg

这个项目的硬件配置非常简单。

  • 将CC3000板的IRQ引脚连接到Arduino板的引脚3。
  • VBAT连接到引脚5,CS连接到引脚10。
  • 将SPI引脚连接到Arduino板:MOSI,MISO和CLK分别连接到引脚11,12和13。
  • Vin连接到Arduino 5V,GND连接到GND。

现在,让我们连接继电器。

将继电器放在面包板上后,你可以开始识别继电器上的两个重要部分:指示继电器的线圈部分和连接LED的开关部分。

  • 首先,将Arduino板的8号引脚连接到线圈的一个引脚。
  • 将另一个引脚连接到Arduino板的接地。

您还必须将整流二极管(阳极连接到接地引脚)放置在线圈的引脚上,以在继电器切换时保护电路。

  • 将Arduino板的+5V连接到继电器开关的公共引脚。        

  • 最后,将开关的另一个引脚(通常是继电器断开时未连接的引脚)连接到与220欧姆电阻串联的LED,并将LED的另一端连接到Arduino的接地。        

测试单个组件

你可以使用以下草图测试继电器:

const int relay_pin = 8; // Relay pin

void setup() {
   Serial.begin(9600);
   pinMode(relay_pin,OUTPUT);
}

void loop() {
   // Activate relay
   digitalWrite(relay_pin, HIGH);
   // Wait for 1 second
   delay(1000);
   // Deactivate relay
   digitalWrite(relay_pin, LOW);
   // Wait for 1 second
   delay(1000);
}

代码说明

代码是不言自明的。你只需将其上传到电路板,继电器将每秒切换状态,LED将相应地亮起和熄灭。

添加WiFi连接

现在让我们使用CC3000 WiFi芯片无线控制继电器。该项目的软件基于TCP协议。但是,对于这个项目,Arduino板将运行一个小的Web服务器,以便我们可以“监听”来自计算机的命令。我们先来看看Arduino草图,然后我们将看到如何编写服务器端代码并创建一个漂亮的界面。

首先,Arduino草图。这里的目标是连接到你的WiFi网络,创建Web服务器,检查是否有传入的TCP连接,然后相应地更改继电器的状态。

代码的重要部分

#include <Adafruit_CC3000.h>
#include <SPI.h>
#include <CC3000_MDNS.h>
#include <Ethernet.h>
#include <aREST.h>

你需要在代码中定义特定于你的配置的内容,即Wi-Fi名称和密码,以及TCP通信端口(我们在此使用了80)。

// WiFi network (change with your settings!)
   #define WLAN_SSID "yourNetwork" // cannot be longer than 32 characters!
   #define WLAN_PASS "yourPassword"
   #define WLAN_SECURITY WLAN_SEC_WPA2 // This can be WLAN_SEC_UNSEC, WLAN_SEC_WEP, 
   // WLAN_SEC_WPA or WLAN_SEC_WPA2

// The port to listen for incoming TCP connections
   #define LISTEN_PORT 80

然后我们可以创建CC3000实例,服务器和aREST实例:

// Server instance
   Adafruit_CC3000_Server restServer(LISTEN_PORT); // DNS responder instance
   MDNSResponder mdns; // Create aREST instance
   aREST rest = aREST();

在草图的setup()部分,我们现在可以将CC3000芯片连接到网络:

cc3000.connectToAP(WLAN_SSID, WLAN_PASS, WLAN_SECURITY);

计算机将如何知道在哪里发送数据?一种方法是运行草图一次,然后获取CC3000板的IP地址,并再次修改服务器代码。但是,我们可以做得更好,这就是CC3000 MDNS库发挥作用的地方。我们将使用此库为我们的CC3000板分配一个固定名称,以便我们可以将此名称直接写入服务器代码。

这可以用下面的代码片段完成:

if (!mdns.begin("arduino", cc3000)) {
   while(1);
}

我们还需要监听传入的连接。

restServer.begin();

接下来,我们要对将被连续执行的草图的loop()函数进行编码。我们首先要更新mDNS服务器。

mdns.update();

在Arduino板上运行的服务器将等待传入连接并处理请求。

Adafruit_CC3000_ClientRef client = restServer.available();
rest.handle(client);

现在通过WiFi测试项目非常容易。确保你使用自己的WiFi名称和密码更新草图,并将草图上传到Arduino板。打开你的Arduino IDE串口监视器,并查找电路板的IP地址。

我们假设其余的是192.168.1.103。

然后,只需进入你喜欢的网络浏览器,然后键入:

192.168.1.103/digital/8/1

你应该看到继电器自动打开。

构建继电器界面

我们现在将编写项目的界面。这里将有两个部分:包含界面的HTML文件和用于处理界面上点击的客户端Javascript文件。这里的界面基于aREST.js项目,这是为了方便从你的计算机控制WiFi设备。
让我们先看一下名为interface.html的HTML文件。第一部分包括导入所有界面需要的库:

<head>
   <meta charset = utf-8 />
   <title> Relay Control </title>
   <link rel = "stylesheet" type = "text/css" 
      href = "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
   <link rel="stylesheet" type = "text/css" href = "style.css">
   <script type = "text/javascript" 
      src = "https://code.jquery.com/jquery-2.1.4.min.js"></script>
   <script type = "text/javascript" 
      src = "https://cdn.rawgit.com/Foliotek/AjaxQ/master/ajaxq.js"></script>
   <script type = "text/javascript" 
      src = "https://cdn.rawgit.com/marcoschwartz/aREST.js/master/aREST.js"></script>
   <script type = "text/javascript" 
      src = "script.js"></script>
</head>

然后,我们在界面中定义两个按钮,一个用于打开继电器,另一个用于再次关闭继电器。

<div class = 'container'>
   <h1>Relay Control</h1>
   <div class = 'row'>
      <div class = "col-md-1">Relay</div>
      <div class = "col-md-2">
         <button id = 'on' class = 'btn btn-block btn-success'>On</button>
      </div>
      <div class = "col-md-2">
         <button id = 'off' class = 'btn btn-block btn-danger'>On</button>
      </div>
   </div>
</div>

现在,我们还需要一个客户端Javascript文件来处理按钮上的点击。我们还将创建一个设备,我们将链接到Arduino设备的mDNS名称。如果你在Arduino代码中改变了这个,你也需要在这里修改它。

// Create device
var device = new Device("arduino.local");
// Button

$('#on').click(function() {
   device.digitalWrite(8, 1);
});

$('#off').click(function() {
   device.digitalWrite(8, 0);
});

该项目的完整代码可以在 GitHub 存储库中找到。进入界面文件夹,只需用你喜欢的浏览器打开HTML文件。应该会在浏览器中看到类似的内容:

3.jpg


尝试点击Web界面上的按钮;它应该立即改变继电器的状态。

如果你设法让它工作了,恭喜你,你刚刚构建了一个Wi-Fi控制的电灯开关。当然,通过这个项目你可以控制更多的电灯。只需确保你的继电器支持你想要控制的设备所需的电源,你就可以很好的实现了。



Arduino 无线通信

无线发射器和接收器模块工作在315 Mhz。它们可以轻松地装入面包板,并可很好的与微控制器配合使用,创建一个非常简单的无线数据链路。使用一对发射器和接收器,模块将只能单向传输数据,因此,你将需要两对(不同频率)作为发射器/接收器对。

注意 - 这些模块是任意的,并会接收相当大量的噪音。发射器和接收器都在共同的频率下工作,并且没有ID。

1.jpg

接收器模块规格

产品型号 - MX-05V
工作电压 - DC5V
静态电流 - 4mA
接收频率 - 315Mhz
接收灵敏度 - -105DB
尺寸 - 30*14*7mm

发射器模块规格

产品型号 - MX-FS-03V
发射距离 - 20-200米(不同电压,不同结果)
工作电压 - 3.5-12V
尺寸 - 19*19mm
操作模式 - AM
传输速率 - 4KB/S
发射功率 - 10mW
发射频率 - 315Mhz
外置天线 - 25cm普通多芯或单芯线
从左到右的引脚分布 - DATA;VCC;GND

必需的组件

你将需要以下组件:

  • 2 × Arduino UNO板
  • 1 × Rf链路发射器
  • 1 × Rf链路接收器

程序

按照电路图进行连接,如下图所示。

2.jpg

草图

在计算机上打开Arduino IDE软件。使用Arduino语言进行编码控制你的电路。通过单击“New”打开一个新的草图文件。

3.jpg

注意 - 你必须在Arduino库文件中包含键盘库。将VirtualWire.lib文件复制并粘贴到库文件夹中,如下面屏幕截图的高亮部分所示。

4.jpg

发射器的Arduino代码

//simple Tx on pin D12
#include <VirtualWire.h>
char *controller;

void setup() {
   pinMode(13,OUTPUT);
   vw_set_ptt_inverted(true);
   vw_set_tx_pin(12);
   vw_setup(4000);// speed of data transfer Kbps
}

void loop() {
   controller="1" ;
   vw_send((uint8_t *)controller, strlen(controller));
   vw_wait_tx(); // Wait until the whole message is gone
   digitalWrite(13,1);
   delay(2000);
   controller="0" ;
   vw_send((uint8_t *)controller, strlen(controller));
   vw_wait_tx(); // Wait until the whole message is gone
   digitalWrite(13,0);
   delay(2000);
}

代码说明

这是一个简单的代码。首先发送字符“1”,两秒后发送字符“0”,以此类推。

接收器的Arduino代码

//simple Rx on pin D12
#include <VirtualWire.h>

void setup() {
   vw_set_ptt_inverted(true); // Required for DR3100
   vw_set_rx_pin(12);
   vw_setup(4000); // Bits per sec
   pinMode(5, OUTPUT);
   vw_rx_start(); // Start the receiver PLL running
}

void loop() {
   uint8_t buf[VW_MAX_MESSAGE_LEN];
   uint8_t buflen = VW_MAX_MESSAGE_LEN;
   if (vw_get_message(buf, &buflen)) // Non-blocking {
      if(buf[0]=='1') {
         digitalWrite(5,1);
      }
      if(buf[0]=='0') {
         digitalWrite(5,0);
      }
   }
}

代码说明

当接收到字符“1”时,连接到Arduino板上引脚5的LED亮起,当接收到字符“0”时,LED熄灭。



Arduino 步进电机

步进电机是无刷同步电机,它将完整的旋转分成多个步骤。与无刷直流电机不同,当向其施加固定的直流电压时,它将连续旋转,步进电机以不连续的步进角旋转。

因此,步进电机被制造成具有每转12,24,72,144,180和200的步长,从而产生每步30°,15°,5°,2.5°,2°和1.8°的步进角。步进电机可以有或没有反馈控制。

想象一下在RC飞机上的电机。电机在一个方向或另一个方向上转速非常快。你可以通过给予电机的功率量来改变速度,但是你不能让螺旋桨停在特定位置。

现在想象一个打印机。打印机内有很多移动部件,包括电机。一个电机用作进纸,当墨水开始印在纸上时旋转滚轴移动纸张。此电机需要能够将纸张移动一个精确的距离,以便能够打印下一行文本或图像的下一行。

还有另一个电机连接到一个螺杆上来回移动打印头。同样,该螺杆需要移动一个精确的量,以便一个字母接一个地打印。这就是步进电机派上用场的地方。

1.jpg

步进电机如何工作?

常规的直流电动机只在方向上旋转,而步进电动机可以以精确的增量旋转。

步进电机可以根据需要转动精确的度数(或步长)。这使你可以完全控制电机,允许你将其移动到精确的位置并保持在该位置。它通过为电机内部的线圈非常短时间的供电来实现。缺点是你必须给电机一直供电,以将它保持在你想要的位置。

你现在需要知道的是,要移动步进电机,你要告诉它在一个方向或另一个方向上移动一定数量的步进,并告诉它沿那个方向步进的速度。步进电机的种类繁多。这里描述的方法可以用于推断如何使用本教程中未提到的其他电机和驱动程序。但是,始终建议你查阅针对具体型号的电机和驱动器的数据表和指南。

2.jpg

必需的组件

你将需要以下组件:

  • 1 × Arduino UNO板
  • 1 ×小型双极步进电机,如下图所示
  • 1 ×LM298驱动IC
3.jpg

程序

按照电路图进行连接,如下图所示。

4.jpg

草图

在计算机上打开Arduino IDE软件。使用Arduino语言进行编码控制你的电路。通过单击“New”打开一个新的草图文件。

5.jpg

Arduino代码

/* Stepper Motor Control */

#include <Stepper.h>
const int stepsPerRevolution = 90;
// change this to fit the number of steps per revolution
// for your motor
// initialize the stepper library on pins 8 through 11:
Stepper myStepper(stepsPerRevolution, 8, 9, 10, 11);

void setup() {
   // set the speed at 60 rpm:
   myStepper.setSpeed(5);
   // initialize the serial port:
   Serial.begin(9600);
}

void loop() {
   // step one revolution in one direction:
   Serial.println("clockwise");
   myStepper.step(stepsPerRevolution);
   delay(500);
   // step one revolution in the other direction:
   Serial.println("counterclockwise");
   myStepper.step(-stepsPerRevolution);
   delay(500);
}

代码说明

该程序驱动单极或双极步进电机。电机连接到Arduino的数字引脚8-11。

结果

电机将在一个方向上旋转一圈,然后在另一个方向上旋转一圈。



Arduino 伺服电机

伺服电机是一种有输出轴的小型设备。通过向伺服发送编码信号,可以将该轴定位到特定的角度位置。只要编码信号存在于输入线上,伺服将保持轴的角位置。如果编码信号改变,则轴的角位置改变。实际上,伺服用于无线电控制的飞机中来定位控制面,如升降舵和方向舵。它们还用于无线电控制的汽车,木偶,当然还有机器人。


1.jpg

伺服在机器人中非常有用。电机体积小,内置控制电路,相对于它们尺寸来说非常强大。标准伺服如Futaba S-148具有42盎司/英寸的扭矩,这对于其尺寸来说是坚固的。它还吸取与机械负载成比例的功率。因此,轻负载伺服不会消耗太多能量。

伺服电机的内胆如下图所示。你可以看到控制电路,电机,一组齿轮和外壳。还可以看到连接到外部的3根电线。一个是接电源(+5伏),一个接地,而白线是控制线。

2.jpg

伺服电机的工作

伺服电机有一些控制电路和一个连接到输出轴上的电位器(一个可变电阻,也称为电位器)。在上图中,电位器可以在电路板的右侧看到。该电位器允许控制电路监视伺服电机的当前角度。

如果轴处于正确的角度,则电机关闭。如果电路发现角度不正确,则会转动电机直到处于所需的角度。伺服的输出轴能够在180度左右的地方移动。通常情况下,它是在210度范围内的某个地方,然而,这取决于制造商。正常伺服用于控制0至180度的角运动。由于主输出齿轮上的机械止动装置,机械上它无法转动更远。

施加到电机上的功率与其需要行进的距离成比例。因此,如果轴需要转动较大的距离,电机将以全速运转。如果只需要少量转动,电机将以较低的速度运转。这称为比例控制

如何沟通伺服应该转动的角度?

控制线用于传达角度。该角度由施加到控制线的脉冲持续时间确定。这称为脉冲编码调制伺服期望每20毫秒(0.02秒)看到一个脉冲。脉冲的长度将决定电机转动的距离。例如,1.5毫秒脉冲将使电机转到90度位置(通常称为中性位置)。如果脉冲短于1.5毫秒,则电机将轴转到更接近0度。如果脉冲长于1.5毫秒,则轴转接近180度。

3.jpg

必需的组件

你将需要以下组件:

  • 1 × Arduino UNO板
  • 1 × 伺服电机
  • 1 × ULN2003驱动IC
  • 1 × 10KΩ电阻

程序

按照电路图进行连接,如下图所示。

4.jpg

草图

在计算机上打开Arduino IDE软件。使用Arduino语言进行编码控制你的电路。通过单击“New”打开一个新的草图文件。

5.jpg

Arduino代码

/* Controlling a servo position using a potentiometer (variable resistor) */

#include <Servo.h>
   Servo myservo; // create servo object to control a servo
   int potpin = 0; // analog pin used to connect the potentiometer
   int val; // variable to read the value from the analog pin

void setup() {
   myservo.attach(9); // attaches the servo on pin 9 to the servo object
}

void loop() {
   val = analogRead(potpin);
   // reads the value of the potentiometer (value between 0 and 1023)
   val = map(val, 0, 1023, 0, 180);
   // scale it to use it with the servo (value between 0 and 180)
   myservo.write(val); // sets the servo position according to the scaled value
   delay(15);
}

代码说明

伺服电机有三个端子:电源,接地和信号。电源线通常为红色,应连接到Arduino上的5V引脚。接地线通常为黑色或棕色,应连接到ULN2003 IC(10-16)的一个端子。为了保护你的Arduino板免受损坏,你将需要一些驱动IC来处理这些。这里我们使用ULN2003 IC来驱动伺服电机。信号引脚通常为黄色或橙色,应连接到Arduino引脚9。

连接电位器

分压器是串联电路中的电阻器,其将输出电压缩放到施加的输入电压的特定比例。下面是电路图:

6.jpg


公式

Vout是输出电位,取决于施加的输入电压(Vin)和电阻(R1R2这意味着流过R1的电流也将流过R2而不被分流。在上述等式中,随着R的值改变,Vout相对于输入电压Vin而缩放。

通常,电位器是一个分压器,它可以根据可变电阻的值而使用旋钮来缩放电路的输出电压。它有三个引脚:GND,Signal和+5V,如下图所示:

7.jpg

结果

通过更改电位器的NOP位置,伺服电机将改变其角度。


Arduino 直流电机

在本章中,我们将使用Arduino板(UNO)连接不同类型的电机,并向你展示如何连接电机并从电路板上驱动它。

有三种不同类型的电机:

  • DC motor 直流电机
  • Servo motor 伺服电机
  • Stepper motor 步进电机

直流电机(DC—Direct Current motor)是最常见的电机类型。直流电动机通常只有两个引线,一个正极和一个负极。如果将这两根引线直接连接到电池,电机将旋转。如果切换引线,电机将以相反的方向旋转。

1.jpg

警告 - 不要直接从Arduino板引脚驱动电机。这可能会损坏电路板。使用驱动电路或IC。

我们将本章分为三个部分:

  • 只让你的电机旋转
  • 控制电机速度
  • 控制直流电机的旋转方向


必需的组件

你将需要以下组件:

  • 1x Arduino UNO 板
  • 1x PN2222 晶体管
  • 1x 小型6V直流电机
  • 1x 1N4001二极管
  • 1x 270Ω电阻

程序

按照电路图进行连接,如下图所示。

2.jpg

预防措施

进行连接时,请采取以下预防措施:

  • 首先,确保晶体管以正确的方式连接。如图所示,晶体管的扁平面应该面向Arduino板。        

  • 其次,根据图像中所示的排列,二极管的条纹端应朝向+5V电源线。        

Arduino旋转控制代码

int motorPin = 3;

void setup() {

}

void loop() {
   digitalWrite(motorPin, HIGH);
}

代码说明

晶体管就像一个开关,控制电机的功率。Arduino引脚3用于打开和关闭晶体管,并在草图中命名为“motorPin”。

结果

当Arduino引脚3变为高电平时,电机将全速旋转。

电机速度控制

以下是连接到Arduino板的直流电机的原理图。

3.jpg

Arduino代码

int motorPin = 9;

void setup() {
   pinMode(motorPin, OUTPUT);
   Serial.begin(9600);
   while (! Serial);
   Serial.println("Speed 0 to 255");
}

void loop() {
   if (Serial.available()) {
      int speed = Serial.parseInt();
      if (speed >= 0 && speed <= 255) {
         analogWrite(motorPin, speed);
      }
   }
}

代码说明

晶体管就像一个开关,控制电机的功率。Arduino引脚3用于打开和关闭晶体管,并在草图中命名为“motorPin”。

当程序启动时,它会提示你提供值以控制电机的速度。你需要在串口监视器中输入介于0和255之间的值。

4.jpg

在“loop”函数中,命令“Serial.parseInt”用于读取在串口监视器中作为文本输入的数字,并将其转换为“int”。你可以在此处输入任何数字。如果数字在0到255之间,下一行中的“if”语句只使用此数字进行模拟写入。

结果

直流电机将根据通过串口接收的值(0到250)以不同的速度旋转。

旋转方向控制

为了控制直流电机的旋转方向,无需互换引线,可以使用称为H桥的电路。H桥是可以双向驱动电机的电子电路。H桥用于许多不同的应用中。最常见的应用之一是控制机器人中的电机。它被称为H桥,是因为它使用四个晶体管连接,使示意图看起来像一个“H”。

我们将在这里使用L298 H桥 IC。L298可以控制直流电机和步进电机的速度和方向,并可以同时控制两个电机。每个电机的额定电流为2A。然而,在这些电流下,你将需要使用散热片。

5.jpg

必需的组件

你将需要以下组件:

  • 1 × L298桥式IC
  • 1 × 直流电机
  • 1 × Arduino UNO
  • 1 × 面包板
  • 10 × 跳线

程序

以下是Arduino Uno板的直流电机接口示意图。

6.jpg

上图显示了如何连接L298 IC控制两个电机。每个电机有三个输入引脚,Motor1的Input1(IN1),Input2(IN2)和Enable1(EN1);Motor2的Input3,Input4和Enable2。

由于在这个例子中我们只控制一个电机,因此我们将把Arduino连接到L298 IC的IN1(引脚5),IN2(引脚7)和Enable1(引脚6)。引脚5和7是数字的,即ON或OFF输入,而引脚6需要脉冲宽度调制(PWM)信号来控制电机速度。

下表显示了电机根据IN1和IN2的数字值转动的方向。

IN1            IN2            电机行为            


制动
1
向前

1向后
11制动

IC L298的引脚IN1连接到Arduino的引脚8,而IN2连接到引脚9。Arduino的这两个数字引脚控制电机的方向。IC的EN A引脚连接到Arduino的PWM引脚2。这将控制电机的速度。

为了设置Arduino引脚8和9的值,我们使用了digitalWrite()函数,而设置引脚2的值,我们必须使用analogWrite()函数。

连接步骤

  • 将IC的5V和接地分别连接到Arduino的5V和接地。
           
  • 将电机连接到IC的引脚2和3。
           
  • 将IC的IN1连接到Arduino的引脚8。
           
  • 将IC的IN2连接到Arduino的引脚9。
           
  • 将IC的EN1连接到Arduino的引脚2。
           
  • 将ICD的SENS A引脚接地。
           
  • 使用Arduino USB线连接Arduino,并使用Arduino IDE软件将程序上传到Arduino。
           
  • 使用电源,电池或USB线为Arduino板供电。
           

Arduino代码

const int pwm = 2 ; //initializing pin 2 as pwm
const int in_1 = 8 ;
const int in_2 = 9 ;
//For providing logic to L298 IC to choose the direction of the DC motor

void setup() {
   pinMode(pwm,OUTPUT) ; //we have to set PWM pin as output
   pinMode(in_1,OUTPUT) ; //Logic pins are also set as output
   pinMode(in_2,OUTPUT) ;
}

void loop() {
   //For Clock wise motion , in_1 = High , in_2 = Low
   digitalWrite(in_1,HIGH) ;
   digitalWrite(in_2,LOW) ;
   analogWrite(pwm,255) ;
   /* setting pwm of the motor to 255 we can change the speed of rotation
   by changing pwm input but we are only using arduino so we are using highest
   value to driver the motor */
   //Clockwise for 3 secs
   delay(3000) ;
   //For brake
   digitalWrite(in_1,HIGH) ;
   digitalWrite(in_2,HIGH) ;
   delay(1000) ;
   //For Anti Clock-wise motion - IN_1 = LOW , IN_2 = HIGH
   digitalWrite(in_1,LOW) ;
   digitalWrite(in_2,HIGH) ;
   delay(3000) ;
   //For brake
   digitalWrite(in_1,HIGH) ;
   digitalWrite(in_2,HIGH) ;
   delay(1000) ;
}

结果

电机首先在顺时针(CW)方向运行3秒,然后逆时针(CCW)运行3秒。


Arduino的Stepper库函数及其控制步进电机(ULN2003)调速

问题来源

最近自学Arduino,在使用步进电机时开始没能使步进电机转起来,转起来后感觉没法调速,遂完成此篇笔记供自己后续查阅以及方便遇到相同问题的诸君寻找灵感。对于如何使步进电机转动就不详述,网上有很多。


Stepper库函数

这个是Arduino库函数,用于控制步进电机,官网Stepper说明网址为https://www.arduino.cc/en/Reference/Stepper,下面介绍个人理解:


Stepper

两种调用形式,Stepper(steps, pin1, pin2)与Stepper(steps, pin1, pin2, pin3, pin4),主要是看电机是两相还是四相,该函数作用是创建Stepper类的实例,参数steps应该是所使用的的电机转一周需要的步数(脉冲数),通过电机步距角计算。后面的pin1,2,3,4,依据实际情况填写。注意这个函数需要写在程序开始,即在void setup()之上。

步数:你的马达每转的步数。如果您的电机给出每步的度数,则将该数字除以360以获得步数(例如,360/3.6给出100步)

setSpeed

调用形式setSpeed(rpms),这个是控制转述的函数,实际是脉冲频率,参数为每分钟电机转的步数。数值为正整数。该函数不会使电机旋转。

step

step(steps),这个函数是使电机转过固定的步数,只有当电机转过指定步数之后才会执行该语句的下一条语句。参数应该是整数,正负号控制旋转方向。

调速

读了上面就知道step()函数是控制转速的。

导致我怀疑电机不能调速的原因是设置不同参数电机都旋转很慢,事实证明我买的电机就只能转这么快。step(300)就到达我所用电机与ULN2003的极限,而此前我不理解网上那程序上述函数的具体意义,调了一通参数也没见到步进电机快速旋转。此外使用电机与驱动器不同,会导致别人程序中的备注文字并不使用与我所写的程序,因此误解别人程序,导致调速迟迟不能成功。


实例

//使用arduino IDE自带的Stepper.h库文件

#include <Stepper.h>


// 这里设置步进电机旋转一圈是多少步,依据步距角计算,及走一圈需要多少脉冲

#define STEPS 64


//设置步进电机的步数和引脚(就是驱动板上IN1~IN4连接的四个数字口)。

Stepper stepper(STEPS, 8, 9, 10, 11);


void setup()

{

  // 设置电机的转速:每分钟为90步

  stepper.setSpeed(290);

  // 初始化串口,用于调试输出信息

  Serial.begin(9600);

}


void loop()

{

    // 顺时针旋转

    Serial.println("shun");

    stepper.step(2048); 

    //delay(500);


    // 逆时针旋转

    /*

    Serial.println("ni");

    stepper.step(-512);

    delay(500);

    */

}



Arduino 步进电机

步进电机是一种将电脉冲转化为角位移的执行机构。通俗一点讲:当步进驱动器接收到一个脉冲信号,它就驱动步进电机按设定的方向转动一个固定的角度(即步进角),多用于雕刻机、3D打印机等需要精确控制的设备。本篇使用ULN2003驱动五线四相减速步进电机。


1. 步进电机介绍

步进电机每次能转动的最小角度叫做步距角。

每当步进电机接收到一个驱动信号后,步进电机将按照一定的方向转动一个固定的角度。

通过控制脉冲的个数来精确的控制步进电机的角位移量,通过控制脉冲的频率来控制电机转动的速度及加速度,从而达到调速的目的。

步进电机按照相数不同分为单相、双相、多相三种,励磁方式分为1相励磁方式和2相励磁方式。

本篇使用的步进电机型号为28BYJ-48,1相励磁方式驱动,通过给ABCD四相依次通电来实现转自不停转动。

1.jpg


2. ULN2003驱动介绍

由于Arduino开发板的通用IO驱动能力有限,有些外设不能直接使用IO进行驱动,需要借助一些驱动电路间接控制大功率器件。ULN2003是大电流驱动阵列,多用于单片机、智能仪表、PLC、数字量输出卡等控制电路中,可直接驱动继电器等负载。

2.jpg



3. 实验材料

Uno R3开发板

配套USB数据线

面包板连接线

ULN2003驱动板

步进电机

4. 实验步骤

1. 根据原理图搭建电路。

ULN2003驱动板上IN1、IN2、IN3、IN4分别连接UNO开发板的数字引脚2,3,4,5;驱动板电源输入+、-引脚分别连接UNO开发板的5V、GND。

1.jpg



实验原理图如下图所示:

1.jpg



实物连接图如下图所示:


1.jpg


2. 新建sketch,拷贝如下代码替换自动生成的代码并进行保存。

/*

 * Stepper_Motor

 * 步进电机驱动,实现正反转

 */

void setup() {

  // put your setup code here, to run once:

  for (int i = 2; i < 6; i++) {

    pinMode(i, OUTPUT);

  }

}


void clockwise(int num)

{

  for (int count = 0; count < num; count++)

  {

    for (int i = 2; i < 6; i++)

    {

      digitalWrite(i, HIGH);

      delay(3);

      digitalWrite(i, LOW);

    }

  }

}


void anticlockwise(int num)

{

  for (int count = 0; count < num; count++)

  {

    for (int i = 5; i > 1; i--)

    {

      digitalWrite(i, HIGH);

      delay(3);

      digitalWrite(i, LOW);

    }

  }

}


void loop() {

  // put your main code here, to run repeatedly:

  clockwise(512);

  delay(10);

  anticlockwise(512);

}


3. 连接开发板,设置好对应端口号和开发板类型,进行程序下载。

1.jpg


5. 实验现象

步进电机顺时针、逆时针交替旋转。

1.gif



6. 实验分析

程序通过ABCD四相通电顺序实现步进电机正反转控制。步进电机旋转一周,程序中循环次数为什么是512次呢?这是根据我们使用的步进电机参数计算而来的:


步进电机电压5V,步距角5.625,减速比1:64

计算A-B-C-D通电一次转动的角度5.625X2X4/64=0.703125 (2是表示1相励磁方式每步是2倍的步距角,4表示走了4步, 64指电机减速比)

转动360度循环ABCD通电的次数360/0.7031=512



Arduino 舵机操作

Arduino舵机操作,可能是由于内容开始是网络地址(http://...)完整的英文词截取后不能换行造成 为避免代码造成手机端排版的混乱,可适当增加文字描述,将代码往后推移

#include <Servo.h>

Servo myservo;

void setup() {

  // Serial.begin(9600);

  myservo.attach(11);//脉冲信号线

 // Serial.println(EEPROM.length());

}

void loop() {

  myservo.write(0);//转到0度

  delay(1000);

  myservo.write(180);//转到180度

  delay(1000);

}

1. 根据原理图搭建电路。

线路连接非常简单,舵机红色线接开发板5V,棕色线接开发板GND,橙色信号线接开发板数字引脚9。


实验原理图如下图所示:

1.jpg



实物连接图如下图所示:


2.jpg


2. 新建sketch,拷贝如下代码替换自动生成的代码并进行保存。

#include <Servo.h>


Servo myservo;  // 定义Servo对象来控制

int pos = 0;    // 角度存储变量


void setup() {

  myservo.attach(9);  // 控制线连接数字9

}


void loop() {

  for (pos = 0; pos <= 180; pos ++) { // 0°到180°

    // in steps of 1 degree

    myservo.write(pos);              // 舵机角度写入

    delay(5);                       // 等待转动到指定角度

  }

  for (pos = 180; pos >= 0; pos --) { // 从180°到0°

    myservo.write(pos);              // 舵机角度写入

    delay(5);                       // 等待转动到指定角度

  }

}


Arduino笔记-定时器中断(MsTimer2库使用)

定时器中断,在某一特定的时候,


不管在干嘛,都要去执行那个定时器中断,指向的代码段。


这里以Arduino UNO为例:


运行截图如下:


每隔1s,亮LED_BUILTIN的灯:

1.png



这里要下载MsTimer2的库,如下:

2.png



输入MsTimer,然后安装即可:


3.png


源码如下:


#include <MsTimer2.h>

 

int led = 8;

 

void onTimer()

{

  digitalWrite(LED_BUILTIN, HIGH);

  delay(300);

  digitalWrite(LED_BUILTIN, LOW);

  delay(300);

}

 

void setup() {

 

  Serial.begin(9600);

  pinMode(led, OUTPUT);

  pinMode(LED_BUILTIN, OUTPUT);

  MsTimer2::set(1000, onTimer); //设置中断,每1000ms进入一次中断服务程序 onTimer()

  MsTimer2::start(); //开始计时_开启定时器中

}

 

void loop() {

 

  digitalWrite(led, HIGH);

  delay(1000);

  digitalWrite(led, LOW);

  delay(1000);

}



Arduino定时器中断attachInterrupt()详解

说明

attachInterrupt()函数是用于为Arduino开发板设置和执行ISR(中断服务程序)用的

ISR(中断服务程序)顾名思义就是中断Arduino当前正在处理的事情而优先去执行中断服务程序。当中断服务程序完成以后,再回来继续执行刚才执行的事情。中断服务程序对监测Arduino输入有很大的用处。

我们可以使用attachInterrupt()函数,利用Arduino的引脚触发中断程序。以下列表说明支持中断的引脚有哪些:

Arduino控制板            支持中断的引脚            
Uno, Nano, Mini2, 3
Mega, Mega2560, MegaADK            2, 3, 18, 19, 20, 21
Micro, Leonardo0, 1, 2, 3, 7
Zero除4号引脚以外的所有数字引脚
MKR1000 Rev.10, 1, 4, 5, 6, 7, 8, 9, A1, A2
Due所有数字引脚

注意

在ISR(中断服务程序)函数中,delay()函数是不工作的,而且millis()函数返回值也不再增长。在ISR(中断服务程序)运行期间Arduino开发板接收到的串口数据也可能丢失。另外ISR函数里所使用的变量应声明为volatile类型。详情请见以下”关于ISR(中断服务程序)”部分。

使用中断

中断很适合执行那些需要不断检查的工作,比如检查一个引脚上连接的按键开关是否被按下。中断更适用于很快就会消失的信号检查,比如某一个引脚用于检测脉冲信号,这个脉冲信号的持续时间可能十分短暂。如果不使用中断,那么假如Arduino开发板正在执行其它任务时,突然这个脉冲信号来了,还不等Arduino开发板完成正在执行的工作,这个脉冲信号可能就已经消失了。而使用中断,就可以确保这个转瞬即逝的脉冲信号可以很好的被Arduino开发板检测到并执行相应任务。

关于ISR(中断服务程序)

对于Arduino开发板来说,ISR(中断服务程序)是一种特殊的函数。它的特殊意味着它具有其它类型函数所不具备的限制和特点。

  • ISR函数不能有任何参数。ISR也没有任何返回值。

  • 通常ISR需要越短小精悍越好!另外如果您的代码中有多个ISR函数,那么每次Arduino只能运行一个ISR函数,其它ISR函数只有在当前的ISR函数执行结束以后,才能按照其优先级别顺序执行。

  • millis()函数的运行依赖Arduino开发板的中断功能,因此ISR函数中的millis()函数是无法正常运行的。micros() 也是类似的情况,它只能在初始的1-2毫秒中可以运行,但是过了这1-2毫秒后就开始出现问题了。 delayMicroseconds() 不需要任何计数器就可以运行,所以delayMicroseconds() 运行是不会受到影响的。

  • 一般情况下,ISR函数与主程序之间传递数据是依靠全局变量来实现的。为了确保全局变量在ISR函数中可以正常的工作,应该将可能被ISR函数中使用的全局变量声明为volatile类型。

如需更多有关中断方面的知识,请参考 Nick Gammon’s notes.

语法

attachInterrupt(digitalPinToInterrupt(pin), ISR, mode);

参数

pin: 中断引脚号
ISR: 中断服务程序名
mode:中断模式

中断模式(mode)有以下几种形式:

LOW: 当引脚为低电平时触发中断服务程序
CHANGE: 当引脚电平发生变化时触发中断服务程序
RISING: 当引脚电平由低电平变为高电平时触发中断服务程序
FALLING: 当引脚电平由高电平变为低电平时触发中断服务程序

返回值

示例

const byte ledPin = 13;
 
//用2号引脚作为中断触发引脚
const byte interruptPin = 2;  
 
volatile byte state = LOW;
 
void setup() {
  pinMode(ledPin, OUTPUT);
 
  //将中断触发引脚(2号引脚)设置为INPUT_PULLUP(输入上拉)模式
  pinMode(interruptPin, INPUT_PULLUP); 
 
  //设置中断触发程序
  attachInterrupt(digitalPinToInterrupt(interruptPin), blink, CHANGE);
}
 
void loop() {
  digitalWrite(ledPin, state);
}
 
//中断服务程序
void blink() {
  state = !state;
}


注意

  • 在中断服务程序中,不能使用delay()函数和millis()函数。因为他们无法在中断服务程序中正常工作。delayMicroseconds()可以在中断服务程序中正常工作。

  • 中断服务程序应尽量保持简单短小。否则可能会影响Arduino工作。

  • 中断服务程序中涉及的变量应声明为volatile类型。

  • 中断服务程序不能返回任何数值。所以应尽量在中断服务程序中使用全局变量。


全新AVR芯片ATMEGA328P烧录程序的教程

玩Arduino有几年了,断断续续的。最近开发一个项目,因为我本身不是电子专业,只是对UNO有些经验,所以先用各种模块搭建的系统进行了测试。
测试通过后,开始设计PCB打样,一开始使用的是和UNO一样的芯片进行设计,结果在打样的时候出了问题。

这是我第一次PCB打样,才知道淘宝打样都是10cm以内20元,超过10cm要额外计算,价格差出好几倍。然后我的设计原件比较多,小了板子空间放不下。
然后想起了Arduino mini,用的是tqfp封装的芯片,可以节省空间,这个芯片和UNO的唯一区别就是封装。
所以改板,重新画PCB,打样,采购元件,安装焊接,中间历经坎坷就不多说了。
本来以为一切就绪,结果出现了新问题,程序下载不进去!开始以为是焊接问题,但是按说我这手法不应该出问题啊,但是不管了,马上重新焊一片,问题依旧。

后来上网查询学习,总结知识点一:如何为全新AVR芯片烧录程序

1. Arduino不是指AVR单片机芯片,而是基于AVR单片机搭建的一个平台。
2. ATMEGA328P是AVR单片机的一个型号,常用的Ardunio UNO和Arduino Pro Mini都是基于这款芯片,当然也有基于其他型号芯片的其他版本Arduino。
3. AVR芯片本身只支持SPI方式烧录程序,新的芯片内不含任何程序。
4. 我们平时用的Arduino编程软件,一般情况下是通过串口烧录程序的。

后面两条看起来相悖,这是为什么呢?因为虽然说硬件只支持SPI方式烧录,但试想一下,如果芯片里已经有一个程序,可以通过串口接收数据,写入到芯片里,就实现了串口烧录。这就是Arduino使用的烧录方式,而这个程序,就是Bootloader。

刚才说了,新的AVR芯片不含任何程序,当然也不含Bootloader,所以我们像为UNO烧录程序一样为新的AVR芯片烧录,自然是不行。那怎么办?答案你已经想到了,就是先通过SPI方式为芯片烧录Bootloader程序。

SPI烧录程序有两种方式,一种是单独买一个烧录设备,学名叫做USBISP,这是一个通用硬件,可以为AVR、51等单片机下载程序,支持通用的烧录软件。但是Arduino用不了,因为Arduino对USBISP的协议做了稍稍的修改,将其命名为USBasp。USBISP可以通过刷固件改为USBasp,这里今天先不说这个,因为今天先不讲USBasp方式烧录。有人说你废了半天话,还卖关子。其实不是的,因为一个是要单独买硬件需要成本,更重要的是win10下没有USBasp驱动,虽然有解决办法但是比较复杂。不如下面说的第二种方式来的简单。

第二种方式,是通过把现有的Arduino模拟成USBISP,实现烧录。这种方式无需为驱动操心,因为你的UNO早就和你的电脑愉快的玩耍在一起了吧。

总结知识点二:通过现有Arduino板为新的AVR芯片烧录程序(烧录Bootloader)

首先连接好UNO,打开Arduino软件,选择文件-示例-ArduinoISP-ArduinoISP,这时软件自动打开了一个程序

   1.jpg    

选择在工具菜单中设置好开发板的型号UNO,以及软口号。
注意因为这个步骤是为你的UNO烧录程序,所以此处设置和平时烧录程序一样。如果你不是UNO就按你的版本设置。

   2.jpg    

然后点上传,和平时一样,为你的UNO烧录程序。
之后你的UNO就以为成为一块模拟USBISP了,按照arduino的说法就是Arduino as ISP。
现在接口定义为:10.RESET、11.MOSI、12.MISO、13.SCK,如果你不是UNO,那就查一下刚才打开这个程序的代码,看下具体定义。

   3.jpg    

现在准备为你新的AVR芯片烧录程序,连接好上面说的4条线路,另外不要忘了+5V和GND。
这里用到了TQFP32烧录器,一会交给大家做法。
然后注意软件设置!端口不变,还是你UNO的端口。开发板和处理器按照你的目标AVR芯片设置,和UNO无关!比如我的目标是328P,5V电压,16M晶振,这里就如图设置。


这里可能又有人要问了,为什么开发板选择的是Arduino Pro Mini?因为Pro Mini是328P芯片啊!刚才说了,Arduino是基于AVR搭建的平台,他卖的是平台不是芯片,所以这里不会提供给你芯片型号选项,而是提供Arduino版本选项,所以你选择和你使用同样芯片的版本就行了。

然后编程器选择Arduino as ISP,如果是使用USBasp,这里就选择USBasp。

   4.jpg    

然后到这里,一切都准备妥当了。之后有两条路,需要注意了。

如果你单纯想烧录程序,不想要Bootloader,可以按住shift再点上传,每次都用这种方式烧录程序就可以了。
如果你想以后和玩Arduino一样,用串口烧录和调试,点击菜单工具-烧录引导程序,这样就写进去Bootloader了,以后就可以用串口操作了。

如果烧录报错了就检查你的硬件,按照我经验如果连线没问题,一般都是晶振未起震。

总结知识点三:新的AVR芯片开光无需设置熔丝位!

新的AVR芯片开光无需设置熔丝位!新的AVR芯片开光无需设置熔丝位!重要的事情说三遍!

不知道什么时候开始,网上教程都说要为芯片设置熔丝位,那是低级的51的玩法好么!我们这么高级的Arduino,才不用设置熔丝位,你烧录Bootloader的时候,程序帮你一切都做好了。

总结知识点四:328P最小系统设计

   5.jpg    

直接看图吧,一共7个外围元件,图中接出了SPI口和串口,当然还有RESET和DTR。
这里要区别一下DTR和RESET,RESET是芯片本身的引脚,而DTR是通过电容接在RESET脚上的。
SPI刷机方式用的是RESET脚,而使用串口刷机方式使用的是DTR信号。

某些版本的USB转串口设备,比如我手上这块CP2102,本身已经集成了DTR电容,那么也可以直接使用RESET引脚。这样就省略了C5,只剩下6个元件。
如果使用村田3脚晶振,因为晶振内置了对地电容,所以C3C4就可以省略了,参照Pro Mini,这样就剩下4个元件。
也就是说保证功能和稳定的前提下,328P的最小系统只要4个元件!

总结知识点五:328P的烧录板制作

在大批量烧录的时候,我们不可能一个一个的去焊引脚,所以就需要一个烧录板,可以快速批量作业。
查了一下万能的淘宝,TQFP32座好贵,要60块钱!
这不是最重要的,重要的是竟然没有成品卖!好吧,我只能自己做一个了。

需要元件:TQFP32座,还有最小系统所需的几个元件。
然后就是手工活了,找了个洞洞板,按照上面电路图焊就行了。

正面样子
   6.jpg    

背面
   7.jpg    

插上烧录座
   8.jpg    

烧录座针脚特写
   9.jpg    

连接NUO的接线


连接USBasp的接线
   10.jpg    

连接CP2102的接线
   11.jpg    

好了完工,此帖到此结束。

最后因为强迫症,打算有时间画个专用的烧录座PCB,把元件烧录座都弄到一张板子上,现在这样好几层看着不舒服。
但是因为有这个能用了,再单画一个又有点不值,所以如果大家有需要的,我就画一个做几片,大家出点费用,不会超过100块钱,如果没有需要的就算了。


Arduino基础入门篇舵机控制

1. 根据原理图搭建电路。

线路连接非常简单,舵机红色线接开发板5V,棕色线接开发板GND,橙色信号线接开发板数字引脚9。

实验原理图如下图所示:

1.png
               
实验原理图

实物连接图如下图所示:

2.png
               
实物连接图

2. 新建sketch,拷贝如下代码替换自动生成的代码并进行保存。

#include <Servo.h>    


   

Servo myservo;  // 定义Servo对象来控制    

int pos = 0;    // 角度存储变量    


   

void setup() {    

  myservo.attach(9);  // 控制线连接数字9    

}    


   

void loop() {    

  for (pos = 0; pos <= 180; pos ++) { // 0°到180°    

    // in steps of 1 degree    

    myservo.write(pos);              // 舵机角度写入    

    delay(5);                       // 等待转动到指定角度    

  }    

  for (pos = 180; pos >= 0; pos --) { // 从180°到0°    

    myservo.write(pos);              // 舵机角度写入    

    delay(5);                       // 等待转动到指定角度    

  }    

}

实验现象

舵机在0°和180°间不断转动。

1.gif                    
实验现象

5. 库函数介绍

用Arduino控制舵机一般有两种方法:

  1. 通过Arduino的普通数字引脚产生占空比不同的方波,模拟产生PWM信号进行舵机控制。
  2. 直接利用Arduino自带的Servo库进行控制。

Servo库常用函数:

  • Attach(接口)—设定舵机接口。
  • Write(角度)—用于设定舵机旋转的角度,可设定范围0°—180°。
  • Read()—用于读取舵机角度的语句,可理解为读取最后一条write()命令中的值。

       

       

Ardunio定时器中断        

       

Ardunio在线函数手册        

Arduino的定时器中断        


       

使用定时器中断前,必须先安装MsTimer2库,并在程序中引用头文件 MsTimer2.h中。使用例程如下:        

#include <MsTimer2.h>     //定时器库的头文件        

         

int tick = 0; //计数值        

         

//中断服务程序        

void onTimer()        

{        

  Serial.print("timer ");        

  Serial.println(tick++);        

}        

         

void setup()        

{        

  Serial.begin(9600); //初始化串口        

          

  MsTimer2::set(1000, onTimer); //设置中断,每1000ms进入一次中断服务程序 onTimer()        

  MsTimer2::start(); //开始计时        

}        

         

void loop()        

{        

          

}        


       

程序运行效果:         

每隔1000毫秒,产生一次时钟中断,并进入中断服务程序 onTimer(), 向串口打印一串文字。        


       

在Arduino IDE中打开串口监视窗口,可见打印的文字        


       

timer 0        


       

timer 1        


       

timer 2        


       

...        


       


       

如果编译过程出现错误“error: 'MsTimer2' has not been declared”,多半是因为MsTimers库未安装。        


       

MsTimer2库的安装方法:        


       

1, 新建一个目录名为 MsTimer2,  在 https://github.com/PaulStoffregen/MsTimer2 下载全部文件, 存入目录中。        


       

2, 在Arduino IDE中,点菜单“Sketch -- Import Liberary -- Add Liberary”,选择MsTimer2目录后,点确定        


       


       

I2C通讯 ARDUINO的I2C通信详解        

       

I2C是非常普通的通信方式,Arduino单片机集成了I2C库,可以直接调用几个函数实现I2C通信。Arduino MEGA2560开发板I2C接口,在班上对应的20--SDA 21--SCL        

       

Arduino的I2C库是Wire.h。常用库函数如下:        

  Wire.beginTransmission(Addr);        

  Wire.write(data);        

  Wire.endTransmission(void);        

  Wire.requestFrom(Addr, num);        

  Wire.available();        

  Wire.read()。        

  每个数据发出后,都必须有应答,只有在等到应答后,才能发出下一个数据或者指令。        

  下面是一段代码:        

  Wire.beginTransmission(0x01);        

  Wire.write(0xAA);        

  如果0x01发出后I2C器件没有应答,那么0xAA就不能发送,这时候用示波器或者逻辑分析仪看,就只能看到发出了Start信号和数据0x01。        

  Wire.beginTransmission(Addr)只发送起始信号和I2C地址,Addr是I2C总线地址。        

  Wire.write(data)只发送数据,data就是希望发送的数据,而且是发一次,地址就自动加一。        

  Wire.endTransmission(void)发送停止信号。        

  Wire.requestFrom(Addr, num)向已知地址器件获取连续num个数据,这时候需要注意,数据只是存起来了,并没有真正返回。        

  Wire.read()就是用来读回前面那些存起来的数据,而且是读一次地址自动加一。        

  Wire.available()则返回已经存了多少个数据。        


       

Ardunio PWM频率库         

       

因之前使用过飞思卡尔及瑞萨16位单片机,在PWM模块上使用很顺手,也没有产生任何疑惑,反倒是Arduino输出的PWM固定频率让人很是恼火。试想你的电机在490hz的PWM控制下捏着鼻子嗡嗡叫,这是一件多么叫人忧伤的事呀。
     Arduino很好,但PWM很鸡肋。当我想要查datasheet来对底层操作时,无意发现了PWM frequency 库,感觉很好用,很省时间。那么,就翻出来造福大家喽。这是我第一个认真写并写完整的技术文档翻译,欢迎交流指正。
       

翻译自http://arduino.cc/forum/index.php?topic=117425.0        

       

   在目前与Arduino微控制器相关的项目中,我发现没有一种方法能不直接操作底层寄存器,而改变PWM频率的。就目前我所google到的,没有一种通用的库能改变arduino微控制器的PWM频率。网上有各种关于改变PWM频率的代码段,但是最终我还是决定参考400多页的ARV-Mega系列单片机的datasheet来实现这些功能。
       据我推测,Arduino的编程人员没有发行任何关于改变PWM频率的方法是因为很难编写一个简单的,直观的硬件定时器封装程序,而不至使初学者困惑。硬件本身就有一些特殊的局限性,它们以一些奇怪的方式表现出来。
请允许我与大家分享一些:
  • PWM的行为是由叫作定时器的集成元件决定的。每个定时器有2个或4个通道。每个通道连到一个控制器引脚上。改变一个引脚的频率需要改变它所连接的定时器的频率,这样反过来也会改变其它连到同一个定时器的引脚的频率。

                   
  • Timer0通常被用作实现Arduino的计时功能(例如,millis()函数)。改变timer0的频率将会破坏你工程里使用计时函数的其它程序段部分。

               
  • Arduino中有两种定时器,8位定时器和16位定时器。简单来说,就是它们所存在的细微差别使得不限制一方或另一方的代码实现变得困难。

               
  • 使用一个8位的定时器来设定定制的频率(使用预分频产生不了的频率)需要牺牲一个通道。换句话说,每个设定定制频率的8位定时器会失去在一个引脚产生pwm的能力(连接A通道的引脚会更准确些)。除了Leonardo的所有Arduino都有两个8位定时器,这意味着如果你把所有定时器设定特殊的频率,上述所说的Arduino控制器总共将会牺牲两个引脚。

               
         先不管这些,我依然觉得对硬件定时器做一个库或封装是非常值得的,这样我或者任何其他选择使用这个库的人不必花那么多时间去深挖那些容易产生bug的逐位操作的模块和初始化部分。
       
这个库有五个全局函数:
InitTimers()                                                                        初始化所有定时器。需要在改变定时器频率或设定一个引脚的占空比之前用。
InitTimersSafe()                                                                除了为了保留计时功能函数而不初始化timer0外,其它都与InitTimers()一样。
pwmWrite(uint8_tpin,uint8_t val)                                与‘analogWrite()’一样,但是只有在相应定时器初始化后才工作
SetPinFrequency(int8_tpin,int32_t frequency)          设定引脚的频率(hz),返回一个设定成功与否的布尔值
SetPinFrequencySafe(int8_tpin,int32_tfrequency)    除了不影响timer0外,其它都与SetPinFrequency函数一样
       
         这个库针对每个定时器还有五个函数。我没有把代码大小减小到一个我认为合理的程度,而是编写了C++类,并做了大量细致的封装工作。这里的每一个函数在都用明显专门的宏定义在编译前调出那些库的头文件里隐晦的函数。
       
对timer1而言,这些函数是:
Timer1_GetFrequency()                                读取定时器的频率(hz)
Timer1_SetFrequency(intfrequency)          设定定时器的频率
Timer1_GetPrescaler()                                  读取预分频因子
Timer1_SetPrescaler(enumvalue)               设定预分频因子
Timer1_GetTop()                                            读取定时器寄存器最大值
Timer1_SetTop(inttop)                                 设定定时器寄存器最大值
Timer1_Initialize()                                          初始化定时器

         预分频因子在不同的定时器中是不一致的。我认为使用枚举数是最好的解决方法,因为大多数无效的类型输入将会在编译的时候被发现。例如一个普通的定时器,使用下面其中一个作为参数:ps_1,ps_8,ps_64,ps_256,ps_1024.如果这些都报类型错误,那么你所使用的定时器就是与其它定时器不一样的一个例子,你应该用Psalt_1,psalt_8,psalt_32,psalt_64,psalt_128,psalt_256,或者psalt_1024代替。如果你想使用不同的定时器,只要改变序号即可(例如,Timer2_GetFrequency()读取定时器2的频率)。这取决于你是否要使用定时器的特殊功能。全局函数对大多数情况下已经够用了。使用这个库,16位的定时器的频率可以从1hz变化到8Mhz,8位定时器的频率可以从31hz变化到2Mhz。但是随着频率的越来越大,占空比可变化的范围就越小。将频率设定为8Mhz是可以实现的,但是这样占空比的变化范围将变得非常小。请确定定时器的频率正确的设置了,并检查这个函数的返回值。如果你想要牺牲任何8位的PWM引脚,就不要调用定时器对应的初始化函数,试着改变预分频因子来改变它的频率。有许多教程解释预分频因子怎么影响定时器的,这个库包函这些方法,并能使其变得更简单,在处理过程中更不容易产生bug。到目前为止,我已经在UNO和Mega上测试过了。这个库能兼容除了Leonardo和Due的任何arduino控制器。如果你拥有的arduino控制器不是Mega或者Uno,请在上面测试并把运行状况告诉我。如果你们手上有示波器,试着变换它生成的频率,显示正确,则说明这个库是可以用的。

现在,这个库正在测试中。这个库的发展将在后面的帖子里描述。
下面是目前这个库的一些特征:

1.使用函数封装了定时器的特殊功能。(例如定时器寄存器的最大值和预分频因子)
2.基于引脚(与定时器无关)的函数。
3.拥有在定时器级或引脚级读取和设定频率的函数。
4.拥有定时器级和引脚级的定时器分辨率测量工具。

最新库是的05版本:
链接:http://code.google.com/p/arduino-pwm-frequency-library/downloads/list

下面是跟帖中比较有用的部分总结:
硬件上,Mega系统控制板,11引脚连着timer1,引脚9连接timer2, 引脚7连接timer4。这是软件改变不了的。

关于分辨率的问题:
8位的定时器兼容8位的分辨率,16位的定时器兼容16位的分辨率。为了与analogWrite()保持一致性,pwmWrite()函数都使用8位分辨率。如何需要更高的分辨率时,使用pwmWriteHR()代替。

Void pwmWrite(uint8_t pin, uint8_t duty) 8-bit, 0-255
Void pwmWriteHR(uint8_t pin, uint16_t duty) 16-bit 0-65535

不幸的是,一旦使用了定制的频率,分辨率控制将不会那么简单。如果你修改定时器的频率,分辨将跟着变化。经验法则是频率越高,分辨率越低。有一些变量在SetFrequency函数里封装了。函数知道他们,并在给定的频率下精确的使用可能的最高的分辨率。尽管pwmWriteHR()接受16位的整数,它将自动映射到定时器任一分辨率下(也就是说它的分辨率不一定是16位的)。为了知道在特定频率下分辨率是否小于界限值,我添加了两个函数。

Float  GetPinResolution(uint8_t pin)
Float  TimerX_GetResolution() (用定时器号来替换X)

这些函数能读出分辨率,以二进制的位数来表示分辨率。请注意这两个函数都返回浮点数,而不是整数,那是我故意这么做的。如果你更喜欢用最大可能值而非二进制数来表示的话,使用TimerX_GetTop()然后加1(其实质是一样的,只不过是十进制的)。
我在工程中添加了一个叫做PWM_lib_resolution_example的例程,来演示这些函数和定时器频率、分辨率之间的关系。

#include <PWM.h>

//use pin 11 on the Mega instead, otherwise there is a frequency cap at 31 Hz
int led = 7;                // the pin that the LED is attached to
int brightness = 0;         // how bright the LED is
int fadeAmount = 5;         // how many points to fade the LED by
int32_t frequency = 20000; //frequency (in Hz)

void setup()
{
  //initialize all timers except for 0, to save time keeping functions
  InitTimersSafe();

  //sets the frequency for the specified pin
  bool success = SetPinFrequencySafe(led, frequency);
 
  //if the pin frequency was set successfully, pin 13 turn on
  if(success) {
    pinMode(13, OUTPUT);
    digitalWrite(13, HIGH);   
  }
}

void loop()
{
  //use this functions instead of analogWrite on 'initialized' pins
  pwmWrite(led, brightness);

  brightness = brightness + fadeAmount;

  if (brightness == 0 || brightness == 255) {
    fadeAmount = -fadeAmount ;
  }     
 
  delay(30);     
}
       



       

PWM原理、频率与占空比        

       

pwm的频率        

是指1秒钟内信号从高电平到低电平再回到高电平的次数(一个周期);        

占空比        

是一个脉冲周期内,高电平的时间与整个周期时间的比例        

       

Arduino使用watchdog方法
       

       

bug修补者-watchdog

最近在自己的一个Arduino项目中发现了一个很奇怪的异常,系统开机一段时间后出现死机,死机出现的时间和触发原因看起来似乎没有任何规律,检查程序也找不到任何错误,同时因为死机出现的随机性,也很难进行调试。问题可能是程序中的一些隐藏bug,或者与硬件有关,但无论如何,一直没有找到确切原因。因此在其中加入watchdog作为一种修补,至少每次出现异常后系统可以自动复位总比一直卡在那好。


Arduino的WDT

Arduino的语法参考中没有包括WDT,但相关开发板的atmega系列单片机的数据手册均显示是支持wdt的,同时因为Arduino IDE本身是基于AVR GCC编译器,因此可以直接在Arduino IDE中使用AVR的原生语句与函数库。需要包含头文件#include <avr/wdt.h>。可以参考avr的详细库函数说明                

avr的WDT 的基本功能语句有三个:

  • WDT使能定时间隔设置wdt_enable(),参数可以从几十ms到几s之间调整,当超过这个时间而单片机又没有复位WDT定时器时,单片机就会进行复位操作,一般来说不要设置过低的时间间隔。
  • WDT禁用语句wdt_disable(),为防止再次烧写程序时复位后反复进入
    wdt复位,需要在初始化程序的最开始位置禁用wdt,这在wdt定时间隔设置过短时尤为重要,否则可能会导致无法再次烧写。
  • WDT定时器复位语句wdt_reset(),如果在WDT的定时间隔内没有使用该语句复位定时器,则WDT会自动复位单片机,该语句的位置根据具体程序的耗时决定,可能需要多处加入。

下面给出了一个wdt的测试程序,主循环内的led闪烁频率会越来越慢,当闪烁间隔超过wdt定时后,会触发wdt复位,在setup()中led快速闪烁三次以标志进入复位。

#include <avr/wdt.h>                

const int onboardLED = 13;                 


               

void setup() {                

 wdt_disable();  //disable wdt at the first of code to prevent it always reboot                


               

  //the WDT time can be choose from                

  //WDTO_25MS  WDTO_30MS  WDTO_60MS  WDTO_250MS  WDTO_500MS                

  //WDTO_1S  WDTO_3S  WDTO_4S  WDTO_8S                

  //be very carfully when use too short time value because  most avr's WDT still on work even after reboot                

  wdt_enable(WDTO_4S);                


               

  pinMode(onboardLED, OUTPUT);                


               

  for (int k = 1; k <= 10; k = k + 1) {                

    digitalWrite(onboardLED, HIGH);                

    delay(50);                

    digitalWrite(onboardLED, LOW);                

    delay(50);                

  }                

  delay(750L);                

}                


               

void loop() {                

  for (int k = 1; k <= 10000; k = k + 1) {                

     wdt_reset();  //reset the watchdog timer every loop to prevent reboot                

    digitalWrite(onboardLED, HIGH);                

    delay(k * 250L);                

    digitalWrite(onboardLED, LOW);                

    delay(250L);                

  }                

}                

bootloader 与WDT冲突解决
               

用上面的代码实际测试中发现当自己用的arduino产生触发wdt复位的条件时,单片机并没有复位而是直接卡死,指示灯快速闪烁或者无反应。且再次烧写程序时出现了问题,强制上电复位才成功。查阅了一些资料后可能原因在于旧版本的arduino的bootloader与wdt间有冲突。
我拿来一块新买的mega2560测试了同样的程序,发现果然可以正常使用wdt。于是我又用avr的usb asp烧写器烧写了新版本的bootloader进我的旧的arduino中,结果显示mega2560使用新的bootloader后可以正常工作,但pro mini仍然无法正常工作。关于如何烧写bootloader已经超过了本篇的范围,请参考我的另外一篇博客Arduino烧写bootloader                



           

           

动手学arduino用mos管驱动马达            

市场上的小风扇很受小朋友们喜欢,你是否也想做一款属于自己的迷你风扇?

你需要准备一片arduino,一个小电机,扇叶,支架等。

本文只介绍核心控制部分,外壳安装省略。

1.jpeg        

Arduino开发板上的每一个引脚只能提供10~20mA的电流,仅能驱动一个LED灯。如果你要用它驱动一个直流电机,最后的结果也许是电机不转,或许把你的芯片烧坏,都是有可能的。为了驱动大功率负载,我们就必须借助一些其他的电子元器件,这就是在硬件领域最重要的开关管MOSFET晶体管,它有三个引脚,arduino的引脚连G极,drain接马达负极、Source接地,马达接VCC。我们给G极高电平,MOSFET就会导通,以此达到小电流控制大电流的目的。

2.jpg        

注意:arduino的GND要与外接电源的负极相连!

建议:G极与地之间接一个数百K的电阻。

在这里,MOS管就像一个开关,控制电机的功率。Arduino引脚2用于打开和关闭MOS管.

在实验中,可以用’NPN型BJT’代替NMOS管,但在改变管脚接线。


       

警告 – 不要直接从Arduino板引脚驱动电机。这可能会损坏电路板。


       

淘宝上有做好的mos模块,如下图:

3.jpeg
各个引脚
4.png
左上角 V+ V- 是输出的正负。右上角 Vin 和 GND 是输入的电源的正负极。V+ 和Vin 是通的,右上角的GND和下面的GND是通的。下面一排分别是 SIG (控制信号),VCC (控制信号电源),GND(控制信号的地)。
为了验证MOS所以设计了一个实验,用Arduino的PWM输出来控制 SIG,然后达到控制灯泡亮度的目的。
 

改变电机的转速——PWM        

PWM(脉冲宽度调制)是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。
 

简单说来,arduino各引脚通常只有0(0伏)和1(5伏)两个状态,要让引脚输出0.4(2伏),怎么办?pwm就有办到。
 

在arduino中,用pwm驱动LED的暗亮程度,电机的转速等。
 

在仿真电路中,在10脚接一个LED电路,并联一个电压表,给一段让10脚电压从0-5V来回变化(LED呼吸灯)效果的代码。

代码:

仿真截图一:

5.jpg        

仿真截图二:

6.jpg        


       

驱动电机:mos的G极接arduino的3脚。

arduino板上画有“~”的引脚表示可以输出pwm。Arduino UNO 控制器的数字引脚中,3,5,6,9,10,11六个可以输出PWM信号。        

7.jpg        

代码:

8.png        

Arduino Uno输出自定义频率的PWM

Arduino里的PWM

首先,Arduino Uno的5,6,9,10,3,11接口可以通过简单语句analogWrite(pin, dutyCycle)来实现一个指定占空比的PWM。其中pin的值选择(5,6,9,10,3,11),dutyCycle的值在0~255之间,0为占空比0%,255为占空比100%。但是这种方式PWM信号的频率是固定的默认值,大约1000Hz左右(16MHz/64/256)。


       

其次,手动切换高电平和低电平,再在中间加入delay函数,可以实现自定义频率的PWM:


       

void setup()

{

  pinMode(13, OUTPUT);

}


       

void loop()

{

  digitalWrite(13, HIGH);

  delayMicroseconds(100); // Approximately 10% duty cycle @ 1KHz

  digitalWrite(13, LOW);

  delayMicroseconds(1000 - 100); //修改这里的1000可以调整频率

}


       

这个例子中:一个循环是1000us = 1ms,所以一秒循环1000次,因此Frequency是1KHz,

每个循环中:有电的比率是100/1000 * 100% = 10%,所以duty cycle(占空比)为10%

这样就可以模拟出5V * 10%=0.5V的电压

好处是任何一个引脚都能通过这样输出PWM,

但是,这种操作需要CPU全神贯注的查数,任何其他的进程的干扰会导致输出的信号频率不准。


       

综上,需要底层的手段来控制Arduino实现PWM的频率调节。

调节Arduino里的时钟频率

Arduino Uno里有三个Timer:Timer0,Timer1,Timer2。 三个Timer都可以自定义调整频率,但是各有特点。Timer0负责控制delay等函数,动了Timer0的频率会导致计时函数不准;Timer1的计数器是16位的,和Timer0,Timer2的8位计数器不太一样;Timer2的频率可调的档位更多,因为它有7档预除数,下文会进一步解释。这里选择Timer2进行调节操作,先上代码:


       

void setup() {

  // put your setup code here, to run once:


       

  pinMode(3, OUTPUT); 

  pinMode(11, OUTPUT); 

  

  TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); //Set Timer2 to varying top limit fast PWM mode

  TCCR2B = _BV(WGM22) | _BV(CS22) | _BV(CS21) | _BV(CS20);//another way to set prescaler CS2=fff

  

  OCR2A = 155; //Top value A

  OCR2B = 30; //Toggle value B, Output at pin 3

 

  //CS2  Divisor  Frequency

  //001    1        31372.55

  //010    8        3921.16

  //011    32       980.39

  //100    64       490.20   <--DEFAULT

  //101    128      245.10

  //110    256      122.55

  //111    1024     30.64

}


       

void loop() {

  // put your main code here, to run repeatedly:


       

}

由以上代码可见,需要的设置分为三个部分:pinMode,TCCR2A/B,OCR2A/B(这里的2是因为选择了Timer2)


       


       

Arduino 鼠标按钮控制 模拟为鼠标        

使用鼠标库,你可以使用Arduino Leonardo,Micro或Due来控制计算机的屏幕光标。        

这个特殊的例子使用五个按钮来移动屏幕上的光标。四个按钮是方向性的(上,下,左,右),一个是用于鼠标左键单击。来自Arduino的光标移动总是相对的。每次读取输入时,光标的位置都会相对于当前位置进行更新。        

只要有一个方向按钮被按下,Arduino就会移动鼠标,在合适的方向上将HIGH输入映射到5的范围。        

第五个按钮用于控制来自鼠标的左键单击。当按钮被释放时,计算机将识别事件。        

必需的组件        

你将需要以下组件:        

  • 1 × Breadboard 面包板
  • 1 × Arduino Leonardo, Micro 或 Due板
  • 5 × 10k欧姆电阻
  • 5 × 瞬时按钮

程序        

按照电路图连接面包板上的组件,如下图所示。        

1.jpg        

草图        

在计算机上打开Arduino IDE软件。使用Arduino语言进行编码控制你的电路。通过单击“New”打开一个新的草图文件。        

对于本例,你需要使用Arduino IDE 1.6.7        

2.jpg        

Arduino代码        

/*
   Button Mouse Control
   For Leonardo and Due boards only .Controls the mouse from 
   five pushbuttons on an Arduino Leonardo, Micro or Due.
   Hardware:
   * 5 pushbuttons attached to D2, D3, D4, D5, D6
   The mouse movement is always relative. This sketch reads
   four pushbuttons, and uses them to set the movement of the mouse.
   WARNING: When you use the Mouse.move() command, the Arduino takes
   over your mouse! Make sure you have control before you use the mouse commands.
*/

#include "Mouse.h"
// set pin numbers for the five buttons:
const int upButton = 2;
const int downButton = 3;
const int leftButton = 4;
const int rightButton = 5;
const int mouseButton = 6;
int range = 5; // output range of X or Y movement; affects movement speed
int responseDelay = 10; // response delay of the mouse, in ms

void setup() {
   // initialize the buttons' inputs:
   pinMode(upButton, INPUT);
   pinMode(downButton, INPUT);
   pinMode(leftButton, INPUT);
   pinMode(rightButton, INPUT);
   pinMode(mouseButton, INPUT);
   // initialize mouse control:
   Mouse.begin();
}

void loop() {
   // read the buttons:
   int upState = digitalRead(upButton);
   int downState = digitalRead(downButton);
   int rightState = digitalRead(rightButton);
   int leftState = digitalRead(leftButton);
   int clickState = digitalRead(mouseButton);
   // calculate the movement distance based on the button states:
   int xDistance = (leftState - rightState) * range;
   int yDistance = (upState - downState) * range;
   // if X or Y is non-zero, move:
   if ((xDistance != 0) || (yDistance != 0)) {
      Mouse.move(xDistance, yDistance, 0);
   }

   // if the mouse button is pressed:
   if (clickState == HIGH) {
      // if the mouse is not pressed, press it:
      if (!Mouse.isPressed(MOUSE_LEFT)) {
         Mouse.press(MOUSE_LEFT);
      }
   } else {                           // else the mouse button is not pressed:
      // if the mouse is pressed, release it:
      if (Mouse.isPressed(MOUSE_LEFT)) {
         Mouse.release(MOUSE_LEFT);
      }
   }
   // a delay so the mouse does not move too fast:
   delay(responseDelay);
}
       

代码说明        

使用micro-USB线将电路板连接到计算机。按钮连接到引脚2至6的数字输入。确保使用10k下拉电阻。        


       


       


       

Arduino 键盘消息 Arduino模拟为键盘        

在此示例中,当按下按钮时,文本字符串作为键盘输入发送到计算机。字符串报告按钮被按下的次数。一旦你完成了Leonardo版的程序化和接线,打开你最喜欢的文本编辑器来查看结果。        

警告 - 当你使用 Keyboard.print()命令时,Arduino将接管你的计算机键盘。为确保在使用此功能运行草图时不会失去对计算机的控制,请在调用 Keyboard.print()之前设置可靠的控制系统。这个草图包括一个按钮来切换键盘,以便它只在按下按钮后运行。        

必需的组件        

你将需要以下组件:        

  • 1 × Breadboard 面包板
  • 1 × Arduino Leonardo, Micro, 或Due板
  • 1 × 瞬时按钮
  • 1 × 10k欧姆电阻

程序        

按照电路图连接面包板上的组件,如下图所示。        

1.jpg        

草图        

在计算机上打开Arduino IDE软件。使用Arduino语言进行编码控制你的电路。通过单击“New”打开一个新的草图文件。        

2.jpg        

Arduino代码        

/*
   Keyboard Message test For the Arduino Leonardo and Micro,
      Sends a text string when a button is pressed.
   The circuit:
   * pushbutton attached from pin 4 to +5V
   * 10-kilohm resistor attached from pin 4 to ground
*/

#include "Keyboard.h"
const int buttonPin = 4; // input pin for pushbutton
int previousButtonState = HIGH; // for checking the state of a pushButton
int counter = 0; // button push counter

void setup() {
   pinMode(buttonPin, INPUT); // make the pushButton pin an input:
   Keyboard.begin(); // initialize control over the keyboard:
}

void loop() {
   int buttonState = digitalRead(buttonPin); // read the pushbutton:
   if ((buttonState != previousButtonState)&& (buttonState == HIGH)) // and it's currently pressed: {
      // increment the button counter
      counter++;
      // type out a message
      Keyboard.print("You pressed the button ");
      Keyboard.print(counter);
      Keyboard.println(" times.");
   }
   // save the current button state for comparison next time:
   previousButtonState = buttonState;
}
       

代码说明        

将按钮的一个端子连接到Arduino上的引脚4。将另一个引脚连接到5V。使用电阻作为下拉电阻,通过将其从引脚4接地来提供接地参考。        

一旦你程序化了电路板,拔下USB电缆,打开一个文本编辑器并将文本光标放在打字区域。再次通过USB将电路板连接到计算机,然后按按钮在文档中写入。        

结果        

通过使用任意文本编辑器,将显示通过Arduino发送的文本。        


       


       

动手学arduino两款智能灯        

智能设备,就是能省时节能。

今天介绍两种通知灯:

一、光控灯

自动控制的路灯,天暗了,路灯自动打开,天亮了,路灯自动熄灭。

下面就介绍这种自动控制的LED灯。

这个项目中要用到一个新元件——光敏电阻或光敏模块(两者一样)。在黑暗的环境中,光敏电阻阻值1兆欧姆以下。光线越强,电阻值反而越低,强光时只有几百到几千欧姆。通过读取这个电阻值,就可以检查光线的亮暗了。我们这里选用的是光敏模块。

1.jpg        

二、人体感应灯

在公寓的楼道上,有声控灯,有触摸灯,还有一种更智能的人体感应灯。这种灯能感应到三米左右人体信号,自动开灯,延时几秒后熄灭。

电路原理如下,把光敏模块更换为红外人体感应模块就可以,注意该模块三个引脚从左到右的顺序为:输出、VCC、GND,不需要限流电阻。

1.jpg        

代码只要加一个延时命令即可。


       

动手学arduino程序控制LED        

       

如果有arduino来做,就太简单了,就用上一次的电路

1.jpg        

只需在原代码的基础上增加三句就行:(本例中LED正极接4脚,代码相应的改成4)


       

Arduino 烧写bootloader        

       

什么是bootloader

一般情况下微处理器写入程序时都通过专门的编程器进行烧写,但是也可以通过在MCU中预先写入一些程序来实现某些基本功能,这些预先写入的程序代码就是bootloader。这样每次复位时MCU就不是从程序存储器起始位置开始读取命令,而是先加载bootloader,实现一些设置和功能,再开始执行代码。
arduino通过预先写入的bootloader,每次通过串口给arduino板下载程序时,首先会令arduino板复位一次,这样就先进入了bootloader,接着向arduino板发送特定的下载指令,bootloader识别到这些指令后就会紧接着开始接收剩下的新的程序代码写入到内部flash中,如果没有进行下载,bootloader没有识别到下载指令,那么它就会跳转到程序区正常工作。

简而言之,Arduino上的bootloader就是能让你用一根usb线就可以愉快下载程序的原因所在

为什么要自己烧写bootloader

有同学可能要问了,既然bootloader已经这么写好在Arduino板里了,那为什么我们还要自己烧写呢,这不是我们喜欢折腾,而是因为:

  • 自己搭建一块Arduino板,如果想自己做一块arduino板或者想将其嵌入到PCB项目里,而新出厂的单片机里是没有bootloader的,这时就需要自己烧写。
  • 某些罕见情况下旧的bootloader会导致一些bug,如我遇到过旧版本bootloader无法使用watchdog的问题,这个时候写入新版本bootloader可能会解决问题(至少watchdog冲突的问题验证过是可以这样解决的)

硬件接口

烧写bootloader本质上只是使用编程器烧写一段特殊的程序到单片机中,因此其方法与使用编程器烧写程序是一样的,这个方法在不同厂商、不同种类的单片机上都是不同的。而对于arduino一般的开发板使用的avr芯片来说,烧写程序是通过ICSP(in-circuit serial programmming)硬件接口来实现的

1.png
               
uno r3上的ICSP接口

如上图所示,arduino uno上有两个ICSP接口,分别对应其中atmega32u4(左上用于usb转串口)和atmega328(右侧用于正常执行指令),一般来说我们只需要对右侧的主处理器烧写bootloader即可。对于如pro mini等来说,片上则只有一个单片机需要烧写。



MISOVCC
SCKMOSI
ResetGND

ICSP接口有6个引脚,其定义如上表所示,其数字顺序从左到右,从上到下,左上为1。其中可以分为三个部分:

  • SPI接口 MISO MOSI SCK            
  • 电源 VCC GND            
  • 复位 Reset            

有些arduino板中不仅引出了ICSP接口,同时其中的SPI接口部分的引脚也可以复用作为普通IO使用,包含在了普通的IO口排座中,而像pro mini等则没有专门设置ICSP接口排针,但所有引脚均引出。

arduino boardMOSIMISOSCK
uno\pro mini111213
mega1280\mega2560515052
arduino 101111213

上表是部分arduino板上IO口与ICSP引脚的对应关系。

烧写过程

烧写bootloader可以使用多种编程器,打开arduino IDE的Tools -> Programmer下显示了所有支持的编程器列表,这里推荐使用Arduino as ISP方式,较为简单而且成功率高。
Arduino as ISP简单来说就是用另一块arduino板,给它用普通方式下载一段专门的程序,将其变为一个ISP下载器,再将其相应引脚与待烧写bootloader的板子相连,再通过arduino IDE来控制进行bootloader烧写。这里斜体字显示了分为三个步骤。

  • 专门的程序
    打开IDE,Files -> Examples -> ArduinoISP -> ArduinoISP,使用这个程序下载到一块arduino板中,我们暂且称这块板为ISP板。
  • ISP板与目标板连接
    由前面介绍可知,这里应该是将ISP板的引脚连接到目标板上的ICSP接口上去,连接对应表如下(以使用UNO作为ISP板为例)
ISP板目标板
MISO / 12MISO / ICSP-1
Vcc / 5VVcc / ICSP-2
SCK / 13SCK / ICSP-3
MOSI / 11MOSI / ICSP-4
10Reset / ICSP-5
GNDGND / ICSP-6

实际连接电路如下所示,其中左边是ISP板,连接到电脑上,右边是等待下载bootloader的目标板

2.png
               
ISP板与目标板的硬件连接图
  • 使用arduino IDE进行bootloader烧写
    按照前面的方式连接好ISP板和目标板后,将ISP板连接到电脑上,打开IDE,首先按住ctrl + shirft + M 打开串口调试窗口,然后点击Tools -> Boards选择目标板的类型。然后在Tools -> Programmer中选择Arduino as ISP,最后点击Tools -> Burn Bootloader,此时会开始进行烧写,同时串口调试窗口里会有相关提示和确认,按照其说明输入相应按键即可完成


作者:cheng3100
链接:https://www.jianshu.com/p/2f274f8b3dab
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


       

       

SPI通讯接口
       

       

SPI模块是MOSI接MOSI MISO接MISO不是交叉连接不同于串口UART上的TXD和RXD交叉连接 ,SS片选, SS低电平选择上通讯芯片        

先简单说一下,关于SPI中一些常见的说法:        

SPI的极性Polarity和相位Phase,最常见的写法是CPOLCPHA,不过也有一些其他写法,简单总结如下:        

(1) CKPOL (Clock Polarity) = CPOL = POL = Polarity = (时钟)极性        

(2) CKPHA (Clock Phase)   = CPHA = PHA = Phase = (时钟)相位        

(3) SCK=SCLK=SPI的时钟        


       

(4) Edge=边沿,即时钟电平变化的时刻,即上升沿(rising edge)或者下降沿(falling edge)

对于一个时钟周期内,有两个edge,分别称为:        

Leading edge=前一个边沿=第一个边沿,对于开始电压是1,那么就是1变成0的时候,对于开始电压是0,那么就是0变成1的时候;        

Trailing edge=后一个边沿=第二个边沿,对于开始电压是1,那么就是0变成1的时候(即在第一次1变成0之后,才可能有后面的0变成1),对于开始电压是0,那么就是1变成0的时候;        


       

本文采用如下用法:        

极性=CPOL

相位=CPHA

SCLK=时钟        

第一个边沿和第二个边沿        


       

SPI的相位和极性】        

CPOLCPHA,分别都可以是0或时1,对应的四种组合就是:        

       


       

Mode 0CPOL=0, CPHA=0
Mode 1CPOL=0, CPHA=1
Mode 2CPOL=1, CPHA=0
Mode 3CPOL=1, CPHA=1


       

 CPOL极性】        

先说什么是SCLK时钟的空闲时刻,其就是当SCLK在数发送8bit比特数据之前和之后的状态,于此对应的,SCLK在发送数据的时候,就是正常的工作的时候,有效active的时刻了。        


       

先说英文,其精简解释为:Clock Polarity = IDLE state of SCK        

再用中文详解:        

SPICPOL,表示当SCLK空闲idle的时候,其电平的值是低电平0还是高电平1        

CPOL=0,时钟空闲idle时候的电平是低电平,所以当SCLK有效的时候,就是高电平,就是所谓的active-high        

CPOL=1,时钟空闲idle时候的电平是高电平,所以当SCLK有效的时候,就是低电平,就是所谓的active-low        


       

 CPHA相位】        

首先说明一点,capture strobe = latch = read = sample,都是表示数据采样,数据有效的时刻。        

相位,对应着数据采样是在第几个边沿(edge),是第一个边沿还是第二个边沿,0对应着第一个边沿,1对应着第二个边沿。        

对于:        

CPHA=0,表示第一个边沿:        

对于CPOL=0idle时候的是低电平,第一个边沿就是从低变到高,所以是上升沿;        

对于CPOL=1idle时候的是高电平,第一个边沿就是从高变到低,所以是下降沿;        

CPHA=1,表示第二个边沿:        

对于CPOL=0idle时候的是低电平,第二个边沿就是从高变到低,所以是下降沿;        

对于CPOL=1idle时候的是高电平,第一个边沿就是从低变到高,所以是上升沿;        

1.png        

取数据就是通讯        

上升沿取数据 CPOL=0 CPHA=0 ,CPOL=1 CPHA=1        

8位 16位的数据帧格式,可设置MBS/LBS先行        

       

MSB LSB:起始地址为最高位, 最后地址为最低位。

LSB MSB:起始地址为最低位,最后地址为最高位。

发送数据接收数据实际就是操作相应寄存器
控制寄存器(SPCR)         

数据寄存器(SPDR)        

状态寄存器(SPSR)        

1.png        

MOSI 实际Master out Slaver in

MISO 实际Master in Slaver  out


       

1.png        


       

1.png        

1.png        

1.png        


       

1.png        


       

1.png        

1.png        

1.png        


       

Arduino IDE 离线安装自定义开发板 / 附加开发板下载时出错 解决方法

在开发板管理器进行开发板安装的时候,出现了下载出错:

1.png


解决方法

第一步:将附加开发板管理器网址复制到浏览器进行访问,我们会访问到一个JSON文件:

1.png


这是一个对附加开发板进行描述的文件,右击进行保存,注意不要重命名。


第二步:打开上面的下载JSON文件:

复制 url 后面的链接到浏览器访问,浏览器会自动将压缩包下载,这是安装附加开发板所需要的库

1.png

到这一步我们会得到两个文件:

1.png

一个是附加开发板的库,一个是开发板的描述文件。


第三步:打开 HFS软件,将我们下载好的开发板库的压缩文件导入:

1.png

然后我们复制上面的文件的链接地址。


第四步:打开上面下载好的JSON文件,将 url 后面的地址替换成我们上一步复制的那个链接地址,然后保存:

1.png

第五步:将这个JSON文件也导入到 HFS软件里,并且复制它对应的链接地址:

1.png

这个时候我们需要把 HFS软件挂到后台,切记一定不要关闭。


第六步:打开Arduino ,将上一步复制的JSON链接地址,填入到 附加开发版管理器网址 这个地方:

1.png

保存,然后打开开发板管理器就可以看到自己想要的开发板了

1.png


然后点击安装即可。

1.png

安装成功,开发板里也有对应的开发板选项了:

1.png

1.png



自定义板子安装后的位置

比如:Heltec-esp32板子

C:\Users\Administrator\AppData\Local\Arduino15\packages\Heltec-esp32\hardware\esp32\0.0.5

Heltec-esp32.rar位于位于C:\Users\Administrator\AppData\Local\Arduino15\packages\




wokwi和tinkercad两个arduino仿真平台

https://wokwi.com/

https://www.tinkercad.com/


       


       


       



       


       


       



       


       


       


   


   










































































Top