
;; Thermomter - Copyright 2010 by Michael Kohn
;; Email: mike to mikekohn.net
;;   Web: http://www.mikekohn.net/
;;
;; Attiny2313 reading a DS18S20 temperature sensor with a 1 wire interface

;.include "tn2313def.inc"
.device ATtiny2313

; r0  = 0
; r1  = 1
; r2  = lower counter for waiting
; r3  = low counter for waiting
; r4  = high counter for waiting
; r5  = Read Bit value (0 or 1) / Write 255 and shift to know if it's done
; r7  = bit marker for writing .. so we know when 8 bits of the command are out
; r6  = 30
; r8  = SREG in interrupt
; r9  = trigger a start temp
; r10 = conversion state: 0 = idle
;                         1 = send_reset_pulse
;                         2 = reset pulse done,
;                         3 = send skip rom command (0xcc);
;                         4 = wait skip rom command done
;                         5 = send read command (0x44);
;                         6 = wait read command done
;                         7 = wait on convert done
;                         8 = convert is done
;                         9 = send_reset_pulse
;                        10 = reset pulse done,
;                        11 = send skip rom command (0xcc);
;                        12 = wait skip rom command done
;                        13 = send read scratchpad (0xbe)
;                        14 = wait read scratchpad done
;                        15 = read scratchpad
;                        16 = all done
; r13 = Temp for writing hex
; r14 = Temp for reading rs232
; r15 = 255
; r16 = READ/WRITE low count
; r17 = Temp in main
; r18 = Temp for writing hex
; r19 = Temp for writing hex
; r20 = Temp in main
; r22 = Interrupt count
; r23 = command to send / byte read in
; r24 = response bytes
; r25 = bytes to read from scratchpad
;

; note: CLKSEL 1111
;  cycles  sample rate   @20 MHz
;  100 cycles per interrupt = 5us per interrupt
;
; 480us reset pulse = 96 interrupts  (Sent by master)
; 60us to 240us presence pulse = 12 to 48 interrupts (Sent by DS18S20)
; 60us for a bit = 12 interrupts
;
; WRITE TIME SLOT 1: 5us low, 55us high, 1us recovery
; WRITE TIME SLOT 0: 60us low, 1us recovery
;
; READ TIME SLOT 1: 1us low, read 60us (bus is released), 1us recovery
; READ TIME SLOT 0: 1us low, read 60us (bus is held down), 1us recovery
;  1us @ 20 MHz is 20 cycles

.equ RESET_PULSE = 96
.equ PRESENCE_PULSE = 48
.equ PULSE60 = 12

.macro ONE_WIRE_HIGH
  sbi PORTD, PD5
.endm

.macro ONE_WIRE_LOW
  cbi PORTD, PD5
.endm

.macro ONE_WIRE_OUTPUT
  sbi PORTD, PD5
  sbi DDRD, PD5
.endm

.macro ONE_WIRE_INPUT
  cbi DDRD, PD5
  cbi PORTD, PD5
.endm

.macro SKIP_ONE_WIRE_HIGH
  sbis PIND, PD5
.endm

.macro SKIP_ONE_WIRE_LOW
  sbic PIND, PD5
.endm

.macro PAUSE
  ldi r17, 2
  mov r12, r17
count_r12:
  clr r13
count_r13:
  clr r14
count_r14:
  dec r14
  brne count_r14
  dec r13
  brne count_r13
  dec r12
  brne count_r12
.endm

.cseg

.org 0x000
  rjmp start
.org 0x008
  ijmp            ; might as well ijmp, IJMP, go ahead and ijmp!

start:
  ;; I'm busy.  Don't interrupt me!
  cli

  ;; Set up stack ptr
  ;ldi r17, RAMEND>>8
  ;out SPH, r17
  ldi r17, RAMEND&255
  out SPL, r17

  ;; r0 = 0, r1 = 1, r15 = 255
  eor r0, r0
  eor r1, r1
  inc r1
  eor r15, r15
  dec r15
  ldi r17, 30
  mov r6, r17

  clr r9                              ; don't force trigger
  clr r10                             ; state = not going
  clr r3
  clr r4
  mov r2, r1

  ;; Set up rs232 baud rate
  ldi r17, ((20000000/(16*1200))-1) >> 8
  out UBRRH, r17
  ldi r17, ((20000000/(16*1200))-1) & 0xff
  out UBRRL, r17        ; 1200 baud

  ;; Set up rs232 options
  ldi r17, (1<<UCSZ0)|(1<<UCSZ1)      ; sets up data as 8N1
  out UCSRC, r17
  ldi r17, (1<<TXEN)|(1<<RXEN)        ; enables send/receive
  out UCSRB, r17
  out UCSRA, r0

  ;; Set up PORTB
  out DDRB, r15             ; PB0 is output for now
  out PORTB, r0             ; Turn on PB0 so it shows up as idle

  ;; Set up PORTD
  ldi r17, (1<<PD5)|(1<<PD4)
  out DDRD, r17
  sbi PORTD, PD4
  sbi PORTD, PD5

  ;; Set up TIMER1
  ldi r17, (99>>8)
  out OCR1AH, r17
  ldi r17, (99&0xff)            ; compare to 100 clocks
  out OCR1AL, r17

  ldi r17, (1<<OCIE1A)
  out TIMSK, r17                 ; enable interrupt comare A 
  out TCCR1C, r0
  out TCCR1A, r0                 ; normal counting (0xffff is top, count up)
  ldi r17, (1<<CS10)|(1<<WGM12)  ; CTC OCR1A  Clear Timer on Compare
  out TCCR1B, r17                ; prescale = 1 from clock source

  ;; set ijmp routine
  ;ldi r31, ingenting>>8
  ;ldi r30, ingenting&0xff
  ldi r31, pause_before_get_temp>>8
  ldi r30, pause_before_get_temp&0xff

  ; Fine, I can be interrupted now
  sei

test_mode:
  ldi r18, -99
test_mode_to_0:
  ser r17
  mov r19, r18
  rcall show_bcd
  PAUSE
  inc r18
  brne test_mode_to_0
test_mode_to_99:
  clr r17
  mov r19, r18
  rcall show_bcd
  PAUSE
  inc r18
  cpi r18, 100
  brne test_mode_to_99

main:
  mov r20, r10

  cpi r20, 2
  brne not_state_2
  ldi r23, 0xcc         ; Skip Rom
  mov r7, r1
  ONE_WIRE_OUTPUT
  ldi r29, send_char_1_wire>>8
  ldi r28, send_char_1_wire&0xff
  movw r30, r28
  inc r10               ; state = 3
  rjmp main

not_state_2:
  cpi r20, 4
  brne not_state_4
  ldi r23, 0x44         ; Convert T
  mov r7, r1
  ONE_WIRE_OUTPUT
  ldi r29, send_char_1_wire>>8
  ldi r28, send_char_1_wire&0xff
  movw r30, r28
  inc r10               ; state = 5
  rjmp main

not_state_4:
  cpi r20, 6
  brne not_state_6
  ;;; DEBUG ;;;;;;;;;
  ;ldi r19, '>'    ;;;
  ;rcall send_char ;;;
  ;mov r19, r23    ;;;
  ;rcall send_hex  ;;;
  ;;; DEBUG ;;;;;;;;;
  clr r5
  ldi r29, wait_for_convert_read_done>>8
  ldi r28, wait_for_convert_read_done&0xff
  movw r30, r28
  inc r10               ; state = 7 (wait for convert done)
  rjmp main

not_state_6:
  cpi r20, 8
  brne not_state_8
  ;;; DEBUG ;;;;;;;;;
  ;ldi r19, 'R'    ;;;
  ;rcall send_char ;;;
  ;;; DEBUG ;;;;;;;;;
  ldi r29, send_reset_pulse>>8
  ldi r28, send_reset_pulse&0xff
  movw r30, r28
  inc r10               ; state = 9 (wait for reset pulse)
  rjmp main

not_state_8:
  cpi r20, 10
  brne not_state_10
  ldi r23, 0xcc         ; Skip Rom
  mov r7, r1
  ONE_WIRE_OUTPUT
  ldi r29, send_char_1_wire>>8
  ldi r28, send_char_1_wire&0xff
  movw r30, r28
  inc r10               ; state = 11
  rjmp main

not_state_10:
  cpi r20, 12
  brne not_state_12
  ;;; DEBUG ;;;;;;;;;
  ;ldi r19, '#'    ;;;
  ;rcall send_char ;;;
  ;mov r19, r23    ;;;
  ;rcall send_hex  ;;;
  ;;; DEBUG ;;;;;;;;;
  ldi r23, 0xbe         ; Read scratch pad
  mov r7, r1
  ONE_WIRE_OUTPUT
  ldi r29, send_char_1_wire>>8
  ldi r28, send_char_1_wire&0xff
  movw r30, r28
  inc r10               ; state = 13 (send read scratchpad)
  rjmp main

not_state_12:
  cpi r20, 14
  brne not_state_14
  ldi r27, SRAM_START>>8
  ldi r26, SRAM_START&0xff
  clr r7
  ldi r25, 9
  ldi r29, read_scratchpad>>8
  ldi r28, read_scratchpad&0xff
  movw r30, r28
  inc r10               ; state = 15 (read scratchpad)
  rjmp main

not_state_14:
  cpi r20, 16
  brne not_state_16

  ldi r29, pause_before_get_temp>>8
  ldi r28, pause_before_get_temp&0xff
  movw r30, r28

  ;ldi r19, '|'
  ;rcall send_char

  ldi r27, SRAM_START>>8
  ldi r26, SRAM_START&0xff
  ldi r25, 10 

dump_mem:
  ld r19, X+
  ;rcall send_hex
  rcall send_char
  dec r25
  brne dump_mem

  lds r17, SRAM_START+2
  lds r19, SRAM_START+1
  clc
  ror r17
  ror r19
  rcall show_bcd

  clr r10               ; state = 0 (idle)
  rjmp main
not_state_16:
  tst r9
  brne start_temp

  in r20, UCSRA         ; poll uart to see if there is a data waiting
  sbrs r20, RXC
  rjmp main             ; if no data, loop around

  in r19, UDR           ; read from serial

  tst r10               ; if (state != 0 || c != 't') { echo and ignore }
  brne no_start_temp
  cpi r19, 't'
  brne no_start_temp

start_temp:
  clr r9
  rcall memset

  mov r10, r1          ; state = 1
  ;; Atomic BOOM!
  ldi r29, send_reset_pulse>>8
  ldi r28, send_reset_pulse&0xff
  movw r30, r28
  rjmp main
no_start_temp:
  ;rcall send_char       ; echo for debug
  rjmp main

;; show_bcd(r17, r19) -  shows r19 in the bcd display.  r17 is negative flags 
show_bcd:
  tst r17
  breq not_neg
  cbi PORTD, PD4
  neg r19
  rjmp do_bcd
not_neg:
  sbi PORTD, PD4
do_bcd:
  clr r17
slow_loser_div:
  cpi r19, 10
  brlo done_with_div
  inc r17
  subi r19, 10
  rjmp slow_loser_div
done_with_div:
  swap r17
  or r19, r17
  out PORTB, r19
  ret

ingenting:
  reti

memset:
  ldi r27, SRAM_START>>8
  ldi r26, SRAM_START&0xff
  ldi r19, 10
memset_again:
  st X+, r0
  dec r19
  brne memset_again
  ret

;; Set reset pulse pin low
send_reset_pulse:
  in r8, SREG
  ONE_WIRE_OUTPUT
  ONE_WIRE_LOW
  ldi r31, reset_state>>8
  ldi r30, reset_state&0xff
  ldi r22, RESET_PULSE
  out SREG, r8
  reti

;; Keep reset pulse low for 480us
reset_state:
  in r8, SREG
  dec r22
  brne reset_state_continue
  ldi r31, wait_presence_pulse>>8
  ldi r30, wait_presence_pulse&0xff
  ldi r22, PRESENCE_PULSE
  ONE_WIRE_INPUT                   ; 1-wire is now input
reset_state_continue:
  out SREG, r8
  reti

;; Wait for line to go low by the DS18S20, so we know the chip answers
wait_presence_pulse:
  in r8, SREG
  SKIP_ONE_WIRE_LOW
  rjmp exit_wait_presence_pulse
  ldi r31, presence_pulse_start>>8
  ldi r30, presence_pulse_start&0xff
exit_wait_presence_pulse:
  out SREG, r8
  reti

;; Wait for presence pulse to end
presence_pulse_start:
  in r8, SREG
  SKIP_ONE_WIRE_LOW
  rjmp present_now
  dec r22
  breq cant_find_presence
  out SREG, r8
  reti
cant_find_presence:
  ldi r31, ingenting>>8
  ldi r30, ingenting&0xff
  clr r10
  out SREG, r8
  reti
present_now:
  ldi r31, ingenting>>8
  ldi r30, ingenting&0xff
  ONE_WIRE_OUTPUT            ; 1 wire idle
  mov r23, r22               ; FIXME - DEBUG
  ;rcall send_hex
  inc r10                    ; state = 2
  out SREG, r8
  reti

; void send_char(r19)  : r14 trashed
send_char:
  in r14, UCSRA       ; check to see if it's okay to send a char
  sbrs r14, UDRE
  rjmp send_char      ; if it's not okay, loop around :)
  out UDR, r19        ; output a char over rs232
  ret

; void send_nibble(r19)  : r14, r18 trashed
send_nibble:
  cpi r19, 10
  brlo hex_nibble_under_10
  subi r19, 10
  ldi r18, 'A'
  add r19, r18
  rcall send_char
  ret
hex_nibble_under_10:
  ldi r18, '0'
  add r19, r18
  rcall send_char
  ret

; void send_hex(r19)  : r13, r14, r18 trashed
send_hex:
  mov r13, r19
  swap r19
  andi r19, 0x0f
  rcall send_nibble
  mov r19, r13
  andi r19, 0x0f
  rcall send_nibble
  ldi r19, ' '
  rcall send_char
  ret

send_char_1_wire:
  in r8, SREG
  tst r7                     ; if (r7 == 0) { done with this byte }
  brne bang_next_bit
  ldi r31, ingenting>>8
  ldi r30, ingenting&0xff
  inc r10                    ; state = 4, 8
  out SREG, r8
  reti
bang_next_bit:
  ldi r22, PULSE60
  lsl r7
  ONE_WIRE_LOW
  ;ldi r29, send_char_1_wire>>8
  ;ldi r28, send_char_1_wire&0xff
  sbrc r23, 0
  rjmp command_bit_set
  ;;;;;;;;;
  ;ldi r19, '0'
  ;rcall send_char
  ;;;;;;;;;
  ldi r31, write_zero>>8
  ldi r30, write_zero&0xff
  lsr r23
  out SREG, r8
  reti
command_bit_set:
  ;;;;;;;;;
  ;ldi r19, '1'
  ;rcall send_char
  ;;;;;;;;;
  ldi r31, write_one>>8
  ldi r30, write_one&0xff
  lsr r23
  out SREG, r8
  reti

;; Wait for a READ ZERO from the chip so we know the convert is done 
wait_for_convert:
  ONE_WIRE_OUTPUT
  ONE_WIRE_LOW
  ldi r29, wait_for_convert_read_done>>8
  ldi r28, wait_for_convert_read_done&0xff
  ldi r31, read_start>>8
  ldi r30, read_start&0xff
  out SREG, r8
  reti

wait_for_convert_read_done:
  in r8, SREG
  ONE_WIRE_OUTPUT
  tst r5
  breq wait_for_convert
  ldi r31, ingenting>>8
  ldi r30, ingenting&0xff
  inc r10                    ; state = 6
  out SREG, r8
  reti

read_scratchpad:
  in r8, SREG
  ONE_WIRE_OUTPUT
  ;;;;;;;;;;;
  ;mov r19, r5
  ;rcall send_hex
  ;;;;;;;;;;;
  lsr r23
  sbrc r5, 0
  sbr r23, 128
  ;sbrs r5, 0
  ;cbr r23, 7
  tst r7
  brne read_next_bit
  ;;;;;;;;;;;
  ;mov r19, r23
  ;rcall send_hex
  ;;;;;;;;;;;
  st X+, r23
  mov r7, r1
  tst r25
  breq read_scratchpad_done
  dec r25
read_next_bit:
  ONE_WIRE_LOW
  lsl r7
  ;clr r16
  ldi r29, read_scratchpad>>8
  ldi r28, read_scratchpad&0xff
  ldi r31, read_start>>8
  ldi r30, read_start&0xff
  out SREG, r8
  reti

read_scratchpad_done:
  ONE_WIRE_OUTPUT
  ldi r31, ingenting>>8
  ldi r30, ingenting&0xff
  inc r10                    ; state = 10
  out SREG, r8
  reti

write_one:
  in r8, SREG
  ONE_WIRE_HIGH
  dec r22
  brne write_one_exit_int
  ;movw r30, r28
  ldi r31, send_char_1_wire>>8
  ldi r30, send_char_1_wire&0xff
write_one_exit_int:
  out SREG, r8
  reti

write_zero:
  in r8, SREG
  dec r22
  brne write_zero_exit_int
  ONE_WIRE_HIGH
  ;movw r30, r28
  ldi r31, send_char_1_wire>>8
  ldi r30, send_char_1_wire&0xff
write_zero_exit_int:
  out SREG, r8
  reti

read_start:
  in r8, SREG
  ONE_WIRE_INPUT
  clr r16
  ldi r31, read_bit>>8
  ldi r30, read_bit&0xff
  ldi r22, PULSE60
  out SREG, r8
  reti

read_bit:
  in r8, SREG
  SKIP_ONE_WIRE_HIGH    ; sbis PIN, BIT
  inc r16               ; inc r16 if one wire is low
  dec r22
  brne read_bit_exit
  ;cpi r16, (PULSE60-4)
  ;brlo read_a_zero
  ONE_WIRE_OUTPUT
  ;mov r19, r16
  ;rcall send_hex
  tst r16
  brne read_a_zero
  mov r5, r1
  movw r30, r28
  out SREG, r8
  reti
read_a_zero:
  clr r5
  movw r30, r28
read_bit_exit:
  out SREG, r8
  reti

pause_before_get_temp:
  in r8, SREG
  inc r3
  brne leave_pause
  inc r4
  brne leave_pause

  dec r2
  brne leave_pause
  mov r2, r6
  mov r9, r1

leave_pause:
  out SREG, r8
  reti

signature:
.db "Thermometer - Copyright 2010 - Michael Kohn - Version 0.02",0,0





