Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ; Targeted for the ATtiny25
- ; NOTE: Since this was developed specifically for communicating with the PCF8574 8-bit I/O expander
- ; on a 16x2 LCD display, it only supports I2C Write as Master and sends the address for each byte sent.
- ; There is no salve mode, read or multi-byte support
- ;Pins used
- ;---------------------------------
- ;
- ; ADC3 Physical pin 2 (Thermistor 1)
- ; ADC2 Physical pin 3 (Thermistor 2)
- ;
- ; PortB, 2 Physical pin 7 (Start/Reset button)
- ;
- ; SCL (Clock) SCK for the Universal Serial Interface (PORTB, 1) (Physical pin 6)
- ; SDA (Data) SDA for the Universal Serial Interface (PORTB, 0) (Physical pin 5)
- ;
- ;Registers used
- ;---------------------------------
- ;
- ; R14 I2C Address for SI7021 Humidity/Temperature sensor unit
- ; R15 I2C Address for LCD display (7-bit addresses only!)
- ; R17 I2C Data byte, bin2dec temp byte
- ; R18 Temporary I2C byte, bin2dec input byte
- ; R19 I2C bitbang counter, bin2dec temp counter
- ; R20 I2C ACK test data, bin2dec temp byte
- ; R21 LCD Data byte
- ; R22 Temp LCD data byte
- ; R23
- ; R24,R25 Delay loop counter (as 16-bit word)
- ; R26,R27 Pointer X to SRAM for display data (0x0060 to start)
- ; R28,R29 Pointer Y to SRAM for display data copy
- ; Data Map
- ;---------------------------------
- ;
- ;
- ;This file defines aliases for all of the various registers and flags
- .include "tn25def.inc"
- RJMP start ; Skip data segment. This should put the data segment at address 0x02
- ; Display buffer. "---" is replaced with actual value based on bin2dec output before sending
- ; the buffer to the LCD via lcd_refresh. use lcd_newbuff to copy this to SRAM first!
- ; D B - - - ° F _ _ W B - - - ° F
- .db 0x44, 0x42, 0x2D, 0x2D, 0x2D, 0xDF, 0x46, 0x20, 0x20, 0x57, 0x42, 0x2D, 0x2D, 0x2D, 0xDF, 0x46
- ; D P - - - ° F _ _ R H - - - % NULL
- .db 0x44, 0x50, 0x2D, 0x2D, 0x2D, 0xDF, 0x46, 0x20, 0x20, 0x52, 0x48, 0x2D, 0x2D, 0x2D, 0x25, 0x00
- ; Thermistor calibration tables (Data TBD)
- ; 23°F 32°F 41°F 50°F 59°F 68°F 77°F 86°F 95°F 104°F 113°F 122°F
- ;.db 0x17, 0x20, 0x29, 0x32, 0x3B, 0x44, 0x4D, 0x56, 0x5F, 0x68, 0x71, 0x7A ; Temp scale (Example data only!)
- ;.db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Thermistor 1
- ;.db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Thermistor 2
- ;.db "Initializing...", 0x00 ; String at 0x46
- ;.db 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20
- ;.db 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00
- start:
- ; General Init
- ;WDR ; Watchdog reset
- ; Clock init - this plus the fuse setting (lfuse = 0x61) should set the internal clock to ~16MHz)
- LDI R30, 0x02
- OUT PLLCSR, R30
- ; I/O pin init. the pins used for I2C on PORTB should always be ZERO
- ; DDRB = 1 means the line is LOW (Sinking current, driving line low)
- ; DDRB = 0 means the line is HIGH (High Z, external pullup driving line high)
- LDI R30, (0<<PB1)|(0<<PB3) ; Set SDA and SCL High (Default state)
- OUT PORTB, R30
- LDI R30, (0<<PB1)|(0<<PB3)
- OUT DDRB, R30 ; Enables internal pullup on pin 5 (PORTB, 1) (SDA) and pin 7 (PORTB, 3) (SCL)
- LDI R16, 0x3F ; 0x3F is the I2C address of the LCD
- MOV R15, R16 ; Stash the address in R15
- LDI R16, 0x40 ; 0x40 is the I2C address of the SI7021 sensor
- MOV R14, R16 ; Stash the address in R14
- RCALL long_delay ; Per the 1620 LCD controller datasheet, we need to wait at least 30ms before talking to it...
- RCALL long_delay ;
- RCALL lcd_init ; LCD is initialized!
- ; Ready to begin out actual program! Anything below is just test stuff...
- ;***************************************************************************************
- ;***************************************************************************************
- ;***************************************************************************************
- ; LDI R26, LOW(7150)
- ; MOV R2, R26
- ; LDI R26, HIGH(7150)
- ; MOV R3, R26
- ;
- ; LDI R26, 0x62 ; Buffer line 1 starts at 0x0060
- ; LDI R27, 0x00 ;
- ; RCALL bin2dec16 ; bin2dec16 is written to use R3:R2 as the input
- ; RJMP trap
- main:
- RCALL lcd_newbuff
- ; Read humidity...
- ; Read temperature...
- MOV R16, R14 ; I2C address for sensor
- LDI R17, 0xF5 ; Command: Read humidity, no master hold
- RCALL i2c_senddata ; Send command to read temperature
- ; R16 will change from the value in R14 to 0 when data is received
- h_convert_wait: ; Attempt to read the data.
- RCALL long_delay
- RCALL i2c_getdata
- CP R14, R16
- BREQ h_convert_wait
- ; From the 16-bit value in R5:R4 we calculate the relative humidity in percent by:
- ; RH% = ((125 * RH_Code) / 65536) - 6
- ;
- ; To preserve decimal places we multiply by 100:
- ; RH% * 100 = ((12500 * RH_Code) / 65536) - 600
- ;
- ; Then, when we convert this value to ASCII for display, we effectively divide by 100
- ; by dropping the last two digits and rounding
- ; Step 1: Multiply sensor data by 12500 (0x30D4)
- ; R4, R5, R6, R7 16-bit multiplicand input (32-bit extended) - Value is already loaded from the i2c_getdata routine
- ; R24, R25 16-bit multiplier
- LDI R24, 0xD4
- LDI R25, 0x30
- RCALL multiply16 ; Result is in R3:R2:R1:R0. We divide by 65536 by simply discarding the low 16-bits (R1:R0).
- ; So the final result is in R3:R2
- LDI R16, 0x58
- LDI R17, 0x02
- SUB R2, R16 ; Subtract 600 (0x0258)
- SBC R3, R17 ; RH * 100 is now in R3:R2. Convert this to ASCII and put it into the memory buffer for the display!
- LDI R26, 0x7B ; "RH" in buffer buffer line 1 starts at 0x0078
- LDI R27, 0x00 ;
- RCALL bin2dec16 ; bin2dec16 is written to use R3:R2 as the input
- ; Read temperature...
- MOV R16, R14 ; I2C address for sensor
- LDI R17, 0xE0 ; Command: Read temp from previous RH measurement
- RCALL i2c_senddata ; Send command to read temperature
- ; R16 will change from the value in R14 to 0 when data is received
- t_convert_wait: ; Attempt to read the data.
- RCALL long_delay
- RCALL i2c_getdata
- CP R14, R16
- BREQ t_convert_wait
- ; From the 16-bit value in R5:R4 we calculate the temperature in degrees C by:
- ; Temp (deg. C) = ((175.72 * Temp_Code) / 65536) - 46.85
- ;
- ; To preserve decimal places we multiply by 100:
- ; Temp (deg. C) * 100 = ((17572 * Temp_Code) / 65536) - 4685
- ;
- ; Then, when we convert this value to ASCII for display, we effectively divide by 100
- ; by dropping the last two digits and rounding
- ; Step 1: Multiply sensor data by 17572 (0x44A4)
- ; R4, R5, R6, R7 16-bit multiplicand input (32-bit extended) - Value is already loaded from the i2c_getdata routine
- ; R24, R25 16-bit multiplier
- LDI R24, 0xA4
- LDI R25, 0x44
- RCALL multiply16 ; Result is in R3:R2:R1:R0. We divide by 65536 by simply discarding the low 16-bits (R1:R0).
- ; So the final result is in R3:R2
- LDI R16, 0x4D
- LDI R17, 0x12
- SUB R2, R16 ; Subtract 4685 (0x124D)
- SBC R3, R17 ; Temp * 100 is now in R3:R2. Convert this to ASCII and put it into the memory buffer for the display!
- ; Convert temp from C to F.
- ; Copy value from R3:R2 to R19:R18 to save for later
- MOV R18, R2
- MOV R19, R3
- ; To convert C to F, we multiply by 9/5, or 1.8. This sets up multiplying by 0.8
- LDI R24, LOW(52429) ; Multiplier
- LDI R25, HIGH(52429)
- MOV R4, R2 ; Multiplicand (value in C)
- MOV R5, R3
- CLR R6
- CLR R7
- RCALL multiply16
- ; Result is in R3:R2. Add the original value saved in R19:R18 for effectively multiplying by 9/5
- ADD R2, R18
- ADC R3, R19
- LDI R18, LOW(3200)
- LDI R19, HIGH(3200)
- ADD R2, R18 ; Add 32. Remember this value is effectively multiplied by 100 as well!
- ADC R3, R19
- ; Done reading temperature! Convert the result and put it into the display buffer...
- LDI R26, 0x62 ; Buffer line 1 starts at 0x0060
- LDI R27, 0x00 ;
- RCALL bin2dec16 ; bin2dec16 is written to use R3:R2 as the input
- RCALL lcd_refresh ; Update dispaly!
- RJMP main ; Loop
- trap: ; Trap loop for debugging - erase later?
- RJMP trap
- ;***********************************************************************************************************************
- ; multiply16
- ;***********************************************************************************************************************
- ; 16-bit multiplication, returns 32-bit value in R3:R2:R1:R0
- ;
- ;
- ; R4, R5, R6, R7 16-bit multiplicand input (32-bit extended)
- ; R0, R1, R2, R3 32-bit output. FINAL output will be in 16-bit word R3:R2
- ; R23 Shift counter (=16 to start)
- ; R24, R25 16-bit multiplier
- ;
- multiply16:
- CLR R0 ; Set output to 0.
- CLR R1
- CLR R2
- CLR R3
- LDI R23, 16 ; Shift counter
- multloop:
- MOV R8, R25 ; Test if the multiplier is zero
- OR R8, R24 ;
- BREQ multend ; Exit if multiplier is 0. This leaves the ouput as 0 as well.
- SBRS R24, 0 ; Is the LSB of R25:R24 one?
- RJMP noadd ; If it's zero, skip this next part...
- ; Add R19:R18:R17:R16 to output
- ADD R0, R4 ; Add first bytes
- ADC R1, R5 ; Add with carry second bytes
- ADC R2, R6 ; Add with carry third bytes
- ADC R3, R7 ; Add with carry fourth bytes
- noadd:
- ; Rotate multiplicand left through carry
- CLC ; Clear carry first so we don't contaminate our data
- ROL R4 ; Shift first byte through carry
- ROL R5 ; Shift second byte through carry
- ROL R6 ; Shift third byte through carry
- ROL R7 ; Shift fourth byte through carry
- ; Rotate multiplier right (no carry)
- CLC ; Clear carry first so we don't contaminate our data
- ROR R25 ; Shift second byte through carry
- ROR R24 ; Shift first byte through carry
- DEC R23 ; Decrement counter
- BREQ multend ; If counter is zero, we're done.
- RJMP multloop ; Next step of the multiplication process
- multend:
- RET
- ;***********************************************************************************************************************
- ; BIN2DEC16
- ;***********************************************************************************************************************
- ; Converts a 16-bit byte in R3:R2 to three-char (XXX) ASCII string (non-terminated).
- ; Expects the input value to be actual value multiplied by 100, so it effectively divides by 100
- ; by dropping the last two digits and rounding.
- ;
- ; Input R3:R2
- ; SRAM address preloaded into register X (R27:R26)
- bin2dec16:
- LDI R20, 0x30 ; Load 0x30 (48, ASCII "0") into counter R20
- LDI R16, LOW(10000) ; Load the value 10,000 into 16-bit word R17:R16
- LDI R17, HIGH(10000) ;
- MOV R10, R2 ; Make a copy of the value to be converted
- MOV R11, R3
- K10000_16:
- CP R10, R16 ; Compare the current value (R11:R10) to what we're subtractiong (R17:R16)
- CPC R11, R17 ; and if equal jump to the next decade
- BRLO STK10000_16
- SUB R10, R16 ; 16-bit Subtract
- SBC R11, r17
- INC R20 ; Add 1 to counter
- RJMP k10000_16 ; Loop
- STK10000_16:
- MOV R4, R20 ; Make a copy of the 10,000s place for later
- LDI R20, 0x30 ; Reload 0x30 (48, ASCII "0") into counter R20
- LDI R16, LOW(1000) ; Load the value 1,000 into 16-bit word R17:R16
- LDI R17, HIGH(1000)
- K1000_16:
- CP R10, R16 ; Compare the current value (R11:R10) to what we're subtractiong (R17:R16)
- CPC R11, R17 ; and if equal jump to the next decade
- BRLO STK1000_16
- SUB R10, R16 ; 16-bit Subtract
- SBC R11, r17
- INC R20 ; Add 1 to counter
- RJMP k1000_16 ; Loop
- STK1000_16:
- MOV R5, R20 ; Make a copy of the 1,000s place for later
- LDI R20, 0x30 ; Reload 0x30 (48, ASCII "0") into counter R20
- LDI R16, LOW(100) ; Load the value 100 into 16-bit word R17:R16
- LDI R17, HIGH(100)
- K100_16:
- CP R10, R16 ; Compare the current value (R11:R10) to what we're subtractiong (R17:R16)
- CPC R11, R17 ; and if equal jump to the next decade
- BRLO STK100_16
- SUB R10, R16 ; 16-bit Subtract
- SBC R11, r17
- INC R20 ; Add 1 to counter
- RJMP k100_16 ; Loop
- STK100_16:
- MOV R6, R20 ; Make a copy of the 100s place for later
- CLR R20
- LDI R16, LOW(10) ; Load the value 10 into 16-bit word R17:R16
- LDI R17, HIGH(10)
- K10_16:
- CP R10, R16 ; Compare the current value (R11:R10) to what we're subtractiong (R17:R16)
- CPC R11, R17 ; and if equal jump to the next decade
- BRLO STK10_16
- SUB R10, R16 ; 16-bit Subtract
- SBC R11, r17
- INC R20 ; Add 1 to counter
- RJMP k10_16 ; Loop
- STK10_16:
- CPI R20, 0x05 ; If the 10s place is 5 or more, round the 100s place up by incrementing
- BRLO skip_round ; If not, skip right to storage
- INC R6 ; so if the remainder >=5 we add 1 to the 100s place and continue rounding
- LDI R20, 0x30 ; Reload 0x30 (48, ASCII "0") into counter R20
- LDI R21, 0x3A ; Value for comparing
- CP R6, R21 ; If the 100s place is 10, carry the rounding through to the 1,000s place
- BRLO skip_round
- MOV R6, R20 ; Set 100s place to "0"
- INC R5 ; Increment the 1,000s place
- CP R5, R21 ; If the 1,000s place is 10, carry the rounding through to the 10,000s place
- BRLO skip_round
- MOV R5, R20 ; Set 1,000s place to "0"
- INC R4 ; Increment the 10,000s place
- skip_round:
- ; Done rounding, let's store the value in SRAM
- ST X+, R4 ; 10,000s place
- ST X+, R5 ; 1,000s place
- ST X, R6 ; 100s place
- ; Now let's remove leading zeros! It's just easier to do this as an postprocess...
- SUBI R26, 0x02 ; Return to the original X location by substracting 2
- LDI R20, 0x20 ; Load " " into R20
- LD R16, X ; Load value from SRAM
- CPI R16, 0x30 ; Is the value "0" ?
- BRNE bin2dec16_done ; If not, don't bother checking anything else
- ST X+, R20 ; If it is, replace with " "
- LD R16, X ; Load value from SRAM
- CPI R16, 0x30 ; Is the value "0" ?
- BRNE bin2dec16_done ; If not, don't bother checking anything else
- ST X, R20 ; If it is, replace with " "
- ; By only doing this twice, we leave a zero before the decimal place and any zeros after the decimal place
- bin2dec16_done:
- RET
- ;***********************************************************************************************************************
- ; LCD Refresh
- ;***********************************************************************************************************************
- ; Updates the LCD display with the data from the buffer pointed at by the X register
- ; (R27:R26). Expects a null terminated string. Will automatically split the string into
- ; 2 lines if necessary.
- lcd_refresh:
- LDI R26, 0x60 ; Buffer starts at 0x0060
- LDI R27, 0x00 ;
- LDI R21, 0x80 ; Move to start of row 1 at address DDRAM 0x00
- RCALL lcd_sendinstr ; Command 0x80 = Instruction 0x80 + Address 0x00
- LDI R24, 0x10 ; Copy up to 16 bytes from buffer to LCD
- refresh1:
- LD R21, X+ ; Load byte from SRAM (X pointer)
- TST R21 ; Test R21 for zero (or minus, which would be invalid)
- BREQ refresh_done ; If it was zero, skip to the end
- MOV R16, R15 ; Load I2C address for LCD
- RCALL lcd_senddata ; Send byte to LCD display
- DEC R24 ; Count down
- BRNE refresh1 ; If counter isn't 0, loop back
- LDI R21, 0xC0 ; Move to start of row 2 at address DDRAM 0x40
- RCALL lcd_sendinstr ; Command 0xC0 = Instruction 0x80 + Address 0x40
- LDI R24, 0x10 ; Copy up to another 16 bytes from buffer to LCD
- refresh2:
- LD R21, X+ ; Load byte from SRAM (X pointer)
- TST R21 ; Test R21 for zero (or minus, which would be invalid)
- BREQ refresh_done ; If it was zero, skip to the end
- MOV R16, R15 ; Load I2C address for LCD
- RCALL lcd_senddata ; Send byte to LCD display
- DEC R24 ; Count down
- BRNE refresh2 ; If counter isn't 0, loop back
- refresh_done:
- RET
- ;***********************************************************************************************************************
- ; LCD New Buffer
- ;***********************************************************************************************************************
- ; Copies the primary LCD default buffer data from program memory to SRAM.
- ; Useful in case you trample the LCD buffer with some other message...
- lcd_newbuff:
- LDI R30, 0x02 ; Data starts at program memory 0x0002
- CLR R31 ; High byte of pointer needs to be 0x00
- LDI R28, 0x60 ; Copy data to SRAM which starts at 0x0060
- CLR R29 ; High byte of pointer needs to be zero
- LDI R17, 0x20 ; Copy 32 bytes total
- buffcopy:
- LPM R18, Z+ ; Copy byte from program memory into register R18, then increment pointer
- ST Y+, R18 ; Copy register R18 into SRAM, then increment pointer
- DEC R17 ; Count down
- BRNE buffcopy ; If counter isn't 0, loop back
- RET
- ;***********************************************************************************************************************
- ; LCD Initialize
- ;***********************************************************************************************************************
- ; Sends the required LCD initialization sequence
- ;
- lcd_init:
- ; Not sure why, but sending the high nybble of the first init byte
- ; twice makes operation more stable and consistent...
- ; The datasheet actually specifies this explicitly, too, without explaination
- LDI R17, 0x24
- MOV R16, R15 ; Load I2C address for LCD
- RCALL i2c_senddata
- LDI R17, 0x20
- RCALL i2c_senddata
- LDI R21, 0x2C
- RCALL lcd_sendinstr
- LDI R21, 0x0C
- RCALL lcd_sendinstr
- LDI R21, 0x01
- RCALL lcd_sendinstr
- RCALL long_delay ;(pause)
- LDI R21, 0x06
- RCALL lcd_sendinstr
- RET
- ;***********************************************************************************************************************
- ; Send LCD instruction byte
- ;***********************************************************************************************************************
- ; This routine takes a byte in register R21, splits it into two nybbles and sends
- ; it to the LCD INSTRUCTION register using 4 commands
- ;
- lcd_sendinstr:
- MOV R22, R21 ; Make a copy of the byte
- SWAP R22 ; Swap high and low nybbles
- LDI R17, 0xF0 ; Use R17 as a temp register
- AND R21, R17 ; Mask lower nybble of copy 1 (High nybble of original)
- AND R22, R17 ; Mask lower nybble of copy 2 (Low nybble of original)
- MOV R16, R15 ; Load I2C address for LCD
- ;Send high nybble with 0x04 and then 0x00 low nybble to load INSTRUCTION register
- MOV R17, R21
- ORI R17, 0x04 ; High nybble + 0x04
- RCALL i2c_senddata
- MOV R17, R21
- ORI R17, 0x00 ; High nybble + 0x00
- RCALL i2c_senddata
- ;Send low nybble with 0x04 and then 0x00 low nybble
- MOV R17, R22
- ORI R17, 0x04 ; High nybble + 0x04
- RCALL i2c_senddata
- MOV R17, R22
- ORI R17, 0x00 ; High nybble + 0x00
- RCALL i2c_senddata
- RET
- ;***********************************************************************************************************************
- ; Send LCD data byte
- ;***********************************************************************************************************************
- ; This routine takes a byte in register R21, splits it into two nybbles and sends
- ; it to the LCD DATA register using 4 commands
- ;
- lcd_senddata:
- MOV R22, R21 ; Make a copy of the byte
- SWAP R22 ; Swap high and low nybbles
- LDI R17, 0xF0 ; Use R17 as a temp register
- AND R21, R17 ; Mask lower nybble of copy 1 (High nybble of original)
- AND R22, R17 ; Mask lower nybble of copy 2 (Low nybble of original)
- MOV R16, R15 ; Load I2C address for LCD
- ;Send high nybble with 0x0D and then 0x09 low nybble to load DATA register
- MOV R17, R21
- ORI R17, 0x0D ; High nybble + 0x0D
- RCALL i2c_senddata
- MOV R17, R21
- ORI R17, 0x09 ; High nybble + 0x09
- RCALL i2c_senddata
- ;Send low nybble with 0x04 and then 0x00 low nybble
- MOV R17, R22
- ORI R17, 0x0D ; High nybble + 0x0D
- MOV R16, R15 ; Copy LCD address to I2C address register
- RCALL i2c_senddata
- MOV R17, R22
- ORI R17, 0x09 ; High nybble + 0x09
- MOV R16, R15 ; Copy LCD address to I2C address register
- RCALL i2c_senddata
- RET
- ;***********************************************************************************************************************
- ; I2C send data
- ;***********************************************************************************************************************
- ; -Send start condition
- ; -Send I2C address from register R16
- ; -Wait for ACK for 2 x Standard Delay period
- ; if no ACK then return with error (How?)
- ; -Send I2C data from register R17
- ; -Wait for ACK for 2 x Standard Delay period
- ; if no ACK then return with error (How?)
- ;
- ; DDRB = 1 means the line is LOW (Sinking current, driving line low)
- ; DDRB = 0 means the line is HIGH (High Z, external pullup driving line high)
- i2c_senddata:
- MOV R18, R16 ; Copy of I2C address
- LSL R18 ; Pre-shift the address byte one bit. LSB = 0 indicating write
- RCALL i2c_start ; I2C start condition
- RCALL i2c_sendbyte ; Sends contents of R18 (address + write)
- SBRS R20, 0 ; Re-test if SDA was high
- RJMP i2c_nextbyte ; If not, we have ACK! Jump to the next byte
- RJMP i2c_stop ; If bit 0 of R20 is set, we have no ACK. Stop the transmission.
- i2c_nextbyte: ; We get here if there was a successful ACK - send the data byte!
- RCALL i2c_delay ; Longer delay between address and data bytes
- RCALL i2c_delay
- RCALL i2c_delay
- MOV R18, R17 ; Make a copy of the data byte
- RCALL i2c_sendbyte ; Send the data byte (without preshifting)
- RJMP i2c_stop ; Even if there wasn't an ACK, we're done here.
- RET ; Just in case? (i2c_stop has a RET in it)
- ;***********************************************************************************************************************
- ; I2C send byte
- ;***********************************************************************************************************************
- ; -Sends 8 bits from register R18
- ; -Wait for ACK for 2 x Standard Delay period
- ; if no ACK then return with error (How?)
- i2c_sendbyte:
- LDI R19, 0x08 ; Load the bit counter to produce 8 bit pulses (8 bits of data)
- i2c_bitbang: ; Begin bitbang loop!
- SBI DDRB, 1 ; Set SCL Low
- SBRC R18, 7 ; Is MSB of R18 low?
- CBI DDRB, 0 ; If not, set SDA high
- SBRS R18, 7 ; If yes, is MSB of R18 high? (Better be!)
- SBI DDRB, 0 ; If not, set SDA low
- RCALL i2c_delay ; Delay
- CBI DDRB, 1 ; Set SCL high
- RCALL i2c_delay ; Delay
- LSL R18 ; Shift the data left by 1 bit
- DEC R19 ; Decrement counter
- BRNE i2c_bitbang ; If counter is zero, we're done!
- ; Process ACK.
- SBI DDRB, 1 ; Set SCL Low
- RCALL i2c_delay ; Delay to give slace time to ack
- CBI DDRB, 0 ; Set SDA high
- RCALL i2c_delay ; Delay
- CBI DDRB, 1 ; Set SCL high. Slave device should pull SDA low now
- RCALL i2c_delay ; Delay
- LDI R19, 0xF0 ; A small delay only
- i2c_ack_test:
- IN R20, PINB ; Input from Port B.
- SBRS R20, 0 ; Is SDA still high?
- RJMP i2c_ack_ok ; If not, we have ACK! Exit the test loop
- DEC R19 ; Decrement counter
- BRNE i2c_ack_test ; Keep testing for a bit
- ; If we make it here, there was no ACK.
- i2c_ack_ok:
- RET
- ;***********************************************************************************************************************
- ; I2C get data
- ;***********************************************************************************************************************
- ; -Generate start condition
- ; -Send I2C address from register R16
- ; -Wait for ACK for 2 x Standard Delay period
- ; if no ACK then return with error (How?)
- ; -Pull 8 bits I2C data into R5
- ; -Generate ACK
- ; -Pull 8 bits I2C data into R4
- ; -Generate NACK (This prevents transmission of checksum byte)
- ; -Generate stop condition
- i2c_getdata:
- MOV R18, R16 ; Make a copy of the address byte
- LSL R18 ; Pre-shift the address byte one bit
- ORI R18, 1 ; Set the LSB, indicating a read operation
- RCALL i2c_start ; I2C Start condition
- RCALL i2c_sendbyte ; Send the byte in R18 (Address + Read)
- IN R20, PINB ; Input from Port B.
- SBRS R20, 0 ; Test if SDA is high.
- RJMP i2c_getstart ; If not, we have ACK! Jump to the next byte
- RJMP i2c_stop ; If bit 0 of R20 is set, we have no ACK.
- i2c_getstart:
- LDI R17, 0x02 ; Get 2 bytes of data
- CLR R4 ; Clear R7:R6:R5:R4 for receiving data
- CLR R5 ; Only R5:R4 are loaded with new data, though
- CLR R6 ;
- CLR R7 ;
- i2c_getbyte: ;
- RCALL i2c_delay ; Longer delay between address and data bytes
- RCALL i2c_delay
- RCALL i2c_delay
- LDI R19, 0x08 ; Load the bit counter to produce 8 bit pulses (8 bits of data)
- SBI DDRB, 1 ; Set SCL Low
- CBI DDRB, 0 ; Set SDA high (slave will pull it low)
- i2c_bitslurp: ; Begin bitslurp loop!
- SBI DDRB, 1 ; Set SCL Low
- RCALL i2c_delay ; Delay
- CBI DDRB, 1 ; Set SCL high
- IN R20, PINB ; Input from Port B.
- ROR R20 ; We're using PB0 for SDA to the input is the LSB only...
- ROL R4 ; Rotate the LSB from R20 into carry and back out into R3, and MSB from R3 into carry
- ROL R5 ; Rotate the MSB of R3 from carry back out into R4.
- DEC R19 ; Decrement counter (read 8 bits)
- BRNE i2c_bitslurp ; If counter is zero, we're done!
- DEC R17 ; We're reading two bytes
- BREQ i2c_getdone ; If counter is zero, we're done!
- ; Produce an ACK by holding SDA low and pulsing the clock
- SBI DDRB, 1 ; SCL Low
- SBI DDRB, 0 ; SDA Low
- RCALL i2c_delay ; Delay
- CBI DDRB, 1 ; Set SCL high
- RCALL i2c_delay ; Delay
- RJMP i2c_getbyte ; Get the next byte
- i2c_getdone:
- ; Produce a NACK (because the datasheet says to?) by releasing SDA and pulsing the clock
- SBI DDRB, 1 ; SCL Low
- SBI DDRB, 1 ; SDA high
- RCALL i2c_delay ; Delay
- CBI DDRB, 1 ; Set SCL high
- RCALL i2c_delay ; Delay
- CLR R16 ; Clear R16 which is the sign that data was received.
- RJMP i2c_stop ; We're done here.
- RET ; Just in case? (i2c_stop has a RET in it)
- ;***********************************************************************************************************************
- ; I2C start
- ;***********************************************************************************************************************
- ; Send I2C start condition
- ; Start condition is when the Data line (SDA) transistions from high to low while
- ; the clock (SCL) remains high, followed by the clock transistioning to low.
- ;
- i2c_start:
- SBI DDRB, 0 ; SDA Low
- RCALL i2c_delay ; Delay
- SBI DDRB, 1 ; SCL Low
- RCALL i2c_delay ; Delay
- RET
- ;***********************************************************************************************************************
- ; I2C stop
- ;***********************************************************************************************************************
- ; Send I2C stop condition
- ; Stop condition is when the Data line (SDA) transistions from high to low while
- ; the clock (SCL) remains high, followed by the clock transistioning to low.
- ;
- i2c_stop:
- SBI DDRB, 1 ; SCL Low (Just to make sure)
- SBI DDRB, 0 ; SDA Low (Just to make sure)
- RCALL i2c_delay
- CBI DDRB, 1 ; SCL High
- RCALL i2c_delay
- CBI DDRB, 0 ; SDA High
- RCALL i2c_delay ; Post-data delay for padding.
- RCALL i2c_delay ; Post-data delay for padding.
- RET
- ;***********************************************************************************************************************
- ; Delay routine
- ;***********************************************************************************************************************
- ; Registers R24 and R25 make a 16-bit word. Delay is set by loading a 16-bit value into this
- ; word and incrementing it until it overflows. The lower the initial value, the longer it will
- ; take to overflow and thus the longer the delay will be.
- ;
- ; This routine takes 7 + 4(65535 - n) cycles where n is the initial value of the registers
- ;
- ; At 1 MHz this puts the range of delay from 7 microseconds (n=65535) to 262,147 microseconds (0.262 sec)
- ;
- ; 0.001 seconds (1000 microseconds): n = 65,287 (0xFF6B)
- long_delay:
- LDI R24, 0x00 ; 0xFEFE happens to give 500.0Hz +/- 0.1% with the specific chip being tested with
- LDI R25, 0xFC ; This is unburdened and actual code will probably affect the overall timing
- usi_delay_loop:
- ADIW R24, 1
- BRNE usi_delay_loop
- i2c_delay:
- RET
- ;***********************************************************************************************************************
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement