芯旺微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的问题,可评论或私聊。