Bitmaps per Drehregler steuern

Moin, liebes Forum!

Folgendes Problem: ich habe eine 8x8 LED-Matrix und möchte nun mehrere bitmaps darauf anzeigen. Per Drehregler möchte ich die verschiedenen Frames anwählen, ohne dabei auf andere bitmaps zuzugreifen. Wenn man am letzten frame angekommen ist, soll man wieder den ersten Frame sehen. Mit modulo komme ich da nicht weiter. Wenn ich den Drehregler nach links drehe, habe ich den Maximalwert von 65535, und wenn ich über die frames des jeweiligen bitmap komme, erscheint das andere bitmap. Das möchte ich ja gerade nicht. Weiß jemand ne Lösung, ich komme nicht drauf :-/

Hier der code:

#include <ClickEncoder.h>
#include <TimerOne.h>
#define BRIGHTNESS=1 // 0=min, 15=max
#include <Adafruit_LEDBackpack.h>  
#include <Wire.h>
#include <Adafruit_GFX.h>
#include "anim.h"
#include "Knopf.h"
#include "zahlenschloss.h"
ClickEncoder *encoder;
int8_t last, value;
uint8_t buttoncounter;
uint8_t encoderValue;
uint32_t code1; // später soll ein code gespeichert werden
uint32_t code2;
uint32_t code3;
uint16_t i, j;
void timerIsr() {
  encoder->service();}
  

//--------------------------------------------------------------------------------------------------------------------- 
Adafruit_8x8matrix matrix = Adafruit_8x8matrix();  
void setup() {
    Serial.begin(9600);
     matrix.begin(0x70);
     matrix.clear();
        matrix.writeDisplay(); 
  encoder = new ClickEncoder(A1, A0, A2, 1);
  encoder->setAccelerationEnabled(false);
  Timer1.initialize(1000);
  Timer1.attachInterrupt(timerIsr);   
  last = -1;
}

//---------------------------------------------------------------------------------------------------------------------//--------------------------------------------------------------------------------------------------------------------- 
void loop() {
       rotary_encoder();
        
    if (buttoncounter == 3) {
       anfang(); 
    }
    if (buttoncounter == 4) {
       knopfposition(); 
    }
    if (buttoncounter == 5) {
       zahlenschloss(); 
    }
    if  (buttoncounter >= 6)  {  matrix.clear();
        matrix.writeDisplay(); }
    }
//---------------------------------------------------------------------------------------------------------------------
void rotary_encoder() {
   value += encoder->getValue();
  if (value/4 != last) {
    encoderValue = value/4;      
    last = encoderValue;
    j = encoderValue;
   
    Serial.print("Encoder Value: ");
    Serial.println(j);
  }
  
  ClickEncoder::Button b = encoder->getButton();
  if (b != ClickEncoder::Open) {
    buttoncounter++;
    value=0;
    Serial.print("Der Button wurde ");
    Serial.print(buttoncounter);
    Serial.println(" mal gedrueckt.");
    #define VERBOSECASE(label) case label: Serial.println(#label); break;
    switch (b) {

      case ClickEncoder::Held:
      anfang; 
          buttoncounter = 2;              
               }    
    }     
} 
void knopfposition() {
  matrix.clear();
  matrix.drawBitmap(0, 0, &knopf[j * 9], 8, 8, 1);
  matrix.writeDisplay();
}
void zahlenschloss() {

  matrix.clear();
  matrix.drawBitmap(0, 0, &zahlen[j * 9], 8, 8, 1);
  matrix.writeDisplay();
}
void anfang() {
     
  for(int i=0; i<(sizeof(anim)/9); i++) { // For each frame...
  matrix.clear();
  matrix.drawBitmap(0, 0, &anim[i * 9], 8, 8, 1);
  matrix.writeDisplay();
delay(100);
  }
}

Versteh ich nicht - Du willst per Encoder zwischen Min- und Maxwert kurbeln. Hab ich das richtig gelesen?

Wenn ja dan setz doch Deine Variable einfach per Abfrage

(Achtung, wieder gefährlicher Pseudocode :))

If encoderwert < minwert
encoderwert = maxwert

If encoderwert > maxwert
encoderwert = minwert

Ich rate mal, es geht dir um die Variablen von void rotary_encoder()

int8_t last, value;
uint8_t encodervalue;
uint16_t j;

Wenn du da meinst, einen Maximalwert "Encoder Value: 65535" zu sehen, hast du dich selbst reingelegt.

Das ist eine -1 oder 255, je nachdem, ob du grade uint8_toder int8_t anguckst.

Am einfachsten wäre es, wenn du durch 8 oder 16 bitmaps rollen könntest, dann kannst du deine /4 und modulo Rechnung ohne jedes stolpern bei Überläufen hinkriegen.

int8_t value;  // keine anderen globalen Variablen erforderlich
void rotary_encoder() {
  value += encoder->getValue();
  byte bmp_number= (value/4) & 0x0f; // 16 bitmaps: 0 .. 15  ( 0x07 für nur 8 bitmaps )
  static byte old_bmp;
  if ( old_bmp != bmp_number ) {
    old_bmp = bmp_number;
    Serial.print(" Encoder Value : "); Serial.println((int)bmp_number);
  }
...
}

ich hoffe, encoder->getValue() liefert -1, 0, +1 zurück je nach Richtung

Danke für die schnelle Hilfe!

Wenn ich meinen seriellen Monitor anschaue, dann gibt der Encoder Werte von 0-65535 aus.

Klaus, ich habe Deinen code probiert, jedoch bleibt das Display beim letzten frame stehen und merkt sich nur die Anzahl der Rasten über diesen Wert, wenn Du verstehst, was ich meine. Ich kann keinen globalen Wert für den Maxwert festlegen, da die Bitmaps unterschiedlich viele frames haben, und der Minwert ist ja 0.

Michael, die verschiedenen Bitmaps rufe ich über den Clickencoder ab. Bei jedem Click ändere ich das Bitmap.
Ja, und das mit dem uint8_t verstehe ich auch nicht. Eigentlich dürfen dann doch nur Werte von 0-255 möglich sein. Mein serieller Monitor sagt aber 65535.

Es geht darum, wie ich in den verschieden bitmaps bleibe und diese durch Drehen des Encoders "loope". Ich habe z.B. frames mit Zahlen von 0-9. Wenn ich jetzt bei 9 angekommen bin, möchte ich wieder bei 0 anfangen. Wenn ich jetzt aber weiterdrehe, dann lande ich im nächsten Bitmap.

Und ein Bitmap hat mehrere Frames ... War mein Verständnisproblem.

Solltest rauskriegen, was es mit den 8 und 16 bit datentypen auf sich hat und mit signed/unsigned.

Nur selber probieren macht schlau, aber versuch mal, dies zu verstehen:

uint8_t myByte=255;
int8_t mySigned = myByte;
Serial.println((int) mySigned); // -1
int myInt = mySigned;
Serial.println(myInt); // -1
uint16_t myWord = mySigned;
Serial.println(myWord); // 65535

Die wenigsten Überlauf Hakeleien ergeben sich wohl, wenn du gleich nach

    value += encoder->getValue();

die Modulo10-Rechnung machst.
Oder, da du noch durch 4 teilen willst, entsprechend nodulo 40.

if (value < 0) value +=40;
if (value >= 40) value -=40;
byte frame= value/4; // 0 .. 9
static byte oldframe;
if (oldframe != frame) {
    oldframe = frame; 
    j = frame; // j wird ausserhalb verwendet -- naja 
    Serial.print(" Encoder Value : "); Serial.println(j);
}

Tissi_2:
Mit modulo komme ich da nicht weiter. Wenn ich den Drehregler nach links drehe, habe ich den Maximalwert von 65535, und wenn ich über die frames des jeweiligen bitmap komme, erscheint das andere bitmap. Das möchte ich ja gerade nicht. Weiß jemand ne Lösung, ich komme nicht drauf :-/
[/code]

Ich kann nicht erkennen, was genau Dein Problem ist, aber das hier sieht nach völligem Quatsch aus:

   value += encoder->getValue();
  if (value/4 != last) {
    encoderValue = value/4;      
    last = encoderValue;
    j = encoderValue;

Das sieht nämlich so aus als wenn Du bei einem Inkrementaldrehgeber eine "Viertelschritt-Auswertung" machst, die Du dann per Division durch 4 zu einer Vollschritt-Auswertung umrechnen möchtest. Sowas kann überhaupt nicht funktionieren, insbesondere dann nicht, wenn Deine gezählten Viertelschritte prellen.

Wenn Du bei Deinem Drehgeber eine Vollschrittzählung brauchst, dann mußt Du auch eine Vollschrittzählung machen! Und keine Viertelschrittzählung, die dann durch 4 geteilt wird, das ergibt nur Murks.

Vielen Dank für die Antworten!
Ich glaube, allen Beteiligten ist klar geworden, dass ich eher wenig von der Sache verstehe. Ich habe mir folgendes Video angeschaut und versucht, den dort benutzen code für meine Anwendung abzuändern:

Dazu kam noch ein Projekt von Adrafruit:

Beides zusammen wollte ich für mein Projekt benutzen.

Das hat soweit ja auch ganz gut funktioniert. Ich bitte daher um ein wenig Verständnis, dass ich von der Materie nicht ganz so viel Plan habe.

Michael, ich verstehe, was Du mir mit den verschiedenen int-Definitionen sagen möchtest, allerdings verstehe ich nicht, warum diese Werte sich dann ändern können.

Auch verstehe ich nicht, dass wenn ich mich in einem bitmap befinde, es trotzdem möglich ist, in ein anderes bitmap zu gelangen.

Ich erwarte von keinem der hier Lesenden/Anwesenden, dass mir der code für mein Projekt geschrieben wird, ich würde gerne nur lernen und verstehen, warum sich die Dinge so verhalten.

Was ich bisher mitgenommen habe ist folgendes:

  • ich muss meinen code für den rotary encoder abändern bzw. eine andere library benutzen
  • bei der Deklaration der Variablen habe ich Murks gebaut, weil die Werte sich nicht entsprechend der Deklaration verhalten und sich im code ändern
  • wahrscheinlich muss ich die bitmaps anders definieren, damit sie sich voneinander abgrenzen. Vielleicht eine andere Speichermethode als "progmem" benutzen?

Auf jeden Fall vielen Dank für die Denkanstöße!

Michael, ich verstehe, was Du mir mit den verschiedenen int-Definitionen sagen möchtest, allerdings verstehe ich nicht, warum diese Werte sich dann ändern können.

Das verstehe ich wiederum nicht :wink: Mein Vorschlag war, probier es aus und versuche, das verblüffende Verhalten der 8- und 16-bit Integer Rechnerei zu verstehen...

Auch verstehe ich nicht, dass wenn ich mich in einem bitmap befinde, es trotzdem möglich ist, in ein anderes bitmap zu gelangen.

Da gibt es bei so einem Controller ohne Betriebssystem überhaupt keinen Schutz.

int c= 0;
int a[3] = {1, 2, 3};  // definiert a[0] ... a[2]
int b = 4;

Serial.println(a[3]); // liefert vermutlich 0 oder 4, aber sicher keinen Fehler

Ich habe mal versucht, die bitmaps in das EEPROM zu schreiben, da ich dann adressenabhängig die frames auslesen kann. Allerdings bin ich da auch zu blöd für. Ich gehe wohl lieber zum Häkeln über :wink:

EEPROM? Wenn dann macht man dass so dass die Daten per PROGMEM im Flash bleiben und nicht ins RAM kopiert werden:

Also Array per PROGMEM deklarieren und das const nicht vergessen:

const byte data[] PROGMEM = { ... };

Und dann mit den entsprechenden Funktionen darauf zugreifen:

byte b = pgm_read_byte(&data[0]);

An pgm_read_byte wird die Adresse des Bytes übergeben das man möchte. Deshalb das &. Im Beispiel auf der PROGMEM Seite wird mit Zeigern gearbeitet. Deshalb steht da einfach "Array Variable + index". Geht auch. Ist das gleiche wie "&Array Variable[index]" Aber die Array Syntax ist hier wahrscheinlich besser verständlich

So sieht bei mir die Abfolge der frames aus (Zahlen von 0-9):

const uint8_t PROGMEM zahlen[] = {
  // Frame 0
  B00111100,
  B01111110,
  B01100110,
  B01100110,
  B01100110,
  B01100110,
  B01111110,
  B00111100,
  25,

  // Frame 1
  B00011000,
  B00111000,
  B01011000,
  B00011000,
  B00011000,
  B00011000,
  B00011000,
  B00111100,
  25,

  // Frame 2
  B00111100,
  B01111110,
  B01100110,
  B00001100,
  B00011000,
  B00110000,
  B01111110,
  B01111110,
  25,

  // Frame 3
  B01111100,
  B01111110,
  B00000110,
  B00111110,
  B00111100,
  B00000110,
  B01111110,
  B01111100,
  25,

  // Frame 4
  B00001100,
  B00011100,
  B00111100,
  B01101100,
  B01111110,
  B01111110,
  B00001100,
  B00001100,
  25,

  // Frame 5
  B01111110,
  B01111110,
  B01100000,
  B01111100,
  B01111110,
  B00000110,
  B01111110,
  B01111100,
  25,

  // Frame 6
  B00111100,
  B01111110,
  B01100000,
  B01111100,
  B01111110,
  B01100110,
  B01111110,
  B00111100,
  25,

  // Frame 7
  B01111110,
  B01111110,
  B00000110,
  B00001110,
  B00011100,
  B00111000,
  B01110000,
  B01100000,
  25,

  // Frame 8
  B00111100,
  B01111110,
  B01100110,
  B00111100,
  B01111110,
  B01100110,
  B01111110,
  B00111100,
  25,

  // Frame 9
  B00111100,
  B01111110,
  B01100110,
  B01111110,
  B00111110,
  B00000110,
  B01111110,
  B00111100,
  25,
};

Mit folgender fuction rufe ich diese aus dem Speicher, wobei "j" der Wert des Rotary encoders ist:

void zahlenschloss() {

  matrix.clear();
  matrix.drawBitmap(0, 0, &zahlen[j * 9], 8, 8, 1);
  matrix.writeDisplay();

Serenifly, könntest Du ein Codebeispiel geben, wie ich es mit Deiner Methode auslese und anzeige und dabei nur in den frames von "Zahlenschloss" bleibe?!

Ich habe doch gesagt du musst die Daten aus dem Flash ins RAM kopieren. Du kannst nicht einfach so an eine Funktion eine Adresse übergeben die ins RAM zeigt wenn die Daten im Flash stehen
drawBitmap() erwartet eigentlich ein komplettes Array. Dann könnte man die Bytes mit memcpy_P() aus dem Flash in einen Puffer im RAM kopieren und diesen als Parameter übergeben.

EDIT:
Da habe ich Unsinn erzählt :frowning:
drawBitmap() kann direkt mit PROGMEM Arrays umgehen :slight_smile:

Hi Serenifly,

erstmal vielen Dank für Deine Hilfe! Wie Du wahrscheinlich siehst, bin ich kein Arduino-Experte, noch nicht einmal ein Amateur - ich bin reiner Anwender und versuche, ein Projekt damit zu verwirklichen. Bisher hat es mit kopierten Code-Schnipseln ganz gut funktioniert, so dass ich schon einige Projekte gut hinbekommen habe.

Ich verstehe, dass man sich in die Materie einarbeiten und sich damit auch beschäftigen muss. Nur übersteigt Deine Antwort meinen bisherigen Wissens- und Verständnishorizont. Ich wusste nicht mal, dass der Chip sowohl Flash als auch Ram hat, geschweige denn wie man diesen anspricht.

Welche Funktion welchen Speicher nutzt, ist mir auch nicht klar. Ich bitte somit um Verständnis, dass ich Deiner Anweisung nicht Folge leisten kann, weil ich sie nicht verstehe.

Ich hoffe dennoch, dass ich weiterhin von Dir oder ähnlich versierten "Fachleuten" Support erhalten werde, damit auch dieses Projekt erfolgreich abgeschlossen werden kann.

MFG Tissi

Grundsätzlich verarbeiten alle normalen Funktionen nur Daten im RAM. Wegen der Harvard-Architektur des Prozessors (d.h. getrennte Adressräume für Flash/ROM und RAM).

Der Flash Speicher ist übrigens der Speicher in dem dein kompiliertes Programm liegt! Wie die Festplatte auf dem PC. ROM wäre ein anderes Wort dafür (nicht zu verwechseln mit dem EEPROM!). Darin kann man aber auch Daten ablegen. Dafür dient "PROGMEM". Wird gewöhnlich nur für größere Sachen wie Arrays verwendet.

Dass es zwei Speicherarten gibt siehst du auch beim Kompilieren:

Compiling 'ArduinoTest3' for 'Arduino/Genuino Uno'
Binary sketch size: 2.190 bytes (used 7% of a 32.256 byte maximum) (0,58 secs)
Minimum Memory Usage: 208 bytes (10% of a 2048 byte maximum)

Das erste ist der Flash Speicher. Das zweite das RAM

Hast du dieses Beispiel gesehen?

Da sieht man ganz klar dass drawBitmap() für Daten im PROGMEM (d.h. Flash) gedacht ist! Also muss man es anders als ich oben gesagt habe nicht erst per Hand ins RAM kopieren :slight_smile:
Sorry, war mein Fehler. :frowning: :frowning: Da hatte ich mir die Library nicht angesehen. Bei Adafruit hätte man sich aber eigentlich denken können dass das schon berücksichtigt ist.

Hier mal Test-Code der einfach jeweils 8 Bytes aus dem Flash ins RAM kopiert und auf Serial ausgibt. Das wird schon mal einfacher wenn man statt einem oder mehrerer getrennter Arrays ein zwei-dimensionales Array verwendet.

Hier mache das kopieren ins RAM per Hand. Bei drawBitmap() brauchst du das nicht!! Da sollte einfach sowas gehen:

matrix.drawBitmap(0, 0, frames[i], 8, 8, 1);

Also direkt eines der Teil-Arrays übergeben

Wichtig ist die Deklaration der Daten! Also zwei-dimensionales Array. Dann kann man einfach die Größe berechnen (aus Gesamtgröße / Elementgröße) und so darüber iterieren. Jeder Index entspricht 8 oder 9 Bytes (kA was bei dir das 25 soll. Kann man wahrscheinlich weglassen)

const byte frames[][9] PROGMEM =
{
  {
    // Frame 0
    B00111100,
    B01111110,
    B01100110,
    B01100110,
    B01100110,
    B01100110,
    B01111110,
    B00111100,
    25
  },

  {
    // Frame 1
    B00011000,
    B00111000,
    B01011000,
    B00011000,
    B00011000,
    B00011000,
    B00011000,
    B00111100,
    25
  },

  {
    // Frame 2
    B00111100,
    B01111110,
    B01100110,
    B00001100,
    B00011000,
    B00110000,
    B01111110,
    B01111110,
    25
  },

  {
    // Frame 3
    B01111100,
    B01111110,
    B00000110,
    B00111110,
    B00111100,
    B00000110,
    B01111110,
    B01111100,
    25
  },

  {
    // Frame 4
    B00001100,
    B00011100,
    B00111100,
    B01101100,
    B01111110,
    B01111110,
    B00001100,
    B00001100,
    25
  },

  {
    // Frame 5
    B01111110,
    B01111110,
    B01100000,
    B01111100,
    B01111110,
    B00000110,
    B01111110,
    B01111100,
    25
  },

  {
    // Frame 6
    B00111100,
    B01111110,
    B01100000,
    B01111100,
    B01111110,
    B01100110,
    B01111110,
    B00111100,
    25
  },

  {
    // Frame 7
    B01111110,
    B01111110,
    B00000110,
    B00001110,
    B00011100,
    B00111000,
    B01110000,
    B01100000,
    25
  },

  {
    // Frame 8
    B00111100,
    B01111110,
    B01100110,
    B00111100,
    B01111110,
    B01100110,
    B01111110,
    B00111100,
    25
  },

  {
    // Frame 9
    B00111100,
    B01111110,
    B01100110,
    B01111110,
    B00111110,
    B00000110,
    B01111110,
    B00111100,
    25
  }
};

const int NUMBER_OF_FRAMES = sizeof(frames) / sizeof(frames[0]);

byte frameBuffer[8];

void setup()
{
  Serial.begin(9600);

  for (int i = 0; i < NUMBER_OF_FRAMES; i++)
  {
    memcpy_P(frameBuffer, frames[i], 8);    //kopiert 8 Bytes aus dem Flash in einen Puffer in RAM (bei drawBitmap() nicht nötig!!)
    printFrame(frameBuffer);                //Puffer ausgeben
  }
}

void loop()
{
}

void printFrame(byte* frameBuffer)
{
  for (int i = 0; i < 8; i++)
  {
    Serial.print(frameBuffer[i], BIN);
    Serial.print(',');
  }
  Serial.println();
}

Hier ist das einfach eine for-Schleife die man auch mit delay() verzögern kann. Oder mit einem Timer steuern kann. Aber du kannst genauso immer bei jedem Encoder-Schritt eins weiterzählen

Vielen Dank! Ich werde es bei Gelegenheit mal ausprobieren und berichten, ob ich es hinbekommen habe. Liebe Grüße