STM32F103RCT6实现智能门禁

趁着现在刚做完这个小项目,脑子里的感觉还新鲜,赶紧写写文章来总结一下,方便日后查看。

本文主要分两大部分来展开,先总结体会心得,再具体说说怎么做

心得体会

思考方式

这种思考方式主要是把“是什么”、“怎么用”这两方面解决就行了,先明确我要干嘛,我在干嘛,这在我的上一个小项目里面也用到。
比如在学习如何使用串口的时候,我把串口的几个参数搞明白是什么,以及如何用串口发送函数、怎么处理接收中断就行了。在调试AS608、SIM900A的时候,因为这些本身就已经模块化、函数化了,我并不需要了解指纹识别算法,SIM900A是如何发送、接收信息的,我只需要知道你给它什么指令,它就怎么去做,这样就已经能够达到目的了。

如何debug

每当调试新的模块,很多时候不是一下子就能成功的,往往遇到问题。
首先是想想自己对这个模块的理解吧,既然要用它,那么这一整套操作流程下来是怎么样的?找出来还有疑问、不清晰的地方,就我目前的体会而言,要多动手,不能让疑惑只留在脑海里,一般在解决疑问的过程就能解决问题。
例如调试SIM900A时,用4节1.5V电池给模块供电,电话打不进去,串口发送指令无响应,并且它上面D5、D6两个LED灯异常长亮,没有用户手册里面说的情况,一开始我也是挺懵的。就梳理了下发送指令的流程,感觉这个应该没什么问题,可能就是模块自身的问题吧,然后发现模块供电必须要求是5V 1A的,四节电池已经6A了,所以就改用三节电池看看,发现LED指示灯的情况符合手册说的供电不足,最后确定问题下来了。

具体做法

AS608指纹识别模块

主要是实现三个功能:录指纹、刷指纹和删除指纹
想了解原理的话可以看这个视频(我不是打广告的)
AS608原理讲解
了解原理后,结合用户手册流程图和例程理解,很快就懂了
在这里插入图片描述

#include "fingerprint.h"
#include "as608.h"
#include "led.h"
#include "pwm.h"
#include "key.h"
#include "delay.h"
#include "MOTOR.h"
u16 ValidN;//模块内有效模板个数

void Add_FR(void)
{
    u8 i=0,unlock=0,ensure,processnum=0;
    u16 ID;
    unlock = encryption();
    if(unlock == 1)
    {
        while(1)
        {
            switch(processnum)
            {
            case 0:
                i++;
                Remind();//提示用户操作,按下指纹
                while(PS_Sta != 1);//等待用户按下手指
                ensure=PS_GetImage();//等待用户按下,命令指纹模块采集图像至图像缓冲区
                if(ensure == 0x00)
                {
                    ensure=PS_GenChar(CharBuffer1);//生成特征,保存至缓冲区1
                    if(ensure==0x00)
                    {
                        i=0;
                        //Success();//提示成功了
                        processnum=1;//跳到第二步
                    }
                    else
                        Error();
                }
                else
                    Error();
                break;

            case 1:
                i++;

                Remind();//提示用户操作,按下指纹
                while(PS_Sta != 1);//等待用户按下手指
                ensure=PS_GetImage();//命令指纹模块采集图像至图像缓冲区
                if(ensure==0x00)
                {
                    ensure = PS_GenChar(CharBuffer2);//生成特征,保存至缓冲区2
                    if(ensure == 0x00)
                    {
                        i = 0;
                        processnum=2;//跳到第三步
                    }
                    else
                        Error();
                }
                else
                    Error();
                break;

            case 2:
                ensure=PS_Match();//比对缓冲区1和2两个特征是否一致
                if(ensure == 0x00)
                {
                    //Success();
                    processnum=3;//跳到第四步
                }
                else
                {
                    Error();
                    i=0;
                    processnum=0;//跳回第一步
                }
                break;

            case 3:
                ensure = PS_RegModel();//将CharBuffer1与CharBuffer2中的特征文件合并生成模块存于CharBuffer1与CharBuffer2
                if(ensure == 0x00)
                {
                    //Success();
                    processnum=4;//跳到第五步
                }
                else
                {
                    processnum = 0;
                    Error();
                }
                break;

            case 4:
                ensure=PS_ValidTempleteNum(&ValidN);//读取指纹个数
                if(ensure != 0x00)
                {
                    Error();
                }
//				ensure=PS_ReadSysPara(&AS608Para);//读AS608模块参数
//				if(ensure != 0x00)
//				{
//					Error();
//				}
//				do
                ID = ValidN;//ID递增
//				while(!(ID<300));
                ensure = PS_StoreChar(CharBuffer2, ID);//储存模板
                if(ensure == 0x00)
                {
                    Success();
                    return ;//返回空值,退出函数
                }
                else
                {
                    processnum=0;
                    Error();
                }
                break;

            }
            if(i == 5)//超过5次没有按手指则退出
            {
                Error();
                break;
            }
        }
    }
    else
        Error();
}

void press_FR(void)
{
	u8 i;
    SearchResult search;
    u8 ensure;
    ensure=PS_GetImage();
    if(ensure == 0x00)//获取图像成功
    {
        ensure=PS_GenChar(CharBuffer1);
        if(ensure == 0x00)//生成特征成功
        {
            ensure= PS_HighSpeedSearch(CharBuffer1,0,300,&search);
            if(ensure == 0x00)//搜索成功
            {
                Success();
				Motorcw();//开锁
				for(i=0;i<5;i++)
					delay_ms(1000);
				Motorccw();//关锁

            }
            else
                Error();
        }
        else
            Error();
    }
}

void Del_FR(void)
{
    u8 ensure,unlock = 0;
    u8 key_num;
    unlock = encryption();
    if(unlock == 1)
    {
        Remind();//提示用户操作
        do
            key_num = KEY_Scan(0);
        while(key_num == 0);

        if(key_num == KEY0_PRES)//key0清空指纹库
        {
            ensure=PS_Empty();//清空指纹库
            if(ensure == 0x00)
                Success();
            else
                Error();
        }
        if(key_num == KEY1_PRES)//key1删除序号最大的指纹,也就是最新录入的那个
        {
            ensure=PS_ValidTempleteNum(&ValidN);//读取指纹个数
            if(ensure == 0x00)
            {
                ensure = PS_DeletChar((ValidN-1),1);
                if(ensure == 0x00)
                    Success();
                else
                    Error();
            }
        }
    }
    else
        Error();
}

指纹模块所用串口2代码

#include "delay.h"
#include "usart2.h"
#include "stdarg.h"	 	 
#include "stdio.h"	 	 
#include "string.h"	 
#include "timer.h"
//USART2_TXD: PA2 
//USART2_RXD: PA3
//AS608指纹模块与单片机接线
/////////////////////////////
//Vi接3.3V
//Tx接PA3
//Rx接PA2
//GND接GND
//WAK接PA6
//Vt接3.3V
/////////////////////////////


//串口接收缓存区 	
u8 USART2_RX_BUF[USART2_MAX_RECV_LEN]; 				//接收缓冲,最大USART2_MAX_RECV_LEN个字节.
u8 USART2_TX_BUF[USART2_MAX_SEND_LEN]; 			  //发送缓冲,最大USART2_MAX_SEND_LEN字节

//通过判断接收连续2个字符之间的时间差不大于10ms来决定是不是一次连续的数据.
//如果2个字符接收间隔超过10ms,则认为不是1次连续数据.也就是超过10ms没有接收到
//任何数据,则表示此次接收完毕.
//接收到的数据状态
//[15]:0,没有接收到数据;1,接收到了一批数据.
//[14:0]:接收到的数据长度
vu16 USART2_RX_STA=0;   	


void USART2_IRQHandler(void)
{
	u8 res;	      
	if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)//接收到数据
	{	 
		res =USART_ReceiveData(USART2);		 
		if((USART2_RX_STA&(1<<15))==0)//接收完的一批数据,还没有被处理,则不再接收其他数据
		{ 
			if(USART2_RX_STA<USART2_MAX_RECV_LEN)	//还可以接收数据
			{
				TIM_SetCounter(TIM5,0);//计数器清空          				//计数器清空
				if(USART2_RX_STA==0) 				//使能定时器5的中断 
				{
					TIM_Cmd(TIM5,ENABLE);//使能定时器5
				}
				USART2_RX_BUF[USART2_RX_STA++]=res;	//记录接收到的值	 
			}else 
			{
				USART2_RX_STA|=1<<15;				//强制标记接收完成
			} 
		}
	}  				 											 
}   


//初始化IO 串口2
//pclk1:PCLK1时钟频率(Mhz)
//bound:波特率	  
void usart2_init(u32 bound)
{  

	NVIC_InitTypeDef NVIC_InitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	// GPIOA时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //串口2时钟使能

 	USART_DeInit(USART2);  //复位串口2
		 //USART2_TX   PA2
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA2
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
  GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA2
   
    //USART2_RX	  PA3
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//浮空输入
  GPIO_Init(GPIOB, &GPIO_InitStructure);  //初始化PA3
	
	USART_InitStructure.USART_BaudRate = bound;//波特率设置
	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(USART2, &USART_InitStructure); //初始化串口2
  

	USART_Cmd(USART2, ENABLE);                    //使能串口 
	
	//使能接收中断
  USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启中断   
	
	//设置中断优先级
	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
	
	
	TIM5_Int_Init(99,7199);		//10ms中断
	USART2_RX_STA=0;		//清零
	TIM_Cmd(TIM5,DISABLE);			//关闭定时器5

}

//串口2,printf 函数
//确保一次发送数据不超过USART2_MAX_SEND_LEN字节
void u2_printf(char* fmt,...)  
{  
	u16 i,j; 
	va_list ap; 
	va_start(ap,fmt);
	vsprintf((char*)USART2_TX_BUF,fmt,ap);
	va_end(ap);
	i=strlen((const char*)USART2_TX_BUF);		//此次发送数据的长度
	for(j=0;j<i;j++)							//循环发送数据
	{
	  while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET); //循环发送,直到发送完毕   
		USART_SendData(USART2,USART2_TX_BUF[j]); 
	} 
}


//串口接收数据时用到的定时器5

//定时器5中断服务程序		    
void TIM5_IRQHandler(void)
{ 	
	if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)//是更新中断
	{	 			   
		USART2_RX_STA|=1<<15;	//标记接收完成
		TIM_ClearITPendingBit(TIM5, TIM_IT_Update  );  //清除TIM5更新中断标志    
		TIM_Cmd(TIM5, DISABLE);  //关闭TIM5 
	}	    
}
 
//通用定时器5中断初始化
//这里时钟选择为APB1的2倍,而APB1为42M
//arr:自动重装值。
//psc:时钟预分频数
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定时器工作频率,单位:Mhz 
//通用定时器中断初始化
//这里始终选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数		 
void TIM5_Int_Init(u16 arr,u16 psc)
{	
	NVIC_InitTypeDef NVIC_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);//TIM5时钟使能    
	
	//定时器TIM5初始化
	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
 
	TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE ); //使能指定的TIM5中断,允许更新中断
	
	TIM_Cmd(TIM5,ENABLE);//开启定时器5
	
	NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;//抢占优先级0
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;		//子优先级2
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
	
}

SIM900A短信模块

SIM900A有许多功能,像打电话、接电话、GPRS等等,这里只用到发短信功能。只需要通过串口发送相关AT指令,收到正确的应答即可。
发短信需要哪几种AT指令,发送流程是怎么样的,这些都可以在其用户手册里面找到,这里不再赘述了。
以下代码是向目标手机发送英文短信的:

#include "sim900a.h"
#include "usart3.h"
#include "string.h"
#include "delay.h"
#include "usart.h"
u8 status=0;


//向sim900A发送命令
//cmd:发送的命令字符串(不需要添加回车了),当cmd<0XFF时,发送数字(比如发送0X1A),大于的时候发送字符串
//ack:期待的应答结果,如果为空,则表示不需要等待应答
//waittime:等待时间(单位:10ms)
//返回值:0,发送成功(得到了期待的应答结果)
//		  1,发送失败
u8 sim900a_send_cmd(u8 *cmd, u8 *ack, u16 waittime)
{
	u8 res = 0;
	USART3_RX_STA=0;
	if((u32)cmd<=0XFF)
	{
		while(DMA1_Channel2->CNDTR!=0);//等待通道2传输完成
		USART3->DR=(u32)cmd;
	}
	else
		u3_printf("%s\r\n", cmd);//向串口发送命令
	if(ack&&waittime)//如果需要等待应答
	{
		while(--waittime)//等待倒计时
		{
			delay_ms(10);
			if(USART3_RX_STA&0X8000)//接收到期待的应答结果
			{
				if(sim900a_check_cmd(ack))//检查所接受到的应答是否没期待值
					break;
				USART3_RX_STA=0;
			}
		}
		if(waittime==0)
			res = 1;
	}
	return res;	
}

//sim900a发送命令后,检测接收到的应答
//str:期待的应答结果
//返回值:0,没有得到期待的应答结果
//		其他,期待应答结果的位置(str的位置)
u8* sim900a_check_cmd(u8 *str)
{
	char *strx = 0;
	if(USART3_RX_STA&0X8000)//接收到一次数据了
	{
		USART3_RX_BUF[USART3_RX_STA&0X7FFF] = 0;//添加结束符
		strx = strstr((const char*)USART3_RX_BUF, (const char*)str);//strstr这个函数是用来找USART3_RX_BUF里面有没有str这个字符串
		
	}
	return (u8*)strx;
}

//1:发送AT指令出错
//2:SIM卡出错
//3:查询不到运营商
u8 sim900a_check_status()
{
	if(sim900a_send_cmd("AT+CPIN?","OK",200))
		return 2;	
	if(sim900a_send_cmd("AT+CGMI","OK",200))
		return 3;
	
	return 0;
}

u8 sim900a_sms_test(u8* msisdn)//输入参数为手机号码
{
	//在进入这个函数之前,需要事先发送"AT"同步波特率,并接受到"OK"
	char cmd[20];
	
	status=sim900a_check_status();
	if(status)
		return status;
	
	if(sim900a_send_cmd("AT+CMGF=1","OK",200))
		return 4;//设置文本模式
	if(sim900a_send_cmd("AT+CSCS=\"GSM\"","OK",200))
		return 5;//设置TE字符集为GSM
	sprintf((char*)cmd, "AT+CMGS=\"%s\"", msisdn);//命令格式:AT+CMGS='xxx'
	
	if(sim900a_send_cmd((u8*)cmd, ">",200))
		return 6;//设置短信息文本模式参数
	
	u3_printf("%s", "dangerous ");
	if(sim900a_send_cmd((u8*)0X1A, "+CMGS:", 1000))
		return 7;//发送结束符
	
	return 0;
	
	
}

SIM900A所用串口3代码

#include "delay.h"
#include "usart3.h"
#include "stdarg.h"
#include "stdio.h"
#include "string.h"
//USART3_TXD: PB10
//USART3_RXD: PB11
//与SIM900A引脚连接
/////////////////////////////////
//GND接GND,共地     Vcc.mcu接5V或者3.3V,为串口TTL电平大小
//如果Vcc.mcu接5V,PB10接5VR,PB11接5VT
//如果Vcc.mcu接3.3V,PB10接3VR,PB11接3VT
/////////////////////////////////

//串口接收缓存区
u8 USART3_RX_BUF[USART3_MAX_RECV_LEN];//接收缓冲,最大USART3_MAX_RECV_LEN个字节
u8 USART3_TX_BUF[USART3_MAX_SEND_LEN];//发送缓冲,最大USART3_MAX_SEND_LEN个字节


//判断接收的两个字符之间的时间差是否大于10ms来判断是不是一次连续的数据
//如何两个字符间隔大于10ms,则判断不是1次连续的数据
//相当于自定义了通信协议
u16 USART3_RX_STA=0;
void USART3_IRQHandler(void)
{
    u8 res;
    if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)//接收到一个字节产生中断
    {
        res = USART_ReceiveData(USART3);
        if((USART3_RX_STA&(1<<15))==0)
        {
            if(USART3_RX_STA<USART3_MAX_RECV_LEN)//还可以接收数据
            {
                TIM_SetCounter(TIM4, 0);//计数器清空
                if(USART3_RX_STA == 0)
                    TIM4_Set(1);//从0开始接收到第一个数,启动TIM4判断间隔
                USART3_RX_BUF[USART3_RX_STA]= res; //记录接收到的值
                USART3_RX_STA++;
            }
            else
            {
                USART3_RX_STA |= 1<<15;//强制标记接收完成,因为已经超出存储范围
            }
        }
    }


}

//初始化IO 串口3
//pclk1:PCLK1时钟频率(Mhz)
//bound:波特率
void usart3_init(u32 bound)
{

    NVIC_InitTypeDef NVIC_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	// GPIOB时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE); //串口3时钟使能

    USART_DeInit(USART3);  //复位串口3
    //USART3_TX   PB10
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PB10
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
    GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化PB10

    //USART3_RX	  PB11
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
    GPIO_Init(GPIOB, &GPIO_InitStructure);  //初始化PB11

    USART_InitStructure.USART_BaudRate = bound;//波特率一般设置为9600;
    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_Cmd(USART3, ENABLE);                    //使能串口
	USART_DMACmd(USART3,USART_DMAReq_Tx,ENABLE);  	//使能串口2的DMA发送
	UART_DMA_Config(DMA1_Channel2,(u32)&USART3->DR,(u32)USART3_TX_BUF);//DMA1通道7,外设为串口2,存储器为USART2_TX_BUF 

    //使能接收中断
    USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);//开启中断

    //设置中断优先级
    NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;//抢占优先级3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器

    TIM4_Init(99,7199);//10ms中断
    USART3_RX_STA=0;		//清零
    TIM4_Set(0);//关闭定时器4
}

//串口3的printf 函数
//确保一次发送数据不超过USART3_MAX_SEND_LEN字节
void u3_printf(char* fmt,...)
{
    va_list ap;
    va_start(ap,fmt);
    vsprintf((char*)USART3_TX_BUF, fmt, ap);
    va_end(ap);
//    i = strlen((const char*)USART3_TX_BUF);//此次发送数据的长度
//    for(j=0; j<i; j++)
//    {
//        while(USART_GetFlagStatus(USART3, USART_FLAG_TC)== RESET);//循环发送,直到发送完毕
//        USART_SendData(USART3, USART3_TX_BUF[j]);//把格式化字符串从开发板串口送出去
//    }
	while(DMA1_Channel2->CNDTR!=0);//等待通道2传输完成
	UART_DMA_Enable(DMA1_Channel2, strlen((const char*)USART3_TX_BUF));//通过DMA发送出去

}


//定时器4中断服务程序
void TIM4_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)//是更新中断
    {
        USART3_RX_STA|=1<<15;	//标记接收完成
        TIM_ClearITPendingBit(TIM4, TIM_IT_Update  );  //清除TIMx更新中断标志
        TIM4_Set(0);			//关闭TIM4
    }
}
//设置TIM4的开关
//sta:0,关闭;1,开启;
void TIM4_Set(u8 sta)
{
    if(sta)
    {

        TIM_SetCounter(TIM4,0);//计数器清空
        TIM_Cmd(TIM4, ENABLE);  //使能TIMx
    } else TIM_Cmd(TIM4, DISABLE);//关闭定时器4
}
//通用定时器中断初始化
//这里始终选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
void TIM4_Init(u16 arr,u16 psc)
{   NVIC_InitTypeDef NVIC_InitStructure;
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //时钟使能//TIM4时钟使能

    //定时器TIM3初始化
    TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
    TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
    TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位

    TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE ); //使能指定的TIM4中断,允许更新中断


    NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;//抢占优先级3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;		//子优先级3
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器

}


//DMA1的各通道配置
//这里的传输形式是固定的,这点要根据不同的情况来修改
//从存储器->外设模式/8位数据宽度/存储器增量模式
//DMA_CHx:DMA通道CHx
//cpar:外设地址
//cmar:存储器地址
void UART_DMA_Config(DMA_Channel_TypeDef*DMA_CHx,u32 cpar,u32 cmar)
{
	DMA_InitTypeDef DMA_InitStructure;
 	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);	//使能DMA传输
  DMA_DeInit(DMA_CHx);   //将DMA的通道1寄存器重设为缺省值
	DMA_InitStructure.DMA_PeripheralBaseAddr = cpar;  //DMA外设ADC基地址
	DMA_InitStructure.DMA_MemoryBaseAddr = cmar;  //DMA内存基地址
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;  //数据传输方向,从内存读取发送到外设
	DMA_InitStructure.DMA_BufferSize = 0;  //DMA通道的DMA缓存的大小
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  //外设地址寄存器不变
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //内存地址寄存器递增
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;  //数据宽度为8位
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  //工作在正常缓存模式
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级 
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //DMA通道x没有设置为内存到内存传输
	DMA_Init(DMA_CHx, &DMA_InitStructure);  //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器	
} 
//开启一次DMA传输
void UART_DMA_Enable(DMA_Channel_TypeDef*DMA_CHx,u16 len)
{
	DMA_Cmd(DMA_CHx, DISABLE );  //关闭 指示的通道        
	DMA_SetCurrDataCounter(DMA_CHx,len);//DMA通道的DMA缓存的大小	
	DMA_Cmd(DMA_CHx, ENABLE);           //开启DMA传输
}	   






蜂鸣器模块

蜂鸣器主要作用是用来提醒用户操作出错,因为用的是无源蜂鸣器,需要PWM波驱动,这里给出PWM波的代码

//无源蜂鸣器模块
//IO口接PB5
//VCC接5V或者3.3V
//GND接地
#include "pwm.h"
#include "usart.h"
#include "sys.h"
#include "stm32f10x_tim.h"
#include "delay.h"
   	  
//通用定时器3中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能

	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	 计数到5000为500ms
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值  10Khz的计数频率  
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
 
	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断

	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  //TIM3中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //从优先级3级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器

	TIM_Cmd(TIM3, ENABLE);  //使能TIMx外设
							 
}
//定时器3中断服务程序
void TIM3_IRQHandler(void)   //TIM3中断
{
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源 
		{
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update  );  //清除TIMx的中断待处理位:TIM 中断源 
		}
}




//TIM3 PWM部分初始化 
//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM3_PWM_Init(u16 arr,u16 psc)
{  
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);	//使能定时器3时钟
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB  | RCC_APB2Periph_AFIO, ENABLE);  //使能GPIO外设和AFIO复用功能模块时钟
	
	GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射  TIM3_CH2->PB5    
 
   //设置该引脚为复用输出功能,输出TIM3 CH2的PWM脉冲波形	GPIOB.5
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
 
   //初始化TIM3
	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
	
	//初始化TIM3 Channel2 PWM模式	 
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
 	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
	TIM_OC2Init(TIM3, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM3 OC2

	TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);  //使能TIM3在CCR2上的预装载寄存器
 
	TIM_Cmd(TIM3, ENABLE);  //使能TIM3
	
}

void Error(void)//提醒用户出错
{
	TIM_SetCompare2(TIM3, 9000);	
	delay_ms(1000);
	TIM_SetCompare2(TIM3, 0);
}

步进电机28BYJ-48

步进电机需要ULN2003来驱动,工作原理可看这个
步进电机原理
原理博文

我采用的是半步驱动方式,时序图如下
在这里插入图片描述
A,B,A‘,B’分别代表IN1,IN2,IN3,IN4,也就是单片机的四个引脚,正转就是从左到右按时序图来写程序就行,反转就是从右往左来看。
我采用的是比较直观的方法——直接设置引脚的高低电平,其实也可以通过PWM波来实现,把周期计算好就行。

代码如下

#include "sys.h"
#include "MOTOR.h"
#include "delay.h"

//只控制电机正反转即可
//引脚连接
//IN1:PC3  IN2:PC2  IN3:PC0  IN4:PC13

//步进电机初始化
void Motor_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	
	GPIO_InitStructure.GPIO_Pin = IN1;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = IN2;
	GPIO_Init(GPIOC, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin =IN3;
	GPIO_Init(GPIOC, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin =IN4;
	GPIO_Init(GPIOC, &GPIO_InitStructure);

	Motor_Stop();
}

void Motor_Start(void)
{
	GPIO_SetBits(GPIOC, IN1);
	delay_ms(1);
}

void Motor_Stop(void)
{
	GPIO_ResetBits(GPIOC, IN1);
	GPIO_ResetBits(GPIOC, IN2);
	GPIO_ResetBits(GPIOC, IN3);
	GPIO_ResetBits(GPIOC, IN4);
}

//步进电机正转函数
void Motorcw(void)
{
	u8 i;
	Motor_Start();
	for(i=0;i<130;i++)
	{
	GPIO_SetBits(GPIOC, IN2);
	delay_ms(1);
	GPIO_ResetBits(GPIOC, IN1);
	delay_ms(1);
	GPIO_SetBits(GPIOC, IN3);
	delay_ms(1);
	GPIO_ResetBits(GPIOC, IN2);
	delay_ms(1);
	GPIO_SetBits(GPIOC, IN4);
	delay_ms(1);
	GPIO_ResetBits(GPIOC, IN3);
	delay_ms(1);
	GPIO_SetBits(GPIOC, IN1);
	delay_ms(1);
	GPIO_ResetBits(GPIOC, IN4);
	delay_ms(1);
	}
	Motor_Stop();
}

void Motorccw(void)
{
	u8 i;
	Motor_Start();
	for(i=0;i<130;i++)
	{
	GPIO_SetBits(GPIOC, IN4);
	delay_ms(1);
	GPIO_ResetBits(GPIOC, IN1);
	delay_ms(1);
	GPIO_SetBits(GPIOC, IN3);
	delay_ms(1);
	GPIO_ResetBits(GPIOC, IN4);
	delay_ms(1);
	GPIO_SetBits(GPIOC, IN2);
	delay_ms(1);
	GPIO_ResetBits(GPIOC, IN3);
	delay_ms(1);
	GPIO_SetBits(GPIOC, IN1);
	delay_ms(1);
	GPIO_ResetBits(GPIOC, IN2);
	delay_ms(1);
	}
	Motor_Stop();
}

注意事项

①供电问题。
SIM900A要求是DC5V 1A的电源,供电电压不能高于5V,我一开始用的就是6V供电,结果两盏LED指示灯异常长亮, 没有用户手册说的情况,找不到原因;供电电压低于5V的话就会出现供电不足的情况,D5反复长亮几秒灭一秒 ,D6 亮一秒灭一秒(快闪) 。后来采用5V 1A的电源适配器+转接线供电,注意与单片机共地,这样就成功了。
ULN2003模块不能用单片机上的5V电源供电,供电电压范围是5V-12V,电流要求较大,我采用的是4节AA电池总计6V,注意与单片机共地。
②驱动步进电机,各个引脚的状态持续时间要注意。
因为步进电机的原理是通电产生磁性吸引转子转动,如果每两个引脚之间的状态延时时间过短(结合上面代码),反应时间过短,通电线圈产生磁性的时间太短,转子没有完全到位,这样电机就会只抖动而不转动。
状态延续时间如果过长,因为步进角是很小的,在一次转动过程中,转子完全被吸引到相应位置后还要等待一段时间,转得非常慢。因此要调试好合适的延时时间,这里我采用的是1ms延时。注意,如果用的是原子例程里面的delay_ms()函数,对72MHz条件下,nms<=1864,也就是说最大延时为1.864秒。
③SIM900A卡座问题
如果用的是手机中的SIM卡,需要搭配卡套使用。
另外在调试过程中,D5长亮,D6亮一秒灭一秒(快闪) ,模块始终在搜寻网络,发送"AT"指令有回应,说明能同步波特率,但是打电话进去关机,发送"AT+CPIN?"查询模块是否检测到手机卡指令,收不到正确应答。确认问题在模块上面,唯一能动手检查的就是卡座,其他像SIM900A坏掉的情况都无法检查,因此就往这个方向去找问题了。解决方法:
在这里插入图片描述

原创文章 3 获赞 5 访问量 1372

猜你喜欢

转载自blog.csdn.net/ddddddddddda/article/details/105779357