; --------------------------------------------------------------------------------------
;
; thermopic.asm - firmware for a thermometer using pic
; Version 1.0, 01/22/2008
;
; Copyright (C) 2008 Waldeck Schutzer <waldeck@dm.ufscar.br>
;
; 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 2
; 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, write to the Free Software
; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
; USA.
;
; --------------------------------------------------------------------------------------
;
; Firmware para o termometro digital usando pic
; Versão 1.0, 22/01/2008
;
; Copyright (C) 2008 Waldeck Schutzer <waldeck@dm.ufscar.br>
;
; Este programa é software livre; você pode redistriuí-lo e/ou
; modificá-lo sob os termos da Licença Pública Geral GNU, conforme
; publicada pela Fundação do Software Livre; seja a versão 2
; da licença, ou (à sua escolha) qualquer versão posterior.
;
; Este programa é distribuído no intuito de que ele possa ser
; útil, mas SEM NENHUMA GARANTIA; sem mesmo a garantia implícita
; de MERCANTIBILIDADE ou ADEQUAÇÃO A UMA FINALIDADE ESPECÍFICA. Veja
; a Licença Púlica Geral GNU para mais detalhes.
;
; Você deve ter recebido uma cópia da Licença Púlica Geral GNU juntamente
; com este programa; caso contrário, escreva para a Fundação do Software
; Livre, Inc.,  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
; EUA.

;	LIST P=12F675
;

	__config _INTRC_OSC_NOCLKOUT & _WDT_ON & _PWRTE_ON & _MCLRE_OFF & _BODEN_ON & _CP_OFF & _CPD_OFF

; Pinagem
; -------
; O que temos conectado em cada pino do microcontrolador.
;
;                     12F675
;                    .---.---.
;               Vdd -| 1   8 |- Vss
; (saída) LED,GPIO5 -| 2   7 |- GPIO0/PGD (n/c)
;  (saída) tx,GPIO4 -| 3   6 |- GPIO1/AN1, tensão de referência (entrada analógica)
;       (n/c) GPIO3 -| 4   5 |- GPIO2/AN2, sinal do termômetro (entrada analógica)
;                    '-------'
;

	include <p12f675.inc>
	radix	dec

#define Fosc	4000000			; Frequencia do oscilador interno
#define SystemClock	(Fosc/4)	; Frequencia do clock do sistema
#define _Tacq   (50*SystemClock/1000000); 50us (valor de Tacq do conversor A/D)
#define Timer1Freq	100		; Hz
#define Timer1Div	(SystemClock/Timer1Freq)
if (SystemClock-Timer1Div*Timer1Freq) != 0
	error "A frequencia escolhida para o Timer1 nao divide o SystemClock"
endif
#define Timer1Value	(65536-Timer1Div+2)
#define TimerSvalue	Timer1Freq

#define _carry STATUS,C
#define _zero  STATUS,Z
#define _dc    STATUS,DC
#define BaseBanco0 0x20
#define TopoBanco0 0x5f

; Flags
#define _TakeSample 0			; Tomar nova amostra
#define _Negative   1			; Temperatura negativa
#define TakeSample Flags,_TakeSample
#define Negative Flags,_Negative

;
#define LED 5
#define _LED	GPIO,LED

;
; --------------------------------------------------------------------------------------
; Constantes para a comunicacao serial

#define	crc_poly	0x85		; Polinômio x^8+x^7+x^2+1
#define	tx	4
#define	_tx	GPIO,tx
baudrate	equ	9600	; bauds
baudconst       equ     ((SystemClock)/(3*baudrate) - 2)
if baudrate<1 || baudconst > 255
   error "Baudrate fora da faixa"
endif
;  #define active_low

SerialOne	macro
  ifdef active_low
	bcf	_tx
  else
	bsf	_tx
  endif
		endm
SerialZero	macro
  ifdef active_low
	bsf	_tx
  else
	bcf	_tx
  endif
		endm
SendChar	macro	ch
		movlw	ch
		call	calc_crc8	; acumula o crc para o caracter
		call	SerialSendChar	; envia o caracter pela porta serial
		endm
SendDigit	macro	dg
		movf	dg,w
		addlw	'0'
		call	calc_crc8	; acumula o crc para o caracter
		call	SerialSendChar	; e o envia pela porta serial
		endm
SendHex		macro	dg		; Atenção: esta macro não deve afetar o crc
		swapf	dg,w
		call	binhex
		call	SerialSendChar
		movf	dg,w
		call	binhex
		call	SerialSendChar
		endm

; 
; ------------------------------------------------------------------------------
; Ajustes de calibração
;

; Atenção: use os valores abaixo apenas como referência. Você
; deve ajustá-los para o seu circuíto em particular. Ver explicação nos
; comentários que precedem a rotina ComputeTemp

#define TensaoRef 2509			; em milivolts, deve ser aprox. 2.5. Medir.
#define GanhoAmp  2438			; /1000, deve ser aproximadamente 2.440. Medir.
#define ZeroSensor (-45000)		; zero graus do sensor
#define quo	((GanhoAmp*1023)/1000)
#define mplr	(2*100*TensaoRef*100-quo)/(2*quo) ; multiplicador
#define Offset	(0)			; ajuste (era -400)
#define base	(ZeroSensor+Offset)

;
; Esta macro realiza multiplicação rápida em precisão dupla de h:l por uma constante,
; uilizando B2:B1:B0 como area de trabalho.

Mult24k	macro	u,h,l,k
	local step, i
step 	set 0
i	set k
	movf	l,W		; Salva u:h:l em B2:B1:B0
	movwf	B0
	movf	h,W
	movwf	B1
	movf	u,W
	movwf	B2
  while i!=0
    if (i&1)==1
      if step==0
	movf	B0,W	; Primeira vez: move B2:B1:B0 para u:h:l
	movwf	l
	movf	B1,W
	movwf	h
	movf	B2,W
	movwf	u
      else
	movf	B0,W	; Demais vezes: soma B2:B1:B0 em u:h:l
	addwf	l,F
	movlw	1
	btfsc	_carry
	addwf	h,F
	btfsc	_carry
	incf	u,F
	movf	B1,W
	addwf	h,F
	btfsc	_carry
	incf	u,F
	movf	B2,W
	addwf	u,F
      endif
step 	set step+1
    endif
	bcf	_carry	; Faz B2:B1:B0 = 2 * B2:B1:B0
	rlf	B0,F
	rlf	B1,F
	rlf	B2,F
i 	set i>>1
  endw
 	endm

Bank0	macro
	bcf	STATUS,RP0	; Seleciona o banco 0
	endm
Bank1	macro
	bsf	STATUS,RP0	; Seleciona o banco 1
	endm

; ------------------------------ Memória RAM --------------------------------------
; Bloco0 da RAM
;
	CBLOCK	BaseBanco0
	iW
	iSTATUS
	ContAtraso
	A0		; Uso geral
	A1
	A2
	A3
	B0		; Uso geral
	B1
	B2
	B3
	C0
	D0
	D1
	D2
	D3
	D4
 	Flags
	TimerCtr
	delay
	txreg
	count
	led_ctr
	crc
	temps_indx
	temps:	32	; últimas 16 temperaturas medidas
	TopOfBank0	; não usar
	endc

  if TopOfBank0>(TopoBanco0+1)
	error "Estouro do banco0"
  endif

; ----------------------------------------------------------------
; Cold boot
	org	0
	nop
	movlw	high Startup
	movwf	PCLATH
	goto	Startup

; ----------------------------------------------------------------
; Vetor de interrupção
	org	4
	movwf	iW			; Mesmo local, não importa o banco
	swapf	STATUS,W		; Copia o status atual para W (movf estraga o flag Z!)
	clrf	STATUS			; Seleciona o banco0
	movwf	iSTATUS			; Salva o status atual

; ----------------------------------------------------------------
; Recarga do registrador do Timer1
;
Timer1Service
	movlw	low Timer1Value		; O valor de recarga é somado ao registrador
	addwf	TMR1L,f			; TMR1, que não pára. Isso garante que a próxima
	movlw	high Timer1Value	; interrupção ocorrerá no instante correto.
	skpnc				; A recarga de TMR1L provoca a perda de dois ciclos
	movlw	1+high Timer1Value	; já contabilizados em Timer1Value
	addwf	TMR1H,f
	
	bcf	PIR1,TMR1IF		; Devemos limpar o indicador de interrupção pendente.
	clrwdt				; Reinicia o temporizador Watchdog

; Decrementa o contador de software
	decfsz	TimerCtr,f
	goto	ExitIE

	movlw	Timer1Freq		; recarrega o contador de software
	movwf	TimerCtr

	bsf	TakeSample		; sinal para tomar uma nova amostra

; ------------------------------------------------------------------------------
; Restaura o contexto e retorna para o processo interrompido
;
ExitIE
	swapf	iSTATUS,W		; Copia status do processo interrompido para W
	movwf	STATUS			; Restaura o status (estamos de volta ao bancoX)
	swapf	iW,F			; Restaura W do processo interrompido
	swapf	iW,W			; (movf estraga o flag Z!)
	retfie				; Retorna ao processo interrompido

; ------------------------------------------------------------------------------
;
Startup
	call	0x3ff			; Constante de calibração do oscilador
	banksel	OSCCAL
	movwf	OSCCAL			; Ajusta a freqüência do oscilador

	banksel	CMCON
	movlw	B'00000111'		; disabilita os comparadores
	movwf	CMCON

	movlw	B'00000000'		; Timer1 control register
;		  ||||||||
;		  |||||||'--- TMR1ON   Timer1 on bit (0=off)
;		  ||||||'---- TMR1CS   Timer1 clock source select (0=internal)
;		  |||||'----- ~T1SYNC  Timer1 external clock input synchronization (don't care)
;		  ||||'------ T1OSCEN  LP oscilator enable (0=oscilator is off)
;		  |||'------- T1CKPS0  Timer1 clock prescale select (00=1:1)
;		  ||'-------- T1CKPS1
;		  |'--------- TMR1GE   Timer1 gate enable (0=disabled)
;		  '---------- 	       Unassigned
	movwf	T1CON	

	movlw	B'01000000'		; Setup the interrupt control register
;		  ||||||||
;		  |||||||'--- GPIF   Port change interrupt flag	(1=interrupt occurs)
;		  ||||||'---- INTF   GP2/INT external interrupt flag (don't care)
;		  |||||'----- T0IF   Timer0 overflow interrupt flag (don't care)
;		  ||||'------ GPIE   Port change interrupt enable (0=disabled)
;		  |||'------- INTE   GP2/INT external interrupt enable (0=disabled)
;		  ||'-------- T0IE   Timer0 overflow interrupt enable (0=disabled)
;		  |'--------- PEIE   Peripheral interrupt enable (1=enabled)
;		  '---------- GIE    Global interrupt enable (0=disabled for now)
	movwf	INTCON

	banksel	TMR1H
	movlw	high Timer1Value	; Preload Timer1 value
	movwf	TMR1H
	movlw	low Timer1Value		
	movwf	TMR1L

	banksel PIE1
	movlw	B'00000001'		; enable timer 1 interrupts
;		  ||  |  |
;		  ||  |  '--- TMR1IE   Timer1 overflow interrupt enable (1=enabled)
;		  ||  '------ CMIE     Comparator interrupt enable (0=disabled)
;		  |'--------- ADIE     A/D converter interrupt enable (0=disabled)
;		  '---------- EEIE     EEPROM write complete interrupt enable (0=disabled)
	movwf	PIE1

	banksel ANSEL
	movlw	b'01010110'
;                  |||||||             Entradas analógicas ligadas:
;                  ||||||'-- ANS0
;                  |||||'--- ANS1	AN1 - tensão de referência
;                  ||||'---- ANS2       AN2 - sinal do termômetro
;                  |||'----- ANS3
;                  ||'------ ADCS0\
;                  |'------- ADCS1|      : qual é a temporização do conversor A/D?
;                  '-------- ADCS2/        101 = Fosc/16
	movwf	ANSEL			; Configura as entradas do conversor A/D

	banksel	ADCON0
	movlw	b'00000000'
;                 ||  ||||
;                 ||  |||'-- ADON:     0 = módulo conversor desligado por economia
;                 ||  ||'--- GO/DONE:  ligando este bit inicia-se a conversão A/D
;                 ||  |'---- CHS0\
;                 ||  '----- CHS1/     qual entrada ler 00=AN0, 01=AN1, 10=AN2, 11=AN3
;                 |'-------- VCFG:     0: Vref=Vdd, 1: Vref=AN1
;                 '--------- ADFM:     Alinhamento de ADRES: 1=direita, 0=esquerda
	movwf	ADCON0

	banksel	TRISIO
	movlw	~(1<<LED|1<<tx)		; Configura as portas de E/S
	movwf	TRISIO

	banksel	GPIO
	clrf	GPIO

	banksel OPTION_REG		; Initialize the options register
	MOVLW	B'10000000'
;                 |||||||'--- PS0    prescaler select bits (don't care)
;                 ||||||'---- PS1		
;                 |||||'----- PS2
;                 ||||'------ PSA    prescaler assign bit (don't care)
;                 |||'------- T0SE   Timer 0 source edge select bit (don't care)
;                 ||'-------- T0CS   Timer 0 clock source select bit (dont'care)
;                 |'--------- INTEDG Interrupt edge select bit (don't care)
;                 '---------- ~GPPU  GPIO pull-up enable bit (pull-ups are disabled)
	movwf	OPTION_REG
	
; Limpa a RAM
	movlw	0xAA		; Um valor não-nulo
	movwf	TopoBanco0	; Ultima posição de memória
	movlw	iW		; Posição inicial a limpar
	movwf	FSR
	clrf	INDF		; limpa memória
	incf	FSR,F		; incrementa o apontador
	movf	TopoBanco0,f	; testa se a ultima posição foi zerada
	btfss	_zero		; salta quando zerar
	goto	$-4		; senão continua limpando
	clrwdt

Limpou
	banksel	TimerCtr
	movlw	Timer1Freq
	movwf	TimerCtr	; Ajusta o contador de software para intervalo de 1s

	movlw	16		; Inicializa a fila de temperaturas, tomando
	movwf	C0		; o número suficiente de amostras
	clrf	temps_indx
	call	Sample
	decfsz	C0,f
	goto	$-2

	banksel T1CON
	bsf	T1CON,TMR1ON	; Liga o Timer1
	bcf	INTCON,INTF	; Limpa o flag de interrupções
	bcf	PIR1,TMR1IF	; Limpa o flag de interrupções do Timer1
	bsf	INTCON,GIE	; Habilitação geral de interrupções

	call	EnviaMensagem
;

; ------------------------------------------------------------
; Processo principal
; ------------------

Main
	clrwdt			; Limpamos o contador do watchdog, evitando reset

	btfsc	TakeSample	; Devemos fazer nova leitura da temperatura?
	call	SendSample	; Sim, vamos ler e enviar pela serial

	movf	led_ctr,f	; Testamos o contador do LED. Se já chegou a zero,
	skpnz			; é porque o LED já está apagado e não há nada a fazer
	goto	Main		; senão voltar ao loop.
	decfsz	led_ctr,f	; Caso contrário, decrementamos o contador e voltamos
	goto	Main		; ao loop se ainda não chegou a zero.

	bcf	_LED		; Quando o contador do LED chegou a zero, apagamos o LED
	goto	Main		; e voltamos ao loop


; ------------------------------------------------------------
; SendSample
; ----------
; Toma uma amostragem de temperatura, converte-a para Celsius e envia o resultado
; pela porta serial.

SendSample
	call	Sample		; Toma uma nova amostragem. Para diminuir o ruído, tiramos
				; a média das últimas temperaturas lidas. Para isso, o novo
				; dado é inserido em uma fila.

	call	MediaTemp	; Calcula a média das amostragens recentes, que estão
				; presentes em uma fila.

	call	ComputeTemp	; Converte o valor amostrado para graus Celsius

	call	BinDec		; Converte o valor binário para decimal

	call	SendTemp	; Envia o resultado pela porta serial

	bcf	TakeSample	; Limpando este flag, vamos ficar na espera do
				; temporizador.

	movlw	250		; Carrega o contador do LED, que irá acender por
	movwf	led_ctr		; alguns instantes.
	bsf	_LED		; Acende o LED agora

	return

; ------------------------------------------------------------
; Sample
; ------
; Toma uma nova leitura da temperatura e envia pela porta serial

Sample
	banksel	ADCON0
	movlw	b'11001001'
;                 ||  ||||
;                 ||  |||'-- ADON:     1 = liga o módulo conversor A/D
;                 ||  ||'--- GO/DONE:  ligando este bit inicia-se a conversão A/D
;                 ||  |'---- CHS0\
;                 ||  '----- CHS1/     lê o canal AN2
;                 |'-------- VCFG:     0: Vref=Vdd, 1: Vref=AN1
;                 '--------- ADFM:     Alinhamento de ADRES: 1=direita, 0=esquerda
	movwf	ADCON0

	call	Tacq		; Espera pelo tempo de amostragem

	bsf	ADCON0,GO	; Dá partida no conversor A/D

	btfsc	ADCON0,GO	; Espera pelo término da conversão
	goto	$-1

	banksel	ADRESL		; Toma o resultado da leitura de ADRESH:ADRESL
	movf	ADRESL,w	; Os 10 bits do resultado estão alinhados à direita
	movwf	A0
	banksel	ADRESH
	movf	ADRESH,w
	movwf	A1

	bcf	ADCON0,ADON	; Desliga o conversor A/D para economizar energia

	call	StoreTemp	; Armazena a temperatura lida na fila temps

	return

; ------------------------------------------------------------
; Converte a medida do conversor A/D em temperatura Celsius
; ----------
; Entrada: A1:A0 = amostragem do conversor A/D
; Saída: A2:A1:A0 = temperatura em milesimos de grau Celsius
;
; Teoria
; ------
; A tensao, medida em milivolts, informada pelo LM35DZ pode
; ser imediatamente interpretada como temperatura. Por
; exemplo, uma tensão de 245 mV equivale a 24.5 graus
; Celsius. Assim cada incremento de 1mV equivale a um décimo
; de grau.
;
; Por outro lado, a tensão de referência é de 2.5V
; via divisor de tensão consistindo de dois resistores
; de 10K em série, o primeiro ligado à Vdd e o segundo a Vss, e
; a tomada central ligada a Vref+. Alem disso, o
; conversor opera com 10 bits, logo cada bit da amostra
; vale 2.5V/1023 = 2.44mV.
;
; Neste projeto estamos usando um amplificador operacional
; para ampliar a tensão fornecida pelo LM35 em aproximadamente
; 2.44 vezes, o que significa que cada incremento um décimo de 
; grau Celsius será representado por um incremento de
; aproximadamente 2.44mV na tensão da saida do amplificador.
;
; Com um trimpot multivoltas a tensão de 500mV foi aplicada
; à entrada do amplificador e na saída verificada a tensão
; de 1.219V. O ganho desse amplificador é, portanto, aproximadamente
; 1.219/0.5 = 2.438 vezes, próximo o suficiente de 2.44.
;
; Além disso, a fim de registrar temperaturas negativas, o pino
; terra do LM35 foi ligado a uma tensão de referência de
; 450mV, fornecida pela saída de um amplificador operacional
; LM358. Isso significa que a temperatura 0 graus Celsius
; será registrada como 45.0 graus.
;
; Portanto, para converter corretamente a amostragem para
; graus Celsius, devemos fazer o seguinte Cálculo:
;
; Tc = ADRES * 100 * 2.5 / 1023 / 2.438 - 45.0 graus Celsius
;
; A unidade de medida interna é de milésimos de grau, e
; com isso essa conta fica:
;
; Tc = ADRES * 100000 * 2.5 / 1023 / 2.438 - 45000 m-graus
;

;
; Como o resultado deve caber em 16 bits, o topo da
; escala não poderá ser maior do que 65.535 graus.
; No entanto, o maior valor de amostragem do conversor A/D
; de 10 bits é 1023. Para esse valor, usando a fórmula
; acima, temos Tc = 57.5 graus, e este valor cabe em 16
; bits. Portanto o topo da escala, limitado pelo
; conversor A/D é 57.5 graus.
;

; Em resumo, para que este programa funcione, é preciso
; medir a tensão de referência, a tensão de zero
; do sensor e o ganho do amplificador. Estes valores
; devem ser alimentados em 

ComputeTemp
	clrf	A2			; A2:A1:A0 = ADRES * mplr
	Mult24k	A2,A1,A0,mplr
	movlw	100
	movwf	B0
	call	Divisao248

	movlw	low(base)		; A2:A1:A0 = A2:A1:A0 - base
	addwf	A0,f
	movlw	high(base)
	skpnc
	movlw	1+high(base)
	addwf	A1,f
	movlw	upper(base)
	skpnc
	movlw	low(1+upper(base))
	addwf	A2,f

	bcf	Negative	; Presume resultado positivo

	btfss	A2,7		; Resultado negativo?
	goto	PositiveRes

	bsf	Negative	; Sinaliza resultado negativo

	comf	A0,f		; Faz temp = -temp (complemento de dois)
	comf	A1,f		; para o resultado ficar positivo
	comf	A2,f
	movlw	1
	addwf	A0,f
	skpnc
	addwf	A1,f
	skpnc
	addwf	A2,f

PositiveRes
; nada mais a fazer senão retornar com a temperatura em m-graus em A2:A1:A0 (ignoramos A2)
	return

; ------------------------------------------------------------
; SendTemp
; --------
; Envia pela porta serial uma mensagem informando a temperatura registrada
; em graus Celsius, com tags, no seguinte formato:
;  <temp>[-][x]y.zC</temp>,CR,LF
; onde xy.z são os dígitos da temperatura.

SendTemp
	SendChar '<'		; Envia o campo com a leitura da temperatura
	SendChar 't'
	SendChar '>'
	clrf	crc		; tudo entre <t> e </t> entrará no cálculo do crc
	btfss	Negative
	goto	SendTenths
	SendChar '-'		; Precede com '-' se a temperatura for negativa
SendTenths
	movf	D4,f
	skpnz			; Suprime zero não-significativo
	goto	SendUnits
	SendDigit D4
SendUnits
	SendDigit D3
	SendChar '.'
	SendDigit D2
	SendChar 'C'		; Temperatura em Celsius
	movf	crc,w		; Cópia backup do crc
	movwf	B0
	SendChar '<'
	SendChar '/'
	SendChar 't'
	SendChar '>'
	SendChar '<'		; Envia o campo com o CRC
	SendChar 'c'
	SendChar '>'
	SendHex	B0
	SendChar '<'
	SendChar '/'
	SendChar 'c'
	SendChar '>'
	SendChar 0x0d	; CR
	SendChar 0x0a	; LF

	return


; ------------------------------------------------------------
; StoreTemp
; ---------
; O valor A1:A0 é armazenado na fila temps, na posição indicada por temps_indx.
; Em seguida, temps_indx é incrementado voltando a zero quando chegar a 16.

StoreTemp
	movlw	temps			; Endereço de temps no registrador w
	addwf	temps_indx,w		; Soma o índice da posição inicial
	addwf	temps_indx,w		; duas vezes, pois cada entrada tem 2 bytes,
	movwf	FSR			; e o resultado vai para o registrador FSR
	movf	A0,w			; LSB do número a armazenar
	movwf	INDF			; é guardado primeiro
	incf	FSR,f			; Avança para a próxima posição
	movf	A1,w			; MSB do número a armazenar
	movwf	INDF			; é guardado em seguida
	incf	temps_indx,f		; Incrementa o indexador
	movlw	15			; fazendo-o retornar a 0
	andwf	temps_indx,f		; quando tiver chegado a 16(=15+1)
	return


; ------------------------------------------------------------
; MediaTemp
; ---------
; Calcula a média dos números armazenados na fila temps

MediaTemp
	movlw	temps			; Endereço da fila temps no registrador w
	movwf	FSR			; vai para o registrador FSR
	movlw	16			; Quantos elementos?
	movwf	C0
	clrf	A0
	clrf	A1
	clrf	A2
MediaTempLoop
	movf	INDF,w			; LSB do elemento em w
	incf	FSR,f			; Avança o ponteiro para o MSB
	addwf	A0,f			; Soma LSB em A0
	movf	INDF,w			; MSB do elemento em w
	skpnc				; Se estourou a soma, então pega o
	incf	INDF,w			; MSB+1 do elemto em w
	incf	FSR,f			; Avança para o LSB do próximo elemento
	addwf	A1,f			; Soma o MSB em A1
	skpnc				; Se estourou a soma, então
	incf	A2,f			; vai um na próxima casa
	decfsz	C0,f			; Decrementa o contador de dígitos
	goto	MediaTempLoop		; Continua somando se ainda há mais elementos
	movlw	15			; Acrescenta 15 à soma, para permitir arredondar
	addwf	A0,f			; o resultado da divisão inteira abaixo.
	movlw	1
	skpnc
	addwf	A1,f
	skpnc
	incf	A2,f
	rrf	A2,f			; Divide o resultado por 16, por meio de 4
	rrf	A1,f			; deslocamentos sucessivos da soma para a
	rrf	A0,f			; direita (cada um divide o número por 2)
	rrf	A2,f
	rrf	A1,f
	rrf	A0,f
	rrf	A2,f
	rrf	A1,f
	rrf	A0,f
	rrf	A2,f
	rrf	A1,f
	rrf	A0,f
	return				; A média está em A1:A0

; --------------------------------------------------------
; calc_crc8
; ---------
; Calculo de CRC-8 sem otimizações
; Entrada: W=byte da mensagem
; Saída: W=byte da mensagem
; Utiliza: A0, A1 e A2
; Atualiza: crc

calc_crc8
	movwf	A0		; Salva o valor do acumulador
	movwf	A1		; e tira uma cópia que será usada pela rotina
	movlw	8		; Número de bits a considerar
	movwf	A2		; Inicializa o contador de bits
calc_crc8_loop
	rlf	A1,f		; Próximo bit da mensagem vai para o carry
	rlf	crc,f		; Gira o buffer para a esquerda. O bit da mensagem entra e
				; o bit de cima sai.
	skpc			; Se o bit que saiu for zero, continua a alimentar bits
	goto	calc_crc8_next ; da mensagem para dentro do buffer.
	movlw	crc_poly	; Por outro lado, se o bit que saiu for um, subtrai o
	xorwf	crc,f		; polinômio do buffer. Em aritmética polinomial módulo 2,
				; isso equivale simplesmente a fezer um ou exclusivo do
				; polinômio com o buffer.
calc_crc8_next
	decfsz	A2,f		; Decrementa o contador de bits e volta ao loop
	goto	calc_crc8_loop	; caso o contador ainda não tenha chegado a zero.
	movf	A0,w		; Restaura o acumulador
	return

; --------------------------------------------------------
; Divisao248
; ----------
; Divisão não-sinalizada de um número 24bits por outro
; de 8 bits.
; Entrada: A2:A1:A0=dividendo
;	         B0=divisor
; Saída:   A2:A1:A0=quociente
;                C0=resto
; Usa: D3:D2:D1:D0 - área de trabalho

Divisao248
	movf	A0,W	; Copia A2:A1:A0 para a área de trabalho D2:D1:D0
	movwf	D0
	movf	A1,W
	movwf	D1
	movf	A2,W
	movwf	D2

	clrf	C0	; Zera o campo que conterá o resto da divisão

	movlw	24	; Inicializa o contador de 24 bits
	movwf	D3
;
Divisao248Loop
	clrc
	rlf	D0,F		; Desloca o dividendo para a esquerda,
	rlf	D1,F
	rlf	D2,F
	rlf	C0,F		; e para dentro do resto c.
	movf	B0,W		; Faz W=c-b
	subwf	C0,W
	skpnc			; O carry será igual a 1 se c>=b
	movwf	C0		; Nesse caso c=c-b
	rlf	A0,F		; Deslocamos o carry para dentro do quociente
	rlf	A1,F
	rlf	A2,F
	decfsz	D3,F
	goto	Divisao248Loop
;
; O quociente agora está em A2:A1:A0 e o resto em C0
	return

; -----------------------------------------------
; BinDec
; ------
; Converte um numero de binario para decimal.
; -------------------------
; Entrada: A2:A1:A0
; Saida: D4:D0 (um dígito por byte) -- valor máximo 99999
; Usa B3:B2:B1:B0
;
; Descrição: 
; Sejam A_15,...,A_0 os bits de A1:A0. Para convertermos o número em A1:A0
; para decimal basta avaliarmos o polinômio
;              2^15 A_15 + 2^14 A_14 + ... + 2 A_1 + A_0
; só que usando aritmética decimal. Isto parece complicado, mas usando o
; dispositivo prático de Briot-Ruffini, pode ser feito de modo bastante
; eficiente:
;             2(... 2(2 A_15 + A_14) + A_13) + ... ) + A_0

BinDec
	movlw	24			; Inicializa o contador de dígitos binários
	movwf	B3

	movf	A0,w			; Copia o número para uma área temporária B2:B1:B0
	movwf	B0
	movf	A1,w
	movwf	B1
	movf	A2,w
	movwf	B2

	clrf	D0			; Zera a área que vai conter o resultado
	clrf	D1
	clrf	D2
	clrf	D3
	clrf	D4

BinDecAjuste
	bcf	_carry			; Alinha o número B1:B0 à esquerda,
	rlf	B0,f			; permitindo a entrada dos bits mais significativos
	rlf	B1,f			; para dentro do carry C.
	rlf	B2,f
	btfsc	_carry			; Efetivamente, estamos evitando considerar
	goto	BinDecLoop		; os zeros à esquerda em B2:B1:B0
	decfsz	B3,f
	goto	BinDecAjuste
	return

BinDecLoop
	rlf	D0,f			; Este bloco multiplica D0 por 2.
	movf	D0,W			; Se o resultado for maior do que 10
	addlw	-10			; então subtrai 10
	btfsc	_carry			; e soma um no próximo digito (antes que
	movwf	D0			; este seja multiplicado por 2).

	rlf	D1,f			; Idem ao bloco acima, para D1
	movf	D1,W
	addlw	-10
	btfsc	_carry
	movwf	D1

	rlf	D2,f			; Idem ao bloco acima, para D2
	movf	D2,W
	addlw	-10
	btfsc	_carry
	movwf	D2

	rlf	D3,f			; Idem ao bloco acima, para D3
	movf	D3,W
	addlw	-10
	btfsc	_carry
	movwf	D3

	rlf	D4,f			; Idem ao bloco acima, para D4
	movf	D4,W
	addlw	-10
	btfsc	_carry
	movwf	D4

	rlf	B0,f			; Alimentamos o próximo dígito binário para
	rlf	B1,f			; dentro do carry C
	rlf	B2,f

	decfsz	B3,f			; Decrementa o contador de dígitos binários
	goto	BinDecLoop

	return				; Retorna quando o contador chegar a zero


; ------------------------------------------------------------
; binhex
; ------
; Converte um número binário para hexa (um dígito)
; Entrada: w = digito a converter em hexa (parte baixa de w)
; Saída: w = dígito hexadecimal (em ascii)
; Usa: somente W e STATUS

binhex
	andlw	0x0f
	addlw	0x08
	btfsc	_dc
	goto	$+3
	xorlw	0x38		; Retorna '0' a '7' se o dígito estiver entre 0 e 7
	return
	andlw	0x07
	skpz
	goto	$+3
	movlw	'8'
	return
	addlw	-1
	skpz
	goto	$+3
	movlw	'9'
	return
	addlw	'a'-1
	return

; ------------------------------------------------------------
; SerialSendChar
; --------------
; Envia um caractere pela porta serial

SerialSendChar
        movwf   txreg		; 1
	SerialZero		; 1 - Send start bit 
;       movlw   baudconst	; 1			 ; Muito inexato
;	movlw	(SystemClock/baudrate-13)/3 + 1/2	 ; Melhor mas ainda inexato
	movlw	(2*SystemClock-23*baudrate)/(6*baudrate) ; Arredondando é melhor
        movwf   delay		; 1
        movlw   9		; 1
        movwf   count		; 1
txbaudwait
        decfsz  delay,f		; 1*baudconst
        goto    txbaudwait	; 2*baudconst-1
;       movlw   baudconst	; 1			 ; Muito inexato
;	movlw	(SystemClock/baudrate-10)/3 + 1/2	 ; Ainda inexato
	movlw	(2*SystemClock-17*baudrate)/(6*baudrate) ; Arredondando é melhor
        movwf   delay		; 1
        decfsz  count,f		; 1
        goto    SendNextBit	; 2 if count!=0, 1 if count=0
        movlw	baudconst	; 1
        movwf   delay		; 1
	SerialOne		; 1 - Send stop bit
	decfsz	delay,f		; 1*baudconst
	goto	$-1		; 2*baudconst-1
	movlw	baudconst	; 1
	movwf	delay		; 1
	decfsz	delay,f		; 1*baudconst
	goto	$-1		; 2*baudconst-1
	movlw	baudconst	; 1
	movwf	delay		; 1
	decfsz	delay,f		; 1*baudconst
	goto	$-1		; 2*baudconst-1
        return			; 2
SendNextBit
        rrf     txreg, F	; 1
	skpc			; 1
        goto    Setlo		; 2 if txbit=1, 1 if txbit=0
	SerialOne		; 1
        goto    txbaudwait	; 2
Setlo
	SerialZero		; 1
        goto    txbaudwait	; 2

; Startbit timing: 5+3*baudconst-1+5+2+2 = 13+3*baudconst
; hence, start bit t=13+3b, (t-13)/3=b

; Intermediate bit timing: 2+3*baudconst-1+5+2+2 = 10+3*baudconst
; hence, bit timing t=10+3b, (t-10)/3=b



; ---------------------------------------------------------------------------------------
; Rotinas de atraso
;

Tacq
Atraso20us				; 2 clocks na chamada
	clrwdt
	movlw	(_Tacq-5)/3+1		; 1 clock
	movwf	ContAtraso		; 1 clock
AtrasoLoop
	decfsz	ContAtraso,F		; n*1 clock
	goto	AtrasoLoop		; n*2 clocks - 1
	return				; 2 clocks no retorno
					; Total t=2+1+1-1+2+3*n=3*n+5clocks
					; Então n=(t-5)/3

; Exemplo para atrasos adicionais
; Atraso50us				; 2 clocks na chamada
;	clrwdt
;	movlw	(T50us-7)/3+1		; 1 clock
;	movwf	ContAtraso		; 1 clock
;	goto	AtrasoLoop		; 2 clocks + 3*n+1 clocks
					; Total: 2+1+1+2+3*n+1=3*n+7
					; Entao n=(t-11)/3

; -------------------------------------------------------------------------------------
; EnviaMensagem
; -------------
; Envia a mensagem contida na área abaixo pela linha serial
;
EnviaMensagem
	clrf	C0		; apontador da mensagem
EnviaMensagemLoop
	movlw	high Mensagem
	movwf	PCLATH
	movlw	low Mensagem
	addwf	C0,w
	skpnc
	incf	PCLATH,f
	call	TabMensagem	; pega o próximo caracter da mensagem
	iorlw	0
	skpnz
	return
	call	SerialSendChar
	incf	C0,f
	goto	EnviaMensagemLoop

TabMensagem
	movwf	PCL
Mensagem
	dt	0x0d,0x0a,"<head>",0x0d,0x0a
	dt	"ThermoPIC V1.0 22/01/2008",0x0d,0x0a
	dt	"-----------------------------",0x0d,0x0a
	dt	"Copyright (c) 2008, Waldeck Schutzer, "
	dt	"waldeck@dm.ufscar.br",0x0d,0x0a
	dt	"</head>",0x0d,0x0a
	dt	0

   if $>1024
      error "Estouro da memoria de programa"
   endif
	
	end