АЦП через регистры
- Войдите на сайт для отправки комментариев
Пт, 19/02/2016 - 22:54
Задумка сделать что то вроде быстрого вольтметра, использование analogRead не устраивает - сожрет слишком много быстродействия на ожидание ответа АЦП. Накарябал код с управлением АЦП через регистры. Опыта с регистрами АЦП ноль - пожайлуста гляньте на предмет явных косяков.
// Version 0.0
// Попытка опрашивать 5 каналов АЦП на прерываниях
// Идея такая - цепляемся к таймеру Timer0 его все равно уже используется millis()
// обрабатываем промежуточную переменную, програмируем АЦП на измерение следующего канала
// АЦП измеряет, генерирует прерывание, подпрограмма читает данные и присваивает их промежуточной
// переменной. И так по кругу...
// Весь этот цирк для того чтобы микроконтролер не простаивал дожидаясь данных с АЦП
// По идее 976 в секунду / на 5 каналов = 200 на канал
// Если фильтровать по 16 значениям получается 12 измерений в секунду на каждый канал
// Масив данных для програмирования входа мультиплексора АЦП
unsigned char pin_AD [5] = {0x47,0x46,0x45,0x44,0x43};
// Если не лоханулся каналы 7, 6, 5, 4, 3
void setup()
{
//Нпастройка прерывания, используется Timer0 чтобы потом не накосячить с генерацией ШИП
OCR0A = 0xAF; // Timer0 уже используется millis() - прерываемся где-то
TIMSK0 |= _BV(OCIE0A); // посередине и вызываем ниже функцию "Compare A"
Serial.begin(9600); // Настройка последовательного порта для отправки данных на компьютер
// Програмирование АЦП
DIDR0 = 0x3F; // отключаем цифровые входы, по идее только для порта А
ADCSRA = 0xAF; // включаем АЦП, разрешаем прерывания, делитель = 128
ADCSRB = 0x40; // Включаем каналы MUX АЦП, режим постоянной выборки
sei(); // устанавливаем флаг разрешающий прерывания
/////////////////////////////////////////////////////////////
byte i=0; // Переменная для чтения масива, заодно и для разнесения данных с одного АЦП
// на 5 переменных
int analogValue = 0; // значение аналогового сигнала АЦП
}
///////////////////////////////////////////////////////////////
SIGNAL(TIMER0_COMPA_vect) // Прерывание вызывается 976.5625 в секунду
{
// действия при прерываниях от таймера
// Тут будет присваивание данных переменной конкретного канала
for (int i=0;i<5;i++) {ADMUX=pin_AD[i];} // Если не накосячил то работает так:
// Берется байт с масива и пишется в регистр - задаем какой канал АЦП читать
bitWrite(ADCSRA, 6, 1);// Запускаем преобразование установкой бита 6 (=ADSC) в ADCSRA
}
/*** Процедура обработки прерывания АЦП ***//////////////////////
ISR(ADC_vect)
{
analogValue = ADCL; // сохраняем младший байт результата АЦП
analogValue += ADCH << 8; // сохраняем старший байт АЦП
bitWrite(ADCSRA, 6, 0);
// Останавливаем АЦП, высокая частота чтения не нужна, запустим от таймера
}
void loop()
{
//Тут буду обрабатывать
}
Использованны статьи, лучшее из найденного по теме:
http://robotosha.ru/arduino/analog-measurements-arduino.html
http://www.junradio.com/index/analogovye_vkhody_arduino/0-284
есть два комментария
1. для повышения точности и стабильности измерений рекомендую делать серию измерений и потом усреднять. Так как АЦП 10 бит, то можно делать от 2 до 64 измерений в 16-ти битный аккумулятор и потом усреднять.
2. если стоит цель делать измерения часто и при этом не тратить времени на обработку то лучше применять режим Free running в таймере. В этом случае запуск измерения будет делать автоматически как только завершилось предыдущее
Бред какой-то. Вы зачем крутите вчодной MUX?
for (int i=0;i<5;i++) {ADMUX=pin_AD[i];} // Если не накосячил то работает так:ISR(TIMER1_COMPB_vect) { static int16_t counter = 0; static int32_t accumul = 0; int32_t temp = ADCL; temp += (ADCH << 8); temp -= adc_Offst; temp = temp * temp; accumul += temp; if(++counter > 1000) { // 20 Hz, 20 frame/sec. ppm_Level = accumul; counter = 0; accumul = 0; flag = 1; } } void adc_init() { ADMUX = 0xC5; // PIN 5 Analog. REF Internal. ADCSRA = ((1<< ADEN)| // 1 = ADC Enable (0<< ADSC)| // ADC Start Conversion (1<<ADATE)| // 1 = ADC Auto Trigger Enable (0<< ADIF)| // ADC Interrupt Flag (0<< ADIE)| // ADC Interrupt Enable (1<<ADPS2)| (0<<ADPS1)| // ADC Prescaler : 1 MHz. (0<<ADPS0)); ADCSRB = ((1<<ADTS2)| // Sets Auto Trigger source - Timer/Counter1 Compare Match B (0<<ADTS1)| (1<<ADTS0)); /* Set up TIMER 1 - ADC sampler */ TIMSK0 = 0x00; TIMSK1 = 0x00; TIMSK2 = 0x00; TCCR1A = 0; TCCR1B = 0; TCCR1C = 0; TCCR1A = ((1<<WGM11) | (1<<WGM10)); // Mode 15, Fast PWM TCCR1B = ((1<<WGM13) | (1<<WGM12)); // Mode 15, Fast PWM TCCR1B |= (1<<CS10); // clk/1 prescaling. OCR1A = SMP_TMR1; OCR1B = SMP_TMR1; TCNT1 = 0; TIFR1 |= (1<<OCF1B); TIMSK1 |= (1<<OCIE1B); }А на форуме нельзя файл приатачить? У меня есть проверенный код с таймером 1
https://drive.google.com/file/d/0Bw4tXXvyWtFVUGw5T280QnZlb1U/view?usp=sharing
AS31979 Вы сравнивали, ваш метод быстрее библиотеки CyberLib.h?
MagicianT в даташите явно сказано, что при использовании тригеров для запуска преобразования или free running изменение admux без остановки преобразования приведет к ситуации когда атмел не гарантирует какой канал был преобразован - в ADMUX может быть одно значение, а преобразование совершено с другим
поэтому если нужно делать преобразование по разным каналам перед исзменением ADMUX нужно останавливать преобразование
Не обязательно. Вы всегда можете проверить с какого входа приходят самплы. Не останавливая АЦП скорость преобразования 14 тактов, а с остановкой 25. Почти вдвое. В примере когда АЦП clock 1 МГц, частота до 76 кГц. Там бывает что MUX не успевает переключиться и отстанет на один такт, но если скорост самплирования не менять на лету, выставив один раз и проверив с какого входа идут измерения, потом можно быть увереным что это не изменится
Primer:
ISR(TIMER1_COMPB_vect) { int16_t adc_value = ADC - adc_Offst; static int16_t last_smpl = 0; if ( smpl_Nmbr < SAMPL_P ) { if( smpl_Nmbr & 0x01 ) { ADMUX = 0xC5; // Voltage, pin AN5 v_s[smpl_Nmbr >>1] = (last_smpl + adc_value) / 2; //Linear Interpolation last_smpl = adc_value; } else { ADMUX = 0xC4; // Current, pin AN4 c_s[smpl_Nmbr >>1] = adc_value; } } smpl_Nmbr++; }user is thus advised not to write new channel or reference selection values to ADMUX until one ADC clock cycle
after ADSC is written.
Правильно, 1 clock , в примере:
Да, но при частоте АЦП выше 250кгц атмел не рекомендует использовать 10бит преобразования, а только 8
для 10бит частота должна быть менее 250кгц
Не могу найти, в сети был график снижения точности АЦП от частоты, наверное удалили. Не всё там так резко, и тот-же дата шит говорит что исходная точность на 200 кГц +- 2LSB.
Дааа..., я вроде на пояснения в скече не поскупился.
Строка 7, 8, 9 там и про фильтрацию, и про частоту преобразования. Задача выдавить из АЦП максимум не стоит - результаты измерений будет обрабатывать сам микроконтролер, куча данных которые микроконтролер не успеет обработать мне даром не нужна, 10-12 измерений по 5 каналам хватит.
Все извращения в коде направленны на то чтобы избежать простоя ядра микроконтролера при выполнении analogRead, меня больше всего волнует не накосячил ли я с битами при програмировании регистров АЦП.
Из всего написанного мне оказались полезны ответы:
MagicianT - протупил я, данные с АЦП можно при прерывании по таймеру сначала забрать измеренные, а потом уже запустить АЦП на однократное измерение следующего канала.
axill - "в даташите явно сказано, что..." никуда не спешу, лучше пару лишних команд всталю чем потом косяк словлю.
MagicianT - если не затруднит, сделайте пожайлуста руские коментарии к свроему примеру, я так понял что АЦП програмируется побитно называя компилятору нужную функцию. Но не уверен точно что именно делается. Не знал что в параметры АЦП можно задавать просто через названия функций.
Данные по снижению точности АЦП от частоты преобразования можно найти в первой ссылке, там в конце описанно понятнее чем в даташите.
Перевод имел бы смысл если даташит был бы на русском. А так не целесообразно. Дата шит скачали? Первое время постоянно приходится сверяться в каком регистре какой бит находится/прописать.
В общем первый блин как и ожидалось - комом, оптимизировал/отрихтовал.
За последующую обработку не беспокоюсь, всегда можно на монитор COM порта послать переменную для отладки. Сильно беспокоит програмирование АЦП - есть соображения где я облажался?
// Version 0.01 // Попытка опрашивать 5 каналов АЦП на прерываниях // Идея такая - цепляемся к таймеру Timer0 его все равно уже используется millis() // задаем какой следующий канал будем измерять, забираем результаты предыдущего измерения, запускаем АЦП // обрабатываем забраную промежуточную переменную, и так по кругу... // Весь этот цирк для того чтобы микроконтролер не простаивал дожидаясь данных с АЦП // По идее 976 раз в секунду / на 5 каналов = 200 на канал // Если фильтровать по 16 значениям получается 12 измерений в секунду на каждый канал // Масив данных для програмирования входа мультиплексора АЦП unsigned char pin_AD [5] = {B11000111,B11000110,B11000101,B11000100,B11000011}; // Если не лоханулся каналы 7, 6, 5, 4, 3 // Разшифровка слева - направо // 11 - REFS1.0 внутрений источник опорного напряжения 1,1В // 0 - ADLAR выравнивание результата преобразования, как я понял указание АЦП // в какой регистр ложить старший байт, а в какой младший. По умолчанию вроде ноль. // xxxxx - номер канала мультиплексора, каналов всего 8, так что 2 старших бита всегда нули. void setup() { //Нпастройка прерывания, используется Timer0 чтобы потом не накосячить с генерацией ШИП OCR0A = 0xAF; // Timer0 уже используется millis() - прерываемся где-то TIMSK0 |= _BV(OCIE0A);// посередине и вызываем ниже функцию "Compare A" Serial.begin(9600); // Настройка последовательного порта для отправки данных на компьютер // Програмирование АЦП DIDR0 = 0x3F; // отключаем цифровые входы, по идее только для порта А ADCSRA = B10000111; // Разшифровка слева - направо // 1 - ADEN - включаю АЦП // 0 - ADSC - запускать преобразование буду отдельно про прерываниям таймера // 0 - ADATE - разовое преобразование // 0 - ADIF - не знаю зачем он вообще, но мне точно не нужен, АЦП по любому // сделает замер раньше чем наступит следующее прерывание // 0 - ADIE - прерывание от компаратора не нужно // 111 - ADPS2.0 - устанавливается делитель тактирования АЦП на 128 /////////////////////////////////////////////////////////////////////// byte i=0; // Переменная для чтения масива, заодно и для разнесения данных // с одного АЦП на 5 переменных int analogValue = 0; // значение аналогового сигнала АЦП } //////////////////////////////////////////////////////////////////////// SIGNAL(TIMER0_COMPA_vect) // Прерывание вызывается 976.5625 в секунду { // действия при прерываниях от таймера for (int i=0;i<5;i++) {ADMUX=pin_AD[i];} // Берется байт с масива и пишется в регистр - задаю какой канал АЦП читать, // задаю еще до чтения данных из регистров АЦП для того чтобы компаратор // пока читаются данные успел переключится. analogValue = ADCL; // сохраняем младший байт результата АЦП analogValue += ADCH << 8; // сохраняем старший байт АЦП (0<<ADATE); // Запуск преобразования установкой бита ADATE в 0 // Позже в этом же прерывании наверное буду крутить оверсемплинг } void loop() { //Тут буду обрабатывать }Там всё неправильно. Перечислять косяки нет смысла, Вы вообще ответы, что выше, читали?
AS31979, я не сразу понял что вы пытались изобразить. Этот режим называется Auto trigged mode using Timer в принципе штатный режим АЦП, вот так он будет по-человечески выглядеть:
#include <avr/delay.h> volatile int aread0,aread1,aread2; void setup() { Serial.begin(9600); OCR0A=0xAF; TIMSK0|=1<<OCIE0A; ADMUX=1<<REFS0; // AVCC with external capacitor at AREF pin ADCSRA=(1<<ADEN)|(1<<ADATE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); ADCSRB=(1<<ADTS1)|(1<<ADTS0); // Timer/Counter0 Compare Match A } ISR(TIMER0_COMPA_vect) { static uint8_t n=0; ADMUX = ADMUX & 0b11111000 | n; switch(n){ case 0: aread2 = ADC; n=1; break; case 1: aread0 = ADC; n=2; break; case 2: aread1 = ADC; n=0; break; } } void loop() { Serial.print("A0="); Serial.print(aread0); Serial.print(" A1="); Serial.print(aread1); Serial.print(" A2="); Serial.println(aread2); _delay_ms(100); }MagicianT - "Там все неправильно..." и т.д. это конечно ОЧЕНЬ проясняет ситуацию, но новичку вроде меня проще перичитать даташит с нуля чем извлечь пользу с вашего ответа.
dimax - спасибо, ваш пример подходит идеально.
К сожалению с коментариями в примере не густо, прошу глянуть правильно ли я разшифровал действия. Есть подозрение что 14 строка примера изменяет нустройки опорного напряжения сделанные в 7 строке!?
Непонятно зачем использовалась нестандартная библиотека формирования задержек, в коде вроде нет ничего что бы нарушало работу millis, у меня заработало с стандартной задержкой.
Фактически тот же скечь только с моими коментариями:
volatile int aread0,aread1,aread2; //Переменные для данных с 3-х входов void setup() { Serial.begin(9600); //задание скорости последовательного порта OCR0A=0xAF; //значения таймера при котором TIMSK0|=1<<OCIE0A; //разрешить прерывание при совпадении ADMUX=(1<<REFS0|1<<REFS1); //у вас опорным было напряжение питания //я ставлю внутрений 1,1В ADCSRA=(1<<ADEN)|(1<<ADATE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); //включение АЦП //режим преобразования задается битами ADTS2:0 //тактовая частота АЦП 1/128 от тактовой частоты микроконтролера ADCSRB=(1<<ADTS1)|(1<<ADTS0); // запуск прерывания от Timer0 } ISR(TIMER0_COMPA_vect) { //действия при прерывании от Timer0 static uint8_t n=0; //локальная переменная которая видна //только обработчику прерывания ADMUX = ADMUX & 0b11111000 | n; //наложение n, вибирается № входа //А вот тут не понял, не соответствует настройкам из вашего примера //вы задали ADMUX 01хууууу, 2 старших бита опорное напряжение AVCC, //а сейчас ставите опорным внутренее!?!?!? //х(ADLAR) - по идее равен нолю(тут не уверен), значение 4 и 5 бита //не имеют значения - входов всего 8(хватает 3-х бит) switch(n){ //тут просто непрерывный цикл с заменной n которая одновременно и //переменная в цикле и данные для ноложения &, хитрый способ case 0: aread2 = ADC; n=1; break; case 1: aread0 = ADC; n=2; break; case 2: aread1 = ADC; n=0; break; //не по порядку потому что не ждем ответа АЦП, програмируем какой //канал АЦП измеряем - забираем данные предыдущего измерения - //изменяем n. При таком большом коэфициенте делителя АЦП без проблем. } } //Дальше уже ничего интересного, чем не устроил стандартный delay //непонятно, но в данном случае непринципиально. void loop() { Serial.print("A0="); Serial.print(aread0); Serial.print(" A1="); Serial.print(aread1); Serial.print(" A2="); Serial.println(aread2); delay(1000); }Если кто будет повторять:
Входы выбираются в строках 34-36, но в отличии от analogRead пины выбираются довольно извращенным образом, сначала выбрали канал, при следующем прерывании дали задание АЦП мерять этот канал и только при третьем прерывании забрали результат.
Лично у меня при delay(100) COM порт лег менее чем за минуту.
Без конденсаторов на входах АЦП и AREF даные будут дико прыгать, на маленьких напряжениях контакты макетной платы могут внести такую погрешность что показания покажутся бредом!!!
AS31979,
- внешняя библа delay.h была взята на всякий случай, если я в процессе эксперементов собью настроки таймера0. Не потребовалась.
- в 8 строке корректнее написать так ADMUX=(1<<REFS0)|(1<<REFS1);
- в 22 строке старшие 5 бит регистра ADMUX остаются неизменны, точно как были заданы в сетапе. Обнуляются три младших бита, затем складываются с переменной n.
Не могу найти, в сети был график снижения точности АЦП от частоты, наверное удалили. Не всё там так резко, и тот-же дата шит говорит что исходная точность на 200 кГц +- 2LSB.
Не график, но для сравнения http://www.gammon.com.au/adc
dimax - 7 и 14 строку я имел в виду из вашего скеча, они у вас по идее конфликтуют. В своей версии я это учел, кроме того есть подозрение что 22 строка моего скеча делеет необязательной 8 строку.
Pyotr - по точности АЦП есть ссылка в самом первом сообщении.
dimax - 7 и 14 строку я имел в виду из вашего скеча, они у вас по идее конфликтуют. В своей версии я это учел, кроме того есть подозрение что 22 строка моего скеча делеет необязательной 8 строку.
Я же вам написал про это. Повторю ещё раз в команда ADMUX = ADMUX & 0b11111000 обнуляет только три младших бита. Старшие какие были такие и остаются.
Пытаюсь измерять ток, что-то как-то не заладилось...
Библиотека отсюда
// PINOUT // L_EN -> 7 // R_EN -> 8 // L_PWM -> 5 // R_PWM -> 6 // R_IS ->A0 // L_IS ->A1 // A3 -> not connected #include <util/delay.h> #include "BTS7960.h" const uint8_t L_EN = 7; const uint8_t R_EN = 8; const uint8_t L_PWM = 5; const uint8_t R_PWM = 6; BTS7960 motorController(L_EN, R_EN, L_PWM, R_PWM); volatile int aread0, aread1, aread2; void setup() { Serial.begin(115200); OCR0A = 0xAF; TIMSK0 |= 1 << OCIE0A; ADMUX = 1 << REFS0; // AVCC with external capacitor at AREF pin ADCSRA = (1 << ADEN) | (1 << ADATE) | (1 << ADPS1) | (1 << ADPS0); ADCSRB = (1 << ADTS1) | (1 << ADTS0); // Timer/Counter0 Compare Match A } ISR(TIMER0_COMPA_vect) { static uint8_t n = 0; ADMUX = ADMUX & 0b11111000 | n; switch (n) { case 0: aread1 = ADC; n = 1; break; case 1: aread0 = ADC; n = 0; break; } } void loop() { motorController.Enable(); for (int speed = 0 ; speed < 255; speed += 5) { motorController.TurnLeft(speed); delay(100); Serial.print("A0="); Serial.print(map(aread0, 0, 1023, 0, 43000)); Serial.print(" A1="); Serial.println(map(aread1, 0, 1023, 0, 43000)); } motorController.Stop(); for (int speed = 255 ; speed > 0; speed -= 5) { motorController.TurnLeft(speed); delay(100); Serial.print("A0="); Serial.print(aread0); Serial.print(" A1="); Serial.println(aread1); } motorController.Stop(); for (int speed = 0 ; speed < 255; speed += 5) { motorController.TurnRight(speed); delay(100); Serial.print("A0="); Serial.print(aread0); Serial.print(" A1="); Serial.println(aread1); } motorController.Stop(); for (int speed = 255 ; speed > 0; speed -= 5) { motorController.TurnRight(speed); delay(100); Serial.print("A0="); Serial.print(aread0); Serial.print(" A1="); Serial.println(aread1); } motorController.Stop(); motorController.Disable(); delay(5000); }ua6em - вы так "подробно и емко" описали проблему, что единственный ответ который я могу вам дать:
"Пытаюсь помочь, что-то как-то с помощью не заладилось..."
И думаю НИКТО НЕ СМОЖЕТ!!!!
ua6em - вы так "подробно и емко" описали проблему, что единственный ответ который я могу вам дать:
"Пытаюсь помочь, что-то как-то с помощью не заладилось..."
И думаю НИКТО НЕ СМОЖЕТ!!!!
думаю никто не поможет, с долей вероятности близкой к 100% чипы поддельные
PS надеялся, что в коде есть косяки, сделал скетч с чтением через analogRead(), ничего не изменилось...
Пины-то нагрузил? У них выход токовый
Пины-то нагрузил? У них выход токовый
1 ком там стандартная нагрузка
Может надо вынести из обработчика static uint8_t n = 0;?
А то case 1 никогда не происходит
P.S. Возможно это сделано специально, для проверки...догадался Штирлиц))
Ну тогда остается немыслимое - измерить мультиметром.
ua6em - вы так "подробно и емко" описали проблему, что единственный ответ который я могу вам дать:
"Пытаюсь помочь, что-то как-то с помощью не заладилось..."
И думаю НИКТО НЕ СМОЖЕТ!!!!
а как перенести измерение на пины A6 и A7?
Входы задаются этой строкой
Только произвольно в моем примере их изменять нельзя - переменная n одновременно переменная цикла.
Хорошо описана адресация входов АЦП тут: http://www.junradio.com/index/analogovye_vkhody_arduino/0-284
так? Если измеряем A5 A6 A7
ISR(TIMER0_COMPA_vect) { static uint8_t n=5; ADMUX = ADMUX & 0b11111000 | n; switch(n){ case 5: aread7 = ADC; n=6; break; case 6: aread5 = ADC; n=7; break; case 7: aread6 = ADC; n=5; break; } }