STC8H8K64U 学习笔记 - 位运算

STC8H8K64U 学习笔记 -位运算

环境说明

该内容仅针对我自己学习的开发板做的笔记,在实际开发中需要针对目标电路板的原理图进行针对性研究。

  • 芯片:STC8H8K64U
  • 烧录软件:stc-isp-v6.92G
  • 编码工具:天问

引脚说明

  • P0_0:蜂鸣器,按频率发音,1:高,0:低
  • P0_1:电动马达,0:停,1:动
  • P5_3:小蓝灯,0:灭,1:亮
  • P2_7:1 号 LED 灯,0:亮,1:灭
  • P2_6:2 号 LED 灯,0:亮,1:灭
  • P1_5:3 号 LED 灯,0:亮,1:灭
  • P1_4:4 号 LED 灯,0:亮,1:灭
  • P2_3:5 号 LED 灯,0:亮,1:灭
  • P2_2:6 号 LED 灯,0:亮,1:灭
  • P2_1:7 号 LED 灯,0:亮,1:灭
  • P2_0:8 号 LED 灯,0:亮,1:灭
  • P5_1:1 号按键,0:按下,1:弹起
  • P5_2:2 号按键,0:按下,1:弹起
  • P5_3:3 号按键,0:按下,1:弹起
  • P5_4:4 号按键,0:按下,1:弹起
  • P3_4:矩阵键盘第 1 行引脚
  • P3_5:矩阵键盘第 2 行引脚
  • P4_0:矩阵键盘第 3 行引脚
  • P4_1:矩阵键盘第 4 行引脚
  • P0_3:矩阵键盘第 1 列引脚
  • P0_6:矩阵键盘第 2 列引脚
  • P0_7:矩阵键盘第 3 列引脚
  • P1_7:矩阵键盘第 4 列引脚

位运算

对位运算进行小结

分析:设 n 为二进制数值的某一个位,在 8 位寄存器中,从高到低依次是:7654 3210

如果希望通过一个变量的二进制位置来作为按钮是否按下的标志位,

uint8 pressed = 0x00;
// 变量 pressed 的低四位表示按键的按下状态, 0 表示未按下, 1表示已按下
/  7  6  5  4     3  2  1  0          --> n 的取值
// 0  0  0  0     0  0  0  0          --> 二进制值
//               K4 K3 K2 K1          --> 针对按钮

若需要检测 n = 0 所在的位置的值是零还是壹,可以将 pressed 取反后与 0x01 进行与运算,如下所示

Let's say pressed is 0000 1010
         ~pressed is 1111 0101
             0x01 is 0000 0001 &
       The result is 0000 0001

Let's say pressed is 0000 1011
         ~pressed is 1111 0100
             0x01 is 0000 0001 &
       The result is 0000 0000

若需要检测 n = 1 所在的位置的值是零还是壹,可以将 pressed 取反后与 0x02 进行与运算,如下所示

Let's say pressed is 0000 1000
         ~pressed is 1111 0111
             0x02 is 0000 0010 &
       The result is 0000 0010

Let's say pressed is 0000 1011
         ~pressed is 1111 0100
             0x01 is 0000 0010 &
       The result is 0000 0000

类推:

  • 若检测第 1 位的值,即 n=0,将 pressed 取反后与 0x01 进行与运算
    • 若运算结果为 0x01, 则判定该位的值为 0
    • 若运算结果为 0x00, 则判定该位的值为 1
  • 若检测第 2 位的值,即 n=1,将 pressed 取反后与 0x02 进行与运算
    • 若运算结果为 0x02, 则判定该位的值为 0
    • 若运算结果为 0x00, 则判定该位的值为 1
  • 若检测第 3 位的值,即 n=2,将 pressed 取反后与 0x04 进行与运算
    • 若运算结果为 0x04, 则判定该位的值为 0
    • 若运算结果为 0x00, 则判定该位的值为 1
  • 若检测第 4 位的值,即 n=3,将 pressed 取反后与 0x08 进行与运算
    • 若运算结果为 0x08, 则判定该位的值为 0
    • 若运算结果为 0x00, 则判定该位的值为 1

不难发现,进行与运算的对象和 n 是有一定的关系的:

1<<0=0x1
1<<1=0x2
1<<2=0x4
1<<3=0x8
1<<4=0x10
1<<5=0x20
1<<6=0x40
1<<7=0x80

综上所述,总结如下:

设 n 为二进制数值的某一个位,在 8 位寄存器中,从高到低依次是:7654 3210

希望通过一个变量 pressed 的二进制位置来作为按钮是否按下的标志位,有如下结论:

若检测 n 位置的的值,将 pressed 取反后与 1<<n 进行& 运算

  • 若运算结果为 1<<n, 则判定该位的值为 0
  • 若运算结果为 0x00, 则判定该位的值为 1

实例

下面是一段来自天问编程工具中的代码。

针对的是开发板上的四个独立按键,它们对应的四个引脚分别是:P5_1、P5_2、P5_3 和 P5_4。

需求是,当按下按键后,向串口发送信息,报告此时按键的状态,并用一个标志位来记录其按下的状态。

#include <STC8HX.h>
uint32 sys_clk = 24000000;//设置PWM、定时器、串口、EEPROM频率参数
#include "lib/twen_board.h"
#include "lib/delay.h"
#include "lib/UART.h"
#include <stdio.h>

#define K1 P5_1
#define K2 P5_2
#define K3 P5_3
#define K4 P5_4

uint8 pressed = 0x00;
// 变量 pressed 的低四位表示按键的按下状态, 0 表示未按下, 1表示已按下
// 0  0  0  0     0  0  0  0
//               K4 K3 K2 K1

void setup() {
  twen_board_init(); //天问51初始化
  uart_init(UART_1, UART1_RX_P30, UART1_TX_P31, 115200, TIM_1);//初始化串口
  P5M1&=~0x02; P5M0&=~0x02; // P5_1 设置为双向IO
  P5M1&=~0x04; P5M0&=~0x04; // P5_2 设置为双向IO
  P5M1&=~0x08; P5M0&=~0x08; // P5_3 设置为双向IO
  P5M1&=~0x80; P5M0&=~0x80; // P5_4 设置为双向IO
}

void loop() {
  // 0000 0001
  if(K1 == 0 && ((0x01 & ~pressed) == 0x01)) {
    pressed|=0x01;
    uart_putstr(UART_1, "Key1 按下");
  } else if(K1 == 1 && ((0x01 &~ pressed) == 0x00)) {
    pressed&=~0x01;
    uart_putstr(UART_1, "Key1 弹起");
  }
  
  // 0000 0010
  if(K2 == 0 && ((0x02 & ~pressed) == 0x02)) {
    pressed|=0x02;
    uart_putstr(UART_1, "Key2 按下");
  } else if(K2 == 1 && ((0x02 &~ pressed) == 0x00)) {
    pressed&=~0x02;
    uart_putstr(UART_1, "Key2 弹起");
  }
  
  // 0000 0100
  if(K3 == 0 && ((0x04 & ~pressed) == 0x04)) {
    pressed|=0x04;
    uart_putstr(UART_1, "Key3 按下");
  } else if(K3 == 1 && ((0x04 &~ pressed) == 0x00)) {
    pressed&=~0x04;
    uart_putstr(UART_1, "Key3 弹起");
  }
  
  // 0000 1000
  if(K4 == 0 && ((0x08 & ~pressed) == 0x08)) {
    pressed|=0x08;
    uart_putstr(UART_1, "Key4 按下");
  } else if(K4 == 1 && ((0x08 &~ pressed) == 0x00)) {
    pressed&=~0x08;
    uart_putstr(UART_1, "Key4 弹起");
  }
  
  delay(200);
}

void main(void) {
  setup();
  while(1){
    loop();
  }
}

运用下刚刚推导的结论,拿案例中的第三个按钮的判断语句来修改,如下所示:

if(K3 == 0 && (((1<<2) & ~pressed) == (1<<2))) {
    pressed|=(1<<2);
    uart_putstr(UART_1, "Key3 按下");
} else if(K3 == 1 && (((1<<2) &~ pressed) == 0x00)) {
    pressed&=~(1<<2);
    uart_putstr(UART_1, "Key3 弹起");
}

进一步改进,声明一个 8 位的变量 flag 来存储位移运算的结果。所以 loop 函数改造如下:


uint8 flag = 0x00;// 用来临时存储位移运算结果

void loop() {
  // 0000 0001
  flag = 1<<0;
  if(K1 == 0 && ((flag & ~pressed) == flag)) {
    pressed|=flag;
    uart_putstr(UART_1, "Key1 按下");
  } else if(K1 == 1 && ((flag &~ pressed) == 0x00)) {
    pressed&=~flag;
    uart_putstr(UART_1, "Key1 弹起");
  }
  
  // 0000 0010
  flag = 1<<1;
  if(K2 == 0 && ((flag & ~pressed) == flag)) {
    pressed|=flag;
    uart_putstr(UART_1, "Key2 按下");
  } else if(K2 == 1 && ((flag &~ pressed) == 0x00)) {
    pressed&=~flag;
    uart_putstr(UART_1, "Key2 弹起");
  }
  
  // 0000 0100
  flag = 1<<2;
  if(K3 == 0 && ((flag & ~pressed) == flag)) {
    pressed|=flag;
    uart_putstr(UART_1, "Key3 按下");
  } else if(K3 == 1 && ((0x04 &~ pressed) == 0x00)) {
    pressed&=~flag;
    uart_putstr(UART_1, "Key3 弹起");
  }
  
  // 0000 1000
  flag = 1<<3;
  if(K4 == 0 && ((flag & ~pressed) == flag)) {
    pressed|=flag;
    uart_putstr(UART_1, "Key4 按下");
  } else if(K4 == 1 && ((flag &~ pressed) == 0x00)) {
    pressed&=~flag;
    uart_putstr(UART_1, "Key4 弹起");
  }
  
  delay(200);
}

其实,该案例如果能用循环将 四个 开关置入循环体来指代,会使得代码简化非常多。这个问题先搁置,等待后续学习。

最后,附加四个位运算的公式:

uint8 variable = 0x00;
uint8 n = 1;
// 将第 n 位置为 1
variable = variable | (1 << n);
// 将第 n 位置为 0
variable = variable & ~(1 << n);
// 将第 n 位置取反
variable = variable ^ (1 << n);
// 取出第 n 位的值
variable = (variable & (1 << n)) >> n;
  • 14
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值