首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

51单片机中操作符的妙用

  • 25-04-24 21:24
  • 2439
  • 8166
blog.csdn.net

1.遇到可位寻址和不可位寻址

首先介绍一下可位寻址和不可位寻址,简单来讲 可位寻址就是对寄存器每一位可单独操作(如TCON),不可位寻址就是只能对整个寄存器操作(如TMOD)

我们在配置定时器模式时候(TMOD),如果使用的是定时器0,则直接TMOD=0x01(这里是16位模式)即可,那为什么一般情况配置的都是TMOD&=0xF0;TMOD|=0x01呢?

假设我们定时器1和定时器0要同时使用,如使用串口通信时我们要用定时器1来满足波特率的溢出,如果同时配置的话,那么此时TMOD的八位应该为0010 0001(T1为八位自动重载模式,T0为16位模式),但是我们写代码时候需要模块化,一般调用不同的函数具有不同的效果(不要让函数的功能太复杂,一个函数只做一件事),初始化定时器0的函数不要去也能初始化定时器1。

  1. void Timer0_Init()
  2. {
  3. TMOD&=0xF0;
  4. TMOD|=0x01;
  5. TR0=1;
  6. TH0=0xFC;
  7. TL0=0x18;
  8. ET0=1;
  9. EA=1;
  10. PT0=0;
  11. }
  12. void Timer1_Init()
  13. {
  14. TMOD&=0x0F;
  15. TMOD|=0x20;
  16. TH1=0xF3;
  17. TL1=0xF3;
  18. ET1=0;
  19. TR1=1;
  20. }

如果当我们的定时器0初始化完毕之后,你如果直接用TMOD=0x20初始化我们的定时器1,那么原有的定时器0初始化便会被破坏,那么我们可以用TMOD先&=0x0F(0000 1111)保证TMOD低四位不变高四位置0,再用TMOD去|=0x20,改变高四位不改变低四位满足定时器1的初始化。

结论:我们想对不可位寻址的寄存器进行操作时,可以运用“&=”“|=”“^=”来进行配置

        一个数&=0xF0时,即高四位不变,低四位置0;

        一个数&=0x0F时,即高四位置0,低四位不变;

        一个数|=0xF0时,即高四位置1,低四位不变;

        一个数|=0x0F时,即高四位不变,低四位置1;

2.取出一个数据中的每一位

  1. #include
  2. #include "Delay.h"
  3. sbit RCK=P3^5; //RCLK
  4. sbit SCK=P3^6; //SRCLK
  5. sbit SER=P3^4; //SER
  6. #define MATRIX_LED_PORT P0
  7. void _74HC595_WriteByte(unsigned char Byte)
  8. {
  9. unsigned char i;
  10. for(i=0;i<8;i++)
  11. {
  12. SER=Byte&(0x80>>i);
  13. SCK=1;
  14. SCK=0;
  15. }
  16. RCK=1;
  17. RCK=0;
  18. }
  19. void MatrixLED_ShowColumn(unsigned char Column,Data)
  20. {
  21. _74HC595_WriteByte(Data);
  22. MATRIX_LED_PORT=~(0x80>>Column);
  23. Delay(1);
  24. MATRIX_LED_PORT=0xFF;
  25. }

在单片机中写代码,取出一个数据中的每一位是一种非常常见的操作,这里我们例举使用74HC595来控制LED点阵屏。

上面代码中我们能够看见一个这样的例子,SER=Byte&0x80,Byte是我们放在移位寄存器上的一个字节,&一个0x80(1000 0000)即取出Byte这个字节的最高位1赋值给我们的SER,然后用for循环和右移操作符便可以实现将Byte的每一位从高到低赋值给我们的SER

当然for循环和左移操作符还可以实现将字节的每一位从低到高赋值给我们的其他数据(比如一些时序结构要求从低到高输入数据每一位)

  1. unsigned char i;
  2. unsiged char Data;
  3. unsigned char Byte;
  4. //从低到高将Byte每一位赋给Data
  5. for(i=0;i<8;i++)
  6. {
  7. Data=Byte&(0x01<
  8. }
  9. //从高到低将Byte每一位赋给Data
  10. for(i=7;i>=0;i--)
  11. {
  12. Data=Byte&(0x01<
  13. }

结论:改变不同的for循环和操作符能够实现不同的赋值效果

3.常见的置1和置0操作

置1和置0会经常性的使用我们的“|”和“&”,这里我们例举DS1302时钟读一个字节的代码

  1. unsigned char DS1302_ReadByte(unsigned char Command)
  2. {
  3.     unsigned char i,Data=0x00;
  4.     Command|=0x01;    //将指令转换为读指令
  5.     DS1302_CE=1;
  6.     for(i=0;i<8;i++)
  7.     {
  8.         DS1302_IO=Command&(0x01<
  9.         DS1302_SCLK=0;
  10.         DS1302_SCLK=1;
  11.     }
  12.     for(i=0;i<8;i++)
  13.     {
  14.         DS1302_SCLK=1;
  15.         DS1302_SCLK=0;
  16.         if(DS1302_IO){Data|=(0x01<
  17.     }
  18.     DS1302_CE=0;
  19.     DS1302_IO=0;    //读取后将IO设置为0,否则读出的数据会出错
  20.     return Data;
  21. }

这里有一个if(DS1302_IO){Data|=(0x01<(必要时加入~取反)

  1. unsigned char i;
  2. unsigned Data;//我们要进行操作的数据
  3. //依次对Data每一位从低到高置1
  4. for(i=0;i<8;i++)
  5. {
  6. Data|=(0x01<
  7. }
  8. //依次对Data每一位从低到高置0
  9. for(i=0;i<8;i++)
  10. {
  11. Data&=~(0x01<//这里我们使用一个~取反,便能达到置0效果
  12. }
  13. //依次对Data每一位从高到低置1
  14. for(i=0;i<8;i++)
  15. {
  16. Data|=(0x80>>i);
  17. }
  18. //依次对Data每一位从高到低置0
  19. for(i=0;i<8;i++)
  20. {
  21. Data&=~(0x80>>i);
  22. }

4.整形提升

由于在单片机代码中很多地方读取数据都只能8位一个字节大小的读取,例如AT24C02储存器,写入一个字节的代码中,就只能一次读取8位,但是假设我们现在想让它存一个年份,比如2024,因为char类型变量大小一个字节8位,即2^8=256,最大只能表示255的数字大小,所以我们定义这个年份时候要用int 类型两个字节16位,即2^16=65536,最大可以表示65535

  1. #include
  2. #include "I2C.h"
  3. #define AT24C02_ADDRESS 0xA0
  4. /**
  5. * @brief AT24C02写入一个字节
  6. * @param WordAddress 要写入字节的地址
  7. * @param Data 要写入的数据
  8. * @retval 无
  9. */
  10. void AT24C02_WriteByte(unsigned char WordAddress,Data)
  11. {
  12. I2C_Start();
  13. I2C_SendByte(AT24C02_ADDRESS);
  14. I2C_ReceiveAck();
  15. I2C_SendByte(WordAddress);
  16. I2C_ReceiveAck();
  17. I2C_SendByte(Data);
  18. I2C_ReceiveAck();
  19. I2C_Stop();
  20. }
  21. /**
  22. * @brief AT24C02读取一个字节
  23. * @param WordAddress 要读出字节的地址
  24. * @retval 读出的数据
  25. */
  26. unsigned char AT24C02_ReadByte(unsigned char WordAddress)
  27. {
  28. unsigned char Data;
  29. I2C_Start();
  30. I2C_SendByte(AT24C02_ADDRESS);
  31. I2C_ReceiveAck();
  32. I2C_SendByte(WordAddress);
  33. I2C_ReceiveAck();
  34. I2C_Start();
  35. I2C_SendByte(AT24C02_ADDRESS|0x01);
  36. I2C_ReceiveAck();
  37. Data=I2C_ReceiveByte();
  38. I2C_SendAck(1);
  39. I2C_Stop();
  40. return Data;
  41. }

这个时候该怎么办呢?"/""%"这两个操作符也是非常重要的,合理运用能简化不少代码,我们看下面代码的解释。

  1. unsigned int Year;
  2. //首先是写入,我们通常使用"/"取出一个数的高位部分,使用"%"取出一个数低位部分
  3. //在这里int为65536,char为256,那么可以用Year/256表示int类型的高八位,Year%256表示低八位
  4. //用两个储存位置分别存储高八位和低八位,然后再合并
  5. AT24C02_WriteByte(1,Year/256);
  6. AT24C02_WriteByte(2,Year%256);
  7. //由于AT24C02_ReadByte的返回值也是char类型,故合并涉及到整形提升
  8. Year=AT24C02_ReadByte(2);
  9. Year|=AT24C02_ReadByte(1)<<8;
  10. //这里我们首先使用<<将高八位左移,eg.假设高八位为1111 1010,左移后得到1111 1010 0000 0000
  11. //然后再使用|=,我们根据第三部分知识知道|=可以使低八位部分保持不变,即若低八位为1011 1011
  12. //则最后Year应该为1111 1010 1011 1011变成16位int类型,实现整形提升

结论:我们的|=和<<的使用可以实现整形提升

5.有关''%''和"/"的用法

在学习过程中碰到一些有关代码简写和比较巧妙的地方

首先是简写,看下面代码

  1. if(i>2){i=0;}
  2. //等价于
  3. i%=3;

其次是在学习红外遥控过程中看到一个这样的代码:

  1. #include
  2. #include "Timer0.h"
  3. #include "Int0.h"
  4. unsigned int IR_Time;
  5. unsigned char IR_State;
  6. unsigned char IR_Data[4];
  7. unsigned char IR_pData;
  8. unsigned char IR_DataFlag;
  9. unsigned char IR_RepeatFlag;
  10. unsigned char IR_Address;
  11. unsigned char IR_Command;
  12. /**
  13. * @brief 红外遥控初始化
  14. * @param 无
  15. * @retval 无
  16. */
  17. void IR_Init(void)
  18. {
  19. Timer0_Init();
  20. Int0_Init();
  21. }
  22. /**
  23. * @brief 红外遥控获取收到数据帧标志位
  24. * @param 无
  25. * @retval 是否收到数据帧,1为收到,0为未收到
  26. */
  27. unsigned char IR_GetDataFlag(void)
  28. {
  29. if(IR_DataFlag)
  30. {
  31. IR_DataFlag=0;
  32. return 1;
  33. }
  34. return 0;
  35. }
  36. /**
  37. * @brief 红外遥控获取收到连发帧标志位
  38. * @param 无
  39. * @retval 是否收到连发帧,1为收到,0为未收到
  40. */
  41. unsigned char IR_GetRepeatFlag(void)
  42. {
  43. if(IR_RepeatFlag)
  44. {
  45. IR_RepeatFlag=0;
  46. return 1;
  47. }
  48. return 0;
  49. }
  50. /**
  51. * @brief 红外遥控获取收到的地址数据
  52. * @param 无
  53. * @retval 收到的地址数据
  54. */
  55. unsigned char IR_GetAddress(void)
  56. {
  57. return IR_Address;
  58. }
  59. /**
  60. * @brief 红外遥控获取收到的命令数据
  61. * @param 无
  62. * @retval 收到的命令数据
  63. */
  64. unsigned char IR_GetCommand(void)
  65. {
  66. return IR_Command;
  67. }
  68. //外部中断0中断函数,下降沿触发执行
  69. void Int0_Routine(void) interrupt 0
  70. {
  71. if(IR_State==0) //状态0,空闲状态
  72. {
  73. Timer0_SetCounter(0); //定时计数器清0
  74. Timer0_Run(1); //定时器启动
  75. IR_State=1; //置状态为1
  76. }
  77. else if(IR_State==1) //状态1,等待Start信号或Repeat信号
  78. {
  79. IR_Time=Timer0_GetCounter(); //获取上一次中断到此次中断的时间
  80. Timer0_SetCounter(0); //定时计数器清0
  81. //如果计时为13.5ms,则接收到了Start信号(判定值在12MHz晶振下为13500,在11.0592MHz晶振下为12442)
  82. if(IR_Time>13500-500 && IR_Time<13500+500)
  83. {
  84. IR_State=2; //置状态为2
  85. }
  86. //如果计时为11.25ms,则接收到了Repeat信号(判定值在12MHz晶振下为11250,在11.0592MHz晶振下为10368)
  87. else if(IR_Time>11250-500 && IR_Time<11250+500)
  88. {
  89. IR_RepeatFlag=1; //置收到连发帧标志位为1
  90. Timer0_Run(0); //定时器停止
  91. IR_State=0; //置状态为0
  92. }
  93. else //接收出错
  94. {
  95. IR_State=1; //置状态为1
  96. }
  97. }
  98. else if(IR_State==2) //状态2,接收数据
  99. {
  100. IR_Time=Timer0_GetCounter(); //获取上一次中断到此次中断的时间
  101. Timer0_SetCounter(0); //定时计数器清0
  102. //如果计时为1120us,则接收到了数据0(判定值在12MHz晶振下为1120,在11.0592MHz晶振下为1032)
  103. if(IR_Time>1120-500 && IR_Time<1120+500)
  104. {
  105. IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8)); //数据对应位清0
  106. IR_pData++; //数据位置指针自增
  107. }
  108. //如果计时为2250us,则接收到了数据1(判定值在12MHz晶振下为2250,在11.0592MHz晶振下为2074)
  109. else if(IR_Time>2250-500 && IR_Time<2250+500)
  110. {
  111. IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8)); //数据对应位置1
  112. IR_pData++; //数据位置指针自增
  113. }
  114. else //接收出错
  115. {
  116. IR_pData=0; //数据位置指针清0
  117. IR_State=1; //置状态为1
  118. }
  119. if(IR_pData>=32) //如果接收到了32位数据
  120. {
  121. IR_pData=0; //数据位置指针清0
  122. if((IR_Data[0]==~IR_Data[1]) && (IR_Data[2]==~IR_Data[3])) //数据验证
  123. {
  124. IR_Address=IR_Data[0]; //转存数据
  125. IR_Command=IR_Data[2];
  126. IR_DataFlag=1; //置收到连发帧标志位为1
  127. }
  128. Timer0_Run(0); //定时器停止
  129. IR_State=0; //置状态为0
  130. }
  131. }
  132. }

IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8));在这个代码中"/"和"%",一个控制了数组的索引变化(每一位的置0都对应了相应的元素),一个限制了左移的范围在0~7。

声明:以上便是一个51萌新在学习B站江科大51单片机的过程中发现的操作符比较奇特的用法(可能很常见,但不喜勿喷),也请各位大佬在留言区给出更多的奇特的操作符使用方法,如有错误也请指出,我会及时改正。

注:本文转载自blog.csdn.net的瑜先森的文章"https://blog.csdn.net/2401_87412882/article/details/144408599"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top