STM32-(18):SPI与数码管(SPI)

上一篇:STM32-(17):SPI与数码管(数码管) 下一篇:STM32-(19):I2C通信(理论基础)

SPI串行接口

SPI是由Motorala公司提出的一种同步串行外围接口。它在速度要求不高、低功耗、需保存少量参数的智能化传感系统中得到了广泛应用。
SPI是一个全双工的同步串行接口。在数据传输过程中,总线上只能是一个主机和一个从机进行通信。

1、MISO(Master In Slave Out)
主机输入、从机输出信号。
2、MOSI(Master Out Slave In)
主机输出、从机输入信号。
3、SCK(Serial Clock)
串行时钟信号。(用来同步使用的)
4、SS(Slave Select)
从机选择信号,低电平有效。

SPI系统连接

SPI总线可在软件的控制下构成各种简单或复杂的系统。
STM32-(18):SPI与数码管(SPI)_第1张图片

SPI通信工作原理

SPI的基本结构相当于两个8位移位寄存器的首位相接,构成16位的环形移位寄存器。从而实现了主机与从机的数据交换。
STM32-(18):SPI与数码管(SPI)_第2张图片

SPI框图

STM32-(18):SPI与数码管(SPI)_第3张图片
分析:
①首先看SCK 管脚,通过波特率发生器产生时钟信号,这个信号可以出去给从机使用,也可以进来给自己驱动 COMMUNICATION CONTROL(通信控制),其中波特率发生器由BR0、BR1、BR2控制,由这三个值来决定波特率的速度,通信控制受MSTR、SSM、SSI控制
,如果通信控制出错,比如控制的CRCERR(CRC校验错误)、MODF(模式)、OVR(数据溢出),就会在SPI_SR的相应位置1。通信控制出来的信号连接着 MASTER CONTROL LOGIC(主控逻辑电路),中控逻辑电路控制着MOSI、MISO这一块。
②数据从MISO进来,进入SHIFT REGISTER(移位寄存器),移位寄存器数据一旦8位接收完整之后,会自动放到 RX BUFFER 中,我们可以通过 RX BUFFER可以读取数据。 当我们要发送内容,就是通过总线(地址总线,数据总线)将数据写入 TX BUFFER 中去,然后送到移位寄存器 ,数据一旦8位接收完整之后,可以通过MOSI将数据发送出去。

SPI通信的几个步骤

1.SPI主从模式

STM32-(18):SPI与数码管(SPI)_第4张图片

设置MSTR(主设备选择)和SPE位(使能位)来选择是否工作在主模式还是从模式下。(这两个位都在SPI_CR1寄存器中都可以设置),作为从机,片选要接地,可以硬件实现也可以软件实现,接电源是作主机

2.时钟信号的相位和极性

SPI接口可由CPOLCPHA设定4种不同传输格式的时序。(CPOL和CPHA在SPI_CR1寄存器中)
CPOL决定时钟脉冲SCK的有效脉冲方式(正脉冲、负脉冲)。CPHA决定数据线MOSI什么时候输出数据或采集数据。
根据CPOLCPHA的组合数目,一共有4种设置情况。

STM32-(18):SPI与数码管(SPI)_第5张图片
分析:
CPOL决定了脉冲的方式,第一行(CPOL=0),是正脉冲,第二行(CPOL=1),是负脉冲(空闲时高电平,来数据下降沿);当CPHA=0,数据是先出来的,即比上方的SCK的电平变化(时钟输出)要快,大概快半拍,我们称之为数据传输相位超前;当CPHA=1,SCK的电平变化之后数据才会出来,相位是同步的,当在SCK的第二个上升沿或者下降沿的时候才开始数据采集

4种时序下的数据传输,其中“第一位数据的输出”和“其他位数据的输出”栏是表示数据在什么时候更新输出。还需注意数据采样是上升沿还是下降沿有效。

数据与时钟的相位关系如下图:
STM32-(18):SPI与数码管(SPI)_第6张图片

3.数据帧的格式

根据SPI_CR1寄存器中的LSBFIRST位,输出数据位时可以MSB在先也可以LSB在先。
根据SPI_CR1寄存器的DFF位,每个数据帧可以是8位或是16位。所选择的数据帧格式对发送和/或接收都有效。

SPI主模式通信

在主模式时,串行时钟在SCK脚产生。
配置步骤:

  1. 通过 SPI_CR1寄存器的 BR[2:0]位定义串行时钟波特率
  2. 选择 CPOL和CPHA 位,定义数据传输和串行时钟间的相位关系
  3. 设置 DFF 位来定义8或16位数据帧格式
  4. 配置 SPI_CR1寄存器的 LSBFIRST 位定义帧格式
  5. 如果 NSS 引脚需要工作在输入模式,硬件模式中在整个数据帧传输期间应把 NSS 脚连接到髙电平:在软件模式中,需设置 SPI_CR1寄存器的 SSM 和 SSI 位 。 如 果 NSS 引脚工作在输出模式.则只需设置 SSOE 位
  6. 必须设置 MSTR 和 SPE 位(只当 NSS 脚被连到高电平.这些位才能保持置位)
    在这个配置中, MOSI 脚是数据输出,而 MISO 脚是数据输入。

数据发送过程

1、 当一字节写进发送缓冲器时,发送过程开始。
2、 在发送第一个数据位时,数据字被并行地(通过内部总线–TX BUFFER)传入移位寄存器,而后串行地移出到 MOSI 脚上; MSB 在先还是 LSB 在先,取决于 SPI_CR1寄存器中的 LSBFIRST 位。数据从发送缓冲器传输到移位寄存器 TXE 标志将被置位,如果设置 SPI_CR1寄存器中的 TXEIE 位,将产生中断。
3、 在试图写发送缓冲器之前,需确认 TXE 标志应该是1

数据接收过程

当数据传输完成时:
1、 移位寄存器里的数据传送到接收缓冲器(8位–>RX BUFFER),并且 RXNE 标志被置位(硬件接受满自动置位1)。如果 SPI_CR2寄存器中的 RXEIE 位被设置,则产生中断。
2、 读 SPI_ DR 寄存器时, SPI 设备返回接收到的数据字。读 SPI_DR 寄存器将清除RXNE 位(也是硬件自动清零)。

数码管显示 123.4

main.c


/* Includes ------------------------------------------------------------------*/
#include "stm32f10x_lib.h"	  //包含了所有的头文件 它是唯一一个用户需要包括在自己应用中的文件,起到应用和库之间界面的作用。
#include "../Module_Function/Module.h"
#include 

TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
TIM_OCInitTypeDef  TIM_OCInitStructure;
ErrorStatus  HSEStartUpStatus;

void Delay_Ms(u16 time);
void RCC_Configuration(void);
void GPIO_Configuration(void);

/* Private functions ---------------------------------------------------------*/ 
/*******************************************************************************
* Function Name  : main
* Description    : Main program.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
int main(void)
{
//	u16 i=0,j;
	#ifdef DEBUG
  	debug();
	#endif

  	RCC_Configuration();//使能外设时钟
	SEG_Init();

	//========实现数码管显示==========
  	while (1)
  	{
		/*for(i=0;i<9999;i++)
		{
			for(j=0;j<500;j++)
				SEG_Display(i,0);
		}	*/	  
		SEG_Display(1234,3);
  	}
}

/*******************************************************************************
* Function Name  : Delay_Ms
* Description    : delay 1 ms.
* Input          : time (ms)
* Output         : None
* Return         : None
*******************************************************************************/
void Delay_Ms(u16 time)  //延时函数
{ 
	u16 i,j;
	for(i=0;i0;j--);
}

/*******************************************************************************
* Function Name  : RCC_Configuration
* Description    : Configures the different system clocks.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void RCC_Configuration(void) 
{
	//==========================使用外部RC晶振========================================
  	RCC_DeInit();				//初始化为缺省状态
  	RCC_HSEConfig(RCC_HSE_ON);  //高速时钟使能
  	while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET);  //等待高速时钟使能就绪

    FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);	//Enable Prefetch Buffer 
    FLASH_SetLatency(FLASH_Latency_2);	  					// Flash 2 wait state 
    RCC_HCLKConfig(RCC_SYSCLK_Div1); 						// HCLK = SYSCLK 
    RCC_PCLK2Config(RCC_HCLK_Div1);							// PCLK2 = HCLK 
    RCC_PCLK1Config(RCC_HCLK_Div2);	  						// PCLK1 = HCLK/2 
    RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);	// PLLCLK = 8MHz * 9 = 72 MHz  
    RCC_PLLCmd(ENABLE);	  									// Enable PLL 
    while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);		// Wait till PLL is ready 

    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);	  			// Select PLL as system clock source 
    while(RCC_GetSYSCLKSource() != 0x08);					// Wait till PLL is used as system clock source 
}

Seg_Module.c

/****************************************************************************
* 版权:	源享教育(www.yxarm.net)
* 文件:	Seg_Module.c
* 版本:	1.0
* 说明:	MP3播放器当前播放歌曲序号,播放第1首时4位数码管显示0001,依次排列
* 作者:	刘斌
* 时间:	2011.6.21
* 说明:	数码管段显由HC595控制,位显由IO口SEG_A1/SEG_A2/SEG_A3/SEG_A4控制。
*			HC595使用SPI通信方式,Cortex M3的SPI使用步骤如下:
*				1、使能APB2外设SPI1时钟:RCC_APB2PeriphClockCmd();
*				2、将外设SPI寄存器重设为缺省值:SPI_I2S_DeInit();
*				3、初始化外设SPI寄存器:SPI_Init();
*				4、使能APB2外设SPI:SPI_Cmd();
*				5、调用SPI数据发送函数:SPI_I2S_SendData();
------------------------------修改记录--------------------------------------
* 修改功能:
* 修改时间:
* 修改作者:
* 遗留问题:
****************************************************************************/
#include "stm32f10x_lib.h"	  //包含了所有的头文件 它是唯一一个用户需要包括在自己应用中的文件,起到应用和库之间界面的作用。

#define	HC595_nCS	 	GPIO_Pin_0			//HC595_nCS = PA0
#define	HC595_RCK 		GPIO_Pin_1			//HC595_RCK = PA1

#define	SEG_A1	 		GPIO_Pin_8			//SEG_A1 = PC8
#define	SEG_A2 			GPIO_Pin_15			//SEG_A2 = PB15
#define	SEG_A3	 		GPIO_Pin_9			//SEG_A3 = PC9
#define	SEG_A4 			GPIO_Pin_8			//SEG_A4 = PE8

u8 const NumberTube_TAB[10]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};//数码管0~9
/*******************************************************************************
* Function Name  : SEG_Init
* Description    : SEG数码管引脚,SPI1引脚初始化
* Input          : None
* Return         : None
*******************************************************************************/
void	SEG_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;	// 声明一个IO口结构体变量
	SPI_InitTypeDef SPI1_InitStructure;	//声明一个SPI结构体变量

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);	// 使能APB2外设GPIOA时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);	// 使能APB2外设GPIOB时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC , ENABLE);	// 使能APB2外设GPIOC时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE , ENABLE);	// 使能APB2外设GPIOE时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 , ENABLE);	// 使能APB2外设SPI1时钟
	
    //==========PA口IO结构体初始化============
  	GPIO_InitStructure.GPIO_Pin	= HC595_nCS|HC595_RCK;	//选择PA.0,PA.1
  	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//管脚频率为50MHZ
  	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	//模式为推挽输出
  	GPIO_Init(GPIOA, &GPIO_InitStructure);   			//初始化GPIOA寄存器
	//==========PB口IO结构体初始化============
  	GPIO_InitStructure.GPIO_Pin	= SEG_A2;	//选择PB.15
  	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//管脚频率为50MHZ
  	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	//模式为推挽输出
  	GPIO_Init(GPIOB, &GPIO_InitStructure);   			//初始化GPIOB寄存器
	//==========PC口IO结构体初始化============
  	GPIO_InitStructure.GPIO_Pin	= SEG_A1|SEG_A3;		//选择PC.8,PC.9
  	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//管脚频率为50MHZ
  	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	//模式为推挽输出
  	GPIO_Init(GPIOC, &GPIO_InitStructure);   			//初始化GPIOC寄存器
	//==========PE口IO结构体初始化============
  	GPIO_InitStructure.GPIO_Pin	= SEG_A4;				//选择PE.8
  	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//管脚频率为50MHZ
  	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	//模式为推挽输出
  	GPIO_Init(GPIOE, &GPIO_InitStructure);   			//初始化GPIOE寄存器

	//==========SPI1复用功能初始化============
	GPIO_InitStructure.GPIO_Pin	= GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;	//选择PA.4,PA.5, PA.6,PA.7
  	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//管脚频率为50MHZ
  	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//模式为复用推挽输出 (SPI1)
  	GPIO_Init(GPIOA, &GPIO_InitStructure);   			//初始化GPIOA寄存器

	//==========设置SPI1工作模式==============
	SPI1_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;	//SPI设置为双线双向全双工
	SPI1_InitStructure.SPI_Mode = SPI_Mode_Master;					//设置为主SPI
	SPI1_InitStructure.SPI_DataSize = SPI_DataSize_8b;				//SPI发送接收8位帧结构
	SPI1_InitStructure.SPI_CPOL = SPI_CPOL_High;					//CPOL = 1
	SPI1_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;					//CPHA = 0
	SPI1_InitStructure.SPI_NSS = SPI_NSS_Hard;						//NSS由外部管脚管理
	SPI1_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;//分频值为64
	SPI1_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;					//数据传输LSB(低位)开始
	SPI1_InitStructure.SPI_CRCPolynomial = 7;						
	SPI_I2S_DeInit(SPI1);	//将外设SPI1寄存器重设为缺省值 ;
	SPI_Init(SPI1, &SPI1_InitStructure);//初始化外设SPI1寄存器
	
	//==========使能SPI1========================
	SPI_Cmd(SPI1, ENABLE);//使能SPI1外设
	GPIO_ResetBits(GPIOA, HC595_nCS);
}										
/*******************************************************************************
* Function Name  : SEG_BitSelect
* Description    : 选择数码管的位选
* Input          : data        选择码	,data的取值为0x01,0x02,0x04,0x08
* Return         : None
*******************************************************************************/
void SEG_BitSelect(u8 data)
{
	if((data & 0x01) != 0)	GPIO_ResetBits(GPIOC, SEG_A1);	// 控制smgA1 = PC8
	else		GPIO_SetBits(GPIOC, SEG_A1);
	if((data & 0x02) != 0)	GPIO_ResetBits(GPIOB, SEG_A2);	// 控制smgA2 = PB15
	else		GPIO_SetBits(GPIOB, SEG_A2);
	if((data & 0x04) != 0)	GPIO_ResetBits(GPIOC, SEG_A3);	// 控制smgA3 = PC9
	else		GPIO_SetBits(GPIOC, SEG_A3);
	if((data & 0x08) != 0)	GPIO_ResetBits(GPIOE, SEG_A4);	// 控制smgA4 = PE8
	else		GPIO_SetBits(GPIOE, SEG_A4);
}	
/*******************************************************************************
* Function Name  : SEG_Display
* Description    : 数码管显示4位数据
* Input          : data		需要显示的数据
*			radix_point	小数点的位置,取值范围为4、3、2
* Return         : None
*******************************************************************************/
void	SEG_Display(u16 data,u8 radix_point)
{
	u16 j,one,ten,hundred,thousand;		//个,十,百,千,的变量声明
	
	thousand = data / 1000;					//计算千位
	if(thousand != 0)   data -= thousand*1000;
	hundred = data / 100;					//计算百位
	if(hundred != 0) data -= hundred*100; 	
	ten = data / 10;					//计算十位
	if(ten != 0) data -= ten*10; 		
	one = data % 10;					//计算个位
	//-------------------显示千位数据----------------
	GPIO_ResetBits(GPIOA, HC595_RCK);
 	if(radix_point==4)		SPI_I2S_SendData(SPI1, NumberTube_TAB[thousand] & 0x7f);
 	else			SPI_I2S_SendData(SPI1, NumberTube_TAB[thousand] );
	GPIO_SetBits(GPIOA, HC595_RCK);
	SEG_BitSelect(0x08);	//打开数码管位选端
	for(j=0;j<500;j++);	//小段延时
	SEG_BitSelect(0x00);	//关闭显示
	//-------------------显示百位数据----------------
	GPIO_ResetBits(GPIOA, HC595_RCK);
	if(radix_point==3)		SPI_I2S_SendData(SPI1, NumberTube_TAB[hundred] & 0x7f);
	else			SPI_I2S_SendData(SPI1, NumberTube_TAB[hundred] );
	GPIO_SetBits(GPIOA, HC595_RCK);
	SEG_BitSelect(0x01);		
	for(j=0;j<500;j++);	
	SEG_BitSelect(0x00);	//关闭显示
	//-------------------显示十位数据----------------
	GPIO_ResetBits(GPIOA, HC595_RCK);
	if(radix_point==2)		SPI_I2S_SendData(SPI1, NumberTube_TAB[ten] & 0x7f);
	else			SPI_I2S_SendData(SPI1, NumberTube_TAB[ten] );
	GPIO_SetBits(GPIOA, HC595_RCK);
	SEG_BitSelect(0x02);
	for(j=0;j<500;j++);	
	SEG_BitSelect(0x00);	//关闭显示	
	//-------------------显示个位数据----------------
	GPIO_ResetBits(GPIOA, HC595_RCK);
 	SPI_I2S_SendData(SPI1, NumberTube_TAB[one] );
	GPIO_SetBits(GPIOA, HC595_RCK);
	SEG_BitSelect(0x04);
	for(j=0;j<500;j++);
	SEG_BitSelect(0x00);	//关闭显示
}	
/*******************************************************************************
* Function Name  : SEG_POWEROFF
* Description    : SEG数码管关闭
* Input          : None
* Return         : None
*******************************************************************************/
void	SEG_POWEROFF(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 , DISABLE);	// 关闭APB2外设SPI1时钟
	GPIO_SetBits(GPIOC, SEG_A1);
	GPIO_SetBits(GPIOB, SEG_A2);
	GPIO_SetBits(GPIOC, SEG_A3);
	GPIO_SetBits(GPIOE, SEG_A4);
}

Led_Module.c

/****************************************************************************
* 版权:	源享教育(www.yxarm.net)
* 文件:	Led_Module.c
* 版本:	1.0
* 说明:	MP3播放器指示灯,显示音乐播放频率以及音调
* 作者:	刘斌
* 时间:	2011.6.21
* 说明:	LED流水灯由LS164芯片控制,LS164输出引脚1~8分别对应LED1~LED8。
*			LS164使用IO口模拟通信方式,Cortex M3的IO口使用步骤如下:
*				1、使能APB2外设GPIOx时钟:RCC_APB2PeriphClockCmd();
*				2、初始化GPIOx寄存器:GPIO_Init();
*				3、GPIOx清零:GPIO_ResetBits();
*				4、GPIOx置位:GPIO_SetBits();
------------------------------修改记录--------------------------------------
* 修改功能:
* 修改时间:
* 修改作者:
* 遗留问题:
****************************************************************************/
#include "stm32f10x_lib.h"	  //包含了所有的头文件 它是唯一一个用户需要包括在自己应用中的文件,起到应用和库之间界面的作用。

#define	LS164_DATA	 	GPIO_Pin_10			//LS164_DATA = PE10
#define	LS164_CLK		GPIO_Pin_5			//LS164_CLK  = PB5
#define	LS164_CLR 		GPIO_Pin_11			//LS164_CLR  = PE11

/*******************************************************************************
* Function Name  : LED_Init
* Description    : LED引脚初始化
* Input          : None
* Return         : None
*******************************************************************************/
void	LED_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;	// 声明一个IO口结构体变量

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE , ENABLE);	// 使能APB2外设GPIOE时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);	// 使能APB2外设GPIOB时钟
    //==========PE口IO结构体初始化============
  	GPIO_InitStructure.GPIO_Pin	= LS164_DATA|LS164_CLR;	//选择PE.10,PE.11
  	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//管脚频率为50MHZ
  	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	//模式为推挽输出
  	GPIO_Init(GPIOE, &GPIO_InitStructure);   			//初始化GPIOE寄存器
	//==========PB口IO结构体初始化============
  	GPIO_InitStructure.GPIO_Pin	= LS164_CLK;	//选择PB.5
  	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//管脚频率为50MHZ
  	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	//模式为推挽输出
  	GPIO_Init(GPIOB, &GPIO_InitStructure);   			//初始化GPIOB寄存器

}
/*******************************************************************************
* Function Name  : LED_Display
* Description    : 向74LS164发送一个8位数据。
* Input          : data	需要发送的数据
* Return         : None
*******************************************************************************/
void	LED_Display(u8 data)
{
	u8	j;					//定义一个8位无符号整型变量j

	GPIO_ResetBits(GPIOE, LS164_CLR);      //74LS164输出清零
	GPIO_SetBits(GPIOE, LS164_CLR);      		
	//----------------模拟时钟信号,循环8次完成数据传送---------------
	for(j=0; j<8; j++)
	{
		GPIO_ResetBits(GPIOB, LS164_CLK);	//向74LS164发送一个低电平时钟信号 
		if ( (data & 0x01) != 0 )			//低位先传送。判断最低位是否为1
			GPIO_SetBits(GPIOE, LS164_DATA);				
		else 
			GPIO_ResetBits(GPIOE, LS164_DATA);	
		data >>=1;							//data右移一位
		GPIO_SetBits(GPIOB, LS164_CLK);		//向74LS164发送一个高电平时钟信号
	}
	GPIO_ResetBits(GPIOB, LS164_CLK);
}

上一篇:STM32-(17):SPI与数码管(数码管) 下一篇:STM32-(19):I2C通信(理论基础)

你可能感兴趣的:(STM32-(18):SPI与数码管(SPI))