RTT学习

电源管理组件

嵌入式系统低功耗管理的目的在于满足用户对性能需求的前提下,尽可能降低系统功耗以延长设备待机时间。
高性能与有限的电池能量在嵌入式系统中矛盾最为突出,硬件低功耗设计与软件低功耗管理的联合应用成为了解决矛盾的有效手段。

现在的各种 MCU 都或多或少的在低功耗方面提供了管理接口。
比如主控时钟频率的调整,工作电压的改变,总线频率的调整甚至关闭,外围设备工作时钟的关闭等。
有了硬件上的支持,合理的软件设计就成为节能的关键,一般可以把低功耗管理分为三个类别:

  • 处理器电源管理:对CPU频率动态管理,以及系统空闲时对工作模式的调整。
  • 设备电源管理 主要实现方式:关闭个别闲置设备
  • 系统平台电源管理 主要实现方式:针对特定系统平台的非常见设备具体定制。

随着物联网 (IoT) 的兴起,产品对功耗的需求越来越强烈。作为数据采集的传感器节点通常需要在电池供电时长期工作,而作为联网的 SOC 也需要有快速的响应功能和较低的功耗。

在产品开发的起始阶段,首先考虑是尽快完成产品的功能开发。在产品功能逐步完善之后,就需要加入电源管理 (Power Management,以下简称 PM) 功能。为了适应 IoT 的这种需求,RT-Thread 提供了电源管理组件。电源管理组件的理念是尽量透明,使得产品加入低功耗功能更加轻松。

  • 基于模式来管理功耗,空闲时动态调整工作模式,支持多个等级的休眠。
  • 对应用透明,组件在底层自动完成电源管理。
  • 支持运行模式下动态变频,根据模式自动更新设备的频率配置,确保在不同的运行模式都可以正常工作。
  • 支持设备电源管理,根据模式自动管理设备的挂起和恢复,确保在不同的休眠模式下可以正确挂起和恢复。
  • 支持可选的休眠时间补偿,让依赖OS Tick的应用可以透明使用。

工作原理

低功耗的本质是系统空闲时CPU停止工作,中断或事件唤醒后继续工作。

在RTOS中,通常包含一个IDLE任务,该任务的优先级最低且一直保持就绪状态,当高优先级任务未就绪时,OS执行IDLE任务。

当未进行低功耗处理时,CPU在IDLE任务中循环执行空指令。
RTT的电源管理组件在IDLE任务中,通过对CPU、时钟和设备等进行管理,从而有效降低系统的功耗。

在这里插入图片描述
当高优先级任务运行结束或被挂起时,系统将进入 IDLE 任务中。
在IDLE任务执行时,它将判定系统是否可以进入到睡眠状态(以节省功耗)。

如果可以进入休眠,将根据芯片情况关闭部分硬件模块,OS Tick也非常有可能进入暂停状态。

此时电源管理框架会根据系统定时器情况,计算出下一个超时时间点,并设置低功耗定时器,让设备能够在这个时刻点唤醒,并进行后续的工作。

当系统被(低功耗定时器中断或其他唤醒中断源)唤醒后,系统也需要知道睡眠时间长度是多少,并对OS Tick 进行补偿,让系统的OS tick值调整为一个正确的值。

设计架构

外设或应用通过投票机制对所需的功耗模式进行投票,当系统空闲时,根据投票数决策出合适的功耗模式,调用抽象接口,控制芯片进入低功耗状态,从而降低系统功耗。

当未进行任务投票时,系统会以默认模式进入(通常为空闲模式)。
与应用不同,某些外设可能在进入低功耗状态时执行特定操作,退出低功耗时采取措施恢复,此时可以通过注册PM设备来实现。

通过注册PM设备,在进入低功耗状态之前,会触发注册设备的suspend回调,开发者可在回调里执行自己的操作;类似地,从低功耗状态退出时,也会触发resume回调。

在这里插入图片描述

低功耗状态和模式

RT-Thread PM组件将系统划分为两种状态:运行状态(RUN)和休眠状态(SLEEP)。

运行状态控制CPU的频率,适用于变频场景;休眠状态根据SOC特性实现休眠CPU,以降低系统功耗。

两种状态分别使用不同的 API 接口,独立控制。

休眠状态
休眠状态也就是通常意义上的低功耗状态,通过关闭外设、执行 SOC 电源管理接口,降低系统功耗。 休眠状态又分为六个模式,呈现为金字塔的形式。随着模式增加,功耗逐级递减的特点。下面是休眠状态下模式的定义,开发者可根据具体的 SOC 实现相应的模式,但需要遵循功耗逐级降低的特点。
在这里插入图片描述

运行状态通常用于改变 CPU 的运行频率,独立于休眠模式。当前运行状态划分了四个等级:高速、正常、中速、低速,如下:

在这里插入图片描述
在 PM 组件里,上层应用可以通过请求和释放休眠模式主动参与功耗管理。应用可以根据场景请求不同的休眠模式,并在处理完毕后释放,只要有任意一个应用或设备请求高等级的功耗模式,就不会切换到比它更低的模式。因此,休眠模式的请求和释放的操作通常成对出现,可用于对某个阶段进行保护,如外设的 DMA 传输过程。

切换到新的运行模式可能会导致 CPU 频率发生变化,如果外设和 CPU 共用一部分时钟,那外设的时钟就会受到影响;在进入新的休眠模式,大部分时钟源会被停止,如果外设不支持休眠的冻结功能,那么从休眠唤醒的时候,外设的时钟就需要重新配置外设。所以 PM 组件里支持了 PM 模式敏感的 PM 设备。使得设备在切换到新的运行模式或者新的休眠模式都能正常的工作。该功能需要底层驱动实现相关的接口并注册为对模式变化敏感的设备。

在这里插入图片描述
首先应用设置进出休眠状态的回调函数,然后调用rt_pm_request请求休眠模式,触发休眠操作;PM 组件在系统空闲时检查休眠模式计数,根据投票数给出推荐的模式;接着 PM 组件调用 notfiy 通知应用,告知即将进入休眠模式;然后对注册的 PM 设备执行挂起操作,返回 OK 后执行 SOC 实现的的休眠模式,系统进入休眠状态(如果使能时间补偿,休眠之前会先启动低功耗定时器)。此时 CPU 停止工作,等待事件或者中断唤醒。当系统被唤醒后,由于全局中断为关闭状态,系统继续从该处执行,获取睡眠时间补偿系统的心跳,依次唤醒设备,通知应用从休眠模式退出。如此一个周期执行完毕,退出,等待系统下次空闲。

在这里插入图片描述
调用该函数会将对应的模式计数加1,并锁住该模式。此时如果请求更低级别的功耗模式,将无法进入,只有释放(解锁)先前请求的模式后,系统才能进入更低的模式;向更高的功耗模式请求则不受此影响。该函数需要和 rt_pm_release 配合使用,用于对某一阶段或过程进行保护。

I/O设备模型

绝大部分的嵌入式系统都包含一些I/O(输入/输出)设备,例如仪器上的数据显示屏、工业设备上的串口通信,数据采集设备上用于保存数据的Flash或SD卡,以及网络设备的以太网接口等,都是嵌入式系统中容易找到的I/O设备例子。

I/O设备模型框架
RTT提供了一套简单的I/O设备模型框架,它位于硬件和应用程序之间,共分成三层,从上到下分别是I/O设备管理层,设备驱动框架层,设备驱动层。

应用程序通过I/O设备管理接口获得正确的设备驱动,然后通过这个设备驱动与底层I/O硬件设备进行数据交互。

I/O设备管理层实现了对设备驱动程序的封装。应用通常图中的“I/O设备管理层”提供的标准接口访问底层设备,设备驱动程序的升级、更替不会对上层应用产生影响。这种方式使得设备的硬件操作相关的代码能够独立于应用程序而存在,双方只需关注各自的功能实现,从而降低了代码的耦合性、复杂性,提高了系统的可靠性。

设备驱动框架层是对同类硬件设备驱动的抽象,将不同厂家的同类硬件设备驱动中相同的部分抽取出来,将不同部分留出接口,由驱动程序实现。

设备驱动层是一组驱使硬件设备工作的程序,实现访问硬件设备的功能。
它负责创建和注册I/O设备,对于操作逻辑简单设备,可以不经过设备驱动框架层,直接将设备注册到I/O设备管理器中。

  • 设备驱动根据设备模型定义,创建出具备硬件访问能力的设备实例,将该设备通过rt_device_register()接口注册到I/O设备管理器中。
  • 应用程序通过rt_device_find()接口查找到设备,然后使用I/O设备管理接口来访问硬件。

对于另一些设备,如看门狗等,则会将创建的设备实例先注册到对应的设备驱动框架中,再由设备驱动框架层向I/O设备管理层进行注册。

  • 看门狗设备驱动层根据看门狗设备模型定义,创建出具备硬件访问能力的看门狗设备实例,并将该看门狗通过rt_hw_watchdog_register()接口注册到看门狗设备驱动框架中。
  • 看门狗设备驱动通过rt_device_register()接口将看门狗设备注册到I/O设备管理器中。
  • 应用程序通过I/O设备管理接口来访问看门狗设备硬件。

I/O设备模型

RTT的设备模型是建立在内核对象模型基础之上的,设备被认为是一类对象,被纳入对象管理器的范畴。
每个设备对象都是由基对象派生而来,每个具体设备都可以继承其父类对象的属性,并派生出私有属性。

struct rt_device{
	struct rt_object parent; //内核对象基类
	enum rt_device_class_type type;//设备类型
	rt_uint16_t flag;//设备参数
	rt_uint16_t               open_flag;     /* 设备打开标志 */
    rt_uint8_t                ref_count;     /* 设备被引用次数 */
    rt_uint8_t                device_id;     /* 设备 ID,0 - 255 */

	/* 数据收发回调函数 */
    rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size);
    rt_err_t (*tx_complete)(rt_device_t dev, void *buffer);

    const struct rt_device_ops *ops;    /* 设备操作方法 */

    /* 设备的私有数据 */
    void *user_data;
};
typedef struct rt_device *rt_device_t;

I/O设备类型

RT_Device_Class_Char             /* 字符设备       */
RT_Device_Class_Block            /* 块设备         */
RT_Device_Class_NetIf            /* 网络接口设备    */
RT_Device_Class_MTD              /* 内存设备       */
RT_Device_Class_RTC              /* RTC 设备        */
RT_Device_Class_Sound            /* 声音设备        */
RT_Device_Class_Graphic          /* 图形设备        */
RT_Device_Class_I2CBUS           /* I2C 总线设备     */
RT_Device_Class_USBDevice        /* USB device 设备  */
RT_Device_Class_USBHost          /* USB host 设备   */
RT_Device_Class_SPIBUS           /* SPI 总线设备     */
RT_Device_Class_SPIDevice        /* SPI 设备        */
RT_Device_Class_SDIO             /* SDIO 设备       */
RT_Device_Class_Miscellaneous    /* 杂类设备        */

其中字符设备、块设备是常用的设备类型,它们的分类依据是设备数据与系统之间的传输处理方式。
字符模式设备允许非结构的数据传输,通常数据传输采用串口的形式,每次一个字节。
字符设备通常是一些简单设备,如串口、按键。

块设备每次传输一个数据块,例如每次传输512个字节数据。
这个数据块是硬件强制性的,数据块可能使用某类数据接口或某些强制性的传输协议,否则就可能发生错误。

驱动层负责创建设备实例,并注册到I/O设备管理器中,可以通过静态声明的方式创建设备实例,也可以通过下面的接口进行动态创建:

rt_device_t rt_device_create(int type, int attach_size);

调用该接口时,系统会从动态内存堆中分配一个设备控制块,大小为struct rt_device和attach_size的和,设备的类型由参数 type 设定。设备被创建后,需要实现它访问硬件的操作方法。

  • init:初始化设备。设备初始化完成后,设备控制块的 flag 会被置成已激活状态 (RT_DEVICE_FLAG_ACTIVATED)。如果设备控制块中的 flag 标志已经设置成激活状态,那么再运行初始化接口时会立刻返回,而不会重新进行初始化。
  • open:打开设备。有些设备并不是系统一启动就已经打开开始运行,或者设备需要进行数据收发,但如果上层应用还未准备好,设备也不应默认已经使能并开始接收数据。所以建议在写底层驱动程序时,在调用 open 接口时才使能设备。
  • close:关闭设备。在打开设备时,设备控制块会维护一个打开计数,在打开设备时进行 + 1 操作,在关闭设备时进行 - 1 操作,当计数器变为 0 时,才会进行真正的关闭操作。
  • read:从设备读取数据。参数 pos 是读取数据的偏移量,但是有些设备并不一定需要指定偏移量,例如串口设备,设备驱动应忽略这个参数。而对于块设备来说,pos 以及 size 都是以块设备的数据块大小为单位的。例如块设备的数据块大小是 512,而参数中 pos = 10, size = 2,那么驱动应该返回设备中第 10 个块 (从第 0 个块做为起始),共计 2 个块的数据。这个接口返回的类型是 rt_size_t,即读到的字节数或块数目。正常情况下应该会返回参数中 size 的数值,如果返回零请设置对应的 errno 值。
  • write:向设备写入数据。参数 pos 是写入数据的偏移量。与读操作类似,对于块设备来说,pos 以及 size 都是以块设备的数据块大小为单位的。这个接口返回的类型是 rt_size_t,即真实写入数据的字节数或块数目。正常情况下应该会返回参数中 size 的数值,如果返回零请设置对应的 errno 值。
  • control:根据 cmd 命令控制设备。命令往往是由底层各类设备驱动自定义实现。例如参数 RT_DEVICE_CTRL_BLK_GETGEOME,意思是获取块设备的大小信息。
  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

饼干饼干圆又圆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值