【飛凌嵌入式 OK3399-C+開發(fā)板試用體驗(yàn)】本地編譯內(nèi)核源碼&DHT11驅(qū)動(dòng)編譯

原創(chuàng) 2020-09-30 11:14:00 RK3399 RK3399開發(fā)板

作者:donatello1996

飛凌廠商提供的Linux源碼資料壓縮包實(shí)際上是多合一源碼,除了最常用的內(nèi)核源碼以外,還有諸多額外內(nèi)容比如交叉編譯鏈,Linux燒錄工具(卡刷+線刷),生成系統(tǒng)鏡像img的腳本,uboot源碼,應(yīng)用程序代碼等諸多內(nèi)容,這些不同內(nèi)容面向不同層次的開發(fā)者,比如開發(fā)應(yīng)用層的只看應(yīng)用程序代碼即可,開發(fā)驅(qū)動(dòng)的只看內(nèi)核源碼和交叉編譯鏈即可,開發(fā)uboot的只看uboot源碼即可,產(chǎn)品部門相關(guān)的只需要會用工具即可。內(nèi)核源碼為主目錄的kernel文件夾,交叉編譯鏈為主目錄的prebuilt文件夾,平時(shí)如果執(zhí)行廠商提供的build_ubuntu.sh腳本,實(shí)際上不只是編譯內(nèi)核和驅(qū)動(dòng)(boot.img,**.ko),還有生成必須要的分區(qū)鏡像如recovery.img,userdata.img等:


我的想法是直接使用板子本地編譯,減少內(nèi)核源碼不兼容的風(fēng)險(xiǎn),那么就只需要保留kernel文件夾的內(nèi)容即可,kernel文件夾就是OK3399板子的全部內(nèi)核源碼,根目錄有Makefile可以直接編譯,因此準(zhǔn)備一個(gè)足夠大的U盤,格式化為EXT4格式,將整個(gè)kernel文件夾都存進(jìn)去,改名為OK3399文件夾,然后把U盤插到板子上,將U盤掛載出來:
1.mount /dev/sda /mnt
2.cd /mnt


然后是在板子上安裝64位aarch編譯工具,通常Ubuntu系統(tǒng)都會自帶的:

apt install aarch64-linux-gnu-gcc


那么就可以直接在板上進(jìn)行本地內(nèi)核編譯了:

1.make clean
2.make rockchip_linux_defconfig
3.make
-這里貌似是不能用-j2 -j4多線程編譯的,效果跟單線程編譯差不多
-編譯時(shí)長1~2小時(shí)
-編譯過程缺失了一些必要的軟件庫如openssl等,要安裝上去
1.apt install libssl-dev
2.apt install libssl1.0.0= 1.0.2g-1ubuntu4.16
-/mnt/OK3399/scripts/gcc_wrapper.py文件對編譯沒有任何作用,但是里面有句關(guān)于utf8的語句會導(dǎo)致編譯報(bào)錯(cuò),這個(gè)文件完全可以刪掉,或者把有關(guān)utf8的語句注釋掉即可。
編譯完畢之后內(nèi)核目錄/mnt/OK3399就可以給各種需要內(nèi)核源碼的驅(qū)動(dòng)代碼編譯了,比如自己寫的GPIO驅(qū)動(dòng),現(xiàn)成的USB網(wǎng)卡驅(qū)動(dòng),4G模塊驅(qū)動(dòng)等,我手頭上有一個(gè)騰達(dá)網(wǎng)卡,主控是RTL8192EU,網(wǎng)上也有相應(yīng)源碼,解壓縮之后內(nèi)容是這樣的:
修改Makefile文件:
-修改源碼目錄KSRC,指定為內(nèi)核根目錄 -修改驅(qū)動(dòng)目錄,即../drivers/net/wireless -指定架構(gòu)ARCH=arm64 -指定交叉編譯鏈aarch64-linux-gnu-gcc
編譯:
1.cd /mnt/RTL8192EU
2.make clean
3.make
生成8192eu.ko文件可以直接安裝: insmod 8192eu.ko
安裝完畢之后ifconfig可以找到一個(gè)亂碼的網(wǎng)卡:
我這邊已經(jīng)用nmcli來連接WIFI了,分配到了IP地址。
然后是LTE模塊,是移遠(yuǎn)的EC20,miniPCIE接口,可以直接插到板子背面的miniPCIE卡座上:
移遠(yuǎn)模塊雖然是miniPCIE接口,但實(shí)際上是走USB協(xié)議的,需要編譯移遠(yuǎn)官方提供的USB驅(qū)動(dòng),并加入到內(nèi)核中,
第一步是打開drivers/usb/serial/option.c文件,添加移遠(yuǎn)設(shè)備的VID:
#define QUECTEL_VENDOR_ID 0x2c7c

然后是EC20/EC25的公用產(chǎn)品ID 0x0125:

#define QUECTEL_PRODUCT_EC25 0x0125 
繼續(xù)在此文件drivers/usb/serial/option.c文件中找到option_ids 數(shù)組,在此數(shù)組里面加入如下內(nèi)容:
{ USB_DEVICE(0x2C7C, 0x0125) }, /* Quectel EC25 */ 


繼續(xù)在此文件drivers/usb/serial/option.c文件中找到option_probe 函數(shù),在此函數(shù)里面添加如下內(nèi)容:

if (dev_desc->idVendor == cpu_to_le16(0x05c6) &&
     dev_desc->idProduct == cpu_to_le16(0x9003) &&
      iface_desc->bInte**ceNumber >= 4)
       return -ENODEV;
if (dev_desc->idVendor == cpu_to_le16(0x05c6) &&
      dev_desc->idProduct == cpu_to_le16(0x9215) &&
      iface_desc->bInte**ceNumber >= 4)
      return -ENODEV;
if (dev_desc->idVendor == cpu_to_le16(0x2c7c) &&
      iface_desc->bInte**ceNumber >= 4)
      return -ENODEV;
繼續(xù)在此文件drivers/usb/serial/option.c 文件里面找到option_1port_device結(jié)構(gòu)體變量,在里面加入休眠/喚醒函數(shù):
static struct usb_serial_driver option_1port_device = {
     .driver = {
                         .owner = THIS_MODULE,
                          .name = "option1",
                 },
     .description = "GSM modem (1-port)",
      .id_table = option_ids,
      .num_ports = 1,
      .probe = option_probe,
      .open = usb_wwan_open,
      .close = usb_wwan_close,
      .dtr_rts = usb_wwan_dtr_rts,
     .write = usb_wwan_write,
     .write_room = usb_wwan_write_room,
     .chars_in_buffer = usb_wwan_chars_in_buffer,
     .tiocmget = usb_wwan_tiocmget,
     .tiocmset = usb_wwan_tiocmset,
     .ioctl = usb_wwan_ioctl,
     .attach = option_attach,
     .release = option_release,
     .port_probe = usb_wwan_port_probe,
     .port_remove = usb_wwan_port_remove,
    .read_int_callback = option_instat_callback,
  #ifdef CONFIG_PM
     .suspend = usb_wwan_suspend,
     .resume = usb_wwan_resume,
     .reset_resume = usb_wwan_resume,
     #endif
};
drivers/usb/serial/usb_wwan.c文件的usb_wwan_setup_urb函數(shù)添加:
static struct urb *usb_wwan_setup_urb(struct usb_serial_port *port,
int endpoint,
int dir, void *ctx, char *buf, int len,
void (*callback) (struct urb *))
{
       struct usb_serial *serial = port->serial;
       struct urb *urb;
      urb = usb_alloc_urb(0, GFP_KERNEL); /* No ISO */
   if (!urb)
      return NULL;
      usb_fill_bulk_urb(urb, serial->dev,
      usb_sndbulkpipe(serial->dev, endpoint) | dir,
      buf, len, callback, ctx);
   if (dir == USB_DIR_OUT) {
        struct usb_device_descriptor *desc = &serial->dev->descriptor;
   if (desc->idVendor == cpu_to_le16(0x05C6) && desc->idProduct == 
cpu_to_le16(0x9090))
       urb->transfer_flags |= URB_ZERO_PACKET;
    if (desc->idVendor == cpu_to_le16(0x05C6) && desc->idProduct == cpu_to_le16(0x9003))
          urb->transfer_flags |= URB_ZERO_PACKET;
    if (desc->idVendor == cpu_to_le16(0x05C6) && desc->idProduct == cpu_to_le16(0x9215))
          urb->transfer_flags |= URB_ZERO_PACKET;
   if (desc->idVendor == cpu_to_le16(0x2C7C))
        urb->transfer_flags |= URB_ZERO_PACKET;
     }
return urb;
}


這樣編譯生成的內(nèi)核就支持EC20模塊的USB通信部分了。然后就是要進(jìn)入menuconfig菜單選項(xiàng)那里把USB網(wǎng)卡的配置打開,還有把Gobi上網(wǎng)選項(xiàng)打開即可,這樣板子上電就能找到/dev/qcmi設(shè)備。
解決了兩個(gè)網(wǎng)絡(luò)模塊的驅(qū)動(dòng),接下來可以弄一下較為簡單一些的功能,比如驅(qū)動(dòng)GPIO輸出高低電平電燈以及驅(qū)動(dòng)DHT11器件,直接對GPIO寫入高低電平非常簡單,但實(shí)際上我人生第一份接觸驅(qū)動(dòng)的代碼并不是GPIO寫入電平,而是比這復(fù)雜得多的DHT11驅(qū)動(dòng),既然DHT11的驅(qū)動(dòng)都能讀懂了,那驅(qū)動(dòng)GPIO讀寫電平當(dāng)然簡單多了。對于GPIO寫入電平的函數(shù)很簡單,一共就3個(gè)gpio_request(),gpio_direction_output(),gpio_set_value(),讀取則是gpio_request(),gpio_direction_input(),gpio_get_value(),然后對于每個(gè)/dev下的驅(qū)動(dòng)文件,操作一共有四個(gè),分別是open()——打開設(shè)備文件,read()——從設(shè)備中讀取數(shù)據(jù),wirte()——寫入數(shù)據(jù)到設(shè)備,close()——關(guān)閉設(shè)備文件,其中讀寫都是以字節(jié)為單位進(jìn)行,也就是傳輸若干字節(jié)(長度可自定義)的unsigned char[]數(shù)組類型。比如定義一個(gè)設(shè)備的文件操作結(jié)構(gòu)體即fops,我們使用板上的GPIO2_A5接口即GPIO69:

static struct file_operations mygpio69_fops = {
           .owner = THIS_MODULE,
           .read = MYGPIO69_read,
           .write = MYGPIO69_write,
           .open = MYGPIO69_open,
           .release = MYGPIO69_close,
     };

與設(shè)備樹的mygpio69節(jié)點(diǎn)對應(yīng):

static const struct of_device_id of_mygpio69_match[] =
{
     { .compatible = "mygpio69", },
       {},
};
要使用這個(gè)設(shè)備樹節(jié)點(diǎn),就要先在設(shè)備樹OK3399.dts里面添加設(shè)備樹節(jié)點(diǎn):
重新燒錄boot.img:
燒錄完畢啟動(dòng)就可以在/proc/device-tree里面找到設(shè)備樹節(jié)點(diǎn),還可以用相同的辦法添加一個(gè)名為dht11的節(jié)點(diǎn),之后要用到:

添加節(jié)點(diǎn)之后就可以在驅(qū)動(dòng)代碼里面進(jìn)行GPIO寫電平操作了,復(fù)寫MYGPIO69_write函數(shù),超級簡單:

static int mygpio69_gpio = 69;
//GPIO號
#define MYGPIO69_LOW gpio_set_value(mygpio69_gpio, 0)
#define MYGPIO69_HIGH gpio_set_value(mygpio69_gpio, 1)
static ssize_t MYGPIO69_write(struct file *file,const char __user *buf,
size_t nbytes, loff_t *ppos)
{
    if(buf[0]==1)
        MYGPIO69_HIGH;
    else if(buf[0]==0)
            MYGPIO69_LOW;
}
當(dāng)然了,要正常調(diào)用這些函數(shù)操作GPIO,是要先初始化GPIO的:
if (gpio_request(mygpio69_gpio, "mygpio69_gpio"))
{
printk("gpio %d request failed!\n", mygpio69_gpio);
gpio_free(mygpio69_gpio);
return -ENODEV;
}
else
printk("gpio %d request success!\n", mygpio69_gpio);

make生成ko文件之后,就會在/dev目錄下生成mygpio69設(shè)備文件,可以在應(yīng)用層代碼直接操作:

int main ()
{
    int fd ;
    unsigned char val[4],value;
    fd = open ( "/dev/mygpio69" , O_RDWR) ;
if ( fd == -1 )
{
    perror ( "open /dev/mygpio69 error\n" ) ;
    exit ( -1 ) ;
}
   printf ( "open /dev/mygpio69 successfully\n" ) ;
while (1)
{
    value=0;
    write(fd,&value,1);
    sleep(1);
   value=1;
        write(fd,&value,1);
        sleep(1);
   }
close ( fd ) ;
}
驅(qū)動(dòng)DHT11單總線溫濕度傳感器的方式與內(nèi)核驅(qū)動(dòng)GPIO讀寫的方式基本無差異,都是通過對/dev目錄下的設(shè)備文件進(jìn)行write/read操作,不同的是DHT11的讀取需要不斷切換GPIO輸入輸出狀態(tài)并不斷做輪詢,其運(yùn)行層面是驅(qū)動(dòng)層,對于系統(tǒng)來說,驅(qū)動(dòng)層代碼的實(shí)時(shí)性要求和讀寫優(yōu)先級都要高于應(yīng)用層代碼,即要將更多的CPU任務(wù)時(shí)間片資源放到驅(qū)動(dòng)層代碼中,也就只有這樣,才能對DHT11這種延時(shí)響應(yīng)時(shí)間要求是微秒級的單總線器件進(jìn)行正常讀寫。廢話不多說放上DHT11的驅(qū)動(dòng)源碼,這份代碼參考了一位開發(fā)驅(qū)動(dòng)的朋友@jackeyt,修改了一些小地方:
static u8 DHT11_Read_Data(u16 *temp,u16 *humi)
{
      u8 buf[5];
      u8 i;
      DHT11_Rst();
    if(DHT11_Check()==0)
 {
    for(i=0;i<5;i++)//讀取40位數(shù)據(jù)
 {
    buf[i]=DHT11_Read_Byte();
 }
     if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])
  {
     *humi=buf[0]<<8|buf[1];
      *temp=buf[2]<<8|buf[3];
       printk("buf=%d,%d,%d,%d,%d\n",buf[0],buf[1],buf[2],buf[3],buf[4]);
    }
  }else return 1;
   return 0;
}

static ssize_t DHT11_read(struct file *file, char __user *buf,
size_t nbytes, loff_t *ppos)
{
       printk("--------------%s--------------\n",__FUNCTION__);
       dht11_data Last_dht11_data;
        if(DHT11_Read_Data(&Last_dht11_data.temp,&Last_dht11_data.hum) == 0);//讀取溫濕度值
          {
   if (raw_copy_to_user(buf,&Last_dht11_data,sizeof(Last_dht11_data)) )
       {
             return EFAULT ;
         }
}
}


static struct file_operations dht11_fops = {
        .owner = THIS_MODULE,
          .read = DHT11_read,
          .open = DHT11_open,
          .release = DHT11_close,
};
static const struct of_device_id of_dht11_match[] = {
        { .compatible = "dht11", },
           {},
   };
MODULE_DEVICE_TABLE(of, of_dht11_match);
static struct platform_driver dht11_driver = {
      .probe = dht11_probe,
      .remove = dht11_remove,
      .shutdown = dht11_shutdown,
          .driver = {
         .name = "dht11_driver",
           .of_match_table = of_dht11_match,
                    },
};
編譯生成dht11_drv.ko文件:
insmod dht11_drv.ko
插入ko驅(qū)動(dòng)的時(shí)候,必須要檢查上面的/proc/device-tree設(shè)備樹列表那有沒有正常生成dht11鍵值,如果沒有的話插入會失敗,并且無法在/dev下生成dht11字符設(shè)備。

做個(gè)簡單的小實(shí)驗(yàn)看下DHT11能否正常讀取溫度:

typedef struct DHT11_SENSOR_DATA
{
      unsigned short temp;//溫度
      unsigned short hum;//濕度
}dht11_data;
    int main ( void )
 {
       int fd ;
      int retval ;
        dht11_data Curdht11_data;
      fd = open ( "/dev/dht11" , O_RDONLY) ;
if ( fd == -1 )
{
    perror ( "open dht11 error\n" ) ;
    exit ( -1 ) ;
}
printf ( "open /dev/dht11 successfully\n" ) ;
       sleep ( 2 ) ;
        while ( 1 )
{
       sleep ( 1 ) ;
       retval = read ( fd , &Curdht11_data , sizeof(Curdht11_data) );
if ( retval == -1 )
{
       printf ( "read dht11 error" ) ;
       exit ( -1 ) ;
}
        if(Curdht11_data.temp != 0xffff)
           printf ( "Temperature:%d.%d C,Humidity:%d.%d %%RH\n",
          Curdht11_data.temp>>8,
          Curdht11_data.temp&0xff,
          Curdht11_data.hum>>8,
          Curdht11_data.hum&0xff ) ;
}
close ( fd ) ;
}


如圖,讀取DHT11數(shù)據(jù)正常。
生成DHT11的驅(qū)動(dòng)之后我很想在QT工程里面試試看能不能正常驅(qū)動(dòng),下一帖試試看。

相關(guān)產(chǎn)品 >

  • FET3399-C核心板

    飛凌RK3399安卓高性能核心板采用 采用六核Rockchip RK3399芯片,雙Cortex-A72大核+四Cortex-A53小核結(jié)構(gòu),對整數(shù)、浮點(diǎn)、內(nèi)存等作了大幅優(yōu)化,在整體性能、功耗及核心面積三個(gè)方面提升。以下將對瑞芯微芯片RK3399參數(shù),RK3399核心板方案及其性能做具體介紹。如您對飛凌RK3399系列核心板有興趣,歡迎咨詢了解。

    了解詳情
    FET3399-C核心板
  • OK3399-C開發(fā)板

    飛凌嵌入式RK3399安卓開發(fā)板主芯片采用高性能六核CPU Rockchip RK3399,GPU采用Mail-T860四核 GPU,RK3399作為目RK產(chǎn)品線中低功耗、高性能的代表,可滿足人臉識別設(shè)備、機(jī)器人、無人機(jī)、IoT物聯(lián)網(wǎng)領(lǐng)域應(yīng)用。飛凌RK3399開發(fā)板在整體性能、功耗及核心面積做了大幅度優(yōu)化,更加滿足工業(yè)設(shè)計(jì)需求。飛凌RK3399開發(fā)板為進(jìn)一步減少用戶二次開發(fā)難度,開放了底板原理圖,并提供了RK3399用戶手冊、芯片手冊,加上優(yōu)質(zhì)的技術(shù)服務(wù),讓您的方案從構(gòu)思到上市時(shí)間縮短。

    了解詳情
    OK3399-C開發(fā)板

推薦閱讀 換一批 換一批