Зачем Программисту Микроконтроллеров Диофантовы Уравнения

от автора

Пролог

В математике есть такая тема как Диофантовы уравнения. В самом простом виде выглядят они так.

ax+by=d \qquad \qquad\qquad (1)

Тут одно уравнение и две неизвестные: x,y. При этом a, b, d — это известные константы.

На первый взгляд может показаться как бесполезная вещь для программирования Micro Controller Unit (MCU), но не тут-то было.

Когда программируешь микроконтроллер (MCU), то первое, что приходится делать — это конфигурировать тактирование процессорного ядра. Тактированием заправляет устройство с названием phase-locked loop (PLL/ФАПЧ). Это подсистема SoC(а).

И там надо подобрать три константы (натуральных числа): M, N и F. В микроконтроллере Artery, абстрактно изображая, эта электрическая цепь выглядит вот так. Умножители и делители частот.

По формуле (2)

\frac{(N\frac{F_{quartz}}{M})   }  {F} = F_{sys}    \qquad  \qquad  (2)

Вычисляется системная частота F_sys (sclk). Это уравнение (2) преобразуется в уравнение (3)

F_{quartz}   \frac{N}{M} -F_{sys}F     = 0   \qquad  \qquad  (3)

А уравнение (3), в свою очередь, это самое настоящее Диофантово уравнение! Да.. Тут F_quartz — это частота кварцевого резонатора, который задается схемотехникой электронной платы.
F_sys задается программистом для достижения желаемой производительности прошивки. Остаются целочисленные неизвестные N, M и F.

Далее накладываются ещё ограничения уже производителем микроконтроллера. Для Artery это

Переменная

Минимальное

Максимальное

M

1

15

N

31

500

FR (степени двойки)

1

32

Это ограничение даже прописано комментариями в исходных кодах на Cи, которые бесплатно предоставляет производитель Artery Tech.

 /**   *    config crm pll   *                        pll_rcs_freq * pll_ns   *         pll clock = --------------------------------   *                           pll_ms * pll_fr_n   *         attemtion:   *                  31 <= pll_ns <= 500   *                  1  <= pll_ms <= 15   *   *                       pll_rcs_freq   *         2mhz <=  ---------------------- <= 16mhz   *                          pll_ms   *   *                       pll_rcs_freq * pll_ns   *         500mhz <=  -------------------------------- <= 1200mhz   *                               pll_ms   * @param  clock_source   *         this parameter can be one of the following values:   *         - CRM_PLL_SOURCE_HICK   *         - CRM_PLL_SOURCE_HEXT   * @param  pll_ns (31~500)   * @param  pll_ms (1~15)   * @param  pll_fr   *         this parameter can be one of the following values:   *         - CRM_PLL_FR_1   *         - CRM_PLL_FR_2   *         - CRM_PLL_FR_4   *         - CRM_PLL_FR_8   *         - CRM_PLL_FR_16   *         - CRM_PLL_FR_32   * @retval none   */ void crm_pll_config(crm_pll_clock_source_type clock_source,                      uint16_t pll_ns,                      uint16_t pll_ms,                     crm_pll_fr_type pll_fr)   

Итак, постановка задачи:

Есть электронная плата с Artery MCU на борту. На PCB также припаян кварцевый резонатор c частотой 8 MHz и подключен к микроконтроллеру. Необходимо программно сконфигурировать PLL так чтобы базовая системная частота ядра стала ровно на 100 MHz.

Какими при этом должны быть коэффициенты PLL: N, M и RF?

Решение

По сути, задача свелась к тому чтобы решить Диофантово уравнение (4).

8000000 \frac{N}{M} -100000000F     = 0   \qquad  \qquad  (4)

Можно попробовать прибегнуть к помощи пресловутого Artificial intelligence (AI) на сайте Wolfram Alfa

Однако это не решение в частном виде. На практике же нужно именно численное решение, чтобы проинициализировать им аргументы функции crm_pll_config().

Однако нам повезло. В частности, тут область значений функции (2) достаточно маленькая, поэтому можно найти решение уравнения (3) обыкновенным перебором.

Для этого я написал свой простой численный решатель Диофантова уравнения для PLL прямо на Си.

uint64_t ipow(uint32_t base, uint32_t exponenta) { uint64_t ret = 1, i = 0;     if(0 != exponenta) {         for(i = 1; i <= exponenta; i++) {             ret *= base;         }     }     return ret; }  typedef struct{     uint32_t ms;     uint32_t ns;     uint32_t fr; }PllArtety_t;  bool pll_calc_artery(uint32_t freq_xtal_hz,                       uint32_t freq_sys_hz,                       PllArtety_t* const PllArtety) {     bool res = false;      LOG_INFO(PLL_CALC, "FreqXtal:%u Hz,FreqSys:%u  Hz",               freq_xtal_hz, freq_sys_hz);     cli_printf("{ [ (  {Xtal:%uHz} /M )*N ]/FR  }= Sys:%u Hz" CRLF,                 freq_xtal_hz, freq_sys_hz);     uint32_t solution_cnt = 0;     if(PllArtety) {         uint32_t m = 0;         uint32_t temp_hz = 0;         uint32_t temp_m_hz = 0;         uint32_t cur_freq_sys_hz = 0;         for(m = 1; m <= 15; m++) {             uint32_t n = 0;             temp_m_hz = freq_xtal_hz/m;             if(2000000<=temp_m_hz) {                 if(temp_m_hz<=16000000){                     for(n = 31; n <= 500; n++) {                         uint32_t f = 0;                         for(f = 0; f <= 5; f++) {                             uint32_t fr = ipow(2, f);                             cur_freq_sys_hz = ((n * freq_xtal_hz) / (m * fr));                             if(freq_sys_hz == cur_freq_sys_hz) {                                 temp_hz = freq_xtal_hz * n / m;                                /*condition from Artery New Clock Config*/                                 if(500000000 <= temp_hz) {                                     if(temp_hz <= 1200000000) {                                         solution_cnt++;                                         cli_printf("%u: MS:%2u,NS:%3u,FR:%2u" CRLF,                                                    solution_cnt, m, n, fr);                                         PllArtety->ms = m;                                         PllArtety->ns = n;                                         PllArtety->fr = fr;                                         res = true;                                     }                                 }                             }                         }                     }             }             }         }     }     if(res) {         LOG_INFO(PLL_CALC, "SpotPllVals! %u Solutions",solution_cnt);     } else {         LOG_ERROR(PLL_CALC, "NoPllVals!");     }     return res; } 

Отладка

Вот так, в UART-CLI прошивки я исполнил команду обсчета PLL и получил аж 4 решения на выбор.

Вот эти решения:

1: MS:1, NS:100, FR:8
2: MS:2, NS:200, FR:8
3: MS:3, NS:300, FR:8
4: MS:4, NS:400, FR:8

Далее подставив четвёртое решение в утилиту проверки PLL я увидел, что конфиг валидный!

Успех!

Итоги

Как видите, программирование микроконтроллеров требует математической подготовки. А как Вы хотели?

Благодаря способности решать Диофантовы уравнения, Вы можете переконфигурировать частоту процессора далеко в run-time.

Можно, например, перейти в режим энергосбережения специально понизив частоту ядра. Или увеличить для ускорения расчета какой-то сложной математической процедуры.

Вот так..

Links/URLs

https://www.wolframalpha.com/input?i=8000000x-100000000y%3D0

http://latex.codecogs.com/eqneditor/editor.php

https://ru.wikipedia.org/wiki/Диофантово_уравнение

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Вам приходилось на работе решать Диофантовы уравнения?

34.48% да30
65.52% нет57

Проголосовали 87 пользователей. Воздержались 10 пользователей.

ссылка на оригинал статьи https://habr.com/ru/articles/803415/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *