南京邮电大学嵌入式系统开发实验5:嵌入式Linux下LED报警灯驱动设计及编程
实验5 嵌入式Linux下LED报警灯驱动设计及编程一.实验目的理解驱动本质,掌握嵌入式Linux系统下驱动开发相关知识,包括端口寄存器访问、接口函数编写、和文件系统挂接、注册及相关应用编程等知识点。二.实验内容实验5.1 嵌入式Linux下LED报警灯驱动设计及跑马灯应用编程实验5.2 添加看门狗功能的跑马灯应用编程三.预备知识Linux使用、驱动相关知识等四.实验设备及工具(包括软件调试工具)
实验5 嵌入式Linux下LED报警灯驱动设计及编程
一.实验目的
理解驱动本质,掌握嵌入式Linux系统下驱动开发相关知识,包括端口寄存器访问、接口函数编写、和文件系统挂接、注册及相关应用编程等知识点。
二.实验内容
实验5.1 嵌入式Linux下LED报警灯驱动设计及跑马灯应用编程
实验5.2 添加看门狗功能的跑马灯应用编程
三.预备知识
Linux使用、驱动相关知识等
四.实验设备及工具(包括软件调试工具)
硬件:ARM 嵌入式开发平台、PC 机Pentium100 以上、串口线。
软件: WinXP或UBUNTU开发环境。
五.实验5.1步骤
5.1 前期准备
(1)看懂相关硬件电路图【见S3C6410实验箱电路图-底板.pdf】,以LED报警灯为例进行设计
打开PDF硬件电路图,明确LED灯用到的多个GPIO及其控制器
本实验电路 LED1-------GPM0
LED2-------GPM1
LED3-------GPM2
LED4-------GPM3
LED5-------GPM4
LED6-------GPM5
LED7-------GPQ0
LED8-------GPQ1
得出结论:8个LED灯使用到的硬件控制器分别为GPM和GPQ两个硬件控制器
(2)在芯片手册中找到相应的硬件控制器部分,重心是看懂端口寄存器
本实验要求完成LED流水灯设计,所以需要设置控制器中端口寄存器:
GPMCON----设置相应位为输出口
GPMDAT-----控制相应位输出高电平-----点亮LED灯
输出低电平-----熄灭LED灯
(3) linux内核中相关寄存器读写函数
读寄存器函数
readl(寄存器虚地址);
写寄存器函数
writel(值(无符号整型), 寄存器虚地址);
具体端口寄存器地址宏定义在/opt/FriendlyARM/linux-2.6.38/arch/arm/mach-s3c64xx/include/mach文件夹下的文件中,如端口M寄存器在gpio-bank-m.h文件中有定义:
#define S3C64XX_GPMCON (S3C64XX_GPM_BASE + 0x00)
#define S3C64XX_GPMDAT (S3C64XX_GPM_BASE + 0x04)
注意:上面斜体部分原代码有错误,请找到该文件并修改
5.2 LED报警灯驱动设计s3c6410_leddrv.c
(1)头文件包含和相关宏定义
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <asm/irq.h>
//#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <linux/list.h>
#include <linux/pci.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <asm/unistd.h>
#include <mach/map.h>
#include <mach/regs-clock.h>
#include <mach/regs-gpio.h>
#include <plat/gpio-cfg.h>
#include <mach/gpio-bank-m.h>
#define ON 1
#define OFF 0
unsigned int old_gpmcon_val;
int leddevNo;
(2)编写驱动接口函数
/*
功能:配置GPM0~5/GPQ0~1为输出口
参数:无
返回值:无
*/
void LedConfig(void)
{
//读出端口M控制寄存器(S3C64XX_GPMCON)值,修改并写回端口寄存器
//add your code
unsigned int tmp;
tmp =readl(S3C64XX_GPMCON);
tmp &= ~((0XF<<0X0)|(0XF<<0X4)|(0XF<<0X8)|(0XF<<0XC)|(0XF<<0X10)|(0XF<<0X14));
tmp |= (0X1<<0X0)|(0X1<<0X4)|(0X1<<0X8)|(0X1<<0XC)|(0X1<<0X10)|(0X1<<0X14);
writel(tmp,S3C64XX_GPMCON);
}
/*
功能:点亮所有LED灯
参数:无
返回值:无
*/
void AllLedOn(void)
{
//读出端口M数据寄存器(S3C64XX_GPMDAT)值,修改并写回端口寄存器
//add your code here
unsigned int tmp;
tmp =readl(S3C64XX_GPMDAT);
tmp &= ~((0X1<<0X0)|(0X1<<0X1)|(0X1<<0X2)|(0X1<<0X3)|(0X1<<0X4)|(0X1<<0X5));
writel(tmp,S3C64XX_GPMDAT);
}
/*
功能:熄灭所有LED灯
参数:无
返回值:无
*/
void AllLedOff(void)
{
//读出端口M数据寄存器(S3C64XX_GPMDAT)值,修改并写回端口寄存器
//add your code here
unsigned int tmp;
tmp =readl(S3C64XX_GPMDAT);
tmp &= ~((0X1<<0X0)|(0X1<<0X1)|(0X1<<0X2)|(0X1<<0X3)|(0X1<<0X4)|(0X1<<0X5));
tmp |= (0X1<<0X0)|(0X1<<0X1)|(0X1<<0X2)|(0X1<<0X3)|(0X1<<0X4)|(0X1<<0X5);
writel(tmp,S3C64XX_GPMDAT);
}
(2)和文件系统接口对接
static int s3c6410_led_open(struct inode *inode, struct file *filp)
{
//把之前的端口M控制寄存器值读出来保存起来
//调用LedConfig函数,把GPIO口配置成输出口
//add your code
old_gpmcon_val=readl(S3C64XX_GPMCON);
LedConfig();
renturn 0;
}
static int s3c6410_led _release(struct inode *inode, struct file *filp)
{
//恢复之前的端口K控制寄存器初始值
//add your code
writel(old_gpmcon_val,S3C64XX_GPMCON);
renturn 0;
}
static long s3c6410_led_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch(cmd)
{
case ON:
//点亮所有LED灯
//add your code
AllLedOn();
break;
case OFF:
//熄灭所有LED灯
//add your code
AllLedOff();
break;
}
}
struct file_operations led_fops=
{
.open=_ s3c6410_led_open___,
.release=_ s3c6410_led_release____,
. unlocked_ioctl=_ s3c6410_led_ioctl____,
};
(3)添加模块标记代码
static int __init led_dev_init(void)
{
leddevNo = _ register_chrdev(0,"leddev",&led_fops)__;//注册设备
if(leddevNo<=0)
{
printk("REGISTER LEDDEV FAIL\n");
return -1;
}
printk ("LED device initialized\n");
return 0;
}
static void __exit led_dev_exit(void)
{
//注销设备
//add your code
_ unregister_chrdev(leddevNo,"leddev")_;
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lic@njupt.");
5.2 编写Makefile并加载到内核
(1)编写Makefile如下:
obj-m:=__ leddrv.o_______
all:
make -C _ /opt/FriendlyARM/linux-2.6.38 __SUBDIRS=$(shell pwd) modules
clean:
rm -rf *.ko *.o
(3) 编译
使用命令编译:
__#make________________________________
编译完成后生成驱动文件_leddrv.ko________。
(4) 加载驱动
将驱动搬运到开发板上【参考文档04- Tiny6410 Linux开发指南.pdf的1.9.1节】
使用命令进行驱动加载__#insmod leddrv.ko_____。
(5)创建设备文件,将驱动设备号和设备文件名关联
查看/proc/devices文件,得到系统为LED设备分配的设备号为_253___。
创建设备节点,相关命令为:
_#mknod /dev/leddev c 253 0_____。
备注:
5.3 编写应用程序
任务:要求每5秒点亮所有的LED灯,然后熄灭,过5秒再点亮LED灯
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#define ON 1
#define OFF 0
void main()
{
int fd;
fd=open(_”/dev/leddev”___,O_RDWR);
if(fd<0)
exit(1);
while(1)
{
//点亮LED灯
ioctl(fd, ON);
sleep(5);
//熄灭LED灯
_ ioctl(fd, OFF);___
sleep(5);
}
close (fd);
}
编译应用程序命令为:
_#arm-linux-gcc -o ledapp ledapp.c_____。
将应用程序加载到开发板上【具体方法参考文档04- Tiny6410 Linux开发指南.pdf的1.9.1节】,并修改权限,命令为:
#chmod +x ledapp
测试应用程序,命令为_#./ledapp____:
测试LED灯变化规律为:
____LED灯以10秒为周期亮灭,其中点亮5秒,然后熄灭5秒,循环往复___________
5.4 按照现在的驱动设计,假设要完成LED跑马灯实验,请问是否可行_B__[A.可行 B.不可行]。如果不可行的话,应该怎样改造驱动代码和应用程序。
驱动代码修改部分:
略
应用程序修改部分:
略
六. 实验5.2步骤
6.1 实验预备知识
(1)相关硬件
看门狗硬件主要用于监控系统软件或者应用软件是否发生故障,如发生故障则可以通过发出硬件复位信号,使得系统能够重启,如果再配合相关自动加载应用程序等措施,则可以保证应用程序在发生故障后能够自恢复和重启。
看门狗硬件由看门狗控制器组成,位于S3C6410处理器内部,无须处理器外其它硬件配合,因此仅需直接编程端口寄存器,并封装成文件系统接口即可。
(2)端口寄存器及相关操作
在内核代码文件中已经定义好看门狗相关端口寄存器对应的虚地址,见/opt/FriendlyARM/linux-2.6.38/arch/arm/plat-samsung/include/plat/regs-watchdog.h,如下:
#define S3C2410_WTCON S3C_WDOGREG(0x00)
#define S3C2410_WTDAT S3C_WDOGREG(0x04)
#define S3C2410_WTCNT S3C_WDOGREG(0x08)
读寄存器函数
readl(寄存器虚地址);
写寄存器函数
writel(值(无符号整型), 寄存器虚地址);
6.2 看门狗驱动代码(s3c6410_wdtdrv.c)设计
(1)头文件包含和相关宏定义
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/timer.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cpufreq.h>
#include <linux/slab.h>
#include <mach/map.h>
#include <plat/regs-watchdog.h>
unsigned int old_wdtcon_val;
int wdtdevNo;
(2)编写驱动接口函数
/*
功能:打开看门狗,允许复位,禁止看门狗中断,并设置看门狗最长看门时间
参数:无
返回值:无
*/
void WdtConfig(void)
{
//读出看门狗控制寄存器S3C2410_WTCON,保存原值,并按照要求修改
//最后写回看门狗端口控制寄存器S3C2410_WTCON
//add your code
unsigned int tmp;
tmp=(0XFF<<8)|(0X0<<6)|(0X1<<5)|(0X2<<3)|(0XFF<<8)|(0X0<<2)|(0X1<<1)|(0XFF<<8)|(0X1<<0);
writel(tmp,S3C2410_WTCON);
}
(2)和文件系统接口对接
static int s3c6410_wdt_open(struct inode *inode, struct file *filp)
{
//打开看门狗,允许复位,禁止看门狗中断,并设置看门狗最长看门时间
//add your code
old_wdtcon_val=readl(S3C2410_WTCON);
WdtConfig();
}
static ssize_t s3c6410_wdt_write(struct file *file, const char __user *data,size_t len, loff_t *ppos)
{
//把data指针所指向的用户空间值更新到看门狗计数寄存器S3C2410_WTCNT中
//add your code
unsigned int val;
copy_from_user(&val,data,4);
writel(val,S3C2410_WTCNT);
return 0;
}
static int s3c6410_wdt_release(struct inode *inode, struct file *filp)
{
//恢复之前的看门狗端口控制寄存器的原始值
//add your code
writel(old_wdtcon_val,S3C2410_WTCON);
return 0;
}
struct file_operations wdt_fops=
{
.open=_ s3c6410_wdt_open__,
.release=_ s3c6410_wdt_release_,
. write=_ s3c6410_wdt_write_,
};
(3)添加模块标记代码
static int __init wdt_dev_init(void)
{
int ret;
ret = _ register_chrdev(0,"wdtdev",&wdt_fops)__;//注册设备
printk (DEVICE_NAME"\tinitialized\n");
return ret;
}
static void __exit wdt_dev_exit(void)
{
//注销设备
//add your code
_ unregister_chrdev(wdtdevNo,"wdtdev");__
}
module_init(wdt_dev_init);
module_exit(wdt_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lic@njupt.");
5.2 编写Makefile并加载到内核
(1)编写Makefile如下:
obj-m:=__ wdtdrv.o____
all:
make -C _ /opt/FriendlyARM/linux-2.6.38___SUBDIRS=$(shell pwd) modules
clean:
rm -rf *.ko *.o
(3) 编译
使用命令编译:
_#make_______________
编译完成后生成驱动文件__wdtdrv.ko______。
(3) 加载驱动
使用命令进行驱动加载__# insmod wdtdrv.ko__。
(4)创建设备文件,将驱动设备号和设备文件名关联
相关命令为:
___#mknod /dev/leddev c 252 0____。
5.3 编写应用程序
任务:要求每5秒点亮所有的LED灯,然后熄灭,过5秒再点亮LED灯,要求能够添加看门狗支持
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <pthread.h>
#define ON 1
#define OFF 0
void *wdt_thrd_func(void *arg);
void main()
{
pthread_t wdt_thd;
fd1=open(_"/dev/leddev"_,O_RDWR);//打开LED设备
if(fd1<0)
exit(1);
fd2=open(_"/dev/wdtdev"_,O_RDWR);//打开看门狗设备
if(fd2<0)
exit(1);
//创建看门狗喂狗线程
if (pthread_create(&wdt_thd,NULL, wdt_thrd_func,NULL)!=0)
{
printf("Create watchdog thread error!\n");
exit(1);
}
while(1)
{
//点亮LED灯
ioctl(fd, ON);
sleep(5);
//熄灭LED灯
ioctl(leddev_fd,OFF);_
sleep(5);
}
close (fd);
}
void *wdt_thrd_func(void *arg)
{
//每隔10秒喂狗一次
unsigned int wdt_val=0XFFFF;
while(1)
{
//喂狗
write(fd2,&wdt_val,sizeof(int));
sleep(10);
}
return;
}
编译时使用命令:
arm-linux-gcc -o wdt_led_app wdt_led_app.c -lpthread
执行命令:
./wdt_led_app
现象:LED灯每10秒亮灭一次,间隔5秒。
如果把上述喂狗线程改为如下:
void *wdt_thrd_func(void *arg)
{
//每隔10秒喂狗一次
unsigned int wdt_val=0XFFFF;
while(1)
{
//喂狗
//write(fd2,&wdt_val,sizeof(int));
sleep(10);
}
return;
}
重新编译并执行文件,观察实验台LED现象为:
__LED灯亮灭几次后系统复位______________________。
更多推荐
所有评论(0)