- Descreva o protocolo de comunicação serial SPI (Serial Peripheral Interface).
- Descreva o protocolo de comunicação serial I2C (Inter Integrated Circuits).
- 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.
é 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 |
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
. - A mensagem termina com o marcador de fim
seguido por um número que é o somatório dos valores ENTRE o e o . - O tamanho da mensagem pode variar.
- A mensagem sempre começa com o marcador de início
- Informação confiável: início =
, fim = 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
e contenha depois um
seguido por um byte, verificar se esse último byte é igual à soma dos valores entre
e
.
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
. - Estado IN_MSG: recebeu
; acumula os bytes lidos (somando) enquanto não vir o marcador de fim . - Estado GOT_END: recebeu
; espera o próximo byte que será o checksum recebido; compara com a soma calculada e decide “correta” ou “errada”; volta para WAIT_START.
- Estado WAIT_START: ignora tudo até encontrar o marcador de início
- Tratamento de casos estranhos:
- Se dentro de IN_MSG aparecer outro
antes do , trata‑se como reinício de mensagem (reseta soma e considera este novo como início). - Se byte de checksum não chegar (fluxo interrompido), o programa fica esperando — isso é natural em leitura serial contínua.
- Se dentro de IN_MSG aparecer outro
Como calcular a soma:
- Seja
onde são os bytes entre e . - Para evitar overflow ao somar muitos bytes, usar um acumulador com largura maior, por exemplo
ou . Na comparação final, converter adequadamente para o espaço do byte recebido ( ) se o protocolo define checksum em um byte. No enunciado os exemplos mostram somas pequenas, mas usar é 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 entree , ou retorna em caso de erro/nada para ler.
- Se a sua versão de
"serial.h"usar nomes diferentes (por exemploserial_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 usaserial_available()eserial_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 deserial_read()(pode ser-1).uint8_t b;— byte lido (quandor >= 0).
Fluxo principal:
- Loop infinito:
- Se
serial_available() > 0, lerr = serial_read(). - Se
r < 0, ignorar (erro temporário). - Caso contrário, converte para
b = (uint8_t) re processa conforme o estado:- WAIT_START: se
b == 13entãostate = IN_MSG; sum = 0; - IN_MSG:
- se
b == 13então reinicia a mensagem (sum = 0; state = IN_MSG;) — trata marca de início duplicada. - else se
b == 21entãostate = GOT_END;— agora o próximo byte será o checksum recebido. - else
sum += b;
- se
- GOT_END:
received_checksum = b;- compara
received_checksumcom(uint8_t) sum(ou comsumdependendo de política). - notifica se “MENSAGEM CORRETA” ou “MENSAGEM ERRADA” e mostra valores para depuração.
state = WAIT_START; sum = 0;
- WAIT_START: se
- Se
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) o programa deve imprimir “MENSAGEM CORRETA”. - Para
MSG2(sequência) o programa deve imprimir “MENSAGEM ERRADA”.
- Para
- Se o protocolo realmente usa soma sem reducción (por exemplo soma de 16 bits), adapte a comparação: compare
received_checksumcomsumdevidamente representado (se o checksum é mais de um byte então será preciso ler dois bytes depois doe combinar). - Caso a
serial.hforneça uma função bloqueante de leitura (ex.:int serial_read_blocking(void)), substitua o trecho deserial_available()+serial_read()por essa função bloqueante.
Resposta
- 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.
- 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.
- 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;
}