Copyright (C) 2019 Erő János

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.


Ez a program szabad szoftver; terjeszthető illetve módosítható a Free Software Foundation által kiadott GNU General Public License dokumentumában leírtak; akár a licenc 3-as, akár (tetszőleges) későbbi változata szerint.

Ez a program abban a reményben kerül közreadásra, hogy hasznos lesz, de minden egyéb GARANCIA NÉLKÜL, az ELADHATÓSÁGRA vagy VALAMELY CÉLRA VALÓ ALKALMAZHATÓSÁGRA való származtatott garanciát is beleértve. További részleteket a GNU General Public License tartalmaz.

A felhasználónak a programmal együtt meg kell kapnia a GNU General Public License egy példányát; ha mégsem kapta meg, akkor tekintse meg a http://www.gnu.org/licenses/ oldalon.


-- Title: ad_dcc3.jal
-- derived from; ad_dcc2_128.jal - for CONTROLLER BOARD with 16F88
-- Timer0 generates DCC signal, Timer1 controls ADC readout
-- Generates DCC signal address "128 speed stages, swi controls light
-- no jallib routines to accelerate
-- Output to seven segment display
-- 128 speed grades,
-- synchronous output bit handling
-- dcc3  - line OFF when SOUND+PROG button, ON when speed=0 + PROG button
-- dcc31 - sound : F2 (code: )

-- pin a0 - analog input
-- pin a1 - address load button input
-- pin a2 - direction switch input
-- pin a3 - light switch input
-- pin a4 - display digit "1" output

-- pin a5 - F1 (sound) button input

-- pin a6 - DCC output +
-- pin a7 - DCC output -

-- pin b0 - display segment a
-- pin b1 - display segment b
-- pin b2 - display segment c
-- pin b3 - display segment d
-- pin b4 - display segment e
-- pin b5 - display segment f
-- pin b6 - display segment g
-- pin b7 - display digit select


INCLUDE 16F88

PRAGMA TARGET CLOCK 8_000_000               -- xtal frequency

PRAGMA TARGET OSC INTOSC_NOCLKOUT           -- internal oscillator, no osc. output
OSCCON_IRCF = 7                             -- set prescaler to 1 (8 MHz)

PRAGMA TARGET LVP disabled                  -- low-voltage programming disabled
PRAGMA TARGET WDT disabled                  -- watchdog disabled
PRAGMA TARGET MCLR internal                 -- reset pin disabled

INCLUDE DELAY

-- set all IO as digital

enable_digital_io()

pin_a0_direction = INPUT
pin_a1_direction = INPUT
pin_a2_direction = INPUT
pin_a3_direction = INPUT

pin_a4_direction = OUTPUT

pin_a5_direction = INPUT
pin_a6_direction = OUTPUT
pin_a7_direction = OUTPUT

pin_b0_direction = OUTPUT
pin_b1_direction = OUTPUT
pin_b2_direction = OUTPUT
pin_b3_direction = OUTPUT
pin_b4_direction = OUTPUT
pin_b5_direction = OUTPUT
pin_b6_direction = OUTPUT
pin_b7_direction = OUTPUT


CONST BYTE  timer0_load_1   = 210                      -- set up for 56us
CONST BYTE  timer0_load_0   = 156                      -- set up for 112us
CONST BYTE  timer0_presc    = 0

CONST timer1_presc    = 0
CONST BYTE  timer1_load_l   = 0
CONST BYTE  timer1_load_h   = 200

CONST BYTE  func_lead = 0x80

VAR BYTE out_segment

VAR BYTE sevenval

VAR BIT  digit_sel AT sevenval:7
VAR BIT  digit_sel_int

VAR BIT  digit_100_int                                 -- digit 100 internal value
VAR BIT  dcc_pin                                       -- output pin value
VAR BYTE dcc_pin_value                                

VAR BIT  dcc_bit
VAR BYTE dcc_out
VAR BYTE dcc_count
VAR BIT  sync AT dcc_count:3                           -- preamble synchronization signal

VAR BYTE word_counter

VAR BYTE address
VAR BYTE speed_pre
VAR BYTE speed
VAR BYTE checksum1

VAR BYTE func_code
VAR BYTE checksum2

VAR BYTE speed_int
VAR BIT  dir  AT speed_int:7

VAR BYTE func_int
VAR BIT  light  AT func_int:4
VAR BIT  light_int
VAR BIT  sound  AT func_int:1                         -- change code
VAR BIT  sound_int

VAR BYTE measure
VAR BYTE measure7

VAR BYTE result
VAR BYTE result_ls
VAR BYTE result_ms

VAR BIT timflag_1

VAR BIT off_state
VAR BYTE flash_counter_l
VAR BYTE flash_counter_h
VAR BIT flash_bit AT flash_counter_h:4


CONST BYTE seven_value_space[] = {
   0B_0011_1111,                            -- value 0 is character 0
   0B_0000_0110,                            -- value 1 is character 1
   0B_0101_1011,                            -- value 2 is character 2
   0B_0100_1111,                            -- value 3 is character 3
   0B_0110_0110,                            -- value 4 is character 4
   0B_0110_1101,                            -- value 5 is character 5
   0B_0111_1101,                            -- value 6 is character 6
   0B_0000_0111,                            -- value 7 is character 7
   0B_0111_1111,                            -- value 8 is character 8
   0B_0110_1111,                            -- value 9 is character 9 

   0B_0011_1110,                            -- value a is character U 
   0B_0011_1110,                            -- value b is character U 
   0B_0011_1110,                            -- value c is character U 
   0B_0011_1110,                            -- value d is character U 
   0B_0011_1110,                            -- value e is character U 
   0B_0011_1110                             -- value f is character U 
}


-- Table to convert 128 step to BCD without 100 values

CONST BYTE dec_values[] = {
0B_0000_0000, 0B_0000_0001, 0B_0000_0010, 0B_0000_0011, 0B_0000_0100, 0B_0000_0101, 0B_0000_0110, 0B_0000_0111,
0B_0000_1000, 0B_0000_1001, 0B_0001_0000, 0B_0001_0001, 0B_0001_0010, 0B_0001_0011, 0B_0001_0100, 0B_0001_0101,
0B_0001_0110, 0B_0001_0111, 0B_0001_1000, 0B_0001_1001, 0B_0010_0000, 0B_0010_0001, 0B_0010_0010, 0B_0010_0011,
0B_0010_0100, 0B_0010_0101, 0B_0010_0110, 0B_0010_0111, 0B_0010_1000, 0B_0010_1001, 0B_0011_0000, 0B_0011_0001,
0B_0011_0010, 0B_0011_0011, 0B_0011_0100, 0B_0011_0101, 0B_0011_0110, 0B_0011_0111, 0B_0011_1000, 0B_0011_1001,
0B_0100_0000, 0B_0100_0001, 0B_0100_0010, 0B_0100_0011, 0B_0100_0100, 0B_0100_0101, 0B_0100_0110, 0B_0100_0111,
0B_0100_1000, 0B_0100_1001, 0B_0101_0000, 0B_0101_0001, 0B_0101_0010, 0B_0101_0011, 0B_0101_0100, 0B_0101_0101,
0B_0101_0110, 0B_0101_0111, 0B_0101_1000, 0B_0101_1001, 0B_0110_0000, 0B_0110_0001, 0B_0110_0010, 0B_0110_0011,
0B_0110_0100, 0B_0110_0101, 0B_0110_0110, 0B_0110_0111, 0B_0110_1000, 0B_0110_1001, 0B_0111_0000, 0B_0111_0001,
0B_0111_0010, 0B_0111_0011, 0B_0111_0100, 0B_0111_0101, 0B_0111_0110, 0B_0111_0111, 0B_0111_1000, 0B_0111_1001,
0B_1000_0000, 0B_1000_0001, 0B_1000_0010, 0B_1000_0011, 0B_1000_0100, 0B_1000_0101, 0B_1000_0110, 0B_1000_0111,
0B_1000_1000, 0B_1000_1001, 0B_1001_0000, 0B_1001_0001, 0B_1001_0010, 0B_1001_0011, 0B_1001_0100, 0B_1001_0101,
0B_1001_0110, 0B_1001_0111, 0B_1001_1000, 0B_1001_1001, 0B_0000_0000, 0B_0000_0001, 0B_0000_0010, 0B_0000_0011,
0B_0000_0100, 0B_0000_0101, 0B_0000_0110, 0B_0000_0111, 0B_0000_1000, 0B_0000_1001, 0B_0001_0000, 0B_0001_0001,
0B_0001_0010, 0B_0001_0011, 0B_0001_0100, 0B_0001_0101, 0B_0001_0110, 0B_0001_0111, 0B_0001_1000, 0B_0001_1001,
0B_0010_0000, 0B_0010_0001, 0B_0010_0010, 0B_0010_0011, 0B_0010_0100, 0B_0010_0101, 0B_0010_0110, 0B_0010_0111
}



PROCEDURE init_tims() IS

  OPTION_REG_T0CS = 0                                  -- Timer0 Clock source: clock 
  OPTION_REG_PSA  = 0                                  -- Prescaler associated with Timer0 

  T0CON_T0PS = timer0_presc                            -- Timer0 Prescaler value
  TMR0 = timer0_load_1

  INTCON_TMR0IF = off                                  -- reset interrupt flag
  INTCON_TMR0IE = on                                   -- enable Timer0 interrupt

  T1CON_TMR1CS = 0                                     -- Select internal clock for Timer1
  T1CON_T1OSCEN = 0                                    -- No dedicated oscillator

  T1CON_T1CKPS = timer1_presc                          -- Timer1 Prescaler value

  T1CON_TMR1ON = 1                                     -- Enable run Timer1

  PIR1_TMR1IF = off                                    -- reset interrupt flag
  PIE1_TMR1IE = on                                     -- enable Timer1 interrupt

  INTCON_GIE  = on                                     -- enable global interrupts
  INTCON_PEIE = on

END PROCEDURE


PROCEDURE init_adc() IS

  JANSEL_ANS0   = 1                                    -- pin a0 analog input

  ADCON1_ADCS2  = 1
  ADCON0_ADCS10 = 01                                   -- ADC clock Fosc/16
  ADCON0_CHS    = 0                                    -- ADC channel: 0

  ADCON1_ADFM   = 0                                    -- AD value left justified - result ADRESH

  ADCON1_VCFG0  = 0                                    -- VCC and VSS as reference voltage
  ADCON1_VCFG1  = 0

  ADCON0_ADON   = on                                   -- start ADC

END PROCEDURE


PROCEDURE isr_0 IS
  PRAGMA INTERRUPT

  IF INTCON_TMR0IF == TRUE THEN                        -- Timer0 interrupt

    IF dcc_bit == 1 THEN
      TMR0 = timer0_load_1                             -- Reload timer counter
    ELSE
      TMR0 = timer0_load_0                             -- Reload timer counter
    END IF

    dcc_pin = !dcc_pin

    IF off_state == 1 THEN                             -- off state: both pins to zero
        dcc_pin_value = 0b_0000_0000
    ELSE
      IF dcc_pin == 1 THEN                             -- generate differential output
        dcc_pin_value = 0b_0100_0000
      ELSE
        dcc_pin_value = 0b_1000_0000
      END IF
    END IF

    IF digit_100_int == 1 THEN                         -- display 100 value connected to PORT_A, send here
      dcc_pin_value = dcc_pin_value | 0b_0001_0000
    END IF

    portA = dcc_pin_value

    IF dcc_pin == 1 THEN                               -- next bit
        
      IF dcc_count == 0 THEN                           -- send delimiter
        IF word_counter == 8 THEN
          dcc_count = 15                               -- set for preamble
          word_counter = 0
          dcc_bit = 1

        ELSIF word_counter == 4 THEN
          dcc_count = 15                               -- set for preamble
          word_counter = word_counter + 1
          dcc_bit = 1

        ELSE
          dcc_bit = 0
          word_counter = word_counter + 1
          dcc_count = 9
          IF word_counter == 1 THEN                    -- packet #1
            dcc_out   = address
          ELSIF word_counter == 2 THEN
            dcc_out   = speed_pre
          ELSIF word_counter == 3 THEN
            dcc_out   = speed
          ELSIF word_counter == 4 THEN
            dcc_out   = checksum1

          ELSIF word_counter == 6 THEN                 -- packet #2
            dcc_out   = address
          ELSIF word_counter == 7 THEN
            dcc_out   = func_code
          ELSIF word_counter == 8 THEN
            dcc_out   = checksum2
          END IF
        END IF
      ELSE
        IF word_counter == 0  THEN                     -- preamble
          dcc_bit = 1
        ELSIF word_counter == 5  THEN                  -- preamble
          dcc_bit = 1
        ELSE
          dcc_bit = dcc_out & 0x80
          dcc_out = dcc_out << 1                       -- shift DCC value
        END IF
      END IF
      dcc_count = dcc_count - 1
    END IF

    INTCON_TMR0IF = off                                -- reset interrupt flag
  END IF

  IF PIR1_TMR1IF == TRUE THEN                          -- Timer1 interrupt

    TMR1H = timer1_load_h                              -- Reload timer counter
    TMR1L = timer1_load_l

    timflag_1 = 1                                      -- set interrupt semaphore

    PIR1_TMR1IF = off                                  -- reset interrupt flag
    INTCON_PEIE = on

  END IF
END PROCEDURE


FUNCTION adc_read_low_res()  RETURN BYTE IS            -- ADC readout routine

VAR BYTE adc_value

  ADCON0_GO = 1                                        -- set interrupt semaphore

  WHILE ADCON0_GO LOOP                                 -- wait until ADC finish
  END LOOP

  adc_value = ADRESH
  RETURN adc_value

END FUNCTION

-- return seven segment value corresponding to lower nibble of x

FUNCTION seven_from_digit ( BYTE IN x ) RETURN BYTE IS         
  RETURN seven_value_space[x]
END FUNCTION

FUNCTION dec_from_byte ( BYTE IN x ) RETURN BYTE IS         
  RETURN dec_values[x]
END FUNCTION



-- MAIN

off_state = 0                                          -- initialize internal variables and output pins

dcc_bit = 0
dcc_pin_value = 0b_0101_0000
dcc_out = 0
dcc_count = 0

word_counter = 0

timflag_1 = 0

out_segment = 0

init_tims()

init_adc()

address = 03                                           -- default address = 03
speed_pre = 0x3F                                       -- Extended speed tag
speed = 0
checksum1 = 0

checksum2 = 0

FOREVER LOOP

  IF timflag_1 == 1 THEN

    measure   = adc_read_low_res()                     -- read in ADC value

    measure   = measure  >> 1                          -- make 7-bit value from 8-bit, DCC speed data 7-bits long
    speed_int = measure
    speed     = speed_int

    sound_int = pin_a5                                 -- sound on <= pin_a5 (F1) 

    IF off_state == 1 THEN                             -- in off-state flash display, wait for exit signal

      flash_counter_l = flash_counter_l + 1
      
      IF flash_counter_l == 0B_1111_1111 THEN
        flash_counter_h = flash_counter_h + 1
      END IF

      IF flash_bit == 1 THEN                           -- off-state: flash all display LEDs
        sevenval = 0B_0111_1111
        digit_100_int = 1
      ELSE
        sevenval = 0B_0000_0000
        digit_100_int = 0
      END IF

      digit_sel_int = !digit_sel_int
      digit_sel = digit_sel_int

      PORTB = sevenval                                 -- write out seven value with digit sel

      IF (pin_a1 == 1) & (measure == 0x00) THEN        -- if address select button AND speed = 0, exit off-state
        off_state = 0                                  -- when exit off-state, reset all outputs
        dcc_bit = 0
        dcc_pin_value = 0b_0101_0000
        dcc_out = 0
        dcc_count = 0

        word_counter = 0                               -- reset DCC packet parameters, not to generate broken packet
      END IF

    ELSE
      dir       = !pin_a2                              -- run direction <= NOT pin_a2 (switch connected opposite)
      speed     = speed_int
      sound_int = pin_a5                               -- sound on <= pin_a5 (F1) 

      checksum1 = address ^ speed ^ speed_pre

      light_int = !pin_a3                              -- light on <= NOT pin_a3 (switch opposite)

      func_int = func_lead

      light = light_int
      sound = sound_int
      
      func_code = func_int

      checksum2 = address ^ func_code

      IF (pin_a1 == 1) THEN                            -- when address select button pressed
        IF (sound_int == 1) THEN                       -- if sound button is also pressed, go into OFF-STATE
          off_state = 1
        ELSE

          IF measure != 0 THEN
            address = measure                          -- ELSE new address <= speed value
          END IF

        END IF
      END IF

-- display

      IF measure > 99 THEN                             -- if speed is over 100, digit_100 on
        digit_100_int = 1
      ELSE
        digit_100_int = 0
      END IF

      result = dec_from_byte (measure)

      result_ms = result >> 4                          -- separate medium segment value
      result_ls = result & 0b1111                      -- separate low segment value

      digit_sel_int = !digit_sel_int                   -- change segment to show

      IF digit_sel_int == 0 THEN
        out_segment = result_ms
      ELSE
        out_segment = result_ls
      END IF

      sevenval  = seven_from_digit ( out_segment )
      digit_sel = digit_sel_int

      PORTB = sevenval                                 -- write out seven value with digit sel

      timflag_1 = 0                                    -- reset timer-0 flag
    END IF
  END IF 

END LOOP