goo blog サービス終了のお知らせ 

JH7UBCブログ

アマチュア無線 電子工作 家庭菜園など趣味のブログです

Raspberry Pi Pico MicroPython HC-SR04 距離の測定

2021-12-13 12:35:36 | Raspberry Pi Pico
 Raspberry Pi Picoで、超音波センサーHC-SR04を使って距離を測定するプログラムをMicroPythonで組んでみます。

 MicroPythonでのプログラムは、既にmicro:bitで実験済みです。記事はこちら。測定原理はその記事を参照してください。なお、今回は、Pico内蔵の温度センサーから温度を読み取り、音速を計算します。

回路図です。表示はOLEDを使い、I2Cは、id=0,sda=Pin(16),scl=Pin(17)とします。HC-SR04のTRIGはGPIO14に、ECHOはGPIO15に接続します。HC-SR04からのECHO出力は、5Vなので、抵抗で分圧してPicoに入力します。



 TRIGに加えるパルスは、10us以上となっていますので、
 trigger=Pin(14,Pin.OUT)  とし、
 trigger.value(1)
 utime.sleep_us(2)
 rtigger.value(0)
 で、triggerパルスをCH-SR04に出力します。この時のパルスの波形です。

 約20us幅のパルスが発生しました。(パルス幅は若干広くなったり狭くなったり、ゆらぎます。)utime.sleep_us()を省略するとパルス幅は、10us以下になってしまいます。

 HC-SR04から帰ってくるECHOパルスを測定してみました。


 echoパルスの立ち上がりの時刻t1と立下りの時刻t2をutime.ticks_us()で、us単位で測定し、t=t2-t1からechoパルスの幅(us)を計算することができます。

 tは、HC-SR04から発せられた超音波が戻ってくるまでの時間で、距離の2倍を音が進む時間です。従って、音速をVs(m/s)、HC-SR04と物体の距離をL(m)とするとL(m)=Vs(m/s)×t(s)/2で計算できます。
距離をL(cm)、t(us)とすると、L(cm)=(Vs(m/s)×100×t(us)/1000000)/2=Vs(m/s)×t(us)/20000で計算することができます。なお、音速は温度によって変わり、Vs(m/s)=331.5+0.6×temp(℃)で計算できます。

 スクリプトです。
-----------------------------------------------------------------------------------------------
from machine import ADC,Pin,I2C
import utime
import ssd1306

trigger=Pin(14,Pin.OUT)
echo=Pin(15,Pin.IN)

i2c=I2C(0,sda=Pin(16),scl=Pin(17),freq=400000)
oled=ssd1306.SSD1306_I2C(128,64,i2c)
oled.text("RP2 HC-SR04 TEST",0,8)
oled.text("TEMP:      C",18,24)
oled.text("L(cm):",18,40)
oled.show()

trigger.value(0)#Triggerの初期値0

coeff=3.3/65535
a=ADC(4)

def mesurement():
     global temp,L
     #Trigger pulse >10us
     trigger.value(1)
     utime.sleep_us(2)
     trigger.value(0)
    #反射時間の測定
     while echo.value()==0:
         t1=utime.ticks_us()
     while echo.value()==1:
         t2=utime.ticks_us()
     t=t2-t1

    #気温の測定(pico内部の温度計を利用)
     v=a.read_u16()*coeff
     temp=round((34-(v-0.706)/0.001721),1)
    #距離の計算
     vs=331.5+0.6*temp#音速の計算
     L=round((vs*t/20000),1)#L(cm)

def display():
     global temp,L
     oled.fill_rect(72,24,32,8,0)
     oled.show()
     oled.text(str(temp),72,24)
     oled.show()
     if L < 2000:
         oled.fill_rect(72,40,56,8,0)
         oled.show()
         oled.text(str(L),72,40,)
         oled.show()

while True:
     mesurement()
     display()
     utime.sleep(1)

-----------------------------------------------------------------------------------------------
 Picoの内蔵温度計の値はADC(4)の電圧として読み取れます。その値を温度に変換する式は
 27-(v-0.706)/0.001721 ですが、実際より低い値になってしまいますので、便宜的に27を34に変更しました。
 1secごとに距離を測定して、OLEDに表示します。

 ブレッドボードです。


 温度の値は1℃以内で細かく変動します。

 実際に物体との距離を測定してみました。距離の表示も1cm以内で細かく変動します。

測定値です。
距離(cm) 測定値(cm)
 10    10.3
 20    20.6
 30    30.1
 40    39.9
 50    49.9
 60    59.4
 70    69.5
 80    79.0
 90    88.8
100    98.9

 micro:bitの時より、かなり精度が良くなっています。1m以内なら±5mm程度の誤差で測定ができそうです。


Raspberry Pi Pico MicroPython 周波数カウンタ

2021-12-10 20:23:14 | Raspberry Pi Pico
 Raspberry Pico Pi をMicroPythonでプログラミングして、周波数カウンタを作ってみます。

 回路図です。入力信号を簡単なアンプで増幅して、GPIO15に加えます。
 入力波形の立ち上がり(0→1)をカウントします。Timer割込みで、1secごとに1sec間のカウント数をOLEDに表示します。


ブレッドボードです。入力ゲートが開いているとき、内蔵のLED(Pin25に接続されている)が点灯します。


 スクリプトです。1sec間ゲートを開け(gate_flag=1)、信号の立ち上がりをでカウントします。1sec経ったら、ゲートを閉じ(gate_flag=0)、count数をOLEDに表示します。ゲートのコントロールはTimer割込みで、1secごとに割込みを発生させています。
 OLEDを使いますので、ssd1306ライブラリをインストールしておき、このスクリプトは、main.pyという名前でPicoに保存します。
 数字を右寄せで表示したいのですが、MicroPythonでは、右揃え関数rjust()が使えないので、文字数を数え、文字数が足りないときは、左側にスペースを入れるプログラムにしました。
-----------------------------------------------------------------------------------------------------
from machine import Timer,Pin,I2C
import utime
import ssd1306

led = Pin(25, Pin.OUT)
sig = Pin(15,Pin.IN)

i2c=I2C(0,sda=Pin(16),scl=Pin(17),freq=400000)
oled=ssd1306.SSD1306_I2C(128,64,i2c)
oled.text("RP2 Fre Counter",0,8)
oled.text("Hz",96,24)
oled.show()
led.value(0)

#Timer生成
timer = Timer()
#counter関係設定
counter = 0
old_sig = 0
gate_flag=0

def gate_ctrl(timer):
     global led,counter,gate_flag
     gate_flag = 1 - gate_flag
     if gate_flag == 0:
         oled.fill_rect(48,24,40,8,0)
         oled.show()
         count=str(counter)
         if len(count)<5:
             for i in range(5-len(count)):
                count=" " + count
         oled.text(count,48,24)
         oled.show()
         counter=0
     led.value(gate_flag)

timer.init(freq=1, mode=Timer.PERIODIC, callback=gate_ctrl)

while True:
     if gate_flag == 1:
         sig_val=sig.value()
         if sig_val==1 and old_sig==0:
             counter += 1
         old_sig = sig_val
-----------------------------------------------------------------------------------------------------
  さて、どの程度の周波数まで測定できるでしょうか。自作のSGから信号を加え、少しずつ周波数を上げていきます。



10KHzまでは、ほぼ正確な値をカウントします。MicroPythonでのプログラミングでは、この辺が限界のようですが、オーディオ周波数なら実用になりそうです。



Raspberry Pi Pico MicroPython Si5351A 7MHz VFO

2021-12-08 14:06:16 | Raspberry Pi Pico
 Raspberry Pi Pico MicroPythonの組み合わせで、3チャンネルクロックジェネレータSi5351Aを使った7MHz VFOを作ってみました。

 Si5351 VFOは、これまでPIC,Arduino,STM32,ESP32などで何度も作りました。Si5351Aをコントロールする実験は、JH7UBCホームページのArduinoのページに詳しく掲載していますので、そちらをご覧ください。

 Raspberry Pi Picoでこれまでテストしてきた各デバイスのスクリプトを組み合わせて今回のスクリプトを作りました。

 回路図です。I2Cは、id=0,sda=Pin16,scl=Pin17とし、Si5351AとOLEDをコントロールします。VFO出力は、SI5351AのCLK0です。ロータリーエンコーダは、GPIO0とGPIO1に接続します。周波数STEPスイッチは、GPIO15に接続し、GPIO0,1,15は内部でプルアップします。

ブレッドボードです。


周波数カウンタを接続して、動作を確認しています。期待通りの動作をしました。


スケッチです。(ちょっと長いです。)Si5351Aは個体により若干の周波数のずれがあります。その補正は、dFの値とCrystal Load Capacitanceの値で行います。周波数STEPは、10K→1K→100→10→1Kと押すたびに変更され、循環します。なお、OLEDを使いますので、ssd1306ライブラリーをPicoにインストールしておきます。このスクリプトは、main.pyとしてPicoに保存します。
--------------------------------------------------------------------------------------------
"""
Si5351A test
7MHz VFO OUTPUT CLK0
2021.12.8
JH7UBC Keiji Hata
"""

from machine import I2C,Pin
import ssd1306
import utime

i2c=I2C(0,sda=Pin(16),scl=Pin(17),freq=400000)
oled=ssd1306.SSD1306_I2C(128,64,i2c)

step_sw=Pin(15,Pin.IN,Pin.PULL_UP)
pinA = Pin(0,Pin.IN,Pin.PULL_UP)
pinB = Pin(1,Pin.IN,Pin.PULL_UP)

#Di5351A関係定義
Si5351A_ADDR = 0x60
MSNA_ADDR = 26
MSNB_ADDR = 34
MS0_ADDR = 42
MS1_ADDR = 50
MS2_ADDR = 58
CLK0_CTRL = 16
CLK1_CTRL = 17
CLK2_CTRL = 18
OUTPUT_CTRL = 3
XTAL_LOAD_C = 183
PLL_RESET = 177
XtalFreq = 25000000
df = -62 #周波数補正値
denom = 1048575
frequency = 7000000 #周波数初期値
frequency_old = frequency
step = 10000 #step初期値
old_fH=""
old_fM=""
old_fL=""
f_Max = 7200000 #周波数最大値
f_Min = 7000000 #周波数最小値


#Rotary Encoder関係定義
befDat=0x11
curDat=0
count=0

buf = bytearray(2)

def Si5351_write(reg,data):
     buf[0] = reg
     buf[1] = data
     i2c.writeto(Si5351A_ADDR,buf)
#Si5351A initialize
def Si5351_init():
     Si5351_write(OUTPUT_CTRL,0xFF)#Disable Output
     Si5351_write(CLK0_CTRL,0x80)   #CLK0 Power down
     Si5351_write(CLK1_CTRL,0x80)   #CLK1 Power down
     Si5351_write(CLK2_CTRL,0x80)   #CLK2 Power down
     Si5351_write(XTAL_LOAD_C,0x40)#Crystal Load Capacitance=6pF
     Si5351_write(PLL_RESET,0xA0)   #Reset PLLA and PLLB
     Si5351_write(CLK0_CTRL,0x4F)   #CLK0 Power up 8mA
#     Si5351_write(CLK1_CTRL,0x4F)   #CLK1 Power up 8mA
#     Si5351_write(CLK2_CTRL,0x4F)   #CLK2 Power up 8mA
     Si5351_write(OUTPUT_CTRL,0xFE)#Enable CLOK0

def PLLA_set(freq):
     global XtalFreq,divider,df,denom
     freq += df #df=周波数補正値
     divider = int(900000000 / freq)
     divider >> 1 #dividerは整数かつ偶数
     divider << 1
     PllFreq = divider * freq
     mult = PllFreq // XtalFreq
    l = PllFreq % XtalFreq
    f = l * denom
     num = f // XtalFreq
     P1 = int(128 * (num / denom))
     P1 = 128 * mult + P1 - 512
     P2 = int(128 * (num / denom))
     P2 = 128*num - denom * P2
     P3 = denom

     Si5351_write(MSNA_ADDR + 0,(P3 & 0x0000FF00) >> 8)  #MSNA_P3[15:8]
     Si5351_write(MSNA_ADDR + 1,(P3 & 0x000000FF))       #MSNA_P3[7:0]
     Si5351_write(MSNA_ADDR + 2,(P1 & 0x00030000) >> 16) #MSNA_P1[17:16]
     Si5351_write(MSNA_ADDR + 3,(P1 & 0x0000FF00) >> 8)  #MSNA_P1[15:8]
     Si5351_write(MSNA_ADDR + 4,(P1 & 0x000000FF))       #MSNA_P1[7:0]
     Si5351_write(MSNA_ADDR + 5,((P3 & 0x000F0000) >> 12) | ((P2 & 0X000F0000) >> 16))#MSNA_P3[19:16]MSNA_P2[19:16]
     Si5351_write(MSNA_ADDR + 6,(P2 & 0x0000FF00) >> 8)  #MSNA_P2[15:8]
     Si5351_write(MSNA_ADDR + 7,(P2 & 0x000000FF))       #MSNA_P2[7:0]

def MS0_set():
     global divider
     P1=128*divider-512
     P2=0
     P3=1
     Si5351_write(MS0_ADDR + 0,(P3 & 0x0000FF00) >> 8)   #MS0_P3[15:8]
     Si5351_write(MS0_ADDR + 1,(P3 & 0x000000FF))        #MS0_P3[7:0]
     Si5351_write(MS0_ADDR + 2,(P1 & 0x00030000) >> 16)  #MS0_P1[17:16]
     Si5351_write(MS0_ADDR + 3,(P1 & 0x0000FF00) >> 8)   #MS0_P1[15:8]
     Si5351_write(MS0_ADDR + 4,(P1 & 0x000000FF))        #MS0_P1[7:0]
     Si5351_write(MS0_ADDR + 5,((P3 & 0x000F0000) >> 12) | ((P2 & 0X000F0000) >> 16))#MS0_P3[19:16]MS0_P2[19:16]
     Si5351_write(MS0_ADDR + 6,(P2 & 0x0000FF00) >> 8)   #MS0_P2[15:8]
     Si5351_write(MS0_ADDR + 7,(P2 & 0x000000FF))        #MS0_P2[7:0 Si5351_init()

#周波数表示
def Freq_Disp(freq):
     global old_fH,old_fM,old_fL
     fre_txt = str(freq)
     fH=fre_txt[0]
     if fH != old_fH:
         oled.fill_rect(28,16,8,8,0)
         oled.show()
         oled.text(fH,28,16)
         oled.show()
         old_fH=fH
     fM=fre_txt[1:4]
     if fM != old_fM:
         oled.fill_rect(44,16,24,8,0)
         oled.show()
         oled.text(fM,44,16)
         oled.show()
         old_fM=fM
     fL=fre_txt[4:]
     if fL != old_fL:
         oled.fill_rect(73,16,24,8,0)
         oled.show()
         oled.text(fL,76,16)
         oled.show()
         old_fL=fL

#STEP表示
def Step_Disp():
     global step
     oled.fill_rect(24,52,32,8,0)
     oled.show()
     if step == 10000:
         oled.text(" 10K",24,52)
     elif step == 1000:
         oled.text("  1K",24,52)
     elif step == 100:
         oled.text(" 100",24,52)
     elif step == 10:
         oled.text("  10",24,52)
     oled.show()

#STEP変更
def Step_Change():
     global step
     utime.sleep_ms(10)
     if step == 10:
         step = 10000
     else:
         step //= 10
     Step_Disp()
    while step_sw.value() == 0:
         utime.sleep_ms(10)

#周波数変更
def Freq_Change():
     global frequency
     PLLA_set(frequency)
     MS0_set()
     Freq_Disp(frequency)

#回転方向判定
def hantei(p):
     global curDat,befDat,count
     global frequency,step
     global f_Max,f_Min
     curDat=pinA.value() + (pinB.value()<<1)
     if curDat != befDat:
         D = ((befDat<<1)^curDat)&3
         if D<2:
             count = count + 1
         else:
             count = count - 1
         befDat = curDat
         if count >= 3:
             frequency += step
             count=0
         elif count <= -3:
             frequency -= step
             count=0
     if frequency >= f_Max:
         frequency = f_Max
     if frequency <= f_Min:
         frequency = f_Min

#周波数初期値設定
Si5351_init()
PLLA_set(frequency)
MS0_set()

#初期画面設定
oled.text("RP2 7MHz VFO",16,0)
oled.rect(24,12,80,16,1)
oled.text(" .   .",28,16)
oled.text("STEP",24,40)
oled.text(" 10K",24,52)
oled.show()
Freq_Disp(frequency)

#ピン状態変化割込み設定
pinA.irq(trigger=Pin.IRQ_FALLING|Pin.IRQ_RISING,handler=hantei)
pinB.irq(trigger=Pin.IRQ_FALLING|Pin.IRQ_RISING,handler=hantei)

#loop
while True:
     if step_sw.value() == 0:
         Step_Change()
     if frequency != frequency_old:
         Freq_Change()
        frequency_old = frequency
     utime.sleep_ms(10)
--------------------------------------------------------------------------------------------
 今回は、周波数を7MHzとしましたが、このプログラムで500KHz~150MHz の周波数を発生させることができます。

Raspberry Pi Pico MicroPython BME280 温度・気圧・湿度計

2021-11-30 10:23:05 | Raspberry Pi Pico
  Raspberry Pi Picoに温度・気圧・湿度センサーBME280を接続し、温度・気圧・湿度をOLEDに表示します。

 BME280とOLEDは、いずれもI2C接続です。接続回路図です。



 電源は、電池2本(3V)とし、スタンドアローンで動作します。

 スクリプトです。これまでのBME280のテストとOLED表示テストの結果を利用します。小数点以下の数値は、けっこう変動しますので、小数点1位を四捨五入しています。測定は5秒ごとに行い、数値が変わった時だけ表示を変更します。
--------------------------------------------------------------------------------------
"""
Raspberry Pi Pico BME280 TPH meter
2021.11.29
JH7UBC Keiji Hata
"""

from machine import I2C,Pin
import bme280
import ssd1306
import utime

i2c=I2C(0,sda=Pin(16),scl=Pin(17),freq=400000)
bme=bme280.BME280(i2c=i2c)
oled=ssd1306.SSD1306_I2C(128,64,i2c)

oled.text("BME280 TPH meter",0,0)
oled.text("T :      C",8,16)
oled.text("P :      hPa",8,32)
oled.text("H :      %",8,47)
oled.show()

old_ti=0
old_pi=0
old_hi=0

def TPH_disp():
    global ti,pi,hi
    global old_ti,old_pi,old_hi

    if ti != old_ti:
        oled.fill_rect(48,16,24,8,0)
        oled.show()
        oled.text(str(ti),48,16)
        oled.show()
        old_ti = ti
    if pi != old_pi:
        oled.fill_rect(40,32,32,8,0)
        oled.show()
        oled.text(str(pi),40,32)
        oled.show()
        old_pi = pi
    if hi != old_hi:
        oled.fill_rect(48,48,24,8,0)
        oled.show()
        oled.text(str(hi),48,48)
        oled.show()
        old_hi = hi

while True:
    #補正された温度t・気圧p・湿度hデータを読み込む
    t,p,h=bme.read_compensated_data()
    ti=int(t/100 + 0.5)
    p=p//256
    pi=int(p/100 + 0.5)
    hi=int(h/1024 + 0.5)
    TPH_disp()#t,p,hを表示
    utime.sleep(5)#5sec待つ

--------------------------------------------------------------------------------------
 ブレッドボードです。

 測定値を市販の温度・湿度計の値と比べてみました。温度、湿度ともほぼ同じ値を示しました。

 

Raspberry Pi Pico MicroPython BME280テスト

2021-11-29 13:13:50 | Raspberry Pi Pico
 Raspberry Pi Picoで、温度・気圧・湿度センサーBME280のテストをします。
 BME280は、秋月電子のAE-BME280モジュールを使います。I2C接続で温度・気圧・湿度の測定データを読み出すことができます。I2Cアドレスは、SDOをGNDに接続した場合0x76、VDDに接続した場合は、0x77です。


接続回路図です。

 Raspberry Pi PicoのI2Cは、id=0、sda=GP16、scl=GP17に設定します。

 BME280の測定値から実際の温度、気圧、湿度を求めるには、補償値も含め複雑な計算が必要です。そこで、利用できるライブラリを探してみます。

 Thonny IDEにおいて、ツール→Manage packages MicroPython deviceを開き、BME280で検索します。
 ESP8266/ESP32用ですが、「micropython-bme280」が使えそうです。Picoにインストールします。

 次のスクリプトでテストしてみました。bme.valuesで温度、気圧、湿度の測定値を読み出すことができます。 
 1secごとにshellに表示させてみると、測定値が小数点以下は細かく変動することが分かります。

 このままでも利用できますが、生データを保証した値も読み出すことができます。bme.read_compensated_data()で、温度、気圧、湿度の順に補正後の値を読み出すことができます。


 温度tは、t/100で実際の温度になります。
 気圧pは、p/256/100で実際の気圧になります。
 湿度hは、h/1024で実際の湿度になります。

 次は、OLED表示器と組み合わせて、温度・気圧・湿度計を作ってみます。