-
第一次 3D 打印乐高 15 单位的 beam , 失败分析
水平及平台距离一直调整不出一个正确的值,原因在於平台的亚克力板不是平的。
考虑去买一片玻璃回来打印。
今天打印非常辛苦,先是清理喷头,不知道多少温度才能容易清理,而料又不要流下来。
经过测试 130-150 度,可以清理喷头,料又不会流出。
今天第一个试打 Beam 失败,原因是丝没黏附到平台上。
然后继续调距离再试,总共失败了四次。
第四次是打 Brim 的时候,边打边调距离,让丝能黏附在平台上。
最终还是失败,但失败得有意思... 呵呵。
下图,第四次,今日最后一次快乐打印中
脑残碰到 USB 线后,停止打印的半成品
虽然是失败品,但还是有很多地方值得分析
红框这几个部份,应该是丝没有黏住下层。
这个 Brim 底座,是我一边看打印状况,一般调距离。
这两个地方问题出得很奇怪,喷头一画过去,就出现这两个掉下来的线丝。
在我眼皮底下出现的...
最后我碰到 USB 线.. 三分钟不到机器就停了.. 下图是停下来的位置
打印心得:
1. 一开始机器的运动线,还算正常,后来就有点乱画,我猜是切片软件的问题,有机会试试鲁蛋说的 makerware
一开始一排圆,是左至右,右至左 画过去。后来,变成先画三个,后来变先画一个... 后来这里画一个那里画一个...
2. 调到差不多.. 我就开始用 SD 卡打... 大家都说要用 SD 卡打..
打印 Arduino pro mini Wire manager Holder 理线器盒子
clf 挑战难度塔内双螺旋打印-成功
2013年9月13日 -
protothread, a powerfull library protothread 一个厉害的 arduino 多线程链接库
protothread, a powerfull library protothread
一个厉害的 arduino 多线程链接库
(学长1: 堆栈的原文是 stack ,又称为栈。意指私有变数)
(学长2: 阻塞的原文是 block 或 blocking 。字面上是中断程序,到此处执行。但有时指的是一个程序的片段。)
Some weeks ago, while browsing around the net i found Adam Dunkels website.
Back in 2005 he wrote protothread as an easy way to implement blocking conditions.
几周以前,我在网络上发现了Adam Dunkels的网站。
2005年时他写了protothread ,以简单的方式实现 阻塞条件
"Protothreads are a extremely lightweight, stackless threads that provides a blocking context on top of an event-driven system, without the overhead of per-thread stacks. The purpose of protothreads is to implement sequential flow of control without using complex state machines or full multi-threading. Protothreads provides conditional blocking inside a C function." [http://www.sics.se/~adam/pt/about.html]
Protothread 是一个很轻量的、无栈式多线程,提供事件驱动系统之上的阻塞式 context ,
不需要管理每个线程的 stack 。
protothread 的方式,是实现一个串行化的流程来控制,而不需要复杂的 状态机制 或 完整的多线程。
Protothreads 提供一个在 C 函数内部的条件式组塞。
Basically protothread is the implementation of stack less coroutines. Of course no real coroutines because the basic µc has only one core and no scheduler. What it does, is that it gives you the possibility to manage your code in a very easy way using 'thread a like functionality'. It consists of a few include files defining macro's that are used to implement threads as switch/case constructs.
The trick was originally invented by Tom Duff and dubbed Duff's device.
基本上 protothread 是无堆栈式协同工作的实现。当然不是真正的协同,
因为基本上 µc (一种实时操作系统) 只有一个核心而且没有排程功能。
它所做的,只是让你可以用一种简单的方式,使用'类多线程'管理程序代码。
由几个定义 macro 的程序文件所组成,macro 里面是使用 switch/case 实现的多线程结构。
When you have a complex program consisting of many functions, maybe some options for user input and periodic calculations or sensor input to check for, you need to organize your code in a way that all functions are called 'often enough'. If not you risk to loose data or, in the case of user input, your user interface will lag. Often you'll have to check if a timer has run out and in case it has not, wait with further action until it has.
当你有一个由很多函数组成的复杂程序,也许包括用户输入的选项和周期式的运算,或是检查感知器讯号输入,
你需要用一种方式管理你的程序代码,让所有的函数尽可能的执行多次。
如果不是的话,你就有可能遗失数据或是用户的输入,或是使用界面延迟。
一般来说,你必须在定时器到达时间时检查,如果未到时间就进行别的工作来等待时间到达。
This is where protothreads come in very handy. They let you use the functions
void PT_WAIT_UNTIL(struct pt *pt, condition);
Block and wait until condition is true.
void PT_WAIT_WHILE(struct pt *pt, condition);
Block and wait while condition is true.
以下是 protothreads 提供的能够简单使用的函数
void PT_WAIT_UNTIL(struct pt *pt, condition);
阻塞并且等待, 直到条件式 condition 成立 (True)
void PT_WAIT_WHILE(struct pt *pt, condition);
当条件式成立(True) 时,阻塞并等待。
among some others. Look here for a condensed version of the protothreads API. But there are some constraints you need to consider:
还有一些其他的。查询这个网站,就有 protothreads API 的介绍版本。
但是有些限制,必须思考一下:
protothreads do not save the stack context across a blocking call, local variables are not preserved when the protothread blocks. This means that local variables should be used with utmost care - if in doubt, do not use local variables inside a protothread!
protothread 没有堆栈(私有的变量)来给阻塞呼叫使用, 所以在 prothread 程序片段内 局部变量(local variables) 是无法保存的。
这表示 局部变量(local variables) 应该“很小心“的使用。
在不确定的情况下,尽量避免在 protothread 内使用 局部变量(local variables) 。
So how do you use it?
Here's a protothread function skeleton:
所以,怎么使用呢?
以下是一个 prothread 功能的骨架
#include <pt.h> static int protothread1(struct pt *pt) { PT_BEGIN(pt); while(1) { PT_WAIT_UNTIL(pt, function_returns_true() ); do_something(); } PT_END(pt); }
Here, the magic is happening: every time this function is called, the boolean condition of PT_WAIT_UNTIL() is evaluated again. So if this condition is a function that can return either true or false, the function_returns_true() is called and if the return value is not true, the protothread1() function stops here, until the next call. This means do_something() doesnt run this time.
Notice that protothread functions have to be declared static int.
神奇的事发生了:每次这个函数被呼叫之后, PT_WAIT_UNTIL() 里的 boolean 条件式,就被执行一次。
所以说,如果这个条件式是一个函数,就可以在执行完后返回 true 或 false 。
上面例子里的 function_returns_true() 被执行之后,如果返回值不是 true ,
protothread1() 函数就会在这里停止,一直到下一次被呼叫。
这表示 do_something() 这个函数,这次不会被执行。
注意 protothread 函数必须被定义成 Static int
And this is the way you have to initialize the protothreads and call them in your main loop
而且, 你必须按照下面举例的方式来初始化 protothreads , 以及在主回圈里面呼叫。
// every protothread needs an own struct pt variable // 每個 protothread 都需要有自已的 pt 結構變數 static struct pt pt1, pt2; int main(void) { /* Initialize the protothread state variables with PT_INIT(). */ /*使用 PT_INIT() 來初始化 protothread 狀態變數 */ PT_INIT(&pt1); PT_INIT(&pt2); /* Then we schedule the two protothreads by repeatedly calling their * protothread functions and passing a pointer to the protothread * state variables as arguments. * 然後我們 排入這兩個 protothread ,在重復執行 prtothread 函數的地方 * 然後用 參數 傳遞 protothread 狀態的指針*/ while(1) { protothread1(&pt1); protothread2(&pt2); } }
"Many of us use the switch/case construct to explicitly implement concurrent state machines in our code. The [protothread] macros merely provide a level of abstraction above that so that the code appears more linear and the overall logic more visible."
我们许多人在程序里面使用 switch/case 结构来实现状态机制.
[protothread] 宏 单纯的 提供 一个在抽象层之上的实现,
所以程序代码显然更直接,程序逻辑更容易阅读
So if this sounds interesting to you, why not download the protothread library and give it a shot?
如果以上这些引起你的兴趣, 为什么不下载 protothread 链接库,而且试它一试?
UPDATE: i also wrote an example as a tutorial, see here
更新:我也写了一个指导范例,在这里
因为域名到期的原因,开始整理、重贴一些有价值的旧文章。
原文:http://harteware.blogspot.tw/2010/11/protothread-powerfull-library.html本文原作者的另一篇指导范例翻译。
2013年9月11日 -
protothread 和 arduino 第一个简单的范例
protothread and arduino, a first easy example
I wrote a little example to demonstrate the possibilities of protothread and an avr µc. I decided to use some arduino specific functions like pinMode(), digitalWrite() and digitalRead() because i think this way it is very easy to understand what's happening in the code. But you should always keep in mind that these functions are up to 50 times slower than direct port access. It's easy to change the read and write functions to direct port access later on in a project if more I/O power is needed. For me arduino is a rapid prototyping platform...
protothread 和 arduino 第一个简单的范例
我写了一个小范例来展示 protothread 的可能性以及 avr µc.
我决定使用某些 arduino 指定的函数, 像是 pinMode(), digitalWrite() 和 digitalRead()
因为我认为这方式比较容易了解程序代码里面发生了什么事.
但你在心里应该清楚的知道,这些函数是比直接操作端口慢了50次以上。
这些函数比直接操作端口 更容易改变 读写功能, 在以后需要更多 I/O 的项目更为明显。
对我来说,arduino 就是一个快速开发原型的平台...
What the code does, is that it toggles an LED every n ms using two independent protothreads for it. One pt toggles every 1000ms, the other one every 900ms. The result is an erratic blinking pattern. Download the protothread library and unpack it into your library directory, This example is already included as pde file. Restart your arduino IDE after unpacking and you will find it listed under "examples -> pt" in the "File" menue of the IDE.
我写的小范例做了些什么呢?它使用了2个 protothreads 分别每格n ms 触发 LED 。
一个 pt 每格 1000 ms 触发 , 另一个900ms .
这样的结果就是时闪时灭的模式。
下载 protothread 链接库 解压到你的 library 目录里面,这个范例已经包括了 pde 文件.
在解压完之后重开 arduino IDE , 你会看到在 file 的选单里 example ->pt 下有份范例列表 。
#include <pt.h> // include protothread library //加载 protothread 链接库 #define LEDPIN 13 // LEDPIN is a constant //LEDPIN 是个常数 static struct pt pt1, pt2; // each protothread needs one of these // 每个 protothread 需要一个 void setup() { pinMode(LEDPIN, OUTPUT); // LED init //LED 初始化 PT_INIT(&pt1); // initialise the two //初始化这两个 protothread 变数 PT_INIT(&pt2); // protothread variables } void toggleLED() { boolean ledstate = digitalRead(LEDPIN); // get LED state //读取 LED 状态 ledstate ^= 1; // toggle LED state using xor //使用xor 来改变 LED 状态 digitalWrite(LEDPIN, ledstate); // write inversed state back //写入改变后的状态 } /* This function toggles the LED after 'interval' ms passed */ /* 这个函数在 interval ms 经过后改变 LED */ static int protothread1(struct pt *pt, int interval) { static unsigned long timestamp = 0; PT_BEGIN(pt); while(1) { // never stop // 永不停止 /* each time the function is called the second boolean * argument "millis() - timestamp > interval" is re-evaluated * and if false the function exits after that. * 每次这个函数被呼叫时, 第二个 boolean 参数 "millis()-timestamp > interval " 就被执行 * 如果计算结果为 false , 就跳离 */ PT_WAIT_UNTIL(pt, millis() - timestamp > interval ); timestamp = millis(); // take a new timestamp toggleLED(); } PT_END(pt); } /* exactly the same as the protothread1 function */ /*跟 protothread1 函数一样*/ static int protothread2(struct pt *pt, int interval) { static unsigned long timestamp = 0; PT_BEGIN(pt); while(1) { PT_WAIT_UNTIL(pt, millis() - timestamp > interval ); timestamp = millis(); toggleLED(); } PT_END(pt); } void loop() { protothread1(&pt1, 900); // schedule the two protothreads //放入两个 protothread protothread2(&pt2, 1000); // by calling them infinitely //无限次数旳被呼叫 }
I hope this example is easy to understand. I will post a more complex one the next days, dealing with input and output via the serial connection and some sort of calculation, all taking place in 'quasi'parallel. Maybe some sort of clock with the possibility to set it and some periodic actions like blinking a led and printing the actual time on the console.
希望这个范例很容易了解。我将在未来几天写更复杂, 由串行端口连接的输入和输出,以及一些排序的计算,
这些都类似并行的运算。
Maybe some sort of clock with the possibility to set it and some periodic actions like blinking a led and printing the actual time on the console.
学长译注:
protothread 的确是一个很好的程式库,让 Arduino 的多执行绪成为可能。
但是又有多少 Arduino 应用的开发者,会愿意改变写作习惯来榨干 Arduino 的运行能力?我愿意!你呢?
原文:http://harteware.blogspot.tw/2010/11/protothread-and-arduino-first-easy.html
另一篇翻译文章:protothread, a powerfull library protothread 一个厉害的 arduino 多线程链接库
2013年9月10日 -
问题:Arduino 使用 delay() 时,中断能触发吗?
有天半夜,学长差不多打算睡觉时,有人问到 delay 时不知道中断还能不能触发?
答案是:可以触发。
今天又想起这件事,还有上次在论坛上也回答过一个类似的问题。
针对这个问题,我们来做一个简单的触发中断的小实验。
接线图如下:
这个接线很简单,Pin2 是 Arduino Uno 的第0号中断。使用这个中断来测试 Delay 时,中断能否触发。
按钮未按下的时候,透过 220 欧姆的电阻上拉,此时状态为 HIGH。
按钮按下的时候接地,此时状态为 LOW 。
程序如下:
void setup(){ Serial.begin(9600); attachInterrupt(0,takeThat,CHANGE); } void loop(){ //Serial.println("Cycle begin"); delay(60000); } void takeThat(){ Serial.print(millis()); Serial.println("Got It!!"); }
1. 程序由 attachInterrupt 设置中断触发时执行 takeThat() 函数。触发类型为 CHANGE 。
2. 设置完成后,开始执行 loop() 内的程序,一个60000 毫秒,长时间的 Delay 不断重覆。
执行结果如下图
实际接线图如下:
图中 Arduino控制板为 mangoII 控制板。
电阻为 220 欧姆。
完成这个实验,证明在 delay 时中断还是能验触发。并且 millis() 函数计时是运行。
最后学长提醒,在一般专案开发应该尽量避免使用 delay () 。
应该尽可能的使用时间比对的方式来计时,时间还未到指定的时候,不执行j指定的程序即可。
2013年9月9日 -
How To Handle Microsecond Rollover With Arduino’s micros() Function
如何处理 Arduino micros() 函数的微秒溢出
When timing on the Arduino, it’s common to use the millis() function,
which counts the milliseconds the current program has been running.
But sometimes you need to do things smaller – enter the microsecond timing function, micros().
当arduino 须要计时的时候, 一般是使用 millis() 函数,
millis() 计算目前程序所已经过的 毫秒(ms) 时间.
但有时候, 需要更细微的计时, 就是使用 micros(us) 计算 微秒 时间。
However, there’s a big problem: its maximum size ends up being too small for long-term programs!
然而, 有一个大问题, 就是对一个长时间执行的程序来说计时的最大值太小了。
To see how that works, consider:
the largest value that micros() can produce is 2^32-1, or 4,294,967,295. At one million ‘ticks’ per second,
we can do
来看看这样的工作情况,
试想: micros() 能处理的最大值是 2^32-1 (4,294,967,295). 一秒钟 1,000,000 次的计数可得以下结果
4,294,967,295 / 1,000,000 = 4294.967295 seconds
Before we overflow (about 71 minutes and 35 seconds).
在这个秒数之后就会溢出(大约71分钟又35秒)
However, what do we do then?
For example, what if we need to keep track,
and our last count was 4,294,967,000, and our new count is 250.
How do we figure out the change?
那我们又应该做些什么?
举例, 需要持续追踪最后的计次是否超过 4,294,967,000 又 250 次?
我们又怎么发现这个变化?
In a nutshell – the same way we always did!
简言之, 跟一般我们处理计时没什么不同!
To see how that works, consider code to check the change in time, or delta:
来看这样的情况, 让 程序代码去检查时间的变化或增量:
unsigned long oldTime=micros(); ...later on... unsigned long newTime=micros()-oldTime;
So if oldTime is 500, and newTime is 750, we have (750-500=250), which is fine. But look at our earlier example:
如果 oldTime 是 500, 並且 newTime 是 750 , 得到(750-500=250), 這樣很好.
但是再看看先前提到的情況
decimal hexadecimal 250 FA - 4,294,967,000 - FFFFFED8 ---------------- ---------------- - 4,294,966,750 00000222 (decimal 546)
What happened? In our world, we have negative numbers, so the result is negative.
But we’re working with unsigned longs – there’s only 32 bits, and no negative.
So the value rolls over like an old-fashioned odometer, and we get 546.
发生了什么事? 在 arduino 的世界里有负数, 所以结果是负数.
但是我们定义 unsigned long , 只有32位而且没有负数。
所以数值就溢出了, 像是旧式的里程表, 得到了 546 这个数值。
To see if this is right, let’s do some math:
为了确定这是正确的, 我们来算一下数学题:
- From 4,294,967,000 to 4,294,967,295 (hexadecimal FFFFFFFF) is 295
- From 4,294,967,295 to 4,294,967,296 is 1
- But 4,294,967,296 doesn’t exist in an unsigned long,
- so it rolls over to zero So, from 0 to 250 is 250
- 295 + 1 + 250 is 546 – the result we expect!
- 从 4,294,967,000 到 4,294,967,295 (十六进制 FFFFFFFF) 是 295
- 从 4,294,967,295 到 4,294,967,296 是 1
- 但 4,294,967,296 不存在于 unsigned long,
- 所以就溢出为 0 所以, 从 0 到 250 是 250
- 295 + 1 + 250 是 546 – 我们得到的结果
So it would seem we can ignore rollover when we do time calculations, and use the result no matter what.
看起来当计算时间的时候可以忽略溢出, 不管如何都能直接使用结果。
Not quite:
其实不全然如此:
For this to work, we MUST have an ‘oldTime’ that occurred in the last 71.5 minutes.
If we cycle over two or more times, the difference is meaningless;
since we can’t ‘hold’ multiple 71.5 minutes cycles in an unsigned long, we can’t figure out the whole time interval.
The value as mentioned must be an unsigned long, which rolls over after FFFFFFFF.
这能正常工作,必须 有个 'oldTime' 变量在上一个 71.5 分钟内.
如果我们在多于两个以上的循环, 就变得无意义了
因为我们不能用同一个 unsigned long 处理多个71.5 分钟的循环, 无法确定这样的时间间隔。
之前提到的值必要是 unsigned long, 在 FFFFFFFF 之后溢出.
However, if we keep this in mind, this means that as long as we time frequently enough, code like
unsigned long newTime=micros()-oldTime;
will work!
然而, 如果我们记得这些限制, 表示只要我们时间频率是足够的, 程序代码
unsigned long newTime=micros()-oldTime;
就能正常工作。
学长译注:
原文:
How To Handle Microsecond Rollover With Arduino’s micros() Function
2013年9月3日 -
Arduino 的 PWM (脉冲宽度调制) 说明-官网翻译,适合新手。
PWM (脉冲宽度调制)
The Fading example demonstrates the use of analog output (PWM) to fade an LED. It is available in the File->Sketchbook->Examples->Analog menu of the Arduino software.
Fading 的示例展示了使用模拟输出(PWM)来让 LED 渐暗,可以从 Arduino-IDE 的菜单 File->Sketchbook->Examples->Analog 打开。
Pulse Width Modulation, or PWM, is a technique for getting analog results with digital means. Digital control is used to create a square wave, a signal switched between on and off. This on-off pattern can simulate voltages in between full on (5 Volts) and off (0 Volts) by changing the portion of the time the signal spends on versus the time that the signal spends off. The duration of "on time" is called the pulse width. To get varying analog values, you change, or modulate, that pulse width. If you repeat this on-off pattern fast enough with an LED for example, the result is as if the signal is a steady voltage between 0 and 5v controlling the brightness of the LED.
脉冲宽度调制或称PWM,是通过数字均值获得模拟结果的技术。数字控制被用来创建一个方波,信号在开和关之间切换。这种开关模式通过改变“开”时间段和“关”时间段的比值完全模拟从开(5伏特)和关(0伏特)之间的电压。“开时间“的周期称为脉冲宽度。为了得到不同的模拟值,你可以更改,或调节脉冲宽度。如果你重复这种开关模式速度足够快,其结果是一个介于0和5V之间的稳定电压用以控制LED的亮度。
In the graphic below, the green lines represent a regular time period. This duration or period is the inverse of the PWM frequency. In other words, with Arduino's PWM frequency at about 500Hz, the green lines would measure 2 milliseconds each. A call to analogWrite() is on a scale of 0 - 255, such that analogWrite(255) requests a 100% duty cycle (always on), and analogWrite(127) is a 50% duty cycle (on half the time) for example.
下图中,绿色线表示一个固定的时间期限。此持续时间或周期是PWM的频率的倒数。换言之,Arduino的PWM频率约为500Hz,每个绿线之间表示2毫秒。一个analogWrite()的调用区间为0- 255,例如analogWrite(255)需要100%占空比(常开),和analogWrite(127)是50%占空比(上一半的时间)。
Once you get this example running, grab your arduino and shake it back and forth. What you are doing here is essentially mapping time across the space. To our eyes, the movement blurs each LED blink into a line. As the LED fades in and out, those little lines will grow and shrink in length. Now you are seeing the pulse width.
一旦你运行这个例子,抓住你的Arduino来回摇晃。你这么做的实质上就是时间对空间的映射。在我们的眼睛看来,每个运动模糊成一条线的LED闪烁。由于LED渐亮和渐暗,那些LED 形成的线的长度会增长和缩短。现在你看到的就是脉冲宽度。
译注,最后一段翻得的不是很好,但时间所迫也只能先这样,有空再修改吧。
2013年9月1日 -
在 linux 环境下编译 ATxmega booloader
受弘毅先生之邀,共同研究 xmega bootloader 之编译,
原本对 bootloader 不甚了解,经过此次的研究,
终於对 bootloader 的功能、及编译,有更深入的了解。
虽然无法自已编写 bootloader ,
但对於应用开发为主的我来说,已经足够。
linux 使用的是 ubuntu 13.04 桌面版,是英文版。
1. sudo apt-get install gcc-avr
2. gcc-avr done
3. sudo apt-get install avr-libc
4. sudo apt-get install gdb-avr
5. sudo apt-get install avrdude
6. make done
本次编译 bootloader 最花时间的是在於了解芯片的各项功能,并且取得相关的配置参数。
查询资料就花了约两天的时间。
不包括 windows 平台 avr 编程环境的配置时间,约2个钟头以失败收场。
而配置 linux 的 avr 编程环境到编译成功 只花了约 1 个钟头左右。
只能说是造化弄人。
至此 bootloader 已经编译完成,xboot.hex 即是。
2013年8月30日 -
上次的图没附折解 这次补上
将爪子从中分开,分为上下两部份,左边是下半部爪子,右边是上半部带中轴跟大齿轮
分开后不同角度
左边把三根手指从三叉轴上拆下,右边再把中轴跟大齿轮拆开
先看右半边,三张三叉轴的拆图。就是把支架拆开而已
再来看左边,手指怎麽拆。
上连续图,不解释喽。
最后所有零件整理一下。
再补两张,未拆之前的图。
Mindstorm EV3 开箱 http://www.985z.com/?post=14
9695 开箱 http://www.985z.com/?post=15
2013年8月30日 -
声纳测距模块又称为超声波测距模块,这个实验所使用的是 HC-SR04 型号的模块。
在实际使用这个模块之前,先大致了解声波测距的过程。
1. 触发发射器发射声波。
2. 拉高 Echo 脚。
3. 声波受到阻碍反射。
4. 接收器收到反射的声波。
5. 下拉 Echo 脚。
因此,只要计算声波移动的速度乘上 Echo 脚上拉的时间,
就可以得到声波从发出之后经过反射到接收器的距离。
在模块跟障碍物静止的状态下,发射器到障碍物的距离跟障碍物到接收器的距离是相等的,
所以我们只要简单的把总距离除以二,就可以得到模块到障碍物的实际距离。
在这个实验里,我们使用 Ardublock 来做为我们的编程环境。
这也是 Ardublock 有利於 Arduino 入门新手的因素之一,在完成 ardublock 编程之后再行解释。
打开 Ardublock 之后,根据 Arduino 的编程经验,第一个想要找的函数就是 pulseIn() 函数,
用来计算 Echo 脚上拉的时间。
在寻找 block 时发现 Ardublock 竟然提供了 ultrasonic 模块,所以我们就直接拉出 ultrasonic 模块,如下图。
如图,我们观察到此模块有两个数入参数,一个输出。
Trigger 参数是触发声波的引脚号码,尖型输入表示类型为数值
Echo 参数是Echo 引脚号码,同样是数值类型。
这个模块的输出,此时我们只知道是数值,是什么单位目前还不知道。
此时,要将输出透过串口打印到串口监视器上。将 Utilities 分类下的 Serial println 所代表的 block 拉出。如下图
我们发现 Serial println 及所属的 message 这两个block ,只能接受的方形的输入,代表只能接受字串或字符的输入。
因此,我们再拉出另一个 block :Number/Constants 分类下的 glue ,
glue 有两个 block ,其中我们要的是接受数值型态输入的 glue ,如下图。
这个 glue ,就是把数值的输入转换为字串后再输出给其他 block 。
接下来,按照步骤将这三个组 block 组好。
1. Ultrasonic 的 Trigger 引脚号改为如接线图所示的12,Echo 脚为11 。
2. 将Ultrasonic 输出接到 glue 的输入。
3. 将 glue 的输出,接到 Serial println 所附属的蓝色 message 方块的输入。
4. 将 message 方块改为“Ultrasonic Inpur:“。
完成之后如下图
最后拉出主循环,把这组已经完成的逻辑方块,放入主循环。如下图:
储存后,按下 Upload 产生程序代码,如下图。
程序如下:
int ardublockUltrasonicSensorCodeAutoGeneratedReturnCM(int trigPin, int echoPin) { int duration; pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(20); digitalWrite(trigPin, LOW); duration = pulseIn(echoPin, HIGH); duration = duration / 59; return duration; } void setup() { digitalWrite( 12 , LOW ); Serial.begin(9600); } void loop() { Serial.print( "Ultrasonic Inpur:" ); Serial.print( ardublockUltrasonicSensorCodeAutoGeneratedReturnCM( 12 , 11 ) ); Serial.println(""); }
一个新手因为经验不足,不可能同时写出函数,又同时计进行计算、控制引脚,
先使用 ardublock 进行逻辑编排产生可执行的代码之后,
再对代码进行学习、优化,这就是刚刚所说的有利於新手的最大因素。
新手在分析程序时可以学习这里的方式,
将程序分成三个部份来看,第一个是 setup() ,第二个是 loop() ,
第三个是 ardublock 生成的 ardublockUltrasonicSensorCodeAutoGeneratedReturnCM() 。
按照顺序先看 setup() ,函数里面将12号数位引脚也就是触发引脚(trig)下拉,
以准备等下让程序触发声波。然後设置串口的 baud rate 为 9600bps 。
loop() 这个函数,就是对应到 ardublock 里所提到的主循环,
功能就是不断的在串口监听器打印出Ultrasonic 模块的输出值。
loop() 的第一行是 print 我们输入在 message 方块里面的字串 Ultrasonic Inpur:
第二行可以看到,print 出 ardublockUltrasonicSensorCodeAutoGeneratedReturnCM(12,11) 的结果。12是我们的 Trig 引脚, 11 是 Echo 。
第三行是空的 println ,这个是打印之后换行,打印内容可以输入单位。
虽然从函数名称上看出返回的单位是 CM ,可以先填入单位:公分,但是更应该证实输出的单位后再填入。
接下来就是重点, ardublockUltrasonicSensorCodeAutoGeneratedReturnCM() 函数,其参数我们已经说明过。
第四行到第八行,就是以一个20 us 的高位脉冲触发声波发射。
第九行 将收到的 Echo 时间长度储存到 duration 变量里面。
第十行 将 duration 除以 59 所得到的结果就是 CM ..... 哈哈。
这个 59 怎麽来的?
这个要从头说起,音速的公式为 音速c = 331.5 + (0.6*气温摄式度)。
以室温来说(不知道谁规定的,只要提到室温就是指20度) 音速 = 331.5 +(0.6*20) = 343.5 m/s
这个数值对我们来说实在是是太,HC-SR04 的最远距离官方表示只有 450CM 。
因此,单位转换为 cm/us 较适合我们使用,室温下音速 334.5 * 100 /1000000 = 0.03435 cm/us
音速的另一个表示的方法为 us/cm ,含意为音速每前进一公分花费多少时间。
1/0.03435 = 29.1 us/cm
回到本实验的最前面测距的公式:距离D = (总时间t/2) * 音速c(cm/us)
将这个公式再修正,则为 距离D = (总时间t/2) * 1/0.03435 = (总时间t/2) /29.1 = (总时间t/58.2)
这里计算出来的 58.2 就是自动生成的 59 的由来,但为什麽是 59 呢?
除了温度的变化外还有另一个因素,就是约定俗成。一般常用的音速,不论温度,就是 340 m/s 。
以 340 m/s 计算,1/0.0340 = 29.411 us/cm ,取小数点后1位,采无条件进位,就是 29.5 us/cm ,
则 距离D (总时间t/2) /29.5 = (总时间t/59) ,这就是由来啦。
好啦,讲这麽多 都头晕了。
将程序编译后上传,用直径超过 3mm 的圆柱体,测试距离。所得结果如下图
上图 单位为公分,各位记得填上就行啦。
以下为实验照片
本实验主旨在於讲解 超声波传感器的使用,以及 ardublock 图形化编程。
有关进阶使用,请参考另一篇:
使用 NewPing 程序库操作 HC-SR04 声纳传感器
如果实验过程有疑问,请多多提问。
2013年8月28日 -
用乐高 9695 零件做的爪子 绝对原创
还要改良,照片先做纪念
2013年8月23日
最新评论