写点什么

基于 STM32 的无人售货机系统设计

作者:DS小龙哥
  • 2023-06-20
    重庆
  • 本文字数:4679 字

    阅读完需:约 15 分钟

一、项目背景

随着科技的发展和生活水平的提高,人们对于购物体验的要求越来越高。传统的商场、超市购物方式已经无法满足消费者的需求,因此无人售货机应运而生。本文针对现有售货机存在的缺陷,设计了一款基于 STM32 的无人售货机系统。该系统采用 STM32 作为主控芯片,使用液晶屏显示各种商品库存与售价,用户按下对应按键选择购买指定商品,在矩阵键盘输入账号密码付款。若付款成功,对应电机旋转一定角度使商品出库,同时修改库存;若余额不足,则进行声光提示。手机端还可查看消费流水、商品库存情况,并进行补货和充值操作。



二、系统设计

2.1 系统硬件设计

该系统的核心部件是 STM32 主控芯片,它负责整个售货机的控制和管理。液晶屏用于显示商品信息、价格等,矩阵键盘用于用户输入账号密码进行支付。电机控制板用于控制商品出库。

硬件组成:

主控芯片选:STM32F103ZET6 液晶屏选择:2.8 寸 TFT-LCD 屏 WIFI 选择:ESP8266-WIFI 与手机 APP 之间通信。模式配置为 STA 模块。连接服务器。 电机旋转角度:28BYJ48 步进电机。 控制出货机出货物。 矩阵键盘:4X4 的矩阵键盘。

2.2 系统软件设计

软件部分主要包括 STM32 程序和手机 APP 程序。STM32 程序是售货机的核心程序,负责控制各个部件的工作,实现售货机的基本功能。APP 程序可以通过与 STM32 通信来实现商品库存查看、补货、充值等功能。

STM32 部分主要分为以下几个模块:

(1)初始化模块:初始化各个部件的工作状态和参数。 (2)商品选择模块:根据用户按下的按钮,选择相应的商品。 (3)支付模块:通过矩阵键盘输入账号密码进行支付,并根据支付结果控制电机的工作状态。 (4)库存管理模块:根据商品销售情况,实时更新商品库存信息。 (5)声光提示模块:在用户付款失败或余额不足时,通过蜂鸣器和 LED 灯进行声光提示。

手机 APP 程序主要分为以下几个模块:

(1)用户登录模块:用户可以通过输入账号密码登录 APP。 (2)商品查看模块:用户可以查看售货机内商品库存情况。 (3)补货模块:商家可以通过 APP 进行补货操作,将商品补充至指定数量。 (4)充值模块:用户可以通过 APP 进行账户充值操作。 (5)消费流水模块:用户和商家可以查看售货机的消费记录。

以上各模块之间通过 STM32 和 APP 程序之间进行通信,实现整个系统的功能。

三、核心代码实现

【1】步进电机控制代码

以下是 28BYJ48 步进电机的代码:

(1)定义一些宏和变量以便于控制步进电机:

cpp #define IN1 GPIO_Pin_0 #define IN2 GPIO_Pin_1 #define IN3 GPIO_Pin_2 #define IN4 GPIO_Pin_3 ​ #define STEPS_PER_REVOLUTION 2048 //步数每圈 #define DELAY_MS 5 //控制转速的延迟时间 ​ GPIO_InitTypeDef GPIO_InitStructure; ​ int step_count = 0; uint16_t steps[] = {IN1 | IN2 | IN3 | IN4,                     IN2 | IN3 | IN4,                     IN1 | IN2 | IN3,                     IN3 | IN4,                     IN1 | IN3 | IN4,                     IN2 | IN4,                     IN1 | IN2,                     IN4}; ​ void delay_ms(uint32_t ms) {     uint32_t i, j;     for (i = 0; i < ms; i++) {         for (j = 0; j < 1141; j++);     } } ​ void setStep(int step) {     GPIO_ResetBits(GPIOB, IN1 | IN2 | IN3 | IN4);     GPIO_SetBits(GPIOB, steps[step]); } ​ void forward(int steps_to_move) {     int i;     for (i = 0; i < steps_to_move; i++) {         setStep(step_count % 8);         step_count++;         delay_ms(DELAY_MS);     } } ​ void backward(int steps_to_move) {     int i;     for (i = 0; i < steps_to_move; i++) {         setStep(step_count % 8);         step_count--;         delay_ms(DELAY_MS);     } }
复制代码

在上面的代码中,定义了四个引脚来控制步进电机,然后定义了一些函数来实现正反转控制。

delay_ms 函数用于延迟控制步进电机的转速。STEPS_PER_REVOLUTION 宏定义了每圈的步数,DELAY_MS 宏定义了控制转速的延迟时间。

setStep 函数根据传入的步数设置引脚状态,接着 forward 和 backward 函数分别根据需要移动的步数控制步进电机的转动方向,并调用 setStep 函数控制步进电机的步数。

最后,将 forward 和 backward 函数封装成一个子函数来更方便地调用:

cpp void control_stepper_motor(int steps_to_move, int direction) {     if (direction == 1) {         forward(steps_to_move);     } else {         backward(steps_to_move);     } }
复制代码

这样,就可以通过调用 control_stepper_motor 函数来实现正反转控制 28BYJ48 步进电机了。

【2】矩阵键盘检测代码

以下是 4x4 电容矩阵键盘的示例代码:

(1)定义一些宏和变量以便于控制电容矩阵键盘:

cpp #define ROW1 GPIO_Pin_0 #define ROW2 GPIO_Pin_1 #define ROW3 GPIO_Pin_2 #define ROW4 GPIO_Pin_3 ​ #define COL1 GPIO_Pin_4 #define COL2 GPIO_Pin_5 #define COL3 GPIO_Pin_6 #define COL4 GPIO_Pin_7 ​ GPIO_InitTypeDef GPIO_InitStructure; ​ const uint8_t keys[4][4] = {     {'1', '2', '3', 'A'},     {'4', '5', '6', 'B'},     {'7', '8', '9', 'C'},     {'*', '0', '#', 'D'} };
复制代码

在上面的代码中,定义了 8 个引脚来控制电容矩阵键盘,并使用一个二维数组来存储每个按键对应的字符。

(2)需要编写一个函数来检测电容矩阵键盘是否有按下。

该函数需要通过轮询扫描键盘来检测按键,如果有按键按下,则返回该按键对应的字符:

cpp char scan_keypad() {     GPIO_ResetBits(GPIOC, ROW1 | ROW2 | ROW3 | ROW4);     GPIO_SetBits(GPIOC, COL1 | COL2 | COL3 | COL4); ​     if (GPIO_ReadInputDataBit(GPIOC, ROW1) == 0) {         while (GPIO_ReadInputDataBit(GPIOC, ROW1) == 0);         return keys[0][0];     } else if (GPIO_ReadInputDataBit(GPIOC, ROW2) == 0) {         while (GPIO_ReadInputDataBit(GPIOC, ROW2) == 0);         return keys[1][0];     } else if (GPIO_ReadInputDataBit(GPIOC, ROW3) == 0) {         while (GPIO_ReadInputDataBit(GPIOC, ROW3) == 0);         return keys[2][0];     } else if (GPIO_ReadInputDataBit(GPIOC, ROW4) == 0) {         while (GPIO_ReadInputDataBit(GPIOC, ROW4) == 0);         return keys[3][0];     } ​     GPIO_ResetBits(GPIOC, ROW1 | ROW2 | ROW3 | ROW4);     GPIO_SetBits(GPIOC, COL1 | COL2 | COL3 | COL4); ​     if (GPIO_ReadInputDataBit(GPIOC, ROW1) == 0) {         while (GPIO_ReadInputDataBit(GPIOC, ROW1) == 0);         return keys[0][1];     } else if (GPIO_ReadInputDataBit(GPIOC, ROW2) == 0) {         while (GPIO_ReadInputDataBit(GPIOC, ROW2) == 0);         return keys[1][1];     } else if (GPIO_ReadInputDataBit(GPIOC, ROW3) == 0) {         while (GPIO_ReadInputDataBit(GPIOC, ROW3) == 0);         return keys[2][1];     } else if (GPIO_ReadInputDataBit(GPIOC, ROW4) == 0) {         while (GPIO_ReadInputDataBit(GPIOC, ROW4) == 0);         return keys[3][1];     } ​     GPIO_ResetBits(GPIOC, ROW1 | ROW2 | ROW3 | ROW4);     GPIO_SetBits(GPIOC, COL1 | COL2 | COL3 | COL4); ​     if (GPIO_ReadInputDataBit(GPIOC, ROW1) == 0) {         while (GPIO_ReadInputDataBit(GPIOC, ROW1) == 0);         return keys[0][2];     } else if (GPIO_ReadInputDataBit(GPIOC, ROW2) == 0) {         while (GPIO_ReadInputDataBit(GPIOC, ROW2) == 0);         return keys[1][2];     } else if (GPIO_ReadInputDataBit(GPIOC, ROW3) == 0) {         while (GPIO_ReadInputDataBit(GPIOC, ROW3) == 0);         return keys[2][2];     } else if (GPIO_ReadInputDataBit(GPIOC, ROW4) == 0) {         while (GPIO_ReadInputDataBit(GPIOC, ROW4) == 0);             return keys[3][2]; } GPIO_ResetBits(GPIOC, ROW1 | ROW2 | ROW3 | ROW4); GPIO_SetBits(GPIOC, COL1 | COL2 | COL3 | COL4); ​ if (GPIO_ReadInputDataBit(GPIOC, ROW1) == 0) {     while (GPIO_ReadInputDataBit(GPIOC, ROW1) == 0);     return keys[0][3]; } else if (GPIO_ReadInputDataBit(GPIOC, ROW2) == 0) {     while (GPIO_ReadInputDataBit(GPIOC, ROW2) == 0);     return keys[1][3]; } else if (GPIO_ReadInputDataBit(GPIOC, ROW3) == 0) {     while (GPIO_ReadInputDataBit(GPIOC, ROW3) == 0);     return keys[2][3]; } else if (GPIO_ReadInputDataBit(GPIOC, ROW4) == 0) {     while (GPIO_ReadInputDataBit(GPIOC, ROW4) == 0);     return keys[3][3]; } ​ return '\0';  }
复制代码

在上面的代码中,使用轮询的方式扫描键盘。首先将所有行引脚都设为低电平,所有列引脚都设为高电平,并检测是否有按键按下。如果有按键按下,则返回该按键对应的字符。 接下来,可以在主函数中循环调用 scan_keypad 函数来读取键值:

cpp int main(void) {     char key = '\0'; ​     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); ​     GPIO_InitStructure.GPIO_Pin = ROW1 | ROW2 | ROW3 | ROW4;     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;     GPIO_Init(GPIOC, &GPIO_InitStructure); ​     GPIO_InitStructure.GPIO_Pin = COL1 | COL2 | COL3 | COL4;     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;     GPIO_Init(GPIOC, &GPIO_InitStructure); ​     while (1) {         key = scan_keypad(); ​         if (key != '\0') {             // 处理读取到的键值         }     } }
复制代码

在上面的代码中,首先初始化了 8 个引脚,并通过循环调用 scan_keypad 函数来读取键值。如果读取到键值,则可以进行相应的处理。

四、系统测试与验证

为了验证系统的可行性和稳定性,在硬件搭建完成后,进行了一系列测试。

(1)测试了系统的整体运行逻辑。通过模拟用户选择商品、支付、出货等情况,验证系统的基本功能。测试结果显示系统能够稳定运行,能够满足用户的购物需求。

(2)测试了系统的库存管理功能。通过模拟商品销售情况,验证系统的库存信息是否能够实时更新。测试结果表明系统能够准确地处理库存信息。

(3)测试了手机端 APP 程序的功能。通过模拟用户登录、查看商品库存、进行补货、充值和查看消费流水等操作,验证 APP 程序的功能。测试结果显示 APP 程序能够正常运行,并且与 STM32 主控芯片之间能够实现良好的通信。

发布于: 刚刚阅读数: 3
用户头像

DS小龙哥

关注

之所以觉得累,是因为说的比做的多。 2022-01-06 加入

熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域

评论

发布
暂无评论
基于STM32的无人售货机系统设计_6 月优质更文活动_DS小龙哥_InfoQ写作社区