From 9920661516fa5658401ecec090e71d4219253f81 Mon Sep 17 00:00:00 2001 From: occheung Date: Mon, 13 Jun 2022 17:27:32 +0800 Subject: [PATCH] 2245: add dio_spi examples --- 2245.tex | 199 +++++++++++++++++++++++++++++++++++++++++++++++- examples/ttl.py | 47 ++++++++++++ 2 files changed, 243 insertions(+), 3 deletions(-) diff --git a/2245.tex b/2245.tex index ca301fa..e4ac475 100644 --- a/2245.tex +++ b/2245.tex @@ -18,7 +18,10 @@ \usepackage{pgfplots} \usepackage{circuitikz} \usetikzlibrary{calc} -\usetikzlibrary{fit,backgrounds} +\usetikzlibrary{fit,backgrounds} + +\usepackage{tikz-timing} +\usetikztiminglibrary{counters} \title{2245 LVDS-TTL} \author{M-Labs Limited} @@ -67,9 +70,9 @@ Only shielded Ethernet Cat-6 cables should be connected. \newcommand*{\MyLabel}[3][2cm]{\parbox{#1}{\centering #2 \\ #3}} \newcommand*{\MymyLabel}[3][4cm]{\parbox{#1}{\centering #2 \\ #3}} -\newcommand{\inputcolorboxminted}[2]{% +\newcommand{\inputcolorboxminted}[3][4]{% \begin{tcolorbox}[colback=white] - \inputminted[#1, gobble=4]{python}{#2} + \inputminted[#2, gobble=#1]{python}{#3} \end{tcolorbox} } @@ -489,6 +492,196 @@ If the gateware counter is enabled on the TTL channel, it can typically count up One channel needs to be configured as input, and the other as output. \inputcolorboxminted{firstline=74,lastline=80}{examples/ttl.py} +\newcommand{\wrapspacer}[1]% #1 = special text +{\bgroup + \sbox0{\begin{minipage}{\linewidth}\hrule height0pt + #1\hrule height0pt + \end{minipage}}% + \dimen0=\dimexpr \ht0+\dp0\relax + \loop\ifdim\dimen0>\baselineskip + \strut\vspace{-\baselineskip}\newline + \advance\dimen0 by -\baselineskip + \repeat + \noindent\strut\usebox0\par +\egroup} + +\newpage +\subsection{SPI Master Device} +If a EEM port is configured as \texttt{dio\char`_spi} instead of \texttt{dio}, its associated TTL channels can be configured as SPI master devices. +Invocation of an SPI transfer follows this pattern: +\begin{enumerate} + % The config register can be set using set_config. + % However, the only difference between these 2 methods is that set_config accepts an arbitrary + % frequency, then translate into the rough frequency divisor for set_config_mu. + % It doesn't guarantee such frequency would be set as the SPI frequency + + % In addition, finding clock division is quite easy. set_config_mu seems to be a more + % straight-forward & representative of the actual implementation. + \item Set the \texttt{config} register (e.g. using \texttt{set\char`_config\char`_mu()}). + \item Start the SPI transfer by writing the \texttt{data} register using \texttt{write()}. + \item If the data from the SPI slave is to be read (i.e. \texttt{SPI\char`_INPUT} was set in \texttt{config}), invoke \texttt{read()} to read. + +\end{enumerate} + +The list of configurations supported in the gateware are listed as below: + +\begin{table}[h] + \centering + \begin{tabular}{|c|l|} + \hline + Flag & Description \\ \hline + \texttt{SPI\char`_OFFLINE} & Switch all pins to high impedance mode. \\ \hline + \texttt{SPI\char`_END} & Next SPI transfer is the last of the transcation. \\ \hline + \texttt{SPI\char`_INPUT} & Submit SPI read data as RTIO input event when the transfer is complete. \\ \hline + \texttt{SPI\char`_CS\char`_POLARITY} & Active level of chip select (CS) \\ \hline + \texttt{SPI\char`_CLK\char`_POLARITY} & Idle level of serial clock (SCK) \\ \hline + \texttt{SPI\char`_CLK\char`_PHASE} & Data is sampled on falling edge \& shifted out on rising edge. \\ \hline + \texttt{SPI\char`_LSB\char`_FIRST} & LSB is the first on bit on the wire. \\ \hline + \texttt{SPI\char`_HALF\char`_DUPLEX} & Use 3-wire SPI, where MOSI is both input \& output capable. \\ \hline + \end{tabular} +\end{table} + +The following ARTIQ example demonstrates the flow of an SPI transcation with a typical SPI setup with 3 homogeneous slaves. +The direction switches on the LVDS-TTL card should be set to the correct IO direction for all relevant channels before powering on. +\begin{center} + \begin{circuitikz}[european, scale=1, every label/.append style={align=center}] + % SPI master + \draw (0, 1.8) node[twoportshape, t={}, circuitikz/bipoles/twoport/width=2.8, circuitikz/bipoles/twoport/height=2, scale=1] (master) {}; + \node [label={center:\large{SPI Master}}] at (-0.6, 2.05) {}; + \node [label={center:\large{(LVDS-TTL)}}] at (-0.6, 1.55) {}; + \node [label=left:{SCK}] at (2, 2.8) {}; + \node [label=left:{MOSI}] at (2, 2.4) {}; + \node [label=left:{MISO}] at (2, 2.0) {}; + \node [label=left:{CS0}] at (2, 1.6) {}; + \node [label=left:{CS1}] at (2, 1.2) {}; + \node [label=left:{CS2}] at (2, 0.8) {}; + + % SPI slaves + % The top one will have its SCK, MOSI, MISO aligned with the master, for wiring simplicity + \draw (7, 2.2) node[twoportshape, t={}, circuitikz/bipoles/twoport/width=2.8, circuitikz/bipoles/twoport/height=1.4, scale=1] (slave0) {}; + \node [label={center:\large{SPI Slave 0}}] at (7.6, 2.2) {}; + \node [label=right:{SCK}] at (5, 2.8) {}; + \node [label=right:{MOSI}] at (5, 2.4) {}; + \node [label=right:{MISO}] at (5, 2.0) {}; + \node [label=right:{$\mathrm{\overline{CS}}$}] at (5, 1.6) {}; + + % The top one will have its SCK, MOSI, MISO aligned with the master, for wiring simplicity + \draw (7, 0) node[twoportshape, t={}, circuitikz/bipoles/twoport/width=2.8, circuitikz/bipoles/twoport/height=1.4, scale=1] (slave1) {}; + \node [label={center:\large{SPI Slave 1}}] at (7.6, 0) {}; + \node [label=right:{SCK}] at (5, 0.6) {}; + \node [label=right:{MOSI}] at (5, 0.2) {}; + \node [label=right:{MISO}] at (5, -0.2) {}; + \node [label=right:{$\mathrm{\overline{CS}}$}] at (5, -0.6) {}; + + % The top one will have its SCK, MOSI, MISO aligned with the master, for wiring simplicity + \draw (7, -2.2) node[twoportshape, t={}, circuitikz/bipoles/twoport/width=2.8, circuitikz/bipoles/twoport/height=1.4, scale=1] (slave2) {}; + \node [label={center:\large{SPI Slave 2}}] at (7.6, -2.2) {}; + \node [label=right:{SCK}] at (5, -1.6) {}; + \node [label=right:{MOSI}] at (5, -2.0) {}; + \node [label=right:{MISO}] at (5, -2.4) {}; + \node [label=right:{$\mathrm{\overline{CS}}$}] at (5, -2.8) {}; + + % Connect the master to slave 0 + \draw [-latexslim] (1.95, 2.8) -- (5.05, 2.8); + \draw [-latexslim] (1.95, 2.4) -- (5.05, 2.4); + \draw [latexslim-] (1.95, 2.0) -- (5.05, 2.0); + \draw [-latexslim] (1.95, 1.6) -- (5.05, 1.6); + + % Connect slave 1 + \draw [-latexslim] (4.2, 2.8) -- (4.2, 0.6) -- (5.05, 0.6); + \draw [-latexslim] (3.8, 2.4) -- (3.8, 0.2) -- (5.05, 0.2); + \draw [-] (3.4, 2.0) -- (3.4, -0.2) -- (5.05, -0.2); + \draw [-latexslim] (1.95, 1.2) -- (3.0, 1.2) -- (3.0, -0.6) -- (5.05, -0.6); + + % Connect slave 2 + \draw [-latexslim] (4.2, 0.6) -- (4.2, -1.6) -- (5.05, -1.6); + \draw [-latexslim] (3.8, 0.2) -- (3.8, -2.0) -- (5.05, -2.0); + \draw [-] (3.4, -0.2) -- (3.4, -2.4) -- (5.05, -2.4); + \draw [-latexslim] (1.95, 0.8) -- (2.6, 0.8) -- (2.6, -2.8) -- (5.05, -2.8); + + % Add dot to intersection to distinguish from overlaps + \node at (4.2, 2.8)[circle,fill,inner sep=0.7pt]{}; + \node at (3.8, 2.4)[circle,fill,inner sep=0.7pt]{}; + \node at (3.4, 2.0)[circle,fill,inner sep=0.7pt]{}; + \node at (4.2, 0.6)[circle,fill,inner sep=0.7pt]{}; + \node at (3.8, 0.2)[circle,fill,inner sep=0.7pt]{}; + \node at (3.4, -0.2)[circle,fill,inner sep=0.7pt]{}; + + \end{circuitikz} +\end{center} + +\newpage +\subsubsection{SPI Configuration} +The following examples will assume the SPI communication has the following properties: +\begin{itemize} + \item Chip select (CS) is active low + \item Serial clock (SCK) idle level is low + \item Data is sampled on rising edge of SCK \& shifted out on falling edge of SCK + \item Most significant bit (MSB) first + \item Full duplex +\end{itemize} +The base line configuration for an \texttt{SPIMaster} instance can be defined as such: +\inputcolorboxminted[0]{firstline=105,lastline=110}{examples/ttl.py} +The \texttt{SPI\char`_END} \& \texttt{SPI\char`_INPUT} flags will be modified during runtime in the following example. + +\subsubsection{SPI frequency} +Frequency of the SPI clock must be the result of RTIO clock frequency divided by an integer factor from [2, 257]. +In the folowing examples, the SPI frequency will be set to 1 MHz by dividing the RTIO frequency (125 MHz) by 125. +\inputcolorboxminted[0]{firstline=112,lastline=112}{examples/ttl.py} + +\subsubsection{SPI write} +Typically, an SPI write operation involves sending an instruction and data to the SPI slaves. +Suppose the instruction and data are 8 bits and 32 bits respectively. +The timing diagram of such write operation is shown in the following. + +\begin{center} +\begin{tikztimingtable} + [ + timing/d/background/.style={fill=white}, + timing/lslope=0.2 + ] + $\mathrm{\overline{CS}}$ & H51{L}H \\ + SCK & LL31{T}; 2{[dotted] T}; 17{T} L \\ + % The better approach is to use pass the counter (\tikztimingcounter{Q}) to a macro, + % then print the label from macro. But it turns out tikz-timing will print + % the counter value separately, even with an additional macro. + % Therefore, it does not work properly. + MOSI & [timing/counter/new={char=I, reset char=J, reset type=arg, increment=-1, text format=I}, timing/counter/new={char=A, reset char=R, reset type=arg, increment=-1, text format=D}] + UJ{7}8{2I}R{31}8{2A}; [dotted] D [dotted] D{}; R{7}8{2A}2U \\ + MOSI & 53U \\ +\end{tikztimingtable}% +\end{center} + +\newpage +Suppose the instruction is \texttt{0x13}, while the data is \texttt{0xDEADBEEF}. In addition, both slave 1 \& 2 are selected. This SPI transcation can be performed by the following code. +\inputcolorboxminted{firstline=119,lastline=128}{examples/ttl.py} + +\subsubsection{SPI read} +A 32-bits read is represented by the following timing diagram. + +\begin{center} +\begin{tikztimingtable} + [ + timing/d/background/.style={fill=white}, + timing/lslope=0.2 + ] + $\mathrm{\overline{CS}}$ & H51{L}H \\ + SCK & LL31{T}; 2{[dotted] T}; 17{T} L \\ + % The better approach is to use pass the counter (\tikztimingcounter{Q}) to a macro, + % then print the label from macro. But it turns out tikz-timing will print + % the counter value separately, even with an additional macro. + % Therefore, it does not work properly. + MOSI & [timing/counter/new={char=I, reset char=J, reset type=arg, increment=-1, text format=I}] + UJ{7}8{2I}36U \\ + MOSI & [timing/counter/new={char=A, reset char=R, reset type=arg, increment=-1, text format=D}] + 17UR{31}8{2A}; [dotted] D [dotted] D{}; R{7}8{2A}2U \\ +\end{tikztimingtable}% +\end{center} + +Suppose the instruction is \texttt{0x81}, where only slave 0 is selected. This SPI transcation can be performed by the following code. +\inputcolorboxminted{firstline=136,lastline=150}{examples/ttl.py} + +\newpage \section{Ordering Information} To order, please visit \url{https://m-labs.hk} and select the 2245 LVDS-TTL in the ARTIQ Sinara crate configuration tool. The card may also be ordered separately by writing to \url{mailto:sales@m-labs.hk}. diff --git a/examples/ttl.py b/examples/ttl.py index c86ad50..aba0f04 100644 --- a/examples/ttl.py +++ b/examples/ttl.py @@ -101,3 +101,50 @@ class ClockGen(EnvExperiment): def run(self): self.core.reset() self.ttl0.set(62.5*MHz) + +from artiq.coredevice import spi2 as spi + +SPI_CONFIG = (0 * spi.SPI_OFFLINE | 0 * spi.SPI_END | + 0 * spi.SPI_INPUT | 0 * spi.SPI_CS_POLARITY | + 0 * spi.SPI_CLK_POLARITY | 0 * spi.SPI_CLK_PHASE | + 0 * spi.SPI_LSB_FIRST | 0 * spi.SPI_HALF_DUPLEX) + +CLK_DIV = 125 + +class SPIWrite(EnvExperiment): + def build(self): + self.setattr_device("core") + self.spi = self.get_device("dio_spi0") + + @kernel + def run(self): + self.core.reset() + self.spi.set_config_mu(SPI_CONFIG, 8, CLK_DIV, 0b110) + self.spi.write(0x13 << 24) # Shift the bits to the MSBs. + # Since SPI_LSB_FIRST is NOT set, + # SPI Machine will shift out bits from + # the MSB of the `data` register.` + self.spi.set_config_mu(SPI_CONFIG | spi.SPI_END, 32, CLK_DIV, 0b110) + self.spi.write(0xDEADBEEF) + + +class SPIRead(EnvExperiment): + def build(self): + self.setattr_device("core") + self.spi = self.get_device("dio_spi0") + + @kernel + def run(self): + self.core.reset() + self.spi.set_config_mu(SPI_CONFIG, 8, CLK_DIV, 0b001) + self.spi.write(0x81 << 24) # Shift the bits to the MSBs. + # Since SPI_LSB_FIRST is NOT set, + # SPI Machine will shift out bits from + # the MSB of the `data` register.` + self.spi.set_config_mu(SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, + 32, CLK_DIV, 0b001) + self.spi.write(0) # write() performs the SPI transfer. + # As suggested by the timing diagram, + # the exact value of this argument + # does not matter. + print(self.spi.read())