开发一个RISC-V上的操作系统(三)—— 串口驱动程序(UART)
目录
文章传送门
一、什么是串口
二、本项目串口的FPGA实现
三、串口驱动程序的编写
四、上板测试
文章传送门
开发一个RISC-V上的操作系统(一)—— 环境搭建_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 = 4'd0,UART_TX_DATA_BUF= 4'd4,UART_RX_DATA_BUF= 4'd8;// 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 = 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状态机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 == 1'b1) 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] = 1'b1; // TI置1,代表发送完毕,需要软件置0endif(uart_rx_state == END && rx_baud_cnt == 1) beginuart_ctrl[0] = 1'b1; // 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 <= 1'b0; endelse if(wr_en_i == 1'b1 && wr_addr_i[3:0] == UART_TX_DATA_BUF) begintx_data_rd <= 1'b1;endelse if(uart_tx_state == END && tx_baud_cnt == 1) begintx_data_rd <= 1'b0;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 <= 13'd0;endelse if(uart_tx_state == IDLE || tx_baud_cnt == BAUD_CNT_MAX - 1) begintx_baud_cnt <= 13'd0;endelse begintx_baud_cnt <= tx_baud_cnt + 1'b1;endend// TX发送模块always @ (posedge clk or negedge rst_n) beginif(!rst_n) beginuart_tx_state <= IDLE;tx_bit_cnt <= 4'd0;uart_tx <= 1'b1;endelse begincase(uart_tx_state)IDLE: beginuart_tx <= 1'b1;if(tx_data_rd == 1'b1) beginuart_tx_state <= BEGIN; endelse beginuart_tx_state <= uart_tx_state;endendBEGIN: beginuart_tx <= 1'b0;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 == 4'd7 && tx_baud_cnt == BAUD_CNT_MAX - 1) begintx_bit_cnt <= 4'd0;uart_tx_state <= END; endelse if(tx_baud_cnt == BAUD_CNT_MAX - 1) begintx_bit_cnt <= tx_bit_cnt + 1'b1; endelse beginuart_tx <= uart_tx_data_buf[tx_bit_cnt];endendEND: beginuart_tx <= 1'b1;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 <= 4'd0;uart_tx <= 1'b1;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 <= 13'd0;endelse if(uart_rx_state == IDLE || rx_baud_cnt == BAUD_CNT_MAX - 1) beginrx_baud_cnt <= 13'd0;endelse beginrx_baud_cnt <= rx_baud_cnt + 1'b1;endend// RX接收模块always @ (posedge clk or negedge rst_n) beginif(!rst_n) beginuart_rx_state <= IDLE;rx_bit_cnt <= 4'd0;uart_rx_data_buf <= `ZERO_WORD;endelse begincase(uart_rx_state)IDLE: beginif(uart_rx_temp == 1'b0 && uart_rx_delay == 1'b1) 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 == 4'd7 && rx_baud_cnt == BAUD_CNT_MAX - 1) beginrx_bit_cnt <= 4'd0;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 + 1'b1; 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 <= 4'd0;uart_rx_data_buf <= `ZERO_WORD;endendcaseendendendmodule
在串口模块uart.v中定义了三个寄存器,分别为 串口控制寄存器 uart_ctrl、 串口发送数据缓存寄存器 uart_tx_data_buf、 串口接收数据缓存寄存器 uart_rx_data_buf,它们的作用分别是:
- uart_ctrl:串口控制寄存器,只有最低两位有效,分别为TI和RI。TI,发送完成标志位,该位在数据发送完成时被设置为1;RI,接收完成标志位,该位在数据接收完成时被设置为高电平。在数据发送/接收完成后由uart模块将TI/RI置1,这样驱动程序就可以通过读取该寄存器的TI/RI位来确定数据是否发送/接收完成,这两位需要软件复位。
- uart_tx_data_buf:串口发送数据缓存寄存器,只要uart_tx_data_buf寄存器有数据写入,就开始发送数据,发送完毕将TI置1。
- uart_rx_data_buf:串口接收数据缓存寄存器,用于接收用户通过rx端口发送的数据,接收完就将RI置1。
它们的地址偏移分别是0,4,8,实际的物理地址为0x10000000,0x10000004,0x10000008:
在了解上述内容后,我们就可以开始编写串口驱动程序了。
三、串口驱动程序的编写
源码放在我的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_CTRL,UART_TX_DATA_BUF,UART_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 交流~
相关文章:

开发一个RISC-V上的操作系统(三)—— 串口驱动程序(UART)
目录 文章传送门 一、什么是串口 二、本项目串口的FPGA实现 三、串口驱动程序的编写 四、上板测试 文章传送门 开发一个RISC-V上的操作系统(一)—— 环境搭建_riscv开发环境_Patarw_Li的博客-CSDN博客 开发一个RISC-V上的操作系统(二&…...

nuxt项目部署,npm run build 和npm run generate的区别
每日鸡汤:每个你想要学习的瞬间都是未来的你向自己求救 非服务端渲染的项目,比如普通的vite vue项目,我们在部署生产环境的时候,只需要两步 运行 npm run build 然后得到了一个 dist 文件夹将这个dist文件夹部署到一个静态服务器…...

数据仓库设计理论
数据仓库设计理论 一、数据仓库基本概念 1.1、数据仓库介绍 数据仓库是一个用于集成、存储和分析大量结构化和非结构化数据的中心化数据存储系统。它旨在支持企业的决策制定和业务分析活动。 1.2、基本特征 主题导向:数据仓库围绕特定的主题或业务领域进行建模…...

数据接口有哪些?(数据接口有哪几种)
数据接口是指不同应用程序或系统之间交换数据的通信界面。在现代信息化社会中,数据接口扮演着极为重要的角色,它们使得不同平台之间能够相互连接和交流,从而实现数据共享和应用集成。 数据接口的种类繁多,常见的有以下几种&#…...

华为云CodeArts产品体验的心得体会及想法
文章目录 前言CodeArts 的产品优势一站式软件开发生产线研发安全Built-In华为多年研发实践能力及规范外溢高质高效敏捷交付 功能特性说明体验感受问题描述完结 前言 华为云作为一家全球领先的云计算服务提供商,致力于为企业和个人用户提供高效、安全、可靠的云服务。…...

下载安装:SQLite+SQLiteStudio+VS
目录 1、SQLite 1.1、下载SQLite 1.2、配置SQLite的环境变量 2、SQLite Studio 2.1、下载SQLite Studio 2.2、安装SQLite Studio 3、Visual Studio 3.1、下载Visual Studio 3.2、安装Visual Studio 1、SQLite 1.1、下载SQLite SQLite官网:SQLite Downl…...

nginx路由
一般我们经常在访问网站时,通常会遇到输入某个页面的网址时,出现路由的转发,重定向等。可能访问的是一个网址,出来的时候就显示的是另外的地址。这是由于使用了nginx的缘故,保护了网址的安全性 (1…...

MobPush Android SDK 厂商推送限制
概述 厂商推送限制 每个厂商通道都有对应的厂商配额和 QPS 限制,当请求超过限制且已配置厂商回执时,MobPush会采取以下措施: 当开发者推送请求超过厂商配额时,MobPush将通过自有通道进行消息下发。当开发者推送请求超过厂商 QP…...

计算机网络 day7 扫描IP脚本 - 路由器 - ping某网址的过程
目录 network 和 NetworkManager关系: 实验:编写一个扫描脚本,知道本局域网里哪些ip在使用,哪些没有使用? 使用的ip对应的mac地址都要显示出来 计算机程序执行的两种不同方式: shell语言编写扫描脚本 …...

gitee 配置ssh 公钥(私钥)
步骤1:添加/生成SSH公钥,码云提供了基于SSH协议的Git服务,在使用SSH协议访问项目仓库之前,需要先配置好账户/项目的SSH公钥。 绑定账户邮箱: git config --global user.name "Your Name" git config --glob…...

JAVA面试总结-Redis篇章(七)——数据淘汰策略
JAVA 面试总结-数据淘汰策略...

[SQL挖掘机] - 算术函数 - round
介绍: 当谈到 SQL 中的 round 函数时,它用于将一个数值四舍五入到指定的小数位数。 用法: round 函数的用法如下: round(number, decimals)其中,number 是要进行四舍五入的数值,decimals 是要保留的小数位数。round 函数接受两…...

php-golang-rpc spiral/goridge库和php spiral/goridge2.4.5实践
golang 代码: package main import ( "fmt" "net" "net/rpc" "github.com/spiral/goridge/v2" ) type App struct{} func (*App) Hi(name string, r *string) error { *r fmt.Sprintf("hello %s!", name) re…...

关于Kubernetes的一些零碎想法
关于Kubernetes的一些零碎想法 容器集群管理系统与容器编排系统 很多使用Kubernetes的企业可能没有认识到Kubernetes最重要的特点。许多企业将其视为一种容器集群管理系统(container management system),只使用其管理容器的能力。然而&#x…...

C—数据的储存(下)
文章目录 前言🌟一、练习一下🌏1.例一🌏2.例二🌏3.例三🌏4.例四 🌟二、浮点型在内存中的储存🌏1.浮点数🌏2.浮点数存储💫(1).二进制浮点数&#x…...

「软件测试面试干货」2023年软件测试面试题大全(持续更新)附答案..
先卖个关子,如果你是面试官,你希望招一个什么样的人进来? 如果这个问题搞明白了,那么可以说测试岗位的面试,就变得非常轻松了。 按照一般的惯例,面试官都会让你自我介绍,介绍你的项目经验&…...

YOLOv2论文对比总结
1、高分辨率图片效果提升 2、Anchor 3、Loss函数 4、小目标友好...

数据中心机房机柜配电新模式的探讨与选型
安科瑞 华楠 摘 要:对数据中心机房列头柜配电方式特征和问题进行深入研究,分析机房末端配电安全性及可用性,主要阐述了数据中心机房机柜配电新模式。 关键词:数据中心;机房机柜;配电模式 1 原始配电方案 …...

Redis未授权访问漏洞
前言 传统数据库都是持久化存储到硬盘中,所以执行某些业务时传统数据库并不是很理想。redis等数据存储在内存中的数据库就应允而生了。基于内存的Redis读速度是110000次/s,写速度是81000次/s 。但是基于内存的缺点就是断电即失,如果服务器产生了意外…...

Python 各种进制转换
def binary_to_decimal(binary_number):#2进制转换成10进制return int(binary_number, 2) def hex_to_decimal(binary_number):#hexadecimal #16进制转换成10进制return int(binary_number, 16) def octal_to_decimal(binary_number):#8进制转换成10进制return int(binary_numb…...

关于K8s的Pod的详解(一)
关于K8s的Pod的详解(一) Pod和API server的通信加快Pod启动更改Pod的资源Pod 的持久卷的单个访问模式Pod 拓扑分布约束Pod 拓扑分布中的最小域数 Pod 作为k8s创建,调度,管理的基本单位。由上级的Controller对Node上安装的Kubelet发…...

Vue3通透教程【十六】TS编译配置
文章目录 🌟 写在前面🌟 初始化配置文件⭐ target⭐ module⭐ lib⭐ types/node⭐ include⭐ outDir🌟 写在最后 🌟 写在前面 专栏介绍: 凉哥作为 Vue 的忠实 粉丝输出过大量的 Vue 文章,应粉丝要求开始更…...

OpenCV系列__chapter2
这里写目录标题 1 图像加减乘除位运算1.1 加法 img cv2.add(img1, img2)1.2 减法 img cv2.subtract(img1, img2)1.3 乘法 img cv2.multiply(img1, img2)1.4 除法 img cv2.divide(img1, img2)1.5 位运算 cv2.bitwise_and() 2 图像增强2.1 线性变换2.2 非线性变换 3 图像几何…...

Chat GPT是什么,初学者怎么使用Chat GPT,需要注意些什么
目录 Chat GPT是什么 初学者怎么使用Chat GPT 使用Chat GPT需要注意什么 一些简单的prompt示例 Chat GPT是什么 Chat GPT是由OpenAI开发的一种大型语言模型,它基于GPT(Generative Pre-trained Transformer)架构。GPT是一种基于深度学习的…...

hcip mgre与rip实验
要求: 1.toop搭建 2.IP地址规划 R1g 0/0/1 192.168.1.1 24 s 4/0/0 188.0.0.2 24 t:10.0.0.1 24R2 s 4/0/0 188.0.0.1 24 s 4/0/1 188.0.1.1 24 s 3/0/0 188.0.2.1 24 loop0 8.8.8.8 24 t:10.0.0.2 24 R3g…...

骨传导耳机对身体有没有别的危害?骨传导耳机有什么好处?
骨传导耳机对身体有没有别的危害? 在此之前,我们先了解一下骨传导的原理:骨传导就跟它的名字一样通过骨头传声,主要是借助头部颅骨传递到听觉中枢,这种传播方式省略了直接接触耳道和耳膜。打个比方,就是我们…...

c++11/c++98动态规划入门第5课,经典DP问题 --- 区间
第1题 取数问题 查看测评数据信息 有一排N个数,你和小明2个人玩游戏,每个人轮流从2端取数,每次可以从左或右取,不能从中间取。你取的所有的数的和是你的得分,小明取的所有的数的和是小明的得分。如果你先取&#x…...

vue中重新获取数据导致页面加长,要求在页面更新之后浏览器滚动条滚动到之前浏览记录的位置。以及获取当前页面中是哪个元素产生滚动条的方法。
目前的页面样式为: 代码是: <section id"detailSection"><el-tableref"multipleTable":data"logDetailList"style"width: 650px;margin:20px auto;"id"dialogDetail":show-header"fals…...

【深度学习】日常笔记14
对神经网络模型参数的初始化方案对保持数值稳定性有很重要的作用。初始化⽅案的选择可以与⾮线性激活函数的选择有趣的结合在⼀起。 突然有感触:做习题和模拟考研就分别是训练集和验证集,考研不就是最后的测试集() p168的↓的解释…...

[JAVAee]synchronized关键字
目录 1.synchronized的特性 ①互斥性 ②可重入性 2.synchronized的使用示例 ①修饰普通方法 ②修饰静态方法 ③修饰代码块 1.synchronized的特性 ①互斥性 互斥性,就像是给门上锁了一样. 当A线程使用了被synchronized修饰的代码块并对其上锁,其他线程(B线程,C线程)想要使…...