'' ============================================================
'' MAX7219_16.spin
'' Driver for two daisy-chained MAX7219s (16 digits)
'' No-decode mode only – raw segment control, DP = bit 7
'' For Parallax Propeller 1 (Spin language)
''
'' Methods:
''   Start(data_pin, clk_pin, cs_pin, bright1, bright2)
''   Digit(digit_num, segs)       ' digit_num 1..16
''   DPLED(digit_num, on)         ' set/clear decimal point
''   Clear                        ' blank all digits
''   Brightness(level)            ' both devices same (0-15)
''   Brightness1(level)           ' device 1 only (digits 1-8)
''   Brightness2(level)           ' device 2 only (digits 9-16)
''   DisplayOn / DisplayOff
''   ShowNum(pos, value, width)   ' right-aligned decimal
'' ============================================================

CON
  REG_NOOP     = $00
  REG_DIG0     = $01
  REG_DIG1     = $02
  REG_DIG2     = $03
  REG_DIG3     = $04
  REG_DIG4     = $05
  REG_DIG5     = $06
  REG_DIG6     = $07
  REG_DIG7     = $08
  REG_DECODE   = $09
  REG_INTENSITY= $0A
  REG_SCANLIMIT= $0B
  REG_SHUTDOWN = $0C
  REG_DISPTEST = $0F

  NO_DECODE    = $00
  SCAN_8DIG    = $07
  SHUTDOWN_OFF = $00
  SHUTDOWN_ON  = $01
  DISPTEST_OFF = $00

  SEG_0 = %01111110
  SEG_1 = %00110000
  SEG_2 = %01101101
  SEG_3 = %01111001
  SEG_4 = %00110011
  SEG_5 = %01011011
  SEG_6 = %01011111
  SEG_7 = %01110000
  SEG_8 = %01111111
  SEG_9 = %01111011
  SEG_BLANK = 0

VAR
  byte data_pin, clk_pin, load_pin
  byte shadow[16]

PUB Start(dout, clk, cs, bright1, bright2)
  data_pin := dout
  clk_pin  := clk
  load_pin := cs

  dira[data_pin] := 1
  dira[clk_pin]  := 1
  dira[load_pin] := 1
  outa[data_pin] := 0
  outa[clk_pin]  := 0
  outa[load_pin] := 0

  bytefill(@shadow, 0, 16)

  WriteBoth(REG_DECODE,   NO_DECODE)
  WriteBoth(REG_SCANLIMIT, SCAN_8DIG)
  WriteBoth(REG_DISPTEST,  DISPTEST_OFF)
  WriteBoth(REG_SHUTDOWN,  SHUTDOWN_OFF)

  WriteToDevice(1, REG_INTENSITY, (0 #> bright1 <# 15))
  WriteToDevice(2, REG_INTENSITY, (0 #> bright2 <# 15))

  WriteBoth(REG_SHUTDOWN,  SHUTDOWN_ON)

  Clear

PUB Digit(digit_num, segs)
  digit_num := (1 #> digit_num) <# 16
  UpdateDigit(digit_num, segs)

PUB DPLED(digit_num, on) | seg_val
  digit_num := (1 #> digit_num) <# 16
  seg_val := shadow[digit_num-1]
  if on
    seg_val := seg_val | $80
  else
    seg_val := seg_val & !$80
  UpdateDigit(digit_num, seg_val)

PUB Clear | i
  repeat i from 1 to 16
    UpdateDigit(i, 0)

PUB Brightness(level)
  level := (0 #> level) <# 15
  WriteToDevice(1, REG_INTENSITY, level)
  WriteToDevice(2, REG_INTENSITY, level)

PUB Brightness1(level)
  level := (0 #> level) <# 15
  WriteToDevice(1, REG_INTENSITY, level)

PUB Brightness2(level)
  level := (0 #> level) <# 15
  WriteToDevice(2, REG_INTENSITY, level)

PUB DisplayOn
  WriteBoth(REG_SHUTDOWN, SHUTDOWN_ON)

PUB DisplayOff
  WriteBoth(REG_SHUTDOWN, SHUTDOWN_OFF)

PUB ShowNum(pos, value, width) | i, d, seg_val
  pos   := 1 #> pos <# 16
  width := 1 #> width <# (17 - pos)
  if width < 1
    return

  repeat i from 0 to width-1
    d := pos + i
    if i == 0
      seg_val := NibbleToSeg(value // 10)
      value := value / 10
    else
      if value == 0
        seg_val := SEG_BLANK
      else
        seg_val := NibbleToSeg(value // 10)
        value := value / 10
    ' Explicitly clear DP bit - DPLED will set it later
    seg_val := seg_val | (shadow[d-1] & $80)
    UpdateDigit(d, seg_val)

PRI UpdateDigit(digit_num, segs)
  shadow[digit_num-1] := segs
  if digit_num =< 8
    WriteToDevice(1, digit_num, segs)
  else
    WriteToDevice(2, digit_num - 8, segs)

PRI WriteToDevice(dev, reg, data)
  outa[load_pin] := 0
  if dev == 1
    Send16(REG_NOOP, 0)
    Send16(reg, data)
  else
    Send16(reg, data)
    Send16(REG_NOOP, 0)
  outa[load_pin] := 1

PRI WriteBoth(reg, data)
  outa[load_pin] := 0
  Send16(reg, data)
  Send16(reg, data)
  outa[load_pin] := 1

PRI Send16(reg, data) | i
  repeat i from 7 to 0
    outa[data_pin] := (reg >> i) & 1
    outa[clk_pin]  := 1
    outa[clk_pin]  := 0
  repeat i from 7 to 0
    outa[data_pin] := (data >> i) & 1
    outa[clk_pin]  := 1
    outa[clk_pin]  := 0

PRI NibbleToSeg(n) : seg
  case n
    0: seg := SEG_0
    1: seg := SEG_1
    2: seg := SEG_2
    3: seg := SEG_3
    4: seg := SEG_4
    5: seg := SEG_5
    6: seg := SEG_6
    7: seg := SEG_7
    8: seg := SEG_8
    9: seg := SEG_9
    other: seg := SEG_BLANK