应用层操控硬件可以通过操作这些硬件的设备文件来进行,设备文件是各种硬件设备向应用层提供的一个接口,应用层通过对设备文件的I/O操作来操控硬件设备。设备文件通常在/dev/目录下,该目录下的文件也称为设备节点,前面驱动开发中的设备节点文件就存放该目录下,通过对相应的设备文件进行读写操作从而达到控制其对应硬件的目的。
除了对设备文件操作可以控制硬件设备外,还可以通过对sysfs文件系统对硬件设备进行操控。sysfs提供了一种机制,可以显式的描述内核对象、对象属性及对象间关系,用来导出内核对象的数据、属性到用户空间,以文件目录结构的形式为用户空间提供对这些数据和属性的访问支持。硬件设备、设备属性、对象关系在sysfs中分别对应目录、文件和链接文件。
sysfs挂载在/sys/目录下,打开/sys/目录,其包含的文件及其层级关系如下图所示。
/sys/devices下存放着系统所有的设备目录;/sys/block是块设备目录;/sys/bus是系统总线类型的目录;/sys/class是按照功能分类放置的目录;/sys/dev是按照设备号方式放置的目录;/sys/firmware是内核中的固件目录;/sys/fs是文件系统目录;/sys/kernel是内核中所有可调参数的位置;/sys/module是系统中存放模块信息的目录;/sys/power是系统中电源选项。/sys/devices是最重要的目录,其他的设备目录都是链接到该目录下。
下面介绍一下通过sysfs控制gpio的方法。
进入到/sys/class/gpio目录下,该目录下包含两个文件export和unexport以及gpio_和gpiochip_等文件夹。gpiochip_对应着开发板的gpio控制器,是一组gpio,后面的数字就是这组gpio中的起点,gpio_就是某一个具体的gpio口。
export用于将指定编号的GPIO引脚导出,在使用GPIO引脚之前,需要将其导出,导出成功之后才能使用它。export文件是只写文件,将一个指定的编号写入到export文件中即可将对应的GPIO引脚导出。导出的命令如下。
echo 0 > export //导出gpio0
使用上面的命令后就可以在/sys/class/gpio目录下生成了一个名为gpio0的文件夹,文件夹里面有相关属性的文件,使用cat命令就可以查看属性值,如下图所示。
unexport用于将导出的GPIO引脚删除,删除的命令如下。
echo 0 > unexport //删除gpio0
direction文件配置GPIO引脚为输入或输出模式,有"in"和"out";value是在GPIO配置为输出模式下,向value文件写入"0"控制GPIO引脚输出低电平,写入"1"则控制GPIO引脚输出高电平。在输入模式下,读取value文件获取GPIO引脚当前的输入电平状态。active_low属性文件用于控制极性,可读可写,默认情况下为0;edge控制中断的触发模式,该文件可读可写,非中断引脚为"none",上升沿触发为"rising",下降沿触发为"falling",边沿触发为"both"。
进到相应的gpio目录下,配置各属性的命令如下,可根据自己的要求给属性设置对应的值。
echo “in” > direction
echo “out” > direction
echo “0” > active_low
echo “1” > active_low
echo “0” > value
echo “1” > value
echo “none” > edge
echo “rising” > edge
echo “falling” > edge
echo “both” > edge
上面是采用命令设置各属性,代码实现如下。
#include
#include
#include
#include
#include
#include
#include
#include
static char gpio_path[100]; //gpio存放路径
static int gpio_config(const char *attr, const char *val) //gpio属性设置函数
{
char file_path[100]; //加属性之后的路径
int fd,ret;
sprintf(file_path,"%s/%s",gpio_path,attr); //属性的路径
fd = open(file_path, O_RDWR);
if(fd < 0)
{
perror("open error");
return fd;
}
ret = write(fd,val,strlen(val)); //给gpio相关属性设置值
if(ret < 0)
{
perror("attribute write error");
return ret;
}
close(fd);
return 0;
}
int main(int argc, char **argv)
{
int fd,ret;
char value[1];
char file_path[100];
if(argc != 3) //argv[1]:gpio number ------ argv[2]:value
{
printf("error argument!\n");
return -1;
}
sprintf(gpio_path,"/sys/class/gpio/gpio%s",argv[1]);
if (access(gpio_path,F_OK)) //如果需要的gpio目录不存在,则需要通过export导出,相当于命令 echo 0 > export
{
fd = open("/sys/class/gpio/export", O_WRONLY); //打开配置文件
if(fd < 0)
{
perror("open error");
return fd;
}
ret = write(fd,argv[1],strlen(argv[1]));
if(ret < 0)
{
perror("gpio write error");
return ret;
}
close(fd);
}
//目标gpio已存在,直接设置
gpio_config("direction","out"); //配置输入输出方向
gpio_config("active_low","0"); //配置低电平
gpio_config("value",argv[2]); //设置值
//gpio_config("edge","both"); //设置中断触发模式
//读取gpio的value属性
sprintf(file_path,"%s/%s",gpio_path,"value");
fd = open(file_path,O_RDONLY);
read(fd,value,1);
printf("value = %c\n",value[0]);
return 0;
}
上面的程序执行时传入两个参数,第一个参数是gpio对应的编号,第二个参数是设置的value属性值,有些gpio口设置value的时候是不被允许的,可以根据自己开发板的情况修改代码执行程序。
为了使系统在异常情况下能自动复位,一般都需要引入看门狗,看门狗本质上是一个可以在一定时间内被复位的计数器。当看门狗启动后,计数器开始自动计数,经过特定的时间,如果计数器没有被复位,计数器就会溢出从而对CPU产生一个复位信号使系统重启。系统正常运行时,需要在看门狗允许的时间间隔内对看门狗计数器清零,俗称喂狗,喂狗是为了不产生复位信号。如果系统不出问题,程序就可以保证按时喂狗,一旦程序跑飞,则不会喂狗,系统收到复位信号进行复位。
使用WDIOC_GETTIMEOUT指令获取超时时间,WDIOC_SETTIMEOUT指令设置超时时间。
int timeout;
ioctl(int fd, WDIOC_GETTIMEOUT, int *timeout); //获取超时时间
ioctl(int fd, WDIOC_SETTIMEOUT, int *timeout); //设置超时时间
WDIOS_ENABLECARD指令开启、WDIOS_DISABLECARD指令停止看门狗。
int option = WDIOS_ENABLECARD;
ioctl(int fd, WDIOC_SETOPTIONS, int *option); //开启
int option = WDIOS_DISABLECARD;
ioctl(int fd, WDIOC_SETOPTIONS, int *option); //关闭
使用WDIOC_KEEPALIVE指令喂狗。
ioctl(int fd, WDIOC_KEEPALIVE, NULL);
开发板的/dev/目录下有watchdog和watchdog0两个和看门狗相关的设备节点,这两个代表的是同一个硬件外设,我在代码中都试了,这两个设备节点都是可以控制看门狗的。
看门狗的测试代码如下。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int timeout; //超时时间设定
int time; //喂狗间隔时间
int op; //设置看门狗状态
int fd;
int ret;
if(argc != 2) //需要用户传入超时时间
{
printf("error argument!\n");
return -1;
}
fd = open("/dev/watchdog", O_RDWR); //打开看门狗设备文件
if (fd < 0)
{
perror("open error");
return fd;
}
op = WDIOS_DISABLECARD;
ret = ioctl(fd,WDIOC_SETOPTIONS,&op); //先停止看门狗计时器
if (ret < 0)
{
close(fd);
return ret;
}
timeout = atoi(argv[1]); //接收参数
ret = ioctl(fd,WDIOC_SETTIMEOUT,&timeout); //设置超时
if (ret < 0)
{
close(fd);
return ret;
}
op = WDIOS_ENABLECARD;
ret = ioctl(fd,WDIOC_SETOPTIONS,&op); //开启看门狗计时器
if (ret < 0)
{
close(fd);
return ret;
}
time = (timeout*1000-100)*1000; //设置喂狗时间间隔,在超时时间到来前100ms喂狗
printf("set timeout : %ds\n",timeout);
ret = 0;
while(1)
{
usleep(time); //喂狗时间间隔
ret++;
printf("KEEPALIVE---%d\n",ret);
ioctl(fd,WDIOC_KEEPALIVE,NULL); //喂狗
}
return 0;
}
程序运行结果如下图所示。
该程序运行起来之后,每隔一段时间,在看门狗将要到时前进行一次喂狗操作,这样系统就能正常运行。按下Ctrl+C终止该程序之后,内核打印信息如下。
watchdog watchdog0: watchdog did not stop!
说明看门狗计时器还在计时,没有因为程序的终止而停下,所以看门狗计时器会溢出,从而发生复位重启,由上图可以看到,计时器到时候开发板就自动重启了。
参考资料:
I.MX6U嵌入式Linux C应用编程指南V1.4——正点原子