В предыдущей части урока мы познакомились с режимами пониженного энергопотребления контроллера STM32, а в частности режима SLEEP, и познакомились со схемой урока.
Теперь можно приступить и к более практической части.
Проект мы сделаем из проекта урока 140 с именем WWDG01 и дадим ему новое имя — SLEEP. Из папки Src удалим всё, кроме файла main.c, а папку Inc удалим полностью со всем содержимым.
Откроем проект в Cube MX и отключим сначала таймер WWDG
Также отключим и таймер 2
В RCC отключим внешнее тактирование
Активируем RTC
Настроим в нём будильник с тем расчётом, чтобы прерывание от него срабатывало через 10 секунд после его включения
Включим в RTC прерывания, в том числе и от 17 линии
Произведём настройки в Clock Configuration
Сохраним проект, сгенерируем проект для System Workbench, откроем его там, зайдём в настройки, установим уровень оптимизации в 1 и удалим отладочные настройки при их наличии.
Также настроим сборку проекта в 4 потока, как мы делали в уроке 143.
Откроем файл main.c и удалим функцию WWDG_Refresh со всем содержимым.
Процедуру обработки прерываний от таймера HAL_TIM_PeriodElapsedCallback также удалим со всем содержимым.
В процедуре обработки прерываний от кнопки удалим активный код, условие оставим, а в теле положительного условия добавим пустую операцию.
Получится вот так
1 2 3 4 5 6 7 8 |
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin== GPIO_PIN_1) { __NOP(); } else{ __NOP(); } } |
Удалим вот эти глобальные переменные
uint8_t tim2_count = 0, fl_no_tirst = 0;
Пуск таймера 2 тоже уберём из тела функции main()
HAL_TIM_Base_Start_IT(&htim2);
Пока помигаем поочерёдно двумя светодиодами в бесконечном цикле без всяких режимов
1 2 3 4 5 6 7 8 9 |
/* USER CODE BEGIN 3 */ LED1_ON(); HAL_Delay(2000); LED1_OFF(); HAL_Delay(2000); LED2_ON(); HAL_Delay(2000); LED2_OFF(); HAL_Delay(2000); |
Попробуем собрать проект.
Если проект нормально собрался, то попробуем его прошить.
После неудачной прошивки перенастроим отладчик в настройках, как мы делали прежде при работе с таким ST-Link.
Теперь всё будет нормально прошиваться и наши диоды будут поочерёдно мигать
Давайте сейчас измерим ток потребления в нормальном режиме в момент свечения светодиода и в момент, когда светодиод не светится
Мы видим, что светодиод потребляет около 1,1 милиампера.
Теперь попробуем «погрузить» наш контроллер в режим SLEEP.
Для этого сначала, как мы помним, отключить бит прерываний от системного таймера.
Для этого есть специальная функция библиотеки HAL.
После того как первый светодиод отгорит 2 секунды, погаснет, и только после паузы в 2 секунды после его потухания мы начнём переводить контроллер в спящий режим
1 2 3 4 5 |
LED1_ON(); HAL_Delay(2000); LED1_OFF(); HAL_Delay(2000); HAL_SuspendTick(); |
Давайте зайдём внутрь данной функции библиотеки HAL и посмотрим что она делает.
Там мы увидим всего лишь одну строку
CLEAR_BIT(SysTick->CTRL, SysTick_CTRL_TICKINT_Msk);
А это есть ничто иное, как сброс бита, TICKINT.
Затем вызовем следующую функцию
1 2 |
HAL_SuspendTick(); HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI); |
Данная функция и переведёт микроконтроллер в спящий режим SLEEP.
Первый, передаваемый в данную функцию, параметр — это макрос, который различает тип питания. При переходе в режим SLEEP он не имеет смысла, поэтому можно выбирать любой, а второй параметр — это собственно то, какую именно команду — WFI или WFE мы будем применять для перехода в спящий режим.
Зайдём внутрь функции и посмотрим, что она делает.
Первым делом сбрасывается бит SLEEPDEEP
CLEAR_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
а затем идёт собственно команда WFI
if(SLEEPEntry == PWR_SLEEPENTRY_WFI)
{
/* Request Wait For Interrupt */
__WFI();
}
или WFE, которая, как мы видели выше, предваряется ещё командой SEV
else
{
/* Request Wait For Event */
__SEV();
__WFE();
__WFE();
}
Причём обратите внимание, что в случае использования команды WFE, последняя посылается дважды (видимо, для пущей надёжности).
Вернёмся в main() и продолжим.
Теперь мы смело можем считать, что мы в спящий режим ушли.
По определению, так как у нас останавливается только тактирование, то счётчик кода у нас на месте и мы после пробуждения вернёмся сюда же. Поэтому после возврата мы восстанавливаем полноценную работу системного таймера. Для этого мы должны заново установить бит, который мы сбросили — TICKINT.
Для этого также имеется специальная функция библиотеки HAL
1 2 |
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI); HAL_ResumeTick(); |
Давайте теперь попробуем собрать код и прошить контроллер.
Мы увидим, что после того, как светодиод отгорит две секунды и затем пройдёт пауза в 2 секунды, у нас ток потребления снизится
Это значит то, что мы попали в спящий режим.
Через примерно 6 секунд наш контроллер должен будет вернуться в обычный режим RUN и процесс продлится, то есть у нас загорится второй светодиод
После того как второй светодиод отгорит 2 секунды, затем будет пауза в 2 секунды, затем по закону цикла загорится 1 светодиод, пройдёт опять пауза в 2 секунды и контроллер опять уйдёт в режим SLEEP.
Только теперь уже через 6 секунд у нас выход из спящего режима не произойдёт, так как будильник уже сработал ранее и повторно он не сработает, он сам не настроится. С этим мы разберёмся позже.
А теперь попробуем выйти из спящего режима, нажав на кнопку, которая у нас подсоединена к ножке PA1, на которой обрабатываются внешние прерывания.
И, не смотря на то, что обработчик у нас практически пустой, прерывание произойдёт и наш контроллер успешно покинет спящий режим и вернётся в обычный режим RUN и процесс начнётся оттуда, где он завершился, то есть у нас загорится второй светодиод
Но нам также интересно, чтобы процесс выхода из спящего режима происходил по истечению определённого интервала времени и после второго входа в него. Поэтому после выхода из спящего режима и восстановления таймера SysTick мы заново сбросим наши часы, для чего мы сначала добавим пару локальных переменных типов определённых структур, одну для времени, другую для будильника
1 2 3 |
/* USER CODE BEGIN 1 */ RTC_TimeTypeDef sTime; RTC_AlarmTypeDef sAlarm; |
И теперь мы сбросим часы, произведя инициализацию структуры и вызвав необходимую функцию
1 2 3 4 5 6 7 8 |
HAL_ResumeTick(); sTime.Hours = 1; sTime.Minutes = 0; sTime.Seconds = 0; if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK) { Error_Handler(); } |
И после этого мы опять настроим будильник, но чтобы он срабатывал не через 10, а через 14 секунд, так как нам надо ещё дать время на работу со вторым светодиодом
1 2 3 4 5 6 7 8 9 10 |
Error_Handler(); } sAlarm.AlarmTime.Hours = 1; sAlarm.AlarmTime.Minutes = 0; sAlarm.AlarmTime.Seconds = 14; sAlarm.Alarm = RTC_ALARM_A; if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK) { Error_Handler(); } |
Вот теперь, скорее всего, будет всё нормально.
Соберём код, прошьём контроллер и мы увидим, что теперь контроллер будет выходить из спящего режима регулярно. И мы теперь можем либо ждать, когда контроллер из режима SLEEP выйдет сам по истечению времени будильника, либо можем ускорить процесс, нажав досрочно кнопку, подключенную к PA1.
Таким образом, в данном уроке мы узнали, какие режимы пониженного энергопотребления существуют у микроконтроллера STM32, в частности у его линейки STM32F1, также один из этих режимов — режим SLEEP — мы изучили подробно и подтвердили наши знания на практике.
Всем спасибо за внимание!
Предыдущая часть Программирование МК STM32 Следующий урок
Техническая документация:
STM32F10xxx-20xxx-21xxx-L1xxxx-Programming-Manual
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Смотреть ВИДЕОУРОК (нажмите на картинку)
Здравствуйте. Для дальнейшего снижения энергопотребления необходимо перевести неиспользуемые порты в режим аналог. Таким образом можно достичь потребления порядка микроампер в режиме Stop. А также потребления заявленного в даташите для других режимов работы. Подробная статья об этом есть на Хабре (ссылку на внешний источник убрал).