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

User Tag List

Страница 16 из 17 ПерваяПервая ... 121314151617 ПоследняяПоследняя
Показано с 151 по 160 из 163

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

  1. #151
    Guru Аватар для svofski
    Регистрация
    20.06.2007
    Адрес
    С.-Петербург
    Сообщений
    4,105
    Спасибо Благодарностей отдано 
    772
    Спасибо Благодарностей получено 
    643
    Поблагодарили
    398 сообщений
    Mentioned
    22 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от Improver Посмотреть сообщение
    Трудность с ардуиной в том, что, из-за недостатка памяти, всё делать надо за один проход и сразу по факту получения сигнала.
    Для этого алгоритма это не трудность. Мудрености там минимум и больше одного символа за раз ему не надо. Предварительный проход там делается для оценки временных параметров. В случае вполне надежного источника для этого достаточно начального писка. Принципиального отличия от того, как читает Вектор, по-моему нет. Просто немного по разному смотрим на то, что происходит с сигналом.
    Больше игр нет

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

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

    По умолчанию

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

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

    По умолчанию

    Очередная версия скетча:

    RW-player 9.57

    Для работы требуются библиотеки TimerOne, SdFat, RTClib, а также стандартные LiquidCrystal и Wire.
    Код:
    #define vers "version 9.57"
    /*
     Вариант на "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(200);           // задержка для накопления инфы...
            noInterrupts();       // запрет прерываний
            CRB_temp = CWB;       // сохраняем CWB во временную переменную
            interrupts();         // разрешение прерываний
    
            //====================================
            do { // пока есть данные в буфере
              dataFile.write(BUFF[lowByte(CRB)]); // пишем байт из буфера
              if (CRB%256 == 0) {    // каждый 256-й байт (1 псевдоблок)
                lcd.setCursor(7, 1); // устанавливаем курсор в позицию 8 в строке 1
                lcd.print(CRB);      // количество сохранённых байт
              }
              CRB++;
              if (CRB_temp <= CRB) {// если индекс записи меньше или равен индексу чтения
                delay(300);     // задержка на ввод данных: макс.скорость 160 * 16 полубит * ~117 байт или ~46 байт на минимальной
                noInterrupts(); // запрет прерываний
                CRB_temp = CWB; // сохраняем CWB во временную переменную
                interrupts();   // разрешение прерываний
              }
            }
            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;
      if (PPeriod < 65000) {  // началось...
        if (bBit < 16) {      // если это не последний полубит
          if (PPeriod <= (PPeriod_sred[PP]>>7)) { // sred/128, если тек. полупериод короткий
            if (CWB <= 255) {   // расчёт среднего значения, если меньше 256 байт считано
              PPeriod_sred[PP] = (PPeriod_sred[PP]*CWB + PPeriod*192)/(CWB+1); // "среднее значение" = 1,5*128 от короткого полупериода
            }
            if (bBit & 1) { // нечётный полубит
              A = (A<<1)+B; // заносим бит
            }               // нечётный -- пропускаем
          }
          else { // получен длинный полупериод
            B ^= 1;         // инвертируем бит
            A = (A<<1)+B;   // заносим бит
            bBit--;         // уменьшаем счётчик полубитов
          }
        }
        else { // если последний полубит, вводим корректировку на задержку между байтами
          // граница будет =([1,5*Tpp*128]*25/19)/128 =~[1,5*Tpp*128]/97 =~1,98*Tpp
          if (PPeriod > (PPeriod_sred[PP]/97)) { // если тек. полупериод длинный
            B ^= 1;         // инвертируем бит
            A = (A<<1)+B;   // заносим бит
            bBit--;         // уменьшаем счётчик полубитов
          }
        }
        // корректировка счётчиков
        PP ^= 1; // инвертируем бит признака чётности полупериода
        if (bBit > 1) {
          bBit--;                  // счётчик полубитов -1
        }
        else {
          BUFF[lowByte(CWB)] = A;  // заносим байт в буфер
          BUFF[lowByte(++CWB)] = 0;// счётчик байтов +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;             // есть сигнал!
      }
    }
    [свернуть]

    В общем, оптимизация алгоритмов захвата сигнала, сокращение количества вызовов секций noInterrupts/Interrupts, а также увеличение границы отличия "короткого" от "длинного" полупериода до значения ~1,98*Трр дало некоторый положительный результат -- с ПК данные считываются более-менее стабильно на разных скоростях. Теперь опять дело за проверкой на реальном железе.

    З.Ы. Так получилось, что индексы полубитов в байте ведутся в таком порядке: 15, 14, 13, ... 2, 1, 16. Вроде бы не логично, но переправлять не стал...

    Последняя версия скетча с библиотеками в архиве: RW-player_9.57.7z

  5. #154
    Master Аватар для Improver
    Регистрация
    06.02.2018
    Адрес
    г. Волгоград
    Сообщений
    970
    Спасибо Благодарностей отдано 
    417
    Спасибо Благодарностей получено 
    392
    Поблагодарили
    217 сообщений
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Готова очередная версия "магнитофона", в новой версии добавлено воспроизведение форматов:

    - CAS -- универсальный формат для файлов Бейсика, Монитора-отладчика и, может быть, других программ. Файлы на SD-карте должны иметь расширение "cas".

    - Формат Монитора-отладчика. В связи с тем, что для этого формата нужно обязательно задать начальный и конечный адреса загрузки, решил использовать такой же метод, как и у многих файлов этого формата в библиотеке Базис -- начальный адрес загрузки определяется по расширению файла, конечный рассчитывается по размеру файла. Это выглядит так: "<filename>.mXX", где ХХ -- это старший разряд начального адреса загрузки в шестнадцатиричном виде, например file1.m01 -- загрузка с адреса 0100h, file2.mA0 -- загрузка с адреса A000h и т.п.
    Примечание: если загрузка должна начинаться с адреса не кратного 256 (например, 01F0h), то это можно будет сделать только конвертацией в формат CAS.

    - Формат записи командами Бейсика CSAVE/CLOAD. В этом формате воспроизводятся файлы с расширением "bas".

    - Формат записи командами Бейсика BSAVE/BLOAD. Тут тоже требуется задание начального/конечного адресов, не стал изобретать велосипед и сделал всё аналогично формату монитора, только расширение должно быть вида "<filename>.bXX", где ХХ -- это старший разряд начального адреса загрузки в шестнадцатиричном виде.

    - Формат записи Ассемблера-Редактора. Файлы должны быть с расширением "asm", их содержимое -- текст с концами строк 0Dh.

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

    Итак, сам скетч:

    RW-рlayer 10.5

    Для работы требуются библиотеки TimerOne, SdFat, RTClib, а также стандартные LiquidCrystal и Wire.
    Код:
    #define vers "version 10.5"
    /*
     Вариант на "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;              // размер файла, байт
    unsigned int StartAddr;        // стартовый адрес для MON
    
    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;           // записываемый бит
    
    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
      0x30, 0x39, 0x30, 0x37, 0x31, 0x38,             // дата: 090718
      0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // имя программы
      0x52, 0x4F, 0x4D, 0x00, 0x00 };                 // расширение "ROM" и нули
    
    // строка часов
    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]=='.'){  // ищем точку в имени файла
                  switch((sfileName[i+1]|0x20)) // следующий после точки символ
                  {
                  case 'r': // первый символ == 'r'|'R'
                    if ( (((sfileName[i+2]|0x20)=='o')|(sfileName[i+2]=='0'))&((sfileName[i+3]|0x20)=='m') ) { // второй символ == 'o'|'O'|'0' и третий == 'm'|'M'
                      if ((sfileName[i+2]|0x20)!='0') { // проверка на вывод нулевого блока по расширению файла
                        BLs = 0x01; // с первого блока
                      }
                      else { 
                        BLs = 0x00; // с нулевого блока
                      }
                      RCrom = PlayROM(sfileName, i);// Передаём короткое имя файла и позицию точки в качестве параметров
                    }
                    break;
                  case 'v': // первый символ == 'v'|'V'
                    if ( ((sfileName[i+2]|0x20)=='k')&((sfileName[i+3]|0x20)=='t') ) { // второй символ == 'k'|'K' и третий == 't'|'T'
                      RCrom = PlayVKT(sfileName); // вызов ПП для формата VKT
                    }
                    break;
                  case 'c': // первый символ == 'c'|'C'
                    if ( ((sfileName[i+2]|0x20)=='a')&((sfileName[i+3]|0x20)=='s') ) { // второй символ == 'a'|'A' и третий == 's'|'S'
                      RCrom = PlayAll(sfileName, i, 0); // вызов ПП для формата CAS
                    }
                    break;
                  case 'b': // первый символ == 'b'|'B'
                    if ( ((sfileName[i+2]|0x20)=='a')&((sfileName[i+3]|0x20)=='s') ) { // второй символ == 'a'|'A' и третий == 's'|'S'
                      RCrom = PlayAll(sfileName, i, 1); // вызов ПП для формата BAS(C)
                      break; // выход из case, если это был BAS-файл
                    }
                  case 'm': // первый символ == 'm'|'M' (или 'b'|'B')
                    StartAddr = 0x100;
                    if ((sfileName[i+2]>='0')&(sfileName[i+2]<='9')) { // преобразуем второй символ расширения в HEX
                      StartAddr = (sfileName[i+2] - '0')*16;
                    }
                    else if (((sfileName[i+2]|0x20)>='a')&((sfileName[i+2]|0x20)<='f')) {
                      StartAddr = ((sfileName[i+2]|0x20) - 87)*16;
                    }
                    if ((sfileName[i+3]>='0')&(sfileName[i+3]<='9')) { // преобразуем третий символ расширения в HEX
                      StartAddr += sfileName[i+3] - '0';
                    }
                    else if (((sfileName[i+3]|0x20)>='a')&((sfileName[i+3]|0x20)<='f')) {
                      StartAddr += (sfileName[i+3]|0x20) - 87;
                    }
                    if (StartAddr < 0x100) { // если стартовый адрес из расширения определён верно
                      RCrom = PlayAll(sfileName, i, 2); // вызов ПП для формата MON или Бейсика BLOAD
                    }
                    break;
                  case 'a': // первый символ == 'a'|'A'
                    if ( ((sfileName[i+2]|0x20)=='s')&((sfileName[i+3]|0x20)=='m') ) { // второй символ == 's'|'S' и третий == 'm'|'M'
                      RCrom = PlayAll(sfileName, i, 3); // вызов ПП для формата ASM
                    }
                    break;
                  }  
                  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(200);           // задержка для накопления инфы...
            noInterrupts();       // запрет прерываний
            CRB_temp = CWB;       // сохраняем CWB во временную переменную
            interrupts();         // разрешение прерываний
    
            //====================================
            do { // пока есть данные в буфере
              dataFile.write(BUFF[lowByte(CRB)]); // пишем байт из буфера
              if (CRB%256 == 0) {    // каждый 256-й байт (1 псевдоблок)
                lcd.setCursor(7, 1); // устанавливаем курсор в позицию 8 в строке 1
                lcd.print(CRB);      // количество сохранённых байт
              }
              CRB++;
              if (CRB_temp <= CRB) {// если индекс записи меньше или равен индексу чтения
                delay(300);     // задержка на ввод данных: макс.скорость 160 * 16 полубит * ~117 байт или ~46 байт на минимальной
                noInterrupts(); // запрет прерываний
                CRB_temp = CWB; // сохраняем CWB во временную переменную
                interrupts();   // разрешение прерываний
              }
            }
            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); // на начало файла
        Nbt = Nbt-4; // уменьшаем размер данных на метку
    
        CRB = 0;   // Сбрасываем индексы.
        bBit = 15;
        CalcTb();  // Вычисляем значение задержки на начало байта Tb
    
          // Начинаем наполнять буфер
        for (CWB=0; CWB<=250; CWB++){ // первые 250 байт
          BUFF[CWB] = dataFile.read();
        }
        CWB = 251;             // продолжаем с 251-го байта буфера
    
        lcd.setCursor(12, 0);  // выводим на экран общее кол-во "псевдоблоков" по 256 байт
        lcd.print(Nbt>>8);     // всего данных / 256
    
        Timer1.setPeriod(Tpp); // Выставляем период таймера
        Timer1.start();        // Запускаем таймер............
    
        return PlayFile(251, 0);// выводим данные из файла, возвращаем код возврата из ПП
      }
      return 3; // ошибка / нет файла 
    }
    
    int PlayAll(char FName[], int pnt, byte FType) // функция вывода остальных типов файлов
    // входные данные: имя файла, длина имени, тип файла: 0=CAS; 1=BAS(C); 2=MON и BAS(B); 3=ASM
    {
      int i;
      byte SB;
      delay(1000);            // ждем 1 с
      if (dataFile.open(FName,O_READ)) { // открываем файл. Открылся?
        CRB = 0;   // Сбрасываем индексы.
        bBit = 15;
        CalcTb();  // Вычисляем значение задержки на начало байта Tb
    
        // Начинаем наполнять буфер
        for (CWB=0; CWB<=255; CWB++){ // первые 255 байт
          if ((FType!=3)|((CWB/64==1)|(CWB/64==3))) { // если не ASM и это 64-127 или 192-255 байты
            BUFF[CWB] = 0;
          }
          else {                                      // иначе, если ASM и это 0-63 или 128-191 байты
            BUFF[CWB] = 0x55;
          }
        }
        CWB = 256;             // продолжаем с 256-го байта буфера
    
        // Tpp = 376;             // стандартная скорость вывода для MON/BAS/ASM
        // Tb = 0;
        Timer1.setPeriod(Tpp); // Выставляем период таймера
        Timer1.start();        // Запускаем таймер............
    
        delay(10);             // немного ждём, чтобы начался вывод первых байт
        ToBUFF(0xE6);          // синхробайт
        switch (FType)
        {
        case 0: // это CAS
          return PlayFile(0, 0);// выводим данные из файла, возвращаем код возврата из ПП
        case 1: // это BAS(C)
          for (i=0;i<=3;i++) ToBUFF(0xD3);    // 4x 0xD3
          break;
        case 2: // это MON или BAS(B)
          for (i=0;i<=3;i++) ToBUFF(0xD2);    // 4x 0xD2
          break;
        case 3: // это ASM
          for (i=0;i<=3;i++) ToBUFF(0xE6);    // 4x 0xE6
        }
    
        for (i=0;i<pnt;i++) {  // вывод имени файла без расширения
          if ((FName[i]>=0x61) && (FName[i]<=0x7A)) {
            SB = FName[i] - 0x20;    // на заглавные буквы
          }
          else if (FName[i]!=0x7E) { // не тильда
            SB = FName[i];
          }
          else {
            SB = '_';                // меняем тильду на подчёркивание, иначе это будет русская "Ч"
          }
          ToBUFF(SB);  // заносим в буфер очередную букву имени
        }
        for (i=0;i<=2;i++) ToBUFF(0x00);       // 3x 0x00
    
        // это BAS(C)
        if (FType==1) {
          for (i=0;i<=767;i++) ToBUFF(0x55);   // 768x 0x55
          ToBUFF(0xE6);                        // синхробайт
          for (i=0;i<=2;i++) ToBUFF(0xD3);     // 3x 0xD3
          ToBUFF(0x00);                        // 1x 0x00
          return PlayFile(0, 2);// выводим данные из файла, возвращаем код возврата из ПП
        }
    
        // это MON, BAS(B) или ASM
        for (i=0;i<=255;i++) ToBUFF(0x00);     // 256x 0x00
        ToBUFF(0xE6);                          // синхробайт
    
        if (FType==2) {    // это MON или BAS(B)
          ToBUFF(lowByte(StartAddr));            // старший байт адреса начала записи
          ToBUFF(0x00);                          // младший байт = 0
          ToBUFF(lowByte(StartAddr+(Nbt-1)/256));// старший байт адреса конца записи
          ToBUFF(lowByte(Nbt-1));                // младший байт адреса конца записи
          return PlayFile(0, 1);// выводим данные из файла, возвращаем код возврата из ПП
        }
    
        // это ASM
        ToBUFF(lowByte(Nbt));                  // младший байт длинны записи
        ToBUFF(highByte(Nbt));                 // старший байт длинны записи
        return PlayFile(0, 2);// выводим данные из файла, возвращаем код возврата из ПП
      }
      return 3; // ошибка / нет файла 
    }
    
    int PlayFile(int Strt, byte TypeCS) // Функция вывода битов файла
    // Strt -- нач.позиция, TypeCS -- вид контрольной суммы
    {
      unsigned int CSF = 0;
      byte SB;
      for (unsigned int i=Strt; i<Nbt; i++){ // данные из файла
        SB = dataFile.read();
        ToBUFF(SB);
        if (TypeCS>0) CSF += SB;
    
        if ((Nbt-i)%256==0){     // если остаток кратен 256
          lcd.setCursor(12, 0);  // выводим на экран кол-во оставшихся "псевдоблоков" по 256 байт
          lcd.print((Nbt-i)>>8); // остаток данных / 256
          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();        // закрываем файл
      // выводим контрольную сумму
      if (TypeCS > 0) ToBUFF(lowByte(CSF));  // младший байт КС
      if (TypeCS > 1) ToBUFF(highByte(CSF)); // старший байт КС
    
      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;              // дополняем пробелами
        }
    
        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 -- завершение вывода программы (?)
        WaitBuffer();  // ожидаем окончания вывода
        return 0;      // выход из ПП с кодом 0.
      }
      return 3; // ошибка / нет файла 
    }
    
    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++;
        Timer1.setPeriod(Tpp+Tb); // Выставляем увеличенный период таймера (начало байта)
      }
    }
    
    ISR(ANALOG_COMP_vect) // ПП обработки прерывания компаратора -- определяет и заносит полученные биты в буфер
    {
      unsigned long iMicros = micros();
      unsigned long PPeriod = iMicros - iMicros_old;
      iMicros_old = iMicros;
      if (PPeriod < 65000) {  // началось...
        if (bBit < 16) {      // если это не последний полубит
          if (PPeriod <= (PPeriod_sred[PP]>>7)) { // sred/128, если тек. полупериод короткий
            if (CWB <= 255) {   // расчёт среднего значения, если меньше 256 байт считано
              PPeriod_sred[PP] = (PPeriod_sred[PP]*CWB + PPeriod*192)/(CWB+1); // "среднее значение" = 1,5*128 от короткого полупериода
            }
            if (bBit & 1) { // нечётный полубит
              A = (A<<1)+B; // заносим бит
            }               // нечётный -- пропускаем
          }
          else { // получен длинный полупериод
            B ^= 1;         // инвертируем бит
            A = (A<<1)+B;   // заносим бит
            bBit--;         // уменьшаем счётчик полубитов
          }
        }
        else { // если последний полубит, вводим корректировку на задержку между байтами
          // граница будет =([1,5*Tpp*128]*25/19)/128 =~[1,5*Tpp*128]/97 =~1,98*Tpp
          if (PPeriod > (PPeriod_sred[PP]/97)) { // если тек. полупериод длинный
            B ^= 1;         // инвертируем бит
            A = (A<<1)+B;   // заносим бит
            bBit--;         // уменьшаем счётчик полубитов
          }
        }
        // корректировка счётчиков
        PP ^= 1; // инвертируем бит признака чётности полупериода
        if (bBit > 1) {
          bBit--;                  // счётчик полубитов -1
        }
        else {
          BUFF[lowByte(CWB)] = A;  // заносим байт в буфер
          BUFF[lowByte(++CWB)] = 0;// счётчик байтов +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;             // есть сигнал!
      }
    }
    [свернуть]

    Архив со всеми библиотеками: RW-player_10.5.7z

    З.Ы. К сожалению, запись на "живом" Векторе я так и не проверил, эта часть скетча осталась без изменений.

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

    По умолчанию

    Свежая версия "магнитофона на ардуино":

    RW-player 11.1

    Для работы требуются библиотеки TimerOne, SdFat, RTClib, а также стандартные LiquidCrystal и Wire.
    Код:
    #define vers "version 11.1"
    /*
     Вариант на "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;              // размер файла, байт
    unsigned int StartAddr;        // стартовый адрес для MON
    unsigned int CSF;              // контрольная сумма
    
    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;           // записываемый бит
    
    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
      0x30, 0x39, 0x30, 0x37, 0x31, 0x38,             // дата: 090718
      0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // имя программы
      0x52, 0x4F, 0x4D, 0x00, 0x00 };                 // расширение "ROM" и нули
    
    // строка часов
    char DT[15] = {
      '0', '0', ':', '0', '0', ':', '0', '0', ' ',
      '0', '0', '/', '0', '0', 0x00 };
    
    // имя файла для записи
    char iFName[13] = "input000.vkt";
    
    volatile unsigned int Tpp = 256; // Начальная длительность задержки сигнала в микросекундах (один полупериод)
    volatile int Tb = 16;  // Дополнительная задержка сигнала на начало байта в микросекундах
    unsigned int TppSTD = 376; // Стандартная длительность задержки сигнала для файлов BAS/MON/ASM... 
    
    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_dplay = 1;     // воспроизведение в формате DOS
    const int M_dplay_in = 11;
    const int M_record = 2;    // запись
    const int M_record_in = 12;
    const int M_setup = 3;     // настройки
    const int M_setup_in = 13;
    
    void setup() {
      //Serial.begin(9600);
      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 > M_play) MLevel--;
          else MLevel = M_setup;
        }
        break;
      case BT_down:   // вниз
        if (MLevel < 10) {
          if (MLevel < M_setup) MLevel++;
          else MLevel = M_play;
        }
        break;
      case BT_right:  // вправо -- вход в меню, запуск действия и т.д.
        if (MLevel < 10) {
          switch (MLevel) // выводим надписи меню
          {
          case M_play: // воспроизведение
            printtext("Play file:",0);
            getMaxFile();
            break;
          case M_dplay: // воспроизведение (DOS)
            printtext("Play file (DOS):",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_dplay: // воспроизведение (DOS)
        printtext("Play file (D)->",0);
        printtime();
        break;
      case M_record: // запись
        printtext("Record data ->",0);
        printtime();
        break;
      case M_setup: // настройки
        printtext("Settings ->",0);
        printtime();
        break;
      case M_play_in: // зашли в меню вопроизведения
      case M_dplay_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 = MLevel - 10; // из корня выходим в стартовое меню
          }
          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);
    
              CSF = 0;
              for (int i=0; i<=12; i++){    // Переводим все буквы имени файла в заглавные
                if ((sfileName[i]>=0x61) && (sfileName[i]<=0x7A)) {
                  sfileName[i] &= 0xDF;     // на заглавные буквы
                }
                if (sfileName[i]=='.'){     // ищем точку в имени файла
                  CSF = i;                  // запоминаем позицию точки в неиспользуемой сейчас переменной, для этого не предназначенной
                }
              }
    
              if (MLevel == M_dplay_in) {           // если вывод в формате DOS
                RCrom = PlayAll(sfileName, CSF, 4); // вызов ПП для формата DOS
              }
              else {                                // другие форматы
                switch(sfileName[CSF+1])            // следующий после точки символ
                {
                case 'R': // первый символ == 'r'|'R'
                  if ( ((sfileName[CSF+2]=='O')|(sfileName[CSF+2]=='0'))&(sfileName[CSF+3]=='M') ) { // второй символ == 'o'|'O'|'0' и третий == 'm'|'M'
                    if (sfileName[CSF+2]!='0') { // проверка на вывод нулевого блока по расширению файла
                      BLs = 0x01; // с первого блока
                    }
                    else { 
                      BLs = 0x00; // с нулевого блока
                    }
                    RCrom = PlayROM(sfileName, CSF);// Передаём короткое имя файла и позицию точки в качестве параметров
                  }
                  break;
                case 'V': // первый символ == 'v'|'V'
                  if ( (sfileName[CSF+2]=='K')&(sfileName[CSF+3]=='T') ) { // второй символ == 'k'|'K' и третий == 't'|'T'
                    RCrom = PlayVKT(sfileName); // вызов ПП для формата VKT
                  }
                  break;
                case 'C': // первый символ == 'c'|'C'
                  if ( (sfileName[CSF+2]=='A')&(sfileName[CSF+3]=='S') ) { // второй символ == 'a'|'A' и третий == 's'|'S'
                    RCrom = PlayAll(sfileName, CSF, 0); // вызов ПП для формата CAS
                  }
                  break;
                case 'B': // первый символ == 'b'|'B'
                  if ( (sfileName[CSF+2]=='A')&(sfileName[CSF+3]=='S') ) { // второй символ == 'a'|'A' и третий == 's'|'S'
                    RCrom = PlayAll(sfileName, CSF, 1); // вызов ПП для формата BAS(C)
                    break; // выход из case, если это был BAS-файл
                  }
                case 'M': // первый символ == 'm'|'M' (или 'b'|'B')
                  StartAddr = 0x100;
                  if ((sfileName[CSF+2]>='0')&(sfileName[CSF+2]<='9')) { // преобразуем второй символ расширения в HEX
                    StartAddr = (sfileName[CSF+2] - '0')*16;
                  }
                  else if ((sfileName[CSF+2]>='A')&(sfileName[CSF+2]<='F')) {
                    StartAddr = (sfileName[CSF+2] - 55)*16;
                  }
                  if ((sfileName[CSF+3]>='0')&(sfileName[CSF+3]<='9')) { // преобразуем третий символ расширения в HEX
                    StartAddr += sfileName[CSF+3] - '0';
                  }
                  else if ((sfileName[CSF+3]>='A')&(sfileName[CSF+3]<='F')) {
                    StartAddr += (sfileName[CSF+3] - 55);
                  }
                  if (StartAddr < 0x100) { // если стартовый адрес из расширения определён верно
                    RCrom = PlayAll(sfileName, CSF, 2); // вызов ПП для формата MON или Бейсика BLOAD
                  }
                  break;
                case 'A': // первый символ == 'a'|'A'
                  if ( (sfileName[CSF+2]=='S')&(sfileName[CSF+3]=='M') ) { // второй символ == 's'|'S' и третий == 'm'|'M'
                    RCrom = PlayAll(sfileName, CSF, 3); // вызов ПП для формата ASM
                  }
                  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;
            case 12:
              printtext("Bad speed mark",0);   // метка скорости в VKT не правильная (больше или меньше границы)
              break;
            default:  
              printtext("ERROR!",0);           // Сообщение об ошибке
            }
            delay(1000);           // ждем 1 с
            if (MLevel != M_dplay_in) {        // если вывод в не формате DOS
              printtext("Play file:",0);
            }
            else {
              printtext("Play file (DOS):",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(200);           // задержка для накопления инфы...
            noInterrupts();       // запрет прерываний
            CRB_temp = CWB;       // сохраняем CWB во временную переменную
            interrupts();         // разрешение прерываний
    
            //====================================
            do { // пока есть данные в буфере
              dataFile.write(BUFF[lowByte(CRB)]); // пишем байт из буфера
              if (CRB%256 == 0) {    // каждый 256-й байт (1 псевдоблок)
                lcd.setCursor(7, 1); // устанавливаем курсор в позицию 8 в строке 1
                lcd.print(CRB);      // количество сохранённых байт
              }
              CRB++;
              if (CRB_temp <= CRB) {// если индекс записи меньше или равен индексу чтения
                delay(300);     // задержка на ввод данных: макс.скорость 160 * 16 полубит * ~117 байт или ~46 байт на минимальной
                noInterrupts(); // запрет прерываний
                CRB_temp = CWB; // сохраняем CWB во временную переменную
                interrupts();   // разрешение прерываний
              }
            }
            while (CRB_temp >= CRB); // если запись на SD обогнала чтение, значит сигнала нет, выходим из цикла
            //=====================================
            ACSR = 0;     // отключаем обработку прерывания компаратора
            ADCSRA = 135; // включить АЦП, установить делитель =128
            Pik = false;  // сбрасываем флаг "есть сигнал"
    
            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 = 256;                         // устанавливаем полупериод на "стандартное" значение
          }
          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);
      //Serial.println(buttonValue);
      if (buttonValue < 60) return BT_right;
      else if (buttonValue < 180) return BT_up;
      else if (buttonValue < 330) return BT_down;
      else if (buttonValue < 530) 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 SwapTpp() // поменять местами значения переменных Tpp и TppSTD
    {
      Tpp = Tpp + TppSTD;     // меняем местами значения Tpp и TppSTD
      TppSTD = Tpp - TppSTD;
      Tpp = Tpp - TppSTD;
      CalcTb();
    }
    
    void WaitBuffer() // ожидание очистки буфера при воспроизведении
    {
      do{                     // Ждём опустошения буфера
        delay(Tpp/64);        // задержка на вывод 1 байта (~Tpp*16/1000)
        BUFF[lowByte(CWB+1)] = 0x00; // обнуляем следующий за последним байт -- иногда и он успевает прочитаться...
        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 12; // не правильная метка
        dataFile.seekSet(0); // на начало файла
        Nbt = Nbt-4; // уменьшаем размер данных на метку (4) и предварительно считанные данные (251)
    
        CRB = 0;   // Сбрасываем индексы.
        bBit = 15;
        CalcTb();  // Вычисляем значение задержки на начало байта Tb
    
        // Начинаем наполнять буфер
        for (CWB=0; CWB<=250; CWB++){ // первые 251 байт
          BUFF[CWB] = dataFile.read();
        }
        CWB = 251;             // продолжаем с 251-го байта буфера
    
        lcd.setCursor(12, 0);  // выводим на экран общее кол-во "псевдоблоков" по 256 байт
        lcd.print(Nbt>>8);     // всего данных / 256
    
        Timer1.setPeriod(Tpp); // Выставляем период таймера
        Timer1.start();        // Запускаем таймер............
    
        int RCV = PlayFile(false);  // выводим данные из файла без подсчёта контрольной суммы
        if (RCV == 0) WaitBuffer(); // ожидаем окончания вывода
        return RCV;                 // выходим с кодом
      }
      return 3; // ошибка / нет файла 
    }
    
    int PlayAll(char FName[], int pnt, byte FType) // функция вывода остальных типов файлов
    // входные данные: имя файла, длина имени, тип файла:
    //    0=CAS и VKT; 1=BAS(C); 2=MON и BAS(B); 3=ASM; 4=DOS
    // Возвращаемые RC:
    //    = 0 -- всё ок
    //    = 1 -- прерывание по кнопке
    //    = 2 -- чтение обогнало запись в буфер
    {
      int i, rcp;
      //  byte SB;     // выводимый байт
      delay(1000);            // ждем 1 с
      if (dataFile.open(FName,O_READ)) { // открываем файл. Открылся?
        CRB = 0;   // Сбрасываем индексы.
        bBit = 15;
    
        // Начинаем наполнять буфер
        for (CWB=0; CWB<=255; CWB++){ // первые 256 байт
          if ((FType!=3)|((CWB/64==1)|(CWB/64==3))) { // если не ASM и это 64-127 или 192-255 байты
            BUFF[CWB] = 0;
          }
          else {                                      // иначе, если ASM и это 0-63 или 128-191 байты
            BUFF[CWB] = 0x55;
          }
        }
        CWB = 256;             // продолжаем с 256-го байта буфера
    
        SwapTpp();             // выставляем стандартную скорость вывода
        // Tpp = 376;             // стандартная скорость вывода
        // Tb = 0;
        Timer1.setPeriod(Tpp); // Выставляем период таймера
        Timer1.start();        // Запускаем таймер............
    
        delay(10);             // немного ждём, чтобы начался вывод первых байт
        ToBUFF(0xE6);          // синхробайт
        switch (FType)
        {
        case 0: // это CAS
          rcp = PlayFile(false);      // выводим данные из файла без подсчёта контрольной суммы
          if (rcp == 0) WaitBuffer(); // ожидаем окончания вывода
          SwapTpp();                  // восстанавливаем Tpp
          return rcp;                 // выходим с кодом
        case 1: // это BAS(C)
          for (i=0;i<=3;i++) ToBUFF(0xD3);    // 4x 0xD3
          break;
        case 2: // это MON или BAS(B)
          for (i=0;i<=3;i++) ToBUFF(0xD2);    // 4x 0xD2
          break;
        case 3: // это ASM
          for (i=0;i<=3;i++) ToBUFF(0xE6);    // 4x 0xE6
          break;
        case 4: // это DOS
          ToBUFF(0x01);
          ToBUFF(0x00);              // 0x0100 -- начальный адрес (всегда)
          ToBUFF(highByte(Nbt-1)+1); // старший байт конечного адреса (нач.адрес+длинна данных)
          ToBUFF(0xFF);              // младший байт конечного адреса (всегда)
          rcp = PlayFile(true);      // выводим байты из файла с подсчётом КС
          if (rcp > 0) {
            SwapTpp();               // восстанавливаем Tpp
            return rcp;              // выходим с кодом
          }
          for (i=lowByte(Nbt-1);i<0xFF;i++) ToBUFF(0x00); // дополняем, если надо, нулями
          ToBUFF(lowByte(CSF));      // выводим младший байт КС
        }
    
        byte SB;     // выводимый байт
        for (i=0;i<pnt;i++) {        // вывод имени файла без расширения, DOS поддерживаются только 'A'-'Z','0'-'9'
          if (FName[i]!=0x7E) {      // не тильда
            SB = FName[i];           // берём символ символ
          }
          else {                     // тильда -- часто встречабщийся символ, остальные можно переименовать вручную
            if (FType!=4) {
              SB = '_';              // меняем тильду на подчёркивание
            }
            else SB = '0';           // для DOS меняем тильду на нолик, иначе при загрузке будет ошибка
          }
          CSF += SB;                 // считаем КС
          ToBUFF(SB);                // заносим в буфер очередную букву имени
        }
    
        // это DOS
        if (FType==4) {
          for (i=pnt;i<8;i++) {    // дополняем имя пробелами до 8 байт
            ToBUFF(0x20);
            CSF += 0x20;
          }
          for (i=1;i<4;i++) {      // выводим расширение файла
            SB = FName[pnt+i];
            CSF += SB;             // считаем КС
            ToBUFF(SB);            // заносим в буфер очередную букву расширени
          }
          ToBUFF(lowByte(CSF)); // выводим младший байт КС (данные + имя файла)
          WaitBuffer();         // ожидаем окончания вывода
          SwapTpp();            // восстанавливаем Tpp
          return 0;             // выходим с кодом 0
        }
    
        for (i=0;i<=2;i++) ToBUFF(0x00);       // 3x 0x00
    
        // это BAS(C)
        if (FType==1) {
          for (i=0;i<=767;i++) ToBUFF(0x55);   // 768x 0x55
          ToBUFF(0xE6);                        // синхробайт
          for (i=0;i<=2;i++) ToBUFF(0xD3);     // 3x 0xD3
          ToBUFF(0x00);                        // 1x 0x00
          rcp = PlayFile(true);                // выводим данные из файла с подсчётом КС
          if (rcp == 0) {
            ToBUFF(lowByte(CSF));              // младший байт КС
            ToBUFF(highByte(CSF));             // старший байт КС
            WaitBuffer();                      // ожидаем окончания вывода
          }
          SwapTpp();                           // восстанавливаем Tpp
          return rcp;                          // выходим с кодом
        }
    
        // это MON, BAS(B) или ASM
        for (i=0;i<=255;i++) ToBUFF(0x00);     // 256x 0x00
        ToBUFF(0xE6);                          // синхробайт
    
        if (FType==2) {    // это MON или BAS(B)
          ToBUFF(lowByte(StartAddr));            // старший байт адреса начала записи
          ToBUFF(0x00);                          // младший байт = 0
          ToBUFF(lowByte(StartAddr+(Nbt-1)/256));// старший байт адреса конца записи
          ToBUFF(lowByte(Nbt-1));                // младший байт адреса конца записи
          rcp = PlayFile(true);                  // выводим данные из файла с подсчётом КС
          if (rcp == 0) {
            ToBUFF(lowByte(CSF));                // младший байт КС
            WaitBuffer();                        // ожидаем окончания вывода
          }
          SwapTpp();                             // восстанавливаем Tpp
          return rcp;                            // выходим с кодом
        }
    
        // остался только ASM
        ToBUFF(lowByte(Nbt));                    // младший байт длинны записи
        ToBUFF(highByte(Nbt));                   // старший байт длинны записи
        rcp = PlayFile(true);                    // выводим данные из файла с подсчётом КС
        if (rcp == 0) {
          ToBUFF(0xFF);                          // дополняем FFh в конце
          ToBUFF(lowByte(CSF));                  // младший байт КС
          ToBUFF(highByte(CSF));                 // старший байт КС
          WaitBuffer();                          // ожидаем окончания вывода
        }
        SwapTpp();                               // восстанавливаем Tpp
        return rcp;                              // выходим с кодом
      }
      return 3; // ошибка / нет файла 
    }
    
    int PlayFile(boolean CSFp) // Функция вывода битов файла
    // CSFp признак необходимости подсчёта контрольной суммы файла
    {
      byte SB;
      CSF = 0; // обнуляем контрольную сумму
      for (unsigned int i=0; i<Nbt; i++){ // данные из файла
        SB = dataFile.read();
        ToBUFF(SB);
        if (CSFp) CSF += SB;
    
        if ((Nbt-i)%256==0){     // если остаток кратен 256
          lcd.setCursor(12, 0);  // выводим на экран кол-во оставшихся "псевдоблоков" по 256 байт
          lcd.print((Nbt-i)>>8); // остаток данных / 256
          lcd.print(' ');
        }
        if (getPressedButton()!=BT_none) { // кнопка нажата?
          Timer1.stop();       // Останавливаем таймер
          dataFile.close();    // закрываем файл
          SwapTpp();           // восстанавливаем Tpp
          return 1;            // выход из ПП с ошибкой 1.
        }
        if (CRB_temp > CWB) {  // проверка -- не обогнало ли чтение запись?
          Timer1.stop();       // Останавливаем таймер
          dataFile.close();    // закрываем файл
          SwapTpp();           // восстанавливаем Tpp
          return 2;            // выход из ПП с ошибкой 2.
        }
      }
      dataFile.close();        // закрываем файл
      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]!=0x7E) {            // не тильда
              SB[i+14] = FName[i];           // заносим символ
            }
            else {
              SB[i+14] = '_';                // меняем тильду на подчёркивание, иначе это будет русская "Ч"
            }
          }
          else SB[i+14] = 0x20;              // дополняем пробелами
        }
    
        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 -- завершение вывода программы (?)
        WaitBuffer();  // ожидаем окончания вывода
        return 0;      // выход из ПП с кодом 0.
      }
      return 3; // ошибка / нет файла 
    }
    
    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++;
        Timer1.setPeriod(Tpp+Tb); // Выставляем увеличенный период таймера (начало байта)
      }
    }
    
    ISR(ANALOG_COMP_vect) // ПП обработки прерывания компаратора -- определяет и заносит полученные биты в буфер
    {
      unsigned long iMicros = micros();
      unsigned long PPeriod = iMicros - iMicros_old;
      iMicros_old = iMicros;
      if (PPeriod < 65000) {  // началось...
        if (bBit < 16) {      // если это не последний полубит
          if (PPeriod <= (PPeriod_sred[PP]>>7)) { // sred/128, если тек. полупериод короткий
            if (CWB <= 255) {   // расчёт среднего значения, если меньше 256 байт считано
              PPeriod_sred[PP] = (PPeriod_sred[PP]*CWB + PPeriod*192)/(CWB+1); // "среднее значение" = 1,5*128 от короткого полупериода
            }
            if (bBit & 1) { // нечётный полубит
              A = (A<<1)+B; // заносим бит
            }               // нечётный -- пропускаем
          }
          else { // получен длинный полупериод
            B ^= 1;         // инвертируем бит
            A = (A<<1)+B;   // заносим бит
            bBit--;         // уменьшаем счётчик полубитов
          }
        }
        else { // если последний полубит, вводим корректировку на задержку между байтами
          // граница будет =([1,5*Tpp*128]*25/19)/128 =~[1,5*Tpp*128]/97 =~1,98*Tpp
          if (PPeriod > (PPeriod_sred[PP]/97)) { // если тек. полупериод длинный
            B ^= 1;         // инвертируем бит
            A = (A<<1)+B;   // заносим бит
            bBit--;         // уменьшаем счётчик полубитов
          }
        }
        // корректировка счётчиков
        PP ^= 1; // инвертируем бит признака чётности полупериода
        if (bBit > 1) {
          bBit--;                  // счётчик полубитов -1
        }
        else {
          BUFF[lowByte(CWB)] = A;  // заносим байт в буфер
          BUFF[lowByte(++CWB)] = 0;// счётчик байтов +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;             // есть сигнал!
      }
    }
    [свернуть]

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

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

    То, что я писал тут про формат DOS подтвержается экспериментами на эмуляторе, плюс ещё такие моменты:
    - если выгружать данные размером не кратным 256 байтам, то всё равно на диске при загрузке файл будет округлён до этого значения.
    - имя файла для выгрузки на ленту командами "3" и "savedos" может быть любым допустимым, но вот назад считаются только файлы с именами из заглавных латинских букв и цифр.

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

    По умолчанию

    Обновление "магнитофона на ардуино":

    RW-player 11.3

    Для работы требуются библиотеки TimerOne, SdFat, RTClib, а также стандартные LiquidCrystal и Wire.
    Код:
    #define vers "version 11.3"
    /*Вариант на "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;               // всего позиций в директории (файлов и поддиректорий)
    boolean isDir = false;         // признак того, что текущая позиция -- это директория
    boolean isRoot = true;         // признак того, что мы в корне
    
    unsigned int Nbt;              // размер файла, байт
    unsigned int CSF;              // контрольная сумма
    
    volatile byte BUFF[256];       // буфер данных
    volatile unsigned int CRB = 0; // индекс чтения из буфера
    volatile byte bBit = 15;       // индекс читаемого полубита
    volatile unsigned int CWB = 0; // индекс записи в буфер
    volatile byte A = 0;           // записываемый байт
    volatile byte B = 0;           // записываемый бит
    
    volatile unsigned long PPeriod_sred[2]; // среднее значение границы длинны нечётного полупериода
    volatile unsigned long iMicros_old = 0; // предыдущее значение микросекунд
    volatile boolean Pik = false;           // есть изменение сигнала
    volatile byte PP = 1;                   // =0 -- чётный, =1 -- нечётный полупериод
    
    boolean DT_good = true; // Признак наличия часов
    
    volatile unsigned int Tpp = 256; // Начальная длительность задержки сигнала в микросекундах (один полупериод)
    volatile byte Tb = 16;  // Дополнительная задержка сигнала на начало байта в микросекундах
    unsigned int TppSTD = 376; // Стандартная длительность задержки сигнала для файлов BAS/MON/ASM...
    
    #define p 3         // номер пина, на который будет вывод сигнала
    #define InputPIN A1 // номер пина, на котором будет получение сигнала
    
    const byte BT_none   = 0; // константы -- коды нажатой кнопки
    const byte BT_right  = 1;
    const byte BT_up     = 2;
    const byte BT_down   = 3;
    const byte BT_left   = 4;
    const byte BT_select = 5;
    
    byte MLevel = 0; // текущий пункт меню
    const byte M_play = 0;      // воспроизведение
    const byte M_play_in = 10;
    const byte M_dplay = 1;     // воспроизведение в формате DOS
    const byte M_dplay_in = 11;
    const byte M_record = 2;    // запись
    const byte M_record_in = 12;
    const byte M_setup = 3;     // настройки
    const byte M_setup_in = 13;
    
    void setup() {
      //Serial.begin(9600);
      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();           // проверяем работу часов
        DT_good = !(now.month() > 12);      // если часы не работают, выставляем признак
      }
      //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() {
      byte RCrom = 0;                      // для кода возврата из ПП
      unsigned int StartAddr = 0x100;      // стартовый адрес для MON
      char iFName[13] = "input000.vkt";    // имя файла для записи
    
      byte button = getPressedButton();     // какая кнопка нажата?
      while (getPressedButton() != BT_none) { // Ждём, пока не будет отпущена кнопка
        delay(50);
      }
      switch (button) // проверяем, что было нажато
      {
        case BT_up:     // вверх
          if (MLevel < 10) {
            if (MLevel > M_play) MLevel--;
            else MLevel = M_setup;
          }
          break;
        case BT_down:   // вниз
          if (MLevel < 10) {
            if (MLevel < M_setup) MLevel++;
            else MLevel = M_play;
          }
          break;
        case BT_right:  // вправо -- вход в меню, запуск действия и т.д.
          if (MLevel < 10) {
            MLevel += 10;   // заходим в подменю
            switch (MLevel) // выводим надписи меню
            {
              case M_play_in:  // воспроизведение
              case M_dplay_in: // воспроизведение (DOS)
                printplay();   // вывод надписи "Play file..."
                getMaxFile();
                break;
              case M_record_in: // запись
                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_in: // настройки
                printtext("Setup", 0);
                break;
            }
            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_dplay: // воспроизведение (DOS)
          printtext("Play to DOS ->", 0);
          printtime();
          break;
        case M_record: // запись
          printtext("Record data ->", 0);
          printtime();
          break;
        case M_setup: // настройки
          printtext("Settings ->", 0);
          printtime();
          break;
        case M_play_in: // зашли в меню вопроизведения
        case M_dplay_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 = MLevel - 10; // из корня выходим в стартовое меню
              }
              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) { //Если это директория, то переход в неё
                sd.chdir(sfileName, true);
                isRoot = false;
                getMaxFile();
              }
              else {         // если не директория -- пробуем воспроизвести файл
                if (Nbt != 0xFFFF) { // проверяем размер файла
                  RCrom = 11; // для вывода сообщения "неверный формат"
                  printtext("Playing...", 0);
    
                  byte PNT = 0;
                  for (byte i = 0; i <= 12; i++) { // Переводим все буквы имени файла в заглавные
                    if ((sfileName[i] >= 0x61) && (sfileName[i] <= 0x7A)) {
                      sfileName[i] &= 0xDF;     // на заглавные буквы
                    }
                    if (sfileName[i] == '.') {  // ищем точку в имени файла
                      PNT = i;                  // запоминаем позицию точки в неиспользуемой сейчас переменной, для этого не предназначенной
                    }
                  }
    
                  if (MLevel == M_dplay_in) {           // если вывод в формате DOS
                    RCrom = PlayAll(PNT, 4, 0); // вызов ПП для формата DOS
                  }
                  else {                                // другие форматы
                    switch (sfileName[PNT + 1])         // следующий после точки символ
                    {
                      case 'R': // первый символ == 'r'|'R'
                        if ( ((sfileName[PNT + 2] == 'O') | (sfileName[PNT + 2] == '0')) & (sfileName[PNT + 3] == 'M') ) { // второй символ == 'o'|'O'|'0' и третий == 'm'|'M'
                          if (sfileName[PNT + 2] != '0') { // проверка на вывод нулевого блока по расширению файла
                            RCrom = PlayROM(PNT, 0x01);// Передаём позицию точки в качестве параметра, вывод с первого блока
                          }
                          else {
                            RCrom = PlayROM(PNT, 0x00);// Передаём позицию точки в качестве параметра, вывод с нулевого блока
                          }
                        }
                        break;
                      case 'V': // первый символ == 'v'|'V'
                        if ( (sfileName[PNT + 2] == 'K') & (sfileName[PNT + 3] == 'T') ) { // второй символ == 'k'|'K' и третий == 't'|'T'
                          RCrom = PlayVKT(); // вызов ПП для формата VKT
                        }
                        break;
                      case 'C': // первый символ == 'c'|'C'
                        if ( (sfileName[PNT + 2] == 'A') & (sfileName[PNT + 3] == 'S') ) { // второй символ == 'a'|'A' и третий == 's'|'S'
                          RCrom = PlayAll(PNT, 0, 0); // вызов ПП для формата CAS
                        }
                        break;
                      case 'B': // первый символ == 'b'|'B'
                        if ( (sfileName[PNT + 2] == 'A') & (sfileName[PNT + 3] == 'S') ) { // второй символ == 'a'|'A' и третий == 's'|'S'
                          RCrom = PlayAll(PNT, 1, 0); // вызов ПП для формата BAS(C)
                          break; // выход из case, если это был BAS-файл
                        }
                      case 'M': // первый символ == 'm'|'M' (или 'b'|'B')
                        //StartAddr = 0x100;
                        if ((sfileName[PNT + 2] >= '0') & (sfileName[PNT + 2] <= '9')) { // преобразуем второй символ расширения в HEX
                          StartAddr = (sfileName[PNT + 2] - '0') * 16;
                        }
                        else if ((sfileName[PNT + 2] >= 'A') & (sfileName[PNT + 2] <= 'F')) {
                          StartAddr = (sfileName[PNT + 2] - 55) * 16;
                        }
                        if ((sfileName[PNT + 3] >= '0') & (sfileName[PNT + 3] <= '9')) { // преобразуем третий символ расширения в HEX
                          StartAddr += sfileName[PNT + 3] - '0';
                        }
                        else if ((sfileName[PNT + 3] >= 'A') & (sfileName[PNT + 3] <= 'F')) {
                          StartAddr += (sfileName[PNT + 3] - 55);
                        }
                        if (StartAddr < 0x100) { // если стартовый адрес из расширения определён верно
                          RCrom = PlayAll(PNT, 2, StartAddr); // вызов ПП для формата MON или Бейсика BLOAD
                        }
                        break;
                      case 'A': // первый символ == 'a'|'A'
                        if ( (sfileName[PNT + 2] == 'S') & (sfileName[PNT + 3] == 'M') ) { // второй символ == 's'|'S' и третий == 'm'|'M'
                          RCrom = PlayAll(PNT, 3, 0); // вызов ПП для формата ASM
                        }
                        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;
                  case 12:
                    printtext("Bad speed mark", 0);  // метка скорости в VKT не правильная (больше или меньше границы)
                    break;
                  default:
                    printtext("ERROR!", 0);          // Сообщение об ошибке
                }
                delay(1000);   // ждем 1 с
                printplay();   // вывод надписи "Play file..."
                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(200);           // задержка для накопления инфы...
              noInterrupts();       // запрет прерываний
              unsigned int CWB_temp = CWB;       // сохраняем CWB во временную переменную
              interrupts();         // разрешение прерываний
    
              //====================================
              do { // пока есть данные в буфере
                dataFile.write(BUFF[lowByte(CRB)]); // пишем байт из буфера
                if (CRB % 256 == 0) {  // каждый 256-й байт (1 псевдоблок)
                  lcd.setCursor(7, 1); // устанавливаем курсор в позицию 8 в строке 1
                  lcd.print(CRB);      // количество сохранённых байт
                }
                CRB++;
                if (CWB_temp <= CRB) {// если индекс записи меньше или равен индексу чтения
                  delay(300);     // задержка на ввод данных: макс.скорость 160 * 16 полубит * ~117 байт или ~46 байт на минимальной
                  noInterrupts(); // запрет прерываний
                  CWB_temp = CWB; // сохраняем CWB во временную переменную
                  interrupts();   // разрешение прерываний
                }
              }
              while (CWB_temp >= CRB); // если запись на SD обогнала чтение, значит сигнала нет, выходим из цикла
              //=====================================
              ACSR = 0;     // отключаем обработку прерывания компаратора
              ADCSRA = 135; // включить АЦП, установить делитель =128
              Pik = false;  // сбрасываем флаг "есть сигнал"
    
              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_good) {                   // если часы работают...
                  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 = 256;                         // устанавливаем полупериод на "стандартное" значение
            }
            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;
      }
    }
    
    //=================================================================
    byte getPressedButton()  // функция проверки нажатой кнопки
    {
      int buttonValue = analogRead(0);
      //Serial.println(buttonValue);
      if (buttonValue < 60) return BT_right;
      else if (buttonValue < 180) return BT_up;
      else if (buttonValue < 330) return BT_down;
      else if (buttonValue < 530) return BT_left;
      else if (buttonValue < 800) return BT_select;
      return BT_none;
    }
    
    void printtext(char* text, byte l) {  // Вывод текста на экран в строке l с очисткой строки
      lcd.setCursor(0, l);
      //byte WRB = lcd.print(text);
      for (byte i = lcd.print(text); i < 16; i++) lcd.print(' '); // вывод текста и очистка строки до конца
    }
    
    void printtime() { // вывод времени и даты
      lcd.setCursor(0, 1); // устанавливаем курсор в позицию 0 в строке 1
      if (DT_good) {                              // если часы работают
        char DT[17];
        DateTime now = RTC.now();                 // получаем текущее время
        DT[0] = now.hour() / 10 + '0';            // перевод из целого в символ
        DT[1] = now.hour() % 10 + '0';            // часы
        DT[2] = ':';
        DT[3] = now.minute() / 10 + '0';          // минуты
        DT[4] = now.minute() % 10 + '0';          // минуты
        DT[5] = ':';
        DT[6] = now.second() / 10 + '0';          // секунды
        DT[7] = now.second() % 10 + '0';          // секунды
        DT[8] = ' ';
        DT[9] = now.day() / 10 + '0';             // день
        DT[10] = now.day() % 10 + '0';            // день
        DT[11] = '/';
        DT[12] = now.month() / 10 + '0';          // месяц
        DT[13] = now.month() % 10 + '0';          // месяц
        DT[14] = ' ';
        DT[15] = ' ';
        DT[16] = 0x00;
        lcd.print(DT);                            // выводим время и дату
      }
      else {
        byte WRB = lcd.print(millis() / 1000);      // выводим количество секунд с момента влючения ардуины вместо времени
        for (byte i = WRB; i < 16; i++) lcd.print(' '); // очистка строки после текста
      }
    }
    
    void printplay() {
      if (MLevel != M_dplay_in) {        // если вывод в не формате DOS
        printtext("Play file:", 0);
      }
      else {
        printtext("Play file (DOS):", 0);
      }
    }
    
    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 X = 1; X < currentFile; X++) { // читаем первые файлы до 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) {                     // это не директория?
        printtext(sfileName, 1);        // вывод имени текущего файла
        if (Nbt < 0xFFFF) {             // если размер файла в допустимых пределах
          lcd.setCursor(16, 1);
          byte WRB;
          if (Nbt >= 1000) {            // если размер больше 1000 байт
            WRB = lcd.print(Nbt / 1024); // подсчёт числа символов
            lcd.setCursor(15 - WRB, 1);
            lcd.print(Nbt / 1024);      // вывод размера файла в кБ
            lcd.print('k');
          }
          else {
            WRB = lcd.print(Nbt);       // подсчёт числа символов
            lcd.setCursor(16 - WRB, 1);
            lcd.print(Nbt);             // вывод размера файла в байтах
          }
        }
      }
      else {
        for (byte 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 SwapTpp() // поменять местами значения переменных Tpp и TppSTD
    {
      Tpp = Tpp + TppSTD;     // меняем местами значения Tpp и TppSTD
      TppSTD = Tpp - TppSTD;
      Tpp = Tpp - TppSTD;
      CalcTb();
    }
    
    void WaitBuffer() // ожидание очистки буфера при воспроизведении
    {
      unsigned int CRB_tmp;
      do {                    // Ждём опустошения буфера
        delay(Tpp / 64);      // задержка на вывод 1 байта (~Tpp*16/1000)
        BUFF[lowByte(CWB + 1)] = 0x00; // обнуляем следующий за последним байт -- иногда и он успевает прочитаться...
        noInterrupts();       // запрет прерываний
        CRB_tmp = CRB;        // сохраняем CRB во временную переменную
        interrupts();         // разрешение прерываний
      }
      while (CRB_tmp < CWB);
      Timer1.stop();  // Останавливаем таймер............
    }
    
    byte PlayVKT() // функция вывода файла VKT
    {
      delay(1000);                                     // ждем 1 с
      if (dataFile.open(sfileName, 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 12;      // не правильная метка
        dataFile.seekSet(0);                           // на начало файла
        Nbt = Nbt - 4; // уменьшаем размер данных на метку (4) и предварительно считанные данные (251)
    
        CRB = 0;   // Сбрасываем индексы.
        bBit = 15;
        CalcTb();  // Вычисляем значение задержки на начало байта Tb
    
        // Начинаем наполнять буфер
        for (CWB = 0; CWB <= 250; CWB++) { // первые 251 байт
          BUFF[CWB] = dataFile.read();
        }
        CWB = 251;             // продолжаем с 251-го байта буфера
    
        lcd.setCursor(12, 0);  // выводим на экран общее кол-во "псевдоблоков" по 256 байт
        lcd.print(Nbt >> 8);   // всего данных / 256
    
        Timer1.setPeriod(Tpp); // Выставляем период таймера
        Timer1.start();        // Запускаем таймер............
    
        int RCV = PlayFile(false);  // выводим данные из файла без подсчёта контрольной суммы
        if (RCV == 0) WaitBuffer(); // ожидаем окончания вывода
        return RCV;                 // выходим с кодом
      }
      return 3; // ошибка / нет файла
    }
    
    byte PlayAll(byte pnt, byte FType, unsigned int StartAddr) // функция вывода остальных типов файлов
    // входные данные: длина имени, тип файла, начальный адрес. Тип файла это:
    //    0=CAS и VKT; 1=BAS(C); 2=MON и BAS(B); 3=ASM; 4=DOS
    // Возвращаемые RC:
    //    = 0 -- всё ок
    //    = 1 -- прерывание по кнопке
    //    = 2 -- чтение обогнало запись в буфер
    {
      int i, rcp;
      //  byte SB;     // выводимый байт
      delay(1000);            // ждем 1 с
      if (dataFile.open(sfileName, O_READ)) { // открываем файл. Открылся?
        CRB = 0;   // Сбрасываем индексы.
        bBit = 15;
    
        // Начинаем наполнять буфер
        for (CWB = 0; CWB <= 255; CWB++) { // первые 256 байт
          if ((FType != 3) | ((CWB / 64 == 1) | (CWB / 64 == 3))) { // если не ASM и это 64-127 или 192-255 байты
            BUFF[CWB] = 0;
          }
          else {                                      // иначе, если ASM и это 0-63 или 128-191 байты
            BUFF[CWB] = 0x55;
          }
        }
        CWB = 256;             // продолжаем с 256-го байта буфера
    
        SwapTpp();             // выставляем стандартную скорость вывода
        // Tpp = 376;             // стандартная скорость вывода
        // Tb = 0;
        Timer1.setPeriod(Tpp); // Выставляем период таймера
        Timer1.start();        // Запускаем таймер............
    
        delay(10);             // немного ждём, чтобы начался вывод первых байт
        ToBUFF(0xE6);          // синхробайт
        switch (FType)
        {
          case 0: // это CAS
            rcp = PlayFile(false);      // выводим данные из файла без подсчёта контрольной суммы
            if (rcp == 0) WaitBuffer(); // ожидаем окончания вывода
            SwapTpp();                  // восстанавливаем Tpp
            return rcp;                 // выходим с кодом
          case 1: // это BAS(C)
            for (i = 0; i <= 3; i++) ToBUFF(0xD3); // 4x 0xD3
            break;
          case 2: // это MON или BAS(B)
            for (i = 0; i <= 3; i++) ToBUFF(0xD2); // 4x 0xD2
            break;
          case 3: // это ASM
            for (i = 0; i <= 3; i++) ToBUFF(0xE6); // 4x 0xE6
            break;
          case 4: // это DOS
            ToBUFF(0x01);
            ToBUFF(0x00);              // 0x0100 -- начальный адрес (всегда)
            ToBUFF(highByte(Nbt - 1) + 1); // старший байт конечного адреса (нач.адрес+длинна данных)
            ToBUFF(0xFF);              // младший байт конечного адреса (всегда)
            rcp = PlayFile(true);      // выводим байты из файла с подсчётом КС
            if (rcp > 0) {
              SwapTpp();               // восстанавливаем Tpp
              return rcp;              // выходим с кодом
            }
            for (i = lowByte(Nbt - 1); i < 0xFF; i++) ToBUFF(0x00); // дополняем, если надо, нулями
            ToBUFF(lowByte(CSF));      // выводим младший байт КС
        }
    
        byte SB;     // выводимый байт
        for (i = 0; i < pnt; i++) {  // вывод имени файла без расширения, DOS поддерживаются только 'A'-'Z','0'-'9'
          if (sfileName[i] != 0x7E) {    // не тильда
            SB = sfileName[i];           // берём символ символ
          }
          else {                     // тильда -- часто встречабщийся символ, остальные можно переименовать вручную
            if (FType != 4) {
              SB = '_';              // меняем тильду на подчёркивание
            }
            else SB = '0';           // для DOS меняем тильду на нолик, иначе при загрузке будет ошибка
          }
          CSF += SB;                 // считаем КС
          ToBUFF(SB);                // заносим в буфер очередную букву имени
        }
    
        // это DOS
        if (FType == 4) {
          for (i = pnt; i < 8; i++) { // дополняем имя пробелами до 8 байт
            ToBUFF(0x20);
            CSF += 0x20;
          }
          for (i = 1; i < 4; i++) { // выводим расширение файла
            SB = sfileName[pnt + i];
            CSF += SB;             // считаем КС
            ToBUFF(SB);            // заносим в буфер очередную букву расширени
          }
          ToBUFF(lowByte(CSF)); // выводим младший байт КС (данные + имя файла)
          WaitBuffer();         // ожидаем окончания вывода
          SwapTpp();            // восстанавливаем Tpp
          return 0;             // выходим с кодом 0
        }
    
        for (i = 0; i <= 2; i++) ToBUFF(0x00); // 3x 0x00
    
        // это BAS(C)
        if (FType == 1) {
          for (i = 0; i <= 767; i++) ToBUFF(0x55); // 768x 0x55
          ToBUFF(0xE6);                        // синхробайт
          for (i = 0; i <= 2; i++) ToBUFF(0xD3); // 3x 0xD3
          ToBUFF(0x00);                        // 1x 0x00
          rcp = PlayFile(true);                // выводим данные из файла с подсчётом КС
          if (rcp == 0) {
            ToBUFF(lowByte(CSF));              // младший байт КС
            ToBUFF(highByte(CSF));             // старший байт КС
            WaitBuffer();                      // ожидаем окончания вывода
          }
          SwapTpp();                           // восстанавливаем Tpp
          return rcp;                          // выходим с кодом
        }
    
        // это MON, BAS(B) или ASM
        for (i = 0; i <= 255; i++) ToBUFF(0x00); // 256x 0x00
        ToBUFF(0xE6);                          // синхробайт
    
        if (FType == 2) {  // это MON или BAS(B)
          ToBUFF(lowByte(StartAddr));            // старший байт адреса начала записи
          ToBUFF(0x00);                          // младший байт = 0
          ToBUFF(lowByte(StartAddr + (Nbt - 1) / 256)); // старший байт адреса конца записи
          ToBUFF(lowByte(Nbt - 1));              // младший байт адреса конца записи
          rcp = PlayFile(true);                  // выводим данные из файла с подсчётом КС
          if (rcp == 0) {
            ToBUFF(lowByte(CSF));                // младший байт КС
            WaitBuffer();                        // ожидаем окончания вывода
          }
          SwapTpp();                             // восстанавливаем Tpp
          return rcp;                            // выходим с кодом
        }
    
        // остался только ASM
        ToBUFF(lowByte(Nbt));                    // младший байт длинны записи
        ToBUFF(highByte(Nbt));                   // старший байт длинны записи
        rcp = PlayFile(true);                    // выводим данные из файла с подсчётом КС
        if (rcp == 0) {
          ToBUFF(0xFF);                          // дополняем FFh в конце
          ToBUFF(lowByte(CSF));                  // младший байт КС
          ToBUFF(highByte(CSF));                 // старший байт КС
          WaitBuffer();                          // ожидаем окончания вывода
        }
        SwapTpp();                               // восстанавливаем Tpp
        return rcp;                              // выходим с кодом
      }
      return 3; // ошибка / нет файла
    }
    
    byte PlayFile(boolean CSFp) // Функция вывода битов файла
    // CSFp признак необходимости подсчёта контрольной суммы файла
    {
      byte SB;
      unsigned int CWB_tmp;
      CSF = 0; // обнуляем контрольную сумму
      for (unsigned int i = 0; i < Nbt; i++) { // данные из файла
        SB = dataFile.read();
        ToBUFF(SB);
        if (CSFp) CSF += SB;
    
        if ((Nbt - i) % 256 == 0) { // если остаток кратен 256
          lcd.setCursor(12, 0);  // выводим на экран кол-во оставшихся "псевдоблоков" по 256 байт
          lcd.print((Nbt - i) >> 8); // остаток данных / 256
          lcd.print(' ');
        }
        if (getPressedButton() != BT_none) { // кнопка нажата?
          Timer1.stop();       // Останавливаем таймер
          dataFile.close();    // закрываем файл
          SwapTpp();           // восстанавливаем Tpp
          return 1;            // выход из ПП с ошибкой 1.
        }
        noInterrupts();               // запрет прерываний
        CWB_tmp = CRB;                // сохраняем CRB во временную переменную
        interrupts();                 // разрешение прерываний
        if (CWB_tmp > CWB) {   // проверка -- не обогнало ли воспроизведение запись в буфер?
          Timer1.stop();       // Останавливаем таймер
          dataFile.close();    // закрываем файл
          SwapTpp();           // восстанавливаем Tpp
          return 2;            // выход из ПП с ошибкой 2.
        }
      }
      dataFile.close();        // закрываем файл
      return 0;
    }
    
    byte PlayROM(int pnt, byte BLs) // функция вывода файла ROM
    {
      delay(1000);           // ждем 1 с
    
      if (dataFile.open(sfileName, O_READ)) { // открываем файл. Открылся?
        byte BLe = Nbt / 256; // всего блоков
        byte BLt;             // осталось блоков
        byte Nst;             // номер строки
        byte St;              // выводимый байт
    
        byte CSz = 0x00;      // контрольная сумма заголовка
        byte CSs = 0x00;      // контрольная сумма строки
        byte i;
        byte j;
        unsigned int CRB_tmp;
    
        if (Nbt % 256 != 0) BLe++; // корректировка количества блоков, если размер файла не кратен 256
    
        // Заголовок блока
        byte SB[25];
        SB[0] = 0x4E;
        SB[1] = 0x4F;
        SB[2] = 0x44;
        SB[3] = 0x49;
        SB[4] = 0x53;
        SB[5] = 0x43;
        SB[6] = 0x30;
        SB[7] = 0x30;  // NODISK00
        SB[22] = 0x52;
        SB[23] = 0x4F;
        SB[24] = 0x4D; // расширение "ROM"
    
        for (i = 0; i <= 7; i++) {           // заносим в SB имя файла
          if (i < pnt) {                     // имя ещё не закончилось?
            if (sfileName[i] != 0x7E) {      // не тильда
              SB[i + 14] = sfileName[i];     // заносим символ
            }
            else {
              SB[i + 14] = '_';              // меняем тильду на подчёркивание, иначе это будет русская "Ч"
            }
          }
          else SB[i + 14] = 0x20;            // дополняем пробелами
        }
    
        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 <= 24; j++) {        // заголовок блока
            CSz += SB[j];
            ToBUFF(SB[j]);
          }
          ToBUFF(0x00);
          ToBUFF(0x00);
          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.
              }
              noInterrupts();               // запрет прерываний
              CRB_tmp = CRB;                // сохраняем CRB во временную переменную
              interrupts();                 // разрешение прерываний
              if (CRB_tmp > CWB) {      // проверка -- не обогнало ли чтение запись?
                Timer1.stop();          // Останавливаем таймер
                dataFile.close();       // закрываем файл
                return 2;               // выход из ПП с ошибкой 2.
              }
            }
            ToBUFF(CSs);                // контр.сумма строки
          }
        }
        dataFile.close(); // закрываем файл
    
        for (j = 0; j <= 31; j++) ToBUFF(0x00); // 00h*32 -- завершение вывода программы (?)
        WaitBuffer();  // ожидаем окончания вывода
        return 0;      // выход из ПП с кодом 0.
      }
      return 3; // ошибка / нет файла
    }
    
    void ToBUFF(byte SBb) {     // Подпрограмма записи байта в буфер
      unsigned int CRB_tmp;
      noInterrupts();               // запрет прерываний
      CRB_tmp = CRB;                // сохраняем CRB во временную переменную
      interrupts();                 // разрешение прерываний
      if (CWB > (CRB_tmp + 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++;
        Timer1.setPeriod(Tpp + Tb); // Выставляем увеличенный период таймера (начало байта)
      }
    }
    
    ISR(ANALOG_COMP_vect) // ПП обработки прерывания компаратора -- определяет и заносит полученные биты в буфер
    {
      unsigned long iMicros = micros();
      unsigned long PPeriod = iMicros - iMicros_old;
      iMicros_old = iMicros;
      if (PPeriod < 65000) {  // началось...
        if (bBit < 16) {      // если это не последний полубит
          if (PPeriod <= (PPeriod_sred[PP] >> 7)) { // sred/128, если тек. полупериод короткий
            if (CWB <= 255) {   // расчёт среднего значения, если меньше 256 байт считано
              PPeriod_sred[PP] = (PPeriod_sred[PP] * CWB + PPeriod * 192) / (CWB + 1); // "среднее значение" = 1,5*128 от короткого полупериода
            }
            if (bBit & 1) { // нечётный полубит
              A = (A << 1) + B; // заносим бит
            }               // нечётный -- пропускаем
          }
          else { // получен длинный полупериод
            B ^= 1;         // инвертируем бит
            A = (A << 1) + B; // заносим бит
            bBit--;         // уменьшаем счётчик полубитов
          }
        }
        else { // если последний полубит, вводим корректировку на задержку между байтами
          // граница будет =([1,5*Tpp*128]*25/19)/128 =~[1,5*Tpp*128]/97 =~1,98*Tpp
          if (PPeriod > (PPeriod_sred[PP] / 97)) { // если тек. полупериод длинный
            B ^= 1;         // инвертируем бит
            A = (A << 1) + B; // заносим бит
            bBit--;         // уменьшаем счётчик полубитов
          }
        }
        // корректировка счётчиков
        PP ^= 1; // инвертируем бит признака чётности полупериода
        if (bBit > 1) {
          bBit--;                  // счётчик полубитов -1
        }
        else {
          BUFF[lowByte(CWB)] = A;  // заносим байт в буфер
          BUFF[lowByte(++CWB)] = 0;// счётчик байтов +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. Оптимизация глобальных переменных. Запустил компиляцию в Arduino IDE 1.8.7, а она рекомендует освободить больше памяти для переменных для большей стабильности. Сделал, насколько это возможно, многие переменные из глобальных переделал в локальные...
    2. При использовании магнитофона заметил, что будет удобно предварительно увидеть размер файла перед выгрузкой, добавил эту функцию. Теперь справа от имени файла показывается его размер в байтах или килобайтах (округляется в меньшую сторону до целых), на файлах больше 63Кб и директориях размер не показывается.

    Архив, скетч + библиотеки: RW-player_11.3.7z

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

    По умолчанию

    Ещё два небольших дополнения в "магнитофоне":
    1. Теперь файлы с расширением "*.com" и "*.c0m" воспроизводятся "магнитофоном" в формате загрузчика, также, как и "*.rom" ("*.r0m").
    2. При воспроизведении в формате ДОС файлов с расширением "*.rom" они сразу переименовываются в "*.com", чтобы потом не переименовывать их вручную.

    RW-player 11.5

    Для работы требуются библиотеки TimerOne, SdFat, RTClib, а также стандартные LiquidCrystal и Wire.
    Код:
    #define vers "version 11.5"
    /*Вариант на "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;               // всего позиций в директории (файлов и поддиректорий)
    boolean isDir = false;         // признак того, что текущая позиция -- это директория
    boolean isRoot = true;         // признак того, что мы в корне
    
    unsigned int Nbt;              // размер файла, байт
    unsigned int CSF;              // контрольная сумма
    
    volatile byte BUFF[256];       // буфер данных
    volatile unsigned int CRB = 0; // индекс чтения из буфера
    volatile byte bBit = 15;       // индекс читаемого полубита
    volatile unsigned int CWB = 0; // индекс записи в буфер
    volatile byte A = 0;           // записываемый байт
    volatile byte B = 0;           // записываемый бит
    
    volatile unsigned long PPeriod_sred[2]; // среднее значение границы длинны нечётного полупериода
    volatile unsigned long iMicros_old = 0; // предыдущее значение микросекунд
    volatile boolean Pik = false;           // есть изменение сигнала
    volatile byte PP = 1;                   // =0 -- чётный, =1 -- нечётный полупериод
    
    boolean DT_good = true; // Признак наличия часов
    
    volatile unsigned int Tpp = 256; // Начальная длительность задержки сигнала в микросекундах (один полупериод)
    volatile byte Tb = 16;  // Дополнительная задержка сигнала на начало байта в микросекундах
    unsigned int TppSTD = 376; // Стандартная длительность задержки сигнала для файлов BAS/MON/ASM...
    
    #define p 3         // номер пина, на который будет вывод сигнала
    #define InputPIN A1 // номер пина, на котором будет получение сигнала
    
    const byte BT_none   = 0; // константы -- коды нажатой кнопки
    const byte BT_right  = 1;
    const byte BT_up     = 2;
    const byte BT_down   = 3;
    const byte BT_left   = 4;
    const byte BT_select = 5;
    
    byte MLevel = 0; // текущий пункт меню
    const byte M_play = 0;      // воспроизведение
    const byte M_play_in = 10;
    const byte M_dplay = 1;     // воспроизведение в формате DOS
    const byte M_dplay_in = 11;
    const byte M_record = 2;    // запись
    const byte M_record_in = 12;
    const byte M_setup = 3;     // настройки
    const byte M_setup_in = 13;
    
    void setup() {
      //Serial.begin(9600);
      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();           // проверяем работу часов
        DT_good = !(now.month() > 12);      // если часы не работают, выставляем признак
      }
      //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() {
      byte RCrom = 0;                      // для кода возврата из ПП
      unsigned int StartAddr = 0x100;      // стартовый адрес для MON
      char iFName[13] = "input000.vkt";    // имя файла для записи
    
      byte button = getPressedButton();     // какая кнопка нажата?
      while (getPressedButton() != BT_none) { // Ждём, пока не будет отпущена кнопка
        delay(50);
      }
      switch (button) // проверяем, что было нажато
      {
        case BT_up:     // вверх
          if (MLevel < 10) {
            if (MLevel > M_play) MLevel--;
            else MLevel = M_setup;
          }
          break;
        case BT_down:   // вниз
          if (MLevel < 10) {
            if (MLevel < M_setup) MLevel++;
            else MLevel = M_play;
          }
          break;
        case BT_right:  // вправо -- вход в меню, запуск действия и т.д.
          if (MLevel < 10) {
            MLevel += 10;   // заходим в подменю
            switch (MLevel) // выводим надписи меню
            {
              case M_play_in:  // воспроизведение
              case M_dplay_in: // воспроизведение (DOS)
                printplay();   // вывод надписи "Play file..."
                getMaxFile();
                break;
              case M_record_in: // запись
                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_in: // настройки
                printtext("Setup", 0);
                break;
            }
            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_dplay: // воспроизведение (DOS)
          printtext("Play to DOS ->", 0);
          printtime();
          break;
        case M_record: // запись
          printtext("Record data ->", 0);
          printtime();
          break;
        case M_setup: // настройки
          printtext("Settings ->", 0);
          printtime();
          break;
        case M_play_in: // зашли в меню вопроизведения
        case M_dplay_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 = MLevel - 10; // из корня выходим в стартовое меню
              }
              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) { //Если это директория, то переход в неё
                sd.chdir(sfileName, true);
                isRoot = false;
                getMaxFile();
              }
              else {         // если не директория -- пробуем воспроизвести файл
                if (Nbt != 0xFFFF) { // проверяем размер файла
                  RCrom = 11; // для вывода сообщения "неверный формат"
                  printtext("Playing...", 0);
    
                  byte PNT = 0;
                  for (byte i = 0; i <= 12; i++) { // Переводим все буквы имени файла в заглавные
                    if ((sfileName[i] >= 0x61) && (sfileName[i] <= 0x7A)) {
                      sfileName[i] &= 0xDF;     // на заглавные буквы
                    }
                    if (sfileName[i] == '.') {  // ищем точку в имени файла
                      PNT = i;                  // запоминаем позицию точки в неиспользуемой сейчас переменной, для этого не предназначенной
                    }
                  }
    
                  if (MLevel == M_dplay_in) {           // если вывод в формате DOS
                    RCrom = PlayAll(PNT, 4, 0); // вызов ПП для формата DOS
                  }
                  else {                                // другие форматы
                    switch (sfileName[PNT + 1])         // следующий после точки символ
                    {
                      case 'C': // первый символ == 'c'|'C'
                        if ( (sfileName[PNT + 2] == 'A') & (sfileName[PNT + 3] == 'S') ) { // второй символ == 'a'|'A' и третий == 's'|'S'
                          RCrom = PlayAll(PNT, 0, 0); // вызов ПП для формата CAS
                          break;
                        } // если не CAS, то далее проверяем на COM/C0M
                      case 'R': // первый символ == 'r'|'R'
                        if ( ((sfileName[PNT + 2] == 'O') | (sfileName[PNT + 2] == '0')) & (sfileName[PNT + 3] == 'M') ) { // второй символ == 'o'|'O'|'0' и третий == 'm'|'M'
                          if (sfileName[PNT + 2] != '0') { // проверка на вывод нулевого блока по расширению файла
                            RCrom = PlayROM(PNT, 0x01);// Передаём позицию точки в качестве параметра, вывод с первого блока
                          }
                          else {
                            RCrom = PlayROM(PNT, 0x00);// Передаём позицию точки в качестве параметра, вывод с нулевого блока
                          }
                        }
                        break;
                      case 'V': // первый символ == 'v'|'V'
                        if ( (sfileName[PNT + 2] == 'K') & (sfileName[PNT + 3] == 'T') ) { // второй символ == 'k'|'K' и третий == 't'|'T'
                          RCrom = PlayVKT(); // вызов ПП для формата VKT
                        }
                        break;
                      case 'B': // первый символ == 'b'|'B'
                        if ( (sfileName[PNT + 2] == 'A') & (sfileName[PNT + 3] == 'S') ) { // второй символ == 'a'|'A' и третий == 's'|'S'
                          RCrom = PlayAll(PNT, 1, 0); // вызов ПП для формата BAS(C)
                          break; // выход из case, если это был BAS-файл
                        }
                      case 'M': // первый символ == 'm'|'M' (или 'b'|'B')
                        //StartAddr = 0x100;
                        if ((sfileName[PNT + 2] >= '0') & (sfileName[PNT + 2] <= '9')) { // преобразуем второй символ расширения в HEX
                          StartAddr = (sfileName[PNT + 2] - '0') * 16;
                        }
                        else if ((sfileName[PNT + 2] >= 'A') & (sfileName[PNT + 2] <= 'F')) {
                          StartAddr = (sfileName[PNT + 2] - 55) * 16;
                        }
                        if ((sfileName[PNT + 3] >= '0') & (sfileName[PNT + 3] <= '9')) { // преобразуем третий символ расширения в HEX
                          StartAddr += sfileName[PNT + 3] - '0';
                        }
                        else if ((sfileName[PNT + 3] >= 'A') & (sfileName[PNT + 3] <= 'F')) {
                          StartAddr += (sfileName[PNT + 3] - 55);
                        }
                        if (StartAddr < 0x100) { // если стартовый адрес из расширения определён верно
                          RCrom = PlayAll(PNT, 2, StartAddr); // вызов ПП для формата MON или Бейсика BLOAD
                        }
                        break;
                      case 'A': // первый символ == 'a'|'A'
                        if ( (sfileName[PNT + 2] == 'S') & (sfileName[PNT + 3] == 'M') ) { // второй символ == 's'|'S' и третий == 'm'|'M'
                          RCrom = PlayAll(PNT, 3, 0); // вызов ПП для формата ASM
                        }
                        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;
                  case 12:
                    printtext("Bad speed mark", 0);  // метка скорости в VKT не правильная (больше или меньше границы)
                    break;
                  default:
                    printtext("ERROR!", 0);          // Сообщение об ошибке
                }
                delay(1000);   // ждем 1 с
                printplay();   // вывод надписи "Play file..."
                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(200);           // задержка для накопления инфы...
              noInterrupts();       // запрет прерываний
              unsigned int CWB_temp = CWB;       // сохраняем CWB во временную переменную
              interrupts();         // разрешение прерываний
    
              //====================================
              do { // пока есть данные в буфере
                dataFile.write(BUFF[lowByte(CRB)]); // пишем байт из буфера
                if (CRB % 256 == 0) {  // каждый 256-й байт (1 псевдоблок)
                  lcd.setCursor(7, 1); // устанавливаем курсор в позицию 8 в строке 1
                  lcd.print(CRB);      // количество сохранённых байт
                }
                CRB++;
                if (CWB_temp <= CRB) {// если индекс записи меньше или равен индексу чтения
                  delay(300);     // задержка на ввод данных: макс.скорость 160 * 16 полубит * ~117 байт или ~46 байт на минимальной
                  noInterrupts(); // запрет прерываний
                  CWB_temp = CWB; // сохраняем CWB во временную переменную
                  interrupts();   // разрешение прерываний
                }
              }
              while (CWB_temp >= CRB); // если запись на SD обогнала чтение, значит сигнала нет, выходим из цикла
              //=====================================
              ACSR = 0;     // отключаем обработку прерывания компаратора
              ADCSRA = 135; // включить АЦП, установить делитель =128
              Pik = false;  // сбрасываем флаг "есть сигнал"
    
              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_good) {                   // если часы работают...
                  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 = 256;                         // устанавливаем полупериод на "стандартное" значение
            }
            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;
      }
    }
    
    //=================================================================
    byte getPressedButton()  // функция проверки нажатой кнопки
    {
      int buttonValue = analogRead(0);
      //Serial.println(buttonValue);
      if (buttonValue < 60) return BT_right;
      else if (buttonValue < 180) return BT_up;
      else if (buttonValue < 330) return BT_down;
      else if (buttonValue < 530) return BT_left;
      else if (buttonValue < 800) return BT_select;
      return BT_none;
    }
    
    void printtext(char* text, byte l) {  // Вывод текста на экран в строке l с очисткой строки
      lcd.setCursor(0, l);
      //byte WRB = lcd.print(text);
      for (byte i = lcd.print(text); i < 16; i++) lcd.print(' '); // вывод текста и очистка строки до конца
    }
    
    void printtime() { // вывод времени и даты
      lcd.setCursor(0, 1); // устанавливаем курсор в позицию 0 в строке 1
      if (DT_good) {                              // если часы работают
        char DT[17];
        DateTime now = RTC.now();                 // получаем текущее время
        DT[0] = now.hour() / 10 + '0';            // перевод из целого в символ
        DT[1] = now.hour() % 10 + '0';            // часы
        DT[2] = ':';
        DT[3] = now.minute() / 10 + '0';          // минуты
        DT[4] = now.minute() % 10 + '0';          // минуты
        DT[5] = ':';
        DT[6] = now.second() / 10 + '0';          // секунды
        DT[7] = now.second() % 10 + '0';          // секунды
        DT[8] = ' ';
        DT[9] = now.day() / 10 + '0';             // день
        DT[10] = now.day() % 10 + '0';            // день
        DT[11] = '/';
        DT[12] = now.month() / 10 + '0';          // месяц
        DT[13] = now.month() % 10 + '0';          // месяц
        DT[14] = ' ';
        DT[15] = ' ';
        DT[16] = 0x00;
        lcd.print(DT);                            // выводим время и дату
      }
      else {
        byte WRB = lcd.print(millis() / 1000);      // выводим количество секунд с момента влючения ардуины вместо времени
        for (byte i = WRB; i < 16; i++) lcd.print(' '); // очистка строки после текста
      }
    }
    
    void printplay() {
      if (MLevel != M_dplay_in) {        // если вывод в не формате DOS
        printtext("Play file:", 0);
      }
      else {
        printtext("Play file (DOS):", 0);
      }
    }
    
    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 X = 1; X < currentFile; X++) { // читаем первые файлы до 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) {                     // это не директория?
        printtext(sfileName, 1);        // вывод имени текущего файла
        if (Nbt < 0xFFFF) {             // если размер файла в допустимых пределах
          lcd.setCursor(16, 1);
          byte WRB;
          if (Nbt >= 1000) {            // если размер больше 1000 байт
            WRB = lcd.print(Nbt / 1024);// подсчёт числа символов
            lcd.setCursor(15 - WRB, 1);
            lcd.print(Nbt / 1024);      // вывод размера файла в кБ
            lcd.print('k');
          }
          else {
            WRB = lcd.print(Nbt);       // подсчёт числа символов
            lcd.setCursor(16 - WRB, 1);
            lcd.print(Nbt);             // вывод размера файла в байтах
          }
        }
      }
      else {
        for (byte 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 SwapTpp() // поменять местами значения переменных Tpp и TppSTD
    {
      Tpp = Tpp + TppSTD;     // меняем местами значения Tpp и TppSTD
      TppSTD = Tpp - TppSTD;
      Tpp = Tpp - TppSTD;
      CalcTb();
    }
    
    void WaitBuffer() // ожидание очистки буфера при воспроизведении
    {
      unsigned int CRB_tmp;
      do {                    // Ждём опустошения буфера
        delay(Tpp / 64);      // задержка на вывод 1 байта (~Tpp*16/1000)
        BUFF[lowByte(CWB + 1)] = 0x00; // обнуляем следующий за последним байт -- иногда и он успевает прочитаться...
        noInterrupts();       // запрет прерываний
        CRB_tmp = CRB;        // сохраняем CRB во временную переменную
        interrupts();         // разрешение прерываний
      }
      while (CRB_tmp < CWB);
      Timer1.stop();  // Останавливаем таймер............
    }
    
    byte PlayVKT() // функция вывода файла VKT
    {
      delay(1000);                                     // ждем 1 с
      if (dataFile.open(sfileName, 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 12;      // не правильная метка
        dataFile.seekSet(0);                           // на начало файла
        Nbt = Nbt - 4; // уменьшаем размер данных на метку (4) и предварительно считанные данные (251)
    
        CRB = 0;   // Сбрасываем индексы.
        bBit = 15;
        CalcTb();  // Вычисляем значение задержки на начало байта Tb
    
        // Начинаем наполнять буфер
        for (CWB = 0; CWB <= 250; CWB++) { // первые 251 байт
          BUFF[CWB] = dataFile.read();
        }
        CWB = 251;             // продолжаем с 251-го байта буфера
    
        lcd.setCursor(12, 0);  // выводим на экран общее кол-во "псевдоблоков" по 256 байт
        lcd.print(Nbt >> 8);   // всего данных / 256
    
        Timer1.setPeriod(Tpp); // Выставляем период таймера
        Timer1.start();        // Запускаем таймер............
    
        byte RCV = PlayFile(false); // выводим данные из файла без подсчёта контрольной суммы
        if (RCV == 0) WaitBuffer(); // ожидаем окончания вывода
        return RCV;                 // выходим с кодом
      }
      return 3; // ошибка / нет файла
    }
    
    byte PlayAll(byte pnt, byte FType, unsigned int StartAddr) // функция вывода остальных типов файлов
    // входные данные: длина имени, тип файла, начальный адрес. Тип файла это:
    //    0=CAS и VKT; 1=BAS(C); 2=MON и BAS(B); 3=ASM; 4=DOS
    // Возвращаемые RC:
    //    = 0 -- всё ок
    //    = 1 -- прерывание по кнопке
    //    = 2 -- чтение обогнало запись в буфер
    {
      int i;
      byte rcp;
      //  byte SB;     // выводимый байт
      delay(1000);            // ждем 1 с
      if (dataFile.open(sfileName, O_READ)) { // открываем файл. Открылся?
        CRB = 0;   // Сбрасываем индексы.
        bBit = 15;
    
        // Начинаем наполнять буфер
        for (CWB = 0; CWB <= 255; CWB++) { // первые 256 байт
          if ((FType != 3) | ((CWB / 64 == 1) | (CWB / 64 == 3))) { // если не ASM и это 64-127 или 192-255 байты
            BUFF[CWB] = 0;
          }
          else {                                      // иначе, если ASM и это 0-63 или 128-191 байты
            BUFF[CWB] = 0x55;
          }
        }
        CWB = 256;             // продолжаем с 256-го байта буфера
    
        SwapTpp();             // выставляем стандартную скорость вывода
        // Tpp = 376;             // стандартная скорость вывода
        // Tb = 0;
        Timer1.setPeriod(Tpp); // Выставляем период таймера
        Timer1.start();        // Запускаем таймер............
    
        delay(10);             // немного ждём, чтобы начался вывод первых байт
        ToBUFF(0xE6);          // синхробайт
        switch (FType)
        {
          case 0: // это CAS
            rcp = PlayFile(false);      // выводим данные из файла без подсчёта контрольной суммы
            if (rcp == 0) WaitBuffer(); // ожидаем окончания вывода
            SwapTpp();                  // восстанавливаем Tpp
            return rcp;                 // выходим с кодом
          case 1: // это BAS(C)
            for (i = 0; i <= 3; i++) ToBUFF(0xD3); // 4x 0xD3
            break;
          case 2: // это MON или BAS(B)
            for (i = 0; i <= 3; i++) ToBUFF(0xD2); // 4x 0xD2
            break;
          case 3: // это ASM
            for (i = 0; i <= 3; i++) ToBUFF(0xE6); // 4x 0xE6
            break;
          case 4: // это DOS
            ToBUFF(0x01);
            ToBUFF(0x00);              // 0x0100 -- начальный адрес (всегда)
            ToBUFF(highByte(Nbt - 1) + 1); // старший байт конечного адреса (нач.адрес+длинна данных)
            ToBUFF(0xFF);              // младший байт конечного адреса (всегда)
            rcp = PlayFile(true);      // выводим байты из файла с подсчётом КС
            if (rcp > 0) {
              SwapTpp();               // восстанавливаем Tpp
              return rcp;              // выходим с кодом
            }
            for (i = lowByte(Nbt - 1); i < 0xFF; i++) ToBUFF(0x00); // дополняем, если надо, нулями
            ToBUFF(lowByte(CSF));      // выводим младший байт КС
        }
    
        byte SB;     // выводимый байт
        for (i = 0; i < pnt; i++) {  // вывод имени файла без расширения, DOS поддерживаются только 'A'-'Z','0'-'9'
          if (sfileName[i] != 0x7E) {// не тильда
            SB = sfileName[i];       // берём символ символ
          }
          else {                     // тильда -- часто встречабщийся символ, остальные можно переименовать вручную
            if (FType != 4) {
              SB = '_';              // меняем тильду на подчёркивание
            }
            else SB = '0';           // для DOS меняем тильду на нолик, иначе при загрузке будет ошибка
          }
          CSF += SB;                 // считаем КС
          ToBUFF(SB);                // заносим в буфер очередную букву имени
        }
    
        // это DOS
        if (FType == 4) {
          for (i = pnt; i < 8; i++) { // дополняем имя пробелами до 8 байт
            ToBUFF(0x20);
            CSF += 0x20;
          }
          // выводим расширение файла
          if ( (sfileName[pnt + 1] == 'R') & (sfileName[pnt + 2] == 'O') & (sfileName[pnt + 3] == 'M') ) {
            SB = 'C';            // меняем ROM на СОМ
          }
          else SB = sfileName[pnt + 1];
          CSF += SB;             // считаем КС
          ToBUFF(SB);            // заносим в буфер первую букву расширения
    
          for (i = 2; i < 4; i++) {
            SB = sfileName[pnt + i];
            CSF += SB;           // считаем КС
            ToBUFF(SB);          // заносим в буфер очередную букву расширения
          }
          ToBUFF(lowByte(CSF));  // выводим младший байт КС (данные + имя файла)
          WaitBuffer();          // ожидаем окончания вывода
          SwapTpp();             // восстанавливаем Tpp
          return 0;              // выходим с кодом 0
        }
    
        for (i = 0; i <= 2; i++) ToBUFF(0x00); // 3x 0x00
    
        // это BAS(C)
        if (FType == 1) {
          for (i = 0; i <= 767; i++) ToBUFF(0x55); // 768x 0x55
          ToBUFF(0xE6);                        // синхробайт
          for (i = 0; i <= 2; i++) ToBUFF(0xD3); // 3x 0xD3
          ToBUFF(0x00);                        // 1x 0x00
          rcp = PlayFile(true);                // выводим данные из файла с подсчётом КС
          if (rcp == 0) {
            ToBUFF(lowByte(CSF));              // младший байт КС
            ToBUFF(highByte(CSF));             // старший байт КС
            WaitBuffer();                      // ожидаем окончания вывода
          }
          SwapTpp();                           // восстанавливаем Tpp
          return rcp;                          // выходим с кодом
        }
    
        // это MON, BAS(B) или ASM
        for (i = 0; i <= 255; i++) ToBUFF(0x00); // 256x 0x00
        ToBUFF(0xE6);                          // синхробайт
    
        if (FType == 2) {  // это MON или BAS(B)
          ToBUFF(lowByte(StartAddr));            // старший байт адреса начала записи
          ToBUFF(0x00);                          // младший байт = 0
          ToBUFF(lowByte(StartAddr + (Nbt - 1) / 256)); // старший байт адреса конца записи
          ToBUFF(lowByte(Nbt - 1));              // младший байт адреса конца записи
          rcp = PlayFile(true);                  // выводим данные из файла с подсчётом КС
          if (rcp == 0) {
            ToBUFF(lowByte(CSF));                // младший байт КС
            WaitBuffer();                        // ожидаем окончания вывода
          }
          SwapTpp();                             // восстанавливаем Tpp
          return rcp;                            // выходим с кодом
        }
    
        // остался только ASM
        ToBUFF(lowByte(Nbt));                    // младший байт длинны записи
        ToBUFF(highByte(Nbt));                   // старший байт длинны записи
        rcp = PlayFile(true);                    // выводим данные из файла с подсчётом КС
        if (rcp == 0) {
          ToBUFF(0xFF);                          // дополняем FFh в конце
          ToBUFF(lowByte(CSF));                  // младший байт КС
          ToBUFF(highByte(CSF));                 // старший байт КС
          WaitBuffer();                          // ожидаем окончания вывода
        }
        SwapTpp();                               // восстанавливаем Tpp
        return rcp;                              // выходим с кодом
      }
      return 3; // ошибка / нет файла
    }
    
    byte PlayFile(boolean CSFp) // Функция вывода битов файла
    // CSFp признак необходимости подсчёта контрольной суммы файла
    {
      byte SB;
      unsigned int CWB_tmp;
      CSF = 0; // обнуляем контрольную сумму
      for (unsigned int i = 0; i < Nbt; i++) { // данные из файла
        SB = dataFile.read();
        ToBUFF(SB);
        if (CSFp) CSF += SB;
    
        if ((Nbt - i) % 256 == 0) { // если остаток кратен 256
          lcd.setCursor(12, 0);  // выводим на экран кол-во оставшихся "псевдоблоков" по 256 байт
          lcd.print((Nbt - i) >> 8); // остаток данных / 256
          lcd.print(' ');
        }
        if (getPressedButton() != BT_none) { // кнопка нажата?
          Timer1.stop();       // Останавливаем таймер
          dataFile.close();    // закрываем файл
          SwapTpp();           // восстанавливаем Tpp
          return 1;            // выход из ПП с ошибкой 1.
        }
        noInterrupts();               // запрет прерываний
        CWB_tmp = CRB;                // сохраняем CRB во временную переменную
        interrupts();                 // разрешение прерываний
        if (CWB_tmp > CWB) {   // проверка -- не обогнало ли воспроизведение запись в буфер?
          Timer1.stop();       // Останавливаем таймер
          dataFile.close();    // закрываем файл
          SwapTpp();           // восстанавливаем Tpp
          return 2;            // выход из ПП с ошибкой 2.
        }
      }
      dataFile.close();        // закрываем файл
      return 0;
    }
    
    byte PlayROM(int pnt, byte BLs) // функция вывода файла ROM
    {
      delay(1000);           // ждем 1 с
    
      if (dataFile.open(sfileName, O_READ)) { // открываем файл. Открылся?
        byte BLe = Nbt / 256; // всего блоков
        byte BLt;             // осталось блоков
        byte Nst;             // номер строки
        byte St;              // выводимый байт
    
        byte CSz = 0x00;      // контрольная сумма заголовка
        byte CSs = 0x00;      // контрольная сумма строки
        byte i;
        byte j;
        unsigned int CRB_tmp;
    
        if (Nbt % 256 != 0) BLe++; // корректировка количества блоков, если размер файла не кратен 256
    
        // Заголовок блока
        byte SB[25];
        SB[0] = 0x4E;
        SB[1] = 0x4F;
        SB[2] = 0x44;
        SB[3] = 0x49;
        SB[4] = 0x53;
        SB[5] = 0x43;
        SB[6] = 0x30;
        SB[7] = 0x30;  // NODISK00
        SB[22] = 0x52;
        SB[23] = 0x4F;
        SB[24] = 0x4D; // расширение "ROM"
    
        for (i = 0; i <= 7; i++) {           // заносим в SB имя файла
          if (i < pnt) {                     // имя ещё не закончилось?
            if (sfileName[i] != 0x7E) {      // не тильда
              SB[i + 14] = sfileName[i];     // заносим символ
            }
            else {
              SB[i + 14] = '_';              // меняем тильду на подчёркивание, иначе это будет русская "Ч"
            }
          }
          else SB[i + 14] = 0x20;            // дополняем пробелами
        }
    
        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 <= 24; j++) {        // заголовок блока
            CSz += SB[j];
            ToBUFF(SB[j]);
          }
          ToBUFF(0x00);
          ToBUFF(0x00);
          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.
              }
              noInterrupts();               // запрет прерываний
              CRB_tmp = CRB;                // сохраняем CRB во временную переменную
              interrupts();                 // разрешение прерываний
              if (CRB_tmp > CWB) {      // проверка -- не обогнало ли чтение запись?
                Timer1.stop();          // Останавливаем таймер
                dataFile.close();       // закрываем файл
                return 2;               // выход из ПП с ошибкой 2.
              }
            }
            ToBUFF(CSs);                // контр.сумма строки
          }
        }
        dataFile.close(); // закрываем файл
    
        for (j = 0; j <= 31; j++) ToBUFF(0x00); // 00h*32 -- завершение вывода программы (?)
        WaitBuffer();  // ожидаем окончания вывода
        return 0;      // выход из ПП с кодом 0.
      }
      return 3; // ошибка / нет файла
    }
    
    void ToBUFF(byte SBb) {     // Подпрограмма записи байта в буфер
      unsigned int CRB_tmp;
      noInterrupts();               // запрет прерываний
      CRB_tmp = CRB;                // сохраняем CRB во временную переменную
      interrupts();                 // разрешение прерываний
      if (CWB > (CRB_tmp + 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++;
        Timer1.setPeriod(Tpp + Tb); // Выставляем увеличенный период таймера (начало байта)
      }
    }
    
    ISR(ANALOG_COMP_vect) // ПП обработки прерывания компаратора -- определяет и заносит полученные биты в буфер
    {
      unsigned long iMicros = micros();
      unsigned long PPeriod = iMicros - iMicros_old;
      iMicros_old = iMicros;
      if (PPeriod < 65000) {  // началось...
        if (bBit < 16) {      // если это не последний полубит
          if (PPeriod <= (PPeriod_sred[PP] >> 7)) { // sred/128, если тек. полупериод короткий
            if (CWB <= 255) {   // расчёт среднего значения, если меньше 256 байт считано
              PPeriod_sred[PP] = (PPeriod_sred[PP] * CWB + PPeriod * 192) / (CWB + 1); // "среднее значение" = 1,5*128 от короткого полупериода
            }
            if (bBit & 1) { // нечётный полубит
              A = (A << 1) + B; // заносим бит
            }               // нечётный -- пропускаем
          }
          else { // получен длинный полупериод
            B ^= 1;         // инвертируем бит
            A = (A << 1) + B; // заносим бит
            bBit--;         // уменьшаем счётчик полубитов
          }
        }
        else { // если последний полубит, вводим корректировку на задержку между байтами
          // граница будет =([1,5*Tpp*128]*25/19)/128 =~[1,5*Tpp*128]/97 =~1,98*Tpp
          if (PPeriod > (PPeriod_sred[PP] / 97)) { // если тек. полупериод длинный
            B ^= 1;         // инвертируем бит
            A = (A << 1) + B; // заносим бит
            bBit--;         // уменьшаем счётчик полубитов
          }
        }
        // корректировка счётчиков
        PP ^= 1; // инвертируем бит признака чётности полупериода
        if (bBit > 1) {
          bBit--;                  // счётчик полубитов -1
        }
        else {
          BUFF[lowByte(CWB)] = A;  // заносим байт в буфер
          BUFF[lowByte(++CWB)] = 0;// счётчик байтов +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;             // есть сигнал!
      }
    }
    [свернуть]
    Архив со скетчем и библиотеками: RW-player_11.5.7z

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

    P.S. Заметил интересную особенность моего Вектора: если загружать данные "на холодную", т.е. после долгого его нахождения в выключеном состоянии, то на скоростях загрузки быстрее 272-280 мкс идут сплошные ошибки, а после "прогрева" хотя бы минут 10-15, скорость воспроизведения можно легко повысить до 184 мкс... Интересно, что может так тормозить в холодном состоянии?

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

    По умолчанию

    Цитата Сообщение от Improver Посмотреть сообщение
    ...
    P.S. Заметил интересную особенность моего Вектора: если загружать данные "на холодную", т.е. после долгого его нахождения в выключеном состоянии, то на скоростях загрузки быстрее 272-280 мкс идут сплошные ошибки, а после "прогрева" хотя бы минут 10-15, скорость воспроизведения можно легко повысить до 184 мкс... Интересно, что может так тормозить в холодном состоянии?
    Точно определить можно только "методом тыка".
    Охлаждаешь системку, и локально нагреваешь паяльником или феном. Начинать с компаратора и его обвязки, и далее по цепочке.
    У меня напарник, при таком ремонте, в морозилку системку засовывал, вычислял полудохлые (плывущие) ОЗУшки.
    Последний раз редактировалось KTSerg; 10.12.2018 в 10:45.

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

    По умолчанию

    Цитата Сообщение от KTSerg Посмотреть сообщение
    У меня напарник, при таком ремонте, в морозилку системку засовывал, вычислял полудохлые (плывущие) ОЗУшки.
    Тут в ветке неподалеку, одна из про ПК-11/16, программатор в морозилке является стандартной частью техпроцесса
    Больше игр нет

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

    По умолчанию

    Цитата Сообщение от KTSerg Посмотреть сообщение
    Охлаждаешь системку, и локально нагреваешь паяльником или феном. Начинать с компаратора и его обвязки, и далее по цепочке.
    Да, наверно попробую так сделать...

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

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

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

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

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

Ваши права

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