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

JH7UBCブログ

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

Raspberry Pi Pico MicroPython 2.2インチ TFT表示テストその1

2022-02-10 19:43:18 | Raspberry Pi Pico
 Raspberry Pi Picoで2.2インチのTFTディスプレイの表示テストをします。

 PicoでのTFT表示に関連する記事をネット上で探すと、Hello Raspberry Piというサイトに表示のデモがありました。
 TFTディスプレイの表示方法は、このサイトの動画と記事を見ていただくと分かるのですが、簡単に説明します。

 2.2インチTFTディスプレイには、ILI9341というコントローラが使われています。そこで、Pico用のILI9341ライブラリをGitHubの下のURLから取得します。
このページから、ili9341.pyとxglcd_font.pyをPi Picoにコピーします。コピー方法は、動画を参考にしてください。
上記ページのfontsからUnispace12x24.c というフォントをPi Picoにfontsというフォルダを作り、そこにコピーします。これで準備ができました。

 Hello Raspberry Piのページの説明に従って、いくつかデモのスクリプトを試し、動作(表示)を確認しました。しかし、どうも使い方が良く分かりません。そこで、自分なりにスクリプトを書いてテストをしてみました。

 まず、PicoとTFTの接続回路図です。SPI0を利用します。

テストしたスクリプトです。各図形の描画書式は、ili9341.pyの中で説明されています。
--------------------------------------------------------------------------------------------
'''
Raspberry Pi Pico SPI LIL9341 Display test
2022.2.10
JH7UBC Keiji Hata
'''

from machine import Pin, SPI
import ili9341
from xglcd_font import XglcdFont

#SPI設定
TFT_CLK_PIN = const(6)
TFT_MOSI_PIN = const(7)
TFT_MISO_PIN = const(4)
TFT_CS_PIN = const(13)
TFT_RST_PIN = const(14)
TFT_DC_PIN = const(15)

spiTFT = SPI(0, baudrate=51200000,
                 sck=Pin(TFT_CLK_PIN), mosi=Pin(TFT_MOSI_PIN))
display = ili9341.Display(spiTFT,dc=Pin(TFT_DC_PIN),
             cs=Pin(TFT_CS_PIN), rst=Pin(TFT_RST_PIN),
             width=320, height=240,rotation=90)
#font設定
unispace = XglcdFont('fonts/Unispace12x24.c', 12, 24)

#テキストの表示
display.draw_text(10, 0, 'Hello World!', unispace,
                  ili9341.color565(255, 255, 255))
#点を描く
display.draw_pixel(180,20,ili9341.color565(255, 255, 0))
#水平な線を描く
display.draw_hline(200,20,90,ili9341.color565(255, 0, 0))
#垂直線を描く
display.draw_vline(300,20,100,ili9341.color565(0, 255, 0))
#直線を描く
display.draw_line(200,30,280,80, ili9341.color565(0,0,255))
#円を描く
display.draw_circle(50, 80, 30, ili9341.color565(0,255,255))
#円を描き、内部を塗りつぶす
display.fill_circle(150, 80, 30, ili9341.color565(0,255,255))
#長方形を描く
display.draw_rectangle(20, 150, 70, 50, ili9341.color565(255,0,255))
#長方形を描き、内部を塗りつぶす
display.fill_rectangle(120, 150, 70, 50, ili9341.color565(255,255,0))
#多角形を描く(例6角形)
display.draw_polygon(6,250,110,30,ili9341.color565(255,255,255))
#多角形を描き、内部を塗りつぶす
display.fill_polygon(6,250,180,30,ili9341.color565(255,0,0))

--------------------------------------------------------------------------------------------
色の指定は、color565(R,G,B)で指定します。

ブレッドボードと上記スクリプトの表示結果です。他に楕円を描いたり、反転させたりすることができます。



Raspberry Pi Pico MicroPython OZ1JHM type CW decoder

2022-02-03 21:59:50 | Raspberry Pi Pico
 昨年末から取り組んでいたArduinoで動作するOZ1JHM CW decoderのプログラムをRaspberry Pi Picoに移植する作業が一応完了しました。

 このCW解読器の心臓部であるGeortzel アルゴリズムによるCWトーン検出部は、前の記事のテストでうまく動作させることができましたが、Raspberry Pi PicoのADCの分解能は16bitですので、ArduinoのADCの分解能10bitに合わせてADC0の読み取り値を右に6bitシフトしています。CW信号の短点と長点を判定するプログラムはOZ1JHMのまま使いましたが、モールス符号のデコードは、以前micro:bitで試作したモールス符号解読器と同じアルゴリズムを使いました。

 モールス信号は、スタートビットを1、短点を0、長点を1としてコード化しています。
 例えば、Aは、・ーですから、スタートビットを先頭にして101となります。Bは、ー・・・ですから11000、Cはー・ー・ですから11010となります。各文字、記号をコード化し、小さい順にならべて、Oubunというリストを作り、受信したモールス符号をコード化しリストから対応した文字を取り出します。今回は、欧文のみのデコードとしました。

 解読した文字は、I2Cインターフェース付きのLCD2004に表示します。最初ライブラリを利用してみたのですが、表示速度が遅いので、以前にテストしたI2C LCD1602用のスクリプトをLCD2004に対応させて使いました。

 回路図です。CW信号は、GPIO26に加え、ADC0を使います。LCD2004は今回はレベル変換しないで、直接PicoにI2C接続しています。

 スクリプトです。ちょっと長いのですが、掲載します。
-------------------------------------------------------------------------------------------
from machine import Pin,I2C,ADC
import utime
import math

i2c=I2C(1,sda=Pin(14),scl=Pin(15),freq=400000)
led=Pin(25,Pin.OUT)
a=ADC(0)

#character list
Oubun = ['*','*','E','T','I','A','N','M','S','U','R','W','D',
          'K','G','O','H','V','F','*','L','*','P','J','B','X',
          'C','Y','Z','Q','*','*','5','4','*','3','*','*','*',
          '2','*','*','*','*','*','*','*','1','6','*','/','*',
          '*','*','(','*','7','*','*','*','8','*','9','0']
Mcode=1

#LCD2004関係定義
LCD_addr=0x27
LCD_EN=0x04   #LCD Enable
LCD_BL=0x08   #Back Light
CMD=0x00 #command mode
CHR=0x01 #character mode
LINE1=0x80 #Line1 top address
LINE2=0xC0 #Line2 top address
LINE3=0x94 #Line3 top address
LINE4=0xD4 #Line4 top address
buf=bytearray(2)

colums=20
rows=4
lcdindex=0
line1=['','','','','','','','','','','','','','','','','','','','']
line2=['','','','','','','','','','','','','','','','','','','','']


#LCD2004表示関係関数
def LCD_write(bits,mode):
     #High 4bits
     data=(bits & 0xF0)|mode
     buf[0]=data|LCD_EN|LCD_BL
     buf[1]=data|LCD_BL
     i2c.writeto(LCD_addr,buf)
     utime.sleep_us(100)#wait 100us
     #Low 4bits
     data=((bits<<4)&0xF0)|mode
     buf[0]=data|LCD_EN|LCD_BL
     buf[1]=data|LCD_BL
     i2c.writeto(LCD_addr,buf)
     utime.sleep_us(100)#wait 100us
def LCD_init():
     LCD_write(0x33,CMD)#8bit mode 0x03を2回送る
     LCD_write(0x32,CMD)#8bit mode,4bit mode 0x02を送る
     LCD_write(0x06,CMD)#Entry modeセット
     LCD_write(0x0C,CMD)#表示ON,カーソルOFF,カーソル点滅OFF
     LCD_write(0x28,CMD)#2桁表示,7ドットモード
     LCD_write(0x01,CMD)#Display clear
     utime.sleep_ms(2)#waite 2ms

def LCD_cursor(x,y):
     if y==0:
         LCD_write(LINE1+x,CMD)
     if y==1:
         LCD_write(LINE2+x,CMD)
     if y==2:
         LCD_write(LINE3+x,CMD)
     if y==3:
         LCD_write(LINE4+x,CMD)

def LCD_print(str):
     for c in str:
         LCD_write(ord(c),CHR)
def printchar(c):
     global lcdindex

     if lcdindex > colums-1:
         lcdindex=0
         if rows==4:
             for i in range(colums):
                LCD_cursor(i,rows-3)
                LCD_print(line2[i])
                line2[i]=line1[i]
         for i in range(colums):
             LCD_cursor(i,rows-2)
             LCD_print(line1[i])
             LCD_cursor(i,rows-1)
             LCD_print(" ")
     line1[lcdindex]=c
     LCD_cursor(lcdindex,rows-1)
     LCD_print(c)
     lcdindex = lcdindex+1

#CW速度表示更新関数
def updateinfolinelcd():
     global wpm
     wpmtxt=str(wpm)
     if wpm<10:
         LCD_cursor(8,0)
         LCD_print("0")
         LCD_cursor(9,0)
         LCD_print(wpmtxt)
         LCD_cursor(10,0)
         LCD_print(" WPM ")
     else:
         LCD_cursor(8,0)
         LCD_print(wpmtxt)
         LCD_cursor(10,0)
         LCD_print(" WPM ")

#モールス符号解読と表示関数
def decode():
     global Mcode,Oubun
     if Mcode >1 and Mcode < 64:
         c=Oubun[Mcode]
         printchar(c)
     elif Mcode==76:
         printchar("?")
     elif Mcode==85:
         printchar(".")
     elif Mcode==90:
         printchar("@")
     elif Mcode==97:
         printchar("-")
     elif Mcode==109:
         printchar(")")
     elif Mcode==115:
         printchar(",")
     elif Mcode==120:
         printchar(":")
     Mcode=1            

#setup
LCD_init()

#Goertzel algprithm関係定義 
Q1=0.0
Q2=0.0
sampling_freq=8928.0
target_freq=558.0
n=48.0
testData=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
           0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
           0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
Pi=3.14159265

magnitude=100.0
magnitudelimit=100
magnitudelimit_low=100
realstate=0#LOW
realstatebefore=0#LOW

#Basic goertzel Calcuration
k=int(0.5+(n*target_freq)/sampling_freq)
omega=(2.0*Pi*k)/n
sine=math.sin(omega)
cosine=math.cos(omega)
coeff=2.0*cosine

#noise blanker関係定義
nbtime=6 #ms
starttimehigh=0
highduration=0
lasthighduration=0
hightimesavg=0
lowtimesavg=0
starttimelow=0
lowduration=0
laststarttime=0
stop=0
wpm=0


filteredstate=0#LOW
filteredstatebefore=0#LOW

#main loop
while True:
     for i in range(n):
         testData[i]=a.read_u16()>>6
         utime.sleep_us(60)
     for i in range(n):
         Q0=coeff*Q1-Q2+testData[i]
         Q2=Q1
         Q1=Q0
     magnitudeSquared=(Q1*Q1)+(Q2*Q2)-Q1*Q2*coeff
     magnitude=math.sqrt(magnitudeSquared)
     Q1=0.0
     Q2=0.0
#set the magnitudelimit automatic
     if magnitude > magnitudelimit_low:
         magnitudelimit = magnitudelimit + ((magnitude - magnitudelimit)/6)
     if magnitudelimit < magnitudelimit_low:
         magnitudelimit = magnitudelimit_low
#check for the magnitude
     if magnitude > magnitudelimit*0.6:
         realstate=1#HIGH
     else:
         realstate=0#LOW

#noise blanker
     if realstate != realstatebefore:
         laststarttime = utime.ticks_ms()
     if (utime.ticks_ms()-laststarttime) > nbtime:
         if realstate != filteredstate:
             filteredstate = realstate
#duration high and low
     if filteredstate != filteredstatebefore:
         if filteredstate == 1:#HIGH
             starttimehigh = utime.ticks_ms()
             lowduration = utime.ticks_ms() - starttimelow

         if filteredstate ==0:#LOW
             starttimelow = utime.ticks_ms()
             highduration = utime.ticks_ms() - starttimehigh
             if highduration < (2*hightimesavg) or hightimesavg == 0:
                 hightimesavg = (highduration + hightimesavg + hightimesavg)/3

             if highduration > (5*hightimesavg):
                hightimesavg = highduration + hightimesavg

#check baud dot dash
     if filteredstate != filteredstatebefore:
         stop = 0#LOW
         if filteredstate == 0:#LOW
             if highduration < (hightimesavg*2) and highduration > (hightimesavg*0.6):
                Mcode =Mcode<< 1
             if highduration > (hightimesavg*2) and highduration < (hightimesavg*6):
                 Mcode =(Mcode<<1) + 1
                wpm = int((wpm + (1200/((highduration)/3)))/2 + 0.5)
         if filteredstate == 1:#HIGH
             lacktime = 1.0
             if wpm > 25:
                lacktime = 1.0
             if wpm > 30:
                lacktime = 1.2
             if wpm > 35:
                lacktime = 1.5
             if lowduration > hightimesavg*(2*lacktime): #and lowduration < hightimesavg*(5*lacktime):#letter space
                decode()
             if lowduration >= hightimesavg*(5*lacktime):#word space
#                decode()
                printchar(" ")
#no more letters
     if utime.ticks_ms() - starttimelow > (highduration*6) and stop == 0:
        decode()
         stop = 1

#LED表示
     if filteredstate == 1:
         led.value(1)
     else:
         led.value(0)
#the end of main loop clean up
     updateinfolinelcd()
     realstatebefore = realstate
     tasthighduration = highduration
     filteredstatebefore = filteredstate

-------------------------------------------------------------------------------------------
 動作テストをCW練習ソフトCWTW-Proを使って行いました。
 CWTW-Proの設定画面です。周波数は、587HzとOZ1JHM CW decoderのCWトーン検出の中心周波数に近い値としました。

CWTW-Proから速度50字/分=10WPMのモールス符号を送出して、デコードしてみます。アルファベットに数字と記号を含めて送出してみました。


 ブレッドボードとデコード結果です。

 速度は、10WPMと妥当な値を表示しました。デコードは、最初の一文字はうまくデコードできませんでしたが、2文字目からは、OKです。ただし、BTや'(アポストロフィ)、"(ダブルクォーテーション)はデコードできません。

 一応Raspberry Pi PicoでCWデコーダができました。スクリプトの改良で和文もデコードできるようになります。

Raspberry Pi Pico Geotzelアルゴリズム テスト

2022-01-27 19:10:09 | Raspberry Pi Pico
 昨年末、Raspberry Pi PicoでOZ1JHMタイプのCW解読器を作ろうと思い、ArduinoのプログラムをmicroPythonに移植したのですが、まったく動きませんでした。
 このタイプで利用されているCWトーン検出のためのアルゴリズム、Goetzel AlgorithmをまずArduinoで確認しました。記事はこちら

 この実験で、Goetzel Algorithmで帯域幅約200Hzのオーディオフィルタを実現していることが分かりました。

 これと同じ実験をRaspberry Pi Picoでやってみました。回路図です。


 ADC0を利用しますので、GPIO26に信号を入力します。

 Arduinoの記事に掲載したプログラムをPico用のmicroPythonに移植したのですが、ターゲット周波数どころかどこにもmagnitudeのピークが現れません。

 そこで、サンプリング周波数とターゲット周波数の関係を良く見ると、
sampling_freq=8928.0(Hz)
target_freq=558.0(Hz)
sampling_freq/target_freq=8928/558=16
ですから、ターゲットの信号1周期あたり、1000000us/8928Hz=112usごとに、16回サンプリングすることになります。
サンプル数は、n=48ですから3周期分サンプリングします。

 ところで、Raspberry Pi PicoのAD変換にかかる時間を測定してみると約30usでした。ということは、サンプリング(AD変換)間隔を110usにするためには、数10usの待ち時間をとればよいのではないかと考えました。

 そこで、サンプル周波数とターゲット周波数を上記のままにして、AD変換の後に40,50,60,70,80usの待ち時間を入れて、それぞれについて周波数100Hz~1000Hzの間でmagnitudeがどのように変化するかを測定してみました。

 それぞれの待ち時間でmagnitudeのピークが現れました。その中で最も特性が良さそうなのが、60usの時で、特性は次のようになりました。


 Arduinoの場合と同等かそれ以上に良い特性だと思います。
 テストしたスクリプトです。
--------------------------------------------------------------------------------------
from machine import Pin,ADC
import utime
import math

a=ADC(0)
Q1=0
Q2=0
sampling_freq=8928.0
target_freq=558.0
n=48.0
Pi=3.14152965
testData=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
           0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
           0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]

#Basic goertzel Calcuration
k=int(0.5+(n*target_freq)/sampling_freq)
omega=(2.0*Pi*k)/n
sine=math.sin(omega)
cosine=math.cos(omega)
coeff=2.0*cosine

while True:
     for i in range(n):
         testData[i]=a.read_u16()
         utime.sleep_us(60)
     for i in range(n):
         Q0=coeff*Q1-Q2+testData[i]
         Q2=Q1
         Q1=Q0
     magnitudeSquared=(Q1*Q1)+(Q2*Q2)-Q1*Q2*coeff
     magnitude=math.sqrt(magnitudeSquared)
     Q1=0.0
     Q2=0.0
     print(magnitude//100)
     utime.sleep(1)
--------------------------------------------------------------------------------------
 これで、Picoでトーン検出ができるようになりました。
 CW解読器として、動くように仕上げていくことにします。

 余談ですが、今年は雪が多く、除雪などで疲れてしまって、マイコン遊びに戻ってくるまで1か月かかってしまいました。
 これからまた少しずつPIC,Arduinoやラズパイ関係の記事を書いていきたいと思います。よろしくお願いします。

Raspberry Pi Pico MicroPython RTC I2C LCD2004表示

2021-12-20 18:34:07 | Raspberry Pi Pico
 Raspberry Pi Pico MicroPythonプログラミングで、RTC(リアルタイムクロック)をI2C LCD2004に表示してみます。

 LCD2004の表示は、今回はライブラリを使います。I2C LCD1602/2004用のライブラリは、ネットやYouTubeで調べると複数あるようです。
 その中で、T-622 /RPI-PICO-I2C-LCDというライブラリを使ってみます。GitHubのこちらのページにアクセスします。 
 lcd_api.py とpico_i2c_lcd.py を開いて、Raspberry Pi Picoに同じファイル名でコピペします。
 回路図です。LCD1602/2004は5V動作ですが、直接I2Cバスに接続することもできるのですが、安全のため、3.3V/5VのレベルコンバータモジュールPCA9306を入れています。(秋月電子のモジュールで、3.3V側、5V側両方にプルアップ抵抗が入っています)I2Cは、id=1,sda=Pin(14),scl=Pin(15)を使います。

 スクリプトです。main.pyとして保存します。1secごとにTimer割込みを発生させて、RTCの表示を更新しています。Pico内蔵LEDは、1secごとに点滅します。Thonnyを経由して、パソコンの時計の値が書き込まれますので、Picoをパソコンに接続している場合は、現在の時刻が表示されます。
 Thonnyを使わないで、電源だけを入れると2021/01/01 00:00:00と表示され、カウントアップしていきます。
-----------------------------------------------------------------------------------------------------
from machine import I2C,Pin,Timer
import utime
from lcd_api import LcdApi
from pico_i2c_lcd import I2cLcd

I2C_ADDR      = 0x27
I2C_NUM_ROWS = 4
I2C_NUM_COLS = 20

i2c = I2C(1, sda=Pin(14), scl=Pin(15), freq=400000)
lcd = I2cLcd(i2c, I2C_ADDR, I2C_NUM_ROWS, I2C_NUM_COLS)
timer=Timer()
led = Pin(25,Pin.OUT)

lcd.move_to(3,0)
lcd.putstr("Real Time Clock")

def clock(t):
     global led
     led.toggle()
     lcd.move_to(0,2)
     time=utime.localtime()
     lcd.putstr("{year:>04d}/{month:>02d}/{day:>02d} {HH:>02d}:{MM:>02d}:{SS:>02d}".format(
             year=time[0], month=time[1], day=time[2],
             HH=time[3], MM=time[4], SS=time[5]))

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

while True:
     pass
-----------------------------------------------------------------------------------------------------
ブレッドボードです。



Raspberry Pi Pico MicroPython レシプロカル(逆数)カウンタ+CWトーンインジケータ

2021-12-19 12:37:02 | Raspberry Pi Pico
 Raspberry Pi Pico MicroPythonの組み合わせで、レシプロカル(逆数)カウンタを作ってみます。
 信号の周期T(sec)を測定して、f(Hz)=1/T(sec)の式で計算して、周波数を求めます。
 回路図です。周波数カウンタと同じです。



 スクリプトです。以前にmicro:bitでレシプロカル・カウンタを作った時のものをPico用に移植しました。CWトーン・インジケータとして使えるように、700Hz±50HzでLEDが点灯するようにプログラミングしました。サンプリング間隔を短くする場合は、下から2行目のutime.sleep(0.5)の値0.5を0.2などに変更します。
-------------------------------------------------------------------------------------------------
from machine import Pin,I2C
import utime
import ssd1306

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

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

oled.text("Reciorocal",16,0)
oled.text("Counter",56,8)
oled.text("Hz",88,32)
oled.show()

old_value = 0
flag = 0

def display():
     global freq
     oled.fill_rect(48,32,40,8,0)
     oled.show()
     oled.text(str(freq),48,32)
     oled.show()
     if freq>=650 and freq<=750:
         led.value(1)
     else:
         led.value(0)

#main loop
while True:
     now_value =sig.value()
     if old_value == 1 and now_value == 0:
         if flag == 0:
             start_time = utime.ticks_us()
             flag = 1
         else:
             end_time = utime.ticks_us()
             freq = int(1000000 / (end_time-start_time)+0.5)
             display()
             flag = 0
             utime.sleep(0.5)
     old_value = now_value

-------------------------------------------------------------------------------------------------
 ブレッドボードです。
 自作のSGから1000Hzを出力して、測定しています。



 ±50Hzくらいの誤差があり、測定値が細かく変化します。
 utime.ticks_us()の値のふらつきが原因でしょうか。