# first attempt at sine wave synthesis # datasheet # https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf # ==================================== # imports import array import time import machine import math from ctypes import addressof # === IO ============================= # define low level i/o # Function select -- datasheeet section 2.19.2 IO_base = 0x40014000 # list of registrs sectionn 2.19.6 # page 268 has bit defines of registers def GPIO_CTRL(chan_num): return chan_num*8 + 4 + IO_base def GPIO_STATUS(chan_num): return chan_num*8 + IO_base #==== DMA ============================ # register adddresses for DMA # datasheet page 102 DMA_base = 0x50000000 # =================================== # === DMA channel control registers # valid channels: 0 to 11 def DMA_RD_ADDR(ch_num): return (0x40*ch_num + DMA_base) def DMA_WR_ADDR(ch_num): return (0x40*ch_num + 0x04 + DMA_base) def DMA_TR_COUNT(ch_num): return (0x40*ch_num + 0x08 + DMA_base) def DMA_CTRL(ch_num): return (0x40*ch_num + 0x0c + DMA_base) # === Kill channels # kill the channels by writing one to the bit number of thh channel DMA_Ch_ABORT = 0x444 + DMA_base # pacing timer The pacing timer produces TREQ assertions # at a rate set by ((X/Y) * sys_clk)<1 # Where X is top 16 bits, Y the bottom 16 bits DMA_TIMER0 = 0x420 + DMA_base # # === bits in ctrl register # busy during transfer DMA_BUSY = (1<<24) # bit 24 high when busy # turn off channel done IRQ DMA_IRQ_QUIET = (1<<21) # bit 21 turn off interrupt # Triggger Source table page 96 section 2.5.3.1 # More tirgger info page 114 # 0x0 to 0x3a → select DREQ n as TREQ from table above # 0x3b → Select Timer 0 as TREQ # 0x3c → Select Timer 1 as TREQ # 0x3d → Select Timer 2 as TREQ (Optional) # 0x3e → Select Timer 3 as TREQ (Optional) # 0x3f → Permanent request, for unpaced transfers. # bits 15:20 trigger request source def DMA_TREQ(trigger_source): return (trigger_source & 0x3f)<<15 # When this channel completes, it will trigger the channel # indicated by CHAIN_TO. Disable by setting CHAIN_TO = # (this channel). def DMA_CHAIN_TO (next_ch): return (next_ch & 0x0f)<<11 # bits 11:14 next chnnel # # Select whether RING_SIZE applies to read or writeaddresses. # If 0, read addresses are wrapped on a (1 << RING_SIZE) # boundary. If 1, write addresses are wrapped. DMA_RING_SEL = (1<<10) # bits 10 -- loop on write addr # If 1, write addresses are wrapped. # Ring sizes between 2 and 32768 BYTES are possible. This # can apply to either read or write addresses, based on # value of RING_SEL. NOTE BYTES, not words # 0x0 → RING_NONE def DMA_RING_SIZE(ring_size): return (ring_size & 0x0f)<<6 # bits 6:9 # increment or keep constant wrrite nd read addr # useful for peripheril write/read DMA_WR_INC = (1<<5) # bits 5 DMA_RD_INC = (1<<4) # bits 4 # Set the size of each bus transfer (byte/halfword/word). # READ_ADDR and WRITE_ADDR advance by this amount # (1/2/4 bytes) with each transfer. # 0x0 → SIZE_BYTE # 0x1 → SIZE_HALFWORD # 0x2 → SIZE_WORD def DMA_DATA_WIDTH(data_width): return (data_width & 0x03)<<2 # bits 2:3 # give this channel more access if several channels aare on DMA_HIGH_PRI = (1<<1) # bit 1 # turn on the channel DMA_EN = 1 # bits 0 # === PWM ============================ # ssection 4.5 PWM_base = 0x40050000 # 4.5.3. List of Registers PWM_ch1_csr = 0x14 + PWM_base # bit fields for CSR # 0x0 → Free-running counting at rate by fractional divider # 0x1 → Fractional divider operation is gated by PWM B pin. # 0x2 → Counter advances with each rising edge of PWM B pin. # 0x3 → Counter advances with each falling edge of PWM B pin. def PWM_divmode(mode): return (mode & 0x03)<<4 # invert channel B PWM_B_inv = 1<<3 # invert channel A PWM_A_inv = 1<<2 # phase correct PWM_ph_correct = 1<<1 # enable channel PWM_EN = 1 # # divide register # 12-bit fixed point # with binary point betweem bit3 and bit4 PWM_ch1_div = 0x18 + PWM_base # the actual PWM 16-bit couonter PWM_ch1_ctr = 0x1c + PWM_base # counter compare 31:16 B, 15:0 A PWM_ch1_cc = 0x20 + PWM_base # counter wrap 16 bit value PWM_ch1_top = 0x24 + PWM_base # GPIO 18 is PWM slice 1A # === define sine array ============== # DMA ch0 source for PWM DMA_sine = array.array('i',[0]*256) i = 0 while i<(256): DMA_sine[i] = int(127 * math.sin(2*math.pi*i/256) + 128) #DMA_sine[i] = i i += 1 # DMA ch1 source for the address of the adddress of the array ptr_to_sine_addr = array.array('i',[addressof(DMA_sine)]) # ==================================== # DMA setup # === Channel 1 transfer the ch0 strt address to Ch0 contrl block machine.mem32[DMA_RD_ADDR(1)] = addressof(ptr_to_sine_addr) # load DMA control target address machine.mem32[DMA_WR_ADDR(1)] = DMA_RD_ADDR(0) # just one word transfer machine.mem32[DMA_TR_COUNT(1)] = 1 # the next write starts the transfer data_32 = 0x02 # 32 bit # set up control and start the DMA machine.mem32[DMA_CTRL(1)] = (DMA_IRQ_QUIET | #DMA_TREQ(perm_trig) | #DMA_WR_INC | #DMA_RD_INC | DMA_DATA_WIDTH(data_32) | DMA_CHAIN_TO(0) | DMA_EN ) # # === Channel 0 transfer the aray to the PWM target machine.mem32[DMA_RD_ADDR(0)] = addressof(DMA_sine) machine.mem32[DMA_WR_ADDR(0)] = PWM_ch1_cc machine.mem32[DMA_TR_COUNT(0)] = len(DMA_sine) # === set up the timer for a given frequency === # timer X=0x00ff, timer Y=0xfff0 # freq resolution for denom=0xffff: # 1/(0xffff) * 125000000 / 256 = 7.45 Hz # dropping the denom to 0xfd70 raises the frequency 1% # dropping the denom to 0x7fff raises the frequency x2 # useful range 7 Hz to > 15 KHz # This iterative calculation Sets the numerator # and denom of timer register by Tylor series expansion Fout = 440 # actual desired frequency Ltable = 256 # sine table length Fclk = 125e6 # cpu clock ffreq # get exact X X0 = (Fout/Fclk) * Ltable * 2**16 # compute actual freq using integger part of X Fx = Fclk*int(X0)/(Ltable * 2**16) # Taylor expanson for delta Y dY = (Fout-Fx) * Ltable * 2**32 /(Fclk * int(X0)) machine.mem32[DMA_TIMER0] = (0xffff-int(dY)) | (int(X0)<<16) # now the conntrol registerr perm_trig = 0x3f # alwas go DREQ_PWM_WRAP1 = 25 # dat request souce number PWM slice 1 DREQ_TIMER0 = 0x3b # dat request souce number Timer0 data_32 = 0x02 # 32 bit data_16 = 0x01 # 16 bit # set up control and start the DMA # triggers on DMA timer 0 event machine.mem32[DMA_CTRL(0)] = (DMA_IRQ_QUIET | DMA_TREQ(DREQ_TIMER0) | #DMA_WR_INC | DMA_RD_INC | DMA_DATA_WIDTH(data_32) | DMA_CHAIN_TO(1) | DMA_EN ) # # ==================================== # PWM setup PWM channel 1 GPIO18_CTRL function select # every overflow triggers the ch0 DMA machine.mem32[GPIO_CTRL(18)] = 4 # pwm function # divider of value unity clocks at cpu rate machine.mem32[PWM_ch1_div] = 1<<4 # binary 1 in 8.4 fixed pt machine.mem32[PWM_ch1_top] = 256 # 8-bit pwm machine.mem32[PWM_ch1_cc] = 128 #50% for testing machine.mem32[PWM_ch1_ctr] = 0 # zero the phase free_run = 0 machine.mem32[PWM_ch1_csr] = PWM_EN | PWM_divmode(free_run) # =================================== # set the frequency while True : Fout = float(input('frequency:' )) if Fout == 0 : break #get exact X X0 = (Fout/Fclk) * Ltable * 2**16 # compute actual freq using integger part of X Fx = Fclk*int(X0)/(Ltable * 2**16) # Taylor expanson for delta Y dY = (Fout-Fx) * Ltable * 2**32 /(Fclk * int(X0)) machine.mem32[DMA_TIMER0] = (0xffff-int(dY)) | (int(X0)<<16) # now wait for a little while for testing # kill PWM machine.mem32[PWM_ch1_csr] = 0 #kill channel 0 and 1 DMA_CH_ABORT = 0x0f # set bit 0 to kill ch0 and ch1 ## ## end