АЦП в STM32. Часть 1

Микроконтроллер штука цифровая и обменивается с внешним миром цифоровыми сигналами: нулями и единицами. Однако иногда перед микроконтроллером встает задача произвести измерение какой либо плавно изменяющейся величины. Это может быть всё то, что принимает несколько промежуточных состояний (а не только два) например это может быть темепература, напряжение, сила тока, освещенность и так далее, примеров много. Однако, нога контроллера настроенная на вход различает только два состояния - присутствие на ноге напряжения (лог. 1) и его отсутствие (лог. 0). Для измерения  температуры это малопригодно, ведь мало кому интересен градусник имеющий только два состояния -50 и +200 градусов :) Для решения проблем измерения аналоговых величин придумали АЦП – Аналого-цифровой преобразователь. Принцип работы с АЦП в двух словах: На вход АЦП поступает аналоговый сигнал и через некоторое время из АЦП можно прочитать результат преобразования, тоесть цифровое представление аналогово сигнала. Существуют микроконтроллеры STM32 со встроенным АЦП, то есть ничего подключать к контроллеру не надо, точнее почти ничего. Посмотрим на картинку: 

 

STM32 ADC

Тут нарисована обвязка которая должна быть обязательно, если мы хотим использовать АЦП. К ней относятся два конденсатора C4 и С3 плюс дроссель L1. Все это добро предназначено для того чтоб обеспечить АЦП контроллера качественным питанием без каких либо помех. Располагать конденсаторы желательно как можно ближе к выводу AVCC. На всякий случай уточню, что питается модуль АЦП отдельно от всего остального контроллера. На плате STM32VL Discovery уже вся эта обвязка имеется, что очень удобно для нас. Остальные детальки будут использованы нами в следующей статье, где мы будем применять АЦП на практике.  Забегая вперед скажу, что в STM32 с АЦП всё не так просто как например в AVR. Имеется очень много параметров, о значении некоторых мне пока  мало что понятно. Но попробуем разобраться что к чему. Начать следует с того что контроллер может измерять напряжения только с определённых ножек в названии которых присутствует слово ADC1_INx, где х некоторый уникальный номер канала. АЦП контроллера может опрашивать каждый из каналов поочередно. И вот тут начинается то, что взорвало мне мозг в клочья поначалу. В STM32 есть два варианта чтения данных с ножек ADC1_INx. Первый называется "Регулярные каналы" (regular channels в даташите). Использование этого метода опроса состоит в том, что АЦП опрашивает по очереди некоторый заранее настроенный список каналов, после каждого опроса результат записывается в один и тот же регистр. Это означает, что нужно своевременно забирать результат преобразования из этого регистра, в противном случае результат будет перетираться. Совсем иначе дело обстоит с "Инжектированными каналами" (Injected channels). В случае использования этого метода опроса можно записывать результат измерения каждого канала АЦП в свой отдельный регистр ничего не перезатирая. Но к сожалению таких регистров всего 4 а ножек АЦП у контроллера 16 штук. Но лично мне еще никогда не доводилось использоваь более двух каналов АЦП, так что использование инжектированных каналов мне пришлось больше по душе :) Еще стоит отметить, что АЦП STM32 обладает приятной штуковиной под названием Analog watchdog. Его предназначение в том, чтоб подать сигнал в случае если напряжние на определённом канале выйдет за допустимый диапазон, это позволяет сэкономить процессорное время за счёт того, что нам не придётся программно заставлять АЦП производить измерение и потом сравнивать полученное значение с пороговыми. Удобная вещь, но пока не использовал. Следующая плюшка - встроенный термометр. В даташите пишут, что использовать его для измерения абсолютных температур лучше не стоит, а вот для того чтоб отследить изменение температуры оно вполне годно. Начнем наше знакомство с АЦП  как обычно с краткого описания регистров, тех назначение которых я более или менее понял. В первую очередь стоит сказать о регистрах в которые записывается результат измерения АЦП, таковых у нас пять. Первый регистр это ADC_DR:

ADC DR

В него последовательно пишутся данные, полученные в результате опроса каналов состоящих в регулярной группе. Каждое последующее измерение одного канала перетирает предыдущее. Для того чтоб своевременно забирать данные и записывать их в определённые переменные можно и нужно использовать прерывания или еще лучше DMA, но я пока с этим не заморачивался и DMA с прерываниями не трогал. О них обязательно пойдет речь в следующих статьях на этом сайте. Таким образом, если мы не используем ни прерываний ни DMA то использовать регулярные группы можно только если канал в этой группе всего один (так у нас не будет перетираться регистр DR данными с разных каналов). Что касается самого регистра, то видно, что он 16-ти битный. А АЦП у нас 12-ти битный. Для удобства данные могут выравниваться как по левому краю регистра так и по правому. Это решается битом ALIGN регистра ADC_CR2. Мы еще вернемся к нему позже. В следующие четыре регистра ADC_JDRx записываются результаты преобразования для каждого из 4-х инжектированных каналов.

ADC JDRx

Ни каких других особенностей этот регистр не имеет. Другой не маловажный регистр который нужно рассмотреть это ADC_SR, он отражает состояние АЦП на данный момент: 

ADC SR

STRT - этот бит устанавливается когда стартует преобразование для регулярного канала
JSTRT - тоже самое но для инжектированного канала
JEOC - закончилось преобразование для инжектированного канала
EOC - закончилось преобразование инжектированного или регулярного канала
AWD - Analog watchdog сработал
 
Все биты сбрасываются программным путём, а EOC может быть сброшен еще и автоматически когда из регистра ADC_DR заберут данные. Для настройки основных АЦП используют два регистра ADC_CR1 и ADC_CR2. Структура первого из них выглядит следующим образом: 

ADC_CR1

AWDENAnalog watchdog мониторит состояние всех каналов регулярной группы
JAWDEN Analog watchdog мониторит состояние всех каналов инжектированной группы
AWDSGL - Analog watchdog мониторит состояние одного канала указанного в битах AWDCH
AWDCH[4:0] - Номер канала АЦП который будет мониторить Analog watchdog

Как правильно выставлять все эти биты, проясняет следующая таблица: 

analog watchdog

JAUTO - разрешает автоматическое преобразование для каналов в инжектированной группе после каналов  регулярной группы.
SCAN - не совсем понятно что такое, судя по всему нужно ставить этот бит если опрашиваем несколько каналов ацп за раз. 
JEOCIE -  Включает или выключает прерывания для инжектированных каналов
AWDIEВключает или выключает прерывания от Analog watchdog
EOCIE - Включает или выключает прерывания по окончанию преобразования в регулярной или инжектированной группе
 
Следующий регистр настроек это ADC_CR2
ADC CR2

TSVREFE - включает температурный сенсор. До установки этого бита читать что-то с канала к которому он подключен - бесполезно.

SWSTART - запускает преобразование для каналов входящих в состав регулярной группы. Для того, чтоб преобразование было запущено этим битом нужно предварительно установить биты EXTSEL[2:0] в единицы.

JSWSTART - запускает преобразование для каналов входящих в состав инжектированной группы. Для того, чтоб преобразование было запущено этим битом нужно предварительно установить биты JEXTSEL[2:0] в единицы.

EXTTRIG - Разрешает использовать внешнее событие для старта преобразования каналов в регулярной группе

EXTSEL[2:0] - Этими битами выбирается источник который будет запускать преобразование каналов в регулярной группе. Доступны следующие битовые комбинации:  

EXTSEL2

EXTSEL1

EXTSEL0

Источник

0

0

0

Timer 1 CC1 event

0

0

1

Timer 1 CC2 event

0

1

0

Timer 1 CC3 event

0

1

1

Timer 2 CC2 event

1

0

0

Timer 3 TRGO event

1

0

1

Timer 4 CC4 event

1

1

0

EXTI line 11

1

1

1

SWSTART

JEXTTRIG - Разрешает использовать внешнее событие для старта преобразования каналов в инжектированой группе

JEXTSEL[2:0] - Этими битами выбирается источник который будет запускать преобразование каналов в инжектированой группе. Доступны следующие битовые комбинации: 

JEXTSEL2

JEXTSEL1

JEXTSEL0

Источник

0

0

0

Timer 1 TRGO event

0

0

1

Timer 1 CC4 event

0

1

0

Timer 2 TRGO event

0

1

1

Timer 2 CC1 event

1

0

0

Timer 3 CC4 event

1

0

1

Timer 4 TRGO event

1

1

0

EXTI line15

1

1

1

JSWSTART

 
 
ALIGN - Устанавливает выравнивание результата в регистре данных (0 - по правому, 1 - по левому)
RSTCAL - Сбрасывает значение калибровки
CAL - Запускает калибровку АЦП. После завершения калибровки бит сбраывается в ноль
CONT - Разрешает непрерывное преобразование
ADON - Включает/выключает модуль АЦП
DMA - Разрешает использовать DMA
 
Самое страшное позади :) , теперь рассмотрим регистры ADC_SMPR1 и ADC_SMPR2. В них задается время выборки индивидуально для каждого канала. 
 
ADC SMPR1
 ADC SMPR2
 
Четыре регистра ADC_JOFRx хранят значения которые будут вычитаться из соответствующих регистров ADC_JDRx. Для чего нужна такая фича, я пока не очень догадываюсь. Но как говорится если звёзды зажигают значит это кому-нибудь нужно.
ADC JOFRx
Регистры ADC_HTR и ADC_LTR задают верхнюю и нижню границу с которыми Analog watchdog сравнивает значение выбранного канала АЦП. Рисовать табличку тут смысла особого нет, так как в регистр просто записываются данные в первые 12 бит. Следующие три регистра ADC_SQRx определяют, какие каналы входят в регулярную группу. Всего можно добавить не более 16-ти каналов. Битами L[3:0] нужно задать число каналов в группе (0000 - 1 канал ... 1111 - 16 каналов). На всякий случай нарисую все три регистра тут, чтоб в даташит лишний раз не лазить: 

ADC SQR1
ADC SQR2
ADC SQR3

И последний регистр на сегодня это ADC_JSQR. Он уже успел принести мне немало хлопот пока я не прочитал то что про него написано мелким  шрифтом в примечании. Для начала нужно сказать, что он хранит в себе номера каналов которые входят с состав инжектированной группы. Сам регистр выгядит так: 
ADC JSQR
Битами JSQx задаются номера каналов, а в JL[1:0] записывается количество каналов в инжектированной группе. Вот тут-то и начинаются сложности. Если в инжектированную группу добавлены все четыре канала то путаницы ни какой нет: В регистр ADC_JDR1 будут записывать данные с канала указанного битами JSQ1, в ADC_JDR2 с канала JSQ2 итд. Но все меняется если мы хотим включить в состав инжектированной группы не все 4 канала, а например только один. Было бы логично предположить, что если канал один то выбирать его нужно битами JSQ1, если каналов 2 то битами JSQ1 и JSQ2 и так далее. Но это не так. В даташите написанно примерно тоже самое что я изобразил в виде таблицы:
 

Кол-во каналов

ADC_JDR1

ADC_JDR2

ADC_JDR3

ADC_JDR4

4

JSQ1

JSQ2

JSQ3

JSQ4

3

JSQ2

JSQ3

JSQ4

2

JSQ3

JSQ4

1

JSQ4


Из таблицы видно, что если мы включаем в группу только один канал то его мы выбираем битами JSQ4 а читаем из регистра ADC_JDR1. Вот такие дела. Ну на сегодня хватит таблиц и какой либо информации вообще. В следующей статье будет практика применения. Спасибо что дочитали до конца :)