Pull to refresh

Comments 28

Для перевода 10 разрядных данных АЦП в 8 разрядные используется функция
map(val, 0, 1023, 0, 255);
где val – int переменная c 10 значимыми разрядами.

Не пробовали использовать функцию сдвига: (uint8_t)(val>>2)?
А включить АЦП в 8-битный режим сразу ещё проще.
вы уж извините, но сдвиги и режими АЦП уступают по простоте… делению на 4 :)
(да, я в курсе, что это сдвиг >> 2)
в регистре ADMUX необходимо выставить бит ADLAR в единицу.
В этом случае в регистре ADCH будут размещаться биты ADC9-ADC2. Считывать необходимо только его.

Для этого в ардуино необходимо править файл wiring_analog.c, создав, например, копию функции analogRead() с модифицированными строками и именем типа analogRead8bit():
///
ADMUX = (analog_reference << 6) | (pin & 0x07) | (1<< ADLAR);//устанавливаем соответствующий бит
///
return high; //все что связано с low можно удалить. Главное прочитать ADCH.

Спасибо за дельный совет. Используя Вашу подсказку сделал следующее.
1. В файле ...\arduino-1.0.6\hardware\arduino\cores\arduino\ wiring_analog.c создал копию функции int analogRead(uint8_t pin): int analogRead8bit(uint8_t pin)
2. В копию внёс следующие изменения
2.1. Заменил “int analogRead8bit(uint8_t pin)” на “byte analogRead8bit(uint8_t pin)”
2.2. Убрал “low” из “uint8_t low, high;”
2.3. Заменил “ADMUX = (analog_reference << 6) | (pin & 0x07);” на “ADMUX = (analog_reference << 6) | (pin & 0x07) | (1<< ADLAR);”
2.4. Убрал строку “low = 0;”
2.5. Заменил “return (high << 8) | low;” на “return (high);”
3. Сделал изменения в программе сканирования АЦП без пропусков (см. пример в задании 5):

const int adc_5 = A5; // ADC port number

void setup() {
Serial.begin (115200); // bit/s
}

void loop(){
for (int i = 0; i < 2048; i++) {
if (i == 0) Serial.print(«A „); // “A» is header
byte adc_byte = analogRead8bit(adc_5);
Serial.write(adc_byte);
}
}
4. Уменьшил шаг времени моделирования c 375.5 мсек на 265 мсек в Simulink модели: “меню > Simulation > Configuration Parameters > Solver > Fixed-step size” и в блоке модели “Serial Receive > Block Sample Time”.

В РЕЗУЛЬТАТЕ частота сэмплирования непрерывно отображаемого сигнала увеличилась с 2.66 КГц до 7.73 КГц.

Еще раз спасибо radiolok.

Да, необходимо также включить строку

byte analogRead8bit(uint8_t);

в файл ...\arduino-1.0.6\hardware\arduino\cores\arduino\ Arduino.h
Писали бы сразу на Си, на сложность кода это никак не повлияло бы…
Сейчас вы уперлись в скорость UART и линейную программу — МК ждет у моря погоды попеременно установку флага АЦП и флага TX empty
Переписав все на прерываниях и используя среду ардуино только для заливки кода можно приблизиться с датащитовым 76кГц.
Для этого надо настроить таймер для синхронного запуска АЦП, настроить АЦП на trigger_режим, в прерывании АЦП класть в TX buf UART-а, настроенного на 1мбод(не в курсе, переварит ли usb-uart мост в uno такую скорость, платы с преобразователем на FT232RL справляются). Ну и немного рутины, чтобы сделать заголовок вашему пакету в 2048 байт.
Фантастика! Если покажете на рабочем примере как это сделать, думаю, многим бы пригодилась работа Arduino с АЦП на таких (или более высоких чем сейчас) частотах.
Набросал и проверил на ATmega1280. беглый взгляд по регистрам ATmega168(328) говорит что для него ничего править не надо.
В setup добавляем вызов функции, которая нам все настроит.
Путем подстановки нужных значений в регистре UBRR достигается необходимый битрейт. Можно взять из таблицы, можно оформить макросом.
У меня выбран канал АЦП1, можно поставить свой согласно таблице датащита.
void autoadcsetup()
void autoadcsetup(){
//set up TIMER0 to  62.5kHz
//TIMER0_OVF will be the trigger for ADC
/*normal mode, no prescaler
16MHz / 256 = 62.5 kHz*/
TCCR0B = (1 << CS00);//timer frequency = clk/1
//set ADC.
ADMUX = (1 << ADLAR) | (1 << REFS0)  | (1 << MUX0);//8-bit mode, ADC0 channel, AVVCC as ref
ADCSRA = (1 << ADEN) | (1 << ADATE) | (1 << ADIE) | (1 << ADPS2);//TUrn ADC On, trigger enable, Interrupt enable, sysclk/16=1MHz_ADC_clk=76kHz conv freq(13ticks per conversion)
ADCSRB = (1<< ADTS2) ;//Auto trigger source
//set UART to 8-n-1 1Mbod:
UBRR0H = 0;//1Mbod (use Examples of Baud Rate Setting table from datasheet)
UBRR0L = 0;//1Mbod
UCSR0B = (1<<TXEN0);//enable Transmitter
UCSR0C = (3<<UCSZ00);//8-bit mode
}


И добавляем вектор прерывания АЦП, в которым можно засунуть рутину по добавлению заголовка. у меня просто проверка того, что прошлая передача была завершена и помещение 8 бит в регистр передачи.
Таким образом скорость ограничена либо частотой преобразования АЦП, либо частотой передачи UART.
Автотриггер для АЦП настроен на 62.5кГц, но, добавив обработчик TIMER0_OVF_vect и в нем устанавливать стартовый TCNT0, можно поднять до максимальных 76кГц.
ISR(ADC_vect)
ISR(ADC_vect){
  if( ( UCSR0A & (1<<UDRE0)) ){
    UDR0 = ADCH;//copy result. 
  }
}


Лайвхак — микроконтроллер все остальное время может заниматься чем угодно. Главное не использовать встроенные ардуино-функции, которые используют указанные модули.
Что-то делаю не так. UDR0 имеет только нулевые значения.
Проверил использование ISR на следующем примере. Работает.
Не могли бы Вы показать вариант заполнения АЦП данными в память Arduino (например, размером 1024 байт) на максимальной частоте, передачи массива в COM порт (на любой частоте) и циклического повторения названных операций.

Рабочий пример с прерываниями из
“Использование прерываний Arduino
sites.google.com/site/vanyambauseslinux/arduino/ispolzovanie-preryvanij-arduino

#define LEDPIN 13 // Вывод светодиода
#define BTNPIN 2 // Вывод кнопки

volatile int count = 0; // Переменная счётчика (volatile означает указание компилятору не оптимизировать код её чтения,
// поскольку её значение изменяется внутри обработчика прерывания)
ISR(INT0_vect)
{
count = 25; // Инициализировать счётчик
}

// Режимы вызова прерывания INT0
#define INT0_SENSE_LOW_LEVEL 0 // Прерывание при низком уровне на выводе
#define INT0_SENSE_LEVEL_CHANGE 1 // Прерывание при изменении уровня
#define INT0_SENSE_FALLING_EDGE 2 // Прерывание по фронту на спад (когда 1 переходит в 0)
#define INT0_SENSE_RISING_EDGE 3 // Прерывание по фронту на подъём (когда 0 переходит в 1)

// Управляющая функция для прерывания INT0
// mode — режим вызова прерывания
// enable — разрешить/запретить прерывание
void int0Control (uint8_t mode, bool enable){
EIMSK &= ~ (1 << INT0); // Запретить прерывание (так как следующая команда устанавливает режим INT0_SENSE_LOW_LEVEL)
EICRA &= ~ (1 << ISC00) | (1 << ISC01); // Обнуляем биты ISC00 и ISC01 в регистре EICRA
EICRA |= mode; // Устанавливаем режим вызова прерывания INT0
if (enable)
EIMSK |= (1 << INT0); // Разрешить прерывание
}

void setup(){
pinMode(LEDPIN, OUTPUT);
pinMode(BTNPIN, INPUT); // Вывод кнопки в режим ввода
int0Control(INT0_SENSE_RISING_EDGE, true); // Разрешить прерывание по фронту на подъём (в данном случае при нажатии на кнопку)
interrupts(); // Разрешить прерывания глобально
}

void loop(){
if(count==0) {
digitalWrite(LEDPIN, LOW); // Выключить светодиод, если счётчик равен 0…
}
else {
digitalWrite(LEDPIN, HIGH); //… иначе включить светодиод,
--count; // и уменьшить счётчик на 1.
}
delay(100); // Подумать 10 милисекунд.
}
Для определения максимальной частоты преобразования АЦП процессора ATmega328 контроллера Arduino UNO взял следующий пример перезапуска АЦП сразу после выставления бита завершения преобразования:
sites.google.com/site/100voltsamper/mikrokontroller-cto-da-kak/rabota-s-acp-na-primere-atmega328p-nastrojka-acp-primer-koda-ispolzovania-acp
и написал тестовую программу определения времени 256 преобразований:

void setup() {
Serial.begin (57600); // 9600, 19200, 38400, 57600 and 115200 bit/s

ADCSRA |= (1 << ADEN) // Включаем АЦП
|(1 << ADPS1)|(1 << ADPS0); // устанавливаем предделитель преобразователя на 8
ADMUX |= (0 << REFS1)|(1 << REFS0) //выставляем опорное напряжение, как внешний ИОН
|(0 << MUX0)|(0 << MUX1)|(0 << MUX2)|(0 << MUX3); // снимать сигнал будем с входа PC0
}

void loop(){
unsigned long time_start = millis();
for (int i = 0; i < 256; i++) {
ADCSRA |= (1 << ADSC); // Начинаем преобразование
while ((ADCSRA & (1 << ADIF)) == 0); // пока не будет выставлено флага об окончании преобразования
// u = (ADCL|ADCH << 8); // Считываем полученное значение
// adc_bytes[i] = ADCL; // Считываем полученное значение
}
unsigned long time_end = millis();
unsigned int dt = time_end — time_start;
Serial.println (dt);
}

Программа (без затрат на обработку прерываний) показала, что максимальная частота преобразований = 9.14 КГц (как 1/(28мс/256)), близка к 10 КГц, но далека от 50… 80 КГц.
На результат не влияют
— установка частоты преобразования 76 КГц и выше битами ADPS0… ADPS2
— добавление чтения: u = (ADCL|ADCH << 8)
— добавление копирования в массив adc_bytes[i] = ADCL

Вывод: максимальная частота преобразования АЦП Arduino UNO c процессором ATmega328 не превышает 10 кГц.

Буду рад узнать, что это ошибочное утверждение и существуют примеры, доказывающие возможность корректного АЦ Преобразования на более высоких частотах для контроллера Arduino UNO.
Вывод: максимальная частота преобразования АЦП Arduino UNO c процессором ATmega328 не превышает 10 кГц.

Не понимаю, что там исследовать, когда есть даташит, в котором расписано всё до последнего цикла (страница 237), и который говорит, что АЦП в 8-битном режиме способен на почти 77 килосемплов в секунду, и 15 в 10-битном.

Спасибо за ссылку на datasheet.
Вариант канала контроллер – COM порт – модель МатЛАБ с сэмплированием на 66 кГц изложен в дополнении к основной работе: последнее “Задание 6”.
Во-первых, хорошей привычкой будет использовать присваивание значений регистру при первом использовании в среде программирования, в которой не можешь быть уверен, не накидала ли среда еще чего. Arduino вполне себе может.
Как раз вот это:
На результат не влияют: установка частоты преобразования 76 КГц и выше битами ADPS0… ADPS2

Должно было вас насторожить.

Во-вторых, вы забыли переключить АЦП в 8-битный режим. В 10-битном режиме максимум — 15килосемплов.

Вот ваш скетч, но выдающий в бенчмарке 65килосемплов. Не 76, но у нас слишком много накладных расходов по дороге
Скетч
void setup() {
Serial.begin (57600); // 9600, 19200, 38400, 57600 and 115200 bit/s

ADCSRA = (1 << ADEN) // Включаем АЦП
|(1 << ADPS2); // устанавливаем предделитель преобразователя на 8
ADMUX = (1<< ADLAR) | (1 << REFS0) //выставляем опорное напряжение, как внешний ИОН
|(0 << MUX0); // снимать сигнал будем с входа PC0 
}
int u=0;
void loop(){ 
unsigned long time_start = millis(); 
for (int i = 0; i < 1024; i++) {
ADCSRA |= (1 << ADSC); // Начинаем преобразование 
while ((ADCSRA & (1 << ADIF)) == 0);// пока не будет выставлено флага об окончании преобразования
 u = ADCH; // Считываем полученное значение
// adc_bytes[i] = ADCL; // Считываем полученное значение
} 
unsigned long time_end = millis(); 
unsigned int dt = time_end - time_start; 
unsigned int f = 1000000000/1024/dt;
Serial.print (f);
Serial.println("Hz");
}




В-третьих, выше я написал корректный код для Atmega1280 (Читать Arduino Mega). Переделывать под ATmega328 мне лень — откройте датащит и проверьте имена регистров. Таки могут отличаться. А еще преобразователь uart-usb у Uno может не тянуть скорость в 1мбод. Он там на Atmega8u2 реализован, и ХЗ что там в программе написано. Не смотрел.
Мой скетч целиком
бенчмарков не делал
void setup()
{
autoadcsetup();
}


void loop()
{

}
int i = 255;

void autoadcsetup(){
//set up TIMER0 to  62.5kHz
//TIMER0_OVF will be the trigger for ADC
/*normal mode, no prescaler
16MHz / 256 = 62.5 kHz*/
TCCR0B = (1 << CS00);//timer frequency = clk/1
//set ADC.
ADMUX = (1 << ADLAR) | (1 << MUX0) | (1 << REFS0);//8-bit mode, ADC0 channel, AVVCC as ref
ADCSRA = (1 << ADEN) | (1 << ADATE) | (1 << ADIE) | (1 << ADPS2);//TUrn ADC On, trigger enable, Interrupt enable, sysclk/16=1MHz_ADC_clk=76kHz conv freq(13ticks per conversion)
ADCSRB = (1<< ADTS2) ;//Auto trigger source
//set UART to 8-n-1 1Mbod:
UBRR0H = 0;//1Mbod (use Examples of Baud Rate Setting table from datasheet)
UBRR0L = 1;//1Mbod
UCSR0B = (1<<TXEN0);//enable Transmitter
UCSR0C = (3<<UCSZ00);//8-bit mode
}

ISR(ADC_vect){
  
  if( ( UCSR0A & (1<<UDRE0)) ){
    UDR0 = ADCH;//copy result. 
  }
}




В четвертых, если вы читаете ADCL — обязательно требуется прочитать ADCH — пока не прочтете старший, данные заблокированы. т.е.е либо младший-старший, либо только старший, но никак не наоборот.

В пятых. Просто напоминаю
57600бод/10 (8-n-1 — старт-8 бит данных-стоп) = 5,76 кГц На передачу.

Спасибо за обстоятельный комментарий.
Вариант канала контроллер – COM порт – модель МатЛАБ с сэмплированием на 66 кГц изложен в дополнении к основной работе: последнее “Задание 6”.
Супер!
Такое построение не требует компиляции Simulink модели с библиотекой реального времени (rtwin.tlc), что позволяет использовать в модели практически любые блоки библиотеки Simulink.

Да, но Desktop Real-Time при умелом испльзовании позволяет замкнуть обратную связь между оборудованием и ПК на частотах вплоть до 20 кГц из под, простите, Windows.
Какие дальнейшие планы, и какие блоки у не получилось скомпилировать до этого?
Мой опыт работы с PCI (!!!) платами: Sensory 626 и NI-6014 (“узнаваемыми” МатЛАБ) говорит, что на 2 КГц в RT режиме (с rtwin.tlc) работа возможна с несложными моделями, которые накапливают данные в workspace не более одной минуты.
В отличии от LabWiew, МатЛАБ не будет компилировать модель для работы в режиме реального времени если не может гарантировать все необходимые вычисления внутри такта (и этим он тоже хорош).

Однако, интересно увидеть ваши примеры работы оборудования с МатЛАБ в контурных задачах в режиме реального времени на частотах 10… 20 кГц.

Планы? — выставлять работы в основном связанные с автоматизацией и управлением технических систем под “dr bob davidov”
>> 2 КГц в RT режиме (с rtwin.tlc) работа возможна с несложными моделями
Вы абсолютно правы. В большинстве случаем так и есть.

Но если
— использовать довольно производительный ПК
— не писать данные в рабочую область
— использовать rtwinert.tlc
— и избежать других узких мест
20 кГц потолок.
Да, и модель сама, и драйвера карты должны быть тоже «удачными».
Не могу назвать это рабочим решением. Скорее вид оверклокинга.
Можно конечно еще подобрать плату с предобработкой, чтобы не гонять сырые данные туда-сюда.
А еще лучше взять отдельный ПК реального времени.
Или подобие.
В режиме rtwin.tlc частота сэмплирования совпадает с частотой RT. Например, при частоте RT 2 КГц вычисления цикла в режиме rtwin.tlc не могут превышать 0.5 мсек…
В предлагаемом варианте период RT может заметно превышать период сэмплирования. Сэмплирование с частотой выше 2 КГц может выполняться в RT режиме с периодом в десятки и сотни мсек. Используя векторную обработку данных (а средства МатЛАБ и ориентированы на работу с матрицами) можно существенно (в десятки и более раз) увеличить вычислительную мощь алгоритмов ( в сравнении с 0.5 мс вариантом) и использовать недорогие популярные средства типа Arduino.
Яростно плюсую. Очень хороший инженерный пост!
0.07 – время цикла (в секундах) — время построения одного кадра. Здесь построение кадра должно быть чуть дольше, чем суммарное время 256 преобразований АЦП (42 мс) и передачи (23 мс). При автономной работе Arduino время передачи используется для обработки данных по найденному в МатЛАБ алгоритму.

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

В этих примерах заголовок (header) может быть и другим. Только его нужно указать и в коде Arduino и в блоке “Serial Receive” модели.

Ребята, вам будет интересно почитать, как с stm32f4 сразу с трех АЦП 12-битной точности по USB-HS CDC (все тот же СОМ порт, только без FTDI) бросать данные на комп?
Просто на мой взгляд, кто с stm32 работает это будет мало интересно, а ардуинщикам — кромешный ад…
Ну, я не первый и не второй, потому было бы интересно :)
При том f4 discovery без дела валяется…
Sign up to leave a comment.

Articles