PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Senden und empfangen auf dem UART mit ISR kompatibel zur bisherigen RP6lib



RolfD
18.04.2014, 23:15
Hallo...
ich hab vor einiger Zeit mal versuchsweise die RP6uart.c umgeschrieben so das sie per ISR sendet.
da ich den Code grade wieder vor liegen habe, poste ich ihn mal zur allgemeinen Begutachtung.
Anlass für die Änderungen waren immer wieder Laufzeitprobleme im Zusammenhang mit UART
Ausgaben sowie teilweise Datenmüll auf der Receiver.

Es wurden noch ein paar Dinge korrigiert.
U.a kann man nun das Zeichen 0x00 zuverlässig empfangen.
Die Sendefunktion blockt nur bis im Software SendeBuffer wieder Platz ist, sonst wird normal weiter gearbeitet.
Den Rest erledigt die SendeISR. Die ReceiverISR prüft nun auf Fehler und nimmt nur Zeichen an wenn kein Datenmüll anliegt.
Bei erkanntem Software Bufferoverrun setzt die Lesefunktion den LeseBuffer zurück.
Die dazu aufgerufene Funktion clearReceptionBuffer(); kann man gut für eigenes Debuging nutzen.
Man kann alle alten Funktionen wie gehabt nutzen, es gibt aber zusätlich 2 Ersatzfunktionen
mit Namen serial_sendc und serial_getc, serial_sendc macht das gleiche wie writeChar, serial_getc
liest das Zeichen jedoch auf ein Pointer (eines buffers) und gibt im Returnwert 0/1 zurück ob ein Zeichen
gelesen wurde. Wenn serial_getc eine 1 zurück gibt ist das Zeichen auf der Bufferadresse gültig!
Auch wenn 0x00 übertragen wurde. Der Sende und Empfangsteil ist unabhängig voneinander,
man kann auch unterschiedlich große Buffer in der .h einstellen. Die Pointer in den Ringbuffern
nutzen jeweils nur noch head und tail. Die Codegröße hat sich kaum verändert, einige Vars
konnte ich einsparen, dafür kommt nun ein weiterer SendeBuffer mit aktuell 32 byte Ram hinzu.

Ich hoffe, die geänderte lib findet Anklang, vielleicht baut man sie sich ja dauerhaft ein.
Wäre schön wenn dazu Feedback kommt.

Dazu die alten Dateien RP6uart.c und RP6uart.h umbenennen -> z.b. so _RP6...
Neue Dateien in den Verzeichnissen anlegen und den Code unten rein schieben.
Oder einfach die angehängten Dateien nutzen.

Die RP6uart.c


/* ************************************************** **************************
* File: RP6uart.c
*
* ISR(USART_RXC_vect) geändert. erkennt Frame errors
* Umbau auf serial_getc, erkennt nun 0x00 chars
* ISR zum senden
*/

/************************************************** ***************************/
// Includes:

#include "RP6uart.h"

/************************************************** ***************************/
// new UART transmit functions:
// Data for Ringbuffer

static volatile t_txbuffer tx_buff;

/**
* UART send ISR.
* Handles transmission from circular buffer.
*/
ISR(USART_UDRE_vect, ISR_BLOCK) {
if (tx_buff.head != tx_buff.tail) {
UDR = tx_buff.ring[tx_buff.tail];
tx_buff.tail = (tx_buff.tail + 1) % UART_SEND_BUFFER_SIZE;
}
else
UCSRB &= ~(1 << UDRIE);
}

/**
* send function, nonblocking if free space in buffer
*/

uint8_t serial_sendc(unsigned char data) {

uint8_t next = ((tx_buff.head + 1) % UART_SEND_BUFFER_SIZE);
while (next == tx_buff.tail);
tx_buff.ring[tx_buff.head] = data;
tx_buff.head = next;
UCSRB |= (1 << UDRIE);
return 1;
}

/**
* compatibility implementation
* old writeChar function, use serial_sendc instead
*/
void writeChar(char ch)
{
serial_sendc(ch);
}

/**
* Writes a null terminated string or buffer from SRAM to UART.
* Uses serial_sendc
* Example:
* writeString("RP6 Robot System\n");
*/
void writeString(char *string)
{
while(*string)
serial_sendc(*string++);
}

/**
* Writes a null terminated string from flash program memory to UART.
* Uses serial_sendc
* Example:
* writeNStringP(PSTR("RP6 Robot System\n"));
* // There is also a Macro that makes life easier and
* // you can simply write:
* writeString_P("RP6 Robot System\n");
*/
void writeNStringP(const char *pstring)
{
unsigned char c;
for (;(c = pgm_read_byte_near(pstring++));serial_sendc(c));
}

/**
* Writes a string with specified length and offset from SRAM to UART.
* Uses serial_sendc
* Example:
* writeStringLength("RP6 Robot Sytem\n",16,0);
* // would output: "RP6 Robot Sytem\n"
* writeStringLength("RP6 Robot Sytem\n",11,4);
* // would output: "Robot System"
* writeStringLength("RP6 Robot Sytem\n",40,4);
* // would output: "Robot System\n"
* // No matter if the specified length is 40 characters!
*/
void writeStringLength(char *string, uint8_t length, uint8_t offset)
{
for(string = &string[offset]; *string && length; length--)
serial_sendc(*string++);
}

/**
* Write a number (with specified base) to the UART.
* Example:
* // Write a hexadecimal number to the UART:
* writeInteger(0xAACC,16);
* // Instead of 16 you can also write "HEX" as this is defined in the
* // RP6RobotBaseLib.h :
* writeInteger(0xAACC, HEX);
* // Other Formats:
* writeInteger(1024,DEC); // Decimal
* writeInteger(044,OCT); // Ocal
* writeInteger(0b11010111,BIN); // Binary
*/
void writeInteger(int16_t number, uint8_t base)
{
char buffer[17];
itoa(number, &buffer[0], base);
writeString(&buffer[0]);
}

/**
* Same as writeInteger, but with defined length.
* Example:
* // Write a hexadecimal number to the UART:
* writeIntegerLength(0xAACC, 16, 8);
* // Instead of 16 you can also write "HEX" as this is defined in the
* // RP6RobotBaseLib.h :
* writeIntegerLength(0xAACC, HEX, 8);
* // Other Formats:
* writeIntegerLength(1024,DEC,6); // Decimal
* writeIntegerLength(044,OCT,4); // Ocal
* writeIntegerLength(0b11010111,BIN,8); // Binary
*/
void writeIntegerLength(int16_t number, uint8_t base, uint8_t length)
{
char buffer[17];
itoa(number, &buffer[0], base);
int8_t cnt = length - strlen(buffer);
if(cnt > 0) {
for(; cnt > 0; cnt--, writeChar('0'));
writeString(&buffer[0]);
}
else
writeStringLength(&buffer[0],length,-cnt);
}

/************************************************** ***************************/
// new UART receive functions:
// Data for Ringbuffer

static volatile t_rxbuffer rx_buff;

/**
* UART receive ISR.
* Handles reception to circular buffer, handles errors.
*/
ISR(USART_RXC_vect, ISR_BLOCK) {
if ( ! (UCSRA & ((1<<FE)|(1<<DOR)|(1<<PE)))) {
volatile uint8_t data = UDR;
volatile uint8_t next = ((rx_buff.head + 1) % UART_RECEIVE_BUFFER_SIZE);
if (next != rx_buff.tail) {
rx_buff.ring[rx_buff.head] = data;
rx_buff.head = next;
} else
rx_buff.uart_error=rx_buff.uart_error + 0x80;
}
else {
volatile uint8_t data __attribute__((unused)) = UDR;
rx_buff.uart_error++;
}
}

/**
* Read a char from the circular buffer to a pointer.
* Reset error counter on frame errors, do buffer reset on overflow
* returns 1 if a valid char is received, 0 if not
* Example:
*
* // [...]
* if(getBufferLength())
if (serial_getc(&receivedData[data_position++]))
found_a_char();
* // [...]
*
*/
uint8_t serial_getc(unsigned char *data) {
if (rx_buff.uart_error < 0x80){
if (rx_buff.head == rx_buff.tail)
return 0;
*data = rx_buff.ring[rx_buff.tail];
rx_buff.tail = (rx_buff.tail + 1) % UART_RECEIVE_BUFFER_SIZE;
rx_buff.uart_error = 0;
return 1;
} else {
clearReceptionBuffer();
return 0;
}
}

/**
* compatibility implementation
* old readChar function, use serial_getc instead
* -> there was no way to check if a received char was NULL or the buffer was even empty
* Example:
* // [...]
* if(getBufferLength())
* receivedData[data_position++] = readChar();
* // [...]
*
*/
char readChar(void)
{
unsigned char data = 0;
serial_getc(&data);
return data;
}

/**
* this function copies numberOfChars chars to buf.
* It also returns the number of characters really copied to the buffer!
* Just in case that there were fewer chars in the buffer...
*/
uint8_t readChars(unsigned char *buf, uint8_t numberOfChars)
{
uint8_t i = 0;
while(serial_getc(&buf[i]) && (i != numberOfChars))
i++;
return i;
}

/**
* Returns the current number of elements in the buffer.
* Example:
* s. readChar function above!
*/
uint8_t getBufferLength(void)
{
return ((rx_buff.head + UART_RECEIVE_BUFFER_SIZE - rx_buff.tail) % UART_RECEIVE_BUFFER_SIZE);
}

/**
* Clears the reception buffer - it disables UART Receive interrupt for a short period of time.
* if we execute this, something is going really wrong with the buffer (overflow)
*/
void clearReceptionBuffer(void)
{
UCSRB &= ~(1 << RXCIE);
rx_buff.head = 0;
rx_buff.tail = 0;
rx_buff.uart_error = 0;
UCSRB |= (1 << RXCIE);

// alert the programmer urgently, we are in heavy trouble
}

// EOF


und die passende RP6uart.h


/* ************************************************** **************************
* File: RP6uart.h
* Version: new 1.00
* Target: RP6 Base & Processor Expansion - ATMEGA32 @8.00 or 16.00MHz
* ************************************************** **************************
*/

#ifndef RP6UART_H
#define RP6UART_H

/************************************************** ***************************/
// Includes:

#include <avr/pgmspace.h> // Program memory (=Flash ROM) access routines.
#include <stdlib.h> // C standard functions (e.g. itoa...)
#include <string.h>
#include <avr/io.h> // I/O Port definitions
#include <avr/interrupt.h> // Interrupt macros (e.g. cli(), sei())

/************************************************** ***************************/
// UART

// TX:

uint8_t serial_sendc(unsigned char);
void writeChar(char);
void writeStringLength(char *, uint8_t, uint8_t );
void writeString(char *);
void writeNStringP(const char *);
#define writeString_P(__pstr) writeNStringP((PSTR(__pstr)))

#define UART_SEND_BUFFER_SIZE 32 // Default buffer size is 32!

typedef struct {
volatile uint8_t ring[UART_SEND_BUFFER_SIZE];
volatile uint8_t head;
volatile uint8_t tail;
} t_txbuffer;

#define HEX 16
#define DEC 10
#define OCT 8
#define BIN 2
void writeInteger(int16_t, uint8_t);
void writeIntegerLength(int16_t, uint8_t, uint8_t);


// RX:
#define UART_RECEIVE_BUFFER_SIZE 32 // Default buffer size is 32!

// t_buffer.uart_error shoud be always 0, if it contains Values from 1-127,
// we found Hardware Problems like Baud mismatch and its reseting by serial_getchar.
// if it contains values over 127, we found buffer overruns.
typedef struct {
volatile uint8_t ring[UART_RECEIVE_BUFFER_SIZE];
volatile uint8_t head;
volatile uint8_t tail;
volatile uint8_t uart_error;
} t_rxbuffer;

uint8_t serial_getc(unsigned char *);
char readChar(void);
uint8_t readChars(unsigned char *, uint8_t);
uint8_t getBufferLength(void);
void clearReceptionBuffer(void);

#endif

// EOF

Gruß

Dirk
19.04.2014, 09:45
Hi Rolf,

sieht gut aus. Werde mal in nächster Zeit testen ...

Ich habe deine Lib im "RP6 - Programmierung" Artikel unter Projekte (http://www.rn-wissen.de/index.php/RP6_-_Programmierung#Projekte_2) verlinkt.

inka
20.04.2014, 08:59
hi Rolf,

so neugierig und manchmal ahnungslos ich manchmal bin habe ich nach ISR gegoogelt. Da kam dann

von ISR - Itzehoer Schrott und Recycling GmbH & Co. KG (http://www.isr-itzehoe.de/)
überISR Ingenieurbüro Schlegel & Reußwig GmbH. (http://www.isr-lage.de/)
bis zu ISR- Internationaler StudierendenRat (http://www.isr-uni-bielefeld.de/)

bei der suche nach AVR + ISR war das dann schon besser :-)...

ich habe mich bisher mit der kommunikation über UART nur ganz wenig beschäftigt, habe mir die "RP6Base_SerialInterface_01" und "RP6Base_SerialInterface_02" angeschaut und wollte in absehbarer zeit - da mein RP6 nun dank BT "draht und kabel" unabhängig rumfahren kann - mit dem thema intensiver beschäftigen. Also nicht nur mit dem robby "übers alter plaudern", sondern auch mal wichtigeres tun und tun lassen...

Ist die neue lib in der richtung etwas, was andere, bessere möglichkeiten bietet als die ursprügliche lib?

RolfD
20.04.2014, 21:43
Fachlich sage ich dazu:
ISR steht im alllgemeinen bei Programmierern für "Interupt Service Routine" weshalb in der WinAVR
http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html
dies auch als Macro definiert ist.
Weitere Infos zu ISR bieten auch das Datenblatt des Prozessors oder der vorhandene RP6lib Quellcode, die boardeigene Suche erschlägt dich gar zum Thema ISR.

In der Originaldatei steht auch drin:

* In this new version, the reception functions have been completely rewritten
* and are now Interrupt based with a circular buffer.

und

/**
* UART receive ISR.
* Handles reception to circular buffer.
*/

Folglich wurde die Datei auch schon mal von Arexx bzw. Slyd umgeschrieben und man kann sich ableiten was eine ISR ist.
Ein blick ins changelog unten in der Datei besagt ebenfalls das man da schon was dran geändert hat.

Auf deine Frage bezogen:

Ist die neue lib in der richtung etwas, was andere, bessere möglichkeiten bietet als die ursprügliche lib?
Ja und nein.
Du wirst den Unterschied zwischen den beiden Versionen vermutlich kaum merken.
Du hattest ja anscheinend auch noch nicht die Schwierigkeiten und Beobachtungen wie ich sie einleitend beschrieb.
Meine Version ist eine Alternative... ein Angebot ...kein verbindlicher Fix.
Und natürlich ändert sich was... genau das was ich beschrieben habe.

Jeder kann sich selbst entscheiden ob er die originale Version nutzt, oder die geänderte Fassung verwendet.

Dirk hat ja auch private Änderungen in der RP6control.c angeboten.

* - v. 1.3beta additions by Dirk Ottensmeyer
* - NEW: ADC task, free I/O port functions, tone frequencies


Nebenbei: Normalerweise würde ich Erweiterungen in einer separaten include Datei anbieten aber da hier nicht nur Funktionen hinzugefügt sondern ersetzt werden, geht das nur durch ersetzen der Originaldatei wenn man sein Quellcode nicht auch noch ändern will. Aber es steht auch jedem frei, die Datei anders zu benennen und z.b. über #define und #if umschaltbar zu includieren... oder komplett zu ignorieren...

Kämst du denn auf die Idee, Dirk oder Slyd die gleiche Frage wie mir zu stellen wenn die Änderungen anbieten?
Naja egal...
Ich hoffe, ich konnte helfen.
Gruß und auch frohe Ostern

inka
21.04.2014, 08:01
In der Originaldatei steht auch drin:
das mit den originaldateien und dem englisch wird mit zunehmendem alter und damit einem sich immer vergrößernden abstand zum berufsleben und dort verwendeten fremdsprachen immer schwieriger und anstrengender...


Du wirst den Unterschied zwischen den beiden Versionen vermutlich kaum merken.
Du hattest ja anscheinend auch noch nicht die Schwierigkeiten und Beobachtungen wie ich sie einleitend beschrieb.
nein hatte ich nicht, um im programmieren weiter zu kommen wird ISR aber sicher ein thema sein müssen...


oder komplett zu ignorieren...
die schlechteste der möglichkeiten um mit verbesserungen umzugehen, man muss sie aber verstehen...


Kämst du denn auf die Idee, Dirk oder Slyd die gleiche Frage wie mir zu stellen wenn die Änderungen anbieten?
klar kam ich schon öfters auf diese idee, beide können glaube ich ein lied von singen...

RolfD
21.04.2014, 11:20
Ok, da das Thema ISR für dich Neuland zu sein scheint, versuche ich das noch etwas genauer zu erklären.

Prozessoren arbeiten normalerweise einfach ein Programm ab und fragen dabei Zustände ab.
Auch wenn Prozessoren dabei sehr schnell sind, bedeutet das ggf. warten auf ein Ereignis/Zustand.
Wärend des Wartens kann es nun sein das an anderer Stelle etwas passiert, also ein Ereignis statt findet.
Dafür hat man Interrupts erfunden. Der erste, wichtigste und selbstverständlichste "Interrupt"
ist eigentlich das Reset Signal. Es zwingt den Prozessor die Programmstruktur von vorn abzuarbeiten, und
unterbricht die laufende Arbeit.
Es gibt weitere Interrupts, manche hängen mit externen Leitungen zusammen, manche passieren auf Grund
von Änderungen im Prozessor. Der Unterschied von einem Reset zu einem echten Interruprt wie man es im
RP6 nutzt ist, das man nach einem Interrupt an der Stelle weiter arbeiten kann, wo man vom Interrupt
unterbrochen wurde. Wärend einer Warteschleife kann also auf Anforderung eines Interrupts spezieller Code
abgearbeitet werden und nach Beendigung des Spezialcodes kehrt der Prozessor an seine Warteschleife zurück.
Diesen speziellen Code nennt man ISR. Interrupts haben dabei Eigenschaften.

Interrupts kann man per Software einzeln oder alle ab und an schalten.
Interruptquellen gibt es massenhaft... ich meine so um ca. 20 im RP6 und werden durch bestimmte Ereigisse ausgelöst.
Andere Prozessoren kennen bis zu mehrere hundert und sogar per Software auslösbare.
Allerdings muss man eine ISR nur für die Interrupts haben wenn man diesen speziellen Interrupt auch braucht / nutzt.
Interrupts bzw. die Ausführung der passenden ISR können geschachtelt passieren wenn 2 Interrupts dicht aufeinander folgen.
ISR sollten kurz sein damit schnell wieder zum vorherigen Programmteil zurück gesprungen werden kann.
Das Hauptprogramm merkt nicht das es durch ein Interrupt unterbrochen wurde. Da ISR aber Register und Variablen ändern
können, sollten alle diese Variablen als volatile definiert sein.
Der Compiler sorgt durch die Definition ISR dafür, das die Register im Prozessor vor Ausführung der ISR gesichert und bei beenden
der ISR der alte Zustand in den Registern wieder hergestellt werden.

Eine ISR ist also ein kleines Stück Programm, welches unabhängig vom Hauptprogramm bei Eintreten eines Ereignisses
ausgeführt wird. Nehmen wir als Beispiel einen Radencoder aus der RP6RobotBaseLib.c.


ISR (INT0_vect)
{
mleft_dist++;
mleft_counter++;
}

Der Radencoder leitet sein Signal ENC_R oder L laut Schaltplan an PD2 bzw. PD3. Impulse an PD2 und PD3 führen
daher zum auslösen des jeweiligen Interrupts. Eingeschaltet wurden die Interrupts in der Funktion void initRobotBase(void)
unter // Initialize External interrupts:

Rollt der RP6 nun, werden sehr viele Interrupts ausgelöst und wie in der ISR zu sehen 2 Variablen aufaddiert.
Würden wir an der Stelle keinen Interrupt einsetzen, müsste der Prozessor laufend den Eingang auf Veränderungen abfragen.
Gehen würde das auch aber der RP6 könnte kaum noch was anderes machen.
Also nutzt man da besser Interrupts.

Guckst du dir nun das UART file an, wirst du sehen das zum Empfangen von Zeichen schon eine ISR genutzt wird.
Beim senden wird in der Originaldatei das Senderegister durch eine Warteschleife mit while (!(UCSRA & (1<<UDRE)));
abgefragt. Das bedeutet, der Prozessor wartet mit dem versenden von Zeichen IM HAUPTPROGRAMM bis jeweils das Senderegister leer ist um ein weiteres Zeichen zu versenden.
In der Zeit passieren zwar Interrupts aber er macht eben nichts anderes.
Meine Änderung ist in der Hauptsache der Umbau der Sendefunktion auf ISR, so das das nächste Zeichen aus einem Ringbuffer (32 Byte) "automatisch" bzw. per ISR ins Senderegister kopiert wird sobald das Register frei ist. "Senderegister frei" ist dabei eine der vielen Interrupt quellen. Das Hauptprogramm füllt also den Buffer und die ISR arbeitet das ab.
Es gibt 2 Situationen, die eine Ausnahme erfordern. Buffer voll.... dann muss eben doch gewartet werden bis wieder Platz
im Buffer ist.. oder Buffer leer.. ist nichts zum senden da ...dann muss man den Interrupt abschalten.
Daraus folgt, das man in der Sendefunktion den Interrupt zunächst anschalten muss damit die ISR dann die weitere Arbeit
machen kann... Das ist auch der Unterschied zwischen Radencoder- und UART-ISR ..Die ISR für die Radencoder läuft
immer, die SendeISR nur dann wenn Zeichen zu übertragen sind. Damit wartet aber nicht mehr das Hauptprogramm auf "Senderegister frei". Das verändert natürlich das Laufzeit Timing des Hauptprogramms so lange der Buffer nicht voll ist.
Ein komplexes Betriebssystem könnte nun z.B. auch noch den Buffer vergrößern wenn es auf "Buffer voll" trifft. Für den RP6 ist das aber zu komplex bzw. zu viel Aufwand.

ISR sind also kleine Progrämmchen, die auf ein Event (IRQ oder Interruptrequest) hin unabhängig vom Hauptprogramm
vom Prozessor angesprungen werden. Das ist ein sehr vielseitiges Konzept und kann sogar dafür genutzt werden,
eine Form von Multitasking (Timeslicing) zu nutzen indem per ISR und Timerinterrupt zwischen mehreren Hauptprogrammen
umgeschaltet wird. Beim RP6 wird dies so nicht genutzt, dort laufen aber viele Reaktionen auf Sensoren über ISR.

So.. das war eine kurze Einführung zum Thema ISR aus dem Ärmel geschüttelt. Vielleicht hilft dir das weiter.

Weitere Änderungen von mir betreffen eigentlich nur einen etwas anderen (einfacherem) Ringbuffer Algorithmus, die Erkennung des Zeichen 0x00, welches vorher nicht erkannt werde konnte weil 0x00 als returnwert für "kein char empfangen" stand und ein wenig Kosmetik.
Gruß

inka
21.04.2014, 14:08
Ok, da das Thema ISR für dich Neuland zu sein scheint, versuche ich das noch etwas genauer zu erklären.

danke für diese hilfreiche erklärung. Werde ich sicher noch paarmal lesen...



Rollt der RP6 nun, werden sehr viele Interrupts ausgelöst und wie in der ISR zu sehen 2 Variablen aufaddiert.
Würden wir an der Stelle keinen Interrupt einsetzen, müsste der Prozessor laufend den Eingang auf Veränderungen abfragen.
Gehen würde das auch aber der RP6 könnte kaum noch was anderes machen.
Also nutzt man da besser Interrupts.
was mich sehr reizen würde wäre eine kommunikation mit dem RP6, wenn er etwas weiter entfernt ist. Kabellos kann ich ja jetzt kontakt halten, ich möchte z.b dass er es mitteilt, wenn bestimmte ereignisse eintreten und auch auf anweisungen reagiert. Nicht als RC-auto oder steuerung über die fernbedienung - verstehst Du was ich meine? Ich habe jetzt aber erstmal noch keinen plan wie ich es anfange, nur völlig nebelhafte ideen...



Interrupts kann man per Software einzeln oder alle ab und an schalten.
Interruptquellen gibt es massenhaft... ich meine so um ca. 20 im RP6 und werden durch bestimmte Ereigisse ausgelöst.
Andere Prozessoren kennen bis zu mehrere hundert und sogar per Software auslösbare.
ist z.b. der MRESET (ist ja auch ein interrupt) per software auslösbar?

RolfD
21.04.2014, 18:23
Also so weit ich weiss, ist die Reset Leitung /RESET eine Input Schaltung am Prozessor.
Seite 34 im PDF zur CPU unter "System Control and Reset" beschreibt aber schon allein 5 Quellen, u.a. auch ein Watchdog,
also eine Art Timer der ein Reset auslösen kann. JTAG Adapter und auch der RS232 Adapter von AREXX können die CPU
ebenfalls von extern aus resetten. Technisch ist resetten aber nur das zwangsweise setzen der CPU auf ein definierten Zustand...
Ich kann mir vorstellen das es in Assembler einen Reset Befehl gibt, ich weis aber nicht ob dieser dazu führt,
das auch das externe /Reset Signal bedient wird. Im simpelsten Fall kann man per Software ein Reset bewirken indem man den Reset Vektor anspringt.
In Normalfall verwendet man sowas aber nicht, da man auch jedes mal die CPU initialisieren müsste. Da kann man aber statt dessen auch gleich die CPU in den State bringen, den sie haben soll.
Es geschehen ja nicht irgend welche wunderlichen Dinge beim resetten - wenn man mal von so Dingen wie dem Bootloader absieht.
Da würde ich dich aber thematisch gern an Slyd verweisen weil der Bootloader nach wie vor nicht open source und auch kaum dokumentiert ist.
Man kann sich aber durchaus auch freie Bootloader angucken und lernen... allerdings ist das glaube ich zu speziell für RP6 Anwender und nicht essentiell für das allgemeine Verständnis von Interrupts relevant. Ich hatte den Reset auch nur angeführt um zu zeigen das die aktuelle Programmausführung durch ein externes Signal unterbrochen wird ohne das das Hauptprogramm das mitbekommt. Von einem Reset aus wird nicht zurück ins Programm gesprungen, von einem INT aus durch reti ja. reti = assemblerbefehl am Schluß einer ISR vergleichbar mit "return from interrupt".

Gruß

RolfD
23.04.2014, 12:25
Ich versuche hier mal laut zu denken...
Es gibt ja die stdio Funktionen, welche man evtl. aus anderen Projekten kennt. Leider führen diese Funktionen zu einer doch recht relevanten code Vergrößerung von ca. üblichen 40 byte RAM und 1400 byte Code, allerdings bieten die auch genormte, erprobte, einheitliche Schnittstellen. Wenn man um jedes Byte Platz in der CPU kämpft, sollte man sie vielleicht vermeiden. Klar! Als einfache "Hallo Welt" Write Funktionen reichen die internen aus der RP6 Lib allemal.
Entschlackt man aber die Programme und wirft die RP6 Funktionen raus, spart man auch wieder Platz. Die reale Codegröße im Projekt ist also kleiner als die stdio Funktionen auf den ersten Blick brauchen. Ich schätze mal vielleicht 1000 Byte. Nimmt man noch mal den vorhandenen Aufwand für bisher extra schreib/lese Funktionen bei TWI, LCD und File_IO auf SD und EEPROM in der RP6 lib, verkleinert sich der Platzbedarf letztlich noch mal gegenüber der bisher genutzten Write Geschichten. Man braucht ja letztlich nur eine lese/schreib funktion per "device" und den rest erledigt printf/scanf. Allerdings ist man dann an die Möglichkeiten dieser Funktionen auch zunächst gebunden. Viele größere Projekte bauen allerdings auf die stdio auf.

Eine Ausgabe auf rs232 und alternativ TWI sähe dann so aus:


// Filehandle erstellen
static FILE uart_io = FDEV_SETUP_STREAM (uart_putchar, uart_getchar, _FDEV_SETUP_RW);
static FILE twi_io = FDEV_SETUP_STREAM (twi_putchar, twi_getchar, _FDEV_SETUP_RW);

//Zuweisung von Standardkanälen, kann alles mögliche an devices sein
stdin = stdout = &uart_io;
//Ausgabe auf Standardkanal
printf ("Hello, world!\n");
//Ausgabe auf TWI-Kanal
fprintf (twi_io,"Hello, world!\n");
//jetzt wird TWI-Kanal als Standard Kanal getauscht
stdin = stdout = &twi_io;
//Ausgabe auf UART-Kanal
fprintf (uart_io,"Hello, world!\n");
//Ausgabe auf twi als Standardkanal
printf ("Hello, world!\n");


Das würde natürllich auch mit scanf, also formatierter Eingabe gehen... und es ständen eben alle Funktionen aus der stdio zur Verfügung.
Das Ganze hat neben dem Platzbedarf einen großen Nachteil für Anfänger und die Demo-Programme.
Man müsste entweder die Write Funktionen auf das stio umschreiben was wieder Platz kostet...
oder man müsste die Demos auf stdio umschreiben was viel Arbeit ist.
Die stdio Funktionen bieten auch die Formatierungen der rp6 Write Funktionen und mehr... incl. WriteLong und WriteFloat und ähnlichem Krempel.

Der Nutzen von stdio wächst mit der Komplexität von Ein/Ausgaben, ein Projekt was jedoch nur ab und zu eine debug Meldung absetzt und ansonsten nur als Statemachine Sensoren abfragt, profitiert weniger davon. Aber wenn es auf geregelte Kommunikation ankommt... also bei Funkmodulen, diverse UARTs, i2c zwischen Prozessorboards, Filesystemen usw., ist sie von Vorteil.

Ich hab ein bischen versucht, die Vor und Nachteile abzuwägen.. nun meine Fragen an euch: Würdet ihr auf stdio umsteigen? Besteht Bedarf für eine angepasste Lib? Wie seht ihr das?

Gruß

Dirk
24.04.2014, 18:34
Hi RolfD,

prinzipiell finde ich deinen Vorschlag gut, allerdings habe ich Verständnis-Fragen dazu:

1. Der Hauptzweck mobiler Roboter besteht ja sicher eher nicht darin, Messwerte und Daten in großer Menge ANZUZEIGEN, sondern ihr Verhalten daran auszurichten. Das heißt nicht, dass das Anzeigen von Daten nicht wichtig wäre: Im Gegenteil kann man das gut zur Kontrolle der eigenen Programmierung nutzen. Frage: Braucht man also optimierte Ausgabefunktionen unter der genannten Einschränkung?

2. Durch die Aufteilung der Prozessoren im RP6-System (Base, M32, M128, M256 WiFi) auf jeweils eigene autonome Systeme müßte man ja im Prinzip die printf/scanf Funktionen für jede dieser Plattformen zur Verfügung stellen (das ist ja bei den jetzigen Funktionen auch so). Macht das Sinn?

3. Die printf/scanf Funktionen haben ja ihre Hauptvorteile neben den standardisierten Formatierungen in der einfachen Wählbarkeit des Ein-/Ausgabekanals. Das finde ich natürlich auch reizvoll. Aber: Nicht alle "Kanäle" (I2C, UART, SPI, Filezugriff auf SD, int./ext. EEPROM ...) sind auf allen Plattformen verfügbar. D.h. man würde nicht den Zugriff auf alle Kanäle auf jeder Plattform brauchen. Einverstanden?

4. Reizvoll wäre auch, wenn die verschiedenen Kanäle über das komplette RP6-System von allen Plattformen hinweg angesprochen werden könnten. Das scheitert allerdings daran, dass als alleinige Verbindung zwischen den Plattformen der I2C-Bus vorgesehen ist. Dazu kommen dann nur noch je nach Hardwareausstattung wenige Ein-Ausgabekanäle auf der jeweiligen Plattform. Und: Zur kabelgebundenen Verbindung zum PC wird ausschließlich der UART verwendet, eine Kommunikation ist aber davon abhängig, ob das Interface gerade an die jeweilige Plattform angeschlossen ist. Welche Vorteile könnten printf/scanf Funktionen bei dieser vorliegenden Hardware bringen?

5. Was meinst du mit "Entschlackt man aber die Programme und wirft die RP6 Funktionen raus, spart man auch wieder Platz."? Die meisten Funktionen in den RP6 Libs sind ja nicht Ein-/Ausgabefunktionen. Was sollte man da rauswerfen (ok, die bisherigen Ein-/Ausgabefunktionen ja auf jeden Fall,- die würden ja durch neue Funktionen ersetzt...), ohne dass Funktionalität fehlt?

6. Was schätzt du, wie hoch der Aufwand ist, die Ein-/Ausgabefunktionen und ggf. die Demos neu zu schreiben? Würdest du das unter best. Bedingungen angehen?

7. Würdest du mal eine "Musterfunktion" oder Demo zum Testen hier zeigen?

SlyD
25.04.2014, 09:36
Nett wäre es auf jeden Fall als Alternative. Wirklich vermisst habe ichs aber bislang auch nicht ;-)
Ich hatte das damals absichtlich nicht verwendet - hast ja auch schon gesagt warum (Speicherplatz).

Alle Beispielprogramme umändern halte ich für nicht sinnvoll.
Einfach ein zwei neue besser dazu passende (wo man die Vorteile sieht) würde doch reichen.
Wenn Du das tatsächlich umsetzt, mach evtl. #defines in den Code damit man zwischen beiden Varianten
wählen kann - oder beides verwenden kann - auf der M256 hat man ja z.B. eh Platz ohne Ende da ist das egal.


MfG,
SlyD

RolfD
26.04.2014, 10:12
Hallo Dirk & Slyd
zunächst auf Dirks Fragen bezogen:

Der Hauptzweck...
Also das muss jeder für sich entscheiden... was Hauptzweck ist. Ich glaube aber, das man mit dem RP6 mehr unternehmen kann als Linienfolger oder Bumpergesteuerte Rempelbots zu bauen. Je mehr Vernetzung, um so komplexer die Kommunikation.

Durch die Aufteilung der Prozessoren...
Richtig. Für die M256 sehe ich da auf Grund von ausreichendem Platz auch kein Problem, bei M32 und Base wirds etwas enger aber auch da nutzt das Slave Programm noch nicht 100% des Platzes, folglich ist auch da noch Platz für solche Änderungen vorhanden. Einzig mit der M128 und ihrem InterpreterC wirds kompliziert aber auch da denke ich, wäre sowas machbar. Ich hab zumindest selbst eine M128 und würde mich dann auch mal daran setzen wollen.

Einverstanden?
Richtig. Aber: Man braucht z.B. auf der M32 kein stdio Kanal für die Motorkontrolle aber trotzdem kann man mit Slave und Master von einer m32 aus die Motorgeschwindigkeit regeln.. woran liegt das? Abstrahiere das mal... weil ein Hardware Protokol (i2c) zur Übertragung von Informationen zwischen m32 und Base vorhanden ist! Dazu gehört weiterhin ein Treiberendstück welches auf der Base liegt.. also quasi der i2c-slave .. und der Master.. also quasi der Treiberkopf... betrachtet man nun slave, i2c und master als eine Einheit, so steuert die m32 durchaus den Motor. Ok, Jetzt noch mal zur Frage ob die m32 ein stdio kanal für die Motorsteuerung braucht... die Frage ist eher.. muss es unbedingt so kompliziert laufen wie bisher wenn man was von einem Board aufs andere kriegen will!

Reizvoll wäre auch
Habe ich mit dem vorherigen Punkt eigentlich schon angesprochen. Ja es wäre möglich, in einer 2.ten Ausbaustufe Datenströme als * File auch über die Boardgrenzen hinweg bereitstellen zu können weil es die Printf Funktion nicht interessiert ob da einfach nur "while (!(UCSRA & (1<<UDRE))); UDR = (uint8_t)ch;" oder ein ganzes i2c-master/slave System hinter der "putchar" steckt.

Was meinst du mit ...
Du hast dir ein Teil des Punktes bereits selbst beantwortet. Überleg aber mal wie eine Ausgabe von Infos als Tabelle bisher ausschaut:

* writeString_P("Toggle Bit:");
* writeChar(rc5data.toggle_bit + '0');
* writeString_P(" | Device Address:");
* writeInteger(rc5data.device, DEC);
* writeString_P(" | Key Code:");
* writeInteger(rc5data.key_code, DEC);
* writeChar('\n');


7 Writes... das lässt sich mit printf in einer einzigen Zeile ausgeben! Das meine ich mit entschlacken.

Was schätzt du...
Also ich baue grade für mich die UART Lib dementsprechend um. Die UART Lib ist ja zumindest für die Base und M32 gleich.
Ich werde die Demos sicher nicht anpassen aber ich bin auch dabei, die bisherigen Writes über Kapselfunktionen an printf zu leiten.
Das sieht dann z.B. so aus:

void writeNStringP(const char *pstring)
{
printf_P(pstring);
}
void writeInteger(int16_t number, uint8_t base)
{
char str[17];
printf("%s", itoa(number, str, base));
}


Ich möchte zunächst die UART LIB fertig bekommen bevor ich mich dann an die M32 LCD Funktionen setze.
Weitere Ein/Ausgabe Ziele habe ich für mich noch nicht geplant, ich denke aber das es mit der Zeit auch mehr wird. Großes Fernziel ist tatsächlich aber auch die angesprochene Nutzung von Geräten anderer Boards per io Stream. Dazu muss ich aber auch noch mal den i2c Treiber überarbeiten und von registerbasierter Übertragung auf Stream umstellen.
Für mich stellt sich eigentlich nur die Frage... mach ich das im stillen Kämmerlein so wie meine freeRTOS Geschichten (die übrigends klasse laufen) oder gibts da Interesse auch von anderen die IO Funktionen bezüglich.

Übrigends ist die SendeISR aus dem Quellcode oben noch verbesserbar.
Sie wird nach Ende der Übertragung noch ein mal angesprungen nur um die ISR abzuschalten.
Ich hab sie jetzt so umgebaut, das mit dem letzten Zeichen im Buffer auch direkt die ISR abgeschaltet wird.


ISR(USART_UDRE_vect, ISR_BLOCK) { // wir werden angesprungen, also ist ein char im buffer
UDR = tx_buff.ring[tx_buff.tail]; // char aus buffer auf UDR schreiben
tx_buff.tail = (tx_buff.tail + 1) % UART_SEND_BUFFER_SIZE; // tail um eins hochzählen
if (tx_buff.head == tx_buff.tail) UCSRB &= ~(1 << UDRIE); // sendebuffer leer, isr aus schalten
}

Ein paar Kommentare sind auch noch hinzu gekommen.

Gruß

Dirk
27.04.2014, 09:49
Hi RolfD,


Ich werde die Demos sicher nicht anpassen aber ich bin auch dabei, die bisherigen Writes über Kapselfunktionen an printf zu leiten.
Das finde ich eine gute Idee. Dann könnten die Demos und bisherige eigene Programme und Libs unverändert bleiben.

Wenn du dich da dran machst und das hier komplett öffentlich machen willst, bin ich gern dabei.

Die (Komplett-)Aufgabe wäre dann:

1. RP6Base:
RP6BaseLib -> IRCOMM, int. EEPROM

2. RP6 CONTROL M32:
RP6ControlLib -> LCD, SPI, int. EEPROM
RP6Control_I2CMasterLib -> unverändert (wenn übergeordnete I2C-Funktionen unveränderte Funktion bieten)

3. RP6Base UND M32 (TWI auch für M256):
RP6uart -> UART
RP6I2CmasterTWI -> I2C
RP6I2CslaveTWI -> I2C

3. RP6 M256 WiFi:
RP6M256Lib -> LCD, SPI, int. EEPROM
RP6M256uart -> UART
RP6M256_WIFIlib -> WIFI UART
SDC-Library -> SD-In-/Output (eigene Kapsel-Lib?)
RP6M256_I2CMasterLib -> unverändert (wenn übergeordnete I2C-Funktionen unveränderte Funktion bieten)

4. RP6 CCPRO M128:
RP6CClib.cc, RP6CClib.cbas -> nicht möglich (Code-Interpreter)

RolfD
29.04.2014, 18:27
Hallo,
also falls jemand Lust hat da mit rein zu gucken... kommt ja nen Feiertag und Regenwetter :) Ich poste mal mein aktuellen stand der Lib. Es hat sich bischen was getan.

Die uart.c incl. grober Beschreibung im Dateikopf:

--- entfernt da neue Version ---
und die dazu gehörende uart.h

--- entfernt da neue Version ---
Das ist keine endgültige Fassung, es ist noch nicht alles geprüft und es sind noch Baustellen/Todos und vermutlich Fehler drin.
Zum Testen und Probieren langts aber schon.
@Dirk vielen Dank für die Aufstellung... scheint ein größeres Vorhaben zu werden :)

Verbesserungen und Anregungen gern hier ins Forum...
Wer z.B. Lust hat, eine Ein- und Ausgabe Funktin a la uart_getchar / uart_putchar für ein anderes Device als die UART zu schreiben .. immer her damit.
Ich hab zwar keine M256 aber ich denke, das kriegen wir auch so angepasst.
Möglich wäre z.B. ein Soundausgabe Treiber (sound_putchar) für den Lautsprecher, welcher Ton Ascii Sequenzen wie "c4,500,g3,200" an den Speaker ausgibt.
Oder als HEX... Im Format will ich aber nicht vorgreifen... Nur so mal als Denkanstoß... keine Ahnung ob sowas schon existiert.

Mit der Vorhandenen Lib wäre es dann per stdio möglich, direkt Töne von einem Terminal über den Uart an den Lautsprecher aus zu geben.
Quasi sowas wie ein "Proof of Concept".

Nachtrag: https://www.roboternetz.de/community/threads/47114-RP6Control-M32-Rechteck-Generator?p=453225&viewfull=1#post453225
Die Kombination aus der uart_2 lib von void enterString(void) und uint8_t getInputLine(void) entspricht übrigends fast dem Ansatz mit meiner Ascii line editor Eingabezeile... also dem ASCII Modus bei getchar, nur fehlt dort die Reaktion auf \b was aber nachrüstbar wäre. Also ein Beispiel wie man eine ASCII Eingabezeile auf Anwendungsebene realisiert. Und schon 4 Jahre alt... Ist mir grade so aufgefallen als ich den code durchgesehen habe :)
Gruß

RolfD
05.05.2014, 03:33
Es gibt Neuigkeiten. z.B. xon/xoff im Ascii mode.. und vieles mehr. Diverse Bugs sind auch behoben.
Ich hab versucht, das ganze im File zu beschreiben und zu kommentieren. Für erfahrene Hasen hab
ich viel zu viel geschrieben, für Newbies viel zu wenig. Die Lib ist noch nicht fertig bis aufs letzte
Byte getestet aber sie tut es schon mal ganz gut. Leider meckert das Forum über zu große Dateien,
ich werde die neuen Versionen einfach als File anhängen. Es sieht erst mal nach viel Programmcode
aus aber es ist garnicht so viel. Der eigentliche UART Code incl. der stio dürfte bei 2-3 kb sein.
Dafür kann der Treiber aber auch was...

Program Memory Usage : 4606 bytes 14,1 % Full
Data Memory Usage : 308 bytes 15,0 % Full

Das sind aktuelle Daten aus meinem Testprojekt mit xon/xoff, mit ASCII Edit mode und allem drum
und dran sinds vielleicht noch 500 oder 1000 Byte mehr.

Der Remotrol M32 Master (nicht auf alles stdio umgeschrieben, nur Init eingetragen) braucht mit der Lib:

Program Memory Usage : 13104 bytes 40,0 % Full
Data Memory Usage : 614 bytes 30,0 % Full

Die Lib ist also "abwärtskompatibel" zur originalen RP6 Uart Lib...

28134 28133
Gruß

Dirk
05.05.2014, 10:42
Hi RolfD,
Du legst ja ein irres Tempo vor ... #-o

Ich habe bis jetzt nur sozusagen die Abwärtskompatibilität in Stichproben gecheckt.

Was hast du für ein "Testprogramm", um alle zusätzlichen Features zu testen?
Würdest du das evtl. teilen?
Wenn ja, würde ich auch da im Hintergrund mitmachen.

Oder als prinzipielle Frage:
Für die Kompatibilität mit den RP6-Demos und -Libs reicht es sicher, deren Funktion nach und nach durchzuspielen. Das mache ich, wenn du signalisierst, dass so in etwa ein Endstand der Entwicklung der Lib erreicht ist (und ich Zeit habe).
Mit welchem Testprogramm sollte man darüber hinaus versuchen, die RP6uart-Lib in den Non-RP6 Modi "an ihre Grenzen zu bringen"? Nimmst du "nur" das eine Kurzprogramm, das du in RP6uart.c erwähnt hast?


Aber erst zu meinen "Abwärtskompatibilitäts-Tests":
Mein größter Wunsch dabei wäre diesbezüglich, dass man die RP6uart Lib ohne Änderung im RP6-Modus nutzen kann.
Also sollte der RP6-Modus sozusagen "Default" sein und keine weiteren Initialisierungen in übergeordneten Libs oder Demos brauchen (ich bin faul und würde sonst alles gern unverändert lassen. Ich denke auch, dass die konkrete Nutzung der neuen UART-Lib bei den RP6 Usern im Sinne eines Ersatzes der Original-Lib durch deine Lib davon abhängen wird, ob sie sich komplett abwärtskompatibel integrieren läßt,- die weiteren Funktionen werden danach erst "erkundet").
Nur wenn die Lib in einer ANDEREN (nicht RP6-Modus konformen) Betriebsart genutzt werden soll, müßte der User die Lib entsprechend initialisieren.

RolfD
05.05.2014, 19:50
Hallo Dirk,
also was die testerei angeht... sobald du die lib anstelle der alten nutzt (überschreibst), werden alle Programme mit der neuen lib gebaut.
Da alle alten Schreibfunktionen über Umleitungen geführt werden, sieht das für dich so aus als wenn du eben die alten Funktionen nutzt.. tatsächlich nutzt du aber schon neue...
Das siehst du als Beispiel z.B. daran:


/**
* compatibility implementation
* Writes a null terminated string or buffer from SRAM to stdio.
* Example:
* writeString("RP6 Robot System\n");
*/
void writeString(char *string)
{
printf (string); //->stdio
}


Ein writeString("RP6 Robot System\n"); ruft also tatsächlich schon stdio funktionen auf... , nur merkst du bis auf den Init der stdio nichts davon. Wie man die Init anders löst überlege ich noch, ist aber für mich z.Z. eher eine marginale Frage, hab ich aber im Blick.

Das bedeutet: Für alte Programme beim schreiben ändert sich nichts...

Beim einlesen (readChar, readChars) benutzen die Funktionen NICHT die stdio... aber schon andere neue Programmteile der Lib.

Das bedeutet: Für alte Programme beim lesen ändert sich nichts...

Ergo.. man kann alle alten Programme mit der Lib compilieren ohne das man erst mal viel davon merkt. (Abgesehen von der Init)
Hier noch mal zur Erinnerung die Init Sequenz, welche in die Main oder in die Hardwareinit der Unit.. oder ggf. noch früher geladen werden sollte:


static FILE uart_io = FDEV_SETUP_STREAM (uart_putchar, uart_getchar, _FDEV_SETUP_RW);
static FILE uart_err = FDEV_SETUP_STREAM (uart_putchar, NULL, _FDEV_SETUP_WRITE);
stdin = stdout = &uart_io;
stderr = &uart_err;
UART_Mode(UART_INIT);


Im UART_RP6 Mode (in dem man sich nach der Init erst mal befindet) ändert sich auch nichts (aus Anwendersicht) gegenüber der alten Lib. Interessant wird das erst wenn man die anderen UART-Modi nutzen will.

Da gibts zunächst die Binary Modi mit und ohne Echo, welche ALLE Zeichen von '\0x00' bis '\0xff' zuverlässig annehmen können müssen.. und diese im Echo mode (übrigends über stdio, also umlenkbar) auch wieder zurück werfen, vergleichbar mit einem Loop Back device. (kennt man aus brücken von rx/tx oder dem loopback bzw. pingen im Netzwerk) Mehr können die binary Modes auch erst mal nicht - ich hab jedenfalls z.Z. nicht vor irgendwelche Protokolle dafür zu schreiben. (was aber möglich wäre... von hexdump a la Intel bis TCP/IP ist da alles möglich)
Um die binary Modes zu testen kann man nun einfach ein Terminal wie Realterm oder Zoc nehmen und direkt Daten senden.. bzw. sehen was zurück kommt. Realterm hat ja z.B. auch Zeichen/String Geneartoren, upload von files im Reiter "Send" usw....
Man könnte z.B. auf dem PR6 eine Prüfsumme aus eingehenden Zeichen errechnen und diese anzeigen.. und diese Prüfsumme auch auf dem PC berechnen und dann vergleichen - es sollten gleiche Ergebnisse bei raus kommen... man kann aber auch einfach ein Binärfile zum RP6 im E-Modus schicken, das Empfangene widerum capturen und speichern und dann inhaltlich vergleichen.
Es gibt sicher noch mehr Möglichkeiten Datenstöme beider richtungen messbar gegenüber zu stellen. Man kann aber auch einfach den Binary_Mode statt dem RP6_Mode benutzen. Technisch ist der RP6_Mode und der Binary_Mode für die Anwendung fast das Gleiche. Denn man kann mit "alten Funktionen" auch im Binary Mode arbeiten... man kann aber auch die "neuen" stdio Funktionen nutzen.

Stdio und die UART_Modi haben nichts miteinander zu tun ausser das sie beide in einem File (uart.c) gleichzeitig bereitgestellt werden. Es geht also UART_RP6 mit stdio genau so wie UART_ASCI mit WriteString... Die UART Modi sind verwand mit der Technik wie man files öffnet und schließt... (binary mode, asci mode, schreibend, lesend, wahlfreier zugriff usw...)
der UART_RP6 mode besagt nur, das er nicht die Sende-ISR nutzt.. und daher z.b. nicht xon/xoff fähig ist und auch weitere Dinge des ASCII Mode nicht unterstützt, dafür aber das Timing der alten Lib einhält. Der UART_Mode und die stdio haben sonst wenig miteinander zu tun.

Kommen wir nun zum Punkt.. wie teste ich die Ascii modi. Dafür ist die uart.h wichtig zu verstehen/lesen.
Nun zunächst mal ist der ASCII Modus wenn er per #define remakrt ist, ein Binärmodus in dem nichts passiert ausser das Zeichen gesendet und empfangen werden.
Schaltet man ihn ein in dem man das Remark entfernt (// davor löschen) wird der Modus aktiv. Man betrachte sich.. am besten mit einem C-fähigen Editor wie notepad++ oder der Amtel Studio umgebung mal an wenn man die #ifdefs in der datei uart.c in der funktion uart_getchar() einklappt...
Es werden also stück für stück code teile frei gegebn je nach dem wie man den ascii mode konfiguriert.

Da xon/xoff nur mit einem ASCII Modus funktioniert (ASCII_Mode bedeutet auch, das nur druckbare Zeichen [->siehe macros isachar(), isanum(), usw..<-] verwendung finden, ggf. manchmal sogar nur 7-bit wertig sind), bauen wir uns ein Programm, welches in den UART_ASCII Mode schaltet .. etwa so.. UART_Mode(UART_ASCII); und aktivieren in der uart.h
#define WENEEDTXXONOFF 1 // bremst die SendeISR auf Anforderung des Terminals
#define WENEEDRXXONOFF 1 // bremst das fremde Terminal wegen Buffer voll
falls da ein // vorstehen sollte. Es macht sinn da ein // vor zu stellen wenn man xon/xoff nicht braucht weil das code einspart. So wir haben jetzt xon/xoff in beiden richtungen aktiviert... da man es auch nur pro Richtung separat nutzen könnte...
Das Programm sieht also bis jetzt so aus:


UART_Mode(UART_ASCII);

uint16_t d;

for (d=0;d<10000;d++)
printf("RP6 Robot System termtest %d\n",d);


Das führt jetzt zu einer Ausgabe von 9999 mal 'RP6 Robot System termtest ZAHL \n' untereinander... wenn man sein Terminal auf newlinemode stehen hat.. es könnte aber auch zu einem Versatz in der Ausgabe führen weil dem Terminal das \r in der Ausgabe fehlt. Schalten wir also noch das //#define WENEEDCRLF 1 // erzwingt crlf beim senden -> vorteilhaft bei ZOC/Realterm
frei in dem wir die // entfernen. Alle anderen defines können remarkt (//) bleiben.

So... läuft das Programm und man hat ein Terminal wie Realterm angeschlossen und - wichtig - das ebenfalls auf xon/xoff eingestellt sieht man die Zeilen durch rattern...
drückt man nun strg-s, sendet man ein xoff cvhar an den rp6 und die Ausgabe bleibt stehen... man wartet.. und schickt ein xon mit strg-q .. läuft sie ansatzlos und ohne probleme weiter...
Und schon hat man xon/xoff in Senderichtung getestet.

Für den Receiver-xon/xoff muss man eine große! Datei an den RP6 schicken. Dazu geht man in realterm auf den Reiter "send" und trägt in der erste zeile vor dem button send numbers z.b.
12345678901234567890 ein. Bei repeats etwas tiefer stellt man z.b. 1500 ein.. würde man nun auf send asci drücken, würde man dem RP6 30000 Zeichen aus der sich widerholenden Folge 123... usw. schicken. Da der RP6 nicht einfach so was annimmt, muss man ihm das bei bringen. Z.B. so:



int i=0,j=0,c,z=0;
printf("RP6 Robot System termtest \n");
while(1){
// delay_us(1000);
c=getchar();
z++;
if (c=='r') z=0;
if (c=='s') printf("\nTermtest %d chars\n",z);
if (c=='q') break;
if (i++>100) {
putchar('.');
i=0;
j++;
}
if (j>10) {
putchar('o');
j=0;
i=0;
}
}

Nun.. was macht das Programm? Ich werde dafür kein Nobelpreis bekommen aber es holt chars, vergleicht diese gegen Buchstaben (r,s,q) und zählt ansonsten Zähler hoch bzw. setzt alle 100 Zeichen ein . und alle 1000 Zeichen ein o ins terminal. Startet man das, zeigt es auf Taste s den Zählerwert aller empfangenen Zeichen (incl. dem s), Taste r resettet den Zähler... und Taste q verlässt die Schleife. Nun was kann man damit machen? Wir drücken 5 mal hinter einander s.. und sehen \nTermtest 1 chars\n .. .\nTermtest 2 chars\n usw...
Ok.. nun resetten wir den Zähler mit r .. verspielte Naturen drücken nochmals s um auch das zu prüfen.. usw...
wir können aber nun die 30000 Zeichen aus dem Terminal Send-Reiter auf den RP6 loslassen. Die gelbe Lampe am RS232 Adapter leuchtet.. 30 sek später sie geht aus....
wir drücken s und sehen \nTermtest 30001 chars\n.
Ok.. der RP6 hat mal eben 30000 chars gelesen.. konnte er vorher mit der alten Lib auch.... aber jetzt ... produzieren wir Prozessorlast ! Viel davon... wir nehmen vor dem Delay das //weg.
Was passiert? Nach jedem empfangenen Zeichen macht der Prozessor 1000us bzw. 1ms nichts ausser intensiv im Kreis laufen!
Jetzt eine kleine Rechnung: 38400 Baud entsprechen fast 4000 Zeichen/pro Sek. Wenn ich bei jedem Zeichen 1 ms Pause mache kann ich im Bestfall 1000 Zeichen/sek empfangen. 4000 angebotene Zeichen ist aber deutlich mehr als max 1000 verarbeitbare Zeichen. Ergo... das Terminal überrent uns.. wäre da nicht der xon/xoff modus.. der das terminal bremst. Und mal eine Schätzung: Bei einem Verhältnis von 1:4 und einem Buffer von 32 byte dauert es wie lange bis ein normaler buffer wie in der ursprünglichen uart.c voll bzw. überlaufen ist? Genau.. ca. 40-50 Zeichen. bei 4000 Zechen/sek also ca. 100 mal die Sekunde... selbst mit einem 128er Buffer noch ca. 20 mal die Sek. Und das bei nur einer einzigen Millisekunde CPU Last pro Durchgang!
Wir probieren das also aus.. und auch bei delay_us(2000);, delay_us(5000); oder delay_us(10000); kommen alle 30001 Zeichen immer zuverlässig an.. es dauert nur eben länger.
Und schon haben wir den neuen xon/xoff Receiver erfolgreich getestet.
Wer Spass daran hat kann das auch mal im RP6 oder Binary bzw. ohne xon/xoff mode testen... es kostet ja nur eine Zeile: UART_Mode(UART_RP6);
Wenn in der aktuellen Fassung die LED3 auf dem M32 schnell blinkt, gabs einen Bufferüberlauf!

Langer Rede, kurzer Sinn.. man könnte als Beispiel das Windows-Remotrol Programm z.B. auch mit xon/xoff Unterstützung bauen (unter windows muss man so ein Gedöns ja nicht erst komplett neu schreiben) und würde feststellen das man keine Zeichen mehr verliert und so ein quatsch wie "Hardbeats" nicht braucht.. oder man verbindet 2 Bots mittels bluetooth Radio an rs232 die miteinander reden .. ohne Zeichen zu verlieren.... ich glaub es gibt genug Ideen wie man das nutzen kann...
Die Lib ist für Leute, die nur das (Zeilen)Terminal im Robotloader für Ausgaben nutzen eher weniger interssant. Spannend wirds da, wo ein "richtiges Terminal" wie Realterm oder ZOC zum Einsatz kommt (und man vernünftige Reaktionen vom Terminal bekommt) oder gar Bots mit Bots oder Computer mit Bots reden.
Das eingebaute Robotloadertermial kann ja nicht mal xon/xoff ... wer aber dann sagt.. "brauch ich auch nicht" ... braucht sich meines Erachtens auch nicht über verlorene Zeichen, "bad Timings" und komische Reaktionen im eigenen Programm zu wundern.

So.. erst mal genug... die anderen Dinge bin ich z.Z. selbst noch am testen... aber den ASCII Terminal Mode kann man z.B. auch am besten im Realterm testen...
Dort könnte man z.B. mal ausprobieren, ob strg-d tatsächlich ein EOF sendet oder was weis ich...
Man kann auch alle defines in der uart.h frei schalten.. nur verbraucht man dann evtl. Codesize, die man nicht braucht. Ein Terminal Mode mit edit Funktion ist für Leute die nur mit getchar arbeiten (readChar ist ja auch nichts anderes) jetzt nicht so interssant und ich glaube auch nicht, das jeder sofort ein Basic- oder noch besser ein Turtle-Interpreter schreiben würde. http://de.wikipedia.org/wiki/Programmiersprachen_f%C3%BCr_Kinder#Turtle-Grafik:_Logo.2C_KTurtle_und_Python_Turtle
(obwohl letzteres auf dem RP6 noch Sinn machen würde)

Ich hoffe, ich konnte etwas weiter helfen.
Es sei aber noch gesagt das die Lib vom Funktionsumfang.. also was sie können soll.. im groben schon so weit fertig ist.. es kommen also keine 1000 Änderungen um NEUES rein zu bringen... es kommen schlimmstenfalls nur noch 1000 fixes damit diese Lib das macht was als Funktionsziel abgesteckt ist.
Und.. ich bin dankbar für jeden Hinweis auf verbesserungsfähige Dinge. Es gibt Dinge die kann ich (oder will ich) nicht umsetzen... z.B. einige ASCII Einstellunge für den UART_RP6 Modus.. da muss ich passen. Aber ich denke, das die lib genügend Möglichkeiten bietet .. auch im Zusammenarbeit mit weiteren Treibern ..., um der alten Lib nicht all zu lang nachzutrauern.

Zu printf/scanf und den ganzen stdio Funktionen selbst gibts im Web genug Beispiele... allein die docu zur Winavr Lib wirft schon einiges aus.
Die Lineinput Funktion ganz am Ende von uart.c ist z.B. aus einer IBM Docu zur stdio entnommen und nur geringstfügig angepasst.
Man kann aber nun auch mal sowas angucken, umsetzen, lernen... die stdio gilt eigentlich als Grundlage für JEDEN Programmierer.
http://www.programmingsimplified.com/c-program-examples

Für weitere Geschichten in Zusammenhang von stdio und anderen Geräten muss ich erst weitere passende Treiber bauen.
Ich werde wie schon gesagt als nächstes ein LCD Treiber für die M32 umsetzen, interssant wäre aber auch der schon besagte sound Treiber oder noch besser ein Treiber welcher Fileio auf dem internen eeprom (und damit auch ggf. als Prototyp für externe eeproms, sdcrads und andere Dateispeicher dienen kann) unterstützt.

Neben der stdio über stdin, stdout und stderr gibt es z.B. durchaus Geräte, die stdio komaptibel über 2 Datenströme gleichzeitig gefahren werden.. quasi einem Datensteuerstom und einem Datendatenstrom. Oder 2 Datenströme. Die Frage ist dann nur, wie multiplext man 2 logische Verbindungen (evtl. sogar beide mit unabhängigen schreib und lese Funktion) über ggf. nur eine physische Leitung ohne großen Programmier- und Code Aufwand. Klingt sehr aus den Sternen gegriffen aber eine Kernfrage, wenn ich den TWI/I2C Treiber unter dem Aspekt der stdio noch mal angehen will.

Gruß

NACHTRAG_1:

Es gibt da übrigends noch ein #define WENEEDRP6IO 1 ... irgendwann wenn alles fertig ist und man ein neues Projekt anfängt, wird man damit ALLE "alten Code Teile" auf ein Schlag abschalten KÖNNEN. Ob man.. und wer es.. dann tut, ist jedem selbst überlassen. Der Compiler optimiert zwar mit -o2 jetzt schon ungenutzte Programmteile weg aber das ist dann auch sowas wie "den inneren Schalter umlegen". Obwohl die Lib erst seid ein paar Tagen so läuft, arbeite ich inzwischen lieber mit der stdio (auch weil ich es als EDVler ... nicht ITler - das sind die mit Schlips und Studium welche c# als hardwarenahe Programmierspache lernen - gewohnt bin).

NACHTRAG_2:

Wer Speicher sparen muss/will, kann sich übrigends mit den Linkerflags: -Wl,-u,vfprintf -lprintf_min
eine kleinere Version von der stdio printf laden, allerdings kann die wohl nur nur int und strings verarbeiten.
Wer dagegen auch Support für float braucht, nimmt die Linkerflags: -Wl,-u,vfprintf -lprintf_flt -lm
Im Atmel Studio ist per default die mittelgroße Lib ohne float aber sonst mit allem in den Linkereinstellungen
vorgegeben, braucht man eine andere, nimmt man die bei Linkeroptions raus und trägt sich die andere bei
Linker->Misc->Other Options von Hand ein. Default ist also: -Wl,-u,vfprintf
Keine Ahnung ob sich noch jemand mit Makefiles rumschlägt aber das ist dort ggf. auch zu berücksichtigen.
Die "min Version" spart etwa 1kb code ein, man nimmt sich aber doch einige wichtige konversionen damit.

NACHTRAG_3:
Eine wirklich gut gemachte Info Sammlung zum Thema Terminal ist auch folgendes:
http://www-user.tu-chemnitz.de/~heha/hs/terminal/terminal.htm
Dort ist auch zu finden wie man z.B. xmodem, also ein altes(=erprobtes & weit unterstütztes) paketorientiertes Format für Binärübertragung auf Terminals einfach umsetzt.

NACHTRAG_4:
Falls jemand die stio selbst auch schon für eigene Zwecke erweitern möchte indem eigene Gerätetreiber dafür geschrieben werden, noch mal das Grundprinzip:
Mit:


FILE *fp;
fp = fdevopen(Streamlesefunktion,Streamschreibfunktion) ;

oder mit


FILE *fp;
static FILE stream = FDEV_SETUP_STREAM (Streamschreibfunktion, Streamlesefunktion, Modus); //Funktionsname ohne()
fp=&stream;

kann man sich ein Filehandle bauen, welches dann mit den stdio Funktionen angesprochen werden kann. 3 streams (stdin, stdout und stderr) sind dazu im System schon definiert, lassen sich aber überschreiben und man kann weitere Streams selbst anlegen.


fprintf(fp,"foobar");

Der Modus ist dabei je nach Funktion _FDEV_SETUP_READ, _FDEV_SETUP_WRITE, oder _FDEV_SETUP_RW, Geräte von denen man z.b. nicht liest brauchen auch keine Streamlesefunktion, diese wird dann auf NULL gesetzt und der Modus auf _FDEV_SETUP_WRITE gestellt.
Wichtig ist dabei eigentlich nur das die Funktionen, die den Stream verarbeiten auch alle "Spezialbefehle" können.. also als Beispiel interpretieren und Anwenden von Formatierungen wie (f)printf, im Gegensatz zu (f)puts, was einfach nur ein unformatierten String ausgibt.
Sinn und Zweck ist eigentlich, die IO pro "Gerät" auf 2 Funktionen (in und out) zu bündeln und nicht mit 100 Spezialfuktionen irdendwas irgendwo im System zu verstellen. Statt mit einem Pseudocursor in einem LCD rumzuspringen und das LCD von Hand zu löschen kann man nämlich z.B. auch ganz einfach nach Ausgabe von \n das LCD automatisch löschen und/oder auf sonstige Steuerzeichen wie \t (tabulator) mit einem Zeilensprung reagieren. Dafür sind die Steuerzeichen immerhin mal aus gutem Grund erfunden worden - und zwar zu Zeiten als ein Kleiderschrank-großer Computer kaum mehr Speicher hatte als heute ein AMega32 Prozessor.
Ob man nun von einem UART kilobyteweise Daten im Stream liest.. oder seine 5 Tasten mit einer Einlesefunktion versieht und die mit getchar anspricht, oder seine Sonarentfernungsmessung abfragt , ist dem Programm dann egal. getchar ist getchar...

NACHTRAG_5:
In der nächsten Version wird die M32 auf das empfangene BELL Zeichen hin ebenfalls piepen. Die Base kann das natürlich nicht.
Dafür muss in der uart_getchar() der Bereich:


case '\a': // BEL / strg-g
#ifdef RINGSTRING
#warning ASCII Modus Ersatz fuer das BELL Zeichen wurde mit compiliert
data='\x10'; // wenn RINGSTRING angegeben, ist das \a ungültig
if (tx_buff.uart_modus==UART_EASCII) printf (RINGSTRING); // stattdessen optisches Signal
#endif
break;

gegen:


case '\a': // BEL / strg-g
#ifdef RP6CONTROLLIB_H
sound(230,25,25); // wir laufen auf einer M32 also piepen wir auch
#endif
#ifdef RINGSTRING
#warning ASCII Modus Ersatz fuer das BELL Zeichen wurde mit compiliert
data='\x10'; // wenn RINGSTRING angegeben, ist das \a ungültig
if (tx_buff.uart_modus==UART_EASCII) printf (RINGSTRING); // stattdessen optisches Signal
#endif
break;

ausgetauscht werden.

Gruß

Dirk
05.05.2014, 21:12
Hi RolfD,


Aber ich denke, das die lib genügend Möglichkeiten bietet .. auch im Zusammenarbeit mit weiteren Treibern ..., um der alten Lib nicht all zu lang nachzutrauern.
Ja, das denke ich auch,- zumal man ja wirklich als Anwender keinen Unterschied merkt.
Ich habe jetzt erstmal als "Standard" WENEEDASCII, WENEEDTXXONOFF, WENEEDRXXONOFF deaktiviert und teste so drauflos.
Danke nochmal für die ausführliche Erklärung!

RolfD
15.05.2014, 23:23
So.. wie versprochen.. ein etwas geänderter LCD Treiber für die M32, zur Verwendung mit stdio geeignet.

Zunächst: Was ändert sich? Für Programme die auf der M32 alte Write-Funktionen nutzen - nichts! Es gibt die Schreibfunktionen über Umleitungen wie beim uart Treiber.
Was hat sich geändert? Ich habe die eigentliche Ausgabefunktion zusammen gefasst. Sie nennt sich m32lcd_data() und kann nibbles, Commands und Daten ans Display schicken.
Will man sich die Lib auf ein anderes Display umschreiben, braucht man nur diese Funktion an die Hardware anzupassen. Hier bin ich insbesondere von den alten Angaben zu CPU-Cycles auf echte Zeiten gegangen wie sie im Datenblatt stehen. Das macht es einfacher bei anderen Taktungen.

Es sind neue Funktionen hinzu gekommen, unter anderem die Hauptfunktion lcd_putchar(), welche man braucht um sie im Programm mit stdio zu nutzen.


Eingebunden wird sie mit:

FILE *M32lcd;
static FILE lcdout = FDEV_SETUP_STREAM (lcd_putchar, NULL, _FDEV_SETUP_WRITE);
M32lcd=&lcdout;

Angesprochen z.B. so:

fprintf ( M32lcd , "L\tCD Test:\v%c=%d foo\a\tbar \x80\n" , lcd_xy(1,4) , i++);


Die Zeile ist für ein 2x16 Display zu lang aber es wird auf das LCD ausgegeben: mit printf support "L CD" durch \t Tabulatoren (derzeit 4) getrennt, dann wird mit
\v%c der cursor umgesetzt und auf die position nach "=" Wert i geschrieben, dann "foo" ausgegeben, einmal gebimmelt, mit tabulator bar
drangehängt und am Schluß noch das erste selbstdefinierte Zeichen aus dem GCRAM (0x80) ausgegeben - abgeschlossen von einem Linefeed,
was dazu führt das bei der nächsten ausgabe erst das Display gelöscht wird. Macht so alles kein Zusammenhang, zeigt aber ein wenig,
was man damit anstellen kann. Es gibt dann noch \r für nächste Zeile löschen usw. Und alles in einer Ausgabezeile...

Das funktioniert alles nur, weil intern u.a. neuerdings ein Cursor mitgeführt wird (wo es zuletzt ein kleines, aber gelöstes Problem mit gab).
Den kann man auch auslesen (lcd_getCursorPos) und setzen (lcd_setCursorPos), ferner wird mit \v ein kumilierter Cursor verwendet,
welcher mit lcd_xy(line,pos) berechnet wird. Ansich ist das einfach nur die Position in einem Display welches nicht in lines unterteilt wäre sondern
alles an einem Stück im Buffer steht. Pos 0 ist erstes Zeichen, Pos 0f das 15te, Pos10 das 16te bzw. 1 in der 2ten Line also line 1.. weil 1 line=0,
Pos 1f daher auch 16 Zeichen in 2 Line... bzw. (1,15) ... Eigenlich müssten auch 4Zeilige Displays laufen wenn sie in der .h eingestellt sind.
Es ist ein Line Wrapping mit Übertrag eingebaut so das man nur auf dem sichtbaren Display Ram schreibt und nicht in den Buffer. Im H-File ist das Display auch auf andere Formate (ungetestet) über Defines zu konfigurieren. Es gibt zudem eine Funktion zum einlesen von selbst definierten Grafik chars (8 Zeichen möglich)
Und zu guter Letzt gibts noch eine Wheel Funktion die ein laufenden Cursor, Sanduhr oder Debug Zähler immitiert. (wirds so ähnlich in der UART Lib auch noch geben da es sich beim debuggen sehr bewährt hat) Ich bin dabei den Code dieser Lib noch zu testen...
Zudem enthält die Lib wieder die 4 LIB2 Funktionen von Dirk. Für diese muss übrigends das Modul libm eingebunden sein wie ich jetzt gelesen habe.
In der .h datei gibts noch ein Flag das man ähnlich dem ASCII/BINARY Mode aus dem UART Treiber nutzen bzw. zur compile Zeit, nicht Laufzeit... einstellen kann #define LCDMODE LCD_ASCII // Oder LCD_BINARY
Im Binary Mode werden die Steuerzeichen nicht verarbeitet, da funktioniert nur das Linewrapping, im ASCII Mode werden die Steuerzeichen: \a \t \r \v%c DEL \b \f und Zeichen 0xFF verarbeitet. Die Funktionen stehen im Code noch mal erläutert. Hinter einem \v MUSS immer sofort danach ein Wert als char %c mit kumulierter Cursorposition folgen, dieser lässt sich mit lcd_xy wie gesagt auch berechnen.

So.. wie nutzt man das?
Man sucht sich in der vorher gesicherten RP6ControlLib.c den Abschnitt von // LCD bis // Keypad:
und wirft alles dazwischen raus. Dann fügt man den Code unten genau dort wieder ein.
Das ganze ebenso mit der .h Datei unten.

Was hat man am Ende davon? Naja einen ziemlich flexibel nutzbaren LCD Treiber mit verbessertem Timing für die M32, der Möglichkeit bietet, das Ganze mit stdio einzubinden.

Was kann der Treiber nicht? Nun er kann nicht mit dem Buffer Mode bzw. moving cursor arbeiten, den die Displays angeblich können. Das bekam ich bei meinen 2 Displays (Nachbauten) nicht zum laufen. Ebenfalls konnte ich den 5x10 Mode und andere Zeilen/Reihengrößen nicht testen.

Ich überlege, ob ich nicht ein komplettes Lib Paket als ZIP mit allen Änderungen raus geben soll. Ich möchte allerdings die Sachen, die ich da nun geschrieben habe auch erst etwas genauer testen. Die Lib verbraucht übrigends nicht viel mehr Speicher als die alten Funktionen wenn man sie in den Binary Mode schaltet. Im ASCI Mode sinds ein paar Byte mehr aber dafür kann die Lib aber auch wieder vieles was bisher nicht ging oder umständlich und damit ebenfals codefressend umzusetzen war.

Der Treiber kann auch bei geänderter Ausgabefunktion für andere LCDs verwendet werden, allerdings sollte man bei gleichzeitiger Nutzung von 2 LCDs darauf achten, das man nur die Ausgabefunktion selktierbar macht und nich den ganzen Treiber 2 mal anlegt. Ich habe es noch nicht ausprobiert aber das könnte über den bisher ungenutzten *Stream Parameter der lcd_putchar() gehen. Dann spart man wieder ein Haufen code da wieder ein Sack voll spezial-Write-Funktionen entfallen können. Wer übrigends nur mit stdio arbeiten will.. es gibt da immer noch das define WENEEDRP6IO ... auch in der LCD lib.



// LCD
//Einstellung für vorgeladene ASCII Zeichen im Display
#define PRELOADLCDCHARS 1

#if PRELOADLCDCHARS==1
const uint8_t chrdata[64] MEM = {
// LCD_GC_CHAR0 124=|
0b00000100,
0b00000100,
0b00000100,
0b00000100,
0b00000100,
0b00000100,
0b00000100,
0b00000000,
// LCD_GC_CHAR1 252=ü
0b00001010,
0b00000000,
0b00010001,
0b00010001,
0b00010001,
0b00010011,
0b00001101,
0b00000000,
// LCD_GC_CHAR2 228=ä
0b00001010,
0b00000000,
0b00001110,
0b00000001,
0b00001111,
0b00010001,
0b00001111,
0b00000000,
// LCD_GC_CHAR3 246=ö
0b00001010,
0b00000000,
0b00001110,
0b00010001,
0b00010001,
0b00010001,
0b00001110,
0b00000000,
// LCD_GC_CHAR4 220=Ü
0b00001010,
0b00010001,
0b00010001,
0b00010001,
0b00010001,
0b00010001,
0b00001110,
0b00000000,
// LCD_GC_CHAR5 196=Ä
0b00001010,
0b00001110,
0b00010001,
0b00010001,
0b00011111,
0b00010001,
0b00010001,
0b00000000,
// LCD_GC_CHAR6 214=Ö
0b00001010,
0b00001110,
0b00010001,
0b00010001,
0b00010001,
0b00010001,
0b00001110,
0b00000000,
// LCD_GC_CHAR7 223=ß
0b00001100,
0b00001010,
0b00001110,
0b00001001,
0b00001001,
0b00001010,
0b00001000,
0b00000000
};
#endif
const char lcd_wheelchar[] MEM = ".,oO0*0Oo,. "; // sinnvoll wäre auch noch const char wheel[] = "0123456789ABCDEF";
const uint8_t LCDLineAdr[] = {LCD_START_LINE1, LCD_START_LINE2, LCD_START_LINE3, LCD_START_LINE4};
#define LCDwheelsize (sizeof(lcd_wheelchar)-1)
static uint8_t lcd_cpos; // nächste Schreibadresse bzw. Cursorposition im Display
static uint8_t lcd_line;

/**
* Sendet Daten an das M32LCD, für andere LCD Anschlüsse muss nur die Ausgabe per writeSPI angepasst werden
*/
void m32lcd_data( uint8_t data, uint8_t mode )
{
if (mode!=LCD_SINGLE) {
if (mode==LCD_DATA) PORTB |= LCD_RS;// RS setzen / data
else PORTB &= ~LCD_RS; // RS löschen / byte cmd
externalPort.LCDD = data >> 4;
writeSPI(externalPort.byte);
PORTD |= STR;
PORTB |= LCD_EN;
PORTD &= ~STR;
_delay_us(LCD_ENABLE_US);
PORTB &= ~LCD_EN;
} else PORTB &= ~LCD_RS; // RS löschen / nibble cmd
externalPort.LCDD = data & 0x0f;
writeSPI(externalPort.byte);
PORTD |= STR;
PORTB |= LCD_EN;
PORTD &= ~STR;
_delay_us(LCD_ENABLE_US);
PORTB &= ~LCD_EN;
_delay_us(LCD_ENABLE_US);
if (mode==LCD_DATA){
_delay_us( LCD_WRITEDATA_US );
lcd_cpos++; // Cursor increment
}
else _delay_ms( LCD_WRITECMD_MS );
}

/**
* Initialize the LCD. Always call this before using the LCD!
*/
void initLCD(void)
{
uint8_t i;
// _delay_ms(LCD_BOOTUP_MS , LCD_SINGLE); No need for Power ON delay as usually the
// Bootloader should have been executed before...
PORTB &= ~LCD_EN;
PORTB &= ~LCD_RS;
_delay_ms(15);
for(i=0;i<2;++i) {
m32lcd_data(0x3 , LCD_SINGLE);
_delay_ms(2);
} //Reset
// 4-bit Modus aktivieren
m32lcd_data(0x2 , LCD_SINGLE);
_delay_ms(2);
// 4-bit Modus / 2 Zeilen / 5x7
m32lcd_data( LCD_SET_FUNCTION | LCD_FUNCTION_4BIT | LCD_FUNCTION_2LINE | LCD_FUNCTION_5X7 , LCD_CMD);
// Display ein / Cursor aus / Blinken aus
m32lcd_data( LCD_SET_DISPLAY | LCD_DISPLAY_ON | LCD_CURSOR_OFF | LCD_BLINKING_OFF , LCD_CMD);
// Cursor inkrement / kein Scrollen
#if LCD_WRAP_LINES==1
m32lcd_data( LCD_SET_ENTRY | LCD_ENTRY_INCREASE | LCD_ENTRY_NOSHIFT , LCD_CMD);
#else
m32lcd_data( LCD_SET_ENTRY | LCD_ENTRY_INCREASE | LCD_ENTRY_SHIFT , LCD_CMD);
#endif
// vordefinieren der 8 Sonderzeichen
#if PRELOADLCDCHARS==1
uint8_t eeval;
m32lcd_data( LCD_SET_CGADR , LCD_CMD );
for (i=0; i<64; i++ ) {
#if MEMORY==0
eeval = pgm_read_byte(&chrdata[i]);
#else
eeval = eeprom_read_byte(&chrdata[i]);
#endif
m32lcd_data( eeval , LCD_DATA);
}
#endif
// Clear
lcd_cpos=0;
lcd_line=0;
m32lcd_data(LCD_CLEAR_DISPLAY, LCD_CMD);
}

#ifdef WENEEDRP6IO
/**
* Clears the whole LCD!
*/
void clearLCD(void)
{
lcd_cpos=0;
lcd_line=0;
m32lcd_data(LCD_CLEAR_DISPLAY , LCD_CMD);
}

/**
* Write a single character to the LCD.
*/
void writeCharLCD(uint8_t ch)
{
m32lcd_data(ch , LCD_DATA);
}

/**
* Writes a null terminated string from flash program memory to the LCD.
* You can use the macro writeStringLCD_P(STRING); instead, this macro
* ensures that the String is stored in program memory only!
* Example:
* writeNStringLCD_P(PSTR("RP6 Control"));
* // There is also a Macro that makes life easier and
* // you can simply write:
* writeStringLCD_P("RP6 Control");
*/
void writeNStringLCD_P(const char *pstring)
{
uint8_t c;
for (;(c = pgm_read_byte_near(pstring++));m32lcd_data(c , LCD_DATA));
}

/**
* Writes a String from SRAM to the LCD.
*/
void writeStringLCD(char *string)
{
while(*string)
m32lcd_data(*string++ , LCD_DATA);
}

/**
* Writes a string with specified length and offset from SRAM to the LCD.
* If it is a null terminated string, output will be stopped at the
* end. It does not need to be null terminated, but it is recommended
* to use only null terminated strings/buffers, otherwise the function could
* output any SRAM memory data stored after the string until it reaches a 0
* or the specified length!
* Example:
* writeStringLength("RP6 Robot Sytem",16,0);
* // would output: "RP6 Robot Sytem\n"
* writeStringLength("RP6 Robot Sytem",11,4);
* // would output: "Robot System"
* writeStringLength("RP6 Robot Sytem",40,4);
* // would output: "Robot System"
* // No matter if the specified length is 40 characters!
*/
void writeStringLengthLCD(char *string, uint8_t length, uint8_t offset)
{
for(string = &string[offset]; *string && length; length--)
m32lcd_data(*string++ , LCD_DATA);
}

/**
* Write a number (with specified base) to the LCD.
* Example:
* // Write a hexadecimal number to the LCD:
* writeInteger(0xAACC,16);
* // Instead of 16 you can also write "HEX" as this is defined in the
* // RP6RobotBaseLib.h :
* writeInteger(0xAACC, HEX);
* // Other Formats:
* writeInteger(1024,DEC); // Decimal
* writeInteger(511,OCT); // Ocal
* writeInteger(0b11010111,BIN); // Binary
*/
void writeIntegerLCD(int16_t number, uint8_t base)
{
char buffer[17];
itoa(number, &buffer[0], base);
writeStringLCD(&buffer[0]);
}

/**
* Same as writeInteger, but with defined length.
* This means this routine will add leading zeros to the number if length is
* larger than the actual value or cut the upper digits if length is smaller
* than the actual value.
* Example:
* // Write a hexadecimal number to the LCD:
* writeIntegerLength(0xAACC, 16, 8);
* // Instead of 16 you can also write "HEX" as this is defined in the
* // RP6ControlLib.h :
* writeIntegerLength(0xAACC, HEX, 8);
* // Other Formats:
* writeIntegerLength(1024,DEC,6); // Decimal
* writeIntegerLength(511,OCT,4); // Ocal
* writeIntegerLength(0b11010111,BIN,8); // Binary
*/

void writeIntegerLengthLCD(int16_t number, uint8_t base, uint8_t length)
{
char buffer[17];
itoa(number, &buffer[0], base);
int8_t cnt = length - strlen(buffer);
if(cnt > 0) {
for(; cnt > 0; cnt--, writeCharLCD('0'));
writeStringLCD(&buffer[0]);
}
else
writeStringLengthLCD(&buffer[0],length,-cnt);
}

/**
* This function is useful for displaying text screens on the LCD.
* It clears the whole LCD and writes the two Strings to line 1 and
* line 2.
*/
void _showScreenLCD_P(const char *line1, const char *line2)
{
clearLCD();
writeNStringLCD_P(line1);
lcd_line=1;
m32lcd_data(LCD_SET_DDADR + LCDLineAdr[lcd_line] + lcd_cpos, LCD_CMD);
writeNStringLCD_P(line2);
}

/**
* Sets the cursor position on LCD.
*/
void setCursorPosLCD(uint8_t line, uint8_t pos)
{
if (line < LCD_LINES) {
lcd_line=line;
lcd_cpos=pos;
m32lcd_data(LCD_SET_DDADR + LCDLineAdr[line] + pos, LCD_CMD);
}
}

/**
* Clears some characters after the given position.
*/
void clearPosLCD(uint8_t line, uint8_t pos, uint8_t length)
{
lcd_line=0;
lcd_cpos=0;
m32lcd_data(LCD_SET_DDADR + LCDLineAdr[lcd_line] + lcd_cpos, LCD_CMD);
while(length--)
m32lcd_data(' ' , LCD_DATA);
}
/************************************************** ***************************/
// die uart_2 lib für LCD
/************************************************** ***************************/
void writeLongLCD(int32_t number, uint8_t base) {
char buffer[33];
ltoa(number, buffer, base); //->stdio
writeStringLCD(&buffer[0]);
}

void writeLongLengthLCD(int32_t number, uint8_t base, uint8_t length) {
char buffer[33];
ltoa(number, &buffer[0], base);
int8_t cnt = length - strlen(buffer);
if(cnt > 0) {
for(; cnt > 0; cnt--, writeCharLCD('0'));
writeStringLCD(&buffer[0]);
} else writeStringLengthLCD(&buffer[0], length, -cnt);
}

void writeDoubleLCD(double number, uint8_t width, uint8_t prec) {
char buffer[width + 1];
dtostrf(number, width, prec, buffer);
writeStringLCD(&buffer[0]);
}

void writeDoubleExpLCD(double number, uint8_t prec, uint8_t flags) {
char buffer[prec + 8];
dtostre(number, buffer, prec, flags);
writeStringLCD(&buffer[0]);
}
#endif

// die neuen LCD Funktionen
void lcd_CalcCursorUP(uint8_t setcursor){ //Boundary check upward
if ( lcd_cpos >= LCD_LINE_LENGTH ) { //linewrap prüfen und nächste Zeile setzen
lcd_cpos=0;
if (lcd_line >= LCD_LINES) lcd_line=0; else lcd_line++;
setcursor=true;
}
if (setcursor==true) m32lcd_data(LCD_SET_DDADR + LCDLineAdr[lcd_line] + lcd_cpos, LCD_CMD);
}
void lcd_CalcCursorDOWN(void){ //Boundary check downward
if (lcd_cpos > 0 ) lcd_cpos--;
else {
if (lcd_line > 0) lcd_line--;
else lcd_line=LCD_LINES-1;
lcd_cpos=LCD_LINE_LENGTH-1;
}
m32lcd_data(LCD_SET_DDADR + LCDLineAdr[lcd_line] + lcd_cpos, LCD_CMD);
}

/************************************************** ***************************/
void lcd_putxy(char c, uint8_t line, uint8_t pos) //gibt Zeichen an xy aus
{
lcd_line=line;
lcd_cpos=pos;
lcd_CalcCursorUP(true);
m32lcd_data(c , LCD_DATA);
lcd_CalcCursorUP(false);
}

int16_t lcd_putchar(char c, __attribute__((unused)) FILE *stream)
{
//*********
#if LCDMODE==LCD_ASCII
static bool nl_seen;
static bool cursor_seen;
uint8_t i;

if (cursor_seen) { // erstes Zeichen nach \v, Cursorposition also \v%c
lcd_cpos = c % LCD_LINE_LENGTH;
lcd_line = (c / LCD_LINE_LENGTH) % LCD_LINES;
lcd_CalcCursorUP(true);
cursor_seen = false; //ok es geht weiter im String
return 1; // darf nicht zur weiteren Auswertung gelangen.
} else { // wenn keine Cursorposition, dann normales Zeichen
switch (c) { // Steuerzeichen nach ISO/IEC 6429
case '\a': // BELL Zeichen
sound(230,25,25);
break;
case '\t': // nächsten Tabulator ansteuern
lcd_cpos=lcd_cpos+LCD_TAB_SIZE-(lcd_cpos % LCD_TAB_SIZE);
lcd_CalcCursorUP(true);
break;
case '\r': // wagenrücklauf, nächste Zeile löschen
lcd_line++;
lcd_cpos=0;
lcd_CalcCursorUP(true);
for(i=0;i<LCD_LINE_LENGTH;i++) m32lcd_data(' ' , LCD_DATA);
lcd_cpos-=LCD_LINE_LENGTH;
lcd_CalcCursorUP(true);
break;
case '\v': //set cursor line, nächstes "char" ist der kumulierte cursorwert
cursor_seen = true;
break;
case '\x7f': // ist zwar ein Zeichen im Displayzeichensatz, aber laut ASCII das DEL char!
lcd_CalcCursorDOWN();
m32lcd_data(' ' , LCD_DATA);
case '\b': // 1 Zeichen zurück gehen, nicht löschen entspricht <- [cursor].
lcd_CalcCursorDOWN();
break;
case '\xff': // steht für signed -1, EOF, ALTSPACE, hier als "jumpover" bzw. SPACE ohne löschen / [cursor] ->
lcd_cpos++;
lcd_CalcCursorUP(true);
break;
case '\n': // indirekter löschbefehl
nl_seen = true;
break;
case '\f': // direkter löschbefehl
lcd_cpos=0;
lcd_line=0;
m32lcd_data(LCD_CLEAR_DISPLAY , LCD_CMD);
break;
default:
if (nl_seen && c != '\n') { // First character after newline, clear display and home cursor.
lcd_cpos=0;
lcd_line=0;
m32lcd_data(LCD_CLEAR_DISPLAY , LCD_CMD);
nl_seen = false;
}
#if PRELOADLCDCHARS==1
switch (c) { // Umsetzungstabelle für Sonderzeichen laut ISO/IEC 8859-1/15
case 124: // LCD_GC_CHAR0 124=|
c=0x80+LCD_GC_CHAR0;
break;
case 252: // LCD_GC_CHAR1 252=ü
c=0x80+LCD_GC_CHAR1;
break;
case 228: // LCD_GC_CHAR2 228=ä
c=0x80+LCD_GC_CHAR2;
break;
case 246: // LCD_GC_CHAR3 246=ö
c=0x80+LCD_GC_CHAR3;
break;
case 220: // LCD_GC_CHAR4 220=Ü
c=0x80+LCD_GC_CHAR4;
break;
case 196: // LCD_GC_CHAR5 196=Ä
c=0x80+LCD_GC_CHAR5;
break;
case 214: // LCD_GC_CHAR6 214=Ö
c=0x80+LCD_GC_CHAR6;
break;
case 223: // LCD_GC_CHAR7 223=ß
c=0x80+LCD_GC_CHAR7;
break;
}
#endif
if ((c >= ' ' && c <= '\x87')) { // ist das ein verwertbares Zeichen incl. CGCHAR?
m32lcd_data(c & 0x7f , LCD_DATA); // ggf. das "Steuerzeichenvehikel" für CGCHAR (0x80+CGCHAR) ausmaskieren
lcd_CalcCursorUP(false); //Linewrap berechen...
} else printf ("%c %x. ",c,c);
break;
}
}
//*********
#elif LCDMODE==LCD_BINARY
m32lcd_data(c , LCD_DATA); //nur ausgeben und cursor boundary berechnen/setzen
lcd_CalcCursorUP(false); //cursor boundary berechnen/setzen
#endif
return 0;
}

void lcd_wheel(uint8_t line, uint8_t pos) //sowas wie Sanduhr/Kringel oder rotierender Cursor, gut zum debuggen
{
static uint8_t i=0;
m32lcd_data(LCD_SET_DDADR + LCDLineAdr[line] + pos, LCD_CMD);
#if MEMORY==0
m32lcd_data(pgm_read_byte(&lcd_wheelchar[i++]), LCD_DATA);
#else
m32lcd_data(eeprom_read_byte(&lcd_wheelchar[i++]), LCD_DATA);
#endif
lcd_cpos-=1; // Cursor korrigieren, war ja kein reguläres char im Display
if (i==LCDwheelsize) i=0;
m32lcd_data(LCD_SET_DDADR + LCDLineAdr[lcd_line] + lcd_cpos, LCD_CMD);
}

uint8_t lcd_getCursorPos(void) // gibt aktuelle cursorposition als kumulierten Wert zurück
{
return ((lcd_line*LCD_LINE_LENGTH) + lcd_cpos);
}

void lcd_setCursorPos(uint8_t data) // setzt aktuelle cursorposition als kumulierten Wert
{
lcd_cpos = data % LCD_LINE_LENGTH;
lcd_line = (data / LCD_LINE_LENGTH) % LCD_LINES;
lcd_CalcCursorUP(true);
}

uint8_t lcd_xy(uint8_t line, uint8_t pos) // gibt x/y cursorposition als kumulierten Wert zurück
{
return ((line*LCD_LINE_LENGTH) + pos);
}

/*
* Erzeugt selbst definierte Zeichen im CGRAM, Beisp.:
* const uint8_t chrdata0[8] EEPMEM = {
* 0b00000000,
* 0b00000000,
* 0b00001010, // X X
* 0b00011111, // XXXXX
* 0b00001110, // XXX
* 0b00000100, // X
* 0b00000000,
* 0b00000000};
*
* lcd_generatechar(LCD_GC_CHAR0, chrdata0); //erzeugt 5x7 oder 5x10 char (+cursorline)
* Die Funktion erkennt selbst wie viel Byte einzulesen sind
* für 5x10 Zeichen muss das bitarray 11 lines haben und man kann nur jedes 2.te GCCHAR nutzen.
* Also LCD_GC_CHAR0, LCD_GC_CHAR2, LCD_GC_CHAR4 und LCD_GC_CHAR6
*/

void lcd_generatechar( uint8_t code, const uint8_t *data)
{
uint8_t i;
m32lcd_data( LCD_SET_CGADR | (code<<3) , LCD_CMD ); // Startposition des Zeichens einstellen
for (i=0; i<sizeof(data); i++ )
m32lcd_data( data[i] , LCD_DATA); // Zeichendaten byteweise hoch laden
lcd_cpos-=i; // Cursor korrigieren, war ja kein char im Display
m32lcd_data(LCD_SET_DDADR + LCDLineAdr[lcd_line] + lcd_cpos, LCD_CMD); // Zurück setzen auf alte cursor Position
}

/************************************************** ***************************/
// Keypad:


Das gleiche macht man sinngemäß mit der RP6ControlLib.h und folgendem Code.



// LCD:
// zur Compile Zeit einstellbar:
#define LCD_ASCII 0 // wie ASCII Terminal, Steuerungszeichen werden verarbeitet
#define LCD_BINARY 1 // RAW Binärübertragung ohne Beeinflussung des Datenstroms
// Modus für putchar, wirkt sich auf die Codesize aus.
#define LCDMODE LCD_ASCII // Oder LCD_BINARY
// das Display selbt hat Eigenschaften:
#define LCD_LINES 0x02 //Anzahl Zeilen
#define LCD_DISP_LENGTH 0x10 //sichtbare Anzahl Zeichen pro Zeile IN HEX !!! 16Z=0x10,20Z=0x14,24Z=0x18
#define LCD_WRAP_LINES 1 // 0 buffer wrap?, 1 (default) wrap auf sichtbaren Zeilengrenzen*
#define LCD_TAB_SIZE 0x04 // Tabulator Position */
//*Die Einstellungen für Bufferwrap konnten noch nicht getestet werden

//Timings
#define LCD_BOOTUP_MS 0x28 //40ms
#define LCD_ENABLE_US 0x01 //1us
#define LCD_WRITEDATA_US 0x32 //50us
#define LCD_WRITECMD_MS 0x03 //3ms

//LCD Befehle
// Clear Display -------------- 0b00000001
#define LCD_CLEAR_DISPLAY 0x01
// Cursor Home ---------------- 0b0000001x
#define LCD_CURSOR_HOME 0x02
// Set Entry Mode ------------- 0b000001xx
#define LCD_SET_ENTRY 0x04
#define LCD_ENTRY_DECREASE 0x00
#define LCD_ENTRY_INCREASE 0x02
#define LCD_ENTRY_NOSHIFT 0x00
#define LCD_ENTRY_SHIFT 0x01
// Set Display ---------------- 0b00001xxx
#define LCD_SET_DISPLAY 0x08
#define LCD_DISPLAY_OFF 0x00
#define LCD_DISPLAY_ON 0x04
#define LCD_CURSOR_OFF 0x00
#define LCD_CURSOR_ON 0x02
#define LCD_BLINKING_OFF 0x00
#define LCD_BLINKING_ON 0x01
// Set Shift ------------------ 0b0001xxxx
#define LCD_SET_SHIFT 0x10
#define LCD_CURSOR_MOVE 0x00
#define LCD_DISPLAY_SHIFT 0x08
#define LCD_SHIFT_LEFT 0x00
#define LCD_SHIFT_RIGHT 0x04
// Set Function --------------- 0b001xxxxx
#define LCD_SOFT_RESET 0x30
#define LCD_SET_FUNCTION 0x20
#define LCD_FUNCTION_4BIT 0x00
#define LCD_FUNCTION_8BIT 0x10
#define LCD_FUNCTION_1LINE 0x00
#define LCD_FUNCTION_2LINE 0x08
#define LCD_FUNCTION_5X7 0x00
#define LCD_FUNCTION_5X10 0x04
// Set CG RAM Address --------- 0b01xxxxxx (Character Generator RAM)
#define LCD_SET_CGADR 0x40
#define LCD_GC_CHAR0 0
#define LCD_GC_CHAR1 1
#define LCD_GC_CHAR2 2
#define LCD_GC_CHAR3 3
#define LCD_GC_CHAR4 4
#define LCD_GC_CHAR5 5
#define LCD_GC_CHAR6 6
#define LCD_GC_CHAR7 7
// Set DD RAM Address --------- 0b1xxxxxxx (Display Data RAM)
#define LCD_SET_DDADR 0x80

// Modus für LCD-schreibfunktion (RP6 Spezifisch)
#define LCD_DATA 0 // 8 bit Daten mit RS1
#define LCD_CMD 1 // 8 bit Befehl mit RS0
#define LCD_SINGLE 2 // 4 bit init

#if LCD_WRAP_LINES==1
#define LCD_LINE_LENGTH LCD_DISP_LENGTH // wrap auf Display Sichtgrenzen
#else
#define LCD_LINE_LENGTH 0x40 // wrap auf Buffer Grenzen, Wert=Buffergröße
#endif

//Dispay Daten Adressen für 2x16, 4x16
#define LCD_START_LINE1 0x00
#define LCD_START_LINE2 0x40
#if LCD_DISP_LENGTH==16 // 16er Displays haben wohl andere Adressen für Line 3&4 als 20er
#define LCD_START_LINE3 0x10
#define LCD_START_LINE4 0x50
#else // 20 zeichen/line
#define LCD_START_LINE3 0x14
#define LCD_START_LINE4 0x54
#endif

// neue stdio Ausgabefunktion, alles andere würd über printf und Steuerzeichen gemacht.
int16_t lcd_putchar(char c, __attribute__((unused)) FILE *stream); // Ausgabe mit Steuerzeichen
// neue LCD Funktion
void lcd_wheel(uint8_t line, uint8_t ); // gibt eine Folge von chars als "Wheel" auf x/y aus
uint8_t lcd_getCursorPos(void); // gibt aktuelle cursorposition als kumulierten Wert zurück
void lcd_setCursorPos(uint8_t data); // setzt aktuelle cursorposition als kumulierten Wert
uint8_t lcd_xy(uint8_t line, uint8_t pos); // gibt x/y cursorposition als kumulierten Wert zurück
void lcd_generatechar( uint8_t code, const uint8_t *data); //schreibt Zeichen in CGRAM
// internal use
void m32lcd_data( uint8_t data, uint8_t mode ); // zentrale Schreibfunktion, Mode LCD_DATA/LCD_CMD/LCD_SINGLE
void lcd_CalcCursorUP(uint8_t setcursor); //Boundary check upward
void lcd_CalcCursorDOWN(void); //Boundary check downward

/*
Benutzung von lcd_generatechar()
const uint8_t chrdata0[8] = {
0b00000000,
0b00000000,
0b00001010, // X X
0b00011111, // XXXXX
0b00001110, // XXX
0b00000100, // X
0b00000000,
0b00000000
};
lcd_generatechar(LCD_GC_CHAR0, chrdata0);
m32lcd_data(LCD_GC_CHAR0,LCD_DATA); // als direkte Ausgabe im Display-Zeichensatz 0-7
//oder
fprintf(M32lcd,"LCD char: \x80"); // die 8 CGCHARS liegen also ab 0x80 bis 0x87 im ASCII Zeichensatz
*/

// alte und geänderte Funktionen
void initLCD(void);

#ifdef WENEEDRP6IO
void clearLCD(void);
void clearPosLCD(uint8_t line, uint8_t pos, uint8_t length);
void writeCharLCD(uint8_t ch);
#define writeStringLCD_P(__pstr) writeNStringLCD_P((PSTR(__pstr)))
void writeStringLengthLCD(char *string, uint8_t length, uint8_t offset);
void writeStringLCD(char *string);
void writeNStringLCD_P(const char *pstring);
void _showScreenLCD_P(const char *line1, const char *line2);
#define showScreenLCD(__line1,__line2); ({_showScreenLCD_P((PSTR(__line1)),(PSTR(__line2)) );})
void writeIntegerLCD(int16_t number, uint8_t base);
void writeIntegerLengthLCD(int16_t number, uint8_t base, uint8_t length);
void setCursorPosLCD(uint8_t line, uint8_t pos);
/************************************************** ***************************/
// die uart_2 lib
/************************************************** ***************************/
#define PRECISION 6
#define DTOSTR_ALWAYS_SIGN 0x01
#define DTOSTR_PLUS_SIGN 0x02
#define DTOSTR_UPPERCASE 0x04
void writeLongLCD(int32_t number, uint8_t base);
void writeLongLengthLCD(int32_t number, uint8_t base, uint8_t length);
void writeDoubleLCD(double number, uint8_t width, uint8_t prec);
void writeDoubleExpLCD(double number, uint8_t prec, uint8_t flags);
/************************************************** ***************************/
#endif
#ifndef HEX
#define HEX 16
#endif
#ifndef DEC
#define DEC 10
#endif
#ifndef OCT
#define OCT 8
#endif
#ifndef BIN
#define BIN 2
#endif

/************************************************** ***************************/
// Keys:


NACHTRAG:
Hab mal die aktuelle Version von mir eingestellt. Die Geschichte mit dem eeprom ist jetzt hier verbaut und läuft unter dem Aspekt "Deutsche Umlaute auf dem Display" mit automatischem Vorladen in der Init. An den Funktionen selbst hat sich nicht mehr viel geändert, ein paar Bugs sind gefixt. Der Treiber ist wie der uart Treiber über #defines auch wieder konfigurierbar und z.B. die Umlaute/Sonderzeichen oder Steuerzeichen abschaltbar.
In die RP6CONTROLLIB.H muss auch noch Folgendes eingefügt werden damit es mit dem Speichermanagement klappt. Hinweise zum eeprom auch im entsprechenden Thread oder einfach #define MEMORY suchen und ggf. 0 setzen. Dann landet alles im PGMFLASH.



/************************************************** ***************************/
// Speicherconfig:

#define MEMORY 0 /*0=PROGMEM,1=EEPMEM,2=EEMEM*/

/************************************************** ***************************/
#if MEMORY==0
#define MEM PROGMEM
#elif MEMORY==1
// wenn kein eep import, WEENEEDEEPIMPORT auskommentieren
#define WEENEEDEEPIMPORT 1
#define EEPMEM __attribute__((section(".realeep")))
#define MEM EEPMEM
#elif MEMORY==2
// wenn kein eep import, WEENEEDEEPIMPORT auskommentieren
#define WEENEEDEEPIMPORT 1
#define MEM EEMEM
#endif

Den Code für die eep-Ladefunktion findet ihr in dem 1K EEPROM Thread. Bitte auch da den Nachtrag beachten.
Ich setz mich dann mal an das stdio per TWI. Das kann ein paar Tage dauern. Ich hatte erst vor das über interpretierte Datenströme zu bauen aber da ihr durch Hardware, Remotrol und TWI master/slave Gefummel an die i2c-Registerverwaltung gewöhnt seid, werde ich es wohl erst mal mit virtuellen Registern für die Endgeräte versuchen obwohl die Datenstromvariante schneller, sicherer und leichter umzusetzen wäre.
Ob Daten an den Motortreiber oder an ein UART durchgereicht werden ist ja vom Ablauf ansich egal... und erstes geht ja schon halbwechs mit der alten lib und den Master/Slave Programmen.

Für den UART Treiber gibts z.Z. 3 Todos:


* TODO Lesebuffer 16bittig machen um errorcode aus UCSRA und overruns mitzuspeichern? Compatibilität zur fleury-lib?
* // -braucht doppelt so viel Ram für Buffer (akt. 2x32 gegen 2x64 byte)
* // +Hardwarefehler leichter erkennbar
* TODO Wheelfunktion als HEX oder Dez Ausgabe
* TODO hexfilegenerator für export_epp
* ----

Ich weis aber noch nicht was ich davon umsetze, mir reicht der Treiber so wie er ist und das Feedback ist mager.

Gruß

Thorben W
26.06.2014, 17:12
Moin,
gibt es diese Lib auch schon für RP6M256uart -> UART ich habe gerade dort ein Problem mit Funkmodulen das mir Daten fehlen.
Oder bedarf es dafür noch einen verregneten Feiertag :)

Thorben

RolfD
27.06.2014, 13:13
Hallo Thorben,
Ich habe leider keine M256 und kann daher die Lib nicht 1:1 anpassen. Da nutzt auch kein Feiertag was.
Mit etwas Fingerspitzengefühl kann man sich die jedoch auch selbst anpassen, ich hab ja im Thread schon recht umfangreich erklärt wo es drauf ankommt.
Ich baue z.Z. an einer stdio-ähnlichen Lib welche jedoch ohne die stdio auskommt und mittels I2C & IR die "Gerätetreiber" über Board und Robot Grenzen hinweg erreichbar macht.
Daher verfolge ich die Konzeption der Lib wie gepostet so nicht mehr. Vielleicht werde ich mir aber mal noch ein M256 Modul zulegen wenn die neue Lib zwischen Base und M32 bzw. zwischen meinen 2 Bots zufriedenstellend läuft. Ob es dann noch Sinn macht, die alte Lib anzupassen kann ich erst sagen wenn ich weis ob und was die neue können wird. Es sieht schon gut aus aber ich will noch nichts versprechen.
Gruß

spider84
21.07.2014, 10:01
Hallo RolfD,
bevor du es geschrieben hast wusste ich garnicht, dass die Serial-Funktion (in störender Art) blockieren könnte. Das mit dem stdio war mindestens interessant. Getestet habe ich deine Lib noch nicht, das lag aber auch daran, dass eben kein zip-file wartet welches die Änderungen der UART und des LCD beinhaltet.
Leider kenne ich mich nicht so gut mit dem RP6 aus, weil mich das Studium regelmäßig zwingt, ihn verstauben zu lassen.
Aber an gebufferten Funktionen bin ich stark interessiert: Ziel ist alles per I2C steuerbar zu machen. Habe auf die Erweiterungsplatine gestern einen Header für ein Stellaris Launchpad (80 MHz ARM, bei watterott für 12 Euro) und für ein HC06 Bluetooth gelötet. Der ARM soll Busmaster werden.

RolfD
21.07.2014, 18:00
Hallo,
grundsätzlich gibt es 2 Methoden um Zustände abzufragen. Polling ( CPU fragt dauernd ein Port ab und reagiert bei Ereignis) was immer blockierend wirkt .. oder Interrupt (CPU reagiert durch Interruptcontroller der das Ereignis feststellt). Man kann alle Interruptquellen auch pollen... was in der Originalfassung der RP6Lib beim serial send z.B. gemacht wird, man kann aber auch Interrupt Serviceroutinen ISR schreiben, die einfach nur den Statuswechsel erkennen .. wie z.B. die Counter-ISR für die Lichtschranken oder man kann komplexe Funktionen da rein bauen... letztlich hängt es von der verwendeten Software/Lib und der Philosophie und Knowhow des Programmieres dahinter ab, wie da mit umgegangen wird. Pollen funktioniert immer dann gut, wenn es nur eine relevante Ereignisquelle gibt - das ist beim RP6 als rollender, sich orientierende Bot aber meist nicht gegeben. Allerdings dient der RP6 oft auch nur als Experimentierplattfrom und da reicht das durchaus. Der Autor der Libs hat aber auch schon immer gesagt, das die Libs nur ein Grundgerüst darstellen, welches ausgebaut werden kann und soll.

Es gibt ja noch andere große AVR Projekte... C'T-Bot, Arduino, usw... wo man das ggf. anders regelt. Ein blick über den Tellerrand ist immer hilfreich, allerdings exist1iert im Gegensatz zu solchen Projekten für den RP6 keine wirkliche Community, welche gemeinsam die Libs weiter entwickelt. Deswegen wird es von mir dazu auch keine ZIP-Files geben. Denn ich möchte nicht, das meine Ergebnisse einfach so unreflektiert in anderen Projekten Eingang finden ohne das sich die Leute Gedanken machen oder Feedback geben.

Zu Buffer und IO noch ein Wort, gebufferte IO ist eine feine Sache aber sie birgt ein riesen Nachteil. Nutzt man mehrere Funktionen hintereinander, welche jeweils für sich buffern, frist das Speicher und ist sehr arbeitsintensiv weil ständig Bufferdaten von einem Buffer zu einem anderen umkopiert werden müssen. Die CPU hat ja keine DMA. Insbesondere dann wenn noch Daten per Value und nicht per Reference übergeben werden, kann man seine CPU massiv ausbremsen. Das allein ist schon Grund genug, die verwendeten IO Funktionen genau zu untersuchen und zu verbessern. Aber wie auch schon beim Interrupt - für ein einfaches LED aus/an oder ein paar Buchstaben auf der serial Console reicht das vorhandene und daher macht sich kaum jemand die Mühe.

Es gibt allerdings auch andere Ansätze dem System Beine zu machen z.B. in dem man sich ein RTOS Framework .. also preemtives Multitasking auf den Prozessor anpasst. Dann wartet ein Task halt bis er sein Job gemacht hat und gut is... hab ich auch mal ansatzweise hier beschrieben, es funktioniert einwandfrei. Allerdings ist der Denkaufwand auch höher weil man sich mit (für viele Leute) ganz neuen Konzepten rumschlagen muss.

Aus diesen und weiteren Überlegungen heraus schreibe ich derzeit sowas wie eine gebufferte, stringorientierte stdio, welche in der Lage ist, über eine Art Software Router ähnlich dem rpc auf Unix Befehlsketten an ein anderes Board via I2C und IRDA zu übergeben. Leider ist das langwierig und fehleranfällig da ich kein Profi progger bin.
Ich möchte aber wieder weg vom RTOS und die RP6Libs sind mir zu aufgebläht.
Ja mal sehen was bei raus kommt.
Dein Projekt klingt diesbezüglich auch interessant, ich hatte mal mit einem ARM7 Board am RP6 rumexperimentiert, ich glaube der User Radbruch hier hat auch ein ARM am RP6 laufen.
Gruß

spider84
21.07.2014, 23:16
OK, es ist zwar kein Geheimnis wie solche Buffer funktionieren (gibt ja auch bei mikrocontroller... genug Beispiele und Libs) aber ich kann trotzdem nachvollziehen wenn du keine ZIPs machen willst wenn das Geben/Nehmen-Verhältnis nicht stimmt.

Ob die Hersteller-Bibliotheken drauf bleiben oder nicht werde ich später entscheiden. Das mit dem Regler/Antrieb und diesen Tasks erscheint mir schon etwas eigenwillig (er übersteuert ja leicht beim Anfahren und man kann nicht ganz auf 100%), aber wenn man damit präzise Bewegungen ausführen kann soll es mir recht sein. Vorerst probiere ich's mit, sonst kann ich da nochmal einen Tag Arbeit reinstecken.

Habe inzwischen auf die Base und die M32-Erweiterung jeweils I2C aufgespielt. Jetzt weiß ich auch, wie man das Ganze mit Atmel Studio nutzt...
Für das Stellaris Launchpad und die Energia IDE habe ich nun entsprechende Klassen geschrieben die die Funktionen der Base und der M32 kapseln. Mit dem Bluetooth-Modul gibts noch Schwierigkeiten. Vielleicht liegts aber auch am verwendeten seriellen Ausgang des Boards. Das gilt es noch herauszufinden. Das Ding soll keine große Nummer werden, mehr so privat. Wenn aber daran Interesse besteht, kann ich (wenn's fertig is) ein neues Topic aufmachen und es vorstellen. Bis jetzt ist glaube ich nur der I2C-Code für die M32 kopiert, weils da nix offizielles gibt. Eigentlich würde ich auch gerne ein Mapping realisieren mit solchen China-Ultraschall-Modulen, habe andererseits aber auf einer Seite heute ein Diagram gesehen das nicht auf hohe Qualität von den Dingern schließen lässt. Aber wer weiß, vlt lässt sich ja über Kalman noch was machen, dann lern ich den auch mal richtig.

RolfD
22.07.2014, 08:28
Naja als Sniplets stehen die zu machenden Änderungen und Beispiele von mir ja im Thread... muss man sich eben nur zusammen suchen. Hab auch schon ganze Texte/Libs als Anhang gepostet... also so schwierig ist es ja nicht. Ich hab ja nichts dagegen wenn jemand das benutzt was ich poste. Aber es gibt hier eben auch genug Flachhirne, die alles nur per copy&paste machen und sich beschweren wenn man mehr als 3 Zeilen Text dazu schreibt und die dann noch meckern wenns nicht auf Anhieb läuft.
Mit dem Atmel Studio kannst du auch ARMs proggen, da gibts eine ARM Toolchain. Ich persönlich finde es angenehmer, wenn man nicht dauernd die IDE wechseln muss.
Die Idee, dein Projekt vor zu stellen finde ich gut.
Gruß

oberallgeier
22.07.2014, 09:01
... Mit dem Atmel Studio kannst du auch ARMs proggen, da gibts eine ARM Toolchain ...Sorry wenn ich jetzt als Flachhirn komme: Vor Deinem Hinweis von eben auf Atmel Studio mit ARM-Funktionalität wußte ich nix davon. Nun lese ich in der RELEASE NOTES Atmel Studio 6.1 ( 42130A-MCU-06/2013 ) schon auf der ersten Seite den gleichen Hinweis. Danke, dass Du das hier erwähnt hattest (mich interessiert an diesem Thread hier die UART-Thematik, die ich als Nicht-PR6er mit PDanneggers - modifizierter - Bibliothek gelöst habe).

Studio 6 wollte ich bisher nicht nehmen, weil das m.E. viel Overhead für eine doch eher aufwändige Projektbearbeitung mitbringt, die für meine Hobbyprojekte nicht nur ~head vielleicht sogar ~kill ist. Dachte ich. Aber nun wirds doch überlegenswert. Bisher hatte ich nur Ratschläge gelesen zu einer separaten IDE für den ARM (hier (https://www.roboternetz.de/community/threads/60074-Einstieg-in-STM32F4DISCOVERY-und-ARM-DS5-DS5CE?p=565551&viewfull=1#post565551) und hier (https://www.roboternetz.de/community/threads/65063-F%C3%BCr-lange-Winterabende?p=600737&viewfull=1#post600737)). Deshalb wird diese Thematik auch zumindest noch die Kollegen Gerhard/oderlachs und White_Fox interessieren.

Frage/Bitte: könntest Du mir bitte etwas mehr zu Deinen Erfahrungen mit dem Studio6, besonders im Hinblick auf ARM schreiben, vielleicht hier hin (https://www.roboternetz.de/community/threads/60074-Einstieg-in-STM32F4DISCOVERY-und-ARM-DS5-DS5CE) (wegen OT hier)? Danke im Voraus!

RolfD
22.07.2014, 20:15
Hallo,
ja die Lib vom Dannegger als Basis tuts auch... es gibt viele Libs zur AVR Serial, welche alle nur mit Wasser kochen bzw. Grundfunktionen bieten. Ich selbst hab mir das auch angeeignet indem ich mir verschiedene Libs und die Manuals dazu angesehen habe. Ich versuche aber immer nachzuvollziehen was warum wie gecodet wurde, und dann sieht man halt irgendwann, das einer eine Fehlerprüfung drin hat.. und wie sie funktioniert.. und der andere eben nicht... so als ein Beispiel. Deswegen ist aber mein Code nicht automatisch besser als der von jemand anderem. Benutze also gern die Lib vom Dannegger weiter.
Meine Vorschläge/Änderungen betreffen aber weniger die eigentliche ISR mit dem Buffer - was technisch sogar ähnlich gelöst ist wie ich grade sehe, wobei der algo zur Berechnung der Bufferposition komplizierter ist - sondern eher so Erweiterungen wie xon/xoff in der ISR.
Zum Studio, im Prinzip ist das Atmel Studio ein modifiziertes Visualstudio von MS, eine durchaus erprobte, stabile und vielseitige IDE mit einer Toolchain alla winavr oder winarm, was beides wiederum den gcc als Crosscompiler nutzt. Da gibts kaum Unterschiede zwischen ARM und AVR. Ich denke da gibt es nicht viel zu schreiben, es funktioniert halt und wenn man dran gewöhnt ist, auch gut.
Es gibt viele weitere IDEs die so arbeiten und es gibt viele Leute die eine oder andere IDE präferieren und sicherlich ihre guten Gründe dafür haben.
Ich bin der Meinung, man sollte die Tools nutzen mit denen man am besten zurecht kommt und da ich nun mal aus der Windows und Linux Programmierung komme, sind mir große IDEs wie eclipse oder eben VS nicht fremd. Ich persönlich finde z.B. in makefiles händisch rum zu fingern viel lästiger aber letzlich arbeitet auch die gcc toolchain nur mit makefiles.
Daher will ich da keine Empfehlung zum Umstieg geben. Es kommt drauf an das ein Auto fährt, nicht wie dick die Reifen sind - und Gas geben tut eh der Fahrer.... Um das mal bildlich darzustellen.
Auf dein outing als Flachhirn weis ich nichts zu sagen... du wirst wissen warum du es schreibst.
Gruß

oberallgeier
23.07.2014, 08:27
... Da gibts kaum Unterschiede zwischen ARM und AVR. Ich denke da gibt es nicht viel zu schreiben, es funktioniert halt ...Danke für die Beschreibung. Das mit dem "Flachhirn" sollte meine völlige Unerfahrenheit mit den ARMs hervorheben. Leider lese ich nicht wirklich aus Deinem Post ob und welche ARMs Du mit dem AVRStudio schon programmierst hattest.

Eigentlich wollte ich nämlich wissen ob Du schon konkrete Erfahrungen mit dem Programmieren der ARMs (welche ?) unter AVRStudio gemacht hattest ("... Deinen Erfahrungen mit dem Studio6, besonders im Hinblick auf ARM ..."). Hast Du?

Nochmal danke für Deine Mühe.

RolfD
24.07.2014, 12:08
http://www.atmel.com/products/microcontrollers/arm/default.aspx
Das sind die ARMs, die soweit vorbereitet sind aber wenn man sich damit auseinander setzt, stellt man fest das auch andere Devices möglich sind. Wie gesagt.. WinARM ist eine GCC Toolchain.
Ich selbst hab LPC2138 Boards wie dieses: http://www.etteam.com/product/arm7_lpc2138.html

Gruß