本帖最后由 RISCVLAR 于 2021-1-12 10:13 编辑
CH32V103应用教程——SPI-单工通信(1条时钟线和1条双向数据线),主机发送从机接收
本章教程主要在SPI单工通信方式下进行1条时钟线和1条双向数据线配置,并进行主机发送从机接收。
1、SPI简介及相关函数介绍 SPI支持以三线同步串行模式进行数据交互,加上片选线支持硬件切换主从模式,支持以单根数据线通讯。其主要特征如下: - 支持全双工同步串行模式
- 支持单线半双工模式
- 支持主模式和从模式,多从模式
- 支持 8 位或者 16 位数据结构
- 最高时钟频率支持到 Fpclk 的一半
- 数据顺序支持 MSB 或者 LSB 在前
- 支持硬件或者软件控制 NSS 引脚
- 收发支持硬件 CRC 校验
- 收发缓冲器支持 DMA 传输
- 支持修改时钟相位和极性
SPI在单工通信方式下具有两种配置,具体如下:
1、1条时钟线和1条双向数据线 将SPI控制寄存器1 BIDIMODE位置1(选择单线双向模式)则启用此种配置方式。在这种配置下,SCK引脚作为时钟(主机控制输出,从机接收),主机使用MOSI引脚与从机使用MISO引脚连接进行数据通信。数据传输方向由SPI控制寄存器1的BIDIOE位控制,当BIDIOE位置1,输出使能(仅发送);当BIDIOE位置0,输出禁止(仅接收)。
2、1条时钟线和一条单向数据线 将SPI控制寄存器1 BIDIMODE位置0(选择双线线双向模式)则启用此种配置方式。在这种配置下,SPI仅可以作为只发送或者只接收。 只发送:此种模式类似于全双工模式(SPI控制寄存器1 BIDIMODE=0,RXONLY=0),数据在发送引脚(主模式时是MOSI、从模式时是MISO)上传输,而接收引脚(主模式时是MISO、从模式时是MOSI)可以作为通用的I/O使用。此时,软件不必理会接收缓冲器中的数据(如果读出数据寄存器,它不包含任何接收数据)。 只接收:此种模式可通过将SPI控制寄存器1的RXONLY位置1配置为禁止输出(只接收模式),此时,发送引脚(主模式时是MOSI、从模式时是MISO)被释放,可以作为其它功能使用。 在只接收情况下,SPI的配置方式为: 在主模式时:使能SPI后,启动数据通信,当SPI控制寄存器1 SPE位置0后禁用SPI,停止接收。在这种模式下,SPI状态寄存器位7 BSY始终为1,因此不必读取BSY标志。 在从模式时:只要NSS被拉低(或在NSS软件模式时, SPI控制寄存器1 SSI位为‘0’)同时SCK有时钟脉冲,SPI就一直在接收。 关于以上各个位的具体介绍,可参考CH32V103应用手册SPI章节关于SPI1控制寄存器1的详细介绍。
关于CH32V103 SPI通信主模式和从模式具体介绍如下: 主模式 在 SPI 模块工作在主模式时,由 SCK 产生串行时钟。配置成主模式进行以下步骤: 1)配置控制寄存器的 BR[2:0]域来确定时钟; 2)配置 CPOL 和 CPHA 位来确定 SPI 模式; 3)配置 DEF 确定数据字长; 4)配置 LSBFIRST 确实帧格式; 5)配置 NSS 引脚,比如置 SSOE 位让硬件去置 NSS。也可以置 SSM 位并把 SSI 位置高; 6)置 MSTR 位和 SPE 位,需要保证 NSS 此时已经是高。 需要发送数据时只需要向数据寄存器写要发送的数据就行了。SPI 会从发送缓冲区并行地把数据送到移位寄存器,然后按照 LSBFIRST 的设置将数据从移位寄存器发出去,当数据已经到了移位寄存器时,TXE 标志会被置位,如果已经置位了 TXEIE,那么会产生中断。如果 TXE 标志位置位需要向数据寄存器里填数据,维持完整的数据流。 当接收器接收数据时,当数据字的最后一个采样时钟沿到来时,数据从移位寄存器并行地转移到接收缓冲区,RXNE 位被置位,如果之前置位了 RXNEIE 位,还会产生中断。此时应该尽快读取数据寄存器取走数据。
从模式 当 SPI 模块工作在从模式时,SCK 用于接收主机发来的时钟,自身的波特率设置无效。配置成从模式的步骤如下: 1)配置 DEF 位设置数据位长度; 2)配置 CPOL 和 CPHA 位匹配主机模式; 3)配置 LSBFIRST 匹配主机数据帧格式; 4)硬件管理模式下,NSS 管脚需要保持为低电平,如果设置 NSS 为软件管理(SSM 置位),那么请保持 SSI 不被置位; 5)清除 MSTR 位,置 SPE 位,开启 SPI 模式。
在发送时,当 SCK 出现第一个从机接收采样沿时,从机开始发送。发送的过程就是发送缓冲区的数据移到发送移位寄存器,当发送缓冲区的数据移到了移位寄存器之后,会置位 TXE 标志,如果之前置位了 TXEIE 位,那么会产生中断。
在接收时,最后一个时钟采样沿之后,RXNE 位被置位,移位寄存器接收到的字节被转移到接收缓冲区,读数据寄存器的读操作可以获得接收缓冲区里的数据。如果在 RXNE 置位之前 RXNEIE 已经被置位,那么会产生中断。
关于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_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_IN_FLOATING;
GPIO_Init( GPIOA, &GPIO_InitStructure );
#endif
#if (SPI_MODE == HOST_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_Master; //配置为主机模式
#elif (SPI_MODE == SLAVE_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_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_RXNE , ENABLE ); //使能开启SPI1接收中断
#endif
SPI_Cmd( SPI1, ENABLE );
}
/*******************************************************************************
* Function Name : SPI1_IRQHandler
* Description : This function handles SPI1 exception.
* Input : None
* Return : None
*******************************************************************************/
void SPI1_IRQHandler(void)
{
#if (SPI_MODE == HOST_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 == SLAVE_MODE)
if( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_RXNE ) != RESET ) //接收缓冲区非空
{
RxData[Rxval++] = SPI_I2S_ReceiveData( 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函数进行配置。 SPI1_IRQHandler函数为SPI1中断服务函数,主要进行主机模式下的数据发送和从机模式下的数据接收。 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"
/*
*@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
*/
/*******************************************************************************
* 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_I2S_ITConfig( SPI1, SPI_I2S_IT_TXE , ENABLE ); //开启中断
#endif
while(1)
{
#if (SPI_MODE == HOST_MODE)
while( Txval<18 );
while( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_TXE ) != RESET )
{
if(SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_BSY ) == RESET)
{
SPI_Cmd( SPI1, DISABLE );
printf("Tx END!\n");
while(1);
}
}
#elif (SPI_MODE == SLAVE_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引脚进行连接,开发板上电后,串口打印如下: 主机打印: 从机打印:
|