打印
[KungFu32位 MCU]

KF32A156系列 SPI 模块的基本使用

[复制链接]
1318|8
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tpgf|  楼主 | 2024-3-5 08:17 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
我们在实际的使用过程中,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进行传输数据。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/Fanshijun1/article/details/131164592

使用特权

评论回复
沙发
wakayi| | 2024-4-3 15:11 | 只看该作者
这波形 太规整了  是仿真得到的波形?

使用特权

评论回复
板凳
tpgf|  楼主 | 2024-4-3 17:27 | 只看该作者
我看很多flash都是spi通讯方式 这是为什么呢

使用特权

评论回复
地板
木木guainv| | 2024-4-3 18:03 | 只看该作者
一般来说多大的数据量需要使用dma呢

使用特权

评论回复
5
磨砂| | 2024-4-3 18:36 | 只看该作者
iis和spi协议的差异具体体现在哪里呢

使用特权

评论回复
6
chenjun89| | 2024-4-3 20:10 | 只看该作者
SPI是MCU的常用串行外设接口

使用特权

评论回复
7
xiaoqizi| | 2024-4-3 22:47 | 只看该作者
如果真的用主从的话 还真不如使用iic

使用特权

评论回复
8
wowu| | 2024-4-3 23:20 | 只看该作者
代码发送很有规律性 完全可以进行一定程度的简化

使用特权

评论回复
9
weifeng90| | 2024-4-7 09:03 | 只看该作者
SPI的通信速率和带宽要优于IIS。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1364

主题

13994

帖子

8

粉丝