基于S3C6410的ARM11学习(九) 点亮led
0赞之前已经对S3C6410的核心初始化完成了,但是这个也只是完成了,但是程序到底对不对了,还需要验证。最简单的验证方法是什么了,就是点亮led。如果在核心初始化完成后,我们写程序去点亮了led,就说明初始化代码是没有问题。
下面就要来点亮led了。这个时候就和开发板的原理图有关系了。我用的是OK6410的开发板,打开对应原理图。
找到4个灯,是共阳接法的。所以要点亮对应的灯,就要使led的负端为低电平。在看看各个led负端端口对应的S3C6410的哪个IO。
从核心板的原理图可以看出,4个LED是接在GPM0-3这四个管脚上的。
有了这个信息,我们只要去控制这四个管脚输出不同的电平,就能实现led的亮灭了。
打开6410的手册。直接看GPIO这一章。
表中列出了所有的GPIO,总共分为17组,从A-Q。每一组有不同的管脚数。控制IO功能是通过GPxCON寄存器来控制的。控制IO管脚上数据是通过GPxDAT来控制的。我们这里就以GPM为例说明。
上图说明GPM的IO操作有3个寄存器。
先看第一个寄存器,GPMCON
GPM组共有6个IO管脚。所以这个寄存器中,分成了6组,每一组控制对应的IO的功能。这里,我们是要使GPM0-3为输出。所以低16位就要设置为16’b0001_0001_0001_0001。
这样,就将GPM0-3设置为输出了。
在看第二个寄存器,GPMDAT。
手册也说得比较清楚,当IO为输入的时候,记录的是IO脚上的电平。当作为输出的时候,记录的是IO上输出的电平。所以当我们要让GPM0-3都输出低电平的话,那么这个寄存器的后4位就要写入4’b0000。
第三个寄存器,GPMPUD
这个是设置IO管脚上下拉电阻的。
以上三个寄存器就是IO的基本寄存器了。每一组的IO都有上诉的3个寄存器。
下面我们就要来写程序了。这里,我写的是流水灯,初始,4个灯为亮灭亮灭,每隔一段时间,灯的状态就翻转一次,
流程:
1、设置对应IO为输出
2、设置IO输出值
3、每隔一段时间对IO输出值取反
下面就是对应的程序了,将流水灯写成一个函数,在reset中调用。
1、先定义GPMCOM和GPMDAT地址。
2、将r0的值设置为0x1111。这个值之前分析过,将GPM0-3设置为输出的值
3、将GPMCON地址装载到r1寄存器
4、将r0的值写入到r1寄存器代表的寄存器中,这样就配置GPM0-3为输出
5、将r0的值设置为0x5。这个用来配置GPM0-3输出的值
6、将GPMDAT地址装载到r1寄存器
7、将r0的值写入到r1寄存器代表的寄存器中,这样就配置GPM0-3输出值为4’b0101
8、将lr的值保存在r4寄存器中。因为这个时候lr寄存器保存的是调用light_led函数处的下一条指令。在这个函数中,后面又会调用其他函数,会破坏lr的值,这样就返回不到调用light_led的位置了。所以这样要先备份一下lr的值。
9、定义a1标号,用于后面的跳转,实现无限循环
10、调用delay函数
11、将r0值取反,r0的值就是IO输出的值
12、将r0的值写入到输出寄存器去,就改变了IO的输出值
13、在跳转到a1标号,实现无限循环
14、函数返回
15、定义delay函数,将r2的值设置为600000,这个值随意设,但是不能设置太小,不然没有灯翻转效果。太大也不行,难得等
16、定义delay_1标签,用于在delay函数中跳转
17、将r2的值减1
18、比较r2和10的大小
19、如果r2大于10的话,跳转到delay_1处,说明延时还没有结束,继续执行延时,也就是继续减r2寄存器。否则往下执行
20、delay函数返回
用汇编来写,要麻烦一些,要定义一些标签,用来跳转。
这样,就完成了点亮流水灯的代码了。下面在reset中调用该函数
这样,就完成了整个程序的设计了。将该程序,编译链接下载到开发板中,运行,就会看到led在不停的闪烁了。
对比STM32,操作可就要麻烦一些了。
首先看下系统结构,看看GPIO是挂在什么总线上的。因为之前说过,STM32是有外设门控时钟的。要使用某外设,就必须要打开对应外设的时钟。这是和ARM11很不一样的一点。
GPIO是挂在APB2总线上的,所以我们打开时钟的时候要去打开APB2上对应GPIO时钟。图中可以知道,GPIO是分为7组的。从A-G。
到GPIO章节去看。
功能描述就说明了,对应每个GPIO端口,有哪些寄存器来控制。STM32是有7个寄存器来进行控制。
STM32的GPIO比S3C6410的GPIO要复杂多了,因为GPIO可以配置为8种模式。
下面是GPIO的基本结构
8种模式中,对推挽式输出和推挽式复用输出说明一下。这两个的输出结构都是一样的,使用CMOS的结构。但是不一样的是不同功能所使用模式不一样。推挽式输出是IO端口作为普通输出时的模式,而推挽式复用输出是IO作为特殊功能时作为输出的模式,比如有的IO有串口输出功能,当使用串口输出功能的时候,就要把IO的模式设置为推挽式复用输出。
输入的话,就没有什么好说的,从名字也可以看的出来。注意的是模拟输入,当使用芯片的内部AD时,IO是要设置为模拟输入的。
下面就要看看各个寄存器
1、GPIOx_CRL寄存器
这个是设置GPIO组的低8位的IO功能。每4位对应一个IO。高两位是配置IO的模式。低两个是配置IO作为输入还是输出,以及作为输出的最大速度是多少。
2、GPIOx_CRH寄存器
和GPIOx_CRL寄存器一样的功能,只不过是配置的高8位IO的功能。
3、GPIOx_IDR寄存器
从描述中得到,这个寄存器保存的IO的状态,其实就是IO上的电平。而且这个寄存器只能读。当IO作为输入的时候,我们来读这个寄存器就知道IO上的电平了。
4、GPIOx_ODR寄存器
这个寄存器就是设置IO的输出了。低16位,每一位对应一个IO。当IO作为输出,可以往这个寄存器写值,就实现了IO输出了。
5、GPIOx_BSRR寄存器
这个寄存器,就是对输出值影响的。可以对输出清零,也可以对输出置1.
6、GPIOx_BRR寄存器
看描述就知道功能了
7、GPIOx_LCKR寄存器
STM32对IO加了一个锁的功能,就是可以将IO给锁起来,这样就不能对IO进行改变了。这个功能就是由这个寄存器来设置的。要锁某个IO的话,先将对应IO位的值设置为1,锁定端口配置,然后往16位写入特定的序列,就实现了IO的锁的功能了。在系统复位前,这锁功能一直有。
知道了对应相关的寄存器了,就知道怎么使用STM32的管脚来控制流水灯了。
流程
1、打开对应IO的时钟使能
2、配置CRL或CRH,使IO作为输出,模式就推挽式输出,最大速度是50M。
3、配置ODR寄存器,使寄存器输出我们想要的值
以配置GPIOA0-3为例说明。
首先打开GPIOA时钟。这个在RCC_APB2ENR寄存器中
第2位控制GPIOA的时钟使能。那么就把这一位置1就好了。但是怎么操作这个寄存器了。当然,我们可以向S3C6410一样,使用指针,去指向这个寄存器的地址,然后再对这个地址操作。但是,官方库已经给我们定义好了这些寄存器了,我们只要直接使用就好了。
在官方给的库(V3.5)中,有两个文件夹,一个inc,一个src
inc里面就是一些头文件,src里面就是具体的函数实现。库将每个外设都封装好了一个头文件和一个c文件。里面就定义了各个外设的寄存器,以及一些库函数,使用这些库函数就可以直接操作这些外设寄存器,而不用和底层的寄存器打交道了。因为STM32的寄存器实在是太多了,每次操作都查表的话,那可是很累的。而且直接操作寄存器的话,代码不直观,不知道配置到底是什么作用的。
这里,我们先用寄存器来进行操作。然后再用库函数来进行操作。在对比
寄存器版本:
寄存器版本的话就和inc和src下面的库文件没有多大关系了。直接打开stm32f10x.h,这个是在官方提供的库CMSIS目录下。
这个头文件定义了stm32f10x系列的所有寄存器对应的地址,相当于51单片机的reg51.h。
这里定义RCC的结构体,里面包含了RCC的各个寄存器。我们这里要用的就是其中的APB2ENR寄存器。因为这个寄存器里面有对GPIOA的时钟有效设置。在STM32中,对寄存器定义都是放在对应功能的结构体中去了,因为这些寄存器是严格4字节对齐的,所以只要放在结构体中,然后定义结构体的首地址,那么后面每个寄存器的地址也就都知道了。上面就定义RCC的结构体。这个是管理时钟和复位功能的。
在去找RCC的地址
这样,我们就可以直接访问了。
使用RCC->APB2ENR就可以访问RCC下的APB2ENR寄存器了。之前说,要把这个寄存器的第二位置为1.所以
RCC->APB2ENR = 0x4;
配置GPIO
同样,也是先去找GPIO结构体。
里面就有上面这个结构体。在STM32中,对外设的寄存器定义都是放在结构体中去了,因为这些寄存器是严格4字节对齐的,所以只要放在结构体中,然后定义结构体的首地址可以知道所有寄存器地址。上面就定义GPIO的结构体。
然后再去找GPIOA,因为我们要对这一组GPIO操作。
我从里面把关键的弄出来了
先定义了外设的基地址,然后再定义APB2的基地址,因为GPIO都是挂在APB2总线上的。然后不同的GPIO组的地址就是APB2基地址加上偏移地址就得了。然后再将这个GPIO组的基地址强制转化为GPIO结构体的首地址。这样不就知道了GPIO组的每个寄存器的地址了。
GPIOA0-3是由GPIOA_CRL配置的,所以就去配置这个寄存器。使用GPIOA->CRL对这个寄存器访问。要设置为输出,最大速度50M,模式为推挽式输出。
代码:GPIOA->CRL = 0x3333
这样就配置好了GPIOA0-3作为输出了,然后就要设置输出值了,假设设置为0101,那么就是去控制ODR寄存器。
代码:GPIOA-ODR = 0XA
这样,我们就实现了配置GPIOA0-3作为输出,输出值为0101了。至于点灯的程序,这里就不写了,就一直操作ODR寄存器就行了。
下面说下库函数开发:
有了库,你会发现STM32的开发变得非常简单。
对于GPIO,打开src目录下的stm32f10x_gpio.h这个文件,这个里面定义了关于GPIO操作的库函数操作的参数以及函数。
里面定义了GPIO初始化的一个结构体,当我们要初始化一个GPIO,就往这个结构体里面填值就行了。填的值可不是乱填的。而是在这文件中规定好的。
第一个值,对应下面的各个参数。表示是配置哪一个GPIO,总共16个。可以使用|操作。
第二个值是枚举类型,设置GPIO的速度
第三个值也是枚举类型,设置的IO的模式
下面我们就只要定义一个结构体,往这结构体里面的参数赋值,然后再调用GPIO的初始化函数就搞定了。
代码就是上面这样,怎么样,这比用寄存器开发要简洁很多吧。一看就知道代码功能是什么。就设置GPIOA的0-3管脚作为推挽式输出,速度是50M。完全不用去操作寄存器,因为库函数就帮我们做了这些事了。
GPIO_Init()这个函数是库函数,就是对外设进行初始化,初始化的参数就是填入的结构体的参数。有兴趣的可以去看看这个函数的实现,这个函数就在stm32f10x_gpio.c中。
我们还忘记了一件事了,那就是打开GPIOA的时钟了。这个时钟是受RCC管理的。去看stm32f10x_rcc.h文件。
这里,有一个函数
从这个名字就可以知道这个函数干嘛的,就是打开APB2总线上外设的时钟了。第一个参数是外设的名字,这个也是预先定义好的。第二个是状态,两个状态,ENABLE和DISABLE。
我们要打开GPIOA时钟,所以参数就是RCC_APB2Periph_GPIOA。
所以完整的GPIOA初始化代码就是
初始化搞定了,怎么来控制这个IO了。别着急,我们是有库函数的。
红色框中的4个函数就是控制IO输出的。从函数名都可以看出功能。第一个参数是GPIO组参数,即GPIOA-GPIOG。第二个参数GPIO_Pin就是指对哪一IO管脚控制,BitVal是指写入位的值是多少。PortVal值写入16位值是多少。
就调用以上这些函数,就可以实现控制IO输出了。
假设对GPIOA0-3都输出0
那么就直接调用
GPIO_ResetBits(GPIOA, GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3);
这样,就将GPIOA0-3的管脚设置输出为低电平了。
以上,就是使用库函数来开发了。可以看出,库开发可以简化开发难度。不用来来回回的查表。而且代码有易懂性。一看就知道代码功能是什么了。
以上就是STM32操作GPIO,和S3C6410相比,多一个配置时钟使能,还有配置的寄存器也要多一些。其他是没有什么区别的。
这样,就完成了点亮LED了。当看到开发板led闪烁的时候,就说明我们的核心初始化代码是正确的。然后就可以开始进行下一步了。