В прошедшие выходные получил посылку с известного китайского сайта, заказывал "Data Logger Shield" с ардуиной уно -- этот шилд как раз подходит для ROM-плеера, есть место, куда можно впаять схему выхода и разъём, плюс бонусом имеет на борту часы. Вот что сейчас имеем:

Фотки

Всё в сборе:
Нажмите на изображение для увеличения. 

Название:	IMG_20180317_151835~.jpg 
Просмотров:	326 
Размер:	52.5 Кб 
ID:	64678

И по отдельности:
Нажмите на изображение для увеличения. 

Название:	IMG_20180317_151718~.jpg 
Просмотров:	343 
Размер:	47.3 Кб 
ID:	64679

Бело-розовый проводок с разъёмом на фото -- это прямой выход с контакта D3, для опытов...
[свернуть]

На всём этом скетч третьей версии загрузился и заработал без проблем, Вектор грузит всё, как и ранее. Но выявился небольшой глючёк -- как оказалось вывод D10 ардуины также задействован платой "LCD Keypad Shield" для управления яркостью подсветки экрана, и во время обращения к СД-карте подсветка становится немного темнее. :-( Решения тут два: первое -- откусить или выпаять соответствующий штырь контакта на шилде с экраном и кнопками, и второе -- считать это фичей, показывающей таким образом то, что происходит обращение к карте памяти. Склоняюсь ко второму варианту. :-)

И раз уж появился бонус в виде часов, решил его задействовать, вот новая версия скетча:

ROM-player_4

Для работы требуются библиотеки TimerOne, SdFat, а также стандартные LiquidCrystal, Wire и RTClib.
Код:
/*
 Вариант на "Data Logger Shield" и "LCD Keypad Shield"
 (на первом есть RTC -- используем для показа времени)

 Выход - D3

 "Data Logger Shield V1.0":
  SD-картридер подключен к выводам ардуино:
 * MOSI  - D11
 * MISO  - D12
 * CLK   - D13
 * CS    - D10

 "LCD Keypad Shield":
 Подключение экрана 1602А:
 * LCD RS pin     - D8
 * LCD Enable pin - D9
 * LCD D4 pin     - D4
 * LCD D5 pin     - D5
 * LCD D6 pin     - D6
 * LCD D7 pin     - D7
 * LCD R/W pin    - GND
 * LCD 5V pin     - 5V
 * Кнопки         - A0
 */

// include the library code:
#include <LiquidCrystal.h>
#include <SdFat.h>
#include <TimerOne.h>
#include <Wire.h>
#include "RTClib.h"

RTC_DS1307 RTC;

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

SdFat sd;
SdFile romFile;

#define filenameLength    100      // максимальная длина имени файла
char fileName[filenameLength + 1]; // имя текущего файла
char sfileName[13];                // короткое имя текущего файла
int currentFile = 1;               // текущая позиция в директории
int maxFile = 0;                   // всего позиций в лиректории (файлов и поддиректорий)
byte isDir = 0;                    // признак того, что текущая позиция -- это директория

byte BLs = 0x01;    // начальный блок
unsigned int Nbt;   // размер файла, байт

volatile byte BUFF[256];       // буфер данных
volatile unsigned int CRB = 0; // индекс чтения из буфера
volatile byte bBit = 15;       // индекс читаемого полубита
unsigned int CWB = 0;          // индекс записи в буфер
unsigned int CRB_temp = 0;     // временная переменная для CRB

// Заголовок
byte SB[27] = { 
  0x4E, 0x4F, 0x44, 0x49, 0x53, 0x43, 0x30, 0x30, // NODISK00
  0x31, 0x34, 0x30, 0x32, 0x31, 0x38,             // дата: 140218
  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // имя программы
  0x20, 0x20, 0x20, 0x00, 0x00 };

char DT[15] = {
  '0', '0', ':', '0', '0', ':', '0', '0', ' ',
  '0', '0', '/', '0', '0', 0x00 };

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;

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();

  Wire.begin();
  RTC.begin();
  if (! RTC.isrunning()) {
    printtext("RTC is NOT run!",0);
    // following line sets the RTC to the date & time this sketch was compiled
    RTC.adjust(DateTime(__DATE__, __TIME__));
    delay(1000);       // ждем 1 с
  }
  //RTC.adjust(DateTime(__DATE__, __TIME__));

  while (!sd.begin(10,SPI_FULL_SPEED)){ // SD-карта готова?
    printtext("SD-card failed!",0);
    tone(p, 200, 100); // нет -- включаем на 200 Гц на 100 мс
    delay(3000);       // ждем 3 с
  }
  printtext("SD-card is OK.",0);

  sd.chdir();            // устанавливаем корневую директорию SD
  getMaxFile();          // получаем количество файлов в директории
  seekFile();            // переходим к первому файлу в директории
}

void loop() {
  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';            // месяц
  printtext(DT,1);                          // выводим время и дату

  int button = getPressedButton(); // какая кнопка нажата?
  while(getPressedButton()!=BT_none) { // Ждём, пока не будет отпущена кнопка
    delay(50); 
  }
  switch (button)
  {
  case BT_right: // вход в директорию, или запуск файла на воспроизведение
    if(isDir==1) { //Если это директория, то переход в неё
      sd.chdir(fileName, true);
      getMaxFile();
      currentFile=1;
    } 
    else {         // если не директория -- пробуем воспроизвести файл
      if(romFile.cwd()->exists(sfileName)) {
        printtext("Not ROM-file",1);
        if (Nbt != 0xFFFF) { // проверяем размер файла
          for (int i=0;12;i++){ // цикл по имени файла
            if (sfileName[i]=='.'){                 // ищем точку в имени файла
              if ((sfileName[i+1]|0x20)=='r') {     // проверяем следующий символ (расширения) == 'r'|'R'
                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; // с нулевого блока
                  }
                  if ((sfileName[i+3]|0x20)=='m') { // == 'm'|'M'
                    printtext("Playing...",1);
                    int RCrom = PlayROM(sfileName, i);// Передаём короткое имя файла и позицию точки в качестве параметров
                    switch (RCrom)                    // Проверяем код возврата
                    {
                    case 0:
                      printtext("Done.",1);           // Всё закончилось успешно.
                      break;
                    case 1:
                      printtext("Stopped",1);         // Сообщение об остановке
                      while(getPressedButton()!=BT_none) { // Ждём, пока не будет отпущена кнопка
                        delay(50); 
                      }
                      break;
                    default:  
                      printtext("ERROR!",1);          // Сообщение об ошибке
                    }
                    digitalWrite(p, LOW);
                  }
                }
              }
              break;
            }
          }
        }
        else {
          printtext("File is too big",1);
        }
      } 
      else {
        printtext("No File Selected",1);
      }
      delay(1000);           // ждем 1 с
    }
    break;
  case BT_left: // возврат в корневую директорию, ремарка ниже:
    //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);
    getMaxFile();
    currentFile=1;
    break;
  case BT_up: // вверх по файлам в директории
    currentFile--;
    if(currentFile<1) {
      getMaxFile();
      currentFile = maxFile;
    }
    break;
  case BT_down: // вниз по файлам в директории
    currentFile++;
    if(currentFile>maxFile) { 
      currentFile=1; 
    }
    break;
  case BT_select: // выход в настройки...
    printtext("Setup",0);
    printtext("Period(mks):",1);
    do {
      delay(300);
      button = getPressedButton(); // какая кнопка нажата?
      switch (button)
      {
      case BT_up:   
        if (Tpp<400) Tpp += 8;
        break;
      case BT_down:   
        if (Tpp>160) Tpp = Tpp - 8;
        break;
      }
      if (Tpp <= 264) { // Выставляем значение задержки на начало байта Tb
        Tb = 16;                        // для полупериода от 248 до 264
        if (Tpp <= 240) Tb = 264 - Tpp; // для полупериода меньше или равном 240 
      }
      else Tb = 0;                      // для полупериода больше 264
      lcd.setCursor(12,1);
      lcd.print(Tpp);
    } 
    while(button!=BT_select); // Цикл пока не будет снова нажата кнопка select
    while(getPressedButton()!=BT_none) { // Ждём, пока не будет отпущена кнопка
      delay(50); 
    }
    break;
  case BT_none: // ничего не нажато
    delay(100); 
    break;
  }
  if (button != BT_none) seekFile();  
}

//=================================================================
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 getMaxFile() { // считаем файлы в текущей директории и сохраняем в maxFile
  romFile.cwd()->rewind();
  maxFile=0;
  while(romFile.openNext(romFile.cwd(),O_READ)) {
    romFile.close();
    maxFile++;
  }
}

void seekFile() { // переход на позицию в директории, сохранение имени файла и показ его на экране
  romFile.cwd()->rewind();
  for(int i=1;i<currentFile;i++) { // читаем первые файлы до currentFile
    romFile.openNext(romFile.cwd(),O_READ);
    romFile.close();
  }
  romFile.openNext(romFile.cwd(),O_READ); // читаем данные текущего файла
  romFile.getName(fileName,filenameLength);
  romFile.getSFN(sfileName);
  isDir = romFile.isDir();
  if (romFile.fileSize()<=49152) { // проверка размера файла
    Nbt = romFile.fileSize();      // размер файла ОК
  }
  else {
    Nbt = 0xFFFF;                  // слишком большой для загрузки
  }
  romFile.close();
  printtext(sfileName,0);       // вывод имени текущего файла
  if (isDir==1) lcd.print('>'); // если это директория, добавляем в конце символ '>'
}

int PlayROM(char FName[], int pnt) // функция вывода файла
{
  delay(1000);           // ждем 1 с

  if (romFile.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;
    romFile.dirEntry(&d);                    // Считываем дату файла
    uint16_t AAA = FAT_DAY(d.lastWriteDate); // Сохраняем дату файла в заголовке -- день
    SB[8] = (AAA%100)/10 + '0';              // перевод из целого в символ
    SB[9] = AAA%10 + '0';
    AAA = FAT_MONTH(d.lastWriteDate);        // месяц
    SB[10] = (AAA%100)/10 + '0';
    SB[11] = AAA%10 + '0';
    AAA = FAT_YEAR(d.lastWriteDate);         // последние две цифры года
    SB[12] = (AAA%100)/10 + '0';
    SB[13] = AAA%10 + '0';

    // Начинаем наполнять буфер
    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, 1);              // выводим на экран кол-во оставшихся блоков
      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 = romFile.read();    // читаем очередной байт из файла
            Nbt--;
          }
          else {                    // нет -- дополняем нулями
            St = 0x00;
          }
          ToBUFF(St);               // передаём считанный байт
          CSs += St;
          if (getPressedButton()!=BT_none) { // кнопка нажата?
            Timer1.stop();          // Останавливаем таймер
            CRB = 0;                // Сбрасываем индексы.
            CWB = 0;
            bBit = 15;
            romFile.close();        // закрываем файл
            return 1;               // выход из ПП с ошибкой 1.
          }
          if (CRB_temp > CWB) {     // проверка -- не обогнало ли чтение запись?
            Timer1.stop();          // Останавливаем таймер
            CRB = 0;                // Сбрасываем индексы.
            CWB = 0;
            bBit = 15;
            romFile.close();        // закрываем файл
            return 2;               // выход из ПП с ошибкой 2.
          }
        }
        ToBUFF(CSs);                // контр.сумма строки
      }  
    }
    romFile.close(); // закрываем файл

    for (j=0; j<=31; j++) ToBUFF(0x00);// 00h*32 -- завершение вывода программы (?)
  }
  else tone(p, 200, 300); // нет файла -- включаем на 200 Гц на 300 мс

  do{                     // Ждём опустошения буфера
    delay(Tpp/64);        // задержка на вывод 1 байта (~Tpp*16/1000)
    noInterrupts();       // запрет прерываний
    CRB_temp = CRB;       // сохраняем CRB во временную переменную
    interrupts();         // разрешение прерываний
  }
  while (CRB_temp < CWB);

  Timer1.stop();  // Останавливаем таймер............
  CRB = 0;        // Сбрасываем индексы.
  CWB = 0;
  bBit = 15;
  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); // Выставляем увеличенный период таймера (начало байта)
  }
}
[свернуть]

Изменения по отношению к предыдущей версии минимальны: добавлены часы вместо отсчёта секунд от включения и немного оптимизированы некоторые алгоритмы (не главные).
Исходники с библиотеками в одном архиве: ROM-player_4.7z

Теперь, имея "генератор сигнала Вектора", можно двигаться дальше -- на второй ардуинке делать запись и сохранение данных... :-)