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

发布者:cxd88988最新更新时间:2023-08-31 来源: elecfans关键字:STM32 手机看文章 扫描二维码
随时随地手机看文章

一、项目背景

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

image-20230517113951921

二、系统设计

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)定义一些宏和变量以便于控制步进电机:


#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函数封装成一个子函数来更方便地调用:


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)定义一些宏和变量以便于控制电容矩阵键盘:


#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)需要编写一个函数来检测电容矩阵键盘是否有按下。


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


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 '�';

 }

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


int main(void) {

  char key = '�';

  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 != '�') {

      // 处理读取到的键值

     }

   }

}

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


四、系统测试与验证

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


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


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


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



关键字:STM32 引用地址:基于STM32的无人售货机系统设计

上一篇:STM32的实时时钟RTC编程详解
下一篇:STM32单片机GPIO口的学习

推荐阅读最新更新时间:2024-11-07 20:01

STM32的启动堆栈初始化
有几个问题,众多博文中抄来抄去,内容一样,却没有解释清楚 上电初始化堆栈,在进入_main后又说初始化堆栈,有什么不同 堆栈的地址是怎么得出来的 关于这两个问题,先借用一下要标准的启动流程 一般而言,系统上电后第一个执行的是由汇编所编写的启动文件,其主要工作为一下五部分: (1)、初始化堆栈指针SP=_initial_sp (2)、初始化PC指针,令其=Reset_Handler (3)、初始化中断向量表 (4)、配置系统时钟 (5)、调用C库函数_main初始化用户堆栈,从而最终调用main函数进入C的世界 STM32的中断向量表规定每一行必须是SP地址,第二行是复位中断入口地址,上电后,C
[单片机]
STM32 USART 使用DMA 详解
前言(绕开吧): 这段时间由于我们的项目Manibus板卡需要融入 WIFI, BLT, 网口,CAN,串口的多位一体通讯,互不干扰,而且可以相互调用彼此进行数据通讯,这里为了节省MCU资源,所以就使用DMA的方式来进行串口 和 ESP8266的通讯,接下来就介绍一下具体的操作内容! DMA具体的不介绍,总的来说,他就是一个中转站,数据给DMA,他帮你传递或接受,你只要读就行了!! 接下来看代码! void localUsartDMAConfig(void){ DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(RC
[单片机]
<font color='red'>STM32</font> USART 使用DMA 详解
STM32 USART1对PWM的影响,串口影响PWM
注意,USART1和TIM1是复用的,如果用TIM1产生PWM(PA9 / PA10),则USART1不应该用该管脚,可以用PB6/PB7。
[单片机]
STM32+DMA+UART+ADC+内部温度传感器
由于文件很多,只列举几个关键的文件。 ADC.c #include STM32Lib\stm32f10x.h u16 ADCCov ; volatile bool ADC_Ok=FALSE; static DMA_InitTypeDef DMA_InitStructure; static ADC_InitTypeDef ADC_InitStructure; //ADC,内部温度传感器配置 void ADCTEMP_Configuration(void) { /* 允许ADC */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE); /* ADC1 */ ADC_InitSt
[单片机]
关于STM32 GPIO配置模式
其实关于GPIO模式,手册有非常详细的说明,可见好好查看Datasheet有多么重要!! 首先关于stm32的GPIO口有输入输出之分,这点与51单片机使用的双向IO口有区别,这就需要根据我们具体是输入还是输出配置为相应的输入输出模式。输入就是输入模式,输出就是输出模式,两者不能混用。 下面这段话是手册这么描述GPIO口的: 通用I/O(GPIO) 复位期间和刚复位后,复用功能未开启, I/O端口被配置成浮空输入模式(CNFx =01b, MODEx =00b)。 复位后, JTAG引脚被置于输入上拉或下拉模式: ─ PA15: JTDI置于上拉模式 ─ PA14: JTCK置于下拉模式 ─ PA13: JTMS置于上拉模式 ─
[单片机]
关于<font color='red'>STM32</font> GPIO配置模式
XPT2046触摸屏实验过程详解与STM32代码解析
1.XPT2046的初始化 XPT2046说起来其实就是一个AD转换器,所以它适合不需要什么初始化设置的,而具体的初始化其实也就是单片机IO的初始化和SPI的初始化。 这次STM32是使用SPI1来进行操作,SPI的设置其实在前几节课已经讲过了,这里就不重复讲了,初始化的具体代码如下: /********************************************************************** *FuncTIonName:TOUCH_Init *DescripTIon:初始化触摸屏 *Input:None *Output:None *Return:None ****
[单片机]
意法半导体STM32U0打造极致的低功耗MCU
ST(意法半导体)近日推出了全新的STM32U0微控制器,这款基于Cortex-M0+内核的产品,可以在带有实时时钟(RTC)的待机模式下,实现仅为160nA的静态功耗,关机模式下更是低至16nA,展现了出色的节能性能。在CoreMark和SESIP 3级评测中,STM32U0获得了140分的高分,使其在超低功耗入门级细分市场中脱颖而出。 STM32U0之所以能实现如此卓越的性能,得益于ST在MCU中积累的丰富经验以及先进的90nm工艺节点的应用。这使得STM32U0在价格与性能之间找到了完美的平衡点,为工程师们在工业、医疗、智能计量和消费者健康市场中的入门级电池供电应用设计提供了更大的自由度。 在实际应用中,STM32U
[单片机]
意法半导体STM32U0打造极致的低功耗MCU
在Mac OSX中开发STM32程序
在Mac OSX下编写STM32程序: 1、下载stm32的gcc软件包,参考下面URL: 2、安装moxa NPORT 5110在虚拟Windows中; 3、安装ST Flash Loader 软件; 需要注意的: 1、Mac OSX 需要10.5; 2、NPORT装完要关机重启;
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved