С любовью к вам, Yandex.Direct
Размещение рекламы на форуме способствует его дальнейшему развитию
Про клаву.
Скрытый текст
Я где-то уже рассказывал, что у меня есть (была) древняя, тяжеленная, настоящая ёмкостная клава от РС-ХТшки.
Когда я её разбирал и изучал "как работает", то обнаружил в поддоне стальную, заземленную пластину. Как только отключал заземление или поднимал от этой пластины плату клавиатуры, клава сразу переставала работать, из-за помех. Но в собранном состоянии работала отлично.[свернуть]
Последний раз редактировалось KTSerg; 28.02.2018 в 06:08.
Новая версия плеера:
ROM-плеер 2
Для работы требуются те же библиотеки, что и ранее: TimerOne, SdFat и LiquidCrystal.
Код:/* SD-картридер подключяется к выводам ардуино: * MOSI - D11 * MISO - D12 * CLK - D13 * CS - D10 Выход - D3 Подключение экрана 1602А (LCD Keypad Shield): * 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 (схема LCD Keypad Shield) */ // include the library code: #include <LiquidCrystal.h> #include <SdFat.h> #include <TimerOne.h> // 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 }; int Tpp = 256; // Начальная длительность задержки сигнала в микросекундах (один полупериод) 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); // объявляем пин как выход pinMode(10, OUTPUT); // CS для SD-картридера lcd.begin(16, 2); // объявляем размер экрана 16 символов и 2 строки Timer1.initialize(Tpp); // инициализировать timer1, и установить период Tpp мкс. Timer1.attachInterrupt(SendHalfBit); // прикрепить SendHalfBit(), как обработчик прерывания по переполнению таймера Timer1.stop(); 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(currentFile); // переходим к первому файлу в директории } void loop() { lcd.setCursor(0, 1); // устанавливаем курсор в позицию 0 в строке 1 lcd.print(millis()/1000); // выводим количество секунд с момента влючения ардуины -- это для теста... int button = getPressedButton(); // какая кнопка нажата? switch (button) { case BT_right: // вход в директорию, или запуск файла на воспроизведение if(isDir==1) { //Если это директория, то переход в неё sd.chdir(fileName, true); getMaxFile(); currentFile=1; seekFile(currentFile); } 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); // Сообщение об ошибке } } } } break; } } } else { printtext("File is too big",1); } } else { printtext("No File Selected",1); } delay(1000); // ждем 1 с printtext(" ",1); // очищаем строку 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; seekFile(currentFile); break; case BT_up: // вверх по файлам в директории currentFile--; if(currentFile<1) { getMaxFile(); currentFile = maxFile; } seekFile(currentFile); break; case BT_down: // вниз по файлам в директории currentFile++; if(currentFile>maxFile) { currentFile=1; } seekFile(currentFile); break; case BT_select: // выход в настройки... printtext("Period(mks):",0); do { while(getPressedButton()!=BT_none) { // Ждём, пока не будет отпущена кнопка delay(50); } delay(200); button = getPressedButton(); // какая кнопка нажата? switch (button) { case BT_up: if (Tpp<400) { Tpp += 8; } break; case BT_down: if (Tpp>200) { Tpp = Tpp - 8; } break; } lcd.setCursor(12,0); lcd.print(Tpp); } while(button!=BT_select); // Цикл пока не будет снова нажата кнопка select seekFile(currentFile); break; case BT_none: // ничего не нажато break; } while(getPressedButton()!=BT_none) { // Ждём, пока не будет отпущена кнопка delay(50); } } //================================================================= int getPressedButton() // функция проверки нажатой кнопки { int buttonValue = analogRead(0); if (buttonValue < 100) return BT_right; else if (buttonValue < 200) return BT_up; else if (buttonValue < 400) 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(" "); lcd.setCursor(0,l); lcd.print(text); } void getMaxFile() { // считаем файлы в текущей директории и сохраняем в maxFile romFile.cwd()->rewind(); maxFile=0; while(romFile.openNext(romFile.cwd(),O_READ)) { romFile.close(); maxFile++; } } void seekFile(int pos) { // переход на позицию в директории, сохранение имени файла и показ его на экране 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--; } else{ bBit = 15; CRB++; } }[свернуть]
Изменения:
- Сделал регулировку периода таймера: вызов через кнопку "select", изменение кнопками "вверх" и "вниз", применение изменений по кнопке "select". Период можно менять в пределах 200...400 мкс.
- Немного изменил процедуру вывода имени файла в заголовках блоков: теперь имена преобразуются в заглавные буквы и тильда -- в подчёркивание.
- Сделал проверку размера файла -- он должен быть не больше 48Кб.
- Сделал возможность вывода файлов с нулевого блока. Т.е. файлы с расширением "rom" теперь выводятся с первого блока, а с расширением "r0m" -- с нулевого.
По последнему пункту вопрос: есть ли какая-то система именования файлов с указанием номера блока, которую можно считать "де факто" стандартом? Например, "*.Rxx", где xx -- шестнадцатиричный номер первого блока...
А в остальном, фактически, проект готов -- осталось сделать варианты под другие экраны/кнопки, ну и, естественно, исправлять баги. :-)
Исходники с библиотеками в одном архиве: ROM-player_2.zip
- - - Добавлено - - -
Я попробовал несколько, но лучше всего пошло с этим (видимо те слишком большие были, или потому что электролиты). Думаю попробовать собрать схемку из проекта для спектрума (D3 подключать справа к сопротивлению R8 в этой схеме):
На основных клавишах я заменил разложившийся поролон на вспененный полиэтилен, а остальные пока не трогал -- надо найти трубку с внутренним диаметром ~13,5 мм для вырубки, а то ножницами кривовато получается...Мой несостоявшийся проект века был конверсией поролоновой клавиатуры в настоящую ёмкостную, которая работает. Я уверен, что это возможно. Поролон пришлось бы все равно раскроить новый, правда.
Скрутить кольцо из полоски жести...... На основных клавишах я заменил разложившийся поролон на вспененный полиэтилен, а остальные пока не трогал -- надо найти трубку с внутренним диаметром ~13,5 мм для вырубки, а то ножницами кривовато получается...
Могу только сказать, что в моих эмуляторах есть только различие между r0m и rom (так же com/bin/vec) и всех устраивает. С кассеты можно грузить куда попало в какой угодно последовательности (см тему с кассетными экспериментами Tim0xa-и), но я никогда не слышал, чтобы это использовалось на практике.
- - - Добавлено - - -
Может электролит другой стороной поставить? По идее плюсом к ардуине. 10мКф из Спектрумофона мне больше нравится, к тому же 10мкф легко можно найти керамику. Аудиофилы не одобрят, но Вектор стерпит. Что делает R8 в схеме из проекта для Спектрума? Может быть есть комплементарный ему последовательный с ножкой ардуины, так что они вместе образуют делитель?
Больше игр нет
Помню, что попадалась "защита от копирования", часть программы грузилась прямо в экранную область. Загрузка прекращалась, когда загружаемые данные натыкались на стек, расположенный вверху справа. Я всё помню удивлялся, что "кубики" рисуемые загрузчиком "затирают" код программы, как она после этого работает... Оказалось, что программа после старта, сначала "собирает/склеивает" себя из кусочков, которые остались в промежутках загрузочной сетки...... С кассеты можно грузить куда попало в какой угодно последовательности (см тему с кассетными экспериментами Tim0xa-и), но я никогда не слышал, чтобы это использовалось на практике. ...
Кстати, я собрал этот кусок выходного каскада, как в схеме. Только заменил резюк 3К на переменник, и к ползунку подключил этот резюк на 10. Должна получиться регулировка громкости. Правда ещё не пробовал. Адаптирую код под LPC-проц.
Последний раз редактировалось KTSerg; 28.02.2018 в 19:51.
Здесь можно скачать актуальные версии Virtual Vector (VV)
У меня тож, заработал... пока без дисплея и кнопок, управление по СОМ-порту с РС...
Сильно не экскрементировал, получается с моей схемой выходного каскада, на "громкости" чуть меньше максимума уверенно грузит с периодом таймера 210мкс. При таймере 190 гарантированы "пропуски".
Отличие номиналов от схемы (собирал из того, что попалось под руку) резюк не 3К а 4.7К , 10Мкф(плюсом к процессору), кондёр пикушник не 10 а 120.
Скрытый текст
Пару часов промаялся, пока нашел прикол компилятора, раньше с таким не сталкивался...
Все переменные объявлены как BYTE, ожидал, что и результат вычисления тоже будет BYTE.
в строке : while((IndW+1)==IndR);
никогда не останавливался, хотя я ожидал, что будет крутиться пока IndW на 1 меньше чем IndR.
Заработал только в варианте: while((BYTE)(IndW+1)==IndR);[свернуть]
Последний раз редактировалось KTSerg; 01.03.2018 в 06:25.
Эту тему просматривают: 1 (пользователей: 0 , гостей: 1)