在STM32中操作寄存器,一般都是向这些内部外设的寄存器写入一些特定的值来操控这个内部外设,进而操控硬件动作。
即:读写寄存器就是操控硬件。
寄存器的特点是按位进行规划和使用。但是寄存器的读写却是整体32位一起进行的。也就是说你只想修改bit5~bit7是不行的,必须整体32bit全部写入。
因此,寄存器操作要求就是:在设定特定位时不能影响其他位。关于位操作的基本概念可以参考我之前的一篇文章:
【C语言学习笔记】精讲篇1 - 位操作符的基本概念
如何做到在设定特定位时不能影响其他位呢?
答:读——改——写三部曲
操作理念就是:
当我想改变一个寄存器中某些特定位时,我不会直接去给他写,我会先读出寄存器整体原来的值,然后在这个基础上修改我想要修改的特定位,再将修改后的值整体写入寄存器。这样达到的效果是:在不影响其他位原来值的情况下,我关心的位的值已经被修改了。
回顾【C语言学习笔记】精讲篇1 - 位操作符的基本概念
#include
int main(void)
{
unsigned int a = 0x12aaaaa7;
unsigned int b = 0xFFFF00FF;
unsigned int c;
c = a & b;
printf("a & b = 0x%x.\n", c); //a & b = 0x12aa00a7.
return 0;
}
#include
int main(void)
{
//把一个寄存器值的bit4~bit7置1,其他位不变
unsigned int a = 0x123d0cd7;
unsigned int b = 0xf0;
unsigned int c;
c = a | b;
printf("a & b = 0x%x.\n", c);
return 0;
}
#include
int main(void)
{
// 把一个寄存器值的bit4~bit7取反,其他位不变
unsigned int a = 0x123d0c37;
unsigned int b = 0xf0;
unsigned int c;
c = a ^ b;
printf("a & b = 0x%x.\n", c);
return 0;
}
从上节可知,对寄存器特定位进行置1或者清0或者取反,关键性的难点在于要事先构建一个特别的数,这个数和原来的值进行位与、位或、位异或等操作,即可达到我们对寄存器操作的要求。
#include
int main(void)
{
unsigned int a;
// 下面表达式含义:位或说明这个数字由2部分组成,第一部分中左移3位说明第一部分从bit3开始,
// 第一部分数字为0x1f说明这部分有5位,所以第一部分其实就是bit3到bit7;
// 第二部分的解读方法同样的,可知第二部分其实就是bit23到bit25;
// 所以两部分结合起来,这个数的特点就是:bit3~bit7和bit23~bit25为1,其余位全部为0.
a = ((0x1f<<3) | (0x7<<23));
printf("a = 0x%x.\n", a); // 工具算出来:0x0380_00f8 程序运行:0x38000f8
return 0;
}
#include
int main(void)
{
//聪明的方法
unsigned int a;
a = ~(0x7f<<4); // 0xfffff80f
printf("a = 0x%x.\n", a);
/*
// 笨方法
unsigned int a;
a = (0xf<<0)|(0x1fffff<<11); // 0xfffff80f
printf("a = 0x%x.\n", a);
*/
return 0;
}
a = a | (1<<3) //或者 a |= (1<<3)
a = a | (0b11111<<3) 或者 a |= (0x1f<<3);
a = a & (~(1<<15)); 或者 a &= (~(1<<15));
a = a & (~(0x1ff<<15)); 或者 a &= (~(0x1ff<<15));
a &= (0x3f<<3);
a >>= 3;
a &= ~(0x7ff<<7);
a |= (937<<7);
#include
int main(void)
{
unsigned int a = 0xc30288f8; // 0xc34648f8
//第一步,先读出原来bit7~bit17的值
unsigned int tmp = 0;
tmp = a & (0x3ff<<7);
//printf("befor shift, tmp = 0x%x.\n", tmp);
tmp >>= 7;
//printf("after shift, tmp = 0x%x.\n", tmp);
//第二步,给这个值加17
tmp += 0;
//第三步,将a的bit7~bit17清零
a &= ~(0x3ff<<7);
//第四步,将第二步算出来的值写入bit7~bit17
a |= tmp<<7;
printf("a = 0x%x.\n", a);
return 0;
}
#include
int main(void)
{
// 第一种解法:直接double第1题。
unsigned int a = 0xc30288f8;
// bit7~bit17赋值937
a &= ~(0x3ff<<7); // bit7~ bit17清零
a |= 937<<7;
// bit21~bit25赋值17
a &= ~(0x1f<<21); // bit21~bit25清零
a |= 17<<21;
printf("a = 0x%x.\n", a); // 0xc223d4f8
return 0;
}
第二种解法:更优的解法
#include
int main(void)
{
// 第二种解法
unsigned int a = 0xc30288f8;
a &= ~((0x3ff<<7) | (0x1f<<21)); // bit7~bit17和bit21~bit25全清零
a |= ((937<<7) | (17<<21)); // 937和17全部赋值
printf("a = 0x%x.\n", a); // 0xc223d4f8
return 0;
}
//用宏定义将32位数x的第n位(右边起算,也就是bit0算第1位)置位
#define SET_NTH_BIT(x, n) (x | ((1U)<<(n-1)))
// 用宏定义将32位数x的第n位(右边起算,也就是bit0算第1位)清零
#define CLEAR_NTH_BIT(x, n) (x & ~((1U)<<(n-1)))
例如:变量0x88, 也就是10001000b,若截取第2~4位,则值为:100b = 4
#define GETBITS(x, n, m) ((x & ~(~(0U)<<(m-n+1))<<(n-1)) >> (n-1))
分析:这个题目相当于是要把x的bit(n-1)到bit(m-1)取出来
复杂宏怎么分析:
第一步,先分清楚这个复杂宏分为几部分:2部分
(x & ~(~(0U)<<(m-n+1))<<(n-1)) >> (n-1)
分析为什么要>>(n-1),将其右移得到结果
第二步,继续解析剩下的:又分为2部分
x & ~(~(0U)<<(m-n+1))<<(n-1)
分析为什么要&,相当于清零
第三步,继续分析剩下的:
~ (~(0U)<<(m-n+1)) << (n-1)
这个分析时要搞清楚第2坨到底应该先左边取反再右边<<,还是先右边<<再左边取反。~的优先级比<<要高,因此
~(~(0U)<<(m-n+1)) << (n-1)
再以此类推分析剩余的部分。
位操作符在嵌入式中的应用至此完结!5.2部分可能理解起来有些困难,可以多写写位操作相关的代码,再分析这个复杂的宏定义。
欢迎大家关注我的微信公众号:redeemer奇
一起交流!一起努力!