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

User Tag List

Страница 16 из 16 ПерваяПервая ... 1213141516
Показано с 151 по 154 из 154

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

  1. #151
    Guru Аватар для svofski
    Регистрация
    20.06.2007
    Адрес
    С.-Петербург
    Сообщений
    2,103
    Благодарностей: 713
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

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

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

  3. #152
    Member Аватар для Improver
    Регистрация
    06.02.2018
    Адрес
    г. Волгоград
    Сообщений
    133
    Благодарностей: 32
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

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

  4. #153
    Member Аватар для Improver
    Регистрация
    06.02.2018
    Адрес
    г. Волгоград
    Сообщений
    133
    Благодарностей: 32
    Mentioned
    0 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. Эти 2 пользователя(ей) поблагодарили Improver за это полезное сообщение:
    artyr_n (17.06.2018), svofski (15.06.2018)

  6. #154
    Member Аватар для Improver
    Регистрация
    06.02.2018
    Адрес
    г. Волгоград
    Сообщений
    133
    Благодарностей: 32
    Mentioned
    0 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

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

  7. Этот пользователь поблагодарил Improver за это полезное сообщение:
    svofski (10.07.2018)

Страница 16 из 16 ПерваяПервая ... 1213141516

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

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

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

Похожие темы

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

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

Ваши права

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