PureBasic - форум

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.



STM32 - 1Wire

Сообщений 1 страница 2 из 2

1

Получил заказанные из Китая STM32F030F4P6 и начал их тестировать. :)  Т. к. мигать светодиодом уже неинтересно, решил сделать что-то более полезное, а именно, считывание температуры с датчика DS18B20. :D

Схема получилась такой.

http://s1.uploads.ru/sZgue.gif

Глядя на нее может сложится впечатление что протокол 1Wire реализован программным методом, ведь аппаратного 1Wire модуля в этом МК нет, а для USART нужно два вывода, и диод на TXD. Но это не так. Используется USART, которому в полудуплексном режиме достаточно одного вывода для приема и передачи, а чтобы не было электрического конфликта, вывод работает в режиме открытого стока.

Основной код программы.

Код:
#include "stm32f0xx_conf.h"
#include "onewire.h"

volatile float tt;

int main(void)
{
    uint8_t buf[9];

    GPIO_InitTypeDef GPIO_InitStruct;

    SystemCoreClockUpdate(); // Вычисление тактовой частоты ядра.
    SysTim_Init(100); // Инициализация системного таймера.

    OW_Init(); // Инициализация 1Wire библиотеки.

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); // Включаем тактирование порта PB.

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;      // Настройка вывода PB.1.
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;  // Вывод работает как выход.
    GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; // Двухтактный выход (т. е. не открытый сток).
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; // Подтягивающие резисторы отключены.
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init(GPIOB, &GPIO_InitStruct);

    while(1)
    {
        // Запуск измерения температуры.
        if (OW_Send(OW_SEND_RESET, "\xCC\x44", 2, 0, 0, OW_NO_READ) == OW_OK)
        {
            SysTim_DelayMS(800); // Ждем 800 миллисекунд.

            // Читаем данные с датчика
            if (OW_Send(OW_SEND_RESET, "\xCC\xBE\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 11, &buf[0], 9, 2) == OW_OK)
            {

                if (OW_CRC8(&buf[0], 8) == buf[8])
                {

                    tt = ((float)((buf[1]<<8)|buf[0]))/16.0; // Вычисляем температуру.

                    GPIO_SetBits(GPIOB, GPIO_Pin_1); // Лог. 1 на выходе PB.1.
                    SysTim_DelayMS(100); // Ждем 100 миллисекунд.
                    GPIO_ResetBits(GPIOB, GPIO_Pin_1); // Лог. 0 на выходе PB.1.
                }
            }
        }

    }
}

Вначале функции main создается массив buf под данные, получаемые из датчика при чтении температуры и экземпляр структуры GPIO_InitTypeDef, необходимый для настройки вывода порта под светодиод, который мигает при успешном получении данных из датчика. Затем вызывается несколько функций инициализации и конфигурации, а после находится цикл while, код которого МК выполняет большую часть времени. В нем, вызов функции OW_Send со вторым аргументом "\xCC\x44" запускает измерение температуры. Далее, после ожидания 800 миллисекунд, необходимых для измерения температуры датчиком, еще раз вызывается функция OW_Send, но на этот раз с командой чтения температуры. Множество xFF это байты отправляемые через USART и необходимые для чтения. Если процесс чтения прошел без ошибок то в массиве buf окажется 9 байт прочитанных с датчика и если контрольная сумма (CRC8) совпадет, то вычисляется температура и записывается в переменную tt, после чего на 100 миллисекунд на выводе PB1 устанавливается логическая единица для мигания светодиода, информирующего что процесс измерения температуры прошел удачно. А затем все начинается сначала (запуск измерения температуры, ее считывание и т. д.).

1Wire-функции находятся в файле onewire.c. Сам я его с нуля не писал, а взял готовый (файлы здесь).
Там версия для STM32F1, а у меня STM32F0 и без доработки не заработало. :writing:  Как оказалось, несмотря на то что это более младшая и более дешевая модель, периферия у нее с большим функционалом. Например у USART появился выбор источника тактирования (внутренний RC генератор, выход умножителя частоты или тактирование от ядра МК), чего нет в STM32F1 (по крайней мере в STM32F103C8T6).Были и другие нюансы такие как отличающиеся выводы порта на который выведен USART, другие номера каналов DMA и т. д.
Теперь по порядку. Функция инициализации выглядит так.

Код:
//-----------------------------------------------------------------------------
// инициализирует USART и DMA
//-----------------------------------------------------------------------------
uint8_t OW_Init()
{
    GPIO_InitTypeDef GPIO_InitStruct;
    USART_InitTypeDef USART_InitStructure;

    RCC_USARTCLKConfig(RCC_USART1CLK_SYSCLK);  // USART тактируется от ядра МК.

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);    // Подаем тактирование на GPIOA,
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // USART1,
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);     // и DMA1.

    // USART TX
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2;        // Настройка вывода PA.2.
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;     // Альтернативная функция вывода.
    GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;   // Выход с открытым стоком.
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; // Подтягивающие резисторы отключены.
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_1); // GPIOA.2 это вывод USART1.

    // Настройка USART.
    USART_InitStructure.USART_BaudRate = 115200;                // Скорость обмена.
    USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 8 бит данных.
    USART_InitStructure.USART_StopBits = USART_StopBits_1;      // 1 стоп бит.
    USART_InitStructure.USART_Parity = USART_Parity_No;         // Нет проверки четности.
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // Настраиваем приемник и передатчик.

    USART_Init(USART1, &USART_InitStructure);

    USART_SetAddress(USART1, 0);          // Адрес. Без этого не работает полудуплексный режим.
    USART_HalfDuplexCmd(USART1 , ENABLE); // Разрешение работы USART в полудуплексном режиме.
    USART_Cmd(USART1, ENABLE);            // Включаем USART.

    return OW_OK;
}

В ней выбирается источник тактирования, USART, подается тактирование на модули GPIOA, USART1 и DMA1, а затем настраивается порт PA2 как вывод USART, работающий в режиме с открытым стоком. После настраивается USART1, у которого устанавливается скорость обмена 115200 бод, 8 бит данный, 1 стоп-бит, отключается проверка четности и аппаратного управления потоком. Затем устанавливается адрес, включается полудуплексный режим и запускается USART1. Зачем в данном случае нужно устанавливать адрес, не знаю, т. к. это относится к многопроцессорному обмену и в данном случае не используется, но без этого полудуплексный режим не работает.

Функция приема/передачи 1Wire выглядит таким образом.

Код:
//-----------------------------------------------------------------------------
// процедура общения с шиной 1-wire
// sendReset - посылать RESET в начале общения.
//     OW_SEND_RESET или OW_NO_RESET
// command - массив байт, отсылаемых в шину. Если нужно чтение - отправляем OW_READ_SLOTH
// cLen - длина буфера команд, столько байт отошлется в шину
// data - если требуется чтение, то ссылка на буфер для чтения
// dLen - длина буфера для чтения. Прочитается не более этой длины
// readStart - с какого символа передачи начинать чтение (нумеруются с 0)
//    можно указать OW_NO_READ, тогда можно не задавать data и dLen
//-----------------------------------------------------------------------------
uint8_t OW_Send(uint8_t sendReset, uint8_t *command, uint8_t cLen,
                uint8_t *data, uint8_t dLen, uint8_t readStart)
{
    uint32_t times;
    uint8_t ret;
    // если требуется сброс - сбрасываем и проверяем на наличие устройств
    if (sendReset == OW_SEND_RESET)
    {
        ret = OW_Reset();
        if (ret != OW_OK)
        {
            return ret;
        }
    }

    while (cLen > 0)
    {

        OW_toBits(*command, ow_buf);
        command++;
        cLen--;

        DMA_InitTypeDef DMA_InitStructure;

        // DMA на чтение
        DMA_DeInit(OW_DMA_CH_RX);
        DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &(USART1->RDR); // Источник - регистр данных приема USART.
        DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) &ow_buf[0];         // Приемник - массив в ОЗУ.
        DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // Копирование данных из периферии в память.
        DMA_InitStructure.DMA_BufferSize = 8; // Размер буфера куда копировать данные.
        DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // Не увеличивать адрес периферии.
        DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;          // Увеличивать адрес памяти (в массив копируем же).
        DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // Размер данных периферии 1 байт.
        DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;  // Размер памяти 1 байт.
        DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;      // Не циклический режим работы.
        DMA_InitStructure.DMA_Priority = DMA_Priority_Low; // Низкий приоритет.
        DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
        DMA_Init(OW_DMA_CH_RX, &DMA_InitStructure);

        // DMA на запись
        DMA_DeInit(OW_DMA_CH_TX);
        DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &(USART1->TDR); // Источник - регистр данных передачи USART.
        DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // Копирование данных из памяти в периферию.
        DMA_Init(OW_DMA_CH_TX, &DMA_InitStructure);

        // старт цикла отправки
        USART_ClearFlag(USART1, USART_FLAG_RXNE | USART_FLAG_TC | USART_FLAG_TXE); // Очистка флагов USATR

        USART_DMACmd(USART1, USART_DMAReq_Tx | USART_DMAReq_Rx, ENABLE); // Включаем DMA в USART.
        DMA_Cmd(OW_DMA_CH_RX, ENABLE); // Старт DMA.
        DMA_Cmd(OW_DMA_CH_TX, ENABLE);

        times = SysTim_GetTick(); // Текущее время с начала работы МК с миллисекундах.

        // Ждем, пока не примем 8 байт
        while (DMA_GetFlagStatus(OW_DMA_FLAG) == RESET)
        {

            if (SysTim_GetTick()-times > OW_TimeOut) // Превышен таймаут.
            {
                // отключаем DMA
                DMA_Cmd(OW_DMA_CH_TX, DISABLE);
                DMA_Cmd(OW_DMA_CH_RX, DISABLE);
                USART_DMACmd(USART1, USART_DMAReq_Tx | USART_DMAReq_Rx, DISABLE);
                return OW_Err_TimeOut;
            }

#ifdef OW_GIVE_TICK_RTOS
            taskYIELD();
#endif
        }

        // отключаем DMA
        DMA_Cmd(OW_DMA_CH_TX, DISABLE);
        DMA_Cmd(OW_DMA_CH_RX, DISABLE);
        USART_DMACmd(USART1, USART_DMAReq_Tx | USART_DMAReq_Rx, DISABLE);

        // если прочитанные данные кому-то нужны - выкинем их в буфер
        if (readStart == 0 && dLen > 0)
        {
            *data = OW_toByte(ow_buf);
            data++;
            dLen--;
        }
        else
        {
            if (readStart != OW_NO_READ)
            {
                readStart--;
            }
        }
    }

    return OW_OK;
}

В ней при необходимости вызывается функция OW_Reset() выполняющая сброс датчика, а затем в цикле передаются и принимаются данные. Для передачи одного байта по 1Wire нужно отправить 8 байт через USART. Прием и передача этих 8 байт осуществляется посредством модуля DMA (по русски это ПДП - прямой доступ к памяти, в данном случае, копирование данных из периферии в память и обратно без участия процессора).

Итак, давайте протестируем работу устройства. Для этого нужно скачать файлы и открыть проект (файл с расширением ebp) в программе EmBitz.
Для наблюдения температуры понадобится отладчик ST-Link или подобный, т. к. другого метода это сделать в данном коде не предусмотрено.
Подключаем отладчик в устройству и компьютеру и запускаем в программе EmBitz процесс отладки кликнув в меню "Отладка" по пункту "Start/Stop Dedug Session" или по аналогичному значку на панели инструментов.

http://s8.uploads.ru/ODb3B.png

Программа будет остановлена отладчиком. Ее нужно запустить кликнув в меню "Отладка" по пункту "Запустить" или нажав F5 на клавиатуре. При этом должен начать мигать светодиод примерно раз в секунду.

Если при этом не будет открыто окно "Watches", то открываем его из меню "Отладка" --> "Окна отладчика" --> "Наблюдать".

http://s2.uploads.ru/t/pYO2U.png

После находим к файле main.c переменную tt и кликаем по ней правой кнопкой мышки и в меню выбираем "Add wath 'tt'". В окне "Добавить наблюдение" ничего не меняем и нажимаем на ОК. Переменная добавится в окно "Watches". Чтобы ее значение регулярно обновлялось кликаем в окне "Watches" по переменной и в меню выбираем "Live updates".

http://sg.uploads.ru/hJB1U.png

Теперь греем и охлаждаем датчик и в реальном времени наблюдаем как меняется температура. :)

0

2

Подключил экранчик от Nokia 5110. http://pure-basic.narod.ru/forum_files/ … P6_SPL.zip

http://s1.uploads.ru/hW3wx.jpg

0