FLEX项目学习总结

MCU:STM32F030R8     

时钟频率:16MHZ

模块资源使用:

    ADC & DMA —— 监测系统电压,用于掉电检测

    UART2 —— 与蓝牙通信

    I2C1 —— 与DSP芯片通信

    I2C2 —— 与电池包通信

    TIM3 & TIM14 —— 呼吸灯PWM、系统延时、系统回调函数注册

MCU存储空间资源分配

 

 1、掉电检测与掉电保存

    单片机在正常工作时,因某种原因突然断电,将会丢失数据存储器(RAM)里的数据。鉴于此,可以将下次上电时要使用的数据保存到内部FLASH或者外部FLASH中;另外,有时为了减少擦写FLASH的次数,也会采用掉电保存这种存储数据的方式。

    在本项目中,提供给用户调节的EQ参数在上电时需要读取上次的设置值,因此采用内部FLASH保存设置值。具体做法: MCU的ADC采样监测系统电压值的变化,当检测到电压值小于640mv时则认为系统正面临断电,立马进行掉电保存(保存EQ参数以及Mute功放)操作。

    在实际应用时,应注意以下几点:

     A、掉电电压阈值的设定应合理,FLEX是带电池包的产品,当只有电池包供电时且电池包低电量时,测得其放电电压是680mv左右,掉电电压阈值应该小于该值,才不会导致电池包低电量供电时MCU判断为掉电而去Mute功放。

     B、测试掉电时间时,除了测系统监测电压值小于640mv,还应该同时测试MCU的VDD管脚观察其掉电情况。结合STM32F030R8的datasheet知道其MCU的正常工作电压最低只能到2.4v,因此需测量从640mv开始MCU的VDD管脚掉到2.4v的时间。实际测量得到这段时间大概是6ms,而擦除一页FLASH以及写入EQ参数的时间为32ms,因此这个时间远远不够。为了解决这个问题,有两种可行方案:一是提高掉电检测电压阈值,但是在检测到掉电时除了要擦写FLASH,还要Mute功放,因此这个电压阈值提高的同时要兼顾只有电池包供电时SYS_DETECT脚的电压,否则当只使用电池包供电时也会把功放Mute掉;二是增大MCU的VDD脚的电容,从而延迟MCU的掉电时间,实际测试到大约需要380uF左右的电容可将掉电时间延迟到40ms左右。具体采用哪种方案应该结合硬件来考虑。

    C、实际项目开发过程中发现,为了保证掉电保存的可靠性,在上电时要对存储EQ参数的FLASH区域进行检测,如果上次掉电时没有写成功则应该赋予EQ参数一些初始值。在这里,判断有两种方法:

          a、在本项目中EQ参数的个数和长度是已知的,,且每次写FLASH之前会将FLASH擦除,因此可以在上电时直接读取EQ参数保存区域的最后一个32位地址,如果该地址的值为0xFFFFFFFF,则证明上次掉电保存没有完全成功,需要自行初始化EQ参数的值。如果该地址的值不为0xFFFFFFFF,则可认为上次掉电保存成功,可以从FLASH中取EQ参数。

          b、当要写的参数长度不确定时,可以采用另一种思路,即另外采用一个没有使用的FLASH地址空间作为更新标志。例如在写参数前将更新标志置为0,写完后置为1.每次上电时对这个FLASH地址进行读取操作,通过其值判断上次掉电保存成功与否。

 

2、HardFault的调试

    在计算滤波器EQ参数以及设置DSP时发生了HardFault错误,调试方法:Debug程序,在发生HardFault时停止运行,找到SP的值,大概4个字后存储的是发生HardFault语句的地址,定位找到程序中的语句再排除错误。一般而言,当数组越界、指针乱指或堆栈溢出时会发生HardFault错误。我这里是因为结构体里的指针传给形参时没有规范好而发生HardFault。

 

3、电池包

     FLEX使用的是可充电的单颗电池包,其中充电由充电IC(BQ24780S)设置充电电压和充电电流,同时监测输入电流、健康度等信息。而电池包芯片(BQ28Z610)则包含电池包的实际信息。当充电时,需要设置充电IC的充电电压和充电电流,如果正常充电,则电池包芯片的电流与设置的充电电流大小相似;当放电时,电池包芯片的电流一般为负值,如果外部没有什么负载耗电也有可能是0mA。因此,程序中判断电池包的充电和放电应根据电池包芯片的电流信息来判定,不能单纯通过充电电压充电电流设置成功来判定。

 

4、蓝牙与MCU的串口通信

      MCU接收蓝牙数据采用串口中断接收,接收到的数据会根据通信协议里的定义分类放入接收池等待解析。本项目中一共有两个地方解析接收数据:一是bt.c文件;二是dsp.c文件。一般的指示、命令和应答都在bt.c内解析,而由调参软件app发过来的调参指令和参数信息则放到dsp.c文件里面解析。在这里需要注意的一点是:因为串口开启中断接收,在什么时候接收到数据是未知的,而BT_Polling()和DSP_Polling()在while循环里面的调用有先后顺序,所以,为了防止出现误丢包(漏掉数据没有解析)的情况,应该在接收数据的结构体中设置一个标志位,标志其是否被bt或者dsp处理过,如果一个轮询周期后该标志没有被bt或者DSP处理则认为其不属于通信协议内规定的指令,则可以丢弃。

    MCU发送数据给蓝牙用到两种发送方式,一种是中断发送,一种是查询发送。在发送池里的数据会有一个“是否立即发送”的标志,如果需要立即发送则采用查询方式,否则采用中断方式,并且每个数据发送的时间间隔也是一致的。在实际应用中,为了保证实际发送数据的顺序跟插入数据时的顺序一致,用到了一个发送表(即队列数据结构),每次发送从队列头取数据,每次插入数据插到队尾。

 

5、注册回调函数

      在本项目的程序中,有许多函数需要每隔一定时间间隔就调用一次,例如ADC采样值的转换、按键的扫描以及电源电压的监测......这里特地编写了一个system.c文件,用于处理这些周期回调函数。具体做法是:配置一个定时器,设定其计时周期,假设为20ms(这个周期即是回调函数调用的周期),之后将需要执行的回调函数注册到这个定时器的计时中断处理中,此后每当定时器计时到20ms就会执行这些回调函数。注册操作由函数SYSTEM_Register(uint32_t period, TIM_CALLBACK_T callback, uint32_t count)完成。每次定时器计时到设定值就进入中断,调用HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim),在该函数内进行调用回调函数的处理。这种注册回调函数的方法,减少了调用者和被调用者之间的直接联系,使得功能更加模块化。

 

6、按键的短按、长按与释放检测策略

       本项目的按键检测中,按键的扫描周期为20ms,并设定了几个判定参数:ShortPressCount(短按计数)、LongPressCount(长按计数)、ReleaseCount(释放计数)。判定规则是:第一次进入按键扫描函数时 ,当按键IO口为高电平时,说明此时按键被按下,将press_count置为ReleaseCount,每次进扫描函数如果IO还为高电平时将press_count递增,当计数到(ShortPressCount + ReleaseCount)时记为一次短按。如果此时还是高电平,则开始longpress_count计数,当计数到(LongPressCount - ShortPressCount)时则记为一次长按;如果是低电平,说明按键在被释放中,将press_count置为ReleaseCount,并开始递减,当减到0时说明释放时间达到要求,将按键状态记为释放。

       需要注意的是,按键检测函数KEY_ScanCallback是系统注册回调函数,每20ms执行一次,因此这个函数仅仅用来记录和改变按键的状态,而根据按键状态要进行的其他耗时操作都在while循环中的KEY_Polling中进行。

 

7、关于一些实际开发中的小细节

     首先是#ifdef的使用,在实际开发过程中可能需要输出大量的调试信息,而用于release的程序则不需要这些调试信息,此时则可以使用#ifdef-#endif来选择开启和屏蔽相关的调试信息。

      其次是大小端问题,FLEX项目除涉及与硬件的交互外,还和软件APP有通信。在通信过程中制定一套可靠的通信协议至关重要,在制定协议的过程中除了考虑数据以什么帧形式传输之外,还应考虑同一数据在不同的开发平台上的存储方式。例如,开发APP的平台中,数据一般按照大端格式存储,即数据的高字节存储在低地址内;而ARM处理器则一般是小端格式,数据的低字节存储在低地址内。当APP和MCU进行数据通信的时候,特别是在传输由几个字节组成的数据(例如浮点数,16位数据以及32位数据)时,就要考虑是否需要移位来保证数据解析正确。在这里,当APP来读取MCU内部FLASH存储的EQ参数时,其读到数据之后应该做移位才能获取到实际的参数值。

     在实际编写代码的过程中,要考虑到代码的执行效率。这里有用了Static inline函数,其中,inline修饰的函数是建议编译器在实际编译过程中在函数调用处直接用函数体代码替代调用(有点类似宏的效果,但与宏不同的是其不用预编译,且会进行参数的安全检查),从而减少了为函数调用分配栈空间,提高了效率。一般而言,inline修饰的函数应该在头文件中定义,这样保证用到它的文件都可以获取到它的定义,替换起来方便。而这里除了inline修饰,还用了static修饰,static修饰的变量只分配一次空间,修饰的函数只在本文件范围内使用。因此,static inline修饰的函数其定义和声明都放在.c文件中,表示该函数只在本文件中使用有效,且使用时编译器会在调用时进行代码替换,提高执行效率。

 

8、关于掉电保存的新思路

    之前基于硬件方面的设计采用ADC采样System_voltage来判断是否掉电,然而这种方式的速度还是比较慢,特别是MCU的主频仅仅采用16MHZ,要实现掉电时绝对地保存参数不太可能。所以之后思考过后,放弃硬件建议的设计,将Adapter插入检测引脚ACOK配置成外部中断,当该IO口上出现下降沿时触发外部中断,在中断中进行Mute功放以及Flash参数保存,众所周知外部中断的优先级高,反应速度非常快,因而采用这种方法很好地抑制了掉电pop声以及实现了参数保存。

    需要注意的是,FLEX是带电池包供电产品,当检测到ACOK出现下降沿(即拔掉Adapter)时,还需要判断此时电池包是否插入,如果有电池包则不需要进行掉电操作,如果没有电池包则Mute功放以及参数保存。

 

9、关于IO口的模式设置

   MCU的GPIO口无论是作为输入还是作为输出,其上拉、下拉和浮空模式都值得注意,在项目开发过程中遇到一个细节问题,有一个GPIO口配置成输入,用于检测硬件信号的高低从而判断是否有无线麦插入。然而在测试过程中发现,实际电表测试该扣得电压为0.2v左右,但软件中判定输入为高。查看手册可知,030RB的GPIO口作为输入时,判定标准是当电压大于0.7VDD(即2.31v)认为是高电平输入,而小于0.3VDD(即0.99v)认为是低电平输入。显然这时的判定是错的,查看电路图发现,该GPIO口与硬件的连接是直接连接,硬件上没有接任何上拉或者下拉电阻,而软件中的GPIO口模式也配置的是NOPULLUP模式,这就导致上电后该GPIO口的状态是不确定的,因此软件对其的判定也就会出现错误。鉴于此,将软件IO口的模式配置成下拉,让该IO口在上电后的默认状态是低电平,则解决了这个问题。在实际开发中,要根据需求(比如需要默认状态是高电平还是低电平)来配置上拉还是下拉,无论是硬件焊接上拉或者下地电阻,还是软件配置内部上下拉,在开发中都应该尽量避免GPIO口上电后出现不确定状态的情况。

     

     

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值