UsbDisplay
Usb Display mit At91SAM7S
Einleitung
Dies ist ein längeres Projekt, das sicher nicht bis zum 18.8 fertig gestellt ist. Dieser Artikel mag aber doch einen Einblick geben, was ich mir vorstelle und den Einen oder Anderen anregen die Ideen aufzugreifen oder weiterzuverfolgen.
Ziel dieses Projektes ist es ein Multifuntkionales Display für MythTV zu bauen. Es soll über USB angeschlossen werden. Das Display soll ein Graphikdisplay sein. Als Steuerung sollen zwei Taster und ein Drehknopf mit Taskfunktion dienen. Dieser Drehknopf erlaubt es Menus durch Drehen zu Selectieren und durch Drücken auszuwählen. Des weiteren soll ein IR Empfänger vorhanden sein, so dass IR Signale dekodiert werden können. Als Prozessor soll ein Prozessor der AT91SAM7S Serie eingesetzt werden. Um Platz auf der Display Platine zu sparen soll es einen Konnektor geben, der eine Uart-Schnittstelle, DBG, JTAG bereit hält. Die Adapter Platine wird über die Display Platine mit Spannung versorgt und soll auch in weiteren Projekten verwendet werden.
Das Schaltungs Design
Die Versorgungs Spannung wird aus dem USB Bus mit einem Linearregler 317 D gewonnen. Die Widerstände R21 und R22 wurden so gewählt, dass sich eine Ausgangsspannung von 3.3 V einstellt. Aufgrund der geringen Leiterbahnbreite, stellen sich etwa 3.1 V ein. Der Regler liefert höchstens 100mA. Dies ist für das Display 110µA und den Prozessor 50mA ausreichend. Der IR-Empfänger (5mA) und die LED der Hintergrund Beleuchtung (40mA) verbrauchen weniger als 50 mA.
Der Prozessor AT91SAM7S256 ist zu den anderen ICs der Serie kompatibel. Wenn die Firmware fertig ist, kann evtl. auch ein kleiner Chip AT91SAMS64 ausreichen. Der Prozessor ist entsprechend den Empfehlungen von Atmel beschaltet worden. Um die Spannung zu glätten sind für die 3.3V und 1.8V Versorgungsspannung 2,2 µF Tantal Kondensatoren vorgesehen C1, C4. Alle weiteren Versorgungsspannungspins sind mit 100nF Condensatoren abgeblockt worden C2, C3, C5, C7, C8, C9, C10.
Für die PLL-Filter und den Quarz wurden die Größen verwendet, die auch in anderen Beispielen zu finden sind.
Es wurde auch ein Reset Baustein vorgesehen, die Praxis zeigt aber, dass dieser Baustein nicht erforderlich ist. Auch ohne dieses teure Stück läuft der Prozessor zuverlässig an. Das Display wird durch den Prozessor zurück gesetzt, so dass er auch hier entbehrlich ist.
Es ist weiter ein Jumper Feld zum Löschen und starten des SAM-BA vorgesehen. Leider wird für SAM-BA PA0 und PA1 auf GND gezogen, so dass SAM-BA nur dann verfügbar wäre, wenn keine Adapter Platine angeschlossen ist und R12 nicht bestückt ist. Einen Bootloader habe ich noch nicht geschrieben, so dass im Moment nur die Programmierung über JTAG bleibt.
Der IR-Empfänger ist klassisch angeschlossen, um den Ausgangspin während eines Resets des Controllers (Verbindung per Pullup zu 3.3V) zu schonen ist ein Spannungsteiler vorgesehen, so dass der Ausgang auch in diesem Zustand nicht belastet wird. Er ist so eingestellt, dass ein High-Pegel etwa 3.3V liefert.
Der Drehimpulsgeber (STEC12E08) und die beiden Taster sind jeweils mit Pullups mit 3.3V verbunden. Diese Widerstände müssten eigentlich auch eingespart werden können, wenn die internen Pullups verwendet werden. Im Datenbaltt des Drehimpuls Gebers sind aber für 5 V 10k empfohlen, aus denen ich die 6,2k für 3.3V hergeleitet habe. Die Kondensatoren habe ich schon eingespart -- die Platine ist ja schon eine Kondesator Senke.
Beim Display habe ich mich für das EA DOGM132-5 entschieden. Es handelt sich um ein Grafik Display 132x32 Punkte, das Pinähnlich auch in anderen Varianten zu haben ist. Dieses Display kommt mit 3.3V Versorgungsspannung aus und kann direkt per SPI vom AT91SAM7S angesprochen werden. Im Internet kursieren einige Bibliotheken, die ähnliche Displays zeigen. Leider habe ich zunächst das falsche Pinning erwischt und schließlich das Symbol neu gezeichnet. Bei dieser Aktion haben sich die Netze für die Kontrastspannung und die Versorgungsspannung verbunden, was ich leider erst nach der Inbetriebnahme bemerkte. Die Lösung dieses Fehlers wird in einem separatem Abschnitt Kontrastspannung reparieren beschrieben. Die Reset Leitung ist mit einem I/O Pin verbunden, so dass das Display gezielt zurück gesetzt werden kann.
Die CS-Leitung wird durch den SPI Teil des AT91SAM7S gesteuert. Leider muss für die Unterscheidung von Display Daten und Kommandos die A0 Leitung gesteuert werden, so dass nicht direkt der Komplette Bildschirminhalt mit einem DMA (PDC) Transfer geschrieben werden kann, sondern immer nur eine Zeile je 8 Bit/Pixel.
Das Display enthält mehrere Ladungspumpen um die Kontrast Spannung zu erzeugen, aus diesem Grund versammeln sich 8 Kondensatoren entlang des Displays.
Die Adapter Platine verbindet die JTAG-Schnittstelle, USART2, DBGU, einen I/O-Pin und einen Analogen Eingang. Der I/O-Pin schaltet eine LED. Der Analog Pin soll dazu dienen zwei Tasten eingaben auf der Adapter Platine anhand der unterschiedlich abfallenden Spannung zu unterscheiden. LED und Analog-Pin sind noch nicht in Betrieb genommen worden. Da die LED aber bei einem Reset an ist und im normalen Betrieb ausgeschaltet ist, gehe ich davon aus, dass die Verbidnung zumindest zu irgend einem Pin besteht ;-)
Die Display Platine entsprich den 8mil Anforderungen von Olimex, die Adapter Platine den 10mil Anforderungen. Die 8/08 Version hat noch keine Massefläche und ist noch völlig ungeprüft! Außerdem wäre eine Optimierung für eine bestimmte gängige Gehäusegröße und einige Bohrungen zur Befestigung sinnvoll.
Aufbau der Hardware
Die aufgeführten Bauteile kosten etwa 70 EUR bei Reichelt. Aus einer DSS (Doppelseitigen Europaplatine) bei Olimex können 4 Display-Platinen und eine Adapter Platine gewonnen werden. So dass eine Platine etwa 10 EUR kostet. Die Material Kosten können weiter gesenkt werden, wenn ein kleinerer Prozessor und sämtliche optionalen Widerstände und Kondensatoren weg gelassen werden und von Mengenrabatten Gebrauch gemacht wird.
Für die Maschinelle Herstellung eignet sich das Layout sicher nicht, die Kondensatoren in der ersten Version waren sehr eng angeordnet.
Beim Aufbau bin ich wie folgt vorgegangen. Ich habe nur die Bauteile für eine Version bestellt. Zunächst habe ich die Adapter Platine aufgebaut. Die flachen Bauteile werden zuerst aufgelötet. Der Treiber Baustein ist ziemlich unempfindlich aus diesem Grund habe ich mit dieser Platine begonnen. Die Markierung des Chip entspricht dem Platinendruck. Bei den großen Bauteilen ist es Praktisch mit dem ARM-JTAG-Stecker zu beginnen. Ihn kann man in eine Unterlage stecken. Bei den LEDs muss der großflächige Anschluss (Kathode) innerhalb der LED auf der flachen Seite liegen. Die Kondensatoren sind aus Kostengründen keine SMD Bausteine die Markierung zeigt jeweils vom IC weg. Die Ausrichtung des Miniatur Stiftleiste (MicroMatch MM FL 14G) ist egal. Ich habe sie wie den JTAG Adapter von oben eingesteckt. Mit den "richtigen" Kabeln kann man die Pins wieder in die richtige Richtung drehen. Ich gehe davon aus, dass er normal, d.h. von unten angelötet wird. Damit das Kabel von der Display Platine 5/08 weg zeigt, müssen die Stecker des Kabels so angeordnet sein, dass sie an beiden Enden nicht gleich angesteckt sind. Die rote Markierung sollte mit dem Pin 1 des JTAG adapters übereinstimmen -- wenn ich eine neue Version der Adapterplatine bauen würde, würde ich dringend eine 1 Markierung vorsehen!
Anschließend habe ich die Display Platine aufgebaut. Angefangen mit dem Linearregler weiter mit den Widerständen und den uni-polaren Kondensatoren. Anschließend habe ich die Tantal Kondensatoren bestückt und jeweils die Polarität mit dem Platinen Layout verglichen. Eine Markierung im Bestückungsdruck fehlt leider. Auch dies sollte bei einer Neuanfertigung korrigiert werden. Aber darauf achten, dass der Druck nicht auf die PADs reicht, da es sonst mit dem Löten schwierig wird.
Unter dem USB Stecker sind unglücklicher Weise noch zwei Widerstände platziert worden. R11 kann ersatzlos entfallen. Aus Symmetriegründen kann auch R24 entfallen, sie ziehen den Eingang auf GND, wenn kein PC verbunden ist. Da ohne die USB Verbindung die Platine stromlos ist. R3 ist der Pull-UP mit dem der PC erkennt dass ein Highspeed gerät angeschlossen ist. Diesen Widerstand kann man nicht dauerhaft weglassen, so dass ich ihn sehr nah zur Platinen Mitte angelötet habe und ihn isoliert habe.
Die MicroMatch und den USB Stecker habe ich auch schon jetzt auf gelötet, weil ich in diesem Zustand die Schaltung prüfen möchte ob auf der Adapter Platine die Spannungsversorgungs-LED leuchtet. Dies hat gezeigt, dass bisher keine Brücke zwischen GND und 3.3V oder GND und 5V besteht.
Anschließend habe ich den AT91SAM7S eingelötet. Das Raster ist fein, aber die Größe oder Feinheit des Lötkolben ist nicht entscheidend. Die Entlötlitzen Methode beseitigt entstandene Brücken zuverlässig. Ich habe zunächst den Fehler gemacht und einen PIN verzinnt. Dieser Pin war dann auch recht bald schon verbogen, weil er an der Kuppe hängen geblieben ist. An den PIN habe ich aber eine Veränderung bemerkt, anschneidend besitzt er einen winzigen Vorrat Lötzinn, der ausreicht um eine Verbindung zwischen Platine und Pin herzustellen. Die Anderen Pins habe ich dann einfach nur noch erwärmt und mit einem mit Zinn benetzten Lötspitze verlötet.
Anschließend habe ich mit der Entlötlitzen-Methode die offensichtlichen Brücken entfernt und mit einer sehr starken Lupe die Lötstellen kontrolliert. Nachdem ich mich versichert habe, dass es keine unerwünschten Verbindungen gibt, habe ich die Platine eingeschaltet und beobachtet, dass beide LEDs der Adapterplatine geleuchtet haben. Dies bedeutet: Der Prozessor hat Spannung und die PINs befinden sich im Defaultzustand. Anschließend habe ich mit dem Olimex JTAG-Interface die Prozessor angesprochen. Der Prozessor wurde auf Anhieb erkannt.
Anschließend konnte ich mit den Beispielen zum AT91 USB und die Debug Schnittstelle testen. Die fehlenden Widerstände haben keinen negativen Einfluss auf diese Funktion.
Inbetriebnahme
Während die Seriellen Beispiele von Atmel schon die grobe Funktion gezeigt haben, konnten die anderen Funktionen nur durch eigene SW benutzt werden. Die Inbetriebnahme dient dazu die Funktion der HW zu testen und sicher zu stellen, dass die notwendigen Verbindungen existieren und jede Teilkomponente für sich funktioniert. Als Basis Software habe ich eine GCC-Port eines USB/Seriell Wandlers verwendet, das im AT91.com Forum gepostet wurde. Mit diesem Code ist nicht viel interessantes passiert, so dass hier nur Code Auszüge gezeigt werden.
Drehimpulsgeber auslesen
Zunächst habe ich in einer Schleife die eingangs Ports abgefragt und ausgegeben. Dies zeigt, dass die Verbindungen vorhanden sind. Das Auslesen des Drehimpulsgebers hat, per Polling leider nicht funktioniert. Die Pins zeigen ein langes hochfrequentes Prellen. Ein weiterer Effekt mag sein, dass ich eine Reihe von Ausgaben gemacht habe. Deswegen habe ich den Interrupt on Change verwendet um den Zustand der Pins ab zu fragen und entsprechend einer Zustandsübergangstabelle in eine Drehrichtung umgesetzt.
Das Decodieren ist reigentlich recht einfach, wenn man davon ausgeht, dass keine Zustände übersprungen werden. Das Prellen ist dann nicht weiter als ein schnelles hin und her springen zwischen zwei Zuständen. Die Aufnahme des Logic Analyzers zeigt, dass niemals zwei Pins gleichzeitig prellen.
Mit Hilfe eines Zustands Automaten kann man sich vor Augen führen, in welcher Reihenfolge die Eingangswerte durchlaufen werden. Die De-Codierung besteht dann darin zu erkennen, aus welchem Vorgänger Zustand in welchen aktuellen Zustand gewechselt wird. Rot/Grün entspricht dann genau je einer Drehrichtung.
Übrigens habe ich mir den Code angesehen und er ist meiner Meinung nach nicht verbesserbar, solange die Algorithmus Idee -- einen Zustandsautomaten zu verwenden weiter verfolgt wird.
static int dir=0; // Die Richtung in die als letztes gedreht wurde static int pos=0; // Die virtuelle Position. Pro Rastung wird 4 Schritte weiter gezählt. void __attribute__((interrupt)) ISR_Button(void) { int curState = 0; AT91F_PIO_GetInterruptStatus(LCD_PIO); unsigned int value = AT91F_PIO_GetInput(LCD_PIO); int receiverState=value & IR_IN; if(receiverState!=lastRecieverState) { lastRecieverState=receiverState; irToggled(); } curState = 0; if (value & BUTTON_ROT_A) { curState = 2; } if (value & BUTTON_ROT_B) { curState += 1; } if (curState != lastState) { switch (lastState << 2 | curState) { case B8(1101): dir = -1; break; case B8(1110): dir = 1; break; case B8(0001): dir = 1; break; case B8(0010): dir = -1; break; case B8(0111): dir = 1; break; case B8(0100): dir = -1; break; case B8(1000): dir = 1; break; case B8(1011): dir = -1; break; default: dir = 0; } pos += dir; if (pos < 0) pos = 99; if (pos > 99) pos = 0; lastState = curState; } AT91F_AIC_AcknowledgeIt(AT91C_BASE_AIC) } // Irgend wo in der Initialisierung AT91F_PIOA_CfgPMC(); AT91F_TC0_CfgPMC(); AT91F_PIO_CfgPullupDisable(LCD_PIO, (LCD_CSB | // Disable All Pullups LCD_RS | LCD_A0 | LCD_SCK | LCD_LED | LCD_MOSI)); AT91F_PIO_CfgPullupDisable(LCD_PIO, BUTTON_E | BUTTON_D | BUTTON_ROT_A | BUTTON_ROT_B | BUTTON_ROT_C | IR_IN); AT91F_PIO_CfgInput(LCD_PIO, BUTTON_E | BUTTON_D | BUTTON_ROT_A | BUTTON_ROT_B | BUTTON_ROT_C | IR_IN); AT91F_PIO_CfgInputFilter(LCD_PIO, BUTTON_E | BUTTON_D | BUTTON_ROT_A | BUTTON_ROT_B | BUTTON_ROT_C | IR_IN); AT91F_AIC_ConfigureIt(AT91C_BASE_AIC, AT91C_ID_PIOA, AT91C_AIC_PRIOR_LOWEST, AT91C_AIC_SRCTYPE_HIGH_LEVEL, //AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL, ISR_Button);
Als Interrupt Handler steht in der Intialisierung
ldr pc, [pc,#-0xF20]
So wird direkt an die Addresse gesprungen, die im AIC konfiguriert ist. Da ich keine Reentrant Interrupts haben möchte, reicht mir diese Simple behandlung. Die Magie geschieht über das Attribut __attribute__((interrupt)) Laut GCC Manual ist dies genau für Interrupt Handler gedacht. Der Compiler speichert nur die Register auf dem Stack, die tatsächlich benötigt werden. Dies ist performanter als per se alle Register zu speichern.
IR Impulse messen
Die nächste Herausforderung war Infrarot signale zu empfangen. Dieser Code ist noch nicht fertig, so dass ich einzelen Tasten empfangen kann, er wäre dazu aber leicht in der Lage. Da ich nicht solange auf einen Signal wechsel warten kann, brauche ich ein Mittel um zu messen, wie lang der letzte Signalwechsel her ist. Dazu verwende ich einen Timer (0). Die Clock wähle ich so dass er in 16 Bit locker eine Sekunde zählen kann. Ich wähle 48/1024 Mhz als Clock. Die Comperator stelle ich so ein, dass er genau nach einer Sekunde einen Interrupt auslöst. Im Interrupt wird auf den Globalen Tick-Zähler der comperator wert addiert. Aus der Summe des globalen Tick-Zählers und dem aktuellen Timerstandes, kann ein 32bit breiter monotoner zähler gweonnen werdenn, der mit 46,875 kHz getaktet ist. Dieser zähler wird etwa einmal am Tag ablaufen -- nur in dieser Zeit ist dann keine IR-Messung möglich.
const int TICKS_PER_SECOND=46875; int ticks =0; void INTERRUPT ISR_Timer() { int status=AT91C_BASE_TC0->TC_SR; if(status& AT91C_TC_CPCS) dir=3; int dummy=AT91C_BASE_TC0->TC_IMR; dummy=dummy; access++; ticks+=TICKS_PER_SECOND; END_INTERRUPT(); } int lastRecieverState=0; int lastMeasureTime=0; int curMeasureTime=0; int measured=0; int lastDuration=0; int measeureArray[40]; int measeureArrayL[40]; void irToggled() { curMeasureTime=ticks+AT91C_BASE_TC0->TC_CV; lastDuration=curMeasureTime-lastMeasureTime; lastMeasureTime=curMeasureTime; if(measured<40) { if(lastDuration>255) lastDuration=0; measeureArray[measured]=lastDuration; measeureArrayL[measured]=lastRecieverState !=0 ? 1 : 0; measured++; } } // irgendwo in der Initialisierung AT91F_AIC_ConfigureIt(AT91C_BASE_AIC, AT91C_ID_TC0, AT91C_AIC_PRIOR_LOWEST, AT91C_AIC_SRCTYPE_HIGH_LEVEL, //AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL, ISR_Timer); AT91C_BASE_TCB->TCB_BMR=0; AT91C_BASE_TC0->TC_CMR=AT91C_TC_CLKS_TIMER_DIV5_CLOCK | (AT91C_TC_CLKI*0) | AT91C_TC_BURST_NONE | AT91C_TC_WAVESEL_UP_AUTO | AT91C_TC_WAVE ; AT91C_BASE_TC0->TC_RC=TICKS_PER_SECOND ;// TC0 Timer interval 1 second AT91C_BASE_TC0->TC_RA=0xFFFF; AT91C_BASE_TC0->TC_RB=0xFFFF; AT91C_BASE_TC0->TC_IER=AT91C_TC_CPCS ; // Interrupt on RC AT91C_BASE_TCB->TCB_BCR=1; int dummy=AT91C_BASE_TC0->TC_IMR; AT91C_BASE_TC0->TC_CCR=AT91C_TC_CLKEN | AT91C_TC_SWTRG; dummy=dummy; AT91F_AIC_EnableIt(AT91C_BASE_AIC, AT91C_ID_TC0 );
Display ansteuern
Das Highlight ist natürlich, wenn das Display das Erste mal etwas ausgibt. Dies war zunächst ein Satz mit X, das Display blieb leer.
Also vergnügte ich mich zunächst mit der Hintergrund Beleuchtung, an ging sie schon doch leider nicht gedimmt. Dafür hatte ich ja extra einen PWM Pin vorgesehen. Also, wie macht man das?
AT91F_PIO_CfgPeriph(LCD_PIO, AT91C_PIO_PA1, 0); // Die Peripherie Konfiguration ist jeweils nur ein Beispiel! AT91F_PWMC_CfgPMC(); // Enable PWM AT91F_PWMC_CfgChannel(AT91C_BASE_PWMC, AT91C_PWMC_CHID0, 1, 0xFF, dutyCycle); AT91F_PWMC_StartChannel(AT91C_BASE_PWMC, AT91C_PWMC_CHID0); AT91F_PWMC_UpdateChannel(AT91C_BASE_PWMC, AT91C_PWMC_CHID0, dutyCycle); // Modified Version!! // Modified Functions: //lib_PWM_SAM.h/1.3/Wed Dec 3 10:23:10 2003// //*---------------------------------------------------------------------------- //* \fn AT91F_PWM_UpdateChannel //* \brief Update Period or Duty Cycle //*---------------------------------------------------------------------------- __inline void AT91F_PWMC_UpdateChannel( AT91PS_PWMC pPWM, // \arg pointer to a PWM controller unsigned int channelId, // \arg PWM channel ID unsigned int update) // \arg Channels IDs to be enabled { pPWM->PWMC_CH[channelId-1].PWMC_CUPDR = update; } //*---------------------------------------------------------------------------- //* \fn AT91F_PWM_CfgChannel //* \brief Test if PWM Interrupt is Set //*---------------------------------------------------------------------------- __inline void AT91F_PWMC_CfgChannel( AT91PS_PWMC pPWM, // \arg pointer to a PWM controller unsigned int channelId, // \arg PWM channel ID unsigned int mode, // \arg PWM mode unsigned int period, // \arg PWM period unsigned int duty) // \arg PWM duty cycle { pPWM->PWMC_CH[channelId-1].PWMC_CMR = mode; pPWM->PWMC_CH[channelId-1].PWMC_CPRDR = period; pPWM->PWMC_CH[channelId-1].PWMC_CDTYR = duty; }
Es sind maximal 0xFF Ticks für die PWM vorgesehen -- so viel sieht man eh nicht... Nach einem Tasten Druck wird jeweils der dutyCycle verädnert, so dass wenn UpdateChannel ausgeführt wird, die jeweils neue Helligkeit eingestellt werden kann. Die 1 sorgt auf magische Weise dafür, dass der richtige PWM-Modus gewählt wird, also 22/Mhz und der Duty Cycle bei einem Update aktualisiert wird. Durch ausprobieren habe ich gefunden, dass diese Frequenz brauchbar ist, so dass sich die Helligkeit ändert und das Licht nicht flackert.
Achja, eine nettigkeit ist, dass StartChannel, CfgChannel und UpdateChannel die Kanal ID im Original unterschiedlich zählen, so dass die drei Funktionen nicht alle mit AT91C_PWMC_CHID0 benutzt werden können
Ich habe darauf Atmel hingewiesen und sie wollen den Fehler weiter leiten. Ob sie allerdings ihre Bibliothek ändern bleibt fraglich, da die Änderung für mit dem original Funktionierendem Code falsch wird. Einzige Möglichkeit ist, den Namen zu ändern, so dass alter funktionierender Code nicht kaputt gemacht wird ... ich bin gespannt.
Kontrastspannung reparieren
Damit das Display etwas anzeigt, braucht es die korrekte Kontrastspannung. Das Display habe ich vorsorglich gesockelt montiert. Mit einem Logic Analyzer habe ich die Signale und Initialisierung kontrolliert, keine Fehler. Dann habe ich die Spannungen gemessen, keine Fehler. Dann habe ich die Verbindungen zu den Kondensatoren und anderen Pins kontrolliert. Dabei ist mir die Verbindung zwischen 3,3V und dem VOUT des Displays aufgefallen.
Anschließend habe ich die Leiterbahnen, wie in den Bildern gezeigt durchtrennt. Der Kondensator C17 war eigentlich für die die Stabilisierung der Kontrastspannung gedacht. Den konnte ich nun nicht mehr verwenden, daher habe ich einen bedrahteten 1µF zwischen VOUT und GND angelötet. Mit einem Stück Draht habe ich dann den 3,3V Pin des Displays verbunden. Wenn man diesen Code ausgeführt hat, hat man dann das folgende Bild gesehen:
eOrientation curOrientation=ORIENTATION_TOP; void setLcdCmd(unsigned char value) { AT91F_PIO_ClearOutput(LCD_PIO, LCD_A0); AT91F_SPI_PutChar(LCD_SPI, value, 0); while ((LCD_SPI->SPI_SR & AT91C_SPI_TDRE) == 0) ; } void setLcdData(unsigned char value) { AT91F_SPI_PutChar(LCD_SPI, value, 0); while ((LCD_SPI->SPI_SR & AT91C_SPI_TDRE) == 0) ; } void setDisplayOrientation(eOrientation orientation) { if (orientation == ORIENTATION_BOTTOM) { setLcdCmd(0xA1); setLcdCmd(0xC0); } else { setLcdCmd(0xA0); setLcdCmd(0xC8); } } // irgend wo in der Initialisierung AT91F_SPI_CfgPMC(); AT91F_PIOA_CfgPMC(); AT91F_TC0_CfgPMC(); AT91F_PIO_CfgPullupDisable(LCD_PIO, (LCD_CSB | // Disable All Pullups LCD_RS | LCD_A0 | LCD_SCK | LCD_LED | LCD_MOSI)); AT91F_PIO_CfgOutput(LCD_PIO, LCD_RS | LCD_A0 | LCD_LED); AT91F_PIO_ClearOutput(LCD_PIO, LCD_LED); AT91F_PIO_SetOutput(LCD_PIO, LCD_LED); AT91F_PIO_ClearOutput(LCD_PIO, LCD_LED); AT91F_PIO_SetOutput(LCD_PIO, LCD_LED); AT91F_PIO_ClearOutput(LCD_PIO, LCD_RS); AT91F_PIO_CfgPeriph(LCD_PIO, LCD_PIO_A, LCD_PIO_B); AT91F_SPI_Reset(LCD_SPI); AT91F_SPI_Disable(LCD_SPI); AT91F_SPI_Enable(LCD_SPI); TRACE_INFO("Habe SPI configuriert...\n"); AT91F_SPI_CfgMode(LCD_SPI, AT91C_SPI_MSTR | // Use Master AT91C_SPI_PS_FIXED | // Fixed CS ~AT91C_SPI_PCSDEC | // Only plain CS lines AT91C_SPI_MODFDIS // No fault detection ); AT91F_SPI_CfgPCS(LCD_SPI, 0); AT91F_PIO_SetOutput(LCD_PIO, LCD_RS); delay(10); AT91F_PIO_ClearOutput(LCD_PIO, LCD_RS); delay(100); AT91F_PIO_SetOutput(LCD_PIO, LCD_RS); delay(100); setLcdCmd(0xE2); // Reset setLcdCmd(0x40); setDisplayOrientation(curOrientation); setLcdCmd(0xA6); setLcdCmd(0xA2); setLcdCmd(0x2F); setLcdCmd(0xF8); setLcdCmd(0x00); setLcdCmd(0x23); setLcdCmd(0x81); setLcdCmd(0x1F); setLcdCmd(0xAC); setLcdCmd(0x00); setLcdCmd(0xAF); //Irgendwo im Header #define LCD_LED AT91C_PIO_PA0 #define LCD_CSB AT91C_PIO_PA11 #define LCD_RS AT91C_PIO_PA12 #define LCD_A0 AT91C_PIO_PA24 #define LCD_SCK AT91C_PIO_PA14 #define LCD_MOSI AT91C_PIO_PA13 #define LCD_PIO AT91C_BASE_PIOA #define LCD_PIO_ID AT91C_ID_PIOA // Define which peripherial Port has to be used #define LCD_PIO_A LCD_MOSI | LCD_SCK | LCD_CSB | LCD_LED | AT91C_PIO_PA1 #define LCD_PIO_B 0 #define LCD_SPI AT91C_BASE_SPI #define BUTTON_E AT91C_PIO_PA17 #define BUTTON_D AT91C_PIO_PA28 #define BUTTON_ROT_A AT91C_PIO_PA31 #define BUTTON_ROT_B AT91C_PIO_PA30 #define BUTTON_ROT_C AT91C_PIO_PA29 #define IR_IN AT91C_PIO_PA5
Wie es weiter geht
Als Betriebsystem habe ich mich für ChibiOS entscheiden. Die Nutzung ist aber noch nicht sehr weit gediehen.
Als Treiber habe ich den usb-skeleton Treiber angepasst. Nachdem ich inzwischen weitere Informationen zu USB gesammelt habe, habe ich nun die Idee gefasst das USB Device als Zwei geräte auf zu fassen. Der eine Zeil bleibt das Input/Output Device einer Seriellen Schnittstelle, die aber der unnötigen Attribute im dritten Endpoint beraupt wird. Die damit frei gewordenen Endpoint können dann dazu genutzt werden eine HID-KeyBoard device zu implementieren, so dass das Display auch ohne spezielle Treiber jede Anwendung steuern kann.
Von lcdProc lässt sich der text Treiber leicht modifzieren, so dass er die Zeilen in das bereit gestellte device schreibt. In dem hier bereit gestelltem Quelltext ist dies geschehen, so dass man das Display leicht in Betrieb nehmen kann und direkt mit MythTV benutzen kann.
Da ich keine Vendor ID besitze und ich auch keinen Vendor-ID besitzer kenne, der mir eine Product ID überlässt, kann ich den Quelltext leider noch nicht in gänze bereit stellen, da ich die vorhanden IDs nicht verwenden darf. Jeder nutzer müsste dann erst seine eigene ID eintragen und dann alles neu compilieren.
Nach nun mehr zwei drei Monaten der unregelmäßigen Wochenendarbeit, kann meine MythTV Installation nun ein dreizeiliges Display ansteuern. Die 4. Zeile ist noch der Debug Ausgabe gewittmet, in der man die Dasten stati, die aktuelle Position des Drehimpulsgebers und die Systemticks sehen kann.
Ein Unschönheit ist, dass die Interrupt programmierung sehr fehler anfällig ist und das Target hin und wieder stehen blieb, wenn der Taskwechsel zum falschen Zeitpunkt kommt.
Unschön ist auch, dass das Device noch keinen USB-Reconnect auslösen kann, so dass der Treiber neu initialisiert wird. Aus diesem Grund, funktioniert USB nur bei einem kalten neustart, bei einem Reset per Knof oder Jtag funktioniert der Reconnect nicht. Hinweise sind zu diesem Problem sind auch willkommen.
Hinweise zu den bereit gestellten Dateien
Die evtl. hier herunter ladbaren Dateien spiegeln den jeweiligen Stand der Entwicklung wieder. Bisher wurde nur die Version 1 (5/08) der Platine tatsächlich aufgebaut und gefertigt. Um aber dem Interessierten Leser eine Chance zu geben nicht die gleichen Fehler zu machen, habe ich die Platine überarbeitet und als Version 3 (8/08) ebenfalls beigelegt. Die Software ist für diese Version noch nicht angepasst. Es muss vor allem PWM3 statt PWM1 verwendet werden. Da der jeweils andere Pin in den anderen Versionen nicht belegt ist, kann die Software beide PWMs gleichzeitig benutzen.
Folgende Bugs sind in der Platine 5/08 bekannt:
- Kontrastspannung des Displays mit +3.3V kurz geschlossen. Lösung: Zwei Leiterbahnen trennen. Einen Kondensator einlöten und einen Fädeldraht entsprechend Anleitung ziehen.
- Der Reset Baustein MAX809 hat kein brauchbares Footprint. Lösung: weglassen! Die Platine funktioniert auch ohne.
- R3 Muss möglichst nahe zur Platinen innenseite Angelötet werden. Und mit geeignetem Material, z. B. Tesafilm isoliert werden
- R11 Muss leider entfallen. Die Platine funktioniert aber auch ohne. Aus Symmetriegründen kann R24 ebenfalls entfallen.
- R20 und R19 müssen nicht bestückt werden, weil wenn der Prozessor Strom hat, liegt auch am Bus Spannung an.
Ich lade nur Dateien hoch, in denen klar ist, dass ich sie hoch laden darf. Weil sie unter der GPL stehen oder ich sie erstellt habe. Die Datei, die die Product-ID enthält kann ich nicht hochladen. Meine Prorgamm-Dateien dürfen entsprechend der GPL3 genutzt werden. Dieser Artikel darf entsprechend der Creative Commons verwendet werden. Das Eagle Layout darf für eigene Projekte verwendet werden. In der Dokumentation soll auf die Quelle hingewiesen werden.
Download
* Usb-Display Eagle Dateien 5/08 * Usb-Display Eagle Dateien 8/08 * Erste Software für die Platine, ohne LCDProc da die Source doch recht groß sind * Erste Software für die Platine
Weitere Informationen und Quellen
* Der Verwendete Prozessor AT91SAM7S256. Datenblatt und Application Notes * Datenblatt des Display DOGM132x-5 * LDCproc * MythTV * Eagle Layout Programm * Anleitung zum Schreiben von USB Treibern * GCC Toolchain für ARM * ChibiOS
Fragen und Anregungen sind willkommen. Insbesondere ein Hinweis zu einer Product ID würde helfen.
Danke!
Danke an Magnetus, der schon Fehler korrigiert hat, bevor ich es tun konnte.