(Не) любителям protothreads посвящается: Высокоуровневые функции для работы с 1-Wire

от автора

Подразумевается, что мы будем писать прошивку под «голое железо». В противном случае применение protothreads смысла не имеет, т.к. мультизадачность должна обеспечиваться средствами ОС. Подразумевается также, что нам необходимо реализовать несколько более-менее сложных алгоритмов, связанных с операциями ввода-вывода. Ну и, как всегда в микроконтроллерах, очевидные требования по экономии RAM и энергопотребления.

В качестве примера рассмотрим задачу обслуживания устройств на шине 1-Wire. Реализацию асинхронных примитивов для нее можно посмотреть в моих предыдущих статьях здесь и здесь.
Для PnP-реализации необходимо, чтобы программа могла самостоятельно определять характеристики шины, такие как максимально допустимая скорость обмена, список идентификаторов подключенных в данный момент устройств и условия по их электропитанию.
Максимально допустимую скорость обмена мы определяем для того, чтобы впоследствии общаться с быстродействующими устройствами как можно более оперативно. При этом медленные устройства этот обмен «не заметят» и мешать нам не будут.
Условия по электропитанию надо знать для того, чтобы (при необходимости) после подачи команды на выполнение измерений (или программирования EEPROM) включить режим active pullup. В противном случае при использовании parasite power мы получим ошибку при попытке прочитать результат измерений (ну либо придется ставить низкоомные подтягивающие резисторы, что, наверное, не является красивым решением).
Ну а уж список идентификаторов подключенных в данный момент устройств нам иметь просто жизненно необходимо. Иначе как мы собираемся к ним обращаться?

Алгоритм определения характеристик шины:

  1. Попытаемся выполнить процедуру RESET в режиме OVERDRIVE. Если при этом был обнаружен PRESENCE, то значит, как минимум, некоторые из подключенных устройств умеют работать на высокой скорости. В этом случае переходим к п.3.
  2. Попытаемся выполнить процедуру RESET в нормальном режиме. Если при этом был обнаружен PRESENCE, то на шине 1-Wire имеется, как минимум, одно подключенное устройство и мы переходим к п.3. В противном случае подключенных к шине устройств в данный момент нет.
  3. Передаем на шину команду «Адресовать все устройства» и затем команду «Прочитать условия электропитания». В случае, если хотя бы одно из подключенных устройств использует режим parasite power,
    установим соответствующий флаг.

А вот алгоритм определения идентификаторов подключенных устройств достаточно громоздкий. Его синхронная реализация приведена в APPLICATION NOTE 187, я просто переделал ее в асихнронную.

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

Далее предполагается, что у читателя есть минимальные знания о protothreads. Кому тяжело понимать англоязычные тексты, может, для начала, почитать здесь и здесь.

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

Основная программа

    PT_INIT(&ptSearchContext.pt);          /* Определение максимальной скорости обмена и условий электропитания подключенных устройств */     while(PT_SCHEDULE(c = ptOneWireProbeBus(&ptSearchContext.pt, &nested))) {         if(PT_WAITING == c) {             /* Операция в процессе выполнения, можно заняться чем-либо еще */             waitComplete();                          continue;         }     }          /* Инициализируем контекст перед первым вызовом */     ptOneWireInitWalkROM(&ptSearchContext);          /* Выполнение задачи определения подключенных к 1-Wire устройств */     while(PT_SCHEDULE(c = ptOneWireWalkROM(&ptSearchContext))) {         if(PT_WAITING == c) {             /* Операция в процессе выполнения, можно заняться чем-либо еще */             waitComplete();                          continue;         }                  /* На шине 1-Wire обнаружено очередное устройство.          * Его S/N находится в массиве ptSearchContext.romid          */         __no_operation();     }          /* Все устройства просканированы */     __no_operation(); 

Это демонстрационный пример. В реальной программе вместо вызова функции waitComplete() мы можем переключаемся на обслуживание других protothreads (а если их нет, то войти в режим пониженного энергопотребления).

Макрокоманды, используемые в реализации

#define OVERDRIVE()                                                     \     drv_onewire_context.overdrive          #define PRESENCE_DETECTED()                                             \     drv_onewire_context.presence  #define PARASITE_POWER                                                  \     drv_onewire_context.parasite          #define STATUS                                                          \     drv_onewire_context.status          #define PT_WAIT_IO_COMPLETE()                                           \     PT_WAIT_WHILE(TASK_CONTEXT, ONEWIRE_STATUS_PROGRESS == (dummy = drvOneWireStatus()))  #define IO_SUCCESS()                                                    \     (ONEWIRE_STATUS_COMPLETE == dummy)  #define PT_TX_BITS(_v,_n) do {                                          \     if(drvOneWireTxBits((_v),(_n))) {                                   \         PT_WAIT_IO_COMPLETE(); } else {                                 \         dummy = ONEWIRE_STATUS_ERROR; } } while(0)          #define PT_TX_BYTE(_v)                                                  \     PT_TX_BITS((_v), 8)          #define PT_RX_BITS(_n)                                                  \     PT_TX_BITS(~0,(_n))          #define PT_TX_BYTE_CONST(_v) do {                                       \     PT_TX_BYTE((_v));                                                   \     if(!IO_SUCCESS() || ((_v) != drvOneWireRxBits(8))) {                \         STATUS = ONEWIRE_STATUS_ERROR; } } while(0) 

Краткое описание:
PT_WAIT_IO_COMPLETE()
Ожидание завершения операции ввода/вывода. Предназначена для применения только внутри protothread.

PT_TX_BITS(_v,_n)
Передача _n бит из значения _v на шину с ожиданием завершения операции ввода/вывода. Предназначена для применения только внутри protothread.

PT_TX_BYTE(_v)
Передача байта _v на шину с ожиданием завершения операции ввода/вывода. Предназначена для применения только внутри protothread.

PT_RX_BITS(_n)
Прием _n бит с ожиданием завершения операции ввода/вывода. Предназначена для применения только внутри protothread.

PT_TX_BYTE_CONST(_v)
Передача байта команды (констатны) на шину с проверкой отсутствия искажений передаваемых данных и ожиданием завершения операции ввода/вывода. Предназначена для применения только внутри protothread.

Следует отметить, что «ожидание завершения ввода вывода» в данном случае обозначает не глухой цикл с проверкой какого-либо условия, а прерывание текущей protothread со статусом PT_WAITING. Это позволяет выполнять другие protothreads с периодической проверкой текущей до момента завершения активированной операции ввода/вывода.

Передача команды адресации всех устройств

PT_THREAD(ptOneWireTargetAll(struct pt * _pt)) {     uint8_t dummy;          PT_BEGIN(TASK_CONTEXT);      PT_TX_BYTE_CONST(OP_SKIP_ROM);          PT_END(TASK_CONTEXT); } 

Операция адресации всех устройств может быть использована и с другими командами шины 1-Wire, поэтому она была оформлена в виде отдельной protothreads.

Процедура определения параметров шины

PT_THREAD(ptOneWireProbeBus(struct pt * _pt, struct pt * _nested)) {     uint8_t dummy;          PT_BEGIN(TASK_CONTEXT);          /* Parasite power not detected */     PARASITE_POWER = 0;          /* Try overdrive procedure first */     if(drvOneWireReset(1)) {         PT_WAIT_IO_COMPLETE();                  if(!IO_SUCCESS() || !PRESENCE_DETECTED()) {             /* Overdrive RESET procedure failed */             if(drvOneWireReset(0)) {                 PT_WAIT_IO_COMPLETE();                          if(!IO_SUCCESS() || !PRESENCE_DETECTED()) {                     /* No devices on the bus */                     PT_EXIT(TASK_CONTEXT);                 }             } else {                 /* Hardware BUSY */                 PT_EXIT(TASK_CONTEXT);             }         }     } else {         /* Hardware BUSY */         PT_EXIT(TASK_CONTEXT);     }          PT_SPAWN(TASK_CONTEXT, _nested, ptOneWireTargetAll(_nested));      if(ONEWIRE_STATUS_COMPLETE == STATUS) {         PT_TX_BYTE_CONST(OP_READ_POWER_SUPPLY);          if(IO_SUCCESS()) {             /* Read one bit after command */             PT_RX_BITS(1);                  if(IO_SUCCESS()) {                 /* Fetch bit value */                 int16_t value = drvOneWireRxBits(1);                              if(value < 0) {                     /* Rx bit decode failed */                     STATUS = ONEWIRE_STATUS_ERROR;                 } else {                     /* If any device sent "0" then it used parasite power */                     PARASITE_POWER = value ? 0 : 1;                 }             }         }     }          PT_END(TASK_CONTEXT); } 

Код достаточно простой и реализует описанный выше алгоритм.

Процедура определения идентификаторов подключенных устройств

PT_THREAD(ptOneWireWalkROM(pt_onewire_search_context_t * _ctx)) {     PT_BEGIN(TASK_CONTEXT);          while(!LAST_DEVICE_FLAG) {         int16_t dummy;                  /* initialize for search */         ID_BIT_NUMBER = 1;         LAST_ZERO = 0;         ROM_BYTE_NUMBER = 0;         ROM_BYTE_MASK = 1;                  /* 1-Wire reset (dependent on OVERDRIVE flag) */         PT_ONEWIRE_RESET();                  if(!IO_SUCCESS() || !PRESENCE_DETECTED()) {             // reset the search             LAST_DISCREPANCY = 0;             LAST_DEVICE_FLAG = 0;             LAST_FAMILY_DISCREPANCY = 0;              /* If presence not detected then no devices on the bus */             PT_EXIT(TASK_CONTEXT);         }          /* issue the search command */         PT_TX_BYTE(OP_SEARCH_ROM);                  if(!IO_SUCCESS() || (OP_SEARCH_ROM != drvOneWireRxBits(8))) {             /* Send command error, repeat procedure from RESET point */                          /* Other solution is abort search procedure */                          continue;         }                  // loop to do the search         do {             // read a bit and its complement             PT_RX_BITS(2);                          if(!IO_SUCCESS()) {                 /* Error while receiving 2 bits.                  * As ID_BIT_NUMBER less than 65 search procedure                  * resumed from state such as original task entry.                  */                 break;             }              if((RX_VALUE = drvOneWireRxBits(2)) < 0) {                 __no_operation();                                  break;             }                          uint8_t id_bit = (RX_VALUE & 0x01) ? 1 : 0;             uint8_t cmp_id_bit = (RX_VALUE & 0x02) ? 1 : 0;              uint8_t search_direction;                          /* check for no devices on 1-wire */             if ((id_bit == 1) && (cmp_id_bit == 1)) {                 /* Same bit values equ "1" indicate no devices on the bus */                 break;             } else {                 /* all devices coupled have 0 or 1 */                 if (id_bit != cmp_id_bit) {                     search_direction = id_bit;  /* bit write value for search */                 } else {                     /* if this discrepancy if before the LAST_DISCREPANCY                      on a previous next then pick the same as last time */                     if (ID_BIT_NUMBER < LAST_DISCREPANCY) {                         search_direction = (ROMID_BYTE_REF(ROM_BYTE_NUMBER) & ROM_BYTE_MASK) ? 1 : 0;                     } else {                         /* if equal to last pick 1, if not then pick 0 */                         search_direction = (ID_BIT_NUMBER == LAST_DISCREPANCY) ? 1 : 0;                     }                      /* if 0 was picked then record its position in LAST_ZERO */                     if (search_direction == 0) {                         LAST_ZERO = ID_BIT_NUMBER;                     }                      /* check for LAST_FAMILY_DISCREPANCY in family */                     if (LAST_ZERO < 9) {                         LAST_FAMILY_DISCREPANCY = LAST_ZERO;                     }                                      }             }              /* set or clear the bit in the ROM byte ROM_BYTE_NUMBER                with mask rom_byte_mask */             if (search_direction == 1) {                 ROMID_BYTE_REF(ROM_BYTE_NUMBER) |= ROM_BYTE_MASK;             } else {                 ROMID_BYTE_REF(ROM_BYTE_NUMBER) &= ~ROM_BYTE_MASK;             }              /* serial number search direction write bit */             PT_TX_BITS(search_direction, 1);                          /* search_direction not stored, therefore we can't check echo */              if(!IO_SUCCESS()) {                 /* Sending direction failed.                  * As ID_BIT_NUMBER less than 65 search procedure                  * resumed from state such as original task entry.                  */                 break;             }              /* increment the byte counter ID_BIT_NUMBER                and shift the mask rom_byte_mask */             ID_BIT_NUMBER++;             ROM_BYTE_MASK <<= 1;              /* if the mask is 0 then go to new SerialNum byte ROM_BYTE_NUMBER and reset mask */             if (ROM_BYTE_MASK == 0) {                 ROM_BYTE_NUMBER++;                 ROM_BYTE_MASK = 1;             }         } while(ROM_BYTE_NUMBER < 8);  /* loop until through all ROM bytes 0-7 */          /* if the search was successful then */         if(!(ID_BIT_NUMBER < 65)) {             uint8_t i;                          /* Calculate CRC */             uint8_t crc = 0;             for(i = 0;i < sizeof(ROMID);i++) {                 crc = modOneWireUpdateCRC(crc, ROMID_BYTE_REF(i));             }                          if(crc) {                 /* CRC error.                  * Repeat procedure from original point                  */                 continue;             }                          /* search successful so set LAST_DISCREPANCY and LAST_DEVICE_FLAG */             LAST_DISCREPANCY = LAST_ZERO;              // check for last device             if (LAST_DISCREPANCY == 0) {                 LAST_DEVICE_FLAG = 1;             }              /* Next device detection complete */         } else {             /* I/O error.              * Retry procedure from original point              */             continue;         }                  if(!ROMID.id.familyCode) {             /* familyCode не должен быть равен 0! */             break;         }                  /* Return detected device S/N */         PT_YIELD(TASK_CONTEXT);     }      /* Reset state for next scan loop (if need) */     ptOneWireInitWalkROM(CONTEXT);          PT_END(TASK_CONTEXT); }  /*  * Initialize device search procedure  */ void ptOneWireInitWalkROM(pt_onewire_search_context_t * _ctx) {     /* Prepare ptOneWireWalkROM() for first call  */     LAST_DISCREPANCY = 0;     LAST_DEVICE_FLAG = 0;     LAST_FAMILY_DISCREPANCY = 0;      /* Initialize protothreads data */     PT_INIT(TASK_CONTEXT); } 

Я просто взял синхронную реализацию от Maxim и заменил обращения к процедурам ввода/вывода на асинхронные макрокоманды. Все это вместе с прогонкой тестовых примеров заняло у меня порядка получаса. Интересно, сколько пришлось бы возиться без применения обертки protothreads?

Полный исходный код проекта для STM8L-Discovery board размещен на github. Для создания сборки с вышеприведенными примерами при компиляции необходимо определить символ HIGH_LEVEL.

Список литературы:

  1. Код проекта на github
  2. Примитивы для реализации 1-Wire master при помощи PWM и ICP для STM8L и STM32
  3. Примитивы для реализации 1-Wire master при помощи PWM и ICP на микроконтроллерах AVR AtMega
  4. Protothreads from Adam Dunkels
  5. Хабр от ldir Многозадачность в микроконтроллерах на основе продолжений
  6. Хабр от LifeV Protothread и кооперативная многозадачность
  7. APPLICATION NOTE 187. 1-Wire Search Algorithm

ссылка на оригинал статьи https://habrahabr.ru/post/326320/


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *