Для работы требуются библиотеки TimerOne, SdFat, а также стандартные LiquidCrystal, Wire и RTClib.
Код:
/*
Вариант на "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 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 };
char DT[15] = {
'0', '0', ':', '0', '0', ':', '0', '0', ' ',
'0', '0', '/', '0', '0', 0x00 };
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;
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();
Wire.begin();
RTC.begin();
if (! RTC.isrunning()) {
printtext("RTC is NOT run!",0);
// following line sets the RTC to the date & time this sketch was compiled
RTC.adjust(DateTime(__DATE__, __TIME__));
delay(1000); // ждем 1 с
}
//RTC.adjust(DateTime(__DATE__, __TIME__));
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(); // переходим к первому файлу в директории
}
void loop() {
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'; // месяц
printtext(DT,1); // выводим время и дату
int button = getPressedButton(); // какая кнопка нажата?
while(getPressedButton()!=BT_none) { // Ждём, пока не будет отпущена кнопка
delay(50);
}
switch (button)
{
case BT_right: // вход в директорию, или запуск файла на воспроизведение
if(isDir==1) { //Если это директория, то переход в неё
sd.chdir(fileName, true);
getMaxFile();
currentFile=1;
}
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); // Сообщение об ошибке
}
digitalWrite(p, LOW);
}
}
}
break;
}
}
}
else {
printtext("File is too big",1);
}
}
else {
printtext("No File Selected",1);
}
delay(1000); // ждем 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;
break;
case BT_up: // вверх по файлам в директории
currentFile--;
if(currentFile<1) {
getMaxFile();
currentFile = maxFile;
}
break;
case BT_down: // вниз по файлам в директории
currentFile++;
if(currentFile>maxFile) {
currentFile=1;
}
break;
case BT_select: // выход в настройки...
printtext("Setup",0);
printtext("Period(mks):",1);
do {
delay(300);
button = getPressedButton(); // какая кнопка нажата?
switch (button)
{
case BT_up:
if (Tpp<400) Tpp += 8;
break;
case BT_down:
if (Tpp>160) Tpp = Tpp - 8;
break;
}
if (Tpp <= 264) { // Выставляем значение задержки на начало байта Tb
Tb = 16; // для полупериода от 248 до 264
if (Tpp <= 240) Tb = 264 - Tpp; // для полупериода меньше или равном 240
}
else Tb = 0; // для полупериода больше 264
lcd.setCursor(12,1);
lcd.print(Tpp);
}
while(button!=BT_select); // Цикл пока не будет снова нажата кнопка select
while(getPressedButton()!=BT_none) { // Ждём, пока не будет отпущена кнопка
delay(50);
}
break;
case BT_none: // ничего не нажато
delay(100);
break;
}
if (button != BT_none) seekFile();
}
//=================================================================
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 getMaxFile() { // считаем файлы в текущей директории и сохраняем в maxFile
romFile.cwd()->rewind();
maxFile=0;
while(romFile.openNext(romFile.cwd(),O_READ)) {
romFile.close();
maxFile++;
}
}
void seekFile() { // переход на позицию в директории, сохранение имени файла и показ его на экране
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--;
if (bBit == 14) Timer1.setPeriod(Tpp); // Выставляем период таймера (биты)
}
else{
bBit = 15;
CRB++;
if (CRB > 200) Timer1.setPeriod(Tpp+Tb); // Выставляем увеличенный период таймера (начало байта)
}
}