自家製の自転車コンピューター、またはArduinoがどのように街頭にたどり着いたか



こんにちは、Habr! 日曜大工の自転車コンピューターの話を共有したいと思います。 自転車を購入した後、どうにか自分の進歩を修正することにしましたが、モバイルトラッカーは最も便利なソリューションではありませんでした。 そのため、私は自分の手で自転車のコンピューターを作ろうとすると同時に、拡張の基礎を築きました。つまり、自転車のボディキット(フロントライトとリアライト、ベル)を制御します。



ちょっとした準備



自転車のコンピューターの中心にあるのは、車輪の回転を検出するリードスイッチであり、それ以外はすべて初等数学です...もちろん、必要な既製の実装と数式をインターネットで検索しました。

すでに書いたように、すべてはリードスイッチに結び付けられています。マグネットはスポークに固定され、リードスイッチ自体は「プラグ」にあります。 プラグの磁石がリードスイッチを閉じると、これはホイールが完全に回転し、自転車が次の距離だけ移動したことを意味します。

2 * Pi * R タイヤ



最初のバージョン



まず、自転車のコンピューターに必要なもののリストを作成しました。





幸いなことに、必要なモジュールはすべて手元にありました。







私はすぐにサンドイッチを組み立てました。画面の上に、SDモジュールの第2層とarduinka自体、そしてその下にある時計です。 かなりコンパクトでした。 もちろん、既成のモジュールを使用せずに、ボードを「緩め」て中毒した場合、多くのスペースを獲得できたでしょう。



写真の結果






リードスイッチを接続して、ディスプレイ英語の速度計を 操作するためにネットワークから取得したスケッチに基づいたスケッチの最初のバージョンのテストを開始しました。

そして、椅子からどれだけ絞り出せますか?


設計が機能していることを確認した後、最初のテストを実施することにしました。 私はいくつかの単三電池または電池から電力を供給することを計画していましたが、その瞬間、それらは手元にありませんでした...信じてください、あなたの腕の下でラップトップで自転車に乗ることはまだ喜びです。

最初のテストの写真






なぜ自転車は戻らないのでしょうか?





テストは報われました。 ロジックに誤りを見つけました。リードスイッチからの信号は、常にホイールの回転に等しくなりました(回転の最小時間が経過した場合)。 すべてが正しいように見えますが、リードスイッチの反対側の磁石の固定を停止すると、プログラムはあなたが非常に速く進んでいるとみなしました。



第二版



プログラムを少し終えた後、私は「電源」を作りました:5本の単三電池のセット。 その前に、私は王冠を使用しようとしましたが、ボディキットでArduinkaには十分ではありませんでした。



5110番目は赤の背景にあったため、バックライトは1ではなく論理ゼロによって制御されていたため、アナログポート経由のソフトウェア制御を拒否し、シンプルなボタンを追加する必要がありました。

さらに、データのないアナログポートはランダムな値を生成しましたが、これは単純な抵抗器によって決定されました。 しかし、アナログポートからのもう1つの驚きに頭を痛めました。バッテリーを使用した場合のリードスイッチの値は、USBポートを使用した場合と同じではありませんでした。



2回目のテストの写真














2番目のテストでは、システムが完全に機能していることが示されました。 唯一の問題は計算エラーでした。「26インチホイール」の半径は「13.5」インチでした。 その結果、走行距離計は少し嘘をつきました(距離を計算する便利なツールを提供してくれたYandex.Mapsに感謝します)。



第三版



デバイスは機能しましたが、もちろん私は外観を完成させたかったのです。 たまたま私が見つけたのはたった1つの建物で、「狭い」ことが判明しました。



数ミリメートル






コンポーネントの場所を変更する必要がありました。すべてがケースに入りましたが、合計サイズが大きくなりました。 はんだ付けの過程で、Arduino NanoとSDモジュールを交換しなければなりませんでした。はんだごてはまあまあ持っているので、結論のいくつかは単にそれを使用できなくしました。



寸法: 72x50x28mm

また、ファイルを操作する必要がありました...














すべてのモジュールの性能を確認した後、揺れに対する耐性を高めるためにエポキシ接着剤で固定することにしました。



このフォームでは、私は去らないコンピューター。 サンディングとペイント先










私の速攻は残酷な冗談を演じました:私はスクリーンの下のワイヤーを十分に修正しなかったし、それらの1つは干渉(実際、「コントラスト」パラメーターのこの効果)を入れ始めました。



古いテレビのように、簡単なタップで処理されます








コードの修正がさらに2つあり、インターフェースは(現時点では)最終的な外観になりました。



デバイスの組み立て


  1. 速度(Km / H)
  2. 走行距離(メートル単位の距離)
  3. 所要時間(分)
  4. 現在の時刻(モスクワ)






クロックはトラブルを引き起こしました:連絡先の1つが出発し、時間がスキップし始めました(あたかもRSTとCLKを混同したかのように)。 しかし、最も興味深いのは、旅行の後、時計が正常に戻ったことです。



おそらく、苦情はマウントの隅に提示することができます:旅行中に見るのは不便です。



ほぼ垂直なフィクスチャ






はい、電気テープの取り付け方法は理想的ではありませんが、自転車のガジェット用に別のブラケットを見つけることができませんでした。 しかし、中国からiderが来ています。 この問題を解決するのに非常に適していると思います。



最終オプション
















費用







合計: 〜1100r



関連リンク







まとめ



Arduinoでの作業や小さな作業用デバイスの構築の経験が少し増えました。 数時間楽しんでいましたが、何よりも重要なことは、今では作業用のコンピューターを持っていることです。 計画では、「グローバル」データの通常の保存/読み込みをメモリカードに書き込み、「グローバル」統計を表示するボタンを追加します。 外部デバイスを制御するために、6つのアナログポート(3つのボタンと3つのサイリスタ)があり、たとえば、距離計(パーキングセンサー)や温度計などの1つのデジタルポートを失いました。 自転車のフレームにある5本の単三電池はあまり見栄えが良くないので、電力の問題を解決したいと思います。

自転車コンピューターのテストを手伝った猫






スケッチコード
#include <SD.h> #include <DS1302.h> //Function: This procedure applies to the Arduino driver NOKIA 5110 LCD. //Time:September 4,2012 #define PIN_SCE 3 #define PIN_RESET 2 #define PIN_DC 4 #define PIN_SDIN 5 #define PIN_SCLK 6 #define reed A0// #define LCD_C LOW #define LCD_D HIGH int count=0; char dat[4]; char disp_tab[]={ '0','1','2','3','4','5','6','7','8','9'}; #define LCD_X 84 #define LCD_Y 48 const int chipSelect = 10; static const byte ASCII[][5] = { { 0x00, 0x00, 0x00, 0x00, 0x00 } // 20 ,{ 0x00, 0x00, 0x5f, 0x00, 0x00 } // 21 ! ,{ 0x00, 0x07, 0x00, 0x07, 0x00 } // 22 " ,{ 0x14, 0x7f, 0x14, 0x7f, 0x14 } // 23 # ,{ 0x24, 0x2a, 0x7f, 0x2a, 0x12 } // 24 $ ,{ 0x23, 0x13, 0x08, 0x64, 0x62 } // 25 % ,{ 0x36, 0x49, 0x55, 0x22, 0x50 } // 26 & ,{ 0x00, 0x05, 0x03, 0x00, 0x00 } // 27 ' ,{ 0x00, 0x1c, 0x22, 0x41, 0x00 } // 28 ( ,{ 0x00, 0x41, 0x22, 0x1c, 0x00 } // 29 ) ,{ 0x14, 0x08, 0x3e, 0x08, 0x14 } // 2a * ,{ 0x08, 0x08, 0x3e, 0x08, 0x08 } // 2b + ,{ 0x00, 0x50, 0x30, 0x00, 0x00 } // 2c , ,{ 0x08, 0x08, 0x08, 0x08, 0x08 } // 2d - ,{ 0x00, 0x60, 0x60, 0x00, 0x00 } // 2e . ,{ 0x20, 0x10, 0x08, 0x04, 0x02 } // 2f / ,{ 0x3e, 0x51, 0x49, 0x45, 0x3e } // 30 0 ,{ 0x00, 0x42, 0x7f, 0x40, 0x00 } // 31 1 ,{ 0x42, 0x61, 0x51, 0x49, 0x46 } // 32 2 ,{ 0x21, 0x41, 0x45, 0x4b, 0x31 } // 33 3 ,{ 0x18, 0x14, 0x12, 0x7f, 0x10 } // 34 4 ,{ 0x27, 0x45, 0x45, 0x45, 0x39 } // 35 5 ,{ 0x3c, 0x4a, 0x49, 0x49, 0x30 } // 36 6 ,{ 0x01, 0x71, 0x09, 0x05, 0x03 } // 37 7 ,{ 0x36, 0x49, 0x49, 0x49, 0x36 } // 38 8 ,{ 0x06, 0x49, 0x49, 0x29, 0x1e } // 39 9 ,{ 0x00, 0x36, 0x36, 0x00, 0x00 } // 3a : ,{ 0x00, 0x56, 0x36, 0x00, 0x00 } // 3b ; ,{ 0x08, 0x14, 0x22, 0x41, 0x00 } // 3c < ,{ 0x14, 0x14, 0x14, 0x14, 0x14 } // 3d = ,{ 0x00, 0x41, 0x22, 0x14, 0x08 } // 3e > ,{ 0x02, 0x01, 0x51, 0x09, 0x06 } // 3f ? ,{ 0x32, 0x49, 0x79, 0x41, 0x3e } // 40 @ ,{ 0x7e, 0x11, 0x11, 0x11, 0x7e } // 41 A ,{ 0x7f, 0x49, 0x49, 0x49, 0x36 } // 42 B ,{ 0x3e, 0x41, 0x41, 0x41, 0x22 } // 43 C ,{ 0x7f, 0x41, 0x41, 0x22, 0x1c } // 44 D ,{ 0x7f, 0x49, 0x49, 0x49, 0x41 } // 45 E ,{ 0x7f, 0x09, 0x09, 0x09, 0x01 } // 46 F ,{ 0x3e, 0x41, 0x49, 0x49, 0x7a } // 47 G ,{ 0x7f, 0x08, 0x08, 0x08, 0x7f } // 48 H ,{ 0x00, 0x41, 0x7f, 0x41, 0x00 } // 49 I ,{ 0x20, 0x40, 0x41, 0x3f, 0x01 } // 4a J ,{ 0x7f, 0x08, 0x14, 0x22, 0x41 } // 4b K ,{ 0x7f, 0x40, 0x40, 0x40, 0x40 } // 4c L ,{ 0x7f, 0x02, 0x0c, 0x02, 0x7f } // 4d M ,{ 0x7f, 0x04, 0x08, 0x10, 0x7f } // 4e N ,{ 0x3e, 0x41, 0x41, 0x41, 0x3e } // 4f O ,{ 0x7f, 0x09, 0x09, 0x09, 0x06 } // 50 P ,{ 0x3e, 0x41, 0x51, 0x21, 0x5e } // 51 Q ,{ 0x7f, 0x09, 0x19, 0x29, 0x46 } // 52 R ,{ 0x46, 0x49, 0x49, 0x49, 0x31 } // 53 S ,{ 0x01, 0x01, 0x7f, 0x01, 0x01 } // 54 T ,{ 0x3f, 0x40, 0x40, 0x40, 0x3f } // 55 U ,{ 0x1f, 0x20, 0x40, 0x20, 0x1f } // 56 V ,{ 0x3f, 0x40, 0x38, 0x40, 0x3f } // 57 W ,{ 0x63, 0x14, 0x08, 0x14, 0x63 } // 58 X ,{ 0x07, 0x08, 0x70, 0x08, 0x07 } // 59 Y ,{ 0x61, 0x51, 0x49, 0x45, 0x43 } // 5a Z ,{ 0x00, 0x7f, 0x41, 0x41, 0x00 } // 5b [ ,{ 0x02, 0x04, 0x08, 0x10, 0x20 } // 5c ¥ ,{ 0x00, 0x41, 0x41, 0x7f, 0x00 } // 5d ] ,{ 0x04, 0x02, 0x01, 0x02, 0x04 } // 5e ^ ,{ 0x40, 0x40, 0x40, 0x40, 0x40 } // 5f _ ,{ 0x00, 0x01, 0x02, 0x04, 0x00 } // 60 ` ,{ 0x20, 0x54, 0x54, 0x54, 0x78 } // 61 a ,{ 0x7f, 0x48, 0x44, 0x44, 0x38 } // 62 b ,{ 0x38, 0x44, 0x44, 0x44, 0x20 } // 63 c ,{ 0x38, 0x44, 0x44, 0x48, 0x7f } // 64 d ,{ 0x38, 0x54, 0x54, 0x54, 0x18 } // 65 e ,{ 0x08, 0x7e, 0x09, 0x01, 0x02 } // 66 f ,{ 0x0c, 0x52, 0x52, 0x52, 0x3e } // 67 g ,{ 0x7f, 0x08, 0x04, 0x04, 0x78 } // 68 h ,{ 0x00, 0x44, 0x7d, 0x40, 0x00 } // 69 i ,{ 0x20, 0x40, 0x44, 0x3d, 0x00 } // 6a j ,{ 0x7f, 0x10, 0x28, 0x44, 0x00 } // 6b k ,{ 0x00, 0x41, 0x7f, 0x40, 0x00 } // 6c l ,{ 0x7c, 0x04, 0x18, 0x04, 0x78 } // 6d m ,{ 0x7c, 0x08, 0x04, 0x04, 0x78 } // 6e n ,{ 0x38, 0x44, 0x44, 0x44, 0x38 } // 6f o ,{ 0x7c, 0x14, 0x14, 0x14, 0x08 } // 70 p ,{ 0x08, 0x14, 0x14, 0x18, 0x7c } // 71 q ,{ 0x7c, 0x08, 0x04, 0x04, 0x08 } // 72 r ,{ 0x48, 0x54, 0x54, 0x54, 0x20 } // 73 s ,{ 0x04, 0x3f, 0x44, 0x40, 0x20 } // 74 t ,{ 0x3c, 0x40, 0x40, 0x20, 0x7c } // 75 u ,{ 0x1c, 0x20, 0x40, 0x20, 0x1c } // 76 v ,{ 0x3c, 0x40, 0x30, 0x40, 0x3c } // 77 w ,{ 0x44, 0x28, 0x10, 0x28, 0x44 } // 78 x ,{ 0x0c, 0x50, 0x50, 0x50, 0x3c } // 79 y ,{ 0x44, 0x64, 0x54, 0x4c, 0x44 } // 7a z ,{ 0x00, 0x08, 0x36, 0x41, 0x00 } // 7b { ,{ 0x00, 0x00, 0x7f, 0x00, 0x00 } // 7c | ,{ 0x00, 0x41, 0x36, 0x08, 0x00 } // 7d } ,{ 0x10, 0x08, 0x08, 0x10, 0x08 } // 7e ← ,{ 0x78, 0x46, 0x41, 0x46, 0x78 } // 7f → }; void LcdCharacter(char character) { LcdWrite(LCD_D, 0x00); for (int index = 0; index < 5; index++) { LcdWrite(LCD_D, ASCII[character - 0x20][index]); } LcdWrite(LCD_D, 0x00); } void LcdClear(void) { for (int index = 0; index < LCD_X * LCD_Y / 8; index++) { LcdWrite(LCD_D, 0x00); } } void LcdInitialise(void) { pinMode(PIN_SCE, OUTPUT); pinMode(PIN_RESET, OUTPUT); pinMode(PIN_DC, OUTPUT); pinMode(PIN_SDIN, OUTPUT); pinMode(PIN_SCLK, OUTPUT); digitalWrite(PIN_RESET, LOW); digitalWrite(PIN_RESET, HIGH); LcdWrite(LCD_C, 0x21 ); // LCD Extended Commands. LcdWrite(LCD_C, 0xB1 ); // Set LCD Vop (Contrast). LcdWrite(LCD_C, 0x04 ); // Set Temp coefficent. //0x04 LcdWrite(LCD_C, 0x14 ); // LCD bias mode 1:48. //0x13 LcdWrite(LCD_C, 0x0C ); // LCD in normal mode. LcdWrite(LCD_C, 0x20 ); LcdWrite(LCD_C, 0x0C ); } void LcdString(char *characters) { while (*characters) { LcdCharacter(*characters++); } } void LcdWrite(byte dc, byte data) { digitalWrite(PIN_DC, dc); digitalWrite(PIN_SCE, LOW); shiftOut(PIN_SDIN, PIN_SCLK, MSBFIRST, data); digitalWrite(PIN_SCE, HIGH); } void gotoXY(int x, int y) { LcdWrite( 0, 0x80 | x); // Column. LcdWrite( 0, 0x40 | y); // Row. } void dispcountt(int count) { LcdCharacter(disp_tab[count/10000]); LcdCharacter(disp_tab[count/1000%10]); LcdCharacter(disp_tab[count/100%10]); LcdCharacter(disp_tab[count%100/10]); LcdCharacter(disp_tab[count%10]); } //storage variables float radius = 13.5;//   .  26  ,  13.5 () boolean reedVal; long timer = 0;//     float kmh = 0.00;//  / float circumference; float distance = 0;//    long totalDistance = 0;//     long totalTime = 0;//     float distanceBuffer = 0;//     long timeBuffer = 0;//     float deltaD;//     boolean moving = false; // -    long time = 0;//    long lastTime = millis(); long duration = 0; DS1302 rtc(9, 8, 7); int maxReedCounter = 100;//      int reedCounter; void setup(void) { rtc.halt(false); rtc.writeProtect(false); pinMode(10, OUTPUT);//    reedCounter = maxReedCounter; deltaD = 2*3.415926535*radius*0.025;//       circumference = 2*3.14*radius;//  ,     (  )     pinMode(A0, INPUT); // TIMER SETUP- the timer interrupt allows preceise timed measurements of the reed switch //for mor info about configuration of arduino timers see http://arduino.cc/playground/Code/Timer1 cli();//stop interrupts //set timer1 interrupt at 1kHz TCCR1A = 0;// set entire TCCR1A register to 0 TCCR1B = 0;// same for TCCR1B TCNT1 = 0; // set timer count for 1khz increments OCR1A = 1999;// = (1/1000) / ((1/(16*10^6))*8) - 1 // turn on CTC mode TCCR1B |= (1 << WGM12); // Set CS11 bit for 8 prescaler TCCR1B |= (1 << CS11); // enable timer compare interrupt TIMSK1 |= (1 << OCIE1A); sei();//allow interrupts //END TIMER SETUP LcdInitialise(); LcdClear(); LcdString("Initializing SD card..."); pinMode(10, OUTPUT); gotoXY(0, 1); if (!SD.begin(chipSelect)) { LcdString("Card failed, or not present"); }else{ LcdString("Card initialized."); File logFile = SD.open("logfile.txt"); if (logFile) { gotoXY(0, 2); LcdString("reading file"); while (logFile.available()) { totalDistance = logFile.parseInt(); totalTime = logFile.parseInt(); } } else { gotoXY(0, 2); LcdString("error read file"); } } LcdClear(); gotoXY(0, 0); LcdString("Spd:");// / gotoXY(0, 1); LcdString("Dst:");//     gotoXY(0, 2); LcdString("Drt:");//    gotoXY(0, 3); LcdString("Clc:");// } ISR(TIMER1_COMPA_vect) {//   1     if(analogRead(reed) >= 680 && analogRead(reed) <= 742){//     reedVal = true; }else{ reedVal = false; } if (reedVal){//  if (timer > 110){ moving = true; } if (reedCounter == 0){//     if(moving){ kmh = (56.8*float(circumference))/float(timer)*1.61;//   distance += deltaD; distanceBuffer } reedCounter = maxReedCounter;// reedCounter timer = 0;//   } else{ if (reedCounter > 0){//    reedCounter -= 1;// reedCounter } } }else{//    if (reedCounter > 0){//    reedCounter -= 1;// reedCounter } } if (timer > 2000){ kmh = 0;//     ,  . moving = false; }else{ timer += 1;//  } } void updateDisplay(){ for(int i = 0; i<=3; i++){ gotoXY(28, i); LcdString(" "); } char Sensor1CharMsg[8]; gotoXY(28, 0); if(moving){ String Sensor1String((int)(kmh), DEC); Sensor1String.toCharArray(Sensor1CharMsg,(Sensor1String.length()+1)); LcdString(Sensor1CharMsg); }else{ LcdString("0"); } LcdString("KM/h"); gotoXY(28, 1); String Sensor1String2((int)(distance), DEC); Sensor1String2.toCharArray(Sensor1CharMsg,(Sensor1String2.length()+1)); LcdString(Sensor1CharMsg); LcdString("m"); gotoXY(28, 2); String Sensor1String3((int)(duration/60), DEC); Sensor1String3.toCharArray(Sensor1CharMsg,(Sensor1String3.length()+1)); LcdString(Sensor1CharMsg); LcdString("min"); gotoXY(28, 3); String Sensor1String4(rtc.getTimeStr(FORMAT_SHORT)); Sensor1String4.toCharArray(Sensor1CharMsg,(Sensor1String4.length()+1)); LcdString(Sensor1CharMsg); } void loop(void) { if (reedVal){//  if (timer <= 110){ moving = false;// ,   }else{ moving = true; } } updateDisplay(); saveData(); if (moving){ int d = (int)((millis()-lastTime)/1000) timeBuffer += d; duration += d; } lastTime = millis(); delay(1000); } void saveData(){ while(timeBuffer>=60){ timeBuffer-=60; totalTime++; } while(distanceBuffer>=11){ distanceBuffer-=11; totalDistance+=11; } }
      
      






All Articles