Продолжаем работать с CMSIS.
И сегодня мы к нашему контроллеру STM32F103 попробуем подключить четырехразрядный семисегментный индикатор.
Причём именно сразу четырехразрядный, так как, думаю, работать со статической индикацией и с одноразрядным индикатором смысла нет, так как это используется очень редко и как правило актуально только на этапе изучения индикаторов. Но мы с вами их уже давно знаем и работали с ними далеко не один раз. В частности по STM в этом плане был урок 11 с использованием библиотеки HAL.
Поэтому приступим сразу к динамической индикации. О динамической индикации мы также знаем не понаслышке и с ней мы работали уже в уроке 12, только использовали мы тогда немного другой контроллер и библиотеку HAL. А на контроллере STM32F103 с использованием библиотеки LL мы работали с динамической индикацией в уроке 148.
На всякий случай повторю, что динамическая индикация — это такой вид общения с многоразрядными светодиодными индикаторами, при котором показывается в определённый момент времени только одна цифра одного отдельно взятого индикатора, затем следующего, а предыдущий гасится, и так далее. Только показывается всё это с такой скоростью, что человеческий глаз (а точнее мозг) не успевает оценить, что цифры горят по очереди и ему кажется что цифры горят все вместе одновременно. Данный тип индикации позволяет сэкономить внушительное количество ножек портов.
Достигается это следующим образом. На все ножки всех индикаторов последовательно по очереди подаются уровни сначала одной цифры, предназначенной, например, для первого индикатора, потом также на все вместе, уровни цифры, предназначенной для второго индикатора и так далее. А иначе никак и не получится, ведь у них лапки все запараллелены. Только в то время, когда подаётся цифра для определённого индикатора, то на его катод, если он с общим катодом, подаётся низкий уровень, а на все остальные катоды — высокий, и уже следовательно остальные индикаторы светиться не будут. А если он с общим анодом, то подаётся на нужный анод высокое напряжение, а на все остальные — низкое и они также светиться не будут.
Давайте посмотрим схему самого индикатора 4-разрядного 7-сегментного. У меня индикатор вот такого вот типа
У меня также индикатор с общим анодом, поэтому будем придерживаться вот этой схемы для написания кода.
Подключим наш индикатор к контроллеру STM32F103 следующим образом
Контроллер на схеме немного другой, так как в библиотеке такого же не нашлось, но ножек столько же и распиновка та же. Также вместо четрырёхразрядного я подключил четыре одноразрядных индикатора, тем самым показав наглядность соединения разрядов. Также по данной схеме вы можете собрать индикатор из четырёх одноразрядных индикаторов. Подключил я ножки контроллера, только используемые для индикаторов, служебные никакие не подключал, они у нас и так отлично разведены на нашей плате. Питать транзисторы и индикатор мы будем прямо от ST-Link, поэтому никаких дополнительных источников подводить не придётся.
То есть схема используется та же, что и использовалась в 148 уроке
Ну, теперь, в принципе, можно переходить спокойно уже и к проекту, так как ничего нового нам по CMSIS изучать не придётся, всё уже изучено на прошлых занятиях.
Поэтому приступим к проекту, который был сделан из проекта урока 168 с именем CMSIS_TIM2 и был назван CMSIS_LED_DYN.
Откроем проект в Keil и в файле main.c удалим для начала данные макросы
#define LED1_ON() SET_BIT(GPIOA->ODR,GPIO_ODR_ODR2)
#define LED1_OFF() CLEAR_BIT(GPIOA->ODR,GPIO_ODR_ODR2)
#define LED2_ON() SET_BIT(GPIOA->ODR,GPIO_ODR_ODR3)
#define LED2_OFF() CLEAR_BIT(GPIOA->ODR,GPIO_ODR_ODR3)
#define LED3_ON() SET_BIT(GPIOA->ODR,GPIO_ODR_ODR4)
#define LED3_OFF() CLEAR_BIT(GPIOA->ODR,GPIO_ODR_ODR4)
#define LED4_ON() SET_BIT(GPIOA->ODR,GPIO_ODR_ODR5)
#define LED4_OFF() CLEAR_BIT(GPIOA->ODR,GPIO_ODR_ODR5)
#define LED5_ON() SET_BIT(GPIOA->ODR,GPIO_ODR_ODR6)
#define LED5_OFF() CLEAR_BIT(GPIOA->ODR,GPIO_ODR_ODR6)
#define LED6_ON() SET_BIT(GPIOA->ODR,GPIO_ODR_ODR7)
#define LED6_OFF() CLEAR_BIT(GPIOA->ODR,GPIO_ODR_ODR7)
#define LED7_ON() SET_BIT(GPIOB->ODR,GPIO_ODR_ODR0)
#define LED7_OFF() CLEAR_BIT(GPIOB->ODR,GPIO_ODR_ODR0)
#define LED8_ON() SET_BIT(GPIOB->ODR,GPIO_ODR_ODR1)
#define LED8_OFF() CLEAR_BIT(GPIOB->ODR,GPIO_ODR_ODR1)
#define LED9_ON() SET_BIT(GPIOB->ODR,GPIO_ODR_ODR10)
#define LED9_OFF() CLEAR_BIT(GPIOB->ODR,GPIO_ODR_ODR10)
#define LED10_ON() SET_BIT(GPIOB->ODR,GPIO_ODR_ODR11)
#define LED10_OFF() CLEAR_BIT(GPIOB->ODR,GPIO_ODR_ODR11)
Так как у нас есть уже настроенный таймер, то с помощью него мы будем перебирать разряды индикатора.
Из процедуры обработки прерываний таймера удалим вот этот код
switch(tim2_count)
{
case 0: LED10_OFF(); LED1_ON(); break;
case 1: LED1_OFF(); LED2_ON(); break;
case 2: LED2_OFF(); LED3_ON(); break;
case 3: LED3_OFF(); LED4_ON(); break;
case 4: LED4_OFF(); LED5_ON(); break;
case 5: LED5_OFF(); LED6_ON(); break;
case 6: LED6_OFF(); LED7_ON(); break;
case 7: LED7_OFF(); LED8_ON(); break;
case 8: LED8_OFF(); LED9_ON(); break;
case 9: LED9_OFF(); LED10_ON(); break;
}
tim2_count++;
if(tim2_count>9) tim2_count=0;
Так как у нас теперь будут ещё и заголовочные файлы, то давайте создадим отдельные папки для заголовочных файлов и файлов с исходными кодами в папке user нашего проекта и назовём их соответственно inc и src
Из дерева проектов пока удалим файл main.c, предварительно его сохранив и закрыв
Переместим файл main.c в папку src на диске и снова его подключим в наш проект
Из проекта урока 148 с именем LL_LED_DYN скопируем файлы led.c и led.h соответственно в папки src и inc нашего проекта и подключим файл led.c к дереву проекта.
Также добавим путь к папке inc в настройках цели
В файле main.c подключим нашу библиотеку индикатора
1 2 |
#include "stm32f10x.h" #include "led.h" |
Из файла led.h удалим все инклуды и макросы
#include «stm32f1xx.h»
#include «stm32f1xx_ll_gpio.h»
#define SA LL_GPIO_PIN_8
#define SB LL_GPIO_PIN_9
#define SC LL_GPIO_PIN_10
#define SD LL_GPIO_PIN_11
#define SE LL_GPIO_PIN_12
#define SF LL_GPIO_PIN_13
#define SG LL_GPIO_PIN_14
#define SH LL_GPIO_PIN_15
#define SA_SET LL_GPIO_ResetOutputPin(GPIOB, SA);
#define SA_RESET LL_GPIO_SetOutputPin(GPIOB, SA);
#define SB_SET LL_GPIO_ResetOutputPin(GPIOB, SB);
#define SB_RESET LL_GPIO_SetOutputPin(GPIOB, SB);
#define SC_SET LL_GPIO_ResetOutputPin(GPIOB, SC);
#define SC_RESET LL_GPIO_SetOutputPin(GPIOB, SC);
#define SD_SET LL_GPIO_ResetOutputPin(GPIOB, SD);
#define SD_RESET LL_GPIO_SetOutputPin(GPIOB, SD);
#define SE_SET LL_GPIO_ResetOutputPin(GPIOB, SE);
#define SE_RESET LL_GPIO_SetOutputPin(GPIOB, SE);
#define SF_SET LL_GPIO_ResetOutputPin(GPIOB, SF);
#define SF_RESET LL_GPIO_SetOutputPin(GPIOB, SF);
#define SG_SET LL_GPIO_ResetOutputPin(GPIOB, SG);
#define SG_RESET LL_GPIO_SetOutputPin(GPIOB, SG);
#define SH_SET LL_GPIO_ResetOutputPin(GPIOB, SH);
#define SH_RESET LL_GPIO_SetOutputPin(GPIOB, SH);
Подключим здесь только один файл
1 2 3 4 5 |
#ifndef LED_H_ #define LED_H_ //-------------------------------------- #include "stm32f10x.h" //-------------------------------------- |
В функции segchar исправим полностью тело
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 |
void segchar (uint8_t seg) { uint8_t r; switch(seg) { case 0: r = 0xC0; break; case 1: r = 0xF9; break; case 2: r = 0xA4; break; case 3: r = 0xB0; break; case 4: r = 0x99; break; case 5: r = 0x92; break; case 6: r = 0x82; break; case 7: r = 0xF8; break; case 8: r = 0x80; break; case 9: r = 0x90; break; case 10: r = 0xFF; break; } MODIFY_REG(GPIOB->ODR, ~(r<<8), r<<8); } |
Алгоритм здесь очень понятный. Мы заносим в переменную r инвертированное значение светящихся сегментов, соответствующих числу, пришедшему в параметре, а затем сначала гасим сегменты, которые светиться не должны, что определяет второй параметр в используемом макросе MODIFY_REG и далее зажигаем сегменты, которые должны светиться, что определяет первый параметр в данном макросе. А сдвиг влево на 8 обусловлен тем, что ножки, отвечающие за сегменты, у нас PB8—PB15.
В файле main.c подключим глобальные переменные из led.c
1 2 3 |
__IO uint8_t tim2_count = 0; extern uint8_t R1,R2,R3,R4; extern uint16_t num_gl; |
Давайте сделаем отдельную функцию для настройки ножек GPIO.
Сначала удалим тактирование портов из функции main()
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN |RCC_APB2ENR_IOPBEN);
Вот это тоже удалим
//Delay after an RCC peripheral clock enabling
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN |RCC_APB2ENR_IOPBEN);
Также удалим настройки ножек
MODIFY_REG(GPIOA->CRL, GPIO_CRL_CNF7 | GPIO_CRL_CNF6 | GPIO_CRL_CNF5 |\
GPIO_CRL_CNF4 | GPIO_CRL_CNF3 | GPIO_CRL_CNF2,\
GPIO_CRL_MODE7_0 | GPIO_CRL_MODE6_0 | GPIO_CRL_MODE5_0 |\
GPIO_CRL_MODE4_0 | GPIO_CRL_MODE3_0 | GPIO_CRL_MODE2_0);
MODIFY_REG(GPIOB->CRH, GPIO_CRH_CNF11 | GPIO_CRH_CNF10,\
GPIO_CRH_MODE11_0 | GPIO_CRH_MODE10_0);
MODIFY_REG(GPIOB->CRL, GPIO_CRL_CNF1 | GPIO_CRL_CNF0,\
GPIO_CRL_MODE1_0 | GPIO_CRL_MODE0_0);
Добавим функцию, в которой настроим наши порты и их ножки, отвечающие за сегменты и разряды, на выход, выше функции main()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//---------------------------------------------------------- static void GPIO_Init(void) { SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN |RCC_APB2ENR_IOPBEN); MODIFY_REG(GPIOA->CRL, GPIO_CRL_CNF7_0 | GPIO_CRL_CNF6_0 | GPIO_CRL_CNF5_0 | GPIO_CRL_CNF4_0,\ GPIO_CRL_MODE7_0 | GPIO_CRL_MODE6_0 | GPIO_CRL_MODE5_0 | GPIO_CRL_MODE4_0); MODIFY_REG(GPIOB->CRH, GPIO_CRH_CNF15_0 | GPIO_CRH_CNF14_0 | GPIO_CRH_CNF13_0 | GPIO_CRH_CNF12_0 | \ GPIO_CRH_CNF11_0 | GPIO_CRH_CNF10_0 | GPIO_CRH_CNF9_0 | GPIO_CRH_CNF8_0,\ GPIO_CRH_MODE15_0 | GPIO_CRH_MODE14_0 | GPIO_CRH_MODE13_0 | GPIO_CRH_MODE12_0 | \ GPIO_CRH_MODE11_0 | GPIO_CRH_MODE10_0 | GPIO_CRH_MODE9_0 | GPIO_CRH_MODE8_0); SET_BIT(GPIOB->ODR, GPIO_ODR_ODR15 | GPIO_ODR_ODR14 | GPIO_ODR_ODR13 | GPIO_ODR_ODR12 |\ GPIO_ODR_ODR11 | GPIO_ODR_ODR10 | GPIO_ODR_ODR9 | GPIO_ODR_ODR8); } //---------------------------------------------------------- |
Вызовем нашу функцию настройки GPIO в main()
1 2 |
TIM2_Init(); GPIO_Init(); |
В функции TIM2_Init уменьшим период срабатываний таймера
WRITE_REG(TIM2->ARR, 100);
Период срабатывания таймера теперь установится в 5 милисекунд, чего вполне достаточно для перебирания разрядов нашего индикатора, а также для того, чтобы они не мерцали.
В функции TIM2_IRQHandler, в зависимости от номера тика, зажжём нужный разряд, отправим цифру в функцию установки сегментов в разряде, а затем обнулим счётчик тиков, если он достигнет последнего разряда. Значения кейсов в switch соответствуют номерам разрядов индикатора справа налево
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
CLEAR_BIT(TIM2->SR, TIM_SR_UIF); if(tim2_count==0) { MODIFY_REG(GPIOA->ODR, GPIO_ODR_ODR5 | GPIO_ODR_ODR6 | GPIO_ODR_ODR7, GPIO_ODR_ODR4); segchar(R1); } if(tim2_count==1) { MODIFY_REG(GPIOA->ODR, GPIO_ODR_ODR4 | GPIO_ODR_ODR6 | GPIO_ODR_ODR7, GPIO_ODR_ODR5); segchar(R2); } if(tim2_count==2) { MODIFY_REG(GPIOA->ODR, GPIO_ODR_ODR4 | GPIO_ODR_ODR5 | GPIO_ODR_ODR7, GPIO_ODR_ODR6); segchar(R3); } if(tim2_count==3) { MODIFY_REG(GPIOA->ODR, GPIO_ODR_ODR4 | GPIO_ODR_ODR5 | GPIO_ODR_ODR6, GPIO_ODR_ODR7); segchar(R4); } tim2_count++; if(tim2_count>3) tim2_count=0; |
Для того, чтобы в убрать лидирующие нули в однозначных, двузначных и трёхзначных числах, добавим также в соответствующие кейсы, код, который будет их гасить
1 2 3 4 |
segchar(R2); if(num_gl<10) SET_BIT(GPIOB->ODR, GPIO_ODR_ODR15 | GPIO_ODR_ODR14 | GPIO_ODR_ODR13 | GPIO_ODR_ODR12 |\ GPIO_ODR_ODR11 | GPIO_ODR_ODR10 | GPIO_ODR_ODR9 | GPIO_ODR_ODR8); |
1 2 3 4 |
segchar(R3); if(num_gl<100) SET_BIT(GPIOB->ODR, GPIO_ODR_ODR15 | GPIO_ODR_ODR14 | GPIO_ODR_ODR13 | GPIO_ODR_ODR12 |\ GPIO_ODR_ODR11 | GPIO_ODR_ODR10 | GPIO_ODR_ODR9 | GPIO_ODR_ODR8); |
1 2 3 4 |
segchar(R4); if(num_gl<1000) SET_BIT(GPIOB->ODR, GPIO_ODR_ODR15 | GPIO_ODR_ODR14 | GPIO_ODR_ODR13 | GPIO_ODR_ODR12 |\ GPIO_ODR_ODR11 | GPIO_ODR_ODR10 | GPIO_ODR_ODR9 | GPIO_ODR_ODR8); |
Добавим переменную для счётчика в функцию main()
1 2 3 |
int main(void) { uint16_t i; |
А в бесконечном цикле мы будем считать от 0 до 9999 и отображать данные числа на нашем индикаторе
1 2 3 4 5 6 7 |
while(1) { for(i=0;i<10000;i++) { ledprint(i); delay_ms(100); } |
Всё готово. Проверим результат нашего труда, собрав код и прошив контроллер
Итак, в данном уроке мы научились работать с динамической индикацией, используя возможности библиотеки CMSIS.
Всем спасибо за внимание!
Предыдущий урок Программирование МК STM32 Следующий урок
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Семисегментный чертырехразрядный индикатор красный с общим анодом 10 шт
Смотреть ВИДЕОУРОК (нажмите на картинку)
Большое спасибо вам за именно низкоуровневые уроки по микропроцессорам линейки STM32. Всегда очень интересно читать и смотреть на ваши подходы к решениям задач. Буду рад если такие уроки будут шириться, ибо по HAL информации в инете просто в разы больше, но сама hal такой фарш в результате создает… заполнить структуру…чтобы внутри за эндцать тактов оно разложилось в пару регистров…