智能家居项目

1、需求分析
  1)LED灯的控制
  2)视频功能
  3)音频功能
     mp3音乐
  4)监控是否有人闯入(红外)
     拿按键模拟 实现报警
  5)g-sensor 实现计步器
  6)在线升级
 
   产出物: 需求分析文档
2、概要设计
  硬件选型:
       cpu: s5p6818
   开放板:x6818
       操作系统: linux
 
  设计软件模块 ,以及模块之间的通信方式
3、详细设计
  每个模块中有哪些函数
  函数的参数、返回值 、出错处理
  函数的执行流程图 (viso)
 
4、编码


5、整合测试


6、项目的发布

1、开发环境的搭建
  cd /home/tarena
  mkdir project
  cd project/
     kernel
     rootfs
 
  安装交叉编译工具
 
  烧写到开发板 uImage 并运行
  nfs方式挂载根文件系统
     /home/tarena/project/rootfs  *(rw,sync,no_root_squash)
     sudo /etc/init.d/nfs-kernel-server restart
     
     setenv bootargs root=/dev/nfs nfsroot=192.168.1.8:/home/tarena/project/rootfs ip=192.168.1.6:192.168.1.8:192.168.1.1:255.255.255.0 console=ttySAC0,115200 maxcpus=1 lcd=wy070ml tp=gslx680-linux cam=OV5645
     saveenv
 


2、QT程序的移植
  2.1PC机上的QT开发环境
     cd workdir/qt/qt_dev
     
     ./qt-opensource-linux-x64-android-5.4.1.run
     
     安装路径 /opt/Qt5.4.1
     
     sudo vi /home/tarena/.bashrc
     PATH=$PATH:/opt/arm-cortex_a9-eabi-4.7-eglibc-2.18/bin/:/opt/Qt5.4.1/5.4/gcc_64/bin:/opt/Qt5.4.1/Tools/QtCreator/bin
     source /home/tarena/.bashrc
     
     使用qtcreator创建一个test工程
     在其中增加了确定按钮
     readelf -d test
        libQt5Widgets.so.5
        libQt5Core.so.5
  2.2 编译ARM版本的QT库
      http://doc.qt.io/qt-t/embedded-linux.html
 
      1)拿到QT源码  官方网站
      2)通过交叉编译工具编译ARM版本的QT库
         cd project/
         cp /home/tarena/workdir/qt/qt_src/qtbase-opensource-src-5.4.1.tar.xz ./
         tar xf qtbase-opensource-src-5.4.1.tar.xz
         cd qtbase-opensource-src-5.4.1
         vi mkspecs/linux-arm-gnueabi-g++/qmake.conf
        14 QMAKE_CC                = arm-cortex_a9-linux-gnueabi-gcc
        15 QMAKE_CXX               = arm-cortex_a9-linux-gnueabi-g++
        16 QMAKE_LINK              = arm-cortex_a9-linux-gnueabi-g++
        17 QMAKE_LINK_SHLIB        = arm-cortex_a9-linux-gnueabi-g++
        18
        19 # modifications to linux.conf
        20 QMAKE_AR                = arm-cortex_a9-linux-gnueabi-ar cqs
        21 QMAKE_OBJCOPY           = arm-cortex_a9-linux-gnueabi-objcopy
        22 QMAKE_NM                = arm-cortex_a9-linux-gnueabi-nm -P
        23 QMAKE_STRIP             = arm-cortex_a9-linux-gnueabi-strip
     ./configure --help 查看QT源码编译前的配置选项
    
         ./configure -prefix /home/tarena/project/qtlib -release -opensource -qt-libpng -qt-libjpeg -plugin-sql-sqlite -widgets -qt-sql-sqlite     -make libs -no-cups -no-nis -no-iconv -no-dbus -no-openssl -no-iconv -no-accessibility -no-sse2 -silent -xplatform linux-arm-gnueabi-g++ -nomake tools -nomake examples -nomake tests -qt-freetype -no-glib -strip -linuxfb -plugindir /home/tarena/project/qtlib/plugins
               执行./configure会根据配置生成合适的Makefile
               -prefix: 编译后的安装路径
                        make install
               -plugindir: 插件库的安装路径
               -xplatform linux-arm-gnueabi-g++
               //ls mkspecs/linux-arm-gnueabi-g++       
        make  -j4
        make install
  2.3 将QT程序移植到开发板运行
     2.3.1 编译ARM版本的可执行程序
        0)cd ......test/
        1)/home/tarena/project/qtlib/bin/qmake //生成合适的Makefile
        2) make clean
        3) make
     2.3.2 将可执行程序及库文件部署到开发板

注意:建议接下来的所有内容都拷贝rootfs/home
        1)mkdir /home/tarena/project/rootfs/home/bin -p
        2)cp test /home/tarena/project/rootfs/home/bin/
        3)mkdir /home/tarena/project/rootfs/home/qt
        4)cp /home/tarena/project/qtlib/lib/ /home/tarena/project/rootfs/home/qt/ -a
        5)mkdir /home/tarena/project/rootfs/home/lib
        6)cp /opt/arm-cortex_a9-eabi-4.7-eglibc-2.18/arm-cortex_a9-linux-gnueabi/sysroot/lib/*.so* /home/tarena/project/rootfs/home/lib/ -a
        7)cp /opt/arm-cortex_a9-eabi-4.7-eglibc-2.18/arm-cortex_a9-linux-gnueabi/sysroot/usr/lib/*.so* /home/tarena/project/rootfs/home/lib/ -a
     2.3.3 部署QT程序运行时使用的插件
        1)cp /home/tarena/project/qtlib/plugins/ rootfs/home/qt/ -a
   
     2.3.4 关于QT的配置文件
        1)mkdir rootfs/home/etc -p    
        2)cp /mnt/hgfs/project/env/qt.zip/profile rootfs/home/etc/
        3)关于profile
        #插件库路径
        export PATH=$PATH:/home/python/bin/
    export LD_LIBRARY_PATH=/home/python/lib:/home/opencv/lib:/home/qt/lib/:/home/lib/:$LD_LIBRARY_PATH
    
    export QT_QPA_PLATFORM_PLUGIN_PATH=/home/qt/plugins/
    export QT_QPA_PLATFORM=linuxfb:fb=/dev/fb0:size=1024x600:tty=/dev/fb0
    export QT_QPA_FONTDIR=/home/qt/lib/fonts
    export QT_QPA_GENERIC_PLUGINS=evdevkeyboard,evdevmouse,evdevtouch:/dev/input/event1

        4) 使配置生效
            source /home/etc/profile
      2.3.5 运行程序
            /home/bin/test
        
 关于开发板GUI界面程序中中文显示乱码的问题:
     env/font.zip
     1)rm /home/tarena/project/rootfs/home/qt/lib/fonts/* -rf
     1)cp /mnt/hgfs/project/env/font/DroidSansFallback.ttf /home/tarena/project/rootfs/home/qt/lib/fonts/   
     

练习: 将QT课程中的小游戏移植到开发板运行
问题:移植qt程序到开发板运行需要哪几步?
     
参考代码:ehome_final.tar.gz

3、LED的控制
  3.1 LED驱动程序
     实质就是一个linux字符设备驱动
     
     cd project
     mkdir drivers
     cd drivers
     mkdir leds
     cd leds
     vi led_drv.c
     vi Makefile
     make
     mkdir ../../rootfs/home/drivers
     cp leds_drv.ko  ../../rootfs/home/drivers/
     vi test.c
     
     编译test.c,测试驱动程序是否好用
     
  3.2 编写LED的控制应用程序
      有两种方式
      3.2.1 简单的方式
           当点击亮灯按钮时,完成该信号的槽函数
           在槽函数中
           open("/dev/leds", ...)
           ioctl(fd, CMD_LED_ON, &i);
           修改图片
           
           再次点击时灭灯
              open("/dev/leds", ...)
           ioctl(fd, CMD_LED_OFF, &i);
           修改图片
           
           GUI界面程序一定部署到开发板才能有效果
           
      3.2.2复杂方式
           希望GUI界面程序不管是运行在PC
           或者运行在开发板,都能控制开发板的LED状态
           
           client (GUI 开发板/PC)        Server (UDP 运行在开发板)
            点击按钮发送命令LED_ON         接收命令,根据命令open设备 ioctl亮灯
        
         1)界面客户端程序
           mkdir gui_client
           cd gui_client
           qtcreator
              建立工程 完成界面编程
              
              先将mainwindow窗口的大小调整文1024*600
              
              添加按钮
                  槽函数中给UDP服务器发送命令
        2)服务器程序
        
           cd project
           mkdir server
           cd server
           vi server.c  //接收命令 分发命令
              //udp server
              
              recvfrom(cmd)
              
              if(cmd == LED_ON)
                 open("/dev/leds",...)
                 ioctl(fd, LED_ON, 0)  
          vi leds.c: 根据命令不同调用操作灯的函数
          vi leds_hw.c:
                      open
                      ioctl
           
          arm-cor.....gcc leds.c leds_hw.c server.c -o server
          cp server rootfs/home/bin/
          启动server (开发板)
          启动client  点击按钮实验是否亮灯
    
    前三项在板子上运行
    insmod /home/drivers/leds_drv.ko
    source /home/etc/profile
    /home/bin/server &
    
    可以运行在开发板上页可以运行在PC
    /home/bin/client
    
   注意编程过程中的调试技巧
#define DEBUG
#ifdef DEBUG
    /*##表示如果可变参数被忽略或为空,将使预处理器( preprocessor )去除掉它前面的那个逗号。*/
    #define pr_debug(fmt, ...) printf(fmt, ##__VA_ARGS__)
#else
    #define pr_debug(fmt, ...)
#endif  


   BUG的调试,顺着数据流分析该调用的函数是否调用到
   
   gui界面程序点击按钮 发送命令函数是否被调用
      qWarning
   命令被正常接收?
      在server中打印收到的命令
   该命令被正常处理了吗?
      leds_operations
          leds
              ioctl
                  ----------
                    led_ioctl
   
   ehome_v1.tar.gz
   
   
4、视频服务器
   4.1摄像头的驱动
      uvc子系统: usb video class
                内核中自带了满足uvc格式的摄像头驱动
      如果你手中的摄像头满足uvc规范,该摄像头就是免驱
      只需要对内核进行配置,将uvc模块对应的代码
      编译到uImage
      
      如何判断摄像头满足uvc格式规范?
          lsusb
      再将摄像头插入开发板
          lsusb
          Bus 001 Device 003: ID 046d:0825  
          
          网络搜索 uvc官网 有一个的页面:
          列出了uvc框架支持的usb摄像头ID        
    配置内核,将uvc子系统编译进内核
        Device Drivers  --->
             <*> Multimedia support  --->
                  [*]   Video capture adapters  --->
                       [*]   V4L USB devices  --->  
                             <*>   USB Video Class (UVC)  
    make uImage
    让开发板加载包含uvc模块的新的内核
    
    ls /dev/video*
    再次插入摄像头
    发现多了一个video9 ,就是插入摄像头的设备文件
    4.2 应用程序
       4.2.1操作摄像头,抓取图像数据
           v4l2: video for linux ver2
                 
                 它属于摄像头软件的中间层
                 向下统一摄像头驱动的格式
                 向上为应用软件访问控制摄像头提供统一的接口
                 简化应用层软件控制摄像头的编程工作
           v4l2用户态编程怎么玩?
            
           v4l2提供的有小程序:v4l_demo.zip/capture.c
               open设备
               
               ioctl设置工作参数
               
               ioctl(fd, VIDIOC_STREAMON, &type);//开始摄像头开始工作
               
               // 获取图像数据
               ioctl(fd, VIDIOC_DQBUF, &buf);
               ioctl(fd, VIDIOC_QBUF, &buf);
         mjpeg-streamer包含了按照v4l2框架去操作摄像头的代码
               而且其中也包含了按照http协议向客户端发送图像数据的代码
               
           重点:移植部署运行mjpeg-streamer
       4.2.2mjpeg-streamer的移植:mjpg-streamer.tar.bz2
            cd project
            mkdir video
            cd video
            cp /mnt/hgfs/project/env/mjpg-streamer.tar.bz2 ./
            tar xf mjpg-streamer.tar.bz2
            cd mjpg-streamer/
            vi README
               make clean all
               ./mjpg-streamer ....
               start.sh
            vi Makefile
               CC = gcc
            find ./ -name "Makefile" -exec sed -i "s/CC = gcc/CC = arm-cortex_a9-linux-gnueabi-gcc/g" {} \;
                
                sed: 文件文件
                awk: 行处理  
                    结合正则表达式 功能非常强大
            make
       4.2.3 部署到开发板
            cp mjpg_streamer ../../rootfs/home/bin/
            cp *.so ../../rootfs/home/lib/ -a
            cp www/ ../../rootfs/home/ -r
       4.2.4 运行
             /home/bin/mjpg_streamer --help
             /home/bin/mjpg_streamer -i "input_uvc.so --help"
            /home/bin/mjpg_streamer -i "/home/lib/input_uvc.so -d /dev/video9 -y -r 320x240 -f 30" -o "/home/lib/output_http.so -w /home/www"        
               -i: 指定输入插件
                  -d: 指定访问的摄像头设备文件
                  -y: 采集图像的格式为YUYV
                  -r: 采集图像的大小
                  -f: 帧频率                  
               
               -o: 指定输出插件
                  -w: 网页资源文件所在目录         
               
            打开浏览器,输入“http://192.168.1.6:8080/”
           
           如果手里没有摄像头,可以使用如下方案
           /home/bin/mjpg_streamer -i "/home/lib/input_testpicture.so -r 320x240 -d 500" -o "/home/lib/output_http.so -w /home/www"
           input_testpicture.so不是采集摄像头数据
               自身有两张图片
               将这两张图片交替的发送给客户端
           打开浏览器,输入“http://192.168.1.6:8080/”
 
   4.3 mjpg-streamer 源码分析
      4.3.1 mjpg_streamer.c  高内聚 低耦合
            ctags -R *
         int main(int argc, char *argv[])
         {
             /*共享库的运行阶段加载有两种方式
               gcc xxx -o a.out -lpthread
               ./a.out 操作系统加载共享库
               
               程序中自主主动加载共享库(插件库)
             */
                                          "input_uvc.so"
             global.in[i].handle = dlopen(global.in[i].plugin, RTLD_LAZY)
                  /*找到input_uvc.so中的input_init函数对应代码在内存中的位置*/
             global.in[i].init = dlsym(global.in[i].handle, "input_init");
             global.in[i].run = dlsym(global.in[i].handle, "input_run");
             /*执行input_uvc.so中的input_init函数*/
             global.in[i].init(&global.in[i].param, i)
             
                                           /*"output_http.so"*/
             global.out[i].handle = dlopen(global.out[i].plugin, RTLD_LAZY);
             global.out[i].init = dlsym(global.out[i].handle, "output_init");
             global.out[i].run = dlsym(global.out[i].handle, "output_run");
             global.out[i].init(&global.out[i].param, i)
             
             global.in[i].run(i)
             global.out[i].run(global.out[i].param.id);
             
             pause();
         return 0;    
    
         }   
    4.3.2 输入插件plugins/input_uvc/
          按照v4l2编程步骤去操作uvc格式的摄像头
          官方例程:capture.c
          Video for linux 2 example (v4l2 demo) - MetalSeed - 博客频道 - CSDN.NET.png
          
          vi plugins/input_uvc/input_uvc.c
             /*打开摄像头 设置工作参数*/
             int input_init(input_parameter *param, int id)
             {
                 init_videoIn(cams[id].videoIn, dev, width, he    ight, fps, format, 1, cams[id].pglobal, id)
                 {
                     init_v4l2(vd)
                     {
                        /*打开"/dev/video9"设备文件*/
                        vd->fd = OPEN_VIDEO(vd->videodevice, O_RDWR)
                        /*查询当前硬件的工作能力*/
                        xioctl(vd->fd, VIDIOC_QUERYCAP, &vd->cap)
                        /*图像格式设置*/
                        ret = xioctl(vd->fd, VIDIOC_S_FMT, &vd->fmt);
                        ...
                     }
                 }
             }
             int input_run(int id)
             {
                 pthread_create(&(cams[id].threadID), NULL, cam_thread, &(cams[id]));   
             }
             void *cam_thread(void *arg)
             {
                 while(!pglobal->stop)
                 {
                     uvcGrab(pcontext->videoIn)
                     {
                         video_enable(vd)
                         {
                             /*VIDIOC_STREAMON:让摄像头开始工作*/
                             ret = xioctl(vd->fd, VIDIOC_STREAMON, &type);
                         }
                         /*获取一帧图像*/
                         xioctl(vd->fd, VIDIOC_DQBUF, &vd->buf)
                         
                     }
                     /* copy JPG picture to global buffer */
                      memcpy_picture(pglobal->in[pcontext->id].buf, pcontext->videoIn->tmpbuffer, pcontext->videoIn->buf.bytesused);
                 }
             }

BUG修改:input_uvc/v4l2uvc.c
428     do{
429     memset(&vd->buf, 0, sizeof(struct v4l2_buffer));
430     vd->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
431     vd->buf.memory = V4L2_MEMORY_MMAP;
432
433     ret = xioctl(vd->fd, VIDIOC_DQBUF, &vd->buf);
434     if(ret < 0) {
435         perror("Unable to dequeue buffer");
436        // goto err;
437     }
438     }while(ret < 0);

      4.3.3 输出插件 plugins/output_http/
           将图像数据封装成http数据包
           通过TCP方式下客户端发送
           
           vi plugins/output_http/output_http.c
              int output_init(output_parameter *param, int id)
              {
                  port = htons(8080);
                  ...
              }
          int output_run(int id)
          {
          pthread_create(&(servers[id].threadID), NULL, server_thread, &(servers[id]));
          }
        void *server_thread(void *arg)
        {
            socket(aip2->ai_family, aip2->ai_socktype, 0)
            bind(pcontext->sd[i], aip2->ai_addr, aip2->ai_addrlen)
            listen(pcontext->sd[i], 10)
            while(!pglobal->stop)
            {
                accept(pcontext->sd[i], (struct sockaddr *)&client_addr, &addr_len)
                pthread_create(&client, NULL, &client_thread, pcfd)
            }
        
        }
        void *client_thread(void *arg)
                {
                  _readline(lcfd.fd, &iobuf, buffer, sizeof(buffer) - 1, 5)
                  {
                     _read(fd, iobuf, &c, 1, timeout)
                     {
                         read(fd, &iobuf->buffer, IO_BUFFER)
                     }
                  }
                  
                  else if(strstr(buffer, "GET /?action=stream") != NULL)
                  {
                      req.type = A_STREAM;//确定客户端请求类型
                  }
                  
                   switch(req.type) {
                          case A_STREAM:
                               send_stream(lcfd.fd, input_number)
                               {
                                    while(!pglobal->stop)
                                    {
                                       /**/
                                       write(http头信息)
                                       /*将pglobal->buf数据拷贝到 frame缓冲区*/
                                        memcpy(frame, pglobal->in[input_number].buf, frame_size);
                                        write(fd, frame, frame_size)
                                        write(http尾信息)
                                    }
                               }
                               break;
              }


练习:
     1)在开发板上假设好视频服务器
     2)编程实现一个tcp client
        向服务器发送 "GET /?action=stream HTTP/1.1\r\n\r\n"
        recv(sd, buf, 1024)
        printf("%s\n",buf) ;
        
        结束            
              
              
              
              
                            
5、视频客户端
  5.1 HTTP协议:http.zip
      超文本传输协议
      
      建立TCP传输的基础上
      
      客户端要给服务器端发送请求request
      
      服务器端根据客户端的请求回送响应response
  5.2 对mjpg-streamer的分析得到以下内容
      如果客户端发送的请求中包含"GET /?action=stream"
      服务器就会将视频数据封装HTTP格式数据帧
      不断发送给客户端
      按照http 协议 request的数据格式
      
      就是给服务器发送"GET /?action=stream HTTP/1.1\r\n\r\n"
  5.3 编写一个tcp客户端程序
      code_for_mjpgstreamer.rar
     1)保证客户端和服务器能够联通
      serverip : 开发板IP
      端口号:   8080
      给服务器发送请求 "GET /?action=stream HTTP/1.1\r\n\r\n"
      读取一次服务器返回的数据,并将数据打印
     2)在1)的基础上,不断地读取数据
        把读到的数据保存/tmp/test.jpg文件
        保存1M数据后程序退出
     3)能不能把http头信息过滤掉 把http尾信息过滤掉
        只将图像信息内容保存到/tmp/test.jpg文件去
        
        hexdump -C /tmp/test.jpeg |less
        
        服务器返回的图像是jpeg格式的
        jpeg图像帧在存储时是有固定的格式
        JPEG是一种有损的图像压缩算法
        JPEG文件由两部分组成: 标记码 压缩数据
        
        FF D8 .... ... .... FF D9

        参考code_for_mjpgstreamer.rar/03代码
    4) 编写GUI客户端显示图像数据
       
       camer:该类负责和HTTP server进行通信
              连接服务器
                  connectToHost
              给服务器发送请求"GET /?action=stream HTTP/1.1\r\n\r\n"
                  requestImage
              接收server返回的数据
              并从中过滤出"ff d8 .... ff d9"
                  void CamClient::readImage()
                  {
                      接收数据
                      过滤出图像
                      发送信号 newImageReady(image)给mainwindow
                  }
              void MainWindow::showNewImage(QImage img)
          {
         /*显示图片*/
         ui->imageLabel->setPixmap(QPixmap::fromImage(img));
          }
              通信过程中使用了QTcpSocket
              使用QBtyeArray 缓存图像数据
              显示图像使用了QImage
              
                  注意1:SIGNAL(readyRead())
                  
                  当收到readyRead()信号时,去调用对应的槽函数接收数据,过滤数据
              
                  camer::camer(QObject *parent) :
 
                QObject(parent)
        {
            connect(&tcpSocket,SIGNAL(readyRead()),this,SLOT(readImage()));
        }
        
        注意2: 自定义的信号
          CamClient 当过滤到一个完整的图像帧发出的
          MainWindow负责处理该信号  
     
     
     视频数据的压缩 H264
     参考代码: 04 视频客户端
               ehome_v2.tar.gz : LED + 视频客户端
             
     
6、红外报警功能
  常见的人体红外感应模块HC-SR501
  用按键来模拟红外
  当按键按下,有人闯入,需要报警
  报警:
       beep响 (选择)
       发一个短信 (gsm)
  6.1驱动程序
     按键驱动
         drivers/buttons
         
     蜂鸣器驱动
         建议按照混杂设备的架构完成
         drivers/beep
         
  6.2应用程序
     实时监控,满足条件就报警
     使用多线程/Qtimer
       去轮询红外(按键)是否有数据,有数据报警
       
     使用定时器间隔一段时间就给server发命令,读是否有按键值  
     如果有键值,给server发蜂鸣器响的命令
     客户端程序: client/
     服务器程序: server/beep.c
                         beep_hw.c
                         leds.c
                         leds_hw.c
         
     
     . /home/etc/profile           
     如果看视频数据
       /home/bin/start.sh &
     安装设备驱动
       insmod home/drivers/beep_drv.ko
       insmod home/drivers/leds_drv.ko
       insmod home/drivers/btn_dev.ko
       insmod home/drivers/btn_drv.ko
       
       按键的驱动:
           1)内核中原有的按键驱动裁剪
           2)对应的设备文件
              cat /proc/bus/input/devices
              
               open("/dev/input/eventX",O_RDONLY|O_NONBLOCK);

     启动udp server 接收客户端发送的命令
         /home/bin/server &   
     
     ehome_v3.tar.gz           
7、MP3功能的实现  
  完成mp3文件的解码工作,
     MP3_format_parse.doc
  将解码之后的数据写入声卡设备
     Linux_sound_guide.doc
     mp3/ALSA Audio API 使用指南(中英版) - 幻雪神界 - 博客频道 - CSDN.NET.html
        
  7.1基本概念
     PCM: 脉冲编码调制
          声音是模拟量
          计算机能处理的是数字量,涉及模拟量和数字量的相互转换
          录音时是模拟量转数字量
          播放时是数字量转模拟量
     采样频率:
          每秒钟抽取声波幅度样本的次数
          当采样频率应该在40kHz左右,保证声音不失真
     量化位数:
          每次采样,模拟音频信号的振幅用多少个bit来表示
          一般使用16bit       
     声道数:
          有单声道和双声道之分,双声道又称为立体声  
     MP3:
          音频数据的压缩算法
          有损压缩(还原后是有失真的)
          压缩比可以接近10:1-12:1
          使压
          缩后的文件在回放时能够达到比较接近原音源的声音效果  
  7.2 MP3文件的解码工作    
      MP3_format_parse.doc
      其存储格式:
          TAGV2
              {
                。。。
              }
          FRAME
              {
                  帧头
                  {
                      。。。。
                  }
                  数据实体
              }
          FRAME
          ...
          TAGV1
                  {
                      。。。
                  }
      mp3文件的解码工作,可以使用开源的库完成
         mp3.rar/libmad-0.15.1b.tar.gz
      libmad库的使用步骤:
         1)cd project/
         2)mkdir mp3
            cd mp3
         3)cp /mnt/hgfs/project/env/mp3/libmad-0.15.1b.tar.gz ./
         4)tar xf libmad-0.15.1b.tar.gz
         5) ./configure --help
            ./configure CC=arm-cortex_a9-linux-gnueabi-gcc --host=arm-linux --prefix=/home/tarena/project/mp3/install
                --host: 运行目标平台
                --prefix: 安装路径 make install
            用来生成合适的Makefile
         6) make && make install
         7) minimad.c
            libmad API使用的演示demo
               从标准输入获取要解码的数据
               进行解码
               解码后的数据打印到标准输出设备
           arm-cortex_a9-linux-gnueabi-gcc minimad.c -o player -L ../install/lib/ -lmad
              如果出现“../install/lib//libmad.so:对‘III_imdct_l’未定义的引用”
              
              解决方式:
                  make clean
                  make
                  make install
                  arm-cortex_a9-linux-gnueabi-gcc minimad.c -o player -L ../install/lib/ -lmad
                      
               
               
               
               计划将解码之后的数据输出到声卡设备                     
    7.3 声卡的驱动程序
        linux下的声卡驱动分两种:
             OSS: Open Sound System
                  最早出现的
                  非开源程序
                  /dev/dsp
                  /dev/mixer
                  
                  open("/dev/dsp",...)
                  ioctl()设置采样频率 量化位数 声道数
                  write(解码后的数据)
                  
             ALSA: 开源    
       make menuconfig
            Device Drivers  --->
                 <*> Sound card support  --->
                     <*>   Advanced Linux Sound Architecture  --->
      ls /dev/snd/
         controlC0  
         controlC1  
         pcmC0D0c   
         pcmC0D0p   
         pcmC1D0p   
         timer
    7.4 alsa库的使用
     
     如果是alsa架构,提供了一套用户空间访问以上设备文件
     的库函数alsa-lib-1.1.1.tar.bz2
     
     alsa库封装了对/dev/snd下设备文件的操作
     编译步骤:
         1) cd mp3/
         2) cp /mnt/hgfs/project/env/mp3/alsa-lib-1.1.1.tar.bz2 ./
             tar xf alsa-lib-1.1.1.tar.bz2
             cd alsa-lib-1.1.1/
         3) ./configure CC=arm-cortex_a9-linux-gnueabi-gcc --host=arm-linux --prefix=/home/tarena/project/mp3/install  --with-configdir=/opt/alsa --with-plugindir=/opt/alsa_lib
          4) make && make install
         
      以上库函数的使用可以参考
         alsa使用官网:http://www.alsa-project.org/alsa-doc/alsa-lib/index.html
                 http://www.alsa-project.org/main/index.php/Download
         或者参考:
             ALSA Audio API 使用指南(中英版) - 幻雪神界 - 博客频道 - CSDN.NET.html
      
    以上两个库libmad alsa库的使用实例mp3_player.c
    
    mp3_player.c和minimad.c相比修改以下内容
       ./player xxx.mp3
       
       1)原来从标准输入读取解码数据
         现在从xxx.mp3文件中读取解码数据
       2)设置声卡的工作属性 44.1KHz 16bit 双声道
          set_pcm() //参考文档中“一个最简单的回放程序”
               {
                  /*打开声卡设备*/
                  rc=snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
                  //硬件设置为交错模式
                  snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
                   //设置16位采样精度
                  rc=snd_pcm_hw_params_set_format(handle, params, SND_PCM _FORMAT_S16_LE);  
                  //设置声道,1表示单声>道,2表示立体声
                  rc=snd_pcm_hw_params_set_channels(handle, params, channels);
                  //设置频率  
             snd_pcm_hw_params_set_rate_near(handle, params, &rate, &dir);  
               }
       
       3)修改了output
          将解码的音频数据输出到声卡设备
       注意:2) 3) 参考 ALSA Audio API 使用指南(中英版) - 幻雪神界 - 博客频道 - CSDN.NET.html
          
          /*libmad解码的音频帧数据写入声卡设备*/
          snd_pcm_writei(handle, OutputPtr, n);
      
      编译 部署 运行 player程序
         1)arm-cortex_a9-linux-gnueabi-gcc mp3_player.c  -lmad -lasound  -L install/lib/ -I install/include/ -o player
            出现了以下错误:
                  libmad.so : III_....
            解决方法:
                  cd libmad 源码目录
                  make clean
                  make
                  make install
                  arm-cortex...... mp3_player.c      
                  
     2)cp player ../rootfs/home/bin/
     3)cp  install/lib/* ../rootfs/home/lib/ -a
     4)配置文件和插件库文件
       注意,配置时指定了路径
       --with-configdir=/opt/alsa --with-plugindir=/opt/alsa_lib
       部署到开发板上时要保持一致
       cp /opt/alsa/ ../rootfs/opt/ -a
       cp /opt/alsa_lib/ ../rootfs/opt/ -a
     5)  mkdir ../rootfs/home/songs
        cp /mnt/hgfs/project/env/mp3/*.mp3 ../rootfs/home/songs/
     6) 运行
        . /home/etc/profile
        /home/bin/player /home/songs/I\ Will\ Always\ Love\ You Always\ Love\ You.mp3 1>/dev/null 2>&1  
        
     练习:
       1)看懂mp3_player.c
       2)编译 部署到开发板运行
       3)/home/bin/player /home/songs/xxx.mp3
          
          //将/home/songs目录下的所有.mp3文件播放一遍
          /home/bin/player /home/songs
          
          参考ehome_final.tar.gz/server/ myplayer*.c
              有目录操作
   7.5 加一个简单的GUI界面
       
       播放:  
             1)开启新线程开始播放
                 或者
             2) 唤醒被暂停的播放线程
                 pthread_cond_signal(&conti);  
       暂停:stat=1
             修改了播放线程中的output
             
             //alsa声卡的暂停,将缓冲区中提提交声卡播放数据全部提交给声卡
              snd_pcm_drop(handle);

             //让播放线程进入睡眠状态, 暂停了播放
              pthread_cond_wait(&conti, &lock);
             
             当再次收到播放命令 会执行到“ pthread_cond_signal(&conti); ”
             //设置声卡处于就绪状态
             snd_pcm_prepare(handle);
              
       上一首
             MUSIC_NEXT  music_stat =2
             播放线程
             music_play()
             {
                 while(遍历歌曲)
                 {
                   decode()
                   {
                              out_put
                             {
                                return MAD_FLOW_STOP//导致decode()执行完毕
                             }
                           }
                           munmap
                           改变cur_pos //标识要播放的目录中的第几首歌
                           
                       }
                 }
             
             
       下一首            
            MUSIC_PREV  music_stat =3
            播放线程
             music_play()
             {
                 while(遍历歌曲)
                 {
                   decode()
                   {
                              out_put
                             {
                                return MAD_FLOW_STOP//导致decode()执行完毕
                             }
                           }
                           munmap
                           cur_pos = cur_pos?(cur_pos-1):0; //标识要播放的目录中的第几首歌
                           
                       }
                 }
                 
     步骤:
          . /home/etc/profile
          /home/bin/start.sh  &
          insmod /home/drivers/beep_drv.ko
          insmod /home/drivers/btn_drv.ko
          insmod /home/drivers/btn_dev.ko
          insmod /home/drivers/leds_drv.ko
          /home/bin/server &
8、计步器的功能实现
   使用g-sensor实现
   
   如何算一步:
       只要xyz任意一轴和上个周期比有了一个比较大的变化
       
   8.1 g-sensor的驱动程序
   
   8.2 应用程序 C/S架构
      c/s: client/server  : qq
      b/s  browser/server : 网页版游戏
      
      
      client: qt gui
              周期性(博尔特)的给服务器发送命令
              让服务器不断的读取g-sensor的数据
              客户端如果收到1,步数+1 ,并显示
              可以使用Qtimer
      server:
              收到相应的命令,读取g-sensor的数据
              上次读到的结果进行比较
              如果变化比较大(人为定义),
              给客户端返回1,否则返回0        
              
              数据的比较可以放在服务器上完成
              其实也可以放在客户端完成
      
    步骤:  
      source /home/etc/profile
      insmod  /home/drivers/beep_drv.ko
      insmod  /home/drivers/btn_dev.ko
      insmod  /home/drivers/btn_drv.ko
      insmod  /home/drivers/leds_drv.ko
      insmod  /home/drivers/mma8653_drv.ko
      /home/bin/start.sh &
      /home/bin/server &     
     
9、项目的发布
  9.1 配置脚本,实现自启动
      /etc/init.d/rcS: 开机自启动的程序可以放入该脚本
      /etc/profile: 全局对所有用户有效的环境变量
      
      开机自启动:
          vi rootfs/etc/init.d/rcS
             最后加入
             
             exec /home/etc/rcS
          vi rootfs/home/etc/rcS
              #配置开发板IP
        ifconfig eth0 192.168.1.6
        ifconfig lo up
        #加载内核模块
        find /home/drivers/ -name *ko -exec insmod {} \;
        
        
        source /home/etc/profile
        
        #启动视频服务器
        /home/bin/start.sh &
        
        #启动UDP服务器
        /home/bin/server &
        #启动GUI界面程序
        /home/bin/client &
          chmod +x rootfs/home/etc/rcS
      环境变量的设置
          vi rootfs/etc/profile
               . /home/etc/profile
   9.2 分区的规划
       前提条件,该分区能够存储下要存储文件
       
       0--------1M---------------17M--------------------273M---------------->剩余
扇区号        0x800            0x8800                  0x88800
         uboot        kernel                rootfs                 userdata
       fdisk 2 3 0x100000:0x1000000  0x1100000:0x10000000  0x11100000:0
      9.2.1 制作uImage 并完成烧写
            tftp 48000000 uImage
            mmc write 48000000 0x800 0x3000
      9.2.2 制作ext4类型个文件系统 并烧写
            
            #创建256M大小的rootfs.ext4文件
            dd if=/dev/zero of=rootfs.ext4 bs=1k count=262144      
            du -h rootfs.ext4
            sudo mkfs.ext4  rootfs.ext4
            sudo mkdir /mnt/ext4
            sudo mount -t ext4 -o loop rootfs.ext4 /mnt/ext4
            sudo cp rootfs/* /mnt/ext4/ -a
            sudo umount /mnt/ext4
            
            cp rootfs.ext4 /tftpboot/
            tftp 0x48000000 rootfs.ext4
            mmc write 48000000 0x8800 0x80000
      9.2.3 启动参数的修改
           setenv bootargs root=/dev/mmcblk0p2 init=/linuxrc console=ttySAC0,115200 rootfstype=ext4  lcd=wy070ml tp=gslx680-linux cam=OV5645  maxcpus=1
           saveenv
          
      系统启动后:
          ls /dev/mmcblk0*
          cat /proc/partitions
          mkfs.vfat /dev/mmcblk0p3
          mount /dev/mmcblk0p3 /mnt/   
            

10、系统功能升级
   通过网络进行升级
   
   通过U盘来实现升级功能
   10.1 U盘的手工挂载
       ls /dev/sd*
       插入U盘
       ls /dev/sd*
       
        mount /dev/sda1 /mnt
        umount /mnt
   10.2 u盘自动挂载
        env/usb.rar
        
        /dev/sda1设备文件自动创建
        是由于热插拔事件产生,导致mdev程序被执行
        由mdev来去创建的设备文件
        其实可以通过设置让mdev在去创建/dev/sda1设备文件
        的同时,可以完成u盘的自动挂载
        问题1:如何配置让mdev既可以自动创建设备文件/dev/sda1
               又可以创建后自动挂载u盘?
            mdev.conf
        问题2: 如何修改mdev.conf ,语法格式?
             vi busybox-1.23.2/docs/mdev.txt
                 sd[a-z][0-9] 0:0 666 @/home/usb/usb_insert.sh /dev/$MDEV
                 
                     sd[a-z][0-9], 设备文件的规则,满足该规则的
                                   sda1 sdb2
                                   sda 不满足
                     0:0 , uid:gid
                     666,  权限
                     @, 创建设备文件
                     /home/usb/usb_insert.sh /dev/$MDEV,
                        创建sda1/sdb2...设备文件时执行/home/usb/usb_insert.sh脚本,
                        并且传递参数/dev/sda1(sdb2)
                sd[a-z] 0:0 666 $/home/usb/usb_remove.sh
                   $, 销毁设备文件之前                
                 
                 
             
             
        实验步骤:env/usb.rar
            1)在rootfs/etc/mdev.conf          
               cp /mnt/hgfs/project/env/usb/usb/mdev.conf  rootfs/etc/
               
            2) mkdir rootfs/home/usb -p
               cp /mnt/hgfs/project/env/usb/usb/usb_insert.sh rootfs/home/usb/
               创建挂载点
               mkdir rootfs/mnt/usb
            3)cp /mnt/hgfs/project/env/usb/usb/usb_remove.sh rootfs/home/usb/
  10.3手工操作将u盘中uImage 更新到
      dd if=/mnt/usb/uImage of=/dev/mmcblk0p1
         输入文件 if指定
         输出到哪去 of
  10.4当点击按钮时自动更新
      system("dd if=/mnt/usb/uImage of=/dev/mmcblk0p1");

      GUI: 给服务器发送命令 m
      server: 收到命令m
            执行system("dd if=/mnt/usb/uImage of=/dev/mmcblk0p1");
   参考:ehome_v7.tar.gz

猜你喜欢

转载自www.cnblogs.com/DXGG-Bond/p/11965928.html