正点原子嵌入式linux驱动开发——TF-A初探

news2024/12/28 5:12:26

上一篇笔记中,正点原子的文档简单讲解了一下什么是TF-A,并且也学习了如何编译TF-A。但是TF-A是如何运行的,它的一个运行流程并未涉及。TF-A的详细运行过程是很复杂的,涉及到很多ARM处理器底层知识,所以这一篇笔记的内容就是讲解一下TF-A的整个框架以及运行的大致流程

设备如何保证安全

设备的安全保护涉及到很多方面,这里的笔记主要就是讲解TF-A。TF-A主要保护的就是设备启动过程,通过各种鉴权,保证设备启动的过程中每个阶段的固件都是安全的

对于传统的ARM处理而言, Linux系统的启动流程就是:内部BootROM->Uboot->kernel->rootfs,整个启动过程是一个链式结构启动过程其实是没有安全校验的(有些公司会自行定义一些校验方法)。加入TF-A固件以后, TF-A就可以对uboot、kernel进行校验,如果还要使用TEE OS(Trusted Execution Environment,TEE),那么TF-A还要完成对TEE OS的校验。

之前的笔记已经有所涉及STM32MP1的安全启动 (Secure Boot,有些资料也叫做安全引导),安全启动目的是为了保证整个启动过程各个镜像的完整性,防止被不法分子破坏或替换掉。Linux启动是一个链式结构,因此安全启动的鉴权 (校验)过程也是链式结构的。在系统启动的过程中,会先对下一个要加载运行的镜像进行鉴权,只有鉴权成功此镜像才能运行,并进入到下一阶段,同样要先对下一阶段的镜像进行鉴权,只要其中有一环鉴权失败,那么整个系统就会启动失败。(也就是说,鉴权成功了才会进入下一个鉴权,不然直接启动失败)。

STM32MP1使用ECDSA(Elliptic Curve Digital Signature,椭圆曲线数字签名算法)验证算法来完成鉴权,ECDSA相比 RSA效果好,而且秘钥更小,STM32MP1使用256位的ECDSA秘钥。一共有两个算法用于计算 ECDSA:

  • P-256 NIST
  • Brainpool 256

可以在STM32MP1头部信息中定义使用哪个算法,STM32MP1整个加密过程比较复杂,正点原子的教程中不做讲解。STM32MP1启动的时候,内部Boot代码会对FSBL进行鉴权,然后FSBL会对后续的内容进行鉴权,这个FSBL就是TF-A。

TF-A概述

TF-A简介

TF-A是ARM可信任固件,是ARM官方提供的一个固件代码,它提供了统一的接口标准,方便不同的半导体厂商将自家的芯片添加到TF-A里面。ST就是在TF-A官
方源码里面添加了STM32MP1系列芯片,之前已经有过笔记教程完成了针对正点原子的STM32MP157开发板完成TF-A的源码补丁,这个补丁文件就是STM32MP1芯片对应的源码补丁文件。

TF-A官网地址为:TF-A官网,此网站不仅仅提供了TF-A,还有针对Cortex-M单片机的TF-M,以及OP-TEE等安全相关软件库。关于TF-A的文档在开发板的资料盘里也是有的,就是Trusted Firmware-A(TF-A用户手册).pdf,如果我之后对这部分感兴趣就去看看,目前只是想学习linux驱动开发,这一部分不是重点。

TF-A一共分为5部分:bl1、bl2、bl2u、bl31、bl32和bl33,打开TF-A源码目录,可以看到这5部分,如下图所示:
TF-A源码
但是在图中可以发现,我们并没有看到bl33这个文件夹,这是因为bl33是TF-A启动的其他镜像固件,比如uboot。上图中bl1、bl2和bl31都属于TF-A固件,而bl32和bl33是TF-A要启动的其他第三方固件,比如TEE OS和uboot。

bl1、bl2、bl31、bl32和bl33是TF-A的不同启动阶段,TF-A的启动过程是链式的,不同的阶段完整的功能不同,bl1、bl2、bl31、bl32和bl33全名如下:

  • bl1:Boot loader stage 1(BL1);
  • bl2:Boot loader stage 2(BL2);
  • bl31:Boot loader stage 3-1 (BL31);
  • bl32:Boot loader stage 3-2 (BL32);
  • bl33:Boot loader stage 3-3 (BL33)。

ARMv7和ARMv8权限等级

TF-A一开始是为ARMv8准备的,ARMv8最突出的特点就是支持64位指令,但是为了兼容原来的ARMv7,ARMv8提供了两种指令集:AAarch64和AArch32,根据字面意思就是64位和32位,其中AArch32和ARMv7基本一样 (会多一些其他操作指令)。 STM32MP1内核为32位的 Cortex-A,所以对应TF-A中的 AArch32。正是因为TF-A一开始是针对 ARMv8准备的,所以TF-A的资料比较难已学习,牵涉到很多底层知识,需要对ARM CPU有所了解。安全不能仅仅依靠软件来实现,也是需要硬件支持的,比如ARM处理器就有不同的运行等级,运行在低等级 (非安全模式)的应用就不能访问高等级 (安全模式)的资源,以此来保证敏感资源的安全性

ARMv7-A工作模式

以前的ARMv7处理器有7种运行模型:User、FIQ、IRQ、Supervisor(SVC)、Abort、Undef和System。但新的ARMv7-A架构加入了TrustZone安全扩展,所以就新加了一种运行模式:Monitor,新的处理器架构还支持虚拟化扩展,因此又加入了另一个运行模式Hyp,所以Cortex-A7处理器有9种处理模式,如下图所示:
ARMv7-A的9种运行模式
不同的处理器模式下,CPU对于硬件的访问权限不同,叫做Privilege Level(特权等级),一共有两个特权级别:Privilege(特权级)和non-privilege(非特权级)。其中只有User模式处于non-privilege,也就是非特权级,剩下的8个模式都是privilege(特权级)。系统启动以后应用软件都是运行在User模式,也就是非特权级,这个时候处理器对于敏感资源的访问是受限的,如果要访问这些敏感资源就需要切换到对应的工作模式下

ARMv7-A对Privilege Level进行了命名:PL0和PL1,后来也出现了PL2,用于虚拟扩展ARMv7-A新增的Monitor模式就是针对安全扩展的,为了支持TEE而引入的

ARMv8工作模式

ARMv8没有Privilege level的概念,取而代之的是**Exception level(异常级别),简称为EL,用于描述特权级别,一共有4个级别:EL0、EL1、EL2和EL3,数字越大,级别越高,权限越大!**这四个EL级别对应的应用场合如下:

  • EL0:一般的应用程序;
  • EL1:操作系统,比如Linux;
  • EL2:虚拟化 (Hypervisor),虚拟机管理器;
  • EL3:最底层的安全固件(安全监视器),比如ARM Trusted Firmware(ARM安全固件, ATF也就是TF-A)。

ARMv8提供了两种安全状态:Secure和Non-secure,也就是安全和非安全,Non-secure也就是正常世界(Normal World)。我们可以在Non-secure运行通用操作系统,比如Linux,在Secure运行可信操作系统,比如OP-TEE,这两个操作系统可以同时运行,这个需要处理器支持 ARM的TrustZone功能。在Normal world和Secure world下, ARMv8的4个EL等级对应的内容如下图所示:
ARMv8在Normal和Secure下EL等级对应的含义
在ARMv8的AArch32模式下,处理器模式如下图所示:
AArch32下处理器模式
从上图可以看出,在AArch32模式下,EL0-LE3对应 ARMv7的不同工作模式:

  • EL0:对应 ARMv7的User工作模式;
  • EL1:对应ARMv7的SVC、ABT、IRQ、UND和SYS这 6种工作模式;
  • EL2:对应ARMv7的Hyp工作模式;
  • EL3:对应ARMv7的Mon工作模式。

可以看出,只有EL3是用于安全监视器的,所以TF-A主要工作在EL3下,TF-A的源码就有大量的“EL3”字样的文件或代码。

TF-A不同启动阶段

TF-A分为不同的启动阶段,按照链式结构依次启动, ATF代码启动流程如下图所示:
TF-A启动流程
从上图中可以看到,当芯片复位以后首先运行bl1代码,bl1一般是芯片内部的ROM代码,bl1主要工作就是将外置Flash中的bl2固件加载到指定的RAM中,然后跳转到bl2部分。

bl2为安全启动固件,bl2会将剩余的三个启动阶段bl31、bl32和bl33对应的镜像文件加载到指定的内存中。比如bl32中的安全操作系统(OP-TEE),bl31中的EL3 运行时固件(Runtime Firware),bl33中的uboot。bl2将这些固件加载完成以后就会启动相应的固件,也就是进入到第三启动阶段。

TF-A 启动流程就是:bl1->bl2->(bl31/bl32/bl33)。注意,bl31、bl32和bl33对应的镜像不需要全部都有,但是bl33一般是必须的,因为bl33一般是uboot

bl1

bl1是TF-A的第一个启动阶段,芯片复位以后就会运行bl1镜像,TF-A 提供了bl1源码。但是,实际上bl1 一般是半导体厂商自己编写的内部Boot ROM代码,并没有使用TF-A提供的bl1镜像,比如STM32MP1的内部ROM代码就是bl1。

一般bl1要做的就是初始化CPU,如果芯片支持不同的启动设备,那么还需要初始化不同的启动设置 ,比如NAND、EMMC、SD、USB或串口等。然后根据BOOT引脚的高低电平来判断当前所选择的启动设备,从对应的启动设备中加载bl2镜像,并放到对应的内存中,最后跳转到bl2镜像并运行

bl2

bl2会进一步的初始化芯片,比如初始化DDR、MMU、串口等。bl2会将剩下三个阶段(bl31、bl32和bl33)对应的镜像加载到指定的内存中,最后根据实际情况来启动剩下三个阶段的镜像。

bl31

在AArch64中,bl31主要是EL3的Runtime固件。(只有AArch64涉及,AArch32没有)。

bl32

bl32一般为安全系统 (TEE OS)固件,比如OP-TEE。 TF-A为AArch32提供了EL3的Runtime软件,这个Runtime软件就是bl32固件,sp_min就是这个Runtime软件。在TF-A的源码bl32文件夹下,就有一个“sp_min”的子文件夹,就是bl32的sp_min源码。

STM32MP1的bl32部分可以使用OP-TEE,也可以使用 sp_min,在正点原子的教程中,为了学习的简便,选择采用sp_min作为bl32镜像

bl33

bl33就是Normal World下的镜像文件,比如uboot。

至此,对TF-A的基本启动流程有了一个大概的了解,我们知道了TF-A分为多个阶段,不同的阶段其工作内容不同。但是,在实际的开发中并不一定会用到TF-A中所有启动阶段。接下来我们就会以STM32MP1为例,看一下 ST是如何在自家的STM32MP1中使用TF-A的

STM32MP1中的TF-A

STM32MP1 TF-A框架

STM32MP1支持TrustZone,所以ST提供的软件包包含了安全固件。相比传统ARM处理器(如ARM9、ARM11等 )最常见的uboot和linux kernel,STM32MP1的软件包还另外提供了TF-A、OP-TEE等安全相关的关键软件,因此STM32MP1的整体软件框架必然和传统的ARM芯片不同,STM32MP1软件架构如下图所示:
STM32MP1软件框架
上图中从左到右分为三部分:Cortex-A7 Secure、Cortex-A7 Non-Secure和Cortex-M4,Cortex-M4属于裸机开发部分,不是linux驱动部分,这个教程不涉及。所以就剩下了Cortex-A7 Secure、Cortex-A7 Non-Secure,也就是A7的安全和非安全两种情况

在Cortex-A7 Secure下重点是TF-A和OP-TEE,TF-A是用于完成安全启动的, OP-TEE是TEE OS,如果使用OP-TEE的话它会和linux内核同时运行, OP-TEE负责可信应用,linux就是普通的应用程序。在Cortex-A7 Non-Secure下就是传统的ARM软件框架:uboot、linux kernel和根文件系统。

之前已经讲解过,TF-A分为了不同阶段:bl1、bl2、bl31、bl32和bl33,这个主要是面向AArch64的,对于 AArch32而言只有4个阶段:

  • bl1:第1个阶段,一般为芯片内部ROM代码;
  • bl2:第2个阶段,可信启动固件;
  • bl32:EL3运行时(Runtime)软件;
  • bl33:非安全固件,比如uboot。

其中 bl1、bl2和bl32都属于TF-A的一部分 (如果使用 TF-A提供的bl1的话)。

STM32MP1下的bl1

bl1部分是可选的,在编译STM32MP1的TF-A的时候可以通过添加BL2_AT_EL3编译选项来移除bl1,默认情况下ST提供的TF-A源码是有添加BL2_AT_EL3编译选项的,在TF-A源码里面找到tf-a-stm32mp-2.2.r1/plat/st/stm32mp1/platform.mk,此文件定义了 STM32MP1这个平台的编译选项,有下图配置:
STM32MP1编译配置文件
可以看出,platform.mk文件定了BL2_AT_EL3为1,因此在编译STM32MP1平台对应的TF-A的时候不会编译bl1部分,STM32MP1内部ROM代码完成了TF-A中的bl1部分的工作,主要就是将外部 Flash中的bl2代码加载到内部RAM中并运行

STM32MP1下的bl2

bl2为可信启动固件,在STM32MP1中就是TF-A的bl2部分,bl2的主要功能就是加载下面几个阶段的固件到内存中,因此bl2需要初始化所要用到的外设。

首先是安全部分,STM32MP1的bl2部分会初始化的外设如下:

  1. BOOT、安全和OTP控制器,也就是BSEC外设;
  2. 扩展的TrustZone保护控制器,也就是ETZPC外设;
  3. TrustZone针对DDR的地址空间保护控制器,也就是TZC外设。

由于bl2需要从外部Flash中加载下一阶段镜像,因此还需要初始化一些外部Flash,例如:

  1. SD卡;
  2. EMMC;
  3. NAND;
  4. NOR。

最后,STM32MP1的bl2还需要初始化一些其他外设:

  1. DDR内存;
  2. 时钟;
  3. 串口,用于调试以及使用STM32CubeProgrammer时需要串口下载系统;
  4. USB,用STM32CubeProgrammer通过USB烧写系统。

bl2还需要对镜像进行验证和鉴权,鉴权是通过调用内部 ROM代码的鉴权服务来完成。最后,bl2会加载bl32和bl33的固件到指定的内存区域,并跳转到bl32,bl32接着运行。

STM32MP1下的bl32

bl32提供运行时安全服务,在TF-A中默认使用sp_min。sp_min是一个最小的AArch32安全负载(Secure Payload),整合了PSCI库以及AArch32的EL3运行时软件。sp_min可以替代可信系统(TEE OS)或者可信执行环境(TEE),比如OP-TEE。当然了,STM32MP1同时支持sp_min以及OP-TEE,用户可以自行选择bl32使用哪个软件包。正点原子是选择用sp_min。

bl32充当安全监控(secure monitor),因此它向非安全系统(non-secure os,比如 linux)提供了一些安全服务。非安全的应用软件可以通过安全监控调用(secure monitor calls)来使用这些安全服务,这些代码支持标准的服务调用,比如PSCI。

另外,bl32也支持ST32MP1所特有的一些安全服务,可以访问特有的安全外设,比如 RCC、PWR、RTC或BSEC。

STM32MP1下的bl33

bl33就是传统的 uboot,并不属于TF-A本身。

简单总结一下,默认情况下TF-A有bl1、bl2、bl31、bl32和bl33这几个启动阶段。如果bl32使用sp_min的话那么 bl1、bl2、bl31和bl32都属于TF-A。但是对于STM23MP1而言,因为其使用的是AArch32,因此没有bl31部分。而bl1部分ST又没有用TF-A提供的,采用的是STM32MP1内部ROM代码,因此就只剩下了bl2和bl32。所以对于STM32MP1而言,TF-A就两个固件:bl2 和bl32(sp_min),TF-A 源码也采用了设备树(device tree)来设备信息,因此对于STM32MP1 而言TF-A 一共有三部分:设备树、bl2和bl32,这三部分在编译的时候会被合并成一个二进制文件。当然了,还要在最前面加上重要的头部信息,最终这4部分就组成了我们烧写到外部flash中的TF-A镜像,比如之前讲义中中烧写到EMMC 中的tf-a-stm32mp157datk-trusted.stm32,其文件结构如下图所示:
TF-A镜像组织结构
STM32MP1的TF-A启动流程如下图所示:
STM32MP1 TF-A启动流程
上图中TF-A 启动分了5步,这5步的含义如下:

  1. 复位以后内部ROM加载TF-A整个镜像,然后运行bl2镜像;
  2. bl2将bl32镜像加载到指定内存区域;
  3. bl2将bl33镜像加载到指定内存区域;
  4. bl2执行完毕以后就会跳转到bl32镜像;
  5. bl32镜像执行完以后跳转到bl33镜像,也就是uboot。

最后,uboot引导非安全系统,也就是linux内核。

STM32MP1 TF-A镜像存储映射

上一小节讲了,最终烧写到STM32MP1里面的TF-A镜像有4部分,除去头部信息,还有设备树、bl2和bl32。这3部分虽然被打包在了一起,但是实际是三部分,比如bl2是一个镜像,bl32是另外一个镜像,其执行顺序都是不一样的!当加载到内存上以后这3部分的存储映射如下图所示:
TF-A存储分配
上图中TF-A各部分存储映射不是固定的,编译TF-A的时候配置不同,其存储地址也不同,重点是存储映射形式

TF-A的基础知识就讲解到这里,对TF-A有了初步的认识,了解了STM32MP1中的TF-A组织形式,方便我们后续学习。

总结

这一章节学习的就是TF-A源码的一些基本框架,熟悉了ARMv7和ARMv8的工作模式,我们的STM32MP157开发板是32位的,也就是工作在AArch32模式,TF-A主要工作在EL3级别下

对于ST公司的这块STM32MP157开发板,TF-A主要就是bl2、bl32以及设备树(bl1已经被写在ROM代码中了来启动bl2镜像,bl33是uboot不属于TF-A)。

这一章后面还有一个TF-A的启动流程详解,但是文档中还没有更新,后续有更新了我会补充上来的。

所以,后面就可以来到正式的传统linux开发的uboot部分!

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

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

相关文章

剖析华为云Astro Platform技术价值与使用体验

面对企业IT专业人员紧缺的挑战,华为云Astro低代码平台提供创新解决方案。让非编程专业人士能快速构建、部署应用,缓解开发资源压力,并高度整合技术架构,强化项目沟通协作。使每个参与其中的角色都找到自己的位置,携手全…

初级篇—第三章多表查询

文章目录 为什么需要多表查询一个案例引发的多表连接初代查询笛卡尔积(或交叉连接)的理解 多表查询分类等值连接 vs 非等值连接自连接 vs 非自连接内连接VS外连接 SQL99语法实现多表查询内连接的实现外连接的实现左外连接右外连接满外连接 UNION的使用7种…

leetCode 455.分发饼干 贪心算法

455. 分发饼干 - 力扣(LeetCode) 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。 对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸&…

全网最全Python系列教程(非常详细)---集合讲解(学Python入门必收藏)

🧡🧡🧡这篇是关于Python中集合的讲解,涉及到以下内容,欢迎点赞和收藏,你点赞和收藏是我更新的动力🧡🧡🧡 1、集合是什么? 2、集合应该怎么去定义&#xff1f…

从零手搓一个【消息队列】实现数据的硬盘管理和内存管理(线程安全)

文章目录 一、硬盘管理1, 创建 DiskDataCenter 类2, init() 初始化3, 封装交换机4, 封装队列5, 关于绑定6, 关于消息 二、内存管理1, 数据结构的设计2, 创建 MemoryDataCenter 类3, 关于交换机4, 关于队列5, 关于绑定6, 关于消息7, 恢复数据 三、小结 创建 Spring Boot 项目, S…

Xcode 超简单实用小秘技让撸码进入新境界

概览 Xcode 是开发  应用不可或缺的王牌主力军,虽然 Xcode 中一些常用使用姿势想必大家都已驾轻就熟,但其中仍有一些隐藏宝藏小技巧不为人知。 充分挖掘它们可以极大加速和方便秃头码农们日常的撸码行为。 一般的,它们分为两类&#xff…

机器人制作开源方案 | 四轴飞行器

1. 概述 基于探索者搭建的模块化四轴飞行器研究平台,采用独特的设计方式,可实现在室内完成对四轴飞行器、无人机等运动控制的原理研究,以及学习飞行控制的原理知识。 2. 组装 请按照下图进行机架的组装。 整体图 请解压文末资料中的 /软件/Mi…

前端JavaScript入门到精通,javascript核心进阶ES6语法、API、js高级等基础知识和实战 —— Web APIs(一)

思维导图 学习目标 变量声明 一、Web API 基本认知 作用和分类 什么是DOM DOM树 DOM对象 二、获取DOM对象 三、操作元素内容 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compa…

基于Java的实验室预约管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

【数据结构】选择排序 堆排序(二)

目录 一&#xff0c;选择排序 1&#xff0c;基本思想 2&#xff0c; 基本思路 3&#xff0c;思路实现 二&#xff0c;堆排序 1&#xff0c;直接选择排序的特性总结&#xff1a; 2&#xff0c;思路实现 3&#xff0c;源代码 最后祝大家国庆快乐&#xff01; 一&#xf…

设计模式8、装饰者模式 Decorator

解释说明&#xff1a;动态地给一个对象增加一些额外的职责。就扩展功能而言&#xff0c;装饰模式提供了一种比使用子类更加灵活的替代方案 抽象构件&#xff08;Component&#xff09;&#xff1a;定义一个抽象接口以规范准备收附加责任的对象 具体构件&#xff08;ConcreteCom…

10OpenMP

OpenMP概述 通过线程实现并行化&#xff0c;与Pthread一样&#xff0c;是基于线程的共享内存库 与Pthread的不同 简而言之&#xff1a; Pthread更加底层&#xff0c;需要用户自己定义每一个线程的行为&#xff0c;OpenMP虽然更加简单&#xff0c;但是底层的线程交互实现很难 …

【单片机】13-实时时钟DS1302

1.RTC的简介 1.什么是实时时钟&#xff08;RTC&#xff09; &#xff08;rtc for real time clock) &#xff08;1&#xff09;时间点和时间段的概念区分 &#xff08;2&#xff09;单片机为什么需要时间点【一定的时间点干什么事情】 &#xff08;3&#xff09;RTC如何存在于…

【Git】Git 原理和使用

Git 一、Git 本地仓库1. 本地仓库的创建2. 配置 Git3. 工作区、暂存区、版本库4. 添加文件5. 查看 .git 文件6. 修改文件7. 版本回退8. 撤销修改9. 删除文件 二、分支管理1. 理解分支2. 创建分支3. 切换分支4. 合并分支5. 删除分支6. 合并冲突7. 分支管理策略8. bug 分支9. 强制…

基于Java的厨艺交流平台设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

JavaScript Web APIs第三天笔记

Web APIs - 第3天 进一步学习 事件进阶&#xff0c;实现更多交互的网页特效&#xff0c;结合事件流的特征优化事件执行的效率 掌握阻止事件冒泡的方法理解事件委托的实现原理 事件流 事件流是对事件执行过程的描述&#xff0c;了解事件的执行过程有助于加深对事件的理解&…

数据结构刷题(三十三):完全背包最小值情况。322. 零钱兑换、279. 完全平方数

题目一&#xff1a; 322. 零钱兑换https://leetcode.cn/problems/coin-change/ 思路&#xff1a;完全背包问题&#xff0c;求解最小组合数。dp[j]&#xff1a;凑足总额为j所需钱币的最少个数为dp[j]。同时需要确保凑足总金额为0所需钱币的个数一定是0&#xff0c;那么dp[0] 0…

如果只是用php纯做api的话,给移动端做数据接口,是否需要用php框架?

API接口对接是现代软件开发中不可或缺的一部分&#xff0c;它允许不同的应用程序之间进行数据交换和服务调用。在PHP中&#xff0c;可以使用多种方式实现API接口的对接&#xff0c;包括基于HTTP协议的传统方法以及现代的API客户端库客户端库客户端库等。 一、实现API接口的对接…

Web开发-登录页面设计流程

目录 确定页面设计样式创建js文件jquery.min.jsbootstrap.min.js 创建css文件bootstrap.min.cssmaterialdesignicons.min.cssstyle.min.css 创建ftl文件header.ftlfooter.ftllogin.ftlcss部分html部分 确定页面设计样式 可以自己用“画图”等软件进行设计&#xff0c;也可以打…

步进电机只响不转

我出现问题的原因是相位线接错。 我使用的滑台上示17H的步进电机&#xff0c;之前用的是57的步进电机。 57步进电机的相位线是A黑、A-绿、B红、B-蓝。 17步进电机的相位线是A红、A-绿、B黑、B-蓝。 这两天被一个问题困扰了好久&#xff0c;在调试步进电机开发板的时候电机发生…