;*************************************************************************** ; ; 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