Генератор с регулируемоей частотой на ардуино.

MAG-N
MAG-N аватар
Offline
Зарегистрирован: 05.06.2017

Продолжил издевательства над Si5351. Повысил верхний предел частоты с 200Е7 до 220Е7 в строке 453, дохожу до  214 000 000 - норма, ещё плюс мегагерц и вместо 215 000 000 получаю 4000 Hz. Начинаю выяснять точную величину этого скачка - получилось 214 748 364 держит, а вместо 214 748 365 опять получаю 4000. Ладно бы "круглая" граница была, а то непонятно что.

Разобрался! Oказывается, уткнулся в предел int32, пришлось переменную freq сделать int64. Вроде работает, во всяком случае на 440 МГц вторую гармонику вижу, но теперь непонятно как работает регулировка частоты. В общем ну его нафиг, не очень-то и хотелось, откатился назад.

Если я правильно понял, всё это - следствие от добавления шага 0,1 Гц - переменная freq выросла в десять раз. Будем "скрещивать" версии 3.1 и 3.2 Десятые герца мне не нужны, а вот разделение пробелами или точками надо будет сделать.

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

MAG-N, всё верно, именно из-за предела int32 я уменьшил верхнюю частоту, так как переход  с int32 на uint32 или на int64 тянул за собой огромные переделки из-за такого пустяка. PS:  Разница между 3.1 и 3.2 довольно существенна в плане улучшения кода. Особенно в частотометре.  Даже если выдрать только вывод на дисплей всё равно придёться повозиться, там же всё "заточено" под вывод удесятерённого значения. Так что проще смирится :)

MAG-N
MAG-N аватар
Offline
Зарегистрирован: 05.06.2017

Нацарапал утилитку для калибровки Si5351. Работает на "железе" V3.2, частота 10 МГц, но можно подставить любую другую. Работа с энкодером "сплагиачена" из скетча генератора (может чего лишнего навыдёргивал). В общем, работает и ладно. Cкетч генератора надо дополнить строкой (после строки 45), и вместо 80000 ввести свою константу (Cal.factor).

si5351.set_correction(80000, SI5351_PLL_INPUT_XO);



#include "Adafruit_ST7735.h"
#include <SPI.h>
#include "si5351.h"
#include <Wire.h>

#define enc_on()  timer_attach_interrupt(TIMER4, TIMER_UPDATE_INTERRUPT, enc_int); 
#define enc_off()  timer_detach_interrupt(TIMER4, TIMER_UPDATE_INTERRUPT); 

Adafruit_ST7735 tft = Adafruit_ST7735(PB12, PB11 ,PB10);
Si5351 si5351;

volatile int encstep=1; //шаг изменения частоты по умолчанию (желаемый *10)
volatile int32_t cal_step=0;
volatile int32_t cal_factor = 0;
uint64_t target_freq = 1000000000ULL; // 10 MHz, in hundredths of hertz

void setup() {
SPI.setModule(2);// выбор SPI2
i2c_master_enable(I2C1, I2C_REMAP); //SDA PB9, SCL PB8
Wire.begin();
si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
si5351.set_freq(target_freq, SI5351_CLK0);
pinMode(PB1,INPUT_PULLUP); //key encoder
pinMode(PB6,INPUT_PULLUP); //encoder
pinMode(PB7,INPUT_PULLUP); //encoder

///////// Таймер4 -обработчик энкодера     
TIMER4_BASE->CR1=(1<<2)|(1<<9);//CKD10 URS
TIMER4_BASE->CCMR1=0xf1f1; (1<<0)|(1<<8 );//cc1s, cc2s input mapped TI1/TI2
TIMER4_BASE->CCER=(1<<1)|(1<<5)|(1<<0)|(1<<4);//Capture/Compare 1,2 output polarity
TIMER4_BASE->SMCR=(1<<1)|(1<<0);//Encoder mode3(SMS bit)стр.407
TIMER4_BASE->CNT=0;
TIMER4_BASE->ARR=1;//ограничение счёта ( =3 для двухимпульсного энкодера)
TIMER4_BASE->SR=0; TIMER4_BASE->EGR=1; 
enc_on();
TIMER4_BASE->CR1|=(1<<0);//запуск 

attachInterrupt(PB1, key_enc_int, RISING); //прерывание кнопки энкодера

tft.initR(INITR_BLACKTAB);
tft.setRotation(3); tft.fillScreen(ST7735_BLACK); tft.setTextWrap(0);
tft.setTextColor(ST7735_GREEN, ST7735_BLACK);  tft.setTextSize(1);
tft.setCursor(0, 10); tft.print("Si5351 Calibration utility");
tft.setCursor(35, 20); tft.print("(c) R1BAO 2018");
tft.setTextColor(ST7735_RED, ST7735_BLACK);  tft.setTextSize(2);
tft.setCursor(20, 35); tft.print("Cal.factor");
tft.setTextColor(ST7735_WHITE, ST7735_BLACK);
tft.setCursor(15, 110); tft.print("Step=");
}

void loop() {
    
si5351.set_correction(cal_factor, SI5351_PLL_INPUT_XO);
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
tft.setTextColor(ST7735_YELLOW, ST7735_BLACK);
tft.setTextSize(3);
if (cal_step==0){tft.setCursor(35, 65); tft.print("        ");}
if (abs(cal_factor)>=1000000){tft.setCursor(20, 65); tft.print(cal_factor);}
if (abs(cal_factor)<1000)   {tft.setCursor(20, 65);  tft.print(cal_factor); tft.print("    ");}
  else if (abs(cal_factor)<10000)  {tft.setCursor(20, 65);  tft.print(cal_factor); tft.print("   ");}
  else if (abs(cal_factor)<100000) {tft.setCursor(20, 65);  tft.print(cal_factor); tft.print("  ");}
  else if (abs(cal_factor)<1000000){tft.setCursor(20, 65);  tft.print(cal_factor); tft.print(" ");}

tft.setTextSize(2); 
tft.setTextColor(ST7735_WHITE, ST7735_BLACK);
tft.setCursor(75, 110);
switch (encstep) { 
  case 1:   tft.print("1 Hz  "); break;
  case 10:  tft.print("10 Hz "); break;
  case 100: tft.print("100 Hz"); break;
 }
}

//обработчик прерываний энкодера
void enc_int(){   
  if((TIMER4_BASE->CR1)&1<<4) {cal_step+=(encstep*-1);} 
  else {cal_step+=(encstep*1);}
  if (cal_step>=10000) {cal_step=10000;}
  if (cal_step<=-10000) {cal_step=-10000;}
  cal_factor = cal_step*100;
}

// обработчик кнопки энкодера
void key_enc_int(){
  encstep*=10;
  if (encstep >100) {encstep=1;}
}

 

MAG-N
MAG-N аватар
Offline
Зарегистрирован: 05.06.2017

ua6em пишет:

И какова процедура калибровки?

Чем измерять частоту - это у кого что есть. Я, за неимением частотомера, использовал трансивер и пограмму WSJT-X. На частоте 10 МГц работают две станции эталонных частот из Москвы и Новосибирска, поэтому и выбрал её для калибровки. Настраиваемся, находим эталонную и свою частоты. Далее всё очень просто: энкодер по часовой стрелке - частота увеличивается, а значение Cal.factor будет отрицательным. Против часовой - частота уменьшается, Cal.factor положительный. Нажатие на кнопку энкодера - шаг перестройки. При шаге 1Гц меняется разряд сотен, а десятки и единицы - это для шага 0.1 и 0.01 Гц - я их не делал, нафиг не надо. У меня Cal.factor получился 79600, т.е. сдвиг частоты вниз на 796 Гц. После точной настройки полученное значение Cal.factor вписываем в скетч и радуемся.

RuslanX
RuslanX аватар
Offline
Зарегистрирован: 20.05.2017

Спасибо большое dimax за генератор. Очень хороший получился аппарат.

Я сделал два генератора что бы через смеситель получить пакеты импульсов. 

Все отлично работает, но как говорится аппетит приходит во время еды. В пакетах нет сихронизации по фазе ну и конечно их качает. Малость не приятно, но не смертельно. Большое Спасибо.

  

miklin
Offline
Зарегистрирован: 08.06.2018

 Моя плата V3.2.

MAG-N
MAG-N аватар
Offline
Зарегистрирован: 05.06.2017

А я не стал связываться с мультиплексором. Поставил дешифратор 74138 и к нему 4 реле, они пятивольтовые с током потребления около 10 мА. Если применить 74145, у которого ток выхода до 80 мА и допустимое напряжение на выходе 15 В - можно использовать 9-12 вольтовые реле с бОльшим током срабатывания.

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

Выложил полный архив с  Arduino IDE и всеми свежими библиотеками и скетчем версии 3.2 в сообщении   #50  Просто разархивировать и выбрать скетч в примерах. Кстати там чуть свежее версия скетча, но принципиальных отличий от ранее выложенной нет.

 

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

anri пишет:

 Загрузка прошла и сразу после нее появилось сообщение в трее что подключено неопознанное устройство. Теперь стмку комп не видит.

Именно так и должно быть, в сетапе ведь отключается виртуальный СОМ порт. Повторная перепрошивка возможна нажатием на ресет в нужный момент, или просто отлюченную плату включить в USB порт в нужный момент.

MAG-N
MAG-N аватар
Offline
Зарегистрирован: 05.06.2017

Похвастаюсь :-) Мой вариант генератора.  Снимал на телефон - ну не умеет он с близкого расстояния снимать, так что извините за качество. Это называется оптическая аберрация.

MAG-N
MAG-N аватар
Offline
Зарегистрирован: 05.06.2017

Самое главное - работает, и очень хорошо работает. Clock Gen настроил по эталонной частоте, потом по этому генератору настроил частотомер (на PIC-e), затем пересчитал "множитель DDS для частоты F_CPU 72МГц"  - и погрешность установки частоты в этом режиме стала почти "нулевая" :-) В режиме PWM на частоте 36 МГц ошибка 1200 герц - но тут тяжело исправить, надо конденсаторы, которые возле кварца, подгонять - ну его нафиг, и так хорошо. Непонятно одно - почему частота скачет на несколько десятков герц туда-сюда, причём только в режиме PWM. Вот картинка сигнала 36000000. Цена деления - 50 Гц.

А это сигнал Si5351, трансивер на частоте 35.999 - биения 1 кГц

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

MAG-N, думаю, что виноват PLL синтезатор в МК.  В Si5351 тоже PLL, но он всё таки задумывался изначально как тактовый генератор, и у него параметры лучше.

MAG-N
MAG-N аватар
Offline
Зарегистрирован: 05.06.2017

Наверное, так как на частоте 9 МГц та же пила, но размах раз в пять-шесть меньше, как и отклонение. На глаз уже не очень заметно, а ухо чует.  Да и не очень то надо на таких частотах PWM гонять, ну мегагерц-два ещё может и понадобится, а дальше Х его З. Самая высокая частота PWM в моих "самоделках" - 31 кГц для управления вентилятором в усилителе на 144 МГц. На звуковых частотах и на малых оборотах эта зараза работает скорее динамиком, чем вентилятором. Зудит, как комар.

MAG-N
MAG-N аватар
Offline
Зарегистрирован: 05.06.2017

Ещё похвастаюсь: с помощью данного генератора поднастроил частотомер (на частоте 200 МГц)- и вот результат - на проводок длиной около 10 см сигнал радиостанции ловит с расстояния 3-4 метра. (435 МГц)

Ванька79
Offline
Зарегистрирован: 15.04.2019

Выкладываю свою печатку в LAY,вариант на голой атмега328р,весия 2.4,в железе проверена

Предусмотрена зарядка на LTC4054 аккумулятора от сотового и USB разъем для зарядки

https://yadi.sk/d/oF1lFfxWapnwxQ

Спасибо!

 

в ардуино 1.8.8 не мог победить расположение массивов,в 1.6.5 все норм но убрал 6 "0х00"

Также энкодер наоборот (исправляется программно)

condensator80
Offline
Зарегистрирован: 24.02.2018

собрал генератор, версия 2.4 с дисплеем 5110, и небольшими доработками

надписи пока не делал

внутри

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

Версия 3.3
-изменён алгоритм обработчика энкодера и обработчика кнопки энкодера, за счёт этого больше не требуются дребезгоподавляющие конденсаторы.
-побочный бонус за счёт первого пункта -управление частотой стало более быстрым, уменьшилось время отклика при вращении энкодера.
-добавлена поддержка микроконтроллеров с интегрированным ЦАП (этой же серии F103), для них не нужно собирать резисторную матрицу, но О.У. на выходе крайне желателен (пример в Application note AN4566)  Проверил работу на  MK stm32f103ret6, не тестировал.

 

Скетч:

 /*Генератор с регулируемой частотой v3.3 (C)Dimax 
 не нужны антидребезговые конденсаторы, добавлена возможность вывода DDS сигнала через МК с встроенным ЦАП */
#define pwm2_polar 0 //полярность выхода PWM2
#define paper        0x000000 // цвет фона экрана
//#define DACINT //раскомментировать  для МК с встроенным ЦАП, выход сигнала DDS с пина PA4 
#define DDSMAX 1E7 //максимальная частота для генератора DDS (удесятерённая)
#define dds_mpl_72 835.05327478167234049174700635502 //множитель DDS для частоты F_CPU 72МГц
//для пересчёта множителя необходимо: частоту на экране прибора * текущий множитель и разделить на фактически измеренную частоту
#define dds_mpl_128 469.7191655978919104715512499704  //множитель DDS для  частоты F_CPU 128Mhz
 #include <Adafruit_GFX_AS.h>    // Core graphics library
   #include <Adafruit_ST7735.h> // Hardware-specific library
    #include <SPI.h> 
    #include "si5351.h"
     #include <Wire.h> 
      #include <libmaple/dac.h>
       Adafruit_ST7735 tft = Adafruit_ST7735(PB12, PB11,PB10); 
        Si5351 si5351;
         boolean si5351_found=0;
      volatile int enc_tic=0, duty_in=50, mon_flag, divider, modebit=1;
      volatile int mode=1;// 0- GEN_si5351, 1-PWM, 2-Duty, 3..7 DDS, 8-Freqmeter 
      volatile int encstep=10; //шаг изменения частоты по умолчанию (желаемый *10)
     volatile int32_t freq=10000; //частота по умолчанию (желаемая *10)
   volatile float duty_out;// переменная счёта скважности
  float t_hi, t_low; //переменные счёта длины импульсов 
 uint8_t wave[512]; //массив для DDS синтеза
uint8_t sine_logo[] __FLASH__ ={25,27,28,30,31,33,34,36,37,38,40,41,42,43,
  44,45,46,47,48,48,49,49,50,50,50,50,50,50,50,49,49,48,48,47,
   46,45,44,43,42,41,40,38,37,36,34,33,31,30,28,27,25,23,22,20,
    19,17,16,14,13,12,10,9,8,7,6,5,4,3,2,2,1,1,0,0,0,0,0,0,0,1,
     1,2,2,3,4,5,6,7,8,9,10,12,13,14,16,17,19,20,22,23};

          void setup() {
          delay(100);// пауза для дисплея
         SPI.setModule(2);// выбор SPI2
        tft.initR(INITR_BLACKTAB);
       tft.setRotation(3);//дисплей горизонтально, контакты слева
      tft.fillScreen(paper);//залить цветом по умолчанию
    tft.setTextWrap(0);//не переносить строки
  Serial.end();// дефолтовый USBCDC не нужен
 nvic_irq_disable_all();//отключить все прерывания 
disableDebugPorts();//отключить режим дебага
systick_disable(); // отключить системный таймер
RCC_BASE->APB1ENR|= (1<<2)|(1<<1)|(1<<0); //включить тактирование tim-2,3,4
 RCC_BASE->APB2ENR|= (1<<3)|(1<<11)|(1<<2)|(1<<0)|(1<<4);////включить тактирование port-a-b-c,tim1
  AFIO_BASE->MAPR|=(1<<8)|(1<<6); //tim 1 && tim 2 Partial remap
   i2c_master_enable(I2C1, I2C_REMAP); //SDA PB9, SCL PB8
    Wire.begin();
     si5351_found = si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);//если нашёлся si5351 поднимается флажок
// if (si5351_found) { si5351.set_freq(16E6*SI5351_FREQ_MULT, SI5351_CLK1);// фиксированная частота с выхода 1
// si5351.set_freq(20E6*SI5351_FREQ_MULT, SI5351_CLK2); }//фиксированная частота с выхода 2
  pinMode(PB0,PWM); //buzzer
   pinMode(PB1,INPUT_PULLUP);//key encoder
    pinMode(PB3,OUTPUT); //мультиплексор
     pinMode(PB4,OUTPUT); //мультиплексор
      pinMode(PB6,INPUT_PULLUP);//encoder
       pinMode(PB7,INPUT_PULLUP);//encoder
        mytone(1000,50);//сигнал после старта
       //прерывание кнопки энкодера
      attachInterrupt(PB1, key_enc_int, RISING);
     attachInterrupt(PB6, enc_int, CHANGE); attachInterrupt(PB7, enc_int, CHANGE);
if (mode==1){ timer_set(0); }
} //end setup



void loop() {
static int old_mode_loop=-1;
  if (mode!=old_mode_loop) {
   tft.fillScreen(paper); old_mode_loop=mode; mon_flag=1;
     }//чистить полностью экран только при смене режимов
    comm();//коммутация выходов мультиплексором
     if (mode==8) {mon_out(); freq_meter();  }
   if (mode >2 && mode<8) { mon_out();  dds_set(); } // запуск DDS режимов
 if (mon_flag) {mon_flag=0; mon_out();} //в остальных ситуациях при наличии флага вывода на дисплей 
}
  

void freq_meter(){
/////////////////////счётчик импульсов
pinMode(PA15,INPUT_PULLDOWN); // вход частотометра
 uint32_t imp_long,imp_hi;//переменные измерения длины такта
   __asm volatile( "cpsid i" );
   /// Timer2 счёт младших 16 бит
    TIMER2_BASE->CR1=0;//стоп таймер
     TIMER2_BASE->CCER=0; TIMER2_BASE->PSC=0;  TIMER2_BASE->CNT=0; 
      TIMER2_BASE->CCR1=0; TIMER2_BASE->CCR2=0; TIMER2_BASE->CCR3=0; 
       TIMER2_BASE->CCR4=0;TIMER2_BASE->PSC=0;TIMER2_BASE->SR=0;
        TIMER2_BASE->CCMR2=0;
         TIMER2_BASE->CR2=1<<5; //MMS:010 управление подчинённым в режиме "Update" 
          TIMER2_BASE->SMCR= (1<<14);// ECE & TS:000  режим 2 внешнего тактирования & разрешение работы от таймера1
         TIMER2_BASE->ARR=65535; //считать до максимума
        TIMER2_BASE->EGR=1; //перечитать регистры.
       TIMER2_BASE->CR1|=(1<<0);//start timer2
      /// Timer3 счёт старших 16 бит
     TIMER3_BASE->CR1=1<<0;//стоп таймер
    TIMER3_BASE->CCER=0; TIMER3_BASE->PSC=0; TIMER3_BASE->CNT=0; 
   TIMER3_BASE->CCR1=0; TIMER3_BASE->CCR2=0; TIMER3_BASE->CCR3=0; 
  TIMER3_BASE->CCR4=0;TIMER3_BASE->PSC=0;TIMER3_BASE->SR=0;TIMER3_BASE->CR2=0;  
 TIMER3_BASE->CCMR1=0; 
TIMER3_BASE->SMCR=(1<<2)|(1<<1)|(1<<0)|(1<<4);//SMS:111 && TS:001  такт брать от 2-го таймера  
 TIMER3_BASE->ARR=65535; //считать до 
  TIMER3_BASE->EGR=1; //перечитать регистры.
   TIMER3_BASE->CR1|=(1<<0);//start timer3
    /// настройка времени разрешения на таймере1 для таймера2
     TIMER1_BASE->CR1=(1<<3)|(1<<2);//один импульс, без прерываний
      TIMER1_BASE->CNT=0;
       TIMER1_BASE->CR2=(1<<4);  //MMS:001 сигнал разрешения работы другим таймерам
        TIMER1_BASE->CCER=0;// отключить выходы таймера на физ ноги
         TIMER1_BASE->PSC=F_CPU/36000 -1;// 1999; // 72000000/2000= 36000кГц тактовая таймера 
          TIMER1_BASE->ARR=35999;//считать до 36000 (1секунда) 
          TIMER1_BASE->EGR=1; //перечитать регистры.
         TIMER1_BASE->CR1|=(1<<0);
       __asm volatile( "cpsie i" );
      while (TIMER1_BASE->CR1&1) {asm volatile("nop"); if(mon_flag) {return;}  }
     freq=  TIMER3_BASE->CNT<<16  | TIMER2_BASE->CNT ; //частота не удесятерённая
    if (freq>1E5){freq*=10; t_low=0;t_hi=0; duty_out=0; mon_flag=1; return;} //выйти если freq больше 100кГц
   // Перенастройка таймера 2 в режии измерения длительности импульса и скважности для частот менее 100 кГц
 divider=1;                                  
while ((F_CPU/divider/((freq>0)? freq : 1 )) > 65000) {divider++;}
 __asm volatile( "cpsid i" );
  TIMER2_BASE->CR1=0;//стоп таймер
   TIMER2_BASE->CCER=0; TIMER2_BASE->PSC=0;  TIMER2_BASE->CNT=0; 
    TIMER2_BASE->CCR1=0; TIMER2_BASE->CCR2=0; TIMER2_BASE->CCR3=0; 
     TIMER2_BASE->CCR4=0;TIMER2_BASE->PSC=0;TIMER2_BASE->SR=0;
      TIMER2_BASE->CCMR2=0;
       TIMER2_BASE->CR2=0;
        TIMER2_BASE->PSC= divider-1;
         TIMER2_BASE->SMCR=(1<<4)|(1<<6)|(1<<2);// TS:101 SMS:100  вход TI1FP1  , Режим сброса
          TIMER2_BASE->CCMR1=(1<<0)|(1<<9);//CC1 input,mapped on TI1, CC2 input,mapped on TI1
         TIMER2_BASE->CCER=(1<<5)|(1<<0)|(1<<4);//cc1-Hi,cc2-lo 
        TIMER2_BASE->EGR=1; //перечитать регистры.
   /// настройка таймера1 для счёта  тайм-аута при измерения PWM 
     TIMER1_BASE->CR1=(1<<3);//один импульс, без прерываний
      TIMER1_BASE->CNT=0; TIMER1_BASE->CR2=0;  TIMER1_BASE->CCER=0;
         TIMER1_BASE->PSC=F_CPU/15625 -1; // тактовая таймера 15625 Герц 
          TIMER1_BASE->ARR=31250;//считать до 31250 (2 секунды) 
          TIMER1_BASE->EGR=1; //перечитать регистры.
          timer_attach_interrupt(TIMER1, TIMER_UPDATE_INTERRUPT, myint);
         TIMER1_BASE->CR1|=(1<<0);// старт счёта 2х секунд      
      __asm volatile( "cpsie i" );
       TIMER2_BASE->CR1=(1<<0);// старт захвата PWM
     while( (TIMER2_BASE->SR&0x65F)!=0x65F) {
      asm volatile("nop"); if(mon_flag) {timer_detach_interrupt(TIMER1, TIMER_UPDATE_INTERRUPT); return;} }
    TIMER2_BASE->CR1=0;// стоп таймер
   timer_detach_interrupt(TIMER1, TIMER_UPDATE_INTERRUPT);
   imp_long=(uint32_t) ((TIMER2_BASE->CCR1)*divider);
  imp_hi=(uint32_t)  ((TIMER2_BASE->CCR2)*divider);
 if (freq <1000){ freq= F_CPU*10 /imp_long ;} //если freq Менее 1кГц то использовать данные второго НЧ-измерения частоты (*10)
 else {freq*=10; } //иначе просто удесятерить результат для корректного вывода информации. 
duty_out=  (float) imp_hi / (imp_long / 100.0) ;
if (duty_out > 100 || duty_out < 0) {duty_out=0;} // на всякий случай ограничение
 t_low= (double)(imp_long-imp_hi) / (F_CPU/1E6) ;
  t_hi=  (double) imp_hi /(F_CPU/1E6);
   mon_flag=1;
   } //END freq meter

// прерывание тайм-аута при отсутствиии сигнала на входе при измерении PWM 
void myint(){ mon_flag=1; t_low=0; t_hi=0; duty_out=0;  freq=0; 
timer_detach_interrupt(TIMER1, TIMER_UPDATE_INTERRUPT);
} 

///////////////////////////////////////////////////////////////////////////
/////////*********** ВЫВОД НА ДИСПЛЕЙ************//////////////////////////
///////////////////////////////////////////////////////////////////////////
void mon_out(){
char mybuf[15];
//************** Вывод первой строчки*****************************
  tft.setCursor(0, 0); //  вперёд, вниз
   tft.setTextColor(ST7735_GREEN, paper);
    tft.setTextSize(2);
    switch(mode){ 
       case 0: tft.print("  Clock Gen  ");  break;       
        case 1: tft.print("  PWM Mode   "); break;
          case 2: tft.print("  Duty Mode  "); break;
           case 3: tft.print("  Sinus DDS  "); break;
            case 4: tft.print(" Triangle DDS"); break;
           case 5: tft.print("  Pila1 DDS  "); break;
          case 6: tft.print("  Pila2 DDS  "); break;
         case 7: tft.print("  Meandr DDS "); break;
       case 8: tft.print(" Freq. meter "); break;
       }
 //*****************Вывод второй строчки*****************************
tft.setTextColor(ST7735_WHITE, paper); 
tft.setCursor(0, 19); tft.setTextSize(3);
 if (freq>=1E8) {tft.print("         ");   tft.setTextSize(2);tft.setCursor(0, 21);}    
     if (freq<10) {sprintf(mybuf,"   0,%ld   ", freq );}                //9 -> 0,9
else if (freq<100){sprintf(mybuf,"   %ld,%ld   ", freq/10, freq%10 );}   //99 -> 9,9                
else if (freq<1E3){sprintf(mybuf,"   %ld,%ld  ", freq/10, freq%10 );}    //999 -> 99,9           
else if (freq<1E4){sprintf(mybuf,"  %ld,%ld  ", freq/10, freq%10 );}    //9999 -> 999,9             
else if (freq<1E5){sprintf(mybuf,"  %ld %03ld  ", freq/10000, (freq/10)%1000 );} //99999 -> 9.999               
else if (freq<1E6){sprintf(mybuf,"  %ld %03ld ", freq/10000, (freq/10)%1000 );}  //999999 -> 99.999                       
else if (freq<1E7){sprintf(mybuf," %ld %03ld ", freq/10000, (freq/10)%1000 );} //999999 -> 999.999                       
else if (freq<1E8){sprintf(mybuf,"%ld %03ld %03ld", freq/10000000, (freq%10000000)/10000, (freq%10000)/10  );} //9999999 -> 9.999.999                       
else              {sprintf(mybuf,"%3ld %03ld %03ld", freq/10000000, (freq%10000000)/10000, (freq%10000)/10  );} //99999999 -> 99.999.999                       
tft.print(mybuf);    //вывод частоты
//Serial.println(mybuf);
  //********************Вывод третьей строчки*****************************
                 tft.setTextSize(3); //крупно
                tft.setCursor(50, 43); //  вперёд, вниз
               tft.setTextColor(ST7735_RED,paper);
               tft.print("Herz");
  //********************* "осциллограммы"******************************
     tft.fillRect(5,90, 100,38,paper);// зачистка пяточка (вправо, вниз, ширина вправо, длина вниз)
     tft.drawRect(0,67, 160,61,ST7735_MAGENTA);//вправо, вниз, ширина вправо, длина вниз
      if (mode==1 ||mode==2 || mode==8){
        tft.drawFastVLine(5, 90, 30, ST7735_CYAN); // восход фронта статическая вер линия
         tft.drawFastHLine(5, 91, (int)duty_out, ST7735_YELLOW);//длина единицы
          tft.drawFastHLine(5, 90, (int)duty_out, ST7735_YELLOW);//паралельная линия для выделения
           tft.drawFastVLine((int)duty_out+5, 91, 30, ST7735_YELLOW);// спад
            tft.drawFastVLine((int)duty_out+4, 90, 30, ST7735_YELLOW);//паралельная линия для выделения
             tft.drawFastVLine(105, 90, 30, ST7735_YELLOW);//спад конец такта статическая вер. линия
              tft.drawFastVLine(104, 90, 30, ST7735_YELLOW);//паралельная линия для выделения
               tft.drawFastHLine((int)duty_out+5, 120, (100-(int)duty_out), ST7735_YELLOW);//линия единицы 2-го такта
                tft.drawFastHLine((int)duty_out+5, 119, (100-(int)duty_out), ST7735_YELLOW);//паралельная линия для выделения
                 }
                 if (mode==3){ // логотип синуса
                    for(uint8_t n=0; n<100; n++){tft.drawPixel(5+n, 73+ sine_logo[n],ST7735_YELLOW);
                    } //END  for
                 } // END if (mode==3)
                       else if (mode==4){// логотип треугольника                         
                      tft.drawLine(5,98,30,73,ST7735_YELLOW);
                     tft.drawLine(30,73,80,123,ST7735_YELLOW);
                    tft.drawLine(80,123,105,98,ST7735_YELLOW); 
                  } //END  mode==4
                         else if (mode==5){ //логотип пилы1
                           tft.drawLine(5,123,105,73,ST7735_YELLOW);
                           tft.drawFastVLine(105, 73, 50, ST7735_YELLOW);//спад конец такта статическая вер. линия
                           } //END  if (mode==5)
                            else if (mode==6){//логотип пилы2
                             tft.drawFastVLine(5, 73, 50, ST7735_YELLOW); // восход фронта статическая вер линия 
                             tft.drawLine(5,73,105,123,ST7735_YELLOW);
                              }// END  if (mode==6)
                                else if (mode==7 || mode==0){ //логотип меандра
                                  tft.drawFastVLine(5,73,25,ST7735_YELLOW);
                                  tft.drawFastHLine(5,73,50,ST7735_YELLOW);
                                  tft.drawFastVLine(55,73,50,ST7735_YELLOW);
                                  tft.drawFastHLine(55,123,50,ST7735_YELLOW);
                                  tft.drawFastVLine(105,98,25,ST7735_YELLOW);
                                }
                   //*********************** характеристики сигнала****************************************
                  tft.setCursor(5, 70); //  вперёд, вниз
                 tft.setTextColor(ST7735_WHITE, paper);
                tft.setTextSize(1);
                 if (mode==1 ||mode==2 || mode==8){
               tft.print("+Width="); if (t_hi<1E3) {tft.print(t_hi); tft.print(" uS  ");} else {tft.print(t_hi/1000); tft.print(" mS  ");}  
              tft.setCursor(5, 80); //  вперёд, вниз
            tft.print("-Width="); if (t_low<1E3) {tft.print(t_low); tft.print(" uS  ");} else {tft.print(t_low/1000); tft.print(" mS  ");}  
           tft.setCursor(114, 70); tft.print("Duty=");
          tft.setCursor(114, 80); tft.print(duty_out,0);tft.print(" %   ");
          } //END if (mode < 2 || mode==8)
         if (mode!=8){ // выводить шаг кроме частотометра
         tft.setCursor(114, 95); tft.print("Step=");
       tft.setCursor(114, 105); 
       switch (encstep) { 
         case 1: tft.print(" 0,1"); break;
         case 10: tft.print("   1"); break;
        case 100: tft.print("  10"); break;
       case 1E3: tft.print(" 100");break;
       case 1E4: tft.print(" 1E3");break;
      case 1E5:  tft.print(" 1E4");break;
      case 1E6:  tft.print(" 1E5");break;
      case 1E7:  tft.print(" 1E6");break;
        }// END switch case        
        } // END if (mode!=8)
//if ( freq < 1)  {tft.fillRect(1,68, 158,59,paper); }
}//END mon_out




//обработчик прерываний энкодера
void enc_int(){   
static char EncPrev=0;      //предыдущее состояние энкодера
 static char EncPrevPrev=0;  //пред-предыдущее состояние энкодера
  char EncCur = 0;
   if(!(  GPIOB_BASE->IDR&64  )){EncCur  = 1;} //опрос фазы 1 энкодера
    if(!(  GPIOB_BASE->IDR&128 )){ EncCur |= 2;} //опрос фазы 2 энкодера
    if(EncCur != EncPrev)             //если состояние изменилось,
    {
    if(EncPrev == 3 &&        //если предыдущее состояние 3
       EncCur != EncPrevPrev )      //и текущее и пред-предыдущее не равны,
    {
      if(EncCur == 2)          //если текущее состояние 2,
        enc_mode(-1);            //шаг вверх
      else                          //иначе
        enc_mode(1);            //шаг вниз
    }
    EncPrevPrev = EncPrev;          //сохранение пред-предыдущего состояния
    EncPrev = EncCur;               //сохранение предыдущего состояния
  }
 }// END VOID


// ФУНКЦИЯ конфигурации режимов 
void enc_mode(int in){
   modebit= digitalRead(PB1); //состояние кнопки PB1. 0-нажата 
   if (!modebit) {// если сейчас идёт переключение режимов (кнопка нажата)
   mytone(880,30); //звук переключения режимов
     mode+=in; 
     if(mode>8){mode=8;}
      if (si5351_found) { if(mode<0){mode=0;}  } else { if(mode<1){mode=1;} }
       if (mode==1 || mode==2 ){timer_set(0);}
        if (mode==0){clock_gen();}
        if (mode==8) {freq=0;}
        if (mode >2 && mode !=8) {if (freq>DDSMAX) {freq=DDSMAX;} }
         mon_flag=1; 
         return; 
          } //сюда попадает при изменении частоты (вращение без нажатия)
         mytone(4400,10); //звук изменения частоты
       switch(mode){ //если сейчас идёт изменение частоты
     case 0:  freq+=(encstep*in); clock_gen(); break;
     case 1:   timer_set(in); break;
     case 2:   duty_in+=in; timer_set(0);  break;  
    case 8:   break; // в частотометре не реагировать на вращение энкодера 
   default: freq+=(encstep*in); if (freq>DDSMAX) {freq=DDSMAX;}  //DDS режимы
  } //end switch case
 if (freq<0){freq=0;}   
mon_flag=1; 
}//end enc_mode


// обработчик кнопки энкодера
void key_enc_int(){
if(!modebit){ // если до этого менялся режим то выдержать паузу (низкий звук в спикер) и выйти
   TIMER2_BASE->SR=0;//доп.защита от дребезга: обнулить флаги таймера2 что бы знать когда генератор закончит пищать
     mytone(30,150); //150ms примерно соответсвует времени отпускания кнопки после вращения
   while( (TIMER2_BASE->SR)==0);//доп.защита от дребезга: подождать пока пропищит
  modebit=1; return;
  }  
    if (mode==2) { mytone(880,30); mode=1; enc_step_control() ; return;} //сменить режим и выйти если были в duty mode
     while((GPIOB_BASE->IDR&2)==0); // после отпускания кнопки если попался LOW -значит это был дребезг, дождёмся HIGH
     TIMER2_BASE->SR=0;//доп.защита от дребезга: обнулить флаги таймера2 что бы знать когда генератор закончит пищать
     mytone(160,100); //выдать звук переключения шага
   while( (TIMER2_BASE->SR)==0);//доп.защита от дребезга: подождать пока пропищит
  encstep*=10; 
 enc_step_control();
}//end 

void enc_step_control(){ //ограничение шага в зависимости от режимов и частот
// для CLOCK режима если  шаг более 1 МГц то шаг сбросить на 0,1 или 1 Герц (зацикливание переключений)
if (mode==0 &&  encstep >1E7) {encstep=1;} // Для CLOCK режима макс шаг 1МГц
if (mode==1 && encstep >1E5) {encstep=1;} //для PWM макс шаг 10 000 Гц
if (mode>2 &&  encstep >1E6) {encstep=1;} //для DDS  макс шаг 100 000 Гц
if (encstep==1 && freq >=10000 ) encstep=10;// менять шаг 0,1 Гц -> 1Гц  на частотах выше 1кГц для всех режимов
mon_flag=1; //флаг вывода на дисплей  
}



////////////////НАСТРОЙКА ТАЙМЕРА-ГЕНЕРАТОРА/////////////////////////////////////////////
void timer_set(int in){ //принимает +1 -1 или 0
int tim_arr; uint32_t imp_long, imp_hi; 
//общие настройки таймера1
GPIOA_BASE->CRL=0xB4444444; //PA7 alt_output
 GPIOA_BASE->CRH=0x4444444B; //PA8 alt_output
  TIMER1_BASE->CR1=0;
   TIMER1_BASE->CCMR2=0;TIMER1_BASE->PSC=0; TIMER1_BASE->CCR2=0; 
    TIMER1_BASE->CCER=(1<<0)|(1<<2)|(pwm2_polar<<3);//cc1e/cc1ne enable 
     //TIMER1_BASE->BDTR=(1<<15)| 255 ;// dead time sample
       TIMER1_BASE->CCMR1=(1<<6)|(1<<5)|(1<<3);//PWM mode 1
         if(freq < 84850){ //изменение частоты таймера по заданной частоте
          if (in) {freq+=(encstep*in);}//если передавалось изменение частоты, то рассчитать
           if (freq<1){freq=1;}  if (freq>(F_CPU/2*10)) {freq=F_CPU/2*10;}// ограничение макс. частоты *10
           tim_arr = F_CPU*10/freq; 
          divider=1; while ( (tim_arr/divider) > 65535) {divider++;} 
         TIMER1_BASE->PSC=divider-1;
        TIMER1_BASE->ARR=(tim_arr/divider)-1;
       } //end f (freq < 84850)
     else { // изменение частоты таймера инкрементом регистра ARR
    tim_arr=TIMER1_BASE->ARR; //снять тукущее состояния регистра     
   if (tim_arr<1000 && encstep > 1000) encstep=1000; // уменьшать шаг с ростом частоты
 if (tim_arr<100 && encstep > 100) encstep=100; // уменьшать шаг с ростом частоты
if (tim_arr<10 && encstep > 10) encstep=10;   // уменьшать шаг с ростом частоты    
in*=(encstep/10);
 tim_arr-=in;
  if (tim_arr<1) {tim_arr=1;} if (tim_arr>65535) {tim_arr=65535;}
   TIMER1_BASE->ARR=tim_arr;
    } // END  изменение частоты таймера инкрементом регистра ARR
     // установка заданного DUTY
      if(duty_in>99){duty_in=99;} if(duty_in<1){duty_in=1;} 
       if (mode==1 && TIMER1_BASE->ARR<100){ TIMER1_BASE->CCR1=(TIMER1_BASE->ARR+1)/2 ; duty_in=50;} //сбрасывать duty на 50% на высоких частотах 
        else{TIMER1_BASE->CCR1= (float) (TIMER1_BASE->ARR+1)* duty_in/100.0 ;} //или рассчитать 
         freq= F_CPU*10 /((TIMER1_BASE->ARR+1)*divider);// рассчёт фактической частоты
          duty_out=  (float) TIMER1_BASE->CCR1 / ((TIMER1_BASE->ARR+1) / 100.0) ; //расчёт фактического duty
          duty_out= floorf(duty_out); //округление
         imp_long=(uint32_t) ((TIMER1_BASE->ARR+1)*divider); //длина периода в тактах
       imp_hi=(uint32_t)  ((TIMER1_BASE->CCR1)*divider); // длина импульса в тактах
     t_low= (imp_long-imp_hi) /(F_CPU/1E6) ; //время LOW
   t_hi=  imp_hi /(F_CPU/1E6); //время HI
 TIMER1_BASE->CR1=1;
mon_flag=1;
}//end timer_set
 

// КОНФИГУРАЦИЯ DDS РЕЖИМОВ
void dds_set(){
 static byte oldmode=255;
   TIMER1_BASE->CCER=0; //timer output pins disable
    #ifdef DACINT
     rcc_clk_enable(RCC_DAC);
      rcc_reset_dev(RCC_DAC);
     gpio_set_mode(GPIOA, 4, GPIO_MODE_ANALOG);
    DAC->regs->CR = DAC_CR_BOFF1 | DAC_CR_EN1 ;
   #define DDS_OUT DAC->regs->DHR8R1
  #else 
#define DDS_OUT GPIOA_BASE->ODR
GPIOA_BASE->CRL = 0x33333333;// pa0-pa7  выход
  #endif 
     if (oldmode !=mode) {
      if (mode==3) {for(uint16_t n=0; n<512; n++){wave[n]=255*(sin(TWO_PI*float(n)/512)+1)/2 ;}}// синус
       else if (mode==4){ for(uint16_t n=0; n<512; n++){if (n<256){ wave[n]=n;} else {wave[n]=(511-n);}}}//треугол
      else if (mode==5){ for(uint16_t n=0; n<512; n++){ wave[n]=(n>>1);}}                               //пила1
     else if (mode==6){ for(uint16_t n=0; n<512; n++){ wave[n]=((~n)>>1);}}                            //пила2
    else if (mode==7){ for(uint16_t n=0; n<512; n++){if (n<256){ wave[n]=0;} else {wave[n]=255;}}}    //меандр
  oldmode=mode; } 
uint32_t dds_shag= (double)freq/10 * ((F_CPU==72E6)? dds_mpl_72 : dds_mpl_128) ;//  шаг= частота*коэффициент
asm volatile (
"mov   R9, %[port];"  "\n\t" // записать в r9 адресс порта "A"-ODR 
 "mov   R8, %[wave];"       "\n\t" //адресс массива положить в r8  
 "mov   R7, %[shag];"   "\n\t" // значение шага в r7
  "dds_loop:"                  "\n\t"
   "add R6, r7;"               "\n\t"  //(1)добавить к аккумулятору шаг
    "lsrs r2, r6, #23;"         "\n\t"  //(1) положить в R2 сдвинутый на 23 бита аккумулятор
   "ldrb r2, [r8, r2];"        "\n\t"  //(2)загрузить в R2 выбранный байт из массива
   "strb  r2, [r9];"           "\n\t"  //(2) запиcать этот байт в PORTA-ODR
  "ldr  R2, [%[flag]];"       "\n\t"  //(2) подгрузить в  R2 флаг 
 "cmp r2, 1;"                "\n\t"  //(1) сравнить 
"bne dds_loop;"              "\n\t"  //(1) перейти в цикл
: : [wave]"r" (&wave),[shag]"r"(dds_shag),[port]"r"(&DDS_OUT),[flag]"r"(&mon_flag)
: "r9","r8","r7","r6","r2" 
);
  GPIOA_BASE->CRL=0x44444444;// все пины в Z
}//END DDS set()
//


  void mytone(int frq, int ms ){
 uint16_t psc=1; uint32_t tim_arr;
 // настройка генератора звука на таймере3
  tim_arr = (F_CPU/2)/frq;
  while ( (tim_arr/psc) > 65535) {psc++;} 
  __asm volatile( "cpsid i" ); 
   TIMER2_BASE->SMCR=0;
    TIMER3_BASE->CCR3=0; //обнулить регистр соответсвующий используемому выходу
     TIMER3_BASE->PSC=psc-1;
      TIMER3_BASE->ARR=(tim_arr/psc)-1;
       TIMER3_BASE->CCMR2=(1<<5)|(1<<4);// OC3M:011
        TIMER3_BASE->CCER=1<<8;//cc3e  подключить аппаратную ногу
         TIMER3_BASE->SMCR=(1<<2)|(1<<0)|(1<<4);//SMS:101 && TS:001  строб от 2-го таймера  
          TIMER3_BASE->EGR=1; //перечитать регистры.
           TIMER3_BASE->CR1=1;
           /// настройка выдержки времени на таймере2
          psc=1;
        tim_arr = (F_CPU/1E3) * ms;
       while ( (tim_arr/psc) > 65536) {psc++;} 
      TIMER2_BASE->CCMR2=0;
     TIMER2_BASE->CR2=0;
    TIMER2_BASE->CR1=(1<<3)|(1<<2);//один импульс, без прерываний
   TIMER2_BASE->CNT=0;
  TIMER2_BASE->CR2=(1<<4);  //MMS:001 сигнал разрешения работы другим таймерам
 TIMER2_BASE->PSC=psc-1;
TIMER2_BASE->ARR=(tim_arr/psc)-1;
TIMER2_BASE->EGR=1; //перечитать регистры.
TIMER2_BASE->CR1|=(1<<0);
  __asm volatile( "cpsie i" );
}

void clock_gen(){ //функция работа с синтезатором si5351
if (!si5351_found) {return;} // на всякий случай 
if (freq <4E4){freq=4E4;} if (freq>200E7){ freq=200E7;} //допустимые рамки частот
si5351.set_freq((freq/10)* SI5351_FREQ_MULT, SI5351_CLK0);
mon_flag=1;
}

void comm(){ //коммутация выходов через мультиплексор
if (mode==1 || mode==2 ) {digitalWrite(PB3,LOW); digitalWrite(PB4,LOW);}
  else  if (mode>2 && mode<8 ) {digitalWrite(PB3,HIGH); digitalWrite(PB4,LOW);}
  else if (mode==0 ) {digitalWrite(PB3,LOW); digitalWrite(PB4,HIGH);}
 else if (mode==8) {digitalWrite(PB3,HIGH); digitalWrite(PB4,HIGH);}
}

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

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

MAG-N
MAG-N аватар
Offline
Зарегистрирован: 05.06.2017

To dimax: Если будет желание ещё что-нибудь подправить, есть предложение внести некоторые "косметические" изменения:

Ввести шаг 10Е7 - по одному мегагерцу щёлкать до 200 долго, проще по 10.

Перенести кнопку на порт РВ5 - этот пин рядом с пинами контактов энкодера, а не по диагонали, как РВ1. Печатку разводить удобнее.

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

На самом деле, изменений побольше, но это так, чтобы глазу приятнее было - запятые на точки заменил, шрифт для всех частот выставил 2, убрал "голубенький" фронт с прямоугольника ну и т.д. В посте #62 это видно.

Вот как-то так.

Частота 9 МГц

 

Ну а вот генератор на 9833, который без резисторов-конденсаторов для энкодера.

Дисплейчик здесь хорош - полноцветный 160х80 IPS, но размер 0.96

73!

 

vk007
Offline
Зарегистрирован: 16.06.2015

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

Так вот насчет просьбы. Не могли бы Вы открыть отдельную тему (тут же в "Проектах" или в "Общем"), скажем, "Обсуждение генератора с регулируемоей частотой на ардуино от dimax", в первом сообщении той темы так и написать: "Здесь ведется обсуждение, задаются вопросы по теме Генератор с регулируемоей частотой на ардуино". А в этой теме написать в последнем сообщении что-типа "Обсуждение и вопросы по сабжу - в теме Обсуждение генератора с регулируемоей частотой на ардуино от dimax (со ссылкой)".

Таким образом и эта тема не будет замусориваться, и общение сохранится. Если что-то полезное и достойное Вашего внимания появится в той теме, то нужное сообщение можно будет скопировать и сюда.

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

vk007, такую тему недавно на радиокоте создали, можно там пообсуждать.

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

Версия 3.4, изменения:
-добавлен шаг 10MHz для режима ClockGen.
-пин дисплея CS посажен на землю, вывод PB12 освободился.
-кнопку  энкодера перекинул с PB1 на  PB5.
-Добавил режим вольтметра (среднеквадратичные вычисления).

В режиме вольтметра дополнительно отображается  внутреннее напряжение питания МК, так что если сделать автономное питание - например запитать напрямую от аккумулятора lifepo4, то можно будет следить за уровнем напряжения АКБ. Так же отображается внешнее напряжение, измеренное с пина PB1(ADC9). Референс берётся от предыдущего измерения Vcc, таким образом при любом допустимом напряжении Vcc измеренные значения должны отображаться верно. Так же есть два настраиваемых предела напряжения -низкий и высокий. При превышении допустимого предела раздатся писк соответственно низкого или высокого тона. Удобно, если нужно следить за каким-то процессом, например за разрядом/зарядом аккумулятора. Как только напряжение выйдет за рамки прибор запищит, чем привлечёт внимание. При номиналах делителя как на схеме максимальное напряжение 20 вольт.

Управление вольтметром- при вращении ручки энкодера изменяется активный предел напряжения. Выбор шага так-же работает обычным способом. Активный предел выбирается нажатием кнопки и поворотом ручки энкодера вправо. Активный предел отображается жёлтым цветом.

Все рисунки кликабельны.

 

Обновлённая схема:

Скетч:

 /*Генератор с регулируемой частотой v3.4 (C)Dimax */
#define pwm2_polar 0 //полярность выхода PWM2
#define paper        0x000000 // цвет фона экрана
#define DDSMAX 1E7 //максимальная частота для генератора DDS (удесятерённая)
#define dds_mpl_72 835.05327478167234049174700635502 //множитель DDS для частоты F_CPU 72МГц
//для пересчёта множителя необходимо: частоту на экране прибора * текущий множитель и разделить на фактически измеренную частоту
#define dds_mpl_128 469.7191655978919104715512499704  //множитель DDS для  частоты F_CPU 128Mhz
#define VrefINT 1209  //внутренее опорное напряжение в милливольтах 
 #define Mn 6.06  //множитель для пересчёта напряжения с учётом резисторного делителя.
 #include <Adafruit_GFX_AS.h>    // Core graphics library
   #include <Adafruit_ST7735.h> // Hardware-specific library
    #include <SPI.h> 
    #include "si5351.h"
     #include <Wire.h> 
      #include <libmaple/dac.h>
       Adafruit_ST7735 tft = Adafruit_ST7735(-1, PB11,PB10); //PB12 освобождён, вывод CS дисплея запаять на землю.
        Si5351 si5351;
         boolean si5351_found=0; boolean modevolt =0;
      volatile int enc_tic=0, duty_in=50, mon_flag, divider, modebit=1;
      volatile int mode=3;// 0- GEN_si5351, 1-PWM, 2-Duty, 3..7 DDS, 8-Freqmeter, 9-VoltMeter 
      volatile int encstep=10; //шаг изменения частоты по умолчанию (желаемый *10)
     volatile int32_t freq=10000; //частота по умолчанию (желаемая *10)
   volatile float duty_out;// переменная счёта скважности
  float t_hi, t_low; //переменные счёта длины импульсов 
  uint32_t Vcc; //переменная внутреннего измерения напряжения питания МК (милливольты)
 int Vin_low=0, Vin_hi=15000; //переменные пределов для вольтмера (милливольты)
 uint8_t wave[512]; //массив для DDS синтеза
uint8_t sine_logo[] __FLASH__ ={25,27,28,30,31,33,34,36,37,38,40,41,42,43,
  44,45,46,47,48,48,49,49,50,50,50,50,50,50,50,49,49,48,48,47,
   46,45,44,43,42,41,40,38,37,36,34,33,31,30,28,27,25,23,22,20,
    19,17,16,14,13,12,10,9,8,7,6,5,4,3,2,2,1,1,0,0,0,0,0,0,0,1,
     1,2,2,3,4,5,6,7,8,9,10,12,13,14,16,17,19,20,22,23};
          void setup() {
          delay(100);// пауза для дисплея
         SPI.setModule(2);// выбор SPI2
        tft.initR(INITR_BLACKTAB);
       tft.setRotation(3);//дисплей горизонтально, контакты слева
      tft.fillScreen(paper);//залить цветом по умолчанию
    tft.setTextWrap(0);//не переносить строки
  Serial.end();// дефолтовый USBCDC не нужен
 nvic_irq_disable_all();//отключить все прерывания 
disableDebugPorts();//отключить режим дебага
systick_disable(); // отключить системный таймер
RCC_BASE->APB1ENR|= (1<<2)|(1<<1)|(1<<0); //включить тактирование tim-2,3,4
 RCC_BASE->APB2ENR|= (1<<3)|(1<<11)|(1<<2)|(1<<0)|(1<<4);////включить тактирование port-a-b-c,tim1
  AFIO_BASE->MAPR|=(1<<8)|(1<<6); //tim 1 && tim 2 Partial remap
   i2c_master_enable(I2C1, I2C_REMAP); //SDA PB9, SCL PB8
    Wire.begin();
     si5351_found = si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);//если нашёлся si5351 поднимается флажок
 if (si5351_found) { si5351.set_freq(16E6*SI5351_FREQ_MULT, SI5351_CLK1);// фиксированная частота с выхода 1
 si5351.set_freq(20E6*SI5351_FREQ_MULT, SI5351_CLK2); }//фиксированная частота с выхода 2
  pinMode(PB0,PWM); //buzzer
  pinMode (PB1,INPUT_ANALOG); // вход АЦП
   pinMode(PB5,INPUT_PULLUP);//key encoder
    pinMode(PB3,OUTPUT); //мультиплексор
     pinMode(PB4,OUTPUT); //мультиплексор
      pinMode(PB6,INPUT_PULLUP);//encoder
       pinMode(PB7,INPUT_PULLUP);//encoder
        mytone(1000,50);//сигнал после старта
      attachInterrupt(PB5, key_enc_int, RISING);//прерывание кнопки энкодера
     attachInterrupt(PB6, enc_int, CHANGE); attachInterrupt(PB7, enc_int, CHANGE);//прерывания энкодера
   adc_enable_single_swstart(ADC1);//запуск АЦП
   ADC1->regs->CR2 |= ADC_CR2_TSVREFE; // Enable VREFINT conversion
   ADC1->regs->SMPR1=3<<21;  //ADC (vrefint) Sample time = 28.5 cycles 
 ADC1->regs->SMPR2=3<<27; //ADC (channel 9) Sample time = 28.5 cycles 
if (mode==1){ timer_set(0); }
} //end setup


void loop() {
static int old_mode_loop=-1;
  if (mode!=old_mode_loop) {
   tft.fillScreen(paper); old_mode_loop=mode; mon_flag=1;
     }//чистить полностью экран только при смене режимов
    comm();//коммутация выходов мультиплексором
     if (mode==8) {mon_out(); freq_meter();  }
   if (mode==9) {volt_meter(); mon_out(); }
   if (mode >2 && mode<8) { mon_out();  dds_set(); } // запуск DDS режимов
 if (mon_flag) {mon_flag=0; mon_out();} //в остальных ситуациях при наличии флага вывода на дисплей 
}
  

void freq_meter(){
/////////////////////счётчик импульсов
pinMode(PA15,INPUT_PULLDOWN); // вход частотометра
 uint32_t imp_long,imp_hi;//переменные измерения длины такта
   __asm volatile( "cpsid i" );
   /// Timer2 счёт младших 16 бит
    TIMER2_BASE->CR1=0;//стоп таймер
     TIMER2_BASE->CCER=0; TIMER2_BASE->PSC=0;  TIMER2_BASE->CNT=0; 
      TIMER2_BASE->CCR1=0; TIMER2_BASE->CCR2=0; TIMER2_BASE->CCR3=0; 
       TIMER2_BASE->CCR4=0;TIMER2_BASE->PSC=0;TIMER2_BASE->SR=0;
        TIMER2_BASE->CCMR2=0;
         TIMER2_BASE->CR2=1<<5; //MMS:010 управление подчинённым в режиме "Update" 
          TIMER2_BASE->SMCR= (1<<14);// ECE & TS:000  режим 2 внешнего тактирования & разрешение работы от таймера1
         TIMER2_BASE->ARR=65535; //считать до максимума
        TIMER2_BASE->EGR=1; //перечитать регистры.
       TIMER2_BASE->CR1|=(1<<0);//start timer2
      /// Timer3 счёт старших 16 бит
     TIMER3_BASE->CR1=1<<0;//стоп таймер
    TIMER3_BASE->CCER=0; TIMER3_BASE->PSC=0; TIMER3_BASE->CNT=0; 
   TIMER3_BASE->CCR1=0; TIMER3_BASE->CCR2=0; TIMER3_BASE->CCR3=0; 
  TIMER3_BASE->CCR4=0;TIMER3_BASE->PSC=0;TIMER3_BASE->SR=0;TIMER3_BASE->CR2=0;  
 TIMER3_BASE->CCMR1=0; 
TIMER3_BASE->SMCR=(1<<2)|(1<<1)|(1<<0)|(1<<4);//SMS:111 && TS:001  такт брать от 2-го таймера  
 TIMER3_BASE->ARR=65535; //считать до 
  TIMER3_BASE->EGR=1; //перечитать регистры.
   TIMER3_BASE->CR1|=(1<<0);//start timer3
    /// настройка времени разрешения на таймере1 для таймера2
     TIMER1_BASE->CR1=(1<<3)|(1<<2);//один импульс, без прерываний
      TIMER1_BASE->CNT=0;
       TIMER1_BASE->CR2=(1<<4);  //MMS:001 сигнал разрешения работы другим таймерам
        TIMER1_BASE->CCER=0;// отключить выходы таймера на физ ноги
         TIMER1_BASE->PSC=F_CPU/36000 -1;// 1999; // 72000000/2000= 36000кГц тактовая таймера 
          TIMER1_BASE->ARR=35999;//считать до 36000 (1секунда) 
          TIMER1_BASE->EGR=1; //перечитать регистры.
         TIMER1_BASE->CR1|=(1<<0);
       __asm volatile( "cpsie i" );
      while (TIMER1_BASE->CR1&1) {asm volatile("nop"); if(mon_flag) {return;}  }
     freq=  TIMER3_BASE->CNT<<16  | TIMER2_BASE->CNT ; //частота не удесятерённая
    if (freq>1E5){freq*=10; t_low=0;t_hi=0; duty_out=0; mon_flag=1; return;} //выйти если freq больше 100кГц
   // Перенастройка таймера 2 в режии измерения длительности импульса и скважности для частот менее 100 кГц
 divider=1;                                  
while ((F_CPU/divider/((freq>0)? freq : 1 )) > 65000) {divider++;}
 __asm volatile( "cpsid i" );
  TIMER2_BASE->CR1=0;//стоп таймер
   TIMER2_BASE->CCER=0; TIMER2_BASE->PSC=0;  TIMER2_BASE->CNT=0; 
    TIMER2_BASE->CCR1=0; TIMER2_BASE->CCR2=0; TIMER2_BASE->CCR3=0; 
     TIMER2_BASE->CCR4=0;TIMER2_BASE->PSC=0;TIMER2_BASE->SR=0;
      TIMER2_BASE->CCMR2=0;
       TIMER2_BASE->CR2=0;
        TIMER2_BASE->PSC= divider-1;
         TIMER2_BASE->SMCR=(1<<4)|(1<<6)|(1<<2);// TS:101 SMS:100  вход TI1FP1  , Режим сброса
          TIMER2_BASE->CCMR1=(1<<0)|(1<<9);//CC1 input,mapped on TI1, CC2 input,mapped on TI1
         TIMER2_BASE->CCER=(1<<5)|(1<<0)|(1<<4);//cc1-Hi,cc2-lo 
        TIMER2_BASE->EGR=1; //перечитать регистры.
   /// настройка таймера1 для счёта  тайм-аута при измерения PWM 
     TIMER1_BASE->CR1=(1<<3);//один импульс, без прерываний
      TIMER1_BASE->CNT=0; TIMER1_BASE->CR2=0;  TIMER1_BASE->CCER=0;
         TIMER1_BASE->PSC=F_CPU/15625 -1; // тактовая таймера 15625 Герц 
          TIMER1_BASE->ARR=31250;//считать до 31250 (2 секунды) 
          TIMER1_BASE->EGR=1; //перечитать регистры.
          timer_attach_interrupt(TIMER1, TIMER_UPDATE_INTERRUPT, myint);
         TIMER1_BASE->CR1|=(1<<0);// старт счёта 2х секунд      
      __asm volatile( "cpsie i" );
       TIMER2_BASE->CR1=(1<<0);// старт захвата PWM
     while( (TIMER2_BASE->SR&0x65F)!=0x65F) {
      asm volatile("nop"); if(mon_flag) {timer_detach_interrupt(TIMER1, TIMER_UPDATE_INTERRUPT); return;} }
    TIMER2_BASE->CR1=0;// стоп таймер
   timer_detach_interrupt(TIMER1, TIMER_UPDATE_INTERRUPT);
   imp_long=(uint32_t) ((TIMER2_BASE->CCR1)*divider);
  imp_hi=(uint32_t)  ((TIMER2_BASE->CCR2)*divider);
 if (freq <1000){ freq= F_CPU*10 /imp_long ;} //если freq Менее 1кГц то использовать данные второго НЧ-измерения частоты (*10)
 else {freq*=10; } //иначе просто удесятерить результат для корректного вывода информации. 
duty_out=  (float) imp_hi / (imp_long / 100.0) ;
if (duty_out > 100 || duty_out < 0) {duty_out=0;} // на всякий случай ограничение
 t_low= (double)(imp_long-imp_hi) / (F_CPU/1E6) ;
  t_hi=  (double) imp_hi /(F_CPU/1E6);
   mon_flag=1;
   } //END freq meter

// прерывание тайм-аута при отсутствиии сигнала на входе при измерении PWM 
void myint(){ mon_flag=1; t_low=0; t_hi=0; duty_out=0;  freq=0; 
timer_detach_interrupt(TIMER1, TIMER_UPDATE_INTERRUPT);
} 

///////////////////////////////////////////////////////////////////////////
/////////*********** ВЫВОД НА ДИСПЛЕЙ************//////////////////////////
///////////////////////////////////////////////////////////////////////////
void mon_out(){
char mybuf[15];
//************** Вывод первой строчки*****************************
  tft.setCursor(0, 0); //  вперёд, вниз
   tft.setTextColor(ST7735_GREEN, paper);
    tft.setTextSize(2);
    switch(mode){ 
       case 0: tft.print("  Clock Gen  ");  break;       
        case 1: tft.print("  PWM Mode   "); break;
          case 2: tft.print("  Duty Mode  "); break;
           case 3: tft.print("  Sinus DDS  "); break;
            case 4: tft.print(" Triangle DDS"); break;
           case 5: tft.print("  Pila1 DDS  "); break;
          case 6: tft.print("  Pila2 DDS  "); break;
         case 7: tft.print("  Meandr DDS "); break;
       case 8: tft.print(" Freq. meter "); break;
      case 9: tft.print(" Volt. meter "); break;
       }
 //*****************Вывод второй строчки*****************************
tft.setTextColor(ST7735_WHITE, paper); 
tft.setCursor(0, 19); tft.setTextSize(3);
 if (freq>=1E8) {tft.print("         ");   tft.setTextSize(2);tft.setCursor(0, 21);}    
     if (freq<10) {sprintf(mybuf,"   0,%ld   ", freq );}                //9 -> 0,9
else if (freq<100){sprintf(mybuf,"   %ld,%ld   ", freq/10, freq%10 );}   //99 -> 9,9                
else if (freq<1E3){sprintf(mybuf,"   %ld,%ld  ", freq/10, freq%10 );}    //999 -> 99,9           
else if (freq<1E4){sprintf(mybuf,"  %ld,%ld  ", freq/10, freq%10 );}    //9999 -> 999,9             
else if (freq<1E5){sprintf(mybuf,"  %ld %03ld  ", freq/10000, (freq/10)%1000 );} //99999 -> 9.999               
else if (freq<1E6){sprintf(mybuf,"  %ld %03ld ", freq/10000, (freq/10)%1000 );}  //999999 -> 99.999                       
else if (freq<1E7){sprintf(mybuf," %ld %03ld ", freq/10000, (freq/10)%1000 );} //999999 -> 999.999                       
else if (freq<1E8){sprintf(mybuf,"%ld %03ld %03ld", freq/10000000, (freq%10000000)/10000, (freq%10000)/10  );} //9999999 -> 9.999.999                       
else              {sprintf(mybuf,"%3ld %03ld %03ld", freq/10000000, (freq%10000000)/10000, (freq%10000)/10  );} //99999999 -> 99.999.999                       
tft.print(mybuf);    //вывод частоты
  //********************Вывод третьей строчки*****************************
                 tft.setTextSize(3); //крупно
                tft.setCursor(50, 43); //  вперёд, вниз
               tft.setTextColor(ST7735_RED,paper);
             if (mode <9)  tft.print("Herz"); else  tft.print(" mV"); 
  //********************* "осциллограммы"******************************
     if (mode!=9){ tft.fillRect(5,90, 100,38,paper); }// зачистка пяточка (вправо, вниз, ширина вправо, длина вниз)
     tft.drawRect(0,67, 160,61,ST7735_MAGENTA);//рамка: вправо, вниз, ширина вправо, длина вниз
      if (mode==1 ||mode==2 || mode==8){
        tft.drawFastVLine(5, 90, 30, ST7735_CYAN); // восход фронта статическая вер линия
         tft.drawFastHLine(5, 91, (int)duty_out, ST7735_YELLOW);//длина единицы
          tft.drawFastHLine(5, 90, (int)duty_out, ST7735_YELLOW);//паралельная линия для выделения
           tft.drawFastVLine((int)duty_out+5, 91, 30, ST7735_YELLOW);// спад
            tft.drawFastVLine((int)duty_out+4, 90, 30, ST7735_YELLOW);//паралельная линия для выделения
             tft.drawFastVLine(105, 90, 30, ST7735_YELLOW);//спад конец такта статическая вер. линия
              tft.drawFastVLine(104, 90, 30, ST7735_YELLOW);//паралельная линия для выделения
               tft.drawFastHLine((int)duty_out+5, 120, (100-(int)duty_out), ST7735_YELLOW);//линия единицы 2-го такта
                tft.drawFastHLine((int)duty_out+5, 119, (100-(int)duty_out), ST7735_YELLOW);//паралельная линия для выделения
                 }
                 if (mode==3){ // логотип синуса
                    for(uint8_t n=0; n<100; n++){tft.drawPixel(5+n, 73+ sine_logo[n],ST7735_YELLOW);
                    } //END  for
                 } // END if (mode==3)
                       else if (mode==4){// логотип треугольника                         
                      tft.drawLine(5,98,30,73,ST7735_YELLOW);
                     tft.drawLine(30,73,80,123,ST7735_YELLOW);
                    tft.drawLine(80,123,105,98,ST7735_YELLOW); 
                  } //END  mode==4
                         else if (mode==5){ //логотип пилы1
                           tft.drawLine(5,123,105,73,ST7735_YELLOW);
                           tft.drawFastVLine(105, 73, 50, ST7735_YELLOW);//спад конец такта статическая вер. линия
                           } //END  if (mode==5)
                            else if (mode==6){//логотип пилы2
                             tft.drawFastVLine(5, 73, 50, ST7735_YELLOW); // восход фронта статическая вер линия 
                             tft.drawLine(5,73,105,123,ST7735_YELLOW);
                              }// END  if (mode==6)
                                else if (mode==7 || mode==0){ //логотип меандра
                                  tft.drawFastVLine(5,73,25,ST7735_YELLOW);
                                  tft.drawFastHLine(5,73,50,ST7735_YELLOW);
                                  tft.drawFastVLine(55,73,50,ST7735_YELLOW);
                                  tft.drawFastHLine(55,123,50,ST7735_YELLOW);
                                  tft.drawFastVLine(105,98,25,ST7735_YELLOW);
                                }
                   //*********************** характеристики сигнала****************************************
                  tft.setCursor(5, 70); //  вперёд, вниз
                 tft.setTextColor(ST7735_WHITE, paper);
                tft.setTextSize(1);
                 if (mode==1 ||mode==2 || mode==8){
               tft.print("+Width="); if (t_hi<1E3) {tft.print(t_hi); tft.print(" uS  ");} else {tft.print(t_hi/1000); tft.print(" mS  ");}  
              tft.setCursor(5, 80); //  вперёд, вниз
            tft.print("-Width="); if (t_low<1E3) {tft.print(t_low); tft.print(" uS  ");} else {tft.print(t_low/1000); tft.print(" mS  ");}  
           tft.setCursor(114, 70); tft.print("Duty=");
          tft.setCursor(114, 80); tft.print(duty_out,0);tft.print(" %   ");
          } //END if (mode < 2 || mode==8)
   if (mode==9) {
    tft.print("Vcc=");tft.print(Vcc); tft.print(" mV   ");          }
       
         if (mode!=8){ // выводить шаг всегда, кроме частотометра
         if (mode!=9) { tft.setCursor(114, 95);}
         tft.print("Step=");
     if (mode!=9) { tft.setCursor(114, 105);} 
       switch (encstep) { 
         case 1: tft.print(" 0,1"); break;
         case 10: tft.print("   1"); break;
        case 100: tft.print("  10"); break;
       case 1E3: tft.print(" 100");break;
       case 1E4: tft.print(" 1E3");break;
      case 1E5:  tft.print(" 1E4");break;
      case 1E6:  tft.print(" 1E5");break;
      case 1E7:  tft.print(" 1E6");break;
      case 1E8:  tft.print(" 1E7");break;

        }// END switch case        
      } // END if (mode!=8)
/// вывод пределов для вольтметра
if (mode==9) {
 tft.setCursor(5, 80); //  вперёд, вниз
    tft.setTextSize(2); 
     tft.setTextColor(modevolt? ST7735_YELLOW : ST7735_WHITE , paper);//выбрать жёлтый цвет если активен
      sprintf(mybuf,"Low_mv=%5d", Vin_low);//выводить 5 символов
      tft.print(mybuf);    //вывод нижнего предела
      tft.setTextColor(modevolt? ST7735_WHITE : ST7735_YELLOW , paper);
    tft.setCursor(5, 100); //  вперёд, вниз
  sprintf(mybuf," Hi_mv=%5d", Vin_hi);
tft.print(mybuf); //вывод верхнего предела 
 }
}//END mon_out




//обработчик прерываний энкодера
void enc_int(){   
static char EncPrev=0;      //предыдущее состояние энкодера
 static char EncPrevPrev=0;  //пред-предыдущее состояние энкодера
  char EncCur = 0;
   if(!(  GPIOB_BASE->IDR&64  )){EncCur  = 1;} //опрос фазы 1 энкодера
    if(!(  GPIOB_BASE->IDR&128 )){ EncCur |= 2;} //опрос фазы 2 энкодера
    if(EncCur != EncPrev)             //если состояние изменилось,
    {
    if(EncPrev == 3 &&        //если предыдущее состояние 3
       EncCur != EncPrevPrev )      //и текущее и пред-предыдущее не равны,
    {
      if(EncCur == 2)          //если текущее состояние 2,
        enc_mode(-1);            //шаг вверх
      else                          //иначе
        enc_mode(1);            //шаг вниз
    }
    EncPrevPrev = EncPrev;          //сохранение пред-предыдущего состояния
    EncPrev = EncCur;               //сохранение предыдущего состояния
  }
 }// END VOID


// ФУНКЦИЯ конфигурации режимов 
void enc_mode(int in){
   modebit= digitalRead(PB5); //состояние кнопки PB5. 0-нажата 
   if (!modebit) {// если сейчас идёт переключение режимов (кнопка нажата)
   mytone(880,30); //звук переключения режимов
     mode+=in; 
     if(mode>9){mode=9; modevolt=!modevolt; }
      if (si5351_found) { if(mode<0){mode=0;}  } else { if(mode<1){mode=1;} }
       if (mode==1 || mode==2 ){timer_set(0);}
        if (mode==0){clock_gen();}
        if (mode==8) {freq=0;}
        if (mode >2 && mode !=8) {if (freq>DDSMAX) {freq=DDSMAX;} }
         mon_flag=1; enc_step_control();
         return; 
          } //сюда попадает при изменении частоты (вращение без нажатия)
         mytone(4400,10); //звук изменения частоты
       switch(mode){ //если сейчас идёт изменение частоты
     case 0:  freq+=(encstep*in); clock_gen(); break;
     case 1:   timer_set(in); break;
     case 2:   duty_in+=in; timer_set(0);  break;  
    case 8:   break; // в частотометре не реагировать на вращение энкодера 
   case 9:   modevolt?  Vin_low+=(encstep/10*in) : Vin_hi+=(encstep/10*in); //регулировка пределов вольтметра
   if (Vin_low<0||Vin_low>99999 ){Vin_low=0;} if (Vin_hi<0||Vin_hi >99999){Vin_hi=0;} break; //ограничения не менее ноля и не более 4х символов 
  default: freq+=(encstep*in); if (freq>DDSMAX) {freq=DDSMAX;}  //DDS режимы
 } //end switch case
 if (freq<0){freq=0;}   
mon_flag=1; 
}//end enc_mode


// обработчик кнопки энкодера
void key_enc_int(){

if(!modebit){ // если до этого менялся режим то выдержать паузу (низкий звук в спикер) и выйти
     mytone(30,150); //150ms примерно соответсвует времени отпускания кнопки после вращения
   while( (TIMER2_BASE->SR)==0);//доп.защита от дребезга: подождать пока пропищит
  modebit=1; return;
  }  
    if (mode==2) { mytone(880,30); mode=1; enc_step_control() ; return;} //сменить режим и выйти если были в duty mode
     while( digitalRead(PB5)==0 ); // после отпускания кнопки если попался LOW -значит это был дребезг, дождёмся HIGH
     mytone(160,100); //выдать звук переключения шага
   while( (TIMER2_BASE->SR)==0);//доп.защита от дребезга: подождать пока пропищит
  encstep*=10; 
 enc_step_control();
}//end 

void enc_step_control(){ //ограничение шага в зависимости от режимов и частот
// для CLOCK режима если  шаг более 1 МГц то шаг сбросить на 0,1 или 1 Герц (зацикливание переключений)
if (mode==0 &&  encstep >1E8) {encstep=1;} // Для CLOCK режима макс шаг 10МГц
if (mode==1 && encstep >1E5) {encstep=1;} //для PWM макс шаг 10 000 Гц
if (mode>2 && mode<8 && encstep >1E6) {encstep=1;} //для DDS  макс шаг 100 000 Гц
if (encstep==1 && freq >=10000 ) encstep=10;// менять шаг 0,1 Гц -> 1Гц  на частотах выше 1кГц для всех режимов
if (mode==9 && (encstep >1E4 || encstep ==1)  ) {encstep=10;} //для вольтметра макс 1000 мв
mon_flag=1; //флаг вывода на дисплей  
}



////////////////НАСТРОЙКА ТАЙМЕРА-ГЕНЕРАТОРА/////////////////////////////////////////////
void timer_set(int in){ //принимает +1 -1 или 0
int tim_arr; uint32_t imp_long, imp_hi; 
//общие настройки таймера1
GPIOA_BASE->CRL=0xB4444444; //PA7 alt_output
 GPIOA_BASE->CRH=0x4444444B; //PA8 alt_output
  TIMER1_BASE->CR1=0;
   TIMER1_BASE->CCMR2=0;TIMER1_BASE->PSC=0; TIMER1_BASE->CCR2=0; 
    TIMER1_BASE->CCER=(1<<0)|(1<<2)|(pwm2_polar<<3);//cc1e/cc1ne enable 
     //TIMER1_BASE->BDTR=(1<<15)| 255 ;// dead time sample
       TIMER1_BASE->CCMR1=(1<<6)|(1<<5)|(1<<3);//PWM mode 1
         if(freq < 84850){ //изменение частоты таймера по заданной частоте
          if (in) {freq+=(encstep*in);}//если передавалось изменение частоты, то рассчитать
           if (freq<1){freq=1;}  if (freq>(F_CPU/2*10)) {freq=F_CPU/2*10;}// ограничение макс. частоты *10
           tim_arr = F_CPU*10/freq; 
          divider=1; while ( (tim_arr/divider) > 65535) {divider++;} 
         TIMER1_BASE->PSC=divider-1;
        TIMER1_BASE->ARR=(tim_arr/divider)-1;
       } //end f (freq < 84850)
     else { // изменение частоты таймера инкрементом регистра ARR
    tim_arr=TIMER1_BASE->ARR; //снять тукущее состояния регистра     
   if (tim_arr<1000 && encstep > 1000) encstep=1000; // уменьшать шаг с ростом частоты
 if (tim_arr<100 && encstep > 100) encstep=100; // уменьшать шаг с ростом частоты
if (tim_arr<10 && encstep > 10) encstep=10;   // уменьшать шаг с ростом частоты    
in*=(encstep/10);
 tim_arr-=in;
  if (tim_arr<1) {tim_arr=1;} if (tim_arr>65535) {tim_arr=65535;}
   TIMER1_BASE->ARR=tim_arr;
    } // END  изменение частоты таймера инкрементом регистра ARR
     // установка заданного DUTY
      if(duty_in>99){duty_in=99;} if(duty_in<1){duty_in=1;} 
       if (mode==1 && TIMER1_BASE->ARR<100){ TIMER1_BASE->CCR1=(TIMER1_BASE->ARR+1)/2 ; duty_in=50;} //сбрасывать duty на 50% на высоких частотах 
        else{TIMER1_BASE->CCR1= (float) (TIMER1_BASE->ARR+1)* duty_in/100.0 ;} //или рассчитать 
         freq= F_CPU*10 /((TIMER1_BASE->ARR+1)*divider);// рассчёт фактической частоты
          duty_out=  (float) TIMER1_BASE->CCR1 / ((TIMER1_BASE->ARR+1) / 100.0) ; //расчёт фактического duty
          duty_out= floorf(duty_out); //округление
         imp_long=(uint32_t) ((TIMER1_BASE->ARR+1)*divider); //длина периода в тактах
       imp_hi=(uint32_t)  ((TIMER1_BASE->CCR1)*divider); // длина импульса в тактах
     t_low= (imp_long-imp_hi) /(F_CPU/1E6) ; //время LOW
   t_hi=  imp_hi /(F_CPU/1E6); //время HI
 TIMER1_BASE->CR1=1;
mon_flag=1;
}//end timer_set
 

// КОНФИГУРАЦИЯ DDS РЕЖИМОВ
void dds_set(){
 static byte oldmode=255;
   TIMER1_BASE->CCER=0; //timer output pins disable
    #if defined(STM32_HIGH_DENSITY) || defined(STM32_XL_DENSITY)
     rcc_clk_enable(RCC_DAC);
      rcc_reset_dev(RCC_DAC);
     gpio_set_mode(GPIOA, 4, GPIO_MODE_ANALOG);
    DAC->regs->CR = DAC_CR_BOFF1 | DAC_CR_EN1 ;
   #define DDS_OUT DAC->regs->DHR8R1
  #else 
#define DDS_OUT GPIOA_BASE->ODR
GPIOA_BASE->CRL = 0x33333333;// pa0-pa7  выход
  #endif 
     if (oldmode !=mode) {
      if (mode==3) {for(uint16_t n=0; n<512; n++){wave[n]=255*(sin(TWO_PI*float(n)/512)+1)/2 ;}}// синус
       else if (mode==4){ for(uint16_t n=0; n<512; n++){if (n<256){ wave[n]=n;} else {wave[n]=(511-n);}}}//треугол
      else if (mode==5){ for(uint16_t n=0; n<512; n++){ wave[n]=(n>>1);}}                               //пила1
     else if (mode==6){ for(uint16_t n=0; n<512; n++){ wave[n]=((~n)>>1);}}                            //пила2
    else if (mode==7){ for(uint16_t n=0; n<512; n++){if (n<256){ wave[n]=0;} else {wave[n]=255;}}}    //меандр
  oldmode=mode; } 
uint32_t dds_shag= (double)freq/10 * ((F_CPU==72E6)? dds_mpl_72 : dds_mpl_128) ;//  шаг= частота*коэффициент
asm volatile (
"mov   R9, %[port];"  "\n\t" // записать в r9 адресс порта "A"-ODR 
 "mov   R8, %[wave];"       "\n\t" //адресс массива положить в r8  
 "mov   R7, %[shag];"   "\n\t" // значение шага в r7
  "dds_loop:"                  "\n\t"
   "add R6, r7;"               "\n\t"  //(1)добавить к аккумулятору шаг
    "lsrs r2, r6, #23;"         "\n\t"  //(1) положить в R2 сдвинутый на 23 бита аккумулятор
   "ldrb r2, [r8, r2];"        "\n\t"  //(2)загрузить в R2 выбранный байт из массива
   "strb  r2, [r9];"           "\n\t"  //(2) запиcать этот байт в PORTA-ODR
  "ldr  R2, [%[flag]];"       "\n\t"  //(2) подгрузить в  R2 флаг 
 "cmp r2, 1;"                "\n\t"  //(1) сравнить 
"bne dds_loop;"              "\n\t"  //(1) перейти в цикл
: : [wave]"r" (&wave),[shag]"r"(dds_shag),[port]"r"(&DDS_OUT),[flag]"r"(&mon_flag)
: "r9","r8","r7","r6","r2" 
);
  GPIOA_BASE->CRL=0x44444444;// все пины в Z
}//END DDS set()
//


  void mytone(int frq, int ms ){
 uint16_t psc=1; uint32_t tim_arr;
 // настройка генератора звука на таймере3
  tim_arr = (F_CPU/2)/frq;
  while ( (tim_arr/psc) > 65535) {psc++;} 
  __asm volatile( "cpsid i" ); 
   TIMER2_BASE->SMCR=0;
    TIMER3_BASE->CCR3=0; //обнулить регистр соответсвующий используемому выходу
     TIMER3_BASE->PSC=psc-1;
      TIMER3_BASE->ARR=(tim_arr/psc)-1;
       TIMER3_BASE->CCMR2=(1<<5)|(1<<4);// OC3M:011
        TIMER3_BASE->CCER=1<<8;//cc3e  подключить аппаратную ногу
         TIMER3_BASE->SMCR=(1<<2)|(1<<0)|(1<<4);//SMS:101 && TS:001  строб от 2-го таймера  
          TIMER3_BASE->EGR=1; //перечитать регистры.
           TIMER3_BASE->CR1=1;
           /// настройка выдержки времени на таймере2
          psc=1;
        tim_arr = (F_CPU/1E3) * ms;
       while ( (tim_arr/psc) > 65536) {psc++;} 
      TIMER2_BASE->CCMR2=0;
     TIMER2_BASE->CR2=0;
    TIMER2_BASE->CR1=(1<<3)|(1<<2);//один импульс, без прерываний
   TIMER2_BASE->CNT=0;
  TIMER2_BASE->CR2=(1<<4);  //MMS:001 сигнал разрешения работы другим таймерам
 TIMER2_BASE->PSC=psc-1;
TIMER2_BASE->ARR=(tim_arr/psc)-1;
TIMER2_BASE->EGR=1; //перечитать регистры.
TIMER2_BASE->SR=0;//отчистить флаги
TIMER2_BASE->CR1|=(1<<0);
  __asm volatile( "cpsie i" );
}

void clock_gen(){ //функция работа с синтезатором si5351
if (!si5351_found) {return;} // на всякий случай 
if (freq <4E4){freq=4E4;} if (freq>200E7){ freq=200E7;} //допустимые рамки частот
si5351.set_freq((freq/10)* SI5351_FREQ_MULT, SI5351_CLK0);
mon_flag=1;
}

void comm(){ //коммутация выходов через мультиплексор
if (mode==1 || mode==2 ) {digitalWrite(PB3,LOW); digitalWrite(PB4,LOW);}
  else  if (mode>2 && mode<8 ) {digitalWrite(PB3,HIGH); digitalWrite(PB4,LOW);}
  else if (mode==0 ) {digitalWrite(PB3,LOW); digitalWrite(PB4,HIGH);}
 else if (mode==8) {digitalWrite(PB3,HIGH); digitalWrite(PB4,HIGH);}
}

void volt_meter() {
static boolean alarm=0;
 uint64_t akkum=0;
 //измерение напряжения питания МК
  for (int n=0; n<=16383; n++ ) {//собирать 16384 выборок 
    akkum += sq(adc_read(ADC1,17)); } //суммировать квадраты
     akkum =  (sqrt(akkum>>14));
        Vcc = (VrefINT <<12) / akkum;
         //измерение напряжения на входе ADC9(PB1)
        akkum=0;
       for (int n=0; n<=16383; n++ ) {//собирать 16384 выборок 
      akkum += sq(adc_read(ADC1,9)); } //суммировать квадраты
     akkum =  (sqrt(akkum>>14));
   for (int n=0; n<=65535; n++ ) {asm volatile("nop"); } //Типа delay
  freq = (double) Mn * ((uint32_t)(akkum * Vcc *10)/4096);
   // если напряжение не удовлетворяет условиям, и буззер не работает -то включить
 if      (freq < Vin_low*10) { alarm =1;  if (TIMER2_BASE->SR!=0) {mytone (200, 10000);} }
  else if (freq >  Vin_hi*10) { alarm =1;  if  (TIMER2_BASE->SR!=0) {mytone (1000, 10000);} }
   else alarm=0;
    //если всё ок, а буззер работает, то выключить.
     if (!alarm && (!TIMER2_BASE->SR) ) {mytone (0, 0);}
  }

Убедительная просьба не нажимать кнопку цитировать!

240265
240265 аватар
Offline
Зарегистрирован: 12.08.2015

А зачем ADC1,17 среднеквадратичное вычислять, там же постоянка ?

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

240265, у меня было 2 цели- (1) адекватно измерять пульсирующие однополярные сигналы (2) и в целях шумопонижения, -тут проблема в том, что АЦП этого мк f103c8t6 умеет измерять аналоговый сигнал только относительно шины Vcc, на которой стоит собственный шум амплитудой до 70мВ(конкретно на своей плате измерял). Поэтому было бы неприятно, измеряя совершенно бесшумный источник, например аккумулятор, получить скачки измерений +/- 35мВ. А с среднекватическим счётом  получается довольно устойчивые измерения :) Хотя  в дополнении к (1) -тут тоже есть свои нюансы. Например если измерять 5-вольтовый 50% ШИМ, то ср.квд. напряжение будет 3,5 вольта. А просто среднее 2,5в. Т.е. в идеале можно было бы выводить на дисплей  и обычное среднее значение, ну или переключать их. Но что-то меня заломало это делать..

AnatolyDem
Offline
Зарегистрирован: 09.06.2016

Парни, подскажите :( собрал генератор версию 2.4, все запустилось, сигнал выдает, но.....пила 1 , 2 и меандр искаженые, синус и треугольник нормальной формы, его поправил в скетче изменением массива, как советовал dimax. Как можно это поправить??  Arduino пока изучаю

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

AnatolyDem, думаю самый простой способ -использовать рекомендованную версию Arduino IDE 1.6.8.

b707
Offline
Зарегистрирован: 26.05.2017

dimax пишет:

Хотя  в дополнении к (1) -тут тоже есть свои нюансы. Например если измерять 5-вольтовый 50% ШИМ, то ср.квд. напряжение будет 3,5 вольта. А просто среднее 2,5в.

dimax. а какой физический смысл в среднеквадратичном напряжении?

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

b707, ну как.. узнать реальное действующее напряжение, актуально когда измеряешь что-то пульсирующее, типа шим-питания светодиодов. А на чистом постоянном напряжении что ср.квд, что среднее -покажут одно и тоже.

b707
Offline
Зарегистрирован: 26.05.2017

dimax пишет:

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

dimax, я наверно глупость спрошу - но разве у прямоугольного сигнала действующее напряжение не равно среднему?

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

b707, именно не равно, я и писал выше про avr 2,5 вольта и rms 3,5 вольта.  т.к. идеология у измерений разная. rms привязан к (мощности/энергии/теплу). Простой наглядный пример поможет разобраться: Если взять чистый постоянный ток 5в и нагрузку 1 Ом, то по закону Ома имеем мощность (5*5)/1 =25 ватт. Теперь даём однополярный меандр, и считаем что у нас "среднее" напряжение стало 2,5 вольта - подставляем в формулу, и опа -имеем не 12,5 ватт, а всего  6,25.  А теперь подставляем rms напряжение - (3,5*3,5)/1 = вуаля, вот нашлись наши 12,5 ватт :) 

Stanislav_2x
Stanislav_2x аватар
Offline
Зарегистрирован: 21.05.2019

dimax пишет:

Stanislav_2x, скомпилил скетч вер.2.4 в ардуино Ide версии 1.6.8 - все массивы встали точно как надо с адреса 0x100 Сознайтесь, вы же компилировали не в той версии, что я рекомендовал в #11?

Для версии ArduinoIDE 1.8.9 musor_mass должен быть таким:

const PROGMEM char musor_mass[]= //массив для подгонки адреса в флэш-памяти
{
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};

и тоже все массивы встают как надо с адреса 0x500

Спасибо Огромное! Все получилось! Вот мой вариант вашего прибора. И проверка на осцилографе. Все так, как у вас в описании. На 100 КГц, небольшие искажения синуса. Все отлично. Вот фото отчет.

Уровни генератора:

Синус= +7db (+/-1)

Меандр, PWM = +11db

Треугольник, Пила= +5db

Измеритель частоты не видит частоту уровня близкую у 0 db. на уровне +23 db видит, измеряет точно.

На выход вольтметра подключил резистор 10К, чтобы не показывал наводки без подключения, на точность измерений не влияет.

Второй пин Ain5 подключил к Литию, для мониторинга батареи и переименовал. 

Питание через литий, с преобразователем напряжения MT3608. И плата зарядки. 

Получилось очень компактно. Корпус использовал от усилителя для антены телевидения. 

Автору Огромное спасибо! И респект. В моей работе (КИП связи на Ж.Д) очень пригодится, особенно для ВЧ аппаратуры и не только.

mataor
Offline
Зарегистрирован: 30.01.2014

Наконец руки дошли свой вариант собрать... dimax-у большое человеческое спасибо

    

внутри помимо основной части добавлены: акб от мобилки на 1800, зарядка на TP4056 с защитой, step-down LM3671MFX-3.3 на основное питание, буферный быстрый ОУ AD8039 (какой был) на DDS выход (для нормальной работы пришлось добавить кондер на 12пФ после резистора на 1к - f среза ~13МГц), а также повышайка до 5в на sx1308 и инвертор 7660 на -3.3в для питания этого ОУ.

на выходе PWM и DDS (после операционника) также добавил резисторы на 200 Ом 

также еще в дороге быстрый компаратор max999 для приведения в норму входа частотомера.

Самое жручее - подсветка. она хавает 150мА. Все вместе кушает до 260мА, многовато но лень заниматься оптимизацией, текущей батареи на 8ч точно хватит, а также всегда можно от ЮСБ запитать.

код пришлось подправить:

1. у меня коммутация каналов 3 релюшками G6K-2F-Y на 3.3в (ток кушают 30мА), а si5351 выведен напрямую разъемами

2. подправил код si5351 - регулировка всех 3-х каналов, значения отвязаны от общей переменной freq (не сбрасываются при переключениях - бонусом работает частотомер из гнезда в гнездо) 

Правленный код если кому интересно тут: https://yadi.sk/d/5461XP6eYPMAd

 

dima2019
Offline
Зарегистрирован: 23.07.2019

На stm32F103 можно измерять частоту до 190 мегагерц. Правда не знаю получится ли это реализовать в IDE Arduino.

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

dima2019, к чему эта фраза была? Если у вас есть методика как без внешних делителей работать с частотами выше половины тактовой -то ознакомьте.

dima2019
Offline
Зарегистрирован: 23.07.2019

Да никакой методики, просто использование возможностей МК. У stm32 есть асинхронный делитель.

https://istarik.ru/blog/stm32/124.html

 

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

dima2019, спасибо за пруф.  Про этот прескалер я знал, но не попадалась информация что он асинхронный. Попробывал включить биты прескалера в функции частотометра (в генераторе v3.4), подал 180МГц на вход -измеряет :) 

dima2019
Offline
Зарегистрирован: 23.07.2019

"подал 180МГц на вход -измеряет :)"

Спасибо что попробовали, а то у меня нечем было проверить. Так что я убрал из той статьи фразу - "теоретически можно измерить частоту около 190МГц"

...

"но не попадалась информация что он асинхронный"

The ETR timer input is the only timer input that features a presacler stage before the resynchronization stage. This presacaler stage is fully asynchronous and can be divided down to eight times less the asynchronous input signal frequency.

Timer clocking using external clock-source стр. 21

 

 

Dmitriy424
Offline
Зарегистрирован: 26.08.2019

Спасибо Вам dimax за инресный проект генератора и предоставленные необходимые материалы (схема, прошивка,  пояснения...).

 

Собранный генератор (Версия 3.4):

MAG-N
MAG-N аватар
Offline
Зарегистрирован: 05.06.2017

А вот так спектр сигнала выглядит - первая картинка PWM, вторая  5351. Частота 36 МГц, одно деление 50 Гц.

Dmitriy424
Offline
Зарегистрирован: 26.08.2019

MAG-N пишет:

..А вот так спектр сигнала выглядит - первая картинка PWM, вторая  5351...

Теперь понятно почему при одной и той же частоте (и форме сигнала)с 5351 Джиттера  нет, а у PWR есть - нестабильный такт.

MAG-N
MAG-N аватар
Offline
Зарегистрирован: 05.06.2017

5351 всёже синтезатор, потому и сигнал "получшее" :-) Да и не очень нужна ШИМ на частотах выше пары мегагерц. А если 5351 на 145 МГц настроить, то третья гармоника (435) очень даже прилично смотрится.

kloker
Offline
Зарегистрирован: 01.01.2019

я дико извиняюсь, PWM на графике с джиттером, или софтовый DDS на R2R матрице, я что-то пропустил?

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

kloker, о каком графике речь? В принципе при PWM генерации таймером существенного джиттера нет, этим страдает только DDS.

kloker
Offline
Зарегистрирован: 01.01.2019

MAG-N пишет:

Ну просядет питание при разряде аккума ниже 3.3 - ну и фиг с ним, значит "покормить" пора.

тогда модуль на TP4056 с функцией защиты от глубокого разряда ставьте, чтобы еще и фаершоу не получить. STM и до 2.5 В вполне себе работает, дисплей возможно и заткнется, не проверял, потому как не купил еще.

kloker
Offline
Зарегистрирован: 01.01.2019

dimax пишет:

kloker, о каком графике речь? В принципе при PWM генерации таймером существенного джиттера нет, этим страдает только DDS.

я про график из комента #156. не знал что и PWM в генераторе есть. на выходных попытаюсь на OLED переобуться, дефолтного дисплея так и не купил.

с таймером да, по кольцу бегает, без разрывов и перекуров.

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

kloker, я по картинке MAG_N из #156  не очень понимаю как воспринимать этот спектр.  По идее джиттер это дрожание фазы а не частоты.  Вот например в обзоре мк lgt8f328 на первом скришоте джиттер так джиттер.

MAG-N
MAG-N аватар
Offline
Зарегистрирован: 05.06.2017

На этих картинках ещё горизонтальные полоски есть - между ними 10 секунд, а эта "пила" даже ухом чуется, чистый тон в килогерц - он и есть чистый, а тут эдакое "комариное жужжание", и на блоки питания грешил, и прочее, но вот что есть, то есть.  Это скорее всего встроенный в контроллер ПЛЛ колбасит. Ещё раз доложусь, что  в "нормальных режимах" :-) ДДС и 5351 работает отменно, да и в ШИМ на частотах до 1-2 МГц тоже. Прибор работает очень даже... Забейте на эти запредельные сигналы. Это был скорее "академический интерес".

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

Версия 3.5.
- обновлён режим частотометра. Теперь максимальная измеряемая частота не менее 180МГц (при F_CPU 72MHz) Предусмотрена возможность включать и отключать внутренний делитель (на 8) входного сигнала. Переключается диапазон нажатием кнопки энкодера.На дисплее соответственно отоборажается предел 32 или 190 МГц. Эти цифры примерные, точно измерить пределы пока нет возможности. Нужно иметь ввиду, что работа с высокими частотами требует качественного входящего ТТЛ сигнала. Поэтому для высоких частот крайне желательно на входе использовать усилитель-формирователь сигнала. Также высокочастотный сигнал нельзя подавать через мультиплексор, с большой вероятностью он исказится,  потеряет форму меандра, и наберёт некоторое смещение от ноля к питанию. Я сам не делал усилитель, для проверки максимума подключал выход тактового генератора  Si5351 короткими проводами  напрямую к входу PA15 МК , при этом частоты до 180 МГц отображались достаточно верно.

-добавлен режим одновибратора (генератор одиночного импульса). Есть выбор из трёх диапазонов - "такты МК" , "Микросекунды"  и "Миллисекунды".  Кнопка энкодера переключает регулируемый параметр (значение/диапазон/шаг/запуск), который подсвечивается белым фоном. Выбранный параметр регулируется ручкой энкодера. При выборе меню "Запуск" вращение энкодера вызывает одиночные импульсы выбранной длины.  Минимальный импульс настройки одновибратора равен длительности 1 такта МК , или 13,8 nS при F_CPU 72MHz   Для чего может пригодится этот режим : Например проверить свой осциллограф, как хорошо он хватает одиночные импульсы, и какой минимальный импульс ему под силу поймать. Или импульс какой минимальной длины может поймать МК внешним прерыванием. Или просто если в работе понадобится импульс определённой длины или  импульс "без дребезга". В общем мне как-то понадобилось, и вот наконец дошли руки сделать. Комплементарный инверсный выход PA7 в режиме одновибратора так же работает.

На фото: дисплей на функции одновибратора, "пойманный" импульс длиной 13nS  осциллографом.

Актуальная схема генератора:

Скетч:
 

 /*       Генератор с регулируемой частотой v3.5  (C)Dimax         */
    #define pwm2_polar 0 //полярность выхода PWM2 (вывод PA7)
   #define paper        0x000000 // цвет фона экрана
  #define DDSMAX 1E7 //максимальная частота для генератора DDS (удесятерённая)
 #define dds_mpl_72 835.05327478167234049174700635502 //множитель DDS для частоты F_CPU 72МГц
//для пересчёта множителя необходимо: частоту на экране прибора * текущий множитель и разделить на фактически измеренную частоту
#define dds_mpl_128 469.7191655978919104715512499704  //множитель DDS для  частоты F_CPU 128Mhz
#define VrefINT 1209  //внутренее опорное напряжение в милливольтах 
 #define Mn 6.06  //множитель для пересчёта напряжения с учётом резисторного делителя.
 #include <Adafruit_ST7735.h> // Hardware-specific library
   #include <SPI.h> 
    #include "si5351.h"
     #include <Wire.h> 
      #include <libmaple/dac.h>
       Adafruit_ST7735 tft = Adafruit_ST7735(-1, PB11,PB10); //PB12 освобождён, вывод CS дисплея запаять на землю.
        Si5351 si5351;
         boolean si5351_found, modevolt, infreqpsc;
      volatile int enc_tic=0, duty_in=50, mon_flag, divider, modebit=1;
      volatile int mode=1;// 0- GEN_si5351, 1-PWM, 2-Duty, 3-impuls , 4..8 DDS, 9-Freqmeter, 10-VoltMeter 
      volatile byte imp_mode=1; //единицы счёта длины импульса  по умолчанию 0-мс, 1-мкс, 2 -такт
      volatile byte imp_mode_menu=0; //переменная выбора меню в одновибраторе (значение длины/единица времени/шаг)
      volatile int encstep=10; //шаг изменения частоты по умолчанию (желаемый *10)
     volatile int32_t freq=10000; //частота по умолчанию (желаемая *10)
   volatile float duty_out;// переменная счёта скважности
  float t_hi, t_low; //переменные счёта длины импульсов 
  uint32_t Vcc; //переменная внутреннего измерения напряжения питания МК (милливольты)
 int Vin_low=0, Vin_hi=15000; //переменные пределов для вольтмера (милливольты)
uint8_t wave[512]; //массив для DDS синтеза
uint8_t sine_logo[] __FLASH__ ={25,27,28,30,31,33,34,36,37,38,40,41,42,43,
 44,45,46,47,48,48,49,49,50,50,50,50,50,50,50,49,49,48,48,47,
   46,45,44,43,42,41,40,38,37,36,34,33,31,30,28,27,25,23,22,20,
    19,17,16,14,13,12,10,9,8,7,6,5,4,3,2,2,1,1,0,0,0,0,0,0,0,1,
     1,2,2,3,4,5,6,7,8,9,10,12,13,14,16,17,19,20,22,23};
          void setup() {
          delay(100);// пауза для загрузки дисплея
          SPI.setModule(2);// выбор SPI2
        tft.initR(INITR_BLACKTAB);
       tft.setRotation(3);//дисплей горизонтально, контакты слева
      tft.fillScreen(paper);//залить цветом по умолчанию
    tft.setTextWrap(0);//не переносить строки
 Serial.end();// дефолтовый USBCDC не нужен
 nvic_irq_disable_all();//отключить все прерывания 
disableDebugPorts();//отключить режим дебага
systick_disable(); // отключить системный таймер
RCC_BASE->APB1ENR|= (1<<2)|(1<<1)|(1<<0); //включить тактирование tim-2,3,4
 RCC_BASE->APB2ENR|= (1<<3)|(1<<11)|(1<<2)|(1<<0)|(1<<4);////включить тактирование port-a-b-c,tim1
  AFIO_BASE->MAPR|=(1<<8)|(1<<6); //tim 1 && tim 2 Partial remap
   i2c_master_enable(I2C1, I2C_REMAP); //SDA PB9, SCL PB8
    Wire.begin();
     si5351_found = si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);//если нашёлся si5351 поднимается флажок
   if (si5351_found) { si5351.set_freq(16E6*SI5351_FREQ_MULT, SI5351_CLK1);// фиксированная частота с выхода 1
 si5351.set_freq(20E6*SI5351_FREQ_MULT, SI5351_CLK2); }//фиксированная частота с выхода 2
  pinMode(PB0,PWM); //buzzer
  pinMode (PB1,INPUT_ANALOG); // вход АЦП
   pinMode(PB5,INPUT_PULLUP);//key encoder
    pinMode(PB3,OUTPUT); //мультиплексор
     pinMode(PB4,OUTPUT); //мультиплексор
      pinMode(PB6,INPUT_PULLUP);//encoder
       pinMode(PB7,INPUT_PULLUP);//encoder
        mytone(1000,50);//сигнал после старта
      attachInterrupt(PB5, key_enc_int, RISING);//прерывание кнопки энкодера
     attachInterrupt(PB6, enc_int, CHANGE); attachInterrupt(PB7, enc_int, CHANGE);//прерывания энкодера
   adc_enable_single_swstart(ADC1);//запуск АЦП
   ADC1->regs->CR2 |= ADC_CR2_TSVREFE; // Enable VREFINT conversion
   ADC1->regs->SMPR1=3<<21;  //ADC (vrefint) Sample time = 28.5 cycles 
 ADC1->regs->SMPR2=3<<27; //ADC (channel 9) Sample time = 28.5 cycles 
if (mode==1){ timer_set(0); }
} //end setup


void loop() {
static int old_mode_loop=-1;
  //чистить полностью экран только при смене режимов
  if (mode!=old_mode_loop) { tft.fillScreen(paper); old_mode_loop=mode; mon_flag=1;}
    comm();//коммутация выходов мультиплексором
     if (mode==9) {mon_out(); freq_meter();  }
   if (mode==10) {volt_meter(); mon_out(); }
   if (mode >3 && mode<9) { mon_out();  dds_set(); } // запуск DDS режимов
   //if (mode==3)
 if (mon_flag) {mon_flag=0; mon_out();} //в остальных ситуациях при наличии флага вывода на дисплей 
}
  

void freq_meter(){
/////////////////////счётчик импульсов
pinMode(PA15,INPUT_PULLDOWN); // вход частотометра
 uint32_t imp_long,imp_hi;//переменные измерения длины такта
   __asm volatile( "cpsid i" );
   /// Timer2 счёт младших 16 бит
RCC_BASE->APB1RSTR |=  1<<0; //сброс таймера2
 RCC_BASE->APB1RSTR &= ~(1<<0); // сброс таймера2
  TIMER2_BASE->CR2=1<<5; //MMS:010 управление подчинённым в режиме "Update" 
    TIMER2_BASE->SMCR= (1<<14)|(infreqpsc<<13)|(infreqpsc<<12);// режим 2 внешнего тактирования + делитель/8 + разрешение работы от таймера1 
     TIMER2_BASE->ARR=65535; //считать до максимума
       TIMER2_BASE->EGR=1; //перечитать регистры.
       TIMER2_BASE->CR1|=(1<<0);//start timer2
      /// Timer3 счёт старших 16 бит
RCC_BASE->APB1RSTR |=  1<<1; //сброс таймера3
 RCC_BASE->APB1RSTR &= ~(1<<1); // запуск таймера 3
 TIMER3_BASE->SMCR=(1<<2)|(1<<1)|(1<<0)|(1<<4);//SMS:111 && TS:001  такт брать от 2-го таймера  
  TIMER3_BASE->ARR=65535; //считать до 
   TIMER3_BASE->EGR=1; //перечитать регистры.
    TIMER3_BASE->CR1|=(1<<0);//start timer3
    /// настройка времени разрешения на таймере1 для таймера2
     TIMER1_BASE->CR1=(1<<3)|(1<<2);//один импульс, без прерываний
      TIMER1_BASE->CNT=0;
       TIMER1_BASE->CR2=(1<<4);  //MMS:001 сигнал разрешения работы другим таймерам
        TIMER1_BASE->CCER=0;// отключить выходы таймера на физ ноги
         TIMER1_BASE->PSC=F_CPU/36000 -1;// 1999; // 72000000/2000= 36000кГц тактовая таймера 
          TIMER1_BASE->ARR=35999;//считать до 36000 (1секунда) 
          TIMER1_BASE->EGR=1; //перечитать регистры.
         TIMER1_BASE->CR1|=(1<<0);
       __asm volatile( "cpsie i" );
      while (TIMER1_BASE->CR1&1) {asm volatile("nop"); if(mon_flag) {return;}  }
     freq=  TIMER3_BASE->CNT<<16  | TIMER2_BASE->CNT; //частота (не удесятерённая)
    if (infreqpsc) {freq*=8;}// если включен делитель на 8 то результат умножить на 8
    if (freq>1E5){freq*=10; t_low=0;t_hi=0; duty_out=0; mon_flag=1; return;} //выйти если freq больше 100кГц
   // Перенастройка таймера 2 в режии измерения длительности импульса и скважности для частот менее 100 кГц
 divider=1;                                  
while ((F_CPU/divider/((freq>0)? freq : 1 )) > 65000) {divider++;}
 __asm volatile( "cpsid i" );
RCC_BASE->APB1RSTR |=  1<<0; //сброс таймера2
 RCC_BASE->APB1RSTR &= ~(1<<0); // запуск таймера 2
  TIMER2_BASE->CR1=0;//стоп таймер
    TIMER2_BASE->PSC= divider-1;
      TIMER2_BASE->SMCR=(1<<4)|(1<<6)|(1<<2);// TS:101 SMS:100  вход TI1FP1  , Режим сброса
        TIMER2_BASE->CCMR1=(1<<0)|(1<<9);//CC1 input,mapped on TI1, CC2 input,mapped on TI1
         TIMER2_BASE->CCER=(1<<5)|(1<<0)|(1<<4);//cc1-Hi,cc2-lo 
        TIMER2_BASE->EGR=1; //перечитать регистры.
   /// настройка таймера1 для счёта  тайм-аута при измерения PWM 
     TIMER1_BASE->CR1=(1<<3);//один импульс, без прерываний
      TIMER1_BASE->CNT=0; TIMER1_BASE->CR2=0;  TIMER1_BASE->CCER=0;
         TIMER1_BASE->PSC=F_CPU/15625 -1; // тактовая таймера 15625 Герц 
          TIMER1_BASE->ARR=31250;//считать до 31250 (2 секунды) 
          TIMER1_BASE->EGR=1; //перечитать регистры.
          timer_attach_interrupt(TIMER1, TIMER_UPDATE_INTERRUPT, myint);
         TIMER1_BASE->CR1|=(1<<0);// старт счёта 2х секунд      
      __asm volatile( "cpsie i" );
       TIMER2_BASE->CR1=(1<<0);// старт захвата PWM
     while( (TIMER2_BASE->SR&0x65F)!=0x65F) {
      asm volatile("nop"); if(mon_flag) {timer_detach_interrupt(TIMER1, TIMER_UPDATE_INTERRUPT); return;} }
    TIMER2_BASE->CR1=0;// стоп таймер
   timer_detach_interrupt(TIMER1, TIMER_UPDATE_INTERRUPT);
   imp_long=(uint32_t) ((TIMER2_BASE->CCR1)*divider);
  imp_hi=(uint32_t)  ((TIMER2_BASE->CCR2)*divider);
 if (freq <1000){ freq= F_CPU*10 /imp_long ;} //если freq Менее 1кГц то использовать данные второго НЧ-измерения частоты (*10)
 else {freq*=10; } //иначе просто удесятерить результат для корректного вывода информации. 
duty_out=  (float) imp_hi / (imp_long / 100.0) ;
if (duty_out > 100 || duty_out < 0) {duty_out=0;} // на всякий случай ограничение
 t_low= (double)(imp_long-imp_hi) / (F_CPU/1E6) ;
  t_hi=  (double) imp_hi /(F_CPU/1E6);
   mon_flag=1;
   } //END freq meter

// прерывание тайм-аута при отсутствиии сигнала на входе при измерении PWM 
void myint(){ mon_flag=1; t_low=0; t_hi=0; duty_out=0;  freq=0; 
timer_detach_interrupt(TIMER1, TIMER_UPDATE_INTERRUPT);
} 

///////////////////////////////////////////////////////////////////////////
/////////*********** ВЫВОД НА ДИСПЛЕЙ************//////////////////////////
///////////////////////////////////////////////////////////////////////////
void mon_out(){
char mybuf[15];
//************** Вывод первой строчки*****************************
  tft.setCursor(0, 0); //  вперёд, вниз
   tft.setTextColor(ST7735_GREEN, paper);
    tft.setTextSize(2);
    switch(mode){ 
       case 0: tft.print("  Clock Gen  ");  break;       
        case 1: tft.print("  PWM Mode   "); break;
          case 2: tft.print("  Duty Mode  "); break;
           case 3: tft.print(" Impuls Mode "); break;
           case 4: tft.print("  Sinus DDS  "); break;
            case 5: tft.print(" Triangle DDS"); break;
           case 6: tft.print("  Pila1 DDS  "); break;
          case 7: tft.print("  Pila2 DDS  "); break;
         case 8: tft.print("  Meandr DDS "); break;
       case 9: tft.print(" Freq. meter "); break;
      case 10: tft.print(" Volt. meter "); break;
       }
 //*****************Вывод второй строчки*****************************
tft.setTextColor(ST7735_WHITE, paper); 
tft.setCursor(0, 19); tft.setTextSize(3);
if (mode !=3) { // в одновибраторе не использовать стандартный вывод 
 if (freq>=1E8) {tft.print("         ");   tft.setTextSize(2);tft.setCursor(0, 21);}    
     if (freq<10) {sprintf(mybuf,"   0,%ld   ", freq );}                //9 -> 0,9
else if (freq<100){sprintf(mybuf,"   %ld,%ld   ", freq/10, freq%10 );}   //99 -> 9,9                
else if (freq<1E3){sprintf(mybuf,"   %ld,%ld  ", freq/10, freq%10 );}    //999 -> 99,9           
else if (freq<1E4){sprintf(mybuf,"  %ld,%ld  ", freq/10, freq%10 );}    //9999 -> 999,9             
else if (freq<1E5){sprintf(mybuf,"  %ld %03ld  ", freq/10000, (freq/10)%1000 );} //99999 -> 9.999               
else if (freq<1E6){sprintf(mybuf,"  %ld %03ld ", freq/10000, (freq/10)%1000 );}  //999999 -> 99.999                       
else if (freq<1E7){sprintf(mybuf," %ld %03ld ", freq/10000, (freq/10)%1000 );} //999999 -> 999.999                       
else if (freq<1E8){sprintf(mybuf,"%ld %03ld %03ld", freq/10000000, (freq%10000000)/10000, (freq%10000)/10  );} //9999999 -> 9.999.999                       
else              {sprintf(mybuf,"%3ld %03ld %03ld", freq/10000000, (freq%10000000)/10000, (freq%10000)/10  );} //99999999 -> 99.999.999                       
} //end if (mode !=3)
// вывод в режиме одновибратора
if (mode==3) { sprintf(mybuf,"   %3ld   ", freq/10 );  if (imp_mode_menu == 0) {tft.setTextColor(ST7735_BLACK,ST7735_WHITE);} }
tft.print(mybuf);    //вывод частоты
  //********************Вывод третьей строчки*****************************
               tft.setTextColor(ST7735_RED,paper); //красный цет строки для всех вариантов              
              
              if (mode==10){ //если вольтметр
                tft.setTextSize(3); //крупно
               tft.setCursor(50, 43);
                tft.print(" mV"); }              

              else if (mode==9) { //если частотометр
               tft.setTextSize(2);
                tft.setCursor(20, 43);
                tft.print("Herz ");
                tft.setCursor(75, 50); tft.setTextSize(1);
                infreqpsc? tft.print("max 190MHz") : tft.print("max 32MHz ") ;
                 }

               else if (mode==3) { //если одновибратор
                if (imp_mode_menu == 1) {tft.setTextColor(ST7735_RED,ST7735_WHITE);}
               tft.setTextSize(2); 
                tft.setCursor(50, 43);
                   switch (imp_mode) { 
                   case 0: tft.print("  mS  "); break;
                   case 1: tft.print("  uS  "); break;
                   case 2: tft.print(" Takt "); break;
                }}

              
               else if (mode <9) { //если генераторы
                tft.setTextSize(3); 
                tft.setCursor(50, 43);
               tft.print("Herz");  
                }

               
  //********************* "осциллограммы"******************************
     if (mode!=10){ tft.fillRect(5,90, 100,38,paper); }// зачистка пяточка (вправо, вниз, ширина вправо, длина вниз)
     tft.drawRect(0,67, 160,61,ST7735_MAGENTA);//рамка: вправо, вниз, ширина вправо, длина вниз
      if (mode==1 ||mode==2 || mode==9){
        tft.drawFastVLine(5, 90, 30, ST7735_CYAN); // восход фронта статическая вер линия
         tft.drawFastHLine(5, 91, (int)duty_out, ST7735_YELLOW);//длина единицы
          tft.drawFastHLine(5, 90, (int)duty_out, ST7735_YELLOW);//паралельная линия для выделения
           tft.drawFastVLine((int)duty_out+5, 91, 30, ST7735_YELLOW);// спад
            tft.drawFastVLine((int)duty_out+4, 90, 30, ST7735_YELLOW);//паралельная линия для выделения
             tft.drawFastVLine(105, 90, 30, ST7735_YELLOW);//спад конец такта статическая вер. линия
              tft.drawFastVLine(104, 90, 30, ST7735_YELLOW);//паралельная линия для выделения
               tft.drawFastHLine((int)duty_out+5, 120, (100-(int)duty_out), ST7735_YELLOW);//линия единицы 2-го такта
                tft.drawFastHLine((int)duty_out+5, 119, (100-(int)duty_out), ST7735_YELLOW);//паралельная линия для выделения
                 }
                        if (mode==3){ //логотип одновибратора
                         tft.drawFastHLine(5,123,28,ST7735_YELLOW);// смещ вправо, смещ вниз, длина вправо                           
                          tft.drawFastHLine(5,122,26,ST7735_YELLOW);// паралельная линия для выделения                                                   
                           tft.drawFastVLine(32,73,50,ST7735_YELLOW); // смещ вправо, смещ вниз, высота вниз                           
                            tft.drawFastVLine(31,73,50,ST7735_YELLOW);// паралельная линия для выделения                             
                             tft.drawFastHLine(31,73,30,ST7735_YELLOW);// смещ вправо, смещ вниз, длина вправо 
                             tft.drawFastHLine(31,74,30,ST7735_YELLOW);                             
                               tft.drawFastVLine(61,73,50,ST7735_YELLOW);
                               tft.drawFastVLine(60,73,50,ST7735_YELLOW);                               
                                 tft.drawFastHLine(61,123,32,ST7735_YELLOW);
                                tft.drawFastHLine(61,122,32,ST7735_YELLOW);
                                }
                       if (mode==4){ // логотип синуса
                    for(uint8_t n=0; n<100; n++){tft.drawPixel(5+n, 73+ sine_logo[n],ST7735_YELLOW);
                    } //END  for
                 } // END if (mode==4)
                       else if (mode==5){// логотип треугольника                         
                      tft.drawLine(5,98,30,73,ST7735_YELLOW);
                     tft.drawLine(30,73,80,123,ST7735_YELLOW);
                    tft.drawLine(80,123,105,98,ST7735_YELLOW); 
                  } //END  mode==5
                         else if (mode==6){ //логотип пилы1
                           tft.drawLine(5,123,105,73,ST7735_YELLOW);
                           tft.drawFastVLine(105, 73, 50, ST7735_YELLOW);//спад конец такта статическая вер. линия
                           } //END  if (mode==6)
                            else if (mode==7){//логотип пилы2
                             tft.drawFastVLine(5, 73, 50, ST7735_YELLOW); // восход фронта статическая вер линия 
                             tft.drawLine(5,73,105,123,ST7735_YELLOW);
                              }// END  if (mode==7)
                                else if (mode==8 || mode==0){ //логотип меандра
                                  tft.drawFastVLine(5,73,25,ST7735_YELLOW);
                                  tft.drawFastHLine(5,73,50,ST7735_YELLOW);
                                  tft.drawFastVLine(55,73,50,ST7735_YELLOW);
                                  tft.drawFastHLine(55,123,50,ST7735_YELLOW);
                                  tft.drawFastVLine(105,98,25,ST7735_YELLOW);
                                }
                   //*********************** характеристики сигнала****************************************
                  tft.setCursor(5, 70); //  вперёд, вниз
                 tft.setTextColor(ST7735_WHITE, paper);
                tft.setTextSize(1);
                 if (mode==1 ||mode==2 || mode==9){
               tft.print("+Width="); if (t_hi<1E3) {tft.print(t_hi); tft.print(" uS  ");} else {tft.print(t_hi/1000); tft.print(" mS  ");}  
              tft.setCursor(5, 80); //  вперёд, вниз
            tft.print("-Width="); if (t_low<1E3) {tft.print(t_low); tft.print(" uS  ");} else {tft.print(t_low/1000); tft.print(" mS  ");}  
           tft.setCursor(114, 70); tft.print("Duty=");
          tft.setCursor(114, 80); tft.print(duty_out,0);tft.print(" %   ");
          } //END if (mode < 2 || mode==9)
   if (mode==10) { tft.print("Vcc=");tft.print(Vcc); tft.print(" mV   ");          }

  if (mode==9){ return;} // в режиме  частотометра выводить на экран больше ничего не нужно.
          
  /////////// установка курсора и вывод шага в разных режимах ///////////////   
              if (mode==3) { // в режиме одновибратора    
if (imp_mode_menu==2) { tft.setTextColor(ST7735_BLACK,ST7735_WHITE);}                     
                              tft.setCursor(114, 70); tft.print("Step=");  
                               tft.setCursor(114, 80);//курсор в поле  значения шага
                               }
                else if (mode<10) { tft.setCursor(114, 95); tft.print("Step="); tft.setCursor(114, 105);} 
                 else if (mode==10)  {tft.print("Step=");} //только в вольтметре
                
                   switch (encstep) { 
                    case 1: tft.print(" 0,1"); break;
                    case 10: tft.print("   1"); break;
                    case 100: tft.print("  10"); break;
                    case 1E3: tft.print(" 100");break;
                    case 1E4: tft.print(" 1E3");break;
                    case 1E5:  tft.print(" 1E4");break;
                    case 1E6:  tft.print(" 1E5");break;
                    case 1E7:  tft.print(" 1E6");break;
                    case 1E8:  tft.print(" 1E7");break;
                   }// END switch case           
   // вывод прочей информации
                 if (mode==3) {// вывод меню запуска импульса
                              if (imp_mode_menu==3) { tft.setTextColor(ST7735_RED,ST7735_WHITE );  }                                                                                                 
                                else { tft.setTextColor(ST7735_RED, paper );        }
                              tft.setTextSize(3);
                              tft.setCursor(100, 95);
                              tft.print("Run");
                              //tft.setTextColor(ST7735_WHITE, paper);
                              //tft.setTextSize(1); 
                            }


/// вывод пределов для вольтметра
if (mode==10) {
 tft.setCursor(5, 80); //  вперёд, вниз
    tft.setTextSize(2); 
     tft.setTextColor(modevolt? ST7735_YELLOW : ST7735_WHITE , paper);//выбрать жёлтый цвет если активен
      sprintf(mybuf,"Low_mv=%5d", Vin_low);//выводить 5 символов
      tft.print(mybuf);    //вывод нижнего предела
      tft.setTextColor(modevolt? ST7735_WHITE : ST7735_YELLOW , paper);
    tft.setCursor(5, 100); //  вперёд, вниз
  sprintf(mybuf," Hi_mv=%5d", Vin_hi);
tft.print(mybuf); //вывод верхнего предела 
 }

}//END mon_out




//обработчик прерываний энкодера
void enc_int(){   
static char EncPrev=0;      //предыдущее состояние энкодера
 static char EncPrevPrev=0;  //пред-предыдущее состояние энкодера
  char EncCur = 0;
   if(!(  GPIOB_BASE->IDR&64  )){EncCur  = 1;} //опрос фазы 1 энкодера
    if(!(  GPIOB_BASE->IDR&128 )){ EncCur |= 2;} //опрос фазы 2 энкодера
    if(EncCur != EncPrev)             //если состояние изменилось,
    {
    if(EncPrev == 3 &&        //если предыдущее состояние 3
       EncCur != EncPrevPrev )      //и текущее и пред-предыдущее не равны,
    {
      if(EncCur == 2)          //если текущее состояние 2,
        enc_mode(-1);            //шаг вверх
      else                          //иначе
        enc_mode(1);            //шаг вниз
    }
    EncPrevPrev = EncPrev;          //сохранение пред-предыдущего состояния
    EncPrev = EncCur;               //сохранение предыдущего состояния
  }
 }// END VOID


// ФУНКЦИЯ конфигурации режимов 
void enc_mode(int in){
  modebit= digitalRead(PB5); //состояние кнопки PB5. 0-нажата 
   if (!modebit) {// если сейчас идёт переключение режимов (кнопка нажата)
   mytone(880,30); //звук переключения режимов
     mode+=in; 
     if(mode>10){mode=10; modevolt=!modevolt; }
      if (si5351_found) { if(mode<0){mode=0;}  } else { if(mode<1){mode=1;} }
       if (mode==1 || mode==2 ){timer_set(0);}
        if (mode==0){clock_gen();}
        if (mode==9) {freq=0;}// сбросить в ноль freq в режиме частотометра
        if (mode==3) { TIMER1_BASE->CR1=0; freq=100;}//настройки в при входе в режим одновибратора 
        if (mode >3 && mode !=9) {if (freq>DDSMAX) {freq=DDSMAX;} }
         mon_flag=1; enc_step_control();
         return; 
          } //сюда попадает при изменении частоты (вращение без нажатия)
         mytone(4400,10); //звук изменения частоты
       switch(mode){ //если сейчас идёт изменение частоты
     case 0:  freq+=(encstep*in); clock_gen(); break;
     case 1:   timer_set(in); break;
     case 2:   duty_in+=in; timer_set(0);  break;  
     case 3:  if (imp_mode_menu==0){ freq+=(encstep*in); if (freq<10) freq=10; if (freq> 500000) {freq=500000;}   }  //переключение длительности импульса
         else if (imp_mode_menu==1){ imp_mode++;  if (imp_mode >2){imp_mode=0;} }//переключение 0-мс, 1-мкс, 2 -такт           
         else if (imp_mode_menu==2){ encstep*=10; enc_step_control();} //переключение шага
         else if (imp_mode_menu==3){ impuls(); } //генерация импульса

    case 9:   break; // в частотометре не реагировать на вращение энкодера 
   case 10:   modevolt?  Vin_low+=(encstep/10*in) : Vin_hi+=(encstep/10*in); //регулировка пределов вольтметра
   if (Vin_low<0||Vin_low>99999 ){Vin_low=0;} if (Vin_hi<0||Vin_hi >99999){Vin_hi=0;} break; //ограничения не менее ноля и не более 4х символов 
  default: freq+=(encstep*in); if (freq>DDSMAX) {freq=DDSMAX;}  //DDS режимы
 } //end switch case
 if (freq<0){freq=0;}   
mon_flag=1; 
}//end enc_mode


// обработчик кнопки энкодера:
void key_enc_int(){//сюда должно попадать только при отжимании кнопки (Rising Edge)
if (digitalRead(PB5)==0) return;// если на пине  ноль значит это дребезг, выходим. 
if(!modebit){ // если до этого менялся режим то выдержать паузу (низкий звук в спикер) и выйти
     mytone(30,150); //150ms примерно соответсвует времени отпускания кнопки после вращения
   while( (TIMER2_BASE->SR)==0);//подождать пока пропищит
  modebit=1; return; //и выйти
  }  //сменить режим и выйти если были в duty mode:
    if (mode==2) { mytone(880,30); mode=1; enc_step_control() ; return;} 
     mytone(160,100); //выдать звук переключения шага
   while( (TIMER2_BASE->SR)==0);//подождать пока пропищит
  if (mode==9) {infreqpsc=!infreqpsc; mon_flag=1; return;} //включать/отключать делитель в режиме частотометра
  if (mode==3) {  imp_mode_menu++;  if(imp_mode_menu >3){imp_mode_menu=0 ;}   }                  
  else {encstep*=10;} 
 enc_step_control();
}//end 

void enc_step_control(){ //ограничение шага в зависимости от режимов и частот
// для CLOCK режима если  шаг более 1 МГц то шаг сбросить на 0,1 или 1 Герц (зацикливание переключений)
if (mode==0 &&  encstep >1E8) {encstep=1;} // Для CLOCK режима макс шаг 10МГц
if ( mode==1  && encstep >1E5) {encstep=1;} //для PWM макс шаг 10 000 Гц
if ( mode==3 ) { if (encstep >1E4) {encstep=10;}}// ограничения для режима одновибратора
if (mode>3 && mode<9 && encstep >1E6) {encstep=1;} //для DDS  макс шаг 100 000 Гц
if (encstep==1 && freq >=10000  ) encstep=10;// менять шаг 0,1 Гц -> 1Гц  на частотах выше 1кГц для всех режимов
if (mode==10 && (encstep >1E4 || encstep ==1)  ) {encstep=10;} //для вольтметра макс 1000 мв
mon_flag=1; //флаг вывода на дисплей  
}



////////////////НАСТРОЙКА ТАЙМЕРА-ГЕНЕРАТОРА/////////////////////////////////////////////
void timer_set(int in){ //принимает +1 -1 или 0
int tim_arr; uint32_t imp_long, imp_hi; 
//общие настройки таймера1
pinMode(PA7,PWM); pinMode(PA8,PWM);
 TIMER1_BASE->CR1=0;
   TIMER1_BASE->CCMR2=0;TIMER1_BASE->PSC=0; TIMER1_BASE->CCR2=0; 
    TIMER1_BASE->CCER=(1<<0)|(1<<2)|(pwm2_polar<<3);//cc1e/cc1ne enable 
     //TIMER1_BASE->BDTR=(1<<15)| 255 ;// dead time sample
       TIMER1_BASE->CCMR1=(1<<6)|(1<<5)|(1<<3);//PWM mode 1
         if(freq < 84850){ //изменение частоты таймера по заданной частоте
          if (in) {freq+=(encstep*in);}//если передавалось изменение частоты, то рассчитать
           if (freq<1){freq=1;}  if (freq>(F_CPU/2*10)) {freq=F_CPU/2*10;}// ограничение макс. частоты *10
           tim_arr = F_CPU*10/freq; 
          divider=1; while ( (tim_arr/divider) > 65535) {divider++;} 
         TIMER1_BASE->PSC=divider-1;
        TIMER1_BASE->ARR=(tim_arr/divider)-1;
       } //end f (freq < 84850)
     else { // изменение частоты таймера инкрементом регистра ARR
    tim_arr=TIMER1_BASE->ARR; //снять тукущее состояния регистра     
   if (tim_arr<1000 && encstep > 1000) encstep=1000; // уменьшать шаг с ростом частоты
 if (tim_arr<100 && encstep > 100) encstep=100; // уменьшать шаг с ростом частоты
if (tim_arr<10 && encstep > 10) encstep=10;   // уменьшать шаг с ростом частоты    
in*=(encstep/10);
 tim_arr-=in;
  if (tim_arr<1) {tim_arr=1;} if (tim_arr>65535) {tim_arr=65535;}
   TIMER1_BASE->ARR=tim_arr;
    } // END  изменение частоты таймера инкрементом регистра ARR
     // установка заданного DUTY
      if(duty_in>99){duty_in=99;} if(duty_in<1){duty_in=1;} 
       if (mode==1 && TIMER1_BASE->ARR<100){ TIMER1_BASE->CCR1=(TIMER1_BASE->ARR+1)/2 ; duty_in=50;} //сбрасывать duty на 50% на высоких частотах 
        else{TIMER1_BASE->CCR1= (float) (TIMER1_BASE->ARR+1)* duty_in/100.0 ;} //или рассчитать 
         freq= F_CPU*10 /((TIMER1_BASE->ARR+1)*divider);// рассчёт фактической частоты
          duty_out=  (float) TIMER1_BASE->CCR1 / ((TIMER1_BASE->ARR+1) / 100.0) ; //расчёт фактического duty
          duty_out= floorf(duty_out); //округление
         imp_long=(uint32_t) ((TIMER1_BASE->ARR+1)*divider); //длина периода в тактах
       imp_hi=(uint32_t)  ((TIMER1_BASE->CCR1)*divider); // длина импульса в тактах
     t_low= (imp_long-imp_hi) /(F_CPU/1E6) ; //время LOW
   t_hi=  imp_hi /(F_CPU/1E6); //время HI
 TIMER1_BASE->CR1=1;
mon_flag=1;
}//end timer_set
 

// КОНФИГУРАЦИЯ DDS РЕЖИМОВ
void dds_set(){
 static byte oldmode=255;
   TIMER1_BASE->CCER=0; //timer output pins disable
    #if defined(STM32_HIGH_DENSITY) || defined(STM32_XL_DENSITY)
     rcc_clk_enable(RCC_DAC);
      rcc_reset_dev(RCC_DAC);
     gpio_set_mode(GPIOA, 4, GPIO_MODE_ANALOG);
    DAC->regs->CR = DAC_CR_BOFF1 | DAC_CR_EN1 ;
   #define DDS_OUT DAC->regs->DHR8R1
  #else 
#define DDS_OUT GPIOA_BASE->ODR
GPIOA_BASE->CRL = 0x33333333;// pa0-pa7  выход
  #endif 
     if (oldmode !=mode) {
      if (mode==4) {for(uint16_t n=0; n<512; n++){wave[n]=255*(sin(TWO_PI*float(n)/512)+1)/2 ;}}// синус
       else if (mode==5){ for(uint16_t n=0; n<512; n++){if (n<256){ wave[n]=n;} else {wave[n]=(511-n);}}}//треугол
      else if (mode==6){ for(uint16_t n=0; n<512; n++){ wave[n]=(n>>1);}}                               //пила1
     else if (mode==7){ for(uint16_t n=0; n<512; n++){ wave[n]=((~n)>>1);}}                            //пила2
    else if (mode==8){ for(uint16_t n=0; n<512; n++){if (n<256){ wave[n]=0;} else {wave[n]=255;}}}    //меандр
  oldmode=mode; } 
uint32_t dds_shag= (double)freq/10 * ((F_CPU==72E6)? dds_mpl_72 : dds_mpl_128) ;//  шаг= частота*коэффициент
asm volatile (
"mov   R9, %[port];"  "\n\t" // записать в r9 адресс порта "A"-ODR 
 "mov   R8, %[wave];"       "\n\t" //адресс массива положить в r8  
 "mov   R7, %[shag];"   "\n\t" // значение шага в r7
  "dds_loop:"                  "\n\t"
   "add R6, r7;"               "\n\t"  //(1)добавить к аккумулятору шаг
    "lsrs r2, r6, #23;"         "\n\t"  //(1) положить в R2 сдвинутый на 23 бита аккумулятор
   "ldrb r2, [r8, r2];"        "\n\t"  //(2)загрузить в R2 выбранный байт из массива
   "strb  r2, [r9];"           "\n\t"  //(2) запиcать этот байт в PORTA-ODR
  "ldr  R2, [%[flag]];"       "\n\t"  //(2) подгрузить в  R2 флаг 
 "cmp r2, 1;"                "\n\t"  //(1) сравнить 
"bne dds_loop;"              "\n\t"  //(1) перейти в цикл
: : [wave]"r" (&wave),[shag]"r"(dds_shag),[port]"r"(&DDS_OUT),[flag]"r"(&mon_flag)
: "r9","r8","r7","r6","r2" 
);
  #if defined (STM32_MEDIUM_DENSITY) 
  GPIOA_BASE->CRL=0x44444444;// все пины в Z для резисторного цап
 #endif
}//END DDS set()
//


  void mytone(int frq, int ms ){
 uint16_t psc=1; uint32_t tim_arr;
 // настройка генератора звука на таймере3
  tim_arr = (F_CPU/2)/frq;
  while ( (tim_arr/psc) > 65535) {psc++;} 
  __asm volatile( "cpsid i" ); 
   TIMER2_BASE->SMCR=0;
    TIMER3_BASE->CCR3=0; //обнулить регистр соответсвующий используемому выходу
     TIMER3_BASE->PSC=psc-1;
      TIMER3_BASE->ARR=(tim_arr/psc)-1;
       TIMER3_BASE->CCMR2=(1<<5)|(1<<4);// OC3M:011
        TIMER3_BASE->CCER=1<<8;//cc3e  подключить аппаратную ногу
         TIMER3_BASE->SMCR=(1<<2)|(1<<0)|(1<<4);//SMS:101 && TS:001  строб от 2-го таймера  
          TIMER3_BASE->EGR=1; //перечитать регистры.
           TIMER3_BASE->CR1=1;
           /// настройка выдержки времени на таймере2
          psc=1;
        tim_arr = (F_CPU/1E3) * ms;
       while ( (tim_arr/psc) > 65536) {psc++;} 
      TIMER2_BASE->CCMR2=0;
     TIMER2_BASE->CR2=0;
    TIMER2_BASE->CR1=(1<<3)|(1<<2);//один импульс, без прерываний
   TIMER2_BASE->CNT=0;
  TIMER2_BASE->CR2=(1<<4);  //MMS:001 сигнал разрешения работы другим таймерам
 TIMER2_BASE->PSC=psc-1;
TIMER2_BASE->ARR=(tim_arr/psc)-1;
TIMER2_BASE->EGR=1; //перечитать регистры.
TIMER2_BASE->SR=0;//отчистить флаги
TIMER2_BASE->CR1|=(1<<0);
  __asm volatile( "cpsie i" );
}

void clock_gen(){ //функция работа с синтезатором si5351
if (!si5351_found) {return;} // на всякий случай 
if (freq <4E4){freq=4E4;} if (freq>200E7){ freq=200E7;} //допустимые рамки частот
si5351.set_freq((freq/10)* SI5351_FREQ_MULT, SI5351_CLK0);
mon_flag=1;
}

void comm(){ //коммутация выходов через мультиплексор
if (mode>0 && mode<4 ) {digitalWrite(PB3,LOW); digitalWrite(PB4,LOW);}
  else  if (mode>3 && mode<9 ) {digitalWrite(PB3,HIGH); digitalWrite(PB4,LOW);}
  else if (mode==0 ) {digitalWrite(PB3,LOW); digitalWrite(PB4,HIGH);}
 else if (mode==9) {digitalWrite(PB3,HIGH); digitalWrite(PB4,HIGH);}
}

void volt_meter() {
static boolean alarm=0;
 uint64_t akkum=0;
 //измерение напряжения питания МК
  for (int n=0; n<=16383; n++ ) {//собирать 16384 выборок 
    akkum += sq(adc_read(ADC1,17)); } //суммировать квадраты
     akkum =  (sqrt(akkum>>14));
        Vcc = (VrefINT <<12) / akkum;
         //измерение напряжения на входе ADC9(PB1)
        akkum=0;
       for (int n=0; n<=16383; n++ ) {//собирать 16384 выборок 
      akkum += sq(adc_read(ADC1,9)); } //суммировать квадраты
     akkum =  (sqrt(akkum>>14));
   for (int n=0; n<=65535; n++ ) {asm volatile("nop"); } //Типа delay
  freq = (double) Mn * ((uint32_t)(akkum * Vcc *10)/4096);
   // если напряжение не удовлетворяет условиям, и буззер не работает -то включить
 if      (freq < Vin_low*10) { alarm =1;  if (TIMER2_BASE->SR!=0) {mytone (200, 10000);} }
  else if (freq >  Vin_hi*10) { alarm =1;  if  (TIMER2_BASE->SR!=0) {mytone (1000, 10000);} }
   else alarm=0;
    //если всё ок, а буззер работает, то выключить.
     if (!alarm && (!TIMER2_BASE->SR) ) {mytone (0, 0);}
  }

///////////////////// ФУНКЦИЯ ОДНОВИБРАТОРА///////////////////////
void impuls(){ 
uint32_t tim_arr=0, div_imp_mode=0; 
pinMode(PA7,PWM); pinMode(PA8,PWM);
__asm volatile( "cpsid i" );
 RCC_BASE->APB2RSTR |= 1<<11; //reset timer 1
  RCC_BASE->APB2RSTR&= ~(1<<11); 
   TIMER1_BASE->BDTR= 1<<14; //Automatic output enable
    TIMER1_BASE->CCMR1=(7<<4);//PWM2 Mode
     TIMER1_BASE->CCER=(1<<0)|(1<<2)|(pwm2_polar<<3);//cc1e/cc1ne enable 
      TIMER1_BASE->CCR1=1; // поднять ногу на этом такте
      TIMER1_BASE->CR1=(1<<3);//режим "один импульс"     
   if (imp_mode<2){ // кроме такта
  switch(imp_mode){// 0-мс, 1-мкс, 2 -такт
 case 0:  div_imp_mode=1000;  break; // делитель для миллисекунд
case 1:  div_imp_mode=1000000;  break;// делитель для микросекунд
 }
 tim_arr =  (freq/10) * (uint32_t)(F_CPU/div_imp_mode) ; 
  divider=1; 
   while ( (tim_arr/divider) > 65535) {divider++;} 
    TIMER1_BASE->PSC=divider-1; // Прескалер
     TIMER1_BASE->ARR=(tim_arr/divider)-1; //длина импульса
      TIMER1_BASE->CCR4= TIMER1_BASE->ARR;//повесить флаг по окончании импульса
      }
      else {  TIMER1_BASE->ARR = (freq/10);} //если imp_mode == такт,
      TIMER1_BASE->EGR=1; 
      mytone(2200,100);
     TIMER1_BASE->CR1|=(1<<0);
  while( (TIMER1_BASE->SR&(1<<4))==0 )   ;//ждать подождать пока закончится импульс
  __asm volatile( "cpsie i" );
}

Так как собрать все библиотеки и успешно скомпмилировать дело не такое уж простое,  выкладываю свою папку Arduino IDE (portable версия) со  всеми нужными библиотеками и скетчем генератора в примерах.   Архив 7zip объёмом 468MB

Убедительная просьба не использовать кнопку "Цитировать" под этим постом, т.к. у меня пропадёт возможность редактировать ошибки, да и просто засирать тему повторной информацией не стоит.

240265
240265 аватар
Offline
Зарегистрирован: 12.08.2015

 

Генератор ОТЛИЧНЫЙ

Спасибо за ГОТОВЫЕ РЕШЕНИЯ  в архиве , я туда  в прошлой версии прикрутил еще и ЕСП8266 и ЕСП32 очень удобно. Сам бы до ишачей ПАСХИ собирался создавать.

Dmitriy424
Offline
Зарегистрирован: 26.08.2019

Спасибо dimax за обновленную версию ПО.

Dmitriy424
Offline
Зарегистрирован: 26.08.2019

kloker, про R2R в данном исполнении - наводок не видно. Если есть возможность делать на 805 SMD - делайте на них. У меня просто их не было и мне пришлось тщательно подбирать (чтобы добиться минимального разброса) из кучи выводных килоомных.