Home ==> Micro beginner ==> 12. IR receiver/transmitter
ATtiny24 ATtiny13

Lecture 12: IR receiver and transmitter


Getting practical: to receive, monitor and analyze infrared remote control signals and to generate our own infrared signals with a transmitter. To reduce the number of source code variations conditioned assembly is used here to some extend.

12.0 Overview

  1. Introduction to infrared signals
  2. Introduction to conditioned assembly
  3. Hardware, components, mounting
  4. To measure infrared signals
  5. An IR transmitter
  6. An IR data transmit/receive system
  7. A self-learning IR three-channel switch

12.1 Introduction to infrared signals

The small well-designed boxes that cover our living room table and that wait until a key is pressed, are majorly unknown boxes. Not many people know how those work and how they control a TV or a CD player via invisible rays. In the earlier years, without those boxes, we had to stand up from the sofa and push the buttons on those devices manually. The remote control boxes allow to stay on the sofa and to push secondary buttons.

Infrared means that those work with heat rays and that we cannot see those signals. As those small boxes transmit with only 250 mW, we cannot sense that with our skin (which is normally our built-in infrared sensor). But even if we could feel and see those rays we would not see a lot because those boxes work only for a few milliseconds, to switch the TV to channel 15.

In order to discriminate those rays from boxes from other infrared generators (such as those from a cup of hot tea), those infrared rays are switched on-and-off in a very fast rhythm. This on-and-off is on bat frequencies of 35 to 56 kcs/s, so even if we would see infrared we would see nothing but a short burst. In the receiver those on-and-off signals are filtered out and selectively amplified so that the TV even on a hot summerday with lots of infrared rays understands the command to switch to channel 15. Such remote control signals look like this (only parts of the signal are displayed here).

IR signal While the IR LED is off, the receiver (in read) does not identify a signal, his output is inactive (high!). The LED then is switched on and off e.g. with 40 kcs/s (12.5 µs on, 12.5 µs off). With the delay of the filter the receiver detects the signal and the output follows by going low, it goes active. The data transmitted is encoded in the duration of the inactive phase. Between two active phases represent between 15 and 30 a binary zero and 50 to 130 a binary one.

The design decision for those signals, to encode the ones and zeroes in the inactive period duration and to leave the active LED signal for the pauses is positive for the battery life-time: zeroes and ones consume the same LED current, zero.

Why we always use the term "e.g." is caused by the fact that every producer of IR remote controls has its own special design for the signals. The design goal is rather to have a unique signal design to avoid that different boxes on the living-room table have a conflict and that switching the TV to channel 15 increases the loudness of the tuner/amplifier of the CD player, too. That is why any device brings its own box with it, and why nobody has invented a "one-box-for-all" solution.

We will have to care about this absence of industrial norms later on.

Home Top IR Conditioned Hardware Measuring Transmit Receive Switch


12.2 Introduction to conditioned assembly programming

Version 2 of ATMEL's assembler offered the opportunity to define conditions under which the source code is assembled or not. Directives were introduced that allow to control the assembly flow. This makes sense if the basic structure of the source code remains the same and only seleced and limited portions of the code need to be changed to fit to other needs.

12.2.1 To define conditions

By using the following formulation the following source code is only assembled if the condition is "1":

.equ Switch = 1 ; Define a switch
; [...]
.if Switch == 1
	; [Assemble this part of the source code]
	.endif
; [...]

The double "==" in the .if condition means that this is a logic decision (that is either true or false), while our Switch can as well be zero, two or three. Again: this decision is made during assembling, the controller will not see and has nothing to do with that decision and with the .if and .endif directive (his decision would involve the instruction BREQ, e.g.). Any .if directive must be followed by an .endif directive, otherwise none of the following code is assembled and the assembler ends with an error message (open condition ...).

If you want to switch on the opposite condition, two methods come into question:

.if Switch != 1
	; [Assemble this part if Switch is not one]
	.endif
; [...]

where "!=" means "not equal". The same effect would have

.if Switch == 0
	; [Do not assemble this part in case Switch is 1]
	.endif
; [...]

12.2.2 If-then-else alternatives

If the switch shall select between two alternative pathes to assemble the .else directive can be used. The following formulation assembles for different polarities of an input pin:

.equ Switch = 1
; [...]
.if Switch == 1
	sbic PINB,PINB0 ; Skip next instruction if input pin is low
	.else
	sbis PINB,PINB0 ; Skip next instruction if input pin is high
	.endif
	rjmp PinConditionNotFulfilled

The code behind the .else directive is assembled if the .if condition is not met. This formulation can get rather intransparent because it mixes assembler conditions and conditioned jump instructions. In such cases it can help to consult the listing that the assembler produces, in order to reduce complexities by one level.

In the .else case it can be sometimes appropriate to decide with an additional condition if assembling should be performed. This can be done by using the .elif directive instead of .else. By doing this requires some attention because it can be that neither source code is assembled. An example to fit a source code as well for an ATtiny13 and an ATtiny24 is:

.equ cType == 13 ; can be 13 or 24
; [...]
.if cType == 13
	; Place Reset- and Int-vector table for ATtiny13 here
	.elif cType == 24
	; Place Reset- and Int-vector table for ATtiny24 here
	.endif

If the user now neither sets cType to 13 nor to 24, no reset and int vector table will be in the assembled code and that could have funny effects.

To avoid this the assembler gavrasm has an additional directive .ifdevice Type. Here you can explicitly formulate .IFDEVICE "ATtiny13" or .IFDEVICE ATTINY13.

12.2.3 Other helpful direktives

The following outputs an error message during assembling if neither 13 nor 24 is defined as device:

.if (cType != 13) && (cType != 24)
	.error "Wrong type!"
	.endif

This directive can further be used to provoke an error message if a constant exceeds certain value ranges (e.g. if the constant is larger than 255).

If only a message shall be outputted, but assembling shall continue, the directive .MESSAGE "Message text" provides that.

Home Top IR Conditioned Hardware Measuring Transmit Receive Switch


12.3 Hardware, components and mounting

12.3.1 Schemes

The schemes for the two types of IR receiver modules are slightly different. Beyond the IR receiver module nothing else changes in the scheme.

Scheme with a TSOP4840

Scheme with a TSOP1740

12.3.2 Components

The IR receiver modules

Both receiver modules are necessary for different remote control types. TSOP4840 is best for older, TSOP1740 for newer remote control devices. In practise this difference is irrelevant, as I realized with my boxes of different ages.

TSOP4840, TSOP1740 The pinning of both types is different. The signal output is to be connected to PA0 of the ATtiny24. That is it.

Both devices are active low, which means that an actively detected IR signal brings the output to low level.

12.3.3 Mounting

Mounting is trivial.

Home Top IR Conditioned Hardware Measuring Transmit Receive Switch


12.4 Measuring IR signals

12.4.1 Task

Besides the feeling that it would be nice to know how those small boxes work it could be helpful to analyze IR signals in case one likes to build his own. In that case we can switch the TV to our own channel 15 without being depending from others in our living room, who might prefer other channels. This requires some packages of research software which we generate here. This also will help us to analyze our own IR transmitter later on in this course.

12.4.2 Start signals

Task

The first task is to analyze start signals of remote control boxes.

Examples for IR signal structures

Start screen That is how the software looks like at the start (the German version). All following software displays values in hexadecimal format. You will see what that is good for later on. Who wants to have those in decimal format can use a hex calculator or uses a spread sheet application like OpenOffice to convert hex to decimal.

All values have to be multiplied with 8 because the timer used to measure times runs with a prescaler of 8 at 1 Mcs/s.

The software now waits for signals from the IR receiver module. After 16 level changes have been found, the measured times are displayed.

Signal start of a TV remote control box The signal starts with a long, inactive pause of 0x1696 * 8 µs duration. Then an active LED period of 0x0232 * 8 µs follows (input signal is low, L). This is followed by pause periods (input signal is high, H) of 0xC8 * 8 µs or 0x3F * 8 µs, in between active signals of 0x49 * 8 µs duration. Looks pretty simple .

Signals from a HDR This looks a little more complex, it stems from an Panasonic Harddrive Recorder. The signal start has a long High period, a shorter Low period, a half-as-long High period and a slightly shorter Low period. The signal pauses (H) are all between 0x0029 and 0x002D, one High signal is at 0x009C. The active signal periods are between 0x3C and 0x41.

Signals from a camera Those are the signals from a camera control. The header structure is similar to the two devices above, but the durations are different.

Table of signal durations header These are the example data in a spreadsheet. The header signals are with 50, 76 resp. 50 ms duration the longest. data bits require 0.7 to 1.0 ms.

An additional value N is listed. Those would be the number of timer periods to perform the on-and-off of a LED with 40 kcs/s in an active period using the CTC mode of the timer.

The program

In principal the program measures the duration between rising and falling as well as between falling and rising levels. For that the 16 bit timer TC1 is used, that is read and then cleared again.

Program structure

The program works in the following stages:

Program

This is the program, the source code file is here. To assemble this requires the LCD routines in the same folder. Please note that the switch "FromBehind" also allows to display the last received data pairs when set to one (conditional assembly).

;
; *****************************************************************
; * IR receiver with ATtiny24 and LCD to measure header durations *
; * (C)2017 by http://www.avr-asm-tutorial.net                    *
; *****************************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ---------- Switch ------------------
.equ FromBehind = 0 ; 0: Header signals
;                     1: The last signals
;
; ---------- Ports ---------------------
.equ pIrIn = PINA ; IR mudule port
.equ bIrIn = 0    ; IR module pin
;
; ---------- Timing --------------------
; Processor clock         1.000.000 cs/s
; Time per clock cycle            1 us
; Prescaler TC1                   8
; Time per timer tick             8 us
; TC1 count after 1 ms          125
; TC1 overflow after            524 ms
;
; ---------- Registers ------------------
; Used: R0 by LCD and for decimal conversion
; Used: R1 for decimal conversion
; free: R2..R14
.def rSreg = R15 ; Save/Restore SREG
.def rmp   = R16 ; Multi purpose register
.def rmo   = R17 ; Second multi purpose register
.def rLine = R18 ; LCD line counter
.def rRead = R19 ; LCD read register
.def rimp  = R20 ; Interrupt multi purpose
.def rFlag = R21 ; Flag register
   .equ bUpd = 0 ; Update display flag
   .equ bShf = 1 ; Shift backwards flag
; free: R22 .. R25
; Used: XH:XL R27:R26 for shifting backwards
; Used: YH:YL R29:R28 Pointer to SRAM
; Used: ZH:ZL R31:R30 in LCD routines
;
; ---------- SRAM ----------------------
.DSEG ; Data segment
.ORG 0x0060 ; at beginning of SRAM
Buffer:
.byte 32 ; Buffer for receiver data
BufferEnd:
Last:
.byte 4 ; Last measured value
LastEnd:
;
; ---------- Reset- and interrupt vectors -----
.CSEG ; Code segment
.ORG 0 ; to the start
	rjmp Start ; Reset vector, init
	reti ; INT0 External Interrupt Request 0
	rjmp Pci0Isr ; PCINT0 Pin Change Interrupt Request 0
	reti ; PCINT1 Pin Change Interrupt Request 1
	reti ; WDT Watchdog Time-out
	reti ; TIM1_CAPT Timer/Counter1 Capture Event
	reti ; TIM1_COMPA Timer/Counter1 Compare Match A
	reti ; TIM1_COMPB Timer/Counter1 Compare Match B
	reti ; TIM1_OVF Timer/Counter1 Overflow
	reti ; TIM0_COMPA Timer/Counter0 Compare Match A
	reti ; TIM0_COMPB Timer/Counter0 Compare Match B
.if FromBehind == 1 ; Last signals only
	rjmp Tc0Isr ; TIM0_OVF TC0 Overflow, update display
	.else
        ; Header signals only
	reti ; TIM0_OVF Timer/Counter0 Overflow
	.endif
	reti ; ANA_COMP Analog Comparator
	reti ; ADC ADC Conversion Complete
	reti ; EE_RDY EEPROM Ready
	reti ; USI_STR USI START
	reti ; USI_OVF USI Overflow
;
; ---------- Interrupt Service Routines -----
Pci0Isr: ; PCINT0 interrupt
	in rSreg,SREG ; Save SREG
	sbic pIrIn,bIrIn ; Skip next instruction when input low
	rjmp Pci0Isr1 ; Input is high
	; Input is low, was high before
	in rimp,TCNT1L ; Low byte first
	std Y+1,rimp ; LSB to SRAM byte 1
	in rimp,TCNT1H ; High byte next
	st Y,rimp ; MSB to SRAM byte 0
.if FromBehind == 0 ; Assemble only if direction is forward
	cpi rimp,0x05 ; Check for signal pause
	brcs Pci0Isr2 ; Not a signal pause
	ldi YH,HIGH(Buffer) ; Pause, Y to SRAM buffer start
	ldi YL,LOW(Buffer)
	in rimp,TCNT1L ; Read again, low Byte first
	std Y+1,rimp ; LSB to SRAM buffer byte 1
	in rimp,TCNT1H ; High byte next
	st Y,rimp ; MSB to SRAM Byte 0
	.endif
	rjmp Pci0Isr2 ; Done, clear TC1 count
Pci0Isr1:
	; Input is high, was low before
	in rimp,TCNT1L ; Low byte first
	std Y+3,rimp ; LSB to SRAM byte 3
	in rimp,TCNT1H ; High byte next
	std Y+2,rimp ; MSB to SRAM byte 2
.if FromBehind == 1 ; Assembled only when backwards
	sbr rFlag,1<<bShf ; Set shift flag
	.else
        ; Assemble when forward
	adiw YL,4 ; Buffer pointer four bytes further
	cpi YL,LOW(BufferEnd) ; 32 bytes = 8 Low/High pairs
	brcs Pci0Isr2 ; Not yet reached
	sbr rFlag,1<<bUpd ; Set update flag
	ldi YH,HIGH(BufferEnd) ; Y to end of buffer
	ldi YL,LOW(BufferEnd)
	.endif
Pci0Isr2:
	ldi rimp,0 ; Restart TC1 counter
	out TCNT1H,rimp ; First the MSB
	out TCNT1L,rimp ; Next the LSB
	out SREG,rSreg ; Restore SREG
	reti ; fertig
;
; TC0 overflow Interrupt Service Routine
Tc0Isr:
	in rSreg,SREG ; Save SREG
	sbr rFlag,1<<bUpd ; Set update flag
	out SREG,rSreg ; Restore SREG
	reti
;
; ---------------- Start, Init --------------
Start:
	ldi rmp,LOW(RAMEND) ; Stack to RAMEND
	out SPL,rmp ; to stack pointer
	; Init I/O port pins
	; Init LCD port outputs
	ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
	out pLcdCR,rmp ; LCD Control port outputs
	clr rmp ; Outputs off
	out pLcdCO,rmp ; to LCD control port
	ldi rmp,mLcdDRW ; Data port output for write
	out pLcdDR,rmp ; to direction port
	; Init LCD
	rcall LcdInit ; Init of the LCD, call include
	ldi ZH,HIGH(2*CodeChars) ; Special characters
	ldi ZL,LOW(2*CodeChars)
	rcall LcdChars ; Write those to LCD
	ldi ZH,HIGH(2*LcdStart) ; Output start text
	ldi ZL,LOW(2*LcdStart)
	rcall LcdText ; call include routine
	; Start value for buffer pointer
.if FromBehind == 1 ; Backwards
	ldi YH,HIGH(Last) ; Behind buffer end
	ldi YL,LOW(Last)
	.else
        ; Forward
	ldi YH,HIGH(Buffer) ; Y to buffer start
	ldi YL,LOW(Buffer)
	.endif
	; Start value for flags
	clr rFlag
.if FromBehind == 1 ; For backwards only
	; Init timer TC0
	ldi rmp,(1<<CS02)|(1<<CS00) ; Prescaler = 1024
	out TCCR0B,rmp ; Start timer
	.endif
	; Init timer TC1, free running with prescaler = 8
	ldi rmp,1<<CS11 ; Prescaler 8
	out TCCR1B,rmp ; to timer control port B
	; PCINT0 for IR Rx module
	ldi rmp,1<<PCINT0 ; Pin 0 Level change INTs
	out PCMSK0,rmo ; to PCINT0 mask
	ldi rmp,1<<PCIE0 ; Enable PCINT0 interrupt
	out GIMSK,rmp ; in General Interrupt mask port
	; Sleep Enable
	ldi rmp,1<<SE ; Sleep mode Idle
	out MCUCR,rmp ; in MCU control port
	; Enable interrupts
	sei ; Set I flag in SREG
Loop:
	sleep ; go to sleep
	nop ; Wake up
	sbrc rFlag,bShf ; Skip next if shift flag is clear
	rcall Shift ; Process shift flag
	sbrc rFlag,bUpd ; Skip next if update flag is clear
	rcall Update ; Process update flag
	rjmp Loop ; go to sleep again
;
; Shift buffer content backwaerts
Shift:
	cbr rFlag,1<<bShf ; Clear shift flag
	clr rmp ; Clear TC0 count
	out TCNT0,rmp
	ldi rmp,1<<TOIE0 ; Disable TC0 interrupt
	out TIMSK0,rmp ; in interrupt mask
	ldi XH,HIGH(Buffer+4) ; Buffer pointer to second set
	ldi XL,LOW(Buffer+4)
	ldi ZH,HIGH(Buffer) ; Buffer pointer to shift target
	ldi ZL,LOW(Buffer)
Shift1: ; Shift buffer backwards
	ld rmp,X+ ; Read byte from origin
	st Z+,rmp ; Write byte to target
	cpi ZL,LOW(BufferEnd) ; All shifted?
	brcs Shift1 ; no, go on
	ret
;
; Process update flag
Update:
	cbr rFlag,1<<bUpd ; Clear update flag
.if FromBehind == 1 ; Only in case of backward
	clr rmp ; Disable TC0 interrupt
	out TIMSK0,rmp ; in interrupt mask port
	.endif
	ldi rmp,1 ; Clear LCD
	rcall LcdC4Byte ; Write to LCD control port
	ldi ZH,HIGH(Buffer) ; Point Z to buffer start
	ldi ZL,LOW(Buffer)
	clr rLine ; Clear line counter
Update1:
.if FromBehind == 1 ; Only if backwards
	ldi rmp,'e' ; Last value pair
	cpi ZL,LOW(BufferEnd-4)
	brcc Update2 ; 'e' marks last value
	.else
	ldi rmp,0x02 ; Pause character when forward
	cpi ZL,LOW(Buffer) ; First word?
	breq Update2 ; Output pause character
	.endif
	ldi rmp,0x01 ; Output low character
	sbrc ZL,1 ; Bit 2 buffer address zero?
	ldi rmp,0x00 ; no, Output high character
Update2:
	rcall LcdD4Byte ; Output special char
	ld rmp,Z+ ; Read MSB from buffer
	rcall Hex2Lcd ; Output MSB in hex
	ld rmp,Z+ ; Read LSB from buffer
	rcall Hex2Lcd ; Output LSB in hex
	mov rmp,ZL ; Check line end
	andi rmp,0x07 ; 8 bytes written?
	brne Update1 ; no, go on
	inc rLine ; Next line
	cpi rLine,4 ; End of display lines?
	brcc Update3 ; Yes, done
	mov rmp,rLine ; Next line
	rcall LcdLine ; To LCD
	rjmp Update1 ; And repeat
Update3: 
	ldi rmp,0x0C ; Cursor and blink off
	rjmp LcdC4Byte ; To LCD
;
; Byte in rmp in hex to LCD
Hex2Lcd:
	push rmp ; Save byte on stack
	swap rmp ; Upper nibble first
	rcall HexNibble2Lcd ; Write Nibble
	pop rmp ; Restore rmp
; Output nibble in hex on LCD
HexNibble2Lcd:
	andi rmp,0x0F ; Mask lower nibble
	subi rmp,-'0' ; Add ASCII-0
	cpi rmp,'9'+1 ; A to F?
	brcs HexNibble2Lcd1 ; No
	subi rmp,-7 ; Add 7 to yield A to F
HexNibble2Lcd1:
	rjmp LcdD4Byte ; Write rmp to LCD
;
; Start text LCD
LcdStart:
.db "IR analysis ATtiny24",0x0D,0xFF
.db "avr-asm-tutorial.net",0x0D,0xFF
.db "  Signal duration   ",0x0D,0xFF
.db " in hex Hi/Lo, 8 us",0xFE
;
; Special chars
CodeChars:
.db 64,0,0,0,0,0,4,4,6,0 ; Z = 0, Low signal
.db 72,0,0,0,0,0,5,7,5,0 ; Z = 1, High signal
.db 80,0,0,0,0,0,0,0,7,0 ; Z = 2, Pause
.db 0,0 ; End of table
;
; Include LCD routines
.include "Lcd4Busy.inc"
;
; End of source code
;

A new instruction that is used here is LD register,pointer. This loads the register from the location that the pointer points to, e.g. to an SRAM location. Pointers can be X, Y or Z register pairs. Variations are auto-incrementing the pointer following loading, e.g. ld R16,Z+, or auto-decrementing before loading, e.g. ld R16,-Z.

12.4.3 End signals

To find out if the IR signals have a different format on the end of the transmit cycle we need to evaluate the last signals that were sent. This requires a significant change to the program source. Here a different way was chosen: a switch in the program head decides whether to measure and display header info or the last bytes.

; ---------- Switch ------------------
.equ FromBehind = 0 ; 0: Header signals
;                     1: The last signals

With FromBehind = 1 the last arrived signals are displayed. Assemble the so changed source code and burn it into the flash, this will implement this massive change.

Signals from behind The last received signals are marked with a small "e". This shows that the last signals have no changed structure.

12.4.4 Number of signals

A wide variety has the number of Hi/Lo pairs transmitted.

Number of signals The next piece of software (German version shown) counts the number of signals, sorts them by high and low polarity and counts those that are longer than 256*8 = 2,048 µs.

Program

The software has a similar structure and is listed in the following. The source code is here, the LCD routines to be included are here.

;
; ***************************************************************
; * IR receiver with ATtiny24 and LCD to measure signal numbers *
; * (C)2017 by http://www.avr-asm-tutorial.net                  *
; ***************************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ---------- Constant ------------------------
.equ cSet     = 0x10 ; MSB long pause between signal bursts
;
; ---------- Ports ---------------------
.equ pIrIn = PINA ; IR-Detektor-Port
.equ bIrIn = 0    ; IR-Detektor-Pin
;
; ---------- Timing --------------------
; Processor clock         1.000.000 cs/s
; Time per clock cycle            1 us
; Prescaler TC1                   8
; Time per timer tick             8 us
; TC1 count after 1 ms          125
; TC1 overflow after            524 ms
;
; ---------- Registers ------------------
; Used: R0 by LCD, decimal conversion
; Used: R1 Decimal conversion
.def rHigh = R2 ; Number of High signals
.def rLow  = R3 ; Number of Low signals
.def rHead = R4 ; Number of header signals
.def rSet  = R5 ; Number of signal sets
; free: R6..R14
.def rSreg = R15 ; Save/Restore SREG
.def rmp   = R16 ; Multi purpose register
.def rmo   = R17 ; Second multi purpose register
.def rLine = R18 ; LCD line counter
.def rRead = R19 ; LCD read register
.def rimp  = R20 ; Interrupt multi purpose
.def rFlag = R21 ; Flag register
   .equ bUpd = 0 ; Update display flag
; free: R22 .. R29
; Used: ZH:ZL R31:R30 in LCD routines
;
; ---------- Reset- and interrupts -----
.CSEG ; Assemble to code segment
.ORG 0 ; to the beginning
	rjmp Start ; Reset vector, Init
	reti ; INT0 External Interrupt Request 0
	rjmp Pci0Isr ; PCINT0 Pin Change Interrupt Request 0
	reti ; PCINT1 Pin Change Interrupt Request 1
	reti ; WDT Watchdog Time-out
	reti ; TIM1_CAPT Timer/Counter1 Capture Event
	reti ; TIM1_COMPA Timer/Counter1 Compare Match A
	reti ; TIM1_COMPB Timer/Counter1 Compare Match B
	reti ; TIM1_OVF Timer/Counter1 Overflow
	reti ; TIM0_COMPA Timer/Counter0 Compare Match A
	reti ; TIM0_COMPB Timer/Counter0 Compare Match B
	rjmp Tc0Isr ; TIM0_OVF TC0 Overflow, update display
	reti ; ANA_COMP Analog Comparator
	reti ; ADC ADC Conversion Complete
	reti ; EE_RDY EEPROM Ready
	reti ; USI_STR USI START
	reti ; USI_OVF USI Overflow
;
; ---------- Interrupt Service Routines -----
Pci0Isr:
	in rSreg,SREG ; Save SREG
	sbic pIrIn,bIrIn ; Skip next if IR input low
	rjmp Pci0Isr1 ; Input is high
	; Input is low, was high before
	inc rHigh ; Count high signals
	in rimp,TCNT1L ; Read TC1 count LSB
	in rimp,TCNT1H ; dto., MSB
	tst rimp ; MSB larger than 0?
	breq Pci0Isr2 ; no
	inc rHead ; Increase number of head signals
	cpi rimp,cSet ; Test if larger or equal cSet
	brcs Pci0Isr2 ; no
	inc rSet ; Increase number of data sets
	rjmp Pci0Isr2 ; To clear TC1
Pci0Isr1:
	inc rLow ; Count low signals
Pci0Isr2:
	ldi rimp,0 ; Restart counter TC1
	out TCNT1H,rimp ; Write MSB first
	out TCNT1L,rimp ; Then LSB
	out TCNT0,rimp ; Restart counter TC0
	ldi rimp,1<<TOIE0 ; Enable time out interrupts
	out TIMSK0,rimp ; in TC0 int mask
	out SREG,rSreg ; Restore SREG
	reti ; Done
;
; TC0-Overflow Interrupt Service Routine
Tc0Isr:
	in rSreg,SREG ; Save SREG
	sbr rFlag,1<<bUpd ; Set update flag
	out SREG,rSreg ; Restore SREG
	reti
;
; ---------------- Start, Init --------------
Start:
	ldi rmp,LOW(RAMEND) ; Stack to RAMEND
	out SPL,rmp ; to stack pointer
	; Init I/O
	; Init LCD port outputs
	ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
	out pLcdCR,rmp ; to LCD control port
	clr rmp ; Output pins off
	out pLcdCO,rmp ; to LCD control port
	ldi rmp,mLcdDRW ; Data port mask write
	out pLcdDR,rmp ; to data port direction
	; Init LCD
	rcall LcdInit ; Call included LCD routine
	ldi ZH,HIGH(2*CodeChars) ; Spezialzeichen
	ldi ZL,LOW(2*CodeChars)
	rcall LcdChars ; Write special chars to LCD
	ldi ZH,HIGH(2*LcdStart) ; Display start text
	ldi ZL,LOW(2*LcdStart)
	rcall LcdText ; Call to included routine
	; Start value for flags
	clr rFlag
	; Clear all counters and content
	rcall Restart ; Restart values
	; Init timer TC0
	ldi rmp,(1<<CS02)|(1<<CS00) ; Prescaler 1024
	out TCCR0B,rmp ; Start timer
	; Init timer TC1, free running by clock div 8
	ldi rmp,1<<CS11 ; Prescaler = 8
	out TCCR1B,rmp ; to timer control port B
	; PCINT0 for IR-Rx
	ldi rmp,1<<PCINT0 ; Pin 0 Level change INTs
	out PCMSK0,rmo ; to mask port 0
	ldi rmp,1<<PCIE0 ; Enable interrupt PCINT0
	out GIMSK,rmp ; to General interrupt mask
	; Sleep Enable
	ldi rmp,1<<SE ; Sleep Mode Idle
	out MCUCR,rmp ; in MCU Kontrollregister
	; Enable interrupts
	sei ; Set I flag in SREG
Loop:
	sleep ; go to sleep
	nop ; Wake up
	sbrc rFlag,bUpd ; Check update flag
	rcall Update ; Process flag
	rjmp Loop ; go to sleep again
;
; Process update flag
Update:
	cbr rFlag,1<<bUpd ; Clear update flag
	clr rmp ; Clear TC0
	out TCNT0,rmp ; TC0 = 0
	out TIMSK0,rmp ; Disable TC0 interrupts
	; Display clear
	ldi rmp,0x01 ; Delete display content
	rcall LcdC4Byte ; to display
	; Display highs
	ldi ZH,High(2*LcdForm) ; Form template
	ldi ZL,Low(2*LcdForm)
	rcall LcdText ; Write on LCD
	ldi rmp,0x01 ; Cursor home
	rcall LcdD4Byte
	mov rmp,rHigh ; Display highs on LCD
	rcall Hex2Lcd
	ldi rmp,' ' ; Blank on LCD
	rcall LcdD4Byte
	ldi rmp,0x00 ; Special character 0
	rcall LcdD4Byte
	mov rmp,rLow ; Display lows on LCD
	rcall Hex2Lcd
	ldi rmp,' ' ; Blank
	rcall LcdD4Byte
	ldi rmp,'h' ; Display header symbol
	rcall LcdD4Byte
	mov rmp,rHead ; Display header signals
	rcall Hex2Lcd
	ldi rmp,' ' ; Blank
	rcall LcdD4Byte
	ldi rmp,'S' ; Display data sets symbol
	rcall LcdD4Byte
	mov rmp,rSet ; Display data sets
	rcall Hex2Lcd
	ldi rmp,0x0C ; Cursor and blink off
	rcall LcdC4Byte ; to LCD
;
; Restart
Restart:
	clr rHead ; Clear number of headers
	clr rHigh ; Clear number of highs
	clr rLow ; Clear number of lows
	ret
;
; Byte in rmp in hex to LCD
Hex2Lcd:
	push rmp ; Save byte
	swap rmp ; Upper nibble first
	rcall HexNibble2Lcd
	pop rmp ; Restore rmp
; Display nibble in hex on LCD
HexNibble2Lcd:
	andi rmp,0x0F ; Mask lower nibble
	subi rmp,-'0' ; Add ASCII-0
	cpi rmp,'9'+1 ; A to F?
	brcs HexNibble2Lcd1 ; no
	subi rmp,-7 ; Adjust to A to F
HexNibble2Lcd1:
	rjmp LcdD4Byte ; Display rmp on LCD
;
; Start text LCD
LcdStart:
.db "IR analysis ATtiny24",0x0D,0xFF
.db "avr-asm-tutorial.net",0x0D,0xFF
.db " Measuring IR signal",0x0D,0xFF
.db "   Signal numbers  ",0xFE
;
; Template
LcdForm:
.db "Number H/L Sig- ",0x0D,0xFF
.db "nals, headers,  ",0x0D,0xFF
.db "long signals    ",0x0D,0xFE
;
; Special characters
CodeChars:
.db 64,0,0,0,0,0,4,4,6,0 ; Z = 0, Low signal
.db 72,0,0,0,0,0,5,7,5,0 ; Z = 1, High signal
.db 0,0 ; End of table
;
; Include LCD routines
.include "Lcd4Busy.inc"
;
; End of source code
;

Example results

Number of signals TV With this piece of software we learn that the remote control of the TV consists of 71 Hi/Lo data pairs, that three header signals are longer than 2.048 ms and that 67 data pairs are part of the burst. This is obviously more than 64 bits, and allows encoding of enormous 1.84E+18 combinations, a lot more than buttons on the box. Develloppers of remote control equipment do obviously not apply rational rules.

Number of signals HDR This here is a real miracle: 146 Hi- and 147 Lo values provide 8.92E+42 combinations, each atom in our galaxy can be encoded individually, leaving rationality far behind.

Number of signals camera The nine buttons of the remote control box for a camera can well be encoded in four bits, but this has 35 Hi and 36 Lo signals. From which 429 million combinations can be encoded. Obviously remote control designers have lost all technical rationality.

12.4.5 Signal durations of data bits

In principle we already see from the diagnostic software so far which duration Hi and Lo signals have. With the following we expand the display of data bits. If we skip the MSB, which is always zero in data bits, we see 24 bytes on the LCD. If we only display either Hi or Lo signals and if we enable the selection of the direction (from the beginning, from the end), we see 48 data bits.

Bit duration short That is the start text on display (the German version).

Program

The program is listed as follows, the source code is here and has to be combined with the LCD routines.

;
; *****************************************************
; * IR receiver with ATtiny24 and LCD to examine bits *
; * (C)2017 by http://www.avr-asm-tutorial.net        *
; *****************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ---------- Switches ------------------
.equ FromBehind = 0 ; 0: first bits
;                     1: last bits
.equ cHigh     = 1 ; 1: Examine high bytes
.equ cLow      = 1 ; 1: Examine low bytes
;
; ---------- Byte output --------------
; .bb.bb.bb.bb.bb.bb ; 6 bytes
; .bb.bb.bb.bb.bb.bb ; 12 bytes
; .bb.bb.bb.bb.bb.bb ; 18 bytes
; .bb.bb.bb.bb.bb.bb ; 24 bytes
;
; ---------- Ports ---------------------
.equ pIrIn = PINA ; IR receiver module port
.equ bIrIn = 0    ; IR receiver module pin
;
; ---------- Timing --------------------
; Controller clock        1.000.000 cs/s
; Time per clock cycle            1 us
; Prescaler TC1                   8
; Time per timer tick             8 us
; Long signal duration       30.000 us
; Long signal timer count, MSB   14
.equ cLong = 14 ; MSB of a long signal
;
; ---------- Registers ------------------
; Used: R0 by LCD routine, decimal conversion
; Used: R1 Decimal conversion
; free: R2..R14
.def rSreg = R15 ; Save/Restore SREG
.def rmp   = R16 ; Multi purpose register
.def rmo   = R17 ; Additional multi purpose
.def rLine = R18 ; LCD line counter
.def rRead = R19 ; LCD read register
.def rimp  = R20 ; Interrupt multi purpose
.def rFlag = R21 ; Flag register
   .equ bUpd = 0 ; Update flag
   .equ bShf = 1 ; Shift backwards flag
   .equ bLng = 2 ; Long high signal flag
; free: R22 .. R25
; Used: XH:XL R27:R26 for Shifting
; Used: YH:YL R29:R28 Pointer to SRAM
; Used: ZH:ZL R31:R30 in LCD routines
;
; ---------- SRAM ----------------------
.DSEG ; Assemble to data segment
.ORG 0x0060 ; Start address of SRAM
Buffer:
.byte 24 ; Buffer for receiver data
BufferEnd:
Last:
.byte 1 ; Last measured value
LastEnd:
;
; ---------- Reset and Interrupts -----
.CSEG ; Assemble to code segment
.ORG 0 ; At the beginning of flash memory
	rjmp Start ; Reset vector, Init
	reti ; INT0 External Interrupt Request 0
	rjmp Pci0Isr ; PCINT0 Pin Change Interrupt Request 0
	reti ; PCINT1 Pin Change Interrupt Request 1
	reti ; WDT Watchdog Time-out
	reti ; TIM1_CAPT Timer/Counter1 Capture Event
	reti ; TIM1_COMPA Timer/Counter1 Compare Match A
	reti ; TIM1_COMPB Timer/Counter1 Compare Match B
	reti ; TIM1_OVF Timer/Counter1 Overflow
	reti ; TIM0_COMPA Timer/Counter0 Compare Match A
	reti ; TIM0_COMPB Timer/Counter0 Compare Match B
	rjmp Tc0Isr ; TIM0_OVF TC0 Overflow, Update display
	reti ; ANA_COMP Analog Comparator
	reti ; ADC ADC Conversion Complete
	reti ; EE_RDY EEPROM Ready
	reti ; USI_STR USI START
	reti ; USI_OVF USI Overflow
;
; ---------- Interrupt Service Routines -----
Pci0Isr: ; PCINT0 ISR
	in rSreg,SREG ; Save SREG
	sbic pIrIn,bIrIn ; Skip next if IR input low
	rjmp Pci0Isr1 ; IR input is high
	; IR Input is low, was high before
.	in rimp,TCNT1L ; Read TC1 low byte first
	st Y,rimp ; Store LSB in SRAM
	in rimp,TCNT1H ; Read high byte second
	cpi rimp,cLong ; Long signal?
	brcs Pci0IsrNoLong ; Not a long signal
	sbr rFlag,1<<bLng ; Set long flag
.if FromBehind == 0 ; Backwards
	ldi YH,HIGH(Buffer) ; Y to buffer start
	ldi YL,LOW(Buffer)
	.endif
	rjmp Pci0Isr2 ; Done, clear TC1
Pci0IsrNoLong:
.if cHigh == 1 ; Only if high signal to be examined
	tst rimp ; test MSB
	brne Pci0IsrMsb ; MSB not zero
.if FromBehind == 1 ; Only if last signals to be examined
	; High signal 
	sbr rFlag,1<<bShf ; Set shift flag
	.else
    ; Only if first signals to be examined
	cpi YL,LOW(BufferEnd) ; End of buffer reached?
	brcc Pci0IsrNoInc ; Behind buffer end
	adiw YL,1 ; Before buffer end, inc pointer
Pci0IsrNoInc:
	.endif
Pci0IsrMsb: ; MSB not zero
	.endif
	rjmp Pci0Isr2 ; Done
Pci0Isr1:
	; IR input is high, was low before
.if cLow == 1 ; Only if low signals to be examined
	in rimp,TCNT1L ; Read TC1 low byte first
	st Y,rimp ; LSB to SRAM
	in rimp,TCNT1H ; Read TC1 high byte second
	tst rimp ; If MSB not zero, ignore
	brne Pci0IsrMsb1 ; ignore
.if FromBehind == 1 ; Only if last signals to be examined
	sbr rFlag,1<<bShf ; Set shift flag
	.else
	clr rimp ; Restart TC0
	out TCNT0,rimp
	cpi YL,LOW(BufferEnd) ; Beyond buffer?
	brcc Pci0IsrNoInc1 ; Yes, do not increase
	adiw YL,1 ; Increase pointer
Pci0IsrNoInc1:
	.endif
Pci0IsrMsb1:
	.endif
Pci0Isr2:
	ldi rimp,0 ; Restart TC1
	out TCNT1H,rimp ; First write MSB
	out TCNT1L,rimp ; Then LSB
	out SREG,rSreg ; Restore SREG
	reti ; Done
;
; TC0-Overflow Interrupt Service Routine
Tc0Isr:
	in rSreg,SREG ; Save SREG
	sbr rFlag,1<<bUpd ; Set update flag
	out SREG,rSreg ; Restore SREG
	reti
;
; ---------------- Start, Init --------------
Start:
	ldi rmp,LOW(RAMEND) ; Stack to RAMEND
	out SPL,rmp ; to stack pointer
	; Init I/O
	; Init LCD port outputs
	ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
	out pLcdCR,rmp ; to LCD control port
	clr rmp ; Outputs off
	out pLcdCO,rmp ; to control port outputs
	ldi rmp,mLcdDRW ; LCD data port output mask write
	out pLcdDR,rmp ; to direction port
	; Init LCD
	rcall LcdInit ; Call included LCD routine
	ldi ZH,HIGH(2*CodeChars) ; Special characters
	ldi ZL,LOW(2*CodeChars)
	rcall LcdChars ; Write to LCD
	ldi ZH,HIGH(2*LcdStart) ; Output start text
	ldi ZL,LOW(2*LcdStart)
	rcall LcdText ; Call included LCD routine
	; Start value for Buffer pointer
.if FromBehind == 1 ; Examine last signals
	ldi YH,HIGH(Last) ; To behind buffer end
	ldi YL,LOW(Last)
	.else 
	ldi YH,HIGH(Buffer) ; Y to buffer start
	ldi YL,LOW(Buffer)
	.endif
	; Start value for flags
	clr rFlag
	; Init TC0 for time out
	ldi rmp,(1<<CS02)|(1<<CS00) ; Prescaler 1024
	out TCCR0B,rmp ; Start timer
	; Init TC1 initiieren, free running
	ldi rmp,1<<CS11 ; Prescaler = 8
	out TCCR1B,rmp ; to timer control port B
	; PCINT0 for IR Rx
	ldi rmp,1<<PCINT0 ; Pin 0 Level change Ints
	out PCMSK0,rmo ; to mask 0
	ldi rmp,1<<PCIE0 ; Enable interrupt PCINT0
	out GIMSK,rmp ; in General interrupt mask
	; Sleep Enable
	ldi rmp,1<<SE ; Sleep mode idle
	out MCUCR,rmp ; to MCU control port
	; Enable interrupts
	sei ; Set I flag in SREG
Loop:
	sleep ; go to sleep
	nop ; Wake up
	sbrc rFlag,bShf ; Skip next if shift flag inactive
	rcall Shift ; Process shift flag
	sbrc rFlag,bLng ; Skip next if long flag inactive
	rcall Long ; Process long flag
	sbrc rFlag,bUpd ; Skip next if update flag inactive
	rcall Update ; Process update flag
	rjmp Loop ; go to sleep again
;
; Long wait signal detected
Long:
	cbr rFlag,1<<bLng ; Clear long flag
	clr rmp ; Clear TC0
	out TCNT0,rmp
	ldi rmp,1<<TOIE0 ; Enable overflow int
	out TIMSK0,rmp ; in TC0 interrupt mask
	ret
;
; Shift last value to buffer
Shift:
	cbr rFlag,1<<bShf ; Clear shift flag
	clr rmp ; Clear TC0
	out TCNT0,rmp
	ldi rmp,1<<TOIE0 ; Enable overflow interrupt
	out TIMSK0,rmp ; in TC0 interrupt mask
	ldi XH,HIGH(Buffer) ; X buffer pointer to start
	ldi XL,LOW(Buffer)
	ldi ZH,HIGH(Buffer+1) ; Z buffer pointer one byte higher
	ldi ZL,LOW(Buffer+1)
Shift1: ; Shift bytes backward
	ld rmp,Z+ ; Read byte one position forward
	st X+,rmp ; Write byte one position backward
	cpi ZL,LOW(LastEnd) ; End of buffer reached?
	brcs Shift1 ; no, go on
	ret
;
; Process update flag
Update:
	cbr rFlag,1<<bUpd ; Clear update flag
	clr rmp ; Disable TC0 overflow interrupt
	out TIMSK0,rmp ; in TC0 interrupt mask
	ldi rmp,1 ; Clear LCD
	rcall LcdC4Byte ; to LCD control port
	ldi ZH,HIGH(Buffer) ; Z to buffer start
	ldi ZL,LOW(Buffer)
Update1:
	cpi ZL,LOW(BufferEnd) ; Complete buffer displayed?
	brcc Update5 ; Yes, done
.if FromBehind == 1 ; Only if last bytes to be displayed
	ldi rmp,'e' ; Mark last value pair
	cpi ZL,LOW(BufferEnd-1) ; Last value pair?
	brcc Update2 ; Yes, 'e' marks last
	.endif
.if cHigh == 1
	; Examine cHigh is on
	.if cLow == 1 ; Both signals
		; cHigh=1, cLow=1
		ldi rmp,' ' ; Blank
		.else
		; cHigh=1, cLow=0
		ldi rmp,0x01 ; Set high mark
		.endif
	.else
	; cHigh=0
	ldi rmp,0x00 ; Set low mark
	.endif
Update2:
	rcall LcdD4Byte ; Display special character
	ld rmp,Z+ ; Read byte from buffer
	rcall Hex2Lcd ; Write in hex
	cpi ZL,LOW(Buffer+6) ; Line 1 full?
	brne UpDate3 ; Z<>6
	rcall LcdLine2 ; Set line 2
	rjmp Update1 ; Continue
Update3:
	cpi ZL,LOW(Buffer+12) ; Line 2 full?
	brne Update4
	rcall LcdLine3 ; Set line 3
	rjmp Update1 ; Continue
Update4:
	cpi ZL,LOW(Buffer+18) ; Line 3 full?
	brne Update1 ; no, continue
	rcall LcdLine4 ; Set line 4
	rjmp Update1
Update5:
	ldi rmp,0x0C ; Cursor and blink off
	rcall LcdC4Byte ; to LCD
	clr rmp ; Clear buffer
.if FromBehind == 1
	; Clear buffer backwards
	ldi ZH,HIGH(LastEnd) ; to buffer end
	ldi ZL,LOW(LastEnd)
Lang1:
	st -Z,rmp ; Clear buffer
	cpi ZL,LOW(Buffer+1) ; Already complete?
	brcc Lang1 ; no
	.else
	; Clear buffer forward
	ldi ZH,HIGH(Buffer) ; Z to buffer start
	ldi ZL,LOW(Buffer)
Lang1:
	st Z+,rmp ; Clear next byte
	cpi ZL,LOW(LastEnd) ; Already at buffer end?
	brcs Lang1 ; no, go on
	.endif
	ret
;
; Byte in rmp in hex to LCD
Hex2Lcd:
	push rmp ; Save byte
	swap rmp ; Upper nibble first
	rcall HexNibble2Lcd
	pop rmp ; Restore rmp
; Display nibble in hex on LCD
HexNibble2Lcd:
	andi rmp,0x0F ; Mask lower nibble
	subi rmp,-'0' ; Add ASCII-0
	cpi rmp,'9'+1 ; A to F?
	brcs HexNibble2Lcd1 ; no
	subi rmp,-7 ; Adjust A to F by adding 7
HexNibble2Lcd1:
	rjmp LcdD4Byte ; Display char in rmp on LCD
;
; Start text on LCD
LcdStart:
.db "IR analysis ATtiny24",0x0D,0xFF
.db "avr-asm-tutorial.net",0x0D,0xFF
.if FromBehind == 1
	.db "Short signals at end",0x0D,0xFF
	.else
	.db "Short signals start ",0x0D,0xFF
	.endif
.if cHigh == 1
	.if cLow ==1
	.db "Hex Hi",0x01,"/Lo",0x00," 8 us",0xFE,0xFE
	.else
	.db "Hex High",0x01,", 8 us",0xFE
	.endif
	.else
	.db "Hex Low",0x00,", 8 us",0xFE,0xFE
	.endif
;
; Special characters
CodeChars:
.db 64,0,0,0,0,0,4,4,6,0 ; Z = 0, Low signal
.db 72,0,0,0,0,0,5,7,5,0 ; Z = 1, High signal
.db 80,0,0,0,0,0,0,0,7,0 ; Z = 2, Pause separator
.db 0,0 ; End of table
;
; Include LCD routines
.include "Lcd4Busy.inc"
;
; End of source code
;

A new instruction used here is ST -Z register, which decreases Z before writing the register content to the SRAM cell that Z points to.

Examples

The code generates the following output on LCD. Hi/Lo signals of a TV remote control With both switches set the high as well as the low durations are measured and displayed. A blank between the signals marks that condition.

Hi/Lo signals at the end The same display with the last bytes received, the e signals the last byte.

Hi signals That is displayed if only the high signals are to be examined.

Hi signals at the end And this is the same for the last signals.
With this, any byte combinations can be examined.

12.4.6 Encoding of IR commands

With the previous methods the encoded keys of a remote control box can only be analyzed with extraordinary high efforts, and only for up to 48 bits. Therefore here is the more convenient solution for even more bits.

Program

The program is here, the source code file here. Of course the LCD routines are needed to assemble this.

;
; ********************************************************
; * IR receiver with ATtiny24 and LCD for burst analysis *
; * (C)2017 by http://www.avr-asm-tutorial.net           *
; ********************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ---------- Switches ------------------
.equ cActiveHi = 0 ; 1: High signals encode bits
;                    0: Low signals encode bits
;
; ---------- Constants -----------------
.equ cIRStart = 0x10 ; Pause before signal (MSB)
.equ cIRHigh  = 0x50 ; Zero/one discrimination level
;
; ---------- Ports ---------------------
.equ pIrIn = PINA ; IR receiver port
.equ bIrIn = 0    ; IR receiver pin
;
; ---------- Timing --------------------
; Controller clock        1.000.000 cs/s
; Time per clock cycle            1 us
; Prescaler TC1                   8
; Time per TC1 tick               8 us
;
; ---------- Registers -----------------
; Used: R0 by LCD, Decimal conversion
; Used: R1 Decimal conversion
.def rShift0 = R2 ; Shift register, byte 0
.def rShift1 = R3 ; dto., byte 1
.def rShift2 = R4 ; dto., byte 2
.def rShift3 = R5 ; dto., byte 3
.def rShift4 = R6 ; dto., byte 4
.def rShift5 = R7 ; dto., byte 5
.def rShift6 = R8 ; dto., byte 6
.def rShift7 = R9 ; dto., byte 7
; free: R10..R14
.def rSreg = R15 ; Save/Restore SREG
.def rmp   = R16 ; Multi purpose register
.def rmo   = R17 ; Another multi purpose register
.def rLine = R18 ; LCD line counter
.def rRead = R19 ; LCD read register
.def rimp  = R20 ; Interrupt multi purpose
.def rFlag = R21 ; Flag register
   .equ bInp = 0 ; Input value available
   .equ bPol = 1 ; Polarity (high/low)
   .equ bUpd = 2 ; Update after pause
   .equ bHOf = 3 ; Header data overflow
   .equ bDOf = 4 ; Data bits overflow
.def rHead = R22 ; Header signal counter
.def rData = R23 ; Bit counter register
; free: R24 .. R25
; Used: XH:XL R27:R26 for shifting
; Used: YH:YL R29:R28 Timer count
; Used: ZH:ZL R31:R30 in LCD routines
;
; ---------- SRAM ----------------------
.DSEG ; Data segment
.ORG 0x0060 ; Start of SRAM memory
Head:
.byte 6 ; Buffer for header data, MSB, LSB
HeadEnd:
StatisticLow:
.byte 3 ; Sum duration low signal, MSB/LSB/Number
StatisticHigh:
.byte 3 ; Sum duration high signal, MSB/LSB/Number
StatisticPause:
.byte 3 ; Sum duration pause, MSB/LSB/Number
StatisticEnd:
;
; ---------- Reset and Interrupts -----
.CSEG ; Assemble to code segment
.ORG 0 ; to the start
	rjmp Start ; Reset vector, Init
	reti ; INT0 External Interrupt Request 0
	rjmp Pci0Isr ; PCINT0 Pin Change Int Req 0
	reti ; PCINT1 Pin Change Interrupt Req 1
	reti ; WDT Watchdog Time-out
	reti ; TIM1_CAPT Timer/Counter1 Capture Event
	reti ; TIM1_COMPA Timer/Counter1 Compare Match A
	reti ; TIM1_COMPB Timer/Counter1 Compare Match B
	reti ; TIM1_OVF Timer/Counter1 Overflow
	reti ; TIM0_COMPA Timer/Counter0 Compare Match A
	reti ; TIM0_COMPB Timer/Counter0 Compare Match B
	rjmp Tc0Isr ; TIM0_OVF TC0 Overflow, update display
	reti ; ANA_COMP Analog Comparator
	reti ; ADC ADC Conversion Complete
	reti ; EE_RDY EEPROM Ready
	reti ; USI_STR USI START
	reti ; USI_OVF USI Overflow
;
; ---------- Interrupt Service Routines -----
Pci0Isr: ; PCINT0
	in rSreg,SREG ; Save SREG
	cbr rFlag,1<<bPol ; Clear polarity flag
	sbic pIrIn,bIrIn ; Skip next if input low
	; IR input is low, was high before
	sbr rFlag,1<<bPol ; Set polarity flag
	in YL,TCNT1L ; Read TC1, low byte first
	in YH,TCNT1H ; Then high byte
Pci0Isr2:
	ldi rimp,0 ; Restart TC1
	out TCNT1H,rimp ; First MSB
	out TCNT1L,rimp ; Then LSB
	sbr rFlag,1<<bInp ; Flag received signal
	out SREG,rSreg ; Restore SREG
	reti ; Done
;
; TC0-Overflow Interrupt Service Routine
Tc0Isr:
	in rSreg,SREG ; Save SREG
	sbr rFlag,1<<bUpd ; Set update flag
	out SREG,rSreg ; Restore SREG
	reti
;
; ---------------- Start, Init --------------
Start:
	ldi rmp,LOW(RAMEND) ; Stack to RAMEND
	out SPL,rmp ; to stack pointer
	; Init I/O
	; Init LCD port outputs
	ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
	out pLcdCR,rmp ; to LCD control port outputs
	clr rmp ; Outputs off
	out pLcdCO,rmp ; to LCD control port
	ldi rmp,mLcdDRW ; LCD data port mask write
	out pLcdDR,rmp ; to LCD data direction port
	; Init LCD
	rcall LcdInit ; Call to included LCD routines
	ldi ZH,HIGH(2*CodeChars) ; Special chars
	ldi ZL,LOW(2*CodeChars)
	rcall LcdChars ; to LCD
	ldi ZH,HIGH(2*LcdStart) ; Display start text
	ldi ZL,LOW(2*LcdStart)
	rcall LcdText
	; Start value for flags
	clr rFlag
	; Delete all content
	rcall Restart ; Restart values
	; Init timer TC0
	ldi rmp,(1<<CS02)|(1<<CS00) ; Prescaler 1024
	out TCCR0B,rmp ; Start timer
	; Init timer TC1, free running
	ldi rmp,1<<CS11 ; Prescaler to 8
	out TCCR1B,rmp ; to timer control port B
	; PCINT0 for IR Rx
	ldi rmp,1<<PCINT0 ; Pin 0 Level change Ints
	out PCMSK0,rmo ; to mask port 0
	ldi rmp,1<<PCIE0 ; Enable PCINT0 interrupt
	out GIMSK,rmp
	; Sleep Enable
	ldi rmp,1<<SE ; Sleep Mode Idle
	out MCUCR,rmp ; in MCU port
	; Enable interrupts
	sei ; Set I flag in SREG
Loop:
	sleep ; go to sleep
	nop ; Wake up
	sbrc rFlag,bInp ; Check input flag
	rcall Input ; Process input flag
	sbrc rFlag,bUpd ; Check update flag
	rcall Update ; Process update flag
	rjmp Loop ; sleep again
;
; Process input flag
Input:
	cbr rFlag,bInp ; Clear input flag
	mov XH,YH ; Copy counter
	mov XL,YL
	; Clear timer TC0
	ldi rmp,0 ; Clear timer TC0
	out TCNT0,rmp
	ldi rmp,1<<TOIE0 ; Enable timer overflow ints
	out TIMSK0,rmp
	tst XH ; MSB not zero?
	breq Input3 ; MSB = zero
	; MSB unequal zero
	cpi XH,cIRStart ; MSB larger than start condition?
	brcs Input1 ; no
	rcall Restart ; Clear all
Input1:
	inc rHead ; Next header count
	cpi rHead,4 ; More than three headers?
	brcs Input2 ; No
	sbr rFlag,1<<bHOf ; Set header overflow flag
	ret
Input2:
	ldi ZH,HIGH(Head) ; Point Z to header info
	ldi ZL,LOW(Head)
	lsl rHead ; Multiply by two
	add ZL,rHead ; Add to pointer
	ldi rmp,0 ; MSB overflow?
	adc ZH,rmp ; Add carry to MSB
	lsr rHead ; Multiply by four
	st Z+,XH ; To header storage
	st Z,XL
	ret
Input3:
.if cActiveHi == 1 ; If input is active high
	sbrc rFlag,bPol ; Skip if polarity is normal
	.else
	sbrs rFlag,bPol ; Skip if polarity is reverse
	.endif
	rjmp Input5 ; Low signal/high signal
	; Input was active, collect data bits
	ldi rmp,cIRHigh ; Load low-high-level
	cp rmp,XL ; Smaller: C clear, larger: C set
	rol rShift0 ; Roll carry into shift register
	rol rShift1
	rol rShift2
	rol rShift3
	rol rShift4
	rol rShift5
	rol rShift6
	rol rShift7
	inc rData ; Count data bits
	cpi rData,66 ; 66 bits are fine
	brcs Input4 ; Fine
	sbr rFlag,1<<bDOf ; Set data bits overflow flag
Input4:
	; Add to sum
	ldi ZH,HIGH(StatisticLow) ; Point Z to low
	ldi ZL,LOW(StatisticLow)
	cp rmp,XL ; Again result to carry flag
	brcc Input6 ; Carry = zero
	ldi ZH,HIGH(StatisticHigh) ; Point Z to high
	ldi ZL,LOW(StatisticHigh)
	rjmp Input6 ; Continue
Input5:
	; Input is reverse polarity
	ldi ZH,HIGH(Statisticpause) ; Point Z to pause
	ldi ZL,LOW(Statisticpause)
Input6:
	ldd rmp,Z+1 ; Load LSB
	add rmp,XL ; Add LSB
	std Z+1,rmp ; and store
	ld rmp,Z ; Load MSB
	adc rmp,XH ; Add MSB
	st Z,rmp ; and store
	adiw ZL,2 ; Point to number counter
	ld rmp,Z ; Load number counter
	inc rmp ; Increase
	st Z,rmp ; and store
	ret
;
; Process update flag
Update:
	cbr rFlag,1<<bUpd ; Clear update flag
	clr rmp ; Disablöe TC0 ints
	out TIMSK0,rmp ; in TC0 interrupt mask
	ldi rmp,0x01 ; Clear display
	rcall LcdC4Byte
	ldi ZH,HIGH(2*LcdMask) ; Show mask on display
	ldi ZL,LOW(2*LcdMask)
	rcall LcdText
Update1:
	; Display head data
	ldi ZH,0 ; LCD position head
	ldi ZL,5
	rcall LcdPos ; Set LCD position
	ldi ZH,HIGH(Head) ; Point to head data
	ldi ZL,LOW(Head)
	clr R0 ; Polarity character low
Update2:
	ldi rmp,0x01 ; Revert polarity character
	eor R0,rmp ; Exor bit 0
	mov rmp,R0 ; Copy to rmp
	rcall LcdD4Byte ; Display on LCD
	ld rmp,Z+ ; Read MSB
	rcall Hex2Lcd ; Display hex on LCD
	ld rmp,Z+ ; Read LSB
	rcall Hex2Lcd ; Display hex on LCD
	cpi ZL,LOW(HeadEnd) ; All header info?
	brcs Update2 ; No, go on
	sbrs rFlag,bHOf ; Skip next if header overflow
	rjmp Update3 ; Display header
	ldi ZH,0 ; Display overflow message on line 1
	ldi ZL,17
	rcall LcdPos
	ldi rmp,' ' ; Blank
	rcall LcdD4Byte
	ldi rmp,'O' ; Large O
	rcall LcdD4Byte
	ldi rmp,'!' ; Error
	rcall LcdD4Byte
Update3: ; Output bit sums and numbers	
	ldi ZH,1 ; Line 2
	ldi ZL,2
	rcall LcdPos ; Set Lcd position
	ldi ZH,HIGH(StatisticLow) ; Point Z to statistic
	ldi ZL,LOW(StatisticLow)
	ld rmp,Z+ ; Read MSB sum
	rcall Hex2Lcd ; Display hex on Lcd
	ld rmp,Z+ ; Read LSB sum
	rcall Hex2Lcd ; Display hex on Lcd
	ldi rmp,' ' ; Write blank
	rcall LcdD4Byte ; to Lcd
	ld rmp,Z+ ; Read number of signals
	rcall Hex2Lcd ; Write hex on Lcd
	ldi rmp,' ' ; Add blank
	rcall LcdD4Byte
	ldi rmp,'1' ; Write 1: on Lcd
	rcall LcdD4Byte
	ldi rmp,':'
	rcall LcdD4Byte
	ld rmp,Z+ ; Read MSB 1
	rcall Hex2Lcd ; Write hex on Lcd
	ld rmp,Z+ ; Read LSB 1
	rcall Hex2Lcd ; Write hex on Lcd
	ldi rmp,' ' ; Add blank
	rcall LcdD4Byte
	ld rmp,Z+ ; Read number 1
	rcall Hex2Lcd ; Write hex on Lcd
	ldi ZH,2 ; Lcd to line 3
	ldi ZL,2
	rcall LcdPos
	ldi ZH,HIGH(Statisticpause) ; Point to pause
	ldi ZL,LOW(Statisticpause)
	ld rmp,Z+ ; Read MSB pause
	rcall Hex2Lcd ; Write hex on Lcd
	ld rmp,Z+ ; Read LSB pause
	rcall Hex2Lcd ; Write hex on Lcd
	ldi rmp,' ' ; Add blank
	rcall LcdD4Byte
	ld rmp,Z+ ; Read number of pauses
	rcall Hex2Lcd ; Write hex on Lcd
	; Shift registers on line 4
	ldi ZH,3 ; LCD to line 4
	ldi ZL,2
	rcall LcdPos
	ldi ZH,0 ; Point Z to register R10
	ldi ZL,10
Update4:
	ld rmp,-Z ; Decrease Z and read byte
	rcall Hex2Lcd ; Display on Lcd in hex
	cpi ZL,3 ; All displayed?
	brcc Update4 ; No, go on
	sbrs rFlag,bDOf ; Bit overflow?
	rjmp Update5 ; No, continue below
	ldi rmp,'O' ; Signal an overflow with O!
	rcall LcdD4Byte
	ldi rmp,'!'
	rcall LcdD4Byte
Update5:
	ldi rmp,0x0C ; Cursor and blink off
	rcall LcdC4Byte ; to LCD
;
; Restart
Restart:
	ser rHead ; Number of heads to 0xFF
	clr rData ; Clear data bit counter
	cbr rFlag,(1<<bHOf)|(1<<bDOf) ; Clear flags
	ldi ZH,0 ; Clear shift registers, point Z to R2
	ldi ZL,2
Restart1:
	st Z+,rData ; Clear register
	cpi ZL,10 ; All cleared?
	brcs Restart1 ; No, continue
	ldi ZH,HIGH(Head) ; Clear SRAM content
	ldi ZL,LOW(Head)
Restart2:
	st Z+,rData ; Write zero
	cpi ZL,LOW(StatisticEnd) ; All cleared?
	brcs Restart2 ; No, go on
	ret
;
; Byte in rmp in hex on LCD
Hex2Lcd:
	push rmp ; Save byte
	swap rmp ; Upper nibble first
	rcall HexNibble2Lcd
	pop rmp ; Restore byte
; Display nibble in hex on LCD
HexNibble2Lcd:
	andi rmp,0x0F ; Mask lower nibble
	subi rmp,-'0' ; Add ASCII-0
	cpi rmp,'9'+1 ; A to F?
	brcs HexNibble2Lcd1 ; no
	subi rmp,-7 ; Adjust to A to F
HexNibble2Lcd1:
	rjmp LcdD4Byte ; Display rmp on LCD
;
; Start text LCD
LcdStart:
.db "IR analysis ATtiny24",0x0D,0xFF
.db "avr-asm-tutorial.net",0x0D,0xFF
.db "Measuring IR signals",0x0D,0xFF
.db " in hex * 8 us ",0xFE
;
; Output mask for measured data
LcdMask:
.db "Head:",0x0D
;         5
.db "0:xxxx nn 1:xxxx nn ",0x0D,0xFF
;      2
.db "P:xxxx nn",0x0D
;      2
.db "D:xxxxxxxxxxxxxxxx ",0xFE
;      2
;
; Special chars
CodeChars:
.db 64,0,0,0,0,0,4,4,6,0 ; Z = 0, Low signal
.db 72,0,0,0,0,0,5,7,5,0 ; Z = 1, High signal
.db 0,0 ; End of table
;
; Include LCD routines
.include "Lcd4Busy.inc"
;
; End of source code
;

Two new instructions here are Another thing that is new here is the application of pointer registers to access registers. If the pointer X, Y or Z is smaller than 32, it accesses the registers R0 to R31. The instructions LD and ST work with this. If the pointer points to loactions 0x20 to 0x5F, the LD and ST instructions work on port numbers (add 0x20 to the original port number to get the pointer value). That is why SRAM starts at 0x0060, in larger devices with more ports even beyond. Use the constant RAMSTART to find out where SRAM in the device starts (it is listed in the def.inc file for the device) or consult the databook.

Examples

Bits of a TV This is the result from my TV remote control box (German version). This generates more than three header signals ("U!" in the first line signals an overflow). But for analyzing header signals we already have a better tool. From the sums of low and high bits and combined with their number we can calculate averages.

Even more helpful is the databit examination behind D:. Here, the data bits encoding the different keys can be read in hex format.

Key table This is such a table with the keys encoded by my TV remote control box. It is obvious that the devellopper of that encoding follows its own rules.

I wish you more luck when exploring your own black boxes, the necessary tools are available here.


Home Top IR Conditioned Hardware Measuring Transmit Receive Switch


12.5 An IR transmitter

To not only being able to use existing remote control boxes, we built our own IR transmitter, design our own encoding protocol and our own norm. Because we have an ATtiny13 from the last experiments, we use this for our transmitter.

12.5.1 The hardware for IR transmitting

Scheme

IR transmitter ATtiny13 That is all we need: an ATtiny13, a key to start the signal and an infrared LED. We avoid driving our IR LED with full power (at 100 mA) because in that case the current that can be driven by an output pin of an ATtiny13 would be exceeded. And the ISP interface would be overloaded with current and we would have to unmount the LED during ISP programming. This reduces our IR signal strength, but works fine.

IR transmitter ATtiny13 with driver Who wants to drive the IR LED with its full power and to use the ISP interface, use this driver with a PNP transistor. An NPN would reverse the polarity of the signal and a few changes to the program would be necessary (to clear the output pin to shut the LED off).

The IR LED

IR LED This is an infrared LED. At nominal 100 mA current it has a voltage drop of 1.35 V. The current limiting resistor at 5 V for a current of 73 mA that the ATtiny13 output pin can drive at maximum is

R [Ohm] = 1000 * (5 - 1,35 - 2 [V]) / 73 [mA] = 23 [Ohm]


If a transistor driver is used and 100 mA are desired the resistor is:

R [ohm] = 1000 * (5 - 1,35 - 0,2 [V]) / 100 [mA] = 35 [Ohm]


68 Ohm resistor

68 Ohm This is the 68 Ω resistor. With this the driver current is

I [mA] = 1000 * (5 - 1,35 - 1,0 [V]) / 68 = 39 [mA]

which is compatible with the ISP programming hardware.

Mounting

Mounting transmitter This here is the simple mounting of the hardware on the breadboard.

12.5.2 Transmitter algorithms

IR transmit signal

For transmitting we use the timer TC0 in the ATtiny13 in CTC mode. When reaching its top level the OC0A output is either set (LED is off) or toggled (LED switched on and off).

The whole timing of the software centers around the generation of the 40 kcs/s signal of the LED. By counting the number of TOP events in the TIM0-COMPA interrupts of the timer the duration of the LED signal is controlled. As there are very long durations are to be generated (header signals) a 16 bit counter is necessary.

At a clock frequency of 1.2 Mcs/s and a LED frequency of 40 kcs/s the two on-and-off signal interrupts occur in 1,200,000 / 40,000 / 2 = 15 clock cycles. The triggering of the interrupt (pushing the current address to the stack, jumping to the vector address) requires four clock cycles, the jump from the vector address to the interrupt service routine requires two clock cycles. This leaves 15 - 4 - 2 = 9 clock cycles for the interrupt service routine. The further execution in the interrupt service routine would be:

TC0CAIsr:
	in R15,SREG ; Save SREG
	sbiw R24,1 ; Count down
	brne TC0CAIsrRet ; Not yet zero
	; [... further code ...]
TC0CAIsrRet:
	out SREG,R15 ; Restore SREG
	reti ; Done, return

The timing of that routine is as follows:

TC0CAIsr: ; 4 for Int, 2 for vector jump = 6
	in R15,SREG ; +1 = 7
	sbiw R24,1 ; +2 = 9
	brne TC0CAIsrRet ; +2 = 11 for branching
	; [... further code ...]
TC0CAIsrRet: ; 11 clock cycles
	out SREG,R15 ; +1 = 12
	reti ; +4 = 16

The interrupt service routine in the normal run (counter is not zero) is therefore 16 clock cycles long and the next COMPA interrupt request is initiated before the execution of the routine ends. For this, and for further operations, not enough time is available. At the default clock frequency this does not work.

A clock frequency of 2.4 Mcs/s, that can be selected by writing to the CLKPR port, is sufficient. Even if, within the service routine, further operations such as switching from toggle to set OC0A or setting flags have to be performed, the available 30 clock cycles offers enough time.

Control of the signal duration

The control of signal duration works with a 16 bit counter. It makes sense to use the register pair R25:R24 for that purpose: this pair can be downcounted with SBIW R24,1 but it cannot be used as a pointer.

Each downcount of this counter stands, at 40 kcs/s, for 12.5 µs. For a wait signal in the header 65536 * 12.5 = 819,200 µs are possible which provides enough range.

If we specify signal durations in µs and we want to know the counter value we have to multiply those by 2 and divide those by 25. We should do this conversion in the assembler source instead of doing that with the controller.

Our transmitter will be able to transmit up to eight header signals (four active, four inactive). To be able to define the duration for each of those signals we program those durations in short routines, place a table with the addresses of those routines to the flash, read those addresses using LPM and jump with the instruction IJMP to that routine. This indirect jump jumps to the address that the Z register pair points to. This enables us to calculate jump addresses and to jump very fast to the target address.

12.5.3 Program for transmitting

The following program can serve as a flexible base for own formulations and formats. It can be adjusted to fit to any need with very small changes. Each key stroke starts two transmitter sequences (bursts), separated by a long inactive phase in between.

During ISP transfer of the assembled code the IR LED shall be disconnected to avoid error messages due to the high current.

The program is listed below, the source code is here.

;
; ********************************************
; * IR transmitter 40 kcs/s with an ATtiny13 *
; * (C)2017 by www.avr-asm-tutorial.net      *
; ********************************************
;
.NOLIST
.INCLUDE "tn13def.inc"
.LIST
;
; -------- Registers -------------
; free: R0 .. R5
.def rData0 = R6 ; Transmit bits, highest, 1 to 8
.def rData1 = R7 ; dto., next lower, 9 to 16
.def rData2 = R8 ; dto., next lower, 17 to 23
.def rData3 = R9 ; dto., next lower, 24 to 31
.def rData4 = R10 ; dto., next lower, 32 to 39
.def rData5 = R11 ; dto., next lower, 40 to 47
.def rData6 = R12 ; dto., next lower, 48 to 55
.def rData7 = R13 ; dto., next lower, 56 to 63
.def rEor = R14 ; for polarity reversion Clear/Toggle
.def rSreg = R15 ; Save SREG
.def rmp = R16 ; Multi purpose register
.def rimp = R17 ; Multi purpose inside int
.def rFlag = R18 ; Flag register
	.equ bRun = 0 ; Transmit sequence is active
	.equ bSta = 1 ; Start transmit
	.equ bTTo = 2 ; Time out during transmit
	.equ bRst = 3 ; Second burst
.def rTOC = R19 ;  Time out counter, counts signals
; free R20 .. R23
.def rCntL = R24 ; Counts CTC compares
.def rCntH = R25
; Used: XH:XL R27:R26
; free: YH:YL R29:R28
; Used: ZH:ZL R31:R30 Read from flash, IJMP
;
; -------- Constants and Timing ------
.equ cClock = 2400000 ; Controller clock
.equ cClockNs = 1000000000 / cClock ; Clock in ns
.equ cIRF = 40000 ; IR transmit frequency cs/s
.equ cIRNs = 1000000000 / cIRF / 2 ; IR TX in ns
.equ cCtc = (cIRNs+cClockNs/2) / cClockNs - 1 ; CTC cycles
; @9.6 Mcs/s: 120; @4.8 Mcs/s: 60; @2.4 Mcs/s: 30
; @1.2 MHz: 15(!!), faster than ISR execution!!
;
; -------- Structure IR signal -----------
; All times in us
; Number of header pairs, 1 .. 4
.equ nHead = 4 ; Number of header pairs H/L, 1..4
.equ cHead = 2*nHead-1 ; Number of header signals
; Duration of header signals (duration in 2*us/25)
.equ cTH1 = 2*50000/25 ; Duration High 1
.equ cTH2 = 2*5000/25 ; Duration Low 1
.equ cTH3 = 2*2500/25 ; Duration High 2
.equ cTH4 = 2*500/25 ; Duration Low 2
.equ cTH5 = 2*1250/25 ; Duration High 3
.equ cTH6 = 2*500/25 ; Duration Low 3
.equ cTH7 = 2*1250/25 ; Duration High 3
.equ cTH8 = 2*500/25 ; Duration Low 3
.equ cTH9 = 2*1250/25 ; Duration High 4
.equ cTH10 = 2*500/25 ; Duration Low 4
; Number of data bits to be transmitted
.equ cBits = 64 ; Number of bits, 1 .. 64
.equ cSign = cHead + 2*cBits ; Total number of signals
; Duration of data bits and active period
.equ cShort = 2*250/25 ; Duration binary zero
.equ cLong = 2*750/25 ; Duration binary one
.equ cPaus = 2*250/25 ; Duration active period
;
; -------- Transmit data ------ 
; Test data to transmit
.equ cWord1 = 0xAAAA ; Highest
.equ cWord2 = 0x5555
.equ cWord3 = 0xAAAA
.equ cWord4 = 0x5555 ; Lowest
;
; -------- Reset and interrupt vectors ---
.CSEG
.ORG 0
	rjmp Start ; Reset vector, program init
	rjmp Int0Isr ; INT0 Key interrupt
	reti ; PCINT0 Pin Change Int Request 0
	reti ; TIM0_OVF Timer/Counter Overflow
	reti ; EE_RDY EEPROM Ready
	reti ; ANA_COMP Analog Comparator
	rjmp TC0CAIsr ; TIM0_COMPA TC0 Compare A
	reti ; TIM0_COMPB TC0 Compare Match B
	reti ; WDT Watchdog Time-out
	reti ; ADC Conversion Complete
;
; -------- Interrupt Service Routines -----
Int0Isr: ; Key interrupt, starts transmit sequence
	sbrc rFlag,bRun ; Skip if not yet running
	reti ; Already running, ignore
	in rSreg,SREG ; Save SREG
	sbr rFlag,(1<<bRun)|(1<<bSta) ; Set flags
	out SREG,rSreg ; Restore SREG
	reti ; Return
; CTC-Timeout Timer, switch IR LED
TC0CAIsr: ; 6 cycles for int and vector rjmp
	in rSreg,SREG ; Save SREG, +1 = 7
	sbiw rCntL,1 ; CTC count down, +2 = 9
	brne Tc0CAIsrRet ; Not zero, +1/2 = 10/11
	sbr rFlag,1<<bTTO ; Set timeout flag, +1 = 11
	in rimp,TCCR0A ; Read toggle bit, +1 = 12
	eor rimp,rEor ; Reverse, +1 = 13
	out TCCR0A,rimp ; Write new toggle bit, +1 = 14
TC0CAIsrRet: ; 11/14
	out SREG,rSreg ; Restore SREG, +1 = 12/15
	reti ; Done, +4 = 16/19
;
; --------- Program start, Init -------
Start:
	; Stack init
	ldi rmp,LOW(RAMEND) ; To RAMEND
	out SPL,rmp ; to stack pointer
	; Clock to 2.4 Mcs/s
	ldi rmp,1<<CLKPCE ; CLK-Enable
	out CLKPR,rmp ; to clock prescaler port
	ldi rmp,1<<CLKPS1 ; Prescaler = 4
	out CLKPR,rmp ; to clock prescaler port
	; Init LED port
	sbi PORTB,PORTB0 ; LED off
	sbi DDRB,DDB0 ; Pin is output
	; Key port pin with pull-up
	sbi PORTB,PORTB1 ; Pull-up on
	; Set start value
	clr rFlag ; Clear flags
	ldi rmp,1<<COM0A1 ; Toggle reverse bit
	mov rEor,rmp ; to exor register
	; Start TC0 as CTC
	ldi rmp,cCtc ; Compare A value 12.5 us
	out OCR0A,rmp ; to compare A port
	; Set OC0A, CTC mode
	ldi rmp,(1<<COM0A1)|(1<<COM0A0)|(1<<WGM01)
	out TCCR0A,rmp ; to timer 0 control port A
	ldi rmp,1<<CS00 ; Precaler = 1
	out TCCR0B,rmp ; to timer 0 control port B
	; INT0 and sleep mode
	ldi rmp,(1<<SE)|(1<<ISC01) ; Sleep idle, INT0
	out MCUCR,rmp ; to master control port
	ldi rmp,1<<INT0 ; Enable INT0 interrupt
	out GIMSK,rmp ; in general interrupt mask
	; Enable interrupts
	sei ; Set I flag in SREG
	; for simulation
	; cbi PORTB,PORTB1 ; INT0 ausloesen
	; sbi PORTB,PORTB1
;
; -------- Program loop ----------
Loop:
	sleep ; go to sleep
	nop ; After wake up
	sbrc rFlag,bTTo ; Time out?
	rcall Timeout ; Process time out
	sbrc rFlag,bSta ; Start flag?
	rcall StartOut ; Process transmit start
	rjmp Loop ; go to sleep again
;
; -------- CTC time out --------------- 
TimeOut: ; Flag was set
	cbr rFlag,1<<bTTo ; Clear time out flag, 1
	inc rTOC ; Next transmit phase, 2
	cpi rTOC,cHead ; Send header?, 3
	brcc TimeOut1 ; No, send data bits, 4/5
	ldi ZH,HIGH(TOH2) ; MSB address head, 6
	ldi ZL,LOW(TOH2) ; dto, LSB, 7
	mov rmp,rTOC ; Copy 8
	lsl rmp ; Multiply by two, 9
	add rmp,rTOC ; Plus one, 10
	add ZL,rmp ; Add to address, 11
	ldi rmp,0 ; Overflow adder, 12
	adc ZH,rmp ; Add overflow, 13
	ijmp ; Jump to address in Z, 15
TimeOut1: ; 5
	cpi rTOC,cSign ; All sent?, 6
	brcc TimeOutEnd ; Yes, finalize, 7/8
	in rmp,TCCR0A ; Read toggle/set bit, 8
	sbrc rmp,COM0A1 ; Check toggle on, 9/10
	rjmp TimeOut2 ; Toggle is off, 11
	; Toggle is on
	ldi rCntH,High(cPaus) ; Load pause duration, 11
	ldi rCntL,Low(cPaus) ; 12
	ret ; Done, 16
TimeOut2: ; Bit transmit, 11
	ldi rCntH,High(cShort) ; Load short bit, 12
	ldi rCntL,Low(cShort) ; 13
	lsl rData7 ; Shift bits, 14
	rol rData6 ; 15
	rol rData5 ; 16
	rol rData4 ; 17
	rol rData3 ; 18
	rol rData2 ; 19
	rol rData1 ; 20
	rol rData0 ; 21
	brcc TimeOut3 ; Bit is zero, 22/23
	ldi rCntH,High(cLong) ; Load long bit, 23
	ldi rCntL,Low(cLong) ; 24
TimeOut3: ; 23/24
	ret ; done, 27/28
TimeOutEnd: ; 8
	sbrs rFlag,bRst ; Skip next if restart flag set, 9/10
	rjmp Restart ; Restart again, 11
	clr rmp ; Disable timer int, 11
	out TIMSK0,rmp ; 12
	; Output OC0A to high
	ldi rmp,(1<<COM0A1)|(1<<COM0A0)|(1<<WGM01) ; 13
	out TCCR0A,rmp ; 14
	cbr rFlag,(1<<bRun)|(1<<bRst) ; Clear flags, 15 
	ret ; Done, 19
;
; Timeout header, set duration
; (All routines must be three instructions long because the
;  target address is calculated!)
TOH2: ; 15
	ldi rCntH,High(cTH2) ; 16
	ldi rCntL,Low(cTH2) ; 17
	ret ; 21
;
	ldi rCntH,High(cTH3)
	ldi rCntL,Low(cTH3)
	ret
;
	ldi rCntH,High(cTH4)
	ldi rCntL,Low(cTH4)
	ret
;
	ldi rCntH,High(cTH5)
	ldi rCntL,Low(cTH5)
	ret
;
	ldi rCntH,High(cTH6)
	ldi rCntL,Low(cTH6)
	ret
;
	ldi rCntH,High(cTH7)
	ldi rCntL,Low(cTH7)
	ret
;
	ldi rCntH,High(cTH8)
	ldi rCntL,Low(cTH8)
	ret
;
; Restart: Start a burst
Restart: ; 11
	clr rmp ; Disable timer int, 12
	out TIMSK0,rmp ; 13
	sbr rFlag,1<<bRst ; Set restart flag, 14
	rjmp StartOut1 ; 16
;
StartOut:
	cbr rFlag,(1<<bSta)|(1<<bRst) ; Clear flags
StartOut1:
	ldi ZH,High(2*DataTable)
	ldi ZL,Low(2*DataTable)
	ldi XH,0 ; Point X to register 14
	ldi XL,14
StartOut2:
	lpm rmp,Z+ ; Read table value
	st -X,rmp ; Decrement and store in register
	cpi XL,7 ; Already at register R6?
	brcc StartOut2 ; No, go on
	ser rToC ; Counter to 0xFF
	ldi rCntH,High(cTH1) ; First header duration
	ldi rCntL,Low(cTH1)
	ldi rmp,1<<OCIE0A ; Enable timer int
	out TIMSK0,rmp
	ret
;
; DataTable
DataTable:
.dw cWord4,cWord3,cWord2,cWord1
;
; End of source code
;

The only new instruction is IJMP, a jump to the address in the register pair Z.

12.5.4 Analysis of the transmitted signals

Diagnose transmit That is what the ATtiny24 has understood from those transmitted signals (German version). The first two header signals with 0x1901 and 0x0288 (51,208 resp. 5,184 µs) are slightly too long. The number of header signals is four, so the header overflow warning is correct.

The data signals at zero show a sum of 0x036A (=874) for 32 signals, for 32 ones a sum of 0x0CC3. This corresponds to 219 µs resp. 769 µs. The shorter zeroes are slightly too short (should be 250), the longer ones are slightly too long (should be 750). Both can be caused by the filter delays of the receiver modules and is tolerable. The total number of signal pairs is 68 and this is correct.

All eight data bytes were correctly identified.


Home Top IR Conditioned Hardware Measuring Transmit Receive Switch


12.6 An IR data transfer system

The system consists of two parts:
  1. An IR transmitter with an ATtiny13, on which a potentiometer is attached. If the adjusted value changes or if a predefined time is over, the ATtiny13 transmits the 10 bit result in two 16 bit bursts.
  2. An IR receiver with an ATtiny24 and an attached LCD. Received IR burst signals are analyzed and, if correct, the 10 bit result is converted to a percentage and displayed. If errors occur respective messages are displayed.

12.6.1 The IR analog data transmitter

Scheme

Scheme analog transmitter This here generates the transmit signals. The IR LED on OC0A produces the transmit signal. An additional red LED is on during transmit. The key starts a transmit sequence manually. The potentiometer signal is equipped with a capacitor of 22 nF that suppresses small changes on the ADC input, to reduce the number of transmit bursts.

Components

Capacitor 22 nF This is the capacitor.

Mounting

Mounting the analog IR transmitter Mounting is not very complicated.

Software

The software is based on the previous template, unnecessary parts were removed. The following lists the software, the source code is here. The only parameter that can be adjusted is the time delay of the default transmit sequence (cAdc10min). This constant is set to 10 seconds and can be increased to up to 3 hours.

;
; ************************************************************
; * IR transmit an analog value with 40 kcs/s on an ATtiny13 *
; * (C)2017 by www.avr-asm-tutorial.net                      *
; ************************************************************
;
.NOLIST
.INCLUDE "tn13def.inc"
.LIST
;
; -------- Registers -------------
; free: R0 .. R8
.def r10mL = R9 ; 10 minute counter
.def r10mM = R10
.def r10mH = R11
.def rData0 = R12 ; Transmit register, upper bits
.def rData1 = R13 ; Transmit register, lower bits
.def rEor = R14 ; For Polarity reversion Toggle/Set OC0A
.def rSreg = R15 ; Save/Restore SREG
.def rmp = R16 ; Multi purpose register
.def rimp = R17 ; Multi purpose inside ints
.def rFlag = R18 ; Flag register
	.equ bRun = 0 ; Transmit sequence running
	.equ bSta = 1 ; Start transmit
	.equ bTTo = 2 ; Time out during transmit
	.equ bRst = 3 ; Restart for second run
	.equ bAdc = 4 ; Analog value complete
.def rTOC = R19 ;  Time out counter, counts signals
.def rAnaL = R20 ; Current analog value
.def rAnaH = R21
.def rSntL = R22 ; Last transmitted analog value
.def rSntH = R23
.def rCntL = R24 ; Counts CTC compares
.def rCntH = R25
; Used: XH:XL R27:R26
; free: YH:YL R29:R28
; Used: ZH:ZL R31:R30
;
; -------- Ports and pins -------------
.equ pOut = PORTB ; LED/key output port
.equ pDir = DDRB ; LED/key direction port
.equ pIn  = PINB ; Key input port
.equ bIrO = PORTB0 ; IR LED output pin
.equ bIrD = DDB0 ; IR LED direction pin
.equ bIrI = PINB0 ; IR LED input pin
.equ bLdO = PORTB2 ; Red LED output pin
.equ bLdD = DDB2 ; Red LED direction pin
.equ bKyO = PORTB1 ; Key output pin for INT0
;
; -------- Constants and timing ------
.equ cClock = 2400000 ; Prozessortakt
.equ cClockNs = 1000000000 / cClock ; Takt ns
.equ cIRF = 40000 ; IR-Sendefrequenz Hz
.equ cIRNs = 1000000000 / cIRF / 2 ; IR-TX ns
.equ cCtc = (cIRNs+cClockNs/2) / cClockNs - 1
; @2.4 Mcs/s: 30
;
; -------- Auto transmit and ADC --------
; Clock               2400000 cs/s
; ADC prescaler           128
; ADC measure cycles       13
; ADC measure frquency   1442 cd/s
; Seconds per 10 minutes  600
.equ cAdc10min = 1442*600 ; Three bytes
.equ cAdc10sec = 1442*10
;
; -------- Structure IR signal -----------
; All times in us
; Number of header signals, 1 .. 4
.equ nHead = 2 ; Number of header pairs H/L
.equ cHead = 2*nHead-1 ; Number of header signals
; Duration of header signals (Duration in 2*us/25)
.equ cTH1 = 2*50000/25 ; Duration High 1
.equ cTH2 = 2*5000/25 ; Duration Low 1
.equ cTH3 = 2*2500/25 ; Duration High 2
.equ cTH4 = 2*500/25 ; Duration Low 2
; Number of data bits to send
.equ cBits = 16 ; Number of bits
.equ cSign = cHead + 2*cBits ; Total number of signals
; Duration of data bits and active periods
.equ cShort = 2*250/25 ; Duration binary zero
.equ cLong = 2*750/25 ; Duration of binary one
.equ cPaus = 2*250/25 ; Duration of active periods
;
;   --- --- --- --- --- --- --- --- ... --- --- --- ... ---
;  |TH1|TH2|TH3|TH4|B16|P16|B15|P15|...|B00|P00|TH1|...|P00|
;   --- --- --- --- --- --- --- --- ... --- --- --- ... ---
;IR Off On  Off On  Off On  Off On  ... Off On  Off ... On
; Milliseconds, all data bits zero:
;  50.0    57.5    58.25   58.75    ...73.25   123.5
;      55.0    58.0    58.5     59.0       73.5     ... 147
; Milliseconds, all data bits one:
; 50.0    57.5    58.75   59.75    ... 88.75   139
;     55.0    58.0    59.0     60.0       89.0      ... 178
; Duty cycle = 2*(5 + 0,5 + 16*0.25)/2 = 9.5 ms, 6 resp. 5%
; Current cons. LED: I = 39 mA, E = 39*9.5/1000/3600 = 0.11uAh
;                    I = 100mA, E = 100*9.5/1000/3600= 0.26uAh
;
; -------- Reset and interrupt vectors ---
.CSEG
.ORG 0
	rjmp Start ; Reset vector, program init
	rjmp Int0Isr ; INT0 Key interrupt
	reti ; PCINT0 Pin Change Int Request 0
	reti ; TIM0_OVF Timer/Counter Overflow
	reti ; EE_RDY EEPROM Ready
	reti ; ANA_COMP Analog Comparator
	rjmp TC0CAIsr ; TIM0_COMPA TC0 Compare A
	reti ; TIM0_COMPB TC0 Compare Match B
	reti ; WDT Watchdog Time-out
	rjmp AdcIsr ; ADC Conversion Complete
;
; -------- Interrupt Service Routines -----
Int0Isr: ; Key interrupt, starts transmitting sequence
	sbrc rFlag,bRun ; Skip if not yet transmitting
	reti ; Already running, ignore
	in rSreg,SREG ; Save SREG
	sbr rFlag,(1<<bRun)|(1<<bSta) ; Set flags
	out SREG,rSreg ; Restore SREG
	reti ; Return
;
; CTC compare A interrupt, switch IR LED
TC0CAIsr: ; 6 clock cycles for int and vector rjmp
	in rSreg,SREG ; Save SREG, 7
	sbiw rCntL,1 ; Count CTC down, 9
	brne Tc0CAIsrRet ; Not zero, 10/11
	sbr rFlag,1<<bTTO ; Set flag, 11
	in rimp,TCCR0A ; Read toggle state, 12
	eor rimp,rEor ; Invert toggle bit, 13
	out TCCR0A,rimp ; Write new toggle bit, 14
TC0CAIsrRet: ; 11/14
	out SREG,rSreg ; Restore SREG, 12/15
	reti ; Done, 16/19
;
; ADC conversion complete
AdcIsr:
	in rSreg,SREG ; Save SREG
	in rAnaL,ADCL ; Read result
	in rAnaH,ADCH
	sbr rFlag,1<<bAdc ; Set flag
        ; Restart ADC
	ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	sbrs rFlag,bRun ; Skip restart if transmit is already running
	out ADCSRA,rimp ; Restart ADC
	out SREG,rSreg ; Restore SREG
	reti
;
; --------- Program start, init -------
Start:
	; Init stack
	ldi rmp,LOW(RAMEND) ; Point to RAMEND
	out SPL,rmp ; to stack pointer
	; Controller clock to 2.4 Mcs/s
	ldi rmp,1<<CLKPCE ; CLK-Enable
	out CLKPR,rmp ; to clock prescaler port
	ldi rmp,1<<CLKPS1 ; Prescaler = 4
	out CLKPR,rmp ; to clock prescaler port
	; Init IR port
	sbi pOut,bIrO ; IR LED off
	sbi pDir,bIrD ; IR pin is output
	; Init red LED
	sbi pOut,bLdO ; LED off
	sbi pDir,bLdD ; Pin is output
	; Key port with pull-up
	sbi pOut,bKyO ; Pull-up on
	; Set start values
	clr rFlag ; Clear flags
	ldi rmp,1<<COM0A1 ; Set toggle/set reversion bit
	mov rEor,rmp ; to exor register
	; Start TC0 as CTC
	ldi rmp,cCtc ; Compare A value for 12.5 us
	out OCR0A,rmp ; to compare A port
        ; TC0 as CTC, set OC0A
	ldi rmp,(1<<COM0A1)|(1<<COM0A0)|(1<<WGM01)
	out TCCR0A,rmp ; to TC0 control port A
	ldi rmp,1<<CS00 ; Prescaler = 1
	out TCCR0B,rmp ; to TC0 control port B
	; Start ADC
	ldi rmp,1<<MUX1 ; Set ADC2 as ADC input pin
	out ADMUX,rmp ; in AD mux port
        ; Start first conversion with interrupts enabled
	ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rmp ; to ADC control port A
	; INT0 and sleep mode
	ldi rmp,(1<<SE)|(1<<ISC01) ; Sleep mode idle, falling key edges
	out MCUCR,rmp ; to master control register
	ldi rmp,1<<INT0 ; Enable INT0 interrupts
	out GIMSK,rmp ; in general interrupt mask
	; Enable interrupts
	sei ; Seit I flag in SREG
;
; -------- Program loop ----------
Loop:
	sleep ; go to sleep
	nop ; After wake up, 20
	sbrc rFlag,bTTo ; Skip if time out flag clear, 21/22
	rcall Timeout ; Process time out, 24
	sbrc rFlag,bSta ; Skip if start flag clear
	rcall StartOut ; Start transmit
	sbrc rFlag,bAdc ; Skip if ADC flag clear
	rcall AdcRdy ; Process ADC
	rjmp Loop ; go to sleep again
;
; -------- Process ADC value --------
AdcRdy:
	cbr rFlag,1<<bAdc ; Clear ADC flag
	mov ZH,rAnaH ; Copy current value to Z
	mov ZL,rAnaL
	adiw ZL,2 ; Tolerance +/- 2 digits
	sub ZL,rSntL ; Subtract last value sent
	sbc ZH,rSntH ; with carry
	brcs AdcRdyUneq ; Start transmission
	cpi ZL,5 ; Above tolerance?
	brcc AdcRdyUneq ; Start transmission
	; Increase 10 minute counter
	inc r10mL
	brne AdcRdy10m ; No overflow
	inc r10mM ; Inc next higher byte
	brne AdcRdy10m ; No overflow
	inc r10mH ; Increase third counter byte
AdcRdy10m:
	mov rmp,r10mL ; Time limit reached?
	cpi rmp,BYTE1(cAdc10min) ; Compare byte 1
	brne AdcRdyRet ; No
	mov rmp,r10mM
	cpi rmp,BYTE2(cAdc10min) ; Compare byte 2
	brne AdcRdyRet ; No
	mov rmp,r10mH
	cpi rmp,BYTE3(cAdc10min) ; Compare byte 3
	brne AdcRdyRet ; No
	; 10 minutes are over
	clr r10mL ; Clear timer registers
	clr r10mM
	clr r10mH
AdcRdyUneq: ; Switch ADC off
	ldi rmp,(1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rmp ; to ADC control port A
	nop ; Wait one cycle
	cbr rFlag,1<<bAdc ; Clear ADC flag
	sbr rFlag,(1<<bRun) ; Set start flag
	rjmp StartOut
AdcRdyRet:
	ret
;
; -------- CTC-Timeout --------------- 
TimeOut: ; Time out flag was set, 24
 	cbr rFlag,1<<bTTo ; Clear time out flag, 25
	inc rTOC ; Next signal, 26
	cpi rTOC,cHead ; Headers to send?, 27
	brcc TimeOut1 ; No, data bits, 28/29
	ldi ZH,HIGH(TOH2) ; MSB address header table, 29
	ldi ZL,LOW(TOH2) ; dto, LSB, 30
	mov rmp,rTOC ; Copy counter, 31
	lsl rmp ; Multiply by 2, 32
	add rmp,rTOC ; Add once, 33
	add ZL,rmp ; Add to LSB address, 34
	ldi rmp,0 ; Overflow adder, 35
	adc ZH,rmp ; Add overflow, 36
	ijmp ; Jump to Z, 38
TimeOut1: ; 29
	cpi rTOC,cSign ; All sent?, 30
	brcc TimeOutEnd ; Yes, finalize, 31/32
	in rmp,TCCR0A ; Read toggle/set bit, 32
	sbrc rmp,COM0A1 ; Skip if toggle bit zero, 33/34
	rjmp TimeOut2 ; Toggle is off, 35
	; Toggle is on
	ldi rCntH,High(cPaus) ; Load pause duration, 35
	ldi rCntL,Low(cPaus) ; 36
	ret ; Done, 40
TimeOut2: ; 35
	ldi rCntH,High(cShort) ; Load short bit duration, 36
	ldi rCntL,Low(cShort) ; 37
	lsl rData1 ; Shift highest bit to carry, 38
	rol rData0 ; 39
	brcc TimeOut3 ; Bit is zero, 40/41
	ldi rCntH,High(cLong) ; Load long bit duration, 41
	ldi rCntL,Low(cLong) ; 42
TimeOut3: ; 41
	ret ; Done, 45/46
TimeOutEnd: ; 32
	sbrs rFlag,bRst ; Skip if restart flag set, 33/34
	rjmp Restart ; No, start new, 35
	clr rmp ; Disable timer ints, 35
	out TIMSK0,rmp ; in TC0 interrupt mask, 36
	; Set OC0A output 
	ldi rmp,(1<<COM0A1)|(1<<COM0A0)|(1<<WGM01) ; 37
	out TCCR0A,rmp ; in TC0 control port A, 38
	; Update last value sent
	mov rSntH,rAnaH ; To sent register, 39
	mov rSntL,rAnaL ; 40
	; Clear run flag
	cbr rFlag,(1<<bRun)|(1<<bRst) ; Clear flags, 41
	; Restart ADC
	ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rmp ; in ADC control port A, 43
	sbi pOut,bLdO ; Clear red LED, 45
	ret ; Done, 49
;
; Time out header, adjust duration
TOH2: ; 38
	ldi rCntH,High(cTH2) ; 39
	ldi rCntL,Low(cTH2) ; 40
	ret ; 44
;
	ldi rCntH,High(cTH3)
	ldi rCntL,Low(cTH3)
	ret
;
	ldi rCntH,High(cTH4)
	ldi rCntL,Low(cTH4)
	ret
;
; Restart again, second burst
Restart:
	clr rmp ; Disable timer int, 12
	out TIMSK0,rmp ; 13
	sbr rFlag,1<<bRst ; Set restart flag, 14
	rjmp StartOut1 ; 16
;
; Start transmit
StartOut:
	cbr rFlag,(1<<bSta)|(1<<bRst) ; Clear start and restart flag, 15
	mov rSntH,rAnaH ; Copy measured value to sent registers
	mov rSntL,rAnaL
Startout1:
	mov rData1,rAnaL ; LSB Analog value, 16/17
	mov rData0,rAnaH ; dto, MSB, 17/18
	ser rToC ; Zaehler auf 0xFF, 18/19
	ldi rCntH,High(cTH1) ; Load first signal duration, 21/22
	ldi rCntL,Low(cTH1) ; 22/23
	ldi rmp,1<<OCIE0A ; Enable timer int, 23/24
	out TIMSK0,rmp ; 24/25
	cbi pOut,bLdO ; Set red LED on, 25/26
	ret ; 29/30
;
; End of source code
;

Simulation of the software with the Studio

The software builds extensively on exact timings. This can be tested with the Studio's simulation software. The following will show the results (source code in the German version). Simulation first header inactive To sent the (inactive) very long first header phase required the precalculated number of clock cycles, differences are caused by rounding.

Simulation first header active The active phase of the first header signal is a slight bit too long, one additional CTC phase has been performed. This is a tolerable difference.

Simulation second header inactive The inactive phase of the second header signal also is by exactly one CTC phase of 12.5 µs too long.

Simulation second header active The analysis shows that it is by one CTC phase longer than expected. Those who want it more exact reduce the header time constants by one phase.

Simulation of a high bit Again the inactive phase of a long 1 bit is by one phase longer.

Simulation of a low bit Also, the inactive phase of a short 0 bit is by one phase longer.

Simulation active phase The duration of an active signal phase is also slightly longer than expected.
All routines in the main loop need a longer time than the 30 clock cycles available for one CTC round, therefore one CTC cycle is added to every active and inactive cycle. As this additional time is insignificant we do not have to correct this. If you want to do this change all respective constants by subtracting 25/2 from each constant calculation.

12.6.2 The IR analog data receiver

Hardware

The receiving of the data transmitted by the analog transmitter uses the same hardware (ATtiny24, LCD, IR receiver module) that was already used before, no changes are necessary.

Software The software for receiving, analyzing and displaying the analog transmitter signals is listed as follows, the source code is here, optimized for the process. It requires the LCD routines. In parts the receiver module delivers very different signals than those that were transmitted. Included therefore are program parts that can be used for diagnosis. If you like you can set the switch "Diagnose" to "1" or "2" to use that diagnosis parts.

;
; *******************************************************
; * IR receiver with ATtiny24 and LCD for analog values *
; * (C)2017 by http://www.avr-asm-tutorial.net          *
; *******************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ---------- Switch ------------------
.equ Diagnose = 0 ; 0: No diagnose
;                   1: Display data bytes in hex
;                   2: Display all received words in hex
;
; ---------- Durations remote control signals
.equ cLong = 20000 ; Lange Pause
.equ cHead = 2700 ; Dauer High-Signal Kopf us
.equ cZero = 512 ; Dauer Null-Byte us
.equ cOne = 1064 ; Dauer Eins-Byte us
;
; ---------- Timing --------------------
; Controller clock        1.000.000 cs/s
; Time per clock cycle            1 us
; Prescaler TC1                   8
; Time per TC1 timer tick         8 us
; Overflow TC1 after ticks   65,536
; Time until TC1 overflows      524.288 ms
.equ cPresc = 8 ; TC1 prescaler
;
; ---------- Signal duration and tolerances ---------
.equ nLong = cLong/cPresc ; Nominal long signal pause
.equ nHead = cHead/cPresc ; Nominal head signal
.equ nZero = cZero/cPresc ; Nominal zero signal 
.equ nOne = cOne/cPresc ; Nominal one signal
.equ cTolerance = 20 ; Tolerance in +/- %
.equ nHeadMin = nHead-(nHead*cTolerance+50)/100
.equ nHeadMax = (nHead*2*cTolerance+50)/100+1
.equ nZeroMin = nZero-(nZero*cTolerance+50)/100
.equ nZeroMax = nZero+(nZero*cTolerance+50)/100+1
.equ nOneMin = nOne-(nOne*cTolerance+50)/100
.equ nOneMax = nOne+(nOne*cTolerance+50)/100+1
;
; Check all data constans
.if nZeroMin > 255
	.error "nZeroMin too large!"
	.endif
.if nZeroMax > 255
	.error "nZeroMax too large!"
	.endif
.if nOneMin > 255 
	.error "nOneMin too large!"
	.endif
.if nOneMax > 255
	.error "nOneMax too large!"
	.endif
;
; ---------- Ports ---------------------
.equ pIrIn = PINA ; IR receiver port
.equ bIrIn = 0    ; IR receiver port pin
;
; ---------- Registers -----------------
; Used: R0 by LCD, decimal conversion
; Used: R1 Decimal conversion
; free: R2..R5
.def rErrL = R6
.def rErrH = R7
.def rMul1 = R8 ; Multiplication
.def rMul2 = R9
.def rMul3 = R10
.def rMulH = R11
.def rFlags = R12 ; Flag storage
.def rDataL = R13 ; Receiver data
.def rDataH = R14
.def rSreg = R15 ; Save/restore SREG
.def rmp   = R16 ; Multi purpose register
.def rmo   = R17 ; Additional multi purpose
.def rLine = R18 ; LCD line counter
.def rRead = R19 ; LCD read register
.def rimp  = R20 ; Multi purpose inside interrupts
.def rFlag = R21 ; Flag register
	.equ bSta = 0 ; Start flag
	.equ bHead = 1 ; Head correct
	.equ bHdSh = 2 ; Head too short
	.equ bHdLg = 3 ; Head too long
	.equ bDSh = 4 ; Data bit too short
	.equ bDMi = 5 ; Data bit middle long
	.equ bDLg = 6 ; Data bit too long
	.equ bDOv = 7 ; Wrong number of data bits
.def rDCtr = R22 ; Data bit counter
; free: R23 .. R25
; Used: XH:XL R27:R26 diverse uses
; Used: YH:YL R29:R28 Counter signal duration
; Used: ZH:ZL R31:R30 diverse uses
;
; ---------- Diagnose SRAM buffer ------
.DSEG
.ORG 0x0060
.if Diagnose == 1
	Buffer:
	.byte 16
	BufferEnd:
	.endif
.if Diagnose == 2
	Buffer:
	.byte 36
	BufferEnd:
	.endif
;
; ---------- Reset and interrupts -----
.CSEG ; Code segment
.ORG 0 ; to the beginning
	rjmp Start ; Reset vector, init
	reti ; INT0 External Interrupt Request 0
	rjmp Pci0Isr ; PCINT0 Pin Change Int Req 0
	reti ; PCINT1 Pin Change Interrupt Req 1
	reti ; WDT Watchdog Time-out
	reti ; TIM1_CAPT Timer/Counter1 Capture Event
	reti ; TIM1_COMPA Timer/Counter1 Compare Match A
	reti ; TIM1_COMPB Timer/Counter1 Compare Match B
	rjmp Tc1Isr ; TIM1_OVF Timer/Counter1 Overflow
	reti ; TIM0_COMPA Timer/Counter0 Compare Match A
	reti ; TIM0_COMPB Timer/Counter0 Compare Match B
	reti ; TIM0_OVF TC0 Overflow
	reti ; ANA_COMP Analog Comparator
	reti ; ADC ADC Conversion Complete
	reti ; EE_RDY EEPROM Ready
	reti ; USI_STR USI START
	reti ; USI_OVF USI Overflow
;
; ---------- Interrupt Service Routines -----
Pci0Isr: ; PCINT0 ISR
	sbis pIrIn,bIrIn ; Skip next if IR input high, 1/2
	reti ; Ignore signal, 5
	; IR input is low, was high before
	in YL,TCNT1L ; Read counter TC1, 3
	in YH,TCNT1H ; 4
	ldi rimp,0 ; Clear counter TC1, 5
	out TCNT1H,rimp ; 6
	out TCNT1L,rimp ; 7
	ldi rimp,1<<TOIE1 ; Enable timer int
	out TIMSK1,rimp ; in timer int mask
	in rSreg,SREG ; Save SREG
	cpi YH,High(nLong) ; Long pause?
	brcs Pci0Isr1 ; Signal was shorter
	ldi rFlag,1<<bSta ; Set start flag
.if diagnose != 0
	ldi XH,High(Buffer) ; Restart buffer
	ldi XL,Low(Buffer)
	.if diagnose == 2
		st X+,YH ; Counter to SRAM
		st X+,YL
		.endif
	.endif
	rjmp Pci0IsrRet
Pci0Isr1: ; Signal is not a long header
	sbrs rFlag,bSta ; Skip next if start flag set?
	rjmp Pci0IsrRet ; Wait until start
	sbrc rFlag,bHead ; Skip next if head flag clear?
	rjmp Pci0Isr2 ; Already correct
.if diagnose == 2
	st X+,YH ; Counter to SRAM
	st X+,YL
	.endif
	subi YL,LOW(nHeadMin) ; Counter minus min head
	sbci YH,HIGH(nHeadMin)
	brcs Pci0IsrErrHS ; Head too short
	subi YL,LOW(nHeadMax) ; Counter minus max head
	sbci YH,HIGH(nHeadMax)
	brcc Pci0IsrErrHL ; Head too long
	sbr rFlag,1<<bHead ; Set flag head
	clr rDCtr ; Start data bit counter
	clr rDataL ; Clear data register
	clr rDataH
	rjmp Pci0IsrRet
Pci0Isr2: ; Head was correct, check bits
.if Diagnose == 1
	tst YH ; Is MSB zero?
	breq Pci0Isr2d ; Yes
	ldi YL,0xFF ; Mark overflow
Pci0Isr2d:
	st X+,YL ; To SRAM
	rjmp Pci0IsrRet
	.endif
.if Diagnose == 2
	st X+,YH ; MSB and
	st X+,YL ; LSB to SRAM
	rjmp Pci0IsrRet
	.endif
	cpi YL,LOW(nZeroMin) ; Check min zero bit
	brcs Pci0IsrErrDSh ; Shorter than min zero bit
	cpi YL,LOW(nZeroMax) ; Check shorter than max zero bit
	brcc Pci0Isr3 ; Beyond zero bit, check one
	clc ; Carry clear
	rjmp Pci0Isr4 ; Shift a zero into result registers
Pci0Isr3:
	cpi YL,Low(nOneMin) ; Shorter than min one bit?
	brcs Pci0IsrErrDMi ; Middle long between zero and one
	cpi YL,Low(nOneMax) ; Shorter than max one bit?
	brcc Pci0IsrErrDLg ; Too long for a one
Pci0Isr4:
	rol rDataL ; Roll bit in carry into result registers 
	rol rDataH
	inc rDCtr ; Increase number of bits
	cpi rDCtr,17 ; Number of data bits ok?
	brcs Pci0IsrRet ; Yes
	sbr rFlag,1<<bDOv ; Too many bits 
	rjmp Pci0IsrErrData ; Error
Pci0IsrErrHS:
	sbr rFlag,(1<<bHead)|(1<<bHdSh) ; Head too short
	ldi rmp,Low(nHeadMin) ; Restore original count
	add YL,rmp
	ldi rmp,High(nHeadMin)
	adc YH,rmp
	rjmp Pci0IsrErrData ; Error
Pci0IsrErrHL:
	sbr rFlag,(1<<bHead)|(1<<bHdLg) ; Head too long
	ldi rmp,Low(nHeadMin+nHeadMax) ; Restore original count
	add YL,rmp
	ldi rmp,High(nHeadMin+nHeadMax)
	adc YH,rmp
	rjmp Pci0IsrErrData ; Error
Pci0IsrErrDSh:
	sbr rFlag,1<<bDSh ; Data bit too short
	rjmp Pci0IsrErrData ; Error
Pci0IsrErrDMi:
	sbr rFlag,1<<bDMi ; Data bit medium long
	rjmp Pci0IsrErrData ; Error
Pci0IsrErrDLg:
	sbr rFlag,1<<bDLg ; Data bit too long
Pci0IsrErrData:
	lsl YL ; N multiplied by 8 (micro seconds)
	rol YH
	lsl YL
	rol YH
	lsl YL
	rol YH
	mov rErrL,YL ; to error register
	mov rErrH,YH
Pci0IsrRet:
	out SREG,rSreg ; Restore SREG
	reti ; Done
;
; TC1 overflow Interrupt Service Routine
Tc1Isr:
	sbrs rFlag,bSta ; Skip next if start bit set
	rjmp Tc1Isr1 ; Ignore, return
	sbrc rFlag,bHead ; Skip next if head flag clear
	set ; Set output flag
Tc1Isr1:
	reti
;
; ---------------- Start, init --------------
Start:
	ldi rmp,LOW(RAMEND) ; Stack to RAMEND
	out SPL,rmp ; to stack pointer
	; Init I/O
	; Init LCD port output pins
	ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
	out pLcdCR,rmp ; To LCD control port
	clr rmp ; LCD control Outputs off
	out pLcdCO,rmp ; to control port output
	ldi rmp,mLcdDRW ; LCD data port mask write
	out pLcdDR,rmp ; to LCD direction port
	; Init LCD
	rcall LcdInit ; Call to included LCD routine
	ldi ZH,HIGH(2*LcdStart) ; Display start text
	ldi ZL,LOW(2*LcdStart)
	rcall LcdText
	; Start value for flags
	clr rFlag
	clt ; T is display flag
	; Init Timer 1, free running
	ldi rmp,1<<CS11 ; Prescaler by 8
	out TCCR1B,rmp ; to TC1 control port B
	; PCINT0 for IR Rx
	ldi rmp,1<<PCINT0 ; Pin 0 level change ints
	out PCMSK0,rmo ; to mask port 0
	ldi rmp,1<<PCIE0 ; Enable interrupt PCINT0
	out GIMSK,rmp ; in general interrupt mask
	; Sleep enable
	ldi rmp,1<<SE ; Sleep mode idle
	out MCUCR,rmp ; to MCU control port
	; Enable interrupts
	sei ; Setting I flag in SREG
Loop:
	sleep ; go to sleep
	nop ; Wake up
	brtc Loop ; Back to sleep if display flag clear
	rcall Update ; Process display flag
	rjmp Loop ; go to sleep again
;
; Process display flag
Update:
	clt ; Clear display flag
	clr rmp ; Disable interrupts TC1
	out TIMSK1,rmp ; to interrupt mask
	mov rmp,rFlag ; Copy error flags
	andi rmp,0xFC ; isolate error flags
	mov rFlags,rmp ; Interim store
	ldi rFlag,0 ; Clear flags for restart
.if Diagnose == 1
	rjmp Diag ; Display diagnose
	.endif
.if Diagnose == 2
	rjmp DiagW ; Display diagnose
	.endif
	brne UpdateErr ; Display error flags
	cpi rDCtr,16 ; 16 bits received?
	breq UpdateCorrect ; Yes, display
	rjmp UpdateTooFewBits ; Display error
UpdateCorrect:
	ldi ZH,2 ; Set position of LCD to line 3
	clr ZL
	rcall LcdPos
	ldi rmp,'R' ; Display R:
	rcall LcdD4Byte
	ldi rmp,':'
	rcall LcdD4Byte
	ldi ZH,2 ; Set position LCD line 3 col 3
	ldi ZL,2
	rcall LcdPos
	rcall Multi ; Multiply
	rjmp Percent ; Display as percentage
UpdateErr: ; Display error messages
	rcall LcdLine3 ; Clear from line 3
	ldi ZH,HIGH(2*LcdOutput)
	ldi ZL,LOW(2*LcdOutput)
	rcall LcdTextC
	rcall LcdLine3 ; Set line 3
	ldi rmp,'E' ; Display E:
	rcall LcdD4Byte
	ldi rmp,':'
	rcall LcdD4Byte
	ldi ZH,HIGH(2*ErrHSh) ; Error header too short
	ldi ZL,LOW(2*ErrHSh)
	mov rmp,rFlags ; Copy error flags
	sbrc rmp,bHdSh ; Skip next if header too short clear
	rjmp Update1
	ldi ZH,HIGH(2*ErrHLg) ; Error header too long
	ldi ZL,LOW(2*ErrHLg)
	sbrc rmp,bHdLg ; Skip next if header too long clear
	rjmp Update1
	ldi ZH,HIGH(2*ErrDSh) ; Error data too short
	ldi ZL,LOW(2*ErrDSh)
	sbrc rmp,bDSh ; Skip next if data too short clear
	rjmp Update1
	ldi ZH,HIGH(2*ErrDMi) ; Error data in the middle
	ldi ZL,LOW(2*ErrDMi)
	sbrc rmp,bDMi ; Skip next if data in the middle clear
	rjmp Update1
	ldi ZH,HIGH(2*ErrDLg) ; Error data too long
	ldi ZL,LOW(2*ErrDLg)
	sbrc rmp,bDLg ; Skip next if data too long clear
	rjmp Update1
	ldi ZH,HIGH(2*ErrDBi) ; Error too many data bits
	ldi ZL,LOW(2*ErrDBi)
Update1:
	rcall LcdTextC ; Display error message
	ldi rmp,'='
	rcall LcdD4Byte
	rjmp DecimalY ; Display Y in decimal
;
UpdateTooFewBits: ; Display number of received bits
	rcall LcdLine4
	ldi rmp,'B' ; Display B:
	rcall LcdD4Byte
	ldi rmp,':'
	rcall LcdD4Byte
	rcall DecimalOut ; Display number of bits decimal
	ldi rmp,' '
	rcall LcdD4Byte
	ldi rmp,'b' ; Display bit
	rcall LcdD4Byte
	ldi rmp,'i'
	rcall LcdD4Byte
	ldi rmp,'t'
	rjmp LcdD4Byte
;
; Convert number in rDataH:rDataL to %
.equ cMulti = 1000*256/1023 ; Multiply by 1000/1023 = 250
Multi:
	mov rmp,rDataH ; Copy received MSB
	andi rmp,0x03 ; Clear unused bits
	mov rDataH,rmp ; and copy back
	clr rMul1 ; Clear result registers
	clr rMul2
	clr rMul3
	clr rMulH ; Clear help register
	ldi rmp,cMulti ; Multiplicator to rmp
Multi1:
	lsr rmp ; Shift multiplicator right
	brcc Multi2 ; No carry, do not add to result
	add rMul1,rDataL ; Add to result
	adc rMul2,rDataH
	adc rMul3,rMulH
Multi2:
	lsl rDataL ; Shift data left
	rol rDataH
	rol rMulH
	tst rmp ; End of multiplication?
	brne Multi1 ; Go on multiplying
	ret
;
; Display number in rMul3:rMul2 in decimal on LCD as percentage
Percent:
	ldi ZH,HIGH(2*Decimal) ; Point Z to decimal table
	ldi ZL,LOW(2*Decimal)
	clt ; Suppress leading zeroes
Percent1:
	lpm XL,Z+ ; Read decimal from table to X
	lpm XH,Z+
	tst XL ; End of table?
	breq Percent5 ; Yes, to last digit
	clr rmp ; rmp is counter
Percent2:
	sub rMul2,XL ; Subtract decimal LSB
	sbc rMul3,XH ; MSB with carry
	brcs Percent3 ; Carry occurred
	inc rmp ; count up
	rjmp Percent2 ; Repeat subtraction
Percent3:
	add rMul2,XL ; Retract one subtraction, LSB
	adc rMul3,XH ; dto., MSB with carry
	tst rmp ; Zero?
	brne Percent4 ; No, display
	brts Percent4 ; Leading zero suppression is off
	cpi XL,10 ; Decimal separator?
	brne Percent1 ; No, go on
	set ; Set leading suppression off
Percent4:
	subi rmp,-'0' ; Convert to decimal ASCII
	rcall LcdD4Byte ; Display
	cpi XL,10 ; Decimal separator?
	brne Percent1 ; No
	ldi rmp,',' ; Display decimal separator
	rcall LcdD4Byte
	rjmp Percent1 ; Continue
Percent5:
	clt ; Clear T flag, used for interrupt signal
	mov rmp,rMul2 ; Last decimal digit
	subi rmp,-'0' ; Convert to ASCII
	rcall LcdD4Byte
	ldi rmp,'%' ; Add percentage char
	rcall LcdD4Byte
	ldi rmp,' ' ; and blank
	rjmp Lcd4Byte
;
; Display number of digits rDCnt as byte in decimal
DecimalOut:
	ldi ZH,HIGH(2*Decimal100) ; Point Z to hundreds
	ldi ZL,LOW(2*Decimal100)
	clt ; Suppress leading zeros
DecimalOut1:
	lpm XL,Z+ ; Read decimal number from table
	lpm XH,Z+
	tst XL ; End of table?
	breq DecimalOut5 ; Yes, last digit
	clr rmp ; rmp is counter
DecimalOut2:
	sub rDCtr,XL ; Subtract decimal number
	brcs DecimalOut3 ; Carry, complete
	inc rmp ; Count up
	rjmp DecimalOut2 ; and subtract again
DecimalOut3:
	add rDCtr,XL ; Retract last subtraction
	tst rmp ; Leading zero?
	brne DecimalOut4 ; No, display
	brts DecimalOut4 ; Leading zero suppression is off
	rjmp DecimalOut1 ; Go on converting
DecimalOut4:
	subi rmp,-'0' ; Convert to decimal ASCII
	rcall LcdD4Byte ; Display character
	set ; Do not suppress leading zeros any more
	rjmp DecimalOut1 ; and repeat
DecimalOut5:
	clt ; Clear T flag
	mov rmp,rDCtr ; Last digit
	subi rmp,-'0' ; Convert to decimal ASCII
	rjmp LcdD4Byte ; and display
;
; Display rErrH:rErrL in decimal format
DecimalY:
	ldi ZH,HIGH(2*Decimal) ; Point Z to decimal table
	ldi ZL,LOW(2*Decimal)
	clt ; Suppress leading zeros
DecimalY2:
	lpm XL,Z+ ; Read decimal from table
	lpm XH,Z+
	tst XL ; End of table?
	breq DecimalY6 ; Yes, to last digit
	clr rmp ; rmp is counter
DecimalY3:
	sub rErrL,XL ; Subtract decimal number from rErr
	sbc rErrH,XH
	brcs DecimalY4 ; Carry, done
	inc rmp ; Increase counter
	rjmp DecimalY3 ; Go on subtracting
DecimalY4:
	add rErrL,XL ; Retract last subtraction
	adc rErrH,XH
	tst rmp ; Result zero?
	brne DecimalY5 ; No
	brts DecimalY5 ; Leading zero suppression off
	rjmp DecimalY2 ; Continue
DecimalY5:
	set ; Do not suppress leadig zeros any more
	subi rmp,-'0' ; Convert to ASCII
	rcall LcdD4Byte
	rjmp DecimalY2 ; Continue conversion
DecimalY6:
	clt ; Clear T flag
	mov rmp,rErrL ; Last decimal digit
	subi rmp,-'0' ; Convert to ASCII
	rcall LcdD4Byte
	ldi rmp,' ' ; Add blank
	rjmp LcdD4Byte
;
; Decimaltable
Decimal: ; 16 bit binary from here
.dw 10000
.dw 1000
Decimal100: ; 8 bit binary from here
.dw 100
.dw 10
.dw 0
;
; Start text on LCD
LcdStart:
.db "IR data receiver 24 ",0x0D,0xFF
.db "avr-asm-tutorial.net",0x0D,0xFF
LcdOutput:
.db "                    ",0x0D,0xFF
.db "                    ",0xFE,0xFF
;
; Error text
ErrHSh:
.db "Head too short!",0xFE
ErrHLg:
.db "Head too long!",0xFE,0xFE
ErrDSh:
.db "DBit too short!",0xFE
ErrDMi:
.db "DBit middle long!",0xFE
ErrDLg:
.db "DBit too long!",0xFE,0xFE
ErrDBi:
.db "DBits > 16",0xFE,0xFE
;
.if Diagnose == 1 ; Data byte output
Diag:
	ldi rmp,0x01 ; Clear display
	rcall LcdC4Byte
	ldi XH,High(Buffer) ; X to buffer start
	ldi XL,Low(Buffer)
Diag1:
	ld rmp,X+ ; Buffer byte
	rcall HexOut ; Display in hex
	ldi rmp,' '
	rcall LcdD4Byte
	cpi XL,Low(Buffer+6) ; Line 2?
	brne Diag1a
	rcall LcdLine2 ; Line 2
	rjmp Diag2
Diag1a:
	cpi XL,Low(Buffer+12) ; Line 3?
	brne Diag2
	rcall LcdLine3 ; Line 3
Diag2:
	cpi XL,Low(BufferEnd) ; Done?
	brne Diag1 ; No, go on
	ret
	.endif
;
.if Diagnose != 0 ; Display rmp in hex
HexOut:
	push rmp ; Save rmp
	swap rmp ; Upper nibble first
	rcall HexOutNibble ; Display nibble
	pop rmp ; Restore rmp
HexOutNibble:
	andi rmp,0x0F ; Isolate lower nibble
	subi rmp,-'0' ; Convert to ASCII
	cpi rmp,'9'+1 ; A to F?
	brcs HexOutNibble1 ; No
	subi rmp,-7 ; Convert to A to F
HexOutNibble1:
	rjmp LcdD4Byte ; Display character
	.endif
;
.if Diagnose == 2 ; Display words
DiagW:
	ldi rmp,0x01 ; Clear LCD
	rcall LcdC4Byte
	ldi XH,HIGH(Buffer) ; X to buffer
	ldi XL,LOW(Buffer)
	ldi ZH,0 ; Line counter
	ldi ZL,0 ; Column counter
DiagW1:
	ld rmp,X+ ; Read MSB
	tst rmp ; MSB zero?
	brne DiagWW ; No, as word
	cpi ZL,19 ; Enough space in that line?
	brcs DiagW2 ; Yes, display byte
	rcall NextLine ; Next line on LCD
DiagW2:
	ld rmp,X+ ; Read LSB
	rcall HexOut ; Display in hex
	subi ZL,-2 ; Add two positions to column count
	rjmp DiagW3 ; Continue
DiagWW:
	cpi ZL,16 ; Enough space in the line for a word?
	brcs DiagWW1 ; Yes
	rcall NextLine ; Next line
DiagWW1:
	rcall HexOut ; Display MSB
	ld rmp,X+ ; Read LSB
	rcall HexOut ; Display LSB
	subi ZL,-4 ; Add four positions to column count
DiagW3:
	cpi ZL,20 ; End of line?
	brcc DiagW4 ; Yes
	ldi rmp,' ' ; Display blank
	rcall LcdD4Byte
	subi ZL,-1 ; Add one position to column count
DiagW4:
	cpi XL,Low(BufferEnd) ; End of buffer?
	brcs DiagW1 ; No, go on
	ret
;
NextLine: ; Set LCD position to next line start
	push rmp ; Save rmp
	inc ZH ; Next line
	clr ZL ; Column = 1
	rcall LcdPos ; Set position on LCD
	pop rmp ; Restore rmp
	ret
	.endif
;
; Include LCD routines
.include "Lcd4Busy.inc"
;
; End of source code
;

One new instruction here is SBCI register,constant. This subtracts the constant from the register and the carry bit as well.

A further new instruction here is ADD register,register. It adds the second register to the first one and sets flags.

Application

Data reception That is how it looks like (the German version). And all this without the mighty floating point math library, just with some intelligence added.


Home Top IR Conditioned Hardware Measuring Transmit Receive Switch


12.7 A self-learning IR receiver with three channel relay switches

12.7.1 Task

Who is not willing to have a small box that can switch electric devices on and off with an ancient remote controller? Now, here it is. And the best is: you do not have to care about your old remote controller and its special encoding of keys, the small box does that for you. This remote control receiver learns the codes that you want to switch your devices with by training it once. It stores the recognized codes in its internal EEPROM and recalls those at power-up. Until the next training exercise has been completed.

12.7.2 Hardware, components and mounting

Hardware

The hardware comes in two versions:

Active-Low-Scheme This is the ATtiny13 version with 5 V switching stages. If your relay has less than 50 mA current consumption you can skip the transistor driver, but the ISP interface is incompatible with large inductivities. The 1 k resistor decouples the TSOP from the ISP programming interface. The key or jumper on PB3 serves as an initiation at start-up to go to the self-learning process, it is not used during operation.

Active-High-Scheme The same scheme, but now with active-high switch drivers.

Tiny24 switch schem The placing of the output channels can be seen here. PA1 serves to sense the state of the key or jumper at startup. Remove this jumper after startup to enable the self-learning process. In the design of the switching stage on channel 1 it has to be considered that the internal pullup on this pin is only about 50 kΩ and that the driver stage must not pull down this to below the state when self-learning is initiated, because this would result in "always self-learning". Selecting an active high output signal is therefore not a good idea for that ATtiny24 switch.

Components

The TSOP receiver module has already been described. The 1 k resistor has the rings brown-black-red-gold (5%) or brown-black-black-brown-brown (1%).

The components for the driver stages are not listed in the hardware table because those are too close to the application and what has to be switched on and off.

Mounting

Mounting tn13 This is the ATtiny13 version. Attached are LEDs only. Those are necessary to follow the self learning process and to signal switching.

Mounting tn24 The same with the ATtiny24 version.

12.7.3 Program

Structure and specialties

The program works similar no matter if it runs on an ATtiny13 or an ATtiny24 with LCD attached. All device specific code is enclosed in .if directives. Of course this applies to the reset and interrupt vector table, too. Because the ATtiny13 does not have a 16 bit timer, time counting is done with the 8 bit timer and an additional MSB register.

As a difference to the previous experiments the timer/counter works with a prescaler of 64. So each timer tick in the Tiny13 is 53, in the Tiny24 64 µs long. This lower resolution has been proven to be absolutely sufficient.

To increase the precision of recognizing zeros ans ones data bits, their duration is averaged over 16 signals. Those durations are stored in a buffer in the SRAm, are summed up and averaged after any burst and the average is stored together with the recognized key codes in the EEPROM.

Recognizing the keys uses only the last 16 bits of a burst. This has been proven as sufficient.

Measuring and recognition of the IR signal is completely performed in the INT0 (ATtiny13) resp. the PCINT0 (ATtiny24) interrupt service routine. Except for two instructions at the beginning this routine is identical for both devices. The algorithm of the ISR is as follows: The recognition that all data bits have been received is after a programmable time during which the timer is not cleared by received data bits and the MSB of the timer exceeds the pre-chosen value. A flag is then set, the processing of the information is performed in the main loop.

Self learning mode

The following conditions initiate the self-learning mode at startup: If the self-learning mode is active then It should be noted that programming the flash memory with a new version erases the EEPROM content (sets it to 0xFF). So after programming the self-learning mode is entered automatically.

EESave fuse This can be changed by setting the EESAVE fuse. In that case the EEPROM content is not erased.

Normal mode

During normal operation the incoming bit combinations are compared with all stored bit combinations. If those are identical to any one combination, the respective channel is switched on or off.

12.7.4 Measured IR remote control codes

The content of the EEPROM can be read with programming tools such as those integrated in ATMEL's Studio. This generates files with the extension .hex that look like this:

:100000001108F5C8B5881528754895A80DFF7FFF1C
:10001000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0
:10002000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0
:10003000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD0
:10004000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC0
:10005000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0
:10006000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA0
:10007000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF90
:00000001FF

The bytes stored in the EEPROM start with "1108...", the last byte ("1C") is a check sum.

The following lists the content in hex if programmed with different RC equipment (all numbers in hex).
RC deviceRedYellowGreenZero/
One
OnOffOnOffOnOff
HDR0805C8C5888528254528A8A511
TV20DF10EFA05F906F609F50AF12
Camera41BE619E817EE11EC13EA15E19
While the HDR and the TV RC are faster (Zero/One = 11..12), the camera RC device is less quick (Zero/one = 19).

12.7.5 Diagnosis with the ATtiny24 version with LCD

Default display

Diagnose output Displayed are the following information in hex (German version):

Further possibilities

With the switches on top of the source code two further diagnoses can be switched on.

Diagnose bytes With the switch cDataB set to one the last received 16 data bytes and the calculated zero/one discrimination level are displayed.

Diagnose words Similarly the switch cDataW lists the results wordwise.

12.7.6 Program

The program for this learning remote control receiver is listed in the following, the source code in asm format is here. If the ATtiny24 with an attached LCD is to be used, the include file with LCD routines is required.

Prior to assembling the switches on top have to be adjusted to what is needed.

;
; *************************************************
; * IR receiver and 3-channel switch with tn13/24 *
; * (C)2017 by www.avr-asm-tutorial.net           *
; *************************************************
;
; -------- Switches -------------
.equ cAvrType = 13 ; Target device type, ATtn13 or 24
.equ cActiveH = 1 ; 1 = Channel outputs active high
.equ cLcd    = 0 ; 1 = LCD attached
.equ cInput  = 0 ; 1 = Self-learning input at startup
.equ cDataB  = 0 ; 1 = Display raw data bytes
.equ cDataW  = 0 ; 1 = Display raw data words
;
.if (cAvrType != 13) && (cAvrType != 24)
	.error "Wrong device type"
	.endif
.if (cLcd == 1) && (cAvrType == 13)
	.error "LCD does not fit to device type"
	.endif
;
.NOLIST
.if cAvrType == 13
	.INCLUDE "tn13def.inc"
	.else
	.INCLUDE "tn24def.inc"
	.endif
.LIST
;
.if cAvrType == 13
; Hardware: ATtiny13 version
;                _________
;               /         |
;   +5V/10k o--|Reset  VCC|--o +5V
;              |          |
; Key/Jumpero--|PB3    PB2|--o Ch2 out ye
;              |          |
;Ch3 out gn o--|PB4    PB1|--o TSOP1740
;              |          |
;        0V o--|GND    PB0|--o Ch1 out rd
;              |__________|
;
	.else
; Hardware: ATtiny24
;                _________
;               /         |
;       +5V o--|VCC    GND|--o 0V
;              |          |
;    LCD-RS o--|PB0    PA0|--o TSOP1740
;              |          |
;   LCD-R/W o--|PB1    PA1|--o Ch 1 out rd
;              |          |
;     RESET o--|RES    PA2|--o Ch 2 out ye
;              |          |
;     LCD-E o--|PB2    PA3|--o Ch 3 out gn
;              |          |
;    LCD-D7 o--|PA7    PA4|--o LCD-D4/SCK
;              |          |
;MOSI/LCD-D6o--|PA6    PA5|--o LCD-D5/MISO
;              |__________|
;
	.endif
;
; -------- Ports, portpins ----------
.if cAvrType == 13
	; Ports
	.equ pOut = PORTB ; Output port
	.equ pDir = DDRB ; Direction port
	.equ pIn  = PINB ; Input port
	; Portbits
	.equ bIrO = PORTB1 ; IR receiver output pin
	.equ bIrD = DDB1 ; IR receiver direction pin
	.equ bIrI = PINB1 ; IR receiver input pin
	.equ bRdO = PORTB0 ; Channel output 1 pin red
	.equ bRdD = DDB0 ; Channel direction 1 pin red
	.equ bYeO = PORTB2 ; Channel output 1 pin yellow
	.equ bYeD = DDB2 ; Channel direction 2 pin yellow
	.equ bGnO = PORTB4 ; Channel output 3 pin green
	.equ bGnD = DDB4 ; Channel direction 3 pin green
	.equ bKyO = PORTB3 ; Key output pin for pullup
	.equ bKyI = PINB3 ; Key input pin
	.else
	; Ports ATtiny24
	.equ pOut = PORTA ; Output port
	.equ pDir = DDRA ; Direction port
	.equ pIn  = PINA ; Input port
	; Portpins
	.equ bIrO = PORTA0 ; IR receiver output pin 
	.equ bIrD = DDA0 ; IR receiver direction pin
	.equ bIrI = PINA0 ; IR receiver input pin
	.equ bRdO = PORTA1 ; Channel 1 output pin red
	.equ bRdD = DDA1 ; Channel 1 direction pin red
	.equ bYeO = PORTA2 ; Channel 2 output pin yellow
	.equ bYeD = DDA2 ; Channel 2 direction pin yellow
	.equ bGnO = PORTA3 ; Channel 3 output pin green
	.equ bGnD = DDA3 ; Channel 3 direction pin green
	.equ bKyO = PORTA1 ; Key output pin for pullup
	.equ bKyD = DDA1 ; Key direction pin
	.equ bKyI = PINA1 ; Key input pin
	.endif
;
; -------- Timing, IR signals -------
.if cAvrType == 13 ; ATtiny13
	; Clock                1200000 cs/s
	; TC0 prescaler             64
	; TC0 tick              18.750 cs/s  
	;                           53.33 us 
	; TC0 overflow          13,563 us
	.equ cTcTick = 53
	;
	.else ; ATtiny24
	; Clock                1000000 cs/s
	; TC0 prescaler             64
	; TC0 tick              15.625 cs/s
	;                           64 us
	; TC0 overflow          16.384 us
	; Pause until eval      30.000 us
	; Medium zeros             448 us
	; Medium ones            1,288 us
	; Zero/one discrim.        868 us
	.equ cTcTick = 64
	.endif
; Zero/one discrimination level:
.equ cOne = 868/cTcTick ; tn13:16; tn24:13
; Header/data bit discrimination
.equ cHead = 4*cOne ; tn13:48; tn24:39 
; Evaluation pause after last bit received
.equ cEval = 1
; Half second, MSB
.equ cSecH = 500000/cTcTick / 256 ; tn13: 36, tn24: 30
; 
; -------- Registers ----------------
; Used: R0 LCD-Routine (only if LCD attached)
; free: R1 .. R4
.def rBits= R5 ; Received bits
.def rBitsO=R6 ; Received bits display
.def rOne= R7 ; Null/Eins-Schwelle
.def rI1L = R8 ; Pre last bit burst, LSB
.def rI1H = R9 ; dto., MSB
.def rI0L = R10 ; Last bit burst, LSB
.def rI0H = R11 ; dto., MSB
.def rIRL = R12 ; Current bit burst, LSB
.def rIRH = R13 ; dto., MSB
.def rCntH= R14 ; MSB TC0 counter
.def rSreg= R15 ; Save SREG
.def rmp  = R16 ; Multi purpose register
.def rimp = R17 ; Multi purpose inside interrupts
.def rFlag= R18 ; Flags
	.equ bOvf = 0 ; Overflow, evaluate signal
	.equ bAdj = 1 ; Learn mode active
	.equ bSec = 2 ; Half second reached
	.equ bLng = 3 ; Long confirmation phase
	.equ bTwo = 4 ; Second burst
	.equ bEqu = 5 ; Equal data burst 1 and 2
	.equ bDop = 6 ; Double received
	.equ bErr = 7 ; Burst 1 and 2 not equal
.def rSel = R19
        ; 0: Red is on, 1: Red is off
        ; 2: Yellow is on, 3: Yellow is off
        ; 4: Green is on, 5: Green is off
.def rCtr = R20 ; Counter EEPROM/Input
.def rmo = R21 ; For LCD and LED control
.if cLcd == 1
	.def rLine = R22 ; Line counter LCD
	.def rRead = R23 ; Read result LCD
	.endif
; free: R24 .. R25
; Used: X for pointer operations
; Used: Y for storing in SRAM
; Used: Z for diverse purposes
;
; -------- SRAM -------------
.DSEG
.ORG 0x0060
sBuffer: ; Data storage for average calculation
.Byte 16 ; of zeros and ones
sBufferEnd:
;
sCodes:
.Byte 4 ; Recognition red, On/Off
.Byte 4 ; dto., yellow, On/Off
.Byte 4 ; dto., green, On/Off
sCodesKeyEnd:
sCodesOne:
.Byte 1 ; Zero/one discrimination level
sCodesEnd:
;
; -------- Reset and int vectors ---
.CSEG
.ORG 0x0000
.if cAvrType == 13
	rjmp Start ; RESET Vector, init
	rjmp Int0Isr ; INT0 Ext. Int Request 0
	reti ; PCINT0 Pin Change Int Request 0
	rjmp TC0OIsr ; TIM0_OVF TC0 Overflow
	reti ; EE_RDY EEPROM Ready
	reti ; ANA_COMP Analog Comparator
	reti ; TIM0_COMPA TC0 Compare Match A
	reti ; TIM0_COMPB TC0 Compare Match B
	reti ; WDT Watchdog Time-out
	reti ; ADC ADC Conversion Complete
	.else ; ATtiny24
	rjmp Start ; Reset Vector, init
	reti ; INT0 External Int Request 0
	rjmp Pci0Isr ; PCINT0 Pin Change Int 0
	reti ; PCINT1 Pin Change Int Request 1
	reti ; WDT Watchdog Time-out
	reti ; TIM1_CAPT TC1 Capture
	reti ; TIM1_COMPA TC1 Comp Match A
	reti ; TIM1_COMPB TC1 Compare Match B
	reti ; TIM1_OVF Timer/Counter1 Overflow
	reti ; TIM0_COMPA TC0 Compare Match A
	reti ; TIM0_COMPB TC0 Compare Match B
	rjmp Tc0OIsr ; TC0_OVF, MSB Timer
	reti ; ANA_COMP Analog Comparator
	reti ; ADC ADC Conversion Complete
	reti ; EE_RDY EEPROM Ready
	reti ; USI_STR USI START
	reti ; USI_OVF USI Overflow
	.endif
;
; -------- Interrupt Service Routines ------
; Check pulse from IR receiver
.if cAvrType == 24
Pci0Isr: ; ATtiny24: PCINT0 interrupt
	sbic pIn,bIRI ; Skip next if IR input low
	reti ; Ignore low phase
	.else
Int0Isr: ; ATtiny13: INT0 interrupt
	.endif
	in rSreg,SREG ; Save SREG
.if cDataW == 1 ; Display data words
	st Y+,rCntH ; Store MSB of counter
	in rimp,TCNT0 ; Read LSB of counter
	st Y+,rimp ; and store
	clr rimp ; Clear TC0
	out TCNT0,rimp
	mov rCntH,rimp
	cpi YL,Low(sBufferEnd) ; Y beyond buffer end
	brcs Int0IsrWRet ; No, continue
	sbr rFlag,1<<bOvf ; Set overflow flag
	ldi YH,High(sBuffer) ; Point to buffer start
	ldi YL,Low(sBuffer)
Int0IsrWRet:
	out SREG,rSreg ; Restore SREG
	reti
	.endif ; End of word storage
	tst rCntH ; Test long signal MSB larger than 0
	brne Int0IsrLong ; Process long signal
	in rimp,TCNT0 ; Read LSB counter
	cpi rimp,cHead ; Compare longer than header
	brcs Int0IsrShort ; No, short signal
	; Header signal received, restart
Int0IsrLong:
	clr rimp ; Restart counter
	out TCNT0,rimp
	clr rCntH ; Clear MSB counter
	mov rBitsO,rBits ; Copy bit count
	clr rBits ; Clear bit count
	clr rIRL ; Clear bit sampler
	clr rIRH
	out SREG,rSreg ; Restore SREG
	reti
Int0IsrShort:
	; Short data signal
	in rimp,TCNT0 ; Read LSB counter
	st Y+,rimp ; Store in SRAM
	cp rOne,rimp ; Compare zero/one discrimination level
	rol rIRL ; Shift bit into sampler
	rol rIRH
	inc rBits ; Increase bit counter
	cpi YL,Low(sBufferEnd) ; Buffer end?
	brne Int0IsrRet ; Not beyond
	ldi YH,High(sBuffer) ; Restart buffer
	ldi YL,Low(sBuffer)
Int0IsrRet:
	; Return from short signal
	clr rimp ; Clear TC0
	out TCNT0,rimp
	out SREG,rSreg ; Restore SREG
	reti
;
; Check counter overflows
TC0OIsr:
	in rSreg,SREG ; Save SREG
	inc rCntH ; Count overflows
	mov rimp,rCntH ; Compare with longer pause?
	cpi rimp,cSecH ; Is a half second over?
	brne TC0OIsr1 ; No
	sbr rFlag,1<<bSec ; Set second flag
	clr rCntH ; Clear MSB counter
	rjmp TC0OIsrRet ; Return
TC0OIsr1:
	cpi rimp,cEval ; Compare with evaluation period?
	brne TC0OIsrRet ; Not yet reached
	tst rBits ; Check if bits received
	breq TC0OIsrRet ; No
	sbr rFlag,1<<bOvf ; Set overflow flag
	mov rI0H,rIRH ; Copy received bits
	mov rI0L,rIRL
	mov rBitsO,rBits ; Copy number of bits
	clr rBits
TC0OIsrRet:
	out SREG,rSreg ; Restore SREG
	reti
;
; -------- Start, init ------
Start:
	; Init Stack
	ldi rmp,LOW(RAMEND) ; Point to RAMEND
	out SPL,rmp ; to stack pointer
	; Init port output directions
	sbi pDir,bRdD ; Channels, direction
	sbi pDir,bYeD
	sbi pDir,bGnD
	; Port outputs inactive
.if cActiveH == 1
	cbi pOut,bRdO ; Port pins low
	cbi pOut,bYeO
	cbi pOut,bGnO
	.else
	sbi pOut,bRdO ; Port pins high
	sbi pOut,bYeO
	sbi pOut,bGnO
	.endif
	; Init receiver input
	sbi pOut,bIrO ; Pullup IR receiver on
	; Clear flags
	clr rFlag
	; Preset zero/one discrimination level
	ldi rmp,cOne ; Default constant
	mov rOne,rmp ; to register
	; Buffer pointer for receiver values
	ldi YH,High(sBuffer) ; To buffer start
	ldi YL,Low(sBuffer)
	; Read recognition values from EEPROM
	rcall ReadCodes
	lds rOne,sCodesOne ; Copy zero/one level
	; If key input active then enter adjust
.if cAvrType == 24
	cbi pDir,bKyD ; Interim input
	sbi pOut,bKyO ; Switch pullup on
	.else
	sbi pOut,bKyO ; Switch pullup on
	.endif
	nop ; Wait a bit
	nop
	sbis pIn,bKyI ; Skip next if key input high
	sbr rFlag,1<<bAdj ; Set flag learn mode
.if cAvrType == 24
	sbi pDir,bKyD ; Channel 1 output again
	.endif
	; If force input clear all
.if cInput == 1
	rcall StartInput ; Prepare input phase
	.else
	sbrc rFlag,bAdj ; Skip if learn mode flag clear
	rcall StartInput ; Prepare input phase
	.endif
	; If an LCD is attached, init LCD
.if cLcd == 1
    ; Init LCD control ports
	cbi pLcdCO,bLcdCOE ; Clear output E
	cbi pLcdCO,bLcdCORS ; Clear output RS
	cbi pLcdCO,bLcdCORW ; Clear output RW
	sbi pLcdCR,bLcdCRE ; Direction E output
	sbi pLcdCR,bLcdCRRS ; Direction RS output
	sbi pLcdCR,bLcdCRRW ; Direction RW output
	; Data port LCD
	rcall LcdStart ; Init and text display
	.endif
	; Start timer TC0
	ldi rmp,(1<<CS01)|(1<<CS00) ; Prescaler to 64
	out TCCR0B,rmp ; to TC0 control port B
	ldi rmp,1<<TOIE0 ; Enable TC0 overflow int
	out TIMSK0,rmp ; in TC0 interrupt mask
	; Sleep mode idle, INT0 resp. PCINT0 IR receiver
.if cAvrType == 13
	ldi rmp,(1<<SE)|(1<<ISC01) ; Falling edges
	out MCUCR,rmp ; in master control port
	ldi rmp,1<<INT0 ; Enable INT0 interrupts
	out GIMSK,rmp ; in general interrupt mask
	.else
	ldi rmp,1<<SE ; Sleep-Mode Idle
	out MCUCR,rmp ; to master control port
	ldi rmp,1<<PCINT0 ; Pin A0 level change ints
	out PCMSK0,rmp ; to PCINT mask 0
	ldi rmp,1<<PCIE0 ; Enable PCINT0 interrupts
	out GIMSK,rmp ; in general interrupt mask
	.endif
	; Enable interrupts
	sei ; by setting I flag in SREG
Loop:
	sleep ; go to sleep
	nop ; Wake up
	sbrc rFlag,bOvf ; Skip next if overflow flag clear
	rcall Overflow ; Process overflow
	sbrc rFlag,bSec ; Skip next if second flag clear
	rcall Second ; Process second
	rjmp Loop ; go back to sleep
;
Second:
	; A half second is over
	cbr rFlag,1<<bSec ; Clear second flag
	; Only if in adjust mode
	sbrs rFlag,bAdj ; Skip next if adjust flag is set
	ret ; Not in adjust mode
	; Long confirmation phase?
	sbrs rFlag,bLng ; Skip next if long flag is set
	rjmp LedBlink ; No, go to blink
	; Long confirmation phase at end?
	dec rCtr ; Decrease counter
	brne SecondRet ; Not ending
	; Longe confirmation phase is over
	cbr rFlag,1<<bLng ; Clear long flag
	; Next Input position
	rcall LedOff ; Current LED/channel off
	subi rSel,-2 ; Add two
	cpi rSel,6 ; Already at rSel = 6
	brcs LedBlink ; Not yet, blink
	brne SecondEnd ; All adjusted
	ldi rSel,1 ; Go on with off codes
	rjmp LedBlink ; Blink
SecondEnd:
	; All channels adjusted
	rcall WriteCodes ; EEPROM write
	clr rSel ; Restart with channel 1 on
	cbr rFlag,1<<bAdj ; Clear adjust flag
	; All outputs off
.if cActiveH == 1
	cbi pOut,bRdO ; Output low
	cbi pOut,bYeO
	cbi pOut,bGnO
	.else
	sbi pOut,bRdO ; Output high
	sbi pOut,bYeO
	sbi pOut,bGnO
	.endif
SecondRet:
	ret
;
; Blink LED rSel
LedBlink:
	rcall GetOutPin ; Active pin to rmp
	in rmo,pOut ; Read pin
	eor rmp,rmo ; Inverse polarity
	out pOut,rmp ; Write pins
	ret
;
; LED rSel on
LedOn:
	rcall GetOutPin ; Active pin to rmp
	in rmo,pOut ; Read pins
.if cActiveH == 1
	or rmp,rmo ; Activate pin
	.else
	com rmp ; Reverse
	and rmp,rmo ; Clear pin
	.endif
	out pOut,rmp ; Write pins
	ret
;
; LED rSel off
LedOff:
	rcall GetOutPin ; Active pin to rmp
	in rmo,pOut ; Read pins
.if cActiveH == 1
	com rmp ; Reverse
	and rmp,rmo ; Clear active pin
	.else
	or rmp,rmo ; Set active pin
	.endif
	out pOut,rmp ; Write pins
	ret
;
; Get current output pin to rmp
GetOutPin:
	mov rmp,rSel ; Copy current position
	lsr rmp ; Shift on/off bit to carry
	cpi rmp,1 ; Yellow LED active?
	brcs GetOutPinRd ; No, red
	brne GetOutPinGn ; No, green
	ldi rmp,1<<bYeO ; Yellow active
	ret
GetOutPinRd:
	ldi rmp,1<<bRdO ; Red active
	ret
GetOutPinGn:
	ldi rmp,1<<bGnO ; Green active
	ret
;
; Overflow of the timer, evaluate result
Overflow:
	cbr rFlag,1<<bOvf ; Clear flag
.if cLcd == 1
  .if cDataW == 1
	ldi rmp,0x01 ; Clear LCD
	rcall LcdC4Byte
	ldi XH,High(sBuffer) ; X points to buffer
	ldi XL,Low(sBuffer)
	clr rLine ; Line 0
	RawDataW1:
	ld rmp,X+
	rcall LcdHex
	ld rmp,X+
	rcall LcdHex
	ldi rmp,' '
	rcall LcdD4Byte
	mov rmp,XL
	andi rmp,0x07
	brne RawDataW1
	inc rLine
	cpi rline,4
	brcc RawDataWRet
	mov ZH,rLine
	clr ZL
	rcall LcdPos
	rjmp RawDataW1
	ldi YH,High(sBuffer) ; Y points to buffer
	ldi YL,Low(sBuffer)
	RawDataWRet:
	ret
	.endif
  .endif
	; Check adjust mode
	sbrs rFlag,bAdj ; Skip next if adjust flag set
	rjmp OverflowCheck ; Adjust is off
	; Learn mode
OverflowCalc:
	clr rmp ; Disable ints
	out GIMSK,rmp
	out TIMSK0,rmp
	; Calculate average duration of the last 16 bits
	ldi ZH,High(sBuffer) ; Z to buffer
	ldi ZL,Low(sBuffer)
	clr XH ; Clear X as sum
	clr XL
OverflowSum:
	ld rmp,Z+ ; Byte from buffer
	add XL,rmp ; Add to sum
	ldi rmp,0 ; Overflow adder
	adc XH,rmp ; Add overflow
	cpi ZL,LOW(sBufferEnd) ; End of buffer?
	brne OverflowSum ; No, continue
	lsr XH ; Divide by 2
	ror XL
	lsr XH ; by 4
	ror XL
	lsr XH ; by 8
	ror XL
	lsr XH ; by 16
	ror XL
	mov rOne,XL ; Copy to zero/one recognition register
	sts sCodesOne,XL ; and to SRAM
	; Compare with the previous value
	sbrc rFlag,bTwo ; Skip next if second burst flag clear
	rjmp OverflowTwo ; Compare two values
	sbr rFlag,1<<bTwo ; Set second burst flag
	mov rI1L,rI0L ; Copy value
	mov rI1H,rI0H
	rjmp OverflowNormal
OverflowTwo:
	; Compare current and previous value
	cbr rFlag,(1<<bTwo)|(1<<bEqu) ; Clear flags
	cp rI1L,rI0L ; Compare LSB
	brne OverflowNormal ; Unequal
	cp rI1H,rI0H ; Compare MSB
	brne OverflowNormal ; Unequal
	; Equal, check code
	sbr rFlag,1<<bEqu ; Set equal flag
	ldi XH,High(sCodes) ; X  to codes
	ldi XL,Low(sCodes) ; in SRAM
OverflowExisting:
	cpi XL,Low(sCodesKeyEnd)
	breq OverflowKeyOk ; End of codes reached
	ld rmp,X+ ; Read LSB
	cp rmp,rI1L ; compare LSB
	ld rmp,X+ ; Read MSB
	brne OverflowExisting ; LSB unequal, next code
	cp rmp,rI1H ; Compare MSB
	brne OverflowExisting ; MSB unequal, next code
	sbr rFlag,1<<bDop ; Set double flag
	rjmp OverflowNormal ; 
	; Store code, start long confirmation
OverflowKeyOk:
	ldi XH,High(sCodes) ; Code Start
	ldi XL,Low(sCodes)
	mov rmp,rSel ; Copy active channel
	lsl rmp ; Multiply by two
	add XL,rmp ; Add to pointer
	ldi rmp,0 ; Overflow adder
	adc XH,rmp ; Add carry
	st X+,rI1L ; Store value
	st X,rI1H
	rcall LedOn ; Switch LED on
	ldi rCtr,5 ; Long confirmation phase
	sbr rFlag,(1<<bEqu)|(1<<bLng) ; Set flags
	rjmp OverflowNormal
OverflowCheck:
	; Check received data
	ldi XH,High(sCodes) ; Pointer to codes
	ldi XL,Low(sCodes)
	ser rSel ; Start with 0xFF
OverflowCheck1:
	inc rSel ; Next value
	cpi rSel,6 ; End reached?
	brcc OverflowNf ; Yes, not found in list
	ld rmp,X+ ; Read value from SRAM
	cp rmp,rI0L ; Compare LSB
	ld rmp,X+ ; Read MSB from SRAM
	brne OverflowCheck1 ; LSB unequal
	cp rmp,rI0H ; Compare MSB
	brne OverflowCheck1 ; MSB unequal
	; Correct code recognized
	cbr rFlag,(1<<bErr)|(1<<bDop) ; Clear flags
	sbrs rSel,0 ; Skip next if lowest bit set
	rcall LedOn ; Switch pin active
	sbrc rSel,0 ; Skip next if lowest bit clear
	rcall LedOff ; Switch pin inactive
	mov rI1H,rI0H ; Copy counter value
	mov rI1L,rI0L
	rjmp OverflowNormal
OverflowNf:
	; Not equal
	clr rSel ; Clear selected channel
	sbr rFlag,1<<bErr ; Set error flag
OverflowNormal:
; If LCD is attached: display
.if cLcd == 1
	; If raw data bytes
	.if cDataB == 1
		ldi rmp,0x01 ; Clear LCD
		rcall LcdC4Byte
		ldi XH,High(sBuffer) ; X pointer to buffer
		ldi XL,Low(sBuffer)
		clr rLine ; To line 0
	RawData1:
		ld rmp,X+ ; Read byte from buffer
		rcall LcdHex ; Display in hex
		ldi rmp,' ' ; Blank
		rcall LcdD4Byte
		cpi XL,Low(sBufferEnd) ; Complete?
		brcc RawDataEnd ; Yes
		mov rmp,XL ; Four bytes per line
		andi rmp,0x03 ; Next line?
		brne RawData1 ; No
		inc rLine ; Next line
		mov ZH,rLine
		clr ZL
		rcall LcdPos
		rjmp RawData1 ; Continue
	RawDataEnd:
		mov rmp,rOne ; Zero/one level
		rcall LcdHex ; in hex
		.else
		; LCD attached, diverse data
		ldi ZH,1 ; Number of bits to line 2
		ldi ZL,5
		rcall LcdPos
		mov rmp,rBitsO
		rcall LcdHex
		ldi ZH,1 ; rSel in line 2
		ldi ZL,12
		rcall LcdPos
		mov rmp,rSel
		andi rmp,0x07
		subi rmp,-'0'
		rcall LcdD4Byte
		ldi ZH,1 ; Flags in line 2
		ldi ZL,18
		rcall LcdPos
		ldi rmp,' '
		sbrc rFlag,bAdj
		ldi rmp,'A'
		sbrc rFlag,bErr
		ldi rmp,'E'
		rcall LcdD4Byte
		ldi ZH,2 ; Last two values
		ldi ZL,4 ; in line 3
		rcall LcdPos
		mov rmp,rI0H
		rcall LcdHex
		mov rmp,rI0L
		rcall LcdHex
		ldi ZH,2
		ldi ZL,13
		rcall LcdPos
		mov rmp,rI1H
		rcall LcdHex
		mov rmp,rI1L
		rcall LcdHex
		ldi ZH,3 ; Zero/one level in line 3
		ldi ZL,7
		rcall LcdPos
		mov rmp,rOne
		rcall LcdHex
		ldi ZH,3 ; Equal/unequal double flag in line 4
		ldi ZL,18
		rcall LcdPos
		ldi rmp,'U' ; Unequal
		sbrc rFlag,bEqu ; Skip if equal
		ldi rmp,'E' ; Equal
		sbrc rFlag,bDop ; Double?
		ldi rmp,'D'
		sbrc rFlag,bErr ; Skip if error flag clear
		ldi rmp,'E'
		rcall LcdD4Byte
		.endif
	.endif
	; Clear MSB, enable interrupts
	clr rCntH
	ldi rmp,1<<TOIE0 ; Timer Int
	out TIMSK0,rmp
.if cAvrType == 13
	ldi rmp,1<<INT0 ; Enable INT0 interrupts
	out GIMSK,rmp
	.else
	ldi rmp,1<<PCIE0 ; Enable PCINT0 interrupts
	out GIMSK,rmp ; in interrupt mask
	.endif
	ret
;
; Read codes from the EEPROM
ReadCodes:
	ldi ZH,0 ; Pointer to EEPROM address
	ldi ZL,0
	ldi XH,High(sCodes) ; Pointer to SRAM
	ldi XL,Low(sCodes)
	ldi rmp,sCodesEnd-sCodes ; Number of bytes
	mov rCtr,rmp
ReadCodes1:
	sbic EECR,EEPE ; Wait until EEPROM ready
	rjmp ReadCodes1
	out EEARL,ZL ; Address port
	sbi EECR,EERE ; Read enable
	in rmp,EEDR ; Read byte
	st X+,rmp ; Store in SRAM
	adiw ZL,1 ; Next address
	dec rCtr ; Count down
	brne ReadCodes1 ; Read further
	cpi rmp,0x00 ; Zero/one level zero?
	breq ReadCodes2 ; Yes, data corrupt
	cpi rmp,0xFF ; Zero/one at FF?
	brne ReadCodes3 ; Yes, data corrupt
ReadCodes2:
	rjmp StartInput ; Data corrupt, restart learning mode
ReadCodes3: ; Data ok
	ret
;
; Write codes to EEPROM
WriteCodes:
	ldi ZH,0 ; Pointer to EEPROM address
	ldi ZL,0
	ldi XH,High(sCodes) ; Pointer to SRAM
	ldi XL,Low(sCodes)
	ldi rmp,sCodesEnd-sCodes
	mov rCtr,rmp
WriteCodes1:
	sbic EECR,EEPE ; Wait until EEPROM ready
	rjmp WriteCodes1 ; Wait on
	clr rmp ; Erase and write mode
	out EECR,rmp ; to control port
	out EEARL,XL ; Address write
	ld rmp,X+ ; Byte from SRAM
	out EEDR,rmp ; to data port
	cli ; Disable interrupts
	sbi EECR, EEMPE ; Set Master Program
	sbi EECR, EEPE ; Program enable
	sei ; Enable interrupts
	dec rCtr ; Byte counter
	brne WriteCodes1 ; Continue
	ret
;
; Restart: clear all codes, set default zero/one level,
; Channel zero, set adjust flag
StartInput:
	ldi ZH,High(sCodes) ; Pointer to codes
	ldi ZL,Low(sCodes)
	clr rmp ; Clear codes
StartInput1:
	st Z+,rmp ; Write code
	cpi ZL,Low(sCodesEnd) ; Code table end?
	brne StartInput1 ; Go on
	ldi rmp,cOne ; Define default zero/one level
	sts sCodesOne,rmp ; to SRAM
	mov rOne,rmp ; and to register
	clr rSel ; Start with channel 1 and on
	sbr rFlag,1<<bAdj ; Set learn mode
	ret
;
.if cLcd == 1 ; Code for LCD
	; Load LCD include
	.include "Lcd4Busy.inc"
	; Start LCD
	LcdStart:
		rcall LcdInit ; Init the LCD
		ldi ZH,High(2*TextOutput)
		ldi ZL,Low(2*TextOutput)
		rjmp LcdText ; Display text
	; LCD text to be displayed
	TextOutput:
	.db " IR Rx Switch tn24 ",0x0D
	.db "Bits=xx Sel=x Flg=x",0x0D
	;         5      12    18
	.db "0 = xxxx 1 = xxxx  ",0x0D
	;        4        13
	.db " One = xx  Comp = x",0xFE
	;           7          18
	;
	; Byte in rmp in hex on LCD
	LcdHex:
		push rmp ; Save rmp
		swap rmp ; Upper nibble first
		rcall LcdHexN ; Display nibble
		pop rmp ; Restore rmp
		; Display nibble in hex on LCD
	LcdHexN:
		andi rmp,0x0F ; Mask lowr nibble
		subi rmp,-'0' ; Add ASCII-0
		cpi rmp,'9'+1 ; A to F?
		brcs LcdHexN1 ; No
		subi rmp,-7 ; Adjust to A to F
	LcdHexN1:
		rjmp LcdD4Byte ; Send rmp to LCD
	.endif
;
; Predefinition of keys
; (Example TV remote controller)
.ESEG
.ORG 0x00
EeStart:
 ; Red, key 1 on, key 4 off
  .DW 0x0835,0xC8F5
 ; Yellow, key 2 on, key 5 off
  .DW 0x88B5,0x2815
 ; Green, key 3 on, key 6 off
  .DW 0x4875,0xA895
  ; Zero/one discrimination level
  .DB cOne
EeEnd:
;
; End of source code
;

One new instruction used here is ROR register. It rolls the content of the register one position to the right, rolls the carry flag into bit 7 of the register and rolls bit 0 of the register to the carry flag, all in one single step.

The code requires 57% of the flash memory of an ATtiny13 and 45% of the SRAM (without stack operation) and so fits perfectly into this small device:

ATtiny13 memory use summary [bytes]:
Segment   Begin    End      Code   Data   Used    Size   Use%
---------------------------------------------------------------
[.cseg] 0x000000 0x000248    584      0    584    1024  57.0%
[.dseg] 0x000060 0x00007d      0     29     29      64  45.3%
[.eseg] 0x000000 0x00000d      0     13     13      64  20.3%

There is enough space left for additional needs.

Home Top IR Conditioned Hardware Measuring Transmit Receive Switch


©2017 by http://www.avr-asm-tutorial.net