Урок 27. Работа с АЦП через регистры CMSIS. Основные режимы преобразования.

АЦП STM32

Продолжим работу с АЦП. Разберем основные режимы преобразования. В качестве примеров разработаем несколько вариантов многоканальных вольтметров.

Предыдущий урок     Список уроков     Следующий урок

В предыдущем уроке мы научились задавать конфигурацию АЦП. Для практической работы с ним осталось узнать совсем немного.

 

Чтобы совершить преобразование аналогового сигнала в STM32 необходимо:

  • Сделать общие установки, задать конфигурацию,  выполнить калибровку и т.п. В общем, все то, что мы сделали в предыдущем уроке.
  • Задать канал или каналы для преобразования в регистрах ADC_SQRx и ADC_JSQR. Этот вопрос также описан в предыдущем уроке.
  • Выбрать режим преобразования.
  • Запустить преобразование.
  • Определить, что преобразование закончилось.
  • Считать результат преобразования из регистров ADC_DR и ADC_JDRx.

Разберемся, что мы из этих пунктов пока не умеем делать.

 

Запуск преобразования.

В предыдущем уроке мы установили в качестве источника запуска для регулярных каналов бит SWSTART. Это программный запуск, который производится установкой бита SWSTART в регистре ADC_CR2.

ADC1->CR2 |= ADC_CR2_SWSTART; // запуск АЦП

Пока будем использовать только этот способ. Остальные рассмотрим позже.

 

Определение завершения преобразования.

О завершении преобразования заданных каналов сообщают биты EOC и JEOC, расположенные в регистре состояния ADC_SR.

  • Бит EOC устанавливается аппаратно при окончании преобразования заданных каналов регулярной или инжектированных групп. Сбрасывается он программно или при чтении регистра ADC_DR.
  • Бит JEOC устанавливается аппаратно при завершении преобразования заданных каналов инжектированной группы. Сбрасывается только программно.

В итоге получаем 3 варианта проверки завершения преобразования АЦП:

  • Для регулярной группы необходимо дождаться, пока состояние бита EOC станет равным 1. После этого считать результат из регистра ADC_DR. Бит EOC  сбросится автоматически.
  • Точно также можно поступить по отношении к инжектированным каналам. Только бит EOC необходимо сбрасывать программно после чтения результата из регистров ADC_JDR1 - ADC_JDR4.
  • Для проверки завершения преобразования инжектированных каналов можно использовать бит JEOC. Сбрасывать его надо программно.

По установке битов EOC и JEOC могут формироваться прерывания, если они разрешены в битах EOCIE и JEOCIE соответственно.

 

Режимы преобразования.

Для задания первых четырех режимов нас интересуют 2 бита:

  • Бит CONT регистра ADC_CR2. Разрешение режима непрерывного преобразования.
  • Бит SCAN регистра ADC_CR1. Разрешение режима сканирования каналов.

При установке бита CONT после окончания преобразования группы каналов, будет автоматически запущено новое преобразование. Таким образом, АЦП будет работать непрерывно.

Установка бита SCAN разрешает режим сканирования, т.е. опрос нескольких каналов. При сброшенном бите будет опрашиваться только один канал.

Состояние битов Режим
CONT SCAN
0 0 Один канал, однократное преобразование
0 1 Несколько каналов, однократное преобразование
1 0 Один канал, непрерывное преобразование
1 1 Несколько каналов, непрерывное преобразование

Поставим конкретную задачу.

Разработать многоканальный вольтметр, измеряющий значение на четырех входах.

  • Результат измерений выводить на LCD-дисплей и передавать на компьютер через последовательный порт.
  • Измеряемое напряжение в пределах 0…3,3 В. При желании можно подключить к входам резисторные делители и тем самым увеличить диапазон измерения.
  • Чтобы вольтметр можно было использовать в реальных разработках, добавим усреднение измеряемых значений.
  • Реализуем эту задачу с использованием различных режимов АЦП.

Вольтметр STM32

Режим один канал, однократное преобразование.

Это режим, в котором запускается преобразование только одного канала. После его завершения АЦП останавливается и ждет следующего запуска.

Мы уже задали этот режим при инициализации АЦП, но давайте повторим ту часть установок, которая изменяется в зависимости от режима.

Будем использовать канал 0 регулярной группы. При изменении режимов лучше запрещать работу АЦП.

//--------------- Режим один регулярный канал, однократное преобразование

ADC1->CR2 &= ~ADC_CR2_ADON; // запретить АЦП

// выбор каналов
ADC1->SQR1 =0; // 1 регулярный канал
ADC1->SQR3 =0; // 1 преобразование - канал 0

ADC1->CR2 &= ~ADC_CR2_CONT; // запрет непрерывного режима
ADC1->CR1 &= ~ADC_CR1_SCAN; // запрет режима сканирования

ADC1->CR2 |= ADC_CR2_ADON; // разрешить АЦП

В цикле:

  • Запускаем АЦП.
  • Ожиданием окончания преобразования.
  • Пересчитываем результат в напряжение.
  • Выводим измеренное значение на LCD-дисплей.
  • Выводим данные в последовательный порт.

float res; // переменная для результата
uint8_t str[32]; // строка

while(1) {
// основной цикл

  // измерение сигнала
  ADC1->CR2 |= ADC_CR2_SWSTART; // запуск АЦП
  while(!(ADC1->SR & ADC_SR_EOC)) ; // ожидание завершения преобразования

  res= (float)ADC1->DR * VREF / 4096. ; // пересчет в напряжение

  // вывод на LCD
  sprintf((char *)str, "%d.%03d V", (uint16_t)res, ((uint16_t)((res - (uint16_t)res)*1000.)) );
  setCursor_LCD(1, 1);
  print_str_LCD(str);

  // передача на компьютер
  str[6]= 0xd; str[7]= 0xa; // перевод строки
  HAL_UART_Transmit(&huart1, str, 8, 100);

  HAL_Delay(300);
}

Вольтметр работает.

Вольтметр STM32

Вольтметр на STM32

Значение напряжения ”скачет” только в последнем разряде.

Проект можно загрузить по ссылке.

 

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!
 

Это самый простой способ использования АЦП микроконтроллера STM32. Несмотря на это, он тоже имеет право на жизнь.

В принципе, этот способ аналогичен применению АЦП в Ардуино. Но только в Ардуино при измерении аналогового сигнала программа ”подвисает” на 105-110 мкс. А в STM32 мы можем добиться времени измерения до 1 мкс. И точность в 4 раза выше.

Для многих приложений идеальный измеритель. Даже не знаю, имеет ли смысл при точечных редких измерениях использовать прерывания или прямой доступ к памяти. Может лучше подождать 1-2 мкс.

На этом изучение АЦП можно заканчивать. Для тех, кто хочет большего, продолжим.

 

Сделаем тоже самое для инжектированного канала 1.

//--------------- Режим один инжектированный канал, однократное преобразование

ADC1->CR2 &= ~ADC_CR2_ADON; // запретить АЦП

// выбор каналов
ADC1->JSQR =0; // 1 инжектированный канал
ADC1->JSQR |= ADC_JSQR_JSQ4_0; // 1 преобразование - канал 1

ADC1->CR2 |= ADC_CR2_JEXTSEL; // источник запуска - JSWSTART
ADC1->CR2 |= ADC_CR2_JEXTTRIG; // разрешение внешнего запуска для инжектированных каналов

ADC1->CR2 &= ~ADC_CR2_CONT; // запрет непрерывного режима
ADC1->CR1 &= ~ADC_CR1_SCAN; // запрет режима сканирования

ADC1->CR2 |= ADC_CR2_ADON; // разрешить АЦП

float res; // переменная для результата
uint8_t str[32]; // строка

while(1) {
// основной цикл

  // измерение сигнала
  ADC1->CR2 |= ADC_CR2_JSWSTART; // запуск АЦП
  while(!(ADC1->SR & ADC_SR_EOC)) ; // ожидание завершения преобразования

  res= (float)ADC1->JDR1 * VREF / 4096. ; // пересчет в напряжение

  ADC1->SR &= ~ADC_SR_EOC; // сброс флага

  // вывод на LCD
  sprintf((char *)str, "%d.%03d V", (uint16_t)res, ((uint16_t)((res - (uint16_t)res)*1000.)) );
  setCursor_LCD(1, 1);
  print_str_LCD(str);

  // передача на компьютер
  str[6]= 0xd; str[7]= 0xa; // перевод строки
  HAL_UART_Transmit(&huart1, str, 8, 100);

  HAL_Delay(300);
}

Отличия:

  • Изменилась установка каналов.
  • Другой выбор источника запуска.
  • Запускаем другой бит (JSWSTART).
  • Результат в регистре JDR1.
  • Сбрасывается флаг EOC.

Вместо бита EOC можно использовать JEOC.

Теперь вольтметр измеряет напряжение не на нулевом, а на первом канал.

 

Один канал, непрерывное преобразование.

Если установить бит CONT, то после окончания преобразования, оно будет запускаться снова. АЦП будет непрерывно измерять значение напряжения на выбранном входе, и помещать его в регистр ADC_DR для регулярного канала и в регистр ADC_JDR1 для инжектированного.

Таким образом, в ходе выполнения программы совсем исчезает необходимость в управлении АЦП. Просто есть регистр, в котором хранится всегда ”свежий” результат преобразования. Ни одна команда программы не тратится на процесс измерения.

//--------------- Режим один регулярный канал, непрерывное преобразование

ADC1->CR2 &= ~ADC_CR2_ADON; // запретить АЦП

// выбор каналов
ADC1->SQR1 =0; // 1 регулярный канал
ADC1->SQR3 =0; // 1 преобразование - канал 0

ADC1->CR2 |= ADC_CR2_CONT; // разрешение непрерывного режима
ADC1->CR1 &= ~ADC_CR1_SCAN; // запрет режима сканирования

ADC1->CR2 |= ADC_CR2_ADON; // разрешить АЦП

float res; // переменная для результата
uint8_t str[32]; // строка

ADC1->CR2 |= ADC_CR2_SWSTART; // запуск АЦП

while(1) {
// основной цикл

  res= (float)ADC1->DR * VREF / 4096. ; // пересчет в напряжение

  // вывод на LCD
  sprintf((char *)str, "%d.%03d V", (uint16_t)res, ((uint16_t)((res - (uint16_t)res)*1000.)) );
  setCursor_LCD(1, 1);
  print_str_LCD(str);

  // передача на компьютер
  str[6]= 0xd; str[7]= 0xa; // перевод строки
  HAL_UART_Transmit(&huart1, str, 8, 100);

  HAL_Delay(300);
}

Мы запускаем преобразование АЦП один раз перед циклом. А в цикле считываем регистр ADC_DR, вычисляем напряжение и выводим.

Давайте запустим непрерывное преобразование для регулярного и инжектированного каналов одновременно. Получим 2 регистра с текущими значениями напряжений на двух входах.

У инжектированных каналов нет отдельного режима непрерывного преобразования. Это следует из их названия. Но есть возможность автоматически запускать их опрос после окончания преобразования регулярной группы.  Для этого надо установить в регистре ADC_CR1 бит JAUTO.

ADC1->CR1 |= ADC_CR1_JAUTO; // разрешение автоматического преобразования инжект. каналов

Теперь в программе есть 2 регистра ADC_DR и ADC_JDR1, в которых содержатся значения напряжений нулевого и первого каналов. Их значения пересчитываем в напряжение и выводим.

Вольтметр STM32

Вольтметр на STM32

Код программы разрастается, поэтому приводить его не буду. Посмотрите в проекте Lesson27_2.

 

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!
 

 

Несколько каналов, однократное преобразование.

В этом режиме происходит автоматический опрос последовательности каналов, заданной в регистрах ADC_SQRx и ADC_JSQR. Иногда его называют режимом сканирования.

Включается режим установкой в регистре ADC_CR1 бита SCAN.

Для результатов преобразований регулярной группы есть только один регистр ADC_DR. Бит EOC, сообщает о завершении преобразования всех заданных каналов группы. Если мы считаем результат преобразования после того, как он установится, то в регистре ADC_DR останется результат последнего канала.

Единственный способ опроса регулярных каналов в таком режиме – использование прямого доступа к памяти. Но я ошибся, когда отложил рассказ о контроллере ПДП (DMA) на последующие уроки. Я это почувствовал уже в уроках про UART. Но сам процесс передачи данных таким способом достаточно простой. Сложнее конфигурировать контроллер ПДП.

Вернусь к UART и АЦП после уроков про ПДП. Сейчас скажу, что при использовании контроллера прямого доступа к памяти результат каждого преобразования АЦП будет переписываться в ячейки памяти.

Для хранения результатов преобразований инжектированных каналов отведены 4 регистра ADC_JDR1- ADC_JDR4 . Поэтому можно использовать один запуск и получить 4 значения, измеренные инжектированными каналами даже без прямого доступа к памяти. Ну и еще один регулярный канал.

Давайте разработаем программу, которая циклически запускает 5 преобразований: одно регулярного канала и четыре для инжектированной группы.

Последовательность каналов инжектированной группы 1, 2, 3, 4.

ADC1->JSQR = 0b00000000001100100000110001000001; // 1, 2, 3, 4 посл. инж. каналов

Разрешаем сканирование.

ADC1->CR2 &= ~ADC_CR2_CONT; // запрет непрерывного режима
ADC1->CR1 |= ADC_CR1_SCAN; // разрешение режима сканирования

В цикле запускаем регулярный канал (установлен только 1) и ожидаем окончания преобразования.

// регулярный канал
ADC1->CR2 |= ADC_CR2_SWSTART; // запуск АЦП
while(!(ADC1->SR & ADC_SR_EOC)) ; // ожидание завершения преобразования

Тоже самое для инжектированной группы (4 канала).

ADC1->CR2 |= ADC_CR2_JSWSTART; // запуск инжектированной группы
while(!(ADC1->SR & ADC_SR_EOC)) ; // ожидание завершения преобразования
ADC1->SR &= ~ADC_SR_EOC;

А затем считываем значения регистров ADC_DR , ADC_JDRx и выводим рассчитанные значения напряжений.

Полностью проект здесь. Lesson27_3.

 

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!
 

 

Несколько каналов, непрерывное преобразование.

Сделаем опрос тех же 5ти каналов в непрерывном режиме.

Устанавливаем режим, разрешаем сканирование и непрерывное преобразование.

ADC1->CR1 |= ADC_CR1_JAUTO; // разрешение автоматического преобразования инжект. каналов

ADC1->CR2 |= ADC_CR2_CONT; // разрешение непрерывного режима
ADC1->CR1 |= ADC_CR1_SCAN; // разрешение режима сканирования

Перед циклом запускаем АЦП.

ADC1->CR2 |= ADC_CR2_SWSTART; // запуск АЦП

В самом цикле остается только пересчитывать значения регистров ADC_DR , ADC_JDRx в напряжение и выводить на экран.

Вот проект Lesson27_4.

 

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!
 

Все, разработанные нами вольтметры  имеют один недостаток – отсутствия усреднения измеряемого сигнала. Поэтому получились не совсем практичные устройства. Усреднение сделаем в следующих уроках, когда будем запускать преобразование от таймера.

 

В следующем уроке продолжим изучать различные режимы работы АЦП.

Предыдущий урок     Список уроков     Следующий урок

1

Автор публикации

не в сети 4 дня

Эдуард

285
Комментарии: 1946Публикации: 200Регистрация: 13-12-2015

13 комментариев на «Урок 27. Работа с АЦП через регистры CMSIS. Основные режимы преобразования.»

  1. Да уже прочёл. Спасибо.
    Больше вопросов не по АЦП а по преобразованию float при выводе на индикатор.
    А можно пояснить как работает данная конструкция:
    sprintf((char *)str, «%d.%03d V», (uint16_t)res, ((uint16_t)((res — (uint16_t)res)*1000.)) ); ?

    0
  2. Здравствуйте. Такой вопрос: использую одиночное преобразование, в общем — первый пример.
    Когда измеряю напряжение с ноги 3.3 V микроконтроллера — в итоге получается 3.3 В. Все хорошо
    Но когда начинаю мерить (например, со стороннего источника питания, или хотя бы с ножки программатора 3,3 V), то получаю меньшее значение напряжения (около 2,2 В)
    Земля везде объединена
    Почему так получается? Когда измеряю сигналы микроконтроллера — измеряется правильно. А когда чего-то стороннего (например хочу снять сигнал с ножки 3.3V программатора)- получаю меньшее значение
    Спасибо

    0
      • Аналоговый вход платы? Вы имеете ввиду ножку АЦП? У меня например А0, в обычном (неработающем) состоянии напряжение на ней 0,4 В (измерил вольтметром).

        0
  3. а в каком байте будет измеренное значение напряжения ? в str[1] или str[2] или str[3] ?
    // передача на компьютер
    str[6]= 0xd; str[7]= 0xa; // перевод строки
    HAL_UART_Transmit(&huart1, str, 8, 100);

    0
    • Здравствуйте!
      Напряжение будет в текстовой строке в символьном виде. Если передаются 2 значения, то первое число это первое значение, после пробела — второе значение. Посмотрите монитором последовательного порта.

      0
  4. Спасибо автору за разносторонний подход к решению задач. Во всяком случае при конфигурации лучше использовать «кубик». Библиотеки необходимы, при желании можно немножко раздеть, но надо уметь работать с отладчиком.

    0

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Нажимая кнопку "Отправить" Вы даёте свое согласие на обработку введенной персональной информации в соответствии с Федеральным Законом №152-ФЗ от 27.07.2006 "О персональных данных".