david-lau 发表于 2024-8-8 17:44

APM32F003应用实例: 在keil MDK中定义非初始化(noini)变量的实际应用

本帖最后由 david-lau 于 2024-8-8 17:53 编辑

在单片机编程中,变量未初始化是一个常见的问题,有时会导致程序出现一些难以预料的行为。不过,在某些情况下,这种"未初始化"的特性也可能会带来一些意想不到的好处。本文将探讨APM32F003微控制器中,未初始化变量得的一些实际应用场景。

APM32F003 RAM的特殊初始化机制

APM32F003是一款基于ARM Cortex-M0+内核的32位微控制器。对于RAM存储区有一个特殊的初始化机制,如果芯片保持不断电,APM32F003的RAM在复位或休眠唤醒时并不一定要是全部初始化为0,还可以保留之前的值。这意味着未初始化的变量会保留上次程序运行时的值。

这种行为可能会在某些情况下引起困扰,但如果善加利用,也可以派上用场。让我们看看几个实际应用例子:

1. 状态机实现

在实现状态机时,未初始化的状态变量可以用来表示系统的初始状态。程序上电后,状态变量的值就是未定义的,恰好可以用来标识系统刚刚启动的初始状态。这样就无需在代码中专门定义一个"初始状态"变量。
typedef enum {
    STATE_INIT,
    STATE_RUN,
    STATE_STOP,
    STATE_POWERON
} system_state_t;

system_state_t current_state __attribute__((section("NoInit"), zero_init));

void main() {
    // 上电后,current_state自动处于初始状态STATE_INIT
    while (1) {
      switch (current_state) {
            case STATE_INIT:
                // 初始化逻辑
                current_state = STATE_RUN;
                break;
            case STATE_RUN:
                // 运行逻辑
                break;
            case STATE_STOP:
                // 停止逻辑
                break;
            case STATE_POWERON:
                // 上电逻辑
                break;
            default:
                // 未知状态处理
                break;
      }
    }
}


2. 断电恢复

在需要断电恢复的场景中,未初始化的变量可以用来判断系统是否刚刚从断电状态恢复。例如,你可以将一个标志位变量设置为未初始化状态,在恢复逻辑中检查该变量的值,来决定是否需要执行特殊的恢复操作。uint16_t power_restored_flag __attribute__((section("NoInit"), zero_init));

void main() {
    // 检查power_restored_flag是否处于未初始化状态
    if (power_restored_flag != 0x55AA) {
      // 执行断电恢复逻辑
      power_restored_flag = 0x55AA; // 设置标志位为特定值
    } else {
      // 正常运行逻辑
    }
}
3. 低功耗模式

在低功耗模式下,MCU会进入睡眠状态,只有在特定事件发生时才会被唤醒。未初始化的变量可以用来标记唤醒原因,避免在唤醒后需要重新初始化状态。

uint8_t wakeup_source __attribute__((section("NoInit"), zero_init));

void main() {
    while (1) {
      // 正常运行逻辑
      enter_low_power_mode();

      // 检查唤醒源
      if (wakeup_source == 0xFF) { // 未初始化时为0xFF
            // 由定时器唤醒
      } else if (wakeup_source == 0x55) {
            // 由外部中断唤醒
      } else {
            // 未知唤醒源
      }

      // 根据唤醒源执行相应的恢复操作
      switch (wakeup_source) {
            case 0xFF:
                // 定时器唤醒恢复逻辑
                break;
            case 0x55:
                // 外部中断唤醒恢复逻辑
                break;
            default:
                // 未知唤醒源处理逻辑
                break;
      }

      // 重置唤醒源标志
      wakeup_source = 0;
    }
}
4. 关闭只能复位才能关闭的外设

某些外设,比如独立看门狗(IWDT,WWDT)在正常运行过程中无法被关闭,只能通过复位才能关闭。但在某些情况下,我们需要在程序运行时关闭这些外设,比如在系统需要进入低功耗或出现故障时。
利用未初始化变量的特性,我们可以通过检查变量的值来判断系统是否刚刚复位,从而决定是否需要关闭IWDT等外设。uint16_t iwdt_disabled_flag __attribute__((section("NoInit"), zero_init));

void main() {
    // 检查iwdt_disabled_flag是否处于未初始化状态
    if (iwdt_disabled_flag != 0x55aa) {
      // 启动独立看门狗(IWDT)
      IWDT_Init(freqLIRC);
    }

    // 正常运行逻辑
    while (1) {
      // 其他工作...

      if (sleep_flag) {
            // 进入睡眠模式
            iwdt_disabled_flag = 0x55aa; // 设置标志位,下次不启动IWDT
            enter_low_power_mode();
      }
    }
}

以上几个例子展示了如何利用APM32F003 RAM未初始化的特性来简化代码逻辑,提高程序的健壮性。当然,在实际应用中还需要结合具体需求来权衡使用。合理利用这一特性,可以让你的APM32F003项目获得意想不到的收益。

如下是在Keil中的具体用法:1,修改SCT文件:
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************

LR_IROM1 0x00000000 0x00008000{    ; load region size_region
ER_IROM1 0x00000000 0x00008000{; load address = execution address
   *.o (RESET, +First)
   *(InRoot$Sections)
   .ANY (+RO)
   .ANY (+XO)
}
<font color="#ff0000">RW_IRAM1 0x20000000 UNINIT 0x00000100{ ;no init section
   *(NoInit)
}</font>
RW_IRAM2 0x20000100 0x00000F00{                ;all other RW data
   .ANY(+RW +ZI)
}
}
2,在代码中声明变量:

unsigned char NI_charVar __attribute__( ( section( "NoInit"),zero_init) ) ;

caigang13 发表于 2024-8-9 07:55

所以在定一个变量时最好同时赋值一个初值。

dalong-168 发表于 2024-8-9 11:41

学习了
页: [1]
查看完整版本: APM32F003应用实例: 在keil MDK中定义非初始化(noini)变量的实际应用