Текстовые часы на STM32F030C8T6

Каждый уважающий себя разработчик на микроконтроллерах должен рано или поздно сделать часы.  Конечно, сначала термометр, а затем уже часы. Термометр, он же таймер я уже делал, очередь за часами. При этом часы будут не простыми – числовыми, а текстовыми.
Текстовые часы показывают не цифры, а текст. Для этого, к примеру можно использовать дисплей, но к контроллеру обычно подключают что-то попроще. Например, светодиодные индикаторы. Вот и у меня были две светодиодные матрицы 8×8 из которых получился отличный дисплей 16×8
Дисплей 8×16 точек можно использовать для цифр и даже для бегущей строки. Если взять цифры 6×3 точки, то как раз помещается четыре цифры на дисплее, что для обычных часов вполне достаточно, но для текста явно маловато.
И тут  я увидел рекламу вот таких часов
Текстовые часы

О, а это идея! Всего лишь определенное количество светодиодов, которые зажигаются в нужных местах и текстовые часы готовы.
Изначально эти часы 11×11. Правда, если внимательно присмотреться, то во-первых точность у них до пяти минут, это ладно, но во-вторых у нас принято писать слева направо и сверху вниз, а если взять “пятнадцать часов”, то будет какая-то каша или “двадцать один час” вообще нет такого.  И что-то не заметил я нуля часов. Подразумевается, что ночью на них никто не смотрит.
И так, у меня есть даже не 11×11, а 16×8 светодиодов, то у меня есть возможность сделать по-русски.
Для начала сделаем текстовку для экрана. Получилось так. Проверил на всех цифрах, надписи идут слева направо сверху вниз и желательно между словами какие-то промежутки, чтобы не сливалось все.  Сделал  файлик в Excel, куда забил все возможные варианты часов и минут. Вот как у меня это выглядело

Матрица для текстовых часов

Часы идут в верхней части, минуты в нижней. В итоге получилась вот такая матрица

Т Р И Ч Е Т Ы Р Е В О С Е М Ь
О Д И Н Ш Е С Т Ь Н О Л Ь Д Е В
П Я Т Ь Д В Е Д В А Д Е С Я Т Ь
Н А Д Ц А Т Ь Д В А
Т Р И О Д И Н Ч А С А
Ч А С О В П Я Т Ь Д В А Т Р И
Д Е С Я Т Ь Н А Д Ц А Т Ь
С О Р О К П Я Т Ь М И Н У Т

Пришлось оставить пятиминутную точность. Совсем шестьдесят минут не влезают.
Светодиодная матрица 1572m L 8×8 у меня с общим анодом , что чрезвычайно удобно для управления при помощи драйвера светодиодов. При этом хотя и выводов целых 16 у каждой, но подключены далеко не все. Матрица одноцветная красная, в таком же корпусе есть и трехцветные с тем же количеством выводов. Естественно подразумевается динамическое управление светодиодами.

ledmartix8x8

Строками матрицы буду управлять через транзисторные ключи, колонками управляем через драйвер светодиодов  STP16CP05, который представляет собой два обычных сдвиговых регистра в одном корпусе. Причем ноги расположены очень удобно именно для управления двумя  матрицами, так что на плате разводка пошла нога к ноге без пересечений и с минимальными переходами между слоями.

 

scheme

Поскольку у меня небыло SMD транзисторов, то пришлось пожертвовать старой материнской платой, на коей транзисторов довольно много , нашел нечто с маркировкой W1P, похоже  PMBT2222A.215, транзистор NPN 40В 600мА, впаял, попробовал, ведет себя как NPN транзистор, значит оно. Если посчитать, в строке 16 светодиодов. Максимальный ток через каждый 20мA, т.е. если всю строку нужно зажечь, то будет 320mA, потянет. Светит вполне сносно, но на максимальной яркости использую редко, обычно ставлю настройку где-то на 5 милиампер, светит и ладно. У STP16CP05 есть вывод R-ext  через который можно играть с протекающим током, если повесить на него резистор определенного номинала.

tabl

 

Согласно таблице у меня на этом выводе висит резистор в 1 КОм постоянный и последовательно к нему 10 КОм  переменный, т.е. я могу подстраивать яркость при помощи переменного резистора от максимума до минимума.

Выводы расположены довольно удобно, если разместить микросхему между светодиодными матрицами
Для работы с драйвером задействум стандартный интерфейс SPI
Поскольку данные идут только в одном направлении, то от STM32 нам достаточно всего двух выводов SPI1_MOSI и SPI1_SCK,

Подключение такое:

stp16cp05

1 GND Ground terminal
2 SDI Serial data input terminal    – PA7 (MOSI)
3 CLK Clock input terminal    – PA5(SCK)
4 LE/DM1 Latch input terminal   – защелка, но работает, если один раз установить в  + 3.3 вольта
5-20 OUT 0-15 Output terminal
21 OE/DM2 Input terminal of output enable (active low)    – сюда можно подключить шим, если 3.3 – вольта, то все светодиоды гаснут, если 0, то загораются в зависимости от переданных данных
22 SDO Serial data out terminal
23 R-EXT Input terminal of an external resistor for constant current programing
24 VDD Supply voltage terminal

 

В качестве контроллера использую STM32F030C8T6 в 48 ножечном корпусе. К сожалению мой любимый 20 ножечный корпус не подойдет по количеству выводов, поскольку у меня еще четыре кнопки. Итого выходит задействованы GPIO  3 – SPI для отображения колонок, 8 прямое включение через транзисторы для отображения строк, 3 на отладочный разъем, еще была мысль оставить один на пищалку, а поскольку у меня подключен кварц  и даже два, то значит еще четыре GPIО уходят под кварц.
Для программирования STM32 будут использованы следующие возможности
1. Управление GPIO – простое включение, выключение строк.
2. Передача данных через SPI, для колонок
3. Таймеры, для счетчиков
4. RCC -часы реального времени.

Для начала активируем внешний кварц на 8МГЦ

RCC->CR|=RCC_CR_CSSON; // Разрешить работу
RCC->CR|=RCC_CR_HSEON; //Запускаем генератор HSE.
while (!(RCC->CR & RCC_CR_HSERDY)) {}; // Ждем готовности
RCC->CFGR &=~RCC_CFGR_SW; //Сбрасываем биты
RCC->CFGR |= RCC_CFGR_SW_HSE; // Переходим на HSE 8Mгц

Активируем SPI

GPIO_InitTypeDef GPIO_InitStructure;

// включить тактирование портов A и B
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);

// a4 для активации приема данных
GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_4    ;
GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_1;
GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //ножка пуш-пул
GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_SetBits(GPIOA, GPIO_Pin_4);

Можно было физически повесить на +3.3 вольта.

// a6
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_3;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //ножка пуш-пул
GPIO_Init( GPIOA , &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_0);

// можно подключить шим, если выключен, то все работает, включен – гаснет
GPIO_ResetBits(GPIOA, GPIO_Pin_6);

SPI_InitTypeDef SPI_InitStructure;

// Тактирование модуля SPI1 и порта А
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

// Настраиваем ноги SPI1 для работы в режиме альтернативной функции
GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_0);
//      GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_0);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_0);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
//GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
//GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 |  GPIO_Pin_6 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 |  GPIO_Pin_5;
GPIO_Init(GPIOA, &GPIO_InitStructure);

//Заполняем структуру с параметрами SPI модуля SPI_Direction_1Line_Tx;
SPI_InitStructure.SPI_Direction =SPI_Direction_2Lines_FullDuplex; //полный дуплекс
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // передаем по 8 бит
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;// SPI_CPOL_Low; // Полярность и
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // фаза тактового сигнала
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // Управлять состоянием сигнала NSS программно
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; // Предделитель SCK
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // Первым отправляется старший бит
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // Режим – мастер
SPI_Init(SPI1, &SPI_InitStructure); //Настраиваем SPI1
SPI_Cmd(SPI1, ENABLE); // Включаем модуль SPI1….

// Поскольку сигнал NSS контролируется программно, установим его в единицу
// Если сбросить его в ноль, то наш SPI модуль подумает, что
// у нас мультимастерная топология и его лишили полномочий мастера.
SPI_NSSInternalSoftwareConfig(SPI1, SPI_NSSInternalSoft_Set);

// раз в милисекунду
SysTick_Config(1000);

Данные передаются по 8 бит. Удобно использовать для нескольких матриц, но у меня их всего две, поэтому чтобы передать данные для всех колонок матрицы нужно передать два байта.

// отправка данных по SPI
void DataSend(uint16_t data)
{

//    нет необходимости проверять флаг занятости в нашем случае
//     while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET);

while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); // ждём, освобождения SPI_TXE

SPI_SendData8(SPI1, data);// отправка по 8 бит, для полной засветки нужно отправлять 2 раза
}

При использовании флага SPI_I2S_FLAG_TXE будет возможность задействовать аппаратный буфер STM32, мы можем передавать по два байта  без перерыва. SPI_I2S_FLAG_BSY используется при работае с дисплеями по SPI, у нас все проще.

// 02 таймер для шахматных часов, это отдельно от часов реального времени
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_SETUP.TIM_CounterMode = TIM_CounterMode_Up;  //считаем вверх
TIM_SETUP.TIM_Period = 100;     //период таймера 1/10 секунды
TIM_SETUP.TIM_Prescaler = 8000 ; //TIMER_PRESCALER;    //предделитель откл
TIM_TimeBaseInit(TIM2, &TIM_SETUP);

//Разрешаем соответствующее прерывание
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM2, ENABLE);
NVIC_EnableIRQ(TIM2_IRQn);

// таймер для шахматных часов, просто уменьшает количество тиков для каждого игрока по алгоритму
void TIM2_IRQHandler()
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);   // обнулили прерывание
// считаем для каждого пользователя
// левый
if (chess_mode==1 && LTimer>0){
LTimer–;
}
// правый
if (chess_mode==2 && RTimer>0){
RTimer–;
}
if (LTimer<0){
LTimer=0;
}
if (RTimer<0){
RTimer=0;
}
}

// b0,1,2,3,4,5,6,7 для для строк
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_1;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //ножка пуш-пул
GPIO_Init(GPIOB, &GPIO_InitStructure);

// для кнопок
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_9|GPIO_Pin_11|GPIO_Pin_12| GPIO_Pin_13;               //Выбираем необходимые пины
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;            //Пины работают на вход
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;        //Скорость работы пинов
GPIO_InitStructure.GPIO_OType = GPIO_Speed_Level_3;          //Тип значение порта – двухуровневый
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;          //Начальное значение пинов
GPIO_Init(GPIOB, &GPIO_InitStructure);                  //Инициализация настроек

SysTick_Config(1000);

В SysTick_Handler  обновляем значение экрана и считываем значение нажатых кнопок
// здесь обновление экрана из буфера
void SysTick_Handler(void)
{

if(!(GPIOB->IDR & (1<<11)) == 0){ //левая Кнопка нажата
…..
}
show_time–;    // строка должна потухнуть и зажечься следующая, когда до нуля дойдет
if  (show_time<1 || show_time > 1000){
show_time=DELAY;
GPIO_ResetBits(GPIOB, row[cur_row]);   // потушили предыдущую
cur_row++;
if (cur_row>7){
cur_row=0;
}
DataSend(Buff[cur_row][0]);
DataSend(Buff[cur_row][1]);
GPIO_SetBits(GPIOB, row[cur_row]);    // зажгли новую
}

В итоге получилось вот такое устройство (видео)

Оставить комментарий