В последнее время было много экспериментов по STM32, о которых я не писал, хотелось запилить статью про USB амперметр-вольтметр. Чем крупнее задумываешь статью, тем ниже шанс что она появится 🙂 В итоге материал завис на 3 месяца, в общем лучше буду как и раньше писать небольшие заметки, желающие могут самостоятельно слепить из маленьких кирпичиков необходимую девайсину. Текущая задача — ознакомиться с работой АЦП, на простом примере.
Цель: считываем измеренное значение с точностью 12 бит(4096), если оно больше 512 зажигаем PD12, больше 1024 — зажигаем PD13, больше 2048 — PD14, 3078 — PD15. Если кому то нужно перевести в вольты,
напряжение = (3.3/4096)*измеренное значение. Остальные пояснения по ходу.
Вначале нам нужно определиться с входом АЦП, советую на компе держать Cube MX это генератор кода, но лично мне он нравится наличием побочных плюшек, т.е. можно посмотреть сколько каких ресурсов, где какие входы/выходы.
Сразу видно что IN0 выделен красным, т.е. его юзать нельзя.
В итоге ковыряемся в схеме и определяем, что на PA0 подключена кнопка, об этом кстати нам и сообщает куб.
В общем это такое лирическое отступление, выбирайте вход аккуратно, что потом не мучаться, почему не работает. Поэтому сразу выбираем PA1. Настраиваем ножку на альтернативную функцию, т.е. как аналоговый вход, т.е. эти настройки относятся не к самому АЦП, а скорее к настройке ножки
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE); //включаем тактирование ацп1 ADC_DeInit(); //сбрасываем настройки АЦП по умолчанию RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //включаем тактирование портаА GPIO_InitTypeDef porta_setup; //создаем структуру porta_setup.GPIO_Mode = GPIO_Mode_AN; //режим - аналоговый вход porta_setup.GPIO_Pin = GPIO_Pin_1; //первая ножка GPIO_Init(GPIOA, &porta_setup); //записать настройки |
Ножки могут быть цифровым входом-выходом, если на этой же ножке подцеплена альтернативная функция — UART, SPI, USB, ADC…. то нужно об этом сообщить в программе, что и сделано.
Теперь уже переходим к настройке непосредственно АЦП
ADC_InitTypeDef adc_setup; //создаем структуру adc_setup.ADC_ContinuousConvMode = DISABLE; //не делать длительных преобразований adc_setup.ADC_Resolution = ADC_Resolution_12b; //разрешение 12 бит adc_setup.ADC_ScanConvMode = DISABLE; //однократное преобразование adc_setup.ADC_ExternalTrigConv = ADC_ExternalTrigConvEdge_None; //не использовать триггер adc_setup.ADC_DataAlign = ADC_DataAlign_Right; //выравнивание результата по правому краю ADC_Init(ADC1, &adc_setup); //записать настройки ADC_Cmd(ADC1,ENABLE); //включить АЦП |
Continuous conversion mode — преобразование начинается после завершения предыдущего. Нам это ни к чему.
Scan mode — режим используется для сканирования группы аналоговых каналов. Отключаем.
Conversion on external trigger and trigger polarity — преобразование может выполняться по внешнему событию, например по захвату таймера или по внешнему прерыванию. Отключаем.
Чтение значений АЦП. Минимальное количество циклов для преобразования считается как разрешение + 3, для 12 bits: 3 + 12 = 15 ADCCLK cycles. Работаем в режиме одиночного преобразования. По окончанию преобразования, устанавливается флаг EOC.
Регистр в который помещается результат 16 битный, а так как данные у нас 12 бит максимум, поэтому результат можно выравнивать либо по левому краю, либо по правому.
Настройка ножек для включения светодиодов
GPIO_InitTypeDef port_setup; //создаем структуру RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); //включаем тактирование портаD port_setup.GPIO_Mode = GPIO_Mode_OUT; //ножка как выход port_setup.GPIO_OType = GPIO_OType_PP; //режим пуш-пул port_setup.GPIO_PuPd = GPIO_PuPd_DOWN; //подтяжка к земле port_setup.GPIO_Pin = GPIO_Pin_All; //настройка для всего порта port_setup.GPIO_Speed = GPIO_Speed_2MHz; //скорость 2мгц GPIO_Init(GPIOD, &port_setup); //записать настройки |
Полный исходник
#include "stm32f4xx.h" #include "stm32f4xx_adc.h" #include "stm32f4xx_gpio.h" #include "stm32f4xx_rcc.h" int result; u16 readADC1(u8 channel) { //настройка - канал 1, время преобразования 3 цикла ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_3Cycles); //начать преобразование ADC_SoftwareStartConv(ADC1); // ждем окончания преобразования while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); // возвращаем полученное значение return ADC_GetConversionValue(ADC1); } int main(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE); ADC_DeInit(); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //настройка альтернативной GPIO_InitTypeDef porta_setup; //функции ножки PA1 GPIO_StructInit(&porta_setup); porta_setup.GPIO_Mode = GPIO_Mode_AN; porta_setup.GPIO_Pin = GPIO_Pin_1; GPIO_Init(GPIOA, &porta_setup); ADC_InitTypeDef adc_setup; adc_setup.ADC_ContinuousConvMode = DISABLE; adc_setup.ADC_Resolution = ADC_Resolution_12b; adc_setup.ADC_ScanConvMode = DISABLE; adc_setup.ADC_ExternalTrigConv = ADC_ExternalTrigConvEdge_None; adc_setup.ADC_DataAlign = ADC_DataAlign_Right; ADC_Init(ADC1, &adc_setup); ADC_Cmd(ADC1,ENABLE); GPIO_InitTypeDef port_setup; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); port_setup.GPIO_Mode = GPIO_Mode_OUT; port_setup.GPIO_OType = GPIO_OType_PP; port_setup.GPIO_PuPd = GPIO_PuPd_DOWN; port_setup.GPIO_Pin = GPIO_Pin_All; port_setup.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(GPIOD, &port_setup); while(1) { result = readADC1(ADC_Channel_1); if(result > 512) // (3,3/4096)*512 = 0,4125V { GPIO_SetBits(GPIOD,GPIO_Pin_12); } else { GPIO_ResetBits(GPIOD,GPIO_Pin_12); } if(result > 1024) //0,825 { GPIO_SetBits(GPIOD,GPIO_Pin_13); } else { GPIO_ResetBits(GPIOD,GPIO_Pin_13); } if(result > 2048)//1,65 { GPIO_SetBits(GPIOD,GPIO_Pin_14); } else { GPIO_ResetBits(GPIOD,GPIO_Pin_14); } if(result > 3072//2,475 { GPIO_SetBits(GPIOD,GPIO_Pin_15); } else { GPIO_ResetBits(GPIOD,GPIO_Pin_15); } } } |
Вот так это все работает:
Если в кубе что-то покраснело, то это не значит что нельзя использовать. Нажимаем на ctrl и удерживая его тыкаем в периферию которая что-то заблокировала. Если есть возможность отремапить, то нужные ножки подсветятся синим и тем самым перенеся периферию освобожнаем ноги.
Спасибо, не знал
Здравствуйте! Каким образом задается частота дискретизации? Возможно ли выставить 500 кГц?
Здесь использовано одноразовое преобразование, поэтому о частоте не может быть и речи. Чуть позже доберусь и до темы озвученной вами, в планах это есть
Спасибо Вам за ответ! Постоянно слежу за вашими материалами!
Спасибо за статью, тоже жду продолжения про частоту дискретизации.
Просто и все понятно. Спасибо за статью.
Замечательные статьи, спасибо! Только только делаю первые шаги. Как сделать чтобы пин горел только в определенном диапазоне, а не после порогового значения?
задать диапазон условием if((adc > 100) && (adc < 200)) pin_on; else pin_off;
Спасибо за ответы, все получилось.
Уважаемый админ, а как вывести на дисплей показания с десятыми долями? Целые получается а вот с десятыми засада
В sprintf преобразование типов с десятичными чета не хочет работать «%f» проэкт в coocox со стандартной спринтэфовской либой переменные объявлены как int/ Калупался в библиотеки и про плавающую запятую не че не нашел кстати на многих форумах ребята то же на этот факт матерятся
result = (3.3/4096)*readADC1(ADC_Channel_1);
lcd_set_xy(0,0);
sprintf(buffer,»X=%f»,result );
lcd_out(buffer);
delay_ms(500);
lcd_clear();
попробуйте 4096.0
правильно перед f точку ставить вот так sprintf(buffa,»%.2f»,data);
у меня вопрос как мне запустить АЦП таймером.Бит Bit 30 SWSTART: Start conversion of regular channels
This bit is set by software to start conversion and cleared by hardware as soon as the
conversion starts.
0: Reset state
1: Starts conversion of regular channels
Note: This bit can be set only when ADON = 1 otherwise no conversion is launched.
Этот бит когда мне включать?
Если честно так на память не помню, но если хочется дергать ацп таймером, то для этого отдельный бит для включения внешнего триггера, отдельный бит для запуска.
Как запустить 2 и более ацп если DR ? в инжекторных каналах 4DR
алгоритм?