ucl 2.0

# Copyright (c) 2007 by Wayne C. Gramlich.
# All rights reserved.

library $pic16f88
library rb2

# The resonator is running at 16MHz:
library clock16mhz
constant microsecond = 4

# The libary of bus access routines for use by the PIC16F88:
library rb2bus_pic16f88

# This module uses a 16Mhz resonator; hence mode HS=High Speed:
configure fosc=hs, wrt=on, cpd=off

# Only the RX and TX pins on this package are used:
package dip
    pin 1 = ra2_in, name=bump
    pin 2 = ra3_in, name=decr
    pin 3 = ra4_in, name=view
    pin 4 = ra5_in, name=slave
    pin 5 = ground
    pin 6 = rb0_in, name=reset
    pin 7 = rb1_in, name=sda_in
    pin 8 = rx
    pin 9 = rb3_in, name=action
    pin 10 = rb4_in, name=scl_in
    pin 11 = tx
    pin 12 = rb6_unused
    pin 13 = rb7_unused
    pin 14 = power_supply
    pin 15 = osc2
    pin 16 = osc1
    pin 17 = ra0_unused
    pin 18 = ra1_unused

origin 0

procedure main
    arguments_none
    returns_nothing

    local command byte
    local id_index byte
    local result byte
    local lcd_initialized bit
    local match byte
    local bump_count byte
    local action_count byte

    call rb2bus_initialize(12)
    id_index := 0
    lcd_initialized := $false
    match := 0
    bump_count := 0
    action_count := 0

    scl_in := $false
    sda_in := $false

    loop_forever
	if slave
	    # In slave mode commands come in via the bus:
	    # Make sure that we have been selected:
	    rb2bus_error := $true
	    while rb2bus_error
		call rb2bus_select_wait()
		command := rb2bus_byte_get()

	    switch command >> 6
	      case 0
		# 00xx xxxx:
		switch (command >> 3) & 7
		  case_maximum 7
		  case 0
		    #: 0000 0xxx:
		    result := 0
		    switch command & 7
		      case_maximum 7
		      case 0
			#: 0000 0000:
			if bump
			    result@0 := $true
			if reset
			    result@1 := $true
			if slave
			    result@2 := $true
			if view
			    result@3 := $true
			if decr
			    result@4 := $true
			if action
			    result@5 := $true
		      case 3
			result := $cmcon
		      case 4
			result := $porta
		      case 5
			result := $portb
		      case 6
			result := $trisa
		      case 7
			result := $trisb
		    call rb2bus_byte_put(result)
	      case 3
		# 11xx xxxx:
		switch (command >> 3) & 7
		  case 7
		    # 1111 1xxx:
		    switch command & 7
		      case 5
			# 1111 1101 (Id_next):
			if id_index < id.size
			    call rb2bus_byte_put(id[id_index])
			    id_index := id_index + 1
		      case 6
			# 1111 1110 (Id_start):
			id_index := 0
		      case 7
			# 1111 1111 (Deselect):
			call rb2bus_deselect()
	else
	    # We are in master mode sending data to the LCD32 on the bus:
	    call rb2bus_select(rb2_lcd32_address)

	    # Debounce bump button:
	    if bump
		bump_count := 0
	    else
		# Bump button is pushed; increment {bump_count}:
		if bump_count@1
		    if !(bump_count@0)
			bump_count@0 := $true
			# We have been depressed long enough:
			if decr
			    match := match - 1
			else
			    match := match + 1
		else
		    bump_count := bump_count + 1

	    if action
		action_count := 0
	    else
		# Action button is pushed; increment {action_count}:
		if action_count@1
		    if !(action_count@0)
			action_count@0 := $true
			# We have been depressed long enough:
			if decr
			    match := match - 1
			else
			    match := match + 1
		else
		    action_count := action_count + 1

	    if rb2bus_byte_timeout_get() = 0xa5
		# We selected the LCD32:
		if !lcd_initialized
		    # Clear the LCD32:
		    call rb2bus_byte_put(rb2_lcd32_form_feed)
		    lcd_initialized := $true

		call rb2bus_byte_put(rb2_lcd32_carriage_return)

		result := '+'
		if decr
		    result := '-'
		call rb2bus_byte_put(result)

		result := 'l'
		if view
		    result := 'v'
		call rb2bus_byte_put(result)
	
		result := 'm'
		if slave
		    result := 's'
		call rb2bus_byte_put(result)

		result := 'a'
		if reset
		    result := 'r'
		call rb2bus_byte_put(result)

		call rb2bus_hex_put(match)

		call rb2bus_byte_put(rb2_lcd32_carriage_return)

	    call milliseconds_sleep(10)


procedure seconds_sleep
    argument seconds byte
    returns_nothing

    # This procedure will sleep for {seconds} seconds.
    # While it is doing so, it will flush any bus traffic.

    loop_exactly seconds
	loop_exactly 10
	    call milliseconds_sleep(100)


procedure milliseconds_sleep
    argument milliseconds byte
    returns_nothing

    # This procedure will sleep for {milliseconds} milliseconds.
    # While it is doing so, it will flush any bus traffic.

    # 200 * 50 * 1uSec = 1 mSec.
    loop_exactly milliseconds
	loop_exactly 200
	    delay 50 * microsecond
		do_nothing

	    # Flush any bus traffic:
	    if $rcif
		# Something came -- dump it (store int {$w}):
		assemble
		    movf $rcreg,w

		# Recover receive errors by toggling {$cren}:
		if $oerr
		    $cren := $false
		if $ferr
		    $cren := $false
		$cren := $true


procedure rb2bus_hex_put
    argument value byte
    returns_nothing

    # This procedure will output {value} to bus a 2 digit hexadecimal number.

    call rb2bus_nibble_put(value >> 4)
    call rb2bus_nibble_put(value)


procedure rb2bus_nibble_put
    argument nibble byte
    returns_nothing

    # This procedure will output the low 4 bits of {nibble} to the bus
    # as a hexadecimal digit.

    call rb2bus_byte_put(hex[nibble & 0xf])

string hex = "0123456789abcdef"


procedure rb2bus_select
    argument module byte
    returns_nothing

    # This procedure will 


    # Wait for transmit buffer to become ready:
    while !$txif
	do_nothing

    # Send the module select:
    $adden := $false
    $tx9d := $true
    $txreg := module

    call rb2bus_byte_timeout_get()


procedure rb2bus_byte_timeout_get
    arguments_none
    returns byte

    # This procedure will get the next byte from the bus and return it.
    # If there is a timeout, 0xfc is returned.

    local result byte

    # Wait for echo to show up:
    loop_exactly 200
	if $rcif
	    # Throw the received byte away (store int {$w}):
	    result := $rcreg

	    # Recover from an receive errors by toggling {$cren}:
	    if $oerr
		$cren := $false
	    if $ferr
		$cren := $false
	    $cren := $true
	    return result
	delay 5 * microsecond
	    do_nothing
    return 0xfc
	    

procedure wait
    arguments_none
    returns_nothing

    # This procedure is repeatably called whenever the software
    # is waiting for a byte to arrive from the bus.

    do_nothing

string id = "\16,0,12,1,3,9\Camera1-A\7\Gramson"

# Frankly this bit bang code for the I2C master came from Gerry Coe's:
#
#   <http://www.robot-electronics.co.uk/htm/using_the_i2c_bus.htm>
#
# The following examples came from the site above:
#
# The 4 primitive functions above can easily be put together to form
# complete I2C transactions. Here's and example to start an SRF08
# ranging in cm:
#
# i2c_start();              // send start sequence
# i2c_tx(0xE0);             // SRF08 I2C address with R/W bit clear
# i2c_tx(0x00);             // SRF08 command register address
# i2c_tx(0x51);             // command to start ranging in cm
# i2c_stop();               // send stop sequence
#
# Now after waiting 65mS for the ranging to complete (I've left that to you)
# the following example shows how to read the light sensor value from
# register 1 and the range result from registers 2 & 3.
#
# i2c_start();              // send start sequence
# i2c_tx(0xE0);             // SRF08 I2C address with R/W bit clear
# i2c_tx(0x01);             // SRF08 light sensor register address
# i2c_start();              // send a restart sequence
# i2c_tx(0xE1);             // SRF08 I2C address with R/W bit set
# lightsensor = i2c_rx(1);  // get light sensor and send acknowledge.
#                           // Internal register address will increment
#                           // automatically.
# rangehigh = i2c_rx(1);    // get the high byte of the range and send
#                           // acknowledge.
# rangelow = i2c_rx(0);     // get low byte of the range - note we don't
#                           // acknowledge the last byte.
# i2c_stop();               // send stop sequence 

constant i2c_delay = 4

bind sda = $trisb1
bind scl = $trisb4

procedure i2c_start
    arguments_none
    returns_nothing

    # This procedure sends an I2C start sequence to the I2C bus.

    sda := $true
    delay i2c_delay
	do_nothing
    scl := $true
    delay i2c_delay
	do_nothing
    sda := $false
    delay i2c_delay
	do_nothing
    scl := $false
    delay i2c_delay
	do_nothing


procedure i2c_stop
    arguments_none
    returns_nothing

    # This procedure sends an I2C stop sequence to the I2C bus.

    sda := $false
    delay i2c_delay
	do_nothing
    scl := $true
    delay i2c_delay
	do_nothing
    sda := $true
    delay i2c_delay
	do_nothing


procedure i2c_rx
    argument ack bit
    returns byte

    # This procedure will get an 8-bit byte from the I2C bus with
    # {ack} being the value of the ACK bit.

    local result byte

    result := 0
    loop_exactly 8
	result := result << 1
	scl := $true
	while !scl_in
	    # Wait for any SCL clock stretching:
	    scl := $true
	delay i2c_delay
	    do_nothing
	if sda_in
	    result@0 := $true
	scl := $false

    # Deal with ACK bit:
    if ack
	sda := $false
    else
	sda := $true
    scl := $true
    delay i2c_delay
	do_nothing
    scl := $false
    sda := $true

    return result


procedure i2c_tx
    argument value byte
    returns bit

    # This procedure will transmit {value} to the I2C bus and return
    # the corresponding ACK bit.

    loop_exactly 8
	if value@7
	    sda := $true
	else
	    sda := $false
	scl := $true
	value := value << 1
	scl := $false
    sda := $true
    scl := $true
    delay i2c_delay
	do_nothing
    if sda_in
	scl := $false
	return $true
    scl := $false
    return $false

