Содержание
Урок 1. Первый проект
Урок 2. Управление кнопками
Урок 3. Подключение LCD
Урок 4. Использование ШИМ
Урок 5. Таймеры
Урок 6.1. Статическая индикация
Урок 6.2. Динамическая индикация
Урок 7.1. Генерация звука
Урок 7.2. Генерация звука. Продолжение
Урок 8.1. Передача данных через UART
Урок 8.2. Передача данных через UART. Продолжение»
Урок 9. Передача данных через SPI
Урок 10. Изучение АЦП. Простой вольтметр
Урок 11. Получение синуса при помощи ШИМ
Урок 12. Измерение температуры
Урок 13. Внешние прерывания.
Урок 14. Использование отладчика
Урок 15.1. Управление инкрементальным энкодером
Урок 15.2. Управление громкостью, при помощи энкодера
Урок 16. Управление RGB светодиодом
Урок 17. Использование ИК
Урок 18.1. Знакомство с графическим дисплеем
Урок 18.2 Вывод изображения на графический дисплей
Урок 18.3 Вывод русскоязычного текста
Урок 19. Формирование сигнала, при помощи ЦАП (R2R)
Урок 20. Опрос матричной клавиатуры
Урок 21. Сторожевой таймер
Урок 22.1 Воспроизведение wav. Введение.
Урок 22.2 Воспроизведение wav. Продолжение.
Урок 23.1 Работа с внешней памятью
Урок 23.2 Работа с файловой системой Fat

adc_logАЦП — Аналого-цифровой преобразователь. Из названия можно догадаться, что на вход подается аналоговый сигнал, который преобразуется в число.

Первое о чем нужно сказать — АЦП микроконтроллера умеет измерять только напряжение. Чтобы произвести измерение других физических величин, их нужно вначале преобразовать в напряжение. Сигнал всегда измеряется относительно точки называемой опорное напряжение, эта же точка является максимумом который можно измерить. В качестве источника опорного напряжения (ИОН), рекомендуется выбирать высокостабильный источник напряжения, иначе все измерения будут плясать вместе с опорным.

Одной из важнейших характеристик является разрешающая способность, которая влияет на точность измерения. Весь диапазон измерения разбивается на части. Минимум ноль, максимум напряжение ИОН. Для 8 битного АЦП это 2^8=256 значений, для 10 битного 2^10=1024 значения. Таким образом, чем выше разрядность тем точнее можно измерять сигнал.

Допустим вы измеряете сигнал от 0 до 10В. Микроконтроллер используем Atmega8, с 10 битным АЦП. Это значит что диапазон 10В будет разделен на 1024 значений.  10В/1024=0,0097В — с таким шагом мы сможем измерять напряжение. Но учтите, что микроконтроллер будет считать, величину 0.0097, 0.0098, 0.0099… одинаковыми.

Тем не менее шаг в 0,01 это достаточно неплохо. Однако, есть несколько рекомендаций, без которых эта точность не будет соблюдена, например для измерения с точностью 10бит, частота на которой работает АЦП должна быть 50-200 кГц. Первое преобразование занимает 25 циклов и 13 циклов далее. Таким образом, при частоте 200кГц мы сможем максимум выжать
200 000/13 = 15 384 измерений.

В качестве источника опорного напряжения можно использовать внутренний источник и внешний. Напряжение внутреннего источника (2,3-2,7В) не рекомендуется использовать, по причине низкой стабильности. Внешний источник подключается к ножке AVCC или Aref, в зависимости от настроек программы.

При использовании АЦП ножка AVCC должна быть подключена. Напряжение AVCC не должно отличаться от напряжения питания микроконтроллера более чем на 0,3В. Как было сказано, максимальное измеряемое напряжение равно опорному напряжению(Vref), находится оно в диапазоне 2В-AVCC. Таким образом, микроконтроллер не может измерить более 5В.

Чтобы расширить диапазон измерения, нужно измерять сигнал через делитель напряжения. Например, максимальное измеряемое напряжение 10В, опорное напряжение 5В. Чтобы расширить диапазон измерения, нужно уменьшить измеряемый сигнал в 2 раза.

Формула для расчета делителя выглядит так:

Uвых =  UвхR2/(R1 + R2)

Подставим наши значения в формулу:

5 = 10*R2/(R1+R2)

(R1+R2)=2*R2

R1=R2

т.е. можно взять любые два одинаковых резистора и подключить их по схеме
adc6

Следовательно, когда мы измеряем напряжение через делитель, нужно полученное значение АЦП умножить на коэффициент=Uвых/Uвх.

Полная формула вычисления измеряемого напряжения будет выглядеть так:
U=(опорное напряжение*значение АЦП*коэффициент делителя)/число разрядов АЦП

Пример: опорное 5В, измеренное значение АЦП = 512, коэффициент делителя =2, АЦП 10разрядный.

(5*512*2)/1024=5В — реальное измеренное значение напряжения.

Некоторые программисты пишут программу так, чтобы микроконтроллер автоматически вычислял коэффициент делителя, для этого выходной сигнал измеряют образцовым прибором и заносят это значение в программу. Микроконтроллер сам соотносит истинное напряжение каждому значению АЦП, сам процесс однократный и носит название калибровки.

Перейдем к программной реализации. Создаем проект с указанными параметрами. Также подключим дисплей на порт D для отображения информации.

adc3

Измерение будет производиться в автоматическом режиме, обработка кода в прерывании, опорное напряжение подключаем к ножке AVCC. По сути нам нужно только обрабатывать получаемые данные. Измеренные данные хранятся в переменной adc_data[0]. Если нужно опрашивать несколько каналов, то выбираем какие каналы сканировать, а данные будут для ножки 0 в adc_data[0], для ножки 1 в adc_data[1] и т.д.

В основном цикле добавим строки:

result=((5.00*adc_data[0])/1024.00); //пересчитываем значение АЦП в вольты
sprintf(lcd_buffer,»U=%.2fV»,result);   //помещаем во временную переменную результат
lcd_puts(lcd_buffer);                          //выводим на экран

Небольшое замечание, чтобы использовать числа с плавающей точкой, нужно в настройках проекта изменить (s)printf Features: int, width на float, width, precision. Если этого не сделать десятые и сотые мы не увидим.
adc_feature

Таким образом, мы всего лишь перевели значение АЦП в вольты и вывели на дисплей. Результат в протеусе выглядит так:
adc1

Резистором можно менять напряжение, измеряемое напряжение выведено на дисплей. При сборке на реальном железе к ножке Aref нужно подключить конденсатор на 0,1мкФ. Урок получился немного сложным, но думаю он вам понравится.

Файл протеуса и прошивка:

Update:
Измерение тока:
adc_current

258 комментариев: Урок 10. АЦП в AVR микроконтроллерах. Простой вольтметр на AVR.

  • Разобрался.

  • Объясните, пожалуйста, механизм получения информации от контроллера — дисплею? И что делают «проводки», конкретно каждый, входящие-выходящие в/из дисплея?

  • Комментарий от:
    Валерий on 25.04.2014 в 13:22
    Получается после активации АЦП я не могу пользоваться ножками ADC1,2,3 как обыкновенными портами?
    Ваш ответ:
    admin on 26.04.2014 в 08:11
    при работе с АЦП рекомендуют на этот порт вообще ничего больше не вешать, только измерение
    Мой вопрос:
    А все же если нужно использовать 2 ножки под АЦП остальные как дискретные например под управление LCD как поступить?

  • Андрей: Использовать STM32F030K6T6 как альтернативу ATmega8. Там каждая нога настраивается как хочется. 😀

  • Алексей. По поводу STM32F030K6T6 я уверен что вы полностью правы, но и в Atmega8, да и любого контроллера так-же можно назначать выводы по своему усмотрению. Иначе это не разумно. У Atmega8 5 АЦП если мне нужен 1 то что к другим ножкам ничего не подключай? В CodeVision AVR есть такая надпись Digital input buffers при настройках АЦП, по-моему нужно указывать там какие АЦП использовать а какие нет.
    P.S. Наверно наш Админ совсем «закрутился» в своих делах, раз мы уже пытаемся на сайте форум организовать.

  • И по теме, какие сложности с АЦП.
    ADMUX = 0; // Номер канала АЦП
    Настройка АЦП осуществляется в регистре ADCSR.
    0-й бит ADPS0
    1-й бит ADPS1
    2-й бит ADPS2 // Эти три бита отвечают за делитель частоты.
    Рекомендуемая частота должна лежать в пределах от 50 до 200 кГц
    Отсюда видно что значения этих трех бит могут принимать значение от 0 до 7.
    Ноль и единица делят частоту кварца на 2, а далее с увеличением на единицу
    4, 8, 16, 32, 64, 128 соответственно. Например частота кварца 8 МГц, 8000000/64=125000.
    То есть 125 кГц. В диапазон вошли, а это значит что число отвечающее за делитель на 64 будет 6. Пишем:
    ADCSR |= (1 << ADPS1) | (1 << ADPS2); // Настроили делитель.
    3-й бит ADIE разрешение прерывания по окончанию преобразования.
    4-й бит ADIF флаг окончания преобразования. Если прерывания включены, то при выставлении этого флага программа ломанется в обработчик прерывания. По нему можно определить конец преобразования.
    5-й бит ADFR это выбор работы АЦП 1-непрерывный, 0-по запросу.
    6-й бит ADSC сюда записываем 1 для старта преобразования. Сбрасывается сам после преобразования.
    7-й бит ADEN разрешение работы АЦП 1 можно 0 нет.

    Вот и вся сложность. После выбора делителя пишем.
    ADCSR &= ~(1 << ADFR); // Запуск преобразования вручную
    ADCSR |= (1 << ADEN); // Разрешаем работу АЦП
    В теле main() пишем.
    int data = 0;
    while(1)
    {
    ADCSR |= (1 << ADSC); // начать преобразование.
    delay_ms(10); // потупим чуток
    data = ADCW; // В data положили значение преобразования.
    }
    Как-то так. С учетом того что изначально выбрали канал 0, все остальные ноги работают просто как порт ввода/вывода.
    Данная программа будет работать в AtmelStudio 6 точно, за CVAVR не ручаюсь, но синтаксис должен совпасть.

  • Подскажите пожалуйста, что означает (float) в выражении
    y = (float)adc_data[0] * K / (1 + (float)T); без него пишет сообщение что команда будет выполнятся длительно? Про длительность понятно.

  • Андрей, (float)adc_data[0] говорит о том что данные находящиеся в ячейке adc_data[0] будут приведены к типу float независимо от текущего типа данного массива.

  • Добрый день, есть Atmega8_32PIN, можно ли использовать ADC6 и ADC7 наравне с ADC0-ADC5 для измерения каналов напряжения? и как к ним обратиться в Codevision?

  • можно, все точно также как и с остальными каналами

  • Добрый день. Сразу хочу поблагодарить за замечательный сайт. Очень много полезного. Я только начинаю и пытаюсь разобраться в вашей программе совсем мало комментариев, если вам не сложно, могли бы вы подробно прокомментировать каждую строку. Я собрал часы по вашему примеру про Дисплей, прикрутил 16Мгц кварц, и кнопки, все четко работает ,спаял, собрал) и Вольтметр по вашей проше работает, а вот по инициализации вообще не могу разобраться, как совместить программы, как подключить разные выводы АЦП, где и в какой строчке задаются… В прошлых статьях было более детально, если снимите видео где прокомментирована каждая строка., будет Вообще ГУД. 😉

  • И если вам не сложно-закомментируйте и разъясните тот пример что с амперметром- он по интереснее будет. ❗ 😉

  • Выводы фиксированные, привязаны к порту C, т.е. вывод ADC0 это PC0, соответственно результат в adc_data[0].
    ADCSRA — ADC Control and Status Register, настройки скорости и биты начала преобразования.
    номер вывода подключенного к АЦП задается в мультиплексоре ADMUX
    Более детально в даташите, смысла его копировать нет

  • Здравствуйте, а как преобразовать float в char и переправить например по UART или SPI. Заранее спасибо

  • float это 4 char. так что пишите как массив

  • А можно поподробнее. А то я дилетант в МК. Вот что например мы принимаем данные с АЦП и необходимо их преобразовать в char как прописать сам массив?

  • Подскажите, а как сделать частотомер с динамической индикацией?
    Насколько я понимаю, нужно в вечном цикле считать импуьсы до прерывания по таймеру, после срабатывания прерывания записывать значение в переменную. Но вот проблема как при этом постоянно выводить динамическую индикацию? Если в томже вечном цикле, то максимальная измеряемая частота уменьшится на количество тактов для динамической индикации и обойти это можно только внешними регистрами. Или можно индикацию сделать по второму прерыванию, но при этом пострадает точность измерения.
    Правильно я понимаю?

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

  • При использовании АЦП, настраивать порт на вход без подтягивающего резистора?

  • что нужно изменить чтобы из этого вольтметра сделать милливольтметр ?

  • Здравствуйте admin, столкнулся я с такой проблемой, собираю модуль управления лабораторным блоком питания на базе Atmega16, все 8 АЦП у меня задействованы, 4 шт. идут на показания напряжения 36, 24, 12 и 5 вольт, а 4шт. на регулируемые выходы, регулировку произвожу при помощи 4-х ШИМов, все вроде как хорошо, вольтметры спокойно измеряют показания блоков питания, но вот при измерении изменяемого напряжения ШИМ(через транзистор, все как полагается) показания вольтметра или «0» или полное, мультиметром замерял — вольтаж плавно регулируется, все нормально. Пробовал менять частоты АЦП и ШИМ — ничего не помогало. Вот данные настроек:
    // Timer/Counter 0 initialization
    // Clock source: System Clock
    // Clock value: 31,250 kHz
    // Mode: Fast PWM top=0xFF
    // OC0 output: Inverted PWM
    // Timer Period: 8,192 ms
    // Output Pulse(s):
    // OC0 Period: 8,192 ms Width: 8,192 ms
    TCCR0=(1<<WGM00) | (1<<COM01) | (1<<COM00) | (1<<WGM01) | (1<<CS02) | (0<<CS01) | (0<<CS00);
    TCNT0=0x00;
    OCR0=0x00;
    /////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Timer/Counter 1 initialization
    // Clock source: System Clock
    // Clock value: 31,250 kHz
    // Mode: Fast PWM top=0x00FF
    // OC1A output: Inverted PWM
    // OC1B output: Inverted PWM
    // Noise Canceler: Off
    // Input Capture on Falling Edge
    // Timer Period: 8,192 ms
    // Output Pulse(s):
    // OC1A Period: 8,192 ms Width: 8,192 ms
    // OC1B Period: 8,192 ms Width: 8,192 ms
    // Timer1 Overflow Interrupt: Off
    // Input Capture Interrupt: Off
    // Compare A Match Interrupt: Off
    // Compare B Match Interrupt: Off
    TCCR1A=(1<<COM1A1) | (1<<COM1A0) | (1<<COM1B1) | (1<<COM1B0) | (0<<WGM11) | (1<<WGM10);
    TCCR1B=(0<<ICNC1) | (0<<ICES1) | (0<<WGM13) | (1<<WGM12) | (1<<CS12) | (0<<CS11) | (0<<CS10);
    TCNT1H=0x00;
    TCNT1L=0x00;
    ICR1H=0x00;
    ICR1L=0x00;
    OCR1AH=0x00;
    OCR1AL=0x00;
    OCR1BH=0x00;
    OCR1BL=0x00;
    //////////////////////////////////////////////////////////////////////////////////////////////////
    // Timer/Counter 2 initialization
    // Clock source: System Clock
    // Clock value: 31,250 kHz
    // Mode: Fast PWM top=0xFF
    // OC2 output: Inverted PWM
    // Timer Period: 8,192 ms
    // Output Pulse(s):
    // OC2 Period: 8,192 ms Width: 8,192 ms
    ASSR=0<<AS2;
    TCCR2=(1<<PWM2) | (1<<COM21) | (1<<COM20) | (1<<CTC2) | (1<<CS22) | (1<<CS21) | (0<<CS20);
    TCNT2=0x00;
    OCR2=0x00;

    // Timer(s)/Counter(s) Interrupt(s) initialization
    TIMSK=(0<<OCIE2) | (0<<TOIE2) | (0<<TICIE1) | (0<<OCIE1A) | (0<<OCIE1B) | (0<<TOIE1) | (0<<OCIE0) | (0<<TOIE0);

    // ADC initialization
    // ADC Clock frequency: 125,000 kHz
    // ADC Voltage Reference: AVCC pin
    // ADC Auto Trigger Source: ADC Stopped
    ADMUX=ADC_VREF_TYPE;
    ADCSRA=(1<<ADEN) | (0<<ADSC) | (0<<ADATE) | (0<<ADIF) | (0<<ADIE) | (1<<ADPS2) | (1<<ADPS1) | (0<<ADPS0);
    SFIOR=(0<<ADTS2) | (0<<ADTS1) | (0<<ADTS0);

  • добавьте кондер во входу ацп

  • Спасибо большое, действительно помогло, чет я сам не догадался стабилизировать.

  • Не подскажите еще, если микруху посадить на внешний кварц — это отразиться на качество работы АЦП и ШИМ?
    Спасибо.

  • Здравствуйте!. Купил себе LCD 16×2 keypad Shleld, на нем имеется 5 кнопок, при нажатии которых на одном из выводов меняется напряжение. Чтоб задействовать эти кнопки необходимо задействовать ADC микроконтроллера Atmega8. Я использую ADC0, для считывания напряжения. Откуда мне считывать данные АЦП, чтоб использовать из в логических операциях? Подскажите, что я делаю не так? Спасибо!

    while
    {
    if (140<=adc_data[0]<=150) //напряжение 0.7В
    {

    lcd_gotoxy(0,0);
    lcd_putsf("PRESS UP");
    }

    if (320<=adc_data[0]<=330) //напряжение 1.6В
    {
    lcd_gotoxy(0,0);
    lcd_putsf("PRESS DOWN");
    }

    if (adc_data[0]==0) //напряжение 0В
    {
    lcd_gotoxy(0,0);
    lcd_putsf("PRESS RIGHT");
    }

    if (490<=adc_data[0]<=510) //напряжение 2.46В
    {
    lcd_gotoxy(0,0);
    lcd_putsf("PRESS LEFT");
    }

  • есть два варианта: 1. запускать преобразование постоянно и читать в прерывании, 2. запускать преобразование вручную, вызовом функции.

  • это как, запускать преобразование постоянно? Я в последующем ввел переменную u типа char, и в неё записывал значения adc_data[0], при этом на LCD у меня при нажатии кнопок изменялось значение напряжения. Вместо сравнений наподобии if (490<=adc_data[0]<=510) я ввел сравнение if (490<=u<=510), но события от нажимания кнопки в виде появления надписи
    PRESS UP не возникало. При компиляции в CodeVisionAVR появляется предупреждение, наподобие того, что я немного не правильно ввел пределы. Не могу понять что я делаю не так. Заранее спасибо!

  • Вы все делаете не так 🙂 В статье выше указано как сгенерить проект с прерыванием от АЦП, даже скрин есть с галочками. Данные с ацп считываются внутри этого прерывания. Тип char, конкретно для cavr, подразумевает что в него будет писаться число от 0 до 255. Если нужно делать двойное сравнение, то делается это через логические операции: if((490<=adc_data[0]) && (adc_data[0]<=510))

  • Пытаюсь передавать в uart сразу несколько данных( с двух ацп), но не знаю как вывести их в виде data_adc_1;data_adc_2 по крайней мере хочу чтобы в терминале шли данные. Основная проблема как вывести строковую переменную, которая и будет выходом в терминал, состоящую из нескольких char.

    #define F_CPU 8000000L
    #define BAUD 9600L
    #define UBRRL_VALUE (F_CPU/(BAUD*16))-1

    #include
    #include

    void setUART(void);
    void outputUART(unsigned char key);
    void portSetup(void);
    void setADC(void);
    //void readADC(unsigned int adc_input);

    char buf0[5];
    char buf1[5];

    void setADC(void){
    ADMUX = (1<<6);
    ADCSRA|=(1<<ADPS0)|(1<<ADPS1)|(1<<ADPS2);
    ADCSRA|=(1<<ADEN);
    }

    unsigned int readADC(unsigned int ch)
    {
    unsigned char set_admux = ADMUX;
    set_admux&=~((1<<MUX3)|(1<<MUX2)|(1<<MUX1)|(1<<MUX0));
    switch(ch)
    {
    case 0: set_admux|=((0<<MUX4)|(0<<MUX3)|(0<<MUX2)|(0<<MUX1)|(0<<MUX0)); break;
    case 1: set_admux|=((0<<MUX4)|(0<<MUX3)|(0<<MUX2)|(0<<MUX1)|(1<<MUX0)); break;
    case 2: set_admux|=((0<<MUX4)|(0<<MUX3)|(0<<MUX2)|(1<<MUX1)|(0<<MUX0)); break;
    case 3: set_admux|=((0<<MUX4)|(0<<MUX3)|(0<<MUX2)|(1<<MUX1)|(1<<MUX0)); break;

    default: break;
    }
    ADMUX=set_admux;
    _delay_us(10);
    int tmp=0;

    for (int i=0;i<64;i++)
    {
    ADCSRA|=(1<<ADSC);

    while ((ADCSRA&(1<>8;
    UBRRL=UBRRL_VALUE;

    UCSRA=0;

    UCSRB|=(1<<TXEN);
    UCSRC!=(1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0);
    }

    void outputUART(unsigned char key){

    while ((UCSRA&(1<<UDRE))==0);
    UDR=key;
    }

    void portSetup(void){
    DDRA&=~((1<<0)|(1<<1));
    }

    void send_Uart_str(unsigned char *s)// Отправка строки
    {
    while (*s != 0) outputUART(*s++);
    }

    int main(void)
    {

    portSetup();
    setUART();
    setADC();

    while(1)
    {

    char a=(readADC(0)+'0');
    char b=(readADC(1)+'0');

    outputUART(a+b);

    _delay_ms(400);

    }
    }

  • про прием http://avr-start.ru/?p=2230 про склейку переменных http://avr-start.ru/?p=4557

  • на 8 меге всё хорошо получилось, и1 и 8 вольтметров, но нужно мне на 32меге и вот тут полный затык. есть какие нибудь принципиальные отличия?

  • вот ведь зараза, целый день колупался, решился написать коментарии а ошибка в протеусе оказалась

  • Админ добавьте пожалуйста про массив adc_data[0] adc_data[1] и тд, а то новичкам вроде меня совсем непонятно было откуда брать данные для одного канала АЦП и для другого. Полдня угробил на выяснение. Ваши уроки очень выручают

  • добавил

  • Спасибо. И кстати не хотели бы написать статью про подключение дисплеев nokia 1100,1110i, 1202 ? Очень животрепещущая тема, если надо у меня есть библиотека для CVAVR под них с шрифтами 5х8, и цифрами побольше 12х16, 24х32. Нарыл за время скитаний по интернету, могу скинуть. И программка для рисования недостающих шрифтов и символов. Проверял в железе -работает с дисплеями 1110i и 1202. Для 1202 добавляется задержка в init LCD, без нее не заводился.

  • Здравствуйте! Нужна помощь, сам не могу придумать как сделать. Допустим к АЦП подключаем терморезистор, так-же мерим падение напряжения на нем, получаем вольты, а что делать дальше? К примеру adc_data[0] = 500 это 0 градусов, а adc_data[0] = 1023 это 100 градусов. По какой формуле правильно привести adc к градусам? Интересует именно не от 0 adc, а от 500 допустим.

  • пропорцией. вообще если заморочиться, то стоит снять экспериментальную зависимость и посмотреть участки, где зависимость линейная, разбить их на разные коэффициенты, т.е. if(adc > 0) && (adc < 100) t = k * adc, где k будет разная для разных участков.

  • Судя по даташиту, полученное после преобразования значение, храниться в регистрах ADCH:ADCL. Однако у Вас указана adc_data[0], в АВР студио пользуются ADC. Вот тут не понятно. Там какие-то фоновые действия по считыванию этих регистров и записи в указанные переменные? Где это можно глянуть в раскрытой форме?

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Последние комментарии
  • Загрузка...
Счетчик
Яндекс.Метрика