13. Ubuntu下Eclipse 开发嵌入式Linux

13.1. Ubuntu安装Eclipse

  1. 下载Eclipse:Eclipse官网下载链接: https://www.eclipse.org/downloads/packages/

    ../../../../_images/image-20201113092805622.png

    解压安装包到 /opt目录下

    sudo tar -zxvf eclipse-cpp-2020-09-R-linux-gtk-x86_64.tar.gz -C /opt/
    

    创建桌面快捷方式

    cd /usr/share/applications
    sudo vim eclipse.desktop
    
    # 文件内容如下:
    [Desktop Entry]
    Encoding=UTF-8
    Name=Eclipse
    Comment=Eclipse
    Exec=/opt/eclipse/eclipse
    Icon=/opt/eclipse/icon.xpm
    Terminal=false
    StartupNotify=true
    Type=Application
    Categories=Application;Development;
    
    # 给文件加权限
    sudo chmod u+x eclipse.desktop
    
    # 在/usr/share/applications目录下将Eclipse图标复制到桌面即可
    
  2. 下载JDK:Eclipse必要的运行环境

    下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html

    ../../../../_images/image-20201113093827789.png

    配置JDK

    sudo mkdir /opt/jvm
    sudo tar zxvf jdk-8u271-linux-x64.tar.gz -C /opt/jvm
    

    配置环境变量

    sudo vim /etc/profile
    
    # 添加下面的代码
    export JAVA_HOME=/opt/jvm/jdk1.8.0_271
    export JRE_HOME=${JAVA_HOME}/jre
    export CLASSPATH=.:%{JAVA_HOME}/lib:%{JRE_HOME}/lib
    export PATH=${JAVA_HOME}/bin:$PATH
    

    验证jdk是否安装成功

    java -version
    

13.2. Ubuntu使用Eclipse 开发嵌入式Linux驱动程序

  1. 打开Eclipse新建一个C Project,在以下界面需要注意Toolchains栏目选择交叉编译环境Cross GCC 。注意:Linux Gcc是ubuntu自带的编译器。

    ../../../../_images/image-20201113113247459.png

  2. 下一步到到Cross GCC Command窗口,做以下配置,选择自己的交叉编译器的安装路径

    ../../../../_images/image-20201113113335483.png

  3. 建立好工程后,按”ALT+ENTER”快捷键,调出Properties窗口,选中”C/C++ Build”

    修改:不勾选Generate Makefiles automatically(不自动生成Makefile,使用自己编写的)

    ../../../../_images/image-20201113113603251.png

  4. 添加linux内核头文件,下面以IMX6ull说明。

    ../../../../_images/image-20201113113915197.png

    点击【C/C++ General 】->【 Paths and Symbols】 -> 【Export Setting…】将当前配置导出为symbols.xml文件进行保存(该文件修改好后,还需要从Import Setting导入进来)

  5. 在 【ebf_6ull_linux/include/generated/ 】目录下执行命令, 完成后在该目录下生成一个symbols_linux.xml文件

    cd ebf_6ull_linux/include/generated/ 
    
    cat autoconf.h |grep define |awk '{print "<macro><name>" $2 "</name><value>" $3 "</value></macro>"}' > symbols_linux.xml
    

    执行过程如下

    ../../../../_images/image-20201113111913818.png

    在symbols_linux.xml文件的开头增加语句:

    <macro><name>__KERNEL__</name><value>1</value></macro> 
    

    修改后的截图如下:

    ../../../../_images/image-20201113111630719.png

  6. 打开之前从eclipse导出的symbols.xml文件如下两处进行修改:

    ../../../../_images/image-20201113112721556.png

    上图中,第一部分修改添加内核头文件,根据自己的情况添加,我的内容如下:

    <includepath>/home/book/embedfire/ebf_6ull_linux/include</includepath>
    <includepath>/home/book/embedfire/ebf_6ull_linux/arch/arm/include</includepath>
    <includepath>/home/book/embedfire/ebf_6ull_linux/arch/arm/mach-imx</includepath>
    <includepath>/home/book/embedfire/ebf_6ull_linux/arch/arm/mach-imx/devices</includepath>
    
  7. 将修改好的symbols.xml文件重新导入Eclipse

    ../../../../_images/image-20201113133453043.png

  8. Eclipse项目下新建led驱动程序,代码如下

    • board_fire_imx6ull_pro.c

      #include <linux/module.h>
      
      #include <linux/fs.h>
      #include <linux/errno.h>
      #include <linux/miscdevice.h>
      #include <linux/kernel.h>
      #include <linux/major.h>
      #include <linux/mutex.h>
      #include <linux/proc_fs.h>
      #include <linux/seq_file.h>
      #include <linux/stat.h>
      #include <linux/init.h>
      #include <linux/device.h>
      #include <linux/tty.h>
      #include <linux/kmod.h>
      #include <linux/gfp.h>
      #include <asm/io.h>
      
      #include "board_fire_imx6ull_pro.h"
      
      static volatile unsigned int *CCM_CCGR1                              ;
      static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
      static volatile unsigned int *GPIO5_GDIR                             ;
      static volatile unsigned int *GPIO5_DR                               ;
      
      
      
      
      void hw_led_init(void)
      {
          int val;
          if (!CCM_CCGR1){
              CCM_CCGR1                               = ioremap(0x20C406C, 4);
              IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x2290014, 4);
              GPIO5_GDIR                              = ioremap(0x020AC000 + 0x4, 4);
              GPIO5_DR                                = ioremap(0x020AC000 + 0, 4);
          }
      
          /* GPIO5_IO03 */
          /* a. 使能GPIO5
          * set CCM to enable GPIO5
          * CCM_CCGR1[CG15] 0x20C406C
          * bit[31:30] = 0b11
          */
          *CCM_CCGR1 |= (3<<30);
      
          /* b. 设置GPIO5_IO03用于GPIO
          * set IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3
          *      to configure GPIO5_IO03 as GPIO
          * IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3  0x2290014
          * bit[3:0] = 0b0101 alt5
          */
          val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
          val &= ~(0xf);
          val |= (5);
          *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;
      
          /* b. 设置GPIO5_IO03作为output引脚
          * set GPIO5_GDIR to configure GPIO5_IO03 as output
          * GPIO5_GDIR  0x020AC000 + 0x4
          * bit[3] = 0b1
          */
          *GPIO5_GDIR |= (1<<3);
      
      }
      void hw_led_reinit(void)
      {
          if (CCM_CCGR1){
              iounmap(CCM_CCGR1);
              iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3); 
              iounmap( GPIO5_GDIR);                
              iounmap(GPIO5_DR);                             
          }
          CCM_CCGR1 =NULL;/* 这里切记要清空,要不然下次从新装载驱动的时候由于CCM_CCGR1不为空的话,初始化函数不会从新ioremop */
          IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 =NULL;
          GPIO5_GDIR=NULL;
          GPIO5_DR=NULL;
      
      }
      void hw_led_write (int index, int state) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
      {
          switch (index){
          case 0:
              if (state) {
                      /* on: output 0*/
                      /* d. 设置GPIO5_DR输出低电平
                      * set GPIO5_DR to configure GPIO5_IO03 output 0
                      * GPIO5_DR 0x020AC000 + 0
                      * bit[3] = 0b0
                      */
                      *GPIO5_DR &= ~(1<<3);
              } else {  
                      /* off: output 1*/
                      /* e. 设置GPIO5_IO3输出高电平
                      * set GPIO5_DR to configure GPIO5_IO03 output 1
                      * GPIO5_DR 0x020AC000 + 0
                      * bit[3] = 0b1
                      */ 
                      *GPIO5_DR |= (1<<3);
              }
          break;
      
          default:
              break;
          }
      
      
      	/*
      	 *   这里要根据次设备号which决定控制哪个灯,用status决定是否亮灭
      	 */
      	printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, index, state ? "on" : "off");
      
      }
      
      
      
      
      
    • board_fire_imx6ull_pro.h

      #ifndef _BOARD_FIRE_IMX6ULL_PRO_H
      #define _BOARD_FIRE_IMX6ULL_PRO_H
      
      void hw_led_init(void);
      void hw_led_write(int index,int state);
      void hw_led_reinit(void);
      #endif
      
      
    • led_drv.c

      #include <linux/module.h>
      
      #include <linux/fs.h>
      #include <linux/errno.h>
      #include <linux/miscdevice.h>
      #include <linux/kernel.h>
      #include <linux/major.h>
      #include <linux/mutex.h>
      #include <linux/proc_fs.h>
      #include <linux/seq_file.h>
      #include <linux/stat.h>
      #include <linux/init.h>
      #include <linux/device.h>
      #include <linux/tty.h>
      #include <linux/kmod.h>
      #include <linux/gfp.h>
      
      #include "board_fire_imx6ull_pro.h"
      //#include "board_rk3288.h"
      
      struct led_char_dev {
      	dev_t  devid;      		 				/* 字符ID 		*/
      	struct class *class; 	 				/* 类 			*/
      	struct device *device;   				/* 设备		*/
      	int    major;            				/* 主设备号 	*/
      	int    minor;            				/* 次设备号 	*/
      };
      struct led_char_dev g_led_dev; 				/* 定义led设备 */
      
      
      /* 3. 实现对应的open/read/write等函数,填入file_operations结构体                   */
      static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
      {
      	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
      	return 0;
      }
      
      /* write(fd, &val, 1); */
      static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
      {
      
      	unsigned long err;
         	uint8_t status = 0;
      	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
      	err = copy_from_user(&status, buf, 1);
      
      
      	/* 根据次设备号和status控制LED */
      	hw_led_write(0, status);
      
      	return 1;
      }
      
      static int led_drv_open (struct inode *node, struct file *file)
      {
      	hw_led_init();
      	file->private_data = &g_led_dev;
      
      	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
      
      	return 0;
      }
      
      static int led_drv_close (struct inode *node, struct file *file)
      {
      	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
      	hw_led_reinit();
      	return 0;
      }
      
      /* 2. 定义自己的file_operations结构体                                              */
      static struct file_operations led_drv = {
      	.owner	 = THIS_MODULE,
      	.open    = led_drv_open,
      	.read    = led_drv_read,
      	.write   = led_drv_write,
      	.release = led_drv_close,
      };
      
      /**
       * @description: led_init - 入口函数
       */
      static int __init led_init(void)
      {
      	/*
      	 *  1: 自动获取主设备号
      	 */
      
      	g_led_dev.major = register_chrdev(0, "xym_led", &led_drv);        /* 注册字符设备驱动 */
      	g_led_dev.class = class_create(THIS_MODULE, "xym_led_class");
      	if (IS_ERR(g_led_dev.class)) {
      		unregister_chrdev(g_led_dev.major, "xym_led");
      		goto led_init_error;
      	}
      
          /* 
      	 *  2.创建设备
      	 */
      
      	g_led_dev.devid = MKDEV(g_led_dev.major,0);
      	device_create(g_led_dev.class, NULL, g_led_dev.devid, NULL, "xym_led"); /* /dev/xym_led */
      
      	printk("%s %s line %d:insmod !\n", __FILE__, __FUNCTION__, __LINE__);
      
      	return 0;
      
      led_init_error:
      
      	printk("%s %s line %d:init error !!!\n", __FILE__, __FUNCTION__, __LINE__);
      	return  -1;
      }
      
      /**
       * @description: led_exit - 出口函数
       */
      static void __exit led_exit(void)
      {
      
          hw_led_reinit();
      	device_destroy(g_led_dev.class, g_led_dev.devid); /* /dev/xym_led */
      	class_destroy(g_led_dev.class);
      	unregister_chrdev(g_led_dev.major, "xym_led");
      	printk("%s %s line %d: rmmod ! \n", __FILE__, __FUNCTION__, __LINE__);
      
      }
      
      
      module_init(led_init);
      module_exit(led_exit);
      
      MODULE_LICENSE("GPL");
      MODULE_AUTHOR("xym_@163.com");
      
      
    • led_app.c

      该应用程序点亮中间的led灯,用来测试上面的驱动文件是否ok

      
      #include <sys/types.h>
      #include <sys/stat.h>
      #include <fcntl.h>
      #include <unistd.h>
      #include <stdio.h>
      #include <string.h>
      
      /*
       * ./led_app  on
       * ./led_app  off
       */
      #define led "/dev/xym_led"
      
       // echo none >> /sys/class/leds/cpu/trigger
      #include <stdio.h>
      int main(int argc, char **argv)
      {
      	int fd;
      	char status;
      
      	/* 1. 判断参数 */
      	if (argc != 2) {
      		printf("Usage: %s <dev> <on | off>\n", argv[0]);
      		return -1;
      	}
      	/* 2. 打开文件 */
      	fd = open(led, O_RDWR);
      	if (fd == -1){
      		return -1;
      	}
      
      	/* 3. 写文件 */
      	if (0 == strcmp(argv[1], "on")){
      		status = 1;
      		write(fd, &status, 1);
      	}else{
      		status = 0;
      		write(fd, &status, 1);
      	}
      
      	close(fd);
      
      	return 0;
      }
      
      
      
  9. Eclipse项目下新建Makefile文件,内容如下

    # 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
    # 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
    # 2.1 ARCH,          比如: export ARCH=arm64
    # 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
    # 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin 
    # 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
    #       请参考各开发板的高级用户使用手册
    ARCH=arm  
    CROSS_COMPILE=arm-linux-gnueabihf-
    
    KERN_DIR = /home/book/embedfire/ebf_6ull_linux
    #KERN_DIR = /home/book/100ask_firefly-rk3288/linux-4.4
    PWD = $(shell pwd)
    
    all:
    	make -C $(KERN_DIR) M=$(PWD) modules 
    	$(CROSS_COMPILE)gcc -o led_app led_app.c 
    
    clean:
    	make -C $(KERN_DIR) M=$(PWD) modules clean
    	rm -rf modules.order
    	rm -f led_app
    
    # 参考内核源码drivers/char/ipmi/Makefile
    # 要想把a.c, b.c编译成ab.ko, 可以这样指定:
    # ab-y := a.o b.o
    # obj-m += ab.o
    
    # led_drv.c board_fire_imx6ull.c 编译成 xym_led.ko
    xym_led-y := led_drv.o  board_fire_imx6ull_pro.o
    #xym_led-y := led_drv.o   board_rk3288.o
    
    obj-m	+= xym_led.o
    

    工程目录结构如下:

    ../../../../_images/image-20201114094833645.png

  10. 修改内核代码的顶层Makefile,切记一定要修改,否则下面编译会出错,即使我们编译驱动模块的makefile里面添加了这两句。

    ../../../../_images/image-20201113165954442.png

  11. 按”Ctrl+B”快捷键,进行编译,可看到成功编译,并生成了xym_led.ko文件,将该文件复制到开发板,执行insmod xym_led.ko 加载该驱动

13.3. Ubuntu使用Eclipse开发嵌入式Linux应用程序

开发应用程序和开发驱动程序很类似,只不过驱动中我们需要使用自己写的makefile,而应用程序我们可以使用Eclipse再带的makefile,不用我们在写了。

步骤如下:

  • 第一步:

    ../../../../_images/image-20201114152035182.png

  • 第二步

    ../../../../_images/image-20201114152107836.png

  • 第三步

    ../../../../_images/image-20201114152257107.png

  • 第四步

    ../../../../_images/image-20201114152416832.png

  • 第五步:添加自己的源代码,这些都是根据自己的需求添加

    ../../../../_images/image-20201114152519272.png

  • 文件添加完毕后,编译即可

    ../../../../_images/image-20201114152715606.png

13.3.1. Eclipse 应用程序库文件使用

13.3.1.1. 生成静态库

  1. 新建工程

    ../../../../_images/image-20201114154114883.png

    ../../../../_images/image-20201114154143007.png

  2. 配置工程:同样需要导入符号表

    ../../../../_images/image-20201114154617675.png

  3. 在工程里面新建自己的源码文件即可

    /*
     * math.c
     *
     *  Created on: Nov 14, 2020
     *      Author: book
     */
    
    
    int add (int a, int b)
    {
    
    	return (a+b);
    }
    
    /*
     * math.h
     *
     *  Created on: Nov 14, 2020
     *      Author: book
     */
    
    #ifndef MATH_H_
    #define MATH_H_
    
    extern int add (int a, int b);
    
    #endif /* MATH_H_ */
    
  4. 编译即可输出库文件libmathStaticLib.a

    ../../../../_images/image-20201114155742666.png

13.3.1.2. 生成动态库

  1. 新建工程

    ../../../../_images/image-20201114161725665.png

    ../../../../_images/image-20201114161747141.png

  2. 配置工程:同样需要导入符号表

    ../../../../_images/image-20201114154617675.png

  3. 在工程里面新建自己的源码文件即可

    /*
     * math.c
     *
     *  Created on: Nov 14, 2020
     *      Author: book
     */
    
    
    int add (int a, int b)
    {
    
    	return (a+b);
    }
    
    /*
     * math.h
     *
     *  Created on: Nov 14, 2020
     *      Author: book
     */
    
    #ifndef MATH_H_
    #define MATH_H_
    
    extern int add (int a, int b);
    
    #endif /* MATH_H_ */
    
  4. 编译即可生成动态库libmathShareLib.so

    ../../../../_images/image-20201114162245276.png

13.3.1.3. 嵌入式应用程序使用库文件

13.3.1.3.1. 静态库使用

需要拷贝刚生成的libmathStaticLib.a和头文件math.h文件到应用程序目录下,放哪里用户自己组织即可。

  1. 添加库文件和库的头文件到工程目录下,自己组织

    我的工程目录如下:

    ../../../../_images/image-20201114165415062.png

  2. 添加头文件路径【includes】到工程

    ../../../../_images/image-20201114164220081.png

  3. 添加库文件到工程

    ../../../../_images/image-20201114164320134.png

  4. 编译即可。

13.3.1.3.2. 动态库使用

使用方法和静态库完全一样。

13.4. Ubuntu使用Eclipse开发嵌入式Linux内核

  • 第一步:保证自己的内核可以在终端编译,我的编译步骤如下:

    export ARCH=arm CROSS_COMPILE=arm-linux-
    make clean
    make imx6_v7_ebf_defconfig
    make zImage V=1
    
    等待编译完成
    
  • 第二步:新建内核工程

    ../../../../_images/image-20201116113609404.png

    ../../../../_images/image-20201116113639194.png

  • 第三步:链接内核文件

    ../../../../_images/image-20201116134455097.png

    选择后的工程目录如下图所示:

    ../../../../_images/image-20201116134618660.png

  • 设置make选项

    ../../../../_images/image-20201116151442897.png

  • 添加内核和平台相关的头文件路径

    ../../../../_images/image-20201116140624373.png

  • 设置工程某些文件或者文件夹不编译

    ../../../../_images/image-20201116140823392.png

    ../../../../_images/image-20201116141144735.png

    过滤后的工程目录效果如下:

    ../../../../_images/image-20201116141332714.png

  • 导出符号表,添加宏定义的符号表,并重新导入到工程

    • 第一步:生成symbols_linux.xml备用

      cd ebf_6ull_linux/include/generated/ 
      
      cat autoconf.h |grep define |awk '{print "<macro><name>" $2 "</name><value>" $3 "</value></macro>"}' > symbols_linux.xml
      
    • 第二步:导出eclipse的符号表到桌面文件名字为:eclipseSymbols.xml

      ../../../../_images/image-20201116142131421.png

    • 第三步:把第一步里面导出来的symbols_linux.xml里面的内容复制到第二步的eclipseSymbols.xml文件的指定位置,并在最前面新增<macro><name>__KERNEL__</name><value>1</value></macro>内容即可

      ../../../../_images/image-20201116142613419.png

    • 第四步重新把eclipseSymbols.xml符号表导入到工程

      ../../../../_images/image-20201116143008277.png

      导入完以后就可以看到Symbols了,然后稍微浏览下这里的宏,有些宏是字符串类型的,会少最后的分号,自己给补上即可,一般都是正确的,所以需要大致浏览下。(该问题是由于在第一步执行脚本的时候我们用awk命令 空格分割autoconf.h里面每一行的内容,当遇见宏定义如下

      #define CONFIG_CMDLINE "noinitrd console=ttymxc0,115200")noinitrd后面有个空格所以执行

      cat autoconf.h |grep define |awk '{print "<macro><name>" $2 "</name><value>" $3 "</value></macro>"}' > symbols_linux.xml
      

      脚本的时候,$3等于"noinitrd而不是等于"noinitrd console=ttymxc0,115200",如此会在symbols_linux.xml出现下面的定义

      <macro><name>CONFIG_CMDLINE</name><value>"noinitrd</value><\macro> 
      

      实际上我们需要的却是

      <macro><name>CONFIG_CMDLINE</name><value>"noinitrd  console=ttymxc0,115200“</value><\macro> 
      

      ../../../../_images/image-20201116143430120.png

  • 第五步:配置 C/C++ Gernal

    • 【indexer】

      • 勾选 【Enable project specific settings】选

      • 去掉【 Index source files not included in the build】

      ../../../../_images/image-20201116144453759.png

    • 【Preprocessor Include Paths】

      • 【Enteries】

        【GNU C –> CDT User Setting Entries –> Add –> Preprocessor Macros File –> 选择 【include/gernerated/autoconf.h】

        所有make menuconfig 时的编译配置信息都在这个文件里,所以需要让eclipse识别这些信息

        ../../../../_images/image-20201116144720970.png

  • 添加生成dtb命令:方便生成dtbs文件

    ../../../../_images/image-20201116153141985.png

13.4.1. 使能自动补全快捷键

【Window-Preferences-c/c++-Editor-Content Assist-Advanced】将未勾选的全部勾选

Alt + / 快捷键:C++自动补全变量名快捷键
Ctrl +Spase 快捷键: 会自动显示代码

../../../../_images/image-20201114094440509.png

13.4.2. Eclipse项目设置过滤

通过make生成的**.o 、mod**等等文件都自动添加到项目架构当中了,但这些文件我们又关心,这就可以增加过滤规则,让项目不自动添加这些文件,设置方式如下图所示【Resource->Resource Filter->Exclude all->Add Filter】

../../../../_images/image-20201114101938474.png

下面是我自己的配置

../../../../_images/image-20201114102210877.png

13.4.3. Eclipse 设置编译器自动保存文件

../../../../_images/image-20201114105517358.png

../../../../_images/image-20201114143628266.png

13.4.4. Eclipse 排除文件编译

方法一:屏蔽自己不想加入工程的文件【选中文件或者文件夹,右键 Properties】然后按照下面的方法设置(测试发现,只有源代码.c或者cpp文件才有效,屏蔽库或者.h无效)

../../../../_images/image-20201114164514382.png

方法二:按alt+enter快捷键进入配置先选,添加过滤,这种方式可以整体选择很过个一起添加。如下图,

../../../../_images/image-20201116140823392.png

14. Eclipse开发应用程序,cmake篇