Arduino, проблема с прерыванием и SSD1306 OLED

interrupt ssd1306

Я не настолько хорош в этом деле. Вычисляет обороты в минуту с помощью периферийной скорости и прерывания.

Все работает нормально, когда я показываю это в IDE с Serial.print без OLED. Когда я подключаю SSD1306 OLED I2C, прерывание перестает работать. Нет серийногономера.

Если я раскомментирую display.display(), та же проблема с display.setRotation(90): все в порядке, и serial.print начинает работать, но, конечно, не OLED.

В коде у меня есть пустая адресация visadisplay(). Если я помещу эту адресацию в void loop (), то все будет работать, но тогда есть риск несинхронизации, неправильных значений. Я также удалил visadisplay(); без изменений.

Помогите...

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // Ширина OLED-дисплея, в пикселях
#define SCREEN_HEIGHT 32 // Высота OLED-дисплея, в пикселях

// Декларация для дисплея SSD1306, подключенного к I2C (контакты SDA, SCL)
#define OLED_RESET -1 // Reset pin # (или -1 при совместном использовании Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< См. Таблицу адресов; 0x3D для 128x64, 0x3C для 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Функция последовательного отображения.
// Соединения для Nano.
// Подключение датчика Холла: (Выход сигнала) --- (D2).
// Подключение дисплея: (SDA) --- (4A) / (SCL) --- (A5)

volatile unsigned long PulsTid = 0UL;
volatile unsigned long StartTid = 0;
volatile unsigned long SlutTid = 0;
volatile unsigned long CalcTid = 0;
volatile float omkrets = 7.5;
volatile float HastighetPerferi = 0;
volatile unsigned long Rpm = 0;
volatile float Vinkel = 0UL;

void isr() { // процедура обслуживания прерываний
  Vinkel = 0;
  StartTid = millis();
  CalcTid = SlutTid;

  detachInterrupt(0);             //отсоединяет прерывание при вычислении

  PulsTid = (StartTid - SlutTid);
  HastighetPerferi = (omkrets / PulsTid);
  SlutTid = StartTid;
  Rpm = ((HastighetPerferi / (omkrets / 1000) * 60) / 2); // Рассчитать скорость

  visadisplay();
  attachInterrupt(0, isr, FALLING);  //повторное присоединение прерывания
}

void visadisplay(void) {
  Serial.print("Varvtal: ");
  Serial.println(Rpm);
 
  display.setRotation(90);
  display.clearDisplay();
  display.setTextSize(1);              // Размер текста.
  display.setTextColor(SSD1306_WHITE); // Белый текст.
  display.setCursor(0, 0);             // Start position
  display.println("RPM: ");
  display.setCursor(40, 10);
  display.setTextSize(2);
  display.print(Rpm);
  display.display();
}

void setup() {
  Serial.begin(115200);
  attachInterrupt(0, isr, FALLING);  //attaching the interrupt

  //SSD1306_SWITCHCAPVCC = генерировать внутреннее напряжение дисплея от 3,3 В
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Адрес 0x3C для 128x32
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Не продолжайте, цикл бесконечен
  }
}    

void loop(){
}

, 👍1

Обсуждение

Я бы предположил, что код отображения нуждается в прерываниях для работы во время его выполнения, чего не происходит внутри ISR. Serial просто помещает данные в буфер, который затем выталкивается через прерывания. Попробуйте установить флаг внутри ISR, а затем выполнить код отображения в главном цикле, если флаг был установлен, @chrisl

Вам не нужно отключать и повторно включать прерывания внутри ISR (на самом деле, вы действительно не должны этого делать). Кроме того, ISR должен быть очень коротким (как можно меньше инструкций), а ваш-нет. Воспользуйтесь советом @chrisl и установите флаг внутри ISR, проверьте наличие флага в главном цикле и оставьте обработку кода отображения главному циклу., @StarCat

Связь I2C (обычно) требует прерываний. Вы не можете использовать прерывания внутри прерывания. Следовательно, вы не можете использовать связь I2C внутри прерывания. Вы очень мало можете сделать в прерывании., @Majenko


2 ответа


2

Ваша проблема (скорее всего) в том, что вы делаете все от прерывания. Это не только плохая практика, но и на ардуино это вообще невозможно.

На небольших микроконтроллерах у вас обычно есть только один приоритет прерывания (иногда два). Это означает, что одновременно может выполняться только одно прерывание, и если прерывание выполняется, то никакое другое прерывание не может его прервать.

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

Это включает в себя такие вещи, как delay() и, в зависимости от реализации, такие вещи, как Wire (I2C) и SPI.

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

Самым простым "решением" вашей проблемы было бы переместить visadisplay() в loop (), а в isr() просто установить флаг bool, когда есть данные для отображения. Затем ваша функция loop() проверяет этот флаг и, если он установлен, сбрасывает его и вызывает visadisplay().

Кроме того, вам не нужно постоянно отсоединять и повторно присоединять прерывание-если ISR работает, то прерывания все равно не могут произойти.

// ... снип ...
volatile bool doDisplay = false;

void isr()          //процедура обслуживания прерываний
{
  Vinkel = 0;
  StartTid = millis();
  CalcTid = SlutTid;

  PulsTid = (StartTid - SlutTid);
  HastighetPerferi = (omkrets/PulsTid);
  SlutTid = StartTid;
  Rpm = ((HastighetPerferi/(omkrets/1000)* 60)/2);  //Рассчитать скорость

  doDisplay = true; // Сигнал основному потоку, в котором есть данные для отображения
}

// ... снип ...

void loop() {
    if (doDisplay) {       // Если есть что отображать
        doDisplay = false; // затем снимите флаг в следующий раз
        visadisplay();     // и отобразить данные.
    }
}
,

Я тестировал с ЖК - дисплеем и SPI - соединением вместо I2C. Тогда все работает как надо. Немного грустно, что приходится использовать обходные решения по таким элементарным потребностям подключения дисплея., @Ola A


0

О повороте текста на OLED-дисплеях: коды поворота: 0, 1, 2, 3. (см. веб-сайт Adafruit).

Итак, ваш код должен выглядеть так:

  Serial.print("Varvtal: ");
  Serial.println(Rpm);
 
  display.setRotation(1);
  display.clearDisplay();
,