Работаем с micro-SD картами памятью, или FS AMEM8 для AVR

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

Файлы:

Вот собственно и всё!

Вообщем комментарии, критика и советы принимаются))) Пишем в комментарии или в группе на фейсбук.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *