外卖自提柜设备端主控 - 嘉立创EDA开源硬件平台

编辑器版本 ×
标准版 Standard

1、简单易用,可快速上手

2、流畅支持300个器件或1000个焊盘以下的设计规模

3、支持简单的电路仿真

4、面向学生、老师、创客

专业版 professional

1、全新的交互和界面

2、流畅支持超过3w器件或10w焊盘的设计规模,支持面板和外壳设计

3、更严谨的设计约束,更规范的流程

4、面向企业、更专业的用户

标准版 外卖自提柜设备端主控

简介:外卖自提柜,类似蜂巢之类的快递柜。 基本功能包括与服务器通信,控制开柜,显示信息,声音提示,验证码输入等等。

开源协议: Public Domain

(未经作者授权,禁止转载)

创建时间: 2020-04-23 13:38:42
更新时间: 2022-04-28 13:54:30
描述
原文在CSDN [项目实战-外卖自提柜 1.项目介绍、协议制定](https://blog.csdn.net/weixin_44578655/article/details/105945891) [项目实战-外卖自提柜 2. CubeMX + FreeRTOS入门](https://blog.csdn.net/weixin_44578655/article/details/105952248) [项目实战-外卖自提柜 3. FreeRTOS主要API的应用](https://blog.csdn.net/weixin_44578655/article/details/105969808) [项目实战-外卖自提柜 4. FreeRTOS 堆栈分配、调试技巧](https://blog.csdn.net/weixin_44578655/article/details/105992659) [项目实战-外卖自提柜 5. ESP8266 01S配置与掉线处理](https://blog.csdn.net/weixin_44578655/article/details/106004124) [项目实战-外卖自提柜 6. 硬件工作(原理图、PCB绘制)](https://blog.csdn.net/weixin_44578655/article/details/106009141) ## 项目介绍 外卖自提柜,类似蜂巢之类的快递柜。 工作流程: * 外卖员通过手机APP扫描柜体上面的固定二维码,在APP中输入客户的手机号 * 完成后,服务器向对应手机号发送含有取货密码的短信 * 同时自动分配一个空柜子,向设备端发送一个开柜指令,内容包括,柜号、开柜密码等 * 设备端收到开柜指令后开柜 * 客户收到短信后凭密码取外卖,取完后设备端上报服务器取货成功的信息。 基本功能包括与服务器通信,控制开柜,显示信息,声音提示,验证码输入等等。 服务器和APP是别人做的,我做设备端,柜体用下面这种。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200506105739397.png) ## 方案选型 方案: MCU + WIFI模块 + GPRS模块 + 显示屏 + 键盘 选型: stm32f103rbt6 + esp8266 + sim800 + lcd彩屏 + 矩阵键盘 一开始觉得这个项目so easy ~~烂大街~~ ,乍一看确实,这选型也太烂大街了(笑),如果说这是一道电赛题,几天也能弄出来,最后花了两个月左右... ## 工作流程 设备端主要工作流程如下: 1. 硬件开机后与服务器连接,连接成功后,硬件自动向服务器发送**注册指令**, 包含本机的Id,服务器收到后会将该机器注册进来,进行监管。 2. 当有客户想要存放时,会扫描硬件二维码获取机器Id,然后在App上打开某个格子,服务器会向该机器发送**存货指令** , 包含要打开的机器Id,格子Id,存放模式,**取货验证码**等等,同时服务器会向取货的客户发送6位验证码短信。 3. 机器接收到存柜**存货指令**后,尝试打开相应格子,并保存验证码,若打开成功,则发回给服务器**开柜成功指令**表示成功。否则返回**开柜失败指令**表示失败。 4. 客户来取物品时,在机器上输入相应的六位密码,响应密码的格子就会自动打开,然后向服务器发送**取货指令**,报告格子被打开。 5. 持续工作,设备需要每30s发送一次**心跳指令**。 ## 协议制定 协议部分雏形是做服务器的同学定的,这部分直接导致系统从裸奔变成跑FreeRTOS。 **帧头 + Length + CmdId + DevId + Content + FrameId + 校验和** | 成分 | 描述 | | --- | --- | | 帧头 | 0x0a 0x0a 0x0a 0x0a | | Length | 指令字节数总长度,包括其本身和校验和,两个字节的无符号short类型,顺序为 [低字节,高字节] | | CmdId | 指令的Id , 一个字节的无符号byte类型 | | DevId | 目标设备的Id,两个字节的无符号short类型,顺序为 [低字节,高字节] | | Content | 该条指令包含的详细信息 | | FrameId | 每一帧的唯一Id,两字节无符号short类型,顺序为[低字节,高字节] | | 校验和 | 一字节有符号byte类型 | **不同指令的Content不同:** 1. **注册帧000**:设备向服务器发送的认证信息,在服务器上注册该设备 Content为空 2. **回复帧001**:回复数据正确 Content为空 3. **心跳帧002**:心跳保持 Content为空 4. **存货开柜帧003**:服务器向设备发送存货开柜指令 Content内容包含: -CellId:机器格子的编号,要开启的格子。两个字节的无符号short类型,顺序为 [低字节,高字节] -Mode:代表存储的模式(常温,保温,制冷),一个字节的无符号byte -PassWord:表示存储密码,六个字节的char字符串,顺序即为密码顺序 -SendAddress:表示存件者的id,11个字节的电话号码,char字符串,顺序即为号码顺序 -ReceiveAddress:表示取件者的id,含义同上 5. **开柜成功帧004**:设备开柜成功 Content内容与指令003相同 6. **开柜失败帧005**:设备开柜失败 Content包含: -SendAddress : 表示存件者的id,11个字节的电话号码,char字符串,顺序即为号码顺序 7. **取货帧006**:客户取货成功 Content包含: -CellId:机器格子的编号,要开启的格子。两个字节的无符号short类型,顺序为 [低字节,高字节] ## 第一个任务 初步入门FreeRTOS以后,着重解决通信部分,重新梳理一下与服务器通信部分的需求: 设备端和服务器通信,发送方每发送一条指令,接收方都要在收到后返回一个应答帧,发送方收到应答帧后,才判断此次通信正常,若规定时间内未收到应答帧,则重新发送。 另外需要注意的是,**发送方在等待接收方返回应答帧时,不能阻塞系统运行,也就是说,即便当前有一帧数据在等待应答,也不影响下一帧数据的发送,且理论上应该保证同时在等待应答的帧的数量不受限制**。 根据上述需求,显而易见的,**应当把每一帧的发送单独作为一个任务**,这个任务对这一帧进行监听,并控制重发。只要系统还有足够的剩余栈,就可以不断地创建新的发送任务,这样就可以保证最大限度地使用硬件资源保证每一帧的通信“并行”。 刚好,**FreeRTOS创建任务时是可以传入一个参数的**,这个参数就可以传入我们要发送的数据。 第一个任务诞生了: **数据发送任务:** ``` c /** * @brief 数据发送任务 * @note 需要向服务器发送一条指令时,就创建一个发送任务,特点是等待回复和重发时不会阻塞其他任务进行 * @param argument:要发送的数据 * @retval None */ void SendData_Task(void const * argument) { //待添加 for(;;) { //待添加 } } ``` 下面来构思函数体中要写些什么 首先,肯定是要发送数据了,发送数据之前,有一件事要考虑,由于传入的是argument是指针,这个任务在进行过程中,这个指针指向的内容很可能被其他任务更改,所先**需要先申请空间来拷贝要发送的数据** 再来回顾一下帧格式: **帧头 + Length + CmdId + DevId + Content + FrameId + 校验和** | 成分 | 描述 | | --- | --- | | 帧头 | 0x0a 0x0a 0x0a 0x0a | | Length | 指令字节数总长度,包括其本身和校验和,两个字节的无符号short类型,顺序为 [低字节,高字节] | | CmdId | 指令的Id , 一个字节的无符号byte类型 | | DevId | 目标设备的Id,两个字节的无符号short类型,顺序为 [低字节,高字节] | | Content | 该条指令包含的详细信息 | | FrameId | 每一帧的唯一Id,两字节无符号short类型,顺序为[低字节,高字节] | | 校验和 | 一字节有符号byte类型 | 我们通过上述的Length获取数据长度,然后用FreeRTOS提供的API: **pvPortMalloc** 申请内存,这个函数与C语言的**malloc**的区别是,前者从FreeRTOS的TOTAL\_HEAP\_SIZE中申请空间,而后者是从系统的堆(heap)中申请空间。 详细的分析看这篇博客: [https://www.cnblogs.com/LinTeX9527/p/8007541.html](https://www.cnblogs.com/LinTeX9527/p/8007541.html) 数据发送任务的前几行代码有着落了: ``` c void SendData_Task(void const * argument) { uint8_t *Data; //创建指针 uint16_t Data_Len = 0; //数据长度 Data_Len = ((uint16_t*)argument)[0];//获取数据长度 Data = pvPortMalloc(Data_Len-1); //申请内存,去掉校验和1字节 memcpy(Data,(uint8_t*)argument,sizeof(uint8_t)*(Data_Len-1)); //复制数组,去掉校验和 for(;;) { //待添加 } } ``` ## 互斥量的使用 当然,如果这里严谨一点的话,你会发现,即便这里进行了数据拷贝,但拷贝也不是一瞬间完成的,所以拷贝的时候,这段数据仍然不是安全的,仍可能被更改,下面就用到FreeRTOS的另一个功能了: **互斥量** **正如其名,一个资源在被一个任务访问时,不能再被另一个任务访问,就叫互斥**。 通过下面两个函数实现互斥: ``` c osMutexWait(mutex_CopyData_h, osWaitForever); //等待互斥量被释放 osMutexRelease(mutex_CopyData_h); //释放互斥量 ``` 这其中**mutex\_CopyData\_h**是互斥量的句柄(可以看作是名称),**osWaitForever**表示一直阻塞等待,直到互斥量被释放。 如何使用呢? 按照上述情形举例,我们要在拷贝数据时用互斥量进行保护,数据发送任务就改进为下面这种形式: ``` c /** * @brief 数据发送任务 * @note 需要向服务器发送一条指令时,就创建一个发送任务,特点是等待回复和重发时不会阻塞其他任务进行 * @param argument:要发送的数据 * @retval None */ void SendData_Task(void const * argument) { uint8_t *Data; //申请内存指针 uint16_t Data_Len = 0; //数据长度 Data_Len = ((uint16_t*)argument)[0];//获取数据长度 Data = pvPortMalloc(Data_Len-1); //申请内存,去掉校验和1字节 osMutexWait(mutex_CopyData_h, osWaitForever); //等待互斥量被释放 /*被互斥量保护的区域*/ memcpy(Data,(uint8_t*)argument,sizeof(uint8_t)*(Data_Len-1)); /*被互斥量保护的区域*/ osMutexRelease(mutex_CopyData_h); //释放互斥量 for(;;) { //待添加发送函数 } } ``` **osMutexWait**和**osMutexRelease**之间,就是我们希望保护的位置。 当然这只完成了一半,同样的,我们需要在**存在数据覆盖风险的位置**设置互斥量的保护区。 例如下面:传入**数据发送任务**的参数是名为**Data_Buf**的数组 ``` c osThreadDef(DATA_SEND_TASK_H,SendData_Task, osPriorityHigh,0, 128); //心跳帧重发任务的宏 osThreadCreate(osThread(DATA_SEND_TASK_H),Data_Buf) ``` 那么我需要在修改Data_Buf的位置设置互斥量保护区: ``` c osMutexWait(mutex_CopyData_h, osWaitForever); //等待互斥量被释放 Data_Buf[0] = 0; osMutexRelease(mutex_CopyData_h); //释放互斥量 ``` 被互斥量保护的区域,同时只能被一个任务访问,直到这个任务释放互斥量,下一个任务才能访问。 这样,我们就可以保证拷贝数据的时候,数据不会被误修改。 ## 消息队列的使用 我们继续完善数据发送任务,回到需求分析,**数据发送任务**除了需要完成数据发送,还需要监听是否收到**与此帧数据匹配**的应答帧。 如果同时有好几个**数据发送任务**在等待应答帧,这时候收到了一条应答帧,对于某一个**数据发送任务**来说,如何判断这条应答帧是发给自己的呢? 上翻查阅数据帧格式的表格,可以看到,每一帧数据有**唯一的FrameId**,回复帧也有FrameId,它的FrameId与它要回复的数据帧的FrameId相同。 对于某一个**数据发送任务**来说,它只需要与收到的回复帧的FrameId进行匹配,若与自己的Frame相同,则判断这个回复帧是回复给自己的,如果是回复给自己的,这个**数据发送任务**就完成了自己的使命,可以把自己删除了。 所以当有多帧数据同时等待回复帧时,需要开设一个缓存区,存放收到的回复帧的FrameId,供**数据发送任务**查询。 这个缓存区,就交给 **消息队列**来完成 FreeRTOS对消息队列的处理,我用到了下面几个API: ``` c //查询队列中元素的个数 osMessageWaiting(MsgBox_Frame_Id_Handle); //获取并删除队列中的一个元素 osMessageGet(MsgBox_Frame_Id_Handle,osWaitForever); //向队列存放一个元素 osMessagePut(MsgBox_Frame_Id_Handle,evt.value.v,osWaitForever); ``` * **MsgBox\_Frame\_Id\_Handle**是这个队列的句柄 * **osWaitForever**表示这个函数执行的超时时间,超过了这个值就会自动退出,这里是永久等待 * **evt.value.v**是要向队列里存入的元素 如何实现查询队列中是否有与自己匹配的FrameId呢? 我的思路是,先通过**osMessageWaiting**读出当前队列中元素的数量N ,进入循环,每个循环中,使用**osMessageGet**取出一个元素,由于队列是**先进先出**,所以这个元素是从队列头部取出的,判断是否匹配,如果匹配,皆大欢喜,这个数据发送任务就解脱了;如果不匹配,再将这个元素用**osMessagePut**重新加入到队列尾部,这样循环N次,就相当于把队列查询了一遍。 数据发送任务就基本完成了: ``` c /** * @brief 数据发送任务 * @note 需要向服务器发送一条指令时,就创建一个发送任务,特点是等待回复和重发时不会阻塞其他任务进行 * @param argument:要发送的数据 * @retval None */ void SendData_Task(void const * argument) { uint8_t *Data; //申请内存指针 uint16_t Data_Len = 0; //数据长度 Data_Len = ((uint16_t*)argument)[0];//获取数据长度 uint16_t FrameId = 0; //帧Id uint32_t MsgBox_Data_Num = 0;//队列中有效数据的数量 osEvent evt; //存放osMessageGet的返回值 Data = pvPortMalloc(Data_Len-1); //申请内存,去掉校验和1字节 osMutexWait(mutex_CopyData_h, osWaitForever); //等待互斥量被释放 /*被互斥量保护的区域*/ memcpy(Data,(uint8_t*)argument,sizeof(uint8_t)*(Data_Len-1)); /*被互斥量保护的区域*/ osMutexRelease(mutex_CopyData_h); //释放互斥量 FrameId = (uint16_t)Data[Data_Len-2]
设计图
原理图
1 /
PCB
1 /
未生成预览图,请在编辑器重新保存一次
ID Name Designator Footprint Quantity
1 K2-1101UT-B4SW-01_JX KEY1,KEY5,KEY4 SW_PUSH_2P_6MM_H5MM_JX 3
2 32.768KHz X1 OSC-TH_BD2.0-P0.70-D0.3 1
3 AMS1117-3.3 U6,U3 SOT-223-4_L6.5-W3.5-P2.30-LS7.0-BR 2
4 HX25003-2A CN10,CN7,CN15,CN13,CN12,CN11,CN9,CN6,CN8,CN5,CN2,CN4,CN3,CN14 CONN-TH_2P-P2.50_HX25003-2A 14
5 10uF C13,C10,C23,C18 C0603 4
6 LED-0603_R LED3,LED4 LED0603_RED 2
7 GP2301 Q3 SOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR 1
8 CH340C U2 SOP-16_L10.0-W3.9-P1.27-LS6.0-BL 1
9 1N4148 D1,D8,D2,D11,D7,D4,D13,D5,D9,D6,D10,D12,D3 SOD-123_L2.8-W1.8-LS3.7-RD 13
10 12pf C21,C20,C17,C16 C0603 4
11 SS-12D10L5 SW1 SW-TH_SS-12D10L5 1
12 100nF C6,C8,C15,C22,C5,C12,C19,C9,C14,C2,C4,C11,C25,C3,C1,C24,C7 C0603 17
13 10118194-0001LF USB2,USB1 MICRO-USB-SMD_10118194-0001LF 2
14 ASMD0805-075 F1 F0805 1
15 LED-0603_B LED2 LED0603_BLUE 1
16 HDR-F-2.54_1x8 H1 HDR-F-2.54_1X8 1
17 500 R14,R15 R0603 2
18 1K R28,R13,R36,R26,R24,R38,R22,R40,R18,R17,R30,R4,R6,R9,R32,R10,R34,R20 R0603 18
19 esp8266 U4 ESP8266 1
20 HDR-M-2.54_1x2 J2,J1,J3 HDR-M-2.54_1X2 3
21 YS-MBZ12085C05R42_C409842 BUZZER1 BUZ-TH_BD12.0-P6.50-D0.6-FD 1
22 10K R2,R31,R19,R25,R5,R33,R27,R7,R21,R35,R41,R16,R37,R29,R23,R3,R39 R0603 17
23 500Ω R1 R0603 1
24 R12,R8 R0603 2
25 8MHz X3 HC-49US_L11.5-W4.5-P4.88 1
26 sim800l U5 HDR1X6_SIM800L_BOARD 1
27 LED-0603_G LED1 LED0603_GREEN 1
28 STM32F103RBT6 U1 LQFP-64_10X10X05P 1
29 HDR-F-2.54_1x10 H3 HDR-F-2.54_1X10 1
30 HDR-F-2.54_1x4 H4,H2 HDR-F-2.54_1X4 2
31 GP2302 Q11,Q10,Q13,Q16,Q8,Q7,Q12,Q5,Q1,Q4,Q9,Q14,Q15,Q2,Q6 SOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR 15
32 HDR-M-2.54_1x4 J4 HDR-M-2.54_1X4 1

展开

工程视频/附件
序号 文件名称 下载次数
1

自提柜测试视频.mp4

2254
2

CUBE_Test.zip

502
工程成员
侵权投诉
相关工程
换一批
加载中...
添加到专辑 ×

加载中...

温馨提示 ×

是否需要添加此工程到专辑?

温馨提示
动态内容涉嫌违规
内容:
  • 153 6159 2675

服务时间

周一至周五 9:00~18:00
  • 技术支持

support
  • 开源平台公众号

MP