(Not) dedicated to fans of protothreads: High-level functions for working with 1-Wire

    It is understood that we will write firmware for bare metal. Otherwise, the use of protothreads does not make sense, because multitasking should be provided by OS tools. It is also understood that we need to implement several more or less complex algorithms related to input-output operations. Well, and, as always with microcontrollers, there are obvious requirements for saving RAM and power consumption.

    As an example, consider the task of servicing devices on the 1-Wire bus. The implementation of asynchronous primitives for it can be seen in my previous articles here and here .

    For a PnP implementation, it is necessary for the program to be able to independently determine the characteristics of the bus, such as the maximum permissible exchange rate, a list of identifiers of the devices currently connected and the conditions for their power supply.
    We define the maximum permissible exchange rate in order to subsequently communicate with high-speed devices as quickly as possible. At the same time, slow devices will not be “noticed” by this exchange and will not interfere with us.

    The power supply conditions must be known in order to enable active pullup mode (if necessary) after giving a command to take measurements (or programming EEPROM). Otherwise, when using parasite power, we get an error when trying to read the measurement result (well, either we have to put low-impedance pull-up resistors, which, probably, is not a beautiful solution).

    Well, the list of identifiers of currently connected devices is simply vital for us. Otherwise, how are we going to address them?

    Algorithm for determining the characteristics of the bus:
    1. Let's try to perform the RESET procedure in OVERDRIVE mode. If PRESENCE was detected, then at least some of the connected devices can operate at high speed. In this case, go to step 3.
    2. We will try to execute the RESET procedure in normal mode. If PRESENCE was detected, then on the 1-Wire bus there is at least one connected device and we go to step 3. Otherwise, there are currently no devices connected to the bus.
    3. We send the command “Address all devices” to the bus and then the command “Read power conditions”. If at least one of the connected devices uses parasite power mode,
      set the corresponding flag.

    But the algorithm for determining the identifiers of connected devices is quite cumbersome. Its synchronous implementation is given in APPLICATION NOTE 187 , I just remade it asynchronous.

    At all stages of the execution of the algorithms, it is desirable to monitor errors that may occur due to the appearance of interference on the 1-Wire bus. Depending on the point of occurrence of the error, you can either automatically try to repeat the operation in progress or return a negative completion status.

    It is further assumed that the reader has minimal knowledge of protothreads . Whoever finds it difficult to understand English texts, can, for a start, read here and here .

    Under the spoiler is an example of calling procedures for determining bus parameters and detecting connected devices from the main program.

    Main program
        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();
    


    This is a demo. In a real program, instead of calling the waitComplete () function, we can switch to servicing other protothreads (and if they are not, then enter the low-power mode).

    Macros Used in Implementation
    #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)
    


    Short Description:

    PT_WAIT_IO_COMPLETE ()
    Waiting for an I / O operation to complete. Designed for use only inside protothread.

    PT_TX_BITS (_v, _n)
    Transfer _n bits from the value of _v to the bus, waiting for the completion of the I / O operation. Designed for use only inside protothread.

    PT_TX_BYTE (_v)
    Transmit the _v byte to the bus, waiting for the I / O operation to complete. Designed for use only inside protothread.

    PT_RX_BITS (_n)
    Receive _n bits, waiting for the completion of the I / O operation. Designed for use only inside protothread.

    PT_TX_BYTE_CONST (_v)
    Transmission of the command byte (constant) to the bus with checking for distortion of the transmitted data and waiting for the completion of the I / O operation. Designed for use only inside protothread.

    It should be noted that “waiting for completion of input input” in this case does not mean a dull cycle with checking of some condition, but interruption of the current protothread with the status PT_WAITING. This allows other protothreads to be performed periodically checking the current one until the end of the activated I / O operation.

    Transmission of the addressing command of all devices
    PT_THREAD(ptOneWireTargetAll(struct pt * _pt)) {
        uint8_t dummy;
        PT_BEGIN(TASK_CONTEXT);
        PT_TX_BYTE_CONST(OP_SKIP_ROM);
        PT_END(TASK_CONTEXT);
    }
    


    The operation of addressing all devices can be used with other commands of the 1-Wire bus, so it was framed as a separate protothreads.

    Tire Definition Procedure
    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);
    }
    


    The code is quite simple and implements the algorithm described above .

    The procedure for determining the identifiers of connected devices
    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);
    }
    


    I just took a synchronous implementation from Maxim and replaced the I / O routines with asynchronous macros. All this, along with running test cases, took me about half an hour. I wonder how much one would have to mess around without using protothreads wrappers?

    The full project source code for the STM8L-Discovery board is available on github . To create an assembly with the above examples at compilation, you must define the HIGH_LEVEL symbol.

    List of references:

    1. Github project code
    2. Primitives for implementing 1-Wire master using PWM and ICP for STM8L and STM32
    3. Primitives for implementing 1-Wire master using PWM and ICP on AVR AtMega microcontrollers
    4. Protothreads from adam dunkels
    5. Hdr from ldir Multitasking in continuity-based microcontrollers
    6. Habr from LifeV Protothread and cooperative multitasking
    7. APPLICATION NOTE 187. 1-Wire Search Algorithm

    Also popular now: