2022-04-07 18:54:11 +02:00

407 lines
15 KiB
TeX

\documentclass[12pt]{article}
\usepackage[margin=2cm]{geometry}
\usepackage[utf8]{inputenc}
\usepackage{hyperref}
\usepackage{graphicx}
\usepackage{minted}
\usepackage{caption}
\title{Laboratory 1 report}
\author{Cédric Hölzl \and Antoine Brunner}
\date{March 2020}
\begin{document}
\maketitle
\section{PWM - servomoteurs}
Dans la première partie du labo, on a du créer un générateur PWM. Nous avons suivi les conseils et d'abord dessiné un schéma block pour avoir une vue d'ensemble de l'implémentation, comme montré dans la figure~\ref{fig:pwm}.
Ce n'est qu'un schéma, donc certains détails d'implémentation ne sont pas présents. Notamment, nous n'avons pas dessiné la logique qui permet d'ignorer les entrées invalides (quand le duty cycle est plus grand que la période par exemple). Nous n'avons pas non plus dessiné les interfaces avec l'extérieur du composant.
Le registre principal est le compteur et permet de savoir à quel moment du signal on se trouve. Pour chacun des deux paramètres principaux du PWM (duty cycle et période) nous avons deux registres. Un registre stocke la valeur actuelle du paramètre, et l'autre stocke la prochaine valeur, celle entrée par l'utilisateur du composant.
La valeur actuelle des paramètres change soit quand l'utilisateur active le PWM, soit quand le compteur atteint la période. La sortie du PWM n'est pas montrée dans le diagramme car elle peut être calculée de façon combinatoire à partir de tous les signaux déjà présents.
Finalement, nous avons écrit les parties de code C pour écrire dans les registres nécessaire pour configurer/démarer/arreter les moteurs (le signal PWM).
\begin{figure}[H]
\centering
\includegraphics[width=\textwidth]{PWM.jpg}
\caption{Block diagram of the PWM circuit}
\label{fig:pwm}
\end{figure}
\section{SPI - joystick}
Dans cette deuxième partie, nous avons designé un composant permettant d'interfacer un convertisseur ADC avec le protocole SPI. Nous avons denouveau commencé par dessiner un schéma de FSM, voir figure~\ref{fig:spi}.
Notez que le schéma de la FSM n'est pas complet, mais n'est qu'une esquisse qui nous a permit l'implémentation du VHDL. Il manque notamment la logique qui permet de charger $channel$ depuis le bus lorsqu'un transfer est initié. Il manque également la logique qui permet de lire les données qui sont reçues sur le bus, lorsqu'on est dans l'état $RCV_D$.
Nous avons ensuite implémenté cette FSM en VHDL ainsi que sa logique de read/write . Nous avons décidé également d'utiliser un unique etat pour recevoir ($RCV\_D$) et envoyer ($SND\_D$) les données. On utilise un compteur indépendant pour suivre le progrès de l'envoi/reception.
Finalement, nous avons écrit le code C nécéssaire pour connecter la position des joystick au signal PWM.
\begin{figure}[H]
\centering
\includegraphics[width=\textwidth]{FSM.jpg}
\caption{FSM for the SPI protocol}
\label{fig:spi}
\end{figure}
\section{Résultats avec l'analyseur logique}
Pour vérifier que notre implémentation fonctionne (bien que le fait que la caméra tourne est déjà bon signe), nous avons branché l'analyseur logique sur la sortie du PWM et capturé les signaux pendant que l'on tenait le joystick en bas, en haut, à gauche et à droite.
Les résultats de l'analyseur logique sont présenté dans la figure~\ref{fig:log}. Les données attendues étaient une période de 25 ms. Pour le mouvement vertical, le duty cycle devait varier entre 0.95 ms et 2.15 ms. Pour le mouvement horizontal, entre 1 ms et 2 ms.
Comme on peut le voir dans la figure~\ref{fig:log}, les valeur réelles sont très proches de celles attendues, ce qui montre que notre implémentation se comporte comme elle le devrait dans cette situation.
\begin{figure}[H]
\centering
\includegraphics[width=\textwidth]{B.png}
\includegraphics[width=\textwidth]{T.png}
\includegraphics[width=\textwidth]{L.png}
\includegraphics[width=\textwidth]{R.png}
\caption{From top to bottom: PWM output when the joystick is at the bottom, top, left and right}
\label{fig:log}
\end{figure}
\newpage
\section{Appendix: code}
Voici le code décrit dans les sections précédentes, si vous préférez le lire depuis le pdf. Afin que le rapport ne soit pas trop long, nous avons mis uniquement le code que nous avons du compléter.
\begin{minted}[breaklines]{C}
#define MICROSEC_TO_CLK(time, freq) ((time)*((freq)/1000000))
void pwm_configure(pwm_dev *dev, uint32_t duty_cycle, uint32_t period, uint32_t module_frequency) {
IOWR_32DIRECT(dev->base, PWM_PERIOD_OFST, MICROSEC_TO_CLK(period, module_frequency));
IOWR_32DIRECT(dev->base, PWM_DUTY_CYCLE_OFST, MICROSEC_TO_CLK(duty_cycle, module_frequency));
}
void pwm_start(pwm_dev *dev) {
IOWR_32DIRECT(dev->base, PWM_CTRL_OFST, PWM_CTRL_START_MASK);
}
void pwm_stop(pwm_dev *dev) {
IOWR_32DIRECT(dev->base, PWM_CTRL_OFST, PWM_CTRL_STOP_MASK);
}
\end{minted}
\captionof{listing}{Le code écrit dans pwm.c\label{code:pwm.c}}
\begin{minted}[breaklines]{VHDL}
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.pwm_constants.all;
entity pwm is
port(
-- Avalon Clock interface
clk : in std_logic;
-- Avalon Reset interface
reset : in std_logic;
-- Avalon-MM Slave interface
address : in std_logic_vector(1 downto 0);
read : in std_logic;
write : in std_logic;
readdata : out std_logic_vector(31 downto 0);
writedata : in std_logic_vector(31 downto 0);
-- Avalon Conduit interface
pwm_out : out std_logic
);
end pwm;
architecture rtl of pwm is
-- The period of the current and next PWM cycle
signal reg_next_period : unsigned(writedata'range) := to_unsigned(DEFAULT_PERIOD, writedata'length);
signal reg_current_period : unsigned(writedata'range) := to_unsigned(DEFAULT_PERIOD, writedata'length);
-- The duty cycle of the current and next PWM cycle
signal reg_next_dutycycle : unsigned(writedata'range) := to_unsigned(DEFAULT_DUTY_CYCLE, writedata'length);
signal reg_current_dutycycle : unsigned(writedata'range) := to_unsigned(DEFAULT_DUTY_CYCLE, writedata'length);
-- The status of the current and next PWM cycle
signal reg_prev_ctrl : std_logic := '0';
signal reg_current_ctrl : std_logic := '0';
-- The internal counter of the PWN
signal reg_counter : unsigned(writedata'range) := to_unsigned(0, writedata'length);
begin
--Avalon-MM slave write
process(clk, reset)
begin
if reset = '1' then
reg_next_period <= to_unsigned(DEFAULT_PERIOD, writedata'length);
reg_next_dutycycle <= to_unsigned(DEFAULT_DUTY_CYCLE, writedata'length);
reg_current_ctrl <= '0';
elsif rising_edge(clk) then
if write = '1' then
case address is
when REG_PERIOD_OFST =>
if unsigned(writedata) >= to_unsigned(2, writedata'length) then
reg_next_period <= unsigned(writedata);
end if;
when REG_DUTY_CYCLE_OFST =>
if (unsigned(writedata) >= to_unsigned(1, writedata'length)) and
(unsigned(writedata) <= reg_next_period) then
reg_next_dutycycle <= unsigned(writedata);
end if;
when REG_CTRL_OFST =>
reg_current_ctrl <= writedata(0);
when others => null;
end case;
end if;
end if;
end process;
--Avalon-MM slave read
process(clk, reset)
begin
if rising_edge(clk) then
if read = '1' then
case address is
when REG_PERIOD_OFST =>
readdata <= std_logic_vector(reg_current_period);
when REG_DUTY_CYCLE_OFST =>
readdata <= std_logic_vector(reg_current_dutycycle);
when others =>
readdata <= (others => '0');
end case;
end if;
end if;
end process;
-- Internal synchronous logic
process(clk, reset)
begin
if reset = '1' then
reg_counter <= to_unsigned(0, writedata'length);
reg_prev_ctrl <= '0';
elsif rising_edge(clk) then
if ((reg_prev_ctrl = '0') and (reg_current_ctrl = '1')) or
(reg_counter = reg_current_period - 1) then
reg_current_period <= reg_next_period;
reg_current_dutycycle <= reg_next_dutycycle;
reg_counter <= to_unsigned(0, writedata'length);
elsif (reg_current_ctrl = '1') then
reg_counter <= reg_counter + 1;
end if;
reg_prev_ctrl <= reg_current_ctrl;
end if;
end process;
-- Avalon Conduit interface
process(clk, reset)
begin
if rising_edge(clk) then
if (reg_counter < reg_current_dutycycle) and (reg_current_ctrl = '1') then
pwm_out <= '1';
else
pwm_out <= '0';
end if;
end if;
end process;
end architecture rtl;
\end{minted}
\captionof{listing}{Le code écrit dans pwm.vhd\label{code:pwm.vhd}}
\begin{minted}[breaklines]{VHDL}
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity mcp3204_spi is
port(
-- 50 MHz
clk : in std_logic;
reset : in std_logic;
busy : out std_logic;
start : in std_logic;
channel : in std_logic_vector(1 downto 0);
data_valid : out std_logic;
data : out std_logic_vector(11 downto 0);
-- 1 MHz
SCLK : out std_logic;
CS_N : out std_logic;
MOSI : out std_logic;
MISO : in std_logic
);
end mcp3204_spi;
architecture rtl of mcp3204_spi is
-- The signals that drive the clock divider
signal reg_clk_divider_counter : unsigned(4 downto 0) := (others => '0'); -- need to be able to count until 24
signal reg_spi_en : std_logic := '0'; -- pulses every 0.5 MHz
signal reg_rising_edge_sclk : std_logic := '0';
signal reg_falling_edge_sclk : std_logic := '0';
signal reg_sclk : std_logic := '0';
-- The state related to the FSM
type state_type is (IDL, SYN, SND_S, SND_SGL, SND_D, WT, RCV_NB, RCV_D, WB);
signal reg_state, next_state : state_type := IDL;
signal reg_bit_idx : unsigned(3 downto 0) := (others => '0');
signal reg_channel : unsigned(1 downto 0);
-- The register that holds the transmitted data
signal reg_data : unsigned(11 downto 0) := (others => '0');
begin
clk_divider_generation : process(clk, reset)
begin
if reset = '1' then
reg_clk_divider_counter <= (others => '0');
elsif rising_edge(clk) then
reg_clk_divider_counter <= reg_clk_divider_counter + 1;
reg_spi_en <= '0';
reg_rising_edge_sclk <= '0';
reg_falling_edge_sclk <= '0';
if reg_clk_divider_counter = 24 then
reg_clk_divider_counter <= (others => '0');
reg_spi_en <= '1';
if reg_sclk = '0' then
reg_rising_edge_sclk <= '1';
elsif reg_sclk = '1' then
reg_falling_edge_sclk <= '1';
end if;
end if;
end if;
end process;
SCLK_generation : process(clk, reset)
begin
if reset = '1' then
reg_sclk <= '0';
elsif rising_edge(clk) then
if reg_spi_en = '1' then
reg_sclk <= not reg_sclk;
end if;
end if;
end process;
STATE_LOGIC : process(clk, reset)
begin
if reset = '1' then
reg_state <= IDL;
reg_bit_idx <= (others => '0');
elsif rising_edge(clk) then
reg_state <= next_state;
case reg_state is
when IDL =>
if next_state = SYN then
reg_channel <= unsigned(channel);
end if;
when SND_SGL =>
if next_state = SND_D then
reg_bit_idx <= to_unsigned(2, reg_bit_idx'length);
end if;
when RCV_NB =>
if next_state = RCV_D then
reg_bit_idx <= to_unsigned(11, reg_bit_idx'length);
end if;
when SND_D | RCV_D =>
if reg_falling_edge_sclk = '1' then
reg_bit_idx <= reg_bit_idx - 1;
end if;
when others =>
null;
end case;
end if;
end process;
-- This is the combinatory logic to compute the next state
next_state <=
SYN when reg_state = IDL and start = '1' else
SND_S when reg_state = SYN and reg_falling_edge_sclk = '1' else
SND_SGL when reg_state = SND_S and reg_falling_edge_sclk = '1' else
SND_D when reg_state = SND_SGL and reg_falling_edge_sclk = '1' else
WT when reg_state = SND_D and reg_falling_edge_sclk = '1' and reg_bit_idx = 0 else
RCV_NB when reg_state = WT and reg_falling_edge_sclk = '1' else
RCV_D when reg_state = RCV_NB and reg_falling_edge_sclk = '1' else
WB when reg_state = RCV_D and reg_falling_edge_sclk = '1' and reg_bit_idx = 0 else
IDL when reg_state = WB else
reg_state;
-- This process reads the bits sent from the ADC
ADC_READ : process(clk, reset)
begin
if reset = '1' then
reg_data <= (others => '0');
elsif rising_edge(clk) then
if reg_state = RCV_D and reg_rising_edge_sclk = '1' then
reg_data(to_integer(reg_bit_idx)) <= MISO;
end if;
end if;
end process;
-- This is the combinatory logic to the ADC converter
SCLK <= reg_sclk;
CS_N <= '1' when reg_state = IDL or reg_state = SYN or reg_state = WB else '0';
MOSI <=
'1' when reg_state = SND_S or reg_state = SND_SGL else
'0' when reg_state = SND_D and reg_bit_idx = 2 else
reg_channel(to_integer(reg_bit_idx)) when reg_state = SND_D else
'0';
-- This is the combinatory logic to the SPI manager
busy <= '0' when reg_state = IDL else
'1';
data_valid <= '1' when reg_state = WB else
'0';
data <= std_logic_vector(reg_data) when reg_state = WB else
(others => '0');
end architecture rtl;
\end{minted}
\captionof{listing}{Le code écrit dans mcp3204_spi.vhd\label{code:mcp3204_spi.vhd}}
\begin{minted}[breaklines]{C}
uint32_t mcp3204_read(mcp3204_dev *dev, uint32_t channel) {
return channel < 4 ? IORD_32DIRECT(dev->base, channel * 4) : 0;
}
\end{minted}
\captionof{listing}{Le code écrit dans mcp3204.c\label{code:mcp3204.c}}
\begin{minted}[breaklines]{C}
uint32_t joysticks_read_left_vertical(joysticks_dev *dev) {
return JOYSTICKS_MAX_VALUE - mcp3204_read(&dev->mcp3204,LV_CHANNEL);
}
uint32_t joysticks_read_left_horizontal(joysticks_dev *dev) {
return mcp3204_read(&dev->mcp3204,LH_CHANNEL);
}
uint32_t joysticks_read_right_vertical(joysticks_dev *dev) {
return JOYSTICKS_MAX_VALUE - mcp3204_read(&dev->mcp3204,RV_CHANNEL);
}
uint32_t joysticks_read_right_horizontal(joysticks_dev *dev) {
return mcp3204_read(&dev->mcp3204,RH_CHANNEL);
}
\end{minted}
\captionof{listing}{Le code écrit dans joysticks.c\label{code:joysticks.c}}
\begin{minted}[breaklines]{C}
uint32_t interpolate(uint32_t input,
uint32_t input_lower_bound,
uint32_t input_upper_bound,
uint32_t output_lower_bound,
uint32_t output_upper_bound) {
return (input - input_lower_bound) * (output_upper_bound - output_lower_bound) / (input_upper_bound - input_lower_bound) + output_lower_bound;
}
\end{minted}
\captionof{listing}{Le code écrit dans app.c\label{code:app.c}}
\end{document}