czwartek, 23 listopada 2017

[31] STM32F4 - Obsługa czujnika DS18B20

W tym poście chciałbym zaprezentować sposób komunikacji z czujnikiem temperatury DS18B20 oraz mikrokontrolerem STM32F4.

[Źródło: http://www.st.com/en/evaluation-tools/stm32f4discovery.html]

Konfiguracja CubeMx:


Tutaj właściwie wystarczy ustawić jeden pin do komunikacji z czujnikiem temperatury. Dodatkowo ustawiany jest USART2 do komunikacji. 

Standardowo włączany jest także debugger oraz zegar RCC. Wobec tego, że nie ma tutaj specjalnie wielu rzeczy do uruchamiania to pominę głębszy opis tej części. Plik do wygenerowania projektu w środowisku CubeMx znajduje się na dole strony. 

Opis funkcji:


Bibliotekę można pobrać pod tym linkiem w katalogu STM32 a następnie STM32F4.

Wygenerowana konfiguracja portu:

  1. static void MX_GPIO_Init(void)
  2. {
  3.   GPIO_InitTypeDef GPIO_InitStruct;
  4.   /* GPIO Ports Clock Enable */
  5.   __HAL_RCC_GPIOH_CLK_ENABLE();
  6.   __HAL_RCC_GPIOA_CLK_ENABLE();
  7.   __HAL_RCC_GPIOB_CLK_ENABLE();
  8.   /*Configure GPIO pin Output Level */
  9.   HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_RESET);
  10.   /*Configure GPIO pin : PB11 */
  11.   GPIO_InitStruct.Pin = GPIO_PIN_11;
  12.   GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  13.   GPIO_InitStruct.Pull = GPIO_NOPULL;
  14.   GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  15.   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  16. }

Opóźnienie:

  1. __STATIC_INLINE void delayMicroSec(__IO uint32_t microSec)
  2. {
  3.     microSec *= (SystemCoreClock / 1000000) / 8;
  4.     while (microSec--) ;
  5. }

Rozpoczęcie pracy z czujnikiem należy rozpocząć od inicjalizacji:

  1. uint8_t ds18b20Init(uint8_t mode)
  2. {
  3.     /* Send Reset pulse then wait for Presence Pulse */
  4.     if(ds18b20ResetDevice())
  5.     {
  6.         return 1;
  7.     }
  8.     /* If skip rom then we adress all devices on one line
  9.      * with the same setting */
  10.     if(mode==SKIP_ROM)
  11.     {
  12.         ds18b20WriteByte(COM_SKIP_ROM);
  13.         ds18b20WriteByte(COM_WRITE_SCRATCHPAD);
  14.         ds18b20WriteByte(0x14); /* Set Th high temp alarm for 50 */
  15.         ds18b20WriteByte(0x8A); /* Set Tl low temp alarm for -10 */
  16.         ds18b20WriteByte(RESOLUTION_12BIT);
  17.     }
  18.     return 0;
  19. }

Powyższa funkcja rozpoczyna pracę z układem od wysłania impulsu reset a następnie czekania na tzw. Presence pulse. Po wykryciu tych komend możliwa jest obsługa komend ROM. Po tej komendzie wysyłana jest wiadomość SKIP_ROM, która powoduje wysyłanie jednakowych komend do wszystkich układów na linii. Następnie wysyłana jest komenda WRITE_SCRATCHPAD. Pozwala ona na wprowadzenie trzech informacji jedna do rejestru Th następna do Tl, trzecia zaś do rejestru konfiguracyjnego. Do tego ostatniego przesyłana jest wartość rozdzielczości.

Każde przeprowadzenie komunikacji z układem musi być poprzedzone resetem linii danych:

  1. static uint8_t ds18b20ResetDevice(void)
  2. {
  3.     uint16_t status = 0;
  4.     uint8_t check = 0;
  5.    
  6.     HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_RESET);
  7.     delayMicroSec(480);
  8.     HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_SET);
  9.     delayMicroSec(70);
  10.    
  11.     status = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_11);
  12.     if(status == 0) {   check++;    }
  13.     delayMicroSec(300);
  14.     status = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_11);
  15.     if(status == 1) {   check++;    }
  16.     if(check == 2) {    return 1;   }
  17.     else           {    return 0;   }
  18. }

Najpierw linia one wire ustawiana jest w stan niski. Po odczekaniu 480us następuje zmiana stanu na wysoki i odczekanie 70us. Po tym czasie ds powinien ustawić linię w stan niski, a następnie po po czasie linia powinna wrócić do stanu wysokiego. Jeśli wszystko przeszło pomyślnie to funkcja zwraca wartość jeden.

Teraz funkcja odpowiedzialna za przesyłanie pojedynczego bitu po linii:

  1. static void ds18b20WriteBit(uint8_t bit)
  2. {
  3.     if(bit == 0)
  4.     {
  5.         HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_RESET);
  6.         delayMicroSec(65);
  7.         HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_SET);
  8.         delayMicroSec(10);
  9.     }
  10.     else
  11.     {
  12.         HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_RESET);
  13.         delayMicroSec(10);
  14.         HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_SET);
  15.         delayMicroSec(65);
  16.     }
  17. }

W zależności czy ustawiany jest bit 1 czy 0 czasy pomiędzy impulsami będą różne, przy czym czas potrzebny na przesłane pojedynczego bitu będzie taki sam.

Odczytanie pojedynczego bitu:

  1. static uint8_t ds18b20ReadBit(void)
  2. {
  3.     uint8_t readBit = 0;
  4.     HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_RESET);
  5.     delayMicroSec(5);
  6.    
  7.     HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_SET);
  8.     delayMicroSec(5);
  9.    
  10.     readBit = (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_11) ? 1 : 0);
  11.     delayMicroSec(45);
  12.     return readBit;
  13. }

Odczyt bitów polega na wystawieniu na linii stanu niskiego a następnie wysokiego. Oba na czas 5 us. Po takiej operacji układ rozpocznie nadawanie. 

Poniżej znajdują się funkcje wysyłające oraz odbierające pojedynczy bajt danych. Obie funkcje korzystają z odczytu bądź zapisu pojedynczego bitu. Przesłanie danych następuje od najmłodszego do najstarszego bitu odwrotnie niż w przypadku odbierania danych.

Przesłanie całego bajtu danych:

  1. static void ds18b20WriteByte(uint8_t data)
  2. {
  3.     for (uint8_t i = 0; i < 8; i++)
  4.     {
  5.         ds18b20WriteBit((data >> i) & 0x01);
  6.         delayMicroSec(5);
  7.     }
  8. }

Odczytanie bajtu danych:

  1. static uint8_t ds18b20ReadByte(void)
  2. {
  3.     uint8_t data = 0;
  4.     for (uint8_t i = 0; i <= 7; i++)
  5.     {
  6.         data += ds18b20ReadBit() << i;
  7.     }
  8.     return data;
  9. }

Odczytanie wartości temperatury:

  1. void ds18b20DevConTemp(uint8_t mode, uint8_t DevNum)
  2. {
  3.     ds18b20ResetDevice();
  4.     if(mode == SKIP_ROM)
  5.     {
  6.         ds18b20WriteByte(COM_SKIP_ROM);
  7.     }
  8.     ds18b20WriteByte(COM_CONVERT_T);
  9.    
  10.     /*
  11.      * Delay to wait for convert temperature finish.
  12.      * With resolution of 12, time to end is give or take 750ms
  13.      * */
  14.     HAL_Delay(800);
  15. }

Procedurę odczytu rozpoczyna się od przesłania resetu układu. Jak urządzenie wejdzie w tryb przyjmowania komend wysyła się SKIP_ROM a następnie CONVERT_T. Ta ostatnia rozpoczyna rzeczywisty pomiar oraz uruchamia konwersję. Po jej zakończeniu jest ona zapisywana do dwu-bajtowym rejestrze w pamięci układu. 

Przesłanie komendy READ_SCRATCHPAD i odczytanie wszystkich wartości z rejestru:

  1. void ds18b20ReadScratchpad(uint8_t mode, uint8_t *data, uint8_t deviceNumber)
  2. {
  3.     uint8_t i;
  4.     ds18b20ResetDevice();
  5.    
  6.     if(mode==SKIP_ROM)
  7.     {
  8.         ds18b20WriteByte(COM_SKIP_ROM);
  9.     }
  10.     ds18b20WriteByte(COM_READ_SCRATCHPAD);
  11.    
  12.     for(i=0;i<8;i++)
  13.     {
  14.         data[i] = ds18b20ReadByte();
  15.     }
  16. }

Pobranie znaku z układu:

  1. uint8_t ds18b20ReadSign(uint16_t data)
  2. {
  3.     if ( data & (1<<11))    {   return 1;   }
  4.     else                    {   return 0;   }
  5. }

Przeprowadzenie konwersji temperatury na typ float:

  1. float ds18b20GetTemp(uint16_t data)
  2. {
  3.     float t;
  4.     /* delete all not needed data */
  5.     t = (float)((data & 0x07FF)>>4);
  6.     /* add fraction part */
  7.     t += (float)(data & 0x000F) / 16.0f;
  8.     return t;
  9. }

Główna pętla programu wygląda następująco:

  1.   /* USER CODE BEGIN 1 */
  2.     char uartBuffer[60];
  3.     uint8_t status = 0;
  4.     uint8_t buffer[8] = {0};
  5.     uint16_t readDataRaw = 0;
  6.     float tempValue = 0.0;
  7.     char c = ' ';
  8.   /* USER CODE END 1 */
  9.   /* MCU Configuration----------------------------------------------------------*/
  10.   /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  11.   HAL_Init();
  12.   /* USER CODE BEGIN Init */
  13.   /* USER CODE END Init */
  14.   /* Configure the system clock */
  15.   SystemClock_Config();
  16.   /* USER CODE BEGIN SysInit */
  17.   /* USER CODE END SysInit */
  18.   /* Initialize all configured peripherals */
  19.   MX_GPIO_Init();
  20.   MX_USART1_UART_Init();
  21.   MX_USART2_UART_Init();
  22.   /* USER CODE BEGIN 2 */
  23.   status = ds18b20Init(SKIP_ROM);
  24.   sprintf(uartBuffer,"Initial status: %drn",status);
  25.   HAL_UART_Transmit(&huart2,(uint8_t*)uartBuffer,strlen(uartBuffer),0x1000);
  26.   /* USER CODE END 2 */
  27.   /* Infinite loop */
  28.   /* USER CODE BEGIN WHILE */
  29.   while (1)
  30.   {
  31.   /* USER CODE END WHILE */
  32.   /* USER CODE BEGIN 3 */
  33.       ds18b20DevConTemp(SKIP_ROM, 0);
  34.       ds18b20ReadScratchpad(SKIP_ROM, buffer, 0);
  35.       sprintf(uartBuffer,"SCRATCHPAD: [0]:%02X [1]:%02X [2]:%02X [3]:%02X [4]:%02X [5]:%02X [6]:%02X [7]:%02X; ",
  36.               buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7]);
  37.       HAL_UART_Transmit(&huart2,(uint8_t*)uartBuffer,strlen(uartBuffer),0x1000);
  38.       readDataRaw = ((uint16_t)buffer[1]<<8) | buffer[0];
  39.       if(ds18b20ReadSign(readDataRaw))  {   c='-';  }
  40.       else                              {   c='+';  }
  41.       tempValue = ds18b20GetTemp(readDataRaw);
  42.       sprintf(uartBuffer,"RegVal: 0x%04X - %c%.2f\r\n", readDataRaw, c, tempValue);
  43.       HAL_UART_Transmit(&huart2,(uint8_t*)uartBuffer,strlen(uartBuffer),0x1000);
  44.       HAL_Delay(500);
  45.   }

Cały projekt wraz z danymi można pobrać z dysku Google pod tym linkiem. Po otwarciu należy wejść w folder STM32 a następnie STM32F4_Discovery. 

Kolejny post z tej serii będzie prezentował metodę dostępu do kilku czujników temperatury oraz odczytanie flagi alarmu.