Counting Toward Zero
Hand the repetition to the machine. Load a count into B, and DJNZ runs the body that many times — one loop fills a whole row where last unit you stepped by hand.
Last unit you walked the finger by hand — write, step, write, step — and you could feel the repetition piling up. Three cells was fine; thirty-two would be absurd. So you don't write it thirty-two times. You tell the machine to.
That's a loop, and on the Z80 the everyday one is DJNZ:
ld b, 32—Bis the counter. Load it with how many times you want.djnz .fill— Decrement B, Jump if Not Zero. In one instruction it subtracts 1 fromBand, ifBisn't yet zero, jumps back to the label.
Put a body between the count and the djnz, and that body runs exactly B times. It's BASIC's FOR i = 1 TO 32 … NEXT, assembled from a counter and a jump you already know.
What you'll see by the end
A red bar clear across the top — the whole top row of 32 colour cells, painted by a loop that ran the same two-line body thirty-two times. You wrote the body once.
The loop
; ============================================================================
; PRIMER — Beat 10: Counting Toward Zero
; ============================================================================
; In Beat 9 you stepped the finger by hand: write, inc, write, inc... A loop
; hands that repetition to the machine.
;
; ld b, 32 -- B is the dedicated COUNTER. Load it with how many times.
; .fill: ... -- the body: do the thing once.
; djnz .fill -- DJNZ = "Decrement B, Jump if Not Zero". One instruction
; subtracts 1 from B and loops back until B reaches 0.
;
; So the body runs exactly B times. Here it colours one cell and steps the
; finger -- 32 times -- which paints the whole top row of the screen red.
; That's BASIC's `FOR i = 1 TO 32 ... NEXT`, built from a counter and a jump.
;
; The pointer walk (ld hl / ld (hl) / inc hl) is exactly Beat 9; the only new
; thing is letting the machine do the counting.
; ============================================================================
org 32768
start:
ld hl, $5800 ; finger on the first colour cell
ld b, 32 ; count: 32 cells = one full row
.fill:
ld (hl), $17 ; colour the cell (PAPER red, INK white)
inc hl ; step to the next
djnz .fill ; B = B - 1; not zero? back to .fill
.loop:
halt
jr .loop
end start
The inside of the loop is exactly Beat 9 — colour the cell, step the finger. The only new thing is the wrapper: ld b, 32 sets the count, and djnz .fill does the counting and the jumping in one go. The finger walks all 32 cells; the bar appears.
Why count toward zero? Because the machine already knows, for free, when a result hits zero — that's the zero flag from Beat 7. DJNZ leans on it: it counts down and stops at zero, instead of counting up and comparing against a limit each time. FOR counts up for human comfort; the CPU counts down for its own.
B is the counter DJNZ is wired to — that's its job in this idiom. (It's why we've kept B free in the loops you've seen.)
Assemble and run
pasmonext --sna counting-toward-zero.asm primer.sna
One loop, a full red row.
Try this: change the count
Set ld b, 16 for half a row, ld b, 10 for ten cells. ld b, N is your FOR i = 1 TO N — the count is the loop's whole story. (Avoid ld b, 0: DJNZ decrements first, so a count of 0 wraps to 256 and runs far longer than you meant — a classic first-loop surprise.)
Try this: the dashed line, properly
Remember Beat 5's dashed line — eight near-identical pokes? Here it is as a loop, all 32 cells across — the same djnz body, pointed at the pixel map and writing the dotted byte:
| 1 | 1 | ; ============================================================================ | |
| 2 | - | ; PRIMER — Beat 10: Counting Toward Zero | |
| 2 | + | ; PRIMER — Beat 10, "Try this: the dashed line, properly" | |
| 3 | 3 | ; ============================================================================ | |
| 4 | - | ; In Beat 9 you stepped the finger by hand: write, inc, write, inc... A loop | |
| 5 | - | ; hands that repetition to the machine. | |
| 6 | - | ; | |
| 7 | - | ; ld b, 32 -- B is the dedicated COUNTER. Load it with how many times. | |
| 8 | - | ; .fill: ... -- the body: do the thing once. | |
| 9 | - | ; djnz .fill -- DJNZ = "Decrement B, Jump if Not Zero". One instruction | |
| 10 | - | ; subtracts 1 from B and loops back until B reaches 0. | |
| 11 | - | ; | |
| 12 | - | ; So the body runs exactly B times. Here it colours one cell and steps the | |
| 13 | - | ; finger -- 32 times -- which paints the whole top row of the screen red. | |
| 14 | - | ; That's BASIC's `FOR i = 1 TO 32 ... NEXT`, built from a counter and a jump. | |
| 15 | - | ; | |
| 16 | - | ; The pointer walk (ld hl / ld (hl) / inc hl) is exactly Beat 9; the only new | |
| 17 | - | ; thing is letting the machine do the counting. | |
| 4 | + | ; Beat 5's dashed line took eight near-identical pokes. Here it is as a loop: | |
| 5 | + | ; point at the screen, write the dotted byte, step, repeat 32 times -- a full | |
| 6 | + | ; dashed line across the very top, and not a line of source repeated. | |
| 18 | 7 | ; ============================================================================ | |
| 19 | 8 | | |
| ... | |||
| 21 | 10 | | |
| 22 | 11 | start: | |
| 23 | - | ld hl, $5800 ; finger on the first colour cell | |
| 24 | - | ld b, 32 ; count: 32 cells = one full row | |
| 12 | + | ld hl, $4000 ; finger on the top-left pixel cell | |
| 13 | + | ld b, 32 ; 32 cells across the top | |
| 25 | 14 | | |
| 26 | 15 | .fill: | |
| 27 | - | ld (hl), $17 ; colour the cell (PAPER red, INK white) | |
| 28 | - | inc hl ; step to the next | |
| 29 | - | djnz .fill ; B = B - 1; not zero? back to .fill | |
| 16 | + | ld (hl), %10101010 ; the dotted byte from Beat 5 | |
| 17 | + | inc hl | |
| 18 | + | djnz .fill | |
| 30 | 19 | | |
| 31 | 20 | .loop: |
Point at the screen instead of the colour map, write the dotted byte, step, repeat. The repetition you typed out by hand in Beat 5 is now four lines that do the lot.
When it's wrong, see why
- Only one cell is coloured. The
inc hlis missing or sits outside the loop, so every pass writes the same box. Both the write and the step belong inside, beforedjnz. - The loop runs far too long / the screen fills up. You loaded
ld b, 0.DJNZdecrements first, so 0 becomes 256. Use the count you mean. pasmonextcan't find.fill. The label and thedjnz .fillmust match exactly, case and all — labels are the assembler's rules, from Beat 7.
What you've learnt
ld b, N then djnz label runs the loop body exactly N times — a counter and a jump that count toward zero — so one loop does what would be N lines by hand.
What's next
Your programs are growing, and chunks of them want names. Next — Call, Return, and a Stack You Can See — we package a job into a subroutine you can call from anywhere and ret from, and watch where the machine remembers its way back: the stack, in plain memory.