Option default integer
Option explicit
Option Autorun on

' These commands are only needed once to set up the Picomite.
' Note that the USB connection will be lost between each command.
' Option reset
' Option CPUspeed 252000
' Option System SPI GP10,GP11,GP12
' Option LCDpanel ST7735, RP,GP8,GP15,GP9
' GUI test LCDpanel (optional)
' Option System I2C GP20,GP21
' Option List (optional)

' GP08 LCD Data/Control
' GP09 LCD CS
' GP10 SPI clock
' GP11 SPI MOSI
' GP15 LCD reset
' GP18 Button
' GP20 I2C SDA
' GP21 I2C SCL

' The following screen scaling factors make best use of the ST7735 1.8in display width
' Scale	Display	Total		Unused
' factor	pixels	per	no of		pixels
'		sensor		display
'		pixel		pixels
'  1 		16 		128		0	128 - (1 * 16 * 7 + 16)
'  2 		8 		120 		8	128 - (2 * 8 * 7 + 8)
'  4 		4 		116 		12	128 - (4 * 4 * 7 + 4)
'  9 		2 		128		0	128 - (9 * 2 * 7 + 2)

' Approximate timings Picomite CPU 252000 with ST7735 1.8in display
' scale  1   77 mS
' scale  2  111 mS
' scale  4  217 mS
' scale  9  698 mS

' Display 160 / 128 pixels

' Despite the display having a theoretical resolution of 65,536 colours,
' red 5 bits = 32 steps, green 6 bits = 64 steps, blue 5 bits = 32 steps (32 * 64 * 32 = 65,536)
' the reality is much much less!
' Firstly, RGB(255,255,255) and RGB(248,252,248) produce exactly the same brightness (or lightness)
' but RGB(247,251,247) is significantly dimmer.
' The change between RGB(247,251,247) and RGB(239,247,239)is less noticeable as is each subsequent step.
' So, the lower the value the less effect it has. RGB (127,127,127) is very dim and RGB(63,63,63) is almost black!
' As a result, it is difficult to get more than about 38 distinct colours across the spectrum, 
' and the span from yellow through green to cyan seems particularly compressed!
' Also note that the display is extremely sensitive to the angle of viewing and must be viewed
' 'head-on' to get the full spectrum of colours. Otherwise, adjacent colours blend in to each other.

' The push button has the following functions:
' Short press - less than 1.5 seconds - change scaling factor 1,2,4,9

' Long press - more than 1.5 seconds - switch sensor between 1 frame and 10 frames per second.
' Note this is the sensor refresh time, not the screen refresh time 
' which at 10 frames per second will be longer than a 10th of a second for scale factors 4 and 9.

' Very long press - more than 10 seconds. Enter calibration mode. The sensor is set to 1 frame per second and 
' 10 readings are taken over a 10 second period. These are then averaged out and the correction factors 
' stored in non-volatile memory so they are persistent.

' Good results were had by holding the sensor 2-3 CM from a white sheet of paper.
 
' If the button is pressed whilst calibration is taking place, calibration is abandoned and
' the correction factors are cleared.
' Note: Let the sensor stabilise for at least 1 minute before attempting a calibration. 

Const True = 1
Const False = 0

Const no_colours = 40	' Number of colours in the colour table
Const BGR_display = False	' Set to false for RGB displays and true for BGR displays
Const HRES_offset = 0	' Some ST7735 displays have a pixel alignment problem!
Const VRES_offset = 0	' Some ST7735 displays have a pixel alignment problem!
Const Fahrenheit = False	' For our American cousins!
Const Minimum_span = 5.0	' Define the minimum temperature span when there is little temperature difference.
				' This prevents wild colour changes when there are even temperatures.
				' Tweak if desired - Lower values more sensitive.
' Sensor constants
Const address = &H68	' (or &H69)
Const PCLT = 0	' Power register &H00 normal - &H10 sleep
Const RST = 1		' Reset register &H30 flag reset - 0&H3F initial reset
Const FPSC = 2	' Frame rate register &H00 10 FPS - &H01 1 FPS
Const INTC = 3	' Interrupt control register &H00 disabled - &H01 Differential mode - &H03 Absolute mode
Const STAT = 4	' Status register 0x02 Interrupt - &H04 Temperature overflow
Const SCLR = 5	' Status reset register &H02 Interrupt reset - &H04 Temperature overflow reset
Const AVE = 7		' Average register &H00 No moving average - &H20 Moving average
Const INTH= 8		' Interrupt upper value (2 bytes low, high)
Const INTL = 10	' Interrupt lower value (2 bytes low, high)
Const INTS = 12	' Interrupt hysteresis (2 bytes low, high)
Const TTH = 14	' Thermistor value (2 bytes low, high)
Const INT = 16	' Interrupt flags (8 bytes 64 bit map)
Const SAM = 31	' Setting average mode???
Const PIX = 128	' Pixels (128 bytes low, high * 64)

' Variables to be stored in non-volatile memory
Dim Scale = 4				' Scaling factor 
Dim Offset = 6 + HRES_offset	' Left margin of display
Dim Frame_rate = 1			' Frame rate 1 or 10 FPS
Dim calibration(7,7) as float	' Calibration data
Dim Var_initialised = false		' Set to True after first run

' Scratch variables
Dim colours(no_colours)		' Array holding the spectrum 0 = Blue to (no_colours -1) = Red
Dim Rd, Gn, Bl			' Working variables used to set-up colour matrix
Dim cycle_time			' Used to calculate the screen refresh time
Dim Display_scale			' Number of display pixels per element
Dim Raw_data(128)			' Integer array to hold the 128 bytes of sensor data
Dim Pixel_minimum as float		' Minimum to calculate spectrum when there is little temperature difference
Dim Pixel_maximum as float		' Maximum to calculate spectrum when there is little temperature difference
Dim Display_minimum as float	' Minimum temperature
Dim Display_maximum as float	' Maximum temperature
Dim Display_average as float	' Average temperature
Dim Temperature(7,7) as float	' Sensor data in degrees
Dim Work_I				' Integer scratch variable
Dim Work_F as float			' Floating point scratch variable
Dim Temp_F as float			' Floating point scratch variable
Dim x_colour(72,8) as float		' Interpolated colours across rows (X axis)
Dim x1,x2,y1,y2			' For loop variables
Dim Button_status			' 0 = not pressed, 1 = short press, 2 = long press, 3 = calibrate
Dim Var_flag = false			' Var update flag

if mm.hres = 128 then poke display hres mm.hres + HRES_offset
if mm.vres = 160 then poke display vres mm.vres + VRES_offset
cls
Setpin GP18, INTB, Button_sub, pullup	' Both pressing and releasing the button causes an interrupt.

' Restore the last settings, or initialise them if not set.
VAR restore
if Var_initialised = false then	' It is set to false if this is the first time ever run to set up defaults
	VAR Clear			' Clear any old saved variables
	Var_initialised = True
	scale = 4
	offset = 6 + HRES_offset
	Frame_rate = 1
	Math set 0,Calibration()
	Var save Var_initialised, Scale, Offset, Frame_rate
	Var save Calibration()
end if

' Initialise the sensor
I2C Write address,0,2,RST,&H30		' Flag reset
if Frame_rate = 0 then
	I2C Write address,0,2,FPSC,&H00	' 10 frames per second
else
	I2C Write address,0,2,FPSC,&H01	' 1 frame per second
end if
I2C Write address,1,2,SAM,&H50		' Switch on moving average - Data sheet is as clear as mud!
I2C Write address,1,2,SAM,&H45		' Switch on moving average - But this is the example,
I2C Write address,1,2,SAM,&H47		' Switch on moving average - which has no explanation!
I2C Write address,1,2,AVE,&H20		' Switch on moving average
I2C Write address,0,2,SAM,&H00		' Switch on moving average

' Load the colour spectrum stored in the data statement table into the colours matrix
' Note spectrum loaded from the end the colours array to the beginning as we want Blue
' to be the first entry. Also note the colours beyond Blue (through magenta to red)
' are not used.
' Some displays are BGR not RGB so if BGR_display is true we swap Red and Blue
restore table
for x1 = no_colours - 1 to 0 step -1
	read Rd,Gn,Bl
	if BGR_display = False then
		colours(x1) = RGB(Rd,Gn,Bl)
	else
		colours(x1) = RGB(Bl,Gn,Rd)
	end if
next x1

' Print the spectrum as bars and pause 2 seconds to show display working
for y1 = 0 to no_colours - 1
	Box HRES_offset + 1,y1 * int(160/no_colours),126,int(160/no_colours) - 1,0,,colours(y1)
next y1
	Box HRES_offset,VRES_offset,128,160,1,RGB(white)
pause 2000

Setup_screen()
Display_scale = int((min(MM.HRES,MM.VRES)) \ (scale * 7 + 1)) ' Calculate the scaling factor for the screen

' Main loop starts here
do
	cycle_time = timer			' Timer so we can refresh the screen 1 or 10 times per second

	I2C Write address,1,1,PIX		' Pixel register start address
	I2C Read address,0,128,Raw_data()	' Read the raw sensor data

	' Convert the raw data into an 8 x 8 array and calculate maximum, minimum and average temperature
	for y1 = 0 to 7
		for x1 = 0 to 7
			work_I = Raw_data(Y1 * 16 + (X1 * 2)) or (Raw_data(Y1 * 16 + (X1 * 2) + 1) << 8)
			if work_I and &H800) <> 0 then work_I = work_I or &HFFFFFFFFFFFFF000	' Sign extend negative values
			Temperature(x1,y1) = (work_I - Calibration(x1,y1)) / 4		' Adjust for calibration and convert to degrees
		next x1
	next y1

	Pixel_minimum = math(min Temperature())
	Pixel_maximum = math(max Temperature())
	' If there is little difference between the maximum and minimum temperature,
	' the display will change colour rapidly, so expand the range of maximum to minimum
	' when this is the case in order to calm the display.
	' Firstly save the real maximum, minimum and average for the displayed values.
	if Fahrenheit = False then
		Display_minimum = Pixel_minimum
		Display_maximum = Pixel_maximum
		Display_average = math(mean Temperature())
	else
		Display_minimum = Pixel_minimum * 1.8 + 32
		Display_maximum = Pixel_maximum * 1.8 + 32
		Display_average = math(mean Temperature()) * 1.8 + 32
	end if


	if Pixel_maximum - Pixel_minimum < Minimum_span then
		Work_F = (Minimum_span - (Pixel_maximum - Pixel_minimum)) / 2.0
		inc Pixel_maximum, Work_F
		inc Pixel_minimum, -Work_F
	end if

	' Convert from temperature to a colour spectrum from 0 to (no_colours - 1)
	' 0 = Blue to (no_colours - 1) = Red
	' Note that Temperature() is floating point, so at this point colours will be fractional rather than discrete.

	math add Temperature(), 0 - Pixel_minimum, Temperature() 
	math scale  Temperature(),1 / ((Pixel_maximum - Pixel_minimum) / (no_colours - 1)),Temperature()

	' Interpolate intermediate colours across the rows (X axis) 
	for y1 = 0 to 7
		x_colour(0,y1) = Temperature(0,y1)
		for x1 = 0 to 6
			Work_F = (Temperature(x1 + 1,y1) - Temperature(x1,y1)) / scale
			for x2 = x1 * scale to x1 * scale + scale - 1
				x_colour(x2 + 1,y1) = x_colour(x2,y1) + Work_F
			next x2
		next x1
	next y1

	' Interpolate intermediate colours across the columns (Y axis) and display result.
	for y1 = 0 to 6
		for x1 = 0 to 7 * scale
			Work_F = x_colour(x1,y1)
			Temp_F = (x_colour(x1,y1 + 1) - x_colour(x1,y1)) / scale
			Work_I = x1 * Display_scale + Offset
			for y2 = y1 * scale * Display_scale + VRES_offset to (y1 * scale + scale - 1) * Display_scale + VRES_offset step Display_scale
				box Work_I, y2,Display_scale,Display_scale,0,,colours(cint(Work_F))
				inc Work_F, Temp_F
			next y2
		next x1
	next y1

	' Interpolate intermediate colours for the last row and display result.
	Work_I = 7 * scale * Display_scale + VRES_offset
	for x1 = 0 to 7 * scale
		Work_F = x_colour(x1,7)
		Temp_F = (x_colour(x1,7 + 1) - x_colour(x1,7)) / scale
		box x1 * Display_scale + Offset,Work_I,Display_scale,Display_scale,0,,colours(cint(Work_F))
		inc Work_F, Temp_F
	next x1

	' Display the text at the bottom of the screen
	text 24 + HRES_offset,135 + VRES_offset,form(Display_minimum),"lt",7,1,RGB(white)
	text 88 + HRES_offset,135 + VRES_offset,form(Display_average),"lt",7,1,RGB(white)
	text 24 + HRES_offset,150 + VRES_offset,form(Display_maximum),"lt",7,1,RGB(white)

	' Check for button press
	if Button_status <> 0 then
		Service_button()
		Button_status = 0
		Setup_screen()
		continue do
	end if

	' Delay to prevent over saving of non-volatile variables
	if Var_flag  <> 0 then
		Inc Var_flag, - 1
		if Var_flag  = 0 then Var save Scale, Offset, Frame_rate
	end if

	''''print "scale " scale, " Timer ", timer - cycle_time

	' Delay just over 1/10 second or 1 second depending upon sensor frame rate
	if Frame_rate = 0 then
		do while timer - cycle_time < 110
		loop
	else
		do while timer - cycle_time < 1030
		loop
	end if
' End of main loop
loop

' Subroutines

Sub Service_button()
		' Short press Update scaling factor to next value and define left margin so display is centred
		if Button_status = 1	then
		if scale = 1 then
			scale = 2
			offset = 2 + HRES_offset
		else if scale = 2 then
			scale = 4
			offset = 6 + HRES_offset
		else if scale = 4 then
			scale = 9
			offset = 0 + HRES_offset
		else
			scale = 1
			offset = 0 + HRES_offset
		end if 
 		' re-calculate the scaling factor for the screen
		Display_scale = int((min(MM.HRES,MM.VRES)) \ (scale * 7 + 1))
		if Frame_rate = 0 then
			Var_flag = 100
		else
			Var_flag = 10
		End if

	' Long press change sensor between 1 and 10 frames per second
	else if Button_status = 2 then
		if Frame_rate = 0 then
			Frame_rate = 1
			I2C Write address,0,2,FPSC,&H01	' 1 frame per second
			Var_flag = 10
		else
			Frame_rate = 0
			I2C Write address,0,2,FPSC,&H00	' 10 frames per second
			Var_flag = 100
		end if

	' 10 second press - calibrate the display
	else if Button_status = 3 then

		' Clear the screen, print Calibrating text and set frame rate to 1 second
		cls
		text 64,70,"Calibrating","ct",1,1,RGB(white)
		Frame_rate = 1
		I2C Write address,0,2,FPSC,&H01	' 1 frame per second

		' Clear the calibration array
		Math set 0,Calibration()

		' Read the sensor 10 times at 1 second intervals and add the result into Calibration(,)
		' If the button is pressed, abandon the calibration.
		for y2 = 1 to 10
			cycle_time = timer
			I2C Write address,1,1,PIX		' Pixel register start address
			I2C Read address,0,128,Raw_data()
			for y1 = 0 to 7
				for x1 = 0 to 7
					work_I = Raw_data(Y1 * 16 + (X1 * 2)) or (Raw_data(Y1 * 16 + (X1 * 2) + 1) << 8)
					if work_I and &H800) <> 0 then work_I = work_I or &HFFFFFFFFFFFFF000	' Sign extend negative values
					inc Calibration(x1,y1), work_I
				next x1
			next y1
			if Button_status <> 3 then 	text 64,90,"Cancelled","ct",1,1,RGB(white)
			do while timer - cycle_time < 1050
			loop
		next y2

		' Calculate the average temperature - will be times 10 as read 10 times
		Work_F = math(median Calibration())

		' Update the Calibration array unless calibration cancelled, in which case zeroise it
		if Button_status = 3 then
			for y1 = 0 to 7
				for x1 = 0 to 7
					Calibration(x1,y1) = (Calibration(x1,y1) - Work_F) / 10
				next x1
			next y1
		else
			Math set 0,Calibration()
		end if
		' Save the calibration data in non-volatile memory
		var save Calibration()
		Var save Frame_rate
	end if
end sub

Sub Setup_screen
	cls
	text 0 + HRES_offset,135 + VRES_offset,"Min:","lt",7,1,RGB(white)
	text 64 + HRES_offset,135 + VRES_offset,"Avr:","lt",7,1,RGB(white)
	text 0 + HRES_offset,150 + VRES_offset,"Max:","lt",7,1,RGB(white)
	if Frame_rate = 1 then
		text 64 + HRES_offset,150 + VRES_offset,"1 FPS  x" + str$(scale,2,0),"lt",7,1,RGB(white)
	else
		text 64 + HRES_offset,150 + VRES_offset,"10 FPS x" + str$(scale,2,0),"lt",7,1,RGB(white)
	end if
end sub

Function form(value as float) as string
	' Format value so that it is always 5 digits 
	if value > 99 then
		form = str$(value,3,1) + "`"
	else if value < -9 then
		form = str$(value,2,1) + "`"
	else
		form = str$(value,2,2) + "`"
	end if
end function

Sub Button_sub
	' Interrupt routine entered when button pressed
	Static Button_timer
	if Pin(GP18) = 0 then				' Switch down
		pause 50
		if Pin(GP18) <> 0 then exit sub		' Debounce switch
		Button_timer = timer
		Button_status = 0
	else if timer - Button_timer < 100 then
		exit sub					' Debounce switch
	else if timer - Button_timer < 1500 then
		Button_status = 1				' Short press
	else if timer - Button_timer > 10000 then
		Button_status = 3				' 10 second + press
	else
		Button_status = 2				' Long press
	end if
end sub

' 15 23 31 39 47 55 63 71 79 87 95 103 111 119 127 135 143 151 159 167 175 183 191 199 207 215 223 231 239 247 255

table:
Data 255,   0,   0 ' Red - Defined twice to enhance hot spots
Data 255,   0,   0 ' Red
Data 255,  63,   0
Data 255,  87,   0
Data 255, 111,   0
Data 255, 135,   0
Data 255, 159,   0
Data 255, 183,   0
Data 255, 207,   0
Data 255, 231,   0
Data 255, 255,   0 ' Yellow
Data 247, 255,   0
Data 231, 255,   0
Data 215, 255,   0
Data 199, 255,   0
Data 183, 255,   0
Data 167, 255,   0
Data 151, 255,   0
Data 119, 255,   0
Data   0, 255,   0 ' Green
Data   0, 255,  71
Data   0, 255,  87
Data   0, 255, 103
Data   0, 255, 119
Data   0, 255, 135
Data   0, 255, 151
Data   0, 255, 167
Data   0, 255, 183
Data   0, 255, 255 ' Cyan
Data   0, 235, 255
Data   0, 211, 255
Data   0, 187, 255
Data   0, 163, 255
Data   0, 139, 255
Data   0, 115, 255
Data   0,  91, 255
Data   0,  67, 255
Data   0,   0, 255 ' Blue
Data   0,   0, 191 ' Note these two entries are not strictly part of the spectrum,
Data   0,   0, 127 ' but are two darker shades of blue.

Data   0,   0,   0

Data 127,   0, 255 ' The spectrum beyond blue is not used!
Data 143,   0, 255
Data 159,   0, 255
Data 175,   0, 255
Data 191,   0, 255
Data 207,   0, 255
Data 223,   0, 255
Data 239,   0, 255
Data 255,   0, 255 ' Magenta
Data 255,   0, 239
Data 255,   0, 215
Data 255,   0, 191
Data 255,   0, 167
Data 255,   0, 143
Data 255,   0, 119
Data 255,   0,  95
Data 255,   0,  71
Data 255,   0,   0 ' Red



