Inhalt
Vorzeichenbehaftete int-Typen | ||||
Typname | Bit-Breite | Wertebereich | C-Entsprechung (avr-gcc) | |
int8_t | 8 | −128 ⋯ 127 | −27 ⋯ 27−1 | signed char |
int16_t | 16 | −32768 ⋯ 32767 | −215 ⋯ 215−1 | signed short, signed int |
int32_t | 32 | −2147483648 ⋯ 2147483647 | −231 ⋯ 231−1 | signed long |
int64_t | 64 | −9223372036854775808 ⋯ 9223372036854775807 | −263 ⋯ 263−1 | signed long long |
Vorzeichenlose int-Typen | ||||
Typname | Bit-Breite | Wertebereich | C-Entsprechung (avr-gcc) | |
uint8_t | 8 | 0 ⋯ 255 | 0 ⋯ 28−1 | unsigned char |
uint16_t | 16 | 0 ⋯ 65535 | 0 ⋯ 216−1 | unsigned short, unsigned int |
uint32_t | 32 | 0 ⋯ 4294967295 | 0 ⋯ 232−1 | unsigned long |
uint64_t | 64 | 0 ⋯ 18446744073709551615 | 0 ⋯ 264−1 | unsigned long long |
Die Programmierung der einzelnen AVR erfolgt in C. Dazu verwende ich als Entwicklungsumgebung unter Windows , wie im Kapitel: Die Toolchain beschrieben, das Atmel Studio und unter Linux die Eclipse-CDT IDE mit dem Eclipse AVR-Plugin.
Im weiteren Verlauf, sollen nach und nach grundlegende Funktionen erstellt werden. Erstes Ziel ist dabei die serielle Kommunikation zwischen dem UART eines AVR und dem UART, erstmal eines PCs. Danach werden wir den I2C-Bus auf den AVR zum laufen bringen, um so dann mehrere AVRs miteinander vernetzen zu können.
|
| ||||||||||
|
| ||||||||||
Download: Standardprogramm.zip |
Schauen wir uns den Quellcode Standardprogramm.c dieses ersten Projekts mal an:
/* * Standardprogramm.c * * Created: 23.02.2013 22:24:17 * Author: Scheik */ #include <avr/io.h> //(1) int main(void) //(2) { while(1) { //TODO:: Please write your application code //(3) } } |
(1): Zuerst wird standardmäßig per #include <avr/io.h> die in der Entwicklungsumgebung bereits vorhandene Header Datei “io.h” aus der AVR-libc eingebunden. Durch dieses Standard- Headerfile und da wir im Studio beim Erstellen des Projekts bereits angegeben haben, welchen Typ Controller wir verwenden, sind somit z.B alle zum programmieren nötigen Registernamen richtig ansprechbar. Es werden also automatisch alle nötigen Headerdateien eingebunden die zum gewählten AVR gehören.
(2): Das eigentliche C- Programm beginnt mit den Anweisungen in der Funktion main(void){}. Jedes ausführbare C- Programm muss genau eine main()- Funktion haben. Sie legt für den Compiler den so genannten Programmeinstiegspunkt fest.
(3): Den eigentliche Programmcode finden wir dann in der Main()-Funktion. Diese enthält hier eine so genannte Hauptschleife (Main-Loop). In dieser Hauptschleife while(1){} würde der Programmcode nun also in einer Endlosschleife abgearbeitet werden. Der Controller würde also in diesem Programm ganz einfach ununterbrochen nichts tun.
Schematische Darstellung der Verbindung eines AVR mit der seriellen Schnittstelle des PC
Ziele und Vorgaben:
Um das umzusetzen muss nun ein Versuchsaufbau her, das kann ein gekauftes Experimentierboard oder eine eigene Schaltung mit einem beliebigen AVR sein. Ich verwende hier für Versuchszwecke einen ATMega32, getaktet mit einem externen 16MHz- Quarz.
Bei der direkten Verbindung des UART eines AVR mit dem PC müssen wir die Sende- und Empfangsleitung, des bei 5 Volt betriebenen Controllers, auf den +/-12V-Pegel der RS232- Schnittstelle des PCs bringen. Dazu kann am einfachsten ein Schnittstellentreiber wie z.B. der MAX232 benutzt werden. Die Schaltung dazu sieht dann so aus:
Schaltung für die Verbindung eines AVR mit der seriellen Schnittstelle des PC mittels Pegelwandler MAX232
Die Verbindung zum PC wird dann mit einem 9- poligem Modem-Kabel hergestellt, also einem normalen Verlängerungskabel, keinem Nullmodemkabel mit gekreuzter Sende- und Empfangsleitung.
Hier ein einfaches Programm, mit der Funktionsbibliothek rs232.c, zum testen der seriellen Kommunikation zwischen dem UART des AVR und der RS232-Schnittstelle des PC:
|
| ||||||||||||||||
|
| ||||||||||||||||
Download |
Die Einstellungen für die serielle Schnittstelle werden in der, zur Funktionsbibliothek gehörenden, Headerdatei rs232.h vorgenommen. Dazu muss aber zuvor im Code die Taktfrequenz des AVR definiert sein. Ich mache das hier in der Headerdatei global.h. Die Einstellungen für die serielle Kommunikation sind hier im Beispiel auf 19.200 Baud, 8 Bit pro Zeichen, keinem Paritäts- und einem Stoppbit , kurz 8N1, eingestellt.
Was passiert mit diesem Programm?
Ist der AVR korrekt mit dem PC verbunden und werden Daten von der seriellen Schnittstelle des PC an den UART des AVR übertragen, unterbricht der AVR bei jedem neu empfangenen Zeichen sein Hauptprogramm und springt in eine Interruptroutine (ISR- Routine). In dieser ISR- Routine wird dann immer das zuletzt empfangene Byte im RX- Puffer gespeichert bzw. angehängt. Ist dabei letzte empfangene Zeichen ein Zeilenumbruch (CR= Carriage Return) oder ist der Eingangspuffer voll, wird ein Merker (Flag) gesetzt.
Funktionsprinzip der Interruptgesteuerten Programmierung mit Interruptroutine
Im eigentlichen Hauptprogramm des AVR, der Main-Funktion wird dieses Flag in einer Endlosschleife ausgewertet. Ist es gesetzt (also ein CR empfangen oder RX- Puffer voll), werden die empfangenen Daten zur Kontrolle einfach über den UART des AVR an die RS232- Schnittstelle des PC zurück gesendet.
Man erkennt hier bereits den Vorteil der Interruptgesteuerten Programmierung, das Programm läuft quasi auf zwei Ebenen ab. Eine, in der durch Interrupts ausgelöster Code abläuft und eine Ebene, mit dem eigentlichen Hauptprogramm, der Main- Funktion. Das Hauptprogramm wird durch die Interrupts unterbrochen und erst nach Abarbeitung der Anweisungen in der zugehörigen Interruptroutine wieder aufgenommen. In diesem Programm werden Daten von der RS232-Schnittstelle im Hintergrund in einer Interruptroutine empfangen und in ein Array zwischengespeichert. Diese Interruptroutine wird immer dann ausgelöst, wenn ein Byte korrekt vom UART des AVR empfangen wurde, und zur Abholung im Daten- Regigster des UART bereit steht. Somit könnte man im Hauptprogramm nebenbei noch anderen, zeitunkritischen Code ausführen. Eine Alternative wäre dazu nur das ständige Polling der seriellen Schnittstelle. Der AVR wäre dann aber nur damit beschäftigt auf die Schnittstelle zu lauschen. In diesem Fall hier wäre das noch egal, der AVR tut hier auch noch nichts anderes. Später wird man noch mehr Möglichkeiten sehen, für die man die Interrupts eines AVR sinnvoll einsetzten kann.
Wir können nun mithilfe eines einfachen Terminal- Programms testen, ob die Kommunikation zwischen PC und AVR über die serielle Schnittstelle, mithilfe unserer Funktionsbibliothek rs232.c, richtig funktioniert. Ich benutze hier als Terminal- Programm die Freeware Putty :
Kommen wir zur Vernetzung mehrerer AVR mittels I2C- Bus. Zum Aufbau und Test brauchen wir also zunächst mindestens einen Master und einen Slave.
Vernetzung zweier AVR mittels I2C- Bus
Wie man sieht wurde die Testumgebung aus dem vorigen Projekt um einen zweiten AVR erweitert. Beide AVR sind über den I2C- Bus miteinander verbunden. Der erste AVR, aus dem vorigen Projekt, mit Verbindung zum PC soll jetzt zusätzlich als Master im I2C- Bus funktionieren. Der zweite AVR, der Slave, kann einfach ein weiteres Experimentierboard oder eine einfache Selbstbauschaltung mit einem beliebigen AVR sein. Ich verwende dafür ein identisches Board wie für den Master, ebenfalls mit einen ATMega32 ausgestattet und mit einem externen 16MHz- Quarz getaktet.
Die Ziele und Vorgaben sind ähnlich wie im Projekt für die seriellen Schnittstelle, erst mal wollen wir diesen Teil der Hard- und Software, also die Kommunikation des Masters mit zunächst einem Slave im I2C- Bus, zum laufen bringen:
Wir werden im folgenden zwei neue Funktionsbibliotheken mit ihren zugehörigen Headerdateien erstellen. Eine für einen AVR als Master im I2C-Bus, TWI_Master.c und eine für einen AVR als Slave im Bus, die Bibliothek TWI_Slave.c. Will man später kommerzielle I2C- Bausteine, z.B. einen PCF8574- Portexpander, am Bus betreiben, reicht die Bibliothek TWI_Master.c aus.
Bleiben wir aber bei der Verbindung zweier AVR mittels I2C- Bus.
Hardwareseitig müssen für die Verbindung nur die Daten- und Taktleitung des jeweiligen TWI-Interface, also die SDA- und SCL- Portpins der beiden AVR miteinander verbunden werden. Zusätzlich muss dafür gesorgt sein, dass beide Controller auf einem gemeinsamen Massepotential hängen. Um für saubere Pegel an der Daten- und Taktleitung zu sorgen werden noch Pull-Up-Widerstände an den Busleitungen benötigt.
PLATZHALTER SCHALTUNG
Kommen wir zuerst zum Master, sein Programm muss nebst den Funktionen für den Betrieb als Master im I2C- Bus zusätzlich die Schnittstelle zum PC mittels UART des AVR bereitstellen, dazu können wir einfach die bereits vorhandene Funktionsbibliothek rs232.c verwenden. Alle Funktionen für den Betrieb des AVR als Master im I2C-Bus sind in der wiederverwendbaren Bibliothek TWI_Master.c zu finden. Mit deren Hilfe kann der AVR als Master im Bus initialisiert werden und dann, als Mastertransmitter im MT- Mode oder Masterreceiver im MR- Mode Daten an Slaves übertragen oder von ihnen zum Empfangen anfordern.
|
| ||||||||||||||||
|
| ||||||||||||||||
Download: |
Alle nötigen Einstellungen für den I2C- Bus wie z.B die Taktrate des Bus werden in der, zur Funktionsbibliothek TWI_Master.c gehörenden Headerdatei TWI_Master.h definiert. Globale Definitionen (FCPU, etc.) findet man wie gehabt im Headerfile global.h.
Zum Ablauf des Programms auf dem Master:
Über die RS232- Schnittstelle des PC, kann eine Zeichenfolge an den Master gesendet werden. Diese Daten werden dann über den I2C-Bus, vom Master im MT-Mode an den Slave gesendet. Dieser speichert über den Bus empfangene Daten in einem Array (Puffer) ab. Unten wird die Software des Slave noch näher beschrieben. Zur Kontrolle wird der Master dann, diesmal im MR- Mode, Daten vom Slave anfordern, einlesen und wieder zur Kontrolle, zurück über die RS232- Schnittstelle, am PC ausgeben.
Hier nun das Programm für den AVR, der als Slave im I2C-Bus agiert. Alle für diesen Betrieb nötigen Funktionen sind dazu in der Bibliothek i2c_slave.c enthalten. Zuerst wird der AVR damit als Slave im Bus initialisiert und ihm eine Adresse gegeben. Dann kann der AVR, als Slavetransmitter (ST- Mode) oder Slavereceiver (SR- Mode), von ihm angeforderte Daten senden oder empfangen.
|
| ||||||||||||
|
| ||||||||||||
Download: |
Test der Kommunikation I2C- Bus
Hat man nun den Master und den Slave programmiert und alles nach obigem Schema aufgebaut, können wir die Funktion des I2C-Bus mit diesen beiden einfachen Testprogrammen erproben. Um den Programmcode übersichtlich zu halten ist die Funktion hier noch sehr einfach. Sendet man Daten, hier einen “String” mit abschließendem Carriage-Return (CR) über die RS232-Schnittstelle des PC an den UART des Master, wird dieser zuerst die empfangenen Daten per I2C als Mastertransmitter an den Slave senden und dann als Masterreceiver wieder von ihm lesen. Zur Kontrolle werden die empfangenen und gesendeten Daten vom Master wieder auf die serielle Schnittstelle des PC ausgegeben. Wir schicken hier also einfach mal Daten im Kreis herum. Dafür reicht auf dem PC oder Raspberry Pi erst mal wieder ein einfaches Terminalprogramm, mit Putty sieht das dann z.B so aus:
Wie man sieht, funktioniert die Kommunikation über den I2C-Bus mittels TWI-Interface der AVR.
Für die weitere Entwicklung haben wir nun, nebst der Funktionsbibliothek rs232.c für die serielle Schnittstelle, zwei weitere Funktionsbibliotheken TWI_Master.c undTWI_Slave.c für die Nutzung des I2C-Bus zur Verfügung.
Kommen wir zu einem weiteren wesentlichen Element mit dem man bei der Programmierung der AVR immer arbeiten wird, die Timerbausteine der AVR.
Timer/Counter sind Bausteine/Register eines AVR, bei deren Verwendung ein Zähler in Form des jeweiligen Timer-Registers z.B. im Systemtakt hochgezählt wird. Erreicht dieser Zähler seinen maximal erreichbaren Wert, läuft also über (Overflow), wird er zurückgesetzt und beginnt wieder von vorne hoch zu zählen. Dies alles passiert dabei im Hintergrund, allein durch die Hardware des AVR, zunächst ohne das eigentliche Programm zu beeinflussen.
Das alleine wäre aber noch nicht nützlich. Deswegen kann man aber z.B. bei jedem Überlauf des Zählers oder immer wenn der Zähler einen bestimmten Wert erreicht, das so genannte Compare Match, vom Timerbaustein einen Interrupt auslösen lassen. Auf diesen Interrupt kann man dann im eigentlichen Programm reagieren.
Da zum hochzählen des Timer-Registers entweder der Systemtakt des AVR oder eine externe Taktquelle verwendet werden kann, können diese Bausteine sowohl als Zeitgeber (Timer) als auch als Zähler (Counter) agieren.
Die heutigen Mikrocontroller und insbesondere die RISC-AVRs sind für viele Steuerungsaufgaben zu schnell. Wenn wir beispielsweise eine LED oder Lampe blinken lassen wollen, können wir selbstverständlich nicht die CPU-Frequenz verwenden, da ja dann nichts mehr vom Blinken zu bemerken wäre. Wir brauchen also eine Möglichkeit, Vorgänge in Zeitabständen durchzuführen, die geringer als die Taktfrequenz des Controllers sind. Selbstverständlich sollte die resultierende Frequenz auch noch möglichst genau und stabil sein
Der Vorteiler dient dazu, den CPU-Takt vorerst um einen einstellbaren Faktor zu reduzieren. Die so geteilte Frequenz wird den Eingängen der Timer zugeführt. Wenn wir mit einem CPU-Takt von 4 MHz arbeiten und den Vorteiler auf 1024 einstellen, wird der Timer mit einer Frequenz von 4 MHz / 1024, also mit etwas weniger als 4 kHz versorgt. Wenn der Timer läuft, wird das Zählregister (TCNTx) mit dieser Frequenz inkrementiert.
Die Möglichkeiten für die Anwendung von Timerbausteinen sind vielfälltig, mögliche Beispiele wären,
Übersicht über die gängigen Timer der AVR
Timer0 | Timer1 | Timer2 | |||||||||||||||||||||||||||
8-Bit- Timer | 16-Bit-Timer | 8-Bit-Timer | |||||||||||||||||||||||||||
|
|
|
Die Timer werden, wie oben beschrieben, über bestimmte Register angesprochen, gesteuert und konfiguriert. So besitzt jeder Timer ein eigenes Timer/Counter Control Register, TCCRn oder sein eigentliches Zählregister das so genannte Timer/Counter Register, TCNTn. Im folgenden werden alle zum Betrieb der Timer nötigen Register näher vorgestellt, schauen wir zunächst aber das Kontrollregister der 8 Bit Timer genauer an, mit dessen einzelnen Bits die eigentliche Funktion des jeweiligen Timers eingestellt wird.
Das Kontrollregister TCCRn der 8 Bit Timer
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
FOCn | WGMn0 | COMn1 | COMn0 | WGMn1 | CSn2 | CSn1 | CSn0 | |
Read/Write | W | R/W | R/W | R/W | R/W | R/W | R/W | R/W |
Initial Value | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Wie man sieht sind die Kontrollregister TCCR0 und TCCR2 der beiden 8-Bit-Timer analog aufgebaut.
Neben den drei Clock-Select-Bits, für die Wahl des Prescalers findet man in den Kontrollregistern der 8-Bit Timer die beiden Bits WGMn0 und WGMn1, sie bestimmen den so genannten Waveform Generation Mode, also den Betriebsmodus, in dem der Timer arbeiten soll. Daneben findet man die beiden Bits COMn0 und COMn1, mit denen der Compare Match Output Mode des Timers konfiguriert wird.
Eine Ausnahme ist hier der Timer0 des ATMega8, in dessen Kontrollregister TCCR0 nur die Clock-Select-Bits beschrieben werden können, da er keine speziellen Modi zulässt:
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
- | - | - | - | - | CS02 | CS01 | CS00 | |
Read/Write | R | R | R | R | R | R/W | R/W | R/W |
Initial Value | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Schauen wir uns nun die einzelnen Bits des Kontrollregisters und ihre Funktion für die Timer genauer an, betrachten wir zuerst die Clock-Select-Bits:
Clock-Select: Die Bits CS0, CS1 und CS2
Werden die Timer über den eigenen Systemtakt gesteuert, kann dieser über einen Vorteiler, den so genannten Prescaler herunterskaliert werden. Mögliche Werte für den Prescaler sind 1, 8, 64, 256 und 1024. So würde zum Beispiel ein Timer, bei einem Systemtakt von 8 MHz und einem gewählten Prescaler von 8, mit 8MHz/8 = 1MHz laufen.
Timer0 | Timer1 | Timer2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Kontrollregister: TCCR0 | Kontrollregister: TCCR1B | Kontrollregister: TCCR2 |
Mit den Clock-Select-Bits wird also der Prescaler festgelegt und der Timer Ein- und Aus geschaltet.
Bei den beiden 8 Bit Timern Timer0 und Timer2 findet man die Clock-Select-Bits in den Registern TCCR0 und TCCR2. Da der Timer1 ein 16- Bit Timer ist, ist auch das Kontrollregister dieses Timers 16- Bit breit. Bedingt durch die 8-Bit Architektur der AVR wurde es in die zwei 8-Bit Register TCCR1A und TCCR1B aufgeteilt. Die Clock-Select-Bits für Timer1 findet man also im Register TCCR1B.
Neben den Clock-Select-Bits, spielen für die Funktion eines Timers aber noch andere Bits im Kontrollregister eine Rolle, bleiben wir hier zunächst bei den 8 Bit Timern, also den zugehörigen Registern TCCR0 und TCCR2.
Waveform Generation Mode: Die Bits WGM0 & WGM1 der 8-Bit Timer
Die Wahl des Betriebsmodus der 8-Bit Timer erfolgt durch die Bits WGMn0 und WGMn1 im Kontrollregister TCCRn
Mode | WGMn1 | WGMn0 | Betriebsmodus | TOP | Update von OCRn | TOVn Flag gesetzt wenn |
0 | 0 | 0 | Normal | 0xFF = 255 | Immediate | MAX |
1 | 0 | 1 | PWM, Phase Correct | 0xFF | TOP | BOTTOM |
2 | 1 | 0 | CTC | OCRn | Immediate | MAX |
3 | 1 | 1 | Fast PWM | 0xFF | BOTTOM | MAX |
Compare Match Output Mode: Die Bits COM0 & COM1 der 8-Bit Timer
Kommen wir zu zwei weiteren Bits im Kontrollregister, die für die Funktion des Timers eine Rolle spielen, den beiden Bits COMn0 und COMn1. Mit diesen beiden Bits kann der jeweilige Hardware- Ausgang (z.B. OC0 für Timer0) an den Timer gekoppelt werden, z.B. wenn der Timer zur Signalerzeugung (Hardware-PWM) benutzt wird. Ihre Funktion hängt dabei von dem, durch die Bits WGMn0 und WGMn1, gewählten Betriebsmodus des Timers ab.
Normal und CTC Mode | Fast PWM Mode | Phase Correct PWM Mode |
Kommen wir zum Timer1, wie gesagt besitzt der 16-Bit Timer ein 16 Bit- breites Kontrollregister aufgeteilt in zwei 8 Bit Register. Außerdem bietet er mehr Funktionalität als die 8-Bit Timer, betrachten wir seine beiden Kontrollregister TCR1A und TCR1B werden wir feststellen dass seine Inbetriebnahme aber analog zu den 8-Bit Timern geschieht.
TCCR1A
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
COM1A1 | COM1A0 | COM1B1 | COM1B0 | FOC1A | FOC1B | WGM11 | WGM10 | |
Read/Write | R/W | R/W | R/W | R/W | W | W | R/W | R/W |
Initial Value | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
TCCR1B
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
ICNC1 | ICES1 | - | WGM13 | WGM12 | CS12 | CS11 | CS10 | |
Read/Write | W | R/W | R/W | R/W | R/W | R/W | R/W | R/W |
Initial Value | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Der Start des Timers und die Wahl des Prescalers erfolgt wie bekannt durch die drei Clock-Select-Bits, CS10, CS11 und CS12
Waveform Generation Mode: Die Bits WGM10, WGM11, WGM12 und WGM13 des 16-Bit Timers
Der Betriebsmodus des Timers wird beim Timer1 durch die 4 Bits WGM13, WGM12, WGM11 und WGM10 bestimmt
Compare Match Output Mode: Die Bits COM1A0 & COM1A1, COM1B0 und COM1B1 des 16-Bit Timers
Timerinterrupts und das Timer/Counter Interrupt Mask Register - TIMSK
Bei der seriellen Kommunikation mit dem UART des AVR haben wir bereits einen Interrupt des AVR kennengelernt und die Funktion von Interrupts kurz beschrieben. Bei einem bestimmten Ereignis, oben war es immer dann, wenn im UART ein Zeichen korrekt empfangen wurde, wird ein so genannter Interrupt ausgelöst und wir können in einer Interruptroutine darauf reagieren. Auch die Timer der AVR bieten uns eine Menge nützlicher Interrupts.
Adresse | Interruptname | Beschreibung |
0x003 | TIMER2_COMP | Timer/Counter2 Compare Match |
0x004 | TIMER2_OVF | Timer/Counter2 Overflow |
0x005 | TIMER1_CAPT | Timer/Counter1 Capture Event |
0x006 | TIMER1_COMPA | Timer/Counter1 Compare Match A |
0x007 | TIMER1_COMPB | Timer/Counter1 Compare Match B |
0x008 | TIMER1_OVF | Timer/Counter1 Overflow |
0x009 | TIMER0_OVF | Timer/Counter0 Overflow |
Das Timer/Counter Interrupt Mask Register - TIMSK
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
OCIE2 | TOIE2 | TICIE1 | OCIE1A | OCIE1B | TOIE1 | . | TOIE0 | |
Read/Write | R/W | R/W | R/W | R/W | R/W | R/W | R/W | R/W |
Initial Value | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
In den folgenden praktischen Beispielen wollen wir anhand einfacher Codebeispiele die verschiedenen Betriebsmodi und die gängigen Interrupts der Timer eines AVR kennenlernen. Beginnen wir mit dem einfachsten aller Fälle, dem Timer0 im Normal Mode, dabei werden wir den Overflow Interrupt kennenlernen, welchen man auch bei jedem anderen Timer finden kann.
Dieses Programm soll zeigen, wie man den Timer0 im Normal Mode konfiguriert und auf einen seiner Interrupts, hier dem Overflow Interrupt reagiert wird. Ich habe dazu einen ATMega8 mit einer internen Taktfrequenz von 8MHz, verwendet.
#include <avr/io.h> #include <avr/interrupt.h> int main(void) { // Timer0 initialisieren TCCR0 = (1<<CS02)|(1<<CS00); // (1) Prescaler 1024 TIMSK |= (1<<TOIE0); // (2) Overflow Interrupt erlauben sei(); // (3) Globale Interrupts ein
DDRB |= (1 << PB1); // (4) Pins PB1 als Ausgang definieren
while(1) { // (5) Programm-Endlosschleife } } ISR (TIMER0_OVF_vect) { // Interrupt alle (8000000MHz/1024)/256 Hz = 30,5175Hz // bzw. 1/30,5175s = 32,8ms PORTB ^= (1 << PB1); // (6) Ausgang PB1 toggeln } |
Zum Programm: Nach dem Einbinden der beiden Standard- Includes avr/io.h und avr/interrupt.h aus der avr-gcc Library folgt auch schon die eigentliche Mainfunktion “int main(void“ des Programms. Diese startet auch direkt mit der Initialisierung des Timer0.
(1) Wie wir wissen, muss man zum Starten des Timers und wählen des Prescalers die richtigen Clock-Select-Bits im Kontrollregister des Timers setzten. Wir initialisieren den Timer hier im Beispiel mit einem Prescaler=1024. Aus dem Datenblatt, oder obiger Tabelle, kann man entnehmen, dass dazu die beiden Bits CS00 und CS02 im Kontrollregister TCCR0 gesetzt werden müssen:
TCCR0 = (1<<CS02)|(1<<CS00); // (1) Prescaler 1024 |
Ab nun läuft der Timer und zählt dabei sein Timer/Counter Register hoch. Mit einer Frequenz von 8Mhz/1024, also mit 7812,5Hz immer von 0 bis 256 und wieder von vorn. Für einen Schritt wird dabei also 1/7812,5Hz = 0,128ms benötigt. Allgemein ist die Frequenz des Timers also immer gleich dem Quotient aus Taktfrequenz des AVR und dem eingestellten Prescaler.
(2)+(3) Nun wollen wir den Overflow Interrupt des Timers einschalten. Dieser wird jedesmal ausgelöst, wenn der Timer überläuft, also seinen höchsten Wert, hier 255 erreicht und wieder bei 0 anfängt zu zählen. Dem Datenblatt des ATMega8 kann man entnehmen, dass man diesen Interrupt aktiviert, indem man das Bit TOIE0 im Timer/Counter Interrupt Mask Register, TIMSK setzt. Natürlich müssen für die korrekte Funktion des Interrupts, diese auch wieder durch die Funktion sei() global erlaubt sein. Beides erreicht man mit den folgenden Codezeilen:
TIMSK |= (1<<TOIE0); // (2) Overflow Interrupt erlauben sei(); // (3) Globale Interrupts ein |
Dieser Overflow Interrupt wird von nun an, bei den insgesamt 256 Schritten des Timers, rechnerisch mit einer Frequenz von 7812,5Hz/256 = 30,52Hz erfolgen, also alle 32,8ms ausgelöst.
(4) Zur Kontrolle der Funktion des Programms wollen wir einfach bei jedem Timerüberlauf einen als Ausgang geschalteten Pin des AVR toggeln. Dieser Pin muss also noch als Ausgang initialisiert werden, was in der folgenden Zeile geschieht.
DDRB |= (1 << PB1); // (4) Pins PB.1 als Ausgang definieren |
(5) In der Programm- Endlosschleife “while(1){}”, dem eigentlichen Hauptprogramm passiert gar nichts. Das Hauptprogramm wird von nun an aber bei jedem Overflow Interrupt des Timer0 für die Interruptroutine ISR (TIMER0_OVF_vect) zur Abhandlung dieses Ereignisses unterbrochen.
(6) Die Interruptroutine für den Timerüberlauf von Timer0 finden wir dann nach der Hauptfunktion des Programms. In Ihr passiert hier nichts weiter, als dass der Pin PB.1 des AVR getoggelt, also abwechselnd An- und Aus geschaltet wird.
ISR (TIMER0_OVF_vect) { // Interrupt alle (8000000MHz/1024)/256 Hz = 30,5175Hz // bzw. 1/30,5175s = 32,8ms PORTB ^= (1 << PB1); // (6) Ausgang PB.1 toggeln } |
Dieses Programm zeigt eine einfache Möglichkeit bestimmte Funktionen und Operationen, hier im Beispiel das toggeln eines Portpins, in regelmäßigen Zeitabständen, mit Hilfe eines Timers ausführen zu lassen. Die Periode und Frequenz des Timers und des Overflow Interrupts hängt dabei von der Taktrate des AVR, der Bitbreite des Timers und dem eingestellten Prescaler ab
Ftimer= Fclk / N
Fovfl= Fclk/ N x TCNTn
Timer0 bei 8MHz
Clock-Select-Bits CS02 CS01 CS00 | Prescaler | Timertakt | Periodendauer Timer | Overflow-Takt | Periodendauer Overflow |
0 0 1 | 1 | 8 000 000 Hz | 0,125 µs | 31 250 Hz | 32 µs |
0 1 0 | 8 | 1 000 000 Hz | 1 µs | 3906,25 Hz | 256 µs |
0 1 1 | 64 | 125 000 Hz | 8 µs | 488,28 Hz | 2,05ms |
1 0 0 | 256 | 31 250 Hz | 32 µs | 122,07 Hz | 8,19 ms |
1 0 1 | 1024 | 7 812,5 Hz | 128 µs | 30,55 Hz | 32,77ms |
Timer0 bei 16MHz
Clock-Select-Bits CS02 CS01 CS00 | Prescaler | Timertakt | Periodendauer Timer | Overflow-Takt | Periodendauer Overflow |
0 0 1 | 1 | 16 000 000 Hz | 0,0625 µs | 62,5 kHz | 16 µs |
0 1 0 | 8 | 2 000 000 Hz | 2 µs | ca 8kHz | 128 µs |
0 1 1 | 64 | 250 000 Hz | 4 µs | ca 1 kHz | ca 1 ms |
1 0 0 | 256 | 62 500 Hz | 16 µs | ca. 244 Hz | ca, 4 ms |
1 0 1 | 1024 | 15 625 Hz | 64 µs | ca 60 Hz | ca 16 ms |
Will man dabei eine hohe Genauigkeit der Timer erreichen, ist man dabei auf eine ausreichend hohe Genauigkeit der Taktfrequenz des AVR angewiesen. Verwendet man den internen RC- Oszillator eines AVR muss hierzu eine Besonderheit beachtet werden.
Ein AVR kann mit seinem internen RC- Oszillator bei 1, 2, 4 oder 8MHz betrieben werden. Dies wird über die Fusebits eingestellt. Der AVR ist werksseitig auf einen internen RC- Oszillator von 1MHz voreingestellt und laut Datenblatt wird bei jedem Reset, also auch Neustart des Prozessors das Calibration Byte für 1MHz in das Register OSCCAL geladen. Diese Calibration Bytes werden werksseitig festgelegt und sind zwar für alle 4 möglichen Taktfrequenzen, also auch für die Werte 2, 4 und 8MHz in der Signaturreihe des AVR hinterlegt, nur leider wird bei Verwendung des internen RC- Oszillator bei den AVR der ATMega- Reihe, egal mit welcher Taktfrequenz wir sie letztlich betreiben, immer automatisch das Calibration Byte für 1MHz in das Register OSCCAL geladen. Dies führt laut Datenblatt insgesamt zu einer Abweichung bei den möglichen Taktfrequenzen von bis zu +/-3% bei 25° und 5V.
Messen wir nun einmal das tatsächliche Timing unseres Programms mit einem Oszilloskop oder Frequenzzähler an PB.1. In unserem Beispiel mit 8MHz Taktfrequenz und einem Prescaler = 1024 müsste an ihm ein Rechtecksignal wie oben dargestellt mit einer Ein- und Ausschaltzeit von rechnerisch 32,8ms zu messen sein.
Messwerte | theoretische Werte | Abweichung |
F = 31,43Hz T = 31,81ms | F = 30,52Hz T = 32,77ms | 2,92% |
Messwerte bei einem AVR, bei 5V, 20° und internem RC Oszillator von 8MHz ohne Anpassung von OSCCAL
Wie man sieht ist die gemessene Abweichung, hier bei 8MHz, schon bei ca.3%. Verwendet man eine andere Taktfrequenz als die werksseitig eingestellten 1MHz und will die Genauigkeit des internen RC-Oszillators noch erhöhen, muss dazu das passende Calibration Byte, aus der Signaturreihe des AVR, in das Programmregister OSCCAL geladen werden. Das Datenblatt verspricht unter Anwendung dieser Methode dann eine Genauigkeit der Taktfrequenz von +/-1% bei 25° und 5V.
Dazu müssen wir das richtige Calibration Byte aber zuerst mal aus der Signaturreihe des verwendeten AVR auslesen, da es sie sich von Controller zu Controller unterscheiden kann. Ich benutze dafür unter Windows das AVR Studio 4, da das Studio ab Version 5 das Auslesen der Calibration Bytes leider nicht mehr unterstützt.
Wie man sieht, hat das werksseitig festgelegte Calibration Byte für den hier verwendeten AVR, für eine Taktfrequenz von 8MHz mit Verwendung des internen RC-Oszillators, den Wert 0xAB. Diesen Wert laden wir nun also nur noch im Programmcode in das Register OSCCAL. Wir machen das gleich zu Beginn des eigentlichen Programms, noch vor der Initialisierung des Timers.
Das Programm wurde also nur um den Befehl OSCCAL=0xAB; erweitert und sieht nun folgendermaßen aus:
#include <avr/io.h> #include <avr/interrupt.h> int main(void) { OSCCAL=0xAB; // Calibration-Byte für intern 8 MHz // Timer0 initialisieren TCCR0 = (1<<CS02)|(1<<CS00); // (1) Prescaler 1024 TIMSK |= (1<<TOIE0); // (2) Overflow Interrupt erlauben sei(); // (3) Globale Interrupts ein
DDRB |= (1 << PB1); // (4) Pins PB1 als Ausgang definieren
while(1) { // (5) Programm-Endlosschleife } } ISR (TIMER0_OVF_vect) { // Interrupt alle (8000000MHz/1024)/256 Hz = 30,5175Hz // bzw. 1/30,5175s = 32,8ms PORTB ^= (1 << PB1)| (1 << PB2); // (6) Ausgang PB1 toggeln } |
Messen wir nun wieder das Timing unseres Programms mit einem Oszilloskop oder Frequenzzähler an PB.1, diesmal mit angepassten Register OSCCAL, so stellen wir fest, dass sich die Genauigkeit des Timers wesentlich erhöht hat:
Messwerte | theoretische Werte | Abweichung |
F = 30,51Hz T = 32,78ms | F = 30,52Hz T = 32,77ms | 0,03% |
Messwerte bei einem AVR, bei 5V, 20° und internem RC Oszillator von 8MHz mit Anpassung von OSCCAL
Mit dem Register OSCCAL kann die Taktfrequenz des internen RC-Oszillator auch noch exakter kalibriert werden, das wäre dann aber auch bei weitem aufwendiger und soll hier nicht das Thema sein.
Wie man im obigen Beispiel sieht, ist die Timerfrequenz und damit auch die Frequenz mit der der Overflow Interrupt auftritt recht starr, nur von der Taktfrequenz des AVR und dem Prescaler abhängig. Eine Möglichkeit die Frequenzen und Perioden feiner anzupassen besteht darin, den Zählerstand des Registers TCNT0 nach einem Overflow auf einen Wert größer null zu setzten, quasi
vorzustellen. Somit bleiben bis zum nächsten Überlauf weniger Schritte und die Frequenz des Timer Overflow würde sich erhöhen. Da das eigentliche Timer/Counter Register TCNT0 vollen Schreib- und Lesezugriff bietet können wir so auch im Normal Mode die Frequenz des Timer Overflows anpassen.
TCNT0 = Fclk / Fovfl x N
Im Programm machen wir dazu mal folgendes, wir stellen den Zähler des Timers, also das Registers TCNT0, in der Overflow Interruptroutine auf einen bestimmten Wert vor, so dass alle folgenden Overflow Interrupts mit einer exakten Frequenz von 50Hz, also mit 20ms Periodendauer auftreten sollen. Nach obiger Gleichung müsste unser Timer also bei 8MHz Taktfrequenz des AVR, einer gewünschten Frequenz von 50Hz und einem Prescaler = 1024 nur auf 156 Schritte zählen. Da der Timer0 im Normal Mode aber immer bis zu einer festen Obergrenze TOP = 255 zählt, müssen wir den Timer also um 255-156 = 99 Schritte vorstellen.
#include <avr/io.h> #include <avr/interrupt.h> int main(void) { OSCCAL=0xAB; // Calibration-Byte für intern 8 MHz // Timer0 initialisieren TCCR0 = (1<<CS02)|(1<<CS00); // (1) Start T0 mit Prescaler 1024 TIMSK |= (1<<TOIE0); // (2) Overflow Interrupt erlauben sei(); // (3) Globale Interrupts ein
DDRB |= (1 << PB1); // (4) Pin PB1 als Ausgang definieren
while(1) { // (5) Programm-Endlosschleife } } ISR (TIMER0_OVF_vect) { // Interrupt alle (8000000MHz/1024)/256 Hz = 30,5175Hz // bzw. 1/30,5175s = 32,8ms
TCNT0 = 99; // (*) Timer Overflow alle 20ms PORTB ^= (1 << PB1); // (7) Ausgang PB1 toggeln } |
|
| ||||||||||
|
| ||||||||||
Download: | xxxxx.zip |
Modernere AVRs kennen allerdings noch einen eleganteren Weg zur Anpassung der Interrupt-Frequenz: den CTC Modus
Noch kein Inhalt
Im Zuge dieses Abschnitts werden wir die Ansteuerung von Modellbauservos mittels eines Pulsweitenmodulierten Signals, kurz PWM- Signal, kennenlernen. Für einen Servocontroller, als erstes Slave-Projekt für die Modulare Steuerung, habe ich mich entschieden, weil es in der Robotik viele denkbare Anwendungsmöglichkeiten für Modelbauservos gibt. Nebst den Schrittmotoren wird man in vielen Robotikanwendungen immer wieder auf Modellbauservos stoßen. Diese Stellmotoren sind verhältnismäßig leicht anzusteuern, da sie die für den Betrieb nötige Regel- und Leistungelektronik bereits integriert haben. Die Ansteuerung erfolgt dabei aber bei allen Modellbauservos gleich, über ein PWM- Signal.
Die Position gängiger Servos wird dabei über einen Impuls mit bestimmter Länge, im Bereich zwischen 1 bis 2ms bei einer Wiederholrate von z.B. 20ms gesteuert, also bei einer Frequenz von 50Hz. Die Wiederholrate des PWM-Signals kann aber generell zwischen 10 bis 30ms liegen. Entscheidender für die Genauigkeit des Servos ist dabei das so genannte Tastverhältnis des PWM-Signals, also die möglichst genaue Erzeugung des variablen Impulses mit einer Ein- und Ausschaltzeit im Bereich zwischen 1 und 2ms, bei einer ausreichend hohen Auflösung.
Wir müssen mit unserem AVR, zur Ansteuerung eines Modellbauservos, also ein Rechtecksignal mit einer variablen Ein- und Ausschaltzeit und mit hohen Auflösung im Bereich zwischen 1-2ms, bei einer festen Grundfrequenz von 20ms erzeugen. Bei einem Blick ins Datenblatt und mit dem Wissen aus Kapitel zu den Timern eines AVR sieht man, dass sich dafür die Timerbausteine eines AVR anbieten. Die Timer eines AVR bieten verschiedene Möglichkeiten zur Erzeugung von PWM- Signalen, reines Hardware- PWM ist dabei die bequemste Lösung mit dem geringsten Programmieraufwand. Man ist dann aber, je nach verwendetem AVR, auf eine durch die Hardware begrenzte Anzahl von PWM- Kanälen beschränkt. Will man eine größere Anzahl Modellbauservos gleichzeitig ansteuern kann man die PWM- Signale aber auch an den Portpins des AVR per Soft- PWM erzeugen, auch hierzu nutzt man die Timer eines AVR.
Zuerst wollen wir uns anschauen, wie man mit einem AVR auf ganz einfache Weise, mithilfe des Timer1 im Fast PWM Mode, bis zu zwei Modellbauservos gleichzeitig per reinem Hardware-PWM ansteuern kann. Dieser 16 Bit Timer steht auch allen kleinen AVR, z.B. dem ATMega8 oder den ATinys zur Verfügung. Die 16 Bit Auflösung des Timer1 ermöglichen uns die bequemste und einfachste Art Modellbauservos mit hoher Auflösung anzusteuern, indem wir dazu reines Hardware- PWM benutzen. Der dafür nötige Code wird sich dann auf wenige Zeilen beschränken. Ist der Timer1 erst mal richtig konfiguriert, kann die Ansteuerung der Servos dann nämlich ganz einfach, über das Schreiben von Werten in die richtigen Register des AVR erfolgen. Die Erzeugung der PWM- Signale wird uns dann komplett von der Hardware des AVR abgenommen und stehen dann an den beiden Timerausgängen OCRA und OCRB des AVR zur Verfügung. Der Nachteil dabei ist aber, dass an Timer1 so, ohne weiteren Aufwand, maximal zwei Servos gleichzeitig betrieben werden können. Zudem ist dann der, oft einzige 16 Bit Timer des AVR vergeben, und kann keine weiteren Aufgaben mehr übernehmen.
Ein einfacher Versuchsaufbau zum Betrieb von zwei Modellbauservos an einem ATMega8 mit dem 16 Bit Timer1
Ein einfaches C- Programm, dass die bequeme Art der Ansteuerung von bis zu zwei Modellbauservos im Fast PWM Mode mit dem 16 Bit Timer1 eines AVR demonstriert:
|
| ||||||||||||||
|
| ||||||||||||||
Download: | Servo_Timer1_Testprogramm.zip |
Im Programm wird zunächst der Timer1 für den Fast PWM Mode konfiguriert und im Register ICR eine neue Obergrenze eingestellt, bis zu der der Timer nun, statt auf TOP, zählen wird. Damit legt man die Wiederholrate von 20ms bei 50Hz fest. Durch schreiben eines Wertes in die Vergleichsregister OCR1A und OCR1B zwischen 1000 (ganz Links) und 2000 (ganz Rechts) können die Servos nun in eine beliebiege Position, in theoretisch 1000 Schritten, gesteuert werden.
DIAGRAMM FAST PWM OCR1A/OCR1B
In diesem Testprogramm werden, in einer Endlosschleife, die beiden Register einfach immer um eins bis zu ihrem Maximum hoch-und anschließend wieder heruntergezählt, die angeschlossenen Servos bewegen sich somit zwischen ihren Endstellungen hin- und her.
STRUCT SERVO_TIMER1_TESTPROGRAMM
Zieht man in Betracht, zusätzliche Servos direkt an den weiteren Timern Timer0 und Timer2 zu betreiben, wird man feststellen, dass reines Hardware-PWM z.B. im Fast PWM Mode bei 8 Bit einen gravierenden Nachteil hat, man wird so keine hohe Auflösung bei der Positionierung der Modellbauservos erreichen. Will man mehrere Modellbauservos gleichzeitig bei großer Auflösung betreiben muss man einen anderen Weg einschlagen, den, des so genannten Soft-PWM. Die Erzeugung der Signale erfolgt dann zwar auf der Zeitbasis der Timer, aber an beliebigen Portpins des AVR. Geschickt programmiert lassen sich so auch mehrere Servos gleichzeitig ansteuern, prinzipiell gibt es dazu verschiedene Lösungsansätze, wir wollen hier folgende Idee verfolgen,:
Dabei können bis zu 8 Servos gleichzeitig, an einem Port des AVR betrieben werden. Folgende Grafik soll die Funktion des Programms verdeutlichen:
Wahl der Timer und deren Betriebsarten
Für die Grundfrequenz von 50Hz wird Timer0 benutzt. Da die Software auch auf einem ATMega8 laufen soll, und dessen Timer0 keinen CTC Mode unterstützt wird der Timer0 im Normal Mode betrieben. Die Frequenz des genutzten Overflow Interrupts von Timer0 wird dabei durch setzten seines Timer/Counter Registers TCCNT0 in der Interruptroutine auf exakt 50Hz angepasst.
Um Timer0 im Normal Mode zu starten müssen wir die Clock- Select- Bits im Kontrollregister TCNT0 entsprechend des gewählten Prescalers setzten:
TCCR0 = (1<<CS02)|(1<<CS00); // (1) Prescaler 1024 |
Da wir den Overflow Interrupt des Timer0 nutzen wollen, müssen wir nun noch das entsprechende Bit TOIE0 im Timer Status Register TIMSK setzten und Interrupts global erlauben:
TIMSK |= (1<<TOIE0); // (2) Overflow Interrupt erlauben sei(); // (3) Globale Interrupts ein |
Schon wird die Interruptroutine für den Timer Overflow des Timer0 bei jedem Überlauf aufgerufen, damit dies alle 20ms geschieht, passen wir in ihr noch das Timer/Counter Register TCNT0 entsprechend an:
ISR (TIMER0_OVF_vect) { TCNT0 = 0x63; // (6) Timer-Overflow alle 20ms } |
In dieser Interruptroutine, sollen nun hintereinander die Impulse zur Ansteuerung der 8 Servos erzeugt werden. Dazu wird der 16 Bit Timer1 im CTC Mode verwendet. Schauen wir dazu zuerst den Ablauf für einen einzelnen Servoimpuls an. Wir schalten dazu in der Overflow Interruptroutine des Timer0 einen Pin ein, starten dann den Timer1 im CTC Mode. Sein Vergleichsregister OCR1A wird so gesetzt, dass dieses nach einer bestimmten Zeit, hier 1,5ms für die Mittelstellung eines Servos, erreicht wird. Anschließend warten wir nur noch bis das Bit OCF1A für das Compare Match im Register TIFR gesetzt ist, schalten dann den Pin und Timer1 wieder aus und setzten das Bit OCF1A zurück. Fertig ist der eine Servoimpuls, hier an PD.1, alle 20ms.
ISR (TIMER0_OVF_vect) { TCNT0 = 0x63; // Timer-Overflow alle 20ms // 1 Servoimpuls erzeugen PORTD |= (1 << PD1); // Ausgang PD.1 einschalten TCCR1B |= (1<<WGM12)|(1<<CS11); // Timer1 einschalten, CTC Mode, Prescaler 8 OCR1A = 1500; // Servoposition zwischen 1000(1ms)-2000(ms), 1500 Mitte while (!(TIFR & (1<<OCF1A))) // Solange Bit für Compare Match Timer1 nicht gesetzt { //Warte bis Servoimpulslänge erreicht } PORTD = 0x00; // PortD = 0, alle Pins ausschalten TCCR1B = 0x00; // Timer1 aus TIFR|=(1<<OCF1A); // Clear flag } |
Um nun 8 Servoimpulse in Folge, an den Pins 0-7 des PortD zu erzeugen packen wir die Befehle für die Erzeugung eines Signals in eine For-Next-Schleife:
ISR (TIMER0_OVF_vect) { TCNT0 = 0x63; // Timer0-Overflow alle 20ms // 8 Servoimpulse erzeugen uint8_t i; // Hilfsvariable für For-Next-Schleife For (i=0;i<8;i++) { PORTD |= (1 << i); // Ausgang PD.i einschalten TCCR1B |= (1<<WGM12)|(1<<CS11); // Timer1 einschalten, CTC Mode, Prescaler 8 OCR1A = 1500; // Servoposition zwischen 1000(1ms)-2000(ms), 1500 Mitte while (!(TIFR & (1<<OCF1A))) // Solange Bit für Compare Match Timer1 nicht gesetzt { //Warte bis Servoimpulslänge erreicht } PORTD = 0x00; // PortD = 0, alle Pins ausschalten TCCR1B = 0x00; // Timer1 aus TIFR|=(1<<OCF1A); // Clear flag } } |
Hier das komplette Codebeispiel für die Erzeugung von 8 PWM-Signalen, mit einer Grundfrequenz von 50Hz, an den Pins 0-7 des PortD eines ATMega8 (getaktet mit seinem internem RC- Oszillator bei 8MHz), mit Hilfe der beiden Timer Timer0 und Timer1. Hier im Codebeispiel wird nacheinander, ca. alle 20ms, auf allen 8 Kanälen derselbe PWM- Impuls von 1500µs = 1,5ms Länge, also für die Servomittelstellung erzeugt. Die Stellung aller 8 Servos wird hierzu durch den Wert des Vergleichsregisters OCR1A des Timer1 bestimmt. Ein Schritt entspricht dabei einer Impulslänge von 1µs. Für ein Signal von 1500µs=1,5ms für die Servomittelstellung also OCR1A =1500. Für den für Modellbauservos gängigen Impuls zwischen 1-2ms (0-90°) haben wir also eine Auflösung von 1000 Schritten.
#include <avr/io.h> #include <avr/interrupt.h> int main(void) { OSCCAL=0xAB; // Calibration-Byte 8 MHz, ausgelesen mit AVR Studio 4 DDRD = 0xFF; // PortD alle 8 Bit als Ausgänge definieren // Timer0 initialisieren TCCR0 = (1<<CS02)|(1<<CS00); // Timer0: Prescaler 1024 TIMSK |= (1<<TOIE0); // Timer0: Overflow Interrupt erlauben sei(); // Globale Interrupts ein
while(1) { // Programm-Endlosschleife } } ISR (TIMER0_OVF_vect) { TCNT0 = 0x63; // Timer0-Overflow alle 20ms // 8 Servoimpulse erzeugen uint8_t i; // Hilfsvariable für For-Next-Schleife For (i=0;i<8;i++) { PORTD |= (1 << i); // Ausgang PD.i einschalten TCCR1B |= (1<<WGM12)|(1<<CS11); // Timer1 einschalten, CTC Mode, Prescaler 8 OCR1A = 1500; // Servoposition zwischen 1000(1ms)-2000(ms), 1500 Mitte while (!(TIFR & (1<<OCF1A))) // Solange Bit für Compare Match Timer1 nicht gesetzt { //Warte bis Servoimpulslänge erreicht } PORTD = 0x00; // PortD = 0, alle Pins ausschalten TCCR1B = 0x00; // Timer1 aus TIFR|=(1<<OCF1A); // Clear flag } } |
Platzhalter: Code 05a ATMega8_Servocontroller
http://de.wikipedia.org/wiki/Raspberry_Pi
Die Spezifikationen des verwendeten Rasberry Pi Model B zusammengefasst:
|
|
Offizielle Seite der Raspberry Pi Foundation: http://www.raspberrypi.org
Inzwischen gibt es 6 offizielle von der Raspberry Pi Foundation unterstützte Betriebssysteme. Diese sind, immer in der aktuellsten Version, direkt erhältlich unter: http://www.raspberrypi.org/downloads/
Anfänger finden hier einen guten, ersten Überblick über die offiziell unterstützten Betriebssysteme:
http://www.raspberry-pi-geek.de/Magazin/2013/05/Distributionen-fuer-den-Raspberry-Pi-im-Ueberblick
Im Netz sind natürlich inzwischen auch viele weitere freie Betriebssysteme für den Raspberry Pi auf unterschiedlichster Basis, meist auch Linux, zu finden. Als Anfänger, gerade als unerfahrener Linux User, sollte man am Anfang aber erst mal bei den offiziell unterstützten Betriebssystemen bleiben. Gerade Rasbpian, ein OS auf Basis der aktuellsten Debianversion “Wheezy”, ist für Anfänger am ehesten geeignet. Es lässt sich einfach Menügeführt konfigurieren und bootet direkt in eine graphische GUI (LXDE), Raspbian ist schon mit viel zusätzlicher Software ausgestattet. Damit kann auch ein unerfahrener Linuxuser direkt mit seinem Raspberry Pi loslegen um das Gerät kennen zu lernen.
Die bisher offiziell unterstützen Betriebssysteme für den Raspberry Pi zusammengefasst:
OS | Basis/ Derivat | Link |
Raspbian | Debian Wheezy | |
Pidora | Fedora Remix | |
OPENELEC | XMBC Mediacentre | |
RASPBMC | XMBC Mediacentre | |
RISC OS | Einziges offizielles OS nicht auf Linuxbasis | |
Arch Linux | ARCH Linux ARM |
Ich benutze zur Zeit RASPBIAN, eine Debian Wheezy Distribution oder ArchLinux ARM eine ArchLinux Distribution für den Raspberry Pi. Die zur Zeit der Dokumentation aktuellsten Versionen sind beides Hardfloat-Varianten vom Januar 2014 bzw. Mai 2014. Die folgenden Anleitungen beziehen sich, soweit nicht anders erwähnt, auf diese Distributionen. Solange nicht anders erwähnt reicht eine Grundinstallation des Betriebssystems mit den korrekten Ländereinstellungen und einer eingerichtete Internetverbindung.
Im Kapitel Toolchain der Dokumentation gehe ich näher auf die beiden Betriebssysteme Raspian und ArchLinux ARM ein.
Die SD-Karte ist für den Raspberry das Pendant zur Festplatte eines Desktop-PC. Sie beherbergt also auch das Betriebssystems des Raspberry und muss vor Gebrauch formatiert werden. Hier zeige ich je eine mögliche Art, die SD-Karte unter Windows und unter Linux zu formatieren, und das Image mit dem Betriebssystem auf die Karte zu schreiben.
Windows
SD-Karte formatieren | Image auf SD schreiben | |
Zum formatieren der SD benutze ich unter Windows7(64Bit) SDFormatter V4.0. Ein Tool der SD Association, auf deren Internetseite können auch für andere Windows-Versionen passende Varianten und auch eine Version für den Mac heruntergeladen werden. | Zum schreiben eines Images eines Betriebssystems auf die SD benutze ich unter Windows7(64Bit) dann das Tool Win32DiskImager. Ein Freeware-Programm unter der GNU-GPL. |
Linux
SD-Karte formattieren | Image auf SD schreiben | |
Unter Linux verwende ich das Tool Gparted bzw. dessen Komandozeilenversion Parted um die SD- Karte zu formatieren (FAT). | http://developer-blog.net/administration/raspberry-pi-image-auf-sd-karte-kopieren/ |
Der Raspberry Pi wäre für viele Bastlerprojekte nur halb so interessant, wenn er nicht neben seinen Standardschnittstellen wie beispielsweise dem HDMI- oder USB-Port noch den so genannten GPIO-Port zur Verfügung hätte. Durch diesen 26-poligen zweireihigen Pfostenstecker stehen dem Anwender mehrere digitale I/Os, I2C, SPI, und eine serielle Schnittstelle auf dem Raspberry zur Verfügung.
GPIO Raspberry Pi Modell B (Revision 2.0)
In diesem Kapitel zeige ich an einem einfachen Beispiel wie man die serielle Schnittstelle des Raspberry Pi nutzen kann. Dazu werde ich zuerst, analog zum Kapitel Der UART der AVR zeigen, wie man die serielle Schnittstelle des RPi mit der RS232-Schnittstelle eines PC hardwareseitig verbindet. Danach werde ich darauf eingehen, wie man spezifisch unter Raspian, Archlinux und Pidora Zugriff auf die RS232-Schnittstelle des Raspberry Pi erhält.
Zuerst sollte man wissen, dass der RPi intern mit einer Betriebsspannung von 3,3V betrieben wird, somit haben auch die beiden für die serielle Kommunikation nötigen Signale TXD und RXD an der GPIO- Schnittstelle des RPi diesen Pegel. Um also auf den RS232- Pegel des PC zu kommen benötigen wir wieder, analog zu den AVR, eine Schaltung als Pegelwandler. Der MAX232, den wir bei bei den AVR verwendet haben, ist dazu aber leider ungeeignet, er arbeitet nur bei 5V korrekt. Doch zum Glück gibt es eine, sogar zum MAX232 pin- kompatible Lösung in Form des ICs MAX3232. Dieser Pegelwandler arbeitet auch mit einer Betriebsspannung von 3,3V um RS232-kompatible Pegel für die serielle Kommunikation mit einem PC zu erzeugen.
Die verwendete Schaltung gleicht bis auf das hier verwendete IC MAX3232 und der unterschiedlichen Kapazität der 4 als Chargepump verwendeten Kondensatoren C1-C4 genau der, die wir bereits im Kapitel Der UART der AVR angewendet haben:
Schaltung für die Verbindung des Raspberry Pi mit der seriellen Schnittstelle des PC mittels Pegelwandler MAX3232
Mit Hilfe dieser Schaltung lässt sich die Verbindung zwischen RPi und dem PC über RS232 mit einem Nullmodemkabelherstellen.
Um den GPIO- Port des RPi bequem verdrahten zu können kann man ein gekauftes Breakout-Kit verwenden:
Um die GPIO- Schnittstelle des RPi zu nutzen kann man zum Beispiel, wie ich in diesem Fall die T-Platine eines T-Cobbler GPIO Breakout Kit verwenden. Diese wird einfach mittels eines 26poligen Flachbandkabels mit dem RPi verbunden. Die benötigten Signale TXD und RXD und die benötigte Betriebsspannung von 3,3V für die Schaltung sind so leicht zugänglich. |
RS232 unter Raspbian einrichten
Hardwareseitig ist die Verbindung zwischen RPi und PC nun also hergestellt, kümmern wir uns um die Softwareseitige Verbindung. Damit man unter RASPBIAN die serielle Schnittstelle des Raspberry Pi nutzen kann müssen systemseitig noch zwei kleine Anpassungen vorgenommen werden.
RASPBIAN ist standardmäßig so konfiguriert, dass der gesamte Bootvorgang über die serielle Schnittstelle protokolliert werden kann. Um das abzustellen, muss man in der Datei ‘/boot/cmdline.txt’ folgende Änderung vorzunehmen:
In der Datei ‘/boot/cmdline.txt’ nach folgender Zeile suchen, dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait Und den rot markierten Teil in dieser Zeile löschen. Die Zeile sieht dann so aus: dwc_otg.lpm_enable=0 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait |
Zu dem ist in den Grundeinstellungen von RASPBIAN die serielle Schnittstelle als Login-Schnittstelle definiert, also bereits von diesem Prozess belegt. Um das abzustellen muss man in der Datei ‘/etc/inittab’ folgende Änderungen vornehmen:
In der Datei ‘/etc/inittab’ nach folgender Zeile suchen: T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100gr und auskommentieren: #T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100 |
RS232 unter ArchLinux ARM und Pidora einrichten
Auch Archlinux ARM und Pidora für den Raspberry Pi sind standardmäßig so konfiguriert, dass der gesamte Bootvorgang über die serielle Schnittstelle protokolliert werden kann. Um das abzustellen, muss man in der Datei ‘/boot/cmdline.txt’ folgende Änderung vorzunehmen:
In der Datei ‘/boot/cmdline.txt’ nach folgendem Text suchen, console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 und löschen. |
Bei der von mir verwendeten Version von ArchLinux ARM vom Mai 2014 für den Raspberry Pi war Getty nicht oder falsch konfiguriert, jedenfalls konnte nach obiger Änderung in der /boot/cmdline.txt die serielle Schnittstelle sofort benutzt werden ohne von Getty blockiert zu sein.
Allegemein: Um die serielle Schnittstelle unter Linux nutzen zu können, muss der jeweilige User auch die nötige Berechtigung für die Gruppen ‘dialout’ oder ‘tty’ haben. Beim User Pi unter RASPBIAN ist das standardmäßig der Fall. Prüfen lässt sich das mit dem Befehl ‘groups username’, der alle Gruppen des Users ‘username’ auflistet. Mit dem Befehl ‘sudo usermod -a -G tty username‘ bzw. ‘sudo usermod -a -G dialout username‘kann man dann z.B. einen User ‘username’ der Gruppe ‘tty’ bzw. ‘dialout’ hinzufügen. Alternativ kann man auch mit ‘sudo chmod a+rw /dev/ttyAMA0’ die Zugriffsbeschränkung für die serielle Schnittstelle für alle User freigeben.
Nun steht einem nichts mehr im Weg, für die Verbindung eines PC mit dem RPi mittels RS232, verwendet wird dazu wieder ein RS232- Verbindungskabel, also kein Nullmodemkabel. Ich habe das ganze getestet, indem ich sowohl auf dem verbundenen PC und dem Raspberry als Terminalprogramm Putty verwende. An beiden Rechnern wird dann mit Putty eine Verbindung mit denselben Parametern (z.B. 19200 Baud 8N1) geöffnet und man sollte problemlos Daten über die RS232 in beide Richtungen im geöffneten Terminal senden und empfangen können.
Versuchsaufbau zur Ansteuerung eines MD49 mit dem Raspberry Pi, frei nach einem Beispielprojekt von http://www.robot-electronics.co.uk/.
Bevor wir die serielle Ansteuerung des MD49 Softwareseitig realisieren, müssen wir zuerst den MD49 an den GPIO-Port des Raspberry Pi nach folgendem Schema anschließen:
Auch hier ist wieder zu beachten, dass der Raspberry Pi und das MD49-Board mit unterschiedlichen RS232-Pegeln arbeiten. Beim Raspberry sind es 3,3V, während der MD49 mit 5V-TTL Pegeln arbeitet. Wir können aber auf eine aufwendige Schaltung in diesem Fall verzichten: Die Sendeleitung (TX) des Raspberry kann direkt mit der Empfangsleitung (RX) des MD49 verbunden werden. Da die 3,3V des Raspberry ausreichen um ein High-Pegel am MD49 zu erzeugen. Um aber den 3,3V- Eingang (RX) des Raspberry gegen Überspannung zu schützen, legen wir die Sendeleitung (TX) des MD49 über einen Spannungsteiler an die Empfangsleitung (RX) des Raspberry.
Danach müssen wir folgenden C-Code auf dem Raspberry Pi zum laufen bekommen:
/* |
Ich habe dazu die Datei MD49.c in das Verzeichnis /home/pi/MD49, also in einen Ordner MD49 im Homeverzeichnis des Users pi, kopiert.
Nun ins Verzeichnis mit dem Quellcode wechseln und diesen mit gcc in eine ausführbare Datei MD49 compilieren:
cd /home/pi/MD49 gcc MD49.c -o MD49 |
Danach können wir den compilierten Code starten indem wir die Datei MD49 mit folgendem Befehl ausführen:
./MD49 |
Wurde alles richtig ausgeführt, sollte der angeschlossene Motor/die angeschlossenen Motoren sich eine bestimmte Anzahl Umdrehungen Vor und anschließend wieder Zurück zu drehen.
Raspian ist ein Betriebssystem basierend auf Debian Wheezy, angepasst für den Raspberry Pi. Gerade für Linux- Neulinge ist Raspbian die einsteigerfreundlichste Linuxdistribution für den Raspberry. Die aktuellste Version kann auf http://www.raspberrypi.org herunterladen werden. Das zur Zeit der Erstellung der Dokumentation aktuellste Image war vom September 2014 (2014-09-09-wheezy-raspbian). Von Haus aus ist bei Raspian sehr viel nützliche Software vorinstalliert (SSH-Server, LXDE Desktop, Python und vieles mehr) und die Einrichtung des Betriebssystems geht mit dem Tool Raspi-Config einfach von statten.
Im folgenden werde ich ein paar nützliche Kniffe vorstellen und zeigen wie ein paar zusätzliche Tools unter dem Betriebssystem Raspian eingerichtet werden können.
Das aktuelIe Image wurde bei mir im Zuge der Einrichtung von Qt5 für den RPi mittels Crosscompile-Toolchain auf einem Hostrechner (Ubuntu 14.04.1/12.04.5 in einer VM) erstellt. Nachdem Qt5 wie hier beschrieben erfolgreich auf dem Host und dem neuen Image erstellt wurde, hat man das Basisimage der jeweils aktuellsten Raspbian Version mit einer selbst kompilierten Version von Qt5 auf einer SD-Karte.
Beim ersten Bootvorgang startet Raspi-Config. Hier zuerst das Filesystem an die Größe der verwendete SD-Karte anpassen und alle länderspeziffischen Einstellungen vornehmen.
in etc/hosts und etc/hostname in Pi-on-Robot ändern
sudo nano /etc/hosts
sudo nano /etc/hostname
t
o
d
o
Unter Raspbian ist das WLAN am einfachsten mit dem grafischen Tool WPA-Suplicant eingerichtet das nach der Installation von Raspbian als Verknüpfung auf dem Desktop liegt.
Um die Stromsparfunktion des Edimax-Treibers zu deaktivieren, andernfalls wird die Verbindung bei längerer Inaktivität unterbrochen, muss eine Konfigurationsdatei für den Treiber angelegt werden:
sudo nano /etc/modprobe.d/8192cu.conf
hier folgende Zeile einfügen, speichern und den RPi neu starten:
options 8192cu rtw_power_mgnt=0 rtw_enusbss=0
Der Dateiname, hier Fall 8192cu.conf ist abhängig vom vorhandenen Treiber der mit dem Befehl lsusb mit angezeigt wurde und kann gegebenenfalls abweichen.
config.txt bearbeiten
# Set stdv mode to PAL (as used in Europe)
sdtv_mode=2
# Defines the aspect ratio for composite output to 4:3
# sdtv_aspect=1
sudo apt-get install tightvncserver
Mit LAMP ist ein Softwarepaket unter Linux zu verstehen, bestehend aus den Komponenten Apache Webserver, MySQL und PHP. Es wird genutzt, um dynamische Webseiten und -Anwendungen zu entwickeln und zur Verfügung zu stellen:
Betriebssystem | Linux |
Webserver | Apache |
Datenbank | MySQL |
Programmiersprache | PHP |
Ich beschreibe hier, wie man das Paket auf dem Raspberry Pi unter dem Betriebssystem Raspbian installiert.
Um auch die aktuellsten Versionen zu installieren frischen wir unseren Paketmanager erst mal mit den neuesten Informationen auf:
sudo apt-get update
Jetzt können wir die nötigen Softwarepakete für den Betrieb eines Apache-Webservers mit MySQL und PHP installieren:
sudo apt-get install apache2
sudo apt-get install mysql-server
sudo apt-get install php5
sudo apt-get install php5-mysql
Bemerkung: Bei der Installation des Pakets mysql-server wird man nach dem root- Passwort für mySQL gefragt, dieses wird später auch bei D
Nach einem Neustart starten die installierten Services dann auch automatisch:
sudo reboot
Erster Test
Wir können nun testen ob und welche Ports zum jeweiligen Service zugeteilt wurden:
netstat -ntl
Dieses Kommando erzeugt bei mir folgende Ausgabe, aus der sich schließen lässt das alles korrekt installiert und gestartet wurde. Der Webserver lauscht auf den TCP-Port 80, MySQL auf 3306 und der vorher bereits vorhandene SSH-Server auf Port 22:
Aktive Internetverbindungen (Nur Server) Proto Recv-Q Send-Q Local Adress Foreign Address State tcp 0 0.0.0.0.0:22 0.0.0.0.* LISTEN tcp 0 0.127.0.0.1:3306 0.0.0.0.* LISTEN tcp 0 0.0.0.0.0:80 0.0.0.0.* LISTEN |
Zugriff vom lokalen Netzwerk aus:
Befinden wir uns hinter einem Router im DHCP-Netzwerk können wir nun in einem beliebigen Browser auf einem unserer Rechner im Heimnetzwerk testen ob der Apache Webserver auch wirklich auf dem Raspberry läuft. Wir geben dazu einfach die lokale IP des Raspberry in die Adresszeile des Browsers ein. Dieser sollte dann diese vorinstallierte Seite des Apache Webservers anzeigen:
Zugriff aus dem Internet:
Will man den Raspberry Pi nun auch von einem beliebigen Rechner im Internet, also nicht nur vom lokalen Heimnetzwerk aus, ansprechen, so muss man dazu am Router eine so genannte NAT-Regel erstellen, um eine Portweiterleitung einzurichten. In meinem Fall für den Port 80 sieht das ganze bei meinem Speedport 732V Typ B so aus:
Ist diese Regel gespeichert und aktiv ist der Webserver auch über das Internet ansprech- aber so natürlich auch leichter angreifbar. Man kann den Webserver nun von jedem beliebigen Rechner der mit dem Internet verbunden ist unter seiner WAN-Adresse, der dynamischen IP-Adresse über die unser Router mit dem Internet verbunden ist, erreichen. Diese kann man auf Seiten wie www.meineip.de oder www.whatsmyip.org ermitteln.
Wichtig: Wenn man, wie ich, einen Speedport des oben genannten Typs verwendet kann man die Funktion der Weiterleitung nicht aus dem lokalen Netzwerk heraus testen, da dieser Router kein NAT-Loopback ermöglicht und Anfragen aus dem Netzwerk an die eigene WAN-IP-Adresse verwirft. Man muss das also von außerhalb, z.B. per Mobilfunk oder einem anderen DSL-Anschluss aus testen.
PHP Server testen
Um die Funktion des PHP-Servers zu testen erstellen wir eine kleine php-Datei mit folgendem Inhalt:
<?php phpinfo(); ?> |
Diese Datei speichern wir z.b. unter dem Namen ‘phpinfo.php’ und legen sie in den Dokumentenordner [/var/www] des Apache-Webservers ab. Jetzt können wir die Datei im Browser aufrufen indem wir die Adresse zur php-Datei in die Adresszeile des Browsers eingeben. Sofern die Installation korrekt war erhält man dann die folgende Ausgabe im Browser, die durch den Befehl ‘phpinfo()’ im Script erzeugt wird:
Der Befehl ‘phpinfo()’ gibt eine große Anzahl von Informationen über den aktuellen Zustand von PHP aus. Dies umfasst Informationen wie die PHP-Version, Server-Informationen und -Umgebung, die PHP-Umgebung selbst, Informationen zum Betriebssystem, Pfade, und einiges mehr.
PHPMyAdmin installieren
MySQL- Datenbanken können zwar auch von der Kommandozeile aus verwaltet werden, komfortabler ist aber die Verwendung von PHPMyAdmin. Die Administration erfolgt dann bequem über den Browser per HTTP. Installiert wird das Tool mit:
sudo apt-get install phpmyadmin |
Während der Installation werden wir zuerst nach dem verwendeten Webserver gefragt, hier Apache2 auswählen. Die darauf folgende Frage, “Konfigurieren der Datenbank mit dbconfig-common?”, beantworten wir mit Ja. Dann müssen wir das zuvor bei der Installation von MySQL angegebene Passwort für den root-User angeben und danach noch ein neues Passwort für PHPMyAdmin selbst auszuwählen. Ist die Installation abgeschlossen können wir von einem beliebigen Rechner im Netzwerk auf den Webserver zugreifen und das Tool PHPMyAdmin im Browser starten:
Webserver installieren:
sudo apt-get install nginx
und starten:
sudo /etc/init.d/nginx start
PHP und SQLite-Unterstützung installieren:
sudo apt-get install php5-fpm php5-sqlite
sudo nano /etc/nginx/sites-available/default
hier folgende Zeile auskommentieren:
listen 80; ## listen for ipv4; ...
und index.php dieser Zeile hinzufügen, so dass sie so aussieht:
index index.php index.html index.htm
und folgende Zeilen auskommentieren:
location ~ \.php$ {
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
}
Nginx neustarten, und PHP sollte laufen:
sudo /etc/init.d/nginx reload
PHP testen:
cd /usr/share/nginx/www
sudo nano index.php
<?php
phpinfo();
?>
SQLite3 installieren:
sudo apt-get install sqlite3
sudo apt-get install libsqlite3-dev
phpLiteAdmin installieren:
https://code.google.com/p/phpliteadmin/
pw admin
phpliteadmin.php in Verzeichnis /usr/share/nginx/www kopieren
Benutzer für Webverzeichnis ändern für Zugriff:
sudo chown www-data.www-data /usr/share/nginx/www/
phpliteadmin starten im Browser:
192.168.2.107/phpliteadmin.php
Anderer User statt www-data für nginx web-folder
siehe Aufbau
wenn bei ssh wegen hardwarewechsel WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
ssh-keygen -R *ip_address_or_hostname*
Während man weniger aufwendigere und als geübter Programmierer vielleicht auch aufwendige Aufgabenstellungen eigentlich auch problemlos direkt auf dem Raspberry Pi selbst programmieren kann, kann diese Angelegenheit aber auch zeitraubend und nervtötend sein. Gerade wenn man nebenher viele Fenster geöffnet hat (PDF-Files, Browser, etc.), wartet man sehr oft auf die CPU des RPi. Eine Lösung kann hier der Einsatz einer so genannten Cross-Compiler-Toolchain sein. Der Quellcode wird dann einfach auf einem leistungstärkeren Rechner (Host) erstellt, aber für ein anderes Zielsystem, hier den RPi, kompatibel kompiliert.
Hier zeige ich, wie man sich eine Crosscompiler-Toolchain einrichten kann, mit der man bequem, von einem leistungsstarken Desktop PC aus, unter Linux (hier Debian Wheezy und Ubuntu), unseren Raspberry Pi in C/C++ programmieren kann. Als Entwicklungsumgebung auf dem PC wird dazu die leistungsstarke Open-source Software Eclipse-IDE benutzt.
Ich habe diese Crosscompiler-Toolchain unter Ubuntu 12.04.5,14.04.1 und unter Debian Wheezy getestet.
Vorraussetzungen auf dem Raspberry Pi
Crosscompiler-Toolchain unter Debian/Ubuntu einrichten
Auf dem Hostrechner, also nicht dem Raspberry Pi, richten wir zuerst die Crosscompiler-Toolchain ein:
#Install the native build tools on your PC along with the GIT tool #which will be used to download / clone the cross compiling toolchain from GitHub.com: sudo apt-get install build-essential git #Create a “rpi” directory in your home directory and switch to it cd ~/ mkdir rpi cd rpi #download (clone) Raspbian’s official cross compiling toolchain from Github: sudo git clone git://github.com/raspberrypi/tools.git |
Damit wir den gewünschten Compiler von nun an auch aus der Kommandozeile aufrufen können, ohne dazu immer erst in dessen Verzeichnis wechseln zu müssen, fügen wir den Pfad zum Compiler der PATH-Variablen des jeweiligen Users hinzu. Unter Debian können wir die PATH-Variable für alle User und root am einfachsten in /etc/login.defs anpassen. Unter Ubuntu editieren wir dazu einfach die .bashrc
DEBIAN WHEEZY
#Die Datei in Nano öffnen nano /etc/login.defs #und den beiden Variablen ENV_SUPATH und ENV_PATH den Pfad zum Compiler, bei mir /home/scheik/rpi/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin anhängen. #Der relevante Ausschnitt aus der Datei /etc/login.defs # # *REQUIRED* The default PATH settings, for superuser and normal users. # # (they are minimal, add the rest in the shell startup files) ENV_SUPATH PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/scheik/rpi/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin ENV_PATH PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/home/scheik/rpi/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin testen: arm-linux-gnueabihf-gcc -v |
UBUNTU
#.bashrc mit Nano öffnen cd ~/ sudo nano .bashrc am Ende der Datei folgende Zeile einfügen auf einem 32bit System export PATH=$PATH:$HOME/rpi/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin auf einem 64bit System export PATH=$PATH:$HOME/rpi/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin reload .bashrc source .bashrc testen: arm-linux-gnueabihf-gcc -v |
C++ Projekt in Eclipse für den RPi erstellen
Hier nun die Anleitung, um ein neues C/C++- Projekt in Eclipse für den RPi auf dem Hostrechner anzulegen:
Damit das compilierte Programm nicht immer händisch auf den RPi zu transferiert werden muss, habe ich dafür ein Skript geschrieben, dass automatisch nach dem kompilieren aufgerufen wird und die kompilierte, also ausführbare Datei per SSH vom PC auf den RPi in den Ordner /home/pi/projects kopiert. Dazu muss aber zuerst noch eine Komponente installiert werden, damit beim Transfer der Datei per Befehl scp das Passwort für die Verbindung automatisch übergeben werden kann:
sudo apt-get install sshpass
Das Skript transfer.sh habe ich nun mit folgendem Inhalt im Homeverzeichnis des Users scheik erstellt:
#!/bin/bash PASSWD="xxxxxxxxx" RPIIP="192.168.2.107" USER="pi" sshpass -p "$PASSWD" scp $1 "$USER"@"$RPIIP":$2 |
Im Skript die drei Variablen PASSWD, RPIIP und USER anpassen. Dann das Skript ausführbar machen mit:
sudo chmod +x transfer.sh
Nun müssen wir nur noch in Eclipse einstellen, dass das Skript immer nach dem kompilieren aufgerufen wird, dazu unter Project->Prosperties->C/C++Build->Settings->Tab Build Steps im Feld Command: bei Post-build steps einfügen:
/home/scheik/transfer.sh $(OutDir)${ProjName} /home/pi/projects/${ProjName} |
Jetzt wird nach jedem Build die erfolgreich kompilierte, ausführbare Datei direkt per SSH auf den RP in das Verzeichnis /home/pi/projects übertragen.
Eclipse aus dem Debian Repository installieren
Wir können Eclipse unter Linux am einfachsten mit folgendem Befehl direkt aus dem offiziellen Repository installieren:
#Eclipse aus dem Standard-Repository installieren: sudo apt-get install eclipse-cdt |
Da Debian & Ubuntu aber eher eine Distribution ist, die auf Stabilität setzt, als immer zwingend jedes Paket auf dem neuesten Stand zu haben, wurde bei mir zum Zeitpunkt der Erstellung der Dokumentation Eclipse Juno (3.8) installiert, während Eclipse Luna (4.4) die zur Zeit der Dokumentation aktuellste Version war. Will man mit einer neueren Version von Eclipse arbeiten kann man wie im folgenden beschrieben vorgehen.
Eclipse-CDT Luna (4.4) unter Debian installieren
Die zur Zeit der Erstellung der Dokumentation aktuellste Version von Eclipse war Luna (4.4) und lässt sich leider nicht ohne Umwege unter Debian installieren, da Eclipse Luna Java 1.7 benötigt. Nachdem Oracle aber leider seit 1.7 die Java-Lizenzierung geändert hat, ist das derzeit letzte Java-Paket für Debian die Version 1.6.0_32, welches noch unter der alten Lizenz veröffentlicht wurde.
Um Java 1.7 auf Debian (beim mir x64) zu installieren, muss dazu das Java Development Kit 7(JDK7) als Archiv von Oracle heruntergeladen und installiert werden. Bei mir war das die Archivdatei jdk_7u67-linux-x64.tar.gz von hier.
Anschliessend erstellen wir noch die Ordner /usr/local/lib64/jvm und /usr/local/lib/jvm.
Danach können wir den Download entpacken:
tar zxvf jdk-7u67-linux-x64.tar.gz -C /usr/local/lib64/jvm
Dann zwei Symlinks anlegen:
ln -s /usr/local/lib64/jvm/jdk1.7.0_45 /usr/local/lib64/jvm/jdk1.7
ln -s /usr/local/lib64/jvm/jdk1.7 /usr/local/lib/jvm/jdk1.7
Schlieslich:
update-alternatives --install /usr/bin/java java /usr/local/lib/jvm/jdk1.7/jre/bin/java 64
update-alternatives --install /usr/bin/javac javac /usr/local/lib/jvm/jdk1.7/bin/javac 64
update-alternatives –config java → /usr/local/lib/jvm/jdk1.7/jre/bin/java wählen
update-alternatives –config javac → /usr/local/lib/jvm/jdk1.7/bin/javac wählen
und Java 7 sollte statt Java 6 unter Debian installiert sein.
Nun das Archiv Eclipse-IDE for C/C++ Developers z.B. Eclipse Luna (4.4) von hier herunterladen und in ein beliebiges Verzeichnis entpacken. Ohne weiteres konnte ich Eclipse so aber noch nicht auf meinem Debian System (LXDE) starten, ich musste Eclipse im GTK2-Mode starten und dazu zuerst die Datei eclipse.ini bearbeiten indem ich die beiden hier rot gekennzeichneten Zeilen an der gezeigten Stelle der Datei einfügte:
-startup plugins/org.eclipse.equinox.launcher_1.3.0.v20140415-2008.jar --launcher.library plugins/org.eclipse.equinox.launcher.gtk.linux.x86_64_1.1.200.v20140603-1326 -product org.eclipse.epp.package.cpp.product --launcher.defaultAction openFile -showsplash org.eclipse.platform --launcher.XXMaxPermSize 256m --launcher.defaultAction openFile --launcher.GTK_version 2 --launcher.appendVmargs -vmargs -Dosgi.requiredJavaVersion=1.7 |
Nun konnte ich Eclipse Luna (4.4) unter Debian Wheezy starten.
Eclipse-CDT Luna (4.4) unter Ubuntu installieren
Unter Ubuntu eine neuere Version von Eclipse, als die in den Repositories enthaltene Version zu installieren geht zugegebenermaßen einfacher vonstatten als unter Debian. Dazu die z.B. 64-Bit Version von hier downloaden und in ein Verzeichnis seiner Wahl entpacken (hier ins Homeverzeichnis des angemeldeten Users):
tar -xvzf eclipse-cpp-luna-R-linux-gtk-x86_64.tar.gz -C ~/
Unter Ubuntu 12.04.5 und 14.04.1 ist standardmäßig kein Java JDK installiert, für Eclipse Luna muss mindestens 1.7 installiert sein:
sudo apt-get install openjdk-7-jdk
sudo update-alternatives --config java (nur wenn vorher schon eine andere Version von Java installiert war nötig) hier dann Java 7 wählen.
Nun ist die Luna Eclipse-CDT IDE auch schon mit Ubuntu(getestet mit 12.05.1 und 14.04.1) nutzbar
Ich habe das folgende auch unter Ubuntu 12.04.5 oder 14.04.1 in einer VM (VirtualBox) unter Windows 7 getestet. Hier dann vor der Installation ‘Geräte’ - ‘Medium mit Gasterweiterungen einlegen’ anwählen und ausführen.
Update System
Install g++
Install the openGL library
Install git
Install ia32-libs to use the 32-bit cross compiler and other tools
Install Qt 5 Native SDK
Install Qt 5.x.x for Linux 64-bit (423 MB). This provides the QtCreator IDE which will be used to write the applications to be run on the raspberry pi (and the option of developing native Linux apps for the host machine). (i tryed qt 5.2.0 and latest version 5.3.1 sucessfully).
Run the installer with read&write permission for writing to the /usr/local directory. Install in this path: /usr/local/Qt5.x.x
Configure QtCreator for Native Building
Add to your default path the path to qtcreator, which should be: /usr/local/Qt5.x.x/Tools/QtCreator/bin/
Now run QtCreator and select the native g++ compiler. Go to Extras/Einstellungen/einstellungen und Ausführung/Compilers. Add a new manual compiler, and name it “GCC Native”. Browse to /usr/bin/g++. Now you should be able to create and run a native hello world program.
Download and unzip latest Raspian imgage on Host
Mount this image
Der Offset kann falls nötig ermittelt werden mit:
$ sudo /sbin/losetup /dev/loop0 raspberry-working-image.img
$ sudo /sbin/fdisk -l /dev/loop0
...
Device Boot Start End Blocks Id System
/dev/loop0p1 8192 122879 57344 c W95 FAT32
/dev/loop0p2 122880 3788799 1832960 83 Linux
...
$ sudo /sbin/losetup -d /dev/loop0
wobei 122880 x 512 = 62914560
Download the Qt5 git source code repository
Initialize the repo
Get the cross-compiler, tools, and install a patch
start the compilation of Qt5 for rpi & host (time to get some coffee no)
We have all the modules installed in the wheezy image, now we can copy it to the sdcard using dd:
(Check the entry point of your sdcard. Mine was /dev/sdb)
Insert SD into Pi and Boot
Setup with raspi-config and set up wlan with WiFi Config in LXDE (startx)
Setting Up Qt Creator on host
You do not need a special build of Qt Creator to build for the Raspberry Pi. Qt Creator uses Kits to select build configurations.
Arduino IDE installieren
sudo apt-get install arduino
Arduino Programmierung per Kommandozeile mit Inotool
Mit Inotool kann man Arduino-Projekte ohne die Arduino IDE (die muss aber installiert sein) unter Linux per Kommandozeile erstellen, kompilieren und übertragen.
Zuerst installieren wir pip, einen Python Package-Installer:
sudo apt-get install python-pip
Nun kann Inotool installiert werden:
sudo pip install ino
sudo apt-get install picocom
Quickstartguide für Inotool
http://inotool.org/quickstart#tweaking-parameters
Neues Beispielprojekt mit Inotool für den Arduino Mega2560 erstellen:
[build] board-model = mega2560 [upload] board-model = mega2560 serial-port = /dev/ttyACM0 [serial] serial-port = /dev/ttyACM0 |
For the full list of board names refer to
Beispielprogramm erstellen, kompilieren und uploaden:
int led = 13; void setup() { Serial.begin(9600); pinMode(led, OUTPUT); } void loop() { Serial.println("High"); digitalWrite(led, HIGH); delay(1000); Serial.println("Low"); digitalWrite(led, LOW); delay(1000); } |
LED an Pin 13 des Arduino müsste nun im Sekundentakt blinken, die serielle Ausgabe kann z.B. mit folgendem Befehl oder in jedem anderen seriellen Terminal getestet werden:
ROSserial mit Arduino
Hier zeige ich einen Weg, mit dem wir Programme auf einem Arduino erstellen können, die direkt als ROS-Node arbeiten, also z.B. direkt Messages publishen oder subscriben, oder Services starten können.
http://wiki.ros.org/rosserial_arduino
http://wiki.ros.org/rosserial_arduino/Tutorials
Dazu müssen wir zuerst einige Komponenten installieren,
Arduino IDE Setup:
sudo apt-get install ros-indigo-rosserial-arduino
sudo apt-get install ros-indigo-rosserial
cd ~/sketchbook/libraries
rm -rf ros_lib
rosrun rosserial_arduino make_libraries.py .
Nach dem Neustart der Arduino-IDE sollte ros_lib unter File -> Examples zu finden sein.
Erster Test mit der Arduino IDE:
http://wiki.ros.org/rosserial_arduino/Tutorials/Hello%20World
Wir können nun in der Arduino IDE z.B. das Beispielprogramm HelloWorld laden, welches als einfacher Chatter fungiert und in einer Endlosschleife eine Message an ROS sendet. Wir laden es dazu einfach per Arduino IDE auf den Controller.
Dann starten wir in einem neuen Terminalfenster den Roscore:
roscore
Als nächstes starten wir die rosserial Anwendung welche die Nachrichten vom Arduino an ROS weiterleitet (Hier den den richtigen seriellen Port angeben!):
rosrun rosserial_python serial_node.py /dev/ttyACM0
Nun können wir uns die ROS-Nachrichten, die der Arduino sendet in einem neuen Terminalfenster anzeigen lassen:
rostopic echo chatter
Arduino Firmware per CMake im ROS-Workspace:
http://wiki.ros.org/rosserial_arduino/Tutorials/CMake
Bei größeren Projekten kann das arbeiten mit der Arduino IDE müßig sein, deshalb zeige ich im hier anhand einfacher Beispiele, wie man Arduino Firmware per CMake kompilieren, und somit in unseren ROS-Workspace integrieren kann. Arduinos können so auch direkt als Nodes für ROS programmiert werden. Beispielcodes dazu sind im ROS-Workspace im Package arduino_examples zu finden.
ROS (Robot Operating System) stellt Bibliotheken und Werkzeuge zur Verfügung, um damit Roboteranwendungen zu erstellen. Visualisierungen, Nachrichtenvermittlung, Paketverwaltung und andere Komponenten sind bereits vorhanden und müssen nicht neu ‘erfunden’ werden. Zudem ist ROS unter der Open Source BSD Lizenz veröffentlicht und sein Inhalt und die Anzahl der Anwender wächst stetig.
Folgendes einfache Setup für ROS stellt die grundsätzliche Funktionsweise von ROS ganz gut dar:
Für unser Setup müssen wir also ROS sowohl auf dem RPi und einer Ubuntu Workstation installieren. Im folgenden beschreibe ich die Installation von ROS Groovy auf dem RPi und der Ubuntu 12.04.5- Workstation:
Use the following steps:
add the repository to your apt sources:
add the apt key:
reload apt sources
install ros packages - ros_comm is a good fit, it has dependencies on roscpp, rospy, and all the core ROS tools, so that they will all be installed.
Now, you have to tell the Pi, where ROS is located and remind him every time, a terminal session is started.
Now you are good to go. Try..
1.) Setup your computer to accept software from packages.ros.org.
sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu precise main" > /etc/apt/sources.list.d/ros-latest.list'
2.) Set up your keys
wget http://packages.ros.org/ros.key -O - | sudo apt-key add -
3.) make sure your Debian package index is up-to-date:
sudo apt-get update
4.) install ROS Groovy
Desktop-Full Install: (Recommended) : ROS, rqt, rviz, robot-generic libraries, 2D/3D simulators, navigation and 2D/3D perception
sudo apt-get install ros-groovy-desktop-full
Desktop Install: ROS, rqt, rviz, and robot-generic libraries
sudo apt-get install ros-groovy-desktop
ROS-Base: (Bare Bones) ROS package, build, and communication libraries. No GUI tools.
sudo apt-get install ros-groovy-ros-base
Individual Package: You can also install a specific ROS package (replace underscores with dashes of the package name):
sudo apt-get install ros-groovy-PACKAGE
e.g.
sudo apt-get install ros-groovy-slam-gmapping
To find available packages, use:
apt-cache search ros-groovy
Before you can use ROS, you will need to initialize rosdep. rosdep enables you to easily install system dependencies for source you want to compile and is required to run some core components in ROS.
sudo rosdep init
rosdep update
It's convenient if the ROS environment variables are automatically added to your bash session every time a new shell is launched:
echo "source /opt/ros/groovy/setup.bash" >> ~/.bashrc
source ~/.bashrc
If you have more than one ROS distribution installed, ~/.bashrc must only source the setup.bash for the version you are currently using.
If you just want to change the environment of your current shell, you can type:
source /opt/ros/groovy/setup.bash
rosinstall is a frequently used command-line tool in ROS that is distributed separately. It enables you to easily download many source trees for ROS packages with one command.
To install this tool on Ubuntu, run:
sudo apt-get install python-rosinstall
to test your installation, please proceed to the ROS Tutorials.
Zusätzliche Pakete:
sudo apt-get install ros-groovy-joystick-drivers
Wir wenden ein gängiges Setup an, indem wir einen Singleboard- Computer (Raspberry Pi) auf dem Roboter und einen stationären Desktop-PC für Monitoringzwecke mit ROS einsetzen. Unter ROS ist es relativ einfach im lokalen Netzwerk von beiden Rechnern aus auf dieselben Services, Topics und Parameter zuzugreifen. Dazu muss natürlich auf beiden Rechnern ROS installiert sein, aber nur auf einem der ROS Master (roscore) gestartet werden. Ich halte es für sinnvoll den ROS-Master dabei auf dem Roboter selbst laufen zu lassen, er soll ja autonom, auch ohne laufende Workstation funktionieren. Auf beiden Rechnern sollte die selbe ROS Version laufen (bei mir derzeit Groovy)
Läuft die Ubuntu-Workstation in einer VM, muss bei dieser in den Einstellungen das Netzwerk auf ‘bridged Network’ eingestellt werden.
Damit ROS im Netzwerkbetrieb reibungslos läuft, sollten beide Rechner zeit-synchronisiert werden. Ein einfacher Weg ist einfach auf beiden Rechnern chrony zu installieren. Ein Tool, das die Uhr des Computers mit einem Server im Internet synchronisiert und somit auf beiden Rechner die Zeit abgleicht:
sudo apt-get install chrony
Um in Zukunft nicht die lokale IP des jeweiligen Rechners verwenden zu müssen nutzen wir zeroconf, auf dem RPi muss dieser Dienst vorher aber noch nachinstalliert werden unter Ubuntu12 ist dieses Paket schon installiert. Ohne dass ein DNS-Server im Netzwerk existiert oder IP-Adressen über DHCP oder manuell zugewiesen wurden, können Rechner so über ihren Rechnernamen angesprochen werden. Dazu wird ein .local an den Rechnernamen gehängt, also beispielsweise rechnername.local. Jeder Dienst des Rechners kann dann über diesen Namen angesprochen werden. Die Namensauflösung über Avahi wird nur bei den Desktop-Installationen von Ubuntu installiert. Bei einem Ubuntu Server lässt sich die Funktionalität über das Paket sudo apt-get install libnss-mdns nachinstallieren.
Mit dem Befehl hostname können dann die Namen der beiden Rechner im Netzwerk ausfindig gemacht werden, bei mir für den Pi ‘Pi-on-Robot’ und für die Workstation ‘Ubuntu12VM-Workstation’ für die VM oder für den Ubuntu Desktop-PC ‘Ubuntu12-Desktop’.
ping Ubuntu12VM-Workstation.local und ping Pi-on-Robot.local sollte dann zeigen dass die Verbindung im Netzwerk hergestellt werden kann.
Nun ein einfaches Beispiel um zu sehen wir ROS im Netzwerk funktioniert:
Auf dem mobilen Gerät (RPi):
roscore
in einem neuen Terminalfenster:
export ROS_IP="Pi-on-Robot.local"
export ROS_HOSTNAME="Pi-on-Robot.local"
export ROS_MASTER_URI=http://raspberrypi:11311
die MASTER_URI kann man im Fenster mit gestartetem roscore finden. Nun lassen wir einen einfachen Listener laufen:
rosrun rospy_tutorials listener.py (zuvor sudo apt-get install ros-groovy-rospy-tutorials, falls nicht installiert.)
Auf der Ubuntu-Workstation (VM):
Zuerst machen wir dem System die MASTER_URI bekannt unter welcher der zuvor gestartete ROS Master läuft:
export ROS_MASTER_URI=http://raspberrypi:11311
Für den reibungslosen Ablauf muss auch auf diesem System die IP und der Hostname des eigenen Rechners dem ROS-System bekannt gemacht werden:
export ROS_IP="Ubuntu12VM-Workstation.local"
export ROS_HOSTNAME="Ubuntu12VM-Workstation.local"
Lassen wir nun einen einfachen Talker auf der Workstation laufen, sollten die von ihm versendeten Nachrichten mit dem Listener auf dem RPi empfangen werden:
rosrun rospy_tutorials talker.py
Wollen wir uns sparen in jedem neuen Terminalfenster die selben 3 Befehle einzugeben, können wir die Daten auch bei jedem Systemstart automatisch zu den globalen Umgebungsvariablen in /etc/environment hinzufügen: (Vielleicht ist ein Launchfile besser?)
Auf der Ubuntu Workstation:
sudo nano /etc/environment
dort folgende Zeilen anhängen
export ROS_IP="Ubuntu12VM-Workstation.local"
export ROS_HOSTNAME="Ubuntu12VM-Workstation.local"
export ROS_MASTER_URI=http://robotOS.local:11311
Auf dem RPi
sudo nano /etc/environment
dort folgende Zeilen anhängen
export ROS_IP="robotOS.local"
export ROS_HOSTNAME="robotOS.local"
export ROS_MASTER_URI=http://robotOS.local:11311
SSH
Standardmässig arbeitet ROS nur mit SSH Hosts die auch im known_hosts file des Systems definiert wurden. Um das zu ändern, weil man z.B. eine Node über das lokale Netzwerk per ROS Launchfile auf einem anderen Rechner starten will, kann man mit:
export ROSLAUNCH_SSH_UNKNOWN=1 dieses Verhalten abschalten. Will man das dauerhaft in jedem neuen Terminal erreichen muss dieser Befehl der ~/.bashrc hinzugefügt werden. Die saubere Lösung ist natürlich die jeweiligen Host in known-hosts zu definieren
Qt wie im Kapitel Die Grundlagen beschrieben auf der Ubuntu-Workstation installieren.
QtCreator muss mit ‘bash -i -c’ gestartet werden damit das shell environment mit den Abhängigkeiten von ROS beim Start der IDE geladen wird. Gilt auch für Eclipse.
Desktop-Verknüpfung für QtCreator
[Desktop Entry] Type=Application Exec= sudo bash -i -c /usr/local/Qt5.3/Tools/QtCreator/bin/qtcreator %F Icon=QtProject-qtcreator Terminal=true Name=Qt Creator GenericName=Integrated Development Environment MimeType=text/x-c++src;text/x-c++hdr;text/x-xsrc;application/x-designer;application/vnd.nokia.qt.qmakeprofile;application/vnd.nokia.xml.qt.resource; Categories=Qt;Development;IDE; |
Ein C++ Package bearbeiten mit dem QtCreator
‘File->Open Project’ und zur CMakeLists.txt des Packages navigieren (/path/to/catkin_ws/src/Package/CMakeLists.txt). QtCreator will nun ein ‘build directory’, dazu einfach das ‘build directory’ im erstellten catkin Workspace angeben (/path/to/catkin_ws/build/). Nun CMake ausführen.
weitere Dependencies: rosconsole, roslib, rostime, std_srvs
helloworld_node.cpp in ~/ROS-Groovy-RPi-Workspace/src/base_controller/src/helloworld erstellen:
// Include the ROS C++ APIs #include <ros/ros.h> // Standard C++ entry point int main(int argc, char** argv) { // Announce this program to the ROS master as a "node" called "hello_world_node" ros::init(argc, argv, "hello_world_node"); // Start the node resource managers (communication, time, etc) ros::start(); // Broadcast a simple log message ROS_INFO_STREAM("Hello, world!"); // Process ROS callbacks until receiving a SIGINT (ctrl-c) ros::spin(); // Stop the node's resources ros::shutdown(); // Exit tranquilly return 0; } |
Kompilieren des gesamten Packages
Dazu sind folgende Schritte notwendig:
Hier sind bisher keine weiteren Einträge in CMakeLists.txt und package.xml nötig, da beim erstellen des Packages die bisher nötigen Dependencies (roscpp, etc.) schon mit angegeben wurden. Prinzipiell gilt aber:
in CMakeLists.txt
find_package(catkin REQUIRED COMPONENTS package-names)
in package.xml
<build_depend>package-name</build_depend>
<run_depend>package-name</run_depend>
in CMakeLists.txt
Prinzipiell müssen alle neue Sourcefiles (Nodes) am Ende der Datei eingefügt werden:
add_executable(executable-name source-files)
target_link_libraries(executable-name ${catkin_LIBRARIES})
Hier also für helloworld.cpp in CMakeLists.txt einfügen:
add_executable(helloworld src/helloworld/helloworld.cpp)
target_link_libraries(helloworld ${catkin_LIBRARIES})
im Workspace- Directory, hier also ~/ROS-Groovy-RPi-Workspace
catkin_make
source devel/setup.bash
Testnode helloworld_node
Nun kann mit gestartetem roscore die Node helloworld getestet werden:
source devel/setup.bash
rosrun base_controller helloworld
Ich habe derzeit den Inhalt meines ROS Catkin Workspace (alles im Ordner /src des Workspaceordners) als Repository auf github.com gespeichert.
Dazu die Datei .gitignore erstellt und commited:
.catkin_workspace build/ devel/ # QtCreator files *.*.user # Compiled Object files *.slo *.lo *.o # Compiled Dynamic libraries *.so *.dylib # Compiled Static libraries *.lai *.la *.a #temporary files *~ # vi stuff .*.sw? # Python stuff *.pyc # Data files *.dat *.csv # Emacs temp files .#* # OSX Files .DS_Store |
Um den Code auf einen anderen Rechner zu klonen folgendermaßen vorgehen:
Test: run roscore, then in another terminal rosrun base_controller helloworld.
sudo apt-get install ros-groovy-joystick-drivers
Den angeschlossenen Joyrick ermitteln mit:
ls /dev/input/
Joyrick testen mit:
sudo jstest /dev/input/js2
in der Liste erscheint dann z.B. js0 oder j2. Nun müssen wir die Berechtigungen für das Gerät prüfen:
ls -l /dev/input/jsX
Ausgabe:
Wenn XX = rw: das Gerät ist richtig konfiguriert
Wenn XX = -- oder r-: das Gerät ist nicht richtig konfiguriert, dann mir:
$ sudo chmod a+rw /dev/input/jsX
To get the joystick data published over ROS we need to start the joy node. First let's tell the joy node which joystick device to use- the default war js0/js2
rosparam set joy_node/dev "/dev/input/jsX"
Now we can start the joy node.
rosrun joy joy_node
Now in a new terminal you can rostopic echo the joy topic to see the data from the joystick:
$ rostopic echo joy
/joy listener example:
#include <ros/ros.h>
#include <sensor_msgs/Joy.h>
void myCallback (const sensor_msgs::Joy::ConstPtr& msg)
{
for (unsigned i = 0; i < msg->axes.size(); ++i) {
ROS_INFO("Axis %d is now at position %f", i, msg->axes[i]);
}
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "listener");
ros::NodeHandle n;
ros::Subscriber sub = n.subscribe("joy", 1000, myCallback);
ros::spin();
return 0;
}
http://wiki.ros.org/ROS/Tutorials/DefiningCustomMessages
http://wiki.ros.org/ROS/Tutorials/CreatingMsgAndSrv#Creating_a_msg
http://wiki.ros.org/rosserial_arduino/Tutorials
Mit dem Arduino Nano sollen auf dem Roboter z.B. folgende Aufgaben erfüllt werden:
Bevor wir uns aber an eine echte Anwendung mit einem Arduino in unserem Roboter machen, will ich hier anhand einfacher Beispiele aufzeigen, wie wir Arduinos als ROS-Nodes programmieren und anwenden können. Welche Schritte also notwendig sind, ein Arduino Programm in unserem Catkin ROS-Workspace zu erstellen, zu kompilieren, es auf den Arduino zu uploaden und per rosserial_node mit ROS zu verbinden.
Dazu wird als Arduino-Firmware zu Demonstrationszwecken zuerst ein einfaches Beispiel erstellt, das einfach im Sekundentakt eine Standardmessage publishen soll. Statt der serial_node aus dem Package rosserial verwenden wir aber eine eigene Node connect_arduino_nano1.py, die wir in unseren eigenen ROS-Workspace im Package arduino_sourcecodes erstellen werden. Es handelt sich dabei eigentlich um die serial_node aus dem offiziellen Package rosserial_python, importiert in unseren Workspace. Das hat den Hintergrund, dass wenn später mehrere Arduino-Plattformen ihren dienst im Roboter tun sollen, mehrere Instanzen dieser Node verwendet werden müssen, dass ließe sich zwar auch verwirklichen, wenn diese Node in verschiedenen Namespaces gestartet werden würde (da nur so dieselbe Node mehrfach gestartet werden kann), der Übersichtlichkeit halber habe ich mich aber für den Weg entschieden für jeden Arduino eine separate serial_node zu verwenden/erstellen.
Example Publisher
Vorraussetzung für die folgenden Schritte ist, dass wie im Kapitel “Betriebssystem (robotOS) für pcDuino einrichten” beschrieben, die Arduino Toolchain installiert wurde.
Schritt 1, Package arduino_examples erstellen:
Schritt 2, Arduino Sourcecode erstellen_
#include <ros.h> |
Schritt 3, CMakeLists.txt Dateien anpassen:
cmake_minimum_required(VERSION 2.8.3) project(arduino_examples) ## Find catkin macros and libraries ## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) ## is used, also find other catkin packages find_package(catkin REQUIRED COMPONENTS rosserial_client std_msgs ) ## System dependencies are found with CMake's conventions # find_package(Boost REQUIRED COMPONENTS system) ## Uncomment this if the package has a setup.py. This macro ensures ## modules and global scripts declared therein get installed ## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html # catkin_python_setup() ################################################ ## Declare ROS messages, services and actions ## ################################################ ## To declare and build messages, services or actions from within this ## package, follow these steps: ## * Let MSG_DEP_SET be the set of packages whose message types you use in ## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...). ## * In the file package.xml: ## * add a build_depend tag for "message_generation" ## * add a build_depend and a run_depend tag for each package in MSG_DEP_SET ## * If MSG_DEP_SET isn't empty the following dependency has been pulled in ## but can be declared for certainty nonetheless: ## * add a run_depend tag for "message_runtime" ## * In this file (CMakeLists.txt): ## * add "message_generation" and every package in MSG_DEP_SET to ## find_package(catkin REQUIRED COMPONENTS ...) ## * add "message_runtime" and every package in MSG_DEP_SET to ## catkin_package(CATKIN_DEPENDS ...) ## * uncomment the add_*_files sections below as needed ## and list every .msg/.srv/.action file to be processed ## * uncomment the generate_messages entry below ## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) ## Generate messages in the 'msg' folder # add_message_files( # FILES # Message1.msg # Message2.msg # ) ## Generate services in the 'srv' folder # add_service_files( # FILES # Service1.srv # Service2.srv # ) ## Generate actions in the 'action' folder # add_action_files( # FILES # Action1.action # Action2.action # ) ## Generate added messages and services with any dependencies listed here # generate_messages( # DEPENDENCIES # std_msgs # ) ################################################ ## Declare ROS dynamic reconfigure parameters ## ################################################ ## To declare and build dynamic reconfigure parameters within this ## package, follow these steps: ## * In the file package.xml: ## * add a build_depend and a run_depend tag for "dynamic_reconfigure" ## * In this file (CMakeLists.txt): ## * add "dynamic_reconfigure" to ## find_package(catkin REQUIRED COMPONENTS ...) ## * uncomment the "generate_dynamic_reconfigure_options" section below ## and list every .cfg file to be processed ## Generate dynamic reconfigure parameters in the 'cfg' folder # generate_dynamic_reconfigure_options( # cfg/DynReconf1.cfg # cfg/DynReconf2.cfg # ) ################################### ## catkin specific configuration ## ################################### ## The catkin_package macro generates cmake config files for your package ## Declare things to be passed to dependent projects ## INCLUDE_DIRS: uncomment this if you package contains header files ## LIBRARIES: libraries you create in this project that dependent projects also need ## CATKIN_DEPENDS: catkin_packages dependent projects also need ## DEPENDS: system dependencies of this project that dependent projects also need catkin_package( # INCLUDE_DIRS include # LIBRARIES arduino_firmware # CATKIN_DEPENDS rosserial_client std_msgs # DEPENDS system_lib ) ########### ## Build ## ########### ## Specify additional locations of header files ## Your package locations should be listed before other locations # include_directories(include) #include_directories( # ${catkin_INCLUDE_DIRS} #) ## Declare a C++ library # add_library(arduino_firmware # src/${PROJECT_NAME}/arduino_firmware.cpp # ) ## Add cmake target dependencies of the library ## as an example, code may need to be generated before libraries ## either from message generation or dynamic reconfigure # add_dependencies(arduino_firmware ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) ## Declare a C++ executable # add_executable(arduino_firmware_node src/arduino_firmware_node.cpp) ## Add cmake target dependencies of the executable ## same as for the library above # add_dependencies(arduino_firmware_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) ## Specify libraries to link a library or executable target against # target_link_libraries(arduino_firmware_node # ${catkin_LIBRARIES} # ) ############# ## Install ## ############# # all install targets should use catkin DESTINATION variables # See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html ## Mark executable scripts (Python etc.) for installation ## in contrast to setup.py, you can choose the destination # install(PROGRAMS # scripts/my_python_script # DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} # ) ## Mark executables and/or libraries for installation # install(TARGETS arduino_firmware arduino_firmware_node # ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} # LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} # RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} # ) ## Mark cpp header files for installation # install(DIRECTORY include/${PROJECT_NAME}/ # DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} # FILES_MATCHING PATTERN "*.h" # PATTERN ".svn" EXCLUDE # ) ## Mark other files for installation (e.g. launch and bag files, etc.) # install(FILES # # myfile1 # # myfile2 # DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} # ) ############# ## Testing ## ############# ## Add gtest based cpp test target and link libraries # catkin_add_gtest(${PROJECT_NAME}-test test/test_arduino_firmware.cpp) # if(TARGET ${PROJECT_NAME}-test) # target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) # endif() ## Add folders to be run by python nosetests # catkin_add_nosetests(test) rosserial_generate_ros_lib( PACKAGE rosserial_arduino SCRIPT make_libraries.py ) rosserial_configure_client( DIRECTORY firmware TOOLCHAIN_FILE ${ROSSERIAL_ARDUINO_TOOLCHAIN} ) rosserial_add_client_target(firmware arduino_example_chatter ALL) rosserial_add_client_target(firmware arduino_example_chatter-upload) |
cmake_minimum_required(VERSION 2.8.3) |
Schritt 4, Arduino Sourcecode kompilieren:
Schritt 5, Programm auf Arduino uploaden:
Schritt 6, Verbinden mit dem Arduino mittels Node connect_arduino_nano1.py aus dem Package arduino_sourcecodes
Dazu wurde das File ~/ROS-Workspace/src/arduino_sourcecodes/src/arduino_serial_nodes/connect_arduino_nano1.py erstellt:
#!/usr/bin/env python import rospy from rosserial_python import SerialClient, RosSerialServer import multiprocessing import sys
if __name__=="__main__": rospy.init_node("serial_node_arduinoNano1") rospy.loginfo("ROS Serial Python Node") port_name = rospy.get_param('~port','/dev/ttyUSB0') baud = int(rospy.get_param('~baud','57600')) # TODO: should these really be global? tcp_portnum = int(rospy.get_param('/rosserial_embeddedlinux/tcp_port', '11411')) fork_server = rospy.get_param('/rosserial_embeddedlinux/fork_server', False) # TODO: do we really want command line params in addition to parameter server params? sys.argv = rospy.myargv(argv=sys.argv) if len(sys.argv) == 2 : port_name = sys.argv[1] if len(sys.argv) == 3 : tcp_portnum = int(sys.argv[2])
if port_name == "tcp" : server = RosSerialServer(tcp_portnum, fork_server) rospy.loginfo("Waiting for socket connections on port %d" % tcp_portnum) try: server.listen() except KeyboardInterrupt: rospy.loginfo("got keyboard interrupt") finally: rospy.loginfo("Shutting down") for process in multiprocessing.active_children(): rospy.loginfo("Shutting down process %r", process) process.terminate() process.join() rospy.loginfo("All done") else : # Use serial port rospy.loginfo("Connecting to %s at %d baud" % (port_name,baud) ) client = SerialClient(port_name, baud) try: client.run() except KeyboardInterrupt: pass |
Es kann dann folgendermaßen gestartet werden:
Schritt 7, Test:
Example Subscriber
Als nächstes wollen wir ein weiteres Beispiel erstellen und unserem bestehenden Package arduino_firmware hinzufügen. Diesmal soll ein einfacher Subscriber erstellt werden.
Ein neues Package muss diesmal nicht erstellt werden, wir fügen das neue Beispiel dem bestehenden Package arduino_sourcecodes hinzu. Dazu erstellen wir zuerst ein neues Sourcefile im Ordner /firmware des Packages. /home/user/ROS-Workspace/src/arduino_sourcecodes/firmware/arduino_example_subscriber.cpp:
/* * rosserial Subscriber Example * Blinks an LED on callback */ #include <ros.h> #include <std_msgs/Empty.h> ros::NodeHandle nh; void messageCb( const std_msgs::Empty& toggle_msg){ digitalWrite(13, HIGH-digitalRead(13)); // blink the led } ros::Subscriber<std_msgs::Empty> sub("toggle_led", &messageCb ); void setup() { pinMode(13, OUTPUT); nh.initNode(); nh.subscribe(sub); } void loop() { nh.spinOnce(); delay(1); } |
Nun muss man die CMakeLists.txt Dateien anpassen, um die neue Quelle für cmake einzubinden:
rosserial_add_client_target(firmware arduino_example_subscriber ALL) rosserial_add_client_target(firmware arduino_example_subscriber-upload) |
generate_arduino_firmware(arduino_example_subscriber |
Jetzt kann wie beim ersten Beispiel schon geschehen der Arduino Sourcecode kompiliert werden:
Das Programm dann wie gehabt auf den Arduino uploaden:
Es kann dann mit derselben serial_node wie im Beispiel mit dem Publisher gestartet werden:
Test:
Die LED am Arduino sollte nun im Sekundentakt blinken
http://wiki.ros.org/rosbridge_suite
http://cs.brown.edu/research/pubs/theses/masters/2012/lee.pdf
http://wiki.ros.org/rosbridge_suite/Tutorials/RunningRosbridge
http://wiki.ros.org/roslibjs/Tutorials/BasicRosFunctionality
http://iguanatronics.com/simple-tutorial-on-rosbridge-and-roslibjs/
http://www.ist.tugraz.at/ais-wiki/roswebuserinterface
http://students.asl.ethz.ch/upl_pdf/289-report.pdf
Die wichtigsten Komponenten für ein Webinterface für den Roboter sind rosbridge_suite und roslibjs.
rosbridge_suite stellt die JSON API für ROS-Funktionalität für nicht ROS Anwendungen bereit, ausgestattet mit einer Vielzahl an Front Ends, welche mit rosbridge interagieren. Unter anderem einem Websocket für Webbrowser.
roslibjs ist eine JavaScript Bibliothek zur Interaktion von ROS mit dem Webbrowser. Sie benutzt WebSockets für die Verbindung mit rosbridge und bietet die Funktionalität für publishing, subscribing, für service calls, URDF parsing und andere essentielle ROS Funktionen.
Falls noch nicht geschehen, installieren wir rosbridge-suite:
Nun kann rosbridge gestartet werden mit:
Dieser Befehl startet rosbridge und generiert ein WebSocket mit dem dafür vorgesehenen Standardport 9090.
Der Port kann auch anders konfiguriert werden indem der ~/port Parameter für ROS per launchfile geändert wird. Dazu habe ich ein launch-File ~/ROS-Workspace/launch/rosbridge_websocket.launch mit folgendem Inhalt erstellt:
<launch> |
Dieses kann man dann folgendermaßen starten, rosbridge wird dann mit Port 9090 gestartet, man kann den Port aber auch anpassen:
Um das ganze zu nutzen schauen wir uns einmal das Beispiel html aus dem Tutorial des ROS-Wikis an:
http://wiki.ros.org/roslibjs/Tutorials/BasicRosFunctionality.
In dieser Datei wird gezeigt, wie man aus einem html-Dokument ROS-Topics subscriben, publishen und Service-Calls aufrufen kann.
Das genannte Beispiel wurde zu Dokumentationszwecken im root-Verzeichnis unseres Webservers auf der Workstation (workstationOS) erstellt: https://github.com/Scheik/ROS-Workspace/data/rosbridge/rosbridge_tutorial.html.
Die für die Funktion nötigen JavaScript- Files (eventemitter2.min.js und roslib.min.js)werden hier lokal aufgerufen, nachdem sie mit wget von http://cdn.robotwebtools.org/EventEmitter2/current/eventemitter2.min.js und
http://cdn.robotwebtools.org/roslibjs/current/roslib.min.js
in den Ordner ~/ROS-Workspace/data/rosbridge/js heruntergeladen wurden.
Test des Beispiels:
Zuerst muss am Router noch eine Portweiterleitung für den verwendeten Port des Websockets (z.B. 9090) eingerichtet werden.
Nun starten wir den Webbrowser (Chrome) und können das Beispiel mit folgender Adresse aufrufen:
http://robotik.ddns.net/rosbridge/rosbridge_tutorial.html
Das ganze setzt natürlich eine vollständige Installation des workstationOS inklusive ROS, NGinx und unseres ROS-Workspace vorraus. In Chrome starten wir die Entwicklertools (Einstellungen -> Weitere Tools -> Entwicklertools) und wechseln dort in die Konsole um die Ausgaben zu sehen.
Ob durch den Aufruf des html-Files auch das an das Topic /cmd_vel gepublished wird kann in der Konsole mit rostopic echo /cmd_vel getestet werden, und ob Messages vom Topic /listener auch subsrcibed werden sehen wir z.B. mit rostopic pub std_msgs/String hello.
Web-UI Design mit jQuery
Live Charts mit http://www.flotcharts.org/
GPS ROS
https://www.youtube.com/watch?v=k5DGWy55SA4
Clone Repository, commit and push changes:
git clone https://github.com/Scheik/ROS-Workspace.git ~/ROS-Workspace
git status
git add .
git commit -m “Message”
git push -u origin master
show branches:
git branch -l
show tags/releases
git tag -l
New branch:
git branch newbranch
git checkout newbranch
oder kürzer:
git checkout -b newbranch
merge branch
git checkout branch_to _merge_into
git merge branch_to_merge_in
git push
delete branch local
git branch -d branch_to_delete
delete remote branch
git push origin :the_remote_branch
Django ist ein Python Webframework welches weit verbreitet und gut dokumentiert ist.
Welche Version von Django ist installiert:
Django installieren:
Neues Django Projekt
Dieser Befehl legt das neue Djangoprojekt, hier namens djangoproject an. Dabei wird der Ordner djangoproject mit folgender Dateistruktur angelegt:
|
Testen wir nun mal, ob das Projekt richtig angelegt wurde. Dazu starten wir mittels manage.py den Development- Server:
Auf dem Rechner lokal sollte nun unter localhost:8080 folgende Seite erscheinen. Im lokalen Netzwerk müsste die selbe Seite unter ip_im _Netzwerk:8080, z.B. 192.168.178.201:8080 laden:
Standardmässig benutzt Django SQLite3 als Datenbankanwendung. Möchte man das ändern, muss man das in der settings.py ändern.
Möglich ist z.B. auch MySQL, Oracle, oder PostgreSQL. Für unser Beispiel belassen wir es bei SQLite, mehr Informationen dazu findet man in der Dokumentation zu settings.py.
Damit nun die in settings.py definierte Datenbank erstmals erzeugt, oder später Änderungen an der Datenbank automatisch vollzogen werden kann folgender Befehl verwendet werden:
Um später das Projekt administrieren zu können legen wir einen so genannten Superuser fest und vergeben eine Email- Adresse und ein Passwort für diesen:
Erstellen wir nun unsere erste App in unserem Djangoprojekt:
Wie man sehen kann wurde somit ein neuer Ordner namens Blogpage mit folgendem Inhalt in unserem Djangoprojekt angelegt:
|
Unsere App Blogpage basiert auf einer Datenbank, in welcher die Inhalte (posts) gespeichert werden. Deswegen definieren wir diese in
/Blogpage/models.py:
from __future__ import unicode_literals from django.db import models from tinymce import models as tinymce_models class posts(models.Model): author = models.CharField(max_length = 40) title = models.CharField(max_length = 200) bodytext = tinymce_models.HTMLField() timestamp = models.DateTimeField() def __unicode__(self): #Python 3 is __str__ return self.title |
In der Klasse posts ist nun die Struktur der Datenbankfelder definiert, für den bodytext verwenden wir den Type HTMLField, dieser ist kein Standardtype von Django, deswegen muss das Package django-tinymce noch installiert werden:
Nun fügen wir in settings.py noch unser App Blogpage und tinymce zu den INSTALLED_APPS hinzu:
...u INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'tinymce', 'Blogpage', ) ... |
und fügen die tinymce-Konfiguration hinzu
... TINYMCE_DEFAULT_CONFIG = { 'theme': "advanced", } |
Da wir nun Änderungen an unserer Datenbank vornehmen führen wir folgenden Befehle aus:
Um Änderungen an der Datenbank vorzunehmen bietet das Django ein Admintool, starten wir dazu zuerst wieder den Entwicklungserver:
Unter http://192.168.178.201:8080/admin/ sollte man nun mit dem Superuseraccount einloggen können. Die Blogposts können aber noch nicht angelegt, bzw. bearbeitet werden. dazu nehmen wir folgende Änderungen an admin.py vor:
from django.contrib import admin from .models import posts class postsAdmin(admin.ModelAdmin): list_display = ["__unicode__", 'timestamp'] fields = ['timestamp', 'author', "title", 'bodytext'] class Meta: model = posts admin.site.register(posts, postsAdmin) |
Unter 192.168.178.201:8080/admin/ sollte nun folgende Seite laden:
Jetzt können wir bequem neue Blogposts anlegen und bereits erstellte Posts editieren.
Um unsere Blogposts später in einer HTML- Datei anzuzeigen müssen wir aber zuerst in views.py definieren, auf welcher Seite die Posts, also das zuvor definierte Model verwendet werden:
from django.shortcuts import render from .models import posts def blogpage(request): entries = posts.objects.all()[:100] return render(request, "blogpage.html",{ 'posts' : entries }) |
In einer neu anzulegenden Datei urls.py im Verzeichnis /Blogpage unserer App mappen wir den View nun zu einer URL:
from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.blogpage, name='blogpage'), ] |
Diese Datei binden wir nun in die Datei urls.py unseres Djangoprojekts ein:
from django.conf.urls import include, url from django.contrib import admin urlpatterns = [ url(r'^Blogpage/', include('Blogpage.urls')), url(r'^admin/', admin.site.urls), ] |
Jetzt legen wir noch die zugehörige HTML-Seite blogpage.html im Ordner /templates unseres Djangoprojekts an:
<!DOCTYPE html> <html> <head> <title> News </title> <meta charset="utf-8"> </head> <!DOCTYPE html> <html> <head> <title> Blog </title> <meta charset="utf-8"> <style> body {font-family: Verdana, sans-serif; font-size:0.8em;} header, nav, section, article, footer {border:1px solid grey; padding:8px;} nav ul {margin:0; padding:0;} nav ul li {display:inline; margin:5px;} </style> </head> <body style="background-color:#000000"> <div class="container" style="width:60%; margin:0 auto;" > <header style="background-color:#adc2eb"> <h1 style="text-align:left">Blogpage</h1> </header> <nav style="background-color:#e5f2ff"> <ul> <li><a href="/admin">Admin</a></li> </ul> </nav> <section style="background-color:#adc2eb"> {% for post in posts %} <article style="background-color:#e5f2ff"> <h2>{{ post.title }}</h2> <h3>Posted on {{ post.timestamp }} by {{ post.author }}</h3> <p>{{ post.bodytext|safe }}</p> </article> {% endfor %} </section> <footer style="background-color:#e5f2ff"> <p>© Fabian Prinzing. Visit my github @ <a href="https://github.com/scheik" target="_blank">https://github.com/scheik</a></p> </footer> </div> </body> </html> |
Damit Django die Seite im Ordner /templates auch findet muss in settings.py noch folgende Änderung(rot) im Abschnitt TEMPLATES vorgenommen werden:
... TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] ... |
Nachdem wir wieder den Development- Server gestartet haben, müsste nun unter http://localhost:8080/Blogpage unsere Seite laden:
Die Seite mit Nginx und uwsgi hosten
Wollen wir die Seite über einen Webserver hosten müssen wir dem Projekt zuerst die Static-Files hinzufügen,
und in settings.py den Pfad zu den Static-Files(rot) angeben:
... STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, "static/") ... |
.
.to
.do
.