Нет, я не буду брать распиаренные сейчас STM32 — зачем стрелять из пушки по воробьям!? Возьмем старую добрую AtMega328P (ну или какую-нибудь Arduino, если так станет кому-либо легче) и соберем все на ней. Вот только делать все будем «по-взрослому», с работой в режиме разрешенных прерываний и с расчетом на последующую реализацию более высоких уровней при помощи protothreads (см. оригинал и расжеванные примеры на русском языке). Т.е. от 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 этот сигнал вообще не выведен во внешний мир.
Итак, для генерации PWM используем контакт OC1A. Для ICP вариантов вообще нет — вывод такого типа у этой микросхемы только один. Для режима «ACTIVE PULLUP» используем любой свободный IO port и настроим его на работу в режиме «OUT».
Так, со схемотехникой разобрались. Теперь освежим в памяти физику работы шины 1-wire. В двух словах ее можно описать так:
- Прием и передача тактируется master-устройством (т.е. нами)
- Временной интервал между передаваемыми и/или принимаемыми битами не критичен (главное, чтобы он был не меньше минимально допустимого)
- Критичным является длительность передаваемого импульса и длительность принимаемого (измеряемого) сигнала
Отлично, тогда выбираем режим работы 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, установим сигнал на выводе OC1A в высокий уровень (на шине 1-Wire будет низкий уровень).
- когда таймер считает «вверх» и его значение совпало со значением из регистра OCR1A, установим сигнал на выводе OC1A в низкий уровень (и, возможно, начнем измерять момент перехода сигнала на выводе ICP с низкого уровня на высокий).
Остальное все просто. Новое значение (длительность следующего передаваемого бита) в регистр 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? В принципе там все очень просто и пишется просто сходу, подглядывая одним глазком в соответствующий datasheet.
Список литературы
- Драйвер шины 1-Wire для контроллеров питанием меньше 5V
- Четыре метода для подключения 1-Wire устройств к Raspberry Pi
- 1-Wire Communication Through Software
- 1-Wire-to-I2C Master Bridge
- Single-Channel 1-Wire Master
- Using a UART to Implement a 1-Wire Bus Master
- А здесь на пальцах объясняется функционирование устройств 1-Wire (на русском языке)
- Protothreads by Adam Dunkels
- Protothread и кооперативная многозадачность
- ATmega328/P datasheet
- Arduino PRO mini
- Arduino MEGA 2560
Комментарии (16)
sim-dev
27.02.2017 21:23Я правильно понял, что вы предлагаете задействовать таймер, 2 прерывания, три линии ввода-вывода микроконтроллера, простыню текста в программе и все ради того, чтобы обеспечить какую-то асинхронность?!
По-моему, обмен не выгодный. Наибольшая ценность в AVR — это как раз порты ввода-вывода, и тут у вас проигрыш троекратный. В большинстве популярных микроконтроллеров таймеров тоже не изобилие, и выделять практически монопольно 16-битный таймер (который чаще всего один, редко два) под не первичной важности задачу — это, с моей точки зрения, тоже не совсем верный подход.
Возможно, я не вижу преимуществ — так и расскажите о них! То, что вы в восторге от Protothread, не аргумент: преимущества должны быть объективными.
В моей практике было впихивание протокола 1-wire в attiny13. Сумеете при вашем подходе повторить (при том, что ICP и Phase Correct PWM вообще отсутствуют в attiny13)?VT100
27.02.2017 21:46Пожалуй — соглашусь. 2 прерывания — пусть, они всё равно есть. А вот три вывода — это, конечно, излишество.
Как вариант, думаю, — можно завязать всё на один вывод ICP, записывая выходные данные «вручную» в прерываниях таймера. Получающийся «джиттер» на пару десятков тактов F_CPU всё ещё останется существенно меньше 120 тактов F_CPU (15 мкс при F_CPU = 8 МГц).Vedga
27.02.2017 22:17+1Если бы пару десятков тактов… Одновременно идет обмен по USART (это точно), по SPI (ну это как управляющему контроллеру в CPU взбредет), по линиям регистрации событий (как фишка ляжет). Сейчас задумываюсь по поводу переноса некоторых I2C устройств с управляющего контроллера на AVR-ку. С выполнением на ней же некоторой предварительной обработки. Чтобы «наверх» передавать только чистые данные.
P.S. А если кто-нибудь воткнет быстродействующее 1wire-устройство? Работать с ним, как и с медленными? Здесь уже формирование PWM по прерываниям никак не спасет на 8MHz.werwolfby
28.02.2017 15:08По моему всё супер и именно так и нужно делать как у вас.
А впихнуть 1-wire в attiny13 ну так проблем я в этом не вижу, всё на nop'а на крайняк всегда можно сделать и такты посчитать. А делать это нужно только в крайних случаях когда места мало.
А вот полностью ассинхронный код почти на голой аппарутуре — это круто, надёжно и стабильно.
Vedga
27.02.2017 22:07+2Наибольшая ценность AVR в умении разработчика использовать возможности оборудования для решения имеющейся задачи ;).
В моем проекте AVR-ка выполняет следующие функции:
— через UART выполняет обмен данными по шине eBus (высокоуровневый, управляет устройством в реалтайме в зависимости от заданных параметров). Т.е. имеем конечный автомат приблизительно с 10 состояниями (уровень протокола обмена) и еще один с 15 состояниями (логика управления). Каждый из них требует 1 или 2-х независимых таймера (с точностью ± несколько миллисекунд, а не микросекунд).
— через PWM/ICP обеспечивает обмен с устройствами по шине 1-wire (собирает данные). А еще надо бы отследить появление новых устройств и исчезновение старых. Вообщем, добавляем задачу device discovery. Discovery должна выполняться периодически (вот еще один таймер), да и данные с датчиков будем собирать через некоторые интервалы времени (и еще таймер).
— всякая мелочевка типа подсчета событий по другим пинам. А вот события терять нельзя, ибо получим расхождения между фактической ситуацией и ситуацией с точки зрения софтины.
— обмен по SPI (где AVR выступает slave) с контроллером более высокого уровня. Ибо параметры управления должен кто-то выдать, а результаты датчиков надо бы куда-то девать. И этот высокоуровневый контроллер не должен ждать ~750ms результатов измерений, ему надо дать ответ сразу. Ну пусть данные и будут устаревшими на те же самые 750ms, но они будут доступны немедленно.
Реализовать 1wire в attiny при помощи bitbang особого ума не надо, все описано в appnotes. Тайминг команд посчитать не трудно. Аналогично и про USART, читай appnote и тупо копируй. Вот только проблема в том, что я для обслуживания 1wire не хочу ставить ставить отдельный кристалл. А все остальные задачи должны жить асинхронно и независимо. Т.е. прерывания должны быть разрешены максимально возможное время. Что убьет тайминг в bitbang. А второго USART-а у меня на кристалле нет.
Преимущества protothreads? Да никаких, кроме более наглядной реализации требуемого алгоритма в программном коде. А имея набор примитивов, живущих по прерываниям, очень просто и наглядно реализуется целевая задача. Ладно, раз просят продолжение, оно будет. Может быть у кого-нибудь и изменится мнение по поводу полезности многопоточного программирования в слабеньких микроконтроллерах ;)…sim-dev
01.03.2017 18:51Т.е. прерывания должны быть разрешены максимально возможное время. Что убьет тайминг в bitbang.
Для bitbang-реализации 1-wire прерывания надо запрещать где-то на 10-15 микросекунд в начале таймслота. Не представляю, как это может помешать высокоуровневым протоколам.
И этот высокоуровневый контроллер не должен ждать ~750ms результатов измерений, ему надо дать ответ сразу. Ну пусть данные и будут устаревшими на те же самые 750ms, но они будут доступны немедленно.
Это речь сейчас о датчиках типа DS18x20? Задержка в 0,75 с никак не связана с bitbang-ом или иным способом реализации интерфейса 1-wire, это более высокий уровень абстракции, так что в данном контексте не в тему вообще.Vedga
02.03.2017 09:41+1А нолик как передавать будем? С разрешенными прерываниями? А если в этот момент идет активный обмен по SPI, I2C и UART? Передача нолика может превратится в передачу RESET ;).
0.75с относилось к protothreads. Т.е. высокоуровневые задачи должны выполняться в фоне, а не «приняли запрос с SPI, выдали команду на 1wire, ждем 0.75с, прочитали ответ с 1wire, ответили на запрос по SPI». И высокоуровневая задача не одна, а несколько.
zapimir
28.02.2017 01:15Нет, я не буду брать распиаренные сейчас STM32 — зачем стрелять из пушки по воробьям!?
Ну STM32 ладно, но почему не взять STM8? Тем более, что они стоят сейчас дешевле даже attiny13, а по возможностям на уровне AtMega328P (которая в 3-4 раза дороже).Vedga
28.02.2017 09:28STM8 тоже вполне подойдет. Идея статьи показать реализацию 1wire в случае, когда bitbang не подходит, а свободного USART нет. Насколько я знаю, такой вариант в appnotes отсутствует.
Nick_Shl
28.02.2017 09:33Зачем? На Али платка с STM32F103C8T6 стоит меньше двух баксов — примерно как Arduino Mini!
Если, конечно, устройство будет выпускаться не миллионными тиражами, где есть смысл экономить каждый цент.zapimir
28.02.2017 15:04Ну платы на базе STM8S103F3P6 на том же ali вообще стоят по 65 центов (это поштучно и с бесплатной доставкой), если брать десяток то будет центов по 45.Так что зачем платить больше, если достаточно решения в 4 раза дешевле?
Vedga
28.02.2017 15:07Так никто и не спорит, что STM8 тоже подходящий вариант. Но и на ней только один UART, который будет задействован на другой задаче. Т.е. решение аналогичное: PWM + ICP.
werwolfby
Конечно стоит писать продолжение!
Я тоже очень люблю protothreads, но с удовольствием прочитаю еще одну реализацию на них (кстати я не часто вмжу ее использование)
Писал что-то похожее но на STM32 с поддержкой DMA для чтения знаений из DHT22, единственный минус был в используемой памяти: ибо на 1 бит использовался 1 байт. Зато было полностью ассинхронно.