直接操作寄存器是危险的编程方式

  cheney

    直接操作寄存器可以写出高效、简洁的代码,但是不小心也容易产生大Bug。以下是我的经历,记录在案,以备以后反省。

    ##注意寄存器初始值

    例如,STM32对内部高速 RC 振荡器可以有两个微调的寄存器 HIS CAL 和 HIS TRIM 。HIS CAL 的8位是在启动时硬件自动加载的,我猜测它的值与温度是有关的,这个部分是只读的,所以随便写入数据是不会造成错误的。HIS TRIM 的值是供用户自己设置的,危险处就在这里:

    如果你认为这个值不设置默认为0,那不定什么时候就会无意中埋下一个超级隐藏的BUG。RCC_CR 寄存器,复位值(初始化的值)读取为 0000 0083h,也就是说默认这个值是 8 的,如果不小心被刷为 0,那么内部时钟就只有 7.5M 左右了,之后串口波特率什么的,调死也调不出来,还找不到原因。。。 我在设置系统时钟,配置锁相环倍频的时候,就这样写过一句:

    RCC->CR |= 0<<24; //失能PLL(哈哈,一个0挪24位能有什么用呢!!)
    RCC-&gt;CR&amp;=(~(1&lt;&lt;24)); //失能PLL (正确写法)

    同样,常用而且有初始值的例子就是 GPIOx_CRL 和 GPIOx_CRH 。他们的初始值都为:4444 4444h,也就是每个IO口都为浮空输入模式。当你想把某一个IO口变为上下拉输出模式时

    这样写是错的: GPIOA->CRL |= 0x300;//这样会配置为开漏输出模式
    应该这样写: GPIOA->CRL |= 0xFFFFF0FF; GPIOA->CRL |= 0x300; //这样才是推挽输出模式

    ##搞清楚 BSRR 和 BRR

    BSRR和BRR这两寄存器使用很频繁,用他们间接操作ODR寄存器有一个很明显的优点:不容易影响其他位。因为对它们写1才会改变对应位的值,对它们写0,不会有任何值被改变。于是就可以很方便的采用直接赋值的方法写入,而不需要 " 读-改-写 "。 例如:GPIOA->BSRR = 0xE00; 等同于: GPIOA->ODR |= 0xE00;

    当然,方便的同时,混用 BSRR 和 BRR 或者想通过置 0 来清除某一位之类的 " 笔误" 的带来的麻烦也不少。

    ##总结
    用库函数编写的程序看起来就像一篇洋洋洒洒的英文的说明文,它把每个部分都给描述一遍就可以正常工作了,至于到底是怎样工作的,那就不知道了。用直接操作寄存器的方式写程序,看起来精炼很多,对于每一个功能的实现都对应详细的过程,能都更好的理解芯片的工作,如果避免了人为的操作失误,让编程变成指尖的舞蹈,让程序变成人与芯片直接交流的桥梁。