The code below running on a 4MHz 16F628 will output PWM from 244Hz to around 6700Hz. This frequency is set by a 47k linear pot. The Duty Cycle is settable with a second 47k linear pot. As the frequency rises, the frequency and duty cycle resolution becomes coarser. The 6700Hz limit is caused primarily by the time needed for the ADC conversion. The 244Hz limit is the PIC speed.

The higher the PWM frequency, the less of the Duty Cycle pot is needed to set a DC between 0% and 100%

Improvements -

Use post-scaler to generate fewer IRQs and therefore fewer ADC readings

Stagger ADC readings to minimise call time to ADC routine (ie read one pot per IRQ, particularly when 10 pots/switches need to be read)

Scale duty cycle pot's reading proportional to frequency so as to use full range of pot - possible use of tables

Reduce PIC speed to lower bottom end frequency - possible use of RC or ExtR with manually variable resistor or digital selection by PIC itself

;tlc549.asm  - read pots and switches via 4051
;              10/02/02  15/04/02
;              mixer.gif
         list p=16F628

;Sample sequence ; CS low (previous MSB at DATA pin)
;                  4 falling edges at I/O to shift out
;                  bits 2,3,4,5 of previous conversion
;                  Sample taken
;                  3 more falling edges to shift out
;                  6,7 & LSB of previous conversion
;                  8th falling I/O holds the sample
;                  CS must go high or I/O must go low
;                  for the chip's 36 internal cycles
;                  Conversion time is 17uS max.
;READ subroutine gets last sample data and performs
;new sample/hold
;Ref+ = 5.12V (Ref- = 0) is 2mV/LSB [Vcc max = 6V]
;Analogue in > Ref+ = 11111111
;Analogue in < Ref- = 00000000

;Read pot postions and modify PWM output
;porta.0  4051 A            o/p
;      1       B            o/p
;      2       C            o/p
;      3  PWM               o/p

;portb.0  start button      i/p
;      1                    o/p
;      2                    o/p
;      3                    o/p
;      4  TLC549  Clock     o/p
;      5          Data      i/p
;      6          CS        o/p
;      7  LED               o/p

trigger equ 0x00       ;start
pout1   equ 0x02
pwm     equ 0x03       ;PWM
clk     equ 0x04       ;ADC Clock
dta     equ 0x05       ;ADC data
cs      equ 0x06       ;ADC Chip Select
led     equ 0x07       ;test point

start  equ 0x00       ;program start vector
ram    equ 0x20       ;start of RAM in bank0

dbuff  equ ram+0x00   ;buffer for serial data
dbit   equ 0x00       ;serial bit value

temp   equ ram+0x01
endlp  equ 0x03       ;"end loop" test bit

delay  equ ram+0x02   ;delay counter

;RAM locations from 0x03 to 0x08 used for pot readings

adc1   equ ram+0x03   ;set PWM duty cycle
adc2   equ ram+0x04   ;set PWM frequency
adc3   equ ram+0x05
adc4   equ ram+0x06
adc5   equ ram+0x07
adc6   equ ram+0x08

;RAM locations from 0x09 to 0x0e used for counters

on1    equ ram+0x09
off1   equ ram+0x0a

wtemp  equ ram+0x10    ;temporary IRQ store for W and STATUS
stemp  equ ram+0x11

tcnt   equ ram+0x12    ;count TMR0 IRQs
change equ ram+0x13
ch     equ 0x00
ashad  equ ram+0x14    ;porta shadow register

       __config _intrc_osc_noclkout & _wdt_off & _pwrte_on & _lvp_off & _boden_off

         org 0x00
         goto entry

         org 0x04
         goto irq

entry    clrf   porta
         movlw  0x07          ;comparators off
         movwf  cmcon
         clrf   status        ;set rp0, rp1 = 0 = Bank0

         bsf    status,rp0    ;tris registers in bank1
         bcf    status,rp1

         movlw  b'00000000'   ;oooo oooo
         movwf  trisa

         movlw  b'00100001'   ;ooio oooo all o/p except Serial Data and Trigger
         movwf  trisb

         movlw  0x86          ;pull-ups off, timer pre-scaler= 128 = 30Hz IRQ
         movwf  option_reg

         bcf    status,rp0
         bcf    status,rp1

         clrf   porta         ;4051 A = B = C = 0
         clrf   ashad         ;clear shadow

         movlw  b'11010000'   ;TLC549 Clock high, CS high
         movwf  portb
         movlw  0x10
         movwf  change
         clrf   fsr

         clrf   ccp1con       ;CCP Module off, set up PWM
         clrf   tmr2

;initial PWM values until first IRQ - low speed, low duty cycle

         movlw  b'11111111'   ;TMR2/PR2 match (frequency) 244-6700Hz
         movwf  pr2

;Tpwm = (PR2+1) x 4 x Tosc x Prescaler
;Tpwm = 256 x 4 x .25us x 16
;Tpwm = 4096us
;f    = 1000000us/4096us = 244Hz

         movlw  b'00000000'   ;duty cycle MSBs
         movwf  ccpr1l

         clrf   intcon        ;clear control registers
         bsf    status,rp0
         clrf   pie1
         bcf    status,rp0
         clrf   pir1

         movlw  b'00011100'   ;duty cycle LSBs <5:4>, xxxx 11xx sets PWM mode
         movwf  ccp1con

         bcf    t2con,t2ckps0 ;pre-scaler, sets PWM frequency
         bsf    t2con,t2ckps1

         bcf    t2con,toutps0 ;post-scaler, sets IRQ frequency
         bcf    t2con,toutps1
         bcf    t2con,toutps2
         bcf    t2con,toutps3

         bsf    t2con,tmr2on  ;Timer2 enabled

         bsf    intcon,peie   ;Peripherals enabled

         bsf    status,rp0
         bsf    pie1,tmr2ie   ;Timer2 IRQ enabled
         bcf    status,rp0

         bcf    pir1,tmr2if   ;clear Timer2 IRQ flag

         bsf    intcon,gie    ;enable interrupts

loop     goto   loop

;        ISR

irq      bcf    pir1,tmr2if

         bsf    portb,led
         bcf    portb,led

         movwf  wtemp
         swapf  status,w
         movwf  stemp

         call   readadc       ;read just first two pots for now

         movf   adc1,w        ;new duty cycle MSB value
         movwf  ccpr1l

         movf   adc2,w        ;new PR2 (ie frequency) value
         bsf    status,rp0
         movwf  pr2
         bcf    status,rp0

         swapf  stemp,w
         movwf  status
         swapf  wtemp,f
         swapf  wtemp,w

;        Read ADC

readadc  clrf   porta         ;reset 4051
         clrf   ashad         ;and shadow
         movlw  0x23          ;RAM address for pot readings
         movwf  fsr

read     call   adcread       ;read last conversion and initiate current
         call   adcread       ;read current conversion

         movf   dbuff,w
         movwf  indf          ;store in RAM+0x03 to RAM+0x08 (adc1 to adc6)

         incf   ashad,f       ;select new pot
         movf   ashad,w
         movwf  porta
         incf   fsr,f         ;destination RAM address

         movlw  0x25          ;test for > 2  ( set number of pots here )
         xorwf  fsr,w
         btfss  status,z
         goto   read
         return               ;back to ISR call

adcread  bcf    portb,cs      ;CS low, puts previous MSB on Data line
         clrf   dbuff         ;wait for TLC549's noise filter

         bsf    dbuff,dbit    ;assume previous MSB bit = 1
         btfss  portb,dta
         bcf    dbuff,dbit    ;or clear if = 0

         movlw  0x01
         movwf  temp          ;bit counter

prev     bcf    status,c
         rlf    dbuff,f       ;move bits through buffer

         bcf    portb,clk     ;get next 7 bits on falling clock edge

         bsf    dbuff,dbit    ;assume bit = 1
         btfss  portb,dta
         bcf    dbuff,dbit    ;or clear if = 0

         bsf    portb,clk     ;clock back high

         incf   temp,f
         btfss  temp,endlp    ;if count limit reached
         goto   prev          ;no

         bcf    portb,clk     ;8th clock, start hold, conversion
         bsf    portb,clk

hold     bsf    portb,cs      ;send CS high
         movlw  0xc0          ;short delay during conversion
         movwf  delay
shdel    incfsz delay,f
         goto   shdel

         return               ;back to READ call