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

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



Подключать будем вот такой датчик:


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

Для начала, необходимы исходники 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]. Получается что-то вроде:



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


Копируем содержимое папки 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



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



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

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


Измеренное расстояние до объекта пропорционально ширине эха, которое кодируется длительностью электрического сигнала на выходе датчика (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  )



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

Полезные ссылки
[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
Просто полезная книжка – Дэвид Харрис и Сара Харрис «Цифровая схемотехника и архитектура компьютера»
Поделиться с друзьями
-->

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


  1. golf2109
    03.12.2016 19:20

    а как с отладкой всего этого?
    есть ли какие-то отладчики?


    1. YuriPanchul
      03.12.2016 22:28

      В качестве отладки можно использовать комбинацию EJTAG / BusBlaster / OpenOCD — см. http://www.silicon-russia.com/2015/08/25/mipsfpga-on-terasic-de0-cv/


      1. golf2109
        04.12.2016 07:56

        какова суммарная цена вопроса для этого?


        1. YuriPanchul
          04.12.2016 08:03

          1. golf2109
            04.12.2016 08:21

            BusBlaster — вещь хорошая, но этот проект уже заброшен (года 3 назад)
            и не развивается,, тем более это любительский проект.
            А более серьезного отладчика от разработчиков кристалла не существует?


            1. YuriPanchul
              04.12.2016 08:32

              Есть N серьезных отладчиков, которые поддерживают EJTAG (отладочный интерфейс ядер MIPS), но они довольно дорогие


              1. golf2109
                04.12.2016 08:47

                Очень жаль, что такая ситуация с отладчиками. Это кардинально тормозит порог вхождения
                в те вещи, которые Вы описываете.


                1. YuriPanchul
                  04.12.2016 09:19

                  На самом деле мне нужно выяснить вопрос с отладчиками получше. Я сам ими практически не пользуюсь, так как пишу на Verilog-е, т.е. работаю в другой части таких проектов.

                  Для MIPS-based микроконтроллеров PIC32MZ, есть дешевые отладчики PICkit 3, но их нельзя подключить к MIPSfpga.

                  Но зададим вопрос шире: что вы имеете в виду когда говорите «в те вещи, которые Вы описываете»? В какие именно вещи? В упражнения с разработкой прототипа системы на кристалле используя FPGA? Для этих упражнений отладчик вообще имхо вещь не 100% нужная, так как в этих упражнениях главное не отладить софтвер, а получить представление как софтвер и хардвер (спроектированный на верилоге и синтезированный) работают вместе. Софтвера как такового в них немного, а хардверную часть вы через софтверный отладчик не отладите.

                  Даже если софтвера много, часто проще сделать custom средства трассировки чего-нибудь (скажем выдавать последовательность событий в системе через последовательный порт на консоль).

                  Отладчики наиболее полезны, когда нужно например делать reverse-engineering чего-нибудь, или скажем отлавливать баги в драйверах операционных систем, когда трассировка не спасает.

                  Какой сценарий использования отладчика вы рассматриваете для упражнений с конструированием простых SoC с реализацией на FPGA? Или вы говорите про использование отладчика, когда на основе прототипа на FPGA кто-то собирается делать ASIC? Так в последнем случае идут совсем другие суммы расходов, отладчик среди них — это копейки.


            1. madprogrammer
              05.12.2016 10:19

              А какого развития вы от него ждете? Это же такой же, как и многие другие более «серьезные» отладчики (Flyswatter2, Olimex ARM-USB-OCD, и др.) универсальный отладчик, использующий в своей основе примитивную схему на базе FTDI FT2232, только в нем еще и CPLD есть, что расширяет его возможности по сравнению с вышеупомянутыми аналогами. Кстати, такие отладчики используют в т. ч. в серьезных больших компаниях. Он просто работает, и особо развивать там нечего. Развиваться должен OpenOCD и другой софт, который с такими отладчиками (на FTDI) работает.

              А по теме насчет серьезного недорогого отладчика, можно посмотреть на Segger J-Link EDU, J-Link BASE, только не уверен, что он поддерживает MIPSfpga.


  1. Kingit
    03.12.2016 19:46

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


  1. GreenStore
    04.12.2016 23:35

    Напишите, пожалуйста, в какое количество логических элементов компилируется MIPSfpga и сколько по времени занимает компиляция?


    1. Kingit
      04.12.2016 23:45

      На моем ноутбуке полная компиляция занимает восемь минут,
      Количество ячеек в ALMs 7058, в LE где-то в 2-2,5 раза больше, регистров 8176


      1. GreenStore
        05.12.2016 05:33

        Спасибо!