从一无所有学习stm32
我在想很多学习stm32的,和我一样是学生,当时问他为什么学习stm32他也不知道,我们所知道的就是各个论坛讨论stm32的很多,而我们很多人之所以学习stm32是很多的淘宝卖家做了大量的图片文字宣传,于是我们经不住诱惑就买了板子,然后我们就开始了我们的学习之旅。
在淘宝卖家的眼里有着齐全的入门资料是板子的最大的卖点,于是当我们拿到开发板的时候,我们可以什么都不用做,直接使用已经建立好的工程模板,或者我们想学习下的话就按照他们的教程拷贝几个文件然后添加下,然后我们就以为我们的stm32入门了,心中暗喜stm32不过如此,哈哈!其实这就是曾经的我,但是随着慢慢的学习一方面我们失去了兴趣,感觉这玩意太乏味,另外一方面心中的恐惧随之而来,我们感觉我们永远不能和别人说自己熟悉stm32,因为脱离了网络的资料我们什么都做不了,这是我们最害怕的事情。
今天我就就像在这里记录下自己一无所有建立stm32工程的过程,是我自己的一个探索过程,同时也是大家互相交流的过程。
第一步:当然是新建一个工程我把它命名为small(这个随便你,青菜萝卜各有所爱……)
第二部:就是选择芯片的型号了,这个按照每个人手上的板子的不同就选择不同的芯片型号。
选择完了之后按确认,然后会出来一个对话框,是问你要不要添加启动文件的,这个简单我们都是白手起家了,别人好不容易送你点东西我们当然照单全收,直接点是啊!哈哈。。。。。
点完是后我们的工程就是差不多建立好了,大家可以看到里面就一个代码文件,是以.s结尾的,是一个启动文件里面的代码全是汇编的,看了有点晕死。。。。,以后再说吧这个。。。。
要不我们编译了看看结果?
一串鸟文的错误,看不怎么懂,但是好像是说什么没有main函数。想想也是啊,自己确实没写main函数,要不我们自己建立一个.c文件,然后写一个main函数?说做就做。。。。。
直接点击file下面的新建图标,然后写一个名字保存,注意别忘保存好之后在工程里面“add files to group”,然后我们再自己写一个空的main函数,大家看看我做的对不对?
这下可以编译了吧?
编译有警告说什么main函数的返回值必须是int。。。。不知道为什么?
百度看了下好像是编译器和c语言标准规范的问题,没办法那就修改下main函数的返回值吧,把前面的main前面的void变成int就好了。
编译通过了。。。
要不我们接下来设置工程属性看看?这个是很多教程中有的,我不想多少,大家自己找找吧。。。。
你看都可以仿真,哈哈那就说明系统运行起来了。。。。但是我们什么都看不到,接下来我们的任务就是想办法点亮一个led灯。
首先我们要控制灯的话就要操作寄存器,还记得我们在51里面要操作P0口吗?是用P0=0x00,这里面简单的说下51里面操作IO口的原理,我们看到这里有一个P0,这个P0是哪里来的?显然不像是int一样是是c语言本身自带的,也不是某一个变量是我们自己定的,其实这个是在reg52.h里面定义的,有图有真相。
可以看到在我们一直使用reg52.h里面他做了这样一件事情,就是把我们的真实物理地址是0x90的这个P1寄存器和P1这个代名词相互联系了,其实P1只是一个代名词,假如我在reg52.h里面修改 把sfr P1 = 0x90;修改为sfr XX = 0x90;这样也是可以用的,只不过下一次你要对P1口进行操作的时候要写XX=0x00;了,所以为了好记我们就把名字取成P1,现在我们知道了51的原理,我们可以依葫芦画瓢来操作stm32的GPIO,先不管我们要操作什么寄存器,我们要接解决的第一个问题怎么使用c语言操作单片机中知道绝对物理地址的寄存器,比如我们通过查资料RM0008的179页知道了关于IO操作的一个寄存器GPIOA_CRL的绝对地址是0x40010800+0x00(其中0x40010800是起始地址,0x00是偏移地址),接下来我们怎么操作他呢?用 sfr?好像sfr是51汇编特有的指令,在arm里面没有。。。这时候我们是否想到c语言的一个和地址紧密相关的内容----指针,我们在上课的时候知道指针的本质就是地址,这样我们是否可以通过它来把实际的物理地址和c语言变量建立关系呢?首先我们要把绝地地址变为指针变量 ,肯定是强制类型转换了(int*(0x40010800+0x00))通过这一步我们已经有一个int类型的指针,这个指针指向的地址就是我们GPIOA_CRL的绝对地址(0x40010800+0x00),有了指针之后我们要取变量,那么很简单只要一个简单的*取变量运算符就可以了(int*(0x40010800+0x00)),这下变量有了那么我们是不是需要给变量取一个名字啊?
那就这样#define GPIOA_CRL *((int*)(0x40010800+0x00)) 好了完成了我们终于可以再c语言的环境中操作我们的寄存器了,接下来我们只要给GPIOA_CRL 这个变量赋值就是我们在给GPIOA_CRL 这个寄存器赋值,先休息下再说。。。。。
这篇文章其实我是一边写代码做测试一边写的,就在上面我想要控制IO的时候花了好长的时间,就是因为自己的不仔细吧!犯了几个小错误。先看我的最终代码。
代码非常的简单,但是为了这几行代码花了我好久的时间,我已经在代码后面写了注释了,具体的怎么来的让我婉婉道来,首先是上面那几行的define的机构我在上面已经介绍过了,具体的地址是怎么知道的?下面我来简单的说下,在stm32的
RM0008
Reference manual
STM32F101xx, STM32F102xx, STM32F103xx, STM32F105xx
and STM32F107xx advanced ARM-based 32-bit MCUs
这个开发文档中的44页内存映射表,如下图
我截图的只是上半部分,还有下半部分,在下半部分我们可以看到
从这里可以看到和GPIOA相关的寄存器的起始地址是0x40010400所以代码中的一开始的基址是这个,然后我们需要知道不同的寄存器的偏移地址,这个是在后面的跳转的链接可以看到的,跳转到179页,偏移地址的表格
这样的话上面的几个define的计算也就水落石出了。
这里简单的说下自己当时犯的一个错误。我一开始写的#define GPIOA_CRL *((int*)0x40010800+0x00) 是这样的,调试后死活不是我想要的工作效果,后来花了好大的力气才想起来原来是宏定义的括号的问题,大家仔细比对下我在代码中的写的和我上面的写法,发现什么端倪没?#define GPIOA_CRL *((int*)(0x40010800+0x00)) 看到红色的括号没?就是因为一个小小的括号耽误了很长时间。
花了这么长的时间我们终于可以操作IO口了,那怎么操作呢?在学过51的人的眼里那是很简单的,就是直接把我们要IO口输出的值送到数据寄存器中不就好了,但是毕竟他是高级的stm32,所以要复杂点,首先我们要开启GPIOA的端口时钟,这也许大家会疑惑了,怎么还和时钟有关系了。。。。还是看资料吧!
可以看到GPIOA是挂载在AHB2总线上面的,他们都有自己的时钟信号的控制端,这是由stm32的机构决定的,我想之所以这样做,一方面可以降低系统功耗,让工作的模块的时钟使能,不工作的就不使能,其实这里的时钟信号就好比是模块的心脏一样,只有先让他工作了我们才可以去对他进行操作,这就是代码上 RCC_APB2ENR=0x00000004;//开启GPIOA的端口时钟这一句的作用,我尝试过,假如去掉这一句话的话,即使我后面对寄存器赋值了,也是没有作用的。所以这一句很重要,而且与下面的顺序是不好交换的。接下来就是设置GPIO的工作方式什么输入输出 模式之类的,对于只学过51的人来说有点新鲜感觉,如果学过其他高级点单片机的 估计已经习以为常了,就那么回事,具体的寄存器的每一位我就多说了,直接看stm32f10XXXX参考手册的113页上满写的很清楚了。
这里再顺便解释下下我看到的一个现象,先看文档
从这里面我们知道任何IO在复位之后都处于浮空输入状态,这是PDF上说的通过我的代码大家看到我只是改变GPIOA0这位的状态所以其他IO口应该还是浮空输入的模式,但是实际上是的吗?
看仿真截图:
大家可以看到其他几个口还是听话的就是PA12 PA13 PA14 PA15不怎么对劲,怎么回事呢?一开始我也疑惑,后来突然想起来了,这不是我们仿真用的jtag口吗?这样的话就对了,于是为了验证我的想法,我查看jtag用的其他IO口的情况,都不是默认的浮空输入模式,这样的话就应该是这个原因了。(提醒大家一下以后设计硬件的时候尽量避免使用jtag口,如果实在避免不了的话在设计程序的时候就要注意关闭jtag模式释放那几个IO口,在此做一个友情提醒因为被坑过几次。。。)
代码写完了,然后编译下载,不出意外的话就可以看到PA0上面接的led灯亮了,这是必然的结果,这一次入门教程也就差不多了。。。,谢谢!
相关代码的下载链接是:https://files.cnblogs.com/51mcu/small_9.20.zip
如果有什么错误的话欢迎批评指正!