首先声明,此文章是基于对国嵌视频教程中tiny6410有关视频教程的总结,为方便大家的复习。再次予以感谢,感谢国嵌各位老师为我们提供如此好的视频教程,为对于想要迈入嵌入式大门却迟迟找不到合适方法的学子们指引一条光明的方向。好了,接下来步入正题,此处将介绍tiny6410 LED驱动程序的设计。
2 下面来看看tiny6410关于LED的原理图如图(1)所示:
图1 LED原理图
3 LED实例,代码如下所示:(代码摘自\光盘4\实验代码\3-3-1\src\main.c)
main.c
/********************************************************** *实验要求: 用Tiny6410上的4个LED资源实现跑马灯程序。 *功能描述: Tiny6410用下面4个引脚连接了LED发光二极管,分别是 * GPK4--LED1 * GPK5--LED2 * GPK6--LED3 * GPK7--LED4 * 本程序将控制这四个管脚的输出电平,实现跑马灯的效果 *日 期: 2011-3-10 *作 者: 国嵌 **********************************************************/ #include "def.h" #include "gpio.h" #define LED1_ON ~(1<<4) #define LED2_ON ~(1<<5) #define LED3_ON ~(1<<6) #define LED4_ON ~(1<<7) #define LED1_OFF (1<<4) #define LED2_OFF (1<<5) #define LED3_OFF (1<<6) #define LED4_OFF (1<<7) #define LEDALL_OFF (0xf<<4) //GPIO #define GPIO_BASE (0x7F008000) //oGPIO_REGS类型在 gpio.h 中定义 #define GPIO (( volatile oGPIO_REGS *)GPIO_BASE) //函数声明 void delay(int times); void LedPortInit(void); void LedRun(void); /* * 程序入口 * */ int main(void) { LedPortInit(); LedRun(); } /* * 延时函数 * */ void delay(int times) { int i; for(;times>0;times--) for(i=0;i<3000;i++); } /* * 初始化连接LED灯的管脚资源 * @ 通过将GPIO_BASE强制转化为(volatile oGPIO_REGS*)型的指针可以很方便 * 的访问各个GPIO寄存器的值,这种方法比通过使用寄存器地址的宏定义访问 * 寄存器单元更加规范和科学。 * */ void LedPortInit(void) { u32 uConValue; uConValue = GPIO->rGPIOKCON0; uConValue &= ~(0xffff<<16); uConValue |= 0x1111<<16; GPIO->rGPIOKCON0 = uConValue; } /* * 跑马灯的实现函数 * @ 通过控制连接LED的管脚的输出电平点亮和熄灭各个LED。 * @ 逐个循环点亮各个LED。在每点亮一个后保持一定时间再熄灭它,接着 * 点亮下一个LED,这样就形成了一个跑马灯的效果。 * @ 这是一个需要改善的跑马灯程序,想想怎么优化这段代码。 * */ void LedRun(void) { GPIO->rGPIOKDAT |= LEDALL_OFF; while(1) { GPIO->rGPIOKDAT &= LED1_ON; delay(1000); GPIO->rGPIOKDAT |= LEDALL_OFF; GPIO->rGPIOKDAT &= LED2_ON; delay(1000); GPIO->rGPIOKDAT |= LEDALL_OFF; GPIO->rGPIOKDAT &= LED3_ON; delay(1000); GPIO->rGPIOKDAT |= LEDALL_OFF; GPIO->rGPIOKDAT &= LED4_ON; delay(1000); GPIO->rGPIOKDAT |= LEDALL_OFF; } }
4 程序代码分析:
首先来看一下宏定义部分:#define GPIO_BASE (0x7F008000) ,此处定义了GPIO所有相关寄存器的初始地址,由芯片手册S3C6410X.pdf,第308页可以得到此地址。以下为手册截图。由下图2可以看出,GPIO相关寄存器的初始地址即为GPACON的地址0x7F008000。
图2 GPIO的内存映射地址
了解#define GPIO (( volatile oGPIO_REGS *)GPIO_BASE)之前我们先来看看oGPIO_REGS的定义(代码摘自\光盘4\实验代码\3-3-1\src\peripheral\gpio.h)
gpio.h
#ifndef __GPIO_H__ #define __GPIO_H__ #ifdef __cplusplus extern "C" { #endif #include "def.h" typedef struct tag_GPIO_REGS { u32 rGPIOACON; //0x7F008000 u32 rGPIOADAT; u32 rGPIOAPUD; u32 rGPIOACONSLP; u32 rGPIOAPUDSLP; u32 reserved1[3]; u32 rGPIOBCON; //0x7F008020 u32 rGPIOBDAT; u32 rGPIOBPUD; u32 rGPIOBCONSLP; u32 rGPIOBPUDSLP; u32 reserved2[3]; u32 rGPIOCCON; //0x7F008040 u32 rGPIOCDAT; u32 rGPIOCPUD; u32 rGPIOCCONSLP; u32 rGPIOCPUDSLP; u32 reserved3[3]; u32 rGPIODCON; //0x7F008060 u32 rGPIODDAT; u32 rGPIODPUD; u32 rGPIODCONSLP; u32 rGPIODPUDSLP; u32 reserved4[3]; u32 rGPIOECON; //0x7F008080 u32 rGPIOEDAT; u32 rGPIOEPUD; u32 rGPIOECONSLP; u32 rGPIOEPUDSLP; u32 reserved5[3]; u32 rGPIOFCON; //0x7F0080A0 u32 rGPIOFDAT; u32 rGPIOFPUD; u32 rGPIOFCONSLP; u32 rGPIOFPUDSLP; u32 reserved6[3]; u32 rGPIOGCON; //0x7F0080C0 u32 rGPIOGDAT; u32 rGPIOGPUD; u32 rGPIOGCONSLP; u32 rGPIOGPUDSLP; u32 reserved7[3]; u32 rGPIOHCON0; //0x7F0080E0 u32 rGPIOHCON1; u32 rGPIOHDAT; u32 rGPIOHPUD; u32 rGPIOHCONSLP; u32 rGPIOHPUDSLP; u32 reserved8[2]; u32 rGPIOICON; //0x7F008100 u32 rGPIOIDAT; u32 rGPIOIPUD; u32 rGPIOICONSLP; u32 rGPIOIPUDSLP; u32 reserved9[3]; u32 rGPIOJCON; //0x7F008120 u32 rGPIOJDAT; u32 rGPIOJPUD; u32 rGPIOJCONSLP; u32 rGPIOJPUDSLP; u32 reserved10[3]; u32 rGPIOOCON; //0x7F008140 u32 rGPIOODAT; u32 rGPIOOPUD; u32 rGPIOOCONSLP; u32 rGPIOOPUDSLP; u32 reserved11[3]; u32 rGPIOPCON; //0x7F008160 u32 rGPIOPDAT; u32 rGPIOPPUD; u32 rGPIOPCONSLP; u32 rGPIOPPUDSLP; u32 reserved12[3]; u32 rGPIOQCON; //0x7F008180 u32 rGPIOQDAT; u32 rGPIOQPUD; u32 rGPIOQCONSLP; u32 rGPIOQPUDSLP; u32 reserved13[3]; u32 rSPCON; //0x7F0081A0 u32 reserved14[3]; u32 rMEM0CONSTOP; //0x7F0081B0 u32 rMEM1CONSTOP; //0x7F0081B4 u32 reserved15[2]; u32 rMEM0CONSLP0; //0x7F0081C0 u32 rMEM0CONSLP1; //0x7F0081C4 u32 rMEM1CONSLP; //0x7F0081C8 u32 reserved; u32 rMEM0DRVCON; //0x7F0081D0 u32 rMEM1DRVCON; //0x7F0081D4 u32 reserved16[10]; u32 rEINT12CON; //0x7f008200 u32 rEINT34CON; //0x7f008204 u32 rEINT56CON; //0x7f008208 u32 rEINT78CON; //0x7f00820C u32 rEINT9CON; //0x7f008210 u32 reserved17[3]; u32 rEINT12FLTCON; //0x7f008220 u32 rEINT34FLTCON; //0x7f008224 u32 rEINT56FLTCON; //0x7f008228 u32 rEINT78FLTCON; //0x7f00822C u32 rEINT9FLTCON; //0x7f008230 u32 reserved18[3]; u32 rEINT12MASK; //0x7f008240 u32 rEINT34MASK; //0x7f008244 u32 rEINT56MASK; //0x7f008248 u32 rEINT78MASK; //0x7f00824C u32 rEINT9MASK; //0x7f008250 u32 reserved19[3]; u32 rEINT12PEND; //0x7f008260 u32 rEINT34PEND; //0x7f008264 u32 rEINT56PEND; //0x7f008268 u32 rEINT78PEND; //0x7f00826C u32 rEINT9PEND; //0x7f008270 u32 reserved20[3]; u32 rPRIORITY; //0x7f008280 u32 rSERVICE; //0x7f008284 u32 rSERVICEPEND; //0x7f008288 u32 reserved21; u32 reserved22[348]; u32 rGPIOKCON0; //0x7f008800 u32 rGPIOKCON1; //0x7f008804 u32 rGPIOKDAT; //0x7f008808 u32 rGPIOKPUD; //0x7f00880c u32 rGPIOLCON0; //0x7f008810 u32 rGPIOLCON1; //0x7f008814 u32 rGPIOLDAT; //0x7f008818 u32 rGPIOLPUD; //0x7f00881c u32 rGPIOMCON; //0x7f008820 u32 rGPIOMDAT; //0x7f008824 u32 rGPIOMPUD; //0x7f008828 u32 reserved23; u32 rGPIONCON; //0x7f008830 u32 rGPIONDAT; //0x7f008834 u32 rGPIONPUD; //0x7f008838 u32 reserved24; u32 reserved25[16]; u32 rSPCONSLP; //0x7f008880 u32 reserved26[31]; u32 rEINT0CON0; //0x7f008900 u32 rEINT0CON1; //0x7f008904 u32 reserved27[2]; u32 rEINT0FLTCON0; //0x7f008910 u32 rEINT0FLTCON1; //0x7f008914 u32 rEINT0FLTCON2; //0x7f008918 u32 rEINT0FLTCON3; //0x7f00891c u32 rEINT0MASK; //0x7f008920 u32 rEINT0PEND; //0x7f008924 u32 reserved28[2]; u32 rSLPEN; //0x7f008930 } oGPIO_REGS; #ifdef __cplusplus } #endif #endif //__GPIO_H__
由此可以看出oGPIO_REGS为一结构体,(( volatile oGPIO_REGS *)GPIO_BASE)为指向此结构体的指针,该指针即GPIO指向的初始地址为GPIO_BASE(0x7F008000),通过使用此指针,可以遍历到从GPIO_BASE地址(0x7F008000)开始到0x7F008930地址处的所有寄存器。注意:上面结构体中所有元素,类型都是u32类型的,刚好四个字节,同时,由图2可知,如GPACON和GPADAT等寄存器地址都相差4,对于这段连续地址,如若中间没有对应某个寄存器,则用某些u32类型的数组填充,如u32 reserved1[3]等等。
下面开始看main.c中的main函数,main函数主要完成两个步骤,(1)LED初始化(LedPortInit()),(2)点亮LED(LedRun()).
LED初始化:
GPK总共有16个引脚,而每个引脚需要GPIO控制寄存器(GP*CON)使用4位来控制IO管脚的功能,即4*16=64位来控制所有GPK组的16个引脚。所以需要GPK使用了两个控制寄存器,GPKCON0和GPKCON1,从图1所示,我们使用的是GPK4,GPK5,GPK6,GPK7来控制LED灯的点亮与熄灭,所以此处我们只需使用GPKCON0来将GPK4,GPK5,GPK6,GPK7设置成输出功能。如图3所示,GPKCON寄存器配置如下:
图3 GPKCON寄存器配置图
配置代码如下:
1 . u32 uConValue;
2 . uConValue = GPIO->rGPIOKCON0;
3 . uConValue &= ~(0xffff<<16); 0000 0000 0000 0000 1111 1111 1111 1111
4 . uConValue |= 0x1111<<16;
5 . GPIO->rGPIOKCON0 = uConValue;
首先我们要知道,在控制某个管脚的时候,我们不能去改变其它不使用管脚的状态,第二行获得GPKCON此时的状态将其赋给uConValue,第三行用于将GPKCON的高16位清零,低16位不变;第四行用于将GPKCON的高16为变为0001 0001 0001 0001(即GPK4,GPK5,GPK6,GPK7管脚都设置为输出模式),低16为仍然不变。最后将此值赋回到GPKCON寄存器当中。至此,完成整个LED的初始化工作。
点亮LED:注意GPKDAT为16位寄存器,虽然由gpio.h中看到rGPIOKDAT为32位,只是相当于我们忽略了其中的高16位,因为数据是从低地址往高地址处依次存放的。
GPIO->rGPIOKDAT |= LEDALL_OFF; //4个灯全灭
while(1)
{
GPIO->rGPIOKDAT &= LED1_ON; //GPK4管脚的灯亮
delay(1000);
GPIO->rGPIOKDAT |= LEDALL_OFF; //GPK4管脚的灯灭
GPIO->rGPIOKDAT &= LED2_ON; //GPK5管脚的灯亮
delay(1000);
GPIO->rGPIOKDAT |= LEDALL_OFF; //GPK4管脚的灯灭
GPIO->rGPIOKDAT &= LED3_ON; //GPK6管脚的灯亮
delay(1000);
GPIO->rGPIOKDAT |= LEDALL_OFF; //GPK4管脚的灯灭
GPIO->rGPIOKDAT &= LED4_ON; //GPK7管脚的灯亮
delay(1000);
GPIO->rGPIOKDAT |= LEDALL_OFF; //GPK4管脚的灯灭
}
首先通过如下宏定义来表明LED的亮与灭。
#define LED1_ON ~(1<<4) 执行步骤:1<<4 -> ~(1<<4) ,对应步骤为:0000 0000 0000 0001->0000 0000 0001 0000->1111 1111 1110 1111
#define LED2_ON ~(1<<5)
#define LED3_ON ~(1<<6)
#define LED4_ON ~(1<<7)
#define LED1_OFF (1<<4) 执行步骤:1<<4 对应步骤为:0000 0000 0000 0001->0000 0000 0001 0000
#define LED2_OFF (1<<5)
#define LED3_OFF (1<<6)
#define LED4_OFF (1<<7)
#define LEDALL_OFF (0xf<<4) 执行步骤:0xf<<4, 对应步骤为:0000 0000 0000 1111->0000 0000 1111 0000
下面完成整个程序的运行步骤:
1 打开rvds
新建一个空的arm可执行镜像LED:
在E:\tiny6410下自动多出一个LED目录
目录下自动创建了如下一些文件及文件夹:
将src目录和6410_scatter.txt拖入LED目录下:
将src下的代码直接拖入rvds:
得到如下添加代码后的工程,并打开main函数。
开始配置工程:
1 在汇编过程中选择处理器型号ARM1176JZF-S
2 在编译中选择处理器型号ARM1176JZF-S
3 在链接过程中选择scattered,文件选择scattered.txt
保存后,编译程序,出现如下错误:
解决方法:在配置框中的汇编的预处理中做如下设置:
然后点击Add,将VIC_MODE预处理值设为1:
保存后重新编译,编译通过,在Debug目录下会多出一个LED.axf文件,此文件为要运行在开发板的文件。
现在开始通过JLINK来调试开发板。首先链接开发板与jlink线。
将开发板上的S2拨到Nand Flash启动那一侧,开启开发板电源,在超级终端上快速按下回车键,让U-Boot停留在功能选单上,不要让它进入Linux系统,如下图所示:
在XP里,点击开始菜单,选择程序->SEGGER->J-Link ARM V4.10i->J-Link GDB Server,启动画面如下所示:
上图表明jlink已经正确链接上了。
然后需要配置AXD Debugge,让它使用J-Link来调试,通过以下方法启动AXD Debugger
启动AXD Debugger后,在AXD Debugger主界面上打开主菜单的Options -> Configure Target,在弹出的Choose Target对话框中,点击Add, 将会弹出文件选择对话框,在文件打开对话框中,定位到J-Link的安装目录(默认是E:\Program Files\SEGGER\JLinkARM_V410i),在目录中选择JLinkRDI.dll打开即可,如下图所示:
可能AXD会提示找不到JLinkARM.DLL,如下图所示:
解决方法是:先不理会这个对话框,打开我的电脑,再次定位到J-Link的安装目录(默认是E:\Program Files\SEGGER\JLinkARM_V410i),将其中的JLinkARM.DLL文件拷贝到RVDS的安装目录下的Bin目录下(默认是C:\Program Files\ARM\ADSv1_2\Bin),再在上面的对话框上点击“确定”即可。
下面通过File->Load image,载入LED.axf文件,进行调试。