FreeRTOS学习记录(六):空闲任务与阻塞延时的实现

2023-05-16

2022-04-24

依据:[野火]《FreeRTOS内核实现与应用开发实战指南》


目录

一、实现空闲任务

1、实现空闲任务

定义空闲任务的任务控制块

创建空闲任务

二、阻塞延时

1、vTaskDelay()函数

2、修改 vTaskSwitchContext()函数

三、SysTick中断服务函数

1、xTaskIncrementTick()函数,更新系统时基。

四、SysTick初始化函数

五、实验


RTOS 中的延时叫阻塞延时,即任务需要延时的时候,任务会放弃 CPU 的使用权,CPU 可以去干其它的事情,当任务延时时间到,重新获取 CPU 使用权,任务继续运行,这样就充分地利用了 CPU 的资源,而不是干等着。

如果没有其它任务可以 运行,RTOS 都会为 CPU 创建一个空闲任务,这个时候 CPU 就运行空闲任务。

在 FreeRTOS 中,空闲任务是系统在【启动调度器】的时候创建的优先级最低的任务,空闲任 务主体主要是做一些系统内存的清理工作

一、实现空闲任务

为了简单起见,我们本章实现的空闲任务只是对一个全局变量进行计数。鉴于空闲任务的这种特性,在实际应用中,当系统进入空闲任务的时候,可在空闲任务中让单片机进入休眠或者低功耗等操作

1、实现空闲任务

目前我们在创建任务时使用的栈和 TCB 都使用的是静态的内存,即需要预先定义好内存,空闲任务也不例外。有关空闲任务的栈和 TCB 需要用到的内存空间均在 main.c 中定义。

//定义空闲任务的栈

#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 )     //(2)

StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE];     //(1)

(1)空闲任务的栈是一个定义好的数组,大小由 FreeRTOSConfig.h 中 定义的宏 configMINIMAL_STACK_SIZE 控制,默认为 128,单位为字,即 512个字节。

定义空闲任务的任务控制块

任务控制块是每一个任务必须的,空闲任务的的任务控制块我们在 main.c 中定义,是 一个全局变量

idle:懒惰的; 闲置的; 没有工作的; 闲散的

//定义空闲任务的任务控制块
TCB_t IdleTaskTCB;

创建空闲任务

当定义好空闲任务的栈,任务控制块后,就可以创建空闲任务。空闲任务在调度器启动函数 vTaskStartScheduler()中创建。

//创建空闲任务

extern TCB_t IdleTaskTCB;
void vApplicationGetIdleTaskMemory( TCB_t **ppxIdleTaskTCBBuffer,
                                    StackType_t **ppxIdleTaskStackBuffer,
                                    uint32_t *pulIdleTaskStackSize );
void vTaskStartScheduler( void )
{
    /*=======================创建空闲任务 start=======================*/ 
    TCB_t *pxIdleTaskTCBBuffer = NULL; /* 用于指向空闲任务控制块 */ 
    StackType_t *pxIdleTaskStackBuffer = NULL; /* 用于空闲任务栈起始地址 */ 
    uint32_t ulIdleTaskStackSize; 

    /* 获取空闲任务的内存:任务栈和任务 TCB */ //(1) 
    vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, 
                                   &pxIdleTaskStackBuffer, 
                                   &ulIdleTaskStackSize ); 

    /* 创建空闲任务 */ //(2) 
    xIdleTaskHandle = 
        xTaskCreateStatic( (TaskFunction_t)prvIdleTask, /* 任务入口 */ 
                           (char *)"IDLE", /* 任务名称,字符串形式 */ 
                           (uint32_t)ulIdleTaskStackSize , /* 任务栈大小,单位为字 */ 
                           (void *) NULL, /* 任务形参 */ 
                           (StackType_t *)pxIdleTaskStackBuffer, /* 任务栈起始地址 */ 
                           (TCB_t *)pxIdleTaskTCBBuffer ); /* 任务控制块 */ 

    /* 将任务添加到就绪列表 */ //(3) 
    vListInsertEnd( &( pxReadyTasksLists[0] ), 
                    &( ((TCB_t *)pxIdleTaskTCBBuffer)->xStateListItem ) ); 
    /*==========================创建空闲任务 end=====================*/ 

    /* 手动指定第一个运行的任务 */
    pxCurrentTCB = &Task1TCB;

    /* 启动调度器 */
    if ( xPortStartScheduler() != pdFALSE )
    {
        /* 调度器启动成功,则不会返回,即不会来到这里 */
    }
}

(1)获取空闲任务的内存 , 即将 pxIdleTaskTCBBuffer pxIdleTaskStackBuffer 这两个接下来要作为形参传到 xTaskCreateStatic()函数的指针分别指 向空闲任务的 TCB 和栈的起始地址,这个操作由函数 vApplicationGetIdleTaskMemory()来 实现,该函数需要用户自定义,目前我们在 main.c 中实现

vApplicationGetIdleTaskMemory()函数

void vApplicationGetIdleTaskMemory( TCB_t **ppxIdleTaskTCBBuffer,
                                    StackType_t **ppxIdleTaskStackBuffer,
                                    uint32_t *pulIdleTaskStackSize )
{
    *ppxIdleTaskTCBBuffer = &IdleTaskTCB;    //空闲任务控制块
    *ppxIdleTaskStackBuffer = IdleTaskStack;    //空闲任务堆栈
    *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;    //最小堆栈大小
}

(2)调用 xTaskCreateStatic()函数创建空闲任务。

(3)将空闲任务插入到就绪列表的开头。在以后会支持优先级, 空闲任务默认的优先级是最低的,即排在就绪列表的开头。

二、阻塞延时

1、vTaskDelay()函数

阻塞延时的阻塞是指任务调用该延时函数后,任务会被剥离 CPU 使用权,然后进入阻 塞状态,直到延时结束,任务重新获取 CPU 使用权才可以继续运行。在任务阻塞的这段时 间,CPU 可以去执行其它的任务,如果其它的任务也在延时状态,那么 CPU 就将运行空闲 任务。阻塞延时函数task.c 中定义。

 // vTaskDelay()函数

void vTaskDelay( const TickType_t xTicksToDelay )
{
    TCB_t *pxTCB = NULL;

    /* 获取当前任务的 TCB */
    pxTCB = pxCurrentTCB;     //(1)

    /* 设置延时时间 */
    pxTCB->xTicksToDelay = xTicksToDelay;     //(2)
 
    /* 任务切换 */
    taskYIELD();     //(3)
}

(1)获取当前任务的任务控制块。pxCurrentTCB 是一个在 task.c 定义的全局指针用于指向当前正在运行或者即将要运行的任务的任务控制块

(2)xTicksToDelay 是任务控制块的一个成员,用于记录任务需要延时的时间,单位为 SysTick 的中断周期。比如我们本书当中 SysTick 的中断周期为 10ms,调 用 vTaskDelay( 2 )则完成 2*10ms 的延时。

xTicksToDelay 定义

typedef struct tskTaskControlBlock
{
    volatile StackType_t *pxTopOfStack; /* 栈顶 */

    ListItem_t xStateListItem; /* 任务节点 */

    StackType_t *pxStack; /* 任务栈起始地址 */
                          /* 任务名称,字符串形式 */
    char pcTaskName[ configMAX_TASK_NAME_LEN ];
 
    //=======================================
    TickType_t xTicksToDelay; /* 用于延时 */         //延时延时延时延时延时延时延时**********
    //=======================================
} tskTCB;

(3)任务切换。调用 tashYIELD()会产生 PendSV中断,在 PendSV中断 服务函数中会调用上下文切换函数 vTaskSwitchContext(),该函数的作用是寻找最高优先级 的就绪任务,然后更新 pxCurrentTCB。上一章我们只有两个任务,则 pxCurrentTCB 不是 指向任务 1 就是指向任务 2,本章节开始我们多增加了一个空闲任务,则需要让 pxCurrentTCB 在这三个任务中切换,算法需要改变

2、修改 vTaskSwitchContext()函数

#if 0
    void vTaskSwitchContext( void )
    { /* 两个任务轮流切换 */
        if ( pxCurrentTCB == &Task1TCB )
        {
            pxCurrentTCB = &Task2TCB;
        }
        else
        {
            pxCurrentTCB = &Task1TCB;
        }
    }
#else

    void vTaskSwitchContext( void ) 
    { 
        /* 如果当前任务是空闲任务,那么就去尝试执行任务 1 或者任务 2, 
        看看他们的延时时间是否结束,如果任务的延时时间均没有到期, 
        那就返回继续执行空闲任务 */ 
        if ( pxCurrentTCB == &IdleTaskTCB )         //(1) 
        { 
            if (Task1TCB.xTicksToDelay == 0) 
            { 
                pxCurrentTCB =&Task1TCB; 
            } 
            else if (Task2TCB.xTicksToDelay == 0) 
            { 
                pxCurrentTCB =&Task2TCB; 
            } 
            else 
            { 
                return; /* 任务延时均没有到期则返回,继续执行空闲任务 */ 
            } 
        } 
        else /* 当前任务不是空闲任务则会执行到这里 */         //(2) 
        { 
            /*如果当前任务是任务 1 或者任务 2 的话,检查下另外一个任务, 
            如果另外的任务不在延时中,就切换到该任务 
            否则,判断下当前任务是否应该进入延时状态, 
            如果是的话,就切换到空闲任务。否则就不进行任何切换 */ 
            if (pxCurrentTCB == &Task1TCB) 
            { 
                if (Task2TCB.xTicksToDelay == 0) 
                { 
                    pxCurrentTCB =&Task2TCB; 
                } 
                else if (pxCurrentTCB->xTicksToDelay != 0) 
                { 
                    pxCurrentTCB = &IdleTaskTCB; 
                } 
                else 
                { 
                    return; /* 返回,不进行切换,因为两个任务都处于延时中 */ 
                } 
            } 
            else if (pxCurrentTCB == &Task2TCB) 
            { 
                if (Task1TCB.xTicksToDelay == 0) 
                { 
                    pxCurrentTCB =&Task1TCB; 
                } 
                else if (pxCurrentTCB->xTicksToDelay != 0) 
                { 
                    pxCurrentTCB = &IdleTaskTCB; 
                } 
                else 
                { 
                    return; /* 返回,不进行切换,因为两个任务都处于延时中     */ 
                } 
            } 
        } 
    } 

#endif

(1)如果当前任务是空闲任务,那么就去尝试执行任务 1 或者任务 2, 看看他们的延时时间是否结束,如果任务的延时时间均没有到期,那就返回继续执行空闲任务。

(2)如果当前任务是任务 1 或者任务 2 的话,检查下另外一个任务,如果另外的任务不在延时中,就切换到该任务。否则,判断下当前任务是否应该进入延时状 态,如果是的话,就切换到空闲任务,否则就不进行任何切换 。

三、SysTick中断服务函数

任务上下文切换函数 vTaskSwitchContext ()中,会判断每个任务的任务控制块中的 延时成员 xTicksToDelay 的值是否为 0,如果为 0就要将对应的任务就绪,如果不为 0 就继续延时。如果一个任务要延时,一开始 xTicksToDelay 肯定不为 0,当 xTicksToDelay 变为 0 的时候表示延时结束,那么 xTicksToDelay 是以什么周期在递减?在哪里递减?在 FreeRTOS 中,这个周期由 SysTick 中断提供,操作系统里面的最小的时间单位就是 SysTick 的中断周期,我们称之为一个 tick,SysTick 中断服务函数在 port.c 中实现

//SysTick 中断服务函数

void xPortSysTickHandler( void )
{
    /* 关中断 */
    vPortRaiseBASEPRI();     //(1)

    /* 更新系统时基 */
    xTaskIncrementTick();     //(2)

    /* 开中断 */
    vPortClearBASEPRIFromISR();     //(3)
}

 (2)更新系统时基,该函数在 task.c 中定义

1、xTaskIncrementTick()函数,更新系统时基。

void xTaskIncrementTick( void )
{
    TCB_t *pxTCB = NULL;
    BaseType_t i = 0;

    /* 更新系统时基计数器 xTickCount,xTickCount 是一个在 port.c 中定义的全局变量 */    //(1)
    const TickType_t xConstTickCount = xTickCount + 1;
    xTickCount = xConstTickCount;

 
    /* 扫描就绪列表中所有任务的 xTicksToDelay,如果不为 0,则减 1 */    //(2)
    for (i=0; i<configMAX_PRIORITIES; i++)
    {
        pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &pxReadyTasksLists[i] ) );
        if (pxTCB->xTicksToDelay > 0)
        {
            pxTCB->xTicksToDelay --;
        }
    }

    /* 任务切换 */    //(3)
    portYIELD();
}

(1)更新系统时基计数器 xTickCount,加一操作。xTickCount 是一个在 port.c 中定义的全局变量,在函数任务启动调度器vTaskStartScheduler()中调用启动调度器 xPortStartScheduler()函数前初始化。

(2)扫描就绪列表中所有任务的 xTicksToDelay,如果不为 0,则减 1。

(3)执行一次任务切换。

四、SysTick初始化函数

SysTick 的中断服务函数要想被顺利执行,则 SysTick 必须先初始化。SysTick 初始化 函数在 port.c 中定义

// vPortSetupTimerInterrupt()函数

/* SysTick 控制寄存器 */     //(1)
#define portNVIC_SYSTICK_CTRL_REG (*((volatile uint32_t *) 0xe000e010 ))
/* SysTick 重装载寄存器寄存器 */
#define portNVIC_SYSTICK_LOAD_REG (*((volatile uint32_t *) 0xe000e014 ))

/* SysTick 时钟源选择 */
#ifndef configSYSTICK_CLOCK_HZ 
    #define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ 
    /* 确保 SysTick 的时钟与内核时钟一致 */ 
    #define portNVIC_SYSTICK_CLK_BIT ( 1UL << 2UL ) 
#else 
    #define portNVIC_SYSTICK_CLK_BIT ( 0 ) 
#endif 

#define portNVIC_SYSTICK_INT_BIT     ( 1UL << 1UL )
#define portNVIC_SYSTICK_ENABLE_BIT  ( 1UL << 0UL )


void vPortSetupTimerInterrupt( void )     //(2)
{
    /* 设置重装载寄存器的值 */     //(3)
    portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;

    /* 设置系统定时器的时钟等于内核时钟     //(4)
       使能 SysTick 定时器中断
       使能 SysTick 定时器 */
    portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT |
                                  portNVIC_SYSTICK_INT_BIT |
                                  portNVIC_SYSTICK_ENABLE_BIT );
}

(1)配置 SysTick 需要用到的寄存器宏定义,在 port.c 中实现。

(2)SysTick 初始化函数 vPortSetupTimerInterrupt() , 在 xPortStartScheduler()中被调用

//xPortStartScheduler() 中调用  vPortSetupTimerInterrupt()


BaseType_t xPortStartScheduler( void )
{
    /* 配置 PendSV 和 SysTick 的中断优先级为最低 */
    portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
    portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI; 

    /* 初始化 SysTick */ 
    vPortSetupTimerInterrupt(); 

    /* 启动第一个任务,不再返回 */
    prvStartFirstTask();

    /* 不应该运行到这里 */
    return 0;
}

(3)设置重装载寄存器的值,决定 SysTick 的中断周期。从代码清单 (1)可以知道:如果没有定义 configSYSTICK_CLOCK_HZ 那么 configSYSTICK_CLOCK_HZ 就等于configCPU_CLOCK_HZconfigSYSTICK_CLOCK_HZ 确实没有定义 ,则 configSYSTICK_CLOCK_HZ 由在 FreeRTOSConfig.h 中定义的 configCPU_CLOCK_HZ 决定,同时 configTICK_RATE_HZ 也 在 FreeRTOSConfig.h 中定义

//configCPU_CLOCK_HZ 与 configTICK_RATE_HZ 宏定义

1 #define configCPU_CLOCK_HZ (( unsigned long ) 25000000)     //(1)
2 #define configTICK_RATE_HZ (( TickType_t ) 100)     //(2)

        (3)、(1)系统时钟的大小, 因为目前是软件仿真,需要配置成 与 system_ARMCM3.c(system_ARMCM4.c 或 system_ARMCM7.c)文件中的 SYSTEM_CLOCK 的一样,即等于 25M。如果有具体的硬件,则配置成与硬件的系统时钟一样。

        (3)、(2)SysTick 每秒中断多少次,目前配置为 100,即每 10ms 中断一次。

(4)设置系统定时器的时钟等于内核时钟,使能 SysTick 定时器中断,使能 SysTick 定时器。

五、实验

/*
 *************************************************************************
 * 包含的头文件
 *************************************************************************
 */
#include "FreeRTOS.h"
#include "task.h"
 
/*
 *************************************************************************
 * 全局变量
 *************************************************************************
 */
portCHAR flag1;
portCHAR flag2;

extern List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
 
 
/*
 *************************************************************************
 * 任务控制块 & STACK
 *************************************************************************
 */
TaskHandle_t Task1_Handle;
#define TASK1_STACK_SIZE 128
StackType_t Task1Stack[TASK1_STACK_SIZE];
TCB_t Task1TCB;
 
TaskHandle_t Task2_Handle;
#define TASK2_STACK_SIZE 128
StackType_t Task2Stack[TASK2_STACK_SIZE];
TCB_t Task2TCB;
 
 
/*
 *************************************************************************
 * 函数声明
 *************************************************************************
 */
void delay (uint32_t count);
void Task1_Entry( void *p_arg );
void Task2_Entry( void *p_arg );
 
/*
 ************************************************************************
 * main 函数
 ************************************************************************
 */
 
int main(void)
{
    /* 硬件初始化 */
    /* 将硬件相关的初始化放在这里,如果是软件仿真则没有相关初始化代码 */

    /* 初始化与任务相关的列表,如就绪列表 */
    prvInitialiseTaskLists();

    /* 创建任务 */
    Task1_Handle =
        xTaskCreateStatic( (TaskFunction_t)Task1_Entry, /* 任务入口 */
                           (char *)"Task1", /* 任务名称,字符串形式 */
                           (uint32_t)TASK1_STACK_SIZE , /* 任务栈大小,单位为字 */
                           (void *) NULL, /* 任务形参 */
                           (StackType_t *)Task1Stack, /* 任务栈起始地址 */
                           (TCB_t *)&Task1TCB ); /* 任务控制块 */
    /* 将任务添加到就绪列表 */
    vListInsertEnd( &( pxReadyTasksLists[1] ),
                    &( ((TCB_t *)(&Task1TCB))->xStateListItem ) );

    Task2_Handle =
        xTaskCreateStatic( (TaskFunction_t)Task2_Entry, /* 任务入口 */
                           (char *)"Task2", /* 任务名称,字符串形式 */
                           (uint32_t)TASK2_STACK_SIZE , /* 任务栈大小,单位为字 */
                           (void *) NULL, /* 任务形参 */
                           (StackType_t *)Task2Stack, /* 任务栈起始地址 */
                           (TCB_t *)&Task2TCB ); /* 任务控制块 */
    /* 将任务添加到就绪列表 */
    vListInsertEnd( &( pxReadyTasksLists[2] ),
                    &( ((TCB_t *)(&Task2TCB))->xStateListItem ) );

    /* 启动调度器,开始多任务调度,启动成功则不返回 */
    vTaskStartScheduler();

    for (;;)
    {
        /* 系统启动成功不会到达这里 */
    }
}
 
/*
 *************************************************************************
 * 函数实现
 *************************************************************************
 */
/* 软件延时 */
void delay (uint32_t count)
{
    for (; count!=0; count--);
}
/* 任务 1 */
void Task1_Entry( void *p_arg )
{
    for ( ;; )
    {
#if 0
        flag1 = 1;
        delay( 100 );
        flag1 = 0;
        delay( 100 );

        /* 任务切换,这里是手动切换 */
        portYIELD();
#else
        flag1 = 1; 
        vTaskDelay( 2 );         //(1) 
        flag1 = 0; 
        vTaskDelay( 2 ); 
#endif
    }
}

/* 任务 2 */
void Task2_Entry( void *p_arg )
{
    for ( ;; )
    {
#if 0
        flag2 = 1;
        delay( 100 );
        flag2 = 0;
        delay( 100 );

        /* 任务切换,这里是手动切换 */
        portYIELD();
#else
        flag2 = 1; 
        vTaskDelay( 2 );     //(2) 
        flag2 = 0; 
        vTaskDelay( 2 ); 
#endif
    }
}
 
/* 获取空闲任务的内存 */ 
StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE];     //(3) 
TCB_t IdleTaskTCB; 
void vApplicationGetIdleTaskMemory( TCB_t **ppxIdleTaskTCBBuffer, 
                                    StackType_t **ppxIdleTaskStackBuffer, 
                                    uint32_t *pulIdleTaskStackSize ) 
{ 
    *ppxIdleTaskTCBBuffer=&IdleTaskTCB; 
    *ppxIdleTaskStackBuffer=IdleTaskStack; 
    *pulIdleTaskStackSize=configMINIMAL_STACK_SIZE; 
}

(1)、(2):延时函数均由原来的软件延时替代为阻塞延时,延时时间均 为 2 个 SysTick 中断周期,即 20ms。

(3)定义空闲任务的栈和 TCB

每天加油、好好学习、多看多学多想!!!!加油!!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FreeRTOS学习记录(六):空闲任务与阻塞延时的实现 的相关文章

  • 将视觉信息转换为mavros

    https github com thien94 vision to mavros blob master src vision to mavros cpp rosmsg show mavros msgs LandingTarget MAV
  • 分蛋糕+中间数

    问题描述 小明今天生日 xff0c 他有n块蛋糕要分给朋友们吃 xff0c 这n块蛋糕 xff08 编号为1到n xff09 的重量分别为a1 a2 an 小明想分给每个朋友至少重量为k的蛋糕 小明的朋友们已经排好队准备领蛋糕 xff0c
  • 基于键盘与扬声器的电子琴设计

    1 功能需求 开发一个基于键盘和主机扬声器 xff08 小喇叭 xff09 的简易电子琴工具 xff0c 同时它也可以自动的演奏指定的简谱文件 通过调用计算机系统的API接口 xff0c 导入kernel32 dll xff0c 调用相关的
  • 最大波动+数位之和

    问题描述 小明正在利用股票的波动程度来研究股票 小明拿到了一只股票每天收盘时的价格 xff0c 他想知道 xff0c 这只股票连续几天的最大波动值是多少 xff0c 即在这几天中某天收盘价格与前一天收盘价格之差的绝对值最大是多少 输入格式
  • 折点计数

    问题描述 给定n个整数表示一个商店连续n天的销售量 如果某天之前销售量在增长 xff0c 而后一天销售量减少 xff0c 则称这一天为折点 xff0c 反过来如果之前销售量减少而后一天销售量增长 xff0c 也称这一天为折点 其他的天都不是
  • 动态规划+贪心算法实现背包问题

    动规背包问题实现 xff1a import java util Scanner public class PackDynamic public static void main String args TODO Auto generated
  • 图像旋转

    问题描述 旋转是图像处理的基本操作 xff0c 在这个问题中 xff0c 你需要将一个图像逆时针旋转90度 计算机中的图像表示可以用一个矩阵来表示 xff0c 为了旋转一个图像 xff0c 只需要将对应的矩阵旋转即可 输入格式 输入的第一行
  • 注册界面表单

    代码 xff1a lt DOCTYPE HTML gt lt html gt lt head gt lt meta charset 61 34 UTF 8 34 gt lt meta name 61 34 Generator 34 cont
  • 关于表单验证错误提示的几种方式

    一 用alert 弹出提示框 xff08 不提倡 xff09 通过alert弹出提示框 xff0c 但输入框多的话会不断弹出提示框 xff0c 每次都要点确定 xff0c 很麻烦 二 利用H5新增的required属性 required 属
  • html+css+js手写练习-仿CCF注册和登录页面

    直接贴代码 xff1a lt DOCTYPE html gt lt html gt lt head gt lt meta charset 61 34 utf 8 34 gt lt title gt 中国计算机学会 注册 lt title g
  • Android学习之点击按钮跳转至另一个Activity并传值和设置返回逻辑父屏幕

    首先新建一个Activity 1 在activity main的layout布局文件中添加一个按钮 xff0c 一个EditText并简单设置样式 我是这样写的 lt xml version 61 34 1 0 34 encoding 61
  • mission planner日志分析

    通过USB连接pixhawk xff0c 打missionplanner 选择需要的日志下载 xff0c 下载很慢 xff0c 慢慢等 https ardupilot org dev docs common diagnosing probl
  • Android学习之SD卡操作

    1 权限声明 对于SD卡的读写 xff0c 需要申请对应的权限 xff0c 即在主配置文件中添加对应的权限请求 lt uses permission android name 61 34 android permission WRITE E
  • Maven利用JBoss创建hibernate核心配置文件

    1 在Help的Eclipse Marketplace中搜索JBoss xff0c 选择下载安装 安装完毕后重启eclipse xff0c 如果重启后maven项目报错 xff0c 很大可能是在下载过程中有jar包受损 xff0c 出现Fa
  • 汇编练习

    1 将下面C语言程序的代码片段转换为功能等价的汇编语言代码片段 xff1b 编写一完整的汇编语言程序验证转换的正确性 xff0c 其中sign与sinteger均为双字变量 if sinteger 61 61 0 sign 61 0 els
  • 教务管理系统

    基于Metronic框架完成的一个课设作业 上方为部分功能截图 下载链接 xff1a https download csdn net download qq 41573860 11249884
  • 关于XTDrone中PX4 1.13安装

    在进行git submodule update init recursive这一步时 xff0c 很难git clone成功 xff0c 会出现 xff0c fatal 无法访问 39 https ghproxy com https git
  • 海康摄像头http抓图

    老版本的摄像头使用海康的协议http user password 64 192 168 1 64 ISAPI Streaming channels 33 picture 就可以取得图片 然后新型号的摄像头http需要Digest autho
  • 深度学习YOLO v3原理及实践---ROS算法入门学习

    文章目录
  • Opencv进行图像处理基础模板

    文章目录 前言一 如何写出基础的卷积模板二 关于Opencv的一些认知1 如何利用opencv访问图像中某一点的像素2 如何利用opencv访问图像中某一区域的像素3 边界像素处理 三 C 43 43 代码 前言 在opencv中如果我们想

随机推荐