N.B., Cannot use 32-bit programmable interrupt timer (PIT) to trigger periodic DMA due to hardware bug.
See here.
The solution shown below uses the 16-bit programmable delay block (PDB).
Disadvantages to using PDB:
Advantages to using PDB:
In [ ]:
import pandas as pd
def get_pdb_divide_params(frequency, F_BUS=int(48e6)):
mult_factor = np.array([1, 10, 20, 40])
prescaler = np.arange(8)
clock_divide = (pd.DataFrame([[i, m, p, m * (1 << p)]
for i, m in enumerate(mult_factor)
for p in prescaler],
columns=['mult_', 'mult_factor',
'prescaler', 'combined'])
.drop_duplicates(subset=['combined'])
.sort_values('combined', ascending=True))
clock_divide['clock_mod'] = (F_BUS / frequency
/ clock_divide.combined).astype(int)
return clock_divide.loc[clock_divide.clock_mod <= 0xffff]
Use linked DMA channels to perform "scan" across multiple ADC input channels.
After each scan, use DMA scatter chain to write the converted ADC values to a
separate output array for each ADC channel. The length of the output array to
allocate for each ADC channel is determined by the sample_count
in the
example below.
See diagram below.
SC1A
configurations to the ADC SC1A
register. Each SC1A
configuration selects an analog input channel.DMA_SSRT = i
), starting the ADC conversion for the first ADC
channel configuration.COCO
), and
copies the output result of the ADC to consecutive locations in the result
array.SC1A
configuration to be loaded immediately
after the current ADC result has been copied to the result array.SC1A
table.
In [ ]:
import arduino_helpers.hardware.teensy as teensy
from arduino_rpc.protobuf import resolve_field_values
from teensy_minimal_rpc import SerialProxy
import teensy_minimal_rpc.DMA as DMA
import teensy_minimal_rpc.ADC as ADC
import teensy_minimal_rpc.SIM as SIM
import teensy_minimal_rpc.PIT as PIT
# Disconnect from existing proxy (if available)
try:
del proxy
except NameError:
pass
proxy = SerialProxy()
proxy.pin_mode(teensy.LED_BUILTIN, 1)
In [ ]:
from IPython.display import display
proxy.update_sim_SCGC6(SIM.R_SCGC6(PDB=True))
sim_scgc6 = SIM.R_SCGC6.FromString(proxy.read_sim_SCGC6().tostring())
display(resolve_field_values(sim_scgc6)[['full_name', 'value']].T)
In [ ]:
dma_channel_scatter = 0
dma_channel_i = 1
dma_channel_ii = 2
In [ ]:
import numpy as np
PDB0_IDLY = 0x4003600C # Interrupt Delay Register
PDB0_SC = 0x40036000 # Status and Control Register
PDB0_MOD = 0x40036004 # Modulus Register
PDB_SC_PDBEIE = 0x00020000 # Sequence Error Interrupt Enable
PDB_SC_SWTRIG = 0x00010000 # Software Trigger
PDB_SC_DMAEN = 0x00008000 # DMA Enable
PDB_SC_PDBEN = 0x00000080 # PDB Enable
PDB_SC_PDBIF = 0x00000040 # PDB Interrupt Flag
PDB_SC_PDBIE = 0x00000020 # PDB Interrupt Enable.
PDB_SC_CONT = 0x00000002 # Continuous Mode Enable
PDB_SC_LDOK = 0x00000001 # Load OK
def PDB_SC_TRGSEL(n): return (((n) & 15) << 8) # Trigger Input Source Select
def PDB_SC_PRESCALER(n): return (((n) & 7) << 12) # Prescaler Divider Select
def PDB_SC_MULT(n): return (((n) & 3) << 2) # Multiplication Factor
def PDB_SC_LDMOD(n): return (((n) & 3) << 18) # Load Mode Select
# PDB0_IDLY = 1; // the pdb interrupt happens when IDLY is equal to CNT+1
proxy.mem_cpy_host_to_device(PDB0_IDLY, np.uint32(1).tostring())
# software trigger enable PDB continuous
PDB_CONFIG = (PDB_SC_TRGSEL(15) | PDB_SC_PDBEN | PDB_SC_CONT | PDB_SC_LDMOD(0))
clock_divide = get_pdb_divide_params(1).iloc[0]
PDB0_SC_ = (PDB_CONFIG | PDB_SC_PRESCALER(clock_divide.prescaler) |
PDB_SC_MULT(clock_divide.mult_) |
PDB_SC_DMAEN | PDB_SC_LDOK) # load all new values
proxy.mem_cpy_host_to_device(PDB0_SC, np.uint32(PDB0_SC_).tostring())
# PDB0_MOD = (uint16_t)(mod-1);
proxy.mem_cpy_host_to_device(PDB0_MOD, np.uint32(clock_divide.clock_mod).tostring())
In [ ]:
# Set ADC parameters
proxy.setAveraging(4, teensy.ADC_0)
proxy.setResolution(10, teensy.ADC_0)
proxy.setConversionSpeed(teensy.ADC_MED_SPEED, teensy.ADC_0)
proxy.setSamplingSpeed(teensy.ADC_MED_SPEED, teensy.ADC_0)
proxy.update_adc_registers(
teensy.ADC_0,
ADC.Registers(CFG2=ADC.R_CFG2(MUXSEL=ADC.R_CFG2.B)))
Pseudo-code to set DMA channel $i$ to be triggered by ADC0 conversion complete.
DMAMUX0_CFGi[SOURCE] = DMAMUX_SOURCE_ADC0 // Route ADC0 as DMA channel source.
DMAMUX0_CFGi[TRIG] = 0 // Disable periodic trigger.
DMAMUX0_CFGi[ENBL] = 1 // Enable the DMAMUX configuration for channel.
DMA_ERQ[i] = 1 // DMA request input signals and this enable request flag
// must be asserted before a channel’s hardware service
// request is accepted (21.3.3/394).
DMA_SERQ = i // Can use memory mapped convenience register to set instead.
In [ ]:
DMAMUX_SOURCE_ADC0 = 40 # from `kinetis.h`
DMAMUX_SOURCE_ADC1 = 41 # from `kinetis.h`
# DMAMUX0_CFGi[SOURCE] = DMAMUX_SOURCE_ADC0 // Route ADC0 as DMA channel source.
# DMAMUX0_CFGi[TRIG] = 0 // Disable periodic trigger.
# DMAMUX0_CFGi[ENBL] = 1 // Enable the DMAMUX configuration for channel.
proxy.update_dma_mux_chcfg(dma_channel_ii,
DMA.MUX_CHCFG(SOURCE=DMAMUX_SOURCE_ADC0,
TRIG=False,
ENBL=True))
proxy.enableDMA(teensy.ADC_0)
In [ ]:
proxy.DMA_registers().loc['']
In [ ]:
import re
import numpy as np
import pandas as pd
import arduino_helpers.hardware.teensy.adc as adc
# The number of samples to record for each ADC channel.
sample_count = 32
teensy_analog_channels = ['A0', 'A1', 'A0', 'A3', 'A0']
sc1a_pins = pd.Series(dict([(v, adc.CHANNEL_TO_SC1A_ADC0[getattr(teensy, v)])
for v in dir(teensy) if re.search(r'^A\d+', v)]))
channel_sc1as = np.array(sc1a_pins[teensy_analog_channels].tolist(), dtype='uint32')
In [ ]:
proxy.free_all()
N = np.dtype('uint16').itemsize * channel_sc1as.size
# Allocate source array
adc_result_addr = proxy.mem_alloc(N)
# Fill result array with zeros
proxy.mem_fill_uint8(adc_result_addr, 0, N)
# Copy channel SC1A configurations to device memory
adc_sda1s_addr = proxy.mem_aligned_alloc_and_set(4, channel_sc1as.view('uint8'))
# Allocate source array
samples_addr = proxy.mem_alloc(sample_count * N)
tcds_addr = proxy.mem_aligned_alloc(32, sample_count * 32)
hw_tcds_addr = 0x40009000
tcd_addrs = [tcds_addr + 32 * i for i in xrange(sample_count)]
hw_tcd_addrs = [hw_tcds_addr + 32 * i for i in xrange(sample_count)]
# Fill result array with zeros
proxy.mem_fill_uint8(samples_addr, 0, sample_count * N)
# Create Transfer Control Descriptor configuration for first chunk, encoded
# as a Protocol Buffer message.
tcd0_msg = DMA.TCD(CITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ITER=1),
BITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ITER=1),
ATTR=DMA.R_TCD_ATTR(SSIZE=DMA.R_TCD_ATTR._16_BIT,
DSIZE=DMA.R_TCD_ATTR._16_BIT),
NBYTES_MLNO=channel_sc1as.size * 2,
SADDR=int(adc_result_addr),
SOFF=2,
SLAST=-channel_sc1as.size * 2,
DADDR=int(samples_addr),
DOFF=2 * sample_count,
DLASTSGA=int(tcd_addrs[1]),
CSR=DMA.R_TCD_CSR(START=0, DONE=False, ESG=True))
# Convert Protocol Buffer encoded TCD to bytes structure.
tcd0 = proxy.tcd_msg_to_struct(tcd0_msg)
# Create binary TCD struct for each TCD protobuf message and copy to device
# memory.
for i in xrange(sample_count):
tcd_i = tcd0.copy()
tcd_i['SADDR'] = adc_result_addr
tcd_i['DADDR'] = samples_addr + 2 * i
tcd_i['DLASTSGA'] = tcd_addrs[(i + 1) % len(tcd_addrs)]
tcd_i['CSR'] |= (1 << 4)
if i == (sample_count - 1): # Last sample, so trigger major loop interrupt
print 'Enable major loop interrupt for sample %d' % i
tcd_i['CSR'] |= (1 << 1) # Set `INTMAJOR` (21.3.29/426)
proxy.mem_cpy_host_to_device(tcd_addrs[i], tcd_i.tostring())
# Load initial TCD in scatter chain to DMA channel chosen to handle scattering.
proxy.mem_cpy_host_to_device(hw_tcd_addrs[dma_channel_scatter],
tcd0.tostring())
proxy.attach_dma_interrupt(dma_channel_scatter)
print 'ADC results:', proxy.mem_cpy_device_to_host(adc_result_addr, N).view('uint16')
print 'Analog pins:', proxy.mem_cpy_device_to_host(adc_sda1s_addr, len(channel_sc1as) *
channel_sc1as.dtype.itemsize).view('uint32')
In [ ]:
ADC0_SC1A = 0x4003B000 # ADC status and control registers 1
sda1_tcd_msg = DMA.TCD(CITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ELINK=False, ITER=channel_sc1as.size),
BITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ELINK=False, ITER=channel_sc1as.size),
ATTR=DMA.R_TCD_ATTR(SSIZE=DMA.R_TCD_ATTR._32_BIT,
DSIZE=DMA.R_TCD_ATTR._32_BIT),
NBYTES_MLNO=4,
SADDR=int(adc_sda1s_addr),
SOFF=4,
SLAST=-channel_sc1as.size * 4,
DADDR=int(ADC0_SC1A),
DOFF=0,
DLASTSGA=0,
CSR=DMA.R_TCD_CSR(START=0, DONE=False))
proxy.update_dma_TCD(dma_channel_i, sda1_tcd_msg)
In [ ]:
ADC0_RA = 0x4003B010 # ADC data result register
ADC0_RB = 0x4003B014 # ADC data result register
tcd_msg = DMA.TCD(CITER_ELINKYES=DMA.R_TCD_ITER_ELINKYES(ELINK=True, LINKCH=1, ITER=channel_sc1as.size),
BITER_ELINKYES=DMA.R_TCD_ITER_ELINKYES(ELINK=True, LINKCH=1, ITER=channel_sc1as.size),
ATTR=DMA.R_TCD_ATTR(SSIZE=DMA.R_TCD_ATTR._16_BIT,
DSIZE=DMA.R_TCD_ATTR._16_BIT),
NBYTES_MLNO=2,
SADDR=ADC0_RA,
SOFF=0,
SLAST=0,
DADDR=int(adc_result_addr),
DOFF=2,
DLASTSGA=-channel_sc1as.size * 2,
CSR=DMA.R_TCD_CSR(START=0, DONE=False,
MAJORELINK=True,
MAJORLINKCH=dma_channel_scatter))
proxy.update_dma_TCD(dma_channel_ii, tcd_msg)
# DMA request input signals and this enable request flag
# must be asserted before a channel’s hardware service
# request is accepted (21.3.3/394).
# DMA_SERQ = i
proxy.update_dma_registers(DMA.Registers(SERQ=dma_channel_ii))
In [ ]:
# Clear output array to zero.
proxy.mem_fill_uint8(adc_result_addr, 0, N)
proxy.mem_fill_uint8(samples_addr, 0, sample_count * N)
In [ ]:
# Software trigger channel $i$ to copy *first* SC1A configuration, which
# starts ADC conversion for the first channel.
#
# Conversions for subsequent ADC channels are triggered through minor-loop
# linking from DMA channel $ii$ to DMA channel $i$ (*not* through explicit
# software trigger).
print 'ADC results:'
for i in xrange(sample_count):
proxy.update_dma_registers(DMA.Registers(SSRT=dma_channel_i))
# Display converted ADC values (one value per channel in `channel_sd1as` list).
print ' Iteration %s:' % i, proxy.mem_cpy_device_to_host(adc_result_addr, N).view('uint16')
print ''
In [ ]:
proxy.update_dma_mux_chcfg(dma_channel_i,
DMA.MUX_CHCFG(SOURCE=48,
TRIG=False,
ENBL=True))
proxy.update_dma_registers(DMA.Registers(SERQ=dma_channel_i))
In [ ]:
%matplotlib inline
In [ ]:
PDB0_SC_ = 0
proxy.mem_cpy_host_to_device(PDB0_SC, np.uint32(PDB0_SC_).tostring())
In [ ]:
proxy.mem_cpy_device_to_host(PDB0_SC, 4)
In [ ]:
# Set sampling frequency
f_sample = 150e3
# Determine timing parameters to meet specified sampling frequency.
clock_divide = get_pdb_divide_params(f_sample).iloc[0]
# Configure Programmable Delay Block (PDB) register state for sampling frequency.
PDB0_SC_ = (PDB_CONFIG | PDB_SC_PRESCALER(clock_divide.prescaler) |
PDB_SC_MULT(clock_divide.mult_) |
PDB_SC_DMAEN | PDB_SC_LDOK) # load all new values
proxy.mem_cpy_host_to_device(PDB0_SC, np.uint32(PDB0_SC_).tostring())
PDB0_SC_ = (PDB_CONFIG | PDB_SC_PRESCALER(clock_divide.prescaler) |
PDB_SC_DMAEN | PDB_SC_MULT(clock_divide.mult_) |
PDB_SC_SWTRIG) # start the counter!
# Copy configured PDB register state to device hardware register.
proxy.mem_cpy_host_to_device(PDB0_SC, np.uint32(PDB0_SC_).tostring())
# **N.B.,** Timer will be stopped by the scatter DMA channel major loop interrupt
# handler after `sample_count` samples have been collected.
In [ ]:
print 'Samples by channel:'
device_dst_data = proxy.mem_cpy_device_to_host(samples_addr, sample_count * N)
df_adc_results = pd.DataFrame(device_dst_data.view('uint16').reshape(-1, sample_count).T,
columns=teensy_analog_channels)
df_adc_results.plot(ylim=(-5, 1030))
# df_adc_results
In [ ]:
proxy.last_dma_channel_done()
In [ ]:
proxy.DMA_registers().loc['']
In [ ]: