| 下一篇 |
|---|
| 如何判断一个浮点型运算结果是否为整数 |
目 录
- 单变量承担多个标志位及计数功能
- 1)判断某个位是 1 还是 0
- 2)将某一位置 1 或置 0
- 3)低位计数,高位判断
- 4)应用案例
- ① 基础案例
- ② 进阶案例
- ③ 补充案例
单变量承担多个标志位及计数功能
使用一个变量,来承担多个标志位的功能,即每一位都可以充当标志位,也可以包含计数功能。
好处:节省空间,让代码看起来高级,集成度会变高。
缺点:代码可读性差,不易理解。
定义一个 8 位整型变量uint8_t flag,则有:
1)判断某个位是 1 还是 0
/*判断位0*/if((flag&0x01)==0)// 位0为0if((flag&0x01)==0x01)// 位0为1/*判断位1*/if((flag&0x02)==0)// 位1为0if((flag&0x02)==0x02)// 位1为1/*判断位2*/if((flag&0x04)==0)// 位2为0if((flag&0x04)==0x04)// 位2为1/*判断位3*/if((flag&0x08)==0)// 位3为0if((flag&0x08)==0x08)// 位3为1/*判断位4*/if((flag&0x10)==0)// 位4为0if((flag&0x10)==0x10)// 位4为1/*判断位5*/if((flag&0x20)==0)// 位5为0if((flag&0x20)==0x20)// 位5为1/*判断位6*/if((flag&0x40)==0)// 位6为0if((flag&0x40)==0x40)// 位6为1/*判断位7*/if((flag&0x80)==0)// 位7为0if((flag&0x80)==0x80)// 位7为12)将某一位置 1 或置 0
/*位0置1或置0*/flag|=0X01;// 位0置1flag&=0X7F;// 位0置0,等效于 flag &= ~0X01;/*位1置1或置0*/flag|=0X02;// 位1置1flag&=0XFD;// 位1置0,等效于 flag &= ~0X02;/*位2置1或置0*/flag|=0X04;// 位2置1flag&=0XFB;// 位2置0,等效于 flag &= ~0X04;/*位3置1或置0*/flag|=0X08;// 位3置1flag&=0XF7;// 位3置0,等效于 flag &= ~0X08;/*位4置1或置0*/flag|=0X10;// 位4置1flag&=0XEF;// 位4置0,等效于 flag &= ~0X10;/*位5置1或置0*/flag|=0X20;// 位5置1flag&=0XDF;// 位5置0,等效于 flag &= ~0X20;/*位6置1或置0*/flag|=0X40;// 位6置1flag&=0XBF;// 位6置0,等效于 flag &= ~0X40;/*位7置1或置0*/flag|=0X80;// 位1置1flag&=0X7F;// 位1置0,等效于 flag &= ~0X80;3)低位计数,高位判断
在实际项目中,常将一个变量的高几位用作状态标志位,低几位用作计数值或索引。例如:
- 使用
uint8_t flag:- bit7、bit6:作为布尔标志(如
TX_READY、RX_COMPLETE) - bit5 ~ bit0(共6位):用于计数(范围 0~63)
- bit7、bit6:作为布尔标志(如
此外,如果计数位数不够,可以定义一个 16 位的变量。
4)应用案例
实际工程当中,我们会用位操作符来代替0x01这些十六进制:
0x01=1<<0;0x02=1<<1;0x04=1<<2;0x08=1<<3;0x10=1<<4;0x20=1<<5;0x40=1<<6;0x80=1<<7;还可以进一步将其定义为宏:
#defineVALUE1(1<<7)#defineVALUE2(1<<6)#defineVALUE3(1<<5)① 基础案例
串口通信:
uint8_tuart_status=0;#defineUART_TX_BUSY(1<<7)#defineUART_RX_VALID(1<<6)#defineUART_ERR_FLAG(1<<5)#defineUART_RX_COUNT0x1F// 低5位存接收字节数(最多31字节)// 接收到1个字节uart_status=(uart_status&~UART_RX_COUNT)|((rx_cnt+1)&UART_RX_COUNT);uart_status|=UART_RX_VALID;// 发送中uart_status|=UART_TX_BUSY;// 发送完成uart_status&=~UART_TX_BUSY;② 进阶案例
我们可以进一步将 “判断某个位是1还是0” 和 “将某一位置1或置0” 封装成宏形式的函数(如果编译器报错了,就把 1U 后面的 U 删了):
#defineSET_BIT(var,bit)((var)|=(1U<<(bit)))// 将变量的某一位置1#defineCLR_BIT(var,bit)((var)&=~(1U<<(bit)))// 将变量的某一位置0#defineIS_SET(var,bit)(((var)&(1U<<(bit)))!=0)// 判断变量的某一位是否为1,返回1则是1,返回0则是0则进阶版的串口通讯实例如下:
#include<stdint.h>// ---------- 位操作宏 ------------------------------#defineSET_BIT(var,bit)((var)|=(1U<<(bit)))// 置1#defineCLR_BIT(var,bit)((var)&=~(1U<<(bit)))// 清0#defineIS_SET(var,bit)(((var)&(1U<<(bit)))!=0)// 判断是否为1// ---------- UART 状态位定义 -----------------------#defineUART_TX_BUSY_BIT7// bit7: 发送忙标志#defineUART_RX_VALID_BIT6// bit6: 接收数据有效#defineUART_ERR_FLAG_BIT5// bit5: 错误标志#defineUART_RX_COUNT_MASK0x1F// 低5位(bit4~bit0):接收字节数(0~31)// 状态变量uint8_tuart_status=0;// ---------- 使用示例 ------------------------------// 1. 开始发送数据(置位 TX_BUSY)SET_BIT(uart_status,UART_TX_BUSY_BIT);// 2. 发送完成(清除 TX_BUSY)CLR_BIT(uart_status,UART_TX_BUSY_BIT);// 3. 接收到一个字节(在 RX 中断中调用){uint8_tcurrent_count=uart_status&UART_RX_COUNT_MASK;// 读取当前计数值if(current_count<31){// 计数+1,并更新到低5位(先清空再写入)uart_status=(uart_status&~UART_RX_COUNT_MASK)|(current_count+1);}SET_BIT(uart_status,UART_RX_VALID_BIT);// 标记有新数据}// 4. 主循环中检查是否有有效数据if(IS_SET(uart_status,UART_RX_VALID_BIT)){uint8_trx_bytes=uart_status&UART_RX_COUNT_MASK;// 获取接收字节数// TODO: 处理接收到的 rx_bytes 个字节数据// ...// 处理完毕,清除有效标志(计数不清零,由下次接收覆盖)CLR_BIT(uart_status,UART_RX_VALID_BIT);}// 5. 出现通信错误(如帧错误、溢出等)SET_BIT(uart_status,UART_ERR_FLAG_BIT);// 6. 检查是否有错误(可选)if(IS_SET(uart_status,UART_ERR_FLAG_BIT)){// TODO: 错误处理CLR_BIT(uart_status,UART_ERR_FLAG_BIT);// 清除错误标志}③ 补充案例
下面是正点原子通用定时器输入捕获例程中的一个函数,就使用的这个方式。
/** * @brief 溢出中断服务函数底层回调函数 * @others 用来记录定时器溢出次数,此函数会被HAL_TIM_IRQHandler()调用 */voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){if((TIM5CH1_CAPTURE_STA&0X80)==0)// 还未成功捕获{if(TIM5CH1_CAPTURE_STA&0X40)// 已经捕获到高电平(上升沿)了{if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)// 高电平太长了(计数计满了){TIM5CH1_CAPTURE_STA|=0X80;// 标记成功捕获了一次TIM5CH1_CAPTURE_VAL=0XFFFF;}else// 计数TIM5CH1_CAPTURE_STA++;}}}