目錄
往期文章傳送門
一、硬件定時器
硬件實現
軟件實現
二、上板測試
往期文章傳送門
開發一個RISC-V上的操作系統(一)—— 環境搭建_riscv開發環境_Patarw_Li的博客-CSDN博客
開發一個RISC-V上的操作系統(二)—— 系統引導程序(Bootloader)_Patarw_Li的博客-CSDN博客
開發一個RISC-V上的操作系統(三)—— 串口驅動程序(UART)_Patarw_Li的博客-CSDN博客
開發一個RISC-V上的操作系統(四)—— 內存管理_Patarw_Li的博客-CSDN博客
開發一個RISC-V上的操作系統(五)—— 協作式多任務_Patarw_Li的博客-CSDN博客
開發一個RISC-V上的操作系統(六)—— 中斷(interrupt)和異常(exception)_Patarw_Li的博客-CSDN博客
本節的代碼在倉庫的?05_HW_TIMER?目錄下,倉庫鏈接:riscv_os: 一個RISC-V上的簡易操作系統
本文代碼的運行調試會在前面開發的RISC-V處理器上進行,倉庫鏈接:cpu_prj: 一個基于RISC-V指令集的CPU實現
一、硬件定時器
生活離不開對時間的管理,操作系統也是一樣。
時鐘節拍(Tick)
- 操作系統中最小的時間單位。
- Tick的單位(周期)由硬件定時器的周期決定(通常為1~100ms)。
- Tick周期越小,系統精度越高,但開銷越大。
系統時鐘?
- 操作系統維護一個整形計數值,記錄著系統啟動直到當前發生的Tick總數。
硬件實現
在本項目中,timer作為一個外設掛載在總線rib上,rtl文件為 cpu_prj\FPGA\rtl\perips\timer.v :?
五個讀寫信號用于讀寫timer模塊中的寄存器,信號?timer_int_flag_o 用于給 clint 中斷模塊發出中斷信號,verilog 代碼如下:
// 32bit 定時器
module timer(input wire clk ,input wire rst_n ,// 讀寫信號 input wire wr_en_i , // write enableinput wire[`INST_ADDR_BUS] wr_addr_i , // write addressinput wire[`INST_REG_DATA] wr_data_i , // write datainput wire[`INST_ADDR_BUS] rd_addr_i , // read addressoutput reg [`INST_REG_DATA] rd_data_o , // read data// 中斷信號output wire timer_int_flag_o );localparam TIMER_CTRL = 4'h0;localparam TIMER_COUNT = 4'h4;localparam TIMER_EVALUE = 4'h8;// [0]: timer enable// [1]: timer int enable// [2]: timer int pending, software write 0 to clear it// addr offset: 0x00reg[31:0] timer_ctrl;// timer current count, read only// addr offset: 0x04reg[31:0] timer_count;// timer expired value// addr offset: 0x08reg[31:0] timer_evalue;assign timer_int_flag_o = ((timer_ctrl[2] == 1'b1) && (timer_ctrl[1] == 1'b1))? 1'b1 : 1'b0;// 讀寫寄存器,write before readalways @ (posedge clk or negedge rst_n) beginif (!rst_n) begintimer_ctrl <= `ZERO_WORD;timer_evalue <= `ZERO_WORD;endelse beginif (wr_en_i == 1'b1) begincase (wr_addr_i[3:0])TIMER_CTRL: begin// 這里代表軟件只能把 timer_ctrl[2]置0,無法將其置1timer_ctrl = {wr_data_i[31:3], (timer_ctrl[2] & wr_data_i[2]), wr_data_i[1:0]};endTIMER_EVALUE: begintimer_evalue = wr_data_i;endendcaseendif(timer_ctrl[0] == 1'b1 && timer_count >= timer_evalue) begintimer_ctrl[0] = 1'b0;timer_ctrl[2] = 1'b1;endcase (rd_addr_i[3:0])TIMER_CTRL: beginrd_data_o = timer_ctrl;endTIMER_COUNT: beginrd_data_o = timer_count;endTIMER_EVALUE: beginrd_data_o = timer_evalue;enddefault: beginrd_data_o = `ZERO_WORD;endendcaseendend// 計數器 timer_countalways @ (posedge clk or negedge rst_n) beginif (!rst_n) begintimer_count <= `ZERO_WORD;endelse beginif (timer_ctrl[0] != 1'b1 || timer_count >= timer_evalue) begintimer_count <= `ZERO_WORD;endelse begintimer_count <= timer_count + 1'b1;endendendendmodule
其中:
timer_ctrl?為控制寄存器,低三位有效,分別是第0位?timer enable ,置1則 timer_count 開始計時;第1位 timer int enable,置1則允許發出中斷信號,反之則不允許;第2位 timer int pending,當 timer_count >= timer_evalue 時,就把該位置1,表示有中斷信號要發出,需要軟件置0。
timer_count 為計數寄存器(只讀)。
timer_evalue 存放過期值,用來與?timer_count 寄存器比較,當 timer_count >= timer_evalue 時則發出中斷信號。
軟件實現
代碼實現為 riscv_os/05_HW_TIMER/timer.c :
// 1s
#define TIMER_INTERVAL 50000000/** The TIMER control registers are memory-mapped at address TIMER (defined in inc/platform.h). * This macro returns the address of one of the registers.*/
#define TIMER_REG_ADDRESS(reg) ((volatile uint32_t *) (TIMER + reg))/** TIMER registers map* timer_count is a read-only reg*/
#define TIMER_CTRL 0
#define TIMER_COUNT 4
#define TIMER_EVALUE 8#define timer_read_reg(reg) (*(TIMER_REG_ADDRESS(reg)))
#define timer_write_reg(reg, data) (*(TIMER_REG_ADDRESS(reg)) = (data))#define TIMER_EN 1 << 0
#define TIMER_INT_EN 1 << 1
#define TIMER_INT_PENDING 1 << 2static uint32_t _tick = 0;void timer_load(uint32_t interval)
{timer_write_reg(TIMER_EVALUE, interval);timer_write_reg(TIMER_CTRL, (timer_read_reg(TIMER_CTRL) | (TIMER_EN)));
}/** enable timer interrupt*/
void timer_init()
{timer_write_reg(TIMER_CTRL, (timer_read_reg(TIMER_CTRL) | (TIMER_INT_EN)));timer_load(TIMER_INTERVAL);
}void timer_handler()
{timer_write_reg(TIMER_CTRL, (timer_read_reg(TIMER_CTRL) & ~(TIMER_INT_PENDING)));_tick++;printf("tick: %d\n", _tick);timer_load(TIMER_INTERVAL);
}
其中:
_tick?為該模塊維護的全局時間節拍。
timer_load(uint32_t interval) 函數用于給定時器模塊寄存器賦值,interval 個硬件時鐘周期后發出定時器中斷(如果 interval = 板子系統時鐘頻率,相當于1s)。
timer_init() 函數用于給定時器模塊寄存器初始化。
timer_handler() 函數用于執行定時器中斷處理,當定時器中斷發生的時候,執行這個函數的內容。該函數會將 _tick 值加一后,執行 timer_load(uint32_t interval) 函數,從而達到持續計數的功能。
二、上板測試
燒錄到板子上后,打開串口調試程序,可以看到tick值一直在計數,從而實現系統時鐘的功能:
遇到問題歡迎加群 892873718 交流~?