Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 1524 lines (1268 sloc) 43.5 KB
; --WunderNoise--
; TMB
; Original: 10/31/05
; Rebuilt to be more better for Spring 2006 Tour-
; 4/2/06
; Still EEpromless but tour ready with five programs that drop bombs.
4/13/06 -TB
; The following is the code for the 12f683 which drives the "Who Got A Big
Ol' Butt" kit made for the Voltage
;tour, fall 2005. Fundamentally it is a ghetto dsp device which makes a lot
of "guesses" about an incoming
;analog signal using the a/d and comparator and outputs different gibberish,
waveforms and whatnot.
;It reminds me of a little accountant, hiring and firing. Cutting checks on
the spot.
;The idea is that with the one button you can engage / disengage the circuit
from the world (turn the output into an input)
;But if you hold said magic button the circuit will change to a different
program. This program gets stored in EEPROM so that
;each particular circuit remembers what it's supposed to be after power off.
Maybe the PWM to the LED is different for each one
;in some artsy way.
;The concepts for some of the routines:
;-------See notes in MoreWunderNoise.txt
;-------------------------------------------------------------------------------------------------------
;-------------------------------------------------------------------------------------------------------
;Compiler Setup
;-------------------------------------------------------------------------------------------------------
;-------------------------------------------------------------------------------------------------------
WunderNoise_setup
processor 12f683
include <p12f683.inc>
__config _CP_OFF & _WDT_OFF & _PWRTE_ON & _BOD_OFF & _MCLRE_OFF &
_INTRC_OSC_NOCLKOUT
org 0
cblock h'20'
randomval4
randomval3
randomval2
randomval1
debounce_counter
switch_held_counter
switch_register
program_selector
output_state
ADresult
LEDchase
currentDelay
delayCounter
loopCounter
comparatorReg
IIRL
IIRH
timer_start_valueL
timer_start_valueH
times_through_loop
current_timer_readingL
current_timer_readingH
tempL
tempH
temp1L
temp1H
step_timer_counter
debugCounter
endc
;-------------------------------------------------------------------------------------------------------
;-------------------------------------------------------------------------------------------------------
;Device Pinout:
;-------------------------------------------------------------------------------------------------------
;-------------------------------------------------------------------------------------------------------
;Device =
;12F683
; -----------------
; | * |
; VDD VSS
; | |
; Output Comparator In
; | |
; Analog In Comparator Reference
; | |
; Switch LED Drive
; | |
; -----------------
;-------------------------------------------------------------------------------------------------------
;-------------------------------------------------------------------------------------------------------
;Defines:
;-------------------------------------------------------------------------------------------------------
;-------------------------------------------------------------------------------------------------------
#define SWITCH_INPUT GPIO,3
#define OUTPUT_PIN GPIO,5
#define DEBOUNCE_TIME d'255'
#define HOLD_TIME d'255'
#define ALREADY_PRESSED switch_register,0
#define VALID_SWITCH switch_register,1
#define TIMER_FLIPPED_FLAG switch_register,2
#define RESET_PROGRAM_FLAG switch_register,3
#define CHANGE_PROGRAM_FLAG switch_register,4
#define OUTPUT_STATE_FLAG output_state,0
#define NOISE_BIT randomval1,0
#define NOISE_THRESHOLD d'175'
#define DELAY_CONSTANT d'8' ; This is the loop multiplier to
determine noise freq. Greater = lower f.
#define STEP_TIME_CONSTANT d'30' ; This value determines the speed of
the stepped tone generator. Lower = faster.
;-------------------------------------------------------------------------------------------------------
;-------------------------------------------------------------------------------------------------------
;Initialization on Powerup.
;-------------------------------------------------------------------------------------------------------
;-------------------------------------------------------------------------------------------------------
;Get your options, input interrupts, tris, A/D setup and the like going.
;Clear your I/Os and get your banks straight.
power_on_routine
bcf STATUS, RP0 ;bank 0
clrf INTCON ;Turns interrupts off.
movlw b'00000010'
movwf CMCON0 ;Comparator is on, taking input from the pins, and
non-inverted. No external O/P.
movlw b'0001101'
movwf ADCON0 ;A/D on, left justified, referenced to VDD, channel 3 (pin
3).
clrf CMCON1 ;Comparator runs asyncronously.
clrf GPIO ;clears outputs.
bsf STATUS, RP0 ;bank1
movlw b'00111011'
movwf TRISIO ;GP0,1,3,4,5 = inputs, GP2 = outputs.
;Remember to turn 5 back into an output when it's time to flip it!
movlw b'01110111' ;Sets the system clock to the internal oscillator and
;ratchets it up to 8MHz.
movwf OSCCON ;Osccon is in bank 1.
movlw b'01011000'
movwf ANSEL ;AN3 to analog input, conversion clock = Fosc / 16.
clrf VRCON ;Voltage reference powered down.
movlw b'10000000'
movwf OPTION_REG ;sets pullups off.
bcf STATUS, RP0 ;bank 0
clrf GPIO ;clear O/Ps again because I'm paranoid.
call init_PWM ;Get the LED poppin.
movf TMR0,W ;Seed the random number generator.
movwf randomval4
movwf randomval3
movwf randomval2
movwf randomval1
;Initialize your variables
movlw DEBOUNCE_TIME
movwf debounce_counter
clrf switch_held_counter
clrf switch_register
clrf program_selector
; movlw b'11111111' ;Why did I do this?
; movwf output_state
bcf ALREADY_PRESSED ;Switch isn't already pressed.
bcf VALID_SWITCH ;Switch isn't valid.
bcf RESET_PROGRAM_FLAG
bcf CHANGE_PROGRAM_FLAG
clrf output_state
movlw d'128'
movwf ADresult
movwf ADRESH
clrf LEDchase
movlw d'127' ;initialize to low frequency.
movwf currentDelay
movlw DELAY_CONSTANT
movwf delayCounter
clrf comparatorReg
; call readprom ;Get the hot shit from EEPROM.
goto program_selector_tree
;-------------------------------------------------------------------------------------------------------
;-------------------------------------------------------------------------------------------------------
;Program Selector Loop
;-------------------------------------------------------------------------------------------------------
;-------------------------------------------------------------------------------------------------------
next_program
incf program_selector
bcf CHANGE_PROGRAM_FLAG
bcf RESET_PROGRAM_FLAG
bsf OUTPUT_STATE_FLAG ;Always start with the shit killin'.
;This is also set in the "switch held" routine immediately
previously.
program_selector_tree
;The main loop just shuttles us off to whichever program we dug out of
eeprom in Setup,
;or puts us in a new program after the button is held.
movlw b'00000000' ;Was it program 0?
clrz
xorwf program_selector,W
skpnz
goto program_0_setup
movlw b'00000001' ;Was it program 1?
clrz
xorwf program_selector,W
skpnz
goto program_1_setup
movlw b'00000010' ;Was it program 2?
clrz
xorwf program_selector,W
skpnz
goto program_2_setup
movlw b'00000011' ;Was it program 3?
clrz
xorwf program_selector,W
skpnz
goto program_3_setup
movlw b'00000100' ;Was it stepped tone?
clrz
xorwf program_selector,W
skpnz
goto stepped_tone_program
;And so on and so forth....
clrf program_selector
goto program_0_setup ;In case some weird number gets in here, or you
increment over the number of programs,
;just go to program 0.
;-------------------------------------------------------------------------------------------------------
;-------------------------------------------------------------------------------------------------------
;Synth Programs:
;-------------------------------------------------------------------------------------------------------
;-------------------------------------------------------------------------------------------------------
program_0_setup
bsf STATUS,RP0 ;Bank 1
bsf TRISIO,5 ;Turn the OP into a high impedance input.
movlw b'01110111' ;Sets the system clock to the internal oscillator and
;ratchets it up to 8MHz.
movwf OSCCON ;Osccon is in bank 1.
movlw b'01011000'
movwf ANSEL ;AN3 to analog input, conversion clock = Fosc / 16.
bcf STATUS,RP0 ;Bank 0
movlw b'00001100' ;CCP set to PWM.
movwf CCP1CON
bcf RESET_PROGRAM_FLAG ;If we've comeback from an idle time this is the
flag that we reset.
;(45 commands long when the button is not pressed, so the loop time is based
on the A/D
;cycle, and therefore around 56 commands or so.)
;The first of the shit-kickers:
;This program reads the A/D and if the level in is outside of a certain
range (ie, the signal's amplitude is
;greater than a threshold) it turns on the output pin and sends it the PSRB.
It must read lower than the threshold for
;a millisecond (or so, about 36 complete A/D cycles) before it turns off the
PSRB -- since there's no averaging on the signal this
;is just a sort of hysteresis or attempt to make sure that a chance read
near the zero point doesn't turn off the signal.
program_0
btfss SWITCH_INPUT
goto un_pressed
call debounce
call on_off_switch_handler
goto switch_hooey_finished
un_pressed
movlw DEBOUNCE_TIME
movwf debounce_counter
bcf ALREADY_PRESSED
clrf switch_held_counter
switch_hooey_finished
call random32 ;Keep it random while you're waiting.
btfss OUTPUT_STATE_FLAG ;If the thing is supposed to be inactive, go to
another loop which just turns off the OP, diddles the LED and waits on
switches.
call bide_your_time
btfsc RESET_PROGRAM_FLAG ;If we return from a bide_your_time call to this
point, this flag tells us to re-init everything.
goto program_0_setup
btfsc CHANGE_PROGRAM_FLAG ;If the Change flag got tripped, break out of
this program to the next_program section.
goto next_program
;Wait for the conversion now, since eveything is done.
btfsc ADCON0,GO
goto $-1
;In 7 cycles, start the A/D again.
movf ADRESH,W
movwf ADresult
; If the AD reading is less than 128, complement it -- a software
absolute value above the reference voltage.
movlw d'128'
clrc
subwf ADresult,W
skpc
comf ADresult
bsf ADCON0,GO
;And they're off! The A/D should be done roughly 48 commands from now, and
ready to
;go again in 8 after that. So it'd be nice to catch the end of the
conversion so I know
;where to count 8 from -- it'll be fastest that way. (see above)
;(the switch checking and RNG at the top of the loop take 12-20 cycles.
Try and be done in another 28 from here)
;Next is the compare section. If the result is higher than a threshold,
bring tha noise.
movlw NOISE_THRESHOLD
bsf STATUS,C ; Set the C bit so that subtracting through zero causes it
to clear.
subwf ADresult,W ; ADresult minus W.
skpc ; If the ADresult is bigger, there's no borrow, nothing changes.
C remains set.
goto input_less_than_threshold ; Therefore, if the C bit is still set,
make some noise. If it is now clear, the reading was lower than the
threshold -- don't do shit.
input_greater_than_threshold
;Turn on the OP port and LED. If you have the cycles, debounce, but
whatever.
bsf STATUS,RP0 ;Bank 1
bcf TRISIO,5 ;Turn the OP into an OP.
bcf STATUS,RP0 ;Bank 0
btfss NOISE_BIT ;Check the bit on the RNG and put it on the output pin.
bcf OUTPUT_PIN
btfsc NOISE_BIT
bsf OUTPUT_PIN
movlw b'11111111' ;Turn up the LED all the way.
movwf CCPR1L
goto program_0 ;You're done, start the loop again.
input_less_than_threshold
bsf STATUS,RP0 ;Bank 1
bsf TRISIO,5 ;Turn the OP into a high impedance input.
bcf STATUS,RP0 ;Bank 0
rrf ADresult,W ;LED shit:
clrf CCPR1L ; A test to see if the input always stays less than the
threshold.
; movwf CCPR1L ; If the noise is off, make the led correspond to input
amplitude, but half max brightness.
goto program_0 ;You're done, start the loop again.
;------------------------------
program_1_setup
bsf STATUS,RP0 ;Bank 1
movlw b'01110111' ;Sets the system clock to the internal oscillator and
;ratchets it up to 8MHz.
movwf OSCCON ;Osccon is in bank 1.
movlw b'01011000'
movwf ANSEL ;AN3 to analog input, conversion clock = Fosc / 16.
bcf STATUS,RP0 ;Bank 0
movlw b'00001100' ;CCP set to PWM.
movwf CCP1CON
;2.) Amplitude-dependent filtered white noise
;Lil' Blood looks at the A/D value and adjusts the frequency of the PSRB,
which is always
;on. With small or no signal in, There are a lot of DC pops. As the
amplitude pins out,
;the shit gets hissy.
;Check the button, like before.
;When the program is engaged, keep the OP an OP.
;Take the AD reading, figure absolute value via complement-
;if A/D - 128 flips the Carry, complement.
;So then a reading of: 0 = 255 and 255 = 255 and 127=128 and 128=128.
;Complement this again.
;Now: 255 = 0 and 128=127.
;This means that we'll get a number back from 0-127 or so where 0 equals the
greatest input amplitude, and
;127 equals the smallest input amplitude.
;Now use this to delay moving the Noise bit to the O/P.
;Basically, this program shoots out random bits.
;It changes the time until the next random bit based on the input magnitude.
;The greater the input magnitude (amplitude), the faster the bits go out.
;So:
;Run the A/D full speed.
;Turn the a/d into a delay time. Something like ((0-127)*32) = cycles
through a 56 command program per noise bit check.
;Compare the Delay calculated from the A/D reading to the current delay
counter.
;If the calculated delay is shorter, reload the delay counter with the new
delay.
;If the calculated delay is longer, (ie, the amplitude is smaller than it
was) keep the old delay counter and
;re-calculate at the next A/D reading.
;First thing, turn on the OP so you don't waste these cycles every loop --
this stays on as long as the
;program is active.
bsf STATUS,RP0 ;Bank 1
bcf TRISIO,5 ;Turn the OP into an OP.
bcf STATUS,RP0 ;Bank 0
bcf RESET_PROGRAM_FLAG
program_1_loop
decf currentDelay ; A carryover from the OP state / freq evaluation at the
end of the loop.
btfss SWITCH_INPUT
goto un_pressed_a
call debounce
call on_off_switch_handler
goto switch_hooey_finished_a
un_pressed_a
movlw DEBOUNCE_TIME
movwf debounce_counter
bcf ALREADY_PRESSED
clrf switch_held_counter
switch_hooey_finished_a
btfss OUTPUT_STATE_FLAG ;If the thing is supposed to be inactive, go to
another loop which just turns off the OP, diddles the LED and waits on
switches.
call bide_your_time
btfsc RESET_PROGRAM_FLAG ;If we return from a bide_your_time call to this
point, this flag tells us to re-init everything.
goto program_1_setup
btfsc CHANGE_PROGRAM_FLAG ;If the Change flag got tripped, break out of
this program to the next_program section.
goto next_program
btfsc ADCON0,GO ;Are we done yet?
goto $-1
;Get the data. In 7 cycles, you're clear to start the A/D again.
; If the AD reading is less than 128, complement it -- a software absolute
value above the reference voltage.
movf ADRESH,W
movwf ADresult
movlw d'128'
bsf STATUS,C
subwf ADresult,W
skpc
comf ADresult
comf ADresult
; clrc
; subwf ADresult,W
; skpc
; comf ADresult
;ADresult should = 128-255 now.
; comf ADresult
;Adresult should = 0-127 now.
;ADresult will = 127 ADresultwith no signal in.
;ADresult is basically a seven bit number now, inversely proportional
to the signal amplitude.
bsf ADCON0,GO ;Restart that ish. Get a new reading.
;Now compare the AD result to the currentDelay. If the ADresult is smaller
(meaning that amplitude at the input is is
;greater than it was last read, or greater than the equivalent current
delay frequency) then load the ADresult into the
;current delay value. If the ADresult is greater, keep the currentDelay.
movf currentDelay,W
bsf STATUS,C
subwf ADresult,W ; This command is ADresult (f) minus currentDelay (W).
skpnc ;(NB:skpc Locks the OP high.) ; If the C bit is still set, W was
less than f, so the ADresult was greater. So if C is set don't change the
delay register.
currentDelayLessThanAD
; If we're here, the ADresult (ie, the inverse of the signal magnitude) is
greater than the value of the decay as it stands
; Don't update the delay, just keep it moving.
goto calculationDone
currentDelayGreaterThanAD
;This means the frequency of the output should increase, because the delay
period is greater (longer) than what the
;magnitude in would dictate. Move ADresult into currentDelay and reset the
delayCounter as well.
movf ADresult,W
movwf currentDelay
; movlw DELAY_CONSTANT ;Don't reset the delay counter here, because if the
input stays at the same value, (like 0)
; movwf delayCounter ;OR if the AD value steadily increases, this keeps
getting reloaded and never getting to the OP.
calculationDone
;Okay, now decrement the delay counter. When it gets to zero, decrement the
currentDelay. When that gets to zero,
;update the bitstream and output. Reset currentDelay to the default value
of 127 or whatever it was.
;Keep it random:
call random32
clrz
decf delayCounter
skpz ;When the delay counter hits zero, reset it and worry about
currentDelay. Otherwise, don't mess -- return to the beginning of the loop.
goto program_1_loop
movlw DELAY_CONSTANT
movwf delayCounter
clrw
clrz
iorwf currentDelay ; Inclusive or 0 with currentDelay. A result of zero
means that currentDelay was zero. Move to the OP section.
skpz ; Otherwise, return to the loop. IOR used because currentDelay
can be loaded with 0 if the AD returns a 255,
; in which case using DEC then SKPZ would lead to a LONG delay with
the input pinned.
goto program_1_loop ; Decrement currentDelay 1st thing in the loop now, to
save another case evaluation here.
;If we're here, the counters have all gotten to zero. Now worry about
putting the random bit on the OP.
btfss NOISE_BIT ;Check the bit on the RNG and put it on the output pin.
goto bitLow
bitHigh
bsf OUTPUT_PIN
movlw b'11111111' ;Turn up the LED all the way.
movwf CCPR1L
goto program_1_loop ;You're done, start the loop again.
bitLow
bcf OUTPUT_PIN
clrf CCPR1L ;Turn down the LED all the way.
goto program_1_loop ;You're done, start the loop again.
;---------------------------------------
;---------------------------------------
program_2_setup
;This program is easy. It looks at the comparator, and if the O/P of the
comparator is high, it sets the O/P bit
;high. If the comparator is low, the O/P goes low. Easy.
;***** -- In here now is the comp XORED with TMR0. It's not bad.
;***** -- See if you can't make the button (or something) somehow select the
TMR0 freq.
;First thing, turn on the OP so you don't waste these cycles every loop --
this stays on as long as the
;program is active.
bsf STATUS,RP0 ;Bank 1
bcf TRISIO,5 ;Turn the OP into an OP.
bcf STATUS,RP0 ;Bank 0
bsf STATUS,RP0 ;Bank 1
movlw b'11000111' ;Pullups off, timer0 driven by internal clock, prescaler
to timer0, prescaler = 1:256.
movwf OPTION_REG
bcf STATUS,RP0 ;Bank 0
movlw b'00110101' ;Free run Timer 1 with max prescale (1/8) -- this gives
us a frequency = (2Mhz) / 8 = 250kHz. Hmmm.
movwf T1CON ;Both of these registers are in Bank 0.
bsf CMCON1,CMSYNC ;Sync the comparator with the falling edge of Timer1
Clock.
bcf RESET_PROGRAM_FLAG
;I think that's all there is to it. But I don't think this'll matter much,
audio-wise, as the T1 Clock is so fast. You could ratchet down the CPU
freq...
program_2_loop
btfss SWITCH_INPUT
goto un_pressed_b
call debounce
call on_off_switch_handler
goto switch_hooey_finished_b
un_pressed_b
movlw DEBOUNCE_TIME
movwf debounce_counter
bcf ALREADY_PRESSED
clrf switch_held_counter
switch_hooey_finished_b
btfss OUTPUT_STATE_FLAG ;If the thing is supposed to be inactive, go to
another loop which just turns off the OP, diddles the LED and waits on
switches.
call bide_your_time
btfsc RESET_PROGRAM_FLAG ;If we return from a bide_your_time call to this
point, this flag tells us to re-init everything.
goto program_2_setup
btfsc CHANGE_PROGRAM_FLAG ;If the Change flag got tripped, break out of
this program to the next_program section.
goto next_program
clrf comparatorReg ;Set a bit based on the comparator OP.
btfsc CMCON0,COUT
bsf comparatorReg,4 ;Change this bit to change the "frequency" of the
"oscillator" you're logically XORing with the comparator.
movf comparatorReg,W
xorwf TMR0,W ;Effectively just XORing the comparator output with (bit 4*)
of TMR0 -- ***MAKE SURE NOT TO WRITE TO TMR0 HERE***
;The (bit 4*) of W is now what we want on the O/P.
;* If we've anded the comparator w/ a different bit in TMR0, make sure
to check that bit for the O/P.
;NB -- try using a different logical operator than XOR. Or, try just
doing a logical operation on the result of
;the A/D instead of TMR0 -- don't hang up the operation waiting on the
A/D, just free run that nasty nas.
;You could use the noise source instead of TMR0 or the A/D, too.
movwf comparatorReg ;You didn't need that register, did you?
btfss comparatorReg,4 ;Test the bit and rock it.
goto XORLow
bsf OUTPUT_PIN
movlw b'11111111'
movwf CCPR1L
goto program_2_loop
XORLow
bcf OUTPUT_PIN
clrf CCPR1L
goto program_2_loop
;The loop below totally works, and sounds like a RAT pedal or something.
Fun for shredding, but not winning any art awards.
; movlw d'10' ;Run this part of the loop this many times before
bothering to check the switches again. Make sure your debouncing works okay
with this program.
; movwf loopCounter
;
;square_wave_loop
;
; btfss CMCON0,COUT ;Test the comparator and rock it.
; bcf OUTPUT_PIN
; btfsc CMCON0,COUT
; bsf OUTPUT_PIN
; clrz
; decf loopCounter
; skpz
; goto square_wave_loop
; goto program_2_loop
;--------------------------------
;---------------------------------------
;---------------------------------------
program_3_setup
;Program 3 examines square wave generation in a different way, by taking an
A/D reading and using the most significant bit to
;set the square wave. Mess with conversion speed to get this to sound
different (aliasing vs max conversion rate violation)
;Right now the clock is scaled down to 4MHz and the A/D conversion speed is
way slowed down.
bsf STATUS,RP0 ;Bank 1
bcf TRISIO,5 ;Turn the OP into an OP.
movlw b'01100111' ;Slows down the internal oscillator like screwed and
chopped.
movwf OSCCON ;Osccon is in bank 1. (4Mhz sounds pretty good)
;The switches in this program are all kind of fucked up -- run the
oscillator back up for the
;waiting period.
movlw b'01101000' ; 0110 is = 1/64, 0000 (MSBs)= 1/2.
movwf ANSEL ;AN3 to analog input, conversion clock optimal = Fosc / 16.
(now f/64)
bcf STATUS,RP0 ;Bank 0
bcf RESET_PROGRAM_FLAG
program_3_loop
btfss SWITCH_INPUT
goto un_pressed_c
call debounce
call on_off_switch_handler
goto switch_hooey_finished_c
un_pressed_c
movlw DEBOUNCE_TIME
movwf debounce_counter
bcf ALREADY_PRESSED
clrf switch_held_counter
switch_hooey_finished_c
btfss OUTPUT_STATE_FLAG ;If the thing is supposed to be inactive, go to
another loop which just turns off the OP, diddles the LED and waits on
switches.
call bide_your_time
btfsc RESET_PROGRAM_FLAG ;If we return from a bide_your_time call to this
point, this flag tells us to re-init everything.
goto program_3_setup
btfsc CHANGE_PROGRAM_FLAG ;If the Change flag got tripped, break out of
this program to the next_program section.
goto next_program
;Wait for the conversion now, since eveything is done.
btfsc ADCON0,GO
goto $-1
;In 7 cycles, start the A/D again.
movf ADRESH,W
movwf ADresult
btfss ADresult,7 ;Test the bit and rock it.
goto goLow
bsf OUTPUT_PIN
movlw b'11111111'
movwf CCPR1L
bsf ADCON0,GO
goto program_3_loop
goLow
bcf OUTPUT_PIN
clrf CCPR1L
bsf ADCON0,GO
goto program_3_loop
;------------------------------
stepped_tone_program
;See notes. This one's complicated.
;Initialize TMR1 and TMR0. Set tmr0 and its counters to get the time
between the steps (1/8th second, say)
;Initialize CCP to Compare -- You won't be able to PWM the LED in this
program.
;Start the first routine and run it once:
;
;Note_one:
;Start TMR0 and any counters associated with it.
;Wait for the comparator to flip.
;Start TMR1.
;Wait for the comparator to flip again.
;When it does, get the value of TMR1, and reset the timer.
;Load this value into an IIR averaging filter like so: newAverage =
(average + currentValue) / 2
;Keep waiting for comparator flips, and averaging the time between them
UNTIL
;TMR0 counts off the step length. Now:
;Take the average from the IIR and put it into the CCP module. Keep
IIR_average.
;
;Now run the following loop forever:
;
;N_notes:
;Clear TMR0. Clear TMR1.
;clear a variable "timer_start_value" to zero.
;clear a variable "times_through_loop" to zero.
;clear a "tmr_flipped" flag.
;Wait for TMR0 to flip again. While you are:
;
;Run TMR1. When TMR1 matches CCP, flip the OP and reset TMR1.
; Check the TMR_flipped flag. If it is clear, set the "tmr_flipped_flag".
; If it is set, increment times_through_loop.
;
;Tracking the incoming frequency--
;We know TMR1 is resetting is dependent on the frequency being output, and
independent of the input frequency.
;When the comparator flips, take the value of TMR1. Call it
current_tmr1_reading or something. Reset TMR1 and keep it moving.
;Now operate on the results.
;Check the tmrflipped flag:
; If it is clear, tmr1 hasn't flipped since the last comparator change.
; Get the new period by simply taking (currentReading -
timer_start_value).
; If it is set, then tmr1 has reset to zero while the new frequency was
being counted.
; The new period is then (times_through_loop * (CCP_value)) + (CCP_value
- timer_start_value) + (current_timer_reading)
;From this we get the new period between comparator changes.
;Now:
;Clear the times_through_loop variable and tmr_flipped flag.
;set timer_start_value to current_tmr1_reading.
;Run the IIR averaging filter on the new period.
;Wait for the next comparator flip or TMR1 flip and handle it as above.
;
;When TMR0 flips, take the Average from the IIR and put it in the CCP.
Leave IIR loaded. Restart the loop.
;Registers
;IIRL, IIRH
;timer_start_valueL
;timer_start_valueH
;times_through_loop (I think you only need 8 bits here)
;TIMER_FLIPPED_FLAG (just one bit in some other register)
;current_timer_readingL
;current_timer_readingH
;tempL
;tempH
;step_timer_counter
init_stepped_tone
;Setup TMR0, TMR1, and the CCP.
bsf STATUS,RP0 ;Bank 1
movlw b'01110111' ;Sets the system clock to the internal oscillator and
;ratchets it up to 8MHz.
movwf OSCCON ;Osccon is in bank 1.
movlw b'11000111' ;Pullups off, timer0 driven by internal clock, prescaler
to timer0, prescaler = 1:256.
movwf OPTION_REG ;This means that TMR0 rolls over 30.5 times a second,
with internal clock = 8Mhz.
bcf TRISIO,5 ;Turn the OP into an OP.
bcf STATUS,RP0 ;Bank 0
movlw b'00110100' ;Set Timer 1 with max prescale (1/8) -- this gives us a
frequency = (2Mhz) / 8 = 250kHz.
movwf T1CON ;All the TMR1 stuff is in Bank 0.
;NOTE: TMR1 isn't on yet until we tell it to be.
clrf CMCON1 ;The comparator won't start unless it's unsynced with TMR1.
;The CCP stuff is in bank 0 also.
movlw b'00001010' ;Set the CCP to compare mode, and have it generate a
software interrupt when the match happens.
movwf CCP1CON ;The TMR1 registers should be unaffected.
;You know, you could use this to do A/D timing in the background
elsewhere...
bsf GPIO,2 ;turn on the LED.
bcf RESET_PROGRAM_FLAG
clrf IIRL
clrf IIRH
clrf timer_start_valueL
clrf timer_start_valueH
clrf times_through_loop
bcf TIMER_FLIPPED_FLAG
clrf current_timer_readingL
clrf current_timer_readingH
clrf tempL
clrf tempH
clrf temp1L
clrf temp1H
movlw STEP_TIME_CONSTANT
movwf step_timer_counter
bcf PIR1,CMIF
;Now that the timers and interrupts are reset, wait for the first comparator
flip. When it happens, start your
;routines and start TMR1. Then do the real loop.
;This'll make sure the first reading into the IIR isn't wildly off.
Hopefully.
btfss PIR1,CMIF ;The Peripheral Interrupt Register and comparator
interrupt flag are in Bank0.
goto $-1
; If here we know that the comparator has flipped. Start TMR1.
bcf PIR1,CMIF ;Clear the comparator interrupt flag.
bsf T1CON,TMR1ON ;Start the timer.
clrf TMR0
bcf INTCON,T0IF
first_note
btfss INTCON,T0IF
goto first_note_TMR0_handling_done
bcf INTCON,T0IF
clrz
decfsz step_timer_counter
goto first_note_TMR0_handling_done
goto first_note_done
; If we've gotten here then TMR0 has rolled STEP_TIME_CONSTANT times. We're
done with the first note.
; Is there no decfsnz command?
first_note_TMR0_handling_done
;Not time to go to the next note. Now what's up with the comparator?
btfss PIR1,CMIF ;The Peripheral Interrupt Register and comparator
interrupt flag are in Bank0.
goto first_note
; If here we know the comparator has flipped again. Stop TMR1, get the
value, and restart the timer.
bcf T1CON,TMR1ON ;Stop the timer.
bcf PIR1,CMIF ;Clear the Comparator interrupt flag.
movf TMR1L,W
movwf current_timer_readingL
movf TMR1H,W
movwf current_timer_readingH ;Move the timer value into the fiddle-ready
registers.
clrf TMR1L
clrf TMR1H
bsf T1CON,TMR1ON ;Start that shit again.
;Now that we've got the info, make it into the average.
call doIIR
goto first_note
;Once we've gotten the IIR updated, go back to the start of the loop.
first_note_done
movf IIRL,W ;Load the compare registers.
movwf CCPR1L
movf IIRH,W
movwf CCPR1H
;Clear timers, clear their interrupts. Clear CCP interrupt. Reload
the step counter with the constant.
; ? Start the timers again?
bcf PIR1,CCP1IF
bcf T1CON,TMR1ON
clrf TMR1L
clrf TMR1H
bcf INTCON,T0IF
movlw STEP_TIME_CONSTANT
movwf step_timer_counter
bcf PIR1,CMIF
clrf TMR0
bsf T1CON,TMR1ON
;--------
n_note
;All the tough shit happens here.
;See the notes that start this program.
;The first section is just like note 1 and checks to see if TMR0 flipped.
btfss SWITCH_INPUT
goto un_pressed_d
call debounce
call on_off_switch_handler
goto switch_hooey_finished_d
un_pressed_d
movlw DEBOUNCE_TIME
movwf debounce_counter
bcf ALREADY_PRESSED
clrf switch_held_counter
switch_hooey_finished_d
btfss OUTPUT_STATE_FLAG ;If the thing is supposed to be inactive, go to
another loop which just turns off the OP, diddles the LED and waits on
switches.
call bide_your_time
btfsc RESET_PROGRAM_FLAG ;If we return from a bide_your_time call to this
point, this flag tells us to re-init everything.
goto init_stepped_tone
btfsc CHANGE_PROGRAM_FLAG ;If the Change flag got tripped, break out of
this program to the next_program section.
goto next_program
btfss INTCON,T0IF ;And the business starts here.
goto n_note_TMR0_handling_done
bcf INTCON,T0IF
clrz
decfsz step_timer_counter
goto n_note_TMR0_handling_done
goto n_note_done
n_note_TMR0_handling_done
; You need to check for comparator flips and CCP matches.
btfsc PIR1,CCP1IF
call handle_CCP_match
btfsc PIR1,CMIF
call handle_comparator_flip
goto n_note
handle_CCP_match
;Here we need to flip the OP bit and reset TMR1.
;Check flipped flag, if it's clear, set it.
;If set, increment times_through_loop.
;Use comparatorReg to keep track of the OP since it doesn't get used
anywhere else in this program.
comf comparatorReg
btfss comparatorReg,0
bcf OUTPUT_PIN
btfsc comparatorReg,0
bsf OUTPUT_PIN
btfsc TIMER_FLIPPED_FLAG
goto increment_times_through_loop
bsf TIMER_FLIPPED_FLAG
return
increment_times_through_loop
incf times_through_loop
return
handle_comparator_flip
; This is the beating heart of the confusing assembly, as undertaken at 2am.
; Get the TMR1L first and hope it hasn't incremented since the comparator
flipped. If it has, no big deal, get it anyway.
; Get the other timer value, too (put in current_timer_reading)
; Check the TIMER_FLIPPED_FLAG. If it hasn't flipped, handle this case by
saying:
; (temp = current - timer_start_value) and adjust this process for 16 bits.
; If the TIMER_FLIPPED_FLAG has flipped, then:
; (times_through_loop * (CCP_value)) + (CCP_value - timer_start_value) +
(current_timer_reading)
; so xor times through loop with zero. If the Z flag is set, go on to
subtraction. Else:
; temp = temp + CCP value. decf times through loop. XOR check again.
; Once temp accounts for times through loop, then subwf CCP and timer start.
Add it to temp.
; Add current timer reading to temp.
; The math is done. Now:
; Make current timer reading into timer_start_value.
; Make temp into currentTimerReading.
; Do the IIR.
; Reset the comparator flag.
; Clear times_through_loop and TIMER_FLIPPED_FLAG.
; Return to the main loop.
movf TMR1L,W
movwf current_timer_readingL
movf TMR1H,W
movwf current_timer_readingH
btfsc TIMER_FLIPPED_FLAG
goto handle_timer_flipped_math
timer_not_flipped_math
;16 bit subtract is like:
setc
movf timer_start_valueL,W
subwf current_timer_readingL,W
movwf tempL
btfss STATUS,C
incf timer_start_valueH
movf timer_start_valueH,W
subwf current_timer_readingH,W
movwf tempH
goto mathDone
handle_timer_flipped_math
;Are the times through loop == zero?
clrw
clrz
xorwf times_through_loop,W
skpnz
goto CCP_minus_timer_start_value
;If not, add the CCP registers to the temp registers and dec times through
loop. When you get to zero, you're done.
clrc
movf CCPR1L,W
addwf tempL,F
btfsc STATUS,C
incf tempH
movf CCPR1H,W
addwf tempH,F
decf times_through_loop
goto handle_timer_flipped_math
CCP_minus_timer_start_value
;now take the (CCP value) - (timer start value) and call that temp1.
setc
movf timer_start_valueL,W
subwf CCPR1L,W
movwf temp1L
btfss STATUS,C
incf timer_start_valueH
movf timer_start_valueH,W
subwf CCPR1H,W
movwf temp1H
;add temp1 to temp.
clrc
movf temp1L,W
addwf tempL,F
btfsc STATUS,C
incf tempH
movf temp1H,W
addwf tempH,F
;the last thing we do is add the currentTimerReading to Temp.
clrc
movf current_timer_readingL,W
addwf tempL,F
btfsc STATUS,C
incf tempH
movf current_timer_readingH,W
addwf tempH,F
mathDone
;Phew. Bet that section will give trouble at runtime.
;Finish up by calling the current read the start for the next read, then the
temp to the current.
;Do the IIR.
; Reset the comparator flag.
; Clear times_through_loop and TIMER_FLIPPED_FLAG.
; Return to the main loop.
movf current_timer_readingL,W
movwf timer_start_valueL
movf current_timer_readingH,W
movwf timer_start_valueH
movf tempL,W
movwf current_timer_readingL
movf tempH,W
movwf current_timer_readingH
call doIIR
bcf PIR1,CMIF
clrf times_through_loop
bcf TIMER_FLIPPED_FLAG
return
n_note_done
;make sure to reinitialize the start value as well as loop counters.
movf IIRL,W ;Load the compare registers.
movwf CCPR1L
movf IIRH,W
movwf CCPR1H
clrf timer_start_valueL
clrf timer_start_valueH
clrf times_through_loop
bcf TIMER_FLIPPED_FLAG
;Clear timers, clear their interrupts. Clear CCP interrupt. Reload
the step counter with the constant.
bcf PIR1,CCP1IF
bcf T1CON,TMR1ON
clrf TMR1L
clrf TMR1H
bcf INTCON,T0IF
movlw STEP_TIME_CONSTANT
movwf step_timer_counter
bcf PIR1,CMIF
clrf TMR0
bsf T1CON,TMR1ON
goto n_note
;-------------------------------------------------------------------------------------------------------
;-------------------------------------------------------------------------------------------------------
;Subroutines:
;-------------------------------------------------------------------------------------------------------
;-------------------------------------------------------------------------------------------------------
doIIR
;Performs an really simple IIR filtering (averaging) algorithm of the form:
; IIRvalue = (IIRvalue + currentSample) / 2
;All this shit is 16-bit.
;Below is the add:
clrc
movf current_timer_readingL,W
addwf IIRL,F
btfsc STATUS,C
incf IIRH
movf current_timer_readingH,W
addwf IIRH,F
;At this point IIR has been replaced with the (current reading + the old
average). current_timer_readings are unchanged.
;Roll the IIR registers right to divide by two.
clrc
rrf IIRH
rrf IIRL
;Simplicity itself.
return
;--------------------------------
random32
;This is a 32-bit Galois LFSR (Linear Feedback Shift Register)
;which seems like it ought to give a nice long random sequence.
;Probably the "randomness" is less important than having it run fast.
;This must be seeded with non-zero values.
;(7 or 13 tcy)
bcf STATUS,C
rrf randomval4,F
rrf randomval3,F
rrf randomval2,F
rrf randomval1,F
btfss STATUS,C
return
movlw 0xA6
xorwf randomval4
xorwf randomval3
xorwf randomval2
xorwf randomval1
return
;--------------------------------
readprom
;This routine reads a byte in from eeprom address 0 to the program select
register.
bsf STATUS,RP0 ;Bank 1.
clrf EEADR ;Sets the eeprom address to 00h.
bsf EECON1,RD ;Execute the read.
movf EEDAT,W ;Put the data into the W register.
bcf STATUS,RP0 ;Go back to bank 0.
movwf program_selector ;Move the data to the program selector register.
return
writeprom
;This routine writes a byte from the W register into address 0 in eeprom.
;Note: this routine will just kick it here until the write is finished, so
the program will
;hang for up to 6ms. This will probably fuck up any timing specific stuff
going on (sampling, software PWM
;cycle counting) so be aware of that when you call this.
bsf STATUS,RP0 ;Bank 1.
clrf EEADR ;eeprom addy to 00h.
movwf EEDAT ;Put that data up in there.
bsf EECON1,WREN ;Set the Write Enable bit.
movlw 0x55 ;Microchip Magic Unlocking Spell.
movwf EECON2 ; " "
movlw 0xAA ; " "
movwf EECON2 ; " "
bsf EECON1,WR ;Start writing. Bring a book...
btfsc EECON1,WR ;Keep testing to see if the write is done.
goto $-1 ;If it isn't, keep testing.
bcf EECON1,WREN ;Once it is, make the eeprom safe again.
bcf STATUS,RP0 ;Go home to bank 0 where simple programmers feel safe.
return
;-------------------------
debounce
;This routine debounces the one switch.
;NOTE: I changed this so the code only gets here if a switch is touched.
;If the switch reads pressed (high) decrement a counter (debounce). If the
counter gets to
;xero, set a flag (VALID). Clear this flag elsewhere after you've done
whatever it is you wanted to happen when the button
;got pressed.
;If the switch ever reads low, reset that counter (and make ready a new
VALID reading by clearing the ALREADY PRESSED flag).
;The VALID flag only gets set once per switch hit even if the switch is
held, thanks to the ALREADY PRESSED flag.
;If the switch is held for a very long time (ie, the debounce counter rolls
over HOLD_TIME times)
;do something I haven't figured out yet.
;(15, 18 or 21 tcy)
movlw HOLD_TIME
clrz
xorwf switch_held_counter,W
skpnz
goto switch_held
clrz
decf debounce_counter
skpz
return
incf switch_held_counter
btfsc ALREADY_PRESSED
return
bsf ALREADY_PRESSED
bsf VALID_SWITCH
return
switch_held
bsf CHANGE_PROGRAM_FLAG
bcf ALREADY_PRESSED ;Is this making trouble?
bcf VALID_SWITCH ;Ditto?
;Handle changing programs in each loop, because a goto at this point would
pre-empt the return from
;debounce and put something on the stack that would never get popped back
off.
clrf switch_held_counter ;reset your counters.
movlw DEBOUNCE_TIME
movwf debounce_counter
;hang up here until genius lets off the switch.
btfsc SWITCH_INPUT
goto $-1
bsf OUTPUT_STATE_FLAG ;Cheat these guys such that the only flag to get
hit is the
bcf RESET_PROGRAM_FLAG ;Next program flag.
return
;-----------------------------------------------------------------------------
on_off_switch_handler
;This routine uses switch information to toggle the output -- get this --
;on and off. Revolutionary!
;(2 or 5 tcy)
btfss VALID_SWITCH
return
bcf VALID_SWITCH
comf output_state
return
;-----------------------------------------------------------------------------
init_PWM
;Here the PWM stuff is all initialized. The TMR2 shit is set up and the
CCP1.
;This will turn the LED on and (almost) all the way up.
;To vary the brightness over an 8-bit range, change the value in CCPR1L.
The two LSBs are in CCP1CON
;and I'm not worrying about them here.
bcf STATUS,RP0 ;Bank 0
movlw b'00000100' ;TMR2 on, scaling to 1:1.
movwf T2CON
movlw b'00001100' ;CCP set to PWM.
movwf CCP1CON
movlw b'11111111' ;Set the Duty cycle to max.
movwf CCPR1L
bsf STATUS,RP1 ;bank1
movlw b'11111111' ;Set the period to max, to make the numbers easier.
movwf PR2
bcf STATUS,RP0 ;bank0
return
;-----------------------------------------------------------------------------
;Here's the surrogate loop that happens when the device is in the off state.
;In the biding time loop, make sure to get the oscillator to 8Mhz.
;Reset the CCP to PWM mode.
;Make a RESET_PROGRAM_FLAG which is set when the program gets to bide your
time, so that it can re-initialize when it returns to whichever program it
was in.
;
;Remember to re-init any A/D weirdness in programs 0 and 1, since it gets
changed in program 3.
;So change OSCON, ANSEL, CCP1CON back to normal.
bide_your_time
bsf STATUS,RP0 ;Bank 1
bsf TRISIO,5 ;Turn the OP into a high impedance input.
movlw b'01110111' ;Sets the system clock to the internal oscillator and
;ratchets it up to 8MHz.
movwf OSCCON ;Osccon is in bank 1.
movlw b'01011000'
movwf ANSEL ;AN3 to analog input, conversion clock = Fosc / 16.
bcf STATUS,RP0 ;Bank 0
movlw b'00001100' ;CCP set to PWM.
movwf CCP1CON
bsf RESET_PROGRAM_FLAG ;This is checked after the return from
bide_your_time in any given program. If it's set, the program vectors back
to it's setup routine.
btfss SWITCH_INPUT
goto un_pressed2
call debounce
call on_off_switch_handler
goto switch_hooey_finished2
un_pressed2
movlw DEBOUNCE_TIME
movwf debounce_counter
bcf ALREADY_PRESSED
clrf switch_held_counter
switch_hooey_finished2
;Check the OP state to return to the program.
;Do the LED.
btfsc OUTPUT_STATE_FLAG
return
btfsc CHANGE_PROGRAM_FLAG ;If we get a Change flag, return to the main
program so we can exit it to the program changer.
return
nop ;Here are 31 nops to make this biding loop the same length as the "on"
loop, to keep the debouncing the same.
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
clrz
incf LEDchase ;Every time this counter passes through zero, bump the PWM
up by one.
skpnz
incf CCPR1L
goto bide_your_time
;----------------------------
debug
;This just blinks and LED forever if the program gets to this point.
bcf STATUS,RP0 ;Bank0
movlw b'00001010' ;Set the CCP to compare mode, and have it generate a
software interrupt when the match happens.
movwf CCP1CON ;The TMR1 registers should be unaffected.
;You know, you could use this to do A/D timing in the background
elsewhere...
bsf GPIO,2 ;turn on the LED.
btfss INTCON,T0IF
goto $-1
bcf INTCON,T0IF
clrz
incf debugCounter
skpz
goto $-6
bcf GPIO,2
btfss INTCON,T0IF
goto $-1
bcf INTCON,T0IF
clrz
incf debugCounter
skpz
goto $-6
goto debug
;-------------------------------------------------------------------------------------------------------
;-------------------------------------------------------------------------------------------------------
end
You can’t perform that action at this time.