Ich implementiere einen schreibgeschützten I 2 C-Slave auf einem PIC18F4620 . Ich habe einen funktionierenden ISR-Handler für das MSSP-Modul erstellt:
unsigned char dataFromMaster;
unsigned char SSPISR(void) {
unsigned char temp = SSPSTAT & 0x2d;
if ((temp ^ 0x09) == 0x00) {
// State 1: write operation, last byte was address
ReadI2C();
return 1;
} else if ((temp ^ 0x29) == 0x00) {
// State 2: write operation, last byte was data
dataFromMaster = ReadI2C();
return 2;
} else if (((temp & 0x2c) ^ 0x0c) == 0x00) {
// State 3: read operation, last byte was address
WriteI2C(0x00);
return 3;
} else if (!SSPCON1bits.CKP) {
// State 4: read operation, last byte was data
WriteI2C(0x00);
return 4;
} else {
// State 5: slave logic reset by NACK from master
return 5;
}
}
Dies ist nur eine Portierung nach C von einem Teil des ASM-Codes in Anhang B von AN734 .
In meiner Hauptschleife überprüfe ich, ob es neue Daten gibt, wie folgt:
void main(void) {
if (dataFromMaster != 0x00) {
doSomething(dataFromMaster);
dataFromMaster = 0x00;
}
}
Dies führt zu einem Problem, wenn der Master Bytes sehr schnell sendet und neue Daten eingehen, bevor die Hauptschleife ankommt doSomething
. Ich möchte daher einen Puffer implementieren, in dem Daten vom Master gespeichert werden. Ich brauche ein 16-Zeichen-Array mit Nullterminierung (das null
nicht als Befehl für den Slave verwendet wird). Die ISR muss neue Daten in dieses Array schreiben, und die Hauptschleife sollte sie in der Reihenfolge, in der sie empfangen wurden, aus dem Array lesen und das Array löschen.
Ich habe keine Ahnung, wie ich das umsetzen soll. Tust du?
Ich habe keine Erfahrung mit PIC, aber das Problem scheint allgemein genug zu sein. Ich würde ein einfaches Array mit zwei unabhängigen Zeigern in das Array erstellen: einen Lesezeiger und einen Schreibzeiger. Immer wenn Sie ein Byte empfangen, erhöhen Sie den Schreibzeiger und schreiben an der neuen Position; In Ihrer Hauptschleife könnten Sie dann überprüfen, ob der Lesezeiger und der Schreibzeiger gleich sind. Wenn nicht, lesen und verarbeiten Sie einfach aus dem Puffer und erhöhen den Lesezeiger für jedes Byte, bis dies der Fall ist.
Sie können dann entweder die Zeiger auf den Anfang des Arrays zurücksetzen oder sie zum Anfang "überfließen" lassen, wodurch im Wesentlichen ein Ringpuffer entsteht. Dies ist am einfachsten, wenn die Größe des Arrays ein Faktor von 2 ist, da Sie dann einfach beide Zeiger nach ihren Inkrementen bitmaskieren können.
Ein Beispiel (Pseudo-)Code:
volatile unsigned int readPointer= 0;
volatile unsigned int writePointer=0;
volatile char theBuffer[32];
...
//in your ISR
writePointer = (writePointer+1) & 0x1F;
theBuffer[writePointer] = ReadI2C(); // assuming this is the easiest way to do it
// I would probably just read the register directly
...
//in main
while (readPointer != writePointer) {
readPointer = (readPointer+1) & 0x1F;
nextByte = theBuffer[readPointer];
// do whatever necessary with nextByte
}
ReadI2C()
wird im Slave-Modus nicht mehr getan, als darauf zu warten, dass das Buffer Full-Flag gesetzt wird, und dann den Puffer zurückzugeben. (Ich habe diese Funktion nur verwendet, anstatt nur das Register für die Lesbarkeit zu lesen.)Wenn Sie dies richtig machen möchten, ist die beste Lösung, eine Art Ringpuffer zu implementieren .
Beachten Sie jedoch, dass die Implementierung "unterbrechungssicher" sein muss. Dies ist erforderlich, da während der Pufferinhalte in der Hauptschleife verarbeitet werden, jederzeit weitere Daten in Ihrem SPI ISR eintreffen können!
Daher müssen Sie sich möglicherweise mit den Operationen using und "ATOMIC" befassen, volatile
wenn Sie damit nicht vertraut sind.
Aus dem Pseudocode der Antwort von fm_andreas habe ich einen funktionierenden C18-Code erstellt:
#define bufferSize 0x20
static volatile unsigned char buffer[bufferSize] = {0}; // This is the actual buffer
static volatile unsigned char readPointer = 0; // The pointer to read data
static volatile unsigned char writePointer = 0; // The pointer to write data
static volatile unsigned bufferOverflow = 0; // Indicates a buffer overflow
// In the ISR...
if (buffer[writePointer] == 0x00) { // If there is no data at the pointer
buffer[writePointer] = SSPBUF; // Put the data in the buffer
writePointer = (writePointer+1)%bufferSize; // Increase the pointer, reset if >32
} else { // If there is data...
bufferOverflow = 1; // Set the overflow flag
}
// In the main loop...
while (1) {
// Do some other stuff
if (readPointer != writePointer) { // If there is a new byte
putc(buffer[readPointer], stdout); // Do something with the data
buffer[readPointer] = 0x00; // Reset the data
readPointer = (readPointer+1)%bufferSize; // Increase the pointer, reset if >32
}
}
Das Schöne an diesem Code ist, dass es sich um einen Ringpuffer handelt :
Es ist daher weniger wahrscheinlich, dass es überläuft, wenn große Datenmengen auf einmal gesendet werden. Dies wird in den Kommentaren zu Nick Alexeevs Antwort diskutiert .
if (buffer[writePointer] == 0x00)
Ich denke nicht, dass das eine gute Bedingung ist, um zu überprüfen, ob keine Daten vorhanden sind. 0x00 könnte ein gültiges Datum sein.@fm-andreas ist mir da zuvorgekommen. Ich wollte dasselbe vorschlagen: Burst-Puffer mit Lese- und Schreibpositionen. (Dies ist kein Ringpuffer. Es ist einfacher. Es läuft nicht herum.) Ein Burst-Puffer kann einen Burst von Daten speichern. Das System sollte so ausgelegt sein, dass zwischen den Bursts genügend Zeit bleibt, um die Daten zu verarbeiten.
Hier ist meine Version (Pseudocode):
const unsigned char g_BUFF_LEN = 16;
unsigned char g_dataBuff[BUFF_LEN]; // buffer
unsigned char g_g_iWriteOffset, g_g_iReadOffset; // write and read offsets
unsigned char g_iFlags;
void main()
{
resetBuffer(); // initialize the burst buffer
// process the contents buffer
while (1)
{
// other code that lives in the main loop
while (g_iWriteOffset > g_iReadOffset) // inner loop for processing the received data
{
doSomething(g_dataBuff[g_iReadOffset]);
// disable the receiveISR. If the ISR occurs in this block, it can corrupr the buffer.
++g_iReadOffset; // advance the read offset
if (g_iReadOffset == g_iWriteOffset) // is there remaining unprocessed data in the buffer?
{
resetBuffer();
}
// re-enable the receive ISR
}
}
}
void resetBuffer()
{
g_iWriteOffset = 0;
g_iReadOffset = 0;
}
void receiveISR()
{
/* Receive the byte.
Specific code for keeping the hardware happy goes here.
Keelan, you've already posted it in the O.P. I'll save some time and not repeat it. */
g_dataBuff[g_iWriteOffset] = newByte;
++g_iWriteOffset; // advance the write offset
if (g_iWriteOffset >= g_BUFF_LEN) // have we got a buffer overflow?
{
g_iFlags |= COMM_BUFF_OVERFLOW;
/* Handling of errors (such as this overflow) is an interesting topic.
However, is depends on the nature of the instrument.
It's somewhat outside the sope of the question. */
}
}
PS
Sehen Sie sich in einem ähnlichen Zusammenhang die Ping-Pong-Puffer an. Dies ist praktisch, wenn Sie Multi-Byte-Pakete (oder Befehle) haben und das gesamte Paket empfangen müssen, bevor Sie mit der Verarbeitung beginnen können.
2x identische Puffer (oder mehr als 2x). ISR füllt einen Puffer, bis es das Ende des Befehls erkennt. Währenddessen verarbeitet die main()-Schleife den anderen Puffer. Wenn die ISR den nächsten vollständigen Befehl erhalten hat und main() mit der Verarbeitung des vorherigen Befehls fertig ist, werden die Pufferzeiger vertauscht.
Olin Lathrop
Benutzer17592