1. Descreva o protocolo de comunicação serial SPI (Serial Peripheral Interface).
  2. Descreva o protocolo de comunicação serial I2C (Inter Integrated Circuits).
  3. Grande parte dos protocolos de comunicação serial se utilizam da checagem de integridade para garantir que a mensagem está correta. Uma delas é realizar a soma de todos os valores da mensagem e enviar esse resultado por último. Para indicar o começo de uma mensagem é comum se utilizar de um valor padrão, por exemplo o número 13, e um segundo número padrão
    é utilizado para marcar o final da mensagem, por exemplo 21. Utilizando essas informações podemos verificar, por exemplo, que a MSG1 abaixo está correta pois o somatório dos números entre o 13 e o 21 , é igual a 28 , conforme escrito na posição depois do número 24 . Já a MSG2 está errada pois a soma retorna 20 e não 21 como representado.
MSG1 13 01 02 05 08 01 03 10 21 28
MSG2 13 00 01 02 03 05 09 10 21 25
Dado que a biblioteca “serial.h” permite ao programador ler qualquer informação que for recebida pela serial, faça um programa em C que realize ciclicamente a leitura dos valores recebidos pela serial e calcule se a mensagem está correta ou errada. O tamanho da mensagem pode variar. As únicas informações confiáveis são de que a mensagem sempre começa com um número 13 , e termina com um número 21 seguido de um número com o somatório dos números intermediários.

Passo 1

O enunciado contém três pedidos separados:
  • (28) Descrever o protocolo de comunicação serial SPI.
  • (29) Descrever o protocolo de comunicação serial I2C.
  • (30) Escrever um programa em C que, ciclicamente, leia valores recebidos pela serial (usando a biblioteca "serial.h") e verifique se cada mensagem está correta ou errada, sabendo que:
    • A mensagem sempre começa com o marcador de início ...13....
    • A mensagem termina com o marcador de fim ...21... seguido por um número que é o somatório dos valores ENTRE o ...13... e o ...21....
    • O tamanho da mensagem pode variar.
  • Informação confiável: início = ...13..., fim = ...21... e depois vem o checksum (soma dos números intermediários).

Passo 2

Resposta didática e passo a passo sobre o protocolo SPI.
O que é SPI em poucas palavras:
  • SPI (Serial Peripheral Interface) é um protocolo serial síncrono, ponto a ponto (normalmente mestre–escravo) usado para comunicação de alta velocidade entre um microcontrolador (master) e dispositivos periféricos (slaves), como ADCs, DACs, memórias SPI, displays, sensores.
Linhas físicas principais:
  • MOSI (Master Out Slave In): linha de dados do master para o slave.
  • MISO (Master In Slave Out): linha de dados do slave para o master.
  • SCLK (Serial Clock): relógio gerado pelo master para sincronizar os bits.
  • SS (Slave Select) ou CS (Chip Select): uma linha por slave (geralmente ativa em nível baixo) usada para selecionar qual slave está ativo.
Como os dados trafegam (visão intuitiva):
  • O master seleciona um slave puxando o respectivo SS para o nível ativo.
  • O master gera pulsos em SCLK; a cada pulso um bit é transferido.
  • A comunicação é full‑duplex: enquanto o master envia um bit no MOSI, o slave pode enviar um bit no MISO ao mesmo tempo.
Modos de operação (CPOL/CPHA):
  • SPI tem quatro modos (denominados 0 a 3) definidos por dois parâmetros:
    • CPOL (Clock Polarity): define nível estacionário do clock (0 → idle = 0, 1 → idle = 1).
    • CPHA (Clock Phase): define em qual borda do clock o bit é amostrado (0 → amostra na borda de transição 1, 1 → amostra na borda de transição 2).
  • Dica prática: master e slave devem concordar no mesmo modo. Se o dado parece “shiftado”, provavelmente há desacordo em CPOL/CPHA.
Ordem de bits:
  • Pode ser MSB first (mais comum) ou LSB first. Deve ser combinada entre dispositivos.
Características importantes:
  • Velocidade alta e baixa sobrecarga de protocolo (sem endereçamento interno).
  • Simples e determinístico, bom para curtas distâncias na mesma placa.
  • Não padroniza níveis de tensão (3,3 V, 5 V etc.); verifique compatibilidade.
  • Requer uma linha SS por slave (hardware adicional para múltiplos slaves).
  • Sem ACK/NACK embutido — integridade deve ser tratada por checksum/CRC no nível de aplicação, se necessário.
Quando usar SPI:
  • Quando precisa de taxa alta e latência baixa e há poucos fios disponíveis por slave.
  • Não é ideal se for preciso comunicar muitos dispositivos com poucas linhas (a não ser um decoder para seleções).
Resumo prático:
  • 4 fios básicos (MOSI, MISO, SCLK, SS), full‑duplex síncrono, modos CPOL/CPHA, sem endereçamento nem ACK nativo, muito rápido e simples.

Passo 3

Resposta didática e passo a passo sobre o protocolo I2C.
O que é I2C em poucas palavras:
  • I2C (Inter-Integrated Circuit) é um barramento serial síncrono multi‑mestre/multi‑slave que usa apenas duas linhas e permite conectar vários dispositivos usando endereços.
Linhas físicas principais:
  • SDA (Serial Data): linha de dados bidirecional.
  • SCL (Serial Clock): clock gerado pelo master (ou esticado por slaves).
Características elétricas e sinais:
  • Linhas são open‑drain/open‑collector e exigem resistores pull‑up externos. Isso permite que múltiplos dispositivos ‘puxem’ a linha para baixo sem conflitos de driver.
  • Start condition: quando SDA cai de 1 → 0 enquanto SCL = 1.
  • Stop condition: quando SDA sobe de 0 → 1 enquanto SCL = 1.
  • Após Start, master envia endereço (7 ou 10 bits) + um bit R/W, e o device com o endereço responde com ACK (0) ou NACK (1) na próxima borda.
Formato de quadro (visão simplificada):
  • Cada byte transmitido é 8 bits, seguido por 1 bit de ACK (0) enviado pelo receptor.
  • ACK = 0 indica que receptor recebeu o byte; NACK = 1 indica não aceitou/último byte.
Endereçamento:
  • Normalmente 7 bits (128 endereços, alguns reservados). Também existe 10‑bit addressing para casos especiais.
  • O master escolhe o endereço e o tipo de operação (leitura/escrita).
Recursos avançados:
  • Clock stretching: o slave pode manter SCL em nível baixo (esperar) para ganhar tempo — master precisa suportar isso.
  • Multi-master: se houver dois masters, existe um mecanismo de arbitragem (quem mantém SCL e SDA).
  • Velocidades típicas: Standard (100 kHz), Fast (400 kHz), Fast Plus (1 MHz), High-Speed (3,4 MHz) — nem todos os dispositivos suportam todos os modos.
Vantagens:
  • Apenas duas linhas para muitos dispositivos.
  • Endereçamento e ACK ajudam controle e detecção de presença.
  • Fácil de usar para configurações e sensores lentos.
Desvantagens:
  • Menos rápido que SPI em geral.
  • Requer resistores pull‑up e cuidados com capacitância do barramento.
  • Protocolicamente mais complexo (endereços, ACKs, condições START/STOP).
Resumo prático:
  • 2 fios (SDA, SCL), endereçamento, ACK/NACK, open‑drain com pull‑ups, bom para múltiplos dispositivos e configurações, mas geralmente mais lento que SPI.

Passo 4

Como vamos resolver o pedido (30) — estratégia geral e explicação didática.
Objetivo: ciclicamente ler bytes da serial e, sempre que encontrar uma sequência que comece com ...13... e contenha depois um ...21... seguido por um byte, verificar se esse último byte é igual à soma dos valores entre ...13... e ...21....
Estratégia robusta (state machine):
  • Usar um pequeno autômato (máquina de estados) para processar o fluxo contínuo de bytes sem pressupor tamanho fixo:
    • Estado WAIT_START: ignora tudo até encontrar o marcador de início ...13....
    • Estado IN_MSG: recebeu ...13...; acumula os bytes lidos (somando) enquanto não vir o marcador de fim ...21....
    • Estado GOT_END: recebeu ...21...; espera o próximo byte que será o checksum recebido; compara com a soma calculada e decide “correta” ou “errada”; volta para WAIT_START.
  • Tratamento de casos estranhos:
    • Se dentro de IN_MSG aparecer outro ...13... antes do ...21..., trata‑se como reinício de mensagem (reseta soma e considera este novo ...13... como início).
    • Se byte de checksum não chegar (fluxo interrompido), o programa fica esperando — isso é natural em leitura serial contínua.
Como calcular a soma:
  • Seja \text{checksum} = \sum_{i} v_i onde v_i são os bytes entre ...13... e ...21....
  • Para evitar overflow ao somar muitos bytes, usar um acumulador com largura maior, por exemplo \text{uint16\_t} ou \text{uint32\_t}. Na comparação final, converter adequadamente para o espaço do byte recebido ( \text{uint8\_t}) se o protocolo define checksum em um byte. No enunciado os exemplos mostram somas pequenas, mas usar \text{uint16\_t} é mais seguro.
Assunções sobre a biblioteca “serial.h”:
  • O enunciado informou que "serial.h" permite ler qualquer informação recebida. Como a API exata não foi detalhada, adoto uma interface razoável e comum:
    • int serial_available(void); retorna quantos bytes estão disponíveis para leitura (>= 0).
    • int serial_read(void); lê e retorna o próximo byte como valor entre ...0... e ...255..., ou retorna ...-1... em caso de erro/nada para ler.
  • Se a sua versão de "serial.h" usar nomes diferentes (por exemplo serial_read_byte() ou uma função bloqueante), adapte a chamada de leitura, mantendo a lógica da máquina de estados. No passo seguinte forneço um código que usa serial_available() e serial_read().

Passo 5

Descrição detalhada da implementação (pré‑código) — explicando cada parte que aparecerá no programa.
Variáveis e tipos:
  • enum state_t { WAIT_START, IN_MSG, GOT_END }; — guarda o estado atual.
  • uint16_t sum; — acumulador da soma dos bytes intermediários.
  • int r; — resultado de serial_read() (pode ser -1).
  • uint8_t b; — byte lido (quando r >= 0).
Fluxo principal:
  • Loop infinito:
    • Se serial_available() > 0, ler r = serial_read().
    • Se r < 0, ignorar (erro temporário).
    • Caso contrário, converte para b = (uint8_t) r e processa conforme o estado:
      • WAIT_START: se b == 13 então state = IN_MSG; sum = 0;
      • IN_MSG:
        • se b == 13 então reinicia a mensagem (sum = 0; state = IN_MSG;) — trata marca de início duplicada.
        • else se b == 21 então state = GOT_END; — agora o próximo byte será o checksum recebido.
        • else sum += b;
      • GOT_END:
        • received_checksum = b;
        • compara received_checksum com (uint8_t) sum (ou com sum dependendo de política).
        • notifica se “MENSAGEM CORRETA” ou “MENSAGEM ERRADA” e mostra valores para depuração.
        • state = WAIT_START; sum = 0;
Mensagem de saída:
  • Para visualizar o resultado, o programa usa printf() (ou outra função de escrita serial se necessário). Em sistemas embarcados, substitua por função que envie pela serial.

Passo 6

Programa C completo (implementação) — código comentado e direto para uso. Este código segue a estratégia descrita e usa as funções assumidas de "serial.h" mencionadas anteriormente. Se a sua "serial.h" tiver APIs diferentes, adapte somente a parte de leitura (serial_available()/serial_read()).
#include "serial.h"   // Biblioteca fornecida pelo enunciado (assumida)
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>

/* Assunção de API em serial.h:
   - int serial_available(void); // retorna número de bytes disponíveis para leitura
   - int serial_read(void);      // lê e retorna próximo byte (0..255) ou -1 se nenhum/erro
   Se a sua serial.h tiver funções diferentes, adapte as chamadas de leitura.
*/

int main(void)
{
    enum { WAIT_START, IN_MSG, GOT_END } state = WAIT_START;
    uint16_t sum = 0; /* acumulador (16 bits para evitar overflow imediato) */

    while (1) {
        /* Verifica se há dados disponíveis */
        if (serial_available() <= 0) {
            /* Nenhum dado no momento; em sistemas embarcados pode-se colocar um sleep curto */
            continue;
        }

        int r = serial_read();
        if (r < 0) {
            /* leitura falhou ou nada para ler; ignora */
            continue;
        }

        uint8_t b = (uint8_t) r;

        switch (state) {
            case WAIT_START:
                if (b == 13) {           /* início detectado */
                    sum = 0;
                    state = IN_MSG;
                }
                /* senão: ignora bytes até achar 13 */
                break;

            case IN_MSG:
                if (b == 13) {
                    /* apareceu outro marcador de início antes do fim: reinicia mensagem */
                    sum = 0;
                    state = IN_MSG; /* continua em IN_MSG */
                } else if (b == 21) {
                    /* fim detectado: próximo byte é checksum recebido */
                    state = GOT_END;
                } else {
                    /* acumula o byte intermediário */
                    sum += b;
                }
                break;

            case GOT_END:
                {
                    uint8_t received_checksum = b;
                    uint8_t computed_checksum = (uint8_t) sum; /* reduz para 8 bits, se for o protocolo */

                    if (received_checksum == computed_checksum) {
                        printf("MENSAGEM CORRETA: soma = %u (recebido = %u)\n",
                               (unsigned) computed_checksum, (unsigned) received_checksum);
                    } else {
                        printf("MENSAGEM ERRADA: soma calculada = %u, soma recebida = %u\n",
                               (unsigned) computed_checksum, (unsigned) received_checksum);
                    }
                    /* depois de processar, volta a esperar novo início */
                    state = WAIT_START;
                    sum = 0;
                }
                break;

            default:
                state = WAIT_START;
                sum = 0;
                break;
        }
    }

    return 0;
}
Dicas finais de uso e testes:
  • Teste com os exemplos do enunciado:
    • Para MSG1 (sequência ...13,01,02,05,08,01,03,10,21,28...) o programa deve imprimir “MENSAGEM CORRETA”.
    • Para MSG2 (sequência ...13,00,01,02,03,05,09,10,21,25...) o programa deve imprimir “MENSAGEM ERRADA”.
  • Se o protocolo realmente usa soma sem reducción (por exemplo soma de 16 bits), adapte a comparação: compare received_checksum com sum devidamente representado (se o checksum é mais de um byte então será preciso ler dois bytes depois do ...21... e combinar).
  • Caso a serial.h forneça uma função bloqueante de leitura (ex.: int serial_read_blocking(void)), substitua o trecho de serial_available() + serial_read() por essa função bloqueante.

Resposta

  1. SPI: protocolo serial síncrono mestre–escravo com quatro sinais principais (MOSI, MISO, SCLK, SS), full‑duplex, modos CPOL/CPHA (0…3), sem endereçamento nem ACK nativo, muito rápido e usado para periféricos de alta taxa.
  2. I2C: barramento síncrono multi‑mestre/multi‑slave com duas linhas (SDA, SCL) open‑drain + pull‑ups, endereçamento (7/10 bits), bytes de 8 bits seguidos de ACK/NACK, suporta clock stretching e vários modos de velocidade; ideal para múltiplos dispositivos com poucas linhas.
  3. Programa C (exato, pronto para compilar/adaptar conforme a API real de “serial.h”):
#include "serial.h"   // Biblioteca fornecida pelo enunciado (assumida)
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>

/* Assunção de API em serial.h:
   - int serial_available(void); // retorna número de bytes disponíveis para leitura
   - int serial_read(void);      // lê e retorna próximo byte (0..255) ou -1 se nenhum/erro
   Se a sua serial.h tiver funções diferentes, adapte as chamadas de leitura.
*/

int main(void)
{
    enum { WAIT_START, IN_MSG, GOT_END } state = WAIT_START;
    uint16_t sum = 0; /* acumulador (16 bits para evitar overflow imediato) */

    while (1) {
        /* Verifica se há dados disponíveis */
        if (serial_available() <= 0) {
            /* Nenhum dado no momento; em sistemas embarcados pode-se colocar um sleep curto */
            continue;
        }

        int r = serial_read();
        if (r < 0) {
            /* leitura falhou ou nada para ler; ignora */
            continue;
        }

        uint8_t b = (uint8_t) r;

        switch (state) {
            case WAIT_START:
                if (b == 13) {           /* início detectado */
                    sum = 0;
                    state = IN_MSG;
                }
                /* senão: ignora bytes até achar 13 */
                break;

            case IN_MSG:
                if (b == 13) {
                    /* apareceu outro marcador de início antes do fim: reinicia mensagem */
                    sum = 0;
                    state = IN_MSG; /* continua em IN_MSG */
                } else if (b == 21) {
                    /* fim detectado: próximo byte é checksum recebido */
                    state = GOT_END;
                } else {
                    /* acumula o byte intermediário */
                    sum += b;
                }
                break;

            case GOT_END:
                {
                    uint8_t received_checksum = b;
                    uint8_t computed_checksum = (uint8_t) sum; /* reduz para 8 bits, se for o protocolo */

                    if (received_checksum == computed_checksum) {
                        printf("MENSAGEM CORRETA: soma = %u (recebido = %u)\n",
                               (unsigned) computed_checksum, (unsigned) received_checksum);
                    } else {
                        printf("MENSAGEM ERRADA: soma calculada = %u, soma recebida = %u\n",
                               (unsigned) computed_checksum, (unsigned) received_checksum);
                    }
                    /* depois de processar, volta a esperar novo início */
                    state = WAIT_START;
                    sum = 0;
                }
                break;

            default:
                state = WAIT_START;
                sum = 0;
                break;
        }
    }

    return 0;
}

Exercícios relacionados