"Бортовой компьютер своими руками" или "Arduino для "чайника""

Автор Yaroslav, 16 Окт. 2017 в 19:30

« назад - далее »

0 Пользователи и 1 гость просматривают эту тему.

Yaroslav

Хочу немного усреднить длину импульса, не получается. Хотел добавить в while и делить на Count но выдает билиберду.
Как правильно усреднить tImpuls что бы выводилось 5 раз в секунду как и остальные показания?
// Время обновления показаний
#define PERIOD  200

// Аналоговые входы
#define PIN_VOLT A0
#define PIN_AMP A1


const float VRef = 5.0149;                            // Опорное напряжение 5в
const float Divider = (100.0 + 5.1) / 5.1;            // Коэффициент делителя (R1 + R2) / R2.
const float Shunt = 1.7;                              // Сопротивление шунта.
float InVolt, Volt, InAmp, Amp, Div, Watt, WtP, WtH, Speed;
unsigned long tImpuls = 0;

void setup() {
  Serial.begin(9600);
  attachInterrupt(0, impuls, RISING); //Внешние прерывания по фронту импульса на входе D2 и вызов функции "impuls"
 
  // Инициализация дисплея
  lcd.begin (16, 2);
  lcd.clear();
  lcd.print("V=");
  lcd.setCursor (9, 0);
  lcd.print("A=");
  lcd.setCursor (0, 1);
  lcd.print("W=");
  lcd.setCursor (9, 1);
  lcd.print("WH=");
}

void impuls(){
  unsigned long tImpuls2 = 0;
  tImpuls = micros() - tImpuls2;
  tImpuls2 = micros();
}

void loop() {
  // Чтение из портов с усреднением
  unsigned long ReadTime = millis();
  int Count = 0;
  InVolt = 0;
  InAmp = 0;
  while ((millis() - ReadTime) < PERIOD) {
    analogReference(DEFAULT);
    InVolt += analogRead(PIN_VOLT);
    analogReference(INTERNAL);
    InAmp += analogRead(PIN_AMP);
    Count++;
  }
   InVolt = InVolt / Count;
   InAmp = InAmp / Count;
   
  // Обработка данных
  Speed = ((1000000 / tImpuls) / 23) * 1.73 * 60 / 1000;
  Volt = InVolt * Divider * VRef / 1023;
  Amp = (InAmp * 1.1 / 1023 / Shunt) * 1000;
  Watt = Volt * Amp;
  WtP += Watt;
  WtH = WtP /(1000 / PERIOD) / 3600;
 
  // Вывод данных
  lcd.setCursor (2, 0);
  lcd.print(Volt, 2);
  lcd.setCursor (11, 0);
  lcd.print(Amp, 2);
  lcd.setCursor (2, 1);
  lcd.print(Watt, 0);
  lcd.setCursor (12, 1);
  lcd.print(WtH);

  Serial.println(Speed);
}
Строю лигерад

TRO

#55
[user]Yaroslav[/user], Делайте асинхронно, не надо пересчитывать с каждым приходом импульса, чтобы проц не перегружать. Просто записывайте время между импульсами в отдельную себе переменную, обновляя ее с приходом каждого импульса по прерыванию от него. А потом в общем прерывнии (когда все пересчитываете, я так понял 5 раз в секунду) читайте из переменной и делайте пересчет, я не думаю что за секунду или её пятую часть скорость значительно изменится.

Лучше время не усреднять, а увеличить точность подсчета времени между импульсами (частоту на счетный таймер поднять). Но если хочется усреднять, то плавающее среднее в помощь. Берете 10 переменных, и пишите в них считываемое значение по очереди, результат каждый раз складываете и делите на 10 (я не делю, я запятую при выводе на экран передвгаю). Можно еще фильт кальмана посмотреть, что бы на количестве переменных съэкономить.

Wahoo 2012 29er, +собран складной двухосис на раме"Land Rover" 69er с эл. мотором, и и МОНОКОЛЕСО

Yaroslav

Залил прошивку в ардуино. Выяснилось пару проблем.
1. Перед измерением напряжение с шунта опорное не переключается на INTERNAL 1,1в меряет относительно 5в. Я что то не так делаю, или это и не должно работать?(в протеусе работало)
2. Так как отдельного генератора не имею, нагуглил простенький код генератора импульсов, шимлю 9 выходом на 2 вход, скорость показывает, но циклично проскакивают заниженные на пару км измерения. Не пойму из за чего это может быть?
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x3f,16,2);


// Время обновления показаний
#define PERIOD  200

// Аналоговые входы
#define PIN_VOLT A0
#define PIN_AMP A2


const float VRef = 4.94;                             // Опорное напряжение 5в
const float IntRef = 1.10;                            //Калибровка внутреннего опорного напряжения 1.10в.
const float Divider = (100.0 + 5.1) / 5.1;            // Коэффициент делителя (R1 + R2) / R2.
const float Shunt = 1.7;                              // Сопротивление шунта.
const byte Polus = 23;                                // Количество пар полюсов МК
const float DR = 1.73;                                // Длина колеса 2*P*R м.
float InVolt, Volt, InAmp, Amp, Div, Watt, WtP, WtH, Speed;
unsigned long tImpuls = 0;
unsigned long tImpuls2 = 0;


void setup() {                 
  Serial.begin(9600);
  attachInterrupt(0, impuls, RISING); //внешние прерывания по фронту импульса на входе D2 и вызов функции "impuls"
 
  // Инициализация дисплея
  lcd.init();
  lcd.backlight();
  lcd.clear();
  lcd.print("V=");
  lcd.setCursor (9, 0);
  lcd.print("A=");
  lcd.setCursor (0, 1);
  lcd.print("W=");
  lcd.setCursor (9, 1);
  lcd.print("WH=");

  //генератор
  pinMode(9,OUTPUT);
  TCCR1A=(1<<COM1A1)|(1<<WGM11);
  TCCR1B=(1<<WGM13)|(1<<WGM12)|(1<<CS10);
  OCR1A=0; ICR1=0;
}

void impuls(){
  tImpuls = micros() - tImpuls2;
  tImpuls2 = micros();
}

void loop() {
  //генератор
  ICR1= (F_CPU/13800)-1;
  OCR1A=  ((float) 0.625 / 0.0625 ) -1  ;
   
  // Чтение из порта с усреднением
  unsigned long ReadTime = millis();
  unsigned long ImpulsRead = 0;
  int Count = 0;
  InVolt = 0;
  InAmp = 0;
  while ((millis() - ReadTime) < PERIOD) {
    analogReference(DEFAULT);
    InVolt += analogRead(PIN_VOLT);
    analogReference(INTERNAL);
    InAmp += analogRead(PIN_AMP);
    Count++;
  }
   InVolt = InVolt / Count;
   InAmp = InAmp / Count;

   if (ReadTime >= PERIOD){
    ImpulsRead = tImpuls;
}
  // Обработка данных
  if (tImpuls > 0){
    Speed = ((1000000 / ImpulsRead) / Polus) * DR * 60 / 1000;
}
  else{
    Speed = 0;
  }
 
  Volt = InVolt * Divider * VRef / 1023;
  Amp = ((InAmp * 1000) * IntRef) / 1023 / Shunt;
  Watt = Volt * Amp;
  WtP += Watt;
  WtH = WtP /(1000 / PERIOD) / 3600;
 
  // Вывод данных
  lcd.setCursor (2, 0);
  lcd.print(Volt, 2);
  lcd.setCursor (11, 0);
  lcd.print(Amp);
  lcd.setCursor (2, 1);
  lcd.print(Watt, 0);
  lcd.setCursor (12, 1);
  lcd.print(WtH);

  //lcd.setCursor (2, 0);
  //lcd.print(Speed, 2);
 

  Serial.println(Speed);
}
Строю лигерад

TRO

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

Wahoo 2012 29er, +собран складной двухосис на раме"Land Rover" 69er с эл. мотором, и и МОНОКОЛЕСО

Yaroslav

[user]TRO[/user], можете показать кусок кода для примера?
Строю лигерад

FobOrgan

На моём опорное внешнее, ну как внешнее, на самой ардуине есть стабилизатор на 3.3В. Есть ардуино нано c microUSB разъёмом, на ней стоит стабилизатор LM1117он очень стабилен по температуре. Замораживал - подогревал, уход напряжения гораздо меньше 1%. На других arduino nano, которые с miniUSB стоит другой стабилизатор - заметно похуже.
Используя генератор для имитации датчика скорости вы себя лишаете прелестей дребезга контактов, например герконов, не у всех же ДД двигатели, редукторники тоже не редкость и нужен внешний датчик с 1 магнитом на оборот, что кстати тоже даёт неудобства, т.к. на скорости 5км/ч от этого датчика приходит 1 сигнал в 1.5сек. Если осреднять по 10 значениям в лоб, то в таких условиях при резком замедлении можно ещё достаточно долго будет любоваться огромной скоростью спидометра. Мой алгоритм туп, он если видит резкое снижение скорости, то обнуляет значения где скорость была высокой и начинает считать только по вновь появляющимся данным. При остановке соответственно ждёт секунды 2.5-3 и потом весь массив зануляет - остановка.
Для осреднения значений лучше сразу сделать класс в котором можно выбирать сколько значений будет участвовать, т.к. прыгают они по разному, а излишнее осреднение тормозит реальность происходящего.
Езжу на 2хQ100/1200Вт/14Sx18Ач Li-ion уже 33300км за 9 лет

Yaroslav

#60
[user]FobOrgan[/user], приедет ads1115 проблем с опорным напряжением вообще не будет, там у него есть свой опорник. Это я временно на входах ардуино тренируюсь.
По поводу геркона, у меня дд, плюс у финика есть выход под СА, раз есть такие ресурсы, работаю с ними. Ардуино в первый раз увидел недели 2 назад, так что все что делаю приходится изучать на ходу, раньше с таким никогда не сталкивался. И задача не стоит создать универсальный прибор который подойдет всем, делаю под себя, а если кто захочет повторить, сможет допилить как ему захочется)
Строю лигерад

TRO

#61
Цитата: Yaroslav от 23 Окт. 2017 в 18:12
[user]TRO[/user], можете показать кусок кода для примера?
Я на бейсике пишу, скетчи выпекать не умею.
Вот на вскидку пример подпрограммы опроса АЦП.
Цитировать _adc1:
'-------------------------------------------------------------------------------
    Waitms 16
Start Adc
    W = Getadc(0 , A)
    Waitms 1
    W = Getadc(0 , A)
   For I = 1 To K
   Waitus 250
   W = W + Getadc(0 , A)
   Next
Stop Adc
'-------------------------------------------------------------------------------
  Return
Конфигурацию АЦП и опоры задаю перед вызовом этой подпрограммы.
Так же перед вызовом подпрограммы выбирается доп. конфиг (типа внутреннего усиления, переменная "А") и сколько раз (задается переменной "К") опросить АЦП и просуммировать.
Видно что первый опрос холостой, потом пауза в 1 миллисекунду, потом первый опрос, и цикл дополнительных опросов с паузами и суммированием.
А плавающее среднее из десяти переменных делаю уже в основной программе.
Мой код вам в лоб все равно не подойдет, ибо язык другой, тут главное алгоритм перенять, и взять то что вам подойдет.

Wahoo 2012 29er, +собран складной двухосис на раме"Land Rover" 69er с эл. мотором, и и МОНОКОЛЕСО

Yaroslav

В бейсике ни грамма не понимаю) но вижу что там можно прочсто "wait", в аруино если написать "delay" то проц останавливается и в это время ничего не делает, ждет :facepalm:, приходится засекать и отнимать милисекунды   :ireful:
А усреднение что в скетче очень даже не плохо работает, кручу на железе, цифры не прыгают, изменять не буду.
Строю лигерад

edw123

Цитата: Yaroslav от 25 Окт. 2017 в 23:34
В бейсике ни грамма не понимаю) но вижу что там можно прочсто "wait", в аруино если написать "delay" то проц останавливается и в это время ничего не делает, ждет :facepalm:, приходится засекать и отнимать милисекунды   :ireful:
А усреднение что в скетче очень даже не плохо работает, кручу на железе, цифры не прыгают, изменять не буду.
Если [user]TRO[/user] пишет тоже для Ардуино, то от языка не должно зависеть, кмк. Разве что какой-нибудь очень крутой компилятор, который wait переделывает через прерывание.

TRO

Цитата: Yaroslav от 25 Окт. 2017 в 23:34
В бейсике ни грамма не понимаю) но вижу что там можно прочсто "wait", в аруино если написать "delay" то проц останавливается и в это время ничего не делает, ждет :facepalm:, приходится засекать и отнимать милисекунды   :ireful:....
А не надо основной цикл гонять по кругу, настройте таймер и вызывайте этот цикл по прерыванию, и вам будет фиолетово на время выполнения кода, главное что бы все успевало посчитатся за время этого цикла. И вообще все времяизмерительное должно быть по прерываниям, в идеале от внешних часов реального времени.

Wahoo 2012 29er, +собран складной двухосис на раме"Land Rover" 69er с эл. мотором, и и МОНОКОЛЕСО

i

Для привязки действий ко времени я использую "машину времени" - это набор программных таймеров, работающих от одного аппаратного.
Вот например как у меня написан цикл мигания светодиодом:
code Migni \ пример переодичной работы
    begin
        ldiW X,1 sek 2/ rcall Delay:
        if_b Red \_ Red else _/ Red then
    again
    c;

в ассемблере это выглядит так:
Migni:      LDI   R27,0
            LDI   R26,8
            RCALL Delay:
            SBIS  PORTA,4
            RJMP  m93
            CBI   PORTA,4
            RJMP  m94
m93:        SBI   PORTA,4
m94:        RJMP  Migni


Хотя это бесконечный цикл, отдельная подзадача, он не мешает выполнению основной программы.
Фокус в подпрограмме Delay: Она получает не только  время задержки (через регистр Х), но и адрес возврата (через стек). Эти данные она сохраняет в памяти и возвращает управление, но не туда откуда её вызвали, а основному циклу.
Основной цикл проверяет аппаратный таймер и когда тот сработает, вызывает подпрограмму которая пробегается по программным таймерам и уменьшает на единицу их счетчики. Если у кого-то счетчик стал нулевым, то делается переход по адресу сохраненному в этом таймере.
В рассматриваемом примере, код после rcall Delay: будет выполнятся каждую полусекунду. При этом на полсекунды повиснет только эта подзадача, а остальные продолжат выполнение.
Не знаю, как это можно сделать на бейсике или скетчах, но в Си подобное реализуется через callback.

TRO

Цитата: i от 28 Окт. 2017 в 13:16
в ассемблере это выглядит так:
....
Не знаю, как это можно сделать на бейсике или скетчах, но в Си подобное реализуется через callback.
Настоящий програмист может писать код в любом языке программирования.... на ассемблере. (старый жизненный прикол)

Wahoo 2012 29er, +собран складной двухосис на раме"Land Rover" 69er с эл. мотором, и и МОНОКОЛЕСО

edw123

Цитата: i от 28 Окт. 2017 в 13:16
Для привязки действий ко времени я использую "машину времени" - это набор программных таймеров, работающих от одного аппаратного.
...
Основной цикл проверяет аппаратный таймер и когда тот сработает, вызывает подпрограмму которая пробегается по программным таймерам и уменьшает на единицу их счетчики.
А если в основном коде ожидание события/ввода или длинный вывод происходит?

TRO

[user]edw123[/user], события ввода должны крутится по своим прерываниям, а подпрограмма их опроса должна складывать данные по полочкам и выставлять флаги, что мол событие лучилось и на полках есть данные нуждается в обработке. А длинный вывод, можно всегда разбить на много коротких. В общем есть богатая культура программирования по правильному написанию кода, куча литературы. Очень полезно пользоваться чужими  изящными и оптимальными решениями, чем городить кучу не оптимального кода.

Еще Петя Нортон писал, что если хотите научится программировать, читайте чужие программы.

Wahoo 2012 29er, +собран складной двухосис на раме"Land Rover" 69er с эл. мотором, и и МОНОКОЛЕСО

i

У меня в программе есть два очень критичных прерывания, от скорости реагирования на них зависит работоспособность всей программы. Поэтому я стараюсь избегать навешивания иных прерываний. В крайнем случае разрешаю прерывать некритичное прерывание.
Любое событие сначала поднимает аппаратный флаг, а потом уже срабатывает прерывание, если это разрешено. Все флаги можно гасить как аппаратно (при переходе на вектор прерывания), так и программно (обычно записав в него 1).
При длинном выводе я в основном цикле читаю флаг передатчика, если флаг поднялся - кидаю очередной байт в аппаратный буфер, гашу флаг и ухожу по другим делам, аппарат уже далее сам пыхтит, выдавливая байт наружу.
Так же и по событиям, как только поднимается флаг - обрабатываю.
В общем основной цикл только тем и занимается, что проверяет разные флаги и реагирует на них.

edw123

Цитата: TRO от 28 Окт. 2017 в 16:55... В общем есть богатая культура программирования по правильному написанию кода, куча литературы. ...
Конечно есть, но и люди есть, для которых это не профессия, а так, пока чай остывает... Я с сотню скетчиков разных понаписал, но до сложностей некогда и поэтому первое, что мне бы точно наделало проблем - вывод данных в терминал. Конечно - это не высшее образование в программировании м-к, но я и не претендую, а программки работают, меряют, моргают... И как раз по теме"чайниковости". :pardon:

mr.Dream

Начали за здравие, кончили за упокой))))
Нету в "техзадании" сильно привязаных ко времени выполнения задач.

В произвльном порядке в цикле основной программы, можно даже раз в несколько проходов, дахо ть раз в секунду, делаем измерения, умножаем на время, прошедшее с предыдущего измерения, складываем в какую то переменную, при ее переполнении увеличиваем на единицу еще одну переменную, а при ее - еще следующую, а "остатки от деления"  оставляем. Таким образом, будем иметь возможность считать 32-48битные значения. Считаем точно, а выводим на экран с "округлением", то есть читаем только переменные с более "крупными" записями. Это чтобы ватт*часы считать.

а с километражом там еще проще, считать "тики" в прерываниях, хоть с ДХ мотора, хоть с геркона колеса и умножать на "длину окружности колеса для одного тика". Параллельно считаем период между тиками для вывода текущей скорости.