Обсуждаем контроллеры компании Atmel.
Ответить

Обработка значений с АЦП Мега8 не работает

Ср дек 07, 2022 12:34:13

Всем привет! Делаю вольтамперметр на 8ой меге для лабораторника.
Но возникла загвоздка в преобразовании значений АЦП в вольты:
Код:
uint8_t messCount = 0;      // счетчик кол-во измерений АЦП
uint8_t analogPin = 3;      // номер входа АЦП
uint8_t recMUX = 0;         // переменная для сохранения исходного значения регистра ADMUX
uint16_t voltADCTmp = 0;    // вр. перем. для получения ср. ариф. АЦП

uint32_t voltage = 0;       // напряжение
uint32_t amperage = 0;      // ток
uint32_t wattage = 0;       // мощность
uint16_t uRef = 26300;      // опорное напряжение для мега8а (внутренний ИОН) x10000
uint8_t rSh = 10;           // сопротивление токового шунта x100
uint8_t kUr = 83;           // отношение напряжений входного делителя x1000

uint16_t voltADCRes[3];

voltage = voltADCRes[2] * uRef / 1024 * 100 / kUr;  // получаем напряжение типа XX.XXX


Последняя строка. voltage получается где-то 40-50. а должен получатся пятизначный результат (максимум). Если же просто выводить значение АЦП, то все в норме - до 1023. Считал на калькуляторе (режим programmer), там значения в норме. Вроде размерность переменных взял, чтобы переполнения не было...
С током тоже самое - 2 других входа АЦП: amperage = voltADCRes[0] * uRef / 1024 / rSh; // ток берем до ОУ

Re: Обработка значений с АЦП Мега8 не работает

Ср дек 07, 2022 12:49:11

Вроде размерность переменных взял, чтобы переполнения не было...

Точно? А мне кажется, что переполнение возникает уже на перемножении двух 16-битных чисел voltADCRes и uRef.
/ 1024 * 100

Лучше наоборот, сначала умножить, потом делить.
voltage = voltADCRes[2] * uRef / 1024 * 100 / kUr; // получаем напряжение типа XX.XXX

Попробуйте привести к 32-битному числу на время вычислений:
Код:
voltage = (uint32_t)voltADCRes[2] * uRef * 100 / 1024 / kUr;

Re: Обработка значений с АЦП Мега8 не работает

Ср дек 07, 2022 12:51:54

а еще можно разбить на этапы, а не всё в куче, и посмотреть в режиме отладки, на каком происходит бяка.

Re: Обработка значений с АЦП Мега8 не работает

Ср дек 07, 2022 13:39:58

Приведение к типу 32 не помогает - все тоже. Да, разобью на куски для отладки.
Проверил...

voltage = voltADCRes[2] * uRef;

Уже вот это выражение выдает 0
Последний раз редактировалось mir0tv0rec Ср дек 07, 2022 14:37:11, всего редактировалось 1 раз.

Re: Обработка значений с АЦП Мега8 не работает

Ср дек 07, 2022 14:28:40

uint16_t uRef = 26300; // опорное напряжение для мега8а (внутренний ИОН) x10000

Внутренний ИОН у меги8 равен 2,56 В… а у Вас как я понял 2,63 В.
Ну и хотелось бы точно знать при каком входном значении какой результат, чтобы понять где ошибка…
Ещё иногда хорошо бы проверить какое значение получается у АЦП без преобразования при определённом входном напряжении…

Ну и формула ИМХО лучше когда выглядит так: ADC * Vtef / 10240… как заметили делим не на 1024, а на 10240…
Vtef = 2560
Последний раз редактировалось VNS Ср дек 07, 2022 14:50:19, всего редактировалось 1 раз.

Re: Обработка значений с АЦП Мега8 не работает

Ср дек 07, 2022 14:41:33

Значения самого АЦП в норме, от 0 до 1023 по всем трем каналам, если выводить только их (3 элемента массива), регулируя положение подстроечника.
Внутренний ИОН, я измерил на AREF выводе, но сейчас все у меня на макетке. В данный момент это не принципиально. Проблема именно в математике.

Re: Обработка значений с АЦП Мега8 не работает

Ср дек 07, 2022 14:55:45

Ну если с АЦП всё в порядке, то из конкретного результата можно понять где ошибка в математике… задайте в симуляторе конкретное значение ADC и посмотрите, где ошибка…

Re: Обработка значений с АЦП Мега8 не работает

Ср дек 07, 2022 15:03:46

для тест, с GCC:
voltADCRes[2] = 512;

при
voltage = (uint32_t)voltADCRes[2] * uRef / 1024 * 100 / kUr;
36

при
voltage = (uint32_t)voltADCRes[2] * uRef / 1024 * 100 / kUr;
15843

можете для теста преобразовать в всех местах:
voltage = (uint32_t)voltADCRes[2] * (uint32_t)uRef / 1024UL * 100UL / (uint32_t)kUr;
мой результат: 15843

Re: Обработка значений с АЦП Мега8 не работает

Ср дек 07, 2022 15:14:46

Вывел пошагово, все в норме, видимо нужно на несколько строк разбить или еще буду пробовать.
Код:
voltage = voltADCRes[2];
       
amperage = voltage * uRef;
       
wattage =  amperage / 1024 * 100 / kUr;


P.S. Если объединить действия 1ой и 2ой строчек в одну, тут косяк и вылезает...
Последний раз редактировалось mir0tv0rec Ср дек 07, 2022 15:30:33, всего редактировалось 1 раз.

Re: Обработка значений с АЦП Мега8 не работает

Ср дек 07, 2022 15:28:58

mir0tv0rec писал(а):uint32_t voltage = 0;       // напряжение

mir0tv0rec писал(а):получаем напряжение типа XX.XXX

Это как дробное число пихается в целочисленный тип?

Re: Обработка значений с АЦП Мега8 не работает

Ср дек 07, 2022 15:34:57

Только целые числа 5 знаков. Т.е. точки там нет конечно, 35655, точку буду добавлять при выводе.

Объясните, почему так не работает: voltage = voltADCRes[2] * uRef;
а так работает:
voltage = voltADCRes[2];
voltage = voltage * uRef;

Re: Обработка значений с АЦП Мега8 не работает

Ср дек 07, 2022 16:11:32

mir0tv0rec писал(а):Объясните, почему так не работает: voltage = voltADCRes[2] * uRef;
а так работает:
voltage = voltADCRes[2];
voltage = voltage * uRef;


Что ж, тогда согласен с
veso74 писал(а):можете для теста преобразовать в всех местах:
voltage = (uint32_t)voltADCRes[2] * (uint32_t)uRef / 1024UL * 100UL / (uint32_t)kUr;

Re: Обработка значений с АЦП Мега8 не работает

Ср дек 07, 2022 19:38:54

Как то странно вы получаете результат. Для ADC должен быть uint16_t, зачем там массив? Затем расширяете разрядность - множитель uint32_t или UL.

Re: Обработка значений с АЦП Мега8 не работает

Чт дек 08, 2022 00:09:08

Я сейчас занят. Поднял из одного проекта. Проект не выстрелил, клиент был сельский, дорого ему было. Выдаю из наработок. Пока сами разбирайтесь. Занят. Помню, что были схожие проблемы, как у ТС, с математикой на С. Пришлось применить костыли. Результат выдал через long. А там через симулятор вычислил соответствие результата, чтобы вывести на дисплей.

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

В проекте было измерение переменного тока (трансформаторы тока) и измерение переменного напряжения. Среднеквадратичное значение.

На тот момент я остановился на коэфициентах.

В общем, разбирайтесь, пробуйте. Работало, оставалось только допилить, чтобы более-менее соответствовало показаниям мультиметра. Особой точности в проекте не требовалось, так что...

Upd. Грубо. Суть такова. У avr плохо с float. Все упирается в ресурсы. Память, время исполнения. Переменку нужно успеть обработать. 10 мс. Поэтому нужно было успевать по максимуму работать с целочисленными значениями.

Схема измерения переменного тока была следующей. ИОН на 2,5 V. Один конец вторичной катушки на выход ИОН, второй конец через резистор 10 кОм на вход АЦП.

Схема измерения переменного напряжения. Делители напряжения. И резистор на выход ИОН для создания средней точки. Виртуальный нуль, что ли, не помню, как правильно называется эта схема.

Описание работы вкратце. Так как частота сети стабильная, детектора перехода через нуль нет. Таймер и АЦП настроены так, чтобы за 10 мс было 100 измерений. Для большей точности нужно сделать замеры нескольких периодов. Результаты измерений суммируются, затем вычисляется среднеквадратичное значение.

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

Спойлер
Код:
//==================
#ifndef METER_H
#define METER_H

#include "meter.h"

#include "main_def_func.h"
//==================

//==================
#define METER_TCNT         TCNT0
#define METER_TIMSK        TIMSK
#define METER_OCIE         OCIE0
#define METER_OCR          OCR0
#define METER_TCCR         TCCR0
#define METER_CS0          CS00
#define METER_CS1          CS01
#define METER_CS2          CS02

#define METER_OCR_VAL      25
//==================

//==================
#define V_REF   5.00
#define ADC_RES 1023.0
#define K_1     1.089
#define K_2     1.061
#define K_3     1.033
//==================

//==================
#define SAMPLES 400
//==================

//==================
enum
{
   PHASE_A,   //фаза A
   PHASE_B,   //фаза B
   PHASE_C,   //фаза C
   PHASE_Usm,
};
//==================

//==================
void meter_timer_init (void);
void meter_timer_off (void);

void meter_init (void);
void meter_off (void);

u16 read_adc (u08 channel);

void set_proc_meter_on (void);
void set_proc_meter_off (void);
void proc_meter (void);

u32 calc_adc_result (u32 sqr_summ);

bool get_measure_complete (void);
//==================

#endif


Спойлер
Код:
//==================
#include "meter.h"
//==================

//==================
bool adc_complete;

static u08 phase;
static u16 sample;

static u32 phase_a_summ_sqr;
static u32 phase_b_summ_sqr;
static u32 phase_c_summ_sqr;

static bool measure_complete;
//==================

//==================
#pragma vector = TIMER0_COMP_vect
__interrupt void meter_timer_comp (void)
{
   u16 adc = 0;
   u16 temp = 0;

   u32 a = 0;

   METER_OCR += METER_OCR_VAL;

   adc = read_adc (PHASE_Usm);

   temp = adc;

   adc = read_adc (phase);

   if (adc >= temp)
      adc -= temp;
   else
      adc = (temp - adc);

   a = (long) (adc * adc);

   switch (phase)
   {
      case PHASE_A:
         phase_a_summ_sqr += a;
         phase = PHASE_B;
         break;

      case PHASE_B:
         phase_b_summ_sqr += a;
         phase = PHASE_C;
         break;

      case PHASE_C:
         phase_c_summ_sqr += a;
         phase = PHASE_A;

         if (++sample > SAMPLES)
         {
            meter_off ();

            measure_complete = true;
         }
         break;
   }
}
//==================

//==================
#pragma inline = forced
void meter_timer_init (void)
{
   METER_TCNT = 0;
   METER_OCR = METER_OCR_VAL;
   set_bit (METER_TIMSK, METER_OCIE);
   set_bit (SFIOR, PSR10);
   METER_TCCR |= ((1<<METER_CS1) | (1<<METER_CS0));
}
//----------
#pragma inline = forced
void meter_timer_off (void)
{
   METER_TCCR = 0;
   clr_bit (METER_TIMSK, METER_OCIE);
   METER_TCNT = 0;
   METER_OCR = 0;
}
//==================

//==================
//#pragma inline = forced
u16 read_adc (u08 channel)
{
   ADMUX = ((0<<REFS1) | (0<<REFS0) | (0<<ADLAR) | channel);

   set_bit (ADCSRA, ADSC);

   while (1)
   {
      __enable_interrupt ();

      sleep_mode_enable ();

      if (adc_complete)
      {
         adc_complete = false;
         break;
      }
   }

   return ADC;
}
//==================

//==================
#pragma vector = ADC_vect
__interrupt void adc_interrupt_handler (void)
{
   adc_complete = true;
}
//==================

//==================
void meter_init (void)
{
   phase = PHASE_A;

   phase_a_summ_sqr = 0;
   phase_b_summ_sqr = 0;
   phase_c_summ_sqr = 0;

   sample = 0;

   measure_complete = false;

   meter_timer_init ();
}
//----------
#pragma inline = forced
void meter_off (void)
{
//   ADCSRA = 0;
//   ADMUX = 0;

   meter_timer_off ();
}
//==================

//==================
static u08 _proc_meter;

void set_proc_meter_on (void)
{
   ACSR = (1<<ACD);

   ADCSRA = ((1<<ADEN) | (0<<ADSC) | (0<<ADATE) | (1<<ADIF) | (1<<ADIE) | (1<<ADPS2) | (0<<ADPS1) | (0<<ADPS0));

   _proc_meter = 1;
}
//----------
void set_proc_meter_off (void)
{
   meter_off ();

   _proc_meter = 0;
}
//----------
void proc_meter (void)
{
   switch (_proc_meter)
   {
      case 0:
         break;

      case 1:
         meter_init ();
         _proc_meter = 2;
         break;

      case 2:
         if (get_measure_complete ())
         {
            phase = PHASE_A;
            _proc_meter = 3;
         }
         break;

      case 3:
         switch (phase)
         {
            case PHASE_A:
               set_i_ph_a (calc_adc_result (phase_a_summ_sqr));
               phase = PHASE_B;
               break;

            case PHASE_B:
               set_i_ph_b (calc_adc_result (phase_b_summ_sqr));
               phase = PHASE_C;
               break;

            case PHASE_C:
               set_i_ph_c (calc_adc_result (phase_c_summ_sqr));
               Set_Event (EV_ID_MEASURE_I_ABC_COMPLETE, USER_EVENT);
               _proc_meter = 1;
               break;
         }
         break;
   }
}
//==================

//==================
u32 calc_adc_result (u32 sqr_summ)
{
   float a;
   u16 temp;

   sqr_summ /= sample;

   a = sqrt (sqr_summ);
   temp = (int) a;

   a = (float) a * (V_REF / ADC_RES);

   if (temp <= 50)
      a = (float) a * K_1;
   if (temp > 50 && temp < 100)
      a = (float) a * K_2;
   if (temp >= 100)
      a = (float) a * K_3;

   a = a * 10000;
   return (long) a;
}
//==================

//==================
bool get_measure_complete (void)
{
   if (measure_complete == true)
      {
         measure_complete = false;
         return true;
      }
   else
      return false;
}
//==================

Re: Обработка значений с АЦП Мега8 не работает

Чт дек 08, 2022 09:03:56

зачем там массив?

Измеряю по 3м входам АЦП, сохраняю в массив (результаты измерения с нужного входа сохраняются в свою ячейку). Потом беру нужную ячейку и дальше вычисляю - мне так удобнее.
Осталось только калибровку придумать...

Re: Обработка значений с АЦП Мега8 не работает

Чт дек 08, 2022 09:12:00

Да что там думать!)
Код:
#define ADC_RESOLUTION    1024
#define V_REF             955L
#define ADC2MV(adc)       (adc) * V_REF / ADC_RESOLUTION

Re: Обработка значений с АЦП Мега8 не работает

Чт дек 08, 2022 10:33:16

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

Re: Обработка значений с АЦП Мега8 не работает

Чт дек 08, 2022 11:35:36

Мне так удобнее. Можно и без массива, но здесь нужны будут "if" или "switch". Как нужно будет понять. с какого входа, куда данные сохранять...
Код:
void ReadADC()
{
  uint16_t result = ADCL | ADCH << 8;    // Получаем 10-битный результат
 
  if (ADC_read)
  {
    // 10 измерений для исключения скачков
    if (messCount < 10)
    {       
      voltADCTmp += result; // аккумулируем показания АЦП канала
      messCount++;  // инкрементируем счетчик кол-во снятых показаний
    }// end if
    else
    {
      voltADCRes[analogPin-3] = voltADCTmp / messCount; // усредняем результат, присваиваем значения с АЦП ячейкам массива
      voltADCTmp = 0;  // обнуляем 
      messCount = 0;   // обнуляем
      analogPin++;     // Перебираем входные пины по кругу (А3...А5 - их может быть больше)
      if (analogPin > 5)
      {
        voltage = voltADCRes[2];
        voltage = voltage * uRef / 1024 * 100 / kUr;    // получаем напряжение типа XX.XXX
                               
        uint16_t resultTmp = voltage % 1000;  // получаем тысячные
       
        //округляем до сотых
        if (resultTmp % 10 < 5) resultTmp = resultTmp / 10;  // если тысячные меньше 5 - просто делим на 10
        else if (resultTmp < 995) resultTmp = resultTmp / 10 + 1; // если тысяные меньше 945 и тысячные больше 4, округляем сотые в большуюю сторону
        else
        { // если > 995
          resultTmp = 0; // обнуляем сотые
          voltage += 1000;  // увеличиваем целые
        }//end else 
        voltage = voltage / 1000 * 100 + resultTmp; // приводим в виду XXXX (2 знака целые, 2 знака сотые)
       
        // если напряжение после ОУ вышло из диапазона опорного (напряжение на шунте больше ~0.5V)
        if (voltADCRes[1] >= 1023)
        {
          amperage = voltADCRes[0];
          amperage = amperage * uRef / 1024 / rSh;  // ток берем до ОУ
        }//end if 
       
        // если напряжение после ОУ в диапазоне опорного
        else
        {
          amperage = voltADCRes[1];
          amperage = amperage * uRef * 10 / 1024 / kOA / rSh;  // ток берем с выхода ОУ (ку ОУ рассчитываем относительно друго входа без усиления) 
          //amperage = amperage * uRef / 1024 / (voltADCRes[1] / voltADCRes[0]) / rSh;  // ток берем с выхода ОУ (ку ОУ рассчитываем относительно друго входа без усиления)
        }//end else
       
        resultTmp = amperage % 1000;  // получаем тысячные
        //округляем до сотых
        if (resultTmp % 10 < 5) resultTmp = resultTmp / 10;  // если тысячные меньше 5 - просто делим на 10
        else if (resultTmp < 995) resultTmp = resultTmp / 10 + 1; // если тысяные меньше 945 и тысячные больше 4, округляем сотые в большуюю сторону
        else
        { // если > 995
          resultTmp = 0; // обнуляем сотые
          amperage += 1000;  // увеличиваем целые
        }//end else 
        amperage = amperage / 1000 * 100 + resultTmp; // приводим в виду XXXX (2 знака целые, 2 знака сотые)
               
        //if (uSw_1 && uSw_2 > uSw_1 && uSw_3 > uSw_2) SwitchRel(); // не переключаем реле, поку не установлены пороги переключения
       
        analogPin = 3; // Все нужные перебрали...возвращаемся к первому
        resultReady = true;
      }//end if
      ADMUX = recMUX | (analogPin & 0x07);  // Устанавливаем новый вход для преобразования
      ADC_read = false; // Устанавливаем флаг смены входного пина - следующее прерывание пропускаем
    }// end else
  }// end if (ADC_read)
  else ADC_read = true;   // Первый раз пропускаем считывание и устанавливаем флаг на чтение в следующий раз
}// end void ReadADC()

Re: Обработка значений с АЦП Мега8 не работает

Чт дек 08, 2022 11:42:53

mir0tv0rec писал(а):Как нужно будет понять. с какого входа, куда данные сохранять...
каждой переменной дать собственное имя, и тогда массив не нужен будет вообще.

Re: Обработка значений с АЦП Мега8 не работает

Чт дек 08, 2022 11:49:07

mir0tv0rec, выше мой пример. Посмотрите, как у меня сделано. В том примере 4 канала измерений.
Ответить