Коронопед

Тема в разделе "Глядите, что я сделал", создана пользователем issaom, 11 май 2020.

  1. issaom

    issaom Гуру

    Подключение велотренажера к ПК. Поскольку из дома выходить нельзя - решил сделать из велотренажера игровое устройство.

    Компьютерный руль своими руками.JPG

    Руль для ПК своими руками.jpg

    Макетная печатная плата.JPG

    Датчики холла и магниты.JPG

    Демонстрация работы устройства:

     
    OldKryptos, Un_ka, KindMan и 5 другим нравится это.
  2. issaom

    issaom Гуру

    Код Leonardo
    Код (C++):
    #include <SPI.h>
    #include <Wire.h>
    #include "spi_master.h"
    #include <Adafruit_GFX.h>
    #include <Adafruit_SSD1306.h>
    #include <Joystick.h>

    Adafruit_SSD1306 display(128, 32, &Wire, -1);


    Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_GAMEPAD,
                       15, 0,                  // Button Count, Hat Switch Count
                       true, true, false,     // X and Y, but no Z Axis
                       false, false, false,   // No Rx, Ry, or Rz
                       false, false,           // No rudder or throttle
                       false, false, false);  // No accelerator, brake, or steering


    int16_t X = 0;          // значение потенциометра руля
    int16_t XL = 0;         // значение потенциометра левой подрульки
    int16_t XR = 0;         // значение потенциометра правой подрульки
    int16_t XF = 0;         // значение оси X готовое (руль)
    int16_t XLm = 490;      // мертвая зона в лево
    int16_t XRm = 500;      // мертвая зона в право
    int16_t wheelAngle = 0; // угол поворота колеса

    int8_t Y = 0;

    // переменные для хранения Часов, Минут, Секунд
    uint8_t hours = 0;
    uint8_t minutes = 0;
    uint8_t seconds = 0;
    // создаем объект класса long для хранения счетчика
    long previousMillis = 0;        // храним время последнего переключения светодиода
    long interval = 1000;           // интервал между включение/выключением светодиода (1 секунда)
    long lastTime = 0;

    void setup (void)
    {
      mater_init();
      // отладка
      //Serial.begin (9600);
      //Serial.println ();
      //кнопки на руле
      pinMode(4, INPUT_PULLUP);
      pinMode(5, INPUT_PULLUP);
      pinMode(6, INPUT_PULLUP);
      pinMode(7, INPUT_PULLUP);
      pinMode(8, INPUT_PULLUP);
      pinMode(9, INPUT_PULLUP);
      pinMode(10, INPUT_PULLUP);
      pinMode(11, INPUT_PULLUP);
      // встроеный светодиод (если включен - режим настройки)
      pinMode(13, OUTPUT);

      master_arr [0] = 0; // n/a
      master_arr [1] = 0; // n/a
      master_arr [2] = 0; // n/a

      // запускаем библиотеку джойстик (автопосыл выключен)
      Joystick.begin(false);
      Joystick.setYAxisRange(-64, 64);
      display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
    }


    void loop (void)
    {
      refreshSPI ();

      unsigned long currentMillis = millis();

      //проверяем не прошел ли нужный интервал, если прошел то
      if (currentMillis - previousMillis > interval) {
        // сохраняем время последнего переключения
        previousMillis = currentMillis;

        //...обновляем  lastTime и добавляем к счетчику Секунд +1
        lastTime = millis();
        seconds++;
        // как только счетчик секунд достигнет 60, обнуляем его и добавляем к счетчику Минут +1...
        if (seconds >= 60) {
          seconds = 0;
          minutes++;
        }
        // ...тоже самое для Часов...
        if (minutes >= 60) {
          minutes = 0;
          hours++;
        }
        // ... и обнуляем счетчик Часов в конце дня
        if (hours >= 24) {
          hours = 0;
        }
      }

      //сброс часов
      if (!digitalRead(7)&&!digitalRead(4)) {
      seconds = 0;
      minutes = 0;
      hours = 0;
      }


      //режим настройка/игра
      digitalWrite(13, bitRead(slave_arr [1], 7));

      wheelAngle = map(analogRead(A5), 2, 1020, 90, 20);
      wheelAngle = wheelAngle * 10;

      Joystick.setXAxisRange(-wheelAngle / 2, wheelAngle / 2);

      X =  map(analogRead(A0), 0, 1023, 1023, 0);

      XL = map(analogRead(A1), 470, 720, 0, 350);
      if (XL < 0) XL = 0;
      if (XL > 350) XL = 350;

      XR = map(analogRead(A2), 580, 340, 0, 350);
      if (XR < 0) XR = 0;
      if (XR > 350) XR = 350;

      if (X >= XLm && X <= XRm) {
        // мертвая зона
        XF = 0;
      } else if (X > XRm) {
        // рулим вправо
        int16_t x = X - XRm;
        if (x > 300)x = 300;
        XF = x / 3;
      } else if (X < XLm) {
        // рулим влево
        int16_t x = X - XLm;
        if (x < -292) x = -300;
        XF = x / 3;
      }

      XF = XF + XR - XL;

      Y = slave_arr [0];

      // Присваиваем значения кнопок руля
      if (!digitalRead(10)) Joystick.pressButton (0); else Joystick.releaseButton (0);    // rudder BT1
      if (!digitalRead(11)) Joystick.pressButton (1); else Joystick.releaseButton (1);    // rudder BT2
      if (!digitalRead(9))  Joystick.pressButton (2); else Joystick.releaseButton (2);    // rudder BT3
      if (!digitalRead(8))  Joystick.pressButton (3); else Joystick.releaseButton (3);    // rudder BT4
      if (!digitalRead(7))  Joystick.pressButton (4); else Joystick.releaseButton (4);    // rudder BT5
      if (!digitalRead(5))  Joystick.pressButton (5); else Joystick.releaseButton (5);    // rudder BT6
      if (!digitalRead(6))  Joystick.pressButton (6); else Joystick.releaseButton (6);    // rudder BT7
      if (!digitalRead(4))  Joystick.pressButton (7); else Joystick.releaseButton (7);    // rudder BT8

      if (bitRead(slave_arr [1], 0)) Joystick.pressButton (8); else Joystick.releaseButton (8);   // rudder BT9
      if (bitRead(slave_arr [1], 1)) Joystick.pressButton (9); else Joystick.releaseButton (9);   // rudder BT10
      if (bitRead(slave_arr [1], 2)) Joystick.pressButton (10); else Joystick.releaseButton (10); // rudder BT11
      if (bitRead(slave_arr [1], 3)) Joystick.pressButton (11); else Joystick.releaseButton (11); // rudder BT12
      if (bitRead(slave_arr [1], 4)) Joystick.pressButton (12); else Joystick.releaseButton (12); // rudder BT13
      if (bitRead(slave_arr [1], 5)) Joystick.pressButton (13); else Joystick.releaseButton (13); // rudder BT14
      if (bitRead(slave_arr [1], 6)) Joystick.pressButton (14); else Joystick.releaseButton (14); // rudder BT15

      Joystick.setXAxis(XF);
      Joystick.setYAxis(Y);


      //Serial.println(XL);
      //Serial.print('=');
      //Serial.println(XF);


      // отображаем значения на дисплее
      display.clearDisplay();
      display.setTextSize(2);                     // Normal 1:1 pixel scale
      display.setTextColor(SSD1306_WHITE);        // Draw white text
      display.setCursor(0, 0);                    // Start at top-left corner
      display.print(XF);
      display.setCursor(64, 0);                   // Start at top-left corner
      display.print(Y);

      if (!digitalRead(13)) {
        // если светодиод не горит - отображаем параметры настройки
        // угол поворота руля X
        display.setTextSize(1);
        display.setCursor(0, 16);
        display.print (wheelAngle);
        // отображаем калибровку оси Y
        display.setCursor(64, 16);
        // печатаем сырое время
        display.print(slave_arr [2]);
        // печатаем значение потенциометра
        display.setCursor(64, 24);
        display.print(slave_arr [3]);
      } else {
        //светодиод горит - отображаем время проведенное в игре
        display.setTextSize(2);
        display.setCursor(0, 16);

        if (hours < 10) display.print(0);
        display.print(hours);
        display.print(':');

        if (minutes < 10) display.print(0);
        display.print(minutes);
        display.print(':');

        if (seconds < 10) display.print(0);
        display.print(seconds);
      }

      display.display();

      //посылаем данные в джойстик
      Joystick.sendState();
    }
     
    spi_master.h

    Код (C++):
    // Массив корторый отправляем ведомому
    uint8_t master_arr [4];
    // Массив корторый получаем от ведомого
    uint8_t slave_arr  [4];

    void mater_init(){
      digitalWrite(SS, HIGH);  // ensure SS stays high for now
      SPI.begin ();
      // замедляем работу мастера
      SPI.setClockDivider(SPI_CLOCK_DIV8);
    }

    void refreshSPI ()          // процедура обмена массивами между платами
    {
      digitalWrite(SS, LOW);    // enable Slave Select
      // отправляем "пустышку" чтобы загрузить первый байт массива slave_arr[]
      SPI.transfer (0xFF);
      // пауза чтобы успел подгрузиться байт на ведомом
      delayMicroseconds (20);
      // цикл по master_arr[]
      for (uint8_t i = 0; i < sizeof(master_arr); i++) {
      slave_arr[i] = SPI.transfer(master_arr[i]);
      // пауза чтобы успел подгрузиться байт на ведомом
      delayMicroseconds (20);  
      }  
      digitalWrite(SS, HIGH);   // disable Slave Select
    } // end of refreshSPI
     
    Un_ka, ИгорьК и BAR__MEN нравится это.
  3. issaom

    issaom Гуру

    Код ведомого устройства:

    Код (C++):
    #include "spi_slave.h"

    volatile uint8_t filter = 0;           // переменная фильтра
    volatile uint32_t fDisk18 = 0;         // оборотов вперед/18
    volatile uint32_t bDisk18 = 0;         // оборотов назад /18
    volatile uint32_t OLDfDisk18 = 0;      // предыдущее оборотов вперед/18
    volatile uint32_t OLDbDisk18 = 0;      // предыдущее оборотов назад /18
    volatile uint32_t startTime = 0;       // предыдущее значение
    volatile uint32_t endTime = 0;         // время 1/18 оборота
    boolean buttonWasUp = true;            // BTSet была ли кнопка отпущена ?

    void setup (void)
    {
      slave_init();                          // инициализируем SPI как SLAVE
      pinMode (2, INPUT_PULLUP);             // yellow hall
      pinMode (3, INPUT_PULLUP);             // green  hall
      pinMode (4, OUTPUT);                   // yellow LED !!!
      pinMode (5, OUTPUT);                   // green  LED !!!
      // включаем внешнее прерывание на порту D
      bitSet(PCICR, 2);
      // вызов прерывания на пинах 2 и 3
      bitSet(PCMSK2, 2);
      bitSet(PCMSK2, 3);
      slave_arr [0] = 0;                     // ось газ/тормоз int8_t
      slave_arr [1] = 0;                     // кнопки
      slave_arr [2] = 255;                   // сырое значение времени
      slave_arr [3] = 0;                     // регулировка макс скорости вращения для макс Y
      pinMode (14, INPUT_PULLUP); // BT9
      pinMode (15, INPUT_PULLUP); // BT10
      pinMode (16, INPUT_PULLUP); // BT11
      pinMode (17, INPUT_PULLUP); // BT12
      pinMode (9, INPUT_PULLUP);  // BT13
      pinMode (8, INPUT_PULLUP);  // BT14
      pinMode (7, INPUT_PULLUP);  // BT15
      pinMode (6, INPUT_PULLUP);  // BTSet
    }


    ISR (PCINT2_vect) {                       // Обработчик запросов прерывания от пинов D0..D7

      // В режиме настройки мигаем светодиодами
      if (!bitRead(slave_arr [1], 7)) {
        bitWrite(PORTD, 4, bitRead(PIND, 2));
        bitWrite(PORTD, 5, bitRead(PIND, 3));
      }

      filter = filter << 1;
      bitWrite(filter, 0, bitRead(PIND, 2));
      filter = filter << 1;
      bitWrite(filter, 0, bitRead(PIND, 3));

      // Обнаружено движение вперед 2,3 B10110100
      if (filter == B10110100) {
        // сохраняем старое значение
        OLDfDisk18 = fDisk18;
        // увеличиваем счетчик оборотов вперед
        fDisk18++;
        endTime =  millis() - startTime;
        startTime = millis();
      }

      // Обнаружено движение назад 2,3 B10000111
      if (filter == B10000111) {
        // сохраняем старое значение
        OLDbDisk18 = bDisk18;
        // увеличиваем счетчик оборотов назад
        bDisk18++;
        endTime =  millis() - startTime;
        startTime = millis();
      }

      // маленькая скорость не интересна
      if (endTime>255) endTime = 255;

    }

    void loop (void)
    {
      if (digitalRead (SS) == HIGH) countSPIb = -1;  //сброс в случае глюка связи

      // кнопка настройки 0 настройка / 1 игра
      boolean buttonIsUp = digitalRead(6);
      // если «кнопка была отпущена и (&&) не отпущена сейчас»...
      if (buttonWasUp && !buttonIsUp) {
        delay(10);
        // считываем сигнал снова
        buttonIsUp = digitalRead(6);
        if (!buttonIsUp) {  // если она всё ещё нажата...
          // ...это клик! 0 настройка / 1 игра
          bitWrite(slave_arr [1], 7, !bitRead(slave_arr [1], 7));
        }
      }
      // запоминаем последнее состояние кнопки для новой итерации
      buttonWasUp = buttonIsUp;

      // переписываем состояние кнопок
      bitWrite(slave_arr [1], 0, !digitalRead(14)); // BT9
      bitWrite(slave_arr [1], 1, !digitalRead(15)); // BT10
      bitWrite(slave_arr [1], 2, !digitalRead(16)); // BT11
      bitWrite(slave_arr [1], 3, !digitalRead(17)); // BT12
      bitWrite(slave_arr [1], 4, !digitalRead(9));  // BT13
      bitWrite(slave_arr [1], 5, !digitalRead(8));  // BT14
      bitWrite(slave_arr [1], 6, !digitalRead(7));  // BT15

      // потенциометр регулировки максимальной скорости вращения
      slave_arr [3] = map(analogRead(A7),0,1023,1,254);

      //проверяем не остановились ли педали
      if (millis() - startTime > 255) {
        slave_arr[0] = 0;
        endTime = 255;
      }


      if (OLDfDisk18 != fDisk18) {  // педали крутятся вперед
        OLDfDisk18 = fDisk18;       // обнуляем значение
        if (endTime < slave_arr [3]) slave_arr [0] = 64;
        else slave_arr [0] = map(endTime, 255, slave_arr [3], 0, 64);
      }

      if (OLDbDisk18 != bDisk18) {   // педали крутятся назад
        OLDbDisk18 = bDisk18;        // обнуляем значение
        if (endTime < slave_arr [3]) slave_arr [0] = -64;
        else slave_arr [0] = map(endTime, 255, slave_arr [3], 0, -64);
      }

      //отдаем сырое значение на дисплей
      slave_arr [2] = endTime;
    }
     
    spi_slave.h

    Код (C++):
    // Массив корторый приходит от мастера
    volatile uint8_t master_arr [4];
    // Массив который отдаем от мастеру
    volatile uint8_t slave_arr  [4];
    // счетчик входящих байт
    volatile int16_t countSPIb = -1;

    void slave_init(){
      // Настройка SPI как SLAVE
      DDRB|=(1<<PB4);                        // Настроить вывод MISO на выход
      SPCR |= (1 << SPIE)|(1 << SPE);        // Прерывание включено и сам SPI как SLAVE
    }

    ISR (SPI_STC_vect)                       // Прерывание SPI - пришел байт
    {
      if (countSPIb < 0) {                   // пришла "пустышка"
        countSPIb++;                         // увеличивам счетчик
        SPDR = slave_arr [countSPIb];        // подгружаем нулевой байт массива ведомого
        return;                              // выходим из процедуры
      }

      master_arr [countSPIb] = SPDR;         // получаем байт от мастера
      countSPIb++;                           // увеличиваем счетчик
      SPDR = slave_arr [countSPIb];          // отдаем байт ведомого (+1 индекс)

      if (countSPIb >= sizeof(master_arr)) { // если кончился массив
        countSPIb = -1;                      // обнуляем счетчик и ждем следующий обмен
      }
    }                                        // Конец SPI - пришел байт
     
    Un_ka, Daniil, ИгорьК и ещё 1-му нравится это.
  4. Ну не... на таком далеко не уедешь.
     
    issaom нравится это.
  5. parovoZZ

    parovoZZ Гуру

    очень даже можно. устал - посадил жену. а сам на диван.
     
  6. parovoZZ

    parovoZZ Гуру

    а есть разница - порожняком идешь или груженый?
     
  7. Airbus

    Airbus Радиохулиган Модератор

    Точно!Надо такой
    [​IMG]
     
  8. [​IMG]
     
    Asper Daffy и Airbus нравится это.
  9. issaom

    issaom Гуру

    Надо кому?