mini2440应用例程学习(一)—— led-player

环境:mini2440开发板

一、led-player程序在mini2440上运行的流程:

①  开机进入系统后,将会自动运行一个LED服务程序(/etc/rc.d/init.d/leds),leds是一个脚本文件,它调用了并运行/usr/bin/led-player。以下是leds脚本的内容:
#!/bin/sh
base=led-player //定义一个变量方便引用led-player程序
# See how we were called. case "$1" in //开关机时传递的参数,开机为start,关机为stop start) /usr/bin/$base & //开机运行该脚本时自动运行led-player于后台 ;; stop) //关机运行该脚本时终止led-player进程 pid=`/bin/pidof $base` //获取led-player进程号(反引号在shell中表示执行命令) if [ -n "$pid" ]; then //若字符长度非0,则执行kill -9 pid(led-player进程号) kill -9 $pid fi ;; esac exit 0
②  led-player运行时会在/tmp目录下创建一个led-control管道文件,如果没有向管道写入规定参数,则执行默认累加器模式。向该管道文件发送不同的参数就可以改变led的闪烁模式:
#echo 0 0.2 >/tmp/led-control
运行该命令后,4个用户led将会以每个间隔0.2秒的时间运行跑马灯;
#echo 1 0.2>/tmp/led-control
运行该命令后,4 个用户led 将会以间隔 0.2秒的时间运行累加器;
#/etc/rc.d/init.d/ledsstop
运行该命令后,4 个用户led 将会停止闪动;
#/etc/rc.d/init.d/ledsstart
运行该命令后,4 个用户led 将会重新开始闪动。

 
二、led-player.c源码文件解读分析:

1)重温基础知识:
①  static修饰词用来声明变量,如果没有初始化变量值,则默认为0;
②  open函数的原型为int open(const chat *pathname,intoflag,…/*多个参数*/),
oflag参数常用取值:
O_RDONLY:只读打开
O_WRONLY:只写打开
O_RDWR:读、写打开
注:很多实现将O_RDONLY定义为0,O_WRONLY定义为1,O_RDWR定义2,以与早期的系统兼容。
O_NONBLOCK:如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的I/O操作设置非阻塞方式。
③  close函数:函数原型为int close(int filedes); filedes参数为文件的描述符,当一个进程终止时,它所有的打开文件都由内核自动关,很多程序一般不显示地使用close关闭打开的文件。
④  perror函数:函数原型为void perror(const char *s);perror( ) 用来将上一个函数发生错误的原因输出到标准设备(stderr)。参数s所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno 的值来决定要输出的字符串。
⑤  unlink函数:函数原型为int unlink(const char *pathname);此函数删除一个现存的目录项,并将由pathname所引用的文件的链接计数减1。如果该文件还有其他链接,则仍可以通过其他链接存取该文件的数据。如果出错,则不对该文件作任何操作。
⑥  mkfifo函数:函数原型为int mkfifo(const char *pathname,mode_tmode);FIFO称为命名管道。管道只能由相关进程使用,它们的共同祖先进程创建了管道。但是通过FIFO,不相关的进程也能交换数据。mkfifo函数用于创建FIFO文件,其中mode参数的规格说明与open函数中的mode相同。
⑦  select函数:函数原型为int select(int nfds,fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);被定义在sys/select.h文件里,使用此函数需要要包含头文件sys/time.h。nfds为需要检查的号码最高的文件描述符加1;readfds为由select()监视的读文件描述符集合;writefds为由select()监视的写文件描述符集合;exceptfds为由select()监视的异常处理文件描述符集合;timeout为超时等待时间。为了维护fd_set类型的参数,会使用下面四个宏:FD_SET(),FD_CLR(), FD_ZERO() 和 FD_ISSET()。这个函数的返回值:
    1)  返回-1表示出错。例如在所指定的描述符都没有准备好时捕捉到一个信号;
    2)  返回0表示没有描述符准备好。若指定的描述符都没有准备好,而且指定的时间已经超过,则发生这种情况。
    3)  返回一个正值说明已经准备好的描述符数。在这种情况下,三个描述符集中仍旧打开的位是对应于已准备好的描述符位。
⑧  fd_set类型:其实是select()机制中的一种数据结构。详见华清远见的《嵌入式Linux应用程序开发详解》。其中select()函数常用的文件描述符处理宏函数有以下几个:
FD_ZERO(fd_set*set):清除一个文件描述符集;
FD_SET(intfd,fd_set *set):将一个文件描述符加入文件描述符集中;
FD_CLR(intfd,fd_set *set):将一个文件描述符从文件描述符集中清除;
FD_ISSET(intfd,fd_set *set):测试该集中的一个给定位是否有变化。
一般来说,在使用select函数之前,首先使用FD_ZERO和FD_SET来初始化文件描述
符集,在使用了select函数时,可循环使用FD_ISSET测试描述符集,在执行完对相关文件描述符测试后,可以使用FD_CLR来清除相应的描述符集。
注:为方便理解select工作机制最好查阅《Unix环境高级编程》的第12章高级I/O。
⑨  struct timeval结构体:结构体原型为
struct timeval
{
    long tv_sec;       /* Seconds. */
    long tv_usec;  /*Microseconds. */
};
在select机制中应用时有以下三种情况:
    1)  timeout=NULL:永远等待。如果捕捉到一个信号则中断此无限期等待。当所有指定的描述符中的一个已准备好或捕捉到一个信号则返回。如果捕捉到一个信号,则select返回-1,errno设置为EINTR。
    2)  timeout->tv_sec==0&&timeout->tv_usec==0:完全不等待。测试所有指定的描述符并立即返回。这是得到多个描述符的状态而不阻塞select函数的轮询方法。
    3)  timeout->tv_sec!=||timeout->tv_usec!=0:等待指定的秒数和微秒数。当指定的描述符之一已准 备好,或当指定的时间值已经超过时立即返回。如果在超时时还没有一个描述符准备好,则返回值是0,(如果系统不提供微秒分辨率,则 timeout->tv_usec==0值取整到最近的支持值。)与第一种情况一样,这种等待可被捕捉到的信号中断。
⑩   ioctl函数:函数原型为int ioctl(int fd, unsigned long cmd, …) ;ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设 备的控制命令,后面的省略号是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。
⑪  memset函数:函数原型为void *memset(void *s, int ch, size_t n); 将s中前n个字节(typedef unsigned int size_t)用 ch 替换并返回 s 。
⑫  read函数:函数原型为ssize_t read(int filedes,void *buff, size_t nbytes);如read成功,则返回读到的字节数,如已到达文件的尾端,则返回0.
⑬  sscanf函数:函数原型为int sscanf(const char *buf,const char *format, …);从buf里按照format格式读取相应类型的数据到指定地方。
⑭  fprintf函数:函数原型为int fprintf(FILE *fp, const char *format, …);函数将格式化的字符串输出到指定文件fp中。函数成功则返回格式化输出的字节数。(不包括字符串的结尾’’),出错返回一个负值,错误原因在error中。
⑮  stderr:标准错误输出,默认输出到终端窗口,文件描述符为2。

(2)led-player源代码及注释
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <string.h>

static int led_fd;      //静态变量,没有初始化则默认为0
static int type = 1;

static void push_leds(void)
{
        static unsigned step;
        unsigned led_bitmap;
        int i;

        switch(type) {
        case 0:             //当type变量值为0时,实现循环流水灯效果,即1-2-3-4-3-2-1如此循环
                if (step >= 6) {
                        step = 0;
                }
                if (step < 3) {        
                        led_bitmap = 1 << step;
                } else {
                        led_bitmap = 1 << (6 - step);
                }
                break;
        case 1:    /*默认type变量值为1,实现累加器功能,即0000-0001-0010-0011-0100-0101...依次累加(1表示灯亮)*/
                if (step > 255) {
                        step = 0;
                }
                led_bitmap = step;     //此时led_bitmap为8位二进制数
                break;
        default:
                led_bitmap = 0;
        }
        step++;      //每次调用执行本函数则step自加1
        for (i = 0; i < 4; i++) {               
                ioctl(led_fd, led_bitmap & 1, i);   //控制leds设备,分四次写入相应的值,估计i是写入驱动设备的位置参数
                led_bitmap >>= 1;
        }
}

int main(void)
{
        int led_control_pipe;
        int null_writer_fd;    // for read endpoint not blocking when control process exit

        double period = 0.5;

        led_fd = open("/dev/leds0", 0);           //以只读方式打开设备leds0
        if (led_fd < 0) {
                led_fd = open("/dev/leds", 0);     //mini2440开发板上的是这个设备
        }
        if (led_fd < 0) {
                perror("open device leds");
                exit(1);
        }
        unlink("/tmp/led-control");                //删除指定文件
        mkfifo("/tmp/led-control", 0666);      //创建指定命名管道文件,权限为0666(首位0表示这个数为八进制数)

        led_control_pipe = open("/tmp/led-control", O_RDONLY | O_NONBLOCK);
        if (led_control_pipe < 0) {
                perror("open control pipe for read");
                exit(1);
        }
        null_writer_fd = open("/tmp/led-control", O_WRONLY | O_NONBLOCK);
        if (null_writer_fd < 0) {
                perror("open control pipe for write");
                exit(1);
        }

        for (;;) {
                fd_set rds;
                struct timeval step;
                int ret;

                FD_ZERO(&rds);                            //将rds描述符集清零
                FD_SET(led_control_pipe, &rds);     //将rds描述符集中描述符led_control_pipe对应的位置1
                step.tv_sec  = period;        
                step.tv_usec = (period - step.tv_sec) * 1000000L;

                ret = select(led_control_pipe + 1, &rds, NULL, NULL, &step);  //只关心readfds描述符集,其他两个为NULL
                if (ret < 0) {                                 
                        perror("select");
                        exit(1);
                }
                if (ret == 0) {                                      //指定等待时间已超过而指定的描述符还没准备好
                        push_leds();
                } else if (FD_ISSET(led_control_pipe, &rds)) {
                        static char buffer[200];
                        for (;;) {
                                char c;
                                int len = strlen(buffer);
                                if (len >= sizeof buffer - 1) {
                                        memset(buffer, 0, sizeof buffer);         //对buffer数组初始化
                                        break;
                                }
                                if (read(led_control_pipe, &c, 1) != 1) {   //从命名管道文件led-control中读取一个字节
                                        break;
                                }
                                if (c == '
') {                              //读出字符为回车符,则忽略后面的语句,重新循环
                                        continue;
                                }
                                if (c == '
') {                             //读出字符为换行符
                                        int tmp_type;
                                        double tmp_period;
                                        if (sscanf(buffer,"%d%lf", &tmp_type, &tmp_period) == 2) {
                                                type = tmp_type;
                                                period = tmp_period;
                                        }
                                        fprintf(stderr, "type is %d, period is %lf
", type, period);     //将参数type、period输出到终端显示
                                        memset(buffer, 0, sizeof buffer);        //成功取完一次参数,将buffer数组清零
                                        break;
                                }
                                buffer[len] = c;        //将读到的字符写入buffer数组(前面的程序已经实现舍弃回车符和换行符)
                        }
                }
        }

        close(led_fd);        //关掉leds设备
        return 0;
}

参考资料:
           ①《Unix环境高级编程》
           ②《嵌入式Linux应用程序开发详解》(华清远见-电子版)
           ③《C Primer Plus中文版》
           ④
mini2440用户手册
           ⑤
源码来自mini2440开发板自带光盘盘







原文地址:https://www.cnblogs.com/11hwu2/p/3714967.html