打印
[RISC-V MCU 应用开发]

第四十七章、CH32V103应用教程——SPI-单工通信(1条时钟线...

[复制链接]
1702|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 RISCVLAR 于 2021-1-12 10:31 编辑

CH32V103应用教程——SPI-单工通信(1条时钟线和1条双向数据线),主机接收从机发送
本章教程主要在SPI单工通信方式下进行1条时钟线和1条双向数据线配置,并进行主机接收从机发送。

1、SPI简介及相关函数介绍
关于SPI单工通信模式下1条时钟线和1条双向数据线配置介绍,在第46章已经进行介绍,在此不再赘述。
关于CH32V103 SPI具体信息,可参考CH32V103应用手册。SPI标准库函数在第十五章节已介绍,在此不再赘述。

2、硬件设计
本章教程主要在SPI单工通信模式下选择1条时钟线和1条双向数据线进行主机接收从机发送实验,需用到两个开发板,且由于采用1条时钟线和1条双向数据线配置,因此主设备使用MOSI引脚,从设备使用MISO引脚进行通讯。此处使用外设为SPI1,主设备MOSI对应引脚为PA7引脚,从设备MISO对应引脚为PA6引脚,将主设备PA7引脚与从设备PA6引脚连接起来,此外还需将两个开发板SPI1对应的SCK引脚PA5连接起来。

此外,由于两个开发板需要同时进行上电传输,因此将两个开发板的3.3V引脚和GND引脚进行连接。

3软件设计
本章教程主要进行SPI单工通信模式1条时钟线和1条双向数据线配置下的主机接收从机发送实验,具体程序如下:
spi.h文件
#ifndef __SPI_H
#define __SPI_H

#include "ch32v10x_conf.h"

/* SPI Mode Definition */
#define HOST_MODE    0
#define SLAVE_MODE   1

/* SPI Communication Mode Selection */
#define SPI_MODE   HOST_MODE
//#define SPI_MODE   SLAVE_MODE

#define  Size  18

extern volatile u8 Txval;
extern volatile u8 Rxval;
extern u16 TxData[Size];
extern u16 RxData[Size];

void SPI_1Lines_HalfDuplex_Init(void);
void SPI1_IRQHandler(void);

#endif
spi.h文件主要进行相关宏定义和函数声明;
spi.c文件
#include "spi.h"

/* Global Variable */
volatile u8 Txval=0, Rxval=0;

u16 TxData[Size] = { 0x0101, 0x0202, 0x0303, 0x0404, 0x0505, 0x0606,
                     0x1111, 0x1212, 0x1313, 0x1414, 0x1515, 0x1616,
                     0x2121, 0x2222, 0x2323, 0x2424, 0x2525, 0x2626 };
u16 RxData[Size];

void SPI1_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));

/*******************************************************************************
* Function Name  : SPI_1Lines_HalfDuplex_Init
* Description    : Configuring the SPI for half-duplex communication.
* Input          : None
* Return         : None
*******************************************************************************/
void SPI_1Lines_HalfDuplex_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    SPI_InitTypeDef  SPI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE );

#if (SPI_MODE == HOST_MODE)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;   //推挽复用输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( GPIOA, &GPIO_InitStructure );

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;  //浮空输入
    GPIO_Init( GPIOA, &GPIO_InitStructure );

#elif (SPI_MODE == SLAVE_MODE)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;  //浮空输入
    GPIO_Init( GPIOA, &GPIO_InitStructure );

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;   //推挽复用输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( GPIOA, &GPIO_InitStructure );
#endif


#if (SPI_MODE == HOST_MODE)
    //配置为单工通信(1条时钟线和1条双向数据线),SPI_Direction_1Line_Rx值为0x8000,即配置SPI控制寄存器位15 BIDIMODE位 为1(单线双向模式)、位14 BIDIOE位为0(禁止输出,仅接收)
    SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Rx;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;  //配置为主机模式

#elif (SPI_MODE == SLAVE_MODE)
    //配置为单工通信(1条时钟线和1条双向数据线),SPI_Direction_1Line_Tx值为0xC000,即配置SPI控制寄存器位15 BIDIMODE位 为1(单线双向模式)、位14 BIDIOE位为1(使能输出,仅发送)
    SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;   //配置为从机模式

#endif

    SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;  //设置SPI通讯数据帧大小
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;   //设置时钟极性
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;  //设置时钟相位
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;     //设置NSS引脚(即片选引脚)的使用模式
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;  //设置波特率分频因子
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;  //设置数据传输高位在前
    SPI_InitStructure.SPI_CRCPolynomial = 7;      //设置用于CRC计算的多项式
    SPI_Init( SPI1, &SPI_InitStructure );

    NVIC_InitStructure.NVIC_IRQChannel = SPI1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);


#if (SPI_MODE == SLAVE_MODE)

    SPI_I2S_ITConfig( SPI1, SPI_I2S_IT_TXE , ENABLE ); //使能开启SPI1接收中断

    //从模式双向模式数据发送,软件必须保证在SPI主设备开始数据传输(接收数据)之前在发送寄存器中写入要发送的数据,即先开启从机SPI再开启主机SPI
    SPI_Cmd( SPI1, ENABLE );

#endif


}

/*******************************************************************************
* Function Name  : SPI1_IRQHandler
* Description    : This function handles SPI1 exception.
* Input          : None
* Return         : None
*******************************************************************************/
void SPI1_IRQHandler(void)
{
#if (SPI_MODE == SLAVE_MODE)

    if( SPI_I2S_GetITStatus( SPI1, SPI_I2S_IT_TXE ) != RESET )            //发送缓冲区非空
    {
        SPI_I2S_SendData( SPI1, TxData[Txval++] );  //发送数据
        if( Txval == 18 )
        {
            SPI_I2S_ITConfig( SPI1, SPI_I2S_IT_TXE , DISABLE ); //关闭发送中断
        }
    }

#elif (SPI_MODE == HOST_MODE)

    if( Rxval<18 )
    {
        if( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_RXNE ) != RESET )   //接收缓冲区非空
        {
                RxData[Rxval++] = SPI_I2S_ReceiveData( SPI1 ); //接收数据

        }
        //主模式下单向只接收模式,需等待倒数第二个RXNE=1且在关闭SPI之前等待一个SPI时钟周期,此处利用延时函数实现
        if( Rxval == 16 )
        {
            Delay_Us(2);
        }

        //在进入停机模式或关闭该模块时钟之前等待最后一个RXNE=1。
        if( Rxval == 17 )
        {
            SPI_Cmd( SPI1, DISABLE );  //关闭SPI1
        }
    }

#endif
}

spi.c文件主要包括两个函数:SPI_1Lines_HalfDuplex_Init函数和SPI1_IRQHandler函数。SPI_1Lines_HalfDuplex_Init函数主要进行SPI1单工通信模式1条时钟线和1条双向数据线配置下的主机接收从机发送配置。首先,由于选择1条时钟线和1条双向数据线配置,在这种配置下,主机需要用到SCK引脚和MOSI引脚,从机需要用到SCK引脚和MISO引脚,此处,由于用到SPI1,对应SCK引脚为PA5,MOSI引脚为PA7,MISO引脚为PA6,因此,在SPI_1Lines_HalfDuplex_Init函数中首先对主机和从机对应GPIO引脚进行初始化配置,此处需要注意,由于是主机接收从机发送,需要将PA7设置为浮空输入模式,PA6设置为复用推挽输出模式。此外,由于主机作为接收,从机作为发送,此处在SPI_1Lines_HalfDuplex_Init函数中需要对SPI进行主机接收和从机发送初始化配置,此配置可根据CH32V103应用手册主模式和从模式配置步骤进行,主要对SPI通信的通信方向、主从模式、数据帧大小、时钟极性、时钟相位、NSS引脚使用方式、波特率等进行配置,可对照手册参考标准库函数ch32v10x_spi.c文件中SPI_Init函数进行配置。

注意:此外,此处需要注意的是,从机在双向模式发送数据时,软件必须保证在SPI主设备开始数据接收之前在发送寄存器写入要发送的数据,即先开启从机SPI功能,再开启主机SPI功能。

SPI1_IRQHandler函数为SPI1中断服务函数,主要进行主机模式下的数据接收和从机模式下的数据发送。

关于主机接收,此处需要注意的是,如果在最后一个数据传输后关闭SPI模块,需进行以下操作进行处理,保证SPI不会开始一次新的传输:
1、等待倒数第二个RXNE=1;
2、在关闭SPI之前等待一个SPI时钟周期,此处可以利用延时函数进行等待;
3、在进入停机模式或关闭该模块时钟之前等待最后一个RXNE=1。
main.c文件
/********************************** (C) COPYRIGHT *******************************
* File Name          : main.c
* Author             : WCH
* Version            : V1.0.0
* Date               : 2020/04/30
* Description        : Main program body.
*******************************************************************************/

#include "debug.h"
#include "spi.h"

/*******************************************************************************
* Function Name  : main
* Description    : Main program.
* Input          : None
* Return         : None
*******************************************************************************/
int main(void)
{
    u8 i;

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
    Delay_Init();
    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n",SystemCoreClock);

    SPI_1Lines_HalfDuplex_Init();

#if (SPI_MODE == SLAVE_MODE)
    printf("SLAVE Mode\r\n");
    Delay_Ms(1000);

#endif

#if (SPI_MODE == HOST_MODE)
    printf("HOST Mode\r\n");
    Delay_Ms(2000);
    SPI_Cmd( SPI1, ENABLE );
    SPI_I2S_ITConfig( SPI1, SPI_I2S_IT_RXNE , ENABLE );

#endif

    while(1)
    {
#if (SPI_MODE == SLAVE_MODE)
        while( Txval<18 );

        /* 等待TXE=1,BSY=0,等待发送完成,关闭SPI1 */
        while( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_TXE ) != RESET )
        {
            if( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_BSY ) == RESET )
            {
                SPI_Cmd( SPI1, DISABLE );  //关闭SPI1
                printf("Tx End\r\n");
                while(1);
            }
        }

#elif (SPI_MODE == HOST_MODE)

        while( Rxval<18 );

        for( i=0; i<18; i++ )
        {
            printf( "Rxdata:%04x\r\n", RxData[i] );
        }

        while(1);

#endif
    }
}

main.c文件主要进行函数初始化以及主机模式下的接收数据打印输出以及从机模式下的数据发送结束提示。

4下载验证
将编译好的程序分别在主机模式和从机模式下下载到两个开发版,并将主机的PA7引脚与从机PA6引脚进行连接,开发板上电后,串口打印如下:
主机打印:
从机打印:

使用特权

评论回复

相关帖子

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

本版积分规则

132

主题

293

帖子

41

粉丝