ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

51单片机+GSM短信模块:实现可远程通知的自动售货机设计

51单片机+GSM短信模块:实现可远程通知的自动售货机设计 很多人在做单片机课程设计或毕业设计时会先翻开发板资料包看到什么芯片就选什么方案。但如果你已经决定做“自动售货机”这个题目又担心普通投币方案太老套、容易被答辩老师追问得无话可说那我建议你认真看一下“51单片机 GSM短信模块”这种组合。它没有破坏自动售货机本身的功能完整性却把系统的交互边界从“现场按键”扩展到了“远程通知”整个项目的技术含量和可展示性都会往上走一档。这篇文章会围绕“基于51单片机的GSM自动售货机”从原理、硬件选型、代码实现到调试排错完整拆解。我会尽量避开纯理论堆砌把重点放在你真正会在实验室里遇到的细节上比如LCD1602怎么显示余额、投币逻辑怎么设计、SIM900A模块为什么发送短信不稳定、AT指令返回“ERROR”时先查哪几个点。读完之后你至少能自己搭出一个能跑通“投币—选商品—出货—发短信通知”的完整演示样机。1. 这篇文章真正要解决的问题传统自动售货机常见的方案是“投币器 单片机 电机出货”。投币器识别硬币后单片机收到脉冲信号然后根据用户按键选择执行对应货道的电机。这种方案本身没有错但它有两个很现实的问题第一运营者无法远程知道机器状态。机器放在教学楼一楼晚上缺货了、卡货了、或者被断电了你不可能守在旁边只能等用户扫码投诉或定期跑过去查看。如果是课程设计答辩老师大概率会问“你如何知道机器需要补货”没有远程模块这个问题很难回答得漂亮。第二纯投币方案在功能上偏静态。用户只能投币、按键、取货整个交互过程是单向的。引入GSM模块之后系统可以在关键事件发生时主动发送短信给管理员比如“某货道已售罄请补货”“当前销售额达到XX元”“设备被非法震动”。这就把一台单纯的售货设备变成了一个具备基础物联网特征的终端。这里要澄清一个容易混淆的判断51单片机本身没有操作系统不能直接上网但GSM模块相当于一个“手机通信网关”。单片机通过串口发送AT指令GSM模块把文字内容以短信形式发送到指定手机号。整个过程不需要宽带、不需要路由器只要有手机信号就能工作非常适合校园、商场、景区等场景的演示和原型验证。所以这篇文章要解决的核心问题不是“51单片机怎么点亮一个LED”而是在51单片机资源非常有限的条件下如何把显示、按键、电机出货、串口通信、短信发送几个模块在同一个任务循环里有条不紊地跑起来。很多人项目做不通不是单个模块不会调而是这几个模块组合在一起时时序、电平、串口占用的冲突没有处理好。下面我会按照实际开发顺序逐步展开。2. 核心概念51单片机、GSM模块与自动售货机控制逻辑2.1 51单片机经典、廉价、教学价值高51单片机通常指Intel 8051内核的兼容芯片国内最常见的是STC89C52系列。它集成了CPU、RAM、ROM、定时器、串口、GPIO等基本资源。虽然主频不高通常12MHz或11.0592MHz、RAM也只有256字节左右但驱动LCD1602、独立按键、直流电机、SIM900A这类外设完全够用。自动售货机用到的主要资源如下定时器0/1提供延时基准和串口波特率。串口UART和GSM模块通信。GPIO控制LCD1602数据线、电机驱动引脚、按键检测。51单片机的优点是教材多、例程序多、焊接调试门槛低。缺点同样明显没有操作系统任务调度靠大循环中断串口同时收发短信时要格外小心缓冲区溢出。但如果你要做课程设计51恰恰是最不容易在答辩时被质疑“不是你自己做的”的芯片。2.2 GSM模块短信通信的关键设备GSMGlobal System for Mobile Communications全球移动通信系统模块本质是一个完整的手机通信基带处理器通过串口和单片机通信。常用型号是SIM900A、SIM800C。模块上电后需要插入一张能正常收发短信的SIM卡建议用小流量或无流量的基础套餐卡并通过AT指令控制。要理解GSM模块在售货机项目里的角色可以做一个类比它相当于一个“微型手机”。你给这个手机发送“ATCMGS\r\n”它就进入短信编辑状态你继续发送内容再发送十六进制的1A结束符它就把短信发出去。单片机是大脑而GSM模块是嘴和耳朵。值得注意的事SIM900A上电瞬间电流峰值可达2A如果用USB转TTL供电很容易电压跌落导致模块反复重启。所以实际项目中GSM模块必须单独用5V/2A以上电源供电或者使用稳压模块不能和单片机共用一个USB口供电。这个细节是很多新手项目失败的真正原因。2.3 自动售货机的核心控制逻辑自动售货机的控制逻辑可以分成四层人机交互层LCD1602显示商品名称、单价、投币余额矩阵键盘或独立按键选择商品和模拟投币。业务逻辑层判断余额是否足够扣减余额解锁货道触发电机转动。执行层继电器或电机驱动模块控制直流电机或舵机把商品推落到取货口。通信层在指定事件发生时通过GSM模块发送短信到管理员手机。在51单片机这种裸机环境下逻辑层一般放在while(1)循环里顺序扫描。按键需要消抖显示刷新不能卡顿电机转动需要定时停止短信发送不能阻塞主循环太久。这些约束决定了代码不能简单地按顺序“先做A再做B”而要设计成一个状态机或事件标志驱动的结构。后面我会给出一个典型的实现思路。3. 系统总体架构与硬件选型3.1 系统架构图从逻辑上看整个系统可以画成以下对应关系[独立按键/矩阵键盘] → [51单片机] → [LCD1602显示] ↑ │ └── 投币信号 ┌──┴──┐ [电机驱动] → [货道出货] [GSM模块] → [短信通知管理员]单片机是核心处理器按键负责投币和选择LCD1602负责信息反馈电机驱动实现出货动作GSM模块负责把售货机的关键事件通过短信发送给管理员。3.2 硬件清单下面是一个比较完整的物料清单表格中标明了模块在系统里的作用模块型号/规格作用注意事项主控芯片STC89C52RC核心控制器和8051指令兼容显示模块LCD1602显示商品名、单价、余额需要1602的驱动字库按键独立按键或4x4矩阵键盘模拟投币、选择商品按键必须消抖出货执行直流电机驱动板或SG90舵机推动商品下落使用电机驱动模块防止电流倒灌通信模块SIM900A/SIM800C发送短信通知独立供电天线要装好电源5V/2A以上适配器给单片机、电机、GSM供电GSM峰值电流大商品模型小瓶装饮料/3D打印货道模拟真实出货展示效果好即可如果学校预算有限电机可以用SG90舵机代替舵机转动一定角度推动挡板结构更简单。不过舵机需要PWM信号51单片机没有硬件PWM也能用定时器模拟代码会稍微复杂一些。本文章节以直流电机驱动为例。3.3 模块接线规划在写代码之前必须先规划好引脚。下面是我常用的一组引脚分配你可以按自己的开发板修改// 引脚分配示例 sbit RS P2^6; // LCD1602 寄存器选择 sbit RW P2^5; // LCD1602 读写选择 sbit EN P2^7; // LCD1602 使能信号 #define LCD_DATA P0 // LCD1602 8位数据总线 sbit KEY_ADD P3^2; // 投币按键模拟1元投币 sbit KEY_BUY P3^3; // 确认购买按键 sbit MOTOR_PIN P1^0; // 电机控制引脚 sbit LED_STATE P1^1; // 状态指示灯方便调试GSM模块和单片机使用串口连接单片机TXDP3.1接模块RXD单片机RXDP3.0接模块TXD然后共地。注意SIM900A的TTL电平标准比51单片机宽但为了稳定多数模块板已经做了电平转换接上就能用。如果模块没有电平转换建议使用MAX232或直接换带电平转换的模块不要随手把5V和3.3V混接。4. 环境准备与开发工具开发51单片机项目不需要复杂的集成环境下面是我推荐的软件组合工具作用说明Keil C51编写和编译C语言代码最常用的51开发IDESTC-ISP烧录程序到STC芯片官方提供串口下载软件串口调试助手调测GSM模块AT指令也可以用SecureCRT万用表检查电压、通断排查硬件故障必备使用Keil C51时建议新建一个空工程选择芯片型号为STC89C52RC或Atmel AT89C52。由于STC系列芯片内部有硬件选项比如外部晶振、复位电路如果使用STC-ISP烧录需要在烧录界面选择外部晶振或内部IRC。GSM模块调试有一个很重要的前置步骤先把SIM900A模块插上SIM卡、接上电源和USB转TTL然后用串口调试助手发送“AT\r\n”如果返回“OK”说明模块已经工作。如果没有返回先检查电源是否足够、SIM卡是否插好、天线是否拧紧。这个验证一定要在连接单片机之前完成。否则你后面无法判断是单片机程序的问题还是GSM模块本身的问题。5. 核心代码实现这一部分给出可以直接编译验证的核心代码框架。为了让代码结构清晰我按文件功能拆分成几个代码块。你可以把驱动函数放在一个.c文件里也可以全部放在main.c中根据个人习惯决定。这里给出的版本尽量精简重点展示关键逻辑。5.1 LCD1602显示驱动LCD1602是字符型液晶内部有字符库我们只需要发指令和数据即可显示ASCII字符和常用符号。下面是基础驱动包含初始化、写命令、写数据、显示字符串和清屏。// 文件lcd1602.c #include reg52.h #include string.h sbit RS P2^6; sbit RW P2^5; sbit EN P2^7; #define LCD_DATA P0 void LCD_Delay(unsigned int t) { unsigned int i; while (t--) { for (i 0; i 100; i); } } void LCD_WriteCmd(unsigned char cmd) { RS 0; RW 0; EN 0; LCD_DATA cmd; LCD_Delay(1); EN 1; LCD_Delay(1); EN 0; } void LCD_WriteData(unsigned char dat) { RS 1; RW 0; EN 0; LCD_DATA dat; LCD_Delay(1); EN 1; LCD_Delay(1); EN 0; } void LCD_Init(void) { LCD_WriteCmd(0x38); // 8位数据2行显示5x7点阵 LCD_WriteCmd(0x0C); // 显示开光标关闪烁关 LCD_WriteCmd(0x06); // 写入后光标自动右移 LCD_WriteCmd(0x01); // 清屏 } void LCD_Clear(void) { LCD_WriteCmd(0x01); } void LCD_ShowString(unsigned char row, unsigned char col, char *str) { unsigned char addr; if (row 0) { addr 0x80 col; } else { addr 0xC0 col; } LCD_WriteCmd(addr); while (*str ! \0) { LCD_WriteData(*str); str; } }LCD1602在写指令之前必须确认模块已经完成上电初始化。有些开发板使用的是慢速单片机初始化指令的时间间隔也要符合LCD1602的时序要求一般十几毫秒即可。如果屏幕显示“花屏”或者全是方块优先检查电源电压和RS、RW、EN三条控制线是否接反尤其注意EN信号高低电平是否触发在正确的沿。5.2 按键扫描与投币逻辑自动售货机的投币逻辑不复杂但需要注意状态保存。我在这里使用一个全局变量balance表示当前余额单位是“角”投币按键每按一次增加余额同时刷新LCD。购买逻辑则是判断余额是否大于等于商品价格如果满足就减扣余额并触发电机。// 文件vending_machine.c #include reg52.h #include lcd1602.h sbit KEY_ADD P3^2; sbit KEY_BUY P3^3; sbit MOTOR_PIN P1^0; unsigned int balance 0; // 当前余额单位角 unsigned int price 3; // 商品单价对应3元单位角 unsigned char KeyScan(void) { if (KEY_ADD 0) { delay_ms(20); while (KEY_ADD 0); return 1; } if (KEY_BUY 0) { delay_ms(20); while (KEY_BUY 0); return 2; } return 0; } void Motor_On(void) { MOTOR_PIN 1; delay_ms(800); // 电机运行0.8秒推动商品掉落到取货口 MOTOR_PIN 0; } void ProcessKey(void) { unsigned char key KeyScan(); switch (key) { case 1: balance balance 10; // 每按一次增加1元 break; case 2: if (balance price) { balance balance - price; Motor_On(); LCD_Clear(); LCD_ShowString(0, 0, SALES OK!); delay_ms(1000); } else { LCD_Clear(); LCD_ShowString(0, 0, NOT ENOUGH); delay_ms(1000); } break; default: break; } LCD_Clear(); LCD_ShowString(0, 0, BALANCE:); LCD_ShowString(0, 9, ); LCD_ShowChar(0, 9, balance / 100 0); LCD_ShowChar(0, 10, balance / 10 % 10 0); LCD_ShowChar(0, 11, .); LCD_ShowChar(0, 12, balance % 10 0); }这里我把余额设计成整数“角”显示的时候再转换成“元.角”避免使用浮点数因为51单片机做浮点运算既慢又占ROM。购买成功后通过GSM发送短信通知管理员的调用可以放在Motor_On()之后这一步我会在下一小节实现。5.3 GSM模块发送短信的实现GSM模块发送短信是串口命令交互过程。51单片机通过串口发送AT指令然后解析模块返回值。最简单的方式是不解析返回值直接延时后发送下一条指令适合演示工程上更推荐按返回的“OK”或“ERROR”状态机跳转。这里给出一个易读的发送短信函数使用串口字符串发送// 文件gsm.c #include reg52.h void UART_Init(void) { SCON 0x50; // 8位数据可变波特率 TMOD 0x0F; TMOD | 0x20; // 定时器1工作在方式2 TH1 0xFD; // 波特率9600晶振11.0592MHz TL1 0xFD; TR1 1; ES 1; EA 1; } void UART_SendString(char *str) { while (*str ! \0) { SBUF *str; while (TI 0); TI 0; str; } } void GSM_SendSMS(char *phone, char *msg) { // 1. 设置短信格式为文本模式 UART_SendString(ATCMGF1\r\n); delay_ms(500); // 2. 发送短信命令 UART_SendString(ATCMGS\); UART_SendString(phone); UART_SendString(\\r\n); delay_ms(500); // 3. 发送短信内容 UART_SendString(msg); // 4. 发送十六进制结束符 0x1A SBUF 0x1A; while (TI 0); TI 0; delay_ms(1000); }如果模块工作正常调用GSM_SendSMS(13800138000, Vending3 out of stock!)就能把短信发出去。这里有几个关键点需要解释ATCMGF1是把GSM模块切换到文本模式否则默认可能是PDU模式中英文内容处理完全不同。ATCMGS手机号后面的\r\n必须完整不能只写\n。短信内容结束必须发送十六进制的0x1A相当于键盘上的CtrlZ表示“短信内容输入结束可以发送了”。每一步之间建议加延时因为GSM模块返回“OK”需要时间。演示项目里用固定延时最简单如果想做得更可靠可以解析串口接收到的“OK”字符串。5.4 主函数整合主函数把上面几个模块整合到一个循环里。基本流程是初始化LCD → 初始化串口 → 显示初始界面 → 循环扫描按键。每次售货成功除了出货和扣款还触发一次短信通知。// 文件main.c #include reg52.h #include lcd1602.h #include vending_machine.h #include gsm.h void main(void) { unsigned char key; unsigned int salesCount 0; LCD_Init(); UART_Init(); LCD_ShowString(0, 0, VENDING MACHINE); LCD_ShowString(1, 0, BALANCE:); LCD_ShowString(1, 9, 0.0); while (1) { key KeyScan(); if (key 1) { balance balance 10; LCD_Clear(); LCD_ShowString(0, 0, BALANCE:); // 刷新显示余额这里可调用显示函数 } if (key 2) { if (balance price) { balance balance - price; Motor_On(); salesCount; LCD_Clear(); LCD_ShowString(0, 0, SALE OK!); // 发送短信通知管理员 GSM_SendSMS(13800138000, Vending: A product was sold.); } else { LCD_Clear(); LCD_ShowString(0, 0, NOT ENOUGH); } delay_ms(1000); LCD_Clear(); LCD_ShowString(0, 0, VENDING MACHINE); } } }这段代码里delay_ms函数没有在示例中展开你可以在工程里定义基于定时器或空循环的软件延时。对于课程设计来说空循环延时已经足够如果要精确控制电机时间建议用定时器中断计时避免主循环被网络或者短信阻塞时电机会失控。5.5 关于中断和串口接收的补充虽然上面的GSM发送函数只用了发送没有做接收解析但在实际项目中最好开启串口接收中断把GSM模块返回的“OK”“ERROR”等状态存到缓冲区主循环通过标志位判断是否继续下一步。这样能极大提高稳定性尤其是连续发短信时不会因为模块处理不及而导致AT指令粘连。下面给一个最简单的串口接收中断框架用于接收“OK”字符串// 串口接收中断示例 unsigned char rxBuffer[32]; unsigned char rxIndex 0; bit okFlag 0; void UART_ISR(void) interrupt 4 { unsigned char ch; if (RI) { RI 0; ch SBUF; rxBuffer[rxIndex] ch; if (rxIndex 32) rxIndex 0; if ((rxIndex 2) (rxBuffer[rxIndex - 2] O) (rxBuffer[rxIndex - 1] K)) { okFlag 1; rxIndex 0; } } }用这个方式主循环可以等待okFlag再发送下一条AT指令避免固定延时的不确定性。这是GSM模块通信里最值得优化的地方也是答辩时一个不错的加分点。6. 运行结果与效果验证6.1 编译与烧录在Keil中按下F7编译确认0 Error。然后打开STC-ISP选择对应芯片型号打开生成的hex文件点击下载给单片机重新上电。烧录成功之后LCD1602会显示“VENDING MACHINE”和“BALANCE:0.0”。如果LCD没有显示不要急着改代码先用万用表检查电源和LCD的对比度可调电阻。6.2 功能测试流程建议按下表逐项验证不要一上来就测试GSM短信因为GSM模块不稳定时排查范围会很大。测试项操作预期现象LCD显示上电两行内容正常显示无乱码投币按键按KEY_ADD余额增加并刷新余额不足购买按KEY_BUY显示“NOT ENOUGH”正常购买先投币再按KEY_BUY电机转动余额扣除显示“SALE OK”GSM发送短信调用GSM_SendSMS管理员手机收到短信当电机转动测试通过之后再接上GSM模块完成最后一次短信通知测试。这样如果短信失败至少能确定不是电机或按键的问题。6.3 判断GSM模块是否正常工作如果发送短信失败先不要看单片机代码用USB转TTL直接连接GSM模块在串口调试助手里手动输入AT然后按回车期望返回OK如果这一条都不通过说明模块供电、SIM卡、天线、串口接线至少有一项有问题。大部分GSM模块发送短信失败的案例都出在电源供电不足上。SIM900A模块在发短信时需要较大电流脉冲电压一旦跌到3.4V以下就会自动关机或返回“ERROR”。解决方法是换5V/2A以上的独立电源并且电源线要尽量短。7. 常见问题与排查思路下面表格总结了我在实际调试中最常遇到的几个问题基本覆盖了新手从硬件到软件的主要坑点。问题现象可能原因排查方式解决方案LCD1602不显示对比度电阻未调好或接线错误万用表检查电压调节V0引脚电压调整到V0约0.5V~1V重新检查RS/RW/EN接线按投币按键余额不变按键抖动或GPIO引脚定义不对串口打印或LED观察检查sbit引脚是否和电路一致增加消抖延时电机不转驱动电流不足测量电机供电电压使用ULN2003或L298N驱动不用GPIO直连GSM模块上电无反应电源功率不够观察模块指示灯是否闪烁后熄灭更换5V/2A电源检查天线和SIM卡发送短信返回ERROR短信中心号码问题或信号弱串口发送ATCSQ查信号质量如果CSQ值为0或99说明信号差换个位置或加天线发送PDU乱码使用文本模式时未设置ATCMGF1手动串口调试每条指令前设置文本模式有几个容易被忽视的细节我再单独说明一下。第一SIM卡。最好是剪好的标准SIM卡不能是停机卡。如果你用的是物联网卡注意是否支持短信业务很多物联网卡默认只支持流量不支持短信。第二短信中心号码。普通电话卡一般插卡后会自动读取短信中心号码不需要手动设置。但如果你发现模块返回“OK”却一直收不到短信可用ATCSCA?查询短信中心号码确认为空时手动设置。第三共地。单片机和GSM模块必须共地否则串口收到的数据会乱码。这几乎是所有串口外设的通用要求。8. 最佳实践与工程建议前面把功能跑通课程设计分数已经不错了。但如果想让代码质量再上一个台阶下面几条建议很有价值。8.1 代码架构把功能拆成独立模块不要把LCD驱动、按键、电机、GSM的所有代码全部堆在main.c里。建议按文件拆分为src/ ├── main.c ├── lcd1602.c ├── vending_machine.c ├── gsm.c └── delay.c每个模块只暴露必要的函数接口例如LCD_Init()、GSM_SendSMS()、Motor_On()。这样做的好处是答辩时老师追问某个细节你能快速定位到对应文件而不是在一千多行的main.c里翻找。8.2 状态机改造现在的示例代码是线性的实际项目中如果并发事件多推荐设计一个轻量状态机。比如状态包括“空闲”“投币中”“出货中”“短信发送中”。每次主循环扫描当前状态根据事件跳转。这样做可以降低电机和短信同时出现时互相干扰的概率也为以后增加多个货道留出扩展空间。8.3 GSM通信可靠性短信模块的通信不是百分之百可靠的。工程上建议每次AT指令之间增加状态机等待“OK/ERROR”后再发下一条指令。短信发送失败时允许设置重发次数上限比如3次不要死循环。短信内容尽量简短中文字符在文本模式下可能乱码建议使用英文或拼音或者改用PDU模式发送中文。生产环境建议使用4G Cat.1模块替代2G GSM模块因为国内部分运营商已经逐步减少2G网络覆盖。8.4 硬件设计注意事项电机必须经过驱动模块或三极管不能直接把电机接在51单片机GPIO上。51单片机的GPIO灌电流能力有限驱动电机时电流可能达到几百毫安甚至更大轻则电机抖动不转重则烧坏IO口。另外GSM模块复位信号、电源指示灯这些引脚建议预留测试点。接插件要选方便插拔的不要焊死因为调试时你大概率需要多次插拔SIM卡和天线。8.5 安全与合规提醒GSM模块和短信功能是合法的远程通信应用但使用时必须注意SIM卡必须是本人或实验室合法取得的号码不能用于发送垃圾短信、骚扰短信或从事其他违法用途。课程设计演示时建议使用自己的手机号或实验室专门配发的测试卡。涉及短信发送的代码也要做好发送频率限制避免因程序bug导致模块反复发送短信造成不必要的费用和影响。9. 总结与后续学习方向基于51单片机的GSM自动售货机是一个典型的多模块协同项目它涵盖了单片机开发中最常见的几个知识点GPIO控制、定时器、外部中断、串口UART、LCD显示、电机驱动以及AT指令通信。如果你能把这样一个项目完整跑通说明你对51单片机的理解已经不只是“点灯”级别了。从输入材料可以看出这篇博客贴合“51单片机课程设计”和“GSM远程通知”两个关键搜索方向。如果你正在准备课程设计或毕业设计建议先按文章里的步骤把基础版调通再往下面几个方向扩展增加多个货道用矩阵键盘选择不同商品不同货道对应不同电机引脚。用DS1302或DS18B20加入时间显示和环境温度检测让系统信息更丰富。把GSM模块换成4G Cat.1模块通过MQTT协议接入云平台实现真正的物联网远程管理。用数码管或OLED代替LCD1602降低显示模块的驱动难度。最后提醒一句做这种嵌入式综合项目难点往往不在某个单独模块的例程而在于模块之间的衔接。所以一定要按“先显示、再按键、再电机、最后GSM”的顺序推进每完成一步就验证一步。只要主线不断最终呈现出来的系统一定能在答辩或展示现场讲出完整故事。建议收藏这篇文章调试时对照着排查会少走不少弯路。
返回列表