До сих пор мы рассматривали преобразование единственного канала аналого-цифрового преобразования. Но на практике часто возникает необходимость преобразования нескольких каналов, причем часто эти каналы преобразуются одним модулем АЦП.
Откроем проект с предыдущего урока в STM32CubeMX и посмотрим повнимательнее на настройку модуля ADC1. Мы осуществляли преобразование только для внутреннего канала термодатчика, а теперь давайте будем использовать в качестве аналогового канала еще сигнал с кнопки. Перейдем на линию PA0 и назначим ее аналоговым входом, выбрав ADC1_IN1. Первый вход модуля ADC1 сконфигурировался как аналоговый.
Перейдем к меню Configuration и зайдем в настройки аналого-цифрового преобразователя. Мы видим, что здесь есть возможность настройки каналов АЦП в качестве основных – Regular Channels или дополнительных – Injected Chanels.
Основные каналы – это те каналы, преобразование которых необходимо осуществлять регулярно. В частности, в предыдущем уроке канал измерения температуры выступал у нас в качестве основного. Преобразование основных каналов можно производить однократно или непрерывно. Но в случае если таких каналов несколько, возникает вопрос со считыванием результатов аналого-цифрового преобразования. Регистр данных ADCx_DR – то всего один на все основные каналы. Поэтому для считывания результатов преобразования нескольких каналов можно использовать обработчик прерывания или прямой доступ к памяти.
Дополнительные каналы – это те каналы, которые необходимо опрашивать время от времени. Опрос дополнительных каналов всегда является однократным и при запуске осуществляет последовательное преобразование всей группы дополнительных каналов, после чего снова осуществляется преобразование основных каналов. Всего микроконтроллеры семейства STM32 поддерживают до 4 дополнительных каналов АЦП, для каждого из которых предусмотрен свой регистр данных.
Давайте попробуем использовать канал кнопки как дополнительный. Для этого количество дополнительных каналов увеличиваем до 1 и заходим в появившееся меню Rank. Убедимся, что в качестве дополнительного канала будет использоваться Channel 1 и оставим время выборки по умолчанию равным 1,5 циклам. Для самого режима преобразования дополнительных каналов необходимо выбрать какой-нибудь фронт внешнего триггера, пусть это будет нарастающий фронт – Rising и тип триггера установить в значение Software trigger. Режим преобразования ставим в значение Discontinuous.
Сохраним настройки и сгенерируем проект для IAR. Для запуска преобразования дополнительных каналов используется функция
со ссылкой на дескриптор модуля АЦП в качестве аргумента.
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
44
{
/* 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);
HAL_ADCEx_InjectedStart(&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 */
}
Давайте запустим преобразование дополнительных каналов после основного и запустим программу на выполнение, зажав кнопку. После прерывания выполнения программы мы видим, что в регистре ADC1_JDR1 содержится значение, близкое к максимально допустимому. Это и есть регистр результата преобразования дополнительных каналов. И таких регистров в модуле АЦП – 4.
Продолжаем выполнение программы, нажав F5, но мы видим, что значение в регистре ADC1_JDR1 не меняется, даже если отжать кнопку. То есть преобразование дополнительных каналов действительно выполнилось однократно перед бесконечным циклом.
Давайте перенесем функцию запуска преобразования дополнительных каналов внутрь бесконечного цикла, снова загрузим программу и запустим на выполнение.
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
44
{
/* 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)
{
HAL_ADCEx_InjectedStart(&hadc1);
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 */
}
Перемещена строка 16 -> 21.
Теперь при каждой остановке значение в регистре ADC1_JDR1 меняется, причем если кнопка отжата, то это значение близко к нулю, а если нажата, то это значение близко к максимальному.
Основные каналы АЦП
Теперь поработаем с основными каналами и перенесем кнопку в их число. Для этого в STM32CubeMX количество дополнительных каналов установим равным нулю, а количество основных каналов увеличим до двух. И сразу STM32CubeMX подсказывает нам, что если количество каналов преобразования больше одного, то необходимо установить параметр Scan Conversion Mode, отвечающий за последовательное преобразование каналов, в состояние Enabled.
Попробуем получать результат аналого-цифрового преобразования с использованием прерываний. При этом будем использовать режим преобразований Discontinuous и запускать выполнение преобразований «вручную», чтобы избежать насыщения системы прерываний. Режимы Continuous и Discontinuous являются взаимоисключающими и не могут использоваться одновременно.
В режиме Discontinuous каждый раз при запуске будет выполнено одно преобразование, но номер канала будет чередоваться циклически.
Далее переходим во вкладку NVIC Settings и разрешаем прерывания модулей ADC1 и ADC2.
Генерируем код проекта и открываем проект в IAR.
Для того чтобы разрешить прерывания по окончании АЦП необходимо вызвать для преобразования функцию
Повторно вызовем эту функцию в бесконечном цикле вместо преобразования дополнительных каналов.
При размещении функции запуска аналого-цифровых преобразований внутри цикла необходимо следить, чтобы время между вызовами было больше, чем время выполнения одного преобразования.
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
44
{
/* 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_IT(&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);
HAL_ADC_Start_IT(&hadc1);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
Перейдем к файлу обработчиков прерываний и поставим точку останова внутри функции обработки прерываний АЦП. Запуская на выполнение, мы можем наблюдать, что значение в регистре данных в этой точке чередуется: измерение напряжения на термодатчике сменяется измерением напряжения на кнопке. Осталось эти значения сохранить. Для этого в файле main.c введем две переменные с глобальной областью видимости. Переменная Vs будет хранить напряжение на датчике температуры, которую мы уже использовали. Просто вынесем ее описание за рамки функции main(). Переменная Button будет хранить напряжение на кнопке. Обе переменные - беззнаковые 16-битные.
Для того чтобы эти переменные были доступны в другом файле (в частности, в файле обработчиков прерываний), необходимо описать их в этом файле как внешние с глобальной областью видимости, сохраняя их имена.
2
3
4
extern uint16_t Vs;
extern uint16_t Button;
/* USER CODE END 0 */
Теперь займемся обработчиком прерывания модуля АЦП. Поскольку каналов преобразования всего два, и каналы чередуются по циклу, каждое четное преобразование (нулевое, второе, четвертое) – это величина напряжения на термодатчике (канал, Rank которого равен 1), а нечетные преобразования – это преобразования напряжения на кнопке (канал, у которого Rank равен 2). Поэтому в обработчике прерывания введем статическую переменную, которая будет увеличиваться на 1 каждый раз при вызове обработчика.
Статическая переменная – это переменная, значение которой сохраняется при выходе из функции, но область видимости такой переменной ограничивается областью видимости функции, то есть из других точек программы эта переменная не видна. При объявлении статическая переменная обязательно должна быть проинициализирована.
А дальше в обработчике прерывания выполним проверку: если значение переменной count четное, то сохраняет полученный результат преобразование в переменной Vs. Для получения результата преобразования можно воспользоваться функцией
со ссылкой на дескриптор модуля АЦП в качестве аргумента, а можно получить это значение непосредственно, обратившись к регистру ADC1_DR, как мы делали это в предыдущем уроке.
Если значение переменной count нечетное, то сохраняем результат преобразования в переменную Button.
2
3
4
5
6
7
8
9
10
11
12
13
14
{
/* USER CODE BEGIN ADC1_2_IRQn 0 */
static uint8_t count = 0;
if (count % 2 == 0)
Vs = HAL_ADC_GetValue(&hadc1);
else
Button = HAL_ADC_GetValue(&hadc1);
count++;
/* USER CODE END ADC1_2_IRQn 0 */
HAL_ADC_IRQHandler(&hadc1);
/* USER CODE BEGIN ADC1_2_IRQn 1 */
/* USER CODE END ADC1_2_IRQn 1 */
}
Теперь давайте реализуем, чтобы температура выводилась на светодиодную шкалу только при нажатой кнопке. Для этого проверим, если значение переменной Button > 4000, то есть кнопка нажата, выводим результат измерения температуры на светодиодную шкалу. Иначе переводим все используемые линии порта GPIOE в состояние Reset, чтобы отключить все светодиоды.
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
44
45
46
47
48
49
50
51
52
53
54
55
56
uint16_t Vs;
uint16_t Button;
/* USER CODE END 0 */
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_IT(&hadc1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if (Button > 4000)
{
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);
}
else
{
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 |
GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 |
GPIO_PIN_14 | GPIO_PIN_15, GPIO_PIN_RESET);
}
HAL_ADC_Start_IT(&hadc1);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
Компилируем проект, загружаем в отладочную плату и запускаем на выполнение. Не забываем снять точку останова из обработчика прерываний. Мы видим, что при нажатой кнопке на светодиодной шкале индицируются показания температуры. При отжатой кнопке все светодиоды выключены.
С нетерпением жду DMA )
Дождались 🙂
Следующий, 18-ый урок выйдет на следующей неделе