Electrónica Digital · Tema 05

Representar información digital

Ya sabés contar en binario y leer hexadecimal. Pero un byte no trae escrito qué significa: 0x25 puede ser el número 37, el +37, el «25» de un reloj o el carácter «%». Este tema enseña los convenios que le dan sentido a los bits: números negativos, decimales para displays y texto.

≈ 45 minutos Sin materiales: solo el navegador Incluye simulador y Python

01 · Punto de partida

Bits sin convenio no dicen nada

Un registro de un sensor te devuelve 0xA5. ¿Cuánto vale? Si es una cuenta sin signo, 165. Si es una temperatura con signo, −91. Si es la hora de un reloj en BCD, ni siquiera es válido (el nibble A no es un dígito). Y si llegó por una UART como texto, no es ni siquiera un número: es un byte fuera de ASCII. Los bits son los mismos; lo que cambia es el convenio con que se leen.

Una escena típica. Medís temperatura con un sensor digital. Un día de invierno la lectura salta de 3 °C a 4093 °C. El cable está bien, el sensor está bien; lo que pasó es que el valor era negativo y tu código lo leyó como número sin signo. O al revés: mostrás en un display la hora del reloj de tu placa y aparecen las 35:89 cuando son las 23:59, porque leíste un registro BCD como si fuera binario.

En programación esto es el tipo: uint8_t, int8_t y char guardan el mismo byte. En el hardware no hay compilador que te avise: el tipo vive en el datasheet y en tu cabeza.

  • ¿Cómo se representan los números negativos con solo ceros y unos?
  • ¿Por qué casi todo el mundo usa complemento a dos?
  • ¿Qué pasa cuando una suma no cabe en su ancho, con y sin signo?
  • ¿Cómo se guardan decimales pensados para un display?
  • ¿Cómo viajan las letras por un cable?

02 · Números con signo

Tres formas de escribir un negativo

Con 8 bits sin signo hay 256 combinaciones y todas se usan para 0…255. Para tener negativos hay que repartir esas 256 combinaciones entre positivos y negativos. Se probaron tres convenios; solo uno sobrevivió.

Signo-magnitud
El bit más alto es el signo (0 = positivo, 1 = negativo) y el resto es el valor absoluto. Es lo más intuitivo y lo que hacemos en papel («−5»). Problema: existen dos ceros (+0 = 00000000 y −0 = 10000000) y sumar exige mirar los signos y decidir si sumar o restar.
Complemento a uno
El negativo se obtiene invirtiendo todos los bits. Suma bien casi siempre, pero sigue teniendo dos ceros (00000000 y 11111111) y la suma pide un «acarreo circular» extra.
Complemento a dos
El negativo se obtiene invirtiendo todos los bits y sumando 1. Un solo cero, una sola regla de suma y el mismo circuito sirve para números con y sin signo. Es el convenio universal en CPU y microcontroladores.
El número −5 y el +5 en 8 bits, según el convenio.
Convenio+5−5RangoCeros
Signo-magnitud0000 01011000 0101−127 … +127Dos (±0)
Complemento a uno0000 01011111 1010−127 … +127Dos (±0)
Complemento a dos0000 01011111 1011−128 … +127Uno

El complemento a dos además gana un valor: el 11111111 que en los otros dos convenios era un «−0» sobrante, aquí es −1, y el rango queda como −128…+127 (un negativo más que positivos). Esa asimetría trae una consecuencia que vas a ver más abajo: −128 no tiene opuesto.

03 · Complemento a dos

Negar es invertir y sumar 1

El truco del complemento a dos es cambiar el peso del bit más alto: en lugar de valer +128, vale −128. Todo lo demás queda igual que en el Tema 4. Un byte con signo se lee sumando los pesos de sus unos, con el MSB restando.

Pesos de un byte en complemento a dos (el MSB es negativo).
Bit7 (MSB)6543210 (LSB)
Peso sin signo+1286432168421
Peso con signo−1286432168421

Con esa regla, 0xA5 = 1010 0101 vale −128 + 32 + 4 + 1 = −91. Y de ahí sale la regla práctica para negar:

91 = 0101 1011 (0x5B)→ invertir →1010 0100 (0xA4)→ sumar 1 →1010 0101 = 0xA5 = −91

La razón por la que funciona es aritmética de reloj. En 8 bits todo es módulo 256: 0xFF + 1 da 0, así que 0xFF se comporta como −1; 0xFE + 2 da 0, así que 0xFE es −2; y en general x y 256 − x suman 0. El complemento a dos de x es exactamente 256 − x.

Valores clave de un byte en complemento a dos.
HexBinarioSin signoCon signoQué es
0x000000 000000El único cero
0x010000 00011+1Positivo mínimo
0x7F0111 1111127+127Positivo máximo
0x801000 0000128−128Negativo mínimo (sin opuesto)
0xFE1111 1110254−2Anteúltimo
0xFF1111 1111255−1Todo en uno = −1
Reconocer el signo
Mirando el MSB: 0 es positivo o cero, 1 es negativo. El MSB funciona como bit de signo, pero no es «signo + magnitud»: pesa −128.
Una suma, dos lecturas
El sumador del Tema 23 suma bits sin saber si tienen signo. 5 + (−3) = 0x05 + 0xFD = 0x102; descartando el noveno bit queda 0x02 = 2. Es el mismo circuito que suma 5 + 253 sin signo.
Restar es sumar el opuesto
a − b = a + (~b + 1). Por eso una CPU no necesita un circuito restador aparte: invierte b, suma y pone el acarreo de entrada en 1.
El caso −128
Negar 0x80 da ~0x80 + 1 = 0x7F + 1 = 0x80: el mismo byte. No hay +128 en 8 bits con signo. Es el único valor sin opuesto, y la fuente de bugs clásicos (abs(INT8_MIN) sigue siendo negativo).

04 · Ancho y desborde

Extensión de signo y desborde

Dos situaciones aparecen en cuanto mezclás bytes de distinto ancho o sumás valores grandes. Las dos se resuelven con una idea: el bit de signo manda.

Extender sin cambiar el valor

Para pasar un valor de 8 a 16 bits sin que cambie, hay que rellenar los bits nuevos copiando el signo: se llama extensión de signo. Sin signo se rellena con ceros.

Ampliar un byte a 16 bits: rellenar con ceros o con el signo.
ByteLecturaA 16 bits (ceros)A 16 bits (signo)Valor correcto
0x25+370x00250x002537 en ambos casos
0xA5−91 con signo / 165 sin signo0x00A5 (165)0xFFA5 (−91)Depende del convenio
0xFF−1 con signo / 255 sin signo0x00FF (255)0xFFFF (−1)Depende del convenio

Esto es lo que pasa cuando un sensor entrega un valor de 16 bits en dos bytes: el alto y el bajo. Con 0xFF y 0x38 se arma 0xFF38, que con signo vale −200, no 65 336. Los acelerómetros y giróscopos tipo MPU-6050 entregan así sus lecturas.

Cuando no cabe: desborde

Sumá dos positivos de 8 bits con signo: 100 + 50 = 150. En binario: 0110 0100 + 0011 0010 = 1001 0110, que con signo es −106. La suma dio «mal» sin avisar. Como el hardware solo tiene ocho cables, el resultado verdadero no entra y el bit de signo queda con un valor que no le corresponde.

Hay una regla simple para detectar desborde en suma con signo: solo ocurre cuando ambos sumandos tienen el mismo signo y el resultado tiene el signo contrario. Positivo + negativo nunca desborda: el resultado siempre cae entre los dos.

Acarreo (carry)
El bit que «sale» por el extremo izquierdo. Indica que una suma sin signo no cupo. 255 + 1 genera acarreo.
Desborde (overflow)
El resultado de una suma con signo no cupo en el rango. 127 + 1 desborda aunque no haya salido nada por la izquierda.
No son lo mismo
5 + (−3) = 0x05 + 0xFD genera acarreo (hay un noveno bit) y no desborda: el resultado, 2, es correcto. Una CPU guarda las dos señales en banderas separadas (C y V) para que decidas cuál mirar.

05 · BCD

Decimales pensados para displays

El binario puro es excelente para calcular y malo para mostrar: para sacar el dígito de las decenas hay que dividir por 10, y dividir es caro en hardware. El BCD (Binary-Coded Decimal) elige otro compromiso: cada dígito decimal ocupa un nibble. Dos dígitos caben en un byte.

Binario puro

Compacto, bueno para calcular

25 = 0x19. Un byte guarda 0…255. Sumar es directo; mostrarlo en dos dígitos exige dividir por 10.

BCD

Legible, bueno para mostrar

25 = 0x25. Un byte guarda 00…99 (100 de 256 combinaciones). Cada nibble va directo a un display; sumar pide un ajuste.

De las 16 combinaciones de cada nibble solo 10 son válidas (0…9). Los códigos A…F no existen en BCD: si aparecen, el dato está corrupto o se leyó con otro convenio. Por eso 0xA5 es «inválido» en BCD y por eso 0x25 BCD y 0x25 binario son números distintos.

Sumar en BCD: la corrección +6

Si sumás 0x25 + 0x17 con un sumador binario obtenés 0x3C. Pero 25 + 17 = 42, no «3C». El problema es que un nibble binario llega hasta 15 y el BCD se «acaba» en 9: los seis códigos A…F sobran. La solución es sumar 6 al nibble que se pasó, para saltarse esos seis valores.

Tres sumas BCD con su corrección.
SumaBinaria crudaCorrecciónResultado BCD
25 + 170x25 + 0x17 = 0x3Cnibble bajo C > 9 → +0x060x42 = «42» ✓
38 + 290x38 + 0x29 = 0x618 + 9 = 17 > 9 → +0x060x67 = «67» ✓
99 + 010x99 + 0x01 = 0x9A+0x06 → 0xA0 → alto > 9 → +0x600x00 con acarreo = «100» ✓

Muchas CPU tienen una instrucción dedicada para esto (DAA, decimal adjust, en el Z80 y el 8051). Hoy el BCD se usa donde el dato se muestra más de lo que se calcula: relojes de tiempo real (los registros de los chips DS1307 y DS3231 guardan horas, minutos y segundos en BCD), multímetros, balanzas y los decodificadores de display de 7 segmentos (74LS47, CD4511), que reciben un nibble BCD y encienden los segmentos correctos.

06 · ASCII y texto

Letras por cable

Un cable solo transporta ceros y unos, así que el texto necesita una tabla acordada de antemano: cada carácter tiene un número. Esa tabla es ASCII (American Standard Code for Information Interchange, 1963), con 128 códigos que caben en 7 bits. Cuando una UART envía «A» no manda una letra: manda el byte 0x41.

Mapa de ASCII: los rangos que conviene tener en la cabeza.
RangoContenidoNotas
0x00 – 0x1FCaracteres de controlNUL 0x00, BEL 0x07, TAB 0x09, LF 0x0A (salto de línea), CR 0x0D (retorno), ESC 0x1B
0x20EspacioPrimer carácter imprimible
0x30 – 0x39Dígitos ‘0’ a ‘9’El valor está en el nibble bajo: ‘7’ = 0x37
0x41 – 0x5AMayúsculas ‘A’ a ‘Z’‘A’ = 0x41, ‘Z’ = 0x5A
0x61 – 0x7AMinúsculas ‘a’ a ‘z’‘a’ = 0x61: 0x20 más que su mayúscula
0x7FDELBorrar; es el último código de ASCII
0x80 – 0xFFFuera de ASCIIExtensiones (Latin-1) o bytes de UTF-8

La tabla tiene diseño de ingeniero, no de azar. Dos regularidades valen oro:

Dígito ↔ valor
Los dígitos empiezan en 0x30, así que c - 0x30 (o c & 0x0F) da el número, y n + 0x30 da el carácter. Es el corazón de atoi y itoa.
Mayúscula ↔ minúscula
Difieren en un solo bit, el 0x20. c | 0x20 pasa a minúscula, c & ~0x20 a mayúscula y c ^ 0x20 las alterna. ‘G’ = 0x47 y ‘g’ = 0x67.
Los números llegan como texto
Si un sensor manda «1234» por serie, llegan los bytes 0x31 0x32 0x33 0x34, no el número 1234. Hay que reconstruirlo: v = v * 10 + (c - 0x30) por cada carácter.
Saltos de línea
Una «línea» puede terminar en LF (0x0A, Unix), CR (0x0D, Mac clásico) o CR LF (0x0D 0x0A, Windows y muchos protocolos). Un módulo AT que responde OK\r\n manda esos dos bytes de control al final.

Más allá de ASCII: Latin-1 y UTF-8

ASCII no tiene «ñ», «é» ni «€». Se resolvió con dos caminos. Latin-1 usa el octavo bit para otros 128 caracteres (‘ñ’ = 0xF1): un byte por carácter, pero solo europeo occidental. UTF-8 mantiene ASCII intacto para 0x00–0x7F y codifica el resto en 2 a 4 bytes que empiezan con un patrón de bits especial (‘ñ’ = 0xC3 0xB1). Por eso un texto ASCII puro es también UTF-8 válido, y por eso mostrar UTF-8 con una tabla Latin-1 produce «Ã±» en lugar de «ñ».

07 · Otros códigos

Gray y punto fijo: dos trucos que vas a cruzar

Hay muchos más convenios. Dos aparecen seguido en electrónica y merecen una mención.

Código Gray: cambiar un solo bit por paso

Al contar en binario, algunas transiciones cambian varios bits a la vez: de 3 (011) a 4 (100) cambian los tres. Si los bits provienen de sensores mecánicos que no conmutan exactamente a la vez, durante un instante la lectura puede ser 111 o 000: un salto falso. El código Gray reordena la secuencia para que de un número al siguiente cambie exactamente un bit.

Binario y Gray de 3 bits. En Gray cada fila difiere de la anterior en un solo bit.
DecimalBinarioGrayDecimalBinarioGray
00000004100110
10010015101111
20100116110101
30110107111100

La conversión es una línea: gray = n ^ (n >> 1). Los encoders rotativos absolutos y varios diseños de contadores y FIFO asíncronos usan Gray precisamente para que una lectura en pleno cambio sea, a lo sumo, el valor anterior o el siguiente, nunca uno disparatado.

Punto fijo: decimales sin coma flotante

Un microcontrolador pequeño no tiene unidad de coma flotante. Para guardar 25,0625 °C se usa punto fijo: se acuerda que el entero guardado está multiplicado por una potencia de dos (o de diez) y se divide al final. El sensor DS18B20, por ejemplo, entrega la temperatura en 16 bits con signo en unidades de 1/16 °C: la lectura 0x0191 es 401 / 16 = 25,0625 °C, y 0xFF5E es −162 / 16 = −10,125 °C. Es complemento a dos y punto fijo en un solo paquete.

08 · Simulador

Laboratorio: el camaleón

El simulador muestra un byte como ocho interruptores y lo lee a la vez en cuatro convenios: sin signo, con signo en complemento a dos, BCD y ASCII. Cambiá un bit y mirá cómo se mueve cada lectura; las que no tienen sentido para ese byte se marcan como inválidas.

  • Cajas amarillas: los bits en 1. El peso de cada posición está arriba; el del MSB es −128 (en naranja).
  • Línea inferior del gráfico: la suma de pesos con signo que produce el valor con signo.
  • Corchetes naranjas: los dos nibbles con su dígito hexadecimal; a la vez, los dos dígitos que leería un BCD.
  • Botones de operación: invertir, negar, sumar y restar 1, alternar mayúscula/minúscula. El mensaje de abajo explica qué pasó, incluidos los desbordes.
SIMULADOR 05

Un byte, cuatro lecturas

0x25 · cuatro lecturas

Un byte leído con pesos de complemento a dos Ocho cajas con los bits del byte y su peso con signo, la suma de pesos que da el valor con signo y los dos nibbles con su dígito hexadecimal. BYTE · PESOS CON SIGNO · 2 NIBBLES
Sin signo37
Con signo (Ca2)+37
BCD25 ✓
ASCII‘%’

0x25: 37 / +37 / BCD 25 / ‘%’.

Recorrido guiado (6 minutos)

  1. 1
    Mirá el byte inicial (0x25).

    Las cuatro lecturas dan algo válido: 37, +37, BCD «25» y ‘%’. Es el camaleón: un único byte con cuatro identidades.

  2. 2
    Pasá a 0xA5 (botón de ejemplo).

    Ahora el MSB está en 1: sin signo vale 165 y con signo −91 (−128 + 37). BCD es inválido (el nibble A no es dígito) y ASCII ya no aplica (mayor que 127). Dos convenios fallan.

  3. 3
    Apretá «Negar (NOT + 1)» sobre 0x25.

    Obtenés 0xDB, que con signo es −37. Volvé a negar: regresás a +37. Negar dos veces es la identidad.

  4. 4
    Probá 0x80 y negalo.

    Sigue siendo 0x80 = −128. Es el único byte que no tiene opuesto. Después probá con 0x00: negar el cero devuelve el cero (un solo cero en Ca2).

  5. 5
    Con 0x7F apretá «+ 1».

    Pasa de +127 a −128: desborde con signo. Con el mismo gesto sobre 0xFF obtenés 0x00: sin signo es un desborde (255 + 1 = 0), con signo es correcto (−1 + 1 = 0). Mismo byte, mismo circuito, veredictos distintos.

  6. 6
    Con 0x41 apretá «Alternar caso».

    ‘A’ (0x41) pasa a ‘a’ (0x61) y viceversa: un solo bit, el 0x20. Recorré el deslizador de 0x30 a 0x39 y verificá que los dígitos ASCII tienen el valor en el nibble bajo.

Preguntas para pensar

  1. Llevá el byte a 0xA5. ¿Qué lecturas son válidas y cuáles no? ¿Por qué?
  2. Buscá 0x41 y 0x61. ¿Qué caracteres son y cuánto difieren en valor?
  3. ¿En qué rango coinciden «sin signo» y «con signo»? ¿Y en qué rango coinciden con ASCII imprimible?
  4. ¿Para qué bytes BCD es válido? ¿Cuántos son de los 256 posibles?
Ver respuestas sugeridas
  1. Sin signo (165) y con signo (−91) son válidas; BCD no (el nibble alto es A) y ASCII no (mayor que 127). Dos convenios numéricos siempre aplican; los otros dos dependen de que el byte caiga en su dominio.
  2. ‘A’ y ‘a’; difieren en 0x20 = 32, un solo bit. Alternar caso es XOR con 0x20.
  3. Coinciden de 0 a 127 (0x00–0x7F): con el MSB en 0 el peso −128 no participa. Los imprimibles de ASCII son 0x20–0x7E, dentro de ese tramo; a partir de 128 divergen los números y ASCII deja de existir.
  4. Para los bytes con ambos nibbles entre 0 y 9: 10 × 10 = 100 de los 256 (el 39 %). El resto son códigos inválidos.

09 · Convenios en código

Cuatro lecturas, ocho experimentos

Reproducí en Python lo que viste en el simulador y agregá lo que el simulador no muestra: extensión de signo, desborde con banderas, suma BCD, texto y Gray. Los bloques usan solo enteros: lo que ves es lo que haría un microcontrolador.

Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.

Paso 1: un byte, cuatro lecturas

def lecturas(b):
    s = b - 256 if b >= 128 else b
    hi, lo = b >> 4, b & 0xF
    bcd = f"{hi}{lo}" if hi <= 9 and lo <= 9 else "invalido"
    if b > 127:
        asc = "no-ASCII"
    elif b < 32 or b == 127:
        asc = f"control 0x{b:02X}"
    else:
        asc = repr(chr(b))
    print(f"0x{b:02X} {b:08b}  U={b:>3}  S={s:+d}  BCD={bcd}  ASCII={asc}")


for b in (0x25, 0xA5, 0xFF, 0x41, 0x00, 0x0A):
    lecturas(b)

0x25 → 37 / +37 / «25» / ‘%’. 0xA5 → 165 / −91 / inválido / no-ASCII. ¿Qué byte es válido en los cuatro convenios y no es el 0x25? Probá 0x41 y 0x30.

Paso 2: a complemento a dos y de vuelta

def a_ca2(n, bits=8):
    lo, hi = -(1 << (bits - 1)), (1 << (bits - 1)) - 1
    if not lo <= n <= hi:
        print(f"{n} no cabe en {bits} bits con signo (rango {lo}..{hi})")
        return None
    v = n & ((1 << bits) - 1)
    print(f"{n:>5} -> {v:0{bits}b} = 0x{v:0{bits // 4}X}")
    return v


def de_ca2(v, bits=8):
    return v - (1 << bits) if v >> (bits - 1) else v


for n in (37, -37, -1, -91, 127, -128, 128):
    a_ca2(n)
print(de_ca2(0xA5), de_ca2(0xFF), de_ca2(0x80))

El truco n & 0xFF convierte cualquier entero de Python en su byte de complemento a dos. 128 no cabe: ¿qué rango tendría con bits=16?

Paso 3: negar paso a paso y restar sumando

def negar8(x):
    inv = ~x & 0xFF
    r = (inv + 1) & 0xFF
    print(f"-{x}: {x:08b} -> invertir {inv:08b} -> +1 {r:08b} = 0x{r:02X}")
    return r


for x in (91, 37, 1, 0, 128):
    negar8(x)

a, b = 5, 3
r = (a + negar8(b)) & 0xFF
print(f"5 - 3 = 5 + 0x{negar8(b):02X} = {r}")

91 → 0xA5, 37 → 0xDB, 0 → 0x00. Fijate en 128 (0x80): negarlo da el mismo byte. ¿Por qué 0 no cambia?

Paso 4: sumar con signo, acarreo y desborde

def suma8(a, b):
    ua, ub = a & 0xFF, b & 0xFF
    total = ua + ub
    r = total & 0xFF
    acarreo = total >> 8
    sr = r - 256 if r >= 128 else r
    desborde = (a >= 0) == (b >= 0) and (sr >= 0) != (a >= 0)
    marca = "SI" if desborde else "no"
    print(f"{a:>4} + {b:>4} = {sr:>4}   0x{ua:02X}+0x{ub:02X}=0x{r:02X}  acarreo={acarreo}  desborde={marca}")


suma8(5, -3)
suma8(100, 50)
suma8(127, 1)
suma8(-128, -1)
suma8(-70, -70)
suma8(-1, 1)

5 + (−3) tiene acarreo y no desborda. 100 + 50 desborda y no tiene acarreo. Son dos cosas distintas. ¿Hay alguna suma con ambas a la vez?

Paso 5: extensión de signo y valores de dos bytes

def extender(v, de=8, a=16):
    m = (1 << a) - 1
    s = v - (1 << de) if v >> (de - 1) else v
    print(f"0x{v:0{de // 4}X}: sin signo -> 0x{v:0{a // 4}X} ({v}), "
          f"con signo -> 0x{s & m:0{a // 4}X} ({s})")


for v in (0x25, 0xA5, 0xFF, 0x80):
    extender(v)


def leer16(alto, bajo):
    raw = (alto << 8) | bajo
    return raw - 65536 if raw & 0x8000 else raw


print(leer16(0xFF, 0x38), leer16(0x01, 0x2C))
print(int.from_bytes(bytes([0xFF, 0x38]), "big", signed=True))
print(leer16(0xFF, 0x5E) / 16, "grados (DS18B20)")

0xFF38 vale −200 y 0x012C vale +300. Un DS18B20 que entrega 0xFF5E marca −10,125 °C. ¿Qué pasa si leés el mismo par sin signo?

Paso 6: BCD, conversión y suma con corrección

def a_bcd(n):
    if not 0 <= n <= 99:
        raise ValueError("un byte BCD guarda 0..99")
    return ((n // 10) << 4) | (n % 10)


def de_bcd(b):
    hi, lo = b >> 4, b & 0xF
    return None if hi > 9 or lo > 9 else hi * 10 + lo


def suma_bcd(a, b):
    s = a + b
    if (a & 0xF) + (b & 0xF) > 9:
        s += 0x06
    if s > 0x99:
        s += 0x60
    res, acarreo = s & 0xFF, s >> 8
    print(f"{a:02X} + {b:02X} (BCD) = {acarreo}{res:02X}  -> {de_bcd(res)} con acarreo {acarreo}")


for n in (25, 9, 59, 99):
    print(n, "->", f"0x{a_bcd(n):02X}")
print(de_bcd(0x59), de_bcd(0xA5))
suma_bcd(0x25, 0x17)
suma_bcd(0x38, 0x29)
suma_bcd(0x99, 0x01)

0x25 + 0x17 = 0x42 y 0x99 + 0x01 = 0x00 con acarreo. Probá suma_bcd(0x59, 0x01): es lo que hace un reloj al pasar de 59 a 60 segundos.

Paso 7: texto, dígitos y mayúsculas

texto = "Hola 42!"
datos = texto.encode("ascii")
print(" ".join(f"{c:02X}" for c in datos))

# de texto a número, como haría un microcontrolador al recibir por UART
v = 0
for c in b"1234":
    v = v * 10 + (c - 0x30)
print(v, "y de vuelta:", str(v).encode().hex(" "))

# un solo bit separa mayúscula y minúscula
for ch in "aG":
    print(ch, f"0x{ord(ch):02X}", "^0x20 ->", chr(ord(ch) ^ 0x20))

# fuera de ASCII: la misma letra, dos codificaciones
print("ñ".encode("latin-1").hex(), "ñ".encode("utf-8").hex())

«Hola 42!» son los bytes 48 6F 6C 61 20 34 32 21. El 1234 llega como 31 32 33 34 y se reconstruye. ‘ñ’ ocupa un byte en Latin-1 (F1) y dos en UTF-8 (C3 B1).

Paso 8: código Gray

def a_gray(n):
    return n ^ (n >> 1)


def de_gray(g):
    n = 0
    while g:
        n ^= g
        g >>= 1
    return n


previo = 0
for n in range(8):
    g = a_gray(n)
    cambios = bin(g ^ previo).count("1")
    print(f"{n} {n:03b} -> Gray {g:03b}  bits que cambian: {cambios}  vuelta: {de_gray(g)}")
    previo = g

En Gray siempre cambia un único bit entre filas consecutivas (salvo la vuelta del 7 al 0, que también cambia uno). Comparalo con binario: de 3 a 4 cambian tres.

10 · Analogía de programador

Tipos, casts y encodings

Todo lo que viste en este tema ya existe en tu lenguaje favorito, con otros nombres. Ponerlos en correspondencia es lo que vuelve intuitiva la electrónica.

El mismo concepto, en dos mundos.
En softwareEn electrónica digital
uint8_t vs int8_tEl mismo byte, leído sin signo o en complemento a dos
(int8_t)0xA5 (cast)No cambia ningún bit: solo cambia el convenio de lectura
Promoción de char a int (printf("%x", (char)0xA5) muestra ffffffa5)Extensión de signo: copiar el MSB en los bits nuevos
int.from_bytes(b, "big", signed=True)Armar un valor de 16 bits con signo desde dos registros de 8
c - '0' y atoi / itoaRestar o sumar 0x30 entre el carácter y el valor del dígito
str.encode("utf-8")Elegir la tabla que convierte letras en bytes
Un número guardado como string «25»BCD: legible para mostrar, incómodo para operar
INT_MAX + 1 (comportamiento indefinido o envuelto)Desborde con signo: el resultado da la vuelta
  1. 1
    Ca2 = int8_t.

    Mismo byte que uint8_t, otra lectura. El tipo vive en el datasheet y en tu código; el hardware no lo guarda.

  2. 2
    BCD = formato de presentación.

    Como conservar «25» como cadena de dígitos: cada dígito es independiente y se muestra directo, pero para operar hay que convertir.

  3. 3
    ASCII = encoding.

    Una tabla acordada entre emisor y receptor. Sin acuerdo, una UART que manda ‘A’ recibe cualquier cosa.

La diferencia con un lenguaje de alto nivel es que ahí hay tipos, conversiones explícitas y a veces advertencias del compilador. En el hardware solo hay cables y un convenio por escrito: si el convenio se pierde, los bits siguen siendo bits perfectamente válidos, solo que sin sentido.

11 · Comprensión

Confusiones frecuentes

«El bit alto es el signo, el resto es el valor»

Eso es signo-magnitud, que casi no se usa. En complemento a dos el MSB pesa −128 y participa en el valor: 1000 0001 vale −127, no «−1». Si ves −1 es que lo leíste como signo-magnitud.

«Para negar un número alcanza con invertir los bits»

Eso es complemento a uno: da −x − 1. Falta sumar 1. Invertir 0x25 da 0xDA = −38; negar 0x25 da 0xDB = −37.

«BCD 0x25 es el número 37»

BCD 0x25 se lee «25»; en binario puro 0x25 es 37. Mezclarlos da horas imposibles. Siempre hay que saber qué convenio usa el registro.

«Acarreo y desborde son lo mismo»

El acarreo mira la suma sin signo (se salió un bit por la izquierda); el desborde mira la suma con signo (el resultado cambió de signo sin motivo). 5 + (−3) acarrea y no desborda; 100 + 50 desborda y no acarrea.

«Un byte con signo se amplía rellenando con ceros»

Solo si no tiene signo. Con signo hay que copiar el MSB: 0xA5 pasa a 0xFFA5 (−91), no a 0x00A5 (165). Es el bug típico al armar un valor de 16 bits desde dos registros.

«ASCII son 8 bits»

ASCII son 7 bits (0…127). Lo que pasa de 127 depende de la extensión (Latin-1, Windows-1252) o de UTF-8, donde los bytes ≥ 0x80 forman parte de caracteres de varios bytes.

«Si leo «5» por la UART tengo el número 5»

Tenés el byte 0x35 (53). Para obtener 5 hay que restar 0x30. Con varios dígitos, además hay que reconstruir el valor sumando en base 10.

12 · Práctica guiada

Ejercicios de convenios

Ejercicio 1: negar a mano

Representá −1, −37 y −128 en 8 bits con complemento a dos. Verificá con a_ca2() y negar8(). ¿Qué pasa si intentás negar −128?

Ver solución razonada

0xFF, 0xDB y 0x80. Para −37: 37 = 0x25 = 0010 0101 → invertir 1101 1010 (0xDA) → +1 = 1101 1011 (0xDB). Negar 0x80 devuelve 0x80: no existe +128 en 8 bits con signo, y por eso el rango es asimétrico.

Ejercicio 2: leer con signo

Interpretá como int8_t los bytes 0xF6 y 0x8C. Calculalo de dos formas: sumando pesos con el MSB en −128, y negando para obtener la magnitud.

Ver solución razonada

−10 y −116. 0xF6 = 1111 0110 = −128 + 64 + 32 + 16 + 4 + 2 = −10; comprobación: invertir da 0x09, +1 = 0x0A = 10. 0x8C = 1000 1100 = −128 + 8 + 4 = −116; invertir da 0x73 = 115, +1 = 116.

Ejercicio 3: detectar el desborde

Calculá en 8 bits con signo 90 + 60 y (−70) + (−70). ¿Cuál es el resultado de cada una, el acarreo y si hay desborde? Verificá con suma8().

Ver solución razonada

90 + 60 = 0x5A + 0x3C = 0x96 = −106: sin acarreo, con desborde (dos positivos dan un negativo). (−70) + (−70) = 0xBA + 0xBA = 0x174 → byte 0x74 = +116, con acarreo y con desborde (dos negativos dan un positivo). Un código que suma sensores con signo en un int8_t debe ampliar el ancho o comprobar el rango.

Ejercicio 4: el reloj en BCD

Un RTC marca 0x23 horas, 0x59 minutos y 0x59 segundos. ¿Qué hora es? ¿Qué hora vería tu programa si lo leyera en binario puro? ¿Qué valor BCD tiene el registro de segundos un instante después?

Ver solución razonada

Es 23:59:59. En binario puro serían 35, 89 y 89: una hora que no existe. Un segundo después, el RTC suma 1 al registro de segundos con corrección: 0x59 + 1 = 0x5A → +6 = 0x60 → el registro llega a 60, que vuelve a 0x00 con acarreo al minuto; las horas se reinician y todo queda en 00:00:00.

Ejercicio 5: un sensor de temperatura

Un DS18B20 entrega 16 bits en complemento a dos con resolución de 1/16 °C. Primero llega el byte bajo y después el alto. Recibís 0x5E y 0xFF, y más tarde 0x91 y 0x01. ¿Qué temperaturas son?

Ver solución razonada

Se arma el valor con el alto primero: 0xFF5E y 0x0191. Con signo, 0xFF5E = 65 374 − 65 536 = −162 → −162 / 16 = −10,125 °C. 0x0191 = 401 → 401 / 16 = 25,0625 °C. Si hubieras leído 0xFF5E sin signo habrías obtenido 4085 °C: el bug del invierno que vimos al principio.

Ejercicio 6: texto por la UART

Llegan los bytes 0x31 0x32 0x33. ¿Qué número se transmitió? ¿Y cómo se mandaría «−5»? Después alternás el caso de ‘g’ (0x67) con una sola operación.

Ver solución razonada

0x31 0x32 0x33 son los caracteres «123»: 123, reconstruido con v = v * 10 + (c - 0x30). «−5» se manda como 0x2D 0x35 (el signo menos es un carácter, ‘-’ = 0x2D). Para ‘g’: 0x67 ^ 0x20 = 0x47 = ‘G’.

13 · Síntesis

Ideas para recordar

  • Un byte no trae su significado: lo da el convenio. 0x25 = 37 = +37 = BCD «25» = ‘%’.
  • Complemento a dos: el MSB pesa −128; negar = invertir + 1; rango −128…+127 con un único cero.
  • El mismo sumador sirve con y sin signo; restar es sumar el opuesto.
  • Extensión de signo: para ampliar un valor con signo se copia el MSB, no ceros.
  • Acarreo = no cupo sin signo; desborde = no cupo con signo (mismo signo, resultado de signo contrario).
  • BCD: un dígito por nibble; A…F son inválidos; suma con corrección +6.
  • ASCII: 7 bits; ‘0’ = 0x30, ‘A’ = 0x41, ‘a’ = 0x61; mayúscula y minúscula difieren en el bit 0x20.
  • Gray cambia un bit por paso; punto fijo guarda decimales como entero escalado.
  • Mezclar convenios es un bug silencioso: siempre hay que anotar cuál se usa.

¿Listo para el Tema 6?

Podés avanzar si sos capaz de responder esto sin mirar:

  • Escribir −50 en complemento a dos de 8 bits.
  • Leer 0xC8 como número sin signo y con signo.
  • Decir por qué 127 + 1 en un int8_t da −128 y cómo detectarlo.
  • Convertir 0xFF a 16 bits con signo y sin signo.
  • Explicar por qué 0x59 + 1 en BCD no es 0x5A.
  • Convertir el carácter ‘7’ en el número 7 y el 7 en el carácter ‘7’.

Ya sabés qué significan los bits. Ahora hay que empezar a decidir con ellos: en el próximo tema, tablas de verdad, el lenguaje con el que se describe qué hace un circuito digital.