Для работы требуются библиотеки TimerOne, SdFat, RTClib, а также стандартные LiquidCrystal и Wire. Вход для записи данных -- D2.
Код:
/*
Вариант на "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 dataFile;
//#define filenameLength 100 // максимальная длина имени файла
//char fileName[filenameLength + 1]; // имя текущего файла
char sfileName[14]; // короткое имя текущего файла/директории
int currentFile = 1; // текущая позиция в директории
int maxFile = 0; // всего позиций в лиректории (файлов и поддиректорий)
byte isDir = 0; // признак того, что текущая позиция -- это директория
boolean isRoot = true; // признак того, что мы в корне
byte BLs = 0x01; // начальный блок
unsigned int Nbt; // размер файла, байт
volatile byte BUFF[256]; // буфер данных
volatile unsigned int CRB = 0; // индекс чтения из буфера
volatile byte bBit = 15; // индекс читаемого полубита
volatile unsigned int CWB = 0; // индекс записи в буфер
unsigned int CRB_temp = 0; // временная переменная для CRB
int i0 = 0; // счётчик циклов без сигнала
volatile byte A = 0; // записываемый байт
volatile byte B = 0; // записываемый бит
volatile unsigned long PPeriod = 0; // текущее значение полупериода
volatile unsigned int PPeriod_sred = 0; // среднее значение границы длинны полупериода
volatile unsigned long iMicros_old = 0; // предыдущее значение микросекунд
volatile boolean Pik = false; // есть изменение сигнала
// Заголовок
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 };
char iFName[13] = "input000.vkt";
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;
int MLevel = 0; // текущий пункт меню
const int M_play = 0;
const int M_play_in = 10;
const int M_record = 1;
const int M_record_in = 11;
const int M_setup = 2;
const int M_setup_in = 12;
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();
printtext("BEKTOP-MF",0);
printtext("version 5.0",1);
delay(2000); // ждем 2 с для солидности :-)
Wire.begin();
RTC.begin();
if (! RTC.isrunning()) {
printtext("RTC is NOT run!",1);
// following line sets the RTC to the date & time this sketch was compiled
RTC.adjust(DateTime(__DATE__, __TIME__));
delay(1000); // ждем 1 с
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);
tone(p, 200, 100); // нет -- включаем на 200 Гц на 100 мс
delay(3000); // ждем 3 с
}
printtext("SD-card is OK.",1);
sd.chdir(); // устанавливаем корневую директорию SD
// getMaxFile(); // получаем количество файлов в директории и переходим к первому файлу в директории
}
void loop() {
int RCrom = 0; // для кода возврата ПП
int button = getPressedButton(); // какая кнопка нажата?
while(getPressedButton()!=BT_none) { // Ждём, пока не будет отпущена кнопка
delay(50);
}
switch (button)
{
case BT_up:
if (MLevel < 10) {
if (MLevel > 0) MLevel--;
else MLevel = 2;
}
break;
case BT_down:
if (MLevel < 10) {
if (MLevel < 2) MLevel++;
else MLevel = 0;
}
break;
case BT_right:
if (MLevel < 10) {
switch (MLevel)
{
case M_play:
printtext("Play file:",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_record:
printtext("Record data ->",0);
printtime();
break;
case M_setup:
printtext("Settings ->",0);
printtime();
break;
case M_play_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 = M_play; // из корня выходим в стартовое меню
}
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);
for (int i=0;8;i++){ // цикл по имени файла
if (sfileName[i]=='.'){ // ищем точку в имени файла
if (((sfileName[i+1]|0x20)=='r')&((sfileName[i+3]|0x20)=='m')) { // проверяем первый и третий символы расширения == 'r'|'R', == 'm'|'M'
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; // с нулевого блока
}
RCrom = PlayROM(sfileName, i);// Передаём короткое имя файла и позицию точки в качестве параметров
}
}
else { // проверяем расширение файла на "VKT"
if (((sfileName[i+1]|0x20)=='v')&((sfileName[i+2]|0x20)=='k')&((sfileName[i+3]|0x20)=='t')) {
delay(2000);
RCrom = 0; // тут будет вызов ПП для формата VKT
}
}
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-файл
break;
default:
printtext("ERROR!",0); // Сообщение об ошибке
}
delay(1000); // ждем 1 с
printtext("Play file:",0);
seekFile();
}
}
break;
case M_record_in: // зашли в меню записи
if (button == BT_right) {
if (dataFile.open(iFName, FILE_WRITE)) { // открываем файл на запись
do {
printtext("Waiting...",0);
i0 = 0; // Сбрасываем индексы
CRB = 0;
bBit = 14; // индекс записываемого полубита. (Начинаем с 15, т.к. первый бит теряется)
attachInterrupt(0, TakeTime, CHANGE); // включаем обработку внешнего прерывания
while (!Pik) { // Ждём сигнал
delay(10); // задержка...
if (getPressedButton()!=BT_none) { // кнопка нажата?
break; // прерывание режима загрузки при нажатии кнопки
}
}
printtext("Start...",0);
noInterrupts(); // запрет прерываний
CRB_temp = CWB; // сохраняем CWB во временную переменную
interrupts(); // разрешение прерываний
//====================================
do {
while ((CRB_temp <= CRB) & Pik)// пока индекс записи меньше или равен индексу чтения и есть сигнал
{ // Ждём заполнения буфера
i0++; // счиаем циклы
Pik = (i0 < 10); // что-то долго ждём...
delay(1); // задержка на ввод данных
noInterrupts(); // запрет прерываний
CRB_temp = CWB; // сохраняем CWB во временную переменную
interrupts(); // разрешение прерываний
}
if (!Pik) break; // выход из чтения т.к. сигнал закончился
i0 = 0;
dataFile.write(BUFF[lowByte(CRB)]); // пишем байт из буфера
CRB++;
}
while (Pik); // пока есть сигнал
//=====================================
detachInterrupt(0); // отключаем обработку внешнего прерывания 0
if (((PPeriod_sred/3*2)%8) < 4) { // полупериод для последующего вывода...
Tpp = (PPeriod_sred/12)*8; // округление до кратного 8 в меньшую сторону
}
else {
Tpp = ((PPeriod_sred/12)+1)*8; // округление до кратного 8 в большую сторону
}
}
while (Tpp < 32); // проверка -- если было ложное срабатывание (задержка <32 мкс), то снова ждём...
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());
}
dataFile.close(); // закрываем файл
printtext("Done. Speed:",0);
lcd.setCursor(13, 0); // устанавливаем курсор в позицию 0 в строке 1
lcd.print(Tpp); // расчётное значение длинны полупериода для вывода
}
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);
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 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.getName(fileName,filenameLength);
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;
}
}
}
}
int PlayROM(char FName[], int pnt) // функция вывода файла
{
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]>=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;
dataFile.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';
CRB = 0; // Сбрасываем индексы.
CWB = 0;
bBit = 15;
if (Tpp <= 264) { // Вычисляем значение задержки на начало байта Tb
Tb = 16; // для полупериода от 248 до 264
if (Tpp <= 240) Tb = 264 - Tpp;// для полупериода меньше или равном 240
}
else Tb = 0; // для полупериода больше 264
// Начинаем наполнять буфер
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 -- завершение вывода программы (?)
}
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(); // Останавливаем таймер............
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); // Выставляем увеличенный период таймера (начало байта)
}
}
void TakeTime()
{
boolean Plong = false; // короткий полупериод по умолчанию
PPeriod = micros() - iMicros_old;
iMicros_old += PPeriod;
if (PPeriod < 1000) { // началось...
if (PPeriod_sred != 0) {
Plong = (PPeriod > PPeriod_sred); // = false, если тек. полупериод короткий
if (!Plong) { // если полупериод короткий, пересчитываем среднее значение
PPeriod_sred = (PPeriod_sred + PPeriod/2*3)/2;
}
}
else {
PPeriod_sred = PPeriod/2*3; // ещё нет статистики, берём текущее значение
}
}
else { // был перерыв в сигнале
if (PPeriod_sred != 0) {
PPeriod_sred = 0; // обнуляем среднее значение
CWB = 0; // начинаем запись с начала
A = 0;
B = 0;
}
}
if (Plong) { // получен длинный полупериод
B ^= 1; // инвертируем бит
A = (A<<1)+B; // заносим бит
bBit--; // уменьшаем счётчик полубитов
}
else { // получен короткий полупериод
if (bBit & 1) { // нечётный полубит
A = (A<<1)+B; // заносим бит
} // нечётный -- пропускаем
}
// корректировка счётчиков
if (bBit > 1) {
bBit--; // счётчик полубитов -1
}
else {
BUFF[lowByte(CWB)] = A; // заносим байт в буфер
CWB++;
A = 0;
bBit += 15; // = +16 -1
}
Pik = true; // есть сигнал!
}