Организация журнала событий в EEPROM

chatlanin
Offline
Зарегистрирован: 14.01.2015

Ведется мониторинг некоторых событий. События происходят примерно 2-3 раза в день. Необходимо создать в EEPROM журнал в котором будет указываться дата и время события. Фиксировать достаточно в течении месяца, а затем по кругу. Пока приходит в голову только организации ячейки, в которой будет храниться указатель на текущий адрес. Может есть у кого более красивые идеи?

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Дата же то же хранится. Найти в массиве самую позднюю дату - фигня вопрос ;0)

chatlanin
Offline
Зарегистрирован: 14.01.2015

brokly пишет:

Дата же то же хранится. Найти в массиве самую позднюю дату - фигня вопрос ;0)

Согласен. При малом количестве данных хороший вариант. Спасибо

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

А много у вас во встроенном еепроме не получится .

2009Shef
Offline
Зарегистрирован: 23.10.2014

определить макс размер данных например  6 байт (пусть у нас 512 этих байт в EEPROM = 84 события (85 - 1 так как гдето надо хранить адрес последнего события))

день, месяц, год(двухзначный), час ,минута, секунда 

при настеплении события функция пише все эти числа в EEPROM начиная например с 0 адреса записываем этот адрес +6 в EEPROM в последние 2 байта 

при следующем собитии адрес считываем из EEPROM и вызываем функцию записи собития и т.д. по кругу

для адреса определяем макс и мин значения шлифуем алгоритм и наслаждаемся

надеюсь понятно.

и на последок у нас еще осталось 4 байта EEPROM для других нужд

Вот если кто подскажет как записать событие что питание пропало ???

chatlanin
Offline
Зарегистрирован: 14.01.2015

2009Shef пишет:

для адреса определяем макс и мин значения шлифуем алгоритм и наслаждаемся

надеюсь понятно.

и на последок у нас еще осталось 4 байта EEPROM для других нужд

Вот если кто подскажет как записать событие что питание пропало ???

Эта идея как раз и была высказана в первом сообщении. У меня секунды не учитываются. поэтому дата и время уместилась в 4 байта. 
По вашему вопросу. Старт программы происходит либо после ресета, либо после пропадания питания. Смею догадаться, вас интересует аварийное пропадание питания? Можно попробовать на питание повесить конденсатор. Емкость посчитать от потребления ардуины и времени необходимого для записи состояния аварии в еепром. Конденсатор подключить через диод. При пропадании напряжения до диода, дает сигнал аварии, И пока не разрядился контенсатор, пытаемся успеть записать. Если использовать доп. питание от батареечки, тогда уж совсем просто.

bwn
Offline
Зарегистрирован: 25.08.2014

Тады уж ионистор.

chatlanin
Offline
Зарегистрирован: 14.01.2015

Из вопроса не совсем понятно что значит "питание пропало". Питание может пропасть из за аварийного отключения, либо из за отключения каким либо выключателем. Что бы посоветовать, нужно знать условия пропадания питания. Но без обвеса дополнительными детальками контроллера, явно не обойтись ИМХО.

axill
Offline
Зарегистрирован: 05.09.2011

chatlanin пишет:

Ведется мониторинг некоторых событий. События происходят примерно 2-3 раза в день. Необходимо создать в EEPROM журнал в котором будет указываться дата и время события. Фиксировать достаточно в течении месяца, а затем по кругу. Пока приходит в голову только организации ячейки, в которой будет храниться указатель на текущий адрес. Может есть у кого более красивые идеи?

место под журнал в EEPROM можно выделить сразу? тогда делаем циклический массив фиксированного размера. делается так:

создаем свой тип данных используя struct (фантазирую, так как нет информации о том, что за событие, информация о месяце не нужна - каждый месяц все стирается):

typedef struct {
    uint8_t    month_day;
    uint8_t    hour;
    uint8_t    minute;
    uint8_t    event_type;
} my_struct;

далее делаем массив. Можно применить распределение памяти самим компилятором:

my_struct log_eeprom[LOG_SIZE] EEMEM;

или самому инициализировав ссылку например на начало EEPROM:

uint16_t log_address = 0;

теперь объявляем глобальную переменную которая содержит индекс текущей свободной ячейки:

uint16_t log_index = 0;

адрес для записи новой записи в EEPROM вычисляем так:

// если адрес задан компилятором используя EEMEM
addr = &log_eeprom[log_index];

// если адреса задали сами
addr = log_address + log_index * sizeof(my_struct);

теперь после записи данных в EEPROM сдвигаем индекс:

if(++log_index == LOG_SIZE) log_index = 0;

 

axill
Offline
Зарегистрирован: 05.09.2011

чтобы успеть записать данные о факте выключения питания можно сделать двумя способами

1. записать данные до потери питания. потребуется какой-то буфер для питания МК на вермя необзодимое для записи. Нужно отделить внешний источник питания от МК диодом и один из входов МК подключить до диода. Тогда мы сможем узнать о пропаже питания по прерыванию до того момента когда разрядится буферный элемент. Буфером может быть ионистор, но нужно будет позаботиться о том, чтобы от ионистора питался только сам МК, чтобы переферия не разрядила ионистор быстрее чем нужно.

2. вариант не требует изменений в железе. Время от времени (скажем каждые 10 минут) записываем по последнему log_index событие сбоя питания без сдвига индекса. Если происходит событие - оно перетирает событие. Если происходит сбой питания - это событие остается

Но если нужны события питания в логе, тогда нужно еще созхранять в EEPROM и сам log_index

chatlanin
Offline
Зарегистрирован: 14.01.2015

axill пишет:

теперь после записи данных в EEPROM сдвигаем индекс:

if(++log_index == LOG_SIZE) log_index = 0;

Это конечно здорово, но при выключении питания индекс потеряется. Его либо писать тоже в епром, либо искать его исходя из того, что в журнал пишетя дата/время, и естественно последовательно. Реализовал второй вариант. В журнал (eeprom) пишется дата время без секунд, свернутое в 24 бита.  и один байт для идентификации события. 

union myUnion {
  unsigned long   longDateTime;
  char            charDateTime[4];
};
........
void Save (int address, byte flag, Time t) {
  unsigned long tflag=flag;
  unsigned long tyear=t.year-2000;
  unsigned long tmon=t.mon;
  unsigned long tdate=t.date;
  unsigned long thour=t.hour;
  unsigned long tmin=t.min;
  unsigned long tsec=t.sec;
  myUnion lcDateTime;
  lcDateTime.longDateTime=tflag<<24 | tyear<<20 | tmon<<16 | tdate<<11 | thour<<6 | tmin;
  for (byte i=0; i<=3; i++) EEPROM.write(address+i, lcDateTime.charDateTime[i]);
}
В flag идентификатор события

 

chatlanin
Offline
Зарегистрирован: 14.01.2015

axill пишет:

2. вариант не требует изменений в железе. Время от времени (скажем каждые 10 минут) записываем по последнему log_index событие сбоя питания без сдвига индекса. Если происходит событие - оно перетирает событие. Если происходит сбой питания - это событие остается

Сбой питания может произойти совершенно случайно. И как карта ляжет в этот момент неизвестно. Может произойти во время перезаписи или между временем наступления события и записью в еепром. Учитывать или нет это, зависит от сукна на картежном столе:-)

axill
Offline
Зарегистрирован: 05.09.2011

ну вы не всю задачу описали, поэтому что-то точное рекомендовать сложно

если говорть про ваш кусок кода, то можно сделать чуть проще:


uint32_t x = tflag<<24 | tyear<<20 | tmon<<16 | tdate<<11 | thour<<6 | tmin;
eeprom_write_block(&x, address, 4);

 

axill
Offline
Зарегистрирован: 05.09.2011

chatlanin пишет:

Сбой питания может произойти совершенно случайно. И как карта ляжет в этот момент неизвестно. Может произойти во время перезаписи или между временем наступления события и записью в еепром. Учитывать или нет это, зависит от сукна на картежном столе:-)

ну да. но на что это влияет? если есть понимание какую задачу решаем, все это решается. второй вариант компромиссный, он решает задачу с оговорками, но проще чем первый. смысл спорить если ваша задача не определена полностью? не ясно что нужно, какая конечная цель, какие допуски могут быть приняты и т.д. Это все важно.

chatlanin
Offline
Зарегистрирован: 14.01.2015

axill пишет:

ну вы не всю задачу описали, поэтому что-то точное рекомендовать сложно

если говорть про ваш кусок кода, то можно сделать чуть проще:


uint32_t x = tflag<<24 | tyear<<20 | tmon<<16 | tdate<<11 | thour<<6 | tmin;
eeprom_write_block(&x, address, 4);

Это задача и вопрос были не мои. А за укорочение кода, спасибо. 

chatlanin
Offline
Зарегистрирован: 14.01.2015

Прошу прощения, но что то не могу найти библиотеку в которой 
eeprom_write_block

я так понимаю avr/eeprom.h Не подскажете ссылочку

axill
Offline
Зарегистрирован: 05.09.2011

да, она, здесь описание на русском http://avr-libc.narod.ru/group__avr__eeprom.html

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Странно, что никто не поднимает вопрос ограничения связанного с конечным количеством раз записи в еепром. Если указатель делать в епроме, то это будет ячейка ограничивающая время жизни всего устройства. Если лень искать по дате - обнуляйте ячейку следующую за последней записью, по ней и бутете находить место записи. Ведь в дате нет нулей, только время может принимать нулевое значение.

chatlanin
Offline
Зарегистрирован: 14.01.2015

brokly пишет:

Странно, что никто не поднимает вопрос ограничения связанного с конечным количеством раз записи в еепром. Если указатель делать в епроме, то это будет ячейка ограничивающая время жизни всего устройства. Если лень искать по дате - обнуляйте ячейку следующую за последней записью, по ней и бутете находить место записи. Ведь в дате нет нулей, только время может принимать нулевое значение.

Дата и время объеденино в 3 байта. 6 бит на минуты, по 5 на часы и день 4 на месяцы 4 на год. Нулевыми такое слово быть не может. Такие данные легко сравнивать на больше меньше. Сделал поиск максимального значения. В следующую пишу новое значение. И так по кругу.  

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

А ну тогда все красиво.

axill
Offline
Зарегистрирован: 05.09.2011

гарантированный ресурс EEPROM 10000 циклов записи. Если события хранятся месяц, то значит и перетираются раз в месяц, выходит ресурс 833 лет

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Эх... не доживу :( А так хочется.....

pastry777
Offline
Зарегистрирован: 16.01.2014

К чему эти сложности с еепром,ведь гораздо удобнее для этой цели прикрутить sd и не ограничиваться никак...

chatlanin
Offline
Зарегистрирован: 14.01.2015

Набросал класс для ведения журнала событий в EEPROM. Первый опыт работы с ардуино, так что не пинайте уж слишком. Создается структура с начальным и конечным адресом в EEPROM. Умеет записывать по кругу, в указанных адресах дату и время А так же 2 битное значения события. И естественно все это читать, и очищать.

#include <avr/eeprom.h>
#include <DS1302.h>

#define lenword 4
#define Adress(__No) (__No*lenword+MinIndex)

struct Rec {
	byte flag;
	Time DateTime;
};
class Log
{
public:
	Log(int _MinIndex, int _MaxIndex);
	boolean MoveLast();
	boolean MoveNext();
	boolean MovePrevious();
	void AddNew(Rec t);
	Rec Read();
	void Clear();
private:
	int MinIndex, MaxIndex;
	int QuantRecord, LastIdx, FerstIdx, Idx;
};
Log::Log(int _MinIndex, int _MaxIndex){
	MinIndex = _MinIndex;
	MaxIndex = _MaxIndex;
	QuantRecord = int((MaxIndex - MinIndex) / lenword);
	Idx = 0; FerstIdx = 0;
	uint32_t _lDT = 0;
	uint32_t _lDTNew = 0;
	for (int i = 0; i <= QuantRecord; i++) {
		eeprom_read_block(&_lDTNew, (void*)Adress(i), 4);
		_lDTNew = _lDTNew & 0x3FFFFFFF;
		if (_lDTNew < _lDT) FerstIdx = i;
		if (_lDTNew >= 0x3FFFFFFF || _lDTNew < _lDT) {
			Idx = i - 1;
			break;
		}
		_lDT = _lDTNew;
	}
	LastIdx = Idx;
}
boolean Log::MoveNext()
{
	if (Idx == LastIdx) return false;
	if (++Idx >= QuantRecord) Idx = 0;
	return true;
}
boolean Log::MovePrevious()
{
	if (Idx == FerstIdx) return false;
	if (--Idx < 0) Idx = QuantRecord-1;
	return true;
}
boolean Log::MoveLast()
{
	if (Idx == -1) return false;
	Idx = LastIdx;
	return true;
}
Rec Log::Read() {
	uint32_t _lDT;
	Rec _Read;
	eeprom_read_block(&_lDT, (void*)Adress(Idx), 4);
	_Read.flag = (_lDT & uint32_t(B11) << 30) >> 30;
	_Read.DateTime.year = ((_lDT & uint32_t(B1111) << 26) >> 26) + 2015;
	_Read.DateTime.mon = (_lDT & uint32_t(B1111) << 22) >> 22;
	_Read.DateTime.date = (_lDT & uint32_t(B11111) << 17) >> 17;
	_Read.DateTime.hour = (_lDT & uint32_t(B11111) << 12) >> 12;
	_Read.DateTime.min = (_lDT & uint32_t(B111111) << 6) >> 6;
	_Read.DateTime.sec = (_lDT & uint32_t(B111111));
	return _Read;
}
void Log::AddNew(Rec t) {
	if (++LastIdx >= QuantRecord) LastIdx = 0;
	if (Idx >= 0 && LastIdx == FerstIdx) FerstIdx++;
	if (FerstIdx >= QuantRecord) FerstIdx = 0;
	Idx = LastIdx;
	uint32_t lDateTime = uint32_t(t.flag) << 30 | uint32_t(t.DateTime.year - 2015) << 26 | uint32_t(t.DateTime.mon) << 22 | uint32_t(t.DateTime.date) << 17 | uint32_t(t.DateTime.hour) << 12 | uint32_t(t.DateTime.min) << 6 | uint32_t(t.DateTime.sec);
	eeprom_write_block(&lDateTime, (void*)Adress(Idx), 4);
}
void Log::Clear() {
	uint32_t lDateTime = 0xFFFFFFFF;
	eeprom_write_block(&lDateTime, (void*)Adress(0), 4);
	Idx = -1;
	LastIdx = Idx;
	FerstIdx = 0;
}

Ну и пример использования

#include <logEEPROM.h>
#include <DS1302.h>

Log LОG(241, 255);
DS1302 rtc(2, 3, 4);


void setup()
{
	rtc.halt(false);                         // Запуск часов
	rtc.writeProtect(false);
	Serial.begin(9600);
}

void loop()
{
	Rec t;
	char output[23]; 
	if (Serial.available() > 0) {
		int incomingByte = Serial.parseInt();
		switch (incomingByte) {
		// Чтение последней записи
		case 1:
			if (LОG.MoveLast()) {
				t = LОG.Read();
				sprintf(output, "%02d:%02d:%02d %02d-%02d-%04d Flag:%01d", t.DateTime.hour, t.DateTime.min, t.DateTime.sec, t.DateTime.date, t.DateTime.mon, t.DateTime.year, t.flag);
				Serial.println(output);
			}
			break;
		// Чтение текущей записи
		case 2:
			t = LОG.Read();
			sprintf(output, "%02d:%02d:%02d %02d-%02d-%04d Flag:%01d", t.DateTime.hour, t.DateTime.min, t.DateTime.sec, t.DateTime.date, t.DateTime.mon, t.DateTime.year, t.flag);
			Serial.println(output);
			break;
		// Чтение предидущей записи
		case 3:
			if (LОG.MovePrevious()) {
				t = LОG.Read();
				sprintf(output, "%02d:%02d:%02d %02d-%02d-%04d Flag:%01d", t.DateTime.hour, t.DateTime.min, t.DateTime.sec, t.DateTime.date, t.DateTime.mon, t.DateTime.year, t.flag);
				Serial.println(output);
			}
			break;
		// Чтение следующей записи
		case 4:
			if (LОG.MoveNext()) {
				t = LОG.Read();
				sprintf(output, "%02d:%02d:%02d %02d-%02d-%04d Flag:%01d", t.DateTime.hour, t.DateTime.min, t.DateTime.sec, t.DateTime.date, t.DateTime.mon, t.DateTime.year, t.flag);
				Serial.println(output);
			}
			break;
		// Добавить новую запись
		case 5:
			t.DateTime = rtc.getTime();;
			t.flag = 2;
			LОG.AddNew(t);
			break;
		// Очистить все записи
		case 0:
			LОG.Clear();
			break;
		}
	}
}