Официальный сайт компании Arduino по адресу arduino.cc
Синхронизация I2C с INT
- Войдите или зарегистрируйтесь, чтобы получить возможность отправлять комментарии
Пт, 23/11/2018 - 17:29
Всем привет. Прошу помощи у знатоков)))
Реализовал Многоканальный Dimmer, работающий в фоновом режиме.
В качестве нагрузки подключил лампочку. Отлично работает.
Добавить OLED дисплей SSD1306 i2c, для отображения информации. Инфа на дисплее отображается, но вот незадача лампочка стала мерцать. Взял исходный код и методом научного тыка выяснилось, что мерцание возникает, если в коде обновляю экран display.display().
Ни как не могу понять, что да как. Конфликт I2C с прерыванием INT? Таймер в какой момент не считает? Подскажите, как правильно реализовать совместную работу диммера и дисплея.
#include <CyberLib.h> //Библиотека от Cyber-Place.ru #include <SPI.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> volatile uint8_t tic, Dimmer1, Dimmer2, Dimmer3; uint8_t data; unsigned long curTime, loopTime; // Время работы программы volatile int Hz = 0; // Назовем частотой в сети переменного тока . #define SCREEN_WIDTH 128 // OLED дисплей ширина, пикселей #define SCREEN_HEIGHT 64 // OLED дисплей высота, пикселей // Декларация для дисплея SSD1306, подключенного к I2C (контакты SDA, SCL) #define OLED_RESET 7 // Reset pin # (or -1 if sharing Arduino reset pin) Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); void setup() { D4_Out; //Настраиваем порты на выход D4_Low; //установить на выходах низкий уровень сигнала D2_In; //настраиваем порт на вход для отслеживания прохождения сигнала через ноль //CHANGE – прерывание вызывается при любом изменении значения на входе; //RISING – вызов прерывания при изменении уровня напряжения с низкого (Low) на высокий(HIGH) //FALLING – вызов прерывания при изменении уровня напряжения с высокого (HIGH) на низкий (Low) attachInterrupt(0, detect_up, LOW); // настроить срабатывание прерывания interrupt0 на pin 2 на низкий уровень StartTimer1(halfcycle, 40); //время для одного разряда ШИМ StopTimer1(); //остановить таймер UART_Init(115200); //инициализация порта display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // Address 0x3D for 128x64 // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally } //********************обработчики прерываний******************************* void halfcycle() //прерывания таймера { tic++; //счетчик if(Dimmer1 < tic ) D4_High; //управляем выходом } void detect_up() // обработка внешнего прерывания. Сработает по переднему фронту { tic=0; //обнулить счетчик ResumeTimer1(); //запустить таймер attachInterrupt(0, detect_down, HIGH); //перепрограммировать прерывание на другой обработчик } void detect_down() // обработка внешнего прерывания. Сработает по заднему фронту { StopTimer1(); //остановить таймер D4_Low; //логический ноль на выходы tic=0; //обнулить счетчик attachInterrupt(0, detect_up, LOW); //перепрограммировать прерывание на другой обработчик } void loop() { Dimmer1=160; display.clearDisplay(); // Сlear display display.setTextSize(2); // Normal 1:1 pixel scale display.setTextColor(WHITE); // Draw white text display.setCursor(0,0); // Start at top-left corner display.print(Hz++); display.display(); delay(500); }
дак выводи на дисплей только когда что-то реально меняется а не постоянно в лупе
Пробовал, по всякому. По времени, по изменению.
Все равно мерцает. Правда реже. В момент обновления экрана)
ну это потому что задача лавирования между двумя прерываниями не так тривиальна как кажется. тут без семафоров не обойтись и нада _очень_ аккуратно программировать. Настолько моя компетенция не простирается. Жди спицалистов.
Пробовал, по всякому. По времени, по изменению.
Все равно мерцает. Правда реже. В момент обновления экрана)
Хобит, во-первых, вот эти три строчки вызывать каждый раз в ЛУПе ни к чему:
вставьте их в Setup
Во-вторых, насколько я помню display.dispay() нужно только для графики а у вас только текст. Попробуйте вовсе выкинуть эту строчку.
Ну и потом, прислушайтесь к Деду - выводите на экран только при обновлении, сейчас вы в коде обновляете экран много раз в секунду - это нафик никому не нужно.
Мерцает 2 раза в секунду.
Пробовал, по всякому. По времени, по изменению.
Хоть так хоть этак. Мерцание в момент обновления.
В этой библиотеке:
display.clearDisplay(); // Очистить дисплей.
display.display(); // Обновить дисплей.
я бы еще попробовал библиотеку "полегче" для вывода на экран.
"Лампочку" запитай отдельным пином без i2c
В этой библиотеке:
display.clearDisplay(); // Очистить дисплей.
display.display(); // Обновить дисплей.
Я вам сказал три строчки убрать в Сетап, а последнюю просто закомментировать. Вы попробовали или просто теоретизируете? - попробуйте, не бойтесь, сломать этим вы ничего не можете
XOBIT,
я понимаю, что код Вы скачали, но ответьте мне, Вы понимаете что там написано и можете осмысленно менять? Или нет? Только честно. Если первое, я расскажу Вам что делать. Если второе. то мой рассказ Вам не поможет, я только пальцы об клавиши зря бить буду.
Да, понимаю что там написано и могу осмысленно менять.
Пробовал обновлять дисплей, когда нет прерывания, когда счетчик не считает, когда выход = 0.
Так ничего и не получилось.
В этой библиотеке:
display.clearDisplay(); // Очистить дисплей.
display.display(); // Обновить дисплей.
Я вам сказал три строчки убрать в Сетап, а последнюю просто закомментировать. Вы попробовали или просто теоретизируете? - попробуйте, не бойтесь, сломать этим вы ничего не можете
Теоретизирую для вас теорию. )
Специально по вашей просьбе попробовал.
И...
О чудо! Мерцание пропало. Спасибо.
НО...
Перестал работать дисплей.
И все почему? Потому что:
В этой библиотеке:
display.clearDisplay(); // Очистить дисплей.
display.display(); // Обновить дисплей.
P.S. вы были правы, я ничего не сломал)
Проверить напряжение на оптроне (D4) осциллографом с экраном (в момент включения) и без экрана.
Деда в #1 посте правильно сказал. Заведи себе флаг и взводи его после смены прерывания. У тебя будет 10 мс на прорисовку. Начинай рисовать только после флага. И делей надо заменить на кошерный миллис как в#5 предложили. Если после этого останется мерцание воспользуйся советом из #6.
Да, понимаю что там написано и могу осмысленно менять.
Ну, значит, будем надеяться. что я не зря потерял час на написание этого поста.
Пробовал обновлять дисплей, когда нет прерывания, когда счетчик не считает, когда выход = 0.
Так ничего и не получилось.
Ну, давайте рассуждать.
Всё, что Вам говорили коллеги (про вынос инициализации дисплея в setup(), и т.п.) всё это верно. Т.е. инициализацию дисплея выносим в setup и это не обсуждается.
Давайте поговорим по алгоритму.
Сейчас Вы делаете следующее
Что имеем.
Итак, что теперь делать.
Про п.3 думайте сами. Может Вас это устраивает, я не знаю. Если не устраивает, то можно, напроимер, просто убрать одну из функций detect_up () или detect_down(), и работать только по одной из ник, считая при этом, что максимальный коэффициент заполнения ШИМ не 250 как сейчас, а 500.
Я буду исходить из того, что п.3 Вы оставляете как есть, а расскажу Вам как бороться с п.п. 5 и 6, т.к. борьба с п.8 требует не переделывания этого кода, а выбрасывания его и переписывания совсем по-другому.
По п.5. делаете следующее.
Всё, таймер у Вас работает 1 раз за период вместо нынешних 250! Функциональность при этом не поменялась (см. примечание * в конце)
По п.6. делаете следующее
Для начала заметьте, что между detect_down() и detect_up() у Вас таймер выключен, и вообще ничего не происходит, т.е. у Вас есть целых 10мс свободного времени, когда Вы можете вывести на экран что хотите и это никому не помешает.
Поэтому, Вы можете, например, прямо в конце функции detect_down() вставить проверку, что с последнего вывода прошло 500мс (через millis()) и если прошло, то прямо там вызывать display.print(Hz++); (только не забудьте разрешить прерывания, а то I2C не сработает). Тогда loop() у Вас останется просто пустым. А чему там ещё быть?
При таком подходе Вы гарантируете, что вывод на экран не пересечётся по времени ни с какими другими действиями.
Можно сделать то же самое и ещё 100500 способами, в любом случае – Вы должны привязать вывод к свободному времени, а не выводить «когда придётся» как Вы это делаете сейчас.
---------------------------
Примечание (*). Это один из тех случаев, когда Вы экономите время на совершеннейших «спичках» (используете Cyberlib) и при этом, там где надо бы экономить – в алгоритме, делаете 250 раз то, что можно сделать 1 раз. У меня на эту тему была здесь статья, посмотрите. Если интересно.
парочка замечаний по диммеру
тиристор или симмистор выключается самостоятельно в нулях напряжения, тока сети, т.е. нет необходимости удерживать его включенным всю оставшуюся часть полупериода, его нужно только включить.
для включения требуется определенное время, конкретно дадашит на конкретный прибор, и, наверно, 100мкс для большинства приборов достаточно. т.е. существует минимально необходимое время включения, в которое пвполне возможно можно уложить вывод на экран.
т.е. процесс может выглядеть примерно так:
- ловим переход напряжения из нуля в верх;
- формируем задержку до включения;
-формируем имульс запуска и вывод на экран;
все, цикл
нет необходимости ловить еще какие-то фронты.
т.е. тут остается открытым вопрос, сколько времени занимает вывод на экран.
ЕвгенийП, спсб за ответ. Подробно, познавательно.
Не уж то я дожил до того момента,
когда приходишь на форум с вопросом, а в ответ тебе конструктивный ответ.
Ни тебе моральных унижений, рассказов о том что есть гугл.)
Вы правильно описали алгоритм работы.
Пока решил оставить алгоритм работы диммера, как есть.
250 раз вместо 1 раз, ни и...
Хочу все таки в первую очередь отладить работу экрана.
Т.е. сразу перешел к п.№6. По вашему совету попробовал в конце функции detect_down() добавить вывод на экран.
Как результат, лампочка включается и через ~1сек гаснет.
Экран отображает число 144. И все. После перезапуска все повторяется.
Попробовал убрать delay и через промежуточную переменную oled_update обновлять экран.
Мерцает стабильно в момент обновления экрана.
Возможно ВН правильно мыслит?
"т.е. тут остается открытым вопрос, сколько времени занимает вывод на экран."
Не успевает. Хотя OLED 400kHz. Прерывания внутри прерывания запрещены.
Выполняются по очереди. Отсюда и просадка, мерцание.
А если посмотреть сколько времени занимает вывод на экран?
Ведь я Вам писал, что "не забудьте разрешить прерывания". Вы не забыли?
Возможно ВН правильно мыслит?
"т.е. тут остается открытым вопрос, сколько времени занимает вывод на экран."
Да, мыслит то он правильно, только 10мс - так много, что вопрос так не стоит.
Ну, напечатайте скорость вывода, чтобы успокоиться, делов-то. Запомните перед выводом значение micros и сравните с ним же после вывода - получите время.
да, тогда будет понятно, в какое из времен этот вывод можно помещать, и какие ограничения при этом могут возникнуть на работу диммера
Результат:
~40мс. не многовато?
однозначно много, выкинуть очистку дисплея, самому преобразовывать число в строку, добивать пробелами недостающие символы и 9 строке выводить уже строку.
Результат:
~38мс.
из любопытства решил сам попробовать скорость вывода
получилось на один символ менее 1 миллисекунды.
тупо вывод строки получается ~ 0.92 миллисекунды на один символ
Результат:
~38мс.
9 строку на 4ое место вставьте - будут более честные результаты
В #20 посте, так делал.
Сейчас не могу посмотреть, посмотрите сами. Почти уверен, что ВСЕ операции, кроме display.display() не работают с экраном, а работаю только с буфером. Поэтому измерять надо только эту операцию.
Такое время меня удивило - ни в какие ворота не лезет. Попробуйте не чистить весь экран, а работать только с нужной областью. Если не поможет, надо смотреть что там в библиотеке так тормозит (например, display.display() может всегда работать только с полным экраном) и улучшать её. Этот дисплей позволяет выводить за раз по 8 пикселей, так что фигачить весь экран нет никакой нужды.
Результат:
~38мс.
жуть!
Посмотрел исходники библиотеки - написано не слишком эффективно. Чтобы обновить 1 пиксель на экране - она каждый раз выплевыет в дисплей весь буфер. Но, по-моему все равно долго, 38мс - это просто вечность.
Возьмите другую библиотеку, их для SSD1306 как минимум десяток в сети.
Такое время меня удивило - ни в какие ворота не лезет. Попробуйте не чистить весь экран, а работать только с нужной областью. Если не поможет, надо смотреть что там в библиотеке так тормозит (например, display.display() может всегда работать только с полным экраном)
там так написано, что все сначала рисуется в буфере, а потом буфер ЦЕЛИКОМ выплевывается на экран. Возможности обновлять экран по частям в библиотеке нет (ну или я не нашел - смотрел по диагонали).
ИМХО, "улучшать" эту библиотеку нет смысла, "проще сделать новую" :) - просто взять другую, благо их в сети несколько
ИМХО, "улучшать" эту библиотеку нет смысла, "проще сделать новую" :) - просто взять другую, благо их в сети несколько
Может сразу конкретную посоветуете? Чтобы время не терять.
ИМХО, "улучшать" эту библиотеку нет смысла, "проще сделать новую" :) - просто взять другую, благо их в сети несколько
Может сразу конкретную посоветуете? Чтобы время не терять.
их реально много - поиск вам в помощь по словам ss1306 library, я например пробовал штук пять, остановился на OzOled (пример выше), но в любом случае дело вкуса.
Может сразу конкретную посоветуете? Чтобы время не терять.
например, в последнем проекте использовал вот эту:
https://github.com/greiman/SSD1306Ascii
она маленькая, не использует буфер и быстрая. Но, как видно из названия - совсем без графики, только текст
Могу предложить то, что сам использую, но там нет шрифтов (и буфера тоже нет). Есть только вывод битмапа. Что мне надо я рисую руками и вывожу точно туда, куда надо и ни на байт больше, чем реально надо не вывожу. Не только в 328, но и в тиньке 45-ой на ура работает (I2C там через USI делаю).
А у меня совсем без текста, только графика. Цифры (огромного размера) приделал когда надо было, как пачку битмапов в прогмеме :)
Планирую использовать дисплей для вывода информации.
Так что мне и текст и цифирки) нужны. Графика - нет.
b707 подскажи что не так?
Мерцает раз в секунду.
b707 подскажи что не так?
Мерцает раз в секунду.
а если oled.clear() в строке 32 закомментировать?
Выводит 1, через секунду 11, 111,1111 и т.д.
за облать экрана уходит...
Сейчас не могу посмотреть, посмотрите сами. Почти уверен, что ВСЕ операции, кроме display.display() не работают с экраном, а работаю только с буфером. Поэтому измерять надо только эту операцию.
Такое время меня удивило - ни в какие ворота не лезет.
09
time_1 = micros();
10
11
display.display();
12
13
Serial
.print(
"Time_oled1:"
);
Serial
.println(micros() - time_1);
// Время
Хобит, попробуйте так
Ещё бы оно влезло, если длительность сериалпринта измерять.
при скорости 9600 вывод одного символа порядка 1мс, у автора их менее двадцати, так что на очистку дисплея все равно тратится примерно 20мс - много
Результат:
~39мс.
Хобит, попробуйте так
Норм
Результат:
~39мс.
фигня какая-то, быть такого не может. Скорость Сериал какая?
Хобит, попробуйте так
Норм
а поподробнее? :) - мерцает, нет?
Если не измерять длительность сериалпринта.
нет, не мерцает.
нет, не мерцает.
проблема решена?
b707 подскажи что не так?
Мерцает раз в секунду.
а если oled.clear() в строке 32 закомментировать?
Надо было не закомментировать, а перенести в Setup.
Надо было не закомментировать, а перенести в Setup.
это вы мне советуете? :)
В Сетапе она нафик не нужна, так как метод begin() сам чистит экран.
У меня такое впечатление. что вы смысла всех этих команд не очень понимаете :) И на вопрос не ответили
Да не очень. Спсб.
Вообще-то 38 мс на передачу всего экрана - это нормально.
Считаем: частота - 400 МГц. Если включать всю служебную информацию, паузы, сигналы начыала и окончагия передачи и пр, это 50 кбайт/с. Объем буфера 1 киб = 1.024 кб. Т.е. если бы мы передавали только нужную информацию, то скорость могла бы быть 1000/50*1.024=20.5 мс. Ну а если добавить служебную, паузы и т.п. - 38 вполне реально.
В качестве альтерниативы рискну предложить на пробу свою библиотечку: http://arduino.ru/forum/proekty/asoled-kompaktnaya-biblioteka-dlya-oled-displeya-128kh64-s-kirillitsei-utf-8