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

User Tag List

Страница 15 из 15 ПерваяПервая ... 1112131415
Показано с 141 по 147 из 147

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

  1. #141
    Activist
    Регистрация
    22.02.2014
    Адрес
    г. Курган
    Сообщений
    413
    Благодарностей: 140
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

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

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

    По умолчанию

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

    RW-player_6

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

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

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

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

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

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

    По умолчанию

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

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

    По умолчанию

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

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

    По умолчанию

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

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

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

    По умолчанию

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

    RW-player_7

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

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

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

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

  7. Эти 2 пользователя(ей) поблагодарили Improver за это полезное сообщение:
    artyr_n (07.04.2018), svofski (06.04.2018)

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

    По умолчанию

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

    Схема




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

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

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

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

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

  9. Эти 2 пользователя(ей) поблагодарили Improver за это полезное сообщение:
    artyr_n (13.04.2018), svofski (13.04.2018)

Страница 15 из 15 ПерваяПервая ... 1112131415

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

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

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

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

Ваши права

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