• 运算符 % 的妙用


     

    鱼鹰  鱼鹰谈单片机  3月3日

    预计阅读时间: 8 分钟

     还在用 if 语句进行延时吗,试试 % 吧

    说完位运算,再说说其他的运算符。+ - * / 不用多说,应该都比较清楚,但是还是要注意的就是使用 / 进行整型变量的计算时,它不像平常一样可以得到小数的,而只有整数部分,并没有小数。还有就是各个运算符的顺序,如果不确定哪个先运算,不如加上括号 () 吧,不用担心效率的问题,因为加了括号只是告诉编译器该如何处理这条语句而已。另外使用 #define 定义一些表达式的时候也最好加上括号,因为你不能确定你这个宏定义会在什么地方使用,为了安全起见还是加上比较好,这些内容在宏定义小节将进行更详细的说明。

    现在来重点说说 % 取余运算。这个运算就很有意思了。如果从 51 单片机过来的,看到这个运算符最多的地方就是在为定时器赋值时将高低字节进行分离了,还有在数码管实验中将一个数分离成十进制的。这些都是很常见的应用。但是其实更广泛的应用不在此。

    编程的时候,很多时候都会要求一个数在某一个范围内进行反复循环,0~100 循环, 0~5 循环等等。一般的方法是使用 if 语句,当判断达到最大值的时候回到开始处。实际上使用这种方法也是可以的,但是如果有更简单更高效的方法你是否还会使用 if 语句呢。

    先说说使用 & 的方法吧。比如说我想让一个数在 0~7 内循环,该如何做呢?temp = (temp++)&0x07,如此就简单的实现了 0~7 循环。因为要实现 0~7 的循环,其实只要提取一个变量递增的低三位即可。不管这个变量如何变化,它的低三位始终都是在 0~7 循环变化的。同理,它也可以实现 0~15、0~31 变化。但是这个方法有局限,它只能按照连续 bit 位的最大值进行循环。

    现在再说 %,这个就厉害了,它不存在这个限制。可以在 0~任意数 循环。比如 0~5 循环,只要 temp = (temp++)%6 (注意是 6 而不是 5 ),那么 temp 就会在 0~5 之间循环了(这是我在看循环队列的时候看到的方法,当时很是震惊,关于循环队列更多的东西将在循环队列中讲解)。

    很神奇吧,更神奇的是使用它还可以计算两个变量之间的距离(因为距离时没有负数的,这里我称之为距离可能不是很好理解,慢慢来)我们知道不管是 8bit 数据,16bit、32bit、64bit,它始终有一个位数的限制,如何在有限的位数里面获得两个数据之间准确距离信息呢。以 8bit 为例,最大数为 255,第一次读取为 0x4,第二次读取是 0x9,那么从 0x04 变化到 0x09,变化了几次(递增数为 1 )?9 – 4 = 5,如果变化后的数小于 255 当然好办,但是超过了 255,又从 0 开始递增呢?这个时候又该如何。比如说一开始读取的是 251,之后再读一次,变成了 1,怎么算,251-1=250?肯定不对,1-250=-250,更不是?那到底变化了几次?252、253、254、255、0、1,这里可以看出是 6,但是该怎么计算,又是否有一个公式可以在不改变原来数据变化的情况下将超过限制和没超过限制这两种情况的计算包含呢。有的。就是 length = (num2 – num1 + max)%max。关于这个公式更详细内容请看循环队列小节。

    确实,使用 % 可以在任意数之间循环,但是她也有限制,就是目前来看只能实现递增 1 的情况,我想递减呢?就是说我想从 9 减为 0 ,然后从 9 开始继续,又该如何。我的一个项目就需要这样的变化,怎么办,如果用 if 确实能够解决问题,但是直觉告诉我,肯定有简单方法实现,所以我就上网搜,但是可能我搜索的方法不对,始终没有搜到,所以我暂时搁置了。直到一天夜里,回想递增循环的情况,慢慢的思考其中的本质,再结合距离计算的公式,突然明悟了。就是 num--; num = (num+max)%max; 这样两条语句去实现。比如 9~0,就是 num--; num = (num+10)%10;

    那么怎么理解呢,按理说 0 再自减就是 0xff,即 255,再加 10 就是 265, 265%10 =5,怎么就变成了 9 呢?这和存储有关。255 从有符号的角度来看,就是 -1,-1+10 等于 9,9%10=9,没错,就是如此。但是我的变量声明不是有符号的,而是无符号的,怎么也没有出问题呢?这是因为溢出了,因为 265 在 8bit 情况下溢出就变成了 9,所以计算也不会出现问题。所以这些计算机基础方面的东西一定要理解透彻清晰,才能更好的驾驭一门语言,你也会发现其中的东西真的很神奇。

    ---------------------------------------------------------------------------------------2018/10/14  Osprey

    在运用的时候发现,其实当最大值就是溢出值时,可以简化这个公式。很多时候单片机都需要一个运行时间信息,一般的处理方法就是使用 if 语句进行延时,但是实际上不需要如此麻烦。

    比如说 STM32 单片机,16 位定时器,分频时间根据你需要延时的最大时间进行设置。比如最大所需延时 6s,16 位定时器最大值 65535,那么在定时器时钟频率 72M 情况下,分频系数设置为 7200-1(分频系数越大,定时精度越小,所以要根据最大延时间确定分频系数),那么最大溢出时间为 6,553,600 us,即 6.5536 s,这样就可以让定时器一直处于运行状态,即使到达最大值也会重新从 0 开始计数,这样就可以不需要变量也可以准确获取时间(如果定时时间很长,即使使用最大分频 65535 也不能得到最大延时,又不想增加变量在溢出中断中进行计时怎么办,可以采用定时器主从模式,用一个定时器给另一个分频)

    既然定时器的寄存器 CNT 一直在 0~65535 循环变化,那么就可以通过获得该时间来实现延时的效果。根据前面的内容,使用 (time –  CNT + 65535)%65535 就可以获得从定时开始到现在的时间了(time 为 16 位变量,该值为开始定时那一刻 CNT 的值)。

    比如现在要定时 1s,那么首先获取当前的 CNT 值,然后等待,如

    这样,当 1s 时间到的时候就可以跳出循环了,当然了可以使用 if 语句,这样就不会卡死在这里,只有时间到的情况下才会进入。如:

    但是你是否发现 65536 是一个很特殊的数字,定时器因为位数的限制,始终都在 0~65535 内循环,不需要使用 % 来让它循环,那么以上的判断语句就可以简化成这样:

    从效率上来说明显后者更高,不信的话可以自己测试。

    但是需要注意的是 (time –CNT + 65536) & 65535 和 (time –CNT + 65536) % 65536 之间的区别,虽然这两种写法都可以使计算的值在 0~65535 之间循环,也等效,但是明显后者效率稍低一些,因为前面乃是位运算。但是呢,后者的好处就像前面说的,没有限制。

        另外再说一点,STM32F4 系列单片机有一个 DWT 模块,在当 systick 时钟被操作系统(如 ucos)使用的时候,可以使用它作为延时时钟,延时精度为系统时钟频率(纳秒级),而且是 32 位的,延时时间也够长,不用白不用啊!

    ---------------------------------------------------------------------------更新于 2018/11/19  Osprey

  • 相关阅读:
    “使用Adobe Reader XI打开PDF文件,左侧无法显示导航列表”解决方法
    水平垂直居中——子元素水平垂直居中于父元素
    Git概念、命令及项目部署(另外补充常用的Linux命令)
    在VSCode中使用Markdown
    Sublime Text3中MarkDown的使用
    <el-table>序号逐次增加,翻页时继续累加,解决翻页时从编号1开始的情况
    Visio2016专业版永久激活码
    ElementUI的Loading组件 —— 想实现在请求后台数据之前开启Loading组件,请求成功或失败之后,关闭Loading组件
    HTML5新特性
    FCC知识点总结
  • 原文地址:https://www.cnblogs.com/CodeWorkerLiMing/p/12007404.html
Copyright © 2020-2023  润新知