A Few Bits of Code

CEC Bus Monitor

Monitor the packets being sent via the one wire bus present in modern HDMI systems.

Summary

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.

Hardware

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.

bitx40v3_circuit_modified.png
Download Full-Size

Software

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				

Download Source (5.26 Kb)

Prototype

Here is a picture of the project built up on my 8052 based prototyping board:

cec_prototype_small.jpg
Download Full-Size

Future

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.