VirtOs - Самый простой двухлучевой осциллограф осциллограф на Arduino
- Войдите на сайт для отправки комментариев
Чт, 01/09/2016 - 10:56
Пример того, как иногда бывает полезно не слушать мнение других.
Все началось с того, что я решил попробовать программировать Ардуинки. Начал с GSM модуля и выяснил, что у меня нет такой важной вещи в быту, как осциллограф. Первым делом пошерстил по сети и нашел «Мини осциллограф Arduino на LCD 5110». Экрана у меня не было и решил создать свой проект «Выжимаем максимум. USB осциллограф на Arduino». Вроде все получилось, но апетиты росли. - Надо внешний АЦП.
Тут стал вопрос. Городить свой колхоз, или спаять уже готовую схему из сети и не парится. Принято решение делать что то самому, но при этом начать с нуля, никого не слушать и не подсматривать как делают другие. Вот у меня родилась схема входного тракта с плавным масштабированием диапазона и автоматической подстройкой нуля. Схему развел на макетке, вроде все работает, но удовлетворение не приходит.
По своему незнанию, что так делать нельзя и никто так не делает, я решил испробовать входной тракт с виртуальной землей.
Задумка такова:
Зачем делать смещение сигнала и потом ловить ноль, если можно сместить щупов землю на необходимое напряжение. То есть земля щупа подключается не к общей земле прибора, а к виртуальной земле, которая поддерживается ОУ.
Что мне это дало:
Плюсы:
1. Из входного тракта пропал один ОУ, который отвечает за смещение сигнала.
2. Опорное напряжение теперь можно плавно изменять в диапазоне 100%-300%, у меня пока только 100%-200%
3. Можно сделать второй луч абсолютно симметричным первому. Смещение будет общим.
4. Общее опорное напряжение для обоих АЦП (внешнего и внутреннего).
5. Смещение можно плавно регулировать. Землю можно притянуть к низу шкалы, таким образом смотреть только положительный сигнал, или к верху, тогда можно смотреть только отрицательное плече. Также его можно плавно смещать на необходимое значение и смотреть не симметричный сигнал на всю шкалу.
6. Отсутствует генератор отрицательно напряжения.
7. Из за отсутствия отрицательного напряжения, нет возможности спалить АЦП при выходе за диапазон.
8. Ну конечно, как вы догадались, два луча.
Минусы:
1. Нет общей земли с прибором, потому работа только от аккумулятора.
2. Максимальная чувствительность прибора 0,016В на пиксель, при шкале 0В-4В.
Вот упрощенная схема входного тракта:
Необходимый уровень смещения земли поддерживается ОУ U1. Опорное запитано с внешнего АЦП. Тут вроде все работает, но аппетиты росли и хотелось чего то большего. Решил изменять опорное 2В переменником, а также изменять смещения земмли.
Тут меня ждал нежданчик. Мощности опорного напряжения, после прохода через переменник 10к явно не хватало. Начинались проседания и взаимопроникновения каналов. Долго бился над проблемой. Добавлять еще один ОУ не хотелось.
В один вечер, при повторном изучении АЦП AD9280, меня осенило. Ведь в АЦП есть свой ОУ, обратная связь которого выведена наружу. Пошаманив несколько часов, получил работающий пример.
Тут надо немного объяснить. Как я понял, обратная связь тут не обычная. До какого то момента все идет по плану, но на грани притягивания REF SENSE к земле, срабатывает тригер в самом АЦП, вход обратной связи REF SENSE отключается и начинает работать как логический вход. Добавил резистор R2 и все стало на свои места. Уменьшая R2, можно добится опорного вплоть до напряжения питания.
Программная и аппаратная часть.
Долго думал какой дисплей выбирать. Решил испробовать черно-белый жидкокристаллический дисплей. Библиотеку похерал как Бог черепаху. Переделал под SPI, убрал чего не нужно, прибрал пару багов по переполнению переменных. Вроде заработало.
Дисплейчик примечателен тем, что он жутко инерционный, и потому отрисовка луча подобно как на ЕЛТ. На высоких частотах до 1мГц, можно даже увидеть сам сигнал. - Мне понравилось. Благо, на дисплее есть свой стабилизатор питания на 3,3В.
Управление энкодером. Тут нечего добавить.
Питание от китайских готовых преобразователей типа DC-DC Step UP.
Скетч. Тут все просто. АЦП тактируется от таймера Т1 частотой 8 000 000 Гц. Максимальный захват за четыре такта 4 000 000 выб/с. Немного повозился с синхронизацией. Второй канал захватывается на высоких частотах линейной функцией, на низких через таймер Т2. На высоких частотах отключаю все прерывания. От использования переменных типа float отказался сразу – нечего там пробразовывать. Подписывать пункты меню не стал. Их всего пять и можно запомнить.




Внимание!!!!
На схеме ошибка.
Должно быть так:
Посчитал смету прибора. Вышло намного больше, чем я думал.
Посчитал смету прибора. Вышло намного больше, чем я думал.
=))по стоимости равен китайскому дешевому ослику
Посчитал смету прибора. Вышло намного больше, чем я думал.
=))по стоимости равен китайскому дешевому ослику
DSO138
На первый взгляд стоит так-же, НО
--в нем нет DC-UP нужно докупить
--в нем нет AKB нужно докупить
--в нем нет зарядки нужно докупить
и он в 4 раза медленней и на один луч.
Вот добавил, как обещал, новые видео и фото.
ВИДЕО
Внешний вид и разборка.
Сигнал 100 кГц, 400 кГц , 666 кГц
Сигнал 1мГц, 2мГц.
Сигнал 100кГц и увеличение 1х1.
Низкие частоты и двухканальный режим.
Вот добавил, как обещал, новые видео и фото.
хороший результат!!!! походу нужно заказывать еще один дисплей)))
Спасибо, Xumuk.
Прибор и прошивка довольно сырые, но усложнять не хотелось. Кому надо, тот еще чего то добавит.
Для начинающего любителя больше и не надо. Потыкать и посмотреть сигнал - самое то. Прибор можно собрать на коленке и не сложен в наладке.
Поправил. Если надо ещё что-то редактировать, обращайтесь.
Классно получилось молодец!!!!!
Okmor не могли бы вы, на схеме, на переменных резистрах указать где какое напряжение должно бить при настройке? А то ацп не хочется спалить.
Классно получилось молодец!!!!!
Okmor не могли бы вы, на схеме, на переменных резистрах указать где какое напряжение должно бить при настройке? А то ацп не хочется спалить.
АЦП здесь нету возможности спалить. Здесь нет отрицательного напряжения. АЦП выдерживает на входе все, что меньше напряжения питания, а болшему негде взятся. Так, что ставте все в среднее положение, и вперед.
И еще вопросик. Я хочу попробовать подключить другой дисплей. Мне нужно освободить A0 перенести на A6 где искать в скетче.
Или как вообще убрать 2 канал покаместь. Что бы место в ардуине освободить. Вы уже знаете где что, а то я не хочу что не буть нужное удалить.
И еще вопросик. Я хочу попробовать подключить другой дисплей. Мне нужно освободить A0 перенести на A6 где искать в скетче.
Или как вообще убрать 2 канал покаместь. Что бы место в ардуине освободить. Вы уже знаете где что, а то я не хочу что не буть нужное удалить.
void setADCSRA () { TCCR1A=0b00000000; FlagSyncOk=false; N=0; ADCSRA = (1 << ADEN) | (1 << ADSC) | (0 << ADATE) | (0 << ADIF) | (0 << ADIE) | (0 << ADPS2) | (1 << ADPS1) | (0 << ADPS0); ADMUX = (0 << REFS1) | (0 << REFS0) | (1 << ADLAR) | (0 << MUX3) | (0 << MUX2) | (0 << MUX1) | (0 << MUX0) ; } //ADCSRA //0 ADEN: Разрешение работы ADC //1 ADSC: Запуск преобразования ADC //0 ADATE: Включение режима автоматического запуска ADC //0 ADIF: Флаг прерывания ADC //0 ADIE: Разрешение прерывания ADC //000 ADPS2:0: Биты управления предделителем ADC //000 - CLK/2 //001 - CLK/2 //010 - CLK/4 //011 - CLK/8 //100 - CLK/16 //101 - CLK/32 //110 - CLK/64 //111 - CLK/128 //ADMUX //REFS1 REFS0 устанавливают какой источник опорного напряжения будет выбран: //00 - опорное напряжение на входе AREF (21 ножка) //01 - Vпитания (вход AREF должен быть отключен. или к нему можно подключить фильтрующий конденсатор) //10 - резерв //11 - внутренний ИОН 1.1 В (к входу AREF можно подключить фильтрующий конденсатор) //ADLAR (5) регистра ADMUX позволяет выравнивать результат преобразования по левому краю при записи в него 1. //MUX3 - MUX0 (3 - 0) - управляют мультиплексором: //0000 - вход ADC0 (23 ножка) //0001 - вход ADC1 (24 ножка) //0010 - вход ADC2 (25 ножка) //0011 - вход ADC3 (26 ножка) //0100 - вход ADC4 (27 ножка) //0101 - вход ADC5 (28 ножка) //0111 - резерв //1000 - датчик температуры //1001 - 1101 - резерв //1110 - 1.1 В //1111 - 0 В (земля)Если я правельно понял
voidsetADCSRA ()это второй канал.Да. setADCSRA () подготавливает АЦП к захвату. Измененя эту функцию, можно переназначить вход АЦП.
Зачем? Используйте А6 для дисплея.
Зачем? Используйте А6 для дисплея.
А6 и А7 это только аналоговый вход АЦП. Так разработчикам AVR захтелось добавить в TQFP и MLF корпус 2 ноги АЦП.
А0-----А5 можно настроить как аналоговые или цифровые порты ввода/вывода.
Кстати. Посмотрите как сделано у меня. Я ногу CS от дисплея тупо посадил на землю. Дисплей один и пусть всегда работает. Ногу RST подтянул к питанию через RC задержку. Не знаю почему, но сбрасывать надо с задержкой.
Итого, экономия двух ног.
Okmor Пологите пожалуйста у меня уже кипят мозги. Уберите с скетча все что касается второго канала и что лишнее на схеме. Выложите или скиньтена почту vetalst777(собака)gmail.com.
Залил сюда. Я не пробовал, но должно работать.
Там есть две функции с задержками. Задержки надо подобрать єкспериментально для точного отображения периода сигнала.
void delay32 () { //ADCSRA = ADCSRA | 1<<ADSC; asm("nop");asm("nop"); asm("nop"); asm("nop");asm("nop"); asm("nop"); PORTB = PORTB | 1<<1; asm("nop");asm("nop"); // х 8 PORTB = PORTB & ~(1<<1); asm("nop"); } void delay64 () { asm("nop");asm("nop"); asm("nop");asm("nop"); asm("nop"); asm("nop");asm("nop"); asm("nop"); PORTB = PORTB | 1<<1; asm("nop"); // х 8 PORTB = PORTB & ~(1<<1); asm("nop"); delay16 ();delay16 ();delay16 (); }Спасибо.
Okmor не могли бы вы подсказать в какой версии программы компилировали свой скетч, а то у меня в Arduino 1.6.11 при компиляции выскакивает ошибка 'freeRam' was not declared in this scope. В чем может быть причина?
У меня 1.6.5.
Относительно freeRam.
Не знаю почему у вас эта функция не поддерживается, но ее можно без ущерба выбросить из скетча, а также все ее упоминания в нем. Функция используется для отладки, что бы видеть оставшуюся оперативную память.
Выбросить вот это и все, где ругается на ее использование.
int freeRam(void) { extern int __bss_end; extern int *__brkval; int free_memory; if ((int)__brkval == 0) { free_memory = ((int)&free_memory) - ((int)&__bss_end); } else { free_memory = ((int)&free_memory) - ((int)__brkval); } return free_memory; }Вот что происходит при компиляции скетча даже в 1.6.5. Выбрасывание этого куска не помогло. Компилирую в ардуино нано АТМега 328. Что еще может быть?
UpgaradeLibSPI.ino: In function 'void setup()':
UpgaradeLibSPI:70: error: 'freeRam' was not declared in this scope
'freeRam' was not declared in this scope
Почему-то скрин экрана загрузился, но не передался. Программа останавливается в
void setup() {
SPI.begin() ;
gicd.begin(12) ;
glcd.display(); delay(500); glcd.clear();
glcd.drawstringS(0, 0, "MEMORY= " + String(freeRam())) ;
На этой строке встает и пишет 'freeRam' was not declared in this scope
Уберите строку
glcd.drawstringS(0, 0, "MEMORY= " + String(freeRam())) ;
После того, как убрал эту строку появились следующие ошибки:
UpgaradeLibSPI.ino: In function 'void __vector_4()':
UpgaradeLibSPI.ino:181:18: warning: suggest parentheses around '&&' within '||' [-Wparentheses]
UpgaradeLibSPI.ino:184:19: warning: suggest parentheses around '&&' within '||' [-Wparentheses]
UpgaradeLibSPI.ino: In function 'void DravValue()':
UpgaradeLibSPI.ino:288:18: warning: unused variable 'kU' [-Wunused-variable]
D:\Моя папка\Arduino\Версии программы Ардуино\arduino-1.6.5-r5\libraries\ST7565\ST7565.cpp:29:77: fatal error: c:\Program Files\Arduino\hardware\arduino\avr\libraries\SPI\SPI.h: No such file or directory
#include <c:\Program Files\Arduino\hardware\arduino\avr\libraries\SPI\SPI.h>
^
compilation terminated.
Ошибка компиляции.
Уважаемый Okmor, не могли бы вы выложить портабельную версию ардуино, в которой компилировали с вашей библиотекой. Подозреваю, что дело только в этом, т.к. в разных версиях разные ошибки.
gun-58
В библиотеке ST7565 откройте файл ST7565.CPP программой блокнот Notepad++. найдите строчку #include <c:\Program Files\Arduino\hardware\arduino\avr\libraries\SPI\SPI.h> это путь к файлу SPI.h в библиотеке SPI. Вам нужно прописать #include < D:\Моя папка\Arduino\Версии программы Ардуино\arduino-1.6.5-r5\hardware\arduino\avr\libraries\SPI\SPI.h> и сохранить файл.
UpgaradeLibSPI.ino: In function 'void __vector_4()':
UpgaradeLibSPI.ino:181:18: warning: suggest parentheses around '&&' within '||' [-Wparentheses]
UpgaradeLibSPI.ino:184:19: warning: suggest parentheses around '&&' within '||' [-Wparentheses]
UpgaradeLibSPI.ino: In function 'void DravValue()':
UpgaradeLibSPI.ino:288:18: warning: unused variable 'kU' [-Wunused-variable]
D:\Моя папка\Arduino\Версии программы Ардуино\arduino-1.6.5-r5\libraries\ST7565\ST7565.cpp:29:77: fatal error: c:\Program Files\Arduino\hardware\arduino\avr\libraries\SPI\SPI.h: No such file or directory
#include <c:\Program Files\Arduino\hardware\arduino\avr\libraries\SPI\SPI.h>
^
compilation terminated.
Ошибка компиляции.
У вас arduino-1.6.5 лежит на диске D D:\Моя папка\Arduino\Версии программы Ардуино\arduino-1.6.5-r5 и в самой Arduino 1.6.5 есть библиотека SPI находиться в папках hardware\arduino\avr\libraries\SPI\SPI.h .Некоторые версии Arduino ID могут глючить из за названий папок на русском по пути к программе Arduino ID.
Arduino 1.6.11 у меня без проблем все компелирует. НО ЕЙ НЕ НРАВИТЬСЯ ДЛИННЫЙ ПУТЬ К СКЕТЧУ (папка в папке и так далие) когда есть папки с названиями написаных на русском языке, только цифры или англ. Я просто кидаю скетч в папке на рабочий стол и нет больше ни каких ошибок. А сначала тоже было куча ошибок, и я подумал что глючьная какая то новоя весия, и холел удалить ее нафиг.
Arduino 1.6.11 хочет чтобы Структура программы была написана по порядку. Сначала наши какие то функции ( int freeRam(void) ) и т.д, Потом void setup() и в конце скетча void loop(). Хотя у меня сейчас глотает все скетчи написанны как угодно, и процентов на 5 -7 уменьшает код по сравнению с 1.6.5 и 1.6.7.
VetalST спасибо за разъяснение. Нашел ошибку в библиотеке SPI. Сейчас компиляция проходит, только пишет, что недостаточно памяти и программа может работать не стабильно.
Okmor приветствую! Пытаюсь заказать детали для повторения вашего устройства. Подскажите пожалуйста, а зачем в перечне деталей нужен кварц?
И у меня к вам тоже вопрос. По мимо кварца ????? Собрал пытаюсь переделать на другой дисплей показывает но. Какие два из перемннных резистров 1 смещение земли, 2 Диапазон 1-2в, 3 подстройка измерителя опорного, вынесены на корпус прибора ???? И не большие проблемы с энкодором. У вас я так понял энкодер 24 такта на оборот, а у меня 20 такта на оборот. Поэтому показания перепрыгивают через один, и попадаю в нужное меню струдом. Как это исправить программно не пойму, перепробывал почьти все. Так что в списке деталей уточните какой энкодер.
Это список для сборки без ардуинки на голом камне. Иначе кварца не надо.
Подстройка измерителя опорного спрятана внутри.
Смещение земли и диапазон наружу.
Увас энкодер с двойным щелчком. Должно лечится:
static boolean n=0; if (n=!n){return;} // снять ремарку для энкодеров с двойным щелчком
ISR (PCINT1_vect) { //static boolean n=0; if (n=!n){return;} // снять ремарку для энкодеров с двойным щелчком TimeClickB = millis(); static byte old_n = PINC & B00011000; // маска B00011000 что б читать только нужные 2 бита byte new_n = PINC & B00011000; if (old_n == 8 && new_n == 24 || old_n == 16 && new_n == 0) { if ((PINC&(1<<5))==0){NumberMenu = constrain(NumberMenu-1,0,4) ;} else {--EncoderGo /*-= 1*/;} } if (old_n == 16 && new_n == 24 || old_n == 8 && new_n == 0) { if ((PINC&(1<<5))==0){NumberMenu = constrain(NumberMenu+1,0,4) ;} else {++EncoderGo /*+= 1*/;} } old_n = new_n; }Это не ошибка, а предупреждение, что может нехватить оперативной памяти. Для этого и включил функцию freeRam(void). Я ею проверял всюду ли хватает памяти.
Можно поинтересоватся под какой дисплей вы переделываете.
Можно поинтересоватся под какой дисплей вы переделываете.
могу предположить что под ls020
Я тоже присматривался к этим дисплеям, но им по схеме нужно питание 9В на подсветку. - Забросил. Лежат три штуки без дела.
Я тоже присматривался к этим дисплеям, но им по схеме нужно питание 9В на подсветку. - Забросил. Лежат три штуки без дела.
думаю step-up в корпус пихнуть не проблема, я щас плату делаю под корпус для этого дисплея и ардуино ослика, с прошивкой от VetalST
Энкодер почьти поборол но на большых частотах все равно успевает иногда впоймать второй щелчк. Нужно ставить номальный как у вас.
ISR (PCINT1_vect) { //static boolean n=0; if (n=!n){return;} // снять ремарку для энкодеров с двойным щелчком TimeClickB = millis(); static byte old_n = PINC & B00011000; // маска B00011000 что б читать только нужные 2 бита byte new_n = PINC & B00011000; static boolean n=0; //if (n=!n){return;} // снять ремарку для энкодеров с двойным щелчком if (old_n == 8 && new_n == 24 || old_n == 16 && new_n == 0) { if (n=!n){return;} if ((PINC&(1<<5))==0){NumberMenu = constrain(NumberMenu-1,0,5) ;} else {--EncoderGo /*-= 1*/;} } if (old_n == 16 && new_n == 24 || old_n == 8 && new_n == 0) { if (n=!n){return;} if ((PINC&(1<<5))==0){NumberMenu = constrain(NumberMenu+1,0,5) ;} else {++EncoderGo /*+= 1*/;} } old_n = new_n; }Экран да ls020. Но никак не могу побороть полностью мерцание, оно то и не мешает но неприятно. Спаял на плате разводил как умею я не спец. Микросхема МРС 6022 была под рукой, думал паять "хамелеон". Сегодня проверил в качестве генератора шим как в пультоскопе и в самодельном "Тестер полупроводников". Шим сигнал 100 кгц, 250 кгц идеольно 500 кгц надо подстроить переменным кондером на (5 рФ) нет под рукой впаял на 3 рФ. Хотя можно и микросхему поменять на AD823 может будет еще лутше.
Теперь о проблемах.
Не определяет частоту после 30 кгц где копать ????
Не могу разобратся полностью с сеткой и нормально с шкалалой. Библиотека под ls020 получается что начало координат Y 0 и X 0 снизу в левом углу, в верх Y 131 и в право X 175. Поэтому все через задницу. Как обычно 0 X 0 Y в верху с лева, X длинна а Y ширина высота экрана. Вот здесь я и запутался. Сегодня поишу как инвертировать экран, или изменю в библиотеке викину все лишнее и оставлю только пиксель линию и печать текста. Okmor может у вас есть нормалная библиотека под ls020. И не могли бы вы прокоментировать в скетче где что для екрана и желательно все остальное.
Не определяет частоту после 30 кгц где копать ????
Все заработало. Позаимствовал с вашей библиотеки функцию drawstringS и скрестил с своей библиотекой.
Теперь нужно разобратся с экраном. Нужно придумать как не закрашивать экран полностью, а затирать только сам график.
Я тоже присматривался к этим дисплеям, но им по схеме нужно питание 9В на подсветку. - Забросил. Лежат три штуки без дела.
Надо копировать буфер захвата, если память позволит.
Затирать график получилось. Теперь нужно разобратся с сеткой и немного помелочам. Попозже освобожусь еще помучаю контроллеры. А то я уже умудрился, залочить две самодельные ардуины, програмировал через usb бар все в зборе. Пришлось доктором мк востанавливать.
Затирать график получилось. Теперь нужно разобратся с сеткой и немного помелочам. Попозже освобожусь еще помучаю контроллеры. А то я уже умудрился, залочить две самодельные ардуины, програмировал через usb бар все в зборе. Пришлось доктором мк востанавливать.
///////////////////////////////////// //D9 ADC clock //D8 lcd RS //D10 lcd CS //D11 lcd DATA //D12 lcd //// //D13 lcd CLK //A0 lcd RST ///////////////////////////////////// //A1 Измерение опорнгого напряжения //A2 Free //A3 encoder //A4 encoder //A5 encoder button //A6 ADC второй канал //A7 Измерение смещения земли //////////////////////////////////// uint8_t MyBuff_ADC[256]; uint8_t MyBuff_AI0[256]; uint8_t AI; byte old_ADCSRA; byte old_ADMUX; uint8_t MyBuff_ADCpov[256]; int StartPointpov = 0; #include <stdio.h> //#include <SPI.h> #include "S65_LCD.h" #include "ADCRead.h" int EncoderGo = 0; //Меню. Номер активного меню int StartPoint = 0; //Синхронізаія Зсув, як результат синхронізації int LastPoint = 0; //Остання точка byte CountPoint = 0; //Кылькысть точок int SyncVal = 127+20; //Синхронізаія Значення синхронызацыъ int Tinc = 20; long Fr=0; #define Epin_A A4 //Енкодер #define Epin_B A3 //Енкодер #define Epin_SW A5 //Енкодер Кнопка byte NumberMenu ; //Меню. Поточне меню int MeasureDelay = 3; //Меню. Частота вибырки byte Scale = 2 ; //Меню. Масштаб / 0, 2, 4 int BiasY = 0 ; //Меню. Змыщення по осі У int BiasX = 0; int BiasAI0 = 0; int NullPoint = 175; //Меню. Нульова точка ///128; int Uref ; //опорна напруга int UrefD; int GroundValue ; //Зміщення землі byte FlagSyncOk = true; //АЦП закінчило зчитування byte ADCGoes = true; //АЦП в процесі зчитування byte N =0; unsigned long TimeClickB; // Коли клыкнули кнопкою /////////////////////////////////////////////////////////// /* void testdrawchar(void) { for (int i=0; i < 168; i++) { // glcd.drawchar((i % 21) * 6, i/21, i+127); } } */ void stretchChart(byte M) { for (int k = 0; k <= M; k++) { for (int i = 254; i >= 1; i-=2) { MyBuff_ADC[i]= MyBuff_ADC[i/2]; } for (int i = 253; i >= 1; i-=2) { MyBuff_ADC[i]= (MyBuff_ADC[i-1]+MyBuff_ADC[i+1])/2; } } } void setADCSRA () { // ВТОРОЙ КАНАЛ НА А6 TCCR1A=0b00000000; FlagSyncOk=false; N=0; ADCSRA = (1 << ADEN) | (1 << ADSC) | (0 << ADATE) | (0 << ADIF) | (0 << ADIE) | (0 << ADPS2) | (1 << ADPS1) | (0 << ADPS0); ADMUX = (0 << REFS1) | (0 << REFS0) | (1 << ADLAR) | (0 << MUX3) | (1 << MUX2) | (1 << MUX1) | (0 << MUX0) ; //(0 << MUX3) | (0 << MUX2) | (0 << MUX1) | (0 << MUX0) ; А0 //(0 << MUX3) | (1 << MUX2) | (1 << MUX1) | (0 << MUX0) ; А6 // PORTB = PORTB | 1<<1; PORTB = PORTB & ~(1<<1); // PORTB = PORTB | 1<<1; PORTB = PORTB & ~(1<<1); // PORTB = PORTB | 1<<1; PORTB = PORTB & ~(1<<1); // PORTB = PORTB | 1<<1; PORTB = PORTB & ~(1<<1); } void scanframe () { switch (MeasureDelay) { case 0: { TCCR1A=0b01000000; noInterrupts(); LoadPin4(); interrupts(); stretchChart(2); FlagSyncOk=true; break; } case 1: { TCCR1A=0b01000000; noInterrupts(); LoadPin4(); interrupts(); stretchChart(1); FlagSyncOk=true; break;} case 2: { TCCR1A=0b01000000; noInterrupts(); LoadPin4(); interrupts(); stretchChart(0); FlagSyncOk=true; break;} case 3: { TCCR1A=0b01000000; noInterrupts(); LoadPin4(); interrupts(); FlagSyncOk=true; break;} case 4: { TCCR1A=0b01000000; noInterrupts(); LoadPin8(); interrupts(); FlagSyncOk=true; break;} case 5: { TCCR1A=0b01000000; noInterrupts(); LoadPin16(); interrupts(); FlagSyncOk=true; break;} case 6: { noInterrupts(); setADCSRA (); LoadPin32(); interrupts(); FlagSyncOk=true; break;} case 7: { noInterrupts(); setADCSRA (); LoadPin64(); interrupts(); FlagSyncOk=true; break;} case 8: { setADCSRA (); TCCR2A=0; TCCR2B=(0<<CS22)|(0<<CS21)|(1<<CS20); OCR2A=100; TIMSK2=(1<<OCIE2A); break;} case 9: { setADCSRA (); TCCR2A=0; TCCR2B=(0<<CS22)|(0<<CS21)|(1<<CS20); OCR2A=227; TIMSK2=(1<<OCIE2A); break;} case 10: { setADCSRA (); TCCR2A=0; TCCR2B=(0<<CS22)|(1<<CS21)|(0<<CS20); OCR2A=61; TIMSK2=(1<<OCIE2A); break;} case 11: { setADCSRA (); TCCR2A=0; TCCR2B=(0<<CS22)|(1<<CS21)|(0<<CS20); OCR2A=127; TIMSK2=(1<<OCIE2A); break;} case 12: { setADCSRA (); TCCR2A=0; TCCR2B=(0<<CS22)|(1<<CS21)|(0<<CS20); OCR2A=255; TIMSK2=(1<<OCIE2A); break;} case 13: { setADCSRA (); TCCR2A=0; TCCR2B=(0<<CS22)|(1<<CS21)|(1<<CS20); OCR2A=128; TIMSK2=(1<<OCIE2A); break;} case 14: { setADCSRA (); TCCR2A=0; TCCR2B=(0<<CS22)|(1<<CS21)|(1<<CS20); OCR2A=255; TIMSK2=(1<<OCIE2A); break;} default: { ; break;} } } ISR(TIMER2_COMPA_vect) { TCNT2 = 0; // Работает, если вручную обнулять PORTB = PORTB | 1<<1; PORTB = PORTB & ~(1<<1); ADCSRA |= (1<<ADSC); MyBuff_ADC[N] = PIND ; MyBuff_AI0[N+2] = ADCH; N++; if (N>= 250) {TIMSK2=0b00000000; FlagSyncOk = true; ; }; } ISR (PCINT1_vect) { //static boolean n=0; if (n=!n){return;} // снять ремарку для энкодеров с двойным щелчком TimeClickB = millis(); static byte old_n = PINC & B00011000; // маска B00011000 что б читать только нужные 2 бита byte new_n = PINC & B00011000; static boolean n=0; //if (n=!n){return;} // снять ремарку для энкодеров с двойным щелчком if (old_n == 8 && new_n == 24 || old_n == 16 && new_n == 0) { if (n=!n){return;} --EncoderGo /*-= 1*/; } if (old_n == 16 && new_n == 24 || old_n == 8 && new_n == 0) { if (n=!n){return;} ++EncoderGo /*+= 1*/; } old_n = new_n; } void ExecEncoder() { /////////////// свое ///////////////////////////////////////////////////// if ((PINC&(1<<5))==0){ delay (30); while((PINC&(1<<5))==0); delay (30); if(NumberMenu < 6){ NumberMenu++; } if(NumberMenu >= 6){ NumberMenu=0; } clear(BLACK); //закрашиваем дисплей, } ////////////////////////////////////////////////////////////////////////// if (EncoderGo != 0) { switch (NumberMenu) { case 0: {//час розгортки if ((PINC&(1<<2))==0) {MeasureDelay = MeasureDelay + (EncoderGo); //*0*/byte MUX = 2; // Частота вибырки MeasureDelay = constrain(MeasureDelay, 0, 14);} else { BiasX = BiasX - EncoderGo; BiasX = constrain(BiasX, 0-StartPoint, 175-StartPoint); }; clear(BLACK); //закрашиваем дисплей, break; } case 1: { // точка синхронізації SyncVal += EncoderGo<<Scale; // constrain(EncoderGo,-10,10)/*<<Scale*/; clear(BLACK); //закрашиваем дисплей, break; } case 2: {// масштаб по вертикалі Scale = constrain(Scale + EncoderGo, 0, 2); //*2*/byte Scale = 0 ;//Масштаб / 0, 2, 4 clear(BLACK); //закрашиваем дисплей, break; }// constrain(Scale+K,0,2); break;} case 3: { BiasY = BiasY + EncoderGo ; //*3*/int Bias ; //Змыщення по осі У clear(BLACK); //закрашиваем дисплей, break; } case 4: { BiasAI0 = BiasAI0 + EncoderGo ; //*3*/int Bias ; //Змыщення по осі У clear(BLACK); //закрашиваем дисплей, break; } case 5: { //ls020_put_string8(60,120,"OFF LCD ",CL8_RED,CL8_BLACK,1,2,1); // пишем break; } }; EncoderGo = 0; } } void synchronization() { StartPoint = 6; LastPoint = 0; CountPoint = 0; for (int i = 6; i <= 253 ; i++) // тут { if( ((MyBuff_ADC[(i-3 )]*2+MyBuff_ADC[(i-2 )]*4+MyBuff_ADC[(i-1 )]*8)<=SyncVal*14)&& ((MyBuff_ADC[(i+3 )]*2+MyBuff_ADC[(i+2 )]*4+MyBuff_ADC[(i+1 )]*8)>=SyncVal*14) ) {// Тут знайшли точку. if (StartPoint==6) {StartPoint = (i) ; i= i+8; } else { if ((i-StartPoint)>125 ) { break;} else { CountPoint++; LastPoint = i; i= i+8; } } }; }; } void ReadRefValue () { NullPoint = (NullPoint*3+analogread_map(7,0,1023,0,255))/4 ; UrefD = (UrefD*3+analogread256(1))/4 ; ///13*3.6 Калыбровка дыапазону Uref = UrefD*13/11; } void DravValue() { // glcd.drawstringS(0, 4, "BiasX= " + String(BiasX)) ; // glcd.drawstringS(0, 5, "StartPoint= " + String(StartPoint)) ; // glcd.drawstringS(0,2, "Cnt= " + String(CountPoint)) ; // glcd.drawstringS(0, 2, "MDelay= " + String(MeasureDelay)) ; // glcd.drawstringS(0, 0, "Fr= " + String((32000000>>MeasureDelay)/((LastPoint-StartPoint)/CountPoint))) ; // glcd.drawstringS(0, 1, "Memory= " + String(freeRam())) ; // glcd.drawstringS(0, 3, "Pin2= " + String(((PINC&(1<<2))==0))) ; unsigned long kU = 65535; byte show = !((millis()-TimeClickB)>2000) ; String period = String(MeasureDelay); switch (MeasureDelay) { case 0: {Tinc = 16 ; period = "0.5"; period[3]=230; break ;} // 0.625мс 32000000 case 1: {Tinc = 16 ; period = "1" ; period[1]=230; break ;} // 1.25мс 16000000 case 2: {Tinc = 20 ; period = "2.5" ; period[3]=230; break ;} // 2.5µs 8000000 case 3: {Tinc = 20 ; period = "5" ; period[1]=230; break ;} // 5мс*******400000************** case 4: {Tinc = 20 ; period = "10" ; period[2]=230; break ;} // 5мс case 5: {Tinc = 20 ; period = "20" ; period[2]=230; break ;} case 6: {Tinc = 25 ; period = "50" ; period[2]=230; break ;} case 7: {Tinc = 25 ; period = "100" ; period[3]=230; break ;} case 8: {Tinc = 25 ; period = "200" ; period[3]=230; break ;} case 9: {Tinc = 25 ; period = "400" ; period[3]=230; break ;} case 10: {Tinc = 25 ; period = "800" ; period[3]=230; break ;} case 11: {Tinc = 25 ; period = "1.6" ; break ;} case 12: {Tinc = 25 ; period = "3.2" ; break ;} case 13: {Tinc = 25 ; period = "6.4" ; break ;} case 14: {Tinc = 25 ; period = "12.8" ; break ;} }; if ((show)|(NumberMenu==0)) { ls020_drawstringS(155-10*period.length(), 2, period + "ms", 1, RED, BLACK) ;/// if (CountPoint!=0){ Fr = ((32000000*CountPoint/(LastPoint-StartPoint)>>MeasureDelay)); // glcd.drawstringS(0, 0, "Fr= " + String(Fr) ) ; ls020_drawstringS(0, 2, "Fr= " + String(Fr) + " Hz ", 1, RED, BLACK) ;///// } else { //glcd.drawstringS(0, 0,"Fr= ----"); ls020_drawstringS(0, 2, "Fr= ----", 1, RED, BLACK) ;///// } } ; // 0 Частота вибырки //if ((show)|(NumberMenu==1)) {glcd.drawstringS(127-6*String(SyncVal).length(), 1, String(SyncVal) ) ;} ; if ((show)|(NumberMenu==1)) {ls020_drawstringS(171-10*String(SyncVal).length(), 12, String(SyncVal), 1,RED, BLACK) ;} ; // if ((show)|(NumberMenu==2)) {glcd.drawstringS(127-6*String(Scale).length(), 2 , String(Scale) ) ;} ; //1 Масштаб / 0, 2, 4 if ((show)|(NumberMenu==2)) {ls020_drawstringS(171-10*String(Scale).length(), 22, String(Scale), 1, RED, BLACK) ;} ; // if ((show)|(NumberMenu==3)) {glcd.drawstringS(127-6*String(BiasY).length(), 3 , String(BiasY) ) ;} ; if ((show)|(NumberMenu==3)) {ls020_drawstringS(171-10*String(BiasY).length(), 32, String(BiasY), 1, RED, BLACK) ;} ; // if ((show)|(NumberMenu==4)) {glcd.drawstringS(127-6*String(BiasAI0).length(), 4 , String(BiasAI0) ) ;} ; if ((show)|(NumberMenu==4)) {ls020_drawstringS(171-10*String(BiasAI0).length(), 42, String(BiasAI0), 1, RED, BLACK) ;} ; if (show){ // указатель меню //glcd.drawline(127, 0+NumberMenu*8, 127, 7+NumberMenu*8, BLACK); //glcd.drawline(126, 0+NumberMenu*8, 126, 7+NumberMenu*8, BLACK); drawLine(175, 0+NumberMenu*10, 175, 10+NumberMenu*10, YELLOW); // линия drawLine(174, 0+NumberMenu*10, 174, 10+NumberMenu*10, YELLOW); // линия drawLine(173, 0+NumberMenu*10, 173, 10+NumberMenu*10, YELLOW); // линия }; } void drawL (int y) { if (y <= 255) { for (int i = 0; i <= 175; i = i + 4) // горизонтальная сетка { setpixel(i,y,WHITE); //lcd_setPixel8(i,y,CL8_BLUE); } } } void DrawGrid() { int y = 0; drawL (NullPoint+Uref/2); drawL (NullPoint+Uref/2-Uref/16); drawL (NullPoint+Uref/4); drawL (NullPoint+Uref/8); drawL (NullPoint+Uref/16); drawL (NullPoint+Uref/2-Uref/8); drawL (NullPoint+Uref/4+Uref/16); drawL (NullPoint+Uref/4-Uref/16); drawL (NullPoint-(Uref/2)); drawL (NullPoint-(Uref/2-Uref/16)); drawL (NullPoint-(Uref/4)); drawL (NullPoint-(Uref/8)); drawL (NullPoint-(Uref/16)); drawL (NullPoint-(Uref/2-Uref/8)); drawL (NullPoint-(Uref/4+Uref/16)); drawL (NullPoint-(Uref/4-Uref/16)); for (int i = 0; i <= 175; i = i + 4) // 0 сетки центр графика { setpixel(i,NullPoint,BLUE); //lcd_setPixel8(i,NullPoint,CL8_BLUE); // setpixel(i+1,NullPoint,CL8_WHITE); //lcd_setPixel8(i+1,NullPoint,CL8_BLUE); setpixel(i+2,NullPoint,BLUE);///// // setpixel(i+3,NullPoint,CL8_WHITE);///// }; for (int x = Tinc; x <= 175; x = x + Tinc) // вертикальная разметка { for ( y = 0; y <= 256; y = y + (4<<Scale)) { setpixel(x, y, WHITE); // lcd_setPixel8(x, y, CL8_BLUE); } }; drawLine(0-BiasX, ScalePint(SyncVal), 5-BiasX, ScalePint(SyncVal),WHITE); drawLine(LastPoint-StartPoint, 126,LastPoint-StartPoint, 131,WHITE); drawLine(0-BiasX, 126,0-BiasX, 131,WHITE); } void DrawChart() // uint8_t MyBuff_ADCpov[256] int StartPointpov = 0; { int aY1 ; int aY2; int bY1 ; int bY2; StartPoint = StartPoint+BiasX; for (int i = StartPointpov; i <= 175+StartPointpov; i++) { bY1= ScalePint(MyBuff_ADCpov[i]); bY2= ScalePint(MyBuff_ADCpov[i+1]); //LCD_line(i-StartPointpov, bY1, i+1-StartPointpov, bY2, CL8_BLACK); // линия drawLine(i-StartPointpov, bY1, i+1-StartPointpov, bY2, BLACK);//график }; ////////////////////////////////////////////////////////////////////////////// for (int i = StartPoint; i <= 175+StartPoint; i++) { aY1= ScalePint(MyBuff_ADC[i]); aY2= ScalePint(MyBuff_ADC[i+1]); // LCD_line(i-StartPoint, aY1, i+1-StartPoint, aY2, CL8_GREEN); drawLine(i-StartPoint, aY1, i+1-StartPoint, aY2, GREEN);//график// линия }; StartPointpov = StartPoint; for(int i = 0; i < 256; i++){ // копируем с одного в другой массив MyBuff_ADCpov[i] = MyBuff_ADC[i]; // убираем все лишнее } }; //////////////////////////////////////////////////////////////////////////////// int ScalePint( int Y) { return constrain((((255 - Y)>>Scale)- BiasY-((127 >> Scale) - 95)) , 0 , 255) ; //((((255 - Y)>>Scale)- BiasY-((127 >> Scale) - 100)), 0 , 255) ; } void setpixel(int X , int Y, char color ) { Y = ScalePint( Y); if ((Y>0)&&(Y<132)) {drawPixel(X, Y, color);} }; int freeRam(void) { extern int __bss_end; extern int *__brkval; int free_memory; if ((int)__brkval == 0) { free_memory = ((int)&free_memory) - ((int)&__bss_end); } else { free_memory = ((int)&free_memory) - ((int)__brkval); } return free_memory; } ///////////////////////////////////////////////////////////////// void setup() { SPI_init(); //Start HW SPI ; do nothing in SW SPI LCD_init(); //init Ports and LCD //закрашиваем дисплей, ls020_8bit_mode(0); //Установите 8-битный режим LCD //ls020_8_color_mode(); clear(BLACK); //закрашиваем дисплей, delay(500); PORTC|=(1<<PC2)|(1<<PC3)|(1<<PC4)|(1<<PC5)|(1<<PC6); //подтяжка кнопок на A3, A4 , A5 DDRB |= (1<<PB1); // Настройка D9 на вихід 9-8=1 // ADMUX = (0 << REFS1) | (0 << REFS0) ; // Зовнішня опорна напруга ADCSRA = (1 << ADEN) | (1 << ADSC) | (0 << ADATE) | (0 << ADIF) | (0 << ADIE) | (0 << ADPS2) | (1 << ADPS1) | (0 << ADPS0); ADMUX = (0 << REFS1) | (0 << REFS0) | (1 << ADLAR) | (0 << MUX3) | (0 << MUX2) | (0 << MUX1) | (0 << MUX0) ; //Енкодер включити переривання PCICR = 1 << PCIE1; //разрешить пренрывание PCMSK1 = B00111000 ; //(1<<PCINT12)|(1<<PCINT11); //выбрать входы //PCMSK1=(1<<PCINT9)|(1<<PCINT10)|(1<<PCINT11); //выбор пинов прерывания (9-A1, 10-A2, 11-A3) // B00000000 // 76543210 //PCINT14 PCINT13 PCINT12 PCINT11 PCINT10 PCINT9 PCINT8 //Таймер ногодриг для АЦП TCCR1A=0b01000000; //подключить выход OC1A первого таймера TCCR1B=0b00001001;// } void loop() { if (FlagSyncOk){ ExecEncoder(); // glcd.clear(); synchronization(); DrawChart(); if ((PINC&(1<<2))==0) { ReadRefValue (); scanframe (); }; DrawGrid(); DravValue(); //glcd.display(); if(NumberMenu==5){ // отключение дисплея drawText(20, 50,"OFF LCD", 2, RED, BLACK);// пишем if (PINC & (1 << PINC2)) { lcd_poweroff (); //выключить дисплей } } }; }; /////////////////////////////////////////////////////////////Okmor Как расчитать сетку что бы была на весь экран ?????? Библиотека стандартная X0, Y0 в верху слева.
return constrain((((255 - Y)>>Scale)- BiasY-((127 >> Scale) - 95)) , 0 , 255) ; методом тыка опустил вниз и все. Сетка на пол экрана и дальше у меня уже мозги кипят.
return constrain((((255 - Y)>>Scale)- BiasY-((127 >> Scale) - 95)) , 0 , 255) ; методом тыка опустил вниз и все. Сетка на пол экрана и дальше у меня уже мозги кипят.
int ScalePint( int Y) { return constrain( (((255 - Y)>>Scale)- BiasY-((127 >> Scale) - 31)) , 0 , 255) ;}Функция ScalePint масштабирует изображение и зеркалирует ось Y.
Scale - делитель масштаба. при Scale = (0, 1, 2) масштаб делится на (/1, /2, /4) соответственно.
(255 - Y)>>Scale - переворачивает изображение и масштабирует его
BiasY - это возможность сдвигать график вверх/вниз при большом масштабе.
(127 >> Scale) - 31) - это поправка для того, чо бы график всегда был по центру, а не масштабировался от нулевой точки. Для вас будет (127 >> Scale) - 62)
При высоте дисплея 132 точки, у вас будет только две градации маштаба Scale = (0, 1)
поправте здесь:
case 2: {// масштаб по вертикалі Scale = constrain(Scale + EncoderGo, 0, 1); //*2*/byte Scale = 0 ;//Масштаб / 0, 2void setpixel(int X , int Y, byte color ) { Y = ScalePint( Y); if ((Y>0)&&(Y<127)) {glcd.setpixel(X, Y, color);} };Я могу ошибатся, тотому, что сам чуть голову не сломал избегая делений и типа float.
Вот убогое видео https://youtu.be/DLu7stRZ8K8 того что получилось
Это видео https://www.youtube.com/watch?v=5UyFiLwjZNM проблеммы с разверткой и синхронезацией. Как это можно исправить. И как она вообще работает и как ее перещитать.
Вот видео https://youtu.be/gyQD55e4lJU Без синхронезации чистый график на всеж режимах развертки.
////////////////////////////// ИЗМЕНЯЛ /////////////////////////////////////////////////// void synchronization() { StartPoint = 4;// = 6 ИЗМЕНЯЛ на 4 LastPoint = 0; CountPoint = 0; for (int i = 4; i <= 253 ; i++) // тут //// i = 6 ИЗМЕНЯЛ на 4 { if(((MyBuff_ADC[(i-3 )]*2+MyBuff_ADC[(i-2 )]*4+MyBuff_ADC[(i-1 )]*8)<=SyncVal*14)&& ((MyBuff_ADC[(i+3 )]*2+MyBuff_ADC[(i+2 )]*4+MyBuff_ADC[(i+1 )]*8)>=SyncVal*14)) {// Тут знайшли точку. if (StartPoint==4) {StartPoint = (i) ; i= i+8; } //StartPoint==6 ИЗМЕНЯЛ на 4 else { if ((i-StartPoint)>110 ) { break;}// 125 ИЗМЕНЯЛ на 110 else { CountPoint++; LastPoint = i; i= i+8; } } }; }; } ///////////////////////////////////////////////////////////////////////////////////////////Меняя в //" for (int i = 4; i <= 253 ; i++) // тут //// i = 6 ИЗМЕНЯЛ на 4"// меньше 4 появляются шумы в начале графика когда нет сигнала.
StartPoint = 4;// = 6 ИЗМЕНЯЛ на 4 и if ((i-StartPoint)>110 ) { break;}// 125 ИЗМЕНЯЛ на 110
Уменишились шумы. На меньшей развертке шумов нет вообще.
А развернуть еще шире шумы появляются до пол экрана
КАК ЭТО МОЖНО ИСПРАВИТЬ ???????
" Зачем делать смещение сигнала и потом ловить ноль, если можно сместить щупов землю на необходимое напряжение."
а у вас при этом между землей осцилографа и землей измеряемого сигнала разности напряжения не получится? ток не потечет?
В посте выше добавил видео без синхронезации чистый график на всеж режимах развертки.
Ток не потечет, если осциллограф питается от аккумулятора. Это цена которую придется платить за простоту.
Читайте мануал. Я до конца не понял, но преобразование происходит за четыре такта. Тоесть конвеером. Потому первые четыре измерения надо отбрасывать.
Это не шумы, а конец захваченого кадра. У нас отсутствует аппаратный тригер, потому мы тупо захватываем кадр и потом чмещаем на значение синхронизации. Побороть можно увеличив буфер. В осциллографе без экрана, тоесть USB, недостающую часть я дополнял предварительным кадром.
Осторожно подделка!!!
Заказал для пробы два ОУ AD823
На картинке усиление синуса коэфициент 1:1. Графики раздвинул для наглядности.