惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

I
Intezer
人人都是产品经理
人人都是产品经理
博客园_首页
云风的 BLOG
云风的 BLOG
WordPress大学
WordPress大学
I
InfoQ
美团技术团队
罗磊的独立博客
F
Full Disclosure
Hugging Face - Blog
Hugging Face - Blog
T
The Blog of Author Tim Ferriss
Security Latest
Security Latest
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Last Week in AI
Last Week in AI
Vercel News
Vercel News
Microsoft Azure Blog
Microsoft Azure Blog
P
Proofpoint News Feed
M
MIT News - Artificial intelligence
H
Hacker News: Front Page
IT之家
IT之家
S
Security Affairs
N
News and Events Feed by Topic
W
WeLiveSecurity
H
Help Net Security
C
Cyber Attacks, Cyber Crime and Cyber Security
雷峰网
雷峰网
B
Blog RSS Feed
Hacker News: Ask HN
Hacker News: Ask HN
The Cloudflare Blog
小众软件
小众软件
The GitHub Blog
The GitHub Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
H
Hackread – Cybersecurity News, Data Breaches, AI and More
A
Arctic Wolf
Google DeepMind News
Google DeepMind News
C
CERT Recently Published Vulnerability Notes
The Register - Security
The Register - Security
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Cloudbric
Cloudbric
Cyberwarzone
Cyberwarzone
爱范儿
爱范儿
阮一峰的网络日志
阮一峰的网络日志
Martin Fowler
Martin Fowler
月光博客
月光博客
MongoDB | Blog
MongoDB | Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
T
Threat Research - Cisco Blogs
L
Lohrmann on Cybersecurity
www.infosecurity-magazine.com
www.infosecurity-magazine.com
L
LINUX DO - 热门话题

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Гибкая настройка тактирования микроконтроллера rp2040 для проектов с батарейным питанием
Станислав Перченко · 2026-06-17 · via Все публикации подряд на Хабре

Средний

10 мин

323

Микроконтроллер RP2040 имеют очень гибкую и понятную систему тактирования, но вот настройка тактирования в нативном SDK реализована очень запутано. В рамках этой заметки я попытался разобраться как гибко настроить тактирование ядра и периферии для достижения необходимого уровня энергопотребления, а также как правильно переводить микроконтроллер в режим минимального энергопотребления для использования в проектах с батарейным питанием.

alt

Рис. 1 - схема тактирования RP2040

Как видно из рис. 1, на котором показана схема тактирования, микроконтроллер имеет несколько источников тактового сигнала. Среди основных - это системный генератор PLL, который имеет широкую сетку частот. Каждый блок контроллера, такой как ядро, периферийные устройства, USB или АЦП может брать тактовый сигнал из любого источника. Также для каждого блока имеется предделитель тактового сигнала и возможность его выключить. Выключение тактового сигнала полностью останавливает блок и, соответственно, снижает энергопотребление.

Настройка источника PLL

Для начала давайте разберемся, как мы можем настроить блок PLL и какие частоты мы от него можем получить. Для этого, прежде всего, в код нужно включить заголовочный файл #include "hardware/clocks.h" и не забыть в файле CMakeLists.txt в разделе подключение библиотек target_link_libraries прописать библиотеку hardware_clocks. SDK предоставляет верхнеуровневую функцию для установки системного тактового сигнала set_sys_clock_hz() от источника PLL, которая принимают желаемую частоту в герцах и возвращает результат успешно она установлена или нет. Расковыряв эту функцию мы видим, что установка частоты производится функцией void set_sys_clock_pll(uint32_t vco_freq, uint post_div1, uint post_div2) Которая принимает на вход три параметра: желаемую частоту ГУНа и два постделителя. Однако не каждый набор параметров допустим. Для проверки допустимости параметров есть функция bool check_sys_clock_hz(uint32_t freq_hz, uint *vco_out, uint *postdiv1_out, uint *postdiv2_out), которая принимает на вход желаемую частоту в герцах и ссылки на результат для параметров PLL. Сама по себе эта функция (рис. 2) реализует просто перебор всех возможных допустимых значений параметров и проверки получаемой частоты на соответствие желаемой. Если соответствие не найдено, желаемая частота не является допустимой.

alt

Рис. 2 - функция проверки допустимости настроек PLL

Исходным параметром тут является частота кварцевого генератора которая в моём случае равна 12 МГц. Также ограничения накладывает диапазон частот генерации ГУНа, который для этого микроконтроллера составляет от 750 до 1600 МГц. Для того чтобы понять с какой сеткой частот мы можем работать я прогнал этот алгоритм отдельно и вывел параметры для всех доступных частот. Затем я попросил ChatGPT отсортировать эти данные по возрастанию частоты и выделил результаты кратные 1 МГц.

Пример настроек для PLL (out - значение выходной частоты, кГц)

vco_khz = 756000; postdiv1 = 7; postdiv2 = 6; out = 18000

vco_khz = 840000; postdiv1 = 7; postdiv2 = 6; out = 20000

vco_khz = 924000; postdiv1 = 7; postdiv2 = 6; out = 22000

vco_khz = 792000; postdiv1 = 6; postdiv2 = 6; out = 22000

vco_khz = 828000; postdiv1 = 6; postdiv2 = 6; out = 23000

vco_khz = 1176000; postdiv1 = 7; postdiv2 = 7; out = 24000

vco_khz = 1008000; postdiv1 = 7; postdiv2 = 6; out = 24000

vco_khz = 864000; postdiv1 = 6; postdiv2 = 6; out = 24000

vco_khz = 840000; postdiv1 = 7; postdiv2 = 5; out = 24000

vco_khz = 900000; postdiv1 = 6; postdiv2 = 6; out = 25000

vco_khz = 1092000; postdiv1 = 7; postdiv2 = 6; out = 26000

vco_khz = 936000; postdiv1 = 6; postdiv2 = 6; out = 26000

vco_khz = 780000; postdiv1 = 6; postdiv2 = 5; out = 26000

Настройки PLL для частот кратных 1 МГц можно посмотреть тут а настройки для всей сетки частот тут.

Как видим одну частоту можно получить несколькими наборами параметров. Забегая вперёд, сразу скажу, что по результатам тестирования выбирать нужно вариант с минимальной частотой ГУН, с целью минимизации энергопотребления.

контроль тактовой частоты

Прежде чем устанавливать тактовые частоты, давайте разберёмся, как производить контроль реально установленных частот. SDK предоставляет функцию uint32_t clock_get_hz(clock_handle_t clock), которая возвращает значение текущей частоты указанного тактового сигнала основываясь на предварительно сделанных настройках. В качестве параметра передаётся тип тактового сигнала, который может выбираться среди значений clk_ref, clk_sys, clk_peri, clk_usb, clk_adc и т. д., в соответствии с рис. 1. Также микроконтроллер имеет независимый счётчик тактовой частоты, которой может измерять значение тактовых частот напрямую. Для этого SDK предоставляет функцию uint32_t frequency_count_khz(uint src) на вход который нужно передать значение источника тактового сигнала для счётчика (рис. 3).

alt

Рис. 3 - доступные значения источников для измерителя тактовой частоты.

Код для вывода текущих значений тактовых сигналов может выглядеть следующим образом:

void print_clocks() {
    printf("System Clock Frequency is %d Hz\n", clock_get_hz(clk_sys));
    printf("Peripheral Clock Frequency is %d Hz\n", clock_get_hz(clk_peri));
    printf("USB Clock Frequency is %d Hz\n", clock_get_hz(clk_usb));
    printf("Reference Clock Frequency is %d Hz\n", clock_get_hz(clk_ref));

    printf("System Clock counted Frequency is %d kHz\n", frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_SYS));
    printf("Peripheral Clock counted Frequency is %d kHz\n\n", frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_PERI));
}

А настройки тактирования микроконтроллера по умолчанию показаны на рис. 3. Видим, что ядро и периферия работают на скорости 125 МГц.

alt

Рис. 4 - настройки тактирования по умолчанию

Выбор и настройка тактового сигнала блоков контроллера

Для выбора тактовых сигналов блоков контроллера SDK предоставляет функции:

  • bool clock_configure(clock_handle_t clock, uint32_t src, uint32_t auxsrc, uint32_t src_freq, uint32_t freq)

  • void clock_configure_undivided(clock_handle_t clock, uint32_t src, uint32_t auxsrc, uint32_t src_freq)

  • void clock_configure_int_divider(clock_handle_t clock, uint32_t src, uint32_t auxsrc, uint32_t src_freq, uint32_t int_divider)

Первый параметр задаёт блок, тактовую частоту которого мы хотим сконфигурировать. Его значение выбирается из следующих вариантов:

typedef enum clock_num_rp2040 {
    clk_gpout0 = 0, ///< Select CLK_GPOUT0 as clock source
    clk_gpout1 = 1, ///< Select CLK_GPOUT1 as clock source
    clk_gpout2 = 2, ///< Select CLK_GPOUT2 as clock source
    clk_gpout3 = 3, ///< Select CLK_GPOUT3 as clock source
    clk_ref = 4, ///< Select CLK_REF as clock source
    clk_sys = 5, ///< Select CLK_SYS as clock source
    clk_peri = 6, ///< Select CLK_PERI as clock source
    clk_usb = 7, ///< Select CLK_USB as clock source
    clk_adc = 8, ///< Select CLK_ADC as clock source
    clk_rtc = 9, ///< Select CLK_RTC as clock source
    CLK_COUNT
} clock_num_t;

Следующие два параметра определяют источник от которого нужно взять тактовый сигнал. Параметр src_freq указывает частоту на которой работает выбранный источник. Для корректной работы это значение должно соответствовать реальной частоте источника тактового сигнала. Кроме того функция clock_configure() позволяет через параметр freq задать делитель для выбранного тактового сигнала. Cоответственно, значение freq должно быть в целое число раз меньше значения src_freq. Также возможно задать делитель напрямую в вызове clock_configure_int_divider().

Настроить тактовый сигнал clk_sys на источник System PLL можно следующим образом:

clock_configure(clk_sys,
                        CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX,
                        CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS,
                        freq_khz * KHZ,
                        freq_khz * KHZ/1);

А вот при настройке тактового сигнала clk_peri я столкнулся с проблемой. Мне не удалось заставить работать делитель для этого тактового сигнала (для всех остальных делитель работает). Идея была в том, чтобы взять тактовую частоту от того же System PLL, поделить ее и заставить периферию работать на частоте ниже ядра. Но, похоже, делитель для clk_peri просто не работает. Соответственно, для тактирования периферии остается несколько обходных вариантов:

  • Взять тактовую частоту напрямую с System PLL (источник auxsrc = CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS). В этом случае можно заставить периферию работать на большей частоте, чем ядро, если использовать делитель для clk_sys;

  • Взять в качестве источника уже сформированный clk_sys (источник auxsrc = CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS). В этом случае периферия всегда будет работать на частоте ядра (не разумно, если МК используется на максимальной скорости 100 - 150 МГц).

  • Выбрать источником USB PLL с фиксированной частотой 48 МГц (источник auxsrc = CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB).

  • Взять тактовый сигнал напрямую с кварцевого генератора XOSC (источник auxsrc = CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_XOSC_CLKSRC). Это может быть полезно, если ядро у нас работает на полной скорости, а периферии нам достаточно медленной, например 12 МГц.

И во всех случаях нужно не забывать что в функции clock_configure_... в параметр src_freq нужно передавать реальную частоту источника. Варианты настроек тактирования периферии под катом:

раскрыть

    clock_stop(clk_peri);
    clock_configure_undivided(clk_peri,
                    0,
                    CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS,   // Источник System PLL
                    pll_freq_hz);



    clock_stop(clk_peri);
    clock_configure_undivided(clk_peri,
                    0,
                    CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS,          // Источник sclk_sys
                    clock_get_hz(clk_sys));



    clock_stop(clk_peri);
    clock_configure_undivided(clk_peri,
                    0,
                    CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,   // Источник USB PLL
                    48000000);



    clock_stop(clk_peri);
    clock_configure_undivided(clk_peri,
                    0,
                    CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_XOSC_CLKSRC,      // Источник XOSC
                    XOSC_HZ);

Тестирование энергопотребления при тактировании от PLL на разных частотах

Для тестов я использую отладочную плату контроллера Waveshare RP2040 Zero. Для минимизации побочного энергопотребления я демонтировал светодиод WS2812B, который по схеме запитан постоянно, что позволит сэкономить несколько сотен микроампер. В таблице 1 приведены результаты измерений токов потребления по напряжению 5В.

alt

Рис. 2 - Энергопотребление на разных частотах тактирования

В колонках с первой по четвёртую показаны частоты тактирования clk_sys и clk_peri, а также источники этих тактовых сигналов. В пятой колонке показаны токи потребления при исполнении программы из Flash-памяти и с включённым блоком USB и, соответственно, USB PLL. Логи выводятся через USB. Необходимо отметить что если тактирование происходит напрямую от кварцевого генератора, то блок System PLL выключается. Видно, что System PLL добавляет к энергопотреблению от 1 до 1,3 мА.

В шестой колонке приведено энергопотребление для случая, когда программа выполняется из Flash-памяти и блок USB выключен (соответственно выключен и USB PLL). Как видно, блок USB накидывает к потреблению в районе 2,2 - 2,4 мА.

if (disable_usb) {
    // Выключаем тактирование ненужных блоков
    clock_stop(clk_adc);
    clock_stop(clk_usb);
        
    // Не забываем выключить генератор PLL 48МГц для USB
    pll_deinit(pll_usb);
}

В седьмой колонке представлен вариант, похожий на предыдущий. Кроме того, что программа выполняется из ОЗУ. Как видно, на низких скоростях разница в потреблении практически незаметна. Но на частотах выше 50 МГц она становится существенной. Последнюю колонку мы обсудим в разделе посвящённом спящему режиму.

Спящий режим микроконтроллера рп-2040

В микроконтроллере реализован режим глубокого сна называется DORMANT Mode, в котором выключаются все внутренние генераторы и останавливаются цифровые блоки. Пробуждение возможно либо от асинхронного внешнего сигнала на GPIO либо от блока RTC при условии тактирования его от внешнего генератора. Производитель обещает что в режиме глубокого сна среднее энергопотребление составит 180 мкА. Однако, забегая вперёд, можно сказать, что в реальных устройствах этот уровень недостижим.

Теперь давайте разберёмся как этот DORMANT режим настроить. Производитель предоставляет для этого официальный пример, однако, в нём есть подводные камни, о которых нигде в руководстве не написано. Используемые для работы со спящим режимом API не входят в стандартный SDK, а являются частью внешней библиотеки, которую нужно скачать, установить и подключить к проекту вручную. Библиотека называется pico-extras и доступна в github-репозитории. Необходимо клонировать этот репозиторий в любое место на ПК. Я использовал домашнюю папку, в которой у меня установлен SDK в папке .pico-sdk. Соответственно библиотеку я склонировал в папку .pico-extras. Далее необходимо путь к библиотеке pico-extras прописать в переменной окружения PICO_EXTRAS_PATH (по аналогии с PICO_SDK_PATH). Теперь нам нужно в папке .pico-extras/external найти файл pico_extras_import.cmake и скопировать его в корень нашего проекта по аналогии с уже существующим файлом pico_sdk_import.cmake. Скопированный файл нужно не забыть прописать в CMakeLists.txt следующим образом:

# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)

# We also need PICO EXTRAS
include(pico_extras_import.cmake)

И в завершении нужно прописать библиотеку hardware_sleep в секцию target_link_libraries файла CMakeLists.txt.

После всех этих подготовительных действий, нужно включить в исходный код заголовок #include "pico/sleep.h" и реализовать переход DORMANT Mode последовательностью вызовов:

    // Set the crystal oscillator as the dormant clock source, UART will be reconfigured from here
    // This is necessary before sending the pico into dormancy
    sleep_run_from_xosc();

    // Go to sleep until we see a high edge on GPIO 10
    sleep_goto_dormant_until_edge_high(WAKE_GPIO);

    // Re-enabling clock sources and generators.
    sleep_power_up();

Первый вызов переводит тактирование всех блоков на кварцевый генератор, поскольку в спящий режим нельзя уходить с тактированием от PLL. Соответственно, тут же отключается тактирование АЦП и USB и выключаются оба PLL генератора.

Второй вызов, собственно, переводит микроконтроллер в спящий режим и задаёт номер GPIO по переднему фронту на котором произойдёт пробуждение.

Третий вызов восстанавливает тактирование всех блоков после пробуждения. Однако, нужно понимать, что это будет тактирование по умолчанию, как будто микроконтроллер только загрузился. После этого нужно перенастроить всё тактирование в соответствии с потребностью приложения, а также переинициализировать UART для сохранения скорости передачи.

Пришло время измерить ток потребления платы в спящем режиме. И тут меня ждало огромное разочарование - ток потребления приближается в 6 мА. Поиск по сети не дал конкретного рецепта по лечению этой ситуации. В нескольких ветках на форумах лишь упоминалось, что это может быть связано с потреблением Flash-памяти, которая остается активной. На плате используется микросхема W25Q16JV, которая в режиме чтения потребляет 2 - 8 мА в зависимости от скорости. Можно попробовать перевести Flash-память в режим Power-down, в котором потребление должно быть 1 - 10 мкА. Для этого есть команда (B9h).

Подключаем в проект библиотеку для работы с Flash-памятью: #include "hardware/flash.h" и прописываем в скрипте сборки CMakeLists.txt в секции target_link_libraries библиотеку hardware_flash. Для отправки команд вo Flash-память по QSPI существует вызов, параметры которого очевидны без расшифровки:

void flash_do_cmd(const uint8_t *txbuf, uint8_t *rxbuf, size_t count);

// и как реализовать вызов
uint8_t cmd = 0xB9;
uint8_t rx = 0;
flash_do_cmd(&cmd, &rx, 1);

Теперь встает вопрос - а как же нам исполнять программу, если Flash-память остановлена? Для этого нужно исполнять программу из ОЗУ. В скрипте сборки можно настроить загрузчик, чтобы он при старте копировал всю прошивку в ОЗУ и передал управление на исполнение кода оттуда. Делается это следующим образом:

pico_set_binary_type(project-name copy_to_ram)

Минус этого подхода в том, что в таком случае не будет работать программный или аппаратный сброс. Поскольку после сброса Flash-память остается выключенной, микроконтроллер просто не сможет запуститься (поможет только выключение-включение питания). Однако, это ограничение можно обойти, если модифицировать пользовательский загрузчик второго уровня, чтобы он выдавал команду включения Flash-памяти: Release Power-down / Device ID (ABh).

Теперь можно посмотреть в Таблицу 1 на последнюю колонку. Там указано энергопотребление с исполнением программы из ОЗУ и с выключенной Flash-памятью (в скобках - разница с предыдущей колонкой). Как видно, выключение Flash-памяти позволяет экономить 4,3 - 4,4 мА.

А теперь можно все-таки измерить реальное потребление платы в спящем режиме и с выключенной Flash-памятью. Значение получилось в пределах 950 - 1050 мкА. Результат, конечно, не выдающийся, но с этим можно жить в некоторых проектах с батарейным питанием.