Arduino使用タイムローターの作り方を雑に紹介

トランスイノベーション様から販売されたアダルトグッズの「タイムローター」をArduinoと他モジュールを用いて再現しましたので紹介します。
※作成にはArduino及び電子工作の最低限の知識が必要になります。
ですが比較的簡単だと思いますので、初心者の方も頑張って作ってみてください。

機能

本家タイムローターの機能を可能な限り再現しました。(実際に体感で比較し誤差がほぼないことを確認しています。)

・csvファイル内一列目に時間、二列目にローター強度を指定し、スイッチON後指定した時間になるたびに指定した強度でローターを動作させることが可能(csvファイル名は「pattern.csv」で固定)
・時間は0.01秒単位で指定可能
・強度は0~1000の範囲で指定可能
・複数の時間・強度を設定することで、細かい時間感覚でローターを制御可能

使用部品

・Arduino Nanoと接続用USB
・DCモータードライバモジュール(TB6612FNG、ピンヘッダ実装済みのものがあると楽)
・RTCモジュール(DS3231)とLIR2032(DS3231はボタン電池CR2032がついて売られているものがあるが、故障の恐れがあるためLIR2032に交換を推奨)
・microSDカードモジュールとmicroSD
・ローター用乾電池ボックス(単3二本)
・スライドスイッチ(1回路2接点、ジャンパワイヤなどで代用することが一応可能)
・LEDと1KΩ程度の抵抗(なくても動作可能)
・Arduino給電用USB電源
・お好みの乾電池2本で動作するローターと固定道具
・ブレッドボードとジャンパワイヤ(はんだ作業無しで作る場合必要)
・ターミナルブロック(ローター、電池ボックスを繋げるときあると便利)

配線図

Arduinoの電源(USB接続)は省略しています。ローターはDCモーターで代用しています。

配線図


TB6612FNG部分とArduino部分で別々で電源が必要です。
スライドスイッチはD3とGNDがつながっている状態がOFF(切るとONになる仕組み)です。
TB6612FNG、microSDカードモジュールとArduinoの接続を以下のとおりです。

<TB6612FNG>
PWMA <---> D10
AIN2 <---> D9
AIN1 <---> D8

<microSDカードモジュール>
CS   <---> D4
MOSI <---> D11
MISO <---> D12
CLK  <---> D13
※CLKはシルク印刷がSCKになっている場合もあり

スケッチ

SDFatとDS3232RTCのライブラリを使用しているため、スケッチ書き込み前に別途ダウンロードが必要です。

#include <limits.h>
#include <SPI.h>
#include <SdFat.h>
#include <avr/io.h>
#include <DS3232RTC.h>
//SDカードリーダ接続ピンアサイン
//MOSI pinD11
//MISO pinD12
//CLK  pinD13
//CS   pinD4
//モータードライバ接続ピンアサイン
//AIN1 pinD8
//AIN2 pinD9
//PWMA pinD10
//RTC接続ピンアサイン
//SDA pinA4
//SCL pinA5
//LED接続ピンアサイン pinD2
//スライドスイッチ接続ピンアサイン pinD3
#define CSV_DELIM ','
#define CS_PIN 4
#define AIN1 8
#define AIN2 9
#define PWMA 10
#define LED_PIN 2
#define SWITCH_PIN 3
//周波数(最大強度)設定
#define FRQ 1000
//起動後開始時間設定
#define ADJ 1000 
SdFat SD;
File file;
tmElements_t tm;
int switchcheck;
double pwr; 
double actime, duty, dutysub;
double master_t, master_sec, sub_sec,sub_mil;
bool chkflag, onflag, endflag;
//CSV内容処理関数(~91行まで)
int csvReadText(File* file, char* str, size_t size, char delim) {
 char ch;
 int rtn;
 size_t n = 0;
 while (true) {
   if (!file->available()) {
     rtn = 0;
     break;
   }
   if (file->read(&ch, 1) != 1) {
     rtn = -1;
     break;
   }
   if (ch == '\r') {
     continue;
   }
   if (ch == delim || ch == '\n') {
     rtn = ch;
     break;
   }
   if ((n + 1) >= size) {
     rtn = -2;
     n--;
     break;
   }
   str[n++] = ch;
 }
 str[n] = '\0';
 return rtn;
}
int csvReadDouble(File* file, double* num, char delim) {
 char buf[20];
 char* ptr;
 int rtn = csvReadText(file, buf, sizeof(buf), delim);
 if (rtn < 0) return rtn;
 *num = strtod(buf, &ptr);
 if (buf == ptr) return -3;
 while(isspace(*ptr)) ptr++;
 return *ptr == 0 ? rtn : -4;
}
//CSV読み込み関数
int fileinit(){
 // CSVチェック
 file = SD.open("pattern.csv", FILE_WRITE);
 if (!file) {
   Serial.println("open failed");
   return -1;
 }
 // CSV読み込み位置初期化
 file.seek(0);
 return 0;
}
//変数初期化関数
void datainit(){
 duty=0;
 dutysub=0;
 master_t=0;
 master_sec=0;
 sub_sec=0;
 sub_mil=0;
 
 chkflag=false;
 onflag=false;
 endflag=false;
}
void setup() {
 pinMode(AIN1,OUTPUT);
 pinMode(AIN2,OUTPUT);
 pinMode(PWMA,OUTPUT);
 pinMode(LED_PIN,OUTPUT);
 pinMode(SWITCH_PIN,INPUT_PULLUP);
 Serial.begin(115200);
 
 //SD読み込み準備
 if (!SD.begin(CS_PIN)) {
   Serial.println("begin failed");
   return;
 }
 //初期時刻設定
 //時、分、秒、日、月、年の順で設定
 setTime(0, 0, 0, 18, 4, 2021);
 //モーター設定
 TCCR1A = 0b00100001;
 TCCR1B = 0b00010010;
 OCR1A = (unsigned int)(1000000 / FRQ);
 digitalWrite(AIN1,HIGH);
 digitalWrite(AIN2,LOW);
 //変数初期化
 datainit();
}
void loop() {
 while(1){
   switchcheck = digitalRead(SWITCH_PIN);
   digitalWrite(LED_PIN,switchcheck);
   //スイッチ状態チェック
   if(switchcheck > 0){
     if(onflag==false){
       if(fileinit() == 0){
         delay(ADJ);
         RTC.set(now());
         sub_mil=millis();
         onflag=true;
       }
     }
   }else{
     if(onflag==true){
       if(file.available()>0){
         file.close();
       }
       datainit();
       OCR1B = (unsigned int)(1000000 / FRQ * 0);
     }
   }
   if(onflag==true){
     if(chkflag == false){
       //CSV読み込み
       if (csvReadDouble(&file, &actime, CSV_DELIM) != CSV_DELIM
           || csvReadDouble(&file, &pwr, CSV_DELIM) != '\n') {
             endflag = true;          
       }
       duty = pwr * 0.001;
       chkflag = true;
     }
     if(endflag==false){
       RTC.read(tm);
       //秒ごとにミリ秒補正
       if(tm.Second!=sub_sec){
         sub_sec=tm.Second;
         master_sec++;
         master_t=master_sec;
         sub_mil=millis();
       //秒以下は0.01秒単位で記録
       }else if(millis()>=(sub_mil+10)){
         sub_mil=millis();
         master_t=master_t+0.01;
       }
       //時間チェック・モーター出力
       if(master_t >= actime){
         dutysub = duty;
         OCR1B = (unsigned int)(1000000 / FRQ * dutysub);
         chkflag = false;
       }
     }else{
       OCR1B = (unsigned int)(1000000 / FRQ * 0);
     }
   }
 }
}

実装例

・ブレッドボード上での実装例
各モジュールはピンヘッダ実装済みのものを用意し、かつローター、乾電池とTB6612FNGの接続をターミナルブロックで行うことではんだごて一切なしで作ることが可能。(写真ではArduinoへのUSB給電は省略しています。またジャンパワイヤの色は適当です。)

ブレッドボード実装例

・ユニバーサル基板上での実装例
はんだづけを頑張れば以下画像のように小さくまとめることが可能。(乾電池を4本(6V)に変更し、乾電池+部分をArduinoとTB6612FNGのVINに接続することで電源を一つにまとめています。)

ユニバーサル基板実装例

使い方

上記配線図、実装例を参考に各部品を接続し、Arduinoには事前にスケッチを書き込んでおきます。空のmicroSD内に好きなpattern.csvファイルを入れ、microSDカードモジュールにセットして準備完了です。
Arduinoへの電源供給後、スライドスイッチをONにすることで動作開始し、csvファイル内で指定した時間になるたびにローターが振動します。
スライドスイッチをOFFにすると途中でも動作停止し、再度ONにするとcsvファイルの初めから読み込みをやり直します。
LEDと抵抗をつけている場合、スライドスイッチのON・OFFに合わせて点灯・消灯します。

あとがき

もともとものはだいぶ前に完成しており、自分で楽しむ為に作ったため今回のように紹介するつもりはなかったのですが、調べたところ未だに需要がありそうな感じで他にも自作、紹介している方もいたので、便乗して記載してみました。(noteでいいのかは不明ですが)
本家も持っているので比較して使ってみたりもしましたが、それなりに再現できているのではと思います。
以下リンクからスケッチや配線図の他、本家と比較して確認した作品名リストなどをまとめてダウンロードできるようにしておいたので、興味のある方は頑張って作ってみてください。

本家に比べると部品点数が多く(特にわざわざRTCを使っているところ)、スケッチも改良の余地があるので誰かもっと良いものを作って紹介してください。(自分の知識ではこれが限界です...)
他にもいろいろ書けるネタはあるのですが(ローター以外を接続して遊んでみたり、スケッチを変更しニッ○ルカップを繋げてUF○SAっぽくしてみるなど)需要が不明なのでとりあえずこの記事のみにしておきます。
要望(もう少し詳しい作り方や部品の購入先など)があれば何かしら追記するかもしれません。
本内容に問題がある場合は削除・修正しますのでその際もコメント等いただければ対応いたします。