Показ дописів із міткою 7-segment display. Показати всі дописи
Показ дописів із міткою 7-segment display. Показати всі дописи

вівторок, 4 червня 2019 р.

STM32: Бібліотека для 7-сегментного 8 розрядного дисплею на MAX7219 по SPI шині


Передмова

Мабуть самий з найпоширеніших дисплеїв в радіоаматорскій практиці є семисегментний світлодіодний дисплей. Це й простота і дешевизна. Але й є недоліки. Це інформаційне обмеження щодо варіантів символів, які можна зобразити і велика кількість ніжок на мікроконтролері (7 або 8 ніжок для сегментів плюс на кожен розряд по 1 ніжці мікоконтролера).

За допомоги мікросхеми MAX7219 можна усунути недолік з великою кількістю ніжок. За допомоги однієї мікросхеми MAX7219 можна керувати 8 розрядним 7-ми сегментним дисплеєм, плюс режим декодування цифр, плюс регулювання яскравості свічення дисплею, плюс не потрібно турбуватись про динамічну індикацію. А данні до мікросхеми надсилати по шині SPI, яка обмежена швидкістю в 10 Мбіт/сек.

В цій статті розглянемо невеличку бібліотеку для зручного друку інформації на семисегментний дисплей з мікросхемою MAX7219:
  • Очищення дисплею
  • Встановлення рівню яскравості
  • Надсилання даних на MAX7219
  • Вмикання/вимикання дисплею
  • Встановлення/скидання режиму декодування
  • Друк цифр (символів) в заданому розряді
  • Друк чисел типу INT та FLOAT

Залізяччя

Для прикладу буду використовувати такі матеріали:

Створюємо проект в STM32CubeMX або STM32CubeIDE

Обираємо свій мікроконтролер або дошку. Та налаштовуємо проект.
Налаштування проекту в Cube
  1. Тиснемо RCC -> High Speed Clock (HSE) -> Crystal/Ceramic Resonator
  2. SYS -> Debug -> Serial Wire
  3. Тиснемо SPI1
  4. Обираємо шпильку Chip Select -> наприклад PB0, обираємо режим GPIO_OUTPUT, та називаємо її CS_MAX7219
  5. Обираємо режим для SPI1 -> Transmit only master
  6. Слідкуємо щоб Baud Rate не перевищував 10 MBits/sec, якщо перевищує збільшіть Prescaler (for Baud Rate)
Генеруємо проект та переходимо до підключення дисплейного модуля до мікроконтролера, завантаження бібліотеки та написання демо-коду.

Підключення дисплейного модуля до мікроконтролера

STM32F103C8T6 Дисплейний модуль
Vcc 3.3V
Vcc
GND
GND
PA7 SPI1_MOSI
DIN
PB0 CS_MAX7219
CS
PA5 SPI1_SCK
CLK

Бібліотека MAX7219

Завантажити бібліотеку можна з GitHub. Бібліотека складається з двох файлів: max7219.h та max7219.c та має такі функції:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void max7219_Init(uint8_t intensivity);
void max7219_SetIntensivity(uint8_t intensivity);
void max7219_Clean(void);
void max7219_SendData(uint8_t addr, uint8_t data);
void max7219_Turn_On(void);
void max7219_Turn_Off(void);
void max7219_Decode_On(void);
void max7219_Decode_Off(void);
void max7219_PrintDigit(MAX7219_Digits position, MAX7219_Numeric numeric, bool point);
MAX7219_Digits max7219_PrintItos(MAX7219_Digits position, int value);
MAX7219_Digits max7219_PrintNtos(MAX7219_Digits position, uint32_t value, uint8_t n);
MAX7219_Digits max7219_PrintFtos(MAX7219_Digits position, float value, uint8_t n);
  1. Ініціалізація дисплею. На вхід "рівень яскравості" (0x0 - 0xF);
  2. Встановлення рівня яскравості. На вхід "рівень яскравості" (0x0 - 0xF);
  3. Очищення дисплею;
  4. Надсилання в шину SPI до MAX7219 даних. На вхід: адреса, дані;
  5. Увімкнення дисплею;
  6. Вимкнення дисплею;
  7. Увімкнути режим декодування;
  8. Вимкнути режим декодування;
  9. Друк цифри (символу). На вхід: позиція (1 - 8), цифра (0 - 9), точка (true/false);
  10. Друк числа типу INT. На вхід: стартова позиція числа на дисплеї, число INT. На виході: поточна позиція курсору;
  11. Друк числа типу INT з фіксованою кількістю розрядів. На вхід: стартова позиція числа на дисплеї, число INT, кількість розрядів. На виході: поточна позиція курсору;
  12. Друк числа типу FLOAT. На вхід: стартова позиція числа на дисплеї, число FLOAT, кількість знаків після коми (не більше 4-х). На виході: поточна позиція курсору;

Демо-код

Відкриваємо свій засіб розробки та експортуємо, згенерований CubeMX або CubeIDE, код. 
Копіюємо файли бібліотеки до проекту. Файли з розширенням *.h до теки INC, файли з розширенням *.c до теки SRC.
Відкриваємо файл max7219.h та налаштовуємо бібліотеку:
  • Кількість розрядів #define NUMBER_OF_DIGITS 8
  • Порт SPI до якого приєднано дисплейний модуль #define SPI_PORT hspi1
Відкриваємо файл main.c і в секції USER CODE Includes вкладаємо нашу бібліотеку:
/* USER CODE BEGIN Includes */
#include "max7219.h"
/* USER CODE END Includes */

В користувацьку секцію 2 додаємо ініціалізацію дисплею з встановленим рівнем яскравості 7 і вмикаємо режим декодування цифр:
/* USER CODE BEGIN 2 */
  max7219_Init(7);
  max7219_Decode_On();
  /* USER CODE END 2 */

В безкінечному циклі друкуємо від'ємне число Pi. Та слово HELP. Чекаємо 1.5 секунди, очищаємо дисплей та друкуємо число 765 на 4-ри розряди (додається незначащий нуль), та ціле число 321. Знову чекаємо 1.5 секунди і все з початку.
/* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
   max7219_Clean();
   max7219_PrintFtos(DIGIT_8, -3.14, 2);
   max7219_PrintDigit(DIGIT_4, LETTER_H, false);
   max7219_PrintDigit(DIGIT_3, LETTER_E, false);
   max7219_PrintDigit(DIGIT_2, LETTER_L, false);
   max7219_PrintDigit(DIGIT_1, LETTER_P, false);
   HAL_Delay(1500);
   max7219_Clean();
   max7219_PrintNtos(DIGIT_8, 765, 4);
   max7219_PrintItos(DIGIT_3, 321);
   HAL_Delay(1500);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
Щоб друкувати довільні символи, то треба вимкнути режим декодування цифр і в масив static uint8_t SYMBOLS[] файлу max7219.c додати ці символи. А в нумерований список MAX7219_Numeric в файлі max7219.h додати позначення і порядковий номер в масиві - нового символу.

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

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

середа, 8 лютого 2017 р.

STM32: Бібліотека для 7-ми сегментного дисплею


Передмова

Семисегментний дисплей, мабуть, найдешевший з дисплеїв. І його застосування в деяких проектах цілком виправдане. З недоліків, це мала інформативність (тільки цифри і деякі знаки), та займає багато ніжок мікроконтролера (7 ніжок на сегменти + 1 ніжка "кома" + по одному виводу мікроконтролера на кожен розряд дисплею).

Бібліотека проста в застосуванні та налаштуванні. Може працювати з 1 - 9 розрядним дисплеєм. Можна при необхідності додати розрядність по аналогії (якщо це комусь може знадобитись). Бібліотека створювалась під свої потреби і має обмежений "сервіс". Але при необхідності можна додати функцій. Наприклад, рядок цифр що біжить (scroll лівий, правий), вивід чисел типу float, тощо. Також майте на увазі, що сегменти дисплею мають бути під'єднані до виводів якогось одного порту мікроконтролера, а розряди дисплею вже по бажанню/можливостям/зручностям, до будь яких портів і виводів мікроконтролеру.

Як приклад, розглянемо схему підключення, налаштування та роботу з чотирьох розрядним 7-сегментним дисплеєм з загальним катодом. Підключати будемо до плати розробника STM32VLDiscovery на мікроконтролері STM32F100RB. Але у вас може бути будь який інший мікроконтролер.

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

На схемі вказано до яких ніжок мікроконтролера під'єднано дисплей. Це для прикладу, який будемо розглядати далі. А ви можете під'єднувати до виводів мікроконтролеру по своїм міркуванням. Головне щоб сегменти дисплею (A,B,C,D,E,F,G і H (DP)) знаходились на одному порту мікроконтролера (GPIOA, GPIOB, GPIOC, GPIOD, тощо). Тисніть на малюнок для збільшення.
Схема підключення 7-сегментного дисплею

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

  • void LED7SegInit(void) - функція ініціалізації дисплею. Викликати, якщо проект не створювався за допомоги CUBEMX. Потрібно внести корективи у функцію, щоб відповідало до яких портів і виводів під'єднаний дисплей. Якщо проект створювався за допомоги CUBEMX, де вказані ніжки до яких під'єднаний дисплей, то викликати цю функцію не треба взагалі. CUBEMX сам згенерує ініціалізацію потрібних ніжок і портів.
  •  void LED7SegPrint(uint32_t incomingNumber) - функція друку цілого позитивного числа. Вхідні параметри: ціле позитивне число.
  • void LED7SegPrintSeparate(uint8_t* aDigit, uint8_t number_of_digits) - функція друку чисел (або знаків, таких як "мінус", або взагалі "пусто") в кожен розряд окремо. Вхідні параметри: вказівник на масив з чисел (знаків), та кількість розрядів дисплею.
  • void LED7SegShow(void) - ця функція відповідає саме за світіння потрібних сегментів і розрядів дисплею (динамічна індикація). Цю функцію потрібно викликати як можна частіше. Розмістити її в головному безкінечному циклі, та потурбуватись, щоб виклик цієї функції ніщо не затримувало. Або створити окреме переривання по таймеру для виклику цієї функції. Або, якщо використовувати RTOS, створити окрему задачу для виклику цієї функції з високим пріоритетом.

LED7Seg.h

Заголовний файл LED7Seg.h містить підключення файлу CMSIS периферії вашого мікроконтролеру в прикладі це #include "stm32f1xx.h". Визначення назв ніжок і портів до яких під'єднано дисплей, якщо це не визначено у файлі main.h за допомоги CUBEMX. Визначено з яких сегментів складаються цифри на дисплеї. Деякі знаки, які можна відтворити на дисплеї. Кількість розрядів дисплею. Прототипи функцій.
/*
 * Автор: Андрій Гончаренко
 * Email: taburyak@gmail.com
 * м.Дніпро
 * 2017
 * Бібліотека у вільному розповсюдженні
 */

#ifndef _LED7SEG_H
#define _LED7SEG_H

// підключити файл з вашою серією мікроконтролера
#include "stm32f1xx.h"

// якщо не визначили ніжки і порти до яких під'єднано дисплей у файлі main.h за допомоги CUBEMX,
// то треба ніжки і порти позначити тут відповідно до електричної схеми підключення дисплею
#if !defined SEG_A_Pin
 #define SEG_A_Pin        GPIO_PIN_0
 #define SEG_A_GPIO_Port  GPIOB
#endif

#if !defined SEG_B_Pin
 #define SEG_B_Pin        GPIO_PIN_1
 #define SEG_B_GPIO_Port  GPIOB
#endif

#if !defined SEG_C_Pin
 #define SEG_C_Pin        GPIO_PIN_2
 #define SEG_C_GPIO_Port  GPIOB
#endif

#if !defined SEG_D_Pin
 #define SEG_D_Pin        GPIO_PIN_3
 #define SEG_D_GPIO_Port  GPIOB
#endif

#if !defined SEG_E_Pin
 #define SEG_E_Pin        GPIO_PIN_4
 #define SEG_E_GPIO_Port  GPIOB
#endif

#if !defined SEG_F_Pin
 #define SEG_F_Pin        GPIO_PIN_5
 #define SEG_F_GPIO_Port  GPIOB
#endif

#if !defined SEG_G_Pin
 #define SEG_G_Pin        GPIO_PIN_6
 #define SEG_G_GPIO_Port  GPIOB
#endif

#if !defined SEG_H_Pin
 #define SEG_H_Pin        0u
 #define SEG_H_GPIO_Port  0u
#endif

#if !defined D0_Pin
 #define D0_Pin         GPIO_PIN_8
 #define D0_GPIO_Port   GPIOB
#endif

#if !defined D1_Pin
 #define D1_Pin         GPIO_PIN_9
 #define D1_GPIO_Port   GPIOB
#endif

#if !defined D2_Pin
 #define D2_Pin         GPIO_PIN_6
 #define D2_GPIO_Port   GPIOC
#endif

#if !defined D3_Pin
 #define D3_Pin         GPIO_PIN_6
 #define D3_GPIO_Port   GPIOA
#endif

#if !defined D4_Pin
 #define D4_Pin         0u
 #define D4_GPIO_Port   0u
#endif

#if !defined D5_Pin
 #define D5_Pin         0u
 #define D5_GPIO_Port   0u
#endif

#if !defined D6_Pin
 #define D6_Pin         0u
 #define D6_GPIO_Port   0u
#endif

#if !defined D7_Pin
 #define D7_Pin         0u
 #define D7_GPIO_Port   0u
#endif

#if !defined D8_Pin
 #define D8_Pin         0u
 #define D8_GPIO_Port   0u
#endif

// складаємо з окремих сегментів відповідні цифри
#define DIG0   ( SEG_A_Pin | SEG_B_Pin | SEG_C_Pin | SEG_D_Pin | SEG_E_Pin | SEG_F_Pin )
#define DIG1   ( SEG_B_Pin | SEG_C_Pin )
#define DIG2   ( SEG_A_Pin | SEG_B_Pin | SEG_G_Pin | SEG_E_Pin | SEG_D_Pin )
#define DIG3   ( SEG_A_Pin | SEG_B_Pin | SEG_G_Pin | SEG_C_Pin | SEG_D_Pin )
#define DIG4   ( SEG_F_Pin | SEG_G_Pin | SEG_B_Pin | SEG_C_Pin )
#define DIG5   ( SEG_A_Pin | SEG_F_Pin | SEG_G_Pin | SEG_C_Pin | SEG_D_Pin )
#define DIG6   ( SEG_A_Pin | SEG_C_Pin | SEG_D_Pin | SEG_E_Pin | SEG_F_Pin | SEG_G_Pin )
#define DIG7   ( SEG_A_Pin | SEG_B_Pin | SEG_C_Pin )
#define DIG8   ( SEG_A_Pin | SEG_B_Pin | SEG_C_Pin | SEG_D_Pin | SEG_E_Pin | SEG_F_Pin | SEG_G_Pin )
#define DIG9   ( SEG_A_Pin | SEG_B_Pin | SEG_C_Pin | SEG_D_Pin | SEG_F_Pin | SEG_G_Pin )
// крапка
#define POINT   ( SEG_H_Pin )
// складемо всі сегменти в купу
#define ALL_SEG ( SEG_A_Pin | SEG_B_Pin | SEG_C_Pin | SEG_D_Pin | SEG_E_Pin | SEG_F_Pin | SEG_G_Pin | SEG_H_Pin )
// порядковий номер в масиві digitsp знаку чи символу
#define NOP  10   // знак "пусто" всі сегменти відключено
#define MINUS  11 // знак - "мінус"

// всі сегменти мають бути під'єднано до одного порту і тут визначається загальний для сегментів порт
#define SEG_PORT SEG_A_GPIO_Port

// кількість розрядів дисплею
#define NUM_DIGIT 4

// Ініціалізація периферії дисплею
void LED7SegInit(void);
// Надрукувати число на дисплеї
void LED7SegPrint(uint32_t incomingNumber);
// Надрукувати числа в дисплей для кожного розряду окреме число
void LED7SegPrintSeparate(uint8_t* aDigit, uint8_t number_of_digits);
// // Запалювання потрібних сегментів та розрядів дисплею (динамічна індикація)
void LED7SegShow(void);
// виставляє в відповідні розряди індикатора числа-розряди
void digitToPort (uint8_t digit);
// число з лічильника розбиває на окремі розряди. вхідні аргументи: значення лічильника, масив для розрядів, кількість розрядів.
void splitToDigit(uint32_t counter, uint8_t* digit, uint8_t number_of_digits);

#endif
 
/*--------------------------------------------------------------------------------*/
//  ENF OF FILE
/*--------------------------------------------------------------------------------*/

LED7Seg.c

Файл LED7Seg.c містить вкладення заголовного файлу, масиви і змінні та самі функції бібліотеки.
/*
 * Автор: Андрій Гончаренко
 * м.Дніпро
 * 2017
 * Бібліотека у вільному розповсюдженні
 */

#include <led7seg.h>
 
// Масив для числа окремо цифра в кожен розряд індикатора
static uint8_t digit[NUM_DIGIT] = {};
// оголошуємо масив з назвами ніжок куди під'єднані розряди індикатора
static const uint16_t digitPin[] = {D0_Pin, D1_Pin, D2_Pin,
                                    D3_Pin, D4_Pin, D5_Pin,
                                    D6_Pin, D7_Pin, D8_Pin};
// оголошуємо масив з назвами портів куди під'єднані розряди індикатора
static GPIO_TypeDef* digitPort[] = {D0_GPIO_Port, D1_GPIO_Port, D2_GPIO_Port,
                                    D3_GPIO_Port, D4_GPIO_Port, D5_GPIO_Port,
                                    D6_GPIO_Port, D7_GPIO_Port, D8_GPIO_Port};
//оголошуємо масив з можливими варіантами символами на індикатор
static const uint16_t digitsp[]={DIG0,DIG1,DIG2,DIG3,DIG4,DIG5,DIG6,DIG7,DIG8,DIG9,~ALL_SEG,SEG_G_Pin};
//Функції ініціалізації та виводу інформації на дисплей

//Ініціалізація периферії до якої підключено дисплей
void LED7SegInit(void)
{
 GPIO_InitTypeDef GPIO_InitStruct;

   /* GPIO Ports Clock Enable */
   __HAL_RCC_GPIOB_CLK_ENABLE();

   /*Configure GPIO pin Output Level */
   HAL_GPIO_WritePin(GPIOB, SEG_A_Pin|SEG_B_Pin|SEG_C_Pin|SEG_D_Pin
                           |SEG_E_Pin|SEG_F_Pin|SEG_G_Pin|SEG_H_Pin
                           |D0_Pin|D1_Pin|D2_Pin|D3_Pin, GPIO_PIN_RESET);

   /*Configure GPIO pins : SEG_A_Pin SEG_B_Pin SEG_C_Pin SEG_D_Pin
                           SEG_E_Pin SEG_F_Pin SEG_G_Pin SEG_H_Pin
                            D0_Pin D1_Pin D2_Pin D3_Pin*/
   GPIO_InitStruct.Pin = SEG_A_Pin|SEG_B_Pin|SEG_C_Pin|SEG_D_Pin
                        |SEG_E_Pin|SEG_F_Pin|SEG_G_Pin|SEG_H_Pin
                        |D0_Pin|D1_Pin|D2_Pin|D3_Pin;
   GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
   GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

}

// Надрукувати число на дисплеї
void LED7SegPrint(uint32_t incomingNumber)
{
 splitToDigit(incomingNumber, digit, NUM_DIGIT);
}

// Надрукувати числа в дисплей для кожного розряду окреме число
void LED7SegPrintSeparate(uint8_t* aDigit, uint8_t number_of_digits)
{
 for (uint8_t i = 0; i < number_of_digits; i++)
  {
   digit[i] = *(aDigit+i);
  }
}

// Запалювання потрібних сегментів та розрядів дисплею (динамічна індикація)
void LED7SegShow(void)
{
 for(uint8_t i = 0; i < sizeof(digit)/sizeof(digit[0]); i++)
 {
  for(uint8_t i = 0; i < NUM_DIGIT; i++)
  {
   digitPort[i] -> BRR = digitPin[i]; //Вимикаємо всі розряди
  }
  digitPort[i] -> BSRR = digitPin[i]; //Вмикаємо потрібний розряд індикатора
  digitToPort(*(digit+i));               //Виводимо цифру у потрібний розряд
  HAL_Delay(1);                          //Невеличка затримка. Хай цифра світиться якийсь час
 }
}

// Локальні, внутрішні функції

//Функція виставляє в порт потрібну цифру
void digitToPort (uint8_t digit)
{
 SEG_PORT -> ODR &= ~ALL_SEG; //Вимикаємо всі сегменти
 SEG_PORT -> ODR |= digitsp[digit]; //Запалюємо потрібні
}

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

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

}

/*--------------------------------------------------------------------------------*/
//  ENF OF FILE
/*--------------------------------------------------------------------------------*/

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

За допомоги CubeMX

Створюєте новий проект в CubeMX, обираєте свій мікроконтролер, наприклад STM32F100RBT6, та позначаєте потрібні ніжки мікроконтролеру на вихід (GPIO_Output). Та не забуваймо дати назву ніжкам (Enter User Label) за принципом:

Сегменти:
  • Сегмент А дисплею - SEG_A,
  • Сегмент B дисплею - SEG_B,
  • Сегмент C дисплею - SEG_C,
  • Сегмент D дисплею - SEG_D,
  • Сегмент E дисплею - SEG_E,
  • Сегмент F дисплею - SEG_F,
  • Сегмент G дисплею - SEG_G,
  • Сегмент H (крапка) дисплею - SEG_H (за потреби);
Розряди:
  • Перший розряд - D0,
  • Другий розряд - D1,
  • Третій розряд - D2,
  • Четвертий розряд - D3;
Зробіть як на малюнку. Червоним позначено виводи до яких підключений дисплей. LED_BLUE, LED_GREEN та USER_BUTTON це периферія плати розробника STM32VLDiscovery. Нам знадобиться для прикладу ще й USER_BUTTON. Налаштуйте ніжку до якої під'єднана кнопка на вхід:
Налаштування підключення дисплею в CubeMX
Генеруєте код для свого засобу розробки. До згенерованого проекту додаєте файли бібліотеки. LED7Seg.h до теки "INC", а LED7Seg.c до теки "SRC" проекту. Та додаєте до файлу main.c такі рядки:
/* USER CODE BEGIN Includes */
#include <stdbool.h> // для оперування змінними типу bool
#include <led7seg.h> // підключаємо бібліотеку 7-сегментного дисплею
/* USER CODE END Includes */
В головній функції main(), оголосимо змінні потрібні для прикладу:
  /* USER CODE BEGIN 1 */
 uint32_t counter = 0;   // ініціалізуємо змінну лічильника 
 bool flagButton = false;  // ініціалізуємо змінну прапорця кнопки
  /* USER CODE END 1 */
Надрукуємо початкове значення лічильника на дисплей. По суті, ми просто задали яке число має світитись на дисплеї.
  /* USER CODE BEGIN 2 */
  LED7SegPrint(counter); // надрукуємо початкове значення лічильника на дисплей
  /* USER CODE END 2 */
Після цього рядка ще нічого не з'явиться на дисплеї, бо за саме світіння потрібних сегментів відповідає інша функція - LED7SegShow().
В безкінечному циклі буде опитування кнопки і коли натиснуто кнопку до лічильника додамо одиницю:
 /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
   // як натиснули кнопку і прапорець кнопки скинуто
   if(HAL_GPIO_ReadPin(USER_BUTTON_GPIO_Port,USER_BUTTON_Pin) && !flagButton)
   {
    LED7SegPrint(++counter);  // додали одиницю до лічильника
    flagButton = true;  // встановили прапорець кнопки
   } else if(!HAL_GPIO_ReadPin(USER_BUTTON_GPIO_Port,USER_BUTTON_Pin) && flagButton){
    flagButton = false;    // коли кнопку відпустили - скинули прапорець кнопки
   }
   LED7SegShow();   // засвітили потрібні сегменти на дисплеї
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */

  }
  /* USER CODE END 3 */
Також в безкінечному циклі постійно викликається функція LED7SegShow(), яка відповідає за динамічну індикацію дисплею.
Якщо потрібно на дисплей вивести якісь специфічні символи, то тоді порядок такий:
  • якщо цього символу немає в бібліотеці (початково в бібліотеці "0-9", "-" і "пусто" (всі сегменти відключено), то треба його створити. Про це далі;
  • оголосити масив uint8_t розміром на кількість задіяних сегментів;
  • заповнити масив "кодами" символів;
  • вивести це на дисплей.
Наприклад, зобразимо на дисплеї "-, NOP, NOP, -", де "NOP" - це нічого, жоден сегмент не світиться.
Оголошуємо в програмі масив:
  uint8_t digit[4] = {}; // Масив для символів в кожен розряд дисплею окремо
Заповнюємо масив "кодами" символів:
digit[0] = MINUS; // -
digit[1] = NOP;   // NOP
digit[2] = NOP;   // NOP
digit[3] = MINUS; // -
Та друкуємо це на дисплей викликавши функцію LED7SegPrintSeparate з параметрами: digit - ім'я масиву та кількість сегментів дисплею:
LED7SegPrintSeparate(digit,4);
Звісно що світінням потрібних сегментів займеться функція LED7SegShow().

Як додати користувацькі символи

Розглянемо це на прикладі. Додамо користувацький символ - літеру "А".
До файлу LED7Seg.h додати такі рядки:
#define LITA ( SEG_F_Pin | SEG_E_Pin | SEG_A_Pin | SEG_B_Pin | SEG_C_Pin | SEG_G_Pin )  // з яких сегментів складається літера "А"
#define A 12    // код літери "А", або порядковий номер у масиві (код 10 та 11 зайнятий NOP і MINUS) 
А до файлу LED7Seg.c внесемо такі зміни. До масиву digitsp[] додамо ім'я створеного символу, що назначили у заголовному файлі:
//оголошуємо масив з можливими варіантами символами на індикатор
static const uint16_t digitsp[]={DIG0,DIG1,DIG2,DIG3,DIG4,DIG5,DIG6,DIG7,DIG8,DIG9,~ALL_SEG,SEG_G_Pin,LITA};
Це і все. Тепер можна наприклад вивести таку інформацію на дисплей, наприклад, "А2-4":
uint8_t digit[4] = {};
digit[0] = 4;          // 4
digit[1] = MINUS;      // -
digit[2] = 2;          // 2
digit[3] = A;          // A
LED7SegPrintSeparate(digit,4);

Без CubeMX

Якщо ви не використовуєте CubeMX, або не зазначили там ніжки, до яких підключено дисплей, то потрібно позначити їх в заголовному файлі:
// якщо не визначили ніжки і порти до яких під'єднано дисплей у файлі main.h за допомоги CUBEMX,
// то треба ніжки і порти позначити тут відповідно до електричної схеми підключення дисплею
#if !defined SEG_A_Pin
 #define SEG_A_Pin        GPIO_PIN_0
 #define SEG_A_GPIO_Port  GPIOB
#endif

#if !defined SEG_B_Pin
 #define SEG_B_Pin        GPIO_PIN_1
 #define SEG_B_GPIO_Port  GPIOB
#endif

#if !defined SEG_C_Pin
 #define SEG_C_Pin        GPIO_PIN_2
 #define SEG_C_GPIO_Port  GPIOB
#endif

#if !defined SEG_D_Pin
 #define SEG_D_Pin        GPIO_PIN_3
 #define SEG_D_GPIO_Port  GPIOB
#endif

#if !defined SEG_E_Pin
 #define SEG_E_Pin        GPIO_PIN_4
 #define SEG_E_GPIO_Port  GPIOB
#endif

#if !defined SEG_F_Pin
 #define SEG_F_Pin        GPIO_PIN_5
 #define SEG_F_GPIO_Port  GPIOB
#endif

#if !defined SEG_G_Pin
 #define SEG_G_Pin        GPIO_PIN_6
 #define SEG_G_GPIO_Port  GPIOB
#endif

#if !defined SEG_H_Pin
 #define SEG_H_Pin        0u
 #define SEG_H_GPIO_Port  0u
#endif

#if !defined D0_Pin
 #define D0_Pin         GPIO_PIN_8
 #define D0_GPIO_Port   GPIOB
#endif

#if !defined D1_Pin
 #define D1_Pin         GPIO_PIN_9
 #define D1_GPIO_Port   GPIOB
#endif

#if !defined D2_Pin
 #define D2_Pin         GPIO_PIN_10
 #define D2_GPIO_Port   GPIOB
#endif

#if !defined D3_Pin
 #define D3_Pin         GPIO_PIN_11
 #define D3_GPIO_Port   GPIOB
#endif

#if !defined D4_Pin
 #define D4_Pin         0u
 #define D4_GPIO_Port   0u
#endif

#if !defined D5_Pin
 #define D5_Pin         0u
 #define D5_GPIO_Port   0u
#endif

#if !defined D6_Pin
 #define D6_Pin         0u
 #define D6_GPIO_Port   0u
#endif

#if !defined D7_Pin
 #define D7_Pin         0u
 #define D7_GPIO_Port   0u
#endif

#if !defined D8_Pin
 #define D8_Pin         0u
 #define D8_GPIO_Port   0u
#endif
Розрядам і сегментам які не будуть задіяні потрібно назначити значення 0u.
А до функції LED7SegInit() у файлі LED7Seg.c внести поправки, щоб порти і виводи відповідали електричній схемі, на який порт і виводи підключений дисплей. Наприклад якщо всі ніжки дисплею під'єднано до порту "B" мікроконтролера, то функція ініціалізації може виглядати так:
//Ініціалізація перифірії до якої підключено дисплей
void LED7SegInit(void)
{
 GPIO_InitTypeDef GPIO_InitStruct;

   /* GPIO Ports Clock Enable */
   __HAL_RCC_GPIOB_CLK_ENABLE();

   /*Configure GPIO pin Output Level */
   HAL_GPIO_WritePin(GPIOB, SEG_A_Pin|SEG_B_Pin|SEG_C_Pin|SEG_D_Pin
                           |SEG_E_Pin|SEG_F_Pin|SEG_G_Pin|SEG_H_Pin
                           |D0_Pin|D1_Pin|D2_Pin|D3_Pin, GPIO_PIN_RESET);

   /*Configure GPIO pins : SEG_A_Pin SEG_B_Pin SEG_C_Pin SEG_D_Pin
                            SEG_E_Pin SEG_F_Pin SEG_G_Pin SEG_H_Pin
                            D0_Pin D1_Pin D2_Pin D3_Pin*/
   GPIO_InitStruct.Pin = SEG_A_Pin|SEG_B_Pin|SEG_C_Pin|SEG_D_Pin
                         |SEG_E_Pin|SEG_F_Pin|SEG_G_Pin|SEG_H_Pin
                         |D0_Pin|D1_Pin|D2_Pin|D3_Pin;
   GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
   GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

}
Та не забувайте викликати цю функцію на початку програми, щоб ініціалізувати периферію. Все інше так само.

Файли

Архів з бібліотекою - LED7Seg
Архів з проектом для - True Studio & CubeMX

середа, 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 мілісекунд (програмний антибрязкіт).