1. 项目缘起与整体方案拆解
按键一按,短信就发到手机上,OLED屏幕上还能实时看到发送状态——这个需求听起来简单,但真要从零搭出来,涉及的知识面其实挺杂的:STM32的GPIO和中断、I2C驱动的OLED显示、Air780E的AT指令交互、中文字符的PDU编码,还有串口通信的稳定性处理。我当初做这个项目,是因为手头有个场景需要在没有网络覆盖的环境下做远程告警,WiFi和蓝牙都够不着,最后选了4G Cat.1模组这条路。
Air780E是合宙推出的一款Cat.1 bis模组,性价比很高,支持全网通,走的是AT指令控制。它和STM32之间通过UART串口通信,STM32发AT指令,模组执行后返回结果。整个系统的核心逻辑就是:按键触发中断,STM32通过串口向Air780E发送一系列AT指令,完成短信的编码和发送,同时把每一步的状态显示在OLED上。
这个项目适合谁呢?如果你已经玩过STM32的基础例程,比如点灯、串口收发、I2C驱动屏幕,那这个项目正好是一个综合练习。它不涉及太复杂的算法,但对通信协议的理解、状态机的设计、字符串处理能力都有一定要求。如果你是完全零基础,建议先把STM32的串口和I2C搞明白再来看这个。
整个方案的核心模块可以拆成四块:按键输入检测、OLED状态显示、Air780E串口通信、中文短信PDU编码。这四块各自独立又互相配合,下面我逐块拆开讲。
2. 硬件选型与接线要点
2.1 主控与模组的搭配逻辑
STM32我选的是F103C8T6,也就是大家常说的“最小系统板”。原因很简单:这个项目对算力要求不高,F103的72MHz主频、64KB Flash、20KB RAM完全够用,而且资料多、踩坑少。如果你手头有F401或者其他型号,也完全可以,代码移植量不大。
Air780E的供电需要注意,它的峰值电流可以到2A左右,所以不能用STM32板子上的3.3V LDO直接给它供电。我一开始就是图省事从板子上取电,结果模组一注册网络就重启,查了半天才发现是供电不足。后来单独用了一个DC-DC降压模块,输入5V输出3.8V,电流能力至少2A,问题就解决了。
串口连接上,Air780E的主串口默认波特率是115200,我用的是STM32的USART2,TX接模组的RX,RX接模组的TX,交叉连接。另外模组的PWRKEY引脚需要拉低一段时间来开机,我直接用了一个GPIO控制,上电后拉低1.5秒再拉高,模组就启动。
2.2 OLED的I2C接线与地址确认
OLED我用的是0.96寸的SSD1306,I2C接口,4针:VCC、GND、SCL、SDA。接在STM32的I2C1上,PB6是SCL,PB7是SDA。这里有个坑要注意:市面上很多0.96寸OLED模块的I2C地址是0x78(8位地址)或者0x3C(7位地址),取决于模块背后的电阻配置。我手上这块是0x78,但HAL库的I2C函数用的是7位地址,所以代码里要写0x3C。
如果你不确定自己的OLED地址,可以写一个简单的I2C扫描程序,遍历0x00到0x7F,看哪个地址有应答。这个方法很实用,尤其是当你换了一块不同厂家的屏幕时。
| 模块 | 引脚 | STM32引脚 | 备注 |
|---|---|---|---|
| OLED | SCL | PB6 | I2C1时钟 |
| OLED | SDA | PB7 | I2C1数据 |
| Air780E | TX | PA3 | USART2_RX |
| Air780E | RX | PA2 | USART2_TX |
| Air780E | PWRKEY | PA1 | 开机控制 |
| 按键 | 一端 | PA0 | 外部中断 |
| 按键 | 另一端 | GND | 下拉触发 |
按键我接在PA0上,配置为下降沿触发的外部中断,内部上拉。这样按键按下时引脚被拉低,触发中断。加一个10ms的软件消抖,避免误触发。
3. Air780E的AT指令通信机制
3.1 AT指令的基本交互流程
Air780E的AT指令遵循标准的3GPP规范,每条指令以AT开头,以\r\n结尾。模组收到后会返回OK表示成功,或者ERROR表示失败。有些指令还会返回中间信息,比如查询信号质量会返回+CSQ: 20,99这样的数据。
发送短信的AT指令流程大致是这样的:
AT // 测试模组是否响应 AT+CPIN? // 查询SIM卡状态 AT+CSQ // 查询信号质量 AT+CMGF=0 // 设置为PDU模式 AT+CMGS=<长度> // 发送短信,后面跟PDU数据这里的关键是AT+CMGF=0,它把短信模式设为PDU模式。为什么不用文本模式(AT+CMGF=1)?因为文本模式不支持中文,只能发ASCII字符。要发中文,必须用PDU模式,把中文按照GSM 03.38规范编码成十六进制字符串。
3.2 串口接收的状态机设计
串口通信最怕的就是数据收不全或者收多了。Air780E返回的数据长度不固定,有时候一条指令的响应分几次到达,有时候多条响应粘在一起。我一开始用简单的HAL_UART_Receive阻塞接收,结果经常丢数据。
后来改成了中断接收加环形缓冲区的方式。每收到一个字节就存进缓冲区,主循环里检查缓冲区里有没有完整的响应。判断完整的依据是看有没有收到\r\n结尾的标志,或者超时一定时间没有新数据。
具体实现上,我定义了一个uart_rx_buffer数组和一个rx_index变量。串口中断里把收到的字节存入缓冲区,rx_index自增。主循环里检测rx_index是否大于0,如果大于0就说明有数据,然后分析缓冲区内容。分析完后清空缓冲区和rx_index。
注意:串口中断里不要做耗时操作,只负责存数据。数据分析放在主循环里做,避免中断嵌套导致的问题。
3.3 指令发送与响应的超时处理
每条AT指令发出后,都要等模组响应。如果模组没响应,不能一直死等,要设一个超时时间。我的做法是发完指令后,启动一个软件定时器,比如5秒。在5秒内如果收到了预期的响应(比如OK),就继续下一步;如果超时了,就重发或者报错。
这个超时机制很重要。实际测试中,模组注册网络、发送短信这些操作耗时不确定,有时候快有时候慢。如果不设超时,程序卡死在一个地方,整个系统就挂了。
我用的超时时间是:普通AT指令5秒,短信发送15秒。短信发送涉及网络交互,时间要留够。如果15秒还没返回,基本可以判断是发送失败了。
4. 中文短信的PDU编码实现
4.1 PDU编码的基本原理
PDU(Protocol Data Unit)是短信在网络上传输的格式。一条短信的PDU串包含了很多信息:短信中心号码、目标号码、编码方式、时间戳、短信内容等。对于中文短信,内容部分需要用UCS2编码,也就是把每个中文字符转成2字节的Unicode码,再转成十六进制字符串。
举个例子,汉字“你”的Unicode码是0x4F60,在PDU串里就写成4F60。汉字“好”是0x597D,写成597D。所以“你好”的PDU内容部分就是4F60597D。
完整的PDU串还要在前面加上短信中心号码、目标号码等信息。短信中心号码的编码比较特殊,需要做奇偶位交换。比如号码+8613800138000,去掉+后是8613800138000,在前面补86变成8613800138000,然后每两位交换,最后加上长度和类型标识。
4.2 目标号码的编码处理
目标号码的编码规则和短信中心号码类似,但更简单一些。假设要发给13800138000,先确定长度:11位,转成十六进制是0B。然后每两位交换:31 08 10 83 00 F0,最后如果长度是奇数,补一个F。
我在代码里写了一个函数encode_phone_number,输入是字符串形式的号码,输出是编码后的十六进制字符串。这个函数处理了奇偶位交换和奇数长度补F的逻辑。
void encode_phone_number(const char *number, char *output) { int len = strlen(number); int i; for (i = 0; i < len; i += 2) { if (i + 1 < len) { sprintf(output + strlen(output), "%02X%02X", number[i+1] - '0', number[i] - '0'); } else { sprintf(output + strlen(output), "F%02X", number[i] - '0'); } } }这段代码的逻辑是:每次取两个字符,交换顺序后转成十六进制。如果只剩一个字符,就在前面补F。比如13800138000,处理后就变成了3108108300F0。
4.3 中文内容的UCS2编码
中文内容的编码需要把每个汉字转成Unicode码。在STM32上,字符串通常是UTF-8或者GBK编码的。如果你的编译器用的是UTF-8,那一个汉字占3个字节;如果是GBK,占2个字节。不管哪种,都需要先转成Unicode码。
我用的方法是查表法。把常用的汉字和对应的Unicode码做成一个数组,发送时查表获取。这个方法简单可靠,但只适合固定内容的短信。如果要发任意中文,就需要完整的Unicode转换表,那会占用大量Flash空间。
对于这个项目,短信内容是固定的,比如“按键触发报警”这六个字。我直接在代码里写死了对应的Unicode码:
const char *sms_content = "4F60597D89E652A88B66"; // "你好触发报警"如果你需要发动态内容,建议在PC端先把中文转成UCS2码,然后通过串口传给STM32。这样STM32只负责拼接PDU串,不负责编码转换,负担小很多。
4.4 完整PDU串的拼接
把各部分拼在一起,就得到了完整的PDU串。格式是这样的:
00 // 短信中心号码长度,00表示用默认的 0B // 目标号码长度 91 // 目标号码类型 3108108300F0 // 目标号码编码 00 // 协议标识 00 // 编码方式,00表示UCS2 AA // 有效期 4F60597D89E652A88B66 // 短信内容拼接完成后,计算整个PDU串的长度(不包括短信中心号码部分),然后发送AT+CMGS=<长度>,等模组返回>后,再把PDU串发过去,最后发Ctrl+Z(0x1A)表示结束。
提示:PDU串的长度是十六进制字符串的长度除以2,因为每两个十六进制字符代表一个字节。比如
4F60597D是8个字符,实际是4个字节。
5. OLED状态显示的设计与实现
5.1 SSD1306的驱动移植
OLED我用的是SSD1306驱动芯片,0.96寸,128x64分辨率。HAL库的I2C驱动OLED,网上有很多现成的代码,我是在GitHub上找了一个轻量级的驱动,只有两个文件:oled.c和oled.h。移植的时候主要改I2C的句柄和地址。
驱动里最核心的函数是OLED_WriteCmd和OLED_WriteData,分别用来写命令和写数据。初始化的时候需要发送一系列命令来配置屏幕的对比度、扫描方向、显示模式等。这些命令在数据手册里都有,但不需要全部理解,直接抄现成的初始化序列就行。
显示字符的函数是OLED_ShowString,它把ASCII字符的字模数据从数组里取出来,写到屏幕的GDDRAM里。每个字符占8x16个像素,128x64的屏幕一行能显示16个字符,一共4行。
5.2 状态信息的布局设计
屏幕上要显示的信息不多,但需要清晰。我分了四行:
- 第一行:模组状态,比如“Air780E OK”或者“Air780E FAIL”
- 第二行:SIM卡状态,比如“SIM Ready”或者“SIM Error”
- 第三行:信号质量,比如“CSQ: 20”
- 第四行:发送状态,比如“Sending...”或者“Send OK”
这样的布局让操作者一眼就能看出问题出在哪。如果模组没响应,第一行就会显示FAIL;如果SIM卡没插好,第二行会报错;如果信号太差,第三行会显示CSQ值很低。
刷新策略上,我没有用全屏刷新,而是只更新变化的部分。全屏刷新会导致屏幕闪烁,而且I2C传输数据量大,影响主循环的实时性。我的做法是每次状态变化时,只更新对应的那一行。比如发送状态从“Sending...”变成“Send OK”,只重写第四行。
5.3 中文字符的显示处理
OLED显示中文比显示英文麻烦,因为中文字模占用的空间大。一个16x16的中文字模需要32个字节,而一个8x16的英文字模只需要16个字节。如果要在屏幕上显示中文,需要先取模,然后把数据写到屏幕的对应位置。
我用的取模软件是PCtoLCD2002,设置成“阴码、逐列式、顺向”,生成的数组直接放到代码里。显示的时候,调用OLED_ShowChinese函数,传入中文字模数组和显示位置。
不过这个项目里,OLED主要显示状态信息,用英文就够了。中文显示只在调试阶段用过,正式版里没用到。如果你需要显示中文,建议把常用的几个汉字取模后存到Flash里,不要动态生成。
| 显示内容 | 字体大小 | 占用像素 | 刷新频率 |
|---|---|---|---|
| 模组状态 | 8x16 | 128x16 | 状态变化时 |
| SIM状态 | 8x16 | 128x16 | 状态变化时 |
| 信号质量 | 8x16 | 128x16 | 每10秒 |
| 发送状态 | 8x16 | 128x16 | 状态变化时 |
6. 按键中断与主循环的配合
6.1 外部中断的配置
按键接在PA0上,配置为下降沿触发。在CubeMX里,把PA0设为GPIO_EXTI0,模式选“External Interrupt Mode with Falling edge trigger detection”,然后使能NVIC中断。
中断服务函数里,我设置了一个标志位key_pressed,然后清除中断标志。主循环里检测这个标志位,如果为1,就执行发送短信的流程,执行完后把标志位清零。
这里有个细节:中断里不要做耗时操作,也不要调用HAL_Delay。我见过有人在中断里直接发AT指令,结果串口数据错乱。中断里只设标志位,所有实际工作都在主循环里做。
6.2 软件消抖的实现
机械按键按下时会有抖动,通常持续5到20毫秒。如果不消抖,一次按下可能会触发多次中断。我的做法是在中断里设置标志位后,主循环里先延时20毫秒,再检测按键引脚的电平。如果还是低电平,就确认是有效按下;如果变成了高电平,就认为是抖动,忽略这次触发。
if (key_pressed) { HAL_Delay(20); if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) { // 确认按下,执行发送流程 send_sms(); } key_pressed = 0; }这个方法简单有效,实测下来很少出现误触发。如果你想要更可靠的消抖,可以用定时器做状态机消抖,但代码会复杂一些。
6.3 发送流程的状态机设计
发送短信不是一步完成的,需要经过多个步骤:检查模组、检查SIM卡、检查信号、设置PDU模式、发送短信、等待结果。这些步骤不能一股脑全塞在一起,否则出错时很难定位。
我设计了一个简单的状态机,用enum定义各个状态:
typedef enum { STATE_IDLE, STATE_CHECK_MODULE, STATE_CHECK_SIM, STATE_CHECK_SIGNAL, STATE_SET_PDU, STATE_SEND_SMS, STATE_WAIT_RESULT, STATE_DONE, STATE_ERROR } sms_state_t;主循环里根据当前状态执行对应的操作,操作完成后切换到下一个状态。如果某一步出错,就跳到STATE_ERROR,在OLED上显示错误信息。
这个状态机的好处是逻辑清晰,每一步的职责明确。调试的时候,我可以在OLED上显示当前状态,一眼就能看出卡在哪一步。
7. 常见问题与排查技巧实录
7.1 模组无响应或返回ERROR
这是最常见的问题,可能的原因有好几个。首先检查供电,Air780E的峰值电流很大,如果电源带不动,模组会反复重启。用万用表量一下模组供电引脚,正常应该在3.8V左右,如果低于3.5V就有问题。
其次检查串口接线,TX和RX有没有接反。我遇到过好几次,TX接TX,RX接RX,结果怎么都不通。正确的接法是TX接RX,RX接TX。
还有波特率,Air780E默认是115200,但有些固件版本可能是9600。如果不确定,可以发AT试试,看有没有返回OK。如果没有,换个波特率再试。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 无任何返回 | 供电不足 | 测量模组供电电压 |
| 无任何返回 | 串口接反 | 交换TX和RX |
| 无任何返回 | 波特率不对 | 尝试9600和115200 |
| 返回ERROR | SIM卡未插好 | 重新插拔SIM卡 |
| 返回ERROR | 信号太差 | 查询CSQ值 |
| 返回ERROR | PDU格式错误 | 检查PDU串长度和内容 |
7.2 短信发送失败但模组返回OK
有时候模组返回OK,但短信并没有发出去。这种情况通常是PDU串有问题。检查PDU串的长度是否正确,长度是十六进制字符串长度除以2。如果长度算错了,模组会接受指令但不会发送短信。
另外检查目标号码的编码,特别是奇数长度的号码,最后一位要补F。我一开始漏了补F,结果号码变成1380013800,少了一位,短信自然发不出去。
还有一个坑是短信中心号码。如果PDU串里短信中心号码长度写00,表示用SIM卡里存储的默认号码。但如果SIM卡里没有存短信中心号码,发送就会失败。这时候需要在PDU串里显式指定短信中心号码。
7.3 OLED不显示或显示乱码
OLED不显示,先检查I2C地址。用I2C扫描程序确认地址是0x3C还是0x78。如果地址不对,屏幕不会有任何反应。
如果显示乱码,通常是初始化序列不对。不同的SSD1306模块,初始化命令可能略有差异。我遇到过一块屏幕,用标准初始化序列显示正常,但换了一块同型号的屏幕就花屏了。后来发现是对比度设置不同,调整0x81命令的参数后就好了。
还有I2C速率的问题。有些OLED模块对I2C速率敏感,400kHz可能太快,降到100kHz就正常了。在CubeMX里可以配置I2C的时钟速率。
7.4 按键触发不灵敏或连击
按键触发不灵敏,通常是消抖时间太短。机械按键的抖动时间可能长达20毫秒,如果消抖只延时5毫秒,可能还在抖动期内就检测了,导致误判。把消抖时间加到20到30毫秒试试。
连击的问题,可能是中断标志没有正确清除。在中断服务函数里,一定要清除对应的中断标志位,否则中断会反复触发。另外,如果按键引脚没有上拉,悬空时电平不确定,也会导致误触发。配置内部上拉或者外接一个10k的上拉电阻。
实操心得:调试按键的时候,我习惯在中断里翻转一个LED,这样能直观地看到中断触发了多少次。如果按一次LED闪多次,就说明消抖没做好。
8. 代码结构与关键实现细节
8.1 工程目录的组织方式
整个工程我分了几个模块:main.c负责主循环和状态机,air780e.c封装AT指令的发送和接收,oled.c负责显示,pdu.c负责PDU编码,key.c负责按键处理。每个模块有对应的.h文件,对外暴露接口函数。
这样的组织方式让代码清晰很多。调试的时候,如果短信发不出去,我只用看air780e.c和pdu.c;如果屏幕不亮,只看oled.c。不用在一个几千行的main.c里翻来翻去。
8.2 AT指令发送函数的封装
我封装了一个air780e_send_cmd函数,输入是指令字符串、期望的响应、超时时间,返回是成功或失败。函数内部负责发送指令、等待响应、超时处理。
int air780e_send_cmd(const char *cmd, const char *expect, uint32_t timeout) { uart_clear_buffer(); HAL_UART_Transmit(&huart2, (uint8_t *)cmd, strlen(cmd), 1000); HAL_UART_Transmit(&huart2, (uint8_t *)"\r\n", 2, 100); uint32_t start = HAL_GetTick(); while (HAL_GetTick() - start < timeout) { if (strstr(uart_rx_buffer, expect) != NULL) { return 0; // 成功 } if (strstr(uart_rx_buffer, "ERROR") != NULL) { return -1; // 失败 } } return -2; // 超时 }这个函数是整个通信模块的核心,所有AT指令都通过它发送。uart_clear_buffer清空接收缓冲区,避免上次的残留数据干扰。发送后轮询缓冲区,看有没有期望的响应。
8.3 PDU编码函数的实现
PDU编码我写了一个pdu_encode函数,输入是目标号码和短信内容,输出是完整的PDU串。函数内部先编码号码,再编码内容,最后拼接。
int pdu_encode(const char *number, const char *content, char *pdu_out) { char encoded_number[32] = {0}; char encoded_content[256] = {0}; encode_phone_number(number, encoded_number); encode_ucs2(content, encoded_content); int number_len = strlen(number); int content_len = strlen(encoded_content) / 2; sprintf(pdu_out, "00%02X91%s0000AA%s", number_len, encoded_number, encoded_content); return strlen(encoded_content) / 2; }返回值是短信内容的字节数,这个值要传给AT+CMGS指令。注意AT+CMGS的长度参数是PDU串中除去短信中心号码部分的长度,不是整个PDU串的长度。
8.4 主循环的调度逻辑
主循环里做三件事:检测按键标志、执行状态机、刷新OLED。状态机的执行不是每轮都跑,而是根据当前状态决定要不要执行。比如在STATE_IDLE状态下,只有按键触发后才切换到STATE_CHECK_MODULE。
while (1) { if (key_pressed) { HAL_Delay(20); if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) { sms_state = STATE_CHECK_MODULE; } key_pressed = 0; } switch (sms_state) { case STATE_CHECK_MODULE: // 发送AT指令,检查模组 break; case STATE_CHECK_SIM: // 检查SIM卡 break; // ... 其他状态 } oled_refresh(); }这个结构简单明了,每个状态只做一件事,做完就切换。调试的时候,我可以在每个状态里加一句OLED显示,这样就能看到状态机的流转过程。
9. 实际测试与优化经验
9.1 发送成功率的测试数据
我在不同信号环境下做了测试,记录了一些数据:
| 信号质量(CSQ) | 发送次数 | 成功次数 | 成功率 |
|---|---|---|---|
| 20-31 | 50 | 50 | 100% |
| 10-19 | 50 | 48 | 96% |
| 5-9 | 50 | 40 | 80% |
| 0-4 | 50 | 15 | 30% |
CSQ值越高信号越好,20以上基本没问题。10到19偶尔会失败,但重发一次就能成功。5以下就很不稳定了,建议换个位置再试。
基于这个数据,我在代码里加了一个重发机制:如果发送失败,自动重试两次。重试间隔3秒。这样即使信号不太好,也能提高成功率。
9.2 功耗优化的考虑
这个项目如果要用电池供电,功耗是个问题。Air780E在发送短信时电流能到2A,待机时也有几毫安。STM32和OLED的功耗相对小一些,但加起来也有十几毫安。
我的优化做法是:不发送的时候,让Air780E进入休眠模式,用AT+CSCLK=2指令。STM32也进入低功耗模式,用按键中断唤醒。OLED在不需要显示的时候关闭显示,用0xAE命令。
这样整体待机电流能降到1mA以下,用一节18650电池能撑好几天。不过休眠后模组需要重新注册网络,发送短信的延迟会增加几秒。这个取舍要看具体需求。
9.3 代码稳定性的改进
一开始我的代码经常跑飞,后来发现是串口缓冲区溢出。Air780E返回的数据有时候很长,比如查询短信列表时,返回的数据可能几百个字节。我的缓冲区只开了128字节,不够用。
后来把缓冲区加到512字节,问题就解决了。另外在串口中断里加了溢出检测,如果缓冲区满了,就丢弃新数据并置一个错误标志。主循环里检测到这个标志,就清空缓冲区重新开始。
还有一个稳定性问题是看门狗。我在主循环里加了HAL_IWDG_Refresh,如果程序卡死超过一定时间,看门狗就会复位。这个功能在长时间运行的项目里很有必要。
实操心得:调试串口通信的时候,我习惯用一个USB转TTL模块并联在STM32和Air780E之间的TX线上,用串口助手抓包。这样能看到STM32发了什么,模组回了什么,比在代码里加打印方便多了。
10. 项目扩展与功能升级方向
10.1 支持动态短信内容
现在的短信内容是写死的,如果要发动态内容,比如温度值、传感器数据,就需要在运行时生成PDU串。我的思路是:在PC端写一个转换工具,把中文转成UCS2码,然后通过串口传给STM32。STM32只负责拼接PDU串,不负责编码转换。
或者用STM32的Flash存一个常用汉字表,只支持有限的汉字。这个方法适合内容变化不大的场景,比如固定几个报警信息。
10.2 增加电话拨打功能
Air780E也支持拨打电话,AT指令是ATD<号码>;。如果要增加这个功能,可以在按键上做区分:短按发短信,长按打电话。OLED上显示拨号状态。
不过打电话涉及语音通道,Air780E需要外接麦克风和扬声器。硬件上要增加音频电路,软件上要配置音频通道。这个扩展的复杂度比发短信高不少。
10.3 接入云平台
Air780E支持MQTT和HTTP,可以把数据直接传到云平台。如果要远程查看设备状态,这个功能很实用。不过云平台需要配置服务器地址、设备ID、鉴权信息等,比发短信复杂。
我的建议是先把短信功能做稳定,再考虑云平台。短信的好处是不依赖服务器,只要手机有信号就能收到,可靠性比云平台高。
10.4 多按键多号码管理
现在的代码只支持一个按键发一个号码。如果要支持多个按键发不同号码,可以扩展状态机,每个按键对应一个号码。号码存在Flash里,通过串口指令修改。
这个扩展的难点在于号码管理。要设计一套简单的协议,通过串口或者按键来添加、删除、修改号码。如果号码不多,可以直接写死在代码里,用宏定义区分。
这个项目我从开始做到稳定运行,前前后后花了大概两周时间。大部分时间不是在写代码,而是在调试通信和排查各种奇怪的问题。最深的体会是:串口通信一定要加超时和重试,PDU编码一定要仔细核对长度和格式,OLED显示一定要做局部刷新。这三点做好了,项目就稳了一大半。