REM ********************************************** REM * Bat detector: * REM * PWM Frequency generator (20kHz - 120kHz) & * REM * controller for a Bat Detector * REM * Jurjen Kranenborg, 02-03-2022 * REM ********************************************** REM REM Bat Detector discussiom om Picaxe Forum (including circuit): REM https://picaxeforum.co.uk/threads/philips-ee-picaxe-a-happy-marriage-between-old-new-sustainable-for-the-future.32590/ REM IMPORTANT!: The display communication uses my take on the AXE133/133Y serial Display, see: REM https://picaxeforum.co.uk/threads/improving-the-axe133-oled-firmware.29710/post-339628 REM For standard AXE133/133Y implementations, please remove the NULL character at the end of the messages in this code REM Runs on a Picaxe-08M2 at 16MHz (SETFREQ m16) to generate frequencies in proper range 20kHz - 120kHz, REM the PWM generator is based on the 10 kHz - 60 kHz generator (@ 8MHz) described here: REM https://picaxeforum.co.uk/threads/simple-generator-for-a-range-of-predefined-frequencies-using-the-m2s-pwm-module.31526/ REM REM This program can generate a large range (ratio 6 of max/min) of frequencies REM (at fixed 50% duty cycle) through the Picaxe's PWM module, as controlled by the PWMOUT command. REM An ADC-reading provides the input to select a proper value REM REM For this implemention, the following aspects have been taken into account: REM 1. 16-bit Picaxe arithmetic limitation and lack of parentheses REM 2. 16-bit integer representation limit REM 3. The byte representation limit of the "period"-parameter in the PWMOUT command REM REM ---------------------------------------------- REM System paraneters: #picaxe 08M2 #no_data REM Define clock frequency to set min. amd max. PWM frequency: SETFREQ m16 ' shift from 10kHz - 60 kHz to 20kHz - 120kHz SYMBOL ClockFreqKHz_x1.25 = 10000 ; For m8(=8MHz=8000kHz): 1.25 x 8000 kHz = 10000 REM Register defs SYMBOL PotADCvalue = b0 SYMBOL OldPotADCvalue = b1 SYMBOL PotADC_LOW = b2 SYMBOL PotADC_HIGH = b3 SYMBOL FreqKHz_x5 = w2 SYMBOL dutyCycle = w3 SYMBOL period8 = b8 SYMBOL State = b9 SYMBOL Frequency = w5 SYMBOL IntervalLength = w6 SYMBOL IntegerQuotient = w7 SYMBOL NULL = $00 REM Pin & Pullup & Interrupt defs SYMBOL OutputFreq = C.2 ; PWM output to Mixer SYMBOL ADCin = C.4 ; Potentiometer input for setting PWM frequency SYMBOL SerialDisplay = C.1 ; Serial output towards AXE131/131Y Serial Display /OLED SYMBOL StateSwitch = C.3 ; Switch, used to define and enter interval mode (switching between two frequencies SYMBOL ToneOut = C.0 ; Output towards end amplifier, to signal start of new listening interval SYMBOL IntMask = %00001000 ; State Switch is at C.3 input ... PULLUP IntMask ; ... and uses an active pullup ... SYMBOL IntInputs = %00000000 ; ... because it is active low for generating an interrupt REM Define Macros for easily displaying various text nessages on first or second line of LCD/OLED REM with proper timings for display #MACRO WriteTextToFirstLine(Text16) SEROUT SerialDisplay, N4800_16,(254,128,NULL) PAUSE 15 SEROUT SerialDisplay, N4800_16,(Text16, NULL) PAUSE 85 #ENDMACRO #MACRO WriteTextToSecondLine(Text16) SEROUT SerialDisplay, N4800_16,(254,192,NULL) PAUSE 15 SEROUT SerialDisplay, N4800_16,(Text16, NULL) PAUSE 85 #ENDMACRO REM User-selectable frequency range definition REM ------------------------------------------ REM Note1: Required: (upperDCfreq_x5 - lowerDCfreq_x5) <= 256 (see below for details), REM for the accuracy of calculation these values should optimally be close to it, REM therefore the frequency definition x 5 has been selected REM Note2: The value of period should fit within a byte --> period8 <= 255 REM Note3: The definitions are made here for 10 - 60 kHz @ 8MHz, by running at 16MHz REM the proper listening frequency range 20kHz - 120kHz for bats is obtained! REM Note4: PotADCvalue * (upperDCfreq_x5 - lowerDCfreq_x5) should not exceed 65535 REM due to 16-bit integer arithmetic limitation of the PICAXE. REM This implies (upperDCfreq_x5 - lowerDCfreq_x5) <= 256 (PotADCvalue never exceeds 255) SYMBOL upperDCfreq_x5 = 300 ; 60kHz (x 5 = 300) @ 8MHz, so 120kHz @ 16MHz SYMBOL lowerDCfreq_x5 = 50 ; 10kHz (x 5 = 50) @ 8MHz, so 20kHz @ 16MHz REM State definitions, to support switching between normal and frequency alternating modes REM -------------------------------------------------------------------------------------- REM State switching is performed by interrupt (for State 0 - 3) or by main program loop (alternating between States 3 - 4) REM REM State = 0: Default mode,dial is used to read listening frequency, for this the ADC is sampled REM State = 1: Message shown that higher freq can be chosen, furthermore same as State 0 REM State = 2: Store ADC dial setting for higher frequency, show message that lower freq can be chosen, furthermore same as State 1 REM State = 3: Store ADC dial setting for lower frequency, show message for 3 secs that alternating frequency will be applied, then start alternating REM State = 4: show message that default operation will occur, then switch back to state 0 by executing a RESET command REM Ready for operation: REM First show intro information and make display ready for use CALL DisplayIntroInfo REM Ready to start at default State 0 CALL IntroSound SETINT IntInputs, IntMask LET OldPotADCvalue = 255 LET State = 0 DO SELECT CASE State CASE 0 to 2 ; Update the frequency (and display info on it) only if the frequency knob has been changed in position ; Read the frequency knob setting anew, has it changed? READADC ADCin, PotADCvalue ; Make the range a bit course (number exactly divisible by 4), ; to avoid continuous jumping between neighboring frequencies LET PotADCvalue = PotADCvalue AND %11111100 ; Only have to refresh the PWM signal and display in case ; something actually has changed ... IF PotADCvalue <> OldPotADCvalue THEN ; Derive frequency and corresponding PWM signal from PotADCvalue CALL GenerateFreqAndPWMfromADC CALL WriteFreqToLCD_Line2 ; store the updated setting OldPotADCvalue = PotADCvalue ENDIF PAUSE 250 CASE 3 ; Alternating mode, LOW frequency ; use TIME variable to switch between low freq (state = 3) and high freq (state = 4) LET PotADCvalue = PotADC_LOW AND %11111100 CALL GenerateFreqAndPWMfromADC CALL WriteFreqToLCD_Line2 ; Check comtinuously for end of interval (including effect of turning the knob ; which sets the interval length DO READADC ADCin, PotADCvalue CALL DeriveAlternatingInterval LET IntegerQuotient = TIME / IntervalLength IF IntegerQuotient > 0 THEN State = 4 DISABLETIME : LET TIME = 0 : ENABLETIME Call BeepHIGH EXIT END IF PAUSE 250 LOOP CASE 4 ; Alternating mode, HIGH frequency ; use TIME variable to switch between low freq (state = 3) and high freq (state = 4) LET PotADCvalue = PotADC_HIGH AND %11111100 CALL GenerateFreqAndPWMfromADC CALL WriteFreqToLCD_Line2 ; Check comtinuously for end of interval (including effect of turning the knob ; which sets the interval length DO READADC ADCin, PotADCvalue CALL DeriveAlternatingInterval LET IntegerQuotient = TIME / IntervalLength IF IntegerQuotient > 0 THEN State = 3 DISABLETIME : LET TIME = 0 : ENABLETIME Call BeepLOW EXIT END IF PAUSE 250 LOOP END SELECT LOOP Interrupt: REM Actions at interrupt are defined depending on state before interrupt REM The state changes immediately after the interrupt SELECT CASE State CASE 0 LET State = 1 ; Determine the HIGHER freq to listen to, nothing changes yet WriteTextToFirstLine("Set HIGHER Freq:") CALL BeepHIGHlong CASE 1 LET State = 2 ; Store the last read ADC value as the HIGH frequency, ; determine the LOWER freq to listen to. Nothing changes yet READADC ADCin, PotADC_HIGH WriteTextToFirstLine("Set LOWER Freq: ") CALL BeepLOWlong CASE 2 LET State = 3 ; Store the last read ADC value as the LOW frequency READADC ADCin, PotADC_LOW WriteTextToFirstLine("Alternating mode") ; Now start alternating in the main loop, switching between low freq (State = 3) ; and high freq (State = 4). This state switching happens in the main loop, not in ; this interrupt routine. Alternating starts wth the lower frequency in State 3. ; Start the Timer to allow alternation: Call BeepLOW ENABLETIME CASE 3 to 4 ; Leave alternating mode with three beeps and switch back to default State 0 by a software reset CALL BeepLOW : PAUSE 250 : CALL BeepLOW : PAUSE 250 : CALL BeepLOW ; Now perform RESET to start all over again at State = 0 RESET END SELECT PAUSE 2000 ; 1s debounce time SETINT IntInputs, IntMask ; Enable interrupts again RETURN DisplayIntroInfo: REM inform on the function and capabilities of this device PAUSE 6000 ; Clear display first serout SerialDisplay, N4800_16,(254,1,NULL) PAUSE 15 WriteTextToFirstLine("EE Bat Detector ") PAUSE 3000 WriteTextToSecondLine("Range: 20-120kHz") PAUSE 8000 ; Now make ready for operation by showing the actual frequency WriteTextToFirstLine("Bat Frequency: ") RETURN DeriveAlternatingInterval: REM Input: potADCvalue REM Output: InrervalLength (to compare with TIME value) SELECT CASE potADCvalue CASE < 50 IntervalLength = 2 ; 2 secs @ 16MHz CASE < 100 IntervalLength = 4 ; 4 secs CASE < 150 IntervalLength = 8 ; 8 secs CASE < 200 IntervalLength = 12 ; 12 secs ELSE IntervalLength = 65535 ; effectively endless ... END SELECT RETURN GenerateFreqAndPWMfromADC: REM Input: PotADCvalue REM Output: Frequency corresponding to ADC value REM Output: corresponding PWM signal according to frequency ; First derive PWM parameters from ADC value LET FreqKhz_x5 = upperDCfreq_x5 - lowerDCfreq_x5 LET FreqKhz_x5 = FreqKhz_x5 * PotADCvalue / 255 + lowerDCfreq_x5 LET period8 = ClockFreqKHz_x1.25 / FreqKhz_x5 - 1 LET dutyCycle = 2*period8 + 1 ; Provide corresponding frequency: LET Frequency = FreqKhz_x5 * 2 / 5 ; and update PWM signal according to this frequency setting PWMOUT OutputFreq, period8, dutycycle RETURN WriteFreqToLCD_line2: ; Update secomd line of the display ; The diplayed number (frequency in kHz) requires either 2 or three characters ; First, set cursor at start of second line SEROUT SerialDisplay, N4800_16,(254, 192, NULL) PAUSE 15 ; Then write the frequency value (in kHz), either two or three characters: SEROUT SerialDisplay, N4800_16,(#Frequency, NULL) ; 2-3 chars total PAUSE 15 IF Frequency < 100 THEN SEROUT SerialDisplay, N4800_16,(" ", NULL) ; 3 chars total PAUSE 5 END IF SEROUT SerialDisplay, N4800_16,(" kHz", NULL) ; 7 chars total PAUSE 20 ; Now, depending on the state we are in, provide additional useful info SELECT CASE State CASE 0 SEROUT SerialDisplay, N4800_16,(" ", NULL) ; 16 chars total CASE 1 SEROUT SerialDisplay, N4800_16,(" (HIGH)", NULL) CASE 2 SEROUT SerialDisplay, N4800_16,(" (LOW)", NULL) CASE 3 SEROUT SerialDisplay, N4800_16,(" [LOW]", NULL) CASE 4 SEROUT SerialDisplay, N4800_16,(" [HIGH]", NULL) END SELECT PAUSE 45 RETURN BeepLOW: SOUND ToneOut, (10, 100) RETURN BeepLOWlong: SOUND ToneOut, (10, 500) RETURN BeepHIGH: SOUND ToneOut, (40, 100) RETURN BeepHIGHlong: SOUND ToneOut, (40, 500) RETURN IntroSound: SOUND ToneOut, (10, 200) SOUND ToneOut, (40, 1000) RETURN