一、测试的平台
本篇文章主要介绍将SPI功能移植到ble_app_hrs_pca10040_s132工程上。
整个测试平台如下:
环境:win10,64位,MDK集成开发环境.
SDK:nRF5_SDK_15.2
协议栈:s132_nrf52_6.1_softdevice.hex.
硬件平台:pca10040开发板.
二、工作原理
这里使用了SPI的PPI模式,能够极大的提高SPI的读写速度,减少MCU的干预,可以极大的节省功耗。当你对SPI的读写预先将NRF_SPIM0->TXD.PTR指向写buffer,将NRF_SPIM0->RXD.PTR指向读buffer。
每次进行SPI读写的时候,指针会自动往后移,当数据的长度达到buffer的长度时,需要将NRF_SPIM0->TXD.PTR和NRF_SPIM0->TXD.PTR重新置位。
三、Application移植
1、添加相关C文件
需要添加TIMER、PPI、SPI、GPIOTE四个外设的相关文件
在工程中添加nrfx_ppi.c,nrf_drv_ppi.c,nrfx_spi.c,nrf_drv_spi.c,nrfx_spim.c,nrfx_gpiote.c这六个文件
2、添加头文件
添加相关头文件
3、加入SPI相关代码
SPI普通模式可以参考nRF5_SDK_15.2.0_9412b96\examples\peripheral\spi例程,在移植到协议栈中时,要注意在sdk_config.h中提高SPI的中断优先级,否则会无法进入中断。
SPI的PPI模式可以参照一下程序去编写:
(1)SPI初始化
在SPI的PPI模式中,只会调用到SCK,SIMO,SOMI这三个引脚。所以片选脚要自己去拉低拉高或者配置为GPIOTE模式,使用PPI去触发GPIOTE事件。
void ADC_SPIDMA_Init (void) //初始化SPI DMA相关参数
{
NRF_SPIM0->TXD.MAXCNT = 2;//每次写两个字节 (16位寄存器)
NRF_SPIM0->RXD.MAXCNT = 2;//每次读两个字节 (16位数据)
NRF_SPIM0->TXD.LIST=1; //开启写DMA
NRF_SPIM0->TXD.PTR=(uint32_t)&SPIWriteList; //指向写buffer
NRF_SPIM0->RXD.LIST=1; //开启读DMA
NRF_SPIM0->RXD.PTR=(uint32_t)&SPIReadList1; //指向读buffer
}
void CS_Gpiote_Init(void) //初始化片选脚,一定要使用GPIOTE模式
{
uint32_t err_code;
nrf_drv_gpiote_out_config_t config = GPIOTE_CONFIG_OUT_TASK_TOGGLE(true); //设置为切换触发
err_code = nrf_drv_gpiote_out_init(SPI_SS_PIN, &config); //设置GPIOTE引脚
APP_ERROR_CHECK(err_code); //
nrf_drv_gpiote_out_task_enable(SPI_SS_PIN); //使能GPIOTE事件
nrf_drv_gpiote_out_set(SPI_SS_PIN); //
}
void spi_init(void)
{
nrf_drv_spi_config_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG;
// spi_config.ss_pin = SPI_SS_PIN;
spi_config.miso_pin = SPI_MISO_PIN;
spi_config.mosi_pin = SPI_MOSI_PIN;
spi_config.sck_pin = SPI_SCK_PIN;
APP_ERROR_CHECK(nrf_drv_spi_init(&spi, &spi_config, spi_event, NULL));
CS_Gpiote_Init();
ADC_SPIDMA_Init();
}
(2)SPI中断事件
当数据达到指定长度时重新置位指针。
void spi_event(nrf_drv_spi_evt_t const * p_event,
void * p_context)
{
spi_count++;
if(spi_count==80)//buffer长度
{
spi_count=0;
NRF_SPIM0->TXD.PTR=(uint32_t)&SPIWriteList;//重新将指针指回起始地址
NRF_SPIM0->RXD.PTR=(uint32_t)&SPIReadList1;//将指针指回起始地址
}
}
也可以加入开关控制,避免数据被覆盖。
当检测到send_flag置1时,进行相关数据的读取,读完数据再调用SPI_start进行SPI的相关使能。
中间可以调用SPI_stop失能SPI和定时器,这样可以节省大部分的功耗。
void spi_event(nrf_drv_spi_evt_t const * p_event,
void * p_context)
{
spi_count++;
if(spi_count==80)
{
nrf_drv_timer_pause(&m_timer1);//暂停PPI触发定时器
spi_count=0;
send_flag = 1;
}
}
void SPI_reset_buff(void)
{
spi_count = 0;
NRF_SPIM0->TXD.PTR=(uint32_t)&SPIWriteList;
NRF_SPIM0->RXD.PTR=(uint32_t)&SPIReadList1;
}
void SPI_stop(void)
{
nrf_drv_timer_disable(&m_timer1);
nrf_spim_disable(NRF_SPIM0);
}
void SPI_start(void)
{
SPI_reset_buff();//复位指针
nrf_spim_enable(NRF_SPIM0); //使能SPI
nrf_drv_timer_enable(&m_timer1);//使能TIMER
}
(3)PPI事件
这里一共有两个PPI事件,事件1是触发SPI开始以及GPIOTE,事件2是触发GPIOTE
void my_ppi_init(void)
{
uint32_t err_code = NRF_SUCCESS;
err_code = nrf_drv_ppi_init();//init PPI
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_timer_init(&m_timer1, NULL, timer_handler1);//init timer 1
APP_ERROR_CHECK(err_code);
/* setup m_timer for compare event every 15us */
uint32_t ticks = nrf_drv_timer_us_to_ticks(&m_timer1, 15);
nrf_drv_timer_extended_compare(&m_timer1, NRF_TIMER_CC_CHANNEL0, ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);//设置比较时间
nrf_drv_timer_enable(&m_timer1);//使能定时器
uint32_t timer_compare_event_addr = nrf_drv_timer_compare_event_address_get(&m_timer1, NRF_TIMER_CC_CHANNEL0);
uint32_t spi_start_event_addr = nrf_drv_spi_start_task_get(&spi);
uint32_t spi_end_event_addr = nrf_drv_spi_end_event_get(&spi);
uint32_t gpiote_out_event_addr = nrf_drv_gpiote_out_task_addr_get(SPI_SS_PIN);
/* setup ppi channel so that timer compare event is triggering sample task in SAADC */
err_code = nrf_drv_ppi_channel_alloc(&time_ppi_channel);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_ppi_channel_assign(time_ppi_channel, timer_compare_event_addr, gpiote_out_event_addr);//timer触发gpiote,片选脚拉低
APP_ERROR_CHECK(err_code);
err_code=nrf_drv_ppi_channel_fork_assign(time_ppi_channel,spi_start_event_addr);//timer触发spi
APP_ERROR_CHECK(err_code);
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, spi_end_event_addr, gpiote_out_event_addr);//spi结束触发gpiote,片选脚拉高
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_ppi_channel_enable(time_ppi_channel);
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_ppi_channel_enable(m_ppi_channel);
APP_ERROR_CHECK(err_code);
}