STM32 с нуля. ADC(АЦП).

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

Начнем-с…Вот некоторые характеристики аналого-цифрового преобразователя в STM32f10x:

  • АЦП является 12-ти битным
  • Возможна генерация прерывания по окончанию преобразования, по окончанию преобразования с инжектированного канала, а также возможно прерывание от Analog Watchdog (что это такое расскажу чуть ниже)
  • Возможно одиночное преобразование и преобразование в непрерывном режиме
  • Самокалибровка
  • Запуск преобразования от внешнего события
  • Работа с ПДП (DMA, прямой доступ к памяти)

Вот структурная схема из даташита, полюбуйтесь )
STM32 ADC

Пока не забыл про Analog Watchdog, опишу его работу.
Он нужен для того, чтобы следить, что напряжение попадает в определенные пределы. Причем он может сканировать как конкретный канал, так и группу каналов. В регистры ADC_HTR и  ADC_LTR заносим значения верхнего и нижнего порога соответственно. И в случае, если проверяемое напряжение выходит за эти пределы, генерируется прерывание. Полезнейшая вещица!

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

Как и в микроконтроллерах AVR возможно выравнивание результата по правому или по левому краю. Тут правда результат 12-ти битный. Но суть та же. Вот как все это выглядит в регистрах:

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

Пробежим быстренько по основным регистрам для работы с АЦП. Хоть мы и не будем с ними взаимодействовать напрямую, так как используем библиотеку Standard Peripheral Library, все равно надо уметь работать непосредственно с регистрами. Не буду переписывать весь даташит, это скучно как писать, так и читать потом ) Расскажу про основные:

Регистр ADC_SR. Статусный регистр. Тут хранятся флаги – например, флаг, сигнализирующий об окончании преобразования.

Регистр ADC_CR1. Контрольный регистр. Тут всякие биты, разрешающие или запрещающие те или иные события. Например, включение Analog Watchdog для регулярных каналов, аналогично для инжектированных, включение различных прерываний – все это сидит здесь, в этом регистре.

Регистр ADC_CR2. Еще один контрольный регистр. А что вы хотели? Много режимов, нужно больше контроля ) Здесь спрятались биты, отвечающие за использование АЦП совместно с DMA, а также за запуск преобразования от внешнего источника. Кроме того, тут можно разрешить или запретить использование датчика температуры. Важный бит – SWSTART, который запускает преобразование регулярных каналов.

Регистры ADC_SMPRx отвечают за время выборки. В ADC_SQR и ADC_JSQR выбираем номера нужных нам каналов. Про регистры для Analog Watchdog уже упоминалось ранее. Подробнее в даташите )

И, наконец, регистр данных – ADC_DR – там и только там мы будем забирать наши драгоценные, полученные с огромным трудом результаты.

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

Открываем файлы stm32f10x_adc.c и stm32f10x_adc.h и ищем что-нибудь, что нам может помочь. И очень быстро в структуре ADC_InitTypeDef находим интересное поле uint32_t ADC_ExternalTrigConv. То что надо! Но что же туда записать? Идем дальше и находим такой кусок:

#define ADC_ExternalTrigConv_T1_CC1                ((uint32_t)0x00000000) 
#define ADC_ExternalTrigConv_T1_CC2                ((uint32_t)0x00020000) 
#define ADC_ExternalTrigConv_T2_CC2                ((uint32_t)0x00060000) 
#define ADC_ExternalTrigConv_T3_TRGO               ((uint32_t)0x00080000) 
#define ADC_ExternalTrigConv_T4_CC4                ((uint32_t)0x000A0000) 
#define ADC_ExternalTrigConv_Ext_IT11_TIM8_TRGO    ((uint32_t)0x000C0000)

Это не что иное, как возможные значения поля ADC_ExternalTrigConv. Мы хотим прерывание по переполнению таймера 3, например, но, тысяча чертей, тут такого нету. Зато есть что-то таинственное под названием ADC_ExternalTrigConv_T3_TRGO. А это такая фишка — Trigger Output Mode. То есть в качестве внешнего события мы можем выбрать то, что нам нужно, но это уже в настройках таймера. Фигня вопрос, идем ковырять файл stm32f10x_tim.c и находим там функцию TIM_SelectOutputTrigger (TIM_TypeDef* TIMx, uint16_t TIM_TRGOSource). В списке ее возможных аргументов без труда находим нужный — TIM_TRGOSource_Update. Вот и все! Мозговой штурм окончен =) Переходим к реализации примера.

Не буду приводить полный код, includ’ы и defin’ы всякие, только суть:

//Тактирование, тактирование и еще раз тактирование
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
//Инициализируем таймер, об этом написано ранее
TIM_TimeBaseStructInit(&timer);
timer.TIM_Prescaler = 720;
timer.TIM_Period = 50;
TIM_TimeBaseInit(TIM3, &timer);	
ADC_StructInit(&adc);
//Режим – от триггера(внешнего события)
adc.ADC_Mode = ADC_Mode_AlterTrig ;
adc.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
ADC_Init(ADC1, &adc);
//PA0 – вход АЦП
GPIO_StructInit(&port);
port.GPIO_Mode = GPIO_Mode_AF_PP;
port.GPIO_Pin = GPIO_Pin_0;
port.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &port);

Про инициализацию таймеров, а также портов GPIO можно прочитать в более ранних статьях:

int main()
{
    __enable_irq ();
    initAll();
    //Включаем таймер и прерывание
    TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
    TIM_Cmd(TIM3, ENABLE);
    TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);	
    //Включаем преобразование АЦП от внешнего события
    ADC_ExternalTrigConvCmd(ADC1, ENABLE);
    NVIC_EnableIRQ(TIM3_IRQn);
    while(1)
    {
    	//Вечное безделие…..
	__NOP();
    }
}
//Тут просто тупим и зачищаем флаг
void TIM3_IRQHandler()
{	
    TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}

Отлично! Программа готова. Но что мы вообще измерять то собрались?! Откуда брать сигнал то? А в этом нам снова поможет язык сценариев Keil’а.  Создаем в папке проекта(!) файл adc.ini и пишем в него:

/*******************************************************************/
signal void adc(void) 
{
    float f;
    for (f = 0.0; f < 3.3; f += 0.1)
    {
	swatch (0.1);                  
	ADC1_IN0 = f;                 
    }
}
/*******************************************************************/

То есть эмулируем изменяющийся со временем, но не непрерывно, входной сигнал на нулевом канале АЦП1.

Запускаем отладчик. Заходим в View -> Serial Windows -> ADC и выбираем ADC1. Находим регистр Data  в открывшемся окне, тут то мы и будем отслеживать результат преобразования. Пишем в командной строке:

include adc.ini

Запускаем программу и после этого запускаем функцию adc(). Подробнее про язык сценариев – в статье отладка программы в Keil
STM32 ADC

Видим, как меняются данные в регистре данных.  Программа работает правильно, так что можем переходить к дальнейшему изучению STM32. Продолжение следует…

Понравилась статья? Поделись с друзьями!

STM32 с нуля. ADC(АЦП).: 26 комментариев
  1. У меня такой вопрос — можно ли в STM32 объеденять каналы в дифиренциальные пары (как в AVR) и измерять напряжение между этими каналами?
    Если можно — то как?
    Спасибо.

    • Прям такого, как в AVR тут нет, но АЦП очень навороченный, так что для любой задачи можно, в принципе, придумать решение

      • вот задача:
        мерять ток на шунте при заряде и разряде аккумулятора.
        Так как меняется знак тока — приходится применять ОУ в режиме дифф-усилителя с искуственной средней точкой. Ну или использовать спец. микросхему вроде ACS712.
        На attiny261 можно сделать очень красиво — но только один канал, а нужно 4 канала и stm32f100 здесь смотриться очень заманчиво…

        • Тут только два варианта — либо добиться значений, подходящих для АЦП в STM32 и подавать на два канала, а вычисления все программно проводить, либо внешний ОУ все-таки придется ставить

  2. Изучал ваш код, заметил: //PA0 – вход АЦП
    GPIO_StructInit(&port);
    port.GPIO_Mode = GPIO_Mode_AF_PP;
    port.GPIO_Pin = GPIO_Pin_0;
    port.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOA, &port);
    Вроде надо GPIO_Mode_AN

  3. Здравствуйте, у меня вопрос. Короче в программирования контроллеров я новичок, сейчас разбираюсь с АЦП, написал программу на STM32F103ZE которая должна измерять напряжение которое подается на вход АЦП. Все норм компилится, но когда дэбажу код то вижу что никакого результата я не получаю, что это может быть???
    Вот код:
    #include «stm32f10x.h»
    #include «stm32f10x_adc.h»
    #include «stm32f10x_gpio.h»
    #include «stm32f10x_rcc.h»

    GPIO_InitTypeDef port;
    ADC_InitTypeDef adc;

    void leds_init ()
    {
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_StructInit(&port);
    port.GPIO_Mode = GPIO_Mode_AF_PP;
    port.GPIO_Pin = GPIO_Pin_1;
    GPIO_Init(GPIOA, &port);
    }
    void adc_init ()
    {
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    ADC_StructInit(&adc);
    adc.ADC_Mode = ADC_Mode_Independent;
    adc.ADC_ContinuousConvMode = DISABLE;
    adc.ADC_DataAlign = ADC_DataAlign_Right;
    adc.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    adc.ADC_ScanConvMode = DISABLE;
    adc.ADC_NbrOfChannel = 1;
    ADC_Init(ADC1, &adc);
    ADC_Cmd(ADC1, ENABLE);
    ADC_ResetCalibration(ADC1);
    /* Check the end of ADC1 reset calibration register */
    while(ADC_GetResetCalibrationStatus(ADC1));
    /* Start ADC1 calibaration */
    ADC_StartCalibration(ADC1);
    /* Check the end of ADC1 calibration */
    while(ADC_GetCalibrationStatus(ADC1));
    }
    u16 readADC1(u8 channel)
    {
    ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_1Cycles5);
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
    return ADC_GetConversionValue(ADC1);
    }
    void Delay(unsigned int Val)
    {
    for (; Val !=0; Val—);
    }
    int main(void)
    {
    leds_init();
    adc_init();
    do{
    unsigned int bin_code = readADC1(ADC_Channel_1);
    double voltage = bin_code*2.96/0xfff;
    Delay(500000);
    }while(1);
    }

  4. Нет все норм работает, только не возвращает значение bin_code вследствие и voltage почему незнаю, может нужно попробывать сгенерировать сигнал как у вас в примере, может после етого он будет хоть чтото подсчитывать.

    • А, кстати….. По-моему в SPL в поле adc.ADC_NbrOfChannel могут быть значения от 1 до 16, то есть нулевой канал — это 1. Тут у нас PA1 — 1-ый канал АЦП, тогда adc.ADC_NbrOfChannel = 2

  5. //МК stm32f10x. PC0,PC1,PC4 соответствуют каналом АЦП 10, 11, 14. Данные после запука программы можно найти в Регистрах данных JDR1,JDR2,JDR3 в ADC1 (во время прошивки железа). Работает (но если записать в периуд DataConvX то TIM6 работать небудет!)
    Я подовал на вход напряжение 3.3В через переменный резисто! Меняя сопротивление мы изменяем периуд моргания диодов. Хотя и без этого АЦП считовал сигнал!
    // Пример с инжекторными каналоми
    #include «stm32f10x.h»
    #include «stm32f10x_rcc.h»
    #include «stm32f10x_gpio.h»
    #include «stm32f10x_tim.h»
    #include «stm32f10x_adc.h»
    #include «misc.h»

    void ADCsInj_ini(void) //АЦП 1
    {
    //#1
    GPIO_InitTypeDef port;
    ADC_InitTypeDef adc;
    TIM_TimeBaseInitTypeDef timer;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOС, ENABLE);

    GPIO_StructInit(&port);
    port.GPIO_Pin = GPIO_Pin_1GPIO_Pin_0| GPIO_Pin_4;
    port.GPIO_Mode = GPIO_Mode_AIN;
    port.GPIO_Speed = GPIO_Speed_10MHz;
    GPIO_Init(GPIOС, &port);

    //Таймер для запуска
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    TIM_TimeBaseStructInit( &timer);
    timer.TIM_Prescaler = 2400-1;// Такт 100мкс
    timer.TIM_Period = 5000;//0.5с
    TIM_TimeBaseInit(TIM3, &timer);
    //АЦП
    RCC_ADCCLKConfig( RCC_PCLK2_Div8);//Делет частоту МК на 8
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

    ADC_StructInit( &adc);
    adc.ADC_Mode = ADC_Mode_Independent;
    adc.ADC_ScanConvMode = ENABLE; //Включаем сканирование каналов
    adc.ADC_ContinuousConvMode = DISABLE;//Включено разовое выполнение
    adc.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;// Внешний тригер запуска
    adc.ADC_DataAlign = ADC_DataAlign_Right;
    adc.ADC_NbrOfChannel = 3;// число каналов
    ADC_Init(ADC1, &adc);

    //#2
    // Задаем число коналов с которых снимаем сигнал не более 4
    ADC_InjectedSequencerLengthConfig(ADC1, 3);
    // Приписываем каналу регистр данных JDRx в который будут записыватьс наши данные, где х 3-й параметр. Ну и задаем скорость отцыфроки для каждого канала
    ADC_InjectedChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_71Cycles5);
    ADC_InjectedChannelConfig(ADC1, ADC_Channel_11, 2, ADC_SampleTime_71Cycles5);
    ADC_InjectedChannelConfig(ADC1, ADC_Channel_14, 3, ADC_SampleTime_71Cycles5);
    //Запускаем АЦП от Тригера (из adc.ADC_ExternalTrigConv)
    ADC_ExternalTrigConvCmd(ADC1, ENABLE);
    ADC_AutoInjectedConvCmd(ADC1, ENABLE);//Авто запуск инжекторных каналов после регулярных

    ADC_ITConfig(ADC1, ADC_IT_JEOC, ENABLE);
    ADC_Cmd(ADC1, ENABLE);
    NVIC_EnableIRQ(ADC1_IRQn);

    // Настройка TIM3 который запускает АЦП
    TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);
    TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
    TIM_Cmd(TIM3, ENABLE);
    NVIC_EnableIRQ( TIM3_IRQn);

    //Каливровка АЦП
    ADC_ResetCalibration(ADC1);
    while(ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while(ADC_GetCalibrationStatus(ADC1));
    }

    void TIMBasT_ini(void)//Тамер для моргания диодами
    {
    TIM_TimeBaseInitTypeDef timer;
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
    // RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE);

    TIM_TimeBaseStructInit( &timer);
    timer.TIM_Prescaler =24000 — 1;
    timer.TIM_Period = 500; 0.5мс
    TIM_TimeBaseInit(TIM6, &timer);

    TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);
    TIM_Cmd(TIM6, ENABLE);
    NVIC_EnableIRQ(TIM6_DAC_IRQn);
    }

    uint16_t DataConvX, DataConv;//extern DataConvX, DataConvY, DataConvZ,
    uint8_t ADC_Data_TX;
    uint8_t i;

    int main(void)
    {
    __enable_irq ();
    ADCsInj_ini();
    Ports_ini();
    TIMBasT_ini();
    GPIO_SetBits(GPIOC,GPIO_Pin_8);
    GPIO_SetBits(GPIOC,GPIO_Pin_9);
    ADC_Data_TX = 0;
    i=0;
    while (1)
    {
    __NOP();
    }
    }

    //Таймер для управления периудом переключений свето диодов
    void TIM6_DAC_IRQHandler (void)
    {
    if (ADC1->JDR1 != 0x0000)// без этого условия АЦП записывае в периуда 0х0000 (как я понил таймер уходит в бесконечное прирывани) и перстает переключать диоды
    {
    if (TIM_GetITStatus(TIM6,TIM_IT_Update)!= RESET)
    {
    TIM_TimeBaseInitTypeDef timer;
    //TIM_TimeBaseStructInit( &timer);
    timer.TIM_Prescaler = 24000-1;
    timer.TIM_Period =(uint16_t) (ADC1->JDR1);//записываем периуд из регистра ADC1->JDR1
    TIM_TimeBaseInit(TIM6, &timer);
    GPIO_Write(GPIOC, GPIO_ReadOutputData(GPIOC)^(GPIO_Pin_8|GPIO_Pin_9));//моргание
    TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
    }
    }
    else
    {
    //Выключаем регисты
    GPIO_ResetBits(GPIOC,GPIO_Pin_8);
    GPIO_ResetBits(GPIOC,GPIO_Pin_9);
    }
    }

    void TIM3_IRQHandler(void)
    {
    if (i== 2) //Каждые 2 прерывания запукается АЦП
    {
    i=0;
    ADC_Data_TX = 0;
    ADC_ExternalTrigConvCmd(ADC1, ENABLE); //Запуск АЦТ от Тригера TIM3 (очень важная функция)
    }
    if (TIM_GetITStatus(TIM3,TIM_IT_Update)!= RESET)
    {
    TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
    i++;
    }
    }

    void ADC1_IRQHandler(void)
    {
    if (ADC_Data_TX != 1)
    {
    if (ADC_GetITStatus(ADC1, ADC_IT_JEOC)!= RESET)
    {
    ADC_ClearITPendingBit(ADC1, ADC_IT_JEOC);
    DataConvX = ADC_GetInjectedConversionValue(ADC1, ADC_InjectedChannel_1);
    DataConvY = ADC_GetInjectedConversionValue(ADC1, ADC_InjectedChannel_2);
    DataConvZ = ADC_GetInjectedConversionValue(ADC1, ADC_InjectedChannel_3);
    ADC_Data_TX = 1; //Типа флага что даннае пришли
    ADC_ExternalTrigConvCmd(ADC1, DISABLE);
    }
    }
    }

  6. //Еще добавьте фукцию для свето диодов
    void Ports_ini(void)
    {
    GPIO_InitTypeDef port;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

    GPIO_StructInit(&port);
    port.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9;
    port.GPIO_Mode = GPIO_Mode_Out_PP;
    port.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOC, &port);
    }

  7. Скажите пожалуйста, почему не работает программа?
    Заранее спасибою
    #include
    #include

    #define LED_PORT GPIOD
    #define LED_GREEN (1<<12)
    #define LED_ORANGE (1<<13)
    #define LED_RED (1<<14)
    #define LED_BLUE (1< AHB1ENR |= RCC_AHB1ENR_GPIODEN;
    RCC -> AHB1ENR |= RCC_AHB1ENR_GPIOAEN;

    GPIOA -> MODER &= ~GPIO_MODER_MODER0;
    LED_PORT -> MODER |= GPIO_MODER_MODER12_0 | GPIO_MODER_MODER13_0 | GPIO_MODER_MODER14_0 |GPIO_MODER_MODER15_0;

    }

    void switch_led_off(void)
    {
    LED_PORT->ODR &= ~GPIO_ODR_ODR_15;
    }

    void switch_leds_off(void)
    {
    LED_PORT->ODR &= ~GPIO_ODR_ODR_12;
    LED_PORT->ODR &= ~GPIO_ODR_ODR_13;
    LED_PORT->ODR &= ~GPIO_ODR_ODR_14;
    LED_PORT->ODR &= ~GPIO_ODR_ODR_15;
    }

    void setup_adc (void)
    {

    ADC_InitTypeDef ADC_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;
    ADC_CommonInitTypeDef ADC_CommonInitStructure;// adc_init
    ADC_DeInit();

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
    //
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    /* ???????? ?????????????? ?????????, ? ?? ?? ???????????? ???????? */
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConvEdge_None;
    ADC_InitStructure.ADC_NbrOfConversion = 1;
    ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
    ADC_Init(ADC1, &ADC_InitStructure);

    // ????? ??????
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_3Cycles);

    // ??????? ?????????
    ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
    ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
    ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
    ADC_CommonInit(&ADC_CommonInitStructure);

    //
    ADC_DiscModeCmd(ADC1, DISABLE);
    ADC_EOCOnEachRegularChannelCmd(ADC1, DISABLE);
    ADC_DMARequestAfterLastTransferCmd(ADC1, DISABLE);
    ADC_DMACmd(ADC1, DISABLE);

    // ???????? ???
    ADC_Cmd(ADC1, ENABLE);
    }
    int main(void)
    {
    int a=0, x=0;
    setup_leds();
    setup_adc ();

    while(1)
    {
    ADC_RegularChannelConfig(ADC1,ADC_Channel_1, 1, ADC_SampleTime_3Cycles);
    ADC_SoftwareStartConv(ADC1);
    while (x3000)
    {
    LED_PORT->ODR = LED_BLUE | LED_ORANGE | LED_RED | LED_GREEN;
    while (x<1000000)
    {
    x++;
    }x=0;
    }

    if (a<2000)
    {
    switch_leds_off();
    while (x<1000000)
    {
    x++;
    }x=0;
    }

    }
    }

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

  9. Именно на проверке флага происходит зависание, подскажите в чём проблема могу скинуть свой код

  10. 1) Я правильно понял из datasheet, что если я беру контроллер любой 100-й серии в корпусе с 64 выводами и менее, я не найду у него снаружи ножек для подключения опорного напряжения, там вроде как эти ножки соединены с ножками аналогового питания???
    2) Где вообще можно посмотреть цоколёвку на конкретный микроконтроллер, скажем развожу я плату под STM32F100RB6, на какую ножку подавать питание на какую кварц вешать? В основном конечно про питание интересует, в datasheet что-то нет распиновки, как например для тех же ATmega?
    3) НУ и самое интересное, что в голове у меня не укладывается, перерыл satasheet но не нашёл, если брать АЦП в той же ATmega, то для него есть формула, которая позволяет определить входное напряжение, зная опорное значение напряжения и разрядность АЦП. А что в свои регистры АЦП записывает STM???? Это уже будут 12 разрядов измеренного напряжения с учётом всех коэффициентов, мне его останется перевести только в десятичный вид? или что?

    • По первой части — видимо ты смотрел reference manual на семейство stm32f10x, если скачать документацию на конкретный контроллер, то там есть распиновки под все корпуса.

      По второй части — ну да, в регистре будет значение, как и в avr, и в любом другом МК. Это значение надо перевести в Вольты, зная опорное напряжение. И в даташите кстати есть формулы тоже по-моему.

  11. Да действительно, нашёл satasheet, там есть полезная для меня информация, буду разбираться, спасибо

  12. Подскажите пожалуйста, что и как настраивать если надо проводить измерения напряжения по 6 каналам одновременно? Если мне надо делать измерения по всем 6 каналам одновременно по переполнению таймера, то как я пойму результат измерения какого канала я забираю из регистра данных? Регистр то один, а каналов несколько. Или так вообще не может быть, и надо перед каждым измерением перенастраивать АЦП на измерение с другого канала по очереди?

    • Вроде там есть режим, предусмотренный аппаратно, в котором опрашиваются несколько каналов и результаты будут каждый в своем регистре.

  13. Микроконтроллер STM32L151CBT6. Нужно сделать программу обслуживания АЦП. Канал один всего используется, но начало преобразования должно происходить при нажатии на кнопку (к одному из выводов ее цепляем), а конец по истечению 3 секунд, то есть таймер нужен, как понимаю. Можете подсказать, пожалуйста, как это осуществить? Желательно с примером кода.

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *