TakSan0 が 2026年01月31日23時16分14秒 に編集
初版
タイトルの変更
マウスで操作する楽器を作ってみた
タグの変更
SPRESENSE
楽器
コンテスト
HID
メイン画像の変更
記事種類の変更
製作品
本文の変更
# はじめに PCで使い慣れているマウス、PCを使う人ならとってもなじみのある道具です。人によっては一日で一番使う道具かもしれない。これを楽器にしたら、みんなお馴染みの道具で音楽を楽しめるのではないかと思いつきました。ネットでちょっと調べても、マウスを直接楽器にする様な事例は殆どありませんでしたので、作ってみました。 なお、末尾の謝辞の所に書いていますが、色々トラブルがあって、全体的にとても中途半端なものになってしまいました。申し訳ありません。詳しくは末尾にて。 # 構成図 [★作成中★] あとで入れないと ## 主な仕様デバイス ## SPRESENSE SPRESENSE は主に音を出す部分を担当させています。 内部には 楽器音を MIDI 感覚で簡単に再生できるライブラリである、SPRESENSE公式で用意されているライブラリ "ssprocLib" の仕組みを使っています。 ## SPRESENSE 拡張基板 3.3V 系のLCD や UART 用インタフェースと、アンプ接続先にもなっています。 ## LCDタッチスクリーン 最初はデバッグ用にしていましたが、音程をわかりやすく表示したり、モード切り替えたり、ボリューム操作するための UI として使いました。 ## RasPiPico W マウスを繋いでその信号を入力できる HIDホスト機能が、パット見た感じ SPRESENSE 標準では使え無さそうだったので、手持ちで余っているマイコンとして Rasberry Pi Pico W を使用し、 UART シリアルで SPRESENSE と接続して通信することでマウス信号を入力しています。 ただしラズパイ Picco の HID は不安定なのか、途中でよく固まり結構最後まで泣かされました(未解決です)。 ## D級アンプ SPRESENSE標準のヘッドホンアンプだけでは、音量不足であったため、手持ちで転がっていたD級アンプを使いました。ダイレクトでつなぐと爆音になったのでボリューム接続するのが良さそうです。 # マウス入力割り当て (UI) について マウスを楽器にとは決めたものの、入力が沢山あり割り当てによっては演奏しやすくもしにくくもなる為、最初にじっくり考え下記の様に割り当てました。 ## 左右移動 先ずマウスの一番の利点として、左右自在に動くところの自由度があるのが左右移動です。 必要に応じてゆっくり動かしたり早く動かしたりできる点です。ですので、先ず楽器の基本動作である音程を決めるところ、強さを決めるところをこの部分に持ってきました。 動かす速度を音量に割り当て、動かす方向を音階に割り当ててみました。 方向は 1オクターブが12諧調あるので12等分に割ります。丁度時計の数字と同じ数です。 意外とやってみると狭いので、モードを切り替え自分で選べるようにもしましたが、この難易度に慣れるまで練習するのも面白いかもしれません。 モードは 以下の様にしています - フルモード:12等分 - 半音無しモード: ピアノでいう所のいわゆる半音 # / b の部分を省いて7当分にしたモード - イージーモード:さらによく使う音階を90 度単位の所に割り当てて調整したモード ## ホイールスクロール移動 入力として上下スクロールなので、感覚的に上下切り替えというイメージがあるので、オクターブ 切り替えに持ってきました。 ## ボタン 普通のマウスにはボタンは3つあります。 ボタンはやはり一番入力として扱いやすく反応しやすい部分でもありますので、直感的にも on /off を制御するのがいいのかと思い、音の on/off 制御に持ってきました。 ### 左ボタン 左ボタンがみんなやはり一番慣れているキーですので、基本中の基本である 音のオン/オフに割り当てました。 ### 右ボタン 色々と試行錯誤していくうちに、和音が出したくなったため右ボタンを割り当ててみました。丁度ピアノのサスティンペダルの様に、右を押している間演奏した音をキープします。 # 実装 ## ハードウェア ### 必要部品 ほぼコンテストにて提供いただいた品となりますが、以下の通りです。 |名称|品名/品番|スペック/説明|備考| |---|---|---|---| |メイン基板|CXD5602PWBMAIN1|SPRESENSE本体ボード|コンテストで提供いただいたもの| |拡張基板|CXD5602PWBEXT1|SPRESENSE拡張用ボード|コンテストで提供いただいたもの| |操作パネル|MSP2807|タッチパネル付き液晶|コンテストで提供いただいたもの| |RasberryPiPicoW|| USB HOST として| | |DQアンプ|GETPRE-1||| |3.5mm ミニモノラルジャック|||| ### 接続 接続は下記の通りです。 |拡張基板 Pin|LCD Pin|信号名|備考| |---|---|---|---| |D03|T_IRQ|タッチセンサ割り込み|未使用| |D04|T_DO|タッチセンサMOSI|SoftSPI| |D05|T_DIN|タッチセンサMISO|SoftSPI| |D06|T_CS|タッチセンサチップセレクト|SoftSPI| |D07|T_CLK|タッチセンサクロック|| |D08|RESET|液晶モニタリセット|| |D09|DC|液晶モニタDC信号|| |SPI4_CS_X|CS|液晶モニタチップセレクト|| |SPI4_MOSI|SDI(MOSI)|液晶モニタMOSI|HW SPI (SPI4)| |SPI4_MISO|SDO(MISO)|液晶モニタMISO|HW SPI (SPI4)| |SPI4_SCK|SCK|液晶モニタクロック|HW SPI (SPI4)| |GND|GND|GROUND|| |AREF|LED,VCC|POWER|3.3V| ## ソフトウェア ### SPRESENSE 用 #### 環境他 開発環境 : Arduino IDE ライブラリ : - LovyanGFX (SPRESENSE用) - SoftSPI (seeed) #### ソース ```arduino: MusicInstrumentWithMouse.ino // -----=====<<<<<[[[[[ Include ]]]]]>>>>>=====----- #include <arch/board/board.h> #include "LGFX_SPRESENSE.hpp" #include "SoftSPI.h" #include <pthread.h> #include <SDSink.h> //#include <SFZSink.h> //#include <OneKeySynthesizerFilter.h> #include <YuruhornSrc.h> //#include "ScoreFilter.h" //#include <SFZSink.h> #include "SimpleQueue.h" #include "SysLog.h" // -----=====<<<<<[[[[[ Define ]]]]]>>>>>=====----- #define PIN_TOUCH_MISO 4 //!< $EN{{Pin number for SoftSPI MISO}}$JP{{SoftSPI ピン番号 : MISO}} #define PIN_TOUCH_MOSI 5 //!< $EN{{Pin number for SoftSPI MOSI}}$JP{{SoftSPI ピン番号 : MOSI}} #define PIN_TOUCH_CLK 7 //!< $EN{{Pin number for SoftSPI CLK}}$JP{{SoftSPI ピン番号 : CLK}} #define PIN_TOUCH_CS 6 //!< $EN{{Pin number for SoftSPI CS}}$JP{{SoftSPI ピン番号 : CS}} #define PIN_TOUCH_IRQ 2 //!< $EN{{Pin number for SoftSPI IRQ (unused)}}$JP{{SoftSPI ピン番号 : IRQ(未使用)}} // TouchControllerレジスタ定義 #define REG_TOUCH_RDX 0xD0u //!< RDX #define REG_TOUCH_RDY 0x90u //!< RDY #define REG_TOUCH_RDZ1 0xB8u //!< RDZ1 #define REG_TOUCH_RDZ2 0xC8u //!< RDZ1 // Touch Callibration part uint16_t calibration_pos = __LINE__; // vvvvvvvvvv Touch Callibration ~ begin vvvvvvvvvv #define TOUCH_MIN_X 228 #define TOUCH_MAX_X 3947 #define TOUCH_MIN_Y 332 #define TOUCH_MAX_Y 3917 // ^^^^^^^^^^ Touch Callibration ~ end ^^^^^^^^^^ #define INVALID_TOUCH_VAL 8191 #define TOUCH_MIN_SIZE_X_Y 2000 #define UART_QUEUE_BUFFER_SIZE 0x100 #define CMD_ID_LEN (5) #define CMD_BTN_SEL_POS (CMD_ID_LEN) #define CMD_BTN_SEL_LEN (1) #define CMD_BTN_UPDOWN_POS (CMD_BTN_SEL_POS + CMD_BTN_SEL_LEN + 1) #define CMD_BTN_UPDOWN_LEN (2) #define CMD_BTN_LEN (CMD_BTN_UPDOWN_POS + CMD_BTN_UPDOWN_LEN) #define CMD_DIR_POS (CMD_ID_LEN) #define CMD_DIR_MIN_LEN (3) #define CMD_WHL_POS (CMD_ID_LEN) #define CMD_WHL_MIN_LEN (1) #define ARRAY_SIZEOF(x) (sizeof(x) / sizeof(x[0])) #define DEADLINE_FOR_SOUND_DMA_MS 4 #define PLAY_DATA_POS_COUNT 4 #define MIN_OCTAVE (-1) #define MAX_OCTAVE (8) // 描画関連 #define VOL_AREA_HEIGHT 20 #define VOL_AREA_WIDTH 240 #define VOL_SLIDER_LEFT 20 // 動作範囲の左端 X #define VOL_SLIDER_RIGHT 220 // 動作範囲の右端 X #define VOL_SLIDER_WIDTH (VOL_SLIDER_RIGHT - VOL_SLIDER_LEFT) // 200dot #define VOL_KNOB_WIDTH 40 // つまみの横幅 #define VOL_KNOB_HEIGHT 20 // つまみの縦幅 #define GAUGE_CENTER_X 120 #define GAUGE_CENTER_Y 140 #define GAUGE_AREA_RADIUS 118 #define GAUGE_OUTER_RADIUS 110 #define GAUGE_INNER_RADIUS 80 #define GAUGE_INDICATOR_LEN 75 #define NOTE_LANG_LEFT (SCREEN_WIDTH - NOTE_LANG_WIDTH) #define NOTE_LANG_TOP (260-10-18) #define NOTE_LANG_WIDTH ((4*8)+2) #define NOTE_LANG_HEIGHT (16+2) #define SPECIAL_FONT_WIDTH 8 #define SPECIAL_FONT_HEIGHT 16 #define SPECIAL_FONT_INVALID_IDX (7) #define NO_THREAD_FOR_SOUND // @ToDo 何故かThreadから呼ぶと問題となるので暫定処理を埋め込む (loop内から呼び出し)余裕あれば調査する //#define SIMPLE_TEST_WITH_BTN // ボタンの押/離 だけで ON/OFF する単純テストモード #define USE_BTN_TRIGGER_FOR_MOV //#define ALIGN_TOUCH_MODE // タッチパネルのキャリブレーションをしたい時だけ有効化 #define SCREEN_WIDTH 240 // 画面の横幅(ピクセル) #define SCREEN_HEIGHT 320 // 画面の高さ(ピクセル) // -----=====<<<<<[[[[[ Types ]]]]]>>>>>=====----- typedef char serial_queue_data_t; typedef enum // オクターブ分割方法 { e_dir_divider_full12 = 0, // 半音ずつ12分割 e_dir_divider_nonblack, // 黒鍵無し等間隔 e_dir_divider_easy7, // 簡単7分割 e_dir_divider_count } e_dir_divider_t; typedef enum // 音階表記 { e_note_langu_english = 0, e_note_langu_japanease, e_note_langu_count } e_note_langu_t; typedef struct { int id; struct { int x1; int y1; int x2; int y2; } area; void (*call_func)(int id, int x, int y); } s_touch_pos_inf_t; typedef struct { uint16_t min_deg; uint16_t max_deg; uint8_t note_index; } st_dir_divider_t; typedef struct { st_dir_divider_t* table; uint8_t size; } st_dir_divider_tables_t; typedef enum { e_vel_level_silent = 0, e_vel_level_small, e_vel_level_mid, e_vel_level_laud, e_vel_level_count } e_vel_level_t; typedef struct { bool is_on; uint8_t tone; e_vel_level_t velocity; uint32_t start_time; uint32_t period; bool indicator_valid; // このスロットにインジケータを出すか int16_t indicator_x; // 終点 X(中心からの直線の終点) int16_t indicator_y; // 終点 Y uint16_t indicator_color; // 線の色 } st_play_data_t; // -----=====<<<<<[[[[[ Variable ]]]]]>>>>>=====----- // [OS Items] pthread_t sound_thread; pthread_t uart_thread; pthread_t screen_thread; pthread_mutex_t mutex_for_uart_queue; pthread_mutex_t mutex_for_log; static LGFX lcd; SoftSPI touch_spi(PIN_TOUCH_MOSI, PIN_TOUCH_MISO, PIN_TOUCH_CLK); SimpleQueue<serial_queue_data_t,UART_QUEUE_BUFFER_SIZE> UartQueue; int8_t current_octave = 4; e_note_langu_t current_note_langu = e_note_langu_japanease;//e_note_langu_english; e_dir_divider_t current_divider = e_dir_divider_full12; int8_t current_volume = 70; st_play_data_t play_data[PLAY_DATA_POS_COUNT]; uint8_t play_data_pos = 0; bool right_sustain_pressed = false; // 右ボタン=サステイン // $MOV 積算用 uint32_t last_mov_time = 0; // ※ 今は使わないが残しておく int accum_x = 0; int accum_y = 0; bool mov_pending = false; // ※ 今は使わないが残しておく // $IDL 区間管理 bool idl_mode = false; // IDL: 受信後の区間中か? bool idl_has_mov = false; // IDL: 以降に MOV を受けたか? bool idl_started_by_btn = false; // 直前に受け取ったコマンドが IDL だったかどうか bool last_was_idl = false; volatile float indicator_angle_deg = 0.0f; // 0°=上, 時計回り volatile e_vel_level_t indicator_level = e_vel_level_silent; // 音量レベル volatile bool indicator_confirmed = false; // -----=====<<<<<[[[[[ Prototype ]]]]]>>>>>=====----- void setup(); void loop(); void uartThreadFunction(void *arg); void soundThreadFunction(void *arg); void screenThreadFunction(void *arg); bool parseAndExecuteContolCommand(String cmd); void clearPlayData(st_play_data_t* p); void startNewPlay(uint8_t midi_note, e_vel_level_t level); void checkIfStopAnyNote(void); void process_idl_interval(void); void drawScreen(); int findSpecializedFontIndex(uint16_t code); void drawspecializedFont(int x, int y, uint16_t code, uint16_t font_col, uint16_t back_col = TFT_BLACK); void drawSpecializedString(int x, int y, String str, uint16_t font_col, uint16_t back_col = TFT_BLACK, int setTextDatum = TL_DATUM ); void drawVolumeBar(); void drawGaugeBase(); void drawGaugeIndicator(); void drawNoteLanguage(); bool readTouchScreen(int &x, int &y); void changeVolume(int id, int x, int y); void changeCurrentDivider(int id, int x, int y); void changeNoteLanguage(); // -----=====<<<<<[[[[[ Tables ]]]]]>>>>>=====----- extern "C" { extern uint8_t specialized_font8x16_code_table[]; extern const uint16_t specialized_font8x16_count; extern const uint8_t specialized_font8x16[][16]; } const SDSink::Item inst_table[] = { #include "InstTable.h" }; SDSink sink(inst_table, ARRAY_SIZEOF(inst_table)); YuruhornSrc inst(sink); /* 1. 半音 12 分割(full12) */ st_dir_divider_t dir_divider_table_full12[] = { {345, 360, 0}, // C (後半) {0, 15, 0}, // C (前半) {15, 45, 1}, // C# {45, 75, 2}, // D {75, 105, 3}, // D# {105, 135, 4}, // E {135, 165, 5}, // F {165, 195, 6}, // F# {195, 225, 7}, // G {225, 255, 8}, // G# {255, 285, 9}, // A {285, 315, 10}, // A# {315, 345, 11}, // B }; /* 2. 黒鍵なし 7 分割(nonblack) */ st_dir_divider_t dir_divider_table_nonblack[] = { {334, 360, 0}, // C (後半) {0, 26, 0}, // C (前半) {26, 77, 2}, // D {77, 129, 4}, // E {129, 180, 5}, // F {180, 232, 7}, // G {232, 283, 9}, // A {283, 334, 11}, // B }; /* 3. 簡易 7 分割(easy7) */ st_dir_divider_t dir_divider_table_easy7[] = { {330, 360, 0}, // C (後半) {0, 22, 0}, // C (前半) {22, 67, 2}, // D {67, 112, 4}, // E {112, 157, 5}, // F {157, 210, 7}, // G {210, 270, 9}, // A {270, 330, 11}, // B }; const uint16_t move_amount_to_vel_lvel_threshold_tbl[e_vel_level_count] = { 0, 50, 155, 200 }; const uint8_t e_vel_level_to_velocity_tbl[e_vel_level_count] = { 0, 32, 64, 81 }; const uint32_t play_period_tbl[e_vel_level_count] = { 0, 100, 200, 500 }; const uint16_t e_vel_level_to_color_tbl[e_vel_level_count] = { TFT_BLUE, // e_vel_level_silent TFT_GREEN, // e_vel_level_small TFT_YELLOW, // e_vel_level_mid TFT_RED // e_vel_level_laud }; const char* index_to_note_str[e_note_langu_count][12] = { {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}, {"\xC4\xDE", /* ド */ "\xC4\xDE#", /* ド# */ "\xDA", /* レ */ "\xDA#", /* レ# */ "\xD0", /* ミ */ "\xCC\xA7", /* ファ */ "\xCC\xA7#", /* ファ# */ "\xBF", /* ソ */ "\xBF#", /* ソ# */ "\xD7", /* ラ */ "\xD7#", /* ラ# */ "\xBC" /* シ */ } }; const char* note_lang_str[e_note_langu_count] = {"CDE ", "\xC4\xDE\xDA\xD0" /* ドレミ */}; #if 0 {0xC4, 0xDE, 0x00}, /* ド */ {0xDA, 0x00}, /* レ */ {0xD0, 0x00}, /* ミ */ {0xCC, 0xA7, 0x00}, /* ファ */ {0xBF, 0x00}, /* ソ */ {0xD7, 0x00}, /* ラ */ {0xBC, 0x00}, /* シ */ #endif const char level_char[e_vel_level_count] = {'_', 'S', 'M', 'L'}; /* ---- 統合テーブル ---- */ st_dir_divider_tables_t dir_divider_tables[e_dir_divider_count] = { { dir_divider_table_full12, ARRAY_SIZEOF(dir_divider_table_full12) }, { dir_divider_table_nonblack, ARRAY_SIZEOF(dir_divider_table_nonblack) }, { dir_divider_table_easy7, ARRAY_SIZEOF(dir_divider_table_easy7) } }; s_touch_pos_inf_t touch_pos_inf_table[] = { { 0, { VOL_SLIDER_LEFT, 0, VOL_SLIDER_RIGHT, VOL_KNOB_HEIGHT }, changeVolume }, { 1, { GAUGE_CENTER_X - GAUGE_INNER_RADIUS, GAUGE_CENTER_Y - GAUGE_INNER_RADIUS, GAUGE_CENTER_X + GAUGE_INNER_RADIUS, GAUGE_CENTER_Y + GAUGE_INNER_RADIUS }, changeCurrentDivider }, { 2, { NOTE_LANG_LEFT, NOTE_LANG_TOP, NOTE_LANG_LEFT + NOTE_LANG_WIDTH - 1, NOTE_LANG_TOP + NOTE_LANG_HEIGHT - 1 }, changeNoteLanguage }, }; // -----=====<<<<<[[[[[ Functions ]]]]]>>>>>=====----- void setup() { Serial.begin(115200); delay(500); Serial.printf("\r\n\r\n\r\n"); if (pthread_mutex_init(&mutex_for_log, NULL) != 0) { Serial.printf("Error; pthread_mutex_init(mutex_for_log).\n"); exit(1); } // 以降ログマクロ使用可能 if (pthread_mutex_init(&mutex_for_uart_queue, NULL) != 0) { Serial.printf("Error; pthread_mutex_init(mutex_for_uart_queue).\n"); exit(1); } #if !defined(ALIGN_TOUCH_MODE) inst.setParam(SDSink::PARAMID_LOOP, true); if (!inst.begin()) { Serial.println("ERROR: inst.begin failed"); while (1) { delay(1000); } } /* Starting threads*/ INF_LOG("Starting Threads"); pthread_attr_t attr; pthread_attr_init(&attr); struct sched_param sch; sch.sched_priority = sched_get_priority_min(SCHED_FIFO); pthread_attr_setschedpolicy(&attr, SCHED_FIFO); pthread_attr_setschedparam(&attr, &sch); #if !defined(NO_THREAD_FOR_SOUND) pthread_create(&sound_thread, NULL, soundThreadFunction, NULL); #endif // !defined(NO_THREAD_FOR_SOUND) pthread_create(&uart_thread, NULL, uartThreadFunction, NULL); pthread_create(&screen_thread, &attr, screenThreadFunction, NULL); pthread_attr_destroy(&attr); #else // !defined(ALIGN_TOUCH_MODE) pthread_create(&screen_thread, NULL, screenThreadFunction, NULL); #endif // !defined(ALIGN_TOUCH_MODE) INF_LOG("Setup() DONE!"); } void loop() { // タッチキャリブレーション時には動かさない #if !defined(ALIGN_TOUCH_MODE) #if defined(NO_THREAD_FOR_SOUND) soundThreadFunction(nullptr); #endif // defined(NO_THREAD_FOR_SOUND) #else // !defined(ALIGN_TOUCH_MODE) //usleep(500 * 1000); #endif // !defined(ALIGN_TOUCH_MODE) } void uartThreadFunction(void *arg) { // [Initialize UART Thread] Serial2.begin(115200); delay(500); // [Enter Main Loop of the UI Thread] INF_LOG("UI Thread Initialized!"); while(1) { serial_queue_data_t code; while (Serial2.available() > 0) { code = (serial_queue_data_t)Serial2.read(); pthread_mutex_lock(&mutex_for_uart_queue); UartQueue.Put(&code); pthread_mutex_unlock(&mutex_for_uart_queue); } usleep(5 * 1000); // $EN{{DO NOT not use "delay(msec)"}}$JP{{delay(msec)関数は使わない事}} } } void soundThreadFunction(void *arg) { // [Initialize Sound Thread] uint32_t last_check_time =0; int bufpos = 0; serial_queue_data_t code; static char ctrl_cmd_str[80]; for (int i = 0; i < PLAY_DATA_POS_COUNT; i++) { clearPlayData(&play_data[i]); } play_data_pos = 0; INF_LOG("Sound Thread Initialized!"); // [Enter Main Loop of the Sound Thread] while(1) { last_check_time = millis(); while ( true ) { bool data_exist_in_queue; if ( ( (millis() - last_check_time) ) >= DEADLINE_FOR_SOUND_DMA_MS) break; pthread_mutex_lock(&mutex_for_uart_queue); data_exist_in_queue = UartQueue.Get(&code); pthread_mutex_unlock(&mutex_for_uart_queue); if ( false == data_exist_in_queue) break; switch(code) { case '\r': ctrl_cmd_str[bufpos] = '\0'; break; case '\n': ctrl_cmd_str[bufpos] = '\0'; if ( 0 != strlen(ctrl_cmd_str)) { parseAndExecuteContolCommand(ctrl_cmd_str); // INF_LOG("Exec>%s", ctrl_cmd_str); } bufpos = 0; break; default: ctrl_cmd_str[bufpos] = code; if (bufpos >= sizeof(ctrl_cmd_str)-1) { ERR_LOG("Control Comand Buffer Overflow @UART(%d)", bufpos); } else bufpos++; break; } } checkIfStopAnyNote(); inst.update(); usleep(1 * 1000); // $EN{{DO NOT not use "delay(msec)"}}$JP{{delay(msec)関数は使わない事}} } } void screenThreadFunction(void *arg) { // [Initialize Screen Thread] // ディスプレイコントローラの初期化 lcd.init(); lcd.startWrite(); lcd.clear(TFT_BLACK); lcd.setSwapBytes(true); // タッチスクリーンの初期化 pinMode(PIN_TOUCH_IRQ, INPUT); pinMode(PIN_TOUCH_CS, OUTPUT); digitalWrite(PIN_TOUCH_CS, HIGH); touch_spi.begin(); touch_spi.setClockDivider(SPI_CLOCK_DIV4); touch_spi.setBitOrder(MSBFIRST); touch_spi.setDataMode(SPI_MODE0); #if !defined(ALIGN_TOUCH_MODE) // 基本画面描画 drawScreen(); INF_LOG("Screen Thread Initialized!"); // [Enter Main Loop of the Screen Thread] while(1) { int x, y; if (readTouchScreen(x, y) == true) { DBG_LOG("Touched(%d,%d)", x, y); for (int i = 0; i < ARRAY_SIZEOF(touch_pos_inf_table); i++) { if ( (touch_pos_inf_table[i].area.x1 <= x ) && ( x <= touch_pos_inf_table[i].area.x2 ) && (touch_pos_inf_table[i].area.y1 <= y ) && ( y <= touch_pos_inf_table[i].area.y2 ) ) { touch_pos_inf_table[i].call_func(touch_pos_inf_table[i].id, x, y); INF_LOG("Touch function [%d] called! (%d, %d, %d)", i, touch_pos_inf_table[i].id, x, y); usleep(500 * 1000); } } } drawGaugeIndicator(); usleep(5 * 1000); } #else // !defined(ALIGN_TOUCH_MODE) // キャリブレーションモードの処理 bool is_invalid_condition = true; // --- まず青画面で「初回タッチ待ち」 --- INF_LOG("Waiting for the first touch to start Calibration!"); lcd.clear(TFT_BLUE); lcd.setTextColor(TFT_WHITE, TFT_BLUE); lcd.setTextSize(2); lcd.setCursor(4, 4); lcd.printf("Touch anyware to start!"); // 初回タッチ待ち while (1) { int rx, ry; if (readTouchScreen(rx, ry)) { break; // 1回でもタッチされたら抜ける } usleep(5 * 1000); } // --- 初回タッチを検出したので緑画面に切り替え、キャリブレーション開始 --- INF_LOG("Touch screen Calibration Started!"); lcd.clear(TFT_GREEN); uint16_t min_x = 65535, max_x = 0; uint16_t min_y = 65535, max_y = 0; bool touched = false; uint32_t last_touched_time = millis(); lcd.setTextColor(TFT_BLACK, TFT_GREEN); lcd.setTextSize(2); while (1) { int rx, ry; touched = readTouchScreen(rx, ry); if (touched) { //touching = true; is_invalid_condition = false; if (rx < min_x) min_x = rx; if (rx > max_x) max_x = rx; if (ry < min_y) min_y = ry; if (ry > max_y) max_y = ry; if ( min_x > max_x ) is_invalid_condition = true; else if ( (max_x - min_x) < TOUCH_MIN_SIZE_X_Y ) is_invalid_condition = true; if ( min_y > max_y ) is_invalid_condition = true; else if ( (max_y - min_y) < TOUCH_MIN_SIZE_X_Y ) is_invalid_condition = true; last_touched_time = millis(); } else { //if (touching) { //touching = false; // = millis(); //} } // リアルタイム表示(緑背景に白文字) lcd.fillRect(0, 0, SCREEN_WIDTH, 64, TFT_GREEN); lcd.setCursor(4, 4); lcd.printf("MIN_X: %5u", min_x); lcd.setCursor(4, 20); lcd.printf("MAX_X: %5u", max_x); lcd.setCursor(4, 36); lcd.printf("MIN_Y: %5u", min_y); lcd.setCursor(4, 52); lcd.printf("MAX_Y: %5u", max_y); // --- 手を離して無効条件じゃなくて 5 秒経過 → キャリブレーション確定 --- if ( (!touched) && (!is_invalid_condition) && ((millis() - last_touched_time) >= 5000) ) { lcd.clear(TFT_RED); lcd.setTextColor(TFT_BLUE, TFT_RED); lcd.setTextSize(2); lcd.setCursor(4, 4); lcd.printf("Touch Calibration DONE!"); Serial.printf("Replace : Line = %d~%d code with calibrated code shown below!!!", calibration_pos + 1, calibration_pos + 6); Serial.println(); Serial.printf("// vvvvvvvvvv Touch Callibration ~ begin vvvvvvvvvv\n"); Serial.printf("#define TOUCH_MIN_X %u\n", min_x); Serial.printf("#define TOUCH_MAX_X %u\n", max_x); Serial.printf("#define TOUCH_MIN_Y %u\n", min_y); Serial.printf("#define TOUCH_MAX_Y %u\n", max_y); Serial.printf("// ^^^^^^^^^^ Touch Callibration ~ end ^^^^^^^^^^\n"); INF_LOG("Touch screen Calibration Terminated!"); // リセットされないように無限ループ while (1) { usleep(1000 * 1000); } } usleep(5 * 1000); } #endif // !defined(ALIGN_TOUCH_MODE) } bool parseAndExecuteContolCommand(String cmd) { bool ret = false; int len = cmd.length(); if (len >= CMD_ID_LEN) { // ----- BTN ----- if ( cmd.substring(0, CMD_ID_LEN) == "$BTN:" ) { last_was_idl = false; if (len >= CMD_BTN_LEN) { String btn = cmd.substring(CMD_BTN_SEL_POS, CMD_BTN_SEL_POS + CMD_BTN_SEL_LEN); String up_down = cmd.substring(CMD_BTN_UPDOWN_POS, CMD_BTN_UPDOWN_POS + CMD_BTN_UPDOWN_LEN); INF_LOG("BTN('%s','%s')!", btn.c_str(), up_down.c_str()); #if !defined(SIMPLE_TEST_WITH_BTN) #if defined(USE_BTN_TRIGGER_FOR_MOV) // 左ボタン:IDL 区間開始/終了 if (btn == "L") { if (up_down == "DW") { idl_mode = true; idl_has_mov = false; accum_x = 0; accum_y = 0; idl_started_by_btn = true; ret = true; } else if (up_down == "UP") { if (idl_mode && idl_has_mov) { process_idl_interval(); } idl_mode = false; idl_has_mov = false; accum_x = 0; accum_y = 0; idl_started_by_btn = false; ret = true; } } #endif // defined(USE_BTN_TRIGGER_FOR_MOV) // 真ん中ボタン:空処理(何もしない) if (btn == "M") { ret = true; } // 右ボタン:サステイン if (btn == "R") { if (up_down == "DW") { right_sustain_pressed = true; ret = true; } else if (up_down == "UP") { right_sustain_pressed = false; ret = true; } } #else // !defined(SIMPLE_TEST_WITH_BTN) #if defined(USE_BTN_TRIGGER_FOR_MOV) // SIMPLE_TEST + BTN トリガーモード if (btn == "L") { if (up_down == "DW") { idl_mode = true; idl_has_mov = false; accum_x = 0; accum_y = 0; idl_started_by_btn = true; ret = true; } else if (up_down == "UP") { if (idl_mode && idl_has_mov) { process_idl_interval(); } idl_mode = false; idl_has_mov = false; accum_x = 0; accum_y = 0; idl_started_by_btn = false; ret = true; } } #endif // defined(USE_BTN_TRIGGER_FOR_MOV) // 真ん中ボタン:空処理 if (btn == "M") { ret = true; } // 右ボタン:サステイン if (btn == "R") { if (up_down == "DW") { right_sustain_pressed = true; ret = true; } else if (up_down == "UP") { right_sustain_pressed = false; ret = true; } } #endif // !defined(SIMPLE_TEST_WITH_BTN) } } // ----- IDL ----- else if ( cmd.substring(0,CMD_ID_LEN) == "$IDL:" ) { #if defined(USE_BTN_TRIGGER_FOR_MOV) INF_LOG("IDL ignored (BTN trigger mode)"); ret = true; #else if (last_was_idl) { ERR_LOG("Idle dobuled!"); ret = true; } else { if (idl_mode && idl_has_mov) { process_idl_interval(); idl_mode = false; idl_has_mov = false; accum_x = 0; accum_y = 0; last_was_idl = true; ret = true; } else { idl_mode = true; idl_has_mov = false; accum_x = 0; accum_y = 0; last_was_idl = true; ret = true; } } #endif } // ----- MOV ----- else if ( cmd.substring(0,CMD_ID_LEN) == "$MOV:" ) { last_was_idl = false; int x, y; if (len >= (CMD_DIR_POS + CMD_DIR_MIN_LEN) ) { sscanf(cmd.substring(CMD_DIR_POS, len).c_str(), "%d,%d", &x, &y); if (idl_mode) { accum_x += x; accum_y += y; idl_has_mov = true; // ここで「現状の積算方向」をリアルタイム更新 float raw_deg = atan2((float)(-accum_y), (float)accum_x) * 180.0f / PI; float deg = 90.0f - raw_deg; if (deg < 0) deg += 360.0f; if (deg >= 360.0f) deg -= 360.0f; float move_amount = sqrt((float)accum_x * accum_x + (float)accum_y * accum_y); e_vel_level_t level = e_vel_level_silent; for (int i = e_vel_level_count - 1; i >= 0; i--) { if (move_amount >= move_amount_to_vel_lvel_threshold_tbl[i]) { level = (e_vel_level_t)i; break; } } indicator_angle_deg = deg; indicator_level = level; indicator_confirmed = false; // 未確定なので点線 } else { // IDL 区間外ならインジケータは消音扱い indicator_level = e_vel_level_silent; indicator_confirmed = false; } ret = true; } } // ----- WHL ----- else if ( cmd.substring(0,CMD_ID_LEN) == "$WHL:" ) { last_was_idl = false; int z; if (len >= (CMD_WHL_POS + CMD_WHL_MIN_LEN) ) { int8_t new_octave; sscanf(cmd.substring(CMD_DIR_POS, len).c_str(), "%d", &z); new_octave = current_octave + z; if (new_octave < MIN_OCTAVE) new_octave = MIN_OCTAVE; if (new_octave > MAX_OCTAVE) new_octave = MAX_OCTAVE; INF_LOG("Octave: %d > %d", current_octave, new_octave); current_octave = new_octave; ret = true; } } } checkIfStopAnyNote(); return(ret); } void clearPlayData(st_play_data_t* p) { p->is_on = false; p->tone = 0; p->velocity = e_vel_level_silent; p->start_time = 0; p->period = 0; p->indicator_valid = false; p->indicator_x = 0; p->indicator_y = 0; p->indicator_color = TFT_BLUE; } void startNewPlay(uint8_t midi_note, e_vel_level_t level) { st_play_data_t* p = &play_data[play_data_pos]; // 既に使用中なら停止して初期化 if (p->is_on) { inst.sendNoteOff(p->tone, DEFAULT_VELOCITY, DEFAULT_CHANNEL); WRN_LOG("Stop exist and play"); clearPlayData(p); } // silent は鳴らさない if (level == e_vel_level_silent) { clearPlayData(p); play_data_pos = (play_data_pos + 1) % PLAY_DATA_POS_COUNT; ERR_LOG("Is Silent Level"); return; } // 新規設定 clearPlayData(p); p->is_on = true; p->tone = midi_note; p->velocity = level; p->start_time = millis(); p->period = play_period_tbl[level]; // ここでインジケータ終点座標と色を決定して保存 { float rad = indicator_angle_deg * PI / 180.0f; int ex = GAUGE_CENTER_X + (int)(GAUGE_INDICATOR_LEN * sin(rad)); int ey = GAUGE_CENTER_Y - (int)(GAUGE_INDICATOR_LEN * cos(rad)); p->indicator_x = (int16_t)ex; p->indicator_y = (int16_t)ey; p->indicator_color = e_vel_level_to_color_tbl[level]; p->indicator_valid = true; } // 再生 inst.sendNoteOn(midi_note, (((long)e_vel_level_to_velocity_tbl[level] * (long)current_volume) / 100), DEFAULT_CHANNEL); //DBG_LOG("Volume debug : lv=%d, current_volume=%d, val=%d", level, current_volume, (((long)e_vel_level_to_velocity_tbl[level] * (long)current_volume) / 100) ); // 次の位置へ play_data_pos = (play_data_pos + 1) % PLAY_DATA_POS_COUNT; } void checkIfStopAnyNote(void) { uint32_t now = millis(); for (int i = 0; i < PLAY_DATA_POS_COUNT; i++) { st_play_data_t* p = &play_data[i]; if (!p->is_on) continue; // 右ボタン押下中は停止しない(サステイン) if (right_sustain_pressed) continue; if (p->period > 0 && (now - p->start_time) >= p->period) { inst.sendNoteOff(p->tone, DEFAULT_VELOCITY, DEFAULT_CHANNEL); clearPlayData(p); } } } // -----=====<<<<<[[[[[ IDL 区間の角度→ノート変換&再生 ]]]]]>>>>>===== void process_idl_interval(void) { // MOV が一度も来ていなければ何もしない if (!idl_has_mov) { ERR_LOG("NO $MOV!"); return; } // 角度計算(0°=上、時計回り) float raw_deg = atan2((float)(-accum_y), (float)accum_x) * 180.0f / PI; // -180〜180 float deg = 90.0f - raw_deg; if (deg < 0) deg += 360.0f; if (deg >= 360.0f) deg -= 360.0f; uint16_t angle = (uint16_t)deg; // 使用するテーブルを取得 st_dir_divider_tables_t* tbl = &dir_divider_tables[current_divider]; // note_index を探す int found_note = -1; for (int i = 0; i < tbl->size; i++) { st_dir_divider_t* e = &tbl->table[i]; if (e->min_deg <= e->max_deg) { // 通常区間 if (angle >= e->min_deg && angle < e->max_deg) { found_note = e->note_index; break; } } else { // 360°またぎ区間 (例: 345〜360, 0〜15) if (angle >= e->min_deg || angle < e->max_deg) { found_note = e->note_index; break; } } } if (found_note < 0) { ERR_LOG("IDL: angle=%d → no note", angle); return; } // MIDI ノート番号に変換(C4=60 基準) int midi_note = ( (current_octave + 1) * 12) + found_note; // 移動量から音量レベルを決定 float move_amount = sqrt((float)accum_x * accum_x + (float)accum_y * accum_y); e_vel_level_t level = e_vel_level_silent; for (int i = e_vel_level_count - 1; i >= 0; i--) { if (move_amount >= move_amount_to_vel_lvel_threshold_tbl[i]) { level = (e_vel_level_t)i; break; } } INF_LOG("Play! %s:%d/%c ~%d [deg=%d, move=%.2f(%d,%d), note=%d(%d)]", index_to_note_str[current_note_langu][(midi_note%12)], (midi_note / 12) - 1, level_char[(level % e_vel_level_count)], play_period_tbl[(level % e_vel_level_count)], angle, move_amount, accum_x, accum_y, midi_note, (midi_note%12)); // 確定インジケータとして更新(ここから実線表示) indicator_angle_deg = (float)angle; indicator_level = level; indicator_confirmed = true; // 再生開始 startNewPlay((uint8_t)midi_note, level); // この区間は処理済み idl_has_mov = false; } int findSpecializedFontIndex(uint16_t code) { for (uint16_t i = 0; i < specialized_font8x16_count; ++i) { if (specialized_font8x16_code_table[i] == (uint8_t)code) { return (int)i; } } ERR_LOG("Unknown specialized font code: 0x%02X", (uint8_t)code); return SPECIAL_FONT_INVALID_IDX; // 0番ではなく '?' のインデックスを返す } void drawspecializedFont(int x, int y, uint16_t code, uint16_t font_col, uint16_t back_col) { int idx = -1; uint8_t u8_code = (uint8_t)code; // コード → インデックス検索 for (uint16_t i = 0; i < specialized_font8x16_count; i++) { if (specialized_font8x16_code_table[i] == u8_code) { idx = (int)i; break; } } // 見つからなかった → '?' を使う if (idx < 0) { ERR_LOG("Unknown specialized font code: 0x%02X", u8_code); idx = SPECIAL_FONT_INVALID_IDX; } const uint8_t* glyph = specialized_font8x16[idx]; for (int row = 0; row < 16; row++) { uint8_t bits = glyph[row]; for (int col = 0; col < 8; col++) { bool on = (bits & (0x80 >> col)) != 0; uint16_t col16 = on ? font_col : back_col; lcd.drawPixel(x + col, y + row, col16); } } } // LovyanGFX の Datum 定数を想定(既にどこかで定義されている前提) void drawSpecializedString(int x, int y, String str, uint16_t font_col, uint16_t back_col, int setTextDatum) { const int len = str.length(); if (len <= 0) return; const int w = SPECIAL_FONT_WIDTH; const int h = SPECIAL_FONT_HEIGHT; const int total_w = w * len; int sx = x; int sy = y; switch (setTextDatum) { case TL_DATUM: // 左上基準 // sx, sy そのまま break; case TC_DATUM: // 上中央 sx = x - total_w / 2; break; case TR_DATUM: // 右上 sx = x - total_w; break; case ML_DATUM: // 左中央 sy = y - h / 2; break; case MC_DATUM: // 中央 sx = x - total_w / 2; sy = y - h / 2; break; case MR_DATUM: // 右中央 sx = x - total_w; sy = y - h / 2; break; case BL_DATUM: // 左下 sy = y - h; break; case BC_DATUM: // 下中央 sx = x - total_w / 2; sy = y - h; break; case BR_DATUM: // 右下 sx = x - total_w; sy = y - h; break; default: // 想定外は TL と同じ扱い break; } // 1 文字ずつ描画 for (int i = 0; i < len; ++i) { uint8_t c = (uint8_t)str[i]; // 拡張 ASCII 前提(0xC4, 0xDE なども 1 バイト) drawspecializedFont(sx + i * w, sy, (uint16_t)c, font_col, back_col); } } void drawScreen() { // 基本画面の描画 lcd.clear(TFT_OLIVE); // ボリュームの描画 drawVolumeBar(); // ゲージの更新 drawGaugeBase(); // 音階言語設定の描画 drawNoteLanguage(); } void drawVolumeBar(void) { const int cy = VOL_AREA_HEIGHT / 2; lcd.fillRect(0, 0, SCREEN_WIDTH, VOL_AREA_HEIGHT, TFT_DARKGREY); lcd.drawLine(VOL_SLIDER_LEFT, cy, VOL_SLIDER_RIGHT, cy, TFT_BLACK); int knob_center_x = (current_volume * 2) + (VOL_KNOB_WIDTH / 2); int knob_left = knob_center_x - (VOL_KNOB_WIDTH / 2); int knob_top = cy - (VOL_KNOB_HEIGHT / 2); lcd.fillRect(knob_left, knob_top, VOL_KNOB_WIDTH, VOL_KNOB_HEIGHT, TFT_YELLOW); lcd.drawRect(knob_left, knob_top, VOL_KNOB_WIDTH, VOL_KNOB_HEIGHT, TFT_RED); } void drawGaugeBase() { const int cx = GAUGE_CENTER_X; const int cy = GAUGE_CENTER_Y; const int r_area = GAUGE_AREA_RADIUS; const int r_outer = GAUGE_OUTER_RADIUS; const int r_inner = GAUGE_INNER_RADIUS; lcd.fillCircle(cx, cy, r_area, TFT_BLACK); lcd.drawCircle(cx, cy, r_outer, TFT_WHITE); lcd.drawCircle(cx, cy, r_inner, TFT_WHITE); st_dir_divider_tables_t* tbl = &dir_divider_tables[current_divider]; auto to_lgfx_deg = [](float deg_0up_cw) { float d = deg_0up_cw - 90.0f; while (d < 0) d += 360.0f; while (d >= 360) d -= 360.0f; return d; }; auto conv = [&](float deg, int r, int &x, int &y) { float rad = deg * PI / 180.0f; x = cx + (int)(r * sin(rad)); y = cy - (int)(r * cos(rad)); }; auto is_black = [](uint8_t idx){ return (idx==1 || idx==3 || idx==6 || idx==8 || idx==10); }; bool label_drawn[12] = {false}; lcd.setFont(&fonts::Font2); lcd.setTextDatum(MC_DATUM); for (int i = 0; i < tbl->size; i++) { st_dir_divider_t* e = &tbl->table[i]; uint8_t note_idx = e->note_index; float d0 = e->min_deg; float d1 = e->max_deg; if (d0 > d1) d1 += 360.0f; bool black = is_black(note_idx); uint16_t fill_col = black ? TFT_BLACK : TFT_WHITE; uint16_t text_col = black ? TFT_WHITE : TFT_BLACK; float s = to_lgfx_deg(d0); float e2 = to_lgfx_deg(d1); if (e2 <= s) e2 += 360.0f; // 扇形塗りつぶし lcd.fillArc(cx, cy, r_inner, r_outer, s, e2, fill_col); // ---- 白鍵同士の境界だけ黒塗り(前後1°) ---- int next_i = (i + 1) % tbl->size; uint8_t next_idx = tbl->table[next_i].note_index; bool this_white = !black; bool next_white = !is_black(next_idx); float boundary_deg = e->max_deg; bool is_zero_line = (boundary_deg <= 0.0001f) || (fabs(boundary_deg - 360.0f) <= 0.0001f); if (this_white && next_white && !is_zero_line) { float b0 = boundary_deg - 1.0f; float b1 = boundary_deg + 1.0f; float bs = to_lgfx_deg(b0); float be = to_lgfx_deg(b1); if (be <= bs) be += 360.0f; // ここが境界太線の本体(fillArc で黒く塗る) lcd.fillArc(cx, cy, r_inner, r_outer, bs, be, TFT_BLACK); } // ---- ラベル(1回だけ) ---- if (!label_drawn[note_idx]) { float label_deg; if (next_idx == note_idx) { float d0_all = e->min_deg; float d1_all = tbl->table[next_i].max_deg; if (d1_all < d0_all) d1_all += 360.0f; label_deg = (d0_all + d1_all) * 0.5f; if (label_deg >= 360.0f) label_deg -= 360.0f; } else { if (e->min_deg <= e->max_deg) label_deg = (e->min_deg + e->max_deg) * 0.5f; else { float tmp = e->max_deg + 360.0f; label_deg = (e->min_deg + tmp) * 0.5f; if (label_deg >= 360.0f) label_deg -= 360.0f; } } int mid_r = (r_inner + r_outer) / 2; int tx, ty; conv(label_deg, mid_r, tx, ty); lcd.setTextColor(text_col, fill_col); drawSpecializedString(tx, ty, index_to_note_str[current_note_langu][note_idx], text_col, fill_col, MC_DATUM); //lcd.drawString(index_to_note_str[current_note_langu][note_idx], tx, ty); label_drawn[note_idx] = true; } } } void drawGaugeIndicator() { const int cx = GAUGE_CENTER_X; const int cy = GAUGE_CENTER_Y; // スクリーンスレッド専用の前回終点(スロットごと) static int prev_x[PLAY_DATA_POS_COUNT]; static int prev_y[PLAY_DATA_POS_COUNT]; static bool initialized = false; if (!initialized) { for (int i = 0; i < PLAY_DATA_POS_COUNT; i++) { prev_x[i] = cx; prev_y[i] = cy; } initialized = true; } // 1) まず前回線を全部消す(背景は常に黒) for (int i = 0; i < PLAY_DATA_POS_COUNT; i++) { if (!(prev_x[i] == cx && prev_y[i] == cy)) { lcd.drawLine(cx, cy, prev_x[i], prev_y[i], TFT_BLACK); } } // 2) 今有効なスロット分だけ線を描画 for (int i = 0; i < PLAY_DATA_POS_COUNT; i++) { st_play_data_t* p = &play_data[i]; if (!p->is_on || !p->indicator_valid) { // 無効なら前回位置をリセット(次回以降は消す必要なし) prev_x[i] = cx; prev_y[i] = cy; continue; } int ex = p->indicator_x; int ey = p->indicator_y; uint16_t col = p->indicator_color; // 実線で描画 lcd.drawLine(cx, cy, ex, ey, col); // 今回の終点を保存 prev_x[i] = ex; prev_y[i] = ey; } } void drawOctave() { } void drawNoteLanguage() { // 背景クリア lcd.fillRect(NOTE_LANG_LEFT, NOTE_LANG_TOP, NOTE_LANG_WIDTH, NOTE_LANG_HEIGHT, TFT_BLACK); lcd.drawRect(NOTE_LANG_LEFT, NOTE_LANG_TOP, NOTE_LANG_WIDTH, NOTE_LANG_HEIGHT, TFT_CYAN); // ラベル描画(左上基準) drawSpecializedString(NOTE_LANG_LEFT + 1, NOTE_LANG_TOP + 1, String(note_lang_str[current_note_langu]), TFT_CYAN, TFT_BLACK, TL_DATUM); } bool readTouchScreen(int &x, int &y) { uint16_t raw_x = 0, raw_y = 0; uint8_t raw_z1 = 0, raw_z2 = 0; // Z は 8bit が正しい digitalWrite(PIN_TOUCH_CS, LOW); // X touch_spi.transfer(REG_TOUCH_RDX); raw_x = (uint16_t)touch_spi.transfer(0) << 8; raw_x |= (uint16_t)touch_spi.transfer(0); raw_x >>= 3; // Y touch_spi.transfer(REG_TOUCH_RDY); raw_y = (uint16_t)touch_spi.transfer(0) << 8; raw_y |= (uint16_t)touch_spi.transfer(0); raw_y >>= 3; // Z1 touch_spi.transfer(REG_TOUCH_RDZ1); raw_z1 = touch_spi.transfer(0); // 8bit // Z2 touch_spi.transfer(REG_TOUCH_RDZ2); raw_z2 = touch_spi.transfer(0); // 8bit digitalWrite(PIN_TOUCH_CS, HIGH); // 押圧判定 if (!(raw_z1 > 0 && raw_z2 > 0)) { return false; // 未タッチ } #if !defined(ALIGN_TOUCH_MODE) // 通常動作時 if (raw_x < TOUCH_MIN_X || raw_x > TOUCH_MAX_X || raw_y < TOUCH_MIN_Y || raw_y > TOUCH_MAX_Y) return false; x = (raw_x - TOUCH_MIN_X) * (SCREEN_WIDTH - 1) / (TOUCH_MAX_X - TOUCH_MIN_X); y = (raw_y - TOUCH_MIN_Y) * (SCREEN_HEIGHT - 1) / (TOUCH_MAX_Y - TOUCH_MIN_Y); x = (SCREEN_WIDTH - 1) - x; if (x < 0) x = 0; if (x >= SCREEN_WIDTH) x = SCREEN_WIDTH - 1; if (y < 0) y = 0; if (y >= SCREEN_HEIGHT) y = SCREEN_HEIGHT - 1; return true; #else // !defined(ALIGN_TOUCH_MODE) // キャリブレーション動作時 x = raw_x; y = raw_y; return true; #endif // !defined(ALIGN_TOUCH_MODE) } void changeVolume(int id, int x, int y) { if (x < VOL_SLIDER_LEFT) x = VOL_SLIDER_LEFT; if (x > VOL_SLIDER_RIGHT) x = VOL_SLIDER_RIGHT; // ボリューム値算出 int vol = ( x - (VOL_KNOB_WIDTH / 2) ) / 2; // 丸め込む(保険) if (vol < 0) vol = 0; if (vol > 100) vol = 100; current_volume = (int8_t)vol; drawVolumeBar(); // つまみ再描画 } void changeCurrentDivider(int id, int x, int y) { if ( (current_divider + 1) < e_dir_divider_count) current_divider = current_divider + 1; else current_divider = e_dir_divider_full12; drawGaugeBase(); drawNoteLanguage(); } void changeNoteLanguage() { current_note_langu = ( (current_note_langu + 1) < e_note_langu_count)? current_note_langu + 1: e_note_langu_english; drawGaugeBase(); drawNoteLanguage(); } ``` ```cpp: SimpleQueue.h /* Copyright (c) TakSan This source file is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License (CC BY-SA 4.0). For license terms and additional notes regarding commercial use, please refer to the LICENSE.txt file in the root directory of this repository. 著作権者:TakSan このソースファイルは Creative Commons 表示 - 継承 4.0 国際 ライセンス(CC BY-SA 4.0)の下に公開されています。 ライセンスの詳細および商用利用に関する追加情報は、 本リポジトリのルートにある LICENSE.txt を参照してください。 */ #ifndef SimpleQueue_h #define SimpleQueue_h /** * @class SimpleQueue * @brief $EN{{Simple ring buffer queue class template}}$JP{{シンプルなリングバッファキューのクラステンプレート}} * @tparam DataType $EN{{Type of data stored in the queue}}$JP{{キューに格納されるデータ型}} * @tparam _Size $EN{{Size of the queue buffer. Default is 1024}}$JP{{キューバッファのサイズ。デフォルトは1024}} */ template <class DataType, int _Size = 1024> class SimpleQueue { public: /** * @brief $EN{{Constructor: Initializes the queue}}$JP{{コンストラクタ:キューを初期化する}} */ SimpleQueue(void) { Clear(); } /** * @brief $EN{{Clears the queue by resetting positions and buffer.}}$JP{{位置とバッファをリセットしてキューをクリアする}} */ void Clear(void) { _PutPos = 0; _GetPos = 0; memset(_DataBuffer, 0, sizeof(_DataBuffer)); } /** * @brief $EN{{Inserts one data item into the queue.}}$JP{{データを1つキューに追加します}} * @param data $EN{{Pointer to the data to insert.}}$JP{{挿入するデータへのポインタ}} * @return $EN{{Result}}$JP{{処理結果}} * @retval true $EN{{Success}}JP{{成功}} * @retval true $EN{{The queue is full}}JP{{キューが満杯}} */ bool Put(DataType* data) { unsigned int NextPos = (_PutPos < _Size) ? _PutPos + 1 : 0; if (NextPos == _GetPos) return (false); _DataBuffer[_PutPos] = *data; _PutPos = NextPos; return (true); } /** * @brief $EN{{Retrieves one data item from the queue.}}$JP{{データを1つキューから取得します。}} * @param data $EN{{Pointer to store the retrieved data.}}$JP{{取得したデータを格納するポインタ。}} * @return $EN{{Result}}$JP{{処理結果}} * @retval true $EN{{Success}}JP{{成功}} * @retval true $EN{{The queue is empty}}JP{{キューが空}} */ bool Get(DataType* data) { unsigned int NextPos; if (_GetPos == _PutPos) return (false); NextPos = (_GetPos < _Size) ? _GetPos + 1 : 0; *data = _DataBuffer[_GetPos]; _GetPos = NextPos; return (true); } /** * @brief $EN{{Checks if there is data available in the queue.}}$JP{{キュー内にデータが存在するかを確認します。}} * @return $EN{{true if data exists, false otherwise.}}$JP{{データが存在すればtrue、存在しなければfalse。}} */ bool DataExists(void) { if (_GetPos == _PutPos) return (false); return (true); } private: unsigned int _PutPos; //!< $EN{{Position to insert the next data.}}$JP{{次にデータを挿入する位置。}} unsigned int _GetPos; //!< $EN{{Position to retrieve the next data.}}$JP{{次にデータを取得する位置。}} //unsigned int _PutErrorCount = 0; //!< デバッグ用*/ //unsigned int _GetErrorCount = 0; //!< デバッグ用*/ //unsigned int _GetCount = 0; //!< デバッグ用*/ DataType _DataBuffer[_Size]; //!< $EN{{Data buffer to hold queue items.}}$JP{{キューのデータを格納するバッファ。}} }; #endif //SimpleQueue_h ``` ```cpp: SysLog.h /* Copyright (c) TakSan This source file is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License (CC BY-SA 4.0). For license terms and additional notes regarding commercial use, please refer to the LICENSE.txt file in the root directory of this repository. 著作権者:TakSan このソースファイルは Creative Commons 表示 - 継承 4.0 国際 ライセンス(CC BY-SA 4.0)の下に公開されています。 ライセンスの詳細および商用利用に関する追加情報は、 本リポジトリのルートにある LICENSE.txt を参照してください。 */ #ifndef SysLog_h #define SysLog_h // -----=====<<<<<[[[[[ Include ]]]]]>>>>>=====----- #include <Arduino.h> // -----=====<<<<<[[[[[ Define ]]]]]>>>>>=====----- /** @defgroup GR_DEFINE $EN{{Define}}$JP{{マクロ定義}} * @brief $EN{{Macro definitions}}$JP{{マクロの定義}} * @{ */ /** @defgroup GR_DEF_LOG_LEVEL $EN{{Log Level}}$JP{{ログレベル}} * @brief $EN{{Log Level definitions}}$JP{{ログレベルの定義}} * @{ */ /** * @def LOG_LEVEL_FATAL * @brief $EN{{Log level: Fatal error}}$JP{{ログレベル:致命的なエラー}} */ #define LOG_LEVEL_FATAL 1 /** * @def LOG_LEVEL_ERROR * @brief $EN{{Log level: Error}}$JP{{ログレベル:エラー}} */ #define LOG_LEVEL_ERROR 2 /** * @def LOG_LEVEL_WARNING * @brief $EN{{Log level: Warning}}$JP{{ログレベル:警告}} */ #define LOG_LEVEL_WARNING 3 /** * @def LOG_LEVEL_INFO * @brief $EN{{Log level: Info}}$JP{{ログレベル:情報}} */ #define LOG_LEVEL_INFO 4 /** * @def LOG_LEVEL_DEBUG * @brief $EN{{Log level: Debug}}$JP{{ログレベル:デバッグ}} */ #define LOG_LEVEL_DEBUG 5 /** * @def MAX_LOG_LEVEL * @brief $EN{{Maximum enabled log level}}$JP{{有効な最大ログレベル}} */ #define MAX_LOG_LEVEL LOG_LEVEL_DEBUG /** @} GR_DEF_LOG_LEVEL */ /** * @def LOG_BUF_SIZE * @brief $EN{{Buffer size for formatted log messages}}$JP{{ログメッセージ用のフォーマット済みバッファサイズ}} */ #define LOG_BUF_SIZE 140 // -----=====<<<<<[[[[[ Types ]]]]]>>>>>=====----- // -----=====<<<<<[[[[[ Valiable ]]]]]>>>>>=====----- /** @defgroup GR_VALS $EN{{Variables and Related Items}}$JP{{変数関連}} * @brief $EN{{Variable Groups}}$JP{{変数}} * @{ */ /** * @var mutex_for_log * @brief $EN{{Mutex for protecting log output}}$JP{{ログ出力を保護するためのミューテックス}} */ extern pthread_mutex_t mutex_for_log; /** @} GR_VARS */ // -----=====<<<<<[[[[[ Class ]]]]]>>>>>=====----- // -----=====<<<<<[[[[[ Prototype ]]]]]>>>>>=====----- // -----=====<<<<<[[[[[ Tables ]]]]]>>>>>=====----- // -----=====<<<<<[[[[[ Functions ]]]]]>>>>>=====----- /** @defgroup DEF_MACRO_FUNCTIONS $EN{{Macro Function Definitions}}$JP{{マクロ関数定義} * @brief $EN{{Macros for utility functions}}$JP{{マクロ関数定義}} * @{ */ /** * @def FTL_LOG * @brief $EN{{Logs a fatal error message (Log level: Fatal error)}}$JP{{致命的エラーのログを出力する(ログレベル:致命的なエラー)}} * @note $EN{{Arguments follow printf-style format string and variable arguments}}$JP{{引数は printf 型式のフォーマットと可変引数が使用可能}} */ #if (MAX_LOG_LEVEL>=LOG_LEVEL_FATAL) #define FTL_LOG(format, ...) do \ {\ char tmp_str[LOG_BUF_SIZE];\ pthread_mutex_lock(&mutex_for_log);\ snprintf(tmp_str, sizeof(tmp_str), format, ##__VA_ARGS__);\ Serial.printf("F:");\ Serial.printf("%s", tmp_str);\ Serial.printf("\n");\ pthread_mutex_unlock(&mutex_for_log);\ } while(0) #else // (MAX_LOG_LEVEL<=LOG_LEVEL_FATAL) #define FTL_LOG(format, ...) #endif // (MAX_LOG_LEVEL<=LOG_LEVEL_FATAL) /** * @def ERR_LOG * @brief $EN{{Logs an error message (Log level: Error)}}$JP{{エラーメッセージをログ出力する(ログレベル:エラー)}} * @note $EN{{Arguments follow printf-style format string and variable arguments}}$JP{{引数は printf 型式のフォーマットと可変引数が使用可能}} */ #if (MAX_LOG_LEVEL>=LOG_LEVEL_ERROR) #define ERR_LOG(format, ...) do \ {\ char tmp_str[LOG_BUF_SIZE];\ pthread_mutex_lock(&mutex_for_log);\ snprintf(tmp_str, sizeof(tmp_str), format, ##__VA_ARGS__);\ Serial.printf("E:");\ Serial.printf("%s", tmp_str);\ Serial.printf("\n");\ pthread_mutex_unlock(&mutex_for_log);\ } while(0) #else // (MAX_LOG_LEVEL<=LOG_LEVEL_ERROR) #define ERR_LOG(format, ...) #endif // (MAX_LOG_LEVEL<=LOG_LEVEL_ERROR) /** * @def WRN_LOG * @brief $EN{{Logs a warning message (Log level: Warning)}}$JP{{警告メッセージをログ出力します(ログレベル:警告)}} * @note $EN{{Arguments follow printf-style format string and variable arguments}}$JP{{引数は printf 型式のフォーマットと可変引数が使用可能}} */ #if (MAX_LOG_LEVEL>=LOG_LEVEL_WARNING) #define WRN_LOG(format, ...) do \ {\ char tmp_str[LOG_BUF_SIZE];\ pthread_mutex_lock(&mutex_for_log);\ snprintf(tmp_str, sizeof(tmp_str), format, ##__VA_ARGS__);\ Serial.printf("W:");\ Serial.printf("%s", tmp_str);\ Serial.printf("\n");\ pthread_mutex_unlock(&mutex_for_log);\ } while(0) #else // (MAX_LOG_LEVEL<=LOG_LEVEL_WARNING) #define WRN_LOG(format, ...) #endif // (MAX_LOG_LEVEL<=LOG_LEVEL_WARNING) /** * @def INF_LOG * @brief $EN{{Logs an informational message (Log level: Info)}}$JP{{情報メッセージをログ出力します(ログレベル:情報)}} * @note $EN{{Arguments follow printf-style format string and variable arguments}}$JP{{引数は printf 型式のフォーマットと可変引数が使用可能}} */ #if (MAX_LOG_LEVEL>=LOG_LEVEL_INFO) #define INF_LOG(format, ...) do \ {\ char tmp_str[LOG_BUF_SIZE];\ pthread_mutex_lock(&mutex_for_log);\ snprintf(tmp_str, sizeof(tmp_str), format, ##__VA_ARGS__);\ Serial.printf("I:");\ Serial.printf("%s", tmp_str);\ Serial.printf("\n");\ pthread_mutex_unlock(&mutex_for_log);\ } while(0) #else // (MAX_LOG_LEVEL<=LOG_LEVEL_INFO) #define INF_LOG(format, ...) #endif // (MAX_LOG_LEVEL<=LOG_LEVEL_INFO) /** * @def DBG_LOG * @brief $EN{{Logs a debug message (Log level: Debug)}}$JP{{デバッグメッセージをログ出力します(ログレベル:デバッグ)}} * @note $EN{{Arguments follow printf-style format string and variable arguments}}$JP{{引数は printf 型式のフォーマットと可変引数が使用可能}} */ #if (MAX_LOG_LEVEL>=LOG_LEVEL_DEBUG) #define DBG_LOG(format, ...) do \ {\ char tmp_str[LOG_BUF_SIZE];\ pthread_mutex_lock(&mutex_for_log);\ snprintf(tmp_str, sizeof(tmp_str), format, ##__VA_ARGS__);\ Serial.printf("D:");\ Serial.printf("%s", tmp_str);\ Serial.printf("\n");\ pthread_mutex_unlock(&mutex_for_log);\ } while(0) #else // (MAX_LOG_LEVEL<=LOG_LEVEL_DEBUG) #define DBG_LOG(format, ...) #endif // (MAX_LOG_LEVEL<=LOG_LEVEL_DEBUG) /** @} DEF_MACRO_FUNCTIONS */ /** @} GR_DEFINE */ #endif//SysLog_h ``` ```cpp: InstTable.h {0, "SawLpf/0_C-1.wav"}, {1, "SawLpf/1_C#-1.wav"}, {2, "SawLpf/2_D-1.wav"}, {3, "SawLpf/3_D#-1.wav"}, {4, "SawLpf/4_E-1.wav"}, {5, "SawLpf/5_F-1.wav"}, {6, "SawLpf/6_F#-1.wav"}, {7, "SawLpf/7_G-1.wav"}, {8, "SawLpf/8_G#-1.wav"}, {9, "SawLpf/9_A-1.wav"}, {10, "SawLpf/10_A#-1.wav"}, {11, "SawLpf/11_B-1.wav"}, {12, "SawLpf/12_C0.wav"}, {13, "SawLpf/13_C#0.wav"}, {14, "SawLpf/14_D0.wav"}, {15, "SawLpf/15_D#0.wav"}, {16, "SawLpf/16_E0.wav"}, {17, "SawLpf/17_F0.wav"}, {18, "SawLpf/18_F#0.wav"}, {19, "SawLpf/19_G0.wav"}, {20, "SawLpf/20_G#0.wav"}, {21, "SawLpf/21_A0.wav"}, {22, "SawLpf/22_A#0.wav"}, {23, "SawLpf/23_B0.wav"}, {24, "SawLpf/24_C1.wav"}, {25, "SawLpf/25_C#1.wav"}, {26, "SawLpf/26_D1.wav"}, {27, "SawLpf/27_D#1.wav"}, {28, "SawLpf/28_E1.wav"}, {29, "SawLpf/29_F1.wav"}, {30, "SawLpf/30_F#1.wav"}, {31, "SawLpf/31_G1.wav"}, {32, "SawLpf/32_G#1.wav"}, {33, "SawLpf/33_A1.wav"}, {34, "SawLpf/34_A#1.wav"}, {35, "SawLpf/35_B1.wav"}, {36, "SawLpf/36_C2.wav"}, {37, "SawLpf/37_C#2.wav"}, {38, "SawLpf/38_D2.wav"}, {39, "SawLpf/39_D#2.wav"}, {40, "SawLpf/40_E2.wav"}, {41, "SawLpf/41_F2.wav"}, {42, "SawLpf/42_F#2.wav"}, {43, "SawLpf/43_G2.wav"}, {44, "SawLpf/44_G#2.wav"}, {45, "SawLpf/45_A2.wav"}, {46, "SawLpf/46_A#2.wav"}, {47, "SawLpf/47_B2.wav"}, {48, "SawLpf/48_C3.wav"}, {49, "SawLpf/49_C#3.wav"}, {50, "SawLpf/50_D3.wav"}, {51, "SawLpf/51_D#3.wav"}, {52, "SawLpf/52_E3.wav"}, {53, "SawLpf/53_F3.wav"}, {54, "SawLpf/54_F#3.wav"}, {55, "SawLpf/55_G3.wav"}, {56, "SawLpf/56_G#3.wav"}, {57, "SawLpf/57_A3.wav"}, {58, "SawLpf/58_A#3.wav"}, {59, "SawLpf/59_B3.wav"}, {60, "SawLpf/60_C4.wav"}, {61, "SawLpf/61_C#4.wav"}, {62, "SawLpf/62_D4.wav"}, {63, "SawLpf/63_D#4.wav"}, {64, "SawLpf/64_E4.wav"}, {65, "SawLpf/65_F4.wav"}, {66, "SawLpf/66_F#4.wav"}, {67, "SawLpf/67_G4.wav"}, {68, "SawLpf/68_G#4.wav"}, {69, "SawLpf/69_A4.wav"}, {70, "SawLpf/70_A#4.wav"}, {71, "SawLpf/71_B4.wav"}, {72, "SawLpf/72_C5.wav"}, {73, "SawLpf/73_C#5.wav"}, {74, "SawLpf/74_D5.wav"}, {75, "SawLpf/75_D#5.wav"}, {76, "SawLpf/76_E5.wav"}, {77, "SawLpf/77_F5.wav"}, {78, "SawLpf/78_F#5.wav"}, {79, "SawLpf/79_G5.wav"}, {80, "SawLpf/80_G#5.wav"}, {81, "SawLpf/81_A5.wav"}, {82, "SawLpf/82_A#5.wav"}, {83, "SawLpf/83_B5.wav"}, {84, "SawLpf/84_C6.wav"}, {85, "SawLpf/85_C#6.wav"}, {86, "SawLpf/86_D6.wav"}, {87, "SawLpf/87_D#6.wav"}, {88, "SawLpf/88_E6.wav"}, {89, "SawLpf/89_F6.wav"}, {90, "SawLpf/90_F#6.wav"}, {91, "SawLpf/91_G6.wav"}, {92, "SawLpf/92_G#6.wav"}, {93, "SawLpf/93_A6.wav"}, {94, "SawLpf/94_A#6.wav"}, {95, "SawLpf/95_B6.wav"}, {96, "SawLpf/96_C7.wav"}, {97, "SawLpf/97_C#7.wav"}, {98, "SawLpf/98_D7.wav"}, {99, "SawLpf/99_D#7.wav"}, {100, "SawLpf/100_E7.wav"}, {101, "SawLpf/101_F7.wav"}, {102, "SawLpf/102_F#7.wav"}, {103, "SawLpf/103_G7.wav"}, {104, "SawLpf/104_G#7.wav"}, {105, "SawLpf/105_A7.wav"}, {106, "SawLpf/106_A#7.wav"}, {107, "SawLpf/107_B7.wav"}, {108, "SawLpf/108_C8.wav"}, {109, "SawLpf/109_C#8.wav"}, {110, "SawLpf/110_D8.wav"}, {111, "SawLpf/111_D#8.wav"}, {112, "SawLpf/112_E8.wav"}, {113, "SawLpf/113_F8.wav"}, {114, "SawLpf/114_F#8.wav"}, {115, "SawLpf/115_G8.wav"}, {116, "SawLpf/116_G#8.wav"}, {117, "SawLpf/117_A8.wav"}, {118, "SawLpf/118_A#8.wav"}, {119, "SawLpf/119_B8.wav"}, {120, "SawLpf/120_C9.wav"}, {121, "SawLpf/121_C#9.wav"}, {122, "SawLpf/122_D9.wav"}, {123, "SawLpf/123_D#9.wav"}, {124, "SawLpf/124_E9.wav"}, {125, "SawLpf/125_F9.wav"}, {126, "SawLpf/126_F#9.wav"}, {127, "SawLpf/127_G9.wav"}, ``` ```cpp: LGFX_SPRESENSE.hpp #pragma once #define LGFX_USE_V1 #include <LovyanGFX.hpp> // SPRESENSEでLovyanGFXを独自設定で利用する場合の設定例 /* このファイルを複製し、新しい名前を付けて、環境に合わせて設定内容を変更してください。 作成したファイルをユーザープログラムからincludeすることで利用可能になります。 複製したファイルはライブラリのlgfx_userフォルダに置いて利用しても構いませんが、 その場合はライブラリのアップデート時に削除される可能性があるのでご注意ください。 安全に運用したい場合はバックアップを作成しておくか、ユーザープロジェクトのフォルダに置いてください。 //*/ /// 独自の設定を行うクラスを、LGFX_Deviceから派生して作成します。 class LGFX : public lgfx::LGFX_Device { /* クラス名は"LGFX"から別の名前に変更しても構いません。 AUTODETECTと併用する場合は"LGFX"は使用されているため、LGFX以外の名前に変更してください。 また、複数枚のパネルを同時使用する場合もそれぞれに異なる名前を付けてください。 ※ クラス名を変更する場合はコンストラクタの名前も併せて同じ名前に変更が必要です。 名前の付け方は自由に決めて構いませんが、設定が増えた場合を想定し、 例えば SPRESENSE でSPI接続のILI9341の設定を行った場合、 LGFX_SPRESENSE_SPI_ILI9341 のような名前にし、ファイル名とクラス名を一致させておくことで、利用時に迷いにくくなります。 //*/ // 接続するパネルの型にあったインスタンスを用意します。 //lgfx::Panel_GC9A01 _panel_instance; //lgfx::Panel_GDEW0154M09 _panel_instance; //lgfx::Panel_HX8357B _panel_instance; //lgfx::Panel_HX8357D _panel_instance; //lgfx::Panel_ILI9163 _panel_instance; lgfx::Panel_ILI9341 _panel_instance; //lgfx::Panel_ILI9342 _panel_instance; //lgfx::Panel_ILI9481 _panel_instance; //lgfx::Panel_ILI9486 _panel_instance; //lgfx::Panel_ILI9488 _panel_instance; //lgfx::Panel_IT8951 _panel_instance; //lgfx::Panel_SH110x _panel_instance; // SH1106, SH1107 //lgfx::Panel_SSD1306 _panel_instance; //lgfx::Panel_SSD1327 _panel_instance; //lgfx::Panel_SSD1331 _panel_instance; //lgfx::Panel_SSD1351 _panel_instance; // SSD1351, SSD1357 //lgfx::Panel_SSD1963 _panel_instance; //lgfx::Panel_ST7735 _panel_instance; //lgfx::Panel_ST7735S _panel_instance; //lgfx::Panel_ST7789 _panel_instance; //lgfx::Panel_ST7796 _panel_instance; // SPIバスのインスタンスを用意します。 lgfx::Bus_SPI _bus_instance; // SPIバスのインスタンス public: // コンストラクタを作成し、ここで各種設定を行います。 // クラス名を変更した場合はコンストラクタも同じ名前を指定してください。 LGFX(void) { { // バス制御の設定を行います。 auto cfg = _bus_instance.config(); // バス設定用の構造体を取得します。 cfg.spi_mode = 0; // SPI通信モードを設定 (0 ~ 3) cfg.freq_write = 40000000; // 送信時のSPIクロック cfg.freq_read = 16000000; // 受信時のSPIクロック cfg.pin_dc = 9; // SPIのD/Cピン番号を設定 (-1 = disable) cfg.spi_port = 4; // Arduino拡張ボードの場合は 4 _bus_instance.config(cfg); // 設定値をバスに反映します。 _panel_instance.setBus(&_bus_instance); // バスをパネルにセットします。 } { // 表示パネル制御の設定を行います。 auto cfg = _panel_instance.config(); // 表示パネル設定用の構造体を取得します。 // cfg.pin_cs = -1; // CSが接続されているピン番号 (-1 = disable) HW CSピンの場合は-1を指定 cfg.pin_cs = 10;//@ // CSが接続されているピン番号 (-1 = disable) HW CSピンの場合は-1を指定 cfg.pin_rst = 8; // RSTが接続されているピン番号 (-1 = disable) cfg.pin_busy = -1;//@9; // BUSYが接続されているピン番号 (-1 = disable) // ※ 以下の設定値はパネル毎に一般的な初期値が設定されていますので、不明な項目はコメントアウトして試してみてください。 cfg.panel_width = 240; // 実際に表示可能な幅 cfg.panel_height = 320; // 実際に表示可能な高さ cfg.offset_x = 0; // パネルのX方向オフセット量 cfg.offset_y = 0; // パネルのY方向オフセット量 cfg.offset_rotation = 0; // 回転方向の値のオフセット 0~7 (4~7は上下反転) cfg.dummy_read_pixel = 8; // ピクセル読出し前のダミーリードのビット数 cfg.dummy_read_bits = 1; // ピクセル以外のデータ読出し前のダミーリードのビット数 cfg.readable = true; // データ読出しが可能な場合 trueに設定 cfg.invert = false; // パネルの明暗が反転してしまう場合 trueに設定 cfg.rgb_order = false; // パネルの赤と青が入れ替わってしまう場合 trueに設定 cfg.dlen_16bit = false; // データ長を16bit単位で送信するパネルの場合 trueに設定 cfg.bus_shared = true; // SDカードとバスを共有している場合 trueに設定(drawJpgFile等でバス制御を行います) // 以下はST7735やILI9163のようにピクセル数が変更できるドライバでのみ設定してください。 // cfg.memory_width = 240; // ドライバICがサポートしている最大の幅 // cfg.memory_height = 320; // ドライバICがサポートしている最大の高さ _panel_instance.config(cfg); } setPanel(&_panel_instance); // 使用するパネルをセットします。 } }; ``` ```cpp: SoftSPI.cpp /* * Copyright (c) 2014, Majenko Technologies * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of Majenko Technologies nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "SoftSPI.h" SoftSPI::SoftSPI(uint8_t mosi, uint8_t miso, uint8_t sck) : SPIClass(SPI_PORT) { _mosi = mosi; _miso = miso; _sck = sck; _delay = 2; _cke = 0; _ckp = 0; _order = MSBFIRST; } void SoftSPI::begin() { pinMode(_mosi, OUTPUT); pinMode(_miso, INPUT); pinMode(_sck, OUTPUT); } void SoftSPI::end() { pinMode(_mosi, INPUT); pinMode(_miso, INPUT); pinMode(_sck, INPUT); } void SoftSPI::setBitOrder(uint8_t order) { _order = order & 1; } void SoftSPI::setDataMode(uint8_t mode) { switch (mode) { case SPI_MODE0: _ckp = 0; _cke = 0; break; case SPI_MODE1: _ckp = 0; _cke = 1; break; case SPI_MODE2: _ckp = 1; _cke = 0; break; case SPI_MODE3: _ckp = 1; _cke = 1; break; } digitalWrite(_sck, _ckp ? HIGH : LOW); } void SoftSPI::setClockDivider(uint32_t div) { switch (div) { case SPI_CLOCK_DIV2: _delay = 2; break; case SPI_CLOCK_DIV4: _delay = 4; break; case SPI_CLOCK_DIV8: _delay = 8; break; case SPI_CLOCK_DIV16: _delay = 16; break; case SPI_CLOCK_DIV32: _delay = 32; break; case SPI_CLOCK_DIV64: _delay = 64; break; case SPI_CLOCK_DIV128: _delay = 128; break; default: _delay = 128; break; } } void SoftSPI::wait(uint_fast8_t del) { for (uint_fast8_t i = 0; i < del; i++) { asm volatile("nop"); } } uint8_t SoftSPI::transfer(uint8_t val) { uint8_t out = 0; if (_order == MSBFIRST) { uint8_t v2 = ((val & 0x01) << 7) | ((val & 0x02) << 5) | ((val & 0x04) << 3) | ((val & 0x08) << 1) | ((val & 0x10) >> 1) | ((val & 0x20) >> 3) | ((val & 0x40) >> 5) | ((val & 0x80) >> 7); val = v2; } uint8_t del = _delay >> 1; uint8_t bval = 0; /* * CPOL := 0, CPHA := 0 => INIT = 0, PRE = Z|0, MID = 1, POST = 0 * CPOL := 1, CPHA := 0 => INIT = 1, PRE = Z|1, MID = 0, POST = 1 * CPOL := 0, CPHA := 1 => INIT = 0, PRE = 1 , MID = 0, POST = Z|0 * CPOL := 1, CPHA := 1 => INIT = 1, PRE = 0 , MID = 1, POST = Z|1 */ int sck = (_ckp) ? HIGH : LOW; for (uint8_t bit = 0u; bit < 8u; bit++) { if (_cke) { sck ^= 1; digitalWrite(_sck, sck); wait(del); } /* ... Write bit */ digitalWrite(_mosi, ((val & (1<<bit)) ? HIGH : LOW)); wait(del); sck ^= 1u; digitalWrite(_sck, sck); /* ... Read bit */ { bval = digitalRead(_miso); if (_order == MSBFIRST) { out <<= 1; out |= bval; } else { out >>= 1; out |= bval << 7; } } wait(del); if (!_cke) { sck ^= 1u; digitalWrite(_sck, sck); } } return out; } uint16_t SoftSPI::transfer16(uint16_t data) { union { uint16_t val; struct { uint8_t lsb; uint8_t msb; }; } in, out; in.val = data; if ( _order == MSBFIRST ) { out.msb = transfer(in.msb); out.lsb = transfer(in.lsb); } else { out.lsb = transfer(in.lsb); out.msb = transfer(in.msb); } return out.val; } ``` ```cpp: SoftSPI.h /* * Copyright (c) 2014, Majenko Technologies * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of Majenko Technologies nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef _SOFTSPI_H #define _SOFTSPI_H #if (ARDUINO >= 100) # include <Arduino.h> #else # include <WProgram.h> #endif #include <SPI.h> #define SPI_PORT 5 class SoftSPI : public SPIClass { private: void wait(uint_fast8_t del); private: uint8_t _cke; uint8_t _ckp; uint8_t _delay; uint8_t _miso; uint8_t _mosi; uint8_t _sck; uint8_t _order; public: SoftSPI(uint8_t mosi, uint8_t miso, uint8_t sck); void begin(); void end(); void setBitOrder(uint8_t); void setDataMode(uint8_t); void setClockDivider(uint32_t); uint8_t transfer(uint8_t); uint16_t transfer16(uint16_t data); }; #endif ``` ```c: Fonts.c #include <stdint.h> #define BIN (uint8_t)((((((((0 #define O )*2+1 #define _ )*2 uint8_t specialized_font8x16_code_table[] = { 0x20 /*' '*/, 0x23 /*'#'*/, 0x3A /*':'*/, 0x3B /*';'*/, 0x3C /*'<'*/, 0x3D /*'='*/, 0x3E /*'>'*/, 0x3F /*'?'*/, 0x28 /*'('*/, 0x29 /*')'*/, 0x2A /*'*'*/, 0x2B /*'+'*/, 0x2C /*','*/, 0x2D /*'-'*/, 0x2E /*'.'*/, 0x2F /*'/'*/, 0x30 /*'0'*/, 0x31 /*'1'*/, 0x32 /*'2'*/, 0x33 /*'3'*/, 0x34 /*'4'*/, 0x35 /*'5'*/, 0x36 /*'6'*/, 0x37 /*'7'*/, 0x38 /*'8'*/, 0x39 /*'9'*/, 0x41 /*'A'*/, 0x42 /*'B'*/, 0x43 /*'C'*/, 0x44 /*'D'*/, 0x45 /*'E'*/, 0x46 /*'F'*/, 0x47 /*'G'*/, 0x4F /*'O'*/, 0x62 /*'b'*/, 0x63 /*'c'*/, 0x74 /*'t'*/, 0xC4 /*'ト'*/, 0xDE /*'゙'*/, 0xDA /*'レ'*/, 0xD0 /*'ミ'*/, 0xCC /*'フ'*/, 0xA7 /*'ァ'*/, 0xBF /*'ソ'*/, 0xD7 /*'ラ'*/, 0xBC /*'シ'*/, }; const uint16_t specialized_font8x16_count = (sizeof(specialized_font8x16_code_table) / sizeof(specialized_font8x16_code_table[0])); /*--------------------------*/ const uint8_t specialized_font8x16[][16]={ /* ' ' ASCII=00H [No.0] */ { BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '#' ASCII=23H [No.35] */ { BIN _ _ O O _ O O _ , BIN _ _ O O _ O O _ , BIN _ _ O O _ O O _ , BIN _ O O O O O O O , BIN _ O O O O O O O , BIN _ _ O O _ O O _ , BIN _ _ O O _ O O _ , BIN _ _ O O _ O O _ , BIN _ _ O O _ O O _ , BIN _ O O O O O O O , BIN _ O O O O O O O , BIN _ _ O O _ O O _ , BIN _ _ O O _ O O _ , BIN _ _ O O _ O O _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* ':' ASCII=3AH [No.58] */ { BIN _ _ _ _ _ _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ O O O O _ _ , BIN _ _ O O O O _ _ , BIN _ _ O O O O _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ O O O O _ _ , BIN _ _ O O O O _ _ , BIN _ _ O O O O _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* ';' ASCII=3BH [No.59] */ { BIN _ _ _ _ _ _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ O O O O _ _ , BIN _ _ O O O O _ _ , BIN _ _ O O O O _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ O O O O _ _ , BIN _ _ O O O O _ _ , BIN _ _ O O O O _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ O O _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '<' ASCII=3CH [No.60] */ { BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ O O O _ , BIN _ _ _ O O O _ _ , BIN _ _ O O O _ _ _ , BIN _ O O O _ _ _ _ , BIN _ O O O _ _ _ _ , BIN _ _ O O O _ _ _ , BIN _ _ _ O O O _ _ , BIN _ _ _ _ O O O _ , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '=' ASCII=3DH [No.61] */ { BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ O O O O O O _ , BIN _ O O O O O O _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ O O O O O O _ , BIN _ O O O O O O _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '>' ASCII=3EH [No.62] */ { BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O O _ _ _ _ , BIN _ _ O O O _ _ _ , BIN _ _ _ O O O _ _ , BIN _ _ _ _ O O O _ , BIN _ _ _ _ O O O _ , BIN _ _ _ O O O _ _ , BIN _ _ O O O _ _ _ , BIN _ O O O _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '?' ASCII=3FH [No.63] */ { BIN _ _ _ O O O O _ , BIN _ _ O O O O O O , BIN _ O O O O O _ O , BIN _ O O _ _ O _ O , BIN _ O _ _ _ O O O , BIN _ _ _ _ _ O O O , BIN _ _ _ _ O O O _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ O O _ O _ , BIN _ _ _ O O _ O _ , BIN _ _ _ O O O O _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '(' ASCII=28H [No.40] */ { BIN _ _ _ _ _ O O _ , BIN _ _ _ _ O O O _ , BIN _ _ _ O O O _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ O O O _ _ _ , BIN _ _ O O _ _ _ _ , BIN _ _ O O _ _ _ _ , BIN _ _ O O _ _ _ _ , BIN _ _ O O _ _ _ _ , BIN _ _ O O O _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ _ O O O _ _ , BIN _ _ _ _ O O O _ , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* ')' ASCII=29H [No.41] */ { BIN _ _ O O _ _ _ _ , BIN _ _ O O O _ _ _ , BIN _ _ _ O O O _ _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ _ O O O _ , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ O O O _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ O O O _ _ , BIN _ _ O O O _ _ _ , BIN _ _ O O _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '*' ASCII=2AH [No.42] */ { BIN _ _ _ _ _ _ _ _ , BIN _ O O _ _ O O _ , BIN _ O O _ _ O O _ , BIN _ _ O O O O _ _ , BIN _ _ O O O O _ _ , BIN _ _ _ O O _ _ _ , BIN _ O O O O O O _ , BIN _ O O O O O O _ , BIN _ _ _ O O _ _ _ , BIN _ _ O O O O _ _ , BIN _ _ O O O O _ _ , BIN _ O O _ _ O O _ , BIN _ O O _ _ O O _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '+' ASCII=2BH [No.43] */ { BIN _ _ _ O O _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ O O O O O O _ , BIN _ O O O O O O _ , BIN _ _ _ O O _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* ',' ASCII=2CH [No.44] */ { BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ O O _ _ _ _ , BIN _ O O O O _ _ _ , BIN _ O O O O _ _ _ , BIN _ _ O O O _ _ _ , BIN _ _ O O _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '-' ASCII=2DH [No.45] */ { BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ O O O O O O _ , BIN _ O O O O O O _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '.' ASCII=2EH [No.46] */ { BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ O O _ _ _ _ , BIN _ O O O O _ _ _ , BIN _ O O O O _ _ _ , BIN _ O O O O _ _ _ , BIN _ _ O O _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '/' ASCII=2FH [No.47] */ { BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ O O , BIN _ _ _ _ _ _ O O , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ O O _ _ _ _ , BIN _ _ O O _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '0' ASCII=30H [No.48] */ { BIN _ _ _ O O O _ _ , BIN _ _ O O O O O _ , BIN _ _ O O _ O O _ , BIN _ O O O _ O O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ O O O , BIN _ O O _ O O O O , BIN _ O O O O _ O O , BIN _ O O O _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O O _ O O O , BIN _ _ O O _ O O _ , BIN _ _ O O O O O _ , BIN _ _ _ O O O _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '1' ASCII=31H [No.49] */ { BIN _ _ _ _ O O _ _ , BIN _ _ O O O O _ _ , BIN _ _ O O O O _ _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '2' ASCII=32H [No.50] */ { BIN _ _ _ O O O _ _ , BIN _ _ O O O O O _ , BIN _ O O O _ O O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O O _ _ O O , BIN _ _ O O _ _ O O , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ O O O _ _ , BIN _ _ O O O _ _ _ , BIN _ O O O O O O O , BIN _ O O O O O O O , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '3' ASCII=33H [No.51] */ { BIN _ _ _ O O O _ _ , BIN _ _ O O O O O _ , BIN _ O O O _ O O O , BIN _ O O _ _ _ O O , BIN _ _ _ _ _ _ O O , BIN _ _ _ _ _ O O O , BIN _ _ _ O O O O _ , BIN _ _ _ O O O O _ , BIN _ _ _ _ _ O O O , BIN _ _ _ _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O O _ O O O , BIN _ _ O O O O O _ , BIN _ _ _ O O O _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '4' ASCII=34H [No.52] */ { BIN _ _ _ _ O O O _ , BIN _ _ _ O O O O _ , BIN _ _ _ O O O O _ , BIN _ _ O O _ O O _ , BIN _ _ O O _ O O _ , BIN _ _ O O _ O O _ , BIN _ O O _ _ O O _ , BIN _ O O _ _ O O _ , BIN _ O O _ _ O O _ , BIN _ O O _ _ O O _ , BIN _ O O O O O O O , BIN _ O O O O O O O , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '5' ASCII=35H [No.53] */ { BIN _ O O O O O O O , BIN _ O O O O O O O , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O O O O _ _ , BIN _ O O O O O O _ , BIN _ _ _ _ _ O O O , BIN _ _ _ _ _ _ O O , BIN _ _ _ _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ O O O , BIN _ O O O O O O _ , BIN _ _ O O O O _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '6' ASCII=36H [No.54] */ { BIN _ _ _ _ _ O O _ , BIN _ _ _ _ O O O _ , BIN _ _ _ O O O _ _ , BIN _ _ O O O _ _ _ , BIN _ _ O O _ _ _ _ , BIN _ O O O O O _ _ , BIN _ O O O O O O _ , BIN _ O O O _ O O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O O _ O O O , BIN _ _ O O O O O _ , BIN _ _ _ O O O _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '7' ASCII=37H [No.55] */ { BIN _ O O O O O O _ , BIN _ O O O O O O O , BIN _ _ _ _ _ _ O O , BIN _ _ _ _ _ _ O O , BIN _ _ _ _ _ _ O O , BIN _ _ _ _ _ O O O , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ O O O _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ O O O _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ O O O _ _ _ , BIN _ _ O O _ _ _ _ , BIN _ _ O O _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '8' ASCII=38H [No.56] */ { BIN _ _ _ O O O _ _ , BIN _ _ O O O O O _ , BIN _ O O O _ O O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O O _ O O O , BIN _ _ O O O O O _ , BIN _ _ O O O O O _ , BIN _ O O O _ O O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ O O O , BIN _ O O O O O O _ , BIN _ _ O O O O _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '9' ASCII=39H [No.57] */ { BIN _ _ _ O O O _ _ , BIN _ _ O O O O O _ , BIN _ O O O _ O O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O O _ _ O O , BIN _ _ O O O O O O , BIN _ _ _ O O O O O , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ O O O _ , BIN _ _ _ O O O _ _ , BIN _ _ O O O _ _ _ , BIN _ _ O O _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* 'A' ASCII=41H [No.65] */ { BIN _ _ _ O O O _ _ , BIN _ _ _ O O O _ _ , BIN _ _ O O O O O _ , BIN _ _ O O _ O O _ , BIN _ _ O O _ O O _ , BIN _ O O O _ O O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O O O O O O , BIN _ O O O O O O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* 'B' ASCII=42H [No.66] */ { BIN _ O O O O O _ _ , BIN _ O O O O O O _ , BIN _ O O _ _ O O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ O O O , BIN _ O O O O O O _ , BIN _ O O O O O O _ , BIN _ O O _ _ O O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ O O O , BIN _ O O O O O O _ , BIN _ O O O O O _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* 'C' ASCII=43H [No.67] */ { BIN _ _ _ O O O _ _ , BIN _ _ O O O O O _ , BIN _ _ O O _ O O O , BIN _ O O O _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O O _ O O O , BIN _ _ O O O O O _ , BIN _ _ _ O O O _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* 'D' ASCII=44H [No.68] */ { BIN _ O O O O _ _ _ , BIN _ O O O O O _ _ , BIN _ O O _ O O O _ , BIN _ O O _ _ O O _ , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ O O _ , BIN _ O O _ O O O _ , BIN _ O O O O O _ _ , BIN _ O O O O _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* 'E' ASCII=45H [No.69] */ { BIN _ O O O O O O O , BIN _ O O O O O O O , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O O O O O _ , BIN _ O O O O O O _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O O O O O O , BIN _ _ O O O O O O , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* 'F' ASCII=46H [No.70] */ { BIN _ _ O O O O O O , BIN _ O O O O O O O , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O O O O O _ , BIN _ O O O O O O _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* 'G' ASCII=47H [No.71] */ { BIN _ _ _ O O O _ _ , BIN _ _ O O O O O _ , BIN _ _ O O _ O O O , BIN _ O O O _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ O O O O , BIN _ O O _ O O O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O O _ O O O , BIN _ _ O O O O O _ , BIN _ _ _ O O O _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* 'O' ASCII=4FH [No.79] */ { BIN _ _ _ O O O _ _ , BIN _ _ O O O O O _ , BIN _ O O O _ O O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O O _ O O O , BIN _ _ O O O O O _ , BIN _ _ _ O O O _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* 'b' ASCII=62H [No.98] */ { BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O O O O _ _ , BIN _ O O O O O O _ , BIN _ O O _ _ O O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ O O O , BIN _ O O O O O O _ , BIN _ O O O O O _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* 'c' ASCII=63H [No.99] */ { BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ O O O _ _ , BIN _ _ O O O O O _ , BIN _ O O O _ O O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ O O , BIN _ O O O _ O O O , BIN _ _ O O O O O _ , BIN _ _ _ O O O _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* 't' ASCII=74H [No.116] */ { BIN _ _ _ O O _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ O O O O O O _ , BIN _ O O O O O O _ , BIN _ _ _ O O _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ _ O O _ O O , BIN _ _ _ O O _ O O , BIN _ _ _ O O O O O , BIN _ _ _ _ O O O _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* 'ト' ASCII=C4H [No.196] */ { BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O O _ _ _ _ , BIN _ O O O O _ _ _ , BIN _ O O O O O _ _ , BIN _ O O _ O O O _ , BIN _ O O _ _ O O _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* '゙' ASCII=DEH [No.222] */ { BIN O O _ O O O _ _ , BIN O O O _ O O _ _ , BIN _ O O _ O O _ _ , BIN _ O O _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* 'レ' ASCII=DAH [No.218] */ { BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ O O _ , BIN _ O O _ O O O _ , BIN _ O O O O O _ _ , BIN _ O O O O _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* 'ミ' ASCII=D0H [No.208] */ { BIN _ _ O O O _ _ _ , BIN _ _ O O O O O O , BIN _ _ _ _ _ O O O , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ O O O _ _ _ , BIN _ _ O O O O O O , BIN _ _ _ _ _ O O O , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ O O O _ _ _ _ , BIN _ O O O O O O O , BIN _ _ _ _ O O O O , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* 'フ' ASCII=CCH [No.204] */ { BIN _ _ _ _ _ _ _ _ , BIN _ O O O O O O _ , BIN _ O O O O O O O , BIN _ _ _ _ _ _ O O , BIN _ _ _ _ _ _ O O , BIN _ _ _ _ _ _ O O , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ O O _ _ _ , BIN _ _ O O O _ _ _ , BIN _ O O O _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* 'ァ' ASCII=A7H [No.167] */ { BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ O O O O O O _ , BIN _ O O O O O O O , BIN _ _ _ _ _ O O O , BIN _ _ _ _ _ _ O O , BIN _ _ _ O O _ O O , BIN _ _ _ O O _ O O , BIN _ _ O O _ O O _ , BIN _ _ O O _ O O _ , BIN _ _ O O _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* 'ソ' ASCII=BFH [No.191] */ { BIN _ _ _ _ _ _ _ _ , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ _ O O _ _ O O , BIN _ _ O O _ _ O O , BIN _ _ O O _ O O _ , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ _ O O _ _ , BIN _ _ _ O O _ _ _ , BIN _ O O O O _ _ _ , BIN _ O O _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* 'ラ' ASCII=D7H [No.215] */ { BIN _ _ O O O O O _ , BIN _ _ O O O O O _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ O O O O O O _ , BIN _ O O O O O O O , BIN _ _ _ _ _ _ O O , BIN _ _ _ _ _ _ O O , BIN _ _ _ _ _ _ O O , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ O O _ _ , BIN _ _ O O O O _ _ , BIN _ _ O O O _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, /* 'シ' ASCII=BCH [No.188] */ { BIN _ O O _ _ _ _ _ , BIN _ O O O O _ _ _ , BIN _ _ _ O O _ O O , BIN _ _ _ _ _ _ O O , BIN _ O O _ _ _ O O , BIN _ O O O O _ O O , BIN _ _ _ O O _ O O , BIN _ _ _ _ _ _ O O , BIN _ _ _ _ _ _ O O , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ _ O O _ , BIN _ _ _ _ O O _ _ , BIN _ O O O O _ _ _ , BIN _ O O O _ _ _ _ , BIN _ _ _ _ _ _ _ _ , BIN _ _ _ _ _ _ _ _ }, }; #undef BIN #undef O #undef _ ``` ### ラズパイPico 用 #### 環境他 開発環境 : Arduino IDE ライブラリ : TinyHID #### ソース ```arduino: RasPiPic_HID_MouseHostToSerial1 // ==== ピン設定(GPIO2/3でUSBホスト) ==== #define PIN_USB_HOST_DP 2 #undef PIN_5V_EN #include "usbh_helper.h" #include "hardware/watchdog.h" #include "SimpleQueue.h" // SimpleQueue を使用 // ==== バターワースフィルタ用 ==== typedef struct { float b0, b1, b2, a1, a2; } butterworth_coeffs_t; #define SAMPLING_FREQUENCY 100.0 #define CUTOFF_FREQUENCY 10.0 // #define USE_BUTTERWORTH_FLT butterworth_coeffs_t coeffs[2]; #if defined(USE_BUTTERWORTH_FLT) butterworth_coeffs_t butterworth_lowpass(float cutoff_frequency, float sampling_frequency); float butterworth_filter(float data, butterworth_coeffs_t *coeffs, float *filtered, float *prev1, float *prev2); #endif void filter_report(hid_mouse_report_t *report); // 前回状態 static uint8_t prev_buttons = 0; volatile bool g_serial_ready = false; // ==== 停止検知用 ==== static uint32_t last_hid_time = 0; static bool stopped_sent = false; #define IDLE_STOP_MS 100 // 無操作とみなすしきい値(ms) // ==== 送信用メッセージキュー ==== typedef struct { char text[30]; } msg_t; SimpleQueue<msg_t, 2048> MsgQueue; // ==== Core0: デバイス側 ==== void setup() { delay(3000); Serial.begin(115200); delay(1000); Serial1.setTX(0); Serial1.setRX(1); Serial1.begin(115200); delay(1000); #if defined(USE_BUTTERWORTH_FLT) coeffs[0] = butterworth_lowpass(CUTOFF_FREQUENCY, SAMPLING_FREQUENCY); coeffs[1] = butterworth_lowpass(CUTOFF_FREQUENCY, SAMPLING_FREQUENCY); #endif Serial.println("setup: start"); Serial.println("setup: done, waiting for host..."); g_serial_ready = true; } void loop() { // 何もしない(Core0 は USB に関与しない) } // ==== Core1: ホスト側 ==== void setup1() { while (!g_serial_ready) delay(1); // Pico W の LED 初期化 pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, LOW); uint32_t now = millis(); last_hid_time = now; stopped_sent = false; // ウォッチドッグ有効(約 2 秒) // timeout_ms は環境に合わせて調整可 watchdog_enable(2000, 1); rp2040_configure_pio_usb(); USBHost.begin(1); } void loop1() { // 通常処理中は LED 消灯 digitalWrite(LED_BUILTIN, LOW); // ここで毎回 feed watchdog_update(); // もし USBHost.task() がハングしたら、 // ここから先に一切進まない → watchdog がタイムアウト → 自動リセット USBHost.task(); uint32_t now = millis(); // ==== 停止検知 ==== if (!stopped_sent && ((now - last_hid_time) > IDLE_STOP_MS)) { msg_t m; snprintf(m.text, sizeof(m.text), "$IDL:"); noInterrupts(); MsgQueue.Put(&m); interrupts(); stopped_sent = true; } // ==== Queue 出力 ==== uint32_t start_ms = now; const uint32_t MAX_SEND_DURATION_MS = 5; while ((millis() - start_ms) < MAX_SEND_DURATION_MS) { msg_t m; bool has; noInterrupts(); has = MsgQueue.Get(&m); interrupts(); if (!has) break; size_t len = strlen(m.text); Serial.printf("Send>\"%s\"\r\n", m.text); Serial1.printf("%s\n", m.text); delay(1); } // USBHost.task() 呼び出し頻度を落としすぎない程度のウェイト delay(5); } // ==== TinyUSB ホストコールバック ==== extern "C" { void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len) { // コールバック側でも念のため feed watchdog_update(); if (tuh_hid_interface_protocol(dev_addr, instance) == HID_ITF_PROTOCOL_MOUSE) tuh_hid_receive_report(dev_addr, instance); } void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) { watchdog_update(); } void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) { // HID レポート受信 → LED 点灯 digitalWrite(LED_BUILTIN, HIGH); uint32_t now = millis(); last_hid_time = now; stopped_sent = false; // 生きている間は必ず feed watchdog_update(); if (len < 3) { tuh_hid_receive_report(dev_addr, instance); return; } hid_mouse_report_t safe = {0}; safe.buttons = report[0]; safe.x = (int8_t)report[1]; safe.y = (int8_t)report[2]; int8_t wheel = (len >= 4) ? (int8_t)report[3] : 0; filter_report(&safe); bool moved = (safe.x != 0) || (safe.y != 0); bool btn_changed = (safe.buttons != prev_buttons); bool wheel_moved = (wheel != 0); if (moved) { msg_t m; snprintf(m.text, sizeof(m.text), "$MOV:%d,%d", safe.x, safe.y); noInterrupts(); MsgQueue.Put(&m); interrupts(); } if (btn_changed) { const char* names[3] = {"L", "R", "M"}; const uint8_t masks[3] = {0x01, 0x02, 0x04}; for (int i = 0; i < 3; i++) { bool old_on = prev_buttons & masks[i]; bool new_on = safe.buttons & masks[i]; if (old_on != new_on) { const char* state = new_on ? "DW" : "UP"; msg_t m; snprintf(m.text, sizeof(m.text), "$BTN:%s,%s", names[i], state); noInterrupts(); MsgQueue.Put(&m); interrupts(); } } } if (wheel_moved) { msg_t m; snprintf(m.text, sizeof(m.text), "$WHL:%d", wheel); noInterrupts(); MsgQueue.Put(&m); interrupts(); } prev_buttons = safe.buttons; tuh_hid_receive_report(dev_addr, instance); } } // extern "C" // ==== フィルタ処理 ==== void filter_report(hid_mouse_report_t *report) { #if defined(USE_BUTTERWORTH_FLT) static float filtered[2] = {0}; static float prev1[2] = {0}; static float prev2[2] = {0}; butterworth_filter(report->x, &coeffs[0], &filtered[0], &prev1[0], &prev2[0]); butterworth_filter(report->y, &coeffs[1], &filtered[1], &prev1[1], &prev2[1]); report->x = (int8_t)filtered[0]; report->y = (int8_t)filtered[1]; #endif } #if defined(USE_BUTTERWORTH_FLT) butterworth_coeffs_t butterworth_lowpass(float cutoff_frequency, float sampling_frequency) { butterworth_coeffs_t coe; float omega = 2.0 * PI * cutoff_frequency / sampling_frequency; float s = sin(omega); float t = tan(omega / 2.0); float alpha = s / (2.0 * t); coe.b0 = 1.0 / (1.0 + 2.0 * alpha + 2.0 * alpha * alpha); coe.b1 = 2.0 * coe.b0; coe.b2 = coe.b0; coe.a1 = (2.0f * (alpha * alpha - 1.0f)) * coe.b0; coe.a2 = (1.0f - 2.0f * alpha + 2.0f * alpha * alpha) * coe.b0; return coe; } float butterworth_filter(float data, butterworth_coeffs_t *coeffs, float *filtered, float *prev1, float *prev2) { float output = coeffs->b0 * data + coeffs->b1 * (*prev1) + coeffs->b2 * (*prev2) - coeffs->a1 * (*filtered) - coeffs->a2 * (*prev1); *prev2 = *prev1; *prev1 = data; *filtered = output; return output; } #endif ``` ```cpp: simpleQueue.h /* Copyright (c) TakSan This source file is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License (CC BY-SA 4.0). For license terms and additional notes regarding commercial use, please refer to the LICENSE.txt file in the root directory of this repository. 著作権者:TakSan このソースファイルは Creative Commons 表示 - 継承 4.0 国際 ライセンス(CC BY-SA 4.0)の下に公開されています。 ライセンスの詳細および商用利用に関する追加情報は、 本リポジトリのルートにある LICENSE.txt を参照してください。 */ #ifndef SimpleQueue_h #define SimpleQueue_h /** * @class SimpleQueue * @brief $EN{{Simple ring buffer queue class template}}$JP{{シンプルなリングバッファキューのクラステンプレート}} * @tparam DataType $EN{{Type of data stored in the queue}}$JP{{キューに格納されるデータ型}} * @tparam _Size $EN{{Size of the queue buffer. Default is 1024}}$JP{{キューバッファのサイズ。デフォルトは1024}} */ template <class DataType, int _Size = 1024> class SimpleQueue { public: /** * @brief $EN{{Constructor: Initializes the queue}}$JP{{コンストラクタ:キューを初期化する}} */ SimpleQueue(void) { Clear(); } /** * @brief $EN{{Clears the queue by resetting positions and buffer.}}$JP{{位置とバッファをリセットしてキューをクリアする}} */ void Clear(void) { _PutPos = 0; _GetPos = 0; memset(_DataBuffer, 0, sizeof(_DataBuffer)); } /** * @brief $EN{{Inserts one data item into the queue.}}$JP{{データを1つキューに追加します}} * @param data $EN{{Pointer to the data to insert.}}$JP{{挿入するデータへのポインタ}} * @return $EN{{Result}}$JP{{処理結果}} * @retval true $EN{{Success}}JP{{成功}} * @retval true $EN{{The queue is full}}JP{{キューが満杯}} */ bool Put(DataType* data) { unsigned int NextPos = (_PutPos < _Size) ? _PutPos + 1 : 0; if (NextPos == _GetPos) return (false); _DataBuffer[_PutPos] = *data; _PutPos = NextPos; return (true); } /** * @brief $EN{{Retrieves one data item from the queue.}}$JP{{データを1つキューから取得します。}} * @param data $EN{{Pointer to store the retrieved data.}}$JP{{取得したデータを格納するポインタ。}} * @return $EN{{Result}}$JP{{処理結果}} * @retval true $EN{{Success}}JP{{成功}} * @retval true $EN{{The queue is empty}}JP{{キューが空}} */ bool Get(DataType* data) { unsigned int NextPos; if (_GetPos == _PutPos) return (false); NextPos = (_GetPos < _Size) ? _GetPos + 1 : 0; *data = _DataBuffer[_GetPos]; _GetPos = NextPos; return (true); } /** * @brief $EN{{Checks if there is data available in the queue.}}$JP{{キュー内にデータが存在するかを確認します。}} * @return $EN{{true if data exists, false otherwise.}}$JP{{データが存在すればtrue、存在しなければfalse。}} */ bool DataExists(void) { if (_GetPos == _PutPos) return (false); return (true); } private: unsigned int _PutPos; //!< $EN{{Position to insert the next data.}}$JP{{次にデータを挿入する位置。}} unsigned int _GetPos; //!< $EN{{Position to retrieve the next data.}}$JP{{次にデータを取得する位置。}} //unsigned int _PutErrorCount = 0; //!< デバッグ用*/ //unsigned int _GetErrorCount = 0; //!< デバッグ用*/ //unsigned int _GetCount = 0; //!< デバッグ用*/ DataType _DataBuffer[_Size]; //!< $EN{{Data buffer to hold queue items.}}$JP{{キューのデータを格納するバッファ。}} }; #endif //SimpleQueue_h ``` # 謝辞 せっかく機材を提供いただいたのに恐縮ですが、試行錯誤でうまくいかない日々があり、方式転換に大分時間を奪われました。(最初はマウスではなかったが、そのデバイスが再起不能になったためかなり直前にマウスに方向転換した)。 その為、最終日までギリギリまで修正を繰り返していたり、3Dプリント筐体が最終前日に全部そろったとバタバタの上、ソフトの方も結構最終段階までうまくいかず動作画像も最終日に流れ込むことに。そんなか迎えた最終日に急に音が全くでなくなりました。 パニックになる中ばらして修復に最終まで粘ってみましたが、復活したと思ったらアンプが発振しているのか故障してしまったのか、うんともすんともで、最終日に撮るつもりだった動画が出来ませんでした。T_T (デバッグ中の動作動画すら残っていない..撮っておけばよかった orz) 粘った結果、記事を書く時間もあまり残っていません。 苦し紛れの言い訳を長々とすみませんが、 あと数分間ギリギリまで出来たところまで更新を繰り返して、記事を提出させていただきます。 それ以降は編集不可となるかと思いますので、中途半端な所が目立つかもしれませんがご了承ください。申し訳ありません。 コンテスト結果がでて、編集可能になった時点でまた見直すつもりではいます。そんなわけで限定公開とさせていただきます。 まことに申し訳ありません。