STM32 I/O модуль на MODBUS и немного АЦП (Превращение брюк)

Хочу немного затронуть тему АЦП и заодно проапгрейдить софт для модуля I/O — до аналоговых входов и чуть чуть затронуть тему подключения термисторов.
DISCLAIMER: Я не претендую на описание принципов работы АЦП в STM32 просто поясню, что сделал.
Этот модуль we.easyelectronics.ru/STM32/stm32-pervaya-osmyslennaya-konstrukciya-i-o-modul-na-modbus-modbus-chast-2.html был задуман так, что входы могут быть сконфигурированы как дискретные так и как аналоговые (выходы в общем то тоже).



Как обычно воспользуемся стандартными библиотеками. При ближайшем рассмотрении все примеры которые приложены с CooCox как обычно были написаны через жопу или использовали непрерывный режим преобразования на одном канале и поэтому пришлось поковыряться пока крохи информации были объединены. У АЦП на STM32 есть несколько режимов работы я честно признаюсь их еще толком не скурил (надеюсь в ближыйшее время восполнить этот пробел). Но меня пока волнует собственно несколько больше чисто экспериментальный аспект и работа АЦП вообще, поэтому мы воспользуемся одним из самых простых методов — мы будем тупо выборочно оцифровывать каждый вход.
Для начала надо работы с АЦП не забыть подать клок на АЦП — RCC_ADCCLKConfig (RCC_PCLK2_Div4) -максимум 12 МГц (щас 6 т.к. делим на 4)
Правильно инициализировать пины.

    //клок на порт A
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

/* Сконфигурируем  порты для  приема аналогового сигнала A0,A1,A2,A3     */
        GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_0|GPIO_Pin_1||GPIO_Pin_2|GPIO_Pin_3;
        GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AIN;;
        GPIO_Init(GPIOA, &GPIO_InitStructure);

Инициализировать АЦП и еще немаловажный момент АЦП калибруется перед использованием (не совсем правда ясно куда эта калибровка пишется т.к. сначала она сбрасывается).

void Setup_ADC(void)
{
	ADC_InitTypeDef ADC_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

	// ADC1 структура инициализации 
	    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
	    ADC_InitStructure.ADC_ScanConvMode = DISABLE; //Мы сами будем все переключать
	    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//Когда нам это будет надо
	    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
	    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
	    ADC_InitStructure.ADC_NbrOfChannel = 4;//у нас 4 канала
	    ADC_Init(ADC1, &ADC_InitStructure);

	    /* Enable ADC1 */
	    ADC_Cmd(ADC1, ENABLE);
              
            //Калибровка
	    /* Enable ADC1 reset calibration register */
	    ADC_ResetCalibration(ADC1);
	    /* Check the end of ADC1 reset calibration register */
	    while (ADC_GetResetCalibrationStatus(ADC1));

	    /* Start ADC1 calibration */
	    ADC_StartCalibration(ADC1);
	    /* Check the end of ADC1 calibration */
	    while (ADC_GetCalibrationStatus(ADC1));
            
}

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

//reading selected channel
uint16_t Read_ADC1(uint8_t channel)
{
  //номер АЦП, канал и время преобразования
  ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_41Cycles5);
  // Пуск преобразования
  ADC_SoftwareStartConvCmd(ADC1, ENABLE);
  // ждем
  while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
  // Результат
  return ADC_GetConversionValue(ADC1);
}

Но результат нас не совсем устроит т.к. это будут коды АЦП от 0 до 4095 т.к. АЦП на STM32 -12 битный, а нам бы хотелось иметь что-то осмысленное и желательно получать температуру с термодатчика например термистора NTC10K3, такой должен подойти — www.chipdip.ru/product/b57164-k103-j.aspx. Почему термистор спросит пытливый читатель? Они дешевы, надежны, распространены и много стандартных датчиков для инженерных систем их использует. А еще их характеристика позволяет полноценно использовать весь диапазон АЦП без сложных согласующих схем. Все бы хорошо, но у термисторов нелинейная характеристика и редко в реальной жизни встретишь коэффициенты, обычно пишут таблицы соответствия сопротивления температуре например у 10K3 при 25 С сопротивление равно 10000 Ом. Пример такой таблицы www.bapihvac.com/content/uploads/2011/07/Thermistor_10K-3.pdf. Входы в модуле были сконструированы так, что имеют подтяжку 10К и если мы например подключим какое то сопротивление между подтяжкой и землей то зная величину подтяжки и код АЦП мы сможем вычислить какое сопротивление у нашего датчика, а затем и определить температуру по забитым табличным значениям — если мне не изменяет память методом «кусочно линейной апроксимации» (если не парю вроде этот метод).
float RES(int adc,int Rup) — эта функция считает сопротивление, adc -код АЦП, Rup — сопротивление подтяжки в омах
float Temp(long int r) — это ищет ближайший к этому сопротивлению участок таблицы и определяет по нему температуру.
Для того чтобы получить некоторую точность из 16 бит регистра умножим значение на 100, при получении мастером его нужно будет разделить на 100 и получим сотые доли.

    	 if(run_loop)
      {
    	 res_table.regs[1]= Temp(RES(Read_ADC1(0),10000))*100;
    	 res_table.regs[4]= Temp(RES(Read_ADC1(1),10000))*100;
    	 res_table.regs[3]= Temp(RES(Read_ADC1(2),10000))*100;
    	 res_table.regs[2]= Temp(RES(Read_ADC1(3),10000))*100;

    	 if(res_table.regs[5]) //a8
    	 GPIO_WriteBit  ( GPIOA,GPIO_Pin_8,Bit_SET);
    	 else
    	 GPIO_WriteBit  ( GPIOA,GPIO_Pin_8,Bit_RESET);

    	 if(res_table.regs[6])//a9
    	 GPIO_WriteBit  ( GPIOA,GPIO_Pin_9,Bit_SET);
    	 else
    	 GPIO_WriteBit  ( GPIOA,GPIO_Pin_9,Bit_RESET);

    	 if(res_table.regs[7])//a10
    	 GPIO_WriteBit  ( GPIOA,GPIO_Pin_10,Bit_SET);
    	 else
    	 GPIO_WriteBit  ( GPIOA,GPIO_Pin_10,Bit_RESET);

    	 if(res_table.regs[8])//a11
    	 GPIO_WriteBit  ( GPIOA,GPIO_Pin_11,Bit_SET);
    	 else
    	 GPIO_WriteBit  ( GPIOA,GPIO_Pin_11,Bit_RESET);

    	 run_loop=0;
      }

Я специально не добавлял никакой фильтрации т.к. мне интерсно посмотреть на то как будут скакать показания, для реального же применения желательно применить хотя бы простой фильтр типа Y=(Y(t-1)+Y(t))/2. Полный проект для CooCox IDE приложен.
Не буду больше мусолить в общем если что пишите…
  • 0
  • 02 сентября 2011, 23:39
  • GYUR22
  • 1
Файлы в топике: io_analog_lite.zip

Комментарии (8)

RSS свернуть / развернуть
то есть этот модуль умеет: собирать данные по АЦП и отправлять их по MODBUS/RTU через RS-485? А на дисковери программа заработает?
0
да с небольшими доработками — проц другой
0
0
попробую повторить! хороший материал!
0
Спасибо автору за отличную статью!
0
не совсем правда ясно куда эта калибровка пишется т.к. сначала она сбрасывается
Калибровочное значение сохраняется в регистре данных ADC_DR. Нужно лишь дождаться ее завершения — после аппаратного сброса бита CAL в ADC_CR2. Возможность калибровки есть в семействе f1xx, в f4xx ее нет. Кстати помимо отличий с калибровкой у них есть некоторые отличия в части способа запуска преобразований… В общем иногда полезно и в ДШ глянуть.
0
  • avatar
  • Wypuk
  • 15 августа 2014, 19:17
RCC_ADCCLKConfig (RCC_PCLK2_Div4) -максимум 12 МГц (щас 6 т.к. делим на 4)

Небольшая описка, поправьте пожалуйста: максимум 24 МГц.
0
У сотки ацп макс. 12мгц
стр.81 даташита на сотку
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.