STM32 + 1-wire. Поиск устройств

В этой статье хочу рассказать про поиск устройств на шине 1-wire. У новичков это вызывает неподдельный ужас, вплоть до того, что начинаются выделения отдельных ножек для каждого 1-wire устройства. Но на самом деле не так все страшно.

Для начала вспомним, как же осуществляется чтение бита данных от slave-устройств 1-wire.
Сначала мастер на определенный промежуток времени прижимает линию к земле, сообщая устройствам что пора бы уже что-то посылать. Если устройство хочет передать «1», то оно ничего не делает. А если хочет передать «0», то в определенный момент времени тянет линию к земле. В принципе, кроме как тянуть линию к земле, устройства больше ничего и не могут сделать.



Получается, что бит «0» — доминантный. То есть, если два устройства одновременно пошлют биты «1» и «0», то мастер увидит на линии только «0». Собственно, этим и пользуются при поиске устройств.

Поиск происходит побитно. Все начинается с самого младшего бита. Мастер посылает Reset на линию, потом посылает команду с кодом 0F — Search ROM. После этой команды все устройства готовят свой первый бит идентификатора к предъявлению.

Итак, все готовы. Мастер читает с шины два бита. Все устройства посылают сначала свой первый бит, а потом комплементарный (обратный данному) бит. В чем пафос данного алгоритма? Устройство, которое посылает 1 ничем не отличается от неактивного устройства. И чтобы их как-то расшевелить, надо чтобы устройство послало 0. А чтобы при этом передать какую-то информацию, то устройство передает ноль либо при первом, либо при втором чтении. В зависимости от бита своего уникального идентификатора.

Какие могут быть варианты:

приняты биты 11 — в этом случае никакое устройство на шине не передало «0». Это означает, что либо устройств нет, либо мы где-то запутались в процессе поиска. В любом случае критическая ситуация.
Приняты биты 10 — это означает, что в едином порыве все активные устройства передали сначала «1», а потом «0», и соответствующий бит в идентификаторе у всех активных устройств «1».
Приняты биты 01 — все то же самое, что и в предыдущей ситуации, только бит «0»
А теперь самое интересное — приняты биты 00. Это означает коллизию — одновлеменно высказались устройства с битом «0» и битом «1» в соответствующем разряде. Помните про доминантный бит?

Как же разрулить коллизию? А просто — следующим ходом мастер отсылает всем устройствам оперделенный бит. Например «1». В этом случае все устройства, у которых в текущем разряде находится «0» просто отключаются и в дальнейших выборах не участвуют. То есть количество активных устройств уменьшается. Ясное дело, что если у всех устройств установлен одинаковый бит в текущем разряде (ситуации 10 и 01), то мы именно этот бит и передаем, чтобы не поломать весь процесс.

И таким образом, когда мы пройдем все 64 бита, у нас будет выбрано одно-единственное устройство.

Теперь наша задача перебрать все устройства на шине, чтобы потом обращаться к каждому поименно.

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



Для того, чтобы обойти все дерево используем следующий алгоритм:
Если ветка одна, то идем по ней (в соотвествующем разряде у всех активных устройств биты совпадают, или всего одно активное устройство)
Если веток две, то сначала идем по правой (где бит 1), и запоминаем развилку.
Как только прошли все разряды, возвращаемся к запомненной развилке и идем налево
Как только перебрали все развилки, останавливаемся.

Попробуем походить:
Reset, 0F
первый бит: от устройств дружно приходит 10 — у всех наших устройств в младшем бите установлена 1, просто идем по этой ветке
второй бит: приходит 00 — коллизия. Идем вправо (мы туде еще не ходили) и запоминаем 2
Как только мы шагнули вправо, устройства №2 и №3 выпали из дальнейшей процедуры поиска и нам не мешают.
третий бит: опять коллизия. Опять идем вправо и запоминаем развилку (бит 3). Отметим, что устройство №1 тоже выпало из поиска
четвертый бит: все устройства (у нас оно одно) рапортуют 10 — бит1.

Итого сложился идентификатор 1111. Записываем его и возвращаемся на предыдущую развилку. Но вернуться так просто нам не получится, надо начинать процесс сначала.

Вторая итерация:
Reset, 0F
первый бит: от устройств дружно приходит 10 — у всех наших устройств в младшем бите установлена 1, просто идем по этой ветке
второй бит: приходит 00 — коллизия. Идем вправо, нам надо добраться до следующего ветвления. Запоминаем 2
третий бит: поскольку мы добрались до развилки 3 и мы направо уже ходили, то идем налево
четвертый бит: опять осталось одно устройство и мы распознали устройство №1

Третья и четвертая итерация аналогично.

А вот, соответственно, и процедура. Все дерево if-ов написано неоптимально, а точно в соответствии с нашим алгоритмом. Оптимизированные функции поиска можно найти в аппноте от Maxim. Разобравшись, уже можно модифицировать как угодно.

//-----------------------------------------------------------------------------
// Данная функция осуществляет сканирование сети 1-wire и записывает найденные
//   ID устройств в массив buf, по 8 байт на каждое устройство.
// переменная num ограничивает количество находимых устройств, чтобы не переполнить
// буфер.
//-----------------------------------------------------------------------------
uint8_t OW_Scan(uint8_t *buf, uint8_t num) {

	uint8_t found = 0;
	uint8_t *lastDevice;
	uint8_t *curDevice = buf;
	uint8_t numBit, lastCollision, currentCollision, currentSelection;

	lastCollision = 0;
	while (found < num) {
		numBit = 1;
		currentCollision = 0;

		// посылаем команду на поиск устройств
		OW_Send(OW_SEND_RESET, (uint8_t*)"\xf0", 1, 0, 0, OW_NO_READ);

		for (numBit = 1; numBit <= 64; numBit++) {
			// читаем два бита. Основной и комплементарный
			OW_toBits(OW_READ_SLOT, ow_buf);
			OW_SendBits(2);

			if (ow_buf[0] == OW_R_1) {
				if (ow_buf[1] == OW_R_1) {
					// две единицы, где-то провтыкали и заканчиваем поиск
					return found;
				} else {
					// 10 - на данном этапе только 1
					currentSelection = 1;
				}
			} else {
				if (ow_buf[1] == OW_R_1) {
					// 01 - на данном этапе только 0
					currentSelection = 0;
				} else {
					// 00 - коллизия
					if (numBit < lastCollision) {
						// идем по дереву, не дошли до развилки
						if (lastDevice[(numBit - 1) >> 3]
								& 1 << ((numBit - 1) & 0x07)) {
							// (numBit-1)>>3 - номер байта
							// (numBit-1)&0x07 - номер бита в байте
							currentSelection = 1;

							// если пошли по правой ветке, запоминаем номер бита
							if (currentCollision < numBit) {
								currentCollision = numBit;
							}
						} else {
							currentSelection = 0;
						}
					} else {
						if (numBit == lastCollision) {
							currentSelection = 0;
						} else {
							// идем по правой ветке
							currentSelection = 1;

							// если пошли по правой ветке, запоминаем номер бита
							if (currentCollision < numBit) {
								currentCollision = numBit;
							}
						}
					}
				}
			}

			if (currentSelection == 1) {
				curDevice[(numBit - 1) >> 3] |= 1 << ((numBit - 1) & 0x07);
				OW_toBits(0x01, ow_buf);
			} else {
				curDevice[(numBit - 1) >> 3] &= ~(1 << ((numBit - 1) & 0x07));
				OW_toBits(0x00, ow_buf);
			}
			OW_SendBits(1);
		}
		found++;
		lastDevice = curDevice;
		curDevice += 8;
		if (currentCollision == 0)
			return found;

		lastCollision = currentCollision;
	}

	return found;
}


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

  • +6
  • 16 февраля 2012, 15:02
  • steel_ne
  • 1
Файлы в топике: onewire103.zip

Комментарии (9)

RSS свернуть / развернуть
Отдельные ножки имеют такое преимущество, как однозначная идентификация положения каждого датчика. Вне зависимости от их замены. Иногда это может быть главнее.
Но статья однозначно нужная и полезная. Уметь делать обход дерева всегда пригодится.
0
  • avatar
  • PRC
  • 16 февраля 2012, 20:03
Однажды попытался таким образом сэкономить на потреблении при батарейном питании. Датчика было ровно два. Логика была такая. Некоторые операции можно делать одновременно по двум линиям, тем самым уменьшив время бодрствования процессора. Да и не нужно обращаться к датчикам по адресу, что тоже снижает время опроса. Но потребление самих ds18b20 в момент измерения оказалось гораздо больше потребления контроллера, выгода была почти незаметна. Вот снижение частоты опроса и разрядности влияло существенно.
А статья хорошо написана, раньше тупо готовый алгоритм использовал, теперь стало ясно, как оно работает :)
0
Я имел в виду не потребление. Когда у меня на каждой ножке один датчик — я всегда точно знаю что именно я считываю и где он находится. Случай, что датчик №1 повесили вместо №2 относится уже к проблемам пользователя. Когда же все датчики весят на одной шине нужно вводить специальные меры по их идентификации. Хотя если мне нужна максимальная/средняя температура, то тогда конечно надо их вешать на одну линию.
0
Например, операцию Convert T можно посылать всем устройствам одновременно, не выбирая адрес. Потом просто считать у каждого температуру, но тут уже адрес.
0
С таким раскладом может и соглашусь, но ножек жалко очень )
Тем более, раз мы уходим от общей шины, то можно ж использовать и какие-то другие датчики, не обязательно 1-wire
0
народ у меня плохо получается. Вот считывать температуру с датчика могу через скип ром, а вот адресно не получается, подскажыте примерами как этот скан правильно исспользовать чтоб адреса датчиков в масивчик сохранять и что б по этому адрессу можно было температуру регистрировать. Я делаю так:

fuondDev=OW_Scan(bufd,2); //сканирую количество устройств и заношу адрес в буффер bufd
но оно заносит в буфер только один адрес а не два как я указал согласно инструкции ссылку на буфер(масив), и максимальное количесвто устройств. Ну плохо я знаю еще С++ трудно еще все разрулить. Один адрес скачиваю вроде стабильно через OW_Scan() по одному датчику отдельно, и вроде адерса правильные потому что код семейства выдает правильный 0x28(40) для обоих датчиков ну и остальное. А вот ввожу его на измерение и не получается:

//-----------------------------------------------------------------
OW_Send(OW_SEND_RESET, "\xcc\x44", 2, 0, 0, OW_NO_READ);
OW_Send(OW_SEND_RESET, «x55\40\85\137\23\3\0\0\71\xbe\xff\xff», 12, buf, 2, 10);
//-----------------------------------------------------------------
че делать? помогите!!!
0
а пауза на время измерения?
0
еще забыл сказать что ретурн после сканирование всегда возвращает 1 при лубом количестве датчиков а согласно коду
//-----------------------------------------------------
found++;
lastDevice = curDevice;
curDevice += 8;
if (currentCollision == 0)
return found;

lastCollision = currentCollision;
}

return found;
//------------------------------------------------------
должен возвращать количество датчиков, может я чет еще не доганяю но вот такие у меня пироги((((((((((
0
В коде поиска в месте "// читаем два бита. Основной и комплементарный
шаблон готовится только для одного
уместно вставить ow_buf[0]=0xff; ow_buf[1]=0xff;
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.