Pull to refresh

Реализация мьютекса вне ОС на примере микроконтроллера AVR и шины TWI

Reading time20 min
Views12K
Решил однажды для себя я соорудить погодную станцию. Датчики там разные, в том числе на шине I2C. И как годится, обычно вначале, сделал все на флагах ожидания. Но путь настоящего джедая иной, и было решено все повесить на прерывания. Вот тут и начался геморрой. Проблема, с которой я столкнулся это обработка нескольких подряд идущих запросов. Например датчик давления BMP085 для дальнейшей работы с ним, требует вытянуть из его EEPROM 11 калибровочных констант:


О том как я пришел к решению и последовательность хода мыслей изложены ниже.

Флаги ожидания

Определим функции get_AC1, get_AC2,…, get_MD. Каждая из них получает соответствующую константу из EEPROM датчика по шине I2C. Формат посылки из даташита:


И пример кода для функции get_AC1:

get_AC1
int get_AC1(void)
{
	union {
		unsigned int Word;
		unsigned char Byte[2];
	} AC1;						// Определяем AC1
	// ----------------------------------------
	TWCR =  (1<<TWEN)|					// Модуль TWI включен
			(0<<TWIE)|(1<<TWINT)|			// Очищаем флаг
			(0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)|		// Даем START сигнал
			(0<<TWWC);
	while (!(TWCR & (1<<TWINT)))			// Ждем установки флага TWINT
		;
	// ----------------------------------------
	TWDR = 0xEE;					// Загружаем SLA+W
	TWCR =  (1<<TWEN)|					// Модуль TWI включен
			(0<<TWIE)|(1<<TWINT)|			// Очищаем флаг
			(0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|		// Отсылаем SLA+W
			(0<<TWWC);
	while (!(TWCR & (1<<TWINT)))			// Ждем установки флага TWINT
		;
	// ----------------------------------------
	TWDR = 0xAA;					// Загружаем 0xAA
	TWCR =  (1<<TWEN)|					// Модуль TWI включен
			(0<<TWIE)|(1<<TWINT)|			// Очищаем флаг
			(0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|		// Отсылаем 0xAA
			(0<<TWWC);
	while (!(TWCR & (1<<TWINT)))			// Ждем установки флага TWINT
		;
	// ----------------------------------------
	TWCR =  (1<<TWEN)|					// Модуль TWI включен
			(0<<TWIE)|(1<<TWINT)|			// Очищаем флаг
			(0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)|		// Даем REPEATED START сигнал
			(0<<TWWC);
	while (!(TWCR & (1<<TWINT)))			// Ждем установки флага TWINT
		;
	// ----------------------------------------
	TWDR = 0xEF;					// Загружаем SLA+R
	TWCR =	(1<<TWEN)|				// Модуль TWI включен
			(0<<TWIE)|(1<<TWINT)|			// Очищаем флаг
			(0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|		// Отсылаем SLA+R
			(0<<TWWC);
	while (!(TWCR & (1<<TWINT)))			// Ждем установки флага TWINT
		;
	// ----------------------------------------
	TWCR =  (1<<TWEN)|					// Модуль TWI включен
			(0<<TWIE)|(1<<TWINT)|			// Очищаем флаг
			(1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|		// Посылаем запрос на чтение байта и ACK
			(0<<TWWC);
	while (!(TWCR & (1<<TWINT)))			// Ждем установки флага TWINT
		;
	// ----------------------------------------
	AC1.Byte[1] = TWDR;					// Читаем MSB
	TWCR =  (1<<TWEN)|					// Модуль TWI включен
			(0<<TWIE)|(1<<TWINT)|			// Очищаем флаг
			(0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|		// Посылаем запрос на чтение байта и NACK
			(0<<TWWC);
	while (!(TWCR & (1<<TWINT)))			// Ждем установки флага TWINT
		;
	// ----------------------------------------
	AC1.Byte[0] = TWDR;					// Читаем LSB
	TWCR =  (1<<TWEN)|					// Модуль TWI включен
			(0<<TWIE)|(1<<TWINT)|			// Очищаем флаг
			(0<<TWEA)|(0<<TWSTA)|(1<<TWSTO)|		// Посылаем STOP сигнал
			(0<<TWWC);
	// ----------------------------------------
	return AC1.Word;
}

А вот ее осциллограмма:


Использование союза union для AC1 связано с разностью порядка байт. Для TWI это big-endian, для AVR — little-endian. Функция get_AC2 отличается лишь посылкой байта 0xAC (смотрим в даташит) вместо 0xAA после команды SLA+W. Во всем остальном функции абсолютно идентичны. Поэтому мы можем определить одну get_Data, которая в качестве параметров будет принимать те самые Register address в соответствии с даташитом:

get_Data
int get_Data(unsigned char Register_adress)
{
	union {
		unsigned int Word;
		unsigned char Byte[2];
	} Data;						// Определяем Data
	// ----------------------------------------
	TWCR =  (1<<TWEN)|					// Модуль TWI включен
			(0<<TWIE)|(1<<TWINT)|			// Очищаем флаг
			(0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)|		// Даем START сигнал
			(0<<TWWC);
	while (!(TWCR & (1<<TWINT)))			// Ждем установки флага TWINT
		;
	// ----------------------------------------
	TWDR = 0xEE;					// Загружаем SLA+W
	TWCR =  (1<<TWEN)|					// Модуль TWI включен
			(0<<TWIE)|(1<<TWINT)|			// Очищаем флаг
			(0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|		// Отсылаем SLA+W
			(0<<TWWC);
	while (!(TWCR & (1<<TWINT)))			// Ждем установки флага TWINT
		;
	// ----------------------------------------
	TWDR = Register_adress;				// Загружаем Register_adress
	TWCR =  (1<<TWEN)|					// Модуль TWI включен
			(0<<TWIE)|(1<<TWINT)|			// Очищаем флаг
			(0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|		// Отсылаем Register_adress
			(0<<TWWC);
	while (!(TWCR & (1<<TWINT)))			// Ждем установки флага TWINT
		;
	// ----------------------------------------
	TWCR =  (1<<TWEN)|					// Модуль TWI включен
			(0<<TWIE)|(1<<TWINT)|			// Очищаем флаг
			(0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)|		// Даем REPEATED START сигнал
			(0<<TWWC);
	while (!(TWCR & (1<<TWINT)))			// Ждем установки флага TWINT
		;
	// ----------------------------------------
	TWDR = 0xEF;					// Загружаем SLA+R
	TWCR =  (1<<TWEN)|					// Модуль TWI включен
			(0<<TWIE)|(1<<TWINT)|			// Очищаем флаг
			(0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|		// Отсылаем SLA+R
			(0<<TWWC);
	while (!(TWCR & (1<<TWINT)))			// Ждем установки флага TWINT
		;
	// ----------------------------------------
	TWCR =  (1<<TWEN)|					// Модуль TWI включен
			(0<<TWIE)|(1<<TWINT)|			// Очищаем флаг
			(1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|		// Посылаем запрос на чтение байта и ACK
			(0<<TWWC);
	while (!(TWCR & (1<<TWINT)))			// Ждем установки флага TWINT
		;
	// ----------------------------------------
	Data.Byte[1] = TWDR;				// Читаем MSB
	TWCR =  (1<<TWEN)|					// Модуль TWI включен
			(0<<TWIE)|(1<<TWINT)|			// Очищаем флаг
			(0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|		// Посылаем запрос на чтение байта и NACK
			(0<<TWWC);
	while (!(TWCR & (1<<TWINT)))			// Ждем установки флага TWINT
		;
	// ----------------------------------------
	Data.Byte[0] = TWDR;				// Читаем LSB
	TWCR =  (1<<TWEN)|					// Модуль TWI включен
			(0<<TWIE)|(1<<TWINT)|			// Очищаем флаг
			(0<<TWEA)|(0<<TWSTA)|(1<<TWSTO)|		// Посылаем STOP сигнал
			(0<<TWWC);
	// ----------------------------------------
	return Data.Word;
}

а дальше просто:

struct {
	short AC1;
	short AC2;
	short AC3;
	unsigned short AC4;
	unsigned short AC5;
	unsigned short AC6;
	short B1;
	short B2;
	short MB;
	short MC;
	short MD;
} BMP085_EEPROM;
...
static void get_EEPROM(void)
{
	BMP085_EEPROM.AC1 = get_Data(0xAA);
	BMP085_EEPROM.AC2 = get_Data(0xAC);
	BMP085_EEPROM.AC3 = get_Data(0xAE);
	BMP085_EEPROM.AC4 = get_Data(0xB0);
	BMP085_EEPROM.AC5 = get_Data(0xB2);
	BMP085_EEPROM.AC6 = get_Data(0xB4);
	BMP085_EEPROM.B1 = get_Data(0xB6);
	BMP085_EEPROM.B2 = get_Data(0xB8);
	BMP085_EEPROM.MB = get_Data(0xBA);
	BMP085_EEPROM.MC = get_Data(0xBC);
	BMP085_EEPROM.MD = get_Data(0xBE);
}

Вот что получилось в итоге:


Работа на прерываниях


Предисловие


Как видно из рисунка выше, длительность одной операции составляет 488 мкс, для частоты передачи шины в 100 кГц, что составляет 3094 такта процессора на частоте 8 МГц. Ну это ни в какие ворота ребята. Конечно, можно повысить частоту, если целевое устройство позволяет. Например для 400 кГц длительность составляет 128 мкс или 1024 такта


Ожидание поднятия флага — операция абсолютно бесполезная. За тысячи тактов процессор может сделать много полезной работы, например, выполнить операцию деления чисел с плавающей запятой. Поэтому единственным адекватным выходом из этой ситуации является использование прерывания.

Работа на прерываниях

Определим структуру:
struct {
	unsigned char SLA;		// Slave address
	unsigned char *pW;		// Откуда?
	unsigned char nW;		// Сколько?
	unsigned char *pR;		// Куда?
	unsigned char nR;		// Сколько?
} TWI;

а теперь и нашу функцию get_AC1:
unsigned char Register_address;
...
void get_AC1(void)
{
	Register_address = 0xAA;				// Определяем Register address
	TWI.SLA = 0x77;					// Slave address нашего BMP085
	TWI.pW = &Register_address;				// Указатель откуда брать данные для записи
	TWI.nW = 1;						// Сколько байт записывать?
	TWI.pR = &BMP085_EEPROM.AC1;			// Указатель куда считывать?
	TWI.nR = 2;						// Сколько байт считывать?
	TWCR =  (1<<TWEN)|					// Модуль TWI включен
			(1<<TWIE)|(1<<TWINT)|			// Разрешаем прерывания! Очищаем флаг
			(0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)|		// Даем START сигнал
			(0<<TWWC);
}

Пара слов об обработчике прерывания. Если nW = n, nR = 0, формат фрейма:

если nW = 0, nR = m:

и, если nW = n, nR = m:

К слову обработчик прерывания может быть каким угодно. Логику конечного автомата можно реализовать по-разному. Пример моего показан ниже:
Обработчик прерывания
ISR(TWI_vect)
{
/* -----------------------------------------------------------------------------------
	Jump table which is stored in flash
------------------------------------------------------------------------------------*/
	static const void * const twi_list[] PROGMEM = {&&TWI_00, &&TWI_08,
													&&TWI_10, &&TWI_18,
													&&TWI_20, &&TWI_28,
													&&TWI_30, &&TWI_38,
													&&TWI_40, &&TWI_48,
													&&TWI_50, &&TWI_58,
													&&TWI_60, &&TWI_68,
													&&TWI_70, &&TWI_78,
													&&TWI_80, &&TWI_88,
													&&TWI_90, &&TWI_98,
													&&TWI_A0, &&TWI_A8,
													&&TWI_B0, &&TWI_B8,
													&&TWI_C0, &&TWI_C8,
													&&TWI_F8};
														  
/* -----------------------------------------------------------------------------------
	Jump to label, address of which is in twi_list[TWSR>>3]
------------------------------------------------------------------------------------*/
	goto *(pgm_read_word(&(twi_list[TWSR>>3])));
	
/* -----------------------------------------------------------------------------------
	Bus error handler
------------------------------------------------------------------------------------*/
TWI_00:
	// STOP condition will be generated
	goto STOP;

/* -----------------------------------------------------------------------------------
	A START condition has been transmitted
	A repeated START condition has been transmitted
	
	nW = nR = 0:		STOP condition will be generated
	nW > 0, nR - don't care:	SLA+W will be send
	nW = 0, nR > 0:		SLA+R will be send
------------------------------------------------------------------------------------*/
TWI_08:
TWI_10:
	if (TWI.nW != 0)
		// SLA+W will be send
		TWDR = (TWI.SLA)<<1;
	else if (TWI.nR != 0)
		// SLA+R will be send
		TWDR = (TWI.SLA)<<1 | 1<<0;
	else
		// STOP condition will be generated
		goto STOP;
	TWCR =  (1<<TWEN)|					// Модуль TWI включен
			(1<<TWIE)|(1<<TWINT)|			// Разрешаем прерывания! Очищаем флаг
			(0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|		// Отсылаем SLA+R/W
			(0<<TWWC);
	return;

/* -----------------------------------------------------------------------------------
	SLA+W has been transmitted; ACK has been received
	Data byte has been transmitted;	ACK has been received
	
	nW > 0, nR - don't care:	Data byte will be transmitted and ACK or NOT ACK will be received
	nW = 0, nR > 0:		Repeated START will be transmitted
	nW = nR = 0:		STOP condition will be generated
------------------------------------------------------------------------------------*/
TWI_18:
TWI_28:
	if (TWI.nW != 0)
	{
		// Data byte will be transmitted and ACK or NOT ACK will be received
		TWDR = *TWI.pW++;
		TWCR =  (1<<TWEN)|				// Модуль TWI включен
				(1<<TWIE)|(1<<TWINT)|			// Разрешаем прерывания! Очищаем флаг
				(0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|	// Отсылаем данные
				(0<<TWWC);
		TWI.nW--;
	}
	else if (TWI.nR != 0)
		// Repeated START will be transmitted
		TWCR =  (1<<TWEN)|				// Модуль TWI включен
				(1<<TWIE)|(1<<TWINT)|			// Разрешаем прерывания! Очищаем флаг
				(0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)|	// Даем START сигнал
				(0<<TWWC);
	else
		// STOP condition will be generated
		goto STOP;
	return;
	
/* -----------------------------------------------------------------------------------
	SLA+W has been transmitted; NOT ACK has been received
------------------------------------------------------------------------------------*/
TWI_20:
	// STOP condition will be generated
	goto STOP;
	
/* -----------------------------------------------------------------------------------
	Data byte has been transmitted; NOT ACK has been received
------------------------------------------------------------------------------------*/
TWI_30:
	// STOP condition will be generated
	goto STOP;

/* -----------------------------------------------------------------------------------
	Arbitration lost in SLA+W or data bytes
	Arbitration lost in SLA+R or NOT ACK bit
------------------------------------------------------------------------------------*/
TWI_38:
	// STOP condition will be generated
	goto STOP;

/* -----------------------------------------------------------------------------------
	SLA+R has been transmitted; ACK has been received
	
	nR = 1:			Data byte will be received and NOT ACK will be returned
	nR > 1:			Data byte will be received and ACK will be returned
------------------------------------------------------------------------------------*/
TWI_40:
	if (TWI.nR == 1)
		// Data byte will be received and NOT ACK will be returned
		TWCR =  (1<<TWEN)|				// Модуль TWI включен
				(1<<TWIE)|(1<<TWINT)|			// Разрешаем прерывания! Очищаем флаг
				(0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|	// Запрос на чтение + NACK
				(0<<TWWC);
	else
		// Data byte will be received and ACK will be returned
		TWCR =  (1<<TWEN)|				// Модуль TWI включен
				(1<<TWIE)|(1<<TWINT)|			// Разрешаем прерывания! Очищаем флаг
				(1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|	// Запрос на чтение + ACK
				(0<<TWWC);
	return;

/* -----------------------------------------------------------------------------------
	SLA+R has been transmitted; NOT ACK has been received
------------------------------------------------------------------------------------*/
TWI_48:
	// STOP condition will be generated
	goto STOP;

/* -----------------------------------------------------------------------------------
	Data byte has been received; ACK has been returned
	
	nR = 2:		Data byte will be received and NOT ACK will be returned
	nR > 2:		Data byte will be received and ACK will be returned
------------------------------------------------------------------------------------*/
TWI_50:
	// Read data
	*TWI.pR++ = TWDR;
	if (TWI.nR-- == 2)
		// Data byte will be received and NOT ACK will be returned
		TWCR =  (1<<TWEN)|				// Модуль TWI включен
				(1<<TWIE)|(1<<TWINT)|			// Разрешаем прерывания! Очищаем флаг
				(0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|	// Запрос на чтение + NACK
				(0<<TWWC);
	else
		// Data byte will be received and ACK will be returned
		TWCR =  (1<<TWEN)|				// Модуль TWI включен
				(1<<TWIE)|(1<<TWINT)|			// Разрешаем прерывания! Очищаем флаг
				(1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|	// Запрос на чтение + ACK
				(0<<TWWC);
	return;

/* -----------------------------------------------------------------------------------
	Data byte has been received; NOT ACK has been returned
	
	Repeated START will be transmitted
	STOP condition will be transmitted and TWSTO Flag will be reset
	STOP condition followed by a START condition will be transmitted and TWSTO Flag will be reset
------------------------------------------------------------------------------------*/
TWI_58:
	// Read data
	*TWI.pR = TWDR;
TWI_60:
TWI_68:
TWI_70:
TWI_78:
TWI_80:
TWI_88:
TWI_90:
TWI_98:
TWI_A0:
TWI_A8:
TWI_B0:
TWI_B8:
TWI_C0:
TWI_C8:
TWI_F8:
	// STOP condition will be transmitted and TWSTO Flag will be reset
STOP:
	TWCR =  (1<<TWEN)|				// Модуль TWI включен
			(1<<TWIE)|(1<<TWINT)|		// Разрешаем прерывания! Очищаем флаг
			(0<<TWEA)|(0<<TWSTA)|(1<<TWSTO)|	// Запрос на чтение + NACK
			(0<<TWWC);
}

ну и результат работы:


как мы видим время выполнения увеличилось с 488 мкс до 538 мкс. Связано это с переходом в и возвратом из обработчика, а также вычислением адреса прыжка по lookup table. Но самое важное что вся передача ведется аппаратно. А значит после выполнения маленькой функции get_AC1, которая длится всего 3,5 мкс или 28 тактов, мы можем смело заниматься другими делами, а не тупить в цикле ожидания.


Давайте посмотрим что будет если вызывать функции get_AC1, get_AC2,…, get_MD по порядку:


Выполнится лишь одна, get_MD, а все потому что:


последняя функция загружает в структуру данные еще до завершения SLA+W, поэтому Register_address соответствует ей и равен 0xBE. На самом деле это пожалуй самый безобидный сценарий. Ведь будь передача чуть быстрее Register_address соответствовал бы функции get_AC5 например, а записали бы мы его и вовсе в BMP085_EEPROM.MD. То есть ожидаем AC1, получаем AC5, сохранённое в MD. А если бы вместо get_AC5 была бы другая, со своим SLA, мы могли бы отослать половину адреса одного, а вторую другого и получили бы NACK, а там от логики обработчика прерывания, либо программа зациклиться, либо выкинет СТОП. Из этой ситуации есть один очевидный выход. Не запускать следующую функцию, пока не закончилась предыдущая. То есть запуская get_AC1 выставляем флаг в 1, а обработчик, отработав СТОП его сбросит. А get_AC2 не запустится, пока этот флаг не сброшен. Да, это выход, но теряется вся прелесть прерываний, которые в данном случае используются аж на 0%. Уж лучше флаговый автомат. Но есть еще одно красивое решение.

Аналог мьютекса

Функции get_AC1,…, get_MD не вызывают передачу данных непосредственно. Они вызывают другую функцию, которая кладет их в очередь и сама инициализирует начало передачи, если очередь не пуста. Перепишем нашу структуру, создав массив структур:
#define size 8
...
typedef struct {
	unsigned char SLA;		// Slave address
	unsigned char *pW;		// Откуда?
	unsigned char nW;		// Сколько?
	unsigned char *pR;		// Куда?
	unsigned char nR;		// Сколько?
} twi;
twi TWI[size];


Посмотрим как будет выглядеть функция get_AC1 теперь:
unsigned char buf[size];
...
void get_AC1(void)
{
	volatile twi *pl;		// Указатель на структуру типа twi	
	buf[0] = 0xAA;		// Определяем Register address
	pl->SLA = 0x77;		// Slave address нашего BMP085
	pl->pW = buf;		// Указатель откуда брать данные для записи
	pl->nW = 1;			// Сколько байт записывать?
	pl->pR = buf;		// Указатель куда считывать?
	pl->nR = 2;			// Сколько байт считывать?
	Scheduler(pl);		// Вызываем планировщик очереди
}

Практически также, только вместо инициализации СТАРТ состояния мы вызываем функцию Scheduler, передавая ей в качестве параметра указатель на структуру типа twi, в которой содержаться все необходимые для TWI фрейма данные. Рассмотрим основные функции планировщика и некоторые изменения обработчика:
  • получая указатель функция копирует все данные в структуру TWI[i], где i — текущий номер элемента массива структур, после чего он увеличивается на 1; если i вываливается за пределы размерности массива, он обнуляется; таким образом мы превращаем массив структур в кольцевой буфер;
  • переменная j указывает на элемент который будет обрабатываться в обработчике прерывания, после чего она увеличивается на 1; если j вываливается за пределы размерности массива, она обнуляется;
  • если i догоняет j (переполнение очереди) — запись в массив структур не возможна;
  • переменная flag инициализируется 0;
  • если i не равно j (очередь не пуста) и flag = 0, то flag = 1, запускаем СТАРТ состояние;
  • в обработчике после отработки фрейма j увеличиваем на 1, таким образом она будет указывать на следующий фрейм;
  • в обработчике после отработки фрейма и изменения j, если i не равно j, даем СТОП-СТАРТ состояние, в противном случае СТОП и flag = 0;

Не стоит брать в голову то что написано выше. Лучше посмотреть небольшую презентацию, которая прояснит некоторые моменты вышесказанного:


Функция-планировщик Sheduler:
void Scheduler(twi *pl)
{
	if (tail-head !=1 && head-tail != size-1)		// Если буфер не переполнен, то
	{
		twi *pg = &TWI[head];				// Получаем казатель на текущую ячейку
		pg->SLA = pl->SLA;				// Копируем SLA
		pg->pW = pl->pW;				// Копируем *pW
		pg->nW = pl->nW;				// Копируем nW
		pg->pR = pl->pR;				// Копируем *pR
		pg->nR = pl->nR;				// Копируем nR
		head = (head+1)&(size-1);			// Если номер элемента выходит за размерность - обнуляем
		if (!flag.twi_run)				// Если модуль TWI еще не запущен (флаг равен 0)
		{
			flag.twi_run = 1;				// Устанавливаем флаг в 1
			TWCR =  (1<<TWEN)|				// Заупскаем TWI модуль
					(1<<TWIE)|(1<<TWINT)|
					(0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)|
					(0<<TWWC);
		}
	}
}

Ну и сам обработчик:
Переделанный обработчик прерывания
ISR(TWI_vect)
{
	twi *p = &TWI[tail];
/* -----------------------------------------------------------------------------------
	Jump table which is stored in flash
------------------------------------------------------------------------------------*/
	static const void * const twi_list[] PROGMEM = {&&TWI_00, &&TWI_08,
													&&TWI_10, &&TWI_18,
													&&TWI_20, &&TWI_28,
													&&TWI_30, &&TWI_38,
													&&TWI_40, &&TWI_48,
													&&TWI_50, &&TWI_58,
													&&TWI_60, &&TWI_68,
													&&TWI_70, &&TWI_78,
													&&TWI_80, &&TWI_88,
													&&TWI_90, &&TWI_98,
													&&TWI_A0, &&TWI_A8,
													&&TWI_B0, &&TWI_B8,
													&&TWI_C0, &&TWI_C8,
													&&TWI_F8};
														  
/* -----------------------------------------------------------------------------------
	Jump to label, address of which is in twi_list[TWSR>>3]
------------------------------------------------------------------------------------*/
	goto *(pgm_read_word(&(twi_list[TWSR>>3])));
	
/* -----------------------------------------------------------------------------------
	Bus error handler
------------------------------------------------------------------------------------*/
TWI_00:
	// STOP condition will be generated
	goto STOP;

/* -----------------------------------------------------------------------------------
	A START condition has been transmitted
	A repeated START condition has been transmitted
	
	nW = nR = 0:		STOP condition will be generated
	nW > 0, nR - don't care:	SLA+W will be send
	nW = 0, nR > 0:		SLA+R will be send
------------------------------------------------------------------------------------*/
TWI_08:
TWI_10:
	if (p->nW != 0)
		// SLA+W will be send
		TWDR = p->SLA<<1;
	else if (p->nR != 0)
		// SLA+R will be send
		TWDR = p->SLA<<1 | 1<<0;
	else
		// STOP condition will be generated
		goto STOP;
	TWCR =  (1<<TWEN)|					// Модуль TWI включен
			(1<<TWIE)|(1<<TWINT)|			// Разрешаем прерывания! Очищаем флаг
			(0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|		// Отсылаем SLA+R/W
			(0<<TWWC);
	return;

/* -----------------------------------------------------------------------------------
	SLA+W has been transmitted; ACK has been received
	Data byte has been transmitted;	ACK has been received
	
	nW > 0, nR - don't care:	Data byte will be transmitted and ACK or NOT ACK will be received
	nW = 0, nR > 0:		Repeated START will be transmitted
	nW = nR = 0:		STOP condition will be generated
------------------------------------------------------------------------------------*/
TWI_18:
TWI_28:
	if (p->nW != 0)
	{
		// Data byte will be transmitted and ACK or NOT ACK will be received
		TWDR = *p->pW;
		p->pW++;
		TWCR =  (1<<TWEN)|				// Модуль TWI включен
				(1<<TWIE)|(1<<TWINT)|			// Разрешаем прерывания! Очищаем флаг
				(0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|	// Отсылаем данные
				(0<<TWWC);
		p->nW--;
	}
	else if (p->nR != 0)
		// Repeated START will be transmitted
		TWCR =  (1<<TWEN)|				// Модуль TWI включен
				(1<<TWIE)|(1<<TWINT)|			// Разрешаем прерывания! Очищаем флаг
				(0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)|	// Даем START сигнал
				(0<<TWWC);
	else
		// STOP condition will be generated
		goto STOP;
	return;
	
/* -----------------------------------------------------------------------------------
	SLA+W has been transmitted; NOT ACK has been received
------------------------------------------------------------------------------------*/
TWI_20:
	// STOP condition will be generated
	goto STOP;
	
/* -----------------------------------------------------------------------------------
	Data byte has been transmitted; NOT ACK has been received
------------------------------------------------------------------------------------*/
TWI_30:
	// STOP condition will be generated
	goto STOP;

/* -----------------------------------------------------------------------------------
	Arbitration lost in SLA+W or data bytes
	Arbitration lost in SLA+R or NOT ACK bit
------------------------------------------------------------------------------------*/
TWI_38:
	// STOP condition will be generated
	goto STOP;

/* -----------------------------------------------------------------------------------
	SLA+R has been transmitted; ACK has been received
	
	nR = 1:		Data byte will be received and NOT ACK will be returned
	nR > 1:		Data byte will be received and ACK will be returned
------------------------------------------------------------------------------------*/
TWI_40:
	if (p->nR == 1)
		// Data byte will be received and NOT ACK will be returned
		TWCR =  (1<<TWEN)|				// Модуль TWI включен
				(1<<TWIE)|(1<<TWINT)|			// Разрешаем прерывания! Очищаем флаг
				(0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|	// Запрос на чтение + NACK
				(0<<TWWC);
	else
		// Data byte will be received and ACK will be returned
		TWCR =  (1<<TWEN)|				// Модуль TWI включен
				(1<<TWIE)|(1<<TWINT)|			// Разрешаем прерывания! Очищаем флаг
				(1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|	// Запрос на чтение + ACK
				(0<<TWWC);
	return;

/* -----------------------------------------------------------------------------------
	SLA+R has been transmitted; NOT ACK has been received
------------------------------------------------------------------------------------*/
TWI_48:
	// STOP condition will be generated
	goto STOP;

/* -----------------------------------------------------------------------------------
	Data byte has been received; ACK has been returned
	
	nR = 2:		Data byte will be received and NOT ACK will be returned
	nR > 2:		Data byte will be received and ACK will be returned
------------------------------------------------------------------------------------*/
TWI_50:
	// Read data
	*p->pR = TWDR;
	p->pR++;
	if (p->nR-- == 2)
		// Data byte will be received and NOT ACK will be returned
		TWCR =  (1<<TWEN)|				// Модуль TWI включен
				(1<<TWIE)|(1<<TWINT)|			// Разрешаем прерывания! Очищаем флаг
				(0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|	// Запрос на чтение + NACK
				(0<<TWWC);
	else
		// Data byte will be received and ACK will be returned
		TWCR =  (1<<TWEN)|				// Модуль TWI включен
				(1<<TWIE)|(1<<TWINT)|			// Разрешаем прерывания! Очищаем флаг
				(1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|	// Запрос на чтение + ACK
				(0<<TWWC);
	return;

/* -----------------------------------------------------------------------------------
	Data byte has been received; NOT ACK has been returned
	
	Repeated START will be transmitted
	STOP condition will be transmitted and TWSTO Flag will be reset
	STOP condition followed by a START condition will be transmitted and TWSTO Flag will be reset
------------------------------------------------------------------------------------*/
TWI_58:
	// Read data
	*p->pR = TWDR;
TWI_60:
TWI_68:
TWI_70:
TWI_78:
TWI_80:
TWI_88:
TWI_90:
TWI_98:
TWI_A0:
TWI_A8:
TWI_B0:
TWI_B8:
TWI_C0:
TWI_C8:
TWI_F8:
	// STOP condition will be transmitted and TWSTO Flag will be reset
STOP:
		tail = (tail+1)&(size-1);			// Увеличиваем tail на 1, если выходит за пределы, то обнуляем
		if (head != tail)				// Если head и tail не равны, то
			TWCR =  (1<<TWEN)|				// Модуль TWI включен
					(1<<TWIE)|(1<<TWINT)|		// Разрешаем прерывания! Очищаем флаг
					(0<<TWEA)|(1<<TWSTA)|(1<<TWSTO)|	// СТАРТ-СТОП
					(0<<TWWC);
		else						// иначе
		{
			TWCR =  (1<<TWEN)|				// Модуль TWI включен
					(1<<TWIE)|(1<<TWINT)|		// Разрешаем прерывания! Очищаем флаг
					(0<<TWEA)|(0<<TWSTA)|(1<<TWSTO)|	// СТОП
					(0<<TWWC);
			flag.twi_run = 0;				// Очищаем флаг
		}
}

ну и результат выполнения get_AC1:


Послесловие

На самом деле если по аналогии get_AC1 сделать get_AC2 и запустить 2 функции подряд, то два раза выполнится только последняя. Связанно это с тем что данные для передачи мы постоянно храним в buf[0]. Чтобы этого избежать мы могли бы записать данные в buf[1] и все бы работало как нужно. Но так не делается. Правильно передавать указатель откуда брать данные и сколько, а некая сторонняя функция будет класть это в буфер для TWI и возвращать указатель откуда в нем брать данные в обработчике. В общем код к примеру:

Код
unsigned char bufTx[size];
unsigned char pos = 0;
...
void get_AC1(void)
{
	volatile twi *pl;					// Указатель на структуру типа twi	
	volatile uint8_t buf[] = {0xAA};			// Ложим Register address в buf
	pl->SLA = 0x77;					// Slave address нашего BMP085
	pl->pW = buf;					// Указатель откуда брать данные для записи
	pl->nW = 1;						// Сколько байт записывать?
	pl->pR = buf;					// Указатель куда считывать?
	pl->nR = 2;						// Сколько байт считывать?
	Scheduler(pl);					// Вызываем планировщик очереди
}
// ============================================
void get_AC2(void)
{
	volatile twi *pl;					// Указатель на структуру типа twi
	volatile uint8_t buf[] = {0xAC};			// Ложим Register address в buf
	pl->SLA = 0x77;					// Slave address нашего BMP085
	pl->pW = buf;					// Указатель откуда брать данные для записи
	pl->nW = 1;						// Сколько байт записывать?
	pl->pR = buf;					// Указатель куда считывать?
	pl->nR = 2;						// Сколько байт считывать?
	Scheduler(pl);					// Вызываем планировщик очереди
}
// ============================================
void Scheduler(volatile twi *pl)
{
	if (tail-head !=1 && head-tail != size-1)		// Если буфер не переполнен, то
	{
		twi *pg = &TWI[head];				// Получаем казатель на текущую ячейку
		pg->SLA = pl->SLA;				// Копируем SLA
		pg->pW = pushToBuf(pl->pW,pl->nW);		// Вызываем функцию, которая копирует данные
//							из buf в bufTX и возращает укзаатель
		pg->nW = pl->nW;				// Копируем nW
		pg->pR = pl->pR;				// Копируем *pR
		pg->nR = pl->nR;				// Копируем nR
		head = (head+1)&(size-1);			// Если номер элемента выходит за размерность - обнуляем
		if (!flag.twi_run)				// Если модуль TWI еще не запущен (флаг равен 0)
		{
			flag.twi_run = 1;				// Устанавливаем флаг в 1
			TWCR =  (1<<TWEN)|				// Заупскаем TWI модуль
					(1<<TWIE)|(1<<TWINT)|
					(0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)|
					(0<<TWWC);
		}
	}
}
// ============================================
unsigned char *pushToBuf(unsigned char *buf, unsigned char n)
{
	unsigned char *p = &bufTx[pos];			// Указатель откуда обработчику брать данные
	do {
		bufTx[pos++] = *buf++;				// Копируем из buf в bufTx
		pos &= size-1;					// Если че - обнуляемся
	} while (--n);					// Копируем n элементов
	return p;						// Возвращаем указатель p 
}

смотрим на картинку:


По-доброму, по аналогии такую же функцию нужно сделать и для принимающего буфера. Ты говоришь ей сколько, а она тебе присылает куда. Также можно модифицировать структуру TWI, например, передавать не указатели, а индексы для оптимизации памяти.
Если у кого возник вопрос о том почему для обнуления используется код:
head = (head+1)&(size-1);
tail = (tail+1)&(size-1);
pos &= size-1;

я скажу так быстрее и меньше кода. Единственное условие, чтобы значение было равно 2n.
Из особенностей стоит отметить также максимальный размер size для TWI. Если он меньше чем максимально возможное количество подряд идущих запросов, мы непременно потеряем часть их. Поэтому важно выделить на этом акцент. Всегда используйте значение size как минимум на 1 больше чем максимальное число одновременных запросов.
Можно было бы еще добавить обработчик полученных данных, после отработки функции. Для нашего примера это обработка полученных AC1 и AC2, которые мы получаем в Big-endianne и должны преобразовать в Little-endiann, а потом сохранить в соответствующем месте. Для этого в структуре TWI можно было бы хранить еще указатель на функцию обработчик и придумать как вызывать ее после отработки.
Пару слов стоит сказать о процессах. Применительно к моей схеме, есть 2 датчика совещенности, давления, 1 микросхема времени и 1 микросхема памяти на шине TWI. И доступ к ним можно расценивать как атомарный, к примеру для датчика давления:


если мы потеряем передачу для uncompressed pressure, полученное значение uncompressed temperature нам ни к чему и мы должны все начать сначала, ибо можем получить не валидные данные.
Также не предусмотрен случай сбоя при передаче по TWI. Так как мы изменяем оригинал TWI[i] в обработчике мы не можем начать передачу заново. Для этого можно объявить глобальную структуру типа twi, и перед каждым стартом данные из TWI[tail] копировать в нее, а обработчик пусть портит копию. В случае же сбоя мы можем восстановить данные из оригинала.

Заключение


Многие спросят: «Зачем? Ведь для для хранения 16 элементов структуры понадобится 16*(1+2+1+2+1) = 112 байт SRAM! Почему бы не использовать RTOS? Пускай мол будет структура с 1 элементом и если в нее не влазит, ставим в очередь на n мс.» Я считаю что подобное решение очень полезно использовать в операционных системах. Подумайте сколько будет длится такая передача? Если мы будет ставить в очередь, то каждая следующая передача будет через n мс, а вся закончится через n*m мс. Да и зачем забивать очередь не нужными m инструкциями, ведь есть вероятность упустить действительно важную задачу. Да и ядро ОС разгружается, не ставя m*(m+1)/2 задач в очередь.
Tags:
Hubs:
+25
Comments9

Articles