Часто в своих устройствах хочется организовать красивую индикацию, а может устройство, в принципе, по своей основной функции должно управлять большим количеством разноцветных светодиодов. В этом случае отличным, да и по сути единственным решением является использование RGB-светодиодов, которые управляются при помощи трех ШИМ сигналов (по одному на каждую цветовую составляющую - Red/Green/Blue). И вроде бы все отлично, но что если светодиодов требуется подключить, к примеру, 12 штук, и все они должны светиться по-разному. И 12 - это еще далеко не предел.
Получается, что нам придется под эти цели отдать как минимум 36 выводов микроконтроллера, а это, согласитесь, совсем немало. Вот как раз для таких случаев и придуманы специальные драйверы светодиодов, и об одном из них я сегодня и расскажу. Собственно, нашей задачей будет спроектировать небольшую схему для подключения как раз-таки 12 RGB-светодиодов и написать программу для управления их цветом.
Начнем с обсуждения электронных компонентов, которые я буду использовать в работе. Итак, в качестве мозгового центра устройства будет выступать микроконтроллер STM32F103RBT6. Какой именно контроллер задействовать абсолютно непринципиально, так что это не имеет большого значения. Важным является выбор драйвера, я буду использовать (и активно использую) DM164. Светодиоды также можно использовать абсолютно разные, главное, чтобы они были с общим анодом.
Собственно, пару слов о самой микросхеме. У DM164 есть 24 выходных канала, то есть к одному драйверу мы можем подключить 8 светодиодов (по три канала на один светодиод). Нам для решения задачи понадобятся два драйвера (8 + 4 диодов). Для каждой цветовой составляющей мы можем задать яркость от 0 до 65536, таким образом, многообразие цветов вполне неплохое ) В зависимости от используемых диодов мы можем настроить значение выходного тока, для этого используются внешние резисторы. Максимально драйвер может выдавать 90 мА. Я не буду перечислять факты из документации на микросхему, поэтому давайте остановимся на уже упомянутых основных характеристиках драйвера и перейдем к практике.
Поскольку мы решили использовать две микросхемы, то я сразу же приведу схему для каскадного включения двух драйверов и поэтапно распишу что и зачем там используется
Давайте начнем по порядку. На выводы VDD1 и VDD2 мы подаем напряжение питания - 3.3 В.
Соответственно, выводы VSSx мы заземляем. Кроме того, подключаем землю на выводы DCKPH и DOUTPH, которые отвечают за настройку протокола обмена. Вот как это выглядит:
В нашем случае DCKPH = DOUTPH = L. К каждому из драйверов подключены три резистора, которые как раз задают выходной ток каналов DM164. Зависимость вот такая:
Мои светодиоды рассчитаны на ток 30 мА, поэтому, исходя из графика, я поставил резисторы номиналом 2.7 кОм. С этим все довольно просто, переходим к управляющим выводам.
Сигнал EN_B - его мы подключаем на какую-нибудь ножку микроконтроллера, которая будет работать в режиме простого выхода. При высоком уровне на EN_B выходы драйвера отключены и светодиоды не горят, при низком, соответственно, светодиоды светятся тем цветом, который мы им передали по SPI.
Раз уж упомянул SPI, то давайте рассмотрим выводы DOUT, DCK и DIN. Собственно, это и есть выводы SPI. Управление драйвером осуществляется именно по этому интерфейсу. Отправляя данные драйверу мы устанавливаем яркость для каждого из выходных каналов, а принимая данные мы можем диагностировать возникновение ошибок. Но в данном примере мы не будем использовать прием, поскольку, по большому счету, для стабильной работы это и не требуется.
Поскольку нам надо подключить два драйвера, а не один, то мы используем так называемое каскадное подключение. Как видно из схемы вывод DOUT первой микросхемы идет дальше, а именно на вход DIN второго драйвера. Таким образом, со стороны контроллера мы используем всего один модуль SPI. Яркости каждого канала соответствует 16-битное число, которое и необходимо отправить. Один светодиод - это три канала, поэтому у первого драйвера мы задействуем все 24, а у второго только половину (12) каналов. В результате нехитрых вычислений получается, что отправлять нам нужно будет 36 значений типа uint16_t. Причем первые 12 из них "проскочат" первый драйвер и благодаря каскадному подключению дойдут до второго.
Двигаемся дальше по выводам микросхемы, и на очереди сигнал GCK. Сюда мы должны подать ШИМ-сигнал с нашего микроконтроллера, он будет использоваться для формирования выходного сигнала каналов драйвера. Максимальная частота при напряжении питания 3.3 В составляет 36 МГц.
Вывод LATCH - "защелка". Именно подав этот сигнал мы дадим драйверу команду активировать выходы в соответствии с данными, которые мы к тому моменту уже должны будем передать.
Вывод ALARM нужен для того, чтобы диагностировать перегрев DM164, его мы тоже в примере не будем задействовать, но на схеме подключение предусмотрено - на всякий случай, для будущих нужд.
IWAVE отвечает за режим работы драйвера и на этот вход мы подадим высокий уровень (высокий - traditional Iout waveform, низкий - average separate Iout waveform).
MSEL позволяет конфигурировать драйвер - для этого его нужно перевести в состояние логической единицы.
И, наконец, ONEST - включает/отключает функцию one-shot. Мы этот режим не будем использовать, поэтому на эту ножку просто подаем низкий уровень.
Диоды я подключил к последовательным каналам - то есть первый диод, к примеру, подключен катодами на IOUT0, IOUT1, IOUT2, а на общий анод подаем напряжение питания (3.3 В). Аналогичным образом подключаются все остальные диоды. У меня получилось, что за синюю составляющую цвета первого светодиода отвечает IOUT0, за красную - IOUT1, ну и за зеленую - IOUT2 (BGR).
Итак, вроде бы с электрическим подключением разобрались, давайте переходить к программной реализации. Для этого проекта я буду использовать Keil и библиотеку SPL.
Время традиционной вставки: поскольку компания STMicroelectronics прекратила поддержку библиотеки SPL, которая использовалась в этом курсе, я создал новый, посвященный работе уже с новыми инструментами, так что буду рад видеть вас там - STM32CubeMx. Кроме того, вот глобальная рубрика по STM32.
Пример программы для драйвера светодиодов.
Начинаем с подключения требующихся файлов:
#include "stm32f10x_gpio.h" #include "stm32f10x_tim.h" #include "stm32f10x_spi.h"
Далее объявим переменные:
SPI_InitTypeDef spi; GPIO_InitTypeDef gpio; TIM_TimeBaseInitTypeDef timer; TIM_OCInitTypeDef timerPWM; uint16_t currentColorData[36]; uint32_t i = 0; uint16_t timeout;
Определим тайм-аут для передачи данных по SPI. На настройках периферии - таймеров и SPI - я не буду отдельно останавливаться, поскольку все это мы уже обсуждали в статьях, посвященных этим модулям.
#define TIMEOUT_TIME 0x1000
Подключено у меня следующим образом:
- ALARM - PB0
- MSEL - PB1
- LTH - PB10
- IWAVE - PC4
- ONEST - PC5
- SPI_MOSI - PA7
- SPI_CLK - PA5
- GCK - PA3
Теперь нам нужно настроить все эти выводы и подать нужный сигнал на каждый из них, а также настроить GCK на генерацию ШИМ и модуль SPI1. GCK у нас на PA3, а это четвертый канал таймера TIM2:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); gpio.GPIO_Mode = GPIO_Mode_Out_PP; gpio.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_13; gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC, &gpio); GPIO_ResetBits(GPIOC, GPIO_Pin_4); GPIO_ResetBits(GPIOC, GPIO_Pin_5); gpio.GPIO_Mode = GPIO_Mode_Out_PP; gpio.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_10; gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &gpio); GPIO_ResetBits(GPIOB, GPIO_Pin_1); GPIO_ResetBits(GPIOB, GPIO_Pin_10); gpio.GPIO_Mode = GPIO_Mode_AF_PP; gpio.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; gpio.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(GPIOA, &gpio); TIM_TimeBaseStructInit(&timer); timer.TIM_Prescaler = 1; timer.TIM_Period = 2; TIM_TimeBaseInit(TIM2, &timer); TIM_OCStructInit(&timerPWM); timerPWM.TIM_Pulse = 1; timerPWM.TIM_OCMode = TIM_OCMode_PWM1; timerPWM.TIM_OutputState = TIM_OutputState_Enable; TIM_OC4Init(TIM2, &timerPWM); TIM_Cmd(TIM2, ENABLE); spi.SPI_Direction = SPI_Direction_1Line_Tx; spi.SPI_DataSize = SPI_DataSize_16b; spi.SPI_CPOL = SPI_CPOL_Low; spi.SPI_CPHA = SPI_CPHA_1Edge; spi.SPI_NSS = SPI_NSS_Soft; spi.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; spi.SPI_FirstBit = SPI_FirstBit_MSB; spi.SPI_CRCPolynomial = 7; spi.SPI_Mode = SPI_Mode_Master; SPI_Init(SPI1, &spi); SPI_Cmd(SPI1, ENABLE);
Подготавливаем передаваемые данные:
for (i = 0; i < 36; i += 3) { currentColorData[i] = 65535; currentColorData[i + 1] = 0; currentColorData[i + 2] = 0; }
Как вы помните, каналы у нас сгруппированы по три (каждый светодиод - 3 канала) и первый канал - это у нас синий, второй - красный, а третий - зеленый цвет. Таким образом, при передаче такого массива все светодиоды должны выдать максимально яркий и чистый синий цвет.
Выполняем отправку:
for (i = 0; i < 36; i++) { timeout = TIMEOUT_TIME; while ((SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) & (timeout != 0)) { timeout--; } SPI_I2S_SendData(SPI1, currentColorData[i]); timeout = TIMEOUT_TIME; while ((SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) & (timeout != 0)) { timeout--; } }
Данные переданы - необходимо их "защелкнуть" - кратковременно подаем высокий уровень на LATCH.
GPIO_SetBits(GPIOB, GPIO_Pin_10); GPIO_ResetBits(GPIOB, GPIO_Pin_10);
Вот и все! Диоды начинают светить красивым сними цветом. В общем-то можно задать любой цвет свечения, а можно разукрасить все светодиоды в разные цвета, а можно и вообще организовать красивую динамическую индикацию ) Все зависит от фантазии и, конечно, от задачи, которую необходимо реализовать. А мы на сегодня на этом заканчиваем, до скорой встречи 🤝
Спасибо! Будем использовать по необходимости.
P.S. Эта та что в корпусе LQFP48 ?
По-моему в DIP тоже есть.