基于STM32的外卖柜,使用红外模块检测外卖状态、舵机模式外卖门,使用GSM模块进行通信,利用矩阵按键进行用户交互。将外卖放入外卖柜,输入手机号码,系统将自动发送数据并携带验证码给我们输入的手机号,拿外卖的时候,我们需要输入验证码才可以打开外卖柜并取走外卖,实现整个智能存取过程。
一、仿真工程搭建
1、电路设计
本次设计为基于STM32的智能外卖柜,由于Proteus并没有GSM模块,所以本次设计利用串口进行设计,实际上STM32实现GSM功能也是通过UART进行与GSM模块的通信。由于Proteus矩阵按键的仿真效果并不好,这边为了系统可用,使用独立按键完成功能。并使用单刀双开关设计红外模块,实际上的红外传感器也是数字传感器,检测到物体输出0,否则为1。为了可视化,本次设计采用LCD1602进行显示,虽然正式硬件设计上使用的是0.96寸OLED,但是可以实现类似功能的仿真。如下图:
2、功能演示
仿真开始,LCD1602显示 Phone 的英文字体,此时我们可以输入号码,并且将红外打到低电平,此时外卖已经放入柜子中,串口会发送 111111111111 Your takeaway has arrived ,同时设置了超时提醒,当外卖放置时间过长,会发出提示:111111111111 You didn't take the takeaway
当我们将红外打到高电平,此时显示屏显示,has been taken,串口调试助手会显示 The takeaway has been picked up。
按下清零,被清空,此时可以进行下一次放置。
3、仿真总结
本次仿真设计借助了Proteus仿真,由于Proteus的限制,功能和实物呈现的效果有细微差异,但是大致仿真出了该系统功能。
二、硬件设计
综合上述仿真设计的经验,我们接下来需要设计硬件,由于本次设计硬件上并不是难点,一般只需要一下接口然后放置外部器件就可以实现,绘制PCB也仅仅是为了美观整合这些模块:如下图:
1、原理图
2、PCB图
3、最终效果图
本次设计在外观上呈现效果并不好,但是功能大致已经实现
三、软件设计
1、Unicode
本次设计的GSM模块利用串口进行通信,但是其编码一般要求是unicode格式,虽然也可以进行更改,将Unicode编码改为UTF-8,因为本次设计并不难,所以编码并不进行修改。号码是数字,量并不算大,所以在使用API函数进行编码转化并不是难事,如下:
/**********************************************************************
描述: ASCII 转 unicode 比如 '1' 转成 "0031"
***********************************************************************/
void ASCII_TO_Unicode(const char *ASCII,char *Unicode)
{
int length;
int i = 0;
int j = 0;
memset(Unicode,'\0',sizeof(Unicode));
length = strlen(ASCII);
for(i=0;i<length;i++)
{
Unicode[j++] = '0';
Unicode[j++] = '0';
Unicode[j++] = (ASCII[i] / 16) + 0x30;
Unicode[j++] = (ASCII[i] % 16) + 0x30;
}
}
但是一些中文用API进行编码会比较繁琐,本次设计中的中文只有固定两句,所以并没有必要进行灵活中文设计,我们可以通过一些网站进行中文转Unicode。借助如下工具便可完成 :在线 Unicode 编码转换 | 菜鸟工具 (jyshare.com)
2、系统功能设计
本次系统功能不同于仿真,本次系统使用STM32作为主控,矩阵按键作为用户交互器件,输入外卖收货人的手机号,输入完毕会给收货人发送信息并携带验证码,同时舵机驱动门关闭,红外检测外卖在柜子内状态。如果长时间未取,系统发送长时间未取的消息给用户手机。取外卖还需要核对系统随机给用户发送的验证码,如果验证码不正确,外卖将无法取出,验证码正确,舵机驱动门打开,用户取走外卖,系统恢复到初始状态。核心代码如下:
#include "stm32f10x.h"
#include "bsp_adc.h"
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "Led.h"
#include "MatrixKey.h"
#include "sys.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <wchar.h>
#include <locale.h>
#include <math.h>
#include "timer.h"
#include "Servo.h"
//配置传感器的GPIO
#define Sensor_ClockConfig RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE)
#define Sensor_GPIOx GPIOB
#define Sensor_GPIOPin0 GPIO_Pin_11
#define Senor PBin(11)
//红外传感器初始化
void Sensor_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
Sensor_ClockConfig;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = Sensor_GPIOPin0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(Sensor_GPIOx, &GPIO_InitStructure);
}
int dis=0;
uint8_t comfire=0;
uint32_t cnt=0;
uint8_t phoneFlag=0;
uint8_t layFlag=0;
char phone[12];
uint8_t index_p=0;
/**********************************************************************
描述: ASCII 转 unicode 比如 '1' 转成 "0031"
***********************************************************************/
void ASCII_TO_Unicode(const char *ASCII,char *Unicode)
{
int length;
int i = 0;
int j = 0;
memset(Unicode,'\0',sizeof(Unicode));
length = strlen(ASCII);
for(i=0;i<length;i++)
{
Unicode[j++] = '0';
Unicode[j++] = '0';
Unicode[j++] = (ASCII[i] / 16) + 0x30;
Unicode[j++] = (ASCII[i] % 16) + 0x30;
}
}
//GMS初始化
void GMS_Init()
{
printf("AT+CSQ\r\n");
Delay_ms(1000);//延时1秒
printf("AT+CMGF=1\r\n");//文本模式
Delay_ms(1000);//延时1秒
printf("AT+CSMP=17,167,2,25\r\n");//设置在工作模式
Delay_ms(1000);//延时1秒
printf("AT+CSCS=\"UCS2\"\r\n");//UCS2编码字集
Delay_ms(1000);//延时1秒
}
//发送数据
void Send_Chinsese(char *phone, char *mess)
{
char Unicode[50];
//将号码进行编码
ASCII_TO_Unicode(phone, Unicode);
printf("AT+CMGS=\"%s\"\r\n", Unicode);//unicode电话号码
Delay_ms(3000);//延时1秒
//发送数据
printf("%s\r\n", mess);//内容unicode码
Delay_ms(3000);//延时1秒
//结束位
Serial_SendByte(0x1A);
}
uint8_t IN_flag=0;
uint8_t runmode=0;
/****
矩阵:
横: PA0 - PA3
竖: PA4 - PA7
OLED显示屏:
SCL: PB10
SDA: PB11
***/
char tem[5];
uint16_t s_cnt=0;
uint8_t timeFlag=0;
uint8_t min_set=1;
int main(void)
{
int i=0;
int res=0;
int pos=0;
char code[5];
char codeIn[5];
char code_index=0;
char sendData[50];
char unicode[50];
int8_t keyNum=-1;
//led初始化
Led_Init();
//关闭led
Led=1;
//伺服电机初始化
Servo_Init();
//伺服电机设置角度
Servo_SetAngle(90);
//红外初始化
Sensor_Init();
//串口1初始化
Serial_Init();
//矩阵按键初始化
MatrixKey_Init();
//oled初始化
OLED_Init();
//定时器3初始化
TIM3_Int_Init(72, 100);
//显示屏显示提示
OLED_ShowString(1, 1, "GSM Init...");
//GSM初始化
GMS_Init();
//关闭计时
Clockflag=0;
while (1)
{
//进行计数,作为随机数的种子
s_cnt++;
s_cnt%=1000;
//获取按键值
keyNum = MatrixKey_Scan();
//判断是否在柜子里,并显示
if(Senor==0){
Led=0;
OLED_ShowNum(4, 15, 1, 1);
}
else{
Led=1;
OLED_ShowNum(4, 15, 0, 1);
}
//模式1,输入手机号,放入外卖模式
if(runmode == 0){
Servo_SetAngle(90);
timeFlag=0;
OLED_ShowString(1, 1, "Phone: ");
//当值大于0时,表示有按键按下
if(keyNum>=0){
//填充电话数组
if(keyNum >=0 && keyNum <= 9){
phone[index_p] = 0x30 + keyNum;
if(index_p<11){
index_p++;
}
}
}
//显示设定提醒时间
OLED_ShowNum(1, 13, min_set, 2);
if(keyNum == 10){
min_set--;
}
else if(keyNum == 11){
min_set++;
}
//确定按键
if(keyNum==15){
comfire=1;
phoneFlag=1;
}
//清零
if(keyNum==14){
for(i=0; i<11; i++){
phone[i]='0';
}
index_p=0;
comfire=0;
phoneFlag=0;
OLED_ShowString(2, 1, " ");
}
//号码显示
pos=1;
for(i=0; i<index_p; i++){
OLED_ShowChar(2, pos, phone[i]);
//固定数加间隔,用于区分
if(i==2)pos+=2;
//固定数加间隔,用于区分
else if(i==6)pos+=2;
//正常段
else pos++;
}
//确定按下,发送第一条短信
if(comfire==1 && Senor==0){
//随机数种子
srand(s_cnt);
// 生成随机数字字符
for(i = 0; i < 4; i++) {
code[i] = '0' + rand() % 10;
}
code[4]='\0';
//备份验证码
for(i=0; i<5; i++){
tem[i] = code[i];
}
//显示发送
OLED_ShowString(4, 1, "Sending...");
//进行编码
ASCII_TO_Unicode(code, unicode);
//发送信息,不能直接发送中文,需要进行unicode编码
sprintf(sendData, "60a87684591653565df25230ff0c9a8c8bc17801662f%s", unicode);
phone[11]='\0';
//测试信号
printf("AT+CSQ\r\n");
Delay_ms(1000);//延时1秒
//发送中文短信
Send_Chinsese(phone, sendData);
comfire=0;
runmode=1;
//OLED清屏
OLED_Clear();
}
OLED_ShowString(4, 1, " ");
}
else if(runmode == 1){
//伺服电机转到180,关闭外卖柜
Servo_SetAngle(180);
//开始计时
Clockflag=1;
//显示提示
OLED_ShowString(1, 1, "Captcha:");
//显示时间
OLED_ShowNum(3, 1, Min, 2);
OLED_ShowString(3, 3, ":");
OLED_ShowNum(3, 4, Sec, 2);
//超时提醒
if(Min >= min_set && timeFlag==0){
timeFlag=1;
}
//超时提醒
if(timeFlag == 1){
Clockflag=0;
timeFlag=2;
OLED_ShowString(4, 1, "Sending...");
phone[11]='\0';
printf("AT+CSQ\r\n");
Delay_ms(1000);//延时1秒
Send_Chinsese(phone, "68c06d4b523060a8768459165356957f65f695f4672a53d6ff0c8bf753ca65f653d68d70ff01");
}
OLED_ShowString(4, 1, " ");
//当值大于0时,表示有按键按下
if(keyNum>=0){
//填充电话数组
if(keyNum >=0 && keyNum <= 9){
codeIn[code_index] = 0x30 + keyNum;
if(code_index<4){
code_index++;
}
}
}
//加上结束符
codeIn[4] = '\0';
//验证码显示
pos=1;
for(i=0; i<code_index; i++){
OLED_ShowChar(2, pos, codeIn[i]);
pos++;
}
if(keyNum==14){
//清空验证码
for(i=0; i<4; i++){
code[i]='0';
}
code_index=0;
OLED_ShowString(2, 1, " ");
}
//校验验证码
if(keyNum==15){
res = strcmp(codeIn, tem);
//检验成功,切换到模式2
if(res == 0){
runmode=2;
OLED_Clear();
//清空电话号码
for(i=0; i<11; i++){
phone[i]='0';
}
//清空验证码
for(i=0; i<4; i++){
code[i]='0';
}
code_index=0;
index_p=0;
comfire=0;
phoneFlag=0;
//计时变量清零
Hour=0;
Min=0;
Sec=0;
}
else{
//清空验证码
for(i=0; i<4; i++){
code[i]='0';
}
code_index=0;
OLED_ShowString(2, 1, " ");
}
}
}
//验证码正确
else if(runmode == 2){
//打开外卖柜门
Servo_SetAngle(90);
//关闭计时
Clockflag=0;
//oled显示,“验证正确,请取走”
OLED_ShowString(1, 1, "The verification");
OLED_ShowString(2, 1, "code is correct");
OLED_ShowString(3, 1, "please take it!");
//判断外卖是否被拿走,拿走切换回模式0,并清屏
if(Senor == 1){
runmode=0;
OLED_Clear();
}
}
}
}
四、实物演示
1、首先输入号码放入外卖按下确认消息发送到了目标手机
2、目标手机查看验证码,同时系统开始计时
3、此时输入验证码,系统响应,外卖柜打开,外卖取走,系统回归原始状态。
4、长时间未取,系统发出信息,提醒取走,同时时间可以自己调节,这里为了演示,设计提醒时间是一分钟。
五、项目总结
本次设计使用STM32作为主控芯片,完成了一个外卖柜的设计,实现了短信提示、模拟门、用户自动输入号码,红外传感器检测外卖状态,验证码开门,完成了STM32智能外卖柜设计。