Вот и я добрался до карточек памяти, к слову давно еще были попытки но не было времени. Сейчас же предстояло хранить данные, а где их хранить как не на карте памяти! Это ведь замечательный и ёмкий накопитель для нашего AVR!
Ну что ж начнём!
Я не стал изобретать велосипед и поэтому воспользовался исходниками из интернета) Но не один меня не удовлетворил полностью, поэтому я совместил несколько исходников в один + до весил своё и получил вполне неплохой результат. Исходники которыми я пользовался были взяты с сайта we.easyelectronics.ru и сайта narodstream.ru. С одного источника взял низкоуровневую библиотеку для работу с картами памяти, а с другого почерпнул идею напрямую работать с блоками(секторами) без файловой системы. И действительно! — зачем нагружать и без того расходующий ресурсы МК ещё и файловой системой. От себя я добавил простую добавку текста или другой информации одной функцией, также навигацию по блокам, перезапись, модификация и т.д.
У меня будет карточка памяти Apacer 2GB (класс на ней не указан). Карту памяти будем подключать так:

Начнем с младших функций, — SPI и работой с блоками.
#include <stdlib.h> //SPI ports #define SPI_PORT PORTB #define SPI_DDR DDRB #define SPI_PIN PINB //SPI pins #define DI 2 //miso #define DO 3 //mosi #define SCK 1 #define CS 6 #define CS_LOW SPI_PORT &= ~(1<<CS) #define CS_HIGH SPI_PORT |= (1<<CS) #define MEM_N 13 //блок начала данных unsigned char sdrx_buff[512]; //приемный буфер //unsigned char sdtx_buff[512]; unsigned long int blokWR=0; // в этой переменной после чтения сектора 1 всегда хранится номер последнего записанного блока unsigned int sizeWR=0; // в этой переменной после чтения всегда хранится размер полезных данных читаемого блока bit wr=0; // также при каждом считывании меняется бит в зависимости от наличия метки WR
Думаю текст выше не нуждается в пояснениях). Перейдем к функциям.
void spi_sendbyte(unsigned char data)
{
unsigned char i;
for (i = 0; i < 8; i++) {
if ((data & 0x80) == 0x00)
PORTB &= ~(1<<DI);
else
PORTB |= (1<<DI);
data = data << 1;
PORTB |= (1<<SCK);
#asm("nop")
PORTB &= ~(1<<SCK);
}
}
unsigned char spi_receivebyte (void)
{
unsigned char i, res = 0;
PORTB |= (1<<DI);
for (i = 0; i < 8; i++) {
PORTB |= (1<<SCK);
res = res << 1;
if ((PINB & (1<<DO))!=0x00)
res = res | 0x01;
PORTB &= ~(1<<SCK);
#asm("nop")
}
return res;
}
unsigned char SD_cmd(char dt0, unsigned long int arg, char dt5) //передача команды (пример даташит стр 40)
{
unsigned char result;
long int cnt;
spi_sendbyte (dt0); //Индекс
spi_sendbyte(((arg & 0xFF000000)>>24));
spi_sendbyte(((arg & 0x00FF0000)>>16));
spi_sendbyte(((arg & 0x0000FF00)>>8));
spi_sendbyte(((arg & 0x000000FF)));
spi_sendbyte (dt5); //контрольная сумма
cnt=0;
do { //Ждем ответ в формате R1 (даташит стр 109)
result=spi_receivebyte();
cnt++;
} while ( ((result&0x80)!=0x00)&&(cnt<0xFFFF) );
return result;
}
unsigned char SD_Init(void)
{
unsigned char i, temp;
long int cnt;
for (i=0;i<10;i++) //80 импульсов (не менее 74) Даташит стр 94
spi_sendbyte(0xFF);
CS_LOW; //опускаем SS
temp=SD_cmd (0x40,0x00,0x95); // CMD0 Даташит стр 102 и 96
if (temp!=0x01) return 3; //Выйти, если ответ не 0х01 (результат
spi_sendbyte (0xFF);
cnt=0;
do{
temp=SD_cmd (0x41,0x00,0x95); //CMD1 (аналогично CMD0, только индекс меняется)
spi_sendbyte (0xFF);
cnt++;
} while ( (temp!=0x00)&&(cnt<0xFFFF) ); //Ждем ответа R1
if (cnt>=0xFFFF) return 4;
return 0;
}
unsigned char SD_Write_Block (char* bf, unsigned long int blok)
{
unsigned char result;
long int cnt,arg;
SD_Init(); //повторно инит изза ухода карты в "спячку"
arg = blok*512;
result=SD_cmd(0x58,arg,0x95); //CMD24 даташит стр 51 и 97-98
if (result!=0x00) return 6; //Выйти, если результат не 0x00
spi_sendbyte (0xFF);
spi_sendbyte (0xFE); //Начало буфера
for (cnt=0;cnt<512;cnt++) spi_sendbyte(bf[cnt]); //Данные
spi_sendbyte (0xFF); //Котрольная сумма
spi_sendbyte (0xFF);
result=spi_receivebyte();
if ((result&0x05)!=0x05) return 6; //Выйти, если результат не 0x05 (Даташит стр 111)
cnt=0;
do { //Ждем окончания состояния BUSY
result=spi_receivebyte();
cnt++;
}while ( (result!=0xFF)&&(cnt<0xFFFF) );
if (cnt>=0xFFFF) return 6;
return 0;
}
unsigned char SD_Read_Block (char* bf, unsigned long int blok)
{
unsigned char result;
long int cnt,arg;
char bf_n[4];
char bf_wr[2];
SD_Init(); //повторно инит изза ухода карты в "спячку"
arg = blok*512;
result=SD_cmd (0x51,arg,0x95); //CMD17 даташит стр 50 и 96
if (result!=0x00) return 5; //Выйти, если результат не 0x00
spi_sendbyte (0xFF);
cnt=0;
do{ //Ждем начала блока
result=spi_receivebyte();
cnt++;
} while ( (result!=0xFE)&&(cnt<0xFFFF) );
if (cnt>=0xFFFF) return 5;
for (cnt=0;cnt<512;cnt++)
{
bf[cnt]=spi_receivebyte();
if(cnt==0||cnt==1||cnt==3){ bf_n[cnt] = bf[cnt]; }
if(cnt==5||cnt==6){ bf_wr[cnt] = bf[cnt]; }
} //получаем байты блока из шины в буфер
spi_receivebyte(); //Получаем контрольную сумму
spi_receivebyte();
sizeWR = atoi(bf_n); //считали ко-во записаной информации в блоке(помимо служебной)
if(bf_wr[0]=='W'&&bf_wr[1]=='R'){ wr=1; }else{ wr=0; } //проверка блока на разрешение записи(метки)
return 0;
}
И так что мы имеем:
unsigned char SD_Init() — функция проводит инициализацию карты — если всё прошло успешно в ответ получим «0».
void spi_sendbyte(unsigned char data) — функция отправляет байты по шине SPI
unsigned char spi_receivebyte(void) — функция получения байтов по шине SPI
unsigned char SD_cmd(char dt0, unsigned long int arg, char dt5) — a функция передаёт команды карте памяти и сообщает об успешной передаче — «0».
unsigned char SD_Write_Block (char* bf, unsigned long int blok) — запись блока(сектора) по номеру начиная с 2-го сектора( в 0- секторе находится файловая система, а в 1 секторе наше подобие файловой системы — номер последнего записанного сектора).
unsigned char SD_Read_Block (char* bf, unsigned long int blok) — чтение блока по номеру — при успехе операции возвращает «0».
В принципе с этим уже можно работать, а номер записанного сектора хранить в eeprom avr, но зачем если можно реализовать хранение на самой карте памяти. Например при инициализации считываем сектор 1 и получаем нужную нам информацию — номер последнего записанного сектора и метку инициализации файловой системы(при новой карте). Также мы будем в начало каждого сектора писать свою служебную информацию: — первые три байта это размер записываемых данных, далее идет метка WR — сигнализируя о том что в секторе есть полезные данные, начиная с 12 байта пойдут наши полезные данные.
Получается вот такая картина.

Для удобства между данными в служебной области оставил пустые «клетки».
В 1 секторе у нас будет хранится информация о последнем записанном блоке, как писалось ранее, в таком виде: с 0 по 8 байт сектора а с 16 по 20 метка в виде слова «AMEM8».
Общая картина получается такая.

Почему именно 3 862 528? да потому что столько показал HEX-редактор, ну и я посчитал — это ко-во секторов для карты памяти 2Гб. И так чтобы записать в последний блок надо довольно крупная переменная — я решил использовать unsigned long int — 4 294 967 295, для блоков вполне достаточно) А как насчёт адреса? — Ведь адрес = Сектор х 512, и так подсчитаем адрес последнего: 1 977 614 336 — ахрееенительно большое число я вам скажу, но long int нам хватит))) Теоретически если подсчитать, то можем использовать карточку и на 4Гб, но что касается большего объёма пока не уверен — есть варианты, но нужно все переделывать. Думаю для МК и 2-4Гб вполне хватит)))
Перейдем к написанию управляющих функций.
int cpyNum(char* bf, int nahalo, int konec)
{
int i=0,j=0,num=0;
char buff[32];
for(i=nahalo;i<=konec;i++)
{
buff[j] = bf[i];
j++;
}
num = atoi(buff);
return num;
}
void cpyNcpy(char* bfin, char* bfout, int nahalo, int konec)
{
int i=0,j=0;
for(i=nahalo;i<=konec;i++)
{
bfout[j] = bfin[i];
j++;
}
}
unsigned char MEM_AVR_Init(void)
{
char sd_buff[64];
SD_Read_Block(sdrx_buff,1); //Считаем блок служебной инфы в буфер
if( (sdrx_buff[16]=='A')&&(sdrx_buff[17]=='M')&&(sdrx_buff[18]=='E')&&(sdrx_buff[19]=='M')&&(sdrx_buff[20]=='8') )
{
//
blokWR = cpyNum(sdrx_buff,0,7);
clearRX();
return 1;
}
else
{
char i=0;
for(i=0;i<=16;i++){ sd_buff[i]=0; }
sd_buff[16]='A';
sd_buff[17]='M';
sd_buff[18]='E';
sd_buff[19]='M';
sd_buff[20]='8';
SD_Write_Block(sd_buff,1);//Запишем блок
return 2;
}
}
char writeText(char *bf, char rewrite)
{
unsigned char temp=1;
int i=0,j=0;
char buf[512], tempB[512];
blokWR++;
if(blokWR==0||blokWR==1){blokWR=2;}
if(rewrite==0){ SD_Read_Block (tempB, blokWR); }
if(wr==0||rewrite==1)
{
itoa(strlen(bf),buf);
for(i=13;i<=512;i++)
{
buf[i]=bf[j];
j++;
}
buf[5]='W'; buf[6]='R';buf[7]='-'; buf[8]='-';buf[9]='-'; buf[10]='-';
temp = SD_Write_Block (buf, blokWR);
if(temp==0)
{
char sd_buff[32];
ltoa(blokWR,sd_buff);
sd_buff[16]='A';
sd_buff[17]='M';
sd_buff[18]='E';
sd_buff[19]='M';
sd_buff[20]='8';
SD_Write_Block(sd_buff,1);//Запишем блок
}else{ blokWR--; }
return temp;
}
else
{
return 10; //обнаружена метка блока
}
}
char modifiedText(char *bf, unsigned long int blok) //
{
unsigned char temp=1;
int i=0,j=0;
char tempB[512];
int szin=0,szsv=0,szN=0;
unsigned long int bl = blok;
SD_Read_Block (tempB, blok);
szin = strlen(bf); //то что надо дописать
szsv = 500-sizeWR;
szN = sizeWR+14;
itoa(szin+sizeWR,tempB);
if(szin<=szsv)
{
for(i=szN;i<=512;i++)
{
tempB[i]=bf[j];
j++;
}
if(bl==0||bl==1){ return 10; }
temp = SD_Write_Block (tempB, bl);
return temp;
}
else{ return 20; }
}
char delitBlok( unsigned long int blok)
{
char buf[512];
int i=0;
for(i=0;i<=512;i++)
{
buf[i]=0;
}
SD_Write_Block(buf,blok);
}
Пройдемся по функциям:
unsigned char MEM_AVR_Init(void) — Здесь мы проводим инициализацию подсистемы памяти на основе нашей флешки и считывание номера последнего записанного блока. В ответ получим 1 если подсистема AMEM8 имеется, или 2 — что означает установку системы с нуля.
cpyNcpy(char* bfin, char* bfout, int nahalo, int konec) — функция предназначена для копирование числа из строки по координатам буфера.
int cpyNum(char* bf, int nahalo, int konec) — функция копирует часть буфера по координатам , — так как буфер велик иногда нужен только кусочек данных, для этого и нужна эта функция.
char writeText(char *bf, char rewrite) — простая запись текста в следующий блок, rewrite — 1-разрешить перезапись при обнаружении WR.
char modifiedText(char *bf, unsigned long int blok) — функция предназначена для дописывания данных в блок — так как мы можем читать и записывать данные только целыми секторами по 512 байт, а данных обычно меньше, то с помощью этой функции можно дописать данные в определенный сектор(блок).
char delitBlok( unsigned long int blok) — функция предназначена для стирания всего блока(заполняет нулями).
Работа с картой памятью идет «последовательно» — то-есть каждая новая записываемая информация автоматически начинается со следующего блока, если используется функция writeText .
Время тестов!
Для тестов я использовал плату Ардуины на основе Atmega32U4 (предварительно восстановив родной загрузчик Atmel) + дисплей ssd1306 и плату логера( часы ds1307 в связке с картой памятью, на которой кстати сразу согласованы уровни что очень удобно))
Модуль Data logging board ID:8122

На дисплее как видите уже запущена тестова прошивка:
Дисплей и часы у нас сидят на шине I2C, а карта памяти на интерфейсе SPI, CS — на выводе PB6.
Вообщем вот схема)

Chip type : ATmega32U4
Program type : Application
AVR Core Clock frequency: 16,000000 MHz
Memory model : Small
External RAM size : 0
Data Stack size : 640
*******************************************************/
#include <mega32u4.h>
#include <delay.h>
#include <i2c.h>
#include <string.h>
#include <stdio.h>
// DS1307 Real Time Clock functions
#include <ds1307.h>
#include "ssd1306.c"
#include "SD_SPI.c"
#define BTN PINB.5
#define BTN_R PINB.4
#define PLCD PORTD.4
unsigned char buff[64]; //буфер дисплея
unsigned char sdtx_buff[250];
unsigned char rx_buff[64];
unsigned char temp=0;
void main(void)
{
// Input/Output Ports initialization
// Port B initialization
// Function: Bit7=In Bit6=In Bit5=In Bit4=In Bit3=In Bit2=Out Bit1=Out Bit0=Out
DDRB=(0<<DDB7) | (1<<DDB6) | (0<<DDB5) | (0<<DDB4) | (0<<DDB3) | (0<<DDB2) | (0<<DDB1) | (1<<DDB0);
// State: Bit7=T Bit6=T Bit5=T Bit4=T Bit3=T Bit2=0 Bit1=0 Bit0=0
PORTB=(0<<PORTB7) | (0<<PORTB6) | (0<<PORTB5) | (0<<PORTB4) | (0<<PORTB3) | (0<<PORTB2) | (0<<PORTB1) | (0<<PORTB0);
// Port C initialization
// Function: Bit7=In Bit6=In
DDRC=(0<<DDC7) | (0<<DDC6);
// State: Bit7=T Bit6=T
PORTC=(0<<PORTC7) | (0<<PORTC6);
// Port D initialization
// Function: Bit7=In Bit6=In Bit5=In Bit4=In Bit3=In Bit2=In Bit1=In Bit0=In
DDRD=(0<<DDD7) | (0<<DDD6) | (0<<DDD5) | (1<<DDD4) | (0<<DDD3) | (0<<DDD2) | (0<<DDD1) | (0<<DDD0);
// State: Bit7=T Bit6=T Bit5=T Bit4=T Bit3=T Bit2=T Bit1=T Bit0=T
PORTD=(0<<PORTD7) | (0<<PORTD6) | (0<<PORTD5) | (0<<PORTD4) | (0<<PORTD3) | (0<<PORTD2) | (0<<PORTD1) | (0<<PORTD0);
// Port E initialization
// Function: Bit6=In Bit2=In
DDRE=(0<<DDE6) | (0<<DDE2);
// State: Bit6=T Bit2=T
PORTE=(0<<PORTE6) | (0<<PORTE2);
// Port F initialization
// Function: Bit7=In Bit6=In Bit5=In Bit4=In Bit1=In Bit0=In
DDRF=(0<<DDF7) | (0<<DDF6) | (0<<DDF5) | (0<<DDF4) | (0<<DDF1) | (0<<DDF0);
// State: Bit7=T Bit6=T Bit5=T Bit4=T Bit1=T Bit0=T
PORTF=(0<<PORTF7) | (0<<PORTF6) | (0<<PORTF5) | (0<<PORTF4) | (0<<PORTF1) | (0<<PORTF0);
PLLCSR=(0<<PINDIV) | (0<<PLLE) | (0<<PLOCK);
PLLFRQ=(0<<PINMUX) | (0<<PLLUSB) | (0<<PLLTM1) | (0<<PLLTM0) | (0<<PDIV3) | (0<<PDIV2) | (0<<PDIV1) | (0<<PDIV0);
delay_ms(500);
PLCD=1;
delay_ms(500);
// Bit-Banged I2C Bus initialization
// I2C Port: PORTD
// I2C SDA bit: 1
// I2C SCL bit: 0
// Bit Rate: 100 kHz
i2c_init();
// DS1307 Real Time Clock initialization
rtc_init(0,0,0);
// DS1307 Real Time Clock initialization
rtc_init(0,0,0);
#asm("sei")
LCD_init();
LCD_Contrast(254);
LCD_Clear();
LCD_Commmand(COMAND, 0xC0);
LCD_Commmand(COMAND, 0xA0);
spi_init();
temp=SD_Init();
LCD_Goto(0,0); sprintf(buff,"SD Init...%d ",temp); LCD_Printf(buff,0); // вывод на дисплей
temp = MEM_AVR_Init();
LCD_Goto(0,1); sprintf(buff,"AMEM8... %d ",temp); LCD_Printf(buff,0); // вывод на дисплей
sprintf(sdtx_buff,"SD next OK");
temp=writeText(sdtx_buff,0);//Запишем блок из буфера
LCD_Goto(0,2); sprintf(buff,"WR povtor %d ",temp); LCD_Printf(buff,0); // вывод на дисплей
sprintf(sdtx_buff,"SD next333 OK");
temp=modifiedText(sdtx_buff,3);//допишем в блок 3 из буфера
SD_Read_Block(sdrx_buff,3); //Считаем блок 3 в буфер
cpyNcpy(sdrx_buff,rx_buff,13,23); LCD_Goto(1,3); LCD_Printf(rx_buff,0); // вывод на дисплей
cpyNcpy(sdrx_buff,rx_buff,24,34); LCD_Goto(1,4); LCD_Printf(rx_buff,0); // вывод на дисплей
clearRX();
LCD_Goto(0,5); sprintf(buff,"WR %d %d ",blokWR,temp); LCD_Printf(buff,0); // вывод на дисплей
while (1)
{
}
}
Все работает! А теперь вытащим флешку, вставим в картридер и откроем её в Hex-редакторе.
На 1 снимке видим систему FAT, далее идет наша подсистема AMEM8. Далее на следующем снимке видим наши дозаписанные данные, а также в начале сектора размер данных и метку записи. К слову — функция дозаписи ест немало памяти RAM — при необходимости её можно удалить, — это уже так сказать на усмотрение разработчика)
Карточку я форматировал в самой обычной системе FAT. В дальнейшем буду использовать данную систему со следующим проектом по логеру электросети, так что скорее всего будут модификации библиотеки.
Файлы:
Вот собственно и всё!
Вообщем комментарии, критика и советы принимаются))) Пишем в комментарии или в группе на фейсбук.




..ошипки громатические ахринеть