Наш магазин на eBay Наш магазин на AliExpress Наш канал в telegram

Программирование ARM-контроллеров STM32 на ядре Cortex-M3. Часть 13. Работа с модулями ADC

Общее описание

ADC (analog to digital converter) — это аналого-цифровой преобразователь (АЦП). Модули ADC предназначены для оцифровки аналоговых сигналов, то есть позволяют измерять аналоговые величины и представлять результаты измерений в цифровой форме. В stm32 таких модулей может быть до 3-х штук.

Фактически ADC контроллера stm32 умеет измерять только напряжение и только в диапазоне от Vref- до Vref+, при том, что эти величины не могут выходить за пределы 0..+Vdd. В корпусах c количеством ног <100 внешние выводы Vref- и Vref+ вообще отсутствуют, а внутри аппаратно соединены с выводами GND и Vdd. Так что все измеряемые величины придётся каким-то образом преобразовывать в форму напряжения и вталкивать в диапазон 0..+3,3В. Для этого можно использовать самые разные схемы - делители напряжения, токоизмерительные резисторы, усилители на операционниках и так далее. В рамках этой статьи такие способы рассматриваться не будут, поскольку это совершенно отдельная тема. Сейчас же мы сосредоточимся на возможностях и особенностях самих модулей АЦП.

Возможности и особенности модулей ADC контроллеров stm32:

  • разрешение 12 бит
  • скорость оцифровки до 1 мкс при частоте 56 МГц или до 1,17 мкс при частоте 72МГц
  • до 18 физических каналов (16 внешних и 2 внутренних)
  • программируемое время сэмплирования для каждого канала
  • две группы — обычная (до 16 каналов) и инжектированная (до 4-х каналов)
  • предельный компаратор
  • прерывания по различным событиям
  • режим одиночных или непрерывных преобразований
  • режим сканирования заданного списка каналов
  • самокалибровка
  • автоматическое выравнивание результатов к старшему или младшему биту регистра
  • прерывистый режим (оцифровка подгруппы каналов по триггеру)
  • двойной режим (спаренная работа двух модулей ADC)
  • использование DMA

Скорость оцифровки, тактирование

Скорость работы ADC зависит от двух факторов — частоты тактирования и количества расходуемых на оцифровку тактов.

Тактируются модули ADC от шины APB2 через собственный предделитель, который может принимать значения 2, 4, 6, 8. Для того, чтобы включить тактирование модуля ADCx — нужно установить бит ADCx в регистре RCC_APB2ENR (функция RCC_APB2PeriphClockCmd библиотеки StdPeriph). Значения предделителя задаются битами ADCPRE[1:0] регистра RCC_CFGR (функция RCC_ADCCLKConfig библиотеки StdPeriph). Частота тактирования модулей ADC не должна превышать 14 МГц. Такая частота получается, например, при частоте системной шины 56 МГц, делителе для APB2 = 1 и делителе для ADC = 4. При частоте системной шины 72 МГц максимально возможная частота ADC получается 12 МГц (с делителем на 6).

Количество расходуемых на оцифровку одного канала тактов складывается из количества тактов расходуемых на выборку (сэмплирование) и количества тактов, расходуемых на выполнение преобразования. Время преобразования фиксировано и составляет 12,5 тактов, а вот время выборки может быть запрограммировано отдельно для каждого канала на несколько фиксированных значений от 1,5 до 239,5 тактов (задаётся битами SMPx[2:0] регистров ADC_SMPRx). Если время выборки установлено в 1,5 такта, то общее время оцифровки канала получается равным 1,5+12,5 = 14 тактов, что при частоте ADC равной 14 МГц составляет по времени как раз минимально возможную 1 мкс.

Физические линии ввода, группы каналов

входные каналы для АЦП в stm32

Физически в stm32 доступно до 18 каналов (линий ввода), которые могут быть подключены к модулям АЦП, — до 16 внешних (соединённых с выводами микроконтроллера) и два внутренних — для температуры и опорного напряжения. Для того, чтобы вывод микроконтроллера мог использоваться в качестве линии ввода для АЦП — нужно в регистрах настройки GPIO сконфигурировать его в режиме аналогового входа (функция GPIO_Init библиотеки StdPeriph, параметр GPIO_Mode). В таблице справа указано, какие выводы можно использовать в качестве входов для АЦП (данные для средней линейки микроконтроллеров stm32, к которым относится, например, популярный чип STM32F103C8T6).

Из 18 физически доступных линий ввода можно для каждого модуля ADC составить две группы каналов (два списка), определяющие с какими линиями ввода и в каком порядке этот модуль будет работать. В обе эти группы могут включаться одни и те же физические линии ввода, более того, один и тот же ввод может включаться в одну и ту же группу несколько раз. Каналы первой группы называются обычными или регулярными, а каналы второй группы — инжектированными. Обычных каналов может быть до 16 штук, а инжектированных — до 4-х.

Главное отличие между группами обычных и инжектированных каналов заключается в том, что для обычной группы существует только один регистр данных, в который помещается результат оцифровки каждого включенного в эту группу канала. Для инжектированной группы под каждый канал выделяется свой регистр данных. Таким образом, результаты оцифровки каналов обычной группы мы должны сразу забирать, пока не оцифровался следующий канал из этой же группы и не затёр предыдущие данные (благо это можно делать автоматически при помощи DMA). Что касается группы инжектированных каналов, то тут мы можем дождаться окончания оцифровки всех каналов и уже потом разбираться с полученными результатами.

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

Список каналов обычной группы задаётся в регистрах ADC_SQRx (функция ADC_RegularChannelConfig библиотеки StdPeriph), а размер группы — битами L[3:0] регистра ADC_SQR1 (функция ADC_Init библиотеки StdPeriph, параметр ADC_NbrOfChannel).

Список каналов инжектированной группы задаётся в регистре ADC_JSQR (функция ADC_InjectedChannelConfig библиотеки StdPeriph). Там же битами JL[1:0] задаётся и размер группы инжектированных каналов (функция ADC_InjectedSequencerLengthConfig библиотеки StdPeriph).

Включение, запуск оцифровки, режимы работы модулей ADC

Помимо того, что модуль ADC нужно затактировать, его ещё нужно включить. Включение модуля ADC производится установкой бита ADON в регистре ADC_CR2. Причём тут есть интересная особенность. Если ADON = 0 и вы записываете в этот бит значение 1, то происходит запуск модуля ADC (пробуждение из режима power-down). Если у Вас уже ADON = 1 и вы снова записываете в этот бит значение 1, то происходит старт оцифровки для группы обычных каналов (бит ADON устанавливается функцией ADC_Cmd библиотеки StdPeriph).

Запустить оцифровку для группы обычных каналов можно также по триггеру. Событие, вызывающее срабатывание триггера настраивается битами EXTSEL[2:0] регистра ADC_CR2 (функция ADC_Init библиотеки StdPeriph, параметр ADC_ExternalTrigConv). Сам режим запуска по триггеру разрешается установкой бита EXTTRIG регистра ADC_CR2 (функция ADC_ExternalTrigConvCmd библиотеки StdPeriph). Одним из вариантов срабатывания триггера является установка бита SWSTART регистра ADC_CR2 (это, так называемый «программный старт»). В библиотеке StdPeriph даже есть специальная функция (ADC_SoftwareStartConvCmd), которая может одновременно включить режим внешнего триггера и устанавить бит SWSTART сразу же вызывая срабатывание этого самого триггера.

Одиночные и непрерывные преобразования

Битом CONT регистра ADC_CR2 можно выбрать режим одиночных или непрерывных преобразований (CONT = 0 — single mode, CONT = 1 — continuous mode). В одиночном режиме после выполнения одного запроса на оцифровку ADC останавливается. В непрерывном режиме после выполнения одного запроса тут же генерируется новый и так до тех пор, пока ADC не будет выключен. С помощью библиотеки StdPeriph состояние бита CONT настраивается функцией ADC_Init (параметр ADC_ContinuousConvMode).

Сканирование списка каналов

Битом SCAN регистра ADC_CR1 можно включить или выключить режим сканирования. Если режим сканирования включен (SCAN = 1), то в результате запуска ADC будут по очереди просканированы и оцифрованы все каналы из обычной или инжектированной группы. В этом режиме нужно обязательно использовать DMA чтобы автоматически забирать результаты оцифровки каналов обычной группы. Если режим сканирования выключен, то будет оцифровываться только один единственный канал, — первый канал списка, указанный в битах SQ1[4:0] регистра ADC_SQR3 для группы обычных каналов, или канал, указанный в битах JSQx[4:0] регистра ADC_JSQR для группы инжектированных каналов (x=4-JL, где JL — размер группы инжектированных каналов, указанный в регистре ADC_JSQR). С помощью библиотеки StdPeriph состояние бита SCAN настраивается функцией ADC_Init (параметр ADC_ScanConvMode).

Использование DMA

Для того, чтобы после оцифровки каждого канала генерировался запрос к DMA — нужно разрешить его использование установкой в 1 бита DMA регистра ADC_CR2 (можно использовать функцию ADC_DMACmd библиотеки StdPeriph). Учтите, что запросы к DMA могут генерировать только ADC1 и ADC3. Забирать с помощью DMA данные от ADC2 можно только в том случае, если модули ADC1,2 работают совместно, в двойном режиме.

В самом контроллере DMA запросы от ADC1 висят на первом канале, который тоже нужно настроить, — указать откуда и куда перекладывать данные, их размер, как управлять счётчиком и так далее. При использовании библиотеки StdPeriph всё это можно сделать функцией DMA_Init.

Опрос инжектированных каналов

Опрос группы инжектированных каналов может запускаться двумя способами. Первый — по внешнему триггеру. Событие, вызывающее срабатывание триггера, в этом случае настраивается битами JEXTSEL[2:0] регистра ADC_CR2 (функция ADC_ExternalTrigInjectedConvConfig библиотеки StdPeriph), а сам режим разрешается установкой бита JEXTTRIG регистра ADC_CR2 (функция ADC_ExternalTrigInjectedConvCmd библиотеки StdPeriph). Для этой группы также есть свой бит программного старта — JSWSTART, а в библиотеке StdPeriph есть своя функция программного запуска оцифровки инжектированных каналов, одновременно устанавливающая биты JEXTTRIG и JSWSTART (фукция ADC_SoftwareStartInjectedConvCmd). Естественно, что для такого программного запуска сначала нужно настроить триггер инжектированной группы на срабатывание от бита JSWSTART.

Если событие, вызывающее срабатывание триггера произошло во время опроса обычной группы каналов, то опрос обычной группы прерывается, выполняется опрос инжектированной группы, после чего возобновляется опрос обычной группы.

Второй вариант запуска оцифровки группы инжектированных каналов — установить бит JAUTO в регистре ADC_CR1. В этом случае оцифровка каналов инжектированной группы будет автоматически запускаться после оцифровки всех каналов обычной группы. С помощью библиотеки StdPeriph состояние бита JAUTO можно задать функцией ADC_AutoInjectedConvCmd.

Прерывистый режим опроса каналов

Ещё одна возможность, предоставляемая модулями ADC, — прерывистый режим (discontinuous mode). Для обычной и инжектированной групп этот режим задаётся отдельно битами DISCEN и JDISCEN соответственно (в библиотеке StdPeriph для этого есть функции ADC_DiscModeCmd и ADC_InjectedDiscModeCmd). Прерывистый режим позволяет разделить каждую группу каналов на подгруппы и по событиям триггера по очереди выполнять оцифровку этих подгрупп. Размер подгруппы для обычной группы задаётся битами DISCNUM[2:0] регистра ADC_CR1 (в библиотеке StdPeriph — функцией ADC_DiscModeChannelCountConfig), для группы инжектированных каналов размер подгруппы равен 1 (то есть просто все каналы перебираются по очереди). Чтобы было понятнее как это устроено — приведу примеры из официальной доки:

  • 1. Оцифровка обычных каналов. Пусть у нас в регистрах ADC_SQRx прописаны каналы 0, 1, 2, 3, 6, 7, 9, 10, длина списка обычных каналов установлена равной 8, выбран прерывистый режим (discontinuous mode), а размер подгуппы установлен равным 3. В этом случае каждое срабатывание триггера группы обычных каналов будет запускать оцифровку очередной подгруппы:
    • 1 срабатывание триггера: оцифровываются каналы 0, 1, 2. После оцифровки каждого канала устанавливается бит EOC.
    • 2 срабатывание триггера: оцифровываются каналы 3, 6, 7. После оцифровки каждого канала устанавливается бит EOC.
    • 3 срабатывание триггера: оцифровываются каналы 9, 10 (в этой подгруппе всего 2 канала, поскольку на этом заканчивается общий список). После оцифровки каждого канала устанавливается бит EOC.
    • 4 срабатывание триггера: оцифровка списа каналов начинается сначала, снова оцифровываются каналы 0, 1, 2. После оцифровки каждого канала устанавливается бит EOC.
  • 2. Оцифровка инжектированных каналов. Пусть у нас в регистре ADC_JSQR прописаны каналы 1, 2, 3, длина списка инжектированных каналов установлена равной 3 и выбран прерывистый режим (discontinuous mode). В этом случае каждое срабатывание триггера для группы инжектированных каналов будет запускать оцифровку очередной подгруппы:
    • 1 срабатывание триггера: оцифровывается канал 1.
    • 2 срабатывание триггера: оцифровывается канал 2.
    • 3 срабатывание триггера: оцифровывается канал 3. После оцифровки канала устанавливаются биты EOC и JEOC.
    • 4 срабатывание триггера: оцифровка списа каналов начинается сначала, снова оцифровывается канал 1.

Режим калибровки

Все модули ADC имеют режим автоматической самокалибровки, которую рекомендуется проводить каждый раз после включения питания. Делается это очень просто:

  • Сначала нужно очистить регистры калибровки, установив в единицу бит RSTCAL регистра ADC_CR2 (в библиотеке StdPeriph для этого есть функция ADC_ResetCalibration). После окончаниия процедуры сброса бит RSTCAL аппаратно сбросится в ноль.
  • Далее нужно запустить самокалибровку, установив в единицу бит CAL в регистре ADC_CR2 (в библиотеке StdPeriph для этого есть функция ADC_StartCalibration). После окончаниия процедуры калибровки бит CAL аппаратно сбросится в ноль.

Процедуру калибровки нельзя запускать ранее чем через два такта после включения модуля ADC (имеется в виду два такта ADC). После окончания калибровки в регистре ADC_DR сохраняется калибровочный код (калибровочное значение).

Предельный компаратор

В каждом модуле ADC есть такая штука как предельный компаратор (analog watchdog). Он может отслеживать, не выходит ли результат оцифровки за установленные границы. Если результат получился больше верхней границы или меньше нижней границы, то компаратор автоматически устанавливает бит AWD в регистре ADC_SR. Границы компаратора задаются в специальных регистрах — ADC_HTR и ADC_LTR (в библиотеке StdPeriph для этого есть функция ADC_AnalogWatchdogThresholdsConfig). Для компаратора не важно как настроено выравнивание результата оцифровки, он в любом случае использует для сравнения только 12 значащих бит.

Предельный компаратор можно настроить для отслеживания результата преобразования одного или сразу группы каналов. Режим работы определяется битами AWDSGL, AWDEN и JAWDEN (в StdPeriph для этого есть функция ADC_AnalogWatchdogCmd). Ниже приведена таблица различных режимов.

режимы работы предельного компаратора в stm32

Двойной режим подробно описывать не буду, и так статья получается очень длинной. Лучше как-нибудь потом отдельно про это напишу, тем более там тоже много всяких подрежимов. Скажу только, что в двойном режиме можно различными способами синхронизировать работу сразу двух АЦП для ускорения оцифровки большого количества каналов.

Регистры

ADC_SR — регистр статуса. Младшие 5 бит этого регистра позволяют отслеживать состояние и различные события модуля ADC. Все эти биты устанавливаются аппаратно, а сбрасываются программно, записью в них нуля.

регистр ADC_SR

Нажми, чтобы узнать значения битов

  • AWD: аппаратно устанавливается в 1 при выходе оцифрованного значения за установленные пределы. Сбрасывается программно, записью нуля. По этому событию может генерироваться прерывание.
  • EOC: аппаратно устанавливается в 1 при завершении оцифровки очередного канала. Сбрасывается программно, записью нуля, или аппаратно при чтении регистра ADC_DR. По этому событию может генерироваться прерывание.
  • JEOC: аппаратно устанавливается в 1 при завершении оцифровки всех каналов инжектированной группы. Сбрасывается программно, записью нуля.
  • JSTRT: устанавливается аппаратно когда начинает оцифровываться группа инжектированных каналов. Сбрасывается программно, записью нуля.
  • STRT: аппаратно устанавливается в 1 при старте оцифровки любого канала обычной группы. Сбрасывается программно, записью нуля.

[свернуть]

ADC_CR1 — регистр настройки.

регистр ADC_CR1

Нажми, чтобы узнать значения битов

  • AWDCH[4:0]: выбор канала для отслеживания предельным компаратором в случае, когда для него установлен режим работы с одним каналом
  • EOCIE: разрешение (1)/запрет (0) прерывания по событию EOC
  • AWDIE: разрешение (1)/запрет (0) прерывания по предельному компаратору
  • JEOCIE: разрешение (1)/запрет (0) прерывания по событию JEOC для группы инжектированных каналов
  • SCAN: включение (1)/выключение (0) режима сканирования
  • AWDSGL: управление режимом компаратора (1 — один канал, 0 — все каналы)
  • JAUTO: включение (1)/выключение (0) автостарта оцифровки каналов инжектированной группы после обычной
  • DISCEN: включение (1)/выключение (0) прерывистого режима опроса каналов обычной группы
  • JDISCEN: включение/выключение прерывистого режима опроса каналов инжектированной группы (0 — выклечено 1 — включен)
  • DISCNUM[2:0]: эти биты определяют размер подгруппы обычных каналов для прерывистого режима
  • DUALMOD[3:0]: здесь задаётся подрежим для двойного режима работы АЦП
  • JAWDEN: включение (1)/выключение (0) использования компаратора для инжектированных каналов
  • AWDEN: включение (1)/выключение (0) использования компаратора для обычных каналов

[свернуть]

ADC_CR2 — регистр настройки.

регистр ADC_CR2

Нажми, чтобы узнать значения битов

  • ADON: включение (1)/выключение (0) ADC + запуск оцифровки (если уже установлена единица и снова записать 1)
  • CONT: переключение между одиночным (0) и непрерывным (1) режимами
  • CAL: бит запуска калибровки (сбрасывается аппаратно после завершения процедуры калибровки)
  • RSTCAL: инициализация (очистка) регистров калибровки (сбрасывается аппаратно после выполнения очистки)
  • DMA: включение (1)/выключение (0) режима использования DMA
  • ALIGN: выравнивание результата оцифровки. 0 — выравнивание вправо (к младшему биту младших 16 бит регистра данных), 1 — выравнивание влево (к старшему биту младших 16 бит регистра данных)
  • JEXTSEL[2:0]: выбор внешнего события для срабатывания триггера запуска оцифровки инжектированных каналов. Для ADC1,2:
    • 000: Timer1 TRGO
    • 001: Timer1 CC4
    • 010: Timer2 TRGO
    • 011: Timer2 CC1
    • 100: Timer3 CC4
    • 101: Timer4 TRGO
    • 110: EXTI line15/TIM8_CC4
    • 111: бит JSWSTART регистра ADC_CR2
  • JEXTTRIG: разрешение (1)/запрет (0) запуска оцифровки инжектированных каналов по триггеру
  • EXTSEL[2:0]: выбор внешнего события для срабатывания триггера запуска оцифровки обычных каналов. Для ADC1,2:
    • 000: Timer1 CC1
    • 001: Timer1 CC2
    • 010: Timer1 CC3
    • 011: Timer2 CC2
    • 100: Timer3 TRGO
    • 101: Timer4 CC4
    • 110: EXTI line11/TIM8_TRGO
    • 111: бит SWSTART регистра ADC_CR2
  • EXTTRIG: разрешение (1)/запрет (0) запуска оцифровки обычных каналов по триггеру
  • JSWSTART: бит программного запуска оцифровки инжектированных каналов (должен быть выбран в качестве события для триггера + должен быть разрешён запуск по триггеру)
  • SWSTART: бит программного запуска оцифровки обычных каналов (должен быть выбран в качестве события для триггера + должен быть разрешён запуск по триггеру)
  • TSVREFE: включение (1)/выключение (0) внутренних каналов для измерения температуры и опорного напряжения

[свернуть]

ADC_SMPR1, ADC_SMPR2 — регистры настройки времени сэмплирования для каждой линии ввода.

регистр ADC_SMPR1
регистр ADC_SMPR2

Нажми, чтобы узнать значения битов

  • SMPx[2:0]: выбор времени сэмплирования для канала x
    • 000: 1,5 такта АЦП
    • 001: 7,5 такта АЦП
    • 010: 13,5 тактов АЦП
    • 011: 28,5 тактов АЦП
    • 100: 41,5 такт АЦП
    • 101: 55,5 тактов АЦП
    • 110: 71,5 такт АЦП
    • 111: 239,5 тактов АЦП
  • Примечание. Для ADC2 каналы 16, 17, а для ADC3 каналы 9, 14, 15, 16, 17 внутренне соединены с Vss.

[свернуть]

ADC_JOFRx (x=1..4) — регистры смещения для инжектированных каналов. Записанные в эти регистры значения будут вычтены из сырых результатов оцифровки перед записью результатов в регистры данных.

регистры ADC_JOFRx

Нажми, чтобы узнать значения битов

  • JOFFSETx[11:0]: смещение для канала x

[свернуть]

ADC_HTR — регистр верхней уставки предельного компаратора.

регистр ADC_HTR

Нажми, чтобы узнать значения битов

  • HT[11:0]: верхняя уставка предельного компаратора. По умолчанию (после ресета) значение ADC_HTR = 0x00000FFF

[свернуть]

ADC_LTR — регистр нижней уставки предельного компаратора.

регистр ADC_LTR

Нажми, чтобы узнать значения битов

  • LT[11:0]: нижняя уставка предельного компаратора

[свернуть]

ADC_SQRx — регистры задания списка каналов для обычной группы, а также определения размера этой группы.

регистр ADC_SQR1
регистр ADC_SQR2
регистр ADC_SQR3

Нажми, чтобы узнать значения битов

  • L[3:0]: размер списка каналов обычной группы
  • SQx[4:0]: здесь прописывается номер линии ввода, которую нужно подключить к каналу x группы обычных каналов

[свернуть]

ADC_JSQR — регистр задания списка каналов для инжектированной группы, а также определения размера этой группы.

регистр ADC_JSQR

Нажми, чтобы узнать значения битов

  • JL[3:0]: размер списка каналов инжектированной группы
  • JSQx[4:0]: здесь прописывается номер линии ввода, которую нужно подключить к каналу x группы инжектированных каналов
  • Примечание: в отличии от обычных каналов здесь список начинает опрашиваться не с первого канала, а с канала 4-JL.

[свернуть]

ADC_JDRx — регистры данных каналов инжектированной группы. Младшие 16 бит каждого регистра содержат выровненные к старшему или младшему биту результаты оцифровки. Поскольку для инжектированной группы каналов результаты вычисляются вычитанием смещения из сырых значений, то они могут быть как положительными, так и отрицательными. В связи с этим, для каждого канала этой группы результат занимает 13 бит (старший бит результата отводится под знак).

ADC_DR — регистр данных каналов обычной группы. Младшие 16 бит этого регистра содержат выровненные к старшему или младшему биту результаты оцифровки обычного канала обработанного последним. Результат беззнаковый и занимает 12 бит выровненных влево или вправо. Если включен двойной режим, то старшие 16 бит регистра данных ADC1 содержат результат работы ADC2.

Пример

В качестве примера рассмотрим программу, которая будет непрерывно оцифровывать шестой канал ADC (подключен к пину PA06) и складывать результаты в кольцевой буфер, размером 512 байт (256 полуслов по 16 бит).

Каждые 10 мс, отсчитанные по системному таймеру, наша программа будет выплёвывать первые два байта этого буфера (то есть данные одного из циклов оцифровки) в порт USART1.

Текст программы для stm32

#include "stm32f10x.h" /* стандартные хидеры */
 
/* сначала всякие переменные (ну а как вы хотели с языками высокого уровня) */
GPIO_InitTypeDef PORT_Structure;	/* структура для настройки линий порта */
EXTI_InitTypeDef EXTI_Structure;	/* структура для настройки внешних прерываний (EXTI) */
USART_InitTypeDef USART_Structure;	/* структура для инициализации USART */
NVIC_InitTypeDef NVIC_Structure;	/* структура для настройки NVIC */
DMA_InitTypeDef DMA_Structure;		/* структура для настройки DMA */
ADC_InitTypeDef ADC_Structure;		/* структура для настройки ADC */
 
ErrorStatus HSEStartUpStatus;		/* переменная для определения ошибки запуска генератора */
uint16_t DataBuf[256];			/* буфер для сохранения ADC (сохраняем значения в кольцевой буфер) */
 
#define TimerTick 56000000/(8*100)-1 	/* чтобы получить 100 Герц с делителем на 8 */
 
/* Прототипы функций */
void RCC_Configuration(void);		/* функция настройки RCC */
void GPIO_Configuration(void);		/* функция настройки GPIO */
void DMA_Configuration(void);		/* функция настройки DMA */
void USART_Configuration(void);		/* функция настройки USART */
void ADC_Configuration(void);		/* функция настройки ADC */
void SYSTICK_Configuration(void);	/* функция настройки SYSTICK */
 
/* Основная программа */
int main(void) {
 
	RCC_Configuration();		/* настраиваем RCC */
	GPIO_Configuration();		/* настраиваем GPIO */
	DMA_Configuration();		/* настраиваем DMA */
	USART_Configuration();		/* настраиваем USART */
	ADC_Configuration();		/* настраиваем ADC */
	SYSTICK_Configuration();	/* настраиваем SysTick */
 
Work:
	goto Work;
}
 
/* Перенастройка системы тактирования */
void RCC_Configuration(void)
{	RCC_DeInit();	/* выключаем всё, что было настроено по умолчанию */
 
	RCC_HSEConfig(RCC_HSE_ON);			/* включаем внешний генератор */
	HSEStartUpStatus = RCC_WaitForHSEStartUp();	/* ждём пока он стабилизируется */
 
	if(HSEStartUpStatus == SUCCESS)
	{	FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);	/* Enable Prefetch Buffer */
		FLASH_SetLatency(FLASH_Latency_2);			/* Flash 2 wait state */
		RCC_HCLKConfig(RCC_SYSCLK_Div1);			/* HCLK = SYSCLK (56 MHz for DMA) */
		RCC_PCLK2Config(RCC_HCLK_Div1);				/* PCLK2 = HCLK (56 MHz for SPI1, USART1, TIM1, EXTI...) */
		RCC_PCLK1Config(RCC_HCLK_Div2);				/* PCLK1 = HCLK/2 (28 MHz for DAC, PWR, CAN, I2C, USART2-5...) */
		RCC_ADCCLKConfig(RCC_PCLK2_Div4);			/* ADCCLK = PCLK2/4 (14 MHz for ADC) */
		RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_7);	/* PLLCLK = 8MHz * 7 = 56 MHz */
		RCC_PLLCmd(ENABLE);					/* Enable PLL */
 
		/* Wait till PLL is ready */
		while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
		{	}
		RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);		/* Select PLL as system clock source */
 
		/* Wait till PLL is used as system clock source */
		while(RCC_GetSYSCLKSource() != 0x08)
		{	}
	}
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);	/* включаем тактирование DMA (56 MHz) */
	/* включаем тактирование USART1, GPIOA */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO | RCC_APB2Periph_ADC1, ENABLE);
}
 
/* Настройка GPIO */
void GPIO_Configuration(void)
{	/* Configure USART1 Tx (PA.09) as alternate function push-pull */
	PORT_Structure.GPIO_Pin = GPIO_Pin_9;			/* пин 9, сюда подключена линия Tx модуля USART */
	PORT_Structure.GPIO_Mode = GPIO_Mode_AF_PP;		/* альтернативная функция push-pull */
	PORT_Structure.GPIO_Speed = GPIO_Speed_50MHz;		/* максимальная скорость 50 МГц */
	GPIO_Init(GPIOA, &PORT_Structure);
 
	/* Configure USART1 Rx (PA.10) as input floating */
	PORT_Structure.GPIO_Pin = GPIO_Pin_10;			/* пин 10, сюда подключена линия Rx модуля USART */
	PORT_Structure.GPIO_Mode = GPIO_Mode_IN_FLOATING;	/* аналоговый вход */
	/* максимальная скорость не меняется, так что заново этот элемент структуры можно не записывать */
	GPIO_Init(GPIOA, &PORT_Structure);
 
	/* Configure PA.06 (ADC Channel6) as analog input */
	PORT_Structure.GPIO_Pin = GPIO_Pin_6;			/* пин 6, сюда подключен 6-й канал ADC1 */
	PORT_Structure.GPIO_Mode = GPIO_Mode_AIN;		/* аналоговый вход */
	/* максимальная скорость не меняется, так что заново этот элемент структуры можно не записывать */
	GPIO_Init(GPIOA, &PORT_Structure);			/* выполняем настройку порта GPIOA*/
}
 
void USART_Configuration(void)
{	/* инициализируем USART */
	USART_Structure.USART_BaudRate = 115200;					/* BaudRate = 115200 */
	USART_Structure.USART_WordLength = USART_WordLength_8b;				/* Word Length = 8 Bits */
	USART_Structure.USART_StopBits = USART_StopBits_1;				/* One Stop Bit */
	USART_Structure.USART_Parity = USART_Parity_No;					/* No parity */
	USART_Structure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	/* Hardware flow control disabled */
	USART_Structure.USART_Mode = USART_Mode_Tx;					/* Receive disabled and transmit enabled */
	USART_Init(USART1, &USART_Structure);
 
	USART_Cmd(USART1, ENABLE);			/* включаем USART */
	USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);	/* включаем режим DMA для USART */
}
 
void ADC_Configuration(void)
{	uint32_t	delay_cycles;			// количество циклов задержки перед калибровкой ADC
	/* инициализируем ADC1 */
	ADC_Structure.ADC_Mode = ADC_Mode_Independent;
	ADC_Structure.ADC_ScanConvMode = ENABLE;
	ADC_Structure.ADC_ContinuousConvMode = ENABLE;
	ADC_Structure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
	ADC_Structure.ADC_DataAlign = ADC_DataAlign_Right;
	ADC_Structure.ADC_NbrOfChannel = 1;
	ADC_Init(ADC1, &ADC_Structure);
 
	ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 1, ADC_SampleTime_13Cycles5);	/* настраиваем 6-й канал ADC1 (подключен к PA06) */
	ADC_DMACmd(ADC1, ENABLE);							/* включаем режим DMA для ADC1 */
	ADC_Cmd(ADC1, ENABLE);								/* включаем ADC1 */
 
	/* перед началом калибровки ADC нужно выждать 2 такта ADC */
	delay_cycles =  1 * 1 * 4;		/* находим произведение делителей для HCLK, PCLK2 и ADCCLK */
	delay_cycles *= 2;			/* умножаем на 2, чтобы узнать сколько тактов CPU занимают 2 такта ADC */
	while(delay_cycles)
	{	delay_cycles--;			/* уменьшаем счётчик */
	}
 
	/* начинаем внутреннюю калибровку АЦП */
	ADC_ResetCalibration(ADC1);				/* сброс калибровки */
	while(ADC_GetResetCalibrationStatus(ADC1) == SET)	/* просто тупить, пока не завершится сброс калибровки */
	{
	}
	ADC_StartCalibration(ADC1);				/* старт калибровки */
	while(ADC_GetCalibrationStatus(ADC1) == SET)		/* просто тупить, пока не завершится калибровка */
	{
	}
 
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);			/* Start ADC1 Software Conversion */
}
 
void DMA_Configuration(void)
{	/* инициализируем четвёртый канал DMA1 (к этому каналу подключен USART1) */
	DMA_Structure.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->DR);
	DMA_Structure.DMA_MemoryBaseAddr = (uint32_t)&DataBuf;
	DMA_Structure.DMA_DIR = DMA_DIR_PeripheralDST;		/* направление из памяти в периферию */
	DMA_Structure.DMA_BufferSize = 2;
	DMA_Structure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_Structure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_Structure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	DMA_Structure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
	DMA_Structure.DMA_Mode = DMA_Mode_Normal;		/* после отправки всех данных нужно взводить счётчик вручную */
	DMA_Structure.DMA_Priority = DMA_Priority_Low;
	DMA_Structure.DMA_M2M = DMA_M2M_Disable;
	DMA_Init(DMA1_Channel4, &DMA_Structure);
 
	/* инициализируем первый канал DMA1 (к этому каналу подключен ADC1) */
	DMA_Structure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR);
	DMA_Structure.DMA_MemoryBaseAddr = (uint32_t)&DataBuf;
	DMA_Structure.DMA_DIR = DMA_DIR_PeripheralSRC;				/* направление из периферии в память */
	DMA_Structure.DMA_BufferSize = 256;
	DMA_Structure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_Structure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_Structure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	DMA_Structure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	DMA_Structure.DMA_Mode = DMA_Mode_Circular;				/* будет писать в буфер по кругу */
	DMA_Structure.DMA_Priority = DMA_Priority_VeryHigh;
	DMA_Structure.DMA_M2M = DMA_M2M_Disable;
	DMA_Init(DMA1_Channel1, &DMA_Structure);
 
	DMA_Cmd(DMA1_Channel1, ENABLE);						/* включаем первый канал DMA1 */
}
 
void SYSTICK_Configuration(void)
{	/* настраиваем системный таймер */
	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);		/* частота системного таймера = 56/8 = 7 МГц */
	SysTick->LOAD = TimerTick;					/* значение для перезагрузки таймера */
	SysTick->VAL = TimerTick;					/* текущее значение */
	SysTick->CTRL |= SysTick_CTRL_TICKINT | SysTick_CTRL_ENABLE;	/* включаем прерывание и таймер */
}
 
void SysTick_Handler(void)
{	/* прерывание от таймера */
	DMA_Cmd(DMA1_Channel4, DISABLE);			/* выключаем четвёртый канал DMA1 */
	DMA1_Channel4->CNDTR = 2;				/* взводим счётчик */
	DMA_Cmd(DMA1_Channel4, ENABLE);				/* включаем четвёртый канал DMA1 */
}

[свернуть]

Ну и, для полноты картины, программа для ПК, позволяющая принимать посылаемые через USART данные АЦП, преобразовывать их в напряжение в Вольтах и показывать это преобразованное значение на экране монитора:

  1. Часть 1. Установка MDK, создание проекта, основы Keil uVision
  2. Часть 2. Команды и директивы ассемблера, структура и синтаксис программы. Первая программа для STM32
  3. Часть 3. Карта памяти контроллеров STM32, методы работы с памятью
  4. Часть 4. Регистры, старт и режимы работы контроллеров STM32
  5. Часть 5. Как залить прошивку в контроллер
  6. Часть 6. Настройка системы тактирования
  7. Часть 7. Работа с портами ввода-вывода
  8. Часть 8. Процедуры на ассемблере для STM32
  9. Часть 9. Система прерываний
  10. Часть 10. CMSIS, использование стандартных библиотек и функций
  11. Часть 11. Подключение и использование драйверов из пакета StdPeriph
  12. Часть 12. Работа с модулями USART и UART.
  13. Часть 13. Работа с модулями ADC
  14. Часть 14. Использование DMA
  15. Часть 15. Таймеры. Глава 1 — Введение. Простейшие таймеры
  16. Часть 15. Таймеры. Глава 2 — Таймеры общего назначения TIM9 — TIM14
  17. Часть 15. Таймеры. Глава 3 — Таймеры общего назначения TIM2 — TIM5
  18. Часть 15. Таймеры. Глава 4 — Продвинутые таймеры TIM1, TIM8
  19. Часть 16. Создание устройства USB HID в Keil uVision при помощи библиотечного компонента USB
  20. Приложение 1. Набор инструкций THUMB-2 и особенности их использования
  21. Приложение 2. Таблица векторов прерываний для семейств STM32F101, STM32F102, STM32F103
  22. Приложение 3. Драйвера и функции библиотеки StdPeriph

Комментарии 5

  • Есть небольшая неточность при калибровке АЦП. После включения надо выждать два такта работы АЦП, а не процессора.

    • Добавил пояснение.

      • Я имел ввиду эти строки в программе:
        /* внутренняя калибровка АЦП */
        __NOP(); /* после включения, перед калибровкой */
        __NOP(); /* нужны минимум два пустых такта проца */

        • Хм, точно. Спасибо. Заменил нопы на вот такой кусок:
          /* перед началом калибровки ADC нужно выждать 2 такта ADC */
          delay_cycles = 1 * 1 * 4; /* находим произведение делителей для HCLK, PCLK2 и ADCCLK */
          delay_cycles *= 2; /* умножаем на 2, чтобы узнать сколько тактов CPU занимают 2 такта ADC */
          while(delay_cycles)
          { delay_cycles—; /* уменьшаем счётчик */
          }

          Понятно, что на 2 можно и не умножать, — в каждом цикле вычитание + проверка — это и так минимум 2 такта получается. Да ещё время на вычисление задержки… Но так нагляднее.

          Есть другие варианты как можно сделать задержку?

  • Я только начал заниматься этим контроллером. Пишу для него программу на ассемблере для своего самодельного токарного. На ардуино нано у меня уже это дело работает, но он несколько медленный для всех моих задумок. Ваш цикл уроков очень мне помог в освоении stm32. Постоянно обращаюсь к набору инструкций THUMB-2, очень удобно оформлен :).

    Насчет задержки для АЦП — думаю ваш вариант универсален для любых установленных делителей. У меня вопрос решен в лоб — обычная задержка.

    Кстати, думаю у вас есть неточность в описании прерываний. Цитирую: «Cохранение контекста происходит только при переходе к обработчику исключения из режима потока (то есть если это не вложенное исключение внутри другого обработчика).» Как я понял из описания (Ядро Cortex-M3 компании ARM. Полное руководство), если происходит исключение с более высоким приоритетом в МОМЕНТ СОХРАНЕНИЯ КОНТЕКСТА текущего прерывания, то только тогда второй раз контекст уже не сохраняется для увеличения быстродействия.
    С уважением.

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