Рейтинг@Mail.ru

Преобразователь SPI в UART на Verilog

автор:
Be the first to comment! ПЛИС
Print Friendly, PDF & Email

Рассмотрим проект преобразователя интерфейсов из SPI в UART, реализованный на языке Verilog.

1Схема преобразователя SPI в UART

ПЛИС будет выступать в роли ведомого устройства SPI, которое принимает от ведущего устройства данные, и передаёт их по UART в приёмник.

Для работы преобразователя нам понадобятся следующие функциональные блоки:

  • PLL (ФАПЧ).
  • синтезатор частот;
  • приёмник SPI;
  • передатчик UART.

2Модуль ФАПЧ (PLL)

Создадим модуль ФАПЧ с помощью стандартного мастера (mega-wizard) создания PLL, имеющегося в Quartus. Входная частота будет та, которой тактируется наша ПЛИС, а выходных частот две: первая (с0) – 12904600 Гц и вторая (с1) – с тем же значением, что и входная. В моём случае входной частотой будет 50 МГц.

Модуль ФАПЧ
Модуль ФАПЧ

3Модуль синтезатора частот

Стандарт UART предусматривает работу на разных скоростях. Чтобы обеспечить универсальность нашему UART, нам нужен для него некоторый синтезатор, генерирующий заданную частоту. Назовём этот модуль sintezator.v. И настроим его на максимальную стандартную скорость 921600 бит/с.

Исходный код модуля синтезатора частот на Verilog (разворачивается)
// Синтезирует частоту для модуля UART.
module sintezator 
(
	input wire clock_sint, // 12_902_400 Гц
	input wire locked,  // 0
	output wire clock_prm,  
	output wire clock_prd, 
	output wire clock_knopi  
);

wire [3:0] n = 4'b0000; // на какую скорость рассчитан UART

/*
скорость,	n
бит/с
300	-> 15
600	-> 14
1200	-> 13
1800	-> 12
2400	-> 11
4800	-> 10
7200	-> 9
9600	-> 8	
14400	-> 7
19200	-> 6
38400	-> 5
57600	-> 4
115200	-> 3
230400	-> 2
460800	-> 1
921600	-> 0
*/

reg[0:0]prm_921600;
reg[6:0]prd_921600;

reg[0:0]prm_460800;
reg[6:0]prd_460800;

reg[0:0]prm_230400;
reg[6:0]prd_230400;

reg[0:0]prm_115200;
reg[6:0]prd_115200;

reg[0:0]prm_57600;
reg[6:0]prd_57600;

reg[2:0]prm_38400;
reg[20:0]prd_38400;

reg[0:0]prm_19200;
reg[6:0]prd_19200;

reg[1:0]prm_14400;
reg[13:0]prd_14400;

reg[0:0]prm_9600;
reg[6:0]prd_9600;

reg[0:0]prm_7200;
reg[6:0]prd_7200;

reg[0:0]prm_4800;
reg[6:0]prd_4800;

reg[0:0]prm_2400;
reg[6:0]prd_2400;

reg[1:0]prm_1800;
reg[13:0]prd_1800;

reg[0:0]prm_1200;
reg[6:0]prd_1200;

reg[0:0]prm_600;
reg[6:0]prd_600;

reg[0:0]prm_300;
reg[6:0]prd_300;

wire[15:0]vihodi_clk_prd;
wire[15:0]vihodi_clk_prm;

assign clock_prd = vihodi_clk_prd[n];   
assign clock_prm = vihodi_clk_prm[n];  

assign clock_knopi = vihodi_clk_prd[15];

initial begin
	prm_921600 =0;
	prd_921600 =0;
	prm_460800=0;
	prd_460800=0;
	
	prm_230400=0;
	prd_230400=0;
	
	prm_115200=0;
	prd_115200=0;
	
	prm_57600=0;
	prd_57600=0;
	
	prm_38400=0;
	prd_38400=0;
	
	prm_19200=0;
	prd_19200=0;
	
	prm_14400=0;
	prd_14400=0;
	
	prm_9600=0;
	prd_9600=0;
	
	prm_7200=0;
	prd_7200=0;
	
	prm_4800=0;
	prd_4800=0;
	
	prm_2400=0;
	prd_2400=0;
	
	prm_1800=0;
	prd_1800=0;
	
	prm_1200=0;
	prd_1200=0;
	
	prm_600=0;
	prd_600=0;
	
	prm_300=0;
	prd_300=0;
end

assign vihodi_clk_prd[15]=prd_300[6];
assign vihodi_clk_prm[15]=prm_300;

assign vihodi_clk_prd[14]=prd_600[6];
assign vihodi_clk_prm[14]=prm_600;

assign vihodi_clk_prd[13]=prd_1200[6];
assign vihodi_clk_prm[13]=prm_1200;

assign vihodi_clk_prd[12]=prd_1800[13];
assign vihodi_clk_prm[12]=prm_1800[1];

assign vihodi_clk_prd[11]=prd_2400[6];
assign vihodi_clk_prm[11]=prm_2400;

assign vihodi_clk_prd[10]=prd_4800[6];
assign vihodi_clk_prm[10]=prm_4800;

assign vihodi_clk_prd[9]=prd_7200[6];
assign vihodi_clk_prm[9]=prm_7200;

assign vihodi_clk_prd[8]=prd_9600[6];
assign vihodi_clk_prm[8]=prm_9600;

assign vihodi_clk_prd[7]=prd_14400[13];
assign vihodi_clk_prm[7]=prm_14400[1];

assign vihodi_clk_prd[6]=prd_19200[6];
assign vihodi_clk_prm[6]=prm_19200;

assign vihodi_clk_prd[5]=prd_38400[20];
assign vihodi_clk_prm[5]=prm_38400[2];

assign vihodi_clk_prd[4]=prd_57600[6];
assign vihodi_clk_prm[4]=prm_57600;

assign vihodi_clk_prd[3]=prd_115200[6];
assign vihodi_clk_prm[3]=prm_115200;

assign vihodi_clk_prd[2]=prd_230400[6];
assign vihodi_clk_prm[2]=prm_230400;

assign vihodi_clk_prd[1]=prd_460800[6];
assign vihodi_clk_prm[1]=prm_460800;

assign vihodi_clk_prd[0]=prd_921600[1];
assign vihodi_clk_prm[0]=prm_921600;

always@(posedge clock_sint)
	if (locked) begin
		prm_921600<=0;
		prd_921600<=0;
	end
	else begin
		prm_921600<=~prm_921600;
		prd_921600<={prd_921600[5:0],~prd_921600[6]};
end

always@(posedge prm_921600) begin
	prm_460800=~prm_460800;
	prd_460800={prd_460800[5:0],~prd_460800[6]};	
end

always@(posedge prm_460800) begin
	prm_230400=~prm_230400;
	prd_230400={prd_230400[5:0],~prd_230400[6]};	
end

always@(posedge prm_230400) begin
	prm_115200=~prm_115200;
	prd_115200={prd_115200[5:0],~prd_115200[6]};
	prm_38400={prm_38400[1:0],~prm_38400[2]};
	prd_38400={prd_38400[19:0],~prd_38400[20]};
end

always@(posedge prm_115200) begin
	prm_57600=~prm_57600;
	prd_57600={prd_57600[5:0],~prd_57600[6]};
end
	
always@(posedge prm_57600) begin
	prm_14400={prm_14400[0],~prm_14400[1]};
	prd_14400={prd_14400[12:0],~prd_14400[13]};
end	
		
always@(posedge prm_38400[0]) begin
	prm_19200=~prm_19200;
	prd_19200={prd_19200[5:0],~prd_19200[6]};
end

always@(posedge prm_19200) begin
	prm_9600=~prm_9600;
	prd_9600={prd_9600[5:0],~prd_9600[6]};
end
	
always@(posedge prm_14400[0]) begin
	prm_7200=~prm_7200;
	prd_7200={prd_7200[5:0],~prd_7200[6]};
end


always@(posedge prm_9600) begin
	prm_4800=~prm_4800;
	prd_4800={prd_4800[5:0],~prd_4800[6]};
end
	
always@(posedge prm_4800) begin
	prm_2400=~prm_2400;
	prd_2400={prd_2400[5:0],~prd_2400[6]};
end
	
always@(posedge prm_7200) begin
	prm_1800={prm_1800[0:0],~prm_1800[1]};
	prd_1800={prd_1800[12:0],~prd_1800[13]};
end
	
always@(posedge prm_2400) begin
	prm_1200=~prm_1200;
	prd_1200={prd_1200[5:0],~prd_1200[6]};
end	

always@(posedge prm_1200) begin
	prm_600=~prm_600;
	prd_600={prd_600[5:0],~prd_600[6]};
end	

always@(posedge prm_600) begin
	prm_300=~prm_300;
	prd_300={prd_300[5:0],~prd_300[6]};
end
			
endmodule

Напишем тестбенч для проверки нашего модуля синтезатора.

Исходный код тестбенча синтезатора частот (разворачивается)
`timescale 1 ns/1 ns // задаём шаг и разрешение временной сетки

// Тест синтезатора частоты:
module tb_sintezator(); 

	reg clock_sint; 
	wire locked, clock_prm, clock_prd, clock_knopi;
	
	sintezator my_tb
	(
		.clock_sint(clock_sint), .locked(locked), .clock_prm(clock_prm), .clock_prd(clock_prd), .clock_knopi(clock_knopi)		
	);
	
	assign locked = 0;
	
	// CLK
	initial begin		
		clock_sint = 0;	
		forever #38 clock_sint = ~clock_sint; 
	end
	
endmodule  

Временная диаграмма работы модуля нормальная:

Временная диаграмма работы модуля синтезатора частоты
Временная диаграмма работы модуля синтезатора частоты

4Модуль передатчика UART

Исходный код модуля передатчика UART на Verilog (разворачивается)
// Передатчик UART
module uart_prd
(
	input wire clk,
	input wire [7:0]data,
	input wire enable_prm,
	output wire out_prd
);

reg out_prd_inner = 1; // регистр на выход
reg [9:0] data_out;
reg [0:0] go;
reg [3:0] count;
reg en_work, work;
reg [9:0] form_data = 0;
wire [9:0] input_data;

assign out_prd = out_prd_inner;
assign input_data[9:0] = {1'b0, data[7:0], 1'b1}; // 0-стоповый бит, данные, 1-стартовый бит

initial begin
	data_out=10'b1111111111; //стартовый бит + 8 бит данных + стоповый бит
	go=1;
	count=0;
	en_work= 0;
	work = 0;
end

// 
always @(posedge enable_prm or posedge work) begin
	if (work) begin
		en_work <= 1'b0;
	end
	else begin
		en_work <= 1'b1;
		form_data <= input_data;
	end
end

// 
always@(posedge clk) begin
	work <= en_work;
	if (work) begin
		go <= 0;
		count <= 0;
		out_prd_inner <= 1;
		data_out <= form_data;
	end
	else begin
		out_prd_inner <= data_out[9];
		data_out <= {data_out[8:0], 1'b1}; // 1 стартовый бит.
		count <= count + 1;
		if (count == 10)
			go<=1;
	end
end

endmodule

Напишем тестбенч для модуля передатчика UART.

Код тестбенча для модуля передатчика UART (разворачивается)
`timescale 1 ns/1 ns 

// Тест передатчика UART:
module tb_prd_uart(); 

	reg [7:0] data;
	reg clk, enable_prm; 
	wire out_prd;
	
	prd_uart my_tb
	(
		.clk(clk), .data(data), .enable_prm(enable_prm), .out_prd(out_prd)
	);
	
	// ENABLE PRM
	initial begin
		enable_prm = 0;
		#500 enable_prm = 1;
		#500 enable_prm = 0;
		#17000 enable_prm = 1;
		#500 enable_prm = 0;
		#17000 enable_prm = 1;
		#500 enable_prm = 0;
	end
	
	// DATA 
	initial begin
		data <= 8'b00000000;
		#100 data <= 8'b11001001;
		#15000 data <= 8'b10010101;
		#17000 data <= 8'b00100010;
		#500000 $stop;
	end
	
	// CLK
	initial begin
		clk = 0;	
		forever #542 clk = ~clk;  
	end

endmodule  

Видно, что модуль работает так, как нужно:

Временная диаграмма работы модуля передатчика UART
Временная диаграмма работы модуля передатчика UART

5Модуль приёмника SPI

Код модуля приёмника SPI на Verilog (разворачивается)
`timescale 1ns / 1ps

// Приёмник SPI 
module spi_prm
(
	input Spi_freq, //SPI SCK
	input Spi_data, //SPI MOSI
	input Spi_cs, //SPI CS
	input Clk,  
	input Rst, // reset_
	output [15:0] Data_prm,  
	output reg Int_prm 
);

parameter siso_shift = 16;  

reg [siso_shift:0] count_bit; // считаем биты
reg [1:0] falling_freq_prm; // выделяем фронт из частоты
wire imp_f_prm_clk;  // импульс длительностью 1 такт clk
reg [siso_shift-1:0]data_prm_;

assign Data_prm = data_prm_[siso_shift-1:0];

initial begin
	count_bit = 0;
	Int_prm = 0;
	falling_freq_prm =0;
end

always @(posedge Clk)
	falling_freq_prm <= {falling_freq_prm[0], Spi_freq};
	
assign imp_f_prm_clk = (~falling_freq_prm[1] & falling_freq_prm[0])&Spi_cs;

always @ (posedge Spi_freq) begin
	data_prm_ <= {Spi_data, Data_prm [siso_shift-1:1]};
end

always @ (posedge Clk)
	if (Rst | (siso_shift==count_bit)) begin
			count_bit <= 0;
	end
	else if (Spi_cs & imp_f_prm_clk) begin
		count_bit <= count_bit + 1;
end		
		
always @ (posedge Clk)
	if (Rst | (count_bit == siso_shift))
		Int_prm <= 1'b1;
	else if (falling_freq_prm)
		Int_prm <= 1'b0;
	
endmodule

Теперь проверим работу нашего модуля, написав тест.

Код тестбенча приёмника SPI (разворачивается)
`timescale 10 ns/1 ns 

// Тест приёмника SPI:
module tb_spi_prm(); 

	reg clk, sck, mosi, cs; 
	wire [15:0] data_out;
	wire int;
	wire rst;
	
	assign rst = 0;

	spi_prm my_tb
	(
		.Clk(clk), .Spi_freq(sck), .Spi_data(mosi), .Spi_cs(cs), .Rst(rst), .Data_prm(data_out), .Int_prm(int)
	);
		
	// CLK
	initial begin		
		clk = 0;	
		forever #1 clk = ~clk; // 50 MHz
	end
	
	// SCK
	initial begin
		sck = 0;	
		#20 sck = 0;
		repeat (16) begin
			#20 sck=1; 
			#20 sck=0;
		end 
		#400 sck=0;
	end
	
	// MOSI
	initial begin
		mosi = 0;
		#30 mosi=1;
		#25 mosi=0;
		#20 mosi=1;
		#25 mosi=0;
		#125 mosi=1;
		#25 mosi=0;
		#20 mosi=1;
		#25 mosi=0;
		#175 mosi=1;
		#20 mosi=0;
		#20 mosi=1;
		#20 mosi=0;
		#100 mosi=1;
		#20 mosi=0;
	end
	
	// CS
	initial begin
		cs = 1;
		#25 cs = 0;
		#700 cs = 1;
		#50 $stop;
	end
	
endmodule 

На линии MOSI выставим 16'b1001100001100011, на линии data_out то же число:

Временная диаграмма работы модуля приёмника SPI
Временная диаграмма работы модуля приёмника SPI

6Соединяем модули преобразователя SPI в UART

Объединим все модули вот таким образом:

RTL диаграмма проекта SPI в UART
RTL диаграмма проекта SPI в UART

Назовём этот модуль top_spi_uart.v.

Исходный код модуля верхнего уровня на Verilog (разворачивается)
`timescale 1ns / 1ps

// Модуль верхнего уровня
module top_spi_uart
(
	input clk, // частота с тактового генератора 	
	input SCK, // не больше 750 кбит/с, а то начнутся сбои в UART!
	input MOSI,
	input CS,  	
	output TxD
);

wire [7:0]data_PRM;
wire clk_prd;

pll12m9 PLL12M9
(
	clk,
	clk50MHz,
	clk_12M904
);

sintezator SINTEZATOR
(
	.clock_sint(clk_12M904), // 12_902_400 Гц или максимально близкую, например, 13.3 МГц 
	.locked(reset_),
	.clock_prm(),
	.clock_prd(clk_prd), // в нашем случае 921600 бит/с
	.clock_knopi()
);

uart_prd UART_PRD
(
	.clk(clk_prd), // clk_prd частота передачи в UART 921600 бит/с
	.data(data_PRM),
	.enable_prm(int_prm),
	.out_prd(TxD)
);

spi_prm#(.siso_shift(8)) SPI_PRM  
(
	.Spi_freq(SCK), // для приёма заднему фронту нужно инвертировать SCK.
	.Spi_data(MOSI),
	.Spi_cs(~CS), // для Active High CS без инверсии, для Active Low - инвертировать.
	.Clk(clk50MHz), // высокая частота с генератора
	.Rst(0),
	.Data_prm(data_PRM),
	.Int_prm(int_prm)
);

endmodule

Напишем тестбенч для проверки модуля.

Тестбенч для преобразователя SPI в UART (разворачивается)
`timescale 1 ns/1 ns

// Тест SPI-в-UART:
module tb_spi2uart(); 

	reg clk, sck, mosi, cs; 
	wire txd;
	
	top_spi_uart my_tb
	(
		.clk(clk), .SCK(sck), .MOSI(mosi), .CS(cs), .TxD(txd)
	);
	
	// CLK
	initial begin
		clk = 0;	
		forever #38 clk = ~clk; // для 12_902_400 Hz полпериода = 38 ns.
	end
	
	// SCK
	initial begin
		sck = 0;	
		#500 sck = 0;
		repeat (8) begin
			#1000 sck=1; 
			#1000 sck=0;
		end 
	end
	
	// MOSI
	initial begin
		mosi = 0;
		#2600 mosi=1;
		#4000 mosi=0;
		#2000 mosi=1;
		#2000 mosi=0;
		#4000 mosi=1;
		#1000 mosi=0;
	end
	
	// CS
	initial begin
		cs = 1;
		#800 cs = 0;
		#17000 cs = 1;
		#100000 $stop;
	end
	

endmodule  

Убедимся, что всё работает исправно:

Временная диаграмма работы преобразователя SPI в UART
Временная диаграмма работы преобразователя SPI в UART

По SPI (линия MOSI) мы передаём 1 байт 8'b01101001. Видно, что на передатчике UART (линия TxD) то же значение.

7Демонстрация работыпреобразователя SPI в UART

Для демонстрации работы преобразователя SPI в UART соберём небольшой стенд. В роли передатчика SPI будет выступать отладочная плата с микросхемой FTDI FT2232HL, подключённая к ноутбуку по USB. В роли приёмника UART – модуль USB-UART, подключённый к USB-порту компьютера. Между ними в роли преобразователя SPI-UART выступает ПЛИС Cyclone II (EP2C5T144C8) на отладочной плате A-C2FB. Такую плату сейчас сложно найти в продаже, но есть другие на 2-ом Циклоне.

Стенд для демонстрации работы преобразователя SPI в UART
Стенд для демонстрации работы преобразователя SPI в UART
Стенд для демонстрации работы преобразователя SPI в UART – плата с FT2232HL и плата A-C2FB с ПЛИС Cyclone ii
Стенд для демонстрации работы преобразователя SPI в UART – плата с FT2232HL и плата A-C2FB с ПЛИС Cyclone ii

Посылать данные с компьютера-источника будем с помощью программы SPI via FTDI, о которой я уже писал. Она переводит подключённую микросхему FTDI в режим SPI с заданными настройками и позволяет осуществлять запись/чтение по интерфейсу SPI.

Не забудем, что наш проект работает со скоростями SPI до 750 кбит/с. Это ограничение вызвано тем, что наш UART работает на пределе возможностей (921600 kbps), и больший поток он просто не успеет обработать.

Принимать будем на другом компьютере с помощью любой терминальной программы. Я в данном случае пользуюсь вот этой терминалкой.

Демонстрация работы преобразователя SPI в UART: приём данных на компьютере
Демонстрация работы преобразователя SPI в UART: приём данных на компьютере

Как видно, данные безошибочно передаются с компьютера на компьютер. Преобразователь интерфейсов работает.

Read 13287 times
Ключевые слова: :

Поблагодарить автора:

Поделиться

Print Friendly, PDF & Email

Leave a comment