Code vom Main trennen, um eine Bibliothek zu bilden

Trennung des Codes vom Hauptteil zur Bildung einer Bibliothek

Wir haben ein Stück wiederverwendbaren Code erstellt, um mit dem LCD zu kommunizieren und Befehle und Zeichen zu senden. Wir haben so viele Funktionen für das LCD aktiviert, wie wir momentan benötigen (wir werden später den 4-Draht - 4-Bit - Modus implementieren, um weniger Pins am Mikrocontroller zu verwenden), daher ist dies ein guter Zeitpunkt, den Code zu nehmen und eine Bibliothek daraus zu machen. Es gibt ein paar Möglichkeiten, wie wir diese Bibliotheken erstellen können. Die Bibliothek kann in einer ".h"-Datei und einer ".c"-Datei liegen, oder sie kann ganz in der ".h"-Datei liegen. Letzteres ist etwas einfacher, aber nicht empfehlenswert, wenn es sich um eine riesige Menge an Code handelt.

Zunächst muss eine sehr wichtige Bedingung in beiden Methoden enthalten sein. Manchmal können diese Bibliotheken in mehreren Dateien im gesamten Projekt enthalten sein. Bibliotheken dürfen jedoch nicht mehr als einmal eingebunden werden. Sie werden sehen, dass die #include sowohl in der Hauptdatei als auch in den Bibliotheksdateien enthalten sein wird, da beide Dateien die Informationen in der Datei io.h benötigen, diese Datei jedoch nicht zweimal geladen werden kann, sodass eine Bedingung hinzugefügt werden muss, damit der Code in der ".h"-Datei nicht zweimal geladen wird. Die Bedingungsanweisung #ifndef wird in diesem Fall verwendet.

Am Anfang der ".h"-Datei verwenden wir eine #define-Anweisung, um die Datei im Wesentlichen zu kennzeichnen. Nehmen wir an, die io.h-Datei hat am Anfang eine #define-Anweisung wie diese: #define _IO_H_. Die #define-Anweisung teilt dem Compiler in diesem Fall lediglich mit, dass die Bezeichnung _IO_H_ definiert ist. Das ist alles! Wenn nun nach der Verarbeitung dieser Anweisung und der Definition von _IO_H_ Sie den Compiler fragen könnten: "Wurde _IO_H_ definiert?". Der Compiler würde mit Ja antworten. Wir machen tatsächlich das Gegenteil. Wir fragen, ob _IO_H_ nicht definiert wurde, und dann weisen wir den Compiler an, den Code zu verarbeiten. Hier kommt #ifndef ins Spiel. #ifndef ist die Abkürzung für "if not defined" (wenn nicht definiert). Im Falle der io.h-Datei würde also Folgendes existieren:

#ifndef _IO_H_
#define _IO_H_

//Der gesamte Code in der .h-Datei

#endif

Das #endif stünde ganz am Ende der Datei. Wenn diese Datei einmal verarbeitet wurde, wissen Sie, dass der Code verarbeitet würde, weil _IO_H_ noch nicht definiert worden wäre (daher das "if not defined"). Wenn der Code zuvor (aus einer anderen Datei) verarbeitet worden wäre, dann wäre das #define _IO_H_ bereits verarbeitet worden und der Compiler würde dem #ifndef sagen, ja es wurde definiert und ich werde den folgenden Code nicht verarbeiten. Tatsächlich gehe ich bis zum Ende der Datei, zur #end if Anweisung und verlasse diese Datei und fahre dort fort, wo ich aufgehört habe!!

Das müssen wir mit unserer ".h"-Datei tun. Da ich die .h- und .c-Datei MrLCD.h und MrLCD.c nenne, nennen wir die Bezeichnung in der #define-Anweisung einfach MrLCD, wie folgt:

#ifndef MrLCD
#define MrLCD

//Der gesamte Code unserer LCD-Bibliothek

#endif

Die erste Methode:

So, nachdem wir das geklärt haben, sprechen wir über die erste Methode, obwohl ich die zweite verwenden werde. Wie ich bereits sagte, werden bei der ersten Methode zwei Dateien erstellt, eine ".h"-Datei und eine ".c"-Datei. Die ".h"-Datei enthält alle Makros (#define)-Anweisungen und alle Prototypen. Die ".c"-Datei enthält alle eigentlichen Routinen. Der Hauptgrund, warum die ".h"-Datei nur mit den Definitionen und Prototypen erstellt wird, ist, Ihnen einen bequemen Ort zum Ändern von Konfigurationen zu bieten. Im Falle der LCD-Bibliothek enthalten die #define-Anweisungen Informationen, die sich höchstwahrscheinlich ändern werden, wie z.B. der Port, der mit den Datenleitungen des LCD verbunden ist, und der Port und die Pins, die die Steuerleitungen von R/W (Read/Write), RS (Register Select) und E (Enable) empfangen.

In dem Tutorial nenne ich die ".c"- und ".h"-Dateien MrLCD.c und MrLCD.h. Wir werden diese Dateien erstellen, indem wir auf Datei -> Neu -> C / C++ klicken. Wenn Sie dies tun, sehen Sie eine neue Registerkarte namens "neu". Dies werden Sie zweimal tun, einmal für MrLCD.c und einmal für MrLCD.h. Um die Registerkarten zu benennen, müssen Sie die Menüauswahl "Speichern unter..." verwenden, und Sie werden aufgefordert, die Datei zu benennen. Optional können Sie die Dateien auch benennen, wenn Sie die Dateien schließen, da das Programm Sie nach dem Namen der Datei fragen wird.

Jetzt müssen wir die Informationen aus der Hauptdatei in die ".h"-Datei und die ".c"-Datei kopieren. Für die .h-Datei müssen Sie die #include-Anweisungen, #define-Anweisungen und Prototyp-Informationen kopieren:

#ifndef MrLCD
#define MrLCD

//Dies sind die Includes
#include
#include

//Dies sind die Define-Anweisungen
#define MrLCDsCrib PORTB
#define DataDir_MrLCDsCrib DDRB
#define MrLCDsControl PORTD
#define DataDir_MrLCDsControl DDRD
#define LightSwitch 5
#define ReadWrite 7
#define BiPolarMood 2

//Dies sind die Prototypen für die Routinen
void Check_IF_MrLCD_isBusy(void);
void Peek_A_Boo(void);
void Send_A_Command(unsigned char command);
void Send_A_Character(unsigned char character);
void Send_A_String(char *StringOfCharacters);
void GotoMrLCDsLocation(uint8_t x, uint8_t y);
void InitializeMrLCD(void);

#endif

Beachten Sie das #ifndef und das #endif. Dieser Code würde nur einmal im gesamten Projekt vorkommen, selbst wenn ich diese Datei in viele andere Dateien einbinden würde. Dies wird bei größeren Projekten nützlich sein, aber wir verwenden Include-Anweisungen für andere Dateien mehr als einmal.

Die ".c"-Datei empfängt alle Routinen (außer der Hauptroutine) und die ".c"-Datei empfängt auch alle globalen Variablen, die die Routinen verwenden. Wir haben eine namens: firstColumnPositionsForMrLCD, die am Anfang der Datei deklariert ist, aber diese hätte auch direkt vor der Routine definiert werden können, die sie verwendet. Die ".c"- Datei enthält auch die #include-Anweisungen, die die Routinen verwenden.

Die ".c"-Datei könnte so aussehen:

//Dies sind die Includes
#include
#include
#include "Mr.LCD"

//Dies sind die Routinen
void Check_IF_MrLCD_isBusy()
{
DataDir_MrLCDsCrib = 0;
MrLCDsControl |= 1< MrLCDsControl &= ~1<
while (MrLCDsCrib >= 0x80)
{
Peek_A_Boo();
}

DataDir_MrLCDsCrib = 0xFF; //0xFF bedeutet 0b11111111
}

void Peek_A_Boo()
{
MrLCDsControl |= 1< asm volatile ("nop");
asm volatile ("nop");
MrLCDsControl &= ~1< }

void Send_A_Command(unsigned char command)
{
Check_IF_MrLCD_isBusy();
MrLCDsCrib = command;
MrLCDsControl &= ~ ((1< Peek_A_Boo();
MrLCDsCrib = 0;
}

void Send_A_Character(unsigned char character)
{
Check_IF_MrLCD_isBusy();
MrLCDsCrib = character;
MrLCDsControl &= ~ (1< MrLCDsControl |= 1< Peek_A_Boo();
MrLCDsCrib = 0;
}

void Send_A_String(char *StringOfCharacters)
{
while(*StringOfCharacters > 0)
{
Send_A_Character(*StringOfCharacters++);
}
}

void GotoMrLCDsLocation(uint8_t x, uint8_t y)
{
Send_A_Command(0x80 + firstColumnPositionsForMrLCD[y-1] + (x-1));
}

void InitializeMrLCD()
{
DataDir_MrLCDsControl |= 1< _delay_ms(15);

Send_A_Command(0x01); //Bildschirm löschen 0x01 = 00000001
_delay_ms(2);
Send_A_Command(0x38);
_delay_us(50);
Send_A_Command(0b00001110);
_delay_us(50);
}

Sie werden vielleicht ein paar Unterschiede feststellen. Erstens ist es nicht notwendig, das #ifndef zu verwenden, da die ".h"-Datei dies bereits enthält. Zweitens ist es notwendig, ein Include zu "Mr.LCD.h" hinzuzufügen, um die Prototypen zu erhalten. Und schließlich gibt es eine neue Routine namens InitializeMrLCD. Diese Routine packt einfach die ersten paar Codezeilen bezüglich des LCDs zusammen. Wir möchten nicht, dass dieser Code Platz in unserer Hauptdatei einnimmt. Wenn wir ihn auf diese Weise verwenden, müssen wir lediglich eine Zeile in der Hauptroutine hinzufügen:

InitializeMrLCD();

Die erste Methode erfordert auch, dass Sie die makefile über die ".c"-Datei informieren, die eingebunden werden soll. In dieser Datei finden Sie den Kommentar: "# List C source files here." Direkt unter dieser Anweisung sehen Sie "SRC = $(TARGET).c ". Die neue ".c"- Datei, die Sie gerade erstellt haben, wird hier eingefügt. So könnte dies aussehen:


# C-Quelldateien hier auflisten. (C-Abhängigkeiten werden automatisch generiert.)
SRC = $(TARGET).c MrLCD.c

Das ist alles, was Sie in der Makefile tun müssen.

Die zweite Methode:

Die zweite Methode ist die einfachste, aber wenn das Programm sehr lang wird, kann es zu Lesbarkeitsproblemen kommen. Daher ist bei sehr großen Dateien die erste Methode die beste. Diese Methode bietet einen großen Vorteil, und ich habe das Wort "einfachste" verwendet, weil sie einfach ist. Sie müssen sich nicht mit der makefile herumschlagen und den MrLCD.c- Verweis hinzufügen. Sie kopieren und fügen lediglich den Code, der sich auf das LCD bezieht, in eine ".h"-Datei ein. Wir nennen diese Datei "MrLCD.h", genau wie zuvor. Danach müssen Sie nur noch ein #include "MrLCD.h" am Anfang Ihrer Hauptdatei haben und die InitializeMrLCD() in die Hauptroutine einfügen. Unten sehen Sie die Hauptdatei und die MrLCD.h-Datei und wie sie aussehen könnten:

Die Hauptdatei (main.c):

#include
#include
#include
#include "MrLCD.h"
int main(void)
{
InitializeMrLCD();

char positionString[4];

while(1)
{
for(int y = 1; y<=4; y++)
{
for(int x = 1;x<=20;x++)
{
itoa(x, positionString, 10);
GotoMrLCDsLocation(12, 3);
Send_A_String("X = ");
Send_A_String(positionString);

itoa(y, positionString, 10);
GotoMrLCDsLocation(12, 4);
Send_A_String("Y = ");
Send_A_String(positionString);

GotoMrLCDsLocation(x, y);
Send_A_String("x");
_delay_ms(50);

GotoMrLCDsLocation(x, y);
Send_A_String(" ");

itoa(x, positionString, 10);
GotoMrLCDsLocation(12, 3);
Send_A_String(" ");

itoa(y, positionString, 10);
GotoMrLCDsLocation(12, 4);
Send_A_String(" ");
}
}
}
}

Die Header-Datei (MrLCD.h):

#ifndef MrLCD
#define MrLCD

#include
#include
#include

#define MrLCDsCrib PORTB
#define DataDir_MrLCDsCrib DDRB
#define MrLCDsControl PORTD
#define DataDir_MrLCDsControl DDRD
#define LightSwitch 5
#define ReadWrite 7
#define BiPolarMood 2

char firstColumnPositionsForMrLCD[4] = {0, 64, 20, 84};

void Check_IF_MrLCD_isBusy(void);
void Peek_A_Boo(void);
void Send_A_Command(unsigned char command);
void Send_A_Character(unsigned char character);
void Send_A_String(char *StringOfCharacters);
void GotoMrLCDsLocation(uint8_t x, uint8_t y);
void InitializeMrLCD(void);

void Check_IF_MrLCD_isBusy()
{
DataDir_MrLCDsCrib = 0;
MrLCDsControl |= 1< MrLCDsControl &= ~1<
while (MrLCDsCrib >= 0x80)
{
Peek_A_Boo();
}

DataDir_MrLCDsCrib = 0xFF; //0xFF bedeutet 0b11111111
}

void Peek_A_Boo()
{
MrLCDsControl |= 1< asm volatile ("nop");
asm volatile ("nop");
MrLCDsControl &= ~1< }

void Send_A_Command(unsigned char command)
{
Check_IF_MrLCD_isBusy();
MrLCDsCrib = command;
MrLCDsControl &= ~ ((1< Peek_A_Boo();
MrLCDsCrib = 0;
}

void Send_A_Character(unsigned char character)
{
Check_IF_MrLCD_isBusy();
MrLCDsCrib = character;
MrLCDsControl &= ~ (1< MrLCDsControl |= 1< Peek_A_Boo();
MrLCDsCrib = 0;
}

void Send_A_String(char *StringOfCharacters)
{
while(*StringOfCharacters > 0)
{
Send_A_Character(*StringOfCharacters++);
}
}

void GotoMrLCDsLocation(uint8_t x, uint8_t y)
{
Send_A_Command(0x80 + firstColumnPositionsForMrLCD[y-1] + (x-1));
}

void InitializeMrLCD()
{
DataDir_MrLCDsControl |= 1< _delay_ms(15);

Send_A_Command(0x01); //Bildschirm löschen 0x01 = 00000001
_delay_ms(2);
Send_A_Command(0x38);
_delay_us(50);
Send_A_Command(0b00001110);
_delay_us(50);
}

#endif
Zurück zum Blog

Hinterlasse einen Kommentar

Bitte beachte, dass Kommentare vor der Veröffentlichung freigegeben werden müssen.