Преобразователь SPI в UART на Verilog
Рассмотрим проект преобразователя интерфейсов из 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
Видно, что модуль работает так, как нужно:
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 то же число:
6Соединяем модули преобразователя 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 (линия 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 via FTDI, о которой я уже писал. Она переводит подключённую микросхему FTDI в режим SPI с заданными настройками и позволяет осуществлять запись/чтение по интерфейсу SPI.
Не забудем, что наш проект работает со скоростями SPI до 750 кбит/с. Это ограничение вызвано тем, что наш UART работает на пределе возможностей (921600 kbps), и больший поток он просто не успеет обработать.
Принимать будем на другом компьютере с помощью любой терминальной программы. Я в данном случае пользуюсь вот этой терминалкой.
Как видно, данные безошибочно передаются с компьютера на компьютер. Преобразователь интерфейсов работает.