11-01-2022 08:05 AM
まつLabさんとコラボしてLabVIEW Communityを使った分光データの収録を行っています。これまではブラックボックスのPICマイコンで制御されていましたが、今回はESP32で制御しています。
「まつLab」と「計測・解析ラボ」で大垣ミニメイカーフェア(12/3,12/4)に出展を予定していますので、興味のある方はぜひお立ち寄りください。準備が間に合えばESP32版も展示します。私は遠いので資料提供だけの参加です。
https://www.iamas.ac.jp/ommf2022/maker/matsulab/
ここではESP32+Arduinoは詳しくは触れませんがプログラムを添付しますので、気になる方はダウンロードしてご覧ください。TCD1304の仕様書と対応させながらプログラムを読んでいただければ分かると思います。
WiFi機能を使って拡張していただける方大歓迎です。
ラインセンサーTCD1304は仕様上最小クロックが800kHzなので200kHzのアナログサンプリングが必要です。ESP32のADCはそんなには速くなくて、20kHzが私が引き出せる最速でした。仕様外でも遅い方は余裕があるといううわさがありましたので、クロック80kHz / ADC 20kHzで動作させています。
仕様内で動かした方が気持ちが良いのでRaspberry Pi Picoに移行しようとしましたが、現状はADCが失敗作ということが理解できたのでやめました。
受信プログラムのフロントパネルです。
シャッター速度を選択できるようにしました。230400bpsでバイナリデータを使って1秒弱ぐらいの更新時間です。
ブロックダイヤグラムは特に新味はありません。過去の記事を参照していただければ好きなように改造できるのではないかと思います。
SpectrometerESP32.zipには以下のファイルが圧縮されています。
Spectrometer.vi
subVIs>sub__PickI16.vi
subVIs>sub_FindHeader.vi
LisEsp32Int16_221004>LisEsp32Int16_221004.ino
11-01-2022 03:10 PM
修正の情報です。
データを保存するときにX軸の波長が整数になっていました。"%d"を"%.3f"に変更してください。
赤外LEDを点灯した例です。
保存してLibreOfficeのCalcで開いてみると以下のように表示されます。
LabVIEWを使ってデータを表示するときは次のようなプログラムを書きます。
11-05-2022 04:39 PM
私はマイコンが得意というわけでもないのでArduino IDEで扱えるという前提で、簡単にパルス生成ができて、精度が良くて高速なADC、16ビットで4000データぐらいを保存できるメモリが使えるマイコンを探していました。TCD1304の基本クロック(ΦM)は400kHzから4MHzで使います。ΦMパルス4個の間隔でデータが出力されますので、ΦMの1/4の周波数のパルスを作り、パルスエッジの割り込みでアナログ入力してデータを配列に書き込みたいと思います。
ESP32を使ったボードはWiFiやBluetoothが使えてAliexpressで500円前後で購入できるので大好きなのですが、私のADCの使い方で試してみると20kHz程度が上限なのが残念です。
Arduino UNOに使われているAVRマイコンをFPGAで構成したAlorium TechnologyのXLR8はAVRと同じパルス生成方法が使えて、ADCは200kHzが可能で12ビットなので申し分ないのですが、UNO同様にメモリが少なくて、かつSRAMを扱うにはSPIのクロックがシャキッとしていなくて、波形整形すれば使えたのですがそこまでするのもな、ということで不採用になりました。
STM32マイコン、Bluepillと呼ばれる格安のボードも試しましたがArduinoとして使うにはUSBシリアルが使いこなせず、UARTから別のUSBシリアル基板を使えばできそうですが、今一つの感があって断念しました。ARMの系列のRaspberry Pi Picoならばもっと良いのではないかと試してみたのですが、ADCが大失敗ということで途中でやめることにしました。仕様書がわかりやすく安心感がありますので次作に期待しています。
結局今回検討したArduino IDEで使えるマイコンの中ではESP32がベストでした。遅くて仕様外で使うしかないのですが、WiFiが付いているので無線接続の可能性が嬉しいと思います。
<<仕様書の5ページの表>>
TCD1304を動作させるプログラムの第1のポイントは思い通りのパルスを生成することです。
ESP32はArduino UNOに使われているAVRよりもわかりやすく柔軟に生成することができます。
以下のサイトが分かりやすく解説してくれています。
https://www.mgo-tec.com/blog-entry-ledc-pwm-arduino-esp32.html/2
第2のポイントは割り込み処理で意図したタイミングでデータを収録することです。
以下のサイトが分かりやすく解説してくれています。
https://lang-ship.com/blog/work/esp32-freertos-l04-interrupt/
第3のポイントはICG(積分クリアゲート)パルスの生成です。
ESP32とTCD1304の間には波形成形のための74HC04が入っていてΦM、SH、ICG信号を反転するので、センサーへの入力とESP32での出力は反転しているので注意が必要です。
仕様書8ページに書いてあるICGパルス遅延時間、ICG・SHタイミング、SHパルス幅、ICG・ΦMタイミングの表を頼りに生成します。
特にICGの立ち下がりが、SHの立ち上がりよりもt2(100nsから1000ns)前というのがプログラミング上の問題です。これは、SHと同じ周波数でパルス幅が少し違うパルスのエッジを使うことにしました。プログラム内ではPreSHという名前で書かれています。
具体的にESP32からのパルス出力は、PreSHの立ち下がりでICGをHIGHにして、(200ms+シャッター時間)を待機、φMの立ち下がりでICGをLOWにします。
プログラムの大まかな流れは次のようなものです。
setup関数内でfaiM、SH、PreSH、TrigSOの4本のパルス信号を動作させます。
Loop関数では、最初にシリアルでシャッター速度変更の指示があるかどうか確認して指示があれば対応します。
その後readLineImage()関数でICG信号を作ります。ICGをLOWにした後で、TrigSO信号(20kHz)の立ち下がりで画素データの読み取りと配列への保存を行います。全画素読み取り終わったら、配列に保存したデータをシリアルで出力します。
//Koji Ohashi MaDA Lab@Morioka 221004
//LisEsp32Int16_221004
//パルス生成:Special thanks to
//https://www.mgo-tec.com/blog-entry-ledc-pwm-arduino-esp32.html/2
//https://logikara.blog/pwm/
//割り込み処理:Special thanks to
//https://lang-ship.com/blog/work/esp32-freertos-l04-interrupt/
//phiMのHighのタイミングでICGをLow
//IO05:phiM 80kHz
//IO23:SH シャッター速度:(1/SUTTER)sec
//IO19:PreSH ICGパルスの先頭のためのタイミングパルス(配線不要)
//IO18:ICG
//IO26:TrigSO 20kHz ReadSOのタイミングパルス(配線不要)
//IO27:ReadSO
//faiM
#define LEDC_CHANNEL_M 0 //channel max 15//CHANNEL_0とCHANNEL_1は周波数とタイマービットを同一値
#define LEDC_TIMER_BIT_M 6 //64(0-63)
#define LEDC_BASE_FREQ_M 80000.0
#define DUTY_M 32//50% 0x20 = 32
#define GPIO_PIN_M 5 //GPIO #36~#39 は設定不可
int default_SH_index=3;
int Shutter[]={1,2,5,10,20,50,100,200,500,1000};
int SH_Time = 1000/Shutter[default_SH_index];//(ms)
//SH
//#define SHUTTER 100//シャッター速度:(1/SUTTER)sec
//#define SHUTTER 10//シャッター速度:(1/SUTTER)sec
//#define SHUTTER 5//シャッター速度:(1/SUTTER)sec
#define LEDC_CHANNEL_SH 2 //channel max 15//CHANNEL_2とCHANNEL_3は周波数とタイマービットを同一値
#define LEDC_TIMER_BIT_SH 16 //65536(0-65535)
//#define LEDC_BASE_FREQ_SH SHUTTER
int LEDC_BASE_FREQ_SH=Shutter[default_SH_index];
#define DUTY_SH 49152//75% 49152/65536
#define GPIO_PIN_SH 23 //GPIO #36~#39 は設定不可
//PreSH
#define LEDC_CHANNEL_PreSH 3 //SHと共通
#define LEDC_TIMER_BIT_PreSH 16 //SHと共通
//#define LEDC_BASE_FREQ_PreSH SHUTTER //SHと共通
int LEDC_BASE_FREQ_PreSH=Shutter[default_SH_index]; //SHと共通
#define DUTY_PreSH 49150//SHよりも250ns早く立ちさがる
#define GPIO_PIN_PreSH 19 //GPIO #36~#39 は設定不可
#define ICG 18
//TrigSO
#define LEDC_CHANNEL_TrigSO 4 //channel max 15//CHANNEL_4とCHANNEL_5は周波数とタイマービットを同一値
#define LEDC_TIMER_BIT_TrigSO 6 //64(0-63)
#define LEDC_BASE_FREQ_TrigSO 20000.0
#define DUTY_TrigSO 8//50% 0x20 = 32
#define GPIO_PIN_TrigSO 26 //GPIO #36~#39 は設定不可
#define PIXELS 3694
#define ReadSO 27
int16_t pixelArray[PIXELS];
volatile int read_count=0;
volatile int16_t sensorValue=0;
byte HEADER[]={0x00,0x00,0xFF,0xFF};
byte SendBytes[2];
//割り込み関数***************
void IRAM_ATTR headICG(){
detachInterrupt(GPIO_PIN_PreSH);
digitalWrite(ICG,HIGH);
}
void IRAM_ATTR tailICG(){
detachInterrupt(GPIO_PIN_M);
digitalWrite(ICG,HIGH);//タイミング調整のため
digitalWrite(ICG,LOW);
}
void IRAM_ATTR readSO(){
//sensorValue = analogRead(analogPin);
sensorValue = analogRead(ReadSO);
pixelArray[read_count]=sensorValue;
read_count++;
}
//割り込み関数***************
void setup() {
Serial.begin(230400);
pinMode(ICG,OUTPUT);
pinMode(ReadSO,ANALOG);
digitalWrite(ICG,LOW);
//faiM
ledcSetup(LEDC_CHANNEL_M, LEDC_BASE_FREQ_M, LEDC_TIMER_BIT_M);
ledcAttachPin(GPIO_PIN_M, LEDC_CHANNEL_M);
ledcWrite(LEDC_CHANNEL_M, DUTY_M);
//SH
ledcSetup(LEDC_CHANNEL_SH, LEDC_BASE_FREQ_SH, LEDC_TIMER_BIT_SH);
ledcAttachPin(GPIO_PIN_SH, LEDC_CHANNEL_SH);
ledcWrite(LEDC_CHANNEL_SH, DUTY_SH);
//PreSH
ledcSetup(LEDC_CHANNEL_PreSH, LEDC_BASE_FREQ_PreSH, LEDC_TIMER_BIT_PreSH);
ledcAttachPin(GPIO_PIN_PreSH, LEDC_CHANNEL_PreSH);
ledcWrite(LEDC_CHANNEL_PreSH, DUTY_PreSH);
//TrigSO
ledcSetup(LEDC_CHANNEL_TrigSO, LEDC_BASE_FREQ_TrigSO, LEDC_TIMER_BIT_TrigSO);
ledcAttachPin(GPIO_PIN_TrigSO, LEDC_CHANNEL_TrigSO);
ledcWrite(LEDC_CHANNEL_TrigSO, DUTY_TrigSO);
}
void loop() {
while (Serial.available() > 0) {
int shutter_index = Serial.parseInt();
if (Serial.read() == '\n') {
changeSH(shutter_index);
delay(200);
}
}
readLineImage();
writeArrayData();
}
void changeSH(int index){
LEDC_BASE_FREQ_SH=Shutter[index];
LEDC_BASE_FREQ_PreSH=Shutter[index];
SH_Time = 1000/Shutter[index];//Shutter[]={1,2,5,10,20,50,100,200,500,1000}
//SH
ledcSetup(LEDC_CHANNEL_SH, LEDC_BASE_FREQ_SH, LEDC_TIMER_BIT_SH);
ledcAttachPin(GPIO_PIN_SH, LEDC_CHANNEL_SH);
ledcWrite(LEDC_CHANNEL_SH, DUTY_SH);
//PreSH
ledcSetup(LEDC_CHANNEL_PreSH, LEDC_BASE_FREQ_PreSH, LEDC_TIMER_BIT_PreSH);
ledcAttachPin(GPIO_PIN_PreSH, LEDC_CHANNEL_PreSH);
ledcWrite(LEDC_CHANNEL_PreSH, DUTY_PreSH);
}
void writeArrayData(){
Serial.write(HEADER,4);
for(int i=0;i<PIXELS;i++){
sensorValue = pixelArray[i];
SendBytes[0] = byte(sensorValue);
SendBytes[1] = byte(sensorValue >> 8);
Serial.write(SendBytes,2);
}
}
void readLineImage(){
float workTime=millis();
attachInterrupt(GPIO_PIN_PreSH, headICG, FALLING);//ICGの先頭タイミング
delay(200);
delay(SH_Time);
attachInterrupt(GPIO_PIN_M, tailICG, FALLING);//ICGの後端タイミング
read_count=0;
attachInterrupt(GPIO_PIN_TrigSO, readSO, FALLING);//SOの読み取りタイミング
while(read_count<PIXELS){} //全画素取り込み
detachInterrupt(GPIO_PIN_TrigSO);//SOの読み取り終了
workTime=millis()-workTime;
//Serial.println(workTime);//時間計測
}
12-23-2022 06:52 AM - 編集済み 12-23-2022 06:53 AM
実際にESP32に書き込むプログラム「LisEsp32Int16_221004.ino」を使われた方からの情報ですが、OS信号のサンプリングが10kHzだったようです。
20kHzのタイミングパルスの立下りで、OS信号を読み込むための割り込み関数 readSO() を呼ぶのですが、割り込み関数の実行に時間がかかって20kHzで動作できなかったようです。
ESP32ボードマネジャーのバージョンの違いで生じているらしいので、当面「LisEsp32Int16_221004.ino」を使う場合は1.0.4を使用してください。
1.0.4に下げたところ期待通りの動作になったとのことです。
02-07-2023 07:55 PM
ほとんど忘備録のような記事で恐縮ですが、タングステンランプの調光についてメモします。
分光器の身近なアプリケーションとして吸収スペクトルが良いのではないかと考えて、安価な光源を探しています。
タングステンランプはなだらかなスペクトルなので手始めに試してみました。やってみるとLEDとは比べられないほど電流が流れるので制御するのが大変なのと、赤外に向けてどんどん出力が上がるのでセンサーのダイナミックレンジではカバーできず使いにくいことがわかりました。
普段から分光器をいじっている人には試すまでもないことだろうと思います。
タングステンランプは1.5V0.5Aのものを以下から購入しました。
https://store.shopping.yahoo.co.jp/c-task/83-4356.html
オーム社のニップル球 2.2V0.25Aの方が良かったかもしれません。
液体用の容器は香水などを小分けするガラス瓶で直径8mmのものを使いました。
https://www.amazon.co.jp/gp/product/B09QFQ68L5/
LM2596を使ったDC - DC降圧コンバータで、5Vから1.5V付近まで落とします。
https://www.amazon.co.jp/gp/product/B07NVSVW1N/
手元にあったMOS-FET 2SK2961にESP32でPWMを作って、降圧した豆電球用電源を制御します。
Arduino IDEでESP32のPWMを使う例(80-255で変調)です。
#define LEDC_CHANNEL_Tu 0 //channel max 15//CHANNEL_0とCHANNEL_1は周波数とタイマービットを同一値
#define LEDC_TIMER_BIT_Tu 8 //0-255
#define LEDC_BASE_FREQ_Tu 1000.0//1kHz
#define GPIO_PIN_Tu 0 //GPIO 0 #36~#39 は設定不可
int DUTY_Tu = 80;
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
ledcSetup(LEDC_CHANNEL_Tu, LEDC_BASE_FREQ_Tu, LEDC_TIMER_BIT_Tu);
ledcAttachPin(GPIO_PIN_Tu, LEDC_CHANNEL_Tu);
ledcWrite(LEDC_CHANNEL_Tu, DUTY_Tu);
}
void loop() {
for(int i=80;i<255;i++){
SetDuty(i);
delay(20);
}
}
void SetDuty(int duty){
ledcWrite(LEDC_CHANNEL_Tu, duty);
}
豆電球は大変だなーと思います。しかも急激に増加するのでそのままでは使えない。
他に良い手はないかと探してみたら、豊田合成の太陽光LEDが良さそうなので注文してみました。
以下のタングステンの出力よりは良いのではないかと思います。
02-09-2023 04:50 PM
豊田合成の太陽光LEDが届きました。
https://akizukidenshi.com/catalog/g/gM-15933/
さっそく分光器で見てみます。
太陽光LEDの公表されているスペクトルとは形が少し異なりますが、一般的な照明のスペクトルとは明らかに違います。
NECのLEDシーリングライト(以下1枚目)と電球型LEDランプFORA TK-BE061C(2枚目)のスペクトル
豊田合成の太陽光LEDはタングステンランプより吸収スペクトルの観察に使いやすそうです。
役立つマイ分光器 !!