基于STM32L0的无线转发电台设计,Lora模块的使用,低功耗设计的总结,心得,笔记

项目主要框架

主要机制

这次接手的一个项目是一个用Lora模块进行无线通信的转发电台,复位后完成MCU各个外设的初始化,再对无线模块,仪表通信进行初始化,尔后进入休眠-检测唤醒-唤醒-接收数据-处理数据-转发数据-休眠的循环当中。

主程序流程图

这张流程图是我刚接手到项目时花了半小时左右设计的,设计时感觉幸行云流水,觉得不出意外十个工作日左右应该可以完成,真正做项目时就遇到了非常多的难题。硬是拖了20个工作日才完成。

卡住我的几个难题 

难题1:第一次接触低功耗设计,无从下手

产生的原因:实际项目经验不足,在学校,在网课的,在小实验的过程中都是针对都几方面的知识点针对性的编写代码,都是使用USB或其他能一直稳定供电的设备进行供电,根本不用考虑功耗问题。而这个系统是在户外使用电池供电的,对续航能力有着较为严苛的要求。

解决方案:从stm32l0系列单片机的了解开始,再到stm32常用低功耗机制的了解,再到Lora模块功耗模式的了解。逐步有了头绪

难题2:低功耗模式下,调试极其困难,无法下载程序,会和调试器断开连接,并且MDK会崩溃,只能用任务管理器把他请出去

产生的原因:由于产品的低功耗设计,需要使用到stm32单片机的停止模式,停止模式下,CPU停止运行,无法正常的调试。看门狗定时器无法再调试模式下正常关闭。

解决方案:使用下面语句,可以在调试状态下,进入低功耗时正常的进行调试

#if (DEBUG == 1)
  // 若需要在低功耗模式下调试程序,可以打开此函数
  HAL_DBGMCU_DBG_DisableLowPowerConfig(DBGMCU_SLEEP | DBGMCU_STOP | DBGMCU_STANDBY);
#endif

经过查阅资料,网上检索了解到,理论上调试状态下IWDG看门狗不会运行,不知道是BUG还是我没设置对,又或者stm32l0系列为了降低成本和功耗阉割了这个特性,调试状态下依然会起作用,导致MCU在我调试时复位,然后我的MDK就会崩,又要去请任务管理器大神出马。于是只能添加了这段代码

#if (DEBUG != 1)
  MX_RTC_Init();
  MX_IWDG_Init();
#endif

调试状态下,直接不初始化这个外设

然后就是无法正常下载,低功耗模式下,下载的IO口是无法正常工作的,所以也是无法正常下载,最终我选择了复位时延时5s来解决,只要在这5s内下载程序就可以以正常下载(PS:使用上面的代码虽然可以在低功耗下正常调试,但前提时进入低功耗模式前进入调试模式,那我这个举例,如果想要下载调试,必须在这5s内进行)

难题3:因为各种原因错误,导致Lora丢包

产生的原因:第一次接触Lora模块,对这个模块的各项参数都不熟悉,导致在低功耗模式下在需要休眠时仍未正确休眠功耗翻车,在需要运行时未正确唤醒,或者唤醒超时时间过短,还没有处理完数据就又休眠了。总之各种对参数的不熟悉导致丢包,功耗翻车

解决方案:深入理解lora的唤醒间隔,空闲时间,超时时间,准确估算MCU处理时间,在需要的地方及时唤醒Lora。

难题4:各种问题导致系统整体电流降不下来

产生的原因:没有关闭应该关闭的外设,时钟,没有妥善处理悬空的引脚,系统频率太高,实际根本不需要这么高的频率

解决方案:将未使用的引脚全部置为模拟态,低功耗模式下将除了与唤醒有关引脚以外的引脚全部置为模拟态,频率不是越高越好,而是越合适越好,不用的外设就关掉,用到时再重新初始化(这是我在学校学习留下来的毛病,拿到芯片直接置为最高频率,不用的引脚直接不管,外设被mx初始化完就不管了)

难题5:较高的实时性要求,需要精准的在需要的地方进行延时

产生的原因:对MCU和Lora模块处理任务时间的不理解,一开始的Lora执行命令函数中,我只会给模块50ms的处理命令时间,于是所有AT命令执行的都非常不稳定,有时候能成功,有时候不能,和玄学一样。

解决方案:无论是任何模块都要了解他的处理时序,嵌入式开发是对实时性要求很高的一门技术,需要精确的指导每一个步骤所需时间,经过我的细致观察我发现Lora执行一个AT指令至少要300ms,部分指令要400ms,有些指令重启生效,波特率等用于通信的指令不可以随便更改

难题6:Flash只有16K,内存资源极其紧张

产生的原因:和前面几个问题产生的根本原因几乎一样----缺乏经验。由于Flash太小,我使用的单片机是装不下多少代码的。我一开始竟然妄图使用RTOS,设计了七八个任务,好几个消息队列,最后足足编写了两天的代码结果Flash完全装不下 

解决方案:依据适合的场景选择适合的开发方式,内存紧张时就要选用裸机开发,不用的外设驱动就裁剪掉,增加编译器优化等级,减少全局变量的使用,内存实在紧张可以考虑使用微库

做的不足的地方

1.没有清晰的开发计划

在这次开发的过程中,没有预先制定每一个模块的开发时间,开发时一直都是走一步看一步,遇到卡壳的地方,就一直死磕,先搜索,搜索不到就AI,AI不行就单步调试,再不行就看手册,再不行就继续这个循环,直到行为止。造成的后果就是原先计划10个工作日左右完成的内容硬是拖了20个工作日才完成。

2.开发习惯不好

在开发的过程中,会随着程序的流程转移开发任务,比如当某个模块要用到串口时,我会转移到串口开发,等开发完串口,再回到原先开发的程序进行编写(有一点像汇编中函数跳转一样),这样的缺点很明显,当开发完子流程后回到原先的开发,那种行云流水,一气呵成的感觉就没了,需要花费额外的时间重新找回这个感觉,经过这次实践,我发现,先写一个子流程函数声明,把这个程序块写完,然后在去编写子流程,这样不仅能增加开发效率,还可以降低开发难度(因为我大脑的栈空间有限,无法支持来回的跳转),这就有一点像C++中的虚函数。

3.封装过度

很合理的封装可以增加可维护性和可读性,但本次的项目中有很多地方我进行了过度封装,这是多余的部分,比如在进入/退出低功耗的函数设计中,我在这些函数中又添加了进入/退出低功耗模式硬件初始化函数,这是多余的,完全可以在进入/退出低功耗的函数中直接实现

做的比较好的地方

1.代码习惯良好。

我学过HAL库,Freertos,Linux驱动开发(虽然只到点灯的地步),这些优秀的代码对我产生了深远影响,他们良好的设计方式,恰逢其时的注释,让我学会了很多增加代码可读性,可移植性的技巧。例如使用结构体封装某一功能,尽量多的使用宏代替常数,每隔函数都是用标准的注释方法注释清楚,以StatusType代替void作为程序的返回值,以if(xxx(xxx,xxx)==FALSE){ErrorHandle();}代替xxx(xxx,xxx);使用#define xxx_IO GPIOx,#define xxx_pin GPIO_PIN_xxx。以下也是一个比较合适的案例

//封装前
uint8_t Uart1_Buf[150];
uint8_t Uart1_Num;
uint8_t Uart1_Flag;
uint32_t Uart1_Time;
uint8_t Uart1_Temp;

uint8_t Uart2_Buf[150];
uint8_t Uart2_Num;
uint8_t Uart2_Flag;
uint32_t Uart2_Time;
uint8_t Uart2_Temp;


//封装后
//usart.h
#define UART_BUFF_SIZE 150
typedef struct
{
	uint8_t Buff[UART_BUFF_SIZE];
	uint8_t Num;
	uint8_t temp;
	volatile uint8_t Flag;
	volatile uint32_t Time;
} _UART_Rx;
extern _UART_Rx UART1_Rx;
extern _UART_Rx UART2_Rx;

//usart.c
_UART_Rx UART1_Rx={0};
_UART_Rx UART2_Rx={0};

2.保持自信,没有被困难吓退

这个项目其实是有一套老代码的,但是使用的Lora模块功耗较大,所以要换模块,原先的代码极其混乱,不易阅读,更不易移植,而这个重构的任务竟然全部交到了我一个人手上,要知道我大四还没毕业,没有任何拿得出手的项目经验,尽然让我来做优化功耗这种极其吃经验的项目。但是我没有退缩,迎难而上。从单片机低功耗系列,到单片机低功耗模式,到单片机低功耗技巧,到啊模块的低功耗,一步一个脚印的去了解,去实践,最终超额完成了任务,任务指标是休眠时,3.6V下,电流小于50uA,经过我的优化达到了12uA。

3.敢于革新

我没有寄希望于老项目微调(其实有,只是老项目实在。。。),用我学习linux开发的模块化思想重构了项目,在解决丢包的过程中我没有守旧使用老工程的办法,而是采用新模块的新特性数据重传,前向纠错,信道检测等等优化数据传输,最终在测试中实现了100%准确传输

我的收获

1.收获了低功耗设计方法,思路,调试技巧

方法:选择低功耗的芯片,选择合适的低功耗模式,仅在需要的时候打开需要的外设,不用的引脚要设为模拟态,如果希望能正常调试,不能复位后立马进入低功耗模式,要预留一些时间用来连接调试器,以便下载程序,调试程序

2.更深刻的明白良好的代码习惯的好处

将一个模块封装为结构体,而不是一大堆散乱的全局变量(diss一下老项目。。。),单独的模块要放在单独的文件中,并且用对应的头文件去声明它的函数,变量,而不是全放在main.c里面(再diss一下老项目)在主程序中只留下清晰的逻辑,将单独的功能放在单独的函数中,而不是所有细节全放在main函数里面(再再diss一下老项目)

3.学会了沟通技巧

之前都是在一些非常成熟的开发板上面进行操作,而且针对初学者,资料特别多,有什么问题一查就知道,实际开发的工程中,板子的功能是比较单一的,最了解它的还是设计这个PCB的人,有什么问题一定要多问,但是也不是高频次的问,而是思考后囤积一下问题,在合适的时间一次性问。同时也学会了相处时的态度把控,有时需要虚心求教,但有时保持自己的观点也是很重要的,如果确实不是自己的问题,找出有说服力的论据,保持理性沟通的心态,据理力争也是有必要的。

4.学习了Lora模块的使用

通过本次项目,我全面的了解了Lora模块的基本使用,包括信道,空中速率,地址等等最只因本的参数,也包括唤醒间隔,超时时间,空闲时间等等与低功耗有关的参数,通过调节这些参数,我学会了控制一个远程通信保持稳定,不丢包的方法。

5.学习了下位机与上位机通信协议技巧

在学校的学习中,上位机下位机通信,最常用的办法就是发送时使用printf重映射,接收时按照固定的长度接收,并且多使用ascii字符,然后解析字符。通过这次实际的项目开发,我发现,使用sprintf+HAL_UART_Transmit+strlen的方式效率会比printf更高,适用范围也更广,使用通信协议,先发送链接码表示已经和上位机链接,要发送数据时,可以先发固定长度字符串,其中包含下一步要发送的数据大小,然后下位机发送确认码,然后上位机再发送数据,这样做在数据比较长时,有比较明显的优势,使用HEX+索引的方式要比字符串更高效,如下代码所示

//字符串解析方式
//上位机发送:A=1,B=2,C=3
//下位机
sscanf(Rx,"A=%d,B=%d,C=%d",&A,&B,&C);

//HEX+索引方式
//上位机发送L:00 01 02
//下位机接收
#define A_Index 0
#define B_Index 1
#define C_Index 2

A=Rx[A_Index];
B=Rx[B_Index];
C=Rx[C_Index];

6.学会了内存省着用

直到这次实际的项目开发,我才体会到MCU资源紧张是什么概念,之前在学校里用的是128KB的Flash,20KB的SRAM,完全想象不到竟然会有MCU装不下RTOS(或许是我不会裁剪),这次使用的是stm32L0系列的一个小容量产品,内存资源极其紧张,使用稍微大一点的数组时,我都要用代码块{}括起来,以便于节省空间,并且减少了库函数的使用,用自己编写的更简短的代码代替库函数。学会了看map文件,学会了看链接脚本sct文件以达到了解内存的分布(其实没有学会,只达到了初步看懂)。

  • 19
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值