суботу, 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 */