(五)Betaflight 移植到keil——BF_OS初始化

目录

5.6.3 BF_OS初始化

5.6.3.1 systemInit()函数

system_clock_config();

cycleCounterInit();

5.6.3.2 tasksInitData()函数

(1)TASK_COUNT:

(2)task_t(任务控制块 数据类型):

(3)task_attribute_t:

1. const int8_t staticPriority; 优先级属性

5.6.3.3 tasksInit();函数

(1)queueAdd()函数

(2)getTask()函数

(3)clockMicrosToCycles()函数

(4)得到DWT计数


5.6.3 BF_OS初始化

(1)dshot

是BLHeli无刷电调固件的控制协议,具体来说,Dshot600是在每个电机驱动PWM周期内,以600kHz的频率向电机发出信号控制电机转速。

作者:边城量子 https://www.bilibili.com/read/cv23778072/?jump_opus=1 出处:bilibili

(2)BF OS 初始化

src/main/fc/init.c 262行systemInit();函数对芯片的时钟、中断、SysTick进行了初始化。

266行:  tasksInitData();

1008行:     tasksInit();

5.6.3.1 systemInit()函数

src/main/fc/init.c 262行systemInit();函数对芯片的时钟、中断、SysTick进行了初始化。

systemInit()函数内容如下:

void systemInit(void)

{

//设置处理器时钟

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



    // Configure NVIC preempt/priority groups

nvic_priority_group_config(NVIC_PRIORITY_GROUPING);



    //获取时钟管理相关的 控制/状态寄存器(手册4.3.21)

    cachedRccCsrValue = CRM->ctrlsts;



//汇编Reset_Handler中已经对中断向量表进行了初始化

//    extern uint8_t isr_vector_table_base;

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



//失能USB1、USB2时钟

    crm_periph_clock_enable(CRM_OTGFS2_PERIPH_CLOCK|CRM_OTGFS1_PERIPH_CLOCK,FALSE);



//清除所有复位标志

    CRM->ctrlsts_bit.rstfc = TRUE;



//使能GPIO与DMA时钟

    enableGPIOPowerUsageAndNoiseReductions();



    //循环计数器初始化(为BF OS提供系统时钟?)

    cycleCounterInit();



    //设置SysTick重载值(周期为1/1000秒)SysTick的时钟源是SCLK)

//system_core_clock变量在system_clock_config();初始化后初赋值为当前SCLK(系统时钟)频率

       //设置SysTick周期为1/1000秒,默认使用AHB不分频时钟?

    SysTick_Config(system_core_clock / 1000);

}

其中system_clock_config();换作用是设置处理器时钟

“6、AT32官方资料\AT32F435_437固件库BSP&Pack应用指南.pdf”文档的第5章“AT32F435/437 外设库函数概述”详细介绍了AT32官方库中每个库函数的参数与功能信息。

“6、AT32官方资料\AT32F435 参考手册(寄存器).pdf”文档中“2.4 外设地址映射”介绍了处理器有哪些具体外设以及其所归属的总线。

闪存存储器接口(FLASH)属于AHB1总线?“6、AT32官方资料\AT32F435 参考手册(寄存器).pdf”文档中“1 系统架构”中图展示了处理器中各外设与总线的归属关系。初步推测Flash的时钟源就是AHB总线的时钟源,也就是“4 时钟和复位管理(CRM)”图中HCLK时钟分支下的“To AHB peripheral/memory”。

引脚开发板不能正常使用的原因:

AT32芯片要求SCLK的频率还得大于288MHz, 而官方程序SCLK默认时钟源为HEXT(外部高速时钟),默认倍分频:*72/1/2;如果HEXT频率为8Hz,则SCLK频率默认就为288Hz,但时引脚开发板使用的HEXT是12Hz,导致SCLK频率过大,进而程序卡死。而如果去掉system_clock_config(void)函数,处理器会默认使用HICK(内部高速时钟),则程序正常运行。

system_clock_config();

系统时钟初始化函数主要是将HEXT(外部高速时钟)作为系统时钟源,并设置SCLK(系统时钟)频率为288MHz,USB时钟频率为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 使能电源控制外设时钟 */

  crm_periph_clock_enable(CRM_PWC_PERIPH_CLOCK, TRUE);



  /* config ldo voltage 设置内核供电域电压为1.3V(从而可以使用最高主频?)*/

  pwc_ldo_output_voltage_set(PWC_LDO_OUTPUT_1V3);



  /* set the flash clock divider 设置闪存时钟3分频 */

  flash_clock_divider_set(FLASH_CLOCK_DIV_3);



  /* reset crm */

  crm_reset();



  /* enable hext 使能外部高速时钟*/

  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 设置PLL时钟源为外部高速时钟与其倍频、分频系数*/

  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 选择PLL作为SCLK时钟源*/

  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 设置usb时钟源与分频系数*/

  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 更新一些AT32库与时钟有关的全局变量*/

  system_core_clock_update();

}

cycleCounterInit();

函数是BF任务调试循环计数器初始化,BF OS(这里将BF源码任务调度相关的算法简称为BF OS)的计数时钟源不同于一般嵌入式操作系统所使用的Systick,而是使用了DWT(数据观察点与跟踪)的计数器作为时钟源。DWT并不是一般使用的外设,而是属于Cotex-M4内核的一个组件,主要用于Debug调试。一般软件编程是不用DWT等内核组件的(DWT与普通定时器的用法差不多),但是,BF就是用了。DWT的时钟源根据源码与实测,应该与Systick相同,都是SCLK -> HCLK

为了区分,之后称DWT的计数值为“OS计数”。

5.6.3.2 tasksInitData()函数

函数位于src\main\fc\tasks.c文件中,主要是对任务基本数据进行初始化,原型:

// 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 = &task_attributes[i];//任务属性

    }

}

(1)TASK_COUNT:

taskId_e枚举体类型的一个枚举类型,值是实际使用的任务数量。从\src\main\scheduler\scheduler.h中的taskId_e枚举体数据类型可以清晰地看出当前实际使用的任务有哪些。

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(任务控制块 数据类型):

任务数据结构体,是与每个任务一一对应位于任务最顶层的结构体数据类型,\src\main\scheduler\scheduler.h定义了其原型,为了方便区分,之后称该数据类型的变量为“任务控制块”:

typedef struct {

    // Task static data

    task_attribute_t *attribute; //任务属性,初始化后作为静态值(常量)



    // Scheduling 任务调度相关参数

    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 任务运行状态数据

    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;

任务调试参数:

(1)timeDelta_t taskLatestDeltaTimeUs;

us级任务执行间隔

(2)timeUs_t lastExecutedAtUs;

us级上次执行完任务的OS计数

运行状态数据:

(1)timeUs_t anticipatedExecutionTime;

anticipated(预期) Execution(执行) Time,预期执行时间。表示期望任务在OS计数等于该值时被执行?

(2)timeUs_t movingSumDeltaTime10thUs;

?

(3)timeUs_t totalExecutionTimeUs;

任务被启动后消耗的总时间(单位:us  ?)

(4)timeUs_t maxExecutionTimeUs;

最大执行时间限制,超过该时间还没执行完则退出?(单位:us  ?)

(5)timeUs_t lastStatsAtUs;

us级上次执行完任务的OS计数?

Delta:间隔?

(3)task_attribute_t:

任务属性结构体。\src\main\scheduler\scheduler.h定义了其原型:

typedef struct {

    // Configuration

    const char * taskName; //任务名

    const char * subTaskName; //?

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

    void (*taskFunc)(timeUs_t currentTimeUs); //任务对应执行函数?

    timeDelta_t desiredPeriodUs;        // target period of execution 任务目标执行周期

    const int8_t staticPriority;        // dynamicPriority grows in steps of this size 优先级?

} task_attribute_t;

在\src\main\fc\tasks.c开头定义并初始化了一个task_attribute_t类型数组,保存着各任务属性的静态值:

// 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

……

};

由于还没有移植驱动相关源码,这里编译时会报错,就选用一个空数组来代替了:

task_attribute_t task_attributes[TASK_COUNT] = {0};

1. const int8_t staticPriority; 优先级属性

关于任务的优先级,定义了一个枚举变量在“\src\main\scheduler\scheduler.h”中

typedef enum {

    TASK_PRIORITY_REALTIME = -1, // 实时优先级 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;

其中除了1~255等普通优先级外,不有一个值为-1的实时优先级TASK_PRIORITY_REALTIME。拥有实时优先级的任务,其任务函数并不在之后扫描任务执行任务函数的算法中执行,而是在自定义的地方单独运行,即“调度程序逻辑之外运行”

5.6.3.3 tasksInit();函数

与之前Linux源码中介绍的一样,这个函数主要进行了三部分操作,使用schedulerInit() 函数进行调度器初始化,使用setTaskEnabled() 函数对各任务进行使能,使用rescheduleTask() 函数对各任务的运行周期重新设置等。其中后两部分,为了先编译通过都注释掉了。

schedulerInit();任务调试器初始化函数:

该函数位于\src\main\scheduler\scheduler.c中,原型为:

void schedulerInit(void)

{

    queueClear(); //清空任务队列

    queueAdd(getTask(TASK_SYSTEM));



    schedLoopStartMinCycles = clockMicrosToCycles(SCHED_START_LOOP_MIN_US);

    schedLoopStartMaxCycles = clockMicrosToCycles(SCHED_START_LOOP_MAX_US);

    schedLoopStartCycles = schedLoopStartMinCycles; //设置任务高度的时钟重装载值

    schedLoopStartDeltaDownCycles = clockMicrosToCycles(1) / SCHED_START_LOOP_DOWN_STEP;//等待时间?

    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;



//GYRO任务目标周期对应的计数值

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



    lastTargetCycles = getCycleCounter(); //得到DWT计数值,作为任务 周期计数值



#if defined(USE_LATE_TASK_STATISTICS)

    nextTimingCycles = lastTargetCycles;

#endif



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

        schedulerResetTaskStatistics(taskId); //复位所有任务时间相关的运行状态数据

    }

(1)queueAdd()函数

位于当前文件中。

memmove函数,是位于<string.h>头文件中的C语言库函数,作用是移动内存块,或者说是复制内存数据到目标位置。

函数声明:void * memmove ( void * destination, const void * source, size_t num );

参数:

destination:指向要在其中复制内容的目标数组的指针,类型转换为 void* 类型的指针。

source:指向要复制的数据源的指针,类型转换为 const void* 类型的指针。

num :要复制的字节数。(size_t 是无符号整数类型)

返回值: 返回目的地。

(2)getTask()函数

参数是任务序号,返回值是任务序号tasks数组对应元素的指针,只在scheduler.c文件中进行了调用。功能很好理解,定义在\src\main\fc\tasks.c中。

(3)clockMicrosToCycles()函数

参数:us数

返回值:对应SCLK时钟周期数

原型:

uint32_t clockMicrosToCycles(uint32_t micros)

{

    return micros * usTicks;

}

usTicks是一个全局变量,在systemInit() -》cycleCounterInit();中初始化为以SCLK时钟频率(288MHz)运行1us需要的周期数。

(4)得到DWT计数

lastTargetCycles = getCycleCounter(); //得到DWT计数值,作为任务 周期计数值

寄存器:分为三类

外设寄存器(UART、SPI)、内核寄存器(core文件标号,属于Cotex-M3、Cotex-M4,如DWT、ITM、SCB等)、指令/架构寄存器(与二进制指令集联系,ARM架构,R0~R14、MSP、PC等)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值