本帖最后由 RISCVLAR 于 2021-9-29 16:45 编辑
CH32V103应用教程——SPI-单线双工互发互收
本章教程主要进行SPI单工通信方式下1条时钟线和1条双向数据线配置的应用,从机接收主机发送的数据,并将接收的数据返回给主机。
1、SPI单工通信简介 当配置SPI为1条时钟线和1条双向数据线时,设置SPI_CR1寄存器中的BIDIMODE位而启用此模式。在这个模式下,SCK引脚作为时钟,主设备使用MOSI引脚而从设备使用MISO引脚作为数据通信。传输的方向由SPI_CR1寄存器里的BIDIOE控制,当这个位是’1’的时候,数据线是输出,否则是输入。关于其具体应用,可见下述程序。 关于CH32V103 SPI具体信息,可参考CH32V103应用手册。SPI标准库函数在第十五章节已介绍,在此不再赘述。
2、硬件设计 本章教程主要在SPI单工通信模式下选择1条时钟线和1条双向数据线进行主机接收从机发送实验,需用到两个开发板,且由于采用1条时钟线和1条双向数据线配置,因此主设备使用MOSI引脚,从设备使用MISO引脚进行通讯。此处使用外设为SPI1,主设备MOSI对应引脚为PA7引脚,从设备MISO对应引脚为PA6引脚,将主设备PA7引脚与从设备PA6引脚连接起来,此外还需将两个开发板SPI1对应的SCK引脚PA5连接起来。
3、软件设计 本章教程主要实现单工双向通信模式下从机从主机接收数据并将数据返回发送给主机,基本上是相当于将主机发送从机接收和从机发送主机接收两个程序结合起来。具体程序如下: spi.h文件 #ifndef __SPI_H
#define __SPI_H
#include "ch32v10x.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 6
extern u8 TxData[Size];
extern u8 RxData[Size];
void SPI_1Lines_HalfDuplex_Init(void);
#endif
spi.h文件主要进行相关宏定义和函数声明; spi.c文件 #include "spi.h"
/*******************************************************************************
* 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;
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_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
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)
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
#elif (SPI_MODE == SLAVE_MODE)
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Rx;
SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;
#endif
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init( SPI1, &SPI_InitStructure );
SPI_Cmd( SPI1, ENABLE );
}
spi.c文件主要进行主机和从机的初始化配置。 main.c文件 /********************************** (C) COPYRIGHT *******************************
* File Name : main.c
* Author : WCH
* Version : V1.0.0
* Date : 2020/04/30
* Description : Main program body.
*******************************************************************************/
/*
*@Note
单线半双工模式,Master/Slave 模式数据收发:
Master:SPI1_SCK(PA5)、SPI1_MOSI(PA7)。
Slave:SPI1_SCK(PA5)、SPI1_MISO(PA6)。
本例程演示 Master 发,Slave 收。
注:两块板子分别下载 Master 和 Slave 程序,同时上电。
硬件连线:PA5 —— PA5
PA7 —— PA6
*/
#include "debug.h"
#include "spi.h"
/* Global typedef */
/* Global define */
/* Global Variable */
volatile u8 Txval=0, Rxval=0;
u8 TxData[Size] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
u8 RxData[Size];
/*******************************************************************************
* Function Name : main
* Description : Main program.
* Input : None
* Return : None
*******************************************************************************/
int main(void)
{
u8 i=0,j=0;
Delay_Init();
USART_Printf_Init(115200);
printf("SystemClk:%d\r\n",SystemCoreClock);
#if (SPI_MODE == SLAVE_MODE)
printf("SLAVE Mode\r\n");
Delay_Ms(1000);
#endif
SPI_1Lines_HalfDuplex_Init();
#if (SPI_MODE == HOST_MODE)
printf("HOST Mode\r\n");
Delay_Ms(2000);
#endif
while(1)
{
#if (SPI_MODE == HOST_MODE)
////////////////////////////////发送///////////////////////////////
while( i<Size )
{
/* 发送数据 */
if( i<Size )
{
if( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_TXE ) != RESET ) //发送缓冲区为空
{
SPI_I2S_SendData( SPI1, TxData[i] ); //发送数据
i++;
}
}
}
/* 单向只发模式,BIDMODE=0,RXONLY=0,关闭SPI */
while( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_TXE ) != RESET )
{
if(SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_BSY ) == RESET)
{
SPI_Cmd( SPI1, DISABLE );
}
}
//////////////////////////////////接收///////////////////////////////
Delay_Ms(5000);
SPI_BiDirectionalLineConfig(SPI1, SPI_Direction_Rx);
SPI_Cmd( SPI1, ENABLE );
while(( j<Size ))
{
/* 接收数据 */
if( j<Size )
{
if( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_RXNE ) != RESET ) //接收缓冲区非空
{
RxData[j] = SPI_I2S_ReceiveData( SPI1 ); //接收数据
j++;
}
/* 单向只接收模式,关闭SPI的软件流程,P470 */
if( j == 4 )
{
Delay_Us(2);
}
if( j == 5 )
{
SPI_Cmd( SPI1, DISABLE ); //关闭SPI1
}
}
}
for( j=0; j<Size; j++ )
{
printf( "Rxdata:%02x\r\n", RxData[j] );
}
#elif (SPI_MODE == SLAVE_MODE)
////////////////////////////////接收///////////////////////////////
while(( i<Size ))
{
/* 接收数据 */
if( i<Size )
{
if( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_RXNE ) != RESET ) //接收缓冲区非空
{
RxData[i] = SPI_I2S_ReceiveData( SPI1 ); //接收数据
i++;
}
}
}
for( i=0; i<Size; i++ )
{
printf( "Rxdata:%02x\r\n", RxData[i] );
}
SPI_Cmd( SPI1, DISABLE ); //关闭SPI1
//////////////////////////////////发送///////////////////////////////
printf( "切换传输方向\r\n" );
SPI_BiDirectionalLineConfig(SPI1, SPI_Direction_Tx);
SPI_Cmd( SPI1, ENABLE );
printf( "切换传输方向完成\r\n" );
while( j<Size )
{
/* 发送数据 */
if( j<Size )
{
if( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_TXE ) != RESET ) //发送缓冲区为空
{
SPI_I2S_SendData( SPI1, RxData[j] ); //发送数据
j++;
}
}
}
/* 等待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
}
}
for( j=0; j<Size; j++ )
{
printf( "TxData:%02x\r\n", RxData[j] );
}
while(1);
#endif
}
}
main.c文件主要进行函数初始化以及数据收发。关于主机接收,此处需要注意的是,如果在最后一个数据传输后关闭SPI模块,需进行以下操作进行处理,保证SPI不会开始一次新的传输: 1、等待倒数第二个RXNE=1; 2、在关闭SPI之前等待一个SPI时钟周期,此处可以利用延时函数进行等待; 3、在进入停机模式或关闭该模块时钟之前等待最后一个RXNE=1。 此外,当主机和从机进行接收方向的切换时,需要调用SPI_BiDirectionalLineConfig函数。
4、下载验证 将编译好的程序分别在主机模式和从机模式下下载到两个开发版,并将主机的PA7、PA5引脚与从机PA6、PA5引脚进行连接,开发板上电后,串口打印如下:
|