• 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开发板自带光盘盘







  • 相关阅读:
    feign远程调用问题
    java8--stream
    feign业务组件远程请求 /oauth/token
    redis实现自增序列
    MySQL数据库 相关知识点
    netty
    spring的启动流程及bean的生命周期
    MethodHandleVS反射
    并发与并行
    关于注解的思考
  • 原文地址:https://www.cnblogs.com/11hwu2/p/3714967.html
Copyright © 2020-2023  润新知