Для работы требуются библиотеки TimerOne, SdFat, RTClib, а также стандартные LiquidCrystal и Wire.
Код:
#define vers "version 11.1"
/*
Вариант на "Data Logger Shield" и "LCD Keypad Shield"
(на первом есть RTC -- используем для показа времени)
Выход - D3
Вход - A1
"Data Logger Shield V1.0":
SD-картридер подключен к выводам ардуино:
* MOSI - D11
* MISO - D12
* CLK - D13
* CS - D10
Часы реального времени (RTC DS1307) подключены:
* SDA - A4
* SDL - A5
"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; // размер файла, байт
unsigned int StartAddr; // стартовый адрес для MON
unsigned int CSF; // контрольная сумма
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; // записываемый бит
volatile unsigned long PPeriod_sred[2]; // среднее значение границы длинны нечётного полупериода
volatile unsigned long iMicros_old = 0; // предыдущее значение микросекунд
volatile boolean Pik = false; // есть изменение сигнала
volatile byte PP = 1; // =0 -- чётный, =1 -- нечётный полупериод
// Заголовок блока
byte SB[27] = {
0x4E, 0x4F, 0x44, 0x49, 0x53, 0x43, 0x30, 0x30, // NODISK00
0x30, 0x39, 0x30, 0x37, 0x31, 0x38, // дата: 090718
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // имя программы
0x52, 0x4F, 0x4D, 0x00, 0x00 }; // расширение "ROM" и нули
// строка часов
char DT[15] = {
'0', '0', ':', '0', '0', ':', '0', '0', ' ',
'0', '0', '/', '0', '0', 0x00 };
// имя файла для записи
char iFName[13] = "input000.vkt";
volatile unsigned int Tpp = 256; // Начальная длительность задержки сигнала в микросекундах (один полупериод)
volatile int Tb = 16; // Дополнительная задержка сигнала на начало байта в микросекундах
unsigned int TppSTD = 376; // Стандартная длительность задержки сигнала для файлов BAS/MON/ASM...
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_dplay = 1; // воспроизведение в формате DOS
const int M_dplay_in = 11;
const int M_record = 2; // запись
const int M_record_in = 12;
const int M_setup = 3; // настройки
const int M_setup_in = 13;
void setup() {
//Serial.begin(9600);
pinMode(InputPIN, INPUT); // объявляем пин как вход (сигнал)
pinMode(p, OUTPUT); // объявляем пин как выход (сигнал)
digitalWrite(p, LOW); // выход =0
pinMode(10, OUTPUT); // CS для SD-картридера и оно же для яркости экрана
digitalWrite(10, HIGH); // включаем подсветку экрана
pinMode(A0, INPUT); // объявляем пин с кнопками как вход
lcd.begin(16, 2); // объявляем размер экрана 16 символов и 2 строки
Timer1.initialize(Tpp); // инициализировать timer1, и установить период Tpp мкс.
Timer1.attachInterrupt(SendHalfBit); // прикрепить SendHalfBit(), как обработчик прерывания по переполнению таймера
Timer1.stop(); // остановить таймер
printtext("BEKTOP-MF",0); // вывод названия
printtext(vers,1); // и версии
delay(2000); // ждем 2 с для солидности :-)
Wire.begin(); // запуск часов
RTC.begin();
if (! RTC.isrunning()) {
printtext("RTC is NOT run!",1); // часы стоят
RTC.adjust(DateTime(__DATE__, __TIME__)); // устанавливаем дату/время на момент копмиляции программы
delay(2000); // ждем 2 с
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 > M_play) MLevel--;
else MLevel = M_setup;
}
break;
case BT_down: // вниз
if (MLevel < 10) {
if (MLevel < M_setup) MLevel++;
else MLevel = M_play;
}
break;
case BT_right: // вправо -- вход в меню, запуск действия и т.д.
if (MLevel < 10) {
switch (MLevel) // выводим надписи меню
{
case M_play: // воспроизведение
printtext("Play file:",0);
getMaxFile();
break;
case M_dplay: // воспроизведение (DOS)
printtext("Play file (DOS):",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_dplay: // воспроизведение (DOS)
printtext("Play file (D)->",0);
printtime();
break;
case M_record: // запись
printtext("Record data ->",0);
printtime();
break;
case M_setup: // настройки
printtext("Settings ->",0);
printtime();
break;
case M_play_in: // зашли в меню вопроизведения
case M_dplay_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 = MLevel - 10; // из корня выходим в стартовое меню
}
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);
CSF = 0;
for (int i=0; i<=12; i++){ // Переводим все буквы имени файла в заглавные
if ((sfileName[i]>=0x61) && (sfileName[i]<=0x7A)) {
sfileName[i] &= 0xDF; // на заглавные буквы
}
if (sfileName[i]=='.'){ // ищем точку в имени файла
CSF = i; // запоминаем позицию точки в неиспользуемой сейчас переменной, для этого не предназначенной
}
}
if (MLevel == M_dplay_in) { // если вывод в формате DOS
RCrom = PlayAll(sfileName, CSF, 4); // вызов ПП для формата DOS
}
else { // другие форматы
switch(sfileName[CSF+1]) // следующий после точки символ
{
case 'R': // первый символ == 'r'|'R'
if ( ((sfileName[CSF+2]=='O')|(sfileName[CSF+2]=='0'))&(sfileName[CSF+3]=='M') ) { // второй символ == 'o'|'O'|'0' и третий == 'm'|'M'
if (sfileName[CSF+2]!='0') { // проверка на вывод нулевого блока по расширению файла
BLs = 0x01; // с первого блока
}
else {
BLs = 0x00; // с нулевого блока
}
RCrom = PlayROM(sfileName, CSF);// Передаём короткое имя файла и позицию точки в качестве параметров
}
break;
case 'V': // первый символ == 'v'|'V'
if ( (sfileName[CSF+2]=='K')&(sfileName[CSF+3]=='T') ) { // второй символ == 'k'|'K' и третий == 't'|'T'
RCrom = PlayVKT(sfileName); // вызов ПП для формата VKT
}
break;
case 'C': // первый символ == 'c'|'C'
if ( (sfileName[CSF+2]=='A')&(sfileName[CSF+3]=='S') ) { // второй символ == 'a'|'A' и третий == 's'|'S'
RCrom = PlayAll(sfileName, CSF, 0); // вызов ПП для формата CAS
}
break;
case 'B': // первый символ == 'b'|'B'
if ( (sfileName[CSF+2]=='A')&(sfileName[CSF+3]=='S') ) { // второй символ == 'a'|'A' и третий == 's'|'S'
RCrom = PlayAll(sfileName, CSF, 1); // вызов ПП для формата BAS(C)
break; // выход из case, если это был BAS-файл
}
case 'M': // первый символ == 'm'|'M' (или 'b'|'B')
StartAddr = 0x100;
if ((sfileName[CSF+2]>='0')&(sfileName[CSF+2]<='9')) { // преобразуем второй символ расширения в HEX
StartAddr = (sfileName[CSF+2] - '0')*16;
}
else if ((sfileName[CSF+2]>='A')&(sfileName[CSF+2]<='F')) {
StartAddr = (sfileName[CSF+2] - 55)*16;
}
if ((sfileName[CSF+3]>='0')&(sfileName[CSF+3]<='9')) { // преобразуем третий символ расширения в HEX
StartAddr += sfileName[CSF+3] - '0';
}
else if ((sfileName[CSF+3]>='A')&(sfileName[CSF+3]<='F')) {
StartAddr += (sfileName[CSF+3] - 55);
}
if (StartAddr < 0x100) { // если стартовый адрес из расширения определён верно
RCrom = PlayAll(sfileName, CSF, 2); // вызов ПП для формата MON или Бейсика BLOAD
}
break;
case 'A': // первый символ == 'a'|'A'
if ( (sfileName[CSF+2]=='S')&(sfileName[CSF+3]=='M') ) { // второй символ == 's'|'S' и третий == 'm'|'M'
RCrom = PlayAll(sfileName, CSF, 3); // вызов ПП для формата ASM
}
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;
case 12:
printtext("Bad speed mark",0); // метка скорости в VKT не правильная (больше или меньше границы)
break;
default:
printtext("ERROR!",0); // Сообщение об ошибке
}
delay(1000); // ждем 1 с
if (MLevel != M_dplay_in) { // если вывод в не формате DOS
printtext("Play file:",0);
}
else {
printtext("Play file (DOS):",0);
}
seekFile(); // показать имя текущего файла
}
}
break;
case M_record_in: // зашли в меню записи
if (button == BT_right) { // нажали кнопку вправо?
if (dataFile.open(iFName, FILE_WRITE)) { // открываем файл на запись
printtext("Prepare...",0); // готовимся...
dataFile.write(0xFF); // пишем байт для создания файла
dataFile.seekSet(0); // переход на начало файла
CRB = 0; // Сбрасываем индекс
printtext("Waiting...",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(A0)!=HIGH) { // кнопка нажата?
break; // прерывание режима записи при нажатии кнопки
}
}
printtext("Recording...",0); // сигнал зафиксирован, записываем
printtext("Bytes:",1);
delay(200); // задержка для накопления инфы...
noInterrupts(); // запрет прерываний
CRB_temp = CWB; // сохраняем CWB во временную переменную
interrupts(); // разрешение прерываний
//====================================
do { // пока есть данные в буфере
dataFile.write(BUFF[lowByte(CRB)]); // пишем байт из буфера
if (CRB%256 == 0) { // каждый 256-й байт (1 псевдоблок)
lcd.setCursor(7, 1); // устанавливаем курсор в позицию 8 в строке 1
lcd.print(CRB); // количество сохранённых байт
}
CRB++;
if (CRB_temp <= CRB) {// если индекс записи меньше или равен индексу чтения
delay(300); // задержка на ввод данных: макс.скорость 160 * 16 полубит * ~117 байт или ~46 байт на минимальной
noInterrupts(); // запрет прерываний
CRB_temp = CWB; // сохраняем CWB во временную переменную
interrupts(); // разрешение прерываний
}
}
while (CRB_temp >= CRB); // если запись на SD обогнала чтение, значит сигнала нет, выходим из цикла
//=====================================
ACSR = 0; // отключаем обработку прерывания компаратора
ADCSRA = 135; // включить АЦП, установить делитель =128
Pik = false; // сбрасываем флаг "есть сигнал"
Tpp = ((PPeriod_sred[0]+PPeriod_sred[1])/384); // расчитываем полупериод для последующего вывода... (((S1+S2)/2)/128)*2/3
if ((Tpp%8) < 4) { // если остаток от деления на 8 меньше 4
Tpp = (Tpp/8)*8; // округляем в меньшую сторону
}
else {
Tpp = (Tpp/8+1)*8; // округляем в большую сторону
}
if (CRB > 25) { // проверка -- если было ложное срабатывание, то ничего не пишем.
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(7, 1); // устанавливаем курсор в позицию 8 в строке 1
lcd.print(CRB); // количество сохранённых байт
lcd.setCursor(13, 0); // устанавливаем курсор в позицию 13 в строке 0
lcd.print(Tpp); // расчётное значение длинны полупериода для вывода
lcd.setCursor(0, 0); // устанавливаем курсор в позицию 0 в строке 0
if ((PPeriod_sred[1]>(PPeriod_sred[0]+3840))|(PPeriod_sred[0]>(PPeriod_sred[1]+3840))) { // если средние значения слишьком сильно отличаются
lcd.print("BadSig"); // пишем сообщение о плохом сигнале
// отладочный вывод инфы о полупериодах
printtext("",1);
lcd.setCursor(0, 1);
lcd.print(PPeriod_sred[0]/192);
lcd.setCursor(5, 1);
lcd.print(PPeriod_sred[1]/192);
lcd.setCursor(10, 1);
lcd.print(CRB); // количество сохранённых байт
delay(3000); // ждём 3 сек.
}
else if ((Tpp<160)|(Tpp>512)) { // если скорость вне допустимых пределов
lcd.print("Error?"); // пишем сообщение о возможной ошибке
delay(3000); // ждём 3 сек.
}
}
else {
printtext("Canceled.",0);
if (!dataFile.remove()) { // удаляем недозаписанный файл
delay(1000);
printtext("Error del.file",0);
}
}
dataFile.close(); // закрываем файл
Tpp = 256; // устанавливаем полупериод на "стандартное" значение
}
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);
//Serial.println(buttonValue);
if (buttonValue < 60) return BT_right;
else if (buttonValue < 180) return BT_up;
else if (buttonValue < 330) return BT_down;
else if (buttonValue < 530) 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 <= 176) { // для полупериода меньше или равном 176
Tb = 88;
}
else {
if (Tpp <= 240) { // для полупериода от 184 до 240
Tb = 264 - Tpp;
}
else {
if (Tpp <= 264) { // для полупериода от 248 до 264
Tb = 16;
}
else Tb = 0; // для полупериода больше 264
}
}
}
void SwapTpp() // поменять местами значения переменных Tpp и TppSTD
{
Tpp = Tpp + TppSTD; // меняем местами значения Tpp и TppSTD
TppSTD = Tpp - TppSTD;
Tpp = Tpp - TppSTD;
CalcTb();
}
void WaitBuffer() // ожидание очистки буфера при воспроизведении
{
do{ // Ждём опустошения буфера
delay(Tpp/64); // задержка на вывод 1 байта (~Tpp*16/1000)
BUFF[lowByte(CWB+1)] = 0x00; // обнуляем следующий за последним байт -- иногда и он успевает прочитаться...
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 12; // не правильная метка
dataFile.seekSet(0); // на начало файла
Nbt = Nbt-4; // уменьшаем размер данных на метку (4) и предварительно считанные данные (251)
CRB = 0; // Сбрасываем индексы.
bBit = 15;
CalcTb(); // Вычисляем значение задержки на начало байта Tb
// Начинаем наполнять буфер
for (CWB=0; CWB<=250; CWB++){ // первые 251 байт
BUFF[CWB] = dataFile.read();
}
CWB = 251; // продолжаем с 251-го байта буфера
lcd.setCursor(12, 0); // выводим на экран общее кол-во "псевдоблоков" по 256 байт
lcd.print(Nbt>>8); // всего данных / 256
Timer1.setPeriod(Tpp); // Выставляем период таймера
Timer1.start(); // Запускаем таймер............
int RCV = PlayFile(false); // выводим данные из файла без подсчёта контрольной суммы
if (RCV == 0) WaitBuffer(); // ожидаем окончания вывода
return RCV; // выходим с кодом
}
return 3; // ошибка / нет файла
}
int PlayAll(char FName[], int pnt, byte FType) // функция вывода остальных типов файлов
// входные данные: имя файла, длина имени, тип файла:
// 0=CAS и VKT; 1=BAS(C); 2=MON и BAS(B); 3=ASM; 4=DOS
// Возвращаемые RC:
// = 0 -- всё ок
// = 1 -- прерывание по кнопке
// = 2 -- чтение обогнало запись в буфер
{
int i, rcp;
// byte SB; // выводимый байт
delay(1000); // ждем 1 с
if (dataFile.open(FName,O_READ)) { // открываем файл. Открылся?
CRB = 0; // Сбрасываем индексы.
bBit = 15;
// Начинаем наполнять буфер
for (CWB=0; CWB<=255; CWB++){ // первые 256 байт
if ((FType!=3)|((CWB/64==1)|(CWB/64==3))) { // если не ASM и это 64-127 или 192-255 байты
BUFF[CWB] = 0;
}
else { // иначе, если ASM и это 0-63 или 128-191 байты
BUFF[CWB] = 0x55;
}
}
CWB = 256; // продолжаем с 256-го байта буфера
SwapTpp(); // выставляем стандартную скорость вывода
// Tpp = 376; // стандартная скорость вывода
// Tb = 0;
Timer1.setPeriod(Tpp); // Выставляем период таймера
Timer1.start(); // Запускаем таймер............
delay(10); // немного ждём, чтобы начался вывод первых байт
ToBUFF(0xE6); // синхробайт
switch (FType)
{
case 0: // это CAS
rcp = PlayFile(false); // выводим данные из файла без подсчёта контрольной суммы
if (rcp == 0) WaitBuffer(); // ожидаем окончания вывода
SwapTpp(); // восстанавливаем Tpp
return rcp; // выходим с кодом
case 1: // это BAS(C)
for (i=0;i<=3;i++) ToBUFF(0xD3); // 4x 0xD3
break;
case 2: // это MON или BAS(B)
for (i=0;i<=3;i++) ToBUFF(0xD2); // 4x 0xD2
break;
case 3: // это ASM
for (i=0;i<=3;i++) ToBUFF(0xE6); // 4x 0xE6
break;
case 4: // это DOS
ToBUFF(0x01);
ToBUFF(0x00); // 0x0100 -- начальный адрес (всегда)
ToBUFF(highByte(Nbt-1)+1); // старший байт конечного адреса (нач.адрес+длинна данных)
ToBUFF(0xFF); // младший байт конечного адреса (всегда)
rcp = PlayFile(true); // выводим байты из файла с подсчётом КС
if (rcp > 0) {
SwapTpp(); // восстанавливаем Tpp
return rcp; // выходим с кодом
}
for (i=lowByte(Nbt-1);i<0xFF;i++) ToBUFF(0x00); // дополняем, если надо, нулями
ToBUFF(lowByte(CSF)); // выводим младший байт КС
}
byte SB; // выводимый байт
for (i=0;i<pnt;i++) { // вывод имени файла без расширения, DOS поддерживаются только 'A'-'Z','0'-'9'
if (FName[i]!=0x7E) { // не тильда
SB = FName[i]; // берём символ символ
}
else { // тильда -- часто встречабщийся символ, остальные можно переименовать вручную
if (FType!=4) {
SB = '_'; // меняем тильду на подчёркивание
}
else SB = '0'; // для DOS меняем тильду на нолик, иначе при загрузке будет ошибка
}
CSF += SB; // считаем КС
ToBUFF(SB); // заносим в буфер очередную букву имени
}
// это DOS
if (FType==4) {
for (i=pnt;i<8;i++) { // дополняем имя пробелами до 8 байт
ToBUFF(0x20);
CSF += 0x20;
}
for (i=1;i<4;i++) { // выводим расширение файла
SB = FName[pnt+i];
CSF += SB; // считаем КС
ToBUFF(SB); // заносим в буфер очередную букву расширени
}
ToBUFF(lowByte(CSF)); // выводим младший байт КС (данные + имя файла)
WaitBuffer(); // ожидаем окончания вывода
SwapTpp(); // восстанавливаем Tpp
return 0; // выходим с кодом 0
}
for (i=0;i<=2;i++) ToBUFF(0x00); // 3x 0x00
// это BAS(C)
if (FType==1) {
for (i=0;i<=767;i++) ToBUFF(0x55); // 768x 0x55
ToBUFF(0xE6); // синхробайт
for (i=0;i<=2;i++) ToBUFF(0xD3); // 3x 0xD3
ToBUFF(0x00); // 1x 0x00
rcp = PlayFile(true); // выводим данные из файла с подсчётом КС
if (rcp == 0) {
ToBUFF(lowByte(CSF)); // младший байт КС
ToBUFF(highByte(CSF)); // старший байт КС
WaitBuffer(); // ожидаем окончания вывода
}
SwapTpp(); // восстанавливаем Tpp
return rcp; // выходим с кодом
}
// это MON, BAS(B) или ASM
for (i=0;i<=255;i++) ToBUFF(0x00); // 256x 0x00
ToBUFF(0xE6); // синхробайт
if (FType==2) { // это MON или BAS(B)
ToBUFF(lowByte(StartAddr)); // старший байт адреса начала записи
ToBUFF(0x00); // младший байт = 0
ToBUFF(lowByte(StartAddr+(Nbt-1)/256));// старший байт адреса конца записи
ToBUFF(lowByte(Nbt-1)); // младший байт адреса конца записи
rcp = PlayFile(true); // выводим данные из файла с подсчётом КС
if (rcp == 0) {
ToBUFF(lowByte(CSF)); // младший байт КС
WaitBuffer(); // ожидаем окончания вывода
}
SwapTpp(); // восстанавливаем Tpp
return rcp; // выходим с кодом
}
// остался только ASM
ToBUFF(lowByte(Nbt)); // младший байт длинны записи
ToBUFF(highByte(Nbt)); // старший байт длинны записи
rcp = PlayFile(true); // выводим данные из файла с подсчётом КС
if (rcp == 0) {
ToBUFF(0xFF); // дополняем FFh в конце
ToBUFF(lowByte(CSF)); // младший байт КС
ToBUFF(highByte(CSF)); // старший байт КС
WaitBuffer(); // ожидаем окончания вывода
}
SwapTpp(); // восстанавливаем Tpp
return rcp; // выходим с кодом
}
return 3; // ошибка / нет файла
}
int PlayFile(boolean CSFp) // Функция вывода битов файла
// CSFp признак необходимости подсчёта контрольной суммы файла
{
byte SB;
CSF = 0; // обнуляем контрольную сумму
for (unsigned int i=0; i<Nbt; i++){ // данные из файла
SB = dataFile.read();
ToBUFF(SB);
if (CSFp) CSF += SB;
if ((Nbt-i)%256==0){ // если остаток кратен 256
lcd.setCursor(12, 0); // выводим на экран кол-во оставшихся "псевдоблоков" по 256 байт
lcd.print((Nbt-i)>>8); // остаток данных / 256
lcd.print(' ');
}
if (getPressedButton()!=BT_none) { // кнопка нажата?
Timer1.stop(); // Останавливаем таймер
dataFile.close(); // закрываем файл
SwapTpp(); // восстанавливаем Tpp
return 1; // выход из ПП с ошибкой 1.
}
if (CRB_temp > CWB) { // проверка -- не обогнало ли чтение запись?
Timer1.stop(); // Останавливаем таймер
dataFile.close(); // закрываем файл
SwapTpp(); // восстанавливаем Tpp
return 2; // выход из ПП с ошибкой 2.
}
}
dataFile.close(); // закрываем файл
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]!=0x7E) { // не тильда
SB[i+14] = FName[i]; // заносим символ
}
else {
SB[i+14] = '_'; // меняем тильду на подчёркивание, иначе это будет русская "Ч"
}
}
else SB[i+14] = 0x20; // дополняем пробелами
}
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 -- завершение вывода программы (?)
WaitBuffer(); // ожидаем окончания вывода
return 0; // выход из ПП с кодом 0.
}
return 3; // ошибка / нет файла
}
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++;
Timer1.setPeriod(Tpp+Tb); // Выставляем увеличенный период таймера (начало байта)
}
}
ISR(ANALOG_COMP_vect) // ПП обработки прерывания компаратора -- определяет и заносит полученные биты в буфер
{
unsigned long iMicros = micros();
unsigned long PPeriod = iMicros - iMicros_old;
iMicros_old = iMicros;
if (PPeriod < 65000) { // началось...
if (bBit < 16) { // если это не последний полубит
if (PPeriod <= (PPeriod_sred[PP]>>7)) { // sred/128, если тек. полупериод короткий
if (CWB <= 255) { // расчёт среднего значения, если меньше 256 байт считано
PPeriod_sred[PP] = (PPeriod_sred[PP]*CWB + PPeriod*192)/(CWB+1); // "среднее значение" = 1,5*128 от короткого полупериода
}
if (bBit & 1) { // нечётный полубит
A = (A<<1)+B; // заносим бит
} // нечётный -- пропускаем
}
else { // получен длинный полупериод
B ^= 1; // инвертируем бит
A = (A<<1)+B; // заносим бит
bBit--; // уменьшаем счётчик полубитов
}
}
else { // если последний полубит, вводим корректировку на задержку между байтами
// граница будет =([1,5*Tpp*128]*25/19)/128 =~[1,5*Tpp*128]/97 =~1,98*Tpp
if (PPeriod > (PPeriod_sred[PP]/97)) { // если тек. полупериод длинный
B ^= 1; // инвертируем бит
A = (A<<1)+B; // заносим бит
bBit--; // уменьшаем счётчик полубитов
}
}
// корректировка счётчиков
PP ^= 1; // инвертируем бит признака чётности полупериода
if (bBit > 1) {
bBit--; // счётчик полубитов -1
}
else {
BUFF[lowByte(CWB)] = A; // заносим байт в буфер
BUFF[lowByte(++CWB)] = 0;// счётчик байтов +1 и обнуляем очередной байт в буфере
A = 0; // обнуляем буфер для битов
bBit += 15; // = +16 -1
}
}
else { // был перерыв в сигнале
PPeriod_sred[0] = 98304; // берём заведомо большое среднее значение (192*512)
PPeriod_sred[1] = 98304; // берём заведомо большое среднее значение
CWB = 0; // начинаем запись с начала
bBit = 15;
A = 0;
B = 0;
PP = 1; // = нечётный полупериод
Pik = true; // есть сигнал!
}
}