Генератор с регулируемоей частотой на ардуино.
- Войдите на сайт для отправки комментариев
Иногда при работе с мк бывает нужно подать сигнал определённой частоты, а специального устройства у меня нет, т.к. не сильно то оно нужно, да и ставить ещё один ящик негде. Вот написал скетчик генератора с регулируемой частотой, в большинстве случаев его достаточно. Что он может: -генерит меандр на 16 битном таймере. Диапазон частот 1Гц - 8МГц. Регулировка частоты производится энкодером. До частоты 2,8 кГц разрешение 1 герц, на частотах выше таймер аппаратно уже не может поддерживать это разрешение, поэтому более высокие частоты синтезируются задавая параметром не требуемую частоту, а просто инкременируя регистр сравнения. Получается чем выше частота -тем больше шаг между щелчками энкодера. Вращая энкодер с ненажатой кнопкой частота меняется на 1Гц , с нажатой кнопкой один шаг -100Гц. Выше 2,8кГц вращение энкодера с нажатой конпкой так-же ускоряет счёт. Програмного подавления дребезга контактов энкодера нет, поэтому нужно повесить конденсаторы 0,01..0,1 мкф относительно земли. На кнопке конденсатор не обязателен. Рассчитанная математически частота выводится в сериал. Энкодер использовался самый популярный, для нестандартных возможно придётся корректировать обработчик.
/* Генератор 1 Hz..8 MHz. Энкодер подключен к пинам A0 и A1, кнопка
энкодера подключена к A2. Требуется использовать конденсаторы 0,01..0,1uf
относительно земли на каждый из 2х выводов энкодера.
Скетч для ардуино на мк atmega328 (UNO,Nano, MiniPro)
*/
float freq;
void setup() {
pinMode (9,OUTPUT); // выход генератора
pinMode(A0,INPUT); // с рассчетом, что энкодере внешняя подтяжка-
pinMode(A1,INPUT); // -к шине питания. Если нету, то подтянуть программно.
pinMode(A2,INPUT_PULLUP); //кнопка энкодера
Serial.begin(9600);
PCICR=1<<PCIE1; //разрешить прерывание PCINT
PCMSK1=(1<<PCINT9);// По сигналу на А1 создавать прерывание
TCCR1A=1<<COM1A0; //подключить выход OC1A первого таймера
TCCR1B=0;//
}
ISR (PCINT1_vect){
static boolean gen_mode=0; //флаг режима управления
static uint32_t enc=1; //переменная счёта энкодера
uint32_t ocr=OCR1A;
uint32_t divider=1; //переменная коэфф. деления прескалера
byte n=PINC&3; //считать значение энкодера
boolean knopka = PINC&(1<<2); // 0-кнопка нажата, 1-кнопка НЕ нажата.
if (freq<2848) gen_mode=0; //переключение режима управления по частоте
if (freq>=2848) gen_mode=1; //переключение режима управления по OCR
// Если увеличение частоты
if (n==3||n==0){
if (gen_mode){if (knopka){ if(ocr>0) {ocr--; } } else { if(ocr>9)ocr-=10; } }
else knopka? enc++ : enc+=100; // в нч режиме
} //end GetUP
// Если уменьшение частоты
if (n==2||n==1){
if (gen_mode){ if (knopka){ if(ocr<65535) {ocr++; } } else { if(ocr<=65525)ocr+=10; } }
else {if (knopka) { if (enc>=2)enc--; } else { if (enc>100) enc-=100; } }
} //end GetDown
if(gen_mode){ OCR1A=ocr; freq= (float)F_CPU/2 / (OCR1A+1); }
else { //расчёт прескалера и OCR по нужной частоте
divider=1; ocr = (F_CPU / enc /2 /divider) -1;
if (ocr >65536) { divider=8; ocr = F_CPU / enc /2 /divider;
if (ocr >65536) { divider=64; ocr = F_CPU / enc /2 /divider;
if (ocr >65536) {divider=256; ocr = F_CPU / enc /2 /divider;
if (ocr >65536) { divider=1024; ocr = F_CPU / enc /2 /divider;
if (ocr >65536){ocr=65536; }}}}} OCR1A=ocr-1;
//запись в регистр прескалера
switch (divider) {
case 1: TCCR1B=1|(1<<WGM12); break;
case 8: TCCR1B=2|(1<<WGM12); break;
case 64: TCCR1B=3|(1<<WGM12); break;
case 256: TCCR1B=4|(1<<WGM12); break;
case 1024: TCCR1B=5|(1<<WGM12); break; }
freq= (float) F_CPU/2 / (OCR1A+1) /divider;
} //end if !gen_mode
}
void loop() {
if (freq <10000) { Serial.print(freq,1);Serial.println(" Hz "); }
if (freq >10000) { Serial.print(freq/1000,3);Serial.println(" kHz");}
delay(100);
}
Кстати заметил ошибку в стартовом сообщении. В 48 строке нужно убрать -1 в конце строки.
dimax. Проверил ваш проект в работе. Смотрел меандр осциллографической приставкой, так же покрутил на частотомере.
Всё отлично!
jeka_tm, вот, модифицировал основной код. Эта версия с регулировкой скважности. Частоту ограничивать в итоге не стал, скважность регулируется на всех частотах, просто начиная с какого-то значения регулируется всё бОльшими рывками. На мегагерце например всего 8 градаций. Но энкодер всё равно везде счёлкает 100 градаций (в процентах).Некоторое неудобство, но избавится от него малыми силами не выйдет. Хочешь, сам повозись, можно по идее ввести разрядность регулировки скважности в зависимости от частоты.
/* Генератор 1 Hz..4 MHz. С регулировкой скважности. Энкодер регулировки частоты подключен к пинам A0 и A1, кнопка энкодера подключена к A2. Энкодер регулировки скважности подключен к пинам 8 и 10 Его кнопка не задействована. Требуется использовать конденсаторы 0,01..0,1uf относительно земли на каждый вывод обоих энкодеров. Скетч для ардуино на мк atmega328 (UNO,Nano, MiniPro) */ #define set_duty() { OCR1A=(uint32_t)ICR1*duty/100; } uint8_t duty=50; float freq; void setup() { Serial.begin(9600); pinMode (9,OUTPUT); // выход генератора PB2 pinMode(A0,INPUT); // PC0 вывод энкодера "частота" pinMode(A1,INPUT); // PC1 вывод энкодера "частота" pinMode(A2,INPUT_PULLUP); // PC2 кнопка энкодера pinMode(8,INPUT); // PB0 вывод энкодера "скважность" pinMode(10,INPUT); // PB2 вывод энкодера "скважность" PCICR=(1<<PCIE1)|(1<<PCIE0); //разрешить прерывания PCINT 0, 1 PCMSK1=1<<PCINT9;// По сигналу на А1 создавать прерывание (устновка частоты) PCMSK0=1<<PCINT0;// По сигналу на pin8 создавать прерывание (скважность) TCCR1A=1<<COM1A1; //подключить выход OC1A первого таймера TCCR1B=0;// } ISR (PCINT1_vect){ static boolean gen_mode=0; //флаг режима управления static uint32_t enc=1; //переменная счёта энкодера uint32_t icr=ICR1; uint16_t divider=1; //переменная коэфф. деления прескалера byte n=PINC&3; //считать значение энкодера частоты boolean knopka = PINC&(1<<2); // 0-кнопка нажата, 1-кнопка НЕ нажата. if (freq<2848) gen_mode=0; //переключение режима управления по частоте if (freq>=2848) gen_mode=1; //переключение режима управления по OCR // Если увеличение частоты if (n==3||n==0){ if (gen_mode){if (knopka){ if(icr>2) {icr--; } } else { if(icr>12)icr-=10; } } else knopka? enc++ : enc+=100; // в нч режиме } //end GetUP // Если уменьшение частоты if (n==2||n==1){ if (gen_mode){ if (knopka){ if(icr<65535) {icr++; } } else { if(icr<=65525)icr+=10; } } else {if (knopka) { if (enc>=2)enc--; } else { if (enc>100) enc-=100; } } } //end GetDown if(gen_mode){ ICR1=icr; set_duty(); freq= (float)F_CPU/2 / ICR1; } else { //расчёт прескалера и ICR по нужной частоте divider=1; icr = F_CPU / enc /2 /divider; if (icr >65536) { divider=8; icr = F_CPU / enc /2 /divider; if (icr >65536) { divider=64; icr = F_CPU / enc /2 /divider; if (icr >65536) {divider=256; icr = F_CPU / enc /2 /divider; if (icr >65536) { divider=1024; icr = F_CPU / enc /2 /divider; if (icr >65536){icr=65536; }}}}} ICR1=icr-1; set_duty(); //запись в регистр прескалера switch (divider) { case 1: TCCR1B=1|(1<<WGM13); break; case 8: TCCR1B=2|(1<<WGM13); break; case 64: TCCR1B=3|(1<<WGM13); break; case 256: TCCR1B=4|(1<<WGM13); break; case 1024: TCCR1B=5|(1<<WGM13); break; } freq= (float) F_CPU/2 / (ICR1+1) /divider; } //конец "если работа в НЧ режиме" } ISR (PCINT0_vect){ //обработчик энкодера "скважность" byte m=PINB&B00000101; //считать значение энкодера2 if (m==0||m==5){ if(duty<100) {duty++; } } if (m==1||m==4){ if(duty>0) {duty--; } } set_duty(); } void loop() { if (freq <10000) { Serial.print(freq,1);Serial.print(" Hz "); } if (freq >10000) { Serial.print(freq/1000,3);Serial.print(" kHz");} Serial.print("<br>"); Serial.print("Duty="); Serial.print(duty); Serial.print("%"); Serial.println(); delay(100); }Сделал генератор-шилд для UNO, как и задумывал ранее. За основу взята версия с регулировкой скважности, но вместо второго энкодера добавил кнопку, которая переключает энкодер в режим частоты или скважности. Фотки и видео:
И видео с демонстрацией режимов (быстрая регулировка частоты, по-герцевая регулировка, регулировка скважности) https://cloud.mail.ru/public/CaFy/QxWcRpwSy
а можеim скинуть код как в 18 посте но кнопкой плюс энкодер как на видео?
jeka_tm, да, конечно.
спасибо. только под 1202 переделаю. кнопка без фиксации?
кстати когда первый код тестировал, наверно глюк моего энкодера, между щелками еще один раз срабатывает. получается за один щелчок происходит срабатывание как за два. что можно сделать не знаешь?
получается почти как у тебя
Внёс рацпредложение от Максима в последний код, заодно исправил мелкий баг обратного перехода через частоту 2848. Если уменьшать по-герцу то на частоте 2847 перебрасывалось сразу на 2900, и так по кругу. Проскочить можно было только щёлкнув в обратную сторону 100 герц. Последняя версия..
jeka_tm печаткой шилда не поделитесь народу ?
пожалуйста
https://yadi.sk/d/mYeiZu4jm8cRv
Генератор версия 2.0
Позаимствована функция на ассемблере с алгоритмом DDS (отсюда),
с ней добавилось возможность генерить табличные формы сигналов
синуса, треугольника, пилы, можно добавить массив с другой формой сигнала,
свободного места во флэше полно. В качестве АЦП используется традиционная схема R2R. Под АЦП задействован весь порт D (выводы ардуино 0...7). Выход АЦП соединён с выходом таймера, в скетче порты соответствующим образом коммутируются. Точнось резисторов принципиальной роли не играет, 5% вполне достаточно. В идеале нужно ещё добавить какой нибудь усилитель на скоростном ОУ (типа OP37), сделать смещение и регулировку амплитуды. Меня пока устраивает и без всего этого :)
Скетч:
Тех. Характеристики генерации таймером:
Генерация прямоугольного сигнала 0..4 Мгц ,
минимальный шаг регулировки частоты в диапазоне 0...2,8кГц - 1Гц
свыше 2,8кГц минимальный шаг постепенно возрастает.
Регулировка коэффициента заполнения (скважности) 1..100%
в диапазоне 1Гц..80кГц регулировка производится с разрешением 1%
Свыше 80кГц разрешение (шаг) регулировки скважности увеличивается.
При изменении рабочей частоты в диапазоне 1Гц-80кГц выбранная скважность сохраняется,
а при изменении частоты свыше 80кГц сбрасывается на 50 %,
но в режиме регулировки скважности её можно снова изменять.
Тех. Характеристики генерации сигнала через DDS:
Почему-то в оригинальной статье автора алгоритма DDS о характеристиках нет ни слова.
Указана только максимальная частота -65кГц. Откуда её взял автор непонятно, я поставил ограничение на 100кГц C увеличением частоты сильно падает разрешение получаемого сигнала на высоких частотах. А конкретно, в диапазонах:
0...6,25 кГц - разрядность от 256 до 128 градаций
6,25...12,5 кГц - разрядность от 128 до 64 градаций
12,5...25кГц -разрядность от 64 до 32 градаций
25кГц...50кГц -разрядность от 32 до 16 градаций
50кГц...100кГц -разрядность от 16 до 8 градаций
Помимо этого с увеличением частоты вырастает джиттер,
особенно заметно на сигналах с резкими фронтами (прямоугольник, пила).
Но на точно установленных частотах 6,25кГц ; 12,5кГц ; 25кГц; 50кГц; 100 кГц джиттера нет, их можно использовать для точных измерений. Остальные частоты для большинства применений тоже подойдут.
Но если нужен идеальный сигнал -то только генерация таймером.
Во всех диапазонах DDS минимальный шаг регулировки частоты - 1Гц.
Для сравнения синус 20кГц и синус 100кГц:
Управление: кнопка переключения режимов переключает последовательно
(0)синус DDS, (1)треугольник DDS, (2)прямоугольник DDS, (3)правая пила DDS,
(4) левая пила DDS. (5) частота генерации таймером (6) скважность генерации таймером
Кнопка на энкодере переключает шаг изменения частоты 1000-1-10-100
в режиме регулировки скважности(6) нажатие на кнопку переключает энкодер в режим (5).
Особенности:

(1) Функция на ассемблере использует не стандартный способ чтения массива, записанного через PROGMEM Для этого способа требуется что бы расположение всех таблиц-массивов было кратно адресу 0x100 В ардуино нет штатных средств что бы положить конкретный массив по конкретному адресу во флэш область, поэтому пришлось обойти этот недостаток не очень красивым способом. Я создал специальный массив musor_mass, который занимает всё свободное место вплоть до адреса 0x100, а после него массивы с таблицами сами попадают точно на нужные адреса. Что бы компилятор не выбросил этот массив пришлось его как-то задействовать ( в строке 147 сетапа). При изменении в коде или при использовании версии ардуино отличной от 1.6.8 возможно потребуется изменение размера мусорного массива таким образом, что бы первый байт табличных массивов сел точно на адрес 0x100. Возможно в какой-то версии компилятора musor_mass нужно будет расположить в скетче не последним, а первым. После нажатия кнопки "проверить" лучше сразу заглянуть в скомпилившийся hex файл. Пример правильного расположения массивов на картинке.
(2) В функции на ассемблере использован свободный 7й бит регистра PORTB, нога физически отключена и задействавана под кварц, так что бит можно использовать в своих целях в качестве булевой переменной. А у автора оригинального DDS генератора в аналогичных нетрадиционных целях задействован бит регистра SPCR (конфигурационный регистр SPI)
В данном скетче вывод информации происходит через Serial, в функции monitor_out() можно настроить вывод на свой дисплей, на плате ардуино ещё свободны 7 выводов (11,12,13,A2-A5).
Версия под дисплей NOKIA 5110
Дисплей подключен на пины A2-A5. Других отличий от сериальной нет. Библа для дисплея LCD5510 от ринки-динка, но подпатченная нашим форумчанином ssvs111 что б дисплей работал на 4 пинах, ссылка на пропатченую версию была тут Скетч:
Ну пока без энкодера. Проба пера. ЦАП и выходные цепи использую с ранее сделаного генератора с Паяльника, который меня не устроил.
Ну и для полной коллекции -последняя версия с более удобной распиновкой дисплея и кнопок для печатной платы. Так же вроде подправил все мелкие недочёты.
Из существенный отличий -использовал 8 ногу для включения/отключения подсветки. Либо можно что-то ещё включать, например сделать управляемое смещение, или переключать выход релюшкой , или ещё что - пригодится в любом случае. Включается комбинацией- нажать кнопку энкодера, + удерживая её нажать кнопку режима. Затем отпустить кнопку энкодера, и последней отпустить кнопку режима. Скетч:
/* Генератор с регулируемой частотой и скважностью на Ардуино Уно v 2.3 * Распиновка: Энкодер A0,A1 (in) * Кнопка энкодера A3 (in) * Кнопка "режим" A2 (in) * Подсветка 8 (out) (вывод BL дисплея) * Дисплей nokia 5110 - 13,12,11,10 (out) * Выход генератора 0..7 + 9 (out) */ const PROGMEM uint8_t sinewave[]= // массив синуса { 0x80,0x83,0x86,0x89,0x8c,0x8f,0x92,0x95,0x98,0x9c,0x9f,0xa2,0xa5,0xa8,0xab,0xae, 0xb0,0xb3,0xb6,0xb9,0xbc,0xbf,0xc1,0xc4,0xc7,0xc9,0xcc,0xce,0xd1,0xd3,0xd5,0xd8, 0xda,0xdc,0xde,0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xed,0xef,0xf0,0xf2,0xf3,0xf5, 0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfc,0xfd,0xfe,0xfe,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xfe,0xfd,0xfc,0xfc,0xfb,0xfa,0xf9,0xf8,0xf7, 0xf6,0xf5,0xf3,0xf2,0xf0,0xef,0xed,0xec,0xea,0xe8,0xe6,0xe4,0xe2,0xe0,0xde,0xdc, 0xda,0xd8,0xd5,0xd3,0xd1,0xce,0xcc,0xc9,0xc7,0xc4,0xc1,0xbf,0xbc,0xb9,0xb6,0xb3, 0xb0,0xae,0xab,0xa8,0xa5,0xa2,0x9f,0x9c,0x98,0x95,0x92,0x8f,0x8c,0x89,0x86,0x83, 0x80,0x7c,0x79,0x76,0x73,0x70,0x6d,0x6a,0x67,0x63,0x60,0x5d,0x5a,0x57,0x54,0x51, 0x4f,0x4c,0x49,0x46,0x43,0x40,0x3e,0x3b,0x38,0x36,0x33,0x31,0x2e,0x2c,0x2a,0x27, 0x25,0x23,0x21,0x1f,0x1d,0x1b,0x19,0x17,0x15,0x13,0x12,0x10,0x0f,0x0d,0x0c,0x0a, 0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x03,0x02,0x01,0x01,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x02,0x03,0x03,0x04,0x05,0x06,0x07,0x08, 0x09,0x0a,0x0c,0x0d,0x0f,0x10,0x12,0x13,0x15,0x17,0x19,0x1b,0x1d,0x1f,0x21,0x23, 0x25,0x27,0x2a,0x2c,0x2e,0x31,0x33,0x36,0x38,0x3b,0x3e,0x40,0x43,0x46,0x49,0x4c, 0x4f,0x51,0x54,0x57,0x5a,0x5d,0x60,0x63,0x67,0x6a,0x6d,0x70,0x73,0x76,0x79,0x7c }; const PROGMEM uint8_t squarewave[]= //массив меандра { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, }; const PROGMEM uint8_t trianglewave[]= //массив треугольника { 0x00,0x02,0x04,0x06,0x08,0x0a,0x0c,0x0e,0x10,0x12,0x14,0x16,0x18,0x1a,0x1c,0x1e, 0x20,0x22,0x24,0x26,0x28,0x2a,0x2c,0x2e,0x30,0x32,0x34,0x36,0x38,0x3a,0x3c,0x3e, 0x40,0x42,0x44,0x46,0x48,0x4a,0x4c,0x4e,0x50,0x52,0x54,0x56,0x58,0x5a,0x5c,0x5e, 0x60,0x62,0x64,0x66,0x68,0x6a,0x6c,0x6e,0x70,0x72,0x74,0x76,0x78,0x7a,0x7c,0x7e, 0x80,0x82,0x84,0x86,0x88,0x8a,0x8c,0x8e,0x90,0x92,0x94,0x96,0x98,0x9a,0x9c,0x9e, 0xa0,0xa2,0xa4,0xa6,0xa8,0xaa,0xac,0xae,0xb0,0xb2,0xb4,0xb6,0xb8,0xba,0xbc,0xbe, 0xc0,0xc2,0xc4,0xc6,0xc8,0xca,0xcc,0xce,0xd0,0xd2,0xd4,0xd6,0xd8,0xda,0xdc,0xde, 0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xee,0xf0,0xf2,0xf4,0xf6,0xf8,0xfa,0xfc,0xfe, 0xff,0xfd,0xfb,0xf9,0xf7,0xf5,0xf3,0xf1,0xef,0xef,0xeb,0xe9,0xe7,0xe5,0xe3,0xe1, 0xdf,0xdd,0xdb,0xd9,0xd7,0xd5,0xd3,0xd1,0xcf,0xcf,0xcb,0xc9,0xc7,0xc5,0xc3,0xc1, 0xbf,0xbd,0xbb,0xb9,0xb7,0xb5,0xb3,0xb1,0xaf,0xaf,0xab,0xa9,0xa7,0xa5,0xa3,0xa1, 0x9f,0x9d,0x9b,0x99,0x97,0x95,0x93,0x91,0x8f,0x8f,0x8b,0x89,0x87,0x85,0x83,0x81, 0x7f,0x7d,0x7b,0x79,0x77,0x75,0x73,0x71,0x6f,0x6f,0x6b,0x69,0x67,0x65,0x63,0x61, 0x5f,0x5d,0x5b,0x59,0x57,0x55,0x53,0x51,0x4f,0x4f,0x4b,0x49,0x47,0x45,0x43,0x41, 0x3f,0x3d,0x3b,0x39,0x37,0x35,0x33,0x31,0x2f,0x2f,0x2b,0x29,0x27,0x25,0x23,0x21, 0x1f,0x1d,0x1b,0x19,0x17,0x15,0x13,0x11,0x0f,0x0f,0x0b,0x09,0x07,0x05,0x03,0x01 }; const PROGMEM uint8_t sawtoothwave[]= //массив пила1 { 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f, 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f, 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f, 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f, 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f, 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f, 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f, 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f, 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f, 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f, 0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf, 0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf, 0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf, 0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdd,0xde,0xdf, 0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef, 0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff }; const PROGMEM uint8_t rewsawtoothwave[]= //массив пила2 { 0xff,0xfe,0xfd,0xfc,0xfb,0xfa,0xf9,0xf8,0xf7,0xf6,0xf5,0xf4,0xf3,0xf2,0xf1,0xf0, 0xef,0xee,0xed,0xec,0xeb,0xea,0xe9,0xe8,0xe7,0xe6,0xe5,0xe4,0xe3,0xe2,0xe1,0xe0, 0xdf,0xde,0xdd,0xdc,0xdb,0xda,0xd9,0xd8,0xd7,0xd6,0xd5,0xd4,0xd3,0xd2,0xd1,0xd0, 0xcf,0xce,0xcd,0xcc,0xcb,0xca,0xc9,0xc8,0xc7,0xc6,0xc5,0xc4,0xc3,0xc2,0xc1,0xc0, 0xbf,0xbe,0xbd,0xbc,0xbb,0xba,0xb9,0xb8,0xb7,0xb6,0xb5,0xb4,0xb3,0xb2,0xb1,0xb0, 0xaf,0xae,0xad,0xac,0xab,0xaa,0xa9,0xa8,0xa7,0xa6,0xa5,0xa4,0xa3,0xa2,0xa1,0xa0, 0x9f,0x9e,0x9d,0x9c,0x9b,0x9a,0x99,0x98,0x97,0x96,0x95,0x94,0x93,0x92,0x91,0x90, 0x8f,0x8e,0x8d,0x8c,0x8b,0x8a,0x89,0x88,0x87,0x86,0x85,0x84,0x83,0x82,0x81,0x80, 0x7f,0x7e,0x7d,0x7c,0x7b,0x7a,0x79,0x78,0x77,0x76,0x75,0x74,0x73,0x72,0x71,0x70, 0x6f,0x6e,0x6d,0x6c,0x6b,0x6a,0x69,0x68,0x67,0x66,0x65,0x64,0x63,0x62,0x61,0x60, 0x5f,0x5e,0x5d,0x5c,0x5b,0x5a,0x59,0x58,0x57,0x56,0x55,0x54,0x53,0x52,0x51,0x50, 0x4f,0x4e,0x4d,0x4c,0x4b,0x4a,0x49,0x48,0x47,0x46,0x45,0x44,0x43,0x42,0x41,0x40, 0x3f,0x3e,0x3d,0x3c,0x3b,0x3a,0x39,0x38,0x37,0x36,0x35,0x34,0x33,0x32,0x31,0x30, 0x2f,0x2e,0x2d,0x2c,0x2b,0x2a,0x29,0x28,0x27,0x26,0x25,0x24,0x23,0x22,0x21,0x20, 0x1f,0x1e,0x1d,0x1c,0x1b,0x1a,0x19,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10, 0x0f,0x0e,0x0d,0x0c,0x0b,0x0a,0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01,0x00, }; 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,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; volatile int32_t freq=1000;// частота по умолчанию volatile uint16_t shag=100; //шаг энкодера по умолчанию volatile uint8_t regim=5; //режим генератора по умолчанию volatile uint8_t monitor_flag; // флаг для вывода на дисплей volatile uint8_t ad2,ad1,ad0; //служебные байты для функции на ассемблере volatile uint8_t duty=50; //скважность volatile uint32_t icr=0; //переменная для управления регистром сравнения таймера1 #include <LCD5110_SSVS.h> extern uint8_t SmallFont[]; extern uint8_t MediumNumbers[]; LCD5110 lcd(10,11,12,13); // DDS algorithm static inline void signalOUT(const uint8_t *signal){ asm volatile( "eor r18, r18 ;r18<-0" "\n\t" "eor r19, r19 ;r19<-0" "\n\t" "1:" "\n\t" "add r18, %0 ;1 cycle" "\n\t" "adc r19, %1 ;1 cycle" "\n\t" "adc %A3, %2 ;1 cycle" "\n\t" "lpm ;3 cycles" "\n\t" "out %4, __tmp_reg__ ;1 cycle" "\n\t" "sbis %5, 7 ;1 cycle if no skip" "\n\t" "rjmp 1b ;2 cycles. Total 10 cycles" "\n\t" : :"r" (ad0),"r" (ad1),"r" (ad2),"e" (signal),"I" (_SFR_IO_ADDR(PORTD)), "I" (_SFR_IO_ADDR(PORTB)) :"r18", "r19" ); } void setup(){ lcd.InitLCD(); for (int n=0; n <sizeof(musor_mass); n++ ) {PORTD=musor_mass[n]; } PORTC|=(1<<PC2)|(1<<PC3); //подтяжка кнопок на A2 A3 TCCR0B=0; TCCR1A=0; TCCR1B=0; DDRB|=1<<PB0; // 8 пин -включение подсветки дисплея = выход PCICR=(1<<PCIE1); //включить прерывание PCINT PCMSK1=(1<<PCINT9)|(1<<PCINT10)|(1<<PCINT11); //выбор пинов прерывания (A1,A2,A3) check_regim(); } //end setup void up_down(boolean x){ // управление регулировками if (TCCR1B==17 && ICR1<2800 && regim==5){ if(x) { if (icr<1000 && shag > 100) shag=100; if (icr<100 && shag > 10) shag=10; if (icr<10 && shag > 1) shag=1; icr-=shag; if (icr<2) icr=2; } else { if (icr > 1800 && shag >100) shag =100; icr+=shag ; } return; } if (regim==6){ if (ICR1>100){ if (x){if(duty<100) {duty++; } } if (!x){ if(duty>0) {duty--; }} } else{ if (x){if(OCR1A<ICR1) {OCR1A++; } } else {if(OCR1A>0) {OCR1A--; } } if(OCR1A>ICR1) OCR1A=ICR1-1; duty=(uint32_t)100*OCR1A/ICR1; } return; } x? freq+=shag : freq-=shag ; if (freq < 1) freq=1; } // ПРЕРЫВАНИЕ от кнопок и энкодера ISR (PCINT1_vect){ PCMSK1=0; PCICR=0; PORTD=0; // если нажата кнопка энкодера if ((PINC&(1<<3))==0){ while ((PINC&(1<<3))==0);// подождать до тех пор, когда кнопку отпустят //блок вкл/отк подсветки дисплея (8 пин ардуино) if ((PINC&(1<<2))==0){ //если после этого нажата кнопка режимов, PINB|=1<<PB0; //менять состояние 8 пина while ((PINC&(1<<2))==0);// теперь подождать пока отпустят кнопку режим PCMSK1=(1<<PCINT9)|(1<<PCINT10)|(1<<PCINT11); PCICR=(1<<PCIE1); return; } if (regim==6) { regim=5; check_regim(); PCMSK1=(1<<PCINT9)|(1<<PCINT10)|(1<<PCINT11); PCICR=(1<<PCIE1); return; } switch (shag){ case 1: shag=10; break; case 10: shag=100; break; case 100: shag=1000; break; case 1000: shag=1; } check_regim(); PCMSK1=(1<<PCINT9)|(1<<PCINT10)|(1<<PCINT11); PCICR=(1<<PCIE1); return; } if ((PINC&(1<<2))==0){ // если нажата кнопка режимов PORTD=0; while ((PINC&(1<<2))==0); regim++; if (regim==7) regim=0; check_regim(); PCMSK1=(1<<PCINT9)|(1<<PCINT10)|(1<<PCINT11);PCICR=(1<<PCIE1); return; } // если кнопки не нажимались -значит крутили энкодер: up_down( (PINC&1)^((PINC&2)>>1) ); //отправить считанное энкодеров в другую функцию // если при вращении счёт идёт не в нужную сторону, то вставить символ '!' up_down(! check_regim(); PCMSK1=(1<<PCINT9)|(1<<PCINT10)|(1<<PCINT11); PCICR=(1<<PCIE1);//включить прерывания } //конец функции обработки прерываний от кнопок PCINT1_vect void pwm_gen(){ //настройка таймера1 uint16_t divider=1; icr = (16000000ul / freq /2 /divider); byte shifts[] = {3,3,2,2}; for(byte i = 0; i < 4; i++){ if (icr > 65536) { divider <<= shifts[i]; icr = 16000000ul / freq /2 /divider; } else { TCCR1B = (i+1)|(1<<WGM13); break; } } ICR1=icr-1; set_duty(); } //end pwm_gen void loop() { if (monitor_flag) { monitor_flag=0; monitor_out(); } if (regim <5){ PORTB&= ~(1<<7); DDRD=0xFF;//set D port as output uint32_t temp=(float)freq /0.095367431640625; ad2=temp>>16; ad1=temp>>8; ad0=temp; switch (regim){ case 0: signalOUT(sinewave); break; case 1: signalOUT(trianglewave); break; case 2: signalOUT(squarewave); break; case 3: signalOUT(sawtoothwave); break; case 4: signalOUT(rewsawtoothwave); } //end switch DDRD=0; PORTD=0; } //end if (regim<5) } void monitor_out(){ String dutystr,stepstr; dutystr= String("Duty="+ String(duty)+ "%"); stepstr=String("Step= "+String(shag)); lcd.clrScr(); //Вывод первой строчки lcd.setFont(SmallFont); if (freq <10000) {lcd.print("Frequency, Hz",LEFT,0 ); } if (freq >=10000) {lcd.print("Frequency, kHz",LEFT,0 ); } //Вывод второй строчки lcd.setFont(MediumNumbers); if (freq <10000) { lcd.printNumI(freq, CENTER, 8); } if (freq >=10000u && freq < 1000000ul ) { lcd.printNumF( ((float)freq/1000),3 ,CENTER, 8); } if (freq >=1000000ul ) { lcd.printNumF( ((float)freq/1000),2 ,CENTER, 8); } //Вывод третьей строчки lcd.setFont(SmallFont); switch(regim){ case 0: lcd.print("Sinus DDS",CENTER, 32); break; case 1: lcd.print("Triangle DDS",CENTER, 32); break; case 2: lcd.print("Meandr DDS",CENTER, 32); break; case 3: lcd.print("Pila1 DDS",CENTER, 32); break; case 4: lcd.print("Pila2 DDS",CENTER, 32); break; case 5: lcd.print("PWM Mode",CENTER,32); break; case 6: lcd.print("Duty Mode", CENTER, 32); } //Вывод четвёртой строчки if (regim==6) lcd.print(dutystr, CENTER, 40); else lcd.print(stepstr, CENTER, 40); }//end monitor out void set_duty(){ //float ocr,delta_ocr; if (regim==6&&ICR1<100) return; if (regim==5 && ICR1<100){ OCR1A=ICR1/2; duty=50; return; } static uint16_t ocr; ocr=(uint32_t)ICR1*duty/100; OCR1A=ocr; } void check_regim(){// проверка и установка режимов генератора if (regim <5){ if (freq > 100000) freq=100000; TCCR1B=0; TCCR1A=0; DDRB&=~(1<<DDB1); // 9 pin arduino set Z-mode }// end if regim <5 if (regim > 4) { TCCR1A=1<<COM1A1; DDRB|=1<<DDB1; // 9 pin set output (pwm out) if (TCCR1B==17 && ICR1<2800){ ICR1=icr; freq= (float) 8000000UL/ICR1; set_duty(); } else { pwm_gen(); } } PORTB|= 1<<7; monitor_flag=1; }Как и первый вариант генератора сделал в виде шилда для UNO.
dimax, спасибо, замечательное устройство.
Предполагаемую схему на ОУ для смещения нуля попробовал, но убедился, что LM358 для этих целей не подходит. Заказал LM833, а пока собрал схему в пректически неизменном варианте:
dimax, спасибо
Кстати, не получаеться исправить в новой версии кода сам енкодер. Пробовал вставить исправления не получилось считает по два.
Етот генератор можно использовать как медицынский прибор при частоте 10Кгц и Синусе после усиления до 60В получиться аналог русского миостимулятора Мион-1
ftor, фикс двойного щелчка энкодера
void up_down(boolean x){ // управление регулировками static boolean n=0; if (n=!n){return;} //далее без измененийДело было вечером, делать было нечего.. А сделать что-то хотелось. Решил добавить в поделку пару полезных функций:
Генератор V2.4, в код добавлены частотометр и вольтметр.
Частотометр -измеряет методом тактирования первого таймера от источника сигнала. Измеряемый диапазон: 1Гц ... 7,999 МГц*

* при превышении верхнего диапазона частотометр будет выдавать неправильные показания. Точность измерений зависит от точности установленного на плате кварцевого резонатора.
Вход подключать к пину D5, в идеале отключая от этой ноги резистор из схемы ЦАП. Но можно и не отключать, -работает и так.
Вольтметр - Измеряет постоянное напряжение на шине Vcc и на пинах A4, A5. Используется оверсемплинг до псевдо 14 бит разрешения. , Вольтметр самокалибруется по встроенному в М.К. И.О.Н. на 1,1 вольта.
Измеряемое напряжение от 0в до напряжения питания М.К. (Vcc), отображаемое разрешение -1мв.
При загрузке можно кнопками выбрать что запускать, вольтметр или частотометр. Генератор запустится через 5 секунд по умолчанию, если выбор не сделан. Выхода из выбранных режимов не предусмотрено, только перезапуск по питанию или ресетом.
Обновлённая схема:
Скетч:
/* Генератор, частотометр, вольтметр на Ардуино Уно v 2.4 * Распиновка: Энкодер A0,A1 (in) * Кнопка энкодера A2 (in) * Кнопка "режим" A3 (in) * Подсветка 8 (out) (вывод BL дисплея) * Дисплей nokia 5110 - 13,12,11,10 (out) * Выход генератора 0..7 + 9 (out) * Вход частотометра - 5 (in) * Входы вольтметра -А4,А5 (in) */ const PROGMEM uint8_t sinewave[]= // массив синуса { 0x80,0x83,0x86,0x89,0x8c,0x8f,0x92,0x95,0x98,0x9c,0x9f,0xa2,0xa5,0xa8,0xab,0xae, 0xb0,0xb3,0xb6,0xb9,0xbc,0xbf,0xc1,0xc4,0xc7,0xc9,0xcc,0xce,0xd1,0xd3,0xd5,0xd8, 0xda,0xdc,0xde,0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xed,0xef,0xf0,0xf2,0xf3,0xf5, 0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfc,0xfd,0xfe,0xfe,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xfe,0xfd,0xfc,0xfc,0xfb,0xfa,0xf9,0xf8,0xf7, 0xf6,0xf5,0xf3,0xf2,0xf0,0xef,0xed,0xec,0xea,0xe8,0xe6,0xe4,0xe2,0xe0,0xde,0xdc, 0xda,0xd8,0xd5,0xd3,0xd1,0xce,0xcc,0xc9,0xc7,0xc4,0xc1,0xbf,0xbc,0xb9,0xb6,0xb3, 0xb0,0xae,0xab,0xa8,0xa5,0xa2,0x9f,0x9c,0x98,0x95,0x92,0x8f,0x8c,0x89,0x86,0x83, 0x80,0x7c,0x79,0x76,0x73,0x70,0x6d,0x6a,0x67,0x63,0x60,0x5d,0x5a,0x57,0x54,0x51, 0x4f,0x4c,0x49,0x46,0x43,0x40,0x3e,0x3b,0x38,0x36,0x33,0x31,0x2e,0x2c,0x2a,0x27, 0x25,0x23,0x21,0x1f,0x1d,0x1b,0x19,0x17,0x15,0x13,0x12,0x10,0x0f,0x0d,0x0c,0x0a, 0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x03,0x02,0x01,0x01,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x02,0x03,0x03,0x04,0x05,0x06,0x07,0x08, 0x09,0x0a,0x0c,0x0d,0x0f,0x10,0x12,0x13,0x15,0x17,0x19,0x1b,0x1d,0x1f,0x21,0x23, 0x25,0x27,0x2a,0x2c,0x2e,0x31,0x33,0x36,0x38,0x3b,0x3e,0x40,0x43,0x46,0x49,0x4c, 0x4f,0x51,0x54,0x57,0x5a,0x5d,0x60,0x63,0x67,0x6a,0x6d,0x70,0x73,0x76,0x79,0x7c }; const PROGMEM uint8_t squarewave[]= //массив меандра { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, }; const PROGMEM uint8_t trianglewave[]= //массив треугольника { 0x00,0x02,0x04,0x06,0x08,0x0a,0x0c,0x0e,0x10,0x12,0x14,0x16,0x18,0x1a,0x1c,0x1e, 0x20,0x22,0x24,0x26,0x28,0x2a,0x2c,0x2e,0x30,0x32,0x34,0x36,0x38,0x3a,0x3c,0x3e, 0x40,0x42,0x44,0x46,0x48,0x4a,0x4c,0x4e,0x50,0x52,0x54,0x56,0x58,0x5a,0x5c,0x5e, 0x60,0x62,0x64,0x66,0x68,0x6a,0x6c,0x6e,0x70,0x72,0x74,0x76,0x78,0x7a,0x7c,0x7e, 0x80,0x82,0x84,0x86,0x88,0x8a,0x8c,0x8e,0x90,0x92,0x94,0x96,0x98,0x9a,0x9c,0x9e, 0xa0,0xa2,0xa4,0xa6,0xa8,0xaa,0xac,0xae,0xb0,0xb2,0xb4,0xb6,0xb8,0xba,0xbc,0xbe, 0xc0,0xc2,0xc4,0xc6,0xc8,0xca,0xcc,0xce,0xd0,0xd2,0xd4,0xd6,0xd8,0xda,0xdc,0xde, 0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xee,0xf0,0xf2,0xf4,0xf6,0xf8,0xfa,0xfc,0xfe, 0xff,0xfd,0xfb,0xf9,0xf7,0xf5,0xf3,0xf1,0xef,0xef,0xeb,0xe9,0xe7,0xe5,0xe3,0xe1, 0xdf,0xdd,0xdb,0xd9,0xd7,0xd5,0xd3,0xd1,0xcf,0xcf,0xcb,0xc9,0xc7,0xc5,0xc3,0xc1, 0xbf,0xbd,0xbb,0xb9,0xb7,0xb5,0xb3,0xb1,0xaf,0xaf,0xab,0xa9,0xa7,0xa5,0xa3,0xa1, 0x9f,0x9d,0x9b,0x99,0x97,0x95,0x93,0x91,0x8f,0x8f,0x8b,0x89,0x87,0x85,0x83,0x81, 0x7f,0x7d,0x7b,0x79,0x77,0x75,0x73,0x71,0x6f,0x6f,0x6b,0x69,0x67,0x65,0x63,0x61, 0x5f,0x5d,0x5b,0x59,0x57,0x55,0x53,0x51,0x4f,0x4f,0x4b,0x49,0x47,0x45,0x43,0x41, 0x3f,0x3d,0x3b,0x39,0x37,0x35,0x33,0x31,0x2f,0x2f,0x2b,0x29,0x27,0x25,0x23,0x21, 0x1f,0x1d,0x1b,0x19,0x17,0x15,0x13,0x11,0x0f,0x0f,0x0b,0x09,0x07,0x05,0x03,0x01 }; const PROGMEM uint8_t sawtoothwave[]= //массив пила1 { 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f, 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f, 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f, 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f, 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f, 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f, 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f, 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f, 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f, 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f, 0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf, 0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf, 0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf, 0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdd,0xde,0xdf, 0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef, 0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff }; const PROGMEM uint8_t rewsawtoothwave[]= //массив пила2 { 0xff,0xfe,0xfd,0xfc,0xfb,0xfa,0xf9,0xf8,0xf7,0xf6,0xf5,0xf4,0xf3,0xf2,0xf1,0xf0, 0xef,0xee,0xed,0xec,0xeb,0xea,0xe9,0xe8,0xe7,0xe6,0xe5,0xe4,0xe3,0xe2,0xe1,0xe0, 0xdf,0xde,0xdd,0xdc,0xdb,0xda,0xd9,0xd8,0xd7,0xd6,0xd5,0xd4,0xd3,0xd2,0xd1,0xd0, 0xcf,0xce,0xcd,0xcc,0xcb,0xca,0xc9,0xc8,0xc7,0xc6,0xc5,0xc4,0xc3,0xc2,0xc1,0xc0, 0xbf,0xbe,0xbd,0xbc,0xbb,0xba,0xb9,0xb8,0xb7,0xb6,0xb5,0xb4,0xb3,0xb2,0xb1,0xb0, 0xaf,0xae,0xad,0xac,0xab,0xaa,0xa9,0xa8,0xa7,0xa6,0xa5,0xa4,0xa3,0xa2,0xa1,0xa0, 0x9f,0x9e,0x9d,0x9c,0x9b,0x9a,0x99,0x98,0x97,0x96,0x95,0x94,0x93,0x92,0x91,0x90, 0x8f,0x8e,0x8d,0x8c,0x8b,0x8a,0x89,0x88,0x87,0x86,0x85,0x84,0x83,0x82,0x81,0x80, 0x7f,0x7e,0x7d,0x7c,0x7b,0x7a,0x79,0x78,0x77,0x76,0x75,0x74,0x73,0x72,0x71,0x70, 0x6f,0x6e,0x6d,0x6c,0x6b,0x6a,0x69,0x68,0x67,0x66,0x65,0x64,0x63,0x62,0x61,0x60, 0x5f,0x5e,0x5d,0x5c,0x5b,0x5a,0x59,0x58,0x57,0x56,0x55,0x54,0x53,0x52,0x51,0x50, 0x4f,0x4e,0x4d,0x4c,0x4b,0x4a,0x49,0x48,0x47,0x46,0x45,0x44,0x43,0x42,0x41,0x40, 0x3f,0x3e,0x3d,0x3c,0x3b,0x3a,0x39,0x38,0x37,0x36,0x35,0x34,0x33,0x32,0x31,0x30, 0x2f,0x2e,0x2d,0x2c,0x2b,0x2a,0x29,0x28,0x27,0x26,0x25,0x24,0x23,0x22,0x21,0x20, 0x1f,0x1e,0x1d,0x1c,0x1b,0x1a,0x19,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10, 0x0f,0x0e,0x0d,0x0c,0x0b,0x0a,0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01,0x00, }; 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,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; volatile int32_t freq=1000;// частота по умолчанию volatile uint32_t icr=0; //переменная для управления регистром сравнения таймера1 volatile uint16_t shag=100; //шаг энкодера по умолчанию volatile uint16_t int_tic=0; volatile uint8_t regim=5; //режим генератора по умолчанию volatile uint8_t monitor_flag; // флаг для вывода на дисплей volatile uint8_t ad2,ad1,ad0; //служебные байты для функции на ассемблере volatile uint8_t duty=50; //скважность volatile uint8_t main_flag=0;//флаг работа в режиме генератора или нет #define int_on() PCMSK1=(1<<PCINT9)|(1<<PCINT10)|(1<<PCINT11); PCICR=(1<<PCIE1); //включить прерывание PCINT1, выбор пинов прерывания A1,A2,A3 #define int_off() PCMSK1=0; PCICR=0; //отключить PCINT1 #include <LCD5110_SSVS.h> extern uint8_t SmallFont[]; extern uint8_t MediumNumbers[]; LCD5110 lcd(10,11,12,13); // DDS algorithm static inline void signalOUT(const uint8_t *signal){ asm volatile( "eor r18, r18 ;r18<-0" "\n\t" "eor r19, r19 ;r19<-0" "\n\t" "1:" "\n\t" "add r18, %0 ;1 cycle" "\n\t" "adc r19, %1 ;1 cycle" "\n\t" "adc %A3, %2 ;1 cycle" "\n\t" "lpm ;3 cycles" "\n\t" "out %4, __tmp_reg__ ;1 cycle" "\n\t" "sbis %5, 7 ;1 cycle if no skip" "\n\t" "rjmp 1b ;2 cycles. Total 10 cycles" "\n\t" : :"r" (ad0),"r" (ad1),"r" (ad2),"e" (signal),"I" (_SFR_IO_ADDR(PORTD)), "I" (_SFR_IO_ADDR(PORTB)) :"r18", "r19" ); } void setup(){ lcd.InitLCD(); for (int n=0; n <sizeof(musor_mass); n++ ) {PORTD=musor_mass[n]; } PORTD=0; DDRD=0; TCCR1A=0; TCCR1B=0; TIMSK1=0; PORTC|=(1<<PC2)|(1<<PC3); //подтяжка кнопок на A2 A3 DDRB|=1<<PB0; // 8 пин -включение подсветки дисплея = выход main_screen(); //стартовое сообщение и выбор задач //далее загрузка генератора по умолчанию TCCR0B=0; check_regim(); int_on();//включить прерывание PCINT1 main_flag=1; } //end setup void up_down(boolean x){ // управление регулировками // static boolean n=0; if (n=!n){return;} // снять ремарку для энкодеров с двойным щелчком if (TCCR1B==17 && ICR1<2800 && regim==5){ if(x) {if (icr<1000 && shag > 100) shag=100; if (icr<100 && shag > 10) shag=10; if (icr<10 && shag > 1) shag=1; icr-=shag; if (icr<2) icr=2; } else { if (icr > 1800 && shag >100) shag =100; icr+=shag ; } return; } if (regim==6){if (ICR1>100){ if (x){if(duty<100) {duty++; } } if (!x){ if(duty>0) {duty--; }} } else{ if (x){if(OCR1A<ICR1) {OCR1A++; } } else {if(OCR1A>0) {OCR1A--; } } if(OCR1A>ICR1) OCR1A=ICR1-1; duty=(uint32_t)100*OCR1A/ICR1; } return; } x? freq+=shag : freq-=shag ; if (freq < 1) freq=1; } //////////////////////////////////////////////////////////////// //****** ПРЕРЫВАНИЕ от кнопок и энкодера*******///////////////// ISR (PCINT1_vect){ int_off(); PORTD=0; ///блок для обработки событий не в режиме генератора/// if (main_flag==0) { if ((PINC&(1<<3))==0){ while ((PINC&(1<<3))==0); //вкл. подсветки if ((PINC&(1<<2))==0){ PINB|=1<<PB0; while ((PINC&(1<<2))==0);} int_on(); return; //выходить если не в режиме генератора }} //далее всё в режиме генаратора // если нажата кнопка энкодера if ((PINC&(1<<3))==0){ while ((PINC&(1<<3))==0);// подождать до тех пор, когда кнопку отпустят //блок вкл/отк подсветки дисплея (8 пин ардуино) if ((PINC&(1<<2))==0){ //если после этого нажата кнопка режимов, PINB|=1<<PB0; //менять состояние 8 пина while ((PINC&(1<<2))==0);// теперь подождать пока отпустят кнопку режим int_on(); return; } if (regim==6) { regim=5; check_regim(); int_on(); return; } switch (shag){ case 1: shag=10; break; case 10: shag=100; break; case 100: shag=1000; break; case 1000: shag=1; break; } check_regim(); int_on(); return; } //конец блока *если нажата кнопка энкодера* if ((PINC&(1<<2))==0){ // если нажата кнопка режимов PORTD=0; while ((PINC&(1<<2))==0); regim++; if (regim==7) regim=0; check_regim(); int_on(); return; } //конец блока *если нажата кнопка режимов* // если кнопки не нажимались -значит крутили энкодер: up_down( ! (PINC&1)^((PINC&2)>>1) ); //отправить считанное энкодеров в другую функцию // если при вращении счёт идёт не в нужную сторону, то (вставить/убрать) символ '!' up_down(! check_regim(); int_on(); } //конец функции обработки прерываний от кнопок PCINT1_vect ////////////////////КОНЕЦ_ПРЕРЫВАНИЕ_от_кнопок_и_энкодера/////////////////////////////// void pwm_gen(){ //настройка таймера1 uint16_t divider=1; icr = (16000000ul / freq /2 /divider); byte shifts[] = {3,3,2,2}; for(byte i = 0; i < 4; i++){ if (icr > 65536) { divider <<= shifts[i]; icr = 16000000ul / freq /2 /divider; } else { TCCR1B = (i+1)|(1<<WGM13); break; } } ICR1=icr-1; set_duty(); } //end pwm_gen void loop() { if (monitor_flag) { monitor_flag=0; monitor_out(); } if (regim <5){ PORTB&= ~(1<<7); DDRD=0xFF;//set D port as output uint32_t temp=(float)freq /0.095367431640625; ad2=temp>>16; ad1=temp>>8; ad0=temp; switch (regim){ case 0: signalOUT(sinewave); break; case 1: signalOUT(trianglewave); break; case 2: signalOUT(squarewave); break; case 3: signalOUT(sawtoothwave); break; case 4: signalOUT(rewsawtoothwave); } //end switch DDRD=0; PORTD=0; } //end if (regim<5) }//end loop void monitor_out(){ String dutystr,stepstr; dutystr= String("Duty="+ String(duty)+ "%"); stepstr=String("Step= "+String(shag)); lcd.clrScr(); //Вывод первой строчки lcd.setFont(SmallFont); if (freq <10000) {lcd.print("Frequency, Hz",LEFT,0 ); } if (freq >=10000) {lcd.print("Frequency, kHz",LEFT,0 ); } //Вывод второй строчки lcd.setFont(MediumNumbers); if (freq <10000) { lcd.printNumI(freq, CENTER, 8); } if (freq >=10000u && freq < 1000000ul ) { lcd.printNumF( ((float)freq/1000),3 ,CENTER, 8); } if (freq >=1000000ul ) { lcd.printNumF( ((float)freq/1000),2 ,CENTER, 8); } //Вывод третьей строчки lcd.setFont(SmallFont); switch(regim){ case 0: lcd.print("Sinus DDS",CENTER, 32); break; case 1: lcd.print("Triangle DDS",CENTER, 32); break; case 2: lcd.print("Meandr DDS",CENTER, 32); break; case 3: lcd.print("Pila1 DDS",CENTER, 32); break; case 4: lcd.print("Pila2 DDS",CENTER, 32); break; case 5: lcd.print("PWM Mode",CENTER,32); break; case 6: lcd.print("Duty Mode", CENTER, 32); } //Вывод четвёртой строчки if (regim==6) lcd.print(dutystr, CENTER, 40); else lcd.print(stepstr, CENTER, 40); }//end monitor out void set_duty(){ if (regim==6&&ICR1<100) return; if (regim==5 && ICR1<100){ OCR1A=ICR1/2; duty=50; return; } static uint16_t ocr; ocr=(uint32_t)ICR1*duty/100; OCR1A=ocr; } void check_regim(){// проверка и установка режимов генератора if (regim <5){ if (freq > 100000) freq=100000; TCCR1B=0; TCCR1A=0; DDRB&=~(1<<DDB1); // 9 pin arduino set Z-mode }// end if regim <5 if (regim > 4) { TCCR1A=1<<COM1A1; DDRB|=1<<DDB1; // 9 pin set output (pwm out) if (TCCR1B==17 && ICR1<2800){ ICR1=icr; freq= (float) 8000000UL/ICR1; set_duty(); } else { pwm_gen(); } } PORTB|= 1<<7; monitor_flag=1; } ISR (TIMER1_OVF_vect){ int_tic++; } // прерывание частотомера void freq_meter(){ lcd.clrScr(); lcd.setFont(SmallFont); lcd.print("Freq.counter",LEFT, 0); int_on();//включить прерывание PCINT1 TIMSK1 = 1<<TOIE1;// подключить прерывание uint32_t freqm=0;// Переменная измерения частоты int_tic=0; TCNT1=0; TIFR1=0;//всё обнулить while(1){ TCCR1B=7;//тактировани от входа Т1 delay(2000); TCCR1B=0; freqm= (((uint32_t)int_tic<<16) | TCNT1)/2; //сложить что натикало int_tic=0; TCNT1 = 0; lcd.clrScr(); lcd.setFont(SmallFont); lcd.print("Freq.counter",LEFT, 0); lcd.setFont(MediumNumbers); if (freqm <10000) { lcd.printNumI(freqm, CENTER, 8); lcd.setFont(SmallFont); lcd.print("Herz",CENTER, 32 ); } if (freqm >=10000u && freqm < 1000000ul ) { lcd.printNumF( ((float)freqm/1000),3 ,CENTER, 8);lcd.setFont(SmallFont); lcd.print("KiloHerz",CENTER, 32 ); } if (freqm >=1000000ul ) { lcd.printNumF( ((float)freqm/1000000ul),3 ,CENTER, 8); lcd.setFont(SmallFont); lcd.print("MegaHerz",CENTER, 32 ); } } } ////////////////////////////////////////////////////////////////////// void volt_meter() { ADCSRA=(1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); ADCSRB=0; DIDR0=48; int_on(); float ain,vcc; String ainstr,vccstr; lcd.clrScr(); lcd.setFont(SmallFont); lcd.print("Volt meter",LEFT, 0); while(1){ ADMUX = (1<<REFS0)|(1<<MUX3)|(1<<MUX2)|(1<<MUX1); //Vcc measure delay(1); vcc=(float)(1.1*65472) / analog_func(); vccstr=String("Vcc= "+String(vcc,3)+" v "); lcd.print(vccstr,LEFT, 40); ADMUX = (1<<REFS0)|(1<<MUX2); //A4 measure delay(1); ain= analog_func()*vcc /65472 ; ainstr=String("Ain4= "+String(ain,3)+" v "); lcd.print(ainstr,LEFT, 16); ADMUX = (1<<REFS0)|(1<<MUX2)|(1<<MUX0); //A5 measure delay(1); ain= analog_func() *vcc /65472 ; ainstr=String("Ain5= "+String(ain,3)+" v "); lcd.print(ainstr,LEFT, 24); };//end while }//end volt meter uint32_t analog_func(){ uint32_t adc_buff=0; for (int n=0; n<=4095; n++ ) { ADCSRA |= (1<<ADSC); while (bit_is_set(ADCSRA,ADSC)); adc_buff += ADC; } return adc_buff>>=6; } ///////////////////////////////////////////////////////////////// void main_screen(){ static boolean flag_exit =0; uint8_t n=84; lcd.setFont(SmallFont); lcd.print("Freq.counter >",LEFT, 8 ); lcd.print("**************",LEFT,24); lcd.print(" Volt meter >",LEFT, 40 ); while(flag_exit==0) { //ждать нажатия кнопки режимов. delay(350); lcd.clrRow(3,n); n-=6; if (n==0) flag_exit=1; if ((PINC&(1<<2))==0){ // если нажата кнопка режимов while ((PINC&(1<<2))==0); freq_meter(); } if ((PINC&(1<<3))==0){ // если нажата кнопка энкодера while ((PINC&(1<<3))==0); volt_meter(); } }// end while }// end main_screenнет ничего такого. бери исходники димакса, выбери плату уно например и заливай с помощью программатора и все))
кстати правильно что свою. я резисторы цапа слишком близко друг другу расположил, неудобно было паять. у меня на 70% спаянная
+ не тестировал, просто включил. вроде работает, экран показывает, режимы переключаются
Доброго времени суток dimax,
воспользовался кодом из поста #6 и появилось 2 вопроса:
1. Даю генератору частоту 0Hz, результат - если сразу после запуска, то не генерит, если до этого была дана частота, генерит предыдущую заданную. Как заставить остановиться по 0?
2. Есть ли какая возможность перевесить выход с D9 на D3?
"Терминальная" версия генератора. Запустить терминалку, послать требуемую частоту в герцах. В ответ напишется та частота, которую смог сделать МК.
void setup() { Serial.begin(9600); pinMode (9,OUTPUT); // выход генератора TCCR1A=0;TCCR1B=0; } void loop() { static uint32_t reqfreq=0; //переменная запроса частоты uint32_t ocr=OCR1A; uint16_t divider=1; float freq; if (Serial.available() > 0){ reqfreq = Serial.parseInt(); if (reqfreq==0 || reqfreq>F_CPU/2) {return;} ocr = (F_CPU / reqfreq /2 /divider); byte shifts[] = {3,3,2,2}; for(byte i = 0; i < 4; i++){ if (ocr > 65536) { divider <<= shifts[i]; ocr = F_CPU / reqfreq /2 /divider; } else { TCCR1B = (i+1)|(1<<WGM12); break; } } //Mode4 (CTC) OCR1A=ocr-1; TCCR1A=1<<COM1A0; freq= (float) F_CPU/2 / (OCR1A+1) /divider; if (freq <10000) { Serial.print(freq,1);Serial.println(" Hz "); } if (freq >=10000) { Serial.print(freq/1000,3);Serial.println(" kHz");} } }Сделал новую версию генератора полностью на новом железе. (Все картинки кликабельны).
На написание ушёл месяц -учитывая что было освоение доселе неизвестного МК, а программа вышла под 400 строк - то наверное это не долго :) В качестве контроллера использована плата на МК STM32F103C8T6, в качестве среды программирования всё тот-же Arduino IDE, но с установленным аддоном для stm32. О плате, и о том как поставить аддон и зашить в неё USB-бутлоадер красочно рассказал наш коллега HWMan, так что освещать этот вопрос не буду. Так же использовал современный TFT дисплей на контроллере st7735, что-б устройство было посимпатичнее. Строка для поиска дисплея на Али 1.8" inch TFT LCD Display module ST7735S 128x160 От кнопки для переключения режимов отказался. Теперь всё делает энкодер. Из-за особенностей обработчика нужно что-бы энкодер выдавал на один щелчок один или два полных импульса. Классический энкодер ардуинщиков ky-040 выдаёт половинку импульса, поэтому крайне не рекомендуется. Он работать будет, но срабатывать станет через щелчок. Строка для поиска на Али: "Rotary encoder EC11"
Тех. характеристики:
Генерация прямоугольного сигнала от 1Гц до 36МГц. От 1Гц д 8кГц возможен шаг изменения 1Герц, далее мин.шаг увеличивается в соответствии с делением тактовой частоты (72МГц) на целочисленные делители. Для понимания масштаба: на 20кГц шаг уже 6 Герц, на 50 кГц -35гц, на 100кГц - 140Гц на 500кГц - шаг уже 3,5кГц. на 1МГц -шаг 14кГц. Последние частоты в шкале возможных, на которых будет работать генератор такие: 8Мгц, 9МГц, 10.286МГц,12МГц, 14.4МГц, 18МГц, 24МГц, 36МГц Регулировка шага изменения частоты. 1-10-100-1000 Герц В pwm-режиме на частотах свыше 8кГц шаг уже не означает чёткую привязку к Герцам, т.к. шаг тут перепривязан к делителю таймера. Регулировка заполнения( далее duty) в процентах. До частоты 720кГц с шагом 1%. При изменении частоты выбранный duty сохраняется. Выше 720кГц шаг начинает возрастать, но вращение энкодера по прежнему меняет по 1% , т.е. возникает пустой ход. Этот момент я не исправлял в связи с ненадобностью такой регулировки на высоких частотах. duty при регулировке частоты свыше 720кГц сбрасыватся на 50%, но в режиме регулировки duty его снова можно изменить. В режиме DDS возможен выбор следующих форм сигналов: синус, треугольник, пила двух видов, меандр. В этом режиме шаг чётко соответсвует фактическому шагу в герцах. Частота условно ограничена на 200кГц. Ограничение можно изменить в 259 строке. До 100кГц сигнал вполне приличный, без существенных изломов. О фактической разрядности DDS уточнение будет ниже. На осцилограмме синус 150кГц
Частотометр. Измеряемый диапазон от 1Герца до 32МГц. Менее одного герца -покажет 0. Более 33МГц начнёт врать. Относительная точность измерения на частотах до 1кГц -примерно 0,1Гц Выше 1кГц -1 Герц. Вольтметр как в версии 2.4 не стал делать за не надобностью. Да и встроенного опорника в этом МК нету. Но свободный аналоговый вход если что остался (PB1). Теоретически возможно дописать в устройство и функцию осциллографа, - благо и флеша и памяти ещё очень много. Но мне он не нужен, а в образовательных целях ломливо.. так что функционально проект развиваться дальше не будет :(
Интерфейсы.
Устройство имеет: вход частотометра. Ввиду отсутствия каких-либо входных цепей подразумевается подача TTL уровней (размахом 3..5 вольт). Два комплементарных выхода таймера (A8 и A7). И выход DDS. Один из комплементарных выходов таймера (А7) физически сидит на старшем бите порта АЦП, поэтому в PWM Mode можно снимать сигнал таймера как с пина A7, так и с выхода R2R АЦП, но стоит учитывать, что фронты на высоких частотах будут сильно сглаживаться. Поэтому для работы с В.Ч. предпочтительней использовать отдельный выход с пина. Выход PWM2 по умолчанию негатив, его можно изменить на позитив в первом дефайне программы. Комплементарные выходы можно сделать с настраиваемым dead-таймом, но за отсутствием надобности и в связи с тем, что это не увязывается с регулировкой заполнения, я в программе не использовал такую возможность. Но пример использования оставил в закомментированной строчке 293. В ней максимальный дидтайм, и выглядит это вот так:
На дисплее отображается название режима, частота. В режимах PWMMode, Duty, Freq.meter так же отображатся условная осциллограмма , тайминги, и процент заполнения. В DDS режимах отображается статическая картинка режима. Так же добавлен электромагнитный излучатель (без встроенного генератора!) для "озвучивания" энкодера. Каждое переключение (частота/режим/ шаг) сопровождается звуковым сигналом соответствующей тональности. С озвучкой работа с генератором стала удобнее. Уровни выходных сигналов: С выходов PWM -стандартный TTL 3.3v . С выхода DDS 1,2v амплитудное (0,9v rms) -это конечно маловато, но я не вспомнил случаев когда мне требовалось напряжение более 1 вольта. А вот менее вольта -часто требовалось, поэтому установил ещё подстроечный резистор на всякий случай. Если его не ставить, то напряжение будет выше. В идеале конечно стоит установить ОУ на выход DDS что б усилить сигнал по напряжению и по току. Мне было лень..) Монтаж: когда развёл в спринте половину платы то понял, что кол-во перемычек уже превышает здравый смысл. Нужно было перерисовывать на 2х сторонний вариант. Это показалось долгим и нудным, поэтому сделал просто на макетке, и все соединения МГТФом. На разведённой печатке только матрица R2R.
Управление:
Вращение ручки энкодера меняет частоту с установленным шагом. Если вращать с нажатой ручкой то будет меняться режим работы Если просто нажать и отпустить ручку - меняется шаг. Если в режиме Duty нажать и отпустить -перейдёт обратно в режим PWM Mode
Схема:
Что бы не делать кучу разъёмов можно использовать реле, которое будет коммутировать выходы на разъём в зависимости от текущего режима.
Программа:
pwm генератор работает на тех-же принципах что и в предыдущей версии, изменения коснулись сущих мелочей. DDS генератор благодаря 32-битному МК упростился, теперь вставка на ассемблере не нужна. Алгоритм написан буквально в одной строчке на Си. Синус и прочие формы рассчитываются перед запуском режима автоматически и записываются в буфер на 512 байт. Скорость отправки на R2R АЦП примерно 3M Samples/sec Что в два раза больше, чем было на меге328. Соответссно полная таблица wave-формы может считываться до частоты ~6кГц На более высоких частотах алгоритм начинает пропуски элементов таблицы. Нетрудно подсчитать число фактических градаций сигнала: нужно 3М разделить на частоту сигнала. Частотометр изменяет частоту в два захода. В первом измеряется количество тактов входящего сигнала за 1 секунду - в этом режиме трудятся в симбиозе аж три таймера. Первый даёт разрешение на счёт в течении 1 секунды второму таймеру. Второй досчитав до 65535 даёт такт третьему таймеру, а сам продолжает с ноля. Всё без использования прерываний. Таким образом суммарное разрешение таймеров в этом режиме 32 бита. Если измеренная частота оказалась ниже 1МГц, то вторым этапом измеряется длина единицы и ноля, вычисляется duty. Я не придавал особого значения точности, второй режим как дополнительный для украшения дисплея псевдо-осциллограмкой, которая отображает только соотношения распознанных 0 и 1 , но не форму сигнала. Но частоты менее 1кГц он измеряет точнее чем первый проход. Поэтому как конечный результат измерения в этом случае выводится результат второго захода. Программа написана с использованием возможностей Maple Library Это библиотека поддержки перефирии STM32 от компании Лифлабс, производителя ардуино-образных плат на мк STM, которая уже входит в состав аддонa для Arduino IDE. Для дисплея используется две библиотеки. Одна (Adafruit_GFX) уже есть в составе аддона. Вторую нужно поставить дополнительно, брать тут.
/*Генератор с регулируемой частотой v3.0 (C)Dimax */ #define pwm2_polar 0 //полярность выхода PWM2 #define paper 0x000000 // цвет фона экрана #include <Adafruit_GFX_AS.h> // Core graphics library #include <Adafruit_ST7735.h> // Hardware-specific library #include <SPI.h> Adafruit_ST7735 tft = Adafruit_ST7735(PB12, PB11,PB10); volatile int enc_tic=0, duty_in=50, divider, mon_flag, modebit=1, tim_mode=0, mode=0; volatile int encstep=1; //шаг изменения частоты по умолчанию volatile float freq=1000; //частота по умолчанию volatile float duty_out;// переменная счёта скважности float t_hi, t_low; //переменные счёта длины импульсов uint8_t wave[512]; //массив для DDS синтеза byte sine_logo[]={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() { SPI.setModule(2);// выбор SPI2 tft.initR(INITR_BLACKTAB); tft.setRotation(3);//дисплей горизонтально, контакты слева tft.fillScreen(paper);//залить цветом по умолчанию tft.setTextWrap(0);//не переосить строки nvic_irq_disable_all();//отключить все прерывания 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 pinMode(PB0,PWM); //buzzer pinMode(PB6,INPUT_PULLUP);//enc pinMode(PB7,INPUT_PULLUP);//enc pinMode(PB8,INPUT_PULLUP);//key enc mytone(1000,50);//сигнал после старта ///////// Таймер4 -обработчик энкодера TIMER4_BASE->CR1=(1<<2)|(1<<9);//CKD10 URS TIMER4_BASE->CCMR1=0xf1f1; (1<<0)|(1<<8 );//cc1s, cc2s input mapped TI1/TI2 TIMER4_BASE->CCER=(1<<1)|(1<<5)|(1<<0)|(1<<4);//Capture/Compare 1,2 output polarity TIMER4_BASE->SMCR=(1<<1)|(1<<0);//Encoder mode3(SMS bit)стр.407 TIMER4_BASE->CNT=0; TIMER4_BASE->ARR=1;//ограничение счёта ( =3 для двухимпульсного энкодера) TIMER4_BASE->SR=0; TIMER4_BASE->EGR=1; timer_attach_interrupt(TIMER4, TIMER_UPDATE_INTERRUPT, enc_int); TIMER4_BASE->CR1|=(1<<0);//запуск //прерывание кнопки энкодера attachInterrupt(PB8, key_enc_int, RISING); timer_set(); } void loop() { static int old_mode_loop=-1; if (freq > 8485) {tim_mode=1;} else {tim_mode=0;}// переключать режимы таймера if (mode!=old_mode_loop) { tft.fillScreen(paper); old_mode_loop=mode; mon_flag=1; }//чистить полностью экран только при смене режимов if (mode==7) {mon_out(); freq_meter(); } if (mode >1 && mode<7) { mon_out(); dds_set(); } // запуск DDS режимов if (mon_flag) { 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=1999; // 72000000/20000= 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>1E6){ t_low=0;t_hi=0; duty_out=50;return;} //выйти если freq больше мегагерца // Перенастройка таймера 2 в режии измерения длительности импульса и скважности divider=1; while ((72E6/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; //перечитать регистры. TIMER2_BASE->CR1=(1<<0); __asm volatile( "cpsie i" ); while( (TIMER2_BASE->SR&0x65F)!=0x65F) {asm volatile("nop"); if(mon_flag) {return;} } TIMER2_BASE->CR1=0;// стоп таймер imp_long=(uint32_t) ((TIMER2_BASE->CCR1)*divider); imp_hi=(uint32_t) ((TIMER2_BASE->CCR2)*divider); if (freq <1000){ freq=72E6 /imp_long;} //если freq Менее 1кГц то использовать данные второго НЧ-измерения частоты duty_out= (float) imp_hi / (imp_long / 100.0) ; t_low= (imp_long-imp_hi) /72.0 ; t_hi= imp_hi /72.0; mon_flag=1; } //END freq meter /////////////////////////////////////////////////////////////////////////// /////////*********** ВЫВОД НА ДИСПЛЕЙ************////////////////////////// /////////////////////////////////////////////////////////////////////////// void mon_out(){ char mybuf[10]; float freq_out; //************** Вывод первой строчки***************************** tft.setCursor(0, 0); // вперёд, вниз tft.setTextColor(ST7735_GREEN, paper); tft.setTextSize(2); switch(mode){ case 0: tft.print(" PWM Mode "); break; case 1: tft.print(" Duty Mode "); break; case 2: tft.print(" Sinus DDS "); break; case 3: tft.print(" Triangle DDS"); break; case 4: tft.print(" Pila1 DDS "); break; case 5: tft.print(" Pila2 DDS "); break; case 6: tft.print(" Meandr DDS "); break; case 7: tft.print(" Freq. meter "); break; } //*****************Вывод второй строчки***************************** tft.setTextColor(ST7735_WHITE, paper); tft.setTextSize(3); tft.setCursor(0, 19); if (freq >=1E6) { freq_out=freq/1E6;} else if (freq>=1E4) { freq_out=freq/1000;} else { freq_out=freq;} if (mode!=7 && freq <10000) { dtostrf(freq_out, 6, 0, mybuf); tft.print(mybuf); tft.print(" "); } else {dtostrf(freq_out, 8, 3, mybuf); tft.print(mybuf);} //********************Вывод третьей строчки***************************** tft.setCursor(50, 43); // вперёд, вниз tft.setTextColor(ST7735_RED,paper); if(freq>=1E6){tft.print("MHz");} else if (freq>=1E4){tft.print("kHz");} else {tft.print(" Hz");} //********************* "осциллограммы"****************************** tft.fillRect(5,90, 100,38,paper);//вправо, вниз, ширина вправо, длина вниз tft.drawRect(0,67, 160,61,ST7735_MAGENTA);//вправо, вниз, ширина вправо, длина вниз if (mode<2 || mode==7){ 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==2){ // логотип синуса for(uint8_t n=0; n<100; n++){tft.drawPixel(5+n, 73+sine_logo[n],ST7735_YELLOW); } //END for } // END if (mode==2) else if (mode==3){// логотип треугольника 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==3 else if (mode==4){ //логотип пилы1 tft.drawLine(5,123,105,73,ST7735_YELLOW); tft.drawFastVLine(105, 73, 50, ST7735_YELLOW);//спад конец такта статическая вер. линия } //END if (mode==3) else if (mode==5){//логотип пилы2 tft.drawFastVLine(5, 73, 50, ST7735_YELLOW); // восход фронта статическая вер линия tft.drawLine(5,73,105,123,ST7735_YELLOW); }// END if (mode==4) else if (mode==6){ //логотип меандра 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 < 2 || mode==7){ 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==7) if (mode!=7){ // выводить шаг кроме частотометра tft.setCursor(114, 95); tft.print("Step="); tft.setCursor(114, 105); switch (encstep) { case 1: tft.print(" 1"); break; case 10: tft.print(" 10"); break; case 100: tft.print(" 100");break; case 1000: tft.print("1000");break; }// END switch case } // END if (mode!=7) if ( freq < 1) {tft.fillRect(1,68, 158,59,paper); } mon_flag=0; }//END mon_out //обработчик прерываний энкодера void enc_int(){ if((TIMER4_BASE->CR1)&1<<4) {enc_mode(1);} else {enc_mode(-1);} }// END VOID // ФУНКЦИЯ конфигурации режимов void enc_mode(int in){ modebit= (GPIOB_BASE->IDR&0x100); //состояние кнопки. 0-нажата if (!modebit) { mytone(880,30); //звук переключения режимов mode+=in; if(mode>7){mode=7;} if(mode<0){mode=0;} if (mode<2){timer_set();} if (mode==7) {freq=0;} mon_flag=1; return; } mytone(4400,10); //звук изменения частоты switch(mode){ case 0: if (tim_mode==0) { freq+=(encstep*in); timer_set(); } else { timer_hi_set(in); } break; case 1: duty_in+=in; if(duty_in>99){duty_in=99;} if(duty_in<1){duty_in=1;} set_duty(); calc_freq(); break; case 7: break; // в частотометре не реагировать на вращение энкодера default: freq+=(encstep*in); mon_flag=1; //wave режимы // проверка на корректность запроса частоты if (freq<0){freq=0;} if (freq>2E5) {freq=2E5;} } //end switch case }//end enc_mode // обработчик кнопки энкодера void key_enc_int(){ if(!modebit){modebit=1; mon_flag=1;return;}// если менялся режим -выйти if (mode==1) { mytone(880,30); mode=0; mon_flag=1; ;return;} //сменить режим и выйти если были в duty mode mytone(220,75); //звук переключения шага switch (encstep){ case 1: encstep=10; break; case 10: encstep=100; break; case 100: encstep=1000; break; case 1000: encstep=1; break; } mon_flag=1; //флаг вывода на дисплей }//end void set_duty(){ if (mode==0 && TIMER1_BASE->ARR<100){ TIMER1_BASE->CCR1=(TIMER1_BASE->ARR+1)/2 ; duty_in=50; return; } TIMER1_BASE->CCR1=(TIMER1_BASE->ARR+1)*duty_in/100; } //ОБЩАЯ НАСТРОЙКА ТАЙМЕРА void timer_set(){ GPIOA_BASE->CRL=0xB4444444; //PA7 alt_output GPIOA_BASE->CRH=0x4444444B; //PA8 alt_output TIMER1_BASE->CCMR1=(1<<6)|(1<<5)|(1<<3);//PWM mode 1 TIMER1_BASE->CCMR2=0;TIMER1_BASE->PSC=0; TIMER1_BASE->CCR2=0; TIMER1_BASE->ARR=1; TIMER1_BASE->CR1=1; TIMER1_BASE->CCER=(1<<0)|(1<<2)|(pwm2_polar<<3);//cc1e/cc1ne enable //TIMER1_BASE->BDTR=(1<<15)| 255 ;// dead time sample if (freq<1){freq=0; TIMER1_BASE->CCR1=0;TIMER1_BASE->ARR= 1; mon_flag=1; return;} if (freq>36E6) {freq=36E6;} divider=1; int tim_arr = 72E6/freq; while ( (tim_arr/divider) > 65535) {divider++;} TIMER1_BASE->PSC=divider-1; TIMER1_BASE->ARR=(tim_arr/divider)-1; set_duty(); calc_freq(); } //НАСТРОЙКА ТАЙМЕРА НА ЧАСТОТАХ ВЫШЕ 8КГЦ void timer_hi_set(int arr){ __asm volatile( "cpsid i" ); GPIOA_BASE->CRL=0xB4444444; //PA7 alt_output GPIOA_BASE->CRH=0x4444444B; //PA8 alt_output TIMER1_BASE->CCMR1=(1<<6)|(1<<5)|(1<<3);//PWM mode 1 TIMER1_BASE->CCER=(1<<0)|(1<<2)|(pwm2_polar<<3);//cc1e/cc1ne enable //насильно уменьшить шаг с ростом частоты if (TIMER1_BASE->ARR<1000 && encstep > 100) encstep=100; if (TIMER1_BASE->ARR<100 && encstep > 10) encstep=10; if (TIMER1_BASE->ARR<10 && encstep > 1) encstep=1; arr*=encstep; int icr= TIMER1_BASE->ARR-=arr; if (icr<1) {icr=1;} if (icr>65535) {icr=65535;} TIMER1_BASE->ARR=icr; __asm volatile( "cpsie i" ); set_duty(); calc_freq(); } // РАСЧЁТ ЧАСТОТЫ И ТАЙМИНГОВ В РЕЖИМАХ PWM void calc_freq(){ uint32_t imp_long, imp_hi; freq= 72E6/((TIMER1_BASE->ARR+1)*divider); duty_out= (float) TIMER1_BASE->CCR1 / ((TIMER1_BASE->ARR+1) / 100.0) ; 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) /72.0 ; t_hi= imp_hi /72.0; mon_flag=1; } // КОНФИГУРАЦИЯ DDS РЕЖИМОВ void dds_set(){ static uint32_t akkum; static byte oldmode=255; TIMER1_BASE->CCER=0; //timer output pins disable && перечитать регистры. GPIOA_BASE->CRL = 0x33333333;// pa0-pa7 выход GPIOA_BASE->CRH =0x44444444; //pa8-pa15 вход if (oldmode !=mode) { if (mode==2) {for(uint16_t n=0; n<512; n++){wave[n]=255*(sin(TWO_PI*float(n)/512)+1)/2 ;}}// синус else if (mode==3){ for(uint16_t n=0; n<512; n++){if (n<256){ wave[n]=n;} else {wave[n]=(511-n);}}}//треугол else if (mode==4){ for(uint16_t n=0; n<512; n++){ wave[n]=(n>>1);}} //пила1 else if (mode==5){ for(uint16_t n=0; n<512; n++){ wave[n]=((~n)>>1);}} //пила2 else if (mode==6){ 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 * 1670.1100841768461768461768461768;// шаг= частота*коэффициент while(!mon_flag){GPIOA_BASE->ODR=wave[(akkum+=dds_shag)>>23];} //генерация DDS GPIOA_BASE->CRL=0x44444444;// все пины в Z }//END DDS set() // void mytone(int frq, int ms ){ if (mode==7){return;} //таймер в режиме частотометра занят uint16_t psc=1; uint32_t tim_arr; // настройка генератора звука на таймере3 tim_arr = 36E6/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 = 72E3 * 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" ); }Убедительная просьба не использовать кнопку "Цитировать" под этим постом, т.к. у меня пропадёт возможность редактировать ошибки, да и просто засирать тему повторной информацией не стоит.
V3_128
Переделал под 128х128 экран (st7735 ) , цвета синий с красным попутаны. Но оно и не мешает.
[code] /*Генератор с регулируемой частотой v3.0 (C)Dimax */ #define pwm2_polar 0 //полярность выхода PWM2 #define paper 0x000000 // цвет фона экрана #include <Adafruit_GFX_AS.h> // Core graphics library #include <Adafruit_ST7735.h> // Hardware-specific library #include <SPI.h> Adafruit_ST7735 tft = Adafruit_ST7735(PB12, PB11,PB10); volatile int enc_tic=0, duty_in=50, divider, mon_flag, modebit=1, tim_mode=0, mode=0; volatile int encstep=1; //шаг изменения частоты по умолчанию volatile float freq=1000; //частота по умолчанию volatile float duty_out;// переменная счёта скважности float t_hi, t_low; //переменные счёта длины импульсов uint8_t wave[512]; //массив для DDS синтеза byte sine_logo[]={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() { SPI.setModule(2);// выбор SPI2 tft.initR(INITR_BLACKTAB); tft.setRotation(3);//дисплей горизонтально, контакты слева tft.fillScreen(paper);//залить цветом по умолчанию tft.setTextWrap(0);//не переосить строки nvic_irq_disable_all();//отключить все прерывания 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 pinMode(PB0,PWM); //buzzer pinMode(PB6,INPUT_PULLUP);//enc pinMode(PB7,INPUT_PULLUP);//enc pinMode(PB8,INPUT_PULLUP);//key enc mytone(1000,50);//сигнал после старта ///////// Таймер4 -обработчик энкодера TIMER4_BASE->CR1=(1<<2)|(1<<9);//CKD10 URS TIMER4_BASE->CCMR1=0xf1f1; (1<<0)|(1<<8 );//cc1s, cc2s input mapped TI1/TI2 TIMER4_BASE->CCER=(1<<1)|(1<<5)|(1<<0)|(1<<4);//Capture/Compare 1,2 output polarity TIMER4_BASE->SMCR=(1<<1)|(1<<0);//Encoder mode3(SMS bit)стр.407 TIMER4_BASE->CNT=0; TIMER4_BASE->ARR=1;//ограничение счёта ( =3 для двухимпульсного энкодера) TIMER4_BASE->SR=0; TIMER4_BASE->EGR=1; timer_attach_interrupt(TIMER4, TIMER_UPDATE_INTERRUPT, enc_int); TIMER4_BASE->CR1|=(1<<0);//запуск //прерывание кнопки энкодера attachInterrupt(PB8, key_enc_int, RISING); timer_set(); } void loop() { static int old_mode_loop=-1; if (freq > 8485) {tim_mode=1;} else {tim_mode=0;}// переключать режимы таймера if (mode!=old_mode_loop) { tft.fillScreen(paper); old_mode_loop=mode; mon_flag=1; }//чистить полностью экран только при смене режимов if (mode==7) {mon_out(); freq_meter(); } if (mode >1 && mode<7) { mon_out(); dds_set(); } // запуск DDS режимов if (mon_flag) { 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=1999; // 72000000/20000= 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>1E6){ t_low=0;t_hi=0; duty_out=50;return;} //выйти если freq больше мегагерца // Перенастройка таймера 2 в режии измерения длительности импульса и скважности divider=1; while ((72E6/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; //перечитать регистры. TIMER2_BASE->CR1=(1<<0); __asm volatile( "cpsie i" ); while( (TIMER2_BASE->SR&0x65F)!=0x65F) {asm volatile("nop"); if(mon_flag) {return;} } TIMER2_BASE->CR1=0;// стоп таймер imp_long=(uint32_t) ((TIMER2_BASE->CCR1)*divider); imp_hi=(uint32_t) ((TIMER2_BASE->CCR2)*divider); if (freq <1000){ freq=72E6 /imp_long;} //если freq Менее 1кГц то использовать данные второго НЧ-измерения частоты duty_out= (float) imp_hi / (imp_long / 100.0) ; t_low= (imp_long-imp_hi) /72.0 ; t_hi= imp_hi /72.0; mon_flag=1; } //END freq meter /////////////////////////////////////////////////////////////////////////// /////////*********** ВЫВОД НА ДИСПЛЕЙ************////////////////////////// /////////////////////////////////////////////////////////////////////////// void mon_out(){ char mybuf[10]; float freq_out; //************** Вывод первой строчки***************************** tft.setCursor(0, 0); // вперёд, вниз tft.setTextColor(ST7735_GREEN, paper); tft.setTextSize(2); switch(mode){ case 0: tft.print(" PWM Mode "); break; case 1: tft.print("Duty Mode "); break; case 2: tft.print("Sinus DDS "); break; case 3: tft.print("Trian DDS"); break; case 4: tft.print("Pila1 DDS "); break; case 5: tft.print("Pila2 DDS "); break; case 6: tft.print("Meandr DDS "); break; case 7: tft.print("Freq meter "); break; } //*****************Вывод второй строчки***************************** tft.setTextColor(ST7735_WHITE, paper); tft.setTextSize(2); tft.setCursor(0, 19); if (freq >=1E6) { freq_out=freq/1E6;} else if (freq>=1E4) { freq_out=freq/1000;} else { freq_out=freq;} if (mode!=7 && freq <10000) { dtostrf(freq_out, 6, 0, mybuf); tft.print(mybuf); tft.print(" "); } else {dtostrf(freq_out, 8, 3, mybuf); tft.print(mybuf);} //********************Вывод третьей строчки***************************** tft.setCursor(30, 43); // вперёд, вниз tft.setTextColor(ST7735_RED,paper); if(freq>=1E6){tft.print("MHz");} else if (freq>=1E4){tft.print("kHz");} else {tft.print(" Hz");} //********************* "осциллограммы"****************************** tft.fillRect(5,90, 100,38,paper);//вправо, вниз, ширина вправо, длина вниз tft.drawRect(0,67, 128,61,ST7735_MAGENTA);//вправо, вниз, ширина вправо, длина вниз if (mode<2 || mode==7){ 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==2){ // логотип синуса for(uint8_t n=0; n<100; n++){tft.drawPixel(5+n, 73+sine_logo[n],ST7735_YELLOW); } //END for } // END if (mode==2) else if (mode==3){// логотип треугольника 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==3 else if (mode==4){ //логотип пилы1 tft.drawLine(5,123,105,73,ST7735_YELLOW); tft.drawFastVLine(105, 73, 50, ST7735_YELLOW);//спад конец такта статическая вер. линия } //END if (mode==3) else if (mode==5){//логотип пилы2 tft.drawFastVLine(5, 73, 50, ST7735_YELLOW); // восход фронта статическая вер линия tft.drawLine(5,73,105,123,ST7735_YELLOW); }// END if (mode==4) else if (mode==6){ //логотип меандра 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 < 2 || mode==7){ 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(60, 95); tft.print("Duty = "); // tft.setCursor(60, 105); tft.print(duty_out,0);tft.print("%"); } //END if (mode < 2 || mode==7) if (mode!=7){ // выводить шаг кроме частотометра tft.setCursor(14, 95); tft.print("Step="); tft.setCursor(14, 105); switch (encstep) { case 1: tft.print(" 1"); break; case 10: tft.print(" 10"); break; case 100: tft.print(" 100");break; case 1000: tft.print("1000");break; }// END switch case } // END if (mode!=7) if ( freq < 1) {tft.fillRect(1,68, 158,59,paper); } mon_flag=0; }//END mon_out //обработчик прерываний энкодера void enc_int(){ if((TIMER4_BASE->CR1)&1<<4) {enc_mode(1);} else {enc_mode(-1);} }// END VOID // ФУНКЦИЯ конфигурации режимов void enc_mode(int in){ modebit= (GPIOB_BASE->IDR&0x100); //состояние кнопки. 0-нажата if (!modebit) { mytone(880,30); //звук переключения режимов mode+=in; if(mode>7){mode=7;} if(mode<0){mode=0;} if (mode<2){timer_set();} if (mode==7) {freq=0;} mon_flag=1; return; } mytone(4400,10); //звук изменения частоты switch(mode){ case 0: if (tim_mode==0) { freq+=(encstep*in); timer_set(); } else { timer_hi_set(in); } break; case 1: duty_in+=in; if(duty_in>99){duty_in=99;} if(duty_in<1){duty_in=1;} set_duty(); calc_freq(); break; case 7: break; // в частотометре не реагировать на вращение энкодера default: freq+=(encstep*in); mon_flag=1; //wave режимы // проверка на корректность запроса частоты if (freq<0){freq=0;} if (freq>2E5) {freq=2E5;} } //end switch case }//end enc_mode // обработчик кнопки энкодера void key_enc_int(){ if(!modebit){modebit=1; mon_flag=1;return;}// если менялся режим -выйти if (mode==1) { mytone(880,30); mode=0; mon_flag=1; ;return;} //сменить режим и выйти если были в duty mode mytone(220,75); //звук переключения шага switch (encstep){ case 1: encstep=10; break; case 10: encstep=100; break; case 100: encstep=1000; break; case 1000: encstep=1; break; } mon_flag=1; //флаг вывода на дисплей }//end void set_duty(){ if (mode==0 && TIMER1_BASE->ARR<100){ TIMER1_BASE->CCR1=(TIMER1_BASE->ARR+1)/2 ; duty_in=50; return; } TIMER1_BASE->CCR1=(TIMER1_BASE->ARR+1)*duty_in/100; } //ОБЩАЯ НАСТРОЙКА ТАЙМЕРА void timer_set(){ GPIOA_BASE->CRL=0xB4444444; //PA7 alt_output GPIOA_BASE->CRH=0x4444444B; //PA8 alt_output TIMER1_BASE->CCMR1=(1<<6)|(1<<5)|(1<<3);//PWM mode 1 TIMER1_BASE->CCMR2=0;TIMER1_BASE->PSC=0; TIMER1_BASE->CCR2=0; TIMER1_BASE->ARR=1; TIMER1_BASE->CR1=1; TIMER1_BASE->CCER=(1<<0)|(1<<2)|(pwm2_polar<<3);//cc1e/cc1ne enable //TIMER1_BASE->BDTR=(1<<15)| 255 ;// dead time sample if (freq<1){freq=0; TIMER1_BASE->CCR1=0;TIMER1_BASE->ARR= 1; mon_flag=1; return;} if (freq>36E6) {freq=36E6;} divider=1; int tim_arr = 72E6/freq; while ( (tim_arr/divider) > 65535) {divider++;} TIMER1_BASE->PSC=divider-1; TIMER1_BASE->ARR=(tim_arr/divider)-1; set_duty(); calc_freq(); } //НАСТРОЙКА ТАЙМЕРА НА ЧАСТОТАХ ВЫШЕ 8КГЦ void timer_hi_set(int arr){ __asm volatile( "cpsid i" ); GPIOA_BASE->CRL=0xB4444444; //PA7 alt_output GPIOA_BASE->CRH=0x4444444B; //PA8 alt_output TIMER1_BASE->CCMR1=(1<<6)|(1<<5)|(1<<3);//PWM mode 1 TIMER1_BASE->CCER=(1<<0)|(1<<2)|(pwm2_polar<<3);//cc1e/cc1ne enable //насильно уменьшить шаг с ростом частоты if (TIMER1_BASE->ARR<1000 && encstep > 100) encstep=100; if (TIMER1_BASE->ARR<100 && encstep > 10) encstep=10; if (TIMER1_BASE->ARR<10 && encstep > 1) encstep=1; arr*=encstep; int icr= TIMER1_BASE->ARR-=arr; if (icr<1) {icr=1;} if (icr>65535) {icr=65535;} TIMER1_BASE->ARR=icr; __asm volatile( "cpsie i" ); set_duty(); calc_freq(); } // РАСЧЁТ ЧАСТОТЫ И ТАЙМИНГОВ В РЕЖИМАХ PWM void calc_freq(){ uint32_t imp_long, imp_hi; freq= 72E6/((TIMER1_BASE->ARR+1)*divider); duty_out= (float) TIMER1_BASE->CCR1 / ((TIMER1_BASE->ARR+1) / 100.0) ; 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) /72.0 ; t_hi= imp_hi /72.0; mon_flag=1; } // КОНФИГУРАЦИЯ DDS РЕЖИМОВ void dds_set(){ static uint32_t akkum; static byte oldmode=255; TIMER1_BASE->CCER=0; //timer output pins disable && перечитать регистры. GPIOA_BASE->CRL = 0x33333333;// pa0-pa7 выход GPIOA_BASE->CRH =0x44444444; //pa8-pa15 вход if (oldmode !=mode) { if (mode==2) {for(uint16_t n=0; n<512; n++){wave[n]=255*(sin(TWO_PI*float(n)/512)+1)/2 ;}}// синус else if (mode==3){ for(uint16_t n=0; n<512; n++){if (n<256){ wave[n]=n;} else {wave[n]=(511-n);}}}//треугол else if (mode==4){ for(uint16_t n=0; n<512; n++){ wave[n]=(n>>1);}} //пила1 else if (mode==5){ for(uint16_t n=0; n<512; n++){ wave[n]=((~n)>>1);}} //пила2 else if (mode==6){ 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 * 1670.1100841768461768461768461768;// шаг= частота*коэффициент while(!mon_flag){GPIOA_BASE->ODR=wave[(akkum+=dds_shag)>>23];} //генерация DDS GPIOA_BASE->CRL=0x44444444;// все пины в Z }//END DDS set() // void mytone(int frq, int ms ){ if (mode==7){return;} //таймер в режиме частотометра занят uint16_t psc=1; uint32_t tim_arr; // настройка генератора звука на таймере3 tim_arr = 36E6/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 = 72E3 * 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" ); } [/code]DIMAX СПАСИБО !
Т.К. энкодер у меня не тот , прилепил кнопки.
[code] /*Генератор с регулируемой частотой v3.0 (C)Dimax IVL (240265) Добавил кнопки. Экран 128х128 ST7735 */ #define pwm2_polar 0 //полярность выхода PWM2 #define paper 0x000000 // цвет фона экрана #include <Adafruit_GFX_AS.h> // Core graphics library #include <Adafruit_ST7735.h> // Hardware-specific library #include <SPI.h> Adafruit_ST7735 tft = Adafruit_ST7735(PB12, PB11,PB10); volatile int enc_tic=0, duty_in=50, divider, mon_flag, modebit=1, tim_mode=0, mode=0; volatile int encstep=1; //шаг изменения частоты по умолчанию volatile float freq=1000; //частота по умолчанию volatile float duty_out;// переменная счёта скважности float t_hi, t_low; //переменные счёта длины импульсов uint8_t wave[512]; //массив для DDS синтеза byte sine_logo[]={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() { SPI.setModule(2);// выбор SPI2 tft.initR(INITR_BLACKTAB); tft.setRotation(3);//дисплей горизонтально, контакты слева tft.fillScreen(paper);//залить цветом по умолчанию tft.setTextWrap(0);//не переосить строки nvic_irq_disable_all();//отключить все прерывания 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 pinMode(PB0,PWM); //buzzer pinMode(PB4,INPUT_PULLUP);//key+ pinMode(PB5,INPUT_PULLUP);//key- pinMode(PB6,INPUT_PULLUP);//enc pinMode(PB7,INPUT_PULLUP);//enc pinMode(PB8,INPUT_PULLUP);//key enc mytone(1000,50);//сигнал после старта ///////// Таймер4 -обработчик энкодера TIMER4_BASE->CR1=(1<<2)|(1<<9);//CKD10 URS TIMER4_BASE->CCMR1=0xf1f1; (1<<0)|(1<<8 );//cc1s, cc2s input mapped TI1/TI2 TIMER4_BASE->CCER=(1<<1)|(1<<5)|(1<<0)|(1<<4);//Capture/Compare 1,2 output polarity TIMER4_BASE->SMCR=(1<<1)|(1<<0);//Encoder mode3(SMS bit)стр.407 TIMER4_BASE->CNT=0; TIMER4_BASE->ARR=1;//ограничение счёта ( =3 для двухимпульсного энкодера) TIMER4_BASE->SR=0; TIMER4_BASE->EGR=1; timer_attach_interrupt(TIMER4, TIMER_UPDATE_INTERRUPT, enc_int); TIMER4_BASE->CR1|=(1<<0);//запуск //прерывание кнопки энкодера attachInterrupt(PB8, key_enc_int, RISING); attachInterrupt(PB4, key_plus_int, RISING); attachInterrupt(PB5, key_min_int, RISING); timer_set(); } void loop() { static int old_mode_loop=-1; if (freq > 8485) {tim_mode=1;} else {tim_mode=0;}// переключать режимы таймера if (mode!=old_mode_loop) { tft.fillScreen(paper); old_mode_loop=mode; mon_flag=1; }//чистить полностью экран только при смене режимов if (mode==7) {mon_out(); freq_meter(); } if (mode >1 && mode<7) { mon_out(); dds_set(); } // запуск DDS режимов if (mon_flag) { 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=1999; // 72000000/20000= 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>1E6){ t_low=0;t_hi=0; duty_out=50;return;} //выйти если freq больше мегагерца // Перенастройка таймера 2 в режии измерения длительности импульса и скважности divider=1; while ((72E6/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; //перечитать регистры. TIMER2_BASE->CR1=(1<<0); __asm volatile( "cpsie i" ); while( (TIMER2_BASE->SR&0x65F)!=0x65F) {asm volatile("nop"); if(mon_flag) {return;} } TIMER2_BASE->CR1=0;// стоп таймер imp_long=(uint32_t) ((TIMER2_BASE->CCR1)*divider); imp_hi=(uint32_t) ((TIMER2_BASE->CCR2)*divider); if (freq <1000){ freq=72E6 /imp_long;} //если freq Менее 1кГц то использовать данные второго НЧ-измерения частоты duty_out= (float) imp_hi / (imp_long / 100.0) ; t_low= (imp_long-imp_hi) /72.0 ; t_hi= imp_hi /72.0; mon_flag=1; } //END freq meter /////////////////////////////////////////////////////////////////////////// /////////*********** ВЫВОД НА ДИСПЛЕЙ************////////////////////////// /////////////////////////////////////////////////////////////////////////// void mon_out(){ char mybuf[10]; float freq_out; //************** Вывод первой строчки***************************** tft.setCursor(0, 0); // вперёд, вниз tft.setTextColor(ST7735_GREEN, paper); tft.setTextSize(2); switch(mode){ case 0: tft.print(" PWM Mode "); break; case 1: tft.print("Duty Mode "); break; case 2: tft.print("Sinus DDS "); break; case 3: tft.print("Trian DDS"); break; case 4: tft.print("Pila1 DDS "); break; case 5: tft.print("Pila2 DDS "); break; case 6: tft.print("Meandr DDS "); break; case 7: tft.print("Freq meter "); break; } //*****************Вывод второй строчки***************************** tft.setTextColor(ST7735_WHITE, paper); tft.setTextSize(2); tft.setCursor(0, 19); if (freq >=1E6) { freq_out=freq/1E6;} else if (freq>=1E4) { freq_out=freq/1000;} else { freq_out=freq;} if (mode!=7 && freq <10000) { dtostrf(freq_out, 6, 0, mybuf); tft.print(mybuf); tft.print(" "); } else {dtostrf(freq_out, 8, 3, mybuf); tft.print(mybuf);} //********************Вывод третьей строчки***************************** tft.setCursor(30, 43); // вперёд, вниз tft.setTextColor(ST7735_RED,paper); if(freq>=1E6){tft.print("MHz");} else if (freq>=1E4){tft.print("kHz");} else {tft.print(" Hz");} //********************* "осциллограммы"****************************** tft.fillRect(5,90, 100,38,paper);//вправо, вниз, ширина вправо, длина вниз tft.drawRect(0,67, 128,61,ST7735_MAGENTA);//вправо, вниз, ширина вправо, длина вниз if (mode<2 || mode==7){ 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==2){ // логотип синуса for(uint8_t n=0; n<100; n++){tft.drawPixel(5+n, 73+sine_logo[n],ST7735_YELLOW); } //END for } // END if (mode==2) else if (mode==3){// логотип треугольника 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==3 else if (mode==4){ //логотип пилы1 tft.drawLine(5,123,105,73,ST7735_YELLOW); tft.drawFastVLine(105, 73, 50, ST7735_YELLOW);//спад конец такта статическая вер. линия } //END if (mode==3) else if (mode==5){//логотип пилы2 tft.drawFastVLine(5, 73, 50, ST7735_YELLOW); // восход фронта статическая вер линия tft.drawLine(5,73,105,123,ST7735_YELLOW); }// END if (mode==4) else if (mode==6){ //логотип меандра 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 < 2 || mode==7){ 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(60, 95); tft.print("Duty = "); // tft.setCursor(60, 105); tft.print(duty_out,0);tft.print("%"); } //END if (mode < 2 || mode==7) if (mode!=7){ // выводить шаг кроме частотометра tft.setCursor(14, 95); tft.print("Step="); tft.setCursor(14, 105); switch (encstep) { case 1: tft.print(" 1"); break; case 10: tft.print(" 10"); break; case 100: tft.print(" 100");break; case 1000: tft.print("1000");break; }// END switch case } // END if (mode!=7) if ( freq < 1) {tft.fillRect(1,68, 158,59,paper); } mon_flag=0; }//END mon_out //обработчик прерываний энкодера void enc_int(){ if((TIMER4_BASE->CR1)&1<<4) {enc_mode(1);} else {enc_mode(-1);} }// END VOID // обработчик кнопки + void key_plus_int(){ // delay(1); enc_mode(1); } // обработчик кнопки - void key_min_int(){ // delay(1); enc_mode(-1); } // ФУНКЦИЯ конфигурации режимов void enc_mode(int in){ modebit= (GPIOB_BASE->IDR&0x100); //состояние кнопки. 0-нажата if (!modebit) { mytone(880,30); //звук переключения режимов mode+=in; if(mode>7){mode=7;} if(mode<0){mode=0;} if (mode<2){timer_set();} if (mode==7) {freq=0;} mon_flag=1; return; } mytone(4400,10); //звук изменения частоты switch(mode){ case 0: if (tim_mode==0) { freq+=(encstep*in); timer_set(); } else { timer_hi_set(in); } break; case 1: duty_in+=in; if(duty_in>99){duty_in=99;} if(duty_in<1){duty_in=1;} set_duty(); calc_freq(); break; case 7: break; // в частотометре не реагировать на вращение энкодера default: freq+=(encstep*in); mon_flag=1; //wave режимы // проверка на корректность запроса частоты if (freq<0){freq=0;} if (freq>2E5) {freq=2E5;} } //end switch case }//end enc_mode // обработчик кнопки энкодера void key_enc_int(){ //delay(10); if(!modebit){modebit=1; mon_flag=1;return;}// если менялся режим -выйти if (mode==1) { mytone(880,30); mode=0; mon_flag=1; ;return;} //сменить режим и выйти если были в duty mode mytone(220,75); //звук переключения шага switch (encstep){ case 1: encstep=10; break; case 10: encstep=100; break; case 100: encstep=1000; break; case 1000: encstep=1; break; } mon_flag=1; //флаг вывода на дисплей }//end void set_duty(){ if (mode==0 && TIMER1_BASE->ARR<100){ TIMER1_BASE->CCR1=(TIMER1_BASE->ARR+1)/2 ; duty_in=50; return; } TIMER1_BASE->CCR1=(TIMER1_BASE->ARR+1)*duty_in/100; } //ОБЩАЯ НАСТРОЙКА ТАЙМЕРА void timer_set(){ GPIOA_BASE->CRL=0xB4444444; //PA7 alt_output GPIOA_BASE->CRH=0x4444444B; //PA8 alt_output TIMER1_BASE->CCMR1=(1<<6)|(1<<5)|(1<<3);//PWM mode 1 TIMER1_BASE->CCMR2=0;TIMER1_BASE->PSC=0; TIMER1_BASE->CCR2=0; TIMER1_BASE->ARR=1; TIMER1_BASE->CR1=1; TIMER1_BASE->CCER=(1<<0)|(1<<2)|(pwm2_polar<<3);//cc1e/cc1ne enable //TIMER1_BASE->BDTR=(1<<15)| 255 ;// dead time sample if (freq<1){freq=0; TIMER1_BASE->CCR1=0;TIMER1_BASE->ARR= 1; mon_flag=1; return;} if (freq>36E6) {freq=36E6;} divider=1; int tim_arr = 72E6/freq; while ( (tim_arr/divider) > 65535) {divider++;} TIMER1_BASE->PSC=divider-1; TIMER1_BASE->ARR=(tim_arr/divider)-1; set_duty(); calc_freq(); } //НАСТРОЙКА ТАЙМЕРА НА ЧАСТОТАХ ВЫШЕ 8КГЦ void timer_hi_set(int arr){ __asm volatile( "cpsid i" ); GPIOA_BASE->CRL=0xB4444444; //PA7 alt_output GPIOA_BASE->CRH=0x4444444B; //PA8 alt_output TIMER1_BASE->CCMR1=(1<<6)|(1<<5)|(1<<3);//PWM mode 1 TIMER1_BASE->CCER=(1<<0)|(1<<2)|(pwm2_polar<<3);//cc1e/cc1ne enable //насильно уменьшить шаг с ростом частоты if (TIMER1_BASE->ARR<1000 && encstep > 100) encstep=100; if (TIMER1_BASE->ARR<100 && encstep > 10) encstep=10; if (TIMER1_BASE->ARR<10 && encstep > 1) encstep=1; arr*=encstep; int icr= TIMER1_BASE->ARR-=arr; if (icr<1) {icr=1;} if (icr>65535) {icr=65535;} TIMER1_BASE->ARR=icr; __asm volatile( "cpsie i" ); set_duty(); calc_freq(); } // РАСЧЁТ ЧАСТОТЫ И ТАЙМИНГОВ В РЕЖИМАХ PWM void calc_freq(){ uint32_t imp_long, imp_hi; freq= 72E6/((TIMER1_BASE->ARR+1)*divider); duty_out= (float) TIMER1_BASE->CCR1 / ((TIMER1_BASE->ARR+1) / 100.0) ; 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) /72.0 ; t_hi= imp_hi /72.0; mon_flag=1; } // КОНФИГУРАЦИЯ DDS РЕЖИМОВ void dds_set(){ static uint32_t akkum; static byte oldmode=255; TIMER1_BASE->CCER=0; //timer output pins disable && перечитать регистры. GPIOA_BASE->CRL = 0x33333333;// pa0-pa7 выход GPIOA_BASE->CRH =0x44444444; //pa8-pa15 вход if (oldmode !=mode) { if (mode==2) {for(uint16_t n=0; n<512; n++){wave[n]=255*(sin(TWO_PI*float(n)/512)+1)/2 ;}}// синус else if (mode==3){ for(uint16_t n=0; n<512; n++){if (n<256){ wave[n]=n;} else {wave[n]=(511-n);}}}//треугол else if (mode==4){ for(uint16_t n=0; n<512; n++){ wave[n]=(n>>1);}} //пила1 else if (mode==5){ for(uint16_t n=0; n<512; n++){ wave[n]=((~n)>>1);}} //пила2 else if (mode==6){ 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 * 1670.1100841768461768461768461768;// шаг= частота*коэффициент while(!mon_flag){GPIOA_BASE->ODR=wave[(akkum+=dds_shag)>>23];} //генерация DDS GPIOA_BASE->CRL=0x44444444;// все пины в Z }//END DDS set() // void mytone(int frq, int ms ){ if (mode==7){return;} //таймер в режиме частотометра занят uint16_t psc=1; uint32_t tim_arr; // настройка генератора звука на таймере3 tim_arr = 36E6/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 = 72E3 * 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" ); } [/code]Дребезг убрал аппаратно.
Тед, в начале изучения stm32 делал что-то похожее, проверять в лом:
//Выход таймера PA8 (T1C1) #define GPIOA_CRH (*(volatile unsigned int*)0x40010804) #define RCC_APB2ENR (*(volatile unsigned int*)0x40021018) #define TIM1_CR1 (*(volatile unsigned int*)0x40012c00) #define TIM1_CCMR1 (*(volatile unsigned int*)0x40012c18) #define TIM1_CCER (*(volatile unsigned int*)0x40012c20) #define TIM1_PSC (*(volatile unsigned int*)0x40012c28) #define TIM1_ARR (*(volatile unsigned int*)0x40012c2c) #define TIM1_CCR1 (*(volatile unsigned int*)0x40012c34) void setup(){ GPIOA_CRH |= (1<<1)|(1<<0)|(1<<3); GPIOA_CRH &= ~(1<<2); RCC_APB2ENR|= 1<<11; //включить тактирование tim1 TIM1_CCER|=1<<0;//cc1e enable TIM1_CCMR1=(1<<6)|(1<<5)|(1<<3);//PWM mode 1 TIM1_PSC=0;//prescaler TIM1_CCR1= 0;// скважность по умолчанию в тактах TIM1_ARR= 1; // частота по умолчанию в тактах TIM1_CR1=1; } void loop() { if (Serial.available()) { int divider=1; int freq = 72E6/ Serial.parseInt(); int duty = Serial.parseInt(); while ( (freq/divider) > 65535) {divider++;} TIM1_PSC=divider-1; TIM1_ARR=(freq/divider)-1; if (!duty){ TIM1_CCR1= freq/divider/2;} else { TIM1_CCR1=(freq/divider)*duty/100; } freq= 72E6/((TIM1_ARR+1)*divider); Serial.print(freq); Serial.print("Hz"); float duty2= (float) TIM1_CCR1 / ((TIM1_ARR+1) / 100.0) ; Serial.print(" Duty=" ); Serial.println(duty2,0); } }терминал принимает два числа через пробел. Частоту и дьюти в процентах. Если дьюти не задать, то он по умолчанию 50%
Спасибо автору за проделанную работу и великолепно поданный материал!
Развел в Sprint Layout 6.0 для односторонки. Первый опыт для данной программы... но если кому полезно могу сбросить... не понял пока как прикрепить к сообщению.
Для "OLED I2C" - Необходимо заменить библиотеку и инициализацию, а также добавить update в конце отрабатки процедуры вывода.
На повторение, версии - 2.3, ушло 5 часов.
Вот так выглядит первое включение осциллографа с генератором V2.4. До 25 кГц работает. Хорошая игрушка.
Конечно можно смотреть и более высокие частоты, но с 25 кГц завал частотной характеристики. Экран неплохой. Применения не вижу. К приборам это отношения не имеет. Я планирую использовать как макет. Поиграться с выводом на экран, попробовать АЦП .
Информация на всякий пожарный:
Загрузил скетч на чистую 328 . Чтобы запустить приходится неско раз жать ресет. Заменил дисплей все Ок!
С другими библиотеками дисплей работает без проблем.
Решил тоже собратьгенератор, но не заморачиваться с печаткой. Взял голую 328, приложил к плате дисплея, исходя из наиболее выгодного расположения подкорректировал скетч и дальше вспомнил радиолюбительскую молодость и начал лепиь навесухой. В принципе на все ушло около четырех часов. Работает неплохо. Правда пила немного не идеальна, но я еще немного грешу на матрицу резисторов. В наличии были только 1,2 и 2,2 кОм. На них и собрал. Конструкция получилась достаточно компактная и жесткая. Максимальная высота монтажа не превышает толщину энкодера. Осталось корпус, но это уже не сегодня.
Вид спереди и сзади.
Осциллограма
есть у кого осцилограммы с данного генератора 1МГц-4МГц
нужна визуальная привязка для настройки входного уся цыфрового осцилографа
зараннее благодарен!
apeks1, да не жалко, вот вам 4МГц, с моего генератора версии 2.3.
ADMUX = (1<<REFS0)|(1<<MUX2)|(1<<MUX1);//ADC6 measureADMUX = (1<<REFS0)|(1<<MUX2)|(1<<MUX1)|(1<<MUX0);//ADC7 measureСпасибо! Вроде всё работает.
Версия 2.4 под экранчик ssd1306 128х32 I2C, вдруг кому надо :) На A4 и A5 вешается экран, A6 и A7 входы вольтометра. Библиотеку ставил через стандартный менеджер библиотек ардуино IDE, на всякий случай в коде указал ссылку на гитхаб либы. Думаю экранчик ssd1306 128х64 I2C тоже заведётся, нужно будет только поменять координаты строк ну и увеличить шрифт где нужно. А, ещё тут указать что 64 пиксела высота: ssd1306_128x64_i2c_init();
/* Генератор, частотометр, вольтметр на Ардуино Уно v 2.4 * Распиновка: Энкодер A0,A1 (in) * Кнопка энкодера A2 (in) * Кнопка "режим" A3 (in) * Подсветка 8 (out) (вывод BL дисплея) * Дисплей ssd1306 128х32 I2C - A4, A5 * Выход генератора 0..7 + 9 (out) * Вход частотометра - 5 (in) * Входы вольтметра -А6,А7 (in) */ const PROGMEM uint8_t sinewave[]= // массив синуса { 0x80,0x83,0x86,0x89,0x8c,0x8f,0x92,0x95,0x98,0x9c,0x9f,0xa2,0xa5,0xa8,0xab,0xae, 0xb0,0xb3,0xb6,0xb9,0xbc,0xbf,0xc1,0xc4,0xc7,0xc9,0xcc,0xce,0xd1,0xd3,0xd5,0xd8, 0xda,0xdc,0xde,0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xed,0xef,0xf0,0xf2,0xf3,0xf5, 0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfc,0xfd,0xfe,0xfe,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xfe,0xfd,0xfc,0xfc,0xfb,0xfa,0xf9,0xf8,0xf7, 0xf6,0xf5,0xf3,0xf2,0xf0,0xef,0xed,0xec,0xea,0xe8,0xe6,0xe4,0xe2,0xe0,0xde,0xdc, 0xda,0xd8,0xd5,0xd3,0xd1,0xce,0xcc,0xc9,0xc7,0xc4,0xc1,0xbf,0xbc,0xb9,0xb6,0xb3, 0xb0,0xae,0xab,0xa8,0xa5,0xa2,0x9f,0x9c,0x98,0x95,0x92,0x8f,0x8c,0x89,0x86,0x83, 0x80,0x7c,0x79,0x76,0x73,0x70,0x6d,0x6a,0x67,0x63,0x60,0x5d,0x5a,0x57,0x54,0x51, 0x4f,0x4c,0x49,0x46,0x43,0x40,0x3e,0x3b,0x38,0x36,0x33,0x31,0x2e,0x2c,0x2a,0x27, 0x25,0x23,0x21,0x1f,0x1d,0x1b,0x19,0x17,0x15,0x13,0x12,0x10,0x0f,0x0d,0x0c,0x0a, 0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x03,0x02,0x01,0x01,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x02,0x03,0x03,0x04,0x05,0x06,0x07,0x08, 0x09,0x0a,0x0c,0x0d,0x0f,0x10,0x12,0x13,0x15,0x17,0x19,0x1b,0x1d,0x1f,0x21,0x23, 0x25,0x27,0x2a,0x2c,0x2e,0x31,0x33,0x36,0x38,0x3b,0x3e,0x40,0x43,0x46,0x49,0x4c, 0x4f,0x51,0x54,0x57,0x5a,0x5d,0x60,0x63,0x67,0x6a,0x6d,0x70,0x73,0x76,0x79,0x7c }; const PROGMEM uint8_t squarewave[]= //массив меандра { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, }; const PROGMEM uint8_t trianglewave[]= //массив треугольника { 0x00,0x02,0x04,0x06,0x08,0x0a,0x0c,0x0e,0x10,0x12,0x14,0x16,0x18,0x1a,0x1c,0x1e, 0x20,0x22,0x24,0x26,0x28,0x2a,0x2c,0x2e,0x30,0x32,0x34,0x36,0x38,0x3a,0x3c,0x3e, 0x40,0x42,0x44,0x46,0x48,0x4a,0x4c,0x4e,0x50,0x52,0x54,0x56,0x58,0x5a,0x5c,0x5e, 0x60,0x62,0x64,0x66,0x68,0x6a,0x6c,0x6e,0x70,0x72,0x74,0x76,0x78,0x7a,0x7c,0x7e, 0x80,0x82,0x84,0x86,0x88,0x8a,0x8c,0x8e,0x90,0x92,0x94,0x96,0x98,0x9a,0x9c,0x9e, 0xa0,0xa2,0xa4,0xa6,0xa8,0xaa,0xac,0xae,0xb0,0xb2,0xb4,0xb6,0xb8,0xba,0xbc,0xbe, 0xc0,0xc2,0xc4,0xc6,0xc8,0xca,0xcc,0xce,0xd0,0xd2,0xd4,0xd6,0xd8,0xda,0xdc,0xde, 0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xee,0xf0,0xf2,0xf4,0xf6,0xf8,0xfa,0xfc,0xfe, 0xff,0xfd,0xfb,0xf9,0xf7,0xf5,0xf3,0xf1,0xef,0xef,0xeb,0xe9,0xe7,0xe5,0xe3,0xe1, 0xdf,0xdd,0xdb,0xd9,0xd7,0xd5,0xd3,0xd1,0xcf,0xcf,0xcb,0xc9,0xc7,0xc5,0xc3,0xc1, 0xbf,0xbd,0xbb,0xb9,0xb7,0xb5,0xb3,0xb1,0xaf,0xaf,0xab,0xa9,0xa7,0xa5,0xa3,0xa1, 0x9f,0x9d,0x9b,0x99,0x97,0x95,0x93,0x91,0x8f,0x8f,0x8b,0x89,0x87,0x85,0x83,0x81, 0x7f,0x7d,0x7b,0x79,0x77,0x75,0x73,0x71,0x6f,0x6f,0x6b,0x69,0x67,0x65,0x63,0x61, 0x5f,0x5d,0x5b,0x59,0x57,0x55,0x53,0x51,0x4f,0x4f,0x4b,0x49,0x47,0x45,0x43,0x41, 0x3f,0x3d,0x3b,0x39,0x37,0x35,0x33,0x31,0x2f,0x2f,0x2b,0x29,0x27,0x25,0x23,0x21, 0x1f,0x1d,0x1b,0x19,0x17,0x15,0x13,0x11,0x0f,0x0f,0x0b,0x09,0x07,0x05,0x03,0x01 }; const PROGMEM uint8_t sawtoothwave[]= //массив пила1 { 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f, 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f, 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f, 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f, 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f, 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f, 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f, 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f, 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f, 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f, 0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf, 0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf, 0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf, 0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdd,0xde,0xdf, 0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef, 0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff }; const PROGMEM uint8_t rewsawtoothwave[]= //массив пила2 { 0xff,0xfe,0xfd,0xfc,0xfb,0xfa,0xf9,0xf8,0xf7,0xf6,0xf5,0xf4,0xf3,0xf2,0xf1,0xf0, 0xef,0xee,0xed,0xec,0xeb,0xea,0xe9,0xe8,0xe7,0xe6,0xe5,0xe4,0xe3,0xe2,0xe1,0xe0, 0xdf,0xde,0xdd,0xdc,0xdb,0xda,0xd9,0xd8,0xd7,0xd6,0xd5,0xd4,0xd3,0xd2,0xd1,0xd0, 0xcf,0xce,0xcd,0xcc,0xcb,0xca,0xc9,0xc8,0xc7,0xc6,0xc5,0xc4,0xc3,0xc2,0xc1,0xc0, 0xbf,0xbe,0xbd,0xbc,0xbb,0xba,0xb9,0xb8,0xb7,0xb6,0xb5,0xb4,0xb3,0xb2,0xb1,0xb0, 0xaf,0xae,0xad,0xac,0xab,0xaa,0xa9,0xa8,0xa7,0xa6,0xa5,0xa4,0xa3,0xa2,0xa1,0xa0, 0x9f,0x9e,0x9d,0x9c,0x9b,0x9a,0x99,0x98,0x97,0x96,0x95,0x94,0x93,0x92,0x91,0x90, 0x8f,0x8e,0x8d,0x8c,0x8b,0x8a,0x89,0x88,0x87,0x86,0x85,0x84,0x83,0x82,0x81,0x80, 0x7f,0x7e,0x7d,0x7c,0x7b,0x7a,0x79,0x78,0x77,0x76,0x75,0x74,0x73,0x72,0x71,0x70, 0x6f,0x6e,0x6d,0x6c,0x6b,0x6a,0x69,0x68,0x67,0x66,0x65,0x64,0x63,0x62,0x61,0x60, 0x5f,0x5e,0x5d,0x5c,0x5b,0x5a,0x59,0x58,0x57,0x56,0x55,0x54,0x53,0x52,0x51,0x50, 0x4f,0x4e,0x4d,0x4c,0x4b,0x4a,0x49,0x48,0x47,0x46,0x45,0x44,0x43,0x42,0x41,0x40, 0x3f,0x3e,0x3d,0x3c,0x3b,0x3a,0x39,0x38,0x37,0x36,0x35,0x34,0x33,0x32,0x31,0x30, 0x2f,0x2e,0x2d,0x2c,0x2b,0x2a,0x29,0x28,0x27,0x26,0x25,0x24,0x23,0x22,0x21,0x20, 0x1f,0x1e,0x1d,0x1c,0x1b,0x1a,0x19,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10, 0x0f,0x0e,0x0d,0x0c,0x0b,0x0a,0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01,0x00, }; 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,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; volatile int32_t freq=1000;// частота по умолчанию volatile uint32_t icr=0; //переменная для управления регистром сравнения таймера1 volatile uint16_t shag=100; //шаг энкодера по умолчанию volatile uint16_t int_tic=0; volatile uint8_t regim=5; //режим генератора по умолчанию volatile uint8_t monitor_flag; // флаг для вывода на дисплей volatile uint8_t ad2,ad1,ad0; //служебные байты для функции на ассемблере volatile uint8_t duty=50; //скважность volatile uint8_t main_flag=0;//флаг работа в режиме генератора или нет #define int_on() PCMSK1=(1<<PCINT9)|(1<<PCINT10)|(1<<PCINT11); PCICR=(1<<PCIE1); //включить прерывание PCINT1, выбор пинов прерывания A1,A2,A3 #define int_off() PCMSK1=0; PCICR=0; //отключить PCINT1 #include "ssd1306.h" // https://github.com/lexus2k/ssd1306 Можно установить через штатный менеджер библиотек // DDS algorithm static inline void signalOUT(const uint8_t *signal){ asm volatile( "eor r18, r18 ;r18<-0" "\n\t" "eor r19, r19 ;r19<-0" "\n\t" "1:" "\n\t" "add r18, %0 ;1 cycle" "\n\t" "adc r19, %1 ;1 cycle" "\n\t" "adc %A3, %2 ;1 cycle" "\n\t" "lpm ;3 cycles" "\n\t" "out %4, __tmp_reg__ ;1 cycle" "\n\t" "sbis %5, 7 ;1 cycle if no skip" "\n\t" "rjmp 1b ;2 cycles. Total 10 cycles" "\n\t" : :"r" (ad0),"r" (ad1),"r" (ad2),"e" (signal),"I" (_SFR_IO_ADDR(PORTD)), "I" (_SFR_IO_ADDR(PORTB)) :"r18", "r19" ); } void setup(){ ssd1306_setFixedFont(ssd1306xled_font6x8); ssd1306_128x32_i2c_init(); ssd1306_setContrast(255); ssd1306_fillScreen( 0x00 ); for (int n=0; n <sizeof(musor_mass); n++ ) {PORTD=musor_mass[n]; } PORTD=0; DDRD=0; TCCR1A=0; TCCR1B=0; TIMSK1=0; PORTC|=(1<<PC2)|(1<<PC3); //подтяжка кнопок на A2 A3 DDRB|=1<<PB0; // 8 пин -включение подсветки дисплея = выход main_screen(); //стартовое сообщение и выбор задач //далее загрузка генератора по умолчанию TCCR0B=0; check_regim(); int_on();//включить прерывание PCINT1 main_flag=1; } //end setup void up_down(boolean x){ // управление регулировками // static boolean n=0; if (n=!n){return;} // снять ремарку для энкодеров с двойным щелчком if (TCCR1B==17 && ICR1<2800 && regim==5){ if(x) {if (icr<1000 && shag > 100) shag=100; if (icr<100 && shag > 10) shag=10; if (icr<10 && shag > 1) shag=1; icr-=shag; if (icr<2) icr=2; } else { if (icr > 1800 && shag >100) shag =100; icr+=shag ; } return; } if (regim==6){if (ICR1>100){ if (x){if(duty<100) {duty++; } } if (!x){ if(duty>0) {duty--; }} } else{ if (x){if(OCR1A<ICR1) {OCR1A++; } } else {if(OCR1A>0) {OCR1A--; } } if(OCR1A>ICR1) OCR1A=ICR1-1; duty=(uint32_t)100*OCR1A/ICR1; } return; } x? freq+=shag : freq-=shag ; if (freq < 1) freq=1; } //////////////////////////////////////////////////////////////// //****** ПРЕРЫВАНИЕ от кнопок и энкодера*******///////////////// ISR (PCINT1_vect){ int_off(); PORTD=0; ///блок для обработки событий не в режиме генератора/// if (main_flag==0) { if ((PINC&(1<<3))==0){ while ((PINC&(1<<3))==0); //вкл. подсветки if ((PINC&(1<<2))==0){ PINB|=1<<PB0; while ((PINC&(1<<2))==0);} int_on(); return; //выходить если не в режиме генератора }} //далее всё в режиме генаратора // если нажата кнопка энкодера if ((PINC&(1<<3))==0){ while ((PINC&(1<<3))==0);// подождать до тех пор, когда кнопку отпустят //блок вкл/отк подсветки дисплея (8 пин ардуино) if ((PINC&(1<<2))==0){ //если после этого нажата кнопка режимов, PINB|=1<<PB0; //менять состояние 8 пина while ((PINC&(1<<2))==0);// теперь подождать пока отпустят кнопку режим int_on(); return; } if (regim==6) { regim=5; check_regim(); int_on(); return; } switch (shag){ case 1: shag=10; break; case 10: shag=100; break; case 100: shag=1000; break; case 1000: shag=1; break; } check_regim(); int_on(); return; } //конец блока *если нажата кнопка энкодера* if ((PINC&(1<<2))==0){ // если нажата кнопка режимов PORTD=0; while ((PINC&(1<<2))==0); regim++; if (regim==7) regim=0; check_regim(); int_on(); return; } //конец блока *если нажата кнопка режимов* // если кнопки не нажимались -значит крутили энкодер: up_down( ! (PINC&1)^((PINC&2)>>1) ); //отправить считанное энкодеров в другую функцию // если при вращении счёт идёт не в нужную сторону, то (вставить/убрать) символ '!' up_down(! check_regim(); int_on(); } //конец функции обработки прерываний от кнопок PCINT1_vect ////////////////////КОНЕЦ_ПРЕРЫВАНИЕ_от_кнопок_и_энкодера/////////////////////////////// void pwm_gen(){ //настройка таймера1 uint16_t divider=1; icr = (16000000ul / freq /2 /divider); byte shifts[] = {3,3,2,2}; for(byte i = 0; i < 4; i++){ if (icr > 65536) { divider <<= shifts[i]; icr = 16000000ul / freq /2 /divider; } else { TCCR1B = (i+1)|(1<<WGM13); break; } } ICR1=icr-1; set_duty(); } //end pwm_gen void loop() { if (monitor_flag) { monitor_flag=0; monitor_out(); } if (regim <5){ PORTB&= ~(1<<7); DDRD=0xFF;//set D port as output uint32_t temp=(float)freq /0.095367431640625; ad2=temp>>16; ad1=temp>>8; ad0=temp; switch (regim){ case 0: signalOUT(sinewave); break; case 1: signalOUT(trianglewave); break; case 2: signalOUT(squarewave); break; case 3: signalOUT(sawtoothwave); break; case 4: signalOUT(rewsawtoothwave); } //end switch DDRD=0; PORTD=0; } //end if (regim<5) }//end loop void monitor_out(){ String dutystr,stepstr; dutystr= String("Duty:"+ String(duty)+ "%"); stepstr=String("Step:"+String(shag)); ssd1306_clearScreen(); //Вывод первой строчки //ssd1306_setFixedFont(ssd1306xled_font6x8); if (freq <10000) {ssd1306_printFixed(0, 0, "Frequency, Hz", STYLE_NORMAL); } if (freq >=10000) {ssd1306_printFixed(0, 0, "Frequency, kHz", STYLE_NORMAL); } //Вывод второй строчки if (freq <10000) { String sfreq((float)freq, 0); sfreq.trim(); const char * szfreq = sfreq.c_str(); ssd1306_printFixedN(0, 8,szfreq, STYLE_NORMAL, FONT_SIZE_2X); } if (freq >=10000u && freq < 1000000ul ) { String sfreq((float)freq/1000, 3); const char * szfreq = sfreq.c_str(); ssd1306_printFixedN(0, 8, szfreq, STYLE_NORMAL, FONT_SIZE_2X); } if (freq >=1000000ul ) { String sfreq((float)freq/1000, 2); const char * szfreq = sfreq.c_str(); ssd1306_printFixedN(0, 8, szfreq, STYLE_NORMAL, FONT_SIZE_2X); } //Вывод третьей строчки switch(regim){ case 0: ssd1306_printFixed(0, 24, "Sinus.DDS", STYLE_NORMAL); break; case 1: ssd1306_printFixed(0, 24, "Triangl.DDS", STYLE_NORMAL); break; case 2: ssd1306_printFixed(0, 24, "Meandr.DDS", STYLE_NORMAL); break; case 3: ssd1306_printFixed(0, 24, "Pila1.DDS", STYLE_NORMAL); break; case 4: ssd1306_printFixed(0, 24, "Pila2.DDS", STYLE_NORMAL); break; case 5: ssd1306_printFixed(0, 24, "PWM Mode", STYLE_NORMAL); break; case 6: ssd1306_printFixed(0, 24, "Duty Mode", STYLE_NORMAL); } //Вывод четвёртой строчки if (regim==6) ssd1306_printFixed(68, 24, dutystr.c_str(), STYLE_NORMAL); else ssd1306_printFixed(68, 24, stepstr.c_str(), STYLE_NORMAL); }//end monitor out void set_duty(){ if (regim==6&&ICR1<100) return; if (regim==5 && ICR1<100){ OCR1A=ICR1/2; duty=50; return; } static uint16_t ocr; ocr=(uint32_t)ICR1*duty/100; OCR1A=ocr; } void check_regim(){// проверка и установка режимов генератора if (regim <5){ if (freq > 100000) freq=100000; TCCR1B=0; TCCR1A=0; DDRB&=~(1<<DDB1); // 9 pin arduino set Z-mode }// end if regim <5 if (regim > 4) { TCCR1A=1<<COM1A1; DDRB|=1<<DDB1; // 9 pin set output (pwm out) if (TCCR1B==17 && ICR1<2800){ ICR1=icr; freq= (float) 8000000UL/ICR1; set_duty(); } else { pwm_gen(); } } PORTB|= 1<<7; monitor_flag=1; } ISR (TIMER1_OVF_vect){ int_tic++; } // прерывание частотомера void freq_meter(){ //ssd1306_setFixedFont(ssd1306xled_font6x8); ssd1306_clearScreen(); ssd1306_printFixed(0, 0, "Freq.counter", STYLE_NORMAL); int_on();//включить прерывание PCINT1 TIMSK1 = 1<<TOIE1;// подключить прерывание uint32_t freqm=0;// Переменная измерения частоты int_tic=0; TCNT1=0; TIFR1=0;//всё обнулить while(1){ TCCR1B=7;//тактировани от входа Т1 delay(2000); TCCR1B=0; freqm= (((uint32_t)int_tic<<16) | TCNT1)/2; //сложить что натикало int_tic=0; TCNT1 = 0; //ssd1306_setFixedFont(ssd1306xled_font6x8); ssd1306_clearScreen(); ssd1306_printFixed(0, 0, "Freq.counter", STYLE_NORMAL); if (freqm <10000) { String sfreqm((float)freqm, 0); sfreqm.trim(); const char * szfreqm = sfreqm.c_str(); ssd1306_printFixedN(0, 8, szfreqm, STYLE_NORMAL, FONT_SIZE_2X); ssd1306_printFixed(0, 24, "Herz", STYLE_NORMAL); } if (freqm >=10000u && freqm < 1000000ul ) { String sfreqm((float)freqm/1000, 3); const char * szfreqm = sfreqm.c_str(); ssd1306_printFixedN(0, 8, szfreqm, STYLE_NORMAL, FONT_SIZE_2X); ssd1306_printFixed(0, 24, "KiloHerz", STYLE_NORMAL); } if (freqm >=1000000ul ) { String sfreqm((float)freqm/1000000ul, 3); const char * szfreqm = sfreqm.c_str(); ssd1306_printFixedN(0, 8, szfreqm, STYLE_NORMAL, FONT_SIZE_2X); ssd1306_printFixed(0, 24, "MegaHerz", STYLE_NORMAL); } } } ////////////////////////////////////////////////////////////////////// void volt_meter() { ADCSRA=(1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); ADCSRB=0; DIDR0=48; int_on(); float ain,vcc; String ainstr,vccstr; //ssd1306_setFixedFont(ssd1306xled_font6x8); ssd1306_clearScreen(); ssd1306_printFixed(0, 0, "Volt meter", STYLE_NORMAL); while(1){ ADMUX = (1<<REFS0)|(1<<MUX3)|(1<<MUX2)|(1<<MUX1); //Vcc measure delay(1); vcc=(float)(1.1*65472) / analog_func(); vccstr=String("Vcc= "+String(vcc,3)+" v "); ssd1306_printFixed(0, 24, vccstr.c_str(), STYLE_NORMAL); ADMUX = (1<<REFS0)|(1<<MUX2)|(1<<MUX1); //ADC6 measure delay(1); ain= analog_func()*vcc /65472 ; ainstr=String("Ain6= "+String(ain,3)+" v "); ssd1306_printFixed(0, 8, ainstr.c_str(), STYLE_NORMAL); ADMUX = (1<<REFS0)|(1<<MUX2)|(1<<MUX1)|(1<<MUX0); //ADC7 measure delay(1); ain= analog_func() *vcc /65472 ; ainstr=String("Ain7= "+String(ain,3)+" v "); ssd1306_printFixed(0, 16, ainstr.c_str(), STYLE_NORMAL); };//end while }//end volt meter uint32_t analog_func(){ uint32_t adc_buff=0; for (int n=0; n<=4095; n++ ) { ADCSRA |= (1<<ADSC); while (bit_is_set(ADCSRA,ADSC)); adc_buff += ADC; } return adc_buff>>=6; } ///////////////////////////////////////////////////////////////// void main_screen(){ static boolean flag_exit =0; uint8_t n=84; //ssd1306_setFixedFont(ssd1306xled_font6x8); ssd1306_printFixed(0, 0, "Freq.counter >", STYLE_NORMAL); ssd1306_printFixed(0, 8, "**************", STYLE_NORMAL); ssd1306_printFixed(0, 16, " Volt meter >", STYLE_NORMAL); while(flag_exit==0) { //ждать нажатия кнопки режимов. delay(350); ssd1306_printFixed(n-12, 8, " ", STYLE_NORMAL); n-=6; if (n==0) flag_exit=1; if ((PINC&(1<<2))==0){ // если нажата кнопка режимов while ((PINC&(1<<2))==0); freq_meter(); } if ((PINC&(1<<3))==0){ // если нажата кнопка энкодера while ((PINC&(1<<3))==0); volt_meter(); } }// end while }// end main_screenОчевидно, что вместо 2E5 какое-то другое число :)
Мне было не очевидно :) Я делал перистальтический насос на ардуине там было ограничение на кол-во оборотов простой цифрой, вот там мне было очевидно, а тут мой проц завис :) Оказалось что 2E5 это число)))) Погуглил, хоть и не понял что это за числа, методом научного тыка написал то что мне надо. Спасибо за помощь.
Генератор на ардуине собирал на макетной плате проводами 1,5 дня, замучился. Решил что надо осваивать изготовление печатных плат. Выбрал KiCad, на осваивание программы и создание платы ушло 3 дня. Травил час, собирал около 3. Вот что получилось:
Не без косяков конечно :) Считал считал, но почему то у энкодера посадочные места по длине растянулись, но он все равно встал, не сделал переходные отверстия для питания экрана и энкодера, решил просто, припаял кусок провода. Не стал лудить, думал покрою лаком, но пока паял все заляпал. Придется теперь лудить. Ну вот вроде бы все.
Файлы KiCad https://yadi.sk/d/9IRSuFxi3X578E посадочные места под энкодер поправил, а вот с переходным отверстия так и не разобрался, наверно надо посадочные отверстия поставить, и не нашел у себя пищалку, соответственно ее не мерил и посадочные места под не из библиотеки.
dimax, еще раз спасибо за проект и поддержку :)
Респект автору за генератор, работает стабильно, правда частоту врёт на доли Герца, но это уж придирки)) Не могу понять чем отличается PWM и Duty режимы, в обои регулируется и частота и длительность, зачем их дублировать было?) Кстати у stm32f103 очень крутые фронты выходных сигналов, около 5 нс.

Добрый вечер! Сделал платку под голую Atmega328 TQFP32 по схеме v2.4, спаял, плата как бы запускается, шим норм идёт, а вот синус получил такой:
Проверил схему на 2 раза, вроде всё верно, помыл от канифоли - не помогает. сейчас заметил, что в версии 2.0 нижний резистор в делителе, который идёт на массу, номиналом 1к, а в версиях 2.3, 2.4 номинал 2к, не думаю что это так сильно бы изуродовало синус, но может я просто не знаю физику процесса? Голую атмегу первый раз шил (до этого только тиньки), фьюзы никакие не выставлял, просто загрузчик сперва записал, потом скеч (говорят загрузчик ставить фьюзы как надо) шил USBASP через Arduino IDE.
Может картинка кому то подскажет чего я накосячил? :)
Плата в формате Sprint Layout 5: https://yadi.sk/d/K0Lq2Inu3YVVkQ
Фотовид (картинка кликабельна):
Focus, Похоже на сдвиг массива, почитайте внимательнее параграф "Особенности" в описании версии 2.0
Поперемещал архив туда-сюда,даже адрес начала у него всегда больше сотни, потом добавил в него нулей х2, синус почти стал походить на синус. Убрал по чуть чуть пару строк нулей - теперь синус как синус. Но адрес помоему получился не кратный 100... Массив пила2 начинается со 100500
Все остальные формы так же пришли в форму :) .
А резистор нижний в делителе всётаки 2к должен быть? или 1к?
Повторил конструкцию.
2 выход через полевой транзистор дл управления нагрузкой
https://mega.nz/#!zR9T3Rib!G8u6PgksZBSvkkWdyijTcffjNB3y4wmQEGyeMM3EKgs
Спасибо.
Версия 2.5 написана под микроконтроллер LGT8F328P, который обозревался недавно тут. Функционально представляет из себя немного окультуренную версию 2.4. Так-же может потребовать подгонки массивов (подробно описано в 2.0) Отличия от 2.4 : убрана дополнительная кнопка, всё управление производится одним энкодером. Вращение энкодера не нажимая его кнопки -меняет частоту. Вращение энкодера с нажатой кнопкой -меняет режимы. Однократное нажатие/отпускание кнопки меняет шаг. Вольтметр и частотометр перенесены в общее меню. Частотометр нормально измеряет примерно до 12МГц (при тактовой МК 32МГц). Вольтметр улучшен, умеет автоматически выбирать нужный референс. (1,024/2,048/4,096 или Vcc) Адаптирована под этот МК функция DDS-синтеза (линк на автора метода). Скорострельность DDS синтеза достигла очень высокого значения -4MS/sec, (в версии 2.4 -1,6MS/Sec) это даже существенно быстрее чем в версии 3.0 на stm32 (2,6MS/Sec) Однако же вкусить плоды такой скорости не представляется возможным, т.к. ЦАП на этом МК сделали с высокоомным выходом, поэтому на высоких частотах он начинает образовывать фильтр, который заметно ухудшает характеристики. Это надо смотреть визуально, что бы понять. На осциллограмме слева -синус на 100кГц с этого ЦАПа сильно перекошён , но не изломанный как был на версии 2.0 (осцилограмма справа).
В связи с этим желательно поставить на выход ЦАПа опер. усилитель, что б ещё сильнее не ухудшить сигнал при подключении нагрузки. Уровень сигнала с ЦАП можно сделать фиксированным с референсом от одного из внутренних опорных напряжений, либо регулируемым, для этого на вход AREF нужно подать напряжение от 0 до Vcc например с переменного резистора.
Схема:
Скетч:
/* Генератор с регулируемой частотой и скважностью v 2.5 * предназначен только для МК lgt8f328p * Распиновка: Энкодер пин A0,A1 (PC0,PC1) * Кнопка энкодера пин 2 (PD2) * Дисплей nokia 5110 - 13,12,11,10 (PB5,PB4,PB3,PB2) * Выход генератора DDS пин 4 (PD4) * Выход генератора PWM пин 9 (PB1) * Вход вольтметра пин A3 (ADC3) * Вход частотометра пин 5 (PD5) */ const PROGMEM uint8_t sinewave[]= // массив синуса { 0x80,0x83,0x86,0x89,0x8c,0x8f,0x92,0x95,0x98,0x9c,0x9f,0xa2,0xa5,0xa8,0xab,0xae, 0xb0,0xb3,0xb6,0xb9,0xbc,0xbf,0xc1,0xc4,0xc7,0xc9,0xcc,0xce,0xd1,0xd3,0xd5,0xd8, 0xda,0xdc,0xde,0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xed,0xef,0xf0,0xf2,0xf3,0xf5, 0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfc,0xfd,0xfe,0xfe,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xfe,0xfd,0xfc,0xfc,0xfb,0xfa,0xf9,0xf8,0xf7, 0xf6,0xf5,0xf3,0xf2,0xf0,0xef,0xed,0xec,0xea,0xe8,0xe6,0xe4,0xe2,0xe0,0xde,0xdc, 0xda,0xd8,0xd5,0xd3,0xd1,0xce,0xcc,0xc9,0xc7,0xc4,0xc1,0xbf,0xbc,0xb9,0xb6,0xb3, 0xb0,0xae,0xab,0xa8,0xa5,0xa2,0x9f,0x9c,0x98,0x95,0x92,0x8f,0x8c,0x89,0x86,0x83, 0x80,0x7c,0x79,0x76,0x73,0x70,0x6d,0x6a,0x67,0x63,0x60,0x5d,0x5a,0x57,0x54,0x51, 0x4f,0x4c,0x49,0x46,0x43,0x40,0x3e,0x3b,0x38,0x36,0x33,0x31,0x2e,0x2c,0x2a,0x27, 0x25,0x23,0x21,0x1f,0x1d,0x1b,0x19,0x17,0x15,0x13,0x12,0x10,0x0f,0x0d,0x0c,0x0a, 0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x03,0x02,0x01,0x01,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x02,0x03,0x03,0x04,0x05,0x06,0x07,0x08, 0x09,0x0a,0x0c,0x0d,0x0f,0x10,0x12,0x13,0x15,0x17,0x19,0x1b,0x1d,0x1f,0x21,0x23, 0x25,0x27,0x2a,0x2c,0x2e,0x31,0x33,0x36,0x38,0x3b,0x3e,0x40,0x43,0x46,0x49,0x4c, 0x4f,0x51,0x54,0x57,0x5a,0x5d,0x60,0x63,0x67,0x6a,0x6d,0x70,0x73,0x76,0x79,0x7c }; const PROGMEM uint8_t squarewave[]= //массив меандра { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, }; const PROGMEM uint8_t trianglewave[]= //массив треугольника { 0x00,0x02,0x04,0x06,0x08,0x0a,0x0c,0x0e,0x10,0x12,0x14,0x16,0x18,0x1a,0x1c,0x1e, 0x20,0x22,0x24,0x26,0x28,0x2a,0x2c,0x2e,0x30,0x32,0x34,0x36,0x38,0x3a,0x3c,0x3e, 0x40,0x42,0x44,0x46,0x48,0x4a,0x4c,0x4e,0x50,0x52,0x54,0x56,0x58,0x5a,0x5c,0x5e, 0x60,0x62,0x64,0x66,0x68,0x6a,0x6c,0x6e,0x70,0x72,0x74,0x76,0x78,0x7a,0x7c,0x7e, 0x80,0x82,0x84,0x86,0x88,0x8a,0x8c,0x8e,0x90,0x92,0x94,0x96,0x98,0x9a,0x9c,0x9e, 0xa0,0xa2,0xa4,0xa6,0xa8,0xaa,0xac,0xae,0xb0,0xb2,0xb4,0xb6,0xb8,0xba,0xbc,0xbe, 0xc0,0xc2,0xc4,0xc6,0xc8,0xca,0xcc,0xce,0xd0,0xd2,0xd4,0xd6,0xd8,0xda,0xdc,0xde, 0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xee,0xf0,0xf2,0xf4,0xf6,0xf8,0xfa,0xfc,0xfe, 0xff,0xfd,0xfb,0xf9,0xf7,0xf5,0xf3,0xf1,0xef,0xef,0xeb,0xe9,0xe7,0xe5,0xe3,0xe1, 0xdf,0xdd,0xdb,0xd9,0xd7,0xd5,0xd3,0xd1,0xcf,0xcf,0xcb,0xc9,0xc7,0xc5,0xc3,0xc1, 0xbf,0xbd,0xbb,0xb9,0xb7,0xb5,0xb3,0xb1,0xaf,0xaf,0xab,0xa9,0xa7,0xa5,0xa3,0xa1, 0x9f,0x9d,0x9b,0x99,0x97,0x95,0x93,0x91,0x8f,0x8f,0x8b,0x89,0x87,0x85,0x83,0x81, 0x7f,0x7d,0x7b,0x79,0x77,0x75,0x73,0x71,0x6f,0x6f,0x6b,0x69,0x67,0x65,0x63,0x61, 0x5f,0x5d,0x5b,0x59,0x57,0x55,0x53,0x51,0x4f,0x4f,0x4b,0x49,0x47,0x45,0x43,0x41, 0x3f,0x3d,0x3b,0x39,0x37,0x35,0x33,0x31,0x2f,0x2f,0x2b,0x29,0x27,0x25,0x23,0x21, 0x1f,0x1d,0x1b,0x19,0x17,0x15,0x13,0x11,0x0f,0x0f,0x0b,0x09,0x07,0x05,0x03,0x01 }; const PROGMEM uint8_t sawtoothwave[]= //массив пила1 { 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f, 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f, 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f, 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f, 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f, 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f, 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f, 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f, 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f, 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f, 0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf, 0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf, 0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf, 0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdd,0xde,0xdf, 0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef, 0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff }; const PROGMEM uint8_t rewsawtoothwave[]= //массив пила2 { 0xff,0xfe,0xfd,0xfc,0xfb,0xfa,0xf9,0xf8,0xf7,0xf6,0xf5,0xf4,0xf3,0xf2,0xf1,0xf0, 0xef,0xee,0xed,0xec,0xeb,0xea,0xe9,0xe8,0xe7,0xe6,0xe5,0xe4,0xe3,0xe2,0xe1,0xe0, 0xdf,0xde,0xdd,0xdc,0xdb,0xda,0xd9,0xd8,0xd7,0xd6,0xd5,0xd4,0xd3,0xd2,0xd1,0xd0, 0xcf,0xce,0xcd,0xcc,0xcb,0xca,0xc9,0xc8,0xc7,0xc6,0xc5,0xc4,0xc3,0xc2,0xc1,0xc0, 0xbf,0xbe,0xbd,0xbc,0xbb,0xba,0xb9,0xb8,0xb7,0xb6,0xb5,0xb4,0xb3,0xb2,0xb1,0xb0, 0xaf,0xae,0xad,0xac,0xab,0xaa,0xa9,0xa8,0xa7,0xa6,0xa5,0xa4,0xa3,0xa2,0xa1,0xa0, 0x9f,0x9e,0x9d,0x9c,0x9b,0x9a,0x99,0x98,0x97,0x96,0x95,0x94,0x93,0x92,0x91,0x90, 0x8f,0x8e,0x8d,0x8c,0x8b,0x8a,0x89,0x88,0x87,0x86,0x85,0x84,0x83,0x82,0x81,0x80, 0x7f,0x7e,0x7d,0x7c,0x7b,0x7a,0x79,0x78,0x77,0x76,0x75,0x74,0x73,0x72,0x71,0x70, 0x6f,0x6e,0x6d,0x6c,0x6b,0x6a,0x69,0x68,0x67,0x66,0x65,0x64,0x63,0x62,0x61,0x60, 0x5f,0x5e,0x5d,0x5c,0x5b,0x5a,0x59,0x58,0x57,0x56,0x55,0x54,0x53,0x52,0x51,0x50, 0x4f,0x4e,0x4d,0x4c,0x4b,0x4a,0x49,0x48,0x47,0x46,0x45,0x44,0x43,0x42,0x41,0x40, 0x3f,0x3e,0x3d,0x3c,0x3b,0x3a,0x39,0x38,0x37,0x36,0x35,0x34,0x33,0x32,0x31,0x30, 0x2f,0x2e,0x2d,0x2c,0x2b,0x2a,0x29,0x28,0x27,0x26,0x25,0x24,0x23,0x22,0x21,0x20, 0x1f,0x1e,0x1d,0x1c,0x1b,0x1a,0x19,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10, 0x0f,0x0e,0x0d,0x0c,0x0b,0x0a,0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01,0x00, }; 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,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; // пользовательские настройки переменных по умолчанию //режимы 0-синус dds, 1-треугольник dds, 2-меандр dds, 3 пила1 dds, 4 пила2 dds //5- гегулировка частоты pwm, 6- Duty pwm, 7-частотометр, 8 -вольтметр volatile uint8_t regim=5; //выбор режима генератора по умолчанию volatile int32_t freq=1000;// частота по умолчанию volatile uint16_t shag=100; //шаг энкодера по умолчанию #define dac_trim 5 // настройка выходного уровня ЦАП // 0 -переменный резистор на пине aref, 1 -1.024V; 2-2.048v; 4-4.096V; 5-Vcc #define F_CPU 32E6 //нужно указать фактическую частоту МК volatile uint16_t int_tic=0;//переменная частотометра (переполнение таймера) uint16_t Vbg, involt;//переменные вольтметра volatile uint32_t icr=0; //переменная для управления регистром сравнения таймера1 volatile uint8_t monitor_flag; // флаг для вывода на дисплей volatile uint8_t duty=50; //скважность по умолчанию //volatile uint8_t main_flag=0;//флаг работа в режиме генератора или нет volatile uint8_t modebit=1;//флаг смены режимов.(для анализа действия энкодера) #include "lgtx8p.h" #define int_on() PCMSK1=(1<<PCINT9); PCICR=(1<<PCIE1); EIMSK=1<<INT0; //прерывание на А1 //включить прерывание PCINT1, выбор пинов прерывания A1, #define int_off() PCMSK1=0; PCICR=0; EIMSK=0; ///отключить прерывания энкодера и кнопки #include <LCD5110_SSVS.h> #include <avr/delay.h> extern uint8_t SmallFont[]; extern uint8_t MediumNumbers[]; LCD5110 lcd(10,11,12,13); // DDS algorithm static inline void signalOUT(uint16_t ad01,uint8_t ad2, const uint8_t *wvt){ asm volatile( "ldi r28,0xA1; " "\n\t" "clr r29;" "\n\t" "eor r18, r18;" "\n\t" "eor r19, r19;" "\n\t" "1:" "\n\t" "add r18, %A0;" "\n\t" "adc r19, %B0;" "\n\t" "adc %A2, %1;" "\n\t" "lpm ;" "\n\t" "st Y, __tmp_reg__ ;" "\n\t" "sbis %3, 7 ;" "\n\t" "rjmp 1b;" "\n\t" ::"r" (ad01),"r" (ad2),"e" (wvt), "I" (_SFR_IO_ADDR(PORTF)) :"r18", "r19","r28", "r29" ); } void setup(){ lcd.InitLCD(); for (int n=0; n <sizeof(musor_mass); n++ ) {PORTF=musor_mass[n]&0xFC; } pinMode(A0,INPUT_PULLUP); //encoder pinMode(A1,INPUT_PULLUP); // encoder pinMode(2,INPUT_PULLUP); //encoder key pinMode(3,OUTPUT);// buzzer TCCR1A=0; TCCR1B=0; TIMSK1=0; EICRA=3; //INT0 -RISING+FALLING EIFR==EIFR; //flag clear TCCR0B=0; check_regim(); //разгон до 32 МГц от внутреннего RC генератора CLKPR = 1<<PMCE;//разрешить изменение CLKPR = 0; //делитель =1 } //end setup /////////////// ПРЕРЫВАНИЕ от кн0пки////////////////////////// ISR (INT0_vect){ // прерывание от кнопки энкодера int_off(); if ( (TIFR3&(1<<0))==0 ){TIFR3|=1<<0;} // флаг выходы из режима частотометра if (!modebit) { modebit=1; int_on(); return;} //меняли режим, просто выйти if (regim==6) { regim=5; check_regim(); return; } switch (shag){ case 1: shag=10; break; case 10: shag=100; break; case 100: shag=1000; break; case 1000: shag=1; break; } check_regim(); return; } //конец прерывания *если нажата кнопка энкодера* //////////////////////////////////////////////////////////// ISR (PCINT1_vect){ //прерывание при вращении энкодера // static boolean n=0; if (n=!n){return;} // снять ремарку для энкодеров с двойным щелчком int_off(); bool x = (PINC&1)^((PINC&2)>>1); if ( (TIFR3&(1<<0))==0 ){TIFR3|=1<<0;} // флаг выхода из режима частотометра //////////////////// переключение режимов при нажатой кнопке////////////////////// modebit= (PIND&(1<<2)); //0 -кнопка нажата. 1 -не нажата if (!modebit) { //если кнопка нажата //флаг что переключаю режимы if (x) { regim++; if (regim>8) {regim=0;} } else {regim-- ; if (regim>8) {regim=8;}} check_regim(); return; } ////////////////// переключение частоты///////////////////////////////////////// if (TCCR1B==17 && ICR1<2800 && regim==5){ //если > 2800 герц if(x) {if (icr<1000 && shag > 100) shag=100; if (icr<100 && shag > 10) shag=10; if (icr<10 && shag > 1) shag=1; icr-=shag; if (icr<2) icr=2; }//ограничитель else { if (icr > 1800 && shag >100) shag =100; icr+=shag ; } check_regim(); return; } ///////////////////////////переключение Duty////////////////////////////////// if (regim==6){if (ICR1>100){ if (x){if(duty<100) {duty++; } } if (!x){ if(duty>0) {duty--; }} } else{ if (x){if(OCR1A<ICR1) {OCR1A++; } } else {if(OCR1A>0) {OCR1A--; } } if(OCR1A>ICR1) OCR1A=ICR1-1; duty=(uint32_t)100*OCR1A/ICR1; } check_regim(); return; } // end перекл. duty x? freq+=shag : freq-=shag ; //изменение частоты менее 2800 Герц if (freq < 1) freq=1; check_regim(); } // end isr pcint void pwm_gen(){ //настройка таймера1 uint16_t divider=1; icr = (uint32_t)(F_CPU / freq /2 /divider); byte shifts[] = {3,3,2,2}; for(byte i = 0; i < 4; i++){ if (icr > 65536) { divider <<= shifts[i]; icr = (uint32_t)F_CPU / freq /2 /divider; } else { TCCR1B = (i+1)|(1<<WGM13); break; } } ICR1=icr-1; set_duty(); } //end pwm_gen void loop() { if (regim <5){ dds_set();} if (regim==7) {freq_meter();} if (regim==8) {volt_meter();} if (monitor_flag) { monitor_flag=0; monitor_out(); } }//end loop void monitor_out(){ char mystr[14];//массив для вывода строк на дисплей lcd.clrScr(); //Вывод первой строчки lcd.setFont(SmallFont); if (regim<8){ if (freq <1E4) {lcd.print("Frequency, Hz",CENTER,0 ); } if (freq >=1E4) {lcd.print("Frequency, kHz",CENTER,0 ); } } if (regim==8){lcd.print("Voltage, Volt",CENTER,0 ); } //Вывод второй строчки lcd.setFont(MediumNumbers); if (regim<8){ if (freq <10000) { lcd.printNumI(freq, CENTER, 8); } if (freq >=10000u && freq < 1000000ul ) { lcd.printNumF( ((float)freq/1000),3 ,CENTER, 8); } if (freq >=1000000ul ) { lcd.printNumF( ((float)freq/1000),2 ,CENTER, 8); } } if (regim==8){ lcd.printNumF( ((float)involt/1000),3 ,CENTER, 8); } //Вывод третьей строчки lcd.setFont(SmallFont); switch(regim){ case 0: lcd.print("Sinus DDS",CENTER, 32); break; case 1: lcd.print("Triangle DDS",CENTER, 32); break; case 2: lcd.print("Meandr DDS",CENTER, 32); break; case 3: lcd.print("Pila1 DDS",CENTER, 32); break; case 4: lcd.print("Pila2 DDS",CENTER, 32); break; case 5: lcd.print("PWM Mode",CENTER,32); break; case 6: lcd.print("Duty Mode", CENTER, 32);break; case 7: lcd.print("Freq counter", CENTER, 32);break; case 8: lcd.print("Volt meter", CENTER, 32); } //Вывод четвёртой строчки if (regim==6) { sprintf(mystr, "Duty=%d %%", duty); lcd.print(mystr, CENTER, 40);} else if (regim < 6) {sprintf(mystr, "Step=%d", shag); lcd.print(mystr, CENTER, 40);} if (regim==8){ sprintf(mystr, "Vcc= %d.%02d v", (int)(Vbg/1000), (int)Vbg%1000 ); lcd.print(mystr, CENTER, 40); }//end if regim 8 } //end monitor out void set_duty(){ if (regim==6&&ICR1<100) return; if (regim==5 && ICR1<100){ OCR1A=ICR1/2; duty=50; return; } static uint16_t ocr; ocr=(uint32_t)ICR1*duty/100; OCR1A=ocr; } void check_regim(){// проверка и установка режимов работы if (regim <5){ // если DDS режимы: if (freq > 100000) { freq=100000;} TCCR1B=0; TCCR1A=0; DDRB&=~(1<<DDB1); // 9 pin arduino set Z-mode }// end if regim <5 if (regim==5 || regim==6) {// если PWM или Duty TCCR1A=1<<COM1A1; DDRB|=1<<DDB1; // 9 pin set output (pwm out) if (TCCR1B==17 && ICR1<2800){ ICR1=icr; freq= F_CPU/2/ICR1; set_duty(); } else { pwm_gen(); } } PORTF|= 1<<7; //поставить флаг что-б выйти из цикла DDS, если были в цикле if (regim==7){ freq=0;} //если частотометр то обнулить частоту monitor_flag=1; int_on() } ISR (TIMER1_OVF_vect){ int_tic++; } // прерывание частотомера void freq_meter(){ //подготовка таймера1 TCCR1B=0; TCCR1A=0; TIMSK1 = 1<<TOIE1;// подключить прерывание int_tic=0; TCNT1=0; TIFR1=TIFR1;//всё обнулить //подготовка таймера3 TCCR3B=0;TCCR3A=0; TIFR3=TIFR3; //все 3 регистра стравнения что б удобнее смотреть регистр флагов uint16_t ocr = ( (uint32_t)F_CPU/1024) -1; OCR3AH=ocr>>8; OCR3AL=ocr;// -2 sec OCR3BH=ocr>>8; OCR3BL=ocr;// -2 sec OCR3CH=ocr>>8; OCR3CL=ocr;// -2 sec TCCR3B=5<<CS30; //старт счёта 2х секунд TCCR1B=7<<CS10;//старт счёта таймера1 от тактового входа while(TIFR3==0);// крутится пока не будет флагов конца счёта TCCR1B=0;// стоп Тimer1 TIMSK1=0; if ( (TIFR3&(1<<0))==0){// если не было переполнения, то считаем freq= ((uint32_t)int_tic<<16) | TCNT1; //сложить что натикало int_tic=0; TCNT1 = 0; } monitor_flag=1; } //////////////////////ВОЛЬТМЕТР///////////////////////////////// void volt_meter() { Vbg=Vbg_read();//измерить напряжение на Vcc involt=auto_range_read(3);//измерить на входе А3 monitor_flag=1; }//end volt meter // функция для измерения напряжения с автоматическим переключением референса uint16_t auto_range_read(uint8_t in){ uint16_t an_in; an_in= _adc(in,3);//вкючение референса 1024 if (an_in <=(800*4)) {return (an_in/4);} an_in= _adc(in,2); //вкючение референса 2048 if (an_in <=(1800*2)) {return (an_in/2); } an_in=_adc(in,4); //вкючение референса 4096 if (an_in <3800) {return an_in; } an_in= _adc(in,1); an_in= (uint32_t)an_in * Vbg / 4096; return an_in; } ////// analog Read (input, reference) uint16_t _adc( uint8_t in, uint8_t refs){ uint16_t adc; ADCSRA=1<<ADEN | 1<<ADPS2 |0<<ADPS1 |1<<ADPS0; // clk /32 ADCSRB=0; ADCSRC=0; DIDR0=1<<in; switch(refs) { //0 -ext, 1-Vcc, 2-2048, 3-1024, 4-4096 case 0: ADCSRD=0; break; // AREF ничего не меняется, REFS=0 case 1: ADCSRD=0;break; // VCC ничего не меняется, REFS=1 case 2: ADCSRD=0; VCAL=VCAL2; break; // 2v048 case 3: ADCSRD=0; VCAL=VCAL1; break; // 1v1024 case 4: refs=0; ADCSRD=1<<REFS2; VCAL=VCAL3; break; // 4v096 } ADMUX=in&0xF | refs<<6; uint32_t akkum=0; for (int n=0x0; n<=0xff; n++ ) { ADCSRA |= (1<<ADSC); while (ADCSRA&(1<<ADSC)); akkum += ADC; } akkum>>=8; return (uint16_t)akkum; } //функция для измерения напряжения на Vcc uint16_t Vbg_read(){ ADCSRB=0; ADCSRC=0; ADCSRA=1<<ADEN | 1<<ADPS2 |0<<ADPS1 |1<<ADPS0; // adc enable , clk/32 ADMUX=1<<REFS0 | 1<<3 | 1<<2 | 1<<0; //ref=avcc, input= ivref ADCSRD=1<<BGEN ; //ivref=1v024 VCAL=VCAL1;//load 1v024 calibrate byte uint32_t bgaread=0; //собрать 256 семплов для усреднения for (int n=0x0; n<=0xff; n++ ) { ADCSRA |= (1<<ADSC); while (bit_is_set(ADCSRA,ADSC)); bgaread += ADC; } bgaread>>=8; bgaread= ((uint32_t)1024<<12) / bgaread; return (uint16_t)bgaread; } void dds_set(){ PORTF&= ~(1<<7); //флаг DDS switch (dac_trim) { case 0: DACON=1<<DACEN|1<<DAOE|1<<DAVS0; break; case 1: DACON=1<<DACEN|1<<DAOE|1<<DAVS1; ADCSRD= 1<BGEN; break; case 2: DACON=1<<DACEN|1<<DAOE|1<<DAVS1; ADCSRD= 1<BGEN|1<<IVSEL0; break; case 4: DACON=1<<DACEN|1<<DAOE|1<<DAVS1; ADCSRD= 1<BGEN|1<<IVSEL1; break; case 5: DACON= 1<<DACEN | 1<<DAOE; break; } uint32_t temp= (float)0x1000000ul / (F_CPU/8ul) * freq; switch (regim){ case 0: signalOUT(temp, temp>>16,sinewave); break; case 1: signalOUT(temp, temp>>16,trianglewave); break; case 2: signalOUT(temp, temp>>16,squarewave); break; case 3: signalOUT(temp, temp>>16,sawtoothwave); break; case 4: signalOUT(temp, temp>>16,rewsawtoothwave); } //end switch }Генератор V3.1 (для МК STM32F103C8T6)
-добавил в программу поддержку тактовой частоты 128МГц, которая появилась в последних аддонах.
-программный DDS синтезатор переписал на ассемблере, скорость работы в сравнении с версией 3.0 выросла примерно в 2 раза, и стала 5 и 9 MSamples/Sec соответственно для тактовых 72 и 128 МГц. Это существенно расширило качественный диапазон. Теперь синус 500кГц вполне прилично выглядит. Пила на той же частоте правда уже не очень.
-добавил поддержку чипа- тактового синтезатора Si5351. С ним возможно устанавливать частоты с любым шагом от 4кГц до 225МГц. Из 3х выходов на si5351 использовал только один, с двух других можно вывести например фиксированные частоты. Пример есть в скетче.
-подключил все 3 выхода и вход через мультиплексор ADG704. (удобно, что-бы всё вывести на один разъём)
Все аппаратные доработки уместились в прежнем корпусе. Модуль si5351 припаял вторым этажом над R2R, а мультиплексор втиснулся рядом с разъёмом.
Схема: Минимальный набор для сборки по прежнему -плата с МК, дисплей и энкодер. Микросхемы мультиплексора и тактового синтезатора ставить не обязательно, синтезатор даже не отобразится в меню, если при старте программа не обнаружила его подключенным. В схеме есть небольшое отличие от версии 3.0 Нужно было освободить пин B8 для шины I2C, и кнопку энкодера я перекинул на PB1 В остальном распиновка совместима с 3.0
Библиотека для si5351 примечание: в этой библиотеке в файлике si5351.cpp нужно найти строчку Wire.requestFrom(i2c_bus_addr, (uint8_t)1, (uint8_t)false); и удалить выделенный операнд. Это нужно для совместимости с библиотекой wire.h. Библиотека для дисплея st7735. Библиотека Adafruit_GFX, и на всякий случай готовая прошивка.
Скетч:
/*Генератор с регулируемой частотой v3.1 (C)Dimax */ #define pwm2_polar 0 //полярность выхода PWM2 #define paper 0x000000 // цвет фона экрана #define DDSMAX 1E6 //максимальная частота для генеартора DDS #define dds_mpl_72 835.05327478167234049174700635502 //множитель DDS для частоты F_CPU 72МГц //для пересчёта множителя необходимо: частоту на экране * текущий множитель и разделить на фактически измеренную частоту #define dds_mpl_128 469.7191655978919104715512499704 //множитель DDS для частоты F_CPU 128Mhz #define enc_on() timer_attach_interrupt(TIMER4, TIMER_UPDATE_INTERRUPT, enc_int); #define enc_off() timer_detach_interrupt(TIMER4, TIMER_UPDATE_INTERRUPT); #include <Adafruit_ST7735.h> // Hardware-specific library #include <SPI.h> #include "si5351.h" #include <Wire.h> Adafruit_ST7735 tft = Adafruit_ST7735(PB12, PB11,PB10); Si5351 si5351; boolean si5351_found=0; volatile int enc_tic=0, duty_in=50, divider, mon_flag, modebit=1, tim_mode=0; volatile int mode=1;// 0- GEN_si5351, 1-PWM, 2-Duty, 3..7 DDS, 8-Freqmeter volatile int encstep=10; //шаг изменения частоты по умолчанию volatile int32_t freq=1000; //частота по умолчанию 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);//не переносить строки nvic_irq_disable_all();//отключить все прерывания 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);//enc pinMode(PB7,INPUT_PULLUP);//enc mytone(1000,50);//сигнал после старта ///////// Таймер4 -обработчик энкодера TIMER4_BASE->CR1=(1<<2)|(1<<9);//CKD10 URS TIMER4_BASE->CCMR1=0xf1f1; (1<<0)|(1<<8 );//cc1s, cc2s input mapped TI1/TI2 TIMER4_BASE->CCER=(1<<1)|(1<<5)|(1<<0)|(1<<4);//Capture/Compare 1,2 output polarity TIMER4_BASE->SMCR=(1<<1)|(1<<0);//Encoder mode3(SMS bit)стр.407 TIMER4_BASE->CNT=0; TIMER4_BASE->ARR=1;//ограничение счёта ( =3 для двухимпульсного энкодера) TIMER4_BASE->SR=0; TIMER4_BASE->EGR=1; enc_on(); TIMER4_BASE->CR1|=(1<<0);//запуск //прерывание кнопки энкодера attachInterrupt(PB1, key_enc_int, RISING); timer_set(); } void loop() { static int old_mode_loop=-1; if (freq > 8485) {tim_mode=1;} else {tim_mode=0;}// переключать режимы таймера 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_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>1E6){ t_low=0;t_hi=0; duty_out=50; mon_flag=1; return;} //выйти если freq больше мегагерца // Перенастройка таймера 2 в режии измерения длительности импульса и скважности 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; //перечитать регистры. TIMER2_BASE->CR1=(1<<0); __asm volatile( "cpsie i" ); while( (TIMER2_BASE->SR&0x65F)!=0x65F) {asm volatile("nop"); if(mon_flag) {return;} } TIMER2_BASE->CR1=0;// стоп таймер imp_long=(uint32_t) ((TIMER2_BASE->CCR1)*divider); imp_hi=(uint32_t) ((TIMER2_BASE->CCR2)*divider); if (freq <1000){ freq=F_CPU /imp_long;} //если freq Менее 1кГц то использовать данные второго НЧ-измерения частоты duty_out= (float) imp_hi / (imp_long / 100.0) ; t_low= (imp_long-imp_hi) / (F_CPU/1E6) ; t_hi= imp_hi /(F_CPU/1E6); mon_flag=1; } //END freq meter /////////////////////////////////////////////////////////////////////////// /////////*********** ВЫВОД НА ДИСПЛЕЙ************////////////////////////// /////////////////////////////////////////////////////////////////////////// void mon_out(){ //************** Вывод первой строчки***************************** 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.setTextSize(3); tft.setCursor(0, 19); tft.print(" "); //стереть строку if (freq>=1E8){tft.setCursor(0, 19);}//переставление курсора else if (freq>=1E5){tft.setCursor(20, 19);} //переставление курсора else if (freq>=1E3){tft.setCursor(40, 19);} //переставление курсора else if (freq>=100){tft.setCursor(60, 19);} //переставление курсора else {tft.setCursor(80, 19);} //переставление курсора tft.print(freq); //вывод частоты //********************Вывод третьей строчки***************************** 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(" 1"); break; case 10: tft.print(" 10"); break; case 100: tft.print(" 100");break; case 1E3: tft.print(" 1E3");break; case 1E4: tft.print(" 1E4");break; case 1E5: tft.print(" 1E5");break; case 1E6: tft.print(" 1E6");break; }// END switch case } // END if (mode!=8) if ( freq < 1) {tft.fillRect(1,68, 158,59,paper); } mon_flag=0; enc_on(); }//END mon_out //обработчик прерываний энкодера void enc_int(){ if((TIMER4_BASE->CR1)&1<<4) {enc_mode(1);} else {enc_mode(-1);} enc_off();//выключить энкодер }// END VOID // ФУНКЦИЯ конфигурации режимов void enc_mode(int in){ modebit= (GPIOB_BASE->IDR&2); //состояние кнопки 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();} 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: if (tim_mode==0) { freq+=(encstep*in); timer_set(); } else { timer_hi_set(in); } break; case 2: duty_in+=in; if(duty_in>99){duty_in=99;} if(duty_in<1){duty_in=1;} set_duty(); calc_freq(); break; case 8: break; // в частотометре не реагировать на вращение энкодера default: freq+=(encstep*in); if (freq>DDSMAX) {freq=DDSMAX;} mon_flag=1; //wave режимы } //end switch case if (freq<0){freq=0;} }//end enc_mode // обработчик кнопки энкодера void key_enc_int(){ if(!modebit){modebit=1; enc_step_control() ;return;}// если менялся режим -выйти if (mode==2) { mytone(880,30); mode=1; enc_step_control() ; return;} //сменить режим и выйти если были в duty mode mytone(220,75); //звук переключения шага encstep*=10; enc_step_control(); }//end void enc_step_control(){ if (mode!=0 && encstep >1E4) {encstep=1;} if (mode==0 && encstep >1E6) {encstep=1;} mon_flag=1; //флаг вывода на дисплей } void set_duty(){ if (mode==1 && TIMER1_BASE->ARR<100){ TIMER1_BASE->CCR1=(TIMER1_BASE->ARR+1)/2 ; duty_in=50; return; } TIMER1_BASE->CCR1=(TIMER1_BASE->ARR+1)*duty_in/100; } //ОБЩАЯ НАСТРОЙКА ТАЙМЕРА void timer_set(){ GPIOA_BASE->CRL=0xB4444444; //PA7 alt_output GPIOA_BASE->CRH=0x4444444B; //PA8 alt_output TIMER1_BASE->CCMR1=(1<<6)|(1<<5)|(1<<3);//PWM mode 1 TIMER1_BASE->CCMR2=0;TIMER1_BASE->PSC=0; TIMER1_BASE->CCR2=0; TIMER1_BASE->ARR=1; TIMER1_BASE->CR1=1; TIMER1_BASE->CCER=(1<<0)|(1<<2)|(pwm2_polar<<3);//cc1e/cc1ne enable //TIMER1_BASE->BDTR=(1<<15)| 255 ;// dead time sample if (freq<1){freq=0; TIMER1_BASE->CCR1=0;TIMER1_BASE->ARR= 1; mon_flag=1; return;} if (freq>(F_CPU/2)) {freq=F_CPU/2;} divider=1; int tim_arr = F_CPU/freq; while ( (tim_arr/divider) > 65535) {divider++;} TIMER1_BASE->PSC=divider-1; TIMER1_BASE->ARR=(tim_arr/divider)-1; set_duty(); calc_freq(); } //НАСТРОЙКА ТАЙМЕРА НА ЧАСТОТАХ ВЫШЕ 8КГЦ void timer_hi_set(int arr){ __asm volatile( "cpsid i" ); GPIOA_BASE->CRL=0xB4444444; //PA7 alt_output GPIOA_BASE->CRH=0x4444444B; //PA8 alt_output TIMER1_BASE->CCMR1=(1<<6)|(1<<5)|(1<<3);//PWM mode 1 TIMER1_BASE->CCER=(1<<0)|(1<<2)|(pwm2_polar<<3);//cc1e/cc1ne enable //насильно уменьшить шаг с ростом частоты if (TIMER1_BASE->ARR<1000 && encstep > 100) encstep=100; if (TIMER1_BASE->ARR<100 && encstep > 10) encstep=10; if (TIMER1_BASE->ARR<10 && encstep > 1) encstep=1; arr*=encstep; int icr= TIMER1_BASE->ARR-=arr; if (icr<1) {icr=1;} if (icr>65535) {icr=65535;} TIMER1_BASE->ARR=icr; __asm volatile( "cpsie i" ); set_duty(); calc_freq(); } // РАСЧЁТ ЧАСТОТЫ И ТАЙМИНГОВ В РЕЖИМАХ PWM void calc_freq(){ uint32_t imp_long, imp_hi; freq= F_CPU /((TIMER1_BASE->ARR+1)*divider); duty_out= (float) TIMER1_BASE->CCR1 / ((TIMER1_BASE->ARR+1) / 100.0) ; 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) ; t_hi= imp_hi /(F_CPU/1E6); mon_flag=1; } // КОНФИГУРАЦИЯ DDS РЕЖИМОВ void dds_set(){ static uint32_t akkum; static byte oldmode=255; TIMER1_BASE->CCER=0; //timer output pins disable GPIOA_BASE->CRL = 0x33333333;// pa0-pa7 выход 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 * ((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"(&GPIOA_BASE->ODR),[flag]"r"(&mon_flag) : "r9","r8","r7","r6","r2" ); GPIOA_BASE->CRL=0x44444444;// все пины в Z }//END DDS set() // void mytone(int frq, int ms ){ if (mode==8){return;} //таймер в режиме частотометра занят 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 <4000){freq=4000;} if (freq>225E6){ freq=225E6;} //допустимые рамки частот si5351.set_freq(freq*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);} }Доделал свой DDS v3.0, зделал корпус.
Автору большая благодарность.
Правда есть проблемы,для контроля выходного сигнала,проходится использовать осцилограф.
Линейных перемеников для печатных плат ненашол,применил что было а они нелинейны.
Нужно переделывать шкалы резисторов.
Генератор v3.2
-изменён вывод на дисплей, каждые три знака в значении частоты разделяются пробелом.
-добавлен шаг 0,1 Гц для частот до 1кГц. Минимальная частота тоже стала 0,1 Герц
-устранены мелкие недочёты в частотометре.
Схема как в версии 3.1 , без изменений.
Готовая прошивка
Скетч:
/*Генератор с регулируемой частотой v3.2 (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 enc_on() timer_attach_interrupt(TIMER4, TIMER_UPDATE_INTERRUPT, enc_int); #define enc_off() timer_detach_interrupt(TIMER4, TIMER_UPDATE_INTERRUPT); #include <Adafruit_ST7735.h> // Hardware-specific library #include <SPI.h> #include "si5351.h" #include <Wire.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();// дефолтовый сериал не нужен nvic_irq_disable_all();//отключить все прерывания 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);//сигнал после старта ///////// Таймер4 -обработчик энкодера TIMER4_BASE->CR1=(1<<2)|(1<<9);//CKD10 URS TIMER4_BASE->CCMR1=0xf1f1; (1<<0)|(1<<8 );//cc1s, cc2s input mapped TI1/TI2 TIMER4_BASE->CCER=(1<<1)|(1<<5)|(1<<0)|(1<<4);//Capture/Compare 1,2 output polarity TIMER4_BASE->SMCR=(1<<1)|(1<<0);//Encoder mode3(SMS bit)стр.407 TIMER4_BASE->CNT=0; TIMER4_BASE->ARR=1;//ограничение счёта ( =3 для двухимпульсного энкодера) TIMER4_BASE->SR=0; TIMER4_BASE->EGR=1; enc_on(); TIMER4_BASE->CR1|=(1<<0);//запуск //прерывание кнопки энкодера attachInterrupt(PB1, key_enc_int, RISING); if (mode==1){ timer_set(0); } } 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_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,%d ", freq );} //9 -> 0,9 else if (freq<100){sprintf(mybuf," %d,%d ", freq/10, freq%10 );} //99 -> 9,9 else if (freq<1E3){sprintf(mybuf," %d,%d ", freq/10, freq%10 );} //999 -> 99,9 else if (freq<1E4){sprintf(mybuf," %d,%d ", 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); } mon_flag=0; enc_on(); }//END mon_out //обработчик прерываний энкодера void enc_int(){ if((TIMER4_BASE->CR1)&1<<4) {enc_mode(1);} else {enc_mode(-1);} enc_off();//выключить энкодер }// END VOID // ФУНКЦИЯ конфигурации режимов void enc_mode(int in){ modebit= (GPIOB_BASE->IDR&2); //состояние кнопки 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;} mon_flag=1; //DDS режимы } //end switch case if (freq<0){freq=0;} }//end enc_mode // обработчик кнопки энкодера void key_enc_int(){ if(!modebit){modebit=1; enc_step_control() ;return;}// если менялся режим -выйти if (mode==2) { mytone(880,30); mode=1; enc_step_control() ; return;} //сменить режим и выйти если были в duty mode mytone(220,75); //звук переключения шага 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 uint32_t akkum; static byte oldmode=255; TIMER1_BASE->CCER=0; //timer output pins disable GPIOA_BASE->CRL = 0x33333333;// pa0-pa7 выход 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"(&GPIOA_BASE->ODR),[flag]"r"(&mon_flag) : "r9","r8","r7","r6","r2" ); GPIOA_BASE->CRL=0x44444444;// все пины в Z }//END DDS set() // void mytone(int frq, int ms ){ if (mode==8){return;} //таймер в режиме частотометра занят 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);} }PS обновлено 12.01.2019 в связи с тем, что у многих проблемы правильно вставить библиотеки, выкладываю полный архив моей arduino IDE со всеми библиотеками и с самим скетчем в примерах. Останется только запустить Arduino.exe и выбрать в примерах скетч. Архив в зипе. Размер архива 430МБ.
Получил сегодня Si5351 и решил замерить точность установки частоты. На частоте 36 МГц в режиме PWM отклонение около 800 Гц, а у Si5351 - немного более 3 кГц, а на 145 МГц - более 10 кГц. Мерил трансивером по нулевым биениям, предварительно проверив его по сигналам эталонных частот.
MAG-N, да похоже на правду. По крайней мере по части таймера МК, у меня тоже примерно такие цифры. Можно попробывать прописать свой дефайн F_CPU что б подогнать под истину, но этот вариант я не тестировал.
800 Гц на такой частоте - вполне нормально, а вот в разы большая погрешность у специально заточенной микросхемы - это не есть хорошо. Возможно, резонатор на платке "кривоватый", всё же китайская поделка.
#define F_CPU попробовал, но при этом изменяется частота на экране.
Поигрался с Si5351. Подцепил к Ардуино Нано и стал гонять примеры из библиотеки Si5351Arduino-master. Пример si5351_calibration натолкнул на некоторые мысли, в результате чего скетч генератора дополнился строкой (после стр. 45)
si5351.set_correction(80000, SI5351_PLL_INPUT_XO);
Ошибка убралась почти на "0", надо точнее подобрать величину коррекции. В примере всё это делается достаточно наглядно через монитор порта, потом только результат подставить. Всё это индивидуально для каждой платы, так что кому надо - может сделать.
Кстати, применённая микросхема позволяет задействовать для стабилизации частоты технологию GPSDO и, в частности одно из предложенных радиолюбителями решений выполнено Кареном Тадевосяном RA3APW )))
На диапазоне 10м в режиме GPSDO среднеквадратическое отклонение несущей частоты составляет примерно 7 миллиГц, а пиковое значение примерно 90 миллиГц.
Дима, что ты там говорил о любительских конструкциях )))
По-моему, для данного генератора это уже перебор, тем более что надо тащить кабель от подоконника до стола с генератором для антенны GPS. Врядли внутри помещения Глонасс-ГПС будет работать.