簡介:在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速率下也可能有影響,測試如果是使用杜邦線,儘量短一點,邏輯分析儀取樣率儘量高。