Очередная версия готова, вот:

Версия на таймере1


Для скетча требуется библиотека TimerOne и, также, как и для предыдущей версии, библиотека SdFat из проекта TZXDuino.
Код:
/*
 * SD-картридер подключяется к выводам ардуино:
 ** MOSI  - D11
 ** MISO  - D12
 ** CLK   - D13
 ** CS    - D10
 *
 *  Выход - D3
 */
#include <SdFat.h>
#include <TimerOne.h>

SdFat sd;
SdFile romFile;

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

// Заголовок
byte SB[27] = { 
  0x4E, 0x4F, 0x44, 0x49, 0x53, 0x43, 0x30, 0x30, // NODISK00
  0x31, 0x34, 0x30, 0x32, 0x31, 0x38,             // дата: 140218
  0x74, 0x65, 0x73, 0x74, 0x74, 0x70, 0x20, 0x20, // testtp.....
  0x20, 0x20, 0x20, 0x00, 0x00 };

int p = 3; // номер пина, на который будет вывод сигнала

const int Tpp = 256; // Длительность задержки сигнала в микросекундах (один полупериод)

void setup() //процедура setup
{
  pinMode(p, OUTPUT);  // объявляем пин как выход
  pinMode(10, OUTPUT); // CS для SD-картридера

  Timer1.initialize(Tpp);               // инициализировать timer1, и установить период Tpp мкс.
  Timer1.attachInterrupt(SendHalfBit);  // прикрепить SendHalfBit(), как обработчик прерывания по переполнению таймера
  Timer1.stop();

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

void loop() //процедура loop
{
  tone(p, 500, 100); //включаем на 500 Гц на 100 мс
  delay(1000);       //ждем 1 с

  if (romFile.open("cybernoi.rom",O_READ)) { // открываем файл. Открылся?
    byte BLs = 0x01;    // начальный блок
    unsigned int Nbt = romFile.fileSize();  // всего байт
    byte BLe = Nbt/256; // всего блоков
    byte BLt;           // осталось блоков
    byte Nst;           // номер строки
    byte St;            // выводимый байт

    byte CSz = 0x00;    // контрольная сумма заголовка
    byte CSs = 0x00;    // контрольная сумма строки
    byte i;
    byte j;

    if (Nbt%256 != 0){  // корректировка количества блоков, если размер файла не кратен 256
      BLe++;
    } 

    // Начинаем наполнять буфер
    for (i=0; i<=3; i++){           // преамбула (4*(00H*25+55H*25))
      for (j=0; j<=24; j++){
        BUFF[CWB] = 0x00;
        CWB++;
      }
      for (j=0; j<=24; j++){
        BUFF[CWB] = 0x55;
        CWB++;
      }
    }  

    Timer1.start();    // Запускаем таймер............

    for (BLt=BLe; BLt>=1; BLt--){   // Вывод блоков данных в цикле
      CSz = BLs;
      CSz += BLe;
      CSz += BLt;

      for (j=0; j<=15; j++){        // 00h*16
        ToBUFF(0x00);
      }
      for (j=0; j<=3; j++){         // 55h*4
        ToBUFF(0x55);
      }
      ToBUFF(0xE6);                 // E6h*1
      for (j=0; j<=3; j++){         // 00h*4
        ToBUFF(0x00);
      }

      for (j=0; j<=26; j++){        // заголовок блока
        CSz += SB[j];
        ToBUFF(SB[j]);
      }
      ToBUFF(BLs);                  // начальный блок
      ToBUFF(BLe);                  // конечный блок
      ToBUFF(BLt);                  // осталось блоков
      ToBUFF(CSz);                  // контр.сумма заголовка

      for (Nst=0x80; Nst<=0x87; Nst++){   // вывод строк (8 шт.)
        for (j=0; j<=3; j++){       // 00h*4
          ToBUFF(0x00);
        }
        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;
        }
        ToBUFF(CSs);                // контр.сумма строки
      }  
    }
    // close the file:
    romFile.close();

    for (j=0; j<=15; j++){          // 00h*16 -- завершение вывода программы (?)
      ToBUFF(0x00);
    }
  }
  else {      
    tone(p, 200, 300); // нет файла -- включаем на 200 Гц на 300 мс
  }
  while (CRB < CWB) {  // Ждём опустошения буфера
    delay(Tpp);
  }
  Timer1.stop();  // Останавливаем таймер............
  CRB = 0;        // Сбрасываем индексы.
  CWB = 0;
  bBit = 15;
  delay(15000);   // ждем 15 с
}

void ToBUFF(byte SBb){     // Подпрограмма записи байта в буфер
  while (CWB > CRB + 255) {// Если позиция записи больше, чем позиция чтения + размер буфера - 1
    delay(Tpp);            // Задержка (Tpp*1000 мкс = Tpp мс)
  }
  BUFF[lowByte(CWB)] = SBb;
  CWB++;
}

void SendHalfBit() {  // Подпрограмма вывода полубита по циклу таймера
  byte Pd=PORTD;
  if (bBit & 1){      // проверка индекса полубитов на чётность
//    if ((bitRead(Pd, p))^(bitRead(BUFF[lowByte(CRB)], bBit/2))){ // Старый вариант...
    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--;
  }
  else{
    bBit = 15;
    CRB++;
  }
}
[свернуть]

В итоге, решил всё-таки сделать на одном закольцованном буфере, а не на двух типа "пинг-понг" -- всё ради экономии памяти... Размер буфера 256 байт, что при таймере в 256 мкс даёт его ёмкость чуть больше секунды. В дебужной версии запись в буфер "догоняет" чтение и уходит в паузу примерно раз в секунду-полторы. На том же "Киберноиде" ни одного провала или ошибки в выводимом сигнале не обнаружено, даже при сокращении времени таймера до 160 мкс, что уже можно считать успехом.
Кстати, по таймеру -- если я правильно посчитал, то при 256 мкс "несущая" частота должна быть примерно 2 кГц, а сколько максимум может вытянуть Вектор?

Ещё такой ньюанс -- тут (в конце) пишут, что "при обращении к общим переменным прерывания, как правило, должны быть отключены", но я пока сделал без отключения -- посмотрим, если будет работать без глюков, то может и так оставим...

Скетч со всеми нужными библиотеками в архиве: TestTP_fromSD_4.zip
Пример выводимой wav-ки: TestWAV35.7z

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

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

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

Цитата Сообщение от svofski Посмотреть сообщение
я бы плюнул и оставил эту библиотеку как есть, раз она удобна в использовании и проверена.
Так и сделал. :-)