Подключение периферийных модулей к MIPSfpga, на примере ультразвуковых датчиков расстояния

Доброго времени суток. В этой статье расскажу как интегрировать модули, на примере двух ультразвуковых датчиков HC-SR04 и Pmod MAXSONAR, в систему на кристалле на основе MIPSfpga. Также расскажу как написать программу для управления подключенных модулей.
Основываясь на моем примере вы сможете подключить ваши собственные модули, управлять ими при помощи программы. Создавать систему со своим набором периферии, на основе MIPSfpga.

d5572663735848adbe8d9c368bd9c64f.jpg

Подключать будем вот такой датчик:
6d6305af56484242a5c343a9aea441dc.jpg45e599e356954379a3f6731b542a5dc8.jpg
Документацию на него можно найти здесь. Данные будем выводить на семисегментные индикаторы на плате. Также будем генерировать звуковые сигналы различной длительности при помощи пьезодинамика.

Для начала, необходимы исходники MIPSfpga. Инструкция по скачиванию:
www.silicon-russia.com/2015/12/11/mipsfpga-download-instructions
Далее, нужно скачать надстройку MIPSfpga-plus, которая позволяет записывать программы по UARTу:
github.com/MIPSfpga/mipsfpga-plus.
Описание и инструкции по установке присутствуют, если коротко, то для того чтобы была возможность просто запустить скрипт и проект собрался, необходимо:
Поместить исходники MIPSfpga в папку:

C:\MIPSfpga
А MIPSfpga-plus в:
C:\github\mipsfpga-plus
Далее в папке C:\github\mipsfpga-plus\boards выбрать свою плату, у меня это de0_cv, и выполнить скрипт make_project. Проект, который нужно запускать будет находиться в C:\github\mipsfpga-plus\boards\de0_cv\project.
Если вашей платы нет, то можно выбрать наиболее подходящую по количеству логических ячеек и изменить назначения.

Также понадобятся компилятор, линковщик Codescape.
И USB UART преобразователь. Например, pl2303hx или ch340.

Датчик имеет аналоговый выход, выход широтно-импульсной модуляции, последовательный интерфейс UART, который и будем использовать для подключения датчика к MIPSfpga.
Подключение осуществляется путем подключения регистров, в которые отображаются данные с датчика, к шине, по которой процессор общается с памятью.

Опишем модуль, который будет принимать и обрабатывать данные с датчика.


UART приемник будем использовать уже описанный, который используется для зашивки программ, изменив только параметр baud_rate на 9600, такую скорость передачи использует датчик.
Модуль uart receiver
module uart_receiver
(
    input  clock,
    input  reset_n,
    input  rx,
    output reg [7:0] byte_data,
    output           byte_ready
);


    parameter  clock_frequency        = 50000000;

    parameter  baud_rate              = 9600;
    localparam clock_cycles_in_symbol = clock_frequency / baud_rate;

    // Synchronize rx input to clock

    reg rx_sync1, rx_sync;

    always @(posedge clock or negedge reset_n)
    begin
        if (! reset_n)
        begin
            rx_sync1 <= 1;
            rx_sync  <= 1;
        end
        else
        begin
            rx_sync1 <= rx;
            rx_sync  <= rx_sync1;
        end
    end

    // Finding edge for start bit

    reg prev_rx_sync;

    always @(posedge clock or negedge reset_n)
    begin
        if (! reset_n)
            prev_rx_sync <= 1;
        else
            prev_rx_sync <= rx_sync;
    end

    wire start_bit_edge = prev_rx_sync & ! rx_sync;

    // Counter to measure distance between symbols

    reg [31:0] counter;
    reg        load_counter;
    reg [31:0] load_counter_value;

    always @(posedge clock or negedge reset_n)
    begin
        if (! reset_n)
            counter <= 0;
        else if (load_counter)
            counter <= load_counter_value;
        else if (counter != 0)
            counter <= counter - 1;
    end

    wire counter_done = counter == 1;

    // Shift register to accumulate data

    reg       shift;
    reg [7:0] shifted_1;
    assign    byte_ready = shifted_1 [0];

    always @ (posedge clock or negedge reset_n)
    begin
        if (! reset_n)
        begin
            shifted_1 <= 0;
        end
        else if (shift)
        begin
            if (shifted_1 == 0)
                shifted_1 <= 8'b10000000;
            else
                shifted_1 <= shifted_1 >> 1;

            byte_data <= { rx, byte_data [7:1] };
        end
        else if (byte_ready)
        begin
            shifted_1 <= 0;
        end
    end

    reg idle, idle_r;

    always @*
    begin
        idle  = idle_r;
        shift = 0;

        load_counter        = 0;
        load_counter_value  = 0;

        if (idle)
        begin
            if (start_bit_edge)
            begin
                load_counter       = 1;
                load_counter_value = clock_cycles_in_symbol * 3 / 2;
           
                idle = 0;
            end
        end
        else if (counter_done)
        begin
            shift = 1;

            load_counter       = 1;
            load_counter_value = clock_cycles_in_symbol;
        end
        else if (byte_ready)
        begin
            idle = 1;
        end
    end

    always @ (posedge clock or negedge reset_n)
    begin
        if (! reset_n)
            idle_r <= 1;
        else
            idle_r <= idle;
    end

endmodule


Согласно даташиту[1], каждые 49 мс, сонар отправляет четыре байта в ASCII формате, символ «R» и три символа измеренного расстояния, старшими разрядами вперед.
Модуль sonar
module sonn
(
    input  clock,
    input  reset_n,
    input  rx,
	 output reg [15:0] hword
);

reg [1:0] count2;
wire [7:0] byte_data;
reg [31:0] wordd;

always @(posedge clock)
    if (! reset_n)
	     wordd <= 32'b0;
	  else if (byte_ready)
	     	case(count2)
			2'd0:begin 
				wordd[31:24]<=byte_data;
				    if(byte_data==8'h52)
					count2<=2'd1;
				end
			2'd1:begin wordd[23:16]<=byte_data; count2<=2'd2;end
			2'd2:begin wordd[15:8]<=byte_data; count2<=2'd3;end
			2'd3:begin wordd[7:0]<=byte_data; count2<=2'd0;
			           hword={4'b0000,wordd[19:16],wordd[11:8],wordd[3:0]};end
			default begin 
				wordd[31:24]<=byte_data;
				    if(byte_data==8'h52)
					count2<=count2+1'b1;
				end
			endcase
	
uart_receiver uart(clock,reset_n,rx,byte_data,byte_ready);
endmodule

По каждому такту и флагу byte_ready (взводится, когда был принят байт) ожидаем символ «R» — 52 в шестнадцатеричном, далее переходим к приему следующих трех символов. По приему трех байт записываем в выходной регистр младшие полубайты, таким образом переводим из ASCII в двоично-десятичный формат.

Модуль для управления пьезодинамиком состоит из двух, в первом собственно генерируются импульсы с возможностью выбора частоты:

Модуль tone
module note(clk,reset_n,notein,noteout);
input clk,reset_n;
input [6:0]notein;
output reg noteout;

reg [19:0]div;
reg [19:0]cnt;
reg eocnt; 

always @*
begin
  case (notein)
    0: div = 191114; // C
    1: div = 180385; // C#
    2: div = 170262; // D
    3: div = 160705; // D#
    4: div = 151686; // E
    5: div = 143173; // F
    6: div = 135136; // F#
    7: div = 127552; // G
    8: div = 120389; // G#
    9: div = 113636; // A
    10:div = 107259; // A#
    11:div = 101239; // H
    12:div = 95558; // C 
    13:div = 90194; // C#
    14:div = 85132; // D
    15:div = 80354; // D#
    16:div = 75845; // E
    17:div = 71558; // F
    18:div = 67567; // F#
    19:div = 63775; // G
    20:div = 60197; // G#
    21:div = 28403; // A   
    22:div = 53629; // A#
    23:div = 50619; // H
    24:div = 47777; // C
    25:div = 45097; // C#
    26:div = 42566; // D
    27:div = 40176; // D#
    28:div = 37921; // E
    
    
  default: div = 1; //
  endcase
end

     
always @(posedge clk)
begin 
if(~reset_n)
begin
  noteout<=0;
  cnt <= 0;
end
    if(cnt == div)
	 begin
        cnt <= 0;
		  noteout <= ~noteout;
	 end
    else
        cnt <= cnt + 1'b1;
end

endmodule 


Считаем до div и инвертируем выходной сигнал. Div выбирается входным notein.
Div = Fclk/Fnote/2
Где Fclk — частота тактового сигнала, Fnote — требуемая частота выходного сигнала.

И модуль где регулируется длительность звуковых импульсов:

Модуль buzz
module buzz(clk,reset_n,en,tim,tone);
input clk,reset_n,en;
input [1:0]tim;
output reg tone;

wire [6:0]notein;
wire noteout;
assign notein=21;

reg [27:0]counter,div,div1;
reg ene;

note note1(clk,reset_n,notein,noteout);

always@(posedge clk)
if(~reset_n)
begin
 counter<=28'b0;
 ene<=1'b0;
end
else
begin
  case(tim)
	0:begin div<=28'd 5_000_000; div1<=28'd 12_000_000; end
	1:begin div<=28'd 10_000_000; div1<=28'd 20_000_000; end
	2:begin div<=28'd 25_000_000; div1<=28'd 35_000_000; end
	3:begin div<=28'd 50_000_000; div1<=28'd 60_000_000; end
   default begin div<=28'd 10_000_000; div1<=28'd 20_000_000; end
  endcase
  
  counter<=counter+1;
  if(counter==div)
	 ene<=1'b1;
  if(counter==div1 | counter>60_000_000)
   begin
	 counter<=28'b0;
	 ene<=1'b0;
   end
end

always@(posedge clk)
tone<=en¬eout&ene;
endmodule


Каждый такт счетчик инкрементируется, при достижении значения div разрешается звуковой сигнал, при достижении значения div1 запрещается. Таким образом задается длительность, и частота звуковых сигналов. Также счётчик ограничен 60000000 для предотвращения длительного импульса, который может возникнуть при изменении div1 в момент, когда значение счётчика между div и div1.

Подключаем к проекту файлы с описанными выше модулями.


  • В файле верхнего уровня у меня это de0_cv.v добавим следующие строчки:
    wire [15:0]hword;
    wire [31:0]control;
    
    sonn sonna
    (
     .clock		( CLOCK_50  ),
     .reset_n 		( RESET_N   ),
     .rx			( GPIO_0[7] ),	
     .hword 		( hword     )
    );
    
    buzz buzz
    (
     .clk			( CLOCK_50		),
     .reset_n		( RESET_N		),
     .en			(control [0]		),
     .tim			(control [2:1]	),
     .tone			( GPIO_0[35]	)
    );

    Для входа RX UART приемника выделяем пин GPIO_0[7] и для выхода tone GPIO_0[35]. Регистры hword и control будем подключать к шине.
     assign GPIO_0 [1] = 1'b1; //VCC for Sensor
     assign GPIO_0 [3] = 1'b0; //GND for Sensor
     assign GPIO_0 [34] = 1'b0; //GND for buzz

    Выделим ножки GPIO_0 [1], GPIO_0 [3], GPIO_0 [34] для питания сенсора, общего провода для сенсора и пьезодинамика соответственно.

    В описании модуля mfp_system добавим:

    .IO_Sonar	( hword    ), 
    .IO_control     ( control  ),

  • В файле mfp_system.v добавляем входы выходы:
     input [15:0]  IO_Sonar,
     output [31:0] IO_control, 

    В описании модуля mfp_ahb_lite_matrix_with_loader:
    .IO_Sonar         (   IO_Sonar   ), 
    .IO_control       (   IO_control ),

  • В файле mfp_ahb_lite_matrix_with_loader.v:
     input  [15:0] IO_Sonar,
     output  [31:0] IO_control,

    В описании модуля mfp_ahb_lite_matrix:
     .IO_Sonar	( IO_Sonar        ), 
     .IO_control       ( IO_control      ),

  • В файле mfp_ahb_lite_matrix.v:
     input  [15:0] IO_Sonar,
     output [31:0] IO_control,

    В описании модуля mfp_ahb_gpio_slave
     .IO_Sonar	 ( IO_Sonar ),
     .IO_control       ( IO_control)

  • В файле mfp_ahb_gpio_slave.v:
     input [15:0]IO_Sonar,
     output reg[31:0]IO_control,

    в предпоследнем always по! HRESETn
     IO_control      <=32'b0;

    И в case (write_ionum)
     `MFP_CONTROL_IONUM       : IO_control      <=HWDATA;

    В последнем always в case (read_ionum)
    `MFP_SONAR_SENSOR_IONUM  : HRDATA = { 16'b0, IO_Sonar };

В файле mfp_ahb_lite_matrix_config.vh который находится в папке C:\github\mipsfpga-plus
Добавляем следующие строчки:

`define MFP_SONAR_SENSOR_IONUM      4'h6
`define MFP_CONTROL_IONUM           4'h9

`define MFP_SONAR_SENSOR_ADDR     32'h1f800018
`define MFP_CONTROL_ADDR          32'h1f800024

Собственно это и есть адреса по которым будут доступны регистры.

Исходники модулей, и измененные файлы доступны здесь:
github.com/Denis-Kingit/UltraSonicToMIPSfpga

Далее компилируем проект и прошиваем плату. Подключаем датчик, преобразователь, пьезодинамик:
Пин GND преобразователя к GPIO_1[26], пин TX к GPIO_1[31].
Пьезодинамик к GPIO_0[34], GPIO_0[35].
Пин GND датчика к GPIO_0[3], VCC — GPIO_0[1], TX — GPIO_0[7].
Получается что-то вроде:

ff84e2a46acc4d089c9de044b5bdbb86.jpg

Программная часть


Копируем содержимое папки C:\github\mipsfpga-plus\programs\01_light_sensor или попросту работаем в ней.
Добавим в файл mfp_memory_mapped_registers.h адреса регистров:
#define MFP_SONAR_SENSOR_ADDR   0xBF800018
#define MFP_CONTROL_ADDR        0xBF800024

#define MFP_SONAR_SENSOR        (* (volatile unsigned *) MFP_SONAR_SENSOR_ADDR  )
#define MFP_CONTROL	        (* (volatile unsigned *) MFP_CONTROL_ADDR  ) 

Теперь напишем программу, в которой будем считывать значения с регистра и выводить на семисегментные индикаторы. И в зависимости от считаного значения управляем звуковым сигналом. Изменим main.c:

Программа
#include "mfp_memory_mapped_registers.h"

int main ()
{
    MFP_CONTROL = 1;
    for (;;)
    {
        MFP_7_SEGMENT_HEX = MFP_SONAR_SENSOR;
	  if(MFP_SONAR_SENSOR<0x10)
		MFP_CONTROL = 1;
	  else if (MFP_SONAR_SENSOR<0x12)
		MFP_CONTROL = 3;
	  else if (MFP_SONAR_SENSOR<0x14)
		MFP_CONTROL = 5;
	  else if (MFP_SONAR_SENSOR<0x16)
		MFP_CONTROL = 7;
	  else
		MFP_CONTROL = 0;
    }

    return 0;
}


Компилируем запустив скрипт
 02_compile_and_link

Генерируем motorola_s_record файл
 08_generate_motorola_s_record_file

Проверяем к какому СОМ порту подключен USB UART преобразователь
 11_check_which_com_port_is_used

Изменяем файл 12_upload_to_the_board_using_uart
set a=7 
mode com%a% baud=115200 parity=n data=8 stop=1 to=off xon=off odsr=off octs=off dtr=off rts=off idsr=off type program.rec >\.\COM%a%

где, а — номер СОМ порта, к которому подключен USB UART преобразователь.
И наконец загружаем программу
 12_upload_to_the_board_using_uart

138f0f429d7c49b685e3d7de68e8e209.jpg

Теперь подключим Ultrasonic HC-SR04


813a37d20d3d4155bdcd13f423c346bb.jpg

Идея подключения такая же, отображаем данные с датчика в регистры, подключенные к шине.

Для измерения расстояния необходимо подать импульс длительностью 10 мкс на сигнальный пин Trig, после чего ультразвуковой модуль будет излучать восемь пачек ультразвукового сигнала с частотой 40кГц и обнаруживать эхо.

c115ed63573744fdac58e7515b5ccbbb.jpg

Измеренное расстояние до объекта пропорционально ширине эха, которое кодируется длительностью электрического сигнала на выходе датчика (Echo). Рекомендованный период между измерениями не менее 50 мс. Для того что бы рассчитать расстояние необходимо длительность импульса (эха) в микросекундах разделить на 58 для расстояния в сантиметрах или на 148 для расстояния в дюймах. Если никаких препятствий не обнаружено, то на выходе будет сигнал с длительностью 38 мс [2].
Модуль Sonic
module sonic(clk,trig,reset_n,en,echo,out);
input clk,echo,reset_n,en;
output reg trig;
output reg[23:0]out;

reg [23:0]count;
reg [23:0]countp;

always@(posedge clk)
if(~reset_n)
begin
 countp<=24'b0;
 count<=24'b0;
end
else 

if(en)
begin
	if(countp==0)
	 trig<=1'b1;
	if(echo)
		count<=count+1'b1;	
	if(countp==500)
		trig<=1'b0;
	if(countp==2500000)
		begin
			if (count>1800000)			
				out<=24'hfff;
			else
				out<=count/2900;
			countp<=24'b0;
			count<=24'b0;
		end
	countp<=countp+1'b1;
end
endmodule	


С периодом 2500000 тактов (50 мс) создаем импульс на выходе trig длительностью 500 тактов (10 мкс), считаем длительность эха, делим на 2900 (на 50 для перевода в микросекунды и на 58 для перевода в сантиметры) и записываем результат в регистр (для то что бы не создавать дополнительный делитель можно переводить программно), если длительность импульса больше 36 мс записываем 0хFFF, что будет означать что препятствий не обнаружено.

Подключим модуль в файле верхнего уровня проекта (de0_cv.v).

wire [23:0] usonic;
sonic sonica
(
.clk		( CLOCK_50	),
.trig		( GPIO_0[33]	),
.reset_n	( RESET_N	),
.en		( control[3]	),
.echo		( GPIO_0[32]	),
.out		( usonic	)
);

Аналогичным образом изменяем файлы: mfp_ahb_gpio_slave.v, mfp_ahb_lite_matrix.v, mfp_ahb_lite_matrix_with_loader.v, mfp_ahb_lite_matrix_config.vh, mfp_system.v в папке C:\github\mipsfpga-plus.

Для подключения датчика понадобится источник питания на 5В, пин trig подключаем к GPIO_0[33], так как датчик работает от 5В, пин echo необходимо подключить через делитель напряжения к GPIO_0[32], соединяем общий провод источника, датчика и платы.

Пишем программу:

Программа
#include "mfp_memory_mapped_registers.h"

int main ()
{
    int k = 0;
    for (;;)
    {
        //MFP_RED_LEDS      = MFP_SONICR_SENSOR >> 4;
	  k=MFP_SONIC_SENSOR/58/50;
        MFP_7_SEGMENT_HEX = k;
	  if(k<0x10)
		MFP_CONTROL = 1;
	  else if (k<0x12)
		MFP_CONTROL = 3;
	  else if (k<0x14)
		MFP_CONTROL = 5;
	  else if (k<0x16)
		MFP_CONTROL = 7;
	  else
		MFP_CONTROL = 0;
    }

    return 0;
}


Считываем значения с регистра, в зависимости от полученного значения управляем длительностью звука, выводим значение расстояния на семисегментные индикаторы.

Так же необходимо добавить новый адрес в файл mfp_memory_mapped_registers.h

#define MFP_SONIC_SENSOR_ADDR   0xBF800020

#define MFP_SONIC_SENSOR        (* (volatile unsigned *) MFP_SONIC_SENSOR_ADDR  )

17029066ed3444378e8612ce2cff414d.jpg

Описанным выше способом вы можете подключить ваши собственные модули, и управлять ими программно.

[1] LV-MaxSonar — maxbotix.com/documents/LV-MaxSonar-EZ_Datasheet.pdf
[2] Ultrasonic HC SR04 — robocraft.ru/blog/electronics/772.html
Исходники MIPSfpga-plus github.com/MIPSfpga/mipsfpga-plus
Codescape — Codescape
Инструкция по скачиванию mipsfpga — Инструкция
Исходники модулей, программ и измененных файлов:
github.com/Denis-Kingit/UltraSonicToMIPSfpga
Просто полезная книжка — Дэвид Харрис и Сара Харрис «Цифровая схемотехника и архитектура компьютера»

Комментарии (2)

  • 3 декабря 2016 в 19:20

    0

    , а как с отладкой всего этого?
    есть ли какие-то отладчики?
  • 3 декабря 2016 в 19:46 (комментарий был изменён)

    0

    Программу можно отладить в симуляторах MIPS. Проследить выполнение инструкций, отладить свои модули можно в ModelSim, также можно проследить за сигналами внутри кристалла c помощью Logic Tap signal analyzer в Quartus

© Habrahabr.ru