; This example application is intended to illustrate some of the
; features of Venom-SC.
;
; The program controls an oven's temperature using a thermistor
; to sense temperature and a digital output to turn a heater
; on and off.
; There is a keypad and LCD for a user interface.
;
; The oven temperature must not change rapidly, so there
; is a ramp-up and ramp-down feature.
;
; This code will run on a VM-1 and Application Board 5802
; - though we don't supply the thermistor.
;
; Features illustrated:
; * MAKE to create objects
; * START to create a new task (multi-tasking)
; * PRINT for LCD control
; * #define for macros/constants.


;************** PROGRAM INIT AND MAIN PROCEDURES ************

;State machine states
#define COLD 0
#define WARMING 1
#define AT_TEMP 2
#define COOLING 3

;Timing constants: all times are in milliseconds
#define KEY_LOOP_TIME 60
#define BUZZ_TIME 20
#define CONTROL_LOOP_TIME 1000
#define BANNER_TIME 1000


; The initialisation procedure, called by startup
;
TO init
  target_temp := 50 ;Oven's final target temperature
  actual_temp := 0 ;Oven's actual temperature
  ramp_temp := 0 ;Oven's slow-rising target temperature
  on_flag := FALSE ;Signal to the oven state-machine
  oven_state := COLD ;State of the oven state-machine

  MAKE lcd alphalcd(20,2,0) ;The User I/F output device [20x2 on the VM-1 Expansion Bus]
  MAKE kpd keypad(0,496) ;The User I/F input device [4x4 on the 2nd I2CBus]
  key_buff := kpd.InputBuffer(2,15)
  MAKE heater Digital(128) ;The heater control [Digital on the 1st I2CBus]
  MAKE buzzer Digital(129) ;Audible warning output.
  MAKE thermistor_in Analogue (47) ;The temperature input [Analogue on the VM-1]
END

; The main program entry, called by startup after init
;
TO main
  START control_task ;The task controlling the oven
  user_interface_loop ;The task doing the User I/F (not started as a task - as we use this one!)
END

;*************** USER I/F TASK PROCEDURES ***************

;This is the menu structure for the application...
;
TO user_interface_loop

  banner

  FOREVER ;a simple menu structure that goes round a loop of menus.
  [
    main_menu
    set_target_menu
    ;Put other menus here...
  ]
END

;Print the opening banner on the LCD at start-up.
;
TO banner
  PRINT TO lcd, cls," Oven Controller",CR
  PRINT TO lcd, " Verson 1.00"
                    ;12345678901234567890 - LCD character positions
  WAIT BANNER_TIME ;Hold the banner on for a while.
END


;The main menu of the Oven Controller. Displays information
; and provides access to the other menu(s)
;
#define DN_KEY 0
#define UP_KEY 1
#define EXIT_KEY 3

TO main_menu
  LOCAL on_off_string , the_key

  ;Set up the Menu text
  PRINT TO lcd, cls,cr, "Start Stop Set"
                          ; 0 1 2 3 : The 'soft keys'
  EVERY KEY_LOOP_TIME
  [
    kpd.Update
    the_key := key_buff.Key
    IF the_key >= 0 ;Any key pressed?
    [
      SELECT CASE the_key ;Which one was it?
        CASE DN_KEY
        [
          on_flag := TRUE ;Tell the oven control task to go ON
        ]
        CASE UP_KEY
        [
          on_flag := FALSE ;Tell the oven control task to go OFF
        ]
        CASE EXIT_KEY
        [
          BREAK ;Drop out of the main menu
        ]
        CASE ELSE
        [
          buzz_wrong_key ;[Other keys aren't used]
        ]
    ]

    IF ((INDEX0 AND $F) = 0) OR (the_key >= 0) ;Don't need to update LCD so often as keypad
    [ ; - only every 16 times round the loop = 480mS.
      IF on_flag
        on_off_string := "On "
      ELSE
        on_off_string := "Off"

      ;Update the display
      PRINT TO lcd, HOME,on_off_string,actual_temp:4,"C[",ramp_temp:3,"C][",target_temp:3,"C]"
    ]
  ]


END

;The menu to set the target temperature.
;
#define MIN_TEMP 0
#define MAX_TEMP 100

TO set_target_menu
LOCAL old_value := NOT target_temp ;This is used to know when to update the display.

  ;Set up the Menu text
  PRINT TO lcd, cls,"Set Temp:",CR
  PRINT TO lcd, " < C > Done"
                    ; 0 1 2 3 - The 'soft keys'
  EVERY KEY_LOOP_TIME ;Timed loop
  [
    kpd . Update ;Scan the Keypad
    the_key := key_buff.Key
    IF the_key >= 0 ;Any key pressed?
    [
      SELECT CASE the_key ;Select an action for each key
      CASE DN_KEY
      [
        IF target_temp > MIN_TEMP
           target_temp := target_temp - 1
      ]
      CASE UP_KEY
      [
        IF target_temp < MAX_TEMP
           target_temp := target_temp + 1
      ]
      CASE EXIT_KEY
      [
        BREAK ;Drop out of this menu
      ]
      CASE ELSE
      [
        buzz_wrong_key
      ]
    ]
    IF old_value <> target_temp ;Only print if value changed.
    [
      old_value := target_temp
      PRINT TO lcd , GOTOXY(3,1), target_temp:3
    ]
  ]
END


;Buzzer operated when an unused key is used.
TO buzz_wrong_key
    buzzer.On
    WAIT BUZZ_TIME
    buzzer.Off
END

;***************** CONTROL TASK PROCEDURES ***************

;This procedure is started as a task to control the oven
;using a state machine and a feedback loop.
;
TO control_task
  EVERY CONTROL_LOOP_TIME ;Timed loop
  [
    state_machine
    feedback_control
  ]
END


;Operate the heater output depending on the temperature.
; - use hysteresis to make sure it's not turned on or off
; too often.
;
TO feedback_control

  actual_temp := read_temperature

  IF actual_temp < ramp_temp - 1
    heater . On
  ELSE if actual_temp > ramp_temp + 1
    heater . Off
END


;The oven state machine. This is one way of doing
; the ramp.
;The state machine has 4 states that are moved between
; depending on the signals on_flag (from the user)
; and ramp_temp vs target_temp.
;
TO state_machine
  SELECT CASE oven_state
  CASE COLD
  [
    if on_flag
        oven_state := WARMING
  ]
  CASE WARMING
  [
    IF on_flag = 0
        oven_state := COOLING
    ELSE IF ramp_temp >= target_temp
      oven_state := AT_TEMP
    ELSE
      ramp(1)
  ]
  CASE AT_TEMP
  [
    if on_flag = 0
        oven_state := COOLING
    ELSE IF ramp_temp < target_temp
        oven_state := WARMING
    ELSE IF ramp_temp > target_temp
        oven_state := COOLING
  ]
  CASE COOLING
  [
    IF on_flag
        oven_state := WARMING
    ELSE IF ramp_temp <= 0
      oven_state := COLD
    ELSE
      ramp(-1)
  ]
END


;Ramp the temperature slowly.
;
TO ramp (increment)

IF increment < 0
[
   IF ramp_temp > 0
     ramp_temp := ramp_temp + increment
]
ELSE
[
   IF ramp_temp < target_temp
     ramp_temp := ramp_temp + increment
]

END


; Read the thermometer input to generate a temperature.
;
TO read_temperature
  RETURN temp(thermistor_in . Value) AS INT
END

;Linearise a precision thermistor reading
;The circuit is a simple divider, with a 10K
;resistor to 5V. The divided voltage is with a 10-bit ADC.
;LA & LB are Constants defining the Thermistor Curve
; - calculated from the Thermistor datasheet to
; give good matching from 0 - 25C. OTher values
;could be used for different ranges.


#define LA_VALUE (-3.7182837)
#define B_VALUE 3854.6692
#define BIAS_RES 10000

TO temp(reading)
    LOCAL resistance
  IF reading = 0 ;avoid Divide by Zero.
    reading := 1
    ;Calculate the resistance of the thermistor.
  resistance := bias_res / (1024 / reading - 1.0)
    ;Calculate the temperature in degrees C; 273.15K is 0C.
  RETURN b_value / (LOG resistance - la_value) - 273.15
END