[Home] [Donate!] [Контакты]

Пример программной реализации 1-Wire с использованием UART

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

Оглавление
1-Wire интерфейс
Использование UART для реализации 1-Wire {Теория}
Пример программной реализации 1-Wire с использованием UART
Введение
Пример
Смотрите также
Измерение температуры. Аналоговые и цифровые датчики. Цифровой датчик DS18B20
UART и USART. COM-порт

Введение

Теоретические основы метода подробно изложены в статье "Использование UART для реализации 1-Wire". Напомним самые важные сведения, которые непосредственно потребуются нам в разработке кода для микроконтроллера.

  1. Для получения 1-Wire шины, вход и выход используемого UART соединяются вместе (для получения эхо-ответа) и через подтягивающий резистор подключаются к источнику питания микроконтроллера; выход UART настраивается для работы в режиме с открытым стоком.
  2. Используются следующие настройки UART: полнодуплексный режим работы, 8 бит данных на фрейм (бит чётности не используется), один стоп-бит. Для передачи данных используется скорость 115200 бит/с, а для сброса - скорость 9600 бит/с.
  3. Для выполнения сброса, на скорости 9600 через UART отправляем байт 0xF0. Если принятый ответный эхо-байт совпадает с отправленным, значит, на шине нет ведомых устройств. Если полученный байт отличается от отправленного, значит, на шине есть хотя бы одно ведомое устройство.
  4. Для передачи ведомому устройству бита 0 (слот записи 0), на скорости 115200 через UART отправляем байт 0x00. Для передачи бита 1 (слот записи 1), на скорости 115200 отправляем байт 0xFF.
  5. Для получения бита от ведомого устройства, на скорости 115200 отправляем байт 0xFF и анализируем входящий байт, считанный UART с шины. Если полученный байт отличается от отправленного, значит, получен бит 0 (слот чтения 0). Если полученный байт совпадает с отправленным, значит, получен бит 1 (слот чтения 1).

Как видим, нет ничего сложного. Теперь перейдём, собственно, к программной реализации.

Пример

Программные реализации описанного метода взаимодействия с шиной 1-Wire могут быть различными. В простейшем случае, для отправки или получения по 1-Wire каждого бита данных мы должны: дождаться готовности устройства UART; выполнить запись соответствующего ситуации значения; дождаться получения ответного значения и проанализировать его. В свою очередь, ожидание готовности UART к передаче и ожидание получения данных также может быть реализовано по-разному. Наиболее простой программа будет в том случае, если будем в цикле анализировать флаги состояния UART. Но можно достичь значительно более эффективного использования вычислительных ресурсов микроконтроллера, если задействовать в программе прерывания от UART. С другой стороны, при этом возрастёт сложность и размер кода, потребуется больше памяти на буферизацию данных. Окончательное решение о том, какой подход в целом более предпочтителен, зависит от многих обстоятельств.

Если предполагается пересылать по 1-Wire значительные объёмы данных (конечно, "значительными", с учётом особенностей 1-Wire, их можно назвать лишь весьма условно), вероятно имеет смысл задействовать DMA. С помощью DMA можем отправлять через UART и получать данные сразу большими блоками с минимальными затратами процессорного времени. Но тогда нужно быть готовым к тому, что код получится ещё сложнее, а расходы памяти на буферизацию - ещё больше. Как ни странно, для простых микроконтроллеров оптимальным может оказаться простейшее, казалось бы, малоэффективное решение. Которое и используется в рассматриваемом далее примере.

Текст программы получился небольшим, поэтому он помещён в единственный файл, содержащий, в том числе и вспомогательные функции (для начальной инициализации используемых аппаратных средств, для взаимодействия с UART).

Основными функциями для взаимодействия с 1-Wire являются две функции:
int ow_reset();
uint8_t ow_send_bit(uint8_t b);

как видно из названия, ow_reset() используется для выполнения сброса на шине 1-Wire; функция возвращает OW_OK, если в ответ на сигнал сброса был получен сигнал присутствия от ведомых устройств и возвращает OW_NO_DEVICE, если устройства на шине не обнаружены (не было сигнала присутствия). Функция ow_send_bit является универсальной функцией, используемой для передачи/получения данных; она передаёт один бит данных (младший бит аргумента, используемого при вызове функции); одновременно с этим выполняет считывание бита данных и возвращает его как результат функции. Это означает, что при вызове в виде ow_send_bit(1), она может использоваться не только для формирования слота записи 1, но и для формирования слота чтения, т.е. для получения бита данных от ведомого устройства.

Так как часто приходится отправлять/считывать данные сразу целыми байтами, то для удобства добавлена функция
uint8_t ow_send(uint8_t b);
которая также универсальна и при вызове в виде ow_send(oxFF) может использоваться как для передачи байта 0xFF, так и для получения байта от ведомого устройства; имеется также эквивалентная вызову ow_send(oxFF) функция uint8_t ow_receive(), специально предназначенная для считывания одного байта.

Для того чтобы можно было передавать данные сразу блоками по несколько байт, добавлены функции

void ow_send(const void *buf, unsigned int size); // отправляет данные из буфера
void ow_receive(void *buf, unsigned int size)     // считывает данные в буфер.

Далее приведён полный текст программы.

ow_over_uart1.zip (скачать пример реализации 1-Wire на основе UART)

/*
******************************************************************************
File:       main.cpp
Info:       Пример кода с использованием программной реализации
            1-Wire интерфейса на основе UART.
            Предполагается использование MCU STM32F100RB.
            Для получения сигнальной линии 1-Wire необходимо соединить выводы
            PA2 (USART2_TX) и PA3 (USART2_RX) и подключить их через резистор
            4.7 кОм к источнику питания микроконтроллера.

            Для проверки работоспособности, можно подключить цифровой датчик
            температуры DS18B20 (с внешним питанием, т.к. здесь не реализовано
            сильное подтягивание для обеспечения тока питания, достаточного для
            работы во всех режимах).
******************************************************************************
*/

#include "stm32f10x.h"

// Байты, отправляемые по UART для эмуляции различных сигналов 1-Wire.
#define OW_RESET            0xF0
#define OW_W0               0x00
#define OW_W1               0xFF
#define OW_R                0xFF

// Скорости UART для выполнения сброса 1-Wire и для передачи данных.
#define OW_RESET_SPEED      9600
#define OW_TRANSFER_SPEED   115200

// Значения, возвращаемые функцией ow_reset()
#define OW_NO_DEVICE        0
#define OW_OK               1

// Функция для включения системного таймера.
void init_sys_tick()
{
    // Устанавливаем максимальный модуль пересчёта.
    SysTick->LOAD=0x00FFFFFF;
    // Включаем таймер.
    SysTick->CTRL=SysTick_CTRL_CLKSOURCE|SysTick_CTRL_ENABLE;
}

// Функция для формирования задержки длительностью us микросекунд.
void udelay(int us)
{
    // Значение счётчика системного таймера в начальный момент.
    int t0=SysTick->VAL;

    // Пересчитываем заданный интервал в количество тактов системного таймера.
    int64_t n=SystemCoreClock;
    n*=us;
    n/=1000000;

    // Следим за количеством прошедших тактов.
    while(n>0)
    {
        int t=SysTick->VAL;
        int r=t0-t;
        if(r<=0)
            r+=SysTick->LOAD+1;
        n-=r;
        t0=t;
    }
}

// Функция устанавливает для USART2 скорость передачи speed (бит/с).
// Не следует вызывать, когда происходит передача/приём данных.
void usart2_change_speed(uint32_t speed)
{
    USART2->BRR=SystemCoreClock/speed;
}

// Функция отправляет байт ch через USART2;
// возврат из функции происходит после успешной записи байта в буфер
// передаваемых данных (если буфер не пуст, функция ждёт его освобождения).
void usart2_send_byte(uint8_t ch) {
    while(!(USART2->SR&USART_SR_TXE)) {}
        USART2 -> DR = ch;
}

// Функция возвращает байт, принятый USART2;
// если буфер принимаемых данных USART2 пуст, функция ждёт получения данных.
char usart2_receive_byte()
{
    while(!(USART2->SR&USART_SR_RXNE)) {}
    return USART2->DR;
}

// Начальная инициализация USART2 и используемых USART2 выводов
// (PA2 - USART2_TX; PA3 - USART2_RX).
void usart2_init()
{
    // Включаем тактирование USART2 и GPIOA; конфигурируем используемые
    // выводы GPIOA:
    // PA2 (USART2_TX) - альтернативный выход с открытым стоком;
    // PA3 (USART2_RX) - вход.
    RCC->APB1ENR|=RCC_APB1ENR_USART2EN;
    RCC->APB2ENR|=RCC_APB2ENR_IOPAEN;
    uint32_t tmp=GPIOA->CRL;
    tmp&=~0xFF00;
    // TX, PA2: out 10MHz, alt. open drain, 1101=0xD
    // RX, PA3: in, floating: 0100=0x4
    tmp|=0x4D00;
    GPIOA->CRL=tmp;

    // По умолчанию установить скорость USART, используемую для передачи
    // данных в 1-Wire линии.
    usart2_change_speed(OW_TRANSFER_SPEED);

    // Включаем USART; включаем передатчик и приёмник.
    // Требуется, чтобы к моменту включения приёмника на 1-Wire шине
    // подтягивающим резистором была установлена 1.
    USART2->CR1|=USART_CR1_UE;
    USART2->CR1|=USART_CR1_TE|USART_CR1_RE;

    // Читаем из буфера входящих данных для его очистки на случай,
    // если буфер был не пуст.
    USART2->DR;
}

// ***************************************************************************
// Функции для взаимодействия с шиной 1-Wire.
// ***************************************************************************

// Функция формирует сигнал сброса на шине 1-Wire;
// возвращает OW_OK, если получен сигнал присутствия от ведомых устройств;
// возвращает OW_NO_DEVICE, если ответный сигнал присутствия не получен.
int ow_reset()
{
    // Для формирования сигнала сброса требуется переключить
    // USART на скорость 9600.
    usart2_change_speed(OW_RESET_SPEED);
    // Формируем сигнал сброса отправкой через USART байта 0xF0.
    usart2_send_byte(OW_RESET);
    // Проверяем наличие сигнала присутствия: если полученный байт совпадает
    // с отправленным - сигнала присутствия нет;
    // если не совпадает - сигнал присутствия получен.
    char r=usart2_receive_byte();
    // Возвращаем скорость USART к значению по умолчанию.
    usart2_change_speed(OW_TRANSFER_SPEED);

    return (r==OW_RESET)?OW_NO_DEVICE:OW_OK;
}

// Функция отправляет бит ведомым устройствам 1-Wire (посылается младший бит
// аргумента b).
// Для получения бита от ведомого устройства, вызвать функцию с аргументом 1.
uint8_t ow_send_bit(uint8_t b)
{
    usart2_send_byte((b&1)?OW_W1:OW_W0);
    return (usart2_receive_byte()==OW_W1)?1:0;
}

// Функция отправляет байт b ведомым устройствам 1-Wire.
// Получение байта эквивалентно отправке байта 0xFF.
uint8_t ow_send(uint8_t b)
{
    int r=0;
    for(int i=0; i<8; i++, b>>=1)
    {
        r>>=1;
        if(ow_send_bit(b))
            r|=0x80;
    }
    return r;
}

// Отправить содержимое буфера buf размера size байт ведомым устройствам 1-Wire
void ow_send(const void *buf, unsigned int size)
{
    const uint8_t *p=(const uint8_t *)buf;
    for(unsigned int i=0; i<size; i++, p++)
        ow_send(*p);
}

// Получить байт от ведомого устройства 1-Wire;
// эквивалентно ow_send(0xFF)
inline uint8_t ow_receive()
{
    return ow_send(0xFF);
}

// Получить size байт от ведомого устройства и поместить их в буфер buf.
void ow_receive(void *buf, unsigned int size)
{
    uint8_t *p=(uint8_t *)buf;
    for(unsigned int i=0; i<size; i++, p++)
        *p=ow_send(0xFF);
}

// Основная функция после выполнения действий по инициализации приступает к
// циклическому измерению температуры с помощью датчика DS18B20;
// предполагается, что датчик - единственное устройство на шине 1-Wire;
// датчик получает внешнее питание.
int main(void)
{
    // Включаем системный таймер.
    init_sys_tick();

    // После включения питания DS18B20, необходимо сделать паузу, так как на
    // включение питания датчик может ответить сигналом присутствия.
    udelay(1000);    // Здесь 1мс.

    // Инициализация USART2, используемого для эмуляции 1-Wire и
    // конфигурирование выводов PA2 (TX), PA3 (RX), используемых USART2.
    usart2_init();

    // Приступаем к циклическому измерению температуры.
    while(true)
    {
        // Сброс/проверка наличия устройства.
        if(ow_reset()!=OW_OK)
            break;
        // Посылаем команды для измерения температуры:
        // SKIP ROM (0xCC), CONVERT T (0x44).
        ow_send("\xCC\x44", 2);

        // Ждём завершения преобразования в ADC;
        // для определения момента завершения можем воспользоваться тем, что
        // при внешнем питании во время преобразование при чтении бита
        // возвращается 0, после завершения преобразования считывается 1.
        while(ow_send_bit(1)==0) {udelay(50000);}

        // Выполняем сброс и посылаем команды
        // SKIP ROM (0xCC), READ SCRATCHPAD (0xBE)
        // для считывания результата измерения.
        ow_reset();
        ow_send("\xCC\xBE", 2);

        // Читаем два байта, получим целое со знаком - температуру в
        // градусах по Цельсию, умноженную на 16.
        // При желании можно прочитать всё содержимое SCRATCHPAD для
        // проверки CRC.
        int16_t tx16=0;
        ow_receive(&tx16, 2);

        // Используем полученное значение, при желании можем преобразовать в
        // вещественный формат.
        // float t=tx16/16.0;
        float t=tx16; t/=16;
    }

    /* Infinite loop */
    int i = 0;
    while (1)
    {
        i++;
    }
}

Можно немного усовершенствовать данный код. Например, ввести простейшие проверки на исправность шины. В случае 1-Wire весьма вероятно, что шина находится за пределами устройства и в этом случае мы не должны доверять ей. Не исключено, например, возникновение короткого замыкания на шине (изоляцию могли погрызть мыши или повредить посторонние предметы). Короткое замыкание легко выявить, если перед формированием сигнала сброса, проверить наличие сигнала высокого уровня на шине. Либо можно контролировать установку флага ошибки фрейма в UART - так удастся обнаружить не только короткое замыкание, но и другие проблемы с прохождением сигнала и не только при сбросе, но и при любой пересылке данных. Полезно будет добавить в функцию чтения UART завершение с ошибкой по тайм-ауту, тем самым обезопасив себя от возможности зависания программы в тех случаях, когда по каким-то причинам UART не может обнаружить входящий фрейм.

Здесь перечисленная функциональность отсутствует, чтобы не загромождать частными деталями код, приведённый исключительно в целях ознакомления с основной идеей метода.

author: hamper; date: 2020-04-18
  Рейтинг@Mail.ru