Skip to content

[WIP] Handle APB frequency change #2250

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jan 9, 2019
Merged
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ set(CORE_SRCS
cores/esp32/cbuf.cpp
cores/esp32/esp32-hal-adc.c
cores/esp32/esp32-hal-bt.c
cores/esp32/esp32-hal-cpu.c
cores/esp32/esp32-hal-dac.c
cores/esp32/esp32-hal-gpio.c
cores/esp32/esp32-hal-i2c.c
Expand Down
183 changes: 183 additions & 0 deletions cores/esp32/esp32-hal-cpu.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "freertos/xtensa_timer.h"
#include "esp_attr.h"
#include "esp_log.h"
#include "soc/rtc.h"
#include "soc/rtc_cntl_reg.h"
#include "rom/rtc.h"
#include "soc/apb_ctrl_reg.h"
#include "esp32-hal.h"
#include "esp32-hal-cpu.h"

typedef struct apb_change_cb_s {
struct apb_change_cb_s * next;
void * arg;
apb_change_cb_t cb;
} apb_change_t;

const uint32_t MHZ = 1000000;

static apb_change_t * apb_change_callbacks = NULL;
static xSemaphoreHandle apb_change_lock = NULL;

static void initApbChangeCallback(){
static volatile bool initialized = false;
if(!initialized){
initialized = true;
apb_change_lock = xSemaphoreCreateMutex();
if(!apb_change_lock){
initialized = false;
}
}
}

static void triggerApbChangeCallback(apb_change_ev_t ev_type, uint32_t old_apb, uint32_t new_apb){
initApbChangeCallback();
xSemaphoreTake(apb_change_lock, portMAX_DELAY);
apb_change_t * r = apb_change_callbacks;
while(r != NULL){
r->cb(r->arg, ev_type, old_apb, new_apb);
r=r->next;
}
xSemaphoreGive(apb_change_lock);
}

bool addApbChangeCallback(void * arg, apb_change_cb_t cb){
initApbChangeCallback();
apb_change_t * c = (apb_change_t*)malloc(sizeof(apb_change_t));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a template class that can be used for linked lists that is generic enough to plug in here, maybe even something from ESP-IDF that can be leveraged instead of managing the list here (and later in remove)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know of one, but I have been thinking of such thing for some time. This is C though... so IDK

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a reason to stick with C only here, there are already a few C++ pieces here and there...

Copy link
Collaborator

@atanisoft atanisoft Dec 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can also use something like this to mix C++ into C...

template<int signalGenerator>  
void IRAM_ATTR signalGeneratorTimerISR(void)

it has C linkage but you can call it with different values to get different versions of it (this is part of how I do ISRs using one method but two diff data sources)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the reason why it is C is so the whole HAL can be used in C-only libs and extensions. Not really sure if it's worth it, but it's how it started :)

if(!c){
log_e("Callback Object Malloc Failed");
return false;
}
c->next = NULL;
c->arg = arg;
c->cb = cb;
xSemaphoreTake(apb_change_lock, portMAX_DELAY);
if(apb_change_callbacks == NULL){
apb_change_callbacks = c;
} else {
apb_change_t * r = apb_change_callbacks;
if(r->cb != cb || r->arg != arg){
while(r->next){
r = r->next;
if(r->cb == cb && r->arg == arg){
goto unlock_and_exit;
}
}
r->next = c;
}
}
unlock_and_exit:
xSemaphoreGive(apb_change_lock);
return true;
}

bool removeApbChangeCallback(void * arg, apb_change_cb_t cb){
initApbChangeCallback();
xSemaphoreTake(apb_change_lock, portMAX_DELAY);
apb_change_t * r = apb_change_callbacks;
if(r == NULL){
xSemaphoreGive(apb_change_lock);
return false;
}
if(r->cb == cb && r->arg == arg){
apb_change_callbacks = r->next;
free(r);
} else {
while(r->next && (r->next->cb != cb || r->next->arg != arg)){
r = r->next;
}
if(r->next == NULL || r->next->cb != cb || r->next->arg != arg){
xSemaphoreGive(apb_change_lock);
return false;
}
apb_change_t * c = r->next;
r->next = c->next;
free(c);
}
xSemaphoreGive(apb_change_lock);
return true;
}

static uint32_t calculateApb(rtc_cpu_freq_config_t * conf){
if(conf->freq_mhz >= 80){
return 80 * MHZ;
}
return (conf->source_freq_mhz * MHZ) / conf->div;
}

void esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us); //private in IDF

bool setCpuFrequency(uint32_t cpu_freq_mhz){
rtc_cpu_freq_config_t conf, cconf;
uint32_t capb, apb;
//Get current CPU clock configuration
rtc_clk_cpu_freq_get_config(&cconf);
//return if frequency has not changed
if(cconf.freq_mhz == cpu_freq_mhz){
return true;
}
//Get configuration for the new CPU frequency
if(!rtc_clk_cpu_freq_mhz_to_config(cpu_freq_mhz, &conf)){
log_e("CPU clock could not be set to %u MHz", cpu_freq_mhz);
return false;
}
//Current APB
capb = calculateApb(&cconf);
//New APB
apb = calculateApb(&conf);
log_i("%s: %u / %u = %u Mhz, APB: %u Hz", (conf.source == RTC_CPU_FREQ_SRC_PLL)?"PLL":((conf.source == RTC_CPU_FREQ_SRC_APLL)?"APLL":((conf.source == RTC_CPU_FREQ_SRC_XTAL)?"XTAL":"8M")), conf.source_freq_mhz, conf.div, conf.freq_mhz, apb);
//Call peripheral functions before the APB change
if(apb_change_callbacks){
triggerApbChangeCallback(APB_BEFORE_CHANGE, capb, apb);
}
//Make the frequency change
rtc_clk_cpu_freq_set_config_fast(&conf);
if(capb != apb){
//Update REF_TICK (uncomment if REF_TICK is different than 1MHz)
//if(conf.freq_mhz < 80){
// ESP_REG(APB_CTRL_XTAL_TICK_CONF_REG) = conf.freq_mhz / (REF_CLK_FREQ / MHZ) - 1;
//}
//Update APB Freq REG
rtc_clk_apb_freq_update(apb);
//Update esp_timer divisor
esp_timer_impl_update_apb_freq(apb / MHZ);
}
//Update FreeRTOS Tick Divisor
uint32_t fcpu = (conf.freq_mhz >= 80)?(conf.freq_mhz * MHZ):(apb);
_xt_tick_divisor = fcpu / XT_TICK_PER_SEC;
//Call peripheral functions after the APB change
if(apb_change_callbacks){
triggerApbChangeCallback(APB_AFTER_CHANGE, capb, apb);
}
return true;
}

uint32_t getCpuFrequency(){
rtc_cpu_freq_config_t conf;
rtc_clk_cpu_freq_get_config(&conf);
return conf.freq_mhz;
}

uint32_t getApbFrequency(){
rtc_cpu_freq_config_t conf;
rtc_clk_cpu_freq_get_config(&conf);
return calculateApb(&conf);
}
47 changes: 47 additions & 0 deletions cores/esp32/esp32-hal-cpu.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef _ESP32_HAL_CPU_H_
#define _ESP32_HAL_CPU_H_

#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>

typedef enum { APB_BEFORE_CHANGE, APB_AFTER_CHANGE } apb_change_ev_t;

typedef void (* apb_change_cb_t)(void * arg, apb_change_ev_t ev_type, uint32_t old_apb, uint32_t new_apb);

bool addApbChangeCallback(void * arg, apb_change_cb_t cb);
bool removeApbChangeCallback(void * arg, apb_change_cb_t cb);

//function takes the following frequencies as valid values:
// 240, 160, 80 <<< For all XTAL types
// 40, 20, 13, 10, 8, 5, 4, 3, 2, 1 <<< For 40MHz XTAL
// 26, 13, 5, 4, 3, 2, 1 <<< For 26MHz XTAL
// 24, 12, 8, 6, 4, 3, 2, 1 <<< For 24MHz XTAL
bool setCpuFrequency(uint32_t cpu_freq_mhz);

uint32_t getCpuFrequency(); // In MHz
uint32_t getApbFrequency(); // In Hz

#ifdef __cplusplus
}
#endif

#endif /* _ESP32_HAL_CPU_H_ */
49 changes: 7 additions & 42 deletions cores/esp32/esp32-hal-misc.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <sys/time.h>
#include "soc/rtc.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/apb_ctrl_reg.h"
#include "rom/rtc.h"
#include "esp_task_wdt.h"
#include "esp32-hal.h"
Expand Down Expand Up @@ -100,57 +101,19 @@ void disableCore1WDT(){
}
#endif

static uint32_t _cpu_freq_mhz = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ;
static uint32_t _sys_time_multiplier = 1;

bool setCpuFrequency(uint32_t cpu_freq_mhz){
rtc_cpu_freq_config_t conf, cconf;
rtc_clk_cpu_freq_get_config(&cconf);
if(cconf.freq_mhz == cpu_freq_mhz && _cpu_freq_mhz == cpu_freq_mhz){
return true;
}
if(!rtc_clk_cpu_freq_mhz_to_config(cpu_freq_mhz, &conf)){
log_e("CPU clock could not be set to %u MHz", cpu_freq_mhz);
return false;
}
#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO
log_i("%s: %u / %u = %u Mhz", (conf.source == RTC_CPU_FREQ_SRC_PLL)?"PLL":((conf.source == RTC_CPU_FREQ_SRC_APLL)?"APLL":((conf.source == RTC_CPU_FREQ_SRC_XTAL)?"XTAL":"8M")), conf.source_freq_mhz, conf.div, conf.freq_mhz);
delay(10);
#endif
rtc_clk_cpu_freq_set_config_fast(&conf);
_cpu_freq_mhz = conf.freq_mhz;
_sys_time_multiplier = 80000000 / getApbFrequency();
return true;
}

uint32_t getCpuFrequency(){
rtc_cpu_freq_config_t conf;
rtc_clk_cpu_freq_get_config(&conf);
return conf.freq_mhz;
}

uint32_t getApbFrequency(){
rtc_cpu_freq_config_t conf;
rtc_clk_cpu_freq_get_config(&conf);
if(conf.freq_mhz >= 80){
return 80000000;
}
return (conf.source_freq_mhz * 1000000) / conf.div;
}

unsigned long IRAM_ATTR micros()
{
return (unsigned long) (esp_timer_get_time()) * _sys_time_multiplier;
return (unsigned long) (esp_timer_get_time());
}

unsigned long IRAM_ATTR millis()
{
return (unsigned long) (micros() / 1000);
return (unsigned long) (esp_timer_get_time() / 1000);
}

void delay(uint32_t ms)
{
vTaskDelay((ms * _cpu_freq_mhz) / (portTICK_PERIOD_MS * 240));
vTaskDelay(ms / portTICK_PERIOD_MS);
}

void IRAM_ATTR delayMicroseconds(uint32_t us)
Expand Down Expand Up @@ -183,8 +146,10 @@ bool btInUse(){ return false; }

void initArduino()
{
//init proper ref tick value for PLL (uncomment if REF_TICK is different than 1MHz)
//ESP_REG(APB_CTRL_PLL_TICK_CONF_REG) = APB_CLK_FREQ / REF_CLK_FREQ - 1;
#ifdef F_CPU
setCpuFrequency(F_CPU/1000000L);
setCpuFrequency(F_CPU/1000000);
#endif
#if CONFIG_SPIRAM_SUPPORT
psramInit();
Expand Down
Loading