目录
1. const int8_t staticPriority; 优先级属性
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等)