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 then 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 run on the MCU clock of 16MHz we use. At the same time we need to reset the OCF0A flag and set 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. And we define 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 set OCR0A before the current interval value has been reached. Therefore we only are allowed a total of 32 cycles for the complete processing of a state as we always must assume that the current interval is short. 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 TCNT0 is programmed with the next interval, which is well within the limit of 32 cycles.