저번장에 이어 이번에는 전원관리중 Backup Domain에 대해 알아보자.
STM32는 Standby모드로 진입하거나 메인 전원이 차단되어 Vbat핀을 통해 배터리 전원을 받는 경우 대부분의 SRAM과 레지스터는 그 내용을 잃는다.
하지만 Backup Domain으로 분류된 RTC(Real Time Clock)와 RTC관련 레지스터, Backup SRAM의 경우 그 값을 그대로 보존한다.
RTC는 아직 다루지 않았으므로 다음 기회에 설명하기로 하고 이번장에서는 Standby모드 진입과 Wake up시 RTC레지스터중 RTC Backup레지스터와 Backup SRAM에 데이터를 저장하고 읽는 작업을 해보자.
|
1. 준비물
Standby모드를 빠져나오기 위한 Pull down된 푸시버튼이 있어야 하므로 앞장 "Power - Low Power Mode"에서 구현해 놓은걸 그대로 쓰면 된다.
2. Backup Domain
Battery Backup Domain은 Vdd의 전원이 차단될 때 Vbat에 배터리가 연결되어 있다면 STM32가 자동으로 Vbat의 전원으로 전환하면서 상태를 유지시켜주는 영역을 의미한다. STM32F429/439시리즈의 경우 아래의 Backup Domain을 가진다.
- RTC(Real time clock)
- 4Kb의 Backup SRAM
- RTC레지스터 영역에 있는 20개의 Backup 레지스터
RTC의 경우 외부 32KHz 클럭으로 현재 시간을 저장하고 있으며 Backup SRAM과 Backup 레지스터는 사용자 임의로 저장이 가능하다.
그리고 STM32F의 Backup SRAM은 0x4002 4000 ~ 0x4002 4FFFF까지 4Kb의 크기를 가진다.
3. Backup SRAM 엑세스 코딩
이전장에서 설명한 Low Power Mode중 Standby모드 역시 이 상황과 비슷한 상황이므로 Standby모드 상태 진입시 우리가 필요한 값을 Backup Domain에 저장하고 Wake up시 이 값을 읽어오도록 해보자.
회로와 소스는 이전장에서 사용한 걸 그대로 사용하므로 구현되어 있지 않다면 이전장을 참고하라.
우선 Backup SRAM 엑세스부터 살펴보자.
Backup SRAM에 엑세스하기 위해서는 우선 RCC_AHB1ENR 레지스터의 BKPSRAMEN 비트를 1로 설정해서 Clock을 Enable시켜야 한다. 그리고 엑세스가 끝나면 다시 Disable시킨다.
이 비트는 아래 두개의 매크로로 구현되어 있다.
__HAL_RCC_BKPSRAM_CLK_ENABLE();
__HAL_RCC_BKPSRAM_CLK_DISABLE();
그리고 SRAM에서 읽을때는 상관없지만 쓸 때는 아래 함수로 Enable/Disable Access처리를 해줘야 한다.
HAL_PWR_EnableBkUpAccess();
HAL_PWR_DisableBkUpAccess();
소스를 열고 먼저 아래 코드를 작성하자.
#define BACKUP_MAGIC 0xC0C0
typedef struct _tagFBackupData
{
uint16_t uiMagic;
int nCount;
char szString[32];
} FBackupData;
Backup SRAM에는 최초에 Gabage값이 들어있으므로 우리가 기록한 값인지 Gabage값인지 판별할 수 있는 방법이 필요하다. 따라서 우리가 SRAM에 기록할 때 처음 2바이트에 매직코드를 입력해서 우리가 기록한 값이라는걸 알아낼 수 있도록 해야한다.
따라서 SRAM에 기록할 때 위에 정의된 BACKUP_MAGIC값을 처음 2바이트에 기록할 것이다. (이 값의 크기나 값은 각자 임의로 조정할것)
다음으로 유지되어야 할 값들을 구조체로 관리하기 위해 구조체 선언을 했다.
이 구조체에서는 먼저 위의 매직코드값과 실제 유지해야할 값 nCount, szString 두개를 선언했다.
다음으로 읽기/쓰기 함수를 각각 만든다.
void writeBackupSRAM(FBackupData* pData)
{
// Backup SRAM에 값쓰기
HAL_PWR_EnableBkUpAccess();
__HAL_RCC_BKPSRAM_CLK_ENABLE();
FBackupData* pSRAM = (FBackupData*)BKPSRAM_BASE;
memcpy(pSRAM, pData, sizeof(FBackupData));
__HAL_RCC_BKPSRAM_CLK_DISABLE();
HAL_PWR_DisableBkUpAccess();
}
void readBackupSRAM(FBackupData* pData)
{
// Backup SRAM값 읽기
FBackupData* pSRAM = (FBackupData*)BKPSRAM_BASE;
__HAL_RCC_BKPSRAM_CLK_ENABLE();
if(pSRAM->uiMagic == BACKUP_MAGIC)
{ // 제일앞의 2바이트가 매직코드라면 SRAM을 읽어서 pData에 저장
memcpy(pData, pSRAM, sizeof(FBackupData));
}
else
{ // 매직코드가 아니라면 디폴트값으로 설정
pData->uiMagic = BACKUP_MAGIC;
pData->nCount = 0;
memset(pData->szString, 0, sizeof(pData->szString));
}
__HAL_RCC_BKPSRAM_CLK_DISABLE();
}
이 소스에서 사용된 BKPSRAM_BASE값은 stm32f429xx.h파일에 0x4002 4000으로 정의되어 있다.(MCU종류에 따라 값은 다를 수 있음)
이번엔 main함수 안에서 실제 값을 읽고 쓰는 루틴을 작성한다.
앞장의 소스와 바뀐점은,
main함수의 while문 이전에 다음과 같이 readBackupSRAM()을 호출하고 int nCount정의 부분은 FBackupData구조체에 포함되므로 뺀다. 그리고 소스 마지막 nCount출력부분에도 nCount대신 retainData.nCount로 변경한다.
또, Standby모드 진입전 writeBackupSRAM()을 호출해 retainData구조체 데이터를 Backup SRAM에 저장한다.
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_ETH_Init();
MX_USART3_UART_Init();
MX_USB_OTG_FS_PCD_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
uint32_t uiStart = HAL_GetTick();
uint32_t uiEnablePrint = 0;
FBackupData retainData;
//////////////////////////////////////////////////////////////////////////
readBackupSRAM(&retainData); // Backup SRAM의 값을 읽어 retainData를 설정
//////////////////////////////////////////////////////////////////////////
// LED초기화
HAL_GPIO_WritePin(LD1_GPIO_Port, LD1_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_RESET);
printf("Started\n");
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
// 아무 입력없이 3초가 지났는지 체크
if(HAL_GetTick() - uiStart >= 3000)
{
if(gCurrentMode != UNSET_MODE)
printf("Entering Low Power Mode : ");
if(gCurrentMode == SLEEP_MODE)
{
printf("Sleep Mode.\n");
HAL_Delay(200); // 디버깅 메시지를 표시하기 위한 Delay
HAL_SuspendTick();
__HAL_RCC_PWR_CLK_ENABLE();
HAL_PWR_EnterSLEEPMode(0, PWR_SLEEPENTRY_WFE); // WFE 즉 Event신호로 깨어나도록 함
HAL_ResumeTick();
SystemClock_Config();
printf("Waking up Sleep Mode.\n");
}
else if(gCurrentMode == STOP_MODE)
{
printf("Stop Mode.\n");
HAL_Delay(200); // 디버깅 메시지를 표시하기 위한 Delay
HAL_SuspendTick();
__HAL_RCC_PWR_CLK_ENABLE();
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFE); // WFE 즉 Event신호로 깨어나도록 함
HAL_ResumeTick();
SystemClock_Config();
HAL_Delay(100);
printf("Waking up Stop Mode.\n");
}
else if(gCurrentMode == STANDBY_MODE)
{
printf("Standby Mode.\n");
HAL_Delay(200); // 디버깅 메시지를 표시하기 위한 Delay
//////////////////////////////////////////////////////////////////////////
// Backup SRAM에 값을 저장
memcpy(retainData.szString, "test", 4); // 테스트용으로 임의의 문자열 대입
writeBackupSRAM(&retainData);
//////////////////////////////////////////////////////////////////////////
// 모든 Wake-up 소스 Disable: PWR_WAKEUP_PIN1
HAL_PWR_DisableWakeUpPin(PWR_WAKEUP_PIN1);
// 모든 Wake-up 플래그 Clear
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
//PA0에 연결된 PWR_WAKEUP_PIN1 다시 활성화
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
//__HAL_RCC_PWR_CLK_ENABLE();
HAL_PWR_EnterSTANDBYMode();
}
// Wake up시 uiStart다시 설정
uiStart = HAL_GetTick();
}
// 버튼 확인
if(HAL_GPIO_ReadPin(BTN_SLEEP_GPIO_Port, BTN_SLEEP_Pin) == GPIO_PIN_SET)
{
gCurrentMode = SLEEP_MODE;
uiStart = HAL_GetTick();
}
if(HAL_GPIO_ReadPin(BTN_STOP_GPIO_Port, BTN_STOP_Pin) == GPIO_PIN_SET)
{
gCurrentMode = STOP_MODE;
uiStart = HAL_GetTick();
}
if(HAL_GPIO_ReadPin(BTN_STANDBY_GPIO_Port, BTN_STANDBY_Pin) == GPIO_PIN_SET)
{
gCurrentMode = STANDBY_MODE;
uiStart = HAL_GetTick();
}
// LED표시
setLED(gCurrentMode);
// 디버그용 카운터 출력
if(uiEnablePrint++ > 100000)
{
printf(">> Count : %d\n", retainData.nCount++);
uiEnablePrint = 0;
}
}
실행하고 Standby모드로 진입한 뒤 Wake up시켜보면 앞장의 소스와는 달리 Count값이 그대로 보존되어 출력되는걸 볼 수 있다.
그리고 한번 실행하고 다시 readBackupSRAM() 안에서 Break point를 건 뒤 Backup SRAM메모리를 보면 소스에서 설정한 대로 값이 들어가 있다.
제일 처음에 매직코드인 C0C0가 2바이트가 나오고 Gabage값 EF, 7C가 2바이트 나온다. 이 Gabage 2바이트는 CPU/MCU, 그리고 컴파일러에 따라 조금씩 다를 수 있는데 구조체 내에서 uint16_t형으로 선언하더라도 4바이트 단위로 강제 조정되는 경우가 있다.
어쨌든 5바이트부터 0x0000 0081값으로 저장된게 nCount의 값이고 그 이후 szString문자열 값이 나온다.
4. RTC Backup 레지스터 엑세스 구현
RTC의 백업 레지스터는 읽을 때는 일반 Peripheral 레지스터 읽을 때와 동일하다. 단지 쓸때는 위에서 설명한 HAL_PWR_EnableBkUpAccess()와 HAL_PWR_DisableBkUpAccess()를 호출해 줘야 한다.
아래 소스는 20개의 RTC Backup레지스터 중 0번 레지스터만 엑세스하도록 코딩됐다. BKP0R은 RTC에서 0x50번째 Offset에 위치하므로 그 다음은 4바이트씩 증가시켜 엑세스 하면 된다. 모든 Backup레지스터에 엑세스하는 함수는 각자 만들기 바란다.
void writeRTCBackupReg0(uint32_t uiValue)
{
HAL_PWR_EnableBkUpAccess();
RTC->BKP0R = uiValue;
HAL_PWR_DisableBkUpAccess();
}
uint32_t readRTCBackupReg0()
{
return RTC->BKP0R;
}
이후 Backup SRAM과 마찬가지로 main함수에서 다음과 같이 읽기/쓰기를 하면 된다.
uint32_t uiRTCBackup0 = readRTCBackupReg0(); // 읽기
writeRTCBackupReg0(GPIOC->MODER); // 쓰기
이 레지스터에 쓸때는 별다른 제약사항이 없으므로 위와 같이 다른 레지스터 값 전체를 써도 되고 자신이 만든 임의의 4바이트 값을 넣어도 된다.
6. 마무리
이번장에서는 Backup Domain으로 분류된 RTC Backup레지스터, Backup SRAM을 엑세스하는 방법을 알아보았다. RTC자체에 대해서는 사용자가 딱히 건드릴게 없으므로 이후 다른 글에서 설명하도록 하겠다.
'임베디드 > STM32' 카테고리의 다른 글
18. [STM32] Clock (0) | 2024.11.14 |
---|---|
17. [STM32] Power - PVD (0) | 2024.11.08 |
15. [STM32] Power - Low Power Mode (0) | 2024.11.04 |
14. [STM32] Interrupt 구현하기 (0) | 2024.10.10 |
13. [STM32] Interrupt-Register (0) | 2024.09.26 |