ドットマトリックスLEDを使ってみたい

ドットマトリックスLED

LEDが縦横にいっぱい並んでるやつ。

お買い物

http://akizukidenshi.com/catalog/g/gI-05163/

http://akizukidenshi.com/catalog/g/gM-11246/

GPIO足りんのでドライバも買った。

GPIOのみで点けてみる

アノードに5Vつけて、カソードにGND、間に抵抗入れてる。

できた。アノードピンとカソードピンが交差したところのLEDが点く。

HT16K33を使う

スケッチ例を動かす

接続しまして。自前でデータシートとにらめっこしながらプログラム書いていくのはつらみがあるのでライブラリを使う。

https://github.com/lpaseen/ht16k33

zip形式でダウンロードして読み込む。スケッチ例のlighttestをやってみる。

16×8をそのまま使ってるので、後ろ8個分が間隔空いてるけどできた。

8×8のLEDを2個使う

すっごい汚い。カソード側は共用できるっぽいので2本まとめてHT16K33につないだ。もんでもっかいlighttestする。

いい感じです。

配列で操作できる感じにしたい

ライブラリのメソッドを見ると

 void    begin(uint8_t address);
  void    end();
  void    clearAll(); // clear all LEDs
  uint8_t sleep();  // stop oscillator to put the chip to sleep
  uint8_t normal(); // wake up chip and start ocillator
  uint8_t clearLed(uint8_t ledno); // 16x8 = 128 LEDs to turn on, 0-127
  uint8_t setLed(uint8_t ledno); // 16x8 = 128 LEDs to turn on, 0-127
  void    define7segFont(uint8_t *ptr); // Pass a pointer to a font table for 7seg
  void    define16segFont(uint16_t *ptr); // Pass a pointer to a font table for 16seg
  uint8_t set7Seg(uint8_t dig, uint8_t cha, boolean dp); // position 0-15, 0-15 (0-F Hexadecimal), decimal point
  uint8_t set16Seg(uint8_t dig, uint8_t cha); // position 0-7, see asciifont.h
  boolean getLed(uint8_t ledno,boolean Fresh=false); // check if a specific led is on(true) or off(false)
  uint8_t setDisplayRaw(uint8_t pos, uint8_t val); // load byte "pos" with value "val"
  uint8_t sendLed(); // send whatever led patter you set
  uint8_t set7SegNow(uint8_t dig, uint8_t cha, boolean dp); // position 0-15, 0-15 (0-F Hexadecimal), decimal point and send led in one function
  uint8_t set7SegRaw(uint8_t dig, uint8_t val); // load byte "pos" with value "val"
  uint8_t set16SegNow(uint8_t dig, uint8_t cha); // position 0-17, see asciifont.h and send led in one function
  uint8_t setLedNow(uint8_t ledno); //Set a single led and send led in one function
  uint8_t clearLedNow(uint8_t ledno); //Clear a single led and send led in one function
  uint8_t setBrightness(uint8_t level); // level 0-16, 0 means display off
  uint8_t keyINTflag(); // INTerrupt flag value, set when a key is pressed
  uint8_t keysPressed(); // report how many keys that are pressed, clear means report as if new
  int8_t  readKey(boolean clear=false);  // read what key was pressed, Fresh=false to go from cache
  void    readKeyRaw(KEYDATA keydata,boolean Fresh=true); //read the raw key info, bitmapped info of all key(s) pressed
  uint8_t setBlinkRate(uint8_t rate); // HT16K33_DSP_{NOBLINK,BLINK2HZ,BLINK1HZ,BLINK05HZ}
  void    displayOn();
  void    displayOff();
  // Some helper functions that can be useful in other parts of the code that use this library
  uint8_t i2c_write(uint8_t val);
  uint8_t i2c_write(uint8_t cmd,uint8_t *data,uint8_t size,boolean LSB=false);
  uint8_t i2c_read(uint8_t addr);
  uint8_t i2c_read(uint8_t addr,uint8_t *data,uint8_t size);

0-127の指定で点灯させる感じ。配置は

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127

こう。これを配列でアクセスするっぽくしたい。悩んだ結果

#include 

HT16K33 HT;

uint8_t disptable[8][16][2] = {
  {{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}, {7, 0}, {8, 0}, {9, 0}, {10, 0}, {11, 0}, {12, 0}, {13, 0}, {14, 0}, {15, 0}},
  {{16, 0}, {17, 0}, {18, 0}, {19, 0}, {20, 0}, {21, 0}, {22, 0}, {23, 0}, {24, 0}, {25, 0}, {26, 0}, {27, 0}, {28, 0}, {29, 0}, {30, 0}, {31, 0}},
  {{32, 0}, {33, 0}, {34, 0}, {35, 0}, {36, 0}, {37, 0}, {38, 0}, {39, 0}, {40, 0}, {41, 0}, {42, 0}, {43, 0}, {44, 0}, {45, 0}, {46, 0}, {47, 0}},
  {{48, 0}, {49, 0}, {50, 0}, {51, 0}, {52, 0}, {53, 0}, {54, 0}, {55, 0}, {56, 0}, {57, 0}, {58, 0}, {59, 0}, {60, 0}, {61, 0}, {62, 0}, {63, 0}},
  {{64, 0}, {65, 0}, {66, 0}, {67, 0}, {68, 0}, {69, 0}, {70, 0}, {71, 0}, {72, 0}, {73, 0}, {74, 0}, {75, 0}, {76, 0}, {77, 0}, {78, 0}, {79, 0}},
  {{80, 0}, {81, 0}, {82, 0}, {83, 0}, {84, 0}, {85, 0}, {86, 0}, {87, 0}, {88, 0}, {89, 0}, {90, 0}, {91, 0}, {92, 0}, {93, 0}, {94, 0}, {95, 0}},
  {{96, 0}, {97, 0}, {98, 0}, {99, 0}, {100, 0}, {101, 0}, {102, 0}, {103, 0}, {104, 0}, {105, 0}, {106, 0}, {107, 0}, {108, 0}, {109, 0}, {110, 0}, {111, 0}},
  {{112, 0}, {113, 0}, {114, 0}, {115, 0}, {116, 0}, {117, 0}, {118, 0}, {119, 0}, {120, 0}, {121, 0}, {122, 0}, {123, 0}, {124, 0}, {125, 0}, {126, 0}, {127, 0}}
};

void test() {
  for (int i = 0 ; i < 8; i++) {
    for (int j = 0; j < 16; j++) {
      if (disptable[i][j][1] == 1) {
        disptable[i][j][1] = 0;
      } else {
        disptable[i][j][1] = 1;
      }
    }
  }
}

void dispset() {
  for (int i = 0 ; i < 8; i++) {
    for (int j = 0; j < 16; j++) {
      if (disptable[i][j][1] == 1) {
        HT.setLed(disptable[i][j][0]);
      } else {
        HT.clearLed(disptable[i][j][0]);
      }
    }
  }
}

void setup() {
  HT.begin(0x00);
}

void loop() {
  test();
  dispset();

  HT.sendLed();
  delay(1000);

}

3次元配列を使ってみることにした。これがいいのか悪いのかはわかんない。動かしてみると

動いた。とりあえずここまで。

カテゴリー: したい | タグ: , , , | コメントする

秋月電子通商が臨時休業

http://akizukidenshi.com/catalog/contents2/news.aspx

緊急事態宣言に伴う秋葉原店・八潮店の臨時休業のお知らせ
平素より格別のご高配を賜り、厚くお礼申し上げます。
本日、政府より新型コロナウイルスの感染拡大防止に向けた緊急事態宣言が行われます。

弊社ではお客様の安全と従業員の安全を優先し、秋葉原店と八潮店を5月6日まで休業とさせていただきます。

通販センターは引き続き営業いたしますが、弊社従業員や運送業者に感染の影響が生じた場合には、商品のお届けに遅延が発生しますことを予めご了承お願い申し上げます。 万一、弊社従業員や運送業者に感染の影響が確認された場合や、政府等からの要請により弊社の営業予定が変更となる場合には、こちらに記載しますのでご確認をお願いいたします。

お客様には重ねてご迷惑をおかけしますが、何卒ご理解・ご協力の程お願い申し上げます。

先週お休みだったから、もしやと思っていたら。でも、しかたないよねぇ。

ちなみに先日初めて通販を利用した^^

カテゴリー: のーと | タグ: | コメントする

RGBフルカラーLEDを点けてみたい

RGBフルカラーLED OSTAMA5B31A

そういえば買ったけど使ってないなーって思ったので使う。

お買い物

http://akizukidenshi.com/catalog/g/gI-12167/

http://akizukidenshi.com/catalog/g/gP-00244/

アノードコモン買ったの失敗したかな、やっぱカソードコモンのが俺にとっては分かりやすい感じがする。

接続

めっちゃ線多いんだけどこんなに必要なんだっけ。

点ける

動画

ArduinoってGPIOの出力電圧5Vで、データシート見るとLEDの電圧は一番大きい青・緑でも定格3.1Vなんすよね。赤にいたっては2.1Vで、最大でも3.6Vと2.6V。で、赤が耐えきれなくて壊れた、と。動画の1:09頃ね。やっぱ適当は駄目だな。

カテゴリー: したい | タグ: , | コメントする

自作基板づくり

基板作りたい

DIP化基板のいい感じのサイズがなかったので、やってみたかったFusionPCBをやってみることにした。

意外と簡単だった!ちゃんとできたかなぁ、届くの楽しみ~

参考

https://www.fusionpcb.jp/

https://voltechno.com/blog/eagle-library1/

http://akiracing.com/2017/05/27/eagle_tutorial/

カテゴリー: のーと | タグ: , , , | コメントする

ある範囲から重複無しかつランダムに全部取り出す

または配列をシャッフル

ある範囲(たとえば1-7)を重複なし(1122334とかじゃない)かつランダム(4635271とか)に全部(1-7だったらその範囲を一個ずつ全部)取り出したい。くじ引きとかおみくじのサンプルプログラムはよく見るんだけど、どれか1個みたいな感じなのでちょと違う。探したらそればっかり出てきてめんどくさかった。なんやかんやあってフィッシャー–イェーツのシャッフルという方法を見つけた。テトリスのテトリミノが落ちる順番がそんな感じらしく、どういうプログラムなんだろーと興味が湧いたのがきっかけ。7-bagっていうんだって。

フィッシャー–イェーツのシャッフル

詳しくはWikipediaとか。下は紙とペンの方法らしいけど、それはすっ飛ばしてダステンフェルド、クヌースの方法を試してみたい。

要素数が n の配列 a をシャッフルする(添字は0からn-1):
i を n – 1 から 1 まで減少させながら、以下を実行する
j に 0 以上 i 以下のランダムな整数を代入する
a[j] と a[i]を交換する

こんな。Wikipediaに書いてあることの繰り返しだけど1回自分でやってみないとよく分からんので表にしてみる。各iの終了時の配列の状態で、jは適当。

a
初期配列(n=7) [0] [1] [2] [3] [4] [5] [6]
1 2 3 4 5 6 7
i j
6 1 1 7 3 4 5 6 2
5 4 1 7 3 4 6 5 2
4 0 6 7 3 4 1 5 2
3 3 6 7 3 4 1 5 2
2 1 6 3 7 4 1 5 2
1 0 3 6 7 4 1 5 2

上記ではi = 3 = jなのでこの時は交換しない。というかa[i=3]とa[j=3]で交換してるといえばいいのかな?あとi = 1のときj = 0 or 1なのでここで終了になる。ふむふむ。

ExcelVBAで書く

ちょこちょこっと書ける環境が現状VBAしか無いんだぜ。

Sub FYShuffle()

    Dim arr As Variant
    arr = Array(1, 2, 3)
    
    arr = shuffle(arr)
    Debug.Print arr(0) & arr(1) & arr(2)

End Sub
Function shuffle(a As Variant)

    Dim j As Integer
    Dim t As Integer

    For i = UBound(a) To 1 Step -1
        Randomize
        
        j = Int((i + 1) * Rnd)
        
        t = a(j)
        a(j) = a(i)
        a(i) = t
    Next i
    
    shuffle = a

End Function

1-7だと7!の組み合わせで5040通りとかなので1-3でやった。結果

231
231
213
312
321
321
231
321
231
231

大丈夫かな?検証してみる。

検証

    For i = 1 To 1000000
        For j = 1 To 10
            arr = shuffle(arr)
            Cells(i, j).Value = arr(0) & arr(1) & arr(2)
        Next j
    Next i

こう変えて、結果

123 1665884
132 1667793
213 1665931
231 1666056
312 1667340
321 1666996

うん、これで合ってるのかな?試しにWikipediaにあった、実装上の誤りの方を試してみる。

実装上の誤り

“取得する乱数の範囲を誤ってしまう”

j = Int((i + 1) * Rnd)

j = Int(i * Rnd)

の結果

123 3334715
132 0
213 0
231 3331656
312 3333629
321 0

おお、全然っすね。円順列!

“インデックス j を常に「配列のすべて」から取得する”

j = Int((i + 1) * Rnd)

j = Int(3 * Rnd)

の結果

123 1667208
132 1668235
213 1665363
231 1667749
312 1666487
321 1664958

あれ?なんかそうでもないような気がする。なんでだろ、まぁいいか。

余談

作りたいものがうまく作れないので、ちょっと時間をおこうと思い他のものに手を出してはまた作れずっていうのが続いててなんかヤダ。今も行き詰まってる。何しよっかなー。

参考

https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A3%E3%83%83%E3%82%B7%E3%83%A3%E3%83%BC%E2%80%93%E3%82%A4%E3%82%A7%E3%83%BC%E3%83%84%E3%81%AE%E3%82%B7%E3%83%A3%E3%83%83%E3%83%95%E3%83%AB

https://teratail.com/questions/28507

https://programming-place.net/ppp/contents/algorithm/other/002.html

カテゴリー: のーと | タグ: , , , | コメントする

Arduinoでサーボモーター使ってみたい

サーボモーターSG90

回転を角度にできるやつ。使ってみたいので使い方調べる。

お買い物

http://akizukidenshi.com/catalog/g/gM-08761/

秋月で売ってる。スタンダードなやつみたい。

ちっちゃい!

プレート?ガイド?なんていうのか分かんないけど付いてた。

接続

直で繋いだ。大丈夫かな。

茶=GND、赤=電源[+]、橙=制御信号 [JRタイプ]

スケッチ

0-90-180度の繰り返し

#include <Servo.h>

int pwmpin = 9;
Servo myservo;

void setup() {
  myservo.attach(pwmpin);
}

void loop() {

  myservo.write(0);
  delay(1000);
  myservo.write(90);
  delay(1000);
  myservo.write(180);
  delay(1000);

}

Servo.hをincludeするだけで使える。

0.1秒毎に0-180度に動かす

#include <Servo.h>

int pwmpin = 9;
Servo myservo;

void setup() {
  myservo.attach(pwmpin);
}

void loop() {

  for (int i = 0 ; i < 180 ; i++) {
    myservo.write(i);
    delay(100);
  }

}

こんなとか。

0.5秒ごとにランダム

#include <Servo.h>

int pwmpin = 9;
int deg = 0;
Servo myservo;

void setup() {
  myservo.attach(pwmpin);
  randomSeed(analogRead(0));
}

void loop() {

  deg = random(0, 180);
  myservo.write(deg);
  delay(500);
}

こんなんも。

 

気になることがあって、ピニオンギアとプレートの歯を合わせると、0度ないし180度の時に筐体と直交しないから見た目的に若干角度がついているように見える。なんかヤダな。まぁ安いしちっちゃいからそこは我慢か。

なぜanalogWrite()だとちゃんと動かないのか

最初、analogWriteの説明にPWMって書いてあったので

int pwmpin = 9;
int deg = 0;
Servo myservo;

void setup() {

  pinMode(pwmpin, OUTPUT);
}

void loop() {

  analogWrite(pwmpin, 0);
  delay(1000);
  analogWrite(pwmpin, 127);
  delay(1000);
  analogWrite(pwmpin, 255);
  delay(1000);
}

ていうコードを書いた。analogWriteは0-255の範囲で値を書き込めるっていうから、0-127-255で0-90-180度になると考えたわけだ。でもこの考えは間違ってた。サーボ全然動かんし。

サーボモーターは電圧の変化で角度を変えているわけではない

秋月にあるSG90の資料を見てみると

電圧は4.8V(5V)で、20msの周期での、0.5-2.4msのデューティサイクルによって角度が決まる。PWMを電圧を低く見せるためではなくて(結果的に低くなっているように見えるけど)、パルス幅を使ってるだけってことみたい。PWM=電圧を変化させること、みたいな雑な理解だったから駄目なんだな。

analogWriteの周期とSG90の周期は違う

analogWriteの周波数は、こちらを見ると約490Hz(≒2.04msに1回)で、SG90は上記から50Hz(=20msに1回)なので、都合10倍ぐらい違う。analogWriteの方が短い。ていうかanalogWriteでこの制御信号を作ろうとしてはいけない。なのでdigitalWriteとdelayMicrosecondsを組み合わせて制御信号を作らないといけないみたい。

踏まえたスケッチ

int pwmpin = 9;

void setup() {

  pinMode(pwmpin, OUTPUT);

}

void loop() {

  pulse(0, 1000);
  pulse(90, 1000);
  pulse(180, 1000);

}

void pulse(int deg, int ms) {
  int d = 0;
  int startms = millis();
  int endms = startms + ms;

  d = map(deg, 0, 180, 500, 2400);

  do {
    digitalWrite(pwmpin, HIGH);
    delayMicroseconds(d);
    digitalWrite(pwmpin, LOW);
    delayMicroseconds(20000 - d);
  } while (endms < millis());

  delay(ms);

}

一応動く。

ただ1分ぐらいすると勝手に止まっちゃう。たぶんこれはintのせいなのかな?

参考

https://www.arduino.cc/en/Reference/Servo

http://www.musashinodenpa.com/arduino/ref/index.php?f=0&pos=2153

http://marupeke296.com/EL_Ard_No9_ServoMotor.html

http://e-words.jp/w/Hz.html

カテゴリー: したい | タグ: , , | 1件のコメント

Google Chromeの拡張を作ってみたい③ 顔認識

顔認識

外部ライブラリとか、WebAPIとか、無くても、ブラウザだけあれば顔認識ができるみたいなので試してみる。

Shape Detection API

flags

試験的な機能なようで、有効化しないと使えない。

chrome://flags/#enable-experimental-web-platform-features

に行って、Experimental Web Platform featuresをEnabledにする。

contentScript.js

動作確認

ていうかそもそも拡張機能内で使えるんだろうかと、ふと頭をよぎったのでお試しをしてみる。

https://dalomo.net/blog/files/face/index.html

こんなんを作りまして、codepenのコードを参考に

debugger;
var image = document.getElementsByTagName('img');

try {
    if (window.FaceDetector == undefined) {
        console.error('Face Detection not supported');
        throw new Error();
    }

    console.log('ignition!');
    var faceDetector = new FaceDetector();
    faceDetector.detect(image[0])
        .then(faces => {
            console.log('yes');
        });

} catch (error) {

}

うーんと、imgタグをgetElementsByTagNameで取得。facedetecterが有効化されてるかチェック。元はreturnだったけど、returnだとなんかエラーになるのでtry-catchでexit代わり。そしてnewして、faceDetector.detect(image[0])で検出。image[0]の部分は

It takes an image object (either a CanvasImageSource, Blob, ImageData or an <img> element)

が対応してるみたい。thenだから非同期処理なんだなこれ。そんで、動かしてみますと

あっ!なんかいい感じな気がする!!!!!やっぱりlandmark(目・鼻・口)の位置はまだ取れないみたい。そしたらcanvasのあれやこれやを追加してみる。

canvasを追加

debugger;
var image = document.getElementsByTagName('img');
var ccan = document.createElement('canvas');
document.body.insertBefore(ccan, image[0].nextSibling);

ccan.width = image[0].width;
ccan.height = image[0].height;

var canvas = document.getElementsByTagName('canvas');

var ctx = canvas[0].getContext("2d");
ctx.drawImage(image[0],
    0, 0, image[0].width, image[0].height,
    0, 0, ccan.width, ccan.height);

const scale = ccan.width / image[0].width;

try {
    if (window.FaceDetector == undefined) {
        console.error('Face Detection not supported');
        throw new Error();
    }

    console.log('ignition!');
    var faceDetector = new FaceDetector();
    faceDetector.detect(image[0])
        .then(faces => {
            console.log('yes');

            ctx.lineWidth = 2;
            for (var i = 0; i < faces.length; i++) {
                const face = faces[i].boundingBox;
                ctx.beginPath();
                ctx.strokeStyle = "red";
                ctx.rect(Math.floor(face.x * scale),
                    Math.floor(face.y * scale),
                    Math.floor(face.width * scale),
                    Math.floor(face.height * scale));
                ctx.stroke();
            }
        });

} catch (error) {

}

愚直に書いた。えーとまず、canvasタグは自分の方には書いてなかったのでcreateElementでcanvasを作る。body直下、子っていうらしい、にimgタグがあるのでimgタグのすぐ下にcanvasが作られるようdocument.body.insertBefore(ccan, image[0].nextSibling)てやる。canvasのサイズと画像サイズを同じにして、getContext(“2d”)で描画機能を有効にする。で、drawImageで元画像をcanas上に描画する。コピーみたいな感じか。サイズの比率をとっとく。これはあんま使わなかった。lineWidthで線の太さを2にして、facesの中の顔の数だけ描画してく。faces.boundingBoxには画像内の顔を囲む四角の座標が入ってる。x, yが四角の左上の座標、width, heightが四角の幅と高さ、top, right, bottom, leftがそれぞれ画像内での、上辺のy座標・右辺のx座標・底辺のy座標・左辺のx座標と思われる。そしたら描画に入ってく。beginPathでリセット…初期化みたいなもんかな、して、strokeStyleで色を設定。rectで矩形の設定、左上の座標と幅高をさっきのboundingBoxの値を利用する。Math.floorで切り捨て整数にする。strokeで描画を実行しますと、結果は

うおー!できた!けどあれ?なんか2人いる…。なんでかなーと思ってcodepenの例見たらhtmlのいmgタグにhidden属性が付けられてた。ただこの状態からelem.style.visibility = “hidden”するとそこだけぽっかり空くみたいな感じになるみたい。なので、image[0].style.display = “none”ってした。

1人になった!よっしゃ、そしたら全部の画像にやってみよう。

サンプルページ内の画像全てに適用

debugger;
var image = document.getElementsByTagName('img');
var ctx = [];
for (var j = 0; j < image.length; j++) {
    var ccan = document.createElement('canvas');
    document.body.insertBefore(ccan, image[j].nextSibling);

    ccan.width = image[j].width;
    ccan.height = image[j].height;

    var canvas = document.getElementsByTagName('canvas');

    ctx[j] = canvas[j].getContext("2d");
    ctx[j].drawImage(image[j],
        0, 0, image[j].width, image[j].height,
        0, 0, ccan.width, ccan.height);

    const scale = ccan.width / image[j].width;

    try {
        if (window.FaceDetector == undefined) {
            console.error('Face Detection not supported');
            throw new Error();
        }
        image[j].style.display = "none";
        console.log('ignition!');
        var faceDetector = new FaceDetector();

        var n = 0;
        faceDetector.detect(image[j])
            .then(faces => {
                console.log('yes');
                
                ctx[n].lineWidth = 2;
                for (var i = 0; i < faces.length; i++) {
                    const face = faces[i].boundingBox;
                    ctx[n].beginPath();
                    ctx[n].strokeStyle = "red";
                    ctx[n].rect(Math.floor(face.x * scale),
                        Math.floor(face.y * scale),
                        Math.floor(face.width * scale),
                        Math.floor(face.height * scale));
                    ctx[n].stroke();
                }
                n++;
            });
            
    } catch (error) {

    }
}

おあー…、動くことを目指して一生懸命書いてたらすっげぇ汚ねぇコードになった…。forで回せばなんとかなんだろと思って書き始めたけど、forのブロック内に非同期処理があると、非同期処理側の処理の完了を待たずに処理が進んでしまうので、思ったとおりに動かない。非同期処理なんだから当たり前なんだけど。どうしよっかなーとデバッガでステップインしながら辻褄が合うように直してったらこうなった。ステップインしながら思ったんだけど、for内のカウントが全部終了してから非同期処理の中をやってくみたいな順番なのね。それはそういうもんなのか、それとも自分の書き方がこうだからそうなったのか、調べる気力は湧きませんでした。

赤線の所まで行ったらforの最初に戻って繰り返し。条件式満たしたら、then以下が実行されてった。うーん、どうすればキレイに書けるんだろう、検索語句も分からん。あ、で、結果

できた…!精度的には真正面だとまぁ認識されて、逆さ顔とか、横顔とか画像がちっちゃかったりとかだとあんま認識されないみたい。まぁサンプル数=1だからなんとなくだけど。

参考

https://wicg.github.io/shape-detection-api/

https://codepen.io/miguelao/pen/PmJWro

https://codepen.io/oliverjam/pen/jzBWNB

https://paul.kinlan.me/face-detection/

https://qiita.com/daisu_yamazaki/items/c782f0154fcb784d4406

https://www.mitsue.co.jp/knowledge/blog/frontend/201810/29_1635.html

https://lealog.hateblo.jp/entry/2018/08/24/153251

https://teratail.com/questions/209176

https://developer.mozilla.org/ja/docs/Web/API/Document/createElement

https://qiita.com/kouh/items/dfc14d25ccb4e50afe89

https://pcmanabu.com/javascript.html

https://stackoverflow.com/questions/15318357/show-hide-image-with-javascript

カテゴリー: したい | タグ: , , , | コメントする

Google Chromeの拡張を作ってみたい② 画像置換

画像を置換

画像を置換するやつを作りたいけど、そんなすぐ書ける人でもないので、そういう拡張を作った記事があったのでそちらを読む。

https://levelup.gitconnected.com/chrome-extension-tutorial-replace-images-in-any-website-with-pikachu-de2a6e3548bb

画像をピカチュウに置き換えるやつ。

manifest.json

{
  "name": "Pikachu Everywhere",
  "version": "0.1",
  "description": "Replace every image with Pikachu images.",
  "manifest_version": 2,
  "icons": {
    "16": "assets/images/icon16.png",
    "32": "assets/images/icon32.png",
    "48": "assets/images/icon48.png",
    "128": "assets/images/icon128.png"
  }
}

ここは特に変わらず。こちらの拡張は、コンテンツスクリプトを使うみたい。なので

,
    "content_scripts": [{
        "matches": [""],
        "all_frames": true,
        "js":      ["assets/js/contentScript.js"]
    }]

と追記。matchesが適用サイト。all_framesが指定されたURL要件に一致するすべてのフレームに挿入するか、タブの一番上のフレームにのみ挿入するかを指定、フレームってなんですかね。jsがcontentScript.jsを置いてる場所で相対パスでフォルダ作ってそん中に入れた。

contentScript.js

確認

console.log("Pikachu Everywhere - Content Script is Running");

取りあえず動くか確認用。

動いとりますね。

ピカチュウAPI

https://some-random-api.ml/pikachuimg

こちらを使う。これ面白いね。アクセスすると

{"link":"https://i.imgur.com/oH5Vi6I.gif"}

みたいなJSONを返してくれるサイトみたい。

流れ

  1. Webページ上のすべての画像要素を取得します。
  2. APIにGETリクエストを実行して、ランダムなピカチュウ画像リンクを取得します。
  3. 画像要素のsrc属性を、取得したリンクに置き換えます。

ふむふむ。この2のところがコンテンツスクリプトだけじゃできないから、その部分はバックグラウンドを使うみたい。コンテンツスクリプトはWebページ依存だけど、バックグラウンドはそうじゃないから、クロスオリジンリクエストを処理できる、のか。クロスオリジンリクエストっつーのはAのサイトに書かれたjsからBのサイトにリクエストを投げるみたいなことらし。これはさせないようにしてるってことなんかな。

コンテンツスクリプトから送信されたバックグラウンドスクリプトでメッセージをリッスンします。Webページが開くたびに、コンテンツスクリプトが実行され、画像のURLを要求するメッセージをバックグラウンドスクリプトに送信します。バックグラウンドスクリプトは、その後、ピカチュウAPIへの非同期呼び出しを実行し、リンクを取得してコンテンツスクリプトに送り返します。

っていう流れ。難しそ。

sendMessege

コンテンツスクリプトとバックグラウンドスクリプトのやり取りっていうのがメッセージでやるらしい。とりあえずコンテンツスクリプトからメッセージを送信する。

let images = document.getElementsByTagName('img');
for(let i = 0; i < images.length; i++){
  chrome.runtime.sendMessage({msg: 'image', index: i}, function({data, index}){
    images[index].src = data.link;
  });
}

画像要素を取得してから、その全てをなめてく。chrome.runtime.sendMessageでメッセージ送る。メッセージはJSON形式のオブジェクトで、ここでは形式と、画像のインデックスを送ってるみたい。メッセージが返ってきたらコールバックが呼ばれる。dataが多分APIの取得結果が入って、indexは送ったデータがそのまま返ってくるような感じかなぁ。ほんでimgタグのsrcに返答のデータのリンクを設定するみたいな感じかね。

background.js

background.jsでメッセージを受け取って、APIでJSONを受け取って、それをコンテンツスクリプトに返す。

chrome.runtime.onMessage.addListener(function (message, sender, senderResponse) {
    if (message.msg === "image") {
        fetch('https://some-random-api.ml/pikachuimg')
            .then(response => response.text())
            .then(data => {
                let dataObj = JSON.parse(data);
                senderResponse({ data: dataObj, index: message.index });
            })
            .catch(error => console.log("error", error))
        return true;  // Will respond asynchronously.
    }
});

chrome.runtime.onMessage.addListenerは設定するとメッセージが送られてくるのを待つみたいなやつ。で、送られてきたらコールバック関数を実行する感じ。messageに送られてきたデータ、senderが送ってきた対象、senderResponseが送り返すデータ(JSON)。message.msg === “image”でコンテンツスクリプトから送られてきたデータかを判断してから、fetchする、fetchってなんですか。

https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch

説明があった。HTTPでのやり取りを分かりやすくやってくれるやつみたい。ネットワークでのやり取りは応答待ちがあって、待ってる間に処理が次に進んだりしちゃうと困るから、応答が来てから次の処理に進むみたいな時に使うやつみたい。非同期処理ってやつか。ちゅーことで、

fetch(‘https://some-random-api.ml/pikachuimg’)でAPIにアクセス。この=>矢印みたいなのはアロー関数とかアロー式っていうらしい。thenっていうのが、アクセスした結果が返ってきたら、って感じで、これだけで非同期な処理ができるようでっす。初めてなので、アロー関数を使わない書き方にすると

.then(function (response) {
    return response.text()
})

こんな書き方になるはず。だからAPIにリクエスト送った返事をテキストに変換してるんだと思う。そんでdata、このdataはどっから出てきたんだと思ったけど多分textに変換した結果が入ってんだろうな、を、JSON形式にパースして、senderResponseにdataとindexをつけて入れて返すみたい。この時senderResponseはコンテンツスクリプト内のプログラムで使えるような形式じゃないとだめだし、この場合dataObjの中にはAPIから取得した

{"link":"https://i.imgur.com/oH5Vi6I.gif"}

が入ってるはず。catchはfetch中にエラーが起きたらそこに飛んでエラーですよってコンソールに表示するやつ。return trueが、このリスナ内の処理は非同期処理がありますよーってやるために書くみたい。これが無いと、非同期処理であることを無視して処理が次に進むらしい。そんで、APIを利用してる時は、manifestに許可が必要みたい。

,
    "permissions": [
        "https://some-random-api.ml/*"
    ]

うむ。

結果

試しにブログトップに行ってみたら

あれ?広告とアイコンぐらいしか変わってない。なんでだろーと思ってソース見てみたらエントリーのとこに使われてる画像はsrcじゃなくて、data-srcとかsrcsetとかがあった。

data-srcはよく分かんなかったので、srcsetの方に変えてみたら

変わってないのもあるけどほぼなった。webむつかしーね。

カテゴリー: したい | タグ: , | コメントする

Google Chromeの拡張を作ってみたい チュートリアル

Chromeの拡張

やってみたいことがあるので、Chromeの拡張の作り方を調べる。

チュートリアルやる

https://developer.chrome.com/extensions/getstarted

チュートリアルがあったので、チュートリアルをやる。ここの背景色を変えるやつみたいだけど、せっかくなのでこのブログのを変えたい。

manifest.json

{
  "name": "Getting Started Example",
  "version": "1.0",
  "description": "Build an Extension!",
  "manifest_version": 2
}

これだけでもう拡張として認識されるみたい。

なった。

background.js

落としてきて、同じフォルダに入れる。あと、manifestのdescription以下に

"background": {
    "scripts": ["background.js"],
    "persistent": false
  },

追記することでbackground.jsを使いますよーという意味になるみたい。persistantは知らん。background.jsの中身は

'use strict';

chrome.runtime.onInstalled.addListener(function() {
  chrome.storage.sync.set({color: '#3aa757'}, function() {
    console.log("The color is green.");
  });
});

なんですが、はっきり言ってさっぱり意味が分からん。うーんと’use strict’;がコードを厳格に書かなきゃいけないみたいなモードの文言みたい。chrome.runtime.onInstalled.addListenerが拡張がインストールされた時とかに走るみたいなやつ。chrome.storage.sync.setがchrome内にデータを保存するやつで、syncが同期可能な感じで保存、ここではcolorというキーに、#3aa757という値を保存してる。そのコールバック…コールバックもよく分かってないけど、成功・失敗時にconsole.logに表示する。

ここの、chrome.storageはchromeで用意されてるAPIで、使うにはmanifest.jsonにpermissionを追加する必要がある。なので、descriptionの下に

"permissions": ["storage"],

を追記する。拡張機能を再読込すると、バックグラウンドページのリンクがついて、クリックするとコンソールが開く。

UserInterface (popup.html, etc)

popup.html

https://dalomo.net/blog/files/popup.html

これを落としまして。htmlうまく貼れない。styleタグ内がcssってやつでボタン作ってる。button idをchangeColorとする。scriptがよく分かんない。ボタン押した時?html開いた時?どっちで実行されんだろ。popup.jsも作ってない。どこにあるんだこれ。とりあえず先に進むとして、popup.htmlを使うためにも、manifest.jsonに追記する必要がある。

"page_action": {
    "default_popup": "popup.html"
  },

こうか。page_actionってのがメニューんとこにあるアイコンをどうのこうのするやつみたい。

icon

アイコンの指定もここでできてimgを落としてきて同じフォルダに置き、

,
      "default_icon": {
        "16": "images/get_started16.png",
        "32": "images/get_started32.png",
        "48": "images/get_started48.png",
        "128": "images/get_started128.png"
      }

また追記。拡張機能管理ページ、アクセス許可の警告、およびファビコン用にも

"icons": {
    "16": "images/get_started16.png",
    "32": "images/get_started32.png",
    "48": "images/get_started48.png",
    "128": "images/get_started128.png"
  },

と追記。

アイコンついた。

declarativeContent

指定urlによってアイコンの状態を変化させるためにbackground.jsのさっきのリスナ内に

chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
    chrome.declarativeContent.onPageChanged.addRules([{
      conditions: [new chrome.declarativeContent.PageStateMatcher({
        pageUrl: {hostEquals: 'developer.chrome.com'},
      })
      ],
          actions: [new chrome.declarativeContent.ShowPageAction()]
    }]);
  });

こう。ううう、何だこれは…。chrome.declarativeContent

to take actions depending on the content of a page, without requiring permission to read the page’s content.

ページのコンテンツを読み取る許可を必要とせずに、ページのコンテンツに応じてアクションを実行します。

うん。で、onPageChangedっていうぐらいだから、ページを遷移した時に自分で設定したルールとマッチするか、みたいな感じだろか。そいでそのルールの編集がremoveRulesaddRulesかな。

https://developer.chrome.com/extensions/events

ここの

Event objects may support rules. These event objects don’t call a callback function when events happen but test whether any registered rule has at least one fulfilled condition and execute the actions associated with this rule. Event objects supporting the declarative API have three relevant methods: events.Event.addRules, events.Event.removeRules, and events.Event.getRules.

イベントオブジェクトはルールをサポートする場合があります。これらのイベントオブジェクトは、イベントが発生したときにコールバック関数を呼び出しませんが、登録されたルールに少なくとも1つの条件を満たす条件があるかどうかをテストし、このルールに関連付けられたアクションを実行します。宣言型APIをサポートするイベントオブジェクトには、events.Event.addRules、 events.Event.removeRules、およびevents.Event.getRulesの 3つの関連メソッドがあり ます。

うん。そのルールの書き方が

var rule = {
    conditions: [ /* my conditions */ ],
    actions: [ /* my actions */ ]
};

が基本形で、最低でもconditionsとactionsが必要。

https://developer.chrome.com/extensions/events#type-Rule

  • conditions
    • List of conditions that can trigger the actions.
  • actions
    • List of actions that are triggered if one of the conditions is fulfilled.

ここではconditionsにchrome.declarativeContent.PageStateMatcherを使って、pageUrl(UrlFilterページのトップレベルURLの 条件が満たされている場合に一致)のhostEquals(URLのホスト名が指定された文字列と等しい場合に一致)でdeveloper.chrome.comを指定してる。このURLをdalomo.netに変えりゃいいんだな。まぁ後にしよ。で、actionsにchrome.declarativeContent.ShowPageActionでメニューバーのアイコンに色付いて使えるようになるし、アクティブタブへのアクセスが許可される。分かりづらいよぅ。慣れてないからなのかなぁ…。

popup.js

おぉ、さっき作ったpopup.htmlで呼ばれる外部スクリプトのpopup.jsだ。

'use strict';

let changeColor = document.getElementById('changeColor');
chrome.storage.sync.get('color', function(data) {
  changeColor.style.backgroundColor = data.color;
  changeColor.setAttribute('value', data.color);
});

えーっと、letは変数のスコープがブロック内になるやつ。そこにgetElementById(‘changeColor’)でエレメントを取得。changeColorが設定されてるタグはButtonなのでそれが取得される。chrome.storage.sync.getでこの前保存したキーのcolorを使って値を取り出す。そのコールバックで、取得した値を引数として、buttonであるchangeColorのstyle.backgroundColorを値で設定して、setAttributeでvalue属性に値を設定する。でいいのかな。data.colorっていう書き方でなんで値が引っ張れるのか分からん。style.colorっていうのがあって、それの戻り値がそれっぽい感じだったけど、ここのdataってstyleなのか?どうなの?Javascriptはそういうもんなのかもしれない。ここまで終わってpopup.htmlは

たんと追加されてた。うーんと、これstyleとvalueって何が違うんだろ。試しにstyleの方を消してみたら色が付かなかった。でもvalue消しても色付いたままだった。なんだろな。

LayerLogic

ようやく背景色変えるところだ。popup.jsに

changeColor.onclick = function(element) {
  let color = element.target.value;
  chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
    chrome.tabs.executeScript(
        tabs[0].id,
        {code: 'document.body.style.backgroundColor = "' + color + '";'});
  });
};

を追加。buttonであるchangeColorの.onclickで、クリックした際にプログラムが走る。だけど、ここのelementが分からない。見た感じchangeColorが入ってるっぽいんだけど、そういうもんとして捉えるしかないんだろうか。代入する文が見当たらないからなんでこのまま使えてるのか困惑してしまう…。こちら

  • イベント発生時の情報をイベントオブジェクトとして引数に受け取ることができる。これはJavaScript側で自動的に、イベントハンドラに指定したコールバック関数へ渡してくれるもの。
  • イベントハンドラとして設定したコールバック関数の引数にイベントオブジェクト「e」を設定しています。「e」はイベント発生時の情報を持つオブジェクトなので「e.target.id」のように情報を得ることが可能。

このあたりなのかな。だからelement.target.valueでさっき属性を追加したvalueの値を引っ張れるんだと思う。んで、chrome.tabs.queryでqueryInfoをactive(タブがウィンドウでアクティブかどうか。)かつ、currentWindow(タブが現在のウィンドウにあるかどうか。)なやつを指定して、コールバックに(ここのtabsも分からん)、chrome.tabs.executeScriptを設定する。プログラムによってContent Scriptsにプログラムを挿入するみたいです。引数は、tabs[0].idで現在のタブを指定して、codeを挿入してる。「document.body.style.backgroundColor =  color;」っていうコードを作って挿入して実行してる。疲れた。あ、manifestに

"activeTab"

を追記。background.jsのサイトをdalomo.netに変更して実行してみると

なった!

options.html

緑じゃないオプションを追加するためのページみたいなのを作る。とりあえずファイルを落としてきまして同じフォルダに置いて、manifestに

"options_page": "options.html",

と追記。そんでoptions.jsを作る。

let page = document.getElementById('buttonDiv');
const kButtonColors = ['#3aa757', '#e8453c', '#f9bb2d', '#4688f1'];
function constructOptions(kButtonColors) {
  for (let item of kButtonColors) {
    let button = document.createElement('button');
    button.style.backgroundColor = item;
    button.addEventListener('click', function () {
      chrome.storage.sync.set({ color: item }, function () {
        console.log('color is ' + item);
      })
    });
    page.appendChild(button);
  }
}
constructOptions(kButtonColors);

getElementById(‘buttonDiv’)でdivタグのとこを取得。constは定数で色コードの配列を作る。constructOptions関数は、その定数の引数として、定数の配列のアイテム数分createElementでボタンを作ってく。style.backgroundColorでボタンの色に定数の色を設定し、addEventListenerでボタンクリック時の処理を書いてく。storage.sync.setでcolorキーに定数の色をセットするから、色が変わるのか。page.appendChildでbuttonDivの直下末尾にbuttonを追記して完了。

結果こういうソースになる。なるほどにゃん。

愚痴

日本語の情報とか見るとさー、manifestとcontent.jsだけでできるとか書いてあるからさー、それでいいのかと思ってたら、チュートリアルだとこんな感じになるのね。どっちがいいのかは俺にはわかんないけど、チュートリアルは俺には敷居が高いな…。やりたいことはあるんだけど、できる気がしなくなってきたな。

参考

https://qiita.com/k7a/items/26d7a22233ecdf48fed8

https://qiita.com/miri4ech/items/ffcebaf593f5baa1c112

https://qiita.com/y-temp4/items/289686fbdde896d22b5e

カテゴリー: したい | タグ: , , , | コメントする

ジョイスティックマウスのカーソルを滑らかに動かしたい

ジョイスティックマウス

滑らかにしたい。したいので調べたことを書いてく。

ジョイスティックの傾き

左から静止状態、X軸が0に変わるギリギリの位置、止まるまで目一杯動かした時の位置。意識的には右の位置で0に変わってるように思うけど、実際は真ん中の位置で0になる。22°ぐらいで0、でも39°ぐらいまでは動く。この差が理想と現実の差を生んでるように思う。ストッパーをつけるとか、値を伸張するとか?ストッパーは3Dプリンタとか使わないとだし、値は伸長したところで22°ー39°間は値が動かなくなるので意味無い気もする。

ジョイスティックの可変抵抗の特性

https://www.marutsu.co.jp/contents/shop/marutsu/mame/78.html

可変抵抗には特性があるそうで、ABCカーブの違いがあるみたい。じゃあこのスティックに使われている可変抵抗の特性はなんじゃらほいと思ったけど、調べるとしたら分解して抵抗を調べるか、角度ごとに値のサンプルを取ってカーブの近似を求めるとかしないといけないと思うのですが、正直できる気がしないし気が引けるのでやりたくない。ここはBカーブであるとの前提のもとに動いてはどうか。どうなんでしょうか。

C++のint-long型の端数処理

map()内部で除算があり、analogRead()はint型で、map()はlong型なのでどちらも整数型だから端数が出たときの丸め方で数値がずれるんじゃないかと思った。実験してみる。

void setup() {
  Serial.begin(9600);
  delay(1000);
  for (int i = 0 ; i <= 1023; i++) {
    Serial.print(map(i, 0, 1023, -3, 3));
    Serial.println();
  }
}

void loop() {
}

こんなコードを書きましてー、出てきた数字をエクセルさんで計算した結果と比較してみる。長いので変化した部分だけ。

-3≦out≦3の場合

raw map
(excel)
map
(arduino)
roundup
(excel)
rounddown
(excel)
0 -3 -3 -3 -3
1 -2.994134897 -3 -3 -2
2 -2.988269795 -3 -3 -2
169 -2.008797654 -3 -3 -2
170 -2.002932551 -3 -3 -2
171 -1.997067449 -2 -2 -1
172 -1.991202346 -2 -2 -1
339 -1.011730205 -2 -2 -1
340 -1.005865103 -2 -2 -1
341 -1 -1 -1 -1
342 -0.994134897 -1 -1 0
343 -0.988269795 -1 -1 0
510 -0.008797654 -1 -1 0
511 -0.002932551 -1 -1 0
512 0.002932551 0 1 0
513 0.008797654 0 1 0
680 0.988269795 0 1 0
681 0.994134897 0 1 0
682 1 1 1 1
683 1.005865103 1 2 1
684 1.011730205 1 2 1
851 1.991202346 1 2 1
852 1.997067449 1 2 1
853 2.002932551 2 3 2
854 2.008797654 2 3 2
1021 2.988269795 2 3 2
1022 2.994134897 2 3 2
1023 3 3 3 3

-12≦out≦12の場合

グラフの引用は上と同じとこだけ。

raw map
(excel)
map
(arduino)
roundup
(excel)
rounddown
(excel)
0 -12 -12 -12 -12
1 -11.97653959 -12 -12 -11
2 -11.95307918 -12 -12 -11
169 -8.035190616 -9 -9 -8
170 -8.011730205 -9 -9 -8
171 -7.988269795 -8 -8 -7
172 -7.964809384 -8 -8 -7
339 -4.046920821 -5 -5 -4
340 -4.023460411 -5 -5 -4
341 -4 -4 -4 -4
342 -3.976539589 -4 -4 -3
343 -3.953079179 -4 -4 -3
510 -0.035190616 -1 -1 0
511 -0.011730205 -1 -1 0
512 0.011730205 0 1 0
513 0.035190616 0 1 0
680 3.953079179 3 4 3
681 3.976539589 3 4 3
682 4 4 4 4
683 4.023460411 4 5 4
684 4.046920821 4 5 4
851 7.964809384 7 8 7
852 7.988269795 7 8 7
853 8.011730205 8 9 8
854 8.035190616 8 9 8
1021 11.95307918 11 12 11
1022 11.97653959 11 12 11
1023 12 12 12 12

うーん。エクセルも癖があって、はっきりとは言えないけど。負の数の場合、エクセルでいうROUNDUPで正の数だとROUNDDOWNと一致する。ただ、Arduinoの方がきれいに階段状になっていて、これはエクセルがアレなのかな?でもまぁ、510-513あたりを見るとほんの少しだけど、Arduinoのmap()は正側に値が振れているような気がする。感覚的には、ROUNDDOWNが近いかなぁと思った。map()のリファレンスには

The map() function uses integer math so will not generate fractions, when the math might indicate that it should do so. Fractional remainders are truncated, and are not rounded or averaged.

Notes & Warnings

As previously mentioned, the map() function uses integer math. So fractions might get suppressed due to this. For example, fractions like 3/2, 4/3, 5/4 will all be returned as 1 from the map() function, despite their different actual values. So if your project requires precise calculations (e.g. voltage accurate to 3 decimal places), please consider avoiding map() and implementing the calculations manually in your code yourself.

ていうことなので、自分でやれってことかぁ。うーんうーん。

直線のグラフを曲線に変換

ハードウェアの電圧からAnalogRead()で数値を取得するところの特性は分からないものの、取得した数値は0-1023で変化する直線のグラフになる。で、等速直線運動っていうのは人間の感覚と合ってないらしい。

https://www.autoexe.co.jp/kijima/column8.html

最初は徐々に速度が上がっていって、時間が経つと速度の上がり方が急になる、みたいな。そういうのが人間の感覚に合ってるみたい。だから、0-1023の値をそういう感じに変化させることができれば滑らか感を得られるのではと思った。で、そういう変換、直線のグラフにカーブをかけるような変換はどういうのがあるんだろうと調べてみた。イメージとしては

こういう感じ。考え方合ってんのか不安になってきた。どうなんだろ。

y=x^3

https://www.wolframalpha.com/input/?i=y%3Dx%5E3&lang=ja

x^3のグラフがそれっぽい。とりあえずエクセルで0-1023に合うように試してみる。

  1. 0-1023を、-1から1に変換する。
    • =((A2-0)/(1023-0))*(1-(-1))+(-1)
  2. 1の値を3乗する。
    • =POWER(B2,3)
  3. 2の値を0-1023に変換する。
    • =((C2-(-1))/(1-(-1)))*(1023-0)

するとこんな感じのグラフになった。

よさげな気がする。3のとこでMouse.moveで使う範囲に変換してもいいかな。

シグモイド関数

探してたらシグモイド関数というのを見つけた。

https://ja.wikipedia.org/wiki/%E3%82%B7%E3%82%B0%E3%83%A2%E3%82%A4%E3%83%89%E9%96%A2%E6%95%B0

ここの標準シグモイド関数っていうのがそれっぽい。けど0.5付近で急になるのじゃなくてそこは潰れてほしい。てことは2つ重ねりゃいいんじゃね?

CC BY-SA 3.0, Link

こんな感じに。やってみよー。

ゲインが1の場合(a=1)

グラフ見るとxが-6から6の区間で、yが0-1でいい感じに変化してるみたいなのでそんな感じにする。

  1. 0-511・512-1023の2区間に分けて、それぞれ(-6)-6に変換
    • =((A2-0)/(511-0))*(6-(-6))+(-6)
    • =((A514-512)/(1023-512))*(6-(-6))+(-6)
  2. 1-1をシグモイド関数で変換して、(-1)-0の区間にするため-1する。1-2は変換するだけ。
    • =((TANH(B2/2)+1)/2)-1
    • =(TANH(B515/2)+1)/2
  3. 目的の区間に変換。ここでは0-1。
    • =(C2-(-1))/(1-(-1))

でけた。

ゲインが5の場合(a=5)

ゲインを5にすると、xが(-1)-1の時に、yが0-1になるっぽく、きれいなのでこっちでもやってみる。上記の流れをちょっと変えるだけ。

  1. (-1)-1に変換
    • =((A2-0)/(511-0))*(1-(-1))+(-1)
    • =((A514-512)/(1023-512))*(1-(-1))+(-1)
  2. シグモイド関数で変換
    • =((TANH((B2*5)/2)+1)/2)-1
    • =(TANH((B514*5)/2)+1)/2
  3. 0-1の区間に変換
    • =(C514-(-1))/(1-(-1))

できた。

違い

gif動画

a=1とa=5の場合だと曲がりがちょっとだけ違う。どうがいいんだろな。

これさ

数式を1行にまとめると

=(((((TANH(((((A2-0)/(511-0))*(1-(-1))+(-1))*5)/2)+1)/2)-1)-(-1))/(1-(-1)))*(1-0)

こんな長くなる。どうやってコード書けばいいんだ。

コードにしてみる

と言っててもどうにもならんので、愚直にコードに起こしてみる。最終的にMouse.moveで使う値になるように。Arduinoのリファレンスに即して-12から12の範囲になるようにしたい。

y=x^3

double tmp = 0;
signed char result = 0;

double mapf(float in , float in_min, float in_max, float out_min, float out_max) {
  return ((in - in_min) / (in_max - in_min)) * (out_max - out_min) + out_min;
}

void setup() {
  Serial.begin(9600);
  delay(1000);

  for (int i = 0 ; i <= 1023; i++) {

    tmp = mapf(i, 0, 1023, -1, 1);
    tmp = pow(tmp, 3);
    tmp = mapf(tmp, -1, 1, -12, 12);
    result = (signed char) tmp;

    Serial.println(result);
  }
}

void loop() {
}

こんなコードを書きまして、結果が

おおう、だいぶ中央が潰れてしまった。これはどうじゃろ…、ちょっとtmp = pow(tmp, 3)のとこをtmp = pow(tmp, 3) + tmpにしてみる。

こんな感じかなぁ…。

シグモイド関数

#include <math.h>

double tmp = 0;
signed char result = 0;

double mapf(float in , float in_min, float in_max, float out_min, float out_max) {
  return ((in - in_min) / (in_max - in_min)) * (out_max - out_min) + out_min;
}

void setup() {
  Mouse.begin();
  Serial.begin(9600);
  delay(1000);

  for (int i = 0 ; i <= 1023; i++) {
    if ( i <= 511) {
      tmp = mapf(i, 0, 511, -6, 6);
      tmp = ((tanh(tmp / 2) + 1) / 2) - 1;
      
    } else if ( i >= 512) {
      tmp = mapf(i, 512, 1023, -6, 6);
      tmp = (tanh(tmp / 2) + 1) / 2;
      
    }

    tmp = mapf(tmp, -1, 1, -12, 12);
    result = (signed char) tmp;

    Serial.println(result);
  }
}

void loop() {
}

ハイパボリックタンジェントという舌噛みそうな関数を使うために#include <math.h>する。あとは条件分岐して書いてみた。結果は

うーん、これも潰れてる気がするし、変化する部分は直線的になってるような気がする。とりあえずゲイン5でもやってみた結果が

若干変わった…ゲイン10でもやってみよ。0-1023からの変換は(-0.4)-4にしてみた。

なんか何がいいのか分かんねーな…。実際使ってみて、触り心地を確かめてみないと机上の空論だなぁ。

速度を計ってみる

いっぱい計算してるので、速度がちょっと気になる。計算が遅かったらジョイスティックを動かした後に、遅れてカーソルが動くとかになりそうだし。ただ基準が分からんよな。0-1023を100回繰り返した。

  • y=x^3+xの場合
    • 結果 millis()では0。micros()で300µs
  • シグモイド関数の場合
    • 結果 28,813ms

全然ちゃうなぁ。y=x^3+xの方がいいかもなぁ…。

カテゴリー: したい | タグ: , , | コメントする