STM32启动代码原理分析

STM32启动代码原理分析(底层技术); 简述 ARM Cortex-M系列MCU的启动代码(使用汇编语言编程则不需要)主要做3件事情: 初始化并正确放置异常/中断矢量表; 分散加载; 初始化C语言运行环境(初始化堆栈以及C Library、浮点等)。 Cortex-M3内核规定,起始地址必须存放堆顶指针,而第二个地址则必须存放复位中断入口矢量地址,这样在Cortex-M3内核复位后,会自动从起始地址的下一个32位空间取出复位中断入口矢量,跳转执行复位中断服务程序。对比ARM7/ARM9内核,Cortex-M3内核则是固定了中断矢量表的位置而起始地址是可变化的。 源码分析 基于STM32F103C6T6的启动文件startup_stm32f103x6.s的简要说明如下: ;******************** (C) COPYRIGHT 2017 STMicroelectronics ******************** ;* File Name : startup_stm32f103x6.s ;* Author : MCD Application Team ;* Description : STM32F103x6 Devices vector table for MDK-ARM toolchain. ;* This module performs: ;* - Set the initial SP ;* - Set the initial PC == Reset_Handler ;* - Set the vector table entries with the exceptions ISR address ;* - Configure the clock system ;* - Branches to __main in the C library (which eventually ;* calls main()). ;* After Reset the Cortex-M3 processor is in Thread mode, ;* priority is Privileged, and the Stack is set to Main. ;****************************************************************************** ;* @attention ;* ;* Copyright (c) 2017 STMicroelectronics. ;* All rights reserved. ;* ;* This software component is licensed by ST under BSD 3-Clause license, ;* the "License"; You may not use this file except in compliance with the ;* License. You may obtain a copy of the License at: ;* opensource.org/licenses/BSD-3-Clause ;* ;****************************************************************************** ; Amount of memory (in bytes) allocated for Stack ; Tailor this value to your application needs ; Stack Configuration ; Stack Size (in Bytes) ; Stack_Size EQU 0x400 ;声明栈的大小为0x400字节 AREA STACK, NOINIT, READWRITE, ALIGN=3 Stack_Mem SPACE Stack_Size ;开辟一段大小为Stack_Size的内存空间作为栈 __initial_sp ;标号__initial_sp,表示栈空间顶地址。 ; Heap Configuration ; Heap Size (in Bytes) ; Heap_Size EQU 0x200 ;声明栈的大小为0x200字节 AREA HEAP, NOINIT, READWRITE, ALIGN=3 __heap_base ;标号__heap_base,表示堆空间起始地址。 Heap_Mem SPACE Heap_Size ;开辟一段大小为Heap_Size的内存空间作为堆。 __heap_limit ;标号__heap_limit,表示堆空间结束地址。 ;-------------------------------------------- ;第一部分: ;启动代码最重要的工作是把异常中断向量表放到正确的Flash地址上 ;把向量表定义为只读数据段,并导出向量表标号(Symbol),让链接器识别此标号并根据分散加载文件正确的放置向量表 ;__Vectors标号需要与分散加载文件合起来看,才会明白其真正的功能 ;-------------------------------------------- PRESERVE8 ;告诉编译器以8字节对齐。 THUMB ;告诉编译器使用THUMB指令集。 ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY ;声明权限为“READONLY”的名称为“RESET”的数据段 ;(假设STM32从FLASH启动,则此中断矢量表起始地址即为0x8000000) EXPORT __Vectors ;将标号__Vectors声明为全局标号,这样外部文档就可以使用这个标号。 EXPORT __Vectors_End EXPORT __Vectors_Size ;标号__Vectors,表示中断矢量表入口地址 ;创建中断矢量表 ;--------------------------------------- ;第二部分: ;__initial_sp ; 1、栈顶指针地址,此语法跟MDK编译器的底层相关,是ARMCC编译器才能识别的语法 ; GCC与IAR的底层编译器ICCARM编译器不能识别; ; 2、__initial_sp 是一个链接器Image Symbol; ; 3、此处__initial_sp相当于是顶地址,或者此处直接把顶地址写到此处也行(如:0x20004000); ; 4、__initial_sp具体是多少,在此种写法下,是由分散加载文件决定的,下文会有详细论述; ; ;Reset_Handler: ; 1、Reset_Handler函数地址,此处相当于把Reset_Handler函数地址赋值给PC,即调用Reset_Handler函数; ; 2、此处也可以是其他函数,只是把复位函数放于此处最符合实际应用场景。 ; 重要关键节点: ; 1、绝大多数cortex-M微控制器(M0、M3、M4都是这样)复位后先进入厂商BOOTROM,此时所有用户行为均无法介入处理器; ; 2、厂商BOOTROM(有些厂商会有其他名称来称呼此功能) 主要负责处理一些芯片最初级初始化、 ; 加密以及一些对MCU的差异化设置等工作: ; 3、BOOTROM顺利完成后,MCu控制权会交给用户,即启动代码; ; 4、启动代码(运行汇编语言则不需要此启动代码),最重要的工作在于设置MSP (主堆栈指针)以及PC(程序计数器)的值; ; 5、Cortex-M微控制器会默认把0x00000000地址里面的值设置为MSP的值,0x00000004地址里面的值设置为PC的值; ; 6、5中的默认地址可以通过修改Cortex-M中的VTOR寄存器来重新映射,比如改到0x20000000地址或其他; ; ;关于Exception(异常) 与Interrupt (中断) 的区别说明 ; 1、Exception(异常)与Interrupt(中断)是不同的,是两个不同的概念,很多人会混淆两者, ; 把他们都按照中断来看待,这是错误的; ; 2、Exception(异常)是向量表的前16个向量,其优先级为负数,高于所有中断,而且不可调整优先级也不可关闭, ; 可以打断正常程序与Interrupt(中断) 的运行: ; 3、从第16个向量以后才是Interrupt(中断),可以设置优先级,不用时可以关闭,但优先级永远低于Exception(异常); ; 4、从这里大家就可以理解Pendsy Handler与svsTick Handler为什么会被用于嵌入式操作系统, ; 因为其优先级高于所有中断,可以确保操作系统拥有高于普通用户程序执行的超级权限, ; SVC_Handler有时也会用于操作系统,原理相同; ; 5、用户应用程序应尽量避免使用Exception(异常); ;--------------------------------------- __Vectors DCD __initial_sp ; Top of Stack 栈顶地址 DCD Reset_Handler ; Reset Handler 复位向量 DCD NMI_Handler ; NMI Handler DCD HardFault_Handler ; Hard Fault Handler DCD MemManage_Handler ; MPU Fault Handler DCD BusFault_Handler ; Bus Fault Handler DCD UsageFault_Handler ; Usage Fault Handler DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD SVC_Handler ; SVCall Handler DCD DebugMon_Handler ; Debug Monitor Handler DCD 0 ; Reserved DCD PendSV_Handler ; PendSV Handler 操作系统会用到的异常向量 DCD SysTick_Handler ; SysTick Handler 操作系统会用到的心跳定时器异常向量(没有操作系统时可以用作普通定时器中断) ;--------------------------------------- ;第三部分: ; 1、这里开始是中断向量表; ; 2、各个向量的顺序是芯片设计的时候就定义好的,不能更改; ;--------------------------------------- ; External Interrupts DCD WWDG_IRQHandler ; Window Watchdog DCD PVD_IRQHandler ; PVD through EXTI Line detect DCD TAMPER_IRQHandler ; Tamper DCD RTC_IRQHandler ; RTC DCD FLASH_IRQHandler ; Flash DCD RCC_IRQHandler ; RCC DCD EXTI0_IRQHandler ; EXTI Line 0 DCD EXTI1_IRQHandler ; EXTI Line 1 DCD EXTI2_IRQHandler ; EXTI Line 2 DCD EXTI3_IRQHandler ; EXTI Line 3 DCD EXTI4_IRQHandler ; EXTI Line 4 DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1 DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2 DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3 DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4 DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5 DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6 DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7 DCD ADC1_2_IRQHandler ; ADC1_2 DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0 DCD CAN1_RX1_IRQHandler ; CAN1 RX1 DCD CAN1_SCE_IRQHandler ; CAN1 SCE DCD EXTI9_5_IRQHandler ; EXTI Line 9..5 DCD TIM1_BRK_IRQHandler ; TIM1 Break DCD TIM1_UP_IRQHandler ; TIM1 Update DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare DCD TIM2_IRQHandler ; TIM2 DCD TIM3_IRQHandler ; TIM3 DCD 0 ; Reserved DCD I2C1_EV_IRQHandler ; I2C1 Event DCD I2C1_ER_IRQHandler ; I2C1 Error DCD 0 ; Reserved DCD 0 ; Reserved DCD SPI1_IRQHandler ; SPI1 DCD 0 ; Reserved DCD USART1_IRQHandler ; USART1 DCD USART2_IRQHandler ; USART2 DCD 0 ; Reserved DCD EXTI15_10_IRQHandler ; EXTI Line 15..10 DCD RTC_Alarm_IRQHandler ; RTC Alarm through EXTI Line DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend __Vectors_End __Vectors_Size EQU __Vectors_End - __Vectors ;--------------------------------------- ;第四部分: ;这部分开始可以称作Reset Handler实体,芯片上电后,经过BOOTROM后进入的用户可控最开始处的地方; ; 1、如果想让Mcu正常使用c语言,务必在此处调用 main函数; ; 2、__main() 不是main() 两者有着本质性的区别; ; 3、__main()是c Library中的函数,Kei1开发环境中自带的c Library中的函数; ; 4、main()是被 __main()调用的,__main()工作完成后最后一步就是调用main(); ; 5、__main()被调用之前,可以根据需要插入一个或多个其他功能函数; ;--------------------------------------- AREA |.text|, CODE, READONLY ;定义只读的代码段 ; Reset handler routine ;复位中断服务程序,PROC…ENDP结构表示程序的开始和结束。 Reset_Handler PROC EXPORT Reset_Handler [WEAK] ;声明复位中断矢量Reset_Handler为全局属性 ;这样外部文档就可以调用此复位中断服务。 IMPORT __main ;声明__main标号。 IMPORT SystemInit ;声明SystemInit标号。 LDR R0, =SystemInit ;跳转SystemInit地址执行 BLX R0 LDR R0, =__main ;跳转__main地址执行 BX R0 ENDP ; Dummy Exception Handlers (infinite loops which can be modified) ;--------------------------------------------------------------- ;第五部分: ; 1、[weak]指定了一个这个函数为"弱函数”; ; 2、这些中断服务函教定义成弱函数的意义是,当中断出现时,需要有一个中断服务函数予以响应,但真实的 ; 用户程序往往只会使用一部分中断,甚至不使用中断,所以以下这些函数给出了异常/中断服务函数的 ; 默认实现,很简单,默认实现就是死循环汇编中的"B."语句,相当于while(1);因为不知道用户是否会 ; 用到多少中断,但这些服务函数又很重要,所以就把这些函数都"实现"并声明为弱函数; ; 3、弱函数的意思是如果用户定义了同样名称的另一个函数,那么默认实现的弱函数就会被覆盖,比如 ; HardFault_Handler异常在下面有一个默认的实现,但这种默认的实现不能满足我的需要的时候,我可以 ; 再重新定义一个HardEault Handler函数这个新定义的HardFault_Handler函数会覆盖原有的被声明 ; 为[WEAK]的弱函数; ; 4、有很多种适合使用弱函数的场合,默认的异常/中断服务函数只是一种应用场景; ; 5、在C语言中声明弱函数是在函数后加“__attribute((weak))”; ;---------------------------------------------------------------- NMI_Handler PROC EXPORT NMI_Handler [WEAK] B . ENDP HardFault_Handler\ PROC EXPORT HardFault_Handler [WEAK] B . ENDP MemManage_Handler\ PROC EXPORT MemManage_Handler [WEAK] B . ENDP BusFault_Handler\ PROC EXPORT BusFault_Handler [WEAK] B . ENDP UsageFault_Handler\ PROC EXPORT UsageFault_Handler [WEAK] B . ENDP SVC_Handler PROC EXPORT SVC_Handler [WEAK] B . ENDP DebugMon_Handler\ PROC EXPORT DebugMon_Handler [WEAK] B . ENDP PendSV_Handler PROC EXPORT PendSV_Handler [WEAK] B . ENDP SysTick_Handler PROC EXPORT SysTick_Handler [WEAK] B . ENDP Default_Handler PROC EXPORT WWDG_IRQHandler [WEAK] EXPORT PVD_IRQHandler [WEAK] EXPORT TAMPER_IRQHandler [WEAK] EXPORT RTC_IRQHandler [WEAK] EXPORT FLASH_IRQHandler [WEAK] EXPORT RCC_IRQHandler [WEAK] EXPORT EXTI0_IRQHandler [WEAK] EXPORT EXTI1_IRQHandler [WEAK] EXPORT EXTI2_IRQHandler [WEAK] EXPORT EXTI3_IRQHandler [WEAK] EXPORT EXTI4_IRQHandler [WEAK] EXPORT DMA1_Channel1_IRQHandler [WEAK] EXPORT DMA1_Channel2_IRQHandler [WEAK] EXPORT DMA1_Channel3_IRQHandler [WEAK] EXPORT DMA1_Channel4_IRQHandler [WEAK] EXPORT DMA1_Channel5_IRQHandler [WEAK] EXPORT DMA1_Channel6_IRQHandler [WEAK] EXPORT DMA1_Channel7_IRQHandler [WEAK] EXPORT ADC1_2_IRQHandler [WEAK] EXPORT USB_HP_CAN1_TX_IRQHandler [WEAK] EXPORT USB_LP_CAN1_RX0_IRQHandler [WEAK] EXPORT CAN1_RX1_IRQHandler [WEAK] EXPORT CAN1_SCE_IRQHandler [WEAK] EXPORT EXTI9_5_IRQHandler [WEAK] EXPORT TIM1_BRK_IRQHandler [WEAK] EXPORT TIM1_UP_IRQHandler [WEAK] EXPORT TIM1_TRG_COM_IRQHandler [WEAK] EXPORT TIM1_CC_IRQHandler [WEAK] EXPORT TIM2_IRQHandler [WEAK] EXPORT TIM3_IRQHandler [WEAK] EXPORT I2C1_EV_IRQHandler [WEAK] EXPORT I2C1_ER_IRQHandler [WEAK] EXPORT SPI1_IRQHandler [WEAK] EXPORT USART1_IRQHandler [WEAK] EXPORT USART2_IRQHandler [WEAK] EXPORT EXTI15_10_IRQHandler [WEAK] EXPORT RTC_Alarm_IRQHandler [WEAK] EXPORT USBWakeUp_IRQHandler [WEAK] ;--------------------------------------------------- ;第六部分 ; 这部分比较简单,看一下第五部分的代码就可以理解下面的部分; ;--------------------------------------------------- WWDG_IRQHandler PVD_IRQHandler TAMPER_IRQHandler RTC_IRQHandler FLASH_IRQHandler RCC_IRQHandler EXTI0_IRQHandler EXTI1_IRQHandler EXTI2_IRQHandler EXTI3_IRQHandler EXTI4_IRQHandler DMA1_Channel1_IRQHandler DMA1_Channel2_IRQHandler DMA1_Channel3_IRQHandler DMA1_Channel4_IRQHandler DMA1_Channel5_IRQHandler DMA1_Channel6_IRQHandler DMA1_Channel7_IRQHandler ADC1_2_IRQHandler USB_HP_CAN1_TX_IRQHandler USB_LP_CAN1_RX0_IRQHandler CAN1_RX1_IRQHandler CAN1_SCE_IRQHandler EXTI9_5_IRQHandler TIM1_BRK_IRQHandler TIM1_UP_IRQHandler TIM1_TRG_COM_IRQHandler TIM1_CC_IRQHandler TIM2_IRQHandler TIM3_IRQHandler I2C1_EV_IRQHandler I2C1_ER_IRQHandler SPI1_IRQHandler USART1_IRQHandler USART2_IRQHandler EXTI15_10_IRQHandler RTC_Alarm_IRQHandler USBWakeUp_IRQHandler B . ENDP ALIGN ;******************************************************************************* ; User Stack and Heap initialization ;******************************************************************************* ;IF…ELSE…ENDIF结构,判断是否使用DEF:__MICROLIB(此处为不使用)。 ;若使用DEF:__MICROLIB,则将__initial_sp,__heap_base,__heap_limit ;亦即栈顶地址,堆始末地址赋予全局属性,使外部程序可以使用。 IF :DEF:__MICROLIB EXPORT __initial_sp EXPORT __heap_base EXPORT __heap_limit ELSE IMPORT __use_two_region_memory ;定义全局标号__use_two_region_memory。 EXPORT __user_initial_stackheap ;声明全局标号__user_initial_stackheap, ;这样外程序也可调用此标号 __user_initial_stackheap ;标号__user_initial_stackheap,表示用户堆栈初始化程序入口 ;分别保存栈顶指针和栈大小,堆始地址和堆大小至R0,R1,R2,R3寄存器。 LDR R0, = Heap_Mem LDR R1, =(Stack_Mem + Stack_Size) LDR R2, = (Heap_Mem + Heap_Size) LDR R3, = Stack_Mem BX LR ALIGN ENDIF END;程序完毕 ;************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE***** 部分解释 以上便是STM32的启动代码的完整解析,接下来对几个小地方做解释: ...

February 25, 2023 · 6 min · Rancho

STM32的启动过程

STM32单片机是如何从上电运行到main()函数的; 用三张图片基本就能理解了: 参考: https://blog.csdn.net/weixin_39118482/article/details/79508747?spm=1001.2014.3001.5502 https://www.modb.pro/db/548699 https://www.cnblogs.com/yucloud/p/stm32_SystemInit_to_main.html 1、函数的调用过程: 2、启动流程1(使用标准库,不使用Microlib) 如下图: 3、启动流程2(使用Microlib) microlib 是缺省 C 库的备选库。它旨在与需要装入到极少量内存中的深层嵌入式应用程序配合使用。这些应用程序不在操作系统中运行。 microlib 进行了高度优化以使代码变得很小。它的功能比缺省 C 库少,并且根本不具备某些 ISOC 特性。某些库函数的运行速度也比较慢,例如, memcpy() 。 microlib与缺省C库之间的主要差异是: microlib不符合ISO C库标准。不支持某些ISO特性,并且其他特性具有的功能也较少; microlib不符合IEEE 754二进制浮点算法标准; microlib进行了高度优化以使代码变得很小; 无法对区域设置进行配置。缺省C区域设置是唯一可用的区域设置; 不能将main()声明为使用参数,并且不能返回内容; 不支持stdio,但未缓冲的stdin、stdout和stderr除外; microlib对C99函数提供有限的支持; microlib不支持操作系统函数; microlib不支持与位置无关的代码; microlib不提供互斥锁来防止非线程安全的代码; microlib不支持宽字符或多字节字符串; 与stdlib不同,microlib不支持可选择的单或双区内存模型。microlib只提供双区内存模型,即单独的堆栈和堆区。 启动流程如下图: 4、其他 关于stm32的启动文件: https://www.fan-pengfei.top/2023/02/25/STM32%E5%90%AF%E5%8A%A8%E4%BB%A3%E7%A0%81%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90/#more

February 25, 2023 · 1 min · Rancho

分散加载文档

分散加载(scatter)文档是一个文本文档,它可以用来描述ARM连接器生成映像文档时所需要的信息; 参考:https://blog.csdn.net/KXue0703/article/details/114018759 一、基础知识 为了充分理解分散加载文档的魅力,需要对工程编译后的内容有详细的了解。 Keil 编译后的内容如下所示: Code:为程序代码部分; RO-Data:表示程序定义的常量及 const 型数据; RW-Data:表示已经初始化的静态变量,变量有初值; ZI-Data:表示未初始化的静态变量,变量无初值; 当 Keil 工程编译完成后,查看其 map 文档,可得到结果如下程序清单: Code (inc. data) RO Data RW Data ZI Data Debug 4194 230 714 16 1640 72715 Grand Totals 4194 230 714 16 1640 72715 ELF Image Totals 4194 230 714 16 0 0 ROM Totals Total RO Size (Code + RO Data) 4908 (4.79kB) Total RW Size (RW Data + ZI Data) 1656 (1.62kB) Total ROM Size (Code + RO Data + RW Data) 4924 (4.81kB) 由map文档可以看出: ROM Size = Code+RO-Data+RW-Data = 4.81kB; RAM Size = RW-Data+ZI-Data = 1.62kB; ...

February 25, 2023 · 5 min · Rancho

git中的rebase用法小结

今天用到了git rebase命令来进行合并两次提交,顺带梳理下rebase命令的用法; 参考:https://www.jianshu.com/p/4a8f4af4e803 rebase在git中是一个非常有魅力的命令,使用得当会极大提高自己的工作效率;相反,如果乱用,会给团队中其他人带来麻烦。它的作用简要概括为:可以对某一段线性提交历史进行编辑、删除、复制、粘贴;因此,合理使用rebase命令可以使我们的提交历史干净、简洁! 前提:不要通过rebase对任何已经提交到公共仓库中的commit进行修改(你自己一个人玩的分支除外); 一、合并多个commit为一个完整commit 当我们在本地仓库中提交了多次,在我们把本地提交push到公共仓库中之前,为了让提交记录更简洁明了,我们希望把如下分支B、C、D三个提交记录合并为一个完整的提交,然后再push到公共仓库。 现在我们在测试分支上添加了四次提交,我们的目标是把最后三个提交合并为一个提交: 这里我们使用命令: git rebase -i [startpoint] [endpoint] 其中-i的意思是--interactive,即弹出交互式的界面让用户编辑完成合并操作,[startpoint] [endpoint]则指定了一个编辑区间,如果不指定[endpoint],则该区间的终点默认是当前分支HEAD所指向的commit(注:该区间指定的是一个前开后闭的区间)。 在查看到了log日志后,我们运行以下命令: git rebase -i 36224db 或: git rebase -i HEAD~3 然后我们会看到如下界面: 上面未被注释的部分列出的是我们本次rebase操作包含的所有提交,下面注释部分是git为我们提供的命令说明。每一个commit id 前面的pick表示指令类型,git 为我们提供了以下几个命令: pick:保留该commit(缩写:p) reword:保留该commit,但我需要修改该commit的注释(缩写:r) edit:保留该commit, 但我要停下来修改该提交(不仅仅修改注释)(缩写:e) squash:将该commit和前一个commit合并(缩写:s) fixup:将该commit和前一个commit合并,但我不要保留该提交的注释信息(缩写:f) exec:执行shell命令(缩写:x) drop:我要丢弃该commit(缩写:d) 根据我们的需求,我们将commit内容编辑如下: 然后是注释修改界面: 编辑完保存即可完成commit的合并了: 二、将某一段commit粘贴到另一个分支上 当我们项目中存在多个分支,有时候我们需要将某一个分支中的一段提交同时应用到其他分支中,就像下图: 我们希望将develop分支中的C~E部分复制到master分支中,这时我们就可以通过rebase命令来实现(如果只是复制某一两个提交到其他分支,建议使用更简单的命令:git cherry-pick)。 在实际模拟中,我们创建了master和develop两个分支: master分支: develop分支: 我们使用命令的形式为: git rebase [startpoint] [endpoint] --onto [branchName] 其中,[startpoint] [endpoint]仍然和上一个命令一样指定了一个编辑区间(前开后闭),--onto的意思是要将该指定的提交复制到哪个分支上。 所以,在找到C(90bc0045b)和E(5de0da9f2)的提交id后,我们运行以下命令: git rebase 90bc0045b^ 5de0da9f2 --onto master 注:因为[startpoint] [endpoint]指定的是一个前开后闭的区间,为了让这个区间包含C提交,我们将区间起始点向后退了一步。 ...

February 22, 2023 · 1 min · Rancho

单总线挂载多个DS18B20并读取温度

当需要同时读取多个点的温度数据时,DS18B20就是一个很好的选择,不仅精度高,而且还可以单总线挂载多个传感器以节省IO口的使用; 初始化函数 DS18B20的通信协议为单总线通信协议; 首先由主机发送一个复位脉冲约480-960us;然后总线被拉高;在15-60us之后传感器向单片机发送一个约60-240us的存在脉冲,然后总线被拉高。 /** * @brief 主机给从机发送复位脉冲 */ static void DS18B20_Reset(void) { DS18B20_Mode_OUT_PP(); // 主机输出 DS18B20_OUT_0; // 主机至少产生 480us 的低电平复位信号 delay_us(750); DS18B20_OUT_1; // 主机在产生复位信号后,需将总线拉高 // 从机接收到主机的复位信号后,会在 15 ~ 60 us 后给主机发一个存在脉冲 delay_us(15); } /** * @brief 检测从机给主机返回的存在脉冲 * @return 0:成功 1:失败 */ static uint8_t DS18B20_Presence(void) { uint8_t pulse_time = 0; DS18B20_Mode_IN_NP(); // 主机设为输入 // 等待存在脉冲的到来,存在脉冲为一个 60 ~ 240 us 的低电平信号 // 如果存在脉冲没有来则做超时处理,从机接收到主机的复位信号后,会在 15 ~ 60 us 后给主机发一个存在脉冲 while (DS18B20_IN && (pulse_time = 100) { return 1; } else { pulse_time = 0; } // 响应脉冲(低电平)到来,且存在的时间不能超过 240 us while (!(DS18B20_IN) && pulse_time = 240) { return 1; } else { return 0; } } /** * @brief DS18B20 初始化函数 * @reurn 0:成功 1:失败 */ uint8_t DS18B20_Init(void) { DS18B20_Mode_OUT_PP(); DS18B20_OUT_1; DS18B20_Reset(); return DS18B20_Presence(); } 配置写函数 当主机将数据线从高逻辑级别拉到低逻辑级别时,将启动写入时隙。有两种类型的写时槽:写1时槽和写0时槽。所有写入时隙的持续时间必须至少为60µs,且每个写入周期之间的恢复时间至少为1µs以上。在DQ线下降后,DS18B20在15µs到60µs的窗口中对DQ线进行采样。 ...

February 22, 2023 · 4 min · Rancho

如何用7个IO口驱动42个LED

尽量用较少的IO口来驱动较多的LED灯; 当IO口数量较少,而又需要驱动较多的LED灯时,就需要想办法通过修改硬件或者软件的方案来进行; 硬件方案的话,就是用串转并芯片例如74HC595或者其他的数码管驱动芯片来控制,当然会增加硬件成本,如果只是用在个人项目中,小小的成本增加并没有什么,但是如果是用在量产项目中,小小的成本增加就会吃掉一大部分盈利; 所以尽量还是使用软件方案,并不需要什么74HC595芯片; 下面介绍的这种方法叫做查理复用: 查理复用(Charlieplex)是一种在驱动大量LED时有效地节约IO口的方法,理论上可以用N个IO驱动N*(N-1)个LED,也有接入二极管用来做按键检测的,理论上可实现用N个IO驱动N*(N-1)个按键; 因而7个脚用满理论上可管理是42个LED,极大节省了IO口的使用; 但是对单片机的IO口有一个要求,也就是这种LED是由单片机I/O口直接驱动,I/O口要在工作在3态(高、低电平和高阻); 使用六个IO口驱动30个LED的原理图如下(第六行并未完整画出): 可以在程序里面每次间隔1ms,扫描一行,总共扫描6行后(6ms),一帧完整就画面结束了,也是利用人眼的视觉暂留画面; 一定要记得,每行扫描的时候,需要亮灯的高低电平点亮,不亮灯的IO口一定要设为悬浮(高阻模式)。

February 21, 2023 · 1 min · Rancho

如何用一个IO口检测两个按键的状态

用单个IO口来检测两个按键的状态; 一、ADC方案 上面是原理图,这个方案很好理解,主要就是利用电阻分压原理来判断多个按键被按下的状态,如果ADC的位数足够多,可以判断的按键数也会很多; 因为原理很简单,在这里就不再多说; 二、非ADC方案 这个方案适用于无ADC引脚或者ADC引脚被其他外设占用的情况,只以单IO口检测两个按键的状态的方案为例; 原理图如下所示: EN是单片机内部的上拉使能开关,S1和S2是待检测的按键; 通过查阅STM32F103C8T6数据手册可以得知: 内部的上拉电阻阻值等效为40K欧姆电阻,高低电平的范围也在数据手册中有给出: 当MCU供电为3.3V时候: IO口低电平电压范围:-0.5-0.8V; IO口高电平电压范围: 2.0-3.8V; 因此得到最开始的检测电路;但有两个注意事项: 这里特别要注意在使用该电路时,电路参数须满足MCU的IO口高低电平的电气特性要求; 电路如果需要具备两个按键同时按的功能要求,需自行调整电路,该电路参数不满足该要求; 电路分析如下: 当EN 闭合时: S1 按下时, V_IO 接近0V,此时IO口为低电平。 S2 按下时, V_IO = 3.3V * R103 / (R+R103) V_IO = 3.3V * 510K/ (510K+(40K//2M)) = 3.06V 此时IO口为高电平。 当EN 断开时: S1 按下时, V_IO 接近0V,此时IO口为低电平。 S2 按下时, V_IO = 3.3V * R103 / (R+R103) V_IO = 3.3V * 510K/ (510K+ 2M) = 0.67V 此时IO口为低电平。 ...

February 21, 2023 · 1 min · Rancho

如何写好CommitMessage

翻译的一篇文章; Commit messages matter. Here’s how to write them well. 提交的备注信息很重要,这里将教给你如何写好它们. 简介:为什么好的提交信息很重要 如果你随机浏览任何一个 Git 存储库的日志,你可能会发现它的提交消息或多或少是一团糟。 例如,下面是我早期致力于 Spring 时的提交信息: $ git log --oneline -5 --author cbeams --before "Fri Mar 26 2009" e5f4b49 Re-adding ConfigurationPostProcessorTests after its brief removal in r814. @Ignore-ing the testCglibClassesAreLoadedJustInTimeForEnhancement() method as it turns out this was one of the culprits in the recent build breakage. The classloader hacking causes subtle downstream effects, breaking unrelated tests. The test method is still useful, but should only be run on a manual basis to ensure CGLIB is not prematurely classloaded, and should not be run as part of the automated build. 2db0f12 fixed two build-breaking issues: + reverted ClassMetadataReadingVisitor to revision 794 + eliminated ConfigurationPostProcessorTests until further investigation determines why it causes downstream tests to fail (such as the seemingly unrelated ClassPathXmlApplicationContextTests) 147709f Tweaks to package-info.java files 22b25e0 Consolidated Util and MutableAnnotationUtils classes into existing AsmUtils 7f96f57 polishing 啊哈,然后将其与来自同一存储库的这些最近的提交进行比较: ...

February 18, 2023 · 5 min · Rancho

与或非等逻辑运算在程序中的应用

今天写代码的时候再次用到了与或非等逻辑运算符来完成一些二进制处理应用,总结记录一下; 一、缘起 以下是一个显示板的原理图,由于LED数量较多,因此在LED控制方案的选择上是选用了一块串转并的芯片74HC595; 对于74HC595的使用在这里就不再赘述; 74HC595串转并是转出8个输出端,再加上三个直接连接单片机引脚的COM端,理论上是可以很容易控制24个LED灯亮灭,就像是控制三个数码管一样,利用人眼的暂留现象,就可以使这24个LED灯的亮灭随意组合; 每一个COM端和8个LED组成一个组合,然后轮询点亮这三组LED,速度够快的话,就能看到三组LED被同时点亮; 二、解决 为了消除残影问题,在每一组的LED点亮后,应该马上写入使LED全部熄灭的命令,以消除可能会出现的残影问题; 首先定义三个uint8_t变量,例如代码中的LED_NUM1、LED_NUM2、LED_NUM3,然后通过控制COM端,分别向每组LED中写入这三个数据: 第一组 COM引脚排布:100 待写入74HC595中的数据:LED_NUM1 第二组 COM引脚排布:010 待写入74HC595中的数据:LED_NUM2 第三组 COM引脚排布:001 待写入74HC595中的数据:LED_NUM3 然后对于每一个单独LED的控制,就需要用到逻辑运算,例如图中的LED1,其属于第三组LED,因为其控制COM引脚为COM3, 因而代码可以写为: //只点亮LED1,而不影响本组内其他LED的显示 LED_NUM3 = LED_NUM3 | 0x01; //只熄灭LED1,而不影响本组内其他LED的显示 LED_NUM3 = LED_NUM3 & (~0x01); 其他的LED控制也是一样的道理; 可以提取出公式,如LED的编号为07,分别接在74HC595的Q0Q1引脚上,设任意一引脚为n号引脚,则其控制代码为: //只点亮LEDn,而不影响本组内其他LED的显示 LED_NUM = LED_NUM | (0x01 PF0 STCP -> PA12 DS -> PB5 COM1->PA15 COM2->PB3 COM3->PB4 */ /** * @brief 模拟SPI向74HC595芯片发送数据 * @param SendVal 待发送八位数据 * @return 无 */ void HC595SendData(uint8_t SendVal) { uint8_t i; for (i = 0; i 0) { LED_NUM1 = LED_NUM1 | 0x08; } else { LED_NUM1 = LED_NUM1 & (~0x08); } HC595SendData(LED_NUM1); HC595SendData(0x00); HAL_GPIO_WritePin(COM1_PIN_GPIO_Port, COM1_PIN_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(COM2_PIN_GPIO_Port, COM2_PIN_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(COM3_PIN_GPIO_Port, COM3_PIN_Pin, GPIO_PIN_RESET); if (LED_second_case > 1) { LED_NUM2 = LED_NUM2 | 0x08; } else { LED_NUM2 = LED_NUM2 & (~0x08); } HC595SendData(LED_NUM2); HAL_Delay(0); HC595SendData(0x00); HAL_GPIO_WritePin(COM1_PIN_GPIO_Port, COM1_PIN_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(COM2_PIN_GPIO_Port, COM2_PIN_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(COM3_PIN_GPIO_Port, COM3_PIN_Pin, GPIO_PIN_SET); if (LED_second_case > 2) { LED_NUM3 = LED_NUM3 | 0x08; } else { LED_NUM3 = LED_NUM3 & (~0x08); } if (LED_power) { LED_NUM3 = LED_NUM3 | 0x40; } else { LED_NUM3 = LED_NUM3 & (~0x40); } if (LED_smart == 1) { if (pwm_high > pwm_t) { LED_NUM3 = LED_NUM3 | (0x20); } else { LED_NUM3 = LED_NUM3 & (~0x20); } } if (pwm_temp % 100 == 0) { if (flag == 1) { pwm_t--; if (pwm_t == 0) { flag = !flag; } } else { pwm_t++; if (pwm_t == 10) { flag = !flag; } } } pwm_high++; if (pwm_high == 10) { pwm_high = 0; } switch (LED_first_case) { case 1: /* code */ LED_NUM3 = LED_NUM3 | (0x01); LED_NUM3 = LED_NUM3 & (~0x02); LED_NUM3 = LED_NUM3 & (~0x10); LED_NUM3 = LED_NUM3 & (~0x04); break; case 2: /* code */ LED_NUM3 = LED_NUM3 & (~0x01); LED_NUM3 = LED_NUM3 | (0x02); LED_NUM3 = LED_NUM3 & (~0x10); LED_NUM3 = LED_NUM3 & (~0x04); break; case 3: /* code */ LED_NUM3 = LED_NUM3 & (~0x01); LED_NUM3 = LED_NUM3 & (~0x02); LED_NUM3 = LED_NUM3 | (0x10); LED_NUM3 = LED_NUM3 & (~0x04); break; case 4: /* code */ LED_NUM3 = LED_NUM3 & (~0x01); LED_NUM3 = LED_NUM3 & (~0x02); LED_NUM3 = LED_NUM3 & (~0x10); LED_NUM3 = LED_NUM3 | (0x04); break; default: break; } HC595SendData(LED_NUM3); HAL_Delay(0); HC595SendData(0x00); } ,

February 14, 2023 · 2 min · Rancho

我的年终总结(2022)

对于我来说,2022年是极为重要的一年; 关于读研 关键词:迷茫 在去年这个时候,我还在考虑读研的事情,我记得自己大三上学期所有科目成绩出来的那一天,我拿计算器算了一遍又一遍,最后得到一个心凉的结果:即使我大三下学期的核心课程考试都是100分,我也够不到保研的名额;算出来这个结果之后,说真的是心凉了一截; 虽然自己之前也大概估计着保研比较悬(每次考试都是考前突击,怎么可能稳保研),但是当自己真的算出来保研无望的那一刻,心里还是失落的;失落归失落,自己总还是要做出一个选择:考研或者工作;当然初期我根本没考虑工作的事情,一是对读研确实还有向往,二是我认识的同学或者学长他们绝大多数的选择都是考研;算是迷茫也算是随大流吧,我就打算考研;当然那个时候还比较早,才是一二月份,寒假在家那些天带着失落,自己也没开始复习,在家还是搞一点东西,在咸鱼接一些单子,自己搞一点东西做,在家也算开心; 开学之后回到学校,就真要开始准备复习了,自己开始也没买什么纸质资料,网上找了些英语数学的考研课程就开始复习了,前些天倒也算努力,还制定了一个简单的复习时间表,英语看的刘晓燕老师的视频课程,数学是看的李永乐老师的视频教程和习题册;准备了一两个月吧,那些天上课时候偷偷复习,晚上也经常自己去中楼的自习室复习;复习这么久,有进展没有嘞,说实话,没什么进展,说是考研复习,自己心里没什么劲头,也只是看同学他们都开始复习,自己跟风罢了; 复习时候,感觉很难受,可能也是自从高中之后自己再也没有像这样坐在那一坐一整天地复习,学来学去,进到自己脑袋的东西也不知道有多少;那时候为了复习还拒绝了很多事情,跟老师说我要考研,推掉本来要我做的项目,跟学姐说我要考研,推掉介绍给我的外快,跟父母说我要考研,让他们安心不要经常给我打电话,看起来自己是为考研做足了准备,貌似我就是要认真复习准备考研了; 在刚回到学校不久,班助学长他们组织了一个考研就业动员大会,从这里也能看出考研和工作的人数比例,我记得讲考研经验的有三四个人,而讲工作经验的只有一个人(这位学长应该是拿了TP-LINK SP或者更高档次的Offer),我还用手机的记事本记了很多考研经验,以备我后边用; 那我是什么时候改变想法了呢,我想是那天听的一个讲座,是毕业工作几年的学长们开的讲座,中间也讲到了工作和读研的抉择,最打动我的是一句话:你应该考虑读研三年和工作三年哪一个对自己的帮助大,你不应该拿着你现在去跟读研三年之后的你比,而应该拿读研三年之后的你和工作三年之后的你比;听了之后对我触动很大,我说,那就去试试找工作吧;我想,几年后,我再回来看今天,会发现今天将是改变我一生的日子(现在一年后来看,的确是这样); 有一句话,很符合我那天的想法: “当你老了,回顾一生,就会发觉:什么时候出国读书、什么时候决定做第一份职业、何时选定了对象而恋爱、什么时候结婚,其实都是命运的巨变。只是当时站在三岔路口,眼见风云千樯,你作出抉择的那一日,在日记上,相当沉闷和平凡,当时还以为是生命中普通的一天。” 然后就放弃了考研,不知道算不算半途而废,因为自己都还没有走到半途;自己的这个打算对自己将来发展是利是弊,现在还未可知也; 至此,我的考研结束。 关于工作 关键词:期待 工作的念头出现的比较晚,就是在上面提到的那个讲座之后,自己才开始考虑找工作的事情; 最先当然是写简历,简历花了挺多时间写,不过也不是一天搞完的,我采取的方法是一天进步一点点,也就是一天写一部分,从个人信息、项目经历、个人荣誉一直写到专业技能,然后又润色润色,搞了差不多一个多星期才输出一份我自己比较满意的简历; 然后就是找合适的公司投递简历,那个时候还比较早,很多公司的提前批都还没开始,所以就先准备投个实习练练手,说起来也没有投几个,我记得就投了海康威视和OPPO这两个公司的实习,OPPO那个是一直没什么什么进展,前几天去看才发现我连简历筛选都没过,可能是跟JD不太匹配吧;至于海康威视,倒是一步步都正常进行,简历筛选过了,然后HR打电话问了我的一些基本情况,然后是技术面和HR面,当然也都顺利通过了,最后我也在杭州海康威视实习了整两个月的时间,虽然最后因为实习时间不够的缘故未能参加最后的转正答辩,但是我还是很感谢我在海康的那段实习经历,第一次让我接触到了实际的工作内容; 有一个比较神奇的地方,就是我后来拿到的大疆Offer的面试甚至比海康面试还要早一些,当我海康一面的时候,我就已经完成了大疆三面的流程在等最后结果了; 大疆是我很早就接触到的公司,我在大二参加了RoboMaster比赛,而这个比赛就是大疆举办的,所以我与大疆还是有一些小缘分的;大疆提前批很早,我记得是4月25那天就开放投递简历了,大疆的提前批其实更准确的说法应该是“RM专属通道”,是为曾经参加过RM比赛的同学们开通的一个投递通道,我刚好参加过一年这个比赛,所以就顺理成章地投递了简历,后来我才知道我竟然还是投递专属通道的前一百位同学中的一个,还收到了大疆寄给我的一份礼物; 大疆的面试流程走的很快,基本一周一个进度,所以在面试海康的时候我就已经完成了大疆的面试流程,面试大疆的面试经验对后边我面试海康起到了非常大的帮助;具体的时间表可以看一下我之前写的面经; 当我拿到海康实习的Offer后,我就决定去了,毕竟大疆的最后面试结果还没有出来,自己还要为后面的秋招准备,想着是有个实习经验,后面找工作会好一些,所以就去了杭州实习,其实实习时候自己还是比较迷茫的,对于自己到底要找什么样的工作,需要做一些什么样的准备都不太清楚,刷题刷了一些,面试经验也看了不少,但是又怀疑自己的学历到底够不够,硕士那么多,自己一个末流985本科找工作能找到吗,天天也是很焦虑,害怕自己找不到什么好的工作,怀疑自己放弃读研去找工作是不是正确的选择;焦虑归焦虑,日子还是一天天的过,看看书,做一点任务,刷刷题目,天天吃的也不少,可能也是海康餐厅的伙食不错; 转折点就在七月份的一天,我接到了大疆HR小姐姐打来的Offer Call,也收到了邮件发来的录用意向书; 我当时就想:好哦,我的秋招结束了; 知道自己拿到大疆Offer的那一刻,自己明显不那么焦虑了,当然学东西也还在学,但是更偏向自己兴趣而不是那些八股文了,因为在我看来,大疆的确是我比较好的工作去向了,不论是我面试的感受还是平常了解到的,大疆确实是我很喜欢的一家公司; 工作找到了,在海康又待了一个月,实习也结束了,又回到了学校;虽然拿到了录用意向书,但是又听说很多公司会有毁意向书的情况存在,又开始焦虑了,然后自己大四上学期又基本没有什么课程任务,就自己开始一天天摆弄自己的小玩意还有等待着最后的正式Offer,然后就在十月底的一天等到了,这样秋招才是正式结束了,悬着的心放下了,自己的秋招之旅也是有了一个满意的结果; 我对工作这件事感觉很陌生,自己上了快20年的学,终于也要到了工作的时候了,有期待也有不安; 但是日子还是一天天的过下去,一眼望到头的生活我不喜欢; 我希望自己成长,能成为自己小时候梦想的工程师那样:无所不能。 关于未来 关键词:无所不能 我前几天看到了一段话,感觉挺有道理: 生活不可能像你想象得那么好,但也不会像你想象得那么糟。我觉得人的脆弱和坚强都超乎自己的想象。有时,我可能脆弱得一句话就泪流满面,有时,也发现自己咬着牙走了很长的路。 ——莫泊桑《一生》 越往前走越能发现自己的路变得清晰;回想自己刚上大学时候那迷茫的样子,再看自己现在关于未来的想法,路变得越来越清晰了; 沿着这条路走下去,我也能成为自己期望的那种人; 新的一年,我希望我的家人平安幸福; 新的一年,我希望自己能在梦想的路上越走越远; 我想,我会成为自己想成为的那个工程师,掌握很多技能,面对问题,自己总能想到办法; 我相信我会成为这样的人:聪明、乐观、幽默、不言放弃、坚信问题终会被解决;

January 1, 2023 · 1 min · Rancho