Аналого-цифровой преобразователь

Аналого-цифровой преобразователь

На этом уроке мы познакомимся с аналого-цифровым преобразователем и попробуем реализовать измеритель температуры на базе встроенного датчика.
 

 
Аналого-цифровой преобразователь (сокращенно АЦП) – это устройство, преобразующее входной аналоговый сигнал в дискретный код, чаще всего – двоичный. Обратное преобразование осуществляется при помощи цифро-аналогового преобразователя (ЦАП).


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

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

Разрешение АЦП – минимальное изменение величины аналогового сигнала, которое может быть преобразовано данным АЦП. Обычно измеряется в вольтах, поскольку для большинства АЦП входным сигналом является электрическое напряжение.
Разрешение АЦП
Разрешение АЦП
где N - разрядность АЦП.
Разрядность АЦП характеризует количество разрядов двоичного кода, которые преобразователь может выдать на выходе. Измеряется в битах. Общее количество дискретных значений выходного кода определяется как 2N.

Микроконтроллер STM32F303VCT6 имеет 4 модуля аналого-цифрового преобразователя, каждый из которых может преобразовывать до 18 каналов, поддерживает разрядность до 12 бит и, соответственно, может выдавать дискретные значения в диапазоне от 0 до 4095.

Для того чтобы иметь однозначное соответствие входного аналогового сигнала и полученного дискретного кода, вводится понятие опорного напряжения. Это напряжение, соответствующее максимальному выходному коду. Чаще всего в качестве опорного используется напряжение питания микроконтроллера. Опорному напряжению соответствует дискретный код 2N-1. При этом дискретный код, соответствующий входному напряжению, определится по указанной формуле:
Дискретный код

Разрешение АЦП определяется как отношение опорного напряжения к максимально допустимому значению дискретного кода.

На практике разрешение АЦП ограничено наличием шумовой составляющей входного сигнала. При большой интенсивности шумов на входе АЦП различение соседних уровней входного сигнала становится невозможным, то есть разрешение ухудшается. Поэтому говорят об эффективной разрядности АЦП, которая, как правило, меньше полной разрядности АЦП на 1-2 разряда.

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

  • времени выборки
  • времени непосредственно преобразования.

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

  • 1,5 машинных цикла
  • 2,5 машинных цикла
  • 4,5 машинных цикла
  • 7,5 машинных циклов
  • 19,5 машинных циклов
  • 61,5 машинный цикл
  • 181,5 машинный цикл
  • 601,5 машинный цикл

.
Чаще всего это значение приходится подбирать экспериментально в зависимости от скорости изменения измеряемого сигнала. Для внутренних сигналов АЦП приводятся рекомендации по установке этого времени.

На время преобразования также оказывает влияние разрядность результата. Чем меньше требуемая разрядность результата, тем быстрее будет произведено преобразование. Разрядность результата можно установить одним из следующих значений:

  • 12 бит,
  • 10 бит,
  • 8 бит
  • 6 бит.

Однако разрядность может задаваться для всего модуля аналого-цифрового преобразования, а не для его отдельных каналов, и это необходимо учитывать.

Результат аналого-цифрового преобразования будет храниться в 16-разрядном регистре ADCx_DR

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

Кроме того, поддерживаются варианты выравнивания со смещением для сохранения числа со знаком.

 

Создание проекта с использованием АЦП

Давайте перейдем к практике, и в качестве примера будем измерять температуру датчика, встроенного в микроконтроллер. Создадим новый проект в STM32CubeMX для нашего микроконтроллера. На вкладке Pinout раскроем меню модуля ADC1 и поставим галочку напротив канала измерения температуры.
Выбор датчика температуры для АЦП

Перейдем на вкладку Clock Configuration, и умный Cube предлагает нам автоматически найти решение с настройкой тактирования. Согласимся с автоматической настройкой. И видим, что нам предложено задействовать внутренний умножитель частоты для тактирования модуля АЦП частотой 16 МГц.
Настройка тактирования АЦП

Думаю, для нашей задачи можно согласиться с указанной частотой и перейти во вкладку Configuration. Оставим настройки модуля АЦП по умолчанию. Хотя тут можно изменить разрешение, выравнивание и другие параметры. Будем производить преобразования в непрерывном режиме, поэтому параметр Continuous Conversion Mode переведем в состояние Enable. И перейдем к настройке канала.

Нам предлагается производить преобразования аналогового сигнала одного канала. Откроем меню Rank и убедимся, что в качестве измеряемого используется канал встроенного датчика температуры.

Для датчика температуры рекомендуется установить время выборки 2,2 мкс. При частоте тактирования модуля АЦП 16 МГц это соответствует 35,2 циклам. Но у нас есть возможность установить это время равным 19,5 циклам или 61,5 циклу. Выберем второй вариант.
Настройка канала АЦП

Сгенерируем проект, сохраним его и откроем в IAR.
Для запуска АЦП используется функция

 
__weak HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef* hadc)

со ссылкой на соответствующий дескриптор в качестве аргумента.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */
  /* MCU Configuration----------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* Configure the system clock */
  SystemClock_Config();
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ADC1_Init();
  /* USER CODE BEGIN 2 */
  HAL_ADC_Start(&hadc1);
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

Добавлена строка 14.

Скомпилируем проект, загрузим в отладочную плату и запустим на выполнение, нажав F5. Посмотрим ход выполнения программы средствами отладки. Остановим выполнение программы в любой момент, выбрав меню Debug->Break или нажав на соответствующую кнопку. И теперь посмотрим состояние регистров микроконтроллера. Для этого откроем соответствующее окно, выбрав меню View->Register. Выберем регистры модуля ADC1 и обратим внимание на содержимое регистра данных. Он содержит некоторое значение, которое меняется в небольших пределах при каждом запуске программы на выполнение с последующей остановкой. Это и есть шум младших разрядов преобразователя.

Давайте попробуем рассчитать, что же измерил наш датчик.
Согласно документации на используемый микроконтроллер, параметр AVG_Slope имеет типовое значение 4,3мВ/°C, а параметр V25 = 1,43 В.
Вычисление температуры
Согласно формуле перевода измеренного напряжения в значение температуры, приведенной в документации, получим следующий вид зависимости температуры от измеренного напряжения.
Зависимость температуры от измеренного напряжения
Как видим, эта характеристика имеет обратный наклон, то есть чем больше измеренное значение напряжения, тем меньше соответствующая ему температура. Ну, и реально измеряемый диапазон температур лежит в пределах примерно 1...1,7 В. Температуру ниже, чем -50 градусов измерять вряд ли интересно.

Напряжение питания микроконтроллера составляет 3 В. Поэтому лучше использовать все 12 разрядов аналого-цифрового преобразователя, чтобы измерения получились точнее. Тем более, что температура – величина медленно меняющаяся, и в данном случае нам важнее точность измерений чем скорость их получения.

Но вернемся к измеренной температуре. Мы получили значение в отсчетах, а на графике представлена зависимость от напряжения. Переводим полученное значение в эквивалент напряжения и видим, что это соответствует температуре около 40°C.

Но, как мне кажется, измеренная температура должна быть близка к комнатной, а для комнатной температуры 40°C – это жарковато 🙂 .

А все дело в том, что прежде чем использовать аналого-цифровой преобразователь, необходимо его откалибровать. Этим занимается функция
 
HAL_StatusTypeDef HAL_ADCEx_Calibration_Start(ADC_HandleTypeDef* hadc, uint32_t SingleDiff)

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

Перед запуском преобразования произведем калибровку модуля АЦП. Загрузим программу в отладочную плату и снова запустим ее на выполнение.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */
  /* MCU Configuration----------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* Configure the system clock */
  SystemClock_Config();
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ADC1_Init();
  /* USER CODE BEGIN 2 */
  HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
  HAL_ADC_Start(&hadc1);
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

Добавлена строка 14.

Прерываем выполнение программы и смотрим на содержимое регистра результата. Произведем те же расчеты, что и в предыдущем случае. Вот теперь температура уже ближе к комнатной.

Пожалуйста, при использовании аналого-цифровых преобразователей не забывайте об их калибровке.

Теперь давайте попробуем сделать примитивный термометр с использованием встроенного датчика температуры. Будем выводить на светодиодах значение температуры с точностью 5°C. Для этого перейдем снова к конфигуратору STM32CubeMX и сконфигурируем линии c PE8 до PE15 как выходы, чтобы использовать их для управления светодиодами на плате.

В текст программы внутри бесконечного цикла вставим ту самую формулу, которая позволяет получить значение температуры в градусах по результату аналого-цифрового преобразования.

Теперь выполним несколько проверок. Если температура выше 5°C, включим светодиод на линии PE8, иначе – выключим его. Напомню, что для управления состоянием линий ввода вывода используется функция
 
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)

которой передаются имя порта, имя линии и константа, определяющая требуемое состояние.
Повторим эту пару строк еще 7 раз. Если температура выше 10°C, включаем светодиод на линии PE9. И дальше по аналогии до 40°C.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */
  /* MCU Configuration----------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* Configure the system clock */
  SystemClock_Config();
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ADC1_Init();
  /* USER CODE BEGIN 2 */
  HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
  HAL_ADC_Start(&hadc1);
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    uint16_t Vs = hadc1.Instance->DR;
    double ToC = (1.43 - ((double)Vs * 3) / 4096) * 1000 / 4.3 + 25.0;
    if (ToC > 5) HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8, GPIO_PIN_SET);
    else HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8, GPIO_PIN_RESET);
    if (ToC > 10) HAL_GPIO_WritePin(GPIOE, GPIO_PIN_9, GPIO_PIN_SET);
    else HAL_GPIO_WritePin(GPIOE, GPIO_PIN_9, GPIO_PIN_RESET);
    if (ToC > 15) HAL_GPIO_WritePin(GPIOE, GPIO_PIN_10, GPIO_PIN_SET);
    else HAL_GPIO_WritePin(GPIOE, GPIO_PIN_10, GPIO_PIN_RESET);
    if (ToC > 20) HAL_GPIO_WritePin(GPIOE, GPIO_PIN_11, GPIO_PIN_SET);
    else HAL_GPIO_WritePin(GPIOE, GPIO_PIN_11, GPIO_PIN_RESET);
    if (ToC > 25) HAL_GPIO_WritePin(GPIOE, GPIO_PIN_12, GPIO_PIN_SET);
    else HAL_GPIO_WritePin(GPIOE, GPIO_PIN_12, GPIO_PIN_RESET);
    if (ToC > 30) HAL_GPIO_WritePin(GPIOE, GPIO_PIN_13, GPIO_PIN_SET);
    else HAL_GPIO_WritePin(GPIOE, GPIO_PIN_13, GPIO_PIN_RESET);
    if (ToC > 35) HAL_GPIO_WritePin(GPIOE, GPIO_PIN_14, GPIO_PIN_SET);
    else HAL_GPIO_WritePin(GPIOE, GPIO_PIN_14, GPIO_PIN_RESET);
    if (ToC > 40) HAL_GPIO_WritePin(GPIOE, GPIO_PIN_15, GPIO_PIN_SET);
    else HAL_GPIO_WritePin(GPIOE, GPIO_PIN_15, GPIO_PIN_RESET);
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}


Скомпилируем проект, загрузим в отладочную плату и запустим на выполнение. На плате светятся 5 светодиодов. Значит, температура в комнате от 25 до 30°C. Можно поставить точку останова и проанализировать вычисленное значение. Для этого открываем окно просмотра значений переменных – меню View->Watch->Watch1 и в появившемся окне указываем имя интересующей нас переменной. В момент останова нам доступно значение этой переменной.

Попробуем нагреть микроконтроллер на плате. И мы видим, что шестой светодиод начинает промаргивать. То есть температура повысилась и сейчас находится в районе 30°C. При остывании шестой светодиод моргает все менее заметно.
Итак, мы рассмотрели работу аналого-цифрового преобразователя и научились работать со встроенным термодатчиком.
Дальше мы поговорим о том, как использовать несколько аналоговых каналов и о том, что такое основные и дополнительные каналы АЦП.

Прокрутить вверх