datasheet

STM32-基于汇编来分析延时

2019-06-14来源: eefocus关键字:STM32  汇编  延时

上一篇文章写了一个延时函数,是这样的:

void Delay(uint32_t nCount) 

{

     for(; nCount != 0; nCount--);

}

为了延时1秒,设置了一个值:1600000。

为什么取这样一个值,这是我实测出来的一个值,是通过多次累计闪灯次数,对应电脑时间,计算出来的。

看见这个值之后,我有一个推测:

1.6M=8M/5

我没有使用外部晶振HSE,使用了默认的内部晶振HSI,主频为8M。

所以,可能这个延时函数循环一次所需要的机器周期数就是5!

怎么验证?

可以看下汇编代码来进行分析。


具体操作步骤:

在成功编译程序后,点击工具栏上一个红色的"D",进入调试状态,再把鼠标点到c代码处,右键查看汇编代码,就可以看到所有c代码编译后的汇编代码了。

延时函数的汇编是这样的:


    53:      for(; nCount != 0; nCount--); 

0x08000206 E000      B        0x0800020A

0x08000208 1E40      SUBS     r0,r0,#1

0x0800020A 2800      CMP      r0,#0x00

0x0800020C D1FC      BNE      0x08000208    


可以看到有4条汇编指令:

B,跳转

SUBS,减

CMP,比较

BNE,根据标志跳转


大体理解,就是这样:进入循环后,先跳转去进行比较。比较后,查看比较结果,若不相等,则跳转去执行减操作。

对于循环次数很多的情况,可以忽略第一次的跳转,所以一般情况下的循环一次,就是执行3条指令:

0x08000208 1E40      SUBS     r0,r0,#1

0x0800020A 2800      CMP      r0,#0x00

0x0800020C D1FC      BNE      0x08000208    


3条指令,5个机器周期,能对应上么?

我上网查了一下,大概的结论是:


stm32 属于ARM ,ARM都是精简指令集,大部分的指令(除STM、LDM、BNE等外)都是单周期指令。

对于跳转指令,需要增加两个指令周期。


按这种方式来估算:

SUBS,1

CMP,1

BNE,3

则 1+1+3=5 ,应该是符合的。


但是我心里不太踏实,毕竟没有谁明确说哪个指令是几个周期,这中间还是有部分猜测的。是否有更靠谱的方式呢?


然后又找到一个好方法:

具体步骤参见:

https://blog.csdn.net/qq_41092963/article/details/82759097


大概说下步骤:

进keil的调试模式,单步调试,记录时间。

查看时间:Logic Analyzer窗口

汇编代码:Disassembly窗口

另外,还可以开一个Watch窗口,查看我们关心的变量值。


其样式见下图:

通过几次单步操作,记录下时间,然后我们可以列出一个表来:


  累计(ns) 指令耗时(ns)

初始 247750  

CMP 247875 125

BNE 248250 375

SUBS 248375 125

125ns,一个机器周期,即频率的倒数:1/8M,完全吻合。

这样,就确实验证了之前的猜测:

SUBS,1个机器周期(125ns)

CMP,1个机器周期(125ns)

BNE,3个机器周期(375ns)

这样,我们就可以从微观的循环周期向宏观的时间进行计算了:

循环一次所需机器周期数: 1+1+3=5 。

5个机器周期,耗时就是125ns*5=625ns

循环1.6M次的耗时:625ns*1600000=625*1.6ms=1000ms=1s


如此,心里就踏实了。



如果试试不同的延时函数的写法呢?


写成while循环,结果会怎样呢?

看看汇编,基本上是一样的:


0x080012D8 E000      B        0x080012DC

0x080012DA 1E40      SUBS     r0,r0,#1

   368:         while(TimeDelay > 0){ 

   369:                 TimeDelay--; 

   370:         } 

0x080012DC 2800      CMP      r0,#0x00

0x080012DE D1FC      BNE      0x080012DA


也是这样3个指令:SUBS,CMP,BNE。还是5个机器周期,没有问题。



如果选择不一样的编译方式呢?

之前使用的是o0优化,现在使用o3优化(Options for Target --> C/C++ -->Optimization),看看:

    42: void Delay(uint32_t nCount)  

    43: { 

0x0800048C 4C03      LDR      r4,[pc,#12]  ; @0x0800049C

0x0800048E 4620      MOV      r0,r4

0x08000490 1E40      SUBS     r0,r0,#1

    44:      for(; nCount != 0; nCount--); 

0x08000492 D1FD      BNE      0x08000490        

        

主循环中只有2个指令了:SUBS,BNE。居然只有4个机器周期了!


现在来推测一下,需要循环多少次:

4*125*n=1000,000,000ns

n=2000,000

来放心的验证吧!


对比上面两种循环的差异,就是少了一个CMP指令。

可是,这样也有点奇怪吧,难道,cmp指令,是可有可无的吗?

这里需要理解下BNE指令。


BNE指令,是个条件跳转,即:是“不相等(或不为0)跳转指令”。如果不为0就跳转到后面指定的地址,继续执行。

而“不相等(或不为0)”,是什么不相等,什么不为0?其实它判断的是CPSR中的 Z 标记。

具体关于 Z 标记怎么设置的?那就依赖于上一个执行指令了。


再看上一条指令,两种优化模式下编译的结果不同,分别是CMP与SUBS。


CMP比较指令,用于把一个寄存器的内容和另一个寄存器的内容或一个立即数进行比较,同时更新CPSR中条件标志位的值。

对于减法,本来有一个SUB。而SUBS的差异,就是多了一个S,它的作用就是会根据执行结果来更新CPSR中的 N、Z、C 和 V 标记。


简单总结一下,BNE的判断依赖于Z标志,而CMP与SUBS会影响标志位,具体是如何影响的,我也不想再深入研究了,大体了解到这里,基本能满足我的好奇心了。



再补充一句,就是关于BNE指令的耗时。

前面我们知道,跳转时它的耗时是3个机器周期。后面我又测试了一下,若不跳转,例如是最后一次比较,结果恰好为0,则顺序往下执行,此时,BNE指令的耗时是1个机器周期。


可见,汇编里面的内容是很深奥的,所幸,我不需要花太多时间去研究,在此,只是大体了解下,解解几个小疑惑即可。



关键字:STM32  汇编  延时

编辑:什么鱼 引用地址:http://www.eeworld.com.cn/mcu/ic464655.html
本网站转载的所有的文章、图片、音频视频文件等资料的版权归版权所有人所有,本站采用的非本站原创文章及图片等内容无法一一联系确认版权者。如果本网所选内容的文章作者及编辑认为其作品不宜公开自由传播,或不应无偿使用,请及时通过电子邮件或电话通知我们,以迅速采取适当措施,避免给双方造成不必要的经济损失。

上一篇:STM32-仿真调试时的SystemInit陷阱
下一篇:STM32-点灯程序

关注eeworld公众号 快捷获取更多信息
关注eeworld公众号
快捷获取更多信息
关注eeworld服务号 享受更多官方福利
关注eeworld服务号
享受更多官方福利

推荐阅读

用于stm32Discovery的图像转代码取模小工具

最近拿着st的官方板子在开发图形界面,看了下代码是直接把整个像素32bit拷贝到控制器中Graphic RAM(就是一块SDRAM)中的,所以以往遇到的生成器都不能用,在内部则又是转换费时费力,占用内存所以针对stm32的discovery lcd显示写了个图像代码生成的程序,可以直接生成32bit的ARGB格式代码等后面发一个成熟点的版本,目前还是有些容易遇到的bug
发表于 2019-06-15
用于stm32Discovery的图像转代码取模小工具

STM32L0xx_HAL_Driver库的使用——UART

单片机型号:STM32L051C8T6开发环境MDK5.12库版本:STM32L0xx_HAL_Driver V1.1.0主机环境:Windows XP之前一直使用的STM32F030C8T6单片机来做开发,因需求更改更换了一个新型号STM32L051C8T6,主要是用到了其低功耗特性,本以为直接把代码拷贝一下就可以使用了,结果是太天真了,STM32F030C8T6使用的库是STM32F0_StdPeriph_Lib而STM32L051C8T6使用的库是STM32L0xx_HAL_Driver两者的差别还是很大的,而且官方也推荐使用后者,没办法,重新学习一下吧。。。参考其例程磕磕绊绊的勉强可以写一个工程了,这里写一下有关UART
发表于 2019-06-15

STM32CubeMx之串行通信

前言我的板子是:STM32ZGT6配置1.打开STM32CubeMX新建工程,选择STM32ZGT62.配置外部高速时钟RCC设置,选择HSE(外部高速时钟)为Crystal/Ceramic Resonator(晶振/陶瓷谐振器),我的开发板外部时钟是25MHZ。 3.配置串行通信选择Asynchronous异步通信。  串口配置设置波特率为115200 Bits/s。传输数据长度为8 Bit。奇偶检验无,停止位1.其他参数默认。 生成报告以及代码,编译程序(最好单独生成.c和.h文件)。在usart.c文件中可看到串口1的初始化函数MX_USART1_UART_Init(void
发表于 2019-06-15

stm32 不断进入串口中断的bug解决方法

在使用stm32的时候,发现usart会莫名的卡在串口中断里,然而串口初始化只配置了RXNE中断,打断点发现不断进入中断却没不是RXNE中断引起的,经过查找资料发现是ORE的问题,2篇博文解决方案如下:http://bbs.21ic.com/icview-160999-1-1.html及http://blog.csdn.net/origin333/article/details/49992383大致原因为开启了RXNE中断之后 ORE也开启了,但是使用USART_GetITStatus却无法读取到ORE的标志位(未使能ERR时),这样也无法消除中断申请,自然一直进入串口中断,如果要消除ORE需要
发表于 2019-06-15

STM32串口USART用法的进阶(HAL库版本)

句话是绑定DMA,来源数USART1,目的是数组,定义好的。第二句是打开空闲中断it.c里面找到void USART1_IRQHandler(void){ UsartReceive_IDLE(&huart1);//自己添加一个函数,这就是中断,搬完以后,空闲中断 自己完成它  HAL_UART_IRQHandler(&huart1);}void UsartReceive_IDLE(UART_HandleTypeDef *huart)  {      uint32_t temp;      if
发表于 2019-06-15

STM32CubeMX学习教程之五:PWM实现呼吸灯效果

软件:STM32CubeMX V4.25.0  System Workbench V2.4固件库版本:STM32Cube FW_F1 V1.6.1硬件:OneNet 麒麟座V2.3在STM32CubeMX中新建项目,选择正确的MCU型号 首先设置RCC和SYS,如下图 然后根据板子实际情况设置时钟(麒麟座外部晶振是12M,STM32F103x的最高主频是72M),如下图设置PC7 管脚为TIM3_CH2, 即定时器TIM3的Channel2然后设置TIM3的Channel2为PWM Generation CH2 从上一篇博文我们知道TIM3是挂在APB1总线上的,看时钟树我们知道
发表于 2019-06-15
STM32CubeMX学习教程之五:PWM实现呼吸灯效果

小广播

何立民专栏

单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号 电信业务审批[2006]字第258号函 京公海网安备110108001534 Copyright © 2005-2019 EEWORLD.com.cn, Inc. All rights reserved