"Вы читаете о роботах и программировании и думаете: «Было бы здорово сделать что-то подобное самому!» Теми, кем эта идея овладевает чуть больше просто мыслей смотрят кто и как делал своего робота. Читают статьи, смотрят видео. На картинках все понятно. В видеороликах тоже обычно показываются уже готовые продукты, а также сжато показываются технологии их изготовления. И вроде бы то же всё понятно: отпилил, прикрутил, припаял, соединил, запрограммировал вон на той программе вот этим кодом."

среда, 10 марта 2021 г.

Ускорение функции analogRead()

Когда мы убедились что стандартные функции для работы с дискретными сигналами работают в разы медленнее, а размер программы на порядок меньше, имеет смысл рассмотреть и другие способы управления сигналами на выводах микроконтроллера, используя регистры нашего микроконтроллера. Однако надо отдавать себе отчёт, что у этого способа есть плюсы и минусы. Основных плюсов два, скорость реакции программы её размер. К основным минусам можно отнести, то что рассматривая регистры конкретного микроконтроллера, мы неизбежно столкнётся, с тем что на других  МК структура регистров и их количество будет различаться, и как следствие программа становиться не переносимой. Другим значительным минусом становиться трудоемкость написания программы, что требует большей квалификации от программиста разработчика.
Тем не менее, мы знаем, что функции ввода-вывода цифровых сигналов digitalWriter() и digitalRead()   можно заменить на непосредственные операции с регистрами портов D,В,C. Теперь пора перейти к рассмотрению аналоговых сигналов, которые может обрабатывать микроконтроллер.
В отличие от остальных функций, ускорение функции analogRead() достигается не модификацией её исходного кода, а настройкой режима работы АЦП. Но обо всем по порядку и начнем с описания работы АЦП микроконтроллера Atmega 328P. 


Микроконтроллер Atmega 328P, на котором построен Arduino Uno содержит встроенный 10-битный аналого-цифровой преобразователь (далее АЦП, англ. ADC — Analog to Digital Converter) последовательного приближения. Именно он, как видно из названия, отвечает за оцифровку входящего аналогового сигнала.


Для осуществления корректного преобразования, АЦП необходимо эталонное значение напряжения, с которым будет сравниваться входящий аналоговый сигнал. Это эталонное значение называется источником опорного напряжения (ИОН). Микроконтроллер Atmega 328P позволяет в качестве ИОН использовать:
  • напряжение питания микроконтроллера — 5 В,
  • внутренний опорный источник на 1,1 В (на Atmega8 — 2,56 В),
  • напряжение на выводе AREF (внешний ИОН, референтное напряжение).




По умолчанию, у Arduino в качестве ИОН выступает напряжение питания МК — 5 В. Для использования в качестве ИОН других источников референтного внешнего напряжения у микроконтроллера есть дополнительный вход AREF. Перед преобразованием аналогового сигнала, при использовании внешнего ИОН необходимо вызвать функцию analogReference().



Сигнал, поданный на вход АЦП должен быть в границах заданного диапазона 0 (GND)...ИОН. 10-битный означает, что заданный диапазон будет разбит на значения, и входной сигнал будет оцифрован в соответствующее цифровое значение из диапазона 0...1023:




Запуск преобразования может осуществляться несколькими способами:
  • в ручном режиме — единичное преобразование
  • а автоматическом режиме — по сигналам из различных источников








Для управления АЦП существует 5 основных восьми битных регистров: 



Регистр ADCSRA (ADC Control and Status Register A)

Назначение битов регистра ADCSRA (ADC Control and Status Register A, регистр управления и состояния):
  • ADEN (ADC Enable, включение АЦП) — флаг, разрешающий использование АЦП, при сбросе флага во время преобразования процесс останавливается;
  • ADATE (ADC Auto Trigger Enable) — выбор режима работы АЦП. 0 – разовое преобразование (Single Conversion Mode); 1 – включение автоматического преобразования по срабатыванию триггера (заданного сигнала). Источник автоматического запуска задается битами ADTS[2:0] регистра ADCSRB. Возможны следующие варианты: 



  • ADSC (ADC Start Conversion, запуск преобразования) — флаг установленный в 1 запускает процесс преобразования.
    В режиме Single Conversion (ADATE=0) для запуска каждого нового преобразования этот флаг должен быть установлен в единицу. После завершения преобразования, флаг ADSC сбрасывается в 0.
    В режиме Free Running Mode (ADATE=1, ADTS[2:0]=000) этот флаг должен быть установлен в единицу один раз — для запуска первого преобразования, следующие происходят автоматически.
    Если установка флага ADSC происходит во время или после разрешения АЦП (ADEN), то перед запрашиваемым преобразованием происходит дополнительное (extended) преобразование, во время которого происходит инициализация АЦП. Сброс флага во время преобразования не оказывает никакого влияния на процесс.
  • ADIF (ADC Interrupt Flag) — флаг прерывания от компаратора
  • ADIE (ADC Interrupt Enable) — разрешение прерывания от компаратора
  • ADPS[2:0] (ADC Prescaler Select) — комбинацией битов задается частота преобразования АЦП по отношению к частоте МК. Выбранная частота влияет на точность — чем выше частота, тем ниже точность. АЦП тактируется через выбранный делитель от частоты ядра микроконтроллера. Значения битов:
 Частота работы МК Atmega 328P 16МГц. При настройках по умолчанию используется пред делитель 128 (ADPS[2:0]=[111]), а это значит, что АЦП работает на частоте 16МГц/128=125КГц, что укладывается в данные даташита – 50-200КГц. Отсюда очень низкая скорость выполнения преобразования, но и самая высокая точность. Для того, чтобы ускорить работу АЦП, необходимо уменьшить пред делитель, но необходимо помнить, что чем выше частота, тем ниже точность преобразования. Экспериментально можно получить значение пред делителя – 16 (ADPS[2:0]=[100]), при котором возможен компромисс 10-кратного прироста скорости, при сохранении точности.

 int pinIn = A0; // Пин аналогового входа
 void setup()
{
 pinMode(pinIn, INPUT);
   ADCSRA |= (1 << ADPS2); //Биту ADPS2 присваиваем единицу - коэффициент деления 16
   ADCSRA &= ~ ((1 << ADPS1) | (1 << ADPS0)); //Битам ADPS1 и ADPS0 присваиваем нули
}
 void loop()
 { 
   Serial.println(analogRead(pinIn));  
  delay(1000); 
}
 Замеры производительности показывают время выполнения функции — 16 мкс (вместо первоначальных 112 мкс):
 
Регистр ADMUX (ADC Multiplexer Selection Register)

Вспомним какие биты содержатся в регистре ADMUX:

Назначение битов регистра ADMUX:
  • REFS[1:0](Reference Selection Bit) — биты определяют источник опорного напряжения. Возможные значения:

  • ADLAR(ADC Left Adjust Result) — бит отвечающий за порядок записи битов результата в регистры ADCL и ADCH. В зависимости от того значения, которое присвоено биту ADLAR возможны 2 варианта:
          Если ADLAR=0, то 
 
          Если ADLAR=1, то
 
  •  MUX[3:0] (Multiplexer Selection Input) — биты выбора аналогового входа. Значения:
 

Использованы материалы: https://codius.ru/articles/Arduino