weiqi7777

基于S3C6410的ARM11学习(九) 点亮led

0
阅读(3363)

 

     之前已经对S3C6410的核心初始化完成了,但是这个也只是完成了,但是程序到底对不对了,还需要验证。最简单的验证方法是什么了,就是点亮led。如果在核心初始化完成后,我们写程序去点亮了led,就说明初始化代码是没有问题。

         下面就要来点亮led了。这个时候就和开发板的原理图有关系了。我用的是OK6410的开发板,打开对应原理图。

         clip_image002

         找到4个灯,是共阳接法的。所以要点亮对应的灯,就要使led的负端为低电平。在看看各个led负端端口对应的S3C6410的哪个IO

   clip_image004

     从核心板的原理图可以看出,4LED是接在GPM0-3这四个管脚上的。

     有了这个信息,我们只要去控制这四个管脚输出不同的电平,就能实现led的亮灭了。

 

     打开6410的手册。直接看GPIO这一章。

clip_image006

     表中列出了所有的GPIO,总共分为17组,从A-Q。每一组有不同的管脚数。控制IO功能是通过GPxCON寄存器来控制的。控制IO管脚上数据是通过GPxDAT来控制的。我们这里就以GPM为例说明。

     clip_image008

     上图说明GPMIO操作有3个寄存器。

     先看第一个寄存器,GPMCON

clip_image010

     GPM组共有6IO管脚。所以这个寄存器中,分成了6组,每一组控制对应的IO的功能。这里,我们是要使GPM0-3为输出。所以低16位就要设置为16’b0001_0001_0001_0001

这样,就将GPM0-3设置为输出了。

        

         在看第二个寄存器,GPMDAT

clip_image012

         手册也说得比较清楚,当IO为输入的时候,记录的是IO脚上的电平。当作为输出的时候,记录的是IO上输出的电平。所以当我们要让GPM0-3都输出低电平的话,那么这个寄存器的后4位就要写入4’b0000

       

         第三个寄存器,GPMPUD

         clip_image014

         这个是设置IO管脚上下拉电阻的。


         以上三个寄存器就是IO的基本寄存器了。每一组的IO都有上诉的3个寄存器。

 

         下面我们就要来写程序了。这里,我写的是流水灯,初始,4个灯为亮灭亮灭,每隔一段时间,灯的状态就翻转一次,

         流程:

1、  设置对应IO为输出

2、  设置IO输出值

3、  每隔一段时间对IO输出值取反

 

下面就是对应的程序了,将流水灯写成一个函数,在reset中调用。

clip_image015

1、  先定义GPMCOMGPMDAT地址。

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、              比较r210的大小

19、              如果r2大于10的话,跳转到delay_1处,说明延时还没有结束,继续执行延时,也就是继续减r2寄存器。否则往下执行

20、              delay函数返回

 

用汇编来写,要麻烦一些,要定义一些标签,用来跳转。

这样,就完成了点亮流水灯的代码了。下面在reset中调用该函数

clip_image016

这样,就完成了整个程序的设计了。将该程序,编译链接下载到开发板中,运行,就会看到led在不停的闪烁了。

 

对比STM32,操作可就要麻烦一些了。

首先看下系统结构,看看GPIO是挂在什么总线上的。因为之前说过,STM32是有外设门控时钟的。要使用某外设,就必须要打开对应外设的时钟。这是和ARM11很不一样的一点。

 

clip_image018

GPIO是挂在APB2总线上的,所以我们打开时钟的时候要去打开APB2上对应GPIO时钟。图中可以知道,GPIO是分为7组的。从A-G

  GPIO章节去看。

clip_image020

   功能描述就说明了,对应每个GPIO端口,有哪些寄存器来控制。STM32是有7个寄存器来进行控制。

   STM32GPIOS3C6410GPIO要复杂多了,因为GPIO可以配置为8种模式。clip_image022

 


下面是GPIO的基本结构

clip_image024

8种模式中,对推挽式输出和推挽式复用输出说明一下。这两个的输出结构都是一样的,使用CMOS的结构。但是不一样的是不同功能所使用模式不一样。推挽式输出是IO端口作为普通输出时的模式,而推挽式复用输出是IO作为特殊功能时作为输出的模式,比如有的IO有串口输出功能,当使用串口输出功能的时候,就要把IO的模式设置为推挽式复用输出。

输入的话,就没有什么好说的,从名字也可以看的出来。注意的是模拟输入,当使用芯片的内部AD时,IO是要设置为模拟输入的。

 

下面就要看看各个寄存器

1GPIOx_CRL寄存器

clip_image026

这个是设置GPIO组的低8位的IO功能。每4位对应一个IO。高两位是配置IO的模式。低两个是配置IO作为输入还是输出,以及作为输出的最大速度是多少。

2GPIOx_CRH寄存器

clip_image028

GPIOx_CRL寄存器一样的功能,只不过是配置的高8IO的功能。

3GPIOx_IDR寄存器

clip_image030

从描述中得到,这个寄存器保存的IO的状态,其实就是IO上的电平。而且这个寄存器只能读。当IO作为输入的时候,我们来读这个寄存器就知道IO上的电平了。

4GPIOx_ODR寄存器

clip_image032

这个寄存器就是设置IO的输出了。低16位,每一位对应一个IO。当IO作为输出,可以往这个寄存器写值,就实现了IO输出了。

5GPIOx_BSRR寄存器

clip_image034

这个寄存器,就是对输出值影响的。可以对输出清零,也可以对输出置1.

6GPIOx_BRR寄存器

clip_image036

看描述就知道功能了

7GPIOx_LCKR寄存器

clip_image038

STM32IO加了一个锁的功能,就是可以将IO给锁起来,这样就不能对IO进行改变了。这个功能就是由这个寄存器来设置的。要锁某个IO的话,先将对应IO位的值设置为1,锁定端口配置,然后往16位写入特定的序列,就实现了IO的锁的功能了。在系统复位前,这锁功能一直有。


知道了对应相关的寄存器了,就知道怎么使用STM32的管脚来控制流水灯了。

流程

1、  打开对应IO的时钟使能

2、  配置CRLCRH,使IO作为输出,模式就推挽式输出,最大速度是50M

3、  配置ODR寄存器,使寄存器输出我们想要的值

 

以配置GPIOA0-3为例说明。

首先打开GPIOA时钟。这个在RCC_APB2ENR寄存器中

clip_image040

clip_image042

         2位控制GPIOA的时钟使能。那么就把这一位置1就好了。但是怎么操作这个寄存器了。当然,我们可以向S3C6410一样,使用指针,去指向这个寄存器的地址,然后再对这个地址操作。但是,官方库已经给我们定义好了这些寄存器了,我们只要直接使用就好了。

         在官方给的库(V3.5)中,有两个文件夹,一个inc,一个src

clip_image044

inc里面就是一些头文件,src里面就是具体的函数实现。库将每个外设都封装好了一个头文件和一个c文件。里面就定义了各个外设的寄存器,以及一些库函数,使用这些库函数就可以直接操作这些外设寄存器,而不用和底层的寄存器打交道了。因为STM32的寄存器实在是太多了,每次操作都查表的话,那可是很累的。而且直接操作寄存器的话,代码不直观,不知道配置到底是什么作用的。

这里,我们先用寄存器来进行操作。然后再用库函数来进行操作。在对比

寄存器版本:

寄存器版本的话就和incsrc下面的库文件没有多大关系了。直接打开stm32f10x.h,这个是在官方提供的库CMSIS目录下。

clip_image046

这个头文件定义了stm32f10x系列的所有寄存器对应的地址,相当于51单片机的reg51.h

 

clip_image048

这里定义RCC的结构体,里面包含了RCC的各个寄存器。我们这里要用的就是其中的APB2ENR寄存器。因为这个寄存器里面有对GPIOA的时钟有效设置。在STM32中,对寄存器定义都是放在对应功能的结构体中去了,因为这些寄存器是严格4字节对齐的,所以只要放在结构体中,然后定义结构体的首地址,那么后面每个寄存器的地址也就都知道了。上面就定义RCC的结构体。这个是管理时钟和复位功能的。

在去找RCC的地址

clip_image050

这样,我们就可以直接访问了。

使用RCC->APB2ENR就可以访问RCC下的APB2ENR寄存器了。之前说,要把这个寄存器的第二位置为1.所以

RCC->APB2ENR = 0x4;

配置GPIO

         同样,也是先去找GPIO结构体。

clip_image051

里面就有上面这个结构体。在STM32中,对外设的寄存器定义都是放在结构体中去了,因为这些寄存器是严格4字节对齐的,所以只要放在结构体中,然后定义结构体的首地址可以知道所有寄存器地址。上面就定义GPIO的结构体。

然后再去找GPIOA,因为我们要对这一组GPIO操作。

我从里面把关键的弄出来了

clip_image053

先定义了外设的基地址,然后再定义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操作的库函数操作的参数以及函数。

clip_image055

clip_image057

里面定义了GPIO初始化的一个结构体,当我们要初始化一个GPIO,就往这个结构体里面填值就行了。填的值可不是乱填的。而是在这文件中规定好的。

第一个值,对应下面的各个参数。表示是配置哪一个GPIO,总共16个。可以使用|操作。

clip_image059

第二个值是枚举类型,设置GPIO的速度

 

clip_image060

第三个值也是枚举类型,设置的IO的模式

clip_image061

下面我们就只要定义一个结构体,往这结构体里面的参数赋值,然后再调用GPIO的初始化函数就搞定了。

clip_image063

代码就是上面这样,怎么样,这比用寄存器开发要简洁很多吧。一看就知道代码功能是什么。就设置GPIOA0-3管脚作为推挽式输出,速度是50M。完全不用去操作寄存器,因为库函数就帮我们做了这些事了。

GPIO_Init()这个函数是库函数,就是对外设进行初始化,初始化的参数就是填入的结构体的参数。有兴趣的可以去看看这个函数的实现,这个函数就在stm32f10x_gpio.c中。

 

我们还忘记了一件事了,那就是打开GPIOA的时钟了。这个时钟是受RCC管理的。去看stm32f10x_rcc.h文件。

这里,有一个函数

clip_image065

从这个名字就可以知道这个函数干嘛的,就是打开APB2总线上外设的时钟了。第一个参数是外设的名字,这个也是预先定义好的。第二个是状态,两个状态,ENABLEDISABLE

clip_image067

我们要打开GPIOA时钟,所以参数就是RCC_APB2Periph_GPIOA

 

所以完整的GPIOA初始化代码就是

clip_image069

 

初始化搞定了,怎么来控制这个IO了。别着急,我们是有库函数的。

clip_image071

红色框中的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闪烁的时候,就说明我们的核心初始化代码是正确的。然后就可以开始进行下一步了。