Themabewertung:
  • 0 Bewertung(en) - 0 im Durchschnitt
  • 1
  • 2
  • 3
  • 4
  • 5
Mikrocontroller, Graphikdisplays - entdecke die Möglichkeiten
#1
Hallo Radiofreunde,

anbei möchte ich Euch an einem kleinen Projekt teilhaben lassen. Wie die Überschrift es sagt, geht es in dem Projekt
um die Verwendung eines Mikrocontrollers und eines Graphikdisplays.
Im Unterschied zu vielen im Forum vorgestellten Projekten verwende ich nicht die Arduino Programmierumgebung. 
Ich möchte selbst herausfinden was unbedingt nötig ist, um z. B. ein magisches Auge durch moderne Komponenten zu ersetzen.

Nicht zuletzt schreibe ich diesen Thread, weil mich unser Mitglied Bernhard45 mit seinen tollen Beiträgen über den
Ersatz magischer Augen neugierig machte.

Vielleicht erkennt der Leser durch diesen ausführlichen Thread ein wenig besser, welche Leistung Bernhard45 erbracht hat,
und kann diese Erfahrungen eventuell für eigene Projekte nutzen.

Ich beginne mit der Auswahl des Mikrocontrollers. Da sonst überwiegend auf Arduino(s) zurückgegriffen wird, entscheide ich
mich für einen Controller vom Typ ATmega32. Er hat einen Programmspeicher von 32kByte, einen SRAM-Speicher von
2kByte Größe und ähnelt damit dem ATmega328P des weit verbreiteten Arduino nano.
Das sollte für erste Tests mehr als ausreichen.

   

Beim Display sieht es finster aus - ich habe keins - und frage einen Freund, ob er mir eins ausleihen könnte.
Das Display ist monochrom (blauer Hintergrund, weisse Schrift) und hat eine Größe von 1,3 Zoll.
Die Auflösung beträgt 128 x 64 Punkte bzw. Pixel. Die Stromversorgung des Displays ist auf 3,3V oder 5V ausgelegt.
Es wird mittels I2C-Bus über 2 Drähte gesteuert. Insgesamt müssen also (nur) 4 Leitungen angeschlossen werden,
was die Verdrahtung vereinfacht.

   

   

Ich benutze für das Projekt ein Entwicklungsboard vom Typ STK500. Bei dem Board sind (fast) alle Pins des Controllers
auf Pfostenstecker 'verdrahtet', so dass ein Versuchsaufbau schnell zusammen gesteckt werden kann.
Das Board besitzt einige Steckplätze zur Aufnahme verschiedenster Mikrocontroller der AVR Familie.
Zusätzlich stellt es 8 Taster zur Signal Eingabe und 8 LEDs zur Ausgabe von digitalen Zuständen bereit.   

   

Die Verbindung zum Rechner erfolgt seriell (mittels eines USB-Seriell Interface).

   

Das Programm wird mit einem Editor (TextWrangler) geschrieben. Die Programmiersprache ist AVR-GCC.
Der GNU Compiler für AVR (avr-gcc) übersetzt die programmierten Zeilen in den Maschinencode.
Das Programm AVRdude überträgt den Maschinencode in den Programmspeicher des Mikrocontrollers.

Das war die Theorie - jetzt folgt die Praxis!
Wie fange ich an, was brauche ich, um solch ein Display benutzen zu können?

An erster Stelle fehlt jetzt eine Bibliothek zur Ansteuerung des Displays. Bibliotheken (engl. Library) stellen dem Programmierer
u.a. Funktionen unterschiedlichster Art zur Verfügung. Das verkürzt den Programmieraufwand ganz wesentlich.

Eine gute Quelle für Informationen rund um Mikrocontroller ist - oh Wunder - das Mikrocontroller-Forum.
Hier suchte ich nach OLED Display und fand einen passenden Beitrag. Das Besondere an dieser Library (finde ich) ist,
dass sie neben grundsätzlichen Zeichenfunktionen (Linie, Kreis, Rechteck...) auch noch den I2C-Treiber für ein Display enthält.
Vielen Dank an den Author, der zusätzlich ein Beispiel des Hauptprogramms (main.c) veröffentlichte!

Um die Funktionen der Library kennen zu lernen und letztendlich nutzen zu können, lese ich das dazugehörige include-file (lcd_gfx.h).

Code:
/*
* This file is part of lcd library for ssd1306/sh1106 oled-display.
*
* lcd library for ssd1306/sh1106 oled-display is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or any later version.
*
* lcd library for ssd1306/sh1106 oled-display is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
*
* Diese Datei ist Teil von lcd library for ssd1306/sh1106 oled-display.
*
* lcd library for ssd1306/sh1106 oled-display ist Freie Software: Sie können es unter den Bedingungen
* der GNU General Public License, wie von der Free Software Foundation,
* Version 3 der Lizenz oder jeder späteren
* veröffentlichten Version, weiterverbreiten und/oder modifizieren.
*
* lcd library for ssd1306/sh1106 oled-display wird in der Hoffnung, dass es nützlich sein wird, aber
* OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite
* Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK.
* Siehe die GNU General Public License für weitere Details.
*
* Sie sollten eine Kopie der GNU General Public License zusammen mit diesem
* Programm erhalten haben. Wenn nicht, siehe <http://www.gnu.org/licenses/>.
*
*  lcd.h
*
*  Created by Michael Köhler on 22.12.16.
*  Copyright 2016 Skie-Systems. All rights reserved.
*
*  lib for OLED-Display with ssd1306/sh1106-Controller
*  first dev-version only for I2C-Connection
*  at ATMega328P like Arduino Uno
****************************************************
*  For other Atmegas/Attinys: GFX-Lib needs a
*  minimum of 1027 byte SRAM!
****************************************************
*
*/

#ifndef LCD_H
#define LCD_H

#ifndef YES
   #define YES                1
#endif
#ifndef NO
   #define NO                0
#endif

#if (__GNUC__ * 100 + __GNUC_MINOR__) < 303
#error "This library requires AVR-GCC 3.3 or later, update to newer AVR-GCC compiler !"
#endif

#include <inttypes.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/eeprom.h>

/* TODO: define displaycontroller */
#define SSD1306            // or SH1106, check datasheet of your display
//#define SH1106    // or SSD1306, check datasheet of your display

/* TODO: setup pins */
//#define SDA_Pin            PC4     // ATmega328p
//#define SDC_Pin            PC5     // ATmega328p

#define SDA_Pin                    PC1     // ATmega32
#define SDC_Pin                    PC0     // ATmega32
#define LCD_PORT                   PORTC   // LCD haengt an Port C
#define LCD_PORT_DDR                DDRC    // Daten-Richtungs-Register
/* TODO: setup i2c/twi */
#define LCD_I2C_ADDR    0x78                    // refer lcd-manual, 0x78 for 8-bit-adressmode,
                                //                     0x3c for 7-bit-adressmode
#define LCD_INIT_I2C    YES          // init I2C via lcd-lib
#define F_I2C            400000UL  // clock i2c
#define PSC_I2C            1        // prescaler i2c
#define SET_TWBR        (F_CPU/F_I2C-16UL)/(PSC_I2C*2UL)

#define LCD_DISP_OFF            0xAE
#define LCD_DISP_ON        0xAF

#define WHITE            0x01
#define BLACK            0x00

#define DISPLAY_WIDTH    128
#define DISPLAY_HEIGHT    64
#define DISPLAYSIZE        DISPLAY_WIDTH*DISPLAY_HEIGHT/8    // 8 pages/lines with 128
                                                        // 8-bit-column: 128*64 pixel
                                                        // 1024 bytes


void i2c_init(void);                    // init hw-i2c
void lcd_send_i2c_start(void);                // send i2c_start_condition
void lcd_send_i2c_stop(void);                // send i2c_stop_condition
void lcd_send_i2c_byte(uint8_t byte);                // send data_byte

void lcd_init(uint8_t dispAttr);            // init display,
// attributes:    LCD_DISPLAY_ON,
// LCD_DISPLAY_OFF
void lcd_home(void);                    // set cursor to (x,y) = (0,0)

void lcd_command(uint8_t cmd[], uint8_t size);        // transmit command to display
void lcd_data(uint8_t data[], uint16_t size);        // transmit data to display
void lcd_gotoxy(uint8_t x, uint8_t y);                // set curser at pos x, y.
                            // x means character, y means line
                            // (page, refer lcd manual)
void lcd_clrscr(void);                    // clear screen
void lcd_putc(char c);                    // print character on screen
void lcd_puts(const char* s);                // print string, \n-terminated, from ram on screen
void lcd_puts_p(const char* progmem_s);                // print string from flash on screen
void lcd_puts_e(const char* eemem_s);                   // print string form eeprom on screen
void lcd_invert(uint8_t invert);            // invert display
uint8_t lcd_isInverted(void);                // returns YES if display is inverted NO if display isn't inverted
void lcd_set_contrast(uint8_t contrast);            // set contrast for display

void lcd_drawPixel(uint8_t x, uint8_t y, uint8_t color);
void lcd_drawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t color);
void lcd_drawRect(uint8_t px1, uint8_t py1, uint8_t px2, uint8_t py2, uint8_t color);
void lcd_fillRect(uint8_t px1, uint8_t py1, uint8_t px2, uint8_t py2, uint8_t color);
void lcd_drawCircle(uint8_t center_x, uint8_t center_y, uint8_t radius, uint8_t color);
void lcd_fillCircle(uint8_t center_x, uint8_t center_y, uint8_t radius, uint8_t color);
void lcd_display(void);                        // copy buffer to display RAM
//void lcd_drawBitmap(uint8_t px, uint8_t py, const uint8_t bitmap[]);  // draw bitmap at pixel position x, y
#endif /*  LCD_H  */

Die Funktionen
void lcd_init(uint8_t dispAttr);
void lcd_set_contrast(uint8_t contrast);
void lcd_clrscr(void);
void lcd_draw_Line(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t color);
lcd_display();
sind nötig, um eine Linie auf dem OLED zu erzeugen.
Ich werde sie im Hauptprogramm einbauen.


Anbei zeige ich Euch den Code des Hauptprogramms (main.c), der eine Linie in der Mitte eines OLED Displays erzeugt.

Code:
/* 
* Name: main.c

* Author: norbert_w
* Darstellung einer Linie in der Mitte eines OLED-Graphik Display
* Auflösung 128 x 64 Pixel, I2C Pin Beschreibung lcd_gfx.h
* MCU ATmega32 F CPU 16MHz
*
*/
#include "main.h"
#include "lcd_gfx.h"

int main(void)
{
 // LCD Initialisieren
 lcd_init(LCD_DISP_ON);
 // LCD Contrast einstellen
 lcd_set_contrast(0x0f);
 // PB5 als Ausgang schalten (Beim Arduino Uno haengt hier eine LED dran: PIN 13)
 DDRB |= (1 << PB5);
 // Display löschen
 lcd_clrscr();             
 //Schleife des Hauptprogramms  
 for(;;){
 // LED toggeln, d.h. je Programmdurchlauf abwechselnd ein- bzw. ausschalten
 PORTB ^= (1 << PB5);
 // zeichnen einer Linie (in den Graphik-Buffer)
 lcd_drawLine(0, 31, 127, 31, WHITE);   
 // Graphik-Buffer anzeigen 
 lcd_display();       
 }  
}

Nach dem Kompilieren erscheint folgende Meldung auf dem Bildschirm:


.png   Bildschirmfoto 2017-06-16 um 16.30.43.png (Größe: 43,82 KB / Downloads: 247)

Das Mini-Programm belegt ganze 2892 Byte im Programmspeicher - erstaunlich wenig. 


.jpg   Linie.JPG (Größe: 75,16 KB / Downloads: 247)

Fortsetzung folgt!
Grüße aus Wassenberg,
Norbert.
Zitieren
#2
Hallo Radiofreunde,

nachdem also der erste Schritt getan ist, folgt der nächste.

Zuerst noch ein wenig Theorie…..
Das Display kann 128 Pixel in X-Richtung und 64 Pixel in Y-Richtung darstellen.

Es soll ein Balken in der Mitte des Displays dargestellt werden, der 100 Pixel lang (X) und 20 Pixel breit (Y) ist.
Die X-Koordinaten beginnen bei X=0 und enden bei X=99.
Die Y-Koordinaten (Mitte des Displays!) beginnen bei Y=22 und enden bei Y=42.
Wenn dieser Balken mit der Linien-Zeichenfunktion void lcd_draw_Line(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t color);
aus dem ersten main.c Programmteil erzeugt werden soll, dann sähe es z.B. so aus:

lcd_draw_Line(0, 22, 0, 42, WHITE);          // für die erste Linie des Balkens
lcd_draw_Line(1, 22, 1, 42, WHITE);          // für die zweite Linie des Balkens
lcd_draw_Line(2, 22, 2, 42, WHITE);          // für die dritte Linie des Balkens
usw. usw. bis
lcd_draw_Line(99, 22, 99, 42, WHITE);      // für die neunundneunzigste Linie des Balkens

Das dieser Weg recht mühsam und unschön ist, sollte eigentlich Jedem einleuchten.
Eine mögliche Lösung besteht darin, eine Variable (X_Pos) mit einem Anfangswert 0 zu bestimmen.
Sie wird bei jedem Programmzyklus um 1 erhöht, solange sie kleiner als der vorgegebene Endwert (100) ist. 
Ist diese Bedingung erfüllt, dann wird die lcd_draw_Line Funktion mit dem gültigen X_Pos Wert aufgerufen.

Anbei die geänderte main.c:


Code:
/*
* Name: main.c
*
* Author: norbert_w
* Darstellung eines Balkens in der Mitte eines OLED-Graphik Display
* Der Balken soll bei X=0 anfangen und eine Breite von 20 Pixel haben.
* Auflösung 128 x 64 Pixel, I2C Pin Beschreibung lcd_gfx.h
* MCU ATmega32 F CPU 16MHz
*
*/
#include "main.h"
#include "lcd_gfx.h"

int main(void)
{
uint8_t X_Pos = 0;          // Variable zum Speichern der X-Position
uint8_t X_Pos_End = 100;    // Variable des Maximalwertes von X
// LCD Initialisieren
lcd_init(LCD_DISP_ON);
// LCD Contrast einstellen
lcd_set_contrast(0x0f);
// PB5 als Ausgang schalten (Beim Arduino Uno haengt hier eine LED dran: PIN 13)
DDRB |= (1 << PB5);
// Display löschen
lcd_clrscr();            
//Schleife des Hauptprogramms  
for(;;){
// LED toggeln, d.h. je Programmdurchlauf abwechselnd ein- bzw. ausschalten
PORTB ^= (1 << PB5);
if (X_Pos < X_Pos_End)
{
  // zeichnen einer Linie (in den Graphik-Buffer)
  lcd_drawLine(X_Pos, 22, X_Pos, 42, WHITE);  
  X_Pos++;     //X_Pos = X_Pos +1
}
// Graphik-Buffer anzeigen
lcd_display();      
}  
}
Das Ergebnis sieht dann so aus:


.jpg   Balken_1.JPG (Größe: 93,13 KB / Downloads: 131)

Ein Blick auf die Speicherbelegung zeigt:


.png   Bildschirmfoto 2017-06-16 um 20.54.01.png (Größe: 48,43 KB / Downloads: 131)

Es gibt noch viel Platz fürs Programm.

Fortsetzung folgt!
Grüße aus Wassenberg,
Norbert.
Zitieren
#3
Hallo Norbert,

das ist toll was Du da aufbaust! Ich werde es mit Spannung verfolgen. Wenn Du wirklich einen magischen Fächer nachbauen willst, kommt jetzt erst die richtige Arbeit auf Dich zu. Ich musste an der Stelle ein paar Vorlesungsunterlagen über Computergrafik durchlesen. Koordinatentransformationen und Vektorgrafiken und so weiter. Der erste Umsetzungsweg über eine Spritedarstellung des Fächers brachte mich arg an die Speicher- und Taktgrenzen des Mega328-Controllers, die Röhre sollte ja in "Echtzeit" und flüssig arbeiten. Eine Darstellung eines runden mag. Auge oder eines Leuchtbandes ist dagegen um ein vielfaches einfacher und ressourcenschonender.
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#4
Es folgt der nächste Schritt,

aber nicht ohne mich beim Bernhard zu bedanken.
Bernhard, Dein Lob bedeutet mir viel - ich versuche so einfach wie möglich vorzugehen.

Es wird stets die gleiche Hardware verwendet, es wird Nichts an der Verdrahtung geändert!
Der Schwerpunkt liegt in der Programmierung des Mikrocontrollers und das wird auch so bleiben.
Mir liegt es daran den Mitlesen eine systematische Vorgehensweise zu zeigen, die verständlich
und nachvollziehbar ist.
Vielleicht erleichtert dieser Thread auch dem Einen oder Anderen den Einstieg in die Mikrocontroller Welt??

Ob am Ende ein mag. Fächer herauskommt? Ganz ehrlich, dass weiss ich heute noch nicht.

Zurück zum eigentlichen Thema, wir sind jetzt in der Lage einen Balken grafisch auf dem Display darzustellen.
Was hindert uns daran 2 Balken, die aufeinander zulaufen, zu programmieren?
Balken 1 beginnt bei X=4, Balken 2 beginnt bei X=123, Balkenbreite 10 Pixel, Balkenlänge 55 Pixel.
Am Anfang der Balken wird ein Block von X=4 Pixeln und Y=10 Pixel gezeichnet.
Damit käme das Aussehen der Gesamtgrafik einer EM84 schon sehr nahe….

Ausserdem wird ein neues Programmelement verwendet, die for Schleife. 


Code:
/*
* Name: main.c
*
* Author: norbert_w
* Darstellung zweier Balken in der Mitte eines OLED-Graphik Display, die aufeinander zulaufen.
* Balken 1 soll bei X=4, Balken 2 bei X=123 anfangen. Die Breite beider Balken beträgt 10 Pixel.
* Die max. Länge der Balken beträgt 55 Pixel.
* Am Anfang beider Balken soll ein Block von 4 Pixel in X-Richtung und 10 Pixel in Y-Richtung gezeichnet werden.  
* Auflösung 128 x 64 Pixel, I2C Pin Beschreibung lcd_gfx.h
* MCU ATmega32 F CPU 16MHz
*
*/
#include "main.h"
#include "lcd_gfx.h"

int main(void)
{
uint8_t X_Pos = 4;          // Variable zum Speichern der X-Position
uint8_t X_Pos_End = 55;     // Variable des Maximalwertes von X (entspricht der Balkenlänge)
uint8_t block =0;           // zur Erzeugung eines Blocks am Anfang bzw. Ende der Balken
// LCD Initialisieren
lcd_init(LCD_DISP_ON);
// LCD Contrast einstellen
lcd_set_contrast(0x0f);
// PB5 als Ausgang schalten (Beim Arduino Uno haengt hier eine LED dran: PIN 13)
DDRB |= (1 << PB5);
// Display löschen
lcd_clrscr();            
for (block=0; block < X_Pos; block++)
{
lcd_drawLine(block, 27, block, 37, WHITE);
lcd_drawLine(127-block, 27, 127-block, 37, WHITE);
}

//Schleife des Hauptprogramms  
for(;;){
// LED toggeln, d.h. je Programmdurchlauf abwechselnd ein- bzw. ausschalten
PORTB ^= (1 << PB5);
if (X_Pos < X_Pos_End)
{
 // zeichnen einer Linie (in den Graphik-Buffer)
 lcd_drawLine(X_Pos, 27, X_Pos, 37, WHITE);
 lcd_drawLine(127-X_Pos, 27, 127-X_Pos, 37, WHITE);  
 X_Pos++;     //X_Pos = X_Pos +1
}
// Graphik-Buffer anzeigen
lcd_display();      
}  
}




.jpg   Doppelbalken.JPG (Größe: 70,77 KB / Downloads: 83)

Fortsetzung folgt!
Grüße aus Wassenberg,
Norbert.
Zitieren
#5
Hallo Mitleser,
die Pause ist vorbei - Zeit für den nächsten Schritt!

Bisher wurde ein Balken oder später ein Doppelbalken gezeichnet.
Was die Grafiken gemeinsam hatten war die Farbe…
Oder anders herum - die Balken wurden gezeichnet und das war es….

Hmmm - da fehlt doch etwas oder?
Wie kann denn ein gezeichneter Balken oder Teile von ihm wieder gelöscht werden?

Im Prinzip ganz einfach. Bisher wurde immer in der Farbe WHITE gezeichnet.
Probieren wir doch einmal die Farbe BLACK aus!

Anbei das ergänzte Hauptprogramm:

Code:
/*
* Name: main.c
*
* Author: norbert_w
* Darstellung zweier Balken in der Mitte eines OLED-Graphik Display, die aufeinander zulaufen.
* Balken 1 soll bei X=4, Balken 2 bei X=123 anfangen. Die Breite beider Balken beträgt 10 Pixel.
* Die max. Länge der Balken beträgt 55 Pixel.
* Am Anfang beider Balken soll ein Block von 4 Pixel in X-Richtung und 10 Pixel in Y-Richtung gezeichnet werden.  
* Auflösung 128 x 64 Pixel, I2C Pin Beschreibung lcd_gfx.h
* MCU ATmega32 F CPU 16MHz
*
*/
#include "main.h"
#include "lcd_gfx.h"

int main(void)
{
uint8_t X_Pos = 4;          // Variable zum Speichern der X-Position
uint8_t X_Pos_End = 55;     // Variable des Maximalwertes von X (entspricht der Balkenlänge)
uint8_t block =0;           // zur Erzeugung eines Blocks am Anfang bzw. Ende der Balken
uint8_t farbe =WHITE;

// LCD Initialisieren
lcd_init(LCD_DISP_ON);
// LCD Contrast einstellen
lcd_set_contrast(0x0f);
// PB5 als Ausgang schalten (Beim Arduino Uno haengt hier eine LED dran: PIN 13)
DDRB |= (1 << PB5);
// Display löschen
lcd_clrscr();            
for (block=0; block < X_Pos; block++)
{
lcd_drawLine(block, 27, block, 37, WHITE);
lcd_drawLine(127-block, 27, 127-block, 37, WHITE);
}

//Schleife des Hauptprogramms  
for(;;){
// LED toggeln, d.h. je Programmdurchlauf abwechselnd ein- bzw. ausschalten
PORTB ^= (1 << PB5);
if (farbe == WHITE) {
 if (X_Pos < X_Pos_End)
 {  
   X_Pos++;     //X_Pos = X_Pos +1
 } else {
   farbe = BLACK;
   X_Pos++;
   }
}
if (farbe == BLACK) {
 if (X_Pos > 4)
 {  
   X_Pos--;     //X_Pos = X_Pos -1
 } else farbe = WHITE;
}

// zeichnen bzw. löschen einer Linie (in den Graphik-Buffer)
 lcd_drawLine(X_Pos, 27, X_Pos, 37, farbe);
 lcd_drawLine(127-X_Pos, 27, 127-X_Pos, 37, farbe);  
// Graphik-Buffer anzeigen
lcd_display();      
}  
}

Fortsetzung folgt!
Grüße aus Wassenberg,
Norbert.
Zitieren


Gehe zu: