Path: Home => AVR-EN => Applications => Signalgenerator M16 => Software
Signalgenerator AVR applications

Sine/Triangle/Rectangle/Saw-tooth signal generator with ATmega16
Software for the signal generator
Logo

Signalgenerator ATmega16 software listing

The following software is listed here and can be found in the orginal source format on the following links:
  1. siggen_m16_v1.asm, listing here
  2. wavetab.inc, listing here,
  3. lcd.inc,listing here

Main source signalgenerator_m16_v1


;
; ***********************************************
; * Signal generator ATmega16: Saw-tooth/Sine   *
; * Triangle/Rectangle, with R/2R-DAC+8-Bit-LCD *
; * Version 1, September 2018                   *
; * (C)2018 by http://www.avr-asm-tutorial.net  *
; ***********************************************
;
; Include file with type-specific constants
.NOLIST
.INCLUDE "m16def.inc" ; Header file for ATmega16
.LIST
;
; ============================================
;    S I M U L A T I O N - S W I T C H E S
; ============================================
;
; Final version: All debug flags = 0
; Debug version: Debugging with simulator, flags = 1
;
; Simulation of CTC values to adjust frequency
.equ debug_CtcCalc = 0 ; Debugging the calculation routine for CTC
.if debug_CtcCalc == 1
  .equ cFrequency = 2 ; Frequency to be simulated in Hz
  .equ cCurve = 1 ; Wave form (0..3)
    ; 00 = Saw-tooth, 01 = Sine, 10 = Triangle, 11 = Rectangle
  .endif
;
; Simulation of the transfer of the calculated values
;   (is only executed if debug_CtcCalc is active)
.equ debug_Transfer = 0 ; Debugging of value transfer
;
; Simulation of the change from the first to the second wave
;   defines frequency and switches of the second wave
.equ debug_Second = 0 ; Fast transfer from first to second
.if debug_Second == 1
  .equ cFrequency2 = 20000 ; The next frequency
  .equ cCurve2 = 0 ; The next wave form
  .equ cTCCR02 = (1<<WGM01)|(1<<CS01) ; Prescaler = 8
  .endif
;
; Switch TC1 off during simulation
.equ debug_TC1off = 0 ; 1 = TC1 off
;
; Simulation without LCD
;   (This switch can be set together with other switches)
.equ debug_NoLcd = 0 ; Debuggen without LCD operations
;
.equ debug_insert = 0 ; Debugging the decimal point/thousand routine
  .if debug_insert == 1
  .equ InsertDecPos = 5
  .equ InsertDecChar = '.'
  .equ InsertThdPos = 1
  .equ InsertThdChar = ','
  .endif
;
; When starting stop after LCD init
.equ debug_lcdonly = 0 ; Test the LCD only (init)
;
; ============================================
;   H A R D W A R E I N F O R M A T I O N E N 
; ============================================
;
; Device = ATmega16
;              ___________
;           1 /           |40
; S1A-S2A o--|PB0     ADC0|--o Frequency adjust (Potentiometer)
; S1B-S2B o--|PB1      PA1|--o S1 00 = 2 - 20 Hz, 01 = 20 - 200 Hz
; S1C-S1C o--|PB2      PA2|--o S2 10 = 200 - 2000 Hz, 11 = 2 - 20 kHz
; S1D-S2D o--|PB3      PA3|--o S3 Curve 0, 00 = Saw-tooth, 01 = Sine
;      NC o--|PB4      PA4|--o S4 Kurve 1, 10 = Triangle, 11 = Rectangle
;    MOSI o--|PB5      PA5|--o LCD-Ctrl RS
;    MISO o--|PB6      PA6|--o LCD-Ctrl R/W
;     SCK o--|PB7      PA7|--o LCD-Ctrl E
;  10k+5V o--|RESET   AREF|--o C=100n
;     +5V o--|VCC      GND|--o 0V
;      0V o--|GND     AVCC|--o C=100n, L=22uH
;    Xtal o--|XTAL2    PC7|--o LCD-D7
;  16 MHz o--|XTAL1    PC6|--o LCD-D6
;  DAC-D0 o--|PD0      PC5|--o LCD-D5
;  DAC-D1 o--|PD1      PC4|--o LCD-D4
;  DAC-D2 o--|PD2      PC3|--o LCD-D3
;  DAC-D3 o--|PD3      PC2|--o LCD-D2
;  DAC-D4 o--|PD4      PC1|--o LCD-D1
;  DAC-D5 o--|PD5      PC0|--o LCD-D0
;  DAC-D6 o--|PD6      PD7|--o DAC-D7
;          20|____________|21
;
;
; ============================================
;      P O R T S   A N D   P I N S 
; ============================================
;
; Ports and Pins for RC filter control
.equ pRcfOut = PORTB ; Output port RC filter
.equ pRcfDir = DDRB  ; Direction port RC filter
.equ bRcfA   = PORTB0 ; Pin RC filter A
.equ bRcfB   = PORTB1 ; Pin RC filter B
.equ bRcfC   = PORTB2 ; Pin RC filter C
.equ bRcfD   = PORTB3 ; Pin RC filter D
.equ mRcf    = (1<<bRcfA)|(1<<bRcfB)|(1<<bRcfC)|(1<<bRcfD) ; Mask
; Ports, pins and masks Digital-Analog-Converter
.equ pDacOut = PORTD ; Output port DAC
.equ pDacDir = DDRD ; Direction port DAC
.equ mDac8   = 0b11111111 ; Mask 256 stage DAC
; Ports and pins for LCD control and data bus
;   (see LCD properties)
; Ports, pins and masks switches
.equ pSwOut  = PORTA ; Output port switches
.equ pSwDir  = DDRA  ; Direction port switches
.equ pSwIn   = PINA  ; Input port switches
.equ bSTune0 = PORTA1 ; Switch 1
.equ bSTune1 = PORTA2 ; Switch 2
.equ bSWave0 = PORTA3 ; Switch 3
.equ bSWave1 = PORTA4 ; Switch 4
.equ mSwPu   = (1<<bSTune0)|(1<<bSTune1)|(1<<bSWave0)|(1<<bSWave1) ; Mask
;
; ============================================
;     L C D   P A R A M E T E R   S E T
; ============================================
;
; Standard parameter set of properties/definitions
.equ clock = 16000000 ; Clock frequency of controller in Hz
; LCD size:
  .equ LcdLines = 1 ; Number of lines (1, 2, 4)
  .equ LcdCols = 8 ; Number of characters per line (8..24)
; LCD bus interface
  .equ LcdBits = 8 ; Bus size (4 or 8)
  ; If 4 bit bus:
    ;.equ Lcd4High = 1 ; Bus nibble (1=Upper, 0=Lower)
  .equ LcdWait = 0 ; Access mode (0 with busy, 1 with delay loops)
; LCD data ports
  .equ pLcdDO = PORTC ; Data output port
  .equ pLcdDD = DDRC ; Data direction port
; LCD control ports und pins
  .equ pLcdCEO = PORTA ; Control E output port
  .equ bLcdCEO = PORTA7 ; Control E output portpin
  .equ pLcdCED = DDRA ; Control E direction port
  .equ bLcdCED = DDA7 ; Control E direction portpin
  .equ pLcdCRSO = PORTA ; Control RS output port
  .equ bLcdCRSO = PORTA5 ; Control RS output portpin
  .equ pLcdCRSD = DDRA ; Control RS direction port
  .equ bLcdCRSD = DDA5 ; Control RS direction portpin
; If LcdWait = 0:
  .equ pLcdDI = PINC ; Data input port
  .equ pLcdCRWO = PORTA ; Control RW output port
  .equ bLcdCRWO = PORTA6 ; Control RW output portpin
  .equ pLcdCRWD = DDRA ; Control RW direction port
  .equ bLcdCRWD = DDA6 ; Control RW direction portpin
; If you need binary to decimal conversion:
  ;.equ LcdDecimal = 1 ; If defined: include those routines
; If you need binary to hexadecimal conversion:
  ;.equ LcdHex = 1 ; If defined: include those routines
; If simulation in the SRAM is desired:
  ;.equ avr_sim = 1 ; 1=Simulate, 0 or undefined=Do not simulate
;
; ================================================
;     C O N S T A N T S   T O   A D J U S T
; ================================================
;
.equ fstart  = 10000 ; Frequency at start-up in Hz
.equ curve = 1 ; Initial wave form, can be:
;                0=Saw-tooth, 1=Sine, 2=Triangle, 3=Rectangle
;
; Language selection:
;   influences decimal point and thousand separator
.equ LangEn = 1 ; 0 = German, 1 = English
;
; =======================================================
;  F I X E D   A N D   D E R I V E D   C O N S T A N T S 
; =======================================================
;
; Conversion of start frequency to SRAM table parameters
.if fstart < 2500
  .equ cFStart = (clock / 256 + fstart/2) / fstart - 1
  .equ cAdd = 1
  .equ cAnd = 0xFF
  .endif
.if (fstart>=2500)&&(fstart<5000)
  .equ cFStart = (clock / 256 * 2 + fstart/2) / fstart - 1
  .equ cAdd = 2
  .equ cAnd = 0x7F
  .endif
.if (fstart>=5000)&&(fstart<10000)
  .equ cFStart = (clock / 256 * 4 + fstart/2) / fstart - 1
  .equ cAdd = 4
  .equ cAnd = 0x3F
  .endif
.if (fstart>=10000)&&(fstart<=20000)
  .equ cFStart = (clock / 256 * 8 + fstart/2) / fstart - 1
  .equ cAdd = 8
  .equ cAnd = 0x1F
  .endif
;
; Constant cAdcAdd: Software counter for ADC measurements
;
.equ cTc0Presc = 1024 ; TC0 prescaler
.equ cTc0Comp  = 243 ; TC0 compare match
.equ cAdcAdd   = 16 ; 16 compare match ADC cycles
;
; Constants for frequency calculation
;
; Gradients for frequency calculation
.equ cM0 = (65536*(20-2)+127)/255 ; Range 2-20 Hz
.equ cM1 = (65536*(200-20)+127)/255 ; Range 20-200 Hz
.equ cM2 = (256*(2000-200)+127)/255 ; Range 200-2,000 Hz
.equ cM3 = (256*(20000-2000)+127)/255 ; Range 2-20 kHz
;
; Adder for potentiometer position 0 degrees
.equ cA0 = 65536*2 ; Range 2-20 Hz
.equ cA1 = 65536*20 ; Range 20-200 Hz
.equ cA2 = 256*200 ; Range 200-2,000 Hz
.equ cA3 = 256*2000 ; Range 2-20 kHz
;
; Potentiometer values for fine tuning
.equ cP10 = ((10-2)*255+(20-2)/2)/(20-2)+1 ; 10 Hz level
.equ cP100 = ((100-20)*255+(200-20)/2)/(200-20)+1 ; 100 Hz level
.equ cP1000 = ((1000-200)*255+(2000-200)/2)/(2000-200) ; 1,000 Hz level
.equ cP2500 = ((2500-2000)*255+(20000-2000)/2)/(20000-2000) ; 2,500 Hz level
.equ cP5000 = ((5000-2000)*255+(20000-2000)/2)/(20000-2000) ; 5,000 Hz level
.equ cP10000 = ((10000-2000)*255+(20000-2000)/2)/(20000-2000) ; 10,000 Hz level
;
; Potentiometer values for capacitor switching
.equ cPC0 = 0 ; Complete range 0
.equ cC0L = 0x0F ; All C on
.equ cC0H = 0x0F ; All C on
.equ cPC1 = 0 ; Complete range 1
.equ cC1L = 0x07 ; All but the highest
.equ cC1H = 0x07 ; All but the highest
.equ cPC2 = 43 ; Range 2
.equ cC2L = 0x04 ; C on less than level
.equ cC2H = 0x03 ; C on higher or equal level
.equ cPC3 = 0 ; Complete fine tuning range 3
.equ cC3L = 0x02 ; Only 1 nF on
.equ cC3H = 0x02 ; Only 1 nF on
.equ cPC46 = 0 ; Complete fine tuning ranges 4 to 6
.equ cC46L = 0x01 ; Only 470 pF on
.equ cC46H = 0x01 ; Only 470 pF on
;
; Divisor for CTC calculation
.equ cDiv01 = clock  ; Divisor Clock/(256*frequency), 1 wave per cycle
.equ cDiv23 = clock/256  ; Divisor Clock/frequency, 1 wave per cycle
.equ cDiv4 = clock/128 ; Divisor Clock/frequency, 2 waves per cycle
.equ cDiv5 = clock/64 ; Divisor Clock/frequency, 4 waves per cycle
.equ cDiv6 = clock/32 ; Divisor Clock/frequency, 8 waves per cycle
;
; Division constants for display calculation
.equ cND0 = ((10*clock+128)/256)*1000 ; 2..9.9999 Hz, 1 wave, 4 decimal digits
.equ cND1 = ((10*clock+128)/256)*100 ; 10..99.999 Hz, 1 wave, 3 decimal digits
.equ cND2 = ((10*clock+128)/256)*10 ; 100..999.99 Hz, 1 wave, 2 decimal digits
.equ cND3 = (10*clock+128)/256 ; 1,000..2,499.9 Hz, 1 wave, 1 decimal digit
.equ cND4 = (10*clock+64)/128 ; 2.500..4.999,9 100 Hz, 2 waves, 1 decimal digit
.equ cND5 = (10*clock+32)/64 ; 5.000..9.999,9 Hz, 4 waves, 1 decimal digit
.equ cND6 = (clock+16)/32 ; 10.000..20.000 Hz, 8 waves, 0 decimal digits
;
; Conversion of debug parameters
;
; Debug the Ctc calculation
.if debug_CtcCalc == 1 ; Simulate CTC calculation if 1
  .if cFrequency < 20
    .equ cCourseTune = 0 ; Course range frequency (0..3)
    .equ cPot = (65536*cFrequency - cA0) / cM0
    .endif
  .if (cFrequency>=20)&&(cFrequency<200)
    .equ cCourseTune = 1 ; Course range frequency (0..3)
    .equ cPot = (65536*cFrequency - cA1) / cM1
    .endif
  .if (cFrequency>=200)&&(cFrequency<2000)
    .equ cCourseTune = 2 ; Course range frequency (0..3)
    .equ cPot = (256*cFrequency - cA2) / cM2
    .endif
  .if (cFrequency>=2000)&&(cFrequency<=20000)
    .equ cCourseTune = 3 ; Course range frequency (0..3)
    .equ cPot = (256*cFrequency - cA3) / cM3
    .endif
  .endif
;
; Debug the change to a second wave form and/or frequency
.if debug_Second == 1
  .if cFrequency2 < 20
    .equ cCourseTune2 = 0
    .equ cAdc2 = (1023*(cFrequency2-2))/18
    .else
    .if cFrequency2 < 200
      .equ cCourseTune2 = 1
      .equ cAdc2 = (1023*(cFrequency2-20))/180
      .else
      .if cFrequency2 < 2000
        .equ cCourseTune2 = 2
        .equ cAdc2 = (1023*(cFrequency2-200))/1800
       .else
        .equ cCourseTune2 = 3
        .equ cAdc2 = (1023*(cFrequency2-2000))/18000
        .endif
      .endif
    .endif
  .equ cPot2 = cAdc2/4
  .equ cTCCR0 = (1<<WGM01)|(1<<CS01)|(1<<CS00) ; Prescaler = 8
    ; for acceleration of the 16 pseudo measurements
  .endif
;
; ============================================
;                T I M I N G
; ============================================
;
; Timer TC1 (16 bit PWM with CompA interrupt):
;   handles the output of the signals via R/2R-DAC
;
;   Clock frequency        = 16.000.000 Hz
;   Prescaler              =          1
;
;   f = clock frequency / 256 * waves per cycle / (CTC+1)
;   (waves per cycle = number of wave curves in 256 steps)
;   (Fine tuning range: 0..6  in brackets)
;
;   +--------------+----------+--------+---------+
;   | Fine range   | Frequency|  Waves | TC1-CTC |
;   |--------------+----------+--------+---------+
;   | 2 - 20 Hz    | 2 Hz     |    1   |  31,249 |
;   | (0)          | 20 Hz    |        |   3,124 |
;   +--------------+----------+--------+---------+
;   | 20 - 200 Hz  | 20 Hz    |    1   |   3,124 |
;   | (1)          | 200 Hz   |        |     312 |
;   +--------------+----------+--------+---------+
;   | 200 - 2000 Hz| 200 Hz   |    1   |     312 |
;   | (2)          | 2000 Hz  |        |      30 |
;   +--------------+----------+--------+---------+
;   | 2 - 2.5 kHz  | 2 kHz    |    1   |      30 |
;   | (3)          | 2.5 kHz  |        |      24 |
;   +--------------+----------+--------+---------+
;   | 2.5 - 5 kHz  | 2.5 kHz  |    2   |      49 |
;   | (4)          | 5 kHz    |        |      24 |
;   +--------------+----------+--------+---------+
;   | 5 - 10 kHz   | 5 kHz    |    4   |      49 |
;   | (5)          | 10 kHz   |        |      24 |
;   +--------------+----------+--------+---------+
;   | 10 - 20 kHz  | 10 kHz   |    8   |      49 |
;   | (6)          | 20 kHz   |        |      24 |
;   +--------------+----------+--------+---------+
;
;   Extreme frequency limits:
;     Lowest frequency : clock / 256 / 65536  = 0.954 Hz
;     Highest frequency: clock / 256 * 8 / 21 = 23.8 kHz
;
; Timing TC0:
;   controls the AD conversion and the update of the
;   frequency and wave form adjustment
;
;   clock frequency TC0   = 16,000,000 Hz
;   Prescaler TC0         =      1,024
;   CTC compare A TC0     =        243
;   Cycle frequency       =         64 Hz
;   Cyle duration         =         15.6 ms
;   Number of conversions =         16
;   Update cycle          =        249.9 ms
;   Update frequency      =          4.002 Hz
;
; ============================================
;   R E G I S T E R   D E F I N I T I O N S
; ============================================
;
.def rN0 = R0 ; Multi calc register, Byte 0
.def rN1 = R1 ; dto., Byte 1
.def rN2 = R2 ; dto., Byte 2
.def rN3 = R3 ; dto., Byte 3
.def rN4 = R4 ; dto., Byte 4
.def rN5 = R5 ; dto., Byte 5
.def rN6 = R6 ; dto., Byte 6
.def rCnt = R7 ; Byte counter for R/2R table
.def rAdd = R8 ; Adder for R/2R table
.def rCntRst = R9 ; Byte counter for restart R/2R table
.def rTabEL = R10 ; Table end, LSB only
.def rTabAL = R11 ; Table start, LSB
.def rTabAH = R12 ; dto., MSB
.def rAdcL = R13 ; ADC result sum, LSB
.def rAdcH = R14 ; dto, MSB
.def rSreg = R15 ; Status port save/restore
.def rmp = R16 ; Multi purpose register
.def rimp = R17 ; Multi purpose inside ints
.def rFlag = R18 ; Flag register
  .equ bStart = 0 ; R/2R cycle complete, restart table
  .equ bSetR2R = 1 ; Transfer flag for new R/2R wave table
  .equ bSetCtc = 2 ; Transfer flag for new CTC value
.def rAdc  = R19 ; ADC average counter
.def rAnd = R20 ; And register for R/2R table write
.def rPot = R21 ; Potentiometer value
; Free: R22..R25
; Used: R27:R26 = X for copying R/2R table
; Used: R29:R28 = Y SRAM address R/2R table output
; Used: R31:R30 = Z for flash table read outside ints
;
; ============================================
;       S R A M   D E F I N I T I O N S
; ============================================
;
.DSEG
.ORG  SRAM_START
; Disposing current parameters
sCurrStart:
sR2RCurrL: ; 0x0060
  .Byte 1 ; Current R/2R table address in flash, LSB
sR2RCurrH: ; 0x0061
  .Byte 1 ; dto., MSB
sR2RCurrAdd: ; 0x0062
  .Byte 1 ; Current R/2R table adder
sR2RCurrAnd: ; 0x0063
  .Byte 1 ; Current R2R table repetition value
sCmpACurrH: ; 0x0064
  .Byte 1 ; Current TC1-CTC value, MSB first
sCmpACurrL: ; 0x0065
  .Byte 1 ; dto., LSB following
sCondCurr: ; 0x0066
  .Byte 1 ; Current capacitor for RC network (0..15)
sFineTuneCurr: ; 0x0067
  .Byte 1 ; Current fine tune range frequency adjust (0..6)
sCourseTuneCurr: ; 0x0068
  .Byte 1 ; Current course range frequency adjust (0..3)
sPotCurr: ; 0x0069
  .Byte 1 ; Current potentiometer value, (0..255)
sCurveCurr: ; 0x006A
  .Byte 1 ; Current wave form, (0..3)
sCurrEnd: ; End of current values
sDummy1: ; 0x006B
  .Byte 0x0070-sCurrEnd ; For a nicer view
;
; Transfer storage for new adjust values
sTransfStart:
sR2RL: ; 0x0070
  .Byte 1 ; R/2R table address, LSB
sR2RH: ; 0x0071
  .Byte 1 ; dto., MSB
sR2RAdd: ; 0x0072
  .Byte 1 ; R/2R table adder
sR2RAnd: ; 0x0073
  .Byte 1 ; R/2R table repetition value
sCmpAH: ; 0x0074
  .Byte 1 ; TC1-CTC value, MSB first
sCmpAL: ; 0x0075
  .Byte 1 ; dto., LSB following
sCond: ; 0x0076
  .Byte 1 ; Capacitor for RC network (0..15)
sFineTune: ; 0x0077
  .Byte 1 ; Fine tune range frequency adjust (0..6)
sCourseTune: ; 0x0078
  .Byte 1 ; Course range frequency adjust (0..3)
sPot: ; 0x0079
  .Byte 1 ; Potentiometer value, (0..255)
sCurve: ; 0x007A
  .Byte 1 ; Wave form, (0..3)
    ; 00 = Saw-tooth, 01 = Sine, 10 = Triangle, 11 = Rectangle
sTransfEnd: ; Ende Uebernahme-Datenbereich
;
sDummy2: ; 0x007B
  .Byte 0x007F-sTransfEnd ; For a nicer view

sDisplayTune: ; 0x007F
  .Byte 1 ; Display range, 0 (2-9.9999 Hz), 1 (10-99.999 Hz),
          ; 2 (100-999.99 Hz), 3 (1-2.4999 kHz),
	  ; 4 (2.5-4.9999 kHz), 5 (5-9.99 kHz), 6 (10-20 kHz)
;
; LCD output buffer
sLcdBuf: ; 0x0080 .. 0x0087
  .Byte 8 ; 8 characters for LCD
;
sDummy3: ; 0x0088
  .Byte 8 ; For nicer view
;
sLcdBufE: ; 0x0090
; R/2R output buffer in SRAM
sR2R: ; 0x0090 .. 0x018F
  .byte 256
sR2REnd:
;
; ==============================================
;   R E S E T   A N D   I N T   V E C T O R S
; ==============================================
;
.CSEG
.ORG $0000
  jmp Main ; Reset vector
  reti ; INT0 Int vector 1
  nop
  reti ; INT1 Int vector 2
  nop
  reti ; TC2COMP Int vector 3
  nop
  reti ; TC2OVF Int vector 4
  nop
  reti ; TC1CAPT Int vector 5
  nop
  rjmp TC1CAIsr ; TC1COMPA Int vector 6
  nop
  reti ; TC1COMPB Int vector 7
  nop
  reti ; TC1OVF Int vector 8
  nop
  reti ; TC0OVF Int vector 9
  nop
  reti ; SPI_STC Int vector 10
  nop
  reti ; USART_RXC Int vector 11
  nop
  reti ; USART_UDRE Int vector 12
  nop
  reti ; USART_TXC Int vector 13
  nop
  rjmp AdcIsr ; ADC Int vector 14
  nop
  reti ; EE_RDY Int vector 15
  nop
  reti ; ANA_COMP Int vector 16
  nop
  reti ; TWI Int vector 17
  nop
  reti ; INT2 Int vector 18
  nop
  rjmp Tc0Isr ; TC0COMP Int vector 19
  nop
  reti ; SPM_RDY Int vector 20
  nop
;
; ==========================================
;    I N T E R R U P T   S E R V I C E
; ==========================================
;
; CTC compareA Interrupt: Output next DAC value
TC1CAIsr: ; 6 clock cycles for Int and vector RJMP
  in rSreg,SREG ; Save status, +1 = 7
  ld rimp,Y+ ; Read next table value, +2 = 9
  out pDacOut,rimp ; write to DAC, +1 = 10
  cpi YL,Low(sR2REnd) ; End of buffer? +1 = 11
  brne TC1CAIsrRet ; +1 = 12, +2 = 13
  ldi YH,High(sR2R) ; Restart buffer, +1 = 13
  sbr rFlag,1<<bStart ; Set flag, +1 = 14
TC1CAIsrRet: ; 13/14 clock cycles
  out SREG,rSreg ; Restore status, +1 = 14/15
  reti ; + 4 = 18/19 clock cycles
;
; TC0 Compare Match Interrupt
Tc0Isr: ; 6 clock cycles for Int and vector jump
  clt ; Clear T flag, +1 = 7
  reti ; +4 = 11 clock cycles
;
; ADC Conversion complete Interrupt
AdcIsr: ; 6 clock cycles for Int and vector jump
  set ; Set T flag, +1 = 7
  reti ; +4 = 11 clock cycles
;
; ============================================
;    M A I N   P R O G R A M   I N I T
; ============================================
;
Main:
; Init stack
  ldi rmp, HIGH(RAMEND) ; Init MSB stack
  out SPH,rmp
  ldi rmp, LOW(RAMEND) ; Init LSB stack
  out SPL,rmp
.if debug_insert == 1 ; Debugging of LCD buffer shifting?
  ldi ZH,High(sLcdBuf)
  ldi ZL,Low(sLcdBuf)
  ldi rmp,' '
  st Z+,rmp
  st Z+,rmp
  st Z+,rmp
  ldi rmp,'1'
  debug_insert1:
    st Z+,rmp
    inc rmp
    cpi rmp,'7'
    brne debug_insert1
  ldi rmp,InsertDecPos
  mov rN0,rmp
  ldi rmp,InsertDecChar
  rcall Insert
  ldi rmp,InsertThdPos
  mov rN0,rmp
  ldi rmp,InsertThdChar
  rcall Insert
  debug_insert_loop:
    rjmp debug_insert_loop
  .endif
; Init port A
  in rmp,pSwOut ; Read output port
  ori rmp,mSwPu ; Set pull-up bits switches
  out pSwOut,rmp ; Write to output port
  in rmp,pSwDir ; Read direction port
  andi rmp,0xE0 ; Clear pull-up pins and ADC0
  out pSwDir,rmp ; Write direction port
; Init port B
  in rmp,pRcfDir ; Read direction port RC filter
  ori rmp,mRcf ; Set RC filter bits
  out pRcfDir,rmp ; Write direction bits RC filter
  in rmp,pRcfOut ; Read output port RC filter port
  andi rmp,0xFF-mRcf ; Mask RC filter bits
  out pRcfOut,rmp ; Write output port RC filter port
; Init port C
  ; (done by lcd.inc during init)
; Init port D
  ldi rmp,mDac8 ; 8 bit DAC
  out pDacDir,rmp ; to direction port
  clr rmp ; to zero
  out pDacOut,rmp ; Write output port
;
.if (debug_CtcCalc == 0)&&(debug_NoLcd == 0)
; Init LCD
  rcall LcdInit ; Init the LCD
  ldi ZH,High(2*Charcodes) ; Point Z to special chars
  ldi ZL,Low(2*Charcodes)
  rcall LcdSpec ; Define special chars
  clr ZH ; Cursor to column 1
  clr ZL
  rcall LcdPos ; Set position in LCD
  ldi ZH,High(2*LcdInitText) ; Point Z to init text
  ldi ZL,Low(2*LcdInitText)
  rcall LcdText ; Text to LCD
  ldi rmp,20 ; Wait 1 second
LcdStart1:
  rcall LcdWait50ms ; Wait for 50 ms
  dec rmp
  brne LcdStart1
  .endif
.if debug_lcdonly ; Test LCD only
LcdOnlyLoop:
  rjmp LcdOnlyLoop
  .endif
; Init ADC
  ldi rAdc,cAdcAdd ; Add 16 ADC values
  ldi rmp,(1<<ADTS1)|(1<<ADTS0) ; TC0 compare match starts ADC
  out SFIOR,rmp ; in Special Function I/O Register
  ldi rmp,1<<REFS0 ; AD channel 0, REFS0=UB
  out ADMUX,rmp ; Set MUX
  ; Start first conversion
  ;   AD enable, start conversion, int enable, TC0 starts conversion 
  .set cADStart = (1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADATE)
  ;   Prescaler = 128
  .set cADStart = cADStart+(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
  ldi rmp,cADStart ; Load command
  out ADCSRA,rmp ; and write to ADC control port
; Start wave output
InitCurve:
  ldi rmp,Curve ; Selected wave form
  lsl rmp ; Multiply by 2
  ldi ZH,High(2*WaveTabAddr) ; Point Z to first wave in table
  ldi ZL,Low(2*WaveTabAddr)
  add ZL,rmp ; Add to table address, LSB
  ldi rmp,0 ; For carry
  adc ZH,rmp ; Add carry to MSB
  lpm rmp,Z+ ; LSB wave form address to rmp
  lpm ZH,Z ; MSB wave form address to ZH
  mov ZL,rmp ; LSB to ZL
  ldi rmp,cAdd ; Load adder (wave multiplyer, 1, 2, 4, 8)
  mov rAdd,rmp ; To adder register
  sts sR2RAdd,rAdd ; And to SRAM
  ldi rAnd,cAnd ; AND for wave restart (0xFF, 0x7F, 0x3F, 0x1F)
  sts sR2RAnd,rAnd ; And to SRAM  
.if (debug_CtcCalc == 0)&&(debug_TC1Off == 0)
  rcall Convert2Sram ; Write wave formn curve to SRAM
  .endif
.if debug_CtcCalc == 1
  ; Debug intercept
  ldi rmp,2*cCourseTune+8*cCurve ; Set switches
  out pSwOut,rmp ; to port output pins
  ldi rmp,0x1E ; Set direction pins as outputs
  out pSwDir,rmp
  ldi rmp,cPot ; Define potentiometer result
  sts sPot,rmp ; in SRAM parameter buffer
  rcall debug_CtcCalc_start
  .if debug_Transfer == 1
    ; Set both parameter transfer flags
    ldi rFlag,(1<<bStart)|(1<<bSetR2R)
    rcall Transfer ; Perform transfer of simulation values
    .endif
  Debug_CtcCalc_Loop:
    rjmp Debug_CtcCalc_Loop ; Endless loop
  .endif
; Start timers
  ; TC0 is is update timer to force ADC conversion
  ;   Compare match starts ADC conversion
  ldi rmp,cTc0Comp ; Set compare value
  out OCR0,rmp ; To compare port
  .if debug_Second == 1 ; Debug code
    ; Predefine switches
    ldi rmp,2*cCourseTune2+8*cCurve2 ; Set switches output port
    out pSwOut,rmp ; Write output
    out PINA,rmp ; And input port (ATmega16 haS NO invert feature
    ldi rmp,0x1E ; Switches as outputs
    out pSwDir,rmp ; to direction port
    ; For acceleration of simulation
    ldi rmp,cTCCR02 ; CTC, Prescaler = 64
    .else
    ; Normal rate
    ldi rmp,(1<<CS02)|(1<<CS00)|(1<<WGM01) ; CTC, Presc=1024
    .endif
  out TCCR0,rmp ; To TC0 control port
; Init TC1 as DA converter driver
  ldi YH,High(sR2R) ; Y points to DAC buffer in SRAM, MSB
  ldi YL,Low(sR2R) ; dto., LSB
  ldi rmp,High(cfstart) ; Start value to compare A
  out OCR1AH,rmp ; High byte first
  ldi rmp,Low(cfstart) ; LSB
  out OCR1AL,rmp ; next
  ldi rmp,(1<<WGM11)|(1<<WGM10) ; Control port A, Fast PWM OCR1A
  out TCCR1A,rmp ; to control port A
  ldi rmp,(1<<WGM13)|(1<<WGM12)|(1<<CS10) ; Fast PWM OCR1A, Presc = 1
  .if debug_TC1Off == 0
    out TCCR1B,rmp ; No debug, start TC1
    .endif
; Timer interrupts
  ldi rmp,(1<<OCIE1A)|(1<<OCIE0) ; TC1 and TC0 compare-A-Int
  out TIMSK,rmp ; To timer int mask
; Init Sleep and Interrupts
  ldi rmp,1<<SE ; Enable sleep
  out MCUCR,rmp ; in Master Control port
  sei ; Enable ints
;
; ============================================
;          P R O G R A M   L O O P
; ============================================
;
Loop:
  sleep ; Go to sleep
  nop ; Dummy for wake up, +1 = 1
  sbrc rFlag,bStart ; Start flag set?, +1/2 = 2/3
  rcall Transfer ; Yes, call transfer routine, +3 = 5/6
  brtc Loop ; ADC flag set?
  rcall AdcCalc ; Yes, call ADC result calculation
  rjmp loop ; Back to Loop
;
; ==========================================
;         A D C   U P D A T E
; ==========================================
;
; ADC flag is set (ADC conversion complete)
;
AdcCalc:
  clt ; Clear flag
  .if debug_Second == 1 ; Debugging!
    ; Debug: Add predefined value instead of ADC result
    ldi rmp,Low(cAdc2) ; Add simulation value, LSB
    add rAdcL,rmp ; To LSB ADC sum
    ldi rmp,High(cAdc2) ; Add MSB
    adc rAdcH,rmp ; To MSB ADC sum
    .else
    ; Normal: Read ADC result and add to sum
    in rmp,ADCL ; Read LSB
    add rAdcL,rmp ; Add to result
    in rmp,ADCH ; dto., MSB
    adc rAdcH,rmp
    .endif
  dec rAdc ; Count ADC cycles down
  breq AdcCalc1 ; 16 cycles complete
  ret ; not yet
AdcCalc1:
  ldi rAdc,cAdcAdd ; Restart cycle counter
  lsl rAdcL ; Shift sum result left (multiply by 2)
  rol rAdcH ; and MSB
  lsl rAdcL ; Shift sum result left (multiply by 4)
  rol rAdcH ; and MSB
  sts sPot,rAdcH ; Result / 256 to SRAM
  mov rPot,rAdcH ; and to register
  clr rAdcL ; Clear sum, LSB
  clr rAdcH ; and MSB
;
; Ermittle den Frequenz-Sollwert aus dem Mittelwert der
; Potentiometer-Stellung
;
debug_CtcCalc_Start: ; Debugging jump label
  ; Freqency range from switches
.if debug_CtcCalc == 1 ; Debug calculation routine
  ldi rmp,cCourseTune ; Calculation simulation value
  ldi rPot,cPot ; for potentiometer result
  sts sPot,rPot ; and in SRAM
  .else
  .if debug_Second == 1 ; Debug change to second wave/freq 
    ldi rmp,cCourseTune2 ; Second course tune
    ldi rPot,cPot2 ; Second potentiometer value
    sts sPot,rPot ; and in SRAM
    .else
    clr rmp ; Get course tune from switches
    sbic pSwIn,bSTune0 ; Switch 3
    ori rmp,1 ; If off: One to bit 0
    sbic pSwIn,bSTune1 ; Switch 4
    ori rmp,2 ; If off: Two to bit 1
    .endif
  .endif
  sts sCourseTune,rmp ; Store course tune in SRAM
  clr rAdd ; rAdd for R2R table to 1
  inc rAdd
  ldi rAnd,0xFF ; rAnd for R2R table to 1:1 copy
  ; Check frequency fine tune range
  cpi rmp,3 ; Course tune = 3?
  brcs SetFineTune ; Fine tune = course tune for 0, 1 or 2
  ; Course tune = 3, check fine tune
  cpi rPot,cP2500 ; above or equal 2,500 Hz?
  brcs SetFineTune ; No, fine tune=3
  lsl rAdd ; Add 2 to R2R table
  lsr rAnd ; Restart R2R table at count=128
  inc rmp ; Fine tune range = 4
  cpi rPot,cP5000 ; Above or equal 5,000 Hz?
  brcs SetFineTune ; No, fine tune = 4
  lsl rAdd ; Add 4 to R2R table values
  lsr rAnd ; Restart R2R table at count=64
  inc rmp ; Fine tune range = 5
  cpi rPot,cP10000 ; Above or equal 10,000 Hz?
  brcs SetFineTune ; No, fine tune = 5
  lsl rAdd ; Add 8 to R2R table
  lsr rAnd ; Restart R2R table at count=32
  inc rmp ; Fine tune range = 6
SetFineTune:
  sts sFineTune,rmp ; Fine tune range to SRAM
; Calculation of the adjusted frequency 
; Frequency = Potentiometer * Slope + Base value
; Slope in rN2:rN1:rN0
; Base value and result in rN5:rN4:rN3
  lds R0,sCourseTune ; Get course tune
  lsl R0 ; Multiply by 2
  mov rmp,R0 ; Copy to rmp
  lsl rmp ; Multiply by four
  add rmp,R0 ; Multiply by six
  ldi ZH,High(2*MultTab) ; Z to slope/base table
  ldi ZL,Low(2*MultTab)
  add ZL,rmp ; Add to address
  ldi rmp,0
  adc ZH,rmp ; Add carry
  lpm rN0,Z+ ; Slope, LSB
  lpm rN1,Z+ ; dto., MSB
  clr rN2 ; Clear multiplicator HSB
  lpm rN3,Z+ ; Base to result, Byte 1
  lpm rN4,Z+ ; dto., Byte 2
  lpm rN5,Z+ ; dto., Byte 3
  lpm rN6,Z+ ; dto., Byte 4
  lds rmp,sPot ; Get potentiometer value
Multip:
  tst rmp ; Multiplicator = Zero?
  breq MultipEnd ; Zero, end of multiplication
Multip1:
  lsr rmp ; next bit to carry
  brcc Multip2 ; Clear, do not add
  add rN3,rN0 ; Add shifted multiplicator to result
  adc rN4,rN1
  adc rN5,rN2
Multip2:
  lsl rN0 ; Shift multiplicator
  rol rN1
  rol rN2
  rjmp Multip ; Continue multiplication
MultipEnd:
  ldi rmp,128 ; Round result check LSB larger or equal 128
  cp rN3,rmp
  brcs ShiftUp ; Do not round up
  ldi rmp,1 ; Round one up
  add rN4,rmp ; Byte 2
  ldi rmp,0
  adc rN5,rmp ; Carry to byte 3
ShiftUp:
  ; Frequency * 256/65536 is in rN5:rN4:rN3
  ; Divide the frequency by 256
  mov rN6,rN5 ; Shift result registers one up
  mov rN5,rN4
  lds rmp,sFineTune ; Read fine tune range
  lsl rmp ; Multiply by two
  lsl rmp ; and by four
  ldi ZH,High(2*DivCtcTab) ; Point to CTC divisor table
  ldi ZL,Low(2*DivCtcTab)
  add ZL,rmp ; Add to table
  ldi rmp,0
  adc ZH,rmp ; Add carry
  lpm rN0,Z+ ; Read byte 1
  lpm rN1,Z+ ; dto., byte 2
  lpm rN2,Z+ ; dto., byte 3
  clr rN3 ; Byte 4 clear
; Division 32 bit by 16 bit
;   Divident in rN3:rN2:rN1:rN0
;   Divisor in rN6:rN5
;   Result in ZH:ZL = R31:R30
Div32:
  clr ZL ; Clear result, LSB
  clr ZH ; dto., MSB
  ldi rmp,16 ; Number of bits to divide
Div32a:
  lsl rN0 ; Divident multiplied by 2, byte 1
  rol rN1 ; dto., byte 2
  rol rN2 ; dto., byte 3
  rol rN3 ; dto., byte 4
  brcs Div32Sub ; Overflow to 33rd bit, subtract
  cp rN2,rN5 ; Compare with divisor, lower byte
  cpc rN3,rN6 ; dto., upper byte and carry
  brcs Div32b ; Shift zero into result
Div32Sub:
  sub rN2,rN5 ; Subtract divisor, lower byte
  sbc rN3,rN6 ; dto., upper byte and carry
  sec ; Shift one into result
  rjmp Div32c
Div32b:
  clc ; Carry clear, shift zero into result
Div32c:
  rol ZL ; Rotate carry into result
  rol ZH
  dec rmp ; Continue dividing?
  brne Div32a ; Yes
  sbiw ZL,1 ; Subtract one for compare value
  sts sCmpAH,ZH ; Store result in SRAM, MSB first
  sts sCmpAL,ZL ; dto., LSB next
.if debug_CtcCalc == 1 ; When debugging
  ldi rmp,cCurve ; Select curve form
  .else
  clr rmp ; Read curve form from switches
  sbic pSwIn,bSWave0 ; Read switch 3
  sbr rmp,1<<0 ; and set bit 0 of curve form
  sbic pSwIn,bSWave1 ; Read switch 4
  sbr rmp,1<<1 ; and set bit 1 of curve form
  .endif
  sts sCurve,rmp ; Store curve form in SRAM
; Set selected wave form
  ldi ZH,High(2*WaveTabAddr) ; Point to wave table addresses
  ldi ZL,Low(2*WaveTabAddr)
  lds rmp,sCurve ; Read wave form
  lsl rmp ; Multiply by 2
  add ZL,rmp ; Add curve form
  ldi rmp,0
  adc ZH,rmp ; Add carry
  lpm rmp,Z+ ; Read address from table, LSB
  sts sR2RL,rmp ; and store in SRAM
  lpm rmp,Z ; Address, MSB
  sts sR2RH,rmp ; and store in SRAM
; Set wave parameters
  ldi ZH,High(2*FineTab) ; Point to fine range table
  ldi ZL,Low(2*FineTab)
  lds rmp,sFineTune ; Read fine range
  lsl rmp ; Multiply by two
  add ZL,rmp ; Add to table address
  ldi rmp,0
  adc ZH,rmp ; Add carry
  lpm rmp,Z+ ; Read adder for fine range
  sts sR2RAdd,rmp ; and store in SRAM
  lpm rmp,Z ; Read table restart mask
  sts sR2RAnd,rmp ; and store in SRAM
; Set capacitors
  lds rmp,sCurve ; Read curve form
  cpi rmp,1 ; Sine?
  ldi rmp,0 ; Capacitors off
  brne SetCond ; Non-sine: all off
  lds rmp,sCourseTune ; Course range
  lds rN0,sFineTune ; Fine range
  mov rmp,rN0 ; Copy to rmp
  lsl rmp ; Multiply by two
  add rmp,rN0 ; Multiply by three
  ldi ZH,High(2*CondTab) ; Point to capacitor table, MSB
  ldi ZL,Low(2*CondTab) ; dto., LSB
  add ZL,rmp ; Add to table address, LSB
  ldi rmp,0
  adc ZH,rmp ; Carry to MSB
  lpm rN0,Z+ ; Read potentiometer level
  lds rmp,sPot ; Read potentiometer value
  cp rmp,rN0 ; Compare with level
  lpm rmp,Z+ ; Read capacitor lower than level
  brcs SetCond ; Is lower than level
  lpm rmp,Z ; Read capacitor equal or higher than level
SetCond:
  sts sCond,rmp ; Write capacitor value to SRAM
; Check if wave form changed
  lds rN0,sR2RCurrH ; Current wave form
  lds rmp,sR2RH ; New wave form
  cp rmp,rN0 ; Compare
  brne SetR2RFlag ; Set wave form change flag
  lds rN0,sR2RCurrAnd ; Current AND value
  lds rmp,sR2RAnd ; New AND value
  cp rmp,rN0 ; Compare
  brne SetR2RFlag ; Set wave form change flag
; Check if CTC value has changed
  lds rN0,sCmpACurrL ; Low byte CTC value current
  lds rmp,sCmpAL ; Low byte CTC value new
  cp rmp,rN0 ; Compare
  brne SetCtcFlag ; Unequal, set CTC flag
  lds rN0,sCmpACurrH ; High byte current CTC value
  lds rmp,sCmpAH ; High-Byte CTC value new
  cp rmp,rN0 ; Compare
  brne SetCtcFlag ; Unequal, set CTC flag
  ret ; Do not set flags, done
SetR2RFlag:
  sbr rFlag,1<<bSetR2R ; Set new wave form flag
  ret
SetCtcFlag:
  sbr rFlag,1<<bSetCtc ; Set new CTC value flag
  ret
;
; Table of fine tune properties
;   First byte: Adder, 1 = all bytes,
;     2, 4, 8: Several waves per sequence
;   Second byte: UND value, 0xFF = all bytes,
;     0x7F, 0x3F, 0x1F: Several waves per sequence
FineTab:
  .db 1,0xFF ; Fine tune range 0
  .db 1,0xFF ; dto., 1
  .db 1,0xFF ; dto., 2
  .db 1,0xFF ; dto., 3
  .db 2,0x7F ; dto., 4
  .db 4,0x3F ; dto., 5
  .db 8,0x1F ; dto., 6
;
; Table of capacitors in fine tune range
;   Byte 1: sPot change level
;   Byte 2: Capacitor smaller than level
;   Byte 3: Capacitor equal or larger than level
CondTab:
  .db cPC0,cC0L,cC0H, cPC1,cC1L,cC1H ; Fine tune ranges 0 and 1
  .db cPC2,cC2L,cC2H, cPC3,cC3L,cC3H ; dto., 2 and 3
  .db cPC46,cC46L,cC46H, cPC46,cC46L,cC46H ; dto., 4 and 5
  .db cPC46,cC46L,cC46H, 0 ; dto., 6 and dummy byte
;
; Slope and base adder for frequency calculation
; Multiplication table for y = a * x + b
;   First word: Gradient a, 16 bit
;   Second word: Base adder b, LWRD
;   Third word: Base adder b, HWRD
MultTab:
  .dw cM0,LWRD(cA0),HWRD(cA0) ; 2..20 Hz
  .dw cM1,LWRD(cA1),HWRD(cA1) ; 20..200 Hz
  .dw cM2,LWRD(cA2),HWRD(cA2) ; 200..2000 Hz
  .dw cM3,LWRD(cA3),HWRD(cA3) ; 2..20 kHz
;
; Dividents of fine tune ranges for CTC calculation
;   First word: LWRD cDiv, second word HWRD cDiv
DivCtcTab:
  .dw LWRD(cDiv01),HWRD(cDiv01) ; 2..20 Hz
  .dw LWRD(cDiv01),HWRD(cDiv01) ; 20..200 Hz
  .dw LWRD(cDiv23),HWRD(cDiv23) ; 200..2000 Hz
  .dw LWRD(cDiv23),HWRD(cDiv23) ; 2..2,5 kHz
  .dw LWRD(cDiv4),HWRD(cDiv4) ; 2,5..5 kHz
  .dw LWRD(cDiv5),HWRD(cDiv5) ; 5..10 kHz
  .dw LWRD(cDiv6),HWRD(cDiv6) ; 10..20 kHz
;
; ==========================================
;  W A V E   S T A R T , Z E R O P H A S E
; ==========================================
;
; Start flag set, transfer of new values
Transfer: ; 5 clock cycles following sleep
  cbr rFlag,1<<bStart ; Clear flag, +1 = 6
  sbrc rFlag,bSetR2R ; Wave change flag set?, +1/2 = 7/8
  rcall NewWave ; Set new wave form, +3 = 10
  sbrc rFlag,bSetCtc ; CTC value change flag set?
  rcall NewCtc ; New CTC value
  ret ; No, ignore start phase
;
; Set new wave form
NewWave: ; 10 clock cycles
  lds ZL,sR2RL ; Read new wave form address, LSB, +2 = 12
  lds ZH,sR2RH ; dto., Read MSB, +2 = 14
  lds rAdd,sR2RAdd ; Read adder, +2 = 16
  lds rAnd,sR2RAnd ; Read wave repeat, +2 = 18
;.if debug_Transfer == 0 ; Not if debugging
  rcall Convert2Sram ; Write new wave form, +3 = 21
;  .endif
; Set new CTC value
NewCtc:
  lds rmp,sCmpAH ; Read new CTC value, MSB first
  out OCR1AH,rmp ; Write new value, MSB
  lds rmp,sCmpAL ; dto., read LSB
  out OCR1AL,rmp ; dto., write LSB
  in rmp,pRcfOut ; Read current capacitor port output
  andi rmp,255-mRcf ; Clear previous capacitor bits
  push R0 ; Save R0
  mov R0,rmp ; Copy bits to R0
  lds rmp,sCond ; Read new capacitor setting
  andi rmp,mRcf ; Isolate capacitor bits
  or rmp,R0 ; Combine bits
  out pRcfOut,rmp ; Write to capacitor output port
  pop R0 ; Restore R0
Copy2Curr:
  ; Copy new parameter set over previous
  ldi XH,High(sCurrStart) ; Point to current values
  ldi XL,Low(sCurrStart)
  ldi ZH,High(sTransfStart) ; Point to new values
  ldi ZL,Low(sTransfStart)
Copy2Curr1:
  ld rmp,Z+ ; Read new value
  st X+,rmp ; Overwrite current value
  cpi ZL,Low(sTransfEnd) ; End of parameter buffer reached?
  brne Copy2Curr1 ; No, continue
  cbr rFlag,(1<<bSetR2R)|(1<<bSetCtc) ; Clear flags
; Update the LCD
; Calculate display range from fine tune range and potentiometer
;   Display ranges:
;   0: 2..9,999 Hz, Fine: 0 up to 9.999, display range = 0
;   1: 10..99,999 Hz, Fine 0 above 10 Hz, Fine 1 up to 99,999, = 1
;   2: 100..999,999 Hz, Fine 1 from 100 Hz, Fine 2 up to 999,99 = 2
;   3: 1000..2499,99 Hz, Fine 2 from 1 kHz, Fine 3 = 3
;   4: 2500..4999,9 Hz, Fine 4
;   5: 5000..9999,9 Hz, Fine 5
;   6: 10000..20000 Hz, Fine 6
  lds rmp,sFineTuneCurr ; Read current fine range
  cpi rmp,3 ; Fine range 3 and above?
  brcc SetDisplayTune ; Display range = Fine range
  cpi rmp,1 ; Fine range 1?
  brcs DisplayTune0 ; Fine 0
  breq DisplayTune1 ; Fine 1
  lds rmp,sPotCurr ; Fine 2, read potentiometer value
  cpi rmp,cP1000 ; Potentiometer smaller 1000?
  ldi rmp,2 ; Display range = 2
  brcs SetDisplayTune ; Yes
  inc rmp ; Display range = 3
  rjmp SetDisplayTune ; Set display range
DisplayTune0: ; Fine = 0
  lds rmp,sPotCurr ; Read potentiometer value
  cpi rmp,cP10 ; Check 10 Hz level reached
  ldi rmp,0 ; Display range 0, smaller than 10 Hz level
  brcs SetDisplayTune ; Display range = 0
  inc rmp ; Display range = 1
  rjmp SetDisplayTune ; Set display range
DisplayTune1: ; Fine = 1
  lds rmp,sPotCurr ; Read potentiometer value
  cpi rmp,cP100 ; 100 Hz level reached
  ldi rmp,1 ; Display range = 1
  brcs SetDisplayTune ; Set display range
  inc rmp ; Display range = 2
SetDisplayTune:
  sts sDisplayTune,rmp ; Store display range in SRAM
; Load divident of the display range
  ldi ZH,High(2*DivTab) ; Point to 32 bit divident table, MSB
  ldi ZL,Low(2*DivTab) ; dto., LSB
  lds rmp,sDisplayTune ; Read display range
  lsl rmp ; Multiply by two
  lsl rmp ; and by four
  add ZL,rmp ; Add to table address
  ldi rmp,0
  adc ZH,rmp ; Add carry
  lpm rN0,Z+ ; Read divident, byte 1
  lpm rN1,Z+ ; dto., byte 2
  lpm rN2,Z+ ; dto., byte 3
  lpm rN3,Z+ ; dto., byte 4
; Division
;   Divident in rN3.rN2:rN1:rN0
;   Divisor TC1 compare + 1 in Z
;   Result in rN6:rN5:rN4
Div24:
  clr rN4 ; Clear result, byte 1
  clr rN5 ; dto., byte 2
  clr rN6 ; dto., byte 3
  lds ZL,sCmpACurrL ; Read low byte of current CTC value
  lds ZH,sCmpACurrH ; dto., high byte
  adiw ZL,1 ; Increase by one
  ldi rmp,17 ; Divide 16 bits
  clc ; Clear carry
Div24a:
  brcs Div24b ; A one was shifted to carry, subtract immediately
  cp rN2,ZL ; Compare low byte
  cpc rN3,ZH ; dito, high-Byte
  brcs Div24c ; Do not subtract, shift zero
Div24b:
  sub rN2,ZL ; Subract low byte
  sbc rN3,ZH ; dito, high byte
  sec ; Set carry, shift one
  rjmp Div24d
Div24c:
  clc ; Clear carry
Div24d:
  rol rN4 ; Shift carry into result
  rol rN5
  rol rN6
  lsl rN0 ; Shift divident left
  rol rN1
  rol rN2
  rol rN3
  dec rmp ; Decrease counter
  brne Div24a ; Continue dividing
  cp rN2,ZL ; Compare again with new divident for rounding
  cpc rN3,ZH
  brcs DecimalConversion ; Do not round up
  inc rN4 ; Round up
  brne DecimalConversion
  inc rN5 ; Round up
  brne DecimalConversion
  inc rN6
; Division result in rN6:rN5:rN4
DecimalConversion:
  ldi XH,High(sLcdBuf) ; X pointer to display buffer in SRAM
  ldi XL,Low(sLcdBuf)
  lds rmp,sCurve ; Read curve form
  st X+,rmp ; Store curve on first position in LCD buffer
  ldi rmp,' ' ; Clear display buffer
DecConv1:
  st X+,rmp ; Blanks to Puffer
  cpi XL,Low(sLcdBuf+8) ; End of buffer?
  brne DecConv1 ; No, continue
  ldi XH,High(sLcdBuf+2) ; Point to third position in buffer
  ldi XL,Low(sLcdBuf+2)
  clr rN3 ; Blank leading zeros
  inc rN3 ; Set to one (blanking is on)
  ldi ZH,High(2*Dec6Tab) ; Point to decimal table
  ldi ZL,Low(2*Dec6Tab)
DecConv2:
  lpm rN0,Z+ ; Read decimal digit binary
  lpm rN1,Z+
  lpm rN2,Z+
  tst rN0 ; End of table?
  breq DecConv6 ; Yes, end of conversion
  ldi rmp,0xFF ; Digit result in rmp
DecConv3:
  inc rmp ; Next result
  sub rN4,rN0 ; Subtract decimal binary, byte 1
  sbc rN5,rN1 ; dto., byte 2
  sbc rN6,rN2 ; dto., byte 3
  brcc DecConv3 ; Continue subtracting
  add rN4,rN0 ; Undo last subtraction, byte 1
  adc rN5,rN1 ; dto., byte 2
  adc rN6,rN2 ; dto., byte 3
  tst rmp ; Result = zero?
  breq DecConv5 ; Yes, check blanking
  clr rN3 ; Clear leading zero blanking
DecConv4:
  ; Do not blank leading zeros
  subi rmp,-'0' ; Convert digit to ASCII
  st X+,rmp ; Store digit in SRAM buffer
  rjmp DecConv2 ; Continue conversion
DecConv5:
  tst rN3 ; Blank leading zeros active?
  breq DecConv4 ; No, display as '0'
  ldi rmp,' ' ; Blank digit
  st X+,rmp ; Store in LCD buffer
  rjmp DecConv2 ; Continue converting
DecConv6:
  ldi rmp,'0' ; Add ASCII 0 to remainder
  add rmp,rN4 ; Add remainder
  st X,rmp ; Store in LCD buffer
; Set decimal point
  ldi ZH,High(2*DecPtPos) ; Pont to decimal point position table
  ldi ZL,Low(2*DecPtPos)
  lds rmp,sDisplayTune ; Load display range
  add ZL,rmp ; Add to address
  lpm rN0,Z ; Read decimal point position
  tst rN0 ; Position = 0?
  breq Thousand ; Do not insert decimal point
  .if LangEn == 1
     ldi rmp,'.' ; Decimal point
     .else
     ldi rmp,',' ; Decimal comma
     .endif
  rcall Insert ; Insert character in rmp on position rN0
Thousand:
  adiw ZL,8 ; Read thousand separator position
  lpm rN0,Z ;   to rN0
  tst rN0 ; Position = 0?
  breq DisplayBuffer ; No thousand separator
  .if LangEn == 1
    ldi rmp,',' ; Thousand separator EN
    .else
    ldi rmp,'.' ; Thousand separator DE
    .endif
  rcall Insert ; Insert thousand separator in rmp on position rN0
DisplayBuffer:
  clr ZH ; Positioning of the LCD to line 1 column 1
  clr ZL
.if debug_NoLcd == 0
  rcall LcdPos ; Set position in Z
  .endif
  ldi ZH,High(sLcdBuf) ; Point to LCD buffer in SRAM
  ldi ZL,Low(sLcdBuf)
  ldi rmp,8 ; Eight characters
.if debug_NoLcd == 0
  rcall LcdSram ; Output buffer in Z on LCD
  .endif
  ret
;
; Insert: Insert character in rmp into buffer
;   on position rN0
Insert:
  push ZH ; Save Z
  push ZL
  ldi ZH,High(sLcdBuf+1) ; To the buffer start + 1
  ldi ZL,Low(sLcdBuf+1)
  ld rmp,Z ; Check if there is still a blank
  cpi rmp,' ' ; Check blank
  brne Insert2 ; No, do not insert
Insert1:
  ldd rN1,Z+1 ; Read next character in buffer
  st Z+,rN1 ; Write to next left position
  dec rN0 ; Decrease position counter
  brne Insert1 ; Continue shifting characters
  st Z,rmp ; Set character in rmp to that position
Insert2:
  pop ZL ; Restore Z
  pop ZH
  ret
;
; Divident table for display calculation of
;   frequency (multiplied by 10**(decimal digits)
;   from CTC value + 1
DivTab:
  .dw LWRD(cND0),HWRD(cND0) ; Display range 0
  .dw LWRD(cND1),HWRD(cND1) ; dto., 1
  .dw LWRD(cND2),HWRD(cND2) ; dto., 2
  .dw LWRD(cND3),HWRD(cND3) ; dto., 3
  .dw LWRD(cND4),HWRD(cND4) ; dto., 4
  .dw LWRD(cND5),HWRD(cND5) ; dto., 5
  .dw LWRD(cND6),HWRD(cND6) ; dto., 6
;
; Decimal table for conversion of 6 digits = 24 Bit
Dec6Tab:
  .db Byte1(100000),Byte2(100000)
  .db Byte3(100000),Byte1(10000)
  .db Byte2(10000),Byte3(10000)
  .db Byte1(1000),Byte2(1000)
  .db Byte3(1000),Byte1(100)
  .db Byte2(100),Byte3(100)
  .db Byte1(10),Byte2(10)
  .db Byte3(10),0
  .db 0,0
;
; Decimal point positions of the display ranges
;   Displ. Text    Decimal Thousands
;     0    M 9,9999   +2      +0
;     1    M 99,999   +3      +0
;     2    M 999,99   +4      +0
;     3    M9.999,9   +5      +1
;     4    M9.999,9   +5      +1
;     5    M9.999,9   +5      +1
;     6    M 19.999   +0      +3
DecPtPos:
  .db 2,3,4,5,5,5,0,0 ; Decimal separator positions
  .db 0,0,0,1,1,1,3,0 ; Thousands separator, +8 bytes displacement
;
; =================================
;   S R A M   W A V E  B U F F E R
; =================================
;
; Fill SRAM wave buffer with wave table values
;   Start: Z points to table start in flash
;     rAdd is adder for R/2R table values
;       1=1:1 copy, 2=128 values, 4=64 values, 8=32 values
;     rAnd is the AND for the counter to restart the table
;       8 Bit = 255; 128 values = 127, 64 values = 63, 32 values = 31
;   Used: rCnt = R7 ; Counter for values in the R/2R table
Convert2Sram: ; 21 clock cycles from bStart+bR2R flag call
  push XH ; Save X, MSB, +2 = 23
  push XL ; dto., LSB, +2 = 25
  ldi XH,High(sR2R) ; X points to SRAM buffer, MSB, +1 = 26
  ldi XL,Low(sR2R) ; dto., LSB, +1 = 27
  clr rCnt ; Byte counter clear, +1 = 28
  push ZH ; Save start address flash, MSB, +2 = 30
  push ZL ; dto., LSB, +2 = 32
Convert2Sram1:
  lpm rmp,Z ; Read value from flash, +3 = 35
  st X+,rmp ; Copy to SRAM buffer, +2 = 37
  add ZL,rAdd ; Add table adder
  ldi rmp,0 ; Add overflow
  adc ZH,rmp ; to MSB
  inc rCnt ; Count bytes written
  breq Convert2SramEnd ; Zero, done
  mov rmp,rCnt ; Copy byte counter
  and rmp,rAnd ; Isolate counter bits for restart
  brne Convert2Sram1
  pop ZL ; Set flash start address, LSB
  pop ZH ; dto., MSB
  push ZH ; Save Z again, MSB
  push ZL ; dto., LSB
  rjmp Convert2Sram1 ; Continue copying
Convert2SramEnd:
  pop ZL ; Restore Z
  pop ZH
  pop XL ; Restore X
  pop XH
  ret

; LCD routines
;
; LCD special character codes for wave form display
;   Table of codes
Charcodes:
.db 64,0,0,1,3,5,9,17,1,0 ; Char = 0, Saw-tooth
.db 72,0,0,8,20,20,21,5,2,0 ; Char = 1, Sine
.db 80,0,0,4,4,10,10,17,17,0 ; Char = 2, Triangle
.db 88,0,0,14,10,10,10,10,27,0 ; Char = 3, Rectangle
.db 0,0 ; End of table
;
.include "lcd.inc" ; Import the LCD routines
;
LcdInitText:
.db "SigGenV1",0xFE,0xFE ; Text display on start-up
;
; Load wave form tables
.include "wavetab.inc"
;
; Table start addresses
WaveTabAddr:
  .dw 2*SawTooth
  .dw 2*Sine
  .dw 2*Triangle
  .dw 2*Rectangle
;
; End of source code
; Copyright
.db "(C)2018 by Gerhard Schmidt  " ; Source code
.db "C(2)18 0ybG reahdrS hcimtd  " ; Assembled
;



To the top of that page

Wave tables to be included


;
; Tables for wave forms
;
; Sine, 270 degrees phase shifted to start with zero
;
Sine: ; Sine table 8 bits, 3V max
	.db 0,0,0,0,0,1,1,1 ; n=0 - 7
	.db 1,2,2,3,3,4,4,5 ; n=8 - 15
	.db 6,7,7,8,9,10,11,12 ; n=16 - 23
	.db 13,14,15,16,17,19,20,21 ; n=24 - 31
	.db 22,24,25,27,28,29,31,32 ; n=32 - 39
	.db 34,36,37,39,40,42,44,45 ; n=40 - 47
	.db 47,49,51,53,54,56,58,60 ; n=48 - 55
	.db 62,63,65,67,69,71,73,75 ; n=56 - 63
	.db 77,78,80,82,84,86,88,90 ; n=64 - 71
	.db 91,93,95,97,99,100,102,104 ; n=72 - 79
	.db 106,108,109,111,113,114,116,117 ; n=80 - 87
	.db 119,121,122,124,125,126,128,129 ; n=88 - 95
	.db 131,132,133,134,136,137,138,139 ; n=96 - 103
	.db 140,141,142,143,144,145,146,146 ; n=104 - 111
	.db 147,148,149,149,150,150,151,151 ; n=112 - 119
	.db 152,152,152,152,153,153,153,153 ; n=120 - 127
	.db 153,153,153,153,153,152,152,152 ; n=128 - 135
	.db 152,151,151,150,150,149,149,148 ; n=136 - 143
	.db 147,146,146,145,144,143,142,141 ; n=144 - 151
	.db 140,139,138,137,136,134,133,132 ; n=152 - 159
	.db 131,129,128,126,125,124,122,121 ; n=160 - 167
	.db 119,117,116,114,113,111,109,108 ; n=168 - 175
	.db 106,104,102,100,99,97,95,93 ; n=176 - 183
	.db 91,90,88,86,84,82,80,78 ; n=184 - 191
	.db 76,75,73,71,69,67,65,63 ; n=192 - 199
	.db 62,60,58,56,54,53,51,49 ; n=200 - 207
	.db 47,45,44,42,40,39,37,36 ; n=208 - 215
	.db 34,32,31,29,28,27,25,24 ; n=216 - 223
	.db 22,21,20,19,17,16,15,14 ; n=224 - 231
	.db 13,12,11,10,9,8,7,7 ; n=232 - 239
	.db 6,5,4,4,3,3,2,2 ; n=240 - 247
	.db 1,1,1,1,0,0,0,0 ; n=248 - 255
;
; Triangle table
;
Triangle: ; Triangle table 8 bits, 3V max
	.db 0,1,2,4,5,6,7,8 ; n=0 - 7
	.db 10,11,12,13,14,16,17,18 ; n=8 - 15
	.db 19,20,22,23,24,25,26,27 ; n=16 - 23
	.db 29,30,31,32,33,35,36,37 ; n=24 - 31
	.db 38,39,41,42,43,44,45,47 ; n=32 - 39
	.db 48,49,50,51,53,54,55,56 ; n=40 - 47
	.db 57,59,60,61,62,63,65,66 ; n=48 - 55
	.db 67,68,69,71,72,73,74,75 ; n=56 - 63
	.db 77,78,79,80,81,82,84,85 ; n=64 - 71
	.db 86,87,88,90,91,92,93,94 ; n=72 - 79
	.db 96,97,98,99,100,102,103,104 ; n=80 - 87
	.db 105,106,108,109,110,111,112,114 ; n=88 - 95
	.db 115,116,117,118,120,121,122,123 ; n=96 - 103
	.db 124,126,127,128,129,130,131,133 ; n=104 - 111
	.db 134,135,136,137,139,140,141,142 ; n=112 - 119
	.db 143,145,146,147,148,149,151,152 ; n=120 - 127
	.db 153,152,151,149,148,147,146,145 ; n=128 - 135
	.db 143,142,141,140,139,137,136,135 ; n=136 - 143
	.db 134,133,131,130,129,128,127,126 ; n=144 - 151
	.db 124,123,122,121,120,118,117,116 ; n=152 - 159
	.db 115,114,112,111,110,109,108,106 ; n=160 - 167
	.db 105,104,103,102,100,99,98,97 ; n=168 - 175
	.db 96,94,93,92,91,90,88,87 ; n=176 - 183
	.db 86,85,84,82,81,80,79,78 ; n=184 - 191
	.db 77,75,74,73,72,71,69,68 ; n=192 - 199
	.db 67,66,65,63,62,61,60,59 ; n=200 - 207
	.db 57,56,55,54,53,51,50,49 ; n=208 - 215
	.db 48,47,45,44,43,42,41,39 ; n=216 - 223
	.db 38,37,36,35,33,32,31,30 ; n=224 - 231
	.db 29,27,26,25,24,23,22,20 ; n=232 - 239
	.db 19,18,17,16,14,13,12,11 ; n=240 - 247
	.db 10,8,7,6,5,4,2,1 ; n=248 - 255
;
; Saw-tooth table
;
Sawtooth: ; Saw-tooth table 8 bits, 3V max
	.db 0,1,1,2,2,3,4,4 ; n=0 - 7
	.db 5,5,6,7,7,8,8,9 ; n=8 - 15
	.db 10,10,11,11,12,13,13,14 ; n=16 - 23
	.db 14,15,16,16,17,17,18,19 ; n=24 - 31
	.db 19,20,20,21,22,22,23,23 ; n=32 - 39
	.db 24,25,25,26,26,27,28,28 ; n=40 - 47
	.db 29,29,30,31,31,32,32,33 ; n=48 - 55
	.db 34,34,35,35,36,37,37,38 ; n=56 - 63
	.db 38,39,40,40,41,41,42,43 ; n=64 - 71
	.db 43,44,44,45,46,46,47,47 ; n=72 - 79
	.db 48,49,49,50,50,51,52,52 ; n=80 - 87
	.db 53,53,54,55,55,56,56,57 ; n=88 - 95
	.db 58,58,59,59,60,61,61,62 ; n=96 - 103
	.db 62,63,64,64,65,65,66,67 ; n=104 - 111
	.db 67,68,68,69,70,70,71,71 ; n=112 - 119
	.db 72,73,73,74,74,75,76,76 ; n=120 - 127
	.db 77,77,78,79,79,80,80,81 ; n=128 - 135
	.db 82,82,83,83,84,85,85,86 ; n=136 - 143
	.db 86,87,88,88,89,89,90,91 ; n=144 - 151
	.db 91,92,92,93,94,94,95,95 ; n=152 - 159
	.db 96,97,97,98,98,99,100,100 ; n=160 - 167
	.db 101,101,102,103,103,104,104,105 ; n=168 - 175
	.db 106,106,107,107,108,109,109,110 ; n=176 - 183
	.db 110,111,112,112,113,113,114,115 ; n=184 - 191
	.db 115,116,116,117,118,118,119,119 ; n=192 - 199
	.db 120,121,121,122,122,123,124,124 ; n=200 - 207
	.db 125,125,126,127,127,128,128,129 ; n=208 - 215
	.db 130,130,131,131,132,133,133,134 ; n=216 - 223
	.db 134,135,136,136,137,137,138,139 ; n=224 - 231
	.db 139,140,140,141,142,142,143,143 ; n=232 - 239
	.db 144,145,145,146,146,147,148,148 ; n=240 - 247
	.db 149,149,150,151,151,152,152,153 ; n=248 - 255
;
; Rectangle table
;
Rectangle: ; Rectangle table 8 bits, 3V max
	.db 0,0,0,0,0,0,0,0 ; n=0 - 7
	.db 0,0,0,0,0,0,0,0 ; n=8 - 15
	.db 0,0,0,0,0,0,0,0 ; n=16 - 23
	.db 0,0,0,0,0,0,0,0 ; n=24 - 31
	.db 0,0,0,0,0,0,0,0 ; n=32 - 39
	.db 0,0,0,0,0,0,0,0 ; n=40 - 47
	.db 0,0,0,0,0,0,0,0 ; n=48 - 55
	.db 0,0,0,0,0,0,0,0 ; n=56 - 63
	.db 0,0,0,0,0,0,0,0 ; n=64 - 71
	.db 0,0,0,0,0,0,0,0 ; n=72 - 79
	.db 0,0,0,0,0,0,0,0 ; n=80 - 87
	.db 0,0,0,0,0,0,0,0 ; n=88 - 95
	.db 0,0,0,0,0,0,0,0 ; n=96 - 103
	.db 0,0,0,0,0,0,0,0 ; n=104 - 111
	.db 0,0,0,0,0,0,0,0 ; n=112 - 119
	.db 0,0,0,0,0,0,0,0 ; n=120 - 127
	.db 153,153,153,153,153,153,153,153 ; n=128 - 135
	.db 153,153,153,153,153,153,153,153 ; n=136 - 143
	.db 153,153,153,153,153,153,153,153 ; n=144 - 151
	.db 153,153,153,153,153,153,153,153 ; n=152 - 159
	.db 153,153,153,153,153,153,153,153 ; n=160 - 167
	.db 153,153,153,153,153,153,153,153 ; n=168 - 175
	.db 153,153,153,153,153,153,153,153 ; n=176 - 183
	.db 153,153,153,153,153,153,153,153 ; n=184 - 191
	.db 153,153,153,153,153,153,153,153 ; n=192 - 199
	.db 153,153,153,153,153,153,153,153 ; n=200 - 207
	.db 153,153,153,153,153,153,153,153 ; n=208 - 215
	.db 153,153,153,153,153,153,153,153 ; n=216 - 223
	.db 153,153,153,153,153,153,153,153 ; n=224 - 231
	.db 153,153,153,153,153,153,153,153 ; n=232 - 239
	.db 153,153,153,153,153,153,153,153 ; n=240 - 247
	.db 153,153,153,153,153,153,153,153 ; n=248 - 255
;
; End of wave tables
;



To the top of that page

LCD include routines


;
; *********************************
; * LCD include routines          *
; * (C)2018 avr-asm-tutorial.net  *
; *********************************
;
; ***********************************************
; *  L C D   I N T E R F A C E  R O U T I N E S *
; ***********************************************
;
; +-------+----------------+--------------+
; |Routine|Function        |Parameters    |
; +-------+----------------+--------------+
; |LcdInit|Inits the LCD   |Ports, pins   |
; |       |in the desired  |              |
; |       |mode            |              |
; +-------+----------------+--------------+
; |LcdText|Displays the    |Z=2*Table     |
; |       |text in flash   |  address     |
; |       |memory starting |0x0D: Next    |
; |       |with line 1     |      line    |
; |       |                |0xFF: Ignore  |
; |       |                |0xFE: Text end|
; +-------+----------------+--------------+
; |LcdSram|Display the text|Z=SRAM-Address|
; |       |in SRAM         |R16: number of|
; |       |                |    characters|
; +-------+----------------+--------------+
; |LcdChar|Display charac- |R16: Character|
; |       |ter on LCD      |              |
; +-------+----------------+--------------+
; |LcdCtrl|Output control  |R16: Control  |
; |       |byte to LCD     |     byte     |
; +-------+----------------+--------------+
; |LcdPos |Set position on |ZH: Line 0123 |
; |       |the LCD         |ZL: Col 0..   |
; +-------+----------------+--------------+
; |LcdSpec|Generate special|Z: 2*Table    |
; |       |characters      |   address    |
; +-------+----------------+--------------+
; |    S W I T C H   L C D D E C I M A L  |
; +-------+----------------+--------------+
; |LcdDec2|Convert to two  |R16: Binary   |
; |       |decimal digits  |     8 bit    |
; +-------+----------------+--------------+
; |LcdDec3|Convert to three|R16: Binary   |
; |       |decimal digits, |     8 bit    |
; |       |supp. leading 0s|              |
; +-------+----------------+--------------+
; |LcdDec5|Convert to five |Z: Binary     |
; |       |decimal digits, |   16 bit     |
; |       |supp. leading 0s|              |
; +-------+----------------+--------------+
; |      S W I T C H   L C D H E X        |
; +-------+----------------+--------------+
; |LcdHex2|Convert to two  |R16: Binary   |
; |       |digits in hex   |     8 bit    |
; +-------+----------------+--------------+
; |LcdHex4|Convert to four |Z: Binary     |
; |       |digits in hex   |   16 bit     |
; +-------+----------------+--------------+
;
; *************************************
;  P A R A M E T E R - T E M P L A T E
; *************************************
;
; (Copy to your source code, remove ; and adjust
;  parameters to fit your hardware)
;
; Standard parameter set of properties/definitions
;.equ clock = 1000000 ; Clock frequency of controller in Hz
; LCD size:
  ;.equ LcdLines = 1 ; Number of lines (1, 2, 4)
  ;.equ LcdCols = 8 ; Number of characters per line (8..24)
; LCD bus interface
  ;.equ LcdBits = 4 ; Bus size (4 or 8)
  ; If 4 bit bus:
    ;.equ Lcd4High = 1 ; Bus nibble (1=Upper, 0=Lower)
  ;.equ LcdWait = 0 ; Access mode (0 with busy, 1 with delay loops)
; LCD data ports
  ;.equ pLcdDO = PORTA ; Data output port
  ;.equ pLcdDD = DDRA ; Data direction port
; LCD control ports und pins
  ;.equ pLcdCEO = PORTB ; Control E output port
  ;.equ bLcdCEO = PORTB0 ; Control E output portpin
  ;.equ pLcdCED = DDRB ; Control E direction port
  ;.equ bLcdCED = DDB0 ; Control E direction portpin
  ;.equ pLcdCRSO = PORTB ; Control RS output port
  ;.equ bLcdCRSO = PORTB1 ; Control RS output portpin
  ;.equ pLcdCRSD = DDRB ; Control RS direction port
  ;.equ bLcdCRSD = DDB1 ; Control RS direction portpin
; If LcdWait = 0:
  ;.equ pLcdDI = PINA ; Data input port
  ;.equ pLcdCRWO = PORTB ; Control RW output port
  ;.equ bLcdCRWO = PORTB2 ; Control RW output portpin
  ;.equ pLcdCRWD = DDRB ; Control RW direction port
  ;.equ bLcdCRWD = DDB2 ; Control RW direction portpin
; If you need binary to decimal conversion:
  ;.equ LcdDecimal = 1 ; If defined: include those routines
; If you need binary to hexadecimal conversion:
  ;.equ LcdHex = 1 ; If defined: include those routines
; If simulation in the SRAM is desired:
  ;.equ avr_sim = 1 ; 1=Simulate, 0 or undefined=Do not simulate
;
; *****************************************
;      T E X T   T E M P L A T E S
; *****************************************
;
; Tables to copy for diverse sizes
;
; --------------------------
; Single line LCD
;   8 chars per line
; Text_1_8:
;   .db "        ",0xFE,0xFF
;        01234567
;
;   16 chars per line
; Text_1_16:
;   .db "                ",0xFE,0xFF
;        0123456789012345
;
;   20 chars per line
; Text_1_20:
;   .db "                    ",0xFE,0xFF
;        01234567890123456789
;
;   24 chars per line
; Text_1_24:
;   .db "                        ",0xFE,0xFF
;        012345678901234567890123
;
; --------------------------
; Two line LCD
;   16 chars per line
; Text_2_16:
;   .db "                ",0x0D,0xFF
;   .db "                ",0xFE,0xFF
;        0123456789012345
;
;   20 chars per line
; Text_2_20:
;   .db "                    ",0x0D,0xFF
;   .db "                    ",0xFE,0xFF
;        01234567890123456789
;
;   24 chars per line
; Text_2_24:
;   .db "                        ",0x0D,0xFF
;   .db "                        ",0xFE,0xFF
;        012345678901234567890123
;
; --------------------------
; Four line LCD
;   16 chars per line
; Text_4_16:
;   .db "                ",0x0D,0xFF
;   .db "                ",0x0D,0xFF
;   .db "                ",0x0D,0xFF
;   .db "                ",0xFE,0xFF
;        0123456789012345
;
;   20 chars per line
; Text_4_20:
;   .db "                    ",0x0D,0xFF
;   .db "                    ",0x0D,0xFF
;   .db "                    ",0x0D,0xFF
;   .db "                    ",0xFE,0xFF
;        01234567890123456789
;
;   24 chars per line
; Text_4_24:
;   .db "                        ",0x0D,0xFF
;   .db "                        ",0x0D,0xFF
;   .db "                        ",0x0D,0xFF
;   .db "                        ",0xFE,0xFF
;        012345678901234567890123
;
; *******************************
;  P A R A M E T E R   C H E C K
; *******************************
;
; Are all parameters correct?
;
; Size defined?
.ifndef LcdLines
  .error "LCD line size (LcdLines) undefined!"
  .else
  .if (LcdLines!=1)&&(LcdLines!=2)&&(LcdLines!=4)
    .error "LCD illegal line size (LcdLines)!"
    .endif
  .endif
.ifndef LcdCols
  .error "LCD column size (LcdCols) undefined!"
  else
  .if (LcdCols<8)||(LcdCols>24)
    .error "LCD illegal column size (LcdCols)!"
    .endif
  .endif
;
; Clock defined?
.ifndef clock
  .error "Clock frequency (clock) undefined!"
  .endif
;
; 4- or 8-bit interface selected?
.ifndef LcdBits
  .error "LCD data bus bits (LcdBits) undefined!"
  .else
  .if (LcdBits != 4) && (LcdBits != 8)
    .error "LCD data bus bits (LcdBits) not 4 or 8!"
    .endif
  .if LcdBits == 4
    .ifndef Lcd4High
      .error "LCD 4 bit data bus nibble (Lcd4High) undefined!"
      .else
      .if (Lcd4High != 0) && (Lcd4High != 1)
        .error "LCD 4 bit data bus nibble (Lcd4High) not 0 or 1!"
        .endif
      .endif
    .endif
  .endif
;
; LCD data ports
.ifndef pLcdDO
  .error "LCD data output port (pLcdDO) undefined!"
  .endif
.ifndef pLcdDD
  .error "LCD data direction port (pLcdDD) undefined!"
  .endif
.if LcdWait == 0
  .ifndef pLcdDI
    .error "LCD data input port (pLcdDI) undefined!"
    .endif
  .endif
;
; LCD control ports und pins
.ifndef pLcdCEO
  .error "LCD control E output port (pLcdCEO) undefined!"
  .endif
.ifndef pLcdCED
  .error "LCD control E direction port (pLcdCED) undefined!"
  .endif
.ifndef bLcdCEO
  .error "LCD control E output pin (bLcdCEO) undefined!"
  .endif
.ifndef bLcdCED
  .error "LCD control E direction pin (bLcdCED) undefined!"
  .endif
.ifndef pLcdCRSO
  .error "LCD control RS output port (pLcdCRSO) undefined!"
  .endif
.ifndef pLcdCRSD
  .error "LCD control RS direction port (pLcdCRSD) undefined!"
  .endif
.ifndef bLcdCRSO
  .error "LCD control RS output pin (bLcdCRSO) undefined!"
  .endif
.ifndef bLcdCRSD
  .error "LCD control RS direction pin (bLcdCRSD) undefined!"
  .endif
.ifndef LcdWait
  .error "LCD operating property (LcdWait) undefined!"
  .else
  .if LcdWait == 0
    .ifndef pLcdCRWO
      .error "LCD control RW output port (pLcdCRWO) undefined!"
      .endif
    .ifndef bLcdCRWO
      .error "LCD control RW output pin (bLcdCRWO) undefined!"
      .endif
    .ifndef pLcdCRWD
      .error "LCD control RW direction port (pLcdCRWD) undefined!"
      .endif
    .ifndef bLcdCRWD
      .error "LCD control RW direction pin (bLcdCRWD) undefined!"
      .endif
    .endif
  .endif
;
; *************************************
;  S I M U L A T I O N   A V R _ S I M
; *************************************
;
; Check if simulation desired
.ifdef avr_sim
  .equ simulation = avr_sim
  .else
  .equ simulation = 0
  .endif
.if simulation == 1
  .dseg
  SimStart:
  SimDisplayPos:
    .byte 1
  SimCtrlClear:
    .byte 1
  SimCtrlReset:
    .byte 1
  SimCtrlInputmode:
    .byte 1
  SimCtrlDisplay:
    .byte 1
  SimCtrlCursorShift:
    .byte 1
  SimCtrlFunctionset:
    .byte 1
  SimCtrlCharGenRamAdr:
    .byte 1
  SimCtrlDisplayRamAdr:
    .byte 1
  SimDataDisplay:
    .byte LcdLines*LcdCols
  SimEnd:
  .cseg
  .endif
;
; *********************************
;     L C D   R O U T I N E S
; *********************************
;
; LcdInit: Init LCD ports and pins
;          Wait cycle for LCD start-up
;          Function set
;          Clear LCD
LcdInit:
  ; Init the LCD control bits
  cbi pLcdCEO,bLcdCEO ; E pin low
  sbi pLcdCED,bLcdCED ; E pin output
  cbi pLcdCRSO,bLcdCRSO ; RS pin low
  sbi pLcdCRSD,bLcdCRSD ; RS pin output
  .ifdef pLcdCRWO
    .ifdef bLcdCRWO
      cbi pLcdCRWO,bLcdCRWO ; RW pin low
      .endif
    .endif
  .ifdef pLcdCRWD
    .ifdef pLcdCRWD
      sbi pLcdCRWD,bLcdCRWD ; RW pin output
      .endif
    .endif
  ; Init the LCD data bus ports
  .if LcdBits == 8
    clr R16 ; Data bus to low
    .else
    in R16,pLcdDO ; Read output bits data bus
    .if Lcd4High == 1
      andi R16,0x0F ; Clear upper nibble
      .else
      andi R16,0xF0 ; Clear lower nibble
      .endif
    .endif
  out pLcdDO,R16 ; Data bus output clear
  .if LcdBits == 8
    ldi R16,0xFF ; Set all direction bits high
    .else
    in R16,pLcdDD ; Read direction bits data bus
    .if Lcd4High == 1
      ori R16,0xF0 ; Set upper nibble
      .else
      ori R16,0x0F ; Set lower nibble
      .endif
    .endif
  out pLcdDD,R16 ; Set direction bits data bus
  ; LCD-Startphase
  .if simulation == 0
    rcall LcdWait50ms
    .endif
  ; LCD to 8 bit data bus
  ldi R16,0x30
  rcall Lcd8Ctrl ; In 8 bit mode to LCD control
  .if simulation == 0
    rcall LcdWait5ms ; Wait for 5 ms
    .endif
  ldi R16,0x30
  rcall Lcd8Ctrl ; In 8 bit mode to LCD control
  .if simulation == 0
    rcall LcdWait5ms ; Wait for 5 ms
    .endif
  ldi R16,0x30
  rcall Lcd8Ctrl ; In 8 bit mode to LCD control
  .if simulation == 0
    rcall LcdWait5ms ; Wait for 5 ms
    .endif
  ldi R16,0x30
  rcall Lcd8Ctrl ; In 8 bit mode to LCD control
  .if simulation == 0
    rcall LcdWait5ms ; Wait for 5 ms
    .endif
  ; If 4 bit data bus interface: switch to 4 bit mode
  .if LcdBits == 4
    ldi R16,0x20 ; 4 bit interface
    rcall Lcd8Ctrl ; In 8 bit mode to LCD control
    .if simulation == 0
      rcall LcdWait5ms
      .endif
    .endif
  ; Function set
  .if LcdBits == 8
    ldi R16,0x30 ; 8 bit data bus
    .else
    ldi R16,0x20 ; 4 bit data bus
    .endif
  .if LcdLines > 1
     ori R16,0x08 ; LCDs with more than one line
    .endif
  rcall LcdCtrl
  ; Display mode
  ldi R16,0x0C ; Display on, underline off, cursor blink off
  rcall LcdCtrl
  ; LCD entry mode set
  ldi R16,0x06 ; Cursor right, cursor move not display shift
  rcall LcdCtrl
  ; Clear LCD
  ldi R16,0x01 ; LCD clear
  rjmp LcdCtrl
;
; LcdText
;   Displays the text in flash memory on the LCD
;     Z points to 2*Text table address
;     0x0D: Line feed and carriage return
;     0xFF: Ignore (fill character, ignored)
;     0xFE: End of the text
LcdText:
  push R0 ; Save R0
  ldi R16,LcdLines ; Maximum line number
  mov R0,R16 ; to R0
LcdText1:
  lpm R16,Z+ ; Read next char from flash
  cpi R16,0xFE ; End of text?
  breq LcdText3 ; Yes
  brcc LcdText1 ; Ignore fill characters
  cpi R16,0x0D ; Line feed and carriage return?
  brne LcdText2 ; No
  dec R0 ; Decrease line counter
  breq LcdText1 ; If zero, continue
  .if simulation == 1
    ; CR/LF in SRAM memory
    push ZH ; Save Z
    push ZL
    push R16 ; Save character
    ldi ZH,High(SimDataDisplay) ; Perform newline
    ldi ZL,Low(SimDataDisplay)
    ldi R16,LcdLines
    LcdText1a:
      adiw ZL,LcdCols
      dec R16
      cp R16,R0
      brne LcdText1a
    ldi R16,Low(SimDataDisplay)
    sub ZL,R16
    ldi R16,High(SimDataDisplay)
    sbc ZH,R16
    sts SimDisplayPos,ZL
    pop R16 ; Restore character
    pop ZL ; Restore Z
    pop ZH
    .endif
  ; CR/LF on LCD
  push ZH ; Save Z
  push ZL
  ldi ZH,LcdLines ; Calculate line
  sub ZH,R0
  clr ZL ; To line start
  rcall LcdPos ; Set LCD position
  pop ZL ; Restore Z
  pop ZH
  rjmp LcdText1 ; Next character in flash
LcdText2:
  rcall LcdChar ; Character in R16 to LCD
  rjmp LcdText1 ; Next character
LcdText3:
  pop R0 ; Restore R0
  ret
;
; LcdSRam displays text in SRAM at the current LCD position
;   Z points to SRAM address
;   R16: Number of characters
LcdSRam:
  push R16 ; Save R16
  ld R16,Z+ ; Read character
  rcall LcdChar ; Display on LCD
  pop R16 ; Restore R16
  dec R16 ; Downcount number of characters
  brne LcdSRam ; Further characters to display
  ret
;
; LcdChar displays character in R16 at the current position
;   R16: Character
LcdChar:
.if simulation == 1
  ; Simulation, write character to SRAM
  push ZH ; Save Z
  push ZL
  push R16 ; Save character
  ldi ZH,High(SimDataDisplay) ; SRAM position
  ldi ZL,Low(SimDataDisplay)
  lds R16,SimDisplayPos
  inc R16
  sts SimDisplayPos,R16
  dec R16
  add ZL,R16
  ldi R16,0
  adc ZH,R16
  pop R16
  st Z,R16 ; Write to SRAM
  pop ZL
  pop ZH
  .endif
  rjmp LcdData ; Write as data to LCD
;
; LcdCtrl writes control byte in R16 on the LCD
LcdCtrl:
.if simulation == 1
    cpi R16,0x80 ; Display RAM address write?
    brcs LcdCtrlSim1
    sts SimCtrlDisplayRamAdr,R16
    push ZH
    push ZL
    mov ZH,R16
    andi R16,0x40
    brne LcdCtrlSim0a
    clr ZL ; Line 1 or 3
    rjmp LcdCtrlSim0b
  LcdCtrlSim0a:
    ldi ZL,LcdCols ; Line 2 or 4
  LcdCtrlSim0b:
    mov R16,ZH
    andi R16,0x3F
    subi R16,LcdCols
    brcc LcdCtrlSim0c ; Line 3 or 4
    subi R16,-LcdCols
    rjmp LcdCtrlSim0d
  LcdCtrlSim0c:
    sbrc ZH,6 ; Line 4?
    subi R16,-LcdCols ; Add column length
  LcdCtrlSim0d:
    add ZL,R16
    sts SimDisplayPos,ZL
    pop ZL
    pop ZH
    rjmp LcdCtrlSim9
  LcdCtrlSim1:
    cpi R16,0x40 ; Character generator RAM address set?
    brcs LcdCtrlSim2
    sts SimCtrlCharGenRamAdr, R16
    rjmp LcdCtrlSim9
  LcdCtrlSim2:
    cpi R16,0x20 ; Function set?
    brcs LcdCtrlSim3
    sts SimCtrlFunctionSet, R16
    rjmp LcdCtrlSim9
  LcdCtrlSim3:
    cpi R16,0x10 ; Cursor shift?
    brcs LcdCtrlSim4
    sts SimCtrlCursorShift, R16
    rjmp LcdCtrlSim9
  LcdCtrlSim4:
    cpi R16,0x08 ; Display control set?
    brcs LcdCtrlSim5
    sts SimCtrlDisplay, R16
    rjmp LcdCtrlSim9
  LcdCtrlSim5:
    cpi R16,0x04 ; Display control?
    brcs LcdCtrlSim6
    sts SimCtrlInputmode, R16
    rjmp LcdCtrlSim9
  LcdCtrlSim6:
    cpi R16,0x02 ; Reset?
    brcs LcdCtrlSim7
    sts SimCtrlReset,R16
    rjmp LcdCtrlSim9
  LcdCtrlSim7:
    cpi R16,0x01 ; Clear?
    brcs LcdCtrlSim8
    sts SimCtrlClear,R16
    ; LCD clear display
    push ZH
    push ZL
    ldi ZH,High(SimEnd)
    ldi ZL,Low(SimEnd)
    clr R16
  LcdCtrl7a:
    st -Z,R16 ; Fill with zeroes backwards
    cpi ZL,Low(SimStart)
    brne LcdCtrl7a
    cpi ZH,High(SimStart)
    brne LcdCtrl7a
    ldi R16,0x01
    pop ZL
    pop ZH
    rjmp LcdCtrlSim9
  LcdCtrlSim8: ; 00 command
  LcdCtrlSim9:
  .endif
  .if LcdWait == 0
    rcall LcdBusy ; Wait for busy flag clear
    .endif
  cbi pLcdCRSO,bLcdCRSO ; RS bit low
  .if LcdBits == 4
    push ZL
    push R16
    in ZL,pLcdDO ; Read data output port
    .if Lcd4High == 1
      andi ZL,0x0F ; Clear upper nibble port
      andi R16,0xF0 ; Clear lower nibble R16
      .else
      andi ZL,0xF0 ; Clear lower nibble port
      swap R16 ; Upper to lower nibble
      andi R16,0x0F ; Clear upper nibble R16
      .endif
    or R16,ZL ; Combine
    out pLcdDO,R16 ; and output
    rcall LcdPulseE ; Activate E
    pop R16 ; R16 restore
    push R16 ; and save again
    in ZL,pLcdDO ; Read data output port
    .if Lcd4High == 1
      andi ZL,0x0F ; Preserve lower nibble of port
      swap R16 ; Lower to upper nibble R16
      andi R16,0xF0 ; Preserve upper nibble R16
      .else
      andi ZL,0xF0 ; Preserve upper nibble of port
      andi R16,0x0F ; Preserve lower nibble R16
      .endif
    out pLcdDO,R16 ; To data bus
    rcall LcdPulseE ; Activate E
    pop R16 ; Restore R16
    pop ZL ; Restore ZL
    .else
    out pLcdDO,R16 ; Input on data bus
    rcall LcdPulseE ; Activate E
    .endif
  .if LcdWait == 1
    andi R16,0xFC ; The six upper bits of control command
    brne LcdCtrl1 ; are not zero, short delay
    rjmp LcdWait1640us ; Wait 1,64 ms
  LcdCtrl1:
    rjmp LcdWait40us ; Wait 40 us
    .else
    ret
    .endif
;
; LcdPos position the LCD cursor to the position in Z
;   ZH: Line (0 to number of lines - 1)
;   ZL: Column (0 to number of colums per line - 1)
LcdPos:
  cpi ZH,1 ; Line = 1?
  ldi R16,0x80 ; Address line 0
  .if LcdLines < 2 ; LCD has only one line
    rjmp LcdPos1
    .endif
  brcs LcdPos1 ; Line 1
  ldi R16,0xC0 ; Line 2 address
  .if LcdLines == 2
    rjmp LcdPos1 ; Lcd has only two lines
    .endif
  breq LcdPos1 ; Line 2 selected
  ldi R16,0x80+LcdCols ; Address line 3
  cpi ZH,2 ; Line = 3
  breq LcdPos1 ; Line 3
  ldi R16,0xC0+LcdCols ; Address line 4
LcdPos1:
  add R16,ZL ; Add column
  rjmp LcdCtrl ; Send as control to LCD
;
; LcdSpec generates special characters on the LCD
;   Z points to 2*Table address
;   Table format:
;     1. byte: Address of character, 0b01zzz000,
;        0: End of the table
;     2. byte: Dummy character, ignored
;     3. to 10 th byte: Pixel data of the lines 1 to 8
LcdSpec:
  push R0 ; R0 is counter
LcdSpec1:
  lpm R16,Z+ ; Read address of character
  tst R16 ; End of the table?
  breq LcdSpec3 ; Yes
  rcall LcdCtrl ; Write address
  adiw ZL,1 ; Overread dummy
  ldi R16,8 ; 8 byte per character
  mov R0,R16 ; R0 is counter
LcdSpec2:
  lpm R16,Z+ ; Read data byte from table
  rcall LcdData ; Output as data byte
  dec R0 ; Count down
  brne LcdSpec2 ; Further data bytes
  rjmp LcdSpec1 ; Next character
LcdSpec3:
  pop R0 ; Restore R0
  ldi ZH,0 ; Cursor to home position
  ldi ZL,0
  rjmp LcdPos
;
; ************************************
;  D E C I M A L  C O N V E R S I O N
; ************************************
;
; Routines for conversion and display decimals
;
.ifdef LcdDecimal
  ;
  ; LcdDec2 converts binary in R16 to a two digit decimal
  ;   without suppression of leading zeroes
  LcdDec2:
    mov ZL,R16 ; Copy number
    ldi R16,'0'-1 ; Tens counter
    cpi ZL,100 ; Larger that two digits?
    brcs LcdDec2a ; No
    ldi ZL,99 ; three digits, limit to max
  LcdDec2a:
    inc R16
    subi ZL,10
    brcc LcdDec2a
    rcall LcdData ; Display tens
    ldi R16,10+'0'
    add R16,ZL
    rcall LcdData ; Display ones
    ret
  ;
  ; LcdDec3 converts binary in R16 to three digits decimal
  ;   blanks leading zeroes
  LcdDec3:
    ldi ZH,1 ; Flag leading zeroes
  LcdDec3null: ; without changing leading zero flag
    push R0 ; Save R0
    mov ZL,R16 ; Binary in ZL
    ldi R16,100 ; Hundreds
    rcall LcdDec3a ; Convert to decimal
    rcall LcdData ; Display digit
    ldi R16,10 ; Tens
    rcall LcdDec3a ; Convert to decimal
    rcall LcdData ; Display digit
    ldi R16,'0'
    add R16,ZL ; Convert ones to ASCII
    rcall LcdData ; Display digit
    pop R0 ; Restore R0
    ret
  ;
  LcdDec3a: ; Calculate decimal digit
    clr R0 ; Counter
    dec R0 ; to - 1
  LcdDec3b:
    inc R0 ; Increase counter
    sub ZL,R16 ; Subtract 100 or 10
    brcc LcdDec3b ; No overflow, continue
    add ZL,R16 ; Recover last subtraction
    tst R0 ; Digit zero?
    breq LcdDec3c ; Yes, check blanking
    clr ZH ; No blanking any more
    ldi R16,'0' ; ASCII-0
    add R16,R0 ; Add counter
    ret
  LcdDec3c:
    tst ZH ; Zero blanking?
    breq LcdDec3d ; No
    ldi R16,' ' ; Blank char
    ret
  LcdDec3d:
    ldi R16,'0' ; Zero char
    ret
  ;
  ; LcdDec5 converts binary Z to five decimal digits
  ;   blank leading zeroes
  LcdDec5:
    push R2 ; Save registers
    push R1
    push R0
    clr R2 ; Leading zeroes
    inc R2 ; Blanking on
    mov R1,ZH ; Copy binary to R1:R0
    mov R0,ZL
    ldi ZH,High(10000) ; First decimal digit
    ldi ZL,Low(10000)
    rcall LcdDec5a ; Convert first digit
    ldi ZH,High(1000) ; Second decimal digit
    ldi ZL,Low(1000)
    rcall LcdDec5a ; Convert second digit
    ldi ZH,High(100) ; Third decimal digit
    ldi ZL,Low(100)
    rcall LcdDec5a ; Convert decimal digit
    ldi ZH,High(10) ; Fourth decimal digit
    ldi ZL,Low(10)
    rcall LcdDec5a ; Convert decimal digit
    ldi R16,'0' ; Convert fifth digit to ASCII
    add R16,R0
    rcall LcdChar ; Display digit
    pop R0 ; Restore registers
    pop R1
    pop R2
    ret
  ;
  LcdDec5a: ; Convert to decimal digit
    ldi R16,'0'-1 ; To ASCII-0 - 1
  LcdDec5b:
    inc R16 ; Increase counter
    sub R0,ZL ; Subtract decimal digit LSB
    sbc R1,ZH ; and MSB with Carry
    brcc LcdDec5b ; Repeat until carry
    add R0,ZL ; Take back last subtraction
    adc R1,ZH
    cpi R16,'0' ; Zero?
    breq LcdDec5c ; Yes, check blanking
    clr R2 ; End blanking
    rjmp LcdChar ; Display char in R16
  LcdDec5c:
    tst R2 ; Blanking active?
    breq LcdDec5d ; No
    ldi R16,' ' ; Blank character
  LcdDec5d:
    rjmp LcdChar ; Display char in R16
  ;
  .endif
;
; ****************************************
;  H E X A D E C I M A L  A U S G E B E N
; ****************************************
;
; Routines for conversion and displaying hexadecimals
;
.ifdef LcdHex
  ;
  ; LcdHex2 converts binary in R16 to hexadecimal
  ;   on the LCD
  LcdHex2:
    push R16 ; Is further needed
    swap R16 ; Upper nibble to lower nibble
    rcall LcdNibble ; Display lower nibble
    pop R16 ; Restore binary
  LcdNibble:
    andi R16,0x0F ; Clear upper nibble
    subi R16,-'0' ; Add ASCII-0
    cpi R16,'9'+1 ; A to F?
    brcs LcdNibble1 ; No
    subi R16,-7 ; Add 7
  LcdNibble1:
    rjmp LcdChar ; Display char on LCD
  ;
  ; LcdHex4 converts binary in Z to hexadecimal
  LcdHex4:
    mov R16,ZH ; MSB to R16
    rcall LcdHex2 ; Display byte hexadecimal
    mov R16,ZL ; LSB to R16
    rjmp LcdHex2 ; Display byte hexadecimal
  ;
  .endif
;
; *******************************
;  L C D   S U B R O U T I N E S
; *******************************
;
; Wait until LCD busy flag clear
;   needed only if wait mode is off
.if LcdWait == 0
  LcdBusy:
    push R16 ; Save R16
    .if LcdBits == 8
      clr R16 ; Data bus direction to input
      .else
      in R16,pLcdDD ; Read direction bits
      .if Lcd4High == 1
        andi R16,0x0F ; Clear upper nibble
        .else
        andi R16,0xF0 ; Clear lower nibble
        .endif
      .endif
    out pLcdDD,R16 ; Write to direction port
    .if LcdBits == 8
      clr R16 ; All output pins to low
      .else
      in R16,pLcdDO ; Read output port
      .if Lcd4High == 1
        andi R16,0x0F ; Clear upper nibble
        .else
        andi R16,0xF0 ; Clear lower nibble
        .endif
      .endif
    out pLcdDO,R16 ; Clear pull-Ups
    cbi pLcdCRSO,bLcdCRSO ; RS pin to low
    sbi pLcdCRWO,bLcdCRWO ; RW pin to high
  LcdBusyWait:
    rcall LcdIn ; Activate E, read data port, deactivate E
    .if LcdBits == 4
      rcall LcdPulseE ; Dummy for lower nibble
      .endif
    lsl R16 ; Busy flag to carry
    brcs LcdBusyWait ; Flag = 1, wait on
    cbi pLcdCRWO,bLcdCRWO ; RW pin to low
    .if LcdBits == 8
      ldi R16,0xFF ;  Data to output
      .else
      in R16,pLcdDD ; Read direction data port
      .if Lcd4High == 1
        ori R16,0xF0 ; Upper nibble high
        .else
        ori R16,0x0F ; Lower nibble high
        .endif
      .endif
    out pLcdDD,R16 ; Set direction port
    pop R16 ; Restore R16
    ret ; Done
  ;
  ; Read busy flag to R16
  ;   Needed only if wait mode off
  LcdIn:
    cbi pLcdCRSO,bLcdCRSO ; LCD RS pin to low
    sbi pLcdCEO,bLcdCEO ; Set bit bLcdCEO on LCD control port
    nop ; Wait at least one clock cycle
    .if clock>1000000 ; Insert further NOPs for higher clocks
      nop
      .endif
    .if clock>2000000
      nop
      .endif
    .if clock>3000000
      nop
      .endif
    .if clock>3000000
      nop
      .endif
    .if clock>4000000
      nop
      .endif
    .if clock>5000000
      nop
      .endif
    .if clock>6000000
      nop
      .endif
    .if clock>7000000
      nop
      .endif
    .if clock>8000000
      nop
      .endif
    .if clock>9000000
      nop
      .endif
    .if clock>10000000
      nop
      .endif
    .if clock>11000000
      nop
      .endif
    .if clock>12000000
      nop
      .endif
    .if clock>13000000
      nop
      .endif
    .if clock>14000000
      nop
      .endif
    .if clock>15000000
      nop
      .endif
    .if clock>16000000
      nop
      .endif
    .if clock>17000000
      nop
      .endif
    .if clock>18000000
      nop
      .endif
    .if clock>19000000
      nop
      .endif
    in R16,pLcdDI ; Read data bus to R16
    cbi pLcdCEO,bLcdCEO ; Clear bit bLcdCEO
    ret
  .endif
;
; 1 us pulse on LCD E pin
LcdPulseE:
  sbi pLcdCEO,bLcdCEO ; Set bit bLcdCEO in LCD control port
  .if clock>1000000 ; Add further NOPs for higher clocks
    nop
    .endif
  .if clock>2000000
    nop
    .endif
  .if clock>3000000
    nop
    .endif
  .if clock>3000000
    nop
    .endif
  .if clock>4000000
    nop
    .endif
  .if clock>5000000
    nop
    .endif
  .if clock>6000000
    nop
    .endif
  .if clock>7000000
    nop
    .endif
  .if clock>8000000
    nop
    .endif
  .if clock>9000000
    nop
    .endif
  .if clock>10000000
    nop
    .endif
  .if clock>11000000
    nop
    .endif
  .if clock>12000000
    nop
    .endif
  .if clock>13000000
    nop
    .endif
  .if clock>14000000
    nop
    .endif
  .if clock>15000000
    nop
    .endif
  .if clock>16000000
    nop
    .endif
  .if clock>17000000
    nop
    .endif
  .if clock>18000000
    nop
    .endif
  .if clock>19000000
    nop
    .endif
  cbi pLcdCEO,bLcdCEO ; Clear bit bLcdCEO
  ret
;
; Write R16 in 8 bit mode to LCD control
Lcd8Ctrl:
  .if simulation == 1
    push R16
    clr R16
    sts SimDisplayPos,R16
    pop R16
    .endif
  .if LcdBits == 4
    push ZL ; Save ZL
    in ZL,pLcdDO ; Read data output port
    .if Lcd4High == 1
      andi ZL,0x0F ; Clear upper nibble output
      andi R16,0xF0 ; Clear lower nibble R16
      .else
      andi ZL,0xF0 ; Clear lower nibble output
      swap R16 ; Swap upper and lower nibble
      andi R16,0x0F ; Clear upper nibble R16
      .endif
    or R16,ZL ; Combine
    pop ZL
    .endif
  out pLcdDO,R16 ; Data to LCD output port
  cbi pLcdCRSO,bLcdCRSO ; RS bit low
  rjmp LcdPulseE ; Activate E
;
; LcdData: Write data in R16 to LCD
;   R16: Character or data
LcdData:
  .if LcdWait == 0
    rcall LcdBusy ; Wait for busy flag of the LCD
    .endif
  sbi pLcdCRSO,bLcdCRSO ; Set RS bit
  .if LcdBits == 4
    push ZL
    push R16
    in ZL,pLcdDO ; Read data output port
    .if Lcd4High == 1
      andi ZL,0x0F ; Clear upper nibble port
      andi R16,0xF0 ; Clear lower nibble R16
      .else
      andi ZL,0xF0 ; Clear lower nibble port
      swap R16 ; Oberes und unteres Nibble vertauschen
      andi R16,0x0F ; Clear upper nibble R16
      .endif
    or R16,ZL ; Combine
    out pLcdDO,R16
    rcall LcdPulseE ; Activate E
    pop R16 ; Restore R16
    push R16 ; and save again
    in ZL,pLcdDO ; Read data output port
    .if Lcd4High == 1
      andi ZL,0x0F ; Preserve lower nibble of port
      swap R16 ; Upper to lower nibble
      andi R16,0xF0 ; Preserve upper nibble R16
      .else
      andi ZL,0xF0 ; Preserve upper nibble port
      andi R16,0x0F ; Preserve lower nibble R16
      .endif
    or R16,ZL ; Combine
    out pLcdDO,R16 ; To data bus
    rcall LcdPulseE ; Activate E
    pop R16 ; Restore R16
    pop ZL ; Restore ZL
    .else
    out pLcdDO,R16 ; Byte to data bus
    rcall LcdPulseE ; Activate E
    .endif
  .if LcdWait == 1
    rjmp LcdWait40us ; Wait for 40 us
    .endif
  ret
;
; ********************************
;    W A I T   R O U T I N E S
; ********************************
;
; Wait for 50 ms
;   At clock frequencies above 3.1 MHz the maximum
;   counting capability of Z exceeds its limit,
;   therefore the 50 ms calls the 5 ms delay 10
;   times
LcdWait50ms:
  push R16 ; Save R16
  ldi R16,10 ; 10 times 5 ms
LcdWait50ms1:
  rcall LcdWait5ms ; Call 5 ms delay
  dec R16 ; Decrease counter
  brne LcdWait50ms1 ; Repeat
  pop R16 ; Restore R16
  ret
;
LcdWait5ms:
  ; Wait for 5 ms, RCALL 3 clock cycles
  .equ cLcdZ5ms = (5*clock/1000 - 18 + 2) / 4
  push ZH ; Save ZH, + 2 cycles
  push ZL ; Save ZL, + 2 cycles
  ldi ZH,High(cLcdZ5ms) ; + 1 cycle
  ldi ZL,Low(cLcdZ5ms) ; + 1 cycle
  rjmp LcdWaitZ ; + 2 cycles
  ; Total: 11 clock cycles
;
.if LcdWait == 1 ;
  ; Wait routines necessary for wait mode
  ; Wait 1.64 ms
  .equ cLcdZ1640us = (164*(clock/1000)/100 - 18 + 2) / 4
  LcdWait1640us:
  push ZH ; Save ZH
  push ZL ; Save ZL
  ldi ZH,High(cLcdZ1640us)
  ldi ZL,Low(cLcdZ1640us)
  rjmp LcdWaitZ
  ;
  ; Wait 40 us
  .equ cLcdZ40us = (40*clock/1000000 - 18 + 2) / 4
  LcdWait40us:
  push ZH ; Save ZH
  push ZL ; Save ZL
  ldi ZH,High(cLcdZ40us)
  ldi ZL,Low(cLcdZ40us)
  rjmp LcdWaitZ
  .endif
;
; Wait routine for Z cycles
LcdWaitZ: ; 11 clock cycles
  sbiw ZL,1 ; Count down, +2 cycles
  brne LcdWaitZ ; If not zero: go on, + 2 cycles; if zero: 1 cycle
  pop ZL ; Restore ZL, +2 cycles
  pop ZH ; Restore ZH, +2 cycles
  ret ; Back, 4 cycles
  ; Total cycles: 11 + 4 * (Z - 1) + 3 + 8
  ;
  ; Number cycles= 11 + 4 * (Z - 1) + 3 + 8
  ;                11: RCALL, PUSH, PUSH, LDI, LDI, RJMP
  ;                4: 4 cycles per Z count (minus 1)
  ;                3: 3 cycles for last count
  ;                8: POP, RET
  ;              = 4 * Z + 11 - 4 + 3 + 8
  ;              = 4 * Z + 18
  ; Z = (Clock cycles - 18 + 2) / 4
  ;                +2: Rounding up during division by 4
;
; End of include file
;



To the top of that page ©2018 by http://www.avr-asm-tutorial.net