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

南無ちゃんのブログ https://namva.net/blog2

上記のアドレスに引っ越しました。

初めてのラズパイpicoW

2025-01-06 14:44:27 | プログラミング
 これまでラズパイpicoを用いてリニアアンプ用液晶表示装置を開発してきましたが、WiFiで通信してPCからリモートコントロールできるようにしたいので、ラズパイpicoからラズパイpicoWに移行したいと思います。
 2021年頃からESP32を使って、いくつかのIoT機器を作ってきたので、WiFiのプログラミング方法は経験済みですが、何年も経つと忘れています。
 そこで、初心に戻り一から順を追ってラズパイpicoWのプログラミングをして行きます。

 手始めにスケッチ例をコンパイルして実行してみました。先ず、ツールタブでボードを"Raspberry Pi Pico W"に設定します。これで、ラズパイpicoWのスケッチ例を使うことができるようになります。
 次に、ファイルタブからスケッチ例-->WiFi-->ScanNetworksを選択します。コンパイル、書き込みの後、Arduino IDEのシリアルモニタを開くと次のように表示されました。

Beginning scan at 156096
Found 3 networks

SSID ENC BSSID CH RSSI
Buffalo-G-96C8 AUTO 34:3D:C4:B7:96:C8 11 -90
Buffalo-G-96C8_EXT AUTO 74:DA:88:3B:80:A5 11 -83
Buffalo-G-A9E0 WPA2 84:E8:CB:6D:E2:80 2 -41

--- Sleeping ---

 Buffalo社製のWiFiルータを使っており、Buffalo-G-xxxxというのは2.4GHzを利用時のSSIDです。WiFiルーターは5GHzもサポートしていますが、ラズパイpicoWからは2.4GHzのものしか見えません。しかし、パソコンに表示されないアクセスポイントも表示されるので、受信感度はパソコンよりも良さそうです。

 次にUdpを試しました。ファイルタブからスケッチ例-->WiFi-->Udpを選択します。ソースコードの22行目辺りの次の部分を修正しました。
#define STASSID "your-ssid"
#define STAPSK "your-password"

 先に実行したScanNetworksで見つけた3つアクセスポイントの内の一つを使うので次のように修正しました。
#define STASSID "Buffalo-G-A9E0"
#define STAPSK "ルータに書いてある通り(ここでは秘密)"

 コンパイル、書き込みの後、Arduino IDEのシリアルモニタを開くと次のように表示されました。
Connected! IP address: 192.168.0.19
UDP server on port 8888

 IPアドレスは、DHCPにより自動的に割り当てられたものです。UDPサーバーのポートアドレスは、8888と表示されていますが、この値はプログラム中で次のように記述されているからです。
unsigned int localPort = 8888; // local port to listen on

 ラズパイpicoWのネットワークレイヤーが動作しているかどうか、pingを使って確かめました。
PS C:\Users\namva> ping 192.168.0.19
192.168.0.19 に ping を送信しています 32 バイトのデータ:
192.168.0.19 からの応答: バイト数 =32 時間 =114ms TTL=255
192.168.0.19 からの応答: バイト数 =32 時間 =2ms TTL=255
192.168.0.19 からの応答: バイト数 =32 時間 =2ms TTL=255
192.168.0.19 からの応答: バイト数 =32 時間 =2ms TTL=255
192.168.0.19 の ping 統計:
パケット数: 送信 = 4、受信 = 4、損失 = 0 (0% の損失)、
ラウンド トリップの概算時間 (ミリ秒):
最小 = 2ms、最大 = 114ms、平均 = 30ms

 次に、固定IPで接続してみました。次のURLの記事を参考にしました。
https://zenn.dev/stranger396/articles/15d5021e61fab7
 WiFi.config()を使えば固定IPにすることができるようです。
https://reference.arduino.cc/reference/en/libraries/wifi/wifi.config/
 WiFi.begin()の前に WiFi.config(ipaddress);を記述することが重要です。具体的には、スケッチ例(Udp.ino)の一部を次のように変更しました。
WiFiUDP Udp;
IPAddress myIP(192,168,0,222);

void setup() {
Serial.begin(115200);
WiFi.config(myIP);
WiFi.begin(STASSID, STAPSK);

 何か良いUDPテストツールは無いかとネットで検索したところ、以前私が作ってものが引っかかりました。自分ではとっくに忘れていたのです。
https://blog.goo.ne.jp/namva/e/fb7c8c737dd9b2ebe82ab60fb7a2c321

 これでテストしたところ、シリアルモニタには次のように表示されました。
Received packet of size 7 from 192.168.0.11:59110
(to 192.168.0.222:8888)
Contents:
hello!


 UDP通信はちゃんと動作しているようなので、これをベースにしてリニアアンプ用液晶表示装置の操作をWiFi(UDP)で通信できるように開発を進めたいと思います。



SDカードからLittleFSにファイルをコピーするプログラム

2024-12-31 16:33:07 | プログラミング
 ラズパイpicoを使用したシステムで、SDカードからLittleFSにコピーするプログラムを作成しました。LittleFSとは、ラズパイpicoのフラッシュメモリ内に作成されたファイルシステムです。
 配線方法などハードウェアは、昨日のブログで述べている通りです。
 毎回SDカードを読まなくても、一度LittleFSにコピーしておけば、それを読み出して、機器のコンフィグレーションなどに使用することができます。
 当該機器のパラメータ(例えばWiFiのSS IDなど)を変更したい時には、パソコン等で、変更したパラメータファイルを作成してSDカードにコピーし、そのSDカードをラズパイpicoのシステムに挿入して電源を起動すれば、LitteFSにコピーされます。アプリ側で、LittleFSのファイルを読み込んで機器をコンフィグレーション(SS IDを設定)するようになっていれば、新しいSS IDに対応できるようなります。

 そこで、次のような機能のプログラムを作成しました。
1)SDカードがあるかどうか確認する
2)SDカードがなければ、TFTにその旨を表示して終了する
3)SDカードに指定されたファイルがあるかどうか確認する
4)ファイルがなければその旨を表示して終了する
5)LittleFSがあるかどうか確認する
6)LittleFSがなければその旨を表示して終了する
7)SDカードから指定されたファイルを読み出してLittleFSにコピーする
8)SDカードを取り外すことを促す(あれば次回起動時に同じことを繰り返すので・・・)

 このプログラム単体で使用するものではなく、この機能が必要なアプリケーションの冒頭に組み込むことを想定しているので、ファンクションブロックを単独のソースファイル(SDcard.ino)にまとめました。その機能をテストするためのプログラム(copySDpico.ino)を用意しました。開発環境は、Arduino-IDE 1.8.9です。
 まず、copySDpico.inoのソースを示します。
// filename: copySDpico.ino
// description: test program for initSD and copyFromSDtoLFS
// author: H.NAMVA
// issued on: 2024.12.31 rev.1


#include <TFT_eSPI.h>
#include <SPI.h>
extern bool mountSD;
extern bool initSD(void);
extern bool copyFromSDtoLFS(void);


TFT_eSPI tft = TFT_eSPI(); // Invoke library


void setup(void) {
//initialize SD card before TFT
mountSD = initSD();
//initialize TFT display
tft.init();
tft.setRotation(3);
tft.fillScreen(TFT_BLACK);
tft.setCursor(0, 0, 2);
tft.setTextColor(TFT_WHITE, TFT_BLACK); tft.setTextSize(1);
if(copyFromSDtoLFS("config.txt")){
delay(1000);
}
}


void loop() {
delay(10000);
}


 続いて、SDcard.inoのソースを示します。
// filename: SDcard.ino
// description: contains functions for SD card and LittleFS
// This program is written intended to call copyFromSDtoLFS from setup().
// messages are displayed on TFT panel during execution
// you need to initialize TFT perior to call functions other than initSD
// author: H.NAMVA
// issued on: 2024.12.31 rev.1


#include <SPI.h>
#include <SD.h>
#include <LittleFS.h>
#include <TFT_eSPI.h>


#define PINS_SPI_CS 13
#define PINS_SPI_SCK 14
#define PINS_SPI_MISO 12
#define PINS_SPI_MOSI 15


bool mountSD = false;


bool initSD(void) {
bool sdInitialized = false;
SPI1.setRX(PINS_SPI_MISO);
SPI1.setTX(PINS_SPI_MOSI);
SPI1.setSCK(PINS_SPI_SCK);
sdInitialized = SD.begin(PINS_SPI_CS, SPI1);
if (!sdInitialized) {
return false;
} else {
return true;
}
}


bool isExistsLFS(){
if(LittleFS.begin()){
return true;
}else{
return false;
}
}

bool copyFromSDtoLFS(String fname){ // copy specified file from SD to LittleFS
String str;
bool errFlg = false;


if(!mountSD){
//try to mount SD card
mountSD = initSD();
if(!mountSD){
tft.println("SD card is not present");
return false;
}
tft.println("SD card detected and initialized");
}
if(!SD.exists(fname)){
tft.println("can't find " + fname + " in SD card");
return false;
}
tft.println("find " + fname + " SD card");
if(!isExistsLFS()){
tft.println("LittleFS is not exist in pico");
return false;
}

//Copy file from SD to LittleFS
File srcFile = SD.open(fname, "r");
if(!srcFile){
tft.println("SD open error for " + fname);
return false;
}
File destFile = LittleFS.open(fname, "w");
if(!destFile){
tft.println("LittleFS open error for " + fname);
return false;
}
while(srcFile.available()){
str = srcFile.readString();
if(!destFile.println(str)){
tft.println("LittleFS write error");
errFlg = true;
break;
}
}
srcFile.close();
destFile.close();
tft.println("copy complete from SD to LittleFS " + fname);
tft.println("recomend to remove SD card after power off");
return true;
}

ラズパイpicoでタイマー割込み

2024-12-26 12:58:20 | プログラミング
 ラズパイpicoでタイマー割込みが使いたいと思って色々探していたら、とても良い記事がありました。
 この例を少し弄って、loop関数内でflagなどを見ずに、タイマー割込みルーチン内だけで、LEDをトグル動作させるようにしてみました。

struct repeating_timer st_timer;
bool Timer(struct repeating_timer *t) {
 if (digitalRead(LED_BUILTIN) == 0) {
  digitalWrite(LED_BUILTIN, HIGH);
 } else {
 digitalWrite(LED_BUILTIN, LOW);
 }
 return true;
}


void setup() {
 pinMode(LED_BUILTIN, OUTPUT);
 add_repeating_timer_us(500000, Timer, NULL, &st_timer); //500ms interval



void loop() {
 ...


 add_repeating_timer_usというAPIがあるみたいなので、ちょっと調べてみましたが答えに辿り着きませんでした。まぁ・・・タイマー割込みの使い方さえわかれば良いので深入りしないことにします。
 タイマー割込みルーチンを1つ書くことができれば、任意の数のタイマーを作ることができます。1msのインターバルで、4つのタイマーを作成した例を示します。4つのタイマーの値が0以上なら1ms毎にデクリメントします。0になったら0のままなので、値が0ならタイムアウトしたと見做すことができます。

int timer[4];
struct repeating_timer st_timer;
bool Timer(struct repeating_timer *t) {
 int i;
 for(i=0, i<4; i++){
  if(timer[i] > 0){
   timer[i]--;
  }
 }
 return true;
}


void setup() {
 add_repeating_timer_us(1000, Timer, NULL, &st_timer); //1ms interval



void loop() {
 ...
 if (some event){
  timer[0] = 200; //set timer0 to 200ms
 }
 ...
 if(timer[0] == 0){ // timer0 time-up
  //exec delay 200ms
 }
 ...
 このようなソフトウェアタイマーを利用して、リアルタイムOSを使わずに、疑似的なマルチタスキングを実現することができます。
 私は、1990年代からステートマシンプログラミングと称して、この手法を用いて数多くの組み込みシステムを構築してきました。
 ステートマシンプログラミングについて語ると長くなるので、機会があれば説明したいと思いますが、さわりだけ説明します。複数のステートマシンをloop関数(メインループ)の中で呼び出すというものです。例えば、次のようにloop関数記述します。ステートマシンが4つあるという想定です。

void loop(){
 stateMachine1();
 stateMachine2();
 stateMachine3();
 stateMachine4();
}

 各ステートマシンの中では、同期的な待ちは一切行わずに、呼び出し元に戻るようにします。こうすることで、全てのステートマシンに制御が渡るようになり、マルチタスキングが可能になるのです。
 前述のソフトウェアタイマーを使用して、次のようにステートマシンを記述します。

void stateMachine1(void){
 static state = 0;
 switch(state){
 case 0: //state0
 ...
 if (condition for transfer to state1){
  timer[0] = 100;
  state = 1;
 }
 break;
 case 1: //state1
 if (timer[0] == 0){ // time-up 100ms, transfer state2
  state = 2;
 }
 break
 case 2:
 ...
 break;
 case 3:
 ...
 break;
 default:
 break;
}

 ステートマシンなので、何らかの条件で別のステートに移行するようにプログラムします。タイムアップしたら別のステートに移行するというような記述が随所に出てくるでしょう。

ADS1115のAD変換をブラッシュアップ

2024-12-22 16:50:13 | プログラミング
 リニアアンプ用液晶表示装置では、LDMOSのVdd(ドレイン電圧)、Id(ドレイン電流)およびFwd(進行波電力)ならびにRef(反射波電力)を表示するようにしています。そのために、ADS1115を用いてAD変換してこれらの値を得ています。
 これまでに作成したプログラムでは、AD変換できればいいやという考えで粗削りな方法でAD変換していましたが、完成に向けて、値がばらつかないようにブラッシュアップしました。GithubにあるADS1x15ライブラリのドキュメントを読みながら色々試してみました。
 これまで、4つのチャンネルのゲインは全て1倍(±4.096V)にしていましたが、Vdd以外は8倍(±512mV)にしました。
 これまで、変換レートはデフォルトの128SPSにしていましたが、それほど高速にAD変換する必要はないので32SPSにしました。
 これまで、ADS.readADC(channel)というメソッドを使って、同期的にAD変換値を読み出していましたが、変換レートを64SPSや32SPSに代えてみたところ、readADC内のタイムアウトの方法に問題があるようで、正常な値が読み取れなかったので、次のような非同期的な方法により値を読み取るようにしました。
  ADS.requestADC(1);
  while(!ADS.isReady()){ delay(10);}
  val_1 = ADS.getValue();
 AD変換の実験時にバラつきを評価するために、標準偏差を得たかったので、Arduinoの統計用ライブラリを使いました。
 ADC(ch1)の入力には単三電池を信号源として5kΩの多回転トリマで分圧して約0.3Vを印加しました。
 サンプリングを変えて、100回分のデータに統計処理した結果を以下に示します。
DataRate: 128SPS(default)
=====================================
Count: 100
Min: 18804.0000
Max: 18821.0000
Average: 18813.7500
variance: 11.9877
pop stdev: 3.4623
unbias stdev: 3.4798
time(ms): 6115

DataRate: 64SPS
=====================================
Count: 100
Min: 18735.0000
Max: 18738.0000
Average: 18736.4492
variance: 0.5876
pop stdev: 0.7666
unbias stdev: 0.7704
time(ms): 9292

DataRate: 32SPS
=====================================
Count: 100
Min: 18716.0000
Max: 18719.0000
Average: 18717.1699
variance: 0.4810
pop stdev: 0.6935
unbias stdev: 0.6970
time(ms): 13500

 測定値が徐々に低下しているのは、使い古した単三電池(電圧約1V)を信号源として使っているためだと考えられます。



 


続・リニアアンプ操作パネルの画面作成

2024-07-14 20:37:39 | プログラミング

 今日は一日中雨だったので、プログラミングをして遊びました。7月2日以来中断していたリニアアンプ操作パネルの続きです。画面の表示位置などは概ね決着したので、次は画面に動きを与える番です。ラズパイpicoにはアナログ入力として使えるピンが3つあります。このうちの2つに可変抵抗器を接続して、その電圧を進行波電力および反射波電力として表示させるようにしました。

 2つの可変抵抗器をドライバーで回すと、進行波電力(FWD)と反射波(REF)のバーグラフと数値表示が変化するようになりました。

 

 ドレイン電圧やドレイン電流などもAD変換したいのですが、ラズパイpico内蔵のアナログ入力だけでは不足しています。I2Cなどを使ってアナログ入力ポートを増やす必要がありそうなので、Amazonかどこかで調達しようと思います。