; Digitize LM35's output and send to PC using ; bit banging serial I/O, no MAX232 ; RB2 is serial Tx ; XT oscillator at 4MHz ; RA3 is Vin+ and is connected to RC filtered ; PWM output (PWM output is RB3). Output of LM35 ; is connected to RA0(Vin-). An LED is connected to RB5. list p=16f628 include "p16f628.inc" include "xt.inc" delay_n_msec_tmp equ 0x20 inputs_crossed_over_n equ 0x21 inputs_crossed_over_i equ 0x22 change_period_tmp equ 0x23 change_period_i equ 0x24 do_adc_tmp equ 0x25 rs232_xmit_port equ PORTB rs232_xmit_pin equ 0x2 rs232_delay_counter equ 0x26 data_putchar equ 0x27 counter_putchar equ 0x28 main_tmp equ 0x29 goto main ;---------------------------------- ; A 1milli second delay routine using ; TMR0. We set prescaler to 1:4, so that ; each increment corresponds to 4 instruction ; cycles, ie, 4microseconds. So, 250 such ; increments should give us 1ms. The inital ; value of TMR0 is set to 5, so that it ; overflows in 250 increments. We write a loop ; which will keep on waiting for this overflow ; to happen. We take the precautions specified ; in the manual when changing prescaler assignment ; from WDT to TMR0 (the manual says we should do ; this even when the WDT is disabled) delay_1ms: movlw 0x8 ; Taking into consideration the delay ; caused by the instructions in this ; routine, about 12micro seconds. movwf TMR0 bcf INTCON, T0IF clrwdt bsf STATUS, RP0 movlw b'11010001' movwf OPTION_REG bcf STATUS, RP0 delay_1ms_L1: btfss INTCON, T0IF goto delay_1ms_L1 return ;------------------------------------- ;Entry: W contains `n' delay_n_msec: movwf delay_n_msec_tmp delay_n_msec_L1: call delay_1ms decfsz delay_n_msec_tmp, F goto delay_n_msec_L1 return ;----------------------------------- ;Enable Timer2, which is needed for PWM configure_timer2: bsf T2CON, TMR2ON return ;------------------------------------ ; We configure the comparators in mode ; b'100' - ie, two independent comparators. ; We will use only one of these comparators (COMP1). ; The Vin+ of COMP1 (pin RA3) is connected to ; the RC filtered PWM output, which is the ; reference signal. The signal which we wish ; to compare is fed to RA0. configure_comparators: movlw b'00000100' ; mode 4, two independent comparators movwf CMCON return ;------------------------------------ ; Loop till Vin- goes above Vin+. At this ; point, C1OUT will become 0. comparator_loop: btfsc CMCON, C1OUT goto comparator_loop return ;------------------------------------- configure_ports: bsf STATUS, RP0 ; Note that for PWM operation, ; RB3 MUST be output. RA0 and ; RA3 are comparator inputs. movlw 0x0 movwf TRISB movlw 0x9 movwf TRISA bcf STATUS, RP0 clrf PORTA return ;------------------------------------- configure_pwm: bsf STATUS, RP0 ; set period register of timer2 (ie, set pwm period) ; Tpwm = (PR2+1)*4*Tosc*(Timer2 prescale value) ; Timer 2 prescale is set to 1 ; (Note: Tosc is the period of the external oscillator. We ; are using a 4MHz oscillator here). movlw 0xff movwf PR2 ;Now set PWM duty cycle ;Duty cycle = CCPR1L:CCP1CON<5:4>*Tosc*(Timer2 prescale) ;Note that LSB 2 bits are taken from CCP1CON<5:4> ;Each increment of CCPR1L:CCP1CON<5:4> represents ;0.25 microseconds. 16 microseconds on and 256 microseconds ;off should give us a DC Low pass filtered voltage of around ;5*(16/256.0) = 0.3125 volts. bcf STATUS, RP0 movlw 0x10 movwf CCPR1L movlw b'00001100' movwf CCP1CON return ;------------------------------------------------------ ; Check whether Vin+ is below Vin- 100 times. If 95 ; times out of 100, we see that Vin+ is below, function ; returns 1 in W, otherwise returns 0 inputs_crossed_over: clrf inputs_crossed_over_n movlw D'100' movwf inputs_crossed_over_i inputs_crossed_over_L1: btfss CMCON, C1OUT incf inputs_crossed_over_n, F decfsz inputs_crossed_over_i, F goto inputs_crossed_over_L1 movf inputs_crossed_over_n, W sublw D'84' btfss STATUS, C ; If C = 0, result is negative ; which means W > 94. So return 1 (in W itself) retlw 0x1 retlw 0x0 ; W <= 94, so return 0 (in W itself) ;---------------------------------------------------------------------- ; Change PWM period. We use only eight bits here ; W contain new period. Split it up and store it in ; 2 bits of CCP1CON<5:4> and 6 bits of CCPR1L change_period: movwf change_period_tmp andlw b'00000011' ; Mask off MSB 6 bits movwf change_period_i bcf STATUS, C ; Dont forget this! rlf change_period_i, F rlf change_period_i, F rlf change_period_i, F rlf change_period_i, F ; Now, D0 and D1 are in D4'th and D5'th places movlw b'00001100' iorwf change_period_i, W movwf CCP1CON ; CCP1CON properly configured ; Now put the other 6 bits of change_period_tmp in CCPR1L bcf STATUS, C rrf change_period_tmp, F rrf change_period_tmp, F movf change_period_tmp, W movwf CCPR1L return ;------------------------------------------------------ ; Total PWM duration is 256 micro seconds. If we have ; 23micro on time, we get 5*(23.0/256) = 0.449V. The LM35 ; sensor outputs this voltage if the current temperature ; is about 45 degree celsius, which is above the maximum we ; are going to get, here in Trichur. If we have 10micro ; on time, we get 5*(10.0/256) = 0.195V, which means about ; 20 degree celsius, the least we are going to have! ; We start outputting PWM pulses with on time equal to 23micro ; second - we keep coming down stopping at the point ; were we discover that the LM35's output has gone above ; our reference PWM RC filtered voltage. We stop at 10 micro ; second. ; For getting 23micro, we have to write a CCPR1L:CCP1CON value ; of 23*4 = 92 (decimal). A 0.5 micro difference in duty ; cycle generates about 10mv change in output. For getting ; this 0.5 micro change, we have to change the count value ; by 2. So we start with a count value of 92, keep on decrementing ; by 2 until we have reached 40 (10*4). do_adc: movlw D'92' movwf do_adc_tmp do_adc_L1: movf do_adc_tmp, W call change_period movlw D'240' call delay_n_msec ; Wait till filtered value settles call inputs_crossed_over addlw 0x0 ; Check whether W is 0. If W is 0, it means inputs ; have not crossed over btfss STATUS, Z ; Skip if W is Zero goto do_adc_L2 decf do_adc_tmp, F decf do_adc_tmp, F movf do_adc_tmp, W sublw D'38' btfsc STATUS, C ; C is 0 if negative, ie W > 38 retlw 0xff ; We return 0xff when we are unable to determine ; the proper digital value of the signal goto do_adc_L1 do_adc_L2: movf do_adc_tmp, W ; Put duty cycle value in W return ; And go back! ;-------------------------------------- ; A delay routine. We are transmitting ; at 9600bps. So we should get about ; 1/9600.0 seconds here. Our clock is ; 4MHz. rs232_delay movlw D'30' movwf rs232_delay_counter doit30 decfsz rs232_delay_counter, F goto doit30 return ;-------------------------------------- ; Implements a bit-banging `putchar'. ; Expects `W' to contain byte to be ; transmitted at entry. putchar movwf data_putchar movlw D'9' movwf counter_putchar bsf rs232_xmit_port, rs232_xmit_pin ; start bit nop nop terminate call rs232_delay decfsz counter_putchar, F goto check_bit ; Not yet ready to go back :-) bcf rs232_xmit_port, rs232_xmit_pin ; Stop bit call rs232_delay return ; GO BACK! check_bit rrf data_putchar, F btfss STATUS, C ; Is it a `ONE' ? goto its_a_zero ; No, It is a `ZERO'. bcf rs232_xmit_port, rs232_xmit_pin ; Send a `ONE'. goto terminate ; Shall We Stop? its_a_zero bsf rs232_xmit_port, rs232_xmit_pin ; Send a `ZERO'. goto terminate ; Shall We Stop? ;------------------------------------------------------------- ; The main routine main: call configure_ports call configure_timer2 call configure_comparators call configure_pwm bcf PORTB, 2 ; Line idle movlw D'20' movwf main_tmp main_L1: call rs232_delay decfsz main_tmp, F goto main_L1 main_L2: call do_adc call putchar goto main_L2 end