субота, 26 листопада 2016 р.

Мій проект: Контроль напруги батарей

Передмова

Цей допис роблю для себе, як документацію на пристрій. Пристрій робився на замовлення в одному екземплярі. Як кому згодиться то дуже добре. В цьому проекті є: RTC, ADC, TIMER, LCD1602, keyboard, micromenu, SD-CARD SPI, UART. Можна подивитись як реалізовано для прикладу і використовувати в своїх проектах.

Призначення


Цей пристрій призначений для контролю поточної напруги +60В, -60В, -24В, на батареях, та у разі виходу напруги за межі норми сповістити користувача звуковою і світловою сигналізацією. Пристрій оснащений символьним дисплеем де виводиться інформація про напруги, поточний час з датою. Також пристрій записує на SD-Card всі події які відбуваються: поточні значення напруги, вихід напруги за межі норми, час коли користувач зреагував на аварійну ситуацію, та коли напруги повернулись в межі норми. Пристрій має розгалужене, зручне меню для гнучкого налаштування параметрів які зберігаються на флеш-карті.

Схема пристрою

Пристрій складається з двох плат на яких розташовані: дільник напруги, мікропроцесорний блок на STM32F103C8T6 (blue peel), дисплей на 2 рядки по 16 символів LCD1602, модуль SD-CARD, клавіатурний блок і блок сигналізації (звукова і світлова), та блок живлення з гальванічною розв'язкою на  5В (наприклад, від зарядного для смартфону).
Схема пристрою. Клацайте щоб збільшити.

Програмне забезпечення

Всі сирці проекту CubeMX + Atolic TrueStudio знаходяться за ланкою.

середа, 9 листопада 2016 р.

STM32: Бібліотека Delay (мілісекунди, мікросекунди) на таймерах


Передмова

Звісно, штучних затримок в програмі потрібно уникати. І часові затримки краще організовувати за допомоги переривань по таймерам з подальшої обробкою. Але в невеличких проектах і там де виконання програми не критичне до затримок, дуже зручно використовувати прості функції delay. Нещодавно почав використовувати драйвер HAL де є вже функція затримки в мілісекундах. Функції затримки в мікросекундах на жаль там немає. Та і прив'язуватись до якогось високорівневого драйверу в такій простій задачі, як затримки, теж немає сенсу. Вирішив створити бібліотеку delay для своїх потреб з затримками в мілісекундах і мікросекундах. Щоб відлік часу був точним, обов'язково організувати відлік часу на таймерах. Щоб була відв'язана від високорівневих драйверів HAL чи SPL і мала мінімум налаштувань. Щоб достатньо було вказати який використовувати для затримок таймер.

Бібліотека delay

Бібліотека "delay" складається з двох файлів: delay.h і delay.c

Має такі функції:
  • void delayInit(void) - функція вмикання потрібного таймеру для відліку пауз;
  • void delayDeInit(void) - функція вимикання таймеру, як відлік пауз вже не потрібен;
  • void delayMs(volatile uint32_t delay) - функція відліку затримки в мілісекундах, на вхід ціле число мілісекунд
  • void delayUs(volatile uint32_t delay) - функція відліку затримки в мікросекундах, на вхід ціле число мікросекунд
В файлі delay.h обрати який таймер буде вести відлік часу. Можна обрати з TIM1, TIM2, TIM3 та TIM4. Як потрібен інший таймер, дописати в файлі delay.c необхідний таймер по аналогії.
Частота для правильного відліку часу береться зі змінної SystemCoreClock, це якщо частота шини таймеру співпадає з SystemCoreClock (дивись малюнок). Червоним обведено ключові місця:
Однакові частоти
Бувають випадки що частоти не співпадають, як, наприклад, на іншому малюнку:
Частоти не однакові
Як видно з малюнку, частота SYSCLK - 32MHz, а шина таймера на шині APB1 периферії вже тактується 8MHz. І шина таймерів на шині периферії APB2 тактується 16MHz. Це все завдяки дільникам і множникам шин APB. Тому будьте уважні і задавайте правильні частоти в герцах для таймерів в файлі delay.h. 
Звичайно можна визначити частоту автоматично за допомоги функцій драйверу HAL, наприклад такими як:
HAL_RCC_GetSysClockFreq(), HAL_RCC_GetHCLKFreq(), HAL_RCC_GetPCLK1Freq(), HAL_RCC_GetPCLK2Freq(), тощо.
Але ж хотілось відійти від високорівневих драйверів. Напевно є спосіб автоматизувати визначення частоти таймеру і без HAL чи SPL, але я ще не дійшов до цього :-)

delay.h


/*
 * my_delay.h
 *
 *  Created on: 20 груд. 2017 р.
 *      Author: Andriy
 */

#ifndef DELAY_H_
#define DELAY_H_

#include <stdint.h>

//Розкоментувати потрібний таймер для функцій затримки
#define TIMER1
// #define TIMER2
// #define TIMER3
// #define TIMER4

#ifdef TIMER1
#define CURRENT_TIMER               ((TIM_TypeDef *) TIM1)
#endif

#ifdef TIMER2
#define CURRENT_TIMER               ((TIM_TypeDef *) TIM2)
#endif

#ifdef TIMER3
#define CURRENT_TIMER               ((TIM_TypeDef *) TIM3)
#endif

#ifdef TIMER4
#define CURRENT_TIMER               ((TIM_TypeDef *) TIM4)
#endif

#define CURRENT_FREQ    SystemCoreClock

void delayInit(void);
void delayDeInit(void);
void delayMs(volatile uint32_t delay);
void delayUs(volatile uint32_t delay);

#endif /* DELAY_H_ */

delay.c


/*
 * delay.c
 *
 *  Created on: 20 груд. 2017 р.
 *      Author: Andriy
 */

#include <delay.h>
#include "stm32f1xx.h"

// Функція вмикання таймеру для потреб delay
void delayInit(void)
{
#ifdef TIMER1
 RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
#endif

#ifdef TIMER2
 RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
#endif

#ifdef TIMER3
 RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
#endif

#ifdef TIMER4
 RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;
#endif
}

// Функція вимикання таймеру як немає потреби в delay
void delayDeInit(void)
{
#ifdef TIMER1
 RCC->APB2ENR &= ~RCC_APB2ENR_TIM1EN;
#endif

#ifdef TIMER2
 RCC->APB1ENR &= ~RCC_APB1ENR_TIM2EN;
#endif

#ifdef TIMER3
 RCC->APB1ENR &= ~RCC_APB1ENR_TIM3EN;
#endif

#ifdef TIMER4
 RCC->APB1ENR &= ~RCC_APB1ENR_TIM4EN;
#endif
}

//Функція формування затримки в мілісекундах
void delayMs(volatile uint32_t delay)
{
 CURRENT_TIMER->PSC = CURRENT_FREQ/1000-1; //Встановлюємо подрібнювач
 CURRENT_TIMER->ARR = delay; //встановлюємо значення переповнювання таймеру, а також і значення при якому генеруеться подія оновлення
 CURRENT_TIMER->EGR |= TIM_EGR_UG; //Генерируемо Подію оновлення для запису даних в регістри PSC і ARR
 CURRENT_TIMER->CR1 |= TIM_CR1_CEN|TIM_CR1_OPM; //Запускаемо таймер записом биту CEN і встановлюємо режим Одного проходу встановленням біту OPM
 while ((CURRENT_TIMER->CR1) & (TIM_CR1_CEN!=0)); //Виконуємо цикл поки рахує таймер до нуля
}

//Функція формування затримки в мікросекундах
void delayUs(volatile uint32_t delay)
{
    CURRENT_TIMER->PSC = CURRENT_FREQ/1000000-1; ///Встановлюємо подрібнювач
 CURRENT_TIMER->ARR = delay; //встановлюємо значення переповнювання таймеру, а також і значення при якому генеруеться подія оновлення
 CURRENT_TIMER->EGR |= TIM_EGR_UG; //Генерируемо Подію оновлення для запису даних в регістри PSC і ARR
 CURRENT_TIMER->CR1 |= TIM_CR1_CEN|TIM_CR1_OPM; //Запускаемо таймер записом биту CEN і встановлюємо режим Одного проходу встановленням біту OPM
 while ((CURRENT_TIMER->CR1) & (TIM_CR1_CEN!=0)); //Виконуємо цикл поки рахує таймер до нуля
}

Як користуватись

Скопіюйте теку, або файли бібліотеки до теки свого проекту. Приєднайте до свого проекту теку, або файли бібліотеки. До головного файлу main.c додайте #include "delay.h". В файлі "delay.h" зазначте який таймер для відліку затримок будете використовувати. І при потребі, частоту шини таймерів в герцах. Коли потрібна затримка просто викличте одну із функцій delayMs(xxx) чи delayUs(xxx) - де xxx часова затримка в мілісекундах чи мікросекундах. Наприклад, delayMs(500) - означає почекаємо пів секунди. Та не забувайте перед викликом цих функцій увімкнути таймер викликом функції delayInit().

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

Завантажити архів з бібліотекою - download

середа, 2 листопада 2016 р.

Мій проект: Лічильник подій 4x4 (поточні, попередні, різниця)


Передмова

Цей допис роблю для себе, як документацію на пристрій. Пристрій робився на замовлення в одному екземплярі. Як кому згодиться то дуже добре.

Призначення

Зовнішній вигляд готового пристрою
Лічильник подій 4х4 фіксує і показує на екрані поточні показники лічильників, різницю між поточними показами лічильників і попереднім зняттям показів лічильників. Показання лічильників виводяться на чотироьохрозрядний семисегментний індикатор по черзі.
Слугує заміною застарілим електромеханічним лічильникам, які вже використали свій ресурс і працюють з великою похибкою.

Як користуватись

Після зняття показів всіх лічильників, натиснути кнопку для фіксації, що значення лічильників занесено до журналу і розрахунок різниці ведеться вже з поточних показів лічильників.

Схеми блоків

Блок фіксації події, гальванічної розв'язки та антибрязкіт.
Схема блоків оптичної розв'язки і антибрязкіт
Мікроконтролерний блок STM32F103C8T6.
Схема мікроконтролерного блоку
Блок індикації і кнопки.
Схема блока індикації і кнопки
Блок живлення.
Схема блоку живлення
Архів зі схемами. Живлення пристрою від акумуляторів 24 Вольта великої ємності в режимі "буфер". Зарядний пристрій до акумуляторів живиться від гарантованої мережі 220В. Додаткового резервного живлення не потребує.

Програмне забезпечення

Початкова ініціалізація периферії згенерована програмою CubeMX. Програма написана мовою C в засобі розробки CooCox CoIDE 1.7. Архів з проектом.

  • mxconstants.h - константи, структури
  • main.c - головна програма
  • stm32f1xx_it.c - обробник переривань
Дещо з коду під спойлером.



Прототипи функцій.
/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
static void DigitToPort (uint8_t digit); // виставляє в відповідні розряди індикатора числа-розряди.
static void ShowScreen(uint8_t* number, uint8_t number_of_digits, const uint16_t* pDigitPin); // Вивід числа на екран. Аргументи: масив з чисел-розрядів після функції SplitToDigit, та кількість розрядів.
static void SplitToDigit(uint32_t counter, uint8_t* digit, uint8_t number_of_digits); // число з лічильника розбиває на окремі розряди. вхідні аргументи: значення лічильника, масив для розрядів, кількість розрядів.
/* USER CODE END PFP */

Деякі змінні.
/* USER CODE BEGIN 2 */
uint8_t digit[NUM_DIGIT] = {}; // Масив для символів в кожен розряд індикатора окремо
static const uint16_t digitPin[] = {D0_Pin,D1_Pin,D2_Pin,D3_Pin};
/* USER CODE END 2 */

Функції для роботи 7-сегментного дисплею
//Функція відображення інформації на дисплей
static void ShowScreen(uint8_t* number, uint8_t number_of_digits, const uint16_t* pDigitPin)
{
 for(uint8_t i = 0; i < number_of_digits; i++)
 {
  IND_PORT -> BRR = (D0_Pin|D1_Pin|D2_Pin|D3_Pin);//Вимикаємо всі розряди
  IND_PORT -> BSRR = pDigitPin[i];//Вмикаємо потрібний розряд індикатора
  DigitToPort(*(number+i));//Виводимо цифру у потрібний розряд
  HAL_Delay(1);//Невеличка затримка. Хай цифра світиться якийсь час
 }
}


// число з лічильника розбиває на окремі розряди. вхідні аргументи: значення лічильника, масив для розрядів, кількість розрядів.
static void SplitToDigit(uint32_t counter, uint8_t* digit, uint8_t number_of_digits)
{
 uint32_t tmp = counter;

 for (uint8_t i = 0; i < number_of_digits; i++)
 {
  *(digit+i) = tmp % 10;
  tmp = tmp / 10;
 }

}


//Функція виставляє в порт потрібну цифру
static void DigitToPort (uint8_t digit)
{
 static const uint8_t digitsp[]={DIG0,DIG1,DIG2,DIG3,DIG4,DIG5,DIG6,DIG7,DIG8,DIG9,SEG_G_Pin,LITA,~DIG8,LITP,LITH,LITPE}; //оголошуємо масив з можливими варіантами символами на індикатор
 IND_PORT -> ODR &= ~DIG8; //Вимикаємо всі сегменти
 IND_PORT -> ODR |= digitsp[digit]; //Запалюємо потрібні
}

Виклик функції для виводу інформації на дисплей. Або в безкінечному циклі, або по перериванням.
ShowScreen(digit,sizeof(digit)/sizeof(digit[0]),digitPin);




Оголошуємо масиви. Створюємо структуру і оголошуємо масив структур.
bool flag_button[NUM_COUNTER] = {}; // Масив з прапорцями стану отримання імпульсу на лічильник

typedef struct
{
 uint32_t current;
 uint32_t previous;
}CounterTypeDef;

CounterTypeDef AI_Counter[NUM_COUNTER] = {}; // Масив з кількістью структур для лічильників

static const uint16_t ButtonPin[] = {AI_COUNTER1_Pin,AI_COUNTER2_Pin,AI_COUNTER3_Pin,AI_COUNTER4_Pin,
                            AI_COUNTER5_Pin,AI_COUNTER6_Pin,AI_COUNTER7_Pin,AI_COUNTER8_Pin,
                            AI_COUNTER9_Pin,AI_COUNTER10_Pin,AI_COUNTER11_Pin,AI_COUNTER12_Pin,
                            AI_COUNTER13_Pin,AI_COUNTER14_Pin,AI_COUNTER15_Pin,AI_COUNTER16_Pin};

static GPIO_TypeDef* ButtonPort[] = {AI_COUNTER1_GPIO_Port,AI_COUNTER2_GPIO_Port,AI_COUNTER3_GPIO_Port,AI_COUNTER4_GPIO_Port,
                            AI_COUNTER5_GPIO_Port,AI_COUNTER6_GPIO_Port,AI_COUNTER7_GPIO_Port,AI_COUNTER8_GPIO_Port,
                            AI_COUNTER9_GPIO_Port,AI_COUNTER10_GPIO_Port,AI_COUNTER11_GPIO_Port,AI_COUNTER12_GPIO_Port,
                            AI_COUNTER13_GPIO_Port,AI_COUNTER14_GPIO_Port,AI_COUNTER15_GPIO_Port,AI_COUNTER16_GPIO_Port};

В циклі опитуємо входи на подію. Якщо прапорець "flag_bounce" в true.
if (flag_bounce)
 {
  flag_bounce = false;
    for (uint8_t i = 0; i < NUM_COUNTER; i++)
     {
       if (!HAL_GPIO_ReadPin(ButtonPort[i],ButtonPin[i]) && !flag_button[i])
     {
           flag_button[i] = true;
           ++AI_Counter[i].current;
     }
     else if(HAL_GPIO_ReadPin(ButtonPort[i],ButtonPin[i]) && flag_button[i])
          {
           flag_button[i] = false;
          }
     }
   }

Увімкнено два таймери. Таймер TIM2 в перериванні скидає прапорець "flag_lock_screen" для зміни інформації на екрані що 3 секунди. Таймер TIM4 в перериванні встановлює прапорець "flag_bounce" для дозволу опитування входів на подію через кожні 25 мілісекунд (програмний антибрязкіт).

субота, 29 жовтня 2016 р.

STM32: Бібліотека для роботи з LCD WH1602(4) або іншим сумісним дисплеєм з контролером HD44780


upd 09.03.2019: Версія цієї бібліотеки для 4-х бітної шини і шини I2C знаходиться тут

Передмова

Ця бібліотека є вільним програмним забезпеченням і може вільно розповсюджується та змінюватись. Я не автор бібліотеки, а вніс зміни щодо автоматизації налаштувань, вніс зміни щодо більшої абстракції, а також усунув недолік, який псував все задоволення від роботи з цією бібліотекою, це випадкове "сміття" на екрані при увімкненні живлення. Щоб прибрати "сміття" потрібно було перезавантажувати пристрій кнопкою "reset". Бібліотека доволі вдала, як на мене, і має великий спектр функцій, щоб задовольнити будь які потреби, у відтворені інформації на дисплей типу WH1602 (WH1604) з контролером HD44780. Ця бібліотека створювалась початково, як кросплатформена для AVR, PIC, ARM. Чи збереглась кросплатформеність перевірити не маю змоги. Але на мікроконтролерах STM32F1xx і STM32F4xx перевірено - працює. В мережі можна зустріти різні варіації цієї бібліотеки, але набір функцій один і той самий. Зроблені мною зміни спрощують налаштування бібліотеки. В принципі налаштовувати взагалі нічого не потрібно. Ну може зовсім трішечки :-)

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

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

Схема підключення на малюнку. Підключати будемо по 4-х провідниковій схемі з заземленим сигналом W/R. Це означає що нам потрібно під'єднати до мікроконтролера шість дротів від дисплея. Такі як D4, D5, D6, D7, E, RS. До яких саме виводів мікроконтролеру під'єднувати дисплей, це вже вирішувати вам самим. Бібліотека допускає підключення кожного виводу дисплея на різні порти і будь які піни обраного/обраних порту/портів. Під'єднайте до дисплею живлення, це ніжка 2 - "+5 Вольт". Загальний мінус на ніжки дисплею 1 та 5. Та до ніжки дисплею під номером 3 резистивний дільник з підстроєчним резистором 10к для регулювання контрасту інформації, яка висвічується на екрані. Якщо ваш дисплей має підсвічування екрану, то можна ще подати живлення на підсвічування, це 15 і 16 ніжки дисплею. Живлення подати напряму, або через транзисторний ключ, який керується з ніжки мікроконтролеру. Тоді підсвічування можна вмикати та вимикати програмно, або навіть регулювати яскравість підсвічування налаштувавши ніжку, яка керує ключем підсвічування, як PWM (ШИМ).

STM32CubeIDE

Щоб бібліотека працювала правильно потрібно всім шпилькам, з якими буде з'єднано мікроконтролер та дисплей WH1602, дати назви (псевдоніми) за принципом:

  • Enable (E) - LCD_E
  • RS - LCD_RS
  • D4 - LCD_D4
  • D5 - LCD_D5
  • D6 - LCD_D6
  • D7 - LCD_D7

Обираєте зручні для вас, або вільні виводи мікроконтролеру. Виставляєте їх в режим "GPIO_Output" та називаєте за принципом, як зазначено вище. Приклад на малюнку. Червоними зонами позначив потрібне для підключення LCD:

Оголошення назв і режиму виводів для підключення LCD в STM32CubeIDE

Щодо налаштування LCD в то це все. Генеруєте код та перейдемо до додавання файлів, підключення бібліотеки та написання демо-коду.

Підключаємо бібліотеку до проекту

  1. Завантажте архів бібліотеки "hd44780.zip" собі на диск. В архіві знаходиться тека "hd44780" з підтеками "inc" та "src".
  2. Копіюємо файли бібліотеки до проекту. Можна скопіювати самі файли бібліотеки до проекту. Заголовні файли "h" у теку "inc" проекту, а сирцеві файли "c" до теки "src" проекту.

Передпроцесорні директиви #include 

Тепер, коли на рівні файлів все під'єднано як слід, треба в коді додати лиш один рядок в головний файл "main.c":

1
2
3
/* USER CODE BEGIN Includes */
#include "hd44780.h"
/* USER CODE END Includes */

Оце і все. Можна компілювати проект для перевірки. Як зроблено все правильно то компілятор скомпілює проект без помилок.

Список функцій бібліотеки

Бібліотека для своєї роботи і зручності користувача - має ряд функцій. Внутрішні функції розглядати не будемо, а розглянемо ті функції, що потрібні нам для виводу на дисплеї різноманітної інформації.

  • lcdInit - функція початкової ініціалізації дисплею;
  • lcdClrScr - функція очищення дисплею, курсор в початкове положення;
  • lcdReturn - функція повернення курсору в початкове положення;
  • lcdSetMode - функція встановлення режимів: дисплей увімк/вимк, курсор блимає увімк/вимк, курсор увімк/вимк;
  • lcdGoto - функція встановлює курсор в певне місце: рядок, стовпець;
  • lcdPuts - функція виводу текстового рядка;
  • lcdPutc - функція виводу окремого символу типу char;
  • lcdLoadChar - функція завантаження до пам'яті дисплею користувацького символу;
  • lcdDrawChar - функція малювання на дисплеї користувацького символу;
  • lcdBackSpace - функція зміщення курсору на один символ ліворуч і його знищення;
  • lcdScroll - функція зміщення всього тексту на один стовпець в потрібному напрямку (ліворуч або праворуч);
  • cursorShift - функція зміщення курсору на позицію праворуч чи ліворуч без знищення символу, що вже зображений. Призначена для редагування/виправлення тексту;
  • lcdItos - функція виводу на екран числа типу integer;
  • lcdFtos - функція виводу на екран числа типу float;
  • lcdNtos - функція виводу на екран числа типу integer з певним числом розрядів. Зайві розряди відсікаються. Розряди, яких не вистачає заповнюються нулями. Для фіксованого розташування на екрані;
  • lcdDrawBar - функція малювання індикатору виконання;
  • lcdClrBar - функція очищення індикатору виконання.

Список деяких параметрів бібліотеки

В файлі "lcd_cfg.h"

//-------------------------------
// DEFAULT CONFIGURATIONS
//-------------------------------
#define DEFAULT_DISPLAY_CONFIG  DISPLAY_CONFIG_4bit_2L_5x8
#define DEFAULT_ENTRY_MODE   ENTRY_MODE_INC_NO_SHIFT
#define DEFAULT_VIEW_MODE   VIEW_MODE_DispOn_BlkOff_CrsOff
#define USE_BUSY_FLAG    0u /* 1 (true) or 0 (false) */
  • дисплей під'єднано по 4-х бітному режиму, на 2 або 4 рядки, з шрифтом 5х8 пікселів;
  • зліва направо, без зсуву видимої частини DDR RAM пам'яті;
  • дисплей увімкнено, блимання вимкнуто, курсор вимкнуто;
  • чи використовується сигнал дисплею R/W, в нашому випадку - ні, тому 0.
#define MCU_FREQ_VALUE    SystemCoreClock/1000000 /* MHz. Minimal value = 1 MHz */

В змінній SystemCoreClock зберігається частота MCU в Герцах, ділимо на мільйон, отримуємо в мегаГерцах. Для правильних таймінгів дисплею.

//-------------------------------
// SET FORMATTED OUTPUT OPTIONS
//-------------------------------
#define USE_FORMATTED_OUTPUT   1u /* 1 (true) or 0 (false) */
#define TAB_SPACE     4u /* 1 .. 255 */
  • використовувати символи форматування /t, /n, /r в рядках з текстом - 1, не використовувати - 0;
  • довжина табуляції - 4.
//-------------------------------
// PROGRESS BAR OPTIONS
//-------------------------------
#define USE_PROGRESS_BAR   0u    /* 1 (true) or 0 (false) */
#define USE_REGRESS_BAR    1u    /* 1 (true) or 0 (false) */
#define PROGRESS_BAR_LINE   LCD_2nd_LINE /* Select lcd line: 1, 2, 3, 4, ... */
#define PROGRESS_BAR_HEIGHT   5u     /* in pixel: 1(min), 2, 3, 4, 5, 6, 7, 8(max) */
#define PROGRESS_BAR_WIDTH   10u    /* Number of chars in lcd line:  1, 2, .. , 8, 16, 20 */
  • використовувати індикатор виконання, що прогресує - 1, ні - 0;
  • використовувати індикатор виконання, що регресує - 1, ні -0;
  • індикатор виконання буде в другому рядку індикатора;
  • висота індикатора виконання в пікселях;
  • ширина індикатора в знакомісцях.

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

Замість того, щоб пояснювати в подробицях кожну функцію бібліотеки, це ви можете і самі знайти в коментарях самої бібліотеки, або в просторах інтернету. Наприклад тут. Краще приведу програму, яка наочно демонструє можливості бібліотеки.

Демо-код

На початку програми в файлі "main.c", оголошуємо прототип функції для імітації друкування тексту з клавіатури. Сама функцію розташуємо вже після головної функції "main":

/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
void imitationPrinting(char *ptr);
/* USER CODE END PFP */

Далі в тілі функції "main" до початку безкінечного циклу в блоці 1 - оголошуємо всі змінні які знадобляться для демонстрації:

/* USER CODE BEGIN 1 */
 uint32_t SystemFreq = HAL_RCC_GetHCLKFreq();
 char str[17] = {};
 float pi = 3.14;
 int t = 123;
 unsigned char men1[FONT_HEIGHT] = {0x0e,0x0e,0x04,0x1f,0x04,0x1a,0x01,0x00};
 unsigned char men2[FONT_HEIGHT] = {0x07,0x07,0x0a,0x07,0x02,0x05,0x09,0x00};
  /* USER CODE END 1 */

Ініціалізуємо наш дисплей в ділянці позначену як "USER CODE BEGIN 2":

/* USER CODE BEGIN 2 */
lcdInit(); // ініціалізація дисплею
/* USER CODE END 2 */

В безкінечному циклі пишемо нашу демонстраційну програму де я постарався використати всі функції бібліотеки, та написав коментар до кожного рядка:

  /* USER CODE BEGIN WHILE */
  while (1)
  {
   // очищення дисплею
   lcdClrScr();
   // встановлення позиції курсору
   lcdGoto(LCD_1st_LINE,1);
   //друкуємо текстовий рядок з переносом на інший рядок
   lcdPuts("CooCox CoIDE\nFreq MCU ");
   // друкуємо частоту мікроконтролеру в Mhz
   lcdItos(SystemFreq/1000000);
   lcdPuts(" Mhz");
   // чекаємо 3 секунди
   HAL_Delay(3000);
   /*
    * В циклі зміщуємо все що на екрані на 16 позицій праворуч
    */
   for (int i = 0; i < 16; ++i)
   {
    lcdScroll(RIGHT);
    HAL_Delay(250);
   }
   // очищаємо дисплей
   lcdClrScr();
   // до буферу str відправляємо текст
   sprintf(str,"STM32F103RB and");
   // функція імітації друкування тексту
   imitationPrinting(str);
   // до буферу str відправляємо текст
   sprintf(str,"LCD WH1602A Demo");
   // встановлення позиції курсору
   lcdGoto(LCD_2nd_LINE,0);
   // функція імітації друкування тексту
   imitationPrinting(str);
   // чекаємо 3 секунди
   HAL_Delay(3000);
   /*
    * В циклі зміщуємо все що на екрані на 16 позицій ліворуч
    */
   for (int i = 0; i < 16; ++i)
   {
    lcdScroll(LEFT);
    HAL_Delay(250);
   }
   // чекаємо секунду
   HAL_Delay(1000);
   /*
    * В циклі зміщуємо все що на екрані на 16 позицій праворуч
    */
   for (int i = 0; i < 16; ++i)
   {
    lcdScroll(RIGHT);
    HAL_Delay(250);
   }
   // чекаємо 2 секунди
   HAL_Delay(2000);
   // очищаємо дисплей
   lcdClrScr();
   // виводимо на екран текст
   lcdPuts("Float = ");
   // виводимо на екран число типу float з двома знаками після коми
   lcdFtos(pi,2);
   // встановлення позиції курсору
   lcdGoto(LCD_2nd_LINE,0);
   // виводимо на екран текст
   lcdPuts("Integer = ");
   // виводимо на екран число типу integer
   lcdItos(t);
   // чекаємо 3 секунди
   HAL_Delay(3000);
   // вмикаємо режим: дисплей увімкнутий, миготіння курсору вимкнуте, курсор увімкнутий
   lcdSetMode(VIEW_MODE_DispOn_BlkOff_CrsOn);
   // встановлення позиції курсору
   lcdGoto(LCD_2nd_LINE,16);
   // в циклі видаляємо весь рядок backspace'ом
   for (int i = 0; i < 16; ++i)
   {
    lcdBackSpace();
    HAL_Delay(100);
   }
   // встановлюємо позицію курсору
   lcdGoto(LCD_1st_LINE,16);
   // в циклі видаляємо весь рядок backspace'ом
   for (int i = 0; i < 16; ++i)
   {
    lcdBackSpace();
    HAL_Delay(100);
   }
   // встановлюємо режим: дисплей увімкнутий, миготіння вимкнуте, курсор вимкнутий
   lcdSetMode(VIEW_MODE_DispOn_BlkOff_CrsOff);
   // повертаємо курсор до дому
   lcdReturn();
   // друкуємо текст з табуляцією
   lcdPuts("\tLoading...");
   // встановлюємо позицію курсору
   lcdGoto(PROGRESS_BAR_LINE, PROGRESS_BAR_WIDTH);
   // друкуємо текст
   lcdPuts("[   %]");
   // в циклі показуємо індикатор виконання
   for(uint8_t i = 0; i <= PROGRESS_BAR_MAX_LOAD; ++i)
   {
    // встановлення позиції курсора
    lcdGoto(PROGRESS_BAR_LINE, PROGRESS_BAR_WIDTH + 1);
    // друкування відсотків виконання в чітко заданій позиції на три розряди
    lcdNtos((i*100)/PROGRESS_BAR_MAX_LOAD, 3 );
    // показуємо поточний стан індикатора виконання
    lcdDrawBar(i);
    // чекаємо 1/10 секунди
    HAL_Delay(100);
   }
   // чекаємо 3 секунди
   HAL_Delay(3000);
   // очищаємо дисплей
   lcdClrScr();
   /*
    * завантажуємо символ користувача до комірки пам'яті 6 і 7 в дисплей
    * комірки пам'яті від 0 по 5 зайняті індикатором виконання
    * якщо ви не використовуєте індикатор виконання, то можна завантажувати
    * з 0 комірки пам'яті дисплею
    */
   lcdLoadChar(men1,6);
   lcdLoadChar(men2,7);
   // друкуємо текст з табуляцією і переходом на новий рядок
   lcdPuts("\tRunning men\n");
   // в циклі показуємо людину що біжить :)))
   for(uint8_t i = 0; i < 8; i++)
   {
    lcdPutc(6);
    HAL_Delay(250);
    cursorShift(LEFT);
    lcdPutc(' ');
    lcdPutc(7);
    HAL_Delay(250);
    cursorShift(LEFT);
    lcdPutc(' ');
   }
   /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */

  }
  /* USER CODE END 3 */

Та не забуваємо про функцію, яку написали для демонстраційної програми і оголосили її прототип на початку програми. Функцію пишемо після головної функції "main" в головному файлі "main.c" в користувацькому блоці 4:

/* USER CODE BEGIN 4 */
// функція імітації друкування тексту з клавіатури
void imitationPrinting(char *ptr)
{
 // встановлюємо режим з увімкнутим курсором
 lcdSetMode(VIEW_MODE_DispOn_BlkOff_CrsOn);
 // поки не кінець рядку
 while(*ptr != 0)
 {
  // друкуємо поточний символ
  lcdPutc(*ptr++);
  // чекаємо 1/10 секунди
  HAL_Delay(100);
 }
 // встановлюємо режим з вимкнутим курсором
 lcdSetMode(VIEW_MODE_DispOn_BlkOff_CrsOff);
}
/* USER CODE END 4 */

Та не забуваємо, що в демонстраційній програмі використовуємо індикатор виконання "progress bar", а для цього  в файлі lcd_cfg.h константу USE_PROGRESS_BAR встановлюємо в 1u. "#define USE_PROGRESS_BAR    1u".

Без CubeMX

Підключення бібліотеки до проекту абсолютно таке саме як я описав вище. Створюєте проект для свого мікроконтролера в своєму засобі розробки. До файлу main.c додаєте рядок - #include "hd44780.h" в файлі бібліотеки lcd_cfg.h перевірте чи файл драйверу SPL чи HAL вказано правильно для вашого контролеру MCU, наприклад - #include "stm32f1xx.h".
А також потрібно потурбуватись самим про ініціалізацію виводів мікроконтролеру підключених до LCD. В кінці файлу "hd44780.c" є функція  "lcdGpioInit" в середині якої треба написати ініціалізацію виводів і портів, які під'єднано до дисплею.
Ось приклад, як це може виглядати, якщо виводи дисплею під'єднано до виводів мікроконтролеру PIN_0 - RS, PIN_1 - E, PIN_4...PIN_7 - D4...D7 порту "C":

void lcdGpioInit(void)
{
  GPIO_InitTypeDef GPIO_InitStruct;

  __HAL_RCC_GPIOC_CLK_ENABLE();

  GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7);
}

А на початку файлу "main.c" треба додати декілька #define, щоб бібліотека працювала правильно. Зверніть увагу! Програма CubeMX додає до назви виводів, які ми називаємо для підключення LCD, ще PIN і GPIO_Port. Наприклад, сигнал RS під'єднано до виводу мікроконтролера PC0, тоді потрібно директивами #define позначити pin - LCD_RS_Pin як GPIO_PIN_0, та port - LCD_RS_GPIO_Port як GPIOC. І так для всіх виводів до яких під'єднано LCD:

#define LCD_RS_Pin GPIO_PIN_0
#define LCD_RS_GPIO_Port GPIOC
#define LCD_E_Pin GPIO_PIN_1
#define LCD_E_GPIO_Port GPIOC
#define LCD_D4_Pin GPIO_PIN_4
#define LCD_D4_GPIO_Port GPIOC
#define LCD_D5_Pin GPIO_PIN_5
#define LCD_D5_GPIO_Port GPIOC
#define LCD_D6_Pin GPIO_PIN_6
#define LCD_D6_GPIO_Port GPIOC
#define LCD_D7_Pin GPIO_PIN_7
#define LCD_D7_GPIO_Port GPIOC

Ну а все інше так само як і з CubeMX. Додайте до головного файлу "main.c" демонстраційну програму яка вже наведена вище. Компілюєте, прошиваєте, спостерігаєте.

Що саме додав до бібліотеки

До файлу "lcd_cfg.h" додав макроси щоб автоматично налаштовувалась бібліотека під визначені користувачем порти і виводи мікроконтролеру, що під'єднані до дисплею. Дяка за ідею користувачу "voland" з українського форуму програмістів.

#define MASK2PIN(mask) ((mask&GPIO_PIN_0)?0:(mask&GPIO_PIN_1)?1:(mask&GPIO_PIN_2)?2:(mask&GPIO_PIN_3)?3: \
   (mask&GPIO_PIN_4)?4:(mask&GPIO_PIN_5)?5:(mask&GPIO_PIN_6)?6:(mask&GPIO_PIN_7)?7: \
   (mask&GPIO_PIN_8)?8:(mask&GPIO_PIN_9)?9:(mask&GPIO_PIN_10)?10:(mask&GPIO_PIN_11)?11: \
   (mask&GPIO_PIN_12)?12:(mask&GPIO_PIN_13)?13:(mask&GPIO_PIN_14)?14:(mask&GPIO_PIN_15)?15:0)

#define RS_PIN_NUMBER (MASK2PIN(LCD_RS_Pin))
#define E_PIN_NUMBER (MASK2PIN(LCD_E_Pin))
#define RW_PIN_NUMBER (MASK2PIN(LCD_RW_Pin))
#define D7_PIN_NUMBER (MASK2PIN(LCD_D7_Pin))
#define D6_PIN_NUMBER (MASK2PIN(LCD_D6_Pin))
#define D5_PIN_NUMBER (MASK2PIN(LCD_D5_Pin))
#define D4_PIN_NUMBER (MASK2PIN(LCD_D4_Pin))

Можливо якийсь компілятор неадекватно сприйме таку конструкцію з опціями оптимізації, але в мене з трьома різними компіляторами оброблялось все коректно. Майте на увазі.

Автоматично визначається робоча частота MCU:

#define MCU_FREQ_VALUE    SystemCoreClock/1000000 /* MHz. Minimal value = 1 MHz */

Змінив функцію початкової ініціалізації дисплею, додаванням послідовності команд 0x30, 0x30, 0x28, щоб перемикання в 4-х бітний режим було надійним. Початково, послідовності 0x30, 0x30 не було. З-за цього на екрані після подачі живлення з'являлось випадкове "сміття". Лікувалось перезавантаженням пристрою кнопкою "ресет". А це дещо не зручно.

/*! \brief Initialize the LCD.
 *  \note This library use the 4-bit interface. */
void lcdInit(void)
{
 /* GPIO initialization. */
 setAllPinsAsOutputs();
 /* LCD initialization. */
 lcdWrite(0x30);
 lcd10usDelay(BUSY_CYCLE_TIME);
 lcdWrite(0x30);
 lcd10usDelay(BUSY_CYCLE_TIME);
 lcdConfig(DEFAULT_DISPLAY_CONFIG);
 lcdSetMode(DEFAULT_VIEW_MODE);
 lcdSetMode(DEFAULT_ENTRY_MODE);
 lcdClrScr();
 lcdReturn();
 #if (USE_PROGRESS_BAR)
  lcdInitBar();
 #endif
}

Невеличкі поради

Як не будете в своєму проекті використовувати "progress bar", то в файлі "lcd_cfg.h" назначте константі USE_PROGRESS_BAR значення 0. Це заощадить вам чимало байтів пам'яті мікроконтролеру. Та вивільнить аж 6 комірок пам'яті під користувацькі символи в дисплеї. 

Відео приклад підключення і використання бібліотеки HD44780

Плата розробника STM32F103RB Nucleo. Засіб розробки True Studio.


Плата розробника STM32F407VG Discovery. Засіб розробки System Workbench for STM32.


Файли з бібліотекою і проектами

середа, 14 вересня 2016 р.

STM32: Експорт проекту з mbed до засобу розробки CooCox IDE

Опис експорту в малюнках

Онлайн засіб розробки це добре, бо маєш доступ до своїх проектів з будь якого комп'ютера на будь якій операційній системі, що має доступ до інтернету. Але буває потрібно попрацювати і в оффлайн.
Онлайн засіб розробки mbed дозволяє експортувати створені в ньому проекти до інших IDE, таких як (на час написання цієї статті): Keil uVision4, CooCox CoIDE, System Workbench for STM32, IAR System, тощо.
Давайте спробуємо експортувати, для прикладу, будь який робочий проект з mbed до CooCox CoIDE і перевірити вже там як компілюється, прошивається і працює наш проект. CooCox CoIDE взятий для прикладу, принцип експорту один й той самий і для інших IDE.
Я за приклад візьму проект з попередньої статті "Підключення 1602 LCD Keypad Shield до STM32F103 Nucleo в засобі розробки mbed". Заходжу до компілятора mbed. З списку проектів, що в вікні ліворуч "Program Workspace" обираю проект "LCD_KeyPad_Shield", та в вікні "Program Details", що праворуч тисну "Export":
Обираємо і експортуємо
Далі відкриється вікно де потрібно обрати ваш засіб розробки зі списку. Я обираю CooCox CoIDE:
Обираємо засіб розробки
Далі тиснемо кнопку "Export" і в теку для завантажень отримуємо zip архів з проектом "LCD_Keypad_Shield_coide_nucleo_f103rb.zip". Копіюємо вміст архіву, теку "LCD_Keypad_Shield" до теки "workspace" нашого CooCox CoIDE.
Тепер запустимо засіб розробки CooCox CoIDE і в ньому відкриємо експортований проект. Отакої, всі коментарі кирилицею - спотворено. Але то нічого, сама програма від того не постраждала. Компілюємо. Прошиваємо до мікроконтролеру. РаДіємо що все працює. 

Відео опис експорту

середа, 7 вересня 2016 р.

STM32: Тестер батарейок з STM32F103 Nucleo та 1602 LCD Keypad Shield

Для ознайомлення з такою периферією мікроконтролера як АЦП (ADC) зробімо простенький прилад "Тестер батарейок 1.5 В і акумуляторів 1.2 В". Який буде вимірювати напругу на батарейці чи акумуляторі і виводити на екран інформацію про напругу в вольтах чи мілівольтах. А також приблизну інформацію про стан батарейки чи акумулятора: чудовий, середній, поганенький, розряджена.

Матеріали і засоби

  1. Плата розробника серії Nucleo (наприклад STM32F103 Nucleo)
  2. Плата розширення LCD Keypad Shield з дисплеєм LCD WH1602 (HD44780)
  3. Будь яка контактна макетна плата breadboard (наприклад така)
  4. Один резистор 1.8 - 2.2 КОм
  5. Чотири з'єднувальні дроти два такі і два такі
  6. Засіб розробки  "mbed compiler workspace" (знайомство з mbed тут)

Схема

Схема дуже проста. Плату розширення LCD Keypad Shield з'єднуєте "бутербродом" з платою розробника STM32F103 Nucleo відповідними роз'ємами Arduino connector. Як вже робили в попередній статті "Підключення 1602 LCD Keypad Shield до STM32F103 Nucleo в засобі розробки mbed".
Між землею і виводом А1 поставити резистор на 1.8 - 2 кОм. І вивести дроти на батарейку, або на тримач батарейок, якщо є в наявності, як показано на малюнку.
Схема підключення

Програма

Програма трішки нагадує програму з попередньої статті "Підключення 1602 LCD Keypad Shield до STM32F103 Nucleo в засобі розробки mbed". В текст програми додано детальні коментарі. Додаткових пояснень не потребує. Константу "const int Vref" треба підібрати відповідно до опорної напруги. Якщо є можливість заміряти напругу 3.3 В на платі розробника точним приладом у мілівольтах - це і буде константа. Як ні, то достатньо приблизно вказати "3330". Все одно це більш іграшка для ознайомлення, чим серйозний прилад.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#include "mbed.h"
#include "freetronicsLCDShield.h"

// Оголошуємо клас freetronicsLCDShield з назвою lcdKeypad,
// та вказуємо всі потрібні піни для роботи
freetronicsLCDShield lcdKeypad(D8, D9, D4, D5, D6, D7, D3, A0); 
//Оголошуємо аналоговий вхід для тестування батарейок
AnalogIn analog_value(A1);

int main()
{
    /* оголошуємо змінну типу float для значень з аналогового
       входу A0, куди підключений keypad */
    float val_button;
    /* оголошуємо змінну типу float для значень з аналогового
       входу A1, на якому будемо тестувати батарейки */
    float meas;
    /* Так як значення зчитуємо з АЦП в межах від 0 до 1, то напруга яку можна
        поміряти 3.3 Вольта це без дільника напруги */
    const int Vref = 3393; // опорна напруга в мілівольтах (для точності підбирається)
    // прапорець - виводити на екран виміряну напругу у вольтах чи мілівольтах
    bool flag_voltage = false;
    // прапорець - міряємо батарейку 1.5В або акумулятор 1.2В
    bool flag_battery = true;
    
    lcdKeypad.cls(); // Очищаємо дисплей
    
    while(1)
    {
        /*Зчитуємо значення на аналоговому вході A0.
        Так як значення в межах від 0 до 1, то множимо на 1000*/
        val_button = lcdKeypad.readButton()*1000;
        // Зчитуємо значення на аналоговому вході A1.
        meas = analog_value.read();
        
        if (flag_battery)
        {
            // Встановлюємо курсор в позицію 0,0 
            lcdKeypad.setCursorPosition(0, 0);
            lcdKeypad.printf("Battery: ");
            
            if (meas == 0)
            {
                // Якщо значення 0 то нічого не під'єднано
                lcdKeypad.printf("No Batt");
            }
            
            if (meas * Vref > 1400)
            {
                // Якщо значення більше 1.4В то батарейка в доброму стані
                lcdKeypad.printf("Perfect");
            }
            else if (meas * Vref < 1400 && meas * Vref > 1100)
            {
                // Якщо значення між 1.1В і 1.4В то батарейка середньо розряджена
                lcdKeypad.printf("Middle ");
            }
            else if (meas * Vref < 1100 && meas * Vref > 950)
            {
                // Якщо значення між 0.95В і 1.1В то батарейка майже розряджена
                lcdKeypad.printf("Badly  ");
            }
            else if (meas * Vref < 950)
            {
                // Якщо значення менше 0.95В то батарейка розряджена
                lcdKeypad.printf("Died   ");
            }
            // Встановлюємо курсор в позицію 1,0 
            lcdKeypad.setCursorPosition(1, 0);
            lcdKeypad.printf("Vbat = ");
        }
        else
        {
            // Встановлюємо курсор в позицію 0,0 
            lcdKeypad.setCursorPosition(0, 0);
            lcdKeypad.printf("Accum  : ");
            
            if (meas == 0)
            {
                // Якщо значення 0 то нічого не під'єднано
                lcdKeypad.printf("No Acc ");
            }
            
            if (meas * Vref > 1150)
            {
                // Якщо значення більше 1.15В то акумулятор в доброму стані
                lcdKeypad.printf("Perfect");
            }
            else if (meas * Vref < 1150 && meas * Vref > 1100)
            {
                // Якщо значення між 1.1В і 1.15В то акумулятор середньо розряджений
                lcdKeypad.printf("Middle ");
            }
            else if (meas * Vref < 1100 && meas * Vref > 1000)
            {
                // Якщо значення між 1В і 1.1В то акумулятор майже розряджений
                lcdKeypad.printf("Badly  ");
            }
            else if (meas * Vref < 1000)
            {
                // Якщо значення менше 1В то акумулятор розряджений
                lcdKeypad.printf("Died   ");
            }
            
            // Встановлюємо курсор в позицію 1,0 
            lcdKeypad.setCursorPosition(1, 0);
            lcdKeypad.printf("Vacc = ");
        }
        
        if (flag_voltage)
        {
            /*Так як значення meas в межах від 0 до 1, а опорна напруга має 3300 mV
            то множимо на 3300 mV (для точності треба заміряти точним приладом)*/
            meas = meas * Vref;
            // Друкуємо результат вимірювань
            lcdKeypad.setCursorPosition(1, 7);
            lcdKeypad.printf("%.0f mV     ", meas);
        }
        else
        {
            /*Так як значення meas в межах від 0 до 1, а опорна напруга має 3.3 V
            то множимо на 3.3 V (для точності треба заміряти точним приладом)*/
            meas = meas * Vref / 1000;
            // Друкуємо результат вимірювань
            lcdKeypad.setCursorPosition(1, 7);
            lcdKeypad.printf("%.2f V     ", meas);
        }
        
        // Перевіряємо яку кнопку натиснуто
        if (val_button < 50)
        {
            // встановлюємо прапорець для тестування батарейок
            flag_battery = true;
        }
        else if (val_button < 250)
        { 
            // встановлюємо прапорець щоб показувати напругу в мілівольтах
            flag_voltage = true;
        } 
        else if (val_button < 500)
        { 
            // встановлюємо прапорець щоб показувати напругу в вольтах
            flag_voltage = false;
        } 
        else if (val_button < 800)
        { 
            // встановлюємо прапорець для тестування акумуляторів 
            flag_battery = false;
        } 
    }
}
Створіть нову пусту програму в mbed, додайте до проекту бібліотеки "mbed.h" і "freetronicsLCDShield.h". Як це зробити в статті  "Підключення 1602 LCD Keypad Shield до STM32F103 Nucleo в засобі розробки mbed". Скопіюйте цю програму до редактора. Компілюєте, завантажений файл копіюєте чи переносите до диску "Nucleo". Все! Тестер батарейок готовий.
Тестер батарейок

Як користуватись

При увімкнені, за замовчуванням, тестуємо батарейки у Вольтах. Кнопка догори "UP" - міряємо у мілівольтах, кнопка вниз "DOWN" - міряємо у Вольтах. Кнопка ліворуч "LEFT" - міряємо акумулятор 1.2В, кнопка праворуч "RIGTH" - батарейку 1.5В. Міряти можна виключно елементи живлення до 3.3 Вольта. Висока напруга може вивести мікроконтролер з ладу.