LPC810メモ:疑似正弦波で音楽を鳴らす(おじいさんの古時計)
前々回http://d.hatena.ne.jp/mikkabo/20160527/1464358308:title=前回]で実現した正弦波による音階の出力を使って、音楽を鳴らしてみる。
短いけど「おじいさんの古時計」を入力してYouTubeにアップしてみた。音だけで絵は動かないのであまり動画の意味ないが、YouTube使ってみたかったので。
前提とする環境はこちら→LPC810メモ 共通の準備
SCTなど基本的なサンプルについてはこちら→LPC810みっかぼ自作サンプル集
音を並べて曲にするには、なんらか譜面をデータ化する必要があって、容量を節約する方法を考えようとそれなりに迷った。最終的に以下のとおりとした。
- 1コマンド1byte(uint8_t)で表す
- 上位4bitでコマンドの種類(16種類、音階、休符、オクターブ指定など)を表す。enumで定義。
- 下位4bitで、1-16のパラメータを表す(1始まりで16段階)。
コマンドとパラメータの表は以下。
コマンド | 値 | コマンドの意味 | パラメータの意味 | 備考 |
---|---|---|---|---|
C | 0 | ド | 音の長さ | |
Cs | 1 | ド# | 音の長さ | |
D | 2 | レ | 音の長さ | |
Ds | 3 | レ# | 音の長さ | |
E | 4 | ミ | 音の長さ | |
F | 5 | ファ | 音の長さ | |
Fs | 6 | ファ# | 音の長さ | |
G | 7 | ソ | 音の長さ | |
Gs | 8 | ソ# | 音の長さ | |
A | 9 | ラ | 音の長さ | |
As | 10 | ラ# | 音の長さ | |
B | 11 | シ | 音の長さ | |
R | 12 | 休符 | 休符の長さ | |
T | 13 | 前の音の継続 | 音の長さ | 16を超える長さを実現したい場合に使う |
O | 14 | オクターブ指定 | オクターブ | 3..6で指定。「O4のド」がピアノ中央のド |
V | 15 | 音量指定 | 音の大きさ | 1..10で指定 |
なお、音の長さは「数字に比例する」長さであり、C(1)が一番短くて、C(16)がその16倍。C(8)が8分音符とか、そういう気のきいた意味ではない。
C(1)が16分音符、C(4)が4分音符、C(16)が全音符、という使い方を想定。
例えば以下のように書く。
// アンパンマン体操の歌いだしの部分 constexpr uint8_t part0[] { C(4),C(4),C(2),D(2),E(2),F(2), // ド、ド、ドレミファ G(4),F(2),E(2),D(4),R(4), // ソ、ファミレ、(休符) };
ここで、C,D,Eのような関数はconstexprで定義していて、コンパイル時にuint8_tの定数になる。つまり、1コマンド1byteになる。
利便性のために、多用しそうなパラメータ1,2,4の音符などは、カッコなしで使えるように別途定数を定義しておく。なので、上記の例は以下のようにも書ける。
// アンパンマン体操の歌いだしの部分 constexpr uint8_t part0[] { C4,C4,C2,D2,E2,F2, // ド、ド、ドレミファ G4,F2,E2,D4,R4, // ソ、ファミレ、(休符) };
そして、この形式で格納されたuint8_tの配列を解釈して、PWMに渡すdutyを決定するための処理をクラス化(以下サンプルのSctTonePart)。
サンプルコード
SctSoundUtil.h -> 前々回のサンプルから持ってくる。
SctSoundUtil.cpp -> 前々回のサンプルから持ってくる。
SctTonePart.h
#ifndef SCTTONEPART_H_ #define SCTTONEPART_H_ #include "LPC8xx.h" class SctTonePart { public: void setMml(const uint16_t u, const uint8_t *const *m); uint8_t proceed(); // 1/10000秒進めて、その時点でのdutyを返す bool eol() const; // 末尾に到達していたらtrue // コマンド種類の定義 enum : uint8_t { ComKind_C = 0 << 4, ComKind_Cs = 1 << 4, ComKind_D = 2 << 4, ComKind_Ds = 3 << 4, ComKind_E = 4 << 4, ComKind_F = 5 << 4, ComKind_Fs = 6 << 4, ComKind_G = 7 << 4, ComKind_Gs = 8 << 4, ComKind_A = 9 << 4, ComKind_As = 10 << 4, ComKind_B = 11 << 4, ComKind_R = 12 << 4, ComKind_T = 13 << 4, ComKind_O = 14 << 4, ComKind_V = 15 << 4, }; // コマンド種類(上位4bit)とパラメータ(下位4bit)を組み合わせてコマンドを作る constexpr static uint8_t MakeCommand(const uint8_t command, const uint8_t arg) { return command | (arg - 1); } // 減衰の状態を取得(後々の都合で、外部から見れるようにしてある) uint16_t getDamper() const { return damper; } private: uint16_t unitTime; // パラメータ1あたりの時間 const uint8_t *const *mmlLines; // 複数行の譜面格納用 const uint8_t *mml; // 現在の譜面 int currentMmlLine; // 現在の譜面が何行目か int8_t octave; // 現在のオクターブ(3 .. 6) int32_t position; // mmlの何文字目を処理中か uint8_t volume; // volume(1 .. 10) uint8_t readNextTone(); // 次のトークンを読む uint16_t toneVerocity; // 現在の音の角速度 uint32_t remainLength; // 現在の音の残り長さ uint16_t angle; // 現在の角度 uint16_t damper; // 減衰の状態、0..31 uint16_t damperCounter; // 減衰用のカウンタ }; constexpr uint8_t C(const uint8_t arg) { return SctTonePart::MakeCommand(SctTonePart::ComKind_C, arg); } constexpr uint8_t Cs(const uint8_t arg) { return SctTonePart::MakeCommand(SctTonePart::ComKind_Cs, arg); } constexpr uint8_t D(const uint8_t arg) { return SctTonePart::MakeCommand(SctTonePart::ComKind_D, arg); } constexpr uint8_t Ds(const uint8_t arg) { return SctTonePart::MakeCommand(SctTonePart::ComKind_Ds, arg); } constexpr uint8_t E(const uint8_t arg) { return SctTonePart::MakeCommand(SctTonePart::ComKind_E, arg); } constexpr uint8_t F(const uint8_t arg) { return SctTonePart::MakeCommand(SctTonePart::ComKind_F, arg); } constexpr uint8_t Fs(const uint8_t arg) { return SctTonePart::MakeCommand(SctTonePart::ComKind_Fs, arg); } constexpr uint8_t G(const uint8_t arg) { return SctTonePart::MakeCommand(SctTonePart::ComKind_G, arg); } constexpr uint8_t Gs(const uint8_t arg) { return SctTonePart::MakeCommand(SctTonePart::ComKind_Gs, arg); } constexpr uint8_t A(const uint8_t arg) { return SctTonePart::MakeCommand(SctTonePart::ComKind_A, arg); } constexpr uint8_t As(const uint8_t arg) { return SctTonePart::MakeCommand(SctTonePart::ComKind_As, arg); } constexpr uint8_t B(const uint8_t arg) { return SctTonePart::MakeCommand(SctTonePart::ComKind_B, arg); } constexpr uint8_t R(const uint8_t arg) { return SctTonePart::MakeCommand(SctTonePart::ComKind_R, arg); } constexpr uint8_t T(const uint8_t arg) { return SctTonePart::MakeCommand(SctTonePart::ComKind_T, arg); } constexpr uint8_t O(const uint8_t arg) { return SctTonePart::MakeCommand(SctTonePart::ComKind_O, arg); } constexpr uint8_t V(const uint8_t arg) { return SctTonePart::MakeCommand(SctTonePart::ComKind_V, arg); } constexpr uint8_t EOD {0xff}; constexpr uint8_t C1 {C(1)}; constexpr uint8_t Cs1 {Cs(1)}; constexpr uint8_t D1 {D(1)}; constexpr uint8_t Ds1 {Ds(1)}; constexpr uint8_t E1 {E(1)}; constexpr uint8_t F1 {F(1)}; constexpr uint8_t Fs1 {Fs(1)}; constexpr uint8_t G1 {G(1)}; constexpr uint8_t Gs1 {Gs(1)}; constexpr uint8_t A1 {A(1)}; constexpr uint8_t As1 {As(1)}; constexpr uint8_t B1 {B(1)}; constexpr uint8_t R1 {R(1)}; constexpr uint8_t T1 {T(1)}; constexpr uint8_t C2 {C(2)}; constexpr uint8_t Cs2 {Cs(2)}; constexpr uint8_t D2 {D(2)}; constexpr uint8_t Ds2 {Ds(2)}; constexpr uint8_t E2 {E(2)}; constexpr uint8_t F2 {F(2)}; constexpr uint8_t Fs2 {Fs(2)}; constexpr uint8_t G2 {G(2)}; constexpr uint8_t Gs2 {Gs(2)}; constexpr uint8_t A2 {A(2)}; constexpr uint8_t As2 {As(2)}; constexpr uint8_t B2 {B(2)}; constexpr uint8_t R2 {R(2)}; constexpr uint8_t T2 {T(2)}; constexpr uint8_t C4 {C(4)}; constexpr uint8_t Cs4 {Cs(4)}; constexpr uint8_t D4 {D(4)}; constexpr uint8_t Ds4 {Ds(4)}; constexpr uint8_t E4 {E(4)}; constexpr uint8_t F4 {F(4)}; constexpr uint8_t Fs4 {Fs(4)}; constexpr uint8_t G4 {G(4)}; constexpr uint8_t Gs4 {Gs(4)}; constexpr uint8_t A4 {A(4)}; constexpr uint8_t As4 {As(4)}; constexpr uint8_t B4 {B(4)}; constexpr uint8_t R4 {R(4)}; constexpr uint8_t T4 {T(4)}; constexpr uint8_t O3 {O(3)}; constexpr uint8_t O4 {O(4)}; constexpr uint8_t O5 {O(5)}; constexpr uint8_t O6 {O(6)}; #endif /* SCTTONEPART_H_ */
SctTonePart.cpp
#include "SctTonePart.h" #include "SctSoundUtil.h" // 1/10000秒進めて、その時点でのduty(0 - 40)を返す uint8_t SctTonePart::proceed() { if(eol()) { return 0; } // 音の長さが0になったら次を読む if(remainLength == 0) { uint8_t kind {readNextTone()}; if(kind != ComKind_T) { // 音の継続でない限り、減衰の状態をリセット damper = 0; damperCounter = 0; } } // 角度を1/10000秒分進めつつ、音の残り時間を減らす angle += toneVerocity; remainLength--; // 1定時間毎に音量を落とし、減衰する音にする damperCounter++; if(damper < 31 && damperCounter == (64 + damper * 32)) { damper++; damperCounter = 0; } return 20 + SctSoundUtil::sin(angle) * (32 - damper) * volume / 320; } void SctTonePart::setMml(const uint16_t u, const uint8_t *const *m) { mmlLines = m; mml = mmlLines[currentMmlLine = 0]; unitTime = u; octave = 4; angle = 0; position = 0; volume = 10; } uint8_t SctTonePart::readNextTone() { uint8_t arg {1}; uint8_t kind {0}; while(mml != nullptr) { const uint8_t command { mml[position++] }; kind = command & 0b11110000; arg = (command & 0b00001111) + 1; if (command == EOD) { mml = mmlLines[++currentMmlLine]; position = 0; } else if(kind == ComKind_O) { if(arg >= 3 && arg <= 6) { octave = arg; } } else if(kind == ComKind_V) { if(arg >= 1 && arg <= 10) { volume = arg; } } else if(kind == ComKind_T) { break; } else if(kind == ComKind_R) { angle = 0; toneVerocity = 0; break; } else { // 音階 toneVerocity = SctSoundUtil::toneAngularVelocity((kind >> 4) + (octave - 3) * 12); break; } } remainLength = arg * unitTime; return kind; } bool SctTonePart::eol() const { return mml == nullptr; }
sample.cpp
#include "LPC8xx.h" #include "SctSoundUtil.h" #include "SctTonePart.h" constexpr uint8_t part0_0[] { // 主旋律 O4,C4, F4,E2,F2,G4,F2,G2, A2,A2,As2,A2,D4,G2,G2, F4,F2,F2,E4,D2,E2, F(12),C2,C2, F4,E2,F2,G4,F2,G2, A2,A2,As2,A2,F4,G2,G2, F4,F2,F2,E4,D2,E2, F(12),F2,A2, O5,C4,O4,A2,G2,F4,E2,F2, G2,F2,E2,D2,C4,F2,A2, O5,C4,O4,A2,G2,F4,E2,F2, G(14),C2, O4,F2,F(6),G(8), A2,A2,As2,A2,D4,G2,G2, F(8),E(8), F(12),R4, EOD }; constexpr uint8_t part0_1[] { // 主旋律とハモるパート O3,A4, O4,C4,O3,As2,O4,C2,E4,D2,E2, F2,F2,G2,F2,D4,E2,E2, C4,C2,C2,C4,O3,As2,O4,C2, C(12),O3,A2,A2, O4,C4,O3,As2,O4,C2,E4,D2,E2, F2,F2,G2,F2,D4,E2,E2, C4,C2,C2,C4,O3,As2,O4,C2, C(12),C2,F2, O4,A4,F2,E2,C4,C2,C2, D2,D2,O3,As2,As2,A4,O4,C2,F2, A4,F2,E2,C4,C2,C2, E(14),R2, O4,C2,C(6),E(8), F2,F2,F2,F2,O3,As4,O4,D2,D2, C(8),C(8), C(12),R4, EOD }; constexpr uint8_t part0_2[] { // ベース R4, O3,A(8),O4,C(8), C(8),O3,As(8), A(8),G(8), A(12),R4, O3,A(8),O4,C(8), C(8),O3,As(8), A(8),G(8), A(12),R4, O4,C(8),O3,A(8), As4,F4,F(8), O4,C(8),O3,A(8), O4,C(14),R2, O3,A2,A(6),O4,C(8), C(8),O3,F(8), A(8),As(8),A(12),R4, EOD }; const uint8_t *part0[] { part0_0, nullptr }; const uint8_t *part1[] { part0_1, nullptr }; const uint8_t *part2[] { part0_2, nullptr }; static SctTonePart sctTonePart[3]; static void handler() { uint8_t duty{0}; bool endFlag{true}; for(auto &t : sctTonePart) { duty += t.proceed(); // 単位時間進めて、その時点でのdutyを取得 if(!t.eol()) { endFlag = false; } } if(endFlag) { SctSoundUtil::stop(); } else { SctSoundUtil::setDuty(duty); // dutyの合計値を設定 } } int main(void) { sctTonePart[0].setMml(1536, part0); // 1536は速度(大きいほど遅い) sctTonePart[1].setMml(1536, part1); sctTonePart[2].setMml(1536, part2); SctSoundUtil::setCallback(handler); SctSoundUtil::init(); SctSoundUtil::start(); return 0; }
接続
前回と同じのため省略。
ただし、電池がCR2032だとどうも安定しないので、単3の充電池3本に変更。
補足/雑感
- 実は最初は譜面を文字列で(よくあるMMLみたいので)表していたのだが、そうすると1音あたり2,3byteくらいになってしまい、ピアノの譜面みながら長めの曲を打ち込んでいたらあっという間に4Kbオーバー。紆余曲折の末に、文字列より入力面倒だけどこの方法となった。
- この「おじいさんの古時計」の例でバイナリが1928byteなので、残り約2Kb、すなわち2000個程度のコマンドを追加可能。
- 合計3Kbくらいになる大作も作ってるから、早めにアップして区切りつけたい。最近これに時間をかけすぎた。
LPC810メモ:疑似正弦波で和音を鳴らす
前回の続き、正弦波を重ねて和音を出してみる。
前提とする環境はこちら→LPC810メモ 共通の準備
SCTなど基本的なサンプルについてはこちら→LPC810みっかぼ自作サンプル集
ドミソ→ド#,ファ,ソ#→レ,ファ#,ラ→...という感じに半音ずつあがっていく。
他に前回と違うのは、音を減衰するようにして、ポーンポーンポーン・・と響くような音にしてみた。
サンプルコード
SctSoundUtil.h -> 前回サンプルから持ってくる。
SctSoundUtil.cpp -> 前回サンプルから持ってくる。
sample.cpp
(2016/05/29修正)
#include "LPC8xx.h" #include "SctSoundUtil.h" struct { uint8_t scale; // 音階(0 .. 47) uint16_t verocity; // 音階に対応する角速度 uint16_t angle; // 現在の角度 } tone[] {{0,0,0}, {4,0,0}, {7,0,0}}; // 音3つ、ド・ミ・ソで初期化 static int count{0}; // 1/10000秒毎にカウントアップする static int volume{0}; // ボリューム調整用変数 static void handler() { // 1/10000秒毎に呼ばれるハンドラ if(count % 2500 == 0) { // 250ms毎に音を更新 for(auto &t : tone) { t.verocity = SctSoundUtil::toneAngularVelocity(t.scale); t.scale++; if(t.scale > 47) { t.scale = 0; } } volume = 10; // 音を更新したタイミングで、ボリュームを10に設定 } count++; if(count % 500 == 0 && volume > 1) { volume--; } // 50ms毎にボリュームを1減らす int8_t duty{0}; for(auto &t : tone) { t.angle += t.verocity; // 角速度分進める duty += SctSoundUtil::sin(t.angle); // 足し合わせて和音にする } SctSoundUtil::setDuty(60 + duty * volume / 10); // ボリュームを加味してduty設定 } int main(void) { SctSoundUtil::setCallback(handler); SctSoundUtil::init(); SctSoundUtil::start(); return 0; }
接続
前回と同じのため省略。
補足/雑感
- 普通にうまくいった。
- と思ったら、後日見直したらvolumeの扱いが変だったので修正。これで不自然さを感じなかったのだから、自分の耳がなんと頼りにならないことか。
LPC810メモ:疑似正弦波を鳴らす
自由研究とかでありそうな「電子オルゴール」を作ってみたい。スイッチを押すと適当に音楽が鳴るおもちゃ。
その第一段階として、SCTによるPWMを用いた「擬似正弦波」の出力を試してみる。
前提とする環境はこちら→LPC810メモ 共通の準備
SCTなど基本的なサンプルについてはこちら→LPC810みっかぼ自作サンプル集
PWMで擬似的に正弦波を出力する理屈はいろんなサイトで解説されているが、自分はこのmbedのサンプル、「とりあえず使ってみる(音を出してみる)」を見て理解した。
理解したところをまとめると、以下のとおり。
- PWMにより高速にHIGH/LOWを切り替えると、擬似的にHIGH(1)とLOW(0)の中間の電圧を出力していると見なせる。
- → duty比、すなわちHIGHになる時間の割合を0%〜100%まで変化させることで、擬似的に0〜1の任意の電圧を出力できる。
- → 一定時間毎に、周波数に応じた角速度で角度を更新しつつ、sin関数の値に応じてdutyを設定すれば、ほぼ正弦波のような出力が可能。
ここまでが一般論として、実際にLPC810で実装する時に決めた点が以下。
- 角度表現は、浮動小数点を使ったらROM容量的に苦しいのは明らかなので、整数で処理する。
- 具体的には、円一周分を「65536単位角度」とする。通常言うところの「360度」や「2piラジアン」が、本サンプルでは「65536単位角度」に相当する。 uint16_tで管理すれば1周するとoverflowして0に戻るから便利。
- 同じくや標準sin関数が使えるとは思わないので、以下仕様の簡易的なものを独自で用意
- uint16_tで上記の「角度単位」で入力
- 出力は-1 .. 1ではなくて、-20..20の整数(int8_t)
- 実装は以下。1k単位角度(=1024単位角度)毎にハードコーディングされた値を返すだけ。
int8_t SctSoundUtil::sin(const uint16_t degree) { const uint16_t kiloDegree {static_cast<uint16_t>(degree / 1024)}; constexpr static uint8_t sinTable[] {0, 2, 4, 6, 8, 9, 11, 13, 14, 15, 17, 18, 18, 19, 20, 20, 20, 20, 20, 19, 18, 18, 17, 15, 14, 13, 11, 9, 8, 6, 4, 2}; return kiloDegree < 32 ? sinTable[kiloDegree] : -sinTable[kiloDegree - 32]; }
-
- この程度のいい加減なsin関数でも、グラフにすると十分にsin関数っぽい。自分程度の聴力ならこれ以上の精度は必要ないはず、と視覚的に納得しておく。
- SCT-PWMの設定について
- H/Lの2本で使う。
- HがPWM用。120カウントでリセットするので、12MHz / 120 = 100KHz。つまり、dutyを0..120の121段階で設定できる。
- Lは10KHzで割り込みを発生するタイマーとして使う。割り込み発生の度に、プログラムでdutyを設定する。
- 音階と角速度について
- 10KHzで割り込み発生するその一回分、すなわち1/10000秒を便宜上「単位時間」と呼ぶ。
- 本サンプルにおける角速度は、単位角度/単位時間。例えば、440Hzの音を出したければ、440Hz x 65536単位角度 / 10000単位時間 ≒ 2884、したがって、割り込み1回につき2884単位角度だけ加算していけば良い。
- 音階は半音毎に0..47の48段階で定義、4オクターブ分。ピアノの真ん中のドの1オクターブ下のド、を0とする。
- 音階毎の角速度も、以下のような感じであらかじめハードコーディングしておく。
uint16_t SctSoundUtil::toneAngularVelocity(const uint8_t tone) { constexpr static uint16_t toneTable[] {1618,1714,1816,1925,2039,2160,2289,2425,2569,2722,2884,3055, 3237,3429,3633,3850,4078,4321,4578,4850,5139,5444,5768,6111, 6474,6859,7267,7700,8157,8643,9156,9701,10278,10889,11537,12223, 12949,13719,14535,15400,16315,17286,18313,19402,20556,21779,23074,24446}; return toneTable[tone]; }
前置き終了、以下サンプル。
サンプルコード
main関数を含むsample.cppは最後。
SctSoundUtil.hとSctSoundUtil.cppは、今後使い回す都合上別ファイル。
SctSoundUtil.h
#ifndef SCTSOUNDUTIL_H_ #define SCTSOUNDUTIL_H_ #include "LPC8xx.h" class SctSoundUtil { public: static void init(); static void setCallback(void (*c)()) { callBack = c; }; static void setDuty(const uint8_t); // dutyを0..120の間で設定 static void sctEvent(); static void start(); static void stop(); // SCTと直接関係ないユーティリティ static int8_t sin(const uint16_t degree); static uint16_t toneAngularVelocity(const uint8_t tone); private: static void (*callBack)(); // 10KHzに一回呼び出されるコールバック }; #endif
SctSoundUtil.cpp
#include "SctSoundUtil.h" // SCTの割り込みハンドラ extern "C" { void SCT_IRQHandler(void) { SctSoundUtil::sctEvent(); } } void (*SctSoundUtil::callBack)(); void SctSoundUtil::sctEvent() { if(LPC_SCT->EVFLAG & (1 << 4)){ callBack(); LPC_SCT->EVFLAG = 1 << 4; } } void SctSoundUtil::init() { // SCT、8番pinに出力0番設定、RESET以外無効化、割り込み利用。詳細略。 LPC_SYSCON->SYSAHBCLKCTRL |= (1<<7); LPC_SWM->PINASSIGN6 = 0x00ffffffUL; LPC_SWM->PINENABLE0 = 0xffffffbfUL; LPC_SYSCON->SYSAHBCLKCTRL |= (1<<8); LPC_SYSCON->PRESETCTRL &= ~(0x1<<8); LPC_SYSCON->PRESETCTRL |= (0x1<<8); NVIC->ISER[0] = (0x1 << 9); // UM10601 10.6.1 SCT configuration register LPC_SCT->CONFIG = (0 << 0 ) // UNIFY = 0 -> 16bitのタイマ2本(L,H)で利用する | (1 << 17) | (1 << 18); // AUTOLIMIT_L = 1、AUTOLIMIT_H = 1 -> matchの0番(後で定義)を、カウントアップの上限とする // 以下、タイマL関連の設定 LPC_SCT->CTRL_L |= (1 << 4) | ((1 - 1) << 5); // BIDIR_L = 1 ->bidirectional mode、PRE_L = 1 -> 1クロックで1カウント。 // カウンタにmatchする値を定義 // UM10601 10.6.20 SCT match reload registers 0 to 4 (REGMODEn bit = 0) LPC_SCT->MATCHREL[0].L = 120; // clock 12MHzなので、100KHz相当 LPC_SCT->MATCHREL[1].L = 60; // 仮の値、duty設定時に更新される // event0番(match 0、LIMITに達した時に発生) // 10.6.22 SCT event state mask registers 0 to 5 // 10.6.23 SCT event control registers 0 to 5 LPC_SCT->EVENT[0].STATE = 0x01; // state0で発生 LPC_SCT->EVENT[0].CTRL = (0 << 0) | (0 << 4) | (1 << 12); // MATCHSEL = 0 -> match0に対応、HEVENT = 0 -> タイマLに対応、COMBMODE = 1 -> match だけに対応 // event1番(match 1、カウンタが0にもどって発生) LPC_SCT->EVENT[1].STATE = 0x01; // state0で発生 LPC_SCT->EVENT[1].CTRL = (1 << 0) | (0 << 4) | (1 << 12); // MATCHSEL = 1 -> match1に対応、HEVENT = 0 -> タイマLに対応、COMBMODE = 1 -> match だけに対応 // 出力0番 LPC_SCT->OUT[0].SET = (1 << 0); // event 0 でset LPC_SCT->OUT[0].CLR = (1 << 1); // event 1 でclear // 以下、タイマH関連の設定 LPC_SCT->CTRL_H |= (0 << 4) | (1 - 1 ) << 5; // BIDIR_L = 0 -> こちらはbidirectional modeにしない、PRE_L = 1クロックで1カウント // match0の設定 LPC_SCT->MATCHREL[0].H = 1200; // 10000Hz // event4(state0でmatch 0に達した時に発生、割り込みを起こす) LPC_SCT->EVENT[4].STATE = 0x01; // state0で発生 LPC_SCT->EVENT[4].CTRL = (0 << 0) | (1 << 4) | (1 << 12); // MATCHSEL = 0 -> match0に対応、HEVENT = 1 -> タイマHに対応、COMBMODE = 1 -> match だけに対応 // event4発生時には割り込みを発生させる。 // 10.6.14 SCT flag enable register LPC_SCT->EVEN = 1 << 4; } // dutyはL側のタイマーのmatch1として表現する。カウンタの都合上、0..120の範囲。 void SctSoundUtil::setDuty(const uint8_t duty) { LPC_SCT->MATCHREL[1].L = duty; } void SctSoundUtil::start() { LPC_SCT->CTRL_L &= ~(1 << 2); LPC_SCT->CTRL_H &= ~(1 << 2); } void SctSoundUtil::stop() { LPC_SCT->CTRL_L |= 1 << 2; LPC_SCT->CTRL_H |= 1 << 2; } // 荒いsin関数。定義域は0..65535だがこれが1周分(0..2π)に相当、値域は0..40で、-1..1に相当。 int8_t SctSoundUtil::sin(const uint16_t degree) { const uint16_t kiloDegree {static_cast<uint16_t>(degree / 1024)}; constexpr static uint8_t sinTable[] {0, 2, 4, 6, 8, 9, 11, 13, 14, 15, 17, 18, 18, 19, 20, 20, 20, 20, 20, 19, 18, 18, 17, 15, 14, 13, 11, 9, 8, 6, 4, 2}; return kiloDegree < 32 ? sinTable[kiloDegree] : -sinTable[kiloDegree - 32]; } // 247Hz(ピアノ中央の一個下のド)を0として、1増える度に半音上がる音の角速度を返す // 定義域は0..47、4オクターブ分。それ以上は問答無用で領域外アクセス。 // なお、sinと同様の角度表現(0..65535が0..2πに相当)で、1/10000秒あたりの角度を返す。 uint16_t SctSoundUtil::toneAngularVelocity(const uint8_t tone) { constexpr static uint16_t toneTable[] {1618,1714,1816,1925,2039,2160,2289,2425,2569,2722,2884,3055, 3237,3429,3633,3850,4078,4321,4578,4850,5139,5444,5768,6111, 6474,6859,7267,7700,8157,8643,9156,9701,10278,10889,11537,12223, 12949,13719,14535,15400,16315,17286,18313,19402,20556,21779,23074,24446}; return toneTable[tone]; }
sample.cpp
(2016/5/26 修正)
#include "LPC8xx.h" #include "SctSoundUtil.h" struct { uint8_t scale; // 音階(0 .. 47) uint16_t verocity; // 音階に対応する角速度 uint16_t angle; // 現在の角度 } tone{0,0,0}; // 初期値、全部0で初期化 static int count{0}; // 1/10000秒毎にカウントアップする static void handler() { // 1/10000秒毎に呼ばれるハンドラ if(count % 2500 == 0) { // 250ms毎に音階を更新 tone.verocity = SctSoundUtil::toneAngularVelocity(tone.scale); tone.scale++; if(tone.scale > 47) { tone.scale = 0; } // 一周したら戻す } count++; tone.angle += tone.verocity; // 角速度分進める SctSoundUtil::setDuty(SctSoundUtil::sin(tone.angle) * 3 + 20 * 3); // sin関数に従いduty決定 } int main(void) { SctSoundUtil::setCallback(handler); SctSoundUtil::init(); SctSoundUtil::start(); return 0; }
接続
補足/雑感
- 聞いてみても「おお、正弦波だ!!」とはならなかった。「正弦波」がどんな音なのか知らないから。
- それでも、この方式だと音を2つ加算して和音にしたり、振れ幅を小さくして音量を変更したりできるのが利点のはず。
LPC810メモ:C++11を使ってみる例
LPCXpressoでも設定すれば普通にC++11の文法が使えることに気付いたので実験。
というか、これまでできないと思っていて損していた模様。
- [Project][Property][C/C++Build][Settings]で[Tool Settings]タブ選択。
- [MCU C++ Compiler][Dialect]で、ISO C++1y(-std=c++1y)を選択
共通の準備にも追記。
とりあえず、むりやりラムダ式を使った例。内容的にはここと全く同じLチカ。
サンプルコード
#include "LPC8xx.h" int main(void) { // GPIOに対してclockを有効化 // UM10601 4.6.13 System clock control register LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6); // PIO0_0(すなわち8番のpin)をoutputに設定 // UM10601 27.1 Packages より、PIO0_0は8番 // UM10601 7.6.3 GPIO port direction registers LPC_GPIO_PORT->DIR0 |= (1<<0); // PIO0_0の出力をtoggleするラムダ // UM10601 7.6.9 GPIO port toggle registers auto ledToggle = [] { LPC_GPIO_PORT->NOT0 |= (1<<0); }; // 適当にループして間隔をあけるラムダ auto waitingLoop = [] (int l) { for(volatile int i{0}; i < l; i++) {} }; // 出力をon/offしてLEDを点滅させる while(1) { ledToggle(); waitingLoop(200000); } return 0 ; }
接続
ここと同じため省略。
英語オリジナルのライトノベルを読みたい
日本製のラノベの翻訳ではなく「もともと英語で書かれたラノベ」を読みたいと考え、以下のようにして探してみた。
これで見つけた小説を列挙してみる。なお、ライトノベルは英語でjuvenileだとかyoung adultだとか言う意見がある気もするが、実際にそれらのワードで検索してラノベっぽい小説が見つかったためしがないので、ストレートに"light novel"とした。
ただ、現時点ではほぼ中身はほぼ読んでないし、本当にラノベ(的な小説)かどうかは未知数。
Lily Clairet and the Romantic Non-Genre
表紙の絵的に、高確率でラノベだと思った。ただ、最初の方をちょっとだけ読んだが、あるいはラノベというより「青い鳥文庫」とか「つばさ文庫」が似合う話かもしれない。ちなみに、主人公(Lily)は日本の高校に通ってて、友達は日本人だ。
Felicia's Second Life
あらすじによれば、もとも29才の男だったのが、7才の女の子に転生するっぽい。「小説家になろう」でよくありそうな異世界転生ものか。
What is a light novel?
15ページしかないし、タイトルからして、英語圏の人に「ラノベとは何か?」を熱く語る論文かと思ったのだが、全然違った。女の子3人がラノベについて語り合う小説で、内容が「ナポレオンの人生はラノベ的か?」みたいな話に終始して興味を持てず、自分にはつまらなかった。
For Our Master
絵はさほどラノベって感じじゃないが、内容があまりにもラノベ的であり、サンプル読み終わる前に購入。
月面で学生をしていた主人公Julienは、父親の死を機に異星人に狙われるが、生前の父親に仕えていた戦闘力抜群の6人のメイドさんに守られながら暮らすことになる。なお、うち5人は実は異星人だが、基本的に見た目は人間の女性。冷徹でヤンデレっぽいリーダーLita、主人公にベタベタの幼なじみOctavia、フランクな猫娘のSammy、天使族だけどいい加減なEmma、実はどこかの星のお姫様である高飛車なIris、着物を着たクールキャラで読心術を使うKinoなど。まだ2割くらいしか読んでないが、雰囲気的に、多分6人とも主人公に惚れると思う。主人公は鈍感系で、まさにラノベという感じ。
Magical Girl Dallas
とある街を守るための魔法少女を増員することになったが、手違いで20才で男のDalasが魔法少女に指名されてしまい・・・みたいな話のようだ。なんか日本のラノベでもそういうの無かったか。
以下、サンプルもあらすじも読んでないもの。
A Girl Corrupted by the Internet is the Summoned Hero?!
Firestarter Yukawa
Yamaarashi no Kuiruzu: Saturn
Atlantis: The Visionary Continent
Crimson Destiny
LPC810みっかぼ自作サンプル集(GPIO、SCTからI2C、SPI通信まで)
3ヶ月間コツコツ作ってきたLPC810用のサンプルコードをまとめます。
LPC810の機能を試したいけれど、LPC Expressoなどに付いてるサンプルは処理が隠蔽されててよくわからない、という人の役には立つと思っています。
- 特長
以下、サンプルの目次です。
共通の準備
- 共通の準備
- 以降のサンプルコードを動かす前提となる開発環境の説明。
GPIO
- GPIO出力
- pinの出力のHIGH,LOWを切り替え。
- Pin interrupts
- pin入力の変化で割り込み発生。
- Pattern match engine
- 複数のpin入力のパターンで割り込み発生。
SCTimer(State Configuration timer)
- SCTimer/PWM (SCT)について
- SCTとは何か、できることのまとめ。
- SCTの小さい例(LED点滅)
- SCTを最低限使ってみる例。
- SCTの例その2(音を鳴らす)
- SCTの機能を組み合わせて複雑なことをしてみた例。
- PWMとして使い、音を鳴らす
- ボタン押下で状態変化し、音をon/offする
- 0.5秒毎に状態変化し、音の周波数を変える
- SCTの機能を組み合わせて複雑なことをしてみた例。
- SCTの例その3(超音波距離センサ)
- SCTのcapture機能を使ってみた例。超音波距離センサから信号が戻るタイミングをcaptureして距離を測る。
省電力モード
- deep power-downモード(wake up pinで復帰)
- 消費電力を最小限に抑えるdeep power-downモードに入って、そこからwake up pinの入力で復帰する例。
- deep power-downモード(Self wake-up timerで復帰)
- deep power-downモードから一定時間毎に復帰する例。
アナログコンパレータ
- アナログコンパレータ(比較結果をそのまま出力)
- 2つの入力pinの電圧を比較する。
- アナログコンパレータ(電圧ラダーの例)
- 1つの入力pinの電圧を、内部で設定する電圧(GNDからVddの間の32段階)と比較する。
I2C通信
- I2C-bus ROM API、I2C slaveとして動作させる(LPC810 2個使ってmaster-slave通信)
- I2C-bus ROM APIを利用し、I2C slaveとしてデータを受信する実験。master,slaveともにLPC810を使うので、LPC810が2個あれば試せる。
- I2C-bus ROM API、slave送信->master受信
- 同じく、I2C slaveとしてデータを送信(masterで受信)する実験。
- I2C-bus ROM API、同じLPC810でI2C slaveとmasterの両機能を使う
- 1つのLPC810を、master/slaveの両方として機能させる実験。