Жизнь делится на два этапа – сначала нет ума, потом здоровья...

Меню навигации для мобильных

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

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

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

zenon

Привет.
Начал эту тему где-то после НГ, хотелось самому детально разобраться, что к чему, в общем медленно, по несколько подходов, но кое-что получилось.
Сначала собрал индикаторы на двух 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) надо было по быстрому что-то собрать.
https://github.com/minamonra/

zenon

Ну и сам вольтамперметр.
Код:
// ====================================================================
#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 на будущее развёл, но не задействовал, хотел переключение обмоток сделать, и термозащиту.
https://github.com/minamonra/

Slabovik

#2
Занятно  :)  Зачётно! Сегодня вволю (на почве своего слабого знания Цэ) помедитировал, как оно работает. Получается, тут не скользящее окно используется для усреднения, а просто суммирование-деление? В принципе, нормально, т.к. скользящее окно много оперативки требует, а тут тинька... У меня пока другой вопрос, касаемо схемы. Там вот цифровая и аналоговая земли разъединены. Это... наверное схема не полная? Что-то я не нашёл, как они соотносятся.

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

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

зызы: на видео не понял, почему у показателя напряжения сотые всё время 0? В коде что-то не найду...
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

Хм, тут бился больше с пониманием 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 реализовать ветвлением, что наверняка будет красивее.
++
По поводу земель, я что-то уже запямятовал... но точно плата именно та, которая в кикадовском проекте, попробую по дорожкам завтра глянуть.
С сотыми напряжения тоже что-то я делал, сорри, начинаю забывать, с мая к этой теме не притрагивался...
https://github.com/minamonra/

Slabovik

А какой процик в реальности стоит?  oops... я посмотрел внимательнее, на схеме Mega8 (виноват, почему-то подумал, что тинька). На 8-меге килобайт озушки, вполне много, чтобы хранить отсчёты АЦП по отдельности. Впрочем, скользящее окно - это на любителя. У него одно преимущество - при резком изменении измеряемого значения, оно не покажет резкий скачок, а "плавно" поднимет показания. В остальном всё то же самое. Но памяти требует много (два байта на каждый отсчёт)

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

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

zenon

Да мега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Впрочем, скользящее окно - это на любителя
Вот тут не пойму, что имеется ввиду под скользящим окном, может разными терминами оперируем?
https://github.com/minamonra/

Slabovik

#6
"Скользящее окно" - это когда в памяти хранится 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 закинуть
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

У меня со сдвигами и двоичной логикой туго... но попытаюсь попробовать.
Про сотые в напряжении - их нет, скакали сильно, избавился их, да и не хватит значений на нормальные показания с сотыми долями.
Тут лаб-то 24 вольт (2х10.5 переменки), не полных, 2 ампера, это временный вариант, есть слаботочная обмотка для индикатора, но уже было поставил реле на переключение обмоток... тока не хватило, гаснут индикаторы при срабатывании. :)
+++
Тот ЛБП, про который упоминал выше (схема с вертепа) буду переделывать, корпус от китайского 1502, в который ~200 ватт трансформатор запихнул,на выходе около 40 вольт, две обмотки, три TO247 на радиаторе (радиатор маловат).
Твоя плата туда никак не лезет, поэтому пока думаю что туда поставить.
+++
А от массива данных с АЦП в этом коде отказались когда в тиньку запихивали код, тут аппаратно тоже сглаживание есть.
https://github.com/minamonra/

Slabovik

#8
Ну, с двоичной логикой достаточно просто
↓ спойлер ↓
блина, были бы предки дальновиднее, они бы двенадцатиричную систему счисления внедрили бы и пользовались, а то, блин, какая-то десятичная мерзость, пальцев, видите ли, на руках не хватало... :-\
[свернуть]
Фишка в том, чтобы не использовать сложные ресурсоёмкие вычисления, при расчётах ограничиться только сложением и делением на двойку в степени. Вот и получается, что делить можно только на два, на четыре, 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: а можно мне присоединиться?
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

#9
Конечно можно!
А то в одного я немного забросил эту тему.
Макет с индикаторами даже не собрал ещё, смотрю что у меня из индикаторов осталось...
Ну и было в планах на stm уже это повторять.
+++
Погрешность у АЦП 2 значения вверх и вниз, сам я оверсемплинг не пробовал в железе, можно.
Цитата: Slabovik от 01 Окт., 2020, 18:40И ещё неплохо бы во время измерения АЦП закидывать ядро в спячку
Там вроде был режим ADC Noise reduction mode.
Увидел в asm коде про спячку.  :o
https://github.com/minamonra/

zenon

Цитата: Slabovik от 01 Окт., 2020, 18:40Мне, правда, в твоей схеме не нравится способ подключения ОУ ко входу АЦП
Так схему мы уже обсуждали вроде, да конденсатор на пине забыл, но подключение ОУ осталось прежнее.
На всякий добавил старый вариант в протеусе.
https://github.com/minamonra/

Slabovik

#11
Да, но в кикаде и в протеусе схемы отличаются. В протеусе как надо сделано, а в кикаде и конденсатора нету, и верхний конец подстроечника (по схеме кикада) с движком не соединён. Со схемой в протеусе я согласен.

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

zenon

Упс, точно там же центральный пин подстроечника тоже соединён с пином ADC.
Только на рабочем варианте не буду менять, показания уплывут, только конденсаторы на пины повешу.
https://github.com/minamonra/

Slabovik

С7 и C11 тоже можно по 100 нан поставить. Потому что параллельно им резисторы в ~10 раз меньше, чем в канале тока. Частота среза будет в районе сотни герц.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

Понял, почему GND и GNDA не соединил на схеме, - kicad зальёт полигон полностью, и не даст соединить GNDA с GND в том месте на плате, где я захочу.
По поводу выжимки из АЦП атмеги всего, - то, что читал в интернетах успехов сильных не дало, включая noise ruduction и оверсэмплинг, те 4-х знаковые индикаторы наверняка будут излишними.
У меня пару MCP3204 лежат уже давно...
https://github.com/minamonra/

Slabovik

Я согласен по поводу шума, ведь там всего 10 разрядов, и младший болтает туда-сюда. Три разряда - это 1024 градации. 1024 - это считай три десятичных знака (с очень небольшой погрешностью). Поэтому есть полное основание оставлять светиться только три "весящих" разряда на индикаторе. Потому что для получения точности в 4 десятичных разряда АЦП нужен 13-разрядный (а фактически 14-разрядный, в бинарном понимании, естественно). 14-разрядные - это уже уровень "продвинутый", а вот 12-разрядные пробовать можно (4096 градаций уровня). Но к ним нужно ещё и ОУ качественные подтягивать, что уже может потянуть на какую-нибудь курсовую по конструированию для пары-тройки успевающих студентов :)

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

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

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

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

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

Чтобы соединить GND и GNDA нужно ввести (придумать) какой-нибудь фиктивный компонент, например, резистор с маленьким сопротивлением, и включить его между землями. Также я заметил, что когда заливаешь полигон, льёшь его на весь участок платы. Рекомендую всё-таки определять полигоны поменьше, располагая их только там, где надо. Это позволит избегать бесполезных "полуостровов", а также делить их для организации "звёзд" и т.п.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

Да, двухдиапазонный вариант тоже неплохо.
https://github.com/minamonra/

zenon

... Как дела?
Собрал "макет", 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++;
    }

}
https://github.com/minamonra/

zenon

#18
Переписал ещё немного.
Опрос АЦП отправил на своё прерывание, сейчас опрашивается 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
// ====================================================================
https://github.com/minamonra/

Slabovik

В выходные был занят на огороде и неплохо отморозился (в прямом смысле - температурка на улице упала вместе с первым снегом, дак, думаю, отходняк ещё пару дней займёт). Зато остатки яблок снял - уже вкусные стали :) Так что, касаемо процессоров и прочего, ничего и не делал  :( В планах первым пунктом стоит завести твою схему в 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), либо... либо это вся овчинка вообще выделки не стоит.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

Сам на дачу в выходные не поехал, сын только съездил за яблоками ;) - но всё не увезти, яблони хорошие, девать особо не куда, раздаем в основном, ну и немного в гараж отвезу. Дело в том, что дача на острове (https://gotonature.ru/1733-ostrov-sarpinskij.html), и на машине тужа можно, но это паром и крюк довольно большой. Добираемся речным транспортом.
Ну и у нас теплее намного :)

АЦП у меня просто на делителях напряжения весит, ОУ ещё не прикрутил.
В основном кодом занимался, вроде неплохо получилось, только не знаю надо ли было в своё прерывание АЦП кидать, для индикатора и в прерывании по таймеру ему неплохо жилось.
А зачем перекидывать в DT? Хотя если уже привык...
ы. На работающем БП ОУ MCP6002 - ноль отличный.
https://github.com/minamonra/

Slabovik

#21
DT - это дань привычке. Я привык к его "крыжикам" (которые более-менее копируют крыжики P-CAD/Altium), а у KiCAD они совсем другие - ломка возникает (как у пилотов: "штурвал или сайдстик" :) ) А поскольку перерисовать всё-равно (мне) придётся, то сохранять прежний инструмент не принципиально. А там и KiCAD'освкую версию можно параллельно вести (глядя друг на друга).

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

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

зы: а можно радикально - восемь регистров и хардверная динамическая индикация. Или вообще статика, но это и индикаторы нужны другие. Наверное, это трогать не стоит - 4x индикаторы легко купить только для динамической индикации, на том же Ali статических 4x и не нашёл даже...
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

#22
Да, катодные ключи - однозначно надо. У меня кучка 2N7002 и BC817 в SOT-23 есть, те мне больше нравится вариант с двумя регистрами и ключами. Статику я твою тоже помню, да симпатичная, но уж сильно большая получается, может потом как-нибудь.
Вообще почти всё в smd делать наверное буду, уже привык.
ы. Кварцы 3525 вот такие:
ыы. Надо подумать на счёт ref192, хотя для 10-бит под вопросом.
ыыы. На счет привычки - периодически открываю что-то в DT - не могу... после кикада меня он аж пугать начинает, за больше чем полгода настолько привык, и скорость довольно неплохая - хоткиями левой рукой m, r, e, g, x, w ... правой мышь, - супер! :)
https://github.com/minamonra/

Slabovik

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

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

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

zenon

#24
О как, с корабля на бал, про подмешивание шума немного читал, можно попробовать, но с пониманием у меня тоже вопрос. :)
Вот тут (https://chipenable.ru/index.php/programming-avr/142-avr121-oversampling-decimation.html).
https://github.com/minamonra/