STM32学习100步之第七十二-七十六步——U盘、TF卡与单片机的通信(利用SPI总线通信)

SPI通信

STM32学习100步之第七十二-七十六步——U盘、TF卡与单片机的通信(利用SPI总线通信)_第1张图片
由图中可以看出,SPI有四条主要的信号线,即MISO(主机输入从机输出)、MOSI(主机输出从机输入),CS是对于从机而言的,当为0时,允许通信,由主机控制是否选通,另外可以使用单片机的IO端口控制多个SPI设备,只需要接至各个SPI设备的片选端即可。SCK是主机发出的时钟,接从机的时钟信号,在这种时钟信号(上升沿或下降沿)时传输数据,需要注意的是所有的SPI设备都需要共地。

SPI的编程灵活,可以设置传输的位数(8位或16位)、传输数据的方向(高位、地位均可在前),可实现全双工通信,即在发送数据的同时可以接收数据。

下图是SPI发送数据的一个时序图,主机发送时钟信号接给从机,在上升沿传输数据,即同步通信。
STM32学习100步之第七十二-七十六步——U盘、TF卡与单片机的通信(利用SPI总线通信)_第2张图片

具体的SPI初始化函数如下:

void SPI2_Init(void){ //SPI2初始化
	SPI_InitTypeDef  SPI_InitStructure;
	GPIO_InitTypeDef  GPIO_InitStructure;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);//使能SPI_2时钟

	GPIO_InitStructure.GPIO_Pin = SPI2_MISO;  //SPI2的MISO(PB14)为浮空输入
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(SPI2PORT,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = SPI2_MOSI | SPI2_SCK;	//SPI2的MOSI(PB15)和SCLK(PB13)为复用推免输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_Init(SPI2PORT,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = SPI2_NSS;	 //SPI2的NSS(PB12)为推免输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(SPI2PORT,&GPIO_InitStructure);

	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//双线输入输出全双工模式
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//设置为SPI的主机模式(SCK主动产生时钟)
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//SPI数据大小:发送8位帧数据结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//空闲状态时SCK的状态,High为高电平,Low为低电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//时钟相位,1表示在SCK的奇数沿边采样,2表示偶数沿边采样
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS由软件控件片选
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//时钟的预分频值(0~256)
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //MSB高位在前
	SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC较验和的多项式
	SPI_Init(SPI2,&SPI_InitStructure); //初始化SPI2的配置项
	SPI_Cmd(SPI2,ENABLE); //使能SPI2  
}

这里需要注意的是SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS由软件控件片选,软件片选的意思是,因为只有一个SPI设备与单片机相连,因此在发送数据前,软件自动将SPI从设备的CS端打开,发送结束之后,再将设备的CS端关闭,如果有多个SPI从设备,这时不能用软件控制,必须手动控制,即单片机在发送数据之前,需要通过程序将SPI从设备打开,实现通信。

SPI接收发函数(主要用于发送)

u8 SPI2_SendByte(u8 Byte){ //通过SPI2口发送1个数据,同时接收1个数据
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE) == RESET); //如果发送寄存器数据没有发送完,循环等待
	SPI_I2S_SendData(SPI2,Byte);  //往发送寄存器写入要发送的数据
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE) == RESET); //如果接受寄存器没有收到数据,循环
	return SPI_I2S_ReceiveData(SPI2);
}

这里先检查发送数据寄存器是否处于发送状态,如果处于发送状态,则等待,若没有处于发送状态,则向SPI2发送一个数据,之后检测是否收到到数据,若没有收到发送的数据,则会收到刚刚发送的数据,存入返回值,若用于发送,则不必考虑返回值。若考虑接收函数,则任意先发送一个值,该值再发送过之后,再等待接收的值,因为作为接受,因此发送很快便完成,这时还没有接收完毕,接收到数据之后,将该发送的值(0xFF)自动替换。
具体用法:

* 函  数  名      : xReadCH376Data
* 描      述      : 从CH376读数据.
*******************************************************************************/
u8 xReadCH376Data(void){
	u8 i;
	delay_us(10);
	i = SPI2_SendByte(0xFF);
	return(i);
}

CH376

CH376 是文件管理控制芯片,用于单片机系统读写 U 盘或者 SD 卡中的文件。 CH376 支持 USB 设备方式和 USB 主机方式,并且内置了 USB 通讯协议的基本固件,内置了处理 Mass-Storage 海量存储设备的专用通讯协议的固件,内置了 SD 卡的通讯接口固件,内置了 FAT16 和 FAT32 以及 FAT12 文件系统的管理固件,支持常用的 USB 存储设备(包括 U 盘/USB 硬盘/USB 闪存盘 /USB 读卡器)和 SD 卡(包括标准容量 SD 卡和高容量 HC-SD 卡以及协议兼容的 MMC 卡和 TF 卡)。 CH376 支持三种通讯接口:8 位并口、SPI 接口或者异步串口,单片机/DSP/MCU/MPU 等控制器可 以通过上述任何一种通讯接口控制 CH376 芯片,存取 U 盘或者 SD 卡中的文件或者与计算机通讯。 CH376 的 USB 设备方式与 CH372 芯片完全兼容,CH376 的 USB 主机方式与 CH375 芯片基本兼容。

其是一种SPI设备(可以用这种方式和单片机通信),应用如下:
STM32学习100步之第七十二-七十六步——U盘、TF卡与单片机的通信(利用SPI总线通信)_第3张图片
对于CH371的初始化,利用下面函数:

void CH376_PORT_INIT(void){ //CH376的SPI接口初始化
	GPIO_InitTypeDef  GPIO_InitStructure; //定义GPIO的初始化枚举结构	
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //选择端口号                        
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //选择IO接口工作方式 //上拉电阻       
	GPIO_Init(GPIOA,&GPIO_InitStructure);	

	GPIO_SetBits(CH376_INTPORT,CH376_INT); //中断输入脚拉高电平
	GPIO_SetBits(SPI2PORT,SPI2_NSS); //片选接口接高电平
}

该函数初始化了CH376的中断输出,连接到了单片机的PA8引脚。

直接调用的初始化函数如下:
u8 mInitCH376Host(void){
	u8	res;	
	delay_ms(600);
	CH376_PORT_INIT( );           /* 接口硬件初始化 */
	xWriteCH376Cmd( CMD11_CHECK_EXIST );    /* 测试单片机与CH376之间的通讯接口 */
	xWriteCH376Data( 0x55 );
	res = xReadCH376Data( );
//	printf("res =%02x \n",(unsigned short)res);
	xEndCH376Cmd( );
	if ( res != 0xAA ) return( ERR_USB_UNKNOWN );  /* 通讯接口不正常,可能原因有:接口连接异常,其它设备影响(片选不唯一),串口波特率,一直在复位,晶振不工作 */
	xWriteCH376Cmd( CMD11_SET_USB_MODE ); /* 设备USB工作模式 */
	xWriteCH376Data( 0x06 ); //06H=已启用的主机方式并且自动产生SOF包  ???
	delay_us(20);
	res = xReadCH376Data( );
//	printf("res =%02x \n",(unsigned short)res);
	xEndCH376Cmd( );

	if ( res == CMD_RET_SUCCESS ){  //RES=51  命令操作成功
	    return( USB_INT_SUCCESS ); //USB事务或者传输操作成功 
	}else{
	    return( ERR_USB_UNKNOWN );/* 设置模式错误 */
	}
}

xWriteCH376Cmd( CMD11_CHECK_EXIST )是用于向CH376写入命令的函数,其命令符参数表如下:STM32学习100步之第七十二-七十六步——U盘、TF卡与单片机的通信(利用SPI总线通信)_第4张图片
STM32学习100步之第七十二-七十六步——U盘、TF卡与单片机的通信(利用SPI总线通信)_第5张图片
前面的检测是先检测端口是否正常输出,只有这个函数输出的不CMD_RET_SUCCESS,而是取反的值,其他的命令若设置成功均是返回COM_RET_ABORT。具体使用方法参照CH376的数据手册即可。

文件系统

学习了SPI驱动为了使单片机和CH376通信,学习CH376的驱动程序为了使单片机给CH376发送指令对U盘和SD卡操作。CH376对U盘的操作,可以使单片机直接读取出来,不仅创建文本文件还可以创建图片文件、表格等复杂文件,还有创建文件夹、删除文件夹,修改文件格式等几乎所有的文件操作。
需要熟悉filesys.c和filesys.h文件,熟悉掌握了file.c文件中包含的函数之后,就能熟练的对U盘进行各种操作。
具体的一个实例如下

	while(1){
		while ( CH376DiskConnect( ) != USB_INT_SUCCESS ) delay_ms(100);  // 检查U盘是否连接,等待U盘拔出
		OLED_DISPLAY_8x16_BUFFER(6," U DISK Ready!  "); //显示字符串
		delay_ms(200); //每次操作后必要的延时
		for ( i = 0; i < 100; i ++ ){ 
			delay_ms( 50 );
			s = CH376DiskMount( );  //初始化磁盘并测试磁盘是否就绪.   
			if ( s == USB_INT_SUCCESS ) /* 准备好 */
			break;                                          
			else if ( s == ERR_DISK_DISCON )/* 检测到断开,重新检测并计时 */
			break;  
			if ( CH376GetDiskStatus( ) >= DEF_DISK_MOUNTED && i >= 5 ) /* 有的U盘总是返回未准备好,不过可以忽略,只要其建立连接MOUNTED且尝试5*50mS */
			break; 
		}
		OLED_DISPLAY_8x16_BUFFER(6," U DISK INIT!   "); //显示字符串
		delay_ms(200); //每次操作后必要的延时
      	s=CH376FileCreatePath( "/洋桃.TXT" ); // 新建多级目录下的文件,支持多级目录路径,输入缓冲区必须在RAM中 
		delay_ms(200); //每次操作后必要的延时
		s = sprintf( (char *)buf , "洋桃电子 www.DoYoung.net/YT");
		s=CH376ByteWrite( buf,s, NULL ); // 以字节为单位向当前位置写入数据块 
		delay_ms(200); //每次操作后必要的延时
		s=CH376FileClose( TRUE );   // 关闭文件,对于字节读写建议自动更新文件长度 
		OLED_DISPLAY_8x16_BUFFER(6," U DISK SUCCESS "); //显示字符串
		while ( CH376DiskConnect( ) == USB_INT_SUCCESS ) delay_ms(500);  // 检查U盘是否连接,等待U盘拔出
		OLED_DISPLAY_8x16_BUFFER(6,"                "); //显示字符串
		delay_ms(200); //每次操作后必要的延时
	}
}
这里CH376对文件的操作,都有返回值,操作之后,将返回值传递给变量S,再用变量S有两种取值,要么使ERR_DISK_DISCON,要么是USB_INT_SUCCESS,根据S的两个值,再使用if判断语句,可以对操作成功和操作失败对应的情况,做出不同的判断,另外,每次对U盘操作之后必须延时,等待U盘硬件电路工作稳定之后再进行其他操作。
		else if ( s == ERR_DISK_DISCON )/* 检测到断开,重新检测并计时 */
		break;  
		语句因为前面一直等待U盘插入(while),所一旦U插入,一般都会检测成功,因此该语句的break作用不大,写这条解释是因为如果检测磁盘失败,跳出了for循环,仍然执行后面的结果,在OLED屏上显示不应该显示的结果(磁盘检测失败)

你可能感兴趣的:(STM32学习100步之第七十二-七十六步——U盘、TF卡与单片机的通信(利用SPI总线通信))