首页 > 其他分享 >FreeRTOS例程开发

FreeRTOS例程开发

时间:2023-09-20 09:44:23浏览次数:43  
标签:__ FreeRTOS 例程 void ----------------------------------------------------------- 

环境配置

  1. 下载官方源码 https://www.freertos.org/

image

找到这个,他就是visual studio示例demo,我们主要在这个的基础上修改

  1. 下载visio studio

https://visualstudio.microsoft.com/zh-hans/

安装时不需要额外任何插件,打开项目会提示你安装c/c++,这样安的快

  1. 打开第一步圈的那个WIN32.sln

image

目录结构能看出,可以写多个demo,最后在main.c里调用即可,下面给出本人翻译过的官方示例Blinky示例

  1. 例程入门级详解

这个demo主要讲的是分别通过任务和定时器向队列收发信息,添加了个键盘按键重置定时器、保存log的功能

main.c
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>

#include <intrin.h>

/* FreeRTOS kernel includes. */
#include "FreeRTOS.h"
#include "task.h"

/* FreeRTOS+Trace includes. */
#include "trcRecorder.h"


#define mainREGION_1_SIZE                     8201
#define mainREGION_2_SIZE                     23905
#define mainREGION_3_SIZE                     16807

#define mainNO_KEY_PRESS_VALUE                -1
#define mainOUTPUT_TRACE_KEY                  't'
#define mainINTERRUPT_NUMBER_KEYBOARD         3

//保存dump的文件名
#define mainTRACE_FILE_NAME                   "Trace.dump"


extern void main_blinky(void);
extern void main_full(void);
extern void main_Task(void);

extern void vFullDemoTickHookFunction(void);
extern void vFullDemoIdleFunction(void);

//用于初始化 FreeRTOS 的堆内存管理器,通常不需要写,自动就调用了
static void prvInitialiseHeap(void);

//内存分配失败时调用,打印错误信息或重启系统等
void vApplicationMallocFailedHook(void);

//空闲任务运行时调用
void vApplicationIdleHook(void);

//堆栈溢出时调用,打印错误信息等
void vApplicationStackOverflowHook(TaskHandle_t pxTask,
    char* pcTaskName);

//Tick中断时调用,比如统计任务运行时间等
void vApplicationTickHook(void);

//创建空闲任务前调用,用于自定义分配内存
void vApplicationGetIdleTaskMemory(StaticTask_t** ppxIdleTaskTCBBuffer,
    StackType_t** ppxIdleTaskStackBuffer,
    uint32_t* pulIdleTaskStackSize);

//定时器任务前调用,自定义分配内存
void vApplicationGetTimerTaskMemory(StaticTask_t** ppxTimerTaskTCBBuffer,
    StackType_t** ppxTimerTaskStackBuffer,
    uint32_t* pulTimerTaskStackSize);


//停止后保存trace文件
static void prvSaveTraceFile(void);

//创建一个 Windows 线程来处理键盘输入
static DWORD WINAPI prvWindowsKeyboardInputThread(void* pvParam);

//接收键盘输入时的中断处理程序。
static uint32_t prvKeyboardInterruptHandler(void);


//blinky的中断程序
extern void vBlinkyKeyboardInterruptHandler(int xKeyPressed);

/*-----------------------------------------------------------*/

//configSUPPORT_STATIC_ALLOCATION = 1 可以通过回调函数来手动分配内存给空闲任务和定时器任务,简单来说就是用于测试demo

StackType_t uxTimerTaskStack[configTIMER_TASK_STACK_DEPTH];


//获取键盘输入的线程 的句柄
static HANDLE xWindowsKeyboardInputThreadHandle = NULL;

//存储最后一个未被处理的按键,空闲时处理
static int xKeyPressed = mainNO_KEY_PRESS_VALUE;

/*-----------------------------------------------------------*/

int main(void)
{
    //初始化堆内存管理器
    prvInitialiseHeap();

    //初始化Tracer,可选择不用Tracer
    configASSERT(xTraceInitialize() == TRC_SUCCESS);

    //开启Tracer,configASSERT()调用了,就会保存Tracer
    printf(
        "The trace will be dumped to the file \"%s\" whenever a call to configASSERT()\r\n"
        "fails or the \'%c\' key is pressed.\r\n",
        mainTRACE_FILE_NAME, mainOUTPUT_TRACE_KEY);

    configASSERT(xTraceEnable(TRC_START) == TRC_SUCCESS);

    //设置键盘输入的中断处理程序。
    vPortSetInterruptHandler(mainINTERRUPT_NUMBER_KEYBOARD, prvKeyboardInterruptHandler);

    // 开处理键盘中断的线程
    xWindowsKeyboardInputThreadHandle = CreateThread(
        NULL,                          //指向线程安全属性
        0,                             //初始化线程堆栈大小,字节为单位
        prvWindowsKeyboardInputThread, //线程函数指针
        NULL,                          // 新线程参数
        0,                             // 标志
        NULL);

    //将未被 FreeRTOS 任务使用的核心分配给 Windows 线程
    SetThreadAffinityMask(xWindowsKeyboardInputThreadHandle, ~0x01u);

    main_Task();

    return 0;
}
/*-----------------------------------------------------------*/

void vApplicationMallocFailedHook(void)
{

    // configUSE_MALLOC_FAILED_HOOK = 1 开启钩子函数 作用是内存分配pvPortMalloc失败进行错误处理
    vAssertCalled(__LINE__, __FILE__);
}
/*-----------------------------------------------------------*/

void vApplicationIdleHook(void)
{


}

/*-----------------------------------------------------------*/

void vApplicationStackOverflowHook(TaskHandle_t pxTask,
    char* pcTaskName)
{
    (void)pcTaskName;
    (void)pxTask;

    //将configCHECK_FOR_STACK_OVERFLOW定义为1或2,则会执行运行时堆栈溢出检查。如果检测到堆栈溢出,将调用此钩子函数
    vAssertCalled(__LINE__, __FILE__);
}
/*-----------------------------------------------------------*/

void vApplicationTickHook(void)
{
    //将configUSE_TICK_HOOK设置为1,则每次tick中断都会调用此函数。可以在此处自定义代码,注意不要阻塞

}
/*-----------------------------------------------------------*/

void vApplicationDaemonTaskStartupHook(void)
{
    //仅在守护任务开始执行时调用一次的钩子函数(有时称为定时器任务)
}
/*-----------------------------------------------------------*/

void vAssertCalled(unsigned long ulLine,
    const char* const pcFileName)
{
    static BaseType_t xPrinted = pdFALSE;
    volatile uint32_t ulSetToNonZeroInDebuggerToContinue = 0;

    // configASSERT() 断言失败调用
    (void)ulLine;
    (void)pcFileName;

    taskENTER_CRITICAL();
    {
        printf("ASSERT! Line %ld, file %s, GetLastError() %ld\r\n", ulLine, pcFileName, GetLastError());

        //停止跟踪记录并保存跟踪
        (void)xTraceDisable();
        prvSaveTraceFile();

        //如果正在调试,则会导致调试器断点
        __debugbreak();

        //将ulSetToNonZeroInDebuggerToContinue设置为一个非零值,
        //可以使程序在断言失败时暂停执行,以便使用调试器来查看当前的程序状态和变量值,定位和解决问题。
        while (ulSetToNonZeroInDebuggerToContinue == 0)
        {
            __asm {
                NOP
            };
            __asm {
                NOP
            };
        }

        // 重启Tracer记录
        (void)xTraceEnable(TRC_START);
    }
    taskEXIT_CRITICAL();
}
/*-----------------------------------------------------------*/

static void prvSaveTraceFile(void)
{
    FILE* pxOutputFile;

    fopen_s(&pxOutputFile, mainTRACE_FILE_NAME, "wb");

    if (pxOutputFile != NULL)
    {
        fwrite(RecorderDataPtr, sizeof(RecorderDataType), 1, pxOutputFile);
        fclose(pxOutputFile);
        printf("\r\nTrace output saved to %s\r\n\r\n", mainTRACE_FILE_NAME);
    }
    else
    {
        printf("\r\nFailed to create trace dump file\r\n\r\n");
    }
}
/*-----------------------------------------------------------*/

static void prvInitialiseHeap(void)
{
    //heap_5是一个具有灵活性和可配置性的堆实现,
    //为了简化示例,我们创建了一个大的数组,并在数组中使用偏移量来确定每个堆区域的位置。
    //这些堆区域之间有间隔和杂乱的对齐,这样做是为了模拟实际场景中可能出现的情况。通过使用这些堆区域,我们可以测试和验证堆的分配和释放操作。
    static uint8_t ucHeap[configTOTAL_HEAP_SIZE];
    volatile uint32_t ulAdditionalOffset = 19; /* Just to prevent 'condition is always true' warnings in configASSERT(). */
    const HeapRegion_t xHeapRegions[] =
    {
        //具有伪偏移量的起始地址
        { ucHeap + 1,                                          mainREGION_1_SIZE },
        { ucHeap + 15 + mainREGION_1_SIZE,                     mainREGION_2_SIZE },
        { ucHeap + 19 + mainREGION_1_SIZE + mainREGION_2_SIZE, mainREGION_3_SIZE },
        { NULL,                                                0                 }
    };

    // 检查定义的尺寸和偏移是否实际适合
    configASSERT((ulAdditionalOffset + mainREGION_1_SIZE + mainREGION_2_SIZE + mainREGION_3_SIZE) < configTOTAL_HEAP_SIZE);

    // 未定义configASSERT()时阻止编译器警告
    (void)ulAdditionalOffset;

    vPortDefineHeapRegions(xHeapRegions);
}
/*-----------------------------------------------------------*/

// configUSE_STATIC_ALLOCATION = 1, 必须提供vApplicationGetIdleTaskMemory()的实现

void vApplicationGetIdleTaskMemory(StaticTask_t** ppxIdleTaskTCBBuffer,
    StackType_t** ppxIdleTaskStackBuffer,
    uint32_t* pulIdleTaskStackSize)
{

    //如果在空闲任务函数中声明的缓冲区变量没有被声明为静态变量,而是被分配在栈上,
    //那么在该函数退出后,栈上的变量会被释放,因此这些缓冲区变量将不再存在,可能会导致错误。
    static StaticTask_t xIdleTaskTCB;
    static StackType_t uxIdleTaskStack[configMINIMAL_STACK_SIZE];

    //该函数会返回一个指向静态任务结构体(StaticTask_t)的指针,该结构体将用于存储空闲任务(Idle task)的状态。
    *ppxIdleTaskTCBBuffer = &xIdleTaskTCB;

    //该函数会返回一个数组,这个数组将作为空闲任务(Idle task)的栈使用。
    *ppxIdleTaskStackBuffer = uxIdleTaskStack;

    //空闲任务栈的大小是通过指针*ppxIdleTaskStackBuffer所指向的数组来确定的。这个数组的类型是StackType_t类型。
    //这个数组的大小是以StackType_t类型的单词为单位来指定的,而不是以字节为单位。
    //configMINIMAL_STACK_SIZE宏定义,它是指栈中最小的可接受空间大小,以确保任务能够正常运行。
    *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
/*-----------------------------------------------------------*/

// configUSE_STATIC_ALLOCATION = 1 静态分配任务内存
// configUSE_TIMERS are both set to 1, 开启定时器
//必须要实现  vApplicationGetTimerTaskMemory() 这个函数功能是为定时器任务分配内存

void vApplicationGetTimerTaskMemory(StaticTask_t** ppxTimerTaskTCBBuffer,
    StackType_t** ppxTimerTaskStackBuffer,
    uint32_t* pulTimerTaskStackSize)
{

    // 如果在vApplicationGetTimerTaskMemory()函数内声明提供给Timer任务的缓冲区,则必须将它们声明为静态变量
    static StaticTask_t xTimerTaskTCB;

    //传递一个指向StaticTask_t结构的指针,其中Timer任务的状态将被存储
    *ppxTimerTaskTCBBuffer = &xTimerTaskTCB;

    // 传递将用作Timer任务堆栈的数组
    *ppxTimerTaskStackBuffer = uxTimerTaskStack;

    //传递指向*ppxTimerTaskStackBuffer的数组大小。由于数组必须是StackType_t类型,因此configMINIMAL_STACK_SIZE 单位是字而不是字节
    *pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}
/*-----------------------------------------------------------*/

//接收键盘输入时的中断处理程序。
static uint32_t prvKeyboardInterruptHandler(void)
{
    /* 处理键盘输入 */
    switch (xKeyPressed)
    {
    case mainNO_KEY_PRESS_VALUE:
        break;
    case mainOUTPUT_TRACE_KEY:
        //通过进入临界区,可以防止在FreeRTOS模拟器内部调用Windows系统调用时发生死锁或错误。这样可以确保保存跟踪文件的操作能够顺利进行
        portENTER_CRITICAL();
        {
            (void)xTraceDisable();
            prvSaveTraceFile();
            (void)xTraceEnable(TRC_START);
        }
        portEXIT_CRITICAL();
        break;
    default:

        /* 调用中断处理程序. */
        vBlinkyKeyboardInterruptHandler(xKeyPressed);

           break;
    }

    //此中断不需要上下文切换,因此返回pdFALSE
    return pdFALSE;
}

/*-----------------------------------------------------------*/
//从Windows线程函数捕获键盘输入并使用整数将其传递到FreeRTOS模拟器
static DWORD WINAPI prvWindowsKeyboardInputThread(void* pvParam)
{
    (void)pvParam;

    for (; ; )
    {
        // 阻塞并等待键盘输入.
        xKeyPressed = _getch();

        //通知FreeRTOS模拟器存在键盘中断。这将触发prvKeyboardInterruptHandler
        vPortGenerateSimulatedInterrupt(mainINTERRUPT_NUMBER_KEYBOARD);
    }

    //不应该到达这里,所以返回负退出状态
    return -1;
}

/*-----------------------------------------------------------*/

// 跟踪记录器使用以下代码进行计时
static uint32_t ulEntryTime = 0;

void vTraceTimerReset(void)
{
    ulEntryTime = xTaskGetTickCount();
}

uint32_t uiTraceTimerGetFrequency(void)
{
    return configTICK_RATE_HZ;
}

uint32_t uiTraceTimerGetValue(void)
{
    return(xTaskGetTickCount() - ulEntryTime);
}

Blink.c
/* Standard includes. */
#include <stdio.h>
#include <conio.h>

/* Kernel includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
#include "semphr.h"

// 任务优先级
#define mainQUEUE_RECEIVE_TASK_PRIORITY		( tskIDLE_PRIORITY + 2 )
#define	mainQUEUE_SEND_TASK_PRIORITY		( tskIDLE_PRIORITY + 1 )

// 发送频率/ms
#define mainTASK_SEND_FREQUENCY_MS			pdMS_TO_TICKS( 200UL )
#define mainTIMER_SEND_FREQUENCY_MS			pdMS_TO_TICKS( 2000UL )

// 队列最大长度
#define mainQUEUE_LENGTH					( 2 )

// 任务/定时器发送给队列的数据
#define mainVALUE_SENT_FROM_TASK			( 100UL )
#define mainVALUE_SENT_FROM_TIMER			( 200UL )

// 键盘输入
#define mainNO_KEY_PRESS_VALUE              ( -1 )
#define mainRESET_TIMER_KEY                 ( 'r' )

/*-----------------------------------------------------------*/

// 任务句柄,分别是向队列收发的任务
static void prvQueueReceiveTask( void *pvParameters );
static void prvQueueSendTask( void *pvParameters );

// 定时器的回调函数
static void prvQueueSendTimerCallback( TimerHandle_t xTimerHandle );

/*-----------------------------------------------------------*/

// 队列句柄
static QueueHandle_t xQueue = NULL;

// 定时器句柄
static TimerHandle_t xTimer = NULL;

/*-----------------------------------------------------------*/

void main_blinky( void )
{
const TickType_t xTimerPeriod = mainTIMER_SEND_FREQUENCY_MS;

    printf( "\r\nStarting the blinky demo. Press \'%c\' to reset the software timer used in this demo.\r\n\r\n", mainRESET_TIMER_KEY );

	// 创建队列
	xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof( uint32_t ) );

	if( xQueue != NULL )
	{
		// 创建任务
		xTaskCreate( prvQueueReceiveTask,			/* The function that implements the task. */
					"Rx", 							/* The text name assigned to the task - for debug only as it is not used by the kernel. */
					configMINIMAL_STACK_SIZE, 		/* The size of the stack to allocate to the task. */
					NULL, 							/* The parameter passed to the task - not used in this simple case. */
					mainQUEUE_RECEIVE_TASK_PRIORITY,/* The priority assigned to the task. */
					NULL );							/* The task handle is not required, so NULL is passed. */

		xTaskCreate( prvQueueSendTask, "TX", configMINIMAL_STACK_SIZE, NULL, mainQUEUE_SEND_TASK_PRIORITY, NULL );

		// 创建定时器
		xTimer = xTimerCreate( "Timer",				/* The text name assigned to the software timer - for debug only as it is not used by the kernel. */
								xTimerPeriod,		/* The period of the software timer in ticks. */
								pdTRUE,			    /* xAutoReload is set to pdTRUE, so this timer goes off periodically with a period of xTimerPeriod ticks. */
								NULL,				/* The timer's ID is not used. */
								prvQueueSendTimerCallback );/* The function executed when the timer expires. */
        //启动定时器
		xTimerStart( xTimer, 0 ); 

		// 开启调度器
		vTaskStartScheduler();
	}

	for( ;; );
}
/*-----------------------------------------------------------*/

static void prvQueueSendTask( void *pvParameters )
{
TickType_t xNextWakeTime;
const TickType_t xBlockTime = mainTASK_SEND_FREQUENCY_MS;
const uint32_t ulValueToSend = mainVALUE_SENT_FROM_TASK;

	// 防警告“入参没用到”的,实际上没用
	( void ) pvParameters;

	// 初始化为当前的tick数
	xNextWakeTime = xTaskGetTickCount();

	for( ;; )
	{
		// 延时Block次,然后next会更新为 next + block
		vTaskDelayUntil( &xNextWakeTime, xBlockTime );

        // 向队列发送数据,数据为100UL
		xQueueSend( xQueue, &ulValueToSend, 0U );
	}
}
/*-----------------------------------------------------------*/

static void prvQueueSendTimerCallback( TimerHandle_t xTimerHandle )
{
const uint32_t ulValueToSend = mainVALUE_SENT_FROM_TIMER;

	( void ) xTimerHandle;

	// 只有定时器到期(设置为2s)才会执行的回调函数,也会向队列发送数据,数据为200UL
	xQueueSend( xQueue, &ulValueToSend, 0U );
}
/*-----------------------------------------------------------*/

static void prvQueueReceiveTask( void *pvParameters )
{
uint32_t ulReceivedValue;

	( void ) pvParameters;

	for( ;; )
	{
		// 从队列收数据,队列长度为2
		xQueueReceive( xQueue, &ulReceivedValue, portMAX_DELAY );

        //进入临界区,确保printf可以执行完毕,因为printf消耗很多堆栈资源
        taskENTER_CRITICAL();
        {
            if (ulReceivedValue == mainVALUE_SENT_FROM_TASK)
            {
                printf("Message received from task - idle time %llu%%\r\n", ulTaskGetIdleRunTimePercent());
            }
            else if (ulReceivedValue == mainVALUE_SENT_FROM_TIMER)
            {
                printf("Message received from software timer\r\n");
            }
            else
            {
                printf("Unexpected message\r\n");
            }
        }
        taskEXIT_CRITICAL();
	}
}
/*-----------------------------------------------------------*/

/* 被它调用:prvKeyboardInterruptSimulatorTask(),定义在 main.c. */
void vBlinkyKeyboardInterruptHandler( int xKeyPressed )
{
    // 处理输入
    switch ( xKeyPressed )
    {
    case mainRESET_TIMER_KEY:

        if ( xTimer != NULL )
        {
            //进入临界区,只允许printf这一个线程,防止死锁
            taskENTER_CRITICAL();
            {
                printf("\r\nResetting software timer.\r\n\r\n");
            }
            taskEXIT_CRITICAL();

            // 重置定时器
            xTimerReset( xTimer, portMAX_DELAY );
        }

        break;

    default:
        break;
    }
}

要新加demo的时候,需要新建一个c文件,在main.c中extern进去,然后再main函数中调用

再次复习官方文档

Assert断言函数

建议在main函数中添加,用于在开发过程中进行断言检查。通过在代码中使用断言,我们可以在开发和调试过程中快速发现和定位潜在的问题。

演示demo中定义在了FreeRTOSconfig.h中

#define configASSERT( x ) if( ( x ) == 0 ) vAssertCalled( __LINE__, __FILE__ )

断言函数定义位于main.c

void vAssertCalled(unsigned long ulLine,
    const char* const pcFileName)
{
    static BaseType_t xPrinted = pdFALSE;
    volatile uint32_t ulSetToNonZeroInDebuggerToContinue = 0;

    // configASSERT() 断言失败调用
    (void)ulLine;
    (void)pcFileName;

    taskENTER_CRITICAL();
    {
        printf("ASSERT! Line %ld, file %s, GetLastError() %ld\r\n", ulLine, pcFileName, GetLastError());

        //停止跟踪记录并保存跟踪
        (void)xTraceDisable();
        prvSaveTraceFile();

        //如果正在调试,则会导致调试器断点
        __debugbreak();

        //将ulSetToNonZeroInDebuggerToContinue设置为一个非零值,
        //可以使程序在断言失败时暂停执行,以便使用调试器来查看当前的程序状态和变量值,定位和解决问题。
        while (ulSetToNonZeroInDebuggerToContinue == 0)
        {
            __asm {
                NOP
            };
            __asm {
                NOP
            };
        }

        // 重启Tracer记录
        (void)xTraceEnable(TRC_START);
    }
    taskEXIT_CRITICAL();
}

如果 x 表达式的结果为假(即为0),则会调用函数 vAssertCalled,并传递当前代码所在的行号和文件名作为参数。这样可以方便地在断言失败时跟踪和记录相关信息,以便进行调试和排查问题。

从未完全禁用中断,即使是临界区

在 FreeRTOS 中,任务调度器是通过中断来触发的。当发生一个中断时,任务调度器会暂停当前任务,并根据优先级切换到下一个任务。在某些情况下,为了确保关键代码的原子性或实时性(原子性指的是,要不就运行完 要不就干脆不运行),可能需要完全禁用中断。但是在移植 FreeRTOS 时,出于特定的需求或硬件限制,选择不完全禁用中断。

任务函数示例

推荐事件驱动型,记得删除

    void vATaskFunction( void *pvParameters )
    {
        for( ;; )
        {
            if( WaitForEvent( EventObject, TimeOut ) == pdPASS )
            {
                -- Handle event here. --
            }
            else
            {
                -- Clear errors, or take actions here. --
            }
        }

        /* As per the first code listing above. */
        vTaskDelete( NULL );
    }

创建任务的宏控

void vTask( void *pvParameters );
可以写成
portTASK_FUNCTION_PROTO( vTask, pvParameters );

队列的几个特点

  1. 消息通过队列以副本的方式发送, 这意味着数据本身被复制到队列中, 而不是队列始终只存储对数据的引用

  2. 使用按副本传递数据的队列不会阻止队列用于按引用传递数据,如果消息太大,也可以开一个队列存指针

标签:__,FreeRTOS,例程,void,-----------------------------------------------------------,
From: https://www.cnblogs.com/xsl-blogs/p/17714445.html

相关文章

  • 没有Android开发的工作经验,该如何求职?
    前言找工作是每一个新毕业生都要面临的难题,尤其是在如今竞争激烈的社会环境下。无工作经验的应届生想要获得心仪Offer无疑更加困难。但是,没有经验并不代表没有机会。推推教你做好以下几点准备,依然可以在求职中脱颖而出!第一,要明确自己的专业方向和职业规划。根据自己的专业知识、技......
  • FPGA开发板实验目录
     数字逻辑基础实验   实验文件夹名称   实验说明   lab1   4位并入串出移位寄存器   lab2   4位串入串出移位寄存器   lab3   5位串入并出移位寄存器   lab4   8线-3线编码器   lab5   8线-3线优先编码器   lab6   38......
  • Python游戏开发:Pygame库入门
    Pygame是一个开源的Python库,用于开发2D游戏。它提供了许多功能,如游戏开发、音频处理和事件处理。安装Pygame库您可以通过以下命令在终端中安装Pygame库:pipinstallpygame创建游戏窗口要创建一个游戏窗口,您可以使用以下代码:importpygamepygame.init()#设置窗口尺寸window_......
  • iOS开发Swift-回调函数
    回调函数:回调函数是一种将函数作为参数传递给另一函数的策略。当特定事件或条件发生时,传递的函数(即回调函数)将被调用。这种机制允许在事件发生时执行自定义的代码,因此它是异步编程的重要组成部分。在Swift中,可以使用闭包(closure)或函数作为回调函数。假设你有一个函数叫做greet(......
  • iOS开发Swift-??
    letg=F(a:s??0.0,b:l??0.0,c:d??0.0)这段代码在创建一个名为g的F对象。F是一个自定义类,它的实例化对象包含一些目标值,如a、b和c。这些目标值通过可选链操作符(??)设置,如果对应的变量为空(nil),那么就会使用默认值0.0。 ??是可选链操作符(nil-coalescingoperato......
  • 敏捷开发的优势
    ​在现今这个快速变化的时代,企业对于软件开发的需求也在不断变化。为了满足市场需求,开发出高质量、具有竞争力的软件产品,越来越多的企业开始采用敏捷开发方法。敏捷开发的优势在于其能够灵活响应变化,提升软件项目的成功率。敏捷开发的优势主要包括以下几点:1、更快地交付高质量......
  • 大型软件开发过程的质量管理体系
    一、按照ISO9126的定义,软件的质量通常可以从以下六个方面去衡量(定义)1.功用性(Functionality),即软件是否满足了客户功能要求;2.可靠性(Reliability),即软件是否能够-直在-个稳定的状态上满足可用性;3.可用性(Usability),即衡量用户能够使用软件需要多大的努力;4.效率(Efficie......
  • 小区物业管理缴费报修活动报名商城小程序开源版开发
    小区物业管理缴费报修活动报名商城小程序开源版开发以下是小区物业管理缴费报修活动报名商城小程序开源版的功能列表:1.用户注册和登录2.小区信息展示(包括小区简介、周边设施等)3.物业公告显示和发布功能4.小区物业费用查询和缴费功能5.基础设施报修功能(包括提交报修信息、查看......
  • OpenHarmony AI框架开发指导
    OpenHarmonyAI框架开发指导一、概述1、功能简介AI业务子系统是OpenHarmony提供原生的分布式AI能力的子系统。AI业务子系统提供了统一的AI引擎框架,实现算法能力快速插件化集成。AI引擎框架主要包含插件管理、模块管理和通信管理模块,完成对AI算法能力的生命周期管理和按需......
  • 常用的敏捷开发工具
    1、Leangoo领歌Leangoo领歌Scrum中文网(scrum.cn)旗下的一款永久免费的专业敏捷研发管理工具,它覆盖了敏捷项目研发全流程,包括小型团队敏捷开发,规模化敏捷SAFe,ScrumofScrums大规模敏捷。能够支持多种场景,如:敏捷研发管理、敏捷项目管理、工作流管理、轻量级项目群管理、任务管理等......