В объемном уроке разберемся с режимами совместной работы модулей АЦП. Разработаем 2 учебных, но действующих проекта с использованием сразу двух АЦП.
Предыдущий урок Список уроков Следующий урок
Извините за долгое отсутствие новых материалов на сайте. Спасибо всем, кто переживал за меня, интересовался моим здоровьем. Нет, я не болел. У меня хорошее здоровье. Я никогда не болею. Просто была какая-то апатия, как и во всей стране. Надеюсь, теперь уроки будут появляться регулярно. Еще раз извините.
Большинство микроконтроллеров семейства STM32 содержат, как минимум, 2 однотипных модуля АЦП. По крайней мере, их два у микроконтроллера STM32F103C8T6, который я принял за базовый в моих уроках.
Я уже говорил, что АЦП STM32 – это сложный узел, но доскональное знание его режимов работы позволяет выполнять задачи, которые невозможно реализовать в других микроконтроллерах.
Одним из уникальных свойств STM32 является широкие возможности совместной работы двух АЦП. Разработчики STM32 создали режимы, позволяющих функционально объединить модули АЦП, превратив их в устройство с качественно другими возможностями и параметрами. Тем самым подтвердили основной закона диалектики – количество переходит в качество.
Режимов сдвоенных преобразования несколько. Не уверен, что они все востребованы. Я бы выделил 2 случая эффективного использования работы модулей АЦП совместно.
- Есть потребность повысить быстродействие аналогово-цифровых преобразований. Объединение модулей позволяет увеличить это параметр в 2 раза.
- Необходимо считывать 2 аналогового сигнала одновременно, например напряжение и ток для вычисления мгновенной мощности.
Режимы сдвоенных преобразований АЦП.
Режим устанавливается в поле DUALMOD[3:0] регистра ADC1_CR1. У второго модуля такого поля нет.
В любом режиме сдвоенных преобразований АЦП 1 становится ведущим, а АЦП 2 – ведомым, и все управление происходит через регистры ведущего. Все запуски происходят от единого источника, который выбирается битами EXTSEL[2:0] или JEXTSEL[2:0] регистра ADC1_CR2.
Существуют следующие режимы, объединяющие модули АЦП в нечто единое.
- Режим одновременных преобразований инжектированных каналов (Injected simultaneous mode).
- Режим одновременных преобразований регулярных каналов (Regular simultaneous mode).
- Режим быстрых преобразований со смещением во времени (Fast interleaved mode).
- Режим медленных преобразований со смещением во времени (Slow interleaved mode).
- Режим поочередного запуска АЦП (Alternate trigger mode).
- Комбинированный режим одновременного преобразования инжектированных и регулярных каналов (Injected simultaneous mode + Regular simultaneous mode).
- Комбинированный режим преобразования одновременного преобразования регулярных каналов и поочередного запуска (Regular simultaneous mode + Alternate trigger mode).
- Комбинированный режим одновременного преобразования инжектированных каналов и преобразований со смещением во времени (Injected simultaneous mode + Interleaved mode).
Режим одновременных преобразований инжектированных каналов.
В этом режиме происходит одновременное преобразование для инжектированных групп обоих АЦП. Запуск обоих модулей происходит через мультиплексор АЦП 1. Источник выбирается битами JEXTSEL[2:0] регистра ADC1_CR2.
По окончанию процесса преобразования:
- Результаты хранятся в регистрах ADC_JDRx соответствующих АЦП.
- Аппаратно устанавливаются биты JEOC для обоих модулей. Сбрасывается только программно.
- Формируется прерывание JEOC , если оно разрешено на одном из модулей.
В этом режиме для обоих АЦП должно быть задано одинаковое время выборки.
Режим одновременных преобразований регулярных каналов.
В этом режиме происходит одновременное преобразование для регулярных групп обоих АЦП. Запуск обоих модулей происходит через мультиплексор АЦП 1. Источник выбирается битами EXTSEL[2:0] регистра ADC1_CR2.
Результаты преобразований передаются в ОЗУ через контроллер ПДП (DMA). Другого способа чтения данных регулярных каналов нет.
По окончанию процесса преобразования:
- Аппаратно устанавливаются биты EOC для обоих модулей.
- Формируется прерывание EOC и запрос DMA, если они разрешены на одном из модулей.
В этом режиме для обоих АЦП должно быть задано одинаковое время выборки.
Режим быстрых преобразований со смещением во времени.
Режим применяется только для регулярных групп. Обычно используется один общий канал для обоих модулей. Цель режима – увеличить быстродействие АЦП вдвое.
Все происходит как в режиме одновременных преобразований регулярных каналов, только АЦП 1 запускается с небольшой задержкой (7 циклов).
- Запуск преобразований происходит от мультиплексора АЦП 1.
- Немедленно запускается преобразование АЦП 2.
- После задержки 7 циклов запускается АЦП 1.
Если бит CONT установлен в регистрах конфигурации обоих АЦП, то процесс преобразования будет непрерывным, т.е. повторяться циклически.
По окончанию процесса конверсии:
- Результаты преобразований обоих модулей хранятся в 32х разрядном регистре ADC1_DR.
- Аппаратно устанавливаются биты EOC каждый для своего АЦП.
- Формируются прерывания EOC и запросы DMA , если они разрешены.
Время выборки не должно превышать 7 циклов, чтобы второе преобразование началось до окончания первого.
Этот режим используется для повышения быстродействия АЦП. Преобразование АЦП занимает 14 циклов. Если задать сдвиг конверсии второго АЦП на 7 циклов, то получим за 14 циклов 2 преобразования, т.е. в два раза чаще.
По такому принципу будет работать цифровой осциллограф, который мы разработаем во второй части урока.
Режим медленных преобразований со смещением во времени.
Все, как в предыдущем режиме с двумя отличиями.
- Смещение увеличено в 2 раза и составляет 14 циклов.
- Независимо от состояния бита CONT преобразования происходят непрерывно.
Последовательность преобразований выглядит так:
- По внешнему запуску немедленно начинается преобразование АЦП 2.
- Через 14 циклов запускается преобразование АЦП 1.
- Еще через 14 циклов опять запускается АЦП 2.
- И так в бесконечном цикле.
Время выборки не должно превышать 14 циклов, чтобы второе преобразование началось до окончания первого.
Режим поочередного запуска АЦП.
Используется только для инжектированных каналов. Запуск, как обычно происходит от мультиплексора АЦП 1.
- По первому запуску выполняются преобразование всех каналов инжектированной группы АЦП 1.
- По второму запуску - всех каналов инжектированной группы АЦП 2.
- И так дальше АЦП чередуются.
По окончанию преобразований своей группы устанавливается соответствующий бит JEOC и формируется прерывание, если оно разрешено.
Есть модификация этого режима. Если для обоих АЦП разрешен прерывистый режим, то логика работы меняется.
- По первому запуску происходит преобразование первого канала АЦП 1.
- По второму – преобразуется первый канал АЦП 2.
- И так далее.
Чередование идет не групп каналов, а отдельных каналов АЦП 1 и 2.
Бит JEOC устанавливается по завершению преобразований соответствующей группы.
Комбинированный режим одновременного преобразования инжектированных и регулярных каналов.
В режиме синхронизируются преобразования как инжектированных, так и регулярных групп. Во время одновременного преобразования регулярных групп можно запустить одновременные инжектированные преобразования. При этом работа с регулярными каналами будет прервана на время преобразования инжектированных групп.
Комбинированный режим преобразования одновременного преобразования регулярных каналов и поочередного запуска.
Это режим, в котором синхронные преобразования регулярных каналов могут прерываться поочередным запуском инжектированных каналов.
- Происходит процесс одновременных преобразований регулярных каналов.
- Запускается преобразование инжектированного канала в режиме чередования.
- Преобразование регулярных каналов приостанавливается и происходит преобразование инжектированной группы АЦП 1.
- Возобновляется конверсия регулярной группы.
- При следующем запуске преобразование регулярной группы приостанавливается и происходит преобразование инжектированной группы АЦП 2.
- И так далее.
При запуске инжектированного канала приостанавливается преобразование регулярных групп обоих АЦП, чтобы не терялась синхронизация между ними.
В случае, если запуск инжектированной группы пришел в момент, когда предыдущее инжектированное преобразование еще не закончено, то он просто игнорируется.
Комбинированный режим одновременного преобразования инжектированных каналов и преобразований со смещением во времени.
В этом режиме преобразование регулярных каналов происходит со смещением во времени. А этот процесс может прерываться одновременными преобразованиями инжектированной группы.
Реализация программы 4х канального вольтметра в режиме одновременных преобразований инжектированных каналов.
В качестве примера использования АЦП STM32 в режиме сдвоенных преобразований повторим разработку вольтметра из предыдущего урока. Только добавим условие, что пары каналов 0, 1 и 2, 3 должны считываться одновременно.
Например, нам необходимо измерить передаточную характеристику аналогового узла с учетом фазочастотной характеристики. И мы боимся, что измерение напряжения на входе и выходе узла со смещением во времени внесет погрешность.
Логично для такого устройства перевести АЦП в режим совместной работы и выбрать режим одновременных преобразований инжектированных каналов. В остальном повторим структуру программы из предыдущего урока.
Изменения коснутся, прежде всего, блока инициализации АЦП.
// инициализация АЦП
// конфигурирование выводов для аналоговых сигналов (PA0...PA3)
. . . . . . . . . . . .
Разрешим тактирование и второго модуля АЦП.
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; // разрешение тактирования АЦП 1
RCC->APB2ENR |= RCC_APB2ENR_ADC2EN; // разрешение тактирования АЦП 2
Запишем нули в управляющие регистры. Слово ”обнулим” применять уже не хочется. В нашей стране оно как-то в разряд мерзких слов перешло.
ADC1->CR1 = 0; // запрещаем все в управляющих регистрах
ADC1->CR2 = 0;
ADC2->CR1 = 0;
ADC2->CR2 = 0;
Время выборки установим в нужных нам каналах в обоих модулях.
ADC1->SMPR2 |= ADC_SMPR2_SMP0_0 | ADC_SMPR2_SMP0_1 ; // время выборки 28,5 циклов
ADC1->SMPR2 &= ~ADC_SMPR2_SMP0_2 ;
ADC2->SMPR2 |= ADC_SMPR2_SMP1_0 | ADC_SMPR2_SMP1_1 ; // время выборки 28,5 циклов
ADC2->SMPR2 &= ~ADC_SMPR2_SMP1_2 ;
ADC1->SMPR2 |= ADC_SMPR2_SMP2_0 | ADC_SMPR2_SMP2_1 ; // время выборки 28,5 циклов
ADC1->SMPR2 &= ~ADC_SMPR2_SMP2_2 ;
ADC2->SMPR2 |= ADC_SMPR2_SMP3_0 | ADC_SMPR2_SMP3_1 ; // время выборки 28,5 циклов
ADC2->SMPR2 &= ~ADC_SMPR2_SMP3_2 ;
Зададим последовательность преобразования каналов:
- 0 и 2 для АЦП 1,
- 1 и 3 для АЦП 2.
// выбор каналов
ADC1->JSQR = 0b00000000000100010000000000000000; // 0, 2 посл. инж. каналов АЦП1
ADC2->JSQR = 0b00000000000100011000010000000000; // 1, 3 посл. инж. каналов АЦП2
В качестве источника запуска оставим переполнение таймера 2 и разрешим запуск АЦП 2.
// источник запуска TIM2_TRGO
ADC1->CR2 &= ~(ADC_CR2_JEXTSEL_0 | ADC_CR2_JEXTSEL_2);
ADC1->CR2 |= ADC_CR2_JEXTSEL_1;
ADC1->CR2 |= ADC_CR2_JEXTTRIG;
ADC2->CR2 |= ADC_CR2_JEXTTRIG; // разрешение запуска АЦП2
Установим режим одновременного преобразования инжектированных каналов.
// Режим совместной работы, одновременное преобразование инжектированных каналов (0101)
ADC1->CR1 |= ADC_CR1_DUALMOD_0 | ADC_CR1_DUALMOD_2;
ADC1->CR1 &= ~ (ADC_CR1_DUALMOD_1 | ADC_CR1_DUALMOD_3);
Оставим режим сканирования, запретим непрерывный режим работы.
// несколько каналов, однократное преобразование
ADC1->CR2 &= ~ADC_CR2_CONT; // запрет непрерывного режима
ADC1->CR1 |= ADC_CR1_SCAN; // разрешение режима сканирования
ADC2->CR2 &= ~ADC_CR2_CONT; // запрет непрерывного режима
ADC2->CR1 |= ADC_CR1_SCAN; // разрешение режима сканирования
Осталось в файле stm32f1xx_it.c подкорректировать блок чтения данных АЦП.
sumADC[0] += ADC1->JDR1;
sumADC[1] += ADC2->JDR1;
sumADC[2] += ADC1->JDR2;
sumADC[3] += ADC2->JDR2;
В результате.
- Таймер 2 в цикле с периодом 100 мкс запускает преобразование.
- АЦП настроены на совместную работу, при которой они одновременно выполняют конверсию 0 и 1, а затем 2 и 3 каналов.
- По окончанию формируется прерывание.
- В обработчике прерывания считываются результаты преобразований и усредняются.
- В основном цикле вычисленные значения передаются на компьютер и выводятся на LCD-дисплей.
Загрузить проект резидентной программы вольтметра можно по этой ссылке.
Программа верхнего уровня из предыдущего урока работает с этим устройством.
Проект простого осциллографа на STM32.
В качестве второго учебного примера разработаем принципиально другое устройство - цифровой осциллограф. Постараемся реализовать сканирование аналогового сигнала на максимальной частоте 2 мГц.
Формализованная постановка задачи выглядит так. Устройство должно:
- Ожидать команды запуска с последовательного порта (любой байт).
- Сделать 8000 выборок значений сигнала на аналоговом входе с временной дискретностью 0,5 мкс, частота 2 мГц.
- Передать считанные значения на компьютер.
- Ожидать новой команды запуска от компьютера.
8000 выборок по 16 разрядов это 16 кБайт ОЗУ. Длительность развертки 4 мс.
Чтобы реализовать время выборки 0,5 мкс придется использовать оба модуля АЦП в режиме быстрых преобразований со смещением во времени. При этом АЦП должны работать с максимальным быстродействием.
Создадим новый проект, у меня Lesson29_2.
Установим частоту тактирования микроконтроллера 56 мГц. Только при такой частоте мы сможем получить максимально допустимую частоту тактирования АЦП 14 мГц. Для этого необходимо делитель АЦП установить на коэффициент деления 4 (56 / 4 = 14). Об этом написано в уроке 26 в разделе Тактирование АЦП. Только при этой частоте мы получим максимальное быстродействие преобразований 1 мкс.
Установим конфигурацию USART1 в режиме без прерываний. Я задал скорость 115200. Пакет данных большой.
Все остальное сделаем через регистры CMSIS.
Открываем проект. Задаем конфигурацию АЦП. Для входа осциллографа я выбрал канал ADC0. В блоке инициализации каждая строка сопровождается комментариями. Не буду повторяться, посмотрите в проекте.
В бесконечном цикле проверяется, не пришел ли новый байт в UART1, т.е. не произошел ли запуск развертки.
while ((USART1->SR & USART_SR_RXNE) == 0) {} // ожидание запуска
При таком быстродействии, конечно, чтение данных АЦП надо производить через контроллер прямого доступа к памяти (DMA). Но мы еще не умеем работать с ним. Постараемся обойтись без него.
На время конверсии сигнала запрещаем глобальные прерывания. Программа не может позволить себе отвлекаться на другие задачи.
Разрешаем работу обоих модулей АЦП.
А дальше собственно блок сканирования сигнала.
// чтение развертки 4 мс
while(adr < 4000) {
while(!(ADC1->SR & ADC_SR_EOC)) ; // ожидание завершения преобразования
adc2Data[adr] = ADC2->DR;
adc1Data[adr] = ADC1->DR;
adr++;
}
В нем в цикле на 4000 двойных выборок:
- Ожидаем признак окончания преобразования АЦП 1.
- Считываем результаты преобразования в отдельные для каждого АЦП массивы.
В официальной документации STM32 написано, что в режиме преобразований со смещением во времени данные обоих АЦП оказываются в одном 32х разрядном режиме данных АЦП 1.
Вранье! Каждое данное оказываются в регистре своего АЦП, а объединятся в одном 32х разрядном регистре только при передаче через DMA.
- Затем блок данных (16000 байт) передается на компьютер.
Данных много. Если мы переведем их в текстовый вид, то размер блока увеличится минимум в 2 раза, а также потребуются преобразования из числового вида в текстовый и наоборот. Поэтому данные передаются в числовом виде. Монитор последовательного порта Arduino IDE не позволяет просматривать их. На CoolTerm надо нажать кнопку View Hex.
Итак. Окончательный проект программы здесь.
Будем проверять.
Проверка работы осциллографа.
Запустим CoolTerm. Не забудем в Options установить скорость 115200.
Подключимся – Connect.
Выберем режим отображения данных – View Hex.
Откроем окно для посылки данных. Connection -> Send String.
Наберем в нем любой символ, и пошлем его осциллографу.
Через пару секунд загрузятся данные из осциллографа.
В окне Options по умолчанию задан размер приемного буфера 10000. Поэтому данные отобразятся не все. Можете изменить этот параметр, но сейчас это не важно. Все равно данные в числовом виде. Поэтому, ничего не понятно.
Главное, что осциллограф запускается и передает данные.
Измерение реального времени развертки.
Есть одно ”узкое” место в программе. Мы перевели АЦП в режим максимального быстродействия, и операции проверки готовности и чтения данных делаем в коротком цикле длительностью всего 1 мкс.
А успевает ли микроконтроллер? Тем более, что мы снизили частоту тактирования до 56 мГц.
Давайте измерим реальное время цикла чтения данных преобразований, т.е. время развертки осциллографа.
Будем использовать специально предназначенный для этого аппаратный модуль микроконтроллера – счетчик DWT. Я писал о нем в уроке 24.
Включим счетчик в блоке инициализации.
// включение DWT-счетчика для контроля времени выполнения
#define DWT_CYCCNT *(volatile uint32_t *)0xE0001004
#define DWT_CONTROL *(volatile uint32_t *)0xE0001000
#define SCB_DEMCR *(volatile uint32_t *)0xE000EDFC
SCB_DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; // разрешение счётчика
DWT_CONTROL |= DWT_CTRL_CYCCNTENA_Msk; // запуск счётчика
Перед запуском развертки сбросим счетчик.
DWT->CYCCNT = 0; // сброс счетчика DWT
По окончании конверсии развертки считаем значения счетчика в переменную .
ttt= DWT->CYCCNT;
И передадим на компьютер.
HAL_UART_Transmit(&huart1, (uint8_t *) (& ttt), 4, 10);
Если отключить вывод данных развертки и включить эту команду, то получим.
При каждом запуске осциллограф выводит в терминал число 36b51. Вычисляем.
- Частота тактирования 56 мГц, что соответствует длительности такта 1 / 56 = 0,01786 мкс.
- Переводим полученное число в десятичный вид и умножаем на длительность такта. 0x36b51 = 224081 * 0,01786 мкс = 4001,446 мкс.
Учитывая время запуска и остановки процесса, все правильно. Тем более число выводится стабильно одно и то же.
Мы не пропускаем ни единого преобразования, быстродействия STM32 хватает даже при частоте 56 мГц.
Программа верхнего уровня.
Без программы верхнего уровня тут совсем не обойтись. Пришлось разработать для осциллографа простенькое приложение.
Загрузить можно здесь.
Пользоваться программой очень просто, что пояснять не знаю.
- Нажимаете кнопку ПУСК.
- Запускается развертка осциллографа.
- Данные сканирования передаются на компьютер и выводятся в окне программы.
Проверка программы.
Дома у меня нет генератора сигналов. На работу тащить все это не хотелось.
Решил использовать в качестве генератора синусоидального сигнала компьютер. В интернете много программ для этого. Самой простой и удобной мне показалась эта.
Ее даже не надо устанавливать. Просто запускаешь.
Сигнал с выхода для наушников подключил через конденсатор на вход платы STM32 ADC0. К нему же остался подключенным подстроечный резистор из схемы предыдущего урока.
АЦП работает только в диапазоне 0…3 В, а сигнал переменного напряжения, двуполярный. С помощью подстроечного резистора задается положительное смещение.
Не подключая сигнал к компьютеру, я несколько раз запустил осциллограф и переменным резистором установил среднее значение нулевого сигнала в середину рабочего диапазона АЦП, примерно 1,65 В.
После подал сигнал частотой 1 кГц и получил осциллограмму.
У меня сигнал шумит из-за подключения к выходу для наушников, отсутствия фильтрации на входе платы. При желании, можно добиться гораздо лучших результатов.
Вот как выглядит сигнал частотой 5 кГц.
Я развернул диаграмму по времени.
В следующем уроке закончим управление АЦП через регистры CMSIS. Подведем итоги.
Спасибо!
Особенно полезно, что автор разъясняет «В официальной документации STM32 написано, что…», а на самом-то деле…
Эдуард, есть вопрос по этому уроку. Повторил в точности весь урок в новой версии Cube 6.2.1 и новой версии Atollic 9.3.0. Откомпилировал но при проверке реального времени цикла чтения данных преобразований у меня выдается число 5FB7C это в десятичном 392060 что дает время цикла 7002,2 . Все установки в кубе идентичны, включен счетчик DWT и сброшен перед началом. В общем все аналогично вашему уроку.
При загрузке вашего хекс файла и аналогичной проверке время цикла близкое к описанному в уроке 36B5B = 4002,2 . В чем может быть дело? Компилировал ваш исходник в новой версии Atollic и генерирования кода в Кубе -все совпадает с уроком. Проверял все хидеры и си файлы — полное совпадение. Отличие только в том что syscalls.c файл не внутри папки Src. Что пропущено может быть мной в предустановках? Интересно разобраться
Смотрите настройки компилятора. Скорее всего сильно не оптимальный код. Посмотрите ключи компилятора. Возможно включено добавление отладочной информации. Или включены дополнительные проверки.
Все круто! Спасибо большое за уроки! А можете сделать отдельный урок по программе верхнего уровня. Я так понимаю вы ее на с# писали?
Спасибо.
Нет. На Borland C++ Builder. Думаю сделать инфопродукт на эту тему.
Здравствуйте,
нет ли у вас готового примера измерения переменного напряжения (тока) для stm32f103c8 ?
Готов был бы заплатить некоторую сумму за готовый пример.
Здравствуйте!
Нет. Но алгоритм такой.
К переменному напряжению применимы измерения амплитудного значения, среднего по модулю и среднеквадратичного (действующего). Делаете определенное число выборок строго за период. При измерении амплитудного значения выбираете максимальное. При измерении среднего по модулю, суммируете значения за период с изменением знака отрицательной полу волны, и делите на число выборок.
Если сигнал имеет синусоидальную форму, то из этих значений можно вычислить среднеквадратичное значение умножением на коэффициент.
Если нет, то суммируете квадраты значений выборок и берете от результата квадратный корень.