当前位置: 首页 > news >正文

点灯案例优化(二) 利用位运算修改特定位

前面,我们对点灯代码进行了第一次优化,效果如下

尽管第一次优化以后代码可读性确实高了不少,也看起来更加简洁,但是,这里仍旧存在一个很严重的问题:就在每一个表达式右边,我们给寄存器的数据赋值的操作。

我们每一个操作都是直接整体全部赋值,与自己不相关的位直接就用默认的0或1处理了,这样就会造成一个问题:原来其他位可能被赋予特定值的现在因为整体赋值而被修改,这样是很不合适的。 我们更希望的是,在修改我们期望的特定位的数据以后仍不改变其他位的值。那么怎么办呢?这时候我们就要利用C语言中的位运算操作了。

一、位运算在代码中的用法

考虑到我们可能长时间没有使用位运算,所以这里放置一段代码,供我们去回忆一下

#include <stdio.h>

#include <stdlib.h>

void printfBinary(char * str, uint32_t num)

{

    char buffer[33];

    itoa(num, buffer, 2); // 把result转成2进制字符串

    printf("%s = (%s)2 \n", str, buffer);

}

int main()

{

    /* 左移 8<<1 = 1000<<1 = 10000*/

    printfBinary("8 << 1", 8 << 1);

    /* 右移 8>>1 = 1000>>1 = 100*/

    printfBinary("8 >> 1", 8 >> 1);

    /* 按位或 8|7 = 1000|0111 = 1111 */

    printfBinary("8 | 7", 8 | 7);

    /* 按位或 8&7 = 1000&0111 = 0000 */

    printfBinary("8 & 7", 8 & 7);

    /* 按位取反 ~8 = ~1000 = 0111 */

    printfBinary("~8", ~8);

    /*

        把某位置 1  (0 位 1位 ...)

            比如把 num 的第 2 位置 1

                1. 得到一个数第 2 位是 1 其他都为 0

                   a =  0000 0100  是由 1<<2 得到

                2. 让 num | a

     */

    printfBinary("8置第 2 位为 1 ", 8 | (1 << 2));

    /*

        把连续的多位同时置 1  (0 位 1位 ...)

            比如把 num 的第 1和2 位置 1

                1  a =  3 << 1

                2. num | a

     */

    printfBinary("8置第 1和2 位为 1 ", 8 | (3 << 1));

    /*

        把某位置 0  (0位 1位 ...)

            比如把 num 的第 2 位置 0

                1. 得到一个数第 2 位是 0 其他都为 1

                   a =  1111 1011  是由 ~(1<<2) 得到

                2. 让 num & a

     */

    printfBinary("7置第 2 位为 0 ", 7 & ~(1 << 2));

    /*

        把连续多位同时置 0  (0位 1位 ...)

            比如把 num 的第 1和2 位置 0

                1. a = ~(3<<1)

                2. 让 num & a

     */

    printfBinary("7置第 1和2 位为 0 ", 7 & ~(3 << 1));

    /*

        把连续的多位同时置位  101 (二进制)

            比如把 num 的第 1,2,3 位置为 101

            1. num的 1,2,3位置为0

                num &= ~(7<<1)

            2. num |= (5 << 1);    (5 = 101)

     */

    unsigned char num = 13;

    num &= ~(7 << 1);

    num |= 5 << 1;

    printfBinary("13", 13);

    printfBinary("13的123位置为101 ", num);

}

通过这段代码可以发现,如果我们想要把特定位置为1,就要利用或运算。比如1000,我们要让他第1位变为1(这里说的第几位是以0开头),则要用或运算,即1000 | 0010,然后每一位对应做或运算,1|0->1,0|0->0,0|1->1,0|0->0,即变成1010 ,很明显这样就可以实现了。同时这里用的0010可以借助移位得到,即1<<1整理一下就是1000 |= (1<<1),这里总结一个方法:置1位或,位1余0如果要把特定位置1,就要让寄存器数据与一个数n做或运算,然后这个数n的取法就是让对应特定位的地方为1,其余给0即可

同理,如果我们想要让特定位变成0,则就要做与运算。比如0011,要让第三位变成1,就要对其进行与操作,与谁做呢,就是与1011或,即0011&1011,0&1->0,0&0->1,1&1->1,1&1->1,这样结果就是0111,很明显实现了我们想要的操作。当然这个1011我们不好去找,所以这里还要用到取反的操作~,我们将100取反,就得到了1011,同时这个100也可以用移位得到,即1<<2整理一下就是0011 &=  ~(1<<2)。实际上就是为了让除了特定位以外的位全部置1,这样才好实现,当然了,这是一个技巧,记得了就好。所以总结一个方法:置0位与,位0余1如果要把特定位置0,就要让寄存器数据与一个数n做与运算,然后这个数n的取法就是让对应特定位的地方为0,其余给1即可(这个过程我们也会用取反实现)

由此可以看出,利用位运算即可实现在不改变其他位的情况下修改特定位的值了,这样做能够更加精确化的修改寄存器中的数据,而不影响其他位的值的情况,更加合理了。

二、利用位运算优化代码

好,前面我们对位运算进行了大致的回忆和使用方法的总结,现在我们就来对代码进行优化。主要就是对表达式右边进行修改。

以点亮第一个LED灯为例

 第一,开启时钟的时候,我们是要将寄存器数据二进制位的第二位置为1,其他不变,就是100。那么,我们想想,前面我们说置1位或,位1余0,好,那就是对原数据或一个...0100就行,同时100也可以写成1<<2,故代码修改后

// 开启时钟 第二位置为1,其他不变
RCC->APB2ENR |= (1<<2); 

第二,配置GPIOA的工作模式部分 

我们是让PA0端口变成最大速度的推挽输出模式,所以要让前四位变成0011。这时候就遇到一个问题,我们之前讲的都是修改一位,那么现在要修改四位,要怎么办呢?诶其实一样的,我们一位一位的修改不就好了。

从高向低位修改,首先看第3位,我们要置0,因为置0位与 位0余1,所以我们要让原数据与一个...110111,同时这个110111是由1000取反得到的,且1000是1<<3得到,因此代码可以写成这样:&= ~(1<<3)

同理再看第二位,也是要置0,那么由于置0位与 位0余1,所以我们要让原数据与一个...111011,同时111011是由100取反得到,且100可以表示成1<<2,因此代码可以写成 &= ~(1<<2)

同理再看第1位,这时候要置为1,那么由于置1位或 位1余0,所以我们要让原数据或一个10,且10可以表示成1<<1,所以代码可以写成 |= (1<<1)

同理再看第0位,也是要置1,那么由于置1位或 位1余0,因此我们让原数据或一个1即可(1也能写成1<<0),所以保证与前三段代码格式一致,这里就改成 |= (1<<0)

// 设置GPIOA的工作模式
GPIOA->CRL &= ~(1<<3)  // 第三位置0
GPIOA->CRL &= ~(1<<2)  // 第二位置0
GPIOA->CRL |= (1<<1)   // 第一位置1
GPIOA->CRL |= (1<<0)   // 第0位置1

 第三,设置端口高低电平

 因为我们要让LED-1灯亮,又连接LED-1灯的端口是PA0,所以我们只需修改端口输出寄存器中的数据的二进制第0位的值为0就好了。要让第0位置0,由于置0位与 位0余1,所以我们让原数据与一个...1110就好,由于...1110可以由1取反得到,所以代码可以写成 &= ~(1<<0)

// 设置PA0为低电平
GPIOA->ODR &= ~(1<<0);  // 第0位置0

 总的二次优化代码如下

#include<stm32f10x.h>int main(void)
{// 开启时钟 RCC->APB2ENR |= (1<<2);// 设置GPIOA的工作模式GPIOA->CRL &= ~(1<<3);GPIOA->CRL &= ~(1<<2);GPIOA->CRL |= (1<<1);GPIOA->CRL |= (1<<0);// 设置PA0为低电平GPIOA->ODR &= ~(1<<0);// 死循环保持状态while(1){}
}

三、测试优化代码

接下来就时在keil中测试了

点击编译,运行,下载

 无错误

观察现象,很明显,黄灯确实亮了,说明本次优化代码没有问题!

 四、结语

通过本次优化代码,我们再次加深了对位运算的理解,也了解到了其一个应用场景

这里实际上还有一个小地方可以简化代码,就是设置GPIOA工作模式地方,我们发现四段代码两两类似,其实咱可以两两合并,只需要用一个或就可以,即

// 设置GPIOA的工作模式
GPIOA->CRL &= ~(1<<3|1<<2)  
GPIOA->CRL |= (1<<1|1<<0)   

这样其实也是同样的意思,实际上就是利用或运算同时对两位进行修改,我们可自行尝试运行一下,效果是一样的

OK,这就是第二次的代码优化了,继续加油,拜拜

相关文章:

点灯案例优化(二) 利用位运算修改特定位

前面&#xff0c;我们对点灯代码进行了第一次优化&#xff0c;效果如下 尽管第一次优化以后代码可读性确实高了不少&#xff0c;也看起来更加简洁&#xff0c;但是&#xff0c;这里仍旧存在一个很严重的问题&#xff1a;就在每一个表达式右边&#xff0c;我们给寄存器的数据赋值…...

【C++备忘录】

记录一些C比较好用的代码块&#xff0c;方便自个查看。 使用std::copy 快速打印序列 #include <iostream> #include <algorithm> #include <iterator>int main() {int a[5] { 1, 2, 3, 4, 5 };copy(begin(a), end(a), ostream_iterator<int>(cout, …...

java编程 斐波拉契数列算法集锦【斐波拉契数列】【下】【集合类】【Stream函数式编程】

斐波那契数列&#xff08;Fibonacci sequence&#xff09;&#xff0c;又称黄金分割数列&#xff0c;是一个非常经典的递归问题。斐波那契数列的算法描述&#xff1a; 斐波那契数列&#xff0c;一个令人着迷而又充满神秘色彩的数字序列&#xff0c;它以0和1作为起始&#xff…...

智慧园区三维可视化平台

背景 随着物联网、人工智能等新一代信息技术的发展&#xff0c;数字孪生技术逐渐成为实现这一目标的关键工具。数字孪生技术能够对物理世界进行高精度、全要素的映射&#xff0c;并实时动态反映其变化情况&#xff0c;从而为园区提供精准的管理和服务。 方案简介 智慧园区数字…...

Redis 有序集合【实现排行榜】

使用 Redis 的 Sorted Set 数据结构可以非常高效地实现实时排行榜功能。Sorted Set 允许将元素按分数进行排序&#xff0c;同时支持插入、删除和查询操作&#xff0c;且这些操作的时间复杂度较低&#xff0c;非常适合处理高并发的场景。 实现思路 插入操作&#xff1a;当用户…...

ORACLE数据库管理系统介绍

1.ORACLE的特点: 可移植性 ORACLE采用C语言开发而成,故产品与硬件和操作系统具有很强的独立性。从大型机到微机上都可运行ORACLE的产品。可在UNIX、DOS、Windows等操作系统上运行。可兼容性 由于采用了国际标准的数据查询语言SQL,与IBM的SQL/DS、DB2等均兼容。并提供读取其它…...

C# 中Linq探讨 Or条件拼接

在C#中&#xff0c;没有直接内置于.NET Core或.NET Framework中的NuGet包能够直接“拼接”LINQ的OR条件&#xff0c;因为LINQ本身设计为一种声明式编程模型&#xff0c;用于查询数据集合。然而&#xff0c;你可以通过一些方式来实现多个条件以OR逻辑组合的效果&#xff0c;而不…...

有关应用层面试题有关库的思维导体

面试题目&#xff1a; TCP通信中3次握手和四次挥手&#xff1f; 答&#xff1a; 第一次握手&#xff1a;客户端发送SYN包&#xff08;SYN1, seq0&#xff09;给服务器&#xff0c;并进入SYN_SENT状态&#xff0c;等待服务器返回确认包。第二次握手&#xff1a;服务器接收到S…...

记一次 SAP BP 编号范围错误引发的一个问题 GET_NRIV_LINE

本来想着循着错误提示去排查&#xff0c;但是还是想看看业务发生了什么&#xff0c;他们的操作是否有问题&#xff0c;不经意间发现 号码段是有问题的&#xff0c;由此大概可以判断是他们编号范围和类型之间的问题 角色和分组是否一致的&#xff0c;如果不一致就发生了以上错误…...

(17)ELK大型储存库的搭建

前言&#xff1a; els是大型数据储存体系&#xff0c;类似于一种分片式存储方式。elasticsearch有强大的查询功能&#xff0c;基于java开发的工具&#xff0c;结合logstash收集工具&#xff0c;收集数据。kibana图形化展示数据&#xff0c;可以很好在大量的消息中准确的找到符…...

每日一问:Kafka消息丢失与堆积问题分析(简化版)

Kafka 消息系统问题解析 在本篇博客中&#xff0c;我们将深入探讨 Kafka 中常见的两大问题&#xff1a;消息丢失和消息堆积。首先&#xff0c;我们将简要介绍 Kafka 的基本工作原理&#xff0c;随后分别分析消息丢失和堆积的原因&#xff0c;并提供针对性的解决方案。 关于其详…...

C语言中函数sizeof和strlen区别

sizeof和strlen是C语言中的两个常用函数&#xff0c;它们的作用和使用方式有所不同。 sizeof sizeof是一个运算符而非函数&#xff0c;用于计算数据类型或变量占用的字节数。它可以计算任意数据类型&#xff08;包括基本类型、自定义结构体、数组等&#xff09;的大小。例如&…...

RAG与LLM原理及实践(14)---- Python + MinIO + Kafka进阶

目录 背景 根因分析 配置 构造 创建 network 构造 zookeeper 构造 kafka 参数构造 原理解析 图解 全过程解析 工具使用 kafkacat 查看 broker python 实现 python send + kafka recv python 代码 kafka recv 运行效果 python recv + kafka send python 代…...

接口自动化-代码实现

接口自动化基础 1、接口自动化测试 接口自动化&#xff1a;使用工具或代码代替人对接口进行测试的技术测试目的&#xff1a; 防止开发修改代码时引入新的问题测试时机&#xff1a; 开发进行系统测试转测前&#xff0c;可以先进行接口自动化脚本的编写开发进行系统测试转测后&…...

如何查看linux大文件

文章目录 一、查看存储情况二、查看指定路径下的文件大小查看临时文件和日志的大小 三、查找home目录下文件大小大于100M的大文件四、查看INNODE使用情况五、查看进程使用情况查看所有进程查看特定进程杀死相关进程 六、清除缓存操作七、 查看docker的硬盘占用情况详细查看 一、…...

生成式人工智能服务大模型备案答疑

问&#xff1a;大模型备案范围 答&#xff1a;利用生成式人工智能技术向中华人民共和国境内公众提供生成文本、图片、音频、视频等内容的服务&#xff0c;适用本办法。 未向境内公众提供生成式人工智能服务的&#xff0c;不适用本办法的规定。 ps&#xff1a;生成式人工智能…...

QT-贪吃蛇小游戏

QT-贪吃蛇小游戏 一、演示效果二、核心代码三、下载链接 一、演示效果 二、核心代码 #include "Food.h" #include <QTime> #include <time.h> #include "Snake.h"Food::Food(int foodSize):foodSize(foodSize) {coordinate.x -1;coordinate.…...

虚幻5|AI视力系统,听力系统,预测系统(1)视力系统

继宠物伴随系统初步篇后续 虚幻5|AI巡逻宠物伴随及定点巡逻—初步篇-CSDN博客 一&#xff0c;听力系统 1.打开宠物ai的角色蓝图 2.选中ai感知组件 右侧细节&#xff0c;找到ai感知&#xff0c;添加感知配置&#xff0c;我们需要的是ai视力配置 3.选中左侧创建的ai感知组件&…...

IC rankIC

IC IC衡量的是预测值和实际值之间的相关系数 计算公式为&#xff1a;IC Pearson(R(predicted),R(actual)) 取值范围&#xff1a;[-1, 1]&#xff0c;其中1表示完全相关&#xff0c;也就是预测值和实际值完全一样。0表示完全不相关&#xff0c;-1表示&#xff0c;反向相关 ra…...

Windows服务器IIS7下如何查看真实报错原因

背景 IIS7默认为友好报错&#xff0c;或只报错代码。如500错误&#xff0c;401错误等。根据这些错误无法定位真实原因&#xff0c;故而需要显示真实的错误信息。 解决方案 以500错误为例说明。 1、打开IIS,点全局设置中的"错误页"(注意必须是全局网站)。 2、右击50…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘

美国西海岸的夏天&#xff0c;再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至&#xff0c;这不仅是开发者的盛宴&#xff0c;更是全球数亿苹果用户翘首以盼的科技春晚。今年&#xff0c;苹果依旧为我们带来了全家桶式的系统更新&#xff0c;包括 iOS 26、iPadOS 26…...

label-studio的使用教程(导入本地路径)

文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

rknn优化教程(二)

文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK&#xff0c;开始写第二篇的内容了。这篇博客主要能写一下&#xff1a; 如何给一些三方库按照xmake方式进行封装&#xff0c;供调用如何按…...

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

cf2117E

原题链接&#xff1a;https://codeforces.com/contest/2117/problem/E 题目背景&#xff1a; 给定两个数组a,b&#xff0c;可以执行多次以下操作&#xff1a;选择 i (1 < i < n - 1)&#xff0c;并设置 或&#xff0c;也可以在执行上述操作前执行一次删除任意 和 。求…...

Mac软件卸载指南,简单易懂!

刚和Adobe分手&#xff0c;它却总在Library里给你写"回忆录"&#xff1f;卸载的Final Cut Pro像电子幽灵般阴魂不散&#xff1f;总是会有残留文件&#xff0c;别慌&#xff01;这份Mac软件卸载指南&#xff0c;将用最硬核的方式教你"数字分手术"&#xff0…...

IT供电系统绝缘监测及故障定位解决方案

随着新能源的快速发展&#xff0c;光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域&#xff0c;IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选&#xff0c;但在长期运行中&#xff0c;例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

优选算法第十二讲:队列 + 宽搜 优先级队列

优选算法第十二讲&#xff1a;队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...

鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南

1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发&#xff0c;使用DevEco Studio作为开发工具&#xff0c;采用Java语言实现&#xff0c;包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...