nordic的nrf52系列32M速率的SPI-SPIM3

2023-12-06 12:02:00

簡介:在nordic的nrf52系列中的nrf52833和nrf52840的SPIM3都是支援最大32M的spi速率,其餘的只有8M,當在需要刷屏,或者一些需要高速32M-SPI時,這是一個很好的使用方式,下面我就結合GPIOTE+PPI+SPIM3實現無CPU參與的32M-SPI資料傳送:

測試環境:軟體:SDK_17.1,在ble_app_uart例子中新增

硬體:nrf52832-DK板

一、引腳選擇

特別注意一點,需要使用32M的SPI時,一定要選擇非低頻的引腳作為SPI的pin腳,如下面這些引腳就不可以:

在資料手冊中還有這樣一條提示,他的意思是在使用32M的時候,可能還要設定為高驅模式,不然可能有資料錯亂。

根據資料手冊我當前選擇4,5,27,26這4個引腳作為SPI的引腳。

二、程式碼編寫

1.原始檔新增

在工程中加入如下幾個.c原始檔,其中spi_test.c不是官方庫檔案,是我的SPIM設定程式碼,這一點注意,你只用新增前面的5個原始檔就行

2、宏定義啟用

 這一步主要是啟用相關的原始檔,如果宏定義不啟用剛剛新增的部分原始檔是無法新增的,這一步很重要:

 第一步啟用SPIM3:

 還要勾選另一個宏定義,在偵錯過程中發現如果只啟動NRFX_SPI_ENABLED,不任意使能它下面的NRFX_SPI0_ENABLED,NRFX_SPI1_ENABLED,NRFX_SPI2_ENABLED的其中一個,居然無法讓NRFX_SPIM_ENABLED生效,這一點需要注意一下。

 經過以上的宏定義勾選,SPI的驅動就啟動完成了。

第二步啟用PPI:

 到這一步宏定義就都啟用完成了,你可以檢測一下加入的原始檔是否都啟用了。

 然後就是程式碼部分,我已經進行了相關注釋,可以直接copy使用。

 三、程式碼

 1、.c檔案

#include "nrf_drv_spi.h"
#include "app_util_platform.h"
#include "nrf_drv_gpiote.h"
#include "nrf_delay.h"
#include "boards.h"
#include "app_error.h"
#include <string.h>

/*log列印的標頭檔案*/
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"


#include "nrf_drv_ppi.h"
#include "spi_test.h"
/*觸發中斷的按鍵,由於我使用的是DK板,所以直接使用按鍵4,如果你測試的是自己的板子,可以自己定義pin腳
#define PIN_IN    BUTTON_4 
#define leng 10
#define TEST_STRING "spi_ppi_test"
/*測試buffer設定*/
uint8_t TX_buffer[]=TEST_STRING;
uint8_t RX_buffer[leng];

/*PPI 通道範例變數定義*/
static nrf_ppi_channel_t     m_ppi_channel;

/*SPIM3可以達到32M*/
static nrfx_spim_t my_spi=NRFX_SPIM_INSTANCE(3);

/*變數定義,用於確定SPI是否正確傳輸,每一次都會進行加一,0x31只是一個本次隨意設定的初始值*/
uint8_t test=0x31;


/*同時設定TX和RX的buffer*/
nrfx_spim_xfer_desc_t m_buffer = NRFX_SPIM_XFER_TRX(TX_buffer,sizeof(TX_buffer),RX_buffer,leng);
/**
 * @brief SPI user event handler.
 * @param event
 */
void spi_event_handler(nrfx_spim_evt_t const * p_event,
                       void *                  p_context)
{
        ret_code_t err_code;
    /*傳輸事件判斷*/
        if(NRF_DRV_SPI_EVENT_DONE == p_event->type)
        {
            NRF_LOG_INFO("Transfer completed.")
            TX_buffer[0]=++test;
            /*一次傳輸完成,提供下一次傳輸的buffer,如果有大量接收的資料,可以使用兩個buffer交替接收,便於流出時間進行資料處理,第二個buffer也是使用 
            nrfx_spim_xfer_desc_t m_buffer = NRFX_SPIM_XFER_TRX(TX_buffer2,sizeof(TX_buffer2),RX_buffer2,leng2);進行定義,如果是隻想單獨定義一個TX和RX
            請使用 NRFX_SPIM_XFER_TX 或者 NRFX_SPIM_XFER_RX
            */
            err_code = nrfx_spim_xfer(&my_spi,&m_buffer,NRF_DRV_SPI_FLAG_HOLD_XFER);
            APP_ERROR_CHECK(err_code);
        }
        /*如果有接收資料就列印一下,實際應用中可以是其餘處理*/
    if (RX_buffer[0] != 0)
    {
        NRF_LOG_INFO(" Received:");
        NRF_LOG_HEXDUMP_INFO(RX_buffer, strlen((const char *)RX_buffer));
    }
}

void gpiote_init(void)
{
     ret_code_t err_code;
    /*由於這個例子是基於ble_app_uart,在 主函數中的buttons_leds_init(&erase_bonds);中已經初始化了GPIOTE,所以註釋掉,不能重複初始化*/
//        err_code = nrf_drv_gpiote_init();
//    APP_ERROR_CHECK(err_code);
    
        nrf_drv_gpiote_in_config_t in_config = NRFX_GPIOTE_CONFIG_IN_SENSE_HITOLO(true);
    in_config.pull = NRF_GPIO_PIN_PULLUP;
    
    /*由於這個例子是基於ble_app_uart,在 主函數中的buttons_leds_init(&erase_bonds);中已經初始化了DK板上的按鍵4,我要用來做SPI資料觸發,所以先解綁這個引腳在設定 */
        nrf_drv_gpiote_in_uninit(PIN_IN);
    /* 中斷初始化,回撥為NULL,因為是通過PPI觸發SPIM所以不需要回撥,當然如果有需要你也可以設定 */
        err_code = nrf_drv_gpiote_in_init(PIN_IN, &in_config, NULL);
    APP_ERROR_CHECK(err_code);
    /* GPIO中斷事件使能 */
        nrf_drv_gpiote_in_event_enable(PIN_IN, true);
}

void spim_init(void)
{
    /*不能使用低頻引腳,還要設定為高驅*/
    nrfx_spim_config_t spi_config = NRFX_SPIM_DEFAULT_CONFIG;
    spi_config.ss_pin   = 27;
    spi_config.miso_pin = 4;//如果是刷屏可以不使用MISO資料pin腳,可以設定為 NRFX_SPIM_PIN_NOT_USED
    spi_config.mosi_pin = 5;
    spi_config.sck_pin  = 26;
        spi_config.frequency=NRF_SPIM_FREQ_32M;
    APP_ERROR_CHECK(nrfx_spim_init(&my_spi, &spi_config, spi_event_handler, NULL));
    
    /*高驅設定*/
        nrf_gpio_cfg(27,
                NRF_GPIO_PIN_DIR_OUTPUT,
                NRF_GPIO_PIN_INPUT_DISCONNECT,
        NRF_GPIO_PIN_NOPULL,
        NRF_GPIO_PIN_H0H1,
        NRF_GPIO_PIN_NOSENSE);
        nrf_gpio_cfg(4,
                NRF_GPIO_PIN_DIR_INPUT,
                NRF_GPIO_PIN_INPUT_CONNECT,
        NRFX_SPIM_MISO_PULL_CFG,
        NRF_GPIO_PIN_H0H1,
        NRF_GPIO_PIN_NOSENSE);
        nrf_gpio_cfg(5,
                NRF_GPIO_PIN_DIR_OUTPUT,
                NRF_GPIO_PIN_INPUT_DISCONNECT,
        NRF_GPIO_PIN_NOPULL,
        NRF_GPIO_PIN_H0H1,
        NRF_GPIO_PIN_NOSENSE);
        nrf_gpio_cfg(26,
                NRF_GPIO_PIN_DIR_OUTPUT,
                NRF_GPIO_PIN_INPUT_DISCONNECT,
        NRF_GPIO_PIN_NOPULL,
        NRF_GPIO_PIN_H0H1,
        NRF_GPIO_PIN_NOSENSE);
                
        NRF_LOG_INFO("SPI init end.");
}

void ppi_init(void)
{
        ret_code_t err_code;

    err_code = nrf_drv_ppi_init();
    APP_ERROR_CHECK(err_code);
    
        /*給spi初始化快取,並flag選擇什麼時候啟動傳輸*/
        /*NRF_DRV_SPI_FLAG_HOLD_XFER ---- 佔時不啟用,可在PPI觸發下的SPIM的傳輸*/
        err_code = nrfx_spim_xfer(&my_spi,&m_buffer,NRF_DRV_SPI_FLAG_HOLD_XFER);
        APP_ERROR_CHECK(err_code);
        
        /* 觸發的事件地址*/
        uint32_t gpio_event_addr = nrfx_gpiote_in_event_addr_get(PIN_IN);
        /* 執行的任務地址 */
        uint32_t spim_tick_addr  = nrfx_spim_start_task_get(&my_spi);
    
        /*PPI設定*/
        //把PPI的通道1分配出來
    err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel);
    APP_ERROR_CHECK(err_code);
        /* 關聯事件和任務*/
        err_code = nrf_drv_ppi_channel_assign(m_ppi_channel,
                                          gpio_event_addr,
                                          spim_tick_addr);
        APP_ERROR_CHECK(err_code);
        /* 啟動PPI*/
        err_code = nrf_drv_ppi_channel_enable(m_ppi_channel);
    APP_ERROR_CHECK(err_code);
    
}

void SPIM_test_init(void)
{
        spim_init();
        gpiote_init();
        ppi_init();
}

2、.h檔案

#ifndef SPI_TEST_H__
#define SPI_TEST_H__


void SPIM_test_init(void);


#endif

只要在主函數中參照SPIM_test_init();就可以了。如下是我的邏輯分析儀的取樣波形,由於我使用的邏輯分析儀器最大隻有100M取樣速率,感覺有點不準確,導致有些偏差,還有就是我使用的是杜邦線連線,32M速率下也可能有影響,測試如果是使用杜邦線,儘量短一點,邏輯分析儀取樣率儘量高。