Оверклокинг процессора или памяти — это понятно, но зачем разгонять подсветку монитора?
Речь пойдёт о стареньком 23-дюймовом Samsung SyncMaster BX2340 (выпущен в январе 2011) со светодиодной подсветкой. Со временем стал замечать, что работать за ним утомительно, а сосредоточиться всё сложнее. И даже не только работать, просто читать, например. Сам монитор остался тот же, но мне стало труднее. А за другими экранами работалось вполне нормально.
Как-то в интернетах читал про субъективные ощущения пользователей телефонов с OLED дисплеями с частотой обновления 240 Гц. Жаловались на утомляемость и головную боль. И были упоминания (без пруфов) исследований по влиянию частоты диммирования подсветки на организм: хотя глаз не видит мерцания в 240 Гц, мозг на него реагирует. А постоянное свечение или с частотой более 3 кГц не нагружает мозг таким образом.
Затем на ютубе попался ролик про переделку подсветки монитора на постоянный ток. Вмешательство в схему было кардинальным. Под роликом были комментарии о смещении цветов при низких токах на сведиодах. А у меня подсветка работает на значениях 10-25%, т. к. помещение довольно тёмное. UPD: В ролике у автора была только одна гирлянда светодиодов, а у меня — 4.
Было решено оставить управление яркостью с помощью ШИМ, но увеличить частоту. Я даже не стал мерять мерцание неинвазивным методом с помощью фоторезистора или фотодиода, сразу разобрал монитор.
Контроллер подсветки — 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 ¦
L-------------
ATtiny45
/* 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()
./* 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 / 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
}
/*
PWM frequency multiplier x128
100n
--------++-------¬
¦ -------------¬ ¦
¦ ¦ 1 8 +-+- VCC
¦ ¦ 2 7 +- PB2 (INT0) INPUT
¦ ¦ 3 6 +- PB1 (OC1A) OUTPUT
GND -+-+ 4 5 ¦
L-------------
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
}
К сожалению, не доступен сервер mySQL