MFM Write Sector with Timer


Using Timer 0 as pulse generator

Timer 0 has already been used to decode the MFM data stream for reading so the idea was to make also use of Timer 0 to generate the MFM stream to write a sector.

We use the fast PWM mode of Timer 0 with OCR0A being the TOP and using OC0B as the write date signal. In fast PWM mode we have the option to clear OC0B when at BOTTOM and set OC0B on a match with OCR0B.

We encode the data into a MFM stream by changing OCR0A after every interval. We use the OCF0A flag in TIFR0 to know when an interval has finished. When TCNT0 has reached OCF0A the timer will be cleared and TIFR0 is set. As we have programmed OC0B to be cleared at bottom, OC0B will be de-asserted. As OCR0B is set to a fixed value of 7 this will result in a negative pulse of 500ns when Timer 0 is running at the 16MHz CPU clock. When the timer is cleared we need to reset the OCF0A flag and set CR0A for the new interval depending on the current status and the next bit to be written. For this we store the three possible values for short, medium and long interval into registers named ishort, imedium and ilong. I created a small macro that checks for the overflow bit, clears it and sets the new TOP value for the next interval into OCR0A.


	ldi	temp, 32-1
	mov	ishort, temp
	ldi	temp, 48-1
	mov	imedium, temp
	ldi	temp, 64-1
	mov	ilong, temp

	.macro	nxtint
	sbis	TIFR0, OCF0A
	rjmp	PC-1
	sbi	TIFR0, OCF0A
	out	OCR0A, @0
	.endmacro

The next step is to initialize the timer. First we make sure it is stopped. Then we set the OCR0B to a value that creates a negative pulse for 500ns which is the typical standard. Before we start the timer we assert the WG signal and then let the timer start with a count value of 3. this is because between the time we set COM0Bx and the time we start the timer by writing TCCR0B the OC0B output will already be controlled by the timer and OC0B will already be low when we write TCCR0A. This is just to make the negative pulse of the first interval to be as close to 500ns as possible.

	out	TCCR0B, zero		; stop timer

	ldi	temp, 7			;
	out	OCR0B, temp		; write pulse is low for 500ns

	sbis    TIFR1, TOV1		; wait for the correct moment to start
	rjmp    PC-1			; writing

	sbi	PORTB, DBG1
	cbi	PORTC, WG

	ldi	temp, 3
	out	TCNT0, temp		; reset timer 
	out	OCR0A, ishort		; short interval

	sbi	TIFR0, OCF0A		; reset overflow flag

	ldi	temp, (1<<COM0B1)|(1<<COM0B0)|(1<<WGM01)|(1<<WGM00)
	out	TCCR0A, temp

	ldi	temp, (1<<CS00)|(1<<WGM02)
	out	TCCR0B, temp		; start timer

Then we start by writing the pre-amble which consists of 96 short intervales

	ldi	count, 95
writetimergap010:
	sbis	TIFR0, OCF0B
	rjmp	PC-1
	sbi	TIFR0, OCF0B
	dec	count
	brne	writetimergap010

Followed by the pattern that creates the three 0xA1 MFM sync marks and then we jump into the statemachine to corresponding state

	nxtint	imedium
	nxtint	ilong
	nxtint	imedium
	nxtint	ilong
	nxtint	imedium

	nxtint	ishort
	nxtint	ilong
	nxtint	imedium
	nxtint	ilong
	nxtint	imedium

	nxtint	ishort
	nxtint	ilong
	nxtint	imedium
	nxtint	ilong
	nxtint	imedium

	rjmp	writestatex1

A small state machine decides the next interval. Y points to the buffer to be written, which typically includes the data mark 0xFB, the sector data, the CRC and the trailer with some 0x4E values. X is the length of the buffer.

;--------------------------------------------------------------------------
;
;	State 00
;
;	The two previous data bits were 00. This implies that we had a
;	clock pulse
;		----->
;	Data	0   0|
;	Clock	  1  |
;	MFM	0 1 0|
;
writestate00:
	dec	count
	brpl	writel0010
	sbiw	xh:xl, 1
	breq	writes0000
	ldi	count, 7
	ld	char, Y+
writel0010:
	lsl	char
	brcs	writel0015
;
;	If the next bit is 0 then we need to create a another clock
;	pulse, or in other words a short interval 
;
;		----->
;	Data	0   0|  0
;	Clock	  1  |1 
;	MFM	0 1 0|1 0
;
	nxtint	ishort
	rjmp	writestate00
;
;	If the next bit is 1 then we need to create a data pulse, or
;	in other words a medium interval  
;
writel0015:
	nxtint	imedium
;
;	Fall through to state 01
;--------------------------------------------------------------------------
;
;	State x1
;
;	The previous data bit was 1. This implies that we had a
;	data pulse
;		----->
;	Data	x   1|
;	Clock	  0  |
;	MFM	x 0 1|
;
writestatex1:
	dec	count
	brpl	writel0020
	sbiw	xh:xl, 1
	breq	writes0000
	ldi	count, 7
	ld	char, Y+
writel0020:
	lsl	char
	brcc	writestate10
;
;	if the next bit is 1 then we need to create another data pulse,
;	or in other words a short interval 
;
;		----->
;	Data	x   1|  1
;	Clock	  0  |0 
;	MFM	x 0 1|0 1
;
	nxtint	ishort
	rjmp	writestatex1
;--------------------------------------------------------------------------
;
;	State 10
;
;	The two previous data bits were 10. This implies that we had a
;	data pulse but we need another bit to decide the next interval
;		----->
;	Data	1   0|
;	Clock	  0  |
;	MFM	1 0 0|
;
writestate10:
	dec	count
	brpl	writel0030
	sbiw	xh:xl, 1
	breq	writes0000
	ldi	count, 7
	ld	char, Y+
writel0030:
	lsl	char
	brcc	writel0040
;
;	if the next bit is 1 then we need to create a data pulse,
;	or in other words a long interval 
;
;		----->
;	Data	1   0|  1
;	Clock	  0  |0 
;	MFM	1 0 0|0 1
;
	nxtint	ilong
	rjmp	writestatex1
;
;	if the next bit is a 0 then we need to create a clock pulse,
;	or in other words a medium intervals
;		----->
;	Data	1   0|  0
;	Clock	  0  |1 
;	MFM	1 0 0|1 0
;
writel0040:
	nxtint	imedium
	rjmp	writestate00
;
;
;
writes0000:
	out	TCCR0A, zero	
	cbi	PORTB, DBG1
	sbi	PORTC, WG

It is important to finish the decision about the next interval before the current interval finished. That means we should start polling OCF0A in TIFR0 before the flag has been set and a new interval started. As the current interval might be a short interval we have a total of 32 cycles. The worst case is when we enter writestatex1 without a remaining bit in the current byte and the first bit of the next byte is 0. This requires up to 25 cycles before we know the next interval and start polling OCF0A in TIFR0.