Cómo lidiar con los desbordamientos int firmados

4

Breve fondo primero; Tengo datos del bus CAN de un ángulo de dirección que obviamente es hexagonal. El ángulo de dirección cubre dos bytes de un mensaje. El documento de especificación que tengo dice que esos dos bytes forman un int de 16 bits con signo, que son el ángulo de dirección que un prescaler de 1/1024, eso es todo lo que tengo (no tengo acceso a la fuente). Lo que estoy tratando de hacer es convertir esos valores hexadecimales en int firmado, sin embargo, no estoy seguro de cómo hacerlo correctamente.

Una pequeña sección del mensaje CAN dentro de un período de tiempo corto (nos centramos en los bytes 2 y 3):

can0  700   [8]  00 00 99 93 55 0B EF BD
can0  700   [8]  00 00 95 95 10 0C 17 BE
can0  700   [8]  00 00 6F 97 FB 0A 17 BE
can0  700   [8]  00 00 39 99 5C 0A 40 BE
can0  700   [8]  00 00 AD 9A 62 08 EF BD
can0  700   [8]  00 00 EF 9B B5 08 40 BE
can0  700   [8]  00 00 CA 9D 9A 09 17 BE
can0  700   [8]  00 00 3E 9F 55 09 40 BE
can0  700   [8]  00 00 91 A0 ED 09 17 BE

Por lo que sé, por lo general, los datos en los mensajes CAN siguen este formato: un byte para los datos reales, un byte para el número de desbordamientos.

Por ejemplo, tomemos un int sin signo de un valor de 2000. Suponiendo que el byte # 0 es para desbordamientos, el byte # 1 es para los datos reales, obtenemos:

CAN message -> [07, D0, x, x, x, x, x, x]

07 que indica que ha habido 7 desbordamientos, D0 que indica que el resto es 208, por lo tanto:

7*255 + 208 = 2000

Entiendo cómo hacerlo con valores sin firmar. Pero esta vez en mi escenario estoy tratando con valores firmados. Supongo que un byte es para desbordamientos, un byte es para el resto, pero no estoy seguro.

  1. ¿Cómo se calculan los desbordamientos para los valores firmados? ¿Está desbordado + = 1 cuando el valor > 127 y desbordamiento - = cuando valor < -128? ¿Tiene sentido incluso que los desbordamientos tengan firmeza?

  2. ¿Cómo puedo convertir estos bytes en decimales firmados en C / C ++? Digamos que mi valor de byte en hexadecimal es 91. La última vez que intenté almacenarlo en int y lo imprimí, se imprimió 145 (binario normal) y no -111 (complemento de 2). ¿Cómo puedo imponer el complemento de 2 (si eso tiene sentido) en mi código?

  3. ¿Podría estar interpretando mal el formato de byte? Todo lo que dice es que estos dos bytes representan el ángulo de dirección y que el ángulo de dirección es int16_t. Los he monitoreado los cambios en tiempo real y uno de ellos cambia erráticamente, casi como saltar de 00 a FF, mientras que el otro está aumentando / disminuyendo lentamente y casi linealmente con el tiempo. Ideas?

Realmente estoy atrapado aquí, ni siquiera sé si voy en la dirección correcta ... ¡Cualquier sugerencia y ayuda son realmente apreciadas!

    
pregunta Hypomania

6 respuestas

16

Es probable que ayude a aclarar algunos malentendidos.

Primero, sus datos son un valor de 16 bits. No hay "desbordamientos" ni "datos reales": los 16 bits se dividen en dos partes de 8 bits (bytes). Para obtener el valor binario correcto, debe concatenar los bytes. En C, puede hacerlo comenzando con valores sin signo y utilizando operadores bitwise, como este:

uint16_t highbyte, lowbyte, data; 
highbyte = get_can_byte();   //Do whatever you normally do to get the bytes
lowbyte = get_can_byte();

data = highbyte<<8 | lowbyte;

Ahora tienes el valor correcto de 16 bits. Si desea que el resultado se firme, simplemente puede convertir el valor a un tipo firmado:

int16_t signed_data;

signed_data = (int16_t)(highbyte<<8 | lowbyte);

Para responder a sus preguntas específicas:

  1. Los desbordamientos producen los mismos valores binarios independientemente de si su variable está firmada o no. Por ejemplo, 0x7fff + 1 == 0x8000. Si interpreta 0x8000 como 32768 o -32768 depende del tipo de datos. (Tenga en cuenta que el desbordamiento de enteros con signo no está definido técnicamente en el estándar C: esto es lo que hará su CPU).

  2. Como dije, todo depende del tipo de datos:

    uint16_t ui = 0xffff;    //65535
    int16_t   i = 0xffff;    //   -1
    
  3. Lo que estás describiendo suena razonable para una medición de alta precisión. Un cambio de +1 o -1 en su byte superior representa solo 1/256 de su rango total de dirección. El byte bajo será extremadamente sensible al ángulo exacto.

Tenga en cuenta que es mejor convertir a un valor firmado lo más tarde posible y hacerlo con una conversión explícita. Cosas como los operadores bitwise pueden comportarse de manera diferente o producir un comportamiento indefinido cuando se usan en valores firmados. En general, cuando esté haciendo manipulación de bits, use valores sin firmar.

    
respondido por el Adam Haun
5

Estás sobre-complicando esto.

can0  700   [8]  00 00 *99 93* 55 0B EF BD -> 0x9399 -> -27751  
can0  700   [8]  00 00 *95 95* 10 0C 17 BE -> 0x9595 -> -27243  
can0  700   [8]  00 00 *6F 97* FB 0A 17 BE -> 0x976F -> -26769  
can0  700   [8]  00 00 *39 99* 5C 0A 40 BE -> 0x9939 -> -26311  

etc ...

    
respondido por el brhans
4

La explicación más probable de por qué un byte parece aleatorio y uno está cambiando linealmente es que el byte lineal es el byte de orden superior (los desbordamientos). El byte de orden inferior aparece aleatorio porque está cambiando rápidamente en comparación con la tasa de actualización.

Yo diría que el byte 3 es el byte de orden superior y el byte 2 es el byte de orden bajo. En enteros con signo que utilizan la representación de complemento a dos (que es casi universal en la actualidad) los números negativos tendrán bits de orden superior establecidos en 1. Mirando el hex, si el byte de orden superior es mayor que 0x80, es un número negativo. Entonces, en tu ejemplo, todos los números son números negativos. 0xffff es -1, 0xfffe es -2, etc. Lo que tienes en tu fragmento es:

  • 0x9399
  • 0x9995
  • 0x976f
  • etc ...

Dado que todos esos números son mayores que 0x8000, todos son negativos.

Lo mejor que puede hacer es estudiar la representación complementaria de dos enteros con signo. Parece que sabes cómo hacer la conversión uint16_t. Lo que puede funcionar para usted es simplemente hacer eso, luego asignar el valor a una variable de int16_t. El compilador puede convertirlo correctamente para usted (no está garantizado por la especificación C, pero muchos compiladores lo hacen de esa manera).

Espero que esto te ayude un poco.

    
respondido por el mkeith
4

No sé nada sobre los sensores de ángulo de dirección reales en los autos ...,

pero tenga en cuenta que un sensor de ángulo puede reportar múltiples valores de giro. es decir, un turno puede ser 0-1024, y el sensor puede reportar +/- 32 giros en un número de 16 bits.

¿Por qué alguien haría esto?

Bueno, imagine que el volante está exactamente en el punto de 0/360 grados y que cambia entre 0 y 360 con la vibración de la carretera. ¿Qué pasa si promedias filtrar el ruido? Obtienes 180 grados, lo cual es completamente incorrecto.

Este es siempre un problema que debe manejarse en algún lugar cuando se usan sensores de ángulo de rotación libres. Algunos sistemas devuelven dos valores de ángulo a 90 grados (sin + cos) para resolver esta ambigüedad. Otros extienden el valor del ángulo a 360 + 180 grados con histéresis, mientras que otros hacen la acumulación de ángulos de múltiples vueltas.

    
respondido por el Henry Crun
3

Las otras respuestas explican bien el formato del mensaje, pero probablemente lo tomaría así:

union
{
    struct
    {
        uint8_t hi;    //the order of these two depends on the endian-nesss of your specific micro
        uint8_t lo;    //swap if the data is garbled
    };
    uint16_t all;      //or sint16_t if you like: the only difference so far is at what value it wraps to the opposite end of its range
} reading;

uint16_t get_reading()
{
    reading.hi = get_hi_byte();
    reading.lo = get_lo_byte();
    return reading.all;
}

La ventaja aquí es que el cálculo aparente (shift + o) se realiza en su totalidad mediante la estructura de la memoria sin ningún esfuerzo real, por lo que incluso un compilador estúpido generará un código eficiente. Y lo estás escribiendo directamente de la manera en que realmente sucede, lo que creo que es una gran ventaja para la legibilidad.

Si haces esto mucho, puedes:

typedef union
{
    struct
    {
        uint8_t hi;
        uint8_t lo;
    };
    uint16_t all;
} byte_int;

byte_int steering;
byte_int throttle;
//etc.
    
respondido por el AaronD
0

Si tuviera un número decimal como 106, podría decir que tuvo un desbordamiento, donde pasó de 99 y "se desbordó" una vez a 100. Podría estar de acuerdo en que esto sería mejor llamado "carry". Entiendes esto, porque en tu publicación multiplicaste 7 * 256 antes de agregar 208 para obtener la respuesta de 2000. (Escribiste 255 pero creo que eso es un resbalón ya que la respuesta fue correcta).

No dijiste si estabas trabajando en C o en cualquier otro idioma. Es posible que solo esté mirando un volcado de CAN y trabajando en papel. Desde este punto de vista, la forma más sencilla de obtener una respuesta es que si el número es negativo (es decir, 2 ^ 15 (32768) o mayor), puedes restar 2 ^ 16 (65536) de la lectura para obtener la respuesta. Es decir, si tenía F8 33 y calculó F8 * 256 + 33, obtendría 63539. Luego, 63539 - 65536 = -1997, que es el valor representado por F833 cuando se considera un número con signo.

La información presentada en las otras respuestas es válida y en su mayoría recomendada, pero esto puede ayudarlo a comenzar, y podría ser apropiado para alguien que aún no ha abordado el extremo de la programación. También es una buena forma de verificar la respuesta que te da tu programa, una vez que lo intentes.

    
respondido por el gbarry

Lea otras preguntas en las etiquetas