今天是我大二寒假回家后的第一天,回想起大二第一学期所经历过的事情,真可谓是“多灾多难”的一个学期,我第一次体会到什么叫做欲哭无泪。
一学期内一共做了60多个实验(物理实验18个,微原实验4个,电路实验4个,数电实验6个,通信试验32个),一个人参加星火杯,一个人完成了数电大作业(虽然这个大作业是将我做的星火杯作品移植过去的),参加了数学竞赛,还报名了有方杯和美赛。同时我们的国庆假期被吞了,而且我还在学期初发烧,被隔离了一个星期。经历了这些的“摧残”后,我觉得我自己真正意义上成功从高中生转变为了大学生,我的个人学习能力有了质的飞跃,也成功变成一个不知道什么是休息的人了(都是被逼无奈啊)。
倒了这么多的苦水,接下来就该介绍我第一次个人设计的硬件项目,基于stm32的宿舍自动开关门系统。
我所在的宿舍是丁香13号楼中(1)室,住过中(1)室的同学都有过这样的体会:宿舍占地面积小,并且宿舍门正对卫生间。只要卫生间上面的窗户不关,便会有大量的冷风涌入宿舍。而我所在的床位又靠近宿舍门,当有人员出入且未关门时会有冷风吹向我导致我被冻醒或感冒。当我发现这个问题时,我对我的星火杯作品有了想法。
首先我从网上搜索资料,发现现有的自动开关门都是依靠机械完成的,例如下图
我再次搜索有无学生自己设计的开关门,发现当时仅有一个用51单片机设计的开关门,但是他的设计思路是依靠步进电机拉动固定在门上的木板进而产生力矩来拉动门,但是经过我的实验这个设计思路完全是不现实的,因为木板是固定在门上的,因此产生的力矩是内力,根本无法拉动门,这就好比是通过让一个人斜向下拉动门把手来开门。不过这个设计给了我一个思路,就是要用步进电机来实现开关门。要想拉动门就必须要通过一个外力,而这个外力可以通过拴在天花板上的绳子和步进电机连接来实现。
我又想起我在大一星火杯制作的“智障小车”[1]里面包含的红外接收模块和自动避障模块,因此整个项目的大体思路就确定了,即通过红外遥控器控制该系统,分为三个功能:功能一为遥控开门,当按下相应的按键后蜂鸣器响0.05秒,电磁继电器闭合,与电磁继电器连接的电磁铁与电源连接进而模拟开门锁插销的动作,两个步进电机分别正转一段时间再反转相同时间。这样做的原因是为了避免绕在步进电机上的绳子紧绷导致人无法正常关门,其余功能的步进电机正转再反转的原因相同。功能二为遥控关门,与功能一不同的是两个步进电机的转动方向相反。功能三为自动关门,当红外避障模块检测到一定范围内无物体,步进电机则会在一段时间后转动进而实现自动关门。
【1】:大一的时候由于星火杯是同学们接触的第一个大型校内比赛,而同学们大多没有硬件软件基础,因此星火杯作品有很大一部分都是大一的同学们买来的各种智能小车散件然后自己焊接起来的。由于大家都不熟悉智能小车内部的程序和硬件原理,因此智能小车在我们私底下经常被戏称为“智障小车”。我当时做的智能小车在星火杯验收时功能正常,但在大一寒假回家时发现由于虚焊导致无法正常工作了。
我选择的单片机是stm32f103c8t6最小系统板,因为我在大一下学期的微原课上学习的就是这个,并且它便宜功能完全够用,还有库函数供我使用,这样我整体的开发速度会快很多。
这个部分是我自认为整个项目中最麻烦的,因为它涉及的知识包括通用计时器的输入捕获、NEC协议解码。当我从正点原子学习完这两部分后,我发现正点原子有解NEC协议的代码,我最终将代码复制下来,改了一下参数。
//红外遥控初始化
//设置IO以及定时器4的输入捕获
void Remote_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //使能PORTB时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE); //TIM4 时钟使能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; //PB8输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //上拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_7); //初始化GPIOB.9
TIM_TimeBaseStructure.TIM_Period = 10000; //设定定时器自动重装值 最大10ms溢出
TIM_TimeBaseStructure.TIM_Prescaler =(72-1); //预分频器,1M的计数频率,1us加1
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_ICInitStructure.TIM_Channel = TIM_Channel_2; // 选择输入端 IC3映射到TI4上
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM_ICInitStructure.TIM_ICFilter = 0x03;//IC4F=0011 配置输入滤波器 8个定时器时钟周期滤波
TIM_ICInit(TIM4, &TIM_ICInitStructure);//初始化定时器输入捕获通道
TIM_Cmd(TIM4,ENABLE); //使能定时器4
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; //TIM4中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占优先级1级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级3级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
TIM_ITConfig( TIM4,TIM_IT_Update|TIM_IT_CC2,ENABLE);//允许更新中断 允许CC3IE捕获中断
}
//处理红外键盘
//返回值:
// 0,没有任何键按下
//其他,按下的按键键值
u8 RmtSta=0;
u16 Dval; //下降沿时计数器的值
u32 RmtRec=0; //红外接收到的数据
u8 RmtCnt=0; //按键按下的次数
void TIM4_IRQHandler(void)
{
#define RDATA PBin(7) //红外数据输入脚
//遥控器接收状态
//[7]:收到了引导码标志
//[6]:得到了一个按键的所有信息
//[5]:保留
//[4]:标记上升沿是否已经被捕获
//[3:0]:溢出计时器
//定时器 4 中断服务程序
if(TIM_GetITStatus(TIM4,TIM_IT_Update)!=RESET)
{
if(RmtSta&0x80) //上次有数据被接收到了
{
RmtSta&=~0X10; //取消上升沿已经被捕获标记
if((RmtSta&0X0F)==0X00)RmtSta|=1<<6; //标记已经完成一次键值信息采集
if((RmtSta&0X0F)<14)RmtSta++;
else
{
RmtSta&=~(1<<7); //清空引导标识
RmtSta&=0XF0; //清空计数器
}
}
}
if(TIM_GetITStatus(TIM4,TIM_IT_CC2)!=RESET)
{
if(RDATA)//上升沿捕获
{
TIM_OC2PolarityConfig(TIM4,TIM_ICPolarity_Falling); //CC4P=1 下降沿捕获
TIM_SetCounter(TIM4,0); //清空定时器值
RmtSta|=0X10; //标记上升沿已经被捕获
}else //下降沿捕获
{
Dval=TIM_GetCapture2(TIM4); //读取 CCR4 也可以清 CC4IF 标志位
TIM_OC2PolarityConfig(TIM4,TIM_ICPolarity_Rising); //CC4P=0 /上升沿捕获
if(RmtSta&0X10) //完成一次高电平捕获
{
if(RmtSta&0X80)///接收到了引导码
{
if(Dval>300&&Dval<800) //560 为标准值,560us
{
RmtRec<<=1; //左移一位
RmtRec|=0; //接收到 0
}else if(Dval>1400&&Dval<1800) //1680 为标准值,1680us
{
RmtRec<<=1; //左移一位
RmtRec|=1; //接收到 1
}else if(Dval>2200&&Dval<2600) //得到按键键值增加的信息 2500 为标准值 2.5ms
{
RmtCnt++; //按键次数增加 1 次
RmtSta&=0XF0; //清空计时器
}
}else if(Dval>4200&&Dval<4700) //4500 为标准值 4.5ms
{
RmtSta|=1<<7; //标记成功接收到了引导码
RmtCnt=0; //清除按键次数计数器
}
}
RmtSta&=~(1<<4);
}
}
TIM_ClearFlag(TIM4,TIM_IT_Update|TIM_IT_CC2);
}
u8 Remote_Scan(void)
{
u8 sta=0;
u8 t1,t2;
if(RmtSta&(1<<6))//得到一个按键的所有信息了
{
t1=RmtRec>>24; //得到地址码
t2=(RmtRec>>16)&0xff; //得到地址反码
if((t1==(u8)~t2)&&t1==0)//检验遥控识别码(ID)及地址
{
t1=RmtRec>>8;
t2=RmtRec;
if(t1==(u8)~t2)sta=t1;//键值正确
}
if((sta==0)||((RmtSta&0X80)==0))//按键数据错误/遥控已经没有按下了
{
RmtSta&=~(1<<6);//清除接收到有效按键标识
RmtCnt=0; //清除按键次数计数器
}
}
return sta;
}
说句实在话,剩下的模块只需用到GPIO输入输出,在现在的我看来是十分简单的,因此我就只将代码从keil复制下来,无非就是使能一些IO口。
void Hummer_Init(void) //蜂鸣器初始化
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //使能PORTB时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //PB5输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_ResetBits(GPIOB,GPIO_Pin_5); //初始化GPIOB.5
}
void Irda_Init(void) //红外寻迹初始化
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //使能PORTB时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PB9 输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //上拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_9); //初始化GPIOB.9
}
void Electromagnetic_Init(void) //电磁继电器初始化
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能PORTA时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_ResetBits(GPIOA,GPIO_Pin_0); //初始化GPIOA.0
}
void Motor_Init(void) //步进电机初始化
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin=(GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);// A1 A2 A3 A4 A5 A6 A7输出
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_ResetBits(GPIOA,(GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7));//A1 A2 A3 A4 A5 A6 A7低电平
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //使能PORTB时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PB0 输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_ResetBits(GPIOB,GPIO_Pin_0); /初始化GPIOB.0
}
#define motor1_A PAout(1)
#define motor1_B PAout(2)
#define motor1_C PAout(3)
#define motor1_D PAout(4)
#define motor2_A PAout(5)
#define motor2_B PAout(6)
#define motor2_C PAout(7)
#define motor2_D PBout(0)
void Motor1_Corotation(void) //步进电机1正转
{
motor1_A =1;//A
delay_ms(1);
motor1_B=1;//AB
delay_ms(1);
motor1_A=0;//B
delay_ms(1);
motor1_C=1;//BC
delay_ms(1);
motor1_B=0;//C
delay_ms(1);
motor1_D=1;//CD
delay_ms(1);
motor1_C=0;//D
delay_ms(1);
motor1_A=1;//DA
delay_ms(1);
motor1_D=0;
}
void Motor1_Reversal(void) //步进电机1反转
{
motor1_D =1;//D
delay_ms(1);
motor1_C=1;//DC
delay_ms(1);
motor1_D=0;//C
delay_ms(1);
motor1_B=1;//CB
delay_ms(1);
motor1_C=0;//B
delay_ms(1);
motor1_A=1;//BA
delay_ms(1);
motor1_B=0;//A
delay_ms(1);
motor1_D=1;//AD
delay_ms(1);
motor1_A=0;
}
void Motor2_Corotation(void) //步进电机2正转
{
motor2_A =1;//A
delay_ms(2);
motor2_B=1;//AB
delay_ms(2);
motor2_A=0;//B
delay_ms(2);
motor2_C=1;//BC
delay_ms(2);
motor2_B=0;//C
delay_ms(2);
motor2_D=1;//CD
delay_ms(2);;
motor2_C=0;//D
delay_ms(2);
motor2_A=1;//DA
delay_ms(2);
motor2_D=0;
}
void Motor2_Reversal(void) //步进电机2反转
{
motor2_D =1;//D
delay_ms(2);
motor2_C=1;//DC
delay_ms(2);
motor2_D=0;//C
delay_ms(2);
motor2_B=1;//CB
delay_ms(2);
motor2_C=0;//B
delay_ms(2);
motor2_A=1;//BA
delay_ms(2);
motor2_B=0;//A
delay_ms(2);
motor2_D=1;//AD
delay_ms(2);
motor2_A=0;
}
#include "stm32f10x.h"
#include "delay.h"
#include "sys.h"
#include "Configuration.h"
#define Hummer PBout(5)
#define Electromagnetic PAout(0)
#define Irda PBin(9)
int main(void)
{
u8 key;
u8 key2;
//u8 t=0;
//u8 *str=0;
int v,i;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
Remote_Init(); //红外接收初始化
Hummer_Init(); //蜂鸣器初始化
Motor_Init(); //步进电机初始化
Electromagnetic_Init();//电磁继电器初始化
Irda_Init(); //红外寻迹初始化
Hummer=1;
delay_ms(50);
Hummer=0;
while(1)
{
key=Remote_Scan();
if(key==152) //按下‘-’关门
{
Hummer=1;
delay_ms(50);
Hummer=0; //蜂鸣器响0.05s
Electromagnetic=1; //电磁继电器启动,电磁铁吸合门锁
for(v=0;Irda==1;v++) //步进电机启动至关门完成
{
Motor2_Corotation(); //步进电机2正转
}
Electromagnetic=0; //电磁继电器关闭
for(;v>0;v--) //步进电机松绳子
{
Motor2_Reversal(); //步进电机2反转
}
key2=Remote_Scan();
if(key2!=152)
continue;
}
if(key==162) //按下‘power’开门
{
Hummer=1;
delay_ms(50);
Hummer=0; //蜂鸣器响0.05s
Electromagnetic=1; //电磁继电器启动,电磁铁吸合门锁
for(v=0; v<630;v++)
{
Motor1_Reversal(); //步进电机1反转
}
Electromagnetic=0; //电磁铁关闭
for(v=629; v>0;v--)
{
Motor1_Corotation(); //步进电机1正转,松绳子
}
key2=Remote_Scan();
if(key2!=162)
continue;
}
if(key==48) //按下‘1’自动关门
{
Hummer=1;
delay_ms(50);
Hummer=0; //蜂鸣器响0.05s
while(1)
{
key2=Remote_Scan();
if(Irda==1)
{
}
if(Irda==0)//门处于打开状态
{
for(v=800;v>0;v--)
{
delay_ms(10);
}//延时保证开门的人可以出去
Electromagnetic=1; //电磁继电器启动,电磁铁吸合门锁
for(v=0;Irda==1;v++) //步进电机启动至关门完成
{
Motor2_Corotation(); //步进电机2正转
}
Electromagnetic=0; //电磁铁关闭
for(;v>0;v--) //步进电机松绳子
{
Motor2_Reversal(); //步进电机2反转
}
}
if(key2==24)//按下‘2’退出自动模式
{
Hummer=1;
delay_ms(50);
Hummer=0; //蜂鸣器响0.05s
break;
}
}
}
}
}
由于我选用的步进电机是5-12v供电,因此仅靠stm32上3.3v的输出电压根本无法驱动。我选择外加一个四个干电池组成的电池盒来组成6v的电压来使它们工作。要注意的是步进电机的“+”端要连接到电池盒正极,而“-”端与电池盒的负极要和单片机的GND共地。同时我选用的电磁铁也是5-12v供电,我要想实现电磁铁的吸合,就必须要将它与电磁继电器串联,电磁继电器的另一个端口连接电池盒。这样当单片机给电磁继电器一个高电平时,电磁铁就可以与电池盒接通,进而实现电磁铁吸合功能。
这个项目是我大学本科期间第一次真正意义上的自己设计的项目(大一时的智障小车不算),历经了很多坎坷。我在大二开学时没有任何硬件基础,我甚至不知道蜂鸣器要靠三极管放大电压来驱动,也不知道单片机的电源不能靠两节干电池来实现,而是要靠稳压芯片asm117来实现。在最后焊接完成时,我发现我的红外接收头总是接触不良,我上网查找资料才知道要并联一个滤波电容,我作品上的那个104电容还是通过通院星火杯交流群临时找别人用1k电阻等价交换的。在编程方面我以前用的是keil4,但我发现要想通过库函数编程stm32要用到MDK,我又跟正点原子从头学起(我们微原课都是靠自己写代码,没有用到库函数,只有微原实验才用到库函数)。
这个项目是我在2020年11月份完成的,在现在的我(2021年1月13日)看来,这个项目的确过于简单了,没有太多的技术含量,得到校三等奖也情有可原(我当时认为可以得到校二等奖)。但是它给我带来的收获不仅仅是一份奖状这么简单,它成功带领我走近硬件的世界。并且正是有了这个基础,我的数电大作业便是将这个项目改成由FPGA驱动,将串行运算改成并行运算仅仅花了我3天的时间。我所在的专业是信息工程,无论我以后的方向是什么,我认为硬件是一个能将我所学到的知识成功转变为实践的一个桥梁,因此这次的项目我认为让我受益匪浅。我相信在未来的日子里我会将硬件融入到我的日常生活中,将它视做一个玩具,用来实现我天马行空的各种想法。