文章目录[隐藏]
1 低功耗管理
参考资料:
- da1469x_software_platform_reference
- 《STM32F103 FreeRTOS开发手册V1.1》主要看FreeRTOS的介绍
1.1 进入休眠与退出休眠的总概览
FreeRTOS 就是通过在处理器处理空闲任务的时候将处理器设置为低功耗模式来降低能耗。一般会在空闲任务的钩子函数中执行低功耗相关处理,比如设置处理器进入低功耗模式、关闭其他外设时钟、降低系统主频等等。
这里就需要使用FreeRTOS的Tickless 模式,即当处理器进入空闲任务周期以后就关闭系统节拍中断(滴答定时器中断),只有当其他中断发生或者其他任务需要处理的时候处理器才会被从低功耗模式中唤醒。
1.2 FreeRTOS低功耗管理分析
这部分低功耗管理主要由FreeRTOS系统处理
1.2.1 Tickless 具体实现
总体请参考《STM32F103 FreeRTOS开发手册V1.1》中Tickless 相关章节
重点关注如下几个:
-
宏
#define configUSE_TICKLESS_IDLE 2
启用Tickless功能需要定义该宏为1,这里定义为 2,是因为portSUPPRESS_TICKS_AND_SLEEP()由用户自己实现(这里是由DA1469X提供),如下:#define portSUPPRESS_TICKS_AND_SLEEP( x ) prvSystemSleep( x )
函数
prvSystemSleep( x )
在 sdk\free_rtos\portable\GCC\DA1469x\port.c 中。 -
宏
configPRE_SLEEP_PROCESSING ()
和configPOST_SLEEP_PROCESSING()
在真正的低功耗设计中不仅仅是将处理器设置到低功耗模式就行了,还需要做一些其他的处理,比如:
● 将处理器降低到合适的频率,因为频率越低功耗越小,甚至可以在进入低功耗模式以后
关闭系统时钟。
● 修改时钟源,晶振的功耗肯定比处理器内部的时钟源高,进入低功耗模式以后可以切换
到内部时钟源,比如 STM32 的内部 RC 振荡器。
● 关闭其他外设时钟,比如 IO 口的时钟。
● 关闭板子上其他功能模块电源,这个需要在产品硬件设计的时候就要处理好,比如可以通过 MOS 管来控制某个模块电源的开关,在处理器进入低功耗模式之前关闭这些模块的电源。
FreeRTOS 为我们提供了一个宏来完成这些操作,它就是configPRE_SLEEP_PROCESSING()
,这个宏的具体实现内容需要用户去编写。如果在进入低功耗模式之前我们降低了处理器频率、关闭了某些外设时钟等的话,那在退出低功耗模式以后就 需 要 恢 复 处 理 器 频 率 、 重 新 打 开 外 设 时 钟 等 , 这 个 操 作 在 宏configPOST_SLEEP_PROCESSING()
中完成,同样的这个宏的具体内容也需要用户去编写。
使用举例:
在DA1469x的SDK代码中如下:#define configPRE_SLEEP_PROCESSING( x ) #define configPOST_SLEEP_PROCESSING()
configPRE_SLEEP_PROCESSING 具体应用位置:
prvSystemSleep -> pm_sleep_enter -> apply_wfi -> configPRE_SLEEP_PROCESSING( sleep_period );
configPOST_SLEEP_PROCESSING 具体应用位置:
prvSystemSleep -> pm_sleep_enter -> system_wake_up -> sys_init_wake_up -> configPOST_SLEEP_PROCESSING();而这里并没有定义具体函数,可由用户自己定义,比如可以通过 MOS 管来控制某个模块电源的开关
1.2.2 空闲任务具体实现
当 FreeRTOS 的调度器启动以后就会自动的创建一个空闲任务,即当调用函数 vTaskStartScheduler()启动任务调度器的时候此函数就会自动创建空闲任务,而DA1469x的低功耗就是在空闲任务中实现的。
#define portSUPPRESS_TICKS_AND_SLEEP( x ) prvSystemSleep( x )
static portTASK_FUNCTION( prvIdleTask, pvParameters )
{
...
for( ;; )
{
...
#if ( configUSE_TICKLESS_IDLE != 0 )
{
if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
{
traceLOW_POWER_IDLE_BEGIN(); /*进入 tickless idle 前需要做的处理,没做任何处理*/
portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );/*该函数即 prvSystemSleep( x ) */
traceLOW_POWER_IDLE_END(); /*退出 tickless idle 后需要做的处理,没做任何处理*/
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TICKLESS_IDLE */
}
}
1.3 DA1469x低功耗管理分析
这部分是DA1469x低功耗的具体实现
1.3.1 prvSystemSleep函数分析
prvSystemSleep 函数即 portSUPPRESS_TICKS_AND_SLEEP ,FreeRTOS系统进入空闲函数后,会执行prvSystemSleep函数,使系统进入低功耗模式,其执行步骤如下:
prvSystemSleep -> pm_sleep_enter -> apply_wfi -> goto_deepsleep();
进入休眠和退出休眠步骤
prvSystemSleep 还是属于FreeRTOS系统内,而到了pm_sleep_enter 函数才是真正处理DA1469x进入低功耗。
要点了解:
- 具体低功耗模式由
current_sleep_mode
参数决定,由sleep_mode_t pm_sleep_mode_set(sleep_mode_t mode)
函数更改 - dg_configUSE_WDOG如果定义为1,则需要计算看门狗的时间,在看门狗复位前如要唤醒;看门狗复位计时快到时则不会进入低功耗。因此看门狗的复位时间需要注意。
- dg_configMIN_SLEEP_TIME 定义了最短休眠时间,默认为3ms
- 需要使能Tickless 模式
- 唤醒模式设置void pm_set_wakeup_mode(bool wait_for_xtalm),参数为true或false,When false, the system will start executing code immediately after wake-up.When true, upon wake-up the system will wait for the system clock to be switched to the appropriate clock source before continuing with code execution.
在pm_sleep_enter 函数中做的工作:
- 判断是否符合进入休眠的条件,不符合则退出;
- 计算可休眠的时间,如果时间太短不会进入休眠;
- 根据相应的休眠模式做准备,如关闭时钟、外设等;
2 看门狗系统
2.1 看门狗使用注意事项
- sys_watchdog_init 函数为初始化看门狗平台,使用看门狗都要调用该函数,且需要尽早调用;
- 每个任务都有相应的看门狗,各自独立,
- 当任务需要长时间阻塞时,需要暫停该任务的看门狗,并在阻塞停止后恢复看门狗
- The hardware watchdog is essentially a countdown timer, which is powered by the always on domain (PD_AON) and it will be automatically enabled as soon as the system powers up. It is decremented by 1 every ~10 ms (assuming default source clock the RC32K) and it is set to max value at reset.
- dg_configWDOG_RESET_VALUE ,Its default value is the maximum 0xFF, which corresponds to approximately 2.6 seconds (assuming default source clock the RC32K).
- The maximum number of tasks that can be monitored is defined by the configuration macro dg_configWDOG_MAX_TASKS_CNT. Its default value is 6 (the absolute maximum is 32).
4~6点总结:看门狗计数定时器是永久上电(相对休眠或工作模式而言),默认时钟源是RC32K,约10ms计数值减1,当复位值为默认值0xFF时,看门狗复位周期是2.6S(255*10=2550ms);默认可以注册6个任务看门狗,最大可设置为32个(即最大任务数) - This results to a maximum watchdog time-out of 84 seconds. If the RCX is used as a source clock, the time-out time is even longer, depending on the RCX frequency.看门狗复位之间可达84秒甚至更长,取决于时钟源RCX
- 看门狗时钟源选择低速时钟
#define dg_configUSE_LP_CLK ( LP_CLK_32768 )
2.2 看门狗相关函数分析
-
int8_t sys_watchdog_register(bool notify_trigger)
Register current task in sys_watchdog module,Returned identifier shall be used in all other calls to sys_watchdog from current task.int8_t wdog_id; wdog_id = sys_watchdog_register(false);
参数notify_trigger为 true 或 false,当 dg_configWDOG_NOTIFY_TRIGGER_TMO 被定义时才起作用,dg_configWDOG_NOTIFY_TRIGGER_TMO的功能是,使用定时器周期的通知看门狗
-
void sys_watchdog_set_latency(int8_t id, uint8_t latency)
给看门狗复位延长一段时间,比如计数值为10,使用该函数增加延时5,那么计数完5之后,再从10开始倒计时,通常用于一段需要长时间计算的代码中,使用该函数后只延时一次,后面看门狗计时不会增加这部分值
3 蓝牙BLE系统
3.1 蓝牙BLE设备参数 ble_dev_params_t
-
Appearance
Appearance即表明这是个什么设备,如鼠标?键盘?等等,用2个字节可以表示很多种类的,某个代码对应某个具体的Appearance表示,这个需要参考SIG的Assigned Numbers,上面有列表的。
可选值如下(在ble_gap.h中)/** GAP device external appearance */ typedef enum { BLE_GAP_APPEARANCE_UNKNOWN = 0, ... BLE_GAP_APPEARANCE_HID_KEYBOARD = 961, BLE_GAP_APPEARANCE_HID_MOUSE = 962, ... } gap_appearance_t;
-
adv_mode
adv_mode 即 广播的发现模式。可选的参数如下(在ble_gap.h中)/** GAP discoverability modes */ typedef enum { GAP_DISC_MODE_NON_DISCOVERABLE, ///< Non-Discoverable mode GAP_DISC_MODE_GEN_DISCOVERABLE, ///< General-Discoverable mode GAP_DISC_MODE_LIM_DISCOVERABLE, ///< Limited-Discoverable mode GAP_DISC_MODE_BROADCASTER, ///< Broadcaster mode } gap_disc_mode_t;
参考文章广播发现模式分析,在DA1469x的SDK中并没有类似文章中的操作,想要关闭广播需要直接调用API关闭,参考下文。
3.2 蓝牙BLE广播
广播类型可选择如下参数
/** GAP connectivity modes */
typedef enum {
GAP_CONN_MODE_NON_CONN, ///< Non-connectable mode
GAP_CONN_MODE_UNDIRECTED, ///< Undirected mode
GAP_CONN_MODE_DIRECTED, ///< Directed mode
GAP_CONN_MODE_DIRECTED_LDC, ///< Directed Low Duty Cycle mode
} gap_conn_mode_t;
说明参考文章广播
广播类型为 GAP_CONN_MODE_UNDIRECTED 或 GAP_CONN_MODE_NON_CONN,调用ble_gap_adv_start 函数开启广播之后,就一直在广播,除非调用 ble_gap_adv_stop 函数停止广播
static void start_advertising()
{
printf("Start advertising...\r\n");
ble_gap_adv_intv_set(BLE_ADV_INTERVAL_MIN, BLE_ADV_INTERVAL_MAX);
ble_gap_adv_start(GAP_CONN_MODE_UNDIRECTED);
}
ble_error_t ble_gap_adv_start ( gap_conn_mode_t adv_type )
Start advertising.
This API call is used to start an advertising air operation. If adv_type is set to be GAP_CONN_MODE_NON_CONN
or GAP_CONN_MODE_UNDIRECTED
, the air operation will go on until it is stopped using ble_gap_adv_stop()
. If adv_type is set to be GAP_CONN_MODE_DIRECTED or GAP_CONN_MODE_DIRECTED_LDC (low duty cycle advertising), the air operation will automatically stop after 1.28s. In both cases, upon advertising completion, a BLE_EVT_GAP_ADV_COMPLETED
event will be sent to the application.
4 General Purpose ADC
4.1 电池监控功能(Battery monitoring function)
参考工程 ble_social_distance_tag
实现的功能,周期性对芯片VBAT引脚采样,获取该引脚电压,并根据实际情况计算电池电量。VBAT引脚即芯片电池供电引脚,采样该引脚电压即可计算电池电量。
4.1.1 代码简介
- 在初始化过程中添加电池服务(Battery Service)
bas = bas_init(NULL, NULL);/* Add Battery Service */
- 配置定时器周期性采样VBAT引脚
/* Create timer for battery monitoring */
bas_tim = OS_TIMER_CREATE("bas",
OS_MS_2_TICKS(10*1000), /*采样周期,单位MS*/
pdTRUE,
(void *) OS_GET_CURRENT_TASK(),
bas_tim_cb);
OS_TIMER_START(bas_tim, OS_TIMER_FOREVER);
- VBAT引脚采样过程
/*
* The values depend on the battery type. uint mVolts
*/
#define MAX_BATTERY_LEVEL 4200 /* 设定电池最高电量 */
#define MIN_BATTERY_LEVEL 2800 /* 设定电池最低电量 */
/* Convert voltage level to percentage */
static uint8_t bat_level(uint16_t voltage)
{
if (voltage >= MAX_BATTERY_LEVEL) {
return 100;
} else if (voltage <= MIN_BATTERY_LEVEL) {
return 0;
}
return (uint8_t) ((int) (voltage - MIN_BATTERY_LEVEL) * 100 /
(MAX_BATTERY_LEVEL - MIN_BATTERY_LEVEL));
}
/* Read the battery level by invoking the GPADC */
uint8_t read_battery_level(void)
{
uint16_t bat_voltage;
uint16_t value;
ad_gpadc_handle_t handle;
handle = ad_gpadc_open(&BATTERY_LEVEL);
ad_gpadc_read(handle, &value);
bat_voltage = ad_gpadc_conv_to_batt_mvolt(BATTERY_LEVEL.drv, value);
ad_gpadc_close(handle, false);
printf("BAT voltage: %dmV\r\n", bat_voltage);
return bat_level(bat_voltage); /*将电压转换为百分比,返回的是百分比*/
}
4.1.2 配置ADC采样引脚为VBAT
采样过程中,调用函数ad_gpadc_open(&BATTERY_LEVEL)
可打开VBAT引脚,配置如下,
const ad_gpadc_driver_conf_t battery_level_driver = {
.clock = HW_GPADC_CLOCK_INTERNAL,
.input_mode = HW_GPADC_INPUT_MODE_SINGLE_ENDED,
.input = HW_GPADC_INPUT_SE_VBAT, /*此处确定输入引脚为VBAT*/
.temp_sensor = HW_GPADC_NO_TEMP_SENSOR,
.sample_time = 15,
.chopping = true,
.oversampling = HW_GPADC_OVERSAMPLING_4_SAMPLES,
.input_attenuator = HW_GPADC_INPUT_VOLTAGE_UP_TO_1V2,
};
const ad_gpadc_controller_conf_t BATTERY_LEVEL = {
HW_GPADC_1, /* 该寄存器地址为0x50030900UL*/
NULL,
&battery_level_driver /*该结构体如上所示*/
};
追踪枚举变量HW_GPADC_INPUT_SE_VBAT
,如下所示
typedef enum {
HW_GPADC_INPUT_SE_P1_09 = 0, /**< GPIO P1_09 */
...
HW_GPADC_INPUT_SE_V30_2 = 6, /**< V30 supply rail */
HW_GPADC_INPUT_SE_VBAT = 8, /**< Battery voltage, scaled from 5V to 1.2V */
...
HW_GPADC_INPUT_DIFF_P0_08_P0_09 = 1, /**< GPIO P0_08 vs P0_09 - all other values */
} HW_GPADC_INPUT;
打开《DA1469x 数据表》查看ADC相关寄存器,P535 ,Table 630: GP_ADC_CTRL_REG (0x50030900),如下所示
4.1.3 代码移植
- 全工程查找
SDT_BATTERY_MONITORING_ENABLE
相关代码 - 一步一步移植,每次编译看有没有错,
- 注意有些头文件或变量需要自己添加,
5 Non Volatile Memory Storage (NVMS) 的应用
参考:
4.2.4. The NVMS Adapter
5.6. Non Volatile Memory Storage (NVMS)
- 选择分区表 ,如开发板选择MX25U3235F, 32Mbit(4MiB with sectors of 4KiB)存储,打开WiRa_10.440.8.6\sdk\bsp\config\partition_table.h
- 配置分区表,如开发板选择MX25U3235F, 32Mbit(4MiB with sectors of 4KiB)存储,打开WiRa_10.440.8.6\sdk\bsp\config\4M\partition_table.h
#define NVMS_PRODUCT_HEADER_PART_START 0x000000
#define NVMS_PRODUCT_HEADER_PART_SIZE 0x002000 /* 8k */
#define NVMS_FIRMWARE_PART_START 0x002000 /* Alignment to 512KB is dictated by the default FLASH_REGION_SIZE. */
#define NVMS_FIRMWARE_PART_SIZE 0x07E000 /* 504k */
/* +----------------512KB---------------------+ */
#define NVMS_GENERIC_PART_START 0x0E0000
#define NVMS_GENERIC_PART_SIZE 0x020000 /* 128k */
#define NVMS_PLATFORM_PARAMS_PART_START 0x100000
#define NVMS_PLATFORM_PARAMS_PART_SIZE 0x0FF000 /* 1020K */
#define NVMS_PARAM_PART_START 0x1FF000
#define NVMS_PARAM_PART_SIZE 0x001000 /* 4K *//* Recommended location, 4KB before the end of the 1st flash section. */
/* +------------------2MB---------------------+ */
#define NVMS_LOG_PART_START 0x200000
#define NVMS_LOG_PART_SIZE 0x100000 /* 1024K */
#define NVMS_BIN_PART_START 0x300000
#define NVMS_BIN_PART_SIZE 0x0FF000 /* 1020K */
#define NVMS_PARTITION_TABLE_START 0x3FF000
#define NVMS_PARTITION_TABLE_SIZE 0x001000 /* 4K *//* Recommended location, 4KB before the end of the flash. */
PARTITION2( NVMS_PRODUCT_HEADER_PART , 0 )
PARTITION2( NVMS_FIRMWARE_PART , 0 )
PARTITION2( NVMS_GENERIC_PART , PARTITION_FLAG_VES )
PARTITION2( NVMS_PLATFORM_PARAMS_PART , PARTITION_FLAG_READ_ONLY )
PARTITION2( NVMS_PARAM_PART , 0 )
PARTITION2( NVMS_LOG_PART , 0 )
PARTITION2( NVMS_BIN_PART , 0 )
PARTITION2( NVMS_PARTITION_TABLE , PARTITION_FLAG_READ_ONLY )
可以看到,4MiB的存储器地址分配如下
/*
*总存储地址为 4194304(0x400000)/1024 = 4096K = 4MiB
*0x000000 - 0x002000 (size 0x002000 8K) NVMS_PRODUCT_HEADER_PART
*0x002000 - 0x080000 (size 0x07E000 504K) NVMS_FIRMWARE_PART
*0x080000 - 0x0E0000 (size 0x060000 384K) NOT USE
*0x0E0000 - 0x100000 (size 0x020000 128K) NVMS_GENERIC_PART
*0x100000 - 0x1FF000 (size 0x0FF000 1020K) NVMS_PLATFORM_PARAMS_PART
*0x1FF000 - 0x200000 (size 0x001000 4K) NVMS_PARAM_PART
*0x200000 - 0x300000 (size 0x100000 1024K) NVMS_LOG_PART
*0x300000 - 0x3FF000 (size 0x0FF000 1020K) NVMS_BIN_PART
*0x3FF000 - 0x400000 (size 0x001000 4K) NVMS_PARTITION_TABLE
*
*8+504+384+128+1020+4+1024+1020+4 = 4096
*/
- 打印分区表信息,以验证配置是否正确
bool res = false;
nvms_t nvms = 0;
size_t partition_count = 0,len = 0;
partition_entry_t entry;
nvms = ad_nvms_open(NVMS_PRODUCT_HEADER_PART);
len = ad_nvms_get_size(nvms); /* 查看分区 NVMS_PRODUCT_HEADER_PART 的大小 */
printf("NVMS_PRODUCT_HEADER_PART size 0x%x\r\n", len);
nvms = ad_nvms_open(NVMS_FIRMWARE_PART);
len = ad_nvms_get_size(nvms); /* 查看分区 NVMS_FIRMWARE_PART 的大小 */
printf("NVMS_FIRMWARE_PART size 0x%x\r\n", len);
nvms = ad_nvms_open(NVMS_GENERIC_PART);
len = ad_nvms_get_size(nvms); /* 查看分区 NVMS_GENERIC_PART 的大小 */
printf("NVMS_GENERIC_PART size 0x%x\r\n", len);
nvms = ad_nvms_open(NVMS_PLATFORM_PARAMS_PART);
len = ad_nvms_get_size(nvms); /* 查看分区 NVMS_PLATFORM_PARAMS_PART 的大小 */
printf("NVMS_PLATFORM_PARAMS_PART size 0x%x\r\n", len);
nvms = ad_nvms_open(NVMS_PARAM_PART);
len = ad_nvms_get_size(nvms); /* 查看分区 NVMS_PARAM_PART 的大小 */
printf("NVMS_PARAM_PART size 0x%x\r\n", len);
nvms = ad_nvms_open(NVMS_LOG_PART);
len = ad_nvms_get_size(nvms); /* 查看分区 NVMS_LOG_PART 的大小 */
printf("NVMS_LOG_PART size 0x%x\r\n", len);
nvms = ad_nvms_open(NVMS_BIN_PART);
len = ad_nvms_get_size(nvms); /* 查看分区 NVMS_BIN_PART 的大小 */
printf("NVMS_BIN_PART size 0x%x\r\n", len);
nvms = ad_nvms_open(NVMS_PARTITION_TABLE);
len = ad_nvms_get_size(nvms); /* 查看分区 NVMS_PARTITION_TABLE 的大小 */
printf("NVMS_PARTITION_TABLE size 0x%x\r\n", len);
partition_count = ad_nvms_get_partition_count(); /* 查看分区总数 */
printf("partition count %d,\r\n",partition_count);
for(char i = partition_count ;i >= 0;i--){ /* 打印所有分区表信息 */
res = ad_nvms_get_partition_info(i,&entry);
if(res == true){
printf("count %d,flags %d,magic 0x%x,sector_count 0x%x,start_sector 0x%x,Partition ID %d,valid 0x%x\r\n\r\n"
, i,entry.flags,entry.magic,entry.sector_count,entry.start_sector,entry.type,entry.valid);
}else{
printf("nvms get partition info fail \r\n");
}
}
串口软件数据输出如下
根据串口数据,和配置信息一致。数据详解如下表
/**
* \brief NVMS Partition IDs
*/
typedef enum {
NVMS_FIRMWARE_PART = 1,
NVMS_PARAM_PART = 2,
NVMS_BIN_PART = 3,
NVMS_LOG_PART = 4,
NVMS_GENERIC_PART = 5,
NVMS_PLATFORM_PARAMS_PART = 15,
NVMS_PARTITION_TABLE = 16,
NVMS_FW_EXEC_PART = 17,
NVMS_FW_UPDATE_PART = 18,
NVMS_PRODUCT_HEADER_PART = 19,
NVMS_IMAGE_HEADER_PART = 20,
} nvms_partition_id_t;
/**
* \brief Partition entry.
*/
typedef struct partition_entry_t {
uint8_t magic; /**< Partition magic number 0xEA */
uint8_t type; /**< Partition ID */
uint8_t valid; /**< Valid marker 0xFF */
uint8_t flags; /**< */
uint16_t start_sector; /**< Partition start sector */
uint16_t sector_count; /**< Number of sectors in partition */
uint8_t reserved2[8]; /**< Reserved for future use */
} partition_entry_t;
6 编译后的程序组成
参考文章:text, data and bss: 代码和数据的所占空间详解
程序编译后可以看到如下信息
- text所包含的内容是代码和常量,最终存放在FLASH里而;
- data包含静态初始化的数据,所以有初值的全局变量和static变量在data区;
- bss是英文Block Started by Symbol的简称,通常是指用来存放程序中未初始化的全局变量的一块内存区域,在程序载入时由内核清0。
- dec(decimal的缩写,即十进制数)是text,data和bss的算术和;(dec = text + data + bss)
- hex是dec的十六进制数。
版权声明:本文为CSDN博主「while(1)」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42900996/article/details/122194040
1 低功耗管理
参考资料:
- da1469x_software_platform_reference
- 《STM32F103 FreeRTOS开发手册V1.1》主要看FreeRTOS的介绍
1.1 进入休眠与退出休眠的总概览
FreeRTOS 就是通过在处理器处理空闲任务的时候将处理器设置为低功耗模式来降低能耗。一般会在空闲任务的钩子函数中执行低功耗相关处理,比如设置处理器进入低功耗模式、关闭其他外设时钟、降低系统主频等等。
这里就需要使用FreeRTOS的Tickless 模式,即当处理器进入空闲任务周期以后就关闭系统节拍中断(滴答定时器中断),只有当其他中断发生或者其他任务需要处理的时候处理器才会被从低功耗模式中唤醒。
1.2 FreeRTOS低功耗管理分析
这部分低功耗管理主要由FreeRTOS系统处理
1.2.1 Tickless 具体实现
总体请参考《STM32F103 FreeRTOS开发手册V1.1》中Tickless 相关章节
重点关注如下几个:
-
宏
#define configUSE_TICKLESS_IDLE 2
启用Tickless功能需要定义该宏为1,这里定义为 2,是因为portSUPPRESS_TICKS_AND_SLEEP()由用户自己实现(这里是由DA1469X提供),如下:#define portSUPPRESS_TICKS_AND_SLEEP( x ) prvSystemSleep( x )
函数
prvSystemSleep( x )
在 sdk\free_rtos\portable\GCC\DA1469x\port.c 中。 -
宏
configPRE_SLEEP_PROCESSING ()
和configPOST_SLEEP_PROCESSING()
在真正的低功耗设计中不仅仅是将处理器设置到低功耗模式就行了,还需要做一些其他的处理,比如:
● 将处理器降低到合适的频率,因为频率越低功耗越小,甚至可以在进入低功耗模式以后
关闭系统时钟。
● 修改时钟源,晶振的功耗肯定比处理器内部的时钟源高,进入低功耗模式以后可以切换
到内部时钟源,比如 STM32 的内部 RC 振荡器。
● 关闭其他外设时钟,比如 IO 口的时钟。
● 关闭板子上其他功能模块电源,这个需要在产品硬件设计的时候就要处理好,比如可以通过 MOS 管来控制某个模块电源的开关,在处理器进入低功耗模式之前关闭这些模块的电源。
FreeRTOS 为我们提供了一个宏来完成这些操作,它就是configPRE_SLEEP_PROCESSING()
,这个宏的具体实现内容需要用户去编写。如果在进入低功耗模式之前我们降低了处理器频率、关闭了某些外设时钟等的话,那在退出低功耗模式以后就 需 要 恢 复 处 理 器 频 率 、 重 新 打 开 外 设 时 钟 等 , 这 个 操 作 在 宏configPOST_SLEEP_PROCESSING()
中完成,同样的这个宏的具体内容也需要用户去编写。
使用举例:
在DA1469x的SDK代码中如下:#define configPRE_SLEEP_PROCESSING( x ) #define configPOST_SLEEP_PROCESSING()
configPRE_SLEEP_PROCESSING 具体应用位置:
prvSystemSleep -> pm_sleep_enter -> apply_wfi -> configPRE_SLEEP_PROCESSING( sleep_period );
configPOST_SLEEP_PROCESSING 具体应用位置:
prvSystemSleep -> pm_sleep_enter -> system_wake_up -> sys_init_wake_up -> configPOST_SLEEP_PROCESSING();而这里并没有定义具体函数,可由用户自己定义,比如可以通过 MOS 管来控制某个模块电源的开关
1.2.2 空闲任务具体实现
当 FreeRTOS 的调度器启动以后就会自动的创建一个空闲任务,即当调用函数 vTaskStartScheduler()启动任务调度器的时候此函数就会自动创建空闲任务,而DA1469x的低功耗就是在空闲任务中实现的。
#define portSUPPRESS_TICKS_AND_SLEEP( x ) prvSystemSleep( x )
static portTASK_FUNCTION( prvIdleTask, pvParameters )
{
...
for( ;; )
{
...
#if ( configUSE_TICKLESS_IDLE != 0 )
{
if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
{
traceLOW_POWER_IDLE_BEGIN(); /*进入 tickless idle 前需要做的处理,没做任何处理*/
portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );/*该函数即 prvSystemSleep( x ) */
traceLOW_POWER_IDLE_END(); /*退出 tickless idle 后需要做的处理,没做任何处理*/
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TICKLESS_IDLE */
}
}
1.3 DA1469x低功耗管理分析
这部分是DA1469x低功耗的具体实现
1.3.1 prvSystemSleep函数分析
prvSystemSleep 函数即 portSUPPRESS_TICKS_AND_SLEEP ,FreeRTOS系统进入空闲函数后,会执行prvSystemSleep函数,使系统进入低功耗模式,其执行步骤如下:
prvSystemSleep -> pm_sleep_enter -> apply_wfi -> goto_deepsleep();
进入休眠和退出休眠步骤
prvSystemSleep 还是属于FreeRTOS系统内,而到了pm_sleep_enter 函数才是真正处理DA1469x进入低功耗。
要点了解:
- 具体低功耗模式由
current_sleep_mode
参数决定,由sleep_mode_t pm_sleep_mode_set(sleep_mode_t mode)
函数更改 - dg_configUSE_WDOG如果定义为1,则需要计算看门狗的时间,在看门狗复位前如要唤醒;看门狗复位计时快到时则不会进入低功耗。因此看门狗的复位时间需要注意。
- dg_configMIN_SLEEP_TIME 定义了最短休眠时间,默认为3ms
- 需要使能Tickless 模式
- 唤醒模式设置void pm_set_wakeup_mode(bool wait_for_xtalm),参数为true或false,When false, the system will start executing code immediately after wake-up.When true, upon wake-up the system will wait for the system clock to be switched to the appropriate clock source before continuing with code execution.
在pm_sleep_enter 函数中做的工作:
- 判断是否符合进入休眠的条件,不符合则退出;
- 计算可休眠的时间,如果时间太短不会进入休眠;
- 根据相应的休眠模式做准备,如关闭时钟、外设等;
2 看门狗系统
2.1 看门狗使用注意事项
- sys_watchdog_init 函数为初始化看门狗平台,使用看门狗都要调用该函数,且需要尽早调用;
- 每个任务都有相应的看门狗,各自独立,
- 当任务需要长时间阻塞时,需要暫停该任务的看门狗,并在阻塞停止后恢复看门狗
- The hardware watchdog is essentially a countdown timer, which is powered by the always on domain (PD_AON) and it will be automatically enabled as soon as the system powers up. It is decremented by 1 every ~10 ms (assuming default source clock the RC32K) and it is set to max value at reset.
- dg_configWDOG_RESET_VALUE ,Its default value is the maximum 0xFF, which corresponds to approximately 2.6 seconds (assuming default source clock the RC32K).
- The maximum number of tasks that can be monitored is defined by the configuration macro dg_configWDOG_MAX_TASKS_CNT. Its default value is 6 (the absolute maximum is 32).
4~6点总结:看门狗计数定时器是永久上电(相对休眠或工作模式而言),默认时钟源是RC32K,约10ms计数值减1,当复位值为默认值0xFF时,看门狗复位周期是2.6S(255*10=2550ms);默认可以注册6个任务看门狗,最大可设置为32个(即最大任务数) - This results to a maximum watchdog time-out of 84 seconds. If the RCX is used as a source clock, the time-out time is even longer, depending on the RCX frequency.看门狗复位之间可达84秒甚至更长,取决于时钟源RCX
- 看门狗时钟源选择低速时钟
#define dg_configUSE_LP_CLK ( LP_CLK_32768 )
2.2 看门狗相关函数分析
-
int8_t sys_watchdog_register(bool notify_trigger)
Register current task in sys_watchdog module,Returned identifier shall be used in all other calls to sys_watchdog from current task.int8_t wdog_id; wdog_id = sys_watchdog_register(false);
参数notify_trigger为 true 或 false,当 dg_configWDOG_NOTIFY_TRIGGER_TMO 被定义时才起作用,dg_configWDOG_NOTIFY_TRIGGER_TMO的功能是,使用定时器周期的通知看门狗
-
void sys_watchdog_set_latency(int8_t id, uint8_t latency)
给看门狗复位延长一段时间,比如计数值为10,使用该函数增加延时5,那么计数完5之后,再从10开始倒计时,通常用于一段需要长时间计算的代码中,使用该函数后只延时一次,后面看门狗计时不会增加这部分值
3 蓝牙BLE系统
3.1 蓝牙BLE设备参数 ble_dev_params_t
-
Appearance
Appearance即表明这是个什么设备,如鼠标?键盘?等等,用2个字节可以表示很多种类的,某个代码对应某个具体的Appearance表示,这个需要参考SIG的Assigned Numbers,上面有列表的。
可选值如下(在ble_gap.h中)/** GAP device external appearance */ typedef enum { BLE_GAP_APPEARANCE_UNKNOWN = 0, ... BLE_GAP_APPEARANCE_HID_KEYBOARD = 961, BLE_GAP_APPEARANCE_HID_MOUSE = 962, ... } gap_appearance_t;
-
adv_mode
adv_mode 即 广播的发现模式。可选的参数如下(在ble_gap.h中)/** GAP discoverability modes */ typedef enum { GAP_DISC_MODE_NON_DISCOVERABLE, ///< Non-Discoverable mode GAP_DISC_MODE_GEN_DISCOVERABLE, ///< General-Discoverable mode GAP_DISC_MODE_LIM_DISCOVERABLE, ///< Limited-Discoverable mode GAP_DISC_MODE_BROADCASTER, ///< Broadcaster mode } gap_disc_mode_t;
参考文章广播发现模式分析,在DA1469x的SDK中并没有类似文章中的操作,想要关闭广播需要直接调用API关闭,参考下文。
3.2 蓝牙BLE广播
广播类型可选择如下参数
/** GAP connectivity modes */
typedef enum {
GAP_CONN_MODE_NON_CONN, ///< Non-connectable mode
GAP_CONN_MODE_UNDIRECTED, ///< Undirected mode
GAP_CONN_MODE_DIRECTED, ///< Directed mode
GAP_CONN_MODE_DIRECTED_LDC, ///< Directed Low Duty Cycle mode
} gap_conn_mode_t;
说明参考文章广播
广播类型为 GAP_CONN_MODE_UNDIRECTED 或 GAP_CONN_MODE_NON_CONN,调用ble_gap_adv_start 函数开启广播之后,就一直在广播,除非调用 ble_gap_adv_stop 函数停止广播
static void start_advertising()
{
printf("Start advertising...\r\n");
ble_gap_adv_intv_set(BLE_ADV_INTERVAL_MIN, BLE_ADV_INTERVAL_MAX);
ble_gap_adv_start(GAP_CONN_MODE_UNDIRECTED);
}
ble_error_t ble_gap_adv_start ( gap_conn_mode_t adv_type )
Start advertising.
This API call is used to start an advertising air operation. If adv_type is set to be GAP_CONN_MODE_NON_CONN
or GAP_CONN_MODE_UNDIRECTED
, the air operation will go on until it is stopped using ble_gap_adv_stop()
. If adv_type is set to be GAP_CONN_MODE_DIRECTED or GAP_CONN_MODE_DIRECTED_LDC (low duty cycle advertising), the air operation will automatically stop after 1.28s. In both cases, upon advertising completion, a BLE_EVT_GAP_ADV_COMPLETED
event will be sent to the application.
4 General Purpose ADC
4.1 电池监控功能(Battery monitoring function)
参考工程 ble_social_distance_tag
实现的功能,周期性对芯片VBAT引脚采样,获取该引脚电压,并根据实际情况计算电池电量。VBAT引脚即芯片电池供电引脚,采样该引脚电压即可计算电池电量。
4.1.1 代码简介
- 在初始化过程中添加电池服务(Battery Service)
bas = bas_init(NULL, NULL);/* Add Battery Service */
- 配置定时器周期性采样VBAT引脚
/* Create timer for battery monitoring */
bas_tim = OS_TIMER_CREATE("bas",
OS_MS_2_TICKS(10*1000), /*采样周期,单位MS*/
pdTRUE,
(void *) OS_GET_CURRENT_TASK(),
bas_tim_cb);
OS_TIMER_START(bas_tim, OS_TIMER_FOREVER);
- VBAT引脚采样过程
/*
* The values depend on the battery type. uint mVolts
*/
#define MAX_BATTERY_LEVEL 4200 /* 设定电池最高电量 */
#define MIN_BATTERY_LEVEL 2800 /* 设定电池最低电量 */
/* Convert voltage level to percentage */
static uint8_t bat_level(uint16_t voltage)
{
if (voltage >= MAX_BATTERY_LEVEL) {
return 100;
} else if (voltage <= MIN_BATTERY_LEVEL) {
return 0;
}
return (uint8_t) ((int) (voltage - MIN_BATTERY_LEVEL) * 100 /
(MAX_BATTERY_LEVEL - MIN_BATTERY_LEVEL));
}
/* Read the battery level by invoking the GPADC */
uint8_t read_battery_level(void)
{
uint16_t bat_voltage;
uint16_t value;
ad_gpadc_handle_t handle;
handle = ad_gpadc_open(&BATTERY_LEVEL);
ad_gpadc_read(handle, &value);
bat_voltage = ad_gpadc_conv_to_batt_mvolt(BATTERY_LEVEL.drv, value);
ad_gpadc_close(handle, false);
printf("BAT voltage: %dmV\r\n", bat_voltage);
return bat_level(bat_voltage); /*将电压转换为百分比,返回的是百分比*/
}
4.1.2 配置ADC采样引脚为VBAT
采样过程中,调用函数ad_gpadc_open(&BATTERY_LEVEL)
可打开VBAT引脚,配置如下,
const ad_gpadc_driver_conf_t battery_level_driver = {
.clock = HW_GPADC_CLOCK_INTERNAL,
.input_mode = HW_GPADC_INPUT_MODE_SINGLE_ENDED,
.input = HW_GPADC_INPUT_SE_VBAT, /*此处确定输入引脚为VBAT*/
.temp_sensor = HW_GPADC_NO_TEMP_SENSOR,
.sample_time = 15,
.chopping = true,
.oversampling = HW_GPADC_OVERSAMPLING_4_SAMPLES,
.input_attenuator = HW_GPADC_INPUT_VOLTAGE_UP_TO_1V2,
};
const ad_gpadc_controller_conf_t BATTERY_LEVEL = {
HW_GPADC_1, /* 该寄存器地址为0x50030900UL*/
NULL,
&battery_level_driver /*该结构体如上所示*/
};
追踪枚举变量HW_GPADC_INPUT_SE_VBAT
,如下所示
typedef enum {
HW_GPADC_INPUT_SE_P1_09 = 0, /**< GPIO P1_09 */
...
HW_GPADC_INPUT_SE_V30_2 = 6, /**< V30 supply rail */
HW_GPADC_INPUT_SE_VBAT = 8, /**< Battery voltage, scaled from 5V to 1.2V */
...
HW_GPADC_INPUT_DIFF_P0_08_P0_09 = 1, /**< GPIO P0_08 vs P0_09 - all other values */
} HW_GPADC_INPUT;
打开《DA1469x 数据表》查看ADC相关寄存器,P535 ,Table 630: GP_ADC_CTRL_REG (0x50030900),如下所示
4.1.3 代码移植
- 全工程查找
SDT_BATTERY_MONITORING_ENABLE
相关代码 - 一步一步移植,每次编译看有没有错,
- 注意有些头文件或变量需要自己添加,
5 Non Volatile Memory Storage (NVMS) 的应用
参考:
4.2.4. The NVMS Adapter
5.6. Non Volatile Memory Storage (NVMS)
- 选择分区表 ,如开发板选择MX25U3235F, 32Mbit(4MiB with sectors of 4KiB)存储,打开WiRa_10.440.8.6\sdk\bsp\config\partition_table.h
- 配置分区表,如开发板选择MX25U3235F, 32Mbit(4MiB with sectors of 4KiB)存储,打开WiRa_10.440.8.6\sdk\bsp\config\4M\partition_table.h
#define NVMS_PRODUCT_HEADER_PART_START 0x000000
#define NVMS_PRODUCT_HEADER_PART_SIZE 0x002000 /* 8k */
#define NVMS_FIRMWARE_PART_START 0x002000 /* Alignment to 512KB is dictated by the default FLASH_REGION_SIZE. */
#define NVMS_FIRMWARE_PART_SIZE 0x07E000 /* 504k */
/* +----------------512KB---------------------+ */
#define NVMS_GENERIC_PART_START 0x0E0000
#define NVMS_GENERIC_PART_SIZE 0x020000 /* 128k */
#define NVMS_PLATFORM_PARAMS_PART_START 0x100000
#define NVMS_PLATFORM_PARAMS_PART_SIZE 0x0FF000 /* 1020K */
#define NVMS_PARAM_PART_START 0x1FF000
#define NVMS_PARAM_PART_SIZE 0x001000 /* 4K *//* Recommended location, 4KB before the end of the 1st flash section. */
/* +------------------2MB---------------------+ */
#define NVMS_LOG_PART_START 0x200000
#define NVMS_LOG_PART_SIZE 0x100000 /* 1024K */
#define NVMS_BIN_PART_START 0x300000
#define NVMS_BIN_PART_SIZE 0x0FF000 /* 1020K */
#define NVMS_PARTITION_TABLE_START 0x3FF000
#define NVMS_PARTITION_TABLE_SIZE 0x001000 /* 4K *//* Recommended location, 4KB before the end of the flash. */
PARTITION2( NVMS_PRODUCT_HEADER_PART , 0 )
PARTITION2( NVMS_FIRMWARE_PART , 0 )
PARTITION2( NVMS_GENERIC_PART , PARTITION_FLAG_VES )
PARTITION2( NVMS_PLATFORM_PARAMS_PART , PARTITION_FLAG_READ_ONLY )
PARTITION2( NVMS_PARAM_PART , 0 )
PARTITION2( NVMS_LOG_PART , 0 )
PARTITION2( NVMS_BIN_PART , 0 )
PARTITION2( NVMS_PARTITION_TABLE , PARTITION_FLAG_READ_ONLY )
可以看到,4MiB的存储器地址分配如下
/*
*总存储地址为 4194304(0x400000)/1024 = 4096K = 4MiB
*0x000000 - 0x002000 (size 0x002000 8K) NVMS_PRODUCT_HEADER_PART
*0x002000 - 0x080000 (size 0x07E000 504K) NVMS_FIRMWARE_PART
*0x080000 - 0x0E0000 (size 0x060000 384K) NOT USE
*0x0E0000 - 0x100000 (size 0x020000 128K) NVMS_GENERIC_PART
*0x100000 - 0x1FF000 (size 0x0FF000 1020K) NVMS_PLATFORM_PARAMS_PART
*0x1FF000 - 0x200000 (size 0x001000 4K) NVMS_PARAM_PART
*0x200000 - 0x300000 (size 0x100000 1024K) NVMS_LOG_PART
*0x300000 - 0x3FF000 (size 0x0FF000 1020K) NVMS_BIN_PART
*0x3FF000 - 0x400000 (size 0x001000 4K) NVMS_PARTITION_TABLE
*
*8+504+384+128+1020+4+1024+1020+4 = 4096
*/
- 打印分区表信息,以验证配置是否正确
bool res = false;
nvms_t nvms = 0;
size_t partition_count = 0,len = 0;
partition_entry_t entry;
nvms = ad_nvms_open(NVMS_PRODUCT_HEADER_PART);
len = ad_nvms_get_size(nvms); /* 查看分区 NVMS_PRODUCT_HEADER_PART 的大小 */
printf("NVMS_PRODUCT_HEADER_PART size 0x%x\r\n", len);
nvms = ad_nvms_open(NVMS_FIRMWARE_PART);
len = ad_nvms_get_size(nvms); /* 查看分区 NVMS_FIRMWARE_PART 的大小 */
printf("NVMS_FIRMWARE_PART size 0x%x\r\n", len);
nvms = ad_nvms_open(NVMS_GENERIC_PART);
len = ad_nvms_get_size(nvms); /* 查看分区 NVMS_GENERIC_PART 的大小 */
printf("NVMS_GENERIC_PART size 0x%x\r\n", len);
nvms = ad_nvms_open(NVMS_PLATFORM_PARAMS_PART);
len = ad_nvms_get_size(nvms); /* 查看分区 NVMS_PLATFORM_PARAMS_PART 的大小 */
printf("NVMS_PLATFORM_PARAMS_PART size 0x%x\r\n", len);
nvms = ad_nvms_open(NVMS_PARAM_PART);
len = ad_nvms_get_size(nvms); /* 查看分区 NVMS_PARAM_PART 的大小 */
printf("NVMS_PARAM_PART size 0x%x\r\n", len);
nvms = ad_nvms_open(NVMS_LOG_PART);
len = ad_nvms_get_size(nvms); /* 查看分区 NVMS_LOG_PART 的大小 */
printf("NVMS_LOG_PART size 0x%x\r\n", len);
nvms = ad_nvms_open(NVMS_BIN_PART);
len = ad_nvms_get_size(nvms); /* 查看分区 NVMS_BIN_PART 的大小 */
printf("NVMS_BIN_PART size 0x%x\r\n", len);
nvms = ad_nvms_open(NVMS_PARTITION_TABLE);
len = ad_nvms_get_size(nvms); /* 查看分区 NVMS_PARTITION_TABLE 的大小 */
printf("NVMS_PARTITION_TABLE size 0x%x\r\n", len);
partition_count = ad_nvms_get_partition_count(); /* 查看分区总数 */
printf("partition count %d,\r\n",partition_count);
for(char i = partition_count ;i >= 0;i--){ /* 打印所有分区表信息 */
res = ad_nvms_get_partition_info(i,&entry);
if(res == true){
printf("count %d,flags %d,magic 0x%x,sector_count 0x%x,start_sector 0x%x,Partition ID %d,valid 0x%x\r\n\r\n"
, i,entry.flags,entry.magic,entry.sector_count,entry.start_sector,entry.type,entry.valid);
}else{
printf("nvms get partition info fail \r\n");
}
}
串口软件数据输出如下
根据串口数据,和配置信息一致。数据详解如下表
/**
* \brief NVMS Partition IDs
*/
typedef enum {
NVMS_FIRMWARE_PART = 1,
NVMS_PARAM_PART = 2,
NVMS_BIN_PART = 3,
NVMS_LOG_PART = 4,
NVMS_GENERIC_PART = 5,
NVMS_PLATFORM_PARAMS_PART = 15,
NVMS_PARTITION_TABLE = 16,
NVMS_FW_EXEC_PART = 17,
NVMS_FW_UPDATE_PART = 18,
NVMS_PRODUCT_HEADER_PART = 19,
NVMS_IMAGE_HEADER_PART = 20,
} nvms_partition_id_t;
/**
* \brief Partition entry.
*/
typedef struct partition_entry_t {
uint8_t magic; /**< Partition magic number 0xEA */
uint8_t type; /**< Partition ID */
uint8_t valid; /**< Valid marker 0xFF */
uint8_t flags; /**< */
uint16_t start_sector; /**< Partition start sector */
uint16_t sector_count; /**< Number of sectors in partition */
uint8_t reserved2[8]; /**< Reserved for future use */
} partition_entry_t;
6 编译后的程序组成
参考文章:text, data and bss: 代码和数据的所占空间详解
程序编译后可以看到如下信息
- text所包含的内容是代码和常量,最终存放在FLASH里而;
- data包含静态初始化的数据,所以有初值的全局变量和static变量在data区;
- bss是英文Block Started by Symbol的简称,通常是指用来存放程序中未初始化的全局变量的一块内存区域,在程序载入时由内核清0。
- dec(decimal的缩写,即十进制数)是text,data和bss的算术和;(dec = text + data + bss)
- hex是dec的十六进制数。
版权声明:本文为CSDN博主「while(1)」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42900996/article/details/122194040
暂无评论