Продолжил издевательства над 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 Десятые герца мне не нужны, а вот разделение пробелами или точками надо будет сделать.
MAG-N, всё верно, именно из-за предела int32 я уменьшил верхнюю частоту, так как переход с int32 на uint32 или на int64 тянул за собой огромные переделки из-за такого пустяка. PS: Разница между 3.1 и 3.2 довольно существенна в плане улучшения кода. Особенно в частотометре. Даже если выдрать только вывод на дисплей всё равно придёться повозиться, там же всё "заточено" под вывод удесятерённого значения. Так что проще смирится :)
Нацарапал утилитку для калибровки Si5351. Работает на "железе" V3.2, частота 10 МГц, но можно подставить любую другую. Работа с энкодером "сплагиачена" из скетча генератора (может чего лишнего навыдёргивал). В общем, работает и ладно. Cкетч генератора надо дополнить строкой (после строки 45), и вместо 80000 ввести свою константу (Cal.factor).
Чем измерять частоту - это у кого что есть. Я, за неимением частотомера, использовал трансивер и пограмму WSJT-X. На частоте 10 МГц работают две станции эталонных частот из Москвы и Новосибирска, поэтому и выбрал её для калибровки. Настраиваемся, находим эталонную и свою частоты. Далее всё очень просто: энкодер по часовой стрелке - частота увеличивается, а значение Cal.factor будет отрицательным. Против часовой - частота уменьшается, Cal.factor положительный. Нажатие на кнопку энкодера - шаг перестройки. При шаге 1Гц меняется разряд сотен, а десятки и единицы - это для шага 0.1 и 0.01 Гц - я их не делал, нафиг не надо. У меня Cal.factor получился 79600, т.е. сдвиг частоты вниз на 796 Гц. После точной настройки полученное значение Cal.factor вписываем в скетч и радуемся.
Спасибо большое dimax за генератор. Очень хороший получился аппарат.
Я сделал два генератора что бы через смеситель получить пакеты импульсов.
Все отлично работает, но как говорится аппетит приходит во время еды. В пакетах нет сихронизации по фазе ну и конечно их качает. Малость не приятно, но не смертельно. Большое Спасибо.
А я не стал связываться с мультиплексором. Поставил дешифратор 74138 и к нему 4 реле, они пятивольтовые с током потребления около 10 мА. Если применить 74145, у которого ток выхода до 80 мА и допустимое напряжение на выходе 15 В - можно использовать 9-12 вольтовые реле с бОльшим током срабатывания.
Выложил полный архив с Arduino IDE и всеми свежими библиотеками и скетчем версии 3.2 в сообщении #50 Просто разархивировать и выбрать скетч в примерах. Кстати там чуть свежее версия скетча, но принципиальных отличий от ранее выложенной нет.
Загрузка прошла и сразу после нее появилось сообщение в трее что подключено неопознанное устройство. Теперь стмку комп не видит.
Именно так и должно быть, в сетапе ведь отключается виртуальный СОМ порт. Повторная перепрошивка возможна нажатием на ресет в нужный момент, или просто отлюченную плату включить в USB порт в нужный момент.
Похвастаюсь :-) Мой вариант генератора. Снимал на телефон - ну не умеет он с близкого расстояния снимать, так что извините за качество. Это называется оптическая аберрация.
Самое главное - работает, и очень хорошо работает. Clock Gen настроил по эталонной частоте, потом по этому генератору настроил частотомер (на PIC-e), затем пересчитал "множитель DDS для частоты F_CPU 72МГц" - и погрешность установки частоты в этом режиме стала почти "нулевая" :-) В режиме PWM на частоте 36 МГц ошибка 1200 герц - но тут тяжело исправить, надо конденсаторы, которые возле кварца, подгонять - ну его нафиг, и так хорошо. Непонятно одно - почему частота скачет на несколько десятков герц туда-сюда, причём только в режиме PWM. Вот картинка сигнала 36000000. Цена деления - 50 Гц.
А это сигнал Si5351, трансивер на частоте 35.999 - биения 1 кГц
MAG-N, думаю, что виноват PLL синтезатор в МК. В Si5351 тоже PLL, но он всё таки задумывался изначально как тактовый генератор, и у него параметры лучше.
Наверное, так как на частоте 9 МГц та же пила, но размах раз в пять-шесть меньше, как и отклонение. На глаз уже не очень заметно, а ухо чует. Да и не очень то надо на таких частотах PWM гонять, ну мегагерц-два ещё может и понадобится, а дальше Х его З. Самая высокая частота PWM в моих "самоделках" - 31 кГц для управления вентилятором в усилителе на 144 МГц. На звуковых частотах и на малых оборотах эта зараза работает скорее динамиком, чем вентилятором. Зудит, как комар.
Ещё похвастаюсь: с помощью данного генератора поднастроил частотомер (на частоте 200 МГц)- и вот результат - на проводок длиной около 10 см сигнал радиостанции ловит с расстояния 3-4 метра. (435 МГц)
Версия 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-й таймер, можно использовать для чего-то ещё. Появилась возможность переносить ноги энкодера на другие пины.
Просьба не нажимать кнопку "цитировать" что-бы осталась возможность редактировать этот пост.
To dimax: Если будет желание ещё что-нибудь подправить, есть предложение внести некоторые "косметические" изменения:
Ввести шаг 10Е7 - по одному мегагерцу щёлкать до 200 долго, проще по 10.
Перенести кнопку на порт РВ5 - этот пин рядом с пинами контактов энкодера, а не по диагонали, как РВ1. Печатку разводить удобнее.
Может, конечно, я со своей колокольни мыслю, но себе сделал, глядишь и ещё кому пригодится.
На самом деле, изменений побольше, но это так, чтобы глазу приятнее было - запятые на точки заменил, шрифт для всех частот выставил 2, убрал "голубенький" фронт с прямоугольника ну и т.д. В посте #62 это видно.
Вот как-то так.
Частота 9 МГц
Ну а вот генератор на 9833, который без резисторов-конденсаторов для энкодера.
Дисплейчик здесь хорош - полноцветный 160х80 IPS, но размер 0.96
dimax, у меня к Вам просьба-предложение. Тема с Вашими генераторами, как видим, очень популярная. И попурна она весьма справедливо, конструкции получились удачные (от меня тоже большое Вам спасибо за них), и они активно обсуждаются в теме. Но, к сожалению, большинство сообщений удаляется. Это правильно, нечего замусоривать тему, но уж очень радикально.
Так вот насчет просьбы. Не могли бы Вы открыть отдельную тему (тут же в "Проектах" или в "Общем"), скажем, "Обсуждение генератора с регулируемоей частотой на ардуино от dimax", в первом сообщении той темы так и написать: "Здесь ведется обсуждение, задаются вопросы по теме Генератор с регулируемоей частотой на ардуино". А в этой теме написать в последнем сообщении что-типа "Обсуждение и вопросы по сабжу - в теме Обсуждение генератора с регулируемоей частотой на ардуино от dimax (со ссылкой)".
Таким образом и эта тема не будет замусориваться, и общение сохранится. Если что-то полезное и достойное Вашего внимания появится в той теме, то нужное сообщение можно будет скопировать и сюда.
Версия 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, у меня было 2 цели- (1) адекватно измерять пульсирующие однополярные сигналы (2) и в целях шумопонижения, -тут проблема в том, что АЦП этого мк f103c8t6 умеет измерять аналоговый сигнал только относительно шины Vcc, на которой стоит собственный шум амплитудой до 70мВ(конкретно на своей плате измерял). Поэтому было бы неприятно, измеряя совершенно бесшумный источник, например аккумулятор, получить скачки измерений +/- 35мВ. А с среднекватическим счётом получается довольно устойчивые измерения :) Хотя в дополнении к (1) -тут тоже есть свои нюансы. Например если измерять 5-вольтовый 50% ШИМ, то ср.квд. напряжение будет 3,5 вольта. А просто среднее 2,5в. Т.е. в идеале можно было бы выводить на дисплей и обычное среднее значение, ну или переключать их. Но что-то меня заломало это делать..
Парни, подскажите :( собрал генератор версию 2.4, все запустилось, сигнал выдает, но.....пила 1 , 2 и меандр искаженые, синус и треугольник нормальной формы, его поправил в скетче изменением массива, как советовал dimax. Как можно это поправить?? Arduino пока изучаю
Хотя в дополнении к (1) -тут тоже есть свои нюансы. Например если измерять 5-вольтовый 50% ШИМ, то ср.квд. напряжение будет 3,5 вольта. А просто среднее 2,5в.
dimax. а какой физический смысл в среднеквадратичном напряжении?
b707, ну как.. узнать реальное действующее напряжение, актуально когда измеряешь что-то пульсирующее, типа шим-питания светодиодов. А на чистом постоянном напряжении что ср.квд, что среднее -покажут одно и тоже.
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, скомпилил скетч вер.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. И плата зарядки.
Получилось очень компактно. Корпус использовал от усилителя для антены телевидения.
Автору Огромное спасибо! И респект. В моей работе (КИП связи на Ж.Д) очень пригодится, особенно для ВЧ аппаратуры и не только.
Наконец руки дошли свой вариант собрать... 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 (не сбрасываются при переключениях - бонусом работает частотомер из гнезда в гнездо)
dima2019, спасибо за пруф. Про этот прескалер я знал, но не попадалась информация что он асинхронный. Попробывал включить биты прескалера в функции частотометра (в генераторе v3.4), подал 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.
5351 всёже синтезатор, потому и сигнал "получшее" :-) Да и не очень нужна ШИМ на частотах выше пары мегагерц. А если 5351 на 145 МГц настроить, то третья гармоника (435) очень даже прилично смотрится.
Ну просядет питание при разряде аккума ниже 3.3 - ну и фиг с ним, значит "покормить" пора.
тогда модуль на TP4056 с функцией защиты от глубокого разряда ставьте, чтобы еще и фаершоу не получить. STM и до 2.5 В вполне себе работает, дисплей возможно и заткнется, не проверял, потому как не купил еще.
kloker, я по картинке MAG_N из #156 не очень понимаю как воспринимать этот спектр. По идее джиттер это дрожание фазы а не частоты. Вот например в обзоре мк lgt8f328 на первом скришоте джиттер так джиттер.
На этих картинках ещё горизонтальные полоски есть - между ними 10 секунд, а эта "пила" даже ухом чуется, чистый тон в килогерц - он и есть чистый, а тут эдакое "комариное жужжание", и на блоки питания грешил, и прочее, но вот что есть, то есть. Это скорее всего встроенный в контроллер ПЛЛ колбасит. Ещё раз доложусь, что в "нормальных режимах" :-) ДДС и 5351 работает отменно, да и в ШИМ на частотах до 1-2 МГц тоже. Прибор работает очень даже... Забейте на эти запредельные сигналы. Это был скорее "академический интерес".
Версия 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
Убедительная просьба не использовать кнопку "Цитировать" под этим постом, т.к. у меня пропадёт возможность редактировать ошибки, да и просто засирать тему повторной информацией не стоит.
Спасибо за ГОТОВЫЕ РЕШЕНИЯ в архиве , я туда в прошлой версии прикрутил еще и ЕСП8266 и ЕСП32 очень удобно. Сам бы до ишачей ПАСХИ собирался создавать.
kloker, как написать вывод я знаю. Подумать надо что можно сделать полезного на этой основе, и главное потом этот код встроить малой кровью в мою программу :)
Вот уже накатал примерчик: ГКЧ 20 Гц..20кГц, (Для частот >20кГц придёться уменьшать массив). В лупе меняется тактовая с таймера, и что-то типа примитивной экспоненты что-б на НЧ подолгу не зависало. Вывод через DMA в GPIOA для резисторного цапа.
Но встроить это в программу легко при условии что никакие параметры не будут регулироваться. Иначе куча рассчётов.. Можно сделать ещё лучше - избавится от лупа и сделать ещё один массив ARR-значений для таймера, тактирующего вывод через DMA. Для этого понадобится ещё один таймер, в общем функция разрастётся в длинную портянку вычислений. Заниматься этой рутиной вообще не тянет (=
Ещё рождается новый вариант синтеза dds -можно тактировать DMA через таймер от внешнего сигнала с Si5351 Появиться преимущество - сильно снизиться джиттер, который довольно ощутимый есть в том алгоритме, что я использую сейчас. И за счёт линейности генерирования тактовой от si5351 можно будет получить довольно линейную регулировку выходной частоты dds , но есть и минусы -возникнет необходимость пересчитывать массив для разных диапазонов частот, ( с объёмом массива 256 байт как в примере выше в принципе нельзя получить частоты выше ~23 кГц (=samplerate 6MHz/256) ) Правда тут ещё сложность -все входы тактирования заняты (A0,А12,А15).. разве что как то перехватывать вход частотометра А15. Но мудрить с переключением тоже не лучший выход.
Продолжил издевательства над 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 Десятые герца мне не нужны, а вот разделение пробелами или точками надо будет сделать.
MAG-N, всё верно, именно из-за предела int32 я уменьшил верхнюю частоту, так как переход с int32 на uint32 или на int64 тянул за собой огромные переделки из-за такого пустяка. PS: Разница между 3.1 и 3.2 довольно существенна в плане улучшения кода. Особенно в частотометре. Даже если выдрать только вывод на дисплей всё равно придёться повозиться, там же всё "заточено" под вывод удесятерённого значения. Так что проще смирится :)
Нацарапал утилитку для калибровки Si5351. Работает на "железе" V3.2, частота 10 МГц, но можно подставить любую другую. Работа с энкодером "сплагиачена" из скетча генератора (может чего лишнего навыдёргивал). В общем, работает и ладно. Cкетч генератора надо дополнить строкой (после строки 45), и вместо 80000 ввести свою константу (Cal.factor).
si5351.set_correction(80000, SI5351_PLL_INPUT_XO);
И какова процедура калибровки?
Чем измерять частоту - это у кого что есть. Я, за неимением частотомера, использовал трансивер и пограмму WSJT-X. На частоте 10 МГц работают две станции эталонных частот из Москвы и Новосибирска, поэтому и выбрал её для калибровки. Настраиваемся, находим эталонную и свою частоты. Далее всё очень просто: энкодер по часовой стрелке - частота увеличивается, а значение Cal.factor будет отрицательным. Против часовой - частота уменьшается, Cal.factor положительный. Нажатие на кнопку энкодера - шаг перестройки. При шаге 1Гц меняется разряд сотен, а десятки и единицы - это для шага 0.1 и 0.01 Гц - я их не делал, нафиг не надо. У меня Cal.factor получился 79600, т.е. сдвиг частоты вниз на 796 Гц. После точной настройки полученное значение Cal.factor вписываем в скетч и радуемся.
Спасибо большое dimax за генератор. Очень хороший получился аппарат.
Я сделал два генератора что бы через смеситель получить пакеты импульсов.
Все отлично работает, но как говорится аппетит приходит во время еды. В пакетах нет сихронизации по фазе ну и конечно их качает. Малость не приятно, но не смертельно. Большое Спасибо.
Моя плата V3.2.
А я не стал связываться с мультиплексором. Поставил дешифратор 74138 и к нему 4 реле, они пятивольтовые с током потребления около 10 мА. Если применить 74145, у которого ток выхода до 80 мА и допустимое напряжение на выходе 15 В - можно использовать 9-12 вольтовые реле с бОльшим током срабатывания.
Выложил полный архив с Arduino IDE и всеми свежими библиотеками и скетчем версии 3.2 в сообщении #50 Просто разархивировать и выбрать скетч в примерах. Кстати там чуть свежее версия скетча, но принципиальных отличий от ранее выложенной нет.
Загрузка прошла и сразу после нее появилось сообщение в трее что подключено неопознанное устройство. Теперь стмку комп не видит.
Именно так и должно быть, в сетапе ведь отключается виртуальный СОМ порт. Повторная перепрошивка возможна нажатием на ресет в нужный момент, или просто отлюченную плату включить в USB порт в нужный момент.
Похвастаюсь :-) Мой вариант генератора. Снимал на телефон - ну не умеет он с близкого расстояния снимать, так что извините за качество. Это называется оптическая аберрация.
Самое главное - работает, и очень хорошо работает. Clock Gen настроил по эталонной частоте, потом по этому генератору настроил частотомер (на PIC-e), затем пересчитал "множитель DDS для частоты F_CPU 72МГц" - и погрешность установки частоты в этом режиме стала почти "нулевая" :-) В режиме PWM на частоте 36 МГц ошибка 1200 герц - но тут тяжело исправить, надо конденсаторы, которые возле кварца, подгонять - ну его нафиг, и так хорошо. Непонятно одно - почему частота скачет на несколько десятков герц туда-сюда, причём только в режиме PWM. Вот картинка сигнала 36000000. Цена деления - 50 Гц.
А это сигнал Si5351, трансивер на частоте 35.999 - биения 1 кГц
MAG-N, думаю, что виноват PLL синтезатор в МК. В Si5351 тоже PLL, но он всё таки задумывался изначально как тактовый генератор, и у него параметры лучше.
Наверное, так как на частоте 9 МГц та же пила, но размах раз в пять-шесть меньше, как и отклонение. На глаз уже не очень заметно, а ухо чует. Да и не очень то надо на таких частотах PWM гонять, ну мегагерц-два ещё может и понадобится, а дальше Х его З. Самая высокая частота PWM в моих "самоделках" - 31 кГц для управления вентилятором в усилителе на 144 МГц. На звуковых частотах и на малых оборотах эта зараза работает скорее динамиком, чем вентилятором. Зудит, как комар.
Ещё похвастаюсь: с помощью данного генератора поднастроил частотомер (на частоте 200 МГц)- и вот результат - на проводок длиной около 10 см сигнал радиостанции ловит с расстояния 3-4 метра. (435 МГц)
Выкладываю свою печатку в LAY,вариант на голой атмега328р,весия 2.4,в железе проверена
Предусмотрена зарядка на LTC4054 аккумулятора от сотового и USB разъем для зарядки
https://yadi.sk/d/oF1lFfxWapnwxQ
Спасибо!
в ардуино 1.8.8 не мог победить расположение массивов,в 1.6.5 все норм но убрал 6 "0х00"
Также энкодер наоборот (исправляется программно)
собрал генератор, версия 2.4 с дисплеем 5110, и небольшими доработками
надписи пока не делал
внутри
Версия 3.3
-изменён алгоритм обработчика энкодера и обработчика кнопки энкодера, за счёт этого больше не требуются дребезгоподавляющие конденсаторы.
-побочный бонус за счёт первого пункта -управление частотой стало более быстрым, уменьшилось время отклика при вращении энкодера.
-добавлена поддержка микроконтроллеров с интегрированным ЦАП (этой же серии F103), для них не нужно собирать резисторную матрицу, но О.У. на выходе крайне желателен (пример в Application note AN4566) Проверил работу на MK stm32f103ret6, не тестировал.
Скетч:
Схема (кликабельно) та-же, только нет конденсаторов и подтяжек, всё подтянуто программно. Энкодер обязательно типа EC-11, буззер обязательно пассивный. Всё остальное как прежде. Мультиплексор ADG704 и тактовый генератор Si5351 устанавливаются опционально по желанию. Освободился 4-й таймер, можно использовать для чего-то ещё. Появилась возможность переносить ноги энкодера на другие пины.
Просьба не нажимать кнопку "цитировать" что-бы осталась возможность редактировать этот пост.
To dimax: Если будет желание ещё что-нибудь подправить, есть предложение внести некоторые "косметические" изменения:
Ввести шаг 10Е7 - по одному мегагерцу щёлкать до 200 долго, проще по 10.
Перенести кнопку на порт РВ5 - этот пин рядом с пинами контактов энкодера, а не по диагонали, как РВ1. Печатку разводить удобнее.
Может, конечно, я со своей колокольни мыслю, но себе сделал, глядишь и ещё кому пригодится.
На самом деле, изменений побольше, но это так, чтобы глазу приятнее было - запятые на точки заменил, шрифт для всех частот выставил 2, убрал "голубенький" фронт с прямоугольника ну и т.д. В посте #62 это видно.
Вот как-то так.
Частота 9 МГц
Ну а вот генератор на 9833, который без резисторов-конденсаторов для энкодера.
Дисплейчик здесь хорош - полноцветный 160х80 IPS, но размер 0.96
73!
dimax, у меня к Вам просьба-предложение. Тема с Вашими генераторами, как видим, очень популярная. И попурна она весьма справедливо, конструкции получились удачные (от меня тоже большое Вам спасибо за них), и они активно обсуждаются в теме. Но, к сожалению, большинство сообщений удаляется. Это правильно, нечего замусоривать тему, но уж очень радикально.
Так вот насчет просьбы. Не могли бы Вы открыть отдельную тему (тут же в "Проектах" или в "Общем"), скажем, "Обсуждение генератора с регулируемоей частотой на ардуино от dimax", в первом сообщении той темы так и написать: "Здесь ведется обсуждение, задаются вопросы по теме Генератор с регулируемоей частотой на ардуино". А в этой теме написать в последнем сообщении что-типа "Обсуждение и вопросы по сабжу - в теме Обсуждение генератора с регулируемоей частотой на ардуино от dimax (со ссылкой)".
Таким образом и эта тема не будет замусориваться, и общение сохранится. Если что-то полезное и достойное Вашего внимания появится в той теме, то нужное сообщение можно будет скопировать и сюда.
vk007, такую тему недавно на радиокоте создали, можно там пообсуждать.
Версия 3.4, изменения:
-добавлен шаг 10MHz для режима ClockGen.
-пин дисплея CS посажен на землю, вывод PB12 освободился.
-кнопку энкодера перекинул с PB1 на PB5.
-Добавил режим вольтметра (среднеквадратичные вычисления).
В режиме вольтметра дополнительно отображается внутреннее напряжение питания МК, так что если сделать автономное питание - например запитать напрямую от аккумулятора lifepo4, то можно будет следить за уровнем напряжения АКБ. Так же отображается внешнее напряжение, измеренное с пина PB1(ADC9). Референс берётся от предыдущего измерения Vcc, таким образом при любом допустимом напряжении Vcc измеренные значения должны отображаться верно. Так же есть два настраиваемых предела напряжения -низкий и высокий. При превышении допустимого предела раздатся писк соответственно низкого или высокого тона. Удобно, если нужно следить за каким-то процессом, например за разрядом/зарядом аккумулятора. Как только напряжение выйдет за рамки прибор запищит, чем привлечёт внимание. При номиналах делителя как на схеме максимальное напряжение 20 вольт.
Управление вольтметром- при вращении ручки энкодера изменяется активный предел напряжения. Выбор шага так-же работает обычным способом. Активный предел выбирается нажатием кнопки и поворотом ручки энкодера вправо. Активный предел отображается жёлтым цветом.
Все рисунки кликабельны.
Обновлённая схема:
Скетч:
Убедительная просьба не нажимать кнопку цитировать!
А зачем ADC1,17 среднеквадратичное вычислять, там же постоянка ?
240265, у меня было 2 цели- (1) адекватно измерять пульсирующие однополярные сигналы (2) и в целях шумопонижения, -тут проблема в том, что АЦП этого мк f103c8t6 умеет измерять аналоговый сигнал только относительно шины Vcc, на которой стоит собственный шум амплитудой до 70мВ(конкретно на своей плате измерял). Поэтому было бы неприятно, измеряя совершенно бесшумный источник, например аккумулятор, получить скачки измерений +/- 35мВ. А с среднекватическим счётом получается довольно устойчивые измерения :) Хотя в дополнении к (1) -тут тоже есть свои нюансы. Например если измерять 5-вольтовый 50% ШИМ, то ср.квд. напряжение будет 3,5 вольта. А просто среднее 2,5в. Т.е. в идеале можно было бы выводить на дисплей и обычное среднее значение, ну или переключать их. Но что-то меня заломало это делать..
Парни, подскажите :( собрал генератор версию 2.4, все запустилось, сигнал выдает, но.....пила 1 , 2 и меандр искаженые, синус и треугольник нормальной формы, его поправил в скетче изменением массива, как советовал dimax. Как можно это поправить?? Arduino пока изучаю
AnatolyDem, думаю самый простой способ -использовать рекомендованную версию Arduino IDE 1.6.8.
Хотя в дополнении к (1) -тут тоже есть свои нюансы. Например если измерять 5-вольтовый 50% ШИМ, то ср.квд. напряжение будет 3,5 вольта. А просто среднее 2,5в.
dimax. а какой физический смысл в среднеквадратичном напряжении?
b707, ну как.. узнать реальное действующее напряжение, актуально когда измеряешь что-то пульсирующее, типа шим-питания светодиодов. А на чистом постоянном напряжении что ср.квд, что среднее -покажут одно и тоже.
b707, ну как.. узнать реальное действующее напряжение, актуально когда измеряешь что-то пульсирующее, типа шим-питания светодиодов.
dimax, я наверно глупость спрошу - но разве у прямоугольного сигнала действующее напряжение не равно среднему?
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, скомпилил скетч вер.2.4 в ардуино Ide версии 1.6.8 - все массивы встали точно как надо с адреса 0x100 Сознайтесь, вы же компилировали не в той версии, что я рекомендовал в #11?
Для версии ArduinoIDE 1.8.9 musor_mass должен быть таким:
и тоже все массивы встают как надо с адреса 0x500
Спасибо Огромное! Все получилось! Вот мой вариант вашего прибора. И проверка на осцилографе. Все так, как у вас в описании. На 100 КГц, небольшие искажения синуса. Все отлично. Вот фото отчет.
Уровни генератора:
Синус= +7db (+/-1)
Меандр, PWM = +11db
Треугольник, Пила= +5db
Измеритель частоты не видит частоту уровня близкую у 0 db. на уровне +23 db видит, измеряет точно.
На выход вольтметра подключил резистор 10К, чтобы не показывал наводки без подключения, на точность измерений не влияет.
Второй пин Ain5 подключил к Литию, для мониторинга батареи и переименовал.
Питание через литий, с преобразователем напряжения MT3608. И плата зарядки.
Получилось очень компактно. Корпус использовал от усилителя для антены телевидения.
Автору Огромное спасибо! И респект. В моей работе (КИП связи на Ж.Д) очень пригодится, особенно для ВЧ аппаратуры и не только.
Наконец руки дошли свой вариант собрать... 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
На stm32F103 можно измерять частоту до 190 мегагерц. Правда не знаю получится ли это реализовать в IDE Arduino.
dima2019, к чему эта фраза была? Если у вас есть методика как без внешних делителей работать с частотами выше половины тактовой -то ознакомьте.
Да никакой методики, просто использование возможностей МК. У stm32 есть асинхронный делитель.
https://istarik.ru/blog/stm32/124.html
dima2019, спасибо за пруф. Про этот прескалер я знал, но не попадалась информация что он асинхронный. Попробывал включить биты прескалера в функции частотометра (в генераторе v3.4), подал 180МГц на вход -измеряет :)
"подал 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
Спасибо Вам dimax за инресный проект генератора и предоставленные необходимые материалы (схема, прошивка, пояснения...).
Собранный генератор (Версия 3.4):
А вот так спектр сигнала выглядит - первая картинка PWM, вторая 5351. Частота 36 МГц, одно деление 50 Гц.
..А вот так спектр сигнала выглядит - первая картинка PWM, вторая 5351...
5351 всёже синтезатор, потому и сигнал "получшее" :-) Да и не очень нужна ШИМ на частотах выше пары мегагерц. А если 5351 на 145 МГц настроить, то третья гармоника (435) очень даже прилично смотрится.
я дико извиняюсь, PWM на графике с джиттером, или софтовый DDS на R2R матрице, я что-то пропустил?
kloker, о каком графике речь? В принципе при PWM генерации таймером существенного джиттера нет, этим страдает только DDS.
Ну просядет питание при разряде аккума ниже 3.3 - ну и фиг с ним, значит "покормить" пора.
тогда модуль на TP4056 с функцией защиты от глубокого разряда ставьте, чтобы еще и фаершоу не получить. STM и до 2.5 В вполне себе работает, дисплей возможно и заткнется, не проверял, потому как не купил еще.
kloker, о каком графике речь? В принципе при PWM генерации таймером существенного джиттера нет, этим страдает только DDS.
я про график из комента #156. не знал что и PWM в генераторе есть. на выходных попытаюсь на OLED переобуться, дефолтного дисплея так и не купил.
с таймером да, по кольцу бегает, без разрывов и перекуров.
kloker, я по картинке MAG_N из #156 не очень понимаю как воспринимать этот спектр. По идее джиттер это дрожание фазы а не частоты. Вот например в обзоре мк lgt8f328 на первом скришоте джиттер так джиттер.
На этих картинках ещё горизонтальные полоски есть - между ними 10 секунд, а эта "пила" даже ухом чуется, чистый тон в килогерц - он и есть чистый, а тут эдакое "комариное жужжание", и на блоки питания грешил, и прочее, но вот что есть, то есть. Это скорее всего встроенный в контроллер ПЛЛ колбасит. Ещё раз доложусь, что в "нормальных режимах" :-) ДДС и 5351 работает отменно, да и в ШИМ на частотах до 1-2 МГц тоже. Прибор работает очень даже... Забейте на эти запредельные сигналы. Это был скорее "академический интерес".
Версия 3.5.
- обновлён режим частотометра. Теперь максимальная измеряемая частота не менее 180МГц (при F_CPU 72MHz) Предусмотрена возможность включать и отключать внутренний делитель (на 8) входного сигнала. Переключается диапазон нажатием кнопки энкодера.На дисплее соответственно отоборажается предел 32 или 190 МГц. Эти цифры примерные, точно измерить пределы пока нет возможности. Нужно иметь ввиду, что работа с высокими частотами требует качественного входящего ТТЛ сигнала. Поэтому для высоких частот крайне желательно на входе использовать усилитель-формирователь сигнала. Также высокочастотный сигнал нельзя подавать через мультиплексор, с большой вероятностью он исказится, потеряет форму меандра, и наберёт некоторое смещение от ноля к питанию. Я сам не делал усилитель, для проверки максимума подключал выход тактового генератора Si5351 короткими проводами напрямую к входу PA15 МК , при этом частоты до 180 МГц отображались достаточно верно.
-добавлен режим одновибратора (генератор одиночного импульса). Есть выбор из трёх диапазонов - "такты МК" , "Микросекунды" и "Миллисекунды". Кнопка энкодера переключает регулируемый параметр (значение/диапазон/шаг/запуск), который подсвечивается белым фоном. Выбранный параметр регулируется ручкой энкодера. При выборе меню "Запуск" вращение энкодера вызывает одиночные импульсы выбранной длины. Минимальный импульс настройки одновибратора равен длительности 1 такта МК , или 13,8 nS при F_CPU 72MHz Для чего может пригодится этот режим : Например проверить свой осциллограф, как хорошо он хватает одиночные импульсы, и какой минимальный импульс ему под силу поймать. Или импульс какой минимальной длины может поймать МК внешним прерыванием. Или просто если в работе понадобится импульс определённой длины или импульс "без дребезга". В общем мне как-то понадобилось, и вот наконец дошли руки сделать. Комплементарный инверсный выход PA7 в режиме одновибратора так же работает.
На фото: дисплей на функции одновибратора, "пойманный" импульс длиной 13nS осциллографом.
Актуальная схема генератора:
Скетч:
Так как собрать все библиотеки и успешно скомпмилировать дело не такое уж простое, выкладываю свою папку Arduino IDE (portable версия) со всеми нужными библиотеками и скетчем генератора в примерах. Архив 7zip объёмом 468MB
Убедительная просьба не использовать кнопку "Цитировать" под этим постом, т.к. у меня пропадёт возможность редактировать ошибки, да и просто засирать тему повторной информацией не стоит.
Генератор ОТЛИЧНЫЙ
Спасибо за ГОТОВЫЕ РЕШЕНИЯ в архиве , я туда в прошлой версии прикрутил еще и ЕСП8266 и ЕСП32 очень удобно. Сам бы до ишачей ПАСХИ собирался создавать.
Спасибо dimax за обновленную версию ПО.
kloker, как написать вывод я знаю. Подумать надо что можно сделать полезного на этой основе, и главное потом этот код встроить малой кровью в мою программу :)
Вот уже накатал примерчик: ГКЧ 20 Гц..20кГц, (Для частот >20кГц придёться уменьшать массив). В лупе меняется тактовая с таймера, и что-то типа примитивной экспоненты что-б на НЧ подолгу не зависало. Вывод через DMA в GPIOA для резисторного цапа.
Но встроить это в программу легко при условии что никакие параметры не будут регулироваться. Иначе куча рассчётов.. Можно сделать ещё лучше - избавится от лупа и сделать ещё один массив ARR-значений для таймера, тактирующего вывод через DMA. Для этого понадобится ещё один таймер, в общем функция разрастётся в длинную портянку вычислений. Заниматься этой рутиной вообще не тянет (=
Ещё рождается новый вариант синтеза dds -можно тактировать DMA через таймер от внешнего сигнала с Si5351 Появиться преимущество - сильно снизиться джиттер, который довольно ощутимый есть в том алгоритме, что я использую сейчас. И за счёт линейности генерирования тактовой от si5351 можно будет получить довольно линейную регулировку выходной частоты dds , но есть и минусы -возникнет необходимость пересчитывать массив для разных диапазонов частот, ( с объёмом массива 256 байт как в примере выше в принципе нельзя получить частоты выше ~23 кГц (=samplerate 6MHz/256) ) Правда тут ещё сложность -все входы тактирования заняты (A0,А12,А15).. разве что как то перехватывать вход частотометра А15. Но мудрить с переключением тоже не лучший выход.
В общем вот такие вот думки..