FreeRTOS_任务通知

目录

1. 任务通知简介

2. 发送任务通知

2.1 函数 xTaskNotify()

2.2 函数 xTaskNotifyFromISR()

2.3 函数 xTaskNotifyGive()

2.4 函数 vTaskNotifyGiveFromISR()

2.5 函数 xTaskNotifyAndQuery()

2.6 函数 xTaskNotifyAndQueryFromISR()

3. 任务通知通用发送函数

3.1 任务级任务通知通用发送函数

3.2 中断级任务通知发送函数

4. 获取任务通知

4.1 函数 ulTaskNotifyTake()

4.2 函数 xTaskNotifyWait()

5. 任务通知模拟二值信号量实验

5.1 实验程序

5.1.1 main.c

5.1.2 usart.c

6. 任务通知模拟计数型信号量实验

6.1 实验程序

6.1.1 main.c

7. 任务通知模拟消息邮箱实验

7.1 实验程序

7.1.1 main.c

8. 任务通知模拟事件标志组实验

8.1 实验程序

8.1.1 main.c

8.1.2 exit.c


        在比较新的版本开始,FreeRTOS 新增了任务通知(Task Notifictions)这个功能,可以使用任务通知来代替信号量、消息队列、事件标志组这些东西。使用任务通知的话效率会更高。

1. 任务通知简介

        任务通知在 FreeRTOS 中是一个可选的功能,要使用任务通知的话就需要将宏 configUSE_TASK_NOTIFICATIONS 定义为 1

#if(configUSE_TASK_NOTIFICATIONS==1)
    volatile uint32_t ulNotifiedValue[configTASK_NOTIFICATION_ARRAY_ENTRIES];
    volatile uint8_t  ucNotifyState[configTASK_NOTIFICATION_ARRAY_ENTRIES];
#endif

任务通知就是用来通知任务的,任务控制块中的结构体成员变量 ulNotifiedValue 就是这个通知值。

        使用队列、信号量、事件标志组都需要另外创建一个结构体,通过中间的结构体进行间接通信!

        但是使用任务通知时,任务结构体 TCB 中就包含了内部对象,可以直接接收别人发过来的 “通知”;(其中包括任务通知状态和任务通知值都属于任务控制块其中的结构体成员变量)

        FreeRTOS 的每个任务都有一个 32 位的通知值,任务控制块中的成员变量 ulNotifiedValue 就是这个通知值任务通知是一个事件,假如某个任务通知的接收任务因为等待任务通知而阻塞话,向这个接收任务发送任务通知以后就会解除这个任务的阻塞状态(任务通知归其根本是一个事件,任务等待这个事件到来,如果任务因为等待这个事件而进入阻塞的话,那么向接收任务发送这个事件就会解除任务的阻塞状态)。也可以更新任务的任务通知值,任务通知可以通过如下方法更新接收任务的通知值:

  •         不覆盖接收任务的通知值(如果上次发送给接收任务的通知还没被处理)。---任务通知值在存放在TCB中的成员变量中,也就是说如果这个成员变量有值,现在接收到的这个成员变量不写进去,如果没有值我再写进去!
  •         覆盖接收任务的通知值。---不管这个成员变量中是否有值,都写进去!
  •         更新接收任务通知值的一个或多个 bit。---类似于事件标志组!
  •         增加接收任务的通知值。---类似于计数型信号量!

        合理、灵活的使用上面这些更改任务通知值的方法可以在一些场合中替代队列、二值信号量、计数型信号量和事件标志组。

        任务通知的发送使用函数 xTaskNotify() 或者 xTaskNotifyGive()(还有此函数的中断版本)来完成这个通知值会一直被保存着直到接收任务调用函数 xTaskNotifyWait() 或者 ulTaskNotifyTake() 来获取这个通知值。假如接收任务因为等待任务通知而阻塞的话那么在接收到任务通知以后就会解除阻塞态

        任务通知虽然可以提高速度,并且减少 RAM 的使用,但是任务通知也是有使用限制的:

  •         FreeRTOS 的任务通知只能有一个接收任务,其实大多数的应用都是这种情况。(无法广播给多个任务!)
  •         接收任务可以因为接收任务通知而进入阻塞态,但是发送任务不会因为任务通知发送失败而阻塞!(发送方无法进入阻塞状态等待!)
  •         无法发送数据给中断,ISR 没有任务结构体,所以无法给中断发送数据;但是中断可以使用任务通知的功能发送数据给任务!
  •         无法缓存多个数据,任务通知是通过更新任务通知值来发送数据的,但是任务结构体中只有一个任务通知值结构体成员变量,只能保持一个数据!

任务通知值的更新方式有多种类型:

        计数值(数值累加,类似信号量)

        相应位置一(类似事件标志组)

        任意数值(支持覆写和不覆写,类似队列)

任务通知状态:        

        #define taskNOT_WAITING_NOTIFICATION        ((uint8_t)0)                任务未等待通知(默认初始状态)

        #define taskWAITING_NOTIFICATION                 ((uint8_t)1)                任务在等待通知(接收方已经全部准备好了,等待发送方给通知)

        #define taskNOTIFICATION_RECEIVED              ((uint8_t)2)                任务在等待接收(发送方已经发出去了,等待接收方接收)

2. 发送任务通知

        发送任务通知的函数有 6 个:

函数:

        xTaskNotify()                        发送通知,带有通知值并且不保留接收任务原通知值,用在任务中。

        xTaskNotifyFromISR()         发送通知,函数  xTaskNotify() 的中断版本。

        xTaskNotifyGive()                发送通知,不带通知值并且不保留接收任务的通知值,此函数会将接收任务的通知值加一,用在任务中。

        vTaskNotifyGiveFromISR()  发送通知,函数 xTaskNotifyGive() 的中断版本。

        xTaskNotifyAndQuery()        发送通知,带有通知值并且保留接收任务的原通知值,用在任务中。

        xTaskNotifyAndQueryFromISR()        发送通知,函数 xTaskNotifyAndQuery() 的中断版本,用在中断服务函数中。

2.1 函数 xTaskNotify()

        此函数用于发送任务通知,此函数发送任务通知的时候带有通知值,此函数是个宏,真正执行的函数是 xTaskGenericNotify(),函数原型如下:

BaseType_t xTaskNotify(TaskHandle_t     xTaskToNotify,
                       uint32_t         ulValue,
                       eNotifyAction    eAction)

参数:

        xTaskToNotify:                任务句柄,指定任务通知是发送给哪个任务的。

        ulValue:                          任务通知值。

        eAction:                          任务通知更新的方法,eNotifyAction 是个枚举类型,在文件 task.h 中有如下定义:

typedef enum
{
    eNoAction=0,
    eSetBits,            //更新指定的位 bit
    eIncrement,          //通知值加一
    eSetValueWithOverwrite,    //覆写的方式更新通知值
    eSetValueWithoutOverwrite    //不覆写通知值
}eNotifyAction;

        此参数可以选择枚举类型中的任意一个,不同的应用环境其选择也不同。

返回值:

        pdFAIL:        当参数 eAction 设置为 eSetValueWithoutOverwrite(不覆写) 的时候,如果任务通知值没有更新成功就返回 pdFAIL。

        pdPASS:      eAction 设置为其他选项的时候统一返回 pdPASS。

2.2 函数 xTaskNotifyFromISR()

        此函数用于发送任务通知,是函数 xTaskNotify() 的中断版本,此函数是个宏,真正执行的是函数 xTaskGenericNotifyFromISR(),此函数原型如下:

BaseType_t xTaskNotify(TaskHandle_t     xTaskToNotify,
                       uint32_t         ulValue,
                       eNotifyAction    eAction,
                       BaseType_t*      pxHigherPriorityTaskWoken);

参数:

        xTaskToNotify:                任务句柄,指定任务通知是发送给哪个任务的。

        ulValue:                          任务通知值。

        eAction:                          任务通知更新的方法。

        pxHigherPriorityTaskWoken:        记退出此函数以后是否进行任务切换,这个变量的值函数会自动设置,用户不用进行设置,用户只需要提供一个变量来保存这个值就可以了。

                                                                当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。

返回值:

        pdFAIL:        当参数 eAction 设置为 eSetValueWithoutOverwrite(不覆写) 的时候,如果任务通知值没有更新成功就返回 pdFAIL。

        pdPASS:      eAction 设置为其他选项的时候统一返回 pdPASS。

2.3 函数 xTaskNotifyGive()

        发送任务通知,相对于函数 xTaskNotify(),此函数发送任务通知的时候不带有通知值。此函数只是将任务通知值简单的加一,此函数是个宏,真正执行的是函数 xTaskGenericNotify(),此函数原型如下:

BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify);

参数:

        xTaskToNotify:        任务句柄,指定任务通知是发送给哪个任务的。

返回值:

        pdPASS:        此函数只会返回 pdPASS。

2.4 函数 vTaskNotifyGiveFromISR()

        此函数为 xTaskNotifyGive() 的中断版本,用在中断服务函数中,函数原型如下:

void vTaskNotifyGiveFromISR(TaskHandle_t xTaskHandle,
                            BaseType_t*  pxHigherPriorityTaskWoken);

参数:

        xTaskToNotify:        任务句柄,指定任务通知是发送给哪个任务的。

        pxHigherPriorityTaskWoken:        记退出此函数以后是否进行任务切换,这个变量的值函数会自动设置,用户不用进行设置,用户只需要提供一个变量来保存这个值就可以了。

                                                                当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。

返回值:

        无。

2.5 函数 xTaskNotifyAndQuery()

        此函数比 xTaskNotify() 多一个参数,此参数用来保存更新前的通知值。此函数是个宏,真正执行的是函数 xTaskGenericNotify(),此函数原型如下:

BaseType_t xTaskNotify(TaskHandle_t     xTaskToNotify,
                       uint32_t         ulValue,
                       eNotifyAction    eAction,
                       uint32_t*        pulPreviousNotificationValue);

参数:

        xTaskToNotify:        任务句柄,指定任务通知是发送给哪个任务的。

        ulValue:                  任务通知值。

        eAction:                  任务通知更新的方法。

        pulPreviousNotificationValue:        用来保存更新前的任务通知值。

返回值:

        pdFAIL:        当参数 eAction 设置为 eSetValueWithoutOverwrite 的时候,如果任务通知值没有更新成功就返回 pdFAIL。

        pdPASS:      eAction 设置为其他选项的时候统一返回 pdPASS。

2.6 函数 xTaskNotifyAndQueryFromISR()

        此函数 xTaskNotifyAndQuery() 的中断版本,用在中断服务函数中。此函数同样是个宏,真正执行的是函数 xTaskGenericNotifyFromISR(),此函数原型如下:

BaseType_t xTaskNotify(TaskHandle_t     xTaskToNotify,
                       uint32_t         ulValue,
                       eNotifyAction    eAction,
                       uint32_t*        pulPreviousNotificationValue,
                       BaseType_t*      pxHigherPriorityTaskWoken);

参数:

        xTaskToNotify:        任务句柄,指定任务通知是发送给哪个任务的。

        ulValue:                  任务通知值。

        eAction:                  任务通知更新的方法。

        pulPreviousNotificationValue:        用来保存更新前的任务通知值。

        pxHigherPriorityTaskWoken:            记退出此函数以后是否进行任务切换,这个变量的值函数会自动设置,用户不用进行设置,用户只需要提供一个变量来保存这个值就可以了。

                                                                 当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。

返回值:

        pdFAIL:        当参数 eAction 设置为 eSetValueWithoutOverwrite 的时候,如果任务通知值没有更新成功就返回 pdFAIL。

        pdPASS:      eAction 设置为其他选项的时候统一返回 pdPASS。

3. 任务通知通用发送函数

3.1 任务级任务通知通用发送函数

        任务通知发送函数:xTaskNotify()、xTaskNotifyGive() 和 xTaskNotifyAndQuery(),这三个函数最终调用的都是函数 xTaskGenericNotify()

BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, //任务句柄 
                               uint32_t ulValue, //任务通知值 
                               eNotifyAction eAction, //任务通知更新方式
                               uint32_t * pulPreviousNotificationValue )//保存更新前的 
//任务通知值 
{ 
    TCB_t * pxTCB; 
    BaseType_t xReturn = pdPASS; 
    uint8_t ucOriginalNotifyState; 
 
    configASSERT( xTaskToNotify ); 
    pxTCB = ( TCB_t * ) xTaskToNotify; 
     
    taskENTER_CRITICAL(); 
    { 
        if( pulPreviousNotificationValue != NULL )                           (1) 判断此参数是否有效,此参数保存更新前的任务通知值
        { 
            *pulPreviousNotificationValue = pxTCB->ulNotifiedValue;          (2) 如果此参数有效,就用此参数保存更新前的任务通知值
        } 
        ucOriginalNotifyState = pxTCB->ucNotifyState;                        (3) 保存任务通知状态,因为下面会修改这个状态,后续我们根据这个状态来判断是否将任务从阻塞态解除
 
        pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED;                    (4) 更新任务通知状态为 taskNOTIFICATION_RECEIVED
 
        switch( eAction )  //根据不同更新方式做不同的处理
        { 
            case eSetBits :                                                  (5) 将指定的位置1,也就是更新接收任务通知值的一个或多个bit
                pxTCB->ulNotifiedValue |= ulValue; 
                break; 
            case eIncrement :                                                (6) 将任务通知值加一
                ( pxTCB->ulNotifiedValue )++; 
                break; 
            case eSetValueWithOverwrite :                                    (7) 直接覆写原来的任务通知值
                pxTCB->ulNotifiedValue = ulValue; 
                break; 
            case eSetValueWithoutOverwrite :                                 (8) 判断原来的任务通知值是否被处理,如果已经被处理了就更新任务通知值
                if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED ) //任务通知值不等于原先接收到的值,证明原先的值被处理了
                { 
                    pxTCB->ulNotifiedValue = ulValue;  //更新任务通知值
                } 
                else 
                { 
                    xReturn = pdFAIL; 没有被处理,标记 xReturn 为 pdFAIL
                } 
                break; 
            case eNoAction: 
            break; 
        } 
 
        traceTASK_NOTIFY(); 
 
        //如果任务因为等待任务通知而进入阻塞态的话就需要解除阻塞 
        if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )             (9) 根据接收任务之前保存的状态值来判断是否有任务需要解除阻塞,如果更新前任务处于 taskWAITING_NOTIFICATION 状态的话说明有任务因为等待任务通知值而进入了阻塞态。
        { 
            ( void ) uxListRemove( &( pxTCB->xStateListItem ) );             (10) 将任务从状态列表中移除
            prvAddTaskToReadyList( pxTCB );                                 (11) 将任务重新添加到就绪列表中
            /******************************************************************/ 
            /********************省略相关的条件编译代码************************/ 
            /******************************************************************/ 
            if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )             (12) 判断刚刚解除阻塞的任务的优先级是否比当前正在运行的任务的优先级高,如果是的话需要进行一次任务切换
            { 
                //解除阻塞的任务优先级比当前运行的任务优先级高,所以需要进行 
                //任务切换。 
                taskYIELD_IF_USING_PREEMPTION(); 
            } 
            else 
            { 
                mtCOVERAGE_TEST_MARKER(); 
            } 
        } 
        else 
        { 
            mtCOVERAGE_TEST_MARKER(); 
        } 
    } 
    taskEXIT_CRITICAL(); 
    return xReturn;                                                     (13) 返回 xReturn的值,pdFAIL 或者 pdPASS
} 

3.2 中断级任务通知发送函数

        中断级任务通知发送函数也有三个,分别为: xTaskNotifyFromISR() 、xTaskNotifyAndQueryFromISR()和 vTaskNotifyGiveFromISR()。其中函数 xTaskNotifyFromISR() 和 xTaskNotifyAndQueryFromISR()最终调用的都是函数 xTaskGenericNotifyFromISR()

BaseType_t xTaskGenericNotifyFromISR(TaskHandle_t    xTaskToNotify,
                                     uint32_t        ulValue,
                                     eNotifyAction   eAction,
                                     uint32_t*       pulPreviousNotificationValue,
                                     BaseType_t*     pxHigherPriorityTaskWoken)

参数:

        xTaskToNotify:                任务句柄,指定任务通知是发送给哪个任务的。

        ulValue:                          任务通知值。

        eAction:                          任务通知更新的方法。

        pulPreviousNotificationValue:        用来保存更新前的任务通知值。

        pxHigherPriorityTaskWoken:         记退出此函数以后是否进行任务切换,这个变量的值函数会自动设置,用户不用进行设置,用户只需要提供一个变量来保存这个值。

                                                                 当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。

返回值:

        pdFAIL:        当参数 eAction 设置为 eSetValueWithoutOverwrite 的时候,如果任务通知值没有更新成功就返回 pdFAIL。

        pdPASS:       eAction 设置为其他选项的时候统一返回 pdPASS。

BaseType_t xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify, 
                                      uint32_t ulValue, 
                                      eNotifyAction eAction, 
                                      uint32_t * pulPreviousNotificationValue, 
                                      BaseType_t * pxHigherPriorityTaskWoken ) 
{ 
    TCB_t * pxTCB; 
    uint8_t ucOriginalNotifyState; 
    BaseType_t xReturn = pdPASS; 
    UBaseType_t uxSavedInterruptStatus; 
 
    configASSERT( xTaskToNotify ); 
 
    portASSERT_IF_INTERRUPT_PRIORITY_INVALID(); 
 
    pxTCB = ( TCB_t * ) xTaskToNotify; 
 
    uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR(); 
    { 
        if( pulPreviousNotificationValue != NULL )                             (1) 判断参数是否有效,如果参数有效就用此参数保存更新前的任务通知值。
        { 
            *pulPreviousNotificationValue = pxTCB->ulNotifiedValue; 
        } 
 
        ucOriginalNotifyState = pxTCB->ucNotifyState;                          (2) 保存任务通知状态,因为下面会修改这个状态,后续我们根据这个状态来确定是否将任务解除阻塞态
        pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED;                      (3) 更新任务通知状态 taskNOTIFICATION_RECEIVED
 
        switch( eAction )                                                      (4) 根据不同通知值来做不同的处理
        { 
            case eSetBits : 
                pxTCB->ulNotifiedValue |= ulValue; 
                break; 
            case eIncrement : 
                ( pxTCB->ulNotifiedValue )++; 
                break; 
            case eSetValueWithOverwrite : 
                pxTCB->ulNotifiedValue = ulValue; 
                break; 
            case eSetValueWithoutOverwrite : 
                if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED ) 
                { 
                    pxTCB->ulNotifiedValue = ulValue; 
                } 
                else 
                { 
                    xReturn = pdFAIL; 
                } 
                break; 
            case eNoAction : 
            break;
        } 
 
        traceTASK_NOTIFY_FROM_ISR(); 
 
        //如果任务因为等待任务通知而进入阻塞态的话就需要解除阻塞 
        if( ucOriginalNotifyState == taskNOTIFICATION_RECEIVED)                (5) 判断是否有任务需要解除阻塞,如果在任务通知值更新前处于 taskNOTIFICATION_RECEIVED 的话就说明有任务因为等待任务通知而进入阻塞态。
        { 
            configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) ==
                          NULL ); 
 
            if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )              (6) 判断任务调度器是否上锁,如果调度器没有上锁的话就将任务从状态列表中移除,然后添加到就绪列表中
            { 
                ( void ) uxListRemove( &( pxTCB->xStateListItem ) ); 
                prvAddTaskToReadyList( pxTCB ); 
            } 
        else                                                                   (7) 如果任务调度器上锁的话就将任务添加到列表 xPendingReadyList 
        { 
            vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) ); 
        } 
 
        if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )                     (8) 判断任务解除阻塞的任务优先级是否比当前的任务优先级高
        { 
            //解除阻塞的任务优先级比当前运行任务的优先级高,所以需要标记 
            //在退出中断服务函数的时候需要做任务切换。 
            if( pxHigherPriorityTaskWoken != NULL ) 
            { 
                *pxHigherPriorityTaskWoken = pdTRUE; 
            } 
            else 
            { 
                xYieldPending = pdTRUE; 
            } 
        } 
        else 
        { 
            mtCOVERAGE_TEST_MARKER(); 
        } 
    } 
} 
    portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus ); 
    return xReturn; 
} 

4. 获取任务通知

        获取任务通知的函数有两个:

函数:

        ulTaskNotifyTake()                        获取任务通知,可以设置在退出此函数的时候将任务通知值清零或者减一。当任务通知用作二值信号量或者计数型信号量的时候使用此函数来获取信号量。

        xTaskNotifyWait()                          等待任务通知,比 ulTaskNotifyTake() 更为强大。

4.1 函数 ulTaskNotifyTake()

        此函数为获取任务通知函数,当任务通知用作二值信号量或者计数型信号量的时候可以使用此函数来获取信号量,函数原型如下:

uint32_t ulTaskNotifyTake(BaseType_t    xClearCountOnExit,
                          TickType_t    xTicksToWait);

参数:

        xClearCountOnExit:        参数为 pdFALSE 的话在退出函数 ulTaskNotifyTake() 的时候任务通知值减一,类似计数型信号量;

                                                   当此参数为 pdTRUE 的话在退出函数的时候任务通知值清零,类似二值信号量。

        xTickToWait:                    阻塞时间。

返回值:

        任何值:        任务通知值减少或者清零之前的值。

uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ) 
{ 
    uint32_t ulReturn; 
 
    taskENTER_CRITICAL(); 
    { 
 
        if( pxCurrentTCB->ulNotifiedValue == 0UL )                         (1) 判断任务通知值是否等于0,如果为0说明还没有接收到任务通知
        { 
            pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION;        (2) 修改任务通知状态位 taskWAITING_NOTIFICATION
            if( xTicksToWait > ( TickType_t ) 0 )                          (3) 如果阻塞时间不为 0 的话就将任务添加到延时列表中,并且进行一次任务调度。
            { 
                prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE ); 
                traceTASK_NOTIFY_TAKE_BLOCK(); 
                portYIELD_WITHIN_API(); 
            } 
            else 
            { 
                mtCOVERAGE_TEST_MARKER(); 
            } 
        } 
        else 
        { 
            mtCOVERAGE_TEST_MARKER(); 
        } 
    } 
    taskEXIT_CRITICAL(); 
 
    taskENTER_CRITICAL(); 
    { 
        traceTASK_NOTIFY_TAKE(); 
        ulReturn = pxCurrentTCB->ulNotifiedValue;                           (4) 如果任务通知值不为 0 的话就先获取任务通知值。
 
        if( ulReturn != 0UL )                                               (5) 任务通知值不为 0 意味着任务通知值大于0
        { 
            if( xClearCountOnExit!= pdFALSE )                               (6) 如果参数 xClearCountOnExit 不为 pdFASLE,就先将任务通知值清零。
            { 
                pxCurrentTCB->ulNotifiedValue = 0UL; 
            } 
            else 
            { 
                pxCurrentTCB->ulNotifiedValue = ulReturn - 1;               (7) 如果参数 xClearCountOnExit 为 pdFASLE,那就先将任务通知值减一。
            } 
        } 
        else 
        { 
            mtCOVERAGE_TEST_MARKER(); 
        } 
 
        pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;         (8) 更新任务通知值为 taskNOT_WAITING_NOTIFICATION。
    } 
    taskEXIT_CRITICAL(); 
    return ulReturn; 
} 

4.2 函数 xTaskNotifyWait()

        此函数也是用来获取任务通知的,不过此函数比 ulTaskNotifyTake() 更为强大,不管任务通知用作二值信号量、计数型信号量、队列和事件标志组中的哪一种,都可以使用此函数来获取任务通知。但是当任务通知用作二值信号量和计数型信号量的时候推荐使用函数 ulTaskNotifyTake()。此函数原型如下:

BaseType_t xTaskNotifyWait(uint32_t    ulBitsToClearOnEntry,
                           uint32_t    ulBitsToClearOnExit,
                           uint32_t*   pulNotificationValue,
                           TickType_t  xTicksToWait);

参数:

        ulBitsToClearOnEntry:        当没有接收到任务通知的时候将任务通知值与此参数的取反值进行按位与运算,当此参数为 0xffffffff 或者 ULONG_MAX 的时候就会将任务通知值清零。

        ulBitsToClearOnExit:           如果接收到了任务通知,在做完相应的处理退出函数之前将任务通知值与此参数的取反值进行按位与运算,当此参数为 0xffffffff 或者 ULONG_MAX 的时候就会将任务通知值清零。

        pulNotificationValue:            此参数用来保存任务通知值。

        xTickToWait:                        阻塞时间。

返回值:

        pdTRUE:        获取到了任务通知。

        pdFALSE:       任务通知获取失败。

BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
                            uint32_t ulBitsToClearOnExit, 
                            uint32_t * pulNotificationValue, 
                            TickType_t xTicksToWait ) 
{ 
    BaseType_t xReturn; 
 
    taskENTER_CRITICAL(); 
    { 
        if( pxCurrentTCB->ucNotifyState != taskNOTIFICATION_RECEIVED )         (1) 任务通知值不为 taskNOTIFICATION_RECEIVED 
        { 
            pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnEntry;            (2) 将任务通知值与 ulBitsToClearOnEntry 取反值进行按位与运算
            pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION;            (3) 任务通知状态改为 taskWAITING_NOTIFICATION
            if( xTicksToWait > ( TickType_t ) 0 )                              (4) 如果阻塞时间大于 0 的话就将任务添加到延时列表中,并且进行一次任务切换。
            { 
                prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE ); 
                traceTASK_NOTIFY_WAIT_BLOCK(); 
                portYIELD_WITHIN_API(); 
            } 
            else 
            { 
                mtCOVERAGE_TEST_MARKER(); 
            } 
        } 
        else 
        { 
            mtCOVERAGE_TEST_MARKER(); 
        } 
    } 
    taskEXIT_CRITICAL(); 
 
    taskENTER_CRITICAL(); 
    { 
        traceTASK_NOTIFY_WAIT(); 
 
        if( pulNotificationValue != NULL )                                     (5) 如果任务通知状态为 taskNOTIFICATION_RECEIVED,并且参数 pulNotificationValue 有效的话就保存任务通知值。
        { 
            *pulNotificationValue = pxCurrentTCB->ulNotifiedValue; 
        } 
        if( pxCurrentTCB->ucNotifyState == taskWAITING_NOTIFICATION )          (6) 如果任务通知的状态又变为 taskWAITING_NOTIFICATION 的话就标记 xRetur 为 pdFALSE。
        { 
            xReturn = pdFALSE; 
        } 
        else 
        { 
            pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnExit;             (7) 如果任务通知的状态一直为 taskNOTIFICATION_RECEIVED 的话就将任务通知的值与参数 ulBitsToClearOnExit的取反值进行按位与运算,并且标记 xReturn 为 pdTRUE 表示获取任务通知成功。
            xReturn = pdTRUE; 
        } 
 
        pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;            (8) 标记任务通知状态为 taskNOT_WAITING_NOTIFICATION
    } 
    taskEXIT_CRITICAL(); 
    return xReturn; 
} 

5. 任务通知模拟二值信号量实验

        使用任务通知代替二值信号量的时候任务解除阻塞态的时间要快 45%,并且需要的 RAM 也很少

        二值信号量就是值最大为 1 的信号量,这也是名字中 “二值” 的来源。当任务通知用于替代二值信号量的时候任务通知值就会代替信号量值,函数 ulTaskNotifyTake() 就可以代替信号量获取函数 xSemaphoreTake(),函数 ulTaskNotifyTake() 的参数 xClearCountOnExit 设置为 pdTRUE。这样在每次获取任务通知的时候模拟的信号量值就会清零

uint32_t ulTaskNotifyTake(BaseType_t    xClearCountOnExit,
                          TickType_t    xTicksToWait);
参数:

        xClearCountOnExit:        参数为 pdFALSE 的话在退出函数 ulTaskNotifyTake() 的时候任务通知值减一,类似计数型信号量;

                                                   当此参数为 pdTRUE 的话在退出函数的时候任务通知值清零,类似二值信号量。

        xTickToWait:                    阻塞时间。

返回值:

        任何值:        任务通知值减少或者清零之前的值。

        函数 xTaskNotifyGive() 和 xTaskNotifyGiveFromISR() 用于替代函数 xSemaphoreGive() 和 xSemaphoreGiveFromISR()

BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify);
参数:
    xTaskToNotify :  发送给哪个任务的任务句柄

5.1 实验程序

        本实验在 —— 二值信号量实验的基础上修改而来;FreeRTOS_信号量之二值信号量-CSDN博客

5.1.1 main.c

#include "stm32f4xx.h"  
#include "FreeRTOS.h" //这里注意必须先引用FreeRTOS的头文件,然后再引用task.h
#include "task.h"     //存在一个先后的关系
#include "LED.h"
#include "LCD.h"
#include "Key.h"
#include "usart.h"
#include "delay.h"
#include "string.h"
#include "beep.h"
#include "malloc.h"
#include "timer.h"
#include "queue.h"
#include "semphr.h"


//任务优先级
#define START_TASK_PRIO     1     //用于创建其他两个任务
//任务堆栈大小
#define START_STK_SIZE      256
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);

//任务优先级
#define TASK1_TASK_PRIO     2   //控制 LED0 闪烁,提示系统正在运行
//任务堆栈大小
#define TASK1_STK_SIZE      256
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);

//任务优先级
#define DATAPROCESS_TASK_PRIO 3  //指令处理函数
//任务堆栈大小	
#define DATAPROCESS_STK_SIZE  256 
//任务句柄
TaskHandle_t DataProcess_Handler;
//任务函数
void DataProcess_task(void *pvParameters);

//二值信号量句柄
SemaphoreHandle_t BinarySemaphore;    //二值信号量句柄

//用于命令解析用的命令值
#define LED1ON  1
#define LED1OFF 2
#define BEEPON  3
#define BEEPOFF 4
#define COMMANDERR  0xFF

//函数 LowerToCap 用于将串口发送过来的命令中的小写字母统一转换成大写字母,
//这样就可以在发送命令的时候不用区分大小写,因为开发板会统一转换成大写。
//将字符串中的小写字母转换为大写
//str:要转换的字符串
//len:字符串长度
void LowerToCap(u8 *str,u8 len)
{
    u8 i;
    for(i=0;i<len;i++)
    {
        //判断字符串的ASCII码是否位于96到123之间
        if((96<str[i])&&(str[i]<123))  //小写字母
        {
            //ASCII码是一种用于表示字符的编码系统。在ASCII码中,每个字符都被赋予一个唯一的整数值。
            //大写字母的ASCII码值是65到90
            //小写字母的ASCII码值是97到122   所以一旦确定ASCII码值位于小写字母的范畴内,只需要将ASCII码值减去32即可转换为大写
            str[i] = str[i] - 32;  //转换为大写
        }
    }
}

//函数 CommandProcess 用于将接收到的命令字符串转换成命令值,比如说命令“LED1ON”转换成命令值就是 0(宏LED1ON为 0)
//命令处理函数,将字符串命令转换成命令值
//str:命令
//返回值:0xFF,命令错误;其他值,命令值
u8 CommandProcess(u8 *str)
{
    u8 CommandValue = COMMANDERR;
    if(strcmp((char*)str,"LED1ON")==0) //strcmp 字符串比较函数
        //这个函数会比较两个参数;比较时,会以字符的ASCII值进行比较
        //如果str1的ASCII码值小于str2,返回一个负数;反之,返回一个正数;
        //如果str1的ASCII码值等于str2,返回 0,此时,if判断语句成立
        CommandValue = LED1ON; //设置的LED1ON的宏为1,也就是在串口输入1,if判断语句成立
    else if(strcmp((char*)str,"LED1OFF")==0)
        CommandValue = LED1OFF; //在串口输入2,if判断语句成立
    else if(strcmp((char*)str,"BEEPON")==0)
        CommandValue = BEEPON; //在串口输入3,if判断语句成立
    else if(strcmp((char*)str,"BEEPOFF")==0)
        CommandValue = BEEPOFF; //在串口输入4,if判断语句成立
    return CommandValue;
}

int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); //设置系统中断优先级
    delay_init(168);
    uart_init(115200);
    LED_Init();
    KEY_Init();
    BEEP_Init();
    LCD_Init();
    my_mem_init(SRAMIN);    //初始化内部内存池
    
    POINT_COLOR=RED;
    LCD_ShowString(10,10,200,16,16,"ATK STM32F407");
    LCD_ShowString(10,30,200,16,16,"FreeRTOS Example");
    LCD_ShowString(10,50,200,16,16,"Task Notify Binary Semaphore");
    LCD_ShowString(10,70,200,16,16,"Command Data:");
    
    //创建开始任务
    xTaskCreate((TaskFunction_t )start_task,            //任务函数
                (const char*    )"start_task",          //任务名称
                (uint16_t       )START_STK_SIZE,        //任务堆栈大小
                (void*          )NULL,                  //传递给任务函数的参数
                (UBaseType_t    )START_TASK_PRIO,       //任务优先级
                (TaskHandle_t*  )&StartTask_Handler);   //任务句柄              
    vTaskStartScheduler();          //开启任务调度
}

//开始任务任务函数
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();    //进入临界区
    
    //这里不需要再创建二值信号量了
    //因为任务通知需要的任务通知状态和任务通知值都在TCB的成员变量中
    
    //创建Task1任务
    xTaskCreate((TaskFunction_t )task1_task,            //任务函数
                (const char*    )"task1_task",          //任务名称
                (uint16_t       )TASK1_STK_SIZE,        //任务堆栈大小
                (void*          )NULL,                  //传递给任务函数的参数
                (UBaseType_t    )TASK1_TASK_PRIO,       //任务优先级
                (TaskHandle_t*  )&Task1Task_Handler);   //任务句柄  
    //创建Task2任务
    xTaskCreate((TaskFunction_t )DataProcess_task,            //任务函数
                (const char*    )"DataProcess_task",          //任务名称
                (uint16_t       )DATAPROCESS_STK_SIZE,        //任务堆栈大小
                (void*          )NULL,                  //传递给任务函数的参数
                (UBaseType_t    )DATAPROCESS_TASK_PRIO,       //任务优先级
                (TaskHandle_t*  )&DataProcess_Handler);   //任务句柄  
                                  
    vTaskDelete(StartTask_Handler);  //删除开始任务
    taskEXIT_CRITICAL();          //退出临界区
}

//Task1任务
//控制 LED0 闪烁,提示系统正在运行
void task1_task(void *pvParameters)
{
    while(1)
    {
        LED0=!LED0;
        vTaskDelay(500);        //延时500ms,也就是500个时钟节拍
    }
}

//DataProcess_task函数
//指令处理任务,根据接收到的指令来控制不同的外设
void DataProcess_task(void *pvParameters)
{
    u8 len=0;
    u8 CommandValue=COMMANDERR;
    u32 NotifyValue;
    
    u8 *CommandStr;
    POINT_COLOR=BLUE;
    while(1)
    {
        NotifyValue = ulTaskNotifyTake(pdTRUE,portMAX_DELAY);   //获取任务通知
        //返回值:任务通知值减少或者清零之前的值
        //第一个参数:参数为 pdFALSE 的话在退出 ulTaskNotifyTake 的时候任务通知值减一,类似于计数型信号量
        //            参数为 pdTRUE 的话在退出 ulTaskNotifyTake 的时候任务通知值清零,类似于二值信号量
        //第二个参数:阻塞时间
        if(NotifyValue==1) //函数返回值为1,表示清零之前的任务通知值为1,说明任务通知有效
        {
            len=USART_RX_STA&0x3fff;  //得到此次接收到的数据长度
            //接收状态
            //bit15,	接收完成标志
            //bit14,	接收到0x0d
            //bit13~0,	接收到的有效字节数目
            CommandStr=mymalloc(SRAMIN,len+1);  //申请内存 指针指向申请内存的首地址
            sprintf((char*)CommandStr,"%s",USART_RX_BUF);  //打印接收缓存区,把接收缓存区的数据保存到CommandStr中
            CommandStr[len]='\0';   //加上字符串结尾符号
            //CommandStr 是个指针,长度为len,数组是从下角标 0 开始的,所以len就表示数组的最后一个
            LowerToCap(CommandStr,len);  //将字符串转换成大写
            CommandValue=CommandProcess(CommandStr);  //命令解析,也就是获取上面定义的宏 1 2 3 4
            if(CommandValue!=COMMANDERR)//if判断语句成立,表示CommandValue不等于0xFF,那也就是 LED1ON、LED1OFF、BEEPON、BEEPOFF 其中一个指令
            {
                LCD_Fill(10,90,210,110,WHITE);  //清除显示区域
                LCD_ShowString(10,90,200,16,16,CommandStr);  //在LCD上显示命令
                printf("命令为:%s\r\n",CommandStr);   
                switch(CommandValue)
                {
                    case LED1ON:
                        LED1=0;
                        break;
                    case LED1OFF:
                        LED1=1;
                        break;
                    case BEEPON:
                        BEEP=1;
                        break;
                    case BEEPOFF:
                        BEEP=0;
                        break;
                }
            }
            else
            {//当命令错误的时候开发板会向串口调试助手发送命令错误的提示信息
                //比如我们发送 LED1_off 这个命令,串口助手会显示:无效的命令,请重新输入!!
                printf("无效的命令,请重新输入!!\r\n");
            }
            USART_RX_STA = 0;
            memset(USART_RX_BUF,0,USART_REC_LEN);  //串口接收缓冲区清零
            myfree(SRAMIN,CommandStr);             //释放内存
        }
        else //else表示清零之前的任务通知值为0,表示任务通知无效
        {
            vTaskDelay(10);   //延时10ms,也就是10个时钟节拍
        }
    }
}

5.1.2 usart.c

extern TaskHandle_t DataProcess_Handler;  //接收任务通知的任务句柄
//QueueHandle_t queue.h 中定义

void USART1_IRQHandler(void)                	//串口1中断服务程序
{
	u8 Res;
    //xHigherPriorityTaskWoken:用来标记退出此函数以后是否进行任务切换,这个变量的值由三个函数来设置,用户不再进行设置
                                 //用户只需要提供一个变量来保存这个值就可以了。
                                 //但是切记要注意:当此值为 pdTURE 的时候在退出中断服务函数之前一定要进行一次任务切换。
    
    BaseType_t xHigherPriorityTaskWoken;  //BaseType_t 也在 queue.h 中定义
    
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
	{
		Res =USART_ReceiveData(USART1);//(USART1->DR);	//读取接收到的数据
		
		if((USART_RX_STA&0x8000)==0)//接收未完成
		{
			if(USART_RX_STA&0x4000)//接收到了0x0d
			{
				if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
				else USART_RX_STA|=0x8000;	//接收完成了 
			}
			else //还没收到0X0D
			{	
				if(Res==0x0d)USART_RX_STA|=0x4000;
				else
				{
					USART_RX_BUF[USART_RX_STA&0X3FFF]=Res;
					USART_RX_STA++;
					if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收	  
				}		 
			}
		} 
    }
    //释放二值信号量
    //指令通过串口发送给开发板,串口中断用来释放二值信号量,任务用来不断获取信号量
    //任务一旦获取到信号量,就会从串口接收缓冲区中提取这些指令,然后根据这些指令控制相应的外设
    if((USART_RX_STA&0x8000)&&(DataProcess_Handler!=NULL)) //串口接收到数据,并且接收任务通知的任务有效
    {
        vTaskNotifyGiveFromISR(DataProcess_Handler,&xHigherPriorityTaskWoken);  //调用在中断中发送任务通知函数
        //函数第一个参数:发送通知,指明任务通知是发送给哪个任务的
        //函数第二个参数:标记是否需要进行任务切换
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken); //如果需要的话进行一次任务切换
    }   //二值信号量用来实现同步的意思就是说:保证中断先释放信号量,然后任务在获取信号量;
}

6. 任务通知模拟计数型信号量实验

        不同于二值信号量,计数型信号量值可以大于 1,这个最大值在创建信号量的时候可以设置。当计数型信号量有效的时候任务可以获取计数型信号量,信号量值只要大于 0 就表示计数型信号量有效。

        当任务通知用作计数型信号量的时候获取信号量相当于获取任务通知值,使用函数 ulTakeNotifyTake() 来代替函数 xSemaphoreTake()函数 ulTaskNotifyTake() 的参数 xClearOnExit 要设置为 pdFALSE,这样每次获取任务通知成功以后任务通知值就会减一。使用任务通知发送函数 xTaskNotifyGive() 和 vTaskNotifyGiveFromISR() 来代替计数型信号量释放函数 xSemaphoreGive() 和 xSemaphoreGiveFromISR()

6.1 实验程序

        本实验在 —— 计数型信号量实验的基础上修改而来;FreeRTOS_信号量之计数型信号量-CSDN博客

6.1.1 main.c

#include "stm32f4xx.h"  
#include "FreeRTOS.h" //这里注意必须先引用FreeRTOS的头文件,然后再引用task.h
#include "task.h"     //存在一个先后的关系
#include "LED.h"
#include "LCD.h"
#include "Key.h"
#include "usart.h"
#include "delay.h"
#include "string.h"
#include "beep.h"
#include "malloc.h"
#include "timer.h"
#include "queue.h"
#include "semphr.h"


//任务优先级
#define START_TASK_PRIO     1     //用于创建其他两个任务
//任务堆栈大小
#define START_STK_SIZE      256
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);

//任务优先级
#define SEMAPGIVE_TASK_PRIO     2   //获取按键状态,当 KEY_UP 键按下去以后就释放任务通知
//任务堆栈大小
#define SEMAPGIVE_STK_SIZE      256
//任务句柄
TaskHandle_t SemapGiveTask_Handler;
//任务函数
void SemapGive_task(void *pvParameters);

//任务优先级
#define SEMAPTAKE_TASK_PRIO 3  //获取任务通知,当获取任务通知成功以后就刷新 LCD 指定区域的背景色
//任务堆栈大小	
#define SEMAPTAKE_STK_SIZE  256 
//任务句柄
TaskHandle_t SemapTakeTask_Handler;
//任务函数
void SemapTake_task(void *pvParameters);


//LCD刷屏时使用的颜色
int lcd_discolor[14] = { WHITE, BLACK, BLUE,  BRED,
                         GRED,  GBLUE, RED,   MAGENTA,
                         GREEN, CYAN,  YELLOW,BROWN,
                         BRRED, GRAY};

int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);  //设置系统中断优先级分组4
    delay_init(168);
    uart_init(115200);
    LED_Init();
    KEY_Init();
    BEEP_Init();
    LCD_Init();
    my_mem_init(SRAMIN);    //初始化内部内存池
    
    POINT_COLOR=RED;
    LCD_ShowString(30,10,200,16,16,"ATK STM32F407");
    LCD_ShowString(30,30,200,16,16,"FreeRTOS Example");
    LCD_ShowString(30,50,200,16,16,"Task Notify Count Semaphore");
    LCD_ShowString(30,70,200,16,16,"ATOM@ALIENTEK");
    LCD_ShowString(30,90,200,16,16,"2023/11/06");
    
    POINT_COLOR=BLACK;
    LCD_DrawRectangle(5,110,234,314);
    LCD_DrawLine(5,130,234,130);
    POINT_COLOR=RED;
    LCD_ShowString(54,111,200,16,16,"Notify Value:   0");
    POINT_COLOR=BLUE;
    
    //创建开始任务
    xTaskCreate((TaskFunction_t )start_task,            //任务函数
                (const char*    )"start_task",          //任务名称
                (uint16_t       )START_STK_SIZE,        //任务堆栈大小
                (void*          )NULL,                  //传递给任务函数的参数
                (UBaseType_t    )START_TASK_PRIO,       //任务优先级
                (TaskHandle_t*  )&StartTask_Handler);   //任务句柄              
    vTaskStartScheduler();          //开启任务调度
}

//开始任务任务函数
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();
    
    //这里不用创建计数型信号量,任务通知所需要任务通知值和任务通知状态存储在TCB中
    
    //创建释放信号量任务
    xTaskCreate((TaskFunction_t )SemapGive_task,             
                (const char*    )"semapgive_task",           
                (uint16_t       )SEMAPGIVE_STK_SIZE,        
                (void*          )NULL,                  
                (UBaseType_t    )SEMAPGIVE_TASK_PRIO,        
                (TaskHandle_t*  )&SemapGiveTask_Handler);   
    //创建获取信号量任务
    xTaskCreate((TaskFunction_t )SemapTake_task,     
                (const char*    )"semaptake_task",   
                (uint16_t       )SEMAPTAKE_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )SEMAPTAKE_TASK_PRIO,
                (TaskHandle_t*  )&SemapTakeTask_Handler);
    vTaskDelete(StartTask_Handler);  //删除开始任务
    taskEXIT_CRITICAL();
}

//释放计数型信号量任务函数
void SemapGive_task(void *pvParameters)
{
    u8 key,i=0;
    while(1)
    {
        key=KEY_Scan(0);  //扫描按键
        if(SemapTakeTask_Handler!=NULL)  
        //SemapTakeTask_Handler 表示获取任务通知的任务句柄,该任务句柄不为空,表示有任务正在等待信号量,也可以说计数型信号量的计数值大于0
        //如果该句柄不为空,那么按键KEY_UP发送任务通知,解除该任务的阻塞态!使得任务得以继续执行!
        {
            switch(key) //key 取值为 1 2 3 4 0,0表示无按键按下
            {
                case WKUP_PRES: //key 等于4
                    xTaskNotifyGive(SemapTakeTask_Handler); //发送任务通知,不带有通知值
                    break;
            }
                
        }
        i++;
        if(i==50)
        {
            i=0;
            LED0=!LED0;
        }
        vTaskDelay(10);   //延时10ms,也就是10个时钟节拍
    }
}

//获取计数型信号量任务函数
void SemapTake_task(void *pvParameters)
{
    u8 num;
    uint32_t NotifyValue;
    while(1)
    {
        NotifyValue = ulTaskNotifyTake(pdFALSE,portMAX_DELAY);  //获取任务通知
        //返回值:任务通知值减少或者清零之前的值。
        num++;
        LCD_ShowxNum(166,111,NotifyValue-1,3,16,0);         //显示当前任务通知值
        //因为返回值获取的是任务通知值减少或者清零之前的值,第一个参数设置为 pdFASLE,表示计数值减减,那么退出函数之前的值就是还没有减减的值
        //所以当前的任务通知值就是 NotifyValue-1;
        LCD_Fill(6,131,233,313,lcd_discolor[num%14]);   //刷屏  
        LED1=!LED1;
        vTaskDelay(1000);                               //延时1s,也就是1000个时钟节拍
    }
}

7. 任务通知模拟消息邮箱实验

        任务通知也可用来向任务发送数据,但是相对于用队列发送消息,任务通知向任务发送消息会受到很多限制

  •         1. 只能发送 32 位的数据值。
  •         2. 消息被保存为任务的任务通知值,而且一次只能保存一个任务通知值,相当于队列长度为 1。

        因此说任务通知可以模拟一个轻量级的消息邮箱而不是一个轻量级的消息队列。任务通知值就是消息邮箱的值

BaseType_t xTaskNotify(TaskHandle_t     xTaskToNotify,
                       uint32_t         ulValue,
                       eNotifyAction    eAction)

参数:

        xTaskToNotify:                任务句柄,指定任务通知是发送给哪个任务的。

        ulValue:                          任务通知值。

        eAction:                          任务通知更新的方法,eNotifyAction 是个枚举类型,在文件 task.h 中有如下定义:

typedef enum
{
    eNoAction=0,
    eSetBits,            //更新指定的位 bit
    eIncrement,          //通知值加一
    eSetValueWithOverwrite,    //覆写的方式更新通知值
    eSetValueWithoutOverwrite    //不覆写通知值
}eNotifyAction;

此参数可以选择枚举类型中的任意一个,不同的应用环境其选择也不同。

返回值:

        pdFAIL:        当参数 eAction 设置为 eSetValueWithoutOverwrite(不覆写) 的时候,如果任务通知值没有更新成功就返回 pdFAIL。

        pdPASS:      eAction 设置为其他选项的时候统一返回 pdPASS。

        发送数据可以使用函数 xTaskNotify() 或者 xTaskNotifyFromISR()函数的参数 eAction 设置 eSetValueWithOverwrite 或者 eSetValueWithoutOverwrite。如果参数 eAction 为 eSetValueWithOverwrite 的话不管接收任务的通知值是否已经被处理,这个通知值都会被更新。如果参数 eAction 为 eSetValueWithoutOverwrite 的话上一个通知值还没有被处理,那么新的任务通知值就不会被更新如果要读取任务通知值的话就使用函数 xTaskNotifyWait()

BaseType_t xTaskNotifyWait(uint32_t    ulBitsToClearOnEntry,
                           uint32_t    ulBitsToClearOnExit,
                           uint32_t*   pulNotificationValue,
                           TickType_t  xTicksToWait);

参数:

        ulBitsToClearOnEntry:        当没有接收到任务通知的时候将任务通知值与此参数的取反值进行按位与运算,当此参数为 0xffffffff 或者 ULONG_MAX 的时候就会将任务通知值清零。

        ulBitsToClearOnExit:           如果接收到了任务通知,在做完相应的处理退出函数之前将任务通知值与此参数的取反值进行按位与运算,当此参数为 0xffffffff 或者 ULONG_MAX 的时候就会将任务通知值清零。

        pulNotificationValue:            此参数用来保存任务通知值。

        xTickToWait:                        阻塞时间。

返回值:

        pdTRUE:        获取到了任务通知。

        pdFALSE:       任务通知获取失败。

        本实验设计三个任务:start_task、task1_task、Keyprocess_task 这三个任务的任务功能如下:

        start_task:用来创建其他 2 个任务。

        task1_task:读取按键的键值,然后将按键值作为任务通知发送给任务 Keyprocess_task。

        Keyprocess_task :按键处理任务,读取任务通知值,根据不同的通知值做相应的处理。

        实验需要三个按键 KEY_UP、KEY2 和 KEY0,不同的按键对应不同的按键值,任务 task1_task() 会将这些值作为任务通知发送给任务 Keyprocess_task 。FreeRTOS_队列-CSDN博客

7.1 实验程序

7.1.1 main.c

#include "stm32f4xx.h"  
#include "FreeRTOS.h" //这里注意必须先引用FreeRTOS的头文件,然后再引用task.h
#include "task.h"     //存在一个先后的关系
#include "LED.h"
#include "LCD.h"
#include "Key.h"
#include "usart.h"
#include "delay.h"
#include "string.h"
#include "beep.h"
#include "malloc.h"
#include "queue.h"
#include "limits.h"


//任务优先级
#define START_TASK_PRIO     1     //用于创建其他两个任务
//任务堆栈大小
#define START_STK_SIZE      256
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);

//任务优先级
#define TASK1_TASK_PRIO     2   //读取按键的键值,将键值发送到队列Key_Queue中,检查队列的剩余容量;相当于写队列
//任务堆栈大小
#define TASK1_STK_SIZE      256
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);

//任务优先级
#define KEYPROCESS_TASK_PRIO     3  //相当于读队列,读取队列Key_Queue中的消息,根据不同的消息值做出相应的处理
//任务堆栈大小
#define KEYPROCESS_STK_SIZE      256
//任务句柄
TaskHandle_t Keyprocess_Handler;
//任务函数
void Keyprocess_task(void *pvParameters);


//LCD刷屏时使用的颜色
int lcd_discolor[14]={  WHITE,  BLACK,  BLUE,   BRED,
                        GRED,   GBLUE,  RED,    MAGENTA,
                        GREEN,  CYAN,   YELLOW, BROWN,
                        BRRED,  GRAY};

int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);  //设置中断优先级分组为4
    delay_init(168);
    uart_init(115200);
    LED_Init();
    KEY_Init();
    BEEP_Init();
    LCD_Init();
    my_mem_init(SRAMIN);         //初始化内部内存池
    
    POINT_COLOR = RED;
	LCD_ShowString(10,10,200,16,16,"ATK STM32F407");	
	LCD_ShowString(10,30,200,16,16,"FreeRTOS Example");
	LCD_ShowString(10,50,200,16,16,"Task Notify Maibox");
	LCD_ShowString(10,70,220,16,16,"KEY_UP:LED1  KEY2:BEEP");
	LCD_ShowString(10,90,200,16,16,"KEY0:Refresh LCD");
    
    POINT_COLOR = BLACK;
    LCD_DrawRectangle(5,125,234,314);   //画矩形
    
    //创建开始任务
    xTaskCreate((TaskFunction_t )start_task,        //任务函数
                (const char*    )"start_task",      //任务名称
                (uint16_t       )START_STK_SIZE,    //任务堆栈大小
                (void*          )NULL,              //传递给任务函数的参数
                (UBaseType_t    )START_TASK_PRIO,   //任务优先级
                (TaskHandle_t*  )&StartTask_Handler);//任务句柄
    vTaskStartScheduler();      //开启任务调度
}

//开始任务的任务函数
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();
    
    //通过任务通知实现消息邮箱是不用创建消息队列的,所需的结构体均存储在 TCB 任务控制块中。
    
    //创建Task1任务
    xTaskCreate((TaskFunction_t )task1_task,         //任务函数
                (const char*    )"task1_task",       //任务名称
                (uint16_t       )TASK1_STK_SIZE,     //任务堆栈大小
                (void*          )NULL,               //传递给任务函数的参数
                (UBaseType_t    )TASK1_TASK_PRIO,    //任务优先级
                (TaskHandle_t*  )&Task1Task_Handler);//任务句柄
    //创建Task2任务--按键处理任务
    xTaskCreate((TaskFunction_t )Keyprocess_task,        //任务函数
                (const char*    )"Keyprocess_task",      //任务名称
                (uint16_t       )KEYPROCESS_STK_SIZE,    //任务堆栈大小
                (void*          )NULL,                   //传递给任务函数的参数
                (UBaseType_t    )KEYPROCESS_TASK_PRIO,   //任务优先级
                (TaskHandle_t*  )&Keyprocess_Handler);   //任务句柄
    vTaskDelete(StartTask_Handler);  //删除开始任务
    taskEXIT_CRITICAL();             //退出临界区
}

//task1任务函数
//将键值通过任务通知的方式发送给Keyprocess_Handler,采用覆写的方式发送
void task1_task(void *pvParameters)
{
    u8 key,i=0;
    BaseType_t err;
    while(1)
    {
        key=KEY_Scan(0);   //按键扫描 KEY0返回1,KEY1返回2,KEY2返回3,KEY_UP返回4,无按键按下返回0
        if((Keyprocess_Handler!=NULL)&&(key)) //Keyprocess_Handler不为空,意味着有任务在等待信号量,接收任务需要等待任务通知,并且有按键被按下
        {
            err=xTaskNotify((TaskHandle_t    )Keyprocess_Handler,       //接收任务通知的任务句柄
                            (uint32_t        )key,                      //任务通知值
                            (eNotifyAction   )eSetValueWithOverwrite);  //覆写的方式发送任务通知
            //pdFAIL:当参数eAction设置eSetValueWithoutOverwrite(不覆写)的时候,不覆盖意味着原本的任务通知值还没有被处理,那么新的任务通知值就要悬挂等待,如果任务通知值没有被更新成功就返回 pdFAIL
            //pdFALSE:eAction设置为其他选项的时候统一返回 pdPASS。
            if(err==pdFAIL) //覆写的情况下统一返回 pdPASS 表示任务通知发送成功!
            {
                printf("任务通知发送失败!\r\n");
            }
        }
        i++;
        if(i==50)
        {
            i=0;
            LED0=!LED0;
        }
        vTaskDelay(10);  //延时10ms,也就是10个时钟节拍    
    }
}

//KeyProcess_task函数
//接收按键的键值,根据不同的任务通知做出对应的处理
void Keyprocess_task(void *pvParameters)
{
    u8 num;
    uint32_t NotifyValue;
    BaseType_t err;
    
    while(1)
    {
        //获取任务通知值函数
        err = xTaskNotifyWait((uint32_t     )0x00,  //当没有接收到任务通知的时候将任务通知值与此参数的取反值进行按位与运算,当此参数为 0xffffffff 或者 ULONG_MAX 的时候就会将任务通知值清零。
                               //进入函数的时候不清除任务的bit
                              (uint32_t     )ULONG_MAX, //如果接收到了任务通知,在做完相应的处理退出函数之前将任务通知值与此参数的取反值进行按位与运算,当此参数为 0xffffffff 或者 ULONG_MAX 的时候就会将任务通知值清零。
                               //退出函数的时候清除所有的bit
                              (uint32_t*    )&NotifyValue,  //此参数用来保存任务通知值
                              (TickType_t   )portMAX_DELAY); //阻塞时间
        //返回值:pdTRUE:获取到了任务通知       
        //        pdFALSE:任务通知获取失败
        if(err==pdTRUE) //返回值为 pdTRUE,表示获取任务通知成功
        {
            switch((u8)NotifyValue)  //此参数 NotifyValue 用来保存任务值
            {
                case WKUP_PRES:   //KEY_UP控制LED1
                    LED1=!LED1;
                    break;
                case KEY2_PRES:   //KEY2控制蜂鸣器
                    BEEP=!BEEP;
                    break;
                case KEY0_PRES:   //KEY0刷新LCD背景
                    num++;
                    LCD_Fill(126,111,233,313,lcd_discolor[num%14]);
                    break;
            }
        }
    }
}

8. 任务通知模拟事件标志组实验

        事件标志组其实就是一组二进制事件标志(位),每个事件标志位的具体意义由应用程序编写者来决定。当一个任务等待事件标志组中的某几个标志(位)的时候可以进入阻塞态,当任务因为等待事件标志(位)而进入阻塞态以后这个任务就不会消耗 CPU。

        当任务通知用作事件标志组的话任务通知值就相当于事件组,这个时候任务通知值的每个 bit 用作事件标志(位)函数 xTaskNotifyWait() 代替事件标志组中的 API 函数 xEventGroupBits()函数 xTaskNotify() 和 xTaskNotifyFromISR()(函数的参数 eAction 为 eSetBits)代替事件标志组中的 API 函数 xEventGroupSetBits() 和 xEventGroupSetBitsFromISR()

8.1 实验程序

        本实验在 —— 事件标志组实验的基础上修改而来;FreeRTOS_事件标志组-CSDN博客

8.1.1 main.c

#include "stm32f4xx.h"  
#include "FreeRTOS.h" //这里注意必须先引用FreeRTOS的头文件,然后再引用task.h
#include "task.h"     //存在一个先后的关系
#include "LED.h"
#include "LCD.h"
#include "Key.h"
#include "usart.h"
#include "delay.h"
#include "string.h"
#include "beep.h"
#include "malloc.h"
#include "timer.h"
#include "exti.h"   
#include "limits.h"


//任务优先级
#define START_TASK_PRIO     1       //用于创建其他任务
//任务堆栈大小
#define START_STK_SIZE      256
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);

//任务优先级
#define EVENTSETBIT_TASK_PRIO     2       //读取按键值,根据不同的按键值将事件标志组中相应的事件位置 1 ,用来模拟事件的发生。
//任务堆栈大小 
#define EVENTSETBIT_STK_SIZE      256
//任务句柄
TaskHandle_t EventSetBit_Handler;
//任务函数
void eventsetbit_task(void *pvParameters);

//任务优先级
#define EVENTGROUP_TASK_PRIO     3       //同时等待事件标志组中的多个事件位,当这些事件位都置 1 的话就执行相应的处理,例程中的处理是刷新 LCD 指定区域的背景色。
//任务堆栈大小 
#define EVENTGROUP_STK_SIZE      256
//任务句柄
TaskHandle_t EventGroupTask_Handler;
//任务函数
void eventgroup_task(void *pvParameters);


//事件标志组是个类似于数组的形式,数组是以下角标的形式进行定义的
//EVENTBIT_0 操作数组下角标为 0 的位,1左移0位,也就是把数组第一位设置为1
//EVENTBIT_1 操作数组下角标为 1 的位,1左移1位,也就是把数组第二位设置为1
//EVENTBIT_2 操作数组下角标为 2 的位,1左移2位,也就是把数组第三位设置为1
#define EVENTBIT_0  (1<<0)          //事件位
#define EVENTBIT_1  (1<<1)
#define EVENTBIT_2  (1<<2)

//LCD刷屏时使用的颜色
int lcd_discolor[14]={	WHITE, BLACK, BLUE,  BRED,      
						GRED,  GBLUE, RED,   MAGENTA,       	 
						GREEN, CYAN,  YELLOW,BROWN, 			
						BRRED, GRAY };

int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);  
    delay_init(168);
    uart_init(115200);
    LED_Init();
    KEY_Init();
    EXTIX_Init();               //初始化外部中断
    LCD_Init();
    my_mem_init(SRAMIN);        //初始化内部内存池
    
    POINT_COLOR=RED;
    LCD_ShowString(30,10,200,16,16,"ATK STM32F407");
    LCD_ShowString(30,30,200,16,16,"FreeRTOS Example");
    LCD_ShowString(30,50,200,16,16,"Task Notify Event Group");
    LCD_ShowString(30,70,200,16,16,"ATOM@ALIENTEK");
    LCD_ShowString(30,90,200,16,16,"2023/11/06");
    
    POINT_COLOR = BLACK;
    LCD_DrawRectangle(5,130,234,314);   //画一个矩形
    POINT_COLOR = BLUE;
    LCD_ShowString(30,110,220,16,16,"Event Group Value:0");
    
    //创建开始任务
    xTaskCreate((TaskFunction_t)start_task,         //任务函数
                (const char*   )"start_task",       //任务名称
                (uint16_t      )START_STK_SIZE,     //任务堆栈大小
                (void*         )NULL,               //传递给任务函数的参数
                (UBaseType_t   )START_TASK_PRIO,    //任务优先级
                (TaskHandle_t* )&StartTask_Handler);//任务句柄
    vTaskStartScheduler();          //开启任务调度
}

//开始任务任务函数
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();
    
    //通过任务通知模拟事件标志组是不需要创建事件标志组的;任务通知所需的结构体保存在任务控制块TCB中;
    
    //创建设置事件位的任务
    xTaskCreate((TaskFunction_t)eventsetbit_task,         //任务函数
                (const char*   )"eventsetbit_task",       //任务名称
                (uint16_t      )EVENTSETBIT_STK_SIZE,     //任务堆栈大小
                (void*         )NULL,               //传递给任务函数的参数
                (UBaseType_t   )EVENTSETBIT_TASK_PRIO,    //任务优先级
                (TaskHandle_t* )&EventSetBit_Handler);//任务句柄
    //创建事件标志组处理任务
    xTaskCreate((TaskFunction_t)eventgroup_task,         //任务函数
                (const char*   )"eventgroup_task",       //任务名称
                (uint16_t      )EVENTGROUP_STK_SIZE,     //任务堆栈大小
                (void*         )NULL,               //传递给任务函数的参数
                (UBaseType_t   )EVENTGROUP_TASK_PRIO,    //任务优先级
                (TaskHandle_t* )&EventGroupTask_Handler);//任务句柄
    vTaskDelete(StartTask_Handler);  //删除开始任务
    taskEXIT_CRITICAL();
}

//设置事件位的任务
//读取按键值,根据不同的按键值将事件标志组中相应的事件位置 1 ,用来模拟事件的发生。
void eventsetbit_task(void *pvParameters)
{
    u8 key,i;
    while(1)
    {
        if(EventGroupTask_Handler!=NULL) //如果任务句柄不为空,表示有任务处于等待任务通知的状态;如果给接收任务发送任务通知,那么该任务将继续执行!
        {
            key = KEY_Scan(0);
            switch(key)
            {
                case KEY1_PRES:
                    //发送任务通知函数
                    xTaskNotify((TaskHandle_t   )EventGroupTask_Handler,    //接收任务通知的任务句柄
                                (uint32_t       )EVENTBIT_1,                //要更新的位bit
                                (eNotifyAction  )eSetBits);                 //更新方法为更新指定的位bit
                    break;
                case KEY2_PRES:
                    //发送任务通知函数
                    xTaskNotify((TaskHandle_t   )EventGroupTask_Handler,    //接收任务通知的任务句柄
                                (uint32_t       )EVENTBIT_2,                //要更新的位bit
                                (eNotifyAction  )eSetBits);                 //更新方法为更新指定的位bit
                    break;
            }
        }
        i++;
        if(i==50)
        {
            i=0;
            LED0=!LED0;
        }
        vTaskDelay(10); //延时10ms,也就是10个时钟节拍
    }
}

//事件标志组处理任务
//同时等待事件标志组中的多个事件位,当这些事件位都置 1 的话就执行相应的处理,例程中的处理是刷新 LCD 指定区域的背景色。
void eventgroup_task(void *pvParameters)
{
    u8 num=0,eventvalue;
    static u8 event0flag,event1flag,event2flag;
    uint32_t NotifyValue;
    BaseType_t err;
    
    while(1)
    {
        //获取任务通知值
        err=xTaskNotifyWait((uint32_t   )0x00,              //进入函数的时候不清除任务bit
                            (uint32_t   )ULONG_MAX,         //退出函数的时候清除所有的bit
                            (uint32_t*  )&NotifyValue,      //保存任务通知值
                            (TickType_t )portMAX_DELAY);    //设置阻塞时间为 portMAX_DELAY,只要获取不到任务通知值就一直等待
        
        if(err==pdPASS)  //任务通知获取成功
        {
            if((NotifyValue&EVENTBIT_0)!=0)         //NotifyValue表示获取的任务通知值,EVENTBIT_0表示事件标志组的第0位设置为1
            //想要 if 判断语句成立,那么就必须保证接收到的任务通知值 NotifyValue 第 0 位为 1,也就表示位 0 对应的事件成立!
            {
                event0flag=1;
            }
            else if((NotifyValue&EVENTBIT_1)!=0)    //事件1发生
            {
                event1flag=1;
            }
            else if((NotifyValue&EVENTBIT_2)!=0)    //事件2发生
            {
                event2flag=1;
            }
            
            eventvalue=event0flag|(event1flag<<1)|(event2flag<<2);  //模拟事件标志组值
            printf("任务通知值为:%d\r\n",eventvalue);
            LCD_ShowxNum(174,110,eventvalue,1,16,0);        //在LCD上显示当前的事件值
            
            if((event0flag==1)&&(event1flag==1)&&(event2flag==1))  //三个事件同时发生
            {
                num++;
                LED1=!LED1;
                LCD_Fill(6,131,233,313,lcd_discolor[num%14]);
                event0flag=0;   //标志清零
                event1flag=0;
                event2flag=0;
            }
        }
        else
        {
            printf("任务通过获取失败\r\n");
        }
    }
}

8.1.2 exit.c

#include "exti.h"
#include "delay.h" 
#include "key.h"
#include "FreeRTOS.h"
#include "task.h"

	   
//外部中断初始化程序
//初始化PE2~4,PA0为中断输入.
void EXTIX_Init(void)
{
	NVIC_InitTypeDef   NVIC_InitStructure;
	EXTI_InitTypeDef   EXTI_InitStructure;
	
	KEY_Init(); //按键对应的IO口初始化
 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);//使能SYSCFG时钟
 
	SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource4);//PE4 连接到中断线4
	
	/* 配置EXTI_Line4 */
	EXTI_InitStructure.EXTI_Line =  EXTI_Line4;
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;		//中断事件
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;				//中断线使能
	EXTI_Init(&EXTI_InitStructure);							//配置
 
	NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;		//外部中断4
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x06;//抢占优先级6
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;	//子优先级0
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//使能外部中断通道
	NVIC_Init(&NVIC_InitStructure);							//配置	   
}

#define EVENTBIT_0 (1<<0)
//任务句柄
extern TaskHandle_t EventGroupTask_Handler;     

//外部中断4服务程序
void EXTI4_IRQHandler(void)
{
	BaseType_t xHigherPriorityTaskWoken; //定义变量标记是否进行任务切换
	
	delay_xms(20);	//消抖
	if(KEY0==0)	 
	{				 
		xTaskNotifyFromISR((TaskHandle_t    )EventGroupTask_Handler,    //发送任务通知,指定任务通知是发送给哪个任务的。
                           (uint32_t        )EVENTBIT_0,                //任务通知值---要更新的位bit
                           (eNotifyAction   )eSetBits,                  //任务通知更新的方法---更新指定的bit
                           (BaseType_t*     )xHigherPriorityTaskWoken); //标记是否进行任务切换
        /*如果函数xTaskResumeFromISR()返回值为pdTRUE,那么说明要恢复的这个
        任务的任务优先级等于或者高于正在运行的任务(被中断打断的任务),所以在
        退出中断的时候一定要进行上下文切换!*/
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
	}		 
	 EXTI_ClearITPendingBit(EXTI_Line4);//清除LINE4上的中断标志位  
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/118351.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

『亚马逊云科技产品测评』活动征文|EC2云服务器一键部署wordpress博客

『亚马逊云科技产品测评』活动征文&#xff5c;EC2云服务器一键部署wordpress博客 授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技…

CCLINK IEFB总线转ETHERNET/IP网络的协议网关使欧姆龙和三菱的数据互通的简单配置方法

想要实现CCLINK IEFB总线和ETHERNET/IP网络的数据互通。 捷米JM-EIP-CCLKIE是一款ETHERNET/IP从站功能的通讯网关&#xff0c;该产品主要功能是实现CCLINK IEFB总线和ETHERNET/IP网络的数据互通。本网关连接到ETHERNET/IP总线和CCLINK IEFB总线上都可以做为从站使用。网关分别…

全面的Docker快速入门教程

前言&#xff1a; 都2023年了&#xff0c;你还在为了安装一个开发或者部署环境、软件而花费半天的时间吗&#xff1f;你还在解决开发环境能够正常访问&#xff0c;而发布正式环境无法正常访问的问题吗&#xff1f;你还在为持续集成和持续交付&#xff08;CI / CD&#xff09;工…

Android Framework学习之Activity启动原理

Android Activity启动原理 Android 13.0 Activity启动原理逻辑流程图如下&#xff1a;

[直播自学]-[汇川easy320]搞起来(2)看文档

2023.11.06.NIGHT 一 、读 《Easy320可编程逻辑控制器用户手册-CN-A02.PDF》 21&#xff1a;30 好现在看文档 里面提到 I/O滤波可设置&#xff1a; I/O支持短路保护&#xff0c;I/O指示灯程序控制 热量是向上走的&#xff0c;而PLC是大脑&#xff0c;所以放到最下面&am…

matlab图像处理

1.图片的读取&#xff08;下左&#xff09; Iimread(可爱猫咪.jpg);%图像读取&#xff0c;这里内为路径\名称,如&#xff1a;E:\examples\可爱猫咪.jpg figure,imshow(I);%图像显示 title(原图) 2.转为灰度图像&#xff08;上右&#xff09; I_grayrgb2gray(I); figure,imsho…

MySQL主从搭建,实现读写分离(基于docker)

一 主从配置原理 mysql主从配置的流程大体如图&#xff1a; 1&#xff09;master会将变动记录到二进制日志里面&#xff1b; 2&#xff09;master有一个I/O线程将二进制日志发送到slave; 3) slave有一个I/O线程把master发送的二进制写入到relay日志里面&#xff1b; 4&#xf…

【逗老师的无线电】Debian Linux手工编译安装MMDVM

看我干了啥&#xff0c;在Vmware里面装了一个Debian Linux并且运行了MMDVMHost&#xff0c;来支持业余无线电通联 开始之前先举个手&#xff0c;有多少朋友能分清MMDVM和Pi-Star关系的&#xff1f; MMDVM、Pi-Star和树莓派的关系 咱们先科普一下这个小知识点。各位HAM们应…

【Head First 设计模式】-- 策略模式

一、背景 Head First 设计模式第一章设计模式入门–策略模式 二、工具箱的工具&#xff08;本章&#xff09; 1、OO基础 封装 继承 多态 抽象 2、OO原则 封装变化 面向接口编程&#xff0c;而非面向实现编程 组合优于继承 3、OO模式 策略模式&#xff0c;所谓策略模式就是定义…

操作系统:文件管理(二)文件系统

一战成硕 4.3 文件系统4.3.1 文件系统结构4.3.2 文件系统布局4.3.3 外存空闲空间管理4.3.4 虚拟文件系统 4.3 文件系统 4.3.1 文件系统结构 4.3.2 文件系统布局 文件系统在磁盘中的结构 文件系统在内存中的结构 内存中的信息用于管理文件系统并通过缓存提高性能&#xff0c;这…

【JavaEE】JVM 剖析

JVM 1. JVM 的内存划分2. JVM 类加载机制2.1 类加载的大致流程2.2 双亲委派模型2.3 类加载的时机 3. 垃圾回收机制3.1 为什么会存在垃圾回收机制?3.2 垃圾回收, 到底实在做什么?3.3 垃圾回收的两步骤第一步: 判断对象是否是"垃圾"第二步: 如何回收垃圾 1. JVM 的内…

数仓分层能减少重复计算,为啥能减少?如何减少?这篇文章包懂!

很多时候&#xff0c;看一些数据领域的文章&#xff0c;说到为什么做数据仓库、数据仓库要分层&#xff0c;我们经常会看到一些结论&#xff1a;因为有ABCD…等等理由&#xff0c;比如降低开发成本、减少重复计算等等好处 然后&#xff0c;多数人就记住了ABCD。但是&#xff0…

python3 阿里云api进行巡检发送邮件

python3 脚本爬取阿里云进行巡检 不确定pip能不能安装上&#xff0c;使用时候可以百度一下&#xff0c;脚本是可以使用的&#xff0c;没有问题的 太长时间了&#xff0c;pip安装依赖忘记那些了&#xff0c;使用科大星火询问了下&#xff0c;给了下面的&#xff0c;看看能不能使…

知识注入以对抗大型语言模型(LLM)的幻觉11.6

知识注入以对抗大型语言模型&#xff08;LLM&#xff09;的幻觉 摘要1 引言2 问题设置和实验2.1 幻觉2.2 生成响应质量 3 结果和讨论3.1 幻觉3.2 生成响应质量 4 结论和未来工作 摘要 大型语言模型&#xff08;LLM&#xff09;内容生成的一个缺点是产生幻觉&#xff0c;即在输…

WPF中的Binding的常见知识点与技巧

完全来源于十月的寒流&#xff0c;感谢大佬讲解 在XAML中&#xff0c;可以绑定到许多不同类型的数据源和属性。以下是一些可以绑定的常见数据源和属性&#xff1a; 属性&#xff1a;可以绑定到对象的属性&#xff0c;例如控件的Text、Visibility、IsEnabled等属性。 集合&am…

linux内的循环

格式 while 【 条件判断 】 do 语句体 done 上图 第一次代码&#xff0c;输入语句在外面&#xff0c;结果输入完&#xff08;非hello&#xff09;程序不断循环&#xff0c;没办法&#xff0c;ctrlc给程序终止了&#xff0c;然后把用户输入的语句放到了循环体里面…

vivo发布“蓝心千询”自然语言对话机器人

&#x1f989; AI新闻 &#x1f680; vivo发布“蓝心千询”自然语言对话机器人 摘要&#xff1a;vivo今日发布了“蓝心千询”自然语言对话机器人&#xff0c;基于蓝心大模型。蓝心千询可以进行知识信息的快速问答&#xff0c;文学创作、图片生成&#xff0c;甚至还能编写程序…

SAM 微调在医学上的尝试

1、2023下半年 1、 UNet与SAM结合的正确的道路SAMUS,一路SOTA没对手&#xff01; https://github.com/xianlin7/SAMUS 2、 本文提出 SonoSAM&#xff1a;一种用于分割超声图像上感兴趣对象的快速基础模型。 https://zhuanlan.zhihu.com/p/663988684 未开源 绿色是预测的&…

流媒体服务实现H5实时预览视频

目录 背景方案业务实践细节注意 待办 背景 客户aws服务磁盘存储告急&#xff0c;最高可扩容16T。排查如下&#xff1a;主要是视频文件存在大量复制使用的情况。例如发布节目时复制、预览时复制&#xff0c;这样上传一份视频后最大会有四份拷贝&#xff08;预览、普通发布、互动…

http中的Content-Type类型

浏览器的Content-Type 最近在做web端下载的时候需要给前端返回一个二进制的流&#xff0c;需要在请求头中设置一个 writer.Header().Set("Content-Type", "application/octet-stream")那么http中的Content-Type有具体有哪些呢&#xff1f;他们具体的使用场…