我们继续在前面加电路
这里,多了一个前级电路,前级电路提供两个信号,一个地址,一个数据。在地址信号通路上,有一个电路模块,判断地址是不是0x77777777,是的话,使能信号为1,这样数据不就直接传递给寄存器了。不是的话,使能信号为0。寄存器的输出不变。这样,通过地址和数据就能改变led了。
这里0x77777777是不是很熟悉,这不就是之前定义的寄存器A的地址吗?原来,地址就是用来判断是否产生使能信号的,使能才能使数据能够输入到寄存器中。
到了这里,是不是对软件控制硬件有些眉目了。别着急,我们继续往下走。
既然前级电路只需要提供地址和数据,并且地址是0x77777777,就可以控制寄存器A的值了,而控制了寄存器A,就控制led。那么我们将前级电路换成一个32位CPU。
我们知道,CPU是可以产生3种总线信号的,一种地址总线,一种数据总线,一种控制总线。控制总线我们这里用不到。将地址总线接到地址线上,将数据总线接到数据线上。就是以下的电路:
如果我们让CPU产生地址为0x77777777,然后发出想要写入寄存器A的数据B,那么数据B不就被写入到寄存器A中,这样,不就控制led了。这里知道为什么地址要32位的把,因为CPU是32位的,地址就是32位宽的。之所以定义32位的地址,目的是为了和CPU的地址总线32位宽兼容。
到这里,是不是有豁然开朗的感觉了。我们再继续。
我们知道CPU是要取机器码然后执行的。如果刚好某条机器码,能让地址总线上产生0x77777777,数据总线上产生数据B。那么结果,数据B不就被写入到寄存器A中了。假设CPU是32位risc的CPU,机器码就是32位。那么该机器码应该是如下:
1001 0000 1110 0000 1011 1010 1111 0010(假设)
既然这是一条机器码,那么就应该有一个汇编指令与之对应,假设是
str r0, r1
我们预先将地址0x77777777写入到r0中,数据B写入到r1中,那么上面一条语句执行后,不就将数据B写入到寄存器A中了,不就控制led了。这样不就实现了软件控制了硬件了。既然汇编代码可以控制硬件了,那么高级语言同样也是控制硬件的,只要编译后的汇编代码是以上代码就行了。
整体控制硬件的代码就是
ldr r0, = 0x77777777
ldr r1, =B
str [r0], r1
对应的C代码就是
(*(volatile unsigned long *)0x77777777) = B;
使用指针操作,往0x77777777地址的寄存器写入数据B,加入volatile关键字,是防止编译器对操作进行优化。
通过上面的过程,是不是也可以理解,为什么在嵌入式底层驱动开发中,基本都是用C语言,而不用其他高级语言,比如JAVA等。因为这些高级语言没有指针,你就不能控制寄存器,不能控制寄存器了,你当然就控制不了硬件了。C++也很少用,因为底层驱动开发需要高效率代码,不能太复杂,而C++在这方面,比不过C语言。
以上是写入的过程了,如果想要知道led的状态呢?通过读取寄存器A的值,不就知道led的状态了。原理是一样的。只不过数据线要变成两根,一个是负责写,一根负责读。电路图如下:
因为只考虑了一个寄存器,当不选中寄存器A时,读取的数据为全0。
通过,上面的讲述,对软件控制硬件有没有了解一些了。软件控制硬件,本质上,就是通过写代码去修改或读取硬件对应的寄存器的值。这样,就相当于间接的控制了硬件。而硬件的寄存器对于一个处理器来说,都是固定的,都预先定义好了地址。所以在看ARM的数据手册中,可以看到很多寄存器的地址。这些地址的作用,也就是能够让你在写程序的时候,能够正确的往这些寄存器里面写入或读取正确的值,从而控制硬件。
CPU对外看到的都是寄存器,所以硬件设计的时候,就要对硬件的功能设置几个寄存器,然后对这几个寄存器分别定义几个地址,这样CPU才可以去控制这几个寄存器,也就能控制硬件了。定义的寄存器地址位宽是和CPU的地址线位宽是有关系的,如果是一个8位的CPU,也就是经典的C51,地址的宽度就是8位,所以你可以在头文件reg51.c中看到使用sfr定义的地址位宽是8位。在STM32中,CPU是32位的,所以地址的宽度就是32位的,所以你看到STM32数据手册中,寄存器的地址都是32位的,而且是4字节对齐的。
以上,CPU只是控制了一个硬件,led,但是我们知道,CPU是可以控制很多硬件的,那这又是怎么实现的了?这个就得谈谈片上互联总线了。