室内CO2濃度の簡単なロガー
前回の「室内炭酸ガスの濃度測定」で少しご紹介したロガーの製作についてです。前の記事から少し間があいてしまいましたため、ここで急いで簡単にご紹介することにします。
ロガーでは、センサーが空気と接するよう、上の写真のようにケースの右側にとりつけてセンサーの前面に小さな穴を設けておきました。換気している部屋の机の隅において、窓を閉めたり全開したりすれば、次のような測定値が記録できます。
前回は、ロガーをブレッドボードで試作したところまででしたが、その後すぐに基板に組んで1号機を作りました。次の写真の下の基板です。
このロガーの回路は前回のとほぼ変わりませんが、都合のよいようにピンを変えたところがありますので、ここに改めて掲載します。
Arduinoプログラムはこの記事の最後の方につけておきます。
最初の基板では電源スイッチをつけましたが、ケースに収めにくいので、「不要なものはとにかく外す」という主義で他の位置関係などを含めて少し改善をしたのが次の元図。
これを作るには、過去の工作で作った部品ごとのパターンを切り貼りしてまずは部品面の図を作り、左右逆転します。毎度ながら時間節約のため、ランドなどは勝手に省略する基板です。基板の大きさは105mm x 78mm です。
いつものことですが、ツールパス・データに変換してCNCルーターで削り20分程でできます。
次が基板です。
加工の前にまずはジャンパー線で補完、今回は10本と少し多めですが。グラウンドは0.6φスズメッキ線5本、その他は細い銅線5本です。
最初に3V用レギュレータだけを配線して、センサーの予定電圧に正確に合わせておきます。
多回転ポテンシオメーターなので上図のように精密に合わせられます。また生活の知恵として他の抵抗器を混ぜないので、温度補正が不要になり常に安定した電圧になります。
ここまで行けば後はさっさと部品を付けて完成。
ブレークアウトモジュールは試作用にL型ヘダーピンが付いたものが多いですが、無理に外して付け替えずにソケットで対応します。なにせハンダを吸い取る場合はパターンを傷つけてしまうことが多いので、必要悪ですね。ハンダ吸い取り線だと時間がかかるし、「はんだシュッ太郎」ではパターンを剥がしやすく、ヒートガンではパーツにダメージを与えることがあるので、とにかくそのまま使うようにしています。
センサー部品(SMD)を直接使うほうが小さくて良いですが、購入数量が少ないと、ブレークアウトモジュールのほうが圧倒的に安いうえ扱いやすい。もしピンヘダーが予め着いてなければ言うことないのですが、そういうものは多くはありません。
上の例では上部のマイクロSDライターと、下部のTM1637はブレークアウトモジュールそのままです。左にあるRTC(リアルタイムクロック)DS3231モジュールは、空いているI2C用4穴に新たな平型ヘダーピンをつけて使います(L型ピンは整形し樹脂粘土をかぶせて脚にしているがカットしてもよい)。中央のロジックレベルコンバーターはコストも安いので必要な穴にだけスズメッキ線を通して直接ハンダづけします(底部がジャンパー線に当たらないよう少し浮かせてつけます)。
組み立て上がりを銅箔面から見ると次です。
1号機と並べると次です。下が今回の2号機。大して変わりませんが。
さて、2つ用意した目的は、もちろん長時間ロギングした値の変化を比較しようというものです。なぜなら、48時間のバーンインの最中に値が上昇する状況を詳しく見たかったからです。そのため、CCS811モジュールを改めて2個入手し、最初から行いました。
バーンイン24時間後時点で再スタートし、残りの24時間の途中で次のように値の上昇がみられます。
驚くべきことに2つの独立したセンサーの出す異常値は同じ変化をしています。つまり環境の僅かな変化が増幅されて値に反映されているように見えます。このセンサーの内蔵ソフトウェアはいったいどのような処理をしているのでしょうか。
とにかくそれからしばらくすると何事もなかったかのように落ち着きますから、このセンサーは凡人の理解を超えています(・・?。
バーンインの終わった状態でケースにとりつけました。このケースはだいぶ前に秋月で購入したポリカーボネートの丈夫なケースで、加工も簡単です。
センサー取り付け部分は、後から側面を適当に加工したため次の写真のように少し曲がってとりつけてしまいましたが、まあよしとします。
これで室内を記録した結果、換気状態に応じた値を敏感に示し異常なく動作します。なお、始まりの20分間はこのセンサーの「Run-in時間」とされ、測定値を無視すべき時間です(下のグラフでは09:22以降が測定値として有効です)。
今のところ1秒単位で計測・表示し、SDへは分単位で(分替わりの最初の測定結果)1つを記録していますが、記録としては秒単位の移動平均をとればスムーズかもしれません。あるいは複数回の平均をとるのもよいかもしれません。とはいえセンサーの提供する個々の値も既に多数回測定し平均した結果だろうとは思いますが。
また、この測定は秒単位の「モード1」なのでセンサーを構成しているヒーターは常時オンになっています。オンオフするモードでどうなるかも今後調べたいところです、時間のあるときにマイペースでなのですが。
しばらく使いこんだら「ベースライン設定」、つまりキャリブレーションが望ましいとのこと。少し手間のかかるセンサーですが、それほど正確なのでしょうか。マニュアルは次のサイトにまとめてリストアップされています。
今回のプログラムは次です。毎度のことですが、Arduino-UnoにIDEで書き込み、ATmega328pの単体を取り出して使います。いつもそういう使い方をするため、UNOの本体には無圧(Zero Pressure)ソケットを装填しておきます。
/* *********************************************************
CO2 Logger V03 (c)2020 Akira Tominaga
Function:
Measure CO2 and TVOC using CCS811, with RTC & TM1637.
Revisions:
V00: Original devloped on 6/25/'20
V02: Show sensor error status on display 6/28/'20
V03: SD-close-switch from D2 to D4 7/05/'20
* ********************************************************/
#include "Wire.h" // I2C for CCS811 and RTC-DS3231
#include "ccs811.h" // CCS811 Air quality (CO2) sensor
#include "SPI.h" // SPI for SD drive interface
#include "SD.h" // Micro SD drive
#include "TM1637Display.h" // TM1637 4dig LED Display
// *** for Air quality sensor CCS811
#define AQadr 0x5A // AQ sensor adr (0x5B alternative)
CCS811 AQ; // AQ as air quality sensor symbol
uint16_t CO2; // CO2 ppm(400-8192), or AQ err_status
uint16_t TVOC; // TVOC ppb(0-1187)
char strCT[10]; // editing area for CO2 and TVOC
// *** for micro SD writer
File aqLog; // aqLog as SD file symbol
#define Cs 10 // SD Chip-select pin is D10
// MOSI=11, MISO=12, CLK=13
#define SDsw 4 // SD-file closing button (L=on)
// *** for TM1637 display
#define TMdio 6 // DIO for TM1637
#define TMclk 7 // CLK for TM1637
TM1637Display disp(TMclk, TMdio); // disp as TM1637 symbol
byte Data[] = { 0x3F, 0x3F, 0x3F, 0x3F }; // data for TM1637 init 0000
// *** for Real Time Clock DS3231
byte vR[8]; // values in RTC registers
int vI[8] = { 0, 0, 0, 0, 0, 0, 0, 0}; // integers for RTC data
#define RTC_addr 0x68
#define mdI 0 // Initial mode
#define mds 1 // ss
#define mdm 2 // mm
#define mdh 3 // hh
#define mdW 4 // WW=Day of Week
#define mdD 5 // DD
#define mdM 6 // MM
#define mdY 7 // YY
#define sS 0
#define mM 1
#define hH 2
char strMDhm[11]; // edit area for calendar and clock
uint8_t mmSv; // minute-llvalue save area
#define erCd CO2 // error code instead of CO2, when error
void setup() { // ***** Arduino setup *****
Serial.begin(9600);
Wire.begin();
pinMode(SDsw, INPUT_PULLUP);
disp.setBrightness(0x0A);
if (!SD.begin(Cs)) {
//Serial.println("SD E");
Data[2] = disp.encodeDigit(0x02); // 0020=SD Error
disp.setSegments(Data);
while (1) {}
}
if (!AQ.begin()) {
//Serial.println("AQ E");
Data[2] = disp.encodeDigit(0x03); // 0030=AQ Error
disp.setSegments(Data);
while (1) {}
}
if ( !AQ.start(CCS811_MODE_1SEC) ) {
//Serial.println("Am E");
Data[2] = disp.encodeDigit(0x04); // 0040=AQ mode-set Error
disp.setSegments(Data);
while (1) {}
}
// set SD file name MMDDhhmm
getTime();
String s = strMDhm;
s.concat(".csv"); // complete Comma-separated-value file name
aqLog = SD.open(s, FILE_WRITE);
aqLog.println("MMDDhhmm,CO2m,VOCb"); // write column hdr
mmSv = vI[mdm]; // save current minute value
edDisp(); // edit Data and display (0000)
delay(2000); // enough time for AQ
}
void loop() { // ****** Arduino Loop *****
getTime();
// *** measure air quality and display it
uint16_t errSt, raw;
AQ.read(&CO2, &TVOC, &errSt, &raw);
if ( errSt == CCS811_ERRSTAT_OK ) {
// for serial plotter
Serial.print("400,");Serial.print(CO2); Serial.println(",1000");
} else if ( errSt == CCS811_ERRSTAT_OK_NODATA ) { // if no data
erCd = 9000; // show waiting-data
} else if ( errSt & CCS811_ERRSTAT_I2CFAIL ) { // if I2C error
erCd = 9100; // show I2C-error
} else { // for all other erros,
erCd = 9200 + errSt; // add error status to erCd 92ee
}
edDisp(); // edit Data and display
// *** Write SD data every minute
if (vI[mdm] != mmSv) { // if minute changed
mmSv = vI[mdm]; // save new minute
String s = strMDhm; // set date-time
s.concat(",") ; // set comma
s.concat(strCT); // set data
aqLog.println(s); // write a record
//Serial.println(s); // this is for debug only
}
// 1000mS delay, with checking SD-close-button every 100mS
for (int i = 0; i < 10; i++) {
if (digitalRead(SDsw) == LOW) { // if SD sw to close
aqLog.close();
disp.clear();
while (1) {}
}
delay(100);
}
}
/**************************************
User defined functions
* *********************************** */
// *** Edit and display values
void edDisp(void) {
// edit CO2 and TVOC value into strCT
sprintf(strCT, "%04d,%04d", CO2, TVOC);
// display CO2 value via Data
for (int i = 0; i < 4; i++) {
Data[i] = disp.encodeDigit(strCT[i]);
}
disp.setSegments(Data);
}
// *** get MMDD & hhmm into strMDhm ***
void getTime(void) {
rRTC(); // read RTC device
cR2I(); // convert RTC-format to Integers
sprintf(strMDhm, "%02d%02d%02d%02d", vI[mdM], vI[mdD], vI[mdh], vI[mdm]);
}
// *** read Real-Time-Clock ***
void rRTC(void) {
Wire.beginTransmission(RTC_addr);
Wire.write(0x00);
Wire.endTransmission();
Wire.requestFrom(RTC_addr, 7);
while (Wire.available() < 7) {} // wait for data ready
for (int i = 1; i < 8; i++) {
vR[i] = Wire.read();
}
Wire.endTransmission();
}
// *** convert RTC-format to Integers ***
void cR2I(void) {
vI[mds] = ((vR[mds] & B01110000) / 16) * 10 + (vR[mds] & B00001111);
vI[mdm] = ((vR[mdm] & B01110000) / 16) * 10 + (vR[mdm] & B00001111);
vI[mdh] = ((vR[mdh] & B00100000) / 32) * 20 + ((vR[mdh] & B00010000) / 16) * 10 + (vR[mdh] & B00001111);
vI[mdW] = vR[mdW];
vI[mdD] = ((vR[mdD] & B01110000) / 16) * 10 + (vR[mdD] & B00001111);
vI[mdM] = ((vR[mdM] & B00010000) / 16) * 10 + (vR[mdM] & B00001111);
vI[mdY] = ((vR[mdY] & B11110000) / 16) * 10 + (vR[mdY] & B00001111);
}
/* End of program */
Arsuino-Unoにどのように無圧ソケットをつけて使うかは次の記事に詳しく書いてありますので、必要な方は次のリンクをクリックしてご参照ください。
Zoom用かんたん操作ボタンをArduino-UNOで作る(その1) - 勝手な電子工作・・
さて、思い出したのでついでに書いておきます。この測定値を従来のNDIRセンサーと比較してみたいところです。最近世界中へ多く出ているのは中国製の次のものらしい。取り寄せたらすぐに来ました。
試しにUART接続で簡単に使ってみると次のように安定して測定されます。記録は分単位で、ピーク2つは息をかけたところです、だいたい2分後(!)に応答します。長時間の記録にも安定して使えるようにみえます。走り出しの2分間はRun-inタイムです。
赤外線吸収タイプなので価格は15ドルほどしますが、ずいぶん低価格になったものです。微細なほこりなどが入らないように空気の出入り口にはフィルターがありますから、反応もそのぶんは遅いでしょうね。
こちらのセンサーも長時間の間にはキャリブレーションを要しますがその操作は簡単です。そのうちにこちらを試してみようかと思いますが、うまく行けば半導体センサーと併用する測定がよいのではないかとふと感じます。
では今回はこの辺で。
©2020 Akira Tominaga, All rights reserved.
室内炭酸ガスの濃度測定―今どき役立ちそう
テレワーク中の部屋の換気が悪いと眠くなります。また、人が少しでも集まる部屋は換気が推奨されているところですね。換気の指標として部屋のCO2(二酸化炭素)を測り、1000PPM(0.1%)以下に保てれば「換気がとても良い」と言えるようです(注1)。
世界中で部屋に籠り勝ちの昨今、二酸化炭素の高価な測定器もよく出回っているようですが、ここではもちろん自作に挑戦ですね。上の写真は今回テストした1つです。
注1:換気については次のサイトの説明が分かり易いと思いました。http://www.jvia.jp/column/igi_13.htm
古典的なCO2の測定方法は、CO2が赤外線の特定波長を吸収する性質を使う大仕掛けなもの(NDIR)。最近は小さくて精度の高い金属酸化物のセンサーが開発され、世界で多く使われています。今回使うCCS811はオーストリアの超小型AirQualityセンサーです。
世界に出回っているブレークアウトモジュールを使います。
センサー自体の表面印字が少し違いますが、メーカーのリリースノートを調べてみたらこちらのほうが版が新しいようです。このモジュールにはI2Cプルアップ抵抗も組み込まれていますね。テストのため海外ネットで2個入手しましたが1つ8.5ドル程でした。
ストロベリーリナックスでは次のブレークアウトモジュールを日本国内で売っています。テストのためこちらも1つ購入しました。ずいぶん小さいですが価格は倍でした(^^;)。こちらにはPull-up抵抗等は入ってません。
どちらにしても電圧は最大3.3Vで、ボードにレギュレータ等はつけられていません。ですので、5Vのマイコンで動かすにはロジックレベル変換が必要です。
I2C接続部分なので2チャンネルのレベル変換があればよいわけですが、手持ちが4チャネル用ICでそれを使用します。次の写真ですが、安価なぶん、ずいぶん雑な作りかも。とはいえどれもこれまでちゃんと動き、なんら不満はないですが^^;
次に汚い手書きですみませんが、回路(というより接続図)です。
環境表示器としてこのAirQualityセンサーの他に、気象センサー(ボッシュのBME280)、リアルタイムクロック(RTC-DS3231)、表示用のLCD(I2C 20x4行)、時刻設置用のボタンなどをとりつけます。CCS811以外のプログラムは単純です(すべてのスケッチをこの記事の後のほうにつけておきます)。
3Vの供給は当初は3.3VレギュレータICを使っていましたが、CCS811では最大値に近いことがわかり可変三端子レギュレータに取り替えて3V未満にしました。Arduino出力の3.3Vは使っていません(このセンサーの最大値に近いので注意が必要です、詳しくは後のほうに書きます)。Arduinoは開発時だけ使い、組み立てるときはどっちみちATmega328P単体だけで動かすわけなので。
試作の結果、感度はとても鋭敏で30センチほどのところから少し息を吹きかけただけでも即反応するのがわかります。
仕様として、動かし初めに常に「Run-inタイム」が20分必要とされます。別途ロガーを作って記録したら、毎回ほぼ12分で測定値が安定するのがわかりました。
閉め切った狭い部屋で長時間テレワークをするときは、換気を忘れるとCO2濃度がどんどん上昇し1000PPMをすぐに越えます。そうすると眠気やら飽きなど色々感じます。換気の必要性を観察・記録するためにロガーがあると便利そうです。この際はもう1つのセンサーでそれも欲張ってトライしました。殆ど同じですからね。
次がその試作です。値はマイクロSDにCSV形式で記録します。なおCO2のPPM値4桁のみをTM1637Displayに出力します。ロガーとしてはこれで十分だと思います。
どちらの試作機でも、センサーがエラーを出したときは、9xEEとしてEEにエラーコード(Error status)を出すようにしました。
両方を比較するとセンサーの示す値は大きくは異なりません。
上の写真ではほぼ一致していますが、大きく差が出る場合も5%程度なので一安心です。また、最近は換気に気を付けているので空気が綺麗です。
このAirQualityセンサーは早い段階で48時間のBurn-inをするように勧められていますので早速開始し、その間ロガーで記録を続けました。
ところがバーンイン中の真夜中に無人の工作室で突然記録値が上昇し、エラーコード16(つまり0x10)が表示され止まっていました。エラー内容は「ヒーター温度が範囲外になった」とのエラーです。もう1台の表示装置側には異常なく、表示とバーンインを続けていました。
原因を詳しく調べるためにセンサーのRawデータを取り出して電流値などを見ると、仕様より少し高めでした。3.3Vレギュレータ電圧が高すぎたのかもしれないと気づいて測ると3.36Vです。限界近くで使うのはエラーも起きやすいわけで、これを可変にとりかえて、どちらの装置のCS811へも3V未満の供給としました。エラーを出したロガー側は思い切り低く2.7V付近に設定しました。可変の電圧レギュレータとしては、LM317T、LM1084Tなど何でもOKです。
余談ではありますが、回路図のように1つのポテンシオメーターを使い、可変部を
Adjにセットすると分圧に温度の影響がないですね。
念のためにロガーのI2C信号授受に問題が全く出てないことを確認しました。
さて、思わぬエラーの説明で手間取りましたが、このCCS811AirQualityセンサーはずいぶん鋭敏な賢いセンサーだと思います。奥が深い仕様で点検すべきマニュアルの種類がデータシートの最後にまとめられています。アプリケーションノートなどを含めて10種にもおよびます。
それらで気づいた注意点や、その後の使用状況や記録などについては記事が長くなり過ぎますので、機会を改めて書こうと思います。この段階での確認が終わったら単体のATmega328pでケースに組みたいと思っています。
次に表示装置のプログラム、ロガーのプログラムの順にArduinoスケッチを掲載しておきます。
/* *******************************************************
Environment display V02
(c)2020.06.20-2020.6.27 Akira Tominaga
Function:
Display temperature, humidity, pressure, CO2, and TVOC.
Revisions:
V00: Original program devloped on June 20, 2020.
V01: Show sensor error status to serial printer.
V02: Show sensor error status on display as "9XEE".
* ******************************************************/
#include "Wire.h"
#include "LiquidCrystal_I2C.h" // 20x4 I2C Liquid Crystal display
#include "ccs811.h" // CCS811 Air quality (CO2) sensor
#include "BME280I2C.h" // BME280 Weather sensor
// *** for LCD 20x4
#define LCDadr 0x27 // 20x4 I2C Liquid Crystal display
//LCD parameters are I2Cadr, en,rw,rs,d4,d5,d6,d7,bl,blpolarity
LiquidCrystal_I2C lcd(LCDadr, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
// *** for BME280
BME280I2C BME; // default standby time = 1000 ms
// *** for CCS811
#define AQadr 0x5A // AQ sensor adr (0x5B alternative)
CCS811 AQ;
uint16_t CO2; // CO2 ppm(400 to 8192), or AQ err_code
uint16_t TVOC; // TVOC ppb(0 to 1187)
char strCT[10]; // edit area for CO2 and TVOC
// *** for clock control buttons
#define Md 2 // Mode button (L=on)
#define Tm 3 // Time button (L=on)
#define St 4 // Set button (L=on)
// *** for Real Time Clock
byte vR[8]; // values in RTC registers
int vI[8] = { 0, 0, 0, 0, 0, 0, 0, 0}; // integers for RTC data
#define RTC_addr 0x68
#define mdI 0 // Initial mode
#define mds 1 // ss
#define mdm 2 // mm
#define mdh 3 // hh
#define mdW 4 // WW=Day of Week
#define mdD 5 // DD
#define mdM 6 // MM
#define mdY 7 // YY
#define sS 0
#define mM 1
#define hH 2
char strYMDHMS[20]; // edit area for calendar and clock
#define longPush 0x01
#define shortPush 0x00
byte pushMd = shortPush;
char Data[5]; // 4 bytes work area for RTC
void setup() { // ***** Arduino setup *****
Serial.begin(9600);
Wire.begin();
pinMode(Md, INPUT_PULLUP);
pinMode(Tm, INPUT_PULLUP);
pinMode(St, INPUT_PULLUP);
lcd.begin(20, 4); // init lcd 20 x 4
lcd.backlight(); // backlight on
// init LCD
lcd.setCursor(0, 0);
lcd.print("Environment sensors ");
if (!AQ.begin()) {
Serial.println("AQ E");
while (1) {}
}
if ( !AQ.start(CCS811_MODE_1SEC) ) {
Serial.println("Am E");
while (1) {}
}
delay(100); // enough time for BME280
if (BME.begin() == false) {
Serial.println("BME280 err");
while (1) {}
}
delay(2000);
lcd.clear();
}
void loop() { // ****** Arduino Loop *****
// calendar and time display
getTime();
lcd.setCursor(0, 0);
lcd.print(strYMDHMS);
// weather display
float tempC = BME.temp();
float humidRh = BME.hum();
float presPa = BME.pres();
#define tAdj -2.18
int tC = round(tempC + tAdj);
int hP = round(humidRh);
int phPa = round(presPa / 100.0);
lcd.setCursor(3, 1);
lcd.print(tC);
lcd.print("C ");
lcd.print(hP);
lcd.print("% ");
lcd.print(phPa);
lcd.print("hPa ");
// *** measure air quality and display it
uint16_t errSt, raw;
AQ.read(&CO2, &TVOC, &errSt, &raw);
if ( errSt == CCS811_ERRSTAT_OK ) {
Serial.print(CO2); Serial.print(","); // for serial plotter
Serial.println(TVOC * 10); // for serial plotter
} else if ( errSt == CCS811_ERRSTAT_OK_NODATA ) {
CO2 = 9000; TVOC = 0; // to show waiting-data
} else if ( errSt & CCS811_ERRSTAT_I2CFAIL ) {
CO2 = 9100; TVOC = 0; // to show I2C-error
} else {
CO2 = 9200 + errSt; TVOC = 0; // show error status
}
lcd.setCursor(3, 2);
lcd.print("CO2 = ");
lcd.print(CO2);
if (CO2<9000){
lcd.print(" PPM ");
lcd.setCursor(3, 3);
lcd.print("TVOC= ");
lcd.print(TVOC);
lcd.print(" PPB ");
}else{
lcd.print(" Error ");
}
// *** output for Serial-plotter
if (CO2 > 300) { // wait until stabilized
Serial.print((tempC+tAdj) * 10,0); // degree-C x 10
Serial.print(" ");
Serial.print(humidRh * 10,0); // relative humidity x 10
Serial.print(" ");
Serial.print(phPa); // pressure hPa
Serial.print(" ");
Serial.print(CO2); // CO2 ppm
Serial.print(" ");
Serial.println(TVOC*10); // TVOC ppb x 10
}
// delay 1sec, checking Set button every 50mS
for (int i = 0; i < 20; i++) {
if (digitalRead(St) == LOW) { // if St button, update clk
rtcSet();
}
delay(50);
}
}
/**************************************
User defined functions
* *********************************** */
// *** get calendar & time into strYMDHMS
void getTime(void) {
rRTC(); // read RTC device
cR2I(); // convert RTC-format to Integers
sprintf(strYMDHMS, "20%02d/%02d/%02d %02d:%02d:%02d", vI[mdY], vI[mdM], vI[mdD], vI[mdh], vI[mdm], vI[mds]);
}
// *** read Real-Time-Clock ***
void rRTC(void) {
Wire.beginTransmission(RTC_addr);
Wire.write(0x00);
Wire.endTransmission();
Wire.requestFrom(RTC_addr, 7);
while (Wire.available() < 7) {} // wait for data ready
for (int i = 1; i < 8; i++) {
vR[i] = Wire.read();
}
Wire.endTransmission();
}
// *** convert RTC-format to Integers ***
void cR2I(void) {
vI[mds] = ((vR[mds] & B01110000) / 16) * 10 + (vR[mds] & B00001111);
vI[mdm] = ((vR[mdm] & B01110000) / 16) * 10 + (vR[mdm] & B00001111);
vI[mdh] = ((vR[mdh] & B00100000) / 32) * 20 + ((vR[mdh] & B00010000) / 16) * 10 + (vR[mdh] & B00001111);
vI[mdW] = vR[mdW];
vI[mdD] = ((vR[mdD] & B01110000) / 16) * 10 + (vR[mdD] & B00001111);
vI[mdM] = ((vR[mdM] & B00010000) / 16) * 10 + (vR[mdM] & B00001111);
vI[mdY] = ((vR[mdY] & B11110000) / 16) * 10 + (vR[mdY] & B00001111);
}
// *** RTC update-mode when set button pushed ***
void rtcSet(void) {
while (digitalRead(St) == LOW) { // wait for button released
}
lcd.clear();
byte mC[8] = {0x00, 's', 'm', 'h', 'W', 'D', 'M', 'Y' }; // mode characters
char Su[3]; // figures used to set RTC value
int maxvI[8] = { 0, 59, 59, 23, 7, 31, 12, 99};
int minvI[8] = {0, 0, 0, 0, 0, 1, 1, 0};
while (digitalRead(Md) == LOW) {}; // wait until Md released
int mD = mdY ; // initial mode=mdY
rRTC(); // read RTC device
cR2I(); // convert RTC-format to Integers
// loop waiting for Set button
while (digitalRead(St) == HIGH) {
Data[0] = mC[mD];
Data[1] = '=';
sprintf(Su, "%02d", vI[mD]);
Data[2] = Su[0];
Data[3] = Su[1];
// set.curseor and display Data to LCD
lcd.setCursor(8, 1);
lcd.print(Data);
// Mode button loop within Set button loop
while (digitalRead(Md) == HIGH) { // do until Md pushed
if (digitalRead(Tm) == LOW) { // if Time button pushed
vI[mD] = vI[mD] + 1; // increase value
if (vI[mD] == maxvI[mD] + 1) { // if exceeded max
vI[mD] = minvI[mD]; // then set minimum & go
}
sprintf(Su, "%02d", vI[mD]); // get vI[mode] to Su
Data[2] = Su[0];
Data[3] = Su[1];
lcd.setCursor(8, 1);
lcd.print(Data);
delay(250);
// if Set switch on, go out of Time loop
if (digitalRead(St) == LOW) break;
} // repeat TM loop
// if Set witch on, go out of Mode loop
if (digitalRead(St) == LOW) break;
// else repeat Mode button loop
}
// out of Mode loop within Set loop
if (digitalRead(Md) == LOW) delay(300);
if (digitalRead(Md) == LOW) delay(500);
if (digitalRead(Md) == LOW) { // if still on
pushMd = longPush; // it it long-push
break; // go out of set loop
}
if (digitalRead(St) == LOW) break; // if Set pushed, go out
mD = mD - 1;
if (mD == mdI) {
mD = mdY;
}
} // out of Set loop
if (pushMd == longPush) { // if Md long-push
pushMd = shortPush; // reset indicator
lcd.setCursor(8, 1);
lcd.print(" ");
return; // and exit from update-mode
}
while (digitalRead(St) == LOW) {} // else, update at St-end
// write RTC
vR[mds] = (vI[mds] / 10) * 16 + vI[mds] % 10;
vR[mdm] = (vI[mdm] / 10) * 16 + vI[mdm] % 10;
vR[mdh] = (vI[mdh] / 20) * 32 + ((vI[mdh] % 20) / 10) * 16 + vI[mdh] % 10;
vR[mdW] = vI[mdW];
vR[mdD] = (vI[mdD] / 10) * 16 + vI[mdD] % 10;
vR[mdM] = (vI[mdM] / 10) * 16 + vI[mdM] % 10;
vR[mdY] = (vI[mdY] / 10) * 16 + vI[mdY] % 10;
Wire.beginTransmission(RTC_addr);
Wire.write(0x00);
for (int i = 1; i < 9; i++) {
Wire.write(vR[i]);
}
Wire.endTransmission();
// show that update has been done
lcd.setCursor(8, 1);
lcd.print("Done");
delay(1000);
lcd.clear();
}
/* End of program */
次がロガーのスケッチです。
/* *******************************************************
CO2 Logger V02 (c)2020.06.20-6.25 Akira Tominaga
Function:
Measure CO2 and TVOC using CCS811, with RTC & TM1637.
Revisions:
V00: Original devloped on June 20, 2020
V01: Print error status to serial printer
V02: Show error status as 9xee on display
* ******************************************************/
#include "Wire.h" // I2C for CCS811 and RTC-DS3231
#include "ccs811.h" // CCS811 Air quality (CO2) sensor
#include "SPI.h" // SPI for SD drive interface
#include "SD.h" // Micro SD drive
#include "TM1637Display.h" // TM1637 4dig LED Display
// *** for Air quality sensor CCS811
#define AQadr 0x5A // AQ sensor adr (0x5B alternative)
CCS811 AQ; // AQ as air quality sensor symbol
uint16_t CO2; // CO2 ppm(400-8192), or AQ err_status
uint16_t TVOC; // TVOC ppb(0-1187)
char strCT[10]; // editing area for CO2 and TVOC
// *** for micro SD writer
File aqLog; // aqLog as SD file symbol
#define Cs 10 // SD Chip-select pin is D10
// MOSI=11, MISO=12, CLK=13
#define SDsw 2 // SD-file closing button (L=on)
// *** for TM1637 display
#define TMdio 6 // DIO for TM1637
#define TMclk 7 // CLK for TM1637
TM1637Display disp(TMclk, TMdio); // disp as TM1637 symbol
uint8_t Data[] = { 0, 0, 0, 0 }; // data area for TM1637
// *** for Real Time Clock DS3231
byte vR[8]; // values in RTC registers
int vI[8] = { 0, 0, 0, 0, 0, 0, 0, 0}; // integers for RTC data
#define RTC_addr 0x68
#define mdI 0 // Initial mode
#define mds 1 // ss
#define mdm 2 // mm
#define mdh 3 // hh
#define mdW 4 // WW=Day of Week
#define mdD 5 // DD
#define mdM 6 // MM
#define mdY 7 // YY
#define sS 0
#define mM 1
#define hH 2
char strMDhm[11]; // edit area for calendar and clock
uint8_t mmSv; // minute-llvalue save area
void setup() { // ***** Arduino setup *****
Serial.begin(9600);
Wire.begin();
pinMode(SDsw, INPUT_PULLUP);
disp.setBrightness(0x0A);
if (!SD.begin(Cs)) {
Serial.println("SD E");
while (1) {}
}
if (!AQ.begin()) {
Serial.println("AQ E");
while (1) {}
}
if ( !AQ.start(CCS811_MODE_1SEC) ) {
Serial.println("Am E");
while (1) {}
}
// set SD file name MMDDhhmm
getTime();
String s = strMDhm;
s.concat(".txt"); // complete file name
aqLog = SD.open(s, FILE_WRITE);
aqLog.println("MMDDhhmm,CO2m,VOCb"); // write column hdr
mmSv = vI[mdm]; // save current minute value
edDisp(); // edit Data and display (0000)
delay(2000); // enough time for AQ
}
void loop() { // ****** Arduino Loop *****
getTime();
// *** measure air quality and display it
uint16_t errSt, raw;
AQ.read(&CO2, &TVOC, &errSt, &raw);
if ( errSt == CCS811_ERRSTAT_OK ) {
Serial.print(CO2); Serial.print(","); // for serial plotter
Serial.println(TVOC * 10); // for serial plotter
} else if ( errSt == CCS811_ERRSTAT_OK_NODATA ) {
CO2 = 9000; TVOC = 0; // to show waiting-data
} else if ( errSt & CCS811_ERRSTAT_I2CFAIL ) {
CO2 = 9100; TVOC = 0; // to show I2C-error
} else {
CO2 = 9200 + errSt; TVOC = 0; // show error status
}
edDisp(); // edit Data and display
// *** Write SD data every minute
if (vI[mdm] != mmSv) { // if minute changed
mmSv = vI[mdm]; // save new minute
String s = strMDhm; // set date-time
s.concat(",") ; // set comma
s.concat(strCT); // set data
aqLog.println(s); // write a record
//Serial.println(s); // this is for debug only
}
// 1000mS delay, with checking SD-close-button every 100mS
for (int i = 0; i < 10; i++) {
if (digitalRead(SDsw) == LOW) { // if SD sw to close
aqLog.close();
disp.clear();
while (1) {}
}
delay(100);
}
}
/**************************************
User defined functions
* *********************************** */
// *** Edit and display values
void edDisp(void) {
// edit CO2 and TVOC value into strCT
sprintf(strCT, "%04d,%04d", CO2, TVOC);
// display CO2 value via Data
for (int i = 0; i < 4; i++) {
Data[i] = disp.encodeDigit(strCT[i]);
}
disp.setSegments(Data);
}
// *** get MMDD & hhmm into strMDhm ***
void getTime(void) {
rRTC(); // read RTC device
cR2I(); // convert RTC-format to Integers
sprintf(strMDhm, "%02d%02d%02d%02d", vI[mdM], vI[mdD], vI[mdh], vI[mdm]);
}
// *** read Real-Time-Clock ***
void rRTC(void) {
Wire.beginTransmission(RTC_addr);
Wire.write(0x00);
Wire.endTransmission();
Wire.requestFrom(RTC_addr, 7);
while (Wire.available() < 7) {} // wait for data ready
for (int i = 1; i < 8; i++) {
vR[i] = Wire.read();
}
Wire.endTransmission();
}
// *** convert RTC-format to Integers ***
void cR2I(void) {
vI[mds] = ((vR[mds] & B01110000) / 16) * 10 + (vR[mds] & B00001111);
vI[mdm] = ((vR[mdm] & B01110000) / 16) * 10 + (vR[mdm] & B00001111);
vI[mdh] = ((vR[mdh] & B00100000) / 32) * 20 + ((vR[mdh] & B00010000) / 16) * 10 + (vR[mdh] & B00001111);
vI[mdW] = vR[mdW];
vI[mdD] = ((vR[mdD] & B01110000) / 16) * 10 + (vR[mdD] & B00001111);
vI[mdM] = ((vR[mdM] & B00010000) / 16) * 10 + (vR[mdM] & B00001111);
vI[mdY] = ((vR[mdY] & B11110000) / 16) * 10 + (vR[mdY] & B00001111);
}
/* End of program */
ロガーもシリアルプロッター信号を出すようにしてあります。息を吹きかけた様子はこれ(これは立ち上げ後10分ほどのRun-in時間中ではありますが)。横軸は秒数です。
では今回はこのへんで。
©2020 Akira Tominaga, All rights reserved.
Arduinoマウスでスピログラフを描く
スピログラフは半世紀も前に流行ったデザイン用のおもちゃで、色んな模様が描けます。その定規は百均でも売られていますが、もはやこれが何であるか知っている人の方が少ないかもしれません。
小さな円を大きな円に内接させながら穴に鉛筆やボールペンなどを差し込んでぐるぐる回せば模様が描けます。器用な人だと、この模様をいくつか組み合わせて次のようなデザインをします。
上の写真は次に載っています。
この最初の品は1965年に発売され、以後10年以上も世界で大ヒットしていたらしいです。
Denys Fisher original Spirograph 1965 | ralph stephenson | Flickr
実際使ってみればわかりますが、不器用な人はどこかで失敗して線が違う方向へとずれたりします、私もですが^^; やり直すのもたいへん。
そこで、PCで描けばずっと簡単にできそうです。そのために、まず式を考えます。汚いメモ書きですみませんが。
円Bの中心PがΘ回れば、円B自体はがa/b倍だけ逆に回るので、座標軸からみた傾き(Pから穴Qへの向き)としては Θー(a/b)Θ 回ることになります。なので上の式になるわけです。
これさえわかると後は簡単。プログラムで確認すると次のようになります。
画面をカメラで撮ったので少し傾いていてすみません<(_ _)>。
驚くような模様が次々に現れるから不思議です。このプログラム自体は記事の最後の方に載せておきますね。今回はMicrosoft Small Basicでちょこっと作ったシンプルなプログラムです。
さて、これでは当たり前で全然面白くないし、第一に電子工作とは言えませんね。電子工作として子供たちにも見せるには、マウスで描いて見せればよいかなと、忙しいときに余計なことをつい考えついてしまいました。
これならArduinoをマウスとして動かせばすぐにもできそうです。早速作るとしましょう。
前にキーボードを作るために余分に買ったArduino Pro Microが余っていますので、これを使ってみます。このモデルはコンパチ機が流通の主体で、海外だと1つ4ドル前後です。最新のIDEではLeonardoとしてプログラミングします。
考えてみたらこれには特別な回路など殆ど要りませんね。ここでは、単にディジタルピン8にタクトスイッチをつなぐだけにしました。
配線はグランドから右へ出した1本だけです。これにタクトスイッチの片側を接続。ハードウェアはたったのこれだけです。
Arduino IDEでプログラミングする際、ボードはLeonardoを指定。プログラム・スケッチは次のとおりで50行ほどの小さなものです。
/* Spirograph drawing machine. Arduino Pro-Micro as a Mouse
(c)Akira Tominaga June 15th, 2020 */
#include "Mouse.h"
#define Button 8 // Button pin
#define bChatT 100 // chattering time
float rA = 300.0;
float rB = 211.0;
float rC = rB * 0.90;
float KakuHi = (rB - rA) / rB;
float Pi = 3.141593;
float gyakuPi = (1.0 / Pi) / 100.0;
int xX;
int yY;
#define pX 0
#define pY 0
#define rBdecr 0.9
#define rCrate 0.8
void setup() {
pinMode(Button, INPUT_PULLUP);
Serial.begin(9600);
Mouse.begin();
while (digitalRead(Button) == HIGH) {}
delay(bChatT);
while (digitalRead(Button) == LOW) {}
}
void loop() {
int xS = pX;
int yS = pY;
int i = 0;
for (float Theta = 0 ; Theta < 300 * Pi ; Theta = Theta + gyakuPi) {
if (i > 0) {
Mouse.press();
}
xX = (float)( (rA - rB) * cos(Theta) + rC * cos(KakuHi * Theta));
yY = (float)( (rA - rB) * sin(Theta) + rC * sin(KakuHi * Theta));
int Xmove = xX - xS;
int Ymove = yY - yS;
Mouse.move(Xmove, Ymove, 0);
if (digitalRead(Button) == LOW) break;
xS = xX;
yS = yY;
i = 1;
}
while (digitalRead(Button) == LOW) {}
delay(bChatT);
Mouse.release();
while (digitalRead(Button) == HIGH) {}
while (digitalRead(Button) == LOW) {}
delay(bChatT);
rB = rB * rBdecr;
rC = rB * rCrate;
KakuHi = (rB - rA) / rB;
delay(300);
}
操作は次のようにしますが単純です。
お絵描きソフトとして、Windows Paint(マウスで描けるソフトならほかに何でもよいです)を立ち上げ、
①書き始めの地点(出来上がる図形の上下中央の右端になります)にカーソルをもっていく。
②タクトスイッチを押す。するとスピログラフを描き出します。
止めたいところで再び
③タクトスイッチを押せばとまります。
止まっている間にブラシや鉛筆、色などを選んでからカーソルの場所を移動して①②③を繰り返せば色や太さが選べます。
これなら子供達の前で見せたり、どや顔もできそう。色々工夫すれば好きなことができますが、何も役立ちませんね^^
最後にWindowsの一番簡単な言語と思われるSmall Basicで書いたプログラムを入れておきます。outputは最初のほうに出した動画です。もちろん言語は何でもよいです。
myTitle="Automatic Spirograph V00 (c)2020 A.Tominaga "
' GraphicsWindow definition
Gt=10 ' top edge position
Gl=50 ' left edge position
Gw=900 ' window width
Gh=900 ' window height
Ds=1.25 'display scaling rate is 125% in this case
GraphicsWindow.top=Gt
GraphicsWindow.Left=Gl
GraphicsWindow.Width=Gw
GraphicsWindow.Height=Gh
GraphicsWindow.Title=myTitle
' TextWindow definition
Tt=(Gt+Gh)*Ds-377 'adjusted text window's top edge position
Tl=(Gl+Gw)*Ds+2 ' set clearance with graphic window
TextWindow.top=Tt
TextWindow.left=Tl
TextWindow.Title=myTitle
' Radii of Circles
rA=400 'A circle radius
rBdefault=171
Inq:
TextWindow.Write("*** rA=400. Input rB initial size -> ")
rBinit=TextWindow.Read() 'B circle radius specified
If rBinit="" Then
rBinit=rBdefault
EndIf
If rB>rA-1 Then
goto Inq
EndIf
rCdefault=0.7 ' default C radius ratio to rB
' Main processes
For rB= rBinit To 350
rC=Math.Round(rB*rCdefault)
' define pen colors
cR=Math.GetRandomNumber(255)
getcG:
cG=Math.GetRandomNumber(255)
If cR+cG>320 Then
Goto getcG
EndIf
cB=Math.Round(255-(cR+cG)/1.2)
GraphicsWindow.PenColor=GraphicsWindow.GetColorFromRGB(cR,cG,cB)
GraphicsWindow.PenWidth=1
'draw Spirograph
GraphicsWindow.Clear()
XS=Gw/2 ' initial X position
YS=Gh/2 ' initial Y position
' Loop to draw graph
for theta= 0 To 500 Step 0.2
XX=(rA-rB)*Math.Cos(theta)+rC*Math.Cos(theta*(rB-rA)/rB)+Gw/2
YY=(rA-rB)*Math.Sin(theta)+rC*Math.Sin(theta*(rB-rA)/rB)+Gh/2
If theta>0 then ' exclude initial position
GraphicsWindow.DrawLine(XS,YS,XX,YY)
EndIf
XS=XX
YS=YY
EndFor
' Draw legend to show radius values of A, B, and C
fontSize=20
GraphicsWindow.FontSize=fontSize
fontW=fontSize/2 ' font width average to be
Lx=150 ' legend characters' X-position
Ly=65 ' legend characters' Y-position
Legend="Radii A=400 B="
LegLen=Text.GetLength(Legend)
GraphicsWindow.DrawText(Lx,Ly,Legend)
GraphicsWindow.DrawText(Lx+LegLen*fontW+2,Ly,rB)
LegLen2p=Lx+(LegLen+4)*fontW 'Len2 as pixel number
GraphicsWindow.DrawText(LegLen2p,Ly," C=")
GraphicsWindow.DrawText(LegLen2p+4*fontW+2,Ly,rC)
Name="Programmmed by A. Tominaga"
NameLen=Text.GetLength(Name)
fontSize=fontSize*0.6
GraphicsWindow.FontSize=fontSize
fontW=fontSize/2
Lx=Gw-(NameLen*fontW+Lx)
GraphicsWindow.DrawText(Lx,Ly+fontW,Name)
Program.Delay(1000)
EndFor
Sound.PlayBellRing()
Program.Delay(2000)
Program.End()
今回は単純で「どこが工作?」という感じではありますが、このへんで失礼したいと思います。
後で気づいたので補足します:三角関数は中学校で習うものと思っていたら、今は高校のようです。このIT時代に不思議なことですね。
©2020 Akira Tominaga, All rights reserved.
Zoom会議用かんたん操作ボタンをArduinoで作る(まとめ)
Zoom会議の操作を楽ちんにするボタンを、今回はBeetleで作りました。前回の記事(Zoom会議用・・・その3)の最後に少し書いておきましたが、BeetleとはArduino Leonardo互換(というよりサブセット)の小さな基板です。今回作ったのは写真の右側のもので、これで3個目(しつこいですね^^;)。
前の記事に付け足そうかとも思いましたが、この「勝手な電子工作」の記事がちょうど50作目になるようなので、別建てにして書くことにしました。
BeetleはPCのUSB端子に直接差し込める小さな基板で、下の写真のようなATmega32U4のブレークアウトボードです。Arduino-IDEではLeonardoとしてプログラミングをすることができます。
今eBayをちらりとみると、数年前より少し値上がりしたようですが、1個500円程度です。日本のアマゾンでみると6倍ほどの価格のものが見られます、たまたまかもしれませんが。
最近は新型コロナの影響による輸送の混乱のためか、若干の送料がかかる品が増えました。しかし複数買えば価格も安い(たとえば2PCSとつけて検索すれば出る)ので、後のために少し補給をしておきたくなり、つい注文してしまいました。
このUSB基板をどんなケースに収めるかですが、FRISKのケースは大きすぎて、USB端子に刺してつかうには幅・厚さともいまひとつです。オリジナルの簡単な方法は、配線してホットメルトをつけ、透明ビニールシートを巻きつけるというやり方です。勝手な知恵ですが、こうすると最終的にしっかりと丈夫にできます。あたりまえですが余計なケースは要りません。こういうのにぴったり。
巻くだけといっても、必要な時にマイコンボードがそのまま取り出せるように作ります。なので直接巻くのではなく、小さなストライプ基板をかませてその基盤側にだけホットメルトをつけます。
汚い手書きですみませんが次の案のように。こうするとケーブル接続も丈夫にできます。
Beetleのピンは、連続するD11、D10、D9、A0(D18)を使うことにします。そして右にあるグランドピンからひっぱってきて、隣のストライプをグラウンドにします。
まず、ストライプ基板をカットしておきます。
そして背中合わせの形にして、スズメッキ線で取り付けます。
こうしておけば、万一あとでBeetleを取り外す場合でも、5か所のはんだを吸い取るだけでOKなので。
そしてホットメルト(ホットボンドともいいますかね)をつけるのですが、ケーブル固定用に次の熱軟化樹脂を粘土として併用すると、私のような不器用者でもうまくできます。これは120℃ぐらいにセットしたヒートガンを使うと早く加工できます。
配線は、シールド被覆のあるUSBケーブルなら、中に4線+グラウンド線がしっかり収まっていますので適当ですしノイズを拾いません。はんだ付けしたら、ストライプ基板の銅箔面側だけにホットメルトを少し塗り、それを起点に透明ビニールシートを2回ほど巻きつけ、最後はホットメルトで同じ面へ止めます。そして冷めたら不要箇所をニッパーで切りとれば出来上がりです。ほら、こんな感じ。
横から見るとこんなかっこう。
貼り付け面側のみかけは少しきたないですが次。
ケーブル取り付け部も頑丈にできあがります。そしてケーブルの片側をスイッチに配線します。
作ったArduinoスケッチは次です。
/* **************************************************
"Zoom" short-cut buttons with Beetle
V00: Initial version May 10, 2020
Funcion: Provides four short-cut-buttons
Button 1 for Mute all members (on/off)
Button 2 for View gallery (on/off=speaker)
Button 3 for Tool-bar (on/off)
Button 4 for Full-screen (on/off)
Remarks: Apply Leonardo to build w/Arduino IDE
(c) Akira Tominaga, All rights reserved.
****************************************************
*/
#include "Keyboard.h" // for Leonardo(ATmega32U4) only
#define B1 11 // Button 1 for Mute-all
#define B2 10 // Button 2 for View-gallery
#define B3 9 // Button 3 for Toolbar
#define B4 18 //=A0 // Button 4 for Full-screen
// Chattering time depends on the Buttons used
#define Cht 30 // chattering time
#define lCht 200 // longer chtrg to ignore
#define typOn 10 // type-on time (mS)
byte Fk=KEY_F1; // save area for cur Func key
void setup() { // Setup **************************
Keyboard.begin();
// define Buttons and pull them up
pinMode(B1, INPUT_PULLUP);
pinMode(B2, INPUT_PULLUP);
pinMode(B3, INPUT_PULLUP);
pinMode(B4, INPUT_PULLUP);
}
void loop() { // Arduino loop ******************
if (digitalRead(B1) == LOW) { // B1: Mute all members
delay(Cht); // avoid chattering
while (digitalRead(B1) == LOW) {} // wait for btn-off
Keyboard.press(KEY_LEFT_ALT); // push Alter key
Keyboard.print("m"); // and type m
delay(typOn);
Keyboard.releaseAll();
delay(lCht); // avoid longer chattering
}
if (digitalRead(B2) == LOW) { // B2:View gallery or Spkr
delay(Cht); // avoid chattering
while (digitalRead(B2) == LOW) {} // wait button-off
if (Fk == KEY_F2) { // checck cur func key
Fk=KEY_F1; // set F1 if currently F2 used
} else {
Fk=KEY_F2; // set F2 if currently F1 used
}
Keyboard.press(KEY_LEFT_ALT); // push Alter key
Keyboard.press(Fk); // and push Function key 1 or 2
delay(typOn);
Keyboard.releaseAll(); // release all keys
delay(lCht); // avoid longer chattering
}
if (digitalRead(B3) == LOW) { // B3: Toolbar
delay(Cht); // avoid chattering
while (digitalRead(B3) == LOW) {} // wait button-off
Keyboard.press(KEY_LEFT_ALT); // push Alter key
delay(10);
Keyboard.releaseAll(); // release all keys
delay(lCht); // avoid longer chattering
}
if (digitalRead(B4) == LOW) { // B4: Full screen
delay(Cht); // avoid chattering
while (digitalRead(B4) == LOW) {} // wait button-off
Keyboard.press(KEY_LEFT_ALT); // push Alter key
Keyboard.print("f"); // and type f
delay(typOn);
Keyboard.releaseAll(); // release all keys
delay(lCht); // avoid longer chattering
}
}
// End of program *******************************
これをArduino-IDEで本体に直接書き込めば全てできあがります。何も問題なく動作をしています。しめしめ、持ち運びも簡単。
もしもち運び用としてまた作るときは、スイッチをもっと軽くしたい(しつこい?)。
今回はチャタリングが相当長くても大丈夫なコーディングにしたのですが、それには次のわけがあります。以下余計な話ではありますが、たまには失敗するのです^^;
スイッチを思い切り軽くしようと思い、実はまず次のキーパッドでトライしました。
このキーパッドは裏の剥離紙をはがすと、貼り付け面がこうなっています。
このパッドのリードフィルムは長すぎて邪魔です。120度ていど折りまげても接続には問題ないことを確認し、もう少しだけ折って蛇腹の形にたたみこんで、小ボックスにとりつけてみました。
なかなか小さくて薄くスマートです。箱は秋月で扱っている小さなものです。裏から見ると次。
これを試してみたところ、スイッチのノイズが大。そこでチャタリング許容時間を伸ばしたら一見問題なさそうでした。
ところがです。1つのキーが効かないのです。調べたら、なんとリードフィルム内で断線してます。いくら現代のキーパッドでも、こんな折りたたみ方をしたらやっぱりだめですねえ。実は折りたたむのも、ヒートガンを少し使ったりして結構大変でした。それではあたりまえでしょうね。
というわけで、長いチャタリングに耐えるプログラムテストの役割は果たしたのでした^^;
話を戻して、肝心のZoom会議はというと、会議中に複数のキーからなるショートカットが使える人はよほど器用な方だと思います。たった4キーであっても、よく使うオンオフ操作ボタンがあると、操作全体がとても楽です。とくにホストの場合はめっちゃ楽。
さて、このシリーズ記事*の最初に挑戦したUNOは、そのうちにFlipで元のUNOに戻すことにするかもしれません。後で作った2つの装置を暫らく使い続けるわけなので。
ということで、今回のシリーズのまとめはこのへんで終わりたいと思います。
最後までご覧頂き有難うございました。
*このシリーズ記事のバックナンバーは次です(インデックスが整ってなくてすみません)。
Zoom用かんたん操作ボタンをArduino-UNOで作る(その1) - 勝手な電子工作・・
Zoom用かんたん操作ボタンをArduino-UNOで作る(その2)やったね! - 勝手な電子工作・・
Zoom会議用かんたん操作ボタンをArduinoで作る(≒その3) - 勝手な電子工作・・
© 2020 Akira Tominaga, All rights reserved.
Zoom会議用かんたん操作ボタンをArduinoで作る(≒その3)
注文したArduino Pro Microが届きましたので、今度はそれで製作。この「かんたん操作ボタン」を使うと、家族のリモート団らんでの画面操作も楽ちんになります、あたりまえか^^;
到着したPro Microはコンパチ機。海外なら4ドル前後ですが日本では千円近くというところでしょうか。小サイズで組込みにも適していますね。これなら今後も少しは使う可能性が高いので今回6つを調達しました。
ごていねいに箱(コンパチ箱!?)もついてますよ。
下の方に書くプログラムで、まずブレッドボードによるテスト。ヘダーピンなど取りつけずにICクリップワイヤーで接続します。ヘダーピンをつけるとあとで組込む際には邪魔になるわけなので。
テスト接続のためのスルーホール用ワイヤーもありますが、基板の縁に近い穴の場合は、むしろICクリップのほうがしっかり接続できます。次のように。
接続先は前回のブレッドボードです。
Arduino Leonardo系ですから、Keyboard Libraryを使うと簡単です。ArduinoのモデルとしてはArduino Microを指定し、次のようにプログラムを作りました。(プログラムを更新しておきましたー2020年5月12日 もちろん前のままでも大丈夫です。)
/* ***************************************************
"Zoom" short-cut buttons with Arduino-Micro
V00: Initial version May 9, 2020
V01,02: Ignore long chattering May 10, 2020
Funcion: Provide four short-cut-buttons
Button 1 for Mute all memabers (on/off)
Button 2 for View gallery (on/off=speaker)
Button 3 for Tool-bar (on/off)
Button 4 for Full-screen (on/off)
(c) Akira Tominaga, All rights reserved.
*****************************************************
*/
#include "Keyboard.h" // for ATmega32U4 only
#define B1 7 // Button 1 for Mute-all
#define B2 8 // Button 2 for View-gallery
#define B3 14 // Button 3 for Toolbar
#define B4 15 // Button 4 for Full-screen
// Chattering time depends on the Buttons used
#define Cht 30 // chattering time
#define lCht 200 // longer chtrg to ignore *V02
#define typOn 10 // type-on time (mS)
byte Fk = KEY_F1; // save area for cur Func key
void setup() { // Setup **************************
Keyboard.begin();
// define Buttons and pull them up
pinMode(B1, INPUT_PULLUP);
pinMode(B2, INPUT_PULLUP);
pinMode(B3, INPUT_PULLUP);
pinMode(B4, INPUT_PULLUP);
}
void loop() { // Arduino loop ******************
if (digitalRead(B1) == LOW) { // B1: Mute all members
delay(Cht); // ignore chattering
while (digitalRead(B1) == LOW) {} // wait for btn-off
Keyboard.press(KEY_LEFT_ALT); // push Alter key
Keyboard.print("m"); // and type m
delay(typOn);
Keyboard.releaseAll();
delay(lCht); // avoid longer chattering *V02
}
if (digitalRead(B2) == LOW) { // B2:View gallery or Spkr
delay(Cht); // avoid chattering
while (digitalRead(B2) == LOW) {} // wait button-off
if (Fk == KEY_F2) { // checck cur func key
Fk = KEY_F1; // set F1 if currently F2 used
} else {
Fk = KEY_F2; // set F2 if currently F1 used
}
Keyboard.press(KEY_LEFT_ALT); // push Alter key
Keyboard.press(Fk); // and push Function key 1 or 2
delay(typOn);
Keyboard.releaseAll(); // release all keys
delay(lCht); // avoid longer chattering *V02
}
if (digitalRead(B3) == LOW) { // B3: Toolbar
delay(Cht); // avoid chattering
while (digitalRead(B3) == LOW) {} // wait button-off
Keyboard.press(KEY_LEFT_ALT); // push Alter key
delay(typOn);
Keyboard.releaseAll(); // release all keys
delay(lCht); // avoid longer chattering *V02
}
if (digitalRead(B4) == LOW) { // B4: Full screen
delay(Cht); // avoid chattering
while (digitalRead(B4) == LOW) {} // wait button-off
Keyboard.press(KEY_LEFT_ALT); // push Alter key
Keyboard.print("f"); // and type f
delay(typOn);
Keyboard.releaseAll(); // release all keys
delay(lCht); // avoid longer chattering *V02
}
}
// End of program *******************************
どんなスイッチを使っても問題ないように、処理に続いて思い切り長い100mSのDelayを追加してあります。操作性には全く問題ありませんし、これでスイッチの質をいっさい気にしなくて大丈夫でしょう。
そしてスイッチ用の箱を加工し、前から目をつけていたスイッチを使い、そのケース中にマイコンも収容しました。よって使用時はこれとPCをUSBケーブルでつなぐだけ。
キートップは紙に印刷して収容しました。
意味はこれまでどおりです。
以上です・・・。これでは工作好きの方には物足りないでしょう。「どこが工作なの?」というぐらい簡単。よって、このプラスチックケースの加工のことを少し書いておくことにします。
そもそもこの特定のキーはとりつけるネジ間隔が80mmもあるし、昔のキーなので高さがやけに高く、なかなか適当なケースはありません。厚さ(深さ)30mm以上ないとどうにもなりません。3Dプリンターで作ればよいのでしょうがそこまではちょっと・・。それなら取付を汎用ケースの外側にするか、と考えつきました。そして手持ちを探したら適当なケースがありました。
50x90x26mmで昔の「タカチGC50」を少し厚くしたような感じ。(ちなみに今のタカチGC50は30x90で深さは時代を反映してずっと浅いです、念のため。)これは台湾製ですが精度も高くよくできています。台北に出張の折にたまに時間があると勝手に光華商場を見物して楽しんでいました。そこで購入したものと思います。表示された台湾ドルはその頃の日本円換算で100円ほどか。
シュリンクラップに包まれラベルに連絡先まで書いてあります!好感がもてますね。
どう加工するかと言えば、お決まりの穴をあけてつなぐやり方。とはいえ今回は部品で見えなくなる穴なので、精度や直線などを気にせずに気楽にできますね。表面の他の部分に傷さえつけなければ良いわけなので、表面はマスキングテープで保護しておいて作業します。
穴をつないだところ。荒っぽいですが仕上げ不要、これで十分。
そしてスイッチを取付。
この中にPro Microをどう取り付けるかが悩ましいところ。ピン数か所をスズメッキ線で半田づけした基板をケースの底にねじ付けすることにしました。
まずは蛇の目基板を加工。
Pro Microの複数のピンをスズメッキ線で基板へ配線(固定を兼ねます)してから、その基盤を2mmφネジ4つでケースの底に取り付けることにしました。取付後は下の写真です。
その際、マイクロUSB-B型端子がきれいに外へ出るよう、その穴をまず先に加工してから、基板の最終的な取り付け位置を決めてネジ穴をあけて取り付けます。そしてスイッチ側から、基盤表面に出しておいたスズメッキ線の端へと配線します。
こうしておけばしっかり取り付けでき、もし後で不要になったらPro Microは簡単に取り出せます。
ところでですが、ATmega32U4の弱プルアップされたインピーダンスは20kΩ程度はあるので、環境の余計な電界ノイズを拾うことがないよう、はい線は一応束ねておきます。
表面が黒なので見えにくいですが、USB端子は次のような感じで穴の真ん中にうまく出ています。
後はPCとこのUSBをUSBケーブルで接続し、Arduino-IDEを使って先ほどのスケッチを書きこめば、あっという間に出来上がりです。最後にケースの付属ネジ4本を締めます。たぶんもう開けることはないでしょう。
ついでに書いておくべきことを思い出しました。スケッチの作成で注意する点としては、Arduino-Leonardo系に共通のことなのですが、テスト段階でやたらとキーボードへの信号を出しっぱなしにしないことです。もしそうやるとシリアル接続がそれに占拠されてスケッチの書き込みができなくなりますから。スイッチなど何かのトリガーでしかキーボード信号を出さないようにするということですね。
もし仮にそれに気づかず失敗をしたら、ATmega32U4を復活するにはリセット(!Reset端子をグランドに接続するとリセットです)を何度かかけながら、運よく書き込めるタイミングでの書き込みを何度かトライするしかなくなると思います。
さて、これで「かんたん操作ボタン」記事は終わり、、かと言えばそうでもありません。
手持ちArduinoの在庫を確かめていたら、"Beetle"を見つけてしまいました。かつてKBハッカー用とか称されて盛んに使われていたもの。マイコンは同じATmega32U4です。
これの手持ちは残り2個だけですが、今回の用途にはこんなに気の利いた基板もないものです!組み込む必要すらなく、単にキーからこれへ配線をしてそのままPCのUSBに突っ込むだけですから!考えることは、たぶんこのBeetleのケースをどうするかだけです^^。
そのうちにやってみたいと思います。うまく出来たらここに書き足すか、別記事として書こうかと・・。
©2020 Akira Tominaga, All rights reserved.
Zoom用かんたん操作ボタンをArduino-UNOで作る(その2)やったね!
とりあえず簡単な4ボタンを使ってプログラムをめでたく完成しました。ごく短いスケッチでできます。この装置をZoom会議本番で使ってみたら、操作ががぜん楽になりました。プログラムを含めて詳しくご紹介したいと思います。
このサブジェクトの最初の記事(その1)の翌日に、プログラム(Arduinoスケッチ)を作り、テストしたら一発で動き快適!調整したのはチャタリング時間の定数設定だけ。最初のテストに作ったボタンは、次のようなブレッドボード上のタクトスイッチです。
このボタン装置ならちょっとした時間でできますね。
次は、なぜか作業時のマスクをしたままですが^^; 最初のテストをやってます。このテストへの参加者は白クマだけですが^^
検討の結果、ショートカットキーの1番目をMute All(ホスト以外)に変更しました。多数が参加するZoom会議本番では、メンバーの中の誰かのスピーカー音がマイクに戻る場合にエコーやハウリングを起こすことがあります。そういう場合、ホスト役としては各参加者のマイクをミュートにして確認しながら、邪魔なノイズを除去します。その際は1つのキーでホスト以外を全てミュートにできると楽ですね。1番目のボタンをそれに変更することにしました。
回路は単純で、各スイッチを押せばグランドに接続するようにつくります。他に部品は要りません。接続は次の本番プログラムを見ていただいたく方が早いかと思います(勝手ながら2020年5月13日プログラムを更新しました:ボタン名Bxにウォーニングが出る場合があるためBtnxにし、また、チャタリングの特に長いスイッチにも対応しました)。
//* **************************************************
"Zoom" short-cut buttons with Arduino-UNO
V00: Initial version for test May 1, 2020
V01: Updated Button assignment May 2, 2020
V02 For very long chatterings May 10,2020
Funcion: Provide four short-cut-buttons
Button 1 for Mute all memabers (on/off) *V01
Button 2 for View gallery (on/off=speaker)
Button 3 for Tool-bar (on/off)
Button 4 for Full-screen (on/off)
(c) Akira Tominaga, All rights reserved.
****************************************************
*/
#define B1 9 // Button 1 for Mute-all *V01
#define B2 10 // Button 2 for View-gallery
#define B3 11 // Button 3 for Toolbar
#define B4 12 // Button 4 for Full-screen
// Chattering time depends on the Buttons used
#define Cht 30 // chattering max time (mS)
#define lCht 200 // time to bypass longer chtrg
byte KbD[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; // HID report
// KbD[0]=Modifier for USB HID Keyboard-code
#define rGUI B10000000 // right Windows key
#define rALT B01000000 // right Alt key
#define rSFT B00100000 // right Shift key
#define rCTL B00010000 // right Ctl key
#define lGUI B00001000 // left Windows key
#define lALT B00000100 // left Alt key
#define lSFT B00000010 // left Shift key
#define lCTL B00000001 // left Ctl key
// KbD[1] should always be 0x00
// KbD[2] to KbD[7] include key-codes (max 6 keys)
// Alphabet code = ASCIIcode-0x5D (ex.a:0x61 -> 0x04)
#define Csub 0x5D // Character value to subtract
// Special key codes
#define ScF1 0x3A // USB KB code for F1 key (Speaker)
#define ScF2 0X3B // USB KB code for F2 key (Gallery)
byte Fk=ScF1; // save area for cur Func key
void setup() { // Setup **************************
Serial.begin(9600);
// define Buttons and pull them up
pinMode(B1, INPUT_PULLUP);
pinMode(B2, INPUT_PULLUP);
pinMode(B3, INPUT_PULLUP);
pinMode(B4, INPUT_PULLUP);
}
void loop() { // Arduino loop ******************
if (digitalRead(B1) == LOW) { // B1: Mute all members
delay(Cht); // avoid chattering
while (digitalRead(B1) == LOW) {} // wait button-off
// Serial.println("B1=Mute all"); // for test only
KbD[2] = 'm' - Csub; // set character m
KbD[0] = lALT; // set ALT key
KbWrite(); // send HID-Make and Brake
}
if (digitalRead(B2) == LOW) { // B2: View gallery
delay(Cht); // avoid chattering
while (digitalRead(B2) == LOW) {} // wait button-off
// Serial.println("B2=View"); // for test only
if (Fk == ScF2) { // select function key
Fk = ScF1; // set F1 if currently F2
} else {
Fk = ScF2; // set F2 if currently F1
}
KbD[2] = Fk; // set the Func-key
KbD[0] = lALT; // set left ALT key
KbWrite(); // send HID-Make and Brake
}
if (digitalRead(B3) == LOW) { // B3: Toolbar
delay(Cht); // avoid chattering
while (digitalRead(B3) == LOW) {} // wait button-off
// Serial.println("B3=Tool"); // for test only
KbD[0] = lALT; // set left ALT key
KbWrite(); // send HID-Make and Brake
}
if (digitalRead(B4) == LOW) { // B4: Full screen
delay(Cht); // avoid chattering
while (digitalRead(B4) == LOW) {} // wait button-off
// Serial.println("B4=Scrn"); // for test only
KbD[2] = 'f' - Csub; // Set keyboard f char
KbD[0] = lALT; // set left ALT key
KbWrite(); // send HID-Make and Brake
}
}
/********************************
* *** User defined functions ***
* *****************************/
// *** KbWrite() send HID-Make and HID-Brake
void KbWrite(void)
{
Serial.write(KbD, 8); // HID-Make
KbD[2] = 0x00; // clear character
KbD[0] = 0x00; // clear modifier
Serial.write(KbD, 8); // HID-Break
delay(lCht); // to ignore longer chtrg *V02
}
ボタンが押されたのを確認する処理は簡単そうですが、実は十分な注意が必要です。
単にLOWになったかどうかをチェックするだけでなく、数ミリ~数十ミリ秒のチャタリング(接点オン時のノイズ)回避用delay時間をとった後で、HIGHになる(ボタンを離す)まで待つ必要があります。ボタンに対する処理の量が少ない(つまりごく短時間に処理が終わる)場合には特に大事なことです。チャタリングの長いスイッチもあるので、念のために各処理の後に余分な時間を入れます。これは操作性には影響しません。
こうしないとチャタリングによって複数回押されたことになり、連続して複数回の同じ処理をしてしまうことにもなります。どれだけの時間を待つかはスイッチの品質にもよりますが、いかに高品質でも物理的なスイッチであればチャタリングは避けられません。回路上でローパスフィルタを入れれば多少は軽減できますが、現代ではそうはせずにソフトウェアで回避するのがいちばん妥当ですね。
人の手によるボタン操作の後処理なので、プログラム側で30mS待っても影響はありません。同じコーディングをされるようにお勧め。スイッチに合わせてこの時間を調節することもできますが、処理の後(この例ではユーザー定義関数の最後)に200mSをいれましたので、調整しなくともどれでも大丈夫ですし、操作性にも影響しません。
テスト段階で使うタクトスイッチは2ピンのものがお勧めです。ブレッドボードで使う場合、2本足のタクトスイッチなら取り付けも操作も安定していますから。普通のタクトスイッチは4ピンのものが殆どですが、基板用なので脚を延ばしても短すぎブレッドボードには不向きです。次の写真で奥のものが不適切な4ピン、手前の2つが適当な2ピンのタクトスイッチです。
2回の会議本番では、以上の4つのボタン装置があれば必要十分と感じました。ご自分の操作しやすさに合わせてお好みのものにするとか、ボタンを増やしたりされても使いやすいかもしれません。
Zoomが現在(2020年5月5日)提供しているショートカットキーは次のものです。
さて、とくに問題もみつからないので、ボタンのセットを作ることにします。
こんなに助かる装置なので、そのうちにArduino Microを調達してスイッチと併せて小さく作りたいと考えました。なので、前の記事(その1)で説明した本命の押釦はそれ用にとっておくことにしました。
そして翌日の会議に間に合うように急いで、一番早く製作できるボタンを使いました(その代り品質は個体差も多くてロクなものではありません、チャタリング回避時間を思い切り延ばしたわけです^^;)。これなら、例えばタカチのプラスチックケースにとりつけるならすぐに加工できますし。なお、消費税が5%の頃に入手した感じ^^
次のように完成しました。
左からMute all, View gallery, Tool, Full screen。4つなら頭文字だけ表示すればよいかな。
小さい箱なので配線にひと手間かかりますが、そこには生活の知恵を。スイッチに直接ケーブルを配線をするのではなく、ごく小さな基板をかませます。ここまではスズメッキ線などで配線します。こうすると速くて確実・丈夫にできます。中身は少しみっともないですが次。
使っているシールドケーブルは使わないUSBケーブルを流用したものです。中に4線がシールドされているのでノイズを拾わず、なにより柔らかい。そして百均のケーブルでも確実であり、こういう用途にはぴったりですね。内部には赤、緑、白、黒、そしてシールドがあります。シールド線にははんだ付け前に1mmφ熱収縮チューブ等をかぶせます。
Arduino UNOも別のケースに収めます。Arduinoとの配線は、ケースに収容して固定しない限り、操作中に抜けたり、長く使うとArduino側ヘダーピン(メス)が傷んでだんだん緩くなってしまいます。よって面倒でもケースに収めるわけです。
もしArduino Microでつくる場合なら、ボタンと併せて1つの筐体にしてしまいますが、今回はUNOなのでやむを得ないですね。こちらには秋月で売っている小さなポリカーボネートケースを使いました。割れないので工作がとても速くできます。余計なソケットなどは省くのが今回は得策ですね。
ところで、後で考えてみればこのUNOのケースにボタンをとりつけてもよかったのか・・?。手元に必要なスペースが増えるのは感心しませんので、これでいいか。
ヘダーピンのところにはL型オス1列ヘダーピンを使用。使うのはグランド、デジタルピン12、11、10、9です。ピン13はUNO基板のLEDを後で使うかもしれないので、スキップし、そのピンはL型ヘダーピンから抜いておきます。
1本ずつではなく連結したピンを使うと、全体が動きにくくなりArduino UNO側のメスピンが傷みにくくなります。
Arduino UNO専用のケースが、かつてAmazon等で百数十円で売られていましたので、その時に沢山入手してあります。そちらは体裁がよいのですが、日頃使う装置にこれを使うとピンの固定にひと工夫がいりますので、今回は敬遠。
以上について真似をされる方は事前に前の記事(その1)を読んでくださいね。Arsuino-UNOのUSB接続をKeyboardにしたりArduinoに戻したりする方法をそちらに書いてありますので。
また、キーボードの文字コードについての情報が必要かと思いますのでここに書きます。OADGで出されているHID(ヒューマンインターフェイスデバイス)のプロトコルに従うものですが、次のコードをご覧になればわかりやすいかと思います。
日本語キーボードでもZoomのショートカットキーは幸い同じようですが、その他のキーの違いを知る必要のある場合は、”日本語 USB-HIDキーボード コード”などでググっていただくとキー配列の設定がいくつか見つかりますのでそちらをご参照ください。
/**
MightyPork/usb_hid_keys.h
*
* USB HID Keyboard scan codes as per USB spec 1.11
* plus some additional codes
*
* Created by MightyPork, 2016
* Public domain
*
* Adapted from:
* https://source.android.com/devices/input/keyboard-devices.html
*/
#ifndef USB_HID_KEYS
#define USB_HID_KEYS
/**
* Modifier masks - used for the first byte in the HID report.
* NOTE: The second byte in the report is reserved, 0x00
*/
#define KEY_MOD_LCTRL 0x01
#define KEY_MOD_LSHIFT 0x02
#define KEY_MOD_LALT 0x04
#define KEY_MOD_LMETA 0x08
#define KEY_MOD_RCTRL 0x10
#define KEY_MOD_RSHIFT 0x20
#define KEY_MOD_RALT 0x40
#define KEY_MOD_RMETA 0x80
/**
* Scan codes - last N slots in the HID report (usually 6).
* 0x00 if no key pressed.
*
* If more than N keys are pressed, the HID reports
* KEY_ERR_OVF in all slots to indicate this condition.
*/
#define KEY_NONE 0x00 // No key pressed
#define KEY_ERR_OVF 0x01 // Keyboard Error Roll Over - used for all slots if too many keys are pressed ("Phantom key")
// 0x02 // Keyboard POST Fail
// 0x03 // Keyboard Error Undefined
#define KEY_A 0x04 // Keyboard a and A
#define KEY_B 0x05 // Keyboard b and B
#define KEY_C 0x06 // Keyboard c and C
#define KEY_D 0x07 // Keyboard d and D
#define KEY_E 0x08 // Keyboard e and E
#define KEY_F 0x09 // Keyboard f and F
#define KEY_G 0x0a // Keyboard g and G
#define KEY_H 0x0b // Keyboard h and H
#define KEY_I 0x0c // Keyboard i and I
#define KEY_J 0x0d // Keyboard j and J
#define KEY_K 0x0e // Keyboard k and K
#define KEY_L 0x0f // Keyboard l and L
#define KEY_M 0x10 // Keyboard m and M
#define KEY_N 0x11 // Keyboard n and N
#define KEY_O 0x12 // Keyboard o and O
#define KEY_P 0x13 // Keyboard p and P
#define KEY_Q 0x14 // Keyboard q and Q
#define KEY_R 0x15 // Keyboard r and R
#define KEY_S 0x16 // Keyboard s and S
#define KEY_T 0x17 // Keyboard t and T
#define KEY_U 0x18 // Keyboard u and U
#define KEY_V 0x19 // Keyboard v and V
#define KEY_W 0x1a // Keyboard w and W
#define KEY_X 0x1b // Keyboard x and X
#define KEY_Y 0x1c // Keyboard y and Y
#define KEY_Z 0x1d // Keyboard z and Z
#define KEY_1 0x1e // Keyboard 1 and !
#define KEY_2 0x1f // Keyboard 2 and @
#define KEY_3 0x20 // Keyboard 3 and #
#define KEY_4 0x21 // Keyboard 4 and $
#define KEY_5 0x22 // Keyboard 5 and %
#define KEY_6 0x23 // Keyboard 6 and ^
#define KEY_7 0x24 // Keyboard 7 and &
#define KEY_8 0x25 // Keyboard 8 and *
#define KEY_9 0x26 // Keyboard 9 and (
#define KEY_0 0x27 // Keyboard 0 and )
#define KEY_ENTER 0x28 // Keyboard Return (ENTER)
#define KEY_ESC 0x29 // Keyboard ESCAPE
#define KEY_BACKSPACE 0x2a // Keyboard DELETE (Backspace)
#define KEY_TAB 0x2b // Keyboard Tab
#define KEY_SPACE 0x2c // Keyboard Spacebar
#define KEY_MINUS 0x2d // Keyboard - and _
#define KEY_EQUAL 0x2e // Keyboard = and +
#define KEY_LEFTBRACE 0x2f // Keyboard [ and {
#define KEY_RIGHTBRACE 0x30 // Keyboard ] and }
#define KEY_BACKSLASH 0x31 // Keyboard \ and |
#define KEY_HASHTILDE 0x32 // Keyboard Non-US # and ~
#define KEY_SEMICOLON 0x33 // Keyboard ; and :
#define KEY_APOSTROPHE 0x34 // Keyboard ' and "
#define KEY_GRAVE 0x35 // Keyboard ` and ~
#define KEY_COMMA 0x36 // Keyboard , and <
#define KEY_DOT 0x37 // Keyboard . and >
#define KEY_SLASH 0x38 // Keyboard / and ?
#define KEY_CAPSLOCK 0x39 // Keyboard Caps Lock
#define KEY_F1 0x3a // Keyboard F1
#define KEY_F2 0x3b // Keyboard F2
#define KEY_F3 0x3c // Keyboard F3
#define KEY_F4 0x3d // Keyboard F4
#define KEY_F5 0x3e // Keyboard F5
#define KEY_F6 0x3f // Keyboard F6
#define KEY_F7 0x40 // Keyboard F7
#define KEY_F8 0x41 // Keyboard F8
#define KEY_F9 0x42 // Keyboard F9
#define KEY_F10 0x43 // Keyboard F10
#define KEY_F11 0x44 // Keyboard F11
#define KEY_F12 0x45 // Keyboard F12
#define KEY_SYSRQ 0x46 // Keyboard Print Screen
#define KEY_SCROLLLOCK 0x47 // Keyboard Scroll Lock
#define KEY_PAUSE 0x48 // Keyboard Pause
#define KEY_INSERT 0x49 // Keyboard Insert
#define KEY_HOME 0x4a // Keyboard Home
#define KEY_PAGEUP 0x4b // Keyboard Page Up
#define KEY_DELETE 0x4c // Keyboard Delete Forward
#define KEY_END 0x4d // Keyboard End
#define KEY_PAGEDOWN 0x4e // Keyboard Page Down
#define KEY_RIGHT 0x4f // Keyboard Right Arrow
#define KEY_LEFT 0x50 // Keyboard Left Arrow
#define KEY_DOWN 0x51 // Keyboard Down Arrow
#define KEY_UP 0x52 // Keyboard Up Arrow
#define KEY_NUMLOCK 0x53 // Keyboard Num Lock and Clear
#define KEY_KPSLASH 0x54 // Keypad /
#define KEY_KPASTERISK 0x55 // Keypad *
#define KEY_KPMINUS 0x56 // Keypad -
#define KEY_KPPLUS 0x57 // Keypad +
#define KEY_KPENTER 0x58 // Keypad ENTER
#define KEY_KP1 0x59 // Keypad 1 and End
#define KEY_KP2 0x5a // Keypad 2 and Down Arrow
#define KEY_KP3 0x5b // Keypad 3 and PageDn
#define KEY_KP4 0x5c // Keypad 4 and Left Arrow
#define KEY_KP5 0x5d // Keypad 5
#define KEY_KP6 0x5e // Keypad 6 and Right Arrow
#define KEY_KP7 0x5f // Keypad 7 and Home
#define KEY_KP8 0x60 // Keypad 8 and Up Arrow
#define KEY_KP9 0x61 // Keypad 9 and Page Up
#define KEY_KP0 0x62 // Keypad 0 and Insert
#define KEY_KPDOT 0x63 // Keypad . and Delete
#define KEY_102ND 0x64 // Keyboard Non-US \ and |
#define KEY_COMPOSE 0x65 // Keyboard Application
#define KEY_POWER 0x66 // Keyboard Power
#define KEY_KPEQUAL 0x67 // Keypad =
#define KEY_F13 0x68 // Keyboard F13
#define KEY_F14 0x69 // Keyboard F14
#define KEY_F15 0x6a // Keyboard F15
#define KEY_F16 0x6b // Keyboard F16
#define KEY_F17 0x6c // Keyboard F17
#define KEY_F18 0x6d // Keyboard F18
#define KEY_F19 0x6e // Keyboard F19
#define KEY_F20 0x6f // Keyboard F20
#define KEY_F21 0x70 // Keyboard F21
#define KEY_F22 0x71 // Keyboard F22
#define KEY_F23 0x72 // Keyboard F23
#define KEY_F24 0x73 // Keyboard F24
#define KEY_OPEN 0x74 // Keyboard Execute
#define KEY_HELP 0x75 // Keyboard Help
#define KEY_PROPS 0x76 // Keyboard Menu
#define KEY_FRONT 0x77 // Keyboard Select
#define KEY_STOP 0x78 // Keyboard Stop
#define KEY_AGAIN 0x79 // Keyboard Again
#define KEY_UNDO 0x7a // Keyboard Undo
#define KEY_CUT 0x7b // Keyboard Cut
#define KEY_COPY 0x7c // Keyboard Copy
#define KEY_PASTE 0x7d // Keyboard Paste
#define KEY_FIND 0x7e // Keyboard Find
#define KEY_MUTE 0x7f // Keyboard Mute
#define KEY_VOLUMEUP 0x80 // Keyboard Volume Up
#define KEY_VOLUMEDOWN 0x81 // Keyboard Volume Down
// 0x82 Keyboard Locking Caps Lock
// 0x83 Keyboard Locking Num Lock
// 0x84 Keyboard Locking Scroll Lock
#define KEY_KPCOMMA 0x85 // Keypad Comma
// 0x86 Keypad Equal Sign
#define KEY_RO 0x87 // Keyboard International1
#define KEY_KATAKANAHIRAGANA 0x88 // Keyboard International2
#define KEY_YEN 0x89 // Keyboard International3
#define KEY_HENKAN 0x8a // Keyboard International4
#define KEY_MUHENKAN 0x8b // Keyboard International5
#define KEY_KPJPCOMMA 0x8c // Keyboard International6
// 0x8d Keyboard International7
// 0x8e Keyboard International8
// 0x8f Keyboard International9
#define KEY_HANGEUL 0x90 // Keyboard LANG1
#define KEY_HANJA 0x91 // Keyboard LANG2
#define KEY_KATAKANA 0x92 // Keyboard LANG3
#define KEY_HIRAGANA 0x93 // Keyboard LANG4
#define KEY_ZENKAKUHANKAKU 0x94 // Keyboard LANG5
// 0x95 Keyboard LANG6
// 0x96 Keyboard LANG7
// 0x97 Keyboard LANG8
// 0x98 Keyboard LANG9
// 0x99 Keyboard Alternate Erase
// 0x9a Keyboard SysReq/Attention
// 0x9b Keyboard Cancel
// 0x9c Keyboard Clear
// 0x9d Keyboard Prior
// 0x9e Keyboard Return
// 0x9f Keyboard Separator
// 0xa0 Keyboard Out
// 0xa1 Keyboard Oper
// 0xa2 Keyboard Clear/Again
// 0xa3 Keyboard CrSel/Props
// 0xa4 Keyboard ExSel
// 0xb0 Keypad 00
// 0xb1 Keypad 000
// 0xb2 Thousands Separator
// 0xb3 Decimal Separator
// 0xb4 Currency Unit
// 0xb5 Currency Sub-unit
#define KEY_KPLEFTPAREN 0xb6 // Keypad (
#define KEY_KPRIGHTPAREN 0xb7 // Keypad )
// 0xb8 Keypad {
// 0xb9 Keypad }
// 0xba Keypad Tab
// 0xbb Keypad Backspace
// 0xbc Keypad A
// 0xbd Keypad B
// 0xbe Keypad C
// 0xbf Keypad D
// 0xc0 Keypad E
// 0xc1 Keypad F
// 0xc2 Keypad XOR
// 0xc3 Keypad ^
// 0xc4 Keypad %
// 0xc5 Keypad <
// 0xc6 Keypad >
// 0xc7 Keypad &
// 0xc8 Keypad &&
// 0xc9 Keypad |
// 0xca Keypad ||
// 0xcb Keypad :
// 0xcc Keypad #
// 0xcd Keypad Space
// 0xce Keypad @
// 0xcf Keypad !
// 0xd0 Keypad Memory Store
// 0xd1 Keypad Memory Recall
// 0xd2 Keypad Memory Clear
// 0xd3 Keypad Memory Add
// 0xd4 Keypad Memory Subtract
// 0xd5 Keypad Memory Multiply
// 0xd6 Keypad Memory Divide
// 0xd7 Keypad +/-
// 0xd8 Keypad Clear
// 0xd9 Keypad Clear Entry
// 0xda Keypad Binary
// 0xdb Keypad Octal
// 0xdc Keypad Decimal
// 0xdd Keypad Hexadecimal
#define KEY_LEFTCTRL 0xe0 // Keyboard Left Control
#define KEY_LEFTSHIFT 0xe1 // Keyboard Left Shift
#define KEY_LEFTALT 0xe2 // Keyboard Left Alt
#define KEY_LEFTMETA 0xe3 // Keyboard Left GUI
#define KEY_RIGHTCTRL 0xe4 // Keyboard Right Control
#define KEY_RIGHTSHIFT 0xe5 // Keyboard Right Shift
#define KEY_RIGHTALT 0xe6 // Keyboard Right Alt
#define KEY_RIGHTMETA 0xe7 // Keyboard Right GUI
#define KEY_MEDIA_PLAYPAUSE 0xe8
#define KEY_MEDIA_STOPCD 0xe9
#define KEY_MEDIA_PREVIOUSSONG 0xea
#define KEY_MEDIA_NEXTSONG 0xeb
#define KEY_MEDIA_EJECTCD 0xec
#define KEY_MEDIA_VOLUMEUP 0xed
#define KEY_MEDIA_VOLUMEDOWN 0xee
#define KEY_MEDIA_MUTE 0xef
#define KEY_MEDIA_WWW 0xf0
#define KEY_MEDIA_BACK 0xf1
#define KEY_MEDIA_FORWARD 0xf2
#define KEY_MEDIA_STOP 0xf3
#define KEY_MEDIA_FIND 0xf4
#define KEY_MEDIA_SCROLLUP 0xf5
#define KEY_MEDIA_SCROLLDOWN 0xf6
#define KEY_MEDIA_EDIT 0xf7
#define KEY_MEDIA_SLEEP 0xf8
#define KEY_MEDIA_COFFEE 0xf9
#define KEY_MEDIA_REFRESH 0xfa
#define KEY_MEDIA_CALC 0xfb
#endif // USB_HID_KEYS
また、FLIPツール(その1をご参照)を使ってUNOが正しくUSBキーボードになったのかを確認するためには、デバイスマネジャーを立ち上げておいて表示をみると良いと前回書きました。ついでですが、Windowsでさらに詳しく確認したい場合についてここでご紹介したいと思います。
マイクロソフトが提供している無料開発ツールのUSB ViewerあるいはUSB Tree Viewerのどちらかを導入して、それを使われると良いと思います。USB Tree Viewerの方が詳しい情報が得られます(が、USB Viewerの方が導入は単純です)。各名称でググっていただくとマイクロソフトの説明ページが出てきますので、必要な方は自己責任にてご導入ください。
次はUSB Tree viewer V3.3.8(現時点で最新)での表示例です。
上図は私の場合ですが、他のキーボードを常時2個(テンキーレスとテンキーをわけているためです)接続していますので、USB-HIDキーボードが3個になって少し紛らわしいかもしれませんね。
この例ではPort7を選び表示される詳細は次です。
=========================== USB Port7 ===========================
Connection Status : 0x01 (Device is connected)
Port Chain : 1-7
Properties : 0x01
IsUserConnectable : yes
PortIsDebugCapable : no
PortHasMultiCompanions : no
PortConnectorIsTypeC : no
ConnectionIndex : 7
======================== USB Device ========================
+++++++++++++++++ Device Information ++++++++++++++++++
Device Description : USB 入力デバイス
Device Path : \\?\usb#vid_03eb&pid_2042#5&16caae6d&0&7#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
Device ID : USB\VID_03EB&PID_2042\5&16CAAE6D&0&7
Hardware IDs : USB\VID_03EB&PID_2042&REV_0000 USB\VID_03EB&PID_2042
Driver KeyName : {745a17a0-74d3-11d0-b6fe-00a0c90f57da}\0032 (GUID_DEVCLASS_HIDCLASS)
Driver : \SystemRoot\System32\drivers\hidusb.sys (Version: 10.0.18362.175 Date: 2019-08-29)
Driver Inf : C:\WINDOWS\inf\input.inf
Legacy BusType : PNPBus
Class : HIDClass
Class GUID : {745a17a0-74d3-11d0-b6fe-00a0c90f57da} (GUID_DEVCLASS_HIDCLASS)
Interface GUID : {a5dcbf10-6530-11d2-901f-00c04fb951ed} (GUID_DEVINTERFACE_USB_DEVICE)
Service : HidUsb
Enumerator : USB
Location Info : Port_#0007.Hub_#0001
Location IDs : PCIROOT(0)#PCI(1400)#USBROOT(0)#USB(7), ACPI(_SB_)#ACPI(PCI0)#ACPI(XHC_)#ACPI(RHUB)#ACPI(HS07)
Container ID : {6a392c6f-8a75-11ea-9c58-b8aeedafcdef}
Manufacturer Info : (標準システム デバイス)
Capabilities : 0x84 (Removable, SurpriseRemovalOK)
Status : 0x0180400A (DN_DRIVER_LOADED, DN_STARTED, DN_REMOVABLE, DN_NT_ENUMERATOR, DN_NT_DRIVER)
Problem Code : 0
HcDisableSelectiveSuspend: 0
EnableSelectiveSuspend : 0
SelectiveSuspendEnabled : 0
EnhancedPowerMgmtEnabled : 1
IdleInWorkingState : 0
WakeFromSleepState : 0
Power State : D0 (supported: D0, D3, wake from D0)
Child Device 1 : HID キーボード デバイス
DevicePath : \\?\hid#vid_03eb&pid_2042#6&39102546&0&0000#{884b96c3-56ef-11d1-bc8c-00a0c91405dd}
KernelName : \Device\00000125
Device ID : HID\VID_03EB&PID_2042\6&39102546&0&0000
Class : Keyboard
---------------- Connection Information ---------------
Connection Index : 0x07 (7)
Connection Status : 0x01 (DeviceConnected)
Current Config Value : 0x01
Device Address : 0x08 (8)
Is Hub : 0x00 (no)
Device Bus Speed : 0x01 (Full-Speed)
Number Of Open Pipes : 0x01 (1 pipe to data endpoints)
Pipe[0] : EndpointID=1 Direction=IN ScheduleOffset=0 Type=Interrupt
Data (HexDump) : 07 00 00 00 12 01 10 01 00 00 00 08 EB 03 42 20 ..............B
00 00 01 02 00 01 01 01 00 08 00 01 00 00 00 01 ................
00 00 00 07 05 81 03 08 00 0A 00 00 00 00 ..............
--------------- Connection Information V2 -------------
Connection Index : 0x07 (7)
Length : 0x10 (16 bytes)
SupportedUsbProtocols : 0x03
Usb110 : 1 (yes)
Usb200 : 1 (yes)
Usb300 : 0 (no)
ReservedMBZ : 0x00
Flags : 0x00
DevIsOpAtSsOrHigher : 0 (Is not operating at SuperSpeed or higher)
DevIsSsCapOrHigher : 0 (Is not SuperSpeed capable or higher)
DevIsOpAtSsPlusOrHigher : 0 (Is not operating at SuperSpeedPlus or higher)
DevIsSsPlusCapOrHigher : 0 (Is not SuperSpeedPlus capable or higher)
ReservedMBZ : 0x00
Data (HexDump) : 07 00 00 00 10 00 00 00 03 00 00 00 00 00 00 00 ................
---------------------- Device Descriptor ----------------------
bLength : 0x12 (18 bytes)
bDescriptorType : 0x01 (Device Descriptor)
bcdUSB : 0x110 (USB Version 1.10)
bDeviceClass : 0x00 (defined by the interface descriptors)
bDeviceSubClass : 0x00
bDeviceProtocol : 0x00
bMaxPacketSize0 : 0x08 (8 bytes)
idVendor : 0x03EB (Atmel Corporation)
idProduct : 0x2042
bcdDevice : 0x0000
iManufacturer : 0x01 (String Descriptor 1)
Language 0x0409 : "Arduino"
iProduct : 0x02 (String Descriptor 2)
Language 0x0409 : "Keyboard"
iSerialNumber : 0x00 (No String Descriptor)
bNumConfigurations : 0x01 (1 Configuration)
Data (HexDump) : 12 01 10 01 00 00 00 08 EB 03 42 20 00 00 01 02 ..........B ....
00 01 ..
------------------ Configuration Descriptor -------------------
bLength : 0x09 (9 bytes)
bDescriptorType : 0x02 (Configuration Descriptor)
wTotalLength : 0x0022 (34 bytes)
bNumInterfaces : 0x01 (1 Interface)
bConfigurationValue : 0x01 (Configuration 1)
iConfiguration : 0x00 (No String Descriptor)
bmAttributes : 0xC0
D7: Reserved, set 1 : 0x01
D6: Self Powered : 0x01 (yes)
D5: Remote Wakeup : 0x00 (no)
D4..0: Reserved, set 0 : 0x00
MaxPower : 0x32 (100 mA)
Data (HexDump) : 09 02 22 00 01 01 00 C0 32 09 04 00 00 01 03 01 ..".....2.......
01 00 09 21 11 01 00 01 22 40 00 07 05 81 03 08 ...!...."@......
00 0A ..
---------------- Interface Descriptor -----------------
bLength : 0x09 (9 bytes)
bDescriptorType : 0x04 (Interface Descriptor)
bInterfaceNumber : 0x00
bAlternateSetting : 0x00
bNumEndpoints : 0x01 (1 Endpoint)
bInterfaceClass : 0x03 (HID - Human Interface Device)
bInterfaceSubClass : 0x01 (Boot Interface)
bInterfaceProtocol : 0x01 (Keyboard)
iInterface : 0x00 (No String Descriptor)
Data (HexDump) : 09 04 00 00 01 03 01 01 00 .........
------------------- HID Descriptor --------------------
bLength : 0x09 (9 bytes)
bDescriptorType : 0x21 (HID Descriptor)
bcdHID : 0x0111 (HID Version 1.11)
bCountryCode : 0x00 (00 = not localized)
bNumDescriptors : 0x01
Data (HexDump) : 09 21 11 01 00 01 22 40 00 .!...."@.
Descriptor 1:
bDescriptorType : 0x22 (Class=Report)
wDescriptorLength : 0x0040 (64 bytes)
Error reading descriptor : ERROR_INVALID_PARAMETER
----------------- Endpoint Descriptor -----------------
bLength : 0x07 (7 bytes)
bDescriptorType : 0x05 (Endpoint Descriptor)
bEndpointAddress : 0x81 (Direction=IN EndpointID=1)
bmAttributes : 0x03 (TransferType=Interrupt)
wMaxPacketSize : 0x0008 (8 bytes)
bInterval : 0x0A (10 ms)
Data (HexDump) : 07 05 81 03 08 00 0A .......
-------------------- String Descriptors -------------------
------ String Descriptor 0 ------
bLength : 0x04 (4 bytes)
bDescriptorType : 0x03 (String Descriptor)
Language ID[0] : 0x0409 (English - United States)
Data (HexDump) : 04 03 09 04 ....
------ String Descriptor 1 ------
bLength : 0x10 (16 bytes)
bDescriptorType : 0x03 (String Descriptor)
Language 0x0409 : "Arduino"
Data (HexDump) : 10 03 41 00 72 00 64 00 75 00 69 00 6E 00 6F 00 ..A.r.d.u.i.n.o.
------ String Descriptor 2 ------
bLength : 0x12 (18 bytes)
bDescriptorType : 0x03 (String Descriptor)
Language 0x0409 : "Keyboard"
Data (HexDump) : 12 03 4B 00 65 00 79 00 62 00 6F 00 61 00 72 00 ..K.e.y.b.o.a.r.
64 00 d.
参考情報がだらだら長引いてしまいました。
Zoomは転送データ量が他のネット会議ツールよりも少なく安定しています。Wifi経由でないスマホからの出席者にとってもよいことですね。私は一昨年から遠隔会議に使ってきましたが、最近は乱入防止のために待機室を使うなど簡単な対策もそろってきましたし、この便利なツールにはとても感謝しています。
ネット会議の需要はまだまだ当分続くことでしょうし、広がる一方でしょう。なのでこの簡単操作ボタンの出番は半永久的?次は全体が一番小さいのを作っておこうかと考え始め、今になって結局Arduino Micro系の基板を注文しておきました。あれっ^^;
小さいのをそちらで作った際には、またここの記事でご報告するつもりです。ただ、記事はなにせ勝手なマイペースですから、途中で他の記事になったりして・・
©2020 Akira Tominaga, All rights reserved.
Zoom用かんたん操作ボタンをArduino-UNOで作る(その1)
お籠り生活の続く中です。Zoomで会議をしている方はとても多いでしょうね。クロマキーなどを自作したりして、、。私もそうですが。Zoomには便利なショートカットキーが色々ありますがなかなか使えません。ホスト側の操作を少しでも楽にしたいと考え、簡単な操作ボタンをArduinoで作ってみることにします。
操作しやすそうなボタンを手持ち部品から探すと次のものです。
ずいぶん昔の部品なのですが、キートップが入れられ、精度が高くボタン操作は超スムーズな逸品です。裏にTKCというマークがきざまれているので、メーカーは東海通信工業で、1990年頃の製品と思われます。当時の日本製の品質はあらためて凄いと思います。もちろん、操作しやすいボタンなら何をつかってもOKです。
手持ちには8キ―と4キーのものがありますが、8キーを使うと操作が却って煩雑になりそう。この際は次のショートカットキーを選んで、単純に4キーに収めることにします。もちろんキーはいつでも取り換えられますが。
さて、これをArduinoで簡単に作りたいと思います。Arduino LeonardoやMicroなどは本体にUSB通信を含むATmega32U4なので、Keyboardライブラリを使えば直接USBキーが作れます。しかし、現時点では手持ちがUnoやMega256、ESPなどばかり。そういう方は多いことでしょう。今さら購入もなんですので、この際は試しにUnoで作ることに。
Arduino UnoはUSB接続部分に周辺マイコンとしてATMEGA16U2を使っています。そのファームウェアを書き換えることでUnoをUSBキーボード・コントローラーに変えてしまいます。上の写真では左上のほうにある小さなICがATMEGA16U2です。
純正のArduino Unoなら問題ないですが、コンパチモデルでもOK。ただしUSB接続マイコンが上記のものでないとだめ(CH340Gなどだとどうにもなりません)。また、次に書きますように、本体のATMEGA328PがSMDでなくDIP型の方が使いやすいです。
ファームウェアを書き換えると、このマイコンはUSBキーボードに化けてしまいます。それをArduino UNOとしてプログラミングする時には、再びATMEGA16U2のファームウェアを元のファームウェアに戻す必要があります。そうするとテスト中何度もファームウェアを入れ替えなくてはならなくなりますので、それを避けるために次の工夫をします。まず無圧ソケットで次のような部品を2つ作り、別のUNOでプログラミングされた本体のATMEGA328Pをこれと入れ替えながら調整するようにします。
28ピン無圧ソケットに普通の28ピンソケットを履かせて、さらにその下に丸ピンソケットを履かせます。丸ピンソケットは14ピンのを2つ差し込む方が簡単にできます。
これでATMEGA328Pに下駄をはかせてUNOに差し込んでおくわけです。こうすれば他のUNOでプログラミングしたものと簡単に取り替えられますから。
こうして細工をした2つのUNO(または互換機)を用意します。
紛らわしいのでKeyboardに化けさせる側に何かで印をつけておくといいですね。ここでは黄色のシールを貼ることにします。
USBインターフェイスであるATMEGA16U2のファームウェアを書き換えるためには、Atmel社(現在はMicrochip社)のFLIPというツールを使います。これは" atmel flip download"で検索すれば簡単に見つかりますのでWindowsにインストールしておきます。とても簡単なツールです。
ファームウェアはGithubのArduino-keyboard-0.3.hexをみつけてコピーしておきますが、それを収容するフォルダーにもパスにも日本語や特殊記号がはいらないように作っておきます。そうしないとFLIPがトラブるためです。また、このファームウエアをいれたあとで、必要に応じてArduino Unoのファームウェアに戻すために、次の場所からhexファイルをコピーして、この同じフォルダーに入れておくのが賢いです。
C:\ProgramFiles (x86)\Arduino\hardware\arduino\avr\firmwares\atmegaxxu2\arduino-usbserial\Arduino-usbserial-atmega16u2-Uno-Rev3.hex
ここまでできたら、まずはArduinoのテスト用スケッチを書き込んでおきます。例はこの記事の最後の方につけておきます。
Windows PCにATMEGA16U2を認識させるためには、次のように左上の方にある3対並んだピンの一番左2つをジャンパーピン(黄色)で閉じておきます。もちろんメスーメスのデュポンワイヤーでも構いません。
その状態でPCにUSB接続をします。そして数秒後にジャンパーピンを抜くことによってWindowsがATMEGA16U2を認識します。デバイスマネジャー画面を立ち上げていればその状態が観察できます。もしPCにATMEGA16U2ドライバーがないときはそこでインストールしてしてから同じことをやり直します。
そしてFLIPを立ち上げてファームウェアを書き換えるのですが、次のように行います。
最初に上の左のようにICの絵をクリックしてデバイスを選択します。次にUSBケーブルの絵をクリックして上の右のようにUSBを選択します。すると次の左のようになります。
そしてOpenをクリックすればATMEGA16U2と接続が確立して上の右のようになります。次にFileをクリックして、先ほど準備しておいたファイルkeyboard-0.3.hexを指定します。
上図右のように選びます。そしてFLIPの左下にあるRunをクリックすればファームウェアが書き換えられます。
次の図の左下のようにVerifyPASSが表示されればおしまい。FLIPを終了させてUSBをさしなおすことにより、USB-HIDキーボードと認識されます。
なお、上図右は、Arduino-UNOに戻すために、ファームウェアを同じ手順で元のArduino-usbserial-atmega16u2-Uno-Rev3.hexに書き直した時の状態です。
テストのためのArduino-Unoのスケッチは次のように簡単に作りました。まだスイッチ部品との接続などは行っておらず、まずはUSBキーボードとして機能することの確認を行うのみです。USB-HID(ヒューマンインターフェイスデバイス)キーボードではキーは同時に6つまで押せ、それにシフトやAltなどを加えた状態を識別しますので、送る信号は1バイトの0x00を加えた8バイト、そしてキーを離す信号も同じフォーマットの0x00を8バイト送ります。これを「レポート」と称します。HIDのアーキテクチャに従うため壮大な無駄?をしてますがHIDは遅いので何ら問題ないわけです。
/* **************************************************
* USB-Keyboard for Arduino-UNO *
* Test version V00 April 30, 2020 *
* (c) Akira Tominaga, All rights reserved. *
****************************************************
*/
byte KbD[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
// KbD[0]=Modifier for USB Keyboard -code
#define rGUI B10000000 // right Windows key
#define rALT B01000000 // right Alt key
#define rSFT B00100000 // right Shift key
#define rCTL B00010000 // right Ctl key
#define lGUI B00001000 // left Windows key
#define lALT B00000100 // left Alt key
#define lSFT B00000010 // left Shift key
#define lCTL B00000001 // left Ctl key
// KbD[1] shoule always be 0x00
// KbD[2] to KbD[7] include scan-codes (max 6 codes)
// Alphabet code = ASCII val-0x5D (from a=0x61 to 0x04)
#define Csub 0x5D // Character value to subtract
// Function key code F1=0x3A, F2=0x3B, etc
#define ScF1 0x3A // USB KB code for F1 key
#define ScF2 0X3B // USB KB code for F2 key
// Space key code =0x2c
#define ScSp 0x2c // USB KB code for Space key
byte Chr = 'a'-Csub; // set initial key = a
void setup() { // Setup **************************
Serial.begin(9600);
}
void loop() { // Arduino loop ******************
KbD[2] = Chr;
KbWrite();
delay(1);
Chr = Chr+0x01;
KbD[2] = Chr;
KbD[0] = 0x00+lSFT; // test modifier (Shift)
KbWrite();
delay(1);
Chr = Chr+0x01;
KbD[2] = Chr;
KbWrite();
delay(1);
KbD[2] = ScSp; // Space key
KbWrite();
Chr = Chr+0x01; // increase char code
if(Chr>'z'-Csub){ // if exceeded 'z'
Chr = 'a'- Csub; // then re-set to 'a'
}
delay(3000);
}
/********************************
* *** User defined functions ***
* *****************************/
// *** KbWrite() write character as HID KB report
void KbWrite(void)
{
Serial.write(KbD, 8); // make
KbD[2] = 0x00; // clear character
KbD[0] = 0x00; // clear modifier
Serial.write(KbD, 8); // break
}
USB keyboardは 標準USB HID(ヒューマンインターフェイスデバイス)として機能しますからドライバーの準備は不要です。差し込めば即USB keyboardとして働きますから、予めメモ帳などを開いておけばそこに文字が書き込まれていきます。上のプログラムではメモ帳に次のように出力されていきます。
ここまでで少し長くなり、4月最後の夜も更けてきました。
少しおまたせすることになってすみませんが、ここまでで、UNOを使うやり方が大体ご理解いただけたかもしれません。文字コードの参照先や、この操作ボタンを使うとZoomホストの操作がどれくらい楽になるかなどを含めて、近日中に同じ題名(その2)を書きたいと思います。
当面はこの装置を試作して次のZoom会議に使うのが優先ですみません^^;
©2020 Akira Tominaga, All rights reserved.