Пролог
В математике есть такая тема как Диофантовы уравнения. В самом простом виде выглядят они так.
Тут одно уравнение и две неизвестные: x,y. При этом a, b, d - это известные константы.
На первый взгляд может показаться как бесполезная вещь для программирования Micro Controller Unit (MCU), но не тут-то было.
Когда программируешь микроконтроллер (MCU), то первое, что приходится делать - это конфигурировать тактирование процессорного ядра. Тактированием заправляет устройство с названием phase-locked loop (PLL/ФАПЧ). Это подсистема SoC(а).
И там надо подобрать три константы (натуральных числа): M, N и F. В микроконтроллере Artery, абстрактно изображая, эта электрическая цепь выглядит вот так. Умножители и делители частот.
По формуле (2)
Вычисляется системная частота F_sys (sclk). Это уравнение (2) преобразуется в уравнение (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).
Можно попробовать прибегнуть к помощи пресловутого 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