Fehler beim Übertragen der mit Atmega328 gelesenen 10-Bit-Zahl über Seriell und Lesen mit "od"

Ich habe ATMEGA328P-PU und habe die Lichtschranke daran angeschlossen. Ich verwende den folgenden Code, um die analogen Werte zu lesen und sie zum Debuggen an den Computer zurückzusenden.

#define F_CPU     800000UL
#define BAUD      11400
#define BRC       ((F_CPU/8/BAUD) - 1)

#include <stdlib.h>

#include <avr/io.h>
#include <avr/interrupt.h>

#include <util/delay.h>
#include <util/setbaud.h> 
#include <avr/io.h>
#include <avr/interrupt.h>

double dutyCycle = 0;

int main(void)
{
    initSerial();


    DDRD = (1 << PORTD6);

    TCCR0A = (1 << COM0A1) | (1 << WGM00) | (1 << WGM01);
    TIMSK0 = (1 << TOIE0);

    setupADC();

    sei();

    TCCR0B = (1 << CS00) | (1 << CS02);

    while(1)
    {
        unsigned int  value = ADCW;
        UDR0 = (value*100/1024) ;                                                                                                                                                                                            
        _delay_ms(1);  
    }
}

void initSerial()
{
    UBRR0H = (BRC >> 8);
    UBRR0L = BRC;

    UCSR0B = ( 1 << TXEN0 );                                                                                                                                                                                                
    UCSR0C = ( 1 << UCSZ00 ) | ( 1 << UCSZ01 );                                                                                                                                                                             

}

void setupADC()
{
    ADMUX = (1 << REFS0) | (1 << MUX0) | (1 << MUX2);
    ADCSRA = (1 << ADEN) | (1 << ADIE) | (1 << ADPS0) | (1 << ADPS1) | (1 << ADPS2);
    DIDR0 = (1 << ADC5D);

    startConversion();
}

void startConversion()
{
    ADCSRA |= (1 << ADSC);
}

ISR(TIMER0_OVF_vect)
{
    OCR0A = dutyCycle;
}

ISR(ADC_vect)
{
    dutyCycle = ADC;
    startConversion();
}

Ich verwende den folgenden Befehl, um die Sensorausgabe anzuzeigen:

od -d /dev/ttyACM3 -w2

Ich kann sehen, dass sich die Ausgabe basierend auf der Lichtmenge ändert, aber es ändert sich wirklich seltsam, wenn ich die Lichtquelle langsam vom Sensor entferne, erhalte ich diese Ausgabe:

      3542
      3298
      3142
      2799
      2243
      2142
      1799
      1543
      1542
      1537
      1542
       262
       257
      1542
       262
       257
      1542
       257
      1537
      1542
         1
       256
       257
       262
     65535
       255
         0
     65535
         0
     65278
     65535
     65278
     30969
     30968
     63736
     63993
     63737
     63479
     63992

Die erste Hälfte sieht gut aus, aber dann springt es plötzlich auf 63.000, das passiert alle 10 cm, geht dann von 63.000 auf 0 runter und springt dann wieder zurück auf 63.000. Kann mir bitte jemand sagen, wie ich ADCW in einen Dezimalwert umwandeln kann? Ich erwarte eine Zahl zwischen 0 und 1024 als Ausgabe.

Im Handbuch steht "ADC hat eine Auflösung von 10 Bit, es werden 10 Bit benötigt, um das Ergebnis zu speichern". Bedeutet das nicht, dass es sich um 10 Bit (16 Bit) anstelle von 8 Bit handelt?

Ich kann nicht verstehen, wie Ihr Code Daten über die serielle Schnittstelle sendet. Können Sie das etwas näher erläutern?
@VladimirCravero Ich verwende POLOLU PGM03A, das "eine serielle Schnittstelle auf TTL-Ebene für die allgemeine Kommunikation" hat.
Sie haben eine 16-Bit-Ganzzahl mit Vorzeichen, die vom AtMega kommt, und Sie zeigen eine 16-Bit-Ganzzahl ohne Vorzeichen an. Sie sehen den Umbruch, wobei -1 als 65.535 angezeigt wird.
Am wichtigsten scheint mir, dass Sie ADCL und ADCH nie lesen. Vielleicht ist ADCW ein Makro dafür, so etwas wie #define ADCW ((ADCH<<8)|ADCL)? Dann setzen Sie das neunte Bit immer auf 1 und richten dann die serielle Schnittstelle im 8-Bit-Modus ein. Und schließlich erwarten Sie, dass Daten im Bereich 0..1023 liegen, aber Sie tun ADCW*100/1024dies, Daten sollten im Bereich 0..100 liegen und in 8 Bit passen. Etwas fehlt hier...
@VladimirCravero danke, kannst du mir bitte sagen, wie ich den 16-Bit-Modus seriell einstellen kann?
Kannst du nicht und brauchst du auch nicht. Ich finde es wirklich seltsam, dass Sie 16-Bit-Zahlen erhalten, das liegt ganz an der Funktionsweise, oddenke ich.
Der ganze Code sieht tatsächlich sehr seltsam aus. Ich verstehe auch nicht, was Sie an die serielle Schnittstelle schreiben
Ich denke, der Schlüssel liegt in der UDR0 = whateverZeile. Er schreibt die 8 lsbs und dann sendet die ISR sie, dann interpretiert er sie als 16-Bit-Ints mit Vorzeichen, also den Müll, den er sieht. Dies erklärt auch, warum der Wert viele Male umgebrochen wird. Wenn der 10-Bit-Bereich vollständig verwendet wird, würden Sie erwarten, dass er jedes Mal umbricht, wenn sich Bit 7 ändert, und dies geschieht 2 ^ 10/2 ^ 8 = 4 Mal.
Die Konvertierung spielt keine Rolle, da valuesie nie verwendet wird.

Antworten (4)

Sie können einen Multibyte-Wert nicht in einem einzigen Vorgang durch einen seriellen 8-Bit-Kanal schieben. Da Sie nicht verschiedene Bitfelder im Quellwert auswählen, senden Sie tatsächlich die niederwertigsten 8 Bits aufeinanderfolgender Messwerte. Und dann 2 oder 4 davon hintereinander als falsche Multibyte-Werte falsch interpretieren .

Wenn Sie 256 unterschiedliche Werte tolerieren können , können Sie Ihren Code ändern, um den Konvertierungswert nach rechts zu verschieben (oder zu Beginn im 8-Bit-Modus arbeiten), sodass Sie eine legitime unsignierte Single-Byte-Messung senden. Sie müssen dann sicherstellen, dass das serielle Linux-Gerät in einem vollständig unformatierten Modus arbeitet, sodass keine Codes speziell interpretiert werden. Schließlich müssten Sie Ihren Anzeigecode so einstellen, dass er die Werte als 8-Bit-Ganzzahlen ohne Vorzeichen interpretiert.

Das Senden von Messungen mit höherer Auflösung ist schwierig

  • Ein naiver Ansatz besteht darin, Sendecode zu schreiben, der jedes Byte des Werts nacheinander sendet, wobei daran zu denken ist, sicherzustellen, dass alle Bytes aus demselben ADC -Lesevorgang stammen. Aber dieser Ansatz ist naiv, da er keinen Mechanismus zum Synchronisieren von Sender und Empfänger bereitstellt. Wenn der Empfänger nicht bereits zuhört, bevor der Sender startet, oder wenn jemals ein Byte aufgrund eines Fehlers ausgelassen wird, kann der Empfänger damit beginnen, Bytes verschiedener Messwerte in verschobener Reihenfolge zu kombinieren, um falsche Werte zu erzeugen. Es ist möglich, dies mit speziellen verbotenen Startcodes zu umgehen, die dann maskiert werden, wenn sie in Daten, periodischen Multibyte-Neusynchronisierungsheadern mit magischem Wert oder am wenigsten zuverlässig erscheinenZeitlücken zwischen den Messwerten (was besonders unklug ist, wenn jemals USB-Seriell-Konverter verwendet werden).

    • In dem speziellen Fall, in dem Ihre Messwerte 10-Bit sind, können Sie sie beispielsweise in zwei 5-Bit-Wörter aufteilen und die hohen Bits von jedem beispielsweise als Feldcode verwenden und wo ist ein 10-Bit-Wert, den 0b011mmmmmSie 0b010nnnnnsenden 0bmmmmmnnnnnmöchten . Dies würde jedoch eine kundenspezifische Empfangssoftware erfordern, die nach einem Leseanfang 0b011xxxxxund einem folgenden Anfang sucht 0b010xxxxxund die entsprechenden Bits von jedem neu kombiniert. (Das 0x40-Bit ist in jedem Fall aktiviert, in der Hoffnung, die Codes in den druckbaren Bereich zu lenken und möglicherweise die Möglichkeit zu bewahren, Befehlszeilentools zur Manipulation der Daten zu verwenden.)

Aber im Allgemeinen verursacht dies eine Menge Kopfschmerzen, sodass es sich lohnt, zuerst zu prüfen, ob ein nicht-binärer Ansatz praktikabel sein könnte.

  • Senden Sie für Menschen lesbare Zeichenfolgen, die die ASCII-Darstellung von Ziffern mit einem Leerzeichen oder Zeilenumbruchtrennzeichen codieren. Da das Begrenzungszeichen nicht mit einer Ziffer verwechselt werden kann, bietet es eine einfache Aufteilung der Messwerte und eine implizite Synchronisation. Selbst im Falle eines Fehlers bleiben die Auswirkungen nicht bestehen, sondern beschränken sich auf ein oder zwei aufeinanderfolgende Messwerte. Die Verwendung eines Zeilenumbruchs ist oft die beste Wahl, da viele Stream-Verarbeitungsschemata natürlich zeilengepuffert sind, ein Verhalten, das möglicherweise explizit geändert werden muss, um Verzögerungen zu vermeiden, wenn ein anderes Trennzeichen verwendet wird.

    • Dezimalwerte sind am einfachsten mit dem Auge zu interpretieren und können für Ihren Zweck in Ordnung sein, aber Sie müssen entweder mit Nullen auffüllen oder akzeptieren, dass die Anzahl der Zeichen pro Wert variabel ist.

    • Hex-Werte (mit führenden Nullen aufgefüllt) ergeben eine saubere Konvertierung der Daten mit fester Größe. Der Wert selbst ist genau doppelt so lang wie der Binärwert - dh 16 Bit werden zu 4 8-Bit-Zeichen oder 32 Bit, aber das Trennzeichen nimmt auch ein Byte, sodass ein 16-Bit-Wert insgesamt fünf Zeichen benötigt. Wenn Ihre Quellauflösung 10 Bit beträgt, könnten Sie tatsächlich nur 3 Hexadezimalziffern und ein Trennzeichen für insgesamt 4 Zeichen pro Lesung verwenden. Natürlich muss verbrauchende Software Hex-Werte interpretieren, aber das funktioniert gut mit den meisten Dingen, die Sie unter Linux ausführen würden; Dennoch kann es in manchen Kontexten etwas schwierig sein, es herauszufinden, es kann erforderlich sein, ein vorangestelltes "0x" einzufügen, bevor die empfangene Zeichenfolge interpretiert wird usw.


Es gibt einige zusätzliche potenzielle Probleme, wenn Sie versuchen, eine feste, aber nervöse programmatische Verzögerung zu verwenden, um zwei Dinge zu regulieren, die geeignetere explizite Bereitschaftsbedingungen haben.

  • Sie können nicht überprüfen, ob der serielle UART für neue Daten bereit ist, bevor Sie in das Datenregister schreiben.

  • Sie sollten das Datenblatt überprüfen, um festzustellen, ob es legitim ist, das ADC-Umwandlungsregister zu lesen, wenn eine Umwandlung nicht abgeschlossen ist; Wenn nicht, müssen Sie auf das Flag „Konvertierung abgeschlossen“ warten.

Sie senden einzelne, unsignierte Bytes über die serielle.

Der richtige Befehl, den Sie benötigen, lautet:

od -t u1 /dev/ttyACM3 -w1

was bedeutet, dass der Typ Ihrer Daten eine vorzeichenlose Ganzzahl mit einer Breite von einem Byte ist, während w1 ein Byte pro Zeile drucken sollte.

Auch in Ihrer Zeile:

UDR0 = (ADCW*100/1024)  ;

Sie verwenden nicht , das Sie ordnungsgemäß in unsignedvalue int umwandeln . Ich bin mir nicht sicher, was der Compiler tut, aber die Verwendung dieser Zeile wäre in der Tat klarer.value

Anscheinend will er 10bit Nummern senden, aber die Umrechnung im Code ist falsch
@EugenSch. Sorry, ich bin jetzt total verwirrt. Im Handbuch steht "ADC hat eine Auflösung von 10 Bit, es werden 10 Bit benötigt, um das Ergebnis zu speichern". Bedeutet das nicht, dass es sich um 10 Bit (16 Bit) anstelle von 8 Bit handelt?
@VladimirCravero Entschuldigung, mein Fehler bezüglich des Werts, ich habe den Code korrigiert, bekomme aber immer noch ein ähnliches Ergebnis. Alles scheint in Ordnung zu sein, aber etwa 8 cm vom Sensor entfernt "bricht" der Wert und macht keinen Sinn mehr. Dann ein paar cm weiter scheint wieder alles in Ordnung zu sein.
Verwenden Sie od im Single-Byte-Modus? Was meinst du mit "Pause"?
@VladimirCravero Mit Pause meine ich - ich habe einen Sensor auf einem Tisch. Ich stelle eine Taschenlampe darauf, die Anzeige zeigt 3.000, dann bewege ich die Taschenlampe weg, sagt 2.000 ... 1.500 ... 1.000 ... und plötzlich 12.000 ... 11.000 ... 10.000 ... und so weiter, bis sie 0 erreicht und springt dann wieder auf 12.000 zurück und geht reibungslos auf 0k und so weiter ... scheint mir statt eines großen Bereichs 4 Unterbereiche zu melden.
+1 Dies identifiziert ziemlich genau das Kernproblem, hat aber nicht explizit erklärt, warum die Werte fehlerhaft und recycelt sind oder wie sie gelöst werden können.

Verwenden Sie die

od -t d2 /dev/ttyACM3 -w2

oder

od -s /dev/ttyACM3 -w2

stattdessen befehlen.

Laut Manod-d zeigt der Switch 2-Byte- Ganzzahlen ohne Vorzeichen an. -soder -t d2zeigt signierte an.

Danke, aber ich bekomme immer noch "Sündenwellen" von Zahlen sowie negative Zahlen. Irgendwelche Vorschläge?
Es sind keine Sinuswellen, es ist nur sehr laut. Vielleicht nicht genug Entkopplung im AtMega, vielleicht eine schlechte Referenzspannung, vielleicht schlechte Impedanzanpassung, vielleicht andere Dinge.
Ja. So ist dein Signal. Überprüfen Sie Ihre Verbindungen und Erdungen.
@Mark es wirkt definitiv wie eine Sündenwelle. Mit den obigen Befehlen geht es von 12k auf 0 auf 12k, aber wie in einer Welle nicht von 0 auf 12k geradeaus.
Ändert es sich, wenn Sie die Fotozelle abdecken?
@EugenSch. Ja, und ich benutze eine Taschenlampe, um es zu testen, und die Messwerte sind "angemessen", wenn ich nur diese seltsamen Schleifen mache.
@K666 Taschenlampe wie in PWM? Sonst fluoreszierend wie bei 100/120 Hz Flimmern? Verwenden Sie Sonnenlicht oder eine Gleichstromquelle für eine Glühlampe, um einen bekannten Stimulus zu haben.
Das ist falsch. Die Daten enthalten nur einzelne Bytewerte (wenn auch fehlerhafte). Selbst dann ist es wahrscheinlich, dass einige der Codes spezielle Interpretationen erhalten, es sei denn, das serielle Gerät wurde in einen Rohmodus versetzt.

Ändern der Linie

UDR0 = ( ADCW * 100 / ( 1024 ) );

Zu

UDR0 = ( ADCW * 10 / ( 1024 ) );

Und Aufruf des Befehls:

od -t d4 /dev/ttyACM2 -w4 

Gab mir den korrekten Wert in einem Bereich von 0 bis 50.529.027, wobei 0 völlige Dunkelheit bedeutet. Das einzige Problem ist, dass die „Schritte“ nur alle 4 cm aktualisiert werden, wo ich normalerweise in der Lage sein sollte, den Unterschied innerhalb weniger Millimeter zu erkennen.

ADCW ist ein 16-Bit-Wert im Bereich von 0~1023. Sie multiplizieren es mit 10, um 0~10230 zu erhalten, und teilen es dann durch 1024, um 0~9 zu erhalten. Sie sollten ADCW einfach durch 4 teilen, um 0~255 zu erhalten (die größte Zahl, die in UDR0 oder OCR0A eingegeben werden kann), dann werden Ihre Schritte viel kleiner sein.
Sie gehen immer noch davon aus, dass Sie in einem einzigen Vorgang einen größeren Wert durch einen 8-Bit-Kanal schieben können. Du kannst nicht . Was tatsächlich passiert, ist, dass Sie 8-Bit-Werte senden und OD fälschlicherweise verwenden, um vier davon als falschen 32-Bit-Wert falsch zu interpretieren . Auch wenn Du