Errores al transmitir el número de 10 bits leído con Atmega328 vía serial y al leerlo con "od"

Tengo ATMEGA328P-PU y le he conectado la fotocélula. Estoy usando el siguiente código para leer los valores analógicos y enviarlos a la computadora para su depuración.

#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();
}

Yo uso el siguiente comando para ver la salida del sensor:

od -d /dev/ttyACM3 -w2

Puedo ver el cambio de salida en función de la cantidad de luz, pero cambia realmente extraño, si elimino lentamente la fuente de luz del sensor obtengo esta salida:

      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

La primera mitad se ve muy bien, pero de repente salta a 63k, esto sucede cada 10 cm, luego baja de 63k a 0 y luego vuelve a saltar a 63k. ¿Alguien puede decirme cómo convertir ADCW a valor decimal? Estoy esperando un número entre 0 y 1024 como salida.

El manual dice "ADC tiene una resolución de 10 bits, requiere 10 bits para almacenar el resultado", ¿eso no significa que es de 10 bits (16 bits) en lugar de 8 bits?

No puedo entender cómo su código envía datos a través de la serie, ¿puede elaborar un poco sobre eso?
@VladimirCravero Estoy usando POLOLU PGM03A que tiene "un puerto serie de nivel TTL para comunicación de uso general"
Tiene un número entero de 16 bits con signo proveniente de AtMega y está mostrando un número entero de 16 bits sin signo. Estás viendo el ajuste, con -1 mostrándose como 65,535.
Lo más importante, me parece que nunca lee ADCL y ADCH. Tal vez ADCW es una macro para eso, algo así como #define ADCW ((ADCH<<8)|ADCL)? Por otra parte, configura el noveno bit para que siempre sea 1 y luego configura la serie en modo de 8 bits. Y, por último, espera que los datos estén en el rango de 0 a 1023, pero ADCW*100/1024los datos deberían estar en el rango de 0 a 100 y encajar en 8 bits. Aquí falta algo...
@VladimirCravero gracias, ¿puede decirme cómo puedo configurar la serie en modo de 16 bits?
No puedes, y no lo necesitas. Encuentro realmente extraño que obtengas números de 16 bits, odsupongo que esto se debe completamente a cómo funciona.
Todo el código en realidad se ve muy raro. Tampoco entiendo lo que estás escribiendo en el puerto serie.
Creo que la clave está en la UDR0 = whateverlínea. Él está escribiendo los 8 lsbs y luego el ISR los envía, luego los interpreta como entradas con signo de 16 bits, por lo tanto, la basura que está viendo. Esto también explica por qué el valor se ajusta muchas veces, si el rango de 10 bits se usa por completo, esperaría que se ajustara cada vez que cambia el bit 7, y esto sucede 2 ^ 10/2 ^ 8 = 4 veces.
La conversión no importa ya que valuenunca se usa.

Respuestas (4)

No puede empujar un valor de varios bytes a través de un canal serie de 8 bits en una sola operación. Como no está seleccionando varios campos de bits en el valor de origen, lo que realmente sucede es que está enviando los 8 bits menos significativos de lecturas sucesivas. Y luego malinterpretar 2 o 4 de ellos seguidos como valores falsos de varios bytes.

Si puede tolerar 256 valores distintos , puede modificar su código para cambiar a la derecha el valor de conversión (u operar en modo de 8 bits para comenzar) para que envíe una medida legítima de un solo byte sin firmar. Luego, deberá asegurarse de que el dispositivo serie de Linux esté funcionando en un modo completamente sin formato, de modo que no se den interpretaciones especiales a los códigos. Finalmente, deberá configurar su código de visualización para interpretar los valores como enteros sin signo de 8 bits.

Enviar medidas de mayor resolución es complicado

  • Un enfoque ingenuo es escribir un código de envío que envíe cada byte del valor en secuencia, recordando asegurarse de que todos los bytes provengan de la misma lectura de ADC. Pero este enfoque es ingenuo porque no proporciona ningún mecanismo para sincronizar el transmisor y el receptor. Si el receptor aún no está escuchando antes de que se inicie el transmisor, o si alguna vez se pierde un byte debido a un error, entonces el receptor puede comenzar a combinar bytes de diferentes lecturas en orden desplazado para generar valores falsos. Es posible solucionar esto con códigos de inicio especiales prohibidos que luego se escapan si aparecen en los datos, encabezados periódicos de resincronización multibyte de valores mágicos, o de forma menos confiableintervalos de tiempo entre lecturas (lo que es especialmente imprudente si alguna vez se utilizan convertidores de serie USB).

    • En el caso específico en el que sus lecturas son de 10 bits, puede hacer algo como dividirlas en dos palabras de 5 bits y usar los bits altos de cada una como un código de campo, por ejemplo, y dónde hay un valor de 10 bits que 0b011mmmmmdesea 0b010nnnnnenviar 0bmmmmmnnnnn. . Sin embargo, esto necesitaría un software de recepción personalizado que busque un comienzo de lectura 0b011xxxxxy un siguiente comienzo 0b010xxxxxy recombinación de los bits apropiados de cada uno. (El bit 0x40 está activado en cada caso con la esperanza de dirigir los códigos al rango imprimible, tal vez preservando la posibilidad de usar herramientas de línea de comandos para manipular los datos)

Pero, en general, esto genera muchos dolores de cabeza, por lo que primero vale la pena ver si un enfoque no binario podría ser viable.

  • Envíe cadenas legibles por humanos codificando la representación ASCII de dígitos con un espacio o delimitador de nueva línea. Debido a que el carácter delimitador no se puede confundir con un dígito numérico, proporciona una división fácil de las lecturas y una sincronización implícita. Incluso en el caso de un error, el impacto no persistirá sino que se limitará a una o dos lecturas sucesivas. El uso de una nueva línea suele ser la mejor opción, ya que muchos esquemas de procesamiento de secuencias tienen un búfer de línea natural, un comportamiento que puede tener que cambiarse explícitamente para evitar demoras cuando se usa un delimitador diferente.

    • Los valores decimales se interpretan más fácilmente a simple vista y pueden estar bien para su propósito, pero deberá rellenar con ceros o aceptar que la cantidad de caracteres por valor es variable.

    • Los valores hexadecimales (rellenados con ceros a la izquierda) producen una conversión limpia de tamaño fijo de los datos. El valor en sí será exactamente el doble de la longitud del valor binario, es decir, 16 bits se convierten en 4 caracteres de 8 bits o 32 bits, pero el delimitador también tomará un byte, por lo que un valor de 16 bits requiere cinco caracteres en total. Dado que la resolución de su fuente es de 10 bits, en realidad podría usar solo 3 dígitos hexadecimales y un delimitador, para un total de 4 caracteres por lectura. Por supuesto, el software que consume necesitará interpretar los valores hexadecimales, pero esto tiende a funcionar bien con la mayoría de las cosas que ejecutaría en Linux; aún así, resolverlo en algunos contextos puede ser un poco complicado, puede requerir inyectar un prefijo "0x" antes de interpretar la cadena recibida, etc.


Hay algunos problemas potenciales adicionales en el caso de que intente utilizar un retraso programático fijo pero nervioso para regular dos cosas que tienen condiciones de preparación explícitas más apropiadas.

  • No puede verificar que el UART serial esté listo para nuevos datos antes de escribir en el registro de datos.

  • debe consultar la hoja de datos para determinar si es legítimo leer el registro de conversión de ADC cuando una conversión no está completa; de lo contrario, debe esperar el indicador de conversión completa.

Está enviando bytes únicos sin firmar a través de la serie.

El comando correcto que necesitas es:

od -t u1 /dev/ttyACM3 -w1

lo que significa que el tipo de sus datos es un entero sin signo de un byte de ancho, mientras que w1 debería imprimir un byte por línea.

Además, en su línea:

UDR0 = (ADCW*100/1024)  ;

No está usando value, que lanzó correctamente a int sin firmar . No estoy seguro de lo que hace el compilador, pero usar valueen esa línea sería más claro.

Aparentemente, quiere enviar números de 10 bits, pero la conversión en el código se realiza incorrectamente.
@EugeneSh. Lo siento, estoy súper confundido ahora. El manual dice "ADC tiene una resolución de 10 bits, requiere 10 bits para almacenar el resultado", ¿eso no significa que es de 10 bits (16 bits) en lugar de 8 bits?
@VladimirCravero disculpe mi error sobre el valor, arreglé el código, pero sigo obteniendo un resultado similar. Todo parece estar bien, pero a unos 8cm del sensor, el valor se "rompe" y deja de tener sentido. Luego, unos pocos centímetros más, todo parece estar midiendo bien.
¿Está utilizando od en modo de un solo byte? ¿Qué quieres decir con 'romper'?
@VladimirCravero Por descanso quiero decir: tengo un sensor en una mesa. Le pongo una linterna, la lectura dice 3.000, luego alejo la linterna, dice 2.000.. 1.500.., 1.000.. y de repente 12.000.. 11.000.. 10.000.... y así sucesivamente hasta llegar a 0 y luego vuelve a saltar a 12,000 nuevamente y baja suavemente a 0k y así sucesivamente... parece que en lugar de un gran rango, me informa 4 sub-rangos.
+1 Esto identifica bastante el problema central, pero no ha explicado explícitamente por qué los valores son erróneos y reciclados, o cómo resolverlo.

Utilizar el

od -t d2 /dev/ttyACM3 -w2

o

od -s /dev/ttyACM3 -w2

comando en su lugar.

Según man od, el -dconmutador muestra enteros sin signo de 2 bytes . -so -t d2mostrará los firmados.

Gracias, pero sigo recibiendo 'ondas sinusoidales' de números, así como números negativos. ¿Alguna sugerencia?
No son ondas sinusoidales, solo es muy ruidoso. Tal vez no haya suficiente desacoplamiento en el AtMega, tal vez un voltaje de referencia deficiente, tal vez una mala coincidencia de impedancia, tal vez otras cosas.
Sí. Así es tu señal. Verifique sus conexiones y terrenos.
@Mark definitivamente actúa como una onda de pecado. Con los comandos de arriba va de 12k a 0 a 12k pero como en una ola no de 0 a 12k seguidos.
¿Cambia si tapas la fotocélula?
@EugeneSh. Sí, y uso una linterna para probarlo y las lecturas son 'apropiadas' simplemente haciendo estos bucles extraños.
@K666 ¿Linterna como en PWM? De lo contrario, ¿fluorescente como en parpadeo de 100/120 Hz? Use la luz del sol o una fuente de CC para una bombilla incandescente para tener un estímulo conocido.
Esto es incorrecto. Los datos solo contienen valores de un solo byte (aunque erróneos). Incluso entonces, es probable que algunos de los códigos reciban interpretaciones especiales, a menos que el dispositivo serial se haya colocado en modo sin procesar.

Cambiando la línea

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

A

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

Y llamando al comando:

od -t d4 /dev/ttyACM2 -w4 

Me dio el valor correcto en un rango de 0 a 50 529 027, donde 0 es oscuridad total. El único problema es que los 'pasos' se actualizan solo cada 4 cm, donde normalmente debería poder detectar la diferencia en un par de milímetros.

ADCW es un valor de 16 bits que oscila entre 0 y 1023. Lo multiplicas por 10 para obtener 0~10230, luego lo divides por 1024 para obtener 0~9. Simplemente debe dividir ADCW por 4 para obtener 0 ~ 255 (el número más grande que se puede poner en UDR0 u OCR0A), luego sus pasos serán mucho más pequeños.
Todavía está operando bajo la creencia errónea de que puede empujar un valor más amplio a través de un canal de 8 bits en una sola operación. no puedes Lo que realmente está sucediendo es que está enviando valores de 8 bits y está utilizando incorrectamente OD para malinterpretar cuatro de ellos como un valor falso de 32 bits. Incluso si tú