Несколько постов назад я заикнулся о том, что выдам на гора программу-пример для работы с АЦП. Пора за базар отвечать :) Делать мы будем простенький цифровой вольтметр с замашками осциллографа. Точнее осциллографом это можно назвать с большой натяжкой, скорей самописец. Так, побаловаться.
Задача:
Получить по очереди напряжение с трех каналов АЦП и отправить его по последовательному порту в комп. По запросу с компа показвыать напряжение на каждом из измеряемых каналов. В компе полученный поток байт представить в виде графика.
Теоретическую часть я уже разобрал, осталось поставить эксперимент в реальном железе.
Собираем схему на демоплате Pinboard.
Нам нужны три разных напряжения. Их проще всего получить с переменных резисторов, включенных потенциометрами. При этом средняя точка переменного резистора подключается к каналу АЦП, а крайние точки к +5 и GND питания. При этом при вращении рукоятки резистора напряжение на его средней точке будет меняться от нуля до +5 вольт. Резистор, подключенный к каналу ADC0 уже так включен и никаких лишних движений не требует. А вот два других надо будет подключить. На видео и на фотках хорошо видно что и куда идет.
![]() |
Теперь осталось написать код. Код я сделаю на прерываниях и конечных автоматах.
Вначале переменные:
1 2 3 4 5 | ; RAM ======================================================== .DSEG RX_sel: .byte 1 ; Переменная состояния отправки данных ADCH_sel: .byte 1 ; Переменная текущего канала АЦП ADCCH: .byte 8 ; Восемь байт под хранение результатов АЦП. По байту на канал. |
Таблица прерываний стандартная, ее я приводить не буду. Сами увидите в проекте, что можно будет скачать в конце статьи
Прерывание по приему байта. Работает просто. Принятый байт проверяет на код цифры 0, 1 или 2. Если совпадает — то выставляет соответствующую переменную и в терминал начинают валиться данные с этого канала АЦП. Если не совпадает — выставляет значение которое блокирует отправку любых данных.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | ; Interrupts ============================================== RX_OK: PUSHF ; Сохраняем SREG и R16 в стеке IN R16,UDR ; Берем принятый байт CPI R16,'0' ; Case 0 BREQ Ch_0 CPI R16,'1' ; Case 1 BREQ Ch_1 CPI R16,'2' ; Case 2 BREQ Ch_2 LDI R16,3 ; Defaul RJMP EXIT_RX Ch_0: LDI R16,0 ; Если выбран 0 канал RJMP EXIT_RX Ch_1: LDI R16,1 ; Если выбран 1 канал RJMP EXIT_RX Ch_2: LDI R16,2 RJMP EXIT_RX ; Если выбран 2 канал EXIT_RX: OUT UDR,R16 ; Даем эхо и инициируем передачу. STS RX_sel,R16 ; Сохраняем выбраное значение канала POPF ; Достаем регистры из стека RETI |
Прерывание по окончании отправки одного байта берет следующее значение и шлет его снова. Тем самым инициализируюя непрерывную передачу данных по прерываниям.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | ;-------------------------------------------------------------------- TX_OK: PUSHF ; Сохраняем флаги и R16 LDS R16,RX_sel ; Смотрим какой канал надо слать CPI R16,0 ; Case 0 BREQ Send0 CPI R16,1 ; Case 1 BREQ Send1 CPI R16,2 ; Case 2 BREQ Send2 ; Default RJMP EXIT_TX Send0: LDS R16,ADCCH ; Шлем выбраный канал в UART OUT UDR,R16 ; Смещение адреса канала задается относительно базы ADCCH RJMP EXIT_TX Send1: LDS R16,ADCCH+1 ; Тут лежат данные первого канала OUT UDR,R16 RJMP EXIT_TX Send2: LDS R16,ADCCH+2 ; А тут второго. Адреса все фиксированные. OUT UDR,R16 RJMP EXIT_TX EXIT_TX: POPF ; Достаем все из стека, выходим. RETI |
Прерывание готовности АЦП работает несколько сложней. Суть в том, что работа АЦП у нас идет по такому алгоритму:
Запускаем одиночное АЦП преобразование и ждем прерывания готовности АЦП.
В прерывании забираем данные.
Сохраняем данные.
Переключаем канал с которого мы снимали показания.
Запускаем следующее преобразование (уже для другого канала)
Выходим из прерывания и ждем следюущего прерывания.
Данный алгоритм хорош тем, что все делается автоматически, в режиме конечного автомата. В результате у нас в памяти, в массиве ADCCH, всегда лежат 8 свежих значений, снятых с 8ми каналов АЦП. Остается их только считать и использовать. При этом главный цикл крутится по своим делам и не парится, зная, что свежие значение всегда его ждут.
В самом прерывании активно используется работа с масками. Для того, чтобы сменить номер канала. Номер канала лежит в последних трех битах регистра ADMUX и может иметь значение от 0 до 7 (000 и 111 соотвествтенно). И нам надо в каждом вызове увеличивать значение канала, перебирая их по очереди. Просто так икрементировать ADMUX нельзя, т.к. кроме номера канала там лежат еще и быти управления АЦП, выравнивания и опорного напряжения — они могут сбиться. Приходится изощряться с масками, чтобы их не задеть.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | ADC_OK: PUSHF PUSH ZL PUSH ZH PUSH R17 IN R16,ADMUX ; Берем ADMUX ANDI R16,0x07 ; Маской отрезаем лишние биты. Получаем номер канала ; С которого было снято измерение. MOV R17,R16 ; Cохранили копию номера канала LDI ZL,low(ADCCH) ; Берем адрес начала массива с будущими данными. LDI ZH,High(ADCCH) ADD ZL,R16 ; Прибавляем к адресу наш номер канала. ; Если было переполнение, то будет флаг С CLR R16 ; Флаг важен, а значение в R16 уже нет. Но нам нужен ноль ; Возьмем и сделаем его из R16. ADC ZH,R16 ; Сложим флаг возможного переполнения с ZH ; Т.о. у нас получается в Z = адрес (ADCCH+номер канала) ; И значения из разных каналов ложатся в разные переменные массива ; с адресом базы ADCCH IN R16,ADCL ; Младшее значение нам не надо. Но считать его нужно. IN R16,ADCH ; Берем в R16 значение из АЦП ST Z,R16 ; Сохраняем его в массив по нужному адресу IN R16,ADMUX ; Опять взяли ADMUX ANDI R16,0xF8 ; На этот раз обнулили номер канала. Оставив остальные биты нетронутыми INC R17 ; Увеличили на 1 заныченный ранее номер канала ANDI R17,0x07 ; Обрезали лишние биты, чтобы не было переполнения. ; Число по маске 0х07 в принципе не может быть больше 7. ; А каналов у нас от 0 до 7. То что надо. OR R16,R17 ; Слепили старое значение из ADMUX c новым значением номера OUT ADMUX,R16 ; Канала. Т.е. по факту сделали MUX = MUX+1 выбрав следующий канал ; Спихнули его в регистр ADMUX. Все, следующий канал выбран. Можно запускать ; Следующее преобразование. OUTI ADCSRA,(1<<ADEN)|(1<<ADIE)|(1<<ADSC)|(0<<ADATE)|(3<<ADPS0) ; Запустили POP R17 ; Корректный выход из прерывания. POP ZH POP ZL POPF RETI |
Также в программе есть инициализация UART (я запустил его на скорости 1200) и инициализация и первичный запуск АЦП.
Стоит обратить внимание на инициализацию UART — там мы разрешаем прерывания по приему и передаче.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | ;Internal Hardware Init =========================================== Reset: STACKINIT ; Инициализация стека RAMFLUSH ; Очистка памяти ; Usart INIT .equ XTAL = 8000000 .equ baudrate = 1200 .equ bauddivider = XTAL/(16*baudrate)-1 uart_init: LDI R16, low(bauddivider) OUT UBRRL,R16 LDI R16, high(bauddivider) OUT UBRRH,R16 LDI R16,0 OUT UCSRA, R16 ; Прерывания разерешены, прием-передача разрешен. LDI R16, (1<<RXEN)|(1<<TXEN)|(1<<RXCIE)|(1<<TXCIE)|(0<<UDRIE) OUT UCSRB, R16 ; Формат кадра - 8 бит, пишем в регистр UCSRC, за это отвечает бит селектор LDI R16, (1<<URSEL)|(1<<UCSZ0)|(1<<UCSZ1) OUT UCSRC, R16 ; ADC Init ; Опорное напряжение с AVCC, выравнивание влево, канал 0. OUTI ADMUX,1<<REFS0|1<<ADLAR|0<<MUX0 ; End Internal Hardware Init =================================== ; Run ========================================================== ; Запуск однократного преобразования. Все остальное сделаем в прерываниях. SEI OUTI ADCSRA,(1<<ADEN)|(1<<ADIE)|(1<<ADSC)|(0<<ADATE)|(3<<ADPS0) ; End Run ====================================================== |
Первый раз АЦП запускается вручную. А потом начинает молотить по прерываниям самостоятельно.
Главный же цикл вообще пустой. Он тут и не нужен. Там можно смело писать любой код, а данные АЦП загребая из памяти. Главное помнить где они лежат ;)
1 2 3 4 | ; Main ========================================================= Main: NOP ; Весь цимес в прерываниях. Нам тут делать нефиг. RJMP Main |
Прошиваем программу в контроллер, жмем RESET
Запускаем теперь чумовую программку Terminal и подсоединяемся на скорости 1200бод. Ничего не происходит, но не беда. Надо сказать контроллеру какой какой вывод АЦП мы хотим поглядеть. Отправляем 0 — в ответ нам полетят данные с нулевого канала. Если отправить 1, то с первого, а если 2, то со второго.
Включив режим показа графика начинаем яростно крутить резисторы из стороны в стороны, наблюдая как наша кривая описывает зигзаги.
![]() |
Сложно? Нет! Буквально пара команд, а сколько удовольствия!
В АЦП заложено столько возможностей, поэтому этой статьей рассказ об АЦП не оканчивается. В следующий раз я расскажу о простейших алгоритмах фильтрации данных, а также о том как сделать АЦП из аналогового компаратора в тех МК, где АЦП не было и в помине.
Данная статья была полностью переписана с учетом заточки курса на демоплату Pinboard. Но предыдущий вариант остался в виде архива.










Не смотрел код, но по описанию я не вижу здесь проверки завершения передачи данных по УАРТу. Представления не имею, надо ли это в данном случае, задние полушария говорят, что АЦП медленнее УАРТа при средних скоростях, но все же.
И вообще. Если юзается восьмибитный УАРТ, то ИМХО правильнее писать не так. В обработчике прерывания АЦП скидываем значение в переменную — и ВСЕ. Потом в основном цикле та переменная сливается в УАРТ, но только если завершена предыдущая передача. Т.о. не будет никаких перегрузок пропускной способности УАРТа только из-за скорости АЦП. То же самое можно делать и в обработчике, просто я не любитель растягивать обработчики хоть на один байт больше необходимого.
Отправку в УАРТ из переменной можно поручить обработчику конца передачи.
недавно наткнулся на uzebox — где чел собрал игрой комп на основе авра.
вот это я понимаю ) полное изучение возможностей + тетрис под рукой.
всегда мечтал свой такой изобрести, возможно скоро начну.
имхо на таком приятней изучать, что и как
http://belogic.com/uzebox/index.htm
Видел я этот комп. Прикольная вещь получилась, ага.
А почему бы и нет? Игрались в своё время на синклере? А мега по производительности рвёт z80 как Тузик грелку. Ведь RISC всё-таки. (Хотя, глядя на систему команд z80, систему команд меги трудно назвать сокращённой)
Сделать комп — не проблема. Проблема — программы. Собрав Синклера или хотя бы Микрошу, ты сразу получал доступ к сотням уже готовых программ для них, в том числе и компиляторы Бэйсика, Ассемблера, Форта, редакторы текстовые и графические, игры, и много еще чего. Для Синклера была даже удобная оболочка для загрузки программ, похожая на Нортон Командера. Сделав же свой, не похожий на другие, комп, ты должен будешь писать все это сам, начиная с драйверов клавиатуры, дисплея, джойстиков, графики, и кончая самими играми, редакторами, компиляторами. Даже собрав в 84г свой компьютер, совместимый программно и аппаратно (по основным сигналам шины) с «МИКРО 80″, я уже мог воспользоваться его программой «МОНИТОР», и BASIC, и Макро Ассемблером, хотя и в 16-ричном коде пописал немало, в основном программки для программаторов. Тогда, в начале пути, это было интересно и вызывало большой энтузиазм. Сейчас, когда без мыша и Виндов вроде и делать нечего, самодельный комп покажется скучной и никчемной железякой. Ну, соединишь между собой пару микросхем и разьемы, подключишь к телевизору, потыркаешься в «TETRIS». Через неделю все это надоест, и ты закинешь его подальше… И пойдешь опять гонять Фар кри или Контрол страйк.
точно такие же мысли можно подвести под всё, что тут описывается.
минироботы на пиках и аврах? в магазине есть уже давно на порядок лучше и по цене доступные. плавают и летают.
какие-нибудь самодельные осциллографы? хороший hp цифровой — это сказка.
сенсоры, передатчики, забавные игрушки? так же по пунктам.
такой миникомп нужен в основном не для игр или ностальжи, а для того, чтобы чел, его собравший, поверил в свои силы. 90% из моих знакомых, кто знает про такое — только на словах могут объяснить, как они легко его соберут.
зы! кроме того, сделать-придумать узкоспециализированную задачу для такого компа думаю, что не сложно. написать один раз программу и пусть он сидит и тупо с видимокамеры движущихся человечков засекает или из шума голоса нужные гармоники )
Так это и будет не компьютер, а микроконтроллер. И под конкретную задачу его можно хорошо оптимизировать. И почему бы тогда не сделать его в виде робота? Возможностей и интереса будет намного больше, чем просто у коробки на столе, привязанной кабелем к телевизору. Одни мозги, без какой-либо периферии, когда не на что реагировать и нечем управлять — это скучно…
тогда с роботом тоже надо подходить основательно.
я помню, как в детстве читал книжки про «это» и ходил в соответствующие кружки. в районе 60-70ых таких роботов делали, что выглядели как роботы )))
и всякой механики, и научных идей. и всё из подручных элементов. тем бы людям наши вычислительные мощности — они б на луну ракету запустили )
Так и запускали. И не только ракеты, но и пара луноходов месяцами бегали, и пробурили Луну на несколько метров вглубь, и колонку грунта метра три длиной на Землю привезли еще в начале 70х. У американцев в то время была только высадка на Луну, и то до сих пор многие американцы в это не верят, хотя вроде аж несколько человек побывало.
Потом были полеты к Марсу, Венере, комете Галлея… Кстати, у американцев с Венерой ничего серьезного так и не получилось, и они предпочли более простой и удобный Марс. А Советские аппараты на Венеру опускались, и даже некоторое время работали на поверхности, при 500 градусной температуре.
Роботу не обязательно выглядеть как человек или роботы из фильмов. Конструкция должна быть рациональной и функциональной. А вот уж над функциональностью стоит повозиться. Робот для одной функции — это просто автомат. Чем их больше — тем интересней, тем «разумней» он будет выглядеть и действовать, тем больше опыта приобретешь. Ну и двухсторонняя связь желательна для интерактивного общения. Он тебе постоянно сообщает о своем состоянии и окружении, ходе выполнения задач, ты даешь ему новые задания…
я пролистал последние выпуски журнала Хацкер, про который пишет ДиХальт
там роботами называют даже моторчик со свето- и фотодиодом.
роботы, которые летали на Венеру — эт хорошо. но я сомневаюсь, что на этом блоге есть больше 2ух человек, кто хотя бы раз удачно запускал своего робота )
Было бы «культурней» в обработчике UART сохранять регистор SREG, ибо флаги портятся (командой CPI …). Это хорошо, что в основном цикле пусто, а то были бы глюки.
И может стоить заметить, что читать ОДНУ половинку ADCx можно только старшую (ADCH). А если бы понадобилась только младшая (допустим если сдвиг вправо), то читать надо обязателно обе и притом сначала ADCL, а потом ADCH.
Ну я же говорю все через задницу тут :) По поводу половинок регистра ацп, да, но на то есть теоретическая часть :) Ну и я планирую еще пару практических примеров по АЦП сделать. С алгоритмами усреднения и накопления. Типо программное повышение точности.
И, кстати первый коментарий совершенно справедлив. И дело не в скоростях UART и АЦП. У тебя передача по УАРТ в двух местах (эхо при приёме с компа и при завершении АЦП), и в обоих не проверяется его готовность. Могут быть накладки, хотя и не смертельно…
Ага и это тоже, согласен. УАРТ захлебывается и теряет байты. В данном случае это некритично т.к. они один фиг идут потоком из более быстрого АЦП, но на ответственной передаче такой поток недопустим.
Стоолкнулся вот с такой проблемкой:
Мое устройство изначально безошибочно работало (не важно , что выполняло)результат пересылало в ПК, но потом я задумал снимать аналоговую велечину. Для проверки АЦП я через реастат подавал напряжения в пределах от 0.2-3.5В на вход ADC0. И Вот что случилось: все замерло. Промучался полдня и не фига не выходит. Есть подозрения, что мог запортачить микруху, но как? разве можно через вход ацп?
При этом программатор ее не видит и УАРТ перестал код гнать. Сразу скажу, что осцилографа нет.
Помогите словом. Что случилось?
Можно побробней о USB датакабеле на чипе Profilic. Это куда его тут воткнуть можно? И разве в USB дата-кабелях есть чипы?
P.S. Описочка:
«О том что значат быти конфигурации UART»
д.б. «биты»
У меня есть такой кабель. Покупал для своего телефона Motorola E365 (ныне покойного). Купил в стандартном ларьке, торгующими телефонами за 100 рублей. Сам удивился цене. При установки в систему, насколько я помню, создаёт виртуальный COM порт.
Помогите сделать такое же на stk500? Что то у меня не выходит :(
з.ы. Atmega 8535
DI HALT, подскажи, пожалуйста, на чём можно сделать. ТЗ такое: сделать многоканальный вольтметр с подключением по USB. Скорость опроса вольтметра не очень высока: на большинстве каналов хватит 10-20 в секунду; но надо сделать один-два побыстрее для некоего подобия осциллографа в звуковом диапазоне. Число каналов от 4х. А на стороне компа желательно чтобы это всё программами виделось как COM-порт[ы], ибо написано много в Инете про него.
На форуме Elementus уже сделал почти такое. Можешь у него спросить, правда он на пиках.
А так все просто — АЦП опрашивает по очереди каналы и шлет данные в сом порт.
Нужна помощь. ниче понять не могу. мега8 работает на 8Мгц от внутреннего генератора. посылаю 2 байта данных с компа по ЮАРТ на скорости 9600 — работает нормально, контроллер шлет их обратно, меняю скорость — бред получаю.
Может у тебя срыв стека или кривой выход из прерывания? Или конфликт прерываний. А на скорости 9600 все очень удачно разруливалось само собой?
Скорость (значение бодрейта) верно высчитал?
работаю через FT232RL. может это как-то влияет?
Я через фт всегда гнал не обращая внимания на скорость.
с мегой 16 та же фигня. на 9600 шлет с Ацп по юарту на terminal четко, а на других скоростях такое гонит. надо бы через настоящий com посмотреть, а не через FT232.
Все понял. На медленных скоростях, до 38400, и без кварца все нормально, а вот на высоких — гонево. Поставил кварц заточенный под ЮАРТ — 7.3728 Мгц- и все прет как надо на 115200.
что то никак :( кварц 7.3728 Мгц скорость 9600, в терминал летят одни буквы «Я» на любую посылку, причем гадит пока дисконект не сделаешь. как сделать на основе этого проектика заглушку для уарта т.е чтоб эхо контроллер реализовывал.
Я собрал схему на Атмеге16, которая считывает с помощью АЦП значение напряжения на средне
й ножке потенциометра (как в приведенной в данной статье схеме), и выводит содержимое регистра ADCH в PORTB. А к порту B я подключаю плату с восемью светодиодами. Т.е. по идее, должно работать так: я кручу ручку потенциометра туда-сюда, а светодиоды показывают содержимое ADCH. Однако, так происходит лишь до некоторого значения напряжения (где-то 0.8 В), по достижении которого АЦП как бы замораживается и в порт B выводится комбинация, соответствующая этому значению напряжения — 0.8 В. При этом если я кручу ручку потенциометра назад, на порту B все равно остается эта комбинация. После отключения питания при следующем запуске снова работает.
Не подскажите, что бы это могло быть? Может, какая-то защита срабатывает от чего-то? Код несколько раз перепроверил, даташит перечитал, ничего такого нет.
Подскажите, пожалуйста, заранее спасибо.
При старте АЦП запускаю на одно преобразование, затем каждый раз в прерывании АЦП вывожу ADCH в PORTB и снова запускаю АЦП.
При этом весь код:
http://easyelectronics.ru/repository.php?act=view&id=19
а у меня не работает=(..прошил 16 мегу,подключил к макс232 и к КОМ-порту…включаю терминалку…отправляю 0,1 …и ничего..что делать?
Такая проблема с ацп.
Написал код, по команде с uart запускающий АЦП на одно преобразование и затем выдающий на uart результат. После первой отправки результата он не реагирует на команды АЦП, потому что считает, что принятый ’1′ не равен ’1′ в блоке switch.. case (если менять на if, результат тот же). К тому же через некоторое время после такой отправки контроллер начинает спамить в uart случайными символами. Я понимаю, что при отправке ломаю конфигурацию, но как и чем, не пойму. Код написан на Си, с использованием прерываний.
Сорри, ложная тревога.