Если тебе плюют в спину, значит ты впереди!
Конфуций

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

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

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

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

Slabovik

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

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

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

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

zenon

Цитата: Slabovik от 15 Окт., 2020, 23:36Прямо перед Sleep можно поставить какой-нибудь бит порта в 1, а первой командой в прерывании вернуть его в 0.
Про дрыгание ногами до и после событий я понял. Факт засыпания где увидеть? ... На кварце будет прекращение генерации?
+++
Про спусковой крючок тут, в моём варианте немного не так.
Если уйду от второго прерывания, то чтение ADC выныривает в цикл программы, никаких спусковых крючков тут и не надо.
В прерывании 1000 Гц оставлю посылку SPI на индикаторы и всё, как оно и задумывалось с этой частотой для динамической индикации.
Ну и опрос антидребезга кнопок туда же можно.
https://github.com/minamonra/

Slabovik

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

zenon

#78
Всё-всё-всё уговорил...  ;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 в аттаче.Нет сообщений, связанных с этим вложением.
https://github.com/minamonra/

zenon

#79
Эмммм, только теперь я не пойму как спать, если стоит 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?
https://github.com/minamonra/

Slabovik

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

zenon

О, эврика, прерывание АЦП использовать только для пробудки из сна-же...
Спс. Натолкнул на мысль.
https://github.com/minamonra/

zenon

Рабочий вариант.
#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) оставил.
https://github.com/minamonra/

zenon

Смотрю на вот этот лот (https://aliexpi.com/nUmQ) STM32F030K6 .... даже не знаю что сказать.. :)
https://github.com/minamonra/

Slabovik

Гм...
Цитата: undefinedПрислал пустышки - чипы звонятся насковозь по ногам питания, ни один из 5 не работает
;)

ЦитатаADMUX = (ADMUX & 0xF0) + (channel++ & 0x01) работает только один канал.
Возможно, что я что-то намудрил с ++, но на деле если без этой маски работает - так и оставить. Потому что здесь оно не сильно-то и надо, просто лишняя операция.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

Да, этот лот лотерея на пустышки, но всё-равно 5 штук живых там есть где-то за рублей 300-400.
Не вижу у тебя R12 и R15 - не нужны?
Развожу потихоньку, вот так пока.
https://github.com/minamonra/

Slabovik

R12 есть (у меня он вроде R32 на последней схеме, но нумерация  ещё съедет - к бабке не ходи), а R15, поскольку в усилителе от токового датчика нет отрицательного сигнала (и резистор ОС и вход "+" измеряют точно относительно Gng), можно не ставить. При этом при подсчёте коэффициента усиления, он (коэффициент) будет получаться на единичку больше, чем просто отношение резисторов в ОС.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

#87
Макет будет примерно такой. :)
Слева вверху uart, 3 светодиода и пару кнопок, ну и сверху пины, может i2c или ещё что нибудь.
https://github.com/minamonra/

Slabovik

#88
Быстр на руку :)  А я всё в раздумьях :-\ Не могу отделаться от того, что для получения прерываний с частотой один килогерц, глубины таймеров 0 или 2 не хватает. Остаётся только таймер 1, который 16-битный. Прерывать приходится по совпадению регистра сравнения.

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

зы: всё написанное выше - не правда. Потому что я туплю, с какого-то посчитав тик с предделителя прерыванием с самого таймера. А это неправильно. С 8-битным таймером можно получить частоту прерываний вплоть до ~61 Гц...
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

:)
Разглядываю какие индикаторы есть/заказать.
Хочется чего-то эдакого... размер, не знаю 0,5 (https://aliexpi.com/atg5) или может 0.8 (https://aliexpi.com/sFfs).
Желтых и белых как-то мало, белые в основном на плате с модулем (https://aliexpi.com/6Y9g) i2c.
Вот жёлтый (https://aliexpi.com/5l65) из четырёх знакомест, но ОА.
https://github.com/minamonra/

Slabovik

14мм (0,56") по-моему самое то. А чисто визуально лучше всего читается красный. Зелёным как правило нужен ток побольше. С синими проблема - глаз на синем плохо фокусируется, хотя они яркие. Можно сочетать: красный (жёлтый) - напряжение, синий - ток. Вот белые я не пробовал.

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

zenon

Синие вырви глаз, ну их.
Вот жёлтые (https://aliexpi.com/j8cJ) более-менее приемлимо по цене, только 0 заказов.
Белые по вот (https://aliexpi.com/dxTE), но 20 штук, я их тоже не щупал.
Не плохо смотрятся оранжевые (https://aliexpi.com/od2M), на фото не очень, в живую лучше.
Чип и дип вообще всегда дорого, хотя последнее время появились позиции с приемлемыми ценами.
Заказал STM32F030K6T6 (https://aliexpi.com/Qhap), макетную делать будем? Тему заведём?
https://github.com/minamonra/

Slabovik

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

zenon

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

Slabovik

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

zenon

А, у LM4040 Irmin = 45 μA, те 45 мкА, а я считал 45 мА... и поставил 47 Ом, то-то я смотрю, а там 4,086 вольт, сейчас поставил, 470 Ом - ровно 4,096 вольт стало, как и положено.
А про ёмкость я тоже не нашёл ничего, поэтому не поставил.
Ref198 у меня пока нет.
ОУ MCP стоят.
Осциллографом чуть позже гляну, покажу что там.
Прыгают бывают по два знакоместа последних на обеих индикаторах синхронно, не пойму из-за чего.
Сначала с землёй намудрил, ноля не было, потом переделал, ноль отличный.
https://github.com/minamonra/

Slabovik

Два знакоместа синхронно - это когда -9-0-9-0- младший разряд меняется. Это вынуждает меняться разряд, который старше.
Из-за шумов последний разряд всегда неточен в пределах одного отсчёта, как бы там ни было.
В алгоритме для получения 40.92 нужно просуммировать 32 отсчёта и поделить их на 8. При этом, если буфер суммирующий, показания будут обновляться с частотой примерно 15 Гц (если следовать алгоритму, что я ранее показывал, 1000 Гц/2 канала /32 выборки). Можно суммировать 64 выборки и делить на 16 - младший разряд болтать станет чуть меньше (возможно). Но болтанка ещё и от самого сигнала зависит. Поэтому постепенно осознаётся то, что быстрая смена показаний на дисплее не улучшает восприимчивость. Максимум - это порядка 8-10 раз в секунду. Ибо всё-равно в младшей цифре будет мешанина. Тут уж от обстоятельств думают, оставлять мешанину (при шуме в измеряемом), или что-то делать (уменьшать частоту вывода, изменять способ подачи показаний, гасить разряд или ещё что-то).
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

#97
Болтанка (https://youtu.be/5xdwYBwdfQM). Чуть изменил напряжение на входе - перестала.
Тока в цепи его измерения нет, те должны быть 0000 (зелёный индикатор).
Tre и монтаж добавил, волоски на плате - это от кисточки.
https://github.com/minamonra/

Slabovik

Треугольник хороший, на 30 Омах R16 R17 должно хватать для смещения.

Осталось либо рассказать, как делаешь подсчёт, либо сразу перейти к постройке алгоритма. Такая синхронность говорит о том, что где-то ошибка (в замерах, в вычислениях, в способе прочтения АЦП, да мало ли). Там на плате с земляным проводом как-то не очень (хотя бы потому, что R16, R17 и AGnd смотрят в три разные точки, а это только то, что я сходу увидел), но это как-то сильно влиять не должно.
Вывод в UART не надо бы использовать, он прерывания генерирует. Если FreeRunning оставил, то с длинной обработкой прерываний можно запросто пропустить снятие данных с АЦП и тоже будет ошибка...
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

Код почти такой же и остался, пока ничего не менял, от "немца" оверсэмплинг и сглаживание.
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++;
    }
}
https://github.com/minamonra/