Вот и я добрался до карточек памяти, к слову давно еще были попытки но не было времени. Сейчас же предстояло хранить данные, а где их хранить как не на карте памяти! Это ведь замечательный и ёмкий накопитель для нашего 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. В дальнейшем буду использовать данную систему со следующим проектом по логеру электросети, так что скорее всего будут модификации библиотеки.
Файлы:
Вот собственно и всё!
Вообщем комментарии, критика и советы принимаются))) Пишем в комментарии или в группе на фейсбук.
..ошипки громатические ахринеть