Skip to content

Instantly share code, notes, and snippets.

@thentenaar
Created April 26, 2024 15:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thentenaar/4b4eb419da5e85a94743638c7b3bb41b to your computer and use it in GitHub Desktop.
Save thentenaar/4b4eb419da5e85a94743638c7b3bb41b to your computer and use it in GitHub Desktop.
Happy Birthday on a PIC16f (for my daughter)
; Happy Birthday on a PIC16f (for my daughter)
;
; Copyright (C) 2020 Tim Hentenaar.
; This code is licenced under the Simplified BSD License.
; https://opensource.org/license/bsd-2-clause
;
; Plays the classic tune on a piezoelectric buzzer (PWM), and blink some LEDs.
;
list p=16f628a
include <p16f628a.inc>
; Configuration words
CONFIG FOSC = INTOSCIO ; Internal Oscillator (RA6/7 are I/O pins)
CONFIG MCLRE = ON ; Enable /MCLR
CONFIG BOREN = OFF ; Disable Brown-out Reset
CONFIG WDTE = OFF ; Disable WDT
CONFIG CPD = OFF ; Data prot. Off
CONFIG CP = OFF ; Code prot. Off
CONFIG LVP = OFF ; Disable Low-Voltage programming
; Number of TMR0 overflows before updating
led_time equ 2
note_time equ 4
; Music note periods
_r equ 0 ; Rest
_c equ D'238' ; 261 Hz
_d equ D'212' ; 293 Hz
_e equ D'189' ; 329 Hz
_f equ D'178' ; 349 Hz
_g equ D'158' ; 392 Hz
_a equ D'141' ; 440 Hz
_b equ D'126' ; 493 Hz
_C equ D'118' ; 523 Hz
_D equ D'103' ; 597 Hz
_E equ D'94' ; 659 Hz
_F equ D'89' ; 698 Hz
_G equ D'78' ; 784 Hz
; Note durations
_whole equ 0x08
_half equ 0x04
_quarter equ 0x02
_8th equ 0x01
_16th equ 0x00
; Variables
counter equ 0x70 ; Timer overflow counter
state_a equ 0x71 ; Local port state copy
state_b equ 0x72
direction equ 0x73 ; LED Pattern direction
note_counter equ 0x74 ; Note timer
note_counter2 equ 0x75
note equ 0x76 ; Current note
; Reset Vector
org 0x00
goto init
; ISR
org 0x04
goto int
;
; Happy Birthday (in C major)
;
n_notes equ D'35'
note_period:
addwf PCL, F
dt _r
dt _g, _r, _g, _r, _a, _g, _C, _b
dt _g, _r, _g, _r, _a, _g, _D, _C
dt _g, _r, _g, _r, _G, _E, _C, _b, _a
dt _F, _r, _F, _r, _E, _C, _D, _C
note_duration:
addwf PCL, F
dt _half
dt _8th, _16th, _8th, _16th, _quarter, _quarter, _quarter, _half
dt _8th, _16th, _8th, _16th, _quarter, _quarter, _quarter, _half
dt _8th, _16th, _8th, _16th, _quarter, _quarter, _quarter, _quarter, _quarter
dt _8th, _16th, _8th, _16th, _quarter, _quarter, _quarter, _half
init:
banksel INTCON ; Ensure we're on bank 0
clrf INTCON ; Disable interrupts
movlw 0x07 ; Disable comparitors
movwf CMCON
movlw 0x0b ; 4 MHz
movwf PCON
; Set the prescaler for TMR0 (1:256)
; and ensure Port B pullups are disabled
banksel OPTION_REG
movlw 0xd7
movwf OPTION_REG
; Enable CCP1 for PWM mode
banksel CCP1CON
movlw 0x0f
movwf CCP1CON
clrw
movwf CCPR1L
; Initialize ports A & B (output)
banksel TRISA
movlw 0x20
movwf TRISA
clrf TRISB
banksel PORTA
movlw 0x10
movwf state_a
clrf PORTA
movlw 0x20 ; PWM Uses B3
movwf state_b
movwf PORTB
; Initialize our variables
movlw led_time
movwf counter
clrf direction
; Load the first note of the tune on the first
; overflow of TMR0
movlw 1
movwf note_counter
movwf note_counter2
movlw 0xFF
movwf note
; TMR2: Enabled with the 1:16 prescaler
movlw 0x07
movwf T2CON
clrf TMR2
banksel PR2
clrf PR2
; Enable interrupts
banksel INTCON
clrf TMR0
bcf INTCON, T0IF
bsf INTCON, T0IE ; Enable the interrupt for TMR0
bsf INTCON, GIE
idle:
nop
nop
nop
goto idle
; Interrupt handler
int:
; We only service TMR0 interrupts
banksel INTCON
btfss INTCON, T0IF
retfie
; TMR0 overflows at a rate of ~3.9 us
; note_counter2 overflows at ~1 ms.
; note_counter overflows at ~256 ms.
decfsz note_counter2, F
goto leds
movlw note_time
movwf note_counter2
decfsz note_counter, F
goto leds
; Our note duration has elapsed
banksel CCPR1L
clrf CCPR1L
; Set-up the next note
incf note, F
; Ensure we wrap-around when we get to the end of the tune
movfw note
bcf STATUS, Z
sublw n_notes
btfsc STATUS, Z
movwf note
; Get the next note period
movfw note
call note_period
; See if it's a rest
iorlw 0
btfsc STATUS, Z
goto duration
; Load it into PR2
banksel PR2
movwf PR2
; Set the duty cycle
banksel CCPR1L
movlw 8
movwf CCPR1L
duration:
; Load the note duration
banksel INTCON
movfw note
call note_duration
movwf note_counter
iorlw 0
btfss STATUS, Z
goto leds
; Zero duration (16th note)
incf note_counter, F
rrf note_counter2, F
leds:
decfsz counter, F
goto ack
movlw led_time
movwf counter
; Check the movement direction
btfsc direction, 0
goto left
; 0 = Right-moving pattern
bcf STATUS, C
rrf state_a, F
bcf STATUS, C
rrf state_b, F
; Skip B3 since we're using it for PWM
bcf STATUS, C
btfsc state_b, 3
rrf state_b, F
btfsc state_b, 0
incf direction, F
goto write_ports
left:
; 1 = Left-moving pattern
bcf STATUS, C
rlf state_a, F
bcf STATUS, C
rlf state_b, F
btfsc state_b, 5
decf direction, F
; Skip B3 since we're using it for PWM
bcf STATUS, C
btfsc state_b, 3
rlf state_b, F
write_ports:
banksel PORTA
movfw state_a
movwf PORTA
movfw state_b
movwf PORTB
ack:
; Ack the interrupt
banksel INTCON
bcf INTCON, T0IF
retfie
END
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment