STM32F4xx开发学习—GPIO
GPIO
学习使用STM32F407VET6GPIO外设
寄存器和标准外设库
1. 寄存器
- 存储器映射
存储器本身是不具有地址的,是一块具有特定功能的内存单元,它的地址是由芯片厂商或用户分配,给存储器分配地址的过程就叫做存储区映射。给内存单元分配地址之后,就可以通过指针去操作内存地址。 - 存储器映射表
STM32是一个32位的单片机,它的地址范围为2的32次方,也就是4GB的地址空间。为了降低不同客户在相同应用时的软件复杂度,存储映射是按Cortex-M4处理器提供的规则预先定义的。在存储器映射表中,一部分地址空间由Arm Cortex-M4的系统外设所占用,且不可更改,其余部分地址空间可由芯片供应商定义使用,如下图所示。
- 什么是寄存器
寄存器是读取速度最快的存储单元,具有特定功能的内存单元,通过操作这些内存单元可以驱动外设工作。寄存器按功能又可分为指令寄存器、地址寄存器和数据寄存器,处理器可以使用相互独立的总线来读取指令和加载/存储数据。 - 寄存器映射
程序存储器,数据存储器,寄存器和I / O端口都在同一个线性的4 GB的地址空间之内。每一个寄存器都对应不同的功能,操作相应的寄存器就可以配置不同的功能。如果我们要控制某个外设工作,那我们可以找到这个单元的起始地址,然后通过c语言指针的方式来访问这些内存单元。但通常我们会给这个特殊的内存单元取一个名字,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射,这个别名就是我们所说的寄存器。 - 寄存器重映射
有时在映射的结果中,地址会不够而造成重复,这里给寄存器再分配一个地址的过程叫做寄存器重映射。 - 总线基地址
片上外设区域分为四条总线,分别为AHB1总线、AHB2总线、APB1总线和APB2总线。AHB总线最高时钟可达168MHZ,APB1总线最高时钟可达42MHZ,APB2总线时钟最高可达84MHZ。根据外设速度的不同,不同的总线挂载着不同的外设。总线的最低地址我们称为该总线的基地址,总线基地址也是挂载在该总线上的首个外设的地址,可以通过参考手册中存储器映射这一节查询,四条总线地址如下表所示。
总线名称 | 总线基地址 | 总线地址范围 |
---|---|---|
APB1 | 0x4000 0000 | 0x4000 0000-0x4000 FFFF |
APB2 | 0x4001 0000 | 0x4001 0000-0x4001 FFFF |
AHB1 | 0x4002 0000 | 0x4002 0000-0x4FFF FFFF |
AHB2 | 0x5000 0000 | 0x5000 0000-0x5FFF FFFF |
- 外设基地址
每个总线上都挂载着很多外设,这些外设也都有自己的地址范围。 - 外设寄存器地址
在外设的地址范围内,分布着该外设的寄存器。以GPIO外设为例,GPIO外设地址范围内有很多个寄存器,每一个都有特定的功能,通过操作对应的寄存器来配置GPIO的功能。每个寄存器都为32位,占4个字节,这里我们以GPIOA端口的寄存器进行介绍。 - 如何操作寄存器
比如我们想让GPIOA端口的16个引脚都置1。我们需要去配置端口输出寄存器GPIOx_ODR(输出数据寄存器),通过查找用户手册283页可以知道这个寄存器的地址偏移量为0x14,GPIOA端口的基地址为0x4002 0000,所以GPIOx_ODR寄存器的地址为0x4002 0000 + 0x14 = 0x4002 0014,那我们就是对这个地址进行操作。
该寄存器高16位保留,仅需配置低16位即可。每一位对应一个引脚输出,即向GPIOx_ODR写入0x0000FFFF即可。
- 通过绝对地址访问内存单元
代码如下
/*GPIOA端口16个引脚全部输出高电平*/
*(unsigned int*)(0x40020014) = 0x0000FFFF;//通过指针向0x40020014这个地址写入数值
- 通过别名访问内存单元
由于直接操作地址很麻烦,可通过预定义将所需用到的地址进行归纳,代码如下
/* GPIOA 端口的16个引脚全部输出高电平 */
#define GPIOA_ODR (unsigned int*)(0x40020014)
*GPIOA_ODR = 0xFFFF;
2. 标准库函数
- 为什么要用库函数
从上一节我们了解到如何去用寄存器驱动外设,但我们也同时了解到STM32的寄存器数量非常多,这么多的寄存器光是定义就需要花费很多的时间,更不用说还要去查找对应的功能,找到对应的地址,然后配置需要的值,这在难度和时间上都是不可取的。为此,库函数就在这种情况下应运而生,库函数能使我们的开发效率大大提高。关于STM32的标准外设库函数,STM32的官方已经给我们开发好了,我们只需要移植到我们的工程使用即可。库函数的使用不需要让我们去了解硬件的机制,只需要根据需要的功能去查找对应的函数,然后调用即可,大大降低了开发要求。 - 标准库函数介绍
库函数就是在寄存器的基础上又封装了一层,使操作起来更简单,最后还是通过寄存器来实现的。 - 寄存器和库函数区别
- 寄存器更能理解原理,更直观,库函数相对来说屏蔽底层,直接面向应用
- 使用库函数较寄存器代码量会增大,库函数会把所有情况都考虑到函数里,有时会造成代码的冗余
- 库函数使用起来相对简单,容易上手,可快速开发应用,大大提高效率
- 寄存器占用内存少,速度快,在资源有限或者要求执行速度的情况下寄存器是一个不错的选择
GPIO外设
每个GPIO具有 4 个 32 位配置寄存器(GPIOx_MODER、GPIOx_OTYPER、GPIOx_OSPEEDR 和 GPIOx_PUPDR)、2 个 32 位数据寄存器(GPIOx_IDR 和 GPIOx_ODR)、1 个 32 位设置/复位寄存器 (GPIOx_BSRR)、1 个 32 位锁定寄存器 (GPIOx_LCKR) 和 2 个 32 位备用功能选择寄存器(GPIOx_AFRH 和 GPIOx_AFRL)。
GPIO基本结构
GPIO外设位于AHB1总线。每个端口通过16个引脚引出,由配置寄存器进行配置。
I/O引脚多路复用和映射
微控制器 I/O 引脚通过多路复用器连接到板载外设/模块,该多路复用器一次只允许一个外设的备用功能(AF)连接到 I/O 引脚。这样,共享同一 I/O 引脚的外设之间就不会发生冲突。同时每个 I/O 引脚都有一个多路复用器,具有 16 个备用功能输入(AF0 至 AF15),可通过 GPIOx_AFRL(引脚 0 至 7)和 GPIOx_AFRH(引脚 8 至 15)寄存器进行配置。在更小的封装内实现更多的外设数量。
输入模式
GPIO输入基本结构如下
当I/O端口被编辑为输入模式时,输出驱动断开。
输出模式
GPIO输出基本结构如下
当I/O端口被编辑为输出模式时,输入驱动没有断开,具有采样功能
复用功能配置
GPIO复用基本结构如下
和输出模式类似,只不过增加了复用功能输入和复用功能输出。
LED灯
LED等基础知识
- LED灯结构组成
LED灯,也称发光二极管,是一种能够将电能转化为可见光的固态的半导体器件,它可以直接把电转化为光。LED的内部是一个半导体的晶片,晶片的一端附在一个支架上,一端是负极,另一端连接电源的正极,整个晶片被环氧树脂封装起来。 - LED灯发光原理
半导体晶片由两部分组成,一部分是P型半导体,另一端是N型半导体。这两种半导体连接起来的时候,它们之间就形成了一个P-N结。当电流通过导线作用于这个晶片的时候,电子就会被推向P区,在P区里电子跟空穴复合,然后就会以光子的形式发出能量,这就是LED灯发光的原理。 - LED灯驱动原理
LED灯的驱动比较简单,只需要给将对应的正负极接到单片机的正负极即可驱动。需要注意的是LED灯的颜色不同,对应的电压也不同,同时需要串联分压电阻。板载LED如下图所示
LED灯驱动流程
通过上面的原理图可知LED灯正极接到电源上,负极连接到单片机GPIO口上,这里给PA6和PA7引脚输出低电平即可点亮LED。
寄存器点亮LED
STM32所有的外设资源时钟默认都是关闭的,因此在配置外设之前需要先开启对应的时钟。点亮板载LED需要使用GPIO外设,在stm32f4xx_gpio.c
文件中知,使用GPIO端口,需要有一下几个步骤:
- 开启GPIO时钟
- 配置GPIO模式
- 配置GPIO输出
开启GPIO外设端口时钟
STM32F407的GPIO外设在AHB1总线,需要配置AHB1使能寄存器,AHB1外设时钟寄存器如下图
该寄存器地址偏移量为0x30
,查表得RCC外设基地址为0x4002 3800
,那么RCC_AHB1ENR寄存器地址为:0x4002 3800 + 0x30 = 0x4002 3830
。所使用得引脚是GPIOA端口,第零位Bit 0值为1,为保持其他位不变这里采用一个或运算。
代码为RCC->AHB1ENR |= 0x01;
。
配置GPIO模式
GPIO的模式配置可分为两步
- 通过控制寄存器(GPIOx_MODER)配置为输入功能,输出功能,复用功能还是模拟功能
- 通过 GPIO 上/下拉寄存器(GPIOx_PUPDR)配置GPIO的上下拉模式或者浮空
该寄存器地址偏移量为0x00
,GPIOA端口基地址为0x4002 0000
,则GPIOx_MODER寄存器地址为:0x4002 0000 + 0x00 = 0x4002 0000
,要使用的是第6号和第7号引脚,即选择该寄存器位12、13、14和15,为保持其他位数据不变,需先清空这两位后再写入数值。操作为:GPIOA_MODER &= 0xFFFF 0FFF;
和GPIOA_MODER |= 0x0000 5000;
。
输出模式一般配置为浮空模式,输入模式才需要考虑上拉还是下拉。通过寄存器GPIOx_PUPDR进行配置
该寄存器地址偏移量为0x0C
,GPIOA端口基地址为0x4002 0000
,则GPIOx_MODER寄存器地址为:0x4002 0000 + 0x0C = 0x4002 000C
,要使用的是第6号和第7号引脚,即选择该寄存器位12、13、14和15,为保持其他位数据不变,需先清空这两位后再写入数值。操作为:GPIOx_PUPDR &= 0xFFFF 0FFF;
和GPIOx_PUPDR |= 0x0000 0000;
。
转化为代码如下
GPIOA->MODER |= (0x05 << 2 * 6);//配置为输出模式
GPIOA->PUPDR &= ~(0x05 << 2 * 6);//配置为浮空模式
配置GPIO的输出
配置GPIO的输出同样分为两步,输出模式和端口速度。
- 通过端口输出模式寄存器(GPIOx_OTYPER)配置为推挽模式、开漏模式
- 通过端口输出速度寄存器(GPIOx_OSPEEDR)配置四种速度
寄存器写入数值过程同上。
转化为代码如下
GPIOA->OTYPER |= 0x0000;//配置为推挽输出
GPIOA->OSPEEDR |= (0x0F << 2 * 6);//配置为最高速
配置GPIO输出高低电平
配置GPIO引脚输出高低电平,即板载引脚PA6和PA7输出高低电平,可以通过端口输出数据寄存器GPIOx_ODR进行配置。
转化为代码:GPIOA->ODR |= 0x0C;//输出高电平
总体代码如下
#include "stm32f4xx.h" // Device headerint main()
{RCC->AHB1ENR |= 0x01;//开启AHB1总线时钟GPIOA->MODER |= (0x05 << 2 * 6);//配置为输出模式GPIOA->PUPDR &= ~(0x05 << 2 * 6);//配置为浮空模式GPIOA->OTYPER &= ~(0x03 << 2 * 3);//配置为推挽模式GPIOA->OSPEEDR |= (0x0F << 2 * 6);//配置为最高速// GPIOA->ODR |= (0x03 << 2 * 3);//输出高电平GPIOA->ODR |= (0x00 << 2 * 3);//输出低电平
}
库函数点亮LED
同样按照上述步骤进行编写代码,这里新建一个LED.c文件便于对外设进行管理。
//LED.c文件
#include "LED.h"void LED_Init()
{//初始化PA6和PA7为输出口 GPIO_InitTypeDef GPIO_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;//LED0和LED1对应IO口GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHzGPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//浮空GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOGPIO_SetBits(GPIOA,GPIO_Pin_6 | GPIO_Pin_7);//设置高,灯灭}void LED1_ON()
{GPIO_ResetBits(GPIOA, GPIO_Pin_6);
}void LED2_ON()
{GPIO_ResetBits(GPIOA, GPIO_Pin_7);
}void LED1_OFF()
{GPIO_SetBits(GPIOA, GPIO_Pin_6);
}void LED2_OFF()
{GPIO_SetBits(GPIOA, GPIO_Pin_7);
}
所对应的头文件LED.h代码如下
//LED.c文件
#ifndef __LED_H
#define __LED_H#include "stm32f4xx.h" // Device headervoid LED_Init(void);//初始化
void LED1_ON();
void LED1_OFF();
void LED2_ON();
void LED2_OFF();#endif
主函数main代码如下
#include "LED.h"
int main()
{//库函数点亮LEDLED_Init();LED1_OFF();LED2_ON();while(1){}
}
相关文章:

STM32F4xx开发学习—GPIO
GPIO 学习使用STM32F407VET6GPIO外设 寄存器和标准外设库 1. 寄存器 存储器映射 存储器本身是不具有地址的,是一块具有特定功能的内存单元,它的地址是由芯片厂商或用户分配,给存储器分配地址的过程就叫做存储区映射。给内存单元分配地址之后…...

引领农业新质生产力,鸿道(Intewell®)操作系统助力农业机器人创新发展
4月27日至29日,2024耒耜国际会议在江苏大学召开。科东软件作为特邀嘉宾出席此次盛会,并为江苏大学-科东软件“农业机器人操作系统”联合实验室揭牌。 校企联合实验室揭牌 在开幕式上,江苏大学、科东软件、上交碳中和动力研究院、遨博智能研究…...

扩展学习|一文读懂知识图谱
一、知识图谱的技术实现流程及相关应用 文献来源:曹倩,赵一鸣.知识图谱的技术实现流程及相关应用[J].情报理论与实践,2015, 38(12):127-132. (一)知识图谱的特征及功能 知识图谱是为了适应新的网络信息环境而产生的一种语义知识组织和服务的方…...

ubuntu中的docker记录(3)——如何安装nvidia-docker以更好地支持GPU加速计算应用程序的运行
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、nvidia-docker2的安装1. 安装docker2. 安装nvidia-docker2(1) 添加密钥(2) 更新软件列表(3) 安装nvidia-docker2(4) 测试nvidia-docker2 二、可能的报错及解…...

MLP实现fashion_mnist数据集分类(1)-模型构建、训练、保存与加载(tensorflow)
1、查看tensorflow版本 import tensorflow as tfprint(Tensorflow Version:{}.format(tf.__version__)) print(tf.config.list_physical_devices())2、fashion_mnist数据集下载与展示 (train_image,train_label),(test_image,test_label) tf.keras.datasets.fashion_mnist.l…...
ChatGPT-税收支持新质生产力
Prompt: 税收发展助力新质生产力 Response: 是的,税收发展可以促进新质生产力的发展。通过税收政策的调整和优化,政府可以提供更好的创新环境,激发企业投资研发,推动新技术、新产品的出现,从而推动经济结构升级和新…...
Linux下深度学习虚拟环境的搭建与模型训练
在深度学习实践中,环境配置是十分重要且免不了的一步。本文以 YOLOv4 模型,介绍在Linux下虚拟环境配置到模型训练的过程。 安装Miniconda: Miniconda是Anaconda的一个轻量级版本,非常适合用于科学计算和数据处理。 wget https:…...
Map-Reduce是个什么东东?
MapReduce是一种用于使用并行分布式算法在集群计算机上处理大型数据集的编程模型及其相关实现。这一概念首先由Google普及,并随后作为Apache Hadoop项目的一部分开源发布。 MapReduce的基本工作流程: 映射(Mapping):这是第一阶段,…...
上位机工作感想-从C#到Qt的转变-2
2.技术总结 语言方面 最大收获就是掌握了C Qt编程,自己也是粗看了一遍《深入理解计算机系统》,大致了解了计算机基本组成、虚拟内存、缓存命中率等基基础知识,那本书确实有的部分看起来很吃力,等这段时间忙完再研读一遍。对于封装…...
【C++】C++ 中 的 lambda 表达式(匿名函数)
C11 引入的匿名函数,通常被称为 Lambda 函数,是语言的一个重要增强,它允许程序员在运行时创建简洁的、一次性使用的函数对象。Lambda 函数的主要特点是它们没有名称,但可以捕获周围作用域中的变量,这使得它们非常适合在…...

OpenSSL实现AES-CBC加解密,可一次性加解密任意长度的明文字符串或字节流(QT C++环境)
本篇博文讲述如何在Qt C的环境中使用OpenSSL实现AES-CBC-Pkcs7加/解密,可以一次性加解密一个任意长度的明文字符串或者字节流,但不适合分段读取加解密的(例如,一个4GB的大型文件需要加解密,要分段读取,每次…...

cURL:命令行下的网络工具
序言 在当今互联网时代,我们经常需要与远程服务器通信,获取数据、发送请求或下载文件。在这些情况下,cURL 是一个强大而灵活的工具,它允许我们通过命令行进行各种类型的网络交互。本文将深入探讨 cURL 的基本用法以及一些高级功能…...
Baumer工业相机堡盟工业相机如何通过NEOAPISDK查询和轮询相机设备事件函数(C#)
Baumer工业相机堡盟工业相机如何通过NEOAPISDK查询和轮询相机设备事件函数(C#) Baumer工业相机Baumer工业相机NEOAPI SDK和相机设备事件的技术背景Baumer工业相机通过NEOAPISDK在相机中查询和轮询相机设备事件函数功能1.引用合适的类文件2.通过NEOAPISDK…...
Day45代码随想录动态规划part07:70. 爬楼梯(进阶版)、322. 零钱兑换、279.完全平方数、139.单词拆分
Day45 动态规划part07 完全背包 70. 爬楼梯(进阶版) 卡码网链接:57. 爬楼梯(第八期模拟笔试) (kamacoder.com) 题意:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬至多m (1 < m < n)个…...

土壤重金属含量分布、Cd镉含量、Cr、Pb、Cu、Zn、As和Hg、土壤采样点、土壤类型分布
土壤是人类赖以生存和发展的重要资源之一,也是陆地生态系统重要的组成部分。近年来, 随着我国城市化进程加快,矿产资源开发、金属加工冶炼、化工生产、污水灌溉以及不合理的化肥农药施用等因素导致重金属在农田土壤中不断富集。重金属作为土壤环境中一种具有潜在危害…...
力扣:100284. 有效单词(Java)
目录 题目描述:输入:输出:代码实现: 题目描述: 有效单词 需要满足以下几个条件: 至少 包含 3 个字符。 由数字 0-9 和英文大小写字母组成。(不必包含所有这类字符。) 至少 包含一个 …...

如何快速掌握DDT数据驱动测试?
前言 网盗概念相同的测试脚本使用不同的测试数据来执行,测试数据和测试行为完全分离, 这样的测试脚本设计模式称为数据驱动。(网盗结束)当我们测试某个网站的登录功能时,我们往往会使用不同的用户名和密码来验证登录模块对系统的影响&#x…...

OpenCV如何实现背投(58)
返回:OpenCV系列文章目录(持续更新中......) 上一篇:OpenCV直方图比较(57) 下一篇:OpenCV如何模板匹配(59) 目标 在本教程中,您将学习: 什么是背投以及它为什么有用如何使用 OpenCV 函数 cv::calcBackP…...

5-在Linux上部署各类软件
1. MySQL 数据库安装部署 1.1 MySQL 5.7 版本在 CentOS 系统安装 注意:安装操作需要 root 权限 MySQL 的安装我们可以通过前面学习的 yum 命令进行。 1.1.1 安装 配置 yum 仓库 # 更新密钥 rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022# 安装Mysql…...
【Jenkins】持续集成与交付 (八):Jenkins凭证管理(实现使用 SSH 、HTTP克隆Gitlab代码)
🟣【Jenkins】持续集成与交付 (八):Jenkins凭证管理(实现使用 SSH 、HTTP克隆Gitlab代码) 1、安装Credentials Binding、git插件2、凭证类型及用途3、(用户名和密码类型)凭证的添加和使用3.1 用户密码类型3.2 测试凭证是否可用3.3 开始构建项目3.3 查看结果(进入Jenk…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...

脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)
一、OpenBCI_GUI 项目概述 (一)项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台,其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言,首次接触 OpenBCI 设备时,往…...
在RK3588上搭建ROS1环境:创建节点与数据可视化实战指南
在RK3588上搭建ROS1环境:创建节点与数据可视化实战指南 背景介绍完整操作步骤1. 创建Docker容器环境2. 验证GUI显示功能3. 安装ROS Noetic4. 配置环境变量5. 创建ROS节点(小球运动模拟)6. 配置RVIZ默认视图7. 创建启动脚本8. 运行可视化系统效果展示与交互技术解析ROS节点通…...

渗透实战PortSwigger Labs指南:自定义标签XSS和SVG XSS利用
阻止除自定义标签之外的所有标签 先输入一些标签测试,说是全部标签都被禁了 除了自定义的 自定义<my-tag onmouseoveralert(xss)> <my-tag idx onfocusalert(document.cookie) tabindex1> onfocus 当元素获得焦点时(如通过点击或键盘导航&…...
Docker、Wsl 打包迁移环境
电脑需要开启wsl2 可以使用wsl -v 查看当前的版本 wsl -v WSL 版本: 2.2.4.0 内核版本: 5.15.153.1-2 WSLg 版本: 1.0.61 MSRDC 版本: 1.2.5326 Direct3D 版本: 1.611.1-81528511 DXCore 版本: 10.0.2609…...

生信服务器 | 做生信为什么推荐使用Linux服务器?
原文链接:生信服务器 | 做生信为什么推荐使用Linux服务器? 一、 做生信为什么推荐使用服务器? 大家好,我是小杜。在做生信分析的同学,或是将接触学习生信分析的同学,<font style"color:rgb(53, 1…...