前言

國中上物理課的時候,大家耳熟能詳的一個題目一定是:請問外面下雨打雷中,突然一道閃電劃空而過,然後你手上剛好有個碼錶開始計時。最後經過 4.7秒後你聽到雷的聲音了。今日室外的溫度計刻度是 23.7度C,請問跟估計一下閃電發生的距離離你多遠啊??

這時候老師會叫你背一下聲音在空氣中的速度公式,聲速 (m/sec) = 331 + 0.6 * T (攝氏溫度)

所以目前聲速為 331+ 0.6 * 23.7 = 345.22 m/s

然後因為 4.7秒後聽到雷聲,所以落雷處跟你的距離為 4.7*345.22 = 1622.534 公尺 = 1.622534 公里 (答案)

所以 331 + 0.6T 這個聲速公式,大家應該再熟悉也不過了!

關於超音波,就是超過 24kHz 頻率以上的聲波,因為頻率超出人耳所能感受的程度,人耳是聽不到的,所以叫超聲波。

最有名關於超聲波常聽到的是蝙蝠這個動物。因為它可以發出超聲波,然後藉由聲波撞擊物體回彈後,由這個時間差來得知物體的遠近。所以即使在暗無天日的山洞裡,無需眼睛,蝙蝠藉由超聲波來探知環境,依舊可以自由自在的飛行。

聲速如前面所示跟溫度有關,溫度越高,聲音的速度越快。假如我們像蝙蝠一樣用超音波來做距離的量測,超音波往返的時間除以二乘上當時聲音的速度就是量測的距離。假如我們的距離永遠是固定的,那量測到的時間就只跟當時聲音的速度有關,聲音速度越快的時候時間越短。但溫度越高的時候,聲音的速度越快。慢著,「溫度越高的時候,聲音的速度越快」,這不是溫度計了嗎。

量測到的時間越短,代表聲音的速度越快,意味著此時空氣的溫度越高。所以量測到的時間,就是空氣的溫度表現囉。

哈,讓我們來用這個原理,來製作一個靠量測聲音速度來得知溫度,很有趣的,超音波溫度計吧。

 

HC-SR04 超音波距離感測器

有玩過 Arduino 的一定會玩過這個感測器啦。HC-SR04是個超音波發射跟接收的一個雙重感測器,它可以像蝙蝠一樣,向周圍環境發射40kHz的超音波。超音波撞擊到物體後會反彈回來,會被HC-SR04 的另一個接收器接收到。藉由計算超音波往返之間的時間差,最後可以得知物體的距離。 (Datasheet 在這裏)

所以原來的 HC-SR04 是被設計來利用超音波來量測距離的。

HC SR04-4.JPG

像一隻 ET 有兩個眼睛,很可愛的 HC-SR04。 標示 T 的那顆眼睛負責發射超音波,標示 R 的那顆眼睛負責接收超音波。

一共有四根接腳,分別是 Vcc電源, Trig, Echo, 跟 GND接地

 

使用方式很簡單,

Pulse diagram.png

Trig 腳位送上 10uS 寬度的 TTL High 訊號後, HC-SR04 的發射端會開始發送8個 40kHz頻率的超音波脈波。

當最後一個脈波送出去後, Echo 的腳位會被拉 High。直到接收到最後一個超音波脈波回來後, Echo 的腳位訊號才會被拉 Low。所以藉由量測 Echo腳位拉High的時間,即可得知超音波往返的時間,進而得知物體的距離遠近 L。

HC SR04.png

 

讓我們來玩一玩這個感測器,下圖為接上 Arduino Uno 的狀況

Trig 給他接到 Arduino Uno Pin2

Echo 給他接到 Arduino Uno Pin3

HC SR04-1.JPG

HC SR04-3.JPG

HC SR04-2.JPG

 

 

FORTH 程式解說

這個部落格是要盡可能的來推廣 FORTH 語言的,所以這次我們不用 Arduino IDE 來撰寫程式,而是採用 Flash FORTH。

 

1. 距離量測

前面提到,要觸發 HC-SR04 開始運作,需要先有一個 10uS High 的 DIO 訊號。 uS 微秒可是 1E-6 次方秒耶,時間非常非常的短,已經開始接近機械語言原生的執行速度囉。 Flash FORTH 只有提供標準 mS 毫秒 1E-3 次方秒的指令, uS 微秒是沒有提供的。 (嚴格來說,其實是有的,開發者有提供一個版本 uS 的程式碼放在外部檔案給有需要的人。)

沒有提供也沒關係,我們可以很簡單的寫一個類似的,就

: delay ( u --) for next ;

很簡單的一個空的 for-loop 就可以拿來做 delay 啦。

不過要測試一下, 量測一下它真實的執行秒數。

: t  ticks $ffff delay ticks  swap - . ;

ticks 會把現在的毫秒(mS)為單位的 tick 數目取出來,然後執行 0xFFFF = 65535 次的 delay,然後再取一次現在的毫秒(mS)為單位的 tick 數目,兩個相減就是 65535 次的 delay 執行所花的毫秒(mS)時間。

delay time test.png

經過多次執行 t 後,數據是 94.5 mS。所以每次的迴圈時間是

94.5 ms / 65535 = 1.44198 uS

所以假如需要 10 uS 的 delay

10 uS / 1.44198 uS = 6.934 大約等於 7

所以 7 delay ,就會得到 10 uS 的延遲時間。

要啟動 HC-SR04 開始量測,需要在 Trig 腳位,送上 10 uS 的 High 訊號,程式碼如下

: trig ( --)     \ trigger HCSR04 to work
   trigPin ->High
   7 delay       \ 10us/1.44198us = 6.934 ~ 7
   trigPin ->Low
;

Trigger HC-SR04 開始工作後,接下來 Echo 腳位的回傳 High,表示8個 40kHz 的超聲波已經傳送出去囉,等待接收反射回來的超聲波訊號。

: wait ( --)     \ wait echo signal to high
   begin  echoPin Pin@  until
;

很間單,begin-until 迴圈讀 echoPin 直到它不為零. (0=Low, 其他為High)

 

EchoPin為 High 的時候,就要開始計時囉。因為 EchoPin 為 Low 的時候代表接收到回傳回來的脈波。所以 High 所維持的時間,就是超聲波傳送到待側物,然後反彈回來的旅行時間。

計時的程式碼如下

: echo@ ( -- width)  \ measure echo pulse width, 0 = failed
   $ffff
   for echoPin Pin@  0=  \ per loop time = 747 ms/65535 = 11.3985 us
       if  $ffff r> - exit then
   next  0
;

用個 0xFFFF (65535) 的最大總數的 for-next 迴圈來計時。

首先讀取 echoPin ,來看說是不是已經 Low 了。如果不是 Low,就繼續 for-loop 計數。最後,假如所有 65535 都數完了,都還沒有 Low, 那肯定是有問題的囉。放個 0 回傳,表示有問題。

假如遇到 echoPin 變成 Low 了,表示有收到回傳波囉。 然後這個 for-next 的計數數目,就是這個 EchoPin 脈波的寬度跟時間囉。

for-next 不是很標準 ANSI FORTH 的指令,然後一般來說 for-next 的計數器應該都是從 0 開始計數的。 Flash FORTH 為了速度,結果它的 for-next 是倒數的。 😅

例如 10 for next 的結果,會是 9, 8, 7, ..., 0 。所以要得到目前的計數數,還得用原來的總數再減一次才會是正確的數字。

所以當 echoPin 變成 Low 了,表示有收到回傳波囉。先 r> 得到目前倒數的數目,然後用 $ffff (65535) 去減,才會得到正確的計數數目。 r> 順便把 for-next 在返回堆疊所建立的結構拆掉清乾淨 ,然後 exit 的強迫停止執行並跳出這個指令。

就這樣, echoPin High 的寬度就量出來囉。但這是 for-next 執行的計數,不是真實的時間。要想辦法化成真正的時間。

所以,定義 tt 來測試一下

: tt              \ test the loop running time on unused Pin5
   Pin5 <INPUT
   Pin5 PULLUP ->High
   
   ticks
   $ffff          \ $ffff full loop will take 747ms on 16Mhz Uno
   for Pin5 Pin@  0=
       if  $ffff r> - exit then
   next
   ticks swap - . cr
;

tt 會開啟 Pin5,接上內建上拉電阻,所以永遠是 High。

然後一模一樣的迴圈程式碼,測試一下執行 0xFFFF (65535) 下所需要花費的時間為多少。

dio read time test.png

 

經過測量後,會是 747 mS

所以一個 for-next 迴圈所花費的時間等於  747 mS / 65535 = 11.3985 uS

 

例如當執行 echo@,結果得到 143 這樣數值的迴圈執行數時,這代表 echo 脈波 High 的寬度為 143 * 11.3985 uS = 1629.9885 uS = 1.63 mS.

所以觸發一次完整的超音波距離量測,程式碼

: hcsr04@ ( -- width)
   trig wait echo@
;

就 trig 產生 10uS 的觸發脈波, wait 等待 echo pin腳回傳 High, echo@ 將量測到的,脈波 High 的寬度給傳回來。

接下來要算距離囉,大家朗朗上口的,聲音的速度 speed = 331 + 0.6*T (攝氏溫度)  m/sec

現在是夏天,所以室內溫度 30 C 很正常, speed = 331 + 0.6*30 = 349 m/sec

距離/速度 = 時間。再來,聲音一來一回,所以跑了兩倍的距離。所以 真實的距離(公尺) = 1/2 * 速度 * 時間 = 1/2 * 349 m/sec * 時間(秒)

我們 hcsr04@ 量到的是 for-next 的 cycle 數。又,我們剛剛有量過了,1個 for-next 的 cycle 花費 11.3985 uS

所以 時間(秒) = cycle * 11.3985E-6

全部合起來就是, 真實的距離(公釐) = 1/2 * 349 * cycle * 11.3985E-6 *1.0E3 = 1.98903845 cycle

我們把 1.9890 設為一個比例常數,所以

dist (mm) = cycle * 19890 / 10000

 

19890 constant ratio


: >dist ( width -- mm )
   ratio 10000 u*/mod nip
;

 >dist 給定 cycle 數後,利用比例算數 u*/mod 乘上 ratio 後除以 10000 得到距離的結果。

 

所以,完整的距離量測

: dist@ ( -- u) 
   hcsr04@  >dist
;

 

列印的指令,距離是 mm,但我們比較熟悉 cm。所以給它加上一個小數點就是囉。

: .[xxx.x] ( dist --)
   0 <# # [char] . hold #s #> type
;

 

最後,連續的量測跟列印結果

: measure
   init
   begin  ." dist = " dist@ .[xxx.x] ." cm" cr
   again
;

 

2. 超音波溫度計

這時候,我們要倒過來算了。距離是以已知且固定不變的。利用量測聲音來回的時間算出此時聲音的速度,再利用這個聲音的速度來算出得知此時空氣分子的溫度囉。

公式推導,假設距離 L,超音波來回的時間為 t,空氣溫度為 T

speed = 2*L / t = 331 + 0.6 T

所以 T = 1/0.6 *(2*L/t - 331) = 5/3*(2*L/t -331)

L 用 1公尺,是非常合理的距離。所以

溫度 T = 5/3 * (2/t - 331) = 5/3 * (2/(cycle*11.3985E-6) - 331) = 5/3*(175461.68/cycle-331) = 292436.139/cycle - 551.667

取一位小數點的話

10*T = 2924361./cycle - 5517

這段化成程式碼為

: >temp  ( width -- Celsius)
    2924361. rot um/mod nip  5517 -
;

2924361 這個數字超過 65535 囉,所以用兩個 word 的雙整數來處理它。

FORTH 語言的規則,只要數字上面有小數點,就會被視為雙整數。 所以給它加上ㄧ點 2924361. 就會是佔兩個堆疊空間的雙整數囉。

rot 把 width (cycle) 翻進來, um/mod 是個雙整數除以單整數的混合除法指令。除完之後,nip 把餘數丟掉,再跟 5517 相減,這樣就完成時間轉成攝氏溫度的計算囉。

 

所以,完整的溫度量測

: temp@ ( -- Celsius)
   hcsr04@  >temp
;

 

連續的溫度量測

: thermometer
   init
   temp@  ( init value)
   begin 49 *   temp@ +  50 u/   ( 50 points average)
      ." temperature = " dup .[xxx.x]  ." C" cr
   again
;

一開始測試的時候只量測單一值,可是發現因為距離一公尺,這個超音波量測比較不穩定,太敏感,變異太大了。

所以採用 50次的平均來降低誤差。

進 begin-again 迴圈前先 temp@ 量個初值,進迴圈之後每次的量測值都用 1/50 的加權平均的方式被合併進來總平均值。這樣就可以降低不穩定所帶來的誤差,整體會逐漸收斂到一個群體的平均值。

 

 

測試結果

 

1. 距離量測,

典型的 HC-SR04 的應用,超音波距離量測儀。測試運作OK

dist result.png

測試影片

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Frank Lin(@ohiyooo)分享的貼文

 

 

2. 超音波溫度計,

拿一個溫度計在旁邊當校正源,適當調整發射距離(在一公尺附近)直到溫度跟校正源所顯示一致時就OK喔!

正常應該做個支架來固定,可以做個很酷的一公尺高的支架,然後讓感測器往地上發送超音波,這樣一定很酷啊,完全用聲音來量測空氣溫度的溫度計啊。放在客廳當擺飾,一定酷呆啦!有客人來問,就可以很臭屁地跟他說,哦,這個是靠發出人耳聽不到的聲音來量溫度的東東哦!

不過這裡只是簡單測試用,好玩就好啦!

thermometer result.png

測試影片

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Frank Lin(@ohiyooo)分享的貼文

 

 

 

 

原始程式列表

\
\  Ultra Sonic Sensor HC-SR04
\      Frank Lin 2021.7.23
\


marker --uno_dio--

\
\ bit masks
\


%00000001 constant bit0
%00000010 constant bit1
%00000100 constant bit2
%00001000 constant bit3
%00010000 constant bit4
%00100000 constant bit5
%01000000 constant bit6
%10000000 constant bit7


\
\ Atmega328 Digital I/O Registers
\

#37 constant PortB
#40 constant PortC
#43 constant PortD


: Pin_Def ( mask Port "Name" --)
   create swap c, c,
   does>  dup  c@        ( mask)
          swap char+ c@  ( Port)
;


\
\  Arduino Uno with ATmega328 Pin definations
\


flash

bit0 PortD  Pin_Def Pin0
bit1 PortD  Pin_Def Pin1
bit2 PortD  Pin_Def Pin2
bit3 PortD  Pin_Def Pin3
bit4 PortD  Pin_Def Pin4
bit5 PortD  Pin_Def Pin5
bit6 PortD  Pin_Def Pin6
bit7 PortD  Pin_Def Pin7

bit0 PortB  Pin_Def Pin8
bit1 PortB  Pin_Def Pin9
bit2 PortB  Pin_Def Pin10
bit3 PortB  Pin_Def Pin11
bit4 PortB  Pin_Def Pin12
bit5 PortB  Pin_Def Pin13

bit0 PortC  Pin_Def Pin14
bit1 PortC  Pin_Def Pin15
bit2 PortC  Pin_Def Pin16
bit3 PortC  Pin_Def Pin17
bit4 PortC  Pin_Def Pin18
bit5 PortC  Pin_Def Pin19

 

\
\   Digital I/O Access Codes
\


: >OUTPUT ( mask port --)   \ set the direction of digital I/O to output
   1-     ( DDR register)
   mset   ( set to High = OUTPUT)
;

: <INPUT  ( mask port --)   \ set the direction of digital I/O to input
   1-     ( DDR register)
   mclr   ( set to Low = INPUT)
;

: PULLUP ; immediate  ( --) \ dummy word


: ->High ( mask port --)   \ put digital I/O to High
   mset
;

: ->Low  ( mask port --)   \ put digital I/O to Low
   mclr
;

: Pin@ ( mask port -- status)   \ read the state of digital I/O, 0=Low
   2-     ( Pin register)
   mtst   ( read the state)
;


\
\  Ultra Sonic HC-SR04 Control
\
\
\ Pin2 = Trig
\ Pin3 = Echo
\


marker --sr04--

 

flash 

Pin2 Pin_Def trigPin
Pin3 Pin_Def echoPin

 

: delay ( u --) for next ;

: t  ticks $ffff delay ticks  swap - . ;

\
\  after tested on my 16MHz Uno, $ffff delay will take
\  about 94.5 ms
\  1 loop will take 94.5ms / 65535 = 1.44198us
\


: trig ( --)     \ trigger HCSR04 to work
   trigPin ->High
   7 delay       \ 10us/1.44198us = 6.934 ~ 7
   trigPin ->Low
;


: wait ( --)     \ wait echo signal to high
   begin  echoPin Pin@  until
;


: tt              \ test the loop running time on unused Pin5
   Pin5 <INPUT
   Pin5 PULLUP ->High
   
   ticks
   $ffff          \ $ffff full loop will take 747ms on 16Mhz Uno
   for Pin5 Pin@  0=
       if  $ffff r> - exit then
   next
   ticks swap - . cr
;

 

: echo@ ( -- width)  \ measure echo pulse width, 0 = failed
   $ffff
   for echoPin Pin@  0=  \ per loop time = 747 ms/65535 = 11.3985 us
       if  $ffff r> - exit then
   next  0
;


\
\   speed of sound is 0.0349 meters/ms @ 30C
\                     34.9 mm/ms
\   dist = width * 0.0113985 * 34.9 / 2 = width * 0.198904
\

19890 constant ratio


: >dist ( width -- mm )
   ratio 10000 u*/mod nip
;

: init
   trigPin >OUTPUT
   echoPin <INPUT
;

: hcsr04@ ( -- width)
   trig wait echo@
;
   
: dist@ ( -- u) 
   hcsr04@  >dist
;


: .[xxx.x] ( dist --)
   0 <# # [char] . hold #s #> type
;


: measure
   init
   begin  ." dist = " dist@ .[xxx.x] ." cm" cr
   again
;


\
\   Ultra Sonic Thermometer
\

\
\ 2L/speed = t,  speed = 331 + 0.6 T
\ 2L/t = speed = 331 + 0.6 T
\
\ So, T = 1/0.6* (2L/t -331) = 5/3*(2L/t-331)
\ if L = 1 meter
\ T = 5/3*(2/t-331)
\
\ 1 cycle on loop = 11.3985 uS
\ So,
\ T = 5/3*(2/(c*11.3985E-6)-331)
\   = 5/3*(175461.683/c-331)
\   = 292436.139/c - 551.666
\
\ 10*T =2924361/c - 5517
\      


: >temp  ( width -- Celsius)
    2924361. rot um/mod nip  5517 -
;


: temp@ ( -- Celsius)
   hcsr04@  >temp
;

: thermometer
   init
   temp@  ( init value)
   begin 49 *   temp@ +  50 u/   ( 50 points average)
      ." temperature = " dup .[xxx.x]  ." C" cr
   again
;

 

   

 

 

arrow
arrow

    ohiyooo2 發表在 痞客邦 留言(0) 人氣()