IMX6ULL学习笔记(15)——GPIO输出接口使用【官方SDK方式】

news2024/10/6 1:43:12

一、GPIO简介

i.MX6ULL 芯片的 GPIO 被分成 5 组,并且每组 GPIO 的数量不尽相同,例如 GPIO1 拥有 32 个引脚, GPIO2 拥有 22 个引脚, 其他 GPIO 分组的数量以及每个 GPIO 的功能请参考 《i.MX 6UltraLite Applications Processor Reference Manual》 第26章General Purpose Input/Output (GPIO)(P1133)

通过 GPIO 硬件结构框图,就可以从整体上深入了解 GPIO 外设及它的各种应用模式。

1.1 IO命名

打开 i.MX6ULL 参考手册的第 32 章“Chapter 32: IOMUX Controller(IOMUXC)”

i.MX6ULL 的 IO 分为两类:SNVS 域的和通用的,这两类 IO 本质上都是一样的。

“IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00”的就是 GPIO 命名,命名形式就是“IOMUXC_SW_MUC_CTL_PAD_XX_XX”,后面的“XX_XX”就是 GPIO 命名,比如:GPIO1_IO01、UART1_TX_DATA、JTAG_MOD 等等。他是 根据某个 IO 所拥有的功能来命名的。比如我们一看到 GPIO1_IO01 就知道这个肯定能做 GPIO,看到 UART1_TX_DATA 肯定就知道这个 IO 肯定能做为 UART1 的发送引脚。

IO 复用功能。 i.MX6ULL 除了 GPIO1_IO00~GPIO1_IO09 引脚外,其它 IO 也是可以复用为 GPIO 功能。同样的,GPIO1_IO00~GPIO_IO09 也是可以复用为其它外设引脚。

1.2 IO复用

IOMUX 译为 IO 复用选择器。i.MX6ULL 的芯片每个 GPIO 都通过 IOMUX 支持多种功能, 例如一个 IO 可用于网络外设 ENET 的数据接收引脚,也可以被配置成 PWM 外设的输出引脚, 这样的设计大大增加了芯片的适用性,这样可选的功能就是由 IOMUX 实现的。IOMUX 相当于增加了多根内部信号线与 IO 引脚相连,最多有 8 根,也就是说一个 IO 最多可支持 8 种可选的功能

以“IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00”这个 IO 为例,打开参考手册的 1568 页。

可以看到有个名为:IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00 的寄存器,寄存器地址为 0X020E005C,这个寄存器是 32 位的,但是只用到了最低 5 位,其中 bit0~bit3(MUX_MODE) 就是设置 GPIO1_IO00 的复用功能的。GPIO1_IO00 一共可以复用为 9 种功能 IO,分别对应 ALT0~ALT8,其中 ALT5 就是作为 GPIO1_IO00。GPIO1_IO00 还可以作为 I2C2_SCL、GPT1_CAPTURE1、ANATOP_OTG1_ID 等。

1.3 IO配置

IOMUX 由其左侧的 IOMUXC 控制(C表示Controler),IOMUXC 提供寄存器给用户进行配置, 它又分成 MUX Mode(IO模式控制) 以及 Pad Settings(Pad配置) 两个部分:

在 IOMUXC 外设中关于 MUX Mode 和 Pad Settings 寄存器命名格式如下:

IOMUXC控制类型寄存器名称
MUX ModeIOMUXC_SW_MUX_CTL_PAD_XXXX
Pad SettingsIOMUXC_SW_PAD_CTL_PAD_XXXX

每个引脚都包含这两个寄存器,表中的XXXX表示引脚的名字

1.3.1 MUX Mode配置

MUX Mode 就是用来配置引脚的复用功能,即选择引脚具体是用于网络外设 ENET 的数据接收, 还是用于 PWM 外设的输出引脚,当然,也可以配置成普通的 IO 口,仅用于控制输出高低电平。

以 GPIO1_IO04 引脚为例对 MUX 寄存器进行说明,该引脚相应的 MUX 寄存器在参考手册中的描述如下:

该寄存器主要有两个配置域,分别是 SIONMUX_MODE

  • SION: 用于设置引脚在输出模式下同时开启输入通道。
  • MUX_MODE: 使用 4 个寄存器位表示可选的 ALT0~ALT7 这 8 个模式。
    • 如 ALT2 模式就是用于 USB 外设的 USB_OTG1_PWR 信号;
    • 若配置为 ALT5 则引脚会用作普通的 GPIO 功能, 用于输出高、低电平。

1.3.2 Pad Settings配置

Pad Settings 用于配置引脚的属性,例如驱动能力,是否使用上下拉电阻, 是否使用保持器,是否使用开漏模式以及使用施密特模式还是CMOS模式等。

以 GPIO1_IO04 引脚中 PAD 寄存器在参考手册中的描述如下:

相对来说 PAD 寄存器的配置项就更丰富了,而且图中仅是该寄存器的部分说明,如 HYS 设置使用施密特模式的滞后功能,PUS 配置上下拉电阻的阻值, 其它的还包含PUE、PKE、ODE、SPEED、DSE 及 SRE 的配置。

1.3.3 PAD(可跳过不看)

PAD 代表了一个 i.MX6ULL 的 GPIO 引脚。在它的左侧是一系列信号通道及控制线,如 input_on 控制输入开关,Dir 控制引脚的输入输出方向,Data_out 控制引脚输出高低电平,Data_in 作为信号输入,这些信号都经过一个 IOMUX 的器件连接到左侧的寄存器。

①PAD引脚
代表一个i.MX6ULL的引脚。
②输出缓冲区
当输出缓冲区使能时,引脚被配置为输出模式。在输出缓冲区中,又包含了如下的属性配置:

  • DSE驱动能力
    当IO用作输出的时候用来设置IO的驱动能力。DSE可以调整芯片内部与引脚串联电阻R0的大小,从而改变引脚的驱动能力。例如,R0的初始值为260欧姆,在3.3V电压下其电流驱动能力为12.69mA,通过DSE可以把R0的值配置为原值的1/2、1/3…1/7等。

    位设置速度
    000输出驱动关闭
    001R0(3.3V 下 R0 是 260Ω,1.8V 下 R0 是 150Ω,接 DDR 的时候是 240Ω)
    010R0/2
    011R0/3
    100R0/4
    101R0/5
    110R0/6
    111R0/7
  • SRE压摆率配置
    设置压摆率。压摆率是指电压转换速率,可理解为电压由波谷升到波峰的时间。增大压摆率可减少输出电压的上升时间。i.MX6ULL的引脚通过SRE支持低速和高速压摆率这两种配置。当此位为0的时候是低压摆率,当为1的时候是高压摆率。压摆率是大信号特性,下面的带宽是小信号特性。

  • SPEED带宽配置
    设置IO的带宽。分别可设置为50MHz、100MHz以及200MHz。带宽的意思是能通过这个IO口最高的信号频率,通俗点讲就是方波不失真,如果超过这个频率方波就变正弦波。但是这个带宽要区别于IO的翻转速率,IO的翻转速率的信号来自于GPIO这个外设,而IO的带宽只是限制了IO口引脚的物理特性,IO口的信号可以来自于内部定时器输出的PWM信号,也可以来自于GPIO翻转输出的信号,两者相比之下,PWM信号的频率是远远高于GPIO翻转输出的信号频率。

    位设置速度
    00低速 50M
    01中速 100M
    10中速 100M
    11最大速度 200M
  • ODE开漏输出配置
    设置引脚是否工作在开漏输出模式。在该模式时引脚可以输出高阻态和低电平,此位为0的时候禁止开路输出,当此位为1的时候就使能开路输出功能。输出高阻态时可由外部上拉电阻拉至高电平。开漏输出模式常用在一些通讯总线中,如I2C。

③输入缓冲区
当输入缓冲区使能时,引脚被配置为输入模式。在输入缓冲区中,又包含了如下的属性配置:

  • HYS滞后使能
    用来使能迟滞比较器。i.MX6ULL的输入检测可以使用普通的CMOS检测或施密特触发器模式(滞后模式)。施密特触发器具有滞后效应,对正向和负向变化的输入信有不同的阈值电压。如果需要对输入波形进行整形的话可以使能此位。此位为0的时候禁止迟滞比较器,为1的时候使能迟滞比较器。常被用于电子开关、波形变换等场合,其转换特性和对比如下,如检测按键时,使用施密特模式即可起到消抖的功能。

④Pull/Keeper上下拉、保持器
引脚的控制逻辑中还包含了上下拉、保持器的功能。芯片内部的上拉和下拉电阻可以将不确定的信号钳位在高、低电平,或小幅提高的电流输出能力,上拉提供输出电流,下拉提供输入电流。注意这些上下拉配置只是弱拉,对于类似I2C之类的总线,还是必须使用外部上拉电阻。i.MX6ULL芯片的电源模块中包含转换器,当转换器停止工作时,保持器会保持输入输出电压。

上下拉、保持器可以通过如下属性配置:

  • PUS上下拉配置
    设置上下拉电阻。PUS可配置项可选为100K欧下拉以及22K欧、47K欧及100K欧上拉。
    位设置含义
    00100K 下拉
    0147K 上拉
    10100K 上拉
    1122K 上拉
  • PUE上下拉、保持器选择
    上下拉功能和保持器功能是二选一的,可以通过PUE来选择。当IO作为输入的时候,这个位用来设置 IO 使用上下拉还是状态保持器。当为0的时候使用状态保持器,当为1的时候使用上下拉。状态保持器在IO作为输入的时候才有用,顾名思义,就是当外部电路断电以后此IO口可以保持住以前的状态。
  • PKE上下拉、保持器配置
    用来使能或者禁止上下拉/状态保持器功能。为0时禁止上下拉/状态保持器,为1时使能上下拉和状态保持器。

注意,当引脚被配置为输出模式时,不管上下拉、保持器是什么配置,它们都会被关闭。

1.4 GPIO配置

GPIO 模块是每个 IO 都具有的外设,它具有 IO 控制最基本的功能,如输出高低电平、检测电平输入等。 它也占用 IOMUX 分配的复用信号,也就是说使用 GPIO 模块功能时同样需要使用 IOMUX 选中 GPIO 外设,对其 GPIO 的功能进行配置。

1.4.1 GDIR方向寄存器

设置某个 IO 的工作方向。控制一个 GPIO 引脚时,要先用 GDIR 方向寄存器配置该引脚用于输出电平信号还是用作输入检测。 典型的例子是使用输出模式可以控制LED灯的亮灭,输入模式时可以用来检测按键是否按下。

GDIR 寄存器的每一个数据位代表一个引脚的方向,对应的位被设置为0时该引脚为输入模式,被设置为1时该引脚为输出模式。

例如,对 GPIO1 的 GDIR 寄存器的 bit3 位被写入为 1,那么 GPIO1.3 引脚的模式即为输出。

1.4.2 DR数据寄存器

DR 数据寄存器直接代表了引脚的电平状态,它也使用 1 个数据位表示 1 个引脚的电平,每位用 1 表示高电平,用 0 表示低电平。

当 GDIR 方向寄存器设置引脚为输出模式时,写入 DR 数据寄存器对应的位即可控制该引脚输出的电平状态, 如这时 GPIO1 的 DR 寄存器的 bit4 被写入为 1,则引脚为输出高电平。

当 GDIR 方向寄存器设置引脚为输入模式时,读取 DR 数据寄存器对应的位即可获取该引脚当前的输入电平状态,例如这里读取 GPIO1 的DR寄存器的 bit4,得到该位的值为 0,表示当前引脚的输入状态为低电平。

1.4.3 PSR引脚状态寄存器

读取相应的位即可获取对应的 GPIO 的状态,也就是 GPIO 的高低电平值。PSR 引脚状态寄存器相当于 DR 寄存器的简化版,它仅在 GDIR 方向寄存器设置为输入模式时有效,它的每个位表示一个引脚当前的输入电平状态。PSR 寄存器的权限是只读的,对它进行写操作是无效的。

特别地,当引脚被配置成输出模式时,若 IOMUXC 中的 MUX 寄存器使能了 SION 功能(输出通道回环至输入), 可以通过 PSR 寄存器读取回引脚的状态值。

二、官方SDK库文件

使用汇编语言和C语言实现点亮LED灯。需要自己查找、定义那么多寄存器。这样做的缺点很明显,易错、费时、代码可读性差。NXP官方SDK中已经将所有的寄存器以及所有可用引脚的复用功能定义好了。

2.1 寄存器定义文件

在官方SDK的 “SDK_2.2_MCIM6ULL_EBF6ULL/devices/MCIMX6Y2” 目录下, 头文件 “ MCIMX6Y2.h” 文件多达4万多行,包含了i.MX6U芯片几乎所有的寄存器定义以及中断编号的定义。

这里只列 GPIO1相关寄存器 的部分代码。其他寄存器定义与此类似。 添加这些定义之后我们就可以 直接使用 “GPIO1->DR” 语句操作GPIO1的DR寄存器。操作方法与STM32非常相似。

typedef struct {
   __IO uint32_t DR;     /**< GPIO data register, offset: 0x0 */
   __IO uint32_t GDIR;   /**< GPIO direction register, offset: 0x4 */
   __I  uint32_t PSR;    /**< GPIO pad status register, offset: 0x8 */
   __IO uint32_t ICR1;   /**< GPIO interrupt configuration register1,*/
   __IO uint32_t ICR2;   /**< GPIO interrupt configuration register2, */
   __IO uint32_t IMR;   /**< GPIO interrupt mask register, offset: 0x14 */
   __IO uint32_t ISR; /**< GPIO interrupt status register, offset: 0x18 */
   __IO uint32_t EDGE_SEL;/**< GPIO edge select register, offset: 0x1C */
} GPIO_Type;

/*********************以下代码省略***************************8*/
/** Peripheral GPIO1 base address */
#define GPIO1_BASE                               (0x209C000u)
/** Peripheral GPIO1 base pointer */
#define GPIO1                                    ((GPIO_Type *)GPIO1_BASE)

2.2 引脚复用和引脚属性定义文件

使用每一个引脚之前我们都要选择引脚的复用功能以及引脚的pad属性。在官方SDK中定义了所有可用引脚以及这些引脚的所有复用功能,我们需要哪种复用功能只需要选择即可,并且官方SDK中提供了初始化函数。

  • 定义引脚的复用功能
    这里只列出了“GPIO1_IO00”引脚的复用功能,其他引脚类似。每个引脚对应多个宏定义代表引脚的不同的复用功能,以宏“IOMUXC_GPIO1_IO00_I2C2_SCL”为例,它表示“GPIO1_IO00”引脚复用为“I2C2”的“SCL”引脚。这些宏定义将会用作某些函数的入口参数。
#define IOMUXC_GPIO1_IO00_I2C2_SCL \
                        0x020E005CU, 0x0U, 0x020E05ACU, 0x1U, 0x020E02E8U
#define IOMUXC_GPIO1_IO00_GPT1_CAPTURE1L \
                        0x020E005CU, 0x1U, 0x020E058CU, 0x0U, 0x020E02E8U
#define IOMUXC_GPIO1_IO00_ANATOP_OTG1_IDL   \
                        0x020E005CU, 0x2U, 0x020E04B8U, 0x0U, 0x020E02E8U
#define IOMUXC_GPIO1_IO00_ENET1_REF_CLK1L  \
                        0x020E005CU, 0x3U, 0x020E0574U, 0x0U, 0x020E02E8U
#define IOMUXC_GPIO1_IO00_MQS_RIGHTL  \
                        0x020E005CU, 0x4U, 0x00000000U, 0x0U, 0x020E02E8U
#define IOMUXC_GPIO1_IO00_GPIO1_IO00L  \
                        0x020E005CU, 0x5U, 0x00000000U, 0x0U, 0x020E02E8U
#define IOMUXC_GPIO1_IO00_ENET1_1588_EVENT0_INL \
                        0x020E005CU, 0x6U, 0x00000000U, 0x0U, 0x020E02E8U
#define IOMUXC_GPIO1_IO00_SRC_SYSTEM_RESETL  \
                        0x020E005CU, 0x7U, 0x00000000U, 0x0U, 0x020E02E8U
#define IOMUXC_GPIO1_IO00_WDOG3_WDOG_BL   \
                        0x020E005CU, 0x8U, 0x00000000U, 0x0U, 0x020E02E8U
#define IOMUXC_GPIO1_IO01_I2C2_SDAL    \
                        0x020E0060U, 0x0U, 0x020E05B0U, 0x1U, 0x020E02ECU
#define IOMUXC_GPIO1_IO01_GPT1_COMPARE1L  \
                        0x020E0060U, 0x1U, 0x00000000U, 0x0U, 0x020E02ECU
#define IOMUXC_GPIO1_IO01_USB_OTG1_OCL    \
                        0x020E0060U, 0x2U, 0x020E0664U, 0x0U, 0x020E02ECU
  • 引脚复用功能设置函数
    IOMUXC_SetPinMux() 拥有6个入口参数, 但是前五个是通过上面的宏定义自动完成设置的。而第6个入口参数“inputOnfiled”用于设置是否开启读回引脚电平功能。
static inline void IOMUXC_SetPinMux(uint32_t muxRegister,
                                    uint32_t muxMode,
                                    uint32_t inputRegister,
                                    uint32_t inputDaisy,
                                    uint32_t configRegister,
                                    uint32_t inputOnfield)
{
   *((volatile uint32_t *)muxRegister) =
                  IOMUXC_SW_MUX_CTL_PAD_MUX_MODE(muxMode) |\
                  IOMUXC_SW_MUX_CTL_PAD_SION(inputOnfield);

   if (inputRegister)
   {
      *((volatile uint32_t *)inputRegister) = \
      IOMUXC_SELECT_INPUT_DAISY(inputDaisy);
   }
}
  • 引脚PAD属性设置函数
    IOMUXC_SetPinConfig() 函数共有6个入口参数,其中前五个是通过上面的宏定义自动完成设置的。而第6个参数用于设置PAD属性,根据每个引脚拥有一个32位PAD属性寄存器。第六个参数就是设置要填入PAD属性寄存器的值。
static inline void IOMUXC_SetPinConfig(uint32_t muxRegister,
                                       uint32_t muxMode,
                                       uint32_t inputRegister,
                                       uint32_t inputDaisy,
                                       uint32_t configRegister,
                                       uint32_t configValue)
{
   if (configRegister)
   {
      *((volatile uint32_t *)configRegister) = configValue;
   }
}

三、引脚确定

我使用的是 野火_EBF6ULL S1 Pro 开发板

从原理图可看到 RGB 灯的三个阴极 R、G、B 连接分别连接至标号 GPIO_4CSI_HSYNCCSI_VSYNC, 这些标号实际上与配套核心板上 i.MX6ULL 芯片的引脚相连。由于引脚功能众多, 绘制原理图时不可避免地无法完全表示引脚信息的所有信息。而无论是具体的引脚名还是复用功能, 我们都无法直接得知这些具体是 i.MX6ULL 芯片的哪个引脚。我们需要知道这些引脚是对应的具体 GPIO,这样我们才能编写程序进行控制。

由于还不清楚标号 GPIO_4CSI_HSYNCCSI_VSYNC 的具体引脚名,我们首先要在核心板原理图中查看它与 i.MX6ULL 芯片的关系。打开 《野火_EBF6ULL S1 邮票孔核心板_V1.0_原理图》,在PDF阅读器的搜索框输入前面的 GPIO_4CSI_HSYNCCSI_VSYNC 标号。


查找到了 GPIO_4 信号的具体引脚名为 GPIO1_IO04。 但是当我们使用同样的方法查找时发现只能找到 CSI_HSYNCCSI_VSYNC, 并没有我们熟悉的 GPIOx_IOx 标注的引脚名。这两个引脚默认情况下不用作 GPIO,而是用作摄像头的某一功能引脚,但是它可以复用为 GPIO,我们怎么找到对应的 GPIO 呢?

  • 方法一:
    在**《i.MX 6UltraLite Applications Processor Reference Manual》的第4章 External Signals and Pin Multiplexing** 搜索引脚名

  • 方法二:
    在官方写好的文件 fsl_iomuxc.h(路径:SDK文件夹/devices/MCIMX6Y2/drivers/fsl_iomuxc.h) 中搜索引脚名

经查阅,我们把以上连接 LED 灯的各个 i.MX6ULL 芯片引脚总结出如表:

LED灯原理图的标号具体引脚名GPIO端口及引脚编号
R灯GPIO_4GPIO1_IO04GPIO1_IO04
G灯CSI_HSYNCCSI_HSYNCGPIO4_IO20
B灯CSI_VSYNCCSI_VSYNCGPIO4_IO19

四、编程流程

1. 移植官方寄存器定义文件
2. 移植引脚复用以及引脚属性定义文件
3. 使用官方定义的寄存器、引脚设置函数实现以下步骤
4. 开启GPIO时钟
5. 设置引脚的复用功能以及引脚属性
6. 设置引脚方向以及输出电平

五、宏定义实现PAD属性设置

通常情况下一个引脚要设置8种PAD属性,而这些属性只能通过数字指定。为简化PAD属性设置野火编写了一个PAD属性配置文件 pad_config.h (embed_linux_driver_tutorial_imx6_code/bare_metal/led_rgb_c/pad_config.h),这里使用宏定义了引脚可选的PAD属性值,并且通过宏定义的名字很容易知道宏代表的属性值:

/* SPEED 带宽配置 */
#define SPEED_0_LOW_50MHz       IOMUXC_SW_PAD_CTL_PAD_SPEED(0)
#define SPEED_1_MEDIUM_100MHz   IOMUXC_SW_PAD_CTL_PAD_SPEED(1)
#define SPEED_2_MEDIUM_100MHz   IOMUXC_SW_PAD_CTL_PAD_SPEED(2)
#define SPEED_3_MAX_200MHz      IOMUXC_SW_PAD_CTL_PAD_SPEED(3)


/* PUE 选择使用保持器还是上下拉 */
#define PUE_0_KEEPER_SELECTED       IOMUXC_SW_PAD_CTL_PAD_PUE(0)
#define PUE_1_PULL_SELECTED         IOMUXC_SW_PAD_CTL_PAD_PUE(1)


/* PUS 上下拉配置 */
#define PUS_0_100K_OHM_PULL_DOWN  IOMUXC_SW_PAD_CTL_PAD_PUS(0)
#define PUS_1_47K_OHM_PULL_UP     IOMUXC_SW_PAD_CTL_PAD_PUS(1)
#define PUS_2_100K_OHM_PULL_UP    IOMUXC_SW_PAD_CTL_PAD_PUS(2)
#define PUS_3_22K_OHM_PULL_UP     IOMUXC_SW_PAD_CTL_PAD_PUS(3)

完整的代码请阅读源文件,这里只列出了文件“pad_config.h”部分代码(embed_linux_driver_tutorial_imx6_code/bare_metal/led_rgb_c/pad_config.h)。

六、编写启动文件

在 Ubuntu 下创建 start.S 文件用于编写启动文件。
在汇编文件中设置“栈地址”并执行跳转命令跳转到main函数执行C代码。

6.1 完整代码

/***********************第一部分*********************/
  .text            //代码段
  .align 2         //设置2字节对齐
  .global _start   //定义一个全局标号

/*************************第二部分*************************/
  _start:          //程序的开始
    b reset      //跳转到reset标号处

/*************************第三部分*************************/
reset:
   mrc     p15, 0, r0, c1, c0, 0     /*  将 CP15 协处理器中的寄存器数据读到 ARM 寄存器中   */
   bic     r0,  r0, #(0x1 << 12)     /*  清除第12位(I位)禁用 I Cache  */
   bic     r0,  r0, #(0x1 <<  2)     /*  清除第 2位(C位)禁用 D Cache  */
   bic     r0,  r0, #0x2             /*  清除第 1位(A位)禁止严格对齐   */
   bic     r0,  r0, #(0x1 << 11)     /*  清除第11位(Z位)分支预测   */
   bic     r0,  r0, #0x1             /*  清除第 0位(M位)禁用 MMU   */
   mcr     p15, 0, r0, c1, c0, 0     /*  将 ARM 寄存器的数据写入到 CP15 协处理器寄存器中   */

/***********************第四部分*********************/
      ldr sp, =0x84000000   //设置栈地址64M
      b main                //跳转到main函数

/***********************第五部分*******************/
    /*进入死循环*/
  loop:
      b loop

6.2 分析代码

  • 第一部分
    .text 定义代码段。
    .align 2 设置字节对齐。
    .global _start 生命全局标号_start。
/*************************第一部分*************************/
.text            //代码段
.align 2         //设置2字节对齐
.global _start   //定义一个全局标号
  • 第二部分
    _start: 定义标号_start: ,它位于汇编的最前面,说以会首先被执行。
    b reset 使用b指令将程序跳转到reset标号处。
/*************************第二部分*************************/
_start:          //程序的开始
   b reset      //跳转到reset标号处
  • 第三部分
    通过修改CP15寄存器(系统控制寄存器) 关闭 I Cache 、D Cache、MMU 等等。
    我们暂时用不到的功能,如果开启可能会影响我们裸机运行,为避免不必要的麻烦暂时关闭这些功能。
/*************************第三部分*************************/
reset:
   mrc     p15, 0, r0, c1, c0, 0     /*  将 CP15 协处理器中的寄存器数据读到 ARM 寄存器中   */
   bic     r0,  r0, #(0x1 << 12)     /*  清除第12位(I位)禁用 I Cache  */
   bic     r0,  r0, #(0x1 <<  2)     /*  清除第 2位(C位)禁用 D Cache  */
   bic     r0,  r0, #0x2             /*  清除第 1位(A位)禁止严格对齐   */
   bic     r0,  r0, #(0x1 << 11)     /*  清除第11位(Z位)分支预测   */
   bic     r0,  r0, #0x1             /*  清除第 0位(M位)禁用 MMU   */
   mcr     p15, 0, r0, c1, c0, 0     /*  将 ARM 寄存器的数据写入到 CP15 协处理器寄存器中   */
  • 第四部分
    ldr sp, =0x84000000 用于设置栈指针。野火i.MX6ULL开发板标配512M的DDR内存,裸机开发用不了这么多。程序中我们将栈地址设置到DDR的64M地址处。 这个值也可以根据需要自行定义。
    b main 只用跳转指令跳转到main函数中执行。
/***********************第四部分*********************/
      ldr sp, =0x84000000   //设置栈地址64M
      b main                //跳转到main函数
  • 第五部分
    b loop 是“无返回”的跳转指令。正常情况下,不会执行第五部分代码。
/***********************第五部分*******************/
  /*进入死循环*/
  loop:
      b loop

七、编写C语言代码

在 Ubuntu 下创建 led.c 文件用于驱动 RGB 灯。

7.1 完整代码

 /*************************第一部分************************/
  #include "MCIMX6Y2.h"
  #include "fsl_iomuxc.h"
  #include "pad_config.h"

  /*************************第二部分************************/
  /*LED GPIO端口、引脚号及IOMUXC复用宏定义*/
  #define RGB_RED_LED_GPIO                GPIO1
  #define RGB_RED_LED_GPIO_PIN            (4U)
  #define RGB_RED_LED_IOMUXC              IOMUXC_GPIO1_IO04_GPIO1_IO04

  #define RGB_GREEN_LED_GPIO              GPIO4
  #define RGB_GREEN_LED_GPIO_PIN          (20U)
  #define RGB_GREEN_LED_IOMUXC            IOMUXC_CSI_HSYNC_GPIO4_IO20

  #define RGB_BLUE_LED_GPIO               GPIO4
  #define RGB_BLUE_LED_GPIO_PIN           (19U)
  #define RGB_BLUE_LED_IOMUXC             IOMUXC_CSI_VSYNC_GPIO4_IO19


  /*************************第三部分************************/
  /* 所有引脚均使用同样的PAD配置 */
  #define LED_PAD_CONFIG_DATA            (SRE_0_SLOW_SLEW_RATE| \
                                          DSE_6_R0_6| \
                                          SPEED_2_MEDIUM_100MHz| \
                                          ODE_0_OPEN_DRAIN_DISABLED| \
                                          PKE_0_PULL_KEEPER_DISABLED| \
                                          PUE_0_KEEPER_SELECTED| \
                                          PUS_0_100K_OHM_PULL_DOWN| \
                                          HYS_0_HYSTERESIS_DISABLED)
      /* 配置说明 : */
      /* 转换速率: 转换速率慢
        驱动强度: R0/6
        带宽配置 : medium(100MHz)
        开漏配置: 关闭
        拉/保持器配置: 关闭
        拉/保持器选择: 保持器(上面已关闭,配置无效)
        上拉/下拉选择: 100K欧姆下拉(上面已关闭,配置无效)
        滞回器配置: 关闭 */

  /*************************第四部分************************/
  /*简单延时函数*/
  void delay(uint32_t count)
  {
      volatile uint32_t i = 0;
      for (i = 0; i < count; ++i)
      {
          __asm("NOP"); /* 调用nop空指令 */
      }
  }


  int main()
  {
      /*************************第五部分************************/
      CCM->CCGR1 |= CCM_CCGR1_CG13(0x3);//开启GPIO1的时钟
      CCM->CCGR3 |= CCM_CCGR3_CG6(0x3); //开启GPIO4的时钟

      /*************************第六部分************************/
      /*设置 红灯 引脚的复用功能以及PAD属性*/
      IOMUXC_SetPinMux(RGB_RED_LED_IOMUXC,0);
      IOMUXC_SetPinConfig(RGB_RED_LED_IOMUXC, LED_PAD_CONFIG_DATA);

      /*设置 绿灯 引脚的复用功能以及PAD属性*/
      IOMUXC_SetPinMux(RGB_GREEN_LED_IOMUXC,0);
      IOMUXC_SetPinConfig(RGB_GREEN_LED_IOMUXC, LED_PAD_CONFIG_DATA);

      /*设置 蓝灯 引脚的复用功能以及PAD属性*/
      IOMUXC_SetPinMux(RGB_BLUE_LED_IOMUXC,0);
      IOMUXC_SetPinConfig(RGB_BLUE_LED_IOMUXC, LED_PAD_CONFIG_DATA);

      /*************************第七部分************************/
      GPIO1->GDIR |= (1<<4);  //设置GPIO1_04为输出模式
      GPIO1->DR |= (1<<4);    //设置GPIO1_04输出电平为高电平

      GPIO4->GDIR |= (1<<20);  //设置GPIO4_20为输出模式
      GPIO4->DR |= (1<<20);    //设置GPIO4_20输出电平为高电平

      GPIO4->GDIR |= (1<<19);  //设置GPIO4_19为输出模式
      GPIO4->DR |= (1<<19);    //设置GPIO4_19输出电平为高电平

      /*************************第八部分************************/
      while(1)
      {
           GPIO1->DR &= ~(1<<4); //红灯亮
           delay(0xFFFFF);
           GPIO1->DR |= (1<<4); //红灯灭

           GPIO4->DR &= ~(1<<20); //绿灯亮
           delay(0xFFFFF);
           GPIO4->DR |= (1<<20); //绿灯灭

           GPIO4->DR &= ~(1<<19); //蓝灯亮
           delay(0xFFFFF);
           GPIO4->DR |= (1<<19); //蓝灯灭
      }
      return 0;
  }

7.2 分析代码

  • 第一部分:添加头文件
    文件 “MCIMX6Y2.h”“fsl_iomuxc.h” 来自SDK。 文件 “pad_config.h” 是野火编写的文件,在其他工程中可直接使用。
 /*************************第一部分************************/
  #include "MCIMX6Y2.h"
  #include "fsl_iomuxc.h"
  #include "pad_config.h"
  • 第二部分:定义GPIO相关引脚以及复用功能
  /*************************第二部分************************/
  /*LED GPIO端口、引脚号及IOMUXC复用宏定义*/
  #define RGB_RED_LED_GPIO                GPIO1
  #define RGB_RED_LED_GPIO_PIN            (4U)
  #define RGB_RED_LED_IOMUXC              IOMUXC_GPIO1_IO04_GPIO1_IO04

  #define RGB_GREEN_LED_GPIO              GPIO4
  #define RGB_GREEN_LED_GPIO_PIN          (20U)
  #define RGB_GREEN_LED_IOMUXC            IOMUXC_CSI_HSYNC_GPIO4_IO20

  #define RGB_BLUE_LED_GPIO               GPIO4
  #define RGB_BLUE_LED_GPIO_PIN           (19U)
  #define RGB_BLUE_LED_IOMUXC             IOMUXC_CSI_VSYNC_GPIO4_IO19
  • 第三部分:定义引脚的PAD属性
    PAD属性宏定义保存在 “pad_config.h” 文件中,这里使用 “|” 运算符将所有属性设置“合并”在一起,后面将作为函数参数。
  /*************************第三部分************************/
  /* 所有引脚均使用同样的PAD配置 */
  #define LED_PAD_CONFIG_DATA            (SRE_0_SLOW_SLEW_RATE| \
                                          DSE_6_R0_6| \
                                          SPEED_2_MEDIUM_100MHz| \
                                          ODE_0_OPEN_DRAIN_DISABLED| \
                                          PKE_0_PULL_KEEPER_DISABLED| \
                                          PUE_0_KEEPER_SELECTED| \
                                          PUS_0_100K_OHM_PULL_DOWN| \
                                          HYS_0_HYSTERESIS_DISABLED)
      /* 配置说明 : */
      /* 转换速率: 转换速率慢
        驱动强度: R0/6
        带宽配置 : medium(100MHz)
        开漏配置: 关闭
        拉/保持器配置: 关闭
        拉/保持器选择: 保持器(上面已关闭,配置无效)
        上拉/下拉选择: 100K欧姆下拉(上面已关闭,配置无效)
        滞回器配置: 关闭 */
  • 第四部分:简单的延时函数
 /*************************第四部分************************/
  /*简单延时函数*/
  void delay(uint32_t count)
  {
      volatile uint32_t i = 0;
      for (i = 0; i < count; ++i)
      {
          __asm("NOP"); /* 调用nop空指令 */
      }
  }
  • 第五部分:开启GPIO时钟
/*************************第五部分************************/
CCM->CCGR1 |= CCM_CCGR1_CG13(0x3);//开启GPIO1的时钟
CCM->CCGR3 |= CCM_CCGR3_CG6(0x3); //开启GPIO4的时钟
  • 第六部分:设置引脚的复用功能以及引脚PAD属性
/*************************第六部分************************/
/*设置 红灯 引脚的复用功能以及PAD属性*/
IOMUXC_SetPinMux(RGB_RED_LED_IOMUXC,0);
IOMUXC_SetPinConfig(RGB_RED_LED_IOMUXC, LED_PAD_CONFIG_DATA);

/*设置 绿灯 引脚的复用功能以及PAD属性*/
IOMUXC_SetPinMux(RGB_GREEN_LED_IOMUXC,0);
IOMUXC_SetPinConfig(RGB_GREEN_LED_IOMUXC, LED_PAD_CONFIG_DATA);

/*设置 蓝灯 引脚的复用功能以及PAD属性*/
IOMUXC_SetPinMux(RGB_BLUE_LED_IOMUXC,0);
IOMUXC_SetPinConfig(RGB_BLUE_LED_IOMUXC, LED_PAD_CONFIG_DATA);
  • 第七部分:设置GPIO为输出并设置初始电平为高电平
/*************************第七部分************************/
GPIO1->GDIR |= (1<<4);  //设置GPIO1_04为输出模式
GPIO1->DR |= (1<<4);    //设置GPIO1_04输出电平为高电平

GPIO4->GDIR |= (1<<20);  //设置GPIO4_20为输出模式
GPIO4->DR |= (1<<20);    //设置GPIO4_20输出电平为高电平

GPIO4->GDIR |= (1<<19);  //设置GPIO4_19为输出模式
GPIO4->DR |= (1<<19);    //设置GPIO4_19输出电平为高电平
  • 第八部分:在while(1)中依次点亮红灯、绿灯和蓝灯
/*************************第八部分************************/
while(1)
{
  GPIO1->DR &= ~(1<<4); //红灯亮
  delay(0xFFFFF);
  GPIO1->DR |= (1<<4); //红灯灭

  GPIO4->DR &= ~(1<<20); //绿灯亮
  delay(0xFFFFF);
  GPIO4->DR |= (1<<20); //绿灯灭

  GPIO4->DR &= ~(1<<19); //蓝灯亮
  delay(0xFFFFF);
  GPIO4->DR |= (1<<19); //蓝灯灭
}

八、编写链接脚本

写好的代码(无论是汇编还是C语言)都要经过编译、汇编、链接等步骤生成二进制文件或者可供下载的文件。在编译阶编译器会对每个源文件进行语法检查并生成对应的汇编语言,汇编是将汇编文件转化为机器码。

使用 arm-none-eabi-gcc -g -c led.S -o led.o 命令完成源码的编译、汇编工作,生成了 .o文件。编译和汇编是针对单个源文件,也就编译完成后一个源文件(.c.S.s)对应一个 .o 文件。程序链接阶段就会将这些 .o 链接成一个文件。

链接脚本的作用就是告诉编译器怎么链接这些文件,比如那个文件放在最前面,程序的代码段、数据段、bss段分别放在什么位置等等。

在 Ubuntu 下创建 led.lds 链接脚本。

8.1 完整代码

 ENTRY(_start)
 SECTIONS {
   . = 0x80000000;

   . = ALIGN(4);
   .text :
   {
   start.o (.text)
   *(.text)
   }

   . = ALIGN(4);
   .data :
   {
   *(.data)
   }

   . = ALIGN(4);
   .bss :
   {
   *(.bss)
   }
 }

8.2 分析代码

  • 指定程序的入口
    ENTRY(_start) 用于指定程序的入口,ENTRY() 是设置入口地址的命令, “_start” 是程序的入口,led程序的入口地址位于 start.S“_start” 标号处。
 ENTRY(_start)
  • 定义SECTIONS
    SECTIONS 可以理解为是一块区域,我们在这块区域排布我们的代码,链接时链接器就会按照这里的指示链接我们的代码。
 SECTIONS {
···
···
}
  • 定义链接起始地址
    “.” 运算符代表当前位置。 我们在SECTION的最开始使用 “.= 0x80000000” 就是将链接起始地址设置为0x80000000。
. = 0x80000000;
  • 设置字节对齐
    “. = ALIGN(4);” 它表示从当前位置开始执行四字节对齐。假设当前位置为0x80000001,执行该命令后当前地址将会空出三个字节转到0x80000004地址处。

  • 设置代码段
    “.text :” 用于定义代码段,固定的语法要求,我们按照要求写即可。在“{}”中指定那些内容放在代码段。
    start.o 中的代码放到代码段的最前面。start.S是启动代码应当首先被执行,所以通常情况下要把它放到代码段的最前面,其他源文件的代码按照系统默认的排放顺序即可,通配符 “*” 在这里表示其他剩余所有的 .o文件。

   . = ALIGN(4);
   .text :
   {
   start.o (.text)
   *(.text)
   }
  • 设置数据段
    同设置代码段类似,首先设置字节对齐,然后定义代码段。在数据段里使用 “*” 通配符, 将所有源文件中的代码添加到这个数据段中。
   . = ALIGN(4);
   .data :
   {
   *(.data)
   }
  • 设置BSS段
    设置方法与设置数据段完全相同。
. = ALIGN(4);
   .bss :
   {
   *(.bss)
   }

九、编写makefile文件

程序编写完成后需要依次输入编译、链接、格式转换命令才能最终生成二进制文件。这种编译方式效率低、容易出错。

使用makefile只需要在所在文件夹下执行make命令,makefile工具便会自动完成程序的编译、链接、格式转换等工作。正常情况下我们可以在当前目录看到生成的一些中间文件以及我们期待的.bin文件。

在 Ubuntu 下创建 makefile 文件。

9.1 完整代码

 all: start.o led.o
    arm-none-eabi-ld -Tled.lds  $^ -o led.elf
    arm-none-eabi-objcopy -O binary -S -g led.elf led.bin

  %.o : %.S
    arm-none-eabi-gcc -g -c $^ -o start.o
  %.o : %.c
    arm-none-eabi-gcc -g -c $^ -o led.o


  .PHONY: clean
  clean:
    rm *.o *.elf *.bin

9.2 分析代码

  • 添加最终目标以及依赖文件
 all: start.o led.o
  • 添加链接命令
    “-Tled.lds” 表示使用led.lds链接脚本链接程序。
    “$^” 代表所有的依赖文件。
    “-o” 指定输出文件名。
    arm-none-eabi-ld -Tled.lds  $^ -o led.elf
  • 添加格式转换命令
    “-O binary” 指定输出二进制文件。
    “-S” 不从源文件中复制重定位信息和符号信息。
    “-g” 不从源文件中复制可调试信息。
    arm-none-eabi-objcopy -O binary -S -g led.elf led.bin
  • 添加汇编文件编译命令
    “$^” 替代要编译的源文件。
  %.o : %.S
    arm-none-eabi-gcc -g -c $^ -o start.o
  • 添加编译C文件的命令
    “$^” 替代要编译的源文件。
  %.o : %.c
    arm-none-eabi-gcc -g -c $^ -o led.o
  • 添加清理命令
    “.PHONY” 定义了伪目标“clean”。伪目标一般没有依赖,并且 “clean” 伪目标一般放在Makefile文件的末尾。
    “clean” 为目标用于删除make生成的文件。
  .PHONY: clean
  clean:
    rm *.o *.elf *.bin

十、编译下载验证

10.1 编译代码

make

执行make命令,生成led.bin文件。

10.2 代码烧写

编译成功后会在当前文件夹下生成.bin文件,这个.bin文件也不能直接放到开发板上运行, 这次是因为需要在.bin文件缺少启动相关信息。

为二进制文件添加头部信息并烧写到SD卡。查看 IMX6ULL学习笔记(12)——通过SD卡启动官方SDK程序

进入烧写工具目录,执行 ./mkimage.sh <烧写文件路径> 命令,例如要烧写的 led.bin 位于 home 目录下,则烧写命令为 ./mkimage.sh /home/led.bin

执行上一步后会列出linux下可烧写的磁盘,选择你插入的SD卡即可。这一步 非常危险!!!一定要确定选择的是你插入的SD卡!!,如果选错很可能破坏你电脑磁盘内容,造成数据损坏!!! 确定磁盘后SD卡以“sd”开头,选择“sd”后面的字符即可。例如要烧写的sd卡是“sdb”则输入“b”即可。

10.3 实验现象

将开发板设置为SD卡启动,接入SD卡,开发板上电,可以看到开发板上RGB红、绿、蓝三种颜色轮流闪烁。


• 由 Leung 写于 2022 年 12 月 25 日

• 参考:6. 完善LED程序

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/115096.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

市级数字政府电子政务大数据中心项目建设和运营方案

【版权声明】本资料来源网络&#xff0c;仅用于行业知识分享&#xff0c;供个人学习参考&#xff0c;不得作商业用途。【侵删致歉】如有侵权请联系小编&#xff0c;将在收到信息后第一时间进行删除&#xff01; 完整资料领取见文末&#xff0c;部分资料内容&#xff1a; 1.1 大…

【QTimeEdit | QDateEdit | QDateTimeEdit | QCalendarWidget | QLCDNumber】

【QTimeEdit | QDateEdit | QDateTimeEdit | QCalendarWidget | QLCDNumber】【1】UI界面设计【2】相关头文件【3】构造函数初始化【4】setDate | setTime | setDateTime | currentDate | currentTime | currentDateTime【5】maximumDate | maximumTime | minimumDate | minimu…

基于Java+SpringBoot+vue等疫情期间网课管理系统详细设计和实现

博主介绍&#xff1a;✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取联系&#x1f345;精彩专栏推荐订阅收藏&#x1f447;&…

使用Prometheus和Grafana监控Apache Doris

目录1. 介绍2. Prometheus和Grafana的简单使用3. 配置Prometheus的prometheus.yml4. 下载Doris Dashboard模板1. 介绍 Doris的FE和BE通过http端口metrics路径&#xff0c;将监控数据暴露出来&#xff0c;以key-value的文本形式对外展现&#xff0c;每个key还可能有不同的Label…

Nacos注册中心

【Spring Cloud Alibaba】 1. Spring Cloud Alibaba Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件&#xff0c;方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。 依托 Spring Cloud …

微信小程序开发—入门到跑路(五)

文章目录1. 今日目标2. 使用 npm2.1 小程序对 npm 的支持和限制问题2.2 了解什么是 vant Weapp2.3 安装 Vant 组件库问题2.4 使用 Vant 组件问题2.5 定义和使用 CSS 变量问题2.6 使用 CSS 变量定制 Vant 的主题样式问题2.7 什么是小程序 API 的 Promise 化2.8 安装并构建 minip…

运输层协议概述(计算机网络-运输层)

目录 运输层协议的位置 运输层为相互通信的应用进程提供了逻辑通信 应用进程之间的通信 客户-服务器通信模式 互联网的运输层协议 UDP 与 TCP 运输层的复用与分用 运输层端口的概念 端口在进程之间的通信中所起的作用 端口号 运输层协议的位置 从通信和信息处理的角度…

2022年终总结(脚踏实地,仰望星空)

2022年终总结 回忆录 2022年焦虑和快乐是这一年中最大的两种情绪了。焦虑主要是因为心里的三块石头&#xff0c;从年初就开始悬着。第一块石头&#xff0c;科研论文录用&#xff0c;第二个石头&#xff0c;拿到国奖&#xff0c;第三个石头是拿到满意的offer。目前只剩下最后一…

网络实验之EtherChannel技术实践

一、EtherChannel简介 EtherChannel简单来说就是将多个物理端口绑定为一个逻辑端口&#xff0c;通过多个端口绑定&#xff0c;能充分利用现有端口来增加带宽。构成etherchannel的端口必须配置成相同的特性&#xff0c;如双工模式、速度、同为FE或GE端口、native VLAN,、VLAN ra…

C++11标准模板(STL)- 算法(std::inner_product)

定义于头文件 <algorithm> 算法库提供大量用途的函数&#xff08;例如查找、排序、计数、操作&#xff09;&#xff0c;它们在元素范围上操作。注意范围定义为 [first, last) &#xff0c;其中 last 指代要查询或修改的最后元素的后一个元素。 计算两个范围的元素的内积…

十七、Docker Compose容器编排第二篇

在上一篇中我们讲解了容器编排是什么、能干什么、怎么安装、使用步骤&#xff0c;如果没有看的大家可以先看下&#xff1a;https://blog.csdn.net/u011837804/article/details/128335166&#xff0c;然后继续看这一篇&#xff0c;好了&#xff0c;我们继续。 1、Docker Compons…

gl-Camera

我的服务原文访问&#xff1a;Camera 1.创建摄像机的坐标系&#xff0c;&#xff08;创建原理&#xff0c;两条直线求其法向量&#xff09; Z轴:在世界坐标中指向摄像机的向量&#xff08;D&#xff09; X轴&#xff1a;随便找一个向上量和Z向量求出的法向量就是X轴&#xf…

PostgreSQL数据库TableAM——Table scan callbacks

TableAM Table scan TableAM提供了如下4个接口用于实现表数据的扫描功能。scan_begin函数的形参nkeys不为零&#xff0c;则扫描结果需要根据scan keys先进行过滤&#xff1b;pscan如果不为null&#xff0c;说明该结构体已经由parallelscan_initialize初始化过了(仅仅在table_b…

初识Docker:(5)Docker自定义镜像

初识Docker&#xff1a;&#xff08;5&#xff09;Docker自定义镜像镜像结构Dockerfile语法什么是Dockerfile构建Java项目案例1&#xff1a;基于ubuntu镜像构建一个新镜像&#xff0c;运行一个java项目案例2&#xff1a;基于java:8-alpine镜像&#xff0c;将一个java项目构建为…

Java+JSP机房课表管理系统(含源码+论文+答辩PPT等)

项目功能简介: 该项目采用技术CSSJavaScriptMySQLServlet、MySQL数据库、项目含有源码、配套开发软件、软件安装教程、项目发布教程等 项目功能介绍&#xff1a; 系统管理&#xff1a;包含用户的注册&#xff0c;管理&#xff0c;信息修改 课程管理&#xff1a;包含课程录入、维…

IT大侦“碳”:VxRail的可持续法宝

环境Environmental      社会责任Social Responsibility      企业治理Corporate Governance      随着碳达峰、碳中和的逐步推进,越来越多的“大厂”或各行业的明星企业都开始重视自己的ESG报告,已然成为了商界新风尚。      可持续发展战略也与前沿技术密切相…

matlab神经网络求解最优化,matlab神经网络训练数据

1、神经网络的准确率是怎么计算的&#xff1f; 其实神经网络的准确率的标准是自己定义的。 我把你的例子赋予某种意义讲解&#xff1a; 1&#xff0c;期望输出[1 0 0 1]&#xff0c;每个元素代表一个属性是否存在。像着4个元素分别表示&#xff1a;是否肺炎&#xff0c;是否肝…

哈希知识点

目录对比map/set1. unordered系列关联式容器1.1 unordered_map2. 底层结构2.1 哈希概念2.2 哈希冲突2.3 哈希函数2.4 哈希冲突解决2.4.1 闭散列线性探测和二次探测扩容&#xff08;负载因子&#xff09;闭散列实现的hash2.4.2 开散列概念开散列思考实现模拟实现模板参数列表的改…

Java项目:springboot农业物资管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 农业物资管理系统&#xff0c;管理员可以对角色进行配置&#xff0c;分配用户角色&#xff1b; 主要功能包含&#xff1a;登录、注册、修改密码…

并查集解决重复员工问题

简介 工作一年多了&#xff0c;天天CRUD&#xff0c;终于以前学习的算法排上用场了。 背景 我们的系统在用户注册时没有校验身份id&#xff08;身份证&#xff09;和电话号码的唯一&#xff0c;可能使用相同的身份id或者电话号码创建多个账号&#xff0c;导致有些人开多个账…