El protocolo IIC (Inter-Integrated Circuit) es fundamental para la comunicación entre diversos componentes electrónicos, como sensores (MPU6050), cámaras (OV5640) y pantallas OLED. Dominar su implementación en hardware es esencial para el desarrollo de sistemas embebidos.
Descripción General del Protocolo IIC
El IIC opera bajo un esquema maestro-esclavo. El maestro inicia y controla todas las transacciones, mientras que los esclavos responden a las solicitudes del maestro. La comunicación se realiza a través de dos líneas: SDA (Serial Data) para la transmisión de datos y SCL (Serial Clock) para la sincronización.
Estados del Protocolo IIC
El protocolo IIC se define por cinco estados clave que, combinados secuencialmente, forman una comunicación completa:
- Estado Inactivo (Idle): Ambas líneas, SCL y SDA, se encuentran en nivel alto.
- Estado de Inicio (Start): El maestro genera una transición de SDA de alto a bajo mientras SCL está en alto, señalizando el inicio de una transmisión.
- Estado de Transmisión de Datos (Data Transfer): Los datos se envían o reciben bit a bit. Durante el ciclo bajo de SCL, se pone un nuevo valor en SDA. Durante el ciclo alto de SCL, el receptor lee el bit de datos.
- Estado de Reconocimiento (Acknowledge/NACK): Tras la transferencia de cada byte, el receptor indica si ha recibido los datos correctamente. Un nivel bajo en SDA durante el ciclo alto de SCL significa un reconocimiento (ACK), mientras que un nivel alto indica no reconocimiento (NACK).
- Estado de Parada (Stop): El maestro genera una transición de SDA de bajo a alto mientras SCL está en alto, indicando el fin de la transmisión.
Operaciones de Lectura y Escritura
Escritura IIC
Una operación de escritura típica involucra la transmisión de la dirección del esclavo (con el bit de escritura en 0), seguida de la dirección del registro y, finalmente, los datos a escribir. La dirección del esclavo suele ser de 7 bits, complementada con el bit de dirección (R/W) para formar un byte.
Lectura IIC
La lectura es ligeramente más compleja e involucra más estados. Tras enviar la dirección del esclavo y la dirección del registro (en modo escritura), se cambia al modo lectura (bit R/W en 1) para que el esclavo comience a transmitir los datos solicitados. Se requiere un ACK después de cada byte recibido, excepto tras el último byte, donde se puede enviar un NACK para indicar el fin de la lectura.
Implementación en Verilog
La implementación se basa en una máquina de estados finitos (FSM) que maneja las transiciones entre los estados del protocolo IIC. Las señales SDA y SCL se controlan según la lógica definida para cada estado.
Definición de Estados
Se deefinen los estados de la máquina de estados utilizando parámetros:
localparam IIC_IDLE = 6'b000_001; // Estado inactivo
localparam IIC_START = 6'b000_010; // Estado de inicio
localparam IIC_WRDATA = 6'b000_100; // Estado de escritura de datos
localparam IIC_RDDATA = 6'b001_000; // Estado de lectura de datos
localparam IIC_ACK = 6'b010_000; // Estado de reconocimiento
localparam IIC_STOP = 6'b100_000; // Estado de parada
Lógica de Transición de Estados
La siguiente estructura always define las transiciones basadas en condiciones específicas de contadores y señales de solicitud/finalización. Se requiere un análisis detallado junto con la simulación para comprender completamente la lógica de control de SDA y SCL.
always @(*) begin
case(state)
IIC_IDLE:
if(IICWriteReq == 1'b1 || IICReadReq == 1'b1)
next_state <= IIC_START;
else
next_state <= IIC_IDLE;
IIC_START:
// Lógica para generar la señal START
if(IICCnt == (IIC_Pre * 'd2)) // Asumiendo IICCnt y IIC_Pre son contadores
next_state <= IIC_WRDATA;
else
next_state <= IIC_START;
IIC_WRDATA:
// Lógica para transmitir un byte de datos
if(IICBitCnt == 'd8 && IICCnt == IIC_Pre /4 && iicCLK == 1'b0) // Asumiendo IICBitCnt, iicCLK
next_state <= IIC_ACK;
else
next_state <= IIC_WRDATA;
IIC_RDDATA:
// Lógica para recibir un byte de datos
if(IICBitCnt == 'd8 && IICCnt == IIC_Pre /4 && iicCLK == 1'b0)
next_state <= IIC_ACK;
else
next_state <= IIC_RDDATA;
IIC_ACK:
// Lógica para enviar o recibir ACK/NACK
if(IICACKStopCnt == 'd1 && IICCnt == IIC_Pre /4 && iicCLK == 1'b0) begin // Asumiendo IICACKStopCnt
if(IICSendBytes == 'd3) // Asumiendo IICSendBytes
if(IICWriteReq == 1'b1)
next_state <= IIC_STOP;
else
next_state <= IIC_RDDATA;
else if(IICSendBytes == 'd2 && IICReadReq == 1'b1)
next_state <= IIC_START;
else if(IICSendBytes == 'd4)
next_state <= IIC_STOP;
else
next_state <= IIC_WRDATA;
end
else
next_state <= IIC_ACK;
IIC_STOP:
// Lógica para generar la señal STOP
if(IICACKStopCnt == 'd1 && IICCnt == IIC_Pre/4 && iicCLK == 1'b1)
next_state <= IIC_IDLE;
else
next_state <= IIC_STOP;
default: next_state <= IIC_IDLE;
endcase
end
El código completo del módulo IIC consta de aproximadamente 280 líneas de Verilog. Este módulo es capaz de interactuar con dispositivos como el sensor OV5640, el módulo MPU6050 y pantallas OLED de 0.96 pulgadas.
Entorno de Simulación (Testbench)
Es crucial verificar la funcionalidad del módulo IIC mediante simulación. El siguiente testbench configura las señales de reloj, reset, y las solicitudes de lectura/escritura para probar el driver IIC.
`timescale 1ns/1ps
module testbench();
reg clk;
reg rst;
wire SDA;
wire SCL;
reg IICWriteReq;
reg IICReadReq;
wire IICWriteDone;
wire IICReadDone;
// Generación de reloj
always # 50 clk = ~clk;
// Secuencia de inicialización y reset
initial begin
clk = 1'b1;
rst = 1'b1;
IICWriteReq = 1'b0;
IICReadReq = 1'b1; // Iniciar con una solicitud de lectura
#100 // Reset manual
rst = 1'b0;
#100
rst = 1'b1;
end
// Control de la solicitud de lectura
always@(posedge clk)
if(IICReadDone == 1'b1)
IICReadReq <= 1'b0; // Desactivar solicitud de lectura una vez completada
else
IICReadReq <= IICReadReq;
// Instanciación del módulo IIC Driver
IIC_Driver IIC_DriverHP(
.sys_clk (clk), // Reloj del sistema
.rst_n (rst), // Reset activo bajo
.IICSCL (SCL), // Salida SCL
.IICSDA (SDA), // Línea SDA
.IICSlave ('h1234), // Dirección del esclavo (ejemplo)
.IICWriteReq (IICWriteReq), // Solicitud de escritura
.IICWriteDone (IICWriteDone), // Indicador de escritura completada
.IICWriteData ('h5a), // Datos a escribir (dirección esclavo + reg + dato)
.IICReadReq (IICReadReq), // Solicitud de lectura
.IICReadDone (IICReadDone), // Indicador de lectura completada
.IICReadData () // Datos leídos (salida del módulo)
);
endmodule
La simulación reveló errores iniciales, destacando la importancia de este paso en el proceso de diseño de hardware.