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…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
前端开发面试题总结-JavaScript篇(一)
文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...
微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...
vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...
dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验
Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...
Ubuntu系统复制(U盘-电脑硬盘)
所需环境 电脑自带硬盘:1块 (1T) U盘1:Ubuntu系统引导盘(用于“U盘2”复制到“电脑自带硬盘”) U盘2:Ubuntu系统盘(1T,用于被复制) !!!建议“电脑…...
