Важная информация

User Tag List

Страница 14 из 17 ПерваяПервая ... 1011121314151617 ПоследняяПоследняя
Показано с 131 по 140 из 164

Тема: ROM-плеер на ардуино

  1. #131
    Master Аватар для Improver
    Регистрация
    06.02.2018
    Адрес
    г. Волгоград
    Сообщений
    974
    Спасибо Благодарностей отдано 
    427
    Спасибо Благодарностей получено 
    396
    Поблагодарили
    221 сообщений
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от KTSerg Посмотреть сообщение
    Идея интересная, только в скетче нужно предусмотреть вариант, когда часов нет (наиболее критично), они остановились, сбросились, или имеют не адекватные показания...
    Ситуацию отсутствия часов не проверял (проверю по возможности), а в остальных случаях на часах будет установлено время с компа, которое было на момент прошивки. А в случае, если их заведомо нет,можно использовать скетч третьей версии -- он, фактически, и отличается отсутствием часов. А потом уж сделаем нечто объединённое, на "#ifdef...".

  2. #132
    Veteran
    Регистрация
    22.02.2014
    Адрес
    г. Курган
    Сообщений
    1,657
    Спасибо Благодарностей отдано 
    218
    Спасибо Благодарностей получено 
    301
    Поблагодарили
    212 сообщений
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Две версии, возможно не очень удобно.
    Если в программе нет принципиальных отличий, то наверное можно в одной версии объединить.
    Если часы не отвечают, можно из функции чтения даты/времени возвращать константу, или возможно таймер запускать... .

  3. #133
    Master Аватар для Improver
    Регистрация
    06.02.2018
    Адрес
    г. Волгоград
    Сообщений
    974
    Спасибо Благодарностей отдано 
    427
    Спасибо Благодарностей получено 
    396
    Поблагодарили
    221 сообщений
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Двигаемся дальше, теперь нужно добавить запись... Вот что я думаю: запись нужно делать полностью, со всеми заголовками, синхроимпульсами и прочей служебной информацией -- это даст возможность писать в любом формате, не только ROM, но и бейсика/монитора/ретекса и т.д. И вывод, соответственно, должен быть идентичен вводу.

    Потом, при захвате сигнала принимаем за аксиому, что во всех форматах Вектора вывод начинается с группы нулей. (Это ведь так?) Это избавляет от необходимости поиска синхроимпульса и инвертирования сигнала "на лету".

    И вот первый пробный вариант:

    Test_InputROM

    Сигнал в цифровом виде подаётся на вход D2 ардуино. Никаких дополнительных библиотек скетчу не нужно.
    Код:
    #define InputPIN 2
    
    volatile unsigned long PPeriod = 0;     // текущее значение полупериода
    volatile unsigned int PPeriod_sred = 0; // среднее значение границы длинны полупериода
    unsigned int Tpp = 0;                   // расчётное значение полупериода для вывода
    volatile unsigned long iMicros_old = 0; // предыдущее значение микросекунд
    volatile boolean Pik = false;           // есть изменение сигнала
    volatile boolean Plong = false;         // "длинный" полупериод
    
    byte BUFF[512];       // буфер данных
    unsigned int CWB = 0; // индекс записи в буфер
    byte bBit = 14;       // индекс записываемого полубита
    byte A = 0;           // записываемый байт
    byte B = 0;           // записываемый бит
    int i0 = 0;           // счётчик циклов без сигнала
    //unsigned int j;
    
    void setup() //процедура setup
    {
      pinMode(InputPIN, INPUT); //объявляем пин как вход
      pinMode(13, OUTPUT);      // объявляем пин как выход
      attachInterrupt(0, TakeTime, CHANGE); // включаем обработку внешнего прерывания
      Serial.begin(9600);
    }
    
    void loop() //процедура loop
    {
      if (Pik) {          // Произошло изменение сигнала
        Pik = false;
        i0 = 0;
    
        if (Plong) {      // получен длинный полупериод
          B ^= 1;         // инвертируем бит
          A = (A<<1)+B;   // заносим бит
          bBit--;         // уменьшаем счётчик полубитов
        }
        else {            // получен короткий полупериод
          if (bBit & 1) { // нечётный полубит
            A = (A<<1)+B; // заносим бит
          }
        }
        // ===================================
        if (bBit > 1) {
          bBit--;         // счётчик -1
        }
        else {
          if (CWB < 512) BUFF[CWB] = A;
          CWB++;
          A = 0;
          bBit += 15;
        }
        // ====================================
        digitalWrite(13, HIGH); // зажигаем светодиод -- сигнал есть
      } 
      else{               // изменения сигнала не было
        if (i0 < 9) {     // считаем 10 раз
          i0++;
        }
        if (i0 == 8) {    // на восьмой раз выводим тестовую инфу
          digitalWrite(13, LOW); // гасим светодиод -- сигнала нет
          Serial.println("--------");
          for (int j = 0; j<=511; j++) {
            Serial.print(BUFF[j], HEX);
            Serial.print('-');
            if (((j+1)%16) == 0) Serial.println('-');
          }
          Serial.println("--------");
          Serial.println(PPeriod_sred); // граница короткий/длинный полупериод
          if (((PPeriod_sred/3*2)%8) < 4) { // полупериод для последующего вывода...
            Tpp = (PPeriod_sred/12)*8;    // округление до кратного 8 в меньшую сторону
          }
          else {
            Tpp = ((PPeriod_sred/12)+1)*8;// округление до кратного 8 в большую сторону
          }
          Serial.println(Tpp); // расчётное значение длинны полупериода для вывода
          // сбрасываем счётчики -- сигнала слишьком долго нет
          CWB = 0; 
          A = 0;
          B = 0;
          bBit = 14;
          delay(100);
        }
      }
      delayMicroseconds(128); // задержка...
    }
    
    void TakeTime()
    {
      PPeriod = micros() - iMicros_old;
      iMicros_old += PPeriod;
      if (PPeriod < 1000) { // началось...
        if (PPeriod_sred != 0) {
          if (!Plong) { 
            if (PPeriod > PPeriod_sred) Plong = true;  // тек. полупериод стал длинный
          }
          else {
            if (PPeriod < PPeriod_sred) Plong = false; // тек. полупериод стал короткий
          }
          // если ни то, ни другое -- они равны, т.е. Plong без изменений
          if (!Plong) { // если полупериод короткий, пересчитываем среднее значение
            PPeriod_sred = (PPeriod_sred + PPeriod/2*3)/2;
          }
        }
        else {
          PPeriod_sred = PPeriod/2*3; // если ещё нет статистики, берём текущее значение
        }
        Pik = true; // есть сигнал
      }
      else { // был перерыв в сигнале
        if (PPeriod_sred != 0) {
          PPeriod_sred = 0;           // обнуляем среднее значение
          Plong = false;              // и считаем, что будет короткий полупериод
        }
      }
    }
    [свернуть]

    В этом варианте решил попробовать сделать захват сигнала через внешнее прерывание, которое на большинстве ардуин работает на порту D2. (Может у кого будут идеи по-лучше?)

    До записи на карту пока не дошёл, скетч пока только определяет наличие сигнала, захватывает первые 512 байт в буфер и по завершении вываливает всё это в терминал, плюс в конце выводит размер полупериода входного сигнала в микросекундах. На живом Векторе пока не проверял, но две ардуины между собой уже передают информацию вполне уверенно.

    Вот этот скетч в архиве: Test_InputROM.7z

  4. #134
    Master Аватар для Improver
    Регистрация
    06.02.2018
    Адрес
    г. Волгоград
    Сообщений
    974
    Спасибо Благодарностей отдано 
    427
    Спасибо Благодарностей получено 
    396
    Поблагодарили
    221 сообщений
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Продолжаем тему... Версия скетча с записью на SD-карту:

    Test_InputROM2

    Для работы требуется библиотека SdFat
    Код:
    /*
      SD-картридер подключяется к выводам ардуино:
     * MOSI  - D11
     * MISO  - D12
     * CLK   - D13
     * CS    - D10
     
     Вход - D2
     
     */
    #define InputPIN 2
    
    #include <SdFat.h>
    
    SdFat sd;
    SdFile dataFile;
    char FName[13] = "input000.vkt";
    
    volatile unsigned long PPeriod = 0;     // текущее значение полупериода
    volatile unsigned int PPeriod_sred = 0; // среднее значение границы длинны полупериода
    unsigned int Tpp = 0;                   // расчётное значение полупериода для вывода
    volatile unsigned long iMicros_old = 0; // предыдущее значение микросекунд
    volatile boolean Pik = false;           // есть изменение сигнала
    
    volatile byte BUFF[256];       // буфер данных
    volatile unsigned int CWB = 0; // индекс записи в буфер
    unsigned int CRB = 0;          // индекс чтения из буфера
    unsigned int CRB_temp = 0;     // временная переменная для CRB
    volatile byte bBit = 15;       // индекс записываемого полубита. (Начинаем с 15, т.к. первый бит теряется)
    volatile byte A = 0;           // записываемый байт
    volatile byte B = 0;           // записываемый бит
    int i0 = 0;                    // счётчик циклов без сигнала
    
    void setup() //процедура setup
    {
      pinMode(InputPIN, INPUT); //объявляем пин как вход
      pinMode(10, OUTPUT);
      Serial.begin(9600);
    
      Serial.print("Initializing SD card...");
      while (!sd.begin(10,SPI_FULL_SPEED)){ // SD-карта готова?
        Serial.println("SD-card failed!");
        delay(3000);       // ждем 3 с
      }
      Serial.println("SD-card is OK.");
      sd.chdir();            // устанавливаем корневую директорию SD
    }
    
    void loop() //процедура loop
    {
      if (dataFile.open(FName, FILE_WRITE)) { // открываем файл на запись
        Serial.print("Save data to file ");
        Serial.print(FName);
        Serial.print(" ... ");
        i0 = 0;
        CRB = 0;
    
        attachInterrupt(0, TakeTime, CHANGE); // включаем обработку внешнего прерывания
        while (!Pik) { // Ждём сигнал
          delay(10);   // задержка...
        }
        Serial.print("start ... ");
    
        noInterrupts();       // запрет прерываний
        CRB_temp = CWB;       // сохраняем CWB во временную переменную
        interrupts();         // разрешение прерываний
        //====================================
        do {
          while ((CRB_temp <= CRB) & Pik)// пока индекс записи меньше или равен индексу чтения и есть сигнал
          {                              // Ждём заполнения буфера
            i0++;
            Pik = (i0 < 10);             // что-то долго ждём...
            delay(1);                    // задержка на ввод данных
            noInterrupts();              // запрет прерываний
            CRB_temp = CWB;              // сохраняем CWB во временную переменную
            interrupts();                // разрешение прерываний
          }
          if (!Pik) break;
          i0 = 0;
    
          dataFile.write(BUFF[lowByte(CRB)]); // пишем байт
          CRB++;
        }
        while (Pik);
        //=====================================
        detachInterrupt(0);               // отключаем обработку внешнего прерывания 0
        if (((PPeriod_sred/3*2)%8) < 4) { // полупериод для последующего вывода...
          Tpp = (PPeriod_sred/12)*8;      // округление до кратного 8 в меньшую сторону
        }
        else {
          Tpp = ((PPeriod_sred/12)+1)*8;  // округление до кратного 8 в большую сторону
        }
        dataFile.write(0xFF);
        dataFile.write(highByte(Tpp));   // сохраняем скорость в файле
        dataFile.write(lowByte(Tpp));
        dataFile.close();      // закрываем файл
        Serial.println("done");
        Serial.print("Speed: ");
        Serial.println(Tpp); // расчётное значение длинны полупериода для вывода
    
      }
      else {
        Serial.print("Error opening ");
        Serial.println(FName);
      } 
      while (1) {
      } // тормозим ардуину
      Serial.println("stop");
    }
    
    void TakeTime()
    {
      boolean Plong = false;         // короткий полупериод по умолчанию
      PPeriod = micros() - iMicros_old;
      iMicros_old += PPeriod;
      if (PPeriod < 1000) { // началось...
        if (PPeriod_sred != 0) {
          Plong = (PPeriod > PPeriod_sred); // = false, если тек. полупериод короткий
          if (!Plong) { // если полупериод короткий, пересчитываем среднее значение
            PPeriod_sred = (PPeriod_sred + PPeriod/2*3)/2;
          }
        }
        else {
          PPeriod_sred = PPeriod/2*3; // ещё нет статистики, берём текущее значение
        }
      }
      else { // был перерыв в сигнале
        if (PPeriod_sred != 0) {
          PPeriod_sred = 0;           // обнуляем среднее значение
          CWB = 0;                    // начинаем запись с начала
          A = 0;
          B = 0;
        }
      }
    
      if (Plong) {      // получен длинный полупериод
        B ^= 1;         // инвертируем бит
        A = (A<<1)+B;   // заносим бит
        bBit--;         // уменьшаем счётчик полубитов
      }
      else {            // получен короткий полупериод
        if (bBit & 1) { // нечётный полубит
          A = (A<<1)+B;    // заносим бит
        }                  // нечётный -- пропускаем
      }
      // корректировка счётчиков
      if (bBit > 1) {
        bBit--;         // счётчик полубитов -1
      }
      else {
        BUFF[lowByte(CWB)] = A;  // заносим байт в буфер
        CWB++;
        A = 0;
        bBit += 15;     // = +16 -1
      }
      Pik = true; // есть сигнал!
    }
    [свернуть]

    Картридер подключается к тем же выводам, что и в ROM-плеере.

    Собственно, с этим скетчем ардуина ждёт подачи сигнала на D2, а при получении записывает все данные в файл "input000.vkt" и подвисает... В конец файла также записывается маркер 0xFF и расчётный период таймера для вывода в микросекундах (два байта) -- придумал свой формат файла для удобства... :-) Если на карте такой файл уже есть, данные дописываются в конец. Пока всё это ещё сыровато, но для отладки технологии записи уже вполне подходит.

    По схеме входа для подачи сигнала с Вектора тоже пока ещё не понятно, что будет лучше -- собрать схему на компараторе СА3, или можно будет всё упростить до предела...

    Архив со скетчем и необходимой библиотекой: Test_InputROM2.7z

  5. #135
    Guru Аватар для svofski
    Регистрация
    20.06.2007
    Адрес
    С.-Петербург
    Сообщений
    4,114
    Спасибо Благодарностей отдано 
    791
    Спасибо Благодарностей получено 
    654
    Поблагодарили
    401 сообщений
    Mentioned
    22 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Может быть для сохранения окажется удобным vcd (value change dump), или что-то похожее на него.
    Больше игр нет

  6. #136
    Veteran
    Регистрация
    22.02.2014
    Адрес
    г. Курган
    Сообщений
    1,657
    Спасибо Благодарностей отдано 
    218
    Спасибо Благодарностей получено 
    301
    Поблагодарили
    212 сообщений
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Подкинуть идейку
    В плеере уже есть и выгрузка и загрузка данных...
    Можно избавиться от ардуинных дисплея и кнопок...
    И использовать сам Вектор для интерфейса плеера...
    Например вот-так (как на скриншоте).
    Миниатюры Миниатюры Нажмите на изображение для увеличения. 

Название:	lvs_bios.jpg 
Просмотров:	106 
Размер:	16.6 Кб 
ID:	64801  

  7. #136
    С любовью к вам, Yandex.Direct
    Размещение рекламы на форуме способствует его дальнейшему развитию

  8. #137
    Master Аватар для Improver
    Регистрация
    06.02.2018
    Адрес
    г. Волгоград
    Сообщений
    974
    Спасибо Благодарностей отдано 
    427
    Спасибо Благодарностей получено 
    396
    Поблагодарили
    221 сообщений
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от svofski Посмотреть сообщение
    Может быть для сохранения окажется удобным vcd (value change dump), или что-то похожее на него.
    Посмотрел я описание этого формата в википедии... Только ASCII-символы, заголовки секций в текстовом виде... Всё это прекрасно при неограниченных ресурсах большого ПК, но не на ардуине, где разбор текстовых строк -- тот ещё подарок... :-(


    Цитата Сообщение от KTSerg Посмотреть сообщение
    Подкинуть идейку
    В плеере уже есть и выгрузка и загрузка данных...
    Можно избавиться от ардуинных дисплея и кнопок...
    И использовать сам Вектор для интерфейса плеера...
    Отличная идея -- сам обдумывал такой вариант. :-) Причём эта идея ещё интересна тем, что тогда ардуину с картридером можно будет разместить в корпусе Вектора (например, в пустой "ноге")... Но если начинать дальше продумывать интерфейс взаимодействия Вектор-ардуино, то сразу хочется сделать и передачу данных по этому же каналу, и тогда получим уже не "майфун", а совсем другое устройство, более близкое к ROM-картриджу. В общем, эту идею я решил пока отложить для другого проекта.

  9. #138
    Veteran
    Регистрация
    22.02.2014
    Адрес
    г. Курган
    Сообщений
    1,657
    Спасибо Благодарностей отдано 
    218
    Спасибо Благодарностей получено 
    301
    Поблагодарили
    212 сообщений
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от Improver Посмотреть сообщение
    ...
    Отличная идея -- сам обдумывал такой вариант. :-) Причём эта идея ещё интересна тем, что тогда ардуину с картридером можно будет разместить в корпусе Вектора (например, в пустой "ноге")... ...
    Если прятать в "ноге", значит вход/выход подключать нужно будет непосредственно в схему. Если оставлять мафонный вход/выход, нужно тумблер в разрыв втыкать, что-бы реальному мафону не мешать...

  10. #139
    Guru Аватар для svofski
    Регистрация
    20.06.2007
    Адрес
    С.-Петербург
    Сообщений
    4,114
    Спасибо Благодарностей отдано 
    791
    Спасибо Благодарностей получено 
    654
    Поблагодарили
    401 сообщений
    Mentioned
    22 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Не обязательно реализовывать vcd буквально. Взять просто идею сохранения времени изменения сигнала.
    Больше игр нет

  11. #140
    Master Аватар для Improver
    Регистрация
    06.02.2018
    Адрес
    г. Волгоград
    Сообщений
    974
    Спасибо Благодарностей отдано 
    427
    Спасибо Благодарностей получено 
    396
    Поблагодарили
    221 сообщений
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от KTSerg Посмотреть сообщение
    Если оставлять мафонный вход/выход, нужно тумблер в разрыв втыкать, что-бы реальному мафону не мешать...
    Не обязательно тумблер ставить, можно просто сделать несложную схемку аудио-развязки "два входа"-"один выход".

    В такой конструкции есть ещё один неразрешённый вопрос: как сделать управление из других программ? Ну, например, как запустить запись из бейсика? А сохранение текста из RETEX? Кнопок-то нет... Хотя, в какой-то мере можно использовать сигнал управления реле Вектора, но на большинстве оно не устанавливалось...

    - - - Добавлено - - -

    Новая версия скетча, теперь банановый с записью:

    RW-pleer_5

    Для работы требуются библиотеки TimerOne, SdFat, RTClib, а также стандартные LiquidCrystal и Wire. Вход для записи данных -- D2.
    Код:
    /*
     Вариант на "Data Logger Shield" и "LCD Keypad Shield"
     (на первом есть RTC -- используем для показа времени)
     
     Выход - D3
     
     "Data Logger Shield V1.0":
     SD-картридер подключен к выводам ардуино:
     * MOSI  - D11
     * MISO  - D12
     * CLK   - D13
     * CS    - D10
     
     "LCD Keypad Shield":
     Подключение экрана 1602А:
     * LCD RS pin     - D8
     * LCD Enable pin - D9
     * LCD D4 pin     - D4
     * LCD D5 pin     - D5
     * LCD D6 pin     - D6
     * LCD D7 pin     - D7
     * LCD R/W pin    - GND
     * LCD 5V pin     - 5V
     * Кнопки         - A0
     */
    
    // include the library code:
    #include <LiquidCrystal.h>
    #include <SdFat.h>
    #include <TimerOne.h>
    #include <Wire.h>
    #include "RTClib.h"
    
    RTC_DS1307 RTC;
    
    // initialize the library with the numbers of the interface pins
    LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
    
    SdFat sd;
    SdFile dataFile;
    
    //#define filenameLength    100      // максимальная длина имени файла
    //char fileName[filenameLength + 1]; // имя текущего файла
    char sfileName[14];                // короткое имя текущего файла/директории
    int currentFile = 1;               // текущая позиция в директории
    int maxFile = 0;                   // всего позиций в лиректории (файлов и поддиректорий)
    byte isDir = 0;                    // признак того, что текущая позиция -- это директория
    boolean isRoot = true;             // признак того, что мы в корне
    
    byte BLs = 0x01;    // начальный блок
    unsigned int Nbt;   // размер файла, байт
    
    volatile byte BUFF[256];       // буфер данных
    volatile unsigned int CRB = 0; // индекс чтения из буфера
    volatile byte bBit = 15;       // индекс читаемого полубита
    volatile unsigned int CWB = 0; // индекс записи в буфер
    unsigned int CRB_temp = 0;     // временная переменная для CRB
    int i0 = 0;                    // счётчик циклов без сигнала
    volatile byte A = 0;           // записываемый байт
    volatile byte B = 0;           // записываемый бит
    
    volatile unsigned long PPeriod = 0;     // текущее значение полупериода
    volatile unsigned int PPeriod_sred = 0; // среднее значение границы длинны полупериода
    volatile unsigned long iMicros_old = 0; // предыдущее значение микросекунд
    volatile boolean Pik = false;           // есть изменение сигнала
    
    // Заголовок
    byte SB[27] = { 
      0x4E, 0x4F, 0x44, 0x49, 0x53, 0x43, 0x30, 0x30, // NODISK00
      0x31, 0x34, 0x30, 0x32, 0x31, 0x38,             // дата: 140218
      0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // имя программы
      0x20, 0x20, 0x20, 0x00, 0x00 };
    
    char DT[15] = {
      '0', '0', ':', '0', '0', ':', '0', '0', ' ',
      '0', '0', '/', '0', '0', 0x00 };
    
    char iFName[13] = "input000.vkt";
    
    volatile int Tpp = 200; // Начальная длительность задержки сигнала в микросекундах (один полупериод)
    volatile int Tb  = 64;  // Дополнительная задержка сигнала на начало байта в микросекундах
    
    const int p = 3; // номер пина, на который будет вывод сигнала
    
    const int BT_none   = 0; // константы -- коды нажатой кнопки
    const int BT_right  = 1;
    const int BT_up     = 2;
    const int BT_down   = 3;
    const int BT_left   = 4;
    const int BT_select = 5;
    
    int MLevel = 0; // текущий пункт меню
    const int M_play = 0;
    const int M_play_in = 10;
    const int M_record = 1;
    const int M_record_in = 11;
    const int M_setup = 2;
    const int M_setup_in = 12;
    
    void setup() {
      pinMode(p, OUTPUT);  // объявляем пин как выход
      digitalWrite(p, LOW);
      pinMode(10, OUTPUT); // CS для SD-картридера
      digitalWrite(10, HIGH);
      lcd.begin(16, 2);    // объявляем размер экрана 16 символов и 2 строки
    
      Timer1.initialize(Tpp);               // инициализировать timer1, и установить период Tpp мкс.
      Timer1.attachInterrupt(SendHalfBit);  // прикрепить SendHalfBit(), как обработчик прерывания по переполнению таймера
      Timer1.stop();
    
      printtext("BEKTOP-MF",0);
      printtext("version 5.0",1);
      delay(2000);       // ждем 2 с для солидности :-)
    
      Wire.begin();
      RTC.begin();
      if (! RTC.isrunning()) {
        printtext("RTC is NOT run!",1);
        // following line sets the RTC to the date & time this sketch was compiled
        RTC.adjust(DateTime(__DATE__, __TIME__));
        delay(1000);       // ждем 1 с
        DateTime now = RTC.now(); // проверяем работу часов
        if (now.month() > 12) DT[0]=0x00; // если часы не работают, обнуляем первый байт в качестве флага
      }
      //RTC.adjust(DateTime(__DATE__, __TIME__));
    
      while (!sd.begin(10,SPI_FULL_SPEED)){ // SD-карта готова?
        printtext("SD-card failed!",1);
        tone(p, 200, 100); // нет -- включаем на 200 Гц на 100 мс
        delay(3000);       // ждем 3 с
      }
      printtext("SD-card is OK.",1);
    
      sd.chdir();            // устанавливаем корневую директорию SD
      //  getMaxFile();          // получаем количество файлов в директории и переходим к первому файлу в директории
    }
    
    void loop() {
      int RCrom = 0; // для кода возврата ПП
      int button = getPressedButton(); // какая кнопка нажата?
      while(getPressedButton()!=BT_none) { // Ждём, пока не будет отпущена кнопка
        delay(50); 
      }
      switch (button)
      {
      case BT_up:
        if (MLevel < 10) {
          if (MLevel > 0) MLevel--;
          else MLevel = 2;
        }
        break;
      case BT_down:
        if (MLevel < 10) {
          if (MLevel < 2) MLevel++;
          else MLevel = 0;
        }
        break;
      case BT_right:
        if (MLevel < 10) {
          switch (MLevel)
          {
          case M_play:
            printtext("Play file:",0);
            getMaxFile();
            break;
          case M_record:
            printtext("Record to file:",0);
            sd.chdir();     // устанавливаем корневую директорию SD
            while (sd.exists(iFName)) {
              if (iFName[7] < '9') {
                iFName[7]++;
              }
              else {
                iFName[7] = '0';
                if (iFName[6] < '9') {
                  iFName[6]++;
                }
                else {
                  iFName[6] = '0';
                  iFName[5]++;
                }
              }  
            }
            printtext(iFName,1);
            break;
          case M_setup:
            printtext("Setup",0);
            break;
          }    
          MLevel += 10;
          button = BT_none;
        }
        break;
      case BT_left:
        break;
      case BT_select: // Возврат в корень меню
        MLevel = 0;
        break;
      case BT_none: // ничего не нажато
        delay(100); 
        break;
      }
    
      switch (MLevel)
      {
      case M_play:
        printtext("Play file ->",0);
        printtime();
        break;
      case M_record:
        printtext("Record data ->",0);
        printtime();
        break;
      case M_setup:
        printtext("Settings ->",0);
        printtime();
        break;
      case M_play_in: // зашли в меню вопроизведения
        switch (button)
        {
        case BT_up: // вверх по файлам
          currentFile--;
          if(currentFile<1) {
            currentFile = maxFile;
          }
          seekFile();
          break;
        case BT_down: // вниз по файлам
          currentFile++;
          if(currentFile>maxFile) { 
            currentFile=1;
          }
          seekFile();
          break;
        case BT_left: // в корень или выход
          if (isRoot) {
            MLevel = M_play; // из корня выходим в стартовое меню
          }
          else {        // возврат в корневую директорию, ремарка ниже:
            //SDFat has no easy way to move up a directory, so returning to root is the easiest way. 
            //each directory (except the root) must have a file called ROOT (no extension)
            sd.chdir(true);
            isRoot = true; // помечаем, что мы в корне
            getMaxFile();
          }
          break;
        case BT_right: // вход в директорию, или запуск файла на воспроизведение
          if (isDir==1) { //Если это директория, то переход в неё
            sd.chdir(sfileName, true);
            isRoot = false;
            getMaxFile();
          } 
          else {         // если не директория -- пробуем воспроизвести файл
            if (Nbt != 0xFFFF) { // проверяем размер файла
              RCrom = 11; // для вывода сообщения "неверный формат"
              printtext("Playing...",0);
              for (int i=0;8;i++){ // цикл по имени файла
                if (sfileName[i]=='.'){                                                // ищем точку в имени файла
                  if (((sfileName[i+1]|0x20)=='r')&((sfileName[i+3]|0x20)=='m')) {     // проверяем первый и третий символы расширения == 'r'|'R', == 'm'|'M'
                    if (((sfileName[i+2]|0x20)=='o')|((sfileName[i+2]|0x20)=='0')) {   // == 'o'|'O'|'0'
                      if ((sfileName[i+2]|0x20)!='0') { // проверка на вывод нулевого блока по расширению файла
                        BLs = 0x01; // с первого блока
                      }
                      else { 
                        BLs = 0x00; // с нулевого блока
                      }
                      RCrom = PlayROM(sfileName, i);// Передаём короткое имя файла и позицию точки в качестве параметров
                    }
                  }
                  else { // проверяем расширение файла на "VKT"
                    if (((sfileName[i+1]|0x20)=='v')&((sfileName[i+2]|0x20)=='k')&((sfileName[i+3]|0x20)=='t')) {
                      delay(2000);
                      RCrom = 0; // тут будет вызов ПП для формата VKT
                    }
                  }  
                  break;
                }
              }
            }
            else {
              RCrom = 10; // для вывода сообщения "большой файл"
            }
    
            digitalWrite(p, LOW);             // выход = 0
            switch (RCrom)                    // Проверяем код возврата
            {
            case 0:
              printtext("Done.",0);           // Всё закончилось успешно.
              break;
            case 1:
              printtext("Stopped",0);         // Сообщение об остановке
              while(getPressedButton()!=BT_none) { // Ждём, пока не будет отпущена кнопка
                delay(50); 
              }
              break;
            case 10:
              printtext("File is too big",0);  // большой файл
              break;
            case 11:
              printtext("Unknown format",0);   // выбран не ROM/R0M/VKT-файл
              break;
            default:  
              printtext("ERROR!",0);           // Сообщение об ошибке
            }
            delay(1000);           // ждем 1 с
            printtext("Play file:",0);
            seekFile();
          }
        }
        break;
      case M_record_in: // зашли в меню записи
        if (button == BT_right) {
          if (dataFile.open(iFName, FILE_WRITE)) { // открываем файл на запись
            do {
              printtext("Waiting...",0);  
              i0 = 0;    // Сбрасываем индексы
              CRB = 0;
              bBit = 14; // индекс записываемого полубита. (Начинаем с 15, т.к. первый бит теряется)
    
              attachInterrupt(0, TakeTime, CHANGE); // включаем обработку внешнего прерывания
              while (!Pik) { // Ждём сигнал
                delay(10);   // задержка...
                if (getPressedButton()!=BT_none) { // кнопка нажата?
                  break;                           // прерывание режима загрузки при нажатии кнопки
                }
              }
              printtext("Start...",0);  
    
              noInterrupts();       // запрет прерываний
              CRB_temp = CWB;       // сохраняем CWB во временную переменную
              interrupts();         // разрешение прерываний
              //====================================
              do {
                while ((CRB_temp <= CRB) & Pik)// пока индекс записи меньше или равен индексу чтения и есть сигнал
                {                              // Ждём заполнения буфера
                  i0++;                        // счиаем циклы
                  Pik = (i0 < 10);             // что-то долго ждём...
                  delay(1);                    // задержка на ввод данных
                  noInterrupts();              // запрет прерываний
                  CRB_temp = CWB;              // сохраняем CWB во временную переменную
                  interrupts();                // разрешение прерываний
                }
                if (!Pik) break;               // выход из чтения т.к. сигнал закончился
                i0 = 0;
    
                dataFile.write(BUFF[lowByte(CRB)]); // пишем байт из буфера
                CRB++;
              }
              while (Pik); // пока есть сигнал
              //=====================================
              detachInterrupt(0);               // отключаем обработку внешнего прерывания 0
              if (((PPeriod_sred/3*2)%8) < 4) { // полупериод для последующего вывода...
                Tpp = (PPeriod_sred/12)*8;      // округление до кратного 8 в меньшую сторону
              }
              else {
                Tpp = ((PPeriod_sred/12)+1)*8;  // округление до кратного 8 в большую сторону
              }
            }
            while (Tpp < 32); // проверка -- если было ложное срабатывание (задержка <32 мкс), то снова ждём...
            dataFile.write(0xFF);
            dataFile.write(highByte(Tpp));   // сохраняем скорость в файле
            dataFile.write(lowByte(Tpp));
            if (DT[0] > 0x00) {
              DateTime now = RTC.now();                 // получаем текущее время и сохраняем в атрибутах файла
              dataFile.timestamp(T_CREATE,now.year(),now.month(),now.day(),now.hour(),now.minute(),now.second());
              dataFile.timestamp(T_WRITE,now.year(),now.month(),now.day(),now.hour(),now.minute(),now.second());
            }
            dataFile.close();                // закрываем файл
            printtext("Done. Speed:",0);
            lcd.setCursor(13, 0); // устанавливаем курсор в позицию 0 в строке 1
            lcd.print(Tpp);                  // расчётное значение длинны полупериода для вывода
          }
          else {
            printtext("Error open file",0);
          } 
          delay(1000);
          MLevel = M_record;
        }
        else {
          if (button == BT_left) MLevel = M_record;
        }
        break;
      case M_setup_in: // зашли в меню настроек
        printtext("Period(mks):",1);
        switch (button)
        {
        case BT_up:   
          if (Tpp<400) Tpp += 8;
          break;
        case BT_down:   
          if (Tpp>160) Tpp = Tpp - 8;
          break;
        case BT_left:
          MLevel = M_setup;
        }
        lcd.setCursor(12,1);
        lcd.print(Tpp);
        break;
      }
    }
    
    //=================================================================
    int getPressedButton()  // функция проверки нажатой кнопки
    {
      int buttonValue = analogRead(0);
      if (buttonValue < 80) return BT_right;
      else if (buttonValue < 200) return BT_up;
      else if (buttonValue < 380) return BT_down;
      else if (buttonValue < 600) return BT_left;
      else if (buttonValue < 800) return BT_select;
      return BT_none;
    }
    
    void printtext(char* text, int l) {  // Вывод текста на экран в строке l с очисткой строки
      lcd.setCursor(0,l);
      lcd.print(text);
      lcd.print("                ");
    }
    
    void printtime() { // вывод времени и даты
      lcd.setCursor(0, 1); // устанавливаем курсор в позицию 0 в строке 1
      if (DT[0] > 0x00) {                         // если не обнулён первый байт -- часы работают
        DateTime now = RTC.now();                 // получаем текущее время
        DT[0] = now.hour()/10 + '0';              // перевод из целого в символ
        DT[1] = now.hour()%10 + '0';              // часы
        DT[3] = now.minute()/10 + '0';            // минуты
        DT[4] = now.minute()%10 + '0';            // минуты
        DT[6] = now.second()/10 + '0';            // секунды
        DT[7] = now.second()%10 + '0';            // секунды
        DT[9] = now.day()/10 + '0';               // день
        DT[10] = now.day()%10 + '0';              // день
        DT[12] = now.month()/10 + '0';            // месяц
        DT[13] = now.month()%10 + '0';            // месяц
        lcd.print(DT);                            // выводим время и дату
        lcd.print("  ");
      }
      else {
        lcd.print(millis()/1000); // выводим количество секунд с момента влючения ардуины вместо времени
        lcd.print("               ");
      }
    }
    
    void getMaxFile() { // считаем файлы в текущей директории и сохраняем в maxFile
      dataFile.cwd()->rewind();
      maxFile=0;
      while(dataFile.openNext(dataFile.cwd(),O_READ)) {
        dataFile.close();
        maxFile++;
      }
      currentFile=1;
      seekFile();
    }
    
    void seekFile() { // переход на позицию в директории, сохранение имени файла и показ его на экране
      dataFile.cwd()->rewind();
      for(int i=1;i<currentFile;i++) { // читаем первые файлы до currentFile
        dataFile.openNext(dataFile.cwd(),O_READ);
        dataFile.close();
      }
      dataFile.openNext(dataFile.cwd(),O_READ); // читаем данные текущего файла
      //dataFile.getName(fileName,filenameLength);
      dataFile.getSFN(sfileName);
      isDir = dataFile.isDir();
      if (dataFile.fileSize()<=0xFFFE) {// проверка размера файла <=65534 или для VKT без служебной информации будет <=44458
        Nbt = dataFile.fileSize();      // размер файла ОК
      }
      else {
        Nbt = 0xFFFF;                  // слишком большой для загрузки
      }
      dataFile.close();
      if (isDir!=1) {
        printtext(sfileName,1);       // вывод имени текущего файла
      }
      else {
        for (int i=0;i<14;i++) {
          if (sfileName[i]==0x00) {
            sfileName[i]='>';             // если это директория, добавляем в конце символ '>'
            sfileName[i+1]=0x00;
            printtext(sfileName,1);       // вывод имени текущей директории
            sfileName[i]=0x00;            // и удаляем его...
            break;
          }
        }
      } 
    }
    
    int PlayROM(char FName[], int pnt) // функция вывода файла
    {
      delay(1000);           // ждем 1 с
    
      if (dataFile.open(FName,O_READ)) { // открываем файл. Открылся?
        byte BLe = Nbt/256; // всего блоков
        byte BLt;           // осталось блоков
        byte Nst;           // номер строки
        byte St;            // выводимый байт
    
        byte CSz = 0x00;    // контрольная сумма заголовка
        byte CSs = 0x00;    // контрольная сумма строки
        byte i;
        byte j;
    
        if (Nbt%256 != 0) BLe++; // корректировка количества блоков, если размер файла не кратен 256
    
        for (i=0; i<=7; i++){                // заносим в SB имя файла
          if (i < pnt) {                     // имя ещё не закончилось?
            if ((FName[i]>=0x61) && (FName[i]<=0x7A)) {
              SB[i+14] = FName[i] - 0x20;    // на заглавные буквы
            }
            else if (FName[i]!=0x7E) {       // не тильда
              SB[i+14] = FName[i];
            }
            else {
              SB[i+14] = '_';                // меняем тильду на подчёркивание, иначе это будет русская "Ч"
            }
          }
          else SB[i+14] = 0x20;              // пробелы
        }
        for (i=1; i<=3; i++){                // заносим в SB расширение файла
          if ((FName[pnt+i]>=0x61) && (FName[pnt+i]<=0x7A)) {
            SB[i+21] = FName[pnt+i] - 0x20;  // на заглавные буквы
          }
          else {
            SB[i+21] = FName[pnt+i];
          }
        }
    
        dir_t d;
        dataFile.dirEntry(&d);                    // Считываем дату файла
        uint16_t AAA = FAT_DAY(d.lastWriteDate); // Сохраняем дату файла в заголовке -- день
        SB[8] = (AAA%100)/10 + '0';              // перевод из целого в символ
        SB[9] = AAA%10 + '0';
        AAA = FAT_MONTH(d.lastWriteDate);        // месяц
        SB[10] = (AAA%100)/10 + '0';
        SB[11] = AAA%10 + '0';
        AAA = FAT_YEAR(d.lastWriteDate);         // последние две цифры года
        SB[12] = (AAA%100)/10 + '0';
        SB[13] = AAA%10 + '0';
    
        CRB = 0;                        // Сбрасываем индексы.
        CWB = 0;
        bBit = 15;
    
        if (Tpp <= 264) { // Вычисляем значение задержки на начало байта Tb
          Tb = 16;                       // для полупериода от 248 до 264
          if (Tpp <= 240) Tb = 264 - Tpp;// для полупериода меньше или равном 240 
        }
        else Tb = 0;                     // для полупериода больше 264
    
        // Начинаем наполнять буфер
        for (i=0; i<=3; i++){            // преамбула (4*(00H*25+55H*25))
          for (j=0; j<=24; j++){
            BUFF[lowByte(CWB)] = 0x00;
            CWB++;
          }
          for (j=0; j<=24; j++){
            BUFF[lowByte(CWB)] = 0x55;
            CWB++;
          }
        }  
    
        Timer1.setPeriod(Tpp); // Выставляем период таймера
        Timer1.start();        // Запускаем таймер............
    
        for (BLt=BLe; BLt>=1; BLt--){   // Вывод блоков данных в цикле
          CSz = BLs;
          CSz += BLe;
          CSz += BLt;
    
          for (j=0; j<=15; j++) ToBUFF(0x00);// 00h*16
          for (j=0; j<=3; j++)  ToBUFF(0x55);// 55h*4
          ToBUFF(0xE6);                      // E6h*1
          for (j=0; j<=3; j++)  ToBUFF(0x00);// 00h*4
    
          for (j=0; j<=26; j++){             // заголовок блока
            CSz += SB[j];
            ToBUFF(SB[j]);
          }
          ToBUFF(BLs);                       // начальный блок
          ToBUFF(BLe);                       // конечный блок
          ToBUFF(BLt);                       // осталось блоков
    
          lcd.setCursor(12, 0);              // выводим на экран кол-во оставшихся блоков
          lcd.print(BLt);
          lcd.print(' ');
    
          ToBUFF(CSz);                       // контр.сумма заголовка
    
          for (Nst=0x80; Nst<=0x87; Nst++){   // вывод строк (8 шт.)
            for (j=0; j<=3; j++) ToBUFF(0x00);// 00h*4
            ToBUFF(0xE6);                     // E6h*1
            CSs = Nst;
            ToBUFF(Nst);                      // номер строки
            CSs += CSz;
            ToBUFF(CSz);                      // контр.сумма заголовка
    
            // начинаем вывод строки данных
            for (j=0; j<=31; j++){      // цикл на 32 байта
              if (Nbt > 0){             // ещё есть данные?
                St = dataFile.read();   // читаем очередной байт из файла
                Nbt--;
              }
              else {                    // нет -- дополняем нулями
                St = 0x00;
              }
              ToBUFF(St);               // передаём считанный байт
              CSs += St;
              if (getPressedButton()!=BT_none) { // кнопка нажата?
                Timer1.stop();          // Останавливаем таймер
                dataFile.close();       // закрываем файл
                return 1;               // выход из ПП с ошибкой 1.
              }
              if (CRB_temp > CWB) {     // проверка -- не обогнало ли чтение запись?
                Timer1.stop();          // Останавливаем таймер
                dataFile.close();       // закрываем файл
                return 2;               // выход из ПП с ошибкой 2.
              }
            }
            ToBUFF(CSs);                // контр.сумма строки
          }  
        }
        dataFile.close(); // закрываем файл
    
        for (j=0; j<=31; j++) ToBUFF(0x00);// 00h*32 -- завершение вывода программы (?)
      }
      else tone(p, 200, 300); // нет файла -- включаем на 200 Гц на 300 мс
    
      do{                     // Ждём опустошения буфера
        delay(Tpp/64);        // задержка на вывод 1 байта (~Tpp*16/1000)
        noInterrupts();       // запрет прерываний
        CRB_temp = CRB;       // сохраняем CRB во временную переменную
        interrupts();         // разрешение прерываний
      }
      while (CRB_temp < CWB);
    
      Timer1.stop();  // Останавливаем таймер............
      return 0;       // выход из ПП с кодом 0.
    }
    
    void ToBUFF(byte SBb){     // Подпрограмма записи байта в буфер
      noInterrupts();               // запрет прерываний
      CRB_temp = CRB;               // сохраняем CRB во временную переменную
      interrupts();                 // разрешение прерываний
      if (CWB > (CRB_temp + 255)) { // Если позиция записи больше, чем позиция чтения + размер буфера - 1
        delay(Tpp);                 // Задержка (Tpp*1000 мкс = Tpp мс = 125 байт)
      }
      BUFF[lowByte(CWB)] = SBb;
      CWB++;
    }
    
    void SendHalfBit() {  // Подпрограмма вывода полубита по циклу таймера
      byte Pd=PORTD;
      if (bBit & 1){      // проверка индекса полубитов на чётность
        if (( (Pd >> p)^( BUFF[lowByte(CRB)] >> (bBit >> 1) ))&1){ // Если состояние порта и выводимый бит разные
          Pd ^= (1 << p); // инвертируем бит в позиции p
        }
      }
      else{               // чётный -- просто инвертируем порт
        Pd ^= (1 << p);   // инвертируем бит в позиции p(=3)
      }
      PORTD = Pd;         // вывод в порт p
      if (bBit > 0) {     // правим счётчики полубитов и байтов
        bBit--;
        if (bBit == 14) Timer1.setPeriod(Tpp); // Выставляем период таймера (биты)
      }
      else{
        bBit = 15;
        CRB++;
        if (CRB > 200) Timer1.setPeriod(Tpp+Tb); // Выставляем увеличенный период таймера (начало байта)
      }
    }
    
    void TakeTime()
    {
      boolean Plong = false;         // короткий полупериод по умолчанию
      PPeriod = micros() - iMicros_old;
      iMicros_old += PPeriod;
      if (PPeriod < 1000) { // началось...
        if (PPeriod_sred != 0) {
          Plong = (PPeriod > PPeriod_sred); // = false, если тек. полупериод короткий
          if (!Plong) { // если полупериод короткий, пересчитываем среднее значение
            PPeriod_sred = (PPeriod_sred + PPeriod/2*3)/2;
          }
        }
        else {
          PPeriod_sred = PPeriod/2*3; // ещё нет статистики, берём текущее значение
        }
      }
      else { // был перерыв в сигнале
        if (PPeriod_sred != 0) {
          PPeriod_sred = 0;           // обнуляем среднее значение
          CWB = 0;                    // начинаем запись с начала
          A = 0;
          B = 0;
        }
      }
    
      if (Plong) {      // получен длинный полупериод
        B ^= 1;         // инвертируем бит
        A = (A<<1)+B;   // заносим бит
        bBit--;         // уменьшаем счётчик полубитов
      }
      else {            // получен короткий полупериод
        if (bBit & 1) { // нечётный полубит
          A = (A<<1)+B;    // заносим бит
        }                  // нечётный -- пропускаем
      }
      // корректировка счётчиков
      if (bBit > 1) {
        bBit--;         // счётчик полубитов -1
      }
      else {
        BUFF[lowByte(CWB)] = A;  // заносим байт в буфер
        CWB++;
        A = 0;
        bBit += 15;     // = +16 -1
      }
      Pik = true; // есть сигнал!
    }
    [свернуть]

    Что сделано:
    - Добавлена запись данных. Запись выполняется в файлы вида "inputXXX.vkt", где ХХХ -- это числа от 000 до 999, выбирается файл с наименьшим числом, которого ещё нет в корне на SD-карте.
    - Увеличена граница максимального размера файла до 65534 байта.
    - Добавлена проверка наличия часов, если их нет, то показывается вывод секунд от старта, как и ранее.
    - Добавлено сохранение даты/времени записи файла (при наличии часов).
    - В связи с перечисленными изменениями, переделан интерфейс. Теперь при старте нужно выбрать "чтение", "запись" или "настройки".

    Назначение кнопок:
    <UP>,<DOWN> -- выбор пункта меню, выбор файла, изменение значения и т.п.
    <LEFT> -- возврат назад
    <RIGHT> -- вход в пункты меню, запуск чтения/записи.
    <SELECT> -- переход в корень меню. (фактически уже не нужна, но путь будет...)

    Ремарка по сохранению данных: сделать первоначальный набор имени файла для сохранения можно, но делать это на пяти кнопках и двух строках экрана будет крайне не удобно, я думаю, гораздо проще переткнуть потом SD-карту в комп и переименовать файл по своему желанию...

    В итоге, осталось только сделать вывод файлов в формате сохранения VKT... Ну и потом протестить на железе, в особенности на не-ROM-форматах.

    Вот архив со скетчем и всеми библиотеками: RW-player_5.7z

Страница 14 из 17 ПерваяПервая ... 1011121314151617 ПоследняяПоследняя

Информация о теме

Пользователи, просматривающие эту тему

Эту тему просматривают: 1 (пользователей: 0 , гостей: 1)

Похожие темы

  1. Портативный AY плеер.
    от Руслан в разделе Звук
    Ответов: 1
    Последнее: 16.04.2014, 08:46
  2. Service rom + 128 basic rom
    от VELESOFT в разделе Оси
    Ответов: 1
    Последнее: 24.03.2013, 04:48
  3. Плеер для pt 3
    от Руслан в разделе Музыка
    Ответов: 25
    Последнее: 14.08.2012, 19:25
  4. Advanced ROM Manager (ROM Switvcher + Prof. ROM)
    от Alex_NEMO в разделе Память
    Ответов: 4
    Последнее: 04.10.2010, 11:43
  5. AY плеер
    от newart в разделе Звук
    Ответов: 19
    Последнее: 20.07.2006, 00:03

Метки этой темы

Ваши права

  • Вы не можете создавать новые темы
  • Вы не можете отвечать в темах
  • Вы не можете прикреплять вложения
  • Вы не можете редактировать свои сообщения
  •