对于任何一门编程语言的学习,绝大部分都是从Hello Word开始的,但是对于大部分嵌入式驱动开发者,通常都是从点Led灯开始的,前人有言,给我一个发光二极管,我将点亮整个世界,从这格层面上讲,点灯已是一种方便快捷的调试手段。同样,本教程也不列外,所有的一切都从点亮一颗Led开始。
任何一款SOC芯片,其外围必定有一堆扩展IO口,通常这些IO是和各种内部外设复用的,比如串口的TXD、RXD信号既可以做功能管脚,也可以做普通IO,对于点亮Led灯,只需要将对应的管脚配置为GPIO输出模式,并设置相应的输出电平状态即可。
本系列教程,基于九鼎的X4412开发板,此开发板性价比高,性能稳定,扩展性强,功能完善,并提供X4412核心板,方便企业用户做深度定制版二次开发。开发板截图:
任何系统开机后都有一个启动过程,同样EXYNOS4412也不列外。Exynos4412属于应用处理器,非一般微控制器,开机启动是一个比较复杂的过程,因这里是第一篇教程,故不作过多阐述,后续系列教程会此进行深入描述。这里简单说明一下,EXYNOS4412芯片启动时,固化在芯片内部的IROM会首先执行,其最主要的任务是读取外部存储器的前8k或者16k代码,并做相应的校验,校验通过后,然后执行。在此过程中仅仅使用内部的IROM及IRAM,不涉及任何外部存储器。当然,究竟是从哪个存储器读取前8k或者16k,则是根据硬件的OM启动模式配置管脚来决定的,本教程OM配置模式都是从外部SD卡启动,相应的程序也都烧录在外部SD卡中的第一个扇区。
前面的这8k或者16k代码后,是系统正常启动的关键。一般都会执行以下动作:关看门狗,设置为系统模式,关MMU,CACHE等,初始化系统主时钟,初始化DDR,拷贝剩余部分代码(当然bootloader都会超过8k的),为C代码准备环境,比如将BSS段清零,初始化已初始化数据段等等,最后是调到C语言的Main函数,至此,启动工作已完成,进入相对较为熟悉的main函数了。
本教程,重点讲述LED灯相关的操作,首先,我们由原理图可以看出,底板上有四颗LED,参考原理图如下:
为了能方便快捷的操作各个LED,我们定义一个函数,void led_set_status(enum led_name name, enum led_status status),这样在调用时只需传递特定的参数就可以控制各个LED了,这里定义了两个枚举类型:
enum led_name {
LED_NAME_LED1 = 1,
LED_NAME_LED2 = 2,
LED_NAME_LED3 = 3,
LED_NAME_LED4 = 4,
};
enum led_status {
LED_STATUS_OFF = 0,
LED_STATUS_ON = 1,
};
在我们执行具体的操作时,我们需要初始化相应的IO口,将其设置为输出上拉模式,初始化代码如下:
/*
* LED1 -> EXYNOS4412_GPX1(6)
* LED2 -> EXYNOS4412_GPX1(7)
* LED3 -> EXYNOS4412_GPX2(6)
* LED4 -> EXYNOS4412_GPX2(7)
*/
void led_initial(void)
{
/* LED1 */
writel(EXYNOS4412_GPX1_BASE + EXYNOS4412_GPIO_CON, (readl(EXYNOS4412_GPX1_BASE + EXYNOS4412_GPIO_CON) & ~(0xf<<24)) | (0x1<<24));
writel(EXYNOS4412_GPX1_BASE + EXYNOS4412_GPIO_PUD, (readl(EXYNOS4412_GPX1_BASE + EXYNOS4412_GPIO_PUD) & ~(0x3<<12)) | (0x2<<12));
writel(EXYNOS4412_GPX1_BASE + EXYNOS4412_GPIO_DAT, (readl(EXYNOS4412_GPX1_BASE + EXYNOS4412_GPIO_DAT) & ~(0x1<<6)) | (0x1<<6));
/* LED2 */
writel(EXYNOS4412_GPX1_BASE + EXYNOS4412_GPIO_CON, (readl(EXYNOS4412_GPX1_BASE + EXYNOS4412_GPIO_CON) & ~(0xf<<28)) | (0x1<<28));
writel(EXYNOS4412_GPX1_BASE + EXYNOS4412_GPIO_PUD, (readl(EXYNOS4412_GPX1_BASE + EXYNOS4412_GPIO_PUD) & ~(0x3<<14)) | (0x2<<14));
writel(EXYNOS4412_GPX1_BASE + EXYNOS4412_GPIO_DAT, (readl(EXYNOS4412_GPX1_BASE + EXYNOS4412_GPIO_DAT) & ~(0x1<<7)) | (0x1<<7));
/* LED3 */
writel(EXYNOS4412_GPX2_BASE + EXYNOS4412_GPIO_CON, (readl(EXYNOS4412_GPX2_BASE + EXYNOS4412_GPIO_CON) & ~(0xf<<24)) | (0x1<<24));
writel(EXYNOS4412_GPX2_BASE + EXYNOS4412_GPIO_PUD, (readl(EXYNOS4412_GPX2_BASE + EXYNOS4412_GPIO_PUD) & ~(0x3<<12)) | (0x2<<12));
writel(EXYNOS4412_GPX2_BASE + EXYNOS4412_GPIO_DAT, (readl(EXYNOS4412_GPX2_BASE + EXYNOS4412_GPIO_DAT) & ~(0x1<<6)) | (0x1<<6));
/* LED4 */
writel(EXYNOS4412_GPX2_BASE + EXYNOS4412_GPIO_CON, (readl(EXYNOS4412_GPX2_BASE + EXYNOS4412_GPIO_CON) & ~(0xf<<28)) | (0x1<<28));
writel(EXYNOS4412_GPX2_BASE + EXYNOS4412_GPIO_PUD, (readl(EXYNOS4412_GPX2_BASE + EXYNOS4412_GPIO_PUD) & ~(0x3<<14)) | (0x2<<14));
writel(EXYNOS4412_GPX2_BASE + EXYNOS4412_GPIO_DAT, (readl(EXYNOS4412_GPX2_BASE + EXYNOS4412_GPIO_DAT) & ~(0x1<<7)) | (0x1<<7));
}
在初始各个控制IO后,我们可以实现具体的设置LED状态的函数了。根据传递的参数设置相应的控制寄存器。
void led_set_status(enum led_name name, enum led_status status)
{
switch(name)
{
case LED_NAME_LED1:
if(status == LED_STATUS_ON)
writel(EXYNOS4412_GPX1_BASE + EXYNOS4412_GPIO_DAT, (readl(EXYNOS4412_GPX1_BASE + EXYNOS4412_GPIO_DAT) & ~(0x1<<6)) | (0x0<<6));
else if(status == LED_STATUS_OFF)
writel(EXYNOS4412_GPX1_BASE + EXYNOS4412_GPIO_DAT, (readl(EXYNOS4412_GPX1_BASE + EXYNOS4412_GPIO_DAT) & ~(0x1<<6)) | (0x1<<6));
break;
case LED_NAME_LED2:
if(status == LED_STATUS_ON)
writel(EXYNOS4412_GPX1_BASE + EXYNOS4412_GPIO_DAT, (readl(EXYNOS4412_GPX1_BASE + EXYNOS4412_GPIO_DAT) & ~(0x1<<7)) | (0x0<<7));
else if(status == LED_STATUS_OFF)
writel(EXYNOS4412_GPX1_BASE + EXYNOS4412_GPIO_DAT, (readl(EXYNOS4412_GPX1_BASE + EXYNOS4412_GPIO_DAT) & ~(0x1<<7)) | (0x1<<7));
break;
case LED_NAME_LED3:
if(status == LED_STATUS_ON)
writel(EXYNOS4412_GPX2_BASE + EXYNOS4412_GPIO_DAT, (readl(EXYNOS4412_GPX2_BASE + EXYNOS4412_GPIO_DAT) & ~(0x1<<6)) | (0x0<<6));
else if(status == LED_STATUS_OFF)
writel(EXYNOS4412_GPX2_BASE + EXYNOS4412_GPIO_DAT, (readl(EXYNOS4412_GPX2_BASE + EXYNOS4412_GPIO_DAT) & ~(0x1<<6)) | (0x1<<6));
break;
case LED_NAME_LED4:
if(status == LED_STATUS_ON)
writel(EXYNOS4412_GPX2_BASE + EXYNOS4412_GPIO_DAT, (readl(EXYNOS4412_GPX2_BASE + EXYNOS4412_GPIO_DAT) & ~(0x1<<7)) | (0x0<<7));
else if(status == LED_STATUS_OFF)
writel(EXYNOS4412_GPX2_BASE + EXYNOS4412_GPIO_DAT, (readl(EXYNOS4412_GPX2_BASE + EXYNOS4412_GPIO_DAT) & ~(0x1<<7)) | (0x1<<7));
break;
default:
break;
}
}
有了以上LED控制函数,我们就可以实现具体的流水灯程序了,闪烁规则如下,从左至右依次点亮,然后再从右至左依次点亮,通过index变量标识是那颗led,flag变量标识流水的方向,从左至右还是从右至左。
int tester_led(int argc, char * argv[])
{
int index = 0;
int flag = 0;
while(1)
{
led_set_status(LED_NAME_LED1, LED_STATUS_OFF);
led_set_status(LED_NAME_LED2, LED_STATUS_OFF);
led_set_status(LED_NAME_LED3, LED_STATUS_OFF);
led_set_status(LED_NAME_LED4, LED_STATUS_OFF);
switch(index)
{
case 0:
led_set_status(LED_NAME_LED1, LED_STATUS_ON);
break;
case 1:
led_set_status(LED_NAME_LED2, LED_STATUS_ON);
break;
case 2:
led_set_status(LED_NAME_LED3, LED_STATUS_ON);
break;
case 3:
led_set_status(LED_NAME_LED4, LED_STATUS_ON);
break;
default:
break;
}
mdelay(50);
if(index == 0 || index == 3)
flag = !flag;
if(flag)
index = (index + 1) % 4;
else
index = (index + 4 - 1) % 4;
}
return 0;
}
如果您对本文感兴趣,想必是同道中人,需要源码的朋友,可移步此处,点击下载,有任何疑问欢迎留言,或者直接加QQ: 8192542。第一篇教程就此结束,先有格感性认识吧,尽请期待后续教程,您的支持就是我的动力,还请各路大侠多多捧场。
附上源码:
Exynos4412裸机系列教程源码之流水灯