суббота, 22 сентября 2012 г.

ArduinoMania

Различные проекты на платформе Arduino.


Проекты на ATMega8

Проекты на АТMega8.
Простые проекты для изучения одного из самых простых и массовых микроконтроллеров.
Для работы вам понадобится как минимум CodeVision AVR2.04.4a и Proteus 7.2 SP6. Если желаете поработать с "железом", вам понадобится и программатор. Советую вот такой - легко собирается, хорошо работает. Единственная проблема с первоначальной прошивкой ATMega8, это можно сделать либо через порт LPT - 5-provodov-avr, либо купить "зашитый" микроконтроллер, я брал здесь.


Светофор №1.

     Простейший светофор на одно направление не требует для создания больших усилий и навыков. Схема собирается за несколько минут. Резисторы подбираются исходя из используемых светодиодов (номинал 470 Ом указан для "советских" АЛ307), следует заметить, что на схеме вывод AVCC подключен к питанию, если его не подключить на реальном микроконтроллере независимо от использования встроенного АЦП (аналого-цифрового преобразователя), то можно вывести микросхему из строя (справедливо для любых контроллеров серии Mega)!!!


     Исходный код настолько простой, что я его представлю прямо здесь. Этот код для Си я нашёл в интернете и немного подправил, однако, любой программист скажет вам, что этот код является бестолковым (далее я опишу, как именно требуется писать такие программы), но зато  - простым, наглядным и рабочим.

/*****************************************************
Chip type : ATmega8
Program type : Application
AVR Core Clock frequency: 1.000000 MHz
Memory model : Small
Data Stack size : 256
*****************************************************/

#include <mega8.h>
#include <delay.h>

void main(void)
{
// Port B initialization
PORTB=0x00;
DDRB=0x00;

// Port C initialization
PORTC=0x00;
DDRC=0x00;

// Port D initialization
PORTD=0x00;
DDRD=0x07;

// External Interrupt(s) initialization
// INT0: Off
// INT1: Off
MCUCR=0x00;

// Analog Comparator initialization
// Analog Comparator: Off
// Analog Comparator Input Capture by Timer/Counter 1: Off
ACSR=0x80;
SFIOR=0x00;

while (1) {

PORTD.2=1; //зеленый загорается
delay_ms(8000); //горит 8 секунд

PORTD.2=0; //Выключаем зеленый
delay_ms(500); //Ждем пол секунды
PORTD.2=1; //Включаем зеленый
delay_ms(500); //Ждем полсекунды
PORTD.2=0; //Выключаем зеленый
delay_ms(500); //Ждем полсекунды
PORTD.2=1; //Включаем зеленый

/*delay_ms(500);//Ждем полсекунды
PORTD.2=0; //Выключаем зеленый
delay_ms(500); //Ждем полсекунды
PORTD.2=1; //Включаем зеленый
delay_ms(500); //Ждем полсекунды*/

PORTD.1=1; //Включаем жёлтый
PORTD.2=0; //Выключаем зеленый
delay_ms(3000); //Ждем 3 секунды

PORTD.0=1; //Включаем красный
PORTD.1=0; //выключаем жёлтый
delay_ms(9000); //на 9 секунд

PORTD.1=1; //включаем жёлтый к красному
delay_ms(3000); //ждем 3 секунды
PORTD.0=0; //Выключаем красный
PORTD.1=0; //и жёлтый
};
}

     Код можно ещё несколько сократить, если учесть, что по умолчанию практически все порты и регистры ATMega8 инициализированы в 0. Теперь для начинающих некоторые пояснения:

#include <mega8.h>
#include <delay.h>

     Это так называемые заголовочные файлы, в них написаны процедуры, которые мы применим из других файлов. Например, процедура delay_ms(X), которая определяет задержку в миллисекундах, была написана другим человеком и содержится в файле delay.с (на самом деле всё может быть не совсем так, или совсем не так, - процедуры может содержать объектный файл типа *.obj, *.asm), а правила её применения описаны в файле delay.h, который мы подключаем.

PORTB=0x00;
DDRB=0x00;

     Зарезервированные имена, обозначающие регистры порта контроллера (его ножки сгруппированные байтом - по 8, каждый бит регистра порта соответствует своей ножке) и регистры направления обмена порта (0-вход, 1-выход)
Например, запись DDRB.5=1 означает что ножка PB5 контроллера настроена на выход, и при записи  PORTB.5=0 или =1, на данной ножке получим 0 (0...0.8 Вольта) или 1 (4...5 Вольт). Разумеется, можно и сразу целиком присвоить значение порту:  PORTB=0xFF, т.е. все ножки порта PB равны 1. 

while(1) {expression};

представляет собой бесконечный цикл, так как 1=TRUE.  

Светофор №2.

      Чтобы получить светофор на два направления, надо модифицировать схему светофора №1:


 и код светофора №1:

/*****************************************************
Chip type : ATmega8
Program type : Application
AVR Core Clock frequency: 1.000000 MHz
Memory model : Small
External RAM size : 0
Data Stack size : 256
*****************************************************/

#include <mega8.h>
#include <delay.h>

void main(void)
{
// Port B initialization
PORTB=0x00;
DDRB=0x00;

// Port C initialization
PORTC=0x00;
DDRC=0x00;

// Port D initialization
PORTD=0x00;
DDRD=0b00111111;

ACSR=0x80;
SFIOR=0x00;

while (1)
{
PORTD.2=1; //зеленый1 загорается Z1
PORTD.3=1; //красный2 загорается K2
delay_ms(8000); //зеленый1 горит 8 секунд

PORTD.2=0; //выключаем зеленый1 ZM1
delay_ms(500); //ждем пол секунды
PORTD.2=1; //включаем зеленый1
delay_ms(500); //ждем полсекунды
PORTD.2=0; //выключаем зеленый1
delay_ms(500); //ждем полсекунды
PORTD.2=1; //включаем зеленый1
delay_ms(500); //ждем полсекунды

PORTD.1=1; //включаем оранжевый1 J1 K2+J2
PORTD.4=1; //включаем оранжевый2
PORTD.2=0; //выключаем зеленый1
delay_ms(2000); //ждем 2 секунды

PORTD.0=1; //включаем красный1 K1
PORTD.5=1; //включаем зеленый2 Z2
PORTD.1=0; //выключаем оранжевый1
PORTD.4=0; //выключаем оранжевый2
PORTD.3=0; //выключаем красный2
delay_ms(8000); //ждём 8 секунд

PORTD.5=0; //выключаем зеленый2 ZM2
delay_ms(500); //ждем полсекунды
PORTD.5=1; //включаем зеленый2
delay_ms(500); //ждем полсекунды
PORTD.5=0; //выключаем зеленый2
delay_ms(500); //ждем полсекунды
PORTD.5=1; //включаем зеленый2
delay_ms(500); //ждем полсекунды

PORTD.1=1; //включаем оранжевый1 к красному1 K1+J1
PORTD.4=1; //включаем оранжевый2 J2
PORTD.5=0; //выключаем зеленый2
delay_ms(2000); //ждем 2 секунды
PORTD.0=0; //Выключаем красный1
PORTD.1=0; //и оранжевый1
PORTD.4=0; //и оранжевый2
};
}

комментарии по коду:

 DDRD=0b00111111;   //0b(b7)(b6)(b5)(b4)(b3)(b2)(b1)(b0)  

справа битовое(двоичное) представление числа в котором слева - самый старший разряд, его же можно было записать в десятичном ( DDRD=63;) или в шестнадцатиричном виде ( DDRD=0x3F;).

void main(void)

main главная процедура, с неё начинается выполнение программы, void означает буквально ничего (нулевой тип), вместо void можно подставить (signed или unsigned) int, long; float, double и так далее (и даже указатель на другую переменную, о чём будет рассказано позже). Причём замена void на какой-либо тип в первой позиции обозначает что данная конструкция  - функция, а не процедура, и может возвращать значение, т.о. она должна быть дополнена внутри словом return.

int fun(void) {
int a=10;
return(a);
}

все изменения производились согласно диаграмме работы:


     Диаграмма работы светофора на два направления с мигающим зелёным сигналом. Данная диаграмма взята также с просторов интернета, и по-моему: миганий зелёного сигнала должно быть больше.



Светофор №3.

     В примерах №1 и №2 было указано на несколько "прямолобое" применение кода, которое позволительно для небольших программ, однако не может быть применено из-за расточительности ресурсами в больших приложениях.
     Для упрощения и сокращения кода в технологических применениях программисты обычно используют автоматы. Автомат является упрощённой и достаточной моделью процесса. Например, всю работу светофора можно разбить на некоторое конечное число состояний, которые будут повторятся с некоторым периодом. Для представления автомата потребуется программный счётчик состояний и дешифратор состояний. Светофор №3, который состоит из транспортного светофора на 4 направления, 4-х пешеходных светофоров, 1-го железнодорожного для переезда, 1-го мигающего пешеходного, было бы проблематично создать без автомата.



Код без обработки пешеходных и ж/д светофоров приведен ниже. Несложно понять что процедуры типа
 void SetRedT1(void) //Транспортный светофор1 включить красный D3
{
ptc|=0x01;
}
устанавливают либо стирают отдельные биты.

В главном цикле значение счётчика i постоянно увеличивается пока не достигнет 48.
i++;
При этом счётчик обнуляется.
if (i==48) {i=0;}; // 24c=0.5*48

В цикле постоянно происходит сравнение значения счётчика с константами, и производится работа с портами. Хоть данный код и более совершенный, чем предыдущие два, но он также более подходит для наглядности чем для работы. Профессионалы чаще всего не используют наборы отдельных операций сравнения, а пользуются операторами switch-case, или вовсе записывают заранее рассчитанные данные в массивы - пользуются готовой таблицей:

flash unsigned char sw[N]={
//G1Y1R1G2Y2R2 XX
0b10000100, //1 Х периодов горят Зелёный1 и Красный2
0b10000100, //2
...
0b00000100, //   Х периодов Зелёный1 мигает,

0b10000100, //
...
0b01001100, //K-1    Зелёный1 гаснет,  загораются  Жёлтый1 и Жёлтый2
0b00110000, //K   Загорается Красный1,  загорается  Зелёный1, гаснут Жёлтый2 и  Красный2

...
0b01101000 //N
};

while (1)
{
i++;
if (i==48) {i=0;}; // 24c=0.5*48 - цикл
 
PORTC=(sw[i]>>x) | mask;
PORTD=(sw[i]>>x) | mask;
delay_ms(500);
};
}

Разумеется, выражения  (sw[i]>>x) | mask описаны условно, всё зависит от положения информации о сигналах в константе sw.

/*****************************************************
Chip type : ATmega8
Program type : Application
AVR Core Clock frequency: 1.000000 MHz
Memory model : Small
External RAM size : 0
Data Stack size : 256
*****************************************************/

#include <mega8.h>
#include <delay.h>

unsigned char ptb=0;
unsigned char ptc=0;
unsigned char ptd=0;

void SetRedT1() //Транспортный светофор1 включить красный D3
{
ptc|=0x01;
}

void ResetRedT1() //Транспортный светофор1 выключить красный D3
{
ptc&=~0x01;
}

void SetYelT1() //Транспортный светофор1 включить жёлтый D2
{
ptc|=0x02;
}

void ResetYelT1() //Транспортный светофор1 выключить жёлтый D2
{
ptc&=~0x02;
}

void SetGrnT1() //Транспортный светофор1 включить зелёный D1
{
ptc|=0x04;
}

void ResetGrnT1() //Транспортный светофор1 выключить зелёный D1
{
ptc&=~0x04;
}

void SetRedT2() //Транспортный светофор2 включить красный D6
{
ptc|=0x08;
}

void ResetRedT2() //Транспортный светофор2 выключить красный D6
{
ptc&=~0x08;
}

void SetYelT2() //Транспортный светофор2 включить жёлтый D5
{
ptc|=0x10;
}

void ResetYelT2() //Транспортный светофор2 выключить жёлтый D5
{
ptc&=~0x10;
}

void SetGrnT2() //Транспортный светофор2 включить зелёный D4
{
ptc|=0x20;
}

void ResetGrnT2() //Транспортный светофор2 выключить зелёный D4
{
ptc&=~0x20;
}


void SetRedT3() //Транспортный светофор3 включить красный D9
{
ptd|=0x01;
}

void ResetRedT3() //Транспортный светофор3 выключить красный D9
{
ptd&=~0x01;
}

void SetYelT3() //Транспортный светофор3 включить жёлтый D8
{
ptd|=0x02;
}

void ResetYelT3() //Транспортный светофор3 выключить жёлтый D8
{
ptd&=~0x02;
}

void SetGrnT3() //Транспортный светофор3 включить зелёный D7
{
ptd|=0x04;
}

void ResetGrnT3() //Транспортный светофор3 выключить зелёный D7
{
ptd&=~0x04;
}


void SetRedT4() //Транспортный светофор2 включить красный D12
{
ptd|=0x08;
}

void ResetRedT4() //Транспортный светофор2 выключить красный D12
{
ptd&=~0x08;
}

void SetYelT4() //Транспортный светофор2 включить жёлтый D11
{
ptd|=0x10;
}

void ResetYelT4() //Транспортный светофор2 выключить жёлтый D11
{
ptd&=~0x10;
}

void SetGrnT4() //Транспортный светофор2 включить зелёный D10
{
ptd|=0x20;
}

void ResetGrnT4() //Транспортный светофор2 выключить зелёный D10
{
ptd&=~0x20;
}

// Declare your global variables here

void main(void)
{
unsigned char i=0;

PORTB=0x00;
DDRB=0xFF;
PORTC=0x00;
DDRC=0xFF;
PORTD=0x00;
DDRD=0xFF;
ACSR=0x80;

while (1)
{
if (i==0) {ResetYelT1();ResetRedT1();SetGrnT1();
ResetYelT2();SetRedT2();
ResetYelT3();ResetRedT3();SetGrnT3();
ResetYelT4();SetRedT4();

}; //0s
if (i==16) {ResetGrnT1();
ResetGrnT3();

}; //8s
if (i==17) {SetGrnT1();
SetGrnT3();

};
if (i==18) {ResetGrnT1();
ResetGrnT3();

};
if (i==19) {SetGrnT1();
SetGrnT3();

};
if (i==20) {ResetGrnT1();SetYelT1();
SetYelT2();
ResetGrnT3();SetYelT3();
SetYelT4();

}; //10s
if (i==24) {ResetYelT1();SetRedT1();
ResetYelT2();ResetRedT2();SetGrnT2();
ResetYelT3();SetRedT3();
ResetYelT4();ResetRedT4();SetGrnT4();

}; //12s
if (i==40) {ResetGrnT2();
ResetGrnT4();

}; //20s
if (i==41) {SetGrnT2();
SetGrnT4();

};
if (i==42) {ResetGrnT2();
ResetGrnT4();

}; //21s
if (i==43) {SetGrnT2();
SetGrnT4();

};
if (i==44) {SetYelT1();
ResetGrnT2();SetYelT2();
SetYelT3();
ResetGrnT4();SetYelT4();

}; //22s
i++;
if (i==48) {i=0;}; // 24c=0.5*48 - цикл

//PORTB=ptb;
PORTC=ptc;
PORTD=ptd;
delay_ms(500);
};
}


Столбчатый индикатор.
     
     Если  в предыдущих примерах использовались цифровые ресурсы микроконтроллера, то данный пример использует аналоговые - АЦП. Аналого-цифровой преобразователь предназначен для дискретных преобразований аналогового сигнала в цифровой код с определённой частотой дискретизации. Для начинающих скажу лишь, что максимальная скорость оцифровки ограничена. 
     Столбчатый индикатор отображает входное напряжение в виде столбца светящихся сегментов (или просто отдельных излучателей), что очень полезно для всяких индикаторов, примерами могут являться столбчатые индикаторы уровня записи (ИУЗ).


     Входное напряжение не должно превышать напряжение питания, и в качестве опорного напряжения используется источник питания микроконтроллера на 5 вольт, в качестве фильтра используется внешний конденсатор на выводе AREF.

Код программы приведен ниже:

/*****************************************************
Chip type : ATmega8
Program type : Application
AVR Core Clock frequency: 4.000000 MHz
Memory model : Small
External RAM size : 0
Data Stack size : 256
*****************************************************/

#include <mega8.h>
#include <delay.h>

#define ADC_VREF_TYPE 0x40 //5V Vcc + C 4.7uF AREF

//PD0 LED1
//PD1 LED2
//PD2 LED3
//PD3 LED4
//PD4 LED5
//PD5 LED6
//PD6 LED7
//PD7 LED8
//PB0 LED9
//PB1 LED10
//PB2 LED11
//PB3 LED12
//PB4 LED13
//PB5 LED14

//PC4 LED15
//PC5 LED16

const
unsigned char PD[17]=
{
0b00000000, //0
0b00000001, //1
0b00000011, //2
0b00000111, //3
0b00001111, //4
0b00011111, //5
0b00111111, //6
0b01111111, //7
0b11111111, //8
//_____________
0b11111111, //9
0b11111111, //10
0b11111111, //11
0b11111111, //12
0b11111111, //13
0b11111111, //14
0b11111111, //15
0b11111111 //16
};
unsigned char PB[17]=
{
0b00000000, //0
0b00000000, //1
0b00000000, //2
0b00000000, //3
0b00000000, //4
0b00000000, //5
0b00000000, //6
0b00000000, //7
0b00000000, //8
//_____________
0b00000001, //9
0b00000011, //10
0b00000111, //11
0b00001111, //12
0b00011111, //13
0b00111111, //14
//_____________
0b00111111, //15
0b00111111 //16
};

// Read the AD conversion result
unsigned int read_adc(unsigned char adc_input)
{
ADMUX=adc_input | (ADC_VREF_TYPE & 0xff);
// Delay needed for the stabilization of the ADC input voltage
delay_us(10);
// Start the AD conversion
ADCSRA|=0x40;
// Wait for the AD conversion to complete
while ((ADCSRA & 0x10)==0);
ADCSRA|=0x10;
return ADCW;
}

void ADC_init()
{
// ADC initialization
// ADC Clock frequency: 125.000 kHz
// ADC Voltage Reference: Vcc, cap. on AREF
ADMUX=ADC_VREF_TYPE & 0xff;
ADCSRA=0x85;
}

void Decode(int signal)
{
PORTD=PD[signal];
PORTB=PB[signal];

if (signal==15) {PORTC |= 0b00010000;PORTC &= 0b11011111;}
else if (signal==16) {PORTC |= 0b00110000;}
else {PORTC &= 0b11001111;}
}

void SetupIO ()
{
// Input/Output Ports initialization
PORTB=0b00000000;
DDRB= 0b00111111;

PORTC=0b00001100;
DDRC= 0b00110000; //PC0,PC1 = ADC0,ADC1, PC2,PC3 setup pins with PUP

PORTD=0x00;
DDRD=0xFF;
}

void main(void)
{
// Analog Comparator initialization
// Analog Comparator: Off
// Analog Comparator Input Capture by Timer/Counter 1: Off
ACSR=0x80;
SFIOR=0x00;

SetupIO();
ADC_init();

while (1)
{
Decode(read_adc(0)*0.049*0.32);
delay_ms(100);
};
}

Практически все приёмы, используемые в данном коде, были рассмотрены выше. Единственно хотелось бы отметить расширенную конструкцию с оператором if:
 if () {}         //если (условие соблюдается) то { }
else if () {}   //иначе  если (условие соблюдается) то { }
else {}         //иначе { }

и выражение read_adc(0)*0.049*0.32
дело в том, что АЦП выдаёт свои значения по шкале 0-1023 (2^10), что соответствует шкале 0-5 вольт (т.к. опорное напряжение 5 вольт), а нам нужно получить сначала вольты, а потом перевести в количество сегментов. 1024*0.049*0.32=16 т.е. вся шкала АЦП, это 16 сегментов.
Число 0.049 взято кратным числу 5 вольт /1024 отсчёта =0.0048828 (~10 отсчётов АЦП), а потом домножено на 0.32 экспериментальным методом, чтобы получить зажигание последнего сегмента при нужном входном напряжении.



Последовательный порт.
     
     А теперь предлагаю окунуться в мир интерфейсов. Самый простой и широко используемый до недавних пор последовательный протокол RS232, хоть и считается устаревшим, но всё ещё входит в состав выпускаемых современных микроконтроллеров - и не в количестве одного, как правило теперь их не менее 4-х. Также, многие устройства, всё еще используют данный интерфейс (например, некоторые экраны VFD NoritakeItron, медленные устройства для производств и т.п.). Данный пример позволяет оценить, насколько легко и просто настроить и использовать RS232 (USART) в микроконтроллерах.


Ниже приведен код, который осуществляет передачу символа 0.

/*****************************************************
Chip type : ATmega8
Program type : Application
AVR Core Clock frequency: 1.000000 MHz
Memory model : Small
External RAM size : 0
Data Stack size : 256
*****************************************************/

#include <mega8.h>

// Standard Input/Output functions
//#include <stdio.h>

#define BAUDRATE 57600
#define BaudValue ((11059200UL/(BAUDRATE*16UL))-1)

void USART_Transmit(unsigned char data)
{
//{while ( !(UCSRA & (1<<UDRE)) ); //Ожидание опустошения буфера приема
UDR = data;
while( !(UCSRA & (1<<UDRE)) );
//UDR = data; //Начало передачи данных
}

void USART_init(void)
{
UBRRH = BaudValue >> 8;
UBRRL = BaudValue & 0xFF;
//
UCSRB=(1<<RXEN)|(1<<TXEN);
DDRD=0x02;
UCSRC = ( 1<<URSEL ) | ( 3<<UCSZ0 );
//UCSRA = (1<<U2X_Value);
}

void main(void)
{
ACSR=0x80;
SFIOR=0x00;
USART_init();
while (1) {USART_Transmit(0x30);};
}


Учтите, что Proteus не поддерживает эмуляцию  UCSRC (установка параметров связи  порта), поэтому установите частоту генератора 3500000, а в терминале поставьте следующие параметры: 





суббота, 8 сентября 2012 г.

Детектор скрытой проводки

Привет всем.

          Проводка вещь важная, и делать ремонт надо всегда аккуратно, вешаешь ли ты полку или делаешь шкаф-купе, или просто картину надо "примостить". Беда в том что проводка в наших домах в большинстве случаев, несмотря на все ГОСТы и т.д., "лежит как ей угодно" (или тому, кто её делал), иногда даже под углом 45 градусов (чтобы сэкономить несколько граммов меди или алюминия) и выяснить, как именно она проложена без экстрасенса порой не представляется возможным. Но выход всё-таки есть - воспользоваться детектором проводки.               Что же такое - детектор проводки? Это прибор который улавливает на антенну электрическое или магнитное поле (или электромагнитные волны) проводника находящегося под напряжением (или подключенному к фазному проводу).
          В интернете полно схем таких приборов, основанных на КМОП логике (К176ЛЕ5, К561ЛН1, К561ЛЕ5 и даже на счётчиках CD4017). Все эти схемы имеют ужасные недостатки: реагируют на любые частоты, кроме нужной (даже на мобильный телефон), могут быть легко повреждены статическим электричеством (особенно  К176ЛЕ5 , которая не имеет защиты от ESD) и не позволяют производить регулировку "на ходу" в широких пределах, что часто необходимо при работе (проводка может быть расположена на разной глубине, и напряжение, а значит сила электро-магнитного поля может существенно колебаться в разных случаях.
          На мой взгляд, для входной цепи детектора проводки идеален ОУ, т.к. он имеет приемлемые свойства и достаточно хорошую защиту от наводок, меньше логики боится ESD, и позволяет производить т.н. резонансное усиление, т.е. каскад на ОУ как правило содержит ещё и фильтр.
          После долгих поисков с пристрастием я остановил свой выбор на схеме с ОУ К140УД1208 отсюда http://tehpoisk.ru/articles/schemiskatskrat. Это достаточно качественный и экономичный программируемый операционный усилитель для мобильных применений. Схема приведена на Рис.1:
Рис.1 - Искатель скрытой проводки на КР140УД1208 со световой и звуковой индикацией

          После некоторых раздумий удалил правую часть схемы как ненужную и неэкономную в плане применения элементов логики.

Рис.2 -  Искатель скрытой проводки на КР140УД1208

          Вот так-то лучше, - подумал я и собрал схему (слева на макетке). 


Но...Оказалось что по свечению светодиода не совсем просто определить (измерить) максимум поля от провода, вернее - то место, где находится сам провод. Зона, где светодиод из-за своей нелинейности светится практически одинаково, довольно-таки большая (сантиметра 3...5), с помощью ручки "чувствительность" и карандаша (водим по стене и рисуем метки) это более-менее можно устранить, но работать (особенно когда тебя ждут километры проводки) не совсем удобно, поэтому возникла идея добавить к этому творению столбчатый индикатор (вольтметр) на A227D (отечественный аналог К1003ПП1) или на микроконтроллере ATTiny15L http://radiokot.ru/circuit/audio/other/14/, которую я нашёл в интернете и тут же подправил "под свои нужды":

Рис.3 - Индикатор уровня сигнала на ATTiny15L (доработка, автор - Робин ван Aрем)

          В этой схеме рекомендую также не забывать ставить конденсаторы 100нФ по питанию. 
Однако, её я так и не собрал-в моих запасах "Тиньки" не обнаружилось. Решил я по-народному на ATMega8 собрать.
          На беду мне под руку попался немецкий индикатор VQC10, и вот что получилось.

          По-быстрому на макетке соорудил схему.

Это она же в протеусе (за неимением модели VQC10 - заменил рассыпучкой)

Вид на самодельный AVR910.

Это то, что я планировал отображать на дисплее.







          Продолжение следует...