26 Нояб., 2020, 06:11

Человек, признающий свою ошибку, когда он не прав - мудрец.
Человек, признающий свою ошибку, когда он прав - женат.


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

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

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

zenon

Цитата: Slabovik от 12 Окт., 2020, 12:29А то, почему MUX2 равен должен быть именно 2
Ну так я первым делом смотрел значения из таблиц и не пойму где тут MUX2=2? Ведь  только 0 или 1 может быть?
Вот себе выписывал:
//          7    6    5    4    3    2    1    0
//ADMUX = REFS1 REFS0 ADLAR  -    MUX3  MUX2  MUX1  MUX0
///////////////////////////////
//  3    2    1    0
// MUX3  MUX2  MUX1  MUX0
//  0    0    0    0      ADC0
//  0    0    0    1      ADC1
//  0    0    1    0      ADC2
//  0    0    1    1      ADC3
// ....
//  0    1    1    0      ADC6
//  0    1    1    1      ADC7
Размер платы пока не планировал, корпуса всё-равно ещё нет, но лицевая панель точно в планах больше чем 80х70, твоя плата довольно-таки большая, ну и трансформатор габаритный.

Slabovik

Ну, то, что он 2-й бит, это из определения следует, в железе так. Вот даже в табличке 3 2 1 0 самой первой строчкой, и в даташите регистр разрисован точно так же. Потому и два. А #define MUX2 = 2 - это в .h файле прописано.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

Так он 2-ой бит, а не значение MUX2. MUX2 же не может быть =2.

Slabovik

Ну дак он и не может каким-то произвольным быть значением. Он (MUX2) - номер бита, не более. Его значение - константа, и она равна 2. MUX2 (а также 0,1 и 3)  как раз олицетворяют понятие "адрес", а не сами данные, что по этому адресу лежат.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

Те тогда верно следующее:
#define adc_select_channel(channel) (ADMUX = (ADMUX & (~((1<<3) | (1<<2) | (1<<1) | (1<<0)))) | (channel))

Slabovik

Да, чисто логически верно.
Ещё удобнее будет написать
ADMUX = ADMUX & 0xF0 + channel

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

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

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

Ещё интереснее может быть так
ADMUX = ADMUX & 0xF0 + ((ADMUX+1) & 0x01)
но тут уже запутаться можно т.к. не зная железо не совсем понятно, почему в этом месте можно +1 :)
[свернуть]
А, да... вот та мысль, про которую говорил чуть ранее
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

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

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

Slabovik

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

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

zenon

По деталям, поехал взял три мк, конденсаторов насыпал, MCP6002T-I/SN заказал горсть, и пяток LM4040DIM3-4.1, приедут после 22-го.
C REF беда, ну может доеду как нибудь до чип-дипа, уж пару-тройку надо.

Slabovik

Интересно, что чуть ли не вся линейка (по объёму памяти) контроллеров имеет одну и ту же цену. Поэтому хочется всегда взять побольше - обыкновенное нормальное чувство. С другой стороны, кода ожидается от силы пара-тройка килобайт. Впрочем, я бы сам 328-х взял пачку и в ус не дул :) Дырочек бы только на плате сделал... для опытов :)

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

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

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

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

zenon

Ну 6002 в soic-8 у меня просто нет, и они не пропадут, да на AD поглядывал конечно, знаю там характеристики на пару порядков лучше.
Думаю не проблема если будет два шунта, сейчас-то так и есть, только один момент напрягает - земля на корпусе.
Про температурный дрейф - там в немецком коде предусмотрен замер температуры МК (ADC8) и калибровка, относительно этого.

Slabovik

Калибровка вынудит работать с Float-числами. Не хотелось бы... Не то, чтобы нельзя, но это очень ресурсоёмкая штука для данного процика.

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

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

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

zenon

В общем-то у меня тоже вундерфавли получаются, если помнишь БП с дежуркой, с отрицательным напряжением, обратноходом в качестве основного питания... Ошибкой было желание всё впихнуть на одну плату.
Тут хотел применить вот такое китайское чудо на 5 вольт (купил в основном из-за того, что самому боязно изготовить мелкий трансформатор, да и брать их где-то надо).
ы. Мы же не спешим, блох вроде не видно?  ;D

zenon

Схема на данный момент такая, две опоры предусмотрел. Подключение к индикаторам - разъём.

Slabovik

13 Окт., 2020, 17:58 #64 Последнее редактирование: 14 Окт., 2020, 12:28 от Slabovik
Т.е. плата индикатора отдельно? Или этажерка?
Для разных опор нужен пересчёт резисторов масштабирующих усилителей.
R25 не нужен. Sleep можно подключать напрямую. Или вообще не подключать (внутри есть подтяжка).
Про C1, C3 не помню, говорил ли - не нужны они при наличии C7, C8.
Vin для U5 может быть напрямую от 5V1, нет никакой необходимости его от AVCC питать.
С10, С14 выглядят огромными. Они такие надо? Я бы туда SMD-шных чуть (со всяких девайсов можно наснимать от 1 до 10 мкФ в формате 0805)...

Во, вспомнил (и добавил сюда, чтобы не писать лишний пост). Небольшой ликбез на тему "Как правильно спать на AVR MCU". Просто оставлю здесь, т.к. даташит на Мегу в этом месте написан каким-то не очень понятным языком, а здесь немного разжёвано по-русски. Главное в Си - не забыть подключить библиотечку "sleep.h"

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

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

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

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

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

zenon

Ээээ, сорри, а можно разрешение побольше или в DT схему.
Тут ни пинов ни значений не разглядеть. :)

Slabovik

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

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

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

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

zenon

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

  if (++oversample_counter[adc_channel] > 128 - 1) {
    adc_oversampled[adc_channel] = oversample_sum[adc_channel] >> 4;
    oversample_sum[adc_channel] = 0;
    oversample_counter[adc_channel] = 0;
  }
  // следующий канал
    adc_channel = (adc_channel+1) % 2;
    adc_select_channel(adc_channel);
}

Slabovik

Прежде чем коряжить ты меня проверь. Я сам мог накоряжить   :P   Щас выверять надо :) Номиналы я уже нашёл (завтра исправлю и пересчитаю) - там фигня по ним в ОУ напряжения какая-то нарисована. Но номиналы не суть. Суть - соединения. Любые вопросы...

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

Ага, с частотой понятно - оно не написано явно. Биты SPR0 SPR1 нулевыми остаются, значит, при 16 на кварце получается 4 МГц тактирование на SPI. Вот, почему так быстро.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

14 Окт., 2020, 00:45 #69 Последнее редактирование: 14 Окт., 2020, 21:08 от zenon
Ну я же тоже проверяю что и как, обвязка ОУ у меня осталась от подбора собранного варианта, её конечно надо пересчитать.
На кнопки согласен, лучше порой лишнее предусмотреть, чем потом навешивать, реле тоже ok. Просто именно этого размера у меня нет, наживное.
Вот у себя увидел, что генератор не правильно, полпитания на 5-ую ногу забыл.
ы. Что на счет процедуры оверсэмплинга?
Цитата: Slabovik от 13 Окт., 2020, 17:58Т.е. плата индикатора отдельно? Или этажерка?
Не знаю пока, сначала вроде как макет хотел собрать, а финиш уже потом.

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

Slabovik

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

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

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

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

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

ADC_work.png ADC_summ.png

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

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

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

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

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

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

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

zenon

Не пойму как проверить засыпает ли?
На основе этого примера добавил в свой код, как там показано.
Тыц.
main.c из примера:
/*
This example project demonstrates the usage of the AVR ADC noise reduction.

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

#define NOISE_REDUCTION 1

volatile uint8_t adc_buffer_count;
volatile uint16_t adc_buffer[100];

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

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

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

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

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

#ifndef F_CPU
#define F_CPU 16000000UL
#endif

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

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

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

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

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

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

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

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

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

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

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

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


Slabovik

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

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

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

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

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

зы: и ещё пересчитал и, надеюсь, правильно изобразил номиналы в измерительном усилителе. Расчёт от условия опоры 4,096 вольта.
12,5 - это 10 кОм плюс половинка от подстроечного 5 кОм. 14 кОм - это 12,5 кОм + 1.5 кОм на выходе ОУ. Банальный делитель, в общем....
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

zenon

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

      sum[adc_channel] = 0;
      counter[adc_channel] = 0;
    }
    adc_channel = (adc_channel+1) % 2;
    adc_select_channel(adc_channel);
}

zenon

15 Окт., 2020, 21:12 #74 Последнее редактирование: 15 Окт., 2020, 21:50 от zenon
Цитата: Slabovik от 15 Окт., 2020, 15:14Основа всего - таймер, формирующий "системный тик"
Ну у меня почти так изначально и было, всё выполнялось в одном прерывании, которое было привязано к таймеру 1000 Гц.
Тут от перемены мест ничего не меняется, ну почти...
Потом я добавил прерывание ADC_vect .... :)
Больше из интереса как это всё будет работать.
Посижу посоображаю как вернуть всё в спять...
Кстати забыл уже на каком этапе в spi появилась pgm_read_byte, это кажется ещё на коте...
ы. Последние дни как-то тоже всё кругом, сосредоточиться и спокойно посидеть - никак.