AK Laboratory

Электроника и радиотехника => Цифровая техника => Тема начата: zenon от 29 Сен., 2020, 22:18

Название: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 29 Сен., 2020, 22:18
Привет.
Начал эту тему где-то после НГ, хотелось самому детально разобраться, что к чему, в общем медленно, по несколько подходов, но кое-что получилось.
Сначала собрал индикаторы на двух 74HC595 и двух 4-х значных семисегментах, потом добавил ещё одну 595, очень помогла эта тема на коте (https://radiokot.ru/forum/viewtopic.php?f=57&t=108443).
Те, получились два модуля с индикаторами, для тренировок.
Честно говоря помучиться пришлось... :)
Писал в geany, на F5 назначил make flash.
Над Makefile немного поиздевался.
Проекты, платы и код в архивах.
Продублирую тут:
Для 2-х 595:
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
unsigned char n_count=0;
// n_count от 0 до 9 проходит один раз за прерывание по нужным катодам
uint8_t segm[10] = {  0,  0,  0,  0,     // 1-й дисплей 4-знака
                      0,  0,  0,  0,     // 2-й дисплей 4-знака
                     10, 10           }; // точки
uint8_t const DigiMass [] PROGMEM = {
    0b10000000, //  0  // 1-й знак на первом
    0b01000000, //  1  // 2-й знак на первом
    0b00100000, //  2  // 3-й знак на первом
    0b00010000, //  3  // 4-й знак на первом
    0b00001000, //  4  // 1-й знак на втором
    0b00000100, //  5  // 2-й знак на втором
    0b00000010, //  6  // 3-й знак на втором
    0b00000001, //  7  // 4-й знак на втором
    0b00100000, //  8  // место точки на 1-ом
    0b00000010  //  9  // место точки на 2-ом
};
// ABCEDFGH
uint8_t const SegmMass [] PROGMEM = {
    0b11000000, //  0  ноль
    0b11111001, //  1  один
    0b10100100, //  2  два
    0b10110000, //  3  три
    0b10011001, //  4  четыре
    0b10010010, //  5  пять
    0b10000010, //  6  шесть
    0b11111000, //  7  семь
    0b10000000, //  8  восемь
    0b10011000, //  9  девять // 0b10011000 = 9 без загогулины 0b10010000 = 9 обычная
    0b01111111, // 10  точка
    0b11111111  // 11  пусто
};  
void int2segm (uint16_t cifra1, uint16_t cifra2);
void setup() {
}
// ====================================================================
int main(void) {
// инициализация портов сдвигового регистра  SPI  ======================
    DDRB  |=  (1<<PB5);      // линия тактирования   clock
    DDRB  |=  (1<<PB3);      // линия данных         data
    DDRB  |=  (1<<PB2);      // линия стробирования  latch                   // или краткая запись  DDRB  |=  ((1<<PB2)|(1<<PB3)|(1<<PB5)); //ножки SPI на выход
    PORTB &= ~(1<<PB2);      // подать низ на .. линия стробирования  latch
    PORTB &= ~(1<<PB3);      // подать низ на .. линия данных         data
    PORTB &= ~(1<<PB5);      // подать низ на .. линия тактирования   clock  // или краткая запись  PORTB &= ~((1<<PB2)|(1<<PB3)|(1<<PB5)); // низкий уровень
    SPCR  =   ((1<<SPE)|(1<<MSTR)); // включим шину, объявим ведущим
// ====================================================================
// настройка прерываний по таймеру 1000 Гц =============================
    TCCR1A  = 0; TCCR1B = 0;                       // установить регистры в 0
    OCR1A   = 250 - 1;                             // установка регистра совпадения  250 - 1; 
    TCCR1B |= (1 << WGM12);                        // включить CTC режим 
    TCCR1B |= (1 << CS11); TCCR1B |= (1 << CS10);  // включить делитель /64
    TIMSK1 |= (1 << OCIE1A);                       // включить прерывание по совпадению таймера 
    sei();                                         // включить глобальные прерывания
// ====================================================================

    int i;
    while(1) {
        for (i=0; i<10000; i++) {
            int2segm (i, i);
            _delay_ms(100);
        }
    }
 return 0;
} // main
// ====================================================================
ISR(TIMER1_COMPA_vect) {
    SPDR = pgm_read_byte(&DigiMass[n_count]);       while(!(SPSR & (1<<SPIF)));
    SPDR = pgm_read_byte(&SegmMass[segm[n_count]]); while(!(SPSR & (1<<SPIF)));
    PORTB |= (1<<PB2); asm ("nop"); PORTB &= ~(1<<PB2);
    n_count++;
    if (n_count>9) n_count=0;
}
// ====================================================================
void int2segm (uint16_t digit_up, uint16_t digit_dn) {
    segm[7] = digit_up/1000;       // up 1
    segm[6] = digit_up%1000/100;   // up 2
    segm[5] = digit_up%100/10;     // up 3
    segm[4] = digit_up%10;         // up 4

    segm[3] = digit_dn/1000;       // dn 1
    segm[2] = digit_dn%1000/100;   // dn 2
    segm[1] = digit_dn%100/10;     // dn 3
    segm[0] = digit_dn%10;         // dn 4
}
// ====================================================================
Для 3-х 595:
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
unsigned char n_count=0;
// n_count от 0 до 9 проходит один раз за прерывание по нужным катодам
uint8_t segm[2][5] = { 
    {0,0,0,0,10},
    {0,0,0,0,10}
};

uint8_t const DigiMass [] PROGMEM = {
    0b01110111, // два 1й -нижний и 1-верхний
    0b10111011, //
    0b11011101, //
    0b11101110,  //
    0b11011110 //  место точкЕК низ и верх

};
// ABCEDFGH
uint8_t const SegmMass [] PROGMEM = {
    0b01110111, //  0  // ноль
    0b00010100, //  1  // один
    0b01101101, //  2  // два
    0b00111101, //  3  // три
    0b00011110, //  4  // четыре
    0b00111011, //  5  // пять
    0b01111011, //  6  // шесть
    0b00010101, //  7  // семь
    0b01111111, //  8  // восемь
    0b00011111, //  9  // без загогулины, нормальная = 0b00111111
    0b10000000, // 10  // точка
    0b00000000  // 11  // пусто
};  
void int2segm (uint16_t cifra1, uint16_t cifra2);
// ====================================================================
int main(void) {
// инициализация портов сдвигового регистра  SPI  ======================
    DDRB  |=  (1<<PB5);      // линия тактирования   clock
    DDRB  |=  (1<<PB3);      // линия данных         data
    DDRB  |=  (1<<PB2);      // линия стробирования  latch                   // или краткая запись  DDRB  |=  ((1<<PB2)|(1<<PB3)|(1<<PB5)); //ножки SPI на выход
    PORTB &= ~(1<<PB2);      // подать низ на .. линия стробирования  latch
    PORTB &= ~(1<<PB3);      // подать низ на .. линия данных         data
    PORTB &= ~(1<<PB5);      // подать низ на .. линия тактирования   clock  // или краткая запись  PORTB &= ~((1<<PB2)|(1<<PB3)|(1<<PB5)); // низкий уровень
    SPCR  =   ((1<<SPE)|(1<<MSTR)); // включим шину, объявим ведущим
// ====================================================================
// настройка прерываний по таймеру 1000 Гц =============================
    TCCR1A  = 0; TCCR1B = 0;                       // установить регистры в 0
    OCR1A   = 250 - 1;                             // установка регистра совпадения  250 - 1; 
    TCCR1B |= (1 << WGM12);                        // включить CTC режим 
    TCCR1B |= (1 << CS11); TCCR1B |= (1 << CS10);  // включить делитель /64
    TIMSK1 |= (1 << OCIE1A);                       // включить прерывание по совпадению таймера 
    sei();                                         // включить глобальные прерывания
// ====================================================================
    int i,k=0;
    while(1) {
        for (i=0; i<10000; i++) {
        k = k+10;
            int2segm (i, k);
            _delay_ms(100);
            if (k>9999) k=0;
        }
    }
 return 0;
} // main
// ====================================================================
ISR(TIMER1_COMPA_vect) {
    SPDR = pgm_read_byte(&DigiMass[n_count]); while(!(SPSR & (1<<SPIF))); // катоды/аноды
    SPDR = pgm_read_byte(&SegmMass[segm[1][n_count]]); while(!(SPSR & (1<<SPIF))); //верх
    SPDR = pgm_read_byte(&SegmMass[segm[0][n_count]]); while(!(SPSR & (1<<SPIF))); //низ
    PORTB |= (1<<PB2); asm ("nop"); PORTB &= ~(1<<PB2);
    n_count++;
    if (n_count>4) n_count=0;
}
// ====================================================================
void int2segm (uint16_t digit_up, uint16_t digit_dn) {
    segm[0][0] = digit_up/1000;       // up 1
    segm[0][1] = digit_up%1000/100;   // up 2
    segm[0][2] = digit_up%100/10;     // up 3
    segm[0][3] = digit_up%10;         // up 4


    segm[1][0] = digit_dn/1000;       // dn 1
    segm[1][1] = digit_dn%1000/100;   // dn 2
    segm[1][2] = digit_dn%100/10;     // dn 3
    segm[1][3] = digit_dn%10;         // dn 4
}
// ====================================================================
Makefile примерно такой:
TARGET=7seg_shield_v01_code_atmega328
SRCS=7seg_shield_v01_code_atmega328.c

MCU = atmega328p
F_CPU = 16000000UL
CC = avr-gcc
OBJCOPY = avr-objcopy
OBJDUMP = avr-objdump
SIZE = avr-size
NM = avr-nm
AVRDUDE = avrdude
MV = mv -f
CFLAGS=-Wall -g -Os -mmcu=${MCU} -DF_CPU=${F_CPU} -I.

# https://www.nongnu.org/avr-libc/user-manual/group__demo__project.html
all:
	${CC} ${CFLAGS} -c ${TARGET}.c
	${CC} ${CFLAGS} -o ${TARGET}.elf ${TARGET}.o
	${OBJCOPY} -j .text -j .data -O ihex ${TARGET}.elf ${TARGET}.hex
	${SIZE} -C --mcu=$(MCU) ${TARGET}.elf
flash:
# avrdude -p ${MCU} -c usbasp -U flash:w:${TARGET}.hex:i -F -P usb
	avrdude -p ${MCU} -c usbasp -U flash:w:${TARGET}.hex:i

clean:
	rm -f *.bin *.hex *.elf *.eep *.o *.map *.cof *.sym *.lss
И всё время, как заноза сидит мысль в голове, зачем нужен этот avr? Надо наконец браться за stm, хоть как-нибудь...
ы. Вольтамперметр я всё-таки собрал (https://www.youtube.com/watch?v=6665u6YP9IA), чуть позже выложу, только плата мне жуть как не нравится, но тк у меня накрылся старый лабораторник (https://vrtp.ru/index.php?showtopic=16392) надо было по быстрому что-то собрать.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 30 Сен., 2020, 13:48
Ну и сам вольтамперметр.
Код:
// ====================================================================
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>

uint8_t const DigiMass [] PROGMEM = {
    0b01111111, //  0  // 1-й знак на первом
    0b10111111, //  1  // 2-й знак на первом
    0b11011111, //  2  // 3-й знак на первом
    0b11101111, //  3  // 4-й знак на первом
    0b11110111, //  4  // 1-й знак на втором
    0b11111011, //  5  // 2-й знак на втором
    0b11111101, //  6  // 3-й знак на втором
    0b11111110, //  7  // 4-й знак на втором
    0b11011111, //  8  // место точки на 1-ом
    0b11111101  //  9  // место точки на 2-ом
};
// ABCEDFGH
uint8_t const SegmMass [] PROGMEM = {
    0b00111111,
    0b00000110,
    0b01011011,
    0b01001111,
    0b01100110,
    0b01101101,
    0b01111101,
    0b00000111,
    0b01111111,
    0b01101111,
    0b10000000,
    0b00000000
    
};  
uint8_t Counters[10] = { 0,  0,  0,  0,  // 1-й дисплей 4-знака
                         0,  0,  0,  0,  // 2-й дисплей 4-знака
                        10, 10 };        // точки

// буфер значений ADC (2 канала)
uint16_t adc_buffer [2];

void indicator_natural1 (uint16_t cifra1, uint16_t cifra2);

int main (void) {
    
// инициализация ADC ==================================================
    ADMUX |=    (0<<REFS1)|(1<<REFS1)|  // выставляем опорное напряжение
     // ((0 << REFS1) | (1 << REFS0))

                (1<<MUX1)|(0<<MUX0)|    // канал АЦП2
                (0<<ADLAR);             // выровнять вправо
    ADCSRA |=   (1<<ADEN)|              // Включаем АЦП
                (1<<ADPS1)|(1<<ADPS0);  // устанавливаем предделитель преобразователя на 8
// ====================================================================
// инициализация портов сдвигового регистра  SPI  ======================
    DDRB  |=  (1<<PB5);      // линия тактирования   clock
    DDRB  |=  (1<<PB3);      // линия данных         data
    DDRB  |=  (1<<PB2);      // линия стробирования  latch                   // или краткая запись  DDRB  |=  ((1<<PB2)|(1<<PB3)|(1<<PB5)); //ножки SPI на выход
    PORTB &= ~(1<<PB2);      // подать низ на .. линия стробирования  latch
    PORTB &= ~(1<<PB3);      // подать низ на .. линия данных         data
    PORTB &= ~(1<<PB5);      // подать низ на .. линия тактирования   clock  // или краткая запись  PORTB &= ~((1<<PB2)|(1<<PB3)|(1<<PB5)); // низкий уровень
    SPCR  =   ((1<<SPE)|(1<<MSTR)); // включим шину, объявим ведущим
// ====================================================================
// настройка прерываний по таймеру 1000 Гц =============================
    TCCR1A  = 0; TCCR1B = 0;                       // установить регистры в 0
    OCR1A   = 250 - 1;                             // установка регистра совпадения  250 - 1; 
    TCCR1B |= (1 << WGM12);                        // включить CTC режим 
    TCCR1B |= (1 << CS11); TCCR1B |= (1 << CS10);  // включить делитель /64
    TIMSK1 |= (1 << OCIE1A);                       // включить прерывание по совпадению таймера 
    sei();                                         // включить глобальные прерывания
// ====================================================================
    int i=0, k=0;

    while(1) {
      i = adc_buffer[0]/4*10;
      k = adc_buffer[1]*2.5*61/1024;
      indicator_natural1 (k, i);
    }
    return 0;
}

unsigned char n_count=0;
ISR(TIMER1_COMPA_vect) {
    SPDR = pgm_read_byte(&DigiMass[n_count]);           while(!(SPSR & (1<<SPIF)));
    SPDR = pgm_read_byte(&SegmMass[Counters[n_count]]); while(!(SPSR & (1<<SPIF)));
    PORTB |= (1<<PB2); asm ("nop"); PORTB &= ~(1<<PB2);
    n_count++;
    if (n_count>9) n_count=0;
    
        // ОПРОС ADC КАНАЛОВ
    static uint16_t adc_temp[2];             // накопители для усреднения
    static uint8_t adc_counter;              // счетчик накоплений (младший бит определяет номер канала ADC) 

    // вот тут нифига не разобрался, но работает :)
    if (++adc_counter == 128){               // т.к. крутим 128 значений, то на каждый канал дается половина, т.е. 64
        adc_counter = 0;
        adc_buffer[0] = adc_temp[0] / 64;    // усредняем и выдаем наверх
        adc_buffer[1] = adc_temp[1] / 64;
        adc_temp[0] = 0;                     // обнуляем накопители
        adc_temp[1] = 0;
    }


    if (adc_counter & 0x01){                 // если TRUE то обработка ADC2, FALSE - обработка ADC3 (ну или наоборот :)...)
        adc_temp[0] += ADC;                  // суммируем 64 раз для усреднения
        ADCSRA |= (1<<ADSC);                 // запустить преобразование
        ADMUX &= ~(1<<MUX0);                 // включаем нужный канал
    }
    else{
        adc_temp[1] += ADC;                  // суммируем 64 раз для усреднения
        ADCSRA |= (1<<ADSC);                 // запустить преобразование 
        ADMUX |= (1<<MUX0);                  // включаем нужный канал
    }
    
}

// ====================================================================
void indicator_natural1 (uint16_t cifra1, uint16_t cifra2) {
    Counters[0] = cifra1%10;
    Counters[1] = cifra1%100/10;
    Counters[2] = cifra1%1000/100;
    Counters[3] = cifra1/1000;
   
    Counters[4] = cifra2%10;
    Counters[5] = cifra2%100/10;
    Counters[6] = cifra2%1000/100;
    Counters[7] = cifra2/1000;
    
}
// ====================================================================
// ============================================================ the end
// ====================================================================
PD6 и PD7 на будущее развёл, но не задействовал, хотел переключение обмоток сделать, и термозащиту.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 30 Сен., 2020, 23:16
Занятно  :)  Зачётно! Сегодня вволю (на почве своего слабого знания Цэ) помедитировал, как оно работает. Получается, тут не скользящее окно используется для усреднения, а просто суммирование-деление? В принципе, нормально, т.к. скользящее окно много оперативки требует, а тут тинька... У меня пока другой вопрос, касаемо схемы. Там вот цифровая и аналоговая земли разъединены. Это... наверное схема не полная? Что-то я не нашёл, как они соотносятся.

зы: в Сях изящно записывается HEX->BCD преобразование. В последние дни меня муха укусила, опять взялся за свой недоделанный измеритель. В общем, HEX->BCD у меня получился на ~280 машинных циклов (до 300 в зависимости от условий) что в общем-то неплохо (если алгоритм "тупо" прогонять, там все 700 циклов будет. Но говорят, можно решение уложить в раза два более быстрый, а самое главное - компактный код. Мозг уже сломал, ей-богу...
Оставлю здесь ссылку для размышлений.  http://we.easyelectronics.ru/AVR/matematika-na-assemblere-v-avr.html

А ты ассемблерный код этого места (HEX->BCD), генерируемый Сями не изучал?

зызы: на видео не понял, почему у показателя напряжения сотые всё время 0? В коде что-то не найду...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 01 Окт., 2020, 00:43
Хм, тут бился больше с пониманием SPI и тем, что стоит в прерывании:
Для 2-х 595:
    SPDR = pgm_read_byte(&DigiMass[n_count]);       while(!(SPSR & (1<<SPIF)));
    SPDR = pgm_read_byte(&SegmMass[segm[n_count]]); while(!(SPSR & (1<<SPIF)));
    PORTB |= (1<<PB2); asm ("nop"); PORTB &= ~(1<<PB2);
    n_count++;
    if (n_count>9) n_count=0;
Для 3-х 595:
    SPDR = pgm_read_byte(&DigiMass[n_count]); while(!(SPSR & (1<<SPIF))); // катоды/аноды
    SPDR = pgm_read_byte(&SegmMass[segm[1][n_count]]); while(!(SPSR & (1<<SPIF))); //верх
    SPDR = pgm_read_byte(&SegmMass[segm[0][n_count]]); while(!(SPSR & (1<<SPIF))); //низ
    PORTB |= (1<<PB2); asm ("nop"); PORTB &= ~(1<<PB2);
    n_count++;
    if (n_count>4) n_count=0;
Методом тыка скорее получилось проталкивание байтов в SPI.
Не понял момент например, если я на этой же линии SPI захочу ещё одно устройство повесить, но это надо конкретно уже пробовать.
К сожалению ассемблер я только пробовал когда-то давно-давно, и на данный момент совсем с ним не дружу.
Ну и тут уже совсем не тинька, но желание сделать компактный код есть всегда.
Я сначала хотел уйти от деления, но честно говоря сильно не упирался, можно наверное int2segm реализовать ветвлением, что наверняка будет красивее.
++
По поводу земель, я что-то уже запямятовал... но точно плата именно та, которая в кикадовском проекте, попробую по дорожкам завтра глянуть.
С сотыми напряжения тоже что-то я делал, сорри, начинаю забывать, с мая к этой теме не притрагивался...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 01 Окт., 2020, 11:00
А какой процик в реальности стоит?  oops... я посмотрел внимательнее, на схеме Mega8 (виноват, почему-то подумал, что тинька). На 8-меге килобайт озушки, вполне много, чтобы хранить отсчёты АЦП по отдельности. Впрочем, скользящее окно - это на любителя. У него одно преимущество - при резком изменении измеряемого значения, оно не покажет резкий скачок, а "плавно" поднимет показания. В остальном всё то же самое. Но памяти требует много (два байта на каждый отсчёт)

ps: SPI не подразумевает множественные подключения. Если есть такая нужда, то назначать адресата можно, например, синхросигналом "Clock", мультиплексируя его (например, каким-нибудь дешифратором типа ИД4 (http://esxema.ru/?p=1502)). Соответственно, линия Data остаётся одна на всех, а Clock посредством мультипрекса через ИД4 будет подаваться только на тот приёмник, которому предназначена Data. Также можно и с Latch поступить (благо ИД4 прекрасно работает и как два независимых дешифратора). Адрес должен подаваться на входы ИД4, а Clock как сигнал разрешения дешифрации.

Второй вариант адресации в SPI - чисто информационная, т.е на основе анализа того, что в Data передаётся (например, первые четыре бита - адрес приёмника). Но это уже уровень выше, простые микросхемы так не умеют, хотя если заморочаться, хардверно вполне можно реализовать, корпусов только понатыкать придётся. Из-за "понатыкать" оно никого и не интересует, хотя внутри ряда микросхем, например, память, всякие измерители и т.п., именно такой вариант и реализован (я именно про SPI, а не про i2c, который несколько другой вариант, хотя и близнец).
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 01 Окт., 2020, 14:05
Да мега8, но был карантин и в закромах только 328-ые остались.
Про int2segm и деление нашёл ссылку (http://blindage.org/?p=2824) как хотел сделать:
void indicator_natural(int cifra) {
 //вывод обычного натурального числа (беззнаковое, целое)
 
 int C0=0, C1=0, C2=0, C3=0; //индексы для массива знаков
 
 while(cifra > 0) {
 //отнимаем тысячи
 if (cifra > 1000) {
 cifra = cifra - 1000;
 C0++;
 } else {
 //отнимаем сотни
 if (cifra > 100) { 
 cifra = cifra - 100;
 C1++;
 } else { 
 //отнимаем десятки
 if (cifra > 10) {
 cifra = cifra - 10;
 C2++;
 } else {
 //отнимаем единицы
 if (cifra > 0) {
 cifra = cifra - 1;
 C3++;
 } 
 }
 }
 } 
 }
 
 //если первая цифра 0, то не зажигаем ничего. последний 0 будет показываться!
 if (C0 == 0) { C0=11; }
 if ((C0 + C1) == 0)  { C0=C1=11; }
 if ((C0 + C1 + C2) == 0)  { C0=C1=C2=11; }
}
Только что-то у меня пошло не так, надо бы макет опять собрать на столе и попробовать.
На плате неудачная разводка ОУ и датчика тока.
Про земли - gnd на gnda в одном месте.
ы:
Цитата: Slabovik от 01 Окт., 2020, 11:00Впрочем, скользящее окно - это на любителя
Вот тут не пойму, что имеется ввиду под скользящим окном, может разными терминами оперируем?
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 01 Окт., 2020, 14:37
"Скользящее окно" - это когда в памяти хранится N последних сделанных измерений. При этом при каждом новом измерении самое старое удаляется. Для этого удобно использовать кольцевой буфер длиной N (если в Си, то массив длиной N).

При этом последующее суммирование с целью усреднения делается всегда по всей длине буфера.

зы: визуально скользящее окно можно представить игрой "Змейка". Змейка - это и есть окно, только в игре оно ещё и растёт  :)

зызы: Я тут картинку про мультиплексирование нарисовал. Микросхема чуть другая, но это не суть.

↓ спойлер ↓
[свернуть]

Хотя на деле линию Data, а также Reset и/или OE точно также можно мультиплексировать таким же способом.
Такое мультиплексирование применяют, когда если вдруг регистров много, у них разные функции. Без мультиплексирования они все последовательно включены, значит, при изменении информации в каких-то отдельных регистрах нужно обновлять их все. Мультиплексирование позволяет разбить их на группы и выбирать группу, в которой информация должна обновиться, не затрагивая остальные.
Но для пары-тройки регистров овчинка выделки не стоит, а вот для массива (например, светодиодная матрица) - уже да.

ps: Про деление на нецелые... В принципе, если сделать диапазон измерений не произвольным, а привязать к возможностям двоичной логики, то становится достаточно просто в плане ухода от нецелых. В двоичной логике легко делить на два, четыре, восемь и т.д.
Измерений можно делать сколь угодно много (в разумных пределах). Верхний предел измерения АЦП - 10 бит, т.е. значение 1023. Точку мы можем поставить вручную где угодно. Например, чтобы получить диапазон измерений от 0.00 до 10,23 вольта, достаточно измерить N раз, просуммировать и разделить на N. Например, N выбираем 32 (потому что степень двойки). А в машинном коде разделить на 32 - это сдвинуть сумму вправо на пять бит (можно ещё флаг переноса прибавить для правильного округления результата). Вуа-ля! На индикаторе будет от 0 до 1023 без длинных вычислений.

Диапазон 0.00 - 20.47 делаем точно также, например, суммируем 32 значения, но сдвигаем вправо на 4 бита (делим на 16).
Диапазон 0.00 - 40.95 - аналогично, например, суммируем 64 значения и сдвигаем вправо на 4 бита.

Чуть посложнее.
0 - 15 : Суммируем 48 измерений, сдвигаем вправо на 5 (делим на 32)
0 - 16 : Суммируем 50 измерений, сдвигаем вправо на 5.
0 - 30 : Суммируем 48 измерений, сдвигаем на 4...
0 - 51 : Суммируем 80, сдвигаем на 4...
... и т.д. и т.п.... вариантов много, хотя они и не совсем произвольные.
Ну, а приведение измеряемого к опорному - это дело аналоговой части (ОУ) :)

С другой стороны, обычно у таких индикаторов времени, не занятого вычислениями, остаётся вагон и туда можно даже работу с Float закинуть
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 01 Окт., 2020, 17:33
У меня со сдвигами и двоичной логикой туго... но попытаюсь попробовать.
Про сотые в напряжении - их нет, скакали сильно, избавился их, да и не хватит значений на нормальные показания с сотыми долями.
Тут лаб-то 24 вольт (2х10.5 переменки), не полных, 2 ампера, это временный вариант, есть слаботочная обмотка для индикатора, но уже было поставил реле на переключение обмоток... тока не хватило, гаснут индикаторы при срабатывании. :)
+++
Тот ЛБП, про который упоминал выше (схема с вертепа) буду переделывать, корпус от китайского 1502, в который ~200 ватт трансформатор запихнул,на выходе около 40 вольт, две обмотки, три TO247 на радиаторе (радиатор маловат).
Твоя плата туда никак не лезет, поэтому пока думаю что туда поставить.
+++
А от массива данных с АЦП в этом коде отказались когда в тиньку запихивали код, тут аппаратно тоже сглаживание есть.

Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 01 Окт., 2020, 18:40
Ну, с двоичной логикой достаточно просто
↓ спойлер ↓
блина, были бы предки дальновиднее, они бы двенадцатиричную систему счисления внедрили бы и пользовались, а то, блин, какая-то десятичная мерзость, пальцев, видите ли, на руках не хватало... :-\
[свернуть]
Фишка в том, чтобы не использовать сложные ресурсоёмкие вычисления, при расчётах ограничиться только сложением и делением на двойку в степени. Вот и получается, что делить можно только на два, на четыре, 8, 16, 32, 64... Ну, а суммировать удобно сам буфер (или не буфер, а просто накапливать в ячеечке).

АЦП десятиразрядный, следовательно, минимальное значение у него 0, максимальное 1023 dec = 3FF hex = 0011 1111 1111bin

Считать просто. [предел] = [длина буфера]*(1023)/[делитель]. Делитель - 2(4,8,16,32,64). Длина буфера - сколько памяти хватит.

Перекинув уравнение, отталкиваясь от нужного предела и имеющегося делителя, можно посчитать нужную длину буфера.

[длина буфера]=[предел]*[делитель]/1023

Надо в результате иметь 50 вольт в пределе. На индикаторе хотим 50.00, т.е. 5000 (точку-то всё-равно вручную ставим)

5000*16/1023 = 78,2 - длина буфера нецелая не может быть, значит, округляем её до целого и считаем [результат] по первой формуле. 79 число какое-то некруглое, а вот 80 прикольное. 80*1023/16=5115, т.е. 51.15 (вольт). Хотя и 79*1023/16=5051 - тоже ничего.

Ну а дальше просто - калибровка ОУ таким образом, чтобы при подаче предельного на вход измерителя, АЦП "дотрагивался" до 03FFhex

Ну, а в тиньке длинный массив просто негде держать - места там всего 64 байта. Ну, может 128, но всё-равно мало, временные переменные да стек ещё от неё отъедят...

ps. А вообще, прыгать как-то по сотым и не должно сильно. Мне, правда, в твоей схеме не нравится способ подключения ОУ ко входу АЦП. Я бы на входе АЦП (т.е. прямо на пине) хотел бы видеть конденсатор, может даже 0.1 мкФ, вторым концом сидящий на аналоговой земле, т.к. там в пине какой-то паразитный ток всё-равно есть. А там регулировочный резистор торчит (да ещё маленько странно включенный). Этот паразитный ток (переменная составляющая) на нём падение напряжения делает, и это нехорошо - всяко шум на младшем разряде.

И ещё неплохо бы во время измерения АЦП закидывать ядро в спячку... На ASM это просто, а как на Сях, увы, не знаю.
ADC-2-Sleep_Mode.png

psps: а можно мне присоединиться?
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 01 Окт., 2020, 19:42
Конечно можно!
А то в одного я немного забросил эту тему.
Макет с индикаторами даже не собрал ещё, смотрю что у меня из индикаторов осталось...
Ну и было в планах на stm уже это повторять.
+++
Погрешность у АЦП 2 значения вверх и вниз, сам я оверсемплинг не пробовал в железе, можно.
Цитата: Slabovik от 01 Окт., 2020, 18:40И ещё неплохо бы во время измерения АЦП закидывать ядро в спячку
Там вроде был режим ADC Noise reduction mode.
Увидел в asm коде про спячку.  :o
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 02 Окт., 2020, 13:23
Цитата: Slabovik от 01 Окт., 2020, 18:40Мне, правда, в твоей схеме не нравится способ подключения ОУ ко входу АЦП
Так схему мы уже обсуждали вроде, да конденсатор на пине забыл, но подключение ОУ осталось прежнее.
На всякий добавил старый вариант в протеусе.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 02 Окт., 2020, 13:52
Да, но в кикаде и в протеусе схемы отличаются. В протеусе как надо сделано, а в кикаде и конденсатора нету, и верхний конец подстроечника (по схеме кикада) с движком не соединён. Со схемой в протеусе я согласен.

А конденсатор на пин можно на платке и так подпаять да посмотреть, что будет.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 02 Окт., 2020, 15:00
Упс, точно там же центральный пин подстроечника тоже соединён с пином ADC.
Только на рабочем варианте не буду менять, показания уплывут, только конденсаторы на пины повешу.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 02 Окт., 2020, 15:07
С7 и C11 тоже можно по 100 нан поставить. Потому что параллельно им резисторы в ~10 раз меньше, чем в канале тока. Частота среза будет в районе сотни герц.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 02 Окт., 2020, 15:21
Понял, почему GND и GNDA не соединил на схеме, - kicad зальёт полигон полностью, и не даст соединить GNDA с GND в том месте на плате, где я захочу.
По поводу выжимки из АЦП атмеги всего, - то, что читал в интернетах успехов сильных не дало, включая noise ruduction и оверсэмплинг, те 4-х знаковые индикаторы наверняка будут излишними.
У меня пару MCP3204 лежат уже давно...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 02 Окт., 2020, 16:39
Я согласен по поводу шума, ведь там всего 10 разрядов, и младший болтает туда-сюда. Три разряда - это 1024 градации. 1024 - это считай три десятичных знака (с очень небольшой погрешностью). Поэтому есть полное основание оставлять светиться только три "весящих" разряда на индикаторе. Потому что для получения точности в 4 десятичных разряда АЦП нужен 13-разрядный (а фактически 14-разрядный, в бинарном понимании, естественно). 14-разрядные - это уже уровень "продвинутый", а вот 12-разрядные пробовать можно (4096 градаций уровня). Но к ним нужно ещё и ОУ качественные подтягивать, что уже может потянуть на какую-нибудь курсовую по конструированию для пары-тройки успевающих студентов :)

MCP - да, интересно пощупать. Но пока валяются в ящике...

У себя я захотел сделать так: на один измеряемый параметр цепляю два ОУ с разными коэффициентами усиления. Мысль такая. Раз АЦП трёх-(в десятичном понимании)-разрядный, то и "светить" надо три разряда. Однако два ОУ с разными коэффициентами усиления помогут разбить весь диапазон  на два. Например, первый ОУ масштабирует 10,2 вольт к опорному, второй - 51,1 вольта.
Если данные с первого АЦП в диапазоне - выводим данные с первого АЦП. Погрешность ("вес отсчёта") 10мВ - можно светить четвёртый разряд.

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

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

Из недостатков такого подхода - нужна "удвоенная" аналоговая часть, причём её ещё и согласовывать надо между собой. Т.е. точность нужна.

Чтобы соединить GND и GNDA нужно ввести (придумать) какой-нибудь фиктивный компонент, например, резистор с маленьким сопротивлением, и включить его между землями. Также я заметил, что когда заливаешь полигон, льёшь его на весь участок платы. Рекомендую всё-таки определять полигоны поменьше, располагая их только там, где надо. Это позволит избегать бесполезных "полуостровов", а также делить их для организации "звёзд" и т.п.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 02 Окт., 2020, 21:20
Да, двухдиапазонный вариант тоже неплохо.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 03 Окт., 2020, 21:29
... Как дела?
Собрал "макет", 2 индикатора, atmega328p, usbasb, ch340.
Набросал uart (надергал отсюда (http://www.rjhcoding.com/avrc-uart.php)), только с кандачка не получилось, инициализацию uart пришлось свою делать, а так - работает, мне пока только передача нужна была.
Больше 57600 не запустилось, пока не понял почему.
#include <avr/io.h>
#include <util/delay.h>
#define F_CPU 16000000UL // 16 MHz
#define UBRR 57600L
#define UBRR_div (F_CPU/(16UL*UBRR)-1) 
#define HI(x) ((x)>>8)
#define LO(x) ((x)& 0xFF)


// Два варианта отправки строки, второй через с циклом через отправку символов
void UART_send_string (char data[]) {
    int i = 0;
    while(data[i] != 0) {
      while (!( UCSR0A & (1<<UDRE0)));  // Wait for empty transmit buffer
      UDR0 = data[i];                   // Put data into buffer, sends the data
      i++;                              // increment counter
    }
}


// Все преобразования надёргал из http://www.rjhcoding.com/avrc-uart.php
void UART_putc (unsigned char data) {
        while (!( UCSR0A & (1<<UDRE0)));  // Wait for empty transmit buffer
        UDR0 = data;                      // Put data into buffer, sends the data
}

void UART_puts(char* s) {
    while(*s > 0) UART_putc(*s++);        // transmit character until NULL is reached
}

void UART_puthex8(uint8_t val) {
    // extract upper and lower nibbles from input value
    uint8_t upperNibble = (val & 0xF0) >> 4;
    uint8_t lowerNibble =  val & 0x0F;
    // convert nibble to its ASCII hex equivalent
    upperNibble += upperNibble > 9 ? 'A' - 10 : '0';
    lowerNibble += lowerNibble > 9 ? 'A' - 10 : '0';
    // print the characters
    UART_putc(upperNibble);
    UART_putc(lowerNibble);
}

void UART_puthex16(uint16_t val) {
    UART_puthex8((uint8_t)(val >> 8));        // transmit upper 8 bits
    UART_puthex8((uint8_t)(val & 0x00FF));    // transmit lower 8 bits
}

void UART_putU8(uint8_t val) {                // Transmitting Decimal Values
    uint8_t dig1 = '0', dig2 = '0';
    // count value in 100s place
    while(val >= 100) {
        val -= 100;
        dig1++;
    }
    // count value in 10s place
    while(val >= 10) {
        val -= 10;
        dig2++;
    }
    // print first digit (or ignore leading zeros)
    if(dig1 != '0') UART_putc(dig1);
    // print second digit (or ignore leading zeros)
    if((dig1 != '0') || (dig2 != '0')) UART_putc(dig2);
    // print final digit
    UART_putc(val + '0');
}

void UART_putS8(int8_t val) {
    // check for negative number
    if(val & 0x80) {
        // print negative sign
        UART_putc('-');
        // get unsigned magnitude
        val = ~(val - 1);
    }
    // print magnitude
    UART_putU8((uint8_t)val);
}

void UART_putU16(uint16_t val) {
    uint8_t dig1 = '0', dig2 = '0', dig3 = '0', dig4 = '0';
    // count value in 10000s place
    while(val >= 10000) {
        val -= 10000;
        dig1++;
    }
    // count value in 1000s place
    while(val >= 1000) {
        val -= 1000;
        dig2++;
    }
    // count value in 100s place
    while(val >= 100) {
        val -= 100;
        dig3++;
    }
    // count value in 10s place
    while(val >= 10) {
        val -= 10;
        dig4++;
    }
    // was previous value printed?
    uint8_t prevPrinted = 0;
    // print first digit (or ignore leading zeros)
    if(dig1 != '0') {UART_putc(dig1); prevPrinted = 1;}
    // print second digit (or ignore leading zeros)
    if(prevPrinted || (dig2 != '0')) {UART_putc(dig2); prevPrinted = 1;}
    // print third digit (or ignore leading zeros)
    if(prevPrinted || (dig3 != '0')) {UART_putc(dig3); prevPrinted = 1;}
    // print third digit (or ignore leading zeros)
    if(prevPrinted || (dig4 != '0')) {UART_putc(dig4); prevPrinted = 1;}
    // print final digit
    UART_putc(val + '0');
}

void UART_putS16(int16_t val) {
    // check for negative number
    if(val & 0x8000) {
        // print minus sign
        UART_putc('-');

        // convert to unsigned magnitude
        val = ~(val - 1);
    }
    // print unsigned magnitude
    UART_putU16((uint16_t) val);
}



int main () {
    uint8_t   nA1 = 0x00;
    uint8_t   nA2 = 0xff;
    uint16_t  nB1 = 0x00ef;
    uint16_t  nB2 = 0x00dd;
    uint8_t   nC1 = 0;
    uint8_t   nC2 = 22;
    int8_t    nS1 = -44;
    int8_t    nS2 = -122;
    uint16_t  nD1 = 55;
    uint16_t  nD2 =499;

    UBRR0H = HI(UBRR_div );
    UBRR0L = LO(UBRR_div );
    UCSR0B = (1 << RXEN0 );             // Бит RXEN0 (4) регистра UCSR0B - разрешение приема если установлен в 1.
    UCSR0B = (1 << TXEN0 );             // Бит TXEN0 (3) регистра UCSR0B - разрешение передачи если установлен в 1.
    UCSR0C = (1 << USBS0 )|             // Бит USBS0 (3) регистра UCSR0C устанавливает количество стоп битов (1 стоп-бит если сброшен в 0 / 2 стоп-бита если установлен в 1).
             (1 << UCSZ00)|(1<<UCSZ01); // биты UCSZ01 и UCSZ00 (2, 1) регистра UCSR0C - устанавливают длину передаваемых посылок 011 - 8 бит
    //|(0<<UMSEL00);     //UCSR0C = (1 << UCSZ00);  //UCSR0C = (1 << UCSZ01); //UCSR0C = ((1 << UCSZ00) | (1 << UCSZ01)); // UCSR0C = (1<<USBS0)|(3<<UCSZ00);
    while(1) { // Loop the messsage continously 
    
    UART_send_string ("ABCDEFGHIJKLMNOPQRSTUVWXYZ\n");
    _delay_ms (100);
    // UART_putc ('Z');
    UART_puts ("abcdefghijklmnopqrstuvwxyz");
    UART_putc ('\n');

    UART_puthex8 (nA1); UART_puts ("     ");
    UART_puthex8 (nA2); UART_putc ('\n');
    nA1++; nA2++;
    
    UART_puthex16 (nB1); UART_puts ("   ");
    UART_puthex16 (nB2); UART_puts ("\n");
    nB1++; nB2++;
    
    UART_putU8 (nC1); UART_puts ("    ");
    UART_putU8 (nC2); UART_puts ("\n");
    nC1++; nC2++;
    
    UART_putS8 (nS1);  UART_puts ("    ");
    UART_putS8 (nS2);  UART_puts ("\n");
    nS1++; nS2++;
    
    UART_putU16 (nD1);  UART_puts ("    ");
    UART_putU16 (nD2);  UART_puts ("\n");
    nD1++; nD2++;
    }

}

Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 05 Окт., 2020, 12:41
Переписал ещё немного.
Опрос АЦП отправил на своё прерывание, сейчас опрашивается 2 канала, но добавить остальные легко.
Ушел от функции деления без остатка, функция bin_bcd и структура bcd.
Вывод без преобразования на индикаторы и в uart.
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>

#define F_CPU 16000000UL // 16 MHz
#define UBRR 57600L
#define UBRR_div (F_CPU/(16UL*UBRR)-1)
#define HI(x) ((x)>>8)
#define LO(x) ((x)& 0xFF)

//double current, voltage;
// Места знаков и точек
uint8_t const DigiMass [] PROGMEM = {
    0b01111111, //  0  // 1-й знак на первом
    0b10111111, //  1  // 2-й знак на первом
    0b11011111, //  2  // 3-й знак на первом
    0b11101111, //  3  // 4-й знак на первом
    0b11110111, //  4  // 1-й знак на втором
    0b11111011, //  5  // 2-й знак на втором
    0b11111101, //  6  // 3-й знак на втором
    0b11111110, //  7  // 4-й знак на втором

    0b11011111, //  8  // место точки на 1-ом
    0b11111101  //  9  // место точки на 2-ом
};
// ABCEDFGH
uint8_t const SegmMass [] PROGMEM = {
    0b00111111, // 0
    0b00000110,
    0b01011011,
    0b01001111,
    0b01100110,
    0b01101101,
    0b01111101,
    0b00000111,
    0b01111111,
    0b01101111,
    0b10000000,
    0b00000000
};

uint8_t Counters[10] = { 0,  0,  0,  0,  // 1-й дисплей 4-знака
                         0,  0,  0,  0,  // 2-й дисплей 4-знака
                        10, 10 };        // точки
unsigned char n_count=0;
// буфер значений ADC (2 канала)
uint16_t adc_buffer [2];
uint8_t adc_counter;
uint8_t  adc_channel;
typedef enum {false, true} bool;
volatile bool trueValue = false;

void bin_bcd(uint16_t a);
struct {
    uint8_t tens,hundreds,thousands;
    uint16_t units;
}bcd;
//struct { uint8_t tens,hundreds,thousands; uint16_t units; }bcd;

void UART_putc(unsigned char data);
void UART_puts(char* s);
void UART_puthex8(uint8_t val);
void UART_puthex16(uint16_t val);
void UART_putU8(uint8_t val);
void UART_putS8(int8_t val);
void UART_putU16(uint16_t val);
void UART_putS16(int16_t val);

int main (void) {
// инициализвция UART =================================================
    UBRR0H = HI(UBRR_div );
    UBRR0L = LO(UBRR_div );
    UCSR0B = (1 << RXEN0 );             // Бит RXEN0 (4) регистра UCSR0B - разрешение приема если установлен в 1.
    UCSR0B = (1 << TXEN0 );             // Бит TXEN0 (3) регистра UCSR0B - разрешение передачи если установлен в 1.
    UCSR0C = (1 << USBS0 )|             // Бит USBS0 (3) регистра UCSR0C устанавливает количество стоп битов (1 стоп-бит если сброшен в 0 / 2 стоп-бита если установлен в 1).
             (1 << UCSZ00)|(1<<UCSZ01); // биты UCSZ01 и UCSZ00 (2, 1) регистра UCSR0C - устанавливают длину передаваемых посылок 011 - 8 бит
// инициализация ADC ==================================================
    ADMUX  |= (0<<REFS1)|(1<<REFS1)|    // Выставляем внешнее опорное напряжение (AREF (TL431=2.469))
              (0<<ADLAR);               // ADLAR=0 Если нужны все 10 бит (полная 10-битная точность)
              //(1<<MUX1 )|(0<<MUX0 )|    // Канал АЦП2
              //(0 << MUX0)|(1 << MUX1)|(0 << MUX2)|(0 << MUX3)|
    //ADCSRA |= (1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); // ADC_CLOCK_PRESCALER128 задаем тактовую частоту ADC (12000/128 = 93,75 кГц). 
    ADCSRA |= (1<<ADIE )|                      // Включаем прерывание
              (1<<ADATE)|                      // Включаем автоматический триггер
              (1<<ADEN )|                      // Включаем ADC
              //(1<<ADPS1)|(1<<ADPS0)|         // устанавливаем предделитель преобразователя на 8
              (1<<ADSC );                      //  Запускаем преобразование
              // Устанавливаем предделитель - 32 (ADPS[2:0]=101)
              ADCSRA |= (1<<ADPS2)|(1<<ADPS0); // Биту ADPS2 присваиваем единицу
              ADCSRA &= ~ (1<<ADPS1);          // Битам ADPS1 и ADPS0 присваиваем нули
              ADCSRB = 0;                      // 000 - непрерывное преобразование - режим Free running
// инициализация портов сдвигового регистра  SPI  ======================
    DDRB  |=  (1<<PB5);      // линия тактирования   clock
    DDRB  |=  (1<<PB3);      // линия данных         data
    DDRB  |=  (1<<PB2);      // линия стробирования  latch                   // или краткая запись  DDRB  |=  ((1<<PB2)|(1<<PB3)|(1<<PB5)); //ножки SPI на выход
    PORTB &= ~(1<<PB2);      // подать низ на .. линия стробирования  latch
    PORTB &= ~(1<<PB3);      // подать низ на .. линия данных         data
    PORTB &= ~(1<<PB5);      // подать низ на .. линия тактирования   clock  // или краткая запись  PORTB &= ~((1<<PB2)|(1<<PB3)|(1<<PB5)); // низкий уровень
    SPCR  =   ((1<<SPE)|(1<<MSTR)); // включим шину, объявим ведущим
// ====================================================================
// настройка прерываний по таймеру 1000 Гц =============================
    TCCR1A  = 0; TCCR1B = 0;                       // установить регистры в 0
    OCR1A   = 250 - 1;                             // установка регистра совпадения  250 - 1; 
    TCCR1B |= (1 << WGM12);                        // включить CTC режим 
    TCCR1B |= (1 << CS11); TCCR1B |= (1 << CS10);  // включить делитель /64
    TIMSK1 |= (1 << OCIE1A);                       // включить прерывание по совпадению таймера 
    sei();                                         // включить глобальные прерывания
// ====================================================================
    uint16_t i=0, k=0;
    while(1) {
      bin_bcd(adc_buffer[0]);  // закидываем прочитанное значение АЦП в функцию bin_bcd, результат отправляется в структуру bcd
      Counters[3] = bcd.thousands;
      Counters[2] = bcd.hundreds;
      Counters[1] = bcd.tens;
      Counters[0] = bcd.units;
      bin_bcd(adc_buffer[1]);
      Counters[7] = bcd.thousands;
      Counters[6] = bcd.hundreds;
      Counters[5] = bcd.tens;
      Counters[4] = bcd.units;

      k = adc_buffer[0];
      i = adc_buffer[1];
      UART_putU16(i); UART_puts("   ");
      UART_putU16(k); UART_puts("\n");

      _delay_ms(50);
    }
    return 0;
} // main =============================================================
// ====================================================================
// Прерывание по таймеру 1000Hz для 7 seg =============================
ISR(TIMER1_COMPA_vect) {
    SPDR = pgm_read_byte(&DigiMass[n_count]);           while(!(SPSR & (1<<SPIF)));
    SPDR = pgm_read_byte(&SegmMass[Counters[n_count]]); while(!(SPSR & (1<<SPIF)));
    PORTB |= (1<<PB2); asm ("nop"); PORTB &= ~(1<<PB2);
    n_count++;
    if (n_count>9) n_count=0;
}
// Прерывание опроса АЦП ==============================================
ISR(ADC_vect) {
    if (trueValue) {
      if (adc_channel==0) { adc_buffer[0] = ADCL | (ADCH << 8); ADMUX = 1; } //ADMUX = (0b00<<REFS0)|1;    // ADC1 pin 24
      if (adc_channel==1) { adc_buffer[1] = ADCL | (ADCH << 8); ADMUX = 3; } //ADMUX = (0b00<<REFS0)|3;    // ADC3 pin 26
      ++adc_channel;
      if (adc_channel>1) { adc_channel = 0; }
      trueValue = false; // Устанавливаем флаг смены входного пина
    }
    else { trueValue = true; } // Первый раз пропускаем считывание и устанавливаем флаг на чтение в следующий раз
}
// Раскладываем 9999 на цифры
void bin_bcd(uint16_t a) {
  //структура для функции bin_bcd 0-9999
  bcd.tens=0;
  bcd.hundreds=0;
  bcd.thousands=0;
  bcd.units=a;
  if(bcd.units>=1000) {
    while (bcd.units>=1000) {
      bcd.units-=1000;
      bcd.thousands++;
    }
  }
  if(bcd.units>=100) {
    while (bcd.units>=100) {
      bcd.units-=100;
      bcd.hundreds++;
    }
  }
  if(bcd.units>=10) {
    while (bcd.units>=10) {
      bcd.units-=10;
      bcd.tens++;
    }
  }
}

// UART ===============================================================
// ====================================================================
// Все преобразования надёргал из http://www.rjhcoding.com/avrc-uart.php
void UART_putc (unsigned char data) {
        while (!( UCSR0A & (1<<UDRE0)));  // Wait for empty transmit buffer
        UDR0 = data;                      // Put data into buffer, sends the data
}
// ====================================================================
void UART_puts(char* s) {
    while(*s > 0) UART_putc(*s++);        // transmit character until NULL is reached
}
// ====================================================================
void UART_puthex8(uint8_t val) {
    // extract upper and lower nibbles from input value
    uint8_t upperNibble = (val & 0xF0) >> 4;
    uint8_t lowerNibble =  val & 0x0F;
    // convert nibble to its ASCII hex equivalent
    upperNibble += upperNibble > 9 ? 'A' - 10 : '0';
    lowerNibble += lowerNibble > 9 ? 'A' - 10 : '0';
    // print the characters
    UART_putc(upperNibble);
    UART_putc(lowerNibble);
}
// ====================================================================
void UART_puthex16(uint16_t val) {
    UART_puthex8((uint8_t)(val >> 8));        // transmit upper 8 bits
    UART_puthex8((uint8_t)(val & 0x00FF));    // transmit lower 8 bits
}
// ====================================================================
void UART_putU8(uint8_t val) {                // Transmitting Decimal Values
    uint8_t dig1 = '0', dig2 = '0';
    // count value in 100s place
    while(val >= 100) {
        val -= 100;
        dig1++;
    }
    // count value in 10s place
    while(val >= 10) {
        val -= 10;
        dig2++;
    }
    // print first digit (or ignore leading zeros)
    if(dig1 != '0') UART_putc(dig1);
    // print second digit (or ignore leading zeros)
    if((dig1 != '0') || (dig2 != '0')) UART_putc(dig2);
    // print final digit
    UART_putc(val + '0');
}
// ====================================================================
void UART_putS8(int8_t val) {
    // check for negative number
    if(val & 0x80) {
        // print negative sign
        UART_putc('-');
        // get unsigned magnitude
        val = ~(val - 1);
    }
    // print magnitude
    UART_putU8((uint8_t)val);
}
// ====================================================================
void UART_putU16(uint16_t val) {
    uint8_t dig1 = '0', dig2 = '0', dig3 = '0', dig4 = '0';
    // count value in 10000s place
    while(val >= 10000) {
        val -= 10000;
        dig1++;
    }
    // count value in 1000s place
    while(val >= 1000) {
        val -= 1000;
        dig2++;
    }
    // count value in 100s place
    while(val >= 100) {
        val -= 100;
        dig3++;
    }
    // count value in 10s place
    while(val >= 10) {
        val -= 10;
        dig4++;
    }
    // was previous value printed?
    uint8_t prevPrinted = 0;
    // print first digit (or ignore leading zeros)
    if(dig1 != '0') {UART_putc(dig1); prevPrinted = 1;}
    // print second digit (or ignore leading zeros)
    if(prevPrinted || (dig2 != '0')) {UART_putc(dig2); prevPrinted = 1;}
    // print third digit (or ignore leading zeros)
    if(prevPrinted || (dig3 != '0')) {UART_putc(dig3); prevPrinted = 1;}
    // print third digit (or ignore leading zeros)
    if(prevPrinted || (dig4 != '0')) {UART_putc(dig4); prevPrinted = 1;}
    // print final digit
    UART_putc(val + '0');
}
// ====================================================================
void UART_putS16(int16_t val) {
    // check for negative number
    if(val & 0x8000) {
        // print minus sign
        UART_putc('-');
        // convert to unsigned magnitude
        val = ~(val - 1);
    }
    // print unsigned magnitude
    UART_putU16((uint16_t) val);
}
// end UART functions
// ====================================================================
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 05 Окт., 2020, 15:28
В выходные был занят на огороде и неплохо отморозился (в прямом смысле - температурка на улице упала вместе с первым снегом, дак, думаю, отходняк ещё пару дней займёт). Зато остатки яблок снял - уже вкусные стали :) Так что, касаемо процессоров и прочего, ничего и не делал  :( В планах первым пунктом стоит завести твою схему в DipTrace, изобразив её в соответствие с ЕСКД (ну, хотя бы приблизительно).

На последнем видео у тебя чёткий 0 - это сигнал напрямую на ногу АЦП подаётся или через ОУ? Если через ОУ, то результат отличный. А то, честно скажу, сомневался по поводу того, что там чёткий 0 будет и хотел пробовать делать небольшое отрицательное питание.

У себя, играясь с изложенным выше алгоритмом, заметил закономерное (закономерным оно стало после процесса "а маленько подумать" :) ) явление, заключающееся в том, что несмотря на возможность чисто математически выводить значения "как бы повышающие разрядность АЦП", на индикаторе значения имею всё-равно согласно разрядности. А промежуточные "типа более точные" значения пробегают довольно быстро. На деле эффект закономерен и вызван... отсутствием стационарных шумов на входе АЦП (в измеряемом сигнале). Вот и думаю, что либо как ввести такие шумы (те, кто занимается аудио, наверняка слышали про dithering (https://ru.wikipedia.org/wiki/%D0%94%D0%B8%D0%B7%D0%B5%D1%80%D0%B8%D0%BD%D0%B3), либо... либо это вся овчинка вообще выделки не стоит.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 05 Окт., 2020, 16:03
Сам на дачу в выходные не поехал, сын только съездил за яблоками ;) - но всё не увезти, яблони хорошие, девать особо не куда, раздаем в основном, ну и немного в гараж отвезу. Дело в том, что дача на острове (https://gotonature.ru/1733-ostrov-sarpinskij.html), и на машине тужа можно, но это паром и крюк довольно большой. Добираемся речным транспортом.
Ну и у нас теплее намного :)

АЦП у меня просто на делителях напряжения весит, ОУ ещё не прикрутил.
В основном кодом занимался, вроде неплохо получилось, только не знаю надо ли было в своё прерывание АЦП кидать, для индикатора и в прерывании по таймеру ему неплохо жилось.
А зачем перекидывать в DT? Хотя если уже привык...
ы. На работающем БП ОУ MCP6002 - ноль отличный.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 05 Окт., 2020, 16:34
DT - это дань привычке. Я привык к его "крыжикам" (которые более-менее копируют крыжики P-CAD/Altium), а у KiCAD они совсем другие - ломка возникает (как у пилотов: "штурвал или сайдстик" :) ) А поскольку перерисовать всё-равно (мне) придётся, то сохранять прежний инструмент не принципиально. А там и KiCAD'освкую версию можно параллельно вести (глядя друг на друга).

Если 0 с этими ОУ стабильный, то я полагаю, в схеме изменений (кроме вот вышеупомянутой коррекции) делать и не нужно. Только решить, два регистра, или три. Тут есть небольшая засада, заключающаяся в допустимом токе через вывод. Общий вывод индикатора всё-равно коммутировать надо, но через него будет идти суммарный ток сегментов, который при динамической индикации довольно высок. Даже если 10 мА на сегмент - это 80 мА в пике. Нога HC595 такого не держит (предел ~30мА).

Может быть, два регистра + катодные ключи (взять индикаторы с общим катодом, чтобы N-FET транзистором, теми же 2N7002, выборку разряда делать?)

зы: а можно радикально - восемь регистров и хардверная динамическая индикация. Или вообще статика, но это и индикаторы нужны другие. Наверное, это трогать не стоит - 4x индикаторы легко купить только для динамической индикации, на том же Ali статических 4x и не нашёл даже...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 05 Окт., 2020, 17:08
Да, катодные ключи - однозначно надо. У меня кучка 2N7002 и BC817 в SOT-23 есть, те мне больше нравится вариант с двумя регистрами и ключами. Статику я твою тоже помню, да симпатичная, но уж сильно большая получается, может потом как-нибудь.
Вообще почти всё в smd делать наверное буду, уже привык.
ы. Кварцы 3525 вот такие:
ыы. Надо подумать на счёт ref192, хотя для 10-бит под вопросом.
ыыы. На счет привычки - периодически открываю что-то в DT - не могу... после кикада меня он аж пугать начинает, за больше чем полгода настолько привык, и скорость довольно неплохая - хоткиями левой рукой m, r, e, g, x, w ... правой мышь, - супер! :)
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 06 Окт., 2020, 10:49
Полагаю, что если заморачиваться dither'ом, то нужно вводить прямо в опорник. Вводить в каналы много геморнее, ибо их много.

↓ спойлер ↓
[свернуть]

Но есть проблема с пониманием, какой именно он должен быть. Достаточно ли будет простого меандра или нужно заморочиться псевдошумом?
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 06 Окт., 2020, 12:24
О как, с корабля на бал, про подмешивание шума немного читал, можно попробовать, но с пониманием у меня тоже вопрос. :)
Вот тут (https://chipenable.ru/index.php/programming-avr/142-avr121-oversampling-decimation.html).
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 07 Окт., 2020, 11:40
О, да. Почитал аппнот, посмотрел видео - результат приятен. Однако шум подмешивать однозначно надо. На рисунке, что я выше кинул, наверное плохой способ подмешивания шума (была мысль о подмешивании псевдослучайной последовательности 0 и 1, которую можно легко получить на сдвиговом регистре даже чисто программно (https://habr.com/ru/post/121849/)).

В аппноте же, насколько я понял (они это не уточняют, говорят только про PWM с заполнением 50%), используют треугольный сигнал для подмешивания в опору. Это имеет смысл т.к. всё становится ещё проще. Думаю, стоит сделать закладку на подмешивание такого сигнала (всего несколько деталей и нога порта), а подмешивать или нет - это уже можно решить в программе.

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

Я бы попробовал такой вариант алгоритма.
Основа синхронизации - прерывания по таймеру. Пусть те же 1000 Гц (250 Гц мерцания на индикаторе - 4 цифры)

1. вывод показаний на индикатор (очередная цифра)

2. запуск однократного преобразования АЦП - (режим Sleep с ожиданием прерывания. В прерывании вообще не нужно ничего делать - оно лишь служит сигналом к продолжению работы программы). Последовательно запускаем так для каждого из каналов.

3. Пересчёт сумм в кольцевом буфере (если буфер накопительный, то пересчёт нужно сделать только 1 раз за n циклов, т.е. когда значения накопятся)

4. Подготовка показаний для индикатора (тоже 1 из 4 раз - только когда на индикатор выведена последняя цифра, если буфер кольцевой, или 1 раз за n циклов, если буфер накопительный)

5. сваливание в Sleep с ожиданием прерывания от таймера.

По времени я прикинул - должно укладываться со значительным запасом.


Что меня смущает... использование аппаратного вывода на индикатор безусловно круто, но... блин, есть две причины, чтобы он мне не нравился. Первая - всё-равно программа "тупо ждёт", когда из буфера выйдет всё, чтобы вывалить туда следующий байт, вторая - всё-равно вручную дёргается PB2 (SS). Ну, программа короче, да...
ps. а вот ещё. У процессоров в 32-выводных QFP корпусах есть два "лишних" входа ADC 6 и ADC7, выведенные на собственные ноги и питающиеся исключительно от AVcc. Возможно, что стоит задействовать? Выводы PC0~PC5 при этом совершенно свободны, собственно, как и PD0~PD7 (UART, я понимаю, исключительно для отладки)
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 07 Окт., 2020, 12:27
В последнем моем варианте никакой синхронизации нет, я тут ещё сам для себя все эти моменты только открываю, опоздал лет так на надцать. :)
Во Free runnig режиме, насколько я понял прерывание работает по окончанию преобразования, далее с результатом я ничего не делал, чистый его вывод с задержкой. Надо сглаживать, накопитель и среднее.
Тот код который с оверсэмплингом не мой, я просто склеил куски и запустил для посмотреть, опять-таки выхлоп чистый, не сглаженный, потому как вот этот кусок кода для меня не понятен от слова совсем.
Отсюда (https://github.com/0xPIT/UltiDMM/blob/master/main.cpp):
// -------------------------------------------------------------
    // smoothing: IIR Filter
    //
    #define IIR_SHIFT 2
    V = ((V << IIR_SHIFT) - V + adcValue(Settings::ChannelVoltage, AdcReadLinearized)) >> IIR_SHIFT;
    C = ((C << IIR_SHIFT) - C + adcValue(Settings::ChannelCurrent, AdcReadLinearized)) >> IIR_SHIFT;

    // -------------------------------------------------------------
    // smoothing: exponential moving average with window
    //
    // weighting factor W = 0 .. 1
    // current average = (current sensor value * W) + (last average * (1 - W))
    // avg = val * 0.1 + avg * (1 - 0.1);
    //
    #define EXP_SHIFT   5
    #define EXP_WEIGHT 15
    #define EXP_SCALE   4
    #define EXP_WINDOW (2 ^ EXP_SHIFT)
    #define EXP_RC     (2 ^ EXP_SHIFT / 2) // Rounding correction: add 0,5 == 2 ^ SHIFT / 2

    smoothV = (V << (EXP_SHIFT - EXP_SCALE)) + ((smoothV * EXP_WEIGHT) >> EXP_SCALE);
    smoothC = (C << (EXP_SHIFT - EXP_SCALE)) + ((smoothC * EXP_WEIGHT) >> EXP_SCALE);

    uint32_t sv = (smoothV + EXP_RC) >> EXP_SHIFT;
    if (abs(sv - V) < EXP_WINDOW) {
      V = sv;
    }

    uint32_t sc = (smoothC + EXP_RC) >> EXP_SHIFT;
    if (abs(sc - C) < EXP_WINDOW) {
      C = sc;
    }
Вот код, на котором работает оверсэмплинг, каналы ADC0 и ADC1, выдернул оттуда же, забор показаний сделал из переменной adcOversampledValue. Там в коде есть ещё компенсации, с ними не разобрался.
Всё сырое!
#include <stdint.h>
#include <avr/io.h>
#include <avr/pgmspace.h>

#define adcChannels    2 // read ADC0 and ADC1
typedef enum AdcValueType_e {
  AdcReadLinearized = (1<<0),
  AdcReadRaw        = (1<<1)
} AdcValueType_t;

typedef struct adcCalibration_s {
  uint32_t hi;
  uint32_t lo;
} AdcCalibration_t;

// calibration points at runtime
extern AdcCalibration_t adcCalibration[adcChannels];

// measured AVCC at startup in mV
extern uint16_t adcAVcc;

extern void     adcInit(void);
extern void     adcLoadCalibrationData(void);
extern void     adcSaveCalibrationData(void);
extern void     adcCalculateReferenceCompensationFactor(void);
extern uint32_t adcValue(uint8_t channel, AdcValueType_t valuetype);

#define adcSelectChannel(channel) \
  (ADMUX = (ADMUX & (~((1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (1<<MUX0)))) | (channel))

#define adcEnableInternalVref()       (ADMUX  |=  ((1<<REFS1) | (1<<REFS0))) // 1.1V internal Bandgap Reference
#define adcEnableExternalVref()       (ADMUX  &= ~((1<<REFS1) | (1<<REFS0))) // VRef at Pin AREF
#define adcEnableVccVref()            (ADMUX  &= ~(1<<REFS1), ADMUX |= (1<<REFS0)) // AVCC with external capacitor at AREF pin

#define adcStartConversion()          (ADCSRA |=  (1<<ADSC))
#define adcIsConversionFinished()     ((ADCSRA &  (1<<ADIF)) ? TRUE : FALSE)
#define adcIsConversionRunning()      ((ADCSRA | ~(1<<ADIF)) ? TRUE : FALSE)
#define adcWaitForConversion()        while (ADCSRA & (1<<ADSC))

#define adcEnable()                   (ADCSRA |=  (1<<ADEN))
#define adcDisable()                  (ADCSRA &= ~(1<<ADEN))

#define adcEnableInt()                (ADCSRA |=  (1<<ADIE))
#define adcDisableInt()               (ADCSRA &= ~(1<<ADIE))

#define adcClearFlag()                (ADCSRA &=  (1<<ADIF))
//
//
//
//
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include <util/delay.h>

// ----------------------------------------------------------------------------- 

#define adcOversampleCount        128
#define adcOversampleShift          4
#define adcCalibrationMarkHi    30000UL  // 1/10mV, upper calibration point
#define adcCalibrationMarkLo     3000UL  // 1/10mV, lower calibration point

#define adcScale                   16UL  // 2^4 fixed point scaling (shift by this value)
#define adcRoundingCorrection   32768UL  // add 0.5, which is: (2 ^ adcScale / 2)

#define adcRangeScaled  ((adcCalibrationMarkHi - adcCalibrationMarkLo) << adcScale)
#define adcOffsetScaled (adcCalibrationMarkLo << adcScale)

#define adcFactor(ch)       (adcRangeScaled / (adcCalibration[ch].hi - adcCalibration[ch].lo))
#define adcCorrection(ch)   (adcOffsetScaled - (adcCalibration[ch].lo * adcFactor(ch)))

// ----------------------------------------------------------------------------- 
// default values (Data Precision 8200 +-10ppm at ATMega Vcc = 5.0V)
// NB: these will vary per ATMega device
//
AdcCalibration_t eeAdcCalibration[adcChannels] EEMEM = {
  {
    4908UL, // 3.000V calibration point
     459UL  // 0.300V calibration point
  },
  {
    4908UL, // 3.000V calibration point
     459UL  // 0.300V calibration point
  }
};

AdcCalibration_t adcCalibration[adcChannels];

// ----------------------------------------------------------------------------- 

uint16_t adcAVcc;
uint8_t adcChannel;



// Места знаков и точек
uint8_t const DigiMass [] PROGMEM = {
    0b01111111, //  0  // 1-й знак на первом
    0b10111111, //  1  // 2-й знак на первом
    0b11011111, //  2  // 3-й знак на первом
    0b11101111, //  3  // 4-й знак на первом
    0b11110111, //  4  // 1-й знак на втором
    0b11111011, //  5  // 2-й знак на втором
    0b11111101, //  6  // 3-й знак на втором
    0b11111110, //  7  // 4-й знак на втором

    0b11011111, //  8  // место точки на 1-ом
    0b11111101  //  9  // место точки на 2-ом
};
// ABCEDFGH
uint8_t const SegmMass [] PROGMEM = {
    0b00111111, // 0
    0b00000110,
    0b01011011,
    0b01001111,
    0b01100110,
    0b01101101,
    0b01111101,
    0b00000111,
    0b01111111,
    0b01101111,
    0b10000000,
    0b00000000
};

uint8_t Counters[10] = { 0,  0,  0,  0,  // 1-й дисплей 4-знака
                         0,  0,  0,  0,  // 2-й дисплей 4-знака
                        10, 10 };        // точки
unsigned char n_count=0;
typedef enum {false, true} bool;
volatile bool trueValue = false;

void bin_bcd(uint16_t a);
struct {
    uint8_t tens,hundreds,thousands;
    uint16_t units;
}bcd;
//struct { uint8_t tens,hundreds,thousands; uint16_t units; }bcd;


volatile uint32_t adcOversampledValue[adcChannels];



//zz  
#define F_CPU 16000000UL // 16 MHz
#define UBRR 57600L
#define UBRR_div (F_CPU/(16UL*UBRR)-1)
#define HI(x) ((x)>>8)
#define LO(x) ((x)& 0xFF)

void UART_putc(unsigned char data);
void UART_puts(char* s);
void UART_puthex8(uint8_t val);
void UART_puthex16(uint16_t val);
void UART_putU8(uint8_t val);
void UART_putS8(int8_t val);
void UART_putU16(uint16_t val);
void UART_putS16(int16_t val);



uint32_t V0 = 0;
uint32_t V1 = 0;

uint32_t smoothV0 = 0;
uint32_t smoothV1 = 0;
int main (void) {
    
    // инициализвция UART =================================================
    UBRR0H = HI(UBRR_div );
    UBRR0L = LO(UBRR_div );
    UCSR0B = (1 << RXEN0 );             // Бит RXEN0 (4) регистра UCSR0B - разрешение приема если установлен в 1.
    UCSR0B = (1 << TXEN0 );             // Бит TXEN0 (3) регистра UCSR0B - разрешение передачи если установлен в 1.
    UCSR0C = (1 << USBS0 )|             // Бит USBS0 (3) регистра UCSR0C устанавливает количество стоп битов (1 стоп-бит если сброшен в 0 / 2 стоп-бита если установлен в 1).
             (1 << UCSZ00)|(1<<UCSZ01); // биты UCSZ01 и UCSZ00 (2, 1) регистра UCSR0C - устанавливают длину передаваемых посылок 011 - 8 бит
    

// инициализация портов сдвигового регистра  SPI  ======================
    DDRB  |=  (1<<PB5);      // линия тактирования   clock
    DDRB  |=  (1<<PB3);      // линия данных         data
    DDRB  |=  (1<<PB2);      // линия стробирования  latch                   // или краткая запись  DDRB  |=  ((1<<PB2)|(1<<PB3)|(1<<PB5)); //ножки SPI на выход
    PORTB &= ~(1<<PB2);      // подать низ на .. линия стробирования  latch
    PORTB &= ~(1<<PB3);      // подать низ на .. линия данных         data
    PORTB &= ~(1<<PB5);      // подать низ на .. линия тактирования   clock  // или краткая запись  PORTB &= ~((1<<PB2)|(1<<PB3)|(1<<PB5)); // низкий уровень
    SPCR  =   ((1<<SPE)|(1<<MSTR)); // включим шину, объявим ведущим
// ====================================================================
// настройка прерываний по таймеру 1000 Гц =============================
    TCCR1A  = 0; TCCR1B = 0;                       // установить регистры в 0
    OCR1A   = 250 - 1;                             // установка регистра совпадения  250 - 1; 
    TCCR1B |= (1 << WGM12);                        // включить CTC режим 
    TCCR1B |= (1 << CS11); TCCR1B |= (1 << CS10);  // включить делитель /64
    TIMSK1 |= (1 << OCIE1A);                       // включить прерывание по совпадению таймера 
    
// ====================================================================


  adcInit();
  sei();
    
  while(1) {
      //V = ((V << IIR_SHIFT) - V + adcValue(1, AdcReadLinearized)) >> IIR_SHIFT;
      //V = adcValue(1, AdcReadLinearized);
      V0 = adcOversampledValue[0];
      V1 = adcOversampledValue[1];
      
      bin_bcd(V0); // закидываем прочитанное значение АЦП в функцию bin_bcd, результат отправляется в структуру bcd
      Counters[7] = bcd.thousands;
      Counters[6] = bcd.hundreds;
      Counters[5] = bcd.tens;
      Counters[4] = bcd.units;
      
      bin_bcd(V1);

      Counters[3] = bcd.thousands;
      Counters[2] = bcd.hundreds;
      Counters[1] = bcd.tens;
      Counters[0] = bcd.units;
      
      
      UART_puts("ADC1 :  "); UART_putU16(V0); UART_puts(" "); 
      UART_puts("ADC2 :  "); UART_putU16(V1); 
      UART_puts("\n");
      _delay_ms(50);
    
  }
 return 0;
}

// Прерывание по таймеру 1000Hz для 7 seg =============================
ISR(TIMER1_COMPA_vect) {
    SPDR = pgm_read_byte(&DigiMass[n_count]);           while(!(SPSR & (1<<SPIF)));
    SPDR = pgm_read_byte(&SegmMass[Counters[n_count]]); while(!(SPSR & (1<<SPIF)));
    PORTB |= (1<<PB2); asm ("nop"); PORTB &= ~(1<<PB2);
    n_count++;
    if (n_count>9) n_count=0;
}


// Раскладываем 9999 на цифры
void bin_bcd(uint16_t a) {
    bcd.tens=0;
    bcd.hundreds=0;
    bcd.thousands=0;
    bcd.units=a;
    if(bcd.units>=1000) {
      while (bcd.units>=1000) {
        bcd.units-=1000;
        bcd.thousands++;
      }
    }
    if(bcd.units>=100) {
      while (bcd.units>=100) {
        bcd.units-=100;
        bcd.hundreds++;
      }
    }
    if(bcd.units>=10) {
      while (bcd.units>=10) {
        bcd.units-=10;
        bcd.tens++;
      }
    }
}


// ----------------------------------------------------------------------------- 
//
ISR(ADC_vect)  {
  static uint32_t adcOversampleSum[adcChannels];
  static uint8_t adcOversampleCounter[adcChannels];
  adcOversampleSum[adcChannel] += ADC;

  if (++adcOversampleCounter[adcChannel] > adcOversampleCount - 1) {
    adcOversampledValue[adcChannel] = adcOversampleSum[adcChannel] >> adcOversampleShift;
    adcOversampleSum[adcChannel] = 0;
    adcOversampleCounter[adcChannel] = 0;
  }
  // next channel
  adcChannel = (adcChannel + 1) % adcChannels;
  adcSelectChannel(adcChannel); 
  // no dummy read after switch results is the necessary noise for oversampling
}

// ----------------------------------------------------------------------------- 
// if we were to wait long engough after power on
// for the voltage to stabilize we can measure 
// the internal 1.1V reference against Vcc == Vref
// and calculate a compensation factor 'adcAVcc'.
//
void adcCalculateReferenceCompensationFactor() {
  cli();
  adcEnableExternalVref();
  adcSelectChannel(0xe);
  adcEnable();
  _delay_ms(500);
  // dump intial dummy conversion
  adcStartConversion();
  adcWaitForConversion();
  adcAVcc = ADC; // ADC must be read, says datasheet
  adcAVcc = 0;   // TODO: check if the compiler swallows something here
  int i = 0;
  for (; i < 5; i++) {
    adcStartConversion();
    adcWaitForConversion();
    adcAVcc += ADC;
  }
  sei();
  adcAVcc = 1126400L / (adcAVcc / 5); // mV compared to Vref
}

// ----------------------------------------------------------------------------- 
void adcInit(void) {
  adcChannel = 0;

  adcLoadCalibrationData();
  adcEnableExternalVref();
  adcSelectChannel(adcChannel);  
  
  // ADCSRA |= (1<<ADPS2) | (1<<ADPS1); // prescale clk/64 = 125kHz@8Mhz
  ADCSRA |= (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); // prescale clk/128 = 62.5kHz@8Mhz
  DIDR0  |= (1<<ADC1D) | (1<<ADC0D); // disable digital inputs on ADC pins
  adcEnable();
  adcEnableInt();
  ADCSRA |= (1<<ADATE); // auto trigger enable
  ADCSRB = 0x00;        // free running mode
  adcStartConversion();
}
// ----------------------------------------------------------------------------- 
uint32_t adcValue(uint8_t channel, AdcValueType_t valuetype) {
  cli();
  uint32_t v = adcOversampledValue[channel];
  sei();
  if (valuetype == AdcReadLinearized) {
    v = (v * adcFactor(channel) + adcCorrection(channel) + adcRoundingCorrection) >> adcScale;
  }
  return v;
}
// ----------------------------------------------------------------------------- 
// result in m°C: 21500 / 1000 = 21.5°C
//
uint16_t adcInternalTemperature() {
  uint16_t result = 0;
  cli();
  adcEnableInternalVref();
  adcSelectChannel(8); // thermometer
  _delay_ms(5);
  // dummy conversion
  adcStartConversion();
  adcWaitForConversion();
  result = ADC;
  adcStartConversion();
  adcWaitForConversion();
  result = ADC;
  sei();
  return (result - 125) * 1075;
}
// ----------------------------------------------------------------------------- 
void adcLoadCalibrationData(void) {
  do {} while (!(eeprom_is_ready()));
  eeprom_read_block(&adcCalibration, &eeAdcCalibration, sizeof(adcCalibration));
}

// ----------------------------------------------------------------------------- 
void adcSaveCalibrationData(void) {
  do {} while (!(eeprom_is_ready()));
  eeprom_write_block(&adcCalibration, &eeAdcCalibration, sizeof(adcCalibration));
}
// -----------------------------------------------------------------------------




///zzzz
// UART ===============================================================
// ====================================================================
// Все преобразования надёргал из http://www.rjhcoding.com/avrc-uart.php
void UART_putc (unsigned char data) {
        while (!( UCSR0A & (1<<UDRE0)));  // Wait for empty transmit buffer
        UDR0 = data;                      // Put data into buffer, sends the data
}
// ====================================================================
void UART_puts(char* s) {
    while(*s > 0) UART_putc(*s++);        // transmit character until NULL is reached
}
// ====================================================================
void UART_puthex8(uint8_t val) {
    // extract upper and lower nibbles from input value
    uint8_t upperNibble = (val & 0xF0) >> 4;
    uint8_t lowerNibble =  val & 0x0F;
    // convert nibble to its ASCII hex equivalent
    upperNibble += upperNibble > 9 ? 'A' - 10 : '0';
    lowerNibble += lowerNibble > 9 ? 'A' - 10 : '0';
    // print the characters
    UART_putc(upperNibble);
    UART_putc(lowerNibble);
}
// ====================================================================
void UART_puthex16(uint16_t val) {
    UART_puthex8((uint8_t)(val >> 8));        // transmit upper 8 bits
    UART_puthex8((uint8_t)(val & 0x00FF));    // transmit lower 8 bits
}
// ====================================================================
void UART_putU8(uint8_t val) {                // Transmitting Decimal Values
    uint8_t dig1 = '0', dig2 = '0';
    // count value in 100s place
    while(val >= 100) {
        val -= 100;
        dig1++;
    }
    // count value in 10s place
    while(val >= 10) {
        val -= 10;
        dig2++;
    }
    // print first digit (or ignore leading zeros)
    if(dig1 != '0') UART_putc(dig1);
    // print second digit (or ignore leading zeros)
    if((dig1 != '0') || (dig2 != '0')) UART_putc(dig2);
    // print final digit
    UART_putc(val + '0');
}
// ====================================================================
void UART_putS8(int8_t val) {
    // check for negative number
    if(val & 0x80) {
        // print negative sign
        UART_putc('-');
        // get unsigned magnitude
        val = ~(val - 1);
    }
    // print magnitude
    UART_putU8((uint8_t)val);
}
// ====================================================================
void UART_putU16(uint16_t val) {
    uint8_t dig1 = '0', dig2 = '0', dig3 = '0', dig4 = '0';
    // count value in 10000s place
    while(val >= 10000) {
        val -= 10000;
        dig1++;
    }
    // count value in 1000s place
    while(val >= 1000) {
        val -= 1000;
        dig2++;
    }
    // count value in 100s place
    while(val >= 100) {
        val -= 100;
        dig3++;
    }
    // count value in 10s place
    while(val >= 10) {
        val -= 10;
        dig4++;
    }
    // was previous value printed?
    uint8_t prevPrinted = 0;
    // print first digit (or ignore leading zeros)
    if(dig1 != '0') {UART_putc(dig1); prevPrinted = 1;}
    // print second digit (or ignore leading zeros)
    if(prevPrinted || (dig2 != '0')) {UART_putc(dig2); prevPrinted = 1;}
    // print third digit (or ignore leading zeros)
    if(prevPrinted || (dig3 != '0')) {UART_putc(dig3); prevPrinted = 1;}
    // print third digit (or ignore leading zeros)
    if(prevPrinted || (dig4 != '0')) {UART_putc(dig4); prevPrinted = 1;}
    // print final digit
    UART_putc(val + '0');
}
// ====================================================================
void UART_putS16(int16_t val) {
    // check for negative number
    if(val & 0x8000) {
        // print minus sign
        UART_putc('-');
        // convert to unsigned magnitude
        val = ~(val - 1);
    }
    // print unsigned magnitude
    UART_putU16((uint16_t) val);
}
// end UART functions
// ====================================================================
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 07 Окт., 2020, 13:16
UART пока только для отладки.
У меня есть китайский генератор (FYT3200S) могу попробовать подмешать с него что-нибудь на опору, какое напряжение и сигнал? Подключать также - 33 кОм + 0,1 мкФ?
Не знал про ADC6/7 в QFP, да, если от AVCC они питаются думаю результат лучше будет.

Цитата: Slabovik от 07 Окт., 2020, 11:40Первая - всё-равно программа "тупо ждёт", когда из буфера выйдет всё, чтобы вывалить туда следующий байт, вторая - всё-равно вручную дёргается PB2 (SS). Ну, программа короче, да...
Разговор про while(!(SPSR & (1<<SPIF)));? Из этого куска:
ISR(TIMER1_COMPA_vect) {
    SPDR = pgm_read_byte(&DigiMass[n_count]);           while(!(SPSR & (1<<SPIF)));
    SPDR = pgm_read_byte(&SegmMass[Counters[n_count]]); while(!(SPSR & (1<<SPIF)));
    PORTB |= (1<<PB2); asm ("nop"); PORTB &= ~(1<<PB2);
    n_count++;
    if (n_count>9) n_count=0;
}
Не знаю, делать софтовую реализацию SPI? Мне кажется под вопросом.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 07 Окт., 2020, 14:18
Да, этот "while" и ожидает появления бита "передача окончена". В принципе, можно и прерывание организовать по окончании передачи, но... будет ли это колхоз проще.
Вот и получается, что этот SPI не совсем аппаратный, а "полу". Байт на биты аппаратно раскладывает, но более не умеет.

Подмес шума наверное надо организовать по схеме из аппнота. В этом случае с генератора достаточно подать меандр. Амплитуда помехи на ARef должна быть где-то 2,5 вольта опоры / 1024 отсчёта = 2,5 мВ, можно немного больше. Помеха не должна быть высокочастотной, чтобы ARef за время преобразования (~15 циклов тактовой АЦП - она ниже тактовой ядра на коэффициент деления) ARef не сместился куда-то далеко. Полагаю, что-нибудь в районе килогерца будет нормально. Если смотреть осциллографом с закрытым входом, там на конденсаторе сигнал должен быть близким к треугольнику (при меандре на входе всей системы).

Код оверсемлинга я тоже что-то сходу не пойму, что они там делают. Надо на бумажке попробовать его "пошагать". Я могу рассказать, как работает мой код, но у меня асм   :-[  хотя вычислительные приёмы - они языконезависимы.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 07 Окт., 2020, 15:36
Мой вариант опроса (без оверсэмплинга) ADC такой:
ISR(ADC_vect) {
    if (trueValue) {
      if (adc_channel==0) { adc_buffer[0] = ADCL | (ADCH << 8); ADMUX = 1; } //ADMUX = (0b00<<REFS0)|1;    // ADC1 pin 24
      if (adc_channel==1) { adc_buffer[1] = ADCL | (ADCH << 8); ADMUX = 3; } //ADMUX = (0b00<<REFS0)|3;    // ADC3 pin 26
      ++adc_channel;
      if (adc_channel>1) { adc_channel = 0; }
      trueValue = false; // Устанавливаем флаг смены входного пина
    }
    else { trueValue = true; } // Первый раз пропускаем считывание и устанавливаем флаг на чтение в следующий раз
}
Тут не обязательно было делать
adc_buffer[0] = ADCL | (ADCH << 8); ADMUX = 1;
можно проще adc_buffer[0] = ADC;.
Можно твой кусок на ассемблере попробовать сюда включить, заодно посмотрел бы как вставка asm(); работает
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 07 Окт., 2020, 19:02
Страшно мне смотреть на выкрутасы с ADMUX. Знаю, что для данного применения нормально, один фиг там управляющие биты по нулям и от нулей отличаются только сами MUX, но... правильно было бы читать ADMUX, изменять MUX (сле-едующий! :) ) и пихать его обратно. Ну или применять здесь дефайны настройки АЦП. При этом завязать номер MUX на переменную-указатель adc_channel (хотя, вынимая номер из MUX всегда можно знать, какой АЦП сейчас был включен.

В реальности же это работоспособности не вредит, только "причёсывает" стиль написания.

А есть .lss этого кусочка? Хочется посмотреть, как оно в машинные команды оттранслировалось...
В asm у меня процедура чтения длинная, но я там все 8 каналов щёлкаю, раскладывая по кольцевым буферам, вряд ли для вставки в Си оно пригодно, разве что только сам кусочек, где непосредственно опрос идёт, но я его приводил ранее.

зы: что я ни делаю, у меня только танк и получается. Напильники наверное не той системы  :-\
Наваял-10-07.png
Две 595 обязательные, третью в общем-то вполне могут заменить либо свободные разряды PC, либо PD полностью отдать. Но третья 595 мне нравится тем, что с ней индикатор меняет своё состояние абсолютно синхронно (это конечно не критично в данном изделии, просто греет ЧСВ). Можно повесить ещё пару светиков. Зачем - не знаю, но имеющиеся два нахожу приятными. Их можно расположить рядом с индикаторами и гореть ими, отображая CC/CV или ещё что-нибудь. Программно, конечно.

А, да... Разъёмчик AntuBug справа - это для возможности нарастить длину индикатора. Иногда полезно в отладочных целях плюнуть туда что-нибудь для проверки.

зызы: "выпихивание" 8 байтов в индикатор чисто программным способом у меня занимает порядка 70 микросекунд при тактовой 14,3 МГц.
AsmCode_Burst_8_bytes.png
 Сможешь посмотреть, как быстро выплёлывает аппаратный SPI? Только для этого SS нужно опускать прямо перед процедурой, а поднимать после (а то ты его сразу же опускаешь после поднятия) - тогда осциллографом легко определить, сколько времени это всё занимает.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 07 Окт., 2020, 20:56
Насмешил про танк! Думал 8 транзисторов в имеющуюся схему внедрить, а у тебя вон она как, может всё-таки 8 ключей?
Не знаю получится ли найти осциллографом что-то.
Это надо сократить код как можно сильнее и сделать плевки с паузой так?
Про lss, у меня Makefile каст сокращенный, но внедрил, сейчас при сборке генерирует lss... Только как и какой кусочек нужен/выдрать?
У меня ж среда - Geany, если что. :)

Цитата: Slabovik от 07 Окт., 2020, 19:02В asm у меня процедура чтения длинная, но я там все 8 каналов щёлкаю, раскладывая по кольцевым буферам, вряд ли для вставки в Си оно пригодно, разве что только сам кусочек, где непосредственно опрос идёт, но я его приводил ранее.
Ну так и нужен только опрос.
Хотя не думаю, что сильный профит будет.
ы. Что-то редко я осциллографом пользуюсь... Встал на data.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 07 Окт., 2020, 21:30
Я вот про этот кусочек кода
ISR(TIMER1_COMPA_vect) {
    SPDR = pgm_read_byte(&DigiMass[n_count]);           while(!(SPSR & (1<<SPIF)));
    SPDR = pgm_read_byte(&SegmMass[Counters[n_count]]); while(!(SPSR & (1<<SPIF)));
    PORTB |= (1<<PB2); asm ("nop"); PORTB &= ~(1<<PB2);
    n_count++;
    if (n_count>9) n_count=0;
}
Предлагаю его сделать таким
ISR(TIMER1_COMPA_vect) {
    PORTB &= ~(1<<PB2);                                 // опускаем строб RDY
    SPDR = pgm_read_byte(&DigiMass[n_count]);           // засылаем первый байт
    while(!(SPSR & (1<<SPIF)));                         // ожидаем освобождение буфера
    SPDR = pgm_read_byte(&SegmMass[Counters[n_count]]); // засылаем второй байт
    while(!(SPSR & (1<<SPIF)));                         // снова ждём, когда байт уйдёт из буфера
    PORTB |= (1<<PB2);                                  // поднимаем строб RDY, защёлкивая данные на выход 595
    n_count++;
    if (n_count>9) n_count=0;
}
По опусканию строба PB2 (RDY, Latch и т.п.) можно чисто визуально наблюдать при помощи осциллографа, что происходит именно эта процедура и, соответственно, измерить, сколько времени она занимает да и вообще как выглядит. Вторым лучом можно Clock смотреть или Data.

Кстати, метод работает при отладке чего-нибудь ещё. Когда надо посмотреть, какое место кода у тебя выполняется и сколько это занимает, опускаешь (поднимаешь) ножку какого-нибудь незадействованного порта, а по окончании поднимаешь (опускаешь).

Не, код вот такой как есть - он самый короткий. Тут просто чисто удивление, что ресурс вроде аппаратный (т.е. работать умеет полностью параллельно), но программа крутит пустой цикл и ждёт, пока "железо" отработает. Второй минус этого решения - привязка к конкретным ножкам. Впрочем, UART тоже можно в этом же режиме использовать (для засылки данных в 595), но суть остаётся та же.

А покажи просто, чего там вообще в .lss. Он выглядит как листинг с кодами? .lss ассемблерного файла выглядит так

Вид-на-lss.png

Сишный должен быть похожим по структуре. Конечно, метки и имена будут нечеловеческие, но кое-что можно подсмотреть.

Восемь транзисторов - это если один регистр с данными делать на оба индикатора. При этом скважность 1:8 получается, соответственно, яркость падает. Думаю, 1:4 всё-таки лучше, а значит, четыре ключа.
Регулировку яркости, кстати, легко ввести. Входы OE нужно от земли оторвать, объединить и подать на них PWM с частотой, кратной частоте смены знака. При таком построении регулироваться будет всё, и светодиоды в том числе.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 07 Окт., 2020, 21:41
Выше отредактировал.
adc_oversamling.zip Всё вместе.
atmrea328p_spi_7seg_only_01.zip lss от кода который только шлёт по SPI.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 07 Окт., 2020, 22:24
Во, отличная инфа для размышления. Завтра помедитирую :)

зы: не совсем понятно, это один бит даты или два... лучше Clock в таком ракусре глянуть, ну, или чуть переделать процедуру и посмотреть RAY (он же Latch, он же PB2)
зызы: фронты малёха звенят. От звона хорошо помогают резисторы небольшого сопротивления (22~220 Ом), включенные недалеко от выходов (на моей схеме наверное видел). На крутизну они особо не влияют, но со звоном (суть - паразитные колебания, как в искровом передатчике) борются хорошо.

:-\ гляжу на STM32 и... полная непонятка а) что у них в качестве основной среды. Многие кивают на IAR, но... там лицензия на раб. место 3к$. Есть небольшой зоопарк из свободного ПО, но не ясно, что с отладкой и т.д. б) полная ж - у них шаг выводов 0.5мм. Если 0.8 на ATмеге ещё терпимо и можно даже на самодельную плату (ЛУТ без маски) их лепить, то что делать с 0.5 - вопрос больной... в) есть всякие платки, но...  вот глядел сегодня на витрины. Нашёл такие (https://www.chipdip.ru/product/nucleo-f030r8), относительно недорого. С чем их едят?
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 07 Окт., 2020, 22:50
А зачем такую плату, не, надо народную BluePill, зоопарк с IDE у них это прям беда и сплошные холивары, пытаюсь Makefile прикрутить, руки не доходят.
В своё время взял по 5 штук BluePiill и BlackPill, ну и десяток таких же камней для них.
Вот собирал Hldi, там GL850 (USB хаб) ещё торчит, не до конца собрал, потому что нет ещё транзисторов на IRF24N15 (мотор на 42в), но уже прошил и проверил работу энкодера, лазера.
Индуктивности - то что было. :)
ы. От звона в разрез линий PB2, PB3, PB5 резисторы 22-220 Ом, ближе к МК, так?
ыы. uart_04_atmega328p_adc_spi.zip - без оверсэмплинга, с твоим вариантом SPI, все файлы.
ыыы. 0,5 мм фоторезистом с принтером у меня нормально получалось, тут основные претензии к шаблону, лазерный принтер, который тонер не жалеет и пары ацетона (или аэрозоль) + сноровка, если нет ламинатора то лучше жидкий фоторезист, если есть Ordyl A340, с нашим НПВЩ - или как его - одно мучение...
4ы. Добавил осциллограмку двумя щупами.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 08 Окт., 2020, 15:51
Проведя немного времени за чтением документации, я правильно понимаю, что если я желаю выбраться по таймеру из режима Idle то нужно использовать второй таймер, т.к. системный клок останавливается, а второй таймер может работать от своего собственного клока (настроить надо). Т.е. нулевой и первый таймеры в режиме (команда SLEEP) останавливаются вместе с ядром?

Если это так, то получается, что для введения шума в ARef, также годится только второй таймер, а единственный его выход приходится на PD3, потому как PB3 желает занять аппаратный MOSI (Data)
↓ спойлер ↓
поэтому я ностальгирую по Z80, у которого только ядро, а нужное довешивать надо, но зато оно (довешенное) может работать независимо (в меру возможности и зависимости от CPU) друг от друга. Здесь же идеология наоборот - внутри есть много чего, но выбрать можно лишь что-то одно...
[свернуть]
Я правильно догадываюсь, или нет?

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

В общем, вот такая дилемма. Задействовав хардверный SPI (MOSI и SCK), лишаемся возможности повесить на эти выводы кнопки (знаю, у тебя их не было, но в моём варианте (https://anklab.ru/forum/index.php?topic=43.0), они именно там по соображениям, что они уживаются с разъёмом PGM), и если они будут нужны, придётся "распечатывать" PD.

Гы: генератор "шума" можно и на микросхемке сделать. Однако учитывая современную корпусобоязнь (ни одним корпусом больше!), решение не станет популярным. Или сделать из принципа "потому что я могу"?

Цитата: zenon от 07 Окт., 2020, 22:50Добавил осциллограмку двумя щупами.
Во, теперь понятно. Весьма шустро получается, и картинка как по учебнику. В 2 с небольшим раза быстрее, чем софтверный.
↓ спойлер ↓
; === ShowDigits выводит 7-сегментный код, находящийся в буфере, на индикатор
; === регистр X (R26, R27) портится - используется как указатель на буфер
ShowDigits:	LDI XL,low(Digits2Led + Total_Digits)
		LDI XH,high(Digits2Led + Total_Digits)
ShowDigits01:	;PUSH R2		; пусть в R2 располагается маска-счётчик для проверки
		LDI R16,Total_Digits
		CBI PORTB,RDY	; бита, ответственного за засветку сегмента
ShowDigits02:	CLR R2		; Задвинем туда '1' и будем сдвигать, пока она не
		SEC		; вылезет с другой стороны байта
		ROR R2		; вот, установили 1 в старшем разряде маски (R2)
		LD R1,-X	; в R1 помещаем код, отсылаемый в сегменты знакоместа
ShowDigits03:	CBI PORTB,Clock
		ROL R1
		BRCS ShowDigits04	; переход - это два цикла, нет перехода - один
		NOP			; поэтому вставляем компенсирующий дрожание NOP
		CBI PORTB,Data
		RJMP ShowDigits05
ShowDigits04:	SBI PORTB,Data
		NOP		; два NOP в этой ветке компенсируют задержку
		NOP		;  от одного RJMP в соседней ветке
ShowDigits05:	SBI PORTB,Clock
		ROR R2		; можно было бы вставить LSR чтобы в R2 слева вдвигался 0, но что там бывают 1 никак не влияет на счёт битов
		BRCC ShowDigits03	; между битами Data 12 циклов
		DEC R16
		BRNE ShowDigits02
		SBI PORTB,RDY	; поднимем строб-защёлку, чтобы отобразить загруженные данные
		RET
Если маленько переработать, можно ускорить процентов на 20, но это будет предел. От счётчика в R2 можно полностью избавиться, задвигая в R1 "1" через флаг переноса при прочтении (-X), а в конце цикла проверяя R1 на нуль. Пара-тройка команд из цикла точно уберётся.
[свернуть]
после переработки
После убирания R2, стало короче. Эта процедура теперь выполняется 820 циклов против 953 в варианте, показанном выше. Но зато чуть длиннее - это способ избавиться от одного джампа, отъедающего время. Увы, за всё приходится платить. Можно ещё сократить время выполнения на 64 цикла, убрав NOP'ы перед поднятием Clock
; === ShowDigits выводит 7-сегментный код, находящийся в буфере, на индикатор
; === регистры R1, R16, X (R26,R27) портятся
; на выходе X показывает на начало буфера

ShowDigits:	LDI XL,low(Digits2Led + Total_Digits)
		LDI XH,high(Digits2Led + Total_Digits)
ShowDigits01:	LDI R16,Total_Digits	; в R16 cчётчик выводимых знакомест
		CBI PORTB,RDY		; опустим RDY
ShowDigits02:	LD R1,-X		; прочитаем знакоместо и задвинем туда '1'
		SEC			; сдвигать, пока она не вылезет с другой стороны байта
		ROL R1			; вот, установили 1 в младшем разряде.
ShowDigits03:	CBI PORTB,Clock		; в "CF" - значение для подачи в линию "Data"
		BRCS ShowDigits04	; переход - это два цикла, нет перехода - один
		NOP			; поэтому вставляем компенсирующий дрожание NOP
		CBI PORTB,Data
		NOP			; пауза перед подъёмом "Clock"
		SBI PORTB,Clock
		LSL R1
		BRNE ShowDigits03	; если задвинутая вначале "1" вылезла
		DEC R16			; переходим к следующему знаку
		BRNE ShowDigits02
		SBI PORTB,RDY		; поднимем строб-защёлку, чтобы отобразить загруженные данные
		RET

ShowDigits04:	SBI PORTB,Data
		NOP			; пауза перед подъёмом "Clock"
		SBI PORTB,Clock
		LSL R1
		BRNE ShowDigits03	; если задвинутая вначале "1" вылезла
		DEC R16			; переходим к следующему знаку
		BRNE ShowDigits02
		SBI PORTB,RDY		; поднимем строб-защёлку, чтобы отобразить загруженные данные
		RET
убрав NOP'ы получаем 756 циклов на весь 8-разрядный индикатор
; === ShowDigits выводит 7-сегментный код, находящийся в буфере, на индикатор
; === регистры R1, R16, X (R26,R27) портятся
; на выходе X показывает на начало буфера

ShowDigits:	LDI XL,low(Digits2Led + Total_Digits)
		LDI XH,high(Digits2Led + Total_Digits)
ShowDigits01:	LDI R16,Total_Digits	; в R16 cчётчик выводимых знакомест
		CBI PORTB,RDY		; опустим RDY
		SEC			; прочитаем знакоместо, задвинем в мл.разряд '1' и будем
ShowDigits02:	LD R1,-X		; сдвигать, пока она не вылезет с слева
		ROL R1			; вот, 1 в младшем разряде, а в CF бит для Data
ShowDigits03:	CBI PORTB,Clock		; опустим Clock
		BRCS ShowDigits04	; переход - это два цикла, нет перехода - один
		NOP			; поэтому вставляем компенсирующий дрожание NOP
		CBI PORTB,Data
		LSL R1
		SBI PORTB,Clock		; поднимем Clock
		BRNE ShowDigits03	; если задвинутая вначале "1" вылезла (ZF=1,CF=1)
		DEC R16			; переходим к следующему знаку
		BRNE ShowDigits02
		SBI PORTB,RDY		; поднимем строб-защёлку, чтобы отобразить загруженные данные
		RET

ShowDigits04:	SBI PORTB,Data
		LSL R1
		SBI PORTB,Clock		; поднимем Clock
		BRNE ShowDigits03	; если задвинутая вначале "1" вылезла (ZF=1,CF=1)
		DEC R16			; переходим к следующему знаку
		BRNE ShowDigits02
		SBI PORTB,RDY		; поднимем строб-защёлку, чтобы отобразить загруженные данные
		RET
[свернуть]
Итого: было 953, стало 749, быстрее на 21% :)
[свернуть]

о технологиях плат
с фоторезистом я баловался, но не смог получить стабильный результат. И от принтера нужной плотности не смог добиться (не от одного, их было много, лазерники, струйники). Минусом послужила ещё необходимость держать "химию", да и и фоторезист портится со временем (пара рулонов лежит, выбросить рука не поднимается). Т.е. нужна массовость, которой у меня нет и не предвидится. Поэтому редкие единичные платы ЛУТ'ом, остальное на завод - там маску сделают, металлизацию...
[свернуть]
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 08 Окт., 2020, 18:55
Мне затея с засыпанием не очень нравиться.
Была где-то ветка со сравнением работы так и так, вывод был одинаково, что так, что так.
Но проверить надо.
+++
Trigger по SPI если в осциллографе поставить это тремя/двумя щупами же надо?
Путаюсь я в названиях, кто из них кто, в осциллографе SCL,SDA,CS, When по timeout`у или по CS.
+++
Вот еще что вспомнил, - даже пару слов писал на эту тему: из BluePill stm32 можно сделать отладчик Black Magic Probe, я его сделал, но дальше не продвинулся. :)
Вот на хабре есть (http://xn--80aaaaknbcaapcpf0cjhhdcatp9akcqgv6lwexbg1f0a.xn--
-,,,-46gbaaaabbbffdncfacl1ambpcda5heh0djcidcg0a6a6bbsvekidfbbd2fi3cb8cdsadtbdb06ag4u4aje.xn--
-83dlpg1a1cdiaghqd1b8n.xn--
+++
triggerspi-87qa0ccx6cdc7fbf4gda7b6chdddvq5bi1aek3cdab8gye1q1b4g/%D0%B4%D0%B2%D1%83%D0%BC%D1%8F%D1%89%D1%83%D0%BF%D0%B0%D0%BC%D0%B8%D0%B6%D0%B5%D0%BD%D0%B0%D0%B4%D0%BE?%3Cbr%3E%D0%9F%D1%83%D1%82%D0%B0%D1%8E%D1%81%D1%8C%D1%8F%D0%B2%D0%BD%D0%B0%D0%B7%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%D1%85,%D0%BA%D1%82%D0%BE%D0%B8%D0%B7%D0%BD%D0%B8%D1%85%D0%BA%D1%82%D0%BE,%D0%B2%D0%BE%D1%81%D1%86%D0%B8%D0%BB%D0%BB%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B5SCL,SDA,CS,When%D0%BF%D0%BEtimeout%60%D1%83%D0%B8%D0%BB%D0%B8%D0%BF%D0%BECS.%3Cbr%3E+++%3Cbr%3E%D0%92%D0%BE%D1%82%D0%B5%D1%89%D0%B5%D1%87%D1%82%D0%BE%D0%B2%D1%81%D0%BF%D0%BE%D0%BC%D0%BD%D0%B8%D0%BB,-%D0%B4%D0%B0%D0%B6%D0%B5%D0%BF%D0%B0%D1%80%D1%83%D1%81%D0%BB%D0%BE%D0%B2%D0%BF%D0%B8%D1%81%D0%B0%D0%BB%D0%BD%D0%B0%D1%8D%D1%82%D1%83%D1%82%D0%B5%D0%BC%D1%83:%D0%B8%D0%B7BluePillstm32%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C%D0%BE%D1%82%D0%BB%D0%B0%D0%B4%D1%87%D0%B8%D0%BABlackMagicProbe,%D1%8F%D0%B5%D0%B3%D0%BE%D1%81%D0%B4%D0%B5%D0%BB%D0%B0%D0%BB,%D0%BD%D0%BE%D0%B4%D0%B0%D0%BB%D1%8C%D1%88%D0%B5%D0%BD%D0%B5%D0%BF%D1%80%D0%BE%D0%B4%D0%B2%D0%B8%D0%BD%D1%83%D0%BB%D1%81%D1%8F.:)%3Cbr%3E%D0%92%D0%BE%D1%82%D0%BD%D0%B0%D1%85%D0%B0%D0%B1%D1%80%D0%B5%D0%B5%D1%81%D1%82%D1%8C.).
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 09 Окт., 2020, 14:54
Бумажка - вещь крайне необходимая. И не только в плане гигиены. Ещё она думать помогает и на ней можно рисовать :)

Введение шума в Ref имеет один крайне неприятный подводный камень в плане применения к AVR. Всё дело в её конструкции: Ref имеет только один вывод. Второй вывод Ref жёстко сидит на земле. И это перечёркивает всю задумку. Вот, полюбуйтесь

К-вопросу-о-тщетности-бытия.png

Суть проста. Если мы модулируем Ref только с одного конца (в нашем случае Ref+), то "сетка" из уровней - она как гармошка растягивается и сжимается. И получается, что Ref-, будучи привязанным к земле, не даёт смещаться нижним уровням. Если мы верхний уровень (Ref+) смещаем на 1 отсчёт (один младший разряд), то близкие к нижу уровни смещаются только на 1/1023 отсчёта. Т.е. вообще никак. Серединка смещается на 0.5 отсчёта...

Вывод: в данном применении не годится. Вот на PIC'ах было бы нормально - там выведены оба конца Ref (Ref+ и Ref-) и их можно двигать синхронно.
Цитата: zenon от 08 Окт., 2020, 18:55затея с засыпанием не очень нравится.
Смотря как на неё смотреть. Есть разные варианты реализации. Один - это крутить цикл, отвлекаясь на прерывания (опрос АЦП, вывод). В цикле можно что-нибудь делать, если есть чего. Вся работа по прерываниям - там и делается всё полезное.
Второй вариант - не использовать прерывания вообще, разве что для синхронизации на вывод. Программа просто бегает по кругу опрос-расчёт-вывод. Засыпание не нужно (старые компьютеры как-то обходились без системы прерываний: Радио-86, Орион-128 и т.п.)

Если опираться на прерывания, то засыпание - это хорошая замена пустому циклу (делать-то всё-равно нечего). В втором случае это отличный способ синхронизировать бег по кругу с выводом на индикатор. В общем, вариантов много, спать не обязательно, просто иногда с ним (уходом в сон) проще.

А вывод конечно одинаковый для глаза будет, тут вопросов нет.
Цитата: zenon от 08 Окт., 2020, 18:55Trigger по SPI если в осциллографе поставить это тремя/двумя щупами же надо?
Триггер удобно поставить по ~|_ на том канале, который на CS (Ready/ST/PB2) смотрит. Тогда легко увидеть весь бурст.

ps. В попытках разобраться, как правильно округлять, сподобился изобразить покрасивее.
Вот график, на котором я попытался изобразить дизер.
↓ спойлер ↓
Дизер-графики.png
[свернуть]
Цветные линии - напряжения на АЦП. Кружочки - отсчёты. Данные отсчётов в квадратиках выше и ниже - они в двоичном коде.
Если без дизера, то результаты измерений были бы 0, 1 и 2. Ориентироваться можно по широким ровным полосам на фоне.
А вот с дизером становится много интереснее, отсчёты перескакивают, но ведь именно это нам и надо ;)

В общем, исходное разрешение было 1. Делаем 16 отсчётов с целью увеличить разрешение вчетверо. Вместо 1 цена деления станет 0.25. Дальше двоичный калькулятор в руки (ох, нравится мне эта система: палочка есть - палочки нет :) )

Расчёт первый. Учитываем флаг переноса, когда "теряем" младшие биты при сдвиге вправо.
Дизер-расчёт.png
Вроде неплохо.

Был вопрос о том, надо ли учитывать округление (тот самый CF). Пробуем без CF
Дизер-расчёт-без-CF.png
Вот и ответ. 2,63 должно однозначно округлиться к 2.75, но без учёта CF оно осталось на 2.50

Впрочем, вопрос всё ещё спорный.

Ну, а правило для вычисления, надеюсь, стало более понятным: считаем сумму, прибавляем к ней половину от количества слагаемых, делим, добавляя CF от последнего сдвига вправо. Запятую ставим где надо :)

зы... зызы... Продолжим  :) Я добавляю сюда т.к. ответов не было.
Залез в микрокап и промикрокапал мысль о вводе дизера в сигнал. Микрокап говорит, что вполне реальная вещь. Сигнал подмешиваем на выходе масштабирующего усилителя.
Дизер-схема.png
Вначале хотел "квадратный сигнал" по причине, что его получить проще. Не прокатило (ну... не мой день). Но генератор хорошего треугольника можно сделать на одном корпусе с двумя ОУ внутри - это то, что надо. И будет он абсолютно независим и некоррелирован с частотой выборки АЦП - это второе "то, что надо".

Вот что получается в итоге (по данным микрокапа, конечно)
Дизер-схема-графики.png
Сигнал на входе АЦП аккуратненько смещается "треугольником в обе стороны". Амплитуда dither'а при этом совершенно не зависит от величины измеряемого сигнала. Необходимо только выдержать ёмкости. С5 и С6 образуют RC фильтр с выходным сопротивлением мастабирующего усилителя, в то время, как С3 и С7 железно завязаны на подключенные к ним последовательно сопротивления.

Думаю, можно заморочиться сделать  ;)

А, да, ещё момент. Период дизера должен быть много меньше (в разы) времени накопления отсчётов. С другой стороны, пока АЦП работает (время, необходимое для выборки, 13 циклов тактовой частоты АЦП, которая может составлять от 50 до 200 кГц), дизер не должен изменить сигнал на заметную величину. По этой причине режим АЦП "Free Running" наверное неприемлем, нужно делать паузы.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 09 Окт., 2020, 20:38
Ну вот не хватает у меня понимания сдвигов, двоичных калькуляторов итп. И на ассемблер я смотрю и, к своему глубокому стыду ничего не понимаю  :o
Для меня функцию опроса АЦП написать на си уже праздник...
С указателями до сих пор не понимаю главного, зачем они нужны? Зачем ссылки на переменные, например типа char, они же ещё больше места занимают.
Суммируя, уходим от прерываний?
Вводим счетчики для выполнения отправки по SPI и для чтения значений АЦП?
ОУ добавить в схему я не против.
Сегодня глянул ещё опору ref192gs - она подешевле, но и чуть похуже параметры, может её вместо 431.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 09 Окт., 2020, 22:04
Ну, ассемблер - это просто. Можно вполне позаниматься. Только у него есть последствия. Как мне сказал один хороший товарищ, "твоё мышление бенадёжно испорчено ассемблером" :)  Собственно, так и есть  :)  Си мне даётся очень трудно  :-\  А когда пытался Perl осваивать, чуть совсем не свихнулся  :P  Бросил - здоровье дороже  ;D

Ссылки и указатели - это необходимые элементы языка высокого уровня. По факту, каждое объявление переменной является объявлением ссылки на неё, хотя и неявной. Явные ссылки используют для организации работ с высоким объёмом данных. Абстракция очень простая.

Переменная: я вот тебе архивчик (на пару помещений) принёс, тебе бумашка была нужна на  третьей полке. Как закончишь, я всё унесу на место.  :o

Ссылка: вот тебе адрес, гони туда (сам ножками), там третья полка, на полке бумашка, прочитай и дуй обратно к себе.

Я надеюсь, алгоритм рассчёта-то понятен?
Цитата: Slabovik от 09 Окт., 2020, 14:54считаем сумму, прибавляем к ней половину от количества слагаемых, делим, добавляя CF от последнего сдвига вправо. Запятую ставим где надо
:)

Цитата: zenon от 09 Окт., 2020, 20:38ОУ добавить в схему я не против.
Один кузовок MCP 6002 (я не ошибся? Два ОУ в одном кузове). И немного усложнить вход АЦП несколькими деталями для подмеса дизера.
Алгоритм (не на си, а так, чисто алгоритмически, на си сам будешь  ;) ) разрисую попозже, наверное уж на следующей неделе. А то завтра в поля подамся, очередную хуюмбулу точить :)

Да, REF'ы - они отличные в плане точности. Даже G вполне хватит - уже она на порядок лучше чем Tl431. Тут интересны не столько абсолютная точность, сколько температурный уход.

REF198? Она 4,096 вольта - как раз почти полный диапазон питания, саму её можно от этих же 5 вольт запитать (запаса хватает т.к. нагрузки на неё практически нет).

Есть более дешёвые LM4040 (.pdf (https://www.ti.com/lit/ds/symlink/lm4040-n.pdf)) - они в SOT23, по принципу как TL431, только без возможности регулировки.

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

Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 09 Окт., 2020, 22:38
Тут я скорее не понимаю где и как указатели использовать, если переменная объявлена глобально в функциях она тоже доступна...
Генератор наверное можно и на народной 358, мы определились будет двухдиапазонное измерение или хватит 2-х каналов АЦП?
Можно и REF198.
LM4040 у своих нашёл тыц (http://www.nvcomponent.ru/?search=ref198&search=LM4040). спс.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 09 Окт., 2020, 22:45
Если делать дизер, то без двух диапазонов нормально выходит 4 десятичных разряда. Я у себя делаю два диапазона потому что не предусматривал дизер и у меня горят всегда только три цифры. А 4-разрядный индикатор использую только для того, чтобы не двигать точку. Это заметно облегчает восприятие показаний (тут уже эргономика вмешивается).

LM358 можно попробовать. Но у неё ограничение сверху до полутора вольт, можно среднюю точку чуть пониже повесить (точка Half), резисторы примерно с отношением 6:5 (т.е. 1,2 кОм верхний, 1,0 кОм нижний). Это даст среднюю точку на уровне 5*1.0/(1.0+1.2)=2.2 вольта. Если генерации не будет, пробовать понемногу уменьшать R4 (33к на последней схеме). От его отношения к R3 зависит амплитуда треугольных колебаний, если треугольник "наткнётся" на клиппинг, генерации не будет. Чем меньше R4, тем меньше амплитуда, но больше частота. А от R5 зависит скорость роста напряжения треугольника, что тоже прямым образом влияет на частоту.

Да, указатели... Явные указатели чисто идеологически используются для сложных структур данных. Когда есть какой-то "неопределённого объёма" набор данных (в памяти, в стеке или ещё где, причём его местонахождение - не константа а меняется по мере прихода-ухода данных), то указатели становятся очень удобным средством. Самые простейшие указатели применительно к микропроцессорам - указатель стека (регистр стека) и указатель исполняемой команды (регистр адреса). Регистр стека показывает (содержит адрес) на верхушку стека (область памяти). Регистр адреса, он же Program Counter (PC), показывает на ячейку памяти, из которой выполняется очередная команда процессора.

Ведь само понятие указатель - это чисто логическая штука, она в голове.  Физически указатель - это адрес. Вот здесь
ShowDigits:	LDI XL,low(Digits2Led + Total_Digits)
		LDI XH,high(Digits2Led + Total_Digits)
ShowDigits01:	LDI R16,Total_Digits	; в R16 cчётчик выводимых знакомест
		CBI PORTB,RDY		; опустим RDY
ShowDigits02:	LD R1,-X		; прочитаем знакоместо и задвинем туда '1'
происходит загрузка указателя в регистры
		LDI XL,low(Digits2Led + Total_Digits)
		LDI XH,high(Digits2Led + Total_Digits)
Digits2Led - это адрес памяти, которая выделена под буфер "экрана". Фактически константа т.к. буфер экрана "закреплён" строго в одном месте памяти. А Ttal_Digits - это длина буфера. Таким образом указатель показывает на конец "экранной памяти", здесь это просто потому, что данные в регистры пересылать надо с конца - там так запаяно.

и затем "взятие" данных их ячейки, на которую показывает этот указатель
		LD R1,-X		; прочитаем знакоместо
т.е. мы по указателю X (бмажка с адресом) сходили по адресу, прочитали то, что там записано и переписали себе в локальную переменную (регистр) R1. С R1 дальше и будем работать.

А вот здесь вот
		LDI R16,Total_Digits	; в R16 cчётчик выводимых знакомест
тоже произошло определение локальной переменной (выделили под неё регистр R16) и присвоение этой переменной начального значения - константы Total_Digits (которая в моём случае равна 8 - это определено в самом начале программы).
Локальная она потому, что я поработаю с этим регистром (у переменной-то и имени фактически нет), а по выходу из процедуры "забуду" про данные из него - они больше не нужны и регистр можно использовать для чего-нибудь ещё.

В Си-шных эквивалентах это выглядело бы примерно так.
-----------------------------
LDI XL,low(Digits2Led + Total_Digits)
LDI XH,high(Digits2Led + Total_Digits)
~~
unsigned shortint X = (&Digits2Led)+Total_Digits;
-----------------------------
LD R1,-X
~~
R1 = --*X;
-----------------------------
LDI R16,Total_Digits
~~
char i=Total_Digits;

Поправьте меня, если я где ошибся...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 10 Окт., 2020, 00:34
Ух и разговорчик у нас получается, - пример на asm, и я неуч в си.
Перечитаю пару раз ещё пару раз, хотя вот вчера до ночи вникать пытался, что нашёл по указателям... туго.
Не я конечно же посмотрел что есть LDI, LD, CBI...
+++
Если 358 неустойчива может быть, то ладно, 6002 есть.
Мысль была на две платы разбить, дизер+масштаб_ус+шунт+опора на одной, остальное на другой, опора под вопросом.
В своем варианте вывод был двух светодиодов на всякий случай, тут я не совсем определился, LM35 можно ещё добавить, или просто терморезистор на 10k. Ну и переключение обмоток как опция, включение/отключение выхода?
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 10 Окт., 2020, 00:47
Не, 358 устойчива, просто она не совсем R2R, что при низком напряжении питания надо учитывать.
Генератор спаять и на проводках можно, чтобы просто проверить - он не сложный, пять резисторов и два конденсатора (Half тоже надо организовать).
Убедиться, что он работает (а если не работает, то наладить), да помучить немного :)

Дизер лучше на платке проца разместить. А вот масштабирующие можно и наружу. Плюс такого решения - мастабирующие не будут греться от индикаторов, да и заменить их легче (например, немного переработав или ещё чего).

Управлять релюшками, коммутирующими напряжение, с индикатора я бы не решился, да и вентилятор лучше сделать самостоятельным узлом (тиньки даже хватит, а если не бояться "тёплолампового аналога, то я там где-то схемку прорабатывал - работает прекрасно).
А вот включение-отключение выхода - вещь полезная. Сюда же можно подумать, как замер тока ограничения сделать (есть два варианта: первый - устраивать к.з, второй - измерять задающее напряжение).
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 10 Окт., 2020, 01:17
Цитата: Slabovik от 10 Окт., 2020, 00:47то я там где-то схемку прорабатывал
Собирал, помню, это я только к кикаду подступаться начал, первый вариант в аттаче, был ещё один, но почему-то кикад плату не открывает, ругается на версию.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 10 Окт., 2020, 13:23
Цитата: Slabovik от 07 Окт., 2020, 19:02Страшно мне смотреть на выкрутасы с ADMUX. Знаю, что для данного применения нормально, один фиг там управляющие биты по нулям и от нулей отличаются только сами MUX, но... правильно было бы читать ADMUX, изменять MUX (сле-едующий! :) ) и пихать его обратно. Ну или применять здесь дефайны настройки АЦП. При этом завязать номер MUX на переменную-указатель adc_channel (хотя, вынимая номер из MUX всегда можно знать, какой АЦП сейчас был включен.

В реальности же это работоспособности не вредит, только "причёсывает" стиль написания.
Вот эту строку у немца я до конца не понял, её бы посидеть покрутить...
Но она мне нравится, и работает :)
#define adc_select_channel(channel) (ADMUX = (ADMUX & (~((1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (1<<MUX0)))) | (channel))

Добавил её в свой код и изменил ADC_vect:
...
...
...
#define adc_channels    2 // read ADC0 and ADC1
#define adc_select_channel(channel) (ADMUX = (ADMUX & (~((1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (1<<MUX0)))) | (channel))
...
...
...
ISR(ADC_vect) {
      adc_buffer[adc_channel] = ADC;
      adc_channel = (adc_channel + 1) % adc_channels;
      adc_select_channel(adc_channel); 
}
...
...
...
Так лучше?
Вот такой lss получается:
// Прерывание опроса АЦП ==============================================
ISR(ADC_vect) {
 11e: 1f 92      push r1
 120: 0f 92      push r0
 122: 0f b6      in r0, 0x3f ; 63
 124: 0f 92      push r0
 126: 11 24      eor r1, r1
 128: 2f 93      push r18
 12a: 3f 93      push r19
 12c: 8f 93      push r24
 12e: 9f 93      push r25
 130: ef 93      push r30
 132: ff 93      push r31
      adc_buffer[adc_channel] = ADC;
 134: 80 91 1f 01 lds r24, 0x011F
 138: 90 e0      ldi r25, 0x00 ; 0
 13a: 20 91 78 00 lds r18, 0x0078
 13e: 30 91 79 00 lds r19, 0x0079
 142: fc 01      movw r30, r24
 144: ee 0f      add r30, r30
 146: ff 1f      adc r31, r31
 148: e0 5e      subi r30, 0xE0 ; 224
 14a: fe 4f      sbci r31, 0xFE ; 254
 14c: 31 83      std Z+1, r19 ; 0x01
 14e: 20 83      st Z, r18
      adc_channel = (adc_channel + 1) % adc_channels;
 150: 01 96      adiw r24, 0x01 ; 1
 152: 81 70      andi r24, 0x01 ; 1
 154: 90 70      andi r25, 0x00 ; 0
 156: 80 93 1f 01 sts 0x011F, r24
      adc_select_channel(adc_channel); 
 15a: ec e7      ldi r30, 0x7C ; 124
 15c: f0 e0      ldi r31, 0x00 ; 0
 15e: 20 81      ld r18, Z
 160: 20 7f      andi r18, 0xF0 ; 240
 162: 28 2b      or r18, r24
 164: 20 83      st Z, r18
}
 166: ff 91      pop r31
 168: ef 91      pop r30
 16a: 9f 91      pop r25
 16c: 8f 91      pop r24
 16e: 3f 91      pop r19
 170: 2f 91      pop r18
 172: 0f 90      pop r0
 174: 0f be      out 0x3f, r0 ; 63
 176: 0f 90      pop r0
 178: 1f 90      pop r1
 17a: 18 95      reti
+++
Начал отрисовывать, правильно?
В опору ничего не надо?
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 11 Окт., 2020, 22:14
Эту строку надо изнутри разбирать
Цитата: zenon от 10 Окт., 2020, 13:23#define adc_select_channel(channel) (ADMUX = (ADMUX & (~((1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (1<<MUX0)))) | (channel))
Вот это '<<' побитовый сдвиг влево. Запись простая: [то, что двигаем] << [на сколько двигаем].
MUX0, MUX1, MUX2 - это имена битов в регистре управления выбором канала АЦП. В числовом выражении это 0-й бит, 1-й бит и 2-й бит соответственно.
Соответственно, запись, 1<<MUX2 означает, что берём единицу и сдвигаем слево побитово на два (потому что определено, что MUX2=2)
В бинарном виде получается
00000001 << 2 = 00000100. Т.е единичка "уехала" на две позиции левее.
С MUX1 и MUX0 то же самое. Только поскольку MUX0 равен нулю, это значит что единичка никуда и не уехала - осталась сама собой.
00000001 << 1 = 00000010
00000001 << 0 = 00000001

Далее вот это '|' - операция "побитовое логическое или". Суть простая: в результате соответствующая позиция бита равна 1, если хотя бы у одного операнда в этой позиции есть 1.
Очень просто:
0 | 1 = 1,
1 | 0 = 1,
1 | 1 = 1,
0 | 0 = 0

С восемью битами то же самое, только в расчёт идут не все биты сразу, а по позициям. Напишу результат также в столбик (нагляднее)
00000100 |
00000010 |
00000001 =
00000111  Очень прикольно :) Скобка закрылась с результатом '7' (00000111bin = 7)
Но на деле интереснее именно бинарный результат.

Далее операция "~" - побитовая инверсия. В общем, те биты, что были '1', становятся нулями и наоборот.
Было 00000111, стало 11111000  :o А зачем?
А затем, что это будет применено в качестве "маски", при помощи которой биты MUX будут обнулены с дальнейшей целью записать туда новое значение так, чтобы другие биты регистра управления АЦП остались нетронутыми.
Для этого используется следующая операция: "побитовое логическое и", суть которой в том, что результат (побитовый, точно также как и в других логических операциях) равен в соответствующей позиции байта только тогда, когда биты оба операнда равны 1, иначе в бите результата будет 0. Получается тоже интересно.

У нас слева в "маске" пять единиц. Первый операнд - содержимое порта ADMUX. Второй операнд - вот эта маска.
После того, как мы их '&', результатом будут не изменённые (взаправдашние) левые пять бит из порта ADMUX (либо нули, либо единицы - нам пофиг, главное, что именно они, а не нечто другое), а справа гарантированных три нулевых бита. И позиции этих битов тютя-в-тютю на позиции номера канала АЦП (это как раз выше определили).

Далее, со всем эти результатом - пятью битами из порта ADMUX и свободным местом для номера канала, снова делаем логическое "или" уже с новым номером канала. По факту он просто прописывается (бинарно) вместо трёх нулевых битов (он конечно сам может быть нулевым, но тут главное, что эти новые биты точно соответствуют нужному нам номеру из "channel").
Ну и последнее ADMUX= - это запись всего этого результата обратно в регистр. Пять "левых" битов остались неизменными, три бита справа содержат новый номер канала. Профит  :) Ты пишешь
adc_select_channel(channel);
а компилятор это видит как
ADMUX = (ADMUX & (~((1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (1<<MUX0)))) | (channel);

зы: я в объяснении использовал три MUX, 4-й добавить туда самостоятельно, думаю, не составит проблем.
Цитата: zenon от 10 Окт., 2020, 13:23Вот такой lss получается
Видно, что переменная adc_channel (да и наверное adc_channels) двубайтовые. Это излишне - ведь там никогда не бывает больше 16 в принципе. Надо бы им в определениях чётко прописать, char мол это, и даже беззнаковый.


Схема imho нормальная. Видятся лишними C1 и C3 потому что C7 и C8 - это они и есть.

А мне вот жужжит мысль "поставь-ка DD3.1 (https://anklab.ru/forum/index.php?msg=271) первой в цепочке, а не последней, как сейчас". зачем - не пойму, просто если как изображено по схеме, первыми в "сдвиг" должны уйти данные для светодиодов и транзисторов, а потом данные на сегменты индикатора, а если переставить - светодиоды и транзисторы будем вдвигать последними. Чисто технически это совершенно пофиг, в какой последовательности ставить, но мысль основывается на том, что DD3.1 может оказаться проще чисто по разводке поставить ближе к процессору. Можно поставить себе крестик, что в этом месте вдруг что-то поменяется и быть готовым к этому.

А опора остаётся как есть. Т.е. что есть - то и ставишь. LM4040 наверное в самый раз, а если REF19x засунуть - это вообще шикарно :) Абсолютное значение напряжения роли большой не играет (это для выходного ЦАП было бы важно) т.к. всё-равно всё приводится к нему мастабирующими усилителями, крутить которыми можно как угодно. Единственное - это то, что значение опорного напряжения я бы выбирал повыше - проще получить стабильный ноль. Как раз идеальное значение в районе 4-х вольт (и уже много, 4 мВ на степ, и до питания ещё не достаёт, что опору можно прямо от этих же +5 и питать). Но и 2,5 и 3 тоже будут нормальным вариантом.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 12 Окт., 2020, 11:01
Тут получается как, - по отдельности вроде понятно, в кучу сгреблось - нет.
Вот упростим (предположим, что у нас только MUX1 и MUX0):
#define adc_select_channel(channel) (ADMUX = (ADMUX & (~((1<<MUX1) | (1<<MUX0)))) | (channel))
теперь всего может быть 00, 01, 11, 10, так?
Значит подставляя в этом сокращённом варианте в channel от 0 до 3 получим эти четыре комбинации?


Цитата: Slabovik от 11 Окт., 2020, 22:14Соответственно, запись, 1<<MUX2 означает, что берём единицу и сдвигаем слево побитово на два (потому что определено, что MUX2=2)
Не понял откуда видно, что MUX2=2?

+++
Начал разводить, пока вот это получается, дороги крупновато взял, 0,44мм, надо на десятку меньше наверное.
Поверхностным монтажом выводные потенциометры сделал, не лучший вариант, но эту плату наверное оставлю без индикаторов, те это макетом будет. В окончательном варианте индикаторы будут на другой стороне.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 12 Окт., 2020, 12:29
Цитата: zenon от 12 Окт., 2020, 11:01теперь всего может быть 00, 01, 11, 10, так?
Да, но не совсем. Комбинации из MUX1 и  MUX0 могут быть именно такими. Но т.к. в выражении формируется именно маска (как бы "закрывающая" часть битов от изменения), то результатом получается, что channel можно выбирать только из этого получившегося ряда.
↓ спойлер ↓
на самом деле это выражение всё-таки маленько "дырявое" по той причине, что маска на считанное значение ADMUX накладывается, но за значением переменной channel следить нужно программисту, не допуская её увеличение свыше, чем есть каналов у АЦП.
[свернуть]
Цитата: zenon от 12 Окт., 2020, 11:01откуда видно, что MUX2=2?
MUX2 - это имя, определённое через #define в заголовочном файла с настройками процессора. Там где-то в начале программы у тебя <include xxxx.h> есть, называется обычно по названию использованного процессора. Там внутри куча сделана дефайнов. Можно найти этот файл и посмотреть на все дефайны - оно бывает полезно.

А то, почему MUX2 равен должен быть именно 2 - это уже от аппаратуры зависит, т.к. оно внутри так работает (у атмела хорошие даташиты с описанием всех узлов, что внутри проца есть). Вот, например, на Мегу 8 (https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-2486-8-bit-AVR-microcontroller-ATmega8_L_datasheet.pdf)-ю, про устройство регистра ADMUX там на 199 странице (по-английски, конечно). На другие там такие же.
Цитата: zenon от 12 Окт., 2020, 11:01дороги крупновато взял, 0,44мм, надо на десятку меньше наверное.
Если плата планируется быть пробной, то можно не мельчить. Удобнее будет резать (я так думаю)...

А в какой размер планируешь уложиться? Как индикаторы расположить? Какой  размер индикаторов (я закладываюсь на СС56-12, 20x50 мм)
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 12 Окт., 2020, 13:25
Цитата: Slabovik от 12 Окт., 2020, 12:29А то, почему MUX2 равен должен быть именно 2
Ну так я первым делом смотрел значения из таблиц и не пойму где тут MUX2=2? Ведь  только 0 или 1 может быть?
Вот себе выписывал:
//          7    6    5    4    3    2    1    0
//ADMUX = REFS1 REFS0 ADLAR  –    MUX3  MUX2  MUX1  MUX0
///////////////////////////////
//  3    2    1    0
// MUX3  MUX2  MUX1  MUX0
//  0    0    0    0      ADC0
//  0    0    0    1      ADC1
//  0    0    1    0      ADC2
//  0    0    1    1      ADC3
// ....
//  0    1    1    0      ADC6
//  0    1    1    1      ADC7
Размер платы пока не планировал, корпуса всё-равно ещё нет, но лицевая панель точно в планах больше чем 80х70, твоя плата довольно-таки большая, ну и трансформатор габаритный.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 12 Окт., 2020, 13:54
Ну, то, что он 2-й бит, это из определения следует, в железе так. Вот даже в табличке 3 2 1 0 самой первой строчкой, и в даташите регистр разрисован точно так же. Потому и два. А #define MUX2 = 2 - это в .h файле прописано.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 12 Окт., 2020, 13:59
Так он 2-ой бит, а не значение MUX2. MUX2 же не может быть =2.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 12 Окт., 2020, 14:00
Ну дак он и не может каким-то произвольным быть значением. Он (MUX2) - номер бита, не более. Его значение - константа, и она равна 2. MUX2 (а также 0,1 и 3)  как раз олицетворяют понятие "адрес", а не сами данные, что по этому адресу лежат.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 12 Окт., 2020, 14:06
Те тогда верно следующее:
#define adc_select_channel(channel) (ADMUX = (ADMUX & (~((1<<3) | (1<<2) | (1<<1) | (1<<0)))) | (channel))
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 12 Окт., 2020, 14:13
Да, чисто логически верно.
Ещё удобнее будет написать
ADMUX = ADMUX & 0xF0 + channel

Вся эта изначально длинная строка придумана из-за того, что MUX0(1,2,3,) могут "вдруг" (в случае трансляции для другого процессора) находиться в другом месте регистра, т.е. быть не первы-вторым... битами, а, например, четвёртым-пятым-шестым...
В случае длинной строки при смене  проца не придётся искать это место и переписывать под новые значения MUX, а случае, как записано числами - придётся. Это единственная причина, зачем изобретают велосипед с этими длинными записями.
↓ спойлер ↓
а на деле в этой гипотетической ситуации, когда MUXx имеют другие значения, значения chsnnel тоже должны пересчитываться, так что строка "немца" не является универсальной в этом плане. Поэтому можешь спокойно вставить мою - работать будет точно так же :)
Но не забывай следить, чтобы channel вдруг не выскочил за пределы 16 (0x0F). Поэтому, я бы засунул туда "предохранитель"
ADMUX = ADMUX & 0xF0 + (channel & 0x0F)

Этот "предохранитель" интересное место, и если вдруг ты используешь только первый и нулевой каналы, причём последовательно, то смену каналов записать можно ещё интереснее.
ADMUX = ADMUX & 0xF0 + (channel++ & 0x01)

при этом channel надо определить как байтовую беззнаковую величину. И никаких более проверок if делать даже не надо.

Ещё интереснее может быть так
ADMUX = ADMUX & 0xF0 + ((ADMUX+1) & 0x01)
но тут уже запутаться можно т.к. не зная железо не совсем понятно, почему в этом месте можно +1 :)
[свернуть]
А, да... вот та мысль, про которую говорил чуть ранее
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 12 Окт., 2020, 14:56
Ну хоть кое-что с этой арифметикой проясняется в голове, спасибо.
ADMUX = ADMUX & 0xF0 + channel
Ага, ну тут даже проще.
ADMUX & 0xF0 побитовое сравнение с 11110000, те первые четыре единицы не трогает, только последние, плюсом подкидываем канал, который как раз и затрагивает четыре последних первых бита, понятно, что за channel в этом случае надо следить.
+++
#define adc_select_channel(channel) (ADMUX = ADMUX & 0xF0 + channel)
: warning: suggest parentheses around + or - in operand of &
Изменил на:
#define adc_select_channel(channel) (ADMUX = (ADMUX & 0xF0) + channel)

Цитата: Slabovik от 12 Окт., 2020, 14:13А, да... вот та мысль, про которую говорил чуть ранее
Понял, я ещё за эту часть только собирался взяться.
На всякий случай :)  CC (common cathode) общие катоды же?
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 12 Окт., 2020, 15:32
Ну, со скобками компилятору виднее (обычно да, рекомендуют скобки рисовать даже в явных случаях для того, чтобы не было разночтений. А здесь похоже, что компилятору не ведомо, что channel не может быть больше какого-то небольшого значения, поэтому он просит указать приоритет операций явно).

СС - да, общий катод.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 12 Окт., 2020, 16:49
По деталям, поехал взял три мк, конденсаторов насыпал, MCP6002T-I/SN заказал горсть, и пяток LM4040DIM3-4.1, приедут после 22-го.
C REF беда, ну может доеду как нибудь до чип-дипа, уж пару-тройку надо.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 12 Окт., 2020, 17:33
Интересно, что чуть ли не вся линейка (по объёму памяти) контроллеров имеет одну и ту же цену. Поэтому хочется всегда взять побольше - обыкновенное нормальное чувство. С другой стороны, кода ожидается от силы пара-тройка килобайт. Впрочем, я бы сам 328-х взял пачку и в ус не дул :) Дырочек бы только на плате сделал... для опытов :)

Я тут другое подумал. Мне понравилось - подумал ещё.
Дело вот в чём. MCP600x хотя и неплохие ОУ, но обладают довольно большими дрейфом и напряжением смещения. Дрейф температурный, это означает, что при изменении температуры изменится напряжение смещения, а напряжение смещения - это систематическая ошибка на выходе усилителя (т.е. либо плюс сколько-то, либо минус) особенно заметная на усилителе с токового шунта.

Есть ОУ получше. Например, AD855x (8551, 8552, 8554) (https://www.chipdip.ru/product/ad8551ar). Обладают обалденными характеристиками - смещение и дрейф примерно в тысячу раз меньше, чем у MCP, но и стоят дороже.
Фишка в том, что и те и другие пересекаются по кузовам SOIC-8. И, если предусмотреть на плате усилители в этом кузове, то можно поставить либо одни, либо другие, в зависимости от ситуации. И таких взаимозамен может быт довольно много, не только вот эти AD'шки). А если зацепиться только за 6001 в кузове SOT23-5, то выбор замен станет заметно уже, хотя тоже не нулевой. Например, OPA333 весьма вкусен, есть ещё...

Да, я думаю, не стоит торопиться тратиться. Запаять MCP на место измерительных, а при удачных опытах попробовать ОУ поточнее. Если же опыты будут неудачны (бывает, и нередко), то для 10-битного разрешения и MCP хватит.

А, да, ещё вопрос. Эта штука для блока питания, но шунт хочет иметь свой собственный? Или будет измерять падение на шунте самого блока?
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 12 Окт., 2020, 18:06
Ну 6002 в soic-8 у меня просто нет, и они не пропадут, да на AD поглядывал конечно, знаю там характеристики на пару порядков лучше.
Думаю не проблема если будет два шунта, сейчас-то так и есть, только один момент напрягает - земля на корпусе.
Про температурный дрейф - там в немецком коде предусмотрен замер температуры МК (ADC8) и калибровка, относительно этого.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 12 Окт., 2020, 18:18
Калибровка вынудит работать с Float-числами. Не хотелось бы... Не то, чтобы нельзя, но это очень ресурсоёмкая штука для данного процика.

А ещё. Включение-отключение выхода. Тут прямо на плате? Релюшка объёмная, но вполне возможная.

И момент с "землёй" не там, где надо решается просто - используется свой собственный источник питания для этого хозяйства. Иначе ничего хорошего не придётся ждать - перетоки токов по земляному проводу испортят всё, что можно. Соединение "земли" с основной схемой должно быть только в одной точке.

Я продолжаю медитации, уж немного терпения. Много вопросов, которые хочется угадать, чтобы угодить. А если буду делать по-своему, получится очередная вундервафля, бессмысленная и беспощадная :)
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 12 Окт., 2020, 20:51
В общем-то у меня тоже вундерфавли получаются, если помнишь БП с дежуркой, с отрицательным напряжением, обратноходом в качестве основного питания... Ошибкой было желание всё впихнуть на одну плату.
Тут хотел применить вот такое китайское чудо на 5 вольт (купил в основном из-за того, что самому боязно изготовить мелкий трансформатор, да и брать их где-то надо).
ы. Мы же не спешим, блох вроде не видно?  ;D
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 13 Окт., 2020, 00:24
Схема на данный момент такая, две опоры предусмотрел. Подключение к индикаторам - разъём.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 13 Окт., 2020, 17:58
Т.е. плата индикатора отдельно? Или этажерка?
Для разных опор нужен пересчёт резисторов масштабирующих усилителей.
R25 не нужен. Sleep можно подключать напрямую. Или вообще не подключать (внутри есть подтяжка).
Про C1, C3 не помню, говорил ли - не нужны они при наличии C7, C8.
Vin для U5 может быть напрямую от 5V1, нет никакой необходимости его от AVCC питать.
С10, С14 выглядят огромными. Они такие надо? Я бы туда SMD-шных чуть (со всяких девайсов можно наснимать от 1 до 10 мкФ в формате 0805)...

Во, вспомнил (и добавил сюда, чтобы не писать лишний пост). Небольшой ликбез на тему "Как правильно спать на AVR MCU (http://we.easyelectronics.ru/AVR/avr-power-management-ili-kak-pravilno-spat.html)". Просто оставлю здесь, т.к. даташит на Мегу в этом месте написан каким-то не очень понятным языком, а здесь немного разжёвано по-русски. Главное в Си - не забыть подключить библиотечку "sleep.h"

Сам я пока использую только ADC Noise Reduction, но таки предлагаю сделать режим Idle для всеобщей синхронизации. Бегло алгоритм я уже рассказывал, а подробнее... подробнее  это писать надо :)

Покажу свою - см. вложение. Индикатор уже показывал - там всё то же самое. Часть с микропроцессором.

Интерфейс индикатора зацеплен как софтверный. Можно перецепить на хардверний (MOSI/SCK), тогда освободятся для чего-нибудь PB0 и PB1, но потеряются кнопки, которые слева. На самом деле в этом варианте они не особо нужны, т.к. есть кнопки справа около реле - они тёпло-ламповые, аналоговые. Но можно и наоборот - убрать кнопки у реле и переключать их от проца.

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

До сих пор не уверен, что получится чёткий 0 даже на этих ОУ.
В общем, всё, каждая деталька обсуждаема, пока не поздно...
Реле - это типа таких (https://finder-relay.ru/katalog/products/promezhutochnye-rele/-40-serija-miniatjurnye-pcb-rele/-405270120000-rele-s-2-perekidnymi) (стандартизированы в размерах)
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 13 Окт., 2020, 23:24
Ээээ, сорри, а можно разрешение побольше или в DT схему.
Тут ни пинов ни значений не разглядеть. :)
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 13 Окт., 2020, 23:29
Согласен - мелковато, но.... читается. Надо выбрать "скачать" (нажать на имя файла, а не на картинку) и оно откроется уже локально в полном разрешении (просмотрщиком, который у тебя на компе/мобильнике). Иначе движок форума его вписывает в имеющуюся ширину экрана, а это действительно может быть нечитаемо... Да и номиналы всё те же. Около ОУ только посчитал, надеюсь, не сильно ошибся... Тут главное - концепт.

Но если не получится - сделаю побольше...

Да, я что-то сходу не нашёл... ты какой предделитель для SPI иcпользуешь? /16?

зы: ну вот, уже ошибку нашел. Как раз в номиналах...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 13 Окт., 2020, 23:45
Ну да, скачал более-менее видно, буду кряжить свою схему :)
Вся настройка SPI и прерывания по таймеру 1000Гц:
// инициализация портов сдвигового регистра  SPI  ======================
    DDRB  |=  (1<<PB5);      // линия тактирования  clock
    DDRB  |=  (1<<PB3);      // линия данных        data
    DDRB  |=  (1<<PB2);      // линия стробирования  latch                  // или краткая запись  DDRB  |=  ((1<<PB2)|(1<<PB3)|(1<<PB5)); //ножки SPI на выход
    PORTB &= ~(1<<PB2);      // подать низ на .. линия стробирования  latch
    PORTB &= ~(1<<PB3);      // подать низ на .. линия данных        data
    PORTB &= ~(1<<PB5);      // подать низ на .. линия тактирования  clock  // или краткая запись  PORTB &= ~((1<<PB2)|(1<<PB3)|(1<<PB5)); // низкий уровень
    SPCR  =  ((1<<SPE)|(1<<MSTR)); // включим шину, объявим ведущим
// ====================================================================
// настройка прерываний по таймеру 1000 Гц =============================
    TCCR1A  = 0; TCCR1B = 0;                      // установить регистры в 0
    OCR1A  = 250 - 1;                            // установка регистра совпадения  250 - 1; 
    TCCR1B |= (1 << WGM12);                        // включить CTC режим 
    TCCR1B |= (1 << CS11); TCCR1B |= (1 << CS10);  // включить делитель /64
    TIMSK1 |= (1 << OCIE1A);                      // включить прерывание по совпадению таймера 
Отправка в прерывании, которое ты редактировал.
+++
Пересократил ... слово то какое, ... оверсэмплинг, вот так оно работает, без сглаживания:
...
...
#define adc_select_channel(channel) (ADMUX = (ADMUX & 0xF0) + channel)
...
...
typedef unsigned char byte; //создание типа - байт
byte adc_channel;
volatile uint32_t adc_oversampled[2];
...
...
ISR(ADC_vect)  {
  static uint32_t oversample_sum[2];
  static uint8_t oversample_counter[2];
  oversample_sum[adc_channel] += ADC;

  if (++oversample_counter[adc_channel] > 128 - 1) { 
    adc_oversampled[adc_channel] = oversample_sum[adc_channel] >> 4;
    oversample_sum[adc_channel] = 0;
    oversample_counter[adc_channel] = 0;
  }
  // следующий канал
    adc_channel = (adc_channel+1) % 2;
    adc_select_channel(adc_channel); 
}
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 13 Окт., 2020, 23:57
Прежде чем коряжить ты меня проверь. Я сам мог накоряжить   :P   Щас выверять надо :) Номиналы я уже нашёл (завтра исправлю и пересчитаю) - там фигня по ним в ОУ напряжения какая-то нарисована. Но номиналы не суть. Суть - соединения. Любые вопросы...

Меня больше интересует, нужны ли кнопки?
Изначально я их впихал с целью выводить на индикатор установочные значения с задатчиков (опорников) блока питания. Нажал кнопочку - он тебе данные (пересчитанные конечно) с соответствующего опорника показал. Потому у меня в том прототипе все восемь каналов задействованы. Это позволит, например, устанавливать ограничение тока вообще без замыкания, или напряжение в режиме ограничения тока (замыкании). Но сам блок на это должен быть рассчитан. Многие блоки не будут иметь такой возможности - вывести напряжение со своего опорника.

Ага, с частотой понятно - оно не написано явно. Биты SPR0 SPR1 нулевыми остаются, значит, при 16 на кварце получается 4 МГц тактирование на SPI. Вот, почему так быстро.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 14 Окт., 2020, 00:45
Ну я же тоже проверяю что и как, обвязка ОУ у меня осталась от подбора собранного варианта, её конечно надо пересчитать.
На кнопки согласен, лучше порой лишнее предусмотреть, чем потом навешивать, реле тоже ok. Просто именно этого размера у меня нет, наживное.
Вот у себя увидел, что генератор не правильно, полпитания на 5-ую ногу забыл.
ы. Что на счет процедуры оверсэмплинга?
Цитата: Slabovik от 13 Окт., 2020, 17:58Т.е. плата индикатора отдельно? Или этажерка?
Не знаю пока, сначала вроде как макет хотел собрать, а финиш уже потом.

Цитата: Slabovik от 13 Окт., 2020, 17:58Для разных опор нужен пересчёт резисторов масштабирующих усилителей.
Ну там обе опоры по 4 вольта. LM4040 4.1 - она же на 4?

Цитата: Slabovik от 13 Окт., 2020, 17:581 до 10 мкФ в формате 0805)...
0805 у меня только 8 пик, около кварца будут, все остальные размеры резисторов, конденсаторов = 1206.
Я 1206 закупился так, что хватит на долго. :)
Да, там с номиналом переборщил, у меня тантал 47х16 и 100х10 есть, а электролиты smd как раз 220х10 и 68х20.
ыы. Искал подстроечные резисторы в smd исполнении, немного офигел от цен.
3ы. Режим standby и реле на включение трансформатора/ИИП?
4ы. Крайний код и видео к нему (https://www.youtube.com/watch?v=hJSyaevqvCo). (генератора пока нет).
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>

#ifndef F_CPU
#define F_CPU 16000000UL
#endif
#define adc_select_channel(channel) (ADMUX = (ADMUX & 0xF0) + channel)

// Места знаков и точек
uint8_t const DigiMass [] PROGMEM = {
    0b01111111, //  0  // 1-й знак на первом
    0b10111111, //  1  // 2-й знак на первом
    0b11011111, //  2  // 3-й знак на первом
    0b11101111, //  3  // 4-й знак на первом
    0b11110111, //  4  // 1-й знак на втором
    0b11111011, //  5  // 2-й знак на втором
    0b11111101, //  6  // 3-й знак на втором
    0b11111110, //  7  // 4-й знак на втором
    0b11011111, //  8  // место точки на 1-ом
    0b11111101  //  9  // место точки на 2-ом
};
// ABCEDFGH
uint8_t const SegmMass [] PROGMEM = {
    0b00111111, // 0
    0b00000110,
    0b01011011,
    0b01001111,
    0b01100110,
    0b01101101,
    0b01111101,
    0b00000111,
    0b01111111,
    0b01101111,
    0b10000000,
    0b00000000
};

uint8_t Counters[10] = { 0,  0,  0,  0,  // 1-й дисплей 4-знака
                        0,  0,  0,  0,  // 2-й дисплей 4-знака
                        10, 10 };        // точки
unsigned char n_count=0;
typedef unsigned char byte; //создание типа - байт
uint8_t adc_channel;
volatile uint32_t adc_oversampled[2];

void bin_bcd(uint16_t a);
void seg7_show (uint16_t x, uint16_t y);
struct {
    uint8_t tens,hundreds,thousands;
    uint16_t units;
}bcd;


int main (void) {
// инициализация ADC ==================================================
    ADCSRA |= (1<<ADIE )|                      // Включаем прерывание
              (1<<ADATE)|                      // Включаем автоматический триггер
              (1<<ADEN )|                      // Включаем ADC
              (1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)| // ADC_CLOCK_PRESCALER128 задаем тактовую частоту ADC (16000/128 = 125 кГц). 
              (1<<ADSC );                      //  Запускаем преобразование
              ADCSRB = 0;                      // 000 - непрерывное преобразование - режим Free running
    ADMUX  |= (0<<REFS1)|(1<<REFS1)|    // Выставляем внешнее опорное напряжение (AREF (TL431=2.469))
              (0<<ADLAR);              // ADLAR=0 Если нужны все 10 бит (полная 10-битная точность)
    DIDR0  |= (1<<ADC1D) | (1<<ADC0D);  // disable digital inputs on ADC pins

// инициализация портов сдвигового регистра  SPI  ======================
    DDRB  |=  (1<<PB5);      // линия тактирования  clock
    DDRB  |=  (1<<PB3);      // линия данных        data
    DDRB  |=  (1<<PB2);      // линия стробирования  latch                  // или краткая запись  DDRB  |=  ((1<<PB2)|(1<<PB3)|(1<<PB5)); //ножки SPI на выход
    PORTB &= ~(1<<PB2);      // подать низ на .. линия стробирования  latch
    PORTB &= ~(1<<PB3);      // подать низ на .. линия данных        data
    PORTB &= ~(1<<PB5);      // подать низ на .. линия тактирования  clock  // или краткая запись  PORTB &= ~((1<<PB2)|(1<<PB3)|(1<<PB5)); // низкий уровень
    // SPCR  =  ((1<<SPE)|(1<<MSTR)); // включим шину, объявим ведущим | Биты SPR0 SPR1 нулевыми остаются, значит, при 16 на кварце получается 4 МГц тактирование на SPI
    SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0); // Enable SPI, Set as Master, Prescaler: Fosc/16, Enable Interrupts
// ====================================================================
// настройка прерываний по таймеру 1000 Гц =============================
    TCCR1A  = 0; TCCR1B = 0;                      // установить регистры в 0
    OCR1A  = 250 - 1;                            // установка регистра совпадения  250 - 1; 
    TCCR1B |= (1 << WGM12);                        // включить CTC режим 
    TCCR1B |= (1 << CS11); TCCR1B |= (1 << CS10);  // включить делитель /64
    TIMSK1 |= (1 << OCIE1A);                      // включить прерывание по совпадению таймера 
    sei();                                        // включить глобальные прерывания
// ====================================================================
    uint32_t V = 0, A = 0;
    while(1) {
      V = (adc_oversampled[1] + (V << 2) - V) >> 2;
      A = (adc_oversampled[0] + (A << 2) - A) >> 2;
/*      
You can optimise an exponential filter if α = 1/(2^n). E.G. if α = 1/4, then:

adc_Eavg = (adc_raw>>2) + adc_Eavg - (adc_Eavg>>2);
Or, for more accuracy, but at risk of overflow:

adc_Eavg = (adc_raw + (adc_Eavg<<2) - adc_Eavg) >> 2;
Or, if your MCU has a faster multiply than shift:

adc_Eavg = (adc_raw + adc_Eavg*3) >> 2;
*/
      seg7_show ( V, A);
    return 0;
} // main =============================================================
// ====================================================================
// Прерывание по таймеру 1000Hz для 7 seg ======== by Slabovik
ISR(TIMER1_COMPA_vect) {
    PORTB &= ~(1<<PB2);                                // опускаем строб RDY
    SPDR = pgm_read_byte(&DigiMass[n_count]);          // засылаем первый байт
    while(!(SPSR & (1<<SPIF)));                        // ожидаем освобождение буфера
    SPDR = pgm_read_byte(&SegmMass[Counters[n_count]]); // засылаем второй байт
    while(!(SPSR & (1<<SPIF)));                        // снова ждём, когда байт уйдёт из буфера
    PORTB |= (1<<PB2);                                  // поднимаем строб RDY, защёлкивая данные на выход 595
    n_count++;
    if (n_count>9) n_count=0;
}

ISR(ADC_vect)  {
    static uint32_t sum[2];
    static uint8_t counter[2];
    sum[adc_channel] += ADC;

    if (++counter[adc_channel] > 128 - 1) { 
      adc_oversampled[adc_channel] = sum[adc_channel] >> 4;

      sum[adc_channel] = 0;
      counter[adc_channel] = 0;
    }
    // следующий канал
    adc_channel = (adc_channel+1) % 2;
    adc_select_channel(adc_channel);
}

void seg7_show (uint16_t x, uint16_t y) {
    bin_bcd(x); // закидываем прочитанное значение АЦП в функцию bin_bcd, результат отправляется в структуру bcd
    Counters[3] = bcd.thousands;
    Counters[2] = bcd.hundreds;
    Counters[1] = bcd.tens;
    Counters[0] = bcd.units;
    
    bin_bcd(y);
    Counters[7] = bcd.thousands;
    Counters[6] = bcd.hundreds;
    Counters[5] = bcd.tens;
    Counters[4] = bcd.units;
}

// Раскладываем 9999 на цифры
void bin_bcd(uint16_t a) {
    bcd.tens=0;
    bcd.hundreds=0;
    bcd.thousands=0;
    bcd.units=a;
    while (bcd.units>=1000) {
      bcd.units-=1000;
      bcd.thousands++;
    }
    while (bcd.units>=100) {
      bcd.units-=100;
      bcd.hundreds++;
    }
    while (bcd.units>=10) {
      bcd.units-=10;
      bcd.tens++;
    }
}
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 14 Окт., 2020, 19:12
Сегодня у меня день неработающей головы. Торчит чего-то на плечах, а для чего - сам не помню... ну, ем туда, да...

Снял картинку своей обновлённой софтовой процедуры запихивания в индикатор. Типа хвастаюсь, что действительно быстрее стала. Не, кварц тот же самый на 14,318МГц (когда-то кварцы на эту частоту были страшным дефицитом, что даже в массовый ZX Spectrum ставили 14.000 МГц, что приводило к немного неверному формированию времянок для телевизора).

Выдача-8-байтов-в-индикатор.png

На заваленные фронты не смотрите - осциллограф относительно низкочастотный так что в этом месте это не показатель.

Хочу зафиксировать один момент. Поскольку у нас есть незадействованные ноги портов, для отладки на них очень удобно выводить какие-нибудь сигналы. Например, ставим "1" на выбранную ножку в начале какой-нибудь интересной процедуры, а в конце снимаем. Или цифру вывести, или ещё чего - вариантов множество. Вот, я например, захотел оценить, сколько времени занимает процедура опроса АЦП и сколько по времени длится подсчёт результатов по всем кольцевым буферам. Вывел три сигнала. Один - ставлю единичку, когда производится вход в процедуру. Второй - ставлю 1 когда "набиваю" регистры АЦП для запуска и снимаю её, когда прочитал из АЦП результат. Третий - ставлю единичку перед началом пересчёта сумм и убираю её по окончании. И, если посмотреть на эти сигналы осциллографом, становится всё очень и очень наглядно. Вот

ADC_work.png ADC_summ.png

Чётко видно, что АЦП работает примерно 63-65 мкс, чтобы оцифровать сигнал. АЦП работают в режиме Noise Reduction (проц отправляется поспать с выходом из сна по прерыванию). Все восемь каналов получается опросить за примерно 520 мкс, остальное время уходит на пересчёт кольцевых буферов.

Таким образом можно контролировать любую процедуру (с четырьмя лучами оно ещё интереснее, но здесь у меня только два).
Ещё интереснее иметь кнопочку с прерыванием, когда интересует результат расчёта какой-нибудь процедуры (регистр там или ячеечка), то можно в конце процедуры вывести нужное на индикатор/порт и встать в Sleep, ожидая нажатия кнопочки. Так получится чисто визуально прочитать, что там есть. Метод эффективный, всё-таки живое железо :)

Напоследок просто памятка (больше мне самому надо) с установкой фьюзов. Установки не догма, но...

Fuses.png
Цитата: zenon от 14 Окт., 2020, 00:45LM4040 4.1 - она же на 4?
Она близко к 4.096. Имеющиеся подстроечники легко компенсируют разницу между 4.00 и 4.096, а 4.096 может оказаться дороже.

А то, что SMD подстроечники дорогие - это да. Ставлю обычные. Подгадывают так, чтобы подстроечником нужно было только компенсировать разброс параметров деталей - тогда, поскольку подстроечники нестабильны, вклад подстроечника в энтропию не особе сказывается, а регулировка получается точнее.

Релюшки у меня на схеме только на установку подкл-откл выхода блока и установку тока. На включение силовой части нужна ещё одна. Но мне кажется, простой выключатель всего - он интереснее. Брутальный и надёжный. А вкл-откл выхода нужен чтобы обесточивать питаемое без отключения блока. Как он должен правильно работать - всё ещё думаю. Просто тумблер(кнопка с фиксацией) не совсем нравится, но и триггерная нажималка имеет свои недостатки. Но первый вариант проще и от проца не зависит.

Процедуру посмотрел только краем глаза. В прицнипе, как и было: накопление в ячейке и затем расчёт по заполнении. Тут главный критерий - правильность результата, а это ты сам на живом лучше оценишь
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 14 Окт., 2020, 23:30
Не пойму как проверить засыпает ли?
На основе этого примера (https://microchiptechnology.sharepoint.com/sites/DeveloperHelp/Shared%20Documents/Forms/AllItems.aspx?id=%2Fsites%2FDeveloperHelp%2FShared%20Documents%2FWiki%20file%20sharing%20folder%2FBox%20Files%2Fadc%5Fnoise%5Freduction%5Fexample%5Fcode%2Ezip&parent=%2Fsites%2FDeveloperHelp%2FShared%20Documents%2FWiki%20file%20sharing%20folder%2FBox%20Files&p=true&originalPath=aHR0cHM6Ly9taWNyb2NoaXB0ZWNobm9sb2d5LnNoYXJlcG9pbnQuY29tLzp1Oi9zL0RldmVsb3BlckhlbHAvRVk2OFhKaldxQ2RJdU9vYk02eFhGVjBCelZNU2tObC1nUDhCVUJjSEVWQUFsQT9ydGltZT00WUNScDJwdzJFZw) добавил в свой код, как там показано.
Тыц (https://microchipdeveloper.com/8avr:adcnoisereduce).
main.c из примера:
/*
This example project demonstrates the usage of the AVR ADC noise reduction.

The project is based on the ATMega324PB Xplained Pro.
*/
#include <asf.h>
#include <avr/sleep.h>

#define NOISE_REDUCTION 1

volatile uint8_t adc_buffer_count;
volatile uint16_t adc_buffer[100];

ISR(ADC_vect) {
 adc_buffer[adc_buffer_count++] = ADC;
#if !NOISE_REDUCTION
 ADCSRA |= 1 << ADSC;
#endif
}

static void adc_init(void)
{
 /* Internal 1v1 as ref */
 ADMUX = 0 << REFS1 | 1 << REFS0 | 1 << MUX1 | 1 << MUX2 | 1 << MUX3 | 1 << MUX4; 
 /* Enable ADC and interrupt. Set prescaler to 64 */
 ADCSRA = 1 << ADEN | 1 << ADIE | 1 << ADPS2 | 1 << ADPS1 | 0 << ADPS0;
 /* Disable the digital input on the pin */
 DIDR0 = 1 << ADC1D;
}

int main (void)
{
 board_init();
 
 set_sleep_mode(SLEEP_MODE_ADC);
 
 adc_init();
 sleep_enable();
 sei();
 
#if !NOISE_REDUCTION
 ADCSRA |= 1 << ADSC;
#endif

 while(1) {
 
#if NOISE_REDUCTION
 sleep_cpu();
#endif

 if (adc_buffer_count >= sizeof(adc_buffer)/2) {
 adc_buffer_count = 0;
 }
 }
}
Строки
set_sleep_mode(SLEEP_MODE_ADC);
sleep_enable(); добавил перед включением прерываний.
ADCSRA |= 1 << ADSC; сразу после чтения из ADC, в прерывание.
Строку sleep_cpu(); в тело основного цикла программы, перед обновлением значений для вывода на дисплей.
+++
Вот мой вариант, #define NOISE_REDUCTION 1 тоже оставил.
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include <avr/sleep.h>

#ifndef F_CPU
#define F_CPU 16000000UL
#endif

#define adc_select_channel(channel) (ADMUX = (ADMUX & 0xF0) + channel)
#define NOISE_REDUCTION 1

// Места знаков и точек
uint8_t const DigiMass [] PROGMEM = {
    0b01111111, //  0  // 1-й знак на первом
    0b10111111, //  1  // 2-й знак на первом
    0b11011111, //  2  // 3-й знак на первом
    0b11101111, //  3  // 4-й знак на первом
    0b11110111, //  4  // 1-й знак на втором
    0b11111011, //  5  // 2-й знак на втором
    0b11111101, //  6  // 3-й знак на втором
    0b11111110, //  7  // 4-й знак на втором
    0b11011111, //  8  // место точки на 1-ом
    0b11111101  //  9  // место точки на 2-ом
};
// ABCEDFGH
uint8_t const SegmMass [] PROGMEM = {
    0b00111111, // 0
    0b00000110,
    0b01011011,
    0b01001111,
    0b01100110,
    0b01101101,
    0b01111101,
    0b00000111,
    0b01111111,
    0b01101111,
    0b10000000,
    0b00000000
};

uint8_t Counters[10] = { 0,  0,  0,  0,  // 1-й дисплей 4-знака
                         0,  0,  0,  0,  // 2-й дисплей 4-знака
                        10, 10 };        // точки
unsigned char n_count=0;
typedef unsigned char byte; //создание типа - байт
uint8_t adc_channel;
volatile uint32_t adc_oversampled[2];

void bin_bcd(uint16_t a);
void seg7_show (uint16_t x, uint16_t y);
struct {
    uint8_t tens,hundreds,thousands;
    uint16_t units;
}bcd;

int main (void) {
// инициализация ADC ==================================================
    ADCSRA |= (1<<ADIE )|                       // Включаем прерывание
              (1<<ADATE)|                       // Включаем автоматический триггер
              (1<<ADEN )|                       // Включаем ADC
              (1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)| // ADC_CLOCK_PRESCALER128 задаем тактовую частоту ADC (16000/128 = 125 кГц). 
              (1<<ADSC );                       //  Запускаем преобразование
              ADCSRB = 0;                      // 000 - непрерывное преобразование - режим Free running
    ADMUX  |= (0<<REFS1)|(1<<REFS1)|    // Выставляем внешнее опорное напряжение (AREF (TL431=2.469))
              (0<<ADLAR);               // ADLAR=0 Если нужны все 10 бит (полная 10-битная точность)
    DIDR0  |= (1<<ADC1D) | (1<<ADC0D);  // disable digital inputs on ADC pins
    
// инициализация портов сдвигового регистра  SPI  ======================
    DDRB  |=  (1<<PB5);      // линия тактирования   clock
    DDRB  |=  (1<<PB3);      // линия данных         data
    DDRB  |=  (1<<PB2);      // линия стробирования  latch                   // или краткая запись  DDRB  |=  ((1<<PB2)|(1<<PB3)|(1<<PB5)); //ножки SPI на выход
    PORTB &= ~(1<<PB2);      // подать низ на .. линия стробирования  latch
    PORTB &= ~(1<<PB3);      // подать низ на .. линия данных         data
    PORTB &= ~(1<<PB5);      // подать низ на .. линия тактирования   clock  // или краткая запись  PORTB &= ~((1<<PB2)|(1<<PB3)|(1<<PB5)); // низкий уровень
    // SPCR  =  ((1<<SPE)|(1<<MSTR)); // включим шину, объявим ведущим | Биты SPR0 SPR1 нулевыми остаются, значит, при 16 на кварце получается 4 МГц тактирование на SPI
    SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0); // Enable SPI, Set as Master, Prescaler: Fosc/16, Enable Interrupts
// ====================================================================
// настройка прерываний по таймеру 1000 Гц =============================
    TCCR1A  = 0; TCCR1B = 0;                       // установить регистры в 0
    OCR1A   = 250 - 1;                             // установка регистра совпадения  250 - 1; 
    TCCR1B |= (1 << WGM12);                        // включить CTC режим 
    TCCR1B |= (1 << CS11); TCCR1B |= (1 << CS10);  // включить делитель /64
    TIMSK1 |= (1 << OCIE1A);                       // включить прерывание по совпадению таймера 

    set_sleep_mode( SLEEP_MODE_ADC );   // avr/sleep.h
    sleep_enable();
    sei();                                         // включить глобальные прерывания
#if !NOISE_REDUCTION
    ADCSRA |= 1 << ADSC;
#endif

// ====================================================================
    uint32_t V = 0, A = 0;
    while(1) {
        
#if NOISE_REDUCTION
    sleep_cpu();
#endif
      V = adc_oversampled[1];
      A = adc_oversampled[0];
      //V = (adc_oversampled[1] + (V << 2) - V) >> 2;
      //A = (adc_oversampled[0] + (A << 2) - A) >> 2;
      seg7_show ( V, A);
    }
    return 0;
} // main =============================================================

// ====================================================================
// Прерывание по таймеру 1000Hz для 7 seg ======== by Slabovik
ISR(TIMER1_COMPA_vect) {
    PORTB &= ~(1<<PB2);                                 // опускаем строб RDY
    SPDR = pgm_read_byte(&DigiMass[n_count]);           // засылаем первый байт
    while(!(SPSR & (1<<SPIF)));                         // ожидаем освобождение буфера
    SPDR = pgm_read_byte(&SegmMass[Counters[n_count]]); // засылаем второй байт
    while(!(SPSR & (1<<SPIF)));                         // снова ждём, когда байт уйдёт из буфера
    PORTB |= (1<<PB2);                                  // поднимаем строб RDY, защёлкивая данные на выход 595
    n_count++;
    if (n_count>9) n_count=0;
}

ISR(ADC_vect)  {
    static uint32_t sum[2];
    static uint8_t counter[2];
    sum[adc_channel] += ADC;
#if !NOISE_REDUCTION
	ADCSRA |= 1 << ADSC;
#endif
    if (++counter[adc_channel] > 128 - 1) { 
      adc_oversampled[adc_channel] = sum[adc_channel] >> 4;

      sum[adc_channel] = 0;
      counter[adc_channel] = 0;
    }
    // следующий канал
    adc_channel = (adc_channel+1) % 2;
    adc_select_channel(adc_channel);
}

void seg7_show (uint16_t x, uint16_t y) {
    bin_bcd(x); // закидываем прочитанное значение АЦП в функцию bin_bcd, результат отправляется в структуру bcd
    Counters[3] = bcd.thousands;
    Counters[2] = bcd.hundreds;
    Counters[1] = bcd.tens;
    Counters[0] = bcd.units;
    
    bin_bcd(y);
    Counters[7] = bcd.thousands;
    Counters[6] = bcd.hundreds;
    Counters[5] = bcd.tens;
    Counters[4] = bcd.units;
}

// Раскладываем 9999 на цифры
void bin_bcd(uint16_t a) {
    bcd.tens=0;
    bcd.hundreds=0;
    bcd.thousands=0;
    bcd.units=a;
    while (bcd.units>=1000) {
      bcd.units-=1000;
      bcd.thousands++;
    }
    while (bcd.units>=100) {
      bcd.units-=100;
      bcd.hundreds++;
    }
    while (bcd.units>=10) {
      bcd.units-=10;
      bcd.tens++;
    }
}

Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 15 Окт., 2020, 15:14
Мнэээ... мне реально трудно разбираться в этом коде, ведь нужно его в голове держать, а я только пробегаюсь взглядом.
Я смотрю, ты в инициализации ADC сразу делаешь режим FreeRunning. А... зачем?
Алгоритм должен быть такой. В инициализации настраиваем регистры ADC, но НЕ запускаем  (т.е. никакого FreeRunning)
Разрешаем команду Sleep.
В процедуре прерывания от ADC ничего не делаем. Совсем (это не обязательно, можно и прочитать, что там пришло, но... зачем? Там С родит гору лишних пуш-попов  при этом, оно нам надо? Всё-равно программа стоит строго в определённой точке - нужно её просто с этой точки продолжить) Это не значит, что процедуры нет, процедура есть, но там заглушка типа "SEI - IRET"

В основном цикле засовываем в регистры ADC нужный канал, засовываем разрешение на работу и валимся в Sleep. Следующей за Sleep командой будет команда чтения данных из ADC, рассовывание по местам, подсчёт...

Вот, я сегодня нарисовал блок-схему алгоритма, по которому должно всё бегать.
Основа всего - таймер, формирующий "системный тик"  ::) Он синхронизирует всё (а "всего" здесь совсем чуть-чуть).
Суть такова. Настраиваем таймер, делающий прерывания с частотой 1 кГц. И отправляем процессор спать (режим Idle, как самый быстрый в плане скорости выхода из него, либо Advanced Sleep - скорость выхода из которого всего 6 процессорных циклов, но работает только таймер 2 плюс тактирование обязательно от внешнего кварца. Думаю, idle универсальнее.)
Как только случается прерывание, в самой процедуре прерывания ничего не делаем - сразу выходим. Делать там что-то вообще незачем т.к. прерывание - просто метод "подождать" в определённой точке круга программы.

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

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

зы: и ещё пересчитал и, надеюсь, правильно изобразил номиналы в измерительном усилителе. Расчёт от условия опоры 4,096 вольта.
12,5 - это 10 кОм плюс половинка от подстроечного 5 кОм. 14 кОм - это 12,5 кОм + 1.5 кОм на выходе ОУ. Банальный делитель, в общем....
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 15 Окт., 2020, 19:45
Цитата: Slabovik от 15 Окт., 2020, 15:14Я смотрю, ты в инициализации ADC сразу делаешь режим FreeRunning. А... зачем?
Забыл инициализацию переделать, и в задатчике опоры ошибка была, вот так сейчас:
    ADCSRA |= (1<<ADEN)|              // Включаем ADC
    (1<<ADIE)|                        // Включаем прерывание
    (1<<ADPS2)|(1<<ADPS1)|(0<<ADPS0);  // Set prescaler to 64
    ADMUX  |= (0<<REFS1)|(0<<REFS0);    // Выставляем внешнее опорное напряжение
Оно-то работает, просто не пойму как узнать, на самом деле режим adc noise reduction включился или нет?
Понял, ты предлагаешь оставить лишь одно прерывание 1000 Гц, остальное в коде, мне сейчас интересно можно ли оставить прерывание как в примере от микрочипа:
грубо говоря вот это в основном цикле программы:
  while(1) {
      sleep_cpu();
      V = adc_oversampled[1];
      A = adc_oversampled[0];
      seg7_show ( V, A);
    }
А пробуждается автоматически после завершения преобразования, так?
Обработка прерывания так:
ISR(ADC_vect)  {
    static uint32_t sum[2];
    static uint8_t counter[2];
    sum[adc_channel] += ADC;
    if (++counter[adc_channel] > 128 - 1) { 
      adc_oversampled[adc_channel] = sum[adc_channel] >> 4;

      sum[adc_channel] = 0;
      counter[adc_channel] = 0;
    }
    adc_channel = (adc_channel+1) % 2;
    adc_select_channel(adc_channel);
}
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 15 Окт., 2020, 21:12
Цитата: Slabovik от 15 Окт., 2020, 15:14Основа всего - таймер, формирующий "системный тик"
Ну у меня почти так изначально и было, всё выполнялось в одном прерывании, которое было привязано к таймеру 1000 Гц.
Тут от перемены мест ничего не меняется, ну почти...
Потом я добавил прерывание ADC_vect .... :)
Больше из интереса как это всё будет работать.
Посижу посоображаю как вернуть всё в спять...
Кстати забыл уже на каком этапе в spi появилась pgm_read_byte, это кажется ещё на коте...
ы. Последние дни как-то тоже всё кругом, сосредоточиться и спокойно посидеть - никак.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 15 Окт., 2020, 23:36
Оставить-то можно, но... зачем? Одно дело, когда прерывание асинхронное (сигнал пришёл совершенно асинхронный к циклам контроллера) - надо принять, что-то быстренько посчитать и оставить след в системе - запись о свершившемся событии. Здесь же прерывание представляет собой исключительно "спусковой крючок - разрешающий сигнал" для продолжения процесса. Всё, что есть внутри прерывания ровно также считается и снаружи прерывания, ну уж так получается. Исходя из этого, помня о правилах "хорошего тона" в обработке: в прерывании работы нужно выполнять минимум, чтобы как можно быстрее покинуть процедуру и дать возможность вовремя обработать другие прерывания. Понятно, что здесь кроме одного этого прерывания ничего толком и нет, но это не повод расслабляться и нарушать эти правила "хорошего тона".

Если пойти от обратного, то в прерывание можно засунуть вообще всё, оставив снаружи единственное while (1) sleep(); Но это, вкупе с тем, что прерывание одно, как раз и показывает, что всю работу можно делать и снаружи.

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

А посмотреть, сколько у тебя процессора спит, сколько выпоняется то или иное, можно - я как раз на прошлой страничке пояснил метод с высовыванием контрольных битов в свободный порт. Прямо перед Sleep можно поставить какой-нибудь бит порта в 1, а первой командой в прерывании вернуть его в 0.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 16 Окт., 2020, 11:29
Цитата: Slabovik от 15 Окт., 2020, 23:36Прямо перед Sleep можно поставить какой-нибудь бит порта в 1, а первой командой в прерывании вернуть его в 0.
Про дрыгание ногами до и после событий я понял. Факт засыпания где увидеть? ... На кварце будет прекращение генерации?
+++
Про спусковой крючок тут, в моём варианте немного не так.
Если уйду от второго прерывания, то чтение ADC выныривает в цикл программы, никаких спусковых крючков тут и не надо.
В прерывании 1000 Гц оставлю посылку SPI на индикаторы и всё, как оно и задумывалось с этой частотой для динамической индикации.
Ну и опрос антидребезга кнопок туда же можно.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 16 Окт., 2020, 12:09
Если используешь Noise Reduction, то прерывание оформить необходимо. Как - уже говорил выше: внутри прерывания ничего не делать, а поставить заглушку - сразу выход. Про 1000 Гц то же самое. Повторю принцип: в обработчике прерывания делать нужно необходимый минимум, всё остальное делается в теле основной программы. Поскольку здесь необходимый минимум - это простой "прыг" в нужную точку кода, то, соответственно, и такой подход.

Вот что Cи делает, когда ты в прерывании хоть что-то начинаешь делать. Это вход
ISR(ADC_vect)  {
 1de:	1f 92       	push	r1
 1e0:	0f 92       	push	r0
 1e2:	0f b6       	in	r0, 0x3f	; 63
 1e4:	0f 92       	push	r0
 1e6:	11 24       	eor	r1, r1
 1e8:	0f 93       	push	r16
 1ea:	1f 93       	push	r17
 1ec:	2f 93       	push	r18
 1ee:	3f 93       	push	r19
 1f0:	4f 93       	push	r20
 1f2:	5f 93       	push	r21
 1f4:	6f 93       	push	r22
 1f6:	7f 93       	push	r23
 1f8:	8f 93       	push	r24
 1fa:	9f 93       	push	r25
 1fc:	af 93       	push	r26
 1fe:	bf 93       	push	r27
 200:	cf 93       	push	r28
 202:	df 93       	push	r29
 204:	ef 93       	push	r30
 206:	ff 93       	push	r31
а это выход
29a:	ff 91       	pop	r31
 29c:	ef 91       	pop	r30
 29e:	df 91       	pop	r29
 2a0:	cf 91       	pop	r28
 2a2:	bf 91       	pop	r27
 2a4:	af 91       	pop	r26
 2a6:	9f 91       	pop	r25
 2a8:	8f 91       	pop	r24
 2aa:	7f 91       	pop	r23
 2ac:	6f 91       	pop	r22
 2ae:	5f 91       	pop	r21
 2b0:	4f 91       	pop	r20
 2b2:	3f 91       	pop	r19
 2b4:	2f 91       	pop	r18
 2b6:	1f 91       	pop	r17
 2b8:	0f 91       	pop	r16
 2ba:	0f 90       	pop	r0
 2bc:	0f be       	out	0x3f, r0	; 63
 2be:	0f 90       	pop	r0
 2c0:	1f 90       	pop	r1
 2c2:	18 95       	reti
Итого 19 байт в стеке и 78 тактов только на их отработку (целых 5 мкс по времени). А по факту эта работа со стеком просто засунута в совершенно линейно идущий кусок программы. Всего-то только нужно - это чтобы этот кусок программы запустился в нужное время.

Сравни с тем, что я предлагаю
                                 int_ADCC:		; 2A : АЦП закончил преобразование
                                
0000c2 9478                      		SEI
0000c3 9518                      		RETI
А вот сам кусок кода, в который встраивается указанное выше
                                 						; всё готово - X показывает на ячейку, куда сохранять данные преобразования
000134 9a59                      ReadADC10:	SBI PORTD,1			; индикатор работы АЦП - бит 1 порта D
000135 2700                      		CLR R16		;LDI R16,(0<<PCIE2)|(0<<PCIE1)|(0<<PCIE0)	; запретим прерывания от изменения состояния ног
000136 9300 0068                 		STS PCICR,R16
000138 e003                      		LDI R16,(0<<SM2)|(0<<SM1)|(1<<SM0)|(1<<SE)	; устанавливаем ADC Noise Reduction и разрешаем инструкцию SLEEP
000139 bf03                      		OUT SMCR,R16
00013a ec0e                      		LDI R16,ConfigADC|(1<<ADSC)|(1<<ADIE)	; подготовим биты запуска ADC и прерывания от ADC
00013b 9300 007a                 		STS ADCSRA,R16
00013d 9588                      		SLEEP				; и отправляем в спячку - это запускает ADC. Выход будет по прерыванию от ADC
                                 ; здесь происходит ожидание прерывания по окончании преобразования ADC.
                                 ; В обработчике прерывания заглушка SEI/RETI, поэтому просто продолжаем бег
00013e 9100 0078                 		LDS R16,ADCL			; первым нужно прочитать младший байт - так надо потому что старший
000140 930d                      		ST X+,R16			; при этом попадёт в защёлку, чтобы быть прочитанным
000141 9100 0079                 		LDS R16,ADCH
000143 930c                      		ST X,R16
000144 e001                      		LDI R16,(0<<PCIE2)|(0<<PCIE1)|(1<<PCIE0)
000145 9300 0068                 		STS PCICR,R16
000147 9859                      		CBI PORTD,1			; выключили индикатор работы АЦП
Здесь первая и последняя команды - это тот самый "индикатор" (вначале "вкл", в конце "выкл", по которому можно смотреть, сколько работает АЦП (картинка на предыдущей странице).

Для Си-шника это конечно неважно, т.к. памяти хватает и всего этого просто не видно - мышление другими категориями. Однако к примеру, на маленьких PIC с их 8 ячейками в стеке (потому что больше просто нету) такой подход катастрофе подобен.

Прерывание же  для смены знака индикатора как раз очень удобно тем, что длительности его периода: а) достаточно для исполнения всего круга программы + нехилый запас на возможные модификации; б) время опроса АЦП составляет не более 10% от этого времени (тут я с большим запасом взял, вдруг частоту АЦП захочется понизить), что позволяет спокойно опрашивать АЦП в режиме Noise Reduction прямо внутри круга программы.
Преимущество такого подхода - АЦП измеряет не абы когда, а "строго по часам" - те же 500 Гц (1000/2 канала).
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 16 Окт., 2020, 14:20
Всё-всё-всё уговорил...  ;D  ;D  ;D
Переписал, пока без засыпаний и оверсэмплинга, тиканье в byte clk1000hz, в самом прерывании только clk1000hz = ~clk1000hz;.
Для чтения АЦП функция adc_read, в которой и планируется спать.
Отсылка по SPI отправил в функцию seg7_show, предварительно обновив цифры.
Ну и селектор каналов чуть по другому...  :)
uart вынес в библиотеку, ну он для подстраховки, тут не используется.
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <avr/sleep.h>
#include "uartz.h"

// Переменные
typedef unsigned char byte;
byte adc_channel = 0;
byte clk1000hz = 0;
struct { uint8_t tens,hundreds,thousands; uint16_t units; }bcd;
// Места знаков и точек
uint8_t const digi7mass [] PROGMEM = { 0b01111111, 0b10111111, 0b11011111, 0b11101111, 0b11110111, 0b11111011, 0b11111101, 0b11111110, 0b11011111, 0b11111101 };
// ABCEDFGH
uint8_t const segm7mass [] PROGMEM = { 0b00111111, 0b00000110, 0b01011011, 0b01001111, 0b01100110, 0b01101101, 0b01111101, 0b00000111, 0b01111111, 0b01101111, 0b10000000, 0b00000000 };
uint8_t counter7s[10] = { 0,  0,  0,  0, 0,  0,  0,  0, 10, 10 };
unsigned char n_count=0;
uint16_t adc_buffer[2];

// Функции
void adc_init(void);
void timer1000hz_init(void);
void spi_init(void);
uint16_t adc_read(uint8_t ch);

void bin_bcd(uint16_t a);
void seg7_show (uint16_t x, uint16_t y);

// +++
int main() {

    adc_init();
    uart_init();
    spi_init();
    timer1000hz_init();
    sei();

    while(1) {
      if (~clk1000hz) {
        adc_buffer[adc_channel] = adc_read(adc_channel);
        seg7_show ( adc_buffer[0], adc_buffer[1]);
        adc_channel = (adc_channel+1) % 2; // следующий канал
      }
    }
}
// +++

void adc_init(void) {
    ADMUX  |= (0<<REFS1)|(0<<REFS0); // REFS1=0 REFS0=0 = Внешний источник, подключенный к AREF, внутренний VREF отключен
    ADCSRA |= (1<<ADEN )|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); // ADC_CLOCK_PRESCALER128 задаем тактовую частоту ADC (16000/128 = 125 кГц). 
    DIDR0  |= (1<<ADC1D)|(1<<ADC0D); // disable digital inputs on ADC pins
}

uint16_t adc_read(uint8_t channel) {
    channel &= 0b00000111;
    ADMUX = (ADMUX & 0xF8)|channel; // выбор канала
    ADCSRA |= (1<<ADSC);            // запуск преобразования
    while(ADCSRA & (1<<ADSC));      // ждём результат
    return (ADC);
}

void timer1000hz_init(void) {
// настройка прерываний по таймеру 1000 Гц =============================
    TCCR1A  = 0; TCCR1B = 0;                      // установить регистры в 0
    OCR1A  = 250 - 1;                            // установка регистра совпадения  250 - 1; 
    TCCR1B |= (1 << WGM12);                        // включить CTC режим 
    TCCR1B |= (1 << CS11); TCCR1B |= (1 << CS10);  // включить делитель /64
    TIMSK1 |= (1 << OCIE1A);                      // включить прерывание по совпадению таймера 
}

ISR(TIMER1_COMPA_vect) {
    clk1000hz = ~clk1000hz;
}

void spi_init() {
// инициализация портов сдвигового регистра  SPI  ======================
    DDRB  |=  (1<<PB5);      // линия тактирования  clock
    DDRB  |=  (1<<PB3);      // линия данных        data
    DDRB  |=  (1<<PB2);      // линия стробирования  latch                  // или краткая запись  DDRB  |=  ((1<<PB2)|(1<<PB3)|(1<<PB5)); //ножки SPI на выход
    PORTB &= ~(1<<PB2);      // подать низ на .. линия стробирования  latch
    PORTB &= ~(1<<PB3);      // подать низ на .. линия данных        data
    PORTB &= ~(1<<PB5);      // подать низ на .. линия тактирования  clock  // или краткая запись  PORTB &= ~((1<<PB2)|(1<<PB3)|(1<<PB5)); // низкий уровень
    SPCR  =  ((1<<SPE)|(1<<MSTR)); // включим шину, объявим ведущим | Биты SPR0 SPR1 нулевыми остаются, значит, при 16 на кварце получается 4 МГц тактирование на SPI      // SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0); // Enable SPI, Set as Master, Prescaler: Fosc/16, Enable Interrupts
// ====================================================================
}


void seg7_show (uint16_t x, uint16_t y) {
    bin_bcd(x); // закидываем прочитанное значение АЦП в функцию bin_bcd, результат отправляется в структуру bcd
    counter7s[3] = bcd.thousands;
    counter7s[2] = bcd.hundreds;
    counter7s[1] = bcd.tens;
    counter7s[0] = bcd.units;
    
    bin_bcd(y);
    counter7s[7] = bcd.thousands;
    counter7s[6] = bcd.hundreds;
    counter7s[5] = bcd.tens;
    counter7s[4] = bcd.units;

    PORTB &= ~(1<<PB2);                                // опускаем строб RDY
    SPDR = pgm_read_byte(&digi7mass[n_count]);          // засылаем первый байт
    while(!(SPSR & (1<<SPIF)));                        // ожидаем освобождение буфера
    SPDR = pgm_read_byte(&segm7mass[counter7s[n_count]]); // засылаем второй байт
    while(!(SPSR & (1<<SPIF)));                        // снова ждём, когда байт уйдёт из буфера
    PORTB |= (1<<PB2);                                  // поднимаем строб RDY, защёлкивая данные на выход 595
    n_count++;
    if (n_count>9) n_count=0;
}

// Раскладываем 9999 на цифры
void bin_bcd(uint16_t a) {
    bcd.tens=0;
    bcd.hundreds=0;
    bcd.thousands=0;
    bcd.units=a;
    while (bcd.units>=1000) {
      bcd.units-=1000;
      bcd.thousands++;
    }
    while (bcd.units>=100) {
      bcd.units-=100;
      bcd.hundreds++;
    }
    while (bcd.units>=10) {
      bcd.units-=10;
      bcd.tens++;
    }
}
lss в аттаче.Нет сообщений, связанных с этим вложением.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 16 Окт., 2020, 15:50
Эмммм, только теперь я не пойму как спать, если стоит SLEEP_MODE_ADC, и засыпаю после  ADCSRA |= (1<<ADSC) то не просыпается.
А когда sleep_cpu() была в прерывании ADC_vect и ADIE=1  то вроде всё работало (https://anklab.ru/forum/index.php?topic=49.msg326#msg326) (на основе примера от микрочипа).
Цитата: Slabovik от 16 Окт., 2020, 12:09Преимущество такого подхода - АЦП измеряет не абы когда, а "строго по часам" - те же 500 Гц (1000/2 канала).
Ну вот тут тогда вопрос - достаточно ли этих данных для оверсэмплинга?
Может есть смысл тактировать не по таймеру, а как-то реализовать по завершению преобразования АЦП в прерывании ADC_vect?
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 16 Окт., 2020, 16:51
Вот этот момент вряд ли влияет, но вместе с ADSC (start conversion) ADIE (interrupt enable) надо бы проверять, потому что по ходу программы кто-то другой мог и опустить этот флаг.
Второе.
Вот что пример (https://microchipdeveloper.com/8avr:adcnoisereduce) показывает в обработчике прерывания. Это просто пример - там он увеличивает счётчик на единицу.
ISR(ADC_vect) {
	adc_buffer[adc_buffer_count++] = ADC;
}
Т.е. по факту ничего не делает (чтение результата в качестве примера, не более. Интересно, в Си можно сразу на выход?)
В основном теле программы у него так
int main (void)
{
	board_init();
	set_sleep_mode(SLEEP_MODE_ADC);
	adc_init();
	sleep_enable();
	sei();
	
// далее падаем в основной цикл
	while(1) {
		
		sleep_cpu(); // эта команда запустит преобразование
// с этого места продолжится работа как только ADC закончит измерение и произойдёт покидание прерывания.
// adc_buffer_count можно и здесь посчитать (++). Счёт в прерывании сделан только для того, чтобы убедиться,
// что прерывание отрабатывает.
		if (adc_buffer_count >= sizeof(adc_buffer)/2) {
			adc_buffer_count = 0;
		}
	}
}

Давай глянем твой код
void adc_init(void) {
    ADMUX = (1<<REFS0);
    ADMUX  |= (0<<REFS1)|(0<<REFS0); // REFS1=0 REFS0=0 = Внешний источник, подключенный к AREF, внутренний VREF отключен
    ADCSRA |= (1<<ADEN )|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); // ADC_CLOCK_PRESCALER128 задаем тактовую частоту ADC (16000/128 = 125 кГц).
    DIDR0  |= (1<<ADC1D)|(1<<ADC0D); // disable digital inputs on ADC pins
и... я не вижу установку 1 << ADIE
Т.е. прерывания от ADC не будет, проц выйдет из спячки (может быть) по какому-нибудь другому событию.

Вот ещё выбор канала
ADMUX = (ADMUX & 0xF8)|channel;
Надо 0xF0, а не 0xF8. Старший бит может быть был взведён (кем-то, типа температурку померить рад в пять секунд?)

И ещё интересный момент. Если ждём прерываний, зачем делать вот это внутри adc_read?
ADCSRA |= (1<<ADSC);            // запуск преобразования
    while(ADCSRA & (1<<ADSC));
Т.е. оно противоречит как-то.

Возьми за основу код примера (по ссылке), добавь туда вывод в serial и более ничего. В serial выведи считываемые с ADC показания и можно adc_buffer_count. Как только у тебя всё побежит - значит оно заработало. Вывод на индикатор пока не вставляй. Дело в том, что после того, как отработало прерывание ADC и считаны показания, нужно на время выключить запуск ADC, потому что следующий sleep должен быть на ожидание таймера, он не должен запустить ADC.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 16 Окт., 2020, 17:16
О, эврика, прерывание АЦП использовать только для пробудки из сна-же...
Спс. Натолкнул на мысль.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 16 Окт., 2020, 20:02
Рабочий вариант.
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <avr/sleep.h>

// Переменные
typedef unsigned char byte; //создание типа - байт
byte adc_channel = 0;
#define adc_select_channel(channel) (ADMUX = (ADMUX & 0xF0) + channel)
//#define adc_select_channel(channel) ADMUX = (ADMUX & 0xF0) + (channel++ & 0x01)
byte clk1000hz = 0;

struct { uint8_t tens,hundreds,thousands; uint16_t units; }bcd;
// Места знаков и точек
uint8_t const digi7mass [] PROGMEM = { 0b01111111, 0b10111111, 0b11011111, 0b11101111, 0b11110111, 0b11111011, 0b11111101, 0b11111110, 0b11011111, 0b11111101 };
// ABCEDFGH
uint8_t const segm7mass [] PROGMEM = { 0b00111111, 0b00000110, 0b01011011, 0b01001111, 0b01100110, 0b01101101, 0b01111101, 0b00000111, 0b01111111, 0b01101111, 0b10000000, 0b00000000 };
uint8_t counter7s[10] = { 0,  0,  0,  0, 0,  0,  0,  0, 10, 10 };
unsigned char n_count=0;
uint16_t adc_buffer[2];

// Функции
void adc_init(void);
void timer1000hz_init(void);
void spi_init(void);

void bin_bcd(uint16_t a);
void seg7_show (uint16_t x, uint16_t y);

// +++
int main() {

    adc_init();
    spi_init();
    timer1000hz_init();
    set_sleep_mode(SLEEP_MODE_ADC);
    sleep_enable();
    sei();

    while(1) {
      if (~clk1000hz) {
        ADCSRA |= (1<<ADSC);                      // запуск преобразования ADC
        sleep_cpu();                              // и засыпаем, проснёмся в прерывании, после того, как результат ADC будет готов
        adc_buffer[adc_channel] = ADC;            // ADC готов, читаем его в выбранный буфер
        seg7_show (adc_buffer[0], adc_buffer[1]);
        adc_channel = (adc_channel+1) % 2;        // следующий канал
        adc_select_channel(adc_channel);
      }
    }
}
// +++

ISR(ADC_vect) {
// тут ничего не делаем, нужно, чтобы проснуться, (1<<ADIE)
}

void adc_init(void) {
    ADMUX  |= (0<<REFS1)|(0<<REFS0); // REFS1=0 REFS0=0 = Внешний источник, подключенный к AREF, внутренний VREF отключен
    ADCSRA |= (1<<ADIE )|(1<<ADEN )|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); // ADC_CLOCK_PRESCALER128 задаем тактовую частоту ADC (16000/128 = 125 кГц). 
    DIDR0  |= (1<<ADC1D)|(1<<ADC0D); // disable digital inputs on ADC pins
}

void timer1000hz_init(void) {
// настройка прерываний по таймеру 1000 Гц =============================
    TCCR1A  = 0; TCCR1B = 0;                      // установить регистры в 0
    OCR1A  = 125 - 1;                            // установка регистра совпадения  250 - 1;  /// если надо например 2000 Гц, меняем на 125 - 1
    TCCR1B |= (1 << WGM12);                        // включить CTC режим 
    TCCR1B |= (1 << CS11); TCCR1B |= (1 << CS10);  // включить делитель /64
    TIMSK1 |= (1 << OCIE1A);                      // включить прерывание по совпадению таймера 
}

ISR(TIMER1_COMPA_vect) {
    clk1000hz = ~clk1000hz; // тик-так
}

void spi_init() {
// инициализация портов сдвигового регистра  SPI  ======================
    DDRB  |=  (1<<PB5);      // линия тактирования  clock
    DDRB  |=  (1<<PB3);      // линия данных        data
    DDRB  |=  (1<<PB2);      // линия стробирования  latch                  // или краткая запись  DDRB  |=  ((1<<PB2)|(1<<PB3)|(1<<PB5)); //ножки SPI на выход
    PORTB &= ~(1<<PB2);      // подать низ на .. линия стробирования  latch
    PORTB &= ~(1<<PB3);      // подать низ на .. линия данных        data
    PORTB &= ~(1<<PB5);      // подать низ на .. линия тактирования  clock  // или краткая запись  PORTB &= ~((1<<PB2)|(1<<PB3)|(1<<PB5)); // низкий уровень
    SPCR  =  ((1<<SPE)|(1<<MSTR)); // включим шину, объявим ведущим | Биты SPR0 SPR1 нулевыми остаются, значит, при 16 на кварце получается 4 МГц тактирование на SPI      // SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0); // Enable SPI, Set as Master, Prescaler: Fosc/16, Enable Interrupts
// ====================================================================
}


void seg7_show (uint16_t x, uint16_t y) {

    bin_bcd(x); // закидываем прочитанное значение АЦП в функцию bin_bcd, результат отправляется в структуру bcd
    counter7s[3] = bcd.thousands;
    counter7s[2] = bcd.hundreds;
    counter7s[1] = bcd.tens;
    counter7s[0] = bcd.units;
    
    bin_bcd(y);
    counter7s[7] = bcd.thousands;
    counter7s[6] = bcd.hundreds;
    counter7s[5] = bcd.tens;
    counter7s[4] = bcd.units;

    PORTB &= ~(1<<PB2);                                    // опускаем строб RDY
    SPDR  = pgm_read_byte(&digi7mass[n_count]);            // засылаем первый байт
    while(!(SPSR & (1<<SPIF)));                            // ожидаем освобождение буфера
    SPDR  = pgm_read_byte(&segm7mass[counter7s[n_count]]); // засылаем второй байт
    while(!(SPSR & (1<<SPIF)));                            // снова ждём, когда байт уйдёт из буфера
    PORTB |= (1<<PB2);                                      // поднимаем строб RDY, защёлкивая данные на выход 595
    n_count++; if (n_count>9) n_count=0;
}

// Раскладываем 9999 на цифры
void bin_bcd(uint16_t a) {
    bcd.tens=0;
    bcd.hundreds=0;
    bcd.thousands=0;
    bcd.units=a;
    while (bcd.units>=1000) {
      bcd.units-=1000;
      bcd.thousands++;
    }
    while (bcd.units>=100) {
      bcd.units-=100;
      bcd.hundreds++;
    }
    while (bcd.units>=10) {
      bcd.units-=10;
      bcd.tens++;
    }
}
ы. Вот так adc_select_channel(channel) ADMUX = (ADMUX & 0xF0) + (channel++ & 0x01) работает только один канал.
Со скобками крутил - не помогло.
Поэтому пока adc_select_channel(channel) (ADMUX = (ADMUX & 0xF0) + channel) оставил.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 17 Окт., 2020, 16:51
Смотрю на вот этот лот (https://aliexpi.com/nUmQ) STM32F030K6 .... даже не знаю что сказать.. :)
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 18 Окт., 2020, 20:21
Гм...
Цитата: undefinedПрислал пустышки - чипы звонятся насковозь по ногам питания, ни один из 5 не работает
;)

ЦитатаADMUX = (ADMUX & 0xF0) + (channel++ & 0x01) работает только один канал.
Возможно, что я что-то намудрил с ++, но на деле если без этой маски работает - так и оставить. Потому что здесь оно не сильно-то и надо, просто лишняя операция.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 19 Окт., 2020, 12:37
Да, этот лот лотерея на пустышки, но всё-равно 5 штук живых там есть где-то за рублей 300-400.
Не вижу у тебя R12 и R15 - не нужны?
Развожу потихоньку, вот так пока.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 19 Окт., 2020, 13:21
R12 есть (у меня он вроде R32 на последней схеме, но нумерация  ещё съедет - к бабке не ходи), а R15, поскольку в усилителе от токового датчика нет отрицательного сигнала (и резистор ОС и вход "+" измеряют точно относительно Gng), можно не ставить. При этом при подсчёте коэффициента усиления, он (коэффициент) будет получаться на единичку больше, чем просто отношение резисторов в ОС.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 19 Окт., 2020, 21:59
Макет будет примерно такой. :)
Слева вверху uart, 3 светодиода и пару кнопок, ну и сверху пины, может i2c или ещё что нибудь.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 21 Окт., 2020, 11:46
Быстр на руку :)  А я всё в раздумьях :-\ Не могу отделаться от того, что для получения прерываний с частотой один килогерц, глубины таймеров 0 или 2 не хватает. Остаётся только таймер 1, который 16-битный. Прерывать приходится по совпадению регистра сравнения.

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

зы: всё написанное выше - не правда. Потому что я туплю, с какого-то посчитав тик с предделителя прерыванием с самого таймера. А это неправильно. С 8-битным таймером можно получить частоту прерываний вплоть до ~61 Гц...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 21 Окт., 2020, 17:00
:)
Разглядываю какие индикаторы есть/заказать.
Хочется чего-то эдакого... размер, не знаю 0,5 (https://aliexpi.com/atg5) или может 0.8 (https://aliexpi.com/sFfs).
Желтых и белых как-то мало, белые в основном на плате с модулем (https://aliexpi.com/6Y9g) i2c.
Вот жёлтый (https://aliexpi.com/5l65) из четырёх знакомест, но ОА.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 21 Окт., 2020, 17:15
14мм (0,56") по-моему самое то. А чисто визуально лучше всего читается красный. Зелёным как правило нужен ток побольше. С синими проблема - глаз на синем плохо фокусируется, хотя они яркие. Можно сочетать: красный (жёлтый) - напряжение, синий - ток. Вот белые я не пробовал.

В чип-дипе они какие-то жутко дорогие...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 21 Окт., 2020, 22:14
Синие вырви глаз, ну их.
Вот жёлтые (https://aliexpi.com/j8cJ) более-менее приемлимо по цене, только 0 заказов.
Белые по вот (https://aliexpi.com/dxTE), но 20 штук, я их тоже не щупал.
Не плохо смотрятся оранжевые (https://aliexpi.com/od2M), на фото не очень, в живую лучше.
Чип и дип вообще всегда дорого, хотя последнее время появились позиции с приемлемыми ценами.
Заказал STM32F030K6T6 (https://aliexpi.com/Qhap), макетную делать будем? Тему заведём?
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 21 Окт., 2020, 23:41
Будем. Programming Manual (https://www.st.com/resource/en/programming_manual/dm00051352-stm32f0xxx-cortexm0-programming-manual-stmicroelectronics.pdf) уже изучаю :)
Я только пока не врубаюсь, как к ней ST-Link подключать. У Атмела (да и у Микрочипа на  PIC'и аналогично) вся дока на чип в кучке, а тут как-то разбросано... Правда, я в последние дни больше про STM8 читал. Вполне по-зубам (моим) чипик, хотя и регистров мало. Смешно: один аккумулятор да два индексных. Но зато Memory-Memory операции умеет, DMA есть (для SPI, ADC и пр. очень полезно).

Я пока мучаю алгоритм, что немногим ранее предложил. Констатирую, что вполне рабочий - только что завёл на своей плате (за исключением динамической индикации - ну просто статика у меня сделана). Из нюансов - таймеры стопятся, когда ADC работает (в режиме Noise Reduction) так что в каждом цикле обязательно должно быть одинаковое количество запусков ADC (собственно, я по одному запуску предполагал, чередуя каналы). При таком построении задержка таймеров тоже одинаковая в каждом цикле и всё чётко, без дрожаний.
Без остановки может работать только таймер #2, да и то в режиме внешнего тактирования. Лепить ещё один внешний генератор совсем неохота, не стоит оно того (не, реально грустная система: заюзал одну фичу - стали недоступны несколько других)

Я пока займусь тоже макетом, приближённым к реальному изделию (т.е. ничего лишнего, но разъём PGM обязателен) чтобы попрактиковаться в асме (нравится мне это прямокодие).
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 23 Окт., 2020, 15:21
Собрал. Прыгают иногда индикаторы. Надо наверное ещё замедлять.
Номиналы на схеме соответствуют тем что на плате.
За ток пока не брался. Напряжение из расчёта 0-40.
Делитель 10k/91k, почти хватило, пришлось R2 увеличить с 10k до 12k, и попал.
Программно просто делю на два.
С19 - не нашёл 4,7 мкФ - поставил 1 мкФ - можно? Только 1 мкФ и 10 мкФ есть сейчас :)
R27, R25 пока перемычки.
R41 (на опоре) 47 Ом, норм? Конденсатор не нужен же на Aref? C15 не стоит, те на Aref ёмкости никакой нет.
Видео (https://www.youtube.com/watch?v=2hmNShjNryc), снял плохо, бликует индикатор, сорри.
На диапазоне 0-14 вольт почти попадает 0,01, ближе к 30 расхождение на 0,05 примерно.
Не знаю как в плане точности UT61E... но вроде не плох.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 23 Окт., 2020, 16:29
R41 надо выбирать исходя из необходимого тока через LM4040. Достаточно в пределах одного-двух миллиампер (потребление по Ref не превышает четверти миллиампера). Если стоит на 4.096 вольта, то нормально вполне 470~680 Ом. А вот 47 Ом явно мало.
К сожалению, не нашёл в даташите данных о подключении ёмкостной нагрузки к LM4040. Я полагаю, с ней можно и без ёмкости.

А вот если Ref198 стоит, то R41 ставить совсем не надо, а на выходе Ref ёмкость нужна.

Если U1 стоит MCP, то R31,R32 надо одинаковые. Если LM358 - то как на схеме.
Сигнал "Tre" осциллографом смотрел?

С19 можно "какой есть", он не является критичным. Можно и десятку засунуть - будет только лучше.

А что значит "индикаторы прыгают"? Если это про неустойчивость показаний в последнем разряде, дак это нормально.

А расхождение 0,1 для 30 - это 0.3% точности. На деле UT61 не является каким-то офигенно точным прибором, увы...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 23 Окт., 2020, 17:01
А, у LM4040 Irmin = 45 μA, те 45 мкА, а я считал 45 мА... и поставил 47 Ом, то-то я смотрю, а там 4,086 вольт, сейчас поставил, 470 Ом - ровно 4,096 вольт стало, как и положено.
А про ёмкость я тоже не нашёл ничего, поэтому не поставил.
Ref198 у меня пока нет.
ОУ MCP стоят.
Осциллографом чуть позже гляну, покажу что там.
Прыгают бывают по два знакоместа последних на обеих индикаторах синхронно, не пойму из-за чего.
Сначала с землёй намудрил, ноля не было, потом переделал, ноль отличный.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 23 Окт., 2020, 17:13
Два знакоместа синхронно - это когда -9-0-9-0- младший разряд меняется. Это вынуждает меняться разряд, который старше.
Из-за шумов последний разряд всегда неточен в пределах одного отсчёта, как бы там ни было.
В алгоритме для получения 40.92 нужно просуммировать 32 отсчёта и поделить их на 8. При этом, если буфер суммирующий, показания будут обновляться с частотой примерно 15 Гц (если следовать алгоритму, что я ранее показывал, 1000 Гц/2 канала /32 выборки). Можно суммировать 64 выборки и делить на 16 - младший разряд болтать станет чуть меньше (возможно). Но болтанка ещё и от самого сигнала зависит. Поэтому постепенно осознаётся то, что быстрая смена показаний на дисплее не улучшает восприимчивость. Максимум - это порядка 8-10 раз в секунду. Ибо всё-равно в младшей цифре будет мешанина. Тут уж от обстоятельств думают, оставлять мешанину (при шуме в измеряемом), или что-то делать (уменьшать частоту вывода, изменять способ подачи показаний, гасить разряд или ещё что-то).
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 23 Окт., 2020, 17:40
Болтанка (https://youtu.be/5xdwYBwdfQM). Чуть изменил напряжение на входе - перестала.
Тока в цепи его измерения нет, те должны быть 0000 (зелёный индикатор).
Tre и монтаж добавил, волоски на плате - это от кисточки.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 23 Окт., 2020, 19:09
Треугольник хороший, на 30 Омах R16 R17 должно хватать для смещения.

Осталось либо рассказать, как делаешь подсчёт, либо сразу перейти к постройке алгоритма. Такая синхронность говорит о том, что где-то ошибка (в замерах, в вычислениях, в способе прочтения АЦП, да мало ли). Там на плате с земляным проводом как-то не очень (хотя бы потому, что R16, R17 и AGnd смотрят в три разные точки, а это только то, что я сходу увидел), но это как-то сильно влиять не должно.
Вывод в UART не надо бы использовать, он прерывания генерирует. Если FreeRunning оставил, то с длинной обработкой прерываний можно запросто пропустить снятие данных с АЦП и тоже будет ошибка...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 23 Окт., 2020, 19:43
Код почти такой же и остался, пока ничего не менял, от "немца" оверсэмплинг и сглаживание.
R16 и R17 замаскированные 15 Ом, два по 30 бутербродом :)
UART не используется тут.
Про FreeRunning тоже вроде договорились - не используем.
Для чтения ADC спим, прерывание только для пробудки.
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>

// Переменные
typedef unsigned char byte;
byte adc_channel = 0;
byte clk1000hz = 0;
volatile uint32_t adc_oversampled[2];
#define adc_select_channel(channel) (ADMUX = (ADMUX & 0xF0) + channel)

struct { uint8_t tens,hundreds,thousands; uint16_t units; }bcd;
// Места знаков и точек
uint8_t const digi7mass [] PROGMEM = { 0b01111111, 0b10111111, 0b11011111, 0b11101111, 0b11110111, 0b11111011, 0b11111101, 0b11111110, 0b11011111, 0b11111101 };
// ABCEDFGH
uint8_t const segm7mass [] PROGMEM = { 0b00111111, 0b00000110, 0b01011011, 0b01001111, 0b01100110, 0b01101101, 0b01111101, 0b00000111, 0b01111111, 0b01101111, 0b10000000, 0b00000000 };
uint8_t counter7s[10] = { 0,  0,  0,  0, 0,  0,  0,  0, 10, 10 };
unsigned char n_count=0;
uint16_t adc_buffer[2];

// Функции
void adc_init(void);
void timer1000hz_init(void);
void spi_init(void);

void bin_bcd(uint16_t a);
void seg7_show (uint16_t x, uint16_t y);
 
static uint32_t    sum[2];
static uint8_t counter[2];

uint32_t V = 0, A = 0;

// +++
int main() {

    adc_init();
    spi_init();
    timer1000hz_init();
    set_sleep_mode(SLEEP_MODE_ADC);
    sleep_enable();
    sei();

    while(1) {

      if (~clk1000hz) {
        ADCSRA |= (1<<ADSC);                      // запуск преобразования ADC
        sleep_cpu();                              // и засыпаем, проснёмся в прерывании, после того, как результат ADC будет готов
        sum[adc_channel] += ADC;
        if (++counter[adc_channel] > 128 - 1) { 
          adc_oversampled[adc_channel] = sum[adc_channel] >> 4;
          sum[adc_channel] = 0;
          counter[adc_channel] = 0;
        }
        V = (adc_oversampled[1] + (V << 2) - V) >> 2;
        A = (adc_oversampled[0] + (A << 2) - A) >> 2;
        seg7_show ( V/2, A);
        adc_channel = (adc_channel+1) % 2;        // следующий канал
        adc_select_channel(adc_channel+6);
      }
    }
}
// +++

ISR(ADC_vect) {
// тут ничего не делаем, нужно, чтобы проснуться, (1<<ADIE)
}

void adc_init(void) {
    ADMUX  |= (0<<REFS1)|(0<<REFS0); // REFS1=0 REFS0=0 = Внешний источник, подключенный к AREF, внутренний VREF отключен
    ADCSRA |= (1<<ADIE )|(1<<ADEN )|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); // ADC_CLOCK_PRESCALER128 задаем тактовую частоту ADC (16000/128 = 125 кГц). 
}

void timer1000hz_init(void) {
// настройка прерываний по таймеру 1000 Гц =============================
    TCCR1A  = 0; TCCR1B = 0;                      // установить регистры в 0
    OCR1A  = 125 - 1;                            // установка регистра совпадения  250 - 1;  /// если надо например 2000 Гц, меняем на 125 - 1
    TCCR1B |= (1 << WGM12);                        // включить CTC режим 
    TCCR1B |= (1 << CS11); TCCR1B |= (1 << CS10);  // включить делитель /64
    TIMSK1 |= (1 << OCIE1A);                      // включить прерывание по совпадению таймера 
}

ISR(TIMER1_COMPA_vect) {
    clk1000hz = ~clk1000hz; // тик-так
}

void spi_init() {
// инициализация портов сдвигового регистра  SPI  ======================
    DDRB  |=  (1<<PB5);      // линия тактирования  clock
    DDRB  |=  (1<<PB3);      // линия данных        data
    DDRB  |=  (1<<PB2);      // линия стробирования  latch                  // или краткая запись  DDRB  |=  ((1<<PB2)|(1<<PB3)|(1<<PB5)); //ножки SPI на выход
    PORTB &= ~(1<<PB2);      // подать низ на .. линия стробирования  latch
    PORTB &= ~(1<<PB3);      // подать низ на .. линия данных        data
    PORTB &= ~(1<<PB5);      // подать низ на .. линия тактирования  clock  // или краткая запись  PORTB &= ~((1<<PB2)|(1<<PB3)|(1<<PB5)); // низкий уровень
    SPCR  =  ((1<<SPE)|(1<<MSTR)); // включим шину, объявим ведущим | Биты SPR0 SPR1 нулевыми остаются, значит, при 16 на кварце получается 4 МГц тактирование на SPI      // SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0); // Enable SPI, Set as Master, Prescaler: Fosc/16, Enable Interrupts
// ====================================================================
}


void seg7_show (uint16_t x, uint16_t y) {

    bin_bcd(x); // закидываем прочитанное значение АЦП в функцию bin_bcd, результат отправляется в структуру bcd
    counter7s[3] = bcd.thousands;
    counter7s[2] = bcd.hundreds;
    counter7s[1] = bcd.tens;
    counter7s[0] = bcd.units;
    
    bin_bcd(y);
    counter7s[7] = bcd.thousands;
    counter7s[6] = bcd.hundreds;
    counter7s[5] = bcd.tens;
    counter7s[4] = bcd.units;

    PORTB &= ~(1<<PB2);                                    // опускаем строб RDY
    SPDR  = pgm_read_byte(&digi7mass[n_count]);            // засылаем первый байт
    while(!(SPSR & (1<<SPIF)));                            // ожидаем освобождение буфера
    SPDR  = pgm_read_byte(&segm7mass[counter7s[n_count]]); // засылаем второй байт
    while(!(SPSR & (1<<SPIF)));                            // снова ждём, когда байт уйдёт из буфера
    PORTB |= (1<<PB2);                                      // поднимаем строб RDY, защёлкивая данные на выход 595
    n_count++; if (n_count>9) n_count=0;
}

// Раскладываем 9999 на цифры
void bin_bcd(uint16_t a) {
    bcd.tens=0;
    bcd.hundreds=0;
    bcd.thousands=0;
    bcd.units=a;
    while (bcd.units>=1000) {
      bcd.units-=1000;
      bcd.thousands++;
    }
    while (bcd.units>=100) {
      bcd.units-=100;
      bcd.hundreds++;
    }
    while (bcd.units>=10) {
      bcd.units-=10;
      bcd.tens++;
    }
}
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 23 Окт., 2020, 20:38
Не синхронизированы запуски ADC и отображения. ADC по факту бегает сам по себе, хотя и в режиме Noise Reduction.
Нужно: 2 режима Sleep. Перед запуском ADC нужно установить режим "ADC Noise Reduction", а по выходу из него нужно установить режим "Idle", снова впасть в Sleep (уже как Idle), теперь уже дожидаясь прерывания от таймера (перед Seg7_show), и продолжить уже показом цифр. Это если не менять тут по ходу написанного.
Мне ещё вот не ясно. Bin2BCD вызывается каждый раз, когда меняется показываемая цифра. Собственно, а зачем? Его надо вызывать один раз, когда окончен расчёт буфера ADC. Соответственно, BCD надо где-то похранить (памяти-то полно), сделав им массивчик (или он уже есть?). Итого будет 128 опросов АЦП (по текущему состоянию счётчиков), 16 показов индикатора, одно преобразование BCD. Экономия существенная (хотя и незаметная - всё-равно проц стоит, ждёт прерывания... да...).

Расчёты немца сне не очень понятны. Если ты ихъ прокомментируешь, может, будет лучше. А пока мне неясно, для чего он вычитает

V = (adc_oversampled[1] + (V << 2) - V) >> 2;
A = (adc_oversampled[0] + (A << 2) - A) >> 2;


а потом ещё ты делишь

seg7_show ( V/2, A);

когда можно сразу

V = (adc_oversampled[1] + (V << 2) - V) >> 3;

И ещё неясное место. Цифр вроде 8. А зачем

n_count++; if (n_count>9) n_count=0;
}


? Почему 9?
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 23 Окт., 2020, 20:46
Цитата: Slabovik от 23 Окт., 2020, 19:09что R16, R17 и AGnd смотрят в три разные точки
А они должны быть на Agnd?
bin2bcd вызывается только перед отправкой данных по SPI.
Цикл основный программы молотит, а if (~clk1000hz) при изменении переменной clk1000hz выполняется опрос ADC.
Переменную clk1000hz изменяет прерывание таймера.
Цитата: Slabovik от 23 Окт., 2020, 20:38И ещё неясное место. Цифр вроде 8. А зачем
Точки же. :)
Немца преобразования мне самому до конца не понятны..  :o
Цитата: Slabovik от 23 Окт., 2020, 20:38когда можно сразу
Это было временное.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 23 Окт., 2020, 20:57
Ну да, я как раз про это. Отправка по SPI отдельная для каждой цифры. Вызывается 8 раз для всего индикатора. А Bin2BCD нужно считать хотя бы только для индикатора в целом. А ещё лучше только один раз - по результатам обсчёта ADC
Цитата: zenon от 23 Окт., 2020, 20:46Цикл основный программы молотит, а if (~clk1000hz) при изменении переменной clk1000hz выполняется опрос ADC.
А это реально работает? Выведи, как я советовал, в свободный пин какого-нибудь порта (то же D) единичку перед вызовом ADC и нулик сразу после окончания работы ADC. Осциллографом можно будет посмотреть, есть ли там 1000 Гц. Мне кажется, что сейчас в условии какое-то неправильное выражение и работает не так, как задумано (а именно, 500 мкс цикл "чешет мимо условия", а вторые 500 мкс гоняет АЦП в хвост и гриву). Лично я бы предпочёл SystemTick байтовой величины, накладывал бы на неё маску и по результатам выбирал и канал, и цифру индикатора.
Цитата: zenon от 23 Окт., 2020, 20:46Точки же.
Точки - это 8-й бит в байте цифры. Передаётся вместо с цифрой в нужном разряде. Зачем 9? 8-)

Я полагаю, что при 128 отсчётах надо
V = (sum[adc_channel] + 64) >> 5;
конечно, это нужно делать в момент, когда счётчик достиг 128 (расчёт окончен).
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 23 Окт., 2020, 21:13
Передачу данных на сегменты писал выдумывал сам, она работает, да может не правильно, посоветоваться не с кем было. :)
Счёт от 0, всего 10, 8-цифры и две последние точки.
Для этого был сделан:
uint8_t counter7s[10] = { 0,  0,  0,  0, 0,  0,  0,  0, 10, 10 };
if (~clk1000hz) - работает, я uart'ом проверял, переменную какую-то дополнительно, осциллографом попробую.
Ещё раз bin2bcd считает только для индикатора, тут лишнего точно не вижу.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 23 Окт., 2020, 21:17
Ты имеешь в виду точки - двоеточие для часов? Дык ведь оно не используется.
Цитата: zenon от 23 Окт., 2020, 21:13bin2bcd считает только для индикатора
Оно считает только одну цифру? Или сразу все? Показываешь-то ты только одну...

UART'ом нельзя проверить - он слишком медленен. Условие (~clk1000hz) работает каждый раз, когда оно ~true, а  не true (т.е. false) оно каждый второй тик таймера. Пока оно false - гоняется АЦП по кругу всего цикла по принципу "а сколько успеет", а потом, когда таймер его перекидывает в true, АЦП вовсе не запускается, главный цикл гоняется впустоту. А гонять нужно один раз за один тик. Исправить можно, если clk1000 перекидывать в первоначальное состояние после (перед) работой АЦП. Тогда каждый тик таймера будет его "взводить", а запуск АЦП "спускать". Будет один запуск АЦП на тик.
ЦитатаА они должны быть на Agnd?
Собственно, земля там едина, но вот если по ней побежать - не совсем, ибо проводник длинный и имеет сопротивление. Но для тестовой платы, повторюсь, это скорее всего не имеет значения. В продакшн такое нельзя - выводить надо точнее (поэтому, потому я за двуслойные платы, там это делать много проще, чем на однослойке)
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 23 Окт., 2020, 21:53
Вот смотри тут тикнули в одну сторону 0/255:
ISR(TIMER1_COMPA_vect) {
    clk1000hz = ~clk1000hz; // тик-так
}
При следующем - в другую.
А тут if (~clk1000hz) при любом изменении сработает.
Плата двусторонняя, но с землей намудрил однозначно. Переходы запаяны кусками выводов.
Слишком медленное замеряю счетчиками, даже светодиодом можно проверить быстрые вещи.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 23 Окт., 2020, 22:02
Эммм, имеешь ввиду вот это разбить на то, чтобы только один раз тут bin2bcd вызывалась?
void seg7_show (uint16_t x, uint16_t y) {

    bin_bcd(x); // закидываем прочитанное значение АЦП в функцию bin_bcd, результат отправляется в структуру bcd
    counter7s[3] = bcd.thousands;
    counter7s[2] = bcd.hundreds;
    counter7s[1] = bcd.tens;
    counter7s[0] = bcd.units;
    
    bin_bcd(y);
    counter7s[7] = bcd.thousands;
    counter7s[6] = bcd.hundreds;
    counter7s[5] = bcd.tens;
    counter7s[4] = bcd.units;

...
...
...
}

Сейчас подумаю как.
.... хотя не знаю как...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 23 Окт., 2020, 22:05
Да.
0 - это False
255 (точнее всё, что не 0) - это true
Таймер у тебя перекидывает значение переменной : -false-true-false-


Проверка if на условие (~clk1000hz) по факту означает, что then выполняется, когда clk1000hz имеет значение false
А теперь смотри. У тебя "then" выполнялось, скобочки закрылись. clk1000hz как-то  изменяла своё значение? По-моему, нет, я не вижу в коде. Проверка в if значение clk1000hz не меняет (это же только проверка). Это значит, что согласно while(1) тут же начнётся, без всяких вариантов и каких-либо ожиданий, снова этот этот же if и снова с точно этим же условием... А нужно, чтобы он дождался, пока таймер перекинет clk1000hz в противоположное значение...

Надо так
while(1) {

      if (~clk1000hz) {
        clk1000hz = ~clk1000hz;    // вот это остановит безумную гонку АЦП до следующего тика таймера
        ADCSRA |= (1<<ADSC);
при этом у условии что ~clk, что просто clk - результат будет один. Таймер перекинет clk в одну сторону, первый же if (clk) перекинет его обратно, выполнив условие только один раз до следующего таймера...

Bin2BCD скорее всего саму редактировать не надо. Надо убрать её вызов из SegmentShow
bin_bcd([font=Arial Black]V[/font]); // закидываем прочитанное значение АЦП в функцию bin_bcd, результат отправляется в структуру bcd
    counter7s[3] = bcd.thousands;
    counter7s[2] = bcd.hundreds;
    counter7s[1] = bcd.tens;
    counter7s[0] = bcd.units;
   
    bin_bcd([font=Arial Black]A[/font]);
    counter7s[7] = bcd.thousands;
    counter7s[6] = bcd.hundreds;
    counter7s[5] = bcd.tens;
    counter7s[4] = bcd.units;
и вставить туда, где

if (++counter[adc_channel] > 128 - 1) { сюда }

На выходе BCD у тебя циферки BCD уложенные в индексированный массив "counter7s". Следом, уже вне этого условия, у тебя SegmentShow берёт очередную циферку по индексу (нормер разряда индикатора) и дует его в SPI. А counter7s, однажды посчитанный по результатам обсчёта буфера ADC, стоит как вкопанный до получения следующего результата от фуфера ADC, что случается только один раз из 128 проходов clk1000hz

Кстати, сюда же, в скобки нужно и подсчёт V и A вставить, а то они тоже считаются с каждым проходом, что совершенно не нужно. Заодно и промежуточное adc_oversampled можно будет забыть (а может и оставить, надо только придумать, зачем)

зыыы. Сейчас вспомнил... у меня же ардуина есть и бредбоард. Вполне могу собрать макетку... Правда, она поганая будет по части аналоговой разводки, что нуль там крайне маловероятен, но можно отлаживать.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 23 Окт., 2020, 22:53
По порядку.
Вот кусок кода 1.
    while(1) {

      if (~clk1000hz) {
  output_low(PORTD, LED);

        ADCSRA |= (1<<ADSC);                      // запуск преобразования ADC
        sleep_cpu();                              // и засыпаем, проснёмся в прерывании, после того, как результат ADC будет готов
        sum[adc_channel] += ADC;
        if (++counter[adc_channel] > 128 - 1) { 
          adc_oversampled[adc_channel] = sum[adc_channel] >> 4;
          sum[adc_channel] = 0;
          counter[adc_channel] = 0;
        }
        V = (adc_oversampled[1] + (V << 2) - V) >> 2;
        A = (adc_oversampled[0] + (A << 2) - A) >> 2;
        seg7_show ( V/2, A);
        adc_channel = (adc_channel+1) % 2;        // следующий канал
        adc_select_channel(adc_channel+6);
    
        
    output_high(PORTD, LED);
      }
    }
После if добавил переброс ноги вниз, в конце вверх.
Кусок кода 2.
    while(1) {

      if (~clk1000hz) {
  output_low(PORTD, LED);

        ADCSRA |= (1<<ADSC);                      // запуск преобразования ADC
        sleep_cpu();                              // и засыпаем, проснёмся в прерывании, после того, как результат ADC будет готов
        sum[adc_channel] += ADC;
        if (++counter[adc_channel] > 128 - 1) { 
          adc_oversampled[adc_channel] = sum[adc_channel] >> 4;
          sum[adc_channel] = 0;
          counter[adc_channel] = 0;
        }
        V = (adc_oversampled[1] + (V << 2) - V) >> 2;
        A = (adc_oversampled[0] + (A << 2) - A) >> 2;
        seg7_show ( V/2, A);
        adc_channel = (adc_channel+1) % 2;        // следующий канал
        adc_select_channel(adc_channel+6);
    
    clk1000hz = ~clk1000hz;
    
    output_high(PORTD, LED);
      }
    }
Где добавил clk1000hz = ~clk1000hz; в конце then.
Ну и картинки с этой ноги. По-моему ничего не изменилось?
Только вот почему частота под 5 kHz???
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 23 Окт., 2020, 23:32
И теперь внимание: во время работы ADC ты ногу опускаешь, по окончании поднимаешь. Первое: почему у тебя ожидание (нога поднята) такое короткое? А опущенна нога 200 с небольшим микросекунд - это время работы АЦП + расчёт. При 125 кГц 13 циклов работы АЦП занимают примерно 105 микросекунд, ещё сотня - расчёты да выпих в SPI.
Из всего получается, не работает таймер. Совсем. Если бы он работал, преобладала бы поднятая нога и период был бы порядка 1100-1200 мксек, а не 200 с небольшим, как сейчас.

Делай:
1. внутри прерывания таймера clk1000hz++ (увеличение на 1)
2. внутри while(1)
{
seg7show (clk1000hz & 3) // номер цифры индикатора от 0 до 7

set_sleep_mode(SLEEP_MODE_ADC);
sei()

if ( (clk1000hz & 1) = 0)
   then { чтение ADC канал V;
          if (counterV=counterVmax)
             then { расчёт нового V;
                    преобразование V в BCD; // в массив Counter7S
                    counterV=0
                  }
             else {counterV=counterV+1;
                  }

        }
   else { чтение ADC канал A
          if (counterA=counterAmax)
             then { расчёт нового A;
                    преобразование A в BCD; // в массив Counter7S
                    counterA=0;
                  }
             else {counterA=counterA+1;
                  }
        };

set_sleep_mode(SLEEP_MODE_IDLE); // уточни, как правильно написать, но вроде так
sei(); // а не забыл ли ты SEI на выходе из прерывания по таймеру? А ведь надо. Вставим здесь.
sleep_cpu();
} // конец главного цикла While - прыг на начало, как только случится прерывание по таймеру (или какое-нибудь другое, если вдруг оно будет настроено).
Я не уточняю, какие именно буквы писать, я показал общий алгоритм, как оно должно работать.

Seg7show надо поправить. У тебя есть преобразованный массив bcd цифр для показа (counter7s), он глобальный, на входе Seg7Show нужен только индекс цифры, которая предназначена к показу.

гы: гляди-как, как полезно дёргать ножкой :)
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 23 Окт., 2020, 23:38
Хммм.
Сделал.
    while(1) {

      if (~clk1000hz) {
        clk1000hz = ~clk1000hz; 
output_low(PORTD, LED);

        ADCSRA |= (1<<ADSC);                      // запуск преобразования ADC
        sleep_cpu();                              // и засыпаем, проснёмся в прерывании, после того, как результат ADC будет готов
        sum[adc_channel] += ADC;
        if (++counter[adc_channel] > 128 - 1) { 
          adc_oversampled[adc_channel] = sum[adc_channel] >> 4;
          sum[adc_channel] = 0;
          counter[adc_channel] = 0;
        
        
                if (adc_channel==0) {
                    bin_bcd(adc_oversampled[1]); // закидываем прочитанное значение АЦП в функцию bin_bcd, результат отправляется в структуру bcd
                    counter7s[3] = bcd.thousands;
                    counter7s[2] = bcd.hundreds;
                    counter7s[1] = bcd.tens;
                    counter7s[0] = bcd.units;
                } else {
                    bin_bcd(adc_oversampled[0]);
                    counter7s[7] = bcd.thousands;
                    counter7s[6] = bcd.hundreds;
                    counter7s[5] = bcd.tens;
                    counter7s[4] = bcd.units;
                }
          
        }
        seg7_show ( V, A);
        adc_channel = (adc_channel+1) % 2;        // следующий канал
        adc_select_channel(adc_channel+6);
      }
    }
Но это не решило проблему, болтанка только увеличилась.
+++++++
Нашёл!
Я тут экспериментировал с этим таймером, который 1000Гц.
Там есть строка:
OCR1A  = 125 - 1;                            // установка регистра совпадения  250 - 1;  /// если надо например 2000 Гц, меняем на 125 - 1
Цифру 125 заменил на 250 = всё! болтанка (дребезг) ушла!
Но вопрос по частоте остаётся, сейчас она 8,47 kHz на ноге контроллера.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 23 Окт., 2020, 23:50
Ну, потому что таймер таки не работает...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 23 Окт., 2020, 23:53
Так, ну я окончательно запутался тогда.
Вот крайний мой вариант, изменил всё как ты сказал.
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
//#include <util/delay.h>
#include <avr/sleep.h>
//#include "uartz.h"

// Переменные
typedef unsigned char byte; //создание типа - байт
byte adc_channel = 0;
byte clk1000hz = 0;
volatile uint32_t adc_oversampled[2];
#define adc_select_channel(channel) (ADMUX = (ADMUX & 0xF0) + channel)

struct { uint8_t tens,hundreds,thousands; uint16_t units; }bcd;
// Места знаков и точек
uint8_t const digi7mass [] PROGMEM = { 0b01111111, 0b10111111, 0b11011111, 0b11101111, 0b11110111, 0b11111011, 0b11111101, 0b11111110, 0b11011111, 0b11111101 };
// ABCEDFGH
uint8_t const segm7mass [] PROGMEM = { 0b00111111, 0b00000110, 0b01011011, 0b01001111, 0b01100110, 0b01101101, 0b01111101, 0b00000111, 0b01111111, 0b01101111, 0b10000000, 0b00000000 };
uint8_t counter7s[10] = { 0,  0,  0,  0, 0,  0,  0,  0, 10, 10 };
unsigned char n_count=0;
uint16_t adc_buffer[2];

// Функции
void adc_init(void);
void timer1000hz_init(void);
void spi_init(void);

void bin_bcd(uint16_t a);
void seg7_show (void);
// void seg7_show (uint16_t x, uint16_t y);

static uint32_t    sum[2];
static uint8_t counter[2];

uint32_t V = 0, A = 0;

#define LED PD7
#define output_low(port,pin) port &= ~(1<<pin)
#define output_high(port,pin) port |= (1<<pin)
#define set_input(portdir,pin) portdir &= ~(1<<pin)
#define set_output(portdir,pin) portdir |= (1<<pin)

// +++
int main() {

    adc_init();
    spi_init();
    timer1000hz_init();
    set_sleep_mode(SLEEP_MODE_ADC);
    sleep_enable();
    set_output(DDRD, LED);
    sei();

    while(1) {

      if (~clk1000hz) {
clk1000hz = ~clk1000hz; 
output_low(PORTD, LED);

        ADCSRA |= (1<<ADSC);                      // запуск преобразования ADC
        sleep_cpu();                              // и засыпаем, проснёмся в прерывании, после того, как результат ADC будет готов
        sum[adc_channel] += ADC;
        if (++counter[adc_channel] > 128 - 1) { 
          adc_oversampled[adc_channel] = sum[adc_channel] >> 4;
          sum[adc_channel] = 0;
          counter[adc_channel] = 0;
        
                V = (adc_oversampled[1] + (V << 2) - V) >> 2;
                A = (adc_oversampled[0] + (A << 2) - A) >> 2;
                if (adc_channel==0) {
                    bin_bcd(V/2); // закидываем прочитанное значение АЦП в функцию bin_bcd, результат отправляется в структуру bcd
                    counter7s[3] = bcd.thousands;
                    counter7s[2] = bcd.hundreds;
                    counter7s[1] = bcd.tens;
                    counter7s[0] = bcd.units;
                } else {
                    bin_bcd(A);
                    counter7s[7] = bcd.thousands;
                    counter7s[6] = bcd.hundreds;
                    counter7s[5] = bcd.tens;
                    counter7s[4] = bcd.units;
                }
          
        }

        seg7_show ();
        adc_channel = (adc_channel+1) % 2;        // следующий канал
        adc_select_channel(adc_channel+6);

output_high(PORTD, LED);
      }
    }
}
// +++

ISR(ADC_vect) {
// тут ничего не делаем, нужно, чтобы проснуться, (1<<ADIE)
}

void adc_init(void) {
    ADMUX  |= (0<<REFS1)|(0<<REFS0); // REFS1=0 REFS0=0 = Внешний источник, подключенный к AREF, внутренний VREF отключен
    ADCSRA |= (1<<ADIE )|(1<<ADEN )|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); // ADC_CLOCK_PRESCALER128 задаем тактовую частоту ADC (16000/128 = 125 кГц). 
    //DIDR0  |= (1<<ADC1D)|(1<<ADC0D); // disable digital inputs on ADC pins
}

void timer1000hz_init(void) {
// настройка прерываний по таймеру 1000 Гц =============================
    TCCR1A  = 0; TCCR1B = 0;                      // установить регистры в 0
    OCR1A  = 250 - 1;                            // установка регистра совпадения  250 - 1;  /// если надо например 2000 Гц, меняем на 125 - 1
    TCCR1B |= (1 << WGM12);                        // включить CTC режим 
    TCCR1B |= (1 << CS11); TCCR1B |= (1 << CS10);  // включить делитель /64
    TIMSK1 |= (1 << OCIE1A);                      // включить прерывание по совпадению таймера 
}

ISR(TIMER1_COMPA_vect) {
    clk1000hz = ~clk1000hz; // тик-так
}

void spi_init() {
// инициализация портов сдвигового регистра  SPI  ======================
    DDRB  |=  (1<<PB5);      // линия тактирования  clock
    DDRB  |=  (1<<PB3);      // линия данных        data
    DDRB  |=  (1<<PB2);      // линия стробирования  latch                  // или краткая запись  DDRB  |=  ((1<<PB2)|(1<<PB3)|(1<<PB5)); //ножки SPI на выход
    PORTB &= ~(1<<PB2);      // подать низ на .. линия стробирования  latch
    PORTB &= ~(1<<PB3);      // подать низ на .. линия данных        data
    PORTB &= ~(1<<PB5);      // подать низ на .. линия тактирования  clock  // или краткая запись  PORTB &= ~((1<<PB2)|(1<<PB3)|(1<<PB5)); // низкий уровень
    SPCR  =  ((1<<SPE)|(1<<MSTR)); // включим шину, объявим ведущим | Биты SPR0 SPR1 нулевыми остаются, значит, при 16 на кварце получается 4 МГц тактирование на SPI      // SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0); // Enable SPI, Set as Master, Prescaler: Fosc/16, Enable Interrupts
// ====================================================================
}


void seg7_show (void) {

    PORTB &= ~(1<<PB2);                                    // опускаем строб RDY
    SPDR  = pgm_read_byte(&digi7mass[n_count]);            // засылаем первый байт
    while(!(SPSR & (1<<SPIF)));                            // ожидаем освобождение буфера
    SPDR  = pgm_read_byte(&segm7mass[counter7s[n_count]]); // засылаем второй байт
    while(!(SPSR & (1<<SPIF)));                            // снова ждём, когда байт уйдёт из буфера
    PORTB |= (1<<PB2);                                      // поднимаем строб RDY, защёлкивая данные на выход 595
    n_count++; if (n_count>9) n_count=0;
}

// Раскладываем 9999 на цифры
void bin_bcd(uint16_t a) {
    bcd.tens=0;
    bcd.hundreds=0;
    bcd.thousands=0;
    bcd.units=a;
    while (bcd.units>=1000) {
      bcd.units-=1000;
      bcd.thousands++;
    }
    while (bcd.units>=100) {
      bcd.units-=100;
      bcd.hundreds++;
    }
    while (bcd.units>=10) {
      bcd.units-=10;
      bcd.tens++;
    }
}

И сейчас это работает хорошо.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 24 Окт., 2020, 00:01
Помедитируй-таки над алгоритмом с предыдущей страницы (https://anklab.ru/forum/index.php?msg=327) (белый такой). Именно его я изобразил парой постов выше. Должно работать, только нужно немного переработать процедуру show7seg - она должна в качестве параметра получать номер показываемой цифры, которые она и так берёт из массива, куда любезно их уложил Bin2BCD. Только в "белом алгоритме" позиций индикатора у меня 4 (я так задумывал), а у тебя все восемь (если я не ошибаюсь)

Также, если пока не ломать, попробуй добавить sei(); на выходе из прерывания по таймеру, а также на выходе из прерывания от ADC. Я не знаю, добавляет ли Си автоматом это, или нет. ВРоде в бинарном коде я не видел SEI на выходе из прерывания. А если нет SEI, значит, прерывания не работают (работают, но только один раз).
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 24 Окт., 2020, 00:02
Цитата: Slabovik от 23 Окт., 2020, 22:05Сейчас вспомнил... у меня же ардуина есть и бредбоард. Вполне могу собрать макетку... Правда, она поганая будет по части аналоговой разводки, что нуль там крайне маловероятен, но можно отлаживать.
Фьюзы даже менять не надо, только бутлоадер затрётся.
Архив с makefile.
Все что надо - geany и две команды:
make all
make flash
:)
Добавлю, на всякий архив проекта кикад, земля там не правильная :)
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 24 Окт., 2020, 00:23
Цитата: Slabovik от 23 Окт., 2020, 23:50Ну, потому что таймер таки не работает...
Ну как же не работает?
Вот есть (http://www.8bit-era.cz/arduino-timer-interrupts-calculator.html) генератор кода.
Взял кусок оттуда:
// TIMER 1 for interrupt frequency 1000 Hz:
TCCR1A = 0; // set entire TCCR1A register to 0
TCCR1B = 0; // same for TCCR1B
TCNT1  = 0; // initialize counter value to 0
// set compare match register for 1000 Hz increments
OCR1A = 15999; // = 16000000 / (1 * 1000) - 1 (must be <65536)
// turn on CTC mode
TCCR1B |= (1 << WGM12);
// Set CS12, CS11 and CS10 bits for 1 prescaler
TCCR1B |= (0 << CS12) | (0 << CS11) | (1 << CS10);
// enable timer compare interrupt
TIMSK1 |= (1 << OCIE1A);
Ровным счётом ничего не изменилось, работает хорошо, на осцилорафе с ноги LED = 8,4 kHz.
sei(); пробовал добавлять .... не.
Попробую другие таймеры ещё.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 24 Окт., 2020, 00:24
А, вот ещё. clk1000hz объявление и инициализация где? В процедуре TimerInit ей надо что-то присвоить. "Что-то" - это 0. Потому что в бинарном коде там ерунда
00000118 <__vector_11>:
 118:	1f 92       	push	r1
 11a:	0f 92       	push	r0
 11c:	0f b6       	in	r0, 0x3f	; 63
 11e:	0f 92       	push	r0
 120:	11 24       	eor	r1, r1
 122:	8f 93       	push	r24
 124:	80 91 0b 01 	lds	r24, 0x010B
 128:	80 95       	com	r24
 12a:	80 93 0b 01 	sts	0x010B, r24
 12e:	8f 91       	pop	r24
 130:	0f 90       	pop	r0
 132:	0f be       	out	0x3f, r0	; 63
 134:	0f 90       	pop	r0
 136:	1f 90       	pop	r1
 138:	18 95       	reti
строка 128 - команда на комплементарное преобразование. У тебя clk по факту знаковое!
Надо: а) объявить clk беззнаковым; б) при инициализации присвоить 0. Поскольку при старте проца в ОЗУ может находиться какой-нибудь мусор от прошлых запусков. Да и вообще взять за правило начальную инициализацию переменных.

Да, команды SEI я тут не вижу. Она должна быть перед RETI (если конечно не задумывалось сто-нибудь другое). Вход в прерывание сбрасывает флаг EI, запрещая другие прерывания совсем. Восстанавливать флаг нужно вручную - это программист должен заботиться.
Макет я всё-равно быстро не соберу. На выходные снова в поля, помёрзну малость (у нас уже минус, снег вон не тает).

Настоятельно рекомендую (https://anklab.ru/forum/index.php?msg=375)
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 24 Окт., 2020, 00:26
Цитата: Slabovik от 24 Окт., 2020, 00:24clk1000hz объявление и инициализация где?
В самом начале:
// Переменные
typedef unsigned char byte; //создание типа - байт
byte adc_channel = 0;
byte clk1000hz = 0;
Цитата: Slabovik от 24 Окт., 2020, 00:24На выходные снова в поля, помёрзну малость (у нас уже минус, снег вон не тает).
Путаюсь, поля - это дача? Или велопрогулки?
Мы ещё в лёгких курточках, сегодня +18, на солнце даже жарко было.
;)
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 24 Окт., 2020, 00:36
А, это я проглядел. Ну, значит, здесь порядок - есть инициализация. Хотя с другой стороны, в .lss я не обнаружил, чтобы в эту ячейку что-то писалось до входа в main... Правда, там какой-то блок из памяти программ в ОЗУ в самом начале копируется. Возможно, это инициализация и есть. Тут без отладчика трудно смотреть...
Цитата: zenon от 24 Окт., 2020, 00:26поля - это дача? Или велопрогулки?
Это деревня. Ведь Тюмень - столица деревень :) А там и огород, и сарай, и поля, и велосипед, и добрейшей души уличная собака  ::) И работы край непочатый  :'(
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 24 Окт., 2020, 00:46
Красота!
А я вот сюда (https://www.google.ru/maps/place/%D0%A1%D1%83%D1%80%D0%BE%D0%B2%D0%B8%D0%BA%D0%B8%D0%BD%D0%BE,+%D0%92%D0%BE%D0%BB%D0%B3%D0%BE%D0%B3%D1%80%D0%B0%D0%B4%D1%81%D0%BA%D0%B0%D1%8F+%D0%BE%D0%B1%D0%BB./@48.6028632,42.8247538,17.75z/data=!4m5!3m4!1s0x411b77bab4a328e5:0x787e482262353969!8m2!3d48.6102216!4d42.8528334) часто езжу.
Речка, собака тоже там... В этом году только редко получалось.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 24 Окт., 2020, 00:54
Красота красотой, но полгода зимы вгоняют в депрессняк :(
↓ спойлер ↓


правда, фотографироваться вообще не любит
[свернуть]
А местность у вас весёлая. Более "бугристая", а отличие от местных болотистых равнин :) На велике должно быть прикольно :))

Тем не менее, продолжаю смотреть .lss
Будешь смеяться: вот как выглядит тот самый if (~clk1000)
 250:	80 91 0b 01 	lds	r24, 0x010B
 254:	80 95       	com	r24
 256:	80 93 0b 01 	sts	0x010B, r24
 25a:	5f 98       	cbi	0x0b, 7	; 11
 25c:	80 91 7a 00 	lds	r24, 0x007A
 260:	80 64       	ori	r24, 0x40	; 64
 262:	80 93 7a 00 	sts	0x007A, r24
 266:	88 95       	sleep
Ну т.е. никак. Никакого IF компилятор не создал вообще. Он читает этот clk, инвертирует его и идёт опрашивать ADC. Никаких ветвлений здесь нет. Значит, IF не работает - его просто нет. Что в свою очередь значит, что нужно менять условия - делать их более явными. Ну, или смотреть, где накосячено со скобками, точками с запятыми и т.п...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 24 Окт., 2020, 18:47
Ха, ты прав!
Какая - такая штуковина получилась.  :o
Сделал явное:
if (clk1000hz == 0) {
    if (clk1000hz == 0) clk1000hz=255; else clk1000hz=0;
...
...
...
}
...
...
...
ISR(TIMER1_COMPA_vect) {
    if (clk1000hz == 0) clk1000hz=255; else clk1000hz=0;
}
Нууууу.... заработать-то заработало, но! индикатор стал медленным как черепаха.
Попробовал вообще убрал прерывание по таймеру и этот злополучный if, - таки всё работает великолепно.
Может что не так с тактовой частотой?
На кварце нормальные 16 MHz.
ы. Код похудел до 1008 байт... :)
+++
ыы. Залил вот такой код, для 28-ой ноги:
#include <avr/io.h>
int main (void) {
  DDRC |= (1 << PC5);
  while (1)
  {
      PORTC |=  (1 << PC5 );
      PORTC &= ~(1 << PC5 );
  }
}
Картинка такая получилась:
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 24 Окт., 2020, 19:44
Значит, компилятор наумничал :)
Теперь давай посчитаем.
Пусть предел по напряжению 4096. 4096 больше 1024 в 4 раза. Для увеличения разрешения вдвое нужно 4 отсчёта. Вчетверо - четыре по четыре. Следовательно, для получения точности 1 на шкале 4096 необходимо 16 отсчётов.
Сейчас делается 128 отсчётов.

Таймер тикает 1 кГц (реально немного меньше из-за торможения от Noise Reduction). Канал замеряется один раз в два тика, т.е. 500 Гц. Для накопления 128 отсчётов нужно 128*2=256 тиков. Итого: показания обновляются два раза в секунду. Не часто.

Когда ты отказываешься от таймера, частота опроса ADC идёт с максимально возможной скоростью (картинку ты ранее показывал).

Необходимо сделать (ччёрт, я же это писал уже)
вариант 1: снизить количество отсчётов до необходимого. Нет никакого смысла считать чего-то долго, если мы всё-равно не пользуемся результатом. Вполне достаточно иметь 32 отсчёта. При 32 отсчётах обновление будет 8 раз в секунду - вполне приемлемо. Если хочется, можно немного (раза в полтора) ускорить таймер - место (по времени) там есть. Можно отказаться от принципа "один цикл - один канал ADC" и опрашивать в каждом цикле оба канала ADC - это ещё увеличит скорость накопления буфера вдвое.

вариант 2: сделать кольцевой буфер. Тогда показания можно будет обновлять хоть каждый отсчёт. Однако каждый отсчёт обновлять индикатор настоятельно не рекомендую. Человек всё-равно не в состоянии уловить столь быструю смену цифр. Быстрее 10-15 Гц нафиг не нужно...

А с опытом и картинкой не понял, какая цель преследовалась. То,ч то получился короткий цикл и такая скорость переключения - это всё закономерно.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 24 Окт., 2020, 20:27
Я хотел удостовериться - действительно ли тактируется от кварца 16 MHz.
Фьюзы сейчас у меня: L:0xFF, H:0xDA, E:0x05.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 24 Окт., 2020, 20:43
Если код (.lss) покажешь, что там, скажу, сколько машинных циклов в этом "while".
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 24 Окт., 2020, 20:50
Тут вот как получается, сейчас, когда без таймера и опрос постоянный, - мне нравится как работает.
Скорость не большая, вот снял (https://www.youtube.com/watch?v=DBajoM08A6s).
freq_test.lss приложил.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 24 Окт., 2020, 21:04
Крутится там вот это
 82:	45 9a       	sbi	0x08, 5	; 8
  84:	45 98       	cbi	0x08, 5	; 8
  86:	fd cf       	rjmp	.-6      	; 0x82 <main+0x2>
sbi-cbi выполняются за два цикла
rjmp тоже выполняется два цикла.
Итого два цикла в положении "1", четыре цикла в положении "0", всего шесть.
при 16 мегагерцах один цикл 62,5 наносекунды.
В положении "1" ожидаем 125 наносекунд, в положении "0" - 250 наносекунд. Всего 375 наносекунд, что соответствует 2,66(6)МГц частоты.
Именно это твой осциллограф и показывает.

зы: Если после SBI вставить два NOP, то на ноге будет ровный меандр, а его частота 2 МГц.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 24 Окт., 2020, 22:56
Пока оставил тот вариант как запасной.
Таймер пришлось настроить для комфортного обновления на 8 kHz (6 тоже нормально).
Тут пока некоторые моменты не так, но мне надо понять где теряются данные.
Если вот этот кусок в теле основного цикла, то работает.
// ---
        if (adc_channel==0) {
            bin_bcd(V/2); // закидываем прочитанное значение АЦП в функцию bin_bcd, результат отправляется в структуру bcd
            counter7s[3] = bcd.thousands;
            counter7s[2] = bcd.hundreds;
            counter7s[1] = bcd.tens;
            counter7s[0] = bcd.units;
        } else {
            bin_bcd(A);
            counter7s[7] = bcd.thousands;
            counter7s[6] = bcd.hundreds;
            counter7s[5] = bcd.tens;
            counter7s[4] = bcd.units;
        }
// ---
Если я его отправляю под if (clk1000hz==0) { то всё по 0000.
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>


// Переменные
typedef unsigned char byte; //создание типа - байт
byte adc_channel = 0;
byte clk1000hz = 0;
volatile uint32_t adc_oversampled[2];
#define adc_select_channel(channel) (ADMUX = (ADMUX & 0xF0) + channel)

struct { uint8_t tens,hundreds,thousands; uint16_t units; }bcd;
struct { uint8_t tens,hundreds,thousands; uint16_t units; }bcd_V;
struct { uint8_t tens,hundreds,thousands; uint16_t units; }bcd_A;
// Места знаков и точек
uint8_t const digi7mass [] PROGMEM = { 0b01111111, 0b10111111, 0b11011111, 0b11101111, 0b11110111, 0b11111011, 0b11111101, 0b11111110, 0b11011111, 0b11111101 };
// ABCEDFGH
uint8_t const segm7mass [] PROGMEM = { 0b00111111, 0b00000110, 0b01011011, 0b01001111, 0b01100110, 0b01101101, 0b01111101, 0b00000111, 0b01111111, 0b01101111, 0b10000000, 0b00000000 };
uint8_t counter7s[10] = { 0,  0,  0,  0, 0,  0,  0,  0, 10, 10 };
unsigned char n_count=0;
uint16_t adc_buffer[2];

// Функции
void adc_init(void);
void timer1000hz_init(void);
void spi_init(void);

void bin_bcd(uint16_t a);
void seg7_show (void);

static uint32_t    sum[2];
static uint8_t counter[2];

uint32_t V = 0, A = 0;

#define LED PD7
#define output_low(port,pin) port &= ~(1<<pin)
#define output_high(port,pin) port |= (1<<pin)
#define set_input(portdir,pin) portdir &= ~(1<<pin)
#define set_output(portdir,pin) portdir |= (1<<pin)

// +++
int main() {
    adc_init();
    spi_init();
    timer1000hz_init();
    set_sleep_mode(SLEEP_MODE_ADC);
    sleep_enable();
    set_output(DDRC, PC5);
    sei();


    while(1) {

        if (clk1000hz==0) {
output_low(PORTC, PC5);
            clk1000hz=255;
            ADCSRA |= (1<<ADSC);                      // запуск преобразования ADC
            sleep_cpu();                              // и засыпаем, проснёмся в прерывании, после того, как результат ADC будет готов
            sum[adc_channel] += ADC;
            if (++counter[adc_channel] > 128 - 1) { 
                adc_oversampled[adc_channel] = sum[adc_channel] >> 4;
                sum[adc_channel] = 0;
                counter[adc_channel] = 0;
            }
            if (adc_channel==0) V = (adc_oversampled[1] + (V << 2) - V) >> 2;
            else                A = (adc_oversampled[0] + (A << 2) - A) >> 2;
            adc_channel = (adc_channel+1) % 2;        // следующий канал
            adc_select_channel(adc_channel+6);
output_high(PORTC, PC5);
        }
// ---
        if (adc_channel==0) {
            bin_bcd(V/2); // закидываем прочитанное значение АЦП в функцию bin_bcd, результат отправляется в структуру bcd
            counter7s[3] = bcd.thousands;
            counter7s[2] = bcd.hundreds;
            counter7s[1] = bcd.tens;
            counter7s[0] = bcd.units;
        } else {
            bin_bcd(A);
            counter7s[7] = bcd.thousands;
            counter7s[6] = bcd.hundreds;
            counter7s[5] = bcd.tens;
            counter7s[4] = bcd.units;
        }
// ---
    }
}
// +++

ISR(ADC_vect) {
// тут ничего не делаем, нужно, чтобы проснуться, (1<<ADIE)
}

void adc_init(void) {
    ADMUX  |= (0<<REFS1)|(0<<REFS0); // REFS1=0 REFS0=0 = Внешний источник, подключенный к AREF, внутренний VREF отключен
    ADCSRA |= (1<<ADIE )|(1<<ADEN )|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); // ADC_CLOCK_PRESCALER128 задаем тактовую частоту ADC (16000/128 = 125 кГц). 
    
}

void timer1000hz_init(void) {
// TIMER 1 for interrupt frequency 8000 Hz:
cli(); // stop interrupts
TCCR1A = 0; // set entire TCCR1A register to 0
TCCR1B = 0; // same for TCCR1B
TCNT1  = 0; // initialize counter value to 0
// set compare match register for 8000 Hz increments
OCR1A = 1999; // = 16000000 / (1 * 8000) - 1 (must be <65536)
// turn on CTC mode
TCCR1B |= (1 << WGM12);
// Set CS12, CS11 and CS10 bits for 1 prescaler
TCCR1B |= (0 << CS12) | (0 << CS11) | (1 << CS10);
// enable timer compare interrupt
TIMSK1 |= (1 << OCIE1A);
sei(); // allow interrupts
}

ISR(TIMER1_COMPA_vect) {
    clk1000hz = 0; 
    seg7_show ();
}

void spi_init() {
// инициализация портов сдвигового регистра  SPI  ======================
    DDRB  |=  (1<<PB5);      // линия тактирования   clock
    DDRB  |=  (1<<PB3);      // линия данных         data
    DDRB  |=  (1<<PB2);      // линия стробирования  latch                   // или краткая запись  DDRB  |=  ((1<<PB2)|(1<<PB3)|(1<<PB5)); //ножки SPI на выход
    PORTB &= ~(1<<PB2);      // подать низ на .. линия стробирования  latch
    PORTB &= ~(1<<PB3);      // подать низ на .. линия данных         data
    PORTB &= ~(1<<PB5);      // подать низ на .. линия тактирования   clock  // или краткая запись  PORTB &= ~((1<<PB2)|(1<<PB3)|(1<<PB5)); // низкий уровень
    SPCR  =  ((1<<SPE)|(1<<MSTR)); // включим шину, объявим ведущим | Биты SPR0 SPR1 нулевыми остаются, значит, при 16 на кварце получается 4 МГц тактирование на SPI       // SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0); // Enable SPI, Set as Master, Prescaler: Fosc/16, Enable Interrupts
// ====================================================================
}


void seg7_show (void) {
    PORTB &= ~(1<<PB2);                                     // опускаем строб RDY
    SPDR   = pgm_read_byte(&digi7mass[n_count]);            // засылаем первый байт
    while(!(SPSR & (1<<SPIF)));                             // ожидаем освобождение буфера
    SPDR   = pgm_read_byte(&segm7mass[counter7s[n_count]]); // засылаем второй байт
    while(!(SPSR & (1<<SPIF)));                             // снова ждём, когда байт уйдёт из буфера
    PORTB |= (1<<PB2);                                      // поднимаем строб RDY, защёлкивая данные на выход 595
    n_count++; if (n_count>9) n_count=0;
}

// Раскладываем 9999 на цифры
void bin_bcd(uint16_t a) {
    bcd.tens=0;
    bcd.hundreds=0;
    bcd.thousands=0;
    bcd.units=a;
    while (bcd.units>=1000) {
      bcd.units-=1000;
      bcd.thousands++;
    }
    while (bcd.units>=100) {
      bcd.units-=100;
      bcd.hundreds++;
    }
    while (bcd.units>=10) {
      bcd.units-=10;
      bcd.tens++;
    }
}

... делать вторую структуру для bin_bcd?
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 25 Окт., 2020, 00:40
Не могу увидеть причину неработы. По мне так структура нафиг не нужна (просто лишняя сущность, без которой можно прекрасно обойтись), но почему там нули - это мне не ясно. Значит, на входе процедуры BCD нули. А это A и V. Обсчитываются они странно, что-то внутри клока, остальное снаружи... Считать ведь надо один раз, а не каждый проход, уж как мне сейчас это поправить? Только переписать полностью, но мой макет не готов...

Но... почему бы не начать писать свой код, добиваясь его работоспособности, а не пытаться обрабатывать методом тыка чужой? (это я про немца). По крайней мере будешь чётко понимать, что и зачем делаешь - уже будет легче.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 25 Окт., 2020, 01:11
Так он уже почти свой.
Цитата: Slabovik от 25 Окт., 2020, 00:40Значит, на входе процедуры BCD нули. А это A и V.
Как они будут нулевыми, если я их забираю сразу после преобразования?
Мне кажется дело в сопоставлении массива counter7s[] цифрам что-то не так.
+++
Ну или пойти по другому пути, оставить одно прерывание АЦП, добавить две глобальные переменные, которые инкрементируются каждый цикл while(1), допустим:
freq7seg_update - будет отвечать за частоту обновления дисплея.
freq_adc_update - соответственно АЦП.
... при достижении нужного кол-ва - делаем то что надо, сбрасываем счёт.
Ну или к одной переменной можно привязать.
+++
Вот, ещё раз, так = все нули:
// +++
int main() {
    adc_init();
    spi_init();
    timer1000hz_init();
    set_sleep_mode(SLEEP_MODE_ADC);
    sleep_enable();
    set_output(DDRC, PC5);
    sei();


    while(1) {

        if (clk1000hz==0) {
output_low(PORTC, PC5);
            clk1000hz=255;
            ADCSRA |= (1<<ADSC);                      // запуск преобразования ADC
            sleep_cpu();                              // и засыпаем, проснёмся в прерывании, после того, как результат ADC будет готов
            sum[adc_channel] += ADC;
            if (++counter[adc_channel] > 128 - 1) { 
                adc_oversampled[adc_channel] = sum[adc_channel] >> 4;
                sum[adc_channel] = 0;
                counter[adc_channel] = 0;
            }
            if (adc_channel==0) V = (adc_oversampled[1] + (V << 2) - V) >> 2;
            else                A = (adc_oversampled[0] + (A << 2) - A) >> 2;
            adc_channel = (adc_channel+1) % 2;        // следующий канал
            adc_select_channel(adc_channel+6);


// ---
        if (adc_channel==0) {
            bin_bcd(V/2); // закидываем прочитанное значение АЦП в функцию bin_bcd, результат отправляется в структуру bcd
            counter7s[3] = bcd.thousands;
            counter7s[2] = bcd.hundreds;
            counter7s[1] = bcd.tens;
            counter7s[0] = bcd.units;
        } else {
            bin_bcd(A);
            counter7s[7] = bcd.thousands;
            counter7s[6] = bcd.hundreds;
            counter7s[5] = bcd.tens;
            counter7s[4] = bcd.units;
        } 
// ---



output_high(PORTC, PC5);
        }  //// if (clk1000hz==0)

    } //// while(1)
} //// int main()
А вот так всё работает:
    while(1) {

        if (clk1000hz==0) {
output_low(PORTC, PC5);
            clk1000hz=255;
            ADCSRA |= (1<<ADSC);                      // запуск преобразования ADC
            sleep_cpu();                              // и засыпаем, проснёмся в прерывании, после того, как результат ADC будет готов
            sum[adc_channel] += ADC;
            if (++counter[adc_channel] > 128 - 1) { 
                adc_oversampled[adc_channel] = sum[adc_channel] >> 4;
                sum[adc_channel] = 0;
                counter[adc_channel] = 0;
            }
            if (adc_channel==0) V = (adc_oversampled[1] + (V << 2) - V) >> 2;
            else                A = (adc_oversampled[0] + (A << 2) - A) >> 2;
            adc_channel = (adc_channel+1) % 2;        // следующий канал
            adc_select_channel(adc_channel+6);
output_high(PORTC, PC5);
        }  //// if (clk1000hz==0)

// ---
        if (adc_channel==0) {
            bin_bcd(V/2); // закидываем прочитанное значение АЦП в функцию bin_bcd, результат отправляется в структуру bcd
            counter7s[3] = bcd.thousands;
            counter7s[2] = bcd.hundreds;
            counter7s[1] = bcd.tens;
            counter7s[0] = bcd.units;
        } else {
            bin_bcd(A);
            counter7s[7] = bcd.thousands;
            counter7s[6] = bcd.hundreds;
            counter7s[5] = bcd.tens;
            counter7s[4] = bcd.units;
        } 
// ---



    } //// while(1)
} //// int main()
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 25 Окт., 2020, 01:20
Нельзя использовать ADC Noise Reduction асинхронно с таймером обновления экрана. Потому что таймер во время Noise Reduction останавливается. Если это игнорировать, будет неодинаковая засветка разрядов.  Именно поэтому ведущим объявляется таймер обновления сегментов, а запуск ADC встраивается внутрь его циклов, благо время позволяет с большим запасом. Если так не делать, то либо отказаться от Noise Reduction, либо от динамической индикации средствами процессора.
И просмотри внимательно точки с запятыми и скобки в if'ах. Там мне как раз в место подсчёта неоднозначность какая-то рисуется. Лучше лишнюю скобку поставить, чем гадать, что выполнится, а что нет...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 25 Окт., 2020, 01:25
Цитата: Slabovik от 25 Окт., 2020, 01:20благо время позволяет с большим запасом.
Ну как же хватает? Я пробовал выполнять оверсэмплинг с частотой 1 кГц, а после обновлять сегменты, - не хватает.
Ну или я что-то делаю не так.
Пойду думать... :)
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 25 Окт., 2020, 01:32
Ну, вполне хватает. Частота тактирования ADC 125 кГц, если делитель не врёт. Получение результата замера с ADC занимает 13 тактов - это 105 микросекунд. Посчитать фуфер - ну ещё сотня микросекунд (твой осциллограф это прекрасно подстверждает). Выгрузить в SPI - ну пусть 50 микросекунд. А таймер тикает каждые 1000 микросекунд. Где же не хватает? Хватает и с солидным запасом.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 25 Окт., 2020, 20:55
Тут вот какая штука вылилась - начал переписывать заново.
Переписал только для сегментов.
Вот такой код вышел.
#include <avr/io.h>
#include <avr/interrupt.h>
typedef unsigned char byte;

void spi_init(void);
void spi_send_bytes(byte S1, byte S2);
void timer0_init(void);

uint8_t const place7array[] = {
0b11111110,
0b11111101,
0b11111011,
0b11110111,
0b11101111,
0b11011111,
0b10111111,
0b01111111
};

// ABCEDFGH
uint8_t const segmt7array[] = {
0b00111111, 
0b00000110, 
0b01011011, 
0b01001111, 
0b01100110, 
0b01101101, 
0b01111101, 
0b00000111, 
0b01111111, 
0b01101111, 
0b10000000, 
0b00000000 };

byte clk1000hz = 0;
byte count7 = 0;
// +++
int main() {
    spi_init();    //
    timer0_init();  // 
    sei();          // разрешить прерывания

    while(1) {
        if (clk1000hz==0) {
            clk1000hz==255;

            spi_send_bytes ( place7array[count7], segmt7array[0]);
            count7++; if (count7 > 9) count7 = 0;

        } //+ if (clk1000hz == 0)
            

    } //+ while(1)

} //+ int main()
// +++

void spi_init(void) {
    DDRB  |=  (1<<PB2)|(1<<PB3)|(1<<PB5);  // RDY, Data, Clock //Set control pins as outputs
    PORTB &= ~(1<<PB2)|(1<<PB3)|(1<<PB5);  // RDY, Data, Clock //Set control pins low
    SPCR  =  ((1<<SPE)|(1<<MSTR));          // включим шину, объявим ведущим //Start SPI as Master
    PORTB &= ~(1<<PB2);                    // Pull LATCH low (Important: this is necessary to start the SPI transfer!)
}

void spi_send_bytes(byte S1, byte S2) {
    PORTB &= ~(1<<PB2);        // опускаем строб RDY
    SPDR = S1;                  // отсылаем байт
    while(!(SPSR & (1<<SPIF))); //
    SPDR = S2;                  // отсылаем второй
    while(!(SPSR & (1<<SPIF))); //
    PORTB |=  (1<<PB2);        // поднимаем строб RDY, защёлкивая данные на выход 595
}

void timer0_init(void) { // TIMER 0 for interrupt frequency 1000 Hz:
    TCCR0A = 0; // set entire TCCR0A register to 0
    TCCR0B = 0; // same for TCCR0B
    TCNT0  = 0; // initialize counter value to 0
    OCR0A = 249; // = 16000000 / (64 * 1000) - 1 (must be <256)          // set compare match register for 1000 Hz increments
    TCCR0B |= (1 << WGM01);  // turn on CTC mode
    TCCR0B |= (0 << CS02) | (1 << CS01) | (1 << CS00);      // Set CS02, CS01 and CS00 bits for 64 prescaler
    TIMSK0 |= (1 << OCIE0A);    // enable timer compare interrupt
}

ISR(TIMER0_COMPA_vect) { //interrupt commands for TIMER 0 here
    clk1000hz = 0;
}
По идее на всех индикаторах должны быть нули.
Заливаю = ноль только на первом!
Крутил и так и так, короче вывел он меня, бросил, потом подошёл
typedef unsigned char byte;
и помянуя о "ты не поверишь! (lss)"поменял на
typedef volatile unsigned char byte;
и оно заработало = нули на всех, ну ладно, дай уберу volatile = убрал, и оно продолжает работать как надо, ходя заливал по нескольку раз....
Ну я в Makefile смотрю
Optimization level, can be [0, 1, 2, 3, s]. 0 turns off optimization.
У меня стоит s....  :'(
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 25 Окт., 2020, 21:45
volatile - это способ отвадить компилятор от попыток "оптимизировать" переменную. Если volatile нет, то переменная может просто исчезнуть в недрах оптимизации, когда компилятор посчитает, что с ней никто и не работает.
Вот, по-быстрому ликбезик нашёл: http://microsin.net/programming/avr/how-to-use-volatile-in-c.html

Кстати, c "if" возможно надо было попробовать не действие "~" (neg), а условие "!" (no). Есть подозрение, что действие в качестве условия не прокатило. Хотя х.з. на самом деле.

зы: ещё раз упомяну, что я не знаток Си и для меня эти сишные выкрутасы самому в диковинку.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 25 Окт., 2020, 22:00
Ну вот нюансы Си я сам только познаю.
Хотя я хорошо знаю, что, например при установке Gentoo в Makefile  (https://wiki.gentoo.org/wiki/GCC_optimization/ru)с оптимизацией gcc шутить не надо, те обычно это -O2, а я влепил -Os.
Цитата: undefined-Os: На этом уровне код будет оптимизирован по объему. Он активирует все параметры -O2, которые не приводят к увеличению размера генерируемого кода. Он может быть полезным на компьютерах, которые обладают чрезвычайно ограниченным пространством жесткого диска и/или процессоры с небольшим размером кэша.
Макет жирный такой! :)
ы. У меня тут вопрос как накатить маску на байт цифры для точки?
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 25 Окт., 2020, 22:18
Там не маску надо, а за-OR'ить символ точки с символом знакоместа, в котором точка нужна (ну или через &, если отображение инверсное, т.е. когда "0" в порту - это "горит").

Вычисляешь BCD, сопоставляешь этому BCD 7-сегментный код, пока без точки, кладёшь его в буфер вывода (из которого SPI будет брать байты для выдвигания). К позиции буфера, в котором нужна точка, добавляешь операцию типа

dig_for_out[n] = dig_for_out[n] | symbol_DP

где symbol_DP - это код точки для семисегментника (у тебя оно вроде 11-я позиция по
списку, после девятки). Названия вымышлены с целью адаптации для понимания :)

зы: макетка китайская, супервонючая. Уже получил за созданную вонь...

Процик засуну в дип-корпусе, чисто ради удобства паяния проводами. Ну, а схема приближена к тому, что я рисовал (у тебя по сути аналогично, только 595-х вроде две). Только добавил два индикатра-столбика, вывод в них сделаю 5-м символом. Есть мыслишка по поводу "мельтешащих цифр"...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 25 Окт., 2020, 22:40
Ага, спс, сделал вот так, точки работают.
    if ((count7 == 1 ) || (count7 == 4)) 
         SPDR = S2 |= 0b10000000; 
    else SPDR = S2;                  // отсылаем второй, дорисовав точки где надо
Да, на этом макете у меня две 595, есть второй с тремя, но я уж сделаю попозже как на схеме.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 26 Окт., 2020, 19:39
А сколько значений достаточно для оверсэмплинга?
Вот я попробовал 16 суммировать:
            if (++counter[adc_channel] > 16 - 1) {
                adc_oversampled[adc_channel] = sum[adc_channel] >> 1;
                sum[adc_channel] = 0;
                counter[adc_channel] = 0;
            }
Тогда, при условии выполнения опроса 1 кГц, скорость обновления показаний достаточная.
Если 32 - ещё более менее, а уже 64 и 128 надо кидать либо в основной цикл программы либо ещё один таймер задействовать.
Залил код с 16-тью сэмплами и сглаживанием, понаблюдаю как будет себя вести.
ы. И вопрос к линейности, не претендую на точность, но если настраиваю на 20 вольт, то при уменьшении до менее 1 вольта расхождение в один-два знака, например 20 точно, 0.23 у меня, 0,24 на мультиметре.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 26 Окт., 2020, 20:39
Минимально-достаточно 16, подстраховаться - 32.
При 16 сдвигать вправо нужно на 2 (>>2), при 32 - на 3.
Не забыть добавить (забыто) перед сдвигом 1/2 (половину) от количества просуммированных отсчётов (т.е. 8 для 16 или 16 для 32)
             if (++counter[adc_channel] > BufLength - 1) {
                adc_oversampled[adc_channel] = (sum[adc_channel] + (BufLength >> 1) ) >> 2;
                sum[adc_channel] = 0;
                counter[adc_channel] = 0;
            }
BufLength определить в самом начале как глобальную установочную константу типа unsigned char (т.е. один беззнаковый байт). Можно две - на каждый канал свою. Это потому что в общем случае глубина буфера у каналов не обязана быть одинаковой.

В результате adc_oversampled содержит величину, которую нужно "скормить" Bin-2-BCD. Уже без всяких делений - это готовый результат измерений.

adc_oversampled и sum - двубайтовые величины (не помню, вроде unsigned short int - проверь сам). Двубайтовая обрабатывается быстрее, чем просто int (он четырёхбайтовый - проверь тоже, вдруг нет). Двубайтовыми они могут быть до глубины буфера включая 64 (проверка: 03FFh * 64dec = 0FFC0h, 0FFFFh-0FFC0h=3Fh (3Fh=63dec)
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 26 Окт., 2020, 21:00
Цитата: Slabovik от 26 Окт., 2020, 20:39При 16 сдвигать вправо нужно на 2 (>>2), при 32 - на 3.
Это, если не делить на два. А так да при 16 сдвигаю на 2 получаю сразу результат в напряжении нужный, это уже понял :)
Цитата: Slabovik от 26 Окт., 2020, 20:39Не забыть добавить (забыто) перед сдвигом 1/2 (половину) от количества просуммированных отсчётов (т.е. 8 для 16 или 16 для 32)
Сорри, эту фразу не могу распознать. ::)
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 26 Окт., 2020, 21:32
1/2 нужна для правильного усреднения результата. Потому что всё, что меньше 0,5 - это 0. Все, что 0,5 и больше - это 1.
Если 0,5 не добавлять, то нулём будет даже 0,9. Раздирали здесь (https://anklab.ru/forum/index.php?msg=286)

Делить на два при подсчёте BCD - совершенно не нужная операция, размазывающая процедуру сдвига вправо по нескольким местам. Зачем? Подсчитать нужно всё в одном месте.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 26 Окт., 2020, 22:12
Те:
if (++counter[adc_channel] > 16 - 1) {
                adc_oversampled[adc_channel] = (sum[adc_channel] + 8) >> 3;
Ноль тогда не ноль, а 0,02.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 26 Окт., 2020, 22:18
Это уже вопрос к ОУ. Или реализации схемотехники. Можно попробовать проверить, закоротив ногу нужного входа ADC на землю (там по схеме можно прямо закоротить, вреда не будет).
Возможно это то, чего я и опасаюсь - несколько милливольт на выходе ОУ, которые удалить можно только переводом ОУ на собственное отдельное питание с минусовым напряжением хотя бы на 0,2 вольта ниже, чем Gnd...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 26 Окт., 2020, 22:27
Так это я сразу сделал = 0,02 при АЦП на землю.
Если включить при замкнутом АЦП (или входа пробовал и так и так) вниз, сначала нули, при первом проходе вверх, вниз уже ноля нет.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 26 Окт., 2020, 22:28
А что такое "проход вверх"?
Но при старте 0 есть. Значит всё считает правильно. Можно попробовать отключить дизер, посмотреть, что изменится. Проконтролировать милливольтметром, осциллографом, что там на ноге АЦП.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 26 Окт., 2020, 22:41
Цитата: Slabovik от 26 Окт., 2020, 22:28А что такое "проход вверх"?
Поднятие напряжения.
Дизер отключал, - не влияет.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 26 Окт., 2020, 23:01
Поднятие напряжения - чем? Резистор - одно, ОУ - другое, закоротка - третье. Я же точно сказал, что именно сделать, чтобы можно было думать дальше...
Вангую, что при закоротке входа АЦП (физически, проволочкой на ногу Gnd), показания будут стабильно 0. Если я прав, то всё дело в аналоговой части - она не может обеспечить 0 на своём выходе.
Проверяется это также милливольтметром (мультиметром) либо осциллографом (это хуже т.к. у цифровых чувствительность мала).
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 26 Окт., 2020, 23:09
Дело не в аналоговой.
Коротил и то и то, ведёт себя одинаково.
ы. На самом деле меня уже такая работа вольтметра уже устраивает.
Сейчас, когда отключал дизер заметил, что работает хуже, то-ли плавать то-ли дёргаться показания начали.
С током сейчас осталось разобраться, как лучше считать, у меня шунт 0,05 делитель 15k/1k.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 26 Окт., 2020, 23:21
Тогда я не знаю. При нуле на АЦП и верной программе там должен быть чёткий 0. Нуля нет.
Хотя... ноль-то есть, при старте. Не хочешь ли ты сказать, что при старте формула по-другому считает?  :o
Надо проверять всё. Гадать невозможно. Всё и досконально. Сколько милливольт на входе АЦП при выкрученной в "0" ручке? Чтобы показывало 0,02, должно быть не менее 6 (шести) милливольт. Они получаются легко и непринуждённо при применённой схемотехнике и ОУ, у которых только напряжение смещения может достигать четырёх милливольт.

p.s. А это ты ЗАЧЕМ ОСТАВИЛ?  :o  :-\
if (adc_channel==0) V = (https://anklab.ru/forum/index.php?msg=409[1] + (V << 2) - V) >> 2; // сглаживаем V
            else                A = (adc_oversampled[0] + (A << 2) - A) >> 2; // сглаживаем A
Зачем таскать всюду код, который не понятно как работает? Уж столько раз говорил про это, но ведь нет - это ВСЁ там, и эта формула, и совершенно неуместное деление на двойку в BCD  :'(

Сглаживание УЖЕ есть, без вот это й херни... Вот эта фигня (а занимается эта фигня тем, что даёт якобы "плавное приближение цифр") и даёт этот неустранимый остаток, плюсом таща за собой затормаживание с выдачей верных показаний.

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

Убрать это нафиг и таки попытаться сделать так, как говорил, ну хотя бы здесь (https://anklab.ru/forum/index.php?msg=409)
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 27 Окт., 2020, 01:57
Без сглаживания у меня последняя цифра дребезжит с вероятностью 50/50, попадешь - стоит, нет болтается например 8-9, или 4-5.
Хотя бы если сдвиги делаю на 1 - то уже воспринимается лучше.
Ну и 0,02 никуда не девается, если я убираю эту "хрень".
Завтра уже осциллографом гляну.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 27 Окт., 2020, 12:18
Ну тогда убери эту 8. Мистика какая-то. Потому что не может 8, поделённая на 16 в бинарном виде стать двойкой. Также не могут быть одновременно все озвученные наблюдения.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 27 Окт., 2020, 12:22
Без дизера.
На ноге АЦП МК, земля на Agnd - DS1Z_QuickPrint8.png
На первой ноге 6002 (out), земля щупа на 4-ой ноге  - DS1Z_QuickPrint9.png
V+ закорочен на Out-.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 27 Окт., 2020, 12:28
Ну как мистика-то - я же не придумываю, абсолютно не зачем... что вижу то есть.
Подожду как макет соберёшь.  ;)
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 27 Окт., 2020, 13:04
Нифига ж себе колбасит-то там... По частоте - тактовая. Размах огромен. Да, надо дождаться макета...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 27 Окт., 2020, 20:54
Тут я наверное зря соединил все земляные пины 3, 5, 21 под корпусом меги. (Хотя в элементе кикада эти выводы вместе).
Если пин 1 вверху.
3, 5 надо соединить и вывести слева, рядом повесив 0,1 мКф.
Пин 21 справа, он рядом с Agnd и Aref, его вправо, тоже на ёмкость. И потом объеденив их соединить внизу (или вверху) с Gnd.
Приблизительно изобразил... переделать надо.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 27 Окт., 2020, 21:07
Тут важно, чтобы точки минусового питания ОУ были  выведены вот на толстую площадку GND внизу, а точка, куда подключаются резисторы на входах и выходах этих ОУ была выведена под процик, оптимально к C2 при таком расположении как сейчас или к C1, если C1 и C2 под проциком не соединяются (Gnd от C1 идёт тоже на толстую Gnd, не проходя через C2)

А я забыл, какой процик у тебя? Или пофиг на совместимость кода, сам напишешь? Я только свой показывать буду...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 27 Окт., 2020, 22:16
Было бы интересно залить твой hex конечно, но и сам могу.
У меня atmega328p U-TH, вот что про них нашёл (https://hamster.in.ua/viewtopic.php?f=40&t=236).
Про ногу ОУ - понадеялся на полигоны земляные, они и вверху и внизу почти сплошной.
Меня больше интересует 21-ая нага gnd, она рядом с aref, и по идее под корпусом соединять не надо, а с двух сторон, а ботом на полигон..
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 27 Окт., 2020, 22:45
У меня из DIP (а на макетке DIP, для QFP надо плату делать, а мне неохота) есть Mega8A и Mega48-20PU. У них у обоих код отличается от 328-й (и друг от друга тоже, у 48-88-168-328 ресурсы хотя бы одинаковые, а у 8А ресурсы на других адресах). Но в принципе, мне ничто не мешает сходить в чип-дип да прикупить 328-е. Но они тоже будут DIP. Это значит, что 6 и 7 каналов АЦП у них нет (на кристалле есть, а на ногах нет). Я присоединю какие-нибудь другие.
А вот по поводу аппаратного SPI ещё не решил. Можно задействовать, можно не задействовать - просто другие ноги порта.

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

зы: на ALI можно брать только их, китайские контроллеры. В остальном подделок дофига. Я пару раз обжёглся на PIC'ах, более не экспериментирую - то было 100% попадание на перепиленый хлам...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 27 Окт., 2020, 23:03
У меня в DIP китайские, а тот, который сейчас atmega328p U-TH куплен в "нормальном" магазине, не с али, поставщик вроде  промэлектроника.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 28 Окт., 2020, 13:55
Не, я не к тому, что надо обязательно в DIP или QFN, надо просто чтобы тип совпадал. Если не совпадает, значит, просто перекомпилировать надо будет с другими установками, готовый бинарник не подойдёт.

Возьму сегодня 328-е...

Взял
Mega328P.jpg

зы: а не посадить ли тебя на asm?
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 28 Окт., 2020, 21:11
Не знаю на счёт асма, не готов пока. Мне бы C получше изучить.
Сегодня психанул - переделал плату, только вытравил.
На установку поставил переходники на камеру (пятак 0,8 теперь 75% кадра занимает), теперь совмещение сторон вроде лучше.
Хотя пока только первый раз, посмотрю как с повторяемостью.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 28 Окт., 2020, 22:37
Цитата: Slabovik от 28 Окт., 2020, 13:55готовый бинарник не подойдёт.
Ну откомпилировать наверное и сам смогу, и поправить где надо.
ы. С AT90USB162 не имел дела?
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 28 Окт., 2020, 22:56
Не, я больше со старьём... с USB надо конкретно отдельно разбираться - там протокол обмена замороченный... А так само ядро такое же, как в Меге.

зы: в качестве совета: на макете предусматривай возможность переподсоединения линий на другие ноги портов. Часто пригождается, когда вдруг по макету готовишь релиз, а разводка не идёт и удобнее переподсоединить оборудование куда-нибудь в другое место. Позволяет сделать это на макете, не дожидаясь платы, и поправить-отладить программу.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 30 Окт., 2020, 23:13
Сегодня целый день (благо спокойствие на работе позволяло) упражнялся с Цэ. Нет, ребяты, это не моё. Хочется иметь предсказуемый и прогнозируемый результат, но однако с Цэ это не так. Не спорю, часто решающим является стиль написания, но всё-таки многие моменты совершенно не ясны, например, почему он из-за незначительной перестановки порядка обработки переменных корёжит получающийся бинарник до неузнаваемости. Непонятно...

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

Вариант первый, вполне логичный для пишущего на Си.

Короткий-в-Цэ---длинный-в-байтах.png

Вариант второй, вроде бы нелогичный: заводим локальную переменную и "гоняем" значения туда-сюда.

Длинный-в-Цэ---короткий-в-байтах.png

Однако смотрите, что происходит.
Короткая на Си запись приводит к гораздо более затратному и бессмысленному с точки зрения кода "гонянию" память-регистр и обратно.

Собственно, к чему это я? К тому, что не нужно уповать на глобальность переменных и работать только с ними. Передача их значений для обработки в локальные переменные функций часто приводит к улучшению получающегося кода. На asm'е это логично и естественно: грузим данные в регистры - крутим - выгружаем. На Си совершенно не очевидно. И гоняться за наиболее компактной записью выражения часто бессмысленно и даже вредно.

зы: а ещё я расстроен тем, что не могу добиться правильного возврата из прерываний с установкой флага "Interrupts Enable". Дело в том, что код SEI необходимо давать непосредственно перед кодом RETI, что в Си мне сделать не удаётся - там на входе и на выходе "push-pop'ная" безусловная заглушка, имеющаяся даже при абсолютно пустой процедуре обработки прерывания, из-за чего SEI вставляется перед гуртом POP'ов, задолго до RETI, что в принципе неправильно и при "лавине" запросов на прерывания приведёт к переполнению стека.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 30 Окт., 2020, 23:32
Ну я не знаток Цэ. Но тут (https://habr.com/ru/post/309796/).
(поискав чуть-чуть) Про это есть:
Цитата: undefined...нужно искать возможность ввести временную локальную переменную, в которую производить запись, и только через какое-то время произвести запись из этой переменной в память....
Тут бы сесть и разобраться, кстати это указателей тоже касается.
А то статью эту по-диагонали просмотрел... :) :o
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 03 Нояб., 2020, 15:28
Просто оставлю ссылку здесь. Полезная инфа про стандартные библиотеки GNU AVR Lib C
https://www.nongnu.org/avr-libc/user-manual/index.html
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 04 Нояб., 2020, 22:15
В общем, в последние дни был занят тем, что возюкался с программой на Си для макета. Сегодня оно было дописано до вменяемо-рабочего состояния (полировка конечно отсутствует как класс - лишь бы все процедуры на месте были).

Из приятного: оно работает. И дизер работает тоже. Причём работает как надо. При выключенном дизере (просто микросхемку вынимаю) показания дискретны с шагом 4 (предел 4096, либо ступенька 6 с пределом 6144). С дизером я могу поставить любое, так что все четыре знака (десятичных) в деле.

Из неприятного. Ты оказался прав, когда говорил, что у тебя всё прыгает на прерываниях. Суть в том, что код на Си весьма медленный и банально не успевает. Мне, переползшего с ассемблера, на такое непонятно как смотреть, потому что, как я уже говорил, за один тик я успеваю окучить 8 (восемь!) каналов и ещё время остаётся на "покурить". На Си у меня даже один не проходит без того, чтобы не занизить скорость отображения дисплея. Если поставить номинальную скорость, начинаются пропуски отсчётов. А поскольку не успевший закончить работу ADC возвращает нули (потому что прибегает "чужое" прерывание от таймера), то и начинается расколбас показаний. Что делать я пока не представляю. Попробую погонять программу на выяснение, какие процедуры сколько времени отнимают.
Варианты лечения. Первый - перейти на статическое отображение и тупо гонять программу по кругу без всяких таймеров, останавливаясь только на ожидание показания от АЦП. В этом случае уже ничто не будет мешать, а с какой скоростью будет идти измерения - с такой и ладно.
Второй - попробовать написать то же самое, но на асме, посмотреть, велик ли выигрыш. Займёт время.

Недоделка-на-макетке-заработала.jpg

зы: я конечно тактовую взял не 16 и даже не 14, а 12 МГц, но это из расчёта поставить частоту АЦП 187 кГц - немного выше, чем это получается при 16 МГц. Хотя, при 16 МГц можно было бы 250 кГц выставить на тиктирование АЦП и, думаю, ему хуже бы от этого не стало. Но аппноты говорят, что для точности надо ориентироваться на ~150 кГц плюс-минус...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 05 Нояб., 2020, 01:12
Воот, только я не понял, мы же, когда запускаем преобразование ADC разве проснуться можем в прерывании таймера? Вроде нет.
Макет получился что надо, выглядит брутально!
Я пробовал также примерно делать, - не, не хочу так больше, трудоёмко.
У меня плату получается сделать быстрее.
ы. Пробовал и пластиковые макетки штыревые - мат стоял в 33 этажа...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 05 Нояб., 2020, 11:57
Я поковырялся сегодня и нахожусь немного в тупоре (именно так, без буквы 'c').
Осциллограф мне показал, что таймер исправно тикает, но вот... сведения на индикатор (по SPI) идут в 1000 раз чаще, чем нужно по таймеру. Возможно, опрос ADC вместе с ними (это предстоит выяснить). Ну т.е. почти непрерывно, но... чётко зависят от настроек таймера (который тикает в 1000 раз реже!).  Явно ошибка в логике кода, но... пока её не вижу - тормоза стоят где надо...

А со штыревыми макетками, которые BreadBord, довольно весело. Но мне их вечно не хватает т.к. они не предполагают плотный монтаж. Плюс тянется проблема контактов - частенько он то есть, то где-то его нет.
На фото макетка 15x18 см, но и то я ещё не приступал к аналоговому узлу. С другой стороны, на ней сделана куча ошибок в расположении, из-за чего хочется переделать, но уже плотнее. Благо, выводно-проводной монтаж это позволяет.

Когда мы запускаем преобразование ADC в режиме Noise Reduction, мы не ожидаем никаких прерываний кроме прерывания от ADC. Таймер и сама программа специально настроены так, чтобы в это время ничего "лишнего" не прилетало. Это раз. Контрольный здесь - это сам механизм. В режиме Noise Reduction приостанавливается тактирование всех (!) таймеров, кроме RTC (если он есть) и возможна работа таймера N2, но только если тактовая на него поступает извне. У нас ни одного из этих "кроме" нет. Поэтому когда мы останавливаемся на ожидание ADC, то первым придёт именно прерывание от ADC. Ну типа это как проснулся утром по таймеру (солнышку), и до следующего тика (восхода солнышка), успеваешь приготовить завтрак да сходить отлить (вывести данные в индикатор), поработать (опросить ADC), книжку почитать (посчитать результаты), да поужинав пойти спать до следующего восхода.

ps. Разобрался, в чём проблема. Она одинаковая в наших случаях. Заключается в том, что ADC не останавливается по окончании преобразования. Либо (предположение №2), снова запускается, как, только режим переключается (регистр SMCR) из ADC Noise Reduction. В итоге, когда случается Idle для ожидания таймера, весело отрабатывает прерывание от ADC, выводя проц из режима ожидания в нормальную работу.

Выход из положения по-красивому пока не придумал как сделать, но просто отключение ADC на время, когда он не используется, работает. Из минусов такого подхода - это то, что на преобразование тратится не 13, а все 26 тактов (на частоте работы ADC), что при моих настройках (12 МГц, коэф.деления 64) даёт 140 микросекунд на преобразование. Но в принципе, это не является какой-то проблемой (такое долгое время) - в запасе остаётся ещё как минимум 700 мкс (именно столько проц находится в режиме Idle в ожидании прерывания от таймера).
↓ спойлер ↓
ADC-капризничает.png
[свернуть]
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 06 Нояб., 2020, 21:36
Ссылка на образовательную статью, где есть формула, по которой "немец" считал сглаживание.
Use Software Filters To Reduce ADC Noise (https://www.electronicdesign.com/technologies/analog/article/21778422/use-software-filters-to-reduce-adc-noise)
↓ спойлер ↓
вообще-то я хотел, чтобы ты сам попробовал - та формула на бумажке за пару проходов понимается, ну а в статье она "A Filter with Feedback"
[свернуть]
И вот ещё по следам, но по-русски http://we.easyelectronics.ru/Theory/chestno-prostoy-cifrovoy-filtr.html

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

И такие накопительные ячейки кольцевого буфера здорово сокращают потребую память под сам буфер. В 200 байт легко уложиться.

Не совсем пойму, нужна ли схема моего макета. Она немного отличается от той, что я показывал (ну хотя бы кузов другой), соответственно, программа привязана к схеме, хотя всё возможно переделать.

Всё-таки сделал я 4 канала измерений исходя из двух вариантов логики.
Первый: два канала измеряют традиционное напряжение на выходе блока. Они выводятся на индикатор по умолчанию.
Вторые два канала измеряют опорные напряжения для ограничителей тока и задатчика напряжения и по-умолчанию они не показываются на индикаторе. Но индикатор переключается на их показ (каждый индивидуально) если только опорное напряжение начинает изменяться (человек крутит ручку). Как только напряжение перестаёт изменяться, происходит переключение (через некоторое время) на показ выходного напряжения.
Таким образом, применяя индикатор можно не задействовать (т.е. не подключать) входы этих каналов, показания по ним будут нулевые, и автопереключения на них не будет происходить и пользователь вообще может не знать о их существовании.

Второй вариант - дополнительные каналы используются традиционно для автоматического переключения диапазона измерения.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 07 Нояб., 2020, 01:46
Ну тогда я не понял, почему ты против цифровой фильтрации?
ы. Ноль есть на макете (я про 0,02)?
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 07 Нояб., 2020, 09:48
Когда я был против?  :o
Я против применения без понимания, и тем более против применения в качестве "розовых очков"  :) Ну это когда последующими обработками пытаешься буквально скрыть косяки в работе (аппаратуры или программы) на предыдущих стадиях. Касаемо этого фильтра - скрыть иногда неверно прочитанные отсчёты АЦП.

Ноль безпроблемный, но масштабирующие ОУ аналоговой части пока ещё не распаяны. Что будет с ОУ - посмотрим в ближайшие дни.

зы: думаю, что добавлю конфигурационные перемычки для того, чтобы можно было выбирать режим работы не прибегая к всяким "конфигурационным зажатиям кнопок".
Кнопки можно две - принудительное гашение младшего разряда  у каждого индикатора в том случае, если имеющих значение знаков становится четыре
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 07 Нояб., 2020, 13:42
Тут же как получается, - разбираю примеры и на этом учусь, но некоторые вещи пропускаю, в надежде на разобраться потом, всё-равно сразу всего не охватить, а возвращаться приходиться, вот и повод на ещё один подход...
За ссылку на изиэелектроникс спасибо, из любопытсва надо бы попробовать ещё и с БИХ фильтром, что там упоминается.

+++
Ноль именно с таким же как у меня способом подсчёта?
sum[adc_channel] += ADC;
if (++counter[1] > 16 - 1) {
        adc_oversampled[1] = (sum[1] + X) >> 2;
        sum[1] = 0;
        counter[1] = 0;
}
Потому, что у меня даже при замыкании каналов АЦП при X=8 ноля не было.
Новую плату правда ещё не собрал. Надо ещё в первый мой макет попробовать залить эту прошивку.
ы. Мне тут белые индикаторы подоспели на TM1637 подоспели.
Не пойму как их лучше задействовать, софтовый i2c или железный? Применять тут их наверное не буду, к stm уже.
ыы. Запустил их - яркие сильно, даже на яркости установленной в 0 отлично. Фильтр сверху им нужен из какого нибудь платика.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 07 Нояб., 2020, 14:38
Гм, я же показал формулу расчёта немного выше (https://anklab.ru/forum/index.php?msg=455). Она в самом низу на картинке. Но буфер у меня устроен по кольцевому принципу. Это значит, что предыдущие отсчёты вплоть до (-n) учитываются автоматически. В формуле "немца" примерно похоже, толко кольца нет. Принцип работы той формулы - новое значение имеет вес только на 25%, остальные 75% - вес старых значений. Это и хорошо и плохо. Хорошо тем, что приближение происходит по экспоненте. В первый проход - 25% новой инфы. Второй проход - 44 процента новой инфы, третий проход - 62 процента новой инфы и т.д. Плохо тем, что 100% новой инфы не бывает никогда и время установления рассчитывается, когда "остаток" старой инфы не будет приводить к изменению самого младшего разряда. А это довольно долго.

Кольцевой буфер "выкидывает" всегда чётко ровное количество старой инфы, поэтому приближение у него линейное, а не по экспоненте. Возвращаясь к тому же примеру, в первый проход 25 новой инфы, второй проход - 50%, третий 75, четвёртый - вся инфа новая.

25% я взял ради примера, поскольку у "немца" было столько.

Индикатор можно применять любой, только процедуру вывода для него написать соответствующую. I2C программный или аппаратный - в данном применении значения не имеет т.к. для использования преимуществ аппаратного нужно придумать, где он может работать параллельно основному ходу программы.

TM - микросхема удобная тем, что там всё внутри есть. Туда "плюнул и забыл". Но I2C подразумевает передачу не только "голой даты", но и предварительных управляющих команд. А максимальная частота Clock у ней ограничивается 500 кГц (типовая для I2C считается 400 кГц). Соответственно, вывод будет длиннее по времени. Вывод: как всегда, смотрим по месту и выбираем, что больше нравится :) Лично мне не трудно на пригоршне ТМ2, приправленных  ЛА3 подобное сколхозить :) А сюда я её вставлять не буду - пусть традиционные 595-е остаются.

зы: поправлю схему в соответствии со своим макетом - поделюсь и схемой и исходником. Надо-всё-таки решать, куда дальше двигаться.
зызы: Выяснил, что REF198 надо хоть немножко нагружать. Сочетание "без нагрузки" (вход AREF нагрузкой не является) и керамический конденсатор на выходе иногда приводят к генерации (после касания рукой, пинцетом и т.п.). Добавление буквально 47 кОм (ток 9 мкА) избавляют от этого свойства.
Генерацию цифровым осциллографом не видно (был как-то спор, мне упирались, мол цифровым всё можно посмотреть). Выявил, когда смотрел аналоговым, по утолщению линии. Частота - несколько МГц, вид - спадающая пила, размах - несколько милливольт. Проявлялось как некоторое увеличение показаний АЦП, буквально на несколько отсчётов в пределах величины дизера, после касания пинцетом или рукой вывода выхода REF198.
Вот такая вот закавыка. Кстати, в даташите указывается на наличие электролитического алюминиевого конденсатора на выходе - похоже, это связано с этим явлением.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 07 Нояб., 2020, 23:12
Ну вот, а разговор был именно про мой вариант оверсэмпла, ну да ладно.
Модуль именно с TM пришлось купить, так как с белыми индикаторами и дешевле чем отдельно индикаторы покупать.
Один модуль с дисплеем 0,36 дюйма и TM = 86 руб. Так что микросхема в довесок :)
Не охота конечно распаивать, но была мысль...
Двигаться надо в сторону stm, хорошо бы у нас одинаковые камни были бы. У STM32F0 отличия свои есть, и я уже не раз напоролся на них. Макросы из библиотеки cmsis тоже разные, хотя похожие. Хотя ты сейчас сразу по даташиту начнёшь с asm и регистрами... тогда я тут пока пас...  :)
REF198 у меня пока так и нет.
↓ спойлер ↓
Сейчас на столе лежит трансформатор 2 обмотки по 28 вольт переменки на 3 ампера 1.5 каждая. Те наверное будут в параллель.
Думал ПиДБ очередной прикрутить, но спрошу у тебя вариант простой и более-менее готовый может есть?
[свернуть]
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 07 Нояб., 2020, 23:33
Ну я же сразу говорил, что я всегда делаю по-своему :) Вариант "немца" я и не думал вставлять т.к. изначально окучивал кольцевой буфер. Мне нравится как он работает. Был бы вариант с малым ОЗУ (как в PIC - там 64 байта ОЗУ типично, а 128 - роскошно), то тогда да.

В сторону STM - да, но надо ли бросать недоделанное? Как я понимаю, тут вопросов ещё куча. Я тут Си подтягиваю (весь экран уже исплёван...) в надежде получить от него некий изящный код... Надо бы довести, чтобы оно работало да попробовать пару фичек, чтобы знать, надо ли оно. А потом и платформу можно поменять. Честно говоря, для измерителя быстродействия меги много-много излишне, не говоря уже про STM. А у STM я действительно хочу asm пощупать, плюс у него ОЗУ большое и памяти программ ещё больше. Так и тянет на что-нибудь графическое.

ПиДБ я в железе не собирал т.к. ужаснулся от того, как он сделан. В симуляторе он показывает ужас, некоторые версии (из последних) вообще не должны работать (и похоже, так оно и есть потому что куча жалоб с симптомами точно как в симуляторе). Из хорошего могу кивнуть в сторону польского блока на трёх ОУ, но с парочкой доработок (на коте, кстати, я показывал, можно здесь завести темку и повторить) и ограничением в 25-26 вольт на выходе.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 08 Нояб., 2020, 00:08
Заведешь тогда тему по польскому варианту?
Кстати я его повторял раз .... эм не помню ну 4 точно. Раздал все. И почему-то мне там опора не нравилась, не помню уже.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 08 Нояб., 2020, 18:48
Целый день сегодня провёл за макетом. Думал, дел наворочу, а нет... практически совсем ничего, ну то есть абсолютно. Выяснял различные способы подключения масташбирующих усилителей, зависимости и т.п. Понял, что абсолютно универсального получить не выйдет, поэтому придётся прнять какое-нибудь, но волевое решение: либо так, либо эдак.

Положу пока схемы непосредственно макета. Естественно, он никак не может считаться чем-то законченным. Позиционные номера деталей тоже как попало (совсем не слежу за этим в таком процессе).
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 09 Нояб., 2020, 12:31
Так скоро точно будет танк, ну зачем ещё один МУ для опоры...
↓ спойлер ↓
Подобие корпуса для БП из фанеры делаю из фанеры пока, надоело бескорпусное безобразие...
[свернуть]
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 09 Нояб., 2020, 12:38
Потому что у простых лабораторников есть извечные проблемы: а) невозможно установить ток ограничения, не сделав короткое замыкание на выходе; б) невозможно понять, какое напряжение установлено на выходе, если блок находится в режиме ограничения тока.

Измерение опорников решает эти вопросы без необходимости устраивать к.з. или обесточивать нагрузку. Всего-то задействовать два лишних входа...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 09 Нояб., 2020, 12:40
Не понял логики, опорное мы и так знаем.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 09 Нояб., 2020, 12:52
Нет, не знаем. Опорное измерителя - это не то опорное, которое хотим знать :) Это то опорное, относительно которого измеряем. А в лабораторнике напряжение и ток задают его собственные опорные. Если измерить их, будем иметь ответы на два пункта выше без необходимости рвать-коротить выходные цепи.

зы: в индикаторе у меня слайдеры (столбики из 10 светодиодов) для опытов. Думаю попробовать на них показывать, в какую сторону происходит движение измеряемой величины. Иногда за мельтешением цифр это сложно понять.

Мой недоделанный код хоть откомпилился?  Там в нём логические косяки есть, ну и недоделан конечно. Гашение незначащих нулей, расстановка запятых - это то, что сразу бросается в глаза.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 09 Нояб., 2020, 13:30
Ну тогда требую пояснений что такое RefC+ Ref- RefV+, где располагается ОУ МУ, так сказать огласите весь список! :) :) :)
Да, код собирается нормально, 1606 байт говорит, но залить пока не могу.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 09 Нояб., 2020, 14:04
Во, это уже нормальный подход. Пояснять - моё любимое :) Потому что это хороший способ находить ошибки.
Это "внешние ссылки" к опорнику (задатчику) внутри блока, который измеряем. Точно такие же, как идут к выходу того же блока. Ну т.е. все масштабирующие усилители входами смотрят наружу. Ты помнишь мою навороченную схему от платки? Там даже разъём предусмотрен под это дело.

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

Там с расчётом усилителей от опорников блока не совсем тривиально т.к. нужно учитывать больше данных. Во-первых, это опорник измерителя и максимальное значение измерителя. Во-вторых, это максимальное значение на выходе самого измеряемого блока и напряжение опорника, ему соответствующее. Но на деле не так страшно, можно табличку накидать для подбора номиналов...

У меня ещё октрытым остался вопрос о частоте среза мастабирующих усилителей. Полагаю, её нужно выбирать достаточно низкой, однако чтобы можно было обойтись керамическими конденсаторами - они до 10 мкФ легкодоставаемы с распая...

Интересно, но у меня код собирается более коротко 1434 байта. Atmel Studio, настройки "по умолчанию".
Вначале собирал прямыми переменными, но передавать в процедуры ссылкаи оказалось и компактнее и быстрее.

А, да... я уже говорил, что кварц у меня сейчас 12 МГц, на схеме указан 14,318 МГц, у тебя 16 МГц, так что возможно надо будет константы для таймеров/периферии посчитать. И там в PortD кое-что плюётся (найдёшь), может конфликтовать с UART...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 09 Нояб., 2020, 18:42
На той схеме, которая у меня (1902) Ref/Set-V/Set-I только, они?
По размеру - это у меня -O2 стоит, сейчас попробовал -Os, получилось 1442 байта.
ы. Аааа всё дошло - ты хочешь в обоих режимах работы БП знать какое напряжение/ток заданы независимо от того что на выходе.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 09 Нояб., 2020, 19:27
Да, именно.
До сих пор этот вопрос, как мне известно, никто не решал таким способом. По крайней мере, я не видел таких конструкций. Те блоки, к которых эту возможность можно было наблюдать, работали немного по-другому: у них цифровое управление и задающие напряжения выставлялись при помощи ЦАП с соответствующей индикацией. Примерно так же, как в радиоприёмнике на микросхеме из соседней темы: дал в ЦАП код, его же и показал на экранчике. Но хочется решить это для "аналоговых" блоков.

зы: я за день поискал свои старые материалы по тому блоку, скоро что-нибудь нарисую.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 09 Нояб., 2020, 23:01
Ну это же не самоцель сделать так, как никто не делал. :)
Вообще конечно интересен вариант задатчика напряжений ЦАПом, потом их измерение и корректировка на выходе, но не на этом этапе.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 10 Нояб., 2020, 08:17
Разве это плохая цель? Ведь повторять чужое 1:1 уныло и грустно, ведь вся суть именно в придумывании...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 10 Нояб., 2020, 21:34
Опять - таки повторять что? И повторять как? А упираться и делать лишь бы было впервые...
Есть много схем которые хотел бы повторить, несмотря на то, что известны давно.
Вот например лежат TDA7040T и КР174ХА34... их бы в утиль уже, а интересно было бы.
↓ спойлер ↓
оффтоп:
Тут схему с кнопкой изобразил. Может будут какие рекомендации?
[свернуть]

Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 19 Нояб., 2020, 00:58
В общем помучал модуль tm1637, - это что-то, процитирую:
Цитата: undefinedПоследовательный интерфейс, разработанный для этого контроллера, является творческой переработкой широко известного "квадратного" I2C. Отличия состоят в других названиях линий, обратном порядке следования битов, и в отсутствии адреса устройства.
Хотя я его завёл на stm, ну как всегда творческая интерпретация взятого из сети материала. :)
Из неприятного - виснет i2c, не пойму почему, наводки/ёмкость... подключен напрямую к ногам мк.
Питание должно быть таким же как и у контроллера, ну или уровни выравнять, если питать от 5-ти вольт и управлять с контроллера 3.3 вольта - не работает.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 19 Нояб., 2020, 10:01
а) согласование уровней обязательно (см. "конструкцию" входа той же STM и представь, что получается, когда туда +5 с чьего-то выхода придёт)
б) скорость. TM не особо тороплива, 500 кГц для неё предел, выдерживать же нужно типовые 400 кГц.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 19 Нояб., 2020, 13:31
В рабочем варианте i2c у меня 100kHz.
DS1Z_QuickPrint2.png - хардварный i2c stm32f030.
DS1Z_QuickPrint3.png - софт вариант, но пока связи с tm1637 нет, где-то у меня не получается.
Но фронты дрыганьем ног получаются лучше.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 19 Нояб., 2020, 14:20
В хардверном варианте однозначно велик резистор подтяжки - нужно уменьшать. Ещё и вопрос, что там навешано у модуля, в типовой схеме я конденсаторы 100 пФ наблюдал, что до 400 кГц и 10 кОм подтяжки более-менее нормально, а что здесь?
p.s. Забыл вставить ссылку. Исправляю недоразумение: https://radioprog.ru/post/198
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 19 Нояб., 2020, 15:55
Обе картинки модуль напрямую к пинам stm.
На модуле два резистора по 10k к питанию.
При прзвонке сопротивление +5 и DIO = 5k, +5 и CLK = 10k.
Схема типовая, прикрепил, только без клавиатуры.
Вообще не нравятся они мне в итоге, главное адреса нет, значит на одну линию их никак.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 19 Нояб., 2020, 16:47
А ёмкости? Можно просто попробовать их убрать и посмотреть, как изменится хардверная осциллограмма.
Также интересно, как соотносятся фронты импульсов, а на такой мелкой не видно. Видно только, что там не 100 кГц, а раза в полтора больше (это не проблема, конечно).
10к и 100 пФ (+15 пФ на входе самой микросхемы) дают постоянную времени немного больше 1 мкс, что даже для 400 кГц вполне нормально (из этого типовые номиналы и указаны на схеме). Но если там что-то "лишнее" на проводах, оно может рождать сюрпризы. Потому внимание каждой мелочи...

Безадресная - это конечно косяк, так, микросхемка на "побаловаться". Уж тогда интереснее MAX7219 заюзать...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 19 Нояб., 2020, 17:10
Но тут же при одинаковых равных осциллограммы, те просто перезаливаю прошивку, в одном случае хардверный i2c, и не снимаю проводов - софтверный, не трогая настройки осциллографа.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 19 Нояб., 2020, 17:26
А на софтверном ты выходы в режим "открытый коллектор" ставишь?
С другой стороны, открытый коллектор для "чистого выхода", когда на нём точно не будет никакого "чужого" сигнала (в общем случае у I2C 'это не так), вполне допустимо не иметь, а иметь самый обычный двутактный выход. Что у софтверного, похоже, и есть.

Также нужно смотреть соотношение во времени фронтов сигналов. Если где-то не хватает (дата выставляется поздновато и т.п.), то вполне возможно неправильное восприятие сигналов микросхемой.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 19 Нояб., 2020, 17:34
Ну да, они родимые - 10 нан вместо 100 пик. Удивительно что работало.
Вот сейчас стало, вообще без ёмкостей.
ы. Тут уже два раза плату вольамперметра "чинил", оба раза smd конденсаторы превращались в ~100 Ом.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 19 Нояб., 2020, 18:03
Совсем другое дело.
Там, похоже, общий провод высокого сопротивления ещё маленько портит дело, но вроде к ложным не должно приводить (во время смены одного сигнала есть провал и на втором, хорошо видно в момент времени "начало передачи").

зы: во время 'Ack' TM "сажает" линию Data на землю, так что двутактный вывод на эту линию применять нельзя - обязательно должна быть типа "общий коллектор".
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 19 Нояб., 2020, 18:08
Теперь думаю какой контроллер сегментов ещё поискать для ОА, почему так - белые - общий анод, жёлтые и не дорогие тоже анод.
Цитата: Slabovik от 19 Нояб., 2020, 17:26А на софтверном ты выходы в режим "открытый коллектор" ставишь?
У stm режим Open-drain, вот в него и ставлю.
Цитата: Slabovik от 19 Нояб., 2020, 16:47Видно только, что там не 100 кГц, а раза в полтора больше (это не проблема, конечно).
Вот, хорошо видно: 98 кГц.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 19 Нояб., 2020, 19:13
Ну, при таком раскладе получается, там не было (см. предыдущие фото) open drain. Ну ладно, сейчас понятнее стало.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 02 Дек., 2020, 12:06
Ещё одно применение - зарядка, точнее "добивка" аккумулятора.
Добавил в разрыв плюса на входе реле навесом, и сделал "качели" до 16.2 вольт для Ca/Ca аккумулятора.
Правда нарвался на грабли, начал уже функции и дополнительные переменные вводить, задержки итд, короче бился пару часов....
Оказалось выключал пин неправильно те так
PORTC |=  (0 << PC0);

ну, как так, основы же, элементарная вещь, делал это не счесть сколько раз, все блинки на этом....!!! :o  :'(
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 02 Дек., 2020, 12:12
Да, блин, ошибка элементарная - чисто внимательность. Ну, для того и отладчики есть. Порой поражаешься, "как же такой можно было ляпнуть", но... бывает  ;)

А вот по поводу "добивки" до таких высоких напряжений на аккумуляторе хочу предостеречь, ежели хочешь долгой и счастливой жизни аккумулятору.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 02 Дек., 2020, 12:33
Воды долил, плотность никак не подниму... Зарядил до 14.8, плотность меньше 1,2.
Аккуму пол-года 75-ка Аком (https://akom.su/sites/promo/index.php/akom-modelnyj-ryad/67-akom-75).
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 02 Дек., 2020, 12:57
Неприятно, но надо "бодяжить" (правильно - "откорректировать") до нормальной плотности. Зарядкой только сульфатацию пластин сделаешь. Сразу не заметно, но следом пойдёт относительно быстрая деградация (уж такой дорогостоящий опыт проделывал несколько раз, и всё на своём кошельке).

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

зы: типовой малообслуживаемый аккумулятор. Может работать годами без вмешательства, и только при длительном простое (в отключенном состоянии 4-6- месяцев, если на машине с сигналкой - раз в месяц т.к. сигналка потребляет) подзаряжать (именно под-) до 13,8-14.0 вольт. А вот добиваться 100% зарядки - imho фетиш, ни к чему хорошему не приводящий...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 02 Дек., 2020, 13:35
Это уже глубоко сидит - не доливать электролит...
Ночь простоял на ~100 мА токе.
Если точнее с утра качели 13.5 ... 15.8 током 1А
Измерил сейчас плотность - всё не так плохо мы в синей зоне зоне, плотность поднялась.
Может и обойдётся.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 04 Дек., 2020, 12:32
Нагородил для кнопки и реле включенного в разрыв плюсового входа вольтамперметра такой код:
#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 градусов...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 04 Дек., 2020, 18:35
Пытался понять твою функцию 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 лучше...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 04 Дек., 2020, 20:04
Вот такое упрощение правильно?
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;
}
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 05 Дек., 2020, 11:12
Видео (https://youtu.be/AUVATr8fZIA) работы с "добивкой".
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 05 Дек., 2020, 13:12
Цитата: zenon от 04 Дек., 2020, 20:04Вот такое упрощение правильно?
В плане получения суммы - годится. В плане того, что idx определена как локальная переменная в процедуре - нет, т.к. она должна быть глобальной.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 05 Дек., 2020, 23:41
Сделал кольцевым буфером, - да ты прав даже без сглаживания почти без болтанки (буфер на 64).
Только памяти он отъедает хорошо.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 06 Дек., 2020, 00:16
Чтобы сэкономить память, часть результатов можно "складывать" в небольшие суммочки, оставляя структуру буфера кольцевым. Собственно, в моём примере так и сделано - суммируется по сколько-то в одну ячейку (ибо результат от АЦП десятибитный и мы можем спокойно в одну двубайтовую ячейку суммировать до 64 раз). Это даёт резкую экономию расходуемой памяти вкупе с тем посылом, что всё-равно нет необходимости пересчитывать суммы после каждого измерения (ибо это нужно для вывода на индикатор, а вывод на индикатор нет нужды делать быстрее 10 Гц, разумно 4~8 Гц).
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 06 Дек., 2020, 15:27
MC14489 - две микросхемы на весь (2x4 цифры) индикатор и никаких забот с динамическим обновлением
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 06 Дек., 2020, 22:27
Меня больше терзает вопрос АЦП, либо попытаться на хорошей опоре питание vdda на stm32f0 сделать.
Или сразу подумать о выборе микросхемы.
ADS1115 дешёвый, ADS1220, вот такой AD7705BRZ - тоже вроде ничего...
Надо определиться и попробовать.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 09 Дек., 2020, 18:54
Так, на счёт АЦП советовать не хочешь ничего... понятно.
Вот сделал плату для двух реле и вентиляторного шим`а.
Будут замечания? :)
Петля vcc какая-то длинная... ;0
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 09 Дек., 2020, 19:29
Как я могу что-то советовать, когда я не понимаю, какая цель преследуется? Тем более, что ты же сам пенял, что лучше делать, как другие делают и не выдумывать...  :-\

MCP320x (https://ww1.microchip.com/downloads/en/DeviceDoc/21034F.pdf) - дёшево, сердито и двенадцатибитно. Работать с ними тоже просто, питание годится как 3.3, так и 5.0

А эти ADS... они же 24-битные. Куда такое? Это как микроскопом гвозди без соответствующей обвязки и конструкции, которые тоже в копеечку влетят...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 09 Дек., 2020, 22:23
Не... непонимание какое-то просквозило... забыли. По факту всё равно - те конструкции которые собирал 1 в один мало что было..., узлы какие-то - да.
Коннектор J5 - вентилятор, pwm.
Реле одно - в разрыв плюса перед входом.
Реле второе - замыкание выхода, там указан порт, но повешу просто на кнопку.
Эти ADS1115 - недорогие, рублей по 100-150.
Про ADS1220 - да загнул.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 09 Дек., 2020, 23:10
Посмотрел и эти... в наших краях нету, пощупать не могу. 16-битные, недорогие. Можно, если есть, но опять же, надо знать, зачем оно столько. 16 бит - это вес младшего разряда полторы тысячных процента. Всё тоже должно соответствовать, или 16 разрядов тут же превратятся в те же 12, хорошо если 13. 10 разрядов при опорном 4.096 дают 4 мВ на младший разряд. 12 разрядов - один милливольт. 14 разрядов - 0.25 милливольта. 16 разрядов - 64 микровольта. Для измерения напряжений блока питания 12 разрядов вполне достаточно, остальное всё-равно избыточно. Для какой-нибудь точной измриловки - это интереснее, но там и аналоговую часть надо вылизывать, и детали прецизионные ставить. ОУ со смещением два-три милливольта и дрейфом 50 мкВ на градус испортят эту ложку мёда бочкой своего...
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 09 Дек., 2020, 23:46
Крутил - вертел по всякому, опять вернулся к простому усреднению с оверсэмплом.
Ну скачет с буфером и всё тут, пока аккумулятор висел, понятно - всё супер.
Обновление значений сделал редкое, как-раз где-то 8-12 Гц.
Этот вариант оставлю - пусть работает такая точность меня устраивает. Видео (https://youtu.be/fE0cK-Uy6FU).
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: Slabovik от 10 Дек., 2020, 02:20
Нужно убедиться (при помощи "закладок" в порты, про них рассказывал ранее), что АЦП работает синхронно с производством выборок из него. На Си и у меня такая проблема и встала, пока я не стал стопить АЦП, в то время как на асме почему-то не было вопроса. Производство чтения из регистров АЦП в то время, как он ещё не закончил обсчёт, приводит к неверным показаниям и прыганию общих результатов.
Название: Re: Вольтамперметр, 7 сегментные индикаторы, SPI
Отправлено: zenon от 10 Дек., 2020, 21:57
оффтоп: мне определённо нравятся TPS5430/5450 - не капризные совсем, припаял - работает.
Если помнишь, ещё на коте мучился с L5973 - попили крови...  >:(