1. 文件详解

嵌入式软件开发:bin, hex, axf, elf不可或缺
其中bin和hex是最常用的下载文件,而axf和elf包含更多调试和信息。通过对比,我们可以看到不同文件格式之间的差异。

文件格式程序数据附加信息概述
binbin文件是简单的二进制程序文件,即机器代码(程序)文件。在下载bin程序文件时,我们必须明确其起始地址,以确保程序的正确执
hex格式数据由Intel制定的十六进制格式,是一种ASCII文本文件,用于将程序代码下载到处理器中。这种文件格式常用于将程序下载到微控制器、EPROMs及其他可编程逻辑设备中。
axf调试信息xf文件专为ARM编译器设计,它不仅包含程序数据(如bin数据)和地址信息(如hex数据),还融入了丰富的调试数据。这些调试信息对于分析和调试程序至关重要,它们将源代码和注释与反汇编代码相结合,使得开发者能够随时切换到源代码进行调试。
elfelf文件是可执行文件和共享库的格式,结构复杂且信息丰富。ELF(Executable and Linkable Format)格式广泛应用于二进制文件、可执行文件、目标代码、共享库以及核心转储格式文件。它由UNIX系统实验室(USL)为应用程序二进制接口(ABI)的开发和发布而创立,并已成为Linux操作系统中主要的可执行文件格式。

ELF文件结构包括四个部分:ELF头、程序头表、节以及节头表。ELF头负责描述整个ELF文件的组织方式和属性;程序头表详细列出文件中的各个segments,为系统创建进程映像提供必要信息;Section从链接的角度来描述ELF文件;而Section Header Table则汇总了文件中各个segments的详细属性信息。|

  1. 你会发现,针对同一段代码进行编译时,所产生的bin文件体积最小,而axf文件则包含最多的信息,因此体积最大。此外,在嵌入式Linux环境中,还有一种名为ELF(Executable and Linkable Format,可执行与可链接格式)的文件,它同样属于程序文件范畴,且包含的信息更为丰富和复杂。
    在这里插入图片描述
  2. 嵌入式软件系统使用C语言,是基于32位内核的较大微控制器(MCU) 所选择的语言。MCU、C和(C++) 编译器是必须且容易得到的。
    采用8位控制器的设计者经常求助于汇编语言的手动编码。但由于8位MCU的有限资源下汇编语言的程序不会降低成本,编译C语言有许多优势
    使用C语言的好处:
    a. 可移植性的原理,嵌入式应用程序成本敏感。为减少每个单元的成本改变部件(或体系结构)是最大的激励
    b. 传统编程降低成本,强调C语言能概括微控制器特征。与特定硬件实施相关的系列能放置在不同的库函数和头文件中,使用C库函数和头文件保证应用程序源代码能针对不同源控制器目标重新编译
    c. 花费在算法上的时间多而实现上的时间更少。
  3. 产品需求
    1)有关硬件的总体决策是问题规范的组成部分,尤其在硬件将受到充分控制的嵌入式工程
    2)结果:
    a. 程序将测量和显示当前的温度;
    b. 程序将按12或24小时制计数实际时间,并在数字显示屏上显示小时和分钟
    c. 程序将接受时间设置并设置时钟
    d. 为三个日常使用周期接受和存储时间设置
    f. 控制制冷和制热间的切换,某些HVAC(heating ventilating and air condition,供热通风及空调) 专家可能会想到偶尔在同一时间里既操作制冷又操作制热的需要。此处需求更接近于传统的自动调温系统
    g. 比较温度与时间周期的设置,按需要打开或关闭外部的制冷或制热单元
    h. 程序将抑制在一个短周期时间里改变两次外部单元的状态,为了使HVAC设备运行良好
    i. 程序将在任何时间接受人工干预,进而立即关闭制热或制冷
    3)请求的意向清单
项目概念
内置调试接口现场可编程(field-programmability)另一种方法也能满足需要,当设备需在现场安装调试时、选用Flash-ROM芯片将比选用EEPROM或ROM设备更明智
ROM代码保护嵌入式CPU往往提供ROM代码保护,防偶然的检查。一个配置位禁止通过程序设计接口读ROM
合理的外设接口电子线路因贪图方便影响组织I/O结构时会迅速降低软件性能,首选处理器有独立改变端口比特的位操作指令吗?多路复用将需要数据方向切换吗
硬件设计的中心决策是处理器选择,这是协商的决定。权衡的因素包含: 预期应用所需的资源、芯片供应的价格和可用性以及可得到的开发工具等
4)初始硬件规范
管理因素评估
--------
操作环境局部环境、中等功耗/中等噪声电子线路
接口一个针对HVAC的多位端口: 也许只有3个针脚是必须的; 针对显示屏的多位I/O接口; 针对键盘的多位I/O接口; 针对温度传感的A/D设备; 实时时钟源
内存大小(参见下文)
特殊功能时钟/计数器或实时时钟; NVRAM的使用依赖于处理器是否和怎样睡眠; 监视定时器或许有用
开发工具C编译器,模拟器或仿真器,开发版
家庭自动调温器工程能分为每个设备的模块(写伪代码)以及设备的每一种函数: HVAC接口,键盘,LCD,温度传感器
其中体系结构的难以决断之处: 采用中断或轮询/查询(将随芯片选择而决定-不分处理器变种不包括中断)。其重要方面是中断和主程序代码间的通信协议,由于中断和主程序间相互独立(指中断能在任何主程序指令期间出现)-所以竞争条件是出现的结果。

之后建立一个变量清单并对RAM和ROM的需要做出评估,内存资源的限制对某些程序员是一个打击

裸机开发指在没有操作系统(os)或其他高级软件支持的情况下,直接在逻辑硬件上进行软件开发的过程。开发者需要直接面对硬件层面的操作和控制,亲自管理CPU、内存和I/O资源,不依赖于任何操作系统提供的抽象层或服务
而os大致分为硬件(Hardware)、操作系统(operating system)、应用程序(application programs)

main(){
	while(1) {
		func1();
		func2();
		func3();
	}
}

操作系统从整体上分为两大类:通用操作系统和实时操作系统。
其中,通用操作系统包括Linux, Windows, MACOS等主流的操作系统,为了保障系统的流畅运行就不能保障每个程序实时响应,在易用性和实时性有所取舍,单片机有限的片上资源不足以支撑通用操作系统的运行;实时操作系统(RTOS-Real Time Operating System)中实时(Real Time)指的是任务(Task)或实现一个功能的线程(Thread)必须在给定的时间(Deadline)内完成。尽管堆砌更多的处理器核心数目、更高的处理器频率,更大的内存,更快的总线速度一定能达到实时性。然而强大的计算能力不能保证实时性,在检测到汽车发生碰撞后安全气囊在30ms中完全打开。若车载ECU有强大的计算能力,但执行复杂计算任务或任务调度对汽车一场状态的检测和安全气囊的响应超过了规定的时间系统实时性将无法保障导致系统失效和人员伤亡。其中,RTOS(实时操作系统)指一类系统,如FreeRTOS,uC/OS,RTX, RT-Thread等都是RTOS类操作系统。而RTOS(官网)是所有实时操作中最欢迎的一款,是市场领先的面向控制器和小型微处理器的实时操作系统(RTOS),与芯片公司合作开发,每170秒下载一次;包括一个内核和不断丰富的IoT库适用于所有行业,构建突出可靠性和易用性。

开源和免费:FreeRTOS是开源的RTOS,采用MIT许可证发布,能免费使用、修改和分发
轻量级设计:FreeRTOS适用于资源受限的嵌入式系统,不占用过多内存和处理器资源
广泛应用:包括工业自动化、医疗设备、消费电子产品、汽车电子等
多平台:FreeRTOS注重可移植性,能移植到不同的硬件平台,支持多种处理架构
丰富功能:多任务调度,任务通信,同步等,适用于复杂的嵌入式应用场景

2. 芯片测试

半导体制造中验证系统级芯片(SoC)功能与制造质量的关键环节,应用于深亚微米工艺下的集成电路生产领域。核心目标在设计初期规划测试方案,通过自动化工具检测和隔离缺陷,确保芯片良率及性能达标。
测试采用自动化测试模式(ATPG)、边界扫描等技术,结合测试复用与模式压缩缩短周期,针对高集成度芯片引入瞬变模型、路径延迟等多故障模型解决时序问题。分为晶圆测试(CP)与封装最终测试(FT),筛选缺陷芯片与验证成品质量。测试方法涵盖电性能参数检测 、功能验证、环境可靠性模拟等维度,利用ATE设备、探针等硬件配合实现高效质量控制。行业趋势聚焦于测试自动化优化(如AI生成向量)、多芯片并行测试及接口设计创新,平衡测试经典与成本

  1. 正确配置逻辑测试的ATPG工具,测试时间段;新型告诉故障模式及多种内存或小型阵列测试。对产线而言,诊断方法不仅找到故障且将故障节点与工作正常节点分离,有空能要采用复用技术节约测试时间。高级程度IC测试领域,ATPG和IDDQ的可测试性设计技术具备强大的故障分离机制
  2. 提前规划的实际参数:扫描的管教数目和管脚端的内存数量。在SoC上嵌入边界扫描,但不限于电路板或多芯片模块上的互连测试。
  3. 芯片测试平台:包括PT-930,VerigyV50,chroma3360,V777,nd1\2\3等

2.1 CP测试

2.2 DRAM测试原理

DRAM(Dynamic Random-Access Memory, DRAM)是一种易失性半导体存储器,广泛应用于个人电脑、服务器、移动设备等领域。DRAM通过存储单元(微型电容和晶体管)中的电荷状态表示数据位0或1;而电容器的电荷会随时间泄露,DRAM需要周期性的刷新操作维持数据完整性,“动态”特性的引脚分类及其功能;随DDR4演进到DDR5,引脚功能、信号协议和测试要求变得日益复杂。
1)电源和接地引脚(VDD, VDDQ, VSS, VPP)
功能:为DRAM芯片内部电路的正常工作提供必需的电源电压和稳定的接地参考。
VDD:核心电源电压,供给DRAM内部逻辑电路和存储阵列。
VDDQ:I/O接口电源电压,专门供给DQ(数据输入/输出)和DQS(数据选通)等I/O缓冲器。在某些设计中,VDD和VDDQ可能使用相同的电压值,但在一些高速设计中可能会有所不同,以优化信号完整性和功耗。
VSS:接地引脚,作为所有电压的参考点。
VPP:字线升压电源,用于在行激活期间驱动字线到比VDD更高的电压,以确保存储单元晶体管完全导通。例如,DDR4的VPP通常为2.5V 3,而DDR5则降至1.8V 4。
详细说明:稳定的电源供应对DRAM的可靠运行至关重要。电压波动或噪声可能导致数据错误或操作失败。DDR4规范中定义VDD = VDDQ = 1.2V ± 5%,外部VPP = 2.5V (+10%, -5%) 3。DDR5则将工作电压进一步降低至VDD/VDDQ = 1.1V,VPP = 1.8V,以降低功耗 4。

  1. memory测试
    Memory测试(内存测试)是指对计算机内存模块进行的一系列检测和验证过程,旨在确保内存的正确性和稳定性。在计算机系统中,内存作为CPU与外部存储器之间的桥梁,其性能直接影响整个系统的运行效率。
    主要测试类型
    1)基础功能测试
    读写测试:验证内存的基本读写功能是否正常
    地址线测试:检查内存地址线是否正确连接
    数据线测试:检测数据线是否存在短路或断路
    存储单元测试:验证每个存储单元能否正确保存数据
    2)性能测试
    带宽测试:测量内存的数据传输率(如GB/s)
    延迟测试:检测内存访问延迟(通常以纳秒为单位)
    吞吐量测试:评估单位时间内能够处理的数据量
    3)稳定性测试
    长时间压力测试:通过持续读写操作验证内存的稳定性
    温度测试:监测内存模块在不同负载下的温度变化
    错误检测与纠正:测试ECC(错误检查和纠正)功能的有效性
    常用测试工具
  2. 专业测试软件
    Memtest86:经典的启动盘内存测试工具,支持全面检测
    Windows内存诊断工具:Windows内置的简易内存检测程序
    Prime95:通过计算压力测试内存稳定性
    AIDA64:提供详细的内存性能分析和压力测试
    开发人员工具
    Valgrind:Linux平台下的内存调试和性能分析工具
    Dr. Memory:Windows平台的内存错误检测工具
    Purify:商业级的内存调试和性能分析工具
    【测试步骤示例】
    准备阶段
    1)关闭所有运行程序
    2)确保系统稳定性
    3)记录测试前的内存状态
    执行测试
    运行基础功能测试(约30-60分钟)
    进行性能基准测试(多次采样取平均值)
    执行72小时稳定性压力测试
    结果分析
    检查错误报告
    分析性能指标
    生成测试报告
    应用场景
    硬件开发:内存模块厂商的质量控制
    系统集成:服务器和工作站的稳定性验证
    故障排查:诊断系统蓝屏或崩溃问题
    超频验证:验证超频后内存的稳定性
    二手交易:检测二手内存的健康状况

2.3 验证的流程

  1. 通用流程目前如下:
    1)提取测试点,明确验什么:分析FS/浮点平台,提取芯片的规格和测试点;分析AS/顶点平台,提取测试点;分析DS,提取测试点并识别asic与算法的不一致点
    2)制定验证方案明确怎么验:刷新测试点列表,明确测试点的覆盖方式-功能覆盖率、代码覆盖率、直接用力;验证环境的搭建策略/自动化工具;重点难点,提前识别重难点并制定相应的对策;刷新用例列表,明确测试用例方法及步骤
    3)用例执行,随机测试,发现bug:执行测试用例发现大部分bug,带随机的大量测试撞出bug
    4)完备性分析,确保无漏验:FA/AS完备性确认,保证FS/AS中所有点纳入测试点,确保亦被覆盖,包括应用场景;接口完备性取人,保证所有的接口时序都被覆盖包括正常时序和异常时序;覆盖率确认,分析所有的代码、功能覆盖率保证全部覆盖
    5)代码分析,掌握电路的实现逻辑,保证所有的电路corner都已经覆盖
    验证人员希望的情况下把所有激励空间都覆盖到,保证无漏测、零漏测;但实际情况是芯片规模越来越大,激励空间近乎无线,但EDA仿真的速度奇慢,无法实现全覆盖,即使是FPGA\EMU等仿真加速器对此也无能为力。因此要合理划分等价类相当重要。
  2. CDV验证:CDV是覆盖率驱动验证的意思,写一大堆覆盖率(断言、功能、代码等)而覆盖率都达到的话代表验证完备
    目标的前提是分析清楚测试点覆盖空间,测试空间是否晚辈
  3. formal验证:传统的仿真都是动态验证;由于仿真效率低下无法遍历所有空间,formal静态验证手段可以遍历所有空间。但在目前formal只能适用于百万门级的模块验证
  4. 服用的模块可以将相关的验证做成BB化,后续芯片复用该模块时,也可以复用验证BB

2.4 5大常用协议超全整理(UART, RS232, RS485, IIC, CPI)

  1. UART总线通信协议:串口通信的数据包由发送设备通过自身的TXD接口传输到接收设备的RXD接口
    在这里插入图片描述
    1)在串口通信的协议层中,规定了数据包的内容,由起始位、主题数据、校验位和停止位组成,通讯双方的数据包要约定一致才能正常收发数据
    2)波特率:串口异步通讯,由于没有时钟信号。所以两个通讯设备间要约定好波特率,每个码元的长度便于对信号进行解码,上图用虚线分开的每一格代表一个码元。常见的波特率为4800,9600,115200等
    3)通信的起始和停止信号:串口的数据包从起始信号开始到停止信号结束,起始由一个逻辑0的数据位标识,而数据包的停止信号可由0.5、1、1.5或2个逻辑1的数据位表示,只要双方约定一致即可
    4)有效数据:在数据包的起始位后是要传输的主题数据内容,也称为有效数据,长度常被约定为5\6\7\8位长
    5)数据校验:可选的数据校验位由于数据通信相对更容易受到外部干扰导致传输数据出现偏差,在传输过程中加上校验位解决该问题。校验方法有奇校验(odd)、偶校验(even)、0校验(space)、1校验(mark)及无校验(noparity)
    其中,奇校验要求有效数据和校验位中“1”的个数为奇数,比如8位长的有效数据为01101001此时总共有4个“1”。未达到奇校验效果,校验位为“1”。最后传输的数据将为8位的有效数据加上1位的校验位总共9位。而偶校验与奇校验要求刚好相反,要求帧数据和校验中“1”的个数为偶数,如数据帧;11001010,此时数据帧“1”的个数为4所以偶校验位为“0”。之后,0校验是不管有效数据中的内容是什么,校验位总为0;1校验是校验位总为“1”;另外,在无校验位的情况下,数据包中不包含校验位。
  2. IIC总线通信协议:I2C是Inter-Intergrated Circuit的简称读作I-Squared-C由飞利浦公司于1980年代提出为了让主板、嵌入式系统或手机以连接低俗周边外部设备发展的。 主要用途SOC(system on chip:集成了CPU、内存和外设控制器-USB/EMAC)等功能的芯片(即微型计算机)和周边外设间的通信。
    1)其中,外部PHY芯片则是物理层收发器(Physical Layer Transcreiver)负责模拟和数字信号的转换。核心功能是将SoC输出的数字信号转为网线/光纤上的模拟信号(或反向转换),实现阻抗匹配、信号放大、噪声过滤等物理层功能。常见类型包括以太网PHY(如Marvell 88E 1111)、USB PHY、SATA PHY等。
    配合使用包括
    在这里插入图片描述
    2)物理限制:包括信号完整性-高频模拟信号(如千兆以太网)易受干扰,独立PHY可优化布局。电压兼容性则代表PHY芯片常需要更高电压(如2.5V/3.3V),而SoC I/O仅支持1.8V,需电平转换。代码场景emac_set_io_power_domain(EMAC_IO_3V3); // 配置SoC的EMAC I/O电压确保SoC的EMAC接口与外部PHY芯片的电压匹配(PHY供电为3.3V,避免SoC输出1.8V损坏PHY)
    在这里插入图片描述
    3)协作流程包括SoC侧:EMAC控制器生成TCP/IP数据包(数字信号)通过RGMII/MII接口输出到PHY;PHY侧则将数字信号转为差分模拟信号(如曼彻斯特编码),通过变压器耦合到网线。
    4)不匹配的后果包含电压不匹配(电压不匹配:SoC输出1.8V而PHY需3.3V表示信号幅度不足,通信失败或损坏PHY)、协议不兼容表示RGMII和SGMII混用代表数据无法解析、时钟不同步则表示PHY没有正确提供TX_CLK数据丢失。
    其他常见组合则有
SoC外设需配合的PHY需配合的PHY
USB OTGUSB PHY支持USB 2.0/3.0物理连接
SATAUSB PHY支持USB 2.0/3.0物理连接
USB OTGUSB PHY支持USB 2.0/3.0物理连接

a. SoC:负责数字处理和协议控制,集成度很高但模拟性能有限
b. PHY:专精物理层信号转换,与SoC互补
c. 配合关键:电压、接口协议、时序必须严格匹配,代码确保这一点
5)参数配置
时钟频率:100KHz(标准模式),400KJ

2.5 嵌入式常用通讯协议

  1. 基础通信协议
    1)UART/USART:串口通信
    a. TX(Transmit):发送数据引脚(输出)
    作用:串行数据输出,发送数据到外部设备
    电平:3.3V/5V TTL电平,空闲时为高电平
    信号:起始位(低电平)+数据位+校验位+停止位(高电平)
    连接:连接到目标设备的RX引脚
    b. RX(Receive):接受数据引脚/输入
    作用:串行数据输入,从外部设备接收数据
    电平: 3.3V/5V TTL电平,支持高阻抗输入
    信号:检测起始位下降沿,按波特率采样数据
    连接:连接到目标设备的TX引脚
    c. CTS(Clear To Send):清除发送引脚(硬件流控输入)
    作用:接收对方的发送许可信号
    逻辑:低电平有效,表示可以发送数据
    流控:防止接收缓冲区溢出
    连接:连接到目标设备的RTS引脚
    d. RTS (Request To Send): 请求发送引脚 (硬件流控输出)
    作用:向对方发送接收准备信号
    逻辑:低电平接收,高电平表示暂停发送
    流控:控制对方的数据发送时机
    连接:连接到目标设备的CTS引脚
    // 串口通信的 原因
    工作原理包括如下内容:
    异步串行通信,无时钟信号;数据帧格式(起始位+数据位+校验位+停止);双方必须协商相同的波特率和数据格式
空闲态    起始位   数据位(8位)    校验位   停止位   空闲态
 ___      __       ___  ___       ___     ___     ___
    |    |  |     |   ||   |     |   |   |   |   |
    |____|  |_____|   ||   |_____|   |___|   |___|
     1     0    D0 D1 D2...D7    P    1     1
          
波特率时间: T = 1/波特率
例如115200bps: T = 1/115200 ≈ 8.68μs

波特率与误差
常用波特率的实际误差计算:
理论值与实际值的误差应<2%
STM32使用系统时钟分频产生波特率
计算公式:实际波特率 = Fclk / (16 × 分频值)
2)引脚配置及详细作用
【相关的工作原理】
同步串行通信,全双工传输;主从模式,主机控制时钟和片选;四种工作模式,由CPOL(时钟极性Clock Polarity)和CPHA(时钟相位Clock Phase)决定;数据在时钟边沿采样和移位
Serial Peripheral Interface:SPI通信
引脚配置及详细作用SCLK/SCK(Serial Clock):串行时钟线(主机输出)
a. 作用:提供同步时钟信号,控制数据传输时序
b. 方向:只有主机(Master)驱动,从机(Slave)被动接收
c. 频率:1MHz-50MHz,决定数据传输速度
d. 模式:支持4种
e. 空闲态:根据CPOL设置为高电平或低电平
此处CPOL=0: 时钟空闲时为低电平(0V)
CPOL=1:空闲时为高电平(Vcc)
而CPHA=0:时钟的第一个边沿采样数据
CPHA=1:第二个边沿采样数据

参数配置

  • 时钟频率1MHz-50Mhz
  • 模式包含Mode0-3(时钟极性和相位)
  • 数据位:8/16位
  • 片选:低电平有效
  • 应用:Flash存储器、SD卡、显示屏、ADC/DAC

f. 驱动能力:要足够驱动力驱动多个从机设备
g. 负载:时钟负载影响最大传输速度
SPI四种工作模式详解
在这里插入图片描述
MOSI(Master Out Slave in):主机输入从机输出数据线
a. 作用:主机向从机发送数据的单向信号线
b. 方向:主机输出->从机输入
c. 时序:在时钟边沿(上升/下降)输出数据
d. 电平:3.3V/5V TTL电平
e. 别名:有些芯片称为SDO(Serial Data Out)或DI(Data in)
f. 驱动能力:要足够驱动力驱动多个从机设备
g. 数据格式:MSB或LSB优先,通常MSB在前
MISO (Master In Slave Out):主机输入从机输出数据线
a. 作用:从机向主机发送数据的单向信号线
b. 方向:从机输出->主机输入
c. 时序:在时钟边沿(上升/下降)输出数据
d. 状态:未选中的从机应保持高阻态
e. 别名:有些芯片称为SDI(Serial Data In)或DO(Data Out)
f. 冲突避免: 多从机共享时需片选控制高阻态
g. 上拉: 有时需要弱上拉电阻防止浮空
CS/SS(Chip Select/Slave Select):片选信号线(主机输出)
a. 作用:选择要通信的特定从机设备
b. 逻辑: 通常低电平有效(也有高电平有效的)
c. 功能: CS拉低时从机开始响应,拉高时从机进入高阻态
d. 多设备: 主机可通过多个CS引脚控制多个从机
e. 时序: 必须在时钟开始前拉低,传输结束后拉高
f. 保持时间: CS变化到时钟有效需要建立时间
g. 独立控制: 每个从机需要独立的CS信号
3)参数配置
波特率:9600,115200,460800,921600
数据位:8位
停止位:1位
校验位:无校验/奇校验/偶校验
流控:无/硬件流控/软件流控
[引脚连接示意图]

设备A           设备B
------          ------
TX   ---------> RX     (数据发送)
RX   <--------- TX     (数据接收)
GND  ---------> GND    (共地)
RTS  ---------> CTS    (流控-可选)
CTS  <--------- RTS    (流控-可选)

注意事项包含:
1)双方波特率必须严格匹配
2)数据格式(数据位,停止位,校验位)要一致
3)确保共地连接,避免地电位差
4)长距离传输需要RS232/RS485转换
5)硬件流控适用于高速或大数据量传输
参考代码

// UART初始化配置
UART_HandleTypeDef huart1;
void MX_USART1_UART_Init(void)
{
    huart1.Instance = USART1;
    huart1.Init.BaudRate = 115200;              // 波特率
    huart1.Init.WordLength = UART_WORDLENGTH_8B; // 8位数据
    huart1.Init.StopBits = UART_STOPBITS_1;     // 1位停止位
    huart1.Init.Parity = UART_PARITY_NONE;      // 无校验
    huart1.Init.Mode = UART_MODE_TX_RX;         // 收发模式
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; // 无流控
    HAL_UART_Init(&huart1);
}
// 发送字符串
void UART_SendString(char *str)
{
    HAL_UART_Transmit(&huart1, (uint8_t*)str, strlen(str), 1000);
}
// 接收数据
HAL_StatusTypeDef UART_ReceiveData(uint8_t *pData, uint16_t Size)
{
    return HAL_UART_Receive(&huart1, pData, Size, 1000);
}
  1. 工业总线协议
协议类型拓扑结构最大节点数传输距离典型速率核心优势
CAN总线型110<=1km1Mbps强抗干扰、错误检测与恢复机制
R2-485总线型32<=1.2km10Mbps差分信号传输、支持多点通信

1)CAN总线(Controller Area Network)
a. 引脚配置及详细作用
CAN_H(CAN High):CAN高电平信号线
* 作用:差分信号的正极信号线
* 电平:显性位时约3.5V,隐性位时约2.5V
* (显性Dominant:电平通常为较低电压如CAN总线中显性位为3.5V, 实际CAN标准是显性位为低电平;逻辑“0”表示主动驱动的强信号;优先级则是显性位会覆盖隐性位,当总线上同时出现显性和隐性时,显性“胜出”)
* 特性:与CAN_L形成差分对,提供抗干扰能力
* 连接: 串联连接所有CAN节点
* 线材: 使用双绞线,特征阻抗120Ω
CAN_L(CAN Low):CAN低电平信号线
* 作用:差分信号的负极信号线
* 电平:显性位时约1.5V,隐性位s
b.
2)

  1. 扩展接口
    1)USB协议:支持热插拔,最高速率达480Mbps(USB2.0);应用场景包括摄像头模组、外置存储设备链接
    2)Ethernet协议:TCP/IP协议栈,速率10/100/1000Mbps;应用场景包括工业物联网设备、远程监控系统
    3)Modbus协议:工业领域标准应用层协议,支持RTU/TCP传输模式;应用场景包括PLC控制系统、智能仪表通信等
  2. 1

3. FreeRTOS基础知识

FreeRTOS是一个完全免费和开源的嵌入式实时操作系统,已被作为一个中间件集成到STM32 MCU固件库中。在STM32Cube开发方式中,用户可以很方便地使用FreeRTOS。拥有以下标准:
a. 抢占式(pre-emptive)或合作式(co-operative)任务调度方式
b. 非常灵活的优先级管理
c. 灵活、快速而轻量化的任务通知(task notification)机制
d. 队列(queue [kjuː])功能
e. 二值信号量binary semaphore
f. 计数信号量counting semaphore
g. 互斥量mutex
h. 递归互斥量recursive mutex
i. 软件定时器software timer
j. 事件组(event group)
k. 时间节拍钩子函数(tick hook function)
l. 空闲时钩子函数(idle hook function)
m.栈溢出检查(stack overflow checking)
n. 踪迹记录(trace recording)
u. 任务运行时间统计收集(task run-time statics gathering)
v. 完整的中断嵌套模型(对某些架构有用)
w.用于低功耗的无节拍(tick-less)特性
除了技术上的优势,FreeRTOS的开源免费许可协议也为用户扫除了使用FreeRTOS的障碍。FreeRTOS不涉及其他任何知识产权(Intellectual Property,IP)问题,因此用户可以完全免费地使用FreeRTOS,即使用于商业性项目,也无须公开自己的源代码,无须支付任何费用。当然,如果用户想获得额外的技术支持,那么可以付费升级为商业版本。FreeRTOS还有两个衍生的商业版本。
而OpenRTOS是一个基于FreeRTOS内核的商业许可版本;SafeRTOS是另一个基于FreeRTOS内核的商业许可

3.1 多任务处理

内核是操作系统的核心组件,看似允许用户同时访问实际上多个用户能同时执行多个程序。每个执行程序都受系统控制的任务/线程,若操作系统能以该方式执行多任务则是多任务操作系统。而多任务操作系统能简化设计
1)操作系统的多任务处理和任务间通信功能允许将复杂的应用程序分割成更小、管理的任务
2)分割可以执行软件测试,分解团队内工作及复用程序
3)复杂的时序和排序细节能从应用程序代码中移除,由操作系统复杂
即单核处理器一次只能执行一项任务,多任务则可以快速切换并制造并发执行的假象。下图展示了与时间相关的三项任务的执行模式,颜色写在左手边,时间从左向右移动;彩色线条显示任何特定时间正执行的任务,上方显示了所感知的并发执行模式下方展示了实际的多任务执行模式。

3.2 相关概念和术语

  1. 实时性:RTOS应用于对实时性有要求的嵌入式系统,实时性指任务的完成时间确定。日常使用的windows、IOS、Android等是非实时性操作系统,非实时则对任务完成时间没有严格要求
    FreeRTOS是一个实时操作系统,特别适用于基于MCU的实时嵌入式应用。这种应用通常包括硬实时(hard real-time)和软实时(soft real-time)。
    软实时,指任务运行要求有一个截止时间,但即便超过这个截止时间,也不会使系统变得毫无用处。例如,对敲按键的反应不够及时,可能使系统显得响应慢一点,但系统不至于无法使用。
    硬实时,指任务运行要求有一个截止时间,如果超过了这个截止时间,可能导致整个系统的功能失效。例如,轿车的安全气囊控制系统,如果在出现撞击时响应缓慢,就可能导致严重的后果。
  2. 任务:操作系统的主要功能实现多任务管理,FreeRTOS支持多任务的实时操作系统。RTOS将任务称为线程Thread/任务task。嵌入式操作系统中的任务与高级语言(如C++、Python)中的线程很相似,任务或线程间通信与同步都使用信号量、互斥量技术。
    一般MCU是单核,处理器在任何时刻内执行一个任务的代码,FreeRTOS的多任务功能通过内核中的任务调度器实现,支持基于任务优先级的抢占式任务调度算法,可以满足硬实时的要求
  3. 移植:少部分与硬件密切相关的源代码,【要针对不同架构的MCU进行一些改写】
  4. 学习单片机通常是裸机编程开始,一般嵌入式系统若功能要求不复杂或程序涉及精良裸机也能实现功能;若功能要求复杂需要分解为多个任务实现则必须使用RTOS。对实时性要求比较高也要使用RTOS。此外使用RTOS且将功能分解为多个任务能将程序功能模块化、程序结构更简单

3.3 主程序

本示例只有一个任务,任务函数是StartDefaultTask()。CubeMX生成了这个函数的基本框架,可以看到这个函数的主体就是一个死循环。本示例中,希望在此任务里让LED1闪烁,在函数StartDefaultTask()里添加用户代码,完成的函数代码如下:

/* USER CODE BEGIN Header_StartDefaultTask */
/**
  * @brief  Function implementing the defaultTask thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
  /* USER CODE BEGIN StartDefaultTask */
  /* Infinite loop */
  for(;;)
  {
	  HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_6);	//LED1输出翻转
	  osDelay(500);												//延时500个tick
  }
  /* USER CODE END StartDefaultTask */
}

4. 嵌入式裸机开发

4.1 主频和时钟配置

  1. I.MX6U时钟系统详解(系统主频为528MHz,有些型号可以跑696MHz;默认情况下内部boot rom会将I.MX6U的主频设为396MHz)
    1)系统时钟来源
    在这里插入图片描述
    系统时钟来源于两部分:32.768KHz晶振是I.MX6U的RTC时钟源,24MHz晶振是I.MX6U内核和外设的时钟源
    2)7路PLL时钟源(PLL锁相环/锁相环路)
    在这里插入图片描述
    其中ARM_PLL(PLL1)此路是提供ARM内核使用的,PLL通过编程的方式最高可倍频到1.3GHz;而528_PLL(PLL2)这条路也叫做System_PLL,该路PLL是固定的22倍频,不可编程修改。
    此路PLL时钟 = 24Mhz22 = 528MHz,分出了4路PFD和528_PLL。通常用于I.MX6U内部系统总线的时钟源,如内处理逻辑单元、DDR接口、NAND/NOR接口等。
    USB1_PLL(PLL3),此路是USB1PHY。PLL也有思路PFD和USB1_PLL,固定20倍频,因此USB1_PLL=24MHz
    20 = 480MHz
    USB2_PLL(PLL7),此路是USB2PHY,频率同PLL3
    ENET_PLL(PLL6)此路PLL固定为20+5/6倍频,因此ENET_PLL=24MHz*(20+5/6)=500MHz,用于生成网络所需的时钟,在PLL的基础上生成25/50/100/125MHz的网络时钟。
    VIDEO_PLL(PLL5),PLL的输出范围在650MHz~1300MHz,该路PLL显示相关外设如LCD,此路PLL的倍频可以调整,PLL在最终输出的时候能进行分频,可选1/2/4/8/16分频
    AUDIO_PLL(PLL4),PLL输出是650MHz~1300MHz,此路PLL用于音频; PLL用于音频相关的外设,此路PLL的倍频可调整,PLL在最终输出的时候进行分频,可选1/2/4分频
    时钟系统就是晶振脉冲进入系统通过不同路的倍频和分频提供给外设。
    3)时钟树简介
    在这里插入图片描述
    外设选择PLL和PFD借助时钟树:参考《IMX6ULL参考手册》其中的CLOCK_SWITCHER包含7路PLL和8路PFD,SYSTEM CLOCKS包含芯片外设,CLOCK ROOT GENERATOR给左边的CLOCK_SWITCHER和右边的SYSTEM CLOCKS进行牵线搭桥。
    使用寄存器控制选择器和分频器
    4)内核时钟设置:寄存器CCM_CACRR,分频
    寄存器CCM_ANALOG_PLL_ARMn,倍频
    PLL1时钟频率的修改
    在这里插入图片描述
    ① 内核始终来源于PLL1,假如此时PLL1为996MHz。通过寄存器CCM_ANALOG_PLL_ARMn进行设置
    ② 通过寄存器CCM_CACRR的ARM_PODF位对PLL1进行分频,选择1/2/4/8分频
    ③ 这里没有!!2分频!!!
    CCM_CACRR寄存器分频:
    CCM_CACRR只有ARM_PODF位,可以设置为0~7,分别对应1-8分频。
    在这里插入图片描述
    寄存器 CCM_ANALOG_PLL_ARMn,倍频为以下内容
    在这里插入图片描述
    PLL1 时钟频率的修改
    这里PLL1 时钟频率的修改还需要暂时让其他时钟代替一下。
    到这里,修改 I.MX6U 主频的步骤就很清晰了,修改步骤如下:
    ① 设置寄存器 CCSR 的 STEP_SEL 位,设置 step_clk 的时钟源为 24M 的晶振。
    ② 设置寄存器 CCSR 的 PLL1_SW_CLK_SEL 位,设置 pll1_sw_clk 的时钟源为step_clk=24MHz,通过这一步我们就将 I.MX6U 的主频先设置为 24MHz,直接来自于外部的24M 晶振。
    ③ 设置寄存器 CCM_ANALOG_PLL_ARMn,将pll1_main_clk(PLL1)设置为 1056MHz。
    ④ 设置寄存器 CCSR 的 PLL1_SW_CLK_SEL 位,重新将 pll1_sw_clk 的时钟源切换回pll1_main_clk,切换回来以后的 pll1_sw_clk 就等于 1056MHz。
    ⑤ 最后设置寄存器 CCM_CACRR 的 ARM_PODF 为 2 分频, I.MX6U 的内核主频就为1056/2=528MHz。
    还是设置寄存器,查询手册知道地址和需要配置的值即可
    5)PFD时钟设置:PLL2、 PLL3 和 PLL7 固定为 528MHz、 480MHz 和 480MHz, PLL4~PLL6 都是针对特殊外设的,用到的时候再设置。
    PLL2的4路PFD和PLL3的4路PFD
    在这里插入图片描述
    而PLL2的4路PFD频率使用到的寄存器则是
    在这里插入图片描述
    PFD0~PFD3共4组,每组 8 个 bit。
    以PFD0为例:
    PFD0_FRAC: PLL2_PFD0 的分频数, PLL2_PFD0 的计算公式为 528*18/PFD0_FRAC,此为 可 设 置 的 范 围 为 12~35 。
    PFD0_STABLE: 此位为只读位,可以通过读取此位判断 PLL2_PFD0 是否稳定。
    PFD0_CLKGATE: PLL2_PFD0 输出使能位,为 1 的时候关闭 PLL2_PFD0 的输出,为 0 的时候使能输出。
    PLL3 的 4 路 PFD则用到的寄存器则是CCM_ANALOG_PFD_480n,配置同PLL2的4个PFD
    在这里插入图片描述
    6)AHB、IPG和PERCLK根时钟设置
    7 路 PLL 和 8 路 PFD 设置完成以后最后还需要设置AHB_CLK_ROOT 、 IPG_CLK_ROOT和 PERCLK_CLK_ROOT的时钟。
    I.MX6U 外设根时钟可设置范围如图:
    在这里插入图片描述
    参考的时钟图如下所示
    在这里插入图片描述
    在这里插入图片描述

4.2 代码相关程序编写

1 #include "bsp_clk.h"
2 
3/***************************************************************
4 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
5 文件名 : bsp_clk.c
6 作者 : 左忠凯
7 版本 : V1.0
8 描述 : 系统时钟驱动。
9 其他 : 无
10 论坛 : www.openedv.com
11 日志 : 初版 V1.0 2019/1/3 左忠凯创建
12
13 V2.0 2019/1/3 左忠凯修改
14 添加了函数 imx6u_clkinit(),完成 I.MX6U 的系统时钟初始化
15 ***************************************************************/
16
17 /*
18 * @description : 使能 I.MX6U 所有外设时钟
19 * @param : 无
20 * @return : 无
21 */
22 void clk_enable(void)
23 {
24 CCM->CCGR0 = 0XFFFFFFFF;
25 CCM->CCGR1 = 0XFFFFFFFF;
26 CCM->CCGR2 = 0XFFFFFFFF;
27 CCM->CCGR3 = 0XFFFFFFFF;
28 CCM->CCGR4 = 0XFFFFFFFF;
29 CCM->CCGR5 = 0XFFFFFFFF;
30 CCM->CCGR6 = 0XFFFFFFFF;
31 }
32
33 /*
34 * @description : 初始化系统时钟 528Mhz,并且设置 PLL2 和 PLL3 各个
35 PFD 时钟,所有的时钟频率均按照 I.MX6U 官方手册推荐的值.
36 * @param : 无
37 * @return : 无
38 */
39 void imx6u_clkinit(void)
40 {
41 unsigned int reg = 0;
42 /* 1、设置 ARM 内核时钟为 528MHz */
43 /* 1.1、判断当使用哪个时钟源启动的,正常情况下是由 pll1_sw_clk 驱动的,而
44 * pll1_sw_clk 有两个来源: pll1_main_clk 和 step_clk, 如果要
45 * 让 I.MX6ULL 跑到 528M, 那必须选择 pll1_main_clk 作为 pll1 的时钟
46 * 源。 如果我们要修改 pll1_main_clk 时钟的话就必须先将 pll1_sw_clk 从
47 * pll1_main_clk 切换到 step_clk,当修改完以后再将 pll1_sw_clk 切换
48 * 回 pll1_main_cl, step_clk 等于 24MHz。
49 */
50
51 if((((CCM->CCSR) >> 2) & 0x1 ) == 0) /* pll1_main_clk */
52 {
53 CCM->CCSR &= ~(1 << 8); /* 配置 step_clk 时钟源为 24MHz OSC */
54 CCM->CCSR |= (1 << 2); /* 配置 pll1_sw_clk 时钟源为 step_clk */
55 }
56
57 /* 1.2、设置 pll1_main_clk 为 1056MHz,也就是 528*2=1056MHZ,
58 * 因为 pll1_sw_clk 进 ARM 内核的时候会被二分频!
59 * 配置 CCM_ANLOG->PLL_ARM 寄存器
60 * bit13: 1 使能时钟输出
61 * bit[6:0]: 88, 由公式: Fout = Fin * div_select / 2.0,
62 * 1056=24*div_select/2.0, 得出: div_select=88。
63 */
64 CCM_ANALOG->PLL_ARM = (1 << 13) | ((88 << 0) & 0X7F);
65 CCM->CCSR &= ~(1 << 2);/* 将 pll_sw_clk 时钟切换回 pll1_main_clk */
66 CCM->CACRR = 1; /* ARM 内核时钟为 pll1_sw_clk/2=1056/2=528Mhz */
67
68 /* 2、设置 PLL2(SYS PLL)各个 PFD */
69 reg = CCM_ANALOG->PFD_528;
70 reg &= ~(0X3F3F3F3F); /* 清除原来的设置 */
71 reg |= 32<<24; /* PLL2_PFD3=528*18/32=297Mhz */
72 reg |= 24<<16; /* PLL2_PFD2=528*18/24=396Mhz */
73 reg |= 16<<8; /* PLL2_PFD1=528*18/16=594Mhz */
74 reg |= 27<<0; /* PLL2_PFD0=528*18/27=352Mhz */
75 CCM_ANALOG->PFD_528=reg; /* 设置 PLL2_PFD0~3 */
76
77 /* 3、设置 PLL3(USB1)各个 PFD */
78 reg = 0; /* 清零 */
79 reg = CCM_ANALOG->PFD_480;
80 reg &= ~(0X3F3F3F3F); /* 清除原来的设置 */
81 reg |= 19<<24; /* PLL3_PFD3=480*18/19=454.74Mhz */
82 reg |= 17<<16; /* PLL3_PFD2=480*18/17=508.24Mhz */
83 reg |= 16<<8; /* PLL3_PFD1=480*18/16=540Mhz */
84 reg |= 12<<0; /* PLL3_PFD0=480*18/12=720Mhz */
85 CCM_ANALOG->PFD_480=reg; /* 设置 PLL3_PFD0~3 */
86
87 /* 4、设置 AHB 时钟 最小 6Mhz, 最大 132Mhz */
88 CCM->CBCMR &= ~(3 << 18); /* 清除设置*/
89 CCM->CBCMR |= (1 << 18); /* pre_periph_clk=PLL2_PFD2=396MHz */
90 CCM->CBCDR &= ~(1 << 25); /* periph_clk=pre_periph_clk=396MHz */
91 while(CCM->CDHIPR & (1 << 5));/* 等待握手完成 */
92
93 /* 修改 AHB_PODF 位的时候需要先禁止 AHB_CLK_ROOT 的输出,但是
94 * 我没有找到关闭 AHB_CLK_ROOT 输出的的寄存器,所以就没法设置。
95 * 下面设置 AHB_PODF 的代码仅供学习参考不能直接拿来使用!!
96 * 内部 boot rom 将 AHB_PODF 设置为了 3 分频,即使我们不设置 AHB_PODF,
97 * AHB_ROOT_CLK 也依旧等于 396/3=132Mhz。
98 */
99 #if 0
100 /* 要先关闭 AHB_ROOT_CLK 输出,否则时钟设置会出错 */
101 CCM->CBCDR &= ~(7 << 10);/* CBCDR 的 AHB_PODF 清零 */
102 CCM->CBCDR |= 2 << 10; /* AHB_PODF 3 分频, AHB_CLK_ROOT=132MHz */
103 while(CCM->CDHIPR & (1 << 1));/* 等待握手完成 */
104 #endif
105
106 /* 5、设置 IPG_CLK_ROOT 最小 3Mhz,最大 66Mhz */
107 CCM->CBCDR &= ~(3 << 8); /* CBCDR 的 IPG_PODF 清零 */
108 CCM->CBCDR |= 1 << 8; /* IPG_PODF 2 分频, IPG_CLK_ROOT=66MHz */
109
110 /* 6、设置 PERCLK_CLK_ROOT 时钟 */
111 CCM->CSCMR1 &= ~(1 << 6); /* PERCLK_CLK_ROOT 时钟源为 IPG */
112 CCM->CSCMR1 &= ~(7 << 0); /* PERCLK_PODF 位清零,即 1 分频 */
113 }

4.3 STM32开发

STM32F103VET6是由意法半导体(ST)推出的基于Cortex-M3内核的32位微控制器,硬件采用LQFP48封装,属于ST公司微控制器中的STM32系列。
在这里插入图片描述
其中的STM32各GPIO口介绍,分为以下7个部分:
1)保护二极管及上、下拉电阻:保护二极管可以防止引脚外部过高或过低的电压输入
2)P-MOS和N-MOS管:控制推挽输出和开漏输出
3)输出数据寄存器:由输出数据寄存器GPIOX_ODR
4)复用功能输出:STM32的其他片上外设对GPIO引脚进行控制
5)输入数据寄存器:存储输入数据寄存器GPIOX_IDR
6)复用功能输入:GPIO引脚的信号传输到STM32其他片上外设
7)模拟输入输出:用于ADC采集电压的输入通道
实验步骤
1)时钟使能:流水灯操作的引脚位于GPIO端口。下图可知AHB总线包含RCC时钟控制,GPIO属于APB2,需要使用的端口复位和时间控制受RCC控制
在这里插入图片描述
2)寄存器地址设置
STEP 1 设置亮灯的高电平输出
在这里插入图片描述
在这里插入图片描述
STEP 2 设置灭灯的低电平输出
在这里插入图片描述
STEP 3 反转输出电平
在这里插入图片描述
3)延时函数:程序执行指令的速度很快。若将灯的亮灭及颜色的切换让人可以看见需要设置延时函数。而延时函数的延时时间跟STM32的晶振频率有关,程序执行指令需要一定时可以涉及一个简单的延时函数。
死循环方式的延时函数没办法精确控制延时时间长短,只是大概的延时时间。因此基本上通过不断修改输入参数大小然后下载到开发板上看实验效果

static void Delay(uint32_t time) {
	  uint32_t i,j;
	  for(i=0;i<time;++i) {
	    for(j=0;j<10000;++j) {       
	      //空循环        
	    }
	  }
	}	
  1. 实操代码
    4.1 电灯:程序分为3个文件:bsp_led.c, bsp_led.h, main.c
    bsp_led.c
		/* 包含头文件 ----------------------------------------------------------------*/
		#include "bsp/led/bsp_led.h"
		/**
	  * 函数功能: 板载LED灯IO引脚初始化.
	  * 输入参数: 无
	  * 返 回 值: 无
	  * 说    明:使用宏定义方法代替具体引脚号,方便程序移植,只要简单修改bsp_led.h
	  *           文件相关宏定义就可以方便修改引脚。
	  */
	void LED_GPIO_Init(void)
	{
	  /* 定义IO硬件初始化结构体变量 */
	  GPIO_InitTypeDef GPIO_InitStructure;
		
	  /* 使能(开启)LED引脚对应IO端口时钟 */  
	  RCC_APB2PeriphClockCmd(LED1_RCC_CLOCKGPIO|LED2_RCC_CLOCKGPIO |LED3_RCC_CLOCKGPIO , ENABLE);
	  
	  /* 设定LED对应引脚IO最大操作速度 :GPIO_Speed_50MHz */
	  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  
	  /* 设定LED对应引脚IO为输出模式 */
	  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  
		
	  /* 设定LED1对应引脚IO编号 */
	  GPIO_InitStructure.GPIO_Pin = LED1_GPIO_PIN; 
	  /* 初始化LED1对应引脚IO */
	  GPIO_Init(LED1_GPIO, &GPIO_InitStructure);
	 
	  /* 设定LED2对应引脚IO编号 */
	  GPIO_InitStructure.GPIO_Pin = LED2_GPIO_PIN;  
	  /* 初始化LED2对应引脚IO */
	  GPIO_Init(LED2_GPIO, &GPIO_InitStructure);
	
	  /* 设定LED3对应引脚IO编号 */
	  GPIO_InitStructure.GPIO_Pin = LED3_GPIO_PIN;  
	  /* 初始化LED3对应引脚IO */
	  GPIO_Init(LED3_GPIO, &GPIO_InitStructure);
	}

bsp_led.h

	#ifndef __BSP_LED_H__
	#define __BSP_LED_H__
	 
	/* 包含头文件 ----------------------------------------------------------------*/
	#include <stm32f10x.h>
	 
	/* 类型定义 ------------------------------------------------------------------*/
	typedef enum
	{
	  LED_OFF = 0,
	  LED_ON,
	}LEDState_TypeDef;
	#define IS_LED_STATE(STATE)           (((STATE) == LED_OFF) || ((STATE) == LED_ON))
	 
	/* 宏定义 --------------------------------------------------------------------*/
	#define LED1                          (uint8_t)0x01
	#define LED2                          (uint8_t)0x02
	#define LED3                          (uint8_t)0x04
	#define IS_LED_TYPEDEF(LED)           (((LED) == LED1) || ((LED) == LED2) || ((LED) == LED3))
	 
	/*
	 * 以下宏定义内容跟开发板硬件息息相关,需要查看开发板电路原理图才能正确编写。
	 */
	 
	#define LED1_RCC_CLOCKGPIO            RCC_APB2Periph_GPIOA
	#define LED1_GPIO_PIN                 GPIO_Pin_8
	#define LED1_GPIO                     GPIOA
	 
	 
	#define LED2_RCC_CLOCKGPIO            RCC_APB2Periph_GPIOA
	#define LED2_GPIO_PIN                 GPIO_Pin_11
	#define LED2_GPIO                     GPIOA
	 
	 
	#define LED3_RCC_CLOCKGPIO            RCC_APB2Periph_GPIOA
	#define LED3_GPIO_PIN                 GPIO_Pin_12
	#define LED3_GPIO                     GPIOA
	 
	#if 1         /* 直接操作寄存器方法 */
	 
	#define LED1_ON                      {LED1_GPIO->BRR=LED1_GPIO_PIN;}   //输出高电平
	#define LED1_OFF                       {LED1_GPIO->BSRR=LED1_GPIO_PIN;}    //输出低电平
	#define LED1_TOGGLE                   {LED1_GPIO->ODR ^=LED1_GPIO_PIN;}  //输出反转
	 
	#define LED2_ON                      {LED2_GPIO->BRR=LED2_GPIO_PIN;}   //输出高电平
	#define LED2_OFF                       {LED2_GPIO->BSRR=LED2_GPIO_PIN;}    //输出低电平
	#define LED2_TOGGLE                   {LED2_GPIO->ODR ^=LED2_GPIO_PIN;}  //输出反转
	 
	#define LED3_ON                      {LED3_GPIO->BRR=LED3_GPIO_PIN;}   //输出高电平
	#define LED3_OFF                       {LED3_GPIO->BSRR=LED3_GPIO_PIN;}    //输出低电平
	#define LED3_TOGGLE                   {LED3_GPIO->ODR ^=LED3_GPIO_PIN;}  //输出反转
	 
	#endif
	/* 扩展变量 ------------------------------------------------------------------*/
	/* 函数声明 ------------------------------------------------------------------*/
	void LED_GPIO_Init(void);
	#endif  // __BSP_LED_H__

main.c

		/* 包含头文件 ----------------------------------------------------------------*/
	#include "stm32f10x.h"
	#include "bsp/led/bsp_led.h"
	 
	static void Delay(uint32_t time);
	 
	/* 函数体 --------------------------------------------------------------------*/
	 
	int main(void)
	{
	  uint8_t count=0;
	 
	  /* 初始化板载LED灯,该函数定义在bsp_led.c文件中,在bsp_led.h声明所以需要包括该头文件 */
	  LED_GPIO_Init();  
	  
	  /* 无限循环 */
	  while (1)
	  {
	 
	    switch(count)
	    {
	      /* 使用宏定义方法控制LED灯 */
	      case 0:           
			LED1_OFF;
	        LED2_OFF;
	        LED3_OFF;
	        break;
	      case 1:           
		    LED1_ON;
	        break;
	      case 2:
			LED1_OFF;
	        LED2_ON;
	        break;
	      case 3:                       
	        LED2_OFF;
	        LED3_ON;
	        break;
	      case 4:
	        LED3_OFF;
	        LED2_ON;
	        break;
	      case 5:
	        LED2_OFF;
	        LED1_ON; 
	        break;
	      case 6:
	        LED1_ON;
	        LED2_ON;
	        LED3_ON;
	        break;
	    }    
	    /* 延时一段时间 */
	    Delay(1000);
	    
	    /* 计数增加1 */
	    count++;
	    /* 重新开始计数 */
	    if(count==7)count=0;
	  }
	}
	 
	 
	static void Delay(uint32_t time)
	{
	  uint32_t i,j;
	 
	  for(i=0;i<time;++i)
	  {
	    for(j=0;j<10000;++j)
	    {       
	      //空循环        
	    }
	  }
	}

4.1 按键:GPIO操作说明-与按键的初始化与LED灯初始化不同,LED是推挽输出、而按键则是输入。而输入分为上拉输入、下拉两种;若按键是共阴极,则按键按下时,IO口输入为低电平,需要在IO口接上拉电阻,则使用上拉输入模式;若按键为共阳极,则按键按下时,IO口输入为高电平,需要在IO口接下拉电阻则使用下拉输入模式。
实验步骤
1)按键消抖:机械开关,按下的时候会产生电平的抖动。延时消抖的方法能通过延时一小段时间消除抖动,除了这种方法还有硬件消抖
在这里插入图片描述
相关代码如下:

		if(GPIO_ReadInputDataBit(KEYx_GPIO, KEYx_GPIO_PIN)==KEYx_DOWN_LEVEL)
	{
	  /* 延时一小段时间,消除抖动 */
	  Delay(10);
	  /* 延时时间后再来判断按键状态,如果还是按下状态说明按键确实被按下 */
	  if(GPIO_ReadInputDataBit(KEYx_GPIO, KEYx_GPIO_PIN)==KEYx_DOWN_LEVEL)
	  {
	    /* 等待按键弹开才退出按键扫描函数 */
	    while(GPIO_ReadInputDataBit(KEYx_GPIO, KEYx_GPIO_PIN)==KEYx_DOWN_LEVEL){ }
	      /* 按键扫描完毕,返回按键被按下状态 */
	        return KEY_DOWN;
	   }
	}
	  /* 按键没被按下,返回没被按下状态 */
	  return KEY_UP;

2)长短按:检测按下时间的长短采取延时计数,通过引入时间计数的KeyTimeCount在按键扫描的时候延时多少时间,增大KeyTimeCount最后自定义判断时间长短。
值得注意的是,要是想检测按下的时间更为精确,需要调整延时函数,且针对不同单片机的定时器有不同的频率,需要根据实际情况自行调整。

	uint16_t KeyTimeCount = 0;
 
	/* 等待按键弹开才退出按键扫描函数 */
	while(GPIO_ReadInputDataBit(KEYx_GPIO, KEYx_GPIO_PIN)==KEYx_DOWN_LEVEL)
	{
	    /* 时间计数 */
		Delay(10);
	    KeyTimeCount = KeyTimeCount + 1;  
	}
	 
	   /* 按键扫描完毕,返回按键被按下状态 */
	if (KeyTimeCount <= 99)   // 小于等于1s
	{
		/* 计数清零 */
		KeyTimeCount = 0;  
		return KEY_DOWN;   // 短按状态
	}
	else if (KeyTimeCount >= 199)   // 大于等于2s
	{
		/* 计数清零 */
		KeyTimeCount = 0; 
		return KEY_DOWN_LONG;   // 长按状态
	}

3)联合点灯:按键部署完后,就是一个联合点灯的过程,将bsp_led.h引入到bsp_key.c,便可以通过按键实现点灯了!(其实是灭灯)

	void KEY_LED(void)
	{
		/* 读取按键状态 */
		int key1State = KEYx_Choice(1);
		
	   /* 短按按键1 */
		if (key1State == 1)
		{
			LED1_OFF;
		}
	}
  1. 1
  2. 1

参考文章
[1] 嵌入式裸机开发
[2] 电灯相关代码,直接copy了一个系列
[3]

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐