青海省城乡建设厅网站首页上的杭州品牌策划公司,具体负责哪些业务?
摘要:青海省城乡建设厅网站首页,杭州品牌策划公司,公司网页,php响应式网站开发百度云目录 文章传送门 一、什么是串口 二、本项目串口的FPGA实现 三、串口驱动程序的编写 四、上板测试 文章传送门 开发一个RISC-V上的操作系统&am
青海省城乡建设厅网站首页,杭州品牌策划公司,公司网页,php响应式网站开发百度云目录
文章传送门
一、什么是串口
二、本项目串口的FPGA实现
三、串口驱动程序的编写
四、上板测试 文章传送门
开发一个RISC-V上的操作系统#xff08;一#xff09;—— 环境搭建_riscv开发环境_Patarw_Li的博客-CSDN博客
开发一个RISC-V上的操作系统#xff08;二一—— 环境搭建_riscv开发环境_Patarw_Li的博客-CSDN博客
开发一个RISC-V上的操作系统二—— 系统引导程序Bootloader_Patarw_Li的博客-CSDN博客
开发一个RISC-V上的操作系统三—— 串口驱动程序UART_Patarw_Li的博客-CSDN博客
一、什么是串口
串口UART又名异步收发传输器Universal Asynchronous Receiver/Transmitter是一种通用的数据通信协议也是异步串行通信口串口的总称它在发送数据时将并行数据转换成串行数据来传输在接收数据时将串行数据转换成并行数据。SPI和I2C为同步通信接口双方时钟频率相同而UART属于异步通信接口没有统一时钟靠起始位和终止位来接收数据。
串口包括RS232、RS499、RS423等接口标准规范我们这里使用的是RS232
上图为串口的通信方式可以同时收发全双工通信。其中rx负责接收tx负责发送每次发送10bit数据起始位8bit数据停止位从最低位开始发送。
二、本项目串口的FPGA实现
在写串口的驱动程序之前我们首先要知道如何与开发板上的串口进行交互所以我们要先看看我们riscv cpu项目是怎么实现串口模块的。
项目仓库
cpu_prj: 一个基于RISC-V指令集的CPU实现
串口模块的实现在 FPGA/rtl/perips/目录下的uart.v文件中它作为一个外设挂载在rib总线上 在总线模块 FPGA/rtl/core/rib.v中可以看到 uart.v外设的地址范围为0x1000_0000 ~ 0x1fff_ffff用于访问uart模块中的寄存器实际uart模块只有三个寄存器不需要这么大空间但是影响不大 下面是uart.v的代码。其中uart_rx和uart_tx引脚为串口接收和发送引脚最下面五个信号则用于读写串口寄存器。
// 串口模块默认波特率为9600
module uart(input wire clk ,input wire rst_n ,input wire uart_rx , // uart接收引脚output reg uart_tx , // uart发送引脚input wire wr_en_i , // uart寄存器写使能信号input wire[INST_ADDR_BUS] wr_addr_i , // uart寄存器写地址input wire[INST_DATA_BUS] wr_data_i , // uart写数据input wire[INST_ADDR_BUS] rd_addr_i , // uart寄存器读地址output reg [INST_DATA_BUS] rd_data_o // uart读数据);// 寄存器地址定义parameter UART_CTRL 4d0,UART_TX_DATA_BUF 4d4,UART_RX_DATA_BUF 4d8;// addr: 0x0// 低两位1:0为TI和RI// TI发送完成位该位在数据发送完成时被设置为高电平// RI接收完成位该位在数据接收完成时被设置为高电平reg[31:0] uart_ctrl;// addr: 0x4// 发送数据寄存器reg[31:0] uart_tx_data_buf;// addr: 0x8// 接收数据寄存器reg[31:0] uart_rx_data_buf;parameter BAUD_CNT_MAX CLK_FREQ / UART_BPS;parameter IDLE 4d0,BEGIN 4d1,RX_BYTE 4d2,TX_BYTE 4d3,END 4d4;wire uart_rx_temp;reg uart_rx_delay; // rx延迟后的输入reg[3:0] uart_rx_state; // rx状态机reg[12:0] rx_baud_cnt; // rx计数器reg[3:0] rx_bit_cnt; // rx比特计数reg[3:0] uart_tx_state; // rx状态机reg[12:0] tx_baud_cnt; // rx计数器reg[3:0] tx_bit_cnt; // rx比特计数reg tx_data_rd; // 发送数据就绪信号// 读写寄存器write before readalways (posedge clk or negedge rst_n) beginif(!rst_n) beginuart_ctrl ZERO_WORD;uart_tx_data_buf ZERO_WORD;endelse beginif(wr_en_i 1b1) begincase(wr_addr_i[3:0])UART_CTRL: beginuart_ctrl wr_data_i;endUART_TX_DATA_BUF: beginuart_tx_data_buf wr_data_i;enddefault: beginendendcaseendif(uart_tx_state END tx_baud_cnt 1) beginuart_ctrl[1] 1b1; // TI置1代表发送完毕需要软件置0endif(uart_rx_state END rx_baud_cnt 1) beginuart_ctrl[0] 1b1; // RI置1代表接收完毕需要软件置0endcase(rd_addr_i[3:0])UART_CTRL: beginrd_data_o uart_ctrl;endUART_TX_DATA_BUF: beginrd_data_o uart_tx_data_buf;endUART_RX_DATA_BUF: beginrd_data_o uart_rx_data_buf;enddefault: beginrd_data_o ZERO_WORD;endendcaseendend/* TX发送模块 */// tx数据就绪信号 tx_data_rdalways (posedge clk or negedge rst_n) beginif(!rst_n) begintx_data_rd 1b0; endelse if(wr_en_i 1b1 wr_addr_i[3:0] UART_TX_DATA_BUF) begintx_data_rd 1b1;endelse if(uart_tx_state END tx_baud_cnt 1) begintx_data_rd 1b0;endelse begintx_data_rd tx_data_rd;endend// tx_baud_cnt计数always (posedge clk or negedge rst_n) beginif(!rst_n) begin tx_baud_cnt 13d0;endelse if(uart_tx_state IDLE || tx_baud_cnt BAUD_CNT_MAX - 1) begintx_baud_cnt 13d0;endelse begintx_baud_cnt tx_baud_cnt 1b1;endend// TX发送模块always (posedge clk or negedge rst_n) beginif(!rst_n) beginuart_tx_state IDLE;tx_bit_cnt 4d0;uart_tx 1b1;endelse begincase(uart_tx_state)IDLE: beginuart_tx 1b1;if(tx_data_rd 1b1) beginuart_tx_state BEGIN; endelse beginuart_tx_state uart_tx_state;endendBEGIN: beginuart_tx 1b0;if(tx_baud_cnt BAUD_CNT_MAX - 1) beginuart_tx_state TX_BYTE; endelse beginuart_tx_state uart_tx_state;endendTX_BYTE: beginif(tx_bit_cnt 4d7 tx_baud_cnt BAUD_CNT_MAX - 1) begintx_bit_cnt 4d0;uart_tx_state END; endelse if(tx_baud_cnt BAUD_CNT_MAX - 1) begintx_bit_cnt tx_bit_cnt 1b1; endelse beginuart_tx uart_tx_data_buf[tx_bit_cnt];endendEND: beginuart_tx 1b1;if(tx_baud_cnt BAUD_CNT_MAX - 1) beginuart_tx_state IDLE; endelse beginuart_tx_state uart_tx_state;endenddefault: beginuart_tx_state IDLE;tx_bit_cnt 4d0;uart_tx 1b1;endendcaseendend/* RX接收模块 */// 将输入rx延迟4个时钟周期减少亚稳态的影响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) beginuart_rx_delay uart_rx_temp;end// rx_baud_cnt计数always (posedge clk or negedge rst_n) beginif(!rst_n) begin rx_baud_cnt 13d0;endelse if(uart_rx_state IDLE || rx_baud_cnt BAUD_CNT_MAX - 1) beginrx_baud_cnt 13d0;endelse beginrx_baud_cnt rx_baud_cnt 1b1;endend// RX接收模块always (posedge clk or negedge rst_n) beginif(!rst_n) beginuart_rx_state IDLE;rx_bit_cnt 4d0;uart_rx_data_buf ZERO_WORD;endelse begincase(uart_rx_state)IDLE: beginif(uart_rx_temp 1b0 uart_rx_delay 1b1) beginuart_rx_state BEGIN; endelse beginuart_rx_state uart_rx_state;endendBEGIN: beginif(rx_baud_cnt BAUD_CNT_MAX - 1) beginuart_rx_state RX_BYTE; endelse beginuart_rx_state uart_rx_state;endendRX_BYTE: beginif(rx_bit_cnt 4d7 rx_baud_cnt BAUD_CNT_MAX - 1) beginrx_bit_cnt 4d0;uart_rx_state END; endelse if(rx_baud_cnt BAUD_CNT_MAX / 2 - 1) beginuart_rx_data_buf[rx_bit_cnt] uart_rx_delay;endelse if(rx_baud_cnt BAUD_CNT_MAX - 1) beginrx_bit_cnt rx_bit_cnt 1b1; endelse beginuart_rx_state uart_rx_state;endendEND: beginif(rx_baud_cnt 1) beginuart_rx_state IDLE; endelse beginuart_rx_state uart_rx_state;endenddefault: beginuart_rx_state IDLE;rx_bit_cnt 4d0;uart_rx_data_buf ZERO_WORD;endendcaseendendendmodule
在串口模块uart.v中定义了三个寄存器分别为 串口控制寄存器 uart_ctrl、 串口发送数据缓存寄存器 uart_tx_data_buf、 串口接收数据缓存寄存器 uart_rx_data_buf它们的作用分别是 uart_ctrl串口控制寄存器只有最低两位有效分别为TI和RI。TI发送完成标志位该位在数据发送完成时被设置为1RI接收完成标志位该位在数据接收完成时被设置为高电平。在数据发送/接收完成后由uart模块将TI/RI置1这样驱动程序就可以通过读取该寄存器的TI/RI位来确定数据是否发送/接收完成这两位需要软件复位。uart_tx_data_buf串口发送数据缓存寄存器只要uart_tx_data_buf寄存器有数据写入就开始发送数据发送完毕将TI置1。uart_rx_data_buf串口接收数据缓存寄存器用于接收用户通过rx端口发送的数据接收完就将RI置1。
它们的地址偏移分别是048实际的物理地址为0x100000000x100000040x10000008 在了解上述内容后我们就可以开始编写串口驱动程序了。
三、串口驱动程序的编写
源码放在我的gitee仓库欢迎star
riscv_os: 一个RISC-V上的简易操作系统
代码在 01_UART 目录下目录结构为 inc目录存放头文件其中platform.h里定义了串口设备首地址UART uart.c即为我们的串口驱动程序代码内容如下
#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 0uart_write_reg(UART_CTRL, 0x00);
}void uart_putc(char ch)
{ // fill send bufuart_write_reg(UART_TX_DATA_BUF, ch);// wait send overwhile((uart_read_reg(UART_CTRL) (1 1)) ! (1 1)){}// set TI to 0uart_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 1while((uart_read_reg(UART_CTRL) (1 0)) ! (1 0)){}// set RI to 0uart_write_reg(UART_CTRL, (uart_read_reg(UART_CTRL) ~(1 0)));// read receive bufreturn 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)用于取得对应寄存器reg的地址下面三个宏UART_CTRLUART_TX_DATA_BUFUART_RX_DATA_BUF 即为寄存器的偏移地址例如uart_ctrl寄存器的地址为 (UART UART_CTRL ) 0x10000000 0。
宏函数uart_read_reg(reg)用于读取对应reg的内容uart_write_reg(reg, data)则用于将data写入对应reg。
uart_init()函数很简单用于初始化uart_ctrl寄存器。
uart_putc(char ch)函数用于发送一个字符给上位机。首先会把要发的内容写入uart_tx_data_buf写入后串口模块会自动发送数据发送完成后TI会置1在此期间该函数会一直轮询查看TI是否置1若TI置1则代表发送完成。发送完成后需要将TI置0。
void uart_putc(char ch)
{ // fill send bufuart_write_reg(UART_TX_DATA_BUF, ch);// wait send overwhile((uart_read_reg(UART_CTRL) (1 1)) ! (1 1)){}// set TI to 0uart_write_reg(UART_CTRL, (uart_read_reg(UART_CTRL) ~(1 1)));
}
uart_getc()函数用于接收上位机发过来的一个字符。因为只有RI为1才代表有一个字节数据需要接收所以在RI为0的时候需要一直循环等待待RI为1后先把RI复位置0然后再读取uart_rx_data_buf的内容得到上位机发过来的一个字符数据。
char uart_getc()
{// wait RI to 1while((uart_read_reg(UART_CTRL) (1 0)) ! (1 0)){}// set RI to 0uart_write_reg(UART_CTRL, (uart_read_reg(UART_CTRL) ~(1 0)));// read receive bufreturn uart_read_reg(UART_RX_DATA_BUF);
}
uart_puts()和uart_gets()则用于连续获取一组字符得到一个字符串是通过连续调用上面两个函数实现的。
至此uart驱动程序的内容已经讲解完毕接下来调用试试看。
四、上板测试
首先我们看看主程序里面的内容
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!
}
可以看到这是一个串口回环会把我们上位机发送的msg又回传到上位机。接下来我们烧录到板子上看看在烧录前确保你的板子已经把我的riscv cpu跑起来了可以看我前面的文章。
首先执行make得到os.bin文件然后通过python烧录程序把os.bin烧录到处理器的memory中烧录完后我们需要用串口工具来调试可以下载我上传到cpu_prj仓库里面的串口工具 打开串口调试工具设置好参数后点击右下角的打开按钮 打开串口后按下复位键即可看到串口输出内容 输出的是printf函数打印的内容pirntf函数也调用了uart_puts函数这里就不细讲了感兴趣的话可以去看printf.c文件的内容 在下面的输入框内输入内容最后加一个回车内容最后一定要加一个回车uart_gets()函数是通过回车符号来判断内容的结束的然后点击发送即可看到上面显示你发送的内容 至此串口驱动程序实验结束实现了串口我们之后开发调试都会方便很多~
遇到问题欢迎加群 892873718 交流~
