前言
含星号*的不太常用。阅读前最好有一点基础知识,可以看个视频入门一下。本文参考书为Arduino Cookbook(Michael Margolis 等著)中译本《Arduino权威指南》。
Arduino是开放的电子开发平台,包含硬件和软件。 常用的一种硬件Arduino UNO如下图:
记: 左边5V输出、GND引脚, 0-5模拟引脚 右边USB接口 GND 13-0数字引脚(3 5 6 9 10 11支持PWM)
模拟引脚带ADC功能,10位精度,analogRead(pin),可将0~5V的电压转换为0 ~1023
PWM:脉冲宽度调制。analogWrite(pin,value); value值改变的是PWM的脉冲宽度,范围为0~255
一. 原型技术中的编程基础(Arduino C)
Arduino中的程序长这样:
const int ledPin=13;
void setup()
{
pinMode(ledPin,OUTPUT);
}
void loop()
{
digitalWrite(ledPin,HIGH);
delay(1000);
digitalWrite(ledPin,LOW);
delay(1000);
}
a. 变量和数据类型
数据类型 | 字节 | 范围 | 使用 |
---|
int | 2 | -32768~36767 | 整数值 | unsigned int | 2 | 0~65535 | 正整数值 | long | 4 | -2^16~ 2 ^16-1 | 大范围整数 | unsigned long | 4 | 0~2^32-1 | 大范围正整数 | float | 4 | 3.4028235E+38~-3.4028235E+38 | 带小数部分的数字,7位有效十进制数字(32位存储巨大范围内的所有值,8位用于小数位置(指数),剩下24位留给符号和数值) | double | 4 | 同float | 在arduino里只是float的另一个名称 | boolean | 1 | false(0)或true(1) | 代表true或false值 | char | 1 | -128~127 | 代表单个字符,也可以代表范围内的有符号值 | Byte | 1 | 0~255 | 类似char,但无符号值 |
其他: string:char阵列(字符),通常用来包含文本 void:仅用在无返回值的函数声明
浮点型变量
boolean almostEqual(float a, float b);
float value=1.1;
void setup()
{
Serial.begin(9600);
}
void loop()
{
value=value-0.1;
if(value==0)
Serial.println("the value is exactly zero");
else if(almostEqual(value,0))
{
Serial.print("The value");
Serial.print(value,7);
Serial.println("is almost equal to zero");
}
else
Serial.println(value);
delay(100);
}
boolean almostEqual(float a, float b)
{
const float DELTA=.00001;
if (a==0) return fabs(b) <=DELTA;
if (b==0) return fabs(a) <=DELTA;
return fabs((a-b)/max(fabs(a),fabs(b))) <= DELTA;
}
数组
int inputPins[]={2,3,4,5};
int ledPinds[]={10,11,12,13};
void setup()
{
for(int index=0;index<4;index++)
{
pinMode(ledPins[index],OUTPUT);
pinMode(inputPins[index],);
digitalWrite(inputPins[index],HIGH);
}
}
void loop()
{
for(int index=x;index<4;index++)
{
int val=digitaleRead(inputPins[index]);
if(val=LOW)
{
digitalWrite(ledPins[index],HIGH);
}
else
{
digitalWrite(ledPins[index],LOW);
}
}
}
要点:使用数字引脚,开关接GND,LED需要接电阻和GND
String
String text1="2只老虎";
String text2="爱跳舞";
String text3;
void setup()
{
Serial.begin(9600);
Serial.print(text1);
Serial.print("is");
Serial.print(text1.length());
Serial.println("characters long.");
Serial.print("text2 is");
Serial.print(text2.length());
Serial.println("characters long.");
text1.concat(text2);
Serial.println("用了text1.concat(text2);后text1变为");
Serial.println(text1);
text3=text1+text2;
Serial.println("用了text3=text1+text2;后text3=");
Serial.println(text3);
}
void loop()
{
}
C字符串
//strcpy(destination,source);//复制字符串从source到de //strncpy(destination,source,6);//复制6个字符从source到de //strcat(destination,source);//复制字符串从source到de的结尾 String text1=“Arduino”; char stringA[8];//声明长达7个字符加上终止空字符的字符串 char stringB[8]=“Arduino”;//同上,并初始化 char stringC[16]=“Arduino”;//同上,但字符串有加长的空间 char stringD[]=“Arduino”;//由编译器初始化串并计算大小 * text1.concat(stringC);可以兼容,但+不能兼容char和string
比较和位运算
1.字符和数量值 比较相等关系
== != < > >= =<
if (strcmp(string1,string2)==0)
Serial.print("相等");
2.逻辑比较
&&与 ||或 !非
3.位运算
&按位与,二进制数,如果两个位都是1,才设置运算后的那个位为1,如3&1等于1(11&01=01) |按位或,对应的两个位有1得1,3|1=11(11|01=11) ^按位异或,对应的两个位是一个1才设置1,如5 ^ 4 =2(110 ^ 100=010) ~取反,反转每个位(比特bit)的值,如 ~ 1 =254(~00000001=11111110)
4.余数 %
5.其他常用计算函数
abs(x)计算x的绝对值 constrain(x,min,max)返回一个在min和max范围内的值 min(x,y) max(x,y) 返回最值 pow(x,y) 计算x的y次方 sqrt(x) x的平方根 floor(x) 向下取整,不大于x的最大整数值 ceil(x)向上取整,不小于x的最小整数值 sin(x) cos(x) tan(x)
*设置和读取位
最右边是最低位 bitSet(x,bitPosition) 设置 写入1 bitClear(x,bitPosition)清除 写入0 bitRead(,)返回指定位的值 bitWrite(x,bitPosition,value) 设置x的第bitPosition位为value值(0或1) bit(bitPosition)返回给定的比特位置的值 bit(0)=1 bit (1)=2 bit(2)=4
所以8个开关的状态可以包装成一个单一的8位值,而不需要8个字节或整数 int整数值刚好由2个字节,16个位组成。 取高低字节就是取高8位低8位:
A=258;
lowByte(A);
highByte(A);
本质上是位运算,所以可以自己定义这样的函数如下
#define highWord(w) ((w)>>16)
#define lowWord(w) ((w) & 0xffff )
*从高字节和低字节组合成一个int或long值 word(high,low)把高低字节组装成一个16位值(2字节)
*复合运算和赋值
运算符 | 例子 |
---|
+= 或-=和*=或/= | value+=5;//本身加5,用于递增或递减 | >>=或<<= | value>>=2;//向右移动2位 | &=或 |= | value&=2;//用2对value做二进制 与屏蔽 |
*字符串和数字的互相转化
void setup()
{
Serial.begin(9600);
}
char buffer1[12];
void loop()
{
long value=15;
itoa(value,buffer1,2);
Serial.print(buffer1);
Serial.print("为转换的二进制值,有");
Serial.print(strlen(buffer1));
Serial.print("位数,");
value=23333;
itoa(value,buffer1,16);
Serial.print(value);
Serial.print("转为16进制数");
Serial.print(buffer1);
Serial.print("有");
Serial.print(strlen(buffer1));
Serial.println("位数");
delay(1000);
}
const int ledPin=LED_BUILTIN;
int blinkDelay;
char strValue[6];
int index=0;
void setup()
{
Serial.begin(9600);
pinMode(ledPin,OUTPUT);
}
void loop()
{
if(Serial.available())
{
char ch=Serial.read();
Serial.print(ch);
if(index<4 && isDigit(ch)){
strValue[index++]=ch;
}
else{
strValue[index]=0;
blinkDelay=atoi(strValue);
index=0;
}
}
blink();
}
void blink()
{
digitalWrite(ledPin,HIGH);
delay(blinkDelay/2);
digitalWrite(ledPin,LOW);
delay(blinkDelay/2);
}
b. 程序结构控制
顺序
选择
1.if if() { 语句; }
const int ledPin=13;
const int inputPin=2;
void setup(){
pinMode(ledPin,OUTPUT);
pinMode(inoutPin,INPUT);
}
void loop(){
int val = digitalRead(inputPin);
if(val==HIGH)
{
digitalWrite(ledPin,HIGH);
}
else
{
digitalWrite(ledPin,LOW);
}
}
2.switch
只能整型或者字符型
switch(字符变量)
{
case '1':
函数1;
break;
case '+':
函数2;
default:
函数3
break;
}
循环
1.while
while(条件){}
do
{
}
while(条件);
2.for
for(int i=0;i<4;i++){
...}
3.跳出循环 break; continue;
while(analogRead(sensorPin)>100)
{
if(digitalRead(switchPin)==HIGH)
{
break;
}
flashLED();
}
c. 函数
int blink3(int period) {…} 返回类型 函数名 (参数类型 参数名字)
需要函数返回多个值可以在最开始定义全局变量,然后在函数中改变全局变量。也可以使用引用,符号&表示该参数是引用,函数内值的变化也将改变调用该函数时被赋值的变量的值。如下: void swap(int &value1,int &value2) 返回类型 函数名 (参数类型 &参数名字)
函数声明:告知编译器你要引用函数,出现在顶部,结束加分号如下: void swap(int &value1,int &value2);
常用函数先略,后面出现再介绍。
d. 库的使用
内置库
#include <Servo.h>//头文件在库的文件夹 #include “Servo.h”//头文件在当前文件夹 一些常用的库:SoftwareSerial软串口 SPI以太网和SPI硬件 Wire I2C设备 Stepper 步进电机的工作 *其他: EEPROM内存 Ethernet以太网通讯 FIrmata简化串行通讯和板卡控制的协议 LiquidCrystal 液晶显示器 SD支持使用外部硬件读取和写文文件到SD卡 Matrix 管理LED阵列 Sprite 激活与LED阵列…
创建自己的库
*可以先不看 #开头的语句被称为预处理命令 #define 标识符字符串 宏定义 #include 文件包含 条件编译 用来防止重复定义 #ifndef 标识符 程序段 #endif
#if 表达式 程序段1 #else 程序段2 #endif
/* 创建一个 blinkLED.cpp 一个以给定的毫秒数时间点亮LED的简单库 */
#include “Arduino.h”//arduin函数和常数的库所需要的 #include “blinkLED.h”//包含了你库的函数定义
//在给定的引脚以给定的时间闪烁LED void blinkLED(int pin, int duration) { digitalWrite(pin, HIGH); //打开LED delay(duration); digitalWrite(pin, LOW); //关闭LED delay(duration); }
/* 再创建一个blinkLED.h要放在和cpp文件一起的文件夹*/
#include “Arduino.h” void blinkLED(int pin, int duration);//函数原型
//最后在blinkLibTest程序中#include “blinkLED.h”
关键字高亮显示需要再建立一个keywords.txt文件,键入:你的关键词 KEYWORD1
二. 物理原型开发基础(Arduino)
a. 交互式硬件原型的开发基础:电流、常?电?元件、示意图与电路图,万?表、焊接、 ?包板的使?。
面包板原理图
从原理图可以看到,面包板上下区是横向5位相通,一般用于接电源和接地,中间区域是纵向5位相通,通常用于放置电路元件和电路连接线。
电阻:对电流起阻碍作用的元件。 电容:装电的容器,旁路、去耦、滤波、储能作用。 二极管:单项传导电流,整流、稳压作用。 三级管:放大、振荡、开关作用。EBC三级,有两种是PNP和NPN。 继电器:可控电子开关,原理电磁效应控制线圈。
传感器 电阻类:光敏电阻(光亮电阻降) 人体热释电红外传感器:对人体辐射出的红外线敏感,无人在监测范围内输出低电平,有人输出高电平脉冲信号。 温度传感器 五相倾斜:内部由一个金属球和4个触点组成,检测4个倾斜方向和水平位置共5种状态。 触摸模块:电容触摸感应原理检测人体接触,有人高无人低。
模拟声音传感器:检测周围环境声音的大小 气体传感器:使用气敏材料是在清洁空气中电导率较低的二氧化锡,当传感器所处环境中存在可燃气体时,传感器的电导率随着空气中可燃气体浓度的增加而增大。使用简单的电路就可以将电导率的变化转换为与该气体浓度相对应的输出信号。 时钟模块:虽然arduino的millis()和micro()可以获取运行时间,但是到一定时间就会溢出,而且断电不保存,可以用时钟模块准确、长时间计时。
显示器
b. 串?通信
串口(UART)
一种信息交互方式,硬件提供Arduino和它正在与之通信的设备之间的电信号,软件使用硬件发送所连接的硬件可以理解的字节或位。 硬件位于0(RX) 1(TX)两个引脚。 Arduino的串口库隔离了大部分硬件的复杂性,比较容易使用。
硬件串口
原理:串口硬件的发送和接收表示序列位的电脉冲数据。Arduino使用TTL电平表示构成字节信息的0和1,用0V表示比特率0 , 5V(或3.3V)表示比特值1。 大多数arduino板子有一个芯片把硬件串口转换成通用串口总线USB,用于连接到其他硬件串口。有的设备使用RS-232标准进行串口连接,通常有9针连接器,是一种旧的通讯协议,采用的电压与arduino不兼容。
Arduino Mega有4个硬件串口,可以与4个串口设备进行通讯,其中一个有USB接口
串口通讯的函数
1.初始化 Serial.begin(9600);//发送和接受的波特率范围是300~115200。9600,即每秒钟传输9600个“位元”(比特bit) 2.输出 Serial.print() 打印完不换行 Serial.println(“char1”); Serial.println(char1); 打印完换行 Serial.println(char1,HEX);//HEX是16进制形式输出 OCT是10进制 BIN是二进制
(*print和write的区别,print发送的不是数据本身而是转换为字符,然后将字符对应的ASCII码发出去,串口监视器收到ASCIII码就会显示对应的字符,而write发送的是数值本身,串口监视器收到后会把数值当做ASCII码而显示对应的字符) 3.输入 Serial.read() 调用该函数时,arduino会从64B的缓冲区中取出1B的数据 4.Serial.available(); 返回值就是当前缓冲区中接收到的数据字节数
*串口事件 void serialEvent(){} 当串口缓冲区中有数据,就会触发该事件。
*可以利用C++的流插入语法和模板,如果声明了一个流模板就可以使用它。 可以简化为 Serial<< “At” << t <<“seconds,speed=”<< s <<", distance =" << d << endl;
举一个USB串口通讯的实例(实际上在前面已经涉及过了)
void setup()
{
Serial.begin(9600);
}
int number=0;
void loop()
{
Serial.print("The number is ");
Serial.println(number);
delay(500);
number++;
}
也可以再复习一下这个用Serial.read接收电脑从串口传来的数据,然后转为数字,改变LED闪烁周期的。 接受单个数字的
const int ledPin=13;
int blinkRate=0;
void setup()
{
Serial.begin(9600);
pinMode(ledPin,OUTPUT);
}
void loop()
{
if(Serial.available())
{
char ch=Serial.read();
if(isDigit(ch))
{
blinkRate=(ch-'0');
blinkRate=blinkRate*100;
}
}
blink();
}
void blink()
{
digitalWrite(ledPin,HIGH);
delay(blinkRate);
digitalWrite(ledPin,LOW);
delay(blinkRate);
}
复杂一点的,要用C语言转换函数atoi或atol把文本转化为数字
const int ledPin=LED_BUILTIN;
int blinkDelay;
char strValue[4];
int index=0;
void setup()
{
Serial.begin(9600);
pinMode(ledPin,OUTPUT);
}
void loop()
{
if(Serial.available())
{
char ch=Serial.read();
Serial.print(ch);
if(index<4 && isDigit(ch)){
strValue[index++]=ch;
}
else{
strValue[index]=0;
blinkDelay=atoi(strValue);
index=0;
}
}
blink();
}
void blink()
{
digitalWrite(ledPin,HIGH);
delay(blinkDelay/2);
digitalWrite(ledPin,LOW);
delay(blinkDelay/2);
}
可以学习一下处理思路,实战可以直接用Serial.parseInt()或Serial.parseFloat()读取串口字符,并返回表示的数值
const int NUMBER_OF_FIELDS =3;
int fieldIndex=0;
int values[NUMBER_OF_FIELDS];
void setup()
{
Serial.begin(9600);
}
void loop()
{
if(Serial.available())
{
for(fieldIndex=0;fieldIndex<3;fieldIndex++)
{
values[fieldIndex]=Serial.parseInt();
}
Serial.print(fieldIndex);
Serial.println("fields received:");
for(int i=0;i<fieldIndex;i++)
{
Serial.println(values[1]);
}
}
}
发送多个文本最简单的方法就是字符分割
软件串口
一个标准的arduino有一个硬件串口,但你也可以用软件库来模拟附加的端口(通信通道),以连接一个以上的设备,即软件串口,它需要耗费内存资源,所以速度效率不如硬件串口。 程序示例:
#include <softwareSerial.h> const int rxpin=2;//用于接收LCD的引脚 const int txpin=3;//用于传送到LCD显示屏的引脚 SoftwareSerial serial_lcd(rxpin,txpin);//新的串口为引脚2个和3个 void setup(){serial_lcd.begin(9600);}
多个串口
1.只有RX接收脚的LCD
/* 在同一时间将数据发送到两个串口设备,硬件串口 */
//void setup(){ // 初始化mega的两个串口 Serial.begin(9600);//主串口 Serial1.begin(9600);//mega也可以用serial1到serial3 }
/* SoftwareSerialOutput 输出数据到软件串口 */ #include <softwareSerial.h> const int rxpin=2;//用于接收LCD的引脚 const int txpin=3;//用于传送到LCD显示屏的引脚 SoftwareSerial serial_lcd(rxpin,txpin);//新的串口为引脚2个和3个 void setup() { Serial.begin(9600); serial_lcd.begin(9600);//初始化软件串口也为9600b/s } int number=0;
void loop(){ serial_lcd.print("The number is ");//文本发送到LCD serial_lcd.print(number); Serial.print("the number is ");//在PC控制台打印数字 Serial.print(number); delay(1000); number++; } GPS同理, 区别是软串口的波特率改为4800 loop中改为这一段
if(serial_gps.available()>0)
{
char c=serial_gps.read();
Serial.Write(c);
}
#include <SoftwareSerial.h>
const int rxpin1=2;
const int txpin1=3;
const int rxpin2=4;
const int txpin2=5;
SoftwareSerial gps(rxpin1,txpin1);
SoftwareSerial xbee(rxpin2,txpin2);
void setup(){
xbee.begin(9600);
gps.begin(4800);
xbee.listen();
}
void loop(){
if(xbee.available()>0)
{
if(xbee.read()=='y')
{
gps.listen();
unsigned long start=millis();
while(start+100000>millis())
{
if(gps.available()>0)
{
char c=gps.read();
}
}xbee.listen();
}
}
}
c. 简单数字和模拟输?
开关
if(digitalRead(inputPin)=HIGH) {digitalWrite(ledPin,HIGH)} arduino内部有上拉电阻,可以不用外部电阻,程序需要修改一下
const int ledPin=13;
const int inputPin=2;
void setup()
{
pinMode(ledPin,OUTPUT);
pinMode(inputPin,INPUT);
digitalWrite(inputPin,HIGH);
}
void loop()
{
int val=digitalRead(inputPin);
if(val==HIGH){
digitalWrite(ledPin,HIGH);
}
else
{
digitalWrite(ledPin,LOW);
}
}
按键消抖
本程序针对下拉电阻,按钮按下输入arduino高电平
const int inputPin=2;
const int ledPin=13;
const int debounceDelay=10;
boolean debounce(int pin)
{
boolean state;
boolean previousState;
previousState=digitalRead(pin);
for(int counter=0;counter<debounceDelay;counter++)
{
delay(1);
state=digitalRead(pin);
if(state!=previousState)
{
counter=0;
previousState=state;
}
}
return state;
}
void setup(){
pinMode(inputPin,INPUT);
pinMode(ledPin,OUTPUT);
}
void loop(){
if(debounce(inputPin)){
digitalWrite(ledPin,HIGH);
}
}
如果用前面提到的上拉电阻,就要改变一下boolean debounce的返回值如下:
boolean debounce(int pin){
boolean state;
boolean previousState;
previousState=digitalRead(Pin);
for(counter=0;counter<debounceDelay;counter++) {
delay(1);
state=digitalRead(pin);
if(state!=previousState){
counter=0;
state=previousState;
}
}
if(state==LOW)
return true;
else
return false;
}
int count;
void setup()
{
pinMode(inPin,INPUT)
pinMode(outPin,OUTPUT)
}
void loop
{
if (debounce(input))
{
digitalWrite(outPin,HIGH);
count++;
Serial.println(count);
}
}
/* *switchTime 函数将返回开关被按压的毫秒数 *使用上拉电阻 *静态变量,即使在函数返回后仍然保留,只能在该函数内访问,好处是不能被一些其他函数意外地修改 */
const int switchAPin=2;//开关2 的引脚 const int switchBPin=3;//开关3 的引脚
//带引用的函数必须显式声明 unsigned long switchTime(int pin, boolean &state,unsigned long &startTime); long switchATime(); long switchBTime();
void setup() { pinMode(switchAPin,INPUT); digitalWrite(switchAPin,HIGH);//打开上拉电阻 pinMode(switchBPin,INPUT); digitalWrite(switchBPin,HIGH);//打开上拉电阻 }
void loop(){ unsigned long time; Serial.print(",按键A的时间为:"); time=switchATime(); Serial.print(time);
Serial.print(",按键B的时间为:"); time=switchBTime(); Serial.print(time); delay(1000); }
unsigned long switchTime(int pin,boolean &state, unsigned long &startTime) { if(digitalRead(pin)!=state)//检查开关量是否改变 { state=!state;//是 则反转状态值 startTime=millis();//存储时间 } if(state == LOW) return millis()-startTime;//返回以毫秒为单位的时间 else{return 0;//开关没有按下 在HIGH状态 } }
long switchATime() { //声明静态变量 //开关状态改变第一次检测被检测到的时间 static unsigned long startTime=0; static boolean state;//开关的当前状态 return switchTime(switchAPin,state,startTime); }
long switchBTime() { //声明静态变量 //开关状态改变第一次检测被检测到的时间 static unsigned long startTime=0; static boolean state;//开关的当前状态 return switchTime(switchBPin,state,startTime); }
数字键盘
按键相当于一个常开开关
const int numRows = 4;
const int numCols = 3;
const int debounceTime = 20;
const char keymap[numRows][numCols] = {
{'1', '2', '3'},
{'4', '5', '6'},
{'7', '8', '9'},
{'*', '0', '#'}
};
const int rowPins[numRows] = {7, 2, 3, 6};
const int colPins[numCols] = {5, 8, 4};
void setup()
{
Serial.begin(9600);
for (int row = 0; row <= numRows; row++)
{
pinMode(rowPins[row], INPUT);
digitalWrite(rowPins[row], HIGH);
}
for (int column = 0; column < numCols; column++)
{
pinMode(colPins[column], OUTPUT);
digitalWrite(colPins[column], HIGH);
}
}
void loop()
{
char key = getKey();
if (key != 0) {
Serial.print("有效按键");
Serial.print(key);
}
}
char getKey()
{
char key = 0;
for (int column=0;column<numCols;column++)
{
digitalWrite(colPins[column],LOW);
for(int row=0;row<numRows;row++)
{
if(digitalRead(rowPins[row]==LOW))
{
delay(debounceTime);
while(digitalRead(rowPins[row])==LOW)
;
key=keymap[row][column];
}
}
digitalWrite(colPins[column],HIGH);
}
return key;
}
模拟输入
**analogRead()**会自动设置输入, 0V对应0,5V对应1023
/* 按一个电位器的位置设置的速度闪烁LED 注意区分模拟输入0和数字输入0 */
const int potPin = 0; //选择电位器的输入引脚 const int ledPin = 13; int val = 0;
void setup() { pinMode(ledPin, OUTPUT); // }
void loop() { val = analogRead(potPin); //读取电位器上的电压 digitalWrite(ledPin, HIGH); delay(val); digitalWrite(ledPin, LOW); delay(val); }
/* 按一个电位器的位置设置的速度闪烁LED */
const int potPin = 0; //选择电位器的输入引脚 const int ledPin = 13; int val = 0;
void setup() { pinMode(ledPin, OUTPUT); Serial.begin(9600); }
void loop() { val = analogRead(potPin); //读取电位器上的电压 int percent;//映射的值 percent=map(val,0,1023,0,100);//map整数运算读取的电压值对应百分比 的映射 digitalWrite(ledPin, HIGH); delay(percent); digitalWrite(ledPin, LOW); delay(100-percent); Serial.println(percent); }
*多路复用模拟输入
测量电压
应用:电量不足时LED闪烁,到临界电压级别时LED长亮 long warningThreshold = 1200; //毫伏在警告级别 - LED闪烁 long criticalThreshold = 1000; //临界电压级别 - LED长量
const int batteryPin = 0; const int ledPin = 13;
void setup() { pinMode(ledPin, OUTPUT); }
void loop() { int val = analogRead(batteryPin); if (val < (warningThreshold * 1023L / 5000)) { //在上面的行中 一个数后跟L得到一个32位的值 flash(val / 1023); } else if (val < (criticalThreshold * 1023L / 5000)) { digitalWrite(ledPin, HIGH); } }
void flash(int percent) { digitalWrite(ledPin, HIGH); delay(percent + 1); digitalWrite(ledPin, LOW); delay(100 - percent); //检查延时==0? }
d. 获取传感器输?
传感器提供信息的方式: 数字开关:倾斜 动作传感器 模拟信号:光 振动 声音 加速度 脉冲宽度:距离传感器 pulseln命令测量脉冲持续时间 串口:RFID GPS 同步协议:I2C SPI 通用传感设备 鼠标和游戏控制器等
各传感器原理简介见二、a
动作(数字量高低电平,类似开关)
//倾斜传感器 const int tiltSensorPin = 2; //连接传感器 的引脚 const int firstLEDPin = 11; const int secondLEDPin = 12;
void setup() { pinMode(tiltSensorPin, INPUT); //设置 传感器引脚 为输入 digitalWrite(tiltSensorPin, HIGH); //上拉电阻
pinMode(firstLEDPin, OUTPUT); pinMode(secondLEDPin, OUTPUT);
}
void loop() { if (digitalRead(tiltSensorPin) ) { digitalWrite(firstLEDPin, HIGH); digitalWrite(secondLEDPin, LOW); } else { digitalWrite(firstLEDPin, LOW); digitalWrite(secondLEDPin, HIGH); } } 可以理由按键消抖的原理消抖,程序略
动作检测(集成被动红外探测器PIR,只需判断模拟量高电平)
这种传感器引脚标有OUT - + 长这个样子的传感器基本上都是这么控制的↓,但是要注意信号电平的高低。 //红外传感器控制LED const int inputPin = 2; //连接传感器 的模拟输入引脚2 const int ledPin = 13;
void setup() { pinMode(ledPin, OUTPUT); //声明LED作为输出 pinMode(inputPin , INPUT); //被动红外传感器为输入引脚 }
void loop() { int val = digitalRead(inputPin); if (val == HIGH) { digitalWrite(ledPin, HIGH); delay(500); digitalWrite(ledPin, LOW); } }
光(光敏电阻模拟量)
//光敏电阻控制LED const int SensorPin = 0; //连接传感器 的模拟输入引脚0 const int ledPin = 13;
void setup() { pinMode(ledPin,OUTPUT);//LED引脚设为输出 }
void loop() { int rate=analogRead(sensorPin);//读取模拟输入 digitalWrite(ledPin,HIGH);//设置LED开 == delay(rate);//等待时间取决于光照水平== digitalWrite(ledPin,LOW);//设置LED开 delay(rate);//等待时间取决于光照水平 }
距离(超声波,输出模式纯净高电平+输入模式计算脉冲时间)
//超声波距离传感器控制LED const int pingPin = 5; //连接传感器 的模拟输入引脚5 const int ledPin = 13;
void setup() { pinMode(ledPin, OUTPUT); //声明LED作为输出 Serial.begin(9600); }
void loop() { int cm = ping(pingPin); Serial.println(cm); digitalWrite(ledPin, HIGH); delay(cm * 10); //每厘米增加了10ms的延迟 digitalWrite(ledPin, LOW); delay(cm * 10); }
int ping(int pingPin) { long duration, cm; pinMode(pingPin, OUTPUT); //首先要给一个简短的的低电平脉冲以确保高电位脉冲触发 digitalWrite(pingPin, LOW); delayMicroseconds(2); digitalWrite(pingPin, HIGH); delayMicroseconds(5); digitalWrite(pingPin, LOW); pinMode(pingPin, INPUT); //传感器引脚改为输入模式,接受信号 duration = pulseIn(pingPin, HIGH); //计算出传感器引脚输入从低到高的时间,单位为微秒 //时间转换成距离 cm = duration * 29/ 2;//来回距离 超声波速度29us/cm }
有不需要ping的超声波传感器,也是用pulseIn(pingPin, HIGH),是计算脉冲宽度,直接读值就可以。测量距离=pulseIn(pingPin, HIGH)*脉冲宽度速率
精确测距(*红外IR传感器比超声波精度高但量程小,输出非线性需要查表)
//红外距离传感器 测距输出 const int sensorPin = 0; //连接传感器 的模拟输入引脚0 const int ledPin = 13; const long referenceMv = 5000; //长整形以防止做乘法时溢出 void setup() { pinMode(ledPin, OUTPUT); //声明LED作为输出 Serial.begin(9600); }
void loop() { int val = analogRead(sensorPin); == int mv = (val * referenceMv) / 1023;==//mv为距离信号
Serial.print(mv); Serial.print(","); int cm = getDistance(mv); Serial.println(cm);
digitalWrite(ledPin, HIGH); delay(cm * 10); //每厘米增加了10ms的延迟 digitalWrite(ledPin, LOW); delay(cm * 10); }
const int TABLE_ENTRIES = 12; const int firstElement = 250; //第一项是250mv const int INTERVAL = 250; //每个项之间的毫伏数 static int distance[TABLE_ENTRIES] = {150, 140, 130, 100, 60, 50, 40, 35, 30, 25, 20,15};//器件手册提供 假设单位是cm
int getDistance(int mV) { if (mV < INTERVAL )//判断是否在范围内, 可能还要写个超出最大距离的判断 return distance[TABLE_ENTRIES - 1]; //当距离比最小距离要小,返回最小距离 else { int index = mV / INTERVAL; //计算近似的信号值 float frac = (mV % 250) / (float)INTERVAL;//取余数,然后除以每项之差,用来插值计算 return distance[index] - ((distance[index] - distance[index + 1])* frac); //返回计算出的距离值 } }
振动(压电传感器/爆震传感器,应力越大电压越高。模拟量)
val=analogRead(sensorPin); if(val>=THRESHOLD){…}
声音
//驻极体话筒 const int sensorPin = 0; //连接传感器 的模拟输入引脚0(可以省略 const int ledPin = 13; const int middleValue = 512; //模拟值范围的中间值 const int numberOfSamples = 128; //每次读取多少个数过小无法覆盖完整波形周期,过大可能错过短的声音
int sample;//每次读取的值 long signal;//你已经去除直流偏移之后的读数 long averageReading;//循环读数的平均值
long runningAverage = 0; //计算值的运行平均数 const int averagedOver = 16; //新值以何种程度影响运行平均值,数字越大速度越慢
const int threshold = 400; //在什么水平LED亮
void setup() { pinMode(ledPin, OUTPUT); //声明LED作为输出 Serial.begin(9600); }
void loop() { long sumOfSquares = 0; //正负幅值的平方和 for (int i = 0; i < numberOfSamples; i++) { sample = analogRead(0); //读取一个数 signal = sample - middleValue; //算模拟信号 距离 直流中心值 的 偏移 signal *= signal; sumOfSquares += signal; } averageReading = sumOfSquares / numberOfSamples; //计算运行平均值 runningAverage = ((averagedOver - 1) * runningAverage + averageReading) / averagedOver; //即新运行平均值=原运行平均值+(这次采样平均值-原运行平均值)/16,分母averagedOver越大,每次改变就越小 //新值影响平均值的速度就越慢 if (runningAverage > threshold) { digitalWrite(ledPin, HIGH); } else { digitalWrite(ledPin, LOW); } Serial.println(runningAverage);//打印检查 }
const int TABLE_ENTRIES = 12; const int firstElement = 250; //第一项是250mv const int INTERVAL = 250; //每个项之间的毫伏数 static int distance[TABLE_ENTRIES] = {150, 140, 130, 100, 60, 50, 40, 35, 30, 25, 20, 15}; //器件手册提供
int getDistance(int mV) { if (mV < INTERVAL * TABLE_ENTRIES - 1) return distance[TABLE_ENTRIES - 1]; //当距离比最小距离要大,返回最小距离 else { int index = mV / INTERVAL; //插值计算, float frac = (mV % 250) / (float)INTERVAL; return distance[index] - ((distance[index] - distance[index + 1]) * frac); } }
温度
类似光敏传感器,该温度传感器模拟电压与温度成正比,需要写相应程序计算。
*RFID标签
RFID 射频识别,对无线电频率敏感,易被干扰。
硬件图注意读取器有使能和输出引脚,前一个是arduino用来发给它激活信号(低电平)的,后一个是它把接收到的RFID传输给Arduino的,占用了硬串口,理论上你应该不能用电脑看到传输了什么数据。程序实现RFID读取并对特定ID回应: const int startByte = 10; //每个标签之前的ASCII换行符 const int endByte = 13; //ASCII回车结束每个标签 const int tagLength = 10; //标签数位 const int totalLength = tagLength + 2; //标签的长度 + 开始和结束字节 char tag[tagLength + 1]; //保存标签和一个终止空白符
int bytesread = 0; void setup() { Serial.begin(2400);//设置为RFID阅读器的波特率 pinMode(2, OUTPUT); //输出到RFID digitalWrite(2, LOW); //使能RFID} } void loop() { if (Serial.available() >= totalLength) //检测是否有足够数据 { if (Serial.read() == startByte) { bytesread == 0; //标签的开始,重置计数为0 while (bytesread < tagLength) //读取10位编码 { int val = Serial.read(); if ((val == startByte) || (val == endByte)) //检查编码结束 break; tag[bytesread] = val; bytesread = bytesread + 1; //准备读取下一个数字 } if (Serial.read() == endByte) //检查正确的结束字符 { tag[bytesread] = 0; //终止字符串 Serial.print(“RFID 标签是:”); Serial.println(tag); } } } }
*旋转动作(旋转编码器)
测量并显示某物的旋转,跟踪速度和方向 代码逻辑: 首先把AB对应的4、2引脚置为输入模式,然后写高激活, 当A脚的状态和上次测量不同时,(上升沿或者下降沿) B是低的,编码器位置减1, 若B高电平,编码器位置加1。 最后计算角度=(编码器位置 %走一圈的步数)*360度/走一圈的步数
拓展知识:中断 利用中断可以实现只有在收到传感器信号“当A脚的状态和上次测量不同时,(上升沿或者下降沿)”的时候程序才会去检查传感器值,而不是始终检查传感器值。
*鼠标
*GPS
*加速度(陀螺仪测量角加速度,加速度计测量移动加速度如重力加速度)
e. 可视输出、物理输出、声?输出
显示
1.数字输出 pinMode(outputPin,OUTPUT) digitalWrite(outputPin,value) 2.模拟输出(uno的3 5 6 9 10 11引脚可以用) analogWrite() 使用了一种脉冲宽度调制PWM技术,用数字脉冲模拟一个模拟信号 3.灯光控制。LED灯、阵列和数码显示,LCD文本和图形显示。 4.LED规格 5.复用,多个LED复用引脚,利用视觉暂留,靠按位扫描驱动。 利用复用技术16个引脚即可控制64个LED, 依次点亮所有LED的程序逻辑如下: 首先在setup中循环调好所有的行列引脚为输出模式 然后在loop中,计数当前为第n个灯,/和%计算出行列值, 之后for循环扫描每一行列依次点灯,具体为:拉低当前列,要判断当前点的灯有没有达到n的位置,未达到或者刚好达到就输出行高电平,超过就输出低电平,最后延时为这个LED给出20ms的帧时间,然后给低电平,关灯。
6.最大引脚电流 <40mA <200mA。驱动高功率需要用晶体管和外接电源。
计算LED串联的电阻值
数码管
这个是共阳极,给低电平点亮 段 数码管相当于8位LED串联,分共阴极和共阳极 驱动单位共阳极数码管的程序逻辑: 首先定义数组存储字形码 const byte numeral[10]={ //ABCDEF/dp 共阴极的字形码 B11111 1100,//0 , , , , …};2 个字节的每一位可以代表一个引脚的输出状态 然后定义引脚,设置为输出模式 在循环中调用数码管显示函数显示值,延时 数码管显示的函数:void showDigit(int number),循环检查是否点亮 for(int segement=1;segment<8;sengment++) 用到bitRead(numeral[number],segment) 读给定数字的字形码,赋值给isBitSet,如果为1,则isBitSet != isBitSet ,如果是共阴极数码管就不用这个操作,最后对应引脚拉低。
驱动多位LED数码管显示器 在单位的基础上,增加一个位数循环,依次点亮指定位指定的led段
为解决引脚占用,可以接MAX7221等数码管驱动芯片,需要查芯片的命令手册,并用到SPI通信, 用自定义函数sendCommend(int command, int value)发送指令函数,利用digitalWrite(使能引脚,LOW) SPI.transfer(command);SPI.transfer(value); digitalWrite(使能引脚,HIGH)
电机
1.舵机 即直流伺服电机。分为带位置反馈的角度控制舵机和断开位置反馈的连续旋转舵机。 红正棕负橙信号
控制舵机角度: 第一步库和对象 #include <Servo.h>//引入舵机库 Servo myservo;//创建舵机对象来控制指定舵机 第二步 信号引脚连接对象 void setup() { myservo.attach(9);//把连接在引脚9上的舵机赋予舵机对象 } 第三步使用wite写角度 void loop() { myservo.write(angle); }
正反转摇摆:
#include <Servo.h>
Servo myservo;
int angle = 0;
void setup()
{
myservo.attach(9);
}
void loop()
{
for (angle = 0; angle < 180; angle++)
{
myservo.write(angle);
delay(20);
}
for (angle = 180; angle > 0; angle--)
{
myservo.wirte(angle);
delay(20);
}
}
如果是连续旋转舵机,控制同理,但是给90°停止转动,距离90°越远,向一个方向转速越快。
2.有刷电机和无刷电机
可用H桥电路控制有刷电机,直接用元件即可,需要接一个用来使能的模拟引脚用analogWrite()PWM控制速度,一对数字引脚控制不同方向旋转和停止。 具体略。
可利用业余调速器控制无刷电机 代码和舵机代码相等
3.步进 每步走1°、2°、30°或更多。也是H桥,但可以用Stepper的库
#include <Stepper.h>
#define STEPS 24
Stepper stepper(STEPS, 2, 3, 4, 5,);
int steps = 30;
void setup()
{
stepper.setSpeed(30);
}
void loop() {
stepper.step(steps);
}
4.振动马达 像点亮LED一样就行,代码见第"三"部分
音频
扬声器原理 压电装置:施加脉冲时会产生声音的陶瓷换能器 有源蜂鸣器自身包含震荡源,给高电平就能响,
所以介绍无源蜂鸣器,由arduino给音频信号
const int speakerPin = 0;
const int pitchPin = 0;
void setup()
{}
void loop()
{
int sensor0Reading = analogRead(pitchPin);
int frequrency = map(sensor0Reading, 0, 1023, 100, 5000);
int duration = 250;
tone (speakerPin, frequency, duration);
delay(1000);
}
如果要同时产生多个音调做出电子琴的效果,可以用Tone.h的库,用switch分支选择输出给扬声器的音调,演奏对象.play(音调)方法。
f. ?线通信和?络
* I2C和SPI
I2C内部集成电路和SPI串行外设接口标准的建立是为传感器和微控制器之间的数字信息传输提供简单的方法。 I2C只需要2路信号连接到arduino,同一时间只能在一个方向上传送。通常用于不需要大量数据的传感器。(加速度计、外部实时时钟RTC、外部存储器芯片EPROM、数字温度计、LED数码管、另一个arduino等) SPI速度更快,有独立的输入输出连线,可以同时收发数据。
I2C总线的两路连线叫SCL和SDA,Arduino作为主设备。 用Wire.h库可以轻松初始化和通讯。
下面一个例子是和游戏手柄I2C通讯
时钟
无线XBEE
分为发送端和接收端,用到VirturalWire.h 库传输文本消息
蓝牙
#include<SoftwareSerial.h>
const int rxpin = 2;
const int txpin = 3;
SoftwareSerial buletooth(rxpin, txpin);
void setup()
{
Serial.begin(9600);
bluetooth.begin(9600);
Serial.println("Serial ready");
bluetooth.println("蓝牙准备就绪");
}
void loop() {
if (bluetooth.available()) {
char c = (char)bluetooth.read();
Serial.write(c);
}
if (Serial.available()) {
char c = (char)Serial.read();
bluetooth.write(c);
}
}
*互联网
1.以太网:低级别的信号层,提供基本的物理信息传递能力。这些信息的源地址和目的地址由媒界访问控制MAC地址来确定。arduino程序定义的MAC地址需要在网络上唯一。 2.传输控制协议TCP和网际协议IP, 3.本地IP地址,路由器提供,在路由器上的动态主机配置协议DHCP服务创建。从web浏览器发出的web请求和所得到的的回应使用超文本传输协议HTTP信息。网页通常使用超文本标记语言HTML格式。网络交换格式已被开发用在应用计算机软件实现可靠的网络数据提取,XML和JSON是流行的格式。
三. 上述技术在设计原型制作和开发中的应?。
1.接收到____信号时,_____动作
最简单的可以看做按钮和LED灯 按钮可以换成各种传感器还有通讯传输接受的数据,LED可以换成电机、蜂鸣器、显示屏等。 原理图都和这个大差不差,图片顺时针转90度, 左边模拟量 连传感器、H桥驱动模块等元件的使能引脚给速度值
右边数字量 连开关、LED、有源蜂鸣器、马达、传感器的使能脚、H桥的方向引脚。 PWM(3/5/6/9/10/11):电机、无源蜂鸣器等。 数字引脚通过程序可以模拟成串口通信引脚。 *(0和1一般不用,占用硬件串口UART。 2和3可以接外部中断。10-13SPI通信)
如光敏传感器和振动马达
const int motorPin = 3;
const int sensorPin = 0;
int sensorAmbient = 0;
const int thresholdMargin = 100;
void setup()
{
pinMode(motorPin, OUTPUT);
pinMode(sensorPin, INPUT);
}
void loop() {
int sensorValue = analogRead(sensorPin) ;
if (sensorValue > thresholdMargin + sensorAmbient)
{
digitalWrite(motorPin, HIGH);
}
else {
digitalWrite(motorPin, LOW);
}
}
细化改一下可以做智能窗帘。
2.智能水杯(传感器、显示器、LED、蜂鸣器、蓝牙等等综合运用)
温度传感器(塑料水杯) 压力传感器(水量监控) LCD液晶显示器 LED灯x2 蜂鸣器
水量监控:压力传感器实时健康水量。
智能饮水累计:初始饮水量为0,如果压力传感器计数小于1,暂停一段时间再计数,并计算饮水数。如果一段时间后饮水量未变,蜂鸣器报警。
不同温度下指示灯不同,温度传感器监测到水温超过。
水温报警功能,如果水温过高拿起水杯,蜂鸣器响起,提示使用者。
蓝牙用于信息传输,可以控制硬件。
|