Auslesen eines EPROMs bzw. eines EEPROMS ist eine zentrale Aufgabe, wenn man mit Mikroprozessoren wie dem 6502 oder dem Z80 experimentiert. Hierzu ist ein entsprechendes Programmiergerät natürlich sehr hilfreich, wenn man den Baustein nicht gänzlich von „Hand“ mit Daten füllen möchte. Im vorliegenden Beispiel gehen wir davon aus, dass ein programmiertes EEPROM vom Typ AT28C256 vorliegt, dessen Daten mit dem Arduino Mega ausgelesen werden sollen. Der Versuchsaufbau sieht wie folgt aus:
Wie man erkennt ist der Anschluss relativ einfach: Daten-Pins kommen an den Port L des Arduino Mega und die Adress-Pins ebenso an die oben im Beitrag schon beschriebenen Ports A und Port C. Damit wird die Programmierung auf die Port-Manipulations-Funktionen des Arduino ermöglicht, die das Ein- und Ausgeben der Daten sehr komfortabel umsetzen lassen.
Lesevorgang
Auf den AT28C256 Baustein kann wie auf ein statisches RAM zugegriffen werden. Daten können ausgelesen werden, wenn die Anschlüsse CE‘ und OE‘ auf LOW liegen und WE‘ auf HIGH gesetzt ist. Dann werden Daten an die durch die Adresspins bestimmten Speicherstelle an den Ausgängen IO0-IO7 ausgegeben.
Aus diesem Grund werden in unserem Versuchsaufbau – wie im obigen Bild zu sehen ist – die Anschlüsse OE‘ und CE‘ auf Masse gelegt werden, d.h. haben einen High Pegel. Ebenso muss der WE#-Pin auf +5V gelegt werden.
Damit ist der Baustein bereit. Legt man nun eine Adresse an die Pins A0-A14 so kann man die im EEPROM gespeicherten Daten auslesen.
Dazu wird im Arduino Programm eine 16bit Variable mit dem Namen address initialisiert. Der Wert dieser Variable muss in einen Low-Byte und einen High-Byte Bereich aufgeteilt werden. Das geht beispielsweise so:
// wandle address in 16 bit word
byte LoByte = (address & 0x00FF);
byte HiByte = ((address & 0xFF00) >>8);
Man erhält das Low-Byte in dem man den 16bit Wert mit 0x00FF UND verknüpft. Dadurch werden die oberen 8 Bit quasi ausgeblendet. Analog verfährt man mit dem High-Byte. Nur dass man hier mit 0xFF00 UND verknüpft, wodurch die unteren 8 Bit der Variable address ausgeblendet werden. Diese werden dann noch um 8 Stellen nach rechts verschoben, damit man einen 8-Bit Wert erhält.
Die so erzeugten 8-Bit Werte kann man nun den Ports A und C zuweisen, somit steht dann eine 16 Bit Wort zur Steuerung bereit. Da man nur 15-Bit für den Baustein benötigt, bleibt der Anschluss A15 einfach offen.
Wichtig ist sich dabei die Grundlagen der Port-Programmierung nochmals zu vergegenwärtigen. Zusammengefasst funktioniert das wie folgt: Die Ports werden von drei Registern gesteuert, die mit „DDRx“, „PORTx“ und „PINx“ bezeichnet werden. Diese drei Register existieren für jeden Port, es gibt also zum Beispiel für Port B ein DDRB-, ein PORTB- und ein PINB-Register. Das jeweilige DDRx-Register steuert, ob die Pins als Input oder Output konfiguriert sein sollen (DDR = Data Direction Register), das PORT-Register ist ein Ausgang, es legt fest, ob die Pins HIGH oder LOW sind und das PIN-Register gibt den Zustand der Pins an, die im DDR-Register auf Input gesetzt wurden.
Das Programm kann dann so aussehen:
/*EEPROM Ansteuerung
LESE Daten aus EEPROM
Alternativ als hexdump oder Step by step
*/
#define CLOCK 2
unsigned int data = 0;
uint16_t address = 0x0000;
int staADR = 0x00;
int endADR = 0xff;
void setup() {
Serial.begin(115200);
pinMode(CLOCK, INPUT);
DDRA = 0b11111111; // Address = Output
DDRC = 0b11111111; // Address = Output
DDRL = 0x00; // Data = Input
// hexDump(staADR, endADR);
attachInterrupt(digitalPinToInterrupt(CLOCK), onClock, RISING);
}
void loop() {
// put your main code here, to run repeatedly:
}
void onClock() {
char output[15];
// wandle address in 16 bit word
byte LoByte = (address & 0x00FF);
byte HiByte = ((address & 0xFF00) >>8);
// Schreibe Adresse an PortPins
PORTA = LoByte;
PORTC = HiByte;
// lese Daten von portPins
data = PINL;
Serial.println("-------------------------------------------------");
sprintf(output, " %04x %02x", address, data);
Serial.println(output);
//Serial.println("-----------------------------------------------");
// next address
address = address+1;
}// end onClock
void hexDump(int startAdr, int endAdr) {
Serial.println();
Serial.println("------------------------------------------------------------------------");
Serial.println(" * HEX-Dump * " );
Serial.println("------------------------------------------------------------------------");
for (unsigned int adr = startAdr; adr <= endAdr; adr++) {
int a = adr;
// wandle addresse adr in 16 bit word
byte LoByte = (adr & 0x00FF);
byte HiByte = ((adr & 0xFF00) >>8);
// Schreibe Adresse an PortPins
PORTA = LoByte;
PORTC = HiByte;
// lese Daten von portPins
data = PINL;
if ( (adr == 0) || (adr % 16 == 0))
Serial.print ( " " + int2hex(adr, 4) + " : "); // Zeige Speicheradresse an
Serial.print (" " + int2hex(data, 2) + " "); // Zeige Daten-Byte an
if( (adr + 1) % 16 == 0)
Serial.println(); // Zeilenvorschub
}// end for
}//hexDump
String int2hex(int wert, int stellen) { // int -› hex-String Konvertierung mit der Angabe der Stellen
String temp = String(wert, HEX);
String prae = "";
int len = temp.length(); // Die Länge der Zeichenkette ermitteln
int diff = stellen - len;
for(int i = 0; i < diff; i++)
prae = prae + "O"; // Führende Nullen erzeugen
return prae + temp; // Führende Nullen + Ergebnis zurückliefern
} // end int2he
Das Programm kann entweder über einen Taster gesteuert werden…in diesem Fall wird die Interrupt-Routine verwendet und der Eingangs-Impuls an Pin2 des Arduino Mega geleitet. So kann man im Einzelschritt sehen, welche Adresse an den EEPROM angelegt wird und welche Daten ausgelesen werden.
Alternativ habe ich die Möglichkeit vorgesehen, einen ganzen Speicherbereich über die entwickelte Hexdump-Funktion auszugeben. Hierzu ist der Interrupt zu deaktivieren und die Start- und Endadresse anzugeben.
Der You-Tube Kanal von Ben Eater befasst sich mit Grundlagen der Digital- und Computertechnik. Insbesondere der Beitrag „Hello, world” from scratch on a 6502 hat mich inspiriert, in dieser Richtung meine eigenen Experimente zu machen.
Im folgenden werden einige Teil-Projekte vorgestellt, die rund um das Thema entwickelt wurden. Es handelt sich somit um eine Sammlung von Einzelteilen und nicht um ein in sich geschlossenes Projekt bzw. einer solchen Beschreibung.
Inhalt
Funktionsweise des 6502 Prozessors
Clock Baustein
EEPROM Programmierung
Arduino als Adress- und Daten-Monitor
6502 Assembler
Ein und Ausgabe von Daten auf den Daten- und Adressbus
Der 6502 hat Computer Geschichte geschrieben. In den Anfängen waren viele der ersten Computer ( Apple II, Commodore PET, KIM, etc)mit diesem Prozessor ausgestattet. Er ist heute noch erhältlich allerdings in einer leicht modifizierten Form. Das Western Design Center hat den Prozessor mit der Bezeichnung W65C02S im Programm. Auf der Webseite sind alle Details sowie das Datenblatt verfügbar.
Eine der wichtigsten Änderungen ist die Möglichkeit den Prozessor nun mit unterschiedlichen Taktraten weit unter 1 MHz zu betreiben. Was ihn für Maker-Anwendungen und natürlich auch für Ausbildungszwecke sehr geeignet macht.
Der Prozessor verfügt über einen aus heutiger Perspektive sehr einfachen Aufbau.
Quelle: www.westerndesigncenter.com
Er besteht aus 6 Register und verfügt über einen überschaubaren Befehlssatz von 69 Instruktionen. Es gibt heute noch eine Vielzahl von Dokumenten wie Beispielsweise das geniale Monitor und Assemblerprogram von Steve Wozniak, das er für den Apple 1 entwickelt hat. Ebenso sind noch Implementierungen der damaligen BASIC Versionen erhältlich, so dass man sich leicht eine Replik aéines der klassischen Computer Modellen nachbauen kann.
Funktionsweise
Die Funktionsweise des Prozessors kann wie folgt beschrieben werden: Nach einem Reset-Signal wenn eine positive Flanke am Pin 40 erkannt wird, gibt es eine Reset-Sequenz, die sieben Taktzyklen dauert. Der Programmzähler wird mit dem Rücksetz-Vektor von den Plätzen FFFC (niedriges Byte) und FFFD (hohes Byte) geladen. Im nächsten Takt, wird dann an diese Adresse gesprungen. Dies ist die Startposition für die Programmsteuerung. Der Programmzähler wird mit dieser Adresse geladen und beginnt dann mit der Programmausführung.
Ich habe dies in meinem Versuchsaufbau nachgebildet. Er besteht aus einem Arduino Mega und einer Lochrasterplatine auf der neben dem 6502 Prozessor nur ein manueller Taktgeber (Monoflop), der Rest-Taster und ein Steckverbinder mit dem Arduino-Port verbaut ist.
Der Datenbus ist mit dem Wert 0xEA = Op-Code für No Operation durch Widerstände vorgelegt, damit etwas sinnvolles anliegt.
Wie man auf der nebenstehenden Abbildung erkennt, findet man in den ersten 6 Takten nach dem Reset keine brauchbaren Daten. Ab dem 7. Takt wird aber zur Adresse 0xFFFc bzw. 0xFFFd gesprungen und die Einsprungadresse für den Programmstart bestimmt. In meinem Beispiel habe ich die Werte 0x00 und 0x80 eingegeben, und somit die Adresse 0x8000 definiert. An dieser Adresse beginnt dann die Programmausführung.
In der Praxis würde man hier an einen Bereich des adressierbaren Speichers verweisen, in dem der abzuarbeitende Programmcode abgelegt ist.
Clock-Baustein
Normalerweise arbeitet der Mikroprozessor mit Taktfrequenzen im MHz Bereich. Um aber etwas genauer verfolgen zu können, wie der Prozessor arbeitet, ist es sinnvoll eine geringere Taktzeit vorzusehen. Dazu werden auf Basis des NE555 Timer IC ein Stabiler und ein Monostabiler Multivibrator gebaut. Mit dem Monoflop Modul lassen sich dann einzelne Schritte ausführen, während der astabile Bruder für eine kontinuierliche Impulsfolge zwischen 1 und 5 Hz (je nach Auslegung des RC Glieds) sorgt.
Die Schaltung ist in folgender Graphik dargestellt:
EEPROM Programmierung
Wenn man mit einem Microcomputer aus der Frühzeit der Computertechnik arbeitet, bietet es sich an, anstelle der EPROMS heute verfügbare EEPROMS zu verwenden. Diese lassen sich bequem mit Hilfe eines entsprechenden Programmiergerätes flexibel Programmieren.
Ich verwende dazu ein USB Programmiergerät der Firma BATRONIX. Meine Version ist schon 20 Jahre alt, verrichtet seinen Dienst aber nach wie vor tadellos und ist auch mit der neusten Version der Programmierumgebung „ProExpress“ einsetzbar. Der USB Chip Programmer (und seine erhältlichen Nachfolger) ist ein besonders flexibler und einfach zu handhabender Eprom Programmer mit umfangreicher Unterstützung für Eproms, EEproms, Flash und weitere Speicherchips. Die besondere Flexibilität wird durch eine komplette Versorgung über den USB Port erreicht. Ein Netzteil oder Batterien werden nicht benötigt, alle Programmierspannungen zwischen 3 und 25 Volt werden intern aus der USB Spannung über Ladungspumpen generiert.
Generierung der EEPROM Programmier-Informationen
Natürlich kann man versuchen die EEPROMS auch von Hand zu programmieren, aber weit sinnvoller ist es, dies natürlich mit Hilfe eines Programms zu machen, das die entsprechenden Daten in eine Datei schreibt, die dann von der Programmiersoftware eingelesen und auf den Chip geschrieben wird.
Als erstes soll ein Python Programm vorgestellt werden, die diese Aufgabe übernimmt. Der Aufbau ist ziemlich übersichtlich. Zuerst wird ein Array definiert, das der Größe des EEPROM entspricht und dann mit Daten gefüllt. Einzelne Stellen können dann noch manuell eingetragen werden. Anschliessend wird das Ganze dann als Datei mit dem Namen „rom.bin“ ausgegeben.
rom = bytearray ([0xEA]*32768)
rom[0x7FFC] = 0x00
rom[0x7FFC] = 0x80
with open ("rom.bin","wb") as out_file:out_file.write(rom)
Natürlich kann auch jede andere Programmiersprache verwendet werden. Ich verwende üblicherweise mein Mathematica bzw. die Wolfram Language für derartige Programmieraufgaben. In Mathematica würde das Problem wie folgt lösen:
Nach dem das File dann über die Programmer-Software auf den EEPROM übertragen wurde, sieht das im Tool dann etwa so aus:
Arduino als Adress- und Daten-Monitor
Der Arduino Mega mit seinen 54 Pins biete sich an, um damit ein Monitor-Programm für den Adress- und Datenbus und andere relevante Datenleitungen des Mikroprozessors zu verwenden. Das folgende Programm bildet die Basis. Mit Hilfe einer Interrupt Routine werden bei jedem Takt-Impuls die 16-Adress- und 8-Daten-Bits ausgelesen und dargestellt.
// *******************************************
// Monitor Programm um Daten und Adress Bits
// mit Arduino Mega auszulesen
// nach Ben Eater modifiziert von
// ProfHof Sept 2022
// Version 1.0
// *******************************************
const char ADDR[] = {22, 24, 26, 28, 30, 32, 34, 36, 40, 42, 44, 46, 47, 49, 51, 53};
const char DATA[] = {14, 15, 16, 17, 18, 19, 20, 21};
#define CLOCK 2
#define READ_WRITE 3
void setup() {
for (int n = 0; n < 16; n += 1) {
pinMode(ADDR[n], INPUT);
}
for (int n = 0; n < 8; n += 1) {
pinMode(DATA[n], INPUT);
}
pinMode(CLOCK, INPUT);
pinMode(READ_WRITE, INPUT);
// sobald clock impulse am Eingang 2 dann wird
// Interrupt routinbe "Clock" aufgerufen
attachInterrupt(digitalPinToInterrupt(CLOCK), onClock, RISING);
Serial.begin(57600);
}
void onClock() {
char output[15];
unsigned int address = 0;
for (int n = 0; n < 16; n += 1) {
int bit = digitalRead(ADDR[n]) ? 1 : 0;
Serial.print(bit);
// Umwandlung in ein Integerwert
address = (address << 1) + bit;
}
Serial.print(" ");
unsigned int data = 0;
for (int n = 0; n < 8; n += 1) {
int bit = digitalRead(DATA[n]) ? 1 : 0;
Serial.print(bit);
// Umwandlung in ein Integerwert
data = (data << 1) + bit;
}
// Ausgabe hat das Format:
// 16bit Adresse __ 8Bit Data ___ HexAdr __ R/W __ HexData
sprintf(output, " %04x %c %02x", address, digitalRead(READ_WRITE) ? 'r' : 'W', data);
Serial.println(output);
}
void loop() {
}
Die Ausgabe sieht dann beispielsweise wie folgt aus:
6502 Assembler
Natürlich kann man kleinere Programme „von Hand“ wie oben gezeigt schreiben. Aber ein Assembler erleichtert die Arbeit schon deutlich. Im Beitrag von Ben Eater wird der Assembler „VASM“ von Volker Bartelmann und Frank Wille eingesetzt. http://sun.hasenbraten.de/vasm/
Das Besondere an diesem Assembler ist, dass man ihn für nicht kommerzielle Zwecke frei nutzen darf und es bereits lauffähige Binaries für den Mac gibt. Natürlich stehen auch Versionen für Windows und Linux Betriebssysteme zur Verfügung.
Die Benutzung erfolgt rein über die Kommandozeile und bedarf daher etwas Übung. Es gibt verschiedene Möglichkeiten den Assembler zu konfigurieren. Im folgenden wird beschrieben, wie man den „VASM“ nutzt, um aus einer Datei in der ein 6502-Assembler Programm steht, den Maschinencode für den Eprom-Programmer zu erzeugen.
Beispiel für blinkende LEDs als 6502 Assembler Programm :
.org $8000
reset:
lda #$ff
sta $6002
lda #$50
sta $6000
loop:
ror
sta $6000
jmp loop
.org $fffc
.word reset
.word $0000
Diese Datei (mit dem Namen blink.s) wird nun dem Assembler übergeben, und diesem mitgeteilt, dass man Binärcode (option -Fbin) erwartet und mnemonic Anweisungen (wie .org, etc.) berücksichtigt haben will (-dotdir). Der Assembler generiert daraus dann das File a.out, das man mit den Befehlen „cut“ und „hexdump“ anschauen kann. In der Kommandozeile sieht das dann etwa so aus:
Der Inhalt dieser Datei lässt sich dann – wie oben beschrieben – in einen EEPROM programmieren und zur Steuerung des Mikroprozessors verwenden. Schematisch lässt sich das wie im nachfolgenden Schaubild darstellen:
Ein und Ausgabe von Daten auf den Daten- und Adressbus
Der Arduino Mega bietet sich mit seinen vielen Ein- und Ausgängen an, nicht nur Daten zu lesen sondern diese auch zu schrieben. Am einfachsten geht das über die Port-Adressen. Diese haben nur das Problem, dass sie nicht in einer adäquaten Reihenfolge [D0…D7] bzw. [A0…A15] an den Steckerleisten angreifbar sind.
Ein Blick auf die interne Pin-Belegung zeigt, dass sich für diese Zwecke die Ports
PORTL = Data (Pin 42…49) PORTA = Address LSB (Pin29 … Pin 22) PORTC = Address MSB (Pin30 … Pin37)
idealerweise einsetzen lassen.
Anbei ein kurzes Test Programm, das eine Möglichkeit vorstellt, wie die Datenverarbeitung aussehen könnte:
// test Programm Daten Handling Arduino MEGA
// PORTL = Data
// PORTA = Address LSB
// PORTC = Address MSB
/*
* Schreibt bzw liest Daten über den Port L
* und gibt diese zur Kontrolle am Monitor aus
* Ziel: Daten Lesen und Schreiben auf den Daten
* und Adressbus des 6502 zu simulieren
*/
unsigned int data = 255;
uint16_t address = 0x0000;
#define CLOCK 2
void setup() {
Serial.begin(115200);
// Interrupt routine "Clock" aufgerufen
attachInterrupt(digitalPinToInterrupt(CLOCK), onClock, RISING);
}
void onClock() {
char output[15];
//writeData(data);
//data = readData();
writeAdr(address);
/*
Serial.println("--------------------------------------");
sprintf(output, " %04x %02x", address, data);
Serial.println(output);
Serial.println("--------------------------------------");
//data = data -1;
*/
address = address+1;
}
void loop() {
// put your main code here, to run repeatedly:
}
void writeData(int d) { // write to Pins
DDRL = 0xFF;
PORTL = data;
// Serial.print("Data to Port L ----------->"); Serial.println(data);
}// end wD
byte readData() { // read from Pins
int d =0;
DDRL = 0x00;
d = PINL;
Serial.print("Data from Port L ----------->");
Serial.print(d,HEX); Serial.print(" dec: ");Serial.print(d,DEC);
return d;
} //end rD
void writeAdr(uint16_t adr) { // write adr to Pins
DDRA = 0xFF; // Address = OUTPUT
DDRC = 0xFF; // Address = OUTPUT
uint16_t Adresse = adr;
byte LoByte = (Adresse & 0x00FF);
byte HiByte = ((Adresse & 0xFF00) >>8);
PINA = LoByte; // Schreibe Adresse an PortPins
PINC = HiByte;
Serial.print( "High Adresse: " ); Serial.println( Adresse, HEX );
Serial.print( "Hi Byte: " ); Serial.print ( HiByte, HEX ); Serial.print(" ... B : " ); Serial.println( HiByte, BIN );
Serial.print( "Lo Byte: " ); Serial.print ( LoByte, HEX ); Serial.print(" ... B : " ); Serial.println( LoByte, BIN );
Serial.println();
}// end wD
Hexdump -Routine
Bei der Arbeit mit Mikroprozessoren ist es immer wieder hilfreich bestimmte Speicherbereiche übersichtlich auszugeben. Dazu verwendet man sogenannte Hexdumps. Im folgenden ein Beispiel wie sowas implementiert werden kann:
// HEXDUMP for memory
unsigned char memory[0x0fff]; // 4K of memory mapped to 0xF000..0xFFFF
void setup() {
Serial.begin(115200);
for (int n=0; n < 0x0fff; n++) {
memory[n] = 0xEA; // NOP
}
// .org 0xF000
// reset:
memory[0x0000] = 0xA9; // LDA
memory[0x0001] = 0x00; // #$00
// loop:
memory[0x0002] = 0x1A; // INC
// storing and loading A to valid memory allows us to see it on the data bus in diagnostics
memory[0x0003] = 0x8D; //STA
memory[0x0004] = 0x00;
memory[0x0005] = 0xF1;
memory[0x0006] = 0xAE; //LDX
memory[0x0007] = 0x00;
memory[0x0008] = 0xF1;
memory[0x0009] = 0x4C; // JMP loop
memory[0x000A] = 0x02;
memory[0x000B] = 0xF0;
// .org 0xFFFC
memory[0x0FFC] = 0x00; // reset
memory[0x0FFD] = 0xF0;
DDRL = 0x00; // Data = INPUT
DDRA = 0x00; // Address = INPUT
DDRC = 0x00; // Address = INPUT
Serial.println();
Serial.println("----------------------------------------------------------");
Serial.println(" * HEX-Dump * " );
Serial.println("----------------------------------------------------------");
hexDump(0,0x100);
} // end setup
void hexDump(int startAdr, int endAdr) {
for (unsigned int adr = startAdr; adr <= endAdr; adr++) {
int a = memory[adr]; // lese adressSpeicher
if ( (adr == 0) || (adr % 16 == 0))
Serial.print ( " " + int2hex(adr, 4) + " : "); // Zeige Speicheradresse an
Serial.print (" " + int2hex(a, 2) + " "); // Zeige Daten-Byte an
if( (adr + 1) % 16 == 0)
Serial.println(); // Zeilenvorschub
}// end for
}//hexDump
String int2hex(int wert, int stellen) { // int -› hex-String Konvertierung mit der Angabe der Stellen
String temp = String(wert, HEX);
String prae = "";
int len = temp.length(); // Die Länge der Zeichenkette ermitteln
int diff = stellen - len;
for(int i = 0; i < diff; i++)
prae = prae + "O"; // Führende Nullen erzeugen
return prae + temp; // Führende Nullen + Ergebnis zurückliefern
} // end int2he
void loop() {
}
Die Routinen ergeben dann folgendes Ergebnis auf dem Monitor des Arduino:
EEPROM AT28C256 auslesen
Das Auslesen eines EPROMs bzw. eines EEPROMS ist eine zentrale Aufgabe, wenn man mit Mikroprozessoren wie dem 6502 oder dem Z80 experimentiert. Hierzu ist ein entsprechendes Programmiergerät natürlich sehr hilfreich, wenn man den Baustein nicht gänzlich von „Hand“ mit Daten füllen möchte.
Im vorliegenden Beispiel gehen wir davon aus, dass ein programmiertes EEPROM vom Typ AT28C256 vorliegt, dessen Daten mit dem Arduino Mega ausgelesen werden sollen. Der Versuchsaufbau sieht wie folgt aus:
Wie man erkennt ist der Anschluss relativ einfach: Daten-Pins kommen an den Port L des Arduino Mega und die Adress-Pins ebenso an die oben im Beitrag schon beschriebenen Ports A und Port C. Damit wird die Programmierung auf die Port-Manipulations-Funktionen des Arduino beschränkt, die das Ein- und Ausgeben der Daten sehr komfortabel umsetzen lassen.
Wichtig ist noch dass die Anschlüsse OE und CE auf Masse gelegt werden, d.h. aktiv Low sind. Ebenso muss der WE-Pin auf +5V gelegt werden. Das Programm kann dann so aussehen:
/*EEPROM Ansteuerung
LESE Daten aus EEPROM
Alternativ als hexdump oder Step by step
*/
#define CLOCK 2
unsigned int data = 0;
uint16_t address = 0x0000;
int staADR = 0x00;
int endADR = 0xff;
void setup() {
Serial.begin(115200);
pinMode(CLOCK, INPUT);
DDRA = 0b11111111; // Address = Output
DDRC = 0b11111111; // Address = Output
DDRL = 0x00; // Data = Input
// hexDump(staADR, endADR);
attachInterrupt(digitalPinToInterrupt(CLOCK), onClock, RISING);
}
void loop() {
// put your main code here, to run repeatedly:
}
void onClock() {
char output[15];
// wandle address in 16 bit word
byte LoByte = (address & 0x00FF);
byte HiByte = ((address & 0xFF00) >>8);
// Schreibe Adresse an PortPins
PORTA = LoByte;
PORTC = HiByte;
// lese Daten von portPins
data = PINL;
Serial.println("-------------------------------------------------");
sprintf(output, " %04x %02x", address, data);
Serial.println(output);
//Serial.println("-----------------------------------------------");
// next address
address = address+1;
}// end onClock
void hexDump(int startAdr, int endAdr) {
Serial.println();
Serial.println("------------------------------------------------------------------------");
Serial.println(" * HEX-Dump * " );
Serial.println("------------------------------------------------------------------------");
for (unsigned int adr = startAdr; adr <= endAdr; adr++) {
int a = adr;
// wandle addresse adr in 16 bit word
byte LoByte = (adr & 0x00FF);
byte HiByte = ((adr & 0xFF00) >>8);
// Schreibe Adresse an PortPins
PORTA = LoByte;
PORTC = HiByte;
// lese Daten von portPins
data = PINL;
if ( (adr == 0) || (adr % 16 == 0))
Serial.print ( " " + int2hex(adr, 4) + " : "); // Zeige Speicheradresse an
Serial.print (" " + int2hex(data, 2) + " "); // Zeige Daten-Byte an
if( (adr + 1) % 16 == 0)
Serial.println(); // Zeilenvorschub
}// end for
}//hexDump
String int2hex(int wert, int stellen) { // int -› hex-String Konvertierung mit der Angabe der Stellen
String temp = String(wert, HEX);
String prae = "";
int len = temp.length(); // Die Länge der Zeichenkette ermitteln
int diff = stellen - len;
for(int i = 0; i < diff; i++)
prae = prae + "O"; // Führende Nullen erzeugen
return prae + temp; // Führende Nullen + Ergebnis zurückliefern
} // end int2he
Das Programm kann entweder über einen Taster gesteuert werden…in diesem Fall wird die Interrupt-Routine verwendet und der Eingangs-Impuls an Pin2 des Arduino Mega geleitet. So kann man im Einzelschritt sehen welche Adresse an den EEPROM angelegt wird und welche Daten ausgelesen werden.
Alternativ habe ich die Möglichkeit vorgesehen einen ganzen Speicherbereich über die entwickelte Hexdump-Funktion auszugeben. Hierzu ist der Interrupt zu deaktivieren und die Start- und Endadresse anzugeben.
Infrarote Strahlung lassen sich mittels IR-sensitiver Fotodioden detektieren. Diese Dioden sehen im wesentlichen aus wie schwarze LED´s. Mit solchen Fotodioden fängt man jedoch die gesamte IR Strahlung ein, egal ob gepulst oder ungepulst. Wenn man nur die gepulste Strahlung einer Fernsteuerung verarbeiten möchte, gibt es spezielle Dioden, mit einem komplexeren Aufbau:
Auszug aus dem Datenblatt der IR Empfänger Diode OS-1838 IR-Empfänger
Dabei handelt es sich um einen Photodetektor mit Vorverstärker der sich in einem dreipoligen Gehäuse mit IR-Filter integriert ist. Der Baustein besitzt eine hohe Störfestigkeit gegenüber Umgebungslicht, hat einen niedrigen Stromverbrauch und ist TTL- und CMOS-kompatibel.
Dieser Empfänger lässt sich ohne großen Schaltungsaufwand an einen Arduino anschliessen und über eine in der Arduino-Entwicklungsumgebung enthaltene Bibliothek mit dem Namen IRremote.h einfach verwenden. Die entsprechende Schaltung ist ebenfalls im Datenblatt enthalten:
Anschluss des Empfängers an einen Microcontroller
Schliesst man den Empfänger nach dieser Methode an einen Arduino an, kann man mit folgendem kurzen Testprogramm und einer IR-Fernsteuerung aus dem Fundus des Makers schnell testen, welche Signale von der Fernsteuerung ausgesendet werden.
#include <IRremote.h>
int RECV_PIN = 8;
IRrecv irrecv(RECV_PIN);
decode_results results;
void setup() {
Serial.begin(9600);
irrecv.enableIRIn();
} // end setup
void loop() {
if (irrecv.decode(&results)) { // Wenn ein IR-Signal erkannt wurde,
Serial.println(results.value, HEX); // wird der Wert im seriellen Monitor ausgegeben
irrecv.resume();
}
delay(100);
} // end loop
In meinem Beispiel mit einer alten DVD-Player Fernsteuerung habe ich folgende Ausgabe erhalten:
Wie man erkennt, kommen etwas eigenwillige Werte an. Beim drücken der Taste A kommen immer wieder Werte wie [66593 und 1057] und bei der Taste B [1056 und 66592]. Da stellt man sich die Frage, welche Werte sind nun die richtigen? Da immer wieder die selben Werte kommen, lässt sich damit natürlich schon mittel if oder case Abfrage eine Steuerungslogik entwickeln, dennoch bleibt die Frage bestehen.
#include <IRremote.h>
int RECV_PIN = 8;
IRrecv irrecv(RECV_PIN);
decode_results results;
int led1 = 11; //Definiere der LEDs
int led2 = 12;
void setup()
{
Serial.begin(9600);
pinMode (led1, OUTPUT); //Die LEDs werden als Output initialisiert
pinMode (led2, OUTPUT);
digitalWrite(led1, LOW);
digitalWrite(led2, LOW);
irrecv.enableIRIn();
}
void loop() {
if (irrecv.decode(&results)) //Wenn ein IR-Signal empfangen wurde,
{
Serial.println(results.value);
switch(results.value) { //wird überprüft, ob es eine Aktion für die Taste gibt
case (66593) : { //Wenn PREV gedrückt wurde,
digitalWrite(led1, HIGH);} //an
break;
case (1068) : { //Wenn PAUSE gedrückt wurde,
digitalWrite(led1, LOW);
digitalWrite(led2, LOW); }
break;
case (1056) : { //Wenn NEXT gedrückt wurde,
digitalWrite(led2, HIGH); }
break;
delay(10); break;
default: delay(100);
} // end switch
irrecv.resume();
} // end if
} // end loop
Eine Antwort findet man auf den Seiten von Wolfgang Ewald. Er beschreibt sehr anschaulich, dass Fernsteuerungen gepulste Signale aussenden und wie diese Signale aufgebaut sind.
Signale von IR Fernbedienungen sozusagen zweifach gepulst sind. Ein Signal besteht aus einer Folge von Pulsen, wobei jeder Puls eine Breite von mehreren hundert Mikrosekunden bis einigen Millisekunden aufweist. Diese Pulse nenne ich mal Hauptpulse, da mir nichts Besseres einfiel. Das ist also kein Fachbegriff. Zwischen den Hauptpulsen gibt es unterschiedlich lange Pausen. Die Hauptpulse bestehen wiederum aus vielen 38 kHz Pulsen.
Auf der Empfängerseite werden die 38 kHz Pulse demoduliert. Außerdem wird das Signal aufgrund des oben beschriebenen Aufbaus der Empfänger umgekehrt. Das heißt, LOW wird HIGH und umgekehrt.
Bei der Auswertung geht es nun darum die Puls- und „Pausenbreiten“ zu vermessen. Das macht man am besten, indem man zunächst eine geeignete Zeiteinheit (timeSegment) definiert, die die Auflösung darstellt. Im nachfolgenden Sketch prüft der Arduino nach jedem Ablauf einer Zeiteinheit, ob der Datenpin am IR Empfänger HIGH oder LOW ist. Die Zeiteinheiten werden addiert bis ein HIGH/LOW bzw. LOW/HIGH Wechsel stattfindet. So hangelt man sich Stück für Stück durch das Signal.
Ist ein Signal vollständig übertragen, dann wird es auf dem seriellen Monitor einmal übersichtlich paarweise und einmal als Array ausgegeben. Letzteres könnt ihr direkt in den Sendesketch, den wir gleich besprechen, hineinkopieren. Der letzte gemessene Wert entspricht maxCount. Diesen brauchen wir nicht, da er nicht mehr zum Signal gehört. Entsprechend lasse ich ihn in der Array-Ausgabe weg.
Auszug aus dem Text von Wolfgang Ewald
Steuerung eines Motors
Das oben vorgestellte Programm lässt sich leicht erweitern…mittels eines 2N 2905 Schalttransistors kann man höhere Lasten wie beispielsweise einen Elektromotor schalten.
Die Grundschaltungen wie man mittels Arduino und Transistor Lasten ansteuern kann sehen wie folgt aus:
Wenn andere Gehäuseformen der Transistoren verwendet werden, muss im Datenblatt nachgeschaut werden, wo sich die erforderlichen Anschlüsse im jeweiligen Gehäuse befinden. In meinem Beispiel verwende ich einen 2N 2219 in einem TO-39 Gehäuse.
Die Schaltung basiert auf dem Datenblatt der IR Diode, der Kondensator ist als Entkopplungs-Kondensator zu dimensionieren. Im Anschluss das Arduino Programm zum Schalten des Motors:
// Ansteuerung einer IR Diode mittels alter Fernsteuerung
// und schalten eines Motors via Leistungstransistor
// PHOF Mai 2022
#define TRANSISTOR 7
#include <IRremote.h>
int RECV_PIN = 8;
int led1 = 11; // Definiere der LEDs
int led2 = 12;
IRrecv irrecv(RECV_PIN);
decode_results results;
void setup(){
Serial.begin(9600);
pinMode (led1, OUTPUT); // Die PINs werden als Output initialisiert
pinMode (led2, OUTPUT);
pinMode(TRANSISTOR, OUTPUT);
digitalWrite(led1, LOW); // Ausgänge auf LOW gesetzt
digitalWrite(led2, LOW);
digitalWrite(TRANSISTOR, HIGH);
irrecv.enableIRIn();
}
void loop() {
if (irrecv.decode(&results)) //Wenn ein IR-Signal empfangen wurde,
{
Serial.println(results.value); // Signal wird protokolliert über Monitor
// Signale einer alten Fernsteuerung wurden im Vorfeld ausgelesen
// PEV Schaltet Motor an PAUSE aus NEXT auch aus (für weitere Steuerungen vorgesehen)
switch(results.value) { //wird überprüft, ob es eine Aktion für die Taste gibt
case (66593) : { //Wenn PREV gedrückt wurde
digitalWrite(led1, HIGH); // LED 1 an und LED2 aus
digitalWrite(led2, LOW);
digitalWrite(TRANSISTOR, HIGH);} // Motor EIN
break;
case (1057) : { // Wenn PREV gedrückt wurde
digitalWrite(led1, HIGH); // alternativer Code
digitalWrite(led2, LOW);
digitalWrite(TRANSISTOR, HIGH);
}
case (1068) : { // Wenn PAUSE gedrückt wurde,
digitalWrite(led1, LOW); // alles ausschalten
digitalWrite(led2, LOW);
digitalWrite(TRANSISTOR, LOW);} // Motor aus
break;
case (1056) : { // Wenn NEXT gedrückt wurde,
digitalWrite(led2, HIGH); // LED 1 aus und LED 2 an
digitalWrite(led1, LOW);
digitalWrite(TRANSISTOR, LOW); // Motor aus
} //an
break;
delay(10); break;
default: delay(100);
} // end switch
irrecv.resume();
} // end if
}
Die dritte Tastenabfrage ist natürlich obsolet, kann aber für weitere Aktivitäten zum Beispiel die Richtungsänderung des Motors weiter verwendet werden. Ich denke aber damit sollte das Prinzip deutlich geworden sein, wie man mittels IR Fernsteuerung und einer entsprechenden Empfänger-Diode eigene Steuerungen entwickeln kann.
Es kann bei manchen Anwendungen vorkommen, dass die Arduino-Input/Output-Funktionen, wie analogRead() oder digitalWrite() zu langsam sind, und den Programmfluss stören. In solchen Fällen kann man auf die Hardware-Ports des ATmega 328 direkt zugreifen.
Da diese Form der Programmierung die Manipulation einzelner Bits erfordert, ist dazu die Kenntnis der wichtigsten Bit-Manipulationsoperationen in der Sprache C notwendig. Der Vorteil dieser Mühe liegt aber in sehr kompakten Programmen und vor allem erfolgt der Ablauf etwa 50mal schneller als mit den Standard Arduino-Input/Output-Funktionen.
Beim Arduino Uno mit dem ATmega 328 werden sämtliche Ein- und Ausgänge zu Ports zusammengefasst. Die komplette Pin-Belegung des ATMega328 ist in der Abbildung zu sehen. Hier erkennt man, dass die Pins nicht einzel angesteuert werden, sondern in mehrere Ports zusammengefasst sind.
Port B (digital pin 8 to 13)
Port C (analog input pins)
Port D (digital pins 0 to 7)
Es stehen bei diesem Prozessor drei Ports zur Verfügung, die mit den Buchstaben B, C und D bezeichnet werden. Port D beinhaltet am Arduino die digitalen Pins 0 bis 7, Port B die digitalen 8 bis 13 und Port C die analogen Pins 0 bis 5.
Wenn über Standardfunktionen wie z.B. digitalRead() sieht es für den Benutzer so aus, als würde er nur auf den von ihm gefragten Pin zugreifen und den Zustand entweder abfragen oder ändern. Im Hintergrund werden aber bei jeder dieser Operationen die Pins eines gesamten Ports angesprochen und dann die entsprechenden Ports so manipuliert, dass es so aussieht, als könnte man einen eInzelnen Pin adressieren. Daher benötigt ein DigitalWrite() Aufruf um einen einzelnen Pin auf HIGH oder LOW zu setzen bis zu 50 mal länger, als die direkte Port-Manipulation.
Die Ports werden von jeweils 3 Registern gesteuert, die mit DDRx, PORTx und PINx bezeichnet werden. Wobei das x für die Ports B, C und D stehen. Diese drei Register existieren für jeden Port, also zum Beispiel existiert für Port B ein DDRB-, ein PORTB- und ein PINB-Register. Analog gilt das auch für Port C und Port D. Das jeweilige DDRx-Register (DDR = Data Direction Register) legt fest, ob die PINs im entsprechenden Port als Input oder Output konfiguriert sein sollen. Das PORTx-Register legt bei der Ausgabe von Daten fest, ob die PINs HIGH oder LOW sind.
Für die Ausgabe von Daten wird das entsprechende Bit im DDRx-Register auf 1 (= Ausgabe) gesetzt. Somit kann nun die Ausgabe dadurch erfolgen, dass das korrespondierende Bit im zugeordneten PORTx- Register auf den gewünschten Wert gesetzt wird. Für die Daten-Eingabe wird im DDRx-Register das jeweilige Bit auf 0 (= Eingabe) gesetzt. Die an den Eingangs-Pins anliegenden Daten werden an die korrespondierenden Bits im PIN-Register übertragen und können dort abgerufen werden.
Beim AT Mega kommen dem selben Prinzip folgend noch zusätzliche Ports hinzu. Hier die Belegungen die ich bisher am meisten verwendet habe habe:
Aber es gibt noch weitere Ports bei AT Mega, die ich hier mal versucht habe in einer Übersicht darzustellen:
Port-Belegungen des Arduino AT Mega
Praktische Anwendung am Beispiel des Arduino Uno
Die einzelnen Register sind in der Arduino-Entwicklungsumgebung bereits als Namen vor definiert, man kann diese somit mit den entsprechenden Bezeichnungen wie beispielsweise PORTB, PINC usw. ansprechen. Beispielsweise setzt der Befehl
DDRD = B00111010 die PINs D0, D2, D6 und D7 als Eingänge
sowie die PINs D1, D3, D4 und D5 als Ausgänge.
Wie schon erwähnt, wird über das PORTx Register fest gelegt, ob die Ausgangs-PINs auf LOW oder HIGH geschaltet werden sollen. Dabei steht das x immer symbolisch für einen der drei Ports. Um beispielsweise die PINs 8, 10 und 12 auf HIGH und PIN 9, 11 und 13 im Port B auf LOW setzen wollen, schreiben wir:
void setup(){
DDRB = B11111111; // alle Bits als Ausgang
}
void loop (){
PORTB = B10101010; // Wechselblinker mit allen Ausgaengen
delay(300);
PORTB = B01010101;
delay(300);
}
//Weitere Beispiele:
DDRD = 0B11111111; // Alle PINs im Port D sind Output
PORTD = 0B11111111; // Alle PINs sind HIGH
Ziel ist es eine Stoppuhr zu programmieren. die Funktionsweise ist sehr gut im Beitrag auf der Internetseite von Start Hardware (https://starthardware.org/stoppuhr-mit-arduino-und-segmentanzeige-arduino-tm1637/) beschrieben:
Um eine derartige Funktionalität zu erzeugen verwendet man vorzugsweise eine State Maschine oder auch Zustandsautomat genannt.
State Maschine (Zustandsautomat)
Ein endlicher Automat (EA, auch Zustandsmaschine, Zustandsautomat; englisch finite state machine, FSM) ist ein Modell eines Verhaltens, bestehend aus Zuständen, Zustandsübergängen und Aktionen. Ein Automat heißt endlich, wenn die Menge der Zustände, die er annehmen kann (später S genannt), endlich ist. Ein endlicher Automat ist ein Spezialfall aus der Menge der Automaten.
Ein Zustandsautomat ist ein mathematisches Modell zur Verhaltensbeschreibung formaler Systeme . Es ist eine sogenannte abstrakte Maschine , die sich zu jeder Zeit in genau einem von endlich vielen Zuständen befinden kann. Der ZA kann als Reaktion auf Eingaben (Events) von einem Zustand in einen anderen wechseln ; der Wechsel von einem Zustand in einen anderen wird als Zustands-Übergang bezeichnet .
Damit lassen sich beliebig komplexe Steuerungsabläufe beschreiben und Relativ einfach in gängigen Programmiersprachen implementieren. Dazu bedient man sich einer sogenannten Zustandstabelle. In dieser ist für jeden Zustand beschrieben bei welchen Ereignissen welcher Folgezustand eingenommen wird.
Ziel ist es dabei immer, die eigentliche Maschine, also das was in der Zustandstabelle ausgedrückt wird, so einfach und überschaubar wie möglich zu präsentieren. Sie implementiert die Logik und definiert was die Maschine (in unserem Fall natürlich das Programm) eigentlich macht und warum sie es macht. Für unsere Stoppuhr ist folgende Zustandsmaschine aus der Spezifikation entstanden:
switch (programState) {
case 0: // gestoppt
// hier werden einfach vier Nullen auf dem Display angezeigt
// wird der Start-Taster gerdückt, springe zu State 1
break;
case 1: // gestartet
// zeige die vergangene Zeit auf dem Display
// Wenn der Zwischenzeit-Taster gedrückt wird, springe zu State 2
// Wenn der Start-Taster gedrückt wird, springe zu State 3
break;
case 2: // Zwischenzeit anzeigen
// zeige die Zwischenzeit an
// Wenn der Zwischenzeit-Taster gedrückt wird, springe zu State 1
break;
case 3: // gestoppt
// Wenn der Zwischenzeit-Taster gedrückt wird, springe zu State 1 (weiter laufen)
// Wenn der Start-Taster gedrückt wird, springe zu State 0 (löschen)
break;
}
Die Umsetzung erfolgt in meinem Fall nicht mit einer 7-Segmentanzeige sondern mit Hilfe eines 2-zeiligen LCD Displays. Das entsprechende Programm sieht dann so aus:
//=====================================
// Stoppuhr
// PHOF 2021
// Uno mit LCD inspiriert vom Beitrag
// auf www.starthardware.org
//=====================================
#define numberOfSeconds(_time_) ((_time_ / 1000) % 60)
#define numberOfMinutes(_time_) (((_time_ / 1000) / 60) % 60)
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27,20,2);
int startPin = 8; // start, stop, delete
int zwischenzeitPin = 9; // pause, continue
int stateStart;
int stateZwischenzeit;
int lastStateStart;
int lastStateZwischenzeit;
int programState = 0;
unsigned long startZeit = 0;
long zwischenzeit = 0;
long zeitAngehalten = 0;
char LCD_Line1[40];
void setup(){
Serial.begin(9600);
lcd.init(); lcd.backlight();
lcd.setCursor(0,0); lcd.print("Stoppuhr");
sprintf(LCD_Line1, "%02d:%02d", 0, 0); // Start-Anzeige
lcd.setCursor(6,1);lcd.print(LCD_Line1);
pinMode(zwischenzeitPin, INPUT);
pinMode(startPin, INPUT);
}// end loop
void showTime(long theTime) {
unsigned long myTime = theTime - startZeit;
int seconds = numberOfSeconds(myTime);
int minutes = numberOfMinutes(myTime);
sprintf(LCD_Line1, "%02d:%02d", minutes, seconds);
lcd.setCursor(6,1);lcd.print(LCD_Line1);
Serial.print(minutes);Serial.print(" min : ");
Serial.print(seconds); Serial.println(" sec");
}// end showTime
void loop(){
stateStart = digitalRead(startPin);
stateZwischenzeit = digitalRead(zwischenzeitPin);
switch (programState) {
case 0: // gestoppt
startZeit = 0;
// start
if ((stateStart == LOW) && (stateStart != lastStateStart)) {
startZeit = millis();
programState = 1;
}
break;
case 1: // gestartet
showTime(millis());
// zwischenzeit
if ((stateZwischenzeit == LOW) && (stateZwischenzeit != lastStateZwischenzeit)) {
zwischenzeit = millis();
programState = 2;
}
// stop
if ((stateStart == LOW) && (stateStart != lastStateStart)) {
zeitAngehalten = millis();
programState = 3;
}
break;
case 2: // zwischenzeit
showTime(zwischenzeit);
// zwischenzeit ausblenden
if ((stateZwischenzeit == LOW) && (stateZwischenzeit != lastStateZwischenzeit)) {
programState = 1;
}
break;
case 3: // gestoppt
// weiter laufen lassen
if ((stateZwischenzeit == LOW) && (stateZwischenzeit != lastStateZwischenzeit)) {
startZeit = startZeit + (millis() - zeitAngehalten);
programState = 1;
}
// löschen
if ((stateStart == LOW) && (stateStart != lastStateStart)) {
programState = 0;
}
break;
} // end case
lastStateStart = stateStart;
lastStateZwischenzeit = stateZwischenzeit;
}
Ein BLDC-Motor besteht aus zwei Hauptteilen, einem Stator und einem Rotor. In dieser Darstellung ist der Rotor ein Permanentmagnet mit zwei Polen, während der Stator aus Spulen besteht, die wie im Bild unten gezeigt angeordnet sind.
Prinzip-Schaubild eines BLDC Motors (Graphiken inspiriert von howtomechatronics.com)
Funktionsweise
Die Funktionsweise ist analog zu einem klassischen Motor. Sobald Strom durch die Spule fliesst, entsteht ein Magnetfeld, dessen Pole von der Richtung des Stroms abhängt. So dass je nach der Stromrichtung an der Spule ein anziehendes oder abstossendes Magnetfeld erzeugt werden kann.
Wird nun jede Spule nacheinander so angesteuert, dass die entstehende Kraftwirkung zwischen Stator und Rotor eine Drehwirkung erzeugt haben wir einen Motor.
Um die Effizienz des Motors zu erhöhen, können wir zwei gegenüberliegende Spulen als einzelne Spule so wickeln, dass entgegengesetzte Pole zu den Rotorpolen erzeugt werden, wodurch wir eine doppelte Anziehungskraft erhalten.
Mit dieser Konfiguration können wir die sechs Pole am Stator mit nur drei Spulen oder Phase erzeugen. Wir können den Wirkungsgrad weiter steigern, indem wir zwei Spulen gleichzeitig bestromen. Auf diese Weise wird eine Spule den Rotor anziehen und die andere Spule abstoßen. Damit ist leicht abzulesen, dass für eine 360 Grad Drehung des Rotors sechs Schritte erforderlich sind:
Wenn wir uns die Stromwellenform ansehen, können wir feststellen, dass in jedem Intervall eine Phase mit positivem Strom, eine Phase mit negativem Strom und die dritte Phase ausgeschaltet ist. Dies ergibt die Idee, dass wir die freien Endpunkte jeder der drei Phasen miteinander verbinden und so den Strom zwischen ihnen aufteilen oder einen einzelnen Strom verwenden können, um die beiden Phasen gleichzeitig zu erregen.
Hier ist ein Beispiel. Wenn wir im obigen Beispiel Phase A mit einer Art Schalter, zum Beispiel einem MOSFET, an die positive Gleichspannung anschließen und auf der anderen Seite die Phase B mit Masse verbinden, dann fließt der Strom von VCC durch Phase A, den Sternpunkt und Phase B, an Masse. So erzeugten wir mit nur einem einzigen Stromfluss die vier verschiedenen Pole, die den Rotor in Bewegung setzen.
Bei dieser Konfiguration haben wir tatsächlich eine Sternschaltung der Motorphasen, bei der der Sternpunkt intern verbunden ist und die anderen drei Enden der Phasen aus dem Motor herauskommen, und deshalb kommen beim bürstenlosen Motor drei Drähte heraus.
Damit der Rotor den vollen Zyklus macht, müssen wir nur die richtigen zwei MOSFETS in jedem der 6 Intervalle aktivieren und genau darum geht es bei ESCs. Ein ESC (=elektronischer Geschwindigkeitsregler) steuert die Bewegung oder Geschwindigkeit des bürstenlosen Motors, indem er die entsprechenden MOSFETs aktiviert, um das rotierende Magnetfeld zu erzeugen, damit sich der Motor dreht. Je höher die Frequenz oder je schneller der ESC die 6 Intervalle durchläuft, desto höher ist die Drehzahl des Motors.
Ansteuerung mit einem Steuergerät
Es gibt verschiedene elektronische Steuergeräte im Internet. Hier soll an einem Beispiel gezeigt werden, wie damit ein ausgedienter Elektro-Rasenmäher-Motor angesteuert werden kann.
Das mir vorliegende Gerät mit der Bezeichnung WS55-220 kommt von einem chinesischen Hersteller ohne weitere Erklärungen. Es hat mehrere Anschlüsse die wie folgt gekennzeichnet sind:
Anschlussbild 1
Daneben finden sich weitere Bilder im Internet, aus denen eine mögliche Beschattung hervorgeht:
Anschlussbild 2
Meine eigenen Untersuchungen haben nach einigem Suchen dann auch zu der Erkenntnis geführt, dass die Steuersignale sich alle auf Masse beziehen und dann zu folgendem Schaltbild geführt.
Ansteuerung über einen Arduino
Möchte man die Ansteuerung anstelle des meist mit gelieferten Potentiometer mit einem Microcontroller durchführen, so muss die Spannungsteilung des Potentiometers durch einen MOSFET Transistor übernommen werden, der über ein PWM Signal angesteuert wird.
Aus dem obigen Prinzipschaltbild geht hervor, dass vom Controller eine Steuerspannung von +10V die über das Potentiometer an den Eingang SV geführt wird. Aus diesem Grund liegt der Ansatz nahe, eine typische MOSFET Motor-Ansteuerung herzunehmen und anstelle des Motors die Anschlüsse SV und +10V zu verwenden.
Schaltbild zum Ansteuern des Motorcontrollers über ein PWM Signal
Das zugehörige Test-Programm sieht so aus:
/*
* Test-Programm für den Arduino UNO
* PHOF Okt 2021
* PWM Signal 255 = Motor Aus / 0 = Motor full speed
*
*/
int analogPin = 9; // PWM output to Input Gate of Mosfet
int i;
void setup(){
Serial.begin(115200);
}
void loop(){
for (i=255; i>0;i=i-5){
Serial.print("Value is:");
Serial.println(i);
analogWrite(analogPin, i); // PWM out from 255 to 0 for Speed Control
delay(3000);
} //end for
}//end loop
Ein Schieberegister ist eine logische Schaltung, die mehrstellige binäre Signale taktgesteuert aufnehmen, speichern und wieder abgeben kann. Schieberegister ermöglichen die Transformation serieller Daten in parallele Daten und umgekehrt. Sie bestehen im Grunde aus hintereinander geschalteten D- oder JK-FlipFlops. Die Takteingänge aller Stufen werden zusammengeschaltet und über einen gemeinsamen Takt versorgt.
Da ein Flip-Flop nur ein Bit speichern kann, müssen je nach Anwendung mehrere Flip-Flops zu einem Schieberegister zusammengeschaltet werden. Bei dem weit verbreiteten IC vom Typ 74HC595 beispielsweise sind 8 FlipFlops integriert, so kann man mit lediglich 3 Arduino-Pins 8 Ausgangspins ansteuern.
Der Schaltkreis hat folgende Pin-Belegung:
Funktionsweise
Die Funktionsweise eines Schieberegisters kann man sichwie in obiger Abbildung vorstellen: Ein serieller Datenstrom wird über eine Datenleitung an den Daten-Pin DS des Bausteins übertragen. Solange das ShiftClock-Pin (SHCP) auf Low steht passiert vorerst gar nichts. Erst wenn ein Pegelwechsel von LOW nach HIGH am ShiftClock-Pin erfolgt, wird der Zustand der am DS-Pin anliegt in den Zwischenspeicher geschoben.
Funktions-Schema eines Schieberegisters
Dieser Vorgang kann beliebig oft wiederholt werden, bis der Zwischenspeicher voll ist oder genügend Daten aufgenommen wurden. Da ein Schieberegister genau genommen aus zwei Registern besteht: dem Schieberegister, in welches der Zustand der einzelnen Ausgangspins seriell, also Bit für Bit gesteuert über das ShiftClock-Pin (SHCP) geschoben wird. Und das Ausgangsregister, in das der Zustand des Schieberegisters über ein weiteres Signal am sogenannten Storage Register Clock-Pin (SRCP) hin kopiert wird.
Es handelt sich somit um zwei separierte Register deren Datenströme kontrolliert Über die Pegelwechsel von LOW nach HIGH an den beiden Steuer-Pins ShiftClock-Pin (SHCP) und Storage Register Clock-Pin (SRCP) zusammen gefügt werden können.
Beispiel Programm
//------------------------------
// Schiebregister Test
//------------------------------
int shiftPin = 4; //SH_CP_PIN
int storePin = 2; //ST_CP_PIN
int dataPin = 7; //DS_PIN
// Dieses Muster soll ausgegeben werden
int muster[8] = {1,1,1,1,1,1,0,0};
void setup() {
pinMode(storePin, OUTPUT);
pinMode(shiftPin, OUTPUT);
pinMode(dataPin, OUTPUT);
digitalWrite(storePin, LOW); // storePin sicherheitshalber auf LOW
for (int i=0; i<8; i++) {
digitalWrite(shiftPin, LOW);
digitalWrite(dataPin, muster[i]);
digitalWrite(shiftPin, HIGH);
} // end for
// wenn alle Daten übertragen, auf Ausgabe schalten
digitalWrite(storePin, HIGH);
}
void loop () {
}
Mehrere Schieberegister zusammenschalten
Als Beispiel für die Möglichkeiten mehr als 8 Ausgänge zu steuern, habe ich eine Schaltung mit 3 IC 74595 entwickelt. Aufgebaut als Experimentierplattform für den Arduino und entsprechenden Anschlussmöglichkeiten auf einer Lochrasterplatine prototypisch umgesetzt.
Prototyp einer 24Bit-Port-Erweiterung mit 3 IC 74595 Bausteinen
Die Ansteuerung erfolgt wie bei einem einzelnen Schaltkreis d.h. alle Steuersignale an den Pins SHCP und SRCP werden parallel an alle ICs angeschlossen. Nur die DS-Leitungen der zweiten und folgenden Schieberegister werden kaskadiert. Dh. der Überlauf-Ausgang Q7S des ersten Schieberegisters wird mit dem DS-Eingang des 2. Registers verbunden usw. Das folgende Blockschaltbild verdeutlicht das Prinzip.
Das Funktionsprinzip: Im Innern eines Encoders (Impulsgebers) sind zwei Taster integriert. Sie schalten um einen halben Klick Phasenverschoben. Detektiert man auf dem Kanal 1 (also vom ersten Taster) einen Wechsel von LOW zu HIGH prüft man, ob Kanal 2 LOW ist. Ist dieser LOW, wurde in die eine Richtung gedreht, ist er HIGH, wurde er in die andere Richtung gedreht.
Das Ganze funktioniert natürlich auch in die andere Richtung: Detektiert man auf dem Kanal 1 (also vom ersten Taster) einen Wechsel von HIGH zu LOW prüft man, ob Kanal 2 HIGH ist. Ist dieser HIGH, wurde in die eine Richtung gedreht, ist er LOW, wurde er in die andere Richtung gedreht.
Dazu muss man zu Beginn festlegen wie man die beiden möglichen Drehrichtungen benennen möchte, da sonst Verwirrungen entstehen können.
int encoderPinA = 2;
int encoderPinB = 3;
int encoderPos = 0;
int encoderPinALast = LOW;
int encoderPinANow = LOW;
void setup() {
pinMode (encoderPinA, INPUT_PULLUP);
pinMode (encoderPinB, INPUT_PULLUP);
Serial.begin (115200);
}
void loop() {
encoderPinANow = digitalRead(encoderPinA);
if ((encoderPinALast == HIGH) && (encoderPinANow == LOW)) {
if (digitalRead(encoderPinB) == HIGH) {
encoderPos++;
} else {
encoderPos--;
}
Serial.println(encoderPos);
}
encoderPinALast = encoderPinANow;
}
Beim DS18B20 handelt es sich um einen digitalen Temperatursensor im TO92 Gehäuse, der über einen One-Wire-Bus angesprochen wird. Auf diese Weise ist es möglich mehrere Sensoren an einem Mikrocontroller-Pin anzuschließen. Der Sensor besitzt eine Auflösung von 12 Bit und eine Messgenauigkeit von ±0.5°C im Messbereich von -55°C bis +125°C.
PIN Belegung (Quelle: AZ-Delivery.com)
Für die Funktion wird der Widerstand R1 = 4,7kOhm (Pullup-Widerstand genannt) zwischen Vcc und dem Daten Pin benötigt, weil er die Spannung bei fehlendem Signal auf die Versorgungsspannung hochzieht), was im Datenblatt so nicht gleich ersichtlich ist.
Der serielle Datenbus (1-Wire-Bus) und das vom Sensor verwendete Protokoll erfordern erheblichen Programmieraufwand, daher wird in den meisten Fällen auf vordefinierte Funktionsbibliotheken zurück gegriffen.
DHT 11 und DHT 22
Der DHT22 ist ein zuverlässiger Sensor zum ermitteln von Temperatur und Luftfeuchtigkeit. Da der Sensor sowohl mit 3,3V als auch 5V betrieben werden kann, eignet er sich zum Anschluss an alle gängige Boards
Der Arduino Esplora ist ein Board aus den Anfängern der Arduino Ära, das heute nicht mehr gebaut wird. Die Besonderheit dieses Boards, ist die Ausstattung mit unterschiedlichen Bausteinen. Für Eingaben gibt es einen Joystick, vier Tasten, einen Lichtsensor, einen Schieberegler, ein Mikrofon, einen Temperatursensor und einen Beschleunigungsmesser. Für die Ausgänge gibt es einen Summer und eine dreifarbige LED. Auf den ersten Blick sieht es aus wie ein Videospiel-Controller.
Infolgedessen unterscheidet sich die Programmierung etwas von der für andere Arduino-Boards. Es verfügt über eine eigene Bibliothek, die das Lesen von den Eingangssensoren und das Schreiben auf die Ausgangsaktoren erleichtert. Informationen zur Verwendung der Bibliothek finden Sie in diesem Handbuch und auf den Referenzseiten der Esplora-Bibliothek.
Der Arduino Esplora verfügt über eine Reihe von Funktionen für die einfache Verbindung mit den auf der Platine montierten Sensoren und Aktoren. Auf die Funktionen kann über die Esplora-Klasse zugegriffen werden. Die Bibliothek bietet einfachen Zugriff auf die Daten von den integrierten Sensoren und bietet die Möglichkeit, den Status der Ausgänge zu ändern.
Das folgende Programm zeigt, wie man den Temperatursensor des Esplora verwenden kann. Die Funktion Esplora.readTemperature () ruft den Wert vom Temperatursensor ab. Je nach Ihrer Wahl erhalten Sie die Temperatur in Grad Celsius oder Grad Fahrenheit. Es wird ein Parameter benötigt, DEGREES_C für Celsius oder DEGREES_F für Fahrenheit.
#include <Esplora.h>
void setup() {
Serial.begin(9600); // initialize serial communications with your computer
}
void loop() {
// read the temperature sensor in Celsius, then Fahrenheit:
int celsius = Esplora.readTemperature(DEGREES_C);
int fahrenheit = Esplora.readTemperature(DEGREES_F);
// print the results:
Serial.print("Temperature is: ");
Serial.print(celsius);
Serial.print(" degrees Celsius, or ");
Serial.print(fahrenheit);
Serial.println(" degrees Fahrenheit.");
Serial.println(" Fahrenheit = (9/5 * Celsius) + 32");
delay(1000);
}
Esplora Digital Write
Um Daten Auszugeben, können die üblichen Funktionen verwendet werden. Auf dem oben abgebildeten „inoffiziellen“ Esplora Diagramm kann man entnehmen, dass ein Ausgang über den Digitalen Pin 3 am Thinkerboard Stecker möglich ist.
// include the Esplora library
#include <Esplora.h>
void setup() {
pinMode(3,OUTPUT);
digitalWrite(3,HIGH);
}
void loop() {
}