CEC Bus Monitor
Monitor the packets being sent via the one wire bus present in modern HDMI systems.
This project monitors the data being sent over the CEC bus found in modern HDMI cables. The CEC bus can be used to control operation of devices on the bus. When units connected to my TV's reciever failed to start and stop apporopriately I needed a way to inspect the packets of data being sent between devices. I used my favorite 8051 microcontroller to monitor the data being sent over the one wire bus.
The hardware for this comes from an Arduino Forum post by user AndrewNC on the subject. The following schematic is copied directly from his post. The circuit converts the CEC bus from 3V3 to 5V that the micro runs at. A simpler solution would be to jut use a 3V3 micro from the start.
The original project posted above used an Arduino. I have always been fond of the 8051 architechure and therefore based my project on it. You can see the code below which is used mostly to make sure that the various timings are correct. Since this project stated as a way to debug problem in the CEC bus the code checks each bit's start and end time against the protocols specs to make sure there are no timing errors. This differs from the above Arduino project which just assumes timing is correct and extracts information based on when it SHOULD be available if everything is working correctly.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; HDMI CEC Bus Monitor ; ; Monitor messages being transfered ; ; over the HDMI CEC Bus ; ; ; ; Author: Tim Shaffer ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Setup Timer2 for Keil uVision which doesn't recognize the associated registers TL2 EQU 0CCH TH2 EQU 0CDH TF2 EQU 0CFH TR2 EQU 0CAH ORG 0000H LJMP INIT ORG 0030H INIT: SETB P1.0 ;Set as INPUT ; CLR P1.1 ;Never used? CLR A MOV SP,#07H ;Set the Stack Pointer MOV R6,#00H MOV SCON,#52H ;MODE1(8bit UART set by Timer1), Receive Enable, Transmit Flag Set(TX ready) MOV TMOD,#21H ;Timer1 - 8-bit auto-reload, Timer0 - 16-bit Timer MOV PCON,#80H ;Double the baud rate MOV TH1,#0FFH ;28800 * 2 = 57600 baud SETB TR1 ;Start Timer1 (baud rate generator) MOV TH0,#00H ;Clear Timer0 Counter MOV TL0,#00H MOV TH2,#00H ;Clear Timer2 Counter MOV TL2,#00H CLR 00H ;Clear Error Indicator Bit START: MOV R1,#50H ;(Re)Set Pointer to memory of stored data MOV R6,#00H ;(Re)Set Counter, # of bytes of stored data LCALL START_BIT ;Check for Start Bit JNB 00H, GET_MSG ;Jump out if no Error LCALL TX_DPL ;Send error code CLR 00H ;Clear the error SJMP START ;Wait for Start Bit GET_MSG: JB 00H, START ;Jump out if Error, Possible? if error is caught in last 2 bits of GET_BYTE? LCALL GET_BYTE ;Get input (1 byte in A, and 2 bits in 01H and 02H bit memory) MOV @R1,A ;Store the byte INC R1 ;Increment data pointer INC R6 ;Increment byte counter JNB 01H,GET_MSG ;01H indicates end of message? CLR 00H MOV R1,#50H ;Reset data pointer MOV A,R6 ;Copy byte counter TX_MSG:;Copy the received data out to the UART MOV DPL,@R1 LCALL TX_DPL INC R1 ;Increment data pointer DEC A ;Until all bytes are read back JZ END_TX SJMP TX_MSG END_TX: MOV DPL,#20H ;Send (space) character ACALL TX_DPL SJMP START ;Go back to the beginning ;Check for Start Bit ;DPL holds ascii char indicating where error occured START_BIT:;Check for Minimum HIGH time on P1.0 JNB P1.0,$ ;Wait for P1.0 to go HIGH MOV DPL,#61H ;'a' MOV TH0,#0F3H ;Setup Timer0 MOV TL0,#68H SETB TR0 ;Start Timer0 MOV TH2,#0F0H ;Setup Timer2 MOV TL2,#8AH SETB TR2 ;Start Timer2 MEMA0:;Wait for Timer0 to overflow or for P1.0 to go LOW JB TF0,MEMA8 JB P1.0,MEMA0 ;Continue to wait if P1.0 is still HIGH SJMP MEMEA ;ERROR if P1.0 goes LOW before Timer0 overflows MEMA8:;Check for Maximum HIGH time on p1.0 MOV DPL,#62H ;'b' CLR TR0 ;Stop Timer0 CLR TF0 ;Clear Timer0 Overflow MOV TH0,#0FEH ;Reset Timer0 MOV TL0,#93H SETB TR0 ;Start Timer0 MEMB7:;Wait for Timer0 to overflow or for P1.0 to go LOW JB TF0,MEMEA ;ERROR if Timer0 overflows before P1.0 goes LOW JNB P1.0,MEMBF SJMP MEMB7 MEMBF:;Check for Minimum LOW time on P1.0 MOV DPL,#63H ;'c' CLR TR0 ;Stop Timer0 CLR TF0 ;Clear Timer0 Overflow MEMC6:;Wait for Timer2 to overflow or P1.0 to go HIGH JB TF2,MEMCE JB P1.0,MEMEA ;ERROR if P1.0 goes high again before minimum bit time SJMP MEMC6 MEMCE:;Check for Maximum LOW time on P1.0 MOV DPL,#64H ;'d' CLR TR2 ;Stop Timer2 CLR TF2 ;Clear Timer2 Overflow MOV TH0,#0FEH ;Setup Timer0 MOV TL0,#93H SETB TR0 ;Start Timer0 MEMDE:;Wait for Timer0 to overflow or P1.0 to go HIGH JB TF0,MEMEA ;ERROR if P1.0 does no go HIGH in time JB P1.0,MEME5 SJMP MEMDE MEME5:;SUCESS CLR TR0 ;Stop Timer0 CLR TF0 ;Clear Timer0 Overflow RET MEMEA:;FAIL CLR TR0 ;Stop Timer0 CLR TF0 ;Clear Timer0 Overflow CLR TR2 ;Stop Timer2 CLR TF2 ;Clear Timer2 Overflow SETB 00H ;Indicate Error RET ;Save a full byte of input plus the End of Message bit and the ACK bit GET_BYTE: MOV R0,#08H GET_BITS: LCALL GET_BIT ;Get input and save in Carry JB 00H, BYTE_ERROR ;Jump out if Error RLC A ;Rotate Carry into A DJNZ R0,GET_BITS ;Get a full byte of input and save it in A LCALL GET_BIT ;Get End of Message bit MOV 01H,C ;Move it into 01H bit memory NOP NOP ;Delay (~4.34 uSec @ 11.0852MHz) NOP NOP LCALL GET_BIT ;Get ACK bit, never used? MOV 02H,C ;Move it into 02H bit memory RET ;Error while getting input byte BYTE_ERROR: MOV A,#0FFH ;Save a dummy value instead CLR 00H RET ; Get a bit of input (from P1.0) and save (in Carry bit) if correct timing conditions are met GET_BIT: MOV TH2,#0F8H ;Setup Timer2 MOV TL2,#0ACH SETB TR2 ;Start Timer2 MOV TH0,#0FCH ;Setup Timer0 MOV TL0,#4EH SETB TR0 ;Start Timer0 JNB TF0,$ ;Wait for Timer0 to Overflow CLR TR0 ;Stop Timer0 CLR TF0 ;Clear Timer0 Overflow MOV C,P1.0 ;Sample input at the midpoint of the sampling period CPL C ;Our input is inversed, so convert the sampled bit to the logical bit JNB TF2,$ ;Wait for Timer2 to Overflow CLR TR2 ;Stop Timer2 CLR TF2 ;Clear Timer2 Overflow MOV TH0, #0FDH ;Setup Timer0 MOV TL0, #7FH SETB TR0 ;Start Timer0 BIT_WAIT: ;Check for Maximum bit time JB TF0, BIT_ERROR ;Error, bit did not finish in time JB P1.0, BIT_GOOD SJMP BIT_WAIT BIT_ERROR: SETB 00H ;Indicate Error BIT_GOOD: CLR TR0 ;Stop Timer0 CLR TF0 ;Clear Timer0 Overflow RET TX_DPL: JNB TI,$ ;wait for Tx to finish CLR TI ;send byte MOV SBUF,DPL RET END
Here is a picture of the project built up on my 8052 based prototyping board:
The project listed above was the first of many I did with the CEC protocol. I tried adding the ability to read and write to the bus using a single 8051 based processor with various levels of success. The biggest problem with using an 8051 is timing. Every bit must be timed correctly while allowing the micro to continue to monitor the bus for conflicts.
I believe that the AT89C51RD2 I am using with its increased clock speed, extra timers, and Programmable Counter Array could actually read AND write to the bus, but I never did find a bulletproof way to do it. At some point I eventually had to cut corners and assume that no timing problems were occuring because I just couldn't measure every bit while still doing work.
A better, and far simpler, solution is to use multiple microcontrollers. Using one micro to write to the bus and one to read it should be fairly simple, but I will continue try for a single chip solution as a challenge.