zephyr的GPIOTE驅動開發記錄——基於nordic的NCS

2022-11-16 18:00:19

簡介:

  本次測試了zephyr的中斷驅動方式(GPIOTE),在這可以去看zephyr的官方檔案對zephyr的中斷定義,連線如下,Interrupts — Zephyr Project Documentation (nordicsemi.com) ;版本可能不對應,但是原理是一致的,今天記錄的就是其中的零延遲中斷,就是減少中斷時間,讓來自外部的中斷能快速響應,進入到我們的中斷服務程式中進行快速執行(也就是ISR)。

 根據檔案,就作者理解來如下,如有更好的理解可以進行指正,有些時候在執行某些執行緒時對時間有要求或者在臨界區進行操作時,不能夠被外部中斷(ISQ)打斷,可以禁止(中斷服務程式)ISR的執行。通過IRQ禁止達到在處理某些執行緒時不會被打斷,但是會讓中斷處理被延遲,但這時候又出現一個矛盾,有些中斷是我想要及時處理的,那麼我們需要不被遮蔽掉,就是這個中斷是比前面列舉的執行緒執行更重要的事,那麼怎麼辦,可以直接使用零延遲中斷進行定義,讓這些中斷直接得到響應,在零延遲中斷中又分為兩種:一是常規的ISR,二是直接的ISR(某些情況下比常規的更快),常規的ISR可能還是會被打斷,導致一下開銷產生,具體在zephyr中有4點列舉:

 

如果某個任務完全不想要被打斷,快速的執行,那麼就可以使用直接ISR(direct ISR)。在作者看來正常情況下(沒有其餘中斷打斷的情況下),他們兩的時間應該是一致的。具體可以點選上面連結,直接看官方描述。

本次使用的是在開發之前預設你已經設定好開發環境,如果是第一次開發,建議去安裝下面給出官方環境參考檔案,或者去比例比例觀看環境搭建的學習視訊(VS code),也可以參看我文章中的關於9160開機測試的文章。官方連線如下:開發你的第一個nRF Connect SDK(NCS)/Zephyr應用程式 - iini - 部落格園 (cnblogs.com)

參考資料:

  nordic的官方講解視訊,可以在嗶哩嗶哩上搜尋nordic半導體去看關於其中一個視訊:zephyr的裝置驅動程式模型,中斷和電源管理視訊,中文講解( https://www.bilibili.com/video/BV1MU4y177Zhis_story_h5=false&p=1&share_from=ugc&share_medium=android&share_plat=android&share_session_id=c0145896-48dc-4bbf-b938f1f4b3a4644a&share_source=WEIXIN&share_tag=s_i×tamp=1668583626&unique_k=29oQkX4),或者直接參看zephyr的官方。

本次測試環境:VS code、NCS1.8

一、建立工程

建立一個zephyr的工程,如果你有NCS,並且已經安裝好相關可以進行開發的環境,那麼可以開啟一個hello Word的工程進行新增,如果沒可以zephyr的SDK,可以依據nordic官方NCS進行開發,它也有如STM32等晶片底層檔案,因為nordic只是在zephyr的SDK中加入了自己的產品形成了NCS包,其餘zephyr原本有的並沒有刪減,所以你可以在NCS中建立如STM32晶片的工程進行開發,且上層的驅動都是抽象的,只是對應餘硬體的定義換成了具體的晶片定義,我們只用管APP開發,所以一套程式碼,可以建立成不同晶片的工程,並且在編譯下載後依然可以執行,不止侷限於nordic的開發。

1、zephyr工程建立

對於zephyr可以直接建立一個資料夾,然後再裡面包含如下的幾個檔案就可以進行編譯開發了,

1)、其中src中放置我們的.c檔案(APP),便於管理;

2)、CMakeLists.txt是工程建立的直接根本檔案,具體內容可以是如下:

#這是cmake的版本
cmake_minimum_required(VERSION 3.20.0)

#新增的庫
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})

#建立的工程名字(本次為hello_world,可以改為GPIOTE等)
project(hello_world)

#新增.c檔案,稍後在src中建立一個main.c
target_sources(app PRIVATE src/main.c)

3)、prj.conf為組態檔,很多時候還需要一個overlay檔案,可以進行裝置樹驅動的更改,有一個預設的,如果需要定義更改,就用overlay檔案進行實現

 由於本次我使用的是nordic的SDK,NCS,我可以在VS code上安裝好相關外掛然後直接映象建立一個工程在其餘檔案中(根據自己選擇,但是保證和NCS處於同一個磁碟中),如果不會請參看前面提到的教學檔案與視訊,在看後,你就可以理解為什麼只是這幾個檔案就可以建立一個工程了。

因此我們根據NCS中的hello_word建立一個映象工程,並把該工程的資料夾名字命令為gpiote,且工程也建立為gpiote,,然後建立一個可以跑在nrf5340的應用核的工程,如下,該工程主要功能是,通過串列埠列印出,hello world+板子資訊。

 2、新增自定義.c檔案

原本已經有一個.c檔案了,該檔案中主要就是串列埠列印資訊,本次測試是需要測試中斷,所以我們在定義一個名字為gpiote.c的檔案,新增到我們工程,然後再進行程式碼編寫,在src中加入一個gpiote.c檔案,

然後把gpiote.c加入到工程,這就需要我們開啟我們的CMakeLists.txt,新增如圖所示程式碼:

 然後點選全編譯,我們就可以看到我們的工程下加入了gpiote.c檔案:

全編譯如下:

 3、overlay檔案加入

這裡有一個隱藏的規則,如果你看了前面推薦的官網連線,那麼應該知道,在工程目錄下建立檔名和我們使用的板子一致時,可以不用在CMakeLists.txt中進行檔案新增,編譯器建立工程時可以識別這overlay檔案,知道你要更改預設的devicetree定義,會把你加入進入,如果不知道請去看下前面給出的連線,那麼zephyr第一那些板子呢,他們的名是什麼,可以直接在vs code確看,就行是你建立工程時選擇的板子名字:

由於我使用的是nrf5340,那麼我就建立一個同名的overlay檔案,最後我們工程目錄如下,就看我框選部分,其餘是建立hello_word映象工程時產生的:

 

 二、裝置樹更改

這裡注意的是我使用了1.8的NCS,如果你使用高版本的NCS如2.1,那麼overlay檔案會有一點便跟,你可以參考其餘工程。

主要是新增一箇中斷口定義,我們在nrf5340dk_nrf5340_cpuapp.overlay中進行處理,在新增前我們來看一下裝置樹檔案zephyr.dts,建立編譯工程後,可以在如下目錄找到它:

 可以看到已經有一個buttons的裝置定義了,我想自己加一個自己的按鍵定義,作為中斷觸發源,我使用的是官方開發板,按鍵依然是那幾個,但是我可以再定義一個,然後起一個其他名字,在overlay中新增如下程式碼:

/*引數加入devicetree的位置*/ 
/{
    /*其別名,這主要給test_button其一個別名,然後可以在APP中通過別名gpiote定位到我們定義的按鍵*/ 
    aliases {    
        gpiote = &test_button;        
    };
    /*在原有的buttons下定義一個測試IO口,並且定位為GPIO0的0x17腳,即P0.23,名字為test_gpiote*/ 
    buttons{
        test_button: test_button {        
            gpios = < &gpio0 0x17 0x11 >;
            label = "test_gpiote";
        };
    };
};

截圖如下:

 編譯後可以在zephyr.dts中看到本次定義:

 三、應用程式碼編寫

1、常規方式:

在gpiote.c中的程式碼如下:

#include "device.h"
#include "irq.h"
#include <zephyr.h>
#include <sys/printk.h>
#include <sys/util.h>
#include <device.h>
#include <devicetree.h>
#include <drivers/gpio.h>
#include <nrfx.h>
#include <dk_buttons_and_leds.h>

#define PIN DT_GPIO_PIN(DT_ALIAS(gpiote), gpios)
/* 建立一個gpio引腳的類*/
struct gpio_pin {
    const char * const port;
    const uint8_t number;
};
/* 定義gpio_pin型別的變數,用於讀取裝置定義資訊,這以陣列的形式定義,
便於有多個按鍵時可以直接定義,ARRAY_SIZE用於計算大小*/
static const struct gpio_pin init_pin[] ={
    {DT_GPIO_LABEL(DT_ALIAS(gpiote), gpios),
     DT_GPIO_PIN(DT_ALIAS(gpiote), gpios)},
};
/* */
static const struct device * init_device[ARRAY_SIZE(init_pin)];
/* 回撥的變數*/
static struct gpio_callback gpiote_cb;

/*回撥函數*/
void gpio_init_handle(const struct device *port,
                    struct gpio_callback *cb,
                    gpio_port_pins_t pins)
{
 printk("run to gpiote test\n");

}
/*GPIOte程式*/
void gpiote_test(void)
{
    /*如果時有多個按鍵可以增加陣列個數*/
    int err;
    uint32_t pin_mask = 0;

    // gpio_flags_t flags = (IS_ENABLED(CONFIG_DK_LIBRARY_INVERT_BUTTONS) ?
    //              GPIO_PULL_UP : GPIO_PULL_DOWN);
    /*獲取裝置*/
    init_device[0]=device_get_binding(init_pin[0].port);
    if (!init_device[0]) {
        printk("Cannot bind gpio device");
    }
    /*設定gpio口,輸入上拉*/
    err = gpio_pin_configure(init_device[0], init_pin[0].number,
                            GPIO_INPUT | GPIO_PULL_UP);
    if (err) {
        printk("Cannot configure button gpio");
    }

    /*中斷設定*/
    err = gpio_pin_interrupt_configure(init_device[0],
            init_pin[0].number, GPIO_INT_DISABLE);
    if (err) {
        printk("Cannot disable callbacks()");
    }
    pin_mask |= BIT(init_pin[0].number);
    /*回撥設定*/

    pin_mask |= BIT(init_pin[0].number);
    /*回撥設定*/
    gpio_init_callback(&gpiote_cb, gpio_init_handle, pin_mask);

    /*將剛剛繫結的結構新增到向量表中*/
    err = gpio_add_callback(init_device[0], &gpiote_cb);
    if (err) {
        printk("Cannot add callback");
    }
    /*將GPIO中斷設定為下降沿觸發,並啟用它*/
    err = gpio_pin_interrupt_configure(init_device[0],
            init_pin[0].number, GPIO_INT_EDGE_FALLING);
    if (err) {
        printk("Cannot disable callbacks()");
    }
    printk("test start\n");

    while(1)
    {
    }
}

/*建立一個區別於main.c中的執行緒,用於初始haulgpiote功能 */
K_THREAD_DEFINE(gpiote_test_id,1024,gpiote_test,NULL,NULL,NULL,7,0,0);

在此程式的基礎上,你可以定義多個按鍵並放入裝置模型陣列,雖然我本次測試只使用了一個按鍵,如果你新增的是多個按鍵,記得初始化時用for迴圈,把每一個裝置都新增一下,我這隻有一個裝置說以只使用了陣列的第0位的裝置(也只有一個)。

結果:

 2、zephyr中的direct ISR(直接中斷模式)

APP我們不用更改,只要把驅動中的IRQ_CONNECT();替換為IRQ_DIRECT_CONNECT();然後再加入zephy官方檔案定義的程式碼:

 你可以在工程的如下地方找到這個檔案,然後更改原始定義,改部分程式碼已經更改:

 可以直接替換程式碼:

#define CONFIG_direct_isr

#ifdef CONFIG_direct_isr
ISR_DIRECT_DECLARE(gpiote_event_handler_direct)
{
   gpiote_event_handler();
   ISR_DIRECT_PM(); /* PM done after servicing interrupt for best latency */
   return 1; /* We should check if scheduling decision should be made */
}
#endif
static int gpio_nrfx_init(const struct device *port)
{
    static bool gpio_initialized;

    if (!gpio_initialized) {
        gpio_initialized = true;
        #ifdef CONFIG_direct_isr
        IRQ_DIRECT_CONNECT(DT_IRQN(GPIOTE_NODE), DT_IRQ(GPIOTE_NODE, priority),
                gpiote_event_handler_direct, 0);

        #else
        IRQ_CONNECT(DT_IRQN(GPIOTE_NODE), DT_IRQ(GPIOTE_NODE, priority),
                gpiote_event_handler, NULL, 0);
        #endif
        irq_enable(DT_IRQN(GPIOTE_NODE));
        nrf_gpiote_int_enable(NRF_GPIOTE, NRF_GPIOTE_INT_PORT_MASK);
    }

    return 0;
}

編譯下載即可:

四、中斷向量表檢視

在如下目錄可以看到我們的中斷服務程式入口:其中21753就是本次中斷ISR的如果地址:

 在這個陣列下還有中斷向量表,可以自行檢視:

 

GPIO測試到此結束。如有錯漏歡迎評論指正。