29 Окт., 2020, 23:48

Профессионализм -- это не столько способность выходить из сложных ситуаций, сколько умение в них не попадать...


Вольтамперметр, 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 на индикаторы и всё, как оно и задумывалось с этой частотой для динамической индикации.
Ну и опрос антидребезга кнопок туда же можно.

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

16 Окт., 2020, 14:20 #78 Последнее редактирование: 16 Окт., 2020, 17:12 от zenon
Всё-всё-всё уговорил...  ;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 в аттаче.Нет сообщений, связанных с этим вложением.

zenon

16 Окт., 2020, 15:50 #79 Последнее редактирование: 16 Окт., 2020, 16:45 от zenon
Эмммм, только теперь я не пойму как спать, если стоит SLEEP_MODE_ADC, и засыпаю после  ADCSRA |= (1<<ADSC) то не просыпается.
А когда sleep_cpu() была в прерывании ADC_vect и ADIE=1  то вроде всё работало (на основе примера от микрочипа).
Цитата: Slabovik от 16 Окт., 2020, 12:09Преимущество такого подхода - АЦП измеряет не абы когда, а "строго по часам" - те же 500 Гц (1000/2 канала).
Ну вот тут тогда вопрос - достаточно ли этих данных для оверсэмплинга?
Может есть смысл тактировать не по таймеру, а как-то реализовать по завершению преобразования АЦП в прерывании ADC_vect?

Slabovik

Вот этот момент вряд ли влияет, но вместе с ADSC (start conversion) ADIE (interrupt enable) надо бы проверять, потому что по ходу программы кто-то другой мог и опустить этот флаг.
Второе.
Вот что пример показывает в обработчике прерывания. Это просто пример - там он увеличивает счётчик на единицу.
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

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

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) оставил.

zenon

Смотрю на вот этот лот STM32F030K6 .... даже не знаю что сказать.. :)

Slabovik

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

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

zenon

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

Slabovik

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

zenon

19 Окт., 2020, 21:59 #87 Последнее редактирование: 20 Окт., 2020, 17:14 от zenon
Макет будет примерно такой. :)
Слева вверху uart, 3 светодиода и пару кнопок, ну и сверху пины, может i2c или ещё что нибудь.

Slabovik

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

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

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

zenon

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

Slabovik

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

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

zenon

Синие вырви глаз, ну их.
Вот жёлтые более-менее приемлимо по цене, только 0 заказов.
Белые по вот, но 20 штук, я их тоже не щупал.
Не плохо смотрятся оранжевые, на фото не очень, в живую лучше.
Чип и дип вообще всегда дорого, хотя последнее время появились позиции с приемлемыми ценами.
Заказал STM32F030K6T6, макетную делать будем? Тему заведём?

Slabovik

Будем. Programming Manual уже изучаю :)
Я только пока не врубаюсь, как к ней 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 ёмкости никакой нет.
Видео, снял плохо, бликует индикатор, сорри.
На диапазоне 0-14 вольт почти попадает 0,01, ближе к 30 расхождение на 0,05 примерно.
Не знаю как в плане точности UT61E... но вроде не плох.

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

Slabovik

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

zenon

23 Окт., 2020, 17:40 #97 Последнее редактирование: 23 Окт., 2020, 18:22 от zenon
Болтанка. Чуть изменил напряжение на входе - перестала.
Тока в цепи его измерения нет, те должны быть 0000 (зелёный индикатор).
Tre и монтаж добавил, волоски на плате - это от кисточки.

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++;
    }
}