82. Индикация на прототипе: ШИМ, RGB, сдвиговые регистры
Индикация — первый инструмент отладки и главный способ устройства что-то сказать пользователю. На прототипе это светодиод с плавной яркостью, RGB-светодиод, семисегментный индикатор через сдвиговый регистр. Как это устроено на STM32, подробно разобрано раньше: ШИМ и глаз — в разделе 57, сдвиговые регистры, динамическая индикация и адресные светодиоды — в разделе 22. Здесь — взгляд с прототипа: что делают привычные функции Arduino, где в типовых примерах ошибки и во что это превращается на STM32.
Модель сдвигового регистра, таблица цифр и развёртка проверены тестом
на компьютере; код для STM32 собран armclang.
Как это называется в документации
Оригинал Перевод PWM, duty cycle ШИМ, коэффициент заполнения perceived brightness, gamma correction воспринимаемая яркость, гамма-коррекция common anode / common cathode общий анод / общий катод seven-segment display семисегментный индикатор multiplexing, refresh rate динамическая индикация, частота обновления serial-in parallel-out shift register сдвиговый регистр с последовательным входом storage register, latch регистр хранения, защёлка output enable разрешение выходов bit order, MSB / LSB first порядок битов, старшим / младшим вперёд daisy chain цепочка, каскад
82.1 Чем показывать
| Средство | Выводов | Замечание |
|---|---|---|
| светодиод | 1 | ток через резистор (раздел 49.3), яркость — ШИМ |
| RGB-светодиод | 3 | три ШИМ-канала, резисторы разные (82.3) |
| семисегментный индикатор, 4 разряда | 32 напрямую, 12 динамически, 3 через регистры | 82.5 и раздел 22.3 |
| светодиодная матрица | как у индикатора | та же динамическая индикация |
| адресная лента | 1 | свой протокол и отдельное питание (раздел 22.4) |
| графический дисплей | SPI или I²C | раздел 18.8 |
| пьезоизлучатель | 1 | звук тоже индикация: частота — таймером (раздел 13) |
82.2 ШИМ: яркость без ЦАП
У Uno нет ЦАП, у F103C8 тоже (раздел 15). Плавную яркость дают широтно-импульсной модуляцией: вывод включается и выключается с постоянной частотой, а меняется доля времени, когда он включён.
Коэффициент заполнения D — длительность импульса, делённая на период: от 0 до 1. В строгой русской терминологии скважность — это обратная величина, период на длительность: при D = 0,25 скважность равна 4. В обиходе, и в разделе 57 тоже, скважностью часто называют сам коэффициент заполнения — отличить легко: скважность в строгом смысле всегда больше единицы.
У светодиода ток задан резистором, поэтому яркость пропорциональна D
(раздел 57.3). Функция analogWrite(pin, 0…255) в Arduino — это 8-битный
ШИМ. На STM32 таймер позволяет больше: при 72 МГц 16-битный ШИМ идёт на
1,1 кГц, 12-битный — на 17,6 кГц (размен частоты на разрешение —
раздел 57.1).
/* TIM2, канал 1 на PA0: 16-битный ШИМ, 72 МГц / 65 536 ≈ 1,1 кГц */
void pwm_init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
GPIOA->CRL = (GPIOA->CRL & ~0xFu) | 0xBu; /* PA0: альтернативная функция */
TIM2->PSC = 0;
TIM2->ARR = 65535;
TIM2->CCMR1 = TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1PE; /* ШИМ, режим 1 */
TIM2->CCER = TIM_CCER_CC1E;
TIM2->CR1 = TIM_CR1_ARPE | TIM_CR1_CEN;
}
void pwm_set(uint16_t duty)
{
TIM2->CCR1 = duty;
}
Зачем столько разрядов. Глаз чувствует яркость нелинейно, и сильнее всего — внизу шкалы. Первая ступень 8-битного ШИМ — это 0,39 % тока, но по шкале воспринимаемой яркости CIE — уже 3,5 %: плавное включение начинается со ступеньки. У 16-битного первая ступень — 0,014 %.
Поправка на глаз. Самое дешёвое приближение — возвести желаемую яркость в квадрат:
/* яркость «на глаз» 0…255 → 16-битное заполнение ШИМ: квадрат вместо прямой */
uint16_t bright_to_duty(uint8_t b)
{
return (uint16_t)((uint32_t)b * b * 65535u / 65025u);
}
Шкала монотонная, от 0 до 65 535, первая ступень — 1. Середина шкалы даёт 25 % заполнения; точная степень 2,2 из раздела 57.3 даёт 21,8 %. Для индикатора квадрата хватает, для освещения лучше таблица.
⚠️ Плавное нарастание с
delay()— цикл из 256 шагов по 20 мс — занимает программу на пять секунд. На STM32 шаг яркости делают из прерывания таймера или по DMA (раздел 9), а программа только задаёт цель.
82.3 RGB-светодиод
Три светодиода в одном корпусе с общим выводом.
- Общий катод — общий на землю, каналы зажигаются единицей: заполнение прямое.
- Общий анод — общий на питание, каналы зажигаются нулём:
заполнение инвертировано, 0 — полная яркость. Проще всего
включить инверсию полярности канала таймера — бит
CCxPвTIMx->CCER(для первого каналаTIM_CCER_CC1P), а не пересчитывать значения.
Три канала одного таймера (CH1–CH3) делят один период — частота у
цветов гарантированно одна.
Резисторы — разные. У красного кристалла падение около 2 В, у зелёного и синего — около 3 В. При питании 3,3 В на 5 мА красному нужен резистор 260 Ом, синему — всего 60 Ом. И для синего это ненадёжно: на резисторе остаются 0,3 В, и разброс падения ±0,1 В от экземпляра к экземпляру даёт ток от 3,3 до 6,7 мА — вдвое. Схема RGB, перенесённая с 5-вольтовой платы, при 3,3 В даёт другой цвет; для синего и зелёного лучше питание 5 В через ключ (раздел 52) или источник тока.
82.4 Сдвиговый регистр: shiftOut и SPI
74HC595 принимает биты по одному и выставляет их на восемь выходов
(раздел 22.1). В Arduino его заполняют функцией
shiftOut(данные, такт, порядок, значение) — это программная передача,
бит за битом. На STM32 то же делает аппаратный SPI: MOSI — данные,
SCK — такт сдвига, защёлка — обычный вывод.
void hc595_send(uint8_t v)
{
GPIOA->BRR = 1u << 4; /* RCLK — низкий */
SPI1->DR = v;
while (!(SPI1->SR & SPI_SR_TXE))
;
while (SPI1->SR & SPI_SR_BSY) /* дождаться последнего бита */
;
GPIOA->BSRR = 1u << 4; /* фронт RCLK — байт на выходы */
}
При делителе SPI на 8 (9 МГц от 72) байт уходит за 0,89 мкс. SPI по
умолчанию передаёт старшим битом вперёд — как shiftOut(…, MSBFIRST, …).
Какие выходы загорятся. По каждому такту бит со входа входит в QA,
а содержимое сдвигается к QH. Значит, первый отправленный бит
оказывается в QH, последний — в QA:
| Отправлено | Порядок | Горят |
|---|---|---|
B10101010 |
старшим вперёд | QB, QD, QF, QH |
B10101010 |
младшим вперёд | QA, QC, QE, QG |
В примерах часто пишут, что B10101010 со старшим битом вперёд
зажигает QA, QC, QE, QG, — модель регистра показывает обратное.
Правило простое: при старшем бите вперёд бит n числа — это выход номер n
(QA — бит 0).
Защёлка. Пока не пришёл фронт RCLK, выходы держат старое значение:
в тесте на выходах было 0F, в регистр задвинули F0 — выходы
остались 0F и стали F0 только после защёлки. Поэтому промежуточные
состояния не видны.
⚠️ ШИМ через
OEинвертирован. Вход разрешения выходовOEактивен нулём. Если подать на него ШИМ, чтобы менять яркость всех восьми выходов разом, то чем больше заполнение, тем темнее: выходы горят, пока наOEноль.⚠️ Ток через одну микросхему. У
74HC595есть предел суммарного тока по выводам питания — по документации 70 мА. Восемь светодиодов по 20 мА — это 160 мА, за пределом; по 8 мА — 64 мА, в пределе.
82.5 Динамическая индикация
Четыре разряда по восемь сегментов напрямую — 32 вывода. Если зажигать
разряды по очереди, хватит 12: восемь линий сегментов общие, и четыре
вывода выбирают разряд. Через два 74HC595 — три вывода на всё.
Цифры — таблица, где бит 0 — сегмент a, бит 6 — g:
const uint8_t seg7[10] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};
Развёртку вызывают из прерывания таймера (раздел 13):
typedef struct {
uint8_t digits[4]; /* что показать, по разрядам */
uint8_t pos; /* какой разряд горит сейчас */
} disp_t;
/* Вызов из прерывания таймера. Вернёт сегменты, номер разряда — в *digit. */
uint8_t disp_tick(disp_t *d, uint8_t *digit)
{
*digit = d->pos;
uint8_t seg = seg7[d->digits[d->pos] % 10];
d->pos = (uint8_t)((d->pos + 1) & 3);
return seg;
}
Тест показал для «1234» разряды 0, 1, 2, 3 по очереди с кодами 06,
5B, 4F, 66 и возврат к нулевому. Состояние — 5 байт.
Порядок в обработчике. Погасить текущий разряд → выставить сегменты
следующего → зажечь следующий разряд. Если сменить сегменты при горящем
разряде, он на мгновение покажет чужую цифру, и на соседних разрядах
появятся слабые «тени». С двумя 74HC595 то же: сначала задвинуть
и защёлкнуть байт с погашенными разрядами, потом — новые сегменты
и новый разряд.
Четыре числа, которые решают схему:
- частота: переключение 1 кГц даёт каждому разряду 250 обновлений в секунду и по 1 мс свечения (разделы 22.3 и 57.4);
- яркость: разряд горит четверть времени, поэтому для 5 мА в среднем нужен импульс 20 мА — в пределах того, что допускает индикатор;
- ток общего вывода: при импульсе 20 мА на сегмент восемь сегментов — это 160 мА через вывод разряда, вшестеро больше предела вывода F103 (раздел 62.6). Разряд выбирают транзистором, а не выводом микроконтроллера напрямую;
- ток регистра сегментов: через
74HC595те же 8 × 20 мА = 160 мА при пределе микросхемы 70 мА (82.4). Либо импульс меньше (8 мА на сегмент — 64 мА, индикатор тусклее), либо сегменты ведёт отдельный драйвер — сборка транзисторов или сдвиговый регистр с мощными выходами.
82.6 Грабли
Плавное включение светодиода начинается со скачка. 8-битный ШИМ: первая ступень — 3,5 % воспринимаемой яркости.
Яркость меняется «неравномерно». Нет поправки на глаз.
RGB даёт не тот цвет. Одинаковые резисторы на все каналы или питание 3,3 В для синего и зелёного.
RGB с общим анодом горит «наоборот». Заполнение инвертировано.
В регистре горят не те выходы. Перепутан порядок битов: при старшем вперёд бит n — выход n.
Индикатор мерцает промежуточными значениями. Защёлку дёргают до окончания передачи или не дёргают вовсе.
ШИМ на OE работает наоборот. OE активен нулём.
Сдвиговый регистр греется. Суммарный ток восьми светодиодов выше предела микросхемы.
Динамический индикатор тусклый. Каждый разряд горит четверть времени — нужен больший импульсный ток.
На соседних разрядах слабые «тени» чужих цифр. Сегменты сменили при горящем разряде: сначала погасить разряд, потом менять сегменты (82.5).
Вывод микроконтроллера на общем проводе разряда вышел из строя. 160 мА через один вывод; нужен транзистор.
82.7 Итоги
- Коэффициент заполнения — импульс на период, от 0 до 1; скважность в строгом смысле — обратная величина.
analogWrite— 8-битный ШИМ; таймер STM32 даёт 16 бит на 1,1 кГц при 72 МГц. Первая ступень 8 бит — 3,5 % воспринимаемой яркости.- Квадрат желаемой яркости — дешёвая поправка на глаз: середина шкалы — 25 % заполнения против 21,8 % у степени 2,2.
- У RGB резисторы разные; при 3,3 В синий и зелёный почти без запаса; общий анод инвертирует заполнение.
shiftOutна STM32 заменяет аппаратный SPI; при старшем бите вперёд бит n числа — выход n регистра; защёлка скрывает промежуточные состояния;OEактивен нулём.- Динамическая индикация: 12 выводов вместо 32, 1 кГц на четыре разряда, импульсный ток вчетверо выше среднего, разряд — через транзистор.
Дальше: раздел 83 «I²C и SPI на прототипе: датчик и дисплей».