Рассматривается код программного SPI микроконтроллера AVR и подключение через него внешнего 12-битного ADC (АЦП) MCP3202.
Аппаратно подключение довольно простое - тип интерфейса SPI, используется 4 сигнала CS, DI, DO и CLK (см. таблицу).
Цоколевка АЦП MCP3202 (аналог ADC0832)
Сигнал MCP3202 |
Порт AVR |
Описание |
CS, вход |
PB5, выход |
Выборка для чипа MCP3202, активный уровень - лог. 0 |
DI, вход |
PB7, выход |
Последовательные данные команды для MCP3202 |
DO, выход |
PC7, вход |
Последовательные данные результата АЦП MCP3202 |
CLK, вход |
PB6, выход |
Такты для бит сигналов DI и DO MCP3202 |
У АЦП два мультиплексируемых входа CH0 и CH1, которые могут быть выбраны программно. В качестве эталонного напряжения (reference voltage) для АЦП MCP3202 служит напряжение питания VDD/VREF.
Используемый протокол обмена данными максимально прост. Сначала по шине DI идут 4 бита команды, потом по шине DO выдвигаются из АЦП 12 бит данных (каждый бит команды DI и каждый бит данных DO тактируется фронтом сигнала CLK). Данные передаются в порядке, когда самый старший бит идет первым (MSBF, Most Significant Bit First).
Так как сигналы DI и DO активны в разное время, то для экономии портов они теоретически могут быть запараллелены. Однако в этом случае необходимо очень аккуратно манипулировать сигналами CS и CLK - чтобы не было паразитного зацикливания с выхода DO на вход DI. Биты данных на DO и DI считываются по нарастанию (фронту) сигнала CLK.
tCYC - полное время цикла аналого-цифрового преобразования. tCSH - минимальное время нахождения сигнала выборки в неактивном (лог. 1) состоянии (500 нс). tSUCS - минимальное время между спадом сигнала выборки и первым фронтом нарастания сигнала тактов (100 нс). tSAMPLE - время, за которое делается выборка и запоминание уровня напряжения на аналоговом входе АЦП (CH0 или CH1). tCONV - время аналого-цифрового преобразования. tDATA - в течение этого времени выключаются внутренние узлы АЦП. START - стартовый бит команды, всегда лог. 1 SGL/DIFF - бит, задающий режим работы входов CH0 и CH1. Если лог. 1, то входы одиночные (недифференциальные), если лог. 0, то входы дифференциальные. ODD/SIGN - бит, задающий номер выбранного входа CH0 или CH1 (когда вход недифференциальный), либо полярность дифференциальных входом (когда вход дифференциальный). Если в недифференциальном режиме значение этого бита равно 0, то выбран как аналоговый вход CH0, если 1, то выбран CH1. MSBF - бит, задающий порядок передачи бит данных. В самом простейшем случае этот бит должен быть равен 1, что означает - самый старший бит будет идти первым, а самый младший - последним. HI-Z - высокоимпедансное состояние выхода данных DO (выход чипа MCP3202 отключен). Don't Care - значение входных данных DI не имеют значения. Null Bit - пустой нулевой, ничего не значащий бит данных. B11..B0 - биты данных АЦП (результат аналого-цифрового преобразования).
Для реализации обмена данными использовался программно формируемый интерфейс SPI. За основу взят код для подключения MCP3202 к микроконтроллеру MCS51 (см. [2]). Код испытывался на чипе AVR USB AT90USB162, но он будет работать на любом микроконтроллере семейства AVR компании Atmel, и может быть портирован на другие микроконтроллеры.
[MCP3202.h]
/* Подпрограмма чтения ADC MCP3202 фирмы Microchip. Идея взята тут: http://www.sixca.com/micro/mcs51/adc12bit/ */
#include "MCP3202.h"
//----------------------------------------------------------
// Чтение аналоговых данных из ADC MCP3202 через программный
// SPI. Подпрограмма не требует предварительной
// инициализации портов для работы с ножками SPI.
// Режим MCP3202: недифференциальный (Single end, 2 канала),
// старший бит идет первым (MSB first).
// Входной параметр channel задает канал: 0 канал с ножки 2,
// 1 канал с ножки 3.
//----------------------------------------------------------
unsigned int ReadMCP3202ADC (unsigned char channel)
{
unsigned char i,k;
unsigned int AdcResult; // 12 бит
//настройка DO как входа без pullup
DDRC &= ~(1<<ADC_DO);
PORTC &= ~(1<<ADC_DO);
//остальные ножки как выходы, у CLK начальное состояние 0,
// у CS и DI начальное состояние 1.
DDRB |= ((1<<ADC_CS)|(1<<ADC_CLK)|(1<<ADC_DI));
PORTB |= ((1<<ADC_CS)|(0<<ADC_CLK)|(1<<ADC_DI));
//ADC_CS=0 активируем выборку чипа
PORTB &= ~(1<<ADC_CS);
k++;k++; // задержка около 2 мкс
#define CMD_BIT_START 0x08
#define CMD_BIT_SGL 0x04
#define CMD_BIT_MSBF 0x01
channel <<= 1;
channel |= (CMD_BIT_START | CMD_BIT_SGL | CMD_BIT_MSBF);
////////////////////////////////////////////////////
// передаем 4 бита команды, старший бит идет первым
for(i=0; i< 4;i++)
{
PORTB = ((channel & 0x08) != 0) ?
PORTB | (1<<ADC_DI) :
PORTB & ~(1<<ADC_DI);
channel <<= 1;
PORTB |= (1 << ADC_CLK); //ADC_CLK=1
k++;k++; // задержка около 2 мкс
PORTB &= ~(1 << ADC_CLK); //ADC_CLK=0
}
k++;k++; // задержка около 2 мкс
PORTB |= (1 << ADC_CLK); //ADC_CLK=1
k++;k++; // задержка около 2 мкс
PORTB &= ~(1 << ADC_CLK); //ADC_CLK=0
k++;k++; // задержка около 2 мкс
////////////////////////////////////////////////////
// чтение 12 бит результата ADC, старший бит первый
AdcResult=0;
for(i=0;i<12;i++)
{
PORTB |= (1 << ADC_CLK); //ADC_CLK=1
k++;k++; // задержка около 2 мкс
AdcResult<<=1;
if (PINC & (1 << ADC_DO))
AdcResult |= 1;
PORTB &= ~(1 << ADC_CLK); //ADC_CLK=0
k++;k++; // задержка около 2 мкс
}
PORTB |= ((1 << ADC_CS)|(0 << ADC_CLK)|(1 << ADC_DI));
return(AdcResult);
}
При подключении АЦП к измеряемым цепям нужно иметь в виду, что входы CH0 и CH1 у MCP3202 имеют низкое и нелинейное сопротивление (которое меняется в зависимости от состояния процесса аналого-цифрового преобразования). Поэтому напрямую подключать MCP3202 в большинстве случаев нельзя, нужен буферный усилитель, иначе результат измерения напряжения будет неверным. На рисунке показан пример простой схемы такого буферного усилителя вместе с антиалиазинговым фильтром низкой частоты на операционном усилителе MCP601.
[Пример использования АЦП MCP3202 - апгрейд программатора AVRISP-MkII]
После изготовления клона AVRISP-MkII (см. [1]) выяснилось, что желательно было бы иметь возможность измерять напряжение питания программируемого микроконтроллера. Так как в микроконтроллере AT90USB162 внутреннего АЦП нету, то возникла идея подключить внешний АЦП.
Резисторы R7 и R8 - защитные. R7 уравнивает разность потенциалов между выходом DOUT АЦП и входным портом микроконтроллера, когда у них различается напряжение питания - у АЦП всегда напряжение питания +5 вольт, а у портов ввода/вывода микроконтроллера напряжение может переключаться в зависимости от положения перемычки SJ1 (либо 3.3 вольт, либо 5 вольт). Буфера на операционном усилителе перед входом CH0 нет, так как выходное сопротивление источника питания VTARGET достаточно мало, и не требуется высокая точность измерения напряжения.
Использование внешнего АЦП MCP3202 в firmware программатора включается на этапе компиляции, если в makefile задан макрос USE_MCP3202. Если этот макрос задан, то в подпрограмме V2ProtocolParams.c -> V2Params_UpdateParamValues напряжение VTarget считывается с АЦП MCP3202 и записывается в V2Params_GetParamFromTable(PARAM_VTARGET)->ParamValue как число в единицах десятых долей вольта.
После такой доработки в утилите программатора AVR Studio на закладке HW Settings стало правильно измеряться и отображаться напряжение питания программируемого чипа VTarget.
[Ссылки]
1. AVR-USB162MU: макетирование и изготовление программатора AVRISP-MKII в домашних условиях. 2. Connecting 12 bit ADC 2 channel - 8052 Code Library site:8052.com. 3. MCP3202 site:futureelectronics.com - страница краткого описания АЦП MCP3202. 4. 110626AVRISP-MKII-fixed-MCP3202.ZIP - исходный код и скомпилированные прошивки программатора AVRISP-MkII, доработанного подключенным АЦП MCP3202. |
Комментарии
microsin: в этом примере схемы ФНЧ реализован фильтр второго порядка. Номиналы резисторов и конденсаторов здесь не указаны потому, что они рассчитываются по заданным исходным параметрам ФНЧ. Т. е. Вы изначально должны определиться, какая должна быть частота среза ФНЧ и крутизна и форма кривой его характеристики. Методика расчета стандартная, прогуглите ключевые слова: расчет ФНЧ на операционном усилителе.
В чем прикол k++; ??? Компилятор их вообще соптимизирует. Почему не использовать _NOP(); ? А еще лучше _delay_us(); ?
Почему бы PORTB тоже не продефайнить скажем как PORT_ADC?
microsin: Вы во всем правы, Виталий. Однако замечания некритичны. Вы ведь поняли, что такое unsigned int? Поняли. Компилятор понял? Наверное тоже понял. Поэтому тут чистое ИМХО - кому что больше нравится. Я например больше люблю обозначения типов еще короче: s8, s16, s32 (это типы со знаком, signed) и u8, u16, u32 (это для типов unsigned). То же самое можно сказать про PORTx - переименовать его было бы конечно красивее, однако код все равно тупо не переносится с порта B на порт C или на какой-то еще (если нужно поменять используемые ножки портов), поэтому такое скрытие деталей может сыграть злую шутку. Так что делайте как Вам самому больше нравится.
Насчет k++ : Вы опять правы, наверное лучше было применить что-то типа NOP(). Я не проверял, какой именно ассемблерный код генерит у меня компилятор, однако проверил реальную работоспособнос ть кода, и мне этого показалось достаточным.
RSS лента комментариев этой записи