Использование интерфейса USI в режиме мастера TWI

Возникла необходимость использовать EEPROM совместно с контроллером ATTiny44. Соответственно выбор пал на AT24C64, работающую по интерфейсу I2C (TWI по атмеловской терминологии). Порыл в документации и в инете — с виду вроде все просто, но при реализации алгоритма несколько раз возникали вилы нигде толком не обозначенные, поэтому решил написать статейку.

Я не мега-гуру, поэтому если будут ошибки или недочеты — надеюсь спецы в комментах поправят. Код буду приводить на Си в простом быдло-кодерском исполнении, так как пока нет нужды заморачивать более сложные варианты. Теперь обо всем по порядку…

Примечание: для понимания материала статьи желательно представлять работу TWI интерфейса и микросхем serial eeprom. Прочитать об этом можно в статьях DI HALT’a про IIC интерфейс и, например, в статье про часы (где-то еще попадалась статья про работу конкретно с serial eeprom, но не нашел…)

Так же я люблю всякие команды типа cbi и sbi, поэтому для корректной работы примеров необходимо вставить в начало программы следующую строчку:

1
#include<compat/deprecated.h>

и еще вставить строчку в разделе определения типов :

1
typedef unsigned char u08;

USI
Интерфейс USI присутствует во многих контроллерах семейства Tiny и части семейства Mega и представляет собой полуфабрикат-заготовку из сдвигового регистра, 4х-битного счетчика и набора регистров инициализации с прерываниями. Может работать как в 3х-проводном режиме (как SPI) так и в 2х-проводном режиме (TWI или I2c), так же у него есть нестандартные применения для реализации UARTов и прочего. Я, например, успешно пихал через него данные в цепочку 74hc595 c 7ми-сегментниками :)

Полностью расписывать весь интерфейс я не буду, это есть в даташитах и в литературе (например А.В. Евстифеев. Микроконтроллеры AVR семейства Tiny.)
Для управления интерфейсом USI у нас есть следующие регистры:

  • USICR — регистр управления
  • USISR — регистр состояния
  • USIDR — регистр данных
  • USIBR — буферный регистр только для чтения (отсутствует в некоторых моделях AVR, типа tiny2313, смотрите даташит)

Регистр данных USIDR представляет собой стандартный регистр ввода-вывода, никаких особенностей кроме той, что его содержимое автоматом сдвигается и вываливает значение старшего бита на соответствующую ногу контроллера.
Буферный регистр USIBR — копия USIDR только для чтения. В него автоматом копируется значение из USIDR после каждой операции сдвига.
Регистр состояния USISR — регистр ввода-вывода, который в старших 4х разрядах содержит флаги прерываний по обнаружению состояний СТАРТ, СТОП, по переполнению 4х-битного счетчика и по обнаружению коллизий при выводе данных, а в младших 4х разрядах содержит 4х-битный счетчик.

ВАЖНО!!!
Обратите внимание: ЧТОБЫ СБРОСИТЬ программно какой-либо флаг прерывания — надо УСТАНОВИТЬ соответствующий бит регистра в 1! Это справедливо почти для всех флагов любой периферии AVR

Регистр управления USICR я распишу немного подробней, т.к. нам его нужно правильно инициализировать:

  • 7ой бит USISIE — разрешение прерывания по обнаружению состояния СТАРТ
  • 6ой бит USIOIE — разрешение прерывания при переполнении счетчика
  • 5ый бит USIWM1 и 4ый бит USIWM0 — режим работы модуля USI
  • 3ий бит USICS1 и 2ой бит USICS0 – выбор источника тактового сигнала
  • 1ый бит USICLK — строб тактового сигнала
  • 0ой бит USITC — переключение состояния вывода тактового сигнала

Выглядит немного непонятно, но более подробное описание всех значений есть в документации а далее по тексту я постараюсь объяснить значения тех или иных битов чтоб было проще и наглядней…

Далее я буду рассматривать только 2х-проводной режим работы интерфейса.
А еще у нас в режиме TWI есть две ноги обозначенные как SCL и SDA (для Tiny44 это выводы PA4 и PA6 соответственно).
Начнем с обозначения этих ног. Я обычно делаю отдельный файл с описанием периферии и называю его controller.h :

1
2
3
4
5
6
7
8
9
// USI pin association
#define sclport		PORTA
#define sclportd	DDRA
#define sdaport 	PORTA
#define sdaportd	DDRA
#define scl		4
#define sda		6
#define sclportinp	PINA
#define sdaportinp	PINA

ВАЖНО!
В процессе работы с интерфейсом выводы scl и sda должны быть сконфигурированы на выход (в режиме работы от модуля USI они подключаются к выходному буферу интерфейса при установленном в 1 своем бите DDRх)!!! Но в режиме приема данных по линии sda данный вывод надо конфигурировать на вход!!!

Небольшое отступление
Общая схема у нас представляет собой Tiny44, затактованную от кварца частотой 7.3728 МГц с подключенным к линиям scl и sda чипом памяти at24c64. Линии scl и sda подтянуты к питанию через внешние резисторы 10k. Собственно, подключение стандартное для IIC интерфейса.

Инициализация интерфейса
Пример сделан на базе атмеловского апнота AVR310Что-то оставил как есть, кое что изменил, но внешнее сходство осталось сильным.

Для начала определим разного рода задержки т.к. скорость TWI все-таки не очень высокая.

1
#define USI_DELAY 20  // собственно сама задержка, подобранная экспериментально.

Задержку я подобрал экспериментально, не заморачиваясь стандартными скоростями I2C. Мне важна была стабильность приема и передачи информации. Стандартную скорость вполне можно подобрать при желании, хоть в режиме standart, хоть в режиме fast.

Теперь сделаем предустановки значений четырех-битного счетчика. Этот счетчик используется для отсчета переданных бит данных. У нас пакет данных представляет собой 8 бит данных и один бит подтверждения. В итоге 9 бит.

1
2
#define USISR_8BIT 0xF0 // значение 4х битного счетчика для передачи 8 бит пакета  информации 
#define USISR_1BIT 0xFE  // значение счетчика для передачи 9го бита пакета информации

Сама процедура инициализации интерфейса взята с AVR310:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void init_usi_i2c_master(void) 
{
 
// init USI
	sbi(sclportd,scl);
	sbi(sdaportd,sda);						// Preload dataregister with "released level" data.
  	USICR    =  (0<<USISIE)|(0<<USIOIE)|				// Disable Interrupts.
              (1<<USIWM1)|(0<<USIWM0)|					// Set USI in Two-wire mode.
              (1<<USICS1)|(0<<USICS0)|(1<<USICLK)|			// Software stobe as counter clock source
              (0<<USITC);
  	USISR   =   (1<<USISIF)|(1<<USIOIF)|(1<<USIPF)|(1<<USIDC)|	// Clear flags,
              (0x0<<USICNT0);	
	sbi(sdaport,sda);
	sbi(sclport,scl);
	return;
}

В нашем случае прерывания нам не нужны, поэтому бит USISIE в 0 и USIOIE тоже в 0.
Далее переводим интерфейс в 2хпроводной режим без удержания линии scl, следовательно бит USIWM1 в 1, а бит USIWM0 в 0.

Если надо работать на прерываниях или в режиме слэйва, то удобней использовать режим, когда оба этих бита установлены в 1. Тогда при уходе в прерывание контроллер автоматически прижмет линию scl в 0 и устройства на шине будут ждать завершения обработки принятых или переданных вами данных.

Далее выставляем очень интересную комбинацию бит:

  • USICS1 в 1
  • USICS0 в 0
  • USICLK в 1

Все возможные комбинации этих битов описаны подробно в даташите. Наша комбинация выбирает источником тактового сигнала для 4х-битного счетчика нашу программу, а еще точнее говорит о том, что линия SCL изменять свое состояние на противоположное при записи в бит USITC единицы и четырехбитный счетчик будет инкрементироваться, а регистр данных будет сдвигаться.

То есть для того чтобы выпнуть значение из регистра данных (и ли принять пришедшее в режиме мастера) нам надо 16 раз дернуть бит USITC в единицу! Фактически это ручной режим.

Ну и пока ставим USITC в 0, чтобы интерфейс пока ничем не дергал.

Также нельзя забывать о конфигурации ног SCL и SDA на выход. С этого момента старший бит регистра USIDR присутствует в виде уровня на ноге SDA! Обе линии находятся в исходном состоянии (в единице).

Теперь опишем процедуры для задания состояний СТАРТ и СТОП.

  • Состояние СТАРТ — это изменение уровня SDA с высокого на низкий при высоком уровне линии SCL
  • Состояние СТОП — это изменение уровня SDA с низкого на высокий при высоком уровне линии SCL

Я перестраховался с этими состояниями, поэтому код несколько избыточен и в тоже время прост как пробка и нагляден:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void i2c_start(void) 
{ // Генерируем состояние Старт (или ПовСтарт)
 
	sbi(sdaport,sda);			// на всякий случай выставляем в исходное состояние sda
	sbi(sclport,scl);			// тоже с SCL
	cbi(sclportd,scl);			// ВАЖНО!!! отключаем SCL от выходного буфера интерфейса
	USISR = (1<<USISIF)|(1<<USIOIF)|(1<<USIPF)|(1<<USIDC)|(0x0<<USICNT0); 	//сбрасываем USISR
	cbi(sdaport,sda);			// переводим  SDA в 0 пока SCL в 1
	dummyloop(USI_DELAY);			// тупим нашу задержку
	sbi(sclportd,scl);			// ВАЖНО!!! подключаем SCL обратно к выходному буферу интерфейса
	cbi(sclport,scl); 			// переводим SCL в 0
	sbi(sdaport,sda); 			// освобождаем линию SDA для последующей передачи/приема данных
	dummyloop(USI_DELAY); 			// еще раз тупим задержку 
	return();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
void i2c_stop(void) {// Генерируем состояние Стоп
 
	cbi(sclport,scl);			 // необязательная подготовка
	dummyloop(USI_DELAY);
	cbi(sdaport,sda);
	dummyloop(USI_DELAY);
	sbi(sclport,scl); 			// перевод SCL в 1
	dummyloop(USI_DELAY);
	sbi(sdaport,sda); 			// перевод  SDA в 1
	dummyloop(USI_DELAY);
	USISR|=(1<<USIPF); 			//сброс флага детекции состояния Стоп в USISR
	return ();
}

Перестраховка в коде заключается в том, что мои процедуры сгенерируют состояния Старт и Стоп независимо от того в каком фактическом состоянии находилась шина. То есть я сразу говорю что я тут один Мастер и других мастеров на шине быть не может. Потому все равняйсь, смирно, хватит фигню в шину пихать… Если у вас несколько мастеров — так однозначно делать не следует

Процедуру i2c_stop можно ускорить/сократить убрав лишние дерганья ногами и задержки.

ВАЖНО!
Обратите особое внимание на строчки выделенные в коде пометкой «Важно». В апнотах этих строчек нет! Если вы не сделаете простую операцию по отключению ноги scl от выходного буфера — то будете долго ломать голову, почему у вас ни чего не работает, какой фиговый интерфейс, и какие лузеры его разрабатывали и т.д. Я сам неделю ломал голову (с аналоговым осцилографом). На цифровике — можно быстро дотумкать запустив аппаратный TWI на той-же Меге и сравнив сигналы, но нету у меня цифры…

А собака порылась вот где: в мануалах, даташитах и литературе вскользь упоминается, что у контроллера есть такая «схема детекции состояния Старт», а в описании битов USIWM1..0 для двухпроводного режима есть фраза, что «драйвер линии SCL формирует на ней низкий уровень либо по сигналу детектора состояния Старт, либо если соответствующий линии бит сброшен в 0».
Поэтому при подключенном драйвере линии SCL, как только вы скажете cbi(sdaport,sda) контроллер моментально сдетектит «Старт» и сразу просадит в 0 SCL.

Если у вас в качестве ведомого будет такая же AVR-ка с USI-интерфейсом — то все прокатит и заработает, что нам успешно демонстрирует связка атмеловских апнотов AVR310-AVR311. А вот если в качестве ведомого у вас EEPROM-ка или тот же таймер 8583 — то он вас просто не поймет, т.к. между переходом sda и scl в 0 должна быть пауза.

Ну и, соответственно, после того как мы сделали свое черное дело, надо сбросить все флаги и счетчик в регистре состояния и вернуть на место драйвер шины SCL.

Процедура задержки. Я использую следующий вариант:

1
2
3
4
5
6
7
8
9
10
11
12
#define nop() asm volatile ("nop")
 
void dummyloop(unsigned int);
 
void dummyloop(unsigned int timetoloop)
{ 
  	while (timetoloop>0) 
	{
	nop();
	timetoloop--;
  	}
}

Скорей всего по быдлокодерски, но работает и не режется оптимизатором.

Дальше пишем процедуру приема/передачи:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
u08 usi_i2c_master_transfer(u08 tmp_sr) {
 
	USISR = tmp_sr; 				// Загружаем USISR нужным нам значением 
	tmp_sr= (0<<USISIE)|(0<<USIOIE)| 
		(1<<USIWM1)|(0<<USIWM0)|
		(1<<USICS1)|(0<<USICS0)|(1<<USICLK)|	// задаем битовую маску для USICR
		(1<<USITC); 				// самый важный бит. Ради него и сделана эта переменная
 
	do 
	{
	dummyloop(USI_DELAY); 			// курим бамбук
	USICR=tmp_sr; 				// запинываем значение в USICR, интерфейс работает 
	while (bit_is_clear(sclportinp,scl)); 	// проверяем, свободна-ли линия
	dummyloop(USI_DELAY); 			// снова курим бамбук
	USICR=tmp_sr; 				// еще раз запинываем USICR
	} 
	while (!(USISR&(1<<USIOIF))); 		// повторяем предыдущие операции до переполнения счетчика
 
	dummyloop(USI_DELAY); 			// тупим в цикле
 
	tmp_sr=USIDR; 				// сохраняем принятые данные
	USIDR=0xff; 				// освобождаем линию sda
	sbi(sdaportd,sda); 			//ВАЖНО!!! восстанавливаем  подключение SDA к выходному буферу интерфейса
	return (tmp_sr); 			// Возвращаем принятые данные
 
}

Данная процедура сдернута мной с апнота и почти без изменений. Примечательна она тем, что осуществляет как чтение так и передачу данных.
Работает она так:

ВАЖНО!
Данная процедура предполагает, что регистр USIDR уже загружен нужными данными для передачи!

Мы получаем от программы значение регистра USISR, с предустановленным счетчиком. Так как наш минимальный пакет данных составляет 8 бит данных в одну сторону и 1 бит в противоположную (смотрите логику работы TWI-интерфейса), то мы должны послать ведомому 8 бит и принять один (или же принять 8 бит послать один если передача идет в другую сторону).
Раз у нас есть аппаратный счетчик, да еще и с флагами, значит не надо городить кучу переменных в циклах и их проверки. Достаточно предустановить счетчик и гонять цикл до его переполнения.
Именно для этого мы определили константы USISR_8BIT и USISR_1BIT, которые задают значения счетчика для обработки 8бит данных и 1 бита данных, а заодно еще и сбрасывали ненужные и нужные флаги состояния.

Далее мы:

  • Толкаем в бит USITC единичку, линия scl начинает менять свое состояние из 0 в 1, счетчик тикает увеличиваясь на 1 и приемник считывает первый переданный нами бит.
  • Проверяем свободна-ли линия — если ведомое ведомое устройство не успевает — оно прижмет SCL в 0. В принципе это должно работает, но могут возникнуть вилы. У меня не было возможности расковырять данный момент досконально, а ведомый вроде всегда успевал.
  • Делаем задержку.
  • Еще раз толкаем в бит USITC единичку. Линия SCL при этом у нас возвращается в 0, регистр USIDR сдвигается на один бит, выпинывая в линию SDA новое значение, а счетчик увеличивается еще на 1.

Таким образом, мы на один проход цикла получаем полный такт передачи 1 бита по шине. Счетчик при этом тикает 2 раза на один проход.
Далее

  • Повторяем все до переполнения.
  • Делаем задержку.
  • Cохраняем полученные данные.
  • Возвращаем линию sda в нормальное состояние (1), для чего запинываем в USIDR любое значение с установленным 7мым битом.
  • Восстанавливаем подключение линии SDA к выходному драйверу. Дело в том, что при передачи данных от мастера к ведомому драйвер у нас всегда подключен, чтобы линия SDA всегда соответствовала значению 7го бита USIDR, а если мы принимаем данные от ведомого, то линия SDA должна быть сконфигурирована как вход. Если этого не сделать — то у меня мастер не мог получить бит подтверждения и другие данные от ведомого. За направлением приема-передачи следит процедура отвечающая за протокол обмена, а в нашей процедуре мы просто восстанавливаем подключение, если оно было сброшено.

Это была процедура непосредственно обмена по интерфейсу, а теперь рассмотрим 2 процедуры более высокого уровня, которые уже реализуют часть протокола обмена данными:

Передача N-ного количества байт от мастера к ведомому

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
u08 i2c_master_send(u08 *data, u08 data_size) 
{
 
	if (bit_is_set(sclportinp,scl)) return(1);		 // проверка, если старт не прошел — выход с состоянием (1)
 
	do 
	{
	USIDR=(*(data++)); 					// загружаем очередной байт данных
	usi_i2c_master_transfer(USISR_8BIT); 			// посылаем 8 бит
	cbi(sdaportd,sda); 						// переклюаемся на прием
	if ((usi_i2c_master_transfer(USISR_1BIT))&0x01) return(2); 	// если нет подтверждения - выход (2)
	data_size--; 							// уменьшаем счетчик данных
	} 
	while (data_size>0); 					// и так пока все не передадим
 
	return(0); 						// успешная передача — выход с состоянием (0)
}

Прием N-ного количества байт от ведомого к мастеру

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
u08 i2c_master_read(u08 *data, u08 data_size) 
{
	if (bit_is_set(sclportinp,scl)) return(1); 		// проверка, если старт не прошел — выход с состоянием (1)
	do 
	{
	cbi(sdaportd,sda); 					// переклюаемся на прием
	*(data++)=usi_i2c_master_transfer(USISR_8BIT); 		// принимаем 8 бит
 
	if (data_size==1) 
		{
		USIDR=0xFF; 				//если последний байт — передаем NACK
		} 
	else 
		{
		USIDR=0x00; 				// если не последний — передаем подтверждение ACK
		}
 
	usi_i2c_master_transfer(USISR_1BIT); 		// собственно передача ACK/NACK
	data_size--; 					// уменьшаем счетчик данных
	} 
	while (data_size>0);  				// и так пока все не примем
	return(0); 					// успешный прием данных, выход с состоянием (0)
 
}

Процедуры простые и особых пояснений вроде не требуют.

Вот собственно и вся база для организации обмена по USI в режиме мастера.

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

К статье приложено несколько файлов
Они в архиве usi.zip

  • usi_i2c.c Исходники рассмотренные здесь
  • i2c_memory.c , в котором набор процедур для работы с eeprom. Процедуры очень простые, но если возникнут вопросы — пишите. Расписывать их подробно — еще такая же статья получится. За раз сложно для понимания… Но если ногами не запинают напишу в виде продолжения…

42 thoughts on “Использование интерфейса USI в режиме мастера TWI”

  1. Пару недель назад столкнулся с данной задачей. Особых трудностей не возникло, в даташите все понятно описано, как по мне. Но такая статья не будет лишней для начинающих.

  2. Возможно я буду не первым, но… атмеловский TWI далеко не синоним I2C. Дело в том, что в спецификации I2C черным по белому написано — линия SDA должна изменять изменять свое состояние при низком уровне сигнала на линии SCL. В остальных случаях это START или STOP.
    Атмеловцы же в своем TWI переключают линию SDA по фронту на SCL. В итоге будет ли работать конкретная микросхема конкретного производителя нифига неизвестно. Может будет, а может и нет.
    На AT91SAM7S стоит именно такой дурацкий контроллер. Да и на других чипах тоже встречается.
    Хотя на Меге с этим вроде порядок (не помню, давно это было).

  3. Во всяком случае AT24C64 и AT24C256, а так же PCF8583 работают без вопросов.
    Сдвиг происходит по заднему фронту SCL что гарантирует отсутствие в данный момент высокого уровня на линии, так что в спецификации они тут вполне укладываются. Чисто аппаратно сдвиг по фронту реализуется прощще чем сдвиг после фронта…

  4. Всегда делал такие вещи полностью программно. Получается, конечно больше кода, но зато мы не привязаны к конкретным ножкам.

    НО! Статья хорошая. Только жаль, что код не на ассемблере, а то я Си плохо воспринимаю.

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

    1. Дело в том, что у AVRки для USI внутрях есть специальная схема с выходными буферами. Вот когда вы устанавливаете ноги SCL и SDA в режим выхода с подтяжкой, эта схема и подключается к ноге взамен стандартной от порта IO. Разумеется только в том случае если у вас разрешен USI. Внешние резисторы шины никто при этом не отменяет, они обязательны.

      1. Сорри, опечатался. И в статье тоже. Не может быть выхода с подтяжкой…
        Вобщем для включения выходного драйвера сигналов SCL и SDA надо установить соответствующий бит регистра DDRх в 1. Выдача сигнала в соответствующий бит регистра PORTх нужна для того, чтобы линия находилась в исходном состоянии (т.е. в 1).

        Ди, если не сложно, поправь пожалуйста в статье. Это вторая сверху сноска «ВАЖНО».

    1. Главное что это короче. Помоему в avr-gcc некоторое время в зависимости от релиза прыгали обозначения стандартных типов, поэтому мы на работе пользовали классический Си-шный тип unsigned char, а компилятор уже сам приводил к нужному ему типу в зависимости от бибилиотеки. Вобщем в данный момент это пережитки давно минувших дней, а я втыкаю этот тип по привычке и для совместимости с приложенными библиотеками.

  5. Вроде не совсем понятно — вроде аппаратная штука, как например тот же УАРТ. А все равно надо сначала сконфигурировать выводы на вход/выход. Почему нельзя сделать это автоматом при включении интерфейса. В УАРТе проще конечно, там одна линия на вход, вторая на выход. Но всё равно — включил, регистры заполнил и гони байты. Он все сам сделает. А ведь по IIC вроде как в обе стороны гнать можно. Так что при смене направления каждый раз на вход/выход перестраивать вывод. Или я где-то невнимательно читал? :)

    1. Да. При смене направления надо самому отслеживать и менять вход/выход. Протокол обмена по шине достаточно жестко регламентирован, поэтому в режиме одного мастера это не сложно.
      Отличие от UARTа в том, что UART полностью аппаратный. Настроил, сунул байт в порт и забыл. А USI это полуфабрикат, который может работать и как 2х-проводной интерфейс(i2c) и как 3х-проводной(SPI). Тоесть аппаратно там только несколько регистров с прерываниями и счетчиком. Остальное — програмно. Типа убиты два зайца: и в контроллер больше интерфейсов впихнули (в основном он все-таки на Tiny присутствует) и объем кода для реализации сократили (по сравнению с полностью программной эмуляцией), что для тех же Tiny важно…

    2. Это еще что… Мастер — то и чисто софтово несложно реализуется. А попробуйте слэва сделать… Даже при полном I2C, реализованном в других контроллерах, все равно дофига ручной обработки в режиме ведомого. У меня сложилось мнение, что вообще в микроконтроллерах I2C имеет смысл только для связи (в режиме мастера) с периферией, в которой I2C встроен чисто аппаратно — типа часов, памяти, термометров, и прочих датчиков. Для связи же межконтроллерной — штука весьма неуклюжая…
      Пока что из всех существующих в микроконтроллерах интерфейсов лучше USART (RS232) — ничего не придумано. Там кинул байт в передатчик — и забыл про него. Освободился буфер передатчика — кинул следующий. Пришло прерывапние с приемника — считал принятый байт, только и делов. Минимум загрузки ядра. SPI вроде тоже неплох, но только пока надо передать 1 байт. Если же надо мастеру не передавать, а читать несколько байт с ведомого — тоже начинает обрастать и тормозить…

  6. А я сразу на какую-то шнягу наткнулся, пытаясь разобраться с TWI по примеру из мануала. Создаю старт-условие, дожидаюсь TWINT=1, проверяю TWSR, там 0x08, как и положено. Далее посылаю адрес PCF8583 на запись — 0xA0, дожидаюсь TWINT=1, проверяю TWSR, а там 0x00. В мануале к TWI написано, что этот статус означает ошибку шины из-за некорректного Start или Stop-условия. Фигня какая-то! Есть мнения на этот счёт, комрады?
    Вот код:

    ldi r16,(1<<TWINT)|(1<<TWSTA)|(1<<TWEN)
    sts TWCR,r16
    wait1:
    lds r16,TWCR
    sbrs r16,TWINT
    rjmp wait1

    lds r16,TWSR
    andi r16,0xF8

    ; r16 = 0x08

    ldi r16,0xA0
    sts TWDR,r16
    ldi r16,(1<<TWINT)|(1<<TWEN)
    sts TWCR,r16
    wait2:
    lds r16,TWCR
    sbrs r16,TWINT
    rjmp wait2

    lds r16,TWSR
    andi r16,0xF8

    ; r16 = 0x00

    Правда, на одном порту с TWI у меня используются 4 ноги под шину данных ЖКИ. Ножки разные: ЖКИ — PC0..PC3, TWI — PC4,PC5. Либа для работы с ЖКИ взята с этого ресурса. Может ли быть конфликт между софтверным драйвером ЖКИ и аппаратным TWI???

      1. Да, DWEN = 1 во Fuse High Byte. То есть, сброшен.
        Думал ещё на тактовую частоту TWI. Снизил её до 1 кГц. Эффекта никакого.
        Набросал софтверный I2C, всё едет, время/дата читается и записывается. А аппаратный TWI ни в какую!

  7. USISR = tmp_sr; //Загружаем USISR нужным нам значением

    …чет не понял, каким нужным??? Ведь в этом регистре 4 старших бита — различные флаги,
    а младшие — 4х битный таймер, и получается что что-то загружать можно только в него….

    никто не реализовывал программно I2C для связи 2х или 3х AVR-ок??? ….может есть проектик на ASM??….
    аппаратно то вроде все просто, но как назло у 2313 его нету(((….

    1. Хоть и давно был коммент (пропустил его) отвечу:
      В данном случае параметру функции передается один из следующих вариантов:

      #define USISR_8BIT 0xF0 // значение 4х битного счетчика для передачи 8 бит пакета информации
      #define USISR_1BIT 0xFE // значение счетчика для передачи 9го бита пакета информации

      которые сбрасывают нужные флаги и одновременно выставляют значение 4-х битного счетчика в соответствующее значение для передачи 8-ми или одного бита.

  8. Только есть одно но! В процедуре
    void init_usi_i2c_master(void)
    Вы запускаете модуль usi,
    USICR=(0<<USISIE)|(0<<USIOIE)|(1<<USIWM1)|(0<<USIWM0)|(1<<USICS1)|(0<<USICS0)|(1<<USICLK)|(0<<USITC);
    который не даст вам ниче сделать с ногой SDA
    поэтому после того как вы включите режим (1<<USIWM1)|(0<<USIWM0) нога SDA не установиться в состояние HI!

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

      1. возможно и рабочий, только на maga32 16pu он потерпел крах причем независимо от частоты, нога SDA находилась, только в одном состоянии и значение ее не изменялось

    1. Конкретно SPI режим не ковырял, не нужен был… Почитайте документацию, там вроде ничего сложного нет. Если не выйдет — SPI очень легко реализуется чисто программно.

  9. Прошу прощения что не по теме, по глупости не смог найти нужное место для задания вопроса.
    Тема касается интерфейса SPI.
    В нём есть такой конфигурационный регистр как «SPCR» ,
    в нём устанавливаются такие значения как включить прерывание, мастер или слэйв и т.п.

    Вопрос такой: По каким причинам этот регистр может самопроизвольно обнуляться?
    (а может и не самопроизвольно, но тем не менее.)
    То есть при чтении в нём оказывается записан ноль. И это только в одном случае почему то произошло и стабильно происходит , если верить виртуальной среде «Proteus».

    Заранее благодарю.

      1. Здравствуйте. Согласен с вами, в «ret» ничего обнуляться не должно да и проверять в железе надёжнее (да нет возможности пока).

        Проблема решилась неожиданным образом…. в следствии абсурдных экспериментов, просто поменяв название метки подпрограммы всё стало нормально… Вот и понять бы, что это было)))

    1. ай ентер случайно нажал.

      Автор активно оперирует битами PORTx, в то же время ДИ в своей статье запрещает такое действо и предостерегает.

      Что проводя сию операцию, во время какого нибудь сбоя, вы рискуете выжечь либо порты AVR, либо порты слева, ибо порты будут находится в режиме ПушПула и причем высоком, а значит линия будет закорочена на питание.

      Не безопаснее ли переводить порты в высокое состояние с помощью HZ, а притягивать к земле уже ОК?

      SDA_0(
      sbit(SDA_DDR,SDA_PN); //Переводим в прижатый ОК

      SDA_1
      cbit(SDA_DDR,SDA_PN); //Переводим в HZ, поднимается автоматом.

      SCK_RISE
      cbit(SCK_DDR,SCK_PN); //Переводим в HZ, поднимается автоматом.

      SCK_FALL
      sbit(SCK_DDR,SCK_PN); //Переводим в прижатый ОК

  10. Сделал конвертер I2C в USART. I2C на USI в ATtiny2313 в режиме Slave всё оказалось просто и работает. Но мне надо по приёму байта от USARTа передать принятый байт в эту шину I2C. 4-х разрядный счётчик USI использовать уже нельзя, т.к. он в Slave участвует всегда и определяет даже то что я передаю, придётся передавать программно. Так вот вопрос: если я буду программно дёргать пины порта PORTB5,7 и DDRB5,7 (SDA, SCL) будут ли они переключаться или они останутся в состоянии на которое настроено USI в режиме Slave ?

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