本帖最后由 呐咯密密 于 2023-10-20 14:07 编辑
#申请原创#
前言 又是许久没有更新原创文章了,最近因为项目的积压,导致没有闲置时间进行开发的记录积累,乘着空闲时间,将最近的开发经验进行短暂的总结。 最近进行MCU平台的更替,老项目的升级换代以及新产品的开发,主控从GD的M0平台升级到N32的M4平台,开发的代码需要进行全面的替换,仅此对核心的外设应用做一个记录。 此文是为了记录主从SPI的开发记录,其中一个SPI设置为主从模式,用于与传感器进行通信,另一个SPI作为从设备,用于向外部设备返回数据,考虑时间的要求,两个SPI均使用DMA进行数据搬运。
一、SPI的相关GPIO初始化 void SPI_GPIO_Inti(void)
{
// RCC_Pclk2_Config(RCC_HCLK_DIV1);
EXTI_InitType EXTI_InitStructure;
/* Enable peripheral clocks --------------------------------------------------*/
/* spi clock enable */
RCC_APB2_Peripheral_Clock_Enable(SPI_MASTER_PERIPH | SPI_SLAVE_PERIPH | RCC_APB2_PERIPH_AFIO);
/* GPIO Periph clock enable */
RCC_AHB_Peripheral_Clock_Enable(SPI_MASTER_PERIPH_GPIO | SPI_SLAVE_PERIPH_GPIO | RCC_AHB_PERIPH_DMA | RCC_AHB_PERIPH_GPIOB);
GPIO_InitType GPIO_InitStructure;
GPIO_Structure_Initialize(&GPIO_InitStructure);
GPIO_InitStructure.Pin = SPI_MASTER_MOSI_PIN | SPI_MASTER_CLK_PIN | SPI_MASTER_MISO_PIN ;
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.GPIO_Slew_Rate = GPIO_SLEW_RATE_FAST;
GPIO_InitStructure.GPIO_Alternate = SPI_MASTER_GPIO_ALTERNATE;
GPIO_Peripheral_Initialize(SPI_MASTER_GPIO, &GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_11 | GPIO_PIN_12 ;
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.GPIO_Alternate = GPIO_AF1_SPI2;
GPIO_Peripheral_Initialize(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_6 ;
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.GPIO_Alternate = GPIO_AF6_SPI2;
GPIO_Peripheral_Initialize(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_15 ;
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_INPUT;
GPIO_Peripheral_Initialize(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_3;
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_INPUT;
GPIO_Peripheral_Initialize(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.Pin = SPI_MASTER_NSS_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_OUT_PP;
GPIO_InitStructure.GPIO_Current = GPIO_DS_4MA;
GPIO_Peripheral_Initialize(SPI_MASTER_GPIO, &GPIO_InitStructure);
// /* Configure key EXTI Line to key input Pin */
GPIO_EXTI_Line_Set(EXTI_LINE_SOURCE15, AFIO_EXTI_PA15);
/* Configure key EXTI line */
EXTI_InitStructure.EXTI_Line = EXTI_LINE15;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Peripheral_Initializes(&EXTI_InitStructure);
}
SPI主使用的是SPI_MASTER(下文均使用SPI1),使用的GPIO如下,均为GPIOA: #define SPI_MASTER_MISO_PIN GPIO_PIN_6 #define SPI_MASTER_MOSI_PIN GPIO_PIN_7 #define SPI_MASTER_CLK_PIN GPIO_PIN_5 #define SPI_MASTER_NSS_PIN GPIO_PIN_4
其中NSS使用软件NSS,所以此处的GPIO配置为输出而非复用。
SPI从设备使用的是SPI_SLAVE(下文均使用SPI2),使用的GPIO如下: #define SPI_SLAVE_MISO_PIN GPIO_PIN_11 #define SPI_SLAVE_MOSI_PIN GPIO_PIN_12 #define SPI_SLAVE_CLK_PIN GPIO_PIN_6 //PB6 #define SPI_SLAVE_NSS_PIN GPIO_PIN_15 //PA15
从设备的时钟和NSS均来自外部主设备,所以NSS设置为输入,并开启外部中断(此处目的是为了适配自己的项目需求)。具体的复用管脚重映射参加下图,图片来自于官方的用户手册。 二、SPI初始化 void SPI_Init(void)
{
SPI_InitType SPI_InitStructure;
/* GPIO configuration ------------------------------------------------------*/
SPI_GPIO_Inti();
/* log configuration ------------------------------------------------------*/
NVIC_Configuration();
/* Initializes the variable */
SPI_I2S_Reset(SPI_MASTER);
SPI_I2S_Reset(SPI_SLAVE);
/* DMA configuration ------------------------------------------------------*/
SPI_DMA_Configuration();
/* SPI_MASTER configuration ------------------------------------------------------*/
SPI_Initializes_Structure(&SPI_InitStructure);
SPI_InitStructure.DataDirection = SPI_DIR_DOUBLELINE_FULLDUPLEX;
SPI_InitStructure.SpiMode = SPI_MODE_MASTER;
SPI_InitStructure.DataLen = SPI_DATA_SIZE_8BITS;
SPI_InitStructure.CLKPOL = SPI_CLKPOL_LOW;
SPI_InitStructure.CLKPHA = SPI_CLKPHA_SECOND_EDGE;
SPI_InitStructure.NSS = SPI_NSS_SOFT;
/* It is recommended that the SPI master mode of the C version chips should not exceed 18MHz */
SPI_InitStructure.BaudRatePres = SPI_BR_PRESCALER_4;
SPI_InitStructure.FirstBit = SPI_FB_MSB;
// SPI_InitStructure.CRCPoly = 7;
SPI_Initializes(SPI_MASTER, &SPI_InitStructure);
SPI_SS_Output_Enable(SPI_MASTER);
// SPI_Set_Nss_Level(SPI_MASTER, SPI_NSS_HIGH);
SPI_InitStructure.DataDirection = SPI_DIR_DOUBLELINE_FULLDUPLEX;
SPI_InitStructure.SpiMode = SPI_MODE_SLAVE;
SPI_InitStructure.DataLen = SPI_DATA_SIZE_8BITS;
SPI_InitStructure.CLKPOL = SPI_CLKPOL_LOW;
SPI_InitStructure.CLKPHA = SPI_CLKPHA_SECOND_EDGE;
SPI_InitStructure.NSS = SPI_NSS_SOFT;
// SPI_InitStructure.SpiMode = SPI_MODE_SLAVE;
SPI_Initializes(SPI_SLAVE, &SPI_InitStructure);
SPI_Set_Nss_Level(SPI_SLAVE, SPI_NSS_LOW);
SPI_I2S_DMA_Transfer_Enable(SPI_SLAVE, SPI_I2S_DMA_TX);
SPI_I2S_DMA_Transfer_Enable(SPI_SLAVE, SPI_I2S_DMA_RX);
SPI_I2S_DMA_Transfer_Enable(SPI_MASTER, SPI_I2S_DMA_TX);
SPI_I2S_DMA_Transfer_Enable(SPI_MASTER, SPI_I2S_DMA_RX);
/* Enable SPI_MASTER */
// SPI_I2S_Interrupts_Enable(SPI_SLAVE, SPI_I2S_INT_RNE);
SPI_ON(SPI_MASTER);
SPI_ON(SPI_SLAVE);
}
在SPI外设的初始化中调用GPIO初始化函数、中断初始化函数和DMA初始化函数,完成SPI的整体初始化,中断以及DMA在下文介绍。 SPI1作为主设备,配置为双向通信,根据从设备的SPI协议定义CPOL和CPHA,NSS采用软件模式,SPI挂载在PCLK上,最大时钟频率是64MHz,这里进行4分频,将主SPI的频率设置为16MHz,使用SPI的主模式需要调用SPI_SS_Output_Enable(SPI_MASTER);函数,否则无法正常使用,此处与之前的使用有所区别。 SPI2作为从设备,无需设置频率,从设备的时钟频率会跟随主设备,但是需要调用SPI_Set_Nss_Level(SPI_SLAVE, SPI_NSS_LOW);定义NSS的生效电平。 因为使用了DMA,此处会使能DMA。
三、SPI的DMA及中断配置 void SPI_DMA_Configuration(void)
{
DMA_InitType DMA_InitStructure;
DMA_Reset(DMA_CH1);
DMA_Reset(DMA_CH2);
DMA_Reset(DMA_CH3);
DMA_Reset(DMA_CH4);
/* SPI_MASTER TX DMA config */
DMA_InitStructure.MemAddr = (uint32_t)&READ_Data_TX_BUF[0];
DMA_InitStructure.MemDataSize = DMA_MEM_DATA_WIDTH_BYTE;
DMA_InitStructure.MemoryInc = DMA_MEM_INC_MODE_ENABLE;
DMA_InitStructure.Direction = DMA_DIR_PERIPH_DST;
DMA_InitStructure.PeriphAddr = (uint32_t)&SPI_MASTER->DAT;
DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_WIDTH_BYTE;
DMA_InitStructure.PeriphInc = DMA_PERIPH_INC_MODE_DISABLE;
DMA_InitStructure.BufSize = BufferSize;
DMA_InitStructure.CircularMode = DMA_CIRCULAR_MODE_DISABLE;
DMA_InitStructure.Mem2Mem = DMA_MEM2MEM_DISABLE;
DMA_InitStructure.Priority = DMA_CH_PRIORITY_MEDIUM;
DMA_Initializes(DMA_CH1, &DMA_InitStructure);
DMA_Channel_Request_Remap(DMA_CH1, SPI_MASTER_DMA_TX_CH);
/* SPI_MASTER RX DMA config */
DMA_InitStructure.MemAddr = (uint32_t)&READ_Data_RX_BUF[0];
DMA_InitStructure.Direction = DMA_DIR_PERIPH_SRC;
DMA_InitStructure.Priority = DMA_CH_PRIORITY_HIGH;
DMA_Initializes(DMA_CH2, &DMA_InitStructure);
DMA_Channel_Request_Remap(DMA_CH2, SPI_MASTER_DMA_RX_CH);
/* SPI_Slave TX DMA config */
DMA_InitStructure.MemAddr = (uint32_t)&READ_ANGLE_TX_BUF[0];
DMA_InitStructure.Direction = DMA_DIR_PERIPH_DST;
DMA_InitStructure.PeriphAddr = (uint32_t)&SPI_SLAVE->DAT;
DMA_InitStructure.Priority = DMA_CH_PRIORITY_HIGHEST;
DMA_Initializes(DMA_CH3, &DMA_InitStructure);
DMA_Channel_Request_Remap(DMA_CH3, SPI_SLAVE_DMA_TX_CH);
/* SPI_Slave RX DMA config */
DMA_InitStructure.MemAddr = (uint32_t)&READ_ANGLE_RX_BUF[0];
DMA_InitStructure.Direction = DMA_DIR_PERIPH_SRC;
DMA_InitStructure.Priority = DMA_CH_PRIORITY_HIGHEST;
DMA_Initializes(DMA_CH4, &DMA_InitStructure);
DMA_Channel_Request_Remap(DMA_CH4, SPI_SLAVE_DMA_RX_CH);
// DMA_Interrupt_Status_Clear(DMA, DMA_CH1_INT_TXC);
// DMA_Interrupts_Enable(DMA_CH1, DMA_INT_TXC);
DMA_Interrupt_Status_Clear(DMA, DMA_CH3_INT_TXC);
DMA_Interrupts_Enable(DMA_CH3, DMA_INT_TXC);
// DMA_Interrupt_Status_Clear(DMA, DMA_CH3_INT_HTX);
// DMA_Interrupts_Enable(DMA_CH3, DMA_INT_HTX);
DMA_Channel_Enable(DMA_CH3);
DMA_Channel_Enable(DMA_CH4);
DMA_Channel_Enable(DMA_CH1);
DMA_Channel_Enable(DMA_CH2);
}
void NVIC_Configuration(void)
{
NVIC_InitType NVIC_InitStructure;
NVIC_Priority_Group_Set(NVIC_PER1_SUB3_PRIORITYGROUP);
//
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = NVIC_SUB_PRIORITY_0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Initializes(&NVIC_InitStructure);
//
NVIC_InitStructure.NVIC_IRQChannel = DMA_Channel1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = NVIC_PER_PRIORITY_0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = NVIC_SUB_PRIORITY_1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Initializes(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = DMA_Channel3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = NVIC_PER_PRIORITY_0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = NVIC_SUB_PRIORITY_2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Initializes(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = SPI2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = NVIC_PER_PRIORITY_0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = NVIC_SUB_PRIORITY_1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Initializes(&NVIC_InitStructure);
}
DMA的配置比较简单,各个平台的配置都很相似,配置内存地址和外设地址,内存地址均为自定义的数组首地址,外设地址为SPI的数据寄存器,发送时DMA将数组数据-->SPI数据寄存器,SPI启动时变会将数据通过SPI外设送出,接收时数据保存进SPI的数据寄存器,DAM将数据搬运到数组中。 中断初始化函数中使能了DMA的通道发送完成中断和SPI的接收完成中断以及SPI2的NSS下降沿中断。(此处的中断均是备选,实际无需要可不开启) 四、SPI的DMA收发函数 由于两个DMA均采用正常模式,在执行完一次数据搬运以后便会停止,所以此处需要包装两个函数,用于启动DMA。 void SPI1_DMA_SR(void)
{
bool P1,P2;
GPIOA->PBC = GPIO_PIN_4;
// SPI1->CTRL2 |= SPI_I2S_DMA_TX;
// SPI1->CTRL2 |= SPI_I2S_DMA_RX;
DMA_CH1->CHCFG &= DMA_CHANNEL_DISABLE;
DMA_CH1->TXNUM = BufferSize;
DMA_CH1->CHCFG |= DMA_CHANNEL_ENABLE;
DMA_CH2->CHCFG &= DMA_CHANNEL_DISABLE;
DMA_CH2->TXNUM = BufferSize;
DMA_CH2->CHCFG |= DMA_CHANNEL_ENABLE;
while(SET == SPI_I2S_Flag_Status_Get(SPI_MASTER,SPI_I2S_FLAG_BUSY));
GPIOA->PBSC = GPIO_PIN_4;
}
void SPI2_DMA_SR(void)
{
// SPI_I2S_DMA_Transfer_Enable(SPI_SLAVE, SPI_I2S_DMA_TX);
// SPI_I2S_DMA_Transfer_Enable(SPI_SLAVE, SPI_I2S_DMA_RX);
// DMA_Channel_Disable(DMA_CH3);
// DMA_Buffer_Size_Config(DMA_CH3,BufferSize);
// DMA_Memory_Address_Config(DMA_CH3,(uint32_t)&READ_ANGLE_TX_BUF);
// DMA_Channel_Enable(DMA_CH3);
// DMA_Channel_Disable(DMA_CH4);
// DMA_Buffer_Size_Config(DMA_CH4,BufferSize);
// DMA_Memory_Address_Config(DMA_CH4,(uint32_t)&READ_ANGLE_RX_BUF);
// DMA_Channel_Enable(DMA_CH4);
DMA_CH3->CHCFG &= DMA_CHANNEL_DISABLE;
DMA_CH3->TXNUM = BufferSize;
DMA_Memory_Address_Config(DMA_CH3,(uint32_t)&READ_ANGLE_TX_BUF);
DMA_CH3->CHCFG |= DMA_CHANNEL_ENABLE;
DMA_CH4->CHCFG &= DMA_CHANNEL_DISABLE;
DMA_CH4->TXNUM = BufferSize;
DMA_CH4->CHCFG |= DMA_CHANNEL_ENABLE;
}
void DMA_Channel1_IRQHandler(void)
{
if(DMA_Interrupt_Status_Get(DMA, DMA_CH1_INT_TXC) == SET)
{
DMA_Interrupt_Status_Clear(DMA, DMA_CH1_INT_TXC);
while(DMA_Flag_Status_Get(DMA, DMA_CH2_TXCF) == RESET)
;
/*用户代码*/
}
}
void DMA_Channel3_IRQHandler(void)
{
if(DMA_Interrupt_Status_Get(DMA, DMA_CH3_INT_TXC) == SET)
{
DMA_Interrupt_Status_Clear(DMA, DMA_CH3_INT_TXC);
while(SET == SPI_I2S_Flag_Status_Get(SPI_SLAVE,SPI_I2S_FLAG_BUSY));
//SLAVE_NSS_FLAG = 0;
SPI2_DMA_SR();
}
}
此处封装两个函数: void SPI1_DMA_SR(void) 用于启动主设备SPI1进行传感器数据的读取。 void SPI2_DMA_SR(void) 启动从设备SPI2用于向外部发送数据。 连个函数均采用寄存器的方式编写,目的是为了尽可能的缩小DMA的启动时间,实测提升很大呦。不然就会出现NSS下拉以后很久才会出现时钟信号,我在DMA的启动函数中都会做此处理。而且为了方便阅读,在SPI2_DMA_SR()函数中我保留了库函数的方式,与下文的寄存器方式是对应的。 在DMA通道3(对应SPI2的接收)中断服务函数中,判断中断触发,表示数据已经发送完毕,此时需要调用SPI2_DMA_SR();再次启动DMA,防止下次指令到来无法送出数据。 源代码因为涉及到项目,无法发出,如果有问题可回复,我看到会给解答。
|