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

Доброго часу доби. У цій статті розповім як інтегрувати модулі, на прикладі двох ультразвукових датчиків 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;

завжди @(posedge clock або 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;

завжди @(posedge clock або 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;

завжди @(posedge clock або 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];

завжди @ (posedge clock або 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

завжди @ (posedge clock або 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;

завжди @(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


завжди @(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);

завжди@(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

завжди@(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;

завжди@(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
Просто корисна книжка – Девід Харріс і Сара Харріс «Цифрова схемотехніка та архітектура комп'ютера»
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.