基于STM32人脸识别系统

(程序+设计报告)

功能介绍

具体功能:

可以实现进入页面的设定自定义DIY;可以自由的添加需要识别的人脸;人脸靠近,按下识别按键可以实现人脸识别。如果在系统库中会提示是那个人,如果不在会提示不在库中。

程序

#include "sys.h"
#include "delay.h"  
#include "usart.h"   
#include "led.h"
#include "lcd.h"
#include "key.h"  
#include "usmart.h"   
#include "sram.h" 
#include "malloc.h" 
#include "w25qxx.h"    
#include "sdio_sdcard.h"
#include "ff.h"  
#include "exfuns.h"    
#include "fontupd.h"
#include "text.h"	
#include "piclib.h"	
#include "string.h"	
#include "math.h"	
#include "dcmi.h"	
#include "ov2640.h"	
#include "beep.h"	
#include "timer.h"
#include "atk_frec.h"
 
int reg_time;
u8 ov2640_mode=0;						//工作模式:0,RGB565模式;1,JPEG模式

//得到path路径下,目标文件的总个数
//path:路径		    
//返回值:总有效文件数
u16 pic_get_tnum(u8 *path)
{	  
	u8 res;
	u16 rval=0;
 	DIR tdir;	 		//临时目录
	FILINFO tfileinfo;	//临时文件信息	
	u8 *fn;	 			 			   			     
    res=f_opendir(&tdir,(const TCHAR*)path); 	//打开目录
  	tfileinfo.lfsize=_MAX_LFN*2+1;				//长文件名最大长度
	tfileinfo.lfname=mymalloc(SRAMIN,tfileinfo.lfsize);//为长文件缓存区分配内存
	if(res==FR_OK&&tfileinfo.lfname!=NULL)
	{
		while(1)//查询总的有效文件数
		{
	        res=f_readdir(&tdir,&tfileinfo);       		//读取目录下的一个文件
	        if(res!=FR_OK||tfileinfo.fname[0]==0)break;	//错误了/到末尾了,退出		  
     		fn=(u8*)(*tfileinfo.lfname?tfileinfo.lfname:tfileinfo.fname);			 
			res=f_typetell(fn);	
			if((res&0XF0)==0X50)//取高四位,看看是不是图片文件	
			{
				rval++;//有效文件数增加1
			}	    
		}  
	} 
	return rval;
}

 
 
//处理JPEG数据
//当采集完一帧JPEG数据后,调用此函数,切换JPEG BUF.开始下一帧采集.
void jpeg_data_process(void)
{ 
	if(ov2640_mode)//只有在JPEG格式下,才需要做处理.
	{ 
	}
} 
//切换为OV2640模式
void sw_ov2640_mode(void)
{
	OV2640_PWDN=0;//OV2640 Power Up
	//GPIOC8/9/11切换为 DCMI接口
 	GPIO_AF_Set(GPIOC,8,13);	//PC8,AF13  DCMI_D2
 	GPIO_AF_Set(GPIOC,9,13);	//PC9,AF13  DCMI_D3
 	GPIO_AF_Set(GPIOC,11,13);	//PC11,AF13 DCMI_D4  
} 
//切换为SD卡模式
void sw_sdcard_mode(void)
{
	OV2640_PWDN=1;//OV2640 Power Down 
	//GPIOC8/9/11切换为 SDIO接口
  	GPIO_AF_Set(GPIOC,8,12);	//PC8,AF12
 	GPIO_AF_Set(GPIOC,9,12);	//PC9,AF12 
 	GPIO_AF_Set(GPIOC,11,12);	//PC11,AF12  
} 

////////////////////////////////////////////////////////////////////////////////////////// 
//LCD显示区域限制参数
u16 face_offx,face_offy;
u16 face_xsize,face_ysize; 

u8 fontsize=12;		//字体大小

//设置图像到屏幕最中心.
void set_image_center(void)
{
	face_offx=0;
	face_offy=0;
	face_xsize=lcddev.width;
	face_ysize=lcddev.height;
	if(lcddev.id==0X1963||lcddev.id==0X5510)
	{ 
		face_offy=80;
		face_ysize=640;
		fontsize=24;
	}else if(lcddev.id==0X5310)
	{
		face_offx=10;
		face_offy=40;
		face_xsize=300;
		face_ysize=400;
		fontsize=16;
	}else fontsize=12;
	LCD_Set_Window(face_offx,face_offy,face_xsize,face_ysize);	//设置开窗口.  
}

//读取原始图片数据
//dbuf:数据缓存区
//xoff,yoff:要读取的图像区域起始坐标
//xsize:要读取的图像区域宽度
//width:要读取的宽度(宽高比恒为3:4)  
void frec_get_image_data(u16 *dbuf,u16 xoff,u16 yoff,u16 xsize,u16 width)
{
	int w, h; 
	u16 height=width*4/3;
	float scale=(float)xsize/width;
	for(h=0;h<height;h++)
	{
		for(w=0;w<width;w++)
		{
			dbuf[h*width+w]=LCD_ReadPoint(xoff+w*scale,yoff+h*scale); 
 		}
	}
}
//加载一个简单界面
//fsize:字体大小
void frec_load_ui(u8 fsize)
{
	if(fsize==16)
	{ 
		Show_Str(10,2,310,fsize,   "            WK_UP:添加人脸模板",fsize,1);							    	 
		Show_Str(10,4+16,310,fsize," KEY2:删除所有模板        KEY0:开始识别",fsize,1);	 			    	   	 
	}else if(fsize==24)
	{		
		Show_Str(10,10,470,fsize,   "           WK_UP:添加人脸模板",fsize,1);	
		Show_Str(10,20+24,470,fsize," KEY2:删除所有模板        KEY0:开始识别",fsize,1);	 			    	     	 		
	}
}
//显示提示信息
//str:要显示的字符串 
//line:第几行;0,第一行;1,第二行;其他,非法.
//fsize:字体大小
void frec_show_msg(u8* str,u8 line)
{
	if(line>1)return;
	if(lcddev.width==240)
	{ 
		Show_Str(10,lcddev.height-(2-line)*fontsize-(2-line)*5,lcddev.width,fontsize,str,fontsize,0);
	}else
	{
		Show_Str(10,lcddev.height-(2-line)*fontsize-(2-line)*(face_offy-fontsize*2)/3,lcddev.width,fontsize,str,fontsize,1);		
	} 
}	

u16 * pixdatabuf; 			//图像缓存

int main(void)
{        
	u8 res;							  
	u8 key;					//键值		
	u8 key_first;
	u8 i;						 
 	u8 msgbuf[30];			//消息缓存区 
	u8 person;
//***********初始化界面相关的显示变量**********//
 	DIR picdir;	 		//图片目录
	FILINFO picfileinfo;//文件信息
	u8 *fn;   			//长文件名
	u8 *pname;			//带路径的文件名
	u16 totpicnum; 		//图片文件总数
	u16 curindex;		//图片当前索引
	u8 pause=0;			//暂停标记
	u8 t;
	u16 temp;
	u16 *picindextbl;	//图片索引表  

	Stm32_Clock_Init(336,8,2,7);//设置时钟,168Mhz 
	delay_init(168);			//延时初始化  
	uart_init(84,115200);		//初始化串口波特率为115200 
	LED_Init();					//初始化LED 
	usmart_dev.init(84);		//初始化USMART
	TIM3_Int_Init(100-1,8400-1);//10Khz计数,10ms中断一次
 	LCD_Init();					//LCD初始化  
	FSMC_SRAM_Init();			//初始化外部SRAM.
 	BEEP_Init();				//蜂鸣器初始化
 	KEY_Init();					//按键初始化   
	W25QXX_Init();				//初始化W25Q128 
	my_mem_init(SRAMIN);		//初始化内部内存池 
	my_mem_init(SRAMEX);		//初始化内部内存池  
	my_mem_init(SRAMCCM);		//初始化CCM内存池 
	exfuns_init();				//为fatfs相关变量申请内存  
  f_mount(fs[0],"0:",1); 		//挂载SD卡  
  f_mount(fs[1],"1:",1); 		//挂载SPI FLASH
	POINT_COLOR=RED; 
	while(font_init()) 			//检查字库
	{	    
		LCD_ShowString(30,50,200,16,16,"Font Error!");
		delay_ms(200);				  
		LCD_Fill(30,50,240,66,WHITE);//清除显示	     
		delay_ms(200);				  
	}
	while(f_opendir(&picdir,"0:/PICTURE"))//打开图片文件夹
 	{	    
		Show_Str(30,170,240,16,"PICTURE文件夹错误!",16,0);
		Show_Str(30,190,240,16,"原因可能是SD卡没有插好",16,0);
		delay_ms(200);				  
		LCD_Fill(30,170,240,186,WHITE);//清除显示	     
		delay_ms(200);				  
	}  
	totpicnum=pic_get_tnum("0:/PICTURE"); //得到总有效文件数
  	while(totpicnum==NULL)//图片文件为0		
 	{	    
		Show_Str(30,170,240,16,"没有图片文件!",16,0);
		delay_ms(200);				  
		LCD_Fill(30,170,240,186,WHITE);//清除显示	     
		delay_ms(200);				  
	}
	picfileinfo.lfsize=_MAX_LFN*2+1;						//长文件名最大长度
	picfileinfo.lfname=mymalloc(SRAMIN,picfileinfo.lfsize);	//为长文件缓存区分配内存
 	pname=mymalloc(SRAMIN,picfileinfo.lfsize);				//为带路径的文件名分配内存
 	picindextbl=mymalloc(SRAMIN,2*totpicnum);				//申请2*totpicnum个字节的内存,用于存放图片索引
 	while(picfileinfo.lfname==NULL||pname==NULL||picindextbl==NULL)//内存分配出错
 	{	    
		Show_Str(30,170,240,16,"内存分配失败!",16,0);
		delay_ms(200);				  
		LCD_Fill(30,170,240,186,WHITE);//清除显示	     
		delay_ms(200);				  
	}
	//记录索引
  res=f_opendir(&picdir,"0:/PICTURE"); //打开目录
	if(res==FR_OK)
	{
		curindex=0;//当前索引为0
		while(1)//全部查询一遍
		{
			temp=picdir.index;								//记录当前index
	    res=f_readdir(&picdir,&picfileinfo);       		//读取目录下的一个文件
	    if(res!=FR_OK||picfileinfo.fname[0]==0)break;	//错误了/到末尾了,退出		  
     	fn=(u8*)(*picfileinfo.lfname?picfileinfo.lfname:picfileinfo.fname);			 
			res=f_typetell(fn);	
			if((res&0XF0)==0X50)//取高四位,看看是不是图片文件	
			{
				picindextbl[curindex]=temp;//记录索引
				curindex++;
			}	    
		} 
	}   
	delay_ms(100);
	piclib_init();										//初始化画图	   	   
	curindex=0;											  //从0开始显示
  res=f_opendir(&picdir,(const TCHAR*)"0:/PICTURE"); 	//打开目录
	if(res==FR_OK)//打开成功
	{	
		dir_sdi(&picdir,picindextbl[curindex]);			//改变当前目录索引	   
    res=f_readdir(&picdir,&picfileinfo);       		//读取目录下的一个文件
    fn=(u8*)(*picfileinfo.lfname?picfileinfo.lfname:picfileinfo.fname);			 
		strcpy((char*)pname,"0:/PICTURE/");				  //复制路径(目录)
		strcat((char*)pname,(const char*)fn);  			//将文件名接在后面
 		LCD_Clear(BLACK);
 		ai_load_picfile(pname,0,0,lcddev.width,lcddev.height,1);//显示图片    
    res = 0;		
	}
	delay_ms(1000); 	
	myfree(SRAMIN,picfileinfo.lfname);	//释放内存			    
	myfree(SRAMIN,pname);				//释放内存			    
	myfree(SRAMIN,picindextbl);			//释放内存 
	
	Show_Str(170,700,240,24,"Init....",24,1);
  LCD_Fill(170,700,240,24,BLACK);		   				//填充单色
	
	key_first = 0;
	while(SD_Init())//检查SD卡
	{	     
		Show_Str(30,190,240,16,"SD Card Error!",16,0);
		delay_ms(200);
	  LCD_Fill(30,190,239,206,WHITE);
		delay_ms(200);			  
	}   	  
	while(OV2640_Init())//初始化OV2640
	{
		Show_Str(30,190,240,16,"OV2640 错误!",16,0);
		delay_ms(200);
	  LCD_Fill(30,190,239,206,WHITE);
		delay_ms(200);
	}
	pixdatabuf=mymalloc(SRAMIN,ATK_GABOR_IMG_WID*ATK_GABOR_IMG_HEI*2);	//申请内存
	delay_ms(10);
 	OV2640_RGB565_Mode();	//RGB565输出
	OV2640_ImageWin_Set((1600-900)/2,0,900,1200);//设置输出尺寸为:900*1200,3:4比例
 	DCMI_Init();			//DCMI配置
	DCMI_DMA_Init((u32)&LCD->LCD_RAM,0,1,1,0);//DCMI DMA配置
	
	Show_Str(170,700,240,24,"Init Sucess!",24,1);
  LCD_Fill(170,700,240,24,BLACK);		   				//填充单色
	delay_ms(300); 
	Show_Str(170,730,240,24,"按任意键进入",24,1);
  LCD_Fill(170,730,240,24,BLACK);		   				//填充单色
	
while(key_first==0)	
	{
		key_first = KEY_Scan(0);//不支持连按
		delay_ms(10); 	
  }
	
	
	LCD_Clear(BLACK);
	set_image_center();		//设置到屏幕正中央
	frec_load_ui(fontsize);	//显示GUI 
 	OV2640_OutSize_Set(face_xsize,face_ysize); 
	sw_sdcard_mode();		//SD卡模式 
	res=atk_frec_initialization();	//初始化人脸识别
	if(res)
	{
		printf("atk_frec_initialization error:%d\r\n",res);//打印错误代码
	} 
	sw_ov2640_mode();	//2640模式
	DCMI_Start();		//启动传输 
	while(1)
	{	 
		delay_ms(10);
 		key=KEY_Scan(0);//不支持连按
		if(key)
		{
			DCMI_Stop();		//停止传输 
			sw_sdcard_mode();	//SD卡模式
			switch(key)
			{
				case KEY2_PRES: //删除所有模板
 					sprintf((char*)msgbuf,"正在删除...");
					frec_show_msg(msgbuf,0);	 			    	 
					for(i=0;i<MAX_LEBEL_NUM;i++)
					{
						res=atk_frec_delete_data(i);//删除模板
						if(res==FR_OK)printf("delete face:%d ok\r\n",i);
						else printf("delete face:%d failed\r\n",i);
					} 
					atk_frec_load_data_model();	//重新加载所有识别模型(被删掉的,将无法加载进来.)
 					if(lcddev.width!=240)LCD_Fill(10,lcddev.height-2*fontsize-2*(face_offy-fontsize*2)/3,lcddev.width,lcddev.height,BLACK);	//清除之前的显示
 					sprintf((char*)msgbuf,"删除完成");
					frec_show_msg(msgbuf,0);	 			    	 
					delay_ms(1000);
 					if(lcddev.width!=240)LCD_Fill(10,lcddev.height-2*fontsize-2*(face_offy-fontsize*2)/3,lcddev.width,lcddev.height,BLACK);		//清除显示
					break;
				case KEY0_PRES: //识别人脸
					frec_get_image_data(pixdatabuf,face_offx,face_offy,face_xsize,30);//读取图像数据 
 					sprintf((char*)msgbuf,"正在识别...");
					frec_show_msg(msgbuf,0);	 			    	 
					reg_time=0; 
 					res=atk_frec_recognition_face(pixdatabuf,&person);//进行识别
					if(res==ATK_FREC_MODEL_DATA_ERR)
					{	 
						sprintf((char*)msgbuf,"没有可用模板,按KEY_UP添加模板!");
					}else if(res==ATK_FREC_UNREC_FACE_ERR)
					{	
						sprintf((char*)msgbuf,"无法识别该人脸,请重试!"); 
					}else 
					{
						sprintf((char*)msgbuf,"识别结果:%02d号  耗时:%dms",person,reg_time*10);  
					}
 					if(lcddev.width!=240)LCD_Fill(10,lcddev.height-2*fontsize-2*(face_offy-fontsize*2)/3,lcddev.width,lcddev.height,BLACK);			//清除之前的显示
					frec_show_msg(msgbuf,0);	 			    	 
					sprintf((char*)msgbuf,"按任意按键继续!");  
					frec_show_msg(msgbuf,1);	 			    	 
					while(!KEY_Scan(0))	//等待按键输入
					{
						delay_ms(10); 
					}
 					if(lcddev.width!=240)LCD_Fill(10,lcddev.height-2*fontsize-2*(face_offy-fontsize*2)/3,lcddev.width,lcddev.height,BLACK);
					break;
				case WKUP_PRES://添加一个人像进入数据库
 					frec_get_image_data(pixdatabuf,face_offx,face_offy,face_xsize,30);//读取图像数据
					sprintf((char*)msgbuf,"正在添加人脸模板...");
					frec_show_msg(msgbuf,0);	 			    	 
					res=atk_frec_add_a_face(pixdatabuf,&person);	//添加一个人脸
					if(res==0)
					{
						sprintf((char*)msgbuf,"添加成功,编号:%02d   ",person);
						atk_frec_load_data_model();	//重新加载所有识别模型(将添加的人脸,加载进来)
					}else 
					{
						sprintf((char*)msgbuf,"添加失败,错误代码:%02d",res);
					}
					frec_show_msg(msgbuf,1);	 			    	 
					delay_ms(1000);
					if(lcddev.width!=240)LCD_Fill(10,lcddev.height-2*fontsize-2*(face_offy-fontsize*2)/3,lcddev.width,lcddev.height,BLACK);
					break;  
				default :
					break;
			} 
			sw_ov2640_mode();	//2640模式
			DCMI_Start(); 				//启动传输 
		}
		delay_ms(10);
		i++;
		if(i==20)//DS0闪烁.
		{
			i=0;
			LED0=!LED0;
 		}
	}
}



#include "delay.h"
#include "sys.h"
////////////////////////////////////////////////////////////////////////////////// 	 
//如果使用OS,则包括下面的头文件(以ucos为例)即可.
#if SYSTEM_SUPPORT_OS
#include "includes.h"					//支持OS时,使用	  
#endif
//////////////////////////////////////////////////////////////////////////////////  
****//完整资料
*//***微信公众号:木子单片机****/
//All rights reserved
//********************************************************************************
//修改说明
//V1.1 20140803 
//1,delay_us,添加参数等于0判断,如果参数等于0,则直接退出. 
//2,修改ucosii下,delay_ms函数,加入OSLockNesting的判断,在进入中断后,也可以准确延时.
//V1.2 20150411  
//修改OS支持方式,以支持任意OS(不限于UCOSII和UCOSIII,理论上任意OS都可以支持)
//添加:delay_osrunning/delay_ostickspersec/delay_osintnesting三个宏定义
//添加:delay_osschedlock/delay_osschedunlock/delay_ostimedly三个函数
//V1.3 20150521
//修正UCOSIII支持时的2个bug:
//delay_tickspersec改为:delay_ostickspersec
//delay_intnesting改为:delay_osintnesting
////////////////////////////////////////////////////////////////////////////////// 

static u8  fac_us=0;							//us延时倍乘数			   
static u16 fac_ms=0;							//ms延时倍乘数,在os下,代表每个节拍的ms数
	
#if SYSTEM_SUPPORT_OS							//如果SYSTEM_SUPPORT_OS定义了,说明要支持OS了(不限于UCOS).
//当delay_us/delay_ms需要支持OS的时候需要三个与OS相关的宏定义和函数来支持
//首先是3个宏定义:
//    delay_osrunning:用于表示OS当前是否正在运行,以决定是否可以使用相关函数
//delay_ostickspersec:用于表示OS设定的时钟节拍,delay_init将根据这个参数来初始哈systick
// delay_osintnesting:用于表示OS中断嵌套级别,因为中断里面不可以调度,delay_ms使用该参数来决定如何运行
//然后是3个函数:
//  delay_osschedlock:用于锁定OS任务调度,禁止调度
//delay_osschedunlock:用于解锁OS任务调度,重新开启调度
//    delay_ostimedly:用于OS延时,可以引起任务调度.

//本例程仅作UCOSII和UCOSIII的支持,其他OS,请自行参考着移植
//支持UCOSII
#ifdef 	OS_CRITICAL_METHOD						//OS_CRITICAL_METHOD定义了,说明要支持UCOSII				
#define delay_osrunning		OSRunning			//OS是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec	OS_TICKS_PER_SEC	//OS时钟节拍,即每秒调度次数
#define delay_osintnesting 	OSIntNesting		//中断嵌套级别,即中断嵌套次数
#endif

//支持UCOSIII
#ifdef 	CPU_CFG_CRITICAL_METHOD					//CPU_CFG_CRITICAL_METHOD定义了,说明要支持UCOSIII	
#define delay_osrunning		OSRunning			//OS是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec	OSCfg_TickRate_Hz	//OS时钟节拍,即每秒调度次数
#define delay_osintnesting 	OSIntNestingCtr		//中断嵌套级别,即中断嵌套次数
#endif


//us级延时时,关闭任务调度(防止打断us级延迟)
void delay_osschedlock(void)
{
#ifdef CPU_CFG_CRITICAL_METHOD   			//使用UCOSIII
	OS_ERR err; 
	OSSchedLock(&err);						//UCOSIII的方式,禁止调度,防止打断us延时
#else										//否则UCOSII
	OSSchedLock();							//UCOSII的方式,禁止调度,防止打断us延时
#endif
}

//us级延时时,恢复任务调度
void delay_osschedunlock(void)
{	
#ifdef CPU_CFG_CRITICAL_METHOD   			//使用UCOSIII
	OS_ERR err; 
	OSSchedUnlock(&err);					//UCOSIII的方式,恢复调度
#else										//否则UCOSII
	OSSchedUnlock();						//UCOSII的方式,恢复调度
#endif
}

//调用OS自带的延时函数延时
//ticks:延时的节拍数
void delay_ostimedly(u32 ticks)
{
#ifdef CPU_CFG_CRITICAL_METHOD
	OS_ERR err; 
	OSTimeDly(ticks,OS_OPT_TIME_PERIODIC,&err);//UCOSIII延时采用周期模式
#else
	OSTimeDly(ticks);						//UCOSII延时
#endif 
}
 
//systick中断服务函数,使用OS时用到
void SysTick_Handler(void)
{	
	if(delay_osrunning==1)					//OS开始跑了,才执行正常的调度处理
	{
		OSIntEnter();						//进入中断
		OSTimeTick();       				//调用ucos的时钟服务程序               
		OSIntExit();       	 				//触发任务切换软中断
	}
}
#endif
			   
//初始化延迟函数
//当使用OS的时候,此函数会初始化OS的时钟节拍
//SYSTICK的时钟固定为AHB时钟的1/8
//SYSCLK:系统时钟频率
void delay_init(u8 SYSCLK)
{
#if SYSTEM_SUPPORT_OS 						//如果需要支持OS.
	u32 reload;
#endif
 	SysTick->CTRL&=~(1<<2);					//SYSTICK使用外部时钟源	 
	fac_us=SYSCLK/8;						//不论是否使用OS,fac_us都需要使用
#if SYSTEM_SUPPORT_OS 						//如果需要支持OS.
	reload=SYSCLK/8;						//每秒钟的计数次数 单位为M	   
	reload*=1000000/delay_ostickspersec;	//根据delay_ostickspersec设定溢出时间
											//reload为24位寄存器,最大值:16777216,在168M下,约合0.7989s左右	
	fac_ms=1000/delay_ostickspersec;		//代表OS可以延时的最少单位	   
	SysTick->CTRL|=1<<1;   					//开启SYSTICK中断
	SysTick->LOAD=reload; 					//每1/delay_ostickspersec秒中断一次	
	SysTick->CTRL|=1<<0;   					//开启SYSTICK    
#else
	fac_ms=(u16)fac_us*1000;				//非OS下,代表每个ms需要的systick时钟数   
#endif
}								    

#if SYSTEM_SUPPORT_OS 						//如果需要支持OS.
//延时nus
//nus:要延时的us数.	
//nus:0~204522252(最大值即2^32/fac_us@fac_us=21)	    								   
void delay_us(u32 nus)
{		
	u32 ticks;
	u32 told,tnow,tcnt=0;
	u32 reload=SysTick->LOAD;				//LOAD的值	    	 
	ticks=nus*fac_us; 						//需要的节拍数 
	delay_osschedlock();					//阻止OS调度,防止打断us延时
	told=SysTick->VAL;        				//刚进入时的计数器值
	while(1)
	{
		tnow=SysTick->VAL;	
		if(tnow!=told)
		{	    
			if(tnow<told)tcnt+=told-tnow;	//这里注意一下SYSTICK是一个递减的计数器就可以了.
			else tcnt+=reload-tnow+told;	    
			told=tnow;
			if(tcnt>=ticks)break;			//时间超过/等于要延迟的时间,则退出.
		}  
	};
	delay_osschedunlock();					//恢复OS调度											    
}  
//延时nms
//nms:要延时的ms数
//nms:0~65535
void delay_ms(u16 nms)
{	
	if(delay_osrunning&&delay_osintnesting==0)//如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度)	    
	{		 
		if(nms>=fac_ms)						//延时的时间大于OS的最少时间周期 
		{ 
   			delay_ostimedly(nms/fac_ms);	//OS延时
		}
		nms%=fac_ms;						//OS已经无法提供这么小的延时了,采用普通方式延时    
	}
	delay_us((u32)(nms*1000));				//普通方式延时
}
#else  //不用ucos时
//延时nus
//nus为要延时的us数.	
//注意:nus的值,不要大于798915us(最大值即2^24/fac_us@fac_us=21)
void delay_us(u32 nus)
{		
	u32 temp;	    	 
	SysTick->LOAD=nus*fac_us; 				//时间加载	  		 
	SysTick->VAL=0x00;        				//清空计数器
	SysTick->CTRL=0x01 ;      				//开始倒数 	 
	do
	{
		temp=SysTick->CTRL;
	}while((temp&0x01)&&!(temp&(1<<16)));	//等待时间到达   
	SysTick->CTRL=0x00;      	 			//关闭计数器
	SysTick->VAL =0X00;       				//清空计数器 
}
//延时nms
//注意nms的范围
//SysTick->LOAD为24位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK单位为Hz,nms单位为ms
//对168M条件下,nms<=798ms 
void delay_xms(u16 nms)
{	 		  	  
	u32 temp;		   
	SysTick->LOAD=(u32)nms*fac_ms;			//时间加载(SysTick->LOAD为24bit)
	SysTick->VAL =0x00;           			//清空计数器
	SysTick->CTRL=0x01 ;          			//开始倒数  
	do
	{
		temp=SysTick->CTRL;
	}while((temp&0x01)&&!(temp&(1<<16)));	//等待时间到达   
	SysTick->CTRL=0x00;       				//关闭计数器
	SysTick->VAL =0X00;     		  		//清空计数器	  	    
} 
//延时nms 
//nms:0~65535
void delay_ms(u16 nms)
{	 	 
	u8 repeat=nms/540;						//这里用540,是考虑到某些客户可能超频使用,
											//比如超频到248M的时候,delay_xms最大只能延时541ms左右了
	u16 remain=nms%540;
	while(repeat)
	{
		delay_xms(540);
		repeat--;
	}
	if(remain)delay_xms(remain);
} 
#endif
			 
















































硬件设计

使用元器件:

单片机:STM32;

添加图片注释,不超过 140 字(可选)

设计资料

01程序

本设计使用软件Keil5 MDK版本编程设计!具体如图!

添加图片注释,不超过 140 字(可选)

02设计报告

一万两千字设计报告,具体如图!

添加图片注释,不超过 140 字(可选)

03设计资料

全部资料包括程序(含注释)、设计报告等。具体内容如下,全网最全! !

添加图片注释,不超过 140 字(可选)

点赞分享一起学习成长。

更多推荐