Привет, Хабр! Габариты этой самоделки в модном корпусе из оргстекла на латунных стойках составляют всего 57x40x26 мм, и то, если учитывать выступающую ручку управления, рычажок микротумблера и разъём питания Micro USB.

Предусмотрена цифровая индикация частоты и амплитуды, а также генераторы стандартных сигналов — синусоиды и меандра. Получился неплохой карманный пробник, который выручит во многих ситуациях.

Набор для сборки такого осциллографа может стать отличным подарком гику, если он способен припаять на печатную плату микросхему в квадратном корпусе SOT389-2 (LQFP44) с шагом выводов 0.8 мм.



▍ Схема питания


Принципиальная схема устройства не содержит ничего особенного. Микроконтроллер питается пятью вольтами напрямую от разъёма Micro USB. Это возможно благодаря тому, что для питания ядра и периферии микроконтроллера предусмотрен встроенный стабилизатор напряжения.



Микроконтроллер может функционировать в диапазоне напряжений питания от 2 до 5.5 вольт, но 0.96-дюймовому OLED-экранчику требуется не менее 2.8 В. То есть возможно питание от трёх полуторавольтовых элементов АА или ААА, либо от литиевого аккумулятора.

Посадочное место с отверстиями для пайки проводов от батареи или аккумулятора предусмотрено, но просто соединено параллельно с разъёмом Micro USB. Иными словами, если подключить внешнее питание, на клеммы химического источника тока попадёт пять вольт. И это, мягко говоря, весьма нежелательно.

Для сравнения, на плате Arduino Uno R3 имеется узел на компараторе и P-канальном полевом транзисторе, отключающий линию +5 вольт разъёма USB-B при наличии внешнего питания на коаксиальном разъёме.



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

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

Это работает, только не следует забывать, что литиевый аккумулятор является источником повышенной опасности, в отношении которого верно следующее:

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

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

▍ Аналого-цифровое преобразование


Входной сигнал поступает на микроконтроллер через резистивный делитель напряжения R4R5 с коэффициентом передачи 2/(10+2) = 1/6. То есть, при тридцати вольтах на входе микроконтроллеру достанется пять вольт.



В таком случае ток через делитель составит 30/12 = 2.5 мА, а мощность тепловыделения на обоих резисторах, согласно закону Джоуля-Ленца, 2.5*30 = 75 милливатт. На первый взгляд, применение одноваттного металлоплёночного резистора типоразмера 2512 в качестве R4 может показаться избыточным.

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

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

Этот миниатюрный осциллограф является проектом с открытым исходным кодом, который можно скачать на github.
Информация об авторских правах
    Copyright (c) 2020 Creative Lau (creativelaulab@gmail.com)

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.


Делитель частоты аналого-цифрового преобразования выбирается в зависимости от выбранной частоты развёртки.

/* scale_h: Time scale 500ms, 200ms, 100ms, 50ms, 20ms, 10ms, 5ms, 2ms, 1ms, 500us, 200us, 100us */
void ADCInit(uint8 scale_h)
{
    uint8 ADC_SPEED;

    switch (scale_h)
    {
    case 0: //500ms
        ADC_SPEED = ADC_SPEED_512;
        break;

    case 1: //200ms
        ADC_SPEED = ADC_SPEED_512;
        break;

    case 2: //100ms
        ADC_SPEED = ADC_SPEED_512;
        break;

    case 3: //50ms
        ADC_SPEED = ADC_SPEED_512;
        break;

    case 4: //20ms
        ADC_SPEED = ADC_SPEED_512;
        break;

    case 5: //10ms
        ADC_SPEED = ADC_SPEED_512;
        break;

    case 6: //5ms
        ADC_SPEED = ADC_SPEED_512;
        break;

    case 7: //2ms
        ADC_SPEED = ADC_SPEED_512;
        break;

    case 8: //1ms
        ADC_SPEED = ADC_SPEED_352;
        break;

    case 9: //500us
        ADC_SPEED = ADC_SPEED_192;
        break;

    case 10: //200us
        ADC_SPEED = ADC_SPEED_32;
        break;

    case 11: //100us
        ADC_SPEED = ADC_SPEED_32;
        break;
    }
    ADCCFG = RESFMT | ADC_SPEED; //Результат выравнивается по правому краю, а тактовая частота АЦП устанавливается на системную тактовую частоту /2/16/16
    ADC_CONTR = ADC_POWER;       //Включаем модуль АЦП
    Delay5ms();                  //Задержка, чтобы АЦП успел включиться
}

Чтение результатов преобразования производится следующим образом.

uint16 ADCRead(uint8 chx)
{
    uint16 res;
    ADC_RES = 0;            //Очищаем регистры результата
    ADC_RESL = 0;           
    ADC_CONTR &= 0xF0;      //Выбор входного канала АЦП
    ADC_CONTR |= chx;
    ADC_CONTR |= ADC_START; //Запускаем преобразование
    _nop_();
    _nop_();

    while (!(ADC_CONTR & ADC_FLAG))
        ; //Флаг завершения преобразования

    ADC_CONTR &= ~ADC_FLAG;          
    res = (ADC_RES << 8) | ADC_RESL; //Чтение результатов АЦП
    return res;
}

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

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

uint16 GetADC_CHX(uint8 chx)
{   uint16 ADCx;
    uint8 i;

    ADCInit(0); //Инициализация АЦП

    //Измеряем напряжение на встроенном источнике опорного напряжения
    ADCRead(chx); //Отбрасываем первые два результата, которые могут быть неточными после переключения канала АЦП
    ADCRead(chx);
    ADCx = 0;

    for (i = 0; i < 16; i++)
    {
        ADCx += ADCRead(chx);
    }

    ADCx >>= 4; //Вычисляем среднее арифметическое
    return ADCx;
}

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

uint16 GetVoltage(uint8 chx, uint16 lsb)
{
    uint16 ADCbg;
    uint16 *BGV;
    uint16 ADCx;
    uint16 Vx;

    ADCInit(0); 

    //Получаем усреднённое значение напряжения ИОН через 16-й канал АЦП
    ADCbg = GetADC_CHX(ADC_CHS_BG);

    //Получаем усреднённое значение входного напряжения ИОН через канал chx
    ADCx = GetADC_CHX(chx);

    //Рассчитываем входное напряжение
    BGV = GetBGV();
    Vx = (uint32)*BGV * ADCx * lsb / ADCbg / 100;
    return Vx;
}

//	ADCRead(chx) Timing:500ms, 200ms, 100ms, 50ms, 20ms, 10ms, 5ms, 2ms, 1ms, 500us, 200us, 100us
//	Горизонтальное разрешение равно 100 точкам - 4 группам по 25
//  24MHz
//	ADC_SPEED_512 28us
//	ADC_SPEED_480 26.2us
//	ADC_SPEED_448 25us
//	ADC_SPEED_416 23.8us
//	ADC_SPEED_384 22us
//	ADC_SPEED_352 20us
//	ADC_SPEED_320 18us
//	ADC_SPEED_288 17us
//	ADC_SPEED_256 15us
//	ADC_SPEED_224 14us
//	ADC_SPEED_192 12us
//	ADC_SPEED_160 10.4us
//	ADC_SPEED_128 9us
//	ADC_SPEED_96 7us
//	ADC_SPEED_64 5.6us
//	ADC_SPEED_32 4us
//
//  27MHz
//	ADC_SPEED_512 26us
//	ADC_SPEED_352 19us
//	ADC_SPEED_192 11us
//	ADC_SPEED_32 4us
void switch_Dealy(uint8 scale_h)
{
    switch (scale_h)
    {
        //500ms ADC_SPEED_512
    case 0:
        Delay19971us();
        break;

        //200ms ADC_SPEED_512
    case 1:
        Delay7971us();
        break;

        //100ms	ADC_SPEED_512
    case 2:
        Delay3971us();
        break;

        //50ms ADC_SPEED_512
    case 3:
        Delay1971us();
        break;

        //20ms ADC_SPEED_512
    case 4:
        Delay771us();
        break;

        //10ms ADC_SPEED_512
    case 5:
        Delay371us();
        break;

        //5ms ADC_SPEED_512
    case 6:
        Delay171us();
        break;

        //2ms ADC_SPEED_512
    case 7:
        Delay51us();
        break;

        //1ms ADC_SPEED_352
    case 8:
        Delay18us();
        break;

        //500us	ADC_SPEED_192
    case 9:
        Delay6us();
        break;

        //200us ADC_SPEED_32
    case 10:
        /* Точная настройка интервала выборки */
        _nop_();
        //_nop_();
        break;

        //100us ADC_SPEED_32
    case 11:

        break;
    }
}

▍ Синхронизация и развёртка


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

Достигнуть того или иного значения можно как сверху, так и снизу. Поэтому предусмотрен выбор режима синхронизации — по подъёму или спаду входного напряжения.

Развёртка может срабатывать однократно, фиксируя осциллограмму до ручного перезапуска, либо автоматически перезапускаться двумя способами: после каждого заполнения буфера для периодических сигналов, либо после срабатывания синхронизации для апериодических или редко повторяющихся.

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

uint16 *GetWaveADC(uint8 chx, uint8 scale_h)
{
    uint8 i, j;
    static uint16 ADCSampling[SAMPLE_NUM];
    uint16 ADCPreSampling[PRE_BUF_NUM + 1]; //Первая дополнительная позиция используется для копирования значения последней позиции при формировании циклического кэша.

    ADCComplete = 0; //Снимаем флаг завершения выборки
    if (ADCInterrupt)
        return ADCSampling;
    memset(ADCSampling, 0x00, SAMPLE_NUM * 2);
    memset(ADCPreSampling, 0x00, (PRE_BUF_NUM + 1) * 2);
    //BGV = GetBGV(); 
    ADCbg = GetADC_CHX(ADC_CHS_BG);                         //Получаем значение опорного напряжения
    TriggerADC = Convert_mv_ADC(TriLevel, BGV, ADCbg, Lsb); //Преобразовываем заданное пользователем значение порога синхронизации в значение АЦП

    //Ждём стабилизации напряжения ИОН
    ADCInit(scale_h);
    ADCRead(chx); //Отбрасываем первые два результата
    ADCRead(chx);

    //Минимальный временной интервал 100 микросекунд не поддерживает однократную развёртку
    if (scale_h == 11) //100 us
    {
        P_Ready = 1; //Начинаем фиксацию мгновенных значений и зажигаем светодиод — индикатор синхронизации
        for (i = 0; i < SAMPLE_NUM; i++)
        {
            if (ADCInterrupt)
                return ADCSampling;
            ADCSampling[i] = ADCRead(chx);
#ifdef DEBUG
            P15 = ~P15;
#endif
        }
        P_Ready = 0; //После заполнения буфера гасим индикатор синхронизации
    }

    /* Выбор режима: однократная или повторяющаяся развёртка*/
    else if (TriMode)
    {
        P_Ready = 0;                       //Предварительно заполняем буфер, не реагируя на порог синхронизации, чтобы не потерять левую часть осциллограммы
        for (j = 1; j <= PRE_BUF_NUM; j++) 
        {
            if (ADCInterrupt)
                return ADCSampling;

            Delay3us();            //Коррекция временного интервала: цикл выборки при однократной развёртке или первом запуске непрерывной на 3 мкс медленнее, чем при автоматическом перезапуске.
            switch_Dealy(scale_h); //Задержка выборки
            ADCPreSampling[j] = ADCRead(chx);
#ifdef DEBUG
            P15 = ~P15;
#endif
        }
        P_Ready = 1; //Когда предварительное кэширование завершено, загорается индикатор и можно обрабатывать порог синхронизации.

        //Цикл кэширует точки выборки PRE_BUF_NUM перед запуском и определяет, соответствуют ли точки выборки условиям запуска.
        while (1)
        {
            if (ADCInterrupt)
                return ADCSampling;

            //Если предварительный буфер переполняется, то последнее значение выборки копируется в первую позицию.
            if (j > PRE_BUF_NUM)
            {
                j = 1;
                ADCPreSampling[0] = ADCPreSampling[PRE_BUF_NUM];
            }
            switch_Dealy(scale_h); //Задержка выборки
            ADCPreSampling[j] = ADCRead(chx);
            if (GetTriggerPos(ADCPreSampling[j - 1], ADCPreSampling[j], TriggerADC, TriSlope)) //Соблюдаются ли условия срабатывания триггера синхронизации?
            {
                P_Ready = 0; //После успешной синхронизации выходим из цикла while и гасим светодиод
                break;
            }
            j++;
#ifdef DEBUG
            P15 = ~P15;
#endif
        }

        //Продолжаем запоминание мгновенных значений входного напряжения
        for (i = 0; i < AFT_BUF_NUM; i++)
        {
            if (ADCInterrupt)
                return ADCSampling;

            Delay3us();            
            switch_Dealy(scale_h); 
            ADCSampling[i + PRE_BUF_NUM] = ADCRead(chx);
#ifdef DEBUG
            P15 = ~P15;
#endif
        }

        //Объединяем первую и последнюю точки выборки PRE_BUF_NUM в полную форму волны.
        for (i = 0; i < PRE_BUF_NUM; i++) //Первое и последнее значения выборки в предварительном кэше равны, первое значение отбрасываем, а оставшиеся точки выборки PRE_BUF_NUM-1 сортируем в порядке выборки как первые точки выборки PRE_BUF_NUM-1 ADCSampling.
        {
            if (ADCInterrupt)
                return ADCSampling;
            if (++j > PRE_BUF_NUM) //Переполнение предварительного кэша
                j = 1;

            ADCSampling[i] = ADCPreSampling[j];
        }
    }

    /* Режим повторяющейся (автоматической) развёртки */
    else
    {
        P_Ready = 1; //Начинаем отбор мгновенных значений и зажигаем индикатор
        for (i = 0; i < SAMPLE_NUM; i++)
        {
            if (ADCInterrupt)
                return ADCSampling;
            ADCSampling[i] = ADCRead(chx);
            Delay3us();            //Коррекция временного интервала
            switch_Dealy(scale_h); //Задержка выборки
#ifdef DEBUG
            P15 = ~P15;
#endif
        }
        P_Ready = 0; //Гасим светодиод
    }
    ADCComplete = 1; //Устанавливаем флаг завершения выборки
    return ADCSampling;
}

#endif

▍ Генератор стандартных сигналов


Тестовый меандр формируется предельно простым способом: при инициализации программного обеспечения осциллографа запускается модуль ШИМ, относящийся, как и модуль АЦП, к разряду периферии, независимой от ядра.

Преобразование меандра в синусоиду осуществляется посредством пассивного RLC-фильтра. При отсутствии резистора RP1 граничная частота фильтра L1C3 равнялась бы 34 килогерцам при характеристическом сопротивлении 213 Ом.



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



▍ Итоги работы


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

Видео: карманный осциллограф
Напишите в комментариях, какие усовершенствования целесообразно внести в этот карманный осциллографический пробник.

Telegram-канал со скидками, розыгрышами призов и новостями IT ?

Комментарии (23)


  1. Luboff_sky
    01.08.2024 13:49
    +2

    Как индикатор - замечательно! Прикрутить батарейку для карманности.


  1. sargon5000
    01.08.2024 13:49
    +9

    Хотелось бы прежде всего видеть характеристики прибора: частота дискретизации, полоса пропускания, разрядность АЦП, глубина памяти, входное сопротивление и ёмкость, разрешение дисплея, имеется ли связь с компьютером...


    1. RichardMerlock
      01.08.2024 13:49

      Мануала к контроллеру и исходного кода более чем достаточно, чтобы все эти параметры оценить. И для этого требуется усилие воли. Если рука к мануалу не потянулась, значит оно вам не надо. Отсекатель бесполезного времяпровождения.


  1. gorod0k
    01.08.2024 13:49
    +5

    Каким образом подобные статьи попадают в "лучшее" ?


    1. Moog_Prodigy
      01.08.2024 13:49
      +3

      1.Это корпоративный блог

      2.А других особо и нету...


      1. bikevit2008
        01.08.2024 13:49

        Факт)


    1. vt16
      01.08.2024 13:49
      +1

      пусть лучше такие


  1. samozvet
    01.08.2024 13:49

    норм

    привыкайте )


  1. Albert2009Zi
    01.08.2024 13:49
    +2

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


    1. lapot2
      01.08.2024 13:49
      +1

      чтобы за потоком лапши никто не увидел плагиат..)))


  1. alcotel
    01.08.2024 13:49

    Сам карманными китайскими осциллами пользуюсь на выездах, или когда нужна развязка от земли. Но не этим ...

    Хуже кода для мк я ещё не видел. Частоту выборки АЦП настраивают с помощью программных задержек и NOPов. А за энкодером следят через прерывание со входного порта GPIO. Рука-лиццо. Не стоит повторять это дома.


  1. Grey83
    01.08.2024 13:49
    +1

    а у этого осциллографа лучше характеристики/возможности, чем у сделанных на основе "синей таблетки" STM32F103C8T6 и экрана ILI9341?

    • Максимальная частота оцифровки 4.27 мГц (Интерлив, ДМА, 120 мГц тактовая частота)

    • Синхронизация: фронт, спад, max, min

    • Входное напряжение 0 ~ 3В

    • Дисплей SPI TFT 2.2" (2.4")) 320x240

    • Цифровой люминофор (0.1сек/дел ~ 10сек/дел)

    • FFT спектр сигнала

    • Заморозка экрана (Freezing)

    • Генератор прямоугольных импульсов 0.1Гц ~ 20мГц

    • Пять кнопок управления

    http://ansvet.ru/stm/f1_osc_320x240/

    Кроме того делали и другие версии осцилографов на основе такой платки, например: https://habr.com/ru/articles/384723/


    1. kuzzdra
      01.08.2024 13:49
      +1

      а у этого осциллографа лучше характеристики/возможности, чем у сделанных на основе "синей таблетки" STM32F103C8T6

      32 разрядный проц математику считать должен быстрее 8 разрядного. И у одного кварц и 72 МГц, у другого, похоже, встроенный RC генератор.. На схеме кварца не показали..


    1. Grey83
      01.08.2024 13:49
      +1

      Нашёл на гитхабе характеристики:

      Specification

      • MCU: STC8A8K64S4A12 @27MHz

      • Display: 0.96" OLED with 128x64 resolution

      • Controller: One EC11 Encoder

      • Input: Single Channel

      • Sec/div: 500ms, 200ms, 100ms, 50ms, 20ms, 10ms, 5ms, 2ms, 1ms, 500us, 200us, 100us

      • 100us only available in Auto Trigger Mode

      • Voltage Range: 0-30V

      • Sampling Rating: 250kHz @100us/div

      Как я понимаю этот осциллограф выигрывает только по входному напряжению.


      1. alcotel
        01.08.2024 13:49
        +1

        по входному напряжению

        Ну да, делитель 10 + 2 кОм поставили. Выиграли /s

        Ничего, что частота выборки, более чем в 10 раз отличается? Хотя у STC8A8K64S4A12 АЦП тоже до 800 kSPS работает, но он один, и нет DMA. А товарищ Ляо даже с прерываниями не стал заморачиваться.


  1. lapot2
    01.08.2024 13:49
    +1

    Очень и очень неплохо, возможно это первый подобный прибор на этом семействе мк.
    Они довольно дешевые, и скорее всего не имеют столько подделок как стм.
    По параметрам конечно до 103 не дотянет, но с атмегой328 потягается


  1. lapot2
    01.08.2024 13:49
    +1

    Автор забыл указать, что проекту 4 года. И что его автор - некий "CreativeLau"
    Стоило бы это хоть обозначить в статье.........


    1. Lunathecat Автор
      01.08.2024 13:49
      +1

      В статье это указано.


      1. lapot2
        01.08.2024 13:49
        +1

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

        Что далеко ходить, я и сам первые минут 10 радовался за вас и думал, мол хоть кто-то ещё в этот проц влез, пока не начал вникать в суть..


        1. alcotel
          01.08.2024 13:49
          +1

          На первой-же картинке на плате чёрным по белому китайским по синему написано


          1. lapot2
            01.08.2024 13:49
            +1

            это еще одно доказательство того. что большинство не вникает) я не смотрел даже)


            1. vt16
              01.08.2024 13:49
              +1

              не большинство. но вникаем. есс-то лучше было бы видеть свою от автора разработку


            1. kuzzdra
              01.08.2024 13:49
              +1

              На КПДВ пластмассовая птичка - значит статья про очередной китайский радиоконструктор. Можно переходить к комментариям ;)