芯旺微chipon KF32A156系列 SPI 模块的基本使用

原创扣字不易,还请尊重原创,转载需注明源出处!!!

我们在实际的使用过程中,SPI也是较为常用的通讯方式,像flahs芯片,一些传感器,通讯模组 等会使用到这种通讯方式。
芯旺微的SPI外设简介
SPI 模块可配置为支持 SPI 协议或者 I2S 协议。SPI 模块默认工作在 SPI 方式,可通过软件将其切换到 I2S 模式。在 I2S 模式下,原则上数据传输为全双工模式,主机和从机同时收发数据,但实际情况下通常只有一个方向上的数据是有意义的。

本次主要讲解SPI主机和SPI从机配置以及收发方式。文章较长,可根据自己的需要进行选择。

SPI主机配置

注意:这里主机放在循环里面进行收发演示的,同步通讯,无需担心被打断等。
三步走:配置GPIO引脚 , 配置外设设置 , 配置外设中断
这里如何查询引脚可以重映射为什么功能就不细说了,可以翻阅其他文章有说。

1、配置GPIO引脚 ,这里做为主机将 时钟脚 , MOSI , MISO 配置为重映射引脚,将片选脚配置为GPIO输出脚。

void Spi_IO_Init()
{

    /* Configure SPI0 IO */
    GPIO_Write_Mode_Bits(GPIOD_SFR, GPIO_PIN_MASK_3, GPIO_MODE_RMP);   //sck
    GPIO_Write_Mode_Bits( GPIOD_SFR, GPIO_PIN_MASK_2, GPIO_MODE_RMP);  //sdi
    GPIO_Write_Mode_Bits(GPIOG_SFR, GPIO_PIN_MASK_15, GPIO_MODE_RMP);  //sdo
    GPIO_Write_Mode_Bits( GPIOG_SFR, GPIO_PIN_MASK_14, GPIO_MODE_OUT);  //cs

    GPIO_Pin_RMP_Config(GPIOD_SFR, GPIO_Pin_Num_3, GPIO_RMP_AF4);
    GPIO_Pin_RMP_Config(GPIOD_SFR, GPIO_Pin_Num_2, GPIO_RMP_AF4);
    GPIO_Pin_RMP_Config(GPIOG_SFR, GPIO_Pin_Num_15, GPIO_RMP_AF4);
//    GPIO_Pin_RMP_Config(PG14_SPI2_SS0_AF4);
}

#define SPI_CS_ENABLE GPIO_Set_Output_Data_Bits(GPIOG_SFR, GPIO_PIN_MASK_14, Bit_RESET)
#define SPI_CS_DISABLE GPIO_Set_Output_Data_Bits(GPIOG_SFR, GPIO_PIN_MASK_14, Bit_SET)

2、配置外设设置:主要的点是:时钟源选择(涉及到波特率),spi模式(这里选择的是空闲时为低,第一个时钟沿发送数据),数据位数(这里选择8位),发送接收接口函数配置。以下为配置及注释

//外设配置
 Spi_Init(SPI2_SFR)
 {
  SPI_InitTypeDef Spi_ConfigPtr;

    /* SPI mode */
    Spi_ConfigPtr.m_Mode = SPI_MODE_MASTER_CLKDIV4;   //四分频
    /* SPI clock */
    Spi_ConfigPtr.m_Clock = SPI_CLK_SCLK;      //选择主频120m
    /* Data transfer start control */
    Spi_ConfigPtr.m_FirstBit = SPI_FIRSTBIT_MSB;   //对齐方式
    /* Spi idle state */
    Spi_ConfigPtr.m_CKP = SPI_CKP_LOW;      //空闲时为低
    /* Spi clock phase(Data shift edge) */
    Spi_ConfigPtr.m_CKE = SPI_CKE_1EDGE;     //第一个时钟沿发送数据
    /* Data width */
    Spi_ConfigPtr.m_DataSize = SPI_DATASIZE_8BITS;    //数据位数为8
    /* Baud rate :Fck_spi=Fck/2(m_BaudRate+1)*/
    Spi_ConfigPtr.m_BaudRate = 0x02;   //这里通过计算120m/4/(2(2+1)) = 5m
    /* Spi reset */
    SPI_Reset(SPIx);
    /* Configure SPI module */
    SPI_Configuration(SPIx, &Spi_ConfigPtr);
    
    SPI_Cmd(SPIx, TRUE);
 }
   
//收发函数:由于是同步通讯在我们进行发送的同时也会进行数据的接收,
//所以我们这里使用接受完成标志位来进行本次发送完成的判断,并且将接收到的数据进行返回。
  uint8_t spi_recieve_SEND_DATA(SPI_SFRmap* SPIx_sfr ,uint8_t data)
{
	SPI_CS_ENABLE;
	SPI_I2S_SendData8(SPIx_sfr, data);   //往发送buff填充数据
	while(!SPI_Get_Receive_Buf_Flag (SPIx_sfr));   //等待接收数据完成
	SPI_CS_DISABLE;
	return SPI_I2S_ReceiveData(SPIx_sfr);
}

3、这里我就不用中断进行收发了,上面已经做好了收发函数,演示一下使用。如果使用中断可以参考

//创建变量进行数据接收,这个可以根据自身要求修改
uint8_t recieve_data1,recieve_data2,recieve_data3,recieve_data4 ,recieve_data5  ;
//演示数据
  	recieve_data1 = spi_recieve_SEND_DATA(SPI2_SFR, 0xAA);

  	recieve_data2 = spi_recieve_SEND_DATA(SPI2_SFR, 0xBB);

  	recieve_data3 = spi_recieve_SEND_DATA(SPI2_SFR, 0xCC);

  	recieve_data4 = spi_recieve_SEND_DATA(SPI2_SFR, 0xDD);

  	recieve_data5 = spi_recieve_SEND_DATA(SPI2_SFR, 0xEE);

通过工具查看总线状态:
在这里插入图片描述
单个数据放大如下图:
在这里插入图片描述

SPI从机配置

1、配置GPIO引脚 ,这里做为从机将 cs(ss)脚,时钟脚 , MOSI脚 , MISO脚 配置为重映射引脚。

void Spi_IO_Init()
{
    /* Configure SPI0 IO */
    GPIO_Write_Mode_Bits(GPIOD_SFR, GPIO_PIN_MASK_3, GPIO_MODE_RMP);
    GPIO_Write_Mode_Bits(GPIOD_SFR, GPIO_PIN_MASK_2, GPIO_MODE_RMP);
    GPIO_Write_Mode_Bits( GPIOG_SFR, GPIO_PIN_MASK_15, GPIO_MODE_RMP);
    GPIO_Write_Mode_Bits(GPIOG_SFR, GPIO_PIN_MASK_14, GPIO_MODE_RMP);

    GPIO_Pin_RMP_Config(GPIOD_SFR, GPIO_Pin_Num_3, GPIO_RMP_AF4);
    GPIO_Pin_RMP_Config( GPIOD_SFR, GPIO_Pin_Num_2, GPIO_RMP_AF4);
    GPIO_Pin_RMP_Config(GPIOG_SFR, GPIO_Pin_Num_15, GPIO_RMP_AF4);
    GPIO_Pin_RMP_Config(GPIOG_SFR, GPIO_Pin_Num_14, GPIO_RMP_AF4);
}

2、配置SPI从机外设,从机主要参数,空闲电平模式,跳变沿数据传输,对齐方式,数据传输位数。

void Spi_Init(SPI_SFRmap *SPIx)
{
    SPI_InitTypeDef Spi_ConfigPtr;

    /* SPI mode */
    Spi_ConfigPtr.m_Mode = SPI_MODE_SLAVE;   //从机模式
    /* SPI clock */
    Spi_ConfigPtr.m_Clock = SPI_CLK_SCLK;
    /* Data transfer start control */
    Spi_ConfigPtr.m_FirstBit = SPI_FIRSTBIT_MSB;
    /* Spi idle state */
    Spi_ConfigPtr.m_CKP = SPI_CKP_LOW;      //空闲时低电平   
    /* Spi clock phase(Data shift edge) */
    Spi_ConfigPtr.m_CKE = SPI_CKE_1EDGE;     //第一个跳变沿发送数据
    /* Data width */
    Spi_ConfigPtr.m_DataSize = SPI_DATASIZE_8BITS;     // 8位
    /* Baud rate :Fck_spi=Fck/2(m_BaudRate+1)*/
    Spi_ConfigPtr.m_BaudRate = 0x2;

    /* Spi reset */
    SPI_Reset(SPIx);
    /* Configure SPI module */
    SPI_Configuration(SPIx, &Spi_ConfigPtr);

    /* Enable SPI module */
    SPI_Cmd(SPIx, TRUE);
}

3、配置中断,由于从机设备属于被动接收发送,故这里配置为中断方式收发。

//配置外设中断
void Spi_Interrupt_Init(SPI_SFRmap *SPIx)
{
    /* Send empty interrupt enable */
    SPI_TNEIE_INT_Enable(SPIx, TRUE);
    /* Receive is not empty interrupt enable */
    SPI_RNEIE_INT_Enable(SPIx, TRUE);
    /* Total interrupt enable */
    	if(SPIx == SPI0_SFR)
	{
		INT_Interrupt_Enable(INT_SPI0, TRUE);
	}
	if(SPIx == SPI1_SFR)
	{
		INT_Interrupt_Enable(INT_SPI1, TRUE);
	}
	if(SPIx == SPI2_SFR)
	{
		INT_Interrupt_Enable(INT_SPI2, TRUE);
	}
}

那中断服务函数可以根据自身协议需求进行配置,这里为了和主机呼应,我们就对主机数据进行解析:主机:0xAA 从机准备 0x11 主机0xBB 从机准备 0x22 … 以此类推;

volatile uint32_t recieve_data ;
void __attribute__((interrupt)) _SPI2_exception(void)
{
        /* Wait send buffer to empty */
        if (SPI_Get_Transmit_Buf_Flag(SPI2_SFR) == RESET)
        {
    		//  user  code
        }
        /* Wait receive buffer not empty */
        if (SPI_Get_Receive_Buf_Flag(SPI2_SFR) == SET)
        {
            /* Receive data */
        	recieve_data =SPI_I2S_ReceiveData(SPI2_SFR);
        	//{ user code}
        	if(recieve_data == 0xAA)
        	{
        		SPI_I2S_SendData8(SPI2_SFR, 0x11);
        	}
        	else if(recieve_data == 0xBB)
			{
				SPI_I2S_SendData8(SPI2_SFR, 0x22);
			}
        	else if(recieve_data == 0xCC)
			{
				SPI_I2S_SendData8(SPI2_SFR, 0x33);
			}
        	else if(recieve_data == 0xDD)
			{
				SPI_I2S_SendData8(SPI2_SFR, 0x44);
			}
        	else if(recieve_data == 0xEE)
			{
				SPI_I2S_SendData8(SPI2_SFR, 0x55);
			}
        	else
        	{
        		;
        	}
        }
}

注意:这里面其实发送中不需要的话也可以不开,接收及时,可以自行在接收中断里面进行发送。

根据从机配置,以及配合前面主机的程序,SPI总线数据状态如下图:
在这里插入图片描述
单个数据放大图
在这里插入图片描述

当然了,如果数据量比较大,为了节省资源,我们也可以使用DMA进行传输数据。

好了,本次关于SPI的使用就说完了。关于spi+DMA的讲解后续持续更新。

本篇第一次更新时间:2023 . 12 . 21
如有需要使用MCU的问题,可评论或私聊。