RS 485 原理与驱动程序

文件完全复制 20 的;然后新增 rs485.c,rs485.h

由于RS485和RS232不一样,不能自己短接,需要连接外部的RS485接口,连接需要注意A连接A,B连接B

  • RS485 的接收程序是用 usart 的接收程序,接收方法与 RS232一样

rs485.h

# ifndef __RS485_H
# define __RS485_H	 
# include "sys.h"

# define RS485PORT	GPIOA	//定义IO接口
# define RS485_RE	GPIO_Pin_8	//定义IO接口


void RS485_Init(void);//初始化
void RS485_printf (char *fmt, ...); //RS485发送
		 				    
# endif

rs485.c

# include "sys.h"
# include "usart.h"
# include "rs485.h"

void RS485_Init(void){ //RS485接口初始化
	GPIO_InitTypeDef  GPIO_InitStructure; 	
    GPIO_InitStructure.GPIO_Pin = RS485_RE; //选择端口号(0~15或all)                        
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式       
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)    
	GPIO_Init(RS485PORT, &GPIO_InitStructure);
	GPIO_ResetBits(RS485PORT,RS485_RE); //RE端控制接收/发送状态,RE为1时发送,为0时接收。(初始化为接收状态)
	
}
 
/*
RS485总线通信,使用USART3,这是RS485专用的printf函数
调用方法:RS485_printf("123"); //向USART3发送字符123
*/
void RS485_printf (char *fmt, ...){ 
	char buffer[USART3_REC_LEN+1];  // 数据长度
	u8 i = 0;
	va_list arg_ptr;
	GPIO_SetBits(RS485PORT,RS485_RE); //为高电平(发送)//RS485收发选择线	
	va_start(arg_ptr, fmt);  
	vsnprintf(buffer, USART3_REC_LEN+1, fmt, arg_ptr);
	while ((i < USART3_REC_LEN) && (i < strlen(buffer))){
        USART_SendData(USART3, (u8) buffer[i++]);
        while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); 
	}
	va_end(arg_ptr);
	GPIO_ResetBits(RS485PORT,RS485_RE); //为低电平(接收)//RS485收发选择线	
}

main.c

/*********************************************************************************************
程序名:	RS485通信测试程序
硬件支持:	STM32F103C8   外部晶振8MHz RCC函数设置主频72MHz   							
说明:
 # 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
 # 可根据自己的需要增加或删减。

*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "touch_key.h"
# include "relay.h"
# include "oled0561.h"

# include "usart.h"
# include "rs485.h"

int main (void){//主程序
	u8 a;
	delay_ms(100); //上电时等待其他器件就绪
	RCC_Configuration(); //系统时钟初始化 
	TOUCH_KEY_Init();//触摸按键初始化
	RELAY_Init();//继电器初始化

	I2C_Configuration();//I2C初始化
	OLED0561_Init(); //OLED初始化
	OLED_DISPLAY_8x16_BUFFER(0,"   YoungTalk "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(2,"  RS485 TEST "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(6,"TX:    RX:   "); //显示字符串

	USART3_Init(115200);//串口3初始化并启动
	RS485_Init();//RS485总线初始化,需要跟在USART3初始化下方

	while(1){
		if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A)){RS485_printf("%c",'A');OLED_DISPLAY_8x16(6,4*8,'A');} //向RS232串口发送字符并在OLED上显示		
		else if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B)){RS485_printf("%c",'B');OLED_DISPLAY_8x16(6,4*8,'B');} //向RS232串口发送字符并在OLED上显示		
		else if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C)){RS485_printf("%c",'C');OLED_DISPLAY_8x16(6,4*8,'C');} //向RS232串口发送字符并在OLED上显示
		else if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_D)){RS485_printf("%c",'D');OLED_DISPLAY_8x16(6,4*8,'D');} //向RS232串口发送字符并在OLED上显示

		//查询方式接收
		if(USART_GetFlagStatus(USART3,USART_FLAG_RXNE) != RESET){  //查询串口待处理标志位
			a =USART_ReceiveData(USART3);//读取接收到的数据
			OLED_DISPLAY_8x16(6,11*8,a);//在OLED上显示
		}
	}
}

CAN总线原理与驱动

本节用到的固件库函数

  • CAN_Init(手册 6.2.2)//CAN总线初始化
  • CAN_FilterInit(手册 6.2.3)//根据CAN_FilterInitStruct中指定的参数初始化外设CAN的寄存器
  • CAN_ITConfig(手册 6.2.5)//使能或者失能指定的CAN中断
  • CAN_TransmitStatus(手册 6.2.7)//检查消息传输的状态
  • CAN_Transmit(手册 6.2.6)//开始一个消息的传输
  • CAN_MessagePending(手册 6.2.10)//返回挂号的信息数量
  • CAN_Receive(手册 6.2.11)//接收一个消息

文件完全复制 20 的;只需添加 can.c,can.h 即可(需要在 Lib文件添加 stm32f10x_can.c

TJA1050 是控制器区域网络(CAN)协议控制器和物理总线之间的接口。该器件为总线提供差分发射能力并为CAN控制器提供差分接收能力。

TJA1050是PCA82C250和PCA82C251之后的第三代Philips高速CAN收发器。最重要的区别是

  1. 由于CANH和CANL输出信号的最佳匹配,电磁辐射变得更低 改善了节点未通电时的性能
  2. 无待机模式
  3. 这使TJA1050非常适合用在部分供电网络中处于节电模式的节点

在传统的8051单片机,内部没有集成CAN总线控制器,使用外部要连接SJA1000外部控制器STM32内部集成了控制器,外部只需添加TJA1050

​ CAN接口兼容规范2.0A和2.0B(主动),位速率高达1兆位/秒。它可以接收和发送11位标识符的标准帧,也可以接收和发送29位标识符的扩展帧。具有3个发送邮箱和2个接收FIFO,3级14个可调节的滤波器。

  • 有1个CAN总线
  • 位速度最高1M位/S
  • 11位标识符
  • 29位扩展帧3个发送邮箱
  • 2个FIFO
  • 3级14个滤波器

CAN用于汽车、工业的智能设备通信
CAN的特点是:通信速度快、距离远、稳定、自动查错

can.h

# ifndef __CAN_H
# define __CAN_H	 
# include "sys.h"


# define CAN_INT_ENABLE	0	//1 开接收中断,0 关接收中断

//设置模式和波特率
//波特率=(pclk1/((1+8+7)*9)) = 36Mhz/16/9 =0.25= 250Kbits设定了一个时间单位的长度9
# define tsjw	CAN_SJW_1tq	//设置项目(1~4)
# define tbs1	CAN_BS1_8tq	//设置项目(1~16)
# define tbs2	CAN_BS2_7tq	//设置项目(1~8)
# define brp		9	//设置项目



u8 CAN1_Configuration(void);//初始化
u8 CAN_Send_Msg(u8* msg,u8 len);//发送数据
u8 CAN_Receive_Msg(u8 *buf);//接收数据
		 				    
# endif
  • 初始化

  • CAN总线通信发送配置流程
  1. 写入标识符 TxMessage.StdId = 0x12;
  2. 写入标识符类型(标准帧/扩展帧) TxMessage.IDE = CAN_ID_STD;
  3. 写入帧类型(远程帧/数据帧) TxMessage.RTR = CAN_RTR_DATA;
  4. 写入数据长度 TxMessage.DLC = len;
  5. 写入数据 for(i = 0; i < len; i++) TxMessage.Data[i] = msg[i];
  6. 发送数据 mbox = CAN_Transmit(CAN1, &TxMessage);
  • StdId 用来设定标准标识符。它的取值范围为0到0x7FF
  • ExtId 用来设定扩展标识符。它的取值范围为0到0x1FFFFFFF

can.c

# include "can.h"



u8 CAN1_Configuration(void)  //CAN初始化(返回0表示设置成功,返回其他表示失败)
{
    GPIO_InitTypeDef        GPIO_InitStructure;
    CAN_InitTypeDef         CAN_InitStructure;
    CAN_FilterInitTypeDef   CAN_FilterInitStructure;

# if CAN_INT_ENABLE
    NVIC_InitTypeDef        NVIC_InitStructure;
# endif
    
	//1. CAN GPIO初始化
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能PORTA时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);    //使能CAN1时钟
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;//TX
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽
    GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化IO
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;//RX
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
    GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化IO
    
    //2. CAN总线初始化配置
    CAN_InitStructure.CAN_TTCM = DISABLE;       //没有使能时间触发通信模式
    CAN_InitStructure.CAN_ABOM = DISABLE;       //没有使能自动离线管理
    CAN_InitStructure.CAN_AWUM = DISABLE;       //没有使能自动唤醒模式;通过软件唤醒(清除CAN->MCR的SLEEP位)
    CAN_InitStructure.CAN_NART = ENABLE;        //禁止报文自动传送
    CAN_InitStructure.CAN_RFLM = DISABLE;       //报文不锁定,新的覆盖旧的
    CAN_InitStructure.CAN_TXFP = DISABLE;       //优先级由报文标识符决定
    CAN_InitStructure.CAN_Mode = CAN_Mode_Normal; //模式设置:CAN_Mode_Normal 普通模式,CAN_Mode_LoopBack 回环模式;
    //设置波特率
    CAN_InitStructure.CAN_SJW = tsjw;           //重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位  CAN_SJW_1tq    CAN_SJW_2tq CAN_SJW_3tq CAN_SJW_4tq
    CAN_InitStructure.CAN_BS1 = tbs1;           //时间段1:Tbs1=tbs1+1个时间单位CAN_BS1_1tq ~ CAN_BS1_16tq
    CAN_InitStructure.CAN_BS2 = tbs2;           //时间段2:Tbs2=tbs2+1个时间单位CAN_BS2_1tq ~ CAN_BS2_8tq
    CAN_InitStructure.CAN_Prescaler = brp;      //时间单位长度:分频系数(Fdiv)为brp+1
    CAN_Init(CAN1, &CAN_InitStructure);         //初始化CAN1
    //设置过滤器
    CAN_FilterInitStructure.CAN_FilterNumber = 0; //过滤器0
    CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask; //屏蔽位模式
    CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit; //32位宽
    CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;  //32位ID
    CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
    CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000; //32位MASK
    CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
    CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0; //过滤器0关联到FIFO0
    CAN_FilterInitStructure.CAN_FilterActivation = ENABLE; //激活过滤器0
    CAN_FilterInit(&CAN_FilterInitStructure);           //滤波器初始化
    
	//3. 以下是用于CAN总线接收 中断优先级配置
# if CAN_INT_ENABLE 	
    CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);            //FIFO0消息挂号中断允许
    NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;//USB低优先级或者CAN接收0中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;     // 主优先级为1
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;            // 次优先级为0
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
# endif
    return 0;
}

//CAN发送一组数据(固定格式:ID为0X12,标准帧,数据帧)
//msg:数据指针,最大为8个字节,len:数据长度(最大为8)
//返回值:0,成功; 其他,失败;
u8 CAN_Send_Msg(u8 *msg, u8 len)
{
    u8 mbox;
    u16 i = 0;
    CanTxMsg TxMessage;
    TxMessage.StdId = 0x12;         // 标准标识符
    TxMessage.ExtId = 0x00;         // 设置扩展标识符
    TxMessage.IDE = CAN_Id_STD; // 标准帧
    TxMessage.RTR = CAN_RTR_DATA;   // 数据帧
    TxMessage.DLC = len;            // 要发送的数据长度
    for(i = 0; i < len; i++)
        TxMessage.Data[i] = msg[i];     //写入数据
    mbox = CAN_Transmit(CAN1, &TxMessage);//发送数据
    i = 0;
    while((CAN_TransmitStatus(CAN1, mbox) == CAN_TxStatus_Failed) && (i < 0XFFF))i++; //等待发送结束
    if(i >= 0XFFF)return 1; //超时
    return 0;
}

//can口接收数据查询
//buf:数据缓存区;
//返回值:0,无数据被收到,其他,接收的数据长度;
u8 CAN_Receive_Msg(u8 *buf)
{
    u32 i;
    CanRxMsg RxMessage;
    if(CAN_MessagePending(CAN1, CAN_FIFO0) == 0)return 0; //没有接收到数据,直接退出
    CAN_Receive(CAN1, CAN_FIFO0, &RxMessage); //读取数据
    for(i = 0; i < 8; i++) //把8个数据放入参数数组
        buf[i] = RxMessage.Data[i];
    return RxMessage.DLC;  //返回数据数量
}

//CAN的中断接收程序(中断处理程序)
//必须在can.h文件里CAN_INT_ENABLE为1才能使用中断
//数据处理尽量在中断函数内完成,外部处理要在处理前关CAN中断,防止数据覆盖
void USB_LP_CAN1_RX0_IRQHandler(void)
{
    CanRxMsg RxMessage;
    vu8 CAN_ReceiveBuff[8]; //CAN总线中断接受的数据寄存器
    vu8 i = 0;
    vu8 u8_RxLen = 0;
    CAN_ReceiveBuff[0] = 0;	//清空寄存器
    RxMessage.StdId = 0x00;
    RxMessage.ExtId = 0x00;
    RxMessage.IDE = 0;
    RxMessage.RTR = 0;
    RxMessage.DLC = 0;
    RxMessage.FMI = 0;
    for(i = 0; i < 8; i++)
    {
        RxMessage.Data[i] = 0x00;
    }
    CAN_Receive(CAN1, CAN_FIFO0, &RxMessage); //读出FIFO0数据
    u8_RxLen = RxMessage.DLC; //读出数据数量
    if(RxMessage.StdId == 0x12) //判断ID是否一致
    {
        CAN_ReceiveBuff[0] = RxMessage.DLC; //将收到数据数量放到数组0的位置
        for( i = 0; i < u8_RxLen; i++) //将收到的数据存入CAN寄存器
        {
            CAN_ReceiveBuff[i] = RxMessage.Data[i]; //将8位数据存入CAN接收寄存器
        }
    }
}

main.c

/*********************************************************************************************
程序名:	CAN通信测试程序
硬件支持:	STM32F103C8   外部晶振8MHz RCC函数设置主频72MHz   
说明:
 # 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
 # 可根据自己的需要增加或删减。

*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "touch_key.h"
# include "relay.h"
# include "oled0561.h"

# include "can.h"

int main (void){//主程序
	u8 buff[8];
	u8 x;
	delay_ms(100); //上电时等待其他器件就绪
	RCC_Configuration(); //系统时钟初始化 
	TOUCH_KEY_Init();//触摸按键初始化
	RELAY_Init();//继电器初始化

	CAN1_Configuration(); //CAN总线初始化 返回0表示成功

	I2C_Configuration();//I2C初始化
	OLED0561_Init(); //OLED初始化
	OLED_DISPLAY_8x16_BUFFER(0,"   YoungTalk "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(2,"   CAN TEST  "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(6,"TX:    RX:   "); //显示字符串


	while(1){
		if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A)){buff[0]='A';CAN_Send_Msg(buff,1);OLED_DISPLAY_8x16(6,4*8,'A');} //向RS232串口发送字符并在OLED上显示(如果想要发送多个则把1改成你需要的个数)	
		else if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B)){buff[0]='B';CAN_Send_Msg(buff,1);OLED_DISPLAY_8x16(6,4*8,'B');} //向RS232串口发送字符并在OLED上显示		
		else if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C)){buff[0]='C';CAN_Send_Msg(buff,1);OLED_DISPLAY_8x16(6,4*8,'C');} //向RS232串口发送字符并在OLED上显示
		else if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_D)){buff[0]='D';CAN_Send_Msg(buff,1);OLED_DISPLAY_8x16(6,4*8,'D');} //向RS232串口发送字符并在OLED上显示

		//CAN查寻方式的接收处理
		x = CAN_Receive_Msg(buff); //检查是否收到数据
		if(x){ //判断接收数据的数量,不为0表示有收到数据
			OLED_DISPLAY_8x16(6,11*8,buff[0]);//在OLED上显示
		}
	}
}

​ CAN是Controller Area Network 的缩写(以下称为CAN) ,是ISO国际标准化的串行通信协议。由德国电气商博世公司在1986年率先提出。此后,CAN通过 ISO11898 及 ISO11519 进行了标准化现在在欧洲已是汽车网络的标准协议。CAN协议经过ISO标准化后有两个标准:ISO11898标准和ISO11519-2标准,其中ISO11898是针对通信速率为125Kbps~1Mbps的高速通信标准,而ISO11519-2是针对通信速率为125Kbps以下的低速通信标准。CAN具有很高的可靠性,广泛应用于:汽车电子、工业自动化、船舶、医疗设备、工业设备等方面。

CAN的优势特点

  • 多主控制总线空闲时,所有单元都可发送消息,而两个以上的单元同时开始发送消息时,根据标识符(ID,非地址)决定优先级。两个以上的单元同时开始发送消息时,对各消息ID的每个位进行逐个仲裁比较。仲裁获胜(优先级最高)的单元可继续发送消息,仲裁失利的单元则立刻停止发送而进行接收工作。

  • 系统柔软性。连接总线的单元没有类似“地址”的信息,因此,在总线上添加单元时,已连接的其他单元的软硬件和应用层都不需要做改变。

  • 速度快,距离远。最高1Mbps(距离<40M),最远可达10KM(速率<5Kbps)。

  • 具有错误检测、错误通知和错误恢复功能。所有单元都可以检测错误(错误检测功能)检测出错误的单元会立即同时通知其他所有单元(错误通知功能),正在发送消息的单元一旦检测出错误,会强制结束当前的发送。强制结束发送的单元会不断反复地重新发送此消息直到成功发送为止(错误恢复功能)。

  • 故障封泳功能。CAN可以判断出错误的类型是总线上暂时的数据错误(如外部噪声等)还是持续的数据错误(如单元内部故障、驱动器故障、断线等)。由此功能,当总线上发生持续数据错误时,可将引起此故障的单元从总线上隔离出去。

  • 连接节点多。CAN总线是可同时连接多个单元的总线。可连接的单元总数理论上是没有限制的。但实际上可连接的单元数受总线上的时间延迟及电气负载的限制。降低通信速度,可连接的单元数增加;提高通信速度,则可连接的单元数减少。

ADC原理与驱动

本节用到的固件库函数

  • RCC_AHBPeriphClockCmd(手册 15.2.21
  • DMA_DeInit(手册 7.2.1
  • DMA_Init(手册 7.2.2
  • DMA_Cmd(手册 7.2.4
  • ADC_Init(手册 4.2.2
  • ADC_RegularChannelConfig(手册 4.2.15
  • ADC_DMACmd(手册 4.2.5
  • ADC_ResetCalibration(手册 4.2.7
  • ADC_GetResetCalibrationStatus(手册 4.2.8
  • ADC_StartCalibration(手册 4.2.9
  • ADC_GetCalibrationStatus(手册 4.2.10
  • ADC_SoftwareStartConvCmd(手册 4.2.11

文件与 22 相同,只需新增 adc.c,adc.h 文件(Lib 文件添加 stm32f10x_adc.c

adc.h

ADC1_DR_Address 的地址参考STM32F10XXX数据手册 2.3ADC1 把偏移量和起始地址相加就是 ADC1这个外设的地址

# ifndef __ADC_H
# define __ADC_H 			   
# include "sys.h"


# define ADC1_DR_Address    ((uint32_t)0x4001244C) //ADC1这个外设的地址(查参考手册得出)

# define ADCPORT		GPIOA	//定义ADC接口
# define ADC_CH4		GPIO_Pin_4	//定义ADC接口 电压电位器
# define ADC_CH5		GPIO_Pin_5	//定义ADC接口 光敏电阻
# define ADC_CH6		GPIO_Pin_6	//定义ADC接口 摇杆X轴
# define ADC_CH7		GPIO_Pin_7	//定义ADC接口 摇杆Y轴


void ADC_DMA_Init(void);//DMA初始化设置
void ADC_GPIO_Init(void);//GPIO初始化设置
void ADC_Configuration(void);//ADC初始化设置

# endif
  • DMA_InitStructure.DMA_Priority = DMA_Priority_High;

    设置 DMA通道的优先级,有低,中,高,超高三种模式,这个在前面讲解过,这里我们设置优先级别为高级,如果要开启多个通道,那么这个值就非常有意义,由于我们这里只使用一个通道,优先级就不关心了,随便设置一个即可

  • ADC_DMACmd(ADC1, ENABLE);

    使能adc使用dma传输的功能,不能忘了,否则你前面配置正确,你不打开这个开关也是白忙活一场。

  • ADC转换的结果只能保存在ADC_DR中。因为规则通道转换的值储存在一个仅有的数据寄存器中,所以当转换多个规则通道时需要使用DMA,这可以避免丢失已经存储在ADC_DR寄存器中的数据,也就是说,如不使用DMA,我们采用规则转换,中间有些存储在ADC_DR寄存器中的数据可能被(下一条通道采集的数据)覆盖掉。这就是有人说的“采集到的数据不对应其通道的值”的原因(所以使用ADC时最好配合DMA)

  • 根据 STM32F10xxx数据手册 6.2 时钟树

只要你使用stm32,就必须配置系统时钟,这是一个必备条件!!!

  • DMA_DeInit(DMA1_Channel1);:下图种说明了DMA在同一时间只能被一个外设使用,所以在使用DMA之前,需要将先将其设置为缺省值,避免DMA打架

  • ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;

  • ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 1, ADC_SampleTime_28Cycles5);采样时间越长,结果就越精确(网上是这么说但是时间长了就没意义了)

adc.c

# include "adc.h"

vu16 ADC_DMA_IN5; //ADC数值存放的变量

void ADC_DMA_Init(void){ //DMA初始化设置
	DMA_InitTypeDef DMA_InitStructure;//定义DMA初始化结构体
	DMA_DeInit(DMA1_Channel1);//复位DMA通道1(将 DMA 的通道 x 寄存器重设为缺省值)
	DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; //定义 DMA通道外设基地址=ADC1_DR_Address
	DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_DMA_IN5; //定义DMA通道ADC数据存储器(其他函数可直接读此变量即是ADC值)
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//指定外设为源地址
	DMA_InitStructure.DMA_BufferSize = 1;//定义DMA缓冲区大小(根据ADC采集通道数量修改)当前只需读光敏电阻一个数据量
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//当前外设寄存器地址不变
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;//当前存储器地址:Disable不变,Enable递增(用于多通道采集)
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//定义外设数据宽度16位
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //定义存储器数据宽度16位
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//DMA通道操作模式位环形缓冲模式
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;//DMA通道优先级高
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//禁止DMA通道存储器到存储器传输
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);//初始化DMA通道1
	DMA_Cmd(DMA1_Channel1, ENABLE); //使能DMA通道1
}
void ADC_GPIO_Init(void){ //GPIO初始化设置
	GPIO_InitTypeDef  GPIO_InitStructure; 	
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE);       
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//使能DMA时钟(用于ADC的数据传送)
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);//使能ADC1时钟
    GPIO_InitStructure.GPIO_Pin = ADC_CH5; //选择端口                        
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //选择IO接口工作方式       
	GPIO_Init(ADCPORT, &GPIO_InitStructure);			
}
void ADC_Configuration(void){ //初始化设置
	ADC_InitTypeDef ADC_InitStructure;//定义ADC初始化结构体变量
	ADC_GPIO_Init();//GPIO初始化设置
	ADC_DMA_Init();//DMA初始化设置
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//ADC1和ADC2工作在独立模式
	ADC_InitStructure.ADC_ScanConvMode = ENABLE; //使能扫描
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//ADC转换工作在连续模式
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//有软件控制转换
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//转换数据右对齐
	ADC_InitStructure.ADC_NbrOfChannel = 1;//顺序进行规则转换的ADC通道的数目(根据ADC采集通道数量修改)
	ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
	//设置指定ADC的规则组通道,设置它们的转化顺序和采样时间
	//ADC1,ADC通道x,规则采样顺序值为y,采样时间为28周期		 
	ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 1, ADC_SampleTime_28Cycles5);//ADC1选择信道x,采样顺序y,采样时间n个周期(规则采样顺序值为1,2,3,4数值越小先被转换)

	ADC_DMACmd(ADC1, ENABLE);// 开启ADC的DMA支持(要实现DMA功能,还需独立配置DMA通道等参数)
	ADC_Cmd(ADC1, ENABLE);//使能ADC1
	ADC_ResetCalibration(ADC1); //重置ADC1校准寄存器
	while(ADC_GetResetCalibrationStatus(ADC1));//等待ADC1校准重置完成
	ADC_StartCalibration(ADC1);//开始ADC1校准
	while(ADC_GetCalibrationStatus(ADC1));//等待ADC1校准完成
	ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能ADC1软件开始转换
}

main.c

/*********************************************************************************************
程序名:	光敏电阻ADC读取程序
硬件支持:	STM32F103C8   外部晶振8MHz RCC函数设置主频72MHz   							
说明:
 # 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
 # 可根据自己的需要增加或删减。

*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "touch_key.h"
# include "relay.h"
# include "oled0561.h"

# include "adc.h"

extern vu16 ADC_DMA_IN5; //声明外部变量

int main (void){//主程序
	delay_ms(500); //上电时等待其他器件就绪
	RCC_Configuration(); //系统时钟初始化 
	TOUCH_KEY_Init();//触摸按键初始化
	RELAY_Init();//继电器初始化

	ADC_Configuration(); //ADC初始化设置

	I2C_Configuration();//I2C初始化
	OLED0561_Init(); //OLED初始化
	OLED_DISPLAY_8x16_BUFFER(0,"   YoungTalk "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(2,"   ADC TEST  "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(6," ADC_IN5:    "); //显示字符串


	while(1){
		//将光敏电阻的ADC数据显示在OLED上
		OLED_DISPLAY_8x16(6,10*8,ADC_DMA_IN5/1000+0x30);//
		OLED_DISPLAY_8x16(6,11*8,ADC_DMA_IN5%1000/100+0x30);//
		OLED_DISPLAY_8x16(6,12*8,ADC_DMA_IN5%100/10+0x30);//
		OLED_DISPLAY_8x16(6,13*8,ADC_DMA_IN5%10+0x30);//
		delay_ms(500); //延时
	}
}

实验现象

光敏电阻和电位器ADC读取程序

文件与 23 相同;只需更改一下 adc.c即可(adc.h不需要更改)

跟23的区别

  • ADC_DMA_IN[2] :用到了数组
  • DMA_InitStructure.DMA_BufferSize = 2; :因为现在用到光敏和电位器
  • DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; :如果这里是disable则数据就不会偏移会叠加
  • GPIO_InitStructure.GPIO_Pin = ADC_CH4 | ADC_CH5; :这里新加了通道4
  • ADC_InitStructure.ADC_NbrOfChannel = 2; :这个是2因为两个通道
  • ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 1, ADC_SampleTime_28Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 2, ADC_SampleTime_28Cycles5); :这个写两条一个是电位器,一个是光敏,这样数据才会写入

adc.c

# include "adc.h"

vu16 ADC_DMA_IN[2]; //ADC数值存放的变量

void ADC_DMA_Init(void){ //DMA初始化设置
	DMA_InitTypeDef DMA_InitStructure;//定义DMA初始化结构体
	DMA_DeInit(DMA1_Channel1);//复位DMA通道1
	DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; //定义 DMA通道外设基地址=ADC1_DR_Address
	DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_DMA_IN; //!!!定义DMA通道ADC数据存储器(其他函数可直接读此变量即是ADC值)
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//指定外设为源地址
	DMA_InitStructure.DMA_BufferSize = 2;//!!!定义DMA缓冲区大小(根据ADC采集通道数量修改)
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//当前外设寄存器地址不变
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//!!! 当前存储器地址:Disable不变,Enable递增(用于多通道采集)
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//定义外设数据宽度16位
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //定义存储器数据宽度16位
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//DMA通道操作模式位环形缓冲模式
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;//DMA通道优先级高
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//禁止DMA通道存储器到存储器传输
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);//初始化DMA通道1
	DMA_Cmd(DMA1_Channel1, ENABLE); //使能DMA通道1
}
void ADC_GPIO_Init(void){ //GPIO初始化设置
	GPIO_InitTypeDef  GPIO_InitStructure; 	
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE);       
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//使能DMA时钟(用于ADC的数据传送)
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);//使能ADC1时钟
    GPIO_InitStructure.GPIO_Pin = ADC_CH4 | ADC_CH5; //!!!选择端口                        
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //选择IO接口工作方式       
	GPIO_Init(ADCPORT, &GPIO_InitStructure);			
}
void ADC_Configuration(void){ //初始化设置
	ADC_InitTypeDef ADC_InitStructure;//定义ADC初始化结构体变量
	ADC_GPIO_Init();//GPIO初始化设置
	ADC_DMA_Init();//DMA初始化设置
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//ADC1和ADC2工作在独立模式
	ADC_InitStructure.ADC_ScanConvMode = ENABLE; //使能扫描
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//ADC转换工作在连续模式
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//有软件控制转换
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//转换数据右对齐
	ADC_InitStructure.ADC_NbrOfChannel = 2;//!!!顺序进行规则转换的ADC通道的数目(根据ADC采集通道数量修改)
	ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
	//设置指定ADC的规则组通道,设置它们的转化顺序和采样时间
	//ADC1,ADC通道x,规则采样顺序值为y,采样时间为28周期		 
	ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 1, ADC_SampleTime_28Cycles5);//!!! ADC1选择信道x,采样顺序y,采样时间n个周期
	ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 2, ADC_SampleTime_28Cycles5);//!!! ADC1选择信道x,采样顺序y,采样时间n个周期

	ADC_DMACmd(ADC1, ENABLE);// 开启ADC的DMA支持(要实现DMA功能,还需独立配置DMA通道等参数)
	ADC_Cmd(ADC1, ENABLE);//使能ADC1
	ADC_ResetCalibration(ADC1); //重置ADC1校准寄存器
	while(ADC_GetResetCalibrationStatus(ADC1));//等待ADC1校准重置完成
	ADC_StartCalibration(ADC1);//开始ADC1校准
	while(ADC_GetCalibrationStatus(ADC1));//等待ADC1校准完成
	ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能ADC1软件开始转换
}

main.c

跟23的区别

  • ADC_DMA_IN[2] :跟23的区别是用到了数组,因为这里是用到了光敏电阻和电位器 所以数据也是两个
/*********************************************************************************************
程序名:	光敏和电位器ADC读取程序
硬件支持:	STM32F103C8   外部晶振8MHz RCC函数设置主频72MHz   						
说明:
 # 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
 # 可根据自己的需要增加或删减。

*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "touch_key.h"
# include "relay.h"
# include "oled0561.h"

# include "adc.h"

extern vu16 ADC_DMA_IN[2]; //声明外部变量

int main (void){//主程序
	delay_ms(500); //上电时等待其他器件就绪
	RCC_Configuration(); //系统时钟初始化 
	TOUCH_KEY_Init();//触摸按键初始化
	RELAY_Init();//继电器初始化

	ADC_Configuration(); //ADC初始化设置

	I2C_Configuration();//I2C初始化
	OLED0561_Init(); //OLED初始化
	OLED_DISPLAY_8x16_BUFFER(0,"   YoungTalk "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(2,"   ADC TEST  "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(4," ADC_IN4:    "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(6," ADC_IN5:    "); //显示字符串


	while(1){
		//将光敏电阻的ADC数据显示在OLED上
		OLED_DISPLAY_8x16(4,10*8,ADC_DMA_IN[0]/1000+0x30);//
		OLED_DISPLAY_8x16(4,11*8,ADC_DMA_IN[0]%1000/100+0x30);//
		OLED_DISPLAY_8x16(4,12*8,ADC_DMA_IN[0]%100/10+0x30);//
		OLED_DISPLAY_8x16(4,13*8,ADC_DMA_IN[0]%10+0x30);//

		OLED_DISPLAY_8x16(6,10*8,ADC_DMA_IN[1]/1000+0x30);//
		OLED_DISPLAY_8x16(6,11*8,ADC_DMA_IN[1]%1000/100+0x30);//
		OLED_DISPLAY_8x16(6,12*8,ADC_DMA_IN[1]%100/10+0x30);//
		OLED_DISPLAY_8x16(6,13*8,ADC_DMA_IN[1]%10+0x30);//

		delay_ms(500); //延时
	}
}

实验现象

模拟摇杆的ADC驱动

文件与 23.1 相同;只需更改一下 adc.c即可(adc.h不需要更改);然后增加 JoyStick.c,JoyStick.h

跟23的区别

  • GPIO_InitStructure.GPIO_Pin =ADC_CH6 | ADC_CH7;把通道4,5改成6,7!!!
  • ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 1, ADC_SampleTime_28Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_7, 2, ADC_SampleTie_28Cycles5); 这个也是把4,5改成6,7!!!

adc.c

# include "adc.h"

vu16 ADC_DMA_IN[2]; //ADC数值存放的变量

void ADC_DMA_Init(void){ //DMA初始化设置
	DMA_InitTypeDef DMA_InitStructure;//定义DMA初始化结构体
	DMA_DeInit(DMA1_Channel1);//复位DMA通道1
	DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; //定义 DMA通道外设基地址=ADC1_DR_Address
	DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_DMA_IN; //!!!定义DMA通道ADC数据存储器(其他函数可直接读此变量即是ADC值)
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//指定外设为源地址
	DMA_InitStructure.DMA_BufferSize = 2;//!!!定义DMA缓冲区大小(根据ADC采集通道数量修改)
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//当前外设寄存器地址不变
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//!!! 当前存储器地址:Disable不变,Enable递增(用于多通道采集)
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//定义外设数据宽度16位
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //定义存储器数据宽度16位
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//DMA通道操作模式位环形缓冲模式
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;//DMA通道优先级高
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//禁止DMA通道存储器到存储器传输
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);//初始化DMA通道1
	DMA_Cmd(DMA1_Channel1, ENABLE); //使能DMA通道1
}
void ADC_GPIO_Init(void){ //GPIO初始化设置
	GPIO_InitTypeDef  GPIO_InitStructure; 	
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE);       
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//使能DMA时钟(用于ADC的数据传送)
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);//使能ADC1时钟
    GPIO_InitStructure.GPIO_Pin = ADC_CH6 | ADC_CH7; //!!!选择端口                        
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //选择IO接口工作方式       
	GPIO_Init(ADCPORT, &GPIO_InitStructure);			
}
void ADC_Configuration(void){ //初始化设置
	ADC_InitTypeDef ADC_InitStructure;//定义ADC初始化结构体变量
	ADC_GPIO_Init();//GPIO初始化设置
	ADC_DMA_Init();//DMA初始化设置
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//ADC1和ADC2工作在独立模式
	ADC_InitStructure.ADC_ScanConvMode = ENABLE; //使能扫描
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//ADC转换工作在连续模式
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//有软件控制转换
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//转换数据右对齐
	ADC_InitStructure.ADC_NbrOfChannel = 2;//!!!顺序进行规则转换的ADC通道的数目(根据ADC采集通道数量修改)
	ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
	//设置指定ADC的规则组通道,设置它们的转化顺序和采样时间
	//ADC1,ADC通道x,规则采样顺序值为y,采样时间为28周期		 
	ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 1, ADC_SampleTime_28Cycles5);//!!! ADC1选择信道x,采样顺序y,采样时间n个周期
	ADC_RegularChannelConfig(ADC1, ADC_Channel_7, 2, ADC_SampleTime_28Cycles5);//!!! ADC1选择信道x,采样顺序y,采样时间n个周期

	ADC_DMACmd(ADC1, ENABLE);// 开启ADC的DMA支持(要实现DMA功能,还需独立配置DMA通道等参数)
	ADC_Cmd(ADC1, ENABLE);//使能ADC1
	ADC_ResetCalibration(ADC1); //重置ADC1校准寄存器
	while(ADC_GetResetCalibrationStatus(ADC1));//等待ADC1校准重置完成
	ADC_StartCalibration(ADC1);//开始ADC1校准
	while(ADC_GetCalibrationStatus(ADC1));//等待ADC1校准完成
	ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能ADC1软件开始转换
}

JoyStick.h

# ifndef __KEY_H
# define __KEY_H	 
# include "sys.h"


# define JoyStickPORT	GPIOB	//定义IO接口组
# define JoyStick_KEY	GPIO_Pin_2	//定义IO接口


void JoyStick_Init(void);//初始化

		 				    
# endif

JoyStick.c

# include "JoyStick.h"

void JoyStick_Init(void){ //微动开关的接口初始化
	GPIO_InitTypeDef  GPIO_InitStructure; //定义GPIO的初始化枚举结构	
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE);       
    GPIO_InitStructure.GPIO_Pin = JoyStick_KEY; //选择端口号(0~15或all)                        
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //选择IO接口工作方式 //上拉电阻       
	GPIO_Init(JoyStickPORT,&GPIO_InitStructure);			
}

main.c

/*********************************************************************************************
程序名:	模拟摇杆ADC读取程序
硬件支持:	STM32F103C8   外部晶振8MHz RCC函数设置主频72MHz   					
说明:
 # 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
 # 可根据自己的需要增加或删减。

*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "touch_key.h"
# include "relay.h"
# include "oled0561.h"

# include "adc.h"
# include "JoyStick.h"

extern vu16 ADC_DMA_IN[2]; //声明外部变量

int main (void){//主程序
	delay_ms(500); //上电时等待其他器件就绪
	RCC_Configuration(); //系统时钟初始化 
	TOUCH_KEY_Init();//触摸按键初始化
	RELAY_Init();//继电器初始化

	ADC_Configuration(); //ADC初始化设置(模拟摇杆的ADC初始化)
	JoyStick_Init(); //模拟摇杆的按键初始化

	I2C_Configuration();//I2C初始化
	OLED0561_Init(); //OLED初始化
	OLED_DISPLAY_8x16_BUFFER(0,"   YoungTalk "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(2,"   ADC TEST  "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(4," ADC_IN6:    "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(6," ADC_IN7:    "); //显示字符串


	while(1){
		//将光敏电阻的ADC数据显示在OLED上
		OLED_DISPLAY_8x16(4,10*8,ADC_DMA_IN[0]/1000+0x30);//
		OLED_DISPLAY_8x16(4,11*8,ADC_DMA_IN[0]%1000/100+0x30);//
		OLED_DISPLAY_8x16(4,12*8,ADC_DMA_IN[0]%100/10+0x30);//
		OLED_DISPLAY_8x16(4,13*8,ADC_DMA_IN[0]%10+0x30);//

		OLED_DISPLAY_8x16(6,10*8,ADC_DMA_IN[1]/1000+0x30);//
		OLED_DISPLAY_8x16(6,11*8,ADC_DMA_IN[1]%1000/100+0x30);//
		OLED_DISPLAY_8x16(6,12*8,ADC_DMA_IN[1]%100/10+0x30);//
		OLED_DISPLAY_8x16(6,13*8,ADC_DMA_IN[1]%10+0x30);//

		if(GPIO_ReadInputDataBit(JoyStickPORT,JoyStick_KEY)==0){
			OLED_DISPLAY_8x16(0,0,'Y');//
		}else{
			OLED_DISPLAY_8x16(0,0,' ');//
		}
		delay_ms(200); //延时
	}
}

实验现象

正常状态下 IN6 和 IN7 都是在2000~2100 之间游动当摇杆往前到底时,IN7的值在4050~4100 之间游动,当摇杆往后到底时,IN7的值在0000~0020 之间游动;当摇杆往左到底时,IN7的值在0000~0020 之间游动,当摇杆往右到底时,IN7的值在4050~4100 之间游动;当摇杆向下按下时,OLED屏幕左上角显示字母 ‘Y’,松开则不显示

MP3播放原理与驱动

文件与 23.2 相同;encoder.h,encoder.c 15 相同;只需更改 usart.c,usart.h;添加 my1690.h,my1690.c

MY1690-16S 语音模块使用

  • 支持 MP3 、WAV 高品质音频格式文件,声音优美。
  • 24 位 DAC 输出,动态范围支持 93dB,信噪比支持 85dB。
  • 完全支持 FAT16、FAT32 文件系统,最大支持 32G TF 卡和 32G 的 U 盘。
  • 支持 UART 异步串口控制:支持播放、暂停、上下曲、音量加减、选曲播放、插播等。
  • ADKEY 功能,通过电阻选择可实现标准 MP3 功能的 5 按键控制和其他功能。
  • 可直接连接耳机,或者外接功放

指令列表

具体的可以查看 MY1690- 16S 语音芯片使用说明书V1.0(中文)

usart.h

# ifndef __USART_H
# define __USART_H
# include <stdarg.h>
# include <stdlib.h>
# include <string.h>
# include "stdio.h"	
# include "sys.h" 


# define USART_n		USART1  //定义使用printf函数的串口,其他串口要使用USART_printf专用函数发送

# define USART1_REC_LEN  			200  	//定义USART1最大接收字节数
# define USART2_REC_LEN  			200  	//定义USART2最大接收字节数
# define USART3_REC_LEN  			200  	//定义USART3最大接收字节数

//不使用某个串口时要禁止此串口,以减少编译量
# define EN_USART1 			1		//使能(1)/禁止(0)串口1
# define EN_USART2 			0		//使能(1)/禁止(0)串口2
# define EN_USART3 			1		//使能(1)/禁止(0)串口3
	  	
extern u8  USART1_RX_BUF[USART1_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 
extern u8  USART2_RX_BUF[USART2_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern u8  USART3_RX_BUF[USART3_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
 
extern u16 USART1_RX_STA;         		//接收状态标记	
extern u16 USART2_RX_STA;         		//接收状态标记	
extern u16 USART3_RX_STA;         		//接收状态标记	

//函数声明
void USART1_Init(u32 bound);//串口1初始化并启动
void USART2_Init(u32 bound);//串口2初始化并启动
void USART3_Init(u32 bound);//串口3初始化并启动
void USART1_printf(char* fmt,...); //串口1的专用printf函数
void USART2_printf(char* fmt,...); //串口2的专用printf函数
void USART3_printf(char* fmt,...); //串口3的专用printf函数

# endif
  • USART_GetITStatus() 和 USART_GetFlagStatus() 的区别

都是访问串口的SR状态寄存器,唯一不同是,USART_GetITStatus()会判断中断是否开启,如果没开启,也会返回false。 ITStatus 该函数不仅会判断标志位是否置1,同时还会判断是否使能了相应的中断。所以在串口中断函数中,如果要获取中断标志位,通常使用该函数。------串口中断函数中使用。 FlagStatus 该函数只判断标志位。在没有使能相应的中断时,通常使用该函数来判断标志位是否置1。------做串口轮询时使用。

  • if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)当接收引脚有数据时,状态寄存器的USART_FLAG_RXNE就会为1,此时USART_GetFlagStatus(USART1,USART_FLAG_RXNE)的返回值就为1(SET),若无数据则为RESET

    本语句一般用于while(SET==USART_GetFlagStatus(USART1, USART_FLAG_RXNE)),或if语句作为检测或判断条件

  • Res =USART_ReceiveData(USART3);//读取接收到的数据:程序也不会随便进入中断服务函数 只有发生中断后(有数据发到单片机)才进入中断服务函数 然后判断对应字符是不是S 然后执行你标记的内容,相当于数据过滤;然后在主程序对过滤后的数据进行对应的操作。

usart.c

# include "sys.h"
# include "usart.h"
	  	 
//使UASRT串口可用printf函数发送
//在usart.h文件里可更换使用printf函数的串口号	  
# if 1
# pragma import(__use_no_semihosting)             
//标准库需要的支持函数                 
struct __FILE {
	int handle; 
}; 
FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
_sys_exit(int x){ 
	x = x; 
} 
//重定义fputc函数 
int fputc(int ch, FILE *f){      
	while((USART_n->SR&0X40)==0);//循环发送,直到发送完毕   
    USART_n->DR = (u8) ch;      
	return ch;
}
# endif 



# if EN_USART3   //如果使能了接收
u8 USART3_RX_BUF[USART3_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15,	接收完成标志
//bit14,	接收到0x0d
//bit13~0,	接收到的有效字节数目
u16 USART3_RX_STA=0;       //接收状态标记	  

/*
USART3专用的printf函数
当同时开启2个以上串口时,printf函数只能用于其中之一,其他串口要自创独立的printf函数
调用方法:USART3_printf("123"); //向USART3发送字符123
*/
void USART3_printf (char *fmt, ...){ 
	char buffer[USART3_REC_LEN+1];  // 数据长度
	u8 i = 0;	
	va_list arg_ptr;
	va_start(arg_ptr, fmt);  
	vsnprintf(buffer, USART3_REC_LEN+1, fmt, arg_ptr);
	while ((i < USART3_REC_LEN) && (i < strlen(buffer))){
        USART_SendData(USART3, (u8) buffer[i++]);
        while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); 
	}
	va_end(arg_ptr);
}

void USART3_Init(u32 BaudRate){ //USART3初始化并启动
   GPIO_InitTypeDef GPIO_InitStructure;
   USART_InitTypeDef USART_InitStructure;
   NVIC_InitTypeDef NVIC_InitStructure; 

   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE); //使能UART3所在GPIOB的时钟
   RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); //使能串口的RCC时钟

   //串口使用的GPIO口配置
   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;//设置USART3的RX接口是PB11
   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//接口模式 浮空输入
   GPIO_Init(GPIOB, &GPIO_InitStructure);

   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//设置USART3的TX接口是PB10
   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//输出速度50MHz
   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//接口模式 复用推挽输出
   GPIO_Init(GPIOB, &GPIO_InitStructure);
   //Usart1 NVIC 配置
   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
    NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器 
   //配置串口
   USART_InitStructure.USART_BaudRate = BaudRate;
   USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
   USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
   USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
   USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
   USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式

   USART_Init(USART3, &USART_InitStructure);//配置串口3
   USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);//使能串口接收中断  
   USART_Cmd(USART3, ENABLE);//使能串口3

}

//串口3中断服务程序(固定的函数名不能修改)
void USART3_IRQHandler(void){ 	
    u8 Res;
    if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET){  //接收中断
        Res =USART_ReceiveData(USART3);//读取接收到的数据
        if(Res=='S'){//判断数据是否是STOP(省略读取S)            
            USART3_RX_STA=1;//如果是STOP则标志位为1      
        }else if(Res=='K'){//判断数据是否是OK(省略读取K)            
            USART3_RX_STA=2;//如果是OK则标志位为2      
        }            
    }
}
# endif	

my1690.h

# ifndef __MY1690_H
# define __MY1690_H	 
# include "sys.h"
# include "usart.h"


void MY1690_Init(void);//初始化

void MY1690_PLAY(void);	//直接输入的指令
void MY1690_LAST(void);
void MY1690_NEXT(void);
void MY1690_PAUSE(void);
void MY1690_VUP(void);
void MY1690_VDOWN(void);
void MY1690_STOP(void);

void MY1690_CMD1(u8 a);	//全部指令输入
void MY1690_CMD2(u8 a,u8 b); 
void MY1690_CMD3(u8 a,u16 b);
		 				    
# endif
  • 长度长度+操作码+参数(有些没有参数,有些有两位参数)+校验码的个数
  • 校验码长度<异或>操作码<异或>参数的值

  • while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET);:检查串口是否发送完成,完成则标志位为 set;没完成则为 Reset,一直反复轮询直到标志位为1

  • c=b/0x100; d=b%0x100; :将16位参数转成2个8位参数(具体看冷知识篇)

my1690.c

# include "MY1690.h"

void MY1690_Init(void){ //初始化
	USART3_Init(9600);//串口3初始化并启动
	MY1690_STOP(); //上电初始化后发送一次指令激活MP3芯片
}
void MY1690_PLAY(void){ //播放
	USART3_printf("\x7e\x03\x11\x12\xef"); //其中 \x 后接十六进制数据
}
void MY1690_PAUSE(void){ //暂停
	USART3_printf("\x7e\x03\x12\x11\xef");
}
void MY1690_LAST(void){ //上一曲
	USART3_printf("\x7e\x03\x14\x17\xef");
}
void MY1690_NEXT(void){ //下一曲
	USART3_printf("\x7e\x03\x13\x10\xef");
}
void MY1690_VUP(void){ //音量加1
	USART3_printf("\x7e\x03\x15\x16\xef");
}
void MY1690_VDOWN(void){ //音量减1
	USART3_printf("\x7e\x03\x16\x15\xef");
}
void MY1690_STOP(void){ //停止
	USART3_printf("\x7e\x03\x1E\x1D\xef");
}

void MY1690_CMD1(u8 a){	//无参数的指令发送  a:操作码
	u8 i;
	i=3^a;//取校验码(异或)校验码=长度异或操作码
	USART_SendData(USART3 , 0x7e);	while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , 0x03);	while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , a);		while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , i);		while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , 0xef);	while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
}
void MY1690_CMD2(u8 a,u8 b){ //有参数的指令发送 a操作码 b参数
	u8 i;
	i=4^a;//取校验码(异或)
	i=i^b;//取校验码(异或)校验码=长度异或操作码异或参数
	USART_SendData(USART3 , 0x7e);	while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , 0x04);	while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , a);		while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , b);		while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , i);		while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , 0xef);	while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
}
void MY1690_CMD3(u8 a,u16 b){ //有参数的指令发送 a操作码 b参数(16位)
	u8 i,c,d;
	c=b/0x100; //将16位参数分成2个8位参数
	d=b%0x100;
	i=5^a;//取校验码(异或)
	i=i^c;//取校验码(异或)
	i=i^d;//取校验码(异或)
	USART_SendData(USART3 , 0x7e);	while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , 0x05);	while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , a);		while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , c);		while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , d);		while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , i);		while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
	USART_SendData(USART3 , 0xef);	while(USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET); //检查发送中断标志位
}

main.c

/*********************************************************************************************
程序名:	MP3播放测试程序
硬件支持:	STM32F103C8   外部晶振8MHz RCC函数设置主频72MHz   							
说明:
 # 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
 # 可根据自己的需要增加或删减。

*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "touch_key.h"
# include "relay.h"
# include "oled0561.h"

# include "encoder.h"
# include "usart.h"
# include "my1690.h"

int main (void){//主程序
	u8 b;
	u8 MP3=0;//默认是处于暂停状态,1代表播放状态
	delay_ms(500); //上电时等待其他器件就绪
	RCC_Configuration(); //系统时钟初始化 
	TOUCH_KEY_Init();//触摸按键初始化
	RELAY_Init();//继电器初始化

	ENCODER_Init(); //旋转编码器初始化
	MY1690_Init(); //MP3芯片初始化

	I2C_Configuration();//I2C初始化
	OLED0561_Init(); //OLED初始化
	OLED_DISPLAY_8x16_BUFFER(0,"   I LOVE YOU "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(3," MP3 PLAY TEST  "); //显示字符串

	while(1){
		if(GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A)==0    //判断4个按键是否按下
			||GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B)==0 
			||GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C)==0 
			||GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_D)==0){
			delay_ms(20); //延时
			if(GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A)==0){	//4个按键:A上一曲,B下一曲,
				MY1690_PREV(); //上一曲
                  MP3=1;
				OLED_DISPLAY_8x16_BUFFER(6,"  -- LAST --    "); //显示字符串
				delay_ms(500); //延时
				OLED_DISPLAY_8x16_BUFFER(6,"  -- PLAY --    "); //显示字符串
			}
			if(GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B)==0){
				MY1690_NEXT(); //下一曲
                  MP3=1;
				OLED_DISPLAY_8x16_BUFFER(6,"  -- NEXT --    "); //显示字符串
				delay_ms(500); //延时
				OLED_DISPLAY_8x16_BUFFER(6,"  -- PLAY --    "); //显示字符串
			}
			if(GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C)==0){
				MY1690_CMD2(0x31,30); //将音量设置为30(最大)
                  OLED_DISPLAY_8x16_BUFFER(6,"  -- Num:003 --    "); //显示字符串
				delay_ms(500); //延时
				OLED_DISPLAY_8x16_BUFFER(6,"  -- PLAY --    "); //显示字符串
			}
			if(GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_D)==0){
				MY1690_CMD3(0x41,0x03); //直接播放第0003曲
				delay_ms(500); //延时
			}
			while(GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A)==0	//等待按键放开
			||GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B)==0 
			||GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C)==0 
			||GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_D)==0);
		}
		b=ENCODER_READ();	//读出旋转编码器按键值	
		if(b==1){MY1690_VUP();} //右转音量加。
		if(b==2){MY1690_VDOWN();}//左转音量减。
		if(b==3){	//按下播放或暂停
			if(MP3==0){	//判断当前是播放还是暂停
				MP3=1;MY1690_PLAY();
				OLED_DISPLAY_8x16_BUFFER(6,"  -- PLAY --    "); //显示字符串
			}else if(MP3==1){
				MP3=0;MY1690_PAUSE();
				OLED_DISPLAY_8x16_BUFFER(6,"  -- PAUSE --   "); //显示字符串
			}
			delay_ms(500); //延时
		}
		//串口接收处理
        if(USART3_RX_STA==1){ //如果标志位是1 表示收到STOP
            MP3=0;
            OLED_DISPLAY_8x16_BUFFER(6,"  -- STOP --    "); //显示字符串
            USART3_RX_STA=0; //将串口数据标志位清0
        }else if(USART3_RX_STA==2){    //如果标志位是1 表示收到OK
            //加入相关的处理程序
            USART3_RX_STA=0; //将串口数据标志位清0
        }
	}
}

实验现象

MP3语音播报程序

文件与 24 相同;rtc.c,rtc.h12 相同

main.c

/*********************************************************************************************
程序名:	MP3语音播报程序
硬件支持:	STM32F103C8   外部晶振8MHz RCC函数设置主频72MHz 
说明:
 # 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
 # 可根据自己的需要增加或删减。

*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "touch_key.h"
# include "relay.h"
# include "oled0561.h"
# include "rtc.h"

# include "encoder.h"
# include "usart.h"
# include "my1690.h"

int main (void) //主程序
{
    delay_ms(500); //上电时等待其他器件就绪
    RCC_Configuration(); //系统时钟初始化
    RTC_Config();//实时时钟初始化
    TOUCH_KEY_Init();//触摸按键初始化
    RELAY_Init();//继电器初始化

    ENCODER_Init(); //旋转编码器初始化
    MY1690_Init(); //MP3芯片初始化

    I2C_Configuration();//I2C初始化
    OLED0561_Init(); //OLED初始化
    OLED_DISPLAY_8x16_BUFFER(0, "   I LOVE YOU "); //显示字符串
    OLED_DISPLAY_8x16_BUFFER(2, " MP3 TIME READ  "); //显示字符串

    while(1)
    {

        if(RTC_Get() == 0)
        {
            OLED_DISPLAY_8x16(4, 8 * 3, rhour / 10 + 0x30); //显示时间
            OLED_DISPLAY_8x16(4, 8 * 4, rhour % 10 + 0x30); //显示时间
            OLED_DISPLAY_8x16(4, 8 * 5, ':'); //
            OLED_DISPLAY_8x16(4, 8 * 6, rmin / 10 + 0x30); //显示时间
            OLED_DISPLAY_8x16(4, 8 * 7, rmin % 10 + 0x30); //显示时间
            OLED_DISPLAY_8x16(4, 8 * 8, ':'); //
            OLED_DISPLAY_8x16(4, 8 * 9, rsec / 10 + 0x30); //显示时间
            OLED_DISPLAY_8x16(4, 8 * 10, rsec % 10 + 0x30); //显示时间
            delay_ms(200); //延时
        }
        if(GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A) == 0 //判断4个按键是否按下
                || GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B) == 0
                || GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C) == 0
                || GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D) == 0)
        {
            delay_ms(20); //延时
            if(GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A) == 0) 	//4个按键:A上一曲,B下一曲,
            {
                OLED_DISPLAY_8x16_BUFFER(6, "  -- PLAY --    "); //显示字符串
                //无语法
                //				MY1690_CMD3(0x41,12); //直接播放
                //				MY1690_CMD3(0x41,rhour/10+1); //直接播放
                //				MY1690_CMD3(0x41,11); //直接播放
                //				MY1690_CMD3(0x41,rhour%10+1); //直接播放
                //				MY1690_CMD3(0x41,13); //直接播放
                //				MY1690_CMD3(0x41,rmin/10+1); //直接播放
                //				MY1690_CMD3(0x41,11); //直接播放
                //				MY1690_CMD3(0x41,rmin%10+1); //直接播放
                //				MY1690_CMD3(0x41,14); //直接播放
                //				MY1690_CMD3(0x41,rsec/10+1); //直接播放
                //				MY1690_CMD3(0x41,11); //直接播放
                //				MY1690_CMD3(0x41,rsec%10+1); //直接播放
                //				MY1690_CMD3(0x41,15); //直接播放

                //有语法
                MY1690_CMD3(0x41, 12); //直接播放
                if(rhour / 10 == 0)
                {
                    //不发音
                }
                else if(rhour / 10 == 1)
                {
                    MY1690_CMD3(0x41, 11); //直接播放
                }
                else
                {
                    MY1690_CMD3(0x41, rhour / 10 + 1); //直接播放
                    MY1690_CMD3(0x41, 11); //直接播放
                }
                if(rhour % 10 != 0 || rhour == 0)
                {
                    MY1690_CMD3(0x41, rhour % 10 + 1); //直接播放
                }
                MY1690_CMD3(0x41, 13); //直接播放
                if(rmin / 10 == 0)
                {
                    MY1690_CMD3(0x41, rmin / 10 + 1); //直接播放
                }
                else if(rmin / 10 == 1)
                {
                    MY1690_CMD3(0x41, 11); //直接播放
                }
                else
                {
                    MY1690_CMD3(0x41, rmin / 10 + 1); //直接播放
                    MY1690_CMD3(0x41, 11); //直接播放
                }
                if(rmin % 10 != 0 || rmin == 0)
                {
                    MY1690_CMD3(0x41, rmin % 10 + 1); //直接播放
                }
                MY1690_CMD3(0x41, 14); //直接播放
                if(rsec / 10 == 0)
                {
                    MY1690_CMD3(0x41, rsec / 10 + 1); //直接播放
                }
                else if(rsec / 10 == 1)
                {
                    MY1690_CMD3(0x41, 11); //直接播放
                }
                else
                {
                    MY1690_CMD3(0x41, rsec / 10 + 1); //直接播放
                    MY1690_CMD3(0x41, 11); //直接播放
                }
                if(rsec % 10 != 0 || rsec == 0)
                {
                    MY1690_CMD3(0x41, rsec % 10 + 1); //直接播放
                }
                MY1690_CMD3(0x41, 15); //直接播放
            }
            if(GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B) == 0)
            {
                delay_ms(20);
                OLED_DISPLAY_8x16_BUFFER(6, "  --  sb  --    "); //显示字符串
                MY1690_CMD3(0x41, 16); //直接播放
            }
            while(GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_A) == 0	//等待按键放开
                    || GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_B) == 0
                    || GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_C) == 0
                    || GPIO_ReadInputDataBit(TOUCH_KEYPORT, TOUCH_KEY_D) == 0);
        }
        //串口接收处理
        if(USART3_RX_STA == 1) //如果标志位是1 表示收到STOP
        {
            OLED_DISPLAY_8x16_BUFFER(6, "  -- STOP --    "); //显示字符串
            USART3_RX_STA = 0; //将串口数据标志位清0
        }
    }
}

实验现象

语音可以通过腾讯云-语音合成然后下载到TF卡即可

SPI总线原理与驱动

了解 SPI(先看这篇再看25.1,25.2)

​ SPI 通信原理比 I ^2^ C 要简单,它主要是主从方式通信,这种模式通常只有一个主机和一个或者多个从机,标准的 SPI 是 4根线,分别是 SSEL (片选,也写作 SCS )、 SCLK (时钟,也写作 SCK )、 MOSI (主机输出从机输入)、MISO(主机输入从机输出)。

  • SSEL:从设备片选使能信号,这是一个可选的引脚。如果从设备是低电平使能的话,当拉低这个引脚后,从设备就会被选中主机和这个被选中的从机进行通信
  • SCLK时钟信号,由主机产生,和 I ^2^ C 通信的 SCL 有点类似。
  • MOSI主机给从机发送指令或者数据的通道
  • MISO主机读取从机的状态或者数据的通道

在某些情况下,我们也可以用 3 根线的 SPI 或者 2 根线的 SPI 进行通信。比如:主机只给从机发送命令,从机不需要回复数据的时候,那么 MISO 就可以不要;而在主机只读取从机的数据,不需要给从机发送指令的时候,那 MOSI 就可以不要当一个主机一个从机的时候,从机的片选有时可以固定为有效电平而一直处于使能状态,那么 SSEL 就可以不要此时如果再加上主机只给从机发送数据,那么 SSEL 和 MISO 都可以不要如果主机只读取从机送来的数据, SSEL 和 MOSI 都可以不要

  • CPOL:就是时钟的极性。通信的整个过程分为空闲时刻和通信时刻,如果 SCLK 在数据发送之前和之后的空闲状态是高电平,那么就是 CPOL=1 ,如果空闲状态 SCLK 是低电平,那么就是 CPOL=0
  • CPHA:就是时钟的相位。数据总是在时钟的边沿附近变化或被采样。而一个时钟周期必定包含了一个上升沿和一个下降沿,这是周期的定义所决定的,只是这两个沿的先后并无规定。又因为数据从产生的时刻到它的稳定是需要一定时间的,那么,如果主机在上升沿输出数据到 MOSI 上,从机就只能在下降沿去采样这个数据了。反之如果一方在下降沿输出数据,那么另一方就必须在上升沿采样这个数据。

配置STM32 的SPI 外设:

需要说明的一点,由于APB1 的最高频率是36Mhz,APB2 的最高频率是72Mhz,SPI 波特率预分频系数为2 时,位于APB2 上的SPI 外设理论上最大速率是36MHz,但是实际上由于STM32F103 的硬件限制,SPI 的最大速率只能达到18Mhz(选型手册也有介绍)。此外,SPI 接口还支持硬件的CRC 校验,保证通信的可靠性

SPI 引脚

在配置STM32 的SPI 模块为主机模式时,MOSI 引脚是数据输出,而MISO 引脚是数据输入。当被配置为从机模式时MOSI 引脚则是数据输入,MISO 引脚是数据输出。因此通信时主从设备的MOSI 两两相连,MISO 两两相连,此外还需要连接SCK 和CS(NSS)引脚

注意:SPI3 模块部分引脚与JTAG 引脚共用,这些引脚不受IO 控制器控制,它们(复位后)被默认保留为JTAG 用途。如果想把引脚配置给SPI3,必须在初始化引脚时关闭JTAG 并切换SWD 接口,代码为:GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);// JTAG-DP 失能+ SW-DP 使能指令

SPI 初始化配置

SPI 配置流程

1、使能SPI 外设时钟及SPI 接口引脚时钟
2、初始化SPI 引脚——初始化NSS、SCK、MISO、MOSI 引脚。
3、初始化SPI 外设——根据需求设置SPI 初始化结构体成员参数
4、使能SPI 外设——SPI 使能库函数为:SPI_Cmd(SPIx, ENABLE); 其中SPIx,x 为1、2、3,用来选择SPI 外设。
5、编写SPI 数据发送和接收函数

发送和接收数据需要注意

SPI 的数据缓冲区叫做数据寄存器(SPI_DR),虽然是一个寄存器,但是实质上包含个缓冲区:发送缓冲和接收缓冲,分别用于进行写操作和读操作;TXE 标志位被置位(Set)仅表示发送缓冲区为空,可以继续向SPI_DR 写入数据,但并不代表数据发送完成,这一点一定要搞清楚向发送缓冲区写入数据会清除TXE 标志位如果TXE=0即发生缓冲区非空时,向SPI_DR 中写入数据会覆盖发送缓冲区中的数据,但不会影响移位寄存器中的数据

RXNE=1 表示接收缓冲区非空,即已经接收到一帧数据。读SPI_DR 寄存器硬件会自动清除RXNE 标志位,并返回接收到的数据。当SPI 接收到一帧数据时,意味着SPI 肯定已经发送完一帧数据,因此判断一帧数据是否发送完成,可以通过检测RXNE 标志位如果设置了SPI_CR1 寄存器中的TXEIE 位或者SPI_CR2 寄存器中的RXNEIE 位,将产生对应的中断

SPI 使用查询方式发送和接收数据时,在发送和接收数据之前需要检测相应标志位,然后再调用库函数发送或者接收数据,这样操作虽然方便,但是由于函数相互调用会占用时间当传输数据量较大时会降低SPI 整体传输效率。为了提高SPI 的整体传输效率,可以采用寄存器方式操作。

SPI操作~检测u盘拔插

本节用到的固件库函数(固件库手册中所示的发送和接
收函数有误,以替换的为准)

  • SPI_Init(手册 17.2.2)//初始化SPI
  • SPI_Cmd(手册 17.2.4)//使能SPI
  • SPI_ReceiveData(手册 17.2.8)替换:SPI_I2S_ReceiveData //返回通过SPIx最近接收的数据
  • SPI_GetFlagStatus(手册 17.2.17)替换:SPI_I2S_GetFlagStatus //检查指定的SPI标志位设置与否
  • SPI_SendData(手册 17.2.7)替换:SPI_I2S_SendData //通过外设SPIx发送一个数据

文件与 24.1 相同;新添加 spi.c,spi.h,ch376.c,ch376.h,filesys.h,filesys.cLib 文件添加 stm32f10x_spi.c)还有添加芯片厂家的文件(ch376inc.h

串行外设接口(SPI)

​ 多达2个SPI接口,在从或主模式下,全双工和半双工的通信速率可达18兆位/秒。3位的预分频器可产生8种主模式频率,可配置成每帧8位或16位。硬件的CRC产生/校验支持基本的SD卡和MMC模式所有的SPI接口都可以使用DMA操作。

SPI串行接口:

​ SPI 同步串行接口信号线包括:SPI片选输入引脚SCS、串行时钟输入引脚SCK、串行数据输入引脚SDI、串行数据输出引脚SDO 以及接口忙状态输出引脚BZ

​ CH376 芯片的SCS 引脚由单片机的SPI 片选输出引脚或者普通输出引脚驱动,SCK 引脚由单片机的SPI 时钟输出引脚SCK驱动,SDI 引脚由单片机的SPI数据输出引脚SDO或MOSI驱动,SDO引脚则连接到单片机的SPI 数据输入引脚SDI 或MISO。对于硬件SPI 接口,建议SPI 设置是CPOL=CPHA=0
或者CPOL=CPHA=1,并且数据位顺序是高位在前MSB first。CH376 的SPI 接口也支持单片机用普通I/O 引脚模拟SPI接口进行通讯。如果不连接INT# 引脚,那么可以通过查询SDO 引脚获知中断,方法是让SDO 引脚独占单片机的某个输入引脚,并通过CMD_SET_SDO_INT 命令设置SDO 引脚在SCS 片选无效时兼做中断请求输出。

CH376 的SPI接口支持SPI模式0和SPI 模式3,CH376总是从SPI时钟SCK的上升沿输入数据,并在允许输出时从SCK 的下降沿输出数据,数据位顺序是高位在前,计满8 位为一个字节。

SPI 的操作步骤是:

  • ① 单片机产生CH376芯片的SPI 片选,低电平有效;
  • ② 单片机按SPI 输出方式发出一个字节的数据,CH376 总是将SPI 片选SCS 有效后收到的首个字节当做命令码,后续字节当做数据;
  • ③ 单片机查询BZ引脚等待CH376的SPI 接口空闲,或者直接延时TSC 时间(约1.5uS);
  • ④ 如果是写操作,单片机向CH376发出一个字节的待写数据,等待SPI接口空闲后,单片机继续发出若干个字节的待写数据,CH376依次接收,直到单片机禁止SPI 片选;
  • ⑤ 如果是读操作,单片机从CH376接收一个字节的数据,等待SPI 接口空闲后,单片机继续从CH376 接收若干个字节的数据,直到单片机禁止SPI 片选;
  • ⑥ 单片机禁止CH376芯片的SPI 片选,以结束当前SPI 操作。

CH376芯片

CH376 是文件管理控制芯片,用于单片机系统读写U 盘或者SD 卡中的文件。CH376 支持三种通讯接口:8 位并口、SPI 接口或者异步串口

spi.h

# ifndef __SPI_H
# define __SPI_H	 
# include "sys.h"

# define SPI2PORT		GPIOB	//定义IO接口
# define SPI2_MOSI		GPIO_Pin_15	//定义IO接口
# define SPI2_MISO		GPIO_Pin_14	//定义IO接口
# define SPI2_SCK		GPIO_Pin_13	//定义IO接口
# define SPI2_NSS		GPIO_Pin_12	//定义IO接口

void SPI2_Init(void);//初始化
u8 SPI2_SendByte(u8 Byte);//数据发送
		 				    
# endif
  • SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; :为什么是8位看上面 SPI串行接口介绍
  • SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; 官方建议是设置成一样CH376中文数据手册 6.3
  • SPI_InitStructure.SPI_CRCPolynomial = 7;表示不使用校验

spi.c

# include "spi.h"


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  
}

//SPI2数据发+收程序(主要用于发送)
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);
}
/*
应用的方法:

SPI_SendByte(a); //发送一个字节数据,a是一个变量或者数值
*/

上面的 SPI2数据发+收程序可以替换成下面是写法(操作寄存器):

u8 SPI2_SendByte(u8 Byte)
{
    while((SPI2->SR & SPI_I2S_FLAG_TXE)==0);
    SPI2->DR=Byte;
    while((SPI2->SR & SPI_I2S_FLAG_RXNE)==0);
    return SPI2->DR;
}

ch376.h

# ifndef __CH376_H
# define __CH376_H	 
# include "sys.h"
# include "spi.h"
# include "delay.h"
# include "ch376inc.h"

# define CH376_INTPORT		GPIOA	//定义IO接口
# define CH376_INT			GPIO_Pin_15	//定义IO接口(接收CH376发生的中断信号)


void CH376_PORT_INIT( void ); /* CH376通讯接口初始化 */
void xEndCH376Cmd( void ); /* 结束SPI命令 */
void xWriteCH376Cmd( u8 mCmd ); /* 向CH376写命令 */
void xWriteCH376Data( u8 mData );/* 向CH376写数据 */
u8 	xReadCH376Data( void );	/* 从CH376读数据 */
u8 	Query376Interrupt( void );/* 查询CH376中断(INT# 引脚为低电平) */
u8 	mInitCH376Host( void ); /* 初始化CH376 */

		 				    
# endif
  • xWriteCH376Cmd( CMD11_CHECK_EXIST ); 这个看手册

  • xWriteCH376Data( 0x06 );看 CH376中文数据手册 5.9

  • 操作成功会有对应返回值

ch376.c

# include "CH376.h"


/*******************************************************************************
* 函  数  名      : CH376_PORT_INIT
* 描      述      : 由于使用软件模拟SPI读写时序,所以进行初始化.
*                   如果是硬件SPI接口,那么可使用mode3(CPOL=1&CPHA=1)或
*                   mode0(CPOL=0&CPHA=0),CH376在时钟上升沿采样输入,下降沿输出,数
*                   据位是高位在前.
*******************************************************************************/
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); //片选接口接高电平
}
/*******************************************************************************
* 函  数  名      : xEndCH376Cmd   结束命令.
*******************************************************************************/ 
void xEndCH376Cmd(void){ //结束命令
	GPIO_SetBits(SPI2PORT,SPI2_NSS); //SPI片选无效,结束CH376命令
}
/*******************************************************************************
SPI输出8个位数据.    * 发送: u8 d:要发送的数据.
*******************************************************************************/
void Spi376OutByte(u8 d){ //SPI发送一个字节数据 
   SPI2_SendByte(d);	 
}
/*******************************************************************************
* 描      述      : SPI接收8个位数据.  u8 d:接收到的数据.
*******************************************************************************/
u8 Spi376InByte(void){	//SPI接收一个字节数据
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE) == RESET); //如果接受寄存器没有收到数据,循环
	return SPI_I2S_ReceiveData(SPI2);
}
/*******************************************************************************
* 描      述      : 向CH376写  命令.
* 输      入      : u8 mCmd:要发送的命令.
*******************************************************************************/
void xWriteCH376Cmd(u8 mCmd){
	GPIO_SetBits(SPI2PORT,SPI2_NSS);   /* 防止之前未通过xEndCH376Cmd禁止SPI片选 */
	delay_us(20);
/* 对于双向I/O引脚模拟SPI接口,那么必须确保已经设置SPI_SCS,SPI_SCK,SPI_SDI为输出
*  方向,SPI_SDO为输入方向 */
	GPIO_ResetBits(SPI2PORT,SPI2_NSS);     /* SPI片选有效 */
	Spi376OutByte( mCmd );  /* 发出命令码 */
	delay_us(1700);   /* 延时1.5mS确保读写周期大于1.5mS,或者用上面一行的状态查询代替 */
}
/*******************************************************************************
* 描      述      : 向CH376写   数据.
* 输      入      : u8 mData:
*                   要发送的数据.
*******************************************************************************/
void xWriteCH376Data(u8 mData){
	Spi376OutByte( mData );
	delay_us(800);  /* 确保读写周期大于0.6mS */
}
/*******************************************************************************
* 函  数  名      : xReadCH376Data
* 描      述      : 从CH376读数据.
*******************************************************************************/
u8 xReadCH376Data(void){
	u8 i;
	delay_us(10);
	i = SPI2_SendByte(0xFF);
	return(i);
}
/*******************************************************************************
* 描      述      : 查询CH376中断(INT# 低电平).
* 返      回      : 0:无中断.       1:有中断.
*******************************************************************************/
u8 Query376Interrupt(void){
	u8 i;
	i = GPIO_ReadInputDataBit(CH376_INTPORT,CH376_INT); 	
	return( i ); 
}			
/*******************************************************************************
* 描      述      : 初始化CH376.
* 返      回      : FALSE:无中断.  TRUE:有中断.
*******************************************************************************/
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);//手册有说至少延时10us,所以这延时20us加上等待
	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 );/* 设置模式错误 */
	}
}

main.c

/*********************************************************************************************
程序名:	U盘插拔测试程序
硬件支持:	STM32F103C8   外部晶振8MHz RCC函数设置主频72MHz   							
说明:
 # 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
 # 可根据自己的需要增加或删减。

*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "touch_key.h"
# include "relay.h"
# include "oled0561.h"

# include "spi.h"
# include "ch376.h"
# include "filesys.h"

int main (void){//主程序
	u8 s;
	delay_ms(500); //上电时等待其他器件就绪
	RCC_Configuration(); //系统时钟初始化 
	TOUCH_KEY_Init();//触摸按键初始化
	RELAY_Init();//继电器初始化

	I2C_Configuration();//I2C初始化
	OLED0561_Init(); //OLED初始化
	OLED_DISPLAY_8x16_BUFFER(0,"   YoungTalk    "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(2,"  U DISK TEST   "); //显示字符串
	//CH376初始化	
	SPI2_Init();//SPI接口初始化
	if(mInitCH376Host()== USB_INT_SUCCESS){//CH376初始化
		OLED_DISPLAY_8x16_BUFFER(4,"   CH376 OK!    "); //显示字符串
	}
	while(1){
		s = CH376DiskConnect();	 //读出U盘的状态 
		if(s == USB_INT_SUCCESS){ //检查U盘是否连接//等待U盘插入
			OLED_DISPLAY_8x16_BUFFER(6," U DISK Ready!  "); //显示字符串(表示u盘已经插入)
		}else{
			OLED_DISPLAY_8x16_BUFFER(6,"                "); //显示字符串(表示U盘等待连接)	
		} 
		delay_ms(500); //刷新的间隔
	}
}

ch376inc.h(这个照抄即可)

# ifndef __CH376INC_H__
# define __CH376INC_H__

# ifdef __cplusplus
extern "C" {
# endif

/* 常用类型和常量定义 */

# ifndef		TRUE
# define		TRUE	1
# define		FALSE	0
# endif
# ifndef		NULL
# define		NULL	0
# endif

# ifndef UINT8
typedef unsigned char                UINT8;
# endif
# ifndef UINT16
typedef unsigned short               UINT16;
# endif
# ifndef UINT32
typedef unsigned long                UINT32;
# endif
# ifndef PUINT8
typedef unsigned char               *PUINT8;
# endif
# ifndef PUINT16
typedef unsigned short              *PUINT16;
# endif
# ifndef PUINT32
typedef unsigned long               *PUINT32;
# endif
# ifndef UINT8V
typedef unsigned char volatile       UINT8V;
# endif
# ifndef PUINT8V
typedef unsigned char volatile      *PUINT8V;
# endif

/* 硬件特性 */

# define		CH376_DAT_BLOCK_LEN		0x40	/* USB单个数据包, 数据块的最大长度, 默认缓冲区的长度 */

/* ********************************************************************************************************************* */
/* 命令代码 */
/* 部分命令兼容CH375芯片, 但是输入数据或者输出数据的可能局部不同) */
/* 一个命令操作顺序包含:
          一个命令码(对于串口方式,命令码之前还需要两个同步码),
          若干个输入数据(可以是0个),
          产生中断通知 或者 若干个输出数据(可以是0个), 二选一, 有中断通知则一定没有输出数据, 有输出数据则一定不产生中断
       仅CMD01_WR_REQ_DATA命令例外, 顺序包含: 一个命令码, 一个输出数据, 若干个输入数据
   命令码起名规则: CMDxy_NAME
       其中的x和y都是数字, x说明最少输入数据个数(字节数), y说明最少输出数据个数(字节数), y如果是H则说明产生中断通知,
       有些命令能够实现0到多个字节的数据块读写, 数据块本身的字节数未包含在上述x或y之内 */
/* 本文件默认会同时提供与CH375芯片命令码兼容的命令码格式(即去掉x和y之后), 如果不需要, 那么可以定义_NO_CH375_COMPATIBLE_禁止 */

/* ********************************************************************************************************************* */
/* 主要命令(手册一), 常用 */

# define	CMD01_GET_IC_VER	0x01			/* 获取芯片及固件版本 */
/* 输出: 版本号( 位7为0, 位6为1, 位5~位0为版本号 ) */
/*           CH376返回版本号的值为041H即版本号为01H */

# define	CMD21_SET_BAUDRATE	0x02			/* 串口方式: 设置串口通讯波特率(上电或者复位后的默认波特率为9600bps,由D4/D5/D6引脚选择) */
/* 输入: 波特率分频系数, 波特率分频常数 */
/* 输出: 操作状态( CMD_RET_SUCCESS或CMD_RET_ABORT, 其它值说明操作未完成 ) */

# define	CMD00_ENTER_SLEEP	0x03			/* 进入睡眠状态 */

# define	CMD00_RESET_ALL		0x05			/* 执行硬件复位 */

# define	CMD11_CHECK_EXIST	0x06			/* 测试通讯接口和工作状态 */
/* 输入: 任意数据 */
/* 输出: 输入数据的按位取反 */

# define	CMD20_CHK_SUSPEND	0x0B			/* 设备方式: 设置检查USB总线挂起状态的方式 */
/* 输入: 数据10H, 检查方式 */
/*           00H=不检查USB挂起, 04H=以50mS为间隔检查USB挂起, 05H=以10mS为间隔检查USB挂起 */

# define	CMD20_SET_SDO_INT	0x0B			/* SPI接口方式: 设置SPI的SDO引脚的中断方式 */
/* 输入: 数据16H, 中断方式 */
/*           10H=禁止SDO引脚用于中断输出,在SCS片选无效时三态输出禁止, 90H=SDO引脚在SCS片选无效时兼做中断请求输出 */

# define	CMD14_GET_FILE_SIZE	0x0C			/* 主机文件模式: 获取当前文件长度 */
/* 输入: 数据68H */
/* 输出: 当前文件长度(总长度32位,低字节在前) */

# define	CMD50_SET_FILE_SIZE	0x0D			/* 主机文件模式: 设置当前文件长度 */
/* 输入: 数据68H, 当前文件长度(总长度32位,低字节在前) */

# define	CMD11_SET_USB_MODE	0x15			/* 设置USB工作模式 */
//00H=未启用的设备方式, 01H=已启用的设备方式并且使用外部固件模式(串口不支持), 
//02H=已启用的设备方式并且使用内置固件模式 03H=SD卡主机模式/未启用的主机模式,用于管理和存取SD卡中的文件 
//04H=未启用的主机方式, 05H=已启用的主机方式, 06H=已启用的主机方式并且自动产生SOF包, 07H=已启用的主机方式并且复位USB总线 */
//输出: 操作状态( CMD_RET_SUCCESS或CMD_RET_ABORT, 其它值说明操作未完成 ) 

# define	CMD01_GET_STATUS	0x22			/* 获取中断状态并取消中断请求 */
/* 输出: 中断状态 */

# define	CMD00_UNLOCK_USB	0x23			/* 设备方式: 释放当前USB缓冲区 */

# define	CMD01_RD_USB_DATA0	0x27			/* 从当前USB中断的端点缓冲区或者主机端点的接收缓冲区读取数据块 */
/* 输出: 长度, 数据流 */

# define	CMD01_RD_USB_DATA	0x28			/* 设备方式: 从当前USB中断的端点缓冲区读取数据块, 并释放缓冲区, 相当于 CMD01_RD_USB_DATA0 + CMD00_UNLOCK_USB */
/* 输出: 长度, 数据流 */

# define	CMD10_WR_USB_DATA7	0x2B			/* 设备方式: 向USB端点2的发送缓冲区写入数据块 */
/* 输入: 长度, 数据流 */

# define	CMD10_WR_HOST_DATA	0x2C			/* 向USB主机端点的发送缓冲区写入数据块 */
/* 输入: 长度, 数据流 */

# define	CMD01_WR_REQ_DATA	0x2D			/* 向内部指定缓冲区写入请求的数据块 */
/* 输出: 长度 */
/* 输入: 数据流 */

# define	CMD20_WR_OFS_DATA	0x2E			/* 向内部缓冲区指定偏移地址写入数据块 */
/* 输入: 偏移, 长度, 数据流 */

# define	CMD10_SET_FILE_NAME	0x2F			/* 主机文件模式: 设置将要操作的文件的文件名 */
/* 输入: 以0结束的字符串(含结束符0在内长度不超过14个字符) */

/* ********************************************************************************************************************* */
/* 主要命令(手册一), 常用, 以下命令总是在操作结束时产生中断通知, 并且总是没有输出数据 */

# define	CMD0H_DISK_CONNECT	0x30			/* 主机文件模式/不支持SD卡: 检查磁盘是否连接 */
/* 输出中断 */

# define	CMD0H_DISK_MOUNT	0x31			/* 主机文件模式: 初始化磁盘并测试磁盘是否就绪 */
/* 输出中断 */

# define	CMD0H_FILE_OPEN		0x32			/* 主机文件模式: 打开文件或者目录(文件夹),或者枚举文件和目录(文件夹) */
/* 输出中断 */

# define	CMD0H_FILE_ENUM_GO	0x33			/* 主机文件模式: 继续枚举文件和目录(文件夹) */
/* 输出中断 */

# define	CMD0H_FILE_CREATE	0x34			/* 主机文件模式: 新建文件,如果文件已经存在那么先删除 */
/* 输出中断 */

# define	CMD0H_FILE_ERASE	0x35			/* 主机文件模式: 删除文件,如果已经打开则直接删除,否则对于文件会先打开再删除,子目录必须先打开 */
/* 输出中断 */

# define	CMD1H_FILE_CLOSE	0x36			/* 主机文件模式: 关闭当前已经打开的文件或者目录(文件夹) */
/* 输入: 是否允许更新文件长度 */
/*          00H=禁止更新长度, 01H=允许更新长度 */
/* 输出中断 */

# define	CMD1H_DIR_INFO_READ	0x37			/* 主机文件模式: 读取文件的目录信息 */
/* 输入: 指定需要读取的目录信息结构在扇区内的索引号 */
/*           索引号范围为00H~0FH, 索引号0FFH则为当前已经打开的文件 */
/* 输出中断 */

# define	CMD0H_DIR_INFO_SAVE	0x38			                                            /* 主机文件模式: 保存文件的目录信息 */
/* 输出中断 */

# define	CMD4H_BYTE_LOCATE	0x39			                                            /* 主机文件模式: 以字节为单位移动当前文件指针 */
/* 输入: 偏移字节数(总长度32位,低字节在前) */
/* 输出中断 */

# define	CMD2H_BYTE_READ		0x3A			                                            /* 主机文件模式: 以字节为单位从当前位置读取数据块 */
/* 输入: 请求读取的字节数(总长度16位,低字节在前) */
/* 输出中断 */

# define	CMD0H_BYTE_RD_GO	0x3B			/* 主机文件模式: 继续字节读 */
/* 输出中断 */

# define	CMD2H_BYTE_WRITE	0x3C			/* 主机文件模式: 以字节为单位向当前位置写入数据块 */
/* 输入: 请求写入的字节数(总长度16位,低字节在前) */
/* 输出中断 */

# define	CMD0H_BYTE_WR_GO	0x3D			/* 主机文件模式: 继续字节写 */
/* 输出中断 */

# define	CMD0H_DISK_CAPACITY	0x3E			/* 主机文件模式: 查询磁盘物理容量 */
/* 输出中断 */

# define	CMD0H_DISK_QUERY	0x3F			/* 主机文件模式: 查询磁盘空间信息 */
/* 输出中断 */

# define	CMD0H_DIR_CREATE	0x40			/* 主机文件模式: 新建目录(文件夹)并打开,如果目录已经存在那么直接打开 */
/* 输出中断 */

# define	CMD4H_SEC_LOCATE	0x4A			/* 主机文件模式: 以扇区为单位移动当前文件指针 */
/* 输入: 偏移扇区数(总长度32位,低字节在前) */
/* 输出中断 */

# define	CMD1H_SEC_READ		0x4B			/* 主机文件模式/不支持SD卡: 以扇区为单位从当前位置读取数据块 */
/* 输入: 请求读取的扇区数 */
/* 输出中断 */

# define	CMD1H_SEC_WRITE		0x4C			/* 主机文件模式/不支持SD卡: 以扇区为单位在当前位置写入数据块 */
/* 输入: 请求写入的扇区数 */
/* 输出中断 */

# define	CMD0H_DISK_BOC_CMD	0x50			/* 主机方式/不支持SD卡: 对USB存储器执行BulkOnly传输协议的命令 */
/* 输出中断 */

# define	CMD5H_DISK_READ		0x54			/* 主机方式/不支持SD卡: 从USB存储器读物理扇区 */
/* 输入: LBA物理扇区地址(总长度32位, 低字节在前), 扇区数(01H~FFH) */
/* 输出中断 */

# define	CMD0H_DISK_RD_GO	0x55			/* 主机方式/不支持SD卡: 继续执行USB存储器的物理扇区读操作 */
/* 输出中断 */

# define	CMD5H_DISK_WRITE	0x56			/* 主机方式/不支持SD卡: 向USB存储器写物理扇区 */
/* 输入: LBA物理扇区地址(总长度32位, 低字节在前), 扇区数(01H~FFH) */
/* 输出中断 */

# define	CMD0H_DISK_WR_GO	0x57			/* 主机方式/不支持SD卡: 继续执行USB存储器的物理扇区写操作 */
/* 输出中断 */

/* ********************************************************************************************************************* */
/* 辅助命令(手册二), 不太常用或者是为了与CH375和CH372兼容 */

# define	CMD10_SET_USB_SPEED	0x04			/* 设置USB总线速度, 在每次CMD11_SET_USB_MODE设置USB工作模式时会自动恢复到12Mbps全速 */
/* 输入: 总线速度代码 */
/*           00H=12Mbps全速FullSpeed(默认值), 01H=1.5Mbps(仅修改频率), 02H=1.5Mbps低速LowSpeed */

# define	CMD11_GET_DEV_RATE	0x0A			/* 主机方式: 获取当前连接的USB设备的数据速率类型 */
/* 输入: 数据07H */
/* 输出: 数据速率类型 */
/*           位4为1则是1.5Mbps低速USB设备, 否则是12Mbps全速USB设备 */

# define	CMD11_GET_TOGGLE	0x0A			/* 获取OUT事务的同步状态 */
/* 输入: 数据1AH */
/* 输出: 同步状态 */
/*           位4为1则OUT事务同步, 否则OUT事务不同步 */

# define	CMD11_READ_VAR8		0x0A			/* 读取指定的8位文件系统变量 */
/* 输入: 变量地址 */
/* 输出: 数据 */

/*# define	CMD11_GET_MAX_LUN	= CMD11_READ_VAR8( VAR_UDISK_LUN )*/	/* 主机方式: 获取USB存储器最大和当前逻辑单元号 */

# define	CMD20_SET_RETRY		0x0B			/* 主机方式: 设置USB事务操作的重试次数 */
/* 输入: 数据25H, 重试次数 */
/*       位7为0则收到NAK时不重试, 位7为1位6为0则收到NAK时无限重试, 位7为1位6为1则收到NAK时最多重试3秒, 位5~位0为超时后的重试次数 */

# define	CMD20_WRITE_VAR8	0x0B			/* 设置指定的8位文件系统变量 */
/* 输入: 变量地址, 数据 */

/*# define	CMD20_SET_DISK_LUN	= CMD20_WRITE_VAR8( VAR_UDISK_LUN )*/	/* 主机方式: 设置USB存储器的当前逻辑单元号 */

# define	CMD14_READ_VAR32	0x0C			/* 读取指定的32位文件系统变量 */
/* 输入: 变量地址 */
/* 输出: 数据(总长度32位,低字节在前) */

# define	CMD50_WRITE_VAR32	0x0D			/* 设置指定的32位文件系统变量 */
/* 输入: 变量地址, 数据(总长度32位,低字节在前) */

# define	CMD01_DELAY_100US	0x0F			/* 延时100uS(串口不支持) */
/* 输出: 延时期间输出0,延时结束输出非0 */

# define	CMD40_SET_USB_ID	0x12			/* 设备方式: 设置USB厂商VID和产品PID */
/* 输入: 厂商ID低字节, 厂商ID高字节, 产品ID低字节, 产品ID高字节 */

# define	CMD10_SET_USB_ADDR	0x13			/* 设置USB地址 */
/* 输入: 地址值 */

# define	CMD01_TEST_CONNECT	0x16			/* 主机方式/不支持SD卡: 检查USB设备连接状态 */
/* 输出: 状态( USB_INT_CONNECT或USB_INT_DISCONNECT或USB_INT_USB_READY, 其它值说明操作未完成 ) */

# define	CMD00_ABORT_NAK		0x17			/* 主机方式: 放弃当前NAK的重试 */

# define	CMD10_SET_ENDP2		0x18			/* 设备方式(串口不支持): 设置USB端点0的接收器 */
/* 输入: 工作方式 */
/*           位7为1则位6为同步触发位, 否则同步触发位不变 */
/*           位3~位0为事务响应方式:  0000-就绪ACK, 1110-正忙NAK, 1111-错误STALL */

# define	CMD10_SET_ENDP3		0x19			/* 设备方式(串口不支持): 设置USB端点0的发送器 */
/* 输入: 工作方式 */
/*           位7为1则位6为同步触发位, 否则同步触发位不变 */
/*           位3~位0为事务响应方式:  0000~1000-就绪ACK, 1110-正忙NAK, 1111-错误STALL */

# define	CMD10_SET_ENDP4		0x1A			/* 设备方式(串口不支持): 设置USB端点1的接收器 */
/* 输入: 工作方式 */
/*           位7为1则位6为同步触发位, 否则同步触发位不变 */
/*           位3~位0为事务响应方式:  0000-就绪ACK, 1110-正忙NAK, 1111-错误STALL */

# define	CMD10_SET_ENDP5		0x1B			/* 设备方式(串口不支持): 设置USB端点1的发送器 */
/* 输入: 工作方式 */
/*           位7为1则位6为同步触发位, 否则同步触发位不变 */
/*           位3~位0为事务响应方式:  0000~1000-就绪ACK, 1110-正忙NAK, 1111-错误STALL */

# define	CMD10_SET_ENDP6		0x1C			/* 设置USB端点2/主机端点的接收器 */
/* 输入: 工作方式 */
/*           位7为1则位6为同步触发位, 否则同步触发位不变 */
/*           位3~位0为事务响应方式:  0000-就绪ACK, 1101-就绪但不返回ACK, 1110-正忙NAK, 1111-错误STALL */

# define	CMD10_SET_ENDP7		0x1D			/* 设置USB端点2/主机端点的发送器 */
/* 输入: 工作方式 */
/*           位7为1则位6为同步触发位, 否则同步触发位不变 */
/*           位3~位0为事务响应方式:  0000-就绪ACK, 1101-就绪但无须应答, 1110-正忙NAK, 1111-错误STALL */

# define	CMD00_DIRTY_BUFFER	0x25			/* 主机文件模式: 清除内部的磁盘和文件缓冲区 */

# define	CMD10_WR_USB_DATA3	0x29			/* 设备方式(串口不支持): 向USB端点0的发送缓冲区写入数据块 */
/* 输入: 长度, 数据流 */

# define	CMD10_WR_USB_DATA5	0x2A			/* 设备方式(串口不支持): 向USB端点1的发送缓冲区写入数据块 */
/* 输入: 长度, 数据流 */

/* ********************************************************************************************************************* */
/* 辅助命令(手册二), 不太常用或者是为了与CH375和CH372兼容, 以下命令总是在操作结束时产生中断通知, 并且总是没有输出数据 */

# define	CMD1H_CLR_STALL		0x41			/* 主机方式: 控制传输-清除端点错误 */
/* 输入: 端点号 */
/* 输出中断 */

# define	CMD1H_SET_ADDRESS	0x45			/* 主机方式: 控制传输-设置USB地址 */
/* 输入: 地址值 */
/* 输出中断 */

# define	CMD1H_GET_DESCR		0x46			/* 主机方式: 控制传输-获取描述符 */
/* 输入: 描述符类型 */
/* 输出中断 */

# define	CMD1H_SET_CONFIG	0x49			/* 主机方式: 控制传输-设置USB配置 */
/* 输入: 配置值 */
/* 输出中断 */

# define	CMD0H_AUTO_SETUP	0x4D			/* 主机方式/不支持SD卡: 自动配置USB设备 */
/* 输出中断 */

# define	CMD2H_ISSUE_TKN_X	0x4E			/* 主机方式: 发出同步令牌, 执行事务, 该命令可代替 CMD10_SET_ENDP6/CMD10_SET_ENDP7 + CMD1H_ISSUE_TOKEN */
/* 输入: 同步标志, 事务属性 */
/*           同步标志的位7为主机端点IN的同步触发位, 位6为主机端点OUT的同步触发位, 位5~位0必须为0 */
/*           事务属性的低4位是令牌, 高4位是端点号 */
/* 输出中断 */

# define	CMD1H_ISSUE_TOKEN	0x4F			/* 主机方式: 发出令牌, 执行事务, 建议用CMD2H_ISSUE_TKN_X命令 */
/* 输入: 事务属性 */
/*           低4位是令牌, 高4位是端点号 */
/* 输出中断 */

# define	CMD0H_DISK_INIT		0x51			/* 主机方式/不支持SD卡: 初始化USB存储器 */
/* 输出中断 */

# define	CMD0H_DISK_RESET	0x52			/* 主机方式/不支持SD卡: 控制传输-复位USB存储器 */
/* 输出中断 */

# define	CMD0H_DISK_SIZE		0x53			/* 主机方式/不支持SD卡: 获取USB存储器的容量 */
/* 输出中断 */

# define	CMD0H_DISK_INQUIRY	0x58			/* 主机方式/不支持SD卡: 查询USB存储器特性 */
/* 输出中断 */

# define	CMD0H_DISK_READY	0x59			/* 主机方式/不支持SD卡: 检查USB存储器就绪 */
/* 输出中断 */

# define	CMD0H_DISK_R_SENSE	0x5A			/* 主机方式/不支持SD卡: 检查USB存储器错误 */
/* 输出中断 */

# define	CMD0H_RD_DISK_SEC	0x5B			/* 主机文件模式: 从磁盘读取一个扇区的数据到内部缓冲区 */
/* 输出中断 */

# define	CMD0H_WR_DISK_SEC	0x5C			/* 主机文件模式: 将内部缓冲区的一个扇区的数据写入磁盘 */
/* 输出中断 */

# define	CMD0H_DISK_MAX_LUN	0x5D			/* 主机方式: 控制传输-获取USB存储器最大逻辑单元号 */
/* 输出中断 */

/* ********************************************************************************************************************* */
/* 以下定义只是为了兼容CH375的INCLUDE文件中的命令名称格式 */

# ifndef	_NO_CH375_COMPATIBLE_
# define	CMD_GET_IC_VER		CMD01_GET_IC_VER
# define	CMD_SET_BAUDRATE	CMD21_SET_BAUDRATE
# define	CMD_ENTER_SLEEP		CMD00_ENTER_SLEEP
# define	CMD_RESET_ALL		CMD00_RESET_ALL
# define	CMD_CHECK_EXIST		CMD11_CHECK_EXIST
# define	CMD_CHK_SUSPEND		CMD20_CHK_SUSPEND
# define	CMD_SET_SDO_INT		CMD20_SET_SDO_INT
# define	CMD_GET_FILE_SIZE	CMD14_GET_FILE_SIZE
# define	CMD_SET_FILE_SIZE	CMD50_SET_FILE_SIZE
# define	CMD_SET_USB_MODE	CMD11_SET_USB_MODE
# define	CMD_GET_STATUS		CMD01_GET_STATUS
# define	CMD_UNLOCK_USB		CMD00_UNLOCK_USB
# define	CMD_RD_USB_DATA0	CMD01_RD_USB_DATA0
# define	CMD_RD_USB_DATA		CMD01_RD_USB_DATA
# define	CMD_WR_USB_DATA7	CMD10_WR_USB_DATA7
# define	CMD_WR_HOST_DATA	CMD10_WR_HOST_DATA
# define	CMD_WR_REQ_DATA		CMD01_WR_REQ_DATA
# define	CMD_WR_OFS_DATA		CMD20_WR_OFS_DATA
# define	CMD_SET_FILE_NAME	CMD10_SET_FILE_NAME
# define	CMD_DISK_CONNECT	CMD0H_DISK_CONNECT
# define	CMD_DISK_MOUNT		CMD0H_DISK_MOUNT
# define	CMD_FILE_OPEN		CMD0H_FILE_OPEN
# define	CMD_FILE_ENUM_GO	CMD0H_FILE_ENUM_GO
# define	CMD_FILE_CREATE		CMD0H_FILE_CREATE
# define	CMD_FILE_ERASE		CMD0H_FILE_ERASE
# define	CMD_FILE_CLOSE		CMD1H_FILE_CLOSE
# define	CMD_DIR_INFO_READ	CMD1H_DIR_INFO_READ
# define	CMD_DIR_INFO_SAVE	CMD0H_DIR_INFO_SAVE
# define	CMD_BYTE_LOCATE		CMD4H_BYTE_LOCATE
# define	CMD_BYTE_READ		CMD2H_BYTE_READ
# define	CMD_BYTE_RD_GO		CMD0H_BYTE_RD_GO
# define	CMD_BYTE_WRITE		CMD2H_BYTE_WRITE
# define	CMD_BYTE_WR_GO		CMD0H_BYTE_WR_GO
# define	CMD_DISK_CAPACITY	CMD0H_DISK_CAPACITY
# define	CMD_DISK_QUERY		CMD0H_DISK_QUERY
# define	CMD_DIR_CREATE		CMD0H_DIR_CREATE
# define	CMD_SEC_LOCATE		CMD4H_SEC_LOCATE
# define	CMD_SEC_READ		CMD1H_SEC_READ
# define	CMD_SEC_WRITE		CMD1H_SEC_WRITE
# define	CMD_DISK_BOC_CMD	CMD0H_DISK_BOC_CMD
# define	CMD_DISK_READ		CMD5H_DISK_READ
# define	CMD_DISK_RD_GO		CMD0H_DISK_RD_GO
# define	CMD_DISK_WRITE		CMD5H_DISK_WRITE
# define	CMD_DISK_WR_GO		CMD0H_DISK_WR_GO
# define	CMD_SET_USB_SPEED	CMD10_SET_USB_SPEED
# define	CMD_GET_DEV_RATE	CMD11_GET_DEV_RATE
# define	CMD_GET_TOGGLE		CMD11_GET_TOGGLE
# define	CMD_READ_VAR8		CMD11_READ_VAR8
# define	CMD_SET_RETRY		CMD20_SET_RETRY
# define	CMD_WRITE_VAR8		CMD20_WRITE_VAR8
# define	CMD_READ_VAR32		CMD14_READ_VAR32
# define	CMD_WRITE_VAR32		CMD50_WRITE_VAR32
# define	CMD_DELAY_100US		CMD01_DELAY_100US
# define	CMD_SET_USB_ID		CMD40_SET_USB_ID
# define	CMD_SET_USB_ADDR	CMD10_SET_USB_ADDR
# define	CMD_TEST_CONNECT	CMD01_TEST_CONNECT
# define	CMD_ABORT_NAK		CMD00_ABORT_NAK
# define	CMD_SET_ENDP2		CMD10_SET_ENDP2
# define	CMD_SET_ENDP3		CMD10_SET_ENDP3
# define	CMD_SET_ENDP4		CMD10_SET_ENDP4
# define	CMD_SET_ENDP5		CMD10_SET_ENDP5
# define	CMD_SET_ENDP6		CMD10_SET_ENDP6
# define	CMD_SET_ENDP7		CMD10_SET_ENDP7
# define	CMD_DIRTY_BUFFER	CMD00_DIRTY_BUFFER
# define	CMD_WR_USB_DATA3	CMD10_WR_USB_DATA3
# define	CMD_WR_USB_DATA5	CMD10_WR_USB_DATA5
# define	CMD_CLR_STALL		CMD1H_CLR_STALL
# define	CMD_SET_ADDRESS		CMD1H_SET_ADDRESS
# define	CMD_GET_DESCR		CMD1H_GET_DESCR
# define	CMD_SET_CONFIG		CMD1H_SET_CONFIG
# define	CMD_AUTO_SETUP		CMD0H_AUTO_SETUP
# define	CMD_ISSUE_TKN_X		CMD2H_ISSUE_TKN_X
# define	CMD_ISSUE_TOKEN		CMD1H_ISSUE_TOKEN
# define	CMD_DISK_INIT		CMD0H_DISK_INIT
# define	CMD_DISK_RESET		CMD0H_DISK_RESET
# define	CMD_DISK_SIZE		CMD0H_DISK_SIZE
# define	CMD_DISK_INQUIRY	CMD0H_DISK_INQUIRY
# define	CMD_DISK_READY		CMD0H_DISK_READY
# define	CMD_DISK_R_SENSE	CMD0H_DISK_R_SENSE
# define	CMD_RD_DISK_SEC		CMD0H_RD_DISK_SEC
# define	CMD_WR_DISK_SEC		CMD0H_WR_DISK_SEC
# define	CMD_DISK_MAX_LUN	CMD0H_DISK_MAX_LUN
# endif

/* ********************************************************************************************************************* */
/* 并口方式, 状态端口(读命令端口)的位定义 */
# ifndef	PARA_STATE_INTB
# define	PARA_STATE_INTB		0x80			/* 并口方式状态端口的位7: 中断标志,低有效 */
# define	PARA_STATE_BUSY		0x10			/* 并口方式状态端口的位4: 忙标志,高有效 */
# endif

/* ********************************************************************************************************************* */
/* 串口方式, 操作命令前的引导同步码 */
# ifndef	SER_CMD_TIMEOUT
# define	SER_CMD_TIMEOUT		32				/* 串口命令超时时间, 单位为mS, 同步码之间及同步码与命令码之间的间隔应该尽量短, 超时后的处理方式为丢弃 */
# define	SER_SYNC_CODE1		0x57			/* 启动操作的第1个串口同步码 */
# define	SER_SYNC_CODE2		0xAB			/* 启动操作的第2个串口同步码 */
# endif

/* ********************************************************************************************************************* */
/* 操作状态 */

# ifndef	CMD_RET_SUCCESS
# define	CMD_RET_SUCCESS		0x51			/* 命令操作成功 */
# define	CMD_RET_ABORT		0x5F			/* 命令操作失败 */
# endif

/* ********************************************************************************************************************* */
/* USB中断状态 */

# ifndef	USB_INT_EP0_SETUP

/* 以下状态代码为特殊事件中断, 如果通过CMD20_CHK_SUSPEND启用USB总线挂起检查, 那么必须处理USB总线挂起和睡眠唤醒的中断状态 */
# define	USB_INT_USB_SUSPEND	0x05			/* USB总线挂起事件 */
# define	USB_INT_WAKE_UP		0x06			/* 从睡眠中被唤醒事件 */

/* 以下状态代码0XH用于USB设备方式 */
/*   内置固件模式下只需要处理: USB_INT_EP1_OUT, USB_INT_EP1_IN, USB_INT_EP2_OUT, USB_INT_EP2_IN */
/*   位7-位4为0000 */
/*   位3-位2指示当前事务, 00=OUT, 10=IN, 11=SETUP */
/*   位1-位0指示当前端点, 00=端点0, 01=端点1, 10=端点2, 11=USB总线复位 */
# define	USB_INT_EP0_SETUP	0x0C			/* USB端点0的SETUP */
# define	USB_INT_EP0_OUT		0x00			/* USB端点0的OUT */
# define	USB_INT_EP0_IN		0x08			/* USB端点0的IN */
# define	USB_INT_EP1_OUT		0x01			/* USB端点1的OUT */
# define	USB_INT_EP1_IN		0x09			/* USB端点1的IN */
# define	USB_INT_EP2_OUT		0x02			/* USB端点2的OUT */
# define	USB_INT_EP2_IN		0x0A			/* USB端点2的IN */
/* USB_INT_BUS_RESET	0x0000XX11B */		/* USB总线复位 */
# define	USB_INT_BUS_RESET1	0x03			/* USB总线复位 */
# define	USB_INT_BUS_RESET2	0x07			/* USB总线复位 */
# define	USB_INT_BUS_RESET3	0x0B			/* USB总线复位 */
# define	USB_INT_BUS_RESET4	0x0F			/* USB总线复位 */

# endif

/* 以下状态代码2XH-3XH用于USB主机方式的通讯失败代码 */
/*   位7-位6为00 */
/*   位5为1 */
/*   位4指示当前接收的数据包是否同步 */
/*   位3-位0指示导致通讯失败时USB设备的应答: 0010=ACK, 1010=NAK, 1110=STALL, 0011=DATA0, 1011=DATA1, XX00=超时 */
/* USB_INT_RET_ACK	0x001X0010B */			/* 错误:对于IN事务返回ACK */
/* USB_INT_RET_NAK	0x001X1010B */			/* 错误:返回NAK */
/* USB_INT_RET_STALL	0x001X1110B */		/* 错误:返回STALL */
/* USB_INT_RET_DATA0	0x001X0011B */		/* 错误:对于OUT/SETUP事务返回DATA0 */
/* USB_INT_RET_DATA1	0x001X1011B */		/* 错误:对于OUT/SETUP事务返回DATA1 */
/* USB_INT_RET_TOUT	0x001XXX00B */			/* 错误:返回超时 */
/* USB_INT_RET_TOGX	0x0010X011B */			/* 错误:对于IN事务返回数据不同步 */
/* USB_INT_RET_PID	0x001XXXXXB */			/* 错误:未定义 */

/* 以下状态代码1XH用于USB主机方式的操作状态代码 */
# ifndef	USB_INT_SUCCESS
# define	USB_INT_SUCCESS		0x14			/* USB事务或者传输操作成功 */
# define	USB_INT_CONNECT		0x15			/* 检测到USB设备连接事件, 可能是新连接或者断开后重新连接 */
# define	USB_INT_DISCONNECT	0x16			/* 检测到USB设备断开事件 */
# define	USB_INT_BUF_OVER	0x17			/* USB传输的数据有误或者数据太多缓冲区溢出 */
# define	USB_INT_USB_READY	0x18			/* USB设备已经被初始化(已经分配USB地址) */
# define	USB_INT_DISK_READ	0x1D			/* USB存储器请求数据读出 */
# define	USB_INT_DISK_WRITE	0x1E			/* USB存储器请求数据写入 */
# define	USB_INT_DISK_ERR	0x1F			/* USB存储器操作失败 */
/* 附加的USB操作状态定义 */
# define ERR_USB_UNKNOWN		0xFA	    /* 未知错误,不应该发生的情况,需检查硬件或者程序错误 */

# endif

/* 以下状态代码用于主机文件模式下的文件系统错误码 */
# ifndef	ERR_DISK_DISCON
# define	ERR_DISK_DISCON		0x82			/* 磁盘尚未连接,可能磁盘已经断开 */
# define	ERR_LARGE_SECTOR	0x84			/* 磁盘的扇区太大,只支持每扇区512字节 */
# define	ERR_TYPE_ERROR		0x92			/* 磁盘分区类型不支持,只支持FAT12/FAT16/BigDOS/FAT32,需要由磁盘管理工具重新分区 */
# define	ERR_BPB_ERROR		0xA1			/* 磁盘尚未格式化,或者参数错误,需要由WINDOWS采用默认参数重新格式化 */
# define	ERR_DISK_FULL		0xB1			/* 磁盘文件太满,剩余空间太少或者已经没有,需要磁盘整理 */
# define	ERR_FDT_OVER		0xB2			/* 目录(文件夹)内文件太多,没有空闲的目录项,FAT12/FAT16根目录下的文件数应该少于512个,需要磁盘整理 */
# define	ERR_FILE_CLOSE		0xB4			/* 文件已经关闭,如果需要使用,应该重新打开文件 */
# define	ERR_OPEN_DIR		0x41			/* 指定路径的目录(文件夹)被打开 */
# define	ERR_MISS_FILE		0x42			/* 指定路径的文件没有找到,可能是文件名称错误 */
# define	ERR_FOUND_NAME		0x43			/* 搜索到相匹配的文件名,或者是要求打开目录(文件夹)而实际结果却打开了文件 */
/* 以下文件系统错误码用于文件系统子程序 */
# define	ERR_MISS_DIR		0xB3			/* 指定路径的某个子目录(文件夹)没有找到,可能是目录名称错误 */
# define	ERR_LONG_BUF_OVER	0x48			/* 长文件缓冲区溢出 */
# define	ERR_LONG_NAME_ERR	0x49			/* 短文件名没有对应的长文件名或者长文件名错误 */
# define	ERR_NAME_EXIST		0x4A			/* 同名的短文件已经存在,建议重新生成另外一个短文件名 */
# endif

/* ********************************************************************************************************************* */
/* 以下状态代码用于主机文件模式下的磁盘及文件状态, VAR_DISK_STATUS */
# ifndef	DEF_DISK_UNKNOWN
# define	DEF_DISK_UNKNOWN	0x00			/* 尚未初始化,未知状态 */
# define	DEF_DISK_DISCONN	0x01			/* 磁盘没有连接或者已经断开 */
# define	DEF_DISK_CONNECT	0x02			/* 磁盘已经连接,但是尚未初始化或者无法识别该磁盘 */
# define	DEF_DISK_MOUNTED	0x03			/* 磁盘已经初始化成功,但是尚未分析文件系统或者文件系统不支持 */
# define	DEF_DISK_READY		0x10			/* 已经分析磁盘的文件系统并且能够支持 */
# define	DEF_DISK_OPEN_ROOT	0x12			/* 已经打开根目录,使用后必须关闭,注意FAT12/FAT16根目录是固定长度 */
# define	DEF_DISK_OPEN_DIR	0x13			/* 已经打开子目录(文件夹) */
# define	DEF_DISK_OPEN_FILE	0x14			/* 已经打开文件 */
# endif

/* ********************************************************************************************************************* */
/* 文件系统常用定义 */

# ifndef	DEF_SECTOR_SIZE
# define	DEF_SECTOR_SIZE		512				/* U盘或者SD卡默认的物理扇区的大小 */
# endif

# ifndef	DEF_WILDCARD_CHAR
# define	DEF_WILDCARD_CHAR	0x2A			/* 路径名的通配符 '*' */
# define	DEF_SEPAR_CHAR1		0x5C		/* 路径名的分隔符 '\' */
# define	DEF_SEPAR_CHAR2		0x2F		/* 路径名的分隔符 '/' */
# define	DEF_FILE_YEAR		2004			/* 默认文件日期: 2004年 */
# define	DEF_FILE_MONTH		1				/* 默认文件日期: 1月 */
# define	DEF_FILE_DATE		1				/* 默认文件日期: 1日 */
# endif

# ifndef	ATTR_DIRECTORY

/* FAT数据区中文件目录信息 */
typedef struct _FAT_DIR_INFO {
	UINT8	DIR_Name[11];					/* 00H,文件名,共11字节,不足处填空格 */
	UINT8	DIR_Attr;						/* 0BH,文件属性,参考后面的说明 */
	UINT8	DIR_NTRes;						/* 0CH */
	UINT8	DIR_CrtTimeTenth;				/* 0DH,文件创建的时间,以0.1秒单位计数 */
	UINT16	DIR_CrtTime;					/* 0EH,文件创建的时间 */
	UINT16	DIR_CrtDate;					/* 10H,文件创建的日期 */
	UINT16	DIR_LstAccDate;					/* 12H,最近一次存取操作的日期 */
	UINT16	DIR_FstClusHI;					/* 14H */
	UINT16	DIR_WrtTime;					/* 16H,文件修改时间,参考前面的宏MAKE_FILE_TIME */
	UINT16	DIR_WrtDate;					/* 18H,文件修改日期,参考前面的宏MAKE_FILE_DATE */
	UINT16	DIR_FstClusLO;					/* 1AH */
	UINT32	DIR_FileSize;					/* 1CH,文件长度 */
} FAT_DIR_INFO, *P_FAT_DIR_INFO;			/* 20H */

/* 文件属性 */
# define ATTR_READ_ONLY			0x01		/* 文件为只读属性 */
# define ATTR_HIDDEN				0x02		/* 文件为隐含属性 */
# define ATTR_SYSTEM				0x04		/* 文件为系统属性 */
# define ATTR_VOLUME_ID			0x08		/* 卷标 */
# define ATTR_DIRECTORY			0x10		/* 子目录(文件夹) */
# define ATTR_ARCHIVE			0x20		/* 文件为存档属性 */
# define ATTR_LONG_NAME			( ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID )	/* 长文件名属性 */
# define ATTR_LONG_NAME_MASK		( ATTR_LONG_NAME | ATTR_DIRECTORY | ATTR_ARCHIVE )
/* 文件属性 UINT8 */
/* bit0 bit1 bit2 bit3 bit4 bit5 bit6 bit7 */
/*  只   隐   系   卷   目   存   未定义   */
/*  读   藏   统   标   录   档            */
/* 文件时间 UINT16 */
/* Time = (Hour<<11) + (Minute<<5) + (Second>>1) */
# define MAKE_FILE_TIME( h, m, s )	( (h<<11) + (m<<5) + (s>>1) )	/* 生成指定时分秒的文件时间数据 */
/* 文件日期 UINT16 */
/* Date = ((Year-1980)<<9) + (Month<<5) + Day */
# define MAKE_FILE_DATE( y, m, d )	( ((y-1980)<<9) + (m<<5) + d )	/* 生成指定年月日的文件日期数据 */

# define LONE_NAME_MAX_CHAR		(255*2)		/* 长文件名最多字符数/字节数 */
# define LONG_NAME_PER_DIR		(13*2)		/* 长文件名在每个文件目录信息结构中的字符数/字节数 */

# endif

/* ********************************************************************************************************************* */
/* SCSI命令和数据输入输出结构 */

# ifndef	SPC_CMD_INQUIRY

/* SCSI命令码 */
# define SPC_CMD_INQUIRY			0x12
# define SPC_CMD_READ_CAPACITY	0x25
# define SPC_CMD_READ10			0x28
# define SPC_CMD_WRITE10			0x2A
# define SPC_CMD_TEST_READY		0x00
# define SPC_CMD_REQUEST_SENSE	0x03
# define SPC_CMD_MODESENSE6		0x1A
# define SPC_CMD_MODESENSE10		0x5A
# define SPC_CMD_START_STOP		0x1B

/* BulkOnly协议的命令块 */
typedef struct _BULK_ONLY_CBW {
	UINT32	CBW_Sig;
	UINT32	CBW_Tag;
	UINT8	CBW_DataLen0;					/* 08H,输入: 数据传输长度,对于输入数据其有效值是0到48,对于输出数据其有效值为0到33 */
	UINT8	CBW_DataLen1;
	UINT16	CBW_DataLen2;
	UINT8	CBW_Flag;						/* 0CH,输入: 传输方向等标志,位7为1则输入数据,位为0则输出数据或者没有数据 */
	UINT8	CBW_LUN;
	UINT8	CBW_CB_Len;						/* 0EH,输入: 命令块的长度,有效值是1到16 */
	UINT8	CBW_CB_Buf[16];					/* 0FH,输入: 命令块,该缓冲区最多为16个字节 */
} BULK_ONLY_CBW, *P_BULK_ONLY_CBW;			/* BulkOnly协议的命令块, 输入CBW结构 */

/* INQUIRY命令的返回数据 */
typedef struct _INQUIRY_DATA {
	UINT8	DeviceType;					/* 00H, 设备类型 */
	UINT8	RemovableMedia;				/* 01H, 位7为1说明是移动存储 */
	UINT8	Versions;					/* 02H, 协议版本 */
	UINT8	DataFormatAndEtc;			/* 03H, 指定返回数据格式 */
	UINT8	AdditionalLength;			/* 04H, 后续数据的长度 */
	UINT8	Reserved1;
	UINT8	Reserved2;
	UINT8	MiscFlag;					/* 07H, 一些控制标志 */
	UINT8	VendorIdStr[8];				/* 08H, 厂商信息 */
	UINT8	ProductIdStr[16];			/* 10H, 产品信息 */
	UINT8	ProductRevStr[4];			/* 20H, 产品版本 */
} INQUIRY_DATA, *P_INQUIRY_DATA;		/* 24H */

/* REQUEST SENSE命令的返回数据 */
typedef struct _SENSE_DATA {
	UINT8	ErrorCode;					/* 00H, 错误代码及有效位 */
	UINT8	SegmentNumber;
	UINT8	SenseKeyAndEtc;				/* 02H, 主键码 */
	UINT8	Information0;
	UINT8	Information1;
	UINT8	Information2;
	UINT8	Information3;
	UINT8	AdditSenseLen;				/* 07H, 后续数据的长度 */
	UINT8	CmdSpecInfo[4];
	UINT8	AdditSenseCode;				/* 0CH, 附加键码 */
	UINT8	AddSenCodeQual;				/* 0DH, 详细的附加键码 */
	UINT8	FieldReplaUnit;
	UINT8	SenseKeySpec[3];
} SENSE_DATA, *P_SENSE_DATA;			/* 12H */

# endif

/* ********************************************************************************************************************* */
/* 主机文件模式下的数据输入和输出结构 */

# ifndef	MAX_FILE_NAME_LEN

# define MAX_FILE_NAME_LEN		(13+1)		/* 文件名最大长度,最大长度是1个根目录符+8个主文件名+1个小数点+3个类型名+结束符=14 */

/* 命令的输入数据和输出数据 */
typedef union _CH376_CMD_DATA {
	struct {
		UINT8	mBuffer[ MAX_FILE_NAME_LEN ];
	} Default;

	INQUIRY_DATA	DiskMountInq;			/* 返回: INQUIRY命令的返回数据 */
											/* CMD0H_DISK_MOUNT: 初始化磁盘并测试磁盘是否就绪,首次执行时 */

	FAT_DIR_INFO	OpenDirInfo;			/* 返回: 枚举到的文件目录信息 */
											/* CMD0H_FILE_OPEN: 枚举文件和目录(文件夹) */

	FAT_DIR_INFO	EnumDirInfo;			/* 返回: 枚举到的文件目录信息 */
											/* CMD0H_FILE_ENUM_GO: 继续枚举文件和目录(文件夹) */

	struct {
		UINT8	mUpdateFileSz;				/* 输入参数: 是否允许更新文件长度, 0则禁止更新长度 */
	} FileCLose;							/* CMD1H_FILE_CLOSE: 关闭当前已经打开的文件 */

	struct {
		UINT8	mDirInfoIndex;				/* 输入参数: 指定需要读取的目录信息结构在扇区内的索引号, 0FFH则为当前已经打开的文件 */
	} DirInfoRead;							/* CMD1H_DIR_INFO_READ: 读取文件的目录信息 */

	union {
		UINT32	mByteOffset;				/* 输入参数: 偏移字节数,以字节为单位的偏移量(总长度32位,低字节在前) */
		UINT32	mSectorLba;					/* 返回: 当前文件指针对应的绝对线性扇区号,0FFFFFFFFH则已到文件尾(总长度32位,低字节在前) */
	} ByteLocate;							/* CMD4H_BYTE_LOCATE: 以字节为单位移动当前文件指针 */

	struct {
		UINT16	mByteCount;					/* 输入参数: 请求读取的字节数(总长度16位,低字节在前) */
	} ByteRead;								/* CMD2H_BYTE_READ: 以字节为单位从当前位置读取数据块 */

	struct {
		UINT16	mByteCount;					/* 输入参数: 请求写入的字节数(总长度16位,低字节在前) */
	} ByteWrite;							/* CMD2H_BYTE_WRITE: 以字节为单位向当前位置写入数据块 */

	union {
		UINT32	mSectorOffset;				/* 输入参数: 偏移扇区数,以扇区为单位的偏移量(总长度32位,低字节在前) */
		UINT32	mSectorLba;					/* 返回: 当前文件指针对应的绝对线性扇区号,0FFFFFFFFH则已到文件尾(总长度32位,低字节在前) */
	} SectorLocate;							/* CMD4H_SEC_LOCATE: 以扇区为单位移动当前文件指针 */

	struct {
		UINT8	mSectorCount;				/* 输入参数: 请求读取的扇区数 */
											/* 返回: 允许读取的扇区数 */
		UINT8	mReserved1;
		UINT8	mReserved2;
		UINT8	mReserved3;
		UINT32	mStartSector;				/* 返回: 允许读取的扇区块的起始绝对线性扇区号(总长度32位,低字节在前) */
	} SectorRead;							/* CMD1H_SEC_READ: 以扇区为单位从当前位置读取数据块 */

	struct {
		UINT8	mSectorCount;				/* 输入参数: 请求写入的扇区数 */
											/* 返回: 允许写入的扇区数 */
		UINT8	mReserved1;
		UINT8	mReserved2;
		UINT8	mReserved3;
		UINT32	mStartSector;				/* 返回: 允许写入的扇区块的起始绝对线性扇区号(总长度32位,低字节在前) */
	} SectorWrite;							/* CMD1H_SEC_WRITE: 以扇区为单位在当前位置写入数据块 */

	struct {
		UINT32	mDiskSizeSec;				/* 返回: 整个物理磁盘的总扇区数(总长度32位,低字节在前) */
	} DiskCapacity;							/* CMD0H_DISK_CAPACITY: 查询磁盘物理容量 */

	struct {
		UINT32	mTotalSector;				/* 返回: 当前逻辑盘的总扇区数(总长度32位,低字节在前) */
		UINT32	mFreeSector;				/* 返回: 当前逻辑盘的剩余扇区数(总长度32位,低字节在前) */
		UINT8	mDiskFat;					/* 返回: 当前逻辑盘的FAT类型,1-FAT12,2-FAT16,3-FAT32 */
	} DiskQuery;							/* CMD_DiskQuery, 查询磁盘信息 */

	BULK_ONLY_CBW	DiskBocCbw;				/* 输入参数: CBW命令结构 */
											/* CMD0H_DISK_BOC_CMD: 对USB存储器执行BulkOnly传输协议的命令 */

	struct {
		UINT8	mMaxLogicUnit;				/* 返回: USB存储器的最大逻辑单元号 */
	} DiskMaxLun;							/* CMD0H_DISK_MAX_LUN: 控制传输-获取USB存储器最大逻辑单元号 */

	INQUIRY_DATA	DiskInitInq;			/* 返回: INQUIRY命令的返回数据 */
											/* CMD0H_DISK_INIT: 初始化USB存储器 */

	INQUIRY_DATA	DiskInqData;			/* 返回: INQUIRY命令的返回数据 */
											/* CMD0H_DISK_INQUIRY: 查询USB存储器特性 */

	SENSE_DATA		ReqSenseData;			/* 返回: REQUEST SENSE命令的返回数据 */
											/* CMD0H_DISK_R_SENSE: 检查USB存储器错误 */

	struct {
		UINT32	mDiskSizeSec;				/* 返回: 整个物理磁盘的总扇区数(总长度32位,高字节在前) */
	} DiskSize;								/* CMD0H_DISK_SIZE: 获取USB存储器的容量 */

	struct {
		UINT32	mStartSector;				/* 输入参数: LBA扇区地址(总长度32位,低字节在前) */
		UINT8	mSectorCount;				/* 输入参数: 请求读取的扇区数 */
	} DiskRead;								/* CMD5H_DISK_READ: 从USB存储器读数据块(以扇区为单位) */

	struct {
		UINT32	mStartSector;				/* 输入参数: LBA扇区地址(总长度32位,低字节在前) */
		UINT8	mSectorCount;				/* 输入参数: 请求写入的扇区数 */
	} DiskWrite;							/* CMD5H_DISK_WRITE: 向USB存储器写数据块(以扇区为单位) */
} CH376_CMD_DATA, *P_CH376_CMD_DATA;

# endif

/* ********************************************************************************************************************* */
/* 主机文件模式下的文件系统变量的地址 */

# ifndef	VAR_FILE_SIZE

/* 8位/单字节变量 */
# define	VAR_SYS_BASE_INFO	0x20			/* 当前系统的基本信息 */
/*           位6用于指示USB存储设备的子类别SubClass-Code, 位6为0则说明子类别为6, 位6为1则说明子类别是非6的其它值 */
/*           位5用于指示USB设备方式下的USB配置状态和USB主机方式下的USB设备连接状态 */
/*                USB设备方式下, 位5为1则USB配置完成, 位5位0则尚未配置 */
/*                USB主机方式下, 位5为1则USB端口存在USB设备, 位5位0则USB端口没有USB设备 */
/*           位4用于指示USB设备方式下的缓冲区锁定状态, 位4为1则说明USB缓冲区处于锁定状态, 位6为1则说明已经释放 */
/*           其它位, 保留,请勿修改 */
# define	VAR_RETRY_TIMES		0x25			/* USB事务操作的重试次数 */
/*           位7为0则收到NAK时不重试, 位7为1位6为0则收到NAK时无限重试(可以用CMD_ABORT_NAK命令放弃重试), 位7为1位6为1则收到NAK时最多重试3秒 */
/*           位5~位0为超时后的重试次数 */
# define	VAR_FILE_BIT_FLAG	0x26			/* 主机文件模式下的位标志 */
/*           位1和位0, 逻辑盘的FAT文件系统标志, 00-FAT12, 01-FAT16, 10-FAT32, 11-非法 */
/*           位2, 当前缓冲区中的FAT表数据是否被修改标志, 0-未修改, 1-已修改 */
/*           位3, 文件长度需要修改标志, 当前文件被追加数据, 0-未追加无需修改, 1-已追加需要修改 */
/*           其它位, 保留,请勿修改 */
# define	VAR_DISK_STATUS		0x2B	   /* 主机文件模式下的磁盘及文件状态 */
# define	VAR_SD_BIT_FLAG		0x30			/* 主机文件模式下SD卡的位标志 */
/*           位0, SD卡版本, 0-只支持SD第一版,1-支持SD第二版 */
/*           位1, 自动识别, 0-SD卡, 1-MMC卡 */
/*           位2, 自动识别, 0-标准容量SD卡, 1-大容量SD卡(HC-SD) */
/*           位4, ACMD41命令超时 */
/*           位5, CMD1命令超时 */
/*           位6, CMD58命令超时 */
/*           其它位, 保留,请勿修改 */
# define	VAR_UDISK_TOGGLE	0x31			/* USB存储设备的BULK-IN/BULK-OUT端点的同步标志 */
/*           位7, Bulk-In端点的同步标志 */
/*           位6, Bulk-In端点的同步标志 */
/*           位5~位0, 必须为0 */
# define	VAR_UDISK_LUN		0x34			/* USB存储设备的逻辑单元号 */
/*           位7~位4, USB存储设备的当前逻辑单元号,CH376初始化USB存储设备后,默认是访问0# 逻辑单元 */
/*           位3~位0, USB存储设备的最大逻辑单元号,加1后等于逻辑单元数 */
# define	VAR_SEC_PER_CLUS	0x38			/* 逻辑盘的每簇扇区数 */
# define	VAR_FILE_DIR_INDEX	0x3B			/* 当前文件目录信息在扇区内的索引号 */
# define	VAR_CLUS_SEC_OFS	0x3C			/* 当前文件指针在簇内的扇区偏移,为0xFF则指向文件末尾,簇结束 */

/* 32位/4字节变量 */
# define	VAR_DISK_ROOT		0x44			/* 对于FAT16盘为根目录占用扇区数,对于FAT32盘为根目录起始簇号(总长度32位,低字节在前) */
# define	VAR_DSK_TOTAL_CLUS	0x48			/* 逻辑盘的总簇数(总长度32位,低字节在前) */
# define	VAR_DSK_START_LBA	0x4C			/* 逻辑盘的起始绝对扇区号LBA(总长度32位,低字节在前) */
# define	VAR_DSK_DAT_START	0x50			/* 逻辑盘的数据区域的起始LBA(总长度32位,低字节在前) */
# define	VAR_LBA_BUFFER		0x54			/* 当前磁盘数据缓冲区的数据对应的LBA(总长度32位,低字节在前) */
# define	VAR_LBA_CURRENT		0x58			/* 当前读写的磁盘起始LBA地址(总长度32位,低字节在前) */
# define	VAR_FAT_DIR_LBA		0x5C			/* 当前文件目录信息所在的扇区LBA地址(总长度32位,低字节在前) */
# define	VAR_START_CLUSTER	0x60			/* 当前文件或者目录(文件夹)的起始簇号(总长度32位,低字节在前) */
# define	VAR_CURRENT_CLUST	0x64			/* 当前文件的当前簇号(总长度32位,低字节在前) */
# define	VAR_FILE_SIZE		0x68			/* 当前文件的长度(总长度32位,低字节在前) */
# define	VAR_CURRENT_OFFSET	0x6C			/* 当前文件指针,当前读写位置的字节偏移(总长度32位,低字节在前) */

# endif

/* ********************************************************************************************************************* */
/* 常用USB定义 */

/* USB的包标识PID, 主机方式可能用到 */
# ifndef	DEF_USB_PID_SETUP
# define	DEF_USB_PID_NULL	0x00			/* 保留PID, 未定义 */
# define	DEF_USB_PID_SOF		0x05
# define	DEF_USB_PID_SETUP	0x0D
# define	DEF_USB_PID_IN		0x09
# define	DEF_USB_PID_OUT		0x01
# define	DEF_USB_PID_ACK		0x02
# define	DEF_USB_PID_NAK		0x0A
# define	DEF_USB_PID_STALL	0x0E
# define	DEF_USB_PID_DATA0	0x03
# define	DEF_USB_PID_DATA1	0x0B
# define	DEF_USB_PID_PRE		0x0C
# endif

/* USB请求类型, 外置固件模式可能用到 */
# ifndef	DEF_USB_REQ_TYPE
# define	DEF_USB_REQ_READ	0x80			/* 控制读操作 */
# define	DEF_USB_REQ_WRITE	0x00			/* 控制写操作 */
# define	DEF_USB_REQ_TYPE	0x60			/* 控制请求类型 */
# define	DEF_USB_REQ_STAND	0x00			/* 标准请求 */
# define	DEF_USB_REQ_CLASS	0x20			/* 设备类请求 */
# define	DEF_USB_REQ_VENDOR	0x40			/* 厂商请求 */
# define	DEF_USB_REQ_RESERVE	0x60			/* 保留请求 */
# endif

/* USB标准设备请求, RequestType的位6位5=00(Standard), 外置固件模式可能用到 */
# ifndef	DEF_USB_GET_DESCR
# define	DEF_USB_CLR_FEATURE	0x01
# define	DEF_USB_SET_FEATURE	0x03
# define	DEF_USB_GET_STATUS	0x00
# define	DEF_USB_SET_ADDRESS	0x05
# define	DEF_USB_GET_DESCR	0x06
# define	DEF_USB_SET_DESCR	0x07
# define	DEF_USB_GET_CONFIG	0x08
# define	DEF_USB_SET_CONFIG	0x09
# define	DEF_USB_GET_INTERF	0x0A
# define	DEF_USB_SET_INTERF	0x0B
# define	DEF_USB_SYNC_FRAME	0x0C
# endif

/* *************************************************************************** */

# ifdef __cplusplus
}
# endif

# endif

filesys.h

# ifndef	__FILESYS_H__
# define __FILESYS_H__
# include "sys.h"
# include "ch376.h"
# include "CH376INC.H"

# define	STRUCT_OFFSET( s, m )	( (UINT8)( & ((s *)0) -> m ) )							/* 定义获取结构成员相对偏移地址的宏 */
# ifdef	EN_LONG_NAME
# ifndef	LONG_NAME_BUF_LEN
# define	LONG_NAME_BUF_LEN	( LONG_NAME_PER_DIR * 20 )									/* 自行定义的长文件名缓冲区长度,最小值为LONG_NAME_PER_DIR*1 */
# endif
# endif

UINT8	CH376ReadBlock( PUINT8 buf );  													/* 从当前主机端点的接收缓冲区读取数据块,返回长度 */

UINT8	CH376WriteReqBlock( PUINT8 buf );  												/* 向内部指定缓冲区写入请求的数据块,返回长度 */

void	CH376WriteHostBlock( PUINT8 buf, UINT8 len );  									/* 向USB主机端点的发送缓冲区写入数据块 */

void	CH376WriteOfsBlock( PUINT8 buf, UINT8 ofs, UINT8 len );  						/* 向内部缓冲区指定偏移地址写入数据块 */

void	CH376SetFileName( PUINT8 name );  												/* 设置将要操作的文件的文件名 */

UINT32	CH376Read32bitDat( void );  													/* 从CH376芯片读取32位的数据并结束命令 */

UINT8	CH376ReadVar8( UINT8 var );  													/* 读CH376芯片内部的8位变量 */

void	CH376WriteVar8( UINT8 var, UINT8 dat );  										/* 写CH376芯片内部的8位变量 */

UINT32	CH376ReadVar32( UINT8 var );  													/* 读CH376芯片内部的32位变量 */

void	CH376WriteVar32( UINT8 var, UINT32 dat );  										/* 写CH376芯片内部的32位变量 */

void	CH376EndDirInfo( void );  														/* 在调用CH376DirInfoRead获取FAT_DIR_INFO结构之后应该通知CH376结束 */

UINT32	CH376GetFileSize( void );  														/* 读取当前文件长度 */

UINT8	CH376GetDiskStatus( void );                                                     /* 获取磁盘和文件系统的工作状态 */

UINT8	CH376GetIntStatus( void );                                                      /* 获取中断状态并取消中断请求 */

# ifndef	NO_DEFAULT_CH376_INT
UINT8	Wait376Interrupt( void );                                                       /* 等待CH376中断(INT# 低电平),返回中断状态码, 超时则返回ERR_USB_UNKNOWN */
# endif

UINT8	CH376SendCmdWaitInt( UINT8 mCmd );                                              /* 发出命令码后,等待中断 */

UINT8	CH376SendCmdDatWaitInt( UINT8 mCmd, UINT8 mDat );                               /* 发出命令码和一字节数据后,等待中断 */

UINT8	CH376DiskReqSense( void );                                                      /* 检查USB存储器错误 */

UINT8	CH376DiskConnect( void );                                                       /* 检查U盘是否连接,不支持SD卡 */

UINT8	CH376DiskMount( void );                                                         /* 初始化磁盘并测试磁盘是否就绪 */

UINT8	CH376FileOpen( PUINT8 name );                                                   /* 在根目录或者当前目录下打开文件或者目录(文件夹) */

UINT8	CH376FileCreate( PUINT8 name );                                                 /* 在根目录或者当前目录下新建文件,如果文件已经存在那么先删除 */

UINT8	CH376DirCreate( PUINT8 name );                                                  /* 在根目录下新建目录(文件夹)并打开,如果目录已经存在那么直接打开 */

UINT8	CH376SeparatePath( PUINT8 path );                                               /* 从路径中分离出最后一级文件名或者目录(文件夹)名,返回最后一级文件名或者目录名的字节偏移 */

UINT8	CH376FileOpenDir( PUINT8 PathName, UINT8 StopName );                            /* 打开多级目录下的文件或者目录的上级目录,支持多级目录路径,支持路径分隔符,路径长度不超过255个字符 */
/* StopName 指向最后一级文件名或者目录名 */

UINT8	CH376FileOpenPath( PUINT8 PathName );                                           /* 打开多级目录下的文件或者目录(文件夹),支持多级目录路径,支持路径分隔符,路径长度不超过255个字符 */

UINT8	CH376FileCreatePath( PUINT8 PathName );                                         /* 新建多级目录下的文件,支持多级目录路径,支持路径分隔符,路径长度不超过255个字符 */

# ifdef	EN_DIR_CREATE
UINT8	CH376DirCreatePath( PUINT8 PathName );                                          /* 新建多级目录下的目录(文件夹)并打开,支持多级目录路径,支持路径分隔符,路径长度不超过255个字符 */
# endif

UINT8	CH376FileErase( PUINT8 PathName );                                              /* 删除文件,如果已经打开则直接删除,否则对于文件会先打开再删除,支持多级目录路径 */

UINT8	CH376FileClose( UINT8 UpdateSz );                                               /* 关闭当前已经打开的文件或者目录(文件夹) */

UINT8	CH376DirInfoRead( void );                                                       /* 读取当前文件的目录信息 */

UINT8	CH376DirInfoSave( void );                                                       /* 保存文件的目录信息 */

UINT8	CH376ByteLocate( UINT32 offset );                                               /* 以字节为单位移动当前文件指针 */

UINT8	CH376ByteRead( PUINT8 buf, UINT16 ReqCount, PUINT16 RealCount );                /* 以字节为单位从当前位置读取数据块 */

UINT8	CH376ByteWrite( PUINT8 buf, UINT16 ReqCount, PUINT16 RealCount );               /* 以字节为单位向当前位置写入数据块 */

UINT8	CH376DiskCapacity( PUINT32 DiskCap );                                           /* 查询磁盘物理容量,扇区数 */

UINT8   CH376DiskQuery( PUINT32 DiskFre );                                              /* 查询磁盘剩余空间信息,扇区数 */

UINT8	CH376SecLocate( UINT32 offset );                                                /* 以扇区为单位移动当前文件指针 */

# ifdef	EN_SECTOR_ACCESS

UINT8	CH376DiskReadSec( PUINT8 buf, UINT32 iLbaStart, UINT8 iSectorCount );           /* 从U盘读取多个扇区的数据块到缓冲区,不支持SD卡 */

UINT8	CH376DiskWriteSec( PUINT8 buf, UINT32 iLbaStart, UINT8 iSectorCount );          /* 将缓冲区中的多个扇区的数据块写入U盘,不支持SD卡 */

UINT8	CH376SecRead( PUINT8 buf, UINT8 ReqCount, PUINT8 RealCount );                   /* 以扇区为单位从当前位置读取数据块,不支持SD卡 */

UINT8	CH376SecWrite( PUINT8 buf, UINT8 ReqCount, PUINT8 RealCount );                  /* 以扇区为单位在当前位置写入数据块,不支持SD卡 */

# endif

# ifdef	EN_LONG_NAME

UINT8	CH376LongNameWrite( PUINT8 buf, UINT16 ReqCount );                              /* 长文件名专用的字节写子程序 */

UINT8	CH376CheckNameSum( PUINT8 DirName );                                            /* 计算长文件名的短文件名检验和,输入为无小数点分隔符的固定11字节格式 */

UINT8	CH376LocateInUpDir( PUINT8 PathName );                                          /* 在上级目录(文件夹)中移动文件指针到当前文件目录信息所在的扇区 */
/* 另外,顺便将当前文件目录信息所在的扇区的前一个扇区的LBA地址写入CH376内部VAR_FAT_DIR_LBA变量(为了方便收集长文件名时向前搜索,否则要多移动一次) */
/* 使用了全局缓冲区GlobalBuf的前12个字节 */

UINT8	CH376GetLongName( PUINT8 PathName, PUINT8 LongName );                           /* 由短文件名或者目录(文件夹)名获得相应的长文件名 */
/* 需要输入短文件名的完整路径PathName,需要提供缓冲区接收长文件名LongName(以UNICODE小端编码,以双0结束) */
/* 使用了全局缓冲区GlobalBuf的前34个字节,sizeof(GlobalBuf)>=sizeof(FAT_DIR_INFO)+2 */

UINT8	CH376CreateLongName( PUINT8 PathName, PUINT8 LongName );                        /* 新建具有长文件名的文件,关闭文件后返回,LongName输入路径必须在RAM中 */
/* 需要输入短文件名的完整路径PathName(请事先参考FAT规范由长文件名自行产生),需要输入以UNICODE小端编码的以双0结束的长文件名LongName */
/* 使用了全局缓冲区GlobalBuf的前64个字节,sizeof(GlobalBuf)>=sizeof(FAT_DIR_INFO)*2 */

# endif

# endif

filesys.c

/* name 参数是指短文件名, 可以包括根目录符, 但不含有路径分隔符, 总长度不超过1+8+1+3+1字节 */
/* PathName 参数是指全路径的短文件名, 包括根目录符、多级子目录及路径分隔符、文件名/目录名 */
/* LongName 参数是指长文件名, 以UNICODE小端顺序编码, 以两个0字节结束, 使用长文件名子程序必须先定义全局缓冲区GlobalBuf, 长度不小于64字节, 可以与其它子程序共用 */

/* 定义 NO_DEFAULT_CH376_INT 用于禁止默认的Wait376Interrupt子程序,禁止后,应用程序必须自行定义一个同名子程序 */
/* 定义 DEF_INT_TIMEOUT 用于设置默认的Wait376Interrupt子程序中的等待中断的超时时间/循环计数值, 0则不检查超时而一直等待 */
/* 定义 EN_DIR_CREATE 用于提供新建多级子目录的子程序,默认是不提供 */
/* 定义 EN_DISK_QUERY 用于提供磁盘容量查询和剩余空间查询的子程序,默认是不提供 */
/* 定义 EN_SECTOR_ACCESS 用于提供以扇区为单位读写文件的子程序,默认是不提供 */
/* 定义 EN_LONG_NAME 用于提供支持长文件名的子程序,默认是不提供 */
/* 定义 DEF_IC_V43_U 用于去掉支持低版本的程序代码,仅支持V4.3及以上版本的CH376芯片,默认是支持低版本 */

# include "filesys.h"
/*******************************************************************************
* 函  数  名      : CH376ReadBlock
* 描      述      : 从当前主机端点的接收缓冲区读取数据块,.
* 输      入      : PUINT8 buf:
*                   指向外部接收缓冲区.
* 返      回      : 返回长度.
*******************************************************************************/
UINT8	CH376ReadBlock( PUINT8 buf ){
	UINT8	s, l;
	xWriteCH376Cmd( CMD01_RD_USB_DATA0 );
	s = l = xReadCH376Data( );  														/* 后续数据长度 */
	if ( l ) 
	{
		do 
		{
			*buf = xReadCH376Data( );
			buf ++;
		} while ( -- l );
	}
	xEndCH376Cmd( );	
	return( s );
}

/*******************************************************************************
* 函  数  名      : CH376WriteReqBlock
* 描      述      : 向内部指定缓冲区写入请求的数据块,返回长度.
* 输      入      : PUINT8 buf:
*                   指向发送缓冲区.
* 返      回      : UINT8 s:后续数据长度.
*******************************************************************************/
UINT8	CH376WriteReqBlock( PUINT8 buf ){
	UINT8	s, l;

	xWriteCH376Cmd( CMD01_WR_REQ_DATA );                                                /* 向内部指定缓冲区写入请求的数据块 */
	s = l = xReadCH376Data( );  														/* 后续数据长度 */
	if ( l ) 
	{
		do 
		{
			xWriteCH376Data( *buf );
			buf ++;
		} while ( -- l );
	}
	xEndCH376Cmd( );
	return( s );
}

/*******************************************************************************
* 函  数  名      : CH376WriteHostBlock
* 描      述      : 向USB主机端点的发送缓冲区写入数据块.
* 输      入      : PUINT8 buf:
*					指向发送数据缓冲区.
*					UINT8 len:
*					数据长度.
* 返      回      : 无.
*******************************************************************************/
void	CH376WriteHostBlock( PUINT8 buf, UINT8 len )
{
	xWriteCH376Cmd( CMD10_WR_HOST_DATA );
	xWriteCH376Data( len );  															/* 后续数据长度 */
	if ( len ) 
	{
		do 
		{
			xWriteCH376Data( *buf );
			buf ++;
		} while ( -- len );
	}
	xEndCH376Cmd( );	
}

/*******************************************************************************
* 函  数  名      : CH376WriteOfsBlock
* 描      述      : 向内部缓冲区指定偏移地址写入数据块.
* 输      入      : PUINT8 buf:
*					指向发送数据缓冲区.
*					UINT8 ofs:
*					偏移地址.
*					UINT8 len:
*					数据长度.
* 返      回      : 无.
*******************************************************************************/
void	CH376WriteOfsBlock( PUINT8 buf, UINT8 ofs, UINT8 len )
{
	xWriteCH376Cmd( CMD20_WR_OFS_DATA );
	xWriteCH376Data( ofs );  															/* 偏移地址 */
	xWriteCH376Data( len );  															/* 数据长度 */
	if ( len ) 
	{
		do 
		{
			xWriteCH376Data( *buf );
			buf ++;
		} while ( -- len );
	}
	xEndCH376Cmd( );
}

/*******************************************************************************
* 函  数  名      : CH376SetFileName
* 描      述      : 设置将要操作的文件的文件名 .
* 输      入      : PUINT8 name:
*					指向文件名缓冲区.
* 返      回      : 无.
*******************************************************************************/
void	CH376SetFileName( PUINT8 name )
{
	UINT8	c;

# ifndef	DEF_IC_V43_U																	/* 默认支持低版本 */
	UINT8	s;

	xWriteCH376Cmd( CMD01_GET_IC_VER );													/* 获取芯片版本 */
	if (  xReadCH376Data( ) < 0x43 ) 
	{
		if ( CH376ReadVar8( VAR_DISK_STATUS ) < DEF_DISK_READY ) 
		{
			xWriteCH376Cmd( CMD10_SET_FILE_NAME );
			xWriteCH376Data( 0 );
			s = CH376SendCmdWaitInt( CMD0H_FILE_OPEN );
			if ( s == USB_INT_SUCCESS ) 
			{
				s = CH376ReadVar8( 0xCF );
				if ( s ) 
				{
					CH376WriteVar32( 0x4C, CH376ReadVar32( 0x4C ) + ( (UINT16)s << 8 ) );
					CH376WriteVar32( 0x50, CH376ReadVar32( 0x50 ) + ( (UINT16)s << 8 ) );
					CH376WriteVar32( 0x70, 0 );
				}
			}
		}
	}
# endif
	xWriteCH376Cmd( CMD10_SET_FILE_NAME );
	c = *name;
	xWriteCH376Data( c );
	while ( c ) 
	{
		name ++;
		c = *name;
		if ( c == DEF_SEPAR_CHAR1 || c == DEF_SEPAR_CHAR2 ) 
		{
			c = 0;  																	/* 强行将文件名截止 */
		}
		xWriteCH376Data( c );
	}
	xEndCH376Cmd( );	
}

/*******************************************************************************
* 函  数  名      : CH376Read32bitDat
* 描      述      : 从CH376芯片读取32位的数据并结束命令.
* 输      入      : 无.
* 返      回      : 32位数据.
*******************************************************************************/
UINT32	CH376Read32bitDat( void )
{
	UINT8	c0, c1, c2, c3;

	c0 = xReadCH376Data( );
	c1 = xReadCH376Data( );
	c2 = xReadCH376Data( );
	c3 = xReadCH376Data( );	
	xEndCH376Cmd( );
	return( c0 | (UINT16)c1 << 8 | (UINT32)c2 << 16 | (UINT32)c3 << 24 );
}

/*******************************************************************************
* 函  数  名      : CH376ReadVar8
* 描      述      : 读CH376芯片内部的8位变量.
* 输      入      : 无.
* 返      回      : 8位变量.
*******************************************************************************/
UINT8	CH376ReadVar8( UINT8 var ) 
{
	UINT8	c0;
	
	xWriteCH376Cmd( CMD11_READ_VAR8 );                                                   /* 读取指定的8位文件系统变量 */
	xWriteCH376Data( var );
	c0 = xReadCH376Data( );
	xEndCH376Cmd( );	
	return( c0 );
}

/*******************************************************************************
* 函  数  名      : CH376WriteVar8
* 描      述      : 写CH376芯片内部的8位变量.
* 输      入      : UINT8 var:
*                   变量地址.
*                   UINT8 dat:
                    数据.
* 返      回      : 无.
*******************************************************************************/
void	CH376WriteVar8( UINT8 var, UINT8 dat )
{
	xWriteCH376Cmd( CMD20_WRITE_VAR8 );                                                 /* 设置指定的8位文件系统变量 */
	xWriteCH376Data( var );
	xWriteCH376Data( dat );
    xEndCH376Cmd( );		
}

/*******************************************************************************
* 函  数  名      : CH376ReadVar8
* 描      述      : 读CH376芯片内部的32位变量.
* 输      入      : UINT8 var:
*                   变量地址.
* 返      回      : 32位变量.
*******************************************************************************/
UINT32	CH376ReadVar32( UINT8 var )
{
	xWriteCH376Cmd( CMD14_READ_VAR32 );
	xWriteCH376Data( var );
	return( CH376Read32bitDat( ) );  													/* 从CH376芯片读取32位的数据并结束命令 */
}

/*******************************************************************************
* 函  数  名      : CH376WriteVar32
* 描      述      : 写CH376芯片内部的32位变量.
* 输      入      : UINT8 var:
*                   变量地址.
*					UINT32 dat:
*					数据.
* 返      回      : 无.
*******************************************************************************/
void	CH376WriteVar32( UINT8 var, UINT32 dat )
{
	xWriteCH376Cmd( CMD50_WRITE_VAR32 );
	xWriteCH376Data( var );
	xWriteCH376Data( (UINT8)dat );
	xWriteCH376Data( (UINT8)( (UINT16)dat >> 8 ) );
	xWriteCH376Data( (UINT8)( dat >> 16 ) );
	xWriteCH376Data( (UINT8)( dat >> 24 ) );
	xEndCH376Cmd( );		
}

/*******************************************************************************
* 函  数  名      : CH376EndDirInfo
* 描      述      : 在调用CH376DirInfoRead获取FAT_DIR_INFO结构之后应该通知CH376结束.
* 输      入      : 无.
* 返      回      : 无.
*******************************************************************************/
void	CH376EndDirInfo( void )
{
	CH376WriteVar8( 0x0D, 0x00 );
}

/*******************************************************************************
* 函  数  名      : CH376GetFileSize
* 描      述      : 读取当前文件长度.
* 输      入      : 无.
* 返      回      : 文件长度.
*******************************************************************************/
UINT32	CH376GetFileSize( void )
{
	return( CH376ReadVar32( VAR_FILE_SIZE ) );
}

/*******************************************************************************
* 函  数  名      : CH376GetDiskStatus
* 描      述      : 获取磁盘和文件系统的工作状态.
* 输      入      : 无.
* 返      回      : 状态.
*******************************************************************************/
UINT8	CH376GetDiskStatus( void )
{
	return( CH376ReadVar8( VAR_DISK_STATUS ) );
}

/*******************************************************************************
* 函  数  名      : CH376GetIntStatus
* 描      述      : 获取中断状态并取消中断请求.
* 输      入      : 无.
* 返      回      : UINT8 s:
*					中断状态.
*******************************************************************************/
UINT8	CH376GetIntStatus( void )
{
	UINT8	s;
	
	xWriteCH376Cmd( CMD01_GET_STATUS );
	s = xReadCH376Data( );
	xEndCH376Cmd( );	
	return( s );
}

/*******************************************************************************
* 函  数  名      : Wait376Interrupt
* 描      述      : 等待CH376中断(INT# 低电平),返回中断状态码, 超时则返回
*                   ERR_USB_UNKNOWN.
* 输      入      : 无.
* 返      回      : 中断状态.
*******************************************************************************/
# ifndef	NO_DEFAULT_CH376_INT
UINT8	Wait376Interrupt( void )
{
# ifdef	DEF_INT_TIMEOUT                                                                 /* 是否定义了超时时间 */
# if		DEF_INT_TIMEOUT < 1                                                             /* 没有定义 */
	while ( Query376Interrupt( ) == FALSE );                                            /* 一直等中断 */
	return( CH376GetIntStatus( ) );                                                     /* 检测到中断 */
# else                                                                                   /* 定义了超时时间 */
	UINT32	i;
	
	for ( i = 0; i < DEF_INT_TIMEOUT; i ++ )                                            /* 计数防止超时 */
	{  
		if ( Query376Interrupt( ) ) 
		{
		    return( CH376GetIntStatus( ) );                                             /* 检测到中断 */
		}
        /* 在等待CH376中断的过程中,可以做些需要及时处理的其它事情 */
	}
	return( ERR_USB_UNKNOWN );                                                          /* 不应该发生的情况 */
# endif
# else
	UINT32	i;
	
	for ( i = 0; i < 5000000; i ++ )                                                    /* 计数防止超时,默认的超时时间,与单片机主频有关 */
	{  
		if ( Query376Interrupt( ) ) 
		{
		    return( CH376GetIntStatus( ) );                                             /* 检测到中断 */
		}
        /* 在等待CH376中断的过程中,可以做些需要及时处理的其它事情 */
	}
	return( ERR_USB_UNKNOWN );                                                          /* 不应该发生的情况 */
# endif
}
# endif

/*******************************************************************************
* 函  数  名      : CH376SendCmdWaitInt
* 描      述      : 发出命令码后,等待中断.
* 输      入      : UINT8 mCmd:
*					命令码.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376SendCmdWaitInt( UINT8 mCmd )
{
	xWriteCH376Cmd( mCmd );
	xEndCH376Cmd( );
	return( Wait376Interrupt( ) );
}

/*******************************************************************************
* 函  数  名      : CH376SendCmdDatWaitInt
* 描      述      : 发出命令码和一字节数据后,等待中断.
* 输      入      : 无.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376SendCmdDatWaitInt( UINT8 mCmd, UINT8 mDat )
{
	xWriteCH376Cmd( mCmd );
	xWriteCH376Data( mDat );
	xEndCH376Cmd( );
	return( Wait376Interrupt( ) );
}

/*******************************************************************************
* 函  数  名      : CH376DiskReqSense
* 描      述      : 检查USB存储器错误.
* 输      入      : 无.
* 返      回      : UINT8 s:
*					状态.
*******************************************************************************/
UINT8	CH376DiskReqSense( void )
{
	UINT8	s;

	delay_ms(5);
	s = CH376SendCmdWaitInt( CMD0H_DISK_R_SENSE );
	delay_ms(5);
	return( s );
}

/*******************************************************************************
* 函  数  名      : CH376DiskConnect
* 描      述      : 检查U盘是否连接,不支持SD卡.
* 输      入      : 无.
* 返      回      : U盘是否连接状态.
*******************************************************************************/
UINT8	CH376DiskConnect( void )
{
	if ( Query376Interrupt( ) ) 
	{
		CH376GetIntStatus( );  															/* 检测到中断 */
	}
	return( CH376SendCmdWaitInt( CMD0H_DISK_CONNECT ) );								/* 检查磁盘是否连接 */
}

/*******************************************************************************
* 函  数  名      : CH376DiskMount
* 描      述      : 初始化磁盘并测试磁盘是否就绪.
* 输      入      : 无.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8 CH376DiskMount( void )
{
	return( CH376SendCmdWaitInt( CMD0H_DISK_MOUNT ) );									/* 初始化磁盘并测试磁盘是否就绪 */
}

/*******************************************************************************
* 函  数  名      : CH376FileOpen
* 描      述      : 在根目录或者当前目录下打开文件或者目录(文件夹).
* 输      入      : 无.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376FileOpen( PUINT8 name ) 
{
	CH376SetFileName( name );  															/* 设置将要操作的文件的文件名 */
# ifndef	DEF_IC_V43_U
	if ( name[0] == DEF_SEPAR_CHAR1 || name[0] == DEF_SEPAR_CHAR2 ) 
	{
		CH376WriteVar32( VAR_CURRENT_CLUST, 0 );
	}
# endif
	return( CH376SendCmdWaitInt( CMD0H_FILE_OPEN ) );
}

/*******************************************************************************
* 函  数  名      : CH376FileCreate
* 描      述      : 在根目录或者当前目录下新建文件,如果文件已经存在那么先删除.
* 输      入      : 无.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376FileCreate( PUINT8 name )
{
	if ( name ) 
	{
		CH376SetFileName( name );  	/* 设置将要操作的文件的文件名 */
	}
	return( CH376SendCmdWaitInt( CMD0H_FILE_CREATE ) );
}

/*******************************************************************************
* 函  数  名      : CH376DirCreate
* 描      述      : 在根目录下新建目录(文件夹)并打开,如果目录已经存在那么直接打开.
* 输      入      : 无.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376DirCreate( PUINT8 name )
{
	CH376SetFileName( name );  	/* 设置将要操作的文件的文件名 */
# ifndef	DEF_IC_V43_U
	if ( name[0] == DEF_SEPAR_CHAR1 || name[0] == DEF_SEPAR_CHAR2 ) 
	{
		CH376WriteVar32( VAR_CURRENT_CLUST, 0 );
	}
# endif
	return( CH376SendCmdWaitInt( CMD0H_DIR_CREATE ) );
}

/*******************************************************************************
* 函  数  名      : CH376SeparatePath
* 描      述      : 从路径中分离出最后一级文件名或者目录(文件夹)名
* 输      入      : PUINT8 path:
*					指向路径缓冲区.
* 返      回      : 返回最后一级文件名或者目录名的字节偏移.
*******************************************************************************/
UINT8	CH376SeparatePath( PUINT8 path )
{
	PUINT8	pName;

	for ( pName = path; *pName != 0; ++ pName );  										/* 到文件名字符串结束位置 */
	while ( *pName != DEF_SEPAR_CHAR1 && *pName != DEF_SEPAR_CHAR2 && pName != path ) 
	{	
		pName --;  																		/*  搜索倒数第一个路径分隔符 */
	}
	if ( pName != path ) 
	{
		pName ++;  																		/* 找到了路径分隔符,则修改指向目标文件的最后一级文件名,跳过前面的多级目录名及路径分隔符 */
	}
	return( pName - path );
}

/*******************************************************************************
* 函  数  名      : CH376FileOpenDir
* 描      述      : 打开多级目录下的文件或者目录的上级目录,支持多级目录路径,
*					支持路径分隔符,路径长度不超过255个字符
* 输      入      : PUINT8 path:
*					指向路径缓冲区.
*					UINT8 StopName:
*					指向最后一级文件名或者目录名
* 返      回      : 返回最后一级文件名或者目录名的字节偏移.
*******************************************************************************/
UINT8	CH376FileOpenDir( PUINT8 PathName, UINT8 StopName )
{
	UINT8	i, s;

	s = 0;
	i = 1;  																			/* 跳过有可能的根目录符 */
	while ( 1 ) 
	{
		while ( PathName[i] != DEF_SEPAR_CHAR1 && PathName[i] != DEF_SEPAR_CHAR2 && PathName[i] != 0 ) 
		{
			++ i;  																		/* 搜索下一个路径分隔符或者路径结束符 */
		}

		if ( PathName[i] ) 
		{
			i ++;  																		/* 找到了路径分隔符,修改指向目标文件的最后一级文件名 */
		}
		else 
		{
			i = 0;  																	/* 路径结束 */
		}
		
		s = CH376FileOpen( &PathName[s] );  											/* 打开文件或者目录 */
		
		if ( i && i != StopName ) 														/* 路径尚未结束 */	
		{  			
			if ( s != ERR_OPEN_DIR ) 													/* 因为是逐级打开,尚未到路径结束,所以,如果不是成功打开了目录,那么说明有问题 */
			{  
				if ( s == USB_INT_SUCCESS ) 
				{
					return( ERR_FOUND_NAME );  											/* 中间路径必须是目录名,如果是文件名则出错 */
				}
				else if ( s == ERR_MISS_FILE ) 
				{
					return( ERR_MISS_DIR );  											/* 中间路径的某个子目录没有找到,可能是目录名称错误 */
				}
				else 
				{
					return( s );  														/* 操作出错 */
				}
			}
			s = i;  																	/* 从下一级目录开始继续 */
		}
		else 
		{
			return( s );  																/* 路径结束,USB_INT_SUCCESS为成功打开文件,ERR_OPEN_DIR为成功打开目录(文件夹),其它为操作出错 */
		}
	}
}

/*******************************************************************************
* 函  数  名      : CH376FileOpenPath
* 描      述      : 打开多级目录下的文件或者目录(文件夹),支持多级目录路径,
*					支持路径分隔符,路径长度不超过255个字符
* 输      入      : PUINT8 path:
*					指向路径缓冲区.
* 返      回      : 返回最后一级文件名或者目录名的字节偏移.
*******************************************************************************/
UINT8	CH376FileOpenPath( PUINT8 PathName )
{
	return( CH376FileOpenDir( PathName, 0xFF ) );
}

/*******************************************************************************
* 函  数  名      : CH376FileCreatePath
* 描      述      : 新建多级目录下的目录(文件夹)并打开,支持多级目录路径,支持路
*					径分隔符,路径长度不超过255个字符.
* 输      入      : PUINT8 path:
*					指向路径缓冲区.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376FileCreatePath( PUINT8 PathName )
{
	UINT8	s;
	UINT8	Name;

	Name = CH376SeparatePath( PathName );  												/* 从路径中分离出最后一级文件名,返回最后一级文件名的偏移 */
	if ( Name ) 																		/* 是多级目录 */
	{  
		s = CH376FileOpenDir( PathName, Name );  										/* 打开多级目录下的最后一级目录,即打开新建文件的上级目录 */
		if ( s != ERR_OPEN_DIR ) 														/* 因为是打开上级目录,所以,如果不是成功打开了目录,那么说明有问题 */	
		{  
			if ( s == USB_INT_SUCCESS ) 
			{
				return( ERR_FOUND_NAME );  												/* 中间路径必须是目录名,如果是文件名则出错 */
			}
			else if ( s == ERR_MISS_FILE ) 
			{
				return( ERR_MISS_DIR );  												/* 中间路径的某个子目录没有找到,可能是目录名称错误 */
			}
			else 
			{
				return( s );  															/* 操作出错 */
			}
		}
	}
	return( CH376FileCreate( &PathName[Name] ) );  										/* 在根目录或者当前目录下新建文件 */
}

/*******************************************************************************
* 函  数  名      : CH376FileCreatePath
* 描      述      : 新建多级目录下的文件,支持多级目录路径,支持路径分隔符,路径长
*					度不超过255个字符
* 输      入      : PUINT8 path:	指向路径缓冲区.
* 返      回      : 操作状态.
*******************************************************************************/
# ifdef	EN_DIR_CREATE
UINT8	CH376DirCreatePath( PUINT8 PathName )
{
	UINT8	s;
	UINT8	Name;
	UINT8	ClustBuf[4];

	Name = CH376SeparatePath( PathName );  												/* 从路径中分离出最后一级目录名,返回最后一级文件名的偏移 */
	if ( Name ) 																		/* 是多级目录 */
	{  
		s = CH376FileOpenDir( PathName, Name );  										/* 打开多级目录下的最后一级目录,即打开新建目录的上级目录 */
		if ( s != ERR_OPEN_DIR ) 														/* 因为是打开上级目录,所以,如果不是成功打开了目录,那么说明有问题 */				
		{  
			if ( s == USB_INT_SUCCESS ) 
			{
				return( ERR_FOUND_NAME );  												/* 中间路径必须是目录名,如果是文件名则出错 */
			}
			else if ( s == ERR_MISS_FILE ) 
			{
				return( ERR_MISS_DIR );  												/* 中间路径的某个子目录没有找到,可能是目录名称错误 */
			}
			else 
			{
				return( s );  															/* 操作出错 */
			}
		}
		xWriteCH376Cmd( CMD14_READ_VAR32 );
		xWriteCH376Data( VAR_START_CLUSTER );  											/* 上级目录的起始簇号 */
		
		for ( s = 0; s != 4; s ++ ) 
		{
			ClustBuf[ s ] = xReadCH376Data( );
		}
		
		xEndCH376Cmd( );
		
		s = CH376DirCreate( &PathName[Name] );  										/* 在当前目录下新建目录 */
		if ( s != USB_INT_SUCCESS ) 
		{
			return( s );
		}
		
		s = CH376ByteLocate( sizeof(FAT_DIR_INFO) + STRUCT_OFFSET( FAT_DIR_INFO, DIR_FstClusHI ) );  /* 移动文件指针 */
		if ( s != USB_INT_SUCCESS ) 
		{
			return( s );
		}
		
		s = CH376ByteWrite( &ClustBuf[2], 2, NULL );  									/* 写入上级目录的起始簇号的高16位 */
		if ( s != USB_INT_SUCCESS ) 
		{
			return( s );
		}
		
		s = CH376ByteLocate( sizeof(FAT_DIR_INFO) + STRUCT_OFFSET( FAT_DIR_INFO, DIR_FstClusLO ) );  /* 移动文件指针 */
		if ( s != USB_INT_SUCCESS ) 
		{
			return( s );
		}
		
		s = CH376ByteWrite( ClustBuf, 2, NULL );  										/* 写入上级目录的起始簇号的低16位 */
		if ( s != USB_INT_SUCCESS ) 
		{
			return( s );
		}
		
		s = CH376ByteLocate( 0 );  														/* 移动文件指针,恢复到目录头位置 */
		if ( s != USB_INT_SUCCESS ) 
		{
			return( s );
		}
	}
	else 																				/* 不是多级目录 */
	{  
		if ( PathName[0] == DEF_SEPAR_CHAR1 || PathName[0] == DEF_SEPAR_CHAR2 ) 
		{
			return( CH376DirCreate( PathName ) );  										/* 在根目录下新建目录 */
		}
		else 
		{
			return( ERR_MISS_DIR );  													/* 必须提供完整路径才能实现在当前目录下新建目录 */
		}
	}
}
# endif

/*******************************************************************************
* 函  数  名      : CH376FileErase
* 描      述      : 删除文件,如果已经打开则直接删除,否则对于文件会先打开再删除,
*					支持多级目录路径.
* 输      入      : PUINT8 path:
*					指向路径缓冲区.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376FileErase( PUINT8 PathName )
{
	UINT8	s;

	if ( PathName ) 																	/* 文件尚未打开 */
	{  
		for ( s = 1; PathName[s] != DEF_SEPAR_CHAR1 && PathName[s] != DEF_SEPAR_CHAR2 && PathName[s] != 0; ++ s );  /* 搜索下一个路径分隔符或者路径结束符 */
		if ( PathName[s] ) 																/* 有路径分隔符,是多级目录下的文件或者目录 */
		{  
			s = CH376FileOpenPath( PathName );  										/* 打开多级目录下的文件或者目录 */
			if ( s != USB_INT_SUCCESS && s != ERR_OPEN_DIR ) 
			{
				return( s );  															/* 操作出错 */
			}
		}
		else 
		{
			CH376SetFileName( PathName );  												/* 没有路径分隔符,是根目录或者当前目录下的文件或者目录,设置将要操作的文件的文件名 */
		}
	}
	return( CH376SendCmdWaitInt( CMD0H_FILE_ERASE ) );
}

/*******************************************************************************
* 函  数  名      : CH376FileClose
* 描      述      : 关闭当前已经打开的文件或者目录(文件夹)
* 输      入      : PUINT8 UpdateSz:
*					是否更新文件长度.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376FileClose( UINT8 UpdateSz )
{
	return( CH376SendCmdDatWaitInt( CMD1H_FILE_CLOSE, UpdateSz ) );
}

/*******************************************************************************
* 函  数  名      : CH376DirInfoRead
* 描      述      : 读取当前文件的目录信息
* 输      入      : 无.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376DirInfoRead( void )
{
	return( CH376SendCmdDatWaitInt( CMD1H_DIR_INFO_READ, 0xFF ) );
}

/*******************************************************************************
* 函  数  名      : CH376DirInfoSave
* 描      述      : 保存文件的目录信息
* 输      入      : 无.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376DirInfoSave( void )
{
	return( CH376SendCmdWaitInt( CMD0H_DIR_INFO_SAVE ) );
}

/*******************************************************************************
* 函  数  名      : CH376ByteLocate
* 描      述      : 以字节为单位移动当前文件指针
* 输      入      : UINT32 offset:
*					指针偏移地址.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376ByteLocate( UINT32 offset )
{
	xWriteCH376Cmd( CMD4H_BYTE_LOCATE );
	xWriteCH376Data( (UINT8)offset );
	xWriteCH376Data( (UINT8)((UINT16)offset>>8) );
	xWriteCH376Data( (UINT8)(offset>>16) );
	xWriteCH376Data( (UINT8)(offset>>24) );
	xEndCH376Cmd( );
	return( Wait376Interrupt( ) );
}

/*******************************************************************************
* 函  数  名      : CH376ByteRead
* 描      述      : 以字节为单位从当前位置读取数据块
* 输      入      : PUINT8 buf:
*					指向数据缓冲区.
*                   UINT16 ReqCount:
*                   请求读取的字节数.
*                   PUINT16 RealCount:
*                   实际读取的字节数.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376ByteRead( PUINT8 buf, UINT16 ReqCount, PUINT16 RealCount )
{
	UINT8	s;
	
	xWriteCH376Cmd( CMD2H_BYTE_READ );
	xWriteCH376Data( (UINT8)ReqCount );
	xWriteCH376Data( (UINT8)(ReqCount>>8) );
	xEndCH376Cmd( );
	if ( RealCount ) 
	{
	    *RealCount = 0;
	}
	
	while ( 1 ) 
	{
		s = Wait376Interrupt( );
		if ( s == USB_INT_DISK_READ )                                                   /* 请求数据读出 */
		{
			s = CH376ReadBlock( buf );                                                  /* 从当前主机端点的接收缓冲区读取数据块,返回长度 */
			xWriteCH376Cmd( CMD0H_BYTE_RD_GO );                                         /* 继续读 */
			xEndCH376Cmd( );
			buf += s;
			if ( RealCount ) 
			{
			    *RealCount += s;
			}
		}
		else 
		{
		    return( s );                                                                /* 错误 */
		}
	}
}

/*******************************************************************************
* 函  数  名      : CH376ByteWrite
* 描      述      : 以字节为单位向当前位置写入数据块.
* 输      入      : PUINT8 buf:
*					指向外部缓冲区.
*                   UINT16 ReqCount:
*                   请求写入的字节数.
*                   PUINT16 RealCount:
*                   实际写入的字节数.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376ByteWrite( PUINT8 buf, UINT16 ReqCount, PUINT16 RealCount )
{
	UINT8	s;
	
	xWriteCH376Cmd( CMD2H_BYTE_WRITE );
	xWriteCH376Data( (UINT8)ReqCount );
	xWriteCH376Data( (UINT8)(ReqCount>>8) );
	xEndCH376Cmd( );
	if ( RealCount ) 
    {
        *RealCount = 0;
    }
	
	while ( 1 ) 
	{
		s = Wait376Interrupt( );
		if ( s == USB_INT_DISK_WRITE ) 
		{
			s = CH376WriteReqBlock( buf );                                              /* 向内部指定缓冲区写入请求的数据块,返回长度 */
			xWriteCH376Cmd( CMD0H_BYTE_WR_GO );
			xEndCH376Cmd( );
			buf += s;
			if ( RealCount ) *RealCount += s;
		}
		else 
	    {
	        return( s );                                                                /* 错误 */
	    }
	}
}

/*******************************************************************************
* 函  数  名      : CH376DiskCapacity
* 描      述      : 查询磁盘物理容量,扇区数.
* 输      入      : PUINT32 DiskCap:
*                   磁盘容量.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376DiskCapacity( PUINT32 DiskCap )
{
	UINT8	s;
	
	s = CH376SendCmdWaitInt( CMD0H_DISK_CAPACITY );
	if ( s == USB_INT_SUCCESS ) 
	{                                                                                   /* 参考CH376INC.H文件中CH376_CMD_DATA结构的DiskCapacity */
		xWriteCH376Cmd( CMD01_RD_USB_DATA0 );
		xReadCH376Data( );                                                              /* 长度总是sizeof(CH376_CMD_DATA.DiskCapacity) */
		*DiskCap = CH376Read32bitDat( );                                                /* CH376_CMD_DATA.DiskCapacity.mDiskSizeSec,从CH376芯片读取32位的数据并结束命令 */
	}
	else 
	{
	    *DiskCap = 0;
	}
	return( s );
}

/*******************************************************************************
* 函  数  名      : CH376DiskQuery
* 描      述      : 查询磁盘剩余空间信息,扇区数.
* 输      入      : PUINT32 DiskFre:
*                   当前逻辑盘的剩余扇区数.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8   CH376DiskQuery( PUINT32 DiskFre )
{
	UINT8	s;
	UINT8	c0, c1, c2, c3;
	
# ifndef	DEF_IC_V43_U
	xWriteCH376Cmd( CMD01_GET_IC_VER );                                                 /* 获取芯片及固件版本 */
	if ( xReadCH376Data( ) < 0x43 ) 
	{
		if ( CH376ReadVar8( VAR_DISK_STATUS ) >= DEF_DISK_READY )                       /* 获取主机文件模式下的磁盘及文件状态是否已经能够支持 */ 
	    {
	        CH376WriteVar8( VAR_DISK_STATUS, DEF_DISK_MOUNTED );
	    }
	}
# endif

	s = CH376SendCmdWaitInt( CMD0H_DISK_QUERY );
	if ( s == USB_INT_SUCCESS ) 
	{                                                                                   /* 参考CH376INC.H文件中CH376_CMD_DATA结构的DiskQuery */
		xWriteCH376Cmd( CMD01_RD_USB_DATA0 );
		xReadCH376Data( );                                                              /* 长度总是sizeof(CH376_CMD_DATA.DiskQuery) */
		xReadCH376Data( );                                                              /* CH376_CMD_DATA.DiskQuery.mTotalSector */
		xReadCH376Data( );
		xReadCH376Data( );
		xReadCH376Data( );
		c0 = xReadCH376Data( );                                                         /* CH376_CMD_DATA.DiskQuery.mFreeSector */
		c1 = xReadCH376Data( );
		c2 = xReadCH376Data( );
		c3 = xReadCH376Data( );
		*DiskFre = c0 | (UINT16)c1 << 8 | (UINT32)c2 << 16 | (UINT32)c3 << 24;
		xReadCH376Data( );                                                              /* CH376_CMD_DATA.DiskQuery.mDiskFat */		
		xEndCH376Cmd( );
	}
	else 
	{
	    *DiskFre = 0;
	}
	
	return( s );
}

/*******************************************************************************
* 函  数  名      : CH376SecLocate
* 描      述      : 以扇区为单位移动当前文件指针.
* 输      入      : UINT32 offset:
*                   要移动的扇区数.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376SecLocate( UINT32 offset )
{
	xWriteCH376Cmd( CMD4H_SEC_LOCATE );
	xWriteCH376Data( (UINT8)offset );
	xWriteCH376Data( (UINT8)((UINT16)offset>>8) );
	xWriteCH376Data( (UINT8)(offset>>16) );
	xWriteCH376Data( 0 );                                                               /* 超出最大文件尺寸 */
	xEndCH376Cmd( );
	return( Wait376Interrupt( ) );
}

/*******************************************************************************
* 函  数  名      : CH376DiskReadSec
* 描      述      : 从U盘读取多个扇区的数据块到缓冲区,不支持SD卡.
* 输      入      : PUINT8 buf:
*                   指向外部数据缓冲区.
*                   UINT32 iLbaStart:
*                   iLbaStart 是准备读取的线性起始扇区号.
*                   UINT8 iSectorCount:
*                   iSectorCount 是准备读取的扇区数                   
* 返      回      : 中断状态.
*******************************************************************************/
# ifdef	EN_SECTOR_ACCESS
UINT8	CH376DiskReadSec( PUINT8 buf, UINT32 iLbaStart, UINT8 iSectorCount )
{
	UINT8	s, err;
	UINT16	mBlockCount;
	
	for ( err = 0; err != 3; ++ err )                                                   /* 出错重试 */
	{  
		xWriteCH376Cmd( CMD5H_DISK_READ );                                              /* 从USB存储器读扇区 */
		xWriteCH376Data( (UINT8)iLbaStart );                                            /* LBA的最低8位 */
		xWriteCH376Data( (UINT8)( (UINT16)iLbaStart >> 8 ) );
		xWriteCH376Data( (UINT8)( iLbaStart >> 16 ) );
		xWriteCH376Data( (UINT8)( iLbaStart >> 24 ) );                                  /* LBA的最高8位 */
		xWriteCH376Data( iSectorCount );                                                /* 扇区数 */
        xEndCH376Cmd( );
		for ( mBlockCount = iSectorCount * DEF_SECTOR_SIZE / CH376_DAT_BLOCK_LEN; mBlockCount != 0; -- mBlockCount ) /* 数据块计数 */
		{  
			s = Wait376Interrupt( );                                                    /* 等待中断并获取状态 */
			if ( s == USB_INT_DISK_READ )                                               /* USB存储器读数据块,请求数据读出 */
			{  
				s = CH376ReadBlock( buf );                                              /* 从当前主机端点的接收缓冲区读取数据块,返回长度 */
				xWriteCH376Cmd( CMD0H_DISK_RD_GO );                                     /* 继续执行USB存储器的读操作 */
				xEndCH376Cmd( );
				buf += s;
			}
			else 
			{
			    break;                                                                  /* 返回错误状态 */
			}
		}
		
		if ( mBlockCount == 0 ) 
		{
			s = Wait376Interrupt( );                                                    /* 等待中断并获取状态 */
			if ( s == USB_INT_SUCCESS ) 
			{
			    return( USB_INT_SUCCESS );                                              /* 操作成功 */
			}
		}
		
		if ( s == USB_INT_DISCONNECT ) 
		{
		    return( s );                                                                /* U盘被移除 */
		}
		
		CH376DiskReqSense( );                                                           /* 检查USB存储器错误 */
	}
	
	return( s );                                                                        /* 操作失败 */
}

/*******************************************************************************
* 函  数  名      : CH376DiskWriteSec
* 描      述      : 将缓冲区中的多个扇区的数据块写入U盘,不支持SD卡.
* 输      入      : PUINT8 buf:
*                   指向外部数据缓冲区.
*                   UINT32 iLbaStart:
*                   写入的线起始性扇区号.
*                   UINT8 iSectorCount:
*                   写入的扇区数.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376DiskWriteSec( PUINT8 buf, UINT32 iLbaStart, UINT8 iSectorCount )
{
	UINT8	s, err;
	UINT16	mBlockCount;
	
	for ( err = 0; err != 3; ++ err )                                                   /* 出错重试 */
	{  
		xWriteCH376Cmd( CMD5H_DISK_WRITE );                                             /* 向USB存储器写扇区 */
		xWriteCH376Data( (UINT8)iLbaStart );                                            /* LBA的最低8位 */
		xWriteCH376Data( (UINT8)( (UINT16)iLbaStart >> 8 ) );
		xWriteCH376Data( (UINT8)( iLbaStart >> 16 ) );
		xWriteCH376Data( (UINT8)( iLbaStart >> 24 ) );                                  /* LBA的最高8位 */
		xWriteCH376Data( iSectorCount );                                                /* 扇区数 */
		xEndCH376Cmd( );
		for ( mBlockCount = iSectorCount * DEF_SECTOR_SIZE / CH376_DAT_BLOCK_LEN; mBlockCount != 0; -- mBlockCount ) /* 数据块计数 */
		{  
			s = Wait376Interrupt( );                                                    /* 等待中断并获取状态 */
			if ( s == USB_INT_DISK_WRITE )                                              /* USB存储器写数据块,请求数据写入 */    
			{  
				CH376WriteHostBlock( buf, CH376_DAT_BLOCK_LEN );                        /* 向USB主机端点的发送缓冲区写入数据块 */
				xWriteCH376Cmd( CMD0H_DISK_WR_GO );                                     /* 继续执行USB存储器的写操作 */
				xEndCH376Cmd( );
				buf += CH376_DAT_BLOCK_LEN;
			}
			else 
			{
			    break;                                                                  /* 返回错误状态 */
			}
		}
		
		if ( mBlockCount == 0 ) 
		{
			s = Wait376Interrupt( );                                                    /* 等待中断并获取状态 */
			if ( s == USB_INT_SUCCESS ) return( USB_INT_SUCCESS );                      /* 操作成功 */
		}
		
		if ( s == USB_INT_DISCONNECT ) 
		{
		    return( s );                                                                /* U盘被移除 */
		}
		
		CH376DiskReqSense( );                                                           /* 检查USB存储器错误 */
	}
	return( s );                                                                        /* 操作失败 */
}

/*******************************************************************************
* 函  数  名      : CH376SecRead
* 描      述      : 以扇区为单位从当前位置读取数据块,不支持SD卡.
* 输      入      : PUINT8 buf:
*                   指向外部数据缓冲区.
*                   UINT8 ReqCount:
*                   请求读出的扇区数.
*                   PUINT8 RealCount:
*                   实际读出的扇区数.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376SecRead( PUINT8 buf, UINT8 ReqCount, PUINT8 RealCount )
{
	UINT8	s;
	UINT8	cnt;
	UINT32	StaSec;
	
# ifndef	DEF_IC_V43_U
	UINT32	fsz, fofs;
# endif
	if ( RealCount ) 
    {
        *RealCount = 0;
    }
	
	do 
	{
# ifndef	DEF_IC_V43_U
		xWriteCH376Cmd( CMD01_GET_IC_VER );
		cnt = xReadCH376Data( );
		
		if ( cnt == 0x41 ) 
		{
			xWriteCH376Cmd( CMD14_READ_VAR32 );
			xWriteCH376Data( VAR_FILE_SIZE );
			xReadCH376Data( );
			fsz = xReadCH376Data( );
			fsz |= (UINT16)(xReadCH376Data( )) << 8;
			cnt = xReadCH376Data( );
			fsz |= (UINT32)cnt << 16;
			
			xWriteCH376Cmd( CMD14_READ_VAR32 );
			xWriteCH376Data( VAR_CURRENT_OFFSET );
			xReadCH376Data( );
			fofs = xReadCH376Data( );
			fofs |= (UINT16)(xReadCH376Data( )) << 8;
			fofs |= (UINT32)(xReadCH376Data( )) << 16;
			
			if ( fsz >= fofs + 510 ) 
			{
			    CH376WriteVar8( VAR_FILE_SIZE + 3, 0xFF );
			}
			else 
			{
			    cnt = 0xFF;
			}
		}
		else 
		{
		    cnt = 0xFF;
		}
# endif
		xWriteCH376Cmd( CMD1H_SEC_READ );
		xWriteCH376Data( ReqCount );
		xEndCH376Cmd( );
		s = Wait376Interrupt( );
# ifndef	DEF_IC_V43_U
		if ( cnt != 0xFF ) 
	    {
	        CH376WriteVar8( VAR_FILE_SIZE + 3, cnt );
	    }
# endif
		if ( s != USB_INT_SUCCESS ) 
		{
		    return( s );
		}
		
		xWriteCH376Cmd( CMD01_RD_USB_DATA0 );
		xReadCH376Data( );                                                              /* 长度总是sizeof(CH376_CMD_DATA.SectorRead) */
		cnt = xReadCH376Data( );                                                        /* CH376_CMD_DATA.SectorRead.mSectorCount */
		xReadCH376Data( );
		xReadCH376Data( );
		xReadCH376Data( );
		StaSec = CH376Read32bitDat( );                                                  /* CH376_CMD_DATA.SectorRead.mStartSector,从CH376芯片读取32位的数据并结束命令 */
		
		if ( cnt == 0 ) 
		{
		    break;
		}
		
		s = CH376DiskReadSec( buf, StaSec, cnt );                                       /* 从U盘读取多个扇区的数据块到缓冲区 */
		if ( s != USB_INT_SUCCESS ) 
		{
		    return( s );
		}
		
		buf += cnt * DEF_SECTOR_SIZE;
		if ( RealCount ) 
		{
		    *RealCount += cnt;
		}
		ReqCount -= cnt;
	
	} while ( ReqCount );
	
	return( s );
}

/*******************************************************************************
* 函  数  名      : CH376SecWrite
* 描      述      : 以扇区为单位在当前位置写入数据块,不支持SD卡.
* 输      入      : PUINT8 buf:
*                   指向外部数据缓冲区.
*                   UINT8 ReqCount:
*                   请求写入的扇区数.
*                   PUINT8 RealCount:
*                   实际写入的扇区数.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376SecWrite( PUINT8 buf, UINT8 ReqCount, PUINT8 RealCount )
{
	UINT8	s;
	UINT8	cnt;
	UINT32	StaSec;
	
	if ( RealCount ) 
	{
	    *RealCount = 0;
	}
	
	do 
	{
		xWriteCH376Cmd( CMD1H_SEC_WRITE );
		xWriteCH376Data( ReqCount );
		xEndCH376Cmd( );
		s = Wait376Interrupt( );
		if ( s != USB_INT_SUCCESS ) 
		{
		    return( s );
		}
		
		xWriteCH376Cmd( CMD01_RD_USB_DATA0 );
		xReadCH376Data( );                                                              /* 长度总是sizeof(CH376_CMD_DATA.SectorWrite) */
		cnt = xReadCH376Data( );                                                        /* CH376_CMD_DATA.SectorWrite.mSectorCount */
		xReadCH376Data( );
		xReadCH376Data( );
		xReadCH376Data( );
		StaSec = CH376Read32bitDat( );                                                  /* CH376_CMD_DATA.SectorWrite.mStartSector,从CH376芯片读取32位的数据并结束命令 */
		if ( cnt == 0 ) 
		{
		    break;
		}
		s = CH376DiskWriteSec( buf, StaSec, cnt );                                      /* 将缓冲区中的多个扇区的数据块写入U盘 */
		if ( s != USB_INT_SUCCESS ) 
		{
		    return( s );
		}
		
		buf += cnt * DEF_SECTOR_SIZE;
		if ( RealCount ) 
		{
		    *RealCount += cnt;
		}
	
		ReqCount -= cnt;
	
	} while ( ReqCount );
	return( s );
}

# endif

/*******************************************************************************
* 函  数  名      : CH376LongNameWrite
* 描      述      : 长文件名专用的字节写子程序.
* 输      入      : PUINT8 buf:
*                   指向外部数据缓冲区.
*                   UINT16 ReqCount:
*                   请求写入的字节数.
* 返      回      : 中断状态.
*******************************************************************************/
# ifdef	EN_LONG_NAME
UINT8	CH376LongNameWrite( PUINT8 buf, UINT16 ReqCount )
{
	UINT8	s;
# ifndef	DEF_IC_V43_U
	UINT8	c;
	
	c = CH376ReadVar8( VAR_DISK_STATUS );
	if ( c == DEF_DISK_OPEN_ROOT ) 
	{
	    CH376WriteVar8( VAR_DISK_STATUS, DEF_DISK_OPEN_DIR );
	}
# endif
	xWriteCH376Cmd( CMD2H_BYTE_WRITE );
	xWriteCH376Data( (UINT8)ReqCount );
	xWriteCH376Data( (UINT8)(ReqCount>>8) );
	xEndCH376Cmd( );
	while ( 1 ) 
	{
		s = Wait376Interrupt( );
		if ( s == USB_INT_DISK_WRITE ) 
		{
			if ( buf ) 
			{
			    buf += CH376WriteReqBlock( buf );                                       /* 向内部指定缓冲区写入请求的数据块,返回长度 */
			}
			else 
			{
				xWriteCH376Cmd( CMD01_WR_REQ_DATA );                                    /* 向内部指定缓冲区写入请求的数据块 */
				s = xReadCH376Data( );                                                  /* 长度 */
				while ( s -- ) 
				{
				    xWriteCH376Data( 0 );                                               /* 填充0 */
				}
			}
			
			xWriteCH376Cmd( CMD0H_BYTE_WR_GO );
			xEndCH376Cmd( );			
		}
		else 
		{
# ifndef	DEF_IC_V43_U
			if ( c == DEF_DISK_OPEN_ROOT ) 
			{
			    CH376WriteVar8( VAR_DISK_STATUS, c );
			}
# endif
			return( s );                                                                /* 错误 */
		}
	}
}

/*******************************************************************************
* 函  数  名      : CH376CheckNameSum
* 描      述      : 计算长文件名的短文件名检验和,输入为无小数点分隔符的固定11字
*                   节格式.
* 输      入      : PUINT8 DirName:
*                   指向外部文件名缓冲区.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376CheckNameSum( PUINT8 DirName )
{
	UINT8	NameLen;
	UINT8	CheckSum;
	
	CheckSum = 0;
	for ( NameLen = 0; NameLen != 11; NameLen ++ ) 
	{
	    CheckSum = ( CheckSum & 1 ? 0x80 : 0x00 ) + ( CheckSum >> 1 ) + *DirName++;
	}
	return( CheckSum );
}

/*******************************************************************************
* 函  数  名      : CH376LocateInUpDir
* 描      述      : 在上级目录(文件夹)中移动文件指针到当前文件目录信息所在的扇区.
*                   另外,顺便将当前文件目录信息所在的扇区的前一个扇区的LBA地址写
*                   入CH376内部VAR_FAT_DIR_LBA变量(为了方便收集长文件名时向前搜索
*                   ,否则要多移动一次.
*                   使用了全局缓冲区GlobalBuf的前12个字节.
* 输      入      : PUINT8 PathName:
*                   指向路径缓冲区.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376LocateInUpDir( PUINT8 PathName ) 
{
	UINT8	s;
	
	xWriteCH376Cmd( CMD14_READ_VAR32 );
	xWriteCH376Data( VAR_FAT_DIR_LBA );                                                 /* 当前文件目录信息所在的扇区LBA地址 */
	for ( s = 4; s != 8; s ++ ) 
	{
	    GlobalBuf[ s ] = xReadCH376Data( );                                             /* 临时保存于全局缓冲区中,节约RAM */
	}
	
	xEndCH376Cmd( );
	
	s = CH376SeparatePath( PathName );                                                  /* 从路径中分离出最后一级文件名或者目录名,返回最后一级文件名或者目录名的偏移 */
	if ( s ) 
	{
	    s = CH376FileOpenDir( PathName, s );                                            /* 是多级目录,打开多级目录下的最后一级目录,即打开文件的上级目录 */
	}
	else 
	{
	    s = CH376FileOpen( "/" );                                                       /* 根目录下的文件,则打开根目录 */
	}
	
	if ( s != ERR_OPEN_DIR ) 
	{
	    return( s );
	}
	*(PUINT32)( &GlobalBuf[0] ) = 0;                                                    /* 目录扇区偏移扇区数,保存在全局缓冲区中,节约RAM */
	
	while ( 1 )                                                                         /* 不断移动文件指针,直到与当前文件目录信息所在的扇区LBA地址匹配 */
	{  
		s = CH376SecLocate( *(PUINT32)(&GlobalBuf[0]) );                                /* 以扇区为单位在上级目录中移动文件指针 */
		if ( s != USB_INT_SUCCESS ) 
		{
		    return( s );
		}
		CH376ReadBlock( &GlobalBuf[8] );                                                /* 从内存缓冲区读取CH376_CMD_DATA.SectorLocate.mSectorLba数据块,返回长度总是sizeof(CH376_CMD_DATA.SectorLocate) */
		if ( *(PUINT32)(&GlobalBuf[8]) == *(PUINT32)(&GlobalBuf[4]) ) 
		{
		    return( USB_INT_SUCCESS );                                                  /* 已到当前文件目录信息扇区 */
		}
		xWriteCH376Cmd( CMD50_WRITE_VAR32 );
		xWriteCH376Data( VAR_FAT_DIR_LBA );                                             /* 得到前一个扇区,设置为新的文件目录信息扇区LBA地址 */
		for ( s = 8; s != 12; s ++ ) 
		{
		    xWriteCH376Data( GlobalBuf[ s ] );
		}
		xEndCH376Cmd( );
		++ *(PUINT32)(&GlobalBuf[0]);
	}
}

/*******************************************************************************
* 函  数  名      : CH376CheckNameSum
* 描      述      : 由短文件名或者目录(文件夹)名获得相应的长文件名.
*                   需要输入短文件名的完整路径PathName,需要提供缓冲区接收长文件
*                   名LongName(以UNICODE小端编码,以双0结束),
*                   使用了全局缓冲区GlobalBuf的前34个字节,
*                   sizeof(GlobalBuf) >= sizeof(FAT_DIR_INFO)+2
* 输      入      : PUINT8 PathName:
*                   短文件名的完整路径.
*                   PUINT8 LongName:
*                   指向长文件名接收缓冲区.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376GetLongName( PUINT8 PathName, PUINT8 LongName ) 
{
	UINT8	s;
	UINT16	NameCount;	                                                                /* 长文件名字节计数 */
	
	s = CH376FileOpenPath( PathName );                                                  /* 打开多级目录下的文件或者目录 */
	if ( s != USB_INT_SUCCESS && s != ERR_OPEN_DIR ) 
	{
	    return( s );
	}
	
	s = CH376DirInfoRead( );                                                            /* 读取当前文件的目录信息FAT_DIR_INFO,将相关数据调到内存中 */
	if ( s != USB_INT_SUCCESS ) 
	{
	    return( s );
	}
	
	CH376ReadBlock( GlobalBuf );                                                        /* 从内存缓冲区读取FAT_DIR_INFO数据块,返回长度总是sizeof(FAT_DIR_INFO) */
	CH376EndDirInfo( );                                                                 /* 获取完FAT_DIR_INFO结构 */
	GlobalBuf[32] = CH376CheckNameSum( GlobalBuf );                                     /* 计算长文件名的短文件名检验和,保存在全局缓冲区中,节约RAM */
	GlobalBuf[33] = CH376ReadVar8( VAR_FILE_DIR_INDEX );                                /* 当前文件目录信息在扇区内的索引号,保存在全局缓冲区中,节约RAM */
	NameCount = 0;
	while ( 1 ) 
	{
		if ( GlobalBuf[33] == 0 )                                                       /* 当前的文件目录信息扇区处理结束,转到前一个扇区 */
		{  
			s = CH376LocateInUpDir( PathName );                                         /* 在上级目录中移动文件指针到当前文件目录信息所在的扇区 */
			if ( s != USB_INT_SUCCESS ) 
			{
			    break;
			}
			
			if ( CH376ReadVar32( VAR_CURRENT_OFFSET ) == 0 )                            /* 当前已经处于目录扇区的开始,无法获取长文件名 */
			{  
				s = ERR_LONG_NAME_ERR;
				break;
			}
			GlobalBuf[33] = DEF_SECTOR_SIZE / sizeof( FAT_DIR_INFO );                   /* 指向前一个扇区的最后一个文件目录信息 */
		}
		
		GlobalBuf[33] --;                                                               /* 从后向前搜索文件目录信息 */
		s = CH376SendCmdDatWaitInt( CMD1H_DIR_INFO_READ, GlobalBuf[33] );               /* 读取指定的目录信息FAT_DIR_INFO,将相关数据调到内存中 */
		if ( s != USB_INT_SUCCESS ) 
		{
		    break;
		}
		
		CH376ReadBlock( GlobalBuf );                                                    /* 从内存缓冲区读取FAT_DIR_INFO数据块,返回长度总是sizeof(FAT_DIR_INFO) */
		CH376EndDirInfo( );                                                             /* 获取完FAT_DIR_INFO结构 */
		if ( ( GlobalBuf[11] & ATTR_LONG_NAME_MASK ) != ATTR_LONG_NAME || GlobalBuf[13] != GlobalBuf[32] ) /* 类型错误或者校验和错误 */
		{  
			s = ERR_LONG_NAME_ERR;
			break;                                                                      /* 没有直接返回是因为如果是打开了根目录那么必须要关闭后才能返回 */
		}
		
		for ( s = 1; s < sizeof( FAT_DIR_INFO ); s += 2 )                               /* 收集长文件名,长文件名的字符在磁盘上UNICODE用小端方式存放 */
		{  
			if ( s == 1 + 5 * 2 ) 
			{
			    s = 14;                                                                 /* 从长文件名的第一组1-5个字符跳到第二组6-11个字符 */
			}
			else if ( s == 14 + 6 * 2 ) 
			{
			    s = 28;                                                                 /* 从长文件名的第二组6-11个字符跳到第三组12-13个字符 */
			}
			
			LongName[ NameCount++ ] = GlobalBuf[ s ];
			LongName[ NameCount++ ] = GlobalBuf[ s + 1 ];
			if ( GlobalBuf[ s ] == 0 && GlobalBuf[ s + 1 ] == 0 ) 
			{
			    break;                                                                  /* 长文件名结束 */
			}
			
			if ( NameCount >= LONG_NAME_BUF_LEN )                                       /* 长文件名缓冲区溢出 */
			{  
				s = ERR_LONG_BUF_OVER;
				goto CH376GetLongNameE;
			}
		}
		
		if ( GlobalBuf[0] & 0x40 )                                                      /* 长文件名目录信息块结束 */
		{  
			if ( s >= sizeof( FAT_DIR_INFO ) ) *(PUINT16)( &LongName[ NameCount ] ) = 0x0000;  /* 尚未收集到长文件名的结束符,则强制结束 */
			s = USB_INT_SUCCESS;                                                        /* 成功完成长文件名收集完成 */
			break;
		}
	}
	
CH376GetLongNameE:
	CH376FileClose( FALSE );                                                            /* 对于根目录则必须要关闭 */
	return( s );
}

/*******************************************************************************
* 函  数  名      : CH376CreateLongName
* 描      述      : 新建具有长文件名的文件,关闭文件后返回,LongName输入路径必须
*                   在RAM中.
*                   需要输入短文件名的完整路径PathName(请事先参考FAT规范由长文
*                   件名自行产生),需要输入以UNICODE小端编码的以双0结束的长文件
*                   名LongName.
*                   使用了全局缓冲区GlobalBuf的前64个字节,
*                   sizeof(GlobalBuf)>=sizeof(FAT_DIR_INFO)*2
* 输      入      : PUINT8 PathName:
*                   短文件名的完整路径.
*                   PUINT8 LongName:
*                   指向长文件名缓冲区.
* 返      回      : 中断状态.
*******************************************************************************/
UINT8	CH376CreateLongName( PUINT8 PathName, PUINT8 LongName )
{
	UINT8	s, i;
	UINT8	DirBlockCnt;	                                                            /* 长文件名占用文件目录结构的个数 */
	UINT16	count;			                                                            /* 临时变量,用于计数,用于字节读文件方式下实际读取的字节数 */
	UINT16	NameCount;		                                                            /* 长文件名字节计数 */
	UINT32	NewFileLoc;		                                                            /* 当前文件目录信息在上级目录中的起始位置,偏移地址 */
	
	for ( count = 0; count < LONG_NAME_BUF_LEN; count += 2 ) 
	{
	    if ( *(PUINT16)(&LongName[count]) == 0 ) 
	    {
	        break;                                                                      /* 到结束位置 */
	    }
	}
	
	if ( count == 0 || count >= LONG_NAME_BUF_LEN || count > LONE_NAME_MAX_CHAR ) 
	{
	    return( ERR_LONG_NAME_ERR );                                                    /* 长文件名无效 */
	}
	
	DirBlockCnt = count / LONG_NAME_PER_DIR;                                            /* 长文件名占用文件目录结构的个数 */
	i = count - DirBlockCnt * LONG_NAME_PER_DIR;
	
	if ( i )                                                                            /* 有零头 */
	{
		if ( ++ DirBlockCnt * LONG_NAME_PER_DIR > LONG_NAME_BUF_LEN ) 
		{
		    return( ERR_LONG_BUF_OVER );                                                /* 缓冲区溢出 */
		}
		count += 2;                                                                     /* 加上0结束符后的长度 */
		i += 2;
		
		if ( i < LONG_NAME_PER_DIR )                                                    /* 最末的文件目录结构不满 */
		{  
			while ( i++ < LONG_NAME_PER_DIR ) 
			{
			    LongName[count++] = 0xFF;                                               /* 把剩余数据填为0xFF */
			}
		}
	}
	
	s = CH376FileOpenPath( PathName );                                                  /* 打开多级目录下的文件 */
	if ( s == USB_INT_SUCCESS )                                                         /* 短文件名存在则返回错误 */
	{   
		s = ERR_NAME_EXIST;
		goto CH376CreateLongNameE;
	}
	
	if ( s != ERR_MISS_FILE ) 
	{
	    return( s );
	}
	
	s = CH376FileCreatePath( PathName );                                                /* 新建多级目录下的文件 */
	if ( s != USB_INT_SUCCESS ) 
	{
	    return( s );
	}
	
	i = CH376ReadVar8( VAR_FILE_DIR_INDEX );                                            /* 临时用于保存当前文件目录信息在扇区内的索引号 */
	s = CH376LocateInUpDir( PathName );                                                 /* 在上级目录中移动文件指针到当前文件目录信息所在的扇区 */
	if ( s != USB_INT_SUCCESS ) 
	{
	    goto CH376CreateLongNameE;                                                      /* 没有直接返回是因为如果是打开了根目录那么必须要关闭后才能返回 */
	}
	
	NewFileLoc = CH376ReadVar32( VAR_CURRENT_OFFSET ) + i * sizeof(FAT_DIR_INFO);       /* 计算当前文件目录信息在上级目录中的起始位置,偏移地址 */
	s = CH376ByteLocate( NewFileLoc );                                                  /* 在上级目录中移动文件指针到当前文件目录信息的位置 */
	if ( s != USB_INT_SUCCESS ) 
	{
	    goto CH376CreateLongNameE;
	}
	
	s = CH376ByteRead( &GlobalBuf[ sizeof(FAT_DIR_INFO) ], sizeof(FAT_DIR_INFO), NULL );/* 以字节为单位读取数据,获得当前文件的目录信息FAT_DIR_INFO */
	if ( s != USB_INT_SUCCESS ) 
	{
	    goto CH376CreateLongNameE;
	}
	
	for ( i = DirBlockCnt; i != 0; -- i )                                               /* 搜索空闲的文件目录结构用于存放长文件名 */
	{  
		s = CH376ByteRead( GlobalBuf, sizeof(FAT_DIR_INFO), &count );                   /* 以字节为单位读取数据,获得下一个文件目录信息FAT_DIR_INFO */
		if ( s != USB_INT_SUCCESS ) 
		{
		    goto CH376CreateLongNameE;
		}
		
		if ( count == 0 ) 
		{
		    break;                                                                      /* 无法读出数据,上级目录结束了 */
		}
		
		if ( GlobalBuf[0] && GlobalBuf[0] != 0xE5 )                                     /* 后面有正在使用的文件目录结构,由于长文件名必须连接存放,所以空间不够,必须放弃当前位置并向后转移 */
		{  
			s = CH376ByteLocate( NewFileLoc );                                          /* 在上级目录中移动文件指针到当前文件目录信息的位置 */
			if ( s != USB_INT_SUCCESS ) 
			{
			    goto CH376CreateLongNameE;
			}
			
			GlobalBuf[ 0 ] = 0xE5;                                                      /* 文件删除标志 */
			for ( s = 1; s != sizeof(FAT_DIR_INFO); s ++ ) 
			{
			    GlobalBuf[ s ] = GlobalBuf[ sizeof(FAT_DIR_INFO) + s ];
			}
			
			s = CH376LongNameWrite( GlobalBuf, sizeof(FAT_DIR_INFO) );                  /* 写入一个文件目录结构,用于删除之前新建的文件,实际上稍后会将之转移到目录的最末位置 */
			if ( s != USB_INT_SUCCESS ) 
			{
			    goto CH376CreateLongNameE;
			}
			
			do                                                                          /* 向后搜索空闲的文件目录结构 */s     
			{  
				s = CH376ByteRead( GlobalBuf, sizeof(FAT_DIR_INFO), &count );           /* 以字节为单位读取数据,获得下一个文件目录信息FAT_DIR_INFO */
				if ( s != USB_INT_SUCCESS ) 
				{
				    goto CH376CreateLongNameE;
				}
			} while ( count && GlobalBuf[0] );                                          /* 如果仍然是正在使用的文件目录结构则继续向后搜索,直到上级目录结束或者有尚未使用过的文件目录结构 */
			
			NewFileLoc = CH376ReadVar32( VAR_CURRENT_OFFSET );                          /* 用上级目录的当前文件指针作为当前文件目录信息在上级目录中的起始位置 */
			i = DirBlockCnt + 1;                                                        /* 需要的空闲的文件目录结构的个数,包括短文件名本身一个和长文件名 */
			
			if ( count == 0 ) 
			{
			    break;                                                                  /* 无法读出数据,上级目录结束了 */
			}
			
			NewFileLoc -= sizeof(FAT_DIR_INFO);                                         /* 倒回到刚才搜索到的空闲的文件目录结构的起始位置 */
		}
	}
	
	if ( i )                                                                            /* 空闲的文件目录结构不足以存放长文件名,原因是上级目录结束了,下面增加上级目录的长度 */
	{  
		s = CH376ReadVar8( VAR_SEC_PER_CLUS );                                          /* 每簇扇区数 */
		if ( s == 128 )                                                                 /* FAT12/FAT16的根目录,容量是固定的,无法增加文件目录结构 */    
	    {  
			s = ERR_FDT_OVER;                                                           /* FAT12/FAT16根目录下的文件数应该少于512个,需要磁盘整理 */
			goto CH376CreateLongNameE;
		}
		
		count = s * DEF_SECTOR_SIZE;                                                    /* 每簇字节数 */
		if ( count < i * sizeof(FAT_DIR_INFO) ) 
		{
		    count <<= 1;                                                                /* 一簇不够则增加一簇,这种情况只会发生于每簇为512字节的情况下 */
		}
		
		s = CH376LongNameWrite( NULL, count );                                          /* 以字节为单位向当前位置写入全0数据块,清空新增加的文件目录簇 */
		if ( s != USB_INT_SUCCESS ) 
		{
		    goto CH376CreateLongNameE;
		}
	}
	
	s = CH376ByteLocate( NewFileLoc );                                                  /* 在上级目录中移动文件指针到当前文件目录信息的位置 */
	if ( s != USB_INT_SUCCESS ) 
	{
	    goto CH376CreateLongNameE;
	}
	
	GlobalBuf[11] = ATTR_LONG_NAME;
	GlobalBuf[12] = 0x00;
	GlobalBuf[13] = CH376CheckNameSum( &GlobalBuf[ sizeof(FAT_DIR_INFO) ] );            /* 计算长文件名的短文件名检验和 */
	GlobalBuf[26] = 0x00;
	GlobalBuf[27] = 0x00;
	
	for ( s = 0; DirBlockCnt != 0; )                                                    /* 长文件名占用的文件目录结构计数 */
	{  
		GlobalBuf[0] = s ? DirBlockCnt : DirBlockCnt | 0x40;                            /* 首次要置长文件名入口标志 */
		DirBlockCnt --;
		NameCount = DirBlockCnt * LONG_NAME_PER_DIR;
		
		for ( s = 1; s < sizeof( FAT_DIR_INFO ); s += 2 )                               /* 输出长文件名,长文件名的字符在磁盘上UNICODE用小端方式存放 */
		{  
			if ( s == 1 + 5 * 2 ) 
			{
			    s = 14;                                                                 /* 从长文件名的第一组1-5个字符跳到第二组6-11个字符 */
			}
			else if ( s == 14 + 6 * 2 ) 
			{
			    s = 28;                                                                 /* 从长文件名的第二组6-11个字符跳到第三组12-13个字符 */
			}
			GlobalBuf[ s ] = LongName[ NameCount++ ];
			GlobalBuf[ s + 1 ] = LongName[ NameCount++ ];
		}
		s = CH376LongNameWrite( GlobalBuf, sizeof(FAT_DIR_INFO) );                      /* 以字节为单位写入一个文件目录结构,长文件名 */
		if ( s != USB_INT_SUCCESS ) 
		{
		    goto CH376CreateLongNameE;
		}
	}
	
	s = CH376LongNameWrite( &GlobalBuf[ sizeof(FAT_DIR_INFO) ], sizeof(FAT_DIR_INFO) ); /* 以字节为单位写入一个文件目录结构,这是转移来的之前新建的文件的目录信息 */

CH376CreateLongNameE:
	CH376FileClose( FALSE );                                                            /* 对于根目录则必须要关闭 */
	return( s );
}
# endif
/************************************ End *************************************/

实验现象

文件系统程序分析

文件与 25 相同;只需改一下 main.c 文件(这里用到了上面的 filesys.h,filesys.c

main.c

  • 这里用到了 sprintf 函数(回顾C语言)

    int sprintf(char *str, const char *format, ...)
    //str -- 这是指向一个字符数组的指针,该数组存储了 C 字符串。
    //format -- 这是字符串,包含了要被写入到字符串 str 的文本
/*********************************************************************************************
程序名:	U盘读写文件程序
硬件支持:	STM32F103C8   外部晶振8MHz RCC函数设置主频72MHz   					
说明:
 # 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
 # 可根据自己的需要增加或删减。

*********************************************************************************************/
# include <string.h>
# include <stdio.h>
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "touch_key.h"
# include "relay.h"
# include "oled0561.h"

# include "spi.h"
# include "ch376.h"
# include"filesys.h"

u8 buf[128]; 

int main (void){//主程序
	u8 s,i;
	delay_ms(500); //上电时等待其他器件就绪
	RCC_Configuration(); //系统时钟初始化 
	TOUCH_KEY_Init();//触摸按键初始化
	RELAY_Init();//继电器初始化

	I2C_Configuration();//I2C初始化
	OLED0561_Init(); //OLED初始化
	OLED_DISPLAY_8x16_BUFFER(0,"   YoungTalk    "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(2,"  U DISK TEST   "); //显示字符串
	//CH376初始化	
	SPI2_Init();//SPI接口初始化
	if(mInitCH376Host()== USB_INT_SUCCESS){//CH376初始化
		OLED_DISPLAY_8x16_BUFFER(4,"   CH376 OK!    "); //显示字符串
	}
	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); //每次操作后必要的延时
	}
}

阵列键盘原理与驱动

本节用到的固件库函数

  • GPIO_ReadInputData(手册 10.2.6

文件复制 25 的,只需增加 KEYPAD4x4.h,KEYPAD4x4.c 即可

阵列键盘占据8个 I/O 口:PA0~PA7

KEYPAD4x4.h

# ifndef __KEYPAD4x4_H
# define __KEYPAD4x4_H	 
# include "sys.h"
# include "delay.h"


# define KEYPAD4x4PORT	GPIOA	//定义IO接口组
# define KEY1	GPIO_Pin_0	//定义IO接口
# define KEY2	GPIO_Pin_1	//定义IO接口
# define KEY3	GPIO_Pin_2	//定义IO接口
# define KEY4	GPIO_Pin_3	//定义IO接口
# define KEYa	GPIO_Pin_4	//定义IO接口
# define KEYb	GPIO_Pin_5	//定义IO接口
# define KEYc	GPIO_Pin_6	//定义IO接口
# define KEYd	GPIO_Pin_7	//定义IO接口


void KEYPAD4x4_Init(void);//初始化
void KEYPAD4x4_Init2(void);//初始化2(用于IO工作方式反转)
u8 KEYPAD4x4_Read (void);//读阵列键盘
		 				    
# endif
  • a = GPIO_ReadInputData(KEYPAD4x4PORT) & 0xff; 因为函数返回的是32位但是我们只需要判断PA0~PA7即可也就是8位,所以&0xff
  • 关于switch里对应的值可以通过图片算出来,比如 0xee左边的e表示高四位最低位为0,右边的e表示低四位最低位为0,别的按键以此类推。

KEYPAD4x4.c

# include "KEYPAD4x4.h"

void KEYPAD4x4_Init(void)  //微动开关的接口初始化
{
    GPIO_InitTypeDef  GPIO_InitStructure; //定义GPIO的初始化枚举结构
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitStructure.GPIO_Pin = KEYa | KEYb | KEYc | KEYd; //选择端口号(0~15或all)
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //选择IO接口工作方式 //上拉电阻
    GPIO_Init(KEYPAD4x4PORT, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = KEY1 | KEY2 | KEY3 | KEY4; //选择端口号(0~15或all)
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式 //推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)
    GPIO_Init(KEYPAD4x4PORT, &GPIO_InitStructure);

}
void KEYPAD4x4_Init2(void)  //微动开关的接口初始化2(用于IO工作方式反转)
{
    GPIO_InitTypeDef  GPIO_InitStructure; //定义GPIO的初始化枚举结构
    GPIO_InitStructure.GPIO_Pin = KEY1 | KEY2 | KEY3 | KEY4; //选择端口号(0~15或all)
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //选择IO接口工作方式 //上拉电阻
    GPIO_Init(KEYPAD4x4PORT, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = KEYa | KEYb | KEYc | KEYd; //选择端口号(0~15或all)
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式 //推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)
    GPIO_Init(KEYPAD4x4PORT, &GPIO_InitStructure);

}
u8 KEYPAD4x4_Read (void) //键盘处理函数
{
    u8 a = 0, b = 0; //定义变量
    KEYPAD4x4_Init();//初始化IO
    GPIO_ResetBits(KEYPAD4x4PORT, KEY1 | KEY2 | KEY3 | KEY4);//低电平
    GPIO_SetBits(KEYPAD4x4PORT, KEYa | KEYb | KEYc | KEYd);//高电平
    if(!GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEYa) ||  	//查寻键盘口的值是否变化
            !GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEYb) ||
            !GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEYc) ||
            !GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEYd))
    {
        delay_ms (20);//延时20毫秒
        if(!GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEYa) ||  	//查寻键盘口的值是否变化
                !GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEYb) ||
                !GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEYc) ||
                !GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEYd))
        {
            a = GPIO_ReadInputData(KEYPAD4x4PORT) & 0xff; //键值放入寄存器a
        }
        KEYPAD4x4_Init2();//IO工作方式反转
        GPIO_SetBits(KEYPAD4x4PORT, KEY1 | KEY2 | KEY3 | KEY4);
        GPIO_ResetBits(KEYPAD4x4PORT, KEYa | KEYb | KEYc | KEYd);
        delay_ms(10);//必要的延时,以保证读取的电平稳定
        b = GPIO_ReadInputData(KEYPAD4x4PORT) & 0xff; //将第二次取得值放入寄存器b
        a = a | b; //将两个数据相或
        switch(a) //对比数据值
        {
        case 0xee:
            b = 16;
            break;//对比得到的键值给b一个应用数据
        case 0xed:
            b = 15;
            break;
        case 0xeb:
            b = 14;
            break;
        case 0xe7:
            b = 13;
            break;
        case 0xde:
            b = 12;
            break;
        case 0xdd:
            b = 11;
            break;
        case 0xdb:
            b = 10;
            break;
        case 0xd7:
            b = 9;
            break;
        case 0xbe:
            b = 8;
            break;
        case 0xbd:
            b = 7;
            break;
        case 0xbb:
            b = 6;
            break;
        case 0xb7:
            b = 5;
            break;
        case 0x7e:
            b = 4;
            break;
        case 0x7d:
            b = 3;
            break;
        case 0x7b:
            b = 2;
            break;
        case 0x77:
            b = 1;
            break;
        default:
            b = 0;
            break;//键值错误处理
        }
        while(!GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEY1) ||  	//等待按键放开
                !GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEY2) ||
                !GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEY3) ||
                !GPIO_ReadInputDataBit(KEYPAD4x4PORT, KEY4));
        delay_ms (20);//延时20毫秒
    }
    return (b);//将b作为返回值
}

main.c

# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "relay.h"
# include "oled0561.h"

# include "KEYPAD4x4.h"


int main (void){//主程序
	u8 s;
	delay_ms(500); //上电时等待其他器件就绪
	RCC_Configuration(); //系统时钟初始化 
	RELAY_Init();//继电器初始化

	I2C_Configuration();//I2C初始化
	OLED0561_Init(); //OLED初始化
	OLED_DISPLAY_8x16_BUFFER(0,"   YoungTalk    "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(3," KEYPAD4x4 TEST "); //显示字符串

	KEYPAD4x4_Init();//阵列键盘初始化

	while(1){

		s=KEYPAD4x4_Read();//读出按键值

		if(s!=0){ //如按键值不是0,也就是说有按键操作,则判断为真
			//-------------------------"----------------"
			OLED_DISPLAY_8x16_BUFFER(6," KEY NO.        "); //显示字符串
			OLED_DISPLAY_8x16(6,8*8,s/10+0x30);//
			OLED_DISPLAY_8x16(6,9*8,s%10+0x30);//
		}
	}
}

实验现象

外部中断原理与驱动

本节用到的固件库函数

  • GPIO_EXTILineConfig(手册 10.2.17
  • EXTI_Init(手册 8.2.2
  • EXTI_GetITStatus(手册 8.2.7
  • EXTI_ClearITPendingBit(手册 8.2.8

文件复制 26 即可,只需添加 NVIC.h,NVIC.cLIb 文件需要增加 stm32f10x_exti.c

NVIC.h

# ifndef __NVIC_H
# define __NVIC_H	 
# include "sys.h"


extern u8 INT_MARK;//中断标志位


void KEYPAD4x4_INT_INIT (void);

# endif
  • RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); :这条函数本来在 KEYPAD4x4.c 里的现在需要移到这,因为不能重复
  • 外部中断函数名字是固定不能改的具体看上面图片中断处理函数
  • 不使用响应优先级,默认为0
  • EXTI_GetFlagStatus EXTI_GetITStatus 两个函数功能差不多都是判断是否发生中断,前者是检查中断标志的,后者是检查中断状态的(详情百度)

NVIC.c

# include "NVIC.h"

u8 INT_MARK;//中断标志位

void KEYPAD4x4_INT_INIT (void) 	 //按键中断初始化
{
    NVIC_InitTypeDef  NVIC_InitStruct;	//定义结构体变量
    EXTI_InitTypeDef  EXTI_InitStruct;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //启动GPIO时钟 (需要与复用时钟一同启动)
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO , ENABLE);//配置端口中断需要启用复用时钟

    //第1个中断
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource4);  //定义 GPIO  中断(PA4)

    EXTI_InitStruct.EXTI_Line = EXTI_Line4; //定义中断线
    EXTI_InitStruct.EXTI_LineCmd = ENABLE;            //中断使能
    EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;   //中断模式为 中断
    EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发

    EXTI_Init(& EXTI_InitStruct);

    NVIC_InitStruct.NVIC_IRQChannel = EXTI4_IRQn; //中断线
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //使能中断
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级 2
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;   //子优先级  2
    NVIC_Init(& NVIC_InitStruct);

    //第2个中断
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource5);  //定义  GPIO 中断

    EXTI_InitStruct.EXTI_Line = EXTI_Line5; //定义中断线
    EXTI_InitStruct.EXTI_LineCmd = ENABLE;            //中断使能
    EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;   //中断模式为 中断
    EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发

    EXTI_Init(& EXTI_InitStruct);

    NVIC_InitStruct.NVIC_IRQChannel = EXTI9_5_IRQn; //中断线
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //使能中断
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级 2
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;   //子优先级  2
    NVIC_Init(& NVIC_InitStruct);

    //第3个中断
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource6);  //定义  GPIO 中断

    EXTI_InitStruct.EXTI_Line = EXTI_Line6; //定义中断线
    EXTI_InitStruct.EXTI_LineCmd = ENABLE;            //中断使能
    EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;   //中断模式为 中断
    EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发

    EXTI_Init(& EXTI_InitStruct);

    NVIC_InitStruct.NVIC_IRQChannel = EXTI9_5_IRQn; //中断线
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //使能中断
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级 2
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;   //子优先级  2
    NVIC_Init(& NVIC_InitStruct);

    //第4个中断
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource7);  //定义  GPIO 中断

    EXTI_InitStruct.EXTI_Line = EXTI_Line7; //定义中断线
    EXTI_InitStruct.EXTI_LineCmd = ENABLE;            //中断使能
    EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;   //中断模式为 中断
    EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发

    EXTI_Init(& EXTI_InitStruct);

    NVIC_InitStruct.NVIC_IRQChannel = EXTI9_5_IRQn; //中断线
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //使能中断
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级 2
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;   //子优先级  2
    NVIC_Init(& NVIC_InitStruct);

}

void  EXTI4_IRQHandler(void)
{
    if(EXTI_GetITStatus(EXTI_Line4) != RESET) //判断某个线上的中断是否发生
    {
        INT_MARK = 1; //标志位置1,表示有按键中断
        EXTI_ClearITPendingBit(EXTI_Line4);   //清除 LINE 上的中断标志位
    }
}
void  EXTI9_5_IRQHandler(void)
{
    if(EXTI_GetITStatus(EXTI_Line5) != RESET) //判断某个线上的中断是否发生
    {
        INT_MARK = 2; //标志位置1,表示有按键中断
        EXTI_ClearITPendingBit(EXTI_Line5);   //清除 LINE 上的中断标志位
    }
    if(EXTI_GetITStatus(EXTI_Line6) != RESET) //判断某个线上的中断是否发生
    {
        INT_MARK = 3; //标志位置1,表示有按键中断
        EXTI_ClearITPendingBit(EXTI_Line6);   //清除 LINE 上的中断标志位
    }
    if(EXTI_GetITStatus(EXTI_Line7) != RESET) //判断某个线上的中断是否发生
    {
        INT_MARK = 4; //标志位置1,表示有按键中断
        EXTI_ClearITPendingBit(EXTI_Line7);   //清除 LINE 上的中断标志位
    }
}

main.c

  • 程序在 while 循环不断运行,不管运行到什么位置,只要 I/O端口出现下降沿,就会触发中断,直接跳出循环去到中断处理函数,执行里面的内容
/*********************************************************************************************
程序名:	键盘中断测试程序
硬件支持:	STM32F103C8   外部晶振8MHz RCC函数设置主频72MHz   						
说明:
 # 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
 # 可根据自己的需要增加或删减。

*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "relay.h"
# include "oled0561.h"

# include "KEYPAD4x4.h"
# include "NVIC.h"


int main (void){//主程序
	u8 s;
	delay_ms(500); //上电时等待其他器件就绪
	RCC_Configuration(); //系统时钟初始化 
	RELAY_Init();//继电器初始化

	I2C_Configuration();//I2C初始化
	OLED0561_Init(); //OLED初始化
	OLED_DISPLAY_8x16_BUFFER(0,"   YoungTalk    "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(3," KEYPAD4x4 TEST "); //显示字符串

	INT_MARK=0;//标志位清0

	NVIC_Configuration();//设置中断优先级
	KEYPAD4x4_Init();//阵列键盘初始化
	KEYPAD4x4_INT_INIT();//阵列键盘的中断初始化

	while(1){

		//其他程序内容

		if(INT_MARK){ //中断标志位为1表示有按键中断
			INT_MARK=0;//标志位清0
			s=KEYPAD4x4_Read();//读出按键值
			if(s!=0){ //如按键值不是0,也就是说有按键操作,则判断为真
				//-------------------------"----------------"
				OLED_DISPLAY_8x16_BUFFER(6," KEY NO.        "); //显示字符串
				OLED_DISPLAY_8x16(6,8*8,s/10+0x30);//
				OLED_DISPLAY_8x16(6,9*8,s%10+0x30);//
			}
		}
	}
}

扩展知识

抢占优先级和响应优先级

  • 抢占优先级不同,会涉及到中断嵌套,抢占优先级高的会优先抢占优先级低的,优先得到执行。(注意:优先级数字越小,优先级越高)

  • 抢占优先级相同,不涉及到中断嵌套,响应优先级不同,响应优先级高的先响应。(例如:两个中断同时响应,这里就会先执行响应优先级高的那个中断)(注意:优先级数字越小,优先级越高)

  • 抢占优先级和响应优先级都相同,则比较它们的硬件中断编号,中断编号越小,优先级越高。(硬件中断编号从中断向量表当中查看)

舵机原理与驱动

本节用到的固件库函数

  • TIM_TimeBaseInit (手册 19.2.2
  • TIM_OCInit (手册 19.2.3) //跟手册有出入
  • TIM_Cmd (手册 19.2.8)//使能或者失能TIMx外设
  • TIM_OC3PreloadConfig (手册 19.2.29)//使能或者失能TIMx在CCR3上的预装载寄存器

sys.h,sys.c,delay.h,delay.c,relay.h,relay.c,oled0561.h,oled0561.c27 相同,touch_key.h,touch_key.c13 相同,新增 SG90.h,SG90.c

SG90.h

# ifndef __SG90_H
# define __SG90_H	 
# include "sys.h"
# include "delay.h"

# define SE_PORT	GPIOA	//定义IO接口
# define SE_OUT	GPIO_Pin_15	//定义IO接口


void SG90_Init(void);//SG90舵机初始化
void SG90_angle(u8 a);//舵机角度设置
		 				    
# endif

SG90.c

# include "SG90.h"

void SG90_Init(void){ //舵机接口初始化
	GPIO_InitTypeDef  GPIO_InitStructure; 	
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);       
    GPIO_InitStructure.GPIO_Pin = SE_OUT; //选择端口号(0~15或all)                        
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式       
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)    
	GPIO_Init(SE_PORT, &GPIO_InitStructure);
	GPIO_ResetBits(SE_PORT,SE_OUT); //接口输出低电平0
}
//延时时间加起来是20000us=20ms
void SG90_angle(u8 a){ //舵机角度控制设置(参数值0~180)对应角度0~180度
	u8 b=100;//角度校正偏移量
	GPIO_WriteBit(SE_PORT,SE_OUT,(BitAction)(1)); //接口输出高电平1
	delay_us(500+a*10+b); //延时
	GPIO_WriteBit(SE_PORT,SE_OUT,(BitAction)(0)); //接口输出低电平0
	delay_us(19500-a*10-b); //延时
} 

main.c

/*********************************************************************************************
程序名:	延时函数驱动舵机程序
硬件支持:	洋桃1号开发板 STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz   					
说明:
 # 本程序是在洋桃1号开发板的硬件基础上编写的,移植需了解硬件接口差异。
 # 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
 # 可根据自己的需要增加或删减。

*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "relay.h"
# include "oled0561.h"
# include "SG90.h"
# include "touch_key.h"


int main (void){//主程序
	delay_ms(500); //上电时等待其他器件就绪
	RCC_Configuration(); //系统时钟初始化 
	RELAY_Init();//继电器初始化

	I2C_Configuration();//I2C初始化
	OLED0561_Init(); //OLED初始化
	OLED_DISPLAY_8x16_BUFFER(0,"   LOVE 520 1314   "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(3,"   SG90 TEST    "); //显示字符串

	TOUCH_KEY_Init();//按键初始化
	SG90_Init();//SG90舵机初始化
	SG90_angle(0);//舵机初步为0(最小值)
//需要通过微调SG90_angle(x)的x值来达到需要的角度
	while(1){
		if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A)){ //读触摸按键的电平
			OLED_DISPLAY_8x16_BUFFER(6,"  Angle 0       "); //显示字符串
			SG90_angle(0);//舵机初步为0(最小值)
		}
		if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B)){ //读触摸按键的电平
			OLED_DISPLAY_8x16_BUFFER(6,"  Angle 45      "); //显示字符串
			SG90_angle(45);//大概45度
		}
		if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C)){ //读触摸按键的电平
			OLED_DISPLAY_8x16_BUFFER(6,"  Angle 90     "); //显示字符串
			SG90_angle(100);//大概90度
		}
		if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_D)){ //读触摸按键的电平
			OLED_DISPLAY_8x16_BUFFER(6,"  Angle 180     "); //显示字符串
			SG90_angle(205);//大概180度
		}
	}
}

定时器PWM程序

文件和 28相同,SG90.h,SG90.c 两个可以不用,只需添加 pwm.h,pwm.c 文件即可(Lib 文件添加 stm32f10x_tim.c

pwm.h

# ifndef  __PWM_H
# define  __PWM_H
# include "sys.h"

void TIM3_PWM_Init(u16 arr,u16 psc);

# endif

pwm.c

# include "pwm.h"


void TIM3_PWM_Init(u16 arr,u16 psc){  //TIM3 PWM初始化 arr重装载值 psc预分频系数
    GPIO_InitTypeDef     GPIO_InitStrue;
    TIM_OCInitTypeDef     TIM_OCInitStrue;
    TIM_TimeBaseInitTypeDef     TIM_TimeBaseInitStrue;
    
    //1.使能时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//使能TIM3和相关GPIO时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//使能GPIOB时钟(LED在PB0引脚)
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//使能AFIO时钟(定时器3通道3需要重映射到BP5引脚)
    //2.配置GPIO为AFIO
    GPIO_InitStrue.GPIO_Pin=GPIO_Pin_0;     // TIM_CH3
    GPIO_InitStrue.GPIO_Mode=GPIO_Mode_AF_PP;    // 复用推挽
    GPIO_InitStrue.GPIO_Speed=GPIO_Speed_50MHz;    //设置最大输出速度
    GPIO_Init(GPIOB,&GPIO_InitStrue);                //GPIO端口初始化设置
    //3.重映射
//    GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE); //映射,重映射只用于64、100、144脚单片机
   //当没有重映射时,TIM3的四个通道CH1,CH2,CH3,CH4分别对应PA6,PA7,PB0,PB1
   //当部分重映射时,TIM3的四个通道CH1,CH2,CH3,CH4分别对应PB4,PB5,PB0,PB1 (GPIO_PartialRemap_TIM3)
   //当完全重映射时,TIM3的四个通道CH1,CH2,CH3,CH4分别对应PC6,PC7,PC8,PC9 (GPIO_FullRemap_TIM3) 
	//4.初始化TIM3
    TIM_TimeBaseInitStrue.TIM_Period=arr;    //设置自动重装载值(溢出值)
    TIM_TimeBaseInitStrue.TIM_Prescaler=psc;        //预分频系数
    TIM_TimeBaseInitStrue.TIM_CounterMode=TIM_CounterMode_Up;    //计数器向上溢出
    TIM_TimeBaseInitStrue.TIM_ClockDivision=TIM_CKD_DIV1;        //时钟的分频因子,起到了一点点的延时作用,一般设为TIM_CKD_DIV1(这个起到滤波的作用,默认0就行)
    TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStrue);        //TIM3初始化设置(设置PWM的周期)
    
    //4.初始化 TIM3 Channel2 PWM 模式
    TIM_OCInitStrue.TIM_OCMode=TIM_OCMode_PWM1;        // PWM模式1:CNT < CCR时输出有效电平
    TIM_OCInitStrue.TIM_OCPolarity=TIM_OCPolarity_High;// 设置极性-有效电平为:高电平
    TIM_OCInitStrue.TIM_OutputState=TIM_OutputState_Enable;// 输出使能
    TIM_OC3Init(TIM3,&TIM_OCInitStrue);        //TIM3的通道3 PWM 模式设置
    
	//使能TIM,CCR预装载寄存器
    TIM_OC3PreloadConfig(TIM3,TIM_OCPreload_Enable);        //使能预装载寄存器
    TIM_Cmd(TIM3,ENABLE);        //使能TIM3   
}

main.c

CCR的值:

/*********************************************************************************************
程序名:	PWM驱动舵机程序
硬件支持:	洋桃1号开发板 STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz   						
说明:
 # 本程序是在洋桃1号开发板的硬件基础上编写的,移植需了解硬件接口差异。
 # 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
 # 可根据自己的需要增加或删减。

*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "relay.h"
# include "oled0561.h"
# include "SG90.h"
# include "touch_key.h"

# include "pwm.h"


int main (void){//主程序
	delay_ms(500); //上电时等待其他器件就绪
	RCC_Configuration(); //系统时钟初始化 
	RELAY_Init();//继电器初始化

	I2C_Configuration();//I2C初始化
	OLED0561_Init(); //OLED初始化
	OLED_DISPLAY_8x16_BUFFER(0,"   YoungTalk    "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(3,"   SG90 TEST2   "); //显示字符串

	TOUCH_KEY_Init();//按键初始化
	TIM3_PWM_Init(59999,23); //设置频率为50Hz,公式为:溢出时间Tout(单位秒)=(arr+1)(psc+1)/Tclk	 20MS = (59999+1)*(23+1)/72000000
                          //Tclk为通用定时器的时钟,如果APB1没有分频,则就为系统时钟,72MHZ
                          //PWM时钟频率=72000000/(59999+1)*(23+1) = 50HZ (20ms),设置自动装载值60000,预分频系数24

	while(1){
		if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A)){ //读触摸按键的电平
			OLED_DISPLAY_8x16_BUFFER(6,"  Angle 0       "); //显示字符串
			TIM_SetCompare3(TIM3,1500);        //改变比较值TIM3->CCR2达到调节占空比的效果(1500为0度)
		}
		if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B)){ //读触摸按键的电平
			OLED_DISPLAY_8x16_BUFFER(6,"  Angle 45      "); //显示字符串
			TIM_SetCompare3(TIM3,3000);        //改变比较值TIM3->CCR2达到调节占空比的效果
		}
		if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C)){ //读触摸按键的电平
			OLED_DISPLAY_8x16_BUFFER(6,"  Angle 90     "); //显示字符串
			TIM_SetCompare3(TIM3,4500);        //改变比较值TIM3->CCR2达到调节占空比的效果
		}
		if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_D)){ //读触摸按键的电平
			OLED_DISPLAY_8x16_BUFFER(6,"  Angle 180     "); //显示字符串
			TIM_SetCompare3(TIM3,7500);        //改变比较值TIM3->CCR2达到调节占空比的效果
		}
	}
}
  • 注意:因为我工程包含了很多之前的.c文件(不管用没用到),所以导致烧写进去单片机只动一次舵机就没反应了,后来我把那些没用到的.c文件全部移除出去舵机才正常(可能IO冲突)

实验现象

DHT11原理与驱动(温湿度传感器)

文件完全复制 28 即可,增加 dht11.c,dht11.h

温湿度传感器介绍

我买的是直接焊好的,引出3条引脚

连接单片机:

VCC -----> 5v

DATA ------> PA15

GND -------> GND

也可以插在面包板上:

串行接口(单线双向)

DATA 用于微处理器与DHT11之间的通讯和同步,采用单总线数据格式,一次通讯时间4ms左右,数据分小数部分和整数部分,具体格式在下面说明,当前小数部分用于以后扩展,现读出为零.操作流程如下:

一次完整的数据传输为40bit,高位先出。数据格式:8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据+8bit校验和

数据传送正确时校验和数据等于8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据所得结果的末8位

用户MCU发送一次开始信号后,DHT11从低功耗模式转换到高速模式,等待主机开始信号结束后,DHT11发送响应信号,送出40bit的数据,并触发一次信号采集,用户可选择读取部分数据.从模式下,DHT11接收到开始信号触发一次温湿度采集,如果没有接收到主机发送开始信号,DHT11不会主动进行温湿度采集.采集数据后转换到低速模式

图A:

总线空闲状态为高电平,主机把总线拉低等待DHT11响应,主机把总线拉低必须大于18毫秒,保证DHT11能检测到起始信号。DHT11接收到主机的开始信号后,等待主机开始信号结束,然后发送80us低电平响应信号.主机发送开始信号结束后,延时等待20-40us后, 读取DHT11的响应信号,主机发送开始信号后,可以切换到输入模式,或者输出高电平均可, 总线由上拉电阻拉高。

图B:

总线为低电平,说明DHT11发送响应信号,DHT11发送响应信号后,再把总线拉高80us,准备发送数据,每一bit数据都以50us低电平时隙开始,高电平的长短定了数据位是0还是1.格式见下面图示.如果读取响应信号为高电平,则DHT11没有响应,请检查线路是否连接正常.当最后一bit数据传送完毕后,DHT11拉低总线
50us,随后总线由上拉电阻拉高进入空闲状态。

数字0信号表示方法如图所示:

图C:

数字1信号表示方法.如图所示:

图D:

dht11.h

# ifndef __DHT11_H
# define __DHT11_H
# include "sys.h"
# include "delay.h"

# define DHT11PORT GPIOA
# define DHT11_IO GPIO_Pin_15

void DHT11_IO_OUT(void);
void DHT11_IO_IN(void);
void DHT11_RST(void);
u8 Dht11_Check(void);
u8 Dht11_ReadBit(void);
u8 Dht11_ReadByte(void);
u8 AHT11_Init(void);
u8 DHT11_ReadData(u8 *h);

# endif
  • 输入(in)就是接收的信号;输出(out)就是发送的信号
  • void DHT11_RST(void) 函数是发送起始信号函数见图B
  • u8 Dht11_Check(void) 函数是等待DHT11回应见图B

dht11.c

# include "dht11.h"



void DHT11_IO_OUT(void)//端口变为输出
{
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = DHT11_IO;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(DHT11PORT, &GPIO_InitStructure);
}

void DHT11_IO_IN(void)//端口变为输入
{
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = DHT11_IO;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
    GPIO_Init(DHT11PORT, &GPIO_InitStructure);
}

//单片机向传感器发送信号
void DHT11_RST(void)//DHT11端口复位,发出起始信号(IO发送)
{
    DHT11_IO_OUT();
    GPIO_ResetBits(DHT11PORT, DHT11_IO); //拉低电平
    delay_ms(20);//拉低至少18ms
    GPIO_SetBits(DHT11PORT, DHT11_IO); //拉高电平
    delay_us(30);//拉高至少20~40us
}

//单片机接收传感器的信号
u8 Dht11_Check(void)//等待DHT11回应,返回1:未检测到DHT11,返回0:成功(IO接收)
{
    u8 retry = 0;
    DHT11_IO_IN();//IO到输入状态
    while(GPIO_ReadInputDataBit(DHT11PORT, DHT11_IO) && retry < 100) //DHT11会拉低40~80us
    {
        retry++;
        delay_us(1);
    }
    if(retry >= 100)return 1;
    else retry = 0;
    while(!GPIO_ReadInputDataBit(DHT11PORT, DHT11_IO) && retry < 100) //DHT11拉低后再次拉高40~80us
    {
        retry++;
        delay_us(1);
    }
    if(retry >= 100)return 1;

    return 0;
}

u8 Dht11_ReadBit(void)//从DHT11读取一个位,返回值:1/0
{
    u8 retry = 0;
    while(GPIO_ReadInputDataBit(DHT11PORT, DHT11_IO) && retry < 100) //等待变为低电平
    {
        retry++;
        delay_us(1);
    }
    retry = 0;
    while(!GPIO_ReadInputDataBit(DHT11PORT, DHT11_IO) && retry < 100) //等待变高电平
    {
        retry++;
        delay_us(1);
    }
    delay_us(40);//进入高电平后延时40us(取一个中间值)
    if(GPIO_ReadInputDataBit(DHT11PORT, DHT11_IO)) //用于判断高低电平,即数据1或0
        return 1;//如果此时是高电平则返回1
    else
        return 0;//如果此时是低电平则返回0
}

u8 Dht11_ReadByte(void)//从DHT11读取一个字节,返回值:读到的数据
{
    u8 i, dat;
    dat = 0;
    for(i = 0; i < 8; i++)
    {
        dat <<= 1;
        dat |= Dht11_ReadBit();
    }
    return dat;
}

u8 Dht11_Init(void)//DHT11初始化
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE); //APB2外设时钟使能
    DHT11_RST();//DHT11端口复位,发出起始信号
    return Dht11_Check();//等待DHT11回应
}

//读取一次数据//湿度值(十进制,范围:20%~90%) ,温度值(十进制,范围:0~50°),
//返回值:0,正常;1,失败
u8 DHT11_ReadData(u8 *h)
{
    u8 buf[5];
    u8 i;
    if(Dht11_Check() == 0) //等待DHT11回应
    {
        for(i = 0; i < 5; i++) //读取5位数据
        {
            buf[i] = Dht11_ReadByte(); //读出数据
        }
        if(buf[0] + buf[1] + buf[2] + buf[3] == buf[4]) //数据校验
        {
            *h = buf[0]; //将湿度值放入指针1
            h++;
            *h = buf[2]; //将温度值放入指针2
        }
    }
    else
        return 1;

    return 0;
}

main.c

/*********************************************************************************************
程序名:	DHT11温湿度显示程序
硬件支持:	洋桃1号开发板 STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz 
说明:
 # 本程序是在洋桃1号开发板的硬件基础上编写的,移植需了解硬件接口差异。
 # 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
 # 可根据自己的需要增加或删减。

*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "relay.h"
# include "oled0561.h"
# include "touch_key.h"
# include "pwm.h"
# include "dht11.h"


int main (void) //主程序
{
    u8 b[2];
    delay_ms(1000); //上电时等待其他器件就绪
    RCC_Configuration(); //系统时钟初始化
    RELAY_Init();//继电器初始化

    I2C_Configuration();//I2C初始化
    OLED0561_Init(); //OLED初始化
    OLED_DISPLAY_8x16_BUFFER(0, "   Yang King    "); //显示字符串
    OLED_DISPLAY_8x16_BUFFER(2, "   DHT11 TEST   "); //显示字符串

    if(DHT11_Init() == 0)
    {
        OLED_DISPLAY_8x16_BUFFER(4, "Humidity:   %   "); //显示字符串
        OLED_DISPLAY_8x16_BUFFER(6, "Temperature:   C"); //显示字符串
    }
    else
    {
        OLED_DISPLAY_8x16_BUFFER(4, "DHT11INIT ERROR!"); //显示字符串
    }

    while(1)
    {
        delay_ms(1000);
        if(DHT11_ReadData(b) == 0) //DHT11初始化	返回0成功,1失败
        {
            OLED_DISPLAY_8x16(4, 9 * 8, b[0] / 10 + 0x30); //显示湿度值
            OLED_DISPLAY_8x16(4, 10 * 8, b[0] % 10 + 0x30); //
            OLED_DISPLAY_8x16(6, 12 * 8, b[1] / 10 + 0x30); //显示温度值
            OLED_DISPLAY_8x16(6, 13 * 8, b[1] % 10 + 0x30); //
        }
        else
        {
            OLED_DISPLAY_8x16_BUFFER(6, "DHT11READ ERROR!"); //显示字符串
        }
        delay_ms(1000); //延时,刷新数据的频率(不得小于1秒)如果显示错误可以延长到2s

    }
}

实验现象

MPU6050原理与驱动

文件复制上面的 29 即可再添加 MPU6050.c,MPU6050.h

接线:

VCC ------> 5v/3.3v

GND ------> GND

SCL -------> PB6

SDA -------> PB7

MPU 6050 介绍

常用寄存器

MPU6050.h

# ifndef __MPU6050_H
# define __MPU6050_H	 
# include "sys.h"
# include "i2c.h"
# include "delay.h"


# define MPU6050_ADD	0xD0	//器件地址(AD0悬空或低电平时地址是0xD0,为高电平时为0xD2,7位地址:1101 000x)


# define MPU6050_RA_XG_OFFS_TC       0x00 
# define MPU6050_RA_YG_OFFS_TC       0x01 
# define MPU6050_RA_ZG_OFFS_TC       0x02 
# define MPU6050_RA_X_FINE_GAIN      0x03 
# define MPU6050_RA_Y_FINE_GAIN      0x04 
# define MPU6050_RA_Z_FINE_GAIN      0x05 
# define MPU6050_RA_XA_OFFS_H        0x06 
# define MPU6050_RA_XA_OFFS_L_TC     0x07
# define MPU6050_RA_YA_OFFS_H        0x08 
# define MPU6050_RA_YA_OFFS_L_TC     0x09
# define MPU6050_RA_ZA_OFFS_H        0x0A 
# define MPU6050_RA_ZA_OFFS_L_TC     0x0B
# define MPU6050_RA_XG_OFFS_USRH     0x13 
# define MPU6050_RA_XG_OFFS_USRL     0x14
# define MPU6050_RA_YG_OFFS_USRH     0x15 
# define MPU6050_RA_YG_OFFS_USRL     0x16
# define MPU6050_RA_ZG_OFFS_USRH     0x17 
# define MPU6050_RA_ZG_OFFS_USRL     0x18
# define MPU6050_RA_SMPLRT_DIV       0x19 //陀螺仪采样率分频寄存器(手册4.2)
# define MPU6050_RA_CONFIG           0x1A //配置寄存器(手册4.3)
# define MPU6050_RA_GYRO_CONFIG      0x1B //陀螺仪配置寄存器(手册4.4)
# define MPU6050_RA_ACCEL_CONFIG     0x1C //加速度传感器配置寄存器(手册4.5)
# define MPU6050_RA_FF_THR           0x1D 
# define MPU6050_RA_FF_DUR           0x1E
# define MPU6050_RA_MOT_THR          0x1F
# define MPU6050_RA_MOT_DUR          0x20
# define MPU6050_RA_ZRMOT_THR        0x21
# define MPU6050_RA_ZRMOT_DUR        0x22
# define MPU6050_RA_FIFO_EN          0x23 //FIFO使能寄存器(手册4.6)
# define MPU6050_RA_I2C_MST_CTRL     0x24
# define MPU6050_RA_I2C_SLV0_ADDR    0x25
# define MPU6050_RA_I2C_SLV0_REG     0x26
# define MPU6050_RA_I2C_SLV0_CTRL    0x27
# define MPU6050_RA_I2C_SLV1_ADDR    0x28
# define MPU6050_RA_I2C_SLV1_REG     0x29
# define MPU6050_RA_I2C_SLV1_CTRL    0x2A
# define MPU6050_RA_I2C_SLV2_ADDR    0x2B
# define MPU6050_RA_I2C_SLV2_REG     0x2C
# define MPU6050_RA_I2C_SLV2_CTRL    0x2D
# define MPU6050_RA_I2C_SLV3_ADDR    0x2E
# define MPU6050_RA_I2C_SLV3_REG     0x2F
# define MPU6050_RA_I2C_SLV3_CTRL    0x30
# define MPU6050_RA_I2C_SLV4_ADDR    0x31
# define MPU6050_RA_I2C_SLV4_REG     0x32
# define MPU6050_RA_I2C_SLV4_DO      0x33
# define MPU6050_RA_I2C_SLV4_CTRL    0x34
# define MPU6050_RA_I2C_SLV4_DI      0x35
# define MPU6050_RA_I2C_MST_STATUS   0x36
# define MPU6050_RA_INT_PIN_CFG      0x37
# define MPU6050_RA_INT_ENABLE       0x38
# define MPU6050_RA_DMP_INT_STATUS   0x39
# define MPU6050_RA_INT_STATUS       0x3A
# define MPU6050_RA_ACCEL_XOUT_H     0x3B//1 加速度传感器数据输出寄存器(0x3B~0x40)H:高8位 L:低8位
# define MPU6050_RA_ACCEL_XOUT_L     0x3C//1
# define MPU6050_RA_ACCEL_YOUT_H     0x3D//1
# define MPU6050_RA_ACCEL_YOUT_L     0x3E//1
# define MPU6050_RA_ACCEL_ZOUT_H     0x3F//1
# define MPU6050_RA_ACCEL_ZOUT_L     0x40//1
# define MPU6050_RA_TEMP_OUT_H       0x41//温度传感器数据输出寄存器(0x41~0x42)
# define MPU6050_RA_TEMP_OUT_L       0x42
# define MPU6050_RA_GYRO_XOUT_H      0x43//2 陀螺仪数据输出寄存器(0x43~0x48)H:高8位 L:低8位
# define MPU6050_RA_GYRO_XOUT_L      0x44//2
# define MPU6050_RA_GYRO_YOUT_H      0x45//2
# define MPU6050_RA_GYRO_YOUT_L      0x46//2
# define MPU6050_RA_GYRO_ZOUT_H      0x47//2
# define MPU6050_RA_GYRO_ZOUT_L      0x48//2
# define MPU6050_RA_EXT_SENS_DATA_00 0x49
# define MPU6050_RA_EXT_SENS_DATA_01 0x4A
# define MPU6050_RA_EXT_SENS_DATA_02 0x4B
# define MPU6050_RA_EXT_SENS_DATA_03 0x4C
# define MPU6050_RA_EXT_SENS_DATA_04 0x4D
# define MPU6050_RA_EXT_SENS_DATA_05 0x4E
# define MPU6050_RA_EXT_SENS_DATA_06 0x4F
# define MPU6050_RA_EXT_SENS_DATA_07 0x50
# define MPU6050_RA_EXT_SENS_DATA_08 0x51
# define MPU6050_RA_EXT_SENS_DATA_09 0x52
# define MPU6050_RA_EXT_SENS_DATA_10 0x53
# define MPU6050_RA_EXT_SENS_DATA_11 0x54
# define MPU6050_RA_EXT_SENS_DATA_12 0x55
# define MPU6050_RA_EXT_SENS_DATA_13 0x56
# define MPU6050_RA_EXT_SENS_DATA_14 0x57
# define MPU6050_RA_EXT_SENS_DATA_15 0x58
# define MPU6050_RA_EXT_SENS_DATA_16 0x59
# define MPU6050_RA_EXT_SENS_DATA_17 0x5A
# define MPU6050_RA_EXT_SENS_DATA_18 0x5B
# define MPU6050_RA_EXT_SENS_DATA_19 0x5C
# define MPU6050_RA_EXT_SENS_DATA_20 0x5D
# define MPU6050_RA_EXT_SENS_DATA_21 0x5E
# define MPU6050_RA_EXT_SENS_DATA_22 0x5F
# define MPU6050_RA_EXT_SENS_DATA_23 0x60
# define MPU6050_RA_MOT_DETECT_STATUS    0x61
# define MPU6050_RA_I2C_SLV0_DO      0x63
# define MPU6050_RA_I2C_SLV1_DO      0x64
# define MPU6050_RA_I2C_SLV2_DO      0x65
# define MPU6050_RA_I2C_SLV3_DO      0x66
# define MPU6050_RA_I2C_MST_DELAY_CTRL   0x67
# define MPU6050_RA_SIGNAL_PATH_RESET    0x68
# define MPU6050_RA_MOT_DETECT_CTRL      0x69
# define MPU6050_RA_USER_CTRL        0x6A
# define MPU6050_RA_PWR_MGMT_1       0x6B	//电源管理寄存器1(手册4.28)
# define MPU6050_RA_PWR_MGMT_2       0x6C	//电源管理寄存器2(手册4.29)
# define MPU6050_RA_BANK_SEL         0x6D
# define MPU6050_RA_MEM_START_ADDR   0x6E
# define MPU6050_RA_MEM_R_W          0x6F
# define MPU6050_RA_DMP_CFG_1        0x70
# define MPU6050_RA_DMP_CFG_2        0x71
# define MPU6050_RA_FIFO_COUNTH      0x72
# define MPU6050_RA_FIFO_COUNTL      0x73
# define MPU6050_RA_FIFO_R_W         0x74
# define MPU6050_RA_WHO_AM_I         0x75   /////////////////////


void MPU6050_Init(void);
void MPU6050_READ(u16* n);   

		 				    
# endif

MPU6050.c

  • I2C_SAND_BYTE(u8 SlaveAddr,u8 writeAddr,u8 pBuffer); 函数意思是往从设备里的寄存器里发送数据
  • 芯片一开始是处于休眠状态的,通过复位可以解除休眠模式
  • I2C_SAND_BYTE(MPU6050_ADD,MPU6050_RA_PWR_MGMT_1,0x80);:这里 MPU6050_RA_PWR_MGMT_1 对应的是0x6B,通过寄存器手册可以查到,0x80由来:

  • n[i]=((t[ 2 x i ]<<8)+t[ 2 x i+1]); 通过运算把两个8位合成一个16位

# include "mpu6050.h"

void MPU6050_Init(void)//初始化MPU6050
{
	I2C_SAND_BYTE(MPU6050_ADD,MPU6050_RA_PWR_MGMT_1,0x80);//解除休眠状态(复位)
	delay_ms(1000);//等待器件就绪
	I2C_SAND_BYTE(MPU6050_ADD,MPU6050_RA_PWR_MGMT_1,0x00);//解除休眠状态(正常工作)
	I2C_SAND_BYTE(MPU6050_ADD,MPU6050_RA_SMPLRT_DIV,0x07);//陀螺仪采样率
	I2C_SAND_BYTE(MPU6050_ADD,MPU6050_RA_CONFIG,0x06);//配置寄存器
	I2C_SAND_BYTE(MPU6050_ADD,MPU6050_RA_ACCEL_CONFIG,0x00);//配置加速度传感器工作在16G模式
	I2C_SAND_BYTE(MPU6050_ADD,MPU6050_RA_GYRO_CONFIG,0x18);//陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
}

//读出X、Y、Z三轴加速度/陀螺仪原始数据 //n[0]是AX,n[1]是AY,n[2]是AZ
//n[3]是GX,n[4]是GY,n[5]是GZ 
void MPU6050_READ(u16* n)
{
	u8 i;
	u8 t[14];
	I2C_READ_BUFFER(MPU6050_ADD,MPU6050_RA_ACCEL_XOUT_H,t,14);//读出连续的数据地址,包括了加速度和陀螺仪共12字节
	for(i=0;i<3;i++)//整合加速度
	{
		n[i]=((t[2*i]<<8)+t[2*i+1]);
	}
	for(i=4;i<7;i++)//整合陀螺仪(这里i从4开始是跳过了那两个没用的字节直接从t[8]开始,t[6]和t[7]是没用的)
	{
		n[i]=((t[2*i]<<8)+t[2*i+1]);
	}
}

main.c

# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "relay.h"
# include "oled0561.h"

# include "MPU6050.h"


int main (void) //主程序
{
    u16 t[6] = {0};
    delay_ms(500); //上电时等待其他器件就绪
    RCC_Configuration(); //系统时钟初始化
    RELAY_Init();//继电器初始化

    I2C_Configuration();//I2C初始化

    OLED0561_Init(); //OLED初始化
    OLED_DISPLAY_8x16_BUFFER(0, "  MPU6050 TEST  "); //显示字符串
    OLED_DISPLAY_8x16_BUFFER(2, "X:       X:     "); //显示字符串
    OLED_DISPLAY_8x16_BUFFER(4, "Y:       Y:     "); //显示字符串
    OLED_DISPLAY_8x16_BUFFER(6, "Z:       Z:     "); //显示字符串

    MPU6050_Init(); //MPU6050初始化

    while(1)
    {
        MPU6050_READ(t);	//加速度
        //其中t[0~2]是加速度ACCEL,t[3~5]是陀螺仪GYRO
        OLED_DISPLAY_8x16(2, 2 * 8, t[0] / 10000 + 0x30); //显示
        OLED_DISPLAY_8x16(2, 3 * 8, t[0] % 10000 / 1000 + 0x30); //显示
        OLED_DISPLAY_8x16(2, 4 * 8, t[0] % 1000 / 100 + 0x30); //
        OLED_DISPLAY_8x16(2, 5 * 8, t[0] % 100 / 10 + 0x30); //
        OLED_DISPLAY_8x16(2, 6 * 8, t[0] % 10 + 0x30); //
        OLED_DISPLAY_8x16(2, 11 * 8, t[3] / 10000 + 0x30); //显示
        OLED_DISPLAY_8x16(2, 12 * 8, t[3] % 10000 / 1000 + 0x30); //显示
        OLED_DISPLAY_8x16(2, 13 * 8, t[3] % 1000 / 100 + 0x30); //
        OLED_DISPLAY_8x16(2, 14 * 8, t[3] % 100 / 10 + 0x30); //
        OLED_DISPLAY_8x16(2, 15 * 8, t[3] % 10 + 0x30); //

        OLED_DISPLAY_8x16(4, 2 * 8, t[1] / 10000 + 0x30); //显示
        OLED_DISPLAY_8x16(4, 3 * 8, t[1] % 10000 / 1000 + 0x30); //显示
        OLED_DISPLAY_8x16(4, 4 * 8, t[1] % 1000 / 100 + 0x30); //
        OLED_DISPLAY_8x16(4, 5 * 8, t[1] % 100 / 10 + 0x30); //
        OLED_DISPLAY_8x16(4, 6 * 8, t[1] % 10 + 0x30); //
        OLED_DISPLAY_8x16(4, 11 * 8, t[4] / 10000 + 0x30); //显示
        OLED_DISPLAY_8x16(4, 12 * 8, t[4] % 10000 / 1000 + 0x30); //显示
        OLED_DISPLAY_8x16(4, 13 * 8, t[4] % 1000 / 100 + 0x30); //
        OLED_DISPLAY_8x16(4, 14 * 8, t[4] % 100 / 10 + 0x30); //
        OLED_DISPLAY_8x16(4, 15 * 8, t[4] % 10 + 0x30); //

        OLED_DISPLAY_8x16(6, 2 * 8, t[2] / 10000 + 0x30); //显示
        OLED_DISPLAY_8x16(6, 3 * 8, t[2] % 10000 / 1000 + 0x30); //显示
        OLED_DISPLAY_8x16(6, 4 * 8, t[2] % 1000 / 100 + 0x30); //
        OLED_DISPLAY_8x16(6, 5 * 8, t[2] % 100 / 10 + 0x30); //
        OLED_DISPLAY_8x16(6, 6 * 8, t[2] % 10 + 0x30); //
        OLED_DISPLAY_8x16(6, 11 * 8, t[5] / 10000 + 0x30); //显示
        OLED_DISPLAY_8x16(6, 12 * 8, t[5] % 10000 / 1000 + 0x30); //显示
        OLED_DISPLAY_8x16(6, 13 * 8, t[5] % 1000 / 100 + 0x30); //
        OLED_DISPLAY_8x16(6, 14 * 8, t[5] % 100 / 10 + 0x30); //
        OLED_DISPLAY_8x16(6, 15 * 8, t[5] % 10 + 0x30); //

        delay_ms(200); //延时(决定刷新速度)
    }
}

实验现象

待补充