;***************************************************************************
;                                                                     
;	Precision Digital Altimeter Transmitter with NSC ADC12130
;	=========================================================
;
;	written by Peter Luethi, 25.12.1999, Switzerland
;	last update : 23.4.2001
;
;	This code and accompanying files may be distributed freely and
;	modified, provided this header with my name and this notice remain
;	intact. Ownership rights remain with me.
;	You may not sell this software without my approval.
;
;	This software comes with no guarantee or warranty except for my
;	good intentions. By using this code you agree to indemnify me from
;	any liability that might arise from its use.
;
;
;	SPECIFICATIONS
;	==============
;	Processor:		Microchip PIC 16F84
;	Clock:			4.00 MHz XT
;	Throughput:		1 MIPS
;	Required Hardware:	NSC A/D Converter ADC12130
;				Motorola MPXS4100 A
;				Quad Op-Amp LM324
;	  - for remote use:	Wireless Transmitter 9600 bps
;	  - for use with PC:	RS232 level shifter (MAX232)
;	Serial Output:		9600 baud, 8 Bit, No Parity, 1 Stopbit
;
;
;	DESCRIPTION
;	===========
;	This program has been designed for the Precision Digital Altimeter
;	transmitter based on the PIC 16F84, the NSC ADC12130 12 bit A/D
;	converter and the Motorola MPXS4100 A absolute pressure sensor.
;	For further processing and visualization of the acquired data,
;	there is another PIC with LCD display or a personal computer
;	necessary. The serial output data of this transmitter has common
;	RS232 format, so you can either use it directly connected to a
;	personal computer or in conjunction with a wireless interface.
;	If used with a personal computer, make sure there is a RS232
;	level shifter (MAX232) between the PIC and the computer.
;
;	The whole transmitter consists of 4 sections:
;
;
;	1. Data Aquisition & Analog Pre-Processing
;	==========================================
;	The MPXS4100 A absolute pressure sensor is connected to A/D
;	input channel 0. Two fourth order Butterworth active low pass
;	filters (fc = 10 Hz) are implemented in front of the A/D channels
;	0 and 1 - realized with a quad Op-Amp - to minimize noise and to
;	prevent aliasing.
;
;	2. A/D Conversion
;	=================
;	To convert the analog to digital data with adequate resolution,
;	the NSC ADC12130 12 bit A/D converter has been used here.
;	The communication between the A/D converter and the PIC is done with
;	a software based SSP (Synchronous Serial Port) interface.
;	Output format: Sign bit, MSB <12 bit data> LSB, X X X
;
;	3. Digital Data Processing
;	==========================
;	After analog filtering and A/D conversion, the data has still some
;	noise, what results in slightly toggling values.
;	If we want to get rid of that, we have to implement a math routine,
;	which calculates the actual value out of the actual sample and the
;	previous ones.
;	In this program it has been realized with a four stage ring buffer,
;	which stores four 16 bit values (12 valid bits). Each of these values
;	is the average calculated from 16 previously acquired 12 bit A/D
;	samples. The current output value results from the average of this
;	ring buffer. So one output value will be calculated every 16 A/D
;	samples, with a history of 48 (=64-16) samples. The last acquired
;	value has only a weight of a quarter.
;	The NSC ADC12130 provides 2 input channels (CH0, CH1). I have used
;	the same digital signal processing methods for both of them.
;
;	4. Data Transmission
;	====================
;	The calculated output value is handled over to an RS232 routine.
;	It is then transmitted serially to either a MAX232 or a wireless
;	transmitter.
;	Here, the m_rs096.asm module has been used to provide compatibility
;	to wireless transmitters. Most of them support only up to 9600 bps.
;	If you want to use this routine with a higher transmission rate (e.g.
;	with a PC interface), you can only change the included m_rs098.asm
;	to another one.
;
;
;	IMPLEMENTATION
;	==============
;	The following features are implemented in the PIC 16F84:
;	- Setup of SSP communication and control lines to A/D converter
;	- Auto Calibration and Mode Configuration of A/D converter
;	- Readout of A/D values
;	- Digital Data Processing (as described above)
;	- RS232 transmission @ 9600 baud
;	- (No RS232 reception needed, only implemented to show complete
;	   RS232 interface for tests or further enhancements.)
;
;                                                                     
;***************************************************************************


;***** PROCESSOR DECLARATION & CONFIGURATION *****

	PROCESSOR 16F84
	#include "p16F84.inc"
	
	; embed Configuration Data within .asm File.
	__CONFIG   _CP_OFF & _WDT_OFF & _PWRTE_ON & _XT_OSC


;***** MEMORY STRUCTURE *****

	ORG	0x00		; processor reset vector
  	goto	MAIN

	ORG	0x04		; interrupt vector location
	goto	ISR		; Interrupt Service Routine


;***** PORT DECLARATION *****	

	#define	CS	PORTA,0	; chip select, active low
	#define	CONV	PORTA,1	; do conversion, active low
	#define	DO	PORTA,2	; PIC serial data output
	#define	SCK	PORTA,3	; serial clock, idle state low
	#define	DI	PORTA,4	; PIC serial data input

	#define	TXport	PORTB,1	; RS232 output port, could be
	#define	TXtris	TRISB,1	; any active push/pull port

	; RS232 input port is RB0, because of its own interrupt flag.
	; The RS232 data capture in the ISR has no functional purpose
	; in this project, implemented only for test use.


;***** CONSTANT DECLARATION *****

	CONSTANT BASE = 0x0C	; Base address of user file registers

   ;*** CONFIGURATION COMMANDS of NSC ADC12130 ***
	CONSTANT AutoCal    = b'00100000'
	CONSTANT AutoZero   = b'00100100'
	CONSTANT PowerUp    = b'00101000'
	CONSTANT PowerDown  = b'00101100'
	CONSTANT StatusRead = b'00110000'
	CONSTANT Unsigned   = b'00110100'
	CONSTANT Signed     = b'10110100'
	CONSTANT AT6        = b'00111000'
	CONSTANT AT10       = b'01111000'
	CONSTANT AT18       = b'10111000'
	CONSTANT AT34       = b'11111000'
	CONSTANT DummyCmd   = b'00000000'

   ;*** CH0 : channel 0, se : single-ended, format, first bit ***
	CONSTANT CH0se12MSB = b'10000000'
	CONSTANT CH0se16MSB = b'10000100'
	CONSTANT CH0se12LSB = b'10010000'
	CONSTANT CH0se16LSB = b'10010100'

   ;*** CH1 : channel 1, se : single-ended, format, first bit ***
	CONSTANT CH1se12MSB = b'11000000'
	CONSTANT CH1se16MSB = b'11000100'
	CONSTANT CH1se12LSB = b'11010000'
	CONSTANT CH1se16LSB = b'11010100'

   ;*** Ring Buffer ***
	CONSTANT BufferBASE = BASE+d'20' ; start address of ring buffer
	CONSTANT BufferLENGTH = d'8'	 ; number of buffer registers,
					 ; what means four 16 bit buffer
					 ; samples

;***** REGISTER DECLARATION *****

	; universal temporary register
	; (TEMP1 used by m_wait, TEMP1 and TEMP2 by m_rsxxx)
	TEMP1	set	BASE+d'0'
	TEMP2	set	BASE+d'1'

	; general
	HI	equ	BASE+d'2'	; high nibble of data
	LO	equ	BASE+d'3'	; low nibble of data
	CH0_HI	equ	BASE+d'4'
	CH0_LO	equ	BASE+d'5'
	CH1_HI	equ	BASE+d'6'
	CH1_LO	equ	BASE+d'7'

	; A/D converter
	AD_cnt	equ	BASE+d'8'	; counter
	SSPSR	equ	BASE+d'9'	; SSP Shift Register

	; average routine
	sum_cnt	equ	BASE+d'10'	; counter
	sum_LO	equ	BASE+d'11'	; summation registers
	sum_HI	equ	BASE+d'12'

	; ring buffer
	RB_pnt	equ	BASE+d'13'	; pointer, BufferBASE offset

	; RS232
	TXD	equ	BASE+d'14'	; used for transmission
	RXD	equ	BASE+d'15'	; received value

	; interrupt context save/restore
	W_TEMP	equ	BASE+d'16'	; context register (ISR)
	STATUS_TEMP equ	BASE+d'17'	; context register (ISR)
	PCLATH_TEMP equ	BASE+d'18'	; context register (ISR)
	FSR_TEMP equ	BASE+d'19'	; context register (ISR)

	; ATTENTION: check beginning of ring buffer (Constant Declaration) !!!


;***** INCLUDE FILES *****

	#include "../m_bank.asm"
	#include "../m_wait.asm"
	#include "../m_rs096.asm"


;***** MACROS *****

ADinit	macro
	BANK1
	movlw	b'11110000'	; set A/D control lines I/O direction
	movwf	TRISA
	BANK0
	movlw	b'00000011'	; set A/D control lines :
	movwf	PORTA		; disable chip select & conversion,
	endm			; SCK low

ADenable macro
	movlw	b'11111100'	; select chip & enable conversion
	andwf	PORTA,1
	endm

ADdisable macro
	movlw	b'00000011'	; deselect chip & disable conversion
	iorwf	PORTA,1
	endm

ADcmd	macro	ADcommand
	movlw	ADcommand
	call	AD_cmd
	endm

RBinit	macro
	call RB_init
	endm


;***** SUBROUTINES *****

;*** A/D Converter ***
AD_cmd	movwf	SSPSR		; call with command in w
	movlw	d'8'
	movwf	AD_cnt
ad_loop	
	;*** TRANSMISSION : MSB out on pin DO ***
	btfss	SSPSR,7		; check MSB, skip if set
	bcf	DO
	btfsc	SSPSR,7		; check MSB, skip if cleared
	bsf	DO
	; btfss/btfsc used due to illegal consecutive write cycles
	; on output pins and to prevent unnecessary spikes:
	; only one write cycle is performed

	;*** rotate SSPSR left, because bit 7 clocked out ***
	rlf	SSPSR,1
	nop			; settle time for transmission

	;*** RECEPTION on rising edge of serial clock ***
	bsf	SCK		; clock high
	nop			; settle time for clock high
	bcf	SSPSR,0		; fetch data applied on DI
	btfsc	DI
	bsf	SSPSR,0
	
	;*** TRANSMISSION on falling edge of serial clock ***
	bcf	SCK		; clock low

	decfsz	AD_cnt,1
	goto	ad_loop
	RETURN

;*** Ring Buffer ***
RB_main	movfw	RB_pnt		; get saved pointer
	movwf	FSR		; put to FSR
	movfw	sum_LO		; get low nibble of sample
	movwf	INDF		; put to register pointing at
	incf	FSR,f		; increment pointer
	movfw	sum_HI		; get high nibble of sample
	movwf	INDF		; put to register pointing at
	incf	FSR,f		; increment pointer
	movfw	FSR		; load pointer to w
	movwf	RB_pnt		; save pointer
	sublw	(BufferBASE + BufferLENGTH)
	skpz			; skip on zero
	RETURN			; normal exit
RB_init	movlw	BufferBASE	; else buffer overrun, init pointer
	movwf	RB_pnt		; set pointer to start location of ringbuffer
	RETURN


;***** INTERRUPT SERVICE ROUTINE *****

; only for test use, no functional purpose (yet...)

ISR	;************************
	;*** ISR CONTEXT SAVE ***
	;************************

	bcf	INTCON,GIE	; disable all interrupts
	btfsc	INTCON,GIE	; assure interrupts are disabled
	goto	ISR
	movwf	W_TEMP		; context save: W
	swapf	STATUS,W	; context save: STATUS
	movwf	STATUS_TEMP	; context save
	clrf	STATUS		; bank 0, regardless of current bank
	movfw	PCLATH		; context save: PCLATH
	movwf	PCLATH_TEMP	; context save
	clrf	PCLATH		; page zero, regardless of current page
	bcf	STATUS,IRP	; return to bank 0
	movfw	FSR		; context save: FSR
	movwf	FSR_TEMP	; context save
	;*** context save done ***

	;**************************
	;*** ISR MAIN EXECUTION ***
	;**************************

	; RECEIVE		(disabled, send only status data back)
	SEND	CR
	SEND	LF
	SEND	LF
	SEND	'P'
	SEND	'R'
	SEND	'E'
	SEND	'C'
	SEND	'I'
	SEND	'S'
	SEND	'I'
	SEND	'O'
	SEND	'N'
	SEND	' '
	SEND	'D'
	SEND	'I'
	SEND	'G'
	SEND	'I'
	SEND	'T'
	SEND	'A'
	SEND	'L'
	SEND	' '
	SEND	'A'
	SEND	'L'
	SEND	'T'
	SEND	'I'
	SEND	'M'
	SEND	'E'
	SEND	'T'
	SEND	'E'
	SEND	'R'
	SEND	CR
	SEND	LF
	SEND	'1'
	SEND	'9'
	SEND	'9'
	SEND	'9'
	SEND	'-'
	SEND	'2'
	SEND	'0'
	SEND	'0'
	SEND	'1'
	SEND	' '
	SEND	'b'
	SEND	'y'
	SEND	' '
	SEND	'P'
	SEND	'e'
	SEND	't'
	SEND	'e'
	SEND	'r'
	SEND	' '
	SEND	'L'
	SEND	'u'
	SEND	'e'
	SEND	't'
	SEND	'h'
	SEND	'i'
	SEND	CR
	SEND	LF
	SEND	LF

	;*****************************************
	;*** ISR TERMINATION (CONTEXT RESTORE) ***
	;*****************************************

ISRend	movfw	FSR_TEMP	; context restore
	movwf	FSR		; context restore
	movfw	PCLATH_TEMP	; context restore
	movwf	PCLATH		; context restore
	swapf	STATUS_TEMP,W	; context restore
	movwf	STATUS		; context restore
	swapf	W_TEMP,F	; context restore
	swapf	W_TEMP,W	; context restore
	RETFIE			; enable global interrupt (INTCON,GIE)

;***** END OF INTERRUPT SERVICE ROUTINE *****


;************** MAIN **************


MAIN	;**************************************************************
	;***                   INITIALIZATION                       ***
	;**************************************************************

	RS232init
	ADinit
	RBinit

	;*** Set A/D output format to unsigned ***
	ADenable
	ADcmd	Unsigned
	ADdisable
	WAIT	0x01

	;*** Set Aquisition Time to 34 CCLK Cycles ***
	ADenable
	ADcmd	AT34		; wait at least 34 tck
	ADdisable		; = 0.0085 ms  @  4 MHz
	WAIT	0x01

	;*** Auto-Calibration of A/D ***
	ADenable
	ADcmd	AutoCal		; wait at least 4944 tck
	ADdisable		; = 1.232 ms  @  4 MHz
	WAIT	0x03

	;*** Auto-Zero of A/D ***
	ADenable
	ADcmd	AutoZero	; wait at least 76 tck
	ADdisable		; = 0.019 ms  @  4 MHz
	WAIT	0x01


	;**************************************************************
	;***                       MAIN LOOP                        ***
	;**************************************************************
LOOP

	;**************************************************************
	;***    A/D CHANNEL 0 READOUT  (get the samples)            ***
	;***    12 bit data unsigned, MSB first: <0000 MSB - LSB>   ***
	;**************************************************************

	ADenable		; send new configuration to A/D
	ADcmd	CH0se16MSB	; CH0, single-ended, 16 bit, MSB first
	bsf	CONV		; disable conversion (for safety reasons)
	ADcmd	DummyCmd
	ADdisable

	movlw	d'16'		; initialize average building routine
	movwf	sum_cnt
	clrf	sum_LO
	clrf	sum_HI

AV_CH0	;*** START OF THE AVERAGE BUILDING ROUTINE ***
	; wait at least Tacq + Tconv = (34 + 44) Tclk
	;                            = 78 Tclk = 20 us @ 4 MHz
	WAIT	0x01		; wait 1 ms

	ADenable
	ADcmd	CH0se16MSB	; CH0, single-ended, 16 bit, MSB first
	movfw	SSPSR	
	movwf	HI		; store high nibble in HI
	bsf	CONV		; disable conversion (for safety reasons)
	ADcmd	DummyCmd
	movfw	SSPSR	
	movwf	LO		; store low nibble in LO
	ADdisable
	; result in HI, LO
	;*** END OF READOUT ***

	;*** 16 TIMES ADDITION ***
	movfw	LO		; load low value
	addwf	sum_LO,1	; add value to sum_LO register
	addcf	sum_HI,1	; add carry
	movfw	HI		; load high value
	addwf	sum_HI,1	; add value to sum_HI register
	decfsz	sum_cnt,1	; decrement loop counter
	goto	AV_CH0
	;------------------------------------------------

	;*** DIVIDE BY 16 EFFICIENTLY ***
	movlw	b'11110000'	; set mask
	andwf	sum_LO,1	; clear low nibble of sum_LO
	swapf	sum_LO,1	; swap halves of sum_LO
	swapf	sum_HI,1
	andwf	sum_HI,0	; get high nibble of sum_HI, store in w
	iorwf	sum_LO,1	; put w to high nibble of sum_LO
	movlw	b'00001111'	; set mask
	andwf	sum_HI,1	; clear high nibble of sum_HI
	; result in sum_HI, sum_LO
	;*** END OF AVERAGE, FILL RING BUFFER ***



	;**************************************************************
	;***       FILL IN RING BUFFER & CALCULATE AVERAGE          ***
	;**************************************************************

	call	RB_main		; fill the ring buffer with new values
				; stored in sum_LO, sum_HI

	;*** BUILD AVERAGE OF RING BUFFER ***
	clrf	sum_LO
	clrf	sum_HI
	movlw	BufferBASE
	movwf	FSR		; set pointer to the beginning of the ring buffer
F_LOOP	movfw	INDF		; load (low) value from pointed register
	addwf	sum_LO,1	; add value to sum_LO register
	addcf	sum_HI,1	; add carry
	incf	FSR,f		; increment pointer
	movfw	INDF		; load (high) value from pointed register
	addwf	sum_HI,1	; add value to sum_HI register
	incf	FSR,f		; increment pointer
	movfw	FSR		; get current pointer
	sublw	(BufferBASE + BufferLENGTH)
	skpz			; skip on zero: buffer overrun, exit
	GOTO	F_LOOP		; not yet finished; resume

	;*****************************************************************
	;*** You have to adapt the following division routine by hand, ***
	;*** if you alter the amount of buffer registers.              ***
	;*** Default: divide by 4 (BufferLENGTH = 8 bytes)             ***
	;*****************************************************************
	rrf	sum_LO,1	; divide by 2
	rrf	sum_LO,1	; divide by 2

	bcf	sum_LO,6	; clear 2. MSB of sum_LO
	btfsc	sum_HI,0	; check LSB of sum_HI
	bsf	sum_LO,6	; set 2. MSB of sum_LO if condition is true
	bcf	sum_LO,7	; clear MSB of sum_LO
	btfsc	sum_HI,1	; check 2. LSB of sum_HI
	bsf	sum_LO,7	; set MSB of sum_LO if condition is true

	rrf	sum_HI,1	; divide by 2
	rrf	sum_HI,1	; divide by 2
	bcf	sum_HI,6	; clear carry in
	bcf	sum_HI,7	; clear carry in
	; result in sum_HI, sum_LO
	;*** END OF RING BUFFER AVERAGE ***

	movfw	sum_LO
	movwf	CH0_LO
	movfw	sum_HI
	movwf	CH0_HI



	;**************************************************************
	;***    A/D CHANNEL 1 READOUT                               ***
	;***    12 bit data unsigned, MSB first: <0000 MSB - LSB>   ***
	;**************************************************************

	ADenable		; send new configuration to A/D
	ADcmd	CH1se16MSB	; CH1, single-ended, 16 bit, MSB first
	bsf	CONV		; disable conversion (for safety reasons)
	ADcmd	DummyCmd
	ADdisable

	movlw	d'16'		; initialize average building routine
	movwf	sum_cnt
	clrf	sum_LO
	clrf	sum_HI

AV_CH1	; wait at least Tacq + Tconv = (34 + 44) Tclk
	;                            = 78 Tclk = 20 us @ 4 MHz
	WAIT	0x01		; wait 1 ms

	ADenable
	ADcmd	CH1se16MSB	; CH1, single-ended, 16 bit, MSB first
	movfw	SSPSR	
	movwf	HI		; store high nibble in HI
	bsf	CONV		; disable conversion (for safety reasons)
	ADcmd	DummyCmd
	movfw	SSPSR	
	movwf	LO		; store low nibble in LO
	ADdisable
	; result in HI, LO
	;*** END OF READOUT ***

	;*** 16 TIMES ADDITION ***
	movfw	LO		; load low value
	addwf	sum_LO,1	; add value to sum_LO register
	addcf	sum_HI,1	; add carry
	movfw	HI		; load high value
	addwf	sum_HI,1	; add value to sum_HI register
	decfsz	sum_cnt,1	; decrement loop counter
	goto	AV_CH1
	;------------------------------------------------

	;*** DIVIDE BY 16 EFFICIENTLY ***
	movlw	b'11110000'	; set mask
	andwf	sum_LO,1	; clear low nibble of sum_LO
	swapf	sum_LO,1	; swap halves of sum_LO
	swapf	sum_HI,1
	andwf	sum_HI,0	; get high nibble of sum_HI, store in w
	iorwf	sum_LO,1	; put w to high nibble of sum_LO
	movlw	b'00001111'	; set mask
	andwf	sum_HI,1	; clear high nibble of sum_HI
	; result in sum_HI, sum_LO
	;*** END OF AVERAGE ***

	movfw	sum_LO
	movwf	CH1_LO
	movfw	sum_HI
	movwf	CH1_HI



	;**************************************************************
	;***               SEND DATA TO PC, MSB FIRST               ***
	;**************************************************************

	SEND	'T'		;*** FRAMING: start sequence ***
	SEND	'U'		; U = b'01010101'
	movfw	CH0_HI		; send channel 0 data
	SENDw
	movfw	CH0_LO
	SENDw
	movfw	CH1_HI		; send channel 1 data
	SENDw
	movfw	CH1_LO
	SENDw
	SEND	'/'		;*** FRAMING: stop sequence ***
	SEND	'?'		; ? = b'00011111'


	goto	LOOP		; re-entry
	END