
Пролог
В математике есть такая тема как Диофантовы уравнения. В самом простом виде выглядят они так.
Тут одно уравнение и две неизвестные: 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;
}
Поверьте, микроконтроллер достаточно мощный, чтобы самостоятельно подобрать себе коэффициенты для PLL. Настолько мощный, что можно найти коэффициенты даже обыкновенным перебором.
Стоит отметить, что настройки PLL нельзя просто так взять и пере инициализировать. Если вы аккуратно пере инициализируете PLL далеко в run-time, то у вас слетят настройки UART, заклинит CLI, поменяется тактирование SPI, I2S, SysTick станет тикать быстрее (или медленнее), HeartBeat LED сойдет с ума, аппаратные таймеры станут мерять время в попугаях. Это будет коллапс системы.

Поэтому, если вы хотите поменять частоту ядра, то надо как-то передать значение частоты через отработку ResetHandler-а. Как известно, ResetHandler при пуске обнуляет всю RAM память. Как же можно сохранить значение переменной в RAM памяти между программными пере сбросами ядра? Ответ прост. Следует передать настройки целевой частоты ядра через NVRAM.
Отладка
Вот так, в 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
Теперь я выполняю команду сохранить настройки желаемой частоты в NVRAM и применить настройки перезагрузив микроконтроллер.

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

Успех!
Еще я собрал отдельную консольную утилиту для вычисления коэффициентов PLL. Там есть поддержка для различных микроконтроллеров в том числе и Artery.

Дальнейшее развитие.
PLL присутствует не только в MCU Artery. У микроконтроллеров STM32 тоже есть настройка PLL. Можно сделать калькулятор и для STM32.

--Диофантовы уравнения также приходится решать при настройке битовой скорости CAN шины. Так как там надо из одного числа суммы квантов найти 4 числа: sync, prop, seg1, seg2.

Достоинства вычисления настроек PLL в run-time ++Вы можете менять частоту ядра без пере прошивки микроконтроллера. Это ускоряет разработку. ++Появляется возможность снижать требования к электропитанию за счет уменьшения производительности процессорного ядра. ++Вы можете устанавливать любые частоты ядра, даже экзотические значения, например 155 MHz.
Недостатки
--Вычисление коэффициентов PLL требует времени (пара секунд). На малых частотах ядра это будет явно заметно. Вот например тут конфиг для PLL вычисляется аж 4,1 сек.

Казалось бы неприятное залипание при каждом старте прошивки. Но даже это не проблема. Вы можете вычислить конфиг далеко в run time в любое удобное для вас время, а затем просто сохранить настройки PLL в NVRAM. При старте прошивки просто взять готовый конфиг для PLL из NVRAM и применить его.
--Надо инициализировать NVRAM до настройки PLL.
Итоги
Как видите, программирование микроконтроллеров требует математической подготовки. А как Вы хотели?
Удалось сделать автоматическое вычисление многочисленных коэффициентов PLL исходя из одной настройки - желаемой частоты процессорного ядра. Это открывает прямую дорогу для изменения частоты ядра во время исполнения программы. Таким образом можно элегантно регулировать потреблением мощности электронной платы без пере сборки всего проекта. Красота да и только! Можно, например, перейти в режим энергосбережения специально понизив частоту ядра. Или, напротив, увеличить тактирование для ускорения расчета какой-то сложной математической процедуры (например программная криптография).
Суть истории в том, что настройки PLL можно рассчитывать прямо на микроконтроллере. Настройки целевой частоты ядра, как и сами коэффициенты, можно хранить прямо в NVRAM памяти.
Как сами видите, программирование микроконтроллеров это не только варка прошивок, а так же составление всяческих вспомогательных инструментальных консольных утилит: lader-ы, калькуляторы, подпись, обновление версий и прочее и прочее.
Благодаря способности решать Диофантовы уравнения, Вы можете переконфигурировать частоту процессора далеко в run-time.
Вот так..
Links/URLs
Название | URL |
Диофантово уравнения | |
Бинарь утилиты pll_calc.exe | |
Пуск LittleFS (NVRAM с запретом до-записи flash) | |
Фазовая автоподстройка частоты | |
Вольфрам математика | https://www.wolframalpha.com/input?i=8000000x-100000000y%3D0 |
Редактор формул |