- PVSM.RU - https://www.pvsm.ru -
Если кто-нибудь использовал linux-драйвер шины 1-Wire на основе GPIO, то наверняка замечал, что в момент выполнения обмена данными загрузка SY% подскакивает чуть ли не до ста 100% (что вполне логично для bitbang-реализации). Конечно, можно поставить специализированный контроллер шины 1-Wire с подключением через I2C DS28E17 [1] или использовать UART [2], но… Все-таки использовать linux для приложений реального времени не лучшая идея. Пусть контроллер на нем занимается высокоуровневой логикой, а весь реалтайм можно вынести на отдельный процессор. Тем более, что задач для этого отдельного процессора в моем проекте более чем достаточно.
Нет, я не буду брать распиаренные сейчас STM32 — зачем стрелять из пушки по воробьям!? Возьмем старую добрую AtMega328P (ну или какую-нибудь Arduino [3], если так станет кому-либо легче) и соберем все на ней. Вот только делать все будем «по-взрослому», с работой в режиме разрешенных прерываний и с расчетом на последующую реализацию более высоких уровней при помощи protothreads (см. оригинал [4] и расжеванные примеры на русском языке [5]). Т.е. от bitbang-а отказываемся сразу и бесповоротно.
Также сохраним UART для будущего использования под другую периферию. Ибо он в нашем случае один, а мне бы еще протокол eBus сюда запихнуть. Ибо это тоже реалтайм.
I2C хорошо, но это еще один не самый дешевый кристалл с обвязкой, а оно нам надо? Итак, из интересного остается PWM и ICP.
Что такое PWM, я думаю, объяснять не надо. Это просто генерация импульсов заданной фазы и длительности, которая производится аппаратно (т.е. с точки зрения программы «в фоновом режиме»), но параметры которой можно изменять программно. А вот ICP достаточно интересная вещь: она позволяет аппаратно сохранять значение таймера в момент изменения уровня сигнала на определенном выводе микроконтроллера. Таким образом возможно достаточно точно получить момент возникновения события (изменения состояния вывода микроконтроллера) и/или измерить его продолжительность.
Итак, будем использовать аппаратный TIMER1 ATmega328 (таймеры 0 и 2 у меня используются для других целей). Для PWM можно использовать выводы OC1A и OC1B. Однако OC1B также используется в качестве сигнала SS, а подключать ATmega328 к целевому устройству я планировал именно по шине SPI. Это удобно с точки зрения апгрейда прошивок ATmega — делаем их при помощи целевого устройства, только один из CE-сигналов с его контроллера SPI заведем на контакт SS Atmega, а другой CE-сигнал заведем на контакт RESET. Да и с согласованием скоростей обмена возиться не нужно. Ну и в качестве еще одного довода против использования OC1B является то, что на плате Arduino MEGA 2560 [6] этот сигнал вообще не выведен во внешний мир.
Итак, для генерации PWM используем контакт OC1A. Для ICP вариантов вообще нет — вывод такого типа у этой микросхемы только один. Для режима «ACTIVE PULLUP» используем любой свободный IO port и настроим его на работу в режиме «OUT».
Так, со схемотехникой разобрались. Теперь освежим в памяти физику работы [7] шины 1-wire. В двух словах ее можно описать так:
Отлично, тогда выбираем режим работы Phase Correct PWM Mode (счетчик сначала считает «вверх» от значения BOTTOM до значения TOP, а после достижения значения TOP начинает считать «вниз» до значения BOTTOM). При этом значение BOTTOM фиксировано и всегда равно 0, а значение TOP может быть как одним из фиксированных значений (0xFF, 0x1FF, 0x3FF), так и значением из регистров OCR1A или ICR1. Два последних варианта нам не подходят, т.к. OCR1A мы будем использовать для формирования импульсов на выводе OC1A, а регистр ICR1 будет использоваться для измерения длительности низкого уровня на шине 1-Wire. Т.е. остается только использовать вариант с фиксированными значениями TOP, благо временной интервал между битами для нас не критичен.
В соответствии со схемой выход OC1A включен в инверсном режиме (т.е. установка на нем высокого уровня приведет к установке низкого уровня на шине 1-Wire и наоборот), будем использовать следующий режим PWM:
Остальное все просто. Новое значение (длительность следующего передаваемого бита) в регистр OCR1A мы будем загружать по прерыванию TIMER1_OVF (в данном режиме оно генерируется при достижения счетчиком значения BOTTOM а активируется затем после достижения значения TOP), а по прерыванию TIMER1_ICP будем вычислять длительность нахождения шины 1-Wire в состоянии низкого уровня и, в зависимости от этого, делать вывод о принятии бита «1» или бита «0».
Таким образом, на каждую операцию приема или передачи одного бита у нас будут генерироваться одно прерывание OVF и одно прерывание ICP. Соответственно их ISR лучше бы оптимизировать, но пока пусть они будут написаны на языке C.
Ну а операцию RESET для шины 1-Wire будем выполнять, перепрограммировав TIMER1 в режим NORMAL (т.к. длительность импульса RESET и последующего возможного импульса PRESENCE достаточно большая, нам необходимо расширить «динамический диапазон» работы таймера. В режиме NORMAL он составляет значение 0xFFFF, чего вполне достаточно в нашем случае. Более того, мы его даже специально ограничим значением из регистра OCR1B, чтобы в случае отсутствия подключенных к шине 1-Wire устройств не ждать, пока таймер дойдет до 0xFFFF, а прервать операцию сразу после достижения максимального критического временного интервала).
Вручную считать параметры настройки таймера в зависимости от тактовой частоты ATmega это моветон (да и лениво проверять все граничные условия), поэтому заставим это делать preprocessor компилятора C воспользовавшись тем фактом, что у нас должна быть определенная константа F_CPU, значение которой равно используемой тактовой частоте в герцах (unsigned long, т.е. константа должна быть определена как 8000000UL либо 16000000UL, иначе preprocessor будет считать ее как int и потеряет значащие разряды).
/* Arduino Pro Mini 1-Wire connection */
#define WIRE1_DDR DDRB
#define WIRE1_PORT PORTB
#define WIRE1_PIN PINB
/* OC1A/PCINT1 */
#define WIRE1_OUT PB1
/* (PCINT0/CLKO/ICP1) */
#define WIRE1_ICP PB0
#define WIRE1_IN_ICP PINB0
/* Active pullup PD7 */
#define WIRE1_PULLUP_DDR DDRD
#define WIRE1_PULLUP_PORT PORTD
#define WIRE1_PULLUP_OUT PD7
/* Presence detection in idle mode: external interrupt #0 */
#define WIRE1_IDLE_PRESENCE_DETECT_IRQ INT0_vect
#define WIRE1_IDLE_PRESENCE_DETECT_EICRA_MASK (_BV(ISC01) | _BV(ISC00))
/* Presence detection in idle mode: low level generate IRQ */
#define WIRE1_IDLE_PRESENCE_DETECT_EICRA_ISC 0
#define WIRE1_IDLE_PRESENCE_DETECT_EIMSK_INT INT0
#define WIRE1_IDLE_PRESENCE_DETECT_EIFR_INTF INTF0
#define WIRE1_IDLE_PRESENCE_DETECT_DDR DDRD
#define WIRE1_IDLE_PRESENCE_DETECT_PORT PORTD
#define WIRE1_IDLE_PRESENCE_DETECT_PIN PIND
#define WIRE1_IDLE_PRESENCE_DETECT_BIT PD2
#define WIRE1_TCNT TCNT1
#define WIRE1_TCCRA TCCR1A
#define WIRE1_TCCRB TCCR1B
#define WIRE1_TCCRC TCCR1C
#define WIRE1_OCRA OCR1A
#define WIRE1_OCRB OCR1B
#define WIRE1_ICR ICR1
#define WIRE1_TIFR TIFR1
#define WIRE1_TIMSK TIMSK1
#define WIRE1_ICP_IRQ TIMER1_CAPT_vect
#define WIRE1_OVF_IRQ TIMER1_OVF_vect
#define WIRE1_COMPA_IRQ TIMER1_COMPA_vect
#define WIRE1_COMPB_IRQ TIMER1_COMPB_vect
#define WIRE1_POWER_ON()
power_timer1_enable()
/* Продолжительность фаз RESET в единицах TSLOT */
#define RESET_DURATION_TSLOTS 8
/* Временные параметры обмена в мкс */
#define NORMAL_TLOWR_MIN 1
#define NORMAL_TLOWR_MAX 15
#define NORMAL_TSLOT_MIN 60
#define NORMAL_TSLOT_MAX 120
#define NORMAL_BIT1_MIN NORMAL_TLOWR_MIN
#define NORMAL_BIT1_MAX NORMAL_TLOWR_MAX
#define NORMAL_BIT0_MIN NORMAL_TSLOT_MIN
#define NORMAL_BIT0_MAX NORMAL_TSLOT_MAX
#define NORMAL_TREC_MIN 1
#define NORMAL_RESET (RESET_DURATION_TSLOTS * NORMAL_TSLOT_MIN)
#define NORMAL_PRESENCE_START_MIN 15
#define NORMAL_PRESENCE_START_MAX 60
#define NORMAL_PRESENCE_MIN 60
#define NORMAL_PRESENCE_MAX 240
#define OVERDRIVE_TLOWR_MIN 1
#define OVERDRIVE_TLOWR_MAX 2
#define OVERDRIVE_TSLOT_MIN 6
#define OVERDRIVE_TSLOT_MAX 16
#define OVERDRIVE_BIT1_MIN OVERDRIVE_TLOWR_MIN
#define OVERDRIVE_BIT1_MAX OVERDRIVE_TLOWR_MAX
#define OVERDRIVE_BIT0_MIN OVERDRIVE_TSLOT_MIN
#define OVERDRIVE_BIT0_MAX OVERDRIVE_TSLOT_MAX
#define OVERDRIVE_TREC_MIN 1
#define OVERDRIVE_RESET (RESET_DURATION_TSLOTS * OVERDRIVE_TSLOT_MIN)
#define OVERDRIVE_PRESENCE_START_MIN 2
#define OVERDRIVE_PRESENCE_START_MAX 6
#define OVERDRIVE_PRESENCE_MIN 8
#define OVERDRIVE_PRESENCE_MAX 24
/* Максимальный требуемый период сигнала */
#define WIRE1_MAX_PERIOD (NORMAL_TSLOT_MAX + NORMAL_TREC_MIN)
/* Минимально требуемая точность (д.б. лучше двойного минимального импульса) */
#define WIRE1_MIN_PRECISION (OVERDRIVE_TLOWR_MAX / 2)
/* Максимально возможная продолжительность процедуры RESET */
#define WIRE1_RESET_PROCEDURE_DURATION (RESET_DURATION_TSLOTS * NORMAL_TSLOT_MIN * 2)
/* Значение TOP в NORMAL MODE */
#define PWM_NORMAL_MODE_TOP 0xFFFF
/* Фиксированные значения TOP, поддерживаемые оборудованием */
#define PWM_FIXED_TOP_1 0xFF
#define PWM_FIXED_TOP_2 0x1FF
#define PWM_FIXED_TOP_3 0x3FF
/* Длительность одного подсчета в мкс для заданного значения делителя */
#define PWM_PRECISION(_n)
((_n) * 1000000UL / F_CPU)
/* Продолжительность _cnt отсчетов в мкс */
#define PWM_DURATION(_n, _cnt)
((_cnt) * (_n) * 1000000UL / F_CPU)
/* Длительность одного периода в мск для заданных значений делителя */
#define PWM_PERIOD(_n, _t)
((_t) * 2 * (_n) * 1000000UL / F_CPU)
/* Определение значения счетчика в зависимости от требуемого времени */
#define PWM_COUNT_VALUE(_n, _t)
(((F_CPU / (_n)) / 1000000UL) * (_t))
/* Определение значения OCRA в зависимости от желаемой длительности ямы */
#define PWM_BOTTOM_VALUE(_n, _t)
((F_CPU / 2 / (_n) / 1000000UL) * (_t))
/*
* Алгоритм выбора значения делителя и TOP:
*
* A. Обязательные условия
* - период сигнала д.б. >= WIRE1_MAX_PERIOD
* - разрешающая способность д.б. < WIRE1_MIN_PRECISION
*
* B. Желательные условия
* - период сигнала должен быть как можно меньше (т.е. минимальный TOP)
*/
#undef PWM_TOP
/* Проверяем возможность использования делителя 8 */
#if PWM_DURATION(8, PWM_FIXED_TOP_3) >= WIRE1_MAX_PERIOD
#if PWM_PRECISION(8) < WIRE1_MIN_PRECISION
#undef PWM_TOP
#define PWM_TOP PWM_FIXED_TOP_3
#undef PWM_DIVIDER
#define PWM_DIVIDER 8
#undef PWM_TCCRA
#define PWM_TCCRA (_BV(WGM11) | _BV(WGM10))
#undef PWM_TCCRB
#define PWM_TCCRB _BV(CS11)
#endif
#endif
#if PWM_DURATION(8, PWM_FIXED_TOP_2) >= WIRE1_MAX_PERIOD
#if PWM_PRECISION(8) < WIRE1_MIN_PRECISION
#undef PWM_TOP
#define PWM_TOP PWM_FIXED_TOP_2
#undef PWM_DIVIDER
#define PWM_DIVIDER 8
#undef PWM_TCCRA
#define PWM_TCCRA _BV(WGM11)
#undef PWM_TCCRB
#define PWM_TCCRB _BV(CS11)
#endif
#endif
#if PWM_DURATION(8, PWM_FIXED_TOP_1) >= WIRE1_MAX_PERIOD
#if PWM_PRECISION(8) < WIRE1_MIN_PRECISION
#undef PWM_TOP
#define PWM_TOP PWM_FIXED_TOP_1
#undef PWM_DIVIDER
#define PWM_DIVIDER 8
#undef PWM_TCCRA
#define PWM_TCCRA _BV(WGM10)
#undef PWM_TCCRB
#define PWM_TCCRB _BV(CS11)
#endif
#endif
/* Проверяем возможность использования делителя 1 */
#if PWM_DURATION(1, PWM_FIXED_TOP_3) >= WIRE1_MAX_PERIOD
#if PWM_PRECISION(1) < WIRE1_MIN_PRECISION
#undef PWM_TOP
#define PWM_TOP PWM_FIXED_TOP_3
#undef PWM_DIVIDER
#define PWM_DIVIDER 1
#undef PWM_TCCRA
#define PWM_TCCRA (_BV(WGM11) | _BV(WGM10))
#undef PWM_TCCRB
#define PWM_TCCRB _BV(CS10)
#endif
#endif
#if PWM_DURATION(1, PWM_FIXED_TOP_2) >= WIRE1_MAX_PERIOD
#if PWM_PRECISION(1) < WIRE1_MIN_PRECISION
#undef PWM_TOP
#define PWM_TOP PWM_FIXED_TOP_2
#undef PWM_DIVIDER
#define PWM_DIVIDER 1
#undef PWM_TCCRA
#define PWM_TCCRA _BV(WGM11)
#undef PWM_TCCRB
#define PWM_TCCRB _BV(CS10)
#endif
#endif
#if PWM_DURATION(1, PWM_FIXED_TOP_1) >= WIRE1_MAX_PERIOD
#if PWM_PRECISION(1) < WIRE1_MIN_PRECISION
#undef PWM_TOP
#define PWM_TOP PWM_FIXED_TOP_1
#undef PWM_DIVIDER
#define PWM_DIVIDER 1
#undef PWM_TCCRA
#define PWM_TCCRA _BV(WGM10)
#undef PWM_TCCRB
#define PWM_TCCRB _BV(CS10)
#endif
#endif
#if !defined(PWM_TOP)
#error "Can't build I/O procedure with current F_CPU value"
#endif
/*
* Процедуру RESET мы будем делать в normal mode, но требуется чтобы
* время достижения TOP было не меньше удвоенного значения RESET
*
*/
#undef PWM_RESET_TCCRB
/* Проверяем возможность использования делителя 8 */
#if PWM_PERIOD(8, PWM_NORMAL_MODE_TOP) >= WIRE1_RESET_PROCEDURE_DURATION
#undef PWM_RESET_DIVIDER
#define PWM_RESET_DIVIDER 8
#undef PWM_RESET_TCCRB
#define PWM_RESET_TCCRB _BV(CS11)
#endif
/* Проверяем возможность использования делителя 1 */
#if PWM_PERIOD(1, PWM_NORMAL_MODE_TOP) >= WIRE1_RESET_PROCEDURE_DURATION
#undef PWM_RESET_DIVIDER
#define PWM_RESET_DIVIDER 1
#undef PWM_RESET_TCCRB
#define PWM_RESET_TCCRB _BV(CS10)
#endif
#if !defined(PWM_RESET_TCCRB)
#error "Can't build reset procedure with current F_CPU value"
#endif
/* Остановка таймера без изменения его остальных настроек */
#define DRV_STOP_CLOCK() do {
_SFR_BYTE(WIRE1_TCCRB) &= ~(_BV(CS12) | _BV(CS11) | _BV(CS10));
} while(0)
/* Установка режима работы "NORMAL MODE" */
#define DRV_TIMER_NORMAL_MODE() do {
_SFR_BYTE(WIRE1_TCCRB) &= ~(_BV(WGM13) | _BV(WGM12));
_SFR_BYTE(WIRE1_TCCRA) &= ~(_BV(WGM11) | _BV(WGM10));
} while(0)
/* Перевод пина OCRA в состояние по умолчанию (low level) */
#define DRV_DISCONNECT_OCRA_PIN() do {
_SFR_BYTE(WIRE1_TCCRA) &= ~(_BV(COM1A1) | _BV(COM1A0)); } while(0)
/* Перевод пина OCRA в состояние low level при достижении значения OCRA */
#define DRV_MATCH_OCRA_PIN_LOW() do {
_SFR_BYTE(WIRE1_TCCRA) =
(_SFR_BYTE(WIRE1_TCCRA) & ~_BV(COM1A0)) | _BV(COM1A1); } while(0)
/* Перевод пина OCRA в состояние high level при достижении значения OCRA */
#define DRV_MATCH_OCRA_PIN_HIGH() do {
_SFR_BYTE(WIRE1_TCCRA) |= _BV(COM1A1) | _BV(COM1A0); } while(0)
/* Отлавливаем переход с высокого на низкий уровень (1 => 0) */
#define DRV_CAPTURE_FALLING_EDGE() do {
_SFR_BYTE(WIRE1_TCCRB) &= ~_BV(ICES1); } while(0)
/* Отлавливаем переход с низкого на высокий уровень (0 => 1) */
#define DRV_CAPTURE_RISING_EDGE() do {
_SFR_BYTE(WIRE1_TCCRB) |= _BV(ICES1); } while(0)
/* Возвращает != 0, если отлавливается переход с низкого уровня на высокий */
#define DRV_IS_CAPTURE_RISING()
(_SFR_BYTE(WIRE1_TCCRB) & _BV(ICES1))
/* Значение OCRA при передаче значения бита 1 в режиме normal */
#define NORMAL_BIT1_VALUE
PWM_BOTTOM_VALUE(PWM_DIVIDER, (NORMAL_BIT1_MIN + NORMAL_BIT1_MAX) / 2)
/* Значение OCRA при передаче значения бита 1 в режиме overdrive */
#define OVERDRIVE_BIT1_VALUE
PWM_BOTTOM_VALUE(PWM_DIVIDER, (OVERDRIVE_BIT1_MIN + OVERDRIVE_BIT1_MAX) / 2)
/* Значение OCRA при передаче значения бита 0 в режиме normal */
#define NORMAL_BIT0_VALUE
PWM_BOTTOM_VALUE(PWM_DIVIDER, (NORMAL_BIT0_MIN + NORMAL_BIT0_MAX) / 2)
/* Значение OCRA при передаче значения бита 0 в режиме overdrive */
#define OVERDRIVE_BIT0_VALUE
PWM_BOTTOM_VALUE(PWM_DIVIDER, (OVERDRIVE_BIT0_MIN + OVERDRIVE_BIT0_MAX) / 2)
/* Минамально возможная длительность бита 1 в режиме normal в тиках таймера */
#define NORMAL_BIT1_MIN_VALUE
PWM_COUNT_VALUE(PWM_DIVIDER, NORMAL_BIT1_MIN)
/* Минамально возможная длительность бита 1 в режиме overdrive в тиках таймера */
#define OVERDRIVE_BIT1_MIN_VALUE
PWM_COUNT_VALUE(PWM_DIVIDER, OVERDRIVE_BIT1_MIN)
/* Максимально возможная длительность бита 1 в режиме normal в тиках таймера */
#define NORMAL_BIT1_MAX_VALUE
PWM_COUNT_VALUE(PWM_DIVIDER, NORMAL_BIT1_MAX)
/* Максимально возможная длительность бита 1 в режиме overdrive в тиках таймера */
#define OVERDRIVE_BIT1_MAX_VALUE
PWM_COUNT_VALUE(PWM_DIVIDER, OVERDRIVE_BIT1_MAX)
/* Максимально возможная длительность бита 0 в режиме normal в тиках таймера */
#define NORMAL_BIT0_MAX_VALUE
PWM_COUNT_VALUE(PWM_DIVIDER, NORMAL_BIT0_MAX)
/* Максимально возможная длительность бита 0 в режиме overdrive в тиках таймера */
#define OVERDRIVE_BIT0_MAX_VALUE
PWM_COUNT_VALUE(PWM_DIVIDER, OVERDRIVE_BIT0_MAX)
/* Тип контекста драйвера */
typedef struct {
/* Состояние драйвера, линии и т.п. */
volatile uint8_t state;
/* Параметры, зависящие от выполняемой задачи */
union {
/* Параметры для задачи выполнения операции RESET */
struct {
/* Измеренное значение presence TPDH */
volatile uint16_t tpdhMeasure;
/* Измеренное значение presence TPDL*/
volatile uint16_t tpdlMeasure;
} reset;
/* Параметры для задачи передачи либо приема байта данных */
struct {
/* Значение, которое действует в compare unit при capture event */
volatile uint16_t icpOCRA;
/* На входе - передаваемое значение, на выходе - принятое значение */
volatile uint8_t value;
/* Кол-во битов, оставшихся до завершения операции */
volatile uint8_t pending;
} io;
} param;
} drv_1wire_context_t;
/* state: Операция RESET завершена */
#define DRV_1WIRE_STATE_RESET_COMPLETE 0b10000000
/* state: Был обнаружен импульс PRESENCE и значения tpdhMeasure/tpdlMeasure валидны */
#define DRV_1WIRE_STATE_PRESENCE_DETECTED 0b01000000
/* state: Устройства могут работать в режиме overdrive */
#define DRV_1WIRE_STATE_OVERDRIVE_DETECTED 0b00100000
/* state: процедура обмена байтом завершена */
#define DRV_1WIRE_STATE_IO_COMPLETE 0b00010000
/* state: при выполнении обмена данными обнаружена ошибка */
#define DRV_1WIRE_STATE_IO_ERROR 0b00001000
/* state: как минимум одно устройство на шине использует parasite power */
#define DRV_1WIRE_STATE_BUS_PARASITE_POWER 0b00000100
/* state: после завершения передачи заданного кол-ва битов требуется активация pullup.
* Имеет смысл только при выполнении операции передачи либо приема битов */
#define DRV_1WIRE_STATE_ACTIVATE_PULLUP 0b00000010
/* state: Проверка управления: установка OCA в high привела к переходу ICP в low.
* Имеет смысл только после завершения операции RESET. */
#define DRV_1WIRE_STATE_OCA_LOW_PASSED 0b00000010
/* state: Проверка управления: установка OCA в low привела к переходу ICP в high.
* Имеет смысл только после завершения операции RESET. */
#define DRV_1WIRE_STATE_OCA_HIGH_PASSED 0b00000001
/* Был обнаружен адаптер 1-wire.
* Имеет смысл только после завершения операции RESET. */
#define DRV_1WIRE_STATE_1WIRE_DETECTED
(DRV_1WIRE_STATE_OCA_LOW_PASSED | DRV_1WIRE_STATE_OCA_HIGH_PASSED)
/* Условие состояния, при котором после операции сброса на шине обнаружено устройство */
#define DRV_1WIRE_STATE_DEVICE_DETECTED(_s)
((_s) & DRV_1WIRE_STATE_PRESENCE_DETECTED)
/* Условие состояния, при котором операция ввода/вывода завершилась успешно */
#define DRV_1WIRE_STATE_IO_OK(_s)
(((_s) & (DRV_1WIRE_STATE_PRESENCE_DETECTED | DRV_1WIRE_STATE_IO_ERROR)) == DRV_1WIRE_STATE_PRESENCE_DETECTED)
#define DRV_1WIRE_TXBITS(_v, _n, _p)
drv1WireStartIo((_v), (_n), (_p))
#define DRV_1WIRE_RXBITS(_n)
DRV_1WIRE_TXBITS(0xFF, (_n), 0)
/* Процедура передачи байта данных на линию */
#define DRV_1WIRE_TXBYTE(_v, _p)
DRV_1WIRE_TXBITS((_v), 8, (_p))
#define DRV_1WIRE_RXBYTE()
DRV_1WIRE_RXBITS(8)
#define DRV_1WIRE_RESET()
drv1WireStartReset(DRV_1WIRE_STATE() & DRV_1WIRE_STATE_OVERDRIVE_DETECTED)
#if defined(WIRE1_PULLUP_OUT)
#define DRV_1WIRE_PULLUP_ON()
drv1WirePullupOn()
#define DRV_1WIRE_PULLUP_OFF()
drv1WirePullupOff()
#else /* defined(WIRE1_PULLUP_OUT) */
#define DRV_1WIRE_PULLUP_ON()
#define DRV_1WIRE_PULLUP_OFF()
#endif /* defined(WIRE1_PULLUP_OUT) */
/* Контекст драйвера */
drv_1wire_context_t ctx1WireDriver;
/**
* Завершение процедуры RESET
*
* @param _flags - флаги по результатам завершения
*/
__INLINE void resetOperationComplete(uint8_t _flags) {
/* Запрещаем все прерывания, т.к. процедура завершена */
_SFR_BYTE(WIRE1_TIMSK) = 0;
/* Отмечаем достигнутое состояние */
ctx1WireDriver.state |= _flags | DRV_1WIRE_STATE_RESET_COMPLETE;
/* !? */
DRV_STOP_CLOCK();
/* Переводим пин OCA в режим low (на шине получаем high level) */
DRV_MATCH_OCRA_PIN_LOW();
_SFR_BYTE(WIRE1_TCCRC) |= _BV(FOC1A);
/* Процедура обнаружения precence завершена */
VOS_KERNEL_FIRE_SIGNIFICANT_EVENT(VOS_SE_REASON_1WIRE_PRESENCE_DETECTOR);
}
/**
* Завершение процедуры обмена
*
* @param _flags
*/
__INLINE void ioOperationComplete(uint8_t _flags) {
/* Запрещаем все прерывания, т.к. процедура завершена */
_SFR_BYTE(WIRE1_TIMSK) = 0;
/* Отмечаем достигнутое состояние */
ctx1WireDriver.state |= _flags | DRV_1WIRE_STATE_IO_COMPLETE;
/* Останавливаем таймер и начинаем приведение его режима к normal */
_SFR_BYTE(WIRE1_TCCRB) = 0;
/* Таймер в режиме normal, мы готовы к принудительной установки OCA в low */
_SFR_BYTE(WIRE1_TCCRA) = _BV(COM1A1);
/* Убеждаемся в том, что сигнал OCA имеет низкий уровень */
_SFR_BYTE(WIRE1_TCCRC) |= _BV(FOC1A);
/* Процедура приема или передачи байта данных завершена */
VOS_KERNEL_FIRE_SIGNIFICANT_EVENT(VOS_SE_REASON_1WIRE_IO_BYTE_COMPLETE);
}
/**
* Выполнение подготовки к передаче очередного бита
*/
__INLINE __OPTIMIZE_SPEED void txBit() {
if(ctx1WireDriver.param.io.value & 0x01) {
/* Будет передаваться бит со значением 1 */
_SFR_WORD(WIRE1_OCRA) =
(ctx1WireDriver.state & DRV_1WIRE_STATE_OVERDRIVE_DETECTED) ?
OVERDRIVE_BIT1_VALUE : NORMAL_BIT1_VALUE;
} else {
/* Будет передаваться бит со значением 0 */
_SFR_WORD(WIRE1_OCRA) =
(ctx1WireDriver.state & DRV_1WIRE_STATE_OVERDRIVE_DETECTED) ?
OVERDRIVE_BIT0_VALUE : NORMAL_BIT0_VALUE;
}
}
void drv1WireAttach() {
/* Разрешаем питание устройства */
WIRE1_POWER_ON();
/* Убеждаемся в остановке таймера */
DRV_STOP_CLOCK();
/* Z-состояние без pullup */
_SFR_BYTE(WIRE1_PORT) &= ~(_BV(WIRE1_OUT) | _BV(WIRE1_ICP));
/* Переводим пин OCA в режим low (на шине получаем high level) */
DRV_MATCH_OCRA_PIN_LOW();
_SFR_BYTE(WIRE1_TCCRC) |= _BV(FOC1A);
/* Set WIRE1_OUT out direction and low signal level, force ICP is in direction */
_SFR_BYTE(WIRE1_DDR) = (_SFR_BYTE(WIRE1_DDR) & ~_BV(WIRE1_ICP)) |
_BV(WIRE1_OUT);
#if defined(WIRE1_IDLE_PRESENCE_DETECT_BIT)
/* Z-состояние без pullup */
_SFR_BYTE(WIRE1_IDLE_PRESENCE_DETECT_PORT) &= ~_BV(WIRE1_IDLE_PRESENCE_DETECT_BIT);
/* Убедимся в том, что пин детектирования presence в idle mode является входом */
_SFR_BYTE(WIRE1_IDLE_PRESENCE_DETECT_DDR) &= ~_BV(WIRE1_IDLE_PRESENCE_DETECT_BIT);
#endif /* defined(WIRE1_IDLE_PRESENCE_DETECT_BIT) */
#if defined(WIRE1_PULLUP_OUT)
/* Z-состояние без pullup */
drv1WirePullupOff();
/* pullup pin всегда является выходом */
_SFR_BYTE(WIRE1_PULLUP_DDR) |= _BV(WIRE1_PULLUP_OUT);
#endif /* defined(WIRE1_PULLUP_OUT) */
/* Начальное состояние драйвера */
ctx1WireDriver.state = 0;
}
#if defined(WIRE1_PULLUP_OUT)
void drv1WirePullupOn() {
_SFR_BYTE(WIRE1_PULLUP_PORT) |= _BV(WIRE1_PULLUP_OUT);
}
void drv1WirePullupOff() {
_SFR_BYTE(WIRE1_PULLUP_PORT) &= ~_BV(WIRE1_PULLUP_OUT);
}
#endif /* defined(WIRE1_PULLUP_OUT) */
/**
* Реализация процедуры "RESET"
*
* @param _overdrive - используется режим OVERDRIVE ( != 0 - да)
*
* Идея следующая:
* 1. Переводим таймер в NORMAL MODE с исходным значением TCNT = 0
* 2. Устанавливаем OCRA = 0 и при помощи FOCA принудительно выставляем
* значение пина OCRA в HIGH (шина будет переведена в состояние LOW)
* 3. Устанавливаем значение OCRA на длительность периода LOW
* 4. Устанавливаем функцию сброса пина OCRA по достижению значения
* (т.е. шина будет переведена в состояние HIGH)
* 5. Запускаем таймер
*
* По достижению таймером значения OCRA пин OCRA будет автоматом переведен
* в состояние LOW (на шине выставится уровень high) и произойдет прерывание
*
*/
void drv1WireStartReset(uint8_t _overdrive) {
/* Убеждаемся в отключении strong pullup, чтобы не жечь лишний раз MOSFET */
DRV_1WIRE_PULLUP_OFF();
/* Текущее состояние */
ctx1WireDriver.state &=
~(DRV_1WIRE_STATE_RESET_COMPLETE |
DRV_1WIRE_STATE_PRESENCE_DETECTED |
DRV_1WIRE_STATE_1WIRE_DETECTED);
/* Устанавливаем длительность периода LOW, генерируемого нами */
if(_overdrive) {
ctx1WireDriver.param.reset.tpdhMeasure = PWM_COUNT_VALUE(PWM_RESET_DIVIDER, OVERDRIVE_RESET);
} else {
ctx1WireDriver.param.reset.tpdhMeasure = PWM_COUNT_VALUE(PWM_RESET_DIVIDER, NORMAL_RESET);
}
/* Запрещаем прерывания */
VOS_KERNEL_ENTER_MODE(VOS_KERNEL_RING_HARD);
/* Убеждаемся в остановке таймера */
DRV_STOP_CLOCK();
/* Переводим таймер в режим NORMAL MODE */
DRV_TIMER_NORMAL_MODE();
/* Инициализируем счетчик */
_SFR_WORD(WIRE1_TCNT) = 0;
/* Переводим пин OCRA в режим high (на шине получаем low level) */
DRV_MATCH_OCRA_PIN_HIGH();
_SFR_BYTE(WIRE1_TCCRC) |= _BV(FOC1A);
/* Устанавливаем длительность периода LOW */
_SFR_WORD(WIRE1_OCRA) = ctx1WireDriver.param.reset.tpdhMeasure;
/* Устанавливаем длительность всей процедуры */
_SFR_WORD(WIRE1_OCRB) = (ctx1WireDriver.param.reset.tpdhMeasure << 1);
/* По достижению OCRA пин OCRA переведем в low (на шине станет high) */
DRV_MATCH_OCRA_PIN_LOW();
/* Убеждаемся в том, что признаки достижения значения OCRA и OCRB сброшены (производится записью 1) */
_SFR_BYTE(WIRE1_TIFR) |= _BV(OCF1A) | _BV(OCF1B);
/* Разрешаем прерывания по достижению значения OCRA и OCRB */
_SFR_BYTE(WIRE1_TIMSK) |= _BV(OCIE1A) | _BV(OCIE1B);
/* Отслеживать будем переход сигнала ICP с уровня HIGH в уровень LOW */
DRV_CAPTURE_FALLING_EDGE();
/* Запускаем таймер в работу (также разрешим noise canceller) */
_SFR_BYTE(WIRE1_TCCRB) |= PWM_RESET_TCCRB | _BV(ICNC1);
/* Было выпоолнено много команд, уровень на входной шине уже должен измениться */
if(!(_SFR_BYTE(WIRE1_PIN) & _BV(WIRE1_ICP))) {
/* Входной сигнал низкого уровня, так и должно быть */
ctx1WireDriver.state |= DRV_1WIRE_STATE_OCA_LOW_PASSED;
}
/* Разрешаем прерывания */
VOS_KERNEL_ENTER_MODE(VOS_KERNEL_RING_SOFT);
}
/**
* Инициирование процедуры обмена байтом (прием или передача)
*
* @param _value - передаваемое значение
* @param _bits - кол-во передаваемых битов (обычно 8)
* @param _pullup - требуется активация pullup после завершения передачи всех битов
*
* Подразумевается, что вызов данной функции возможен только при остановленном
* таймере и сигнале OCA == 0 (исходное состояние).
*/
void drv1WireStartIo(uint8_t _value, uint8_t _bits, uint8_t _pullup) {
/* Убеждаемся в отключении strong pullup, чтобы не жечь лишний раз MOSFET */
DRV_1WIRE_PULLUP_OFF();
/* Состояние: процедура обмена только что начата и ошибок пока не обнаружено */
ctx1WireDriver.state &=
~(DRV_1WIRE_STATE_IO_COMPLETE | DRV_1WIRE_STATE_IO_ERROR);
/* Признак необходимости активации pullup после завершения передачи */
if(_pullup) {
ctx1WireDriver.state |= DRV_1WIRE_STATE_ACTIVATE_PULLUP;
} else {
ctx1WireDriver.state &= ~DRV_1WIRE_STATE_ACTIVATE_PULLUP;
}
/* Передаваемое значение */
ctx1WireDriver.param.io.value = _value;
/* Кол-во битов, подлежащих обмену */
ctx1WireDriver.param.io.pending = _bits;
/* Запрещаем прерывания */
VOS_KERNEL_ENTER_MODE(VOS_KERNEL_RING_HARD);
/* Убеждаемся в том, что признаки capture и bottm reach сброшены (производится записью 1) */
_SFR_BYTE(WIRE1_TIFR) |= _BV(ICF1) | _BV(TOV1);
/* Нас интересуют прерывания capture и достижение bottom */
_SFR_BYTE(WIRE1_TIMSK) |= _BV(ICIE1) | _BV(TOIE1);
/* Значение OCRA при передаче первого бита */
txBit();
/* Значение счетчика выберем на 1 меньше текущего top,
* чтобы через 1 clock значение OCRA применилось в схеме сравнения
*/
_SFR_WORD(WIRE1_TCNT) = PWM_TOP - 1;
/*
* Режим работы:
* Phase Correct PWM с фиксированным значением TOP,
* установка OCA=1 при совпадении с OCRA при подсчете вниз и
* установка OCA=0 при совпадении с OCRA при подсчете вверх
*/
_SFR_BYTE(WIRE1_TCCRA) = PWM_TCCRA | _BV(COM1A1);
/*
* Остаток настроек:
* WGM13 и WGM12 == 0 для Phase Correct PWM с фиксированным значением TOP
* установка делителя частоты
* установка Input Capture Noise Canceler
* capture будет происходить по переходу ICP с низкого на высокий уровень
*
* Таймер запущен и началась процедура передачи первого бита.
*/
_SFR_BYTE(WIRE1_TCCRB) = PWM_TCCRB | _BV(ICNC1) | _BV(ICES1);
/* Разрешаем прерывания */
VOS_KERNEL_ENTER_MODE(VOS_KERNEL_RING_SOFT);
}
/**
* Прерывание по достижению BOTTOM, т.к. для обмена данными мы используем режим
* Phase Correct PWM Mode.
* В этом режиме в точке BOTTOM мы можем загрузить значение OCRA, которое
* будет автоматически передано на схему сравнения только после достижения
* точки TOP.
*/
__OPTIMIZE_SPEED ISR(WIRE1_OVF_IRQ) {
if(ctx1WireDriver.param.io.pending--) {
/* Выталкиваем переданный бит и подготавливаем место для записи принятого */
ctx1WireDriver.param.io.value >>= 1;
/* Действующее в данный момент значение в compare unit */
ctx1WireDriver.param.io.icpOCRA = _SFR_WORD(WIRE1_OCRA);
/* Загружаем в OCRA значение, которое будет использоваться при передаче
* следующего бита. Реально оно начнет использоваться только после
* достижения очередного TOP.
*/
if(ctx1WireDriver.param.io.pending) {
txBit();
} else {
/* Передан последний бит, сдвиг делать не нужно */
}
} else {
/* Процедура обмена не завершилась на capture после всех битов.
* Это означает ошибку обмена.
*/
ioOperationComplete(DRV_1WIRE_STATE_IO_ERROR);
}
}
/**
* Прерывание по capture
*/
__OPTIMIZE_SPEED ISR(WIRE1_ICP_IRQ) {
if(ctx1WireDriver.state & DRV_1WIRE_STATE_RESET_COMPLETE) {
/*
* Процедура RESET завершена. Раз мы сюда все-таки попали, значит
* идет стандартная процедура обмена данными. В этом случае мы
* отлавливаем момент перехода сигнала ICP с уровня 0 в уровень 1.
* Продолжительность низкого уровня (общая, включая длительность строба)
* составляет io.icpOCRA + _SFR_WORD(WIRE1_ICR) единиц таймера.
*
* Т.к. все имеющиеся у нас длительности в мкс не превышают 255,
* будем использовать арифметику uint8_t.
*/
/* Длительность низкого уровня входного сигнала в тиках таймера */
uint16_t lowDuration =
ctx1WireDriver.param.io.icpOCRA + _SFR_WORD(WIRE1_ICR);
if(ctx1WireDriver.state & DRV_1WIRE_STATE_OVERDRIVE_DETECTED) {
/* Режим OVERDRIVE */
if((lowDuration < OVERDRIVE_BIT1_MIN_VALUE) ||
(lowDuration > OVERDRIVE_BIT0_MAX_VALUE)) {
/* Не выдержаны временные параметры протокола обмена */
ioOperationComplete(DRV_1WIRE_STATE_IO_ERROR);
/* Дальнейшая работа смысла не имеет */
return;
}
/* Выполняем декодирование бита */
if(lowDuration < OVERDRIVE_BIT1_MAX_VALUE) {
/* Принята нормальная лог. 1*/
ctx1WireDriver.param.io.value |= 0x80;
}
} else {
/* Нормальный режим */
if((lowDuration < NORMAL_BIT1_MIN_VALUE) ||
(lowDuration > NORMAL_BIT0_MAX_VALUE)) {
/* Не выдержаны временные параметры протокола обмена */
ioOperationComplete(DRV_1WIRE_STATE_IO_ERROR);
/* Дальнейшая работа смысла не имеет */
return;
}
/* Выполняем декодирование бита */
if(lowDuration < NORMAL_BIT1_MAX_VALUE) {
/* Принята нормальная лог. 1*/
ctx1WireDriver.param.io.value |= 0x80;
}
}
if(!ctx1WireDriver.param.io.pending) {
/* Было передано (и, возможно, принято) все заданное кол-во битов */
if(ctx1WireDriver.state & DRV_1WIRE_STATE_ACTIVATE_PULLUP) {
/* После завершения обмена была запрошена активация pullup */
DRV_1WIRE_PULLUP_ON();
}
/* Операция ввода/вывода завершена успешно */
ioOperationComplete(0);
}
} else {
/* Выполнение процедуры RESET.
* Сигнал ICP только что перешел из состояния HIGH в состояние LOW.
* Возможно это начало presence pulse.
*/
if(DRV_IS_CAPTURE_RISING()) {
/* ICES установлен, захват по возрастанию уровня */
/* Продолжительность TPDL */
ctx1WireDriver.param.reset.tpdlMeasure = _SFR_WORD(WIRE1_ICR) - ctx1WireDriver.param.reset.tpdlMeasure;
/* Т.к. обнаружен переход на высокий уровень, то и тестирование
* адаптера на переход на высокий уровень при снятии сигнала OCR
* также прошла успешно.
*/
ctx1WireDriver.state |= DRV_1WIRE_STATE_OCA_HIGH_PASSED;
/* Операция завершена, presence обнаружен */
resetOperationComplete(DRV_1WIRE_STATE_PRESENCE_DETECTED);
} else {
/* ICES сброшен, захват по спаду уровня */
ctx1WireDriver.param.reset.tpdlMeasure = _SFR_WORD(WIRE1_ICR);
/* Продолжительность TPDH */
ctx1WireDriver.param.reset.tpdhMeasure = ctx1WireDriver.param.reset.tpdlMeasure - ctx1WireDriver.param.reset.tpdhMeasure;
/* Теперь будем делать захват по возрастанию уровня сигнала */
DRV_CAPTURE_RISING_EDGE();
}
}
}
/**
* Прерывание по достижению значения OCRA
*/
__OPTIMIZE_SPEED ISR(WIRE1_COMPA_IRQ) {
/* Значение OCRA достигнуто при выполнении процедуры RESET.
* Это означает, что сигнал на пине OCRA только что был переведен
* в состояние low, а сигнал на шине начал переводиться в состояние high.
* Теперь нам надо определить интервал, через который сигнал на шине
* будет вновь переходить из состояния high в состояние low (т.е. обнаружить
* начало presence pulse, если таковой вообще будет).
*/
/* Принудительно сбрасываем флаг ICF (производится записью 1) */
_SFR_BYTE(WIRE1_TIFR) |= _BV(ICF1);
/* Разрешаем прерывания по capture */
_SFR_BYTE(WIRE1_TIMSK) |= _BV(ICIE1);
}
/**
* Прерывание по достижению значения OCRB
*
* Нами оно используется только в процедуре выполнения операции RESET для
* ограничения максимально допустимого времени ожидания сигнала PRESENCE.
*/
__OPTIMIZE_SPEED ISR(WIRE1_COMPB_IRQ) {
/* Значение OCRB достигнуто при выполнении процедуры RESET.
* Это означает, что сигнал presence обнаружен не был и устройства на
* шине отсутствуют.
* Однако мы можем проверить функционирование интерфейса 1-wire с точки
* зрения корректной передачи сигналов.
*/
if(_SFR_BYTE(WIRE1_PIN) & _BV(WIRE1_ICP)) {
/* Входной сигнал высокого уровня, так и должно быть */
ctx1WireDriver.state |= DRV_1WIRE_STATE_OCA_HIGH_PASSED;
}
/* Операция завершена, presence не обнаружен */
resetOperationComplete(0);
}
Вот пример осциллограммы выполнения приема:
Канал C подключен к выходу OC1A (импульсы одинаковой длительности), а канал B подключен к шине 1-Wire. Видно, что длительность 1-го и 3-го импульса на канале B больше, чем длительность соответствующего синхроимпульса на канале C. Т.е. slave-устройство в битах 1 и 3 передает значение «0». А длительность импульса 2 на каналах B и C приблизительно равна, что соответствует биту «1».
Так как используемый драйвер шины 1-Wire поддерживает также режим «ACTIVE PULLUP» (что также реализовано в примитивах), ниже приводятся соответствующие осциллограммы:
Активация режима «ACTIVE PULLUP» после завершения передачи последнего бита команды (сигнал канала B переходит из низкого уровня в высокий, после чего в течении не больше 10мкс сигнал активации PULLUP также переходит в активное состояние, подавая через транзистор Q5 напряжение питания на сигнальную шину).
Тот же режим «ACTIVE PULLUP», но в меньшем масштабе (передача команды, активация PULLUP, деактивация PULLUP и команда чтения с получением результатов).
Окончание режиме «ACTIVE PULLUP» с передачей команды на чтение результатов в укрупненном виде.
Процедура RESET (первый импульс низкого уровня на канале B, совпадающий по длительности с импульсом высокого уровня на канале C) с последующим PRESENCE от устройства, подключенного к шине 1-Wire (второй импульс низкого уровня на канале B, когда на канале C также присутствует низкий уровень).
То же самое, но после окончания режима «ACTIVE PULLUP».
Вопрос к читателям: имеет ли смысл писать продолжение, где будет реализация команд протокола обмена 1-Wire на основе этих примитивов и библиотеки protothreads [8]? В принципе там все очень просто и пишется просто сходу, подглядывая одним глазком в соответствующий datasheet.
Список литературы
Автор: Vedga
Источник [21]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/arduino/246766
Ссылки в тексте:
[1] DS28E17: #i2c1wire
[2] UART: #uart1wire
[3] Arduino: #megapromini
[4] оригинал: #protothreads_eng
[5] расжеванные примеры на русском языке: #protothreads_rus
[6] Arduino MEGA 2560: #mega2560
[7] физику работы: #microsin
[8] protothreads: http://protothreads_rus
[9] Драйвер шины 1-Wire для контроллеров питанием меньше 5V: https://geektimes.ru/post/286374/
[10] Четыре метода для подключения 1-Wire устройств к Raspberry Pi: http://blog.gegg.us/2013/03/4-different-methods-of-1-wire-access-on-raspberry-pi/
[11] 1-Wire Communication Through Software: https://www.maximintegrated.com/en/app-notes/index.mvp/id/126
[12] 1-Wire-to-I2C Master Bridge: https://www.maximintegrated.com/en/products/interface/controllers-expanders/DS28E17.html
[13] Single-Channel 1-Wire Master: https://www.maximintegrated.com/en/products/interface/controllers-expanders/DS2482-100.html
[14] Using a UART to Implement a 1-Wire Bus Master: https://www.maximintegrated.com/en/app-notes/index.mvp/id/214
[15] А здесь на пальцах объясняется функционирование устройств 1-Wire (на русском языке): http://microsin.ru/content/view/508/44
[16] Protothreads by Adam Dunkels: http://dunkels.com/adam/pt/index.html
[17] Protothread и кооперативная многозадачность: http://Protothread и кооперативная многозадачность
[18] ATmega328/P datasheet: http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_Datasheet.pdf
[19] Arduino PRO mini: https://www.arduino.cc/en/Main/arduinoBoardProMini
[20] Arduino MEGA 2560: https://www.arduino.cc/en/Main/arduinoBoardMega2560
[21] Источник: https://habrahabr.ru/post/322710/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.