Anmerkung zur Installation:
Download der Entwicklungsumgebung von https://code.visualstudio.com/download
Erweiterung PlatformIO installieren
Erstellen einer ATMEGA-Anwendung:
Überprüfen sie zuerst die Funktion und USB Verbindung der Controllerplatine! (Geräte-Manager, COM-Port)
PlatformIO Home auswählen, Button New Project
Programmnamen eingeben
Platine auswählen: „Arduino Nano Atmega328“ oder „Arduino Mega Atmega2560“
für "Arduino-C" Framework Arduino auswählen, für Ansci C diese Auswahl frei lassen
Use default location NICHT auswählen, danach gewünschtes Verzeichnis auswählen
Button Finish
Programmvorlagen aus a.oswald\Atmel\Programmvorlage in die Programmordner kopieren:
code_atmega.c in den Ordner src kopieren
Datei main.cpp aus dem Ordner src löschen
AODEF.h in den Ordner include kopieren
Aktuelles Programm als Arbeitsbereich auswählen (Menüpunkt in der Fußzeile)
Kompilieren
Programm auf den AtMega Hochladen
Achtung:
Arduino Nano-Platine: Atmega328 auswählen, NICHT Variante New Bootloader
Arduino Mega-Platine: Atmega2560 auswählen
Immer aktuelles Programm in der PlatformIO Fußzeile prüfen
Vorlage für Platine ArduinoNano:
[env:nanoatmega328]
platform = atmelavr
board = nanoatmega328
framework = oder framework = arduino (Für Arduino-C Befehle)
Vorlage für Platine ArduinoMega:
[env:megaatmega2560]
platform = atmelavr
board = megaatmega2560
framework = oder framework = arduino (Für Arduino-C Befehle)
Standard bei mehreren Varianten:
[platformio]
default_envs = nanoatmega328
Weitere Optionen:
; Serial Monitor options
monitor_speed = 115200
; Minimalistic printf version
build_flags = -Wl,-u,vfprintf -lprintf_min
;Floating point printf version (requires MATH_LIB = -lm below)
build_flags = -Wl,-u,vfprintf -lprintf_flt -lm
Setzen von Fuse Bits: (Beispiel Mega328 interner Oszillator 8Mhz)
board_fuses.efuse = 0xFF
board_fuses.hfuse = 0xD9
board_fuses.lfuse = 0xE2
Verwenden alternativer Programmer: (Beispiel Programmer USBASP)
upload_flags =
-Pusb
-e
-E reset,novc
Menü Datei(File), Punkt Einstellungen(Preferences): Compiler- und Upload-Meldungen anzeigen aktivieren.
Die Verwendete Hardware wird im Menü Wekzeuge, Punkt Board, eingestellt
Arduino Nano-Platine: Atmega328 auswählen, NICHT Variante New Bootloader
Arduino Mega-Platine: Atmega2560 auswählen
ESP-CAM (für Robobar): AI Thinker ESP32-CAM auswählen
Links und Quellverweis:
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial
http://www.kreatives-chaos.com/artikel/avr
http://www.roboternetz.de/wissen/index.php/Avr-gcc
http://www.pronix.de/pronix-4.html
http://www.atmel.com/products/AVR/overview.asp
avr-libc Standard C library for AVR-GCC https://www.nongnu.org/avr-libc/user-manual/index.html
Datentypen (<inttypes.h>)
Typ Länge Bereich
uint8_t 8 Bit 0 ... 255
int8_t 8 Bit -128 … 127
uint16_t 16 Bit 0 ... 65.535
int16_t 16 Bit -32.768 ... 32.767
uint32_t 32 Bit 0 ... 4.294.967.295
int32_t 32 Bit -2.147.483.648 … 2.147.483.647
float 32 Bit Reelle Zahlen mit ca. 7 Kommastellen
Schreiben von Bits
Ein ganzes Byte kann durch Befüllen mit einer Dezimal-, Binär- oder HEX-Zahl gesetzt werden:
x = 7; // Bitmuster 0000 1001 gesetzt (dezimal)
x = 0x22; // Bitmuster 0010 0010 gesetzt (hexadezimal)
x = 0b01000100; // Bitmuster 0100 0100 gesetzt (binär)
Einzelne Bits setzt man mittels logischer (Bit-) Operationen:
x |= (1 << Bitnummer); // wird ein Bit in x gesetzt (1)
x &= ~(1 << Bitnummer); // wird ein Bit in x geloescht (0)
Setzten mehrerer Bits:
x |= (1<<1) | (1<<4); // BIT 1 und 4 in x gesetzt (1)
x &= ~( (1<<0) | (1<<3) ); // BIT 0 und 3 in x gelöscht (0)
Bit Toggeln (invertieren):
PORTB ^= (1<<0); /* toggle Bit 0, Ex-OR mit Standardschreibweise */
Anwendungsbeispiel: Setzten von Registern:
DDRB |= (1 << 2); // Port B.2 auf OUTPUT (1)
PORTB &= ~(1 << 2); // Port B.2 auf LOW (0)
Bitsstatus abfragen
if ( x & (1<<5) ) /* Fuehre Aktion aus, wenn Bit Nr. 5 in x gesetzt (1) ist */
{
/* Aktion */
}
if ( !(x & (1<<6)) ) /* Fuehre Aktion aus, wenn Bit Nr. 6 in x gelöscht ist */
{
/* Aktion */
}
C-Makros - Zur Vereinfachung der Schreibweise können Befehle durch C-Makros umbenannt werden:
// Setzen und Löschen von Bits als Macro
#define SETBIT1(ADRESS,BIT) (ADRESS |= (1<<BIT))
#define SETBIT0(ADRESS,BIT) (ADRESS &= ~(1<<BIT))
#define SETBITT(ADRESS,BIT) (ADRESS ^= (1<<BIT))
//Macro zur BIT-Abfrage
#define CHECKBIT1(ADRESS,BIT) (ADRESS & (1<<BIT))
#define CHECKBIT0(ADRESS,BIT) (!(ADRESS & (1<<BIT)))
Diese C-Makros stehen in der Vorlage AODEF.h zur Verfügung.
Um diese Makros zu verwenden muss im Kopf der C-Datei folgender include stehen:
#include "AODEF.h"
Umwandlung von Variablen Text:
itoa(i,s,10); (integer to ascii), utoa(i,s,10); (unsigned to ascii),
ultoa(i,s,10); (unsigned long to a.), dtostrf (f, 6, 3, s); (float to a.)
#include <stdlib.h>
uint16_t Zahl=0;
float fZahl=0;
char TextZahl[7];
Zahl=4711;
utoa (Zahl, TextZahl, 10); // Umwandlung Ganzzahl in einen Text
lcd_puts(TextZahl);
fZahl=12.34;
dtostrf (fZahl, 4, 2, TextZahl); // Umwandlung Kommazahl in einen Text
lcd_puts(TextZahl);
Aufbau eines Programm in C
Arduino-C Ansi C
(Auszug aus Datenblatt Atmega328 www.microchip.at, Ports as General Digital I/O )
Eingänge / Inputs
Dienen zum Abfragen
Für Taster, Sensoren …
Pull-UP Widerstand zum Verhindern offener Eingänge nötig (ca 30kΩ)
Ausgänge / Outputs
Werden vom Programm gesetzt
Für LEDs, Transistoren, ...
AtMega: Max DC Current per I/O Pin: 40.0mA
Gesamter erlaubter Strom aller Anschlüsse: 200mA
ESP32: Max DC Current per I/O Pin: 12.0mA
Gesamter erlaubter Strom aller Anschlüsse: 120mA
C-Beispiel mit Digital-Macros:
Port B, Bit 2 auf Datenausgang schalten und auf LOW setzen:
SETBIT1(DDRB, 2); // Port B.2 auf OUTPUT (1)
SETBIT0(PORTB, 2); // Port B.2 auf LOW (0)
Port C, Bit 3 auf Dateneingang schalten und PullUp aktivieren:
SETBIT0(DDRC, 3); // Port C.3 auf INPUT (0)
SETBIT1(PORTC, 3);// PullUp bei Port C.3 aktivieren (1)
Port B, Bit 7 abfragen:
if (CHECKBIT0(PINB,7) ) /* Port B.7 abfragen */
{
/* Aktion */
}
pinMode(2, OUTPUT);// PinNr 2 auf Datenausgang schalten
digitalWrite(2, LOW);/ PinNr 2 auf LOW setzen
pinMode(PinNr, INPUT_PULLUP); //PinNr 4 auf Dateneingang und PullUp
if (digitalRead(PinNr) == LOW)
{
/* Aktion */
}
Für die Kommunikation zwischen PC und Mikrocontroller und besonders zur Fehlersuche in Programmen kann die serielle Schnittstelle verwendet werden:
1.) Übertragungsgeschwindigkeit einstellen und starten (Unterprogramme aus Programmvorlage)
void uart_init(void)
int uart_putchar(char c, FILE *stream)
int uart_getchar(FILE *stream)
2.) Schnittstelle festlegen (Unterprogramme aus Programmvorlage)
uart_init();
fdevopen(uart_putchar, uart_getchar); // Standard-IO festlegen
3.) Senden vom Controller an den PC
printf("PROGRAMMSTART");
4.) Daten Empfangen
if ( CHECKBIT1(UCSR0A, RXC0) ) // Ist etwas angekommen?
{
Zeichen = UDR0; // Das empfangene Zeichen ist im Register UDR
printf("Zeichen %c empfangen!", Zeichen);
}
Einstellungen der Programmvorlage:
Übertragung 9600 Bit/s, 8 Datenbits, 1 Stopbit, keine Parität, keine Flusssteuerung
Ausgabe einer 8 oder 16 bit Zahl ohne Vorzeichen: printf("Zahl: %u ", Variable);
Ausgabe einer 32 bit Zahl: printf("Zahl: %ld ", Variable);
Die Ausgabe von Float-Zahlen durch:
- Umwandlung der Float-Variable in einen Text dtostrf (fZahl, 4, 2, TextZahl);
- Aktivieren der erweiterten printf-Variante in der PlatformIO.ini
Für das Festlegen von Seriellen Kommunikation sind Makros vordefiniert:
1.) Übertragungsgeschwindigkeit einstellen und starten
Serial.begin(9600);
2.) Senden vom Controller an den PC
Serial.print("Hallo Welt");
Serial.println("Hallo Welt");
Serial.write("Hallo Welt")
4.) Daten Empfangen
if (Serial.available())
{
char Zeichen = (char)Serial.read();
Serial.print(">>"); Serial.println(Zeichen);
if (Zeichen=='e')
{
Serial.println("Zeichen e empfangen");
}
}
Mikrocontroller besitzen eine unterschiedliche Anzahl an programmierbaren Timern. Bei den ATmegas sind das 8-Bit Timer und 16-Bit Timer.
Timer funktionieren nach dem allgemeinen Prinzip, dass eine Zahl (im weiteren als Zähler bezeichnet) um 1 erhöht oder um 1 verkleinert wird. Als Taktquelle dafür können externe Pins, der CPU Takt oder der CPU Takt mit Vorteiler (Prescaler) gewählt werden.
Wird eine interne Taktquelle (clk) verwendet, spricht man von einem Timer. Werden externe Pins als Takt genommen, spricht man von einem Zähler (Counter).
Dazu den entsprechenden Pin auf Eingang und ev. Pull Up schalten!
Der Zähler/Timer-Wert ist im Register TCNT0 gespeichert und kann durch Auslesen im Programm verwendet werden. Der Zählerstand kann durch Setzten des Registers auch jederzeit festgelegt werden.
Weiters besteht die Möglichkeit, dass der Timer bei Überlauf einen Interrupt auslösen
Timer sind hier nicht vorgesehen, Zeitsteuerung kann mit der Millis-Funktion gelöst werden
unsigned long startmillis = 0;
unsigned long dauer = 1000;
void setup()
{
Serial.begin(9600);
startmillis = millis();
}
void loop()
{
if (millis() > (startmillis+dauer))
{
startmillis = millis();
Serial.println("Eine Sekunde ist um");
}
}
Analog-Digital-Converter wandeln Spannung in ein Zahl um!
Datei adc.c in das src-Verzeichnis kopieren
Datei adc.h in das include-Verzeichnis kopieren
h-Datei einbinden: #include "adc.h"
ADC-Einstellungen in der Datei adc.c eintragen (Vorteiler, Referenzspannung und Enable)
Verwendeten ADC-Anschluss als Eingang definieren (Register DDR), kein PullUp aktivieren!
Befehl zum Einlesen eines Analogwertes:
uint16_t ADC_Wert=0;
ADC_Wert = ReadChannel(Kanalnummer);
Einstellen der Referenzspannung - Register ADMUX, Bit 7 (REFS1) und Bit 6 (REFS0):
Externe AREF
AVCC als Referenz (5V)
Interne 2,56 Volt
Einstellen des Vorteilers für die ADC Geschwindigkeit: - Register ADCSRA, Bit 0 bis Bit 3:
ADPS2
ADPS1
ADPS0
Teilungsfaktor:
Laut Datenblatt soll die ADC-Frequenz zwischen 50kHz und 200kHz liegen! Der Vorteiler muss also so eingestellt werden, dass die CPU-Taktfrequenz dividiert durch den Teilungsfaktor diesen Bereich ergibt.
Die Funktion analogRead(pinNr) liest den Wert vom angegebenen analogen Pin ein (10 Bit, 0 bis 1023)
Parameter pinNr: Mini, Nano - A0 to A7
Mega, Mega2560 - A0 to A14
void loop() {
int ADCval = 0;
ADCval = analogRead( A3 ); // Pin einlesen
Serial.println(ADCval); // Wert ausgeben
}
Die Funktion analogReference(type) legt die Referenzspannung fest, die Voreinstellung ist Aref=AVcc (5V)
Parameter type: DEFAULT, INTERNAL2V56, oder EXTERNAL
EEPPROMS dienen dazu, Variabelninhalte abzulegen, die auch im abgeschaltenen Zustand erhalten bleiben.
#include <avr/eeprom.h>
//fixe Speicherplätze für das Abspeichern festlegen
#define EE_DUMMY 0x000
#define EE_WERT1 ( EE_DUMMY + sizeof( uint16_t ) )
#define EE_WERT2 ( EE_WERT1 + sizeof( uint16_t ) )
#define EE_WERT3 ( EE_WERT2 + sizeof( uint16_t ) )
#define EE_WERT4 ( EE_WERT3 + sizeof( uint16_t ) )
#define EE_WERT5 ( EE_WERT4 + sizeof( uint16_t ) )
void set_eeprom (void)
{
while(!eeprom_is_ready());
eeprom_write_word(EE_WERT1, 1313);
}
void get_eeprom (void)
{
x1 = 4711; //zum Test mit Dummy-Wert befüllen
while(!eeprom_is_ready());
x1 = eeprom_read_word(EE_WERT1);
}
Anmerkungen zum Anwenden der EEPROM Funktionen:
Der EEPROM-Speicher lässt nur eine begrenzte Anzahl von Schreibzugriffen zu (ca. 100.000), danach wird die Funktion der Zelle nicht mehr garantiert. Bei geschickter Programmierung, wobei die zu beschreibenden Zellen regelmäßig gewechselt werden, kann man eine deutlich höhere Anzahl an Zugriffen erreichen.
Bei Nutzung des EEPROMs ist zu beachten, dass vor dem Zugriff auf diesen Speicher abgefragt wird, ob der Controller die vorherige EEPROM-Operation abgeschlossen hat. Man sollte auch verhindern, dass der Zugriff durch die Abarbeitung einer Interrupt-Routine unterbrochen wird.
Bei Nutzung der Funktionen aus der avr-libc/eeprom.h-Datei ist das gegeben.
Der Inhalt des EEPROMS kann auch mittels Programmieradapter ausgelesen und beschreiben werden. Diese Funktionen könne über das Makefile eingestellt werden.
Der EEPROM-Speicher enthält nach der Übertragung des Programmers mittels ISP abhängig von der Einstellung der EESAVE-Fuse die vorherigen Daten (programmed = 0) oder den Standardwert 0xFF
(unprogrammed = 1). Zur Kontrolle sollten Programme immer Standardwerte enthalten und beim Lesen im EEPROM auf 0xFF prüfen!
Die Funktion EEPROM.write(address, value) schreibt in das EEPROM.
Parameter address: Speicheradresse im EEPROM beginnend mit 0
value: Wert der abgespeichert wird, zwischen 0 und 255 (byte)
Die Funktion EEPROM.read(address) liest aus dem EEPROM aus.
Parameter address: Leseadresse im EEPROM
#include <EEPROM.h>
int addr = 0;
int value;
void setup()
{
Serial.begin(9600);
EEPROM.write(addr, 123); //Schreibt 123 in die Adresse
}
void loop()
{
value = EEPROM.read(addr);
Serial.print("Adresse : ");
Serial.print(addr);
Serial.print(", Wert: ");
Serial.print(value);
Serial.println();
}
Komplexere Inhalte und Datentypen können mit den Funktionen bearbeitet werden.
( siehe https://www.arduino.cc/en/Reference/EEPROM )
EEPROM.put()
EEPROM.get()
EEPROM.update()
Bei der Pulsweitenmodulation (PWM) wird eine elektrischer Spannung abwechselnd Ein/Aus-geschaltet. Dabei wird bei konstanter Frequenz (T) die Einschaltdauer (t1) des Signales verändert.
- Bei t1=0 ist das Signal immer ausgeschaltet,
- bei t1=T ist das Signal immer eingeschaltet.
Das PWM Signal wird an den Controller-Anschlüssen OCx ausgegeben.
Die PWM Erzeugung wird mit der Funktion analogWrite(pin, value) gestartet.
Parameter pin: Arduino-Pin auf den geschrieben werden soll
value: Zykluszeit zwischen 0 (aus) und 255 (immer an)
PWM-Pins und Frequenz:
Uno, Nano, Mini Pins: 3, 5, 6, 9, 10, 11 Frequenz: 490 Hz (Pins 5 und 6: 980 Hz)
Mega Pins: 2 - 13, 44 – 46 Frequenz: 490 Hz (Pins 4 und 13: 980 Hz)
analogWrite(3, 128); //PWM Signal an PIN 3, 50% ein
Ein Servomotor (kurz Servo) ist ein Elektromotor, der eine exakten Winkelposition einnehmen kann. Ein Servomotor besteht aus einem DC-Motor, einem Getriebe und einer Steuerplatine mit Potentiometer.
Die Steuerplatine setzt die Signale in genaue Befehle um und über das Potentiometer wird die Position des Motors überprüft. Die üblichen Servos besitzen drei Pins zur Ansteuerung: GND, VCC und PWM.
Zum Steuern des Servomotors verwendet man ein sich alle 20 Millisekunden wiederholendes Signal.
Es besteht aus einem HIGH-Impuls, der zwischen 1 und 2 Millisekunden lang ist, und einem LOW-Impuls. Die Dauer des HIGH-Impulses bestimmt den Zielwinkel (normalerweise von 0 bis 180°).
digitalWrite(ServoPinNr,HIGH);
delayMicroseconds(1500);
digitalWrite(ServoPinNr,LOW);
delayMicroseconds(18500);
#include <Servo.h> // Servo-Bibliothek einbinden
Servo myservo; // erstellt ein Servo-Objekt
int val;
void setup() {
myservo.attach(9); // verknüpft den Servomotor an Pin 9 am Mikrocontroller
}
void loop()
{
val = 100; // Zielposition des Servomotors (zwischen 0 und 180)
myservo.write(val); // überträgt die Zielposition an den Servomotors
delay(15); // lässt dem Servomotor Zeit, die Zielposition zu erreichen
}