趁着现在刚做完这个小项目,脑子里的感觉还新鲜,赶紧写写文章来总结一下,方便日后查看。
本文主要分两大部分来展开,先总结体会心得,再具体说说怎么做
目录
心得体会
思考方式
这种思考方式主要是把“是什么”、“怎么用”这两方面解决就行了,先明确我要干嘛,我在干嘛,这在我的上一个小项目里面也用到。
比如在学习如何使用串口的时候,我把串口的几个参数搞明白是什么,以及如何用串口发送函数、怎么处理接收中断就行了。在调试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坏掉的情况都无法检查,因此就往这个方向去找问题了。解决方法: