So I have a little PIC12F683 eight pin device pulsing an LED with the PWM register in front of me on a breadboard. It's a tap-tempo triangle wave LFO. I've heard the 8-bit LFO's get to sounding a little grainey. I haven't tried it yet on my filter or a trem circuit to see if this is true, but the concept works well and grainey or not, it's useable. Some filtering on the output should remedy the grainey sound (it's just extra parts is the issue). If I used a crystal and sped it up to 20 MHz on the clock, I may be able to get the full 10 bits from the PWM register. I'll save for later experimenting.
With no further ado, here's the code to do what I've done. Perhaps others who are into this PIC on guitar FX thing will have some ideas to contibute for making it better....Let me know if any of you assemble it and take it for a test drive.
; FILE: Tap.asm
; AUTH: R.Billing
; DATE: 03/02/2006
; DESC: Tap Tempo Low Frequency Oscillator. Output on PWM pin
; as calculated triangle wave. TMR1 (16-bit timer register) is
; preset with a value so to overflow every 12.8 (+/-) milliseconds.
; PER_CT increments on every overflow as a more coarse time counter.
; regA stores final count from PER_CT, and this count is used to
; set output sample rate where 1 iteration through the sample rate
; delay loop is at 1/512 of 12.8ms (about 50us). Total period
; contains 512 samples. The count in regA therefore ties the link
; between output sample rate and time elapsed between first button
; tap, and second button tap.
;
; Operation on the outside looks like this:
; Tap button once, TMR1 starts counting, GP4 turns on (used for
; indicator LED to see when it's "recording". Tap button a
; second time, timer stops, turns off GP4, and writes new sample
; rate to oscillator.
; My disclaimer: I didn't go through and do instruction cycle
; counting on each bit of code, so the measured time between taps
; may be gaining or losing time during execution of instructions,
; though each instruction takes up a whole 0.5 microseconds, so
; it's probably not noticeable. The theory (algorithme) checks out
; on paper, and in Scilab. It's just ASMOP :-)
;-------E---------------------------------------------------------------
; cpu equates (memory map)
INCLUDE P12F683.INC
PW_TX EQU 0x20
PW_TEMP EQU 0x21
PW_DEC EQU 0x22
regA EQU 0X23
decA EQU 0X24
PER_CT EQU 0X25
;----------------------------------------------------------------------
GOTO MAIN
;*********************************************************************
;
; Interrupt Service Routine (ISR)
;
;*********************************************************************
ORG 0x4
BTFSC PIR1,0 ;Check if tmr1 interrupt
GOTO timer_oflow
CALL debounce
MOVLW b'01000000'
XORWF INTCON,F ;toggle timer interrupt
GOTO notlow
timer_oflow
MOVLW 0xA3 ;100 ck cycles on TMR1l w/ compensation for 6 cycles during interrupt reset
MOVWF TMR1L
CLRF TMR1H
BSF GPIO,4
MOVLW 0x1
ADDWF PER_CT,F ;Increment period counter so it sets carry bit
BTFSS STATUS,0 ;Test carry bit
GOTO clr_tmr_flg
BCF INTCON,6 ;Disable tmr1 interrupt if regA overflows
MOVF PER_CT, W
MOVWF regA ;Save countable period
CLRF PER_CT
BCF GPIO,4
clr_tmr_flg
BCF PIR1,0 ;Clear interrupt flag
RETFIE
notlow
CALL debounce
BTFSC GPIO,5
GOTO notlow ;ensure pin is low
MOVF PER_CT,W
BTFSS INTCON,6
MOVWF regA
BTFSS INTCON,6
CLRF PER_CT
MOVLW 0x9C
MOVWF TMR1L
CLRF TMR1H
BCF GPIO,4 ;"Timing" LED off
BCF INTCON,0 ;clear interrupt on change flag
nop
RETFIE
;*********************************************************************
;
; MAIN PROGRAM
;
;*********************************************************************
MAIN
BCF STATUS,5 ;Bank 0
CLRF GPIO
CLRF CMCON0
BSF STATUS,5
CLRF ANSEL
MOVLW b'00100000'
MOVWF TRISIO ; All GPIO configured as outputs
MOVLW b'11110000'
MOVWF OSCCON
BSF IOC, 5 ;SET GP0 AS INTERRUPTIBLE PIN
BCF STATUS,5
CLRF INTCON
CLRF T1CON
BSF T1CON,0 ;timer1 on
BSF STATUS,5
BSF PIE1,0 ;timer1 ouverflow interrupt enable
BCF STATUS,5
BCF PIR1,0 ;clear interrupt flag for kicks and chix
CLRF T2CON
BSF T2CON,2 ;timer2 on
BCF INTCON,0
BSF INTCON,GIE
BSF INTCON,GPIE ;enable interrupt on GPIO change
MOVLW 0xC ;CCP CONFIGURATION BITS FOR PWM MODE
MOVWF CCP1CON
BSF STATUS,5 ;Bank 1 select
MOVLW 0x3F
MOVWF PR2
BCF STATUS,5 ;Bank 0 select
MOVLW 0xAA
MOVWF regA ;Presest default LFO rate
loop_forever
NOP
FORWARD
CALL sample_delay
MOVLW 0x1
ADDWF CCPR1L,F
BTFSS STATUS,0
GOTO FORWARD
MOVLW 0xFF
MOVWF CCPR1L ;set PWM reg to max
REVERSE
CALL sample_delay
DECFSZ CCPR1L ;TRIANGLE LFO
GOTO REVERSE
GOTO loop_forever
;*************************************************************************
; SUBROUTINE--debounce
; Debounce switches
;*************************************************************************
debounce
MOVLW 0x05
MOVWF PW_DEC
outer
MOVLW 0xFF
MOVWF PW_TEMP
dloop
DECFSZ PW_TEMP
GOTO dloop
DECFSZ PW_DEC
GOTO outer
CLRF PW_DEC
CLRF PW_TEMP
RETURN
;************************************************************************
;*************************************************************************
; SUBROUTINE-sample_delay
; Sets output sample rate to change LFO speed
; Input: regA , holds number of iterations for 12.7 ms loop
;*************************************************************************
sample_delay
MOVF regA,W
MOVWF decA
sample_outer
MOVLW 0x13
MOVWF PW_TEMP
sample_inner
NOP
NOP
DECFSZ PW_TEMP
GOTO sample_inner
nop
DECFSZ decA
GOTO sample_outer
CLRF PW_TEMP
RETURN
end
;----------------------------------------------------------------------
; Developed in MPLAB
; at blast time, select:
; memory uprotected
; watchdog timer disabled
; internal clock/no clkout (8 MHz)
; power-up timer on
;
; If not using MPLAB IDE, add a line in the header to set confinguration bits
; according to the above before assembling
Urk. I've been doing object-oriented programming in high-level languages, all those gotos and the lack of nice curly-braces scares the hell out of me. I've never coded in assembler!
Time to roll up the sleeves, I guess.
Thanks for sharing this, I'll be back to reread it sometime soon, I'm sure.
At least I understood some pieces of the code,still have to become
less afraid of the hardware!Thanks for that stunning example!!!
Greetings JME
:icon_cool: :icon_cool: :icon_cool: :icon_cool: :icon_cool:
Hey, Peter and Trans:
That debounce thing is probably something that we're going to have to use a lot. I recall that there's some debounce code in my bass pedal program (of course, I don't have the annotated source anymore since my *&*(_)_)*((_*&*^& prior computer took a catastrophic head crash). I recommend that we see if there's a way to turn a debounce procedure into some kind of generalized subroutine or macro so it just becomes one of the things we do.
By the way, your header looks like a good first step toward a standard format.
Quote from: DavidS on March 03, 2006, 04:39:07 AM
Urk. I've been doing object-oriented programming in high-level languages, all those gotos and the lack of nice curly-braces scares the hell out of me. I've never coded in assembler!
Time to roll up the sleeves, I guess.
Thanks for sharing this, I'll be back to reread it sometime soon, I'm sure.
You can still write elegant assembler code. It just takes a little more work.
QuoteWith no further ado, here's the code to do what I've done. Perhaps others who are into this PIC on guitar FX thing will have some ideas to contibute for making it better
Great stuff. It looks a lot like some of my LFO stuff. Of course, LFOs are not all that different one from another.
QuoteQuoteUrk. I've been doing object-oriented programming in high-level languages, all those gotos and the lack of nice curly-braces scares the hell out of me. I've never coded in assembler!
You can still write elegant assembler code. It just takes a little more work.
Ah! There's someone who's written some code with his eyes open. Darn right! The discipline in structuring your code can just as well come from your head as curly braces. In fact, if you have the discipline to write well-structured code, you don't need crutches like languages that force you to declare and stick in matching braces. Just like a true wood craftsman can turn out out masterpieces with only hand tools. He may go faster with power tools, but the artistry is there whether or not he has the fancy stuff.
For good programming THE LANGUAGE DOES NOT MATTER. It's how you organize your thoughts.
Just as a tip for those of you who never used assembler - it's entirely possible with a good macro assembler to define macros which amount to all of the structured statements and program only in the structured stuff. Hey - that sounds like how a compiler does it... hmmm...
QuoteThat debounce thing is probably something that we're going to have to use a lot. ... I recommend that we see if there's a way to turn a debounce procedure into some kind of generalized subroutine or macro so it just becomes one of the things we do.
Another really, really important concept - don't reinvent any more than you have to.
One thing you could do is to got to the PICList and look up "debounce". You'll turn up lots of examples of debouncers written by experts, previously tested, and free for the asking. Back when I first did my eight way footswitcher I found a so-called "vertical adder" there that
debounces all eight footswitches at the same time, with the same code, and leaves the state of all eight switches in one byte. It let me assign individual switches to alternate action or momentary with a "switch action" byte in the code.
In the words of the immortal Tom Lehrer:
QuoteI think of great Lobachevski and I get idea... plagiarize! Let no one else's work evade your eyes, so plagiarize, plagiarize, plagiarize.
Only please always to call it research.
QuoteBy the way, your header looks like a good first step toward a standard format.
Absolutely. A header is mandatory, not optional.
I've never understood the obsession with getting rid of GOTO instructions in "higher level" languages. It always seemed to me that if it was good enough for the CPU to speak, why shouldn't I?
Debouncing is something that we will do quite a bit of around here, but in the microcontroller assembly world it's often something that you do while you do other things. You can't always make something for I/O into a nice little packaged library like you do in C, but you can make snippets that perform the same function which are then placed in-line with other code. The way the switch may be hooked up in the circuit can vary all over the place, so very often the snippet needs a little customization for your hardware. There are several ways to accomplish debouncing but you can think of them as very low resolution digital low-pass filters. ;)
As R.G. just mentioned, there are sources of zillions of standard I/O routines on the net. :icon_biggrin:
QuoteI've never understood the obsession with getting rid of GOTO instructions in "higher level" languages. It always seemed to me that if it was good enough for the CPU to speak, why shouldn't I?
Yeah - real manly-guy programmers program right down to the bare metal!
It took me a while to figure that one out. I was extruded through some formal proof-of-correctness programming stuff. It turns out that GOTOs are perfectly fine for that. The problem is in the programmer's head. It turns out that a GOTO is always done for a side effect. It's practically never just to neatly organize ideas. It's about 99% used for implementing something like testing a condition, then conditionally executing some code. Problem is, once you have written the code, a week later you can't remember the side effects you were depending on to make that GOTO work properly.
The whole nonsense of WHILE...END, IF... THEN...ELSE...END, CASE X ... CASE END, GOSUB, and later on the whole idea of Java classes is to sledgehammer-force programmers to have a clearly written out reason for why they took that jump. The idea is that if you can't make the programmers better, at least you can put training wheels on the language to force them to do it right.
OK, I'm ranting now. It's more like using safety guards on machine tools. If there is a safety guard there, you usually can't hurt yourself by being inattentive. You can take the guards off and still get hurt, but presumably it's because you wanted to get rid of those pesky excess fingers.
Another way of looking at it is that as a society we needed a big slug of programmers, and we used up all the talented great ones too fast. We scraped the barrel harder by raising programmer salaries so high that people who didn't really have huge aptitudes for the work decided to program. That worked, but it was a lot more productive if we didn't let the lesser-skilled ones hurt themselves and others by coloring outside the lines. It's a productivity measure.
The funny thing about prohibiting GOTOs in uCs is that in the limited memory space and relative cluttering of the space with special purpose registers, GOTOs really do have a place to restructure code logically.
... and GOTOs are GREAT!!! for confusing someone else about what your code is doing... :icon_biggrin:
Quote from: Peter Snowberg on March 03, 2006, 09:51:42 AM
I've never understood the obsession with getting rid of GOTO instructions in "higher level" languages. It always seemed to me that if it was good enough for the CPU to speak, why shouldn't I?
I'll give you two reasons, but I'll venture to guess that few people here will understand them:
1) The GOTO DEPENDING ON statement
2) The ALTER statement
Quote from: Peter Snowberg on March 03, 2006, 09:51:42 AM
You can't always make something for I/O into a nice little packaged library like you do in C, but you can make snippets that perform the same function which are then placed in-line with other code.
Touche. OK, then, some judiciously-written INCLUDE files for incorporation at the proper place in the code. You like?
Quote from: David on March 03, 2006, 11:45:16 AM
Quote from: Peter Snowberg on March 03, 2006, 09:51:42 AM
You can't always make something for I/O into a nice little packaged library like you do in C, but you can make snippets that perform the same function which are then placed in-line with other code.
Touche. OK, then, some judiciously-written INCLUDE files for incorporation at the proper place in the code. You like?
It sounds reasonable to me. IDE's generally make such things very easy to do with object linkers and such.
Does Atmel have a free IDE for working with their devices?
Atmel does have a free IDE. I played with it a little last night. Built-in simulator/debugger. I didn't do too much with it, just ran some example code and fiddled with it a little, but the program seems pretty nice.
Quote from: R.G. on March 03, 2006, 10:27:11 AM
The whole nonsense of WHILE...END, IF... THEN...ELSE...END, CASE X ... CASE END, GOSUB, and later on the whole idea of Java classes is to sledgehammer-force programmers to have a clearly written out reason for why they took that jump. The idea is that if you can't make the programmers better, at least you can put training wheels on the language to force them to do it right.
not to argue with the expert(s) (but I do with my wife, so why not here?) but next someone is going to say there is no use for jsr's or call's or btfsc's . . .
Been a while since I've seen assembly code, I tend to stay away from that stuff :icon_mrgreen:.
About the GOTO statement, I don't think I will ever understand why there are programmers who stick to the idea 'If it was good enough for my grandpa in 1955, it's good enough for me!'. The reason why goto is evil was published in 1968 by Dijkstra (http://www.acm.org/classics/oct95/ (http://www.acm.org/classics/oct95/)). In higher level languages, the necessity of goto is near non-existent. The control flow just does everything for you, and makes it a lot easier to read too.
I don't really agree with the view that all this fancy while end do if stuff was just to force monkeys to be able to program in a understandable way. If it was actually meant to do that, it failed horribly... I think it has more to do that you can write code a lot more compact and therefore easier to read. Try making a for loop in assembly and you'll see what I mean. Programs got bigger and bigger and more complicated so low level languages just became too much of a hassle, not to mention it being more error prone.
For the record, my Grandpa worked for the Post Office. :icon_wink:
To restate my argument, the CPU uses GOTO (or JMP) as one of it's very few instructions.
Why should I be forced to exclude this perfectly valid instruction in order to follow somebodies academic and linear concept of saving me from myself when it's just not necessary?
A lot of code I've written could not be written without jumps because of cycle or stack space restrictions. JMP isn't just an option in microcontroller land, sometimes it's imperative. The early PICs only had two words of stack; not exactly CALL friendly.
I'm an assembly programmer, and always will be. :icon_biggrin:
Anyway, let us turn back to Transmogrifox's code. 8) 8) 8)
Who is going to be the first one to make it watch four foot taps and average the tap rate?
Quote from: Peter Snowberg on March 04, 2006, 08:32:47 PM
Who is going to be the first one to make it watch four foot taps and average the tap rate?
well, on a quick glance it looks like he's already looking for the switch on the ISR, so it shouldn't be too hard to set two eight bit words for the number of cycles between switch contacts, then add them and divide for the average . . .
Quote from: jrem on March 05, 2006, 07:41:17 PM
Quote from: Peter Snowberg on March 04, 2006, 08:32:47 PM
Who is going to be the first one to make it watch four foot taps and average the tap rate?
well, on a quick glance it looks like he's already looking for the switch on the ISR, so it shouldn't be too hard to set two eight bit words for the number of cycles between switch contacts, then add them and divide for the average . . .
I think the addition of some conditionals in it would be necessary. Give it a timeout condition so if you only give it two or three taps, it doesn't screw up your tempo the next time when you try to go to a much slower of faster rate by averaging the old tap times with your intended tempo.
Still not hard to do. If anyone knows of links to some simple 8-bit averaging algorithms, or even division, that would be a good start.
Division in an 8-bit uC is not real straight forward operation. For the addition, you would have to alot each sample two registers to deal with the carry bit. If you divide first, then add, you may lose a little resololution...but if you're off by 3 ms, who is going to detect that? It would save the trouble of dealing with carry bits and the extra registers.
Yeah, but off by 3ms isn't off by 3ms each beat, it's additive. So you're off 3ms, 6ms, 9ms, 12ms, etc. At 250ms tempo, you're off 720ms after a minute. OK, yeah, not a huge deal...
If you looked at the last tap relative to the first, wouldn't you automatically have the average if you divided by four?
It does add up and PICs are quick so a little more math isn't much of a problem.
Meta-code for average of 4 numbers:
load value1
add value2
add value3
add value4
shift right
shift right
save result
If you're playing in 11/17 or some other timing, the code gets a little more tricky. :icon_wink:
That's what I was doing when my programmer went belly up. Two shifts to the right. The only difference is that I was using the 16-bit timer and ignoring all but the first and fourth switch closures (as I mentioned earlier).
Quote from: Peter Snowberg on March 06, 2006, 01:06:30 PM
It does add up and PICs are quick so a little more math isn't much of a problem.
Meta-code for average of 4 numbers:
load value1
add value2
add value3
add value4
shift right
shift right
save result
If you're playing in 11/17 or some other timing, the code gets a little more tricky. :icon_wink:
Since we don't have any 16-bit registers we may want to do something like this:
load value1
load value2
shift right value1
shift right value2
add 2 results, store in A
load value3
load value4
shift right value3
shift right value4
add results, store in B
load valueA
load valueB
shift right valueA
shift right valueB
add results, store in result register
...or we could keep track of an extra register per byte for carries
...or we could keep track of the non-zero 2
-1 bits that shift off the end and add 1 to the final result if there is a pair generated...or just add 1 to the result if there is a non-zero 2
-1 bit at any point in the operation.
I think the being off by 1 will be inconsequential. We would perceive a phase lag rather than a tempo change if the musicians kept perfect tempo in the song, but usually we (speaking for myself mostly) don't keep to that degree of precision where our technology would be at fault for getting us out of sync. We would need better than 8-bit processing if we were that good.