17 Окт., 2021, 18:45

Если сразу заработало, значит, собрано неправильно...


Вольтамперметр, 7 сегментные индикаторы, SPI

Автор zenon, 29 Сен., 2020, 22:18

« предыдущая - следующая »

zenon

Теперь думаю какой контроллер сегментов ещё поискать для ОА, почему так - белые - общий анод, жёлтые и не дорогие тоже анод.
Цитата: Slabovik от 19 Нояб., 2020, 17:26А на софтверном ты выходы в режим "открытый коллектор" ставишь?
У stm режим Open-drain, вот в него и ставлю.
Цитата: Slabovik от 19 Нояб., 2020, 16:47Видно только, что там не 100 кГц, а раза в полтора больше (это не проблема, конечно).
Вот, хорошо видно: 98 кГц.

Slabovik

Ну, при таком раскладе получается, там не было (см. предыдущие фото) open drain. Ну ладно, сейчас понятнее стало.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

Ещё одно применение - зарядка, точнее "добивка" аккумулятора.
Добавил в разрыв плюса на входе реле навесом, и сделал "качели" до 16.2 вольт для Ca/Ca аккумулятора.
Правда нарвался на грабли, начал уже функции и дополнительные переменные вводить, задержки итд, короче бился пару часов....
Оказалось выключал пин неправильно те так
PORTC |=  (0 << PC0);

ну, как так, основы же, элементарная вещь, делал это не счесть сколько раз, все блинки на этом....!!! :o  :'(

Slabovik

Да, блин, ошибка элементарная - чисто внимательность. Ну, для того и отладчики есть. Порой поражаешься, "как же такой можно было ляпнуть", но... бывает  ;)

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

zenon

Воды долил, плотность никак не подниму... Зарядил до 14.8, плотность меньше 1,2.
Аккуму пол-года 75-ка Аком.

Slabovik

Неприятно, но надо "бодяжить" (правильно - "откорректировать") до нормальной плотности. Зарядкой только сульфатацию пластин сделаешь. Сразу не заметно, но следом пойдёт относительно быстрая деградация (уж такой дорогостоящий опыт проделывал несколько раз, и всё на своём кошельке).

Как вариант, слить этот электролит, купить новый, с типовой плотностью (концентрат серной всё-равно не продают), залить.

зы: типовой малообслуживаемый аккумулятор. Может работать годами без вмешательства, и только при длительном простое (в отключенном состоянии 4-6- месяцев, если на машине с сигналкой - раз в месяц т.к. сигналка потребляет) подзаряжать (именно под-) до 13,8-14.0 вольт. А вот добиваться 100% зарядки - imho фетиш, ни к чему хорошему не приводящий...
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

Это уже глубоко сидит - не доливать электролит...
Ночь простоял на ~100 мА токе.
Если точнее с утра качели 13.5 ... 15.8 током 1А
Измерил сейчас плотность - всё не так плохо мы в синей зоне зоне, плотность поднялась.
Может и обойдётся.

zenon

04 Дек., 2020, 12:32 #207 Последнее редактирование: 04 Дек., 2020, 12:53 от zenon
Нагородил для кнопки и реле включенного в разрыв плюсового входа вольтамперметра такой код:
#define pin0value          ((PINB & (1<<PINB0)) >> PINB0) //uint8_t
#define ledPD7_off()        PORTD |=  (1<<PD7)
#define ledPD7_on()        PORTD &= ~(1<<PD7)
#define ledPD7_toggle()    PIND = _BV(PD7);
#define relayPC0_on()      PORTC |=  (1<<PC0) // реле вкл высоким
#define relayPC0_off()      PORTC &= ~(1<<PC0) // реле выкл низким
#define charge_upper_limit  148
#define charge_lower_limit  135
...
...
...
uint16_t rcounter = 0, bcounter = 0, btout = 0;
char but0state = 0, relay_onoff = 1;

void button0revert (void) {
    if (but0state == 1) {
        but0state = 0;
        btout=300;
        return;
    }
    if (but0state == 0) {
        but0state = 1;
        btout=800;
        return;
    }
}

void button0read (void) {
    if (!(pin0value))
        if(bcounter < 100) bcounter++;
        else button0revert ();
    else
        if(bcounter > 0) bcounter--;
}


...
...
...
//в цикле, где 1000Гц
// go кнопки
            if (btout==0) button0read(); else btout--;
// end кнопки
            if (but0state==0)
            {
// go для зарядаки - добивки аккумулятора
                rcounter++;
                if (rcounter>300) {
                    rcounter=0;
                    if ((V/10) <= charge_lower_limit) relay_onoff=1;
                    if ((V/10) >= charge_upper_limit) relay_onoff=0;
                    if (relay_onoff) ledPD7_on(); else ledPD7_toggle() // если режим зарядки - включим светодиод, иначе режим ожидания - мигаем
                }
                if (relay_onoff==1) {relayPC0_on();  }
                if (relay_onoff==0) {relayPC0_off(); }
// end для зарядаки - добивки аккумулятора
            }
            else {
// обычный режим включаем реле и гасим светодиод
                relayPC0_on();
                ledPD7_off();
            }
...
...
...

Два режим получилось, обычный режим - реле всегда вкл, и режим добивки акк.
Кнопкам задержки выдумывал... может и намудрил, работает нормально.\
(Заряжал второй аккум made in Казахстан "Электра", обычный свинцовый 60Ач, влезло в него почти 50Ач током 2А + добивка до 14.8, плотность набрал хорошо.)
ы. А всё почему - наконец-то минусовая температура, -15 в начале декабря уже редкость, жалко только снега нет вообще, пару недель назад Ростовскую область завалило снегом, мимо нас за 100 км...
Но новый год чувствую опять будет около 0 градусов...

zenon

Пытался понять твою функцию ReadADC - запутался.
Можно как пример для одного/двух буферов?
Вот это PORTD |= 0b00000100 там для чего?
SMCR = Sleep_ADC это вместо set_sleep_mode(SLEEP_MODE_ADC)?
Выписал кусок:
// -- SL import start -----------------------------------------------------------------------------------------------------
#define Sleep_ADC (0<<SM2)|(0<<SM1)|(1<<SM0)|(1<<SE)
#define Sleep_Idle (0<<SM2)|(0<<SM1)|(0<<SM0)|(1<<SE)
#define Sleep_disable (0<<SM2)|(0<<SM1)|(0<<SM0)|(0<<SE)

#define loV_leng 8 // длины накопительных циклических буферов
#define hiV_leng 8 // зависят от того, какое максимальное значение хотим видеть
#define loC_leng 8 // от длины буфера зависит, насколько быстро показания будут перемещаться
#define hiC_leng 10 // при изменении измеряемой величины (как будето инерция)

#define Cell_depth  5 // "глубина" ячейки: сколько значений суммировать в одной ячейке буфера - общая для всех (!)
 // число - это степень при двойке. При Cell_depth =2 суммируются 4 значения, при 3 - 8, при 4 - 16...
 // 6 (64 значения) - это максимум, больше нельзя (будет переполнение и утрата данных)
 // чем больше глубина ячейки, том реже обновление данных на индикаторе
 // т.к. он обновляется только когда все ячейки заполнены "до верху"

#define loV_rshift 3 // количество логических сдвигов вправо в процедуре деления
#define hiV_rshift 1 // Максимальный показываемый результат равен
#define loC_rshift 3 // 1023 x *_leng >> *_rshift (от Cell_depth не зависит - учтено в процедуре рассчёта)
#define hiC_rshift 1 // значения (*_rshift+Cell_depth) не могут быть 0 ! (будет крах)


#define ADC_loV 0 // назначения физических каналов ADC измеряемым величинам
#define ADC_hiV 1
#define ADC_loC 2
#define ADC_hiC 3

byte Buf_loV_pos = 0; // указатели позиции в циклических буферах
byte Buf_hiV_pos = 0;
byte Buf_loC_pos = 0;
byte Buf_hiC_pos = 0;

word loV [loV_leng]; // буфер для напряжения 0-10 вольт
word loV_sum; // сумма буфера для усреднения
word hiV [hiV_leng]; // буфер для напряжения 0-40 вольт
word hiV_sum; // сумма буфера

word loC [loC_leng]; // буфер для тока 0-1000 мА
word loC_sum; // сумма буфера
word hiC [hiC_leng]; // буфер для тока 0-6 А
word hiC_sum; // сумма буфера

void ReadADC_bySL (byte Channel);
// -- SL import end -------------------------------------------------------------------------------------------------------
...
...
...
//-- Start of ReadADC_bySL ------------------------------------------------------------------------------------------------------
void ReadADC_bySL (byte Channel) // однократно считывает показания ADC назначенного логического канала,
// кладёт считанное в буфер и обновляет результат вычислений по буферу
// для чтения ADC используется режим 'ADC Noise Reduction' - это полный стоп на 13 тактов частоты работы ADC (!)
// в том числе и всех таймеров
{
byte mux, bufpos, bufleng, divisor;
word *ADCbuf, *ADCbuf_begin, *ADCresult; // указатели
byte NotNewCell; // флажок, (не)указывающий на переход к новой ячейке буфера
word ADC_cell; // для временного хранения содержимого ячейки из буфера
unsigned long ADCsum; // используем для подсчёта суммы по буферу

 // фактически этот switch назначает (сопоставляет) логические каналы ADC (согласно byte Channel) их
 // физическим опреледениям, данных в константах. Для изменения привязки логического канала
 // просто меняем номер в case
  NotNewCell = (Channel & (((1<<Cell_depth)-1)<<2)); // пока переменная пустует, используем её для выявления перехода на сл.ячейку буфера

  switch (Channel & 0b00000011) // номер канала - младшие два бита
 { // case 0: // т.к. channel по программе не может быть другим, кроме как от 0 до 3, вместо одного (любого)
 // case используем default - это только ради того, чтобы компилятор не заваливал предупреждениями
 // типа: 'mux' may be used uninitialized in this function [-Wmaybe-uninitialized]

 default: bufleng = loV_leng; // узнаем длину буфера
   bufpos = Buf_loV_pos; // узнаем указатель позиции (если NotNewCell=0, то нужно уеличить на 1)

   if (NotNewCell>0) { ADCbuf = &loV[bufpos]; // если не было перепрыга на новую ячейку, будем суммировать со значением в ячейке
     ADC_cell = *ADCbuf; // будем показания ADC суммировать со значением в ячейке
   }
 else   { bufpos++; // проверить на достижение конца буфера
     if (bufpos == loV_leng) bufpos=0;
     Buf_loV_pos = bufpos; // обновим указатель позиции, хранящийся в ОЗУ
     ADCbuf = &loV[bufpos]; // и вычислить указатель на ячейку для записи результата ADC
     ADC_cell = 0;
   };

   mux = ADC_loV; // включаем каналы ADC согласно назначениям
   ADCbuf_begin = &loV[0]; // указатель на начало буфера для подсчёта суммы
   divisor = loV_rshift + Cell_depth; // делитель (количество сдвигов вправо)
   ADCresult = &loV_sum; // указатель на ячейку для результата вычислений
   break;

   case 1: bufleng = hiV_leng;
   bufpos = Buf_hiV_pos;

   if (NotNewCell>0) { ADCbuf = &hiV[bufpos]; // и вычислить указатель на ячейку для записи результата ADC
     ADC_cell = *ADCbuf;
   }
 else   { bufpos++;
     if (bufpos == hiV_leng) bufpos = 0;
     Buf_hiV_pos = bufpos;
     ADCbuf = &hiV[bufpos];
     ADC_cell = 0;
   };
   mux = ADC_hiV;
   ADCbuf_begin = &hiV[0];
   divisor = hiV_rshift + Cell_depth;
   ADCresult = &hiV_sum;
   break;

   case 2: bufleng = loC_leng;
   bufpos = Buf_loC_pos;
   if (NotNewCell>0) { ADCbuf = &loC[bufpos];
     ADC_cell = *ADCbuf;
   }
 else   { bufpos++;
     if (bufpos == loC_leng) bufpos = 0;
     Buf_loC_pos = bufpos;
     ADCbuf = &loC[bufpos];
          ADC_cell = 0;
   };
   mux = ADC_loC;
   ADCbuf_begin = &loC[0];
   divisor = loC_rshift + Cell_depth;
   ADCresult = &loC_sum;;
   break;

   case 3: bufpos = Buf_hiC_pos;
   if (NotNewCell>0) { ADCbuf = &hiC[bufpos];
     ADC_cell = *ADCbuf;
   }
 else   { bufpos++;
     if (bufpos == hiC_leng) bufpos = 0;
     Buf_hiC_pos = bufpos; // на выходе имеем: ADCbuf - адрес ячейки, куда положить результат ADC
     ADCbuf = &hiC[bufpos]; // ADCbuf_begin - адрес начала буфера, bufleng - длина буфера,
     ADC_cell = 0;
   };
   mux = ADC_hiC; // mux - физический номер канала ADC, соотв. логическому каналу
   bufleng = hiC_leng;
   ADCbuf_begin = &hiC[0];
   divisor= hiC_rshift + Cell_depth;
   ADCresult = &hiC_sum;
   break;
 };
   
 ADMUX = (ADMUX & 0b11111000) + mux; // включим выбранный канал ADC
 PORTD |= 0b00000100;
// set_sleep_mode(SLEEP_MODE_ADC); // подготовим режим сна : ADC Noise Rediction
// sleep_enable(); // разрешим поспать
// sei(); // обязательно разрешить (пусть даже повторно) прерывания.
 SMCR = Sleep_ADC;

 ADCSRA |= (1<<ADEN)|(1<<ADSC);
 sleep_cpu();                            // и засыпаем, проснёмся в прерывании, после того, как результат ADC будет готов
 ADCSRA &= ~(1<<ADEN);
 PORTD &= ~0b00000100;

        ADC_cell = ADC_cell + ADC;
 if (NotNewCell==0)  { ADCsum = 0; // посчитаем сумму по всему буферу
       do  { bufleng--;
     ADCsum = ADCsum + *ADCbuf_begin;
     ADCbuf_begin++;
   } while (bufleng > 0);
       *ADCresult = ((ADCsum + (1<<(divisor - 1)))>>divisor); // прибавим половину от младшего разряда и разделим сумму
     };
 *ADCbuf = ADC_cell; // сохранить первое значение в новую ячейку можно только после того, как посчитали содержимое буфера

}
//-- end of ReadADC -------------------------------------------------------------------------------------------------------------
И застрял на адаптации.
Про функцию с изиэлектроникс.
// http://we.easyelectronics.ru/Theory/chestno-prostoy-cifrovoy-filtr.html
// Nb = 256 -- Na
uint16_t filter(uint16_t x, byte Nb, byte k){
  static  uint16_t y = 0;
  static  uint16_t z = 0;
  z += (x - y);
  return y = (Nb * z) >> k;
};
Если я все правильно понял вот так её сделал.
Но на практике не пойму какие значения в Nb и k лучше...

zenon

Вот такое упрощение правильно?
uint32_t summ = 0;
uint16_t buffer[16];
uint16_t ring_buffer_upd (uint16_t new) {
    byte idx;
    summ = summ - buffer[idx];
    summ = summ + new;
    buffer[idx] = new;
    idx++;
    if(idx == 16) {
        idx = 0;
    }
    return summ/16;
}

zenon


Slabovik

Цитата: zenon от 04 Дек., 2020, 20:04Вот такое упрощение правильно?
В плане получения суммы - годится. В плане того, что idx определена как локальная переменная в процедуре - нет, т.к. она должна быть глобальной.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

Сделал кольцевым буфером, - да ты прав даже без сглаживания почти без болтанки (буфер на 64).
Только памяти он отъедает хорошо.

Slabovik

Чтобы сэкономить память, часть результатов можно "складывать" в небольшие суммочки, оставляя структуру буфера кольцевым. Собственно, в моём примере так и сделано - суммируется по сколько-то в одну ячейку (ибо результат от АЦП десятибитный и мы можем спокойно в одну двубайтовую ячейку суммировать до 64 раз). Это даёт резкую экономию расходуемой памяти вкупе с тем посылом, что всё-равно нет необходимости пересчитывать суммы после каждого измерения (ибо это нужно для вывода на индикатор, а вывод на индикатор нет нужды делать быстрее 10 Гц, разумно 4~8 Гц).
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Slabovik

MC14489 - две микросхемы на весь (2x4 цифры) индикатор и никаких забот с динамическим обновлением
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

Меня больше терзает вопрос АЦП, либо попытаться на хорошей опоре питание vdda на stm32f0 сделать.
Или сразу подумать о выборе микросхемы.
ADS1115 дешёвый, ADS1220, вот такой AD7705BRZ - тоже вроде ничего...
Надо определиться и попробовать.

zenon

Так, на счёт АЦП советовать не хочешь ничего... понятно.
Вот сделал плату для двух реле и вентиляторного шим`а.
Будут замечания? :)
Петля vcc какая-то длинная... ;0

Slabovik

Как я могу что-то советовать, когда я не понимаю, какая цель преследуется? Тем более, что ты же сам пенял, что лучше делать, как другие делают и не выдумывать...  :-\

MCP320x - дёшево, сердито и двенадцатибитно. Работать с ними тоже просто, питание годится как 3.3, так и 5.0

А эти ADS... они же 24-битные. Куда такое? Это как микроскопом гвозди без соответствующей обвязки и конструкции, которые тоже в копеечку влетят...
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

Не... непонимание какое-то просквозило... забыли. По факту всё равно - те конструкции которые собирал 1 в один мало что было..., узлы какие-то - да.
Коннектор J5 - вентилятор, pwm.
Реле одно - в разрыв плюса перед входом.
Реле второе - замыкание выхода, там указан порт, но повешу просто на кнопку.
Эти ADS1115 - недорогие, рублей по 100-150.
Про ADS1220 - да загнул.

Slabovik

Посмотрел и эти... в наших краях нету, пощупать не могу. 16-битные, недорогие. Можно, если есть, но опять же, надо знать, зачем оно столько. 16 бит - это вес младшего разряда полторы тысячных процента. Всё тоже должно соответствовать, или 16 разрядов тут же превратятся в те же 12, хорошо если 13. 10 разрядов при опорном 4.096 дают 4 мВ на младший разряд. 12 разрядов - один милливольт. 14 разрядов - 0.25 милливольта. 16 разрядов - 64 микровольта. Для измерения напряжений блока питания 12 разрядов вполне достаточно, остальное всё-равно избыточно. Для какой-нибудь точной измриловки - это интереснее, но там и аналоговую часть надо вылизывать, и детали прецизионные ставить. ОУ со смещением два-три милливольта и дрейфом 50 мкВ на градус испортят эту ложку мёда бочкой своего...
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

Крутил - вертел по всякому, опять вернулся к простому усреднению с оверсэмплом.
Ну скачет с буфером и всё тут, пока аккумулятор висел, понятно - всё супер.
Обновление значений сделал редкое, как-раз где-то 8-12 Гц.
Этот вариант оставлю - пусть работает такая точность меня устраивает. Видео.

Slabovik

Нужно убедиться (при помощи "закладок" в порты, про них рассказывал ранее), что АЦП работает синхронно с производством выборок из него. На Си и у меня такая проблема и встала, пока я не стал стопить АЦП, в то время как на асме почему-то не было вопроса. Производство чтения из регистров АЦП в то время, как он ещё не закончил обсчёт, приводит к неверным показаниям и прыганию общих результатов.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

оффтоп: мне определённо нравятся TPS5430/5450 - не капризные совсем, припаял - работает.
Если помнишь, ещё на коте мучился с L5973 - попили крови...  >:(