Оверклокинг процессора или памяти — это понятно, но зачем разгонять подсветку монитора?
Речь пойдёт о стареньком 23-дюймовом Samsung SyncMaster BX2340 (выпущен в январе 2011) со светодиодной подсветкой. Со временем стал замечать, что работать за ним утомительно, а сосредоточиться всё сложнее. И даже не только работать, просто читать, например. Сам монитор остался тот же, но мне стало труднее. А за другими экранами работалось вполне нормально.
Как-то в интернетах читал про субъективные ощущения пользователей телефонов с OLED дисплеями с частотой обновления 240 Гц. Жаловались на утомляемость и головную боль. И были упоминания (без пруфов) исследований по влиянию частоты диммирования подсветки на организм: хотя глаз не видит мерцания в 240 Гц,
Затем на ютубе попался ролик про переделку подсветки монитора на постоянный ток. Вмешательство в схему было кардинальным, и размеры деталей этого блока не вписались в габариты копуса монитора (ЕМНИП). Под роликом были комментарии о смещении цветов при низких токах на сведиодах. А у меня подсветка работает на значениях 10-25%, т. к. помещение довольно тёмное.
Так было решено оставить управление яркостью с помощью ШИМ, но увеличить частоту. Я даже не стал мерять мерцание неинвазивным методом с помощью фоторезистора или фотодиода, сразу разобрал монитор.
Контроллер подсветки — OZ9993CN. Нормального даташита не оказалось, только групповой драйверов подсветки производства O2Micro. Выяснилось, что драйвер занимается также и повышением напряжения (согласно измерениям с 14,4 В до 54,6 В) с использованием мощного внешнего полевого транзистора и индуктивности.
Одна из схем похожего по смыслу драйвера, номера выводов не совпадают:
На плате дорожка сигнала ШИМ на драйвер подписана как B-Dim (Backlight dimming?), искать не пришлось. Далее в дело вступил клон цифрового USB-осцилографа USBee AX в сочетании с sigrok на стороне ПК. Замер показал, что частота подсветки 180 Гц (маловато будет!). Высокий уровень сигнала — 5 В.
Теперь нужно как-то поднять частоту ШИМ до килогерцовых значений, раз в 16. Первое, что пришло в голову влепить в разрыв дорожки ШИМ микроконтроллер для приёма сигнала и воспроизведения его в 16 раз ускоренном варианте. Нужны 2 таймера, один будет измерять длительность низкого и высокого уровней, другой — выдавать сигнал ШИМ. Подобрав коэффициенты предделителя, обойдёмся вообще без арифметики, просто копированием. Нет, Ардуино не будет. Ассемблера тоже не будет, будет GCС. Мелким МК с минимум двумя таймерами (из имеющихся в запасе) оказался ATtiny15. Но WinAVR не хочет с ним работать, поэтому пришлось взять более старшую версию — ATtiny45 (ATtiny25/85 так же подойдут).
Схема:
100n
┌───────┤├───────┐
│ ┌────────────┐ │
│ │ 1 8 ├─┴─ VCC
│ │ 2 7 ├─ PB2 (INT0) INPUT
│ │ 3 6 ├─ PB1 (OC1A) OUTPUT
GND ─┴─┤ 4 5 │
└────────────┘
ATtiny45
Подбираем множители предделителей таймеров. Частоту CPU возьмём примерно 8 МГц, от встроенного RC-генератора.
- Измерительный таймер. Сколько тактов в периоде диммирования? . Чтобы это влезло в восьмибитный регистр таймера с минимальной потерей точности, предделитель возьмём 256, максимальное значение счётчика будет .
- Таймер ШИМ. Частоту сделаем в 16 раз больше: , тогда предделитель во столько же раз меньше: .
Входной сигнал заведён на ножку внешнего прерывания. Обработчик оного:
/* External Interrupt 0 */
ISR(INT0_vect, ISR_NAKED) {
uint8_t timer = TCNT0; // Значение таймера измерения интервалов
if (PINB & 1<<PB2) { // Входящий сигнал. Высокий уровень - окончание цикла измерения
OCR1C = timer; // Период ШИМ
TCNT0 = 0; // Обнуление таймера измерения интервалов
}
else { // Низкий уровень - скважность
OCR1A = timer; // Скважность ШИМ
}
reti();
}
while(1) {}
), и что не будет вызовов из подпрограмм. Ну и в конце прописывам возврат из функции с взведением флага разрешения прерываний reti()
.
Спаял, прошил — и оно заработало!
Но дроссель стал пищать. Смотрим, что там на затворе полевика, управляющим током через силовой дроссель:
С дросселем всё в порядке, он продолжает работать на частоте 320 кГц, но если раньше частота ШИМ была 180 Гц и почти не слышна (только если поднести ухо), то 2,9 кГц очень хорошо слышно. И комфорта явно не прибавилось. А что если вывести частоту за верхнюю границу слышимости? Например, ? Меняем множитель предделителя таймера ШИМ с 16 на 2, прошиваем. Оказалось, что всё в порядке. Почти.
Восьмибитных таймеров в данном случае недостаточно, нужно больше минералов. Проявляется это в виде низкочастотных флуктуаций яркости, с плавным нарастанием и исчезновением периодичностью в несколько секунд. Чтобы справиться с этой напастью, можно взять кристалл пожирнее, но это не наш путь. Будем наращивать разрядность измеряющего таймера программным путём и введём порог (гистерезис) для надёжного обнаружения переключения яркости пользователем (0–100 с дискретностью 1). Точность измерительного таймера поднимем в 256 раз, и множитель предделителя становится равным 1.
Обработчик переполнения измерительного таймера с вариантом «что-то пошло не так и длительность уровня затянулась»:
/* Timer/Counter0 Overflow */
ISR(TIM0_OVF_vect, ISR_NAKED) {
#define TIME_H_LIM (UCHAR_MAX-1)
if (time_h < TIME_H_LIM) { // Normal way
time_h += 1;
}
else { // High part overflowed
if (PINB & 1<<PB2) {
OCR1A = TIME_H_LIM; // Always on
}
else {
OCR1A = 0; // Always off
}
OCR1C = TIME_H_LIM;
time_h = 0;
time_cycle = 0;
time_on = 0;
}
reti(); // Because ISR_NAKED
}
Внешнее прерывание теперь обрабатывается тоже несколько сложнее:
/* External Interrupt 0 */
ISR(INT0_vect, ISR_NAKED) {
// F_CPU / Timer1 prescaler / F_PWM_IN / grades / 4
#define THRESHOLD (F_CPU / 1 / 180 / 100 / 4)
uint16_t time;
uint8_t time_l = TCNT0;
if ((TIFR & 1<<TOV0) && (time_l <= UCHAR_MAX/2)) { // Overflow occured right now
time_l = UCHAR_MAX; // 0xff
}
time = (time_h << 8) + time_l;
if (PINB & 1<<PB2) { // Risen
if (abs(time - time_cycle) > THRESHOLD) {
time_cycle = time;
OCR1C = time_h;
}
TCNT0 = 0;
time_h = 0;
if (TIFR & 1<<TOV0) {
TIFR = 1<<TOV0; // Clear Timer0 overflow flag
}
}
else { // Falled
if (abs(time - time_on) > THRESHOLD) {
time_on = time;
OCR1A = time_h;
}
}
reti(); // Because ISR_NAKED
}
Появились глобальные переменные, которые я загнал в регистры, у нас же оверклокинг как-никак. SRAM используется только для сохранения адреса возврата при входе в обработчики прерываний. Старшая часть счётчика измерения интервалов находится в переменной time_h, а величины измеренной длины цикла ШИМ и скважности — в time_cycle и time_on соответственно. THRESHOLD — порог детекции изменения яркости.
Вот теперь всё заработало, как и задумывалось.
/*
PWM frequency multiplier x128
100n
┌───────┤├───────┐
│ ┌────────────┐ │
│ │ 1 8 ├─┴─ VCC
│ │ 2 7 ├─ PB2 (INT0) INPUT
│ │ 3 6 ├─ PB1 (OC1A) OUTPUT
GND ─┴─┤ 4 5 │
└────────────┘
ATtiny45
fuses: lfuse=0xe2 hfuse=0xdf
*/
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <stdlib.h>
#include <limits.h>
#define F_CPU 8000000UL
#define F_PWM_IN 180U
register uint8_t time_h asm("r4"); // High part of time counter
register uint16_t time_cycle asm("r12"); // Period
register uint16_t time_on asm("r14"); // H level duration
__attribute__((naked)) int main(void) {
time_h = 0;
time_cycle = 0;
time_on = 0;
ACSR |= 1<<ACD; // Comparator disable
// Timer0
TCCR0A = 0;
// CK/1
TCCR0B = 1<<CS00;
// Timer1
DDRB |= 1<<PB1; // PWM output
// CK/2, Clear the OC1A output line
TCCR1 = 1<<CTC1|1<<PWM1A|2<<COM1A0|2<<CS10;
TIMSK |= 1<<TOIE0; // Timer0 overflow
// Ext int 0
MCUCR |= 1<<ISC00; // Any logical change on INT0 generates an interrupt request
GIMSK |= 1<<INT0; // External Interrupt Request 0 Enable
PORTB |= 1<<PB2; // Input
wdt_enable(WDTO_120MS); // Watchdog on
sei(); // Interrupts enable
while (1) { // Do not use flags or registers
wdt_reset(); // Watchdog reset
}
}
/* External Interrupt 0 */
ISR(INT0_vect, ISR_NAKED) {
// F_CPU / Timer1 prescaler / F_PWM_IN / grades / 4
#define THRESHOLD (F_CPU / 1 / F_PWM_IN / 100 / 4)
uint16_t time;
uint8_t time_l = TCNT0;
if ((TIFR & 1<<TOV0) && (time_l <= UCHAR_MAX/2)) { // Overflow occured right now
time_l = UCHAR_MAX; // 0xff
}
time = (time_h << 8) + time_l;
if (PINB & 1<<PB2) { // Risen
if (abs(time - time_cycle) > THRESHOLD) {
time_cycle = time;
OCR1C = time_h;
}
TCNT0 = 0;
time_h = 0;
if (TIFR & 1<<TOV0) {
TIFR = 1<<TOV0; // Clear Timer0 overflow flag
}
}
else { // Falled
if (abs(time - time_on) > THRESHOLD) {
time_on = time;
OCR1A = time_h;
}
}
reti(); // Because ISR_NAKED
}
/* Timer/Counter0 Overflow */
ISR(TIM0_OVF_vect, ISR_NAKED) {
#define TIME_H_LIM (UCHAR_MAX-1)
if (time_h < TIME_H_LIM) { // Normal way
time_h += 1;
}
else { // High part overflowed
if (PINB & 1<<PB2) {
OCR1A = TIME_H_LIM; // Always on
}
else {
OCR1A = 0; // Always off
}
OCR1C = TIME_H_LIM;
time_h = 0;
time_cycle = 0;
time_on = 0;
}
reti(); // Because ISR_NAKED
}
Можете называть это самовнушением, но результат такой: жить стало лучше, жить стало веселей! Даже сдвинулись давно зависшие проекты.
Если в вашем случае частота подсветки никак не влияет на самочувствие и продуктивность — считайте, что вам повезло. Наверное. Как и людям, уверяющим, что им абсолютно комфортно при содержании CO2 в помещении более 0,2% (2000 м. д.).
Автор: jaiprakash