(5) Betaflight transplanted to keil – BF_OS initialization

Table of Contents

5.6.3 BF_OS initialization

5.6.3.1 systemInit() function

system_clock_config();

cycleCounterInit();

5.6.3.2 tasksInitData() function

(1)TASK_COUNT:

(2) task_t (task control block data type):

(3) task_attribute_t:

1. const int8_t staticPriority; priority attribute

5.6.3.3 tasksInit(); function

(1) queueAdd() function

(2)getTask() function

(3) clockMicrosToCycles() function

(4) Get DWT count


5.6.3 BF_OS initialization

(1)dshot

It is the control protocol of BLHeli brushless ESC firmware. Specifically, Dshot600 sends a signal to the motor at a frequency of 600kHz to control the motor speed during each motor drive PWM cycle.

Author: Border Town Quantum https://www.bilibili.com/read/cv23778072/?jump_opus=1 Source: bilibili

(2) BF OS initialization

src/main/fc/init.c line 262 systemInit(); function initializes the chip’s clock, interrupt, and SysTick.

Line 266: tasksInitData();

Line 1008: tasksInit();

5.6.3.1 systemInit() function

src/main/fc/init.c line 262 systemInit(); function initializes the chip’s clock, interrupt, and SysTick.

The systemInit() function content is as follows:

void systemInit(void)

{

//Set processor clock

system_clock_config();//config system clock to 288mhz usb 48mhz



    //Configure NVIC preempt/priority groups

nvic_priority_group_config(NVIC_PRIORITY_GROUPING);



    //Get the control/status register related to clock management (manual 4.3.21)

    cachedRccCsrValue = CRM->ctrlsts;



//The interrupt vector table has been initialized in assembly Reset_Handler

// extern uint8_t isr_vector_table_base;

// nvic_vector_table_set((uint32_t) & amp;isr_vector_table_base, 0x0);



//Disable USB1, USB2 clock

    crm_periph_clock_enable(CRM_OTGFS2_PERIPH_CLOCK|CRM_OTGFS1_PERIPH_CLOCK,FALSE);



//Clear all reset flags

    CRM->ctrlsts_bit.rstfc = TRUE;



//Enable GPIO and DMA clock

    enableGPIOPowerUsageAndNoiseReductions();



    //Loop counter initialization (provide system clock for BF OS?)

    cycleCounterInit();



    //Set the SysTick reload value (the period is 1/1000 seconds) the clock source of SysTick is SCLK)

//The system_core_clock variable is initially assigned the current SCLK (system clock) frequency after initialization in system_clock_config();

       //Set the SysTick period to 1/1000 seconds and use the AHB undivided clock by default?

    SysTick_Config(system_core_clock / 1000);

}

Among them, system_clock_config(); is used to set the processor clock.

Chapter 5 “AT32F435/437 Peripheral Library Function Overview” in the document “6. AT32 Official Information\AT32F435_437 Firmware Library BSP & Pack Application Guide.pdf” details the parameters and functions of each library function in the AT32 official library. information.

“2.4 Peripheral Address Mapping” in the document “6. AT32 Official Information\AT32F435 Reference Manual (Register).pdf” introduces the specific peripherals of the processor and the bus to which they belong.

Flash memory interface (FLASH) belongs to AHB1 bus? The figure in “1 System Architecture” in the document “6. AT32 Official Information\AT32F435 Reference Manual (Register).pdf” shows the ownership relationship between the peripherals and buses in the processor. It is preliminarily speculated that the clock source of the Flash is the clock source of the AHB bus, which is “To AHB peripheral/memory” under the HCLK clock branch in the “4 Clock and Reset Management (CRM)” figure.

Reasons why the pin development board cannot be used normally:

The AT32 chip requires that the frequency of SCLK must be greater than 288MHz. The default clock source of SCLK in the official program is HEXT (external high-speed clock), and the default frequency division is: *72/1/2; if the HEXT frequency is 8Hz, the SCLK frequency defaults to 288Hz, but the HEXT used by the pin development board is 12Hz, which causes the SCLK frequency to be too high and the program gets stuck. If the system_clock_config(void) function is removed, the processor will use HICK (internal high-speed clock) by default, and the program will run normally.

system_clock_config();

The system clock initialization function mainly uses HEXT (external high-speed clock) as the system clock source, and sets the SCLK (system clock) frequency to 288MHz and the USB clock frequency to 48MHz.

/**

  * @brief system clock config program

  * @note the system clock is configured as follow:

  * - system clock = (hext * pll_ns)/(pll_ms * pll_fr)

  * - system clock source = pll (hext)

  * - hext = 8000000

  * - sclk = 288000000

  * - ahbdiv = 1

  * - ahbclk = 288000000

  * - apb1div = 2

  * - apb1clk = 144000000

  * - apb2div = 2

  * - apb2clk = 144000000

  * - pll_ns = 72

  * - pll_ms = 1

  * - pll_fr = 2

  * @param none

  * @retval none

  */

void system_clock_config(void)

{

  /* enable pwc periph clock enable power control peripheral clock */

  crm_periph_clock_enable(CRM_PWC_PERIPH_CLOCK, TRUE);



  /* config ldo voltage sets the core power supply domain voltage to 1.3V (so that the highest frequency can be used?) */

  pwc_ldo_output_voltage_set(PWC_LDO_OUTPUT_1V3);



  /* set the flash clock divider Set the flash clock divider by 3 */

  flash_clock_divider_set(FLASH_CLOCK_DIV_3);



  /* reset crm */

  crm_reset();



  /* enable hext enables external high-speed clock*/

  crm_clock_source_enable(CRM_CLOCK_SOURCE_HEXT, TRUE);



   /* wait till hext is ready */

  while(crm_hext_stable_wait() == ERROR)

  {

  }





  /* enable hick */

  crm_clock_source_enable(CRM_CLOCK_SOURCE_HICK, TRUE);



   /* wait till hick is ready */

  while(crm_flag_get(CRM_HICK_STABLE_FLAG) != SET)

  {

  }





  /* config pll clock resource Set the PLL clock source to the external high-speed clock and its multiplication and division coefficients*/

  crm_pll_config(CRM_PLL_SOURCE_HEXT, 72, 1, CRM_PLL_FR_2);



  /* enable pll */

  crm_clock_source_enable(CRM_CLOCK_SOURCE_PLL, TRUE);



  /* wait till pll is ready */

  while(crm_flag_get(CRM_PLL_STABLE_FLAG) != SET)

  {

  }





  /* config ahbclk */

  crm_ahb_div_set(CRM_AHB_DIV_1);



  /* config apb2clk */

  crm_apb2_div_set(CRM_APB2_DIV_2);



  /* config apb1clk */

  crm_apb1_div_set(CRM_APB1_DIV_2);



  /* enable auto step mode */

  crm_auto_step_mode_enable(TRUE);





  /* select pll as system clock source Select PLL as SCLK clock source */

  crm_sysclk_switch(CRM_SCLK_PLL);



  /* wait till pll is used as system clock source */

  while(crm_sysclk_switch_status_get() != CRM_SCLK_PLL)

  {

  }





  /* disable auto step mode */

  crm_auto_step_mode_enable(FALSE);





  /* config usbclk from pll Set USB clock source and frequency division coefficient */

  crm_usb_clock_div_set(CRM_USB_DIV_6);

  crm_usb_clock_source_select(CRM_USB_CLOCK_SOURCE_PLL);





  /* update system_core_clock global variable Update some clock-related global variables of the AT32 library */

  system_core_clock_update();

}
cycleCounterInit();

The function is to initialize the BF task debugging loop counter. The counting clock source of BF OS (the algorithm related to BF source code task scheduling is referred to as BF OS here) is different from the Systick used by general embedded operating systems, but uses DWT (data observation). point and trace) counter as the clock source. DWT is not a commonly used peripheral, but a component of the Cotex-M4 core, mainly used for debugging. General software programming does not use kernel components such as DWT (the usage of DWT is similar to that of ordinary timers), but BF is used. According to the source code and actual measurement, the clock source of DWT should be the same as Systick, both SCLK -> HCLK

For distinction, the count value of DWT is called “OS count” from now on.

5.6.3.2 tasksInitData() function

The function is located in the src\main\fc\tasks.c file. It is mainly used to initialize the basic data of the task. The prototype is:

// Has to be done before tasksInit() in order to initialize any task data which may be uninitialized at boot

void tasksInitData(void)

{

    for (int i = 0; i < TASK_COUNT; i + + ) {

        tasks[i].attribute = & amp;task_attributes[i];//Task attributes

    }

}
(1)TASK_COUNT:

An enumeration type of taskId_e enumeration type, the value is the actual number of tasks used. From the taskId_e enumeration data type in \src\main\scheduler\scheduler.h, you can clearly see which tasks are actually currently used.

typedef enum {

    /* Actual tasks */

    TASK_SYSTEM = 0,

    TASK_MAIN,

    TASK_GYRO,

    TASK_FILTER,

    TASK_PID,

    TASK_ACCEL,

    TASK_ATTITUDE,

    TASK_RX,

    TASK_SERIAL,

    TASK_DISPATCH,

    TASK_BATTERY_VOLTAGE,

    TASK_BATTERY_CURRENT,

    TASK_BATTERY_ALERTS,

#ifdef USE_BEEPER

    TASK_BEEPER,

#endif

#ifdef USE_GPS

    TASK_GPS,

#endif

#ifdef USE_MAG

    TASK_COMPASS,

#endif

#ifdef USE_BARO

    TASK_BARO,

#endif

#ifdef USE_RANGEFINDER

    TASK_RANGEFINDER,

#endif

#if defined(USE_BARO) || defined(USE_GPS)

    TASK_ALTITUDE,

#endif

#ifdef USE_DASHBOARD

    TASK_DASHBOARD,

#endif

#ifdef USE_TELEMETRY

    TASK_TELEMETRY,

#endif

#ifdef USE_LED_STRIP

    TASK_LEDSTRIP,

#endif

#ifdef USE_TRANSPONDER

    TASK_TRANSPONDER,

#endif

#ifdef USE_STACK_CHECK

    TASK_STACK_CHECK,

#endif

#ifdef USE_OSD

    TASK_OSD,

#endif

#ifdef USE_BST

    TASK_BST_MASTER_PROCESS,

#endif

#ifdef USE_ESC_SENSOR

    TASK_ESC_SENSOR,

#endif

#ifdef USE_CMS

    TASK_CMS,

#endif

#ifdef USE_VTX_CONTROL

    TASK_VTXCTRL,

#endif

#ifdef USE_CAMERA_CONTROL

    TASK_CAMCTRL,

#endif



#ifdef USE_RCDEVICE

    TASK_RCDEVICE,

#endif



#ifdef USE_ADC_INTERNAL

    TASK_ADC_INTERNAL,

#endif



#ifdef USE_PINIOBOX

    TASK_PINIOBOX,

#endif



#ifdef USE_CRSF_V3

    TASK_SPEED_NEGOTIATION,

#endif



    /* Count of real tasks */

    TASK_COUNT,



    /* Service task IDs */

    TASK_NONE = TASK_COUNT,

    TASK_SELF

} taskId_e;
(2) task_t (task control block data type):

The task data structure is a structure data type located at the top level of the task that corresponds to each task one-to-one. \src\main\scheduler\scheduler.h defines its prototype. In order to facilitate the distinction, the variables of this data type will be called “Mission Control Block”:

typedef struct {

    //Task static data

    task_attribute_t *attribute; //Task attribute, as a static value (constant) after initialization



    // Scheduling task scheduling related parameters

    uint16_t dynamicPriority; //? measurement of how old task was last executed, used to avoid task starvation

    uint16_t taskAgePeriods;

    timeDelta_t taskLatestDeltaTimeUs;

    timeUs_t lastExecutedAtUs; // last time of invocation

    timeUs_t lastSignaledAtUs; // time of invocation event for event-driven tasks

    timeUs_t lastDesiredAt; // time of last desired execution



    // Statistics task running status data

    float movingAverageCycleTimeUs;

    timeUs_t anticipatedExecutionTime; // Fixed point expectation of next execution time

    timeUs_t movingSumDeltaTime10thUs; // moving sum over 64 samples

    timeUs_t movingSumExecutionTime10thUs;

    timeUs_t maxExecutionTimeUs;

    timeUs_t totalExecutionTimeUs; // total time consumed by task since boot

    timeUs_t lastStatsAtUs; // time of last stats gathering for rate calculation

#if defined(USE_LATE_TASK_STATISTICS)

    uint32_t runCount;

    uint32_t lateCount;

    timeUs_t execTime;

#endif

} task_t;

Task debugging parameters:

(1) timeDelta_t taskLatestDeltaTimeUs;

us level task execution interval

(2) timeUs_t lastExecutedAtUs;

OS count of last completed task at us level

Running status data:

(1)timeUs_t anticipatedExecutionTime;

anticipated Execution Time, expected execution time. Indicates that the task is expected to be executed when the OS count is equal to this value?

(2)timeUs_t movingSumDeltaTime10thUs;

?

(3)timeUs_t totalExecutionTimeUs;

The total time spent after the task was started (unit: us?)

(4)timeUs_t maxExecutionTimeUs;

Maximum execution time limit, will it exit before the execution is completed after this time? (Unit: us?)

(5)timeUs_t lastStatsAtUs;

OS count of last completed task at us level?

Delta: Interval?

(3)task_attribute_t:

Task attribute structure. \src\main\scheduler\scheduler.h defines its prototype:

typedef struct {

    //Configuration

    const char * taskName; //task name

    const char * subTaskName; //?

    bool (*checkFunc)(timeUs_t currentTimeUs, timeDelta_t currentDeltaTimeUs); //?

    void (*taskFunc)(timeUs_t currentTimeUs); //Task corresponding execution function?

    timeDelta_t desiredPeriodUs; // target period of execution task target execution period

    const int8_t staticPriority; // dynamicPriority grows in steps of this size priority?

} task_attribute_t;

A task_attribute_t type array is defined and initialized at the beginning of \src\main\fc\tasks.c, which stores the static values of each task attribute:

// Task ID data in .data (initialised data)

task_attribute_t task_attributes[TASK_COUNT] = {

    [TASK_SYSTEM] = DEFINE_TASK("SYSTEM", "LOAD", NULL, taskSystemLoad, TASK_PERIOD_HZ(10), TASK_PRIORITY_MEDIUM_HIGH),

    [TASK_MAIN] = DEFINE_TASK("SYSTEM", "UPDATE", NULL, taskMain, TASK_PERIOD_HZ(1000), TASK_PRIORITY_MEDIUM_HIGH),

    [TASK_SERIAL] = DEFINE_TASK("SERIAL", NULL, NULL, taskHandleSerial, TASK_PERIOD_HZ(100), TASK_PRIORITY_LOW), // 100 Hz should be enough to flush up to 115 bytes @ 115200 baud

…

};

Since the source code related to the driver has not been transplanted yet, an error will be reported when compiling here, so an empty array is used instead:

task_attribute_t task_attributes[TASK_COUNT] = {0};

1. const int8_t staticPriority; priority attribute

Regarding the priority of the task, an enumeration variable is defined in “\src\main\scheduler\scheduler.h”

typedef enum {

    TASK_PRIORITY_REALTIME = -1, // Real-time priority Task will be run outside the scheduler logic Task will be run outside the scheduler logic

    TASK_PRIORITY_LOWEST = 1,

    TASK_PRIORITY_LOW = 2,

    TASK_PRIORITY_MEDIUM = 3,

    TASK_PRIORITY_MEDIUM_HIGH = 4,

    TASK_PRIORITY_HIGH = 5,

    TASK_PRIORITY_MAX = 255

} taskPriority_e;

Except for ordinary priorities such as 1~255, there is no real-time priority TASK_PRIORITY_REALTIME with a value of -1. For tasks with real-time priority, their task functions are not executed in the algorithm that scans the task and executes the task function later, but runs separately in a customized place, that is, “runs outside the scheduler logic”

5.6.3.3 tasksInit(); function

As introduced in the previous Linux source code, this function mainly performs three parts of operations. It uses the schedulerInit() function to initialize the scheduler, uses the setTaskEnabled() function to enable each task, and uses the rescheduleTask() function to run each task. Cycle reset, etc. The last two parts are commented out in order to compile and pass first.

schedulerInit(); task debugger initialization function:

This function is located in \src\main\scheduler\scheduler.c, and its prototype is:

void schedulerInit(void)

{

    queueClear(); //Clear the task queue

    queueAdd(getTask(TASK_SYSTEM));



    schedLoopStartMinCycles = clockMicrosToCycles(SCHED_START_LOOP_MIN_US);

    schedLoopStartMaxCycles = clockMicrosToCycles(SCHED_START_LOOP_MAX_US);

    schedLoopStartCycles = schedLoopStartMinCycles; //Set the clock reload value of task height

    schedLoopStartDeltaDownCycles = clockMicrosToCycles(1) / SCHED_START_LOOP_DOWN_STEP; //Waiting time?

    schedLoopStartDeltaUpCycles = clockMicrosToCycles(1) / SCHED_START_LOOP_UP_STEP;



    taskGuardMinCycles = clockMicrosToCycles(TASK_GUARD_MARGIN_MIN_US);

    taskGuardMaxCycles = clockMicrosToCycles(TASK_GUARD_MARGIN_MAX_US);

    taskGuardCycles = taskGuardMinCycles;

    taskGuardDeltaDownCycles = clockMicrosToCycles(1) / TASK_GUARD_MARGIN_DOWN_STEP;

    taskGuardDeltaUpCycles = clockMicrosToCycles(1) / TASK_GUARD_MARGIN_UP_STEP;



//Count value corresponding to the GYRO task target period

    desiredPeriodCycles = (int32_t)clockMicrosToCycles((uint32_t)getTask(TASK_GYRO)->attribute->desiredPeriodUs);



    lastTargetCycles = getCycleCounter(); //Get the DWT count value as the task cycle count value



#if defined(USE_LATE_TASK_STATISTICS)

    nextTimingCycles = lastTargetCycles;

#endif



    for (taskId_e taskId = 0; taskId < TASK_COUNT; taskId + + ) {

        schedulerResetTaskStatistics(taskId); //Reset all task time-related running status data

    }
(1) queueAdd() function

Located in the current file.

The memmove function is a C language library function located in the header file. Its function is to move memory blocks, or copy memory data to the target location.

Function declaration: void * memmove ( void * destination, const void * source, size_t num );

parameter:

destination: Pointer to the destination array into which the contents are to be copied, type-cast to a pointer of type void*.

source: Pointer to the data source to be copied, type converted to a pointer of const void* type.

num: Number of bytes to copy. (size_t is an unsigned integer type)

Return value: Return destination.

(2) getTask() function

The parameter is the task serial number, and the return value is the pointer to the corresponding element of the tasks array of the task serial number. It is only called in the scheduler.c file. The function is easy to understand and is defined in \src\main\fc\tasks.c.

(3) clockMicrosToCycles() function

Parameter: number of us

Return value: Corresponding number of SCLK clock cycles

prototype:

uint32_t clockMicrosToCycles(uint32_t micros)

{

    return micros * usTicks;

}

usTicks is a global variable, which is initialized in systemInit() -》cycleCounterInit(); to the number of cycles required to run 1us at the SCLK clock frequency (288MHz).

(4) Get DWT count

lastTargetCycles = getCycleCounter(); //Get the DWT count value as the task cycle count value

Registers: divided into three categories

Peripheral registers (UART, SPI), core registers (core file labels, belonging to Cotex-M3, Cotex-M4, such as DWT, ITM, SCB, etc.), instruction/architecture registers (associated with binary instruction set, ARM architecture, R0~ R14, MSP, PC, etc.)