\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}
