目錄
文章傳送門
一、什么是串口
二、本項目串口的FPGA實現(xiàn)
三、串口驅(qū)動程序的編寫
四、上板測試
文章傳送門
開發(fā)一個RISC-V上的操作系統(tǒng)(一)—— 環(huán)境搭建_riscv開發(fā)環(huán)境_Patarw_Li的博客-CSDN博客
開發(fā)一個RISC-V上的操作系統(tǒng)(二)—— 系統(tǒng)引導(dǎo)程序(Bootloader)_Patarw_Li的博客-CSDN博客
開發(fā)一個RISC-V上的操作系統(tǒng)(三)—— 串口驅(qū)動程序(UART)_Patarw_Li的博客-CSDN博客
一、什么是串口
串口(UART)又名異步收發(fā)傳輸器(Universal Asynchronous Receiver/Transmitter),是一種通用的數(shù)據(jù)通信協(xié)議,也是異步串行通信口(串口)的總稱,它在發(fā)送數(shù)據(jù)時將并行數(shù)據(jù)轉(zhuǎn)換成串行數(shù)據(jù)來傳輸,在接收數(shù)據(jù)時將串行數(shù)據(jù)轉(zhuǎn)換成并行數(shù)據(jù)。SPI和I2C為同步通信接口,雙方時鐘頻率相同,而UART屬于異步通信接口,沒有統(tǒng)一時鐘,靠起始位和終止位來接收數(shù)據(jù)。
串口包括RS232、RS499、RS423等接口標準規(guī)范,我們這里使用的是RS232:
上圖為串口的通信方式,可以同時收發(fā)(全雙工通信)。其中rx負責(zé)接收,tx負責(zé)發(fā)送,每次發(fā)送10bit數(shù)據(jù)(起始位+8bit數(shù)據(jù)+停止位),從最低位開始發(fā)送。?
二、本項目串口的FPGA實現(xiàn)
在寫串口的驅(qū)動程序之前,我們首先要知道如何與開發(fā)板上的串口進行交互,所以我們要先看看我們riscv cpu項目是怎么實現(xiàn)串口模塊的。
項目倉庫:
cpu_prj: 一個基于RISC-V指令集的CPU實現(xiàn)
串口模塊的實現(xiàn)在?FPGA/rtl/perips/目錄下的uart.v文件中,它作為一個外設(shè)掛載在rib總線上:
在總線模塊 FPGA/rtl/core/rib.v中可以看到, uart.v外設(shè)的地址范圍為0x2000_0000 ~ 0x2fff_ffff,用于訪問uart模塊中的寄存器(實際uart模塊只有三個寄存器,不需要這么大空間,但是影響不大):
下面是uart.v的代碼。其中uart_rx和uart_tx引腳為串口接收和發(fā)送引腳;最下面五個信號則用于讀寫串口寄存器。
// 串口模塊,默認波特率為9600
module uart(
input wire clk ,
input wire rst_n ,
input wire uart_rx , // uart接收引腳
output reg uart_tx , // uart發(fā)送引腳
input wire wr_en_i , // uart寄存器寫使能信號
input wire[`INST_ADDR_BUS] wr_addr_i , // uart寄存器寫地址
input wire[`INST_DATA_BUS] wr_data_i , // uart寫數(shù)據(jù)
input wire[`INST_ADDR_BUS] rd_addr_i , // uart寄存器讀地址
output reg [`INST_DATA_BUS] rd_data_o // uart讀數(shù)據(jù)
);
// 寄存器地址定義
parameter UART_CTRL = 4'd0,
UART_TX_DATA_BUF= 4'd4,
UART_RX_DATA_BUF= 4'd8;
// addr: 0x0
// 低兩位(1:0)為TI和RI
// TI:發(fā)送完成位,該位在數(shù)據(jù)發(fā)送完成時被設(shè)置為高電平
// RI:接收完成位,該位在數(shù)據(jù)接收完成時被設(shè)置為高電平
reg[31:0] uart_ctrl;
// addr: 0x4
// 發(fā)送數(shù)據(jù)寄存器
reg[31:0] uart_tx_data_buf;
// addr: 0x8
// 接收數(shù)據(jù)寄存器
reg[31:0] uart_rx_data_buf;
parameter BAUD_CNT_MAX = `CLK_FREQ / `UART_BPS;
parameter IDLE = 4'd0,
BEGIN= 4'd1,
RX_BYTE = 4'd2,
TX_BYTE = 4'd3,
END = 4'd4;
wire uart_rx_temp;
reg uart_rx_delay; // rx延遲后的輸入
reg[3:0] uart_rx_state; // rx狀態(tài)機
reg[12:0] rx_baud_cnt; // rx計數(shù)器
reg[3:0] rx_bit_cnt; // rx比特計數(shù)
reg[3:0] uart_tx_state; // rx狀態(tài)機
reg[12:0] tx_baud_cnt; // rx計數(shù)器
reg[3:0] tx_bit_cnt; // rx比特計數(shù)
reg tx_data_rd; // 發(fā)送數(shù)據(jù)就緒信號
// 讀寫寄存器,write before read
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
uart_ctrl <= `ZERO_WORD;
uart_tx_data_buf <= `ZERO_WORD;
end
else begin
if(wr_en_i == 1'b1) begin
case(wr_addr_i[3:0])
UART_CTRL: begin
uart_ctrl = wr_data_i;
end
UART_TX_DATA_BUF: begin
uart_tx_data_buf = wr_data_i;
end
default: begin
end
endcase
end
if(uart_tx_state == END && tx_baud_cnt == 1) begin
uart_ctrl[1] = 1'b1; // TI置1,代表發(fā)送完畢,需要軟件置0
end
if(uart_rx_state == END && rx_baud_cnt == 1) begin
uart_ctrl[0] = 1'b1; // RI置1,代表接收完畢,需要軟件置0
end
case(rd_addr_i[3:0])
UART_CTRL: begin
rd_data_o = uart_ctrl;
end
UART_TX_DATA_BUF: begin
rd_data_o = uart_tx_data_buf;
end
UART_RX_DATA_BUF: begin
rd_data_o = uart_rx_data_buf;
end
default: begin
rd_data_o = `ZERO_WORD;
end
endcase
end
end
/* TX發(fā)送模塊 */
// tx數(shù)據(jù)就緒信號 tx_data_rd
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
tx_data_rd <= 1'b0;
end
else if(wr_en_i == 1'b1 && wr_addr_i[3:0] == UART_TX_DATA_BUF) begin
tx_data_rd <= 1'b1;
end
else if(uart_tx_state == END && tx_baud_cnt == 1) begin
tx_data_rd <= 1'b0;
end
else begin
tx_data_rd <= tx_data_rd;
end
end
// tx_baud_cnt計數(shù)
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
tx_baud_cnt <= 13'd0;
end
else if(uart_tx_state == IDLE || tx_baud_cnt == BAUD_CNT_MAX - 1) begin
tx_baud_cnt <= 13'd0;
end
else begin
tx_baud_cnt <= tx_baud_cnt + 1'b1;
end
end
// TX發(fā)送模塊
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
uart_tx_state <= IDLE;
tx_bit_cnt <= 4'd0;
uart_tx <= 1'b1;
end
else begin
case(uart_tx_state)
IDLE: begin
uart_tx <= 1'b1;
if(tx_data_rd == 1'b1) begin
uart_tx_state <= BEGIN;
end
else begin
uart_tx_state <= uart_tx_state;
end
end
BEGIN: begin
uart_tx <= 1'b0;
if(tx_baud_cnt == BAUD_CNT_MAX - 1) begin
uart_tx_state <= TX_BYTE;
end
else begin
uart_tx_state <= uart_tx_state;
end
end
TX_BYTE: begin
if(tx_bit_cnt == 4'd7 && tx_baud_cnt == BAUD_CNT_MAX - 1) begin
tx_bit_cnt <= 4'd0;
uart_tx_state <= END;
end
else if(tx_baud_cnt == BAUD_CNT_MAX - 1) begin
tx_bit_cnt <= tx_bit_cnt + 1'b1;
end
else begin
uart_tx <= uart_tx_data_buf[tx_bit_cnt];
end
end
END: begin
uart_tx <= 1'b1;
if(tx_baud_cnt == BAUD_CNT_MAX - 1) begin
uart_tx_state <= IDLE;
end
else begin
uart_tx_state <= uart_tx_state;
end
end
default: begin
uart_tx_state <= IDLE;
tx_bit_cnt <= 4'd0;
uart_tx <= 1'b1;
end
endcase
end
end
/* RX接收模塊 */
// 將輸入rx延遲4個時鐘周期,減少亞穩(wěn)態(tài)的影響
delay_buffer #(
.DEPTH(4),
.DATA_WIDTH(1)
) u_delay_buffer(
.clk (clk), // Master Clock
.data_i (uart_rx), // Data Input
.data_o (uart_rx_temp) // Data Output
);
always @ (posedge clk) begin
uart_rx_delay <= uart_rx_temp;
end
// rx_baud_cnt計數(shù)
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
rx_baud_cnt <= 13'd0;
end
else if(uart_rx_state == IDLE || rx_baud_cnt == BAUD_CNT_MAX - 1) begin
rx_baud_cnt <= 13'd0;
end
else begin
rx_baud_cnt <= rx_baud_cnt + 1'b1;
end
end
// RX接收模塊
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
uart_rx_state <= IDLE;
rx_bit_cnt <= 4'd0;
uart_rx_data_buf <= `ZERO_WORD;
end
else begin
case(uart_rx_state)
IDLE: begin
if(uart_rx_temp == 1'b0 && uart_rx_delay == 1'b1) begin
uart_rx_state <= BEGIN;
end
else begin
uart_rx_state <= uart_rx_state;
end
end
BEGIN: begin
if(rx_baud_cnt == BAUD_CNT_MAX - 1) begin
uart_rx_state <= RX_BYTE;
end
else begin
uart_rx_state <= uart_rx_state;
end
end
RX_BYTE: begin
if(rx_bit_cnt == 4'd7 && rx_baud_cnt == BAUD_CNT_MAX - 1) begin
rx_bit_cnt <= 4'd0;
uart_rx_state <= END;
end
else if(rx_baud_cnt == BAUD_CNT_MAX / 2 - 1) begin
uart_rx_data_buf[rx_bit_cnt] <= uart_rx_delay;
end
else if(rx_baud_cnt == BAUD_CNT_MAX - 1) begin
rx_bit_cnt <= rx_bit_cnt + 1'b1;
end
else begin
uart_rx_state <= uart_rx_state;
end
end
END: begin
if(rx_baud_cnt == 1) begin
uart_rx_state <= IDLE;
end
else begin
uart_rx_state <= uart_rx_state;
end
end
default: begin
uart_rx_state <= IDLE;
rx_bit_cnt <= 4'd0;
uart_rx_data_buf <= `ZERO_WORD;
end
endcase
end
end
endmodule
在串口模塊uart.v中定義了三個寄存器,分別為 串口控制寄存器 uart_ctrl、 串口發(fā)送數(shù)據(jù)緩存寄存器 uart_tx_data_buf、?串口接收數(shù)據(jù)緩存寄存器 uart_rx_data_buf,它們的作用分別是:
- ?uart_ctrl:串口控制寄存器,只有最低兩位有效,分別為TI和RI。TI,發(fā)送完成標志位,該位在數(shù)據(jù)發(fā)送完成時被設(shè)置為1;RI,接收完成標志位,該位在數(shù)據(jù)接收完成時被設(shè)置為高電平。在數(shù)據(jù)發(fā)送/接收完成后由uart模塊將TI/RI置1,這樣驅(qū)動程序就可以通過讀取該寄存器的TI/RI位來確定數(shù)據(jù)是否發(fā)送/接收完成,這兩位需要軟件復(fù)位。
- uart_tx_data_buf:串口發(fā)送數(shù)據(jù)緩存寄存器,只要uart_tx_data_buf寄存器有數(shù)據(jù)寫入,就開始發(fā)送數(shù)據(jù),發(fā)送完畢將TI置1。
- uart_rx_data_buf:串口接收數(shù)據(jù)緩存寄存器,用于接收用戶通過rx端口發(fā)送的數(shù)據(jù),接收完就將RI置1。
它們的地址偏移分別是0,4,8,實際的物理地址為0x20000000,0x20000004,0x20000008:
在了解上述內(nèi)容后,我們就可以開始編寫串口驅(qū)動程序了。
三、串口驅(qū)動程序的編寫
源碼放在我的gitee倉庫,歡迎star:
riscv_os: 一個RISC-V上的簡易操作系統(tǒng)
代碼在?01_UART?目錄下,目錄結(jié)構(gòu)為:
inc目錄存放頭文件,其中platform.h里定義了串口設(shè)備首地址UART:
uart.c即為我們的串口驅(qū)動程序,代碼內(nèi)容如下:?
#define UART_REG_ADDRESS(reg) ((uint8_t *) (UART + reg))
/*
* UART registers map
*/
#define UART_CTRL 0
#define UART_TX_DATA_BUF 4
#define UART_RX_DATA_BUF 8
#define uart_read_reg(reg) (*(UART_REG_ADDRESS(reg)))
#define uart_write_reg(reg, data) (*(UART_REG_ADDRESS(reg)) = (data))
void uart_init()
{
// init uart_ctrl reg to 0
uart_write_reg(UART_CTRL, 0x00);
}
void uart_putc(char ch)
{
// fill send buf
uart_write_reg(UART_TX_DATA_BUF, ch);
// wait send over
while((uart_read_reg(UART_CTRL) & (1 << 1)) != (1 << 1)){}
// set TI to 0
uart_write_reg(UART_CTRL, (uart_read_reg(UART_CTRL) & ~(1 << 1)));
}
void uart_puts(char *s)
{
while(*s){
uart_putc(*s++);
}
}
char uart_getc()
{
// wait RI to 1
while((uart_read_reg(UART_CTRL) & (1 << 0)) != (1 << 0)){}
// set RI to 0
uart_write_reg(UART_CTRL, (uart_read_reg(UART_CTRL) & ~(1 << 0)));
// read receive buf
return uart_read_reg(UART_RX_DATA_BUF);
}
void uart_gets(char *s, uint8_t len){
uint8_t i = 0;
while(i < len - 1 && (*s = uart_getc()) != '\n'){
++i;
++s;
}
*(s + 1) = '\0';
}
其中第一行定義的宏UART_REG_ADDRESS(reg)用于取得對應(yīng)寄存器reg的地址,下面三個宏UART_CTRL,UART_TX_DATA_BUF,UART_RX_DATA_BUF 即為寄存器的偏移地址,例如uart_ctrl寄存器的地址為 (UART +?UART_CTRL ) = 0x20000000 + 0。
宏函數(shù)uart_read_reg(reg)用于讀取對應(yīng)reg的內(nèi)容,uart_write_reg(reg, data)則用于將data寫入對應(yīng)reg。
uart_init()函數(shù)很簡單,用于初始化uart_ctrl寄存器。
uart_putc(char ch)函數(shù)用于發(fā)送一個字符給上位機。首先會把要發(fā)的內(nèi)容寫入uart_tx_data_buf;寫入后串口模塊會自動發(fā)送數(shù)據(jù),發(fā)送完成后TI會置1,在此期間該函數(shù)會一直輪詢查看TI是否置1,若TI置1則代表發(fā)送完成。發(fā)送完成后需要將TI置0。
void uart_putc(char ch)
{
// fill send buf
uart_write_reg(UART_TX_DATA_BUF, ch);
// wait send over
while((uart_read_reg(UART_CTRL) & (1 << 1)) != (1 << 1)){}
// set TI to 0
uart_write_reg(UART_CTRL, (uart_read_reg(UART_CTRL) & ~(1 << 1)));
}
uart_getc()函數(shù)用于接收上位機發(fā)過來的一個字符。因為只有RI為1才代表有一個字節(jié)數(shù)據(jù)需要接收,所以在RI為0的時候需要一直循環(huán)等待;待RI為1后,先把RI復(fù)位(置0),然后再讀取uart_rx_data_buf的內(nèi)容,得到上位機發(fā)過來的一個字符數(shù)據(jù)。
char uart_getc()
{
// wait RI to 1
while((uart_read_reg(UART_CTRL) & (1 << 0)) != (1 << 0)){}
// set RI to 0
uart_write_reg(UART_CTRL, (uart_read_reg(UART_CTRL) & ~(1 << 0)));
// read receive buf
return uart_read_reg(UART_RX_DATA_BUF);
}
uart_puts()和uart_gets()則用于連續(xù)獲取一組字符得到一個字符串,是通過連續(xù)調(diào)用上面兩個函數(shù)實現(xiàn)的。
至此,uart驅(qū)動程序的內(nèi)容已經(jīng)講解完畢,接下來調(diào)用試試看。
四、上板測試
首先我們看看主程序里面的內(nèi)容:
void start_kernel(void){
/* User code begin */
printf("Hello %d", 110);
printf("World!\n");
char msg[20] = "";
while(1){
uart_gets(msg, 20);
uart_puts(msg);
}
/* User code end */
while(1){}; // stop here!
}
可以看到這是一個串口回環(huán),會把我們上位機發(fā)送的msg又回傳到上位機。接下來我們燒錄到板子上看看,在燒錄前確保你的板子已經(jīng)把我的riscv cpu跑起來了,可以看我前面的文章。
首先執(zhí)行make得到os.bin文件,然后通過python燒錄程序把os.bin燒錄到處理器的memory中,燒錄完后我們需要用串口工具來調(diào)試,可以下載我上傳到cpu_prj倉庫里面的串口工具:
打開串口調(diào)試工具,設(shè)置好參數(shù)后點擊右下角的打開按鈕:
打開串口后,按下復(fù)位鍵即可看到串口輸出內(nèi)容?,輸出的是printf函數(shù)打印的內(nèi)容(pirntf函數(shù)也調(diào)用了uart_puts函數(shù),這里就不細講了,感興趣的話可以去看printf.c文件的內(nèi)容):
在下面的輸入框內(nèi)輸入內(nèi)容,最后加一個回車(內(nèi)容最后一定要加一個回車,uart_gets()函數(shù)是通過回車符號來判斷內(nèi)容的結(jié)束的),然后點擊發(fā)送即可看到上面顯示你發(fā)送的內(nèi)容:
至此,串口驅(qū)動程序?qū)嶒灲Y(jié)束,實現(xiàn)了串口,我們之后開發(fā)調(diào)試都會方便很多~文章來源:http://www.zghlxwxcb.cn/news/detail-607090.html
遇到問題歡迎加群 892873718 交流~文章來源地址http://www.zghlxwxcb.cn/news/detail-607090.html
到了這里,關(guān)于開發(fā)一個RISC-V上的操作系統(tǒng)(三)—— 串口驅(qū)動程序(UART)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!