четвер, 24 травня 2018 р.

STM32: Бібліотека для зручного надсилання інформації налагодження в порт UART

Передмова

В проектах на мікроконтролерах часто-густо потрібно десь виводити інформацію для налагодження (debug). Найпростіше і зручніше це зробити в порт UART. Тоді, коли в цьому є потреба, в певних ділянках коду, вставляємо надсилання потрібних повідомлень в порт UART, а на ПК в будь-якій термінальній програмі читаємо повідомлення, що надсилає нам мікроконтролер. Це можуть бути повідомлення про помилку, або данні телеметрії з сенсорів і датчиків, або якась службова інформація, тощо. 
Для зручного надсилання в порт UART текстової, числової інформації зручно використовувати бібліотеку "uart". За її допомоги можна надсилати як поодинокі символи, так і числа типу INT, або числа представлені в шістнадцятковому форматі HEX8, HEX16, HEX32, або ж цілий текстовий рядок, або передати цілий буфер даних. 
Всі функції бібліотеки вгледів тут. Функцію ініціалізації порту UART не перейняв, тому що я всі налаштування периферії виконую в програмі CubeMX for STM32.

CubeMX

  • Створіть новий проект, або відкрийте проект з яким вже працюєте і вам потрібна інформація налагодження в COM PORT
  • Увімкніть потрібний порт UART
  • Налаштуйте по необхідності цей порт. За замовчуванням швидкість порту 115200
  • Згенеруйте або оновіть проект, з увімкненим UART

Бібліотека UART

Скопіюйте файли бібліотеки до вашого проекту. Бібліотека містить два файли:
Файл uart.h до теки inc проекту, а файл uart.c до теки src вашого проекту.
В файлі uart.h пропишіть ім'я структури порту UART, що надав CubeMX. Наприклад, huart1, huart2, тощо. Та файл HAL драйверу відповідно до вашого мікроконтролера, наприклад, stm32f1xx_hal.h якщо використовуєте stm32f1 серії: 
#include "stm32f1xx_hal.h"
#define UART_PORT  huart1
Все інше залиште без змін.

Перейдемо до файлу main.c проекту, та підключимо бібліотеку UART:
/* USER CODE BEGIN Includes */
#include "uart.h"
/* USER CODE END Includes */

Ну і щось надрукуємо для прикладу. Рядок тексту, оголосимо змінну INT та надрукуємо її як в десятковому так і шістнадцятковому представленні:
/* USER CODE BEGIN 2 */
UART_SendStr("UART is OK!\n\r");
  int var = 12345;
  UART_SendInt(var);
  UART_SendStr("\n\r");
  UART_SendHex16(var);
  UART_SendStr("\n\r");
/* USER CODE END 2 */

Компілюємо, заливаємо і в термінальній програмі споглядаємо на результат. Звісно що порт UART мікроконтролеру має бути з'єднаний з ПК за допомоги UART to USB TTL конвертеру. Або якщо, наприклад, у вас плата розробника STM32F103RB Nucleo, то достатньо використовувати USART2 на мікроконтролері, данні будуть надсилатись в USB порт програматора ST-LINK що на борту плати. І додаткових дротів і конверторів не потрібно.

Архів з бібліотекою та джерело

неділя, 20 травня 2018 р.

STM32: Бібліотека для роботи з EEPROM типу AT24XXX по шині I2C

update 01.06.2018: шановний ReAl з форуму replace допоміг зі створенням вже оновленої версії цієї бібліотеки. Обговорення тут. Йому велика дяка, всім іншим - користуйтесь на здоров'я.

Передмова

В проектах, які вже "виросли" за межі просто поблимати світлодіодами, іноді виникає потреба десь зберігати дані в енергонезалежній пам'яті. Це можуть бути як і дані налаштувань, так і дані телеметрії, тощо. Варіантів безліч. Звісно, можна замість зовнішньої EEPROM використати внутрішню FLASH пам'ять самого мікроконтролеру і там зберігати якісь дані. Але ця стаття саме про зовнішню енергонезалежну EEPROM типу AT24Cxx, як її під'єднати до мікроконтролеру, як записати і як прочитати дані.

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

Мікросхеми типу AT24Cxx працюють по шині I2C і підключаються до мікроконтролеру як і будь-який інший пристрій з шиною I2C:
Схема підключення EEPROM до мікроконтролера
Схема типова для всіх мікросхем цієї серії. В схемі що на малюнку EEPROM має адресу 0x50 (A0, A1, A2 - GND), та є дозвіл на запис (HOLD - GND). Не забуваймо про підтяжку до плюс живлення (логічної 1) ліній SDA і SCL шини I2C.

Корисно ознайомитись з документацією на свій чип EEPROM в мене це AT24C512. А також по цій темі є дуже хороша стаття: "Робота з EEPROM пам'яттю 24CXX -- огляд". Зі схемами і подробицями роботи I2C. 

Бібліотека AT24Cxx

Бібліотеку взяв у Ben Brown допис STM32 I2C EEPROM with HAL. Переробив на мову C замість C++, додав одну функцію для визначення чи під'єднаний чип EEPROM, чи ні.
Бібліотека має два файли:
У файлі AT24Cxx.h під'єднуємо бібліотеку HAL для свого чипу, прописуємо порт I2C до якого під'єднаний EEPROM, адресу EEPROM на шині I2C та розмір сторінки пам'яті EEPROM.
Наприклад, EEPROM AT24C512 (512 сторінок по 128 байт) під'єднаний до STM32F103C8T6 до першого I2C1 з адресою 0x50 (A0, A1, A2 - GND), то значення пишемо такі:
#include "stm32f1xx_hal.h"
#define EEPROM_I2C  hi2c1
#define EEPROM_ADDRESS  0x50
#define EEPROM_PAGESIZE 128

Бібліотека має всього три функції:
HAL_StatusTypeDef AT24Cxx_IsConnected(void);
HAL_StatusTypeDef AT24Cxx_ReadEEPROM(unsigned address, const void* src, unsigned len);
HAL_StatusTypeDef AT24Cxx_WriteEEPROM(unsigned address, const void* src, unsigned len);

Перша, це просто перевірка чи є відгук за адресою EEPROM на шині I2C, чи немає. Повертає успіх чи невдачу з переліку HAL_StatusTypeDef.


Друга, читання з EEPROM в RAM мікроконтролера. Приймає початкову адресу в межах адресного простору вашого чипу EEPROM звідки прочитуються збережені дані, вказівник на змінну куди прочитуються дані, розмір даних які читаються з EEPROM. Повертає успіх чи невдачу з переліку HAL_StatusTypeDef.

Третя, запис до EEPROM. Приймає початкову адресу в межах адресного простору вашого чипу EEPROM куди будуть зберігатись дані, вказівник на дані які потрібно зберегти, розмір даних для збереження. Повертає успіх чи невдачу з переліку HAL_StatusTypeDef.

Приклад роботи

Для відладки і виводу різної інформації дуже зручно використовувати порт UART мікроконтролеру який під'єднано до ПК через UART2USB перехідник. Скористаємось такою нагодою і в CubeMX крім шини I2C1 увімкнемо порт UART1. Та приєднаємо бібліотеку UART до проекту.

CubeMX

Запускаємо CubeMX, створюємо новий проект, обираємо свій мікроконтролер, та вмикаємо потрібну переферію:
Налаштування в CubeMX
Робимо як на світлині позначено червоним:
  1. Вмикаємо шину I2C1
  2. Вмикаємо тактування від зовнішнього кварцевого резонатору
  3. Вмикаємо налогодження по SerialWire
  4. Вмикаємо порт UART1
Налаштування всі за замовчуванням нічого не міняв. Зберігаємо проект, генеруємо код.

Демо-код

Відкриваємо чи експортуємо проект в своєму засобі розробки. Копіюємо в теку inc проекту файл AT24Cxx.h, а в теку src проекту копіюємо файл AT24Cxx.c. І в визначені CubeMX для користувача ділянки - додаємо такий код:

Під'єднуємо бібліотеки до проекту.
/* USER CODE BEGIN Includes */
#include "AT24Cxx.h"
#include "uart.h"
/* USER CODE END Includes */

Оголосимо структуру для прикладу:
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
typedef struct{
 int intVar;
 char str[256];
 uint8_t flag;
 uint16_t save;
}ExampleTypeDef;
/* USER CODE END PV */

Та зміну цієї структури:
/* USER CODE BEGIN 0 */
ExampleTypeDef example;
/* USER CODE END 0 */

І сам демо-код.
 /* USER CODE BEGIN 2 */

  UART_SendStr("UART is OK!\n\r");

  if(AT24Cxx_IsConnected() == HAL_OK)
  {
   UART_SendStr("EEPROM is OK!\n\r");
   UART_SendStr("Size data = ");
   UART_SendInt(sizeof(example));
   UART_SendStr("\n\r");

   if(AT24Cxx_ReadEEPROM(396, &example, sizeof(example)) == HAL_OK)
   {
    UART_SendStr("EEPROM read is OK!\n\r");

    if(example.save != 0xABCD)
    {
     example.flag = 1;
       example.intVar = 12345678;
       example.save = 0xABCD;
       strcpy(example.str, "Testing for save any data structure. Thank you, ReAl!!! The link http://replace.org.ua/topic/9430/");

       if(AT24Cxx_WriteEEPROM(396, &example, sizeof(example)) == HAL_OK)
     {
      UART_SendStr("EEPROM save is OK!\n\r");
     }
     else
     {
      UART_SendStr("EEPROM save is failed!\n\r");
     }
    }

    UART_SendInt(example.intVar);
    UART_SendStr("\n\r");
    UART_SendStr("0x");
    UART_SendHex16(example.save);
    UART_SendStr("\n\r");
    UART_SendStr(example.str);
    UART_SendStr("\n\r");
   }
   else
   {
    UART_SendStr("EEPROM read is failed!\n\r");
   }
  }
  else
  {
   UART_SendStr("EEPROM is not connected!\n\r");
  }
  /* USER CODE END 2 */
Тут в зміну example читаємо дані з EEPROM і якщо ще не було запису до EEPROM, то записуємо певну інформацію. Компілюємо, заливаємо до мікроконтролеру і в терміналі ПК спостерігаємо запис/читання даних.
Тепер можна зняти живлення з мікроконтролеру і знову подати його. В термінал надрукуються ті самі дані, що були збережені до EEPROM першого разу.

Архів з бібліотекою

Завантажити архів з бібліотекою AT24Cxx.rar

четвер, 10 травня 2018 р.

STM32: Годинник реального часу на stm32f1xx. Проблеми, рішення (частина друга).


update 07.10.2018: знайшов помилку в бібліотеці у функції задавання часу. Виправив бібліотеку і трішки змінив саму бібліотеку. Всі функції залишились на своєму місці. Коли викликається функція взяття часу, то крім часу визначається і дата. Коли викликається дата, то просто повертається дата, яку вже вирахували під час взяття часу. Якщо, до взяття дати,  функція взяття часу, ніколи не викликалась, то в функції взяття дати, виклик функції взяття часу, станеться автоматично. Так само автоматично оновиться дата при її зміні. 

Передмова

Це продовження статті "STM32: Годинник реального часу на stm32f1xx. Проблеми, рішення (частина перша)". В першій частині описано проблеми RTC на STM32F1xx серії та невдалі спроби подолати їх за допомоги HAL драйверу. Проблема полягає в тому, що при використанні HAL_RTC - лічильник RTC, який рахує секунди, функціями HAL драйверу, "обрізається" в межах доби. Максимально 86400 секунд. А дата зберігається окремо просто в RAM пам'яті мікроконтролера. Тому при вимкнені мікроконтролеру чи перехід в сон, втрачається дата RTC. Можна записати дату в backup регістри, але це не врятує, якщо мікроконтролер буде без живлення при переході на нову дату. Тому на серії STM32F1xx єдиний вихід правильної роботи RTC це враховувати в лічильнику RTC секунди, як часу так і дати - разом, а за допомоги розрахунків видобувати вже поточну час і дату. Ну і якщо вже використовуємо HAL драйвера та не створювати дублікат бібліотеки HAL_RTC, я просто створив невеличку бібліотеку зі зміненими деякими функціями:
HAL_StatusTypeDef mRTC_GetTime(RTC_HandleTypeDef* hrtc, RTC_TimeTypeDef* sTime, uint32_t Format);
HAL_StatusTypeDef  mRTC_GetDate(RTC_HandleTypeDef* hrtc, RTC_DateTypeDef* sDate, uint32_t Format);
HAL_StatusTypeDef  mRTC_SetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format);
HAL_StatusTypeDef  mRTC_SetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format);
HAL_StatusTypeDef mRTC_SetAlarm(RTC_HandleTypeDef *hrtc, RTC_AlarmTypeDef *sAlarm, uint32_t Format);
HAL_StatusTypeDef mRTC_SetAlarm_IT(RTC_HandleTypeDef *hrtc, RTC_AlarmTypeDef *sAlarm, uint32_t Format);
Це функції встановлення/взяття часу, та встановелння/взяття дати, а також встановлення будильнику.

Щоб коректно задавати час, та брати час слід використовувати саме ці функції, а не аналогічні функції, які надаються HAL драйверами.

Налаштування і запуск RTC в CubeMX

Всі налаштування RTC в CubeMX залишаються без змін і не мають ніяких особливостей. Як налаштувати роботу RTC можна переглянути в попередній статті, все абсолютно так само в однойменному розділі "Налаштування і запуск RTC в CubeMX"
Та не забудьте увімкнути переривання від RTC раз на секунду:
Увімкнути переривання від RTC раз на секунду
Це потрібно для демонстрації роботи RTC.

Додаткова бібліотека RTC з модифікованими функціями

Додаткова бібліотека mRTC з модіфікованими функціями містить два файли:
В цій бібліотеці є змінені функції аналогічні функціям HAL драйверу, так і додаткові для зручності використання.

Список всіх функцій додаткової бібліотеки mRTC:
#define TIME_COUNTER(hrtc) mRTC_ReadCounter(hrtc) // повертає значення лічильнику часу
#define ALARM_COUNTER(hrtc) mRTC_ReadAlarmCounter(hrtc) // повертає значення лічильнику будильника
#define SECOND(hrtc, format) mRTC_GetSecond(hrtc, format) // повертає секунди
#define MINUTE(hrtc, format) mRTC_GetMinute(hrtc, format) // повертає хвилини
#define HOUR(hrtc, format) mRTC_GetHour(hrtc, format) // повертає години
#define WEEKDAY(hrtc) mRTC_GetWeekDay(hrtc) // повертає день тижня
#define DAY(hrtc, format) mRTC_GetDay(hrtc, format) // повертає день
#define MONTH(hrtc, format) mRTC_GetMonth(hrtc, format) // повертає місяць
#define YEAR(hrtc, format) mRTC_GetYear(hrtc, format) // повертає рік

void mRTC_Begin(RTC_HandleTypeDef* hrtc); // оновлює дату в буфері, викликати раз після увімкнення MCU
uint8_t mRTC_GetSecond(RTC_HandleTypeDef* hrtc, uint32_t Format); // повертає секунди
uint8_t mRTC_GetMinute(RTC_HandleTypeDef* hrtc, uint32_t Format); // повертає хвилини
uint8_t mRTC_GetHour(RTC_HandleTypeDef* hrtc, uint32_t Format); // повертає години
uint8_t mRTC_GetWeekDay(RTC_HandleTypeDef* hrtc); // повертає день тижня
uint8_t mRTC_GetDay(RTC_HandleTypeDef* hrtc, uint32_t Format); // повертає день
uint8_t mRTC_GetMonth(RTC_HandleTypeDef* hrtc, uint32_t Format); // повертає місяць
uint16_t mRTC_GetYear(RTC_HandleTypeDef* hrtc, uint32_t Format); // повертає рік

HAL_StatusTypeDef mRTC_GetTime(RTC_HandleTypeDef* hrtc, RTC_TimeTypeDef* sTime, uint32_t Format); // повертає час в структуру часу
HAL_StatusTypeDef mRTC_GetDate(RTC_HandleTypeDef* hrtc, RTC_DateTypeDef* sDate, uint32_t Format); // повертає дату в структуру дати
HAL_StatusTypeDef mRTC_SetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format); // задає час з структури часу
HAL_StatusTypeDef mRTC_SetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format); // задає дату з структури дати

HAL_StatusTypeDef mRTC_SetAlarm(RTC_HandleTypeDef *hrtc, RTC_AlarmTypeDef *sAlarm, uint32_t Format); // задає час будильника в межах доби
HAL_StatusTypeDef mRTC_SetAlarm_IT(RTC_HandleTypeDef *hrtc, RTC_AlarmTypeDef *sAlarm, uint32_t Format); // задає час будильника в межах доби з перериваннями
На початку програми, після увімкнення MCU потрібно один раз викликати функцію mRTC_Begin для оновлення дати в буфері. Щоб не вираховувати кожного разу день тижня, день, місяць і рік у функціях: mRTC_GetWeekDay, mRTC_GetDay, mRTC_GetMonth, mRTC_GetYear, та mRTC_GetDate - значення беруться просто з буферу. Кожного разу коли викликається функція mRTC_GetTime, автоматично оновлюється дата в буфері. Якщо користуватись тільки функцією RTC_GetDate про зміну дня не потрібно турбуватись, функція mRTC_GetTime, при зміні дня, викличеться автоматично. Якщо користуватись функціями взяття окремо годин (mRTC_GetHour), хвилин (mRTC_GetMinute), секунд (mRTC_GetSecond), дня (mRTC_GetDay), місяця (mRTC_GetMonth), року (mRTC_GetYear) і дня тижня (mRTC_GetWeekDay), то теж потрібно самому контролювати вчасне оновлення дати в буфері викликом функції mRTC_Begin, або mRTC_GetTime.

Демонстраційна програма

  • За допомоги CubeMX створіть проект з налаштуваннями RTC, як зазначено вище у розділі "Налаштування і запуск RTC в CubeMX". Дайте йому ім'я, та згенеруйте код ініціалізації для свого засобу розробки.
  • Відкрийте чи імпортуйте проект створений програмою CubeMX у своєму засобі розробки. В мене це Atolic TrueStudio, професійний засіб розробки абсолютно безкоштовний.
  • Додайте файли бібліотеки до свого проекту. Файли з розширенням ".h" до теки "inc", а файли з розширенням ".c" до теки "src"
  • Відрийте файл "main.c" і в зазначені для користувача ділянки коду додаємо демонстраційний код.
Приєднуємо до програми бібліотеку "mRTC":
/* USER CODE BEGIN Includes */
#include "m_rtc.h"
/* USER CODE END Includes */

Оголошуємо глобальні змінні та структури часу і дати:
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
uint8_t aShowTime[12] = {0};
uint8_t aShowDate[12] = {0};

RTC_TimeTypeDef sTime;
RTC_DateTypeDef sDate;
/* USER CODE END PV */

Оголошуємо прототипи функцій показу часу/дати та встановлення часу/дати:

/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
void Show_RTC_Calendar(void);
void setDataTime(void);
/* USER CODE END PFP */

Викликаємо функції: увімкнути переривання RTC кожної секунди, оновити в буфері значення дати, показати дату і час.
  /* USER CODE BEGIN 2 */
  HAL_RTCEx_SetSecond_IT(&hrtc);
  // setDataTime(); // якщо потрібно встановити початковий час і дату, то розкоментуйте рядок
  mRTC_Begin(&hrtc);
  Show_RTC_Calendar();
  /* USER CODE END 2 */

Та не забуваємо написати самі функції в користувацькому блоці 4:
/* USER CODE BEGIN 4 */
void HAL_RTCEx_RTCEventCallback (RTC_HandleTypeDef* hrtc)
{
 Show_RTC_Calendar();
}

void Show_RTC_Calendar(void)
{
 /* Note: need to convert in decimal value in using __LL_RTC_CONVERT_BCD2BIN helper macro */
 /* Display time Format : hh:mm:ss */
 mRTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
 sprintf((char*)aShowTime,"%.2d:%.2d:%.2d", sTime.Hours, sTime.Minutes, sTime.Seconds);

 /* Display date Format : mm-dd-yy */
 mRTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
 sprintf((char*)aShowDate,"%.2d-%.2d-%.2d-%.2d", sDate.Date, sDate.Month, sDate.Year, sDate.WeekDay);
}

void setDataTime(void)
{
 sTime.Hours = 21;
 sTime.Minutes = 37;
   sTime.Seconds = 00;

   if(mRTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
   {
  Error_Handler();
   }

   sDate.Date = 9;
   sDate.Month = 5;
   sDate.Year = 18;

   if(mRTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK)
   {
    Error_Handler();
   }
}
/* USER CODE END 4 */

В такому варіанті кожної секунди буде викликатись функція Show_RTC_Calendar і в масивах aShowTime та aShowDate будуть знаходитись текстові рядки з актуальним часом і датою. А вже цей текст з датою і часом виводьте - чи то на дисплей, чи то в послідовний порт. На ваш розсуд.

RTC Alarm

Також можна встановити будильник за допомоги функцій mRTC_SetAlarm або mRTC_SetAlarm_IT значення лічильника RTC_Alarm встановлюється в межах доби, якщо поточний час більший за значення часу будильника і якщо значення будильника менше значення поточного часу, то будильник встановиться на наступну добу.

Значення лічильника RTC_Alarm встановлюється коректним і по досягненню лічильника RTC значення RTC_Alarm будильника скидається. Але чомусь самого переривання по Alarm не відбувається. Хоча все робив як в прикладі, що йде з HAL драйверами. Запустити будильник мені так і не вдалось: як з рідними HAL драйвером RTC, як за допомоги CMSIS,  так не виходило і за допомоги STD_Periph драйверів, ну і звісно і зараз не вийшло.
Можливо я просто не розумію як працює цей alarm і не правильно користуюсь ним. Як хто має позитивний досвід використання RTC_Alarm на STM32F1xx серії - поділіться цим зі мною.

RTC LL

Зараз в додачу до HAL драйверів з'явились LL драйвер (сучасний нащадок Std_Periph). Пробував запустити RTC на STM32F1xx серії за допомоги LL драйверу. То також повне фіаско. В лічильнику RTC додається одна секунда аж раз на 30 реальних секунд. В кого є позитивний досвід використання RTC за допомоги LL драйверу, теж, будь ласка, відгукніться.

Бібліотека mRTC