STM Урок 101. HC-05. Передаём температуру



Продолжаем работать с модулем HC-05, который обеспечивает передачу данных без проводов по Bluetooth.

Мы уже научились с помощью двух таких модулей передавать данные с одного контроллера на другой, а сегодня мы попробуем передать уже более осознанные данные. У нас уже стало традицией передавать температуру с датчика DS18B20 и мы пока не будем её нарушать. Да и библиотека у нас писалась именно под такую же плату, какая используется у нас на ведомом устройстве. Но так как эта задача, я думаю, особого труда нам не составит, то тогда, чтобы урок не был слишком коротким и простым, у меня родилась ещё одна задумка. Мы попробуем на ведущем устройстве время от времени узнавать статус нашего подключения. Может это и не сильно важная для нас информация — статус соединения, но зато мы научимся посылать команды и получать на них ответы, а также обрабатывать затем эти ответы, именно не из терминальной программы, как в прежних занятиях, а именно из контроллера. Использовать при этом мы конечно будем накопленные нами за 100 предыдущих уроков знания по работе с USART-портом, с прерываниями, с приоритетами, со строками и т.д.

Поэтому давайте не будем себя томить ожиданием и приступим наконец к делу.

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

Откроем наш проект в Cube MX и сначала конечно же включим ножку, к которой мы присоединим наш датчик температуры. Мы помним из урока по датчику, что нам не важно направление, нужно, чтобы была хотя бы какая-то инициализация, да и то, что вывод будет задействован, мы будем видеть и не забудем о нём впоследствии. Включим, например, ножку PB11

 

 

Зайдём в Configuration, откроем раздел NVIC и распределим приоритеты прерываниям от USART и таймера, они так лучше между собой «договорятся»

 

 

Сгенерируем наш проект, откроем его в Keil, настроим программатор на автоперезагрузку, а также включим уровень оптимизации 1 и попробуем его собрать.

Если всё собралось без ошибок, то скопируем файлы из проекта урока 94 по датчику температуры ds18b20.h и ds18b20.c в соответствующие папки нашего нового проекта.

Подключим к дереву проектов файл ds18b20.c и также подключим нашу библиотеку в файле main.h

 

/* USER CODE BEGIN Includes */

#include "ds18b20.h"

/* USER CODE END Includes */

 

Также для работы с датчиком мы добавим ряд глобальных переменных и массивов в файле main.c

 

  "String5\r\n"

};

uint8_t Dev_ID[8][8]={0};

uint8_t Dev_Cnt;

uint8_t dt[8];

uint16_t raw_temper;

float temper;

char c;

 

Теперь у нас проект хотя бы соберётся.

Инициализируем наш датчик в main()

 

/* USER CODE BEGIN 2 */

port_init();

ds18b20_init(SKIP_ROM);

HAL_UART_Receive_IT(&huart2,(uint8_t*)str1,1);

 

Перейдём в файл ds18b20.c и исправим там код функции ds18b20_Convert, убрав оттуда всё лишнее. Иначе у нас отрицательная температура показывается неправильно. Просто у меня не было возможности проверить, а сейчас, когда на улице морозы, такая возможность появилась. Я просто подключил датчик, который на проводе к макетной плате и выкинул его в окно, закрыв окно на проводе, в окне есть резинка, поэтому с проводом ничего не случится. Код в функции будет теперь вот такой

 

float ds18b20_Convert(int16_t dt)

{

  float t;

  t = (float)dt / 16.0f;

  return t;

}

 

То есть мы теперь рассматриваем показания в регистре как знаковый результат, в 16 раз больший, чем на самом деле и всё.

В прототипе также исправим тип входного аргумента.

Идём в main.c В процедуре обработки прерываний от таймера также код изменится

 

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)

{

  if(htim==&htim2)

  {

    HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);

    i++;

    if(i>10)

    {

      if(i%4==0)

      {

        ds18b20_MeasureTemperCmd(SKIP_ROM, 0);

      }

      else if(i%4==2)

      {

        ds18b20_ReadStratcpad(SKIP_ROM, dt, 0);

        raw_temper = ((int16_t)dt[1]<<8)|dt[0];

        temper = ds18b20_Convert(raw_temper);

        if(temper>0) sprintf(str1,"+%.2f \r\n", temper);

        else sprintf(str1,"%.2f \r\n", temper);

        HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

      }

      if(i==252) i=12;

    }

  }

}

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

Схема наша на практике не изменилась, только единственное, мы подключили к ней датчик температуры, который давно уже к нашей плате привык. Пока подключим обычный незащищённый датчик для удобства работы, а если нам надо будет померить например отрицательную температуру за окном и передать её по воздуху в комнату, то возможно, следует подключить датчик на проводе и в металлическом экране. Такой у меня тоже есть если что. Вот наша схема

 

 

Подключим программатор данной схемы к USB-порту компьютера, соберем и прошьём наш проект. Также подключим схему с ведущим модулем и дисплеем, запитав её в свою очередь от независимого источника (блока питания с USB-выходом либо от POWER-банка. Теперь на дисплее должна будет отображаться приходящая примерно раз в 2 секунды температура с ведомого устройства. Можно проверить, что она обновляется, прикасаясь к датчику температуры. Температура при этом будет увеличиваться

 

 

Всё прекрасно работает. Можно теперь наоборот подключить ST-Link ведомого устройства к независимому исочнику, а плату с ведущим модулем — к USB-порту компьютера и заняться реализацией второй затеи по мониторингу статуса соединению, ну и ещё некоторых эстетических задумок.

Проект будет создан на основе проекта урока 99 для ведущего устройства и назван HC_05_MASTER_AT.

 

 

Откроем наш проект в Cube MX и сначала включим наш любимый таймер 2

 

 

Также включим на выход ножку PA12, к которой мы подключим ножку KEY модуля HC-05 для управления режимом приёма и отправки команд AT

 

 

Теперь перейдём в Configuration и для начала настроим таймер

 

 

Включим прерывания

 

 

Немного добавим скорость для ножки порта

 

 

Затем настроим приоритеты прерываний таймера и порта USART по анологии с ведомым устройством

 

 

Сгенерируем проект, откроем его в Keil, настроим программатор на автоперезагрузку, установим уровень оптимизации в 1 и подключим файл lcd.c к проекту. Попробуем собрать проект. Если проект нормально собрался, то начнем сочинять код.

Первым делом удалим вывод тестовых строк из функции main()

 

sprintf(str1,"String 1");

LCD_StrBottom(str1);

sprintf(str1,"String 2");

LCD_StrBottom(str1);

sprintf(str1,"String 3");

LCD_StrBottom(str1);

sprintf(str1,"String 4");

LCD_StrBottom(str1);

 

 

Перейдём в файл lcd.c и добавим ещё одну функцию, которая будет выводить строку не на определённое место в строке, а просто на определённую строку, также она будет забивать пробелом оставшиеся символы в строке, чтобы стирать информацию от той строки, которая была до этого. Данную функцию мы добавим над функцией LCD_StrBottom

 

//------------------------------------------------

void LCD_StringAtLine(uint8_t y, char* st)

{

  uint8_t i=0, str_cnt=0;

  LCD_SetPos(0,y);

  while((st[str_cnt]!=0)&&(st[str_cnt]!=0x0D)&&(st[str_cnt]!=0x0A))

  {

    sendbyte(st[str_cnt],1);

    str_cnt++;

  }

  //остальное - пробелы

  for(i=str_cnt;i<20;i++)

  {

    sendbyte((uint8_t)' ',1);

  }

}

//------------------------------------------------

 

Вообщем-то, код здесь простой и в обяснении, думаю, не нуждается.

Создадим для данной функции прототип в хедер-файле и вернёмся в файл main.c.

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

 

uint8_t usart_buf[40];

 

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

 

USART_prop_ptr usartprop;

enum bt_states

{

  BT_NONE = 0,

  BT_INITIALIZED,

  BT_READY,

  BT_PAIRABLE,

  BT_PAIRED,

  BT_INQUIRING,

  BT_CONNECTING,

  BT_CONNECTED,

  BT_DISCONNECTED

};

__IO uint8_t bt_state = BT_NONE;

char str_state[14]="NONE";

volatile uint32_t Tim2Cnt=0;

 

Запустим таймер в main()

 

HAL_UART_Receive_IT(&huart1,(uint8_t*)str1,1);

HAL_TIM_Base_Start_IT(&htim2);

 

Добавим обработчик событий от таймера

 

    UART1_RxCpltCallback();

  }

}

//-----------------------------------------------

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)

{

  if(htim==&htim2)

  {

    if(Tim2Cnt>9)

    {

      if(Tim2Cnt%10==0)

      {

        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_SET);

      }

      if(Tim2Cnt%10==1)

      {

        HAL_UART_Transmit(&huart1,(uint8_t*)"AT+STATE?\r\n",11,0x1000);

      }

      if(Tim2Cnt%10==3)

      {

        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_RESET);

      }

    }

    Tim2Cnt++;

  }

}

//-----------------------------------------------

/* USER CODE END 4 */

 

Код здесь хоть и кажется сложным на первый взгляд, но на самом деле здесь всё просто. Отсчитаем десять тиков, так сказать дадим модулям договориться, затем каждые десять тиков (или 5 секунд приблизительно) мы подаём плюсовой потенциал на ножку KEY, тем самым заставляя модуль принимать команды. Когда модули соединены, недостаточно просто подать потенциал и тут же его убрать. Чтобы модуль реагировал на команды, необходимо в это время держать уровень питания на данной ножке. А уже данные, приходящие в модуль, находящийся в таком состоянии, в порт поступать не будут, но будут сохраняться в буфере, и когда мы закончим работу с командами и установим уровень 0 на ножке KEY, они все как правило из буфера благополучно поступят в порт, так что бояться потери пришедших данных по воздуху не стоит. Затем мы ждём полсекунды и отправляем команду запроса статуса, ответ на которую уже поступит в обработчик от USART, мы даём на это целую секунду и уже по прошествии этой секунды уберём положительный уровень с ножки KEY.

Теперь давайте обработаем ответ.

Для этого мы немного сначала переделаем код в функции UART1_RxCpltCallback. Ну, вернее, не для этого а в связи с тем, что у нас увеличился буфер и чтобы нам его весь принять, но в строку LCD потом весь не отправлять. Первым делом увеличим максимальную длину буфера

 

if (usartprop.usart_cnt>39)

 

И чтобы лишнего не выводилось на дисплей, по окончанию приёма, если получилось больше чем надо, вставим ноль в соответствующее место, обозначив тем самым окончание строки

 

string_parse((char*)usartprop.usart_buf);

if (usartprop.usart_cnt>19)//у нас строка на дисплее 20 символов

{

  usartprop.usart_buf[20] = 0;

}

usartprop.usart_cnt=0;

 

Ну и, соответственно функция разбора строки у нас увеличилась серьёзно

 

void string_parse(char* buf_str)

{

  if(!strncmp(buf_str,"+ST",3))

  {

    if(!strncmp(buf_str+7,"CONNECTE",8))

    {

      bt_state=BT_CONNECTED;

      strcpy(str_state,"CONNECTED");

      HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_SET);

    }

    else

    {

      if(!strncmp(buf_str+7,"INI",3))

      {

        bt_state=BT_INITIALIZED;

        strcpy(str_state,"INITIALIZED");

      }

      else if(!strncmp(buf_str+7,"RE",2))

      {

        bt_state=BT_READY;

        strcpy(str_state,"READY");

      }

      else if(!strncmp(buf_str+7,"PAIRA",5))

      {

        bt_state=BT_PAIRABLE;

        strcpy(str_state,"PAIRABLE");

      }

      else if(!strncmp(buf_str+7,"PAIRE",5))

      {

        bt_state=BT_PAIRED;

        strcpy(str_state,"PAIRED");

      }

      else if(!strncmp(buf_str+7,"INQ",3))

      {

        bt_state=BT_INQUIRING;

        strcpy(str_state,"INQUIRING");

      }

      else if(!strncmp(buf_str+7,"CONNECTI",8))

      {

        bt_state=BT_CONNECTING;

        strcpy(str_state,"CONNECTING");

      }

      else if(!strncmp(buf_str+7,"DI",2))

      {

        bt_state=BT_DISCONNECTED;

        strcpy(str_state,"DISCONNECTED");

      }

      else

      {

        bt_state=BT_NONE;

        strcpy(str_state,"NONE");

      }

      HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_RESET);

    }

  }

  else if(buf_str[0]!='O')

  {

    LCD_StringAtLine(3,buf_str);

    LCD_StringAtLine(0,str_state);

  }

  else

  {

  }

}

 

Мы сначала узнали, что это была не обычная какая-то строка. а ответ на запрос статуса, и затем в теле этого условия мы уже парсим строку, начиная с того символа, где идёт именно информация о статусе и берём из этой строки необходимое количество символов и сравниваем их с образцовыми, и если совпало с каким-то статусом, то мы уже присваиваем такой же статус переменной и заранее прописываем в строковый буфер строку со статусом, которую мы отобразим на дисплее тогда, когда будем отображать пришедшую строку с показаниями датчика температуры. Если мы будем строку со статусом отображать сразу, то мы рискуем не успеть обработать следующую строку. Можете проверить, будут глюки. Если это не ответ со статусом, то проверяем дальше. Смотрим чтобы не было символа 'O', чтобы не показывать строку с ответом OK, так как она тоже приходит, и если это не она, то мы просто выводим в нижнюю строку показания температуры, а в верхнюю — строку статуса.

Соберём код, прошьём кристалл и посмотрим результат нашей работы. При нормальном состоянии соединения должно быть вот так

 

 

 

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

Также покажу как наш термометр измеряет отрицательную температуру

 

 

Вот так теперь выглядит весь наш приёмо-передающий комплекс (нажмите на картинку для увеличения изображения)

 

 

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

Благодарю всех за внимание!

 

 

Предыдущий урок Программирование МК STM32 Следующий урок

 

 

Модуль bluetooth HC-05 можно купить здесь bluetooth HC-05

Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6

Отладочную плату NUCLEO-F303K8 можно купить здесь NUCLEO-F303K8

Программатор недорогой можно купить здесь ST-Link V2

Датчик температуры в экране с проводом можно приобрести здесь DS18B20 в экране с проводом

 

 

Исходный код SLAVE

Исходный код MASTER

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM HC-05. Передаём температуру

2 комментария на “STM Урок 101. HC-05. Передаём температуру
  1. Дмитрий:

    Здравствуйте! Благодарю за столь полезные и понятные уроки. Решаю несколько другую задачу, нежели работа с Блю-тус, но похожую. Есть небольшая загвоздка:
    Отправляю с МК АТ-команду «АТ», но никак не могу в парсере найти ответ «ОК». Вот мой код функции парсера:

    1. void string_parse(char* buf_str) {
    2. if (!strncmp(buf_str, «AT» , 2)) {
    3. if (!strncmp(buf_str + 8, «OK» , 2))
    4. HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1); } }

    В третьей строке (в части buf_str+8) перебрал сложение с buf_str от 1 до 8. При этом строку (buf_str, «AT», 2) парсер находит без проблем.

    Не могли бы вы подсказать мне что исправить? Заранее спасибо.

  2. Геннадий:

    Отрицательная температура все равно не получается по вашим исходникам
    FF5E должно быть -10,125
    получается
    -4085,875

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

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

*