LPC11U35でUSB-CDC
LPC11U35の特徴の一つは、USB関連のプログラムがROMに入っていて、それらを使うことによって比較的簡単にUSBを使うプログラムが書けることである。
LPC11U_LPC13U_CodeBaseでそれを使うのは非常に簡単で、CFG_USB_CDCを定義すれば、自動的にsrc/core/usb/usbd.hやusb_cdc.hが組み込まれて、USB-CDCが使えるようになる。
しかし、予想に反して容量が非常に大きくなってしまった。
調べてみると、usbd.cの中で、シリアル番号の文字列を求めるのにsprintfを使っており、これが肥大化の原因だったので、それを使わないように改造したら、1.7k程度の増加ですんだ。
USB CDCが1.7kというのは、非常にコンパイクトである。
LPC11U35には、2kのUSB用の専用のRAMも用意されており、ROM上のUSBのプログラムがこれを使っているらしい。
心配なのは、LPC11U_LPC13U_CodeBaseのusbd.cでは、この領域のさらに後ろの2kの領域のRAMを使っているっぽいことである。
その領域のことはマニュアルには記載が無いけど、アクセスは出来ることは確認したし、動いているので、気にしないことにした。
早速、mruby/cで標準入出力としてUSB CDCを使うようにしてみたら、なんとか64kには収まった。
しかし、mrbコードを入れるスペースが1kほどしか残っていない。
次は、それをどうするかを考えることにしたい。
LPC11U35のコンパイルと書き込み
LPC11U35のプログラムをコンパイルするにあたって、まず問題になったのが、実は開発環境である。
mbedが終了したので、ローカルに環境を整える必要がある。
その候補として、MCUXpresso IDEという統合開発環境や、LPCOpenとかMCUXpresso SDKというライブラリなどがNXP社から提供されている。
しかし、一つのマイコンを使うためだけに、わざわざ使ったことの無い統合開発環境を使うのも面倒だし、ライブラリをダウンロードするのに、アカウントが必要だったりと、敷居が高い。
いろいろと調べていたら、githubにLPC11U_LPC13U_CodeBaseというものを見付けた。
これらのマイコンを積んだ様々なマイコンボード用に作られたもののようだ。
LPC11U37がサポートされており、仕様の近いLPC11U35もなんとかなりそうである。
他にも良いものがあるかも知れないが、とりあえずこれを使ってみることにした。
まず、Debianにコンパイラなどを入れるために、以下のコマンドを実行した。
sudo apt install build-essential gcc-arm-none-eabi binutils-arm-none-eabi libnewlib-arm-none-eabi picolibc-arm-none-eabi
そしてgithubから落してきたzipを展開する。
LPC11U35用にメモリの容量を書き換えて、cmsis/lpc11u35.ldを作る。
mainがどこにあるのか見付けるのに苦労したが、src/boards/の中のマイコンボード用のフォルダのどれかに入っており、そのマイコンボードはMakefile中で指定されている。
新しくボードのフォルダとファイルを作って、それをLPC11U35用に書き替える。
Makefileが長く、様々なライブラリも同時にコンパイルされるので、無駄が多いように感じるが、さらにそこにmruby/cのファイルもコンパイルするように追加する。
また、コンパイルした後に、チェックサムを追加するツールがtools/lpcrcにあるので、これを実行できるように、そのフォルダでmakeを実行しておく。
これで準備が完了で、makeとすると、多くのファイルがコンパイルされて、bin/firmware.binができる。
書き込みは、少し特殊である。
ISPモードで立ち上げると、PCにはディスクとして認識されるので、そこにfirmware.binをコピーすると、flashへの書き込みが行われる。
残念ながらDebianではファイラからこのディスクにはファイルのコピーが行えないので、以下のようなコマンドでコピーする。
sudo dd if=bin/firmware.bin of="/media/USER/CRP DISABLD/firmware.bin" conv=notrunc; sudo sync
USERのところは、適切に変更して、ディスクの取り外しもきちんとした方が良いだろう。
flashは64kなので、FLOATとTASKとmrblibは消した状態で、mruby/cに基本的な機能のAPIを組み込んでコンパイルしてみたら、それなりの容量になった。
これに標準入出力としてUSB-CDCを加えたら、容量が64kを越えるのでは無いかと心配になる。
DEBUGを無効にするのを忘れていて、それを指定したら多少はましになったが。
Stringを消すのも方法だが、できればやりたくない。
さらにmrbコードを保存する容量も確保しないといけないのに。
ch32funでdeep sleep
mruby/cを省電力にするために、deep sleepを取り入れることにした。
Arduinoを使ったmruby/cでのEPS8266やESP32用には、すでにこれを取り入れていたが、それを様々なマイコン用に一般化できるようにしてみたのである。
その仕様についても迷っていたのだが、deep sleepについては秒で指定するようにするが、浮動小数点を使えるマイコンでは、サブルーチンは引数は整数で渡したいのでms単位で指示を出すことにした。
それと、深く眠った後にresetのかかるdeep sleepと、消費電力は減るけどすぐに起きられるlight sleepを作ったが、後者はNO_SLEEP_LIGHTを定義することで、無効にできるようにした。
まずは、ch32funを使って、CH32V006,CH32X035,CH32V203について、deep sleepをできるようにして、006以外は動作チェックも行った。
CH32V006については、examples_x00x/standby_autowakeという例があったので、それを参考にした。
一方、CH32X035については、例が無かったので、リファレンスマニュアルを参考にして作ったが、Auto-wakeup moduleというのが出来ていて、レジスタの指定やクロック元が違ったりしたが、006と似たように書くことができた。
ただ、ch32funではCH32035用のレジスタが定義されていなかったので、以下のように定義した。
typedef struct {
__IO uint32_t CSR;
__IO uint32_t WR;
__IO uint32_t PSC;
} AWU_TypeDef;
#define AWU ((AWU_TypeDef *) AWU_BASE)
もしかしたら、最新判ではすでに定義されているかも知れないけど。
CH32V203では、さらに仕組みが変っており、real time clockを使って起きるような仕様になっていた。
かなり癖があって苦労したが、extralibs/rtc.hに関数が定義されていたので、それが非常に参考になった。
さらに、FLOATが有効な場合とそうでない場合で分岐していたのだが、その部分にミスがあって、CH32V203ではなかなかうまく行かずに苦労したが、なんとか動いた。
これでch32funを使ったmruby/cは、ほぼ完成したのでは無いだろうか。
CH32V006でのrv003usbがもう少し安定したら、その部分を調整する必要があるだろうが。
しかし、動作チェックが足りていない機能もあるので、それを少しずつやらないといけない。
今回もピン番号のミスを見付けたので、他にもミスが残っているかも。
LPC11U35の通信機能
最近いじりはじめたマイコンであるLPC11U35には、他のデバイスとの通信機能としてはI2CとSPIとUARTが搭載されている。
これらの機能についてプログラムを書いてみたので、そのことについて説明する。
SPIやUARTはマイコン自体が小さなバッファを持っているので、自前でバッファを作らずに簡単にプログラムを書くことができた。
ただし、SPIはMSB firstに固定だったので、LSB firstのときには、bitの順番を入れ替える処理を加える必要があった。
一方I2Cでは、状態を表すレジスタの値から、次に行う動作を判定して行くという仕様で設計されているようで、なかなかうまく通信出来ずに苦労した。
writeまたはreadだけの場合には、startしてからwriteまたはreadしてstopするという単純な作業だが、特にwriteしてからreadする場合との共存が面倒だった。
その場合には、start,write,restart,read,stopとすることになるが、startとrestartはしなければいけない操作が微妙に異なり、(re)startするときに、startなのかrestartなのかを区別しなければならない。
バスに送るデータはstartとrestartで同じなのだが、マイコンでの処理のプロセスが微妙に違うのである。
まだ完全では無いかもしれないが、これらの三つの通信機能のプログラムができて来たので、次はUSBかな。
デバイスでは無くてPCとのだけど、これも通信機能だった。
LPC11U35のピン制御
LPC11U35を動かすためのプログラムを書くために、リファレンスマニュアルを読んでいたのだが、ピンの設定がかなり不規則であることが分かった。
これまでに私が使ってきたマイコンでは、GPIOを使おうと思ったら、一部の特殊な機能のピンではその機能を開放する手続きが必要だが、同じ設定でほぼすべてのピンを使うことができた。
しかしLPC11U35では、7つものピンで、GPIOとして使うときに設定する数値が、通常の0ではなく1になっていた。
ADCについても、8つのピンで使えるのだが、そのうちの3つのピンで、この数値が2ではなく1になっていた。
別のマイコンでも、ADCの割り当てられているピンの番号が不規則なことはよくあったが、その設定の仕方はほとんど同じだった。
PWMが使えるピンが複雑な割り当てになっているのは、他のマイコンと同じだった。
これらの三つの機能を使えるようにするプログラムを書くためには、これらを適切に処理しなければならない。
これまでもPWMについては、ピン毎の設定を記録したテーブルを作って、それを元に設定していた。
GPIOやADCの不規則な設定についても、このテーブルに含めてみた。
しかし、頑張って条件分岐を効率化した場合とサイズあまり変らなかったので、どちらが良いのか悩ましいところだ。
汎用性を考えて、テーブルを参照するようにしようかな。
PWMについても予想と違うことがあった。
一つのタイマーに対して、MAT0-3が設定でき、これらの値と一致したときに、出力を変化させたりできるのだが、その中の一つを使ってタイマーの周期を設定する必要があり、dutyを指定するためには残りの三つの値でしか出来無いのである。
一つのピンだけでPWMを使うときには、そのピンに割り当てられていないMATを周期の決定に使えば良いのだが、複数のピンでPWMを使う場合には問題になるので、どのように設計するかが課題である。
一つのやり方はMAT3のピンは使わないことにして、それを周期を指定するのに使うという方法である。
また、その場合にも、MAT3に一致したときにピンのON/OFFを切り換えるように設定すれば、周波数が半分でdutyが50%として使える。
もっとも、同じタイマーを使うPWMピンは、周波数が同じになってしまうという制約はあるので、完全に次優に複数のPWMを使うことはできないのだが。
残念なのは、私が使っているマイコンボードのLEDは、PWMが使えないので、PWMの動作確認ができないことである。
GPIOとADCについては、少なくとも一部のピンでは動作確認はできて、mruby/cからも動いた。
mruby/cのI2C,SPI,UARTのunit
mruby/cで、UARTに加えてI2CやSPIでunit番号使えるようにしつつ、必要無いときには使わないようにしたいと考えていたが、良い方法を思いついた。
例えばNO_UART_HANDLEという定数を定義してある場合には、handleとunitを無くして、そうで無いときにはunitを処理するという方法である。
このようにすれば、マイコン毎にhandleを定義するかどうかを変えることができ、mruby/c側のプログラムでそれを判定して、コードを分岐すれば良いのである。
しかし、マイコン側の関数は、呼び出すたびにhandleを使う場合と使わない場合で分岐するか、引数の数を等しくするためにhandleを使わないときにはダミーの引数を指定するようにしなければならない。
後者の方が無難かな。
そのためには、マイコン側としてこれまで書いてきたarduino, ch32, gv32vfの三つのプログラムを修正しなければならない。
これらのマイコンには、複数のI2CやSPIを持っているものもあったのだが、これまではそれらの一つしか有効にしていなかった。
これらを有効にするよい機会かも知れないけど、コンパイルサイズも大きくなってしまうだろうから、それが問題だ。
I2Cの場合には、アドレスで複数のデバイスと通信できるのに、複数のI2Cを持つ必要はあまり無い気もする。
SPIは複数のデバイスを繋ぐのは得意では無いので、複数のSPIがあるのは納得できるけど。
Arduinoでも、ハードでは複数使える場合でも、一つしか定義されていないことが普通のようだ。
mruby/c側だけ書いておいて、マイコン側は後から考えることにしよう。
LPC11U35とmruby/c
入手しやすいマイコンボードで、mruby/cが動きそうなものとして、NXP社のLPC11U35というものを見付けた。
このマイコンは、webブラウザ上で開発できるmbedが使えたことで知られているが、mbedのサービスが終了したので、開発環境が悩ましいものとなっている。
特徴としては、ROMにUSBのプログラムが搭載されているので簡単にUSBが扱える点と、USBからdiskとして認識させてそれに書き込むとflashが書き換えられるという点と、消費電力が小さいので電池駆動に向いている点などが挙げられる。
LPC11U35は64kのflashと8kのRAMをもち、ギリギリmruby/cが動きそうではある。
mruby/c部分とマイコン部分のプログラムを分離したことで、新たなマイコン用のmruby/cを作りやすくなったので、このマイコン用でもmruby/cを動かしてみようかな。
しかし、このマイコンを活かすに当って問題がある。
現状のmruby/c部分のプログラムには、I2CとSPIは一つしか使わない設計になっており、また省電力のためのdeep sleepなども組み込まれていない。
LPC11U35にはSPIが二つあるのである。
まずは、これらを拡張するところから始めようと思う。
しかし、機能を増やすということは、サイズが大きくなるということを意味しており、難しい問題である。
でも、仕様を変更するのなら、初期の方が良い。
UARTについては、すでにunitをいくつも指定できるようにしているが、マイコン側のサブルーチンにはhandleをそのまま渡している。
マイコンの仕様によって、バッファが必要だったり、様々な状況が考えられるので、そのようにした。
I2CとSPIについても、そのようにするべきか、バッファなどは作らないかマイコンにまかせることにしてunit番号だけを渡すようにするのかの選択肢がある。
後者の方が楽に書けそうだし、サイズも小さくなりそうだけど、将来の拡張性が限定されてしまう。
どうしようかな。
sleepについては、sleepやsleep_msがkernelのmethodになっているので、deep sleepも同じようにすれば良いだろう。
問題は、時間の単位を秒にするのか、ミリ秒にするのかという点である。
本当のdeep sleepは、秒で良い気がする。
一方、省電力用のlight sleepはどちらが良いだろうか。
これも悩ましい。
mruby/cとマイコン依存のコードの分離
様々なマイコンで簡単にmruby/cを使えるようにするために、mruby/c依存の部分とマイコン依存の部分を分離するという方法を考えて、2026/10/18のブログで紹介した。
その主な部分が出来てきたのでそのことについて説明したい。
mruby/c依存の部分は、いくつかのマイコン用に以前書いたコードの共通の部分を取り出したら、比較的簡単に作ることができた。
また、これまでArduinoとch32funとgd32vf103用にmruby/c環境を作って来たが、それらからマイコン依存の部分を取り出すこともそれほど難しくは無かった。
新しく作った環境でも、コンパイルはできることと、サイズがそれほど変らないことは確認できた。
まだ動作確認はあまりできていないが、私のホームページのマイコンのところに公開することにした。
ファイルの数が多くならないように、すべてヘッダファイルのみで構成することにした。
mruby/c依存の部分は合計で20kバイト程度になった。
一方でマイコン依存の部分は10kから15kバイト程度で、Arduino用は6k弱になった。
バイト数だけでは判断できないが、一つのマイコン用に作らなければならないコードの合計が、30k強から10k強になり、開発が楽になると考えられる。
ちなみに、今回新しく公開したLongan Nanoについては、カラー液晶を利用して、エスケープシークエンスで文字の色を変えられるようにした。
deep sleepなども、このやり方で組み込もうかな。
サイクルコントロールユニットの自作
試料を加熱するために電気炉を使っているが、そのヒーターの制御には、ソリッドステートリレー(SSR)またはサイリスタを使っている。 より精密な制御にはサイリスタの方が望ましいが、当然価格は高くなり、部品も大きくなってしまう。 電流がそれほど大きく無い場合には、コンパクトな理化工業のSSNP-15Fをよく使っていたのだが、今は廃盤になってしまっている。
代わりに使えるようなコンパクトなサイリスタが無いかを探していたら、OMRONのサイクルコントロールユニットG32A-Eというものを見付けた。 これは、ゼロクロス方式のSSRと組み合せることによって、サイクル制御方式のサイリスタとしての役割をもたせるためのものである。 小型のSSRと組合せれば、コンパクトに収められるのではと考えて、仕様の詳細を調べたら、分解能が低いことが分かった。 このコントローラーでは、0.2sの間で交流の何サイクルかをONにするという制御を行っているために、その間の波の数で分解能が決ってしまうのである。 これでは、ちょっと使えない。
15年ぐらい前に私が設計したサイリスタコントローラーでは、1サイクル毎に出力の割合を積算して、それが1を超えたら1サイクル出力して、積算値から1を引くというやり方をしたが、そうすれば単純な計算で十分な分解能にできる。 売って無いなら作れば良いだろうということで、温度調節機からの4-20mAの信号を読み取ってSSRを制御する機器を作ってみることにした。 後で知ったのだが、OMRONの多点パワーコントローラG3ZAというものがあり、そこでは高い分解能と同時に8個までのSSRを制御でき、今回作成したものと同じような機能を持つことが分かったが、入力が特別な信号になるようだ。
このような機器を実現するにあたって、交流のサイクルをどのように検出するかが課題である。 15年前には、トランスで電圧を落として、その電圧をマイコンで読み取ることで判定していた。 それと同時に降圧した電圧からマイコン用の5Vも作るようにいていた。 しかし、トランスで大きくなるし、以前に使っていた基板用のトランスも廃盤になってしまっているので、色々と考えて今回は、フォトカプラを用いて検出することにした。 具体的には、フォトカプラの入力とダイオードを極性を逆にして並列にしたものに、100kohm程度の抵抗を直列に繋いで、それをAC100Vに接続して、フォトカプラの出力をプルアップしたマイコンのピンで読み取る。 すると、ダイオードによって交流は半波になり、その電圧が高くなったときに、マイコンのピンに電流が流れてlowになるという仕組みで、交流のサイクルを検出できるのである。 ただ、フォトカプラには寿命があるので、できるだけ電流は小さく抑えて寿命を長くして、さらに素子を交換可能にした。 一方、マイコン用の電源は、安価で小型の電源ユニットを用いることにした。 そして、温度調節機の出力を220ohmに流したときの電圧を、ADCで読み取って、それを元にサイクル毎のon/offを判定して、マイコンから出力すれば良いのである。 マイコンボードにはUIAPduinoを使うと、それを合わせても、千円以下しかかからない。 ちなみに、ADCが6個あったので、6つまで制御できるようにしてみた。 作ったプログラムは以下の通りである。
#include "ch32fun.h" #include <stdio.h> #define NUM_HEATERS 6 // the number of heaters #define ADC_4MA_MIN 180 // 220 ohm #define ADC_20MA_MAX 900 // #define RESOLUTION_MAX (ADC_20MA_MAX - ADC_4MA_MIN) #define RESOLUTION_MAX_SCALED (RESOLUTION_MAX << 8) // OUT pins { 3, 4, 10, 8, 9, 7 } static volatile uint8_t heater_pins[NUM_HEATERS] = { PC1, PC2, PD0, PC6, PC7, PC5 }; // ADC pins {TX, RX, A0, A1, A2, A3 } static volatile uint8_t analog_pins[NUM_HEATERS] = { PD5, PD6, PA2, PA1, PC4, PD2 }; static volatile uint8_t adc_channels[NUM_HEATERS] = { 5, 6, 0, 1, 2, 3 }; // AC 100V - 33kohm - (photo-coupler + diode) - 33kohm - AC 100V // to disable reset for PD7, please run .
様々なマイコンでmruby/cを使う方法
あるマイコンでmruby/cを使うためには、mruby/cをコンパイルするだけでは駄目で、GPIO, ADC, PWM, I2C, SPI, UARTなどの機能を使えるようにプログラムを作る必要がある。 このことがmruby/cを使う敷居を上げているように思われる。 私がmruby/cを初めて使った時には、様々なマイコンを利用できるArduinoを活用して、ESP8266やESP32やCH32V203に機能を組み込んだ。 その後、ch32シリーズに限れば、サイズが小さくなる上に細かな操作もできるch32funの方が良いことに気が付き、CH32V203とCH32X035とCH32V006でmruby/cを動かすことに成功した。 そして最近、GD32VF103でmruby/cを動かしてみた。 この際には、ch32fun用のコードを変更して作ったのだが、思ったよりも簡単だったが、それなりに面倒ではあった。 異なるマイコン用にmruby/c用のプログラムを書いたことによって、その特徴が多少は理解できて、プログラムを作る効率を上げる仕組みを思い付いたので、そのことについて説明したい。 それがうまく行けば、新たなマイコンでmruby/cに機能を追加する手間が減り、mruby/cの敷居が下るのではないかと期待している。
例えばGPIOについては、ArduinoではdigitalWrite、ch32funではfunDigitalWriteなどを使って扱うことができ、これらの関数を使うとmruby/cからGPIOを使えるようにするのは比較的に簡単である。 他のマイコンについても、mrbcDigitalWriteという関数を作れば、ほぼ同様のやり方でプログラムを作ることができる。 これをさらに進めれば、マイコン固有の書き方をしなければいけない部分を、mrbcDigitalWriteなどの関数に押し込んで、mruby/c固有の書き方をする部分と分離することができる。 これまでは、それぞれの機能について、ヘッダファイルとC言語のソースファイルの二つを作っていたので、これらをそれぞれのマイコンについて書き換えなければならなかった。 しかし、上記のようにすれば、新たなマイコンでmruby/cを動かすときには、mruby/c固有の部分は共通のものを使い、マイコン固有のmrbcDigitalWriteなどの関数のみを書き換えれば良くなるはずである。
GPIOについてのmruby/c固有の部分を作ってみたが、以下のような感じになった。 ピンは番号で指定することにして、GPIOを扱う関数とモード用の定数を使って記述した。 また、エラー処理なども省いて、できるだけコンパクトにした。 さらに、FloatとStringの両方を使わないときには、できるだけ小さくするために、methodの数を減らすようにした。
#ifndef _MRBC_GPIO_H #define _MRBC_GPIO_H #include "mrbc_api.h" #include "mrubyc.h" typedef struct GPIO_HANDLE { uint8_t pin_num; } GPIO_HANDLE; static void c_gpio_new(mrb_vm *vm, mrb_value *v, int argc){ v[0] = mrbc_instance_new(vm, v[0].cls, sizeof(GPIO_HANDLE)); GPIO_HANDLE *handle = (GPIO_HANDLE *)v[0].instance->data; if(argc>0){ handle->pin_num = GET_INT_ARG(1); } if(argc>1) mrbcPinMode( handle->pin_num, GET_INT_ARG(2) ); } static void c_gpio_setmode(mrb_vm *vm, mrb_value *v, int argc){ uint8_t pin=0xff; uint8_t mode; if( v[0].