Прошло чуть больше месяца с тех пор, как я портировал open source модуль UART16550 на шину AHB-Lite. Писать об этом на тот момент было несколько не логично, так как еще не была опубликована статья про прерывания MIPSfpga.
Если вы опытный разработчик, то для вас только одна полезная новость: UART16550 добавлен в состав системы MIPSfpga-plus, дальше можете не читать. А тем, кого интересует разобранный пример использования этого модуля — добро пожаловать под кат.
Введение
Предполагается, что читатель:
- знаком с предметной областью в объеме учебника Харрис-энд-Харрис [L1];
- имеет доступ к исходным кодам MIPSfpga [L2] и mipsfpga-plus [L3];
- имеет некоторый опыт программирования микроконтроллеров (любой архитектуры), включая использование UART. Освежить, что из себя представляет UART можно здесь [L4];
Что такое UART16550
История появления этой микросхемы неплохо описана в [L5], найти документацию на нее можно в google [L6], для нас важно другое:
- она получила крайне широкое распространения;
- работа с ней поддерживается ядром Linux;
- для нее уже давно существует open source реализация на verilog [L7, L8, L9], но для шины Wishbone;
- цена портирования этой реализации на используемую в MIPSfpga шину AHB-Lite — около 100 строчек кода [S1]:
module mfp_ahb_lite_uart16550(
//ABB-Lite side
input HCLK,
input HRESETn,
input [ 31 : 0 ] HADDR,
input [ 2 : 0 ] HBURST,
input HMASTLOCK, // ignored
input [ 3 : 0 ] HPROT, // ignored
input HSEL,
input [ 2 : 0 ] HSIZE,
input [ 1 : 0 ] HTRANS,
input [ 31 : 0 ] HWDATA,
input HWRITE,
output reg [ 31 : 0 ] HRDATA,
output HREADY,
output HRESP,
input SI_Endian, // ignored
//UART side
input UART_SRX, // UART serial input signal
output UART_STX, // UART serial output signal
output UART_RTS, // UART MODEM Request To Send
input UART_CTS, // UART MODEM Clear To Send
output UART_DTR, // UART MODEM Data Terminal Ready
input UART_DSR, // UART MODEM Data Set Ready
input UART_RI, // UART MODEM Ring Indicator
input UART_DCD, // UART MODEM Data Carrier Detect
//UART internal
output UART_BAUD, // UART baudrate output
output UART_INT // UART interrupt
);
parameter S_INIT = 0,
S_IDLE = 1,
S_READ = 2,
S_WRITE = 3;
reg [ 1:0 ] State, Next;
assign HRESP = 1'b0;
assign HREADY = (State == S_IDLE);
always @ (posedge HCLK) begin
if (~HRESETn)
State <= S_INIT;
else
State <= Next;
end
reg [ 2:0 ] ADDR_old;
wire [ 2:0 ] ADDR = HADDR [ 4:2 ];
wire [ 7:0 ] ReadData;
parameter HTRANS_IDLE = 2'b0;
wire NeedAction = HTRANS != HTRANS_IDLE && HSEL;
always @ (*) begin
//State change decision
case(State)
default : Next = S_IDLE;
S_IDLE : Next = ~NeedAction ? S_IDLE : (
HWRITE ? S_WRITE : S_READ );
endcase
end
always @ (posedge HCLK) begin
case(State)
S_INIT : ;
S_IDLE : if(HSEL) ADDR_old <= ADDR;
S_READ : HRDATA <= { 24'b0, ReadData};
S_WRITE : ;
endcase
end
wire [ 7:0 ] WriteData = HWDATA [ 7:0 ];
wire [ 2:0 ] ActionAddr;
wire WriteAction;
wire ReadAction;
reg [ 10:0 ] conf;
assign { ReadAction, WriteAction, ActionAddr } = conf;
always @ (*) begin
//io
case(State)
default : conf = { 2'b00, 8'b0 };
S_READ : conf = { 2'b10, ADDR };
S_WRITE : conf = { 2'b01, ADDR_old };
endcase
end
// Registers
uart_regs regs(
.clk ( HCLK ),
.wb_rst_i ( ~HRESETn ),
.wb_addr_i ( ActionAddr ),
.wb_dat_i ( WriteData ),
.wb_dat_o ( ReadData ),
.wb_we_i ( WriteAction ),
.wb_re_i ( ReadAction ),
.modem_inputs ( { UART_CTS, UART_DSR, UART_RI, UART_DCD } ),
.stx_pad_o ( UART_STX ),
.srx_pad_i ( UART_SRX ),
.rts_pad_o ( UART_RTS ),
.dtr_pad_o ( UART_DTR ),
.int_o ( UART_INT ),
.baud_o ( UART_BAUD )
);
endmodule
Основные особенности реализации
- в качестве базового решения взят проект [L8], который, в свою очередь, основан на [L7, L9];
- по сравнению с базовым решением из проекта исключен код, связанный с шиной Wishbone. Он заменен на соответствующий интерфейсный модуль для AHB-Lite (mfp_ahb_lite_uart16550) [S1];
- весь заимствованный из базового проекта код размещен в каталоге uart16550 [S2];
- модуль mfp_ahb_lite_uart16550 включен в состав системы на постоянной основе, опция MFP_USE_DUPLEX_UART [S3] в файле mfp_ahb_lite_matrix_config.vh определяет доступность сигналов UART_SRX и UART_STX;
- для уже имеющихся сигналов UART_RX и UART_TX предусмотрен следующий порядок использования:
UART_RX — вне зависимости от режима используется только для загрузки прошивки в память системы, как это и было ранее (модуль mfp_uart_receiver, к UART16550 не относится). Если опция MFP_USE_DUPLEX_UART не активна, то UART_TX используется для передачи данных от UART16550 наружу, прием в этом случае не доступен. Если опция MFP_USE_DUPLEX_UART активна, то UART_TX — не используются, для передачи данных из/в mfp_ahb_lite_uart16550 служат UART_SRX и UART_STX [S4]; - вывод интерфейса управления модемом на уровень top модуля не производился. Соответствующие линии доступны в интерфейсе модуля ahb_lite_uart16550 и могут быть использованы при необходимости (UART_RTS, UART_CTS, UART_DTR, UART_DSR, UART_RI, UART_DCD) [S5];
- сигнала прерывания (UART_INT) подключен к входу hw3 (сигнал SI_Int[3]) для режимов обратной совместимости и векторного. И к eic5 (сигнал EIC_input[5]) для контроллера внешних прерываний [S6];
- при запуске в режиме симуляции линия UART_STX замкнута на UART_SRX [S7];
- работоспособность полученного решения в режиме симуляции проверена с помощью Modelsim;
- работоспособность на железе проверена на плате Terasic DE10-Lite [L10];
- в качестве примера работы с UART16550 написаны две программы: 05_uart [S8] и 08_uart_irq [S9]: после сброса выполняется настройка uart (8n1, 115200), отправка приветствия после чего код каждого принятого символа отображается на LED и 7-сегментных индикаторах. При запуске на плате каждый принимаемый по uart символ отправляется обратно;
- отладка разработанного модуля доступна "в отрыве" от кода mipsfpga+ в рамках проекта [L11];
- для программного конфигурирования модуля следует использовать документацию базового проекта, [D1]. Рекомендую бегло ознакомиться с ней, чтобы лучше понимать приводимый ниже пример;
- работа с шиной AHB-Lite детально описана в [D2];
Пример
Порядок запуска
-
проверить, что в файле mfp_ahb_lite_matrix_config.vh установлена следующая настройка [S3]:
`define MFP_USE_DUPLEX_UART
- перейти в каталог с программой: mipsfpga-plus/programs/05_uart [S8], либо 08_uart_irq [S9];
-
в файле main.c установить [S10]:
#define RUNTYPE SIMULATION
-
выполнить сборку программы и ее запуск в симуляторе:
02_compile_and_link.bat 05_generate_verilog_readmemh_file.bat 06_simulate_with_modelsim.bat
- после завершения работы скрипта симуляции нажать "нет", чтобы предотвратить закрытие симулятора;
- то, как работает этот код "на железе", видно на заглавной gif-ке.
Описание программы и конфигурации системы
- так как пример 08_uart_irq предусматривает помимо настройки самого UART16550 еще и использование генерируемых им прерываний, то ниже рассматривается именно эта конфигурация;
- директивы, обеспечивающие работу с регистрами контроллера вынесены в заголовочный файл uart16550.h [S11];
-
при запуске производится настройка UART16550 [S12]:
void uartInit(uint16_t divisor) { // 8n1 uart mode MFP_UART_LCR = MFP_UART_LCR_8N1; // Divisor Latches access enable MFP_UART_LCR |= MFP_UART_LCR_LATCH; // Divisor LSB MFP_UART_DLL = divisor & 0xFF; // Divisor MSB MFP_UART_DLH = (divisor >> 8) & 0xff; // Divisor Latches access disable MFP_UART_LCR &= ~MFP_UART_LCR_LATCH; //enable Received Data available interrupt MFP_UART_IER = MFP_UART_IER_RDA; //set 4 byte Receiver FIFO Interrupt trigger level MFP_UART_FCR = MFP_UART_FCR_ITL4; }
-
настройка прерываний [S13] (детально разобрана в [L12]):
void mipsInterruptInit(void) { // Status.BEV 0 - vector interrupt mode mips32_bicsr (SR_BEV); // Cause.IV, 1 - special int vector (0x200) // where 0x200 - base for others interrupts; mips32_biscr (CR_IV); // get IntCtl reg value uint32_t intCtl = mips32_getintctl(); // set interrupt table vector spacing (0x20 in our case) // see exceptions.S for details mips32_setintctl(intCtl | INTCTL_VS_32); // interrupt enable, HW3 unmasked mips32_bissr (SR_IE | SR_HINT3); }
-
обработка прерывания предполагает проверку того, что оно вызвано именно наличием данных во входящем FIFO [S14]:
// uart interrupt handler void __attribute__ ((interrupt, keep_interrupts_masked)) __mips_isr_hw3 () { // Receiver Data available interrupt handler if(MFP_UART_IIR & MFP_UART_IIR_RDA) uartReceive(); }
-
после чего следует их чтение (до тех пор, пока FIFO не опустеет) и вывод [S15]:
void uartReceive(void) { // is there something in receiver fifo? while (MFP_UART_LSR & MFP_UART_LSR_DR) { // data receive uint8_t data = MFP_UART_RXR; receivedDataOutput(data); #if RUNTYPE == HARDWARE uartTransmit(data); #endif } }
- в случае, если работа идет на "железе", то все полученные данные отправляются обратно [S16]:
void uartTransmit(uint8_t data) { // waiting for transmitter fifo empty while (!(MFP_UART_LSR & MFP_UART_LSR_TFE)); // data transmit MFP_UART_TXR = data; }
-
ниже представлен результат работы программы. Так как при настройке модуля мы выставили режим срабатывания прерывания после приема 4х символов, то передача на время прерывается для приема. Оставшиеся символы получены нами после еще одного такого же прерывания, но которое уже было вызвано не наличием 4х символов в очереди, а не пустой очередью и таймаутом (контроллер понял, что больше символов не будет и сообщил, что очередь не пустая);
-
для программы 05_uart прием выполняется уже после завершения передачи, все это время принятые данные ждут в FIFO приемника:
Благодарности
Автор выражает благодарность коллективу переводчиков учебника Дэвида Харриса и Сары Харрис «Цифровая схемотехника и архитектура компьютера», компании Imagination Technologies за академическую лицензию на современное процессорное ядро и персонально Юрию Панчулу YuriPanchul за его работу по популяризации MIPSfpga.
Ссылки
[L1] — Цифровая схемотехника и архитектура компьютера;
[L2] — Как начать работать с MIPSfpga;
[L3] — Проект MIPSfpga-plus на github;
[L4] — Wikipedia: UART;
[L5] — Wikipedia: UART16550;
[L6] — Google: UART16550;
[L7] — Проект freecores/uart16550;
[L8] — Проект olofk/uart16550;
[L9] — Проект opencores/UART 16550 core;
[L10] -FPGA плата Terasic DE10-Lite;
[L11] — Проект ahb_lite_uart16550;
[L12] — MIPSfpga и прерывания;
Документация
[D1] — UART IP Core Specification;
[D2] — MIPS32 microAptiv UP Processor Core AHB-Lite Interface;
Изображения и таблицы
[P1] — Работа примера на отладочной плате;
[P2] — Работа в режиме обработки прерывания UART;
[P3] — Работа путем периодического опроса регистра;
Ссылки на исходный код
[S1] — Модуль mfp_ahb_lite_uart16550;
[S2] — Каталог mipsfpga-plus/uart16550;
[S3] — Опция MFP_USE_DUPLEX_UART;
[S4] — Сигналы UART_SRX и UART_STX;
[S5] — Интерфейс управления модемом;
[S6] — Подключение сигнала прерывания;
[S7] — Сигналы UART в режиме симуляции;
[S8] — Пример использования '05_uart';
[S9] — Пример использования '08_uart_irq';
[S10] — Настройка режима работы программы-примера;
[S11] — Заголовочный файл uart16550.h;
[S12] — Программная настройка UART16550;
[S13] — Настройка прерываний;
[S14] — Обработка прерывания UART;
[S15] — Чтение полученных данных UART;
[S16] — Обратная отправка принятых данных;
Автор: Stanislav Zhelnio