# first attempt at sine wave synthesis # datasheet # https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf # ==================================== # imports import array import time import machine import math # ==================================== #adddressof an array @micropython.asm_thumb # passing an array name to the assembler # actually passes in the address def addressof(r0): # r0 is the output register, so address beomes output # this is really a NOP mov(r0, r0) # === 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 GPIO18_CTRL = 0x094 + IO_base #==== DMA ============================ # register adddresses for DMA # datasheet page 102 DMA_base = 0x50000000 # === DMA0 regs # starting read address DMA_RD_ADDR0 = 0x000 + DMA_base # sets start adddr, then triggers channel DMA_RD_ADDR0_trigger = 0x03c + DMA_base # starting write address DMA_WR_ADDR0 = 0x004 + DMA_base # number off ITEMS (not bytes to trnasffer) DMA_TR_CNT0 = 0x008 + DMA_base # Channel control -- many fields -- see below DMA_CTRL0 = 0x00c + DMA_base # === DMA1 regs # starting read address DMA_RD_ADDR1 = 0x040 + DMA_base # starting write address DMA_WR_ADDR1 = 0x044 + DMA_base # number off ITEMS (not bytes to trnasffer) DMA_TR_CNT1 = 0x048 + DMA_base # Channel control -- many fields -- see below DMA_CTRL1 = 0x04c + 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_ADDR1] = addressof(ptr_to_sine_addr) # load DMA control target address machine.mem32[DMA_WR_ADDR1] = DMA_RD_ADDR0 # just one word transfer machine.mem32[DMA_TR_CNT1] = 1 # the next write starts the transfer data_32 = 0x02 # 32 bit # set up control and start the DMA machine.mem32[DMA_CTRL1] = (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_ADDR0] = addressof(DMA_sine) machine.mem32[DMA_WR_ADDR0] = PWM_ch1_cc machine.mem32[DMA_TR_CNT0] = len(DMA_sine) # 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 machine.mem32[DMA_TIMER0] = 0x000f_7fff # 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_CTRL0] = (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[GPIO18_CTRL] = 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) # =================================== # now wait for a little while for testing time.sleep(8) # 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