субота, 11 травня 2019 р.

STM32: Бібліотека OneWire і Dallas Temperature HAL по UART з DMA

update 04.01.2021: повністю оновлена стаття, переписана бібліотека, приклади, додано відео.

update 10.12.2020: завдяки коментаторам додав корективи. Зазначив, що для UART на якому будуть сенсори, крім DMA, потрібно ще увімкнути глобальні переривання "USART global interrupt". Та в файлі OneWire.c поміняв місцями функції:
HAL_UART_Receive_DMA(&HUARTx, ow_buf, num_bits);
HAL_UART_Transmit_DMA(&HUARTx, ow_buf, num_bits);

Первіша має бути функція приймання. Сподіваюсь це допоможе. Бо в мене і так і так все нормально працювало.

Передмова

Макет

Бібліотека OneWire створена за мотивами статей "Stm32 + 1-wire + DMA (продолжение)" та "STM32 + 1-wire. Поиск устройств". А бібліотека Dallas Temperature клон бібліотеки "DallasTemperature" для Arduino. За винятком: не працює з "Alarm" - не розібрався, не реалізована робота з паразитним живленням датчиків, та без перевірки на всі нулі функцією "IsAllZeros". У файлі "dallastemperature.c" в функції DT_IsConnected_ScratchPad закоментована перевірка "IsAllZeros". В іншому все працює коректно.

Створюємо проект в CubeMX або CubeIDE

Запускаємо CubeIDE, обираємо свій мікроконтролер, або дошку. Даємо назву проекту, та налаштовуємо проект:
Налаштування проекту
  1. RCC - тактуємось від зовнішнього кварцу "Crystal/Ceramic resonator";
  2. SYS - відладка по "Serial Wire";
  3. USART1 або USART3 - обираємо режим "Single Wire (Half-Duplex)" та додаємо канали DMA як для прийому так і для передачі, а також вмикаємо глобальні переривання "USART global interrupt";
  4. USART2 - за замовчуванням, для передачі даних температури на послідовний порт комп'ютера.
Генеруємо проект, та додаємо файли бібліотеки до проекту. OneWire.h і DallasTemperature.h до теки "inc" проекту, а файли OneWire.с і DallasTemperature.с до теки "src" проекту.

В файлі DallasTemperature.h за замовчуванням, на лінії 1-Wire йде пошук, максимум 5 сенсорів. Змінити можна в рядку:

#define ONEWIRE_MAX_DEVICES	5

Схема підключення

Схема підключення стандартна:
Схема підключення

Живлення, земля, дата + підтяжка до живлення через резистор 4.7 - 5.6 кОм. Ніжка "DQ" до USART1 (PA9) або USART3 (PC10) .

Демо-код

Відкриваємо файл проекту main.c та додаємо заголовні файли бібліотек до коду:
/* USER CODE BEGIN Includes */
#include "OneWire.h"
#include "DallasTemperature.h"
/* USER CODE END Includes */

Додаємо функцію відправки тексту по USART2 та функцію друкування адреси сенсорів:
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int _write(int file, char *ptr, int len)
{
 HAL_UART_Transmit(&huart2, (uint8_t *) ptr, len, HAL_MAX_DELAY);
 return len;
}

// function to print a device address
void printAddress(CurrentDeviceAddress deviceAddress)
{
  for (uint8_t i = 0; i < 8; i++)
  {
	  printf("0x%02X ", deviceAddress[i]);
  }
}
/* USER CODE END 0 */

Приклад розрахований для двох DS18B20 на одній лінії USART3. Як у вас один сенсор, видаліть код для другого сенсора з індексом 1. Та якщо у вас лінія 1-wire не на USART3, то зробіть відповідні зміни в демо-коді.

Оголошуємо структури для OneWire та для DallasTemperature:
/* USER CODE BEGIN PV */
OneWire_HandleTypeDef ow;
DallasTemperature_HandleTypeDef dt;
/* USER CODE END PV */

В секцію 2 додаємо перевірку чи на лінії є якісь сенсори 1-Wire, взнаємо кількість підключених до лінії сенсорів, задаємо роздільну здатність сенсорів, та прочитаємо їхню адресу і поточну роздільну здатність:
/* USER CODE BEGIN 2 */
  printf("[%8lu] Debug UART2 is OK!\r\n", HAL_GetTick());

  OW_Begin(&ow, &huart3);

  if(OW_Reset(&ow) == OW_OK)
  {
	  printf("[%8lu] OneWire devices are present :)\r\n", HAL_GetTick());
  }
  else
  {
	  printf("[%8lu] OneWire no devices :(\r\n", HAL_GetTick());
  }

  DT_SetOneWire(&dt, &ow);

  // arrays to hold device address
  CurrentDeviceAddress insideThermometer;

  // locate devices on the bus
  printf("[%8lu] Locating devices...", HAL_GetTick());

  DT_Begin(&dt);

  printf("[%8lu] Found %d devices.\r\n", HAL_GetTick(), DT_GetDeviceCount(&dt));

  if (!DT_GetAddress(&dt, insideThermometer, 0))
	  printf("[%8lu] Unable to find address for Device 0\r\n", HAL_GetTick());

  printf("[%8lu] Device 0 Address: ", HAL_GetTick());
  printAddress(insideThermometer);
  printf("\r\n");

  // set the resolution to 12 bit (Each Dallas/Maxim device is capable of several different resolutions)
  DT_SetResolution(&dt, insideThermometer, 12, true);

  printf("[%8lu] Device 0 Resolution: %d\r\n", HAL_GetTick(), DT_GetResolution(&dt, insideThermometer));

  if (!DT_GetAddress(&dt, insideThermometer, 1))
	  printf("[%8lu] Unable to find address for Device 1\r\n", HAL_GetTick());

  printf("[%8lu] Device 1 Address: ", HAL_GetTick());
  printAddress(insideThermometer);
  printf("\r\n");

  // set the resolution to 12 bit (Each Dallas/Maxim device is capable of several different resolutions)
  DT_SetResolution(&dt, insideThermometer, 12, true);

  printf("[%8lu] Device 1 Resolution: %d\r\n", HAL_GetTick(), DT_GetResolution(&dt, insideThermometer));
  /* USER CODE END 2 */

Приклад розрахований для двох DS18B20 на одній лінії. Як у вас один сенсор, видаліть код для другого сенсора з індексом 1.

А в безкінечному циклі раз на 2 секунди будемо опитувати сенсори і виводити в послідовний порт отриману температуру:

/* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	// call DT_RequestTemperatures(&dt) to issue a global temperature
	// request to all devices on the bus
	printf("[%8lu] Requesting temperatures...", HAL_GetTick());
	DT_RequestTemperatures(&dt); // Send the command to get temperatures
	printf("\r\n[%8lu] DONE\r\n", HAL_GetTick());
	// After we got the temperatures, we can print them here.
	// We use the function ByIndex, and as an example get the temperature from the first sensor only.
	printf("[%8lu] Temperature for the device 1 (index 0) is: %.2f\r\n", HAL_GetTick(), DT_GetTempCByIndex(&dt, 0));
	printf("[%8lu] Temperature for the device 2 (index 1) is: %.2f\r\n", HAL_GetTick(), DT_GetTempCByIndex(&dt, 1));
	HAL_Delay(2000);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

Щоб функція "printf" могла виводити значення типу "float" потрібно відповідно налаштувати проект в CubeIDE, а саме додати ключ "-u _printf_float":

Налаштування друку float

І для Atolic TrueStudio обрати "NewLib standart":

Налаштування для друку float

Компілюємо, заливаємо. Запускаємо будь-яку термінальну програму і спостерігаємо:

Приклад роботи двох датчиків на лінії

Відео-посібник

Текст коду з відео:

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "OneWire.h"
#include "DallasTemperature.h"
/* USER CODE END Includes */

/* USER CODE BEGIN PV */
OneWire_HandleTypeDef ow1;
DallasTemperature_HandleTypeDef dt1;
OneWire_HandleTypeDef ow2;
DallasTemperature_HandleTypeDef dt2;
/* USER CODE END PV */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int _write(int file, char *ptr, int len)
{
	HAL_UART_Transmit(&huart2, (uint8_t *) ptr, len, HAL_MAX_DELAY);
	return len;
}

// function to print a device address
void printAddress(CurrentDeviceAddress deviceAddress)
{
  for (uint8_t i = 0; i < 8; i++)
  {
	  printf("0x%02X ", deviceAddress[i]);
  }
}
/* USER CODE END 0 */

/* USER CODE BEGIN 2 */
printf("[%8lu] Debug UART2 is OK!\r\n", HAL_GetTick());

OW_Begin(&ow1, &huart1);
OW_Begin(&ow2, &huart3);

if(OW_Reset(&ow1) == OW_OK)
{
  printf("[%8lu] OneWire 1 line devices are present :)\r\n", HAL_GetTick());
}
else
{
  printf("[%8lu] OneWire 1 line no devices :(\r\n", HAL_GetTick());
}

if(OW_Reset(&ow2) == OW_OK)
{
  printf("[%8lu] OneWire 2 line devices are present :)\r\n", HAL_GetTick());
}
else
{
  printf("[%8lu] OneWire 2 line no devices :(\r\n", HAL_GetTick());
}

DT_SetOneWire(&dt1, &ow1);
DT_SetOneWire(&dt2, &ow2);

// arrays to hold device address
CurrentDeviceAddress insideThermometer;

// locate devices on the bus
printf("[%8lu] 1-line Locating devices...\r\n", HAL_GetTick());
DT_Begin(&dt1);
uint8_t numDevOneLine = DT_GetDeviceCount(&dt1);
printf("[%8lu] 1-line Found %d devices.\r\n", HAL_GetTick(), numDevOneLine);

printf("[%8lu] 2-line Locating devices...\r\n", HAL_GetTick());
DT_Begin(&dt2);
uint8_t numDevTwoLine = DT_GetDeviceCount(&dt2);
printf("[%8lu] 2-line Found %d devices.\r\n", HAL_GetTick(), numDevTwoLine);

for (int i = 0; i < numDevOneLine; ++i)
{
	if (!DT_GetAddress(&dt1, insideThermometer, i))
		  printf("[%8lu] 1-line: Unable to find address for Device %d\r\n", HAL_GetTick(), i);
	printf("[%8lu] 1-line: Device %d Address: ", HAL_GetTick(), i);
	printAddress(insideThermometer);
	printf("\r\n");
	// set the resolution to 12 bit (Each Dallas/Maxim device is capable of several different resolutions)
	DT_SetResolution(&dt1, insideThermometer, 12, true);
	printf("[%8lu] 1-line: Device %d Resolution: %d\r\n", HAL_GetTick(), i, DT_GetResolution(&dt1, insideThermometer));
}

for (int i = 0; i < numDevTwoLine; ++i)
{
	if (!DT_GetAddress(&dt2, insideThermometer, i))
		printf("[%8lu] 2-line: Unable to find address for Device %d\r\n", HAL_GetTick(), i);
	printf("[%8lu] 2-line: Device %d Address: ", HAL_GetTick(), i);
	printAddress(insideThermometer);
	printf("\r\n");
	// set the resolution to 12 bit (Each Dallas/Maxim device is capable of several different resolutions)
	DT_SetResolution(&dt2, insideThermometer, 12, true);
	printf("[%8lu] 2-line: Device %d Resolution: %d\r\n", HAL_GetTick(), i, DT_GetResolution(&dt2, insideThermometer));
}
/* USER CODE END 2 */

/* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	// call sensors.requestTemperatures() to issue a global temperature
	// request to all devices on the bus
	printf("[%8lu] 1-line: Requesting temperatures...", HAL_GetTick());
	DT_RequestTemperatures(&dt1); // Send the command to get temperatures
	printf("\r\n[%8lu] 1-line: DONE\r\n", HAL_GetTick());

	printf("[%8lu] 2-line: Requesting temperatures...", HAL_GetTick());
	DT_RequestTemperatures(&dt2); // Send the command to get temperatures
	printf("\r\n[%8lu] 2-line: DONE\r\n", HAL_GetTick());

	for (int i = 0; i < numDevOneLine; ++i)
	{
		// After we got the temperatures, we can print them here.
		// We use the function ByIndex, and as an example get the temperature from the first sensor only.
		printf("[%8lu] 1-line: Temperature for the device %d (index %d) is: %.2f\r\n", HAL_GetTick(), i, i, DT_GetTempCByIndex(&dt1, i));
	}

	for (int i = 0; i < numDevTwoLine; ++i)
	{
		// After we got the temperatures, we can print them here.
		// We use the function ByIndex, and as an example get the temperature from the first sensor only.
		printf("[%8lu] 2-line: Temperature for the device %d (index %d) is: %.2f\r\n", HAL_GetTick(), i, i, DT_GetTempCByIndex(&dt2, i));
	}

	HAL_Delay(DT_MillisToWaitForConversion(DT_GetAllResolution(&dt1)));
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

26 коментарів:

  1. Бібліотека на FreeRTOS буде працювати?

    ВідповістиВидалити
    Відповіді
    1. Не бачу перешкод для цього. В якійсь задачі іноді опитуйте датчик, та й все.

      Видалити
  2. Добрый день.
    Запустил на stm32l476rg, но отрабатывает только reset (по анализатору видно, что presence condition приходит), но после этого зависает на DT_Begin и все. Подключил только один датчик...

    ВідповістиВидалити
    Відповіді
    1. Зависает на функции OW_Send...Состояние HAL_UART_STATE_READY никогда не появляется. Надо будет найти еще один датчик, может этот бракованный.

      Видалити
    2. Шановний, Валерій.
      Зараз немає можливості перепровірити для stm32f4xx. Але здається я перевіряв роботу на discovery 407 . Одначе колись в мене була схожа ситуація, як у вас. Розбиратись не було часу тоді і створив новий проект з нуля, а код зкопіпастив і все запрацювало. Я не знаю в чому була справа і більш такого не виникало. Декілька людей відписувало, що все працює нормально.

      Видалити
    3. Добрый день.
      Подключил вашу библиотеку к проекту для discovery 407, вместо huart2 вывожу данные на TFT с EmWin. Зависает на OW_Reset (). Можно как нибудь посмотреть ваш проект для stm32f407? Большое спасибо за вашу работу.

      Видалити
  3. Добрый день. Спасибо. Попробую так сделать, т.к. думаю что проблемы где-то в настройках дма...

    ВідповістиВидалити
  4. Андрей, здравствуйте
    Недавно перешел на STM32 и язык С, пытаюсь раскочегарить DS18B20 с помощью вашей библиотеки, поскольку использую те же самые инструменты CubeMX -> Atollic-> STM32F103C8T6
    Начал с простого:
    int b = OW_Reset();
    number = b; //выводится на 8-сегментный индикатор
    HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // проверка на зависание главного цикла
    HAL_Delay(1000);

    До этого момента всё прекрасно.
    Отправляю команду
    OW_Send(OW_SEND_RESET, (uint8_t *) "\xcc\x44", 2, (uint8_t *) NULL, 0, OW_NO_READ);
    HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
    HAL_Delay(1000);
    Уже на этом этапе зависает.

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

    ВідповістиВидалити
    Відповіді
    1. Вітання, бібліотека сира. Хоч в мене і працює, але є випадки що в інших не працює. Як вам не принципово по uart dma, то можете використовувати цю бібліотеку з ногодригом https://github.com/taburyak/ds18b20 - працює як слід.

      Видалити
    2. Я ещё пока сам не знаю, критично мне или нет :)
      Когда на атмеге дрыгал ногодрыгом, работоспособность протокола зависела от количества и нагруженности прерываний. В аналогичной задаче их количество пришлось уменьшать. Поскольку STM32F103 раз в 10 быстрее, прерывания выполняются за меньшее время, они должны гораздо меньше влиять на тайминги протокола. Так что да, по крайней мере в этой задаче всё должно работать. Спасибо.

      Видалити
  5. А можна контакти для детальнішого питання?

    ВідповістиВидалити
  6. Шановний Андрію! Як налаштовується STM32F103C8Tx і які при цьому параметри FreeTos у випадку "ногодрига"?
    Дякую заздалегить.

    ВідповістиВидалити
    Відповіді
    1. В бібліотеці https://github.com/taburyak/ds18b20 є конфігураційний файл де можна зазначити що буде використовуватись FRTOS а також порт і пін де шина 1-wire

      Видалити
  7. Дякую, я це бачив. Який режим треба вибрати для цього піну, бо він жеш один що на прийом, що на передачу. Це може бути тільки на пінах USART, налаштованих в режимі OW. Чи я помиляюсь?

    ВідповістиВидалити
    Відповіді
    1. Вказуєш в бібліотеці який пін 1-wire. Все інше робить бібліотека своїми функціями.

      Видалити
  8. У bool DT_ReadScratchPad()
    виправіть рядок
    b = OW_Send(OW_SEND_RESET, query, 19, scratchPad, 9, 10);//була помилка 18-8.
    Тоді функція DT_IsConnected_ScratchPad()
    з
    return b && (OW_crc8(scratchPad, 8) == scratchPad[SCRATCHPAD_CRC]);
    запрацює.



    Можна ще додати перевірку на нулі
    // Returns true if all bytes of scratchPad are '\0'
    bool isAllZeros(const uint8_t * const scratchPad, const size_t length) {
    for (size_t i = 0; i < length; i++) {
    if (scratchPad[i] != 0) {
    return false;
    }
    }

    return true;
    }
    А строку з перевіркою на
    return b && !isAllZeros(scratchPad, 8) && (OW_crc8(scratchPad, 8) == scratchPad[SCRATCHPAD_CRC]);

    Працює на f070 та f103 без корректіровок.
    Дякую автору за публікацію.
    ###########
    P.S.На космодроме купил датчики в ТО-92 и на метровом проводе. На метровом проводе работает ок на +127-50'С.
    Для ТО-92 уменьшил скорость baudRate = (baudRate*88)/100;
    , иначе не работает в полном диапазоне температур +127-50'C.

    ВідповістиВидалити
    Відповіді
    1. Щира дяка за ваш коментар. Я найближчим часом перевірю. І як, все добре, обов'язково виправлю і внесу корекції до статті.

      Видалити
    2. З вашими виправляннями return b && (OW_crc8(scratchPad, 8) == scratchPad[SCRATCHPAD_CRC]); запрацювало, але для сенсора по індексу 0. На жаль інші сенсори вже не бачаться. Тому поки питання залишається відкритим.

      Видалити
  9. 1. В описі слід наголосити, що в кубі треба включати глобальні переривання для того usart на якому висить датчик температури. Бо цей момент не є очевидним для всіх. Без перериваннь функція HAL_UART_Transmit_DMA працює некоректно. Зарившись в нутрощі цієї функції можна побачити, що статус "HAL_UART_STATE_READY" передавачу повертається саме після обробки переривання transmitcomplete. Саме тому у більшості коментаторів програма зависає в циклі
    while (HAL_UART_GetState(&HUARTx) != HAL_UART_STATE_READY)
    {
    __NOP();
    }

    2. Слід переставити місцями функції HAL_UART_Transmit_DMA та HAL_UART_Receive_DMA - першим включаємо приймач, а вже за ним передавач. Теоретично, на низькій частоті системної шини приймач може пропустити корисну інформацію від передавача в момент свого запуска.

    ВідповістиВидалити
    Відповіді
    1. Щира дяка за такий конструктивний коментар. Найближчим часом перевірю і виправлю. Дякую.

      Видалити
    2. Корективи до статті додав. Зазначив що потрібно увімкнути глобальні переривання для USART.
      Функції поміняв місцями.
      Щира дяка.

      Видалити
    3. Елки! Полтора года спустя! Хехе, спасибо, даже сейчас тема не утратила актуальности!

      Видалити