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

User Tag List

Страница 15 из 17 ПерваяПервая ... 11121314151617 ПоследняяПоследняя
Показано с 141 по 150 из 164

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

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

    По умолчанию

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

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

    По умолчанию

    Свежая версия "цифрового магнитофона" для Вектора:

    RW-player_6

    Для работы требуются библиотеки TimerOne, SdFat, RTClib, а также стандартные LiquidCrystal и Wire.
    Код:
    /*
     Вариант на "Data Logger Shield" и "LCD Keypad Shield"
     (на первом есть RTC -- используем для показа времени)
     
     Выход - D3
     Вход - D2
     
     "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 <LiquidCrystal.h>
    #include <SdFat.h>
    #include <TimerOne.h>
    #include <Wire.h>
    #include "RTClib.h"
    
    //инициализация часов
    RTC_DS1307 RTC;
    // инициализация экрана
    LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
    
    SdFat sd;
    SdFile dataFile;
    
    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; // индекс чтения из буфера
    unsigned int CRB_temp = 0;     // временная переменная для CRB
    volatile byte bBit = 15;       // индекс читаемого полубита
    volatile unsigned int CWB = 0; // индекс записи в буфер
    volatile byte A = 0;           // записываемый байт
    volatile byte B = 0;           // записываемый бит
    int i0 = 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
      0x32, 0x39, 0x30, 0x33, 0x31, 0x38,             // дата: 290318
      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 6.0",1);           // и версии
      delay(2000);                          // ждем 2 с для солидности :-)
    
      Wire.begin();                         // запуск часов
      RTC.begin();
      if (! RTC.isrunning()) {
        printtext("RTC is NOT run!",1);     // часы стоят
        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);
        delay(3000);       // ждем 3 с
      }
      printtext("SD-card is OK.",1);
      sd.chdir();            // устанавливаем корневую директорию SD
    }
    
    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')) {
                      RCrom = PlayVKT(sfileName); // вызов ПП для формата 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-файл, либо не найдена метка скорости в 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);            // устанавливаем курсор в позицию 13 в строке 0
            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.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;
          }
        }
      } 
    }
    
    void CalcTb()  // Вычисление значения задержки на начало байта Tb
    {
      if (Tpp <= 264) {
        Tb = 16;                       // для полупериода от 248 до 264
        if (Tpp <= 240) Tb = 264 - Tpp;// для полупериода меньше или равном 240 
      }
      else Tb = 0;                     // для полупериода больше 264
    }
    
    void WaitBuffer() // ожидание очистки буфера при воспроизведении
    {
      do{                     // Ждём опустошения буфера
        delay(Tpp/64);        // задержка на вывод 1 байта (~Tpp*16/1000)
        noInterrupts();       // запрет прерываний
        CRB_temp = CRB;       // сохраняем CRB во временную переменную
        interrupts();         // разрешение прерываний
      }
      while (CRB_temp < CWB);
      Timer1.stop();  // Останавливаем таймер............
    }
    
    int PlayVKT(char FName[]) // функция вывода файла VKT
    {
      delay(1000);            // ждем 1 с
      if (dataFile.open(FName,O_READ)) { // открываем файл. Открылся?
        dataFile.seekSet(Nbt-3); // на позицию -3 байт от конца файла
        if (dataFile.read()!=0xFF) return 11; // метка не найдена
        Tpp = dataFile.read()*256 + dataFile.read(); // считываем Tpp
        if ((Tpp < 160)|(Tpp > 511)) return 11; // не правильная метка
        dataFile.seekSet(0); // на начало файла
    
        CRB = 0;                        // Сбрасываем индексы.
        bBit = 15;
        CalcTb();  // Вычисляем значение задержки на начало байта Tb
    
        // Начинаем наполнять буфер
        for (CWB=0; CWB<=250; CWB++){ // первые 250 байт
          BUFF[CWB] = dataFile.read();
        }
        CWB = 251;             // продолжаем с 251-го байта буфера
    
        Timer1.setPeriod(Tpp); // Выставляем период таймера
        Timer1.start();        // Запускаем таймер............
    
        for (unsigned int i=251; i<=Nbt-4; i++){ // всё остальное, кроме последних 3 байт
          ToBUFF(dataFile.read());
    
          lcd.setCursor(12, 0);  // выводим на экран кол-во оставшихся "псевдоблоков" по 256 байт
          lcd.print((Nbt-i)>>8);
          lcd.print(' ');
    
          if (getPressedButton()!=BT_none) { // кнопка нажата?
            Timer1.stop();          // Останавливаем таймер
            dataFile.close();       // закрываем файл
            return 1;               // выход из ПП с ошибкой 1.
          }
          if (CRB_temp > CWB) {     // проверка -- не обогнало ли чтение запись?
            Timer1.stop();          // Останавливаем таймер
            dataFile.close();       // закрываем файл
            return 2;               // выход из ПП с ошибкой 2.
          }
        }
    
        dataFile.close(); // закрываем файл
      }
      else return 3; // нет файла 
      WaitBuffer();  // ожидаем окончания вывода
      return 0;
    }
    
    int PlayROM(char FName[], int pnt) // функция вывода файла ROM
    {
      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);                    // Считываем дату файла и сохраняем в заголовке
        SB[8] = FAT_DAY(d.lastWriteDate)/10 + '0';         // перевод из целого в символ -- день
        SB[9] = FAT_DAY(d.lastWriteDate)%10 + '0';
        SB[10] = FAT_MONTH(d.lastWriteDate)/10 + '0';      // месяц
        SB[11] = FAT_MONTH(d.lastWriteDate)%10 + '0';
        SB[12] = (FAT_YEAR(d.lastWriteDate)%100)/10 + '0'; // последние две цифры года
        SB[13] = FAT_YEAR(d.lastWriteDate)%10 + '0';
    
        CRB = 0;                        // Сбрасываем индексы.
        CWB = 0;
        bBit = 15;
        CalcTb(); // Вычисляем значение задержки на начало байта Tb
    
        // Начинаем наполнять буфер
        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 return 3; // нет файла 
      WaitBuffer();  // ожидаем окончания вывода
      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; // есть сигнал!
    }
    [свернуть]

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

    Вопрос по схеме для входа пока открыт... Вижу три варианта:
    1. Собрать схему на транзисторе, как в zx-tapper.
    2. Подключаться "по цифре" непосредственно к выходу 14 микросхемы ВВ55А Вектора.
    3. Собрать схему на компараторе К554СА3А, как в Векторе. Но этот вариант требует двуполярного питания, которого в ардуине нет... :-(

    Будем пробовать, начиная с первого варианта.

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

  3. #143
    Activist Аватар для Trol73
    Регистрация
    07.05.2015
    Адрес
    г. Ульяновск
    Сообщений
    350
    Записей в дневнике
    1
    Спасибо Благодарностей отдано 
    50
    Спасибо Благодарностей получено 
    41
    Поблагодарили
    25 сообщений
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Как вариант, ещё можно попробовать использовать встроенный компаратор AVR-ки.

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

    По умолчанию

    Цитата Сообщение от Trol73 Посмотреть сообщение
    Как вариант, ещё можно попробовать использовать встроенный компаратор AVR-ки.
    Да, и это тоже вариант, вот только я пока ещё не разобрался, как это сделать... Есть хороший пример?

  5. #145
    Activist Аватар для Trol73
    Регистрация
    07.05.2015
    Адрес
    г. Ульяновск
    Сообщений
    350
    Записей в дневнике
    1
    Спасибо Благодарностей отдано 
    50
    Спасибо Благодарностей получено 
    41
    Поблагодарили
    25 сообщений
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    С точки зрения схемотехники попадался такой простой вариант сделать из двуполярного напряжения однополярное:
    Нажмите на изображение для увеличения. 

Название:	converter.GIF 
Просмотров:	263 
Размер:	4.2 Кб 
ID:	64819
    На сколько оно будет дружить с входящим сигналом - надо проверять экспериментально..

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

    По умолчанию

    Итак, прикрутил я встроенный ардуиновский компаратор, вот скетч:

    RW-player_7

    Для работы требуются библиотеки TimerOne, SdFat, RTClib, а также стандартные LiquidCrystal и Wire.
    Код:
    /*
     Вариант на "Data Logger Shield" и "LCD Keypad Shield"
     (на первом есть RTC -- используем для показа времени)
     
     Выход - D3
     Вход - A1
     
     "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 <LiquidCrystal.h>
    #include <SdFat.h>
    #include <TimerOne.h>
    #include <Wire.h>
    #include "RTClib.h"
    
    //инициализация часов
    RTC_DS1307 RTC;
    // инициализация экрана
    LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
    
    SdFat sd;
    SdFile dataFile;
    
    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; // индекс чтения из буфера
    unsigned int CRB_temp = 0;     // временная переменная для CRB
    volatile byte bBit = 15;       // индекс читаемого полубита
    volatile unsigned int CWB = 0; // индекс записи в буфер
    volatile byte A = 0;           // записываемый байт
    volatile byte B = 0;           // записываемый бит
    int i0 = 0;                    // счётчик циклов без сигнала
    
    volatile unsigned long PPeriod = 0;     // текущее значение полупериода
    volatile unsigned long PPeriod_sred;    // среднее значение границы длинны полупериода
    volatile unsigned long iMicros_old = 0; // предыдущее значение микросекунд
    volatile boolean Pik = false;           // есть изменение сигнала
    
    // Заголовок блока
    byte SB[27] = { 
      0x4E, 0x4F, 0x44, 0x49, 0x53, 0x43, 0x30, 0x30, // NODISK00
      0x32, 0x39, 0x30, 0x33, 0x31, 0x38,             // дата: 290318
      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; // номер пина, на который будет вывод сигнала
    #define InputPIN A1 // номер пина, на котором будет получение сигнала
    
    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(InputPIN, INPUT); //объявляем пин как вход
      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 7.0",1);           // и версии
      delay(2000);                          // ждем 2 с для солидности :-)
    
      Wire.begin();                         // запуск часов
      RTC.begin();
      if (! RTC.isrunning()) {
        printtext("RTC is NOT run!",1);     // часы стоят
        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);
        delay(3000);       // ждем 3 с
      }
      printtext("SD-card is OK.",1);
      sd.chdir();            // устанавливаем корневую директорию SD
    
      DIDR1 = (1<<AIN1D)|(1<<AIN0D); // Выключить цифровые входы контактов Digital 6 и 7
    }
    
    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')) {
                      RCrom = PlayVKT(sfileName); // вызов ПП для формата 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-файл, либо не найдена метка скорости в 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)) { // открываем файл на запись
            printtext("Waiting...",0);  // ожидание сигнала
            i0 = 0;    // Сбрасываем индексы
            CRB = 0;
    
            ADCSRA = 0;          // выключить АЦП
            ADCSRB |= (1<<ACME); // включить мультиплексор
            ADMUX = (1<<MUX0)    // включить вход на A1
              |(1<<REFS1)|(1<<REFS0); // опорное напряжение АЦП (?)
    
            ACSR  = (1<<ACIE)    // Разрешить прерывание аналогового компаратора
              |(1<<ACBG);        // внутреннее опорное напряжение
            delay(1);            // ждём немного
            Pik = false;         // сбрасываем первое срабатывание
            while (!Pik) { // Ждём сигнал
              delay(10);   // задержка...
              if (digitalRead(0)!=HIGH) { // кнопка нажата?
                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); // пока есть сигнал
            //=====================================
            ACSR = 0;     // отключаем обработку прерывания компаратора
            ADCSRA = 135; // включить АЦП, установить делитель =128
    
            if (((PPeriod_sred/3*2)%8) < 4) { // расчитываем полупериод для последующего вывода...
              Tpp = (PPeriod_sred/12)*8;      // округление до кратного 8 в меньшую сторону
            }
            else {
              Tpp = ((PPeriod_sred/12)+1)*8;  // округление до кратного 8 в большую сторону
            }
    
            if (CRB > 0) { // проверка -- если было ложное срабатывание, то ничего не пишем.
              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());
              }
              printtext("Done. Speed:",0);
              lcd.setCursor(13, 0);            // устанавливаем курсор в позицию 13 в строке 0
              lcd.print(Tpp);                  // расчётное значение длинны полупериода для вывода
            }
            else {
              printtext("Canceled.",0);
              if (!dataFile.remove()) {
                delay(1000);
                printtext("Error del.file",0);
              }
            }
            dataFile.close();        
          }
          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.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;
          }
        }
      } 
    }
    
    void CalcTb()  // Вычисление значения задержки на начало байта Tb
    {
      if (Tpp <= 264) {
        Tb = 16;                       // для полупериода от 248 до 264
        if (Tpp <= 240) Tb = 264 - Tpp;// для полупериода меньше или равном 240 
      }
      else Tb = 0;                     // для полупериода больше 264
    }
    
    void WaitBuffer() // ожидание очистки буфера при воспроизведении
    {
      do{                     // Ждём опустошения буфера
        delay(Tpp/64);        // задержка на вывод 1 байта (~Tpp*16/1000)
        noInterrupts();       // запрет прерываний
        CRB_temp = CRB;       // сохраняем CRB во временную переменную
        interrupts();         // разрешение прерываний
      }
      while (CRB_temp < CWB);
      Timer1.stop();  // Останавливаем таймер............
    }
    
    int PlayVKT(char FName[]) // функция вывода файла VKT
    {
      delay(1000);            // ждем 1 с
      if (dataFile.open(FName,O_READ)) { // открываем файл. Открылся?
        dataFile.seekSet(Nbt-3); // на позицию -3 байт от конца файла
        if (dataFile.read()!=0xFF) return 11; // метка не найдена
        Tpp = dataFile.read()*256 + dataFile.read(); // считываем Tpp
        if ((Tpp < 160)|(Tpp > 511)) return 11; // не правильная метка
        dataFile.seekSet(0); // на начало файла
    
        CRB = 0;   // Сбрасываем индексы.
        bBit = 15;
        CalcTb();  // Вычисляем значение задержки на начало байта Tb
    
          // Начинаем наполнять буфер
        for (CWB=0; CWB<=250; CWB++){ // первые 250 байт
          BUFF[CWB] = dataFile.read();
        }
        CWB = 251;             // продолжаем с 251-го байта буфера
    
        Timer1.setPeriod(Tpp); // Выставляем период таймера
        Timer1.start();        // Запускаем таймер............
    
        for (unsigned int i=251; i<=Nbt-4; i++){ // всё остальное, кроме последних 3 байт
          ToBUFF(dataFile.read());
    
          lcd.setCursor(12, 0);  // выводим на экран кол-во оставшихся "псевдоблоков" по 256 байт
          lcd.print((Nbt-i)>>8);
          lcd.print(' ');
    
          if (getPressedButton()!=BT_none) { // кнопка нажата?
            Timer1.stop();          // Останавливаем таймер
            dataFile.close();       // закрываем файл
            return 1;               // выход из ПП с ошибкой 1.
          }
          if (CRB_temp > CWB) {     // проверка -- не обогнало ли чтение запись?
            Timer1.stop();          // Останавливаем таймер
            dataFile.close();       // закрываем файл
            return 2;               // выход из ПП с ошибкой 2.
          }
        }
        dataFile.close(); // закрываем файл
      }
      else return 3; // нет файла 
      WaitBuffer();  // ожидаем окончания вывода
      return 0;
    }
    
    int PlayROM(char FName[], int pnt) // функция вывода файла ROM
    {
      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);                    // Считываем дату файла и сохраняем в заголовке
        SB[8] = FAT_DAY(d.lastWriteDate)/10 + '0';         // перевод из целого в символ -- день
        SB[9] = FAT_DAY(d.lastWriteDate)%10 + '0';
        SB[10] = FAT_MONTH(d.lastWriteDate)/10 + '0';      // месяц
        SB[11] = FAT_MONTH(d.lastWriteDate)%10 + '0';
        SB[12] = (FAT_YEAR(d.lastWriteDate)%100)/10 + '0'; // последние две цифры года
        SB[13] = FAT_YEAR(d.lastWriteDate)%10 + '0';
    
        CRB = 0;                        // Сбрасываем индексы.
        CWB = 0;
        bBit = 15;
        CalcTb(); // Вычисляем значение задержки на начало байта Tb
    
          // Начинаем наполнять буфер
        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 return 3; // нет файла 
      WaitBuffer();  // ожидаем окончания вывода
      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); // Выставляем увеличенный период таймера (начало байта)
      }
    }
    
    ISR(ANALOG_COMP_vect) // ПП обработки прерывания компаратора -- определяет и заносит полученные биты в буфер
    {
      boolean Plong = false;         // короткий полупериод по умолчанию
      PPeriod = micros() - iMicros_old;
      iMicros_old += PPeriod;
      if (Pik) { // началось...
        Plong = (PPeriod > PPeriod_sred); // = false, если тек. полупериод короткий
        if (!Plong) { // если полупериод короткий, пересчитываем среднее значение
          if (CWB <= 255) PPeriod_sred = (PPeriod_sred*CWB + PPeriod*3/2 + 15)/(CWB+1);
        }
      }
      else { // был перерыв в сигнале
        PPeriod_sred = 1024; // берём заведомо большое среднее значение
        CWB = 0;             // начинаем запись с начала
        bBit = 15;
        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; // есть сигнал!
    }
    [свернуть]

    Что изменилось:
    - Так как компаратор в ардуине умеет работать только с выводами D6, D7 и А0-А7 (первые два заняты экраном и А0 -- кнопками), сигнал с Вектора теперь принимается на входе А1.
    - Улучшил алгоритм расчёта значения средней задержки полупериода сигнала -- с компаратором эти значения сильно "скакали". И теперь для расчёта используются только данные с первых 256 байт.

    В работе компаратор использует внутреннее опорное напряжение 1,1В, а напряжение на выходе Вектора, насколько я помню, не превышает его по амплитуде, поэтому входная схема всё-таки потребуется. Это может быть транзистор, как в схеме zx-tapper, или может даже будет достаточно схемки Trol73 -- пока сказать не могу, надо пробовать... По идее, идеальным будет вариант, когда центр сигнала будет находиться на уровне опорного напряжения 1,1В, ведь для записи Векторовского сигнала нужно только чётко отмерять временные промежутки, когда меняется фаза. Можно пойти и другим путём: задействовать внешнее опорное напряжение на выводе AREF и сделать его равным нулю (заземлить вывод). Какой вариант лучше -- покажет практика.

    Архив в полном комплекте: RW-player_7.7z

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

    По умолчанию

    Для общего понимания, нарисовал схему подключений "магнитофона для Вектора" на ардуино:

    Схема




    Схема.7z в более высоком качестве
    [свернуть]

    Но хочу отметить, что проще и удобнее всё то же самое собрать на трёх платках: Arduino UNO r3 (на Atmega328p) + "Data Logger Shield v1.0" + "LCD Keypad Shield". В этом случае нужно будет только впаять в платку сопротивления и конденсаторы на вход и выход.

    Пробовал записывать сигнал с компа, прямо со звуковой карты -- в принципе, для входящего сигнала достаточно будет сопротивлений (как на картинке), но для хорошей записи надо поиграться громкостью и регулировкой сопротивления R8. Оптимально, получается так: подключаешь вход к ПК (без сигнала), включаешь ардуину и выставляешь R8 так, чтобы на выводе А1 было напряжение 0,9-1,1В. Точно на 1,1В лучше не выставлять, иначе компаратор будет срабатывать на шумы. Хинт для ностальгирующих: в этот момент можно представить, что крутишь головку магнитофона. Ну и дальше уже можно включать запись.

    Ближайшее время хочу всё-таки проверить работу на реальном Векторе...
    Миниатюры Миниатюры Нажмите на изображение для увеличения. 

Название:	RW-player8_bb4~.jpg 
Просмотров:	457 
Размер:	88.9 Кб 
ID:	64975  

  8. Этот пользователь поблагодарил Improver за это полезное сообщение:

    Alex_NEMO (04.05.2022)

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

    По умолчанию

    Почти два месяца я гонял по-всякому подпрограмму записи с Вектора, но, к сожалению, пока что стабильной работы плеера нет... Буду продолжать, но хочу поделиться промежуточными результатами, чтобы не забылось. Вот последняя версия скетча:

    RW-player 9

    Для работы требуются библиотеки TimerOne, SdFat, RTClib, а также стандартные LiquidCrystal и Wire.
    Код:
    #define vers "version 9.38"
    /*
     Вариант на "Data Logger Shield" и "LCD Keypad Shield"
     (на первом есть RTC -- используем для показа времени)
     
     Выход - D3
     Вход  - A1
     
     "Data Logger Shield V1.0":
     SD-картридер подключен к выводам ардуино:
     * MOSI - D11
     * MISO - D12
     * CLK  - D13
     * CS   - D10
     
     Часы реального времени (RTC DS1307) подключены:
     * SDA  - A4
     * SDL  - A5
     
     "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 <LiquidCrystal.h>
    #include <SdFat.h>
    #include <TimerOne.h>
    #include <Wire.h>
    #include "RTClib.h"
    
    //инициализация часов
    RTC_DS1307 RTC;
    // инициализация экрана
    LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
    
    SdFat sd;
    SdFile dataFile;
    
    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; // индекс чтения из буфера
    unsigned int CRB_temp = 0;     // временная переменная для CRB
    volatile byte bBit = 15;       // индекс читаемого полубита
    volatile unsigned int CWB = 0; // индекс записи в буфер
    volatile byte A = 0;           // записываемый байт
    volatile byte B = 0;           // записываемый бит
    //int i0 = 0;                    // счётчик циклов без сигнала
    
    //volatile unsigned long PPeriod = 0;     // текущее значение полупериода
    volatile unsigned long PPeriod_sred[2]; // среднее значение границы длинны нечётного полупериода
    volatile unsigned long iMicros_old = 0; // предыдущее значение микросекунд
    volatile boolean Pik = false;           // есть изменение сигнала
    volatile byte PP = 1;                   // =0 -- чётный, =1 -- нечётный полупериод
    
    // Заголовок блока
    byte SB[27] = { 
      0x4E, 0x4F, 0x44, 0x49, 0x53, 0x43, 0x30, 0x30, // NODISK00
      0x32, 0x39, 0x30, 0x33, 0x31, 0x38,             // дата: 290318
      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 unsigned int Tpp = 200; // Начальная длительность задержки сигнала в микросекундах (один полупериод)
    volatile int Tb  = 64;  // Дополнительная задержка сигнала на начало байта в микросекундах
    
    const int p = 3; // номер пина, на который будет вывод сигнала
    #define InputPIN A1 // номер пина, на котором будет получение сигнала
    
    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(InputPIN, INPUT); // объявляем пин как вход (сигнал)
      pinMode(p, OUTPUT);       // объявляем пин как выход (сигнал)
      digitalWrite(p, LOW);     // выход =0
      pinMode(10, OUTPUT);      // CS для SD-картридера и оно же для яркости экрана
      digitalWrite(10, HIGH);   // включаем подсветку экрана
      pinMode(A0, INPUT);       // объявляем пин с кнопками как вход
    
      lcd.begin(16, 2);    // объявляем размер экрана 16 символов и 2 строки
    
      Timer1.initialize(Tpp);               // инициализировать timer1, и установить период Tpp мкс.
      Timer1.attachInterrupt(SendHalfBit);  // прикрепить SendHalfBit(), как обработчик прерывания по переполнению таймера
      Timer1.stop();                        // остановить таймер
    
      printtext("BEKTOP-MF",0);             // вывод названия
      printtext(vers,1);                    // и версии
      delay(2000);                          // ждем 2 с для солидности :-)
    
      Wire.begin();                         // запуск часов
      RTC.begin();
      if (! RTC.isrunning()) {
        printtext("RTC is NOT run!",1);     // часы стоят
        RTC.adjust(DateTime(__DATE__, __TIME__)); // устанавливаем дату/время на момент копмиляции программы
        delay(2000);                        // ждем 2 с
        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);
        delay(3000);       // ждем 3 с
      }
      printtext("SD-card is OK.",1);
      sd.chdir();            // устанавливаем корневую директорию SD
    
      DIDR1 = (1<<AIN1D)|(1<<AIN0D); // Выключить цифровые входы контактов Digital 6 и 7
    }
    
    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')) {
                      RCrom = PlayVKT(sfileName); // вызов ПП для формата 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-файл, либо не найдена метка скорости в 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)) { // открываем файл на запись
            printtext("Prepare...",0); // готовимся...
            dataFile.write(0xFF);      // пишем байт для создания файла
            dataFile.seekSet(0);       // переход на начало файла
            CRB = 0;                   // Сбрасываем индекс
            printtext("Waiting...",0); // ожидание сигнала
    
            ADCSRA = 0;          // выключить АЦП
            ADCSRB |= (1<<ACME); // включить мультиплексор
            ADMUX = (1<<MUX0)    // включить вход на A1
              |(1<<REFS1)|(1<<REFS0); // опорное напряжение АЦП (?)
    
            ACSR  = (1<<ACIE)    // Разрешить прерывание аналогового компаратора
              |(1<<ACBG);        // внутреннее опорное напряжение
            delay(1);            // ждём немного
            Pik = false;         // сбрасываем первое срабатывание
            while (!Pik) { // Ждём сигнал
              delay(10);   // задержка...
              if (digitalRead(A0)!=HIGH) { // кнопка нажата?
                break;                     // прерывание режима записи при нажатии кнопки
              }
            }
            printtext("Recording...",0);  // сигнал зафиксирован, записываем
            printtext("Bytes:",1);
            delay(100);   // задержка для накопления инфы...
    
            //====================================
            do { // пока есть сигнал
              noInterrupts();       // запрет прерываний
              CRB_temp = CWB;       // сохраняем CWB во временную переменную
              interrupts();         // разрешение прерываний
              if (CRB_temp <= CRB) {// если индекс записи меньше или равен индексу чтения
                delay(200);             // задержка на ввод данных: макс.скорость 160 * 16 полубит * ~78 байт или ~31 байт на минимальной
                noInterrupts();         // запрет прерываний
                CRB_temp = CWB;         // сохраняем CWB во временную переменную
                interrupts();           // разрешение прерываний
              }
              dataFile.write(BUFF[lowByte(CRB)]); // пишем байт из буфера
              CRB++;
              if (CRB%10 == 0) {
                lcd.setCursor(7, 1);           // устанавливаем курсор в позицию 8 в строке 1
                lcd.print(CRB);                // количество сохранённых байт
              }
            }
            while (CRB_temp >= CRB); // если запись на SD обогнала чтение, значит сигнала нет, выходим из цикла
            //=====================================
            ACSR = 0;     // отключаем обработку прерывания компаратора
            ADCSRA = 135; // включить АЦП, установить делитель =128
    
            Tpp = ((PPeriod_sred[0]+PPeriod_sred[1])/384); // расчитываем полупериод для последующего вывода... (((S1+S2)/2)/128)*2/3
            if ((Tpp%8) < 4) {                             // если остаток от деления на 8 меньше 4
              Tpp = (Tpp/8)*8;                             // округляем в меньшую сторону
            }
            else {
              Tpp = (Tpp/8+1)*8;                         // округляем в большую сторону
            }
    
            if (CRB > 25) { // проверка -- если было ложное срабатывание, то ничего не пишем.
              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());
              }
              printtext("Done.  Speed:",0);
              lcd.setCursor(7, 1);             // устанавливаем курсор в позицию 8 в строке 1
              lcd.print(CRB);                  // количество сохранённых байт
              lcd.setCursor(13, 0);            // устанавливаем курсор в позицию 13 в строке 0
              lcd.print(Tpp);                  // расчётное значение длинны полупериода для вывода
              lcd.setCursor(0, 0);             // устанавливаем курсор в позицию 0 в строке 0
              if ((PPeriod_sred[1]>(PPeriod_sred[0]+3840))|(PPeriod_sred[0]>(PPeriod_sred[1]+3840))) { // если средние значения слишьком сильно отличаются
                lcd.print("BadSig");           // пишем сообщение о плохом сигнале
    
                // отладочный вывод инфы о полупериодах
                printtext("",1);
                lcd.setCursor(0, 1);
                lcd.print(PPeriod_sred[0]/192);
                lcd.setCursor(5, 1);
                lcd.print(PPeriod_sred[1]/192);
                lcd.setCursor(10, 1);
                lcd.print(CRB);                // количество сохранённых байт            
    
                delay(3000);                   // ждём 3 сек.
              }
              else if ((Tpp<160)|(Tpp>512)) {  // если скорость вне допустимых пределов
                lcd.print("Error?");           // пишем сообщение о возможной ошибке
                delay(3000);                   // ждём 3 сек.
              }
            }
            else {
              printtext("Canceled.",0);
              if (!dataFile.remove()) {        // удаляем недозаписанный файл
                delay(1000);
                printtext("Error del.file",0);
              }
            }
            dataFile.close();                  // закрываем файл       
            Tpp = 200;                         // устанавливаем полупериод на "стандартное" значение
          }
          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.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;
          }
        }
      } 
    }
    
    void CalcTb()  // Вычисление значения задержки на начало байта Tb
    {
      if (Tpp <= 176) {     // для полупериода меньше или равном 176
        Tb = 88;
      }
      else {
        if (Tpp <= 240) {   // для полупериода от 184 до 240
          Tb = 264 - Tpp;
        }
        else {
          if (Tpp <= 264) { // для полупериода от 248 до 264
            Tb = 16;
          }
          else Tb = 0;      // для полупериода больше 264
        }
      }
    }
    
    void WaitBuffer() // ожидание очистки буфера при воспроизведении
    {
      do{                     // Ждём опустошения буфера
        delay(Tpp/64);        // задержка на вывод 1 байта (~Tpp*16/1000)
        noInterrupts();       // запрет прерываний
        CRB_temp = CRB;       // сохраняем CRB во временную переменную
        interrupts();         // разрешение прерываний
      }
      while (CRB_temp < CWB);
      Timer1.stop();  // Останавливаем таймер............
    }
    
    int PlayVKT(char FName[]) // функция вывода файла VKT
    {
      delay(1000);            // ждем 1 с
      if (dataFile.open(FName,O_READ)) { // открываем файл. Открылся?
        dataFile.seekSet(Nbt-3); // на позицию -3 байт от конца файла
        if (dataFile.read()!=0xFF) return 11; // метка не найдена
        Tpp = dataFile.read()*256 + dataFile.read(); // считываем Tpp
        if ((Tpp < 160)|(Tpp > 511)) return 11; // не правильная метка
        dataFile.seekSet(0); // на начало файла
    
        CRB = 0;   // Сбрасываем индексы.
        bBit = 15;
        CalcTb();  // Вычисляем значение задержки на начало байта Tb
    
          // Начинаем наполнять буфер
        for (CWB=0; CWB<=250; CWB++){ // первые 250 байт
          BUFF[CWB] = dataFile.read();
        }
        CWB = 251;             // продолжаем с 251-го байта буфера
    
        Timer1.setPeriod(Tpp); // Выставляем период таймера
        Timer1.start();        // Запускаем таймер............
    
        for (unsigned int i=251; i<=Nbt-4; i++){ // всё остальное, кроме последних 3 байт
          ToBUFF(dataFile.read());
    
          lcd.setCursor(12, 0);  // выводим на экран кол-во оставшихся "псевдоблоков" по 256 байт
          lcd.print((Nbt-i)>>8);
          lcd.print(' ');
    
          if (getPressedButton()!=BT_none) { // кнопка нажата?
            Timer1.stop();          // Останавливаем таймер
            dataFile.close();       // закрываем файл
            return 1;               // выход из ПП с ошибкой 1.
          }
          if (CRB_temp > CWB) {     // проверка -- не обогнало ли чтение запись?
            Timer1.stop();          // Останавливаем таймер
            dataFile.close();       // закрываем файл
            return 2;               // выход из ПП с ошибкой 2.
          }
        }
        dataFile.close(); // закрываем файл
      }
      else return 3; // нет файла 
      WaitBuffer();  // ожидаем окончания вывода
      return 0;
    }
    
    int PlayROM(char FName[], int pnt) // функция вывода файла ROM
    {
      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);                    // Считываем дату файла и сохраняем в заголовке
        SB[8] = FAT_DAY(d.lastWriteDate)/10 + '0';         // перевод из целого в символ -- день
        SB[9] = FAT_DAY(d.lastWriteDate)%10 + '0';
        SB[10] = FAT_MONTH(d.lastWriteDate)/10 + '0';      // месяц
        SB[11] = FAT_MONTH(d.lastWriteDate)%10 + '0';
        SB[12] = (FAT_YEAR(d.lastWriteDate)%100)/10 + '0'; // последние две цифры года
        SB[13] = FAT_YEAR(d.lastWriteDate)%10 + '0';
    
        CRB = 0;                        // Сбрасываем индексы.
        CWB = 0;
        bBit = 15;
        CalcTb(); // Вычисляем значение задержки на начало байта Tb
    
          // Начинаем наполнять буфер
        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 return 3; // нет файла 
      WaitBuffer();  // ожидаем окончания вывода
      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); // Выставляем увеличенный период таймера (начало байта)
      }
    }
    
    ISR(ANALOG_COMP_vect) // ПП обработки прерывания компаратора -- определяет и заносит полученные биты в буфер
    {
      unsigned long iMicros = micros();
      unsigned long PPeriod = iMicros - iMicros_old;
      iMicros_old = iMicros;
      boolean Plong = false;  // короткий полупериод по умолчанию
      if (PPeriod < 1024) {   // началось...
        if (bBit < 16) {      // если это не последний полубит
          Plong = (PPeriod > (PPeriod_sred[PP]>>7)); // sred/128, =false, если тек. полупериод короткий
          if (CWB <= 127) {   // расчёт среднего значения, если меньше 256 байт считано
            if (!Plong) {     // если полупериод короткий
              PPeriod_sred[PP] = (PPeriod_sred[PP]*CWB + PPeriod*192)/(CWB+1); // "среднее значение" = 1,5*128 от короткого полупериода
            }
            //      else {          // если полупериод длинный, берём PPeriod/2
            //        PPeriod_sred = (PPeriod_sred*CWB + PPeriod*3/4)/(CWB+1);
            //      }
          }
        }
        else { // если последний полубит, вводим корректировку на задержку между байтами
          Plong = (PPeriod > (PPeriod_sred[PP]*25/2432)); // с учётом увеличения периода между байтами, граница будет =([1,5]*25/19)/128 =1,97*Tpp
          //Plong = (PPeriod > ((PPeriod_sred[PP]>>7) + 96)); // с учётом увеличения периода между байтами на 96 мкс
        }
        PP ^= 1; // инвертируем бит признака чётности полупериода
    
        // расшифровка данных
        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++;          // счётчик байтов +1
          A = 0;          // обнуляем буфер для битов
          bBit += 15;     // = +16 -1
        }
    
      }
      else { // был перерыв в сигнале
        PPeriod_sred[0] = 98304; // берём заведомо большое среднее значение (192*512)
        PPeriod_sred[1] = 98304; // берём заведомо большое среднее значение
        CWB = 0;                 // начинаем запись с начала
        bBit = 15;
        A = 0;
        B = 0;
        PP = 1;                 // = нечётный полупериод
        Pik = true;             // есть сигнал!
      }
    }
    [свернуть]

    Что сделано:
    1. Как оказалось, расчёт среднего времени не очень удачен, и хотя в экзеле показывал хорошие значения, но из-за округления результатов целочисленного деления в ардуине всё было плохо... В связи с этим сохраняемое "среднее значение" полупериода пришлось увеличить на два порядка (точнее умножить на 128), в таком виде он работает лучше.
    2. Опять же, для улучшения результатов, решил сохранять средние значения двух полупериодов отдельно -- верхней и нижней части сигнала. Эти значения также можно использовать для оценки настройки компаратора, если они близки или даже равны, то всё настроено хорошо.
    3. Теперь при записи показывается количество считанных байт, это позволяет контролировать процесс и оценить качество записи.
    4. Возможно у меня SD-карта уже подглючивает, но последнее время начало первой записи в файл подвисает секунд на 5-10, пришлось сделать для этого тестовое сохранение байта до старта записи.

    Что ещё вызывает проблемы:
    - Межбайтовые промежутки... Это просто что-то -- при воспроизведении с Вектора они могут достигать двойной длинны, причём "тапир" сохранённую wav-ку проглатывает со 100% результатом, а вот ардуина капризничает... Сделал корректировку на начало байта, но она не всегда спасает. Вот один из лучших вариантов записанного файла: Primer.7z -- input004.vkt -- что было записано последней версией скетча, input0xx.vkt -- что должно быть. Сбой идёт с 340-го байта. А это wav-ка, записанная непосредственно с Вектора, "для опытов": Vektor_WAV.7z.

    Надо думать, как это можно победить в ардуине. Я не исключаю такой вариант, что внутренний компаратор вносит свои погрешности, возможно надо попробовать сделать на внешнем...

    И, в конце, полный комплект последней версии скетча: RW-player_9.7z

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

    По умолчанию

    Алгоритмы Тапира не особо мудреные, их можно подсмотреть. cas.js разбивает сигнал на пары интервалов между инверсиями LL/LS/SL/SS, а scanner.js из парных символов делает биты.

    Кстати, если я правильно понимаю, эта часть ROM-плеера никак не привязана к железу. Такие вещи проще отлаживать на писишке. Сделать тестовый проект, который легко запустить, увидеть результат, может быть в отладчике пощупать. А когда алгоритм уже работает железно, вот тогда и засовывать в железо.
    Больше игр нет

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

    По умолчанию

    Цитата Сообщение от svofski Посмотреть сообщение
    Алгоритмы Тапира не особо мудреные, их можно подсмотреть. cas.js разбивает сигнал на пары интервалов между инверсиями LL/LS/SL/SS, а scanner.js из парных символов делает биты.
    Да, это несколько другой подход к расшифровке сигнала, хотя можно подумать и в этом направлении. Трудность с ардуиной в том, что, из-за недостатка памяти, всё делать надо за один проход и сразу по факту получения сигнала. А, с другой стороны, Вектор с этой задачей справляется легко, значит есть шансы. :-)

    Кстати, если я правильно понимаю, эта часть ROM-плеера никак не привязана к железу. Такие вещи проще отлаживать на писишке. Сделать тестовый проект, который легко запустить, увидеть результат, может быть в отладчике пощупать. А когда алгоритм уже работает железно, вот тогда и засовывать в железо.
    Да, можно попробовать и так. Для приближения к реальности алгоритма можно даже ардуиной записать подряд все временные промежутки реального Векторовского сигнала, а потом отрабатывать на полученных данных... Объём, конечно, возрастёт в ~32 раза (до 16 промежутков на байт данных Х 2 байта на запись, т.к. промежутки часто больше 256 мкс.), но ардуина должна справится с таким потоком. Пойду, подумаю...

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

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

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

Эту тему просматривают: 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

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

Ваши права

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