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,            //更新指定的位 biteIncrement,          //通知值加一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_RECEIVEDswitch( eAction )  //根据不同更新方式做不同的处理{ case eSetBits :                                                  (5) 将指定的位置1,也就是更新接收任务通知值的一个或多个bitpxTCB->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_RECEIVEDswitch( 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_NOTIFICATIONif( 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_NOTIFICATIONif( 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 4if(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);  //设置系统中断优先级分组4delay_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 等于4xTaskNotifyGive(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,            //更新指定的位 biteIncrement,          //通知值加一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);  //设置中断优先级分组为4delay_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,无按键按下返回0if((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控制LED1LED1=!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);                 //更新方法为更新指定的位bitbreak;case KEY2_PRES://发送任务通知函数xTaskNotify((TaskHandle_t   )EventGroupTask_Handler,    //接收任务通知的任务句柄(uint32_t       )EVENTBIT_2,                //要更新的位bit(eNotifyAction  )eSetBits);                 //更新方法为更新指定的位bitbreak;}}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;		//外部中断4NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x06;//抢占优先级6NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;	//子优先级0NVIC_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上的中断标志位  
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL数据脱敏(Data masking plugin functions)

对于企业而言&#xff0c;数据脱敏可以在数据共享或测试时用于保护敏感数据&#xff08;如信用卡&#xff0c;社保卡&#xff0c;地址等&#xff09;。通过对敏感数据进行脱敏处理&#xff0c;组织可以最大限度地降低数据泄露和未经授权访问的风险&#xff0c;同时仍能够使用真…

[Machine Learning][Part 8]神经网络的学习训练过程

目录 训练过程 一、建立模型&#xff1a; 二、建立损失函数 J(w,b): 三、寻找最小损失函数的(w,b)组合 为什么需要激活函数 激活函数种类 二分法逻辑回归模型 线性回归模型 回归模型 训练过程 一、建立模型&#xff1a; 根据需求建立模型&#xff0c;从前面神经网络的…

springboot常见网络相关错误及原因解析

在基于spring-boot开发过程尤其是上线后&#xff0c;经常出现网络相关的错误&#xff0c;令人难以琢磨和下手&#xff0c;所以就spring-boot使用过程中可能碰到的网络相关问题进行分析&#xff0c;结合网络转包、日志报错和前端输出&#xff0c;针对网络连接超时、连接被拒绝、…

JVM虚拟机:如何查看自己的JVM默认的垃圾回收器

只需要在程序运行的时候指定下面的参数就可以看到当前自己的JVM默认的垃圾回收器是什么&#xff1f;如下所示&#xff1a; 如上所示&#xff0c;默认使用的是G1回收器&#xff0c;这是我的电脑&#xff0c;因为我的电脑安装jdk的版本是1.9 如果你的jdk的版本是1.8&#xff0c;那…

K8s:部署 CNI 网络组件+k8s 多master集群部署+负载均衡及Dashboard k8s仪表盘图像化展示管理

目录 1 部署 CNI 网络组件 1.1 部署 flannel 1.2 部署 Calico 1.3 部署 CoreDNS 2 负载均衡部署 3 部署 Dashboard 1 部署 CNI 网络组件 1.1 部署 flannel K8S 中 Pod 网络通信&#xff1a; ●Pod 内容器与容器之间的通信 在同一个 Pod 内的容器&#xff08;Pod 内的容…

Pyhotn: Mac安装selenium没有chromedriver-114以上及chromedriver无法挪到/usr/bin目录下的问题

1.0 安装selenium 终端输入&#xff1a; pip install selenium 查看版本&#xff1a; pip show selenium2.0 安装chromedriver 查看chrome版本 网上大多数是&#xff0c;基本到114就停了。 https://registry.npmmirror.com/binary.html?pathchromedriver/ 各种搜索&#…

8-2、T型加减速计算简化【51单片机控制步进电机-TB6600系列】

摘要&#xff1a;本节介绍简化T型加减速计算过程&#xff0c;使其适用于单片机数据处理。简化内容包括浮点数转整型数计算、加减速对称处理、预处理计算 一、浮点数转整型数计算 1.1简化∆t_1计算 根据上一节内容已知 K0.676 step1.8/X&#xff08;x为细分值&#xff0c;1.8对…

Windows 系统服务器部署jar包时,推荐使用winsw,将jar包注册成服务,并设置开机启动。

一、其他方式不推荐的原因 1、Spring Boot生成的jar包&#xff0c;可以直接用java -jar运行&#xff0c;但是前提是需要登录用户&#xff0c;而且注销用户后会退出程序&#xff0c;所以不可用。 2、使用计划任务&#xff0c;写一个bat处理文件&#xff0c;里面写java -jar运行…

视频编辑软件Corel VideoStudio 会声会影2024中文剪辑使用教程

会声会影&#xff08;Corel VideoStudio&#xff09;2024为加拿大Corel公司发布的一款功能丰富的视频编辑软件。会声会影2023简单易用&#xff0c;具有史无前例的强大功能&#xff0c;拖放式标题、转场、覆叠和滤镜&#xff0c;色彩分级、动态分屏视频和新增强的遮罩创建器&…

技术分享 | 一文带你了解测试流程的体系

软件测试是软件质量保证的关键步骤。越早发现软件中存在的问题&#xff0c;修复问题的成本就越低&#xff0c;软件质量也就越高&#xff0c;软件发布后的维护费用越低。 为了能更好的保障软件质量&#xff0c;在软件测试的实践中&#xff0c;慢慢形成了一些流程用来达到这一目…

蓝桥云课--1014 第 1 场算法双周赛

2-数树数【算法赛】&#xff08;找规律&#xff09; 一、题目要求 二、思路 由此可以推导出来&#xff0c;当s[i]L时&#xff0c;下一个编号当前编号*2-1&#xff1b;当s[i]R时&#xff0c;下一个编号当前编号*2&#xff1b; 三、代码 #include<bits/stdc.h> #define…

Selenium学习(Java + Edge)

Selenium /səˈliːniəm/ 1. 简介 ​ Selenium是一个用于Web应用程序自动化测试工具。Selenium测试直接运行在浏览器中&#xff0c;就像真正的用户在操作一样。支持的浏览器包括IE、Mozilla Firefox、Safari、Google Chrome、Opera、Edge等。 ​ 适用于自动化测试&#x…

缺陷之灵魂操作bug

一、前言 正常来说&#xff0c;我们在测试缺陷的时候都是按照case来测试的&#xff0c;但是有些场景&#xff0c;例如说发散思维这种场景&#xff0c;就会找到一些比较不太正常、不好复现的缺陷&#xff0c;然后如果要辅助研发修复&#xff0c;就会极为痛苦。 二、场景描述 大…

装修服务预约小程序的内容如何

大小装修不断&#xff0c;市场中大小品牌也比较多&#xff0c;对需求客户来说&#xff0c;可以线下咨询也可以线上寻找品牌&#xff0c;总是可以找到满意的服务公司&#xff0c;而对装修公司来说如今线下流量匮乏&#xff0c;很多东西也难以通过线下方式承载&#xff0c;更需要…

腾讯云CVM服务器操作系统镜像大全

腾讯云CVM服务器的公共镜像是由腾讯云官方提供的镜像&#xff0c;公共镜像包含基础操作系统和腾讯云提供的初始化组件&#xff0c;公共镜像分为Windows和Linux两大类操作系统&#xff0c;如TencentOS Server、Windows Server、OpenCloudOS、CentOS Stream、CentOS、Ubuntu、Deb…

多目标优化中的“latent action”是什么?

2020 NeurIPS 中的“latent action”&#xff1a; Our model defines latent action as a boundary that splits the region represented by a node into a high-performing and a low performing region. 这里的latent action代表一个边界&#xff08;分类器&#xff09;&…

【案例】3D地球

效果图&#xff1a; 直接放源码 <!DOCTYPE html> <html> <head><meta http-equiv"Content-Type" content"text/html; charsetutf-8" /><meta name"viewport" content"initial-scale1.0, user-scalableno" …

Mysql高级——Mysql8一主一从,多主多从搭建

修改 /etc/hosts文件 ip地址 master1 ip地址 master2 ip地址 slave1 ip地址 slave2一主一从 create database master1db;create table master1db.master1tab(name char(50));insert into master1db.master1tab VALUES(1111);insert into master1db.master1tab VALUES(2222);m…

ARM 版 OpenEuler 22.03 部署 KubeSphere v3.4.0 不完全指南续篇

作者&#xff1a;运维有术 前言 知识点 定级&#xff1a;入门级KubeKey 安装部署 ARM 版 KubeSphere 和 KubernetesARM 版 KubeSphere 和 Kubernetes 常见问题 实战服务器配置 (个人云上测试服务器) 主机名IPCPU内存系统盘数据盘用途ks-master-1172.16.33.1661650200KubeSp…

苹果加大对印度的扶持,提高在其生产iphone的比重

KlipC报道&#xff1a;跟踪苹果产业链&#xff0c;有分析师预计2023年全球约12%-14%的iphone在印度生产&#xff0c;预计2024年&#xff0c;印度将生产20%-25%的iphone。 KlipC的合伙人Andi D表示&#xff1a;“近年来随着苹果对中国的以来&#xff0c;印度已经成为高科技制造和…