Electronics Engineering BLOG

Блог об электронике

Измеритель напряжения с передачей данных на ПК по USB.

| 1 комментарий

Измеритель-напряжения-с-передачей-данных-на-ПК-по-USB_izmeritel-naprjazhenija-s-peredachej-dannyh-na-pk-po-usb_Собранное_устройствоПредставленное устройство предназначено для измерения входного напряжения амплитудой от -10 до +10В. Измеренный уровень сигнала в цифровом виде передаётся на ПК по USB. Питается устройство непосредственно от шины USB. Устройство оснащено светодиодными индикаторами индицирующими передачу данных в ПК, приём данных из ПК, подключение питания. Устройство очень простое. Это одно из первых разработанных и собранных мной устройств на микроконтроллере. Единственная сложность с которой может столкнуться начинающий радиолюбитель – монтаж СМД компонентов. Видео по монтажу СМД компонентов можно посмотреть у меня на сайте, перейдя по ссылке (уже скоро…). Это не измерительный прибор, это «показометр» он не может гарантировать какой либо точности измерений. Тем не менее, вольтметр на AVR может быть интересен начинающим радиолюбителям, осваивающим микроконтроллеры AVR, так как в данном устройстве используются АЦП и USART модули микроконтроллера.

Задача состояла в том, чтобы сделать устройство оцифровывающее аналоговый сигнал, и передающее данные на ПК для дальнейшей обработки. Данный прибор нужен был для демонстрации основ обработки и передачи данных для студентов первого курса в университете, в котором я учился. Единственным требованием преподавателя было — диапазон измеряемых напряжений от -10 до 10В. Так как демонстрация должна была быть из серии: «светим на фотодатчик, напряжение выросло. Накрыли его от источника света, напряжение уменьшилось» и всё в таком духе, то точность измерений не является основной задачей. Следовательно, я старался упростить устройство и для себя я сформировал ещё несколько требований:

  • Устройство должно питаться от USB порта. (мне не хотелось городить дополнительный блок питания)
  • Устройство должно измерять переменное входное напряжение относительно общего провода питания устройства (для возможности измерять напряжение на каком либо другом устройстве также питающимся от USB)

В итоге было разработано радиоэлектронное устройство схема которого представлена на рисунке.

Измеритель-напряжения-с-передачей-данных-на-ПК-по-USB_izmeritel-naprjazhenija-s-peredachej-dannyh-na-pk-po-usb_Схема При сигнале от -10 до +10В, размах напряжения на входе получается 20В. Для того, чтобы подать данный сигнал на АЦП его нужно пропорционально уменьшить, то есть поделить. В данной схеме входной сигнал делится резистивным делителем состоящим из R9, R10 (верхнее плечё) и R8 – нижнее плечё. По моим подсчётам, размах напряжения после делителя должен был составлять 2В  (от -1 до +1В). Так как АЦП данного микроконтроллера не может измерять отрицательное напряжение (относительно общего провода его питания (GND), необходимо каким либо образом сдвинуть диапазон (от -1 до +1В) в область положительных значений амплитуд, например  от 0 до 2В. Значения из данного диапазона довольно просто измерить АЦП микроконтроллера. Есть несколько способов как правильно сдвинуть входное напряжение не потеряв в точности, а так как у меня не стояла задача сделать измерительный прибор, то я постарался всё упростить по максимуму. Смещение задаётся резисторами R5, R6, R7. Это плохой вариант смещения входного напряжения, если рассматривать его с точки зрения получения высокой точности, но очень хороший вариант с точки зрения простоты. А простота это то, что и было мне нужно! На элементах DA1, R14, R15, R16 собран источник опорного напряжения на 3В. На делителе R11, R12, R13 формируется напряжение 2 В для опорного напряжения АЦП, которое АЦП принимает за максимальное при измерениях. Сердцем устройства является микроконтроллер AtMega8. Я использовал микроконтроллер в TQFP корпусе, и номера выводов на схеме соответствуют данному корпусу для поверхностного монтажа.  Микроконтроллер, при помощи встроенного АЦП, измеряет амплитуду входного сигнала, оцифровывает его и отправляет полученный 8-ми битный результат по USART в преобразователь USB-USART выполненный на микросхеме FT232R. Исходный код был написан на ассемблере. Вернее сказать не был написан, а был использован готовый код с сайта http://easyelectronics.ru/, который я немного подправил под себя. В те времена, когда  я делал данное устройство, я плохо разбирался в микроконтроллерах, в ассемблере, и код правил методом проб и ошибок, так что вы не судите строго. Код корявый, но устройство работает. Скачать исходники проекта можно в конце статьи. Теперь немного пробежимся по коду:

.include "m8def.inc"              ; Используем ATMega8
;Макрос

; Загрузка числа в порт

.MACRO outi                ; Это описание макроса. Везде где outi встретится в коде, то заменяется на

LDI        R16,@1          ; этот кусок кода, причем @0,@1 это параметры, они заменятся введенными параметрами
OUT        @0,R16          ; макроса. Данный макрос тупо копирует введенное число сначала в регистр R16, а из него

.ENDMACRO

Здесь всё просто, первым делом подключаем файл m8def.inc с описанием регистров ввода-вывода, и описанием других конфигурационных ячеек периферии микроконтроллера AtMega8. Дальше описан макрос, который позволяет упростить работу программисту и переложить её на компилятор. Все мы знаем что записать сразу число в регистр ввода вывода в микроконтроллере AVR не получится, нужно использовать промежуточный регистр, например R16. Хотя запись числа в регистр ввода вывода очень востребованная и часто используемая операция.

Для того чтобы в  PotrB установить число 10 необходимо написать следующее:

LDI          R16, 10
OUT        PortB, R16

Использование макроса (outi @0, @1) позволяет не писать каждый раз в тексте программы запись через данный промежуточный регистр, а просто написать к примеру outi PotrB, 10 всё остальное сделает компилятор. При данной записи компилатор сначала загрузит число 10 (операнд @1) в регистр R16 а затем из R16 в PotrB (операнд @0). В прицепи, порядок и последовательность действий не меняется, но сама запись укорачивается и выглядит мене запутанно и громоздко. В любом случае компилятор при компиляции её развернёт, но это уже делаем не мы. Так что макросы очень удобная штука, особенно в языках низкого уровня. С помощью макросов можно сократить рутинные операции или даже написать что-то вроде своего собственного языка программирования.

; Собственно код начинается отсюда

.CSEG

.ORG       0x0000                                   ; Проц стартует с нуля, но дальше идут вектора
RJMP       Reset                                      ; прерываний, поэтому отсяюда сразу же прыгаем

; на начало программы. На метку Reset

.ORG       INT0addr                ; External Interrupt Request 0
RETI

.ORG       INT1addr                ; External Interrupt Request 1
RETI

.ORG       OC2addr                 ; Timer/Counter2 Compare Match
RETI

.ORG       OVF2addr                ; Timer/Counter2 Overflow
RETI

.ORG       ICP1addr                ; Timer/Counter1 Capture Event
RETI

.ORG       OC1Aaddr                ; Timer/Counter1 Compare Match A
RETI

.ORG       OC1Baddr                ; Timer/Counter1 Compare Match B
RETI

.ORG       OVF1addr                ; Timer/Counter1 Overflow
RETI

.ORG       OVF0addr                ; Timer/Counter0 Overflow
RETI

.ORG       SPIaddr                 ; Serial Transfer Complete
RETI

.ORG       URXCaddr                 ; USART, Rx Complete
RJMP       UART_RX

.ORG       UDREaddr                 ; USART Data Register Empty
RETI

.ORG       UTXCaddr                 ; USART, Tx Complete
RJMP       UART_TX

.ORG       ADCCaddr                 ; ADC Conversion Complete
RJMP       ADC_OK

.ORG       ERDYaddr                  ; EEPROM Ready
RETI

.ORG       ACIaddr                   ; Analog Comparator
RETI

.ORG       TWIaddr                   ; 2-wire Serial Interface
RETI

.ORG       SPMRaddr                  ; Store Program Memory Ready
RETI

Дальше, начинается сегмент кода. Первое что описывается в данном сегменте, таблица прерываний для микроконтроллера, в данном случае используется микроконтроллер AtMega8, и таблица соответственно для 8 меги. Для других микроконтроллеров она может отличаться, и у вас ничего не будет работать. Таблицу для вашего микроконтроллера можно посмотреть в даташите.

Из всей таблицы для данного проекта используется всего 3 прерывания: URXCaddr (приняли что-то по USART), UTXCaddr (предыдущий байт по USART отправился, теперь можно передавать новый), ADC_OK (ADC закончило преобразование, и можно забрать результат).

На все остальные прерывания которые нам не нужны установлены заглушки (RETI). Это сделано для минимизации последствий от возможной ошибки при инициализации периферии. Можно ошибиться, и разрешить прерывания по событию которое нам не нужно и следовательно обработчик прерывания для данного события мы не предусмотрели. К примеру, представим что RETI нет, и случайно выставился флаг прерывания OVF0addr, при этом программа остановится, запишет в стек адрес выполняемой инструкции, чтобы потом, после обработки прерывания вернуться и продолжить выполнение программы, и перейдёт к выполнению программы с адреса на котором расположен вектор OVF0addr. Так как из данного адреса мы не попадаем в обработчик прерываний (нет RJMP на нужный адрес), микроконтроллер начнёт выполнять следующую по счёту инструкцию, а именно SPIaddr, так как и тут также нет прыжка в нужное место, микроконтроллер перейдёт к выполнению следующей по счёту инструкции URXCaddr, и тут он уже перепрыгнет на обработчик UART_RX (приняли что-то по USART). Хотя на самом деле микроконтроллер ничего не принял, а произошла ошибка, мы всего лишь ошиблись с 1 битом (случайно разрешили прерывание Timer/Counter0 Overflow)! Подобные неочевидные ошибки с прерываниями очень коварная штука, можно несколько часов потратить на понимая, почему устройство то работает нормально, то сбоит без видной причины. Для того, чтобы избежать подобных нелепых ошибок, все неиспользуемые прерывания закрываются «заглушками» RETI. При попадании на данную заглушку (если сработало не нужное прерывание), микроконтроллер вернётся обратно по адресу, который он запомнил в стеке когда переходил на выполнение данного прерывания, и продолжит выполнение программы с места, с которого он «отвлёкся» на прерывание. То есть не произойдёт ничего страшного, микроконтроллер потеряет немного времени на переход по прерыванию и обратно (всего несколько машинных тактов), но зато не сменит алгоритм работы.

Reset:     OUTI       SPL,low(RAMEND)
           OUTI       SPH,High(RAMEND)

;=============================================================================

; Все инициализации тут.

RAM_Flush:            LDI  ZL,Low(SRAM_START)
                      LDI  ZH,High(SRAM_START)

                      CLR  R16

Flush:                ST  Z+,R16

                      CPI  ZH,High(RAMEND)
                      BRNE  Flush

                      CPI  ZL,Low(RAMEND)
                      BRNE  Flush

                      CLR  ZL
                      CLR  ZH

Дальше идёт инициализация стека. Это очень важная вещь! Без стека вы не сможете использовать прерывания или подпрограммы. Стек растёт с конца памяти к началу. Поэтому в стековые регистры SPL, SPH записывается адрес конца RAM памяти.

Дальше идёт очистка RAM памяти, или, если по другому сказать производится инициализация всех ячеек памяти значением 0. Лично я никогда не очищаю всю память, мне кажется это лишнее. В любом случае, перед тем как что-то считать с памяти, в неё всегда что-то записывается, и поэтому не важно что в памяти, мусор, либо 0. Если, мне и нужно чтобы в какой-то ячейке изначально был 0, я её инициализирую 0. Инициализирую только данную ячейку, а не всю память. Зачем тратить микроконтроллерное время для очистки всей памяти? Более того, в данном проекте RAM память не используется, но я оставил всё так, как было у автора.

; Инициализация периферии

;========================================================================================

.equ XTAL           = 8000000

.equ baudrate       = 9600

.equ bauddivider    = XTAL/(16*baudrate)-1

OUTI       UBRRL,low(bauddivider)

OUTI       UBRRH,high(bauddivider)

OUTI       UCSRA, 0

OUTI       UCSRB,(1<<RXEN)|(1<<TXEN)|(1<<RXCIE)|(0<<TXCIE)

OUTI       UCSRC,(1<<URSEL)|(1<<UCSZ0)|(1<<UCSZ1)

;========================================================================================

В выше приведенном участке кода инициализируется USART и ADC микроконтроллера.

Начнём с USART. Для начала нужно задать скорость передачи данных записав в необходимые регистры делителя рассчитанные значения. Данные значения рассчитываются исходя из тактовой частоты микроконтроллера (XTAL= 8000000 Гц), и из необходимой скорости передачи (baudrate =9600). Всё это можно посчитать вручную, и записать полученные значения в регистры, а можно всю работу переложить на компилятор, что в данном примере и сделано.

.equ XTAL           = 8000000
.equ baudrate       = 9600
.equ bauddivider    = XTAL/(16*baudrate)-1

      OUTI       UBRRL,low(bauddivider)
      OUTI       UBRRH,high(bauddivider)

Это очень удобно, например, нужно вам сменить скорость USART, либо тактовую частоту микроконтроллера, либо и то и другое, вы просто меняете нужные значения и перекомпилируете проект, и не нужно ничего пересчитывать, компилятор сам всё посчитает. Очень удобно!

Регистр UCSRA – флаговый регистр, каждый флаг данного регистра сигнализирует о произошедших событиях, например таких как ошибка чётности, ошибка кадра и т. д.

В конфигурационный регистр UCSRB USART-а записывается значение разрешающее соответственно: приём, передачу, прерывание при принятом байте, прерывание при переданном байте.

В конфигурационный регистр UCSRB USART-а записывается значение (1<<UCSZ0)|(1<<UCSZ1) что конфигурирует USART на работу в 8-ми битном режиме.

Очень интересный бит, URSEL (7 бит). Он определяет в какой именно регистр будет произведена запись.  Дело в том, что Atmel, не знаю по каким причинам, расположил регистры UCSRC и UBRRH по одному адресу, если URSEL=1 запись производится в регистр UCSRC иначе в UBRRH.

;Init ADC

;========================================================================================

OUTI       ADMUX,0b00100100

Здесь инициализируется ADC микроконтроллера. Конфигурируем его таким образом, чтобы сигнал для оцифровки брался со входа PC4, и результат преобразования выравнивался по левому краю, то есть отбрасываем два младших бита, в которых как правило шум.

С функцией main всё просто, не будем останавливаться.

;=============================================================================

; Interrupts

;=============================================================================

UART_RX:               
         IN   R16,UDR
         OUT  UDR,R16

         CPI  R16,'0'          ; Если в UART упал ASCII код нуля, то
         BREQ    STOP_ADC     ; Переход на остановку АЦП

         OUTI  ADCSRA,(1<<ADEN)|(1<<ADIE)|(1<<ADSC)|(1<<ADFR)|(3<<ADPS0)              ; Запуск непрерывного преобразования.

RETI                          ; Выход из прерывания

STOP_ADC: 
         OUTI  ADCSRA,(0<<ADEN)|(0<<ADIE)|(0<<ADSC)|(0<<ADFR)|(3<<ADPS0)              ; Остановка преобразования.

RETI                          ; Выход из прерывания

;==============================================================================

UART_TX:               RETI

;==============================================================================

ADC_OK:   IN  R16,ADCH
          OUT  UDR,R16

RETI

При включении, устройство находится в неактивном состоянии. Для запуска необходимо передать по USART любой байт кроме 0х30. В этом случае запускается непрерывное преобразование ADC. Как только ADC закончит преобразование, сработает прерывание ADC_OK в обработчике которого результат преобразования поместится регистр UDR из которого будет отправлено по USART. Как только будет готов результат следующего преобразования, всё повториться. Всё можно остановить отправив по USART  0х30 (код цифры 0), при этом в регистр ADCSRA записывается комбинация останавливающая преобразование ADC.

Под данное устройство была разработана печатная плата в  программе sprint layout. Исходники можно скачать в конце статьи.
Измеритель-напряжения-с-передачей-данных-на-ПК-по-USB_izmeritel-naprjazhenija-s-peredachej-dannyh-na-pk-po-usb_Четёж
Плата были изготовлена по методу ЛУТ, протравлена и залужена. Я всегда лужу дорожки, лужёные платы выглядят на много красивее. Ниже представлены фото платы до и после лужения.Измеритель-напряжения-с-передачей-данных-на-ПК-по-USB_izmeritel-naprjazhenija-s-peredachej-dannyh-na-pk-po-usb_Плата до луженияИзмеритель-напряжения-с-передачей-данных-на-ПК-по-USB_izmeritel-naprjazhenija-s-peredachej-dannyh-na-pk-po-usb_Плата_после_ луженияГотовое собранное устройство показано на следующих фото.

Измеритель-напряжения-с-передачей-данных-на-ПК-по-USB_izmeritel-naprjazhenija-s-peredachej-dannyh-na-pk-po-usb_Собранное_устройствоИзмеритель-напряжения-с-передачей-данных-на-ПК-по-USB_izmeritel-naprjazhenija-s-peredachej-dannyh-na-pk-po-usb_Вид_сврху

В видео представлен пример работы данного устройства. Устройство передаёт в программу Terminal данные, и они выводятся в виде графика. В качестве входного сигнала я использую +5В от USB. Я пинцетом подключаю вход устройства к 5В и видно что график поднимается, то есть устройство что-то измеряет.



Мой одногрупник написал под данное устройство программу для обработки, накопления и анализа полученных данных, и возможно, это устройство ещё работает)))

Скачать файлы проекта можно по ссылке: http://www.elenblog.ru/wp-content/uploads/2013/04/izmeritel-naprjazhenija-s-peredachej-dannyh-na-pk-po-usb.rar

Один комментарий

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

Обязательные поля отмечены *.