а елси весь файл в буфер зачитать а потом уже транслировать?? емкости ардуны не хватит?? а если Мегу взять ?
а елси весь файл в буфер зачитать а потом уже транслировать?? емкости ардуны не хватит?? а если Мегу взять ?
Profi v3.2 -=- Speccy2010,r2
Интересная идея. А если сделать один буфер? Допустим, так:
- Основная программа подготавливает данные и сливает их в массив, допустим, на 256 байт. И есть две переменных, первая -- индекс, откуда сейчас читаем, а вторая -- куда пишем.
- Как буфер заполнится, запускаем таймер на время, равное периоду вывода 1 байта (8 бит). По прерыванию с него выводим байт с позиции чтения, делаем индекс чтения +1.
- Далее основная программа заполняет с максимальной скоростью буфер до состояния "позиция записи" = ("позиция чтения" - 1), а по таймеру идёт непрерывный вывод данных из буфера. (Естественно, при достижении конца буфера запись циклически начнётся с его начала.)
Нормально так будет?
Не, там оперативки всего-то 2 Кб, на меге -- 8 Кб... Ну если только во флеш переносить.
Другими словами:
- Буфер закольцован.
- Программа вызываемая из прерывания, занимается отправкой с текущей позиции чтения из буфера.
- Основная программа следит за разностью между индексами чтения и записи в буфер, и подчитывает, заполняя буфер.
Кольцо удобно когда поставщик и потребитель неизвестно в каком темпе работают: буфер последовательного канала итп. А тут все известно, чтение происходит порциями фиксированного размера в буфер, поэтому пинг-понг проще:
* не надо считать никаких голов и хвостов
* чтение всегда линейное, то есть можно пользоваться стандартными библиотеками для чтения прямо в буфер
* просто гарантировать то, что запись не происходит туда, откуда еще не все прочитано
Если очень хочется именно кольцо, то достаточно представить себе пинг-понг буфер кольцом длиной 2, где каждый элемент — это буфер
- - - Добавлено - - -
Так нельзя делать. Если обработчик все время будет выводить байт, то как только он выйдет, он тут же будет вызван снова, потому что пришел черед следующего байта. Получается, что наша программа просто переехала в прерывание, а времени на чтение как не было, так и нет.
Смысл прерывания в том, что основное свободное время, которое у нас есть, находится между полубитами. Время вывода полубитов так же является критичным для задачи. Поэтому прерывание выводит только один полубит. Это происходит по таймеру, поэтому мы знаем, что вывод работает максимально точно. После вывода полубита обработчик обязательно должен вернуться, чтобы во-первых иметь возможность быть вызванным снова вовремя и, во-вторых, чтобы дать время основному циклу для поддержания заполненности буферов.
Больше игр нет
Ну, если задержки вызывает именно чтение, то не обращать на них внимание. Читать стандартными функциями по 32 или 256 байт (как нравится) в буфер, до начала вывода преамбулы (строки 32 Байт) и не обращать внимание на паузы, т.к. Вектор их (в этом месте) спокойно пережуёт.
svofski, пожалуй, Вы правы -- надо делать таймер на полупериод. Это получается, что чётные разы его срабатывания в программе будет просто инверсия состояния порта, а нечётные -- состояние будет меняться (или не меняться) в зависимости от выводимого бита... Ок, попробую это реализовать.
- - - Добавлено - - -
В последнем варианте почти так и есть, но хотелось бы лучшего... :-) А буфер, как я догадываюсь, имеет размер одного кластера SD-карты.
Посмотрел бегло на SdFat в TZXDuino. Ключевой момент, где подкачивается буфер:
https://github.com/sadken/TZXDuino/b...tFile.cpp#L760
Кеш там - это буфер 512 байт, который используется для всего на свете. Для текущих данных, но так же и для таблиц FAT и каталогов. Это круто, но делает переделку этой библиотеки под пинг-понг буфер для данных задачей непропорционально премудрой.
Скрытый текст
Будь это мой проект, я бы эту библиотеку отправил туда, где ей место. А вместо нее взял бы адекватную, написанную без вселенских замашек. Но, пытаясь поставить себя на место Improver-a,[свернуть]
я бы плюнул и оставил эту библиотеку как есть, раз она удобна в использовании и проверена. А данные читал бы в два буфера по 256 байт, только не read(void) а сразу read(uint8_t * dst, size_t count). Вот если памяти перестанет хватать, тогда уже можно подумать, как сделать оптимальней.
Больше игр нет
Просто насколько я понимаю, стандартными средствами до того буфера (считанного кластера), нет доступа. Библиотека предоставляет лишь функцию, которая предоставляет пользователю данные из считанного кластера. Вот я и думаю, что вызывать стандартную функцию библиотеки для чтения одного байта, это избыточно, нужно сразу получить в свой буфер например 32 байта и спокойно их отправить, потом запросить следующие... правда это лишний расход памяти...
Очередная версия готова, вот:
Версия на таймере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
- - - Добавлено - - -
Пробовал я так сделать, и при этом задержки получаются даже немного больше... С буфером на SD не всё так просто: во-первых, в некоторых статьях упоминается о наличии в SD-картах аппаратного буфера чтения/записи, который может быть разного размера в зависимости от модели карты (пруфов не сохранил), а во-вторых, сами карты, в принципе, не умеют читать/писать информацию иначе, чем блоками размером в кластер. Поэтому, я думаю, чтение первого байта происходит с большей задержкой, чем остальных в пределах одного кластера. В общем, сейчас эта проблема решена использованием таймера и таким псевдо-распараллеливанием процессов чтения-передачи, что-то ещё делать с библиотекой я не вижу особого смысла.
- - - Добавлено - - -
Так и сделал. :-)
Поздравляю! Это уже красиво.
Маленькое замечание — delay() для ожидания изменения бита не имеет смысла, потому что внутри его такой же цикл.
ToBUFF() умеренно безопасен, пока получается поддерживать разрыв между указателями.
Больше игр нет
Эту тему просматривают: 1 (пользователей: 0 , гостей: 1)