由图中可以看出,SPI有四条主要的信号线,即MISO(主机输入从机输出)、MOSI(主机输出从机输入),CS是对于从机而言的,当为0时,允许通信,由主机控制是否选通,另外可以使用单片机的IO端口控制多个SPI设备,只需要接至各个SPI设备的片选端即可。SCK是主机发出的时钟,接从机的时钟信号,在这种时钟信号(上升沿或下降沿)时传输数据,需要注意的是所有的SPI设备都需要共地。
下图是SPI发送数据的一个时序图,主机发送时钟信号接给从机,在上升沿传输数据,即同步通信。
具体的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 是文件管理控制芯片,用于单片机系统读写 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设备(可以用这种方式和单片机通信),应用如下:
对于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写入命令的函数,其命令符参数表如下:
前面的检测是先检测端口是否正常输出,只有这个函数输出的不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); //每次操作后必要的延时
}
}
else if ( s == ERR_DISK_DISCON )/* 检测到断开,重新检测并计时 */
break;
语句因为前面一直等待U盘插入(while),所一旦U插入,一般都会检测成功,因此该语句的break作用不大,写这条解释是因为如果检测磁盘失败,跳出了for循环,仍然执行后面的结果,在OLED屏上显示不应该显示的结果(磁盘检测失败)