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
.