Доброго времени суток. В этой статье расскажу как интегрировать модули, на примере двух ультразвуковых датчиков 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:githubmipsfpga-plus
Далее в папке C:githubmipsfpga-plusboards выбрать свою плату, у меня это de0_cv, и выполнить скрипт make_project. Проект, который нужно запускать будет находиться в C:githubmipsfpga-plusboardsde0_cvproject.
Если вашей платы нет, то можно выбрать наиболее подходящую по количеству логических ячеек и изменить назначения.
Также понадобятся компилятор, линковщик Codescape.
И USB UART преобразователь. Например, pl2303hx или ch340.
Датчик имеет аналоговый выход, выход широтно-импульсной модуляции, последовательный интерфейс UART, который и будем использовать для подключения датчика к MIPSfpga.
Подключение осуществляется путем подключения регистров, в которые отображаются данные с датчика, к шине, по которой процессор общается с памятью.
Опишем модуль, который будет принимать и обрабатывать данные с датчика.
UART приемник будем использовать уже описанный, который используется для зашивки программ, изменив только параметр baud_rate на 9600, такую скорость передачи использует датчик.
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” и три символа измеренного расстояния, старшими разрядами вперед.
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 в двоично-десятичный формат.
Модуль для управления пьезодинамиком состоит из двух, в первом собственно генерируются импульсы с возможностью выбора частоты:
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 – требуемая частота выходного сигнала.
И модуль где регулируется длительность звуковых импульсов:
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:githubmipsfpga-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:githubmipsfpga-plusprograms1_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].
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:githubmipsfpga-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
Просто полезная книжка – Дэвид Харрис и Сара Харрис «Цифровая схемотехника и архитектура компьютера»
Автор: Kingit