從0開始學FreeRTOS-(消息隊列)-5

来源:https://www.cnblogs.com/iot-dev/archive/2019/10/15/11681026.html
-Advertisement-
Play Games

問題解答 曾經有人問我,FreeRTOS那麼多API,到底怎麼記住呢? 我想說,其實API不難記,就是有點難找,因為FreeRTOS的API很多都是帶參巨集,所以跳來跳去的比較麻煩,而且註釋也很多,要找還真不是那麼容易,不過也沒啥,一般都會有API手冊的,我就告訴大家一下: FreeRTOS Kern ...


問題解答

曾經有人問我,FreeRTOS那麼多API,到底怎麼記住呢?
我想說,其實API不難記,就是有點難找,因為FreeRTOS的API很多都是帶參巨集,所以跳來跳去的比較麻煩,而且註釋也很多,要找還真不是那麼容易,不過也沒啥,一般都會有API手冊的,我就告訴大家一下:
FreeRTOS Kernel: Reference Manual
FreeRTOS內核:參考手冊,大家可以在官網下載,也能在後臺得到。
當然書本是英文的,如果英語像我這樣子不咋地的同學,可以用谷歌瀏覽器在官網直接看API手冊,直接翻譯一下就行了。傳送門:https://www.freertos.org/a00018.html
Reference Manual
FreeRTOS官網的API

FreeRTOS消息隊列

基於 FreeRTOS 的應用程式由一組獨立的任務構成——每個任務都是具有獨立許可權的程式。這些獨立的任務之間的通訊與同步一般都是基於操作系統提供的IPC通訊機制,而FreeRTOS 中所有的通信與同步機制都是基於隊列實現的。
消息隊列是一種常用於任務間通信的數據結構,隊列可以在任務與任務間、中斷和任務間傳送信息,實現了任務接收來自其他任務或中斷的不固定長度的消息。任務能夠從隊列裡面讀取消息,當隊列中的消息是空時,掛起讀取任務,用戶還可以指定掛起的任務時間;當隊列中有新消息時,掛起的讀取任務被喚醒並處理新消息,消息隊列是一種非同步的通信方式。

隊列特性

1.數據存儲

隊列可以保存有限個具有確定長度的數據單元。隊列可以保存的最大單元數目被稱為隊列的“深度”。在隊列創建時需要設定其深度和每個單元的大小。
通常情況下,隊列被作為 FIFO(先進先出)緩衝區使用,即數據由隊列尾寫入,從隊列首讀出。當然,由隊列首寫入也是可能的。
往隊列寫入數據是通過位元組拷貝把數據複製存儲到隊列中;從隊列讀出數據使得把隊列中的數據拷貝刪除。

2.讀阻塞

當某個任務試圖讀一個隊列時,其可以指定一個阻塞超時時間。在這段時間中,如果隊列為空,該任務將保持阻塞狀態以等待隊列數據有效。當其它任務或中斷服務常式往其等待的隊列中寫入了數據,該任務將自動由阻塞態轉移為就緒態。當等待的時間超過了指定的阻塞時間,即使隊列中尚無有效數據,任務也會自動從阻塞態轉移為就緒態。
由於隊列可以被多個任務讀取,所以對單個隊列而言,也可能有多個任務處於阻塞狀態以等待隊列數據有效。這種情況下,一旦隊列數據有效,只會有一個任務會被解除阻塞,這個任務就是所有等待任務中優先順序最高的任務。而如果所有等待任務的優先順序相同,那麼被解除阻塞的任務將是等待最久的任務。

說些題外話,ucos中是具有廣播消息的,當有多個任務阻塞在隊列上,當發送消息的時候可以選擇廣播消息,那麼這些阻塞的任務都能被解除阻塞。

3.寫阻塞

與讀阻塞想反,任務也可以在寫隊列時指定一個阻塞超時時間。這個時間是當被寫隊列已滿時,任務進入阻塞態以等待隊列空間有效的最長時間。
由於隊列可以被多個任務寫入,所以對單個隊列而言,也可能有多個任務處於阻塞狀態以等待隊列空間有效。這種情況下,一旦隊列空間有效,只會有一個任務會被解除阻塞,這個任務就是所有等待任務中優先順序最高的任務。而如果所有等待任務的優先順序相同,那麼被解除阻塞的任務將是等待最久的任務。

消息隊列的工作流程

1.發送消息

任務或者中斷服務程式都可以給消息隊列發送消息,當發送消息時,如果隊列未滿或者允許覆蓋入隊, FreeRTOS 會將消息拷貝到消息隊列隊尾,否則,會根據用戶指定的阻塞超時時間進行阻塞,在這段時間中,如果隊列一直不允許入隊,該任務將保持阻塞狀態以等待隊列允許入隊。當其它任務從其等待的隊列中讀取入了數據(隊列未滿),該任務將自動由阻塞態轉為就緒態。當任務等待的時間超過了指定的阻塞時間,即使隊列中還不允許入隊,任務也會自動從阻塞態轉移為就緒態,此時發送消息的任務或者中斷程式會收到一個錯誤碼 errQUEUE_FULL。
發送緊急消息的過程與發送消息幾乎一樣,唯一的不同是,當發送緊急消息時,發送的位置是消息隊列隊頭而非隊尾,這樣,接收者就能夠優先接收到緊急消息,從而及時進行消息處理。
下麵是消息隊列的發送API介面,函數中有FromISR則表明在中斷中使用的。
消息隊列入隊(發送)的API介面

1 /*-----------------------------------------------------------*/
 2 BaseType_t xQueueGenericSend( QueueHandle_t xQueue,      (1) 
 3                               const void * const pvItemToQueue,  (2)
 4                               TickType_t xTicksToWait,       (3)
 5                               const BaseType_t xCopyPosition )   (4)
 6 {
 7     BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
 8     TimeOut_t xTimeOut;
 9     Queue_t * const pxQueue = ( Queue_t * ) xQueue;
10 
11     /* 已刪除一些斷言操作 */
12 
13     for ( ;; ) {
14         taskENTER_CRITICAL();                    (5)
15         {
16             /* 隊列未滿 */
17             if ( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength )
18                  || ( xCopyPosition == queueOVERWRITE ) ) {  (6) 
19                 traceQUEUE_SEND( pxQueue );
20                 xYieldRequired =
21           prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); (7)
22 
23                 /* 已刪除使用隊列集部分代碼 */
24                 /* 如果有任務在等待獲取此消息隊列 */
25       if ( listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToReceive))==pdFALSE){ (8)
26                     /* 將任務從阻塞中恢復 */
27             if ( xTaskRemoveFromEventList(
28                   &( pxQueue->xTasksWaitingToReceive ) )!=pdFALSE) { (9)
29                         /* 如果恢復的任務優先順序比當前運行任務優先順序還高,
30                         那麼需要進行一次任務切換 */
31                         queueYIELD_IF_USING_PREEMPTION();    (10)
32                     } else {
33                         mtCOVERAGE_TEST_MARKER();
34                     }
35                 } else if ( xYieldRequired != pdFALSE ) {
36                     /* 如果沒有等待的任務,拷貝成功也需要任務切換 */
37                     queueYIELD_IF_USING_PREEMPTION();        (11)
38                 } else {
39                     mtCOVERAGE_TEST_MARKER();
40                 }
41 
42                 taskEXIT_CRITICAL();             (12)
43                 return pdPASS;
44             }
45             /* 隊列已滿 */
46             else {                       (13)
47                 if ( xTicksToWait == ( TickType_t ) 0 ) {
48                     /* 如果用戶不指定阻塞超時時間,退出 */
49                     taskEXIT_CRITICAL();         (14)
50                     traceQUEUE_SEND_FAILED( pxQueue );
51                     return errQUEUE_FULL;
52                 } else if ( xEntryTimeSet == pdFALSE ) { 
53                  /* 初始化阻塞超時結構體變數,初始化進入
54              阻塞的時間xTickCount和溢出次數xNumOfOverflows */
55                     vTaskSetTimeOutState( &xTimeOut );       (15)
56                     xEntryTimeSet = pdTRUE;
57                 } else {
58                     mtCOVERAGE_TEST_MARKER();
59                 }
60             }
61         }
62         taskEXIT_CRITICAL();                 (16)
63         /* 掛起調度器 */
64         vTaskSuspendAll();
65         /* 隊列上鎖 */
66         prvLockQueue( pxQueue );
67 
68         /* 檢查超時時間是否已經過去了 */
69         if (xTaskCheckForTimeOut(&xTimeOut, &xTicksToWait)==pdFALSE){ (17)
70             /* 如果隊列還是滿的 */
71             if ( prvIsQueueFull( pxQueue ) != pdFALSE ) {    (18)    
72                 traceBLOCKING_ON_QUEUE_SEND( pxQueue );  
73                 /* 將當前任務添加到隊列的等待發送列表中
74                    以及阻塞延時列表,延時時間為用戶指定的超時時間xTicksToWait */
75                 vTaskPlaceOnEventList(
76                    &( pxQueue->xTasksWaitingToSend ), xTicksToWait );(19)
77                 /* 隊列解鎖 */
78                 prvUnlockQueue( pxQueue );           (20)
79 
80                 /* 恢復調度器 */
81                 if ( xTaskResumeAll() == pdFALSE ) {
82                     portYIELD_WITHIN_API();
83                 }
84             } else {
85                 /* 隊列有空閑消息空間,允許入隊 */
86                 prvUnlockQueue( pxQueue );           (21)
87                 ( void ) xTaskResumeAll();
88             }
89         } else {
90             /* 超時時間已過,退出 */
91             prvUnlockQueue( pxQueue );               (22)
92             ( void ) xTaskResumeAll();
93 
94             traceQUEUE_SEND_FAILED( pxQueue );
95             return errQUEUE_FULL;
96         }
97     }
98 }
99 /*-----------------------------------------------------------*/

如果阻塞時間不為 0,任務會因為等待入隊而進入阻塞, 在將任務設置為阻塞的過程中, 系統不希望有其它任務和中斷操作這個隊列的 xTasksWaitingToReceive 列表和 xTasksWaitingToSend 列表,因為可能引起其它任務解除阻塞,這可能會發生優先順序翻轉。比如任務 A 的優先順序低於當前任務,但是在當前任務進入阻塞的過程中,任務 A 卻因為其它原因解除阻塞了,這顯然是要絕對禁止的。因此FreeRTOS 使用掛起調度器禁止其它任務操作隊列,因為掛起調度器意味著任務不能切換並且不准調用可能引起任務切換的 API 函數。但掛起調度器並不會禁止中斷,中斷服務函數仍然可以操作隊列阻塞列表,可能會解除任務阻塞、可能會進行上下文切換,這也是不允許的。於是,FreeRTOS解決辦法是不但掛起調度器,還要給隊列上鎖,禁止任何中斷來操作隊列。
下麵來看看流程圖:
消息隊列發送流程
相比在任務中調用的發送函數,在中斷中調用的函數會更加簡單一些, 沒有任務阻塞操作。
函數 xQueueGenericSend中插入數據後, 會檢查等待接收鏈表是否有任務等待,如果有會恢復就緒。如果恢復的任務優先順序比當前任務高, 則會觸發任務切換;但是在中斷中調用的這個函數的做法是返回一個參數標誌是否需要觸發任務切換,並不在中斷中切換任務。
在任務中調用的函數中有鎖定和解鎖隊列的操作, 鎖定隊列的時候, 隊列的事件鏈表不能被修改。 而在被中斷中發送消息的處理是: 當遇到隊列被鎖定的時候, 將新數據插入到隊列後, 並不會直接恢復因為等待接收的任務, 而是累加了計數, 當隊列解鎖的時候, 會根據這個計數, 對應恢復幾個任務。
遇到隊列滿的情況, 函數會直接返回, 而不是阻塞等待, 因為在中斷中阻塞是不允許的!!!

 1 BaseType_t xQueueGenericSendFromISR(
 2        QueueHandle_t xQueue,
 3        const void * const pvItemToQueue,
 4        /* 不在中斷函數中觸發任務切換, 而是返回一個標記 */
 5        BaseType_t * const pxHigherPriorityTaskWoken,
 6        const BaseType_t xCopyPosition )
 7{
 8    BaseType_t xReturn;
 9    UBaseType_t uxSavedInterruptStatus;
10    Queue_t * const pxQueue = ( Queue_t * ) xQueue;
11
12    uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
13    {
14        // 判斷隊列是否有空間插入新內容
15        if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
16        {
17            const int8_t cTxLock = pxQueue->cTxLock;
18
19            // 中斷中不能使用互斥鎖, 所以拷貝函數只是拷貝數據,
20            // 沒有任務優先順序繼承需要考慮
21            ( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
22
23            // 判斷隊列是否被鎖定
24            if( cTxLock == queueUNLOCKED )
25            {
26            #if ( configUSE_QUEUE_SETS == 1 )
27                // 集合相關代碼
28            #else /* configUSE_QUEUE_SETS */
29                {
30                    // 將最高優先順序的等待任務恢復到就緒鏈表
31                    if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
32                    {
33                        if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE)
34                        {
35                            // 如果有高優先順序的任務被恢復
36                            // 此處不直接觸發任務切換, 而是返回一個標記
37                            if( pxHigherPriorityTaskWoken != NULL )
38                            {
39                                *pxHigherPriorityTaskWoken = pdTRUE;
40                            }
41                        }
42                    }
43                }
44            #endif /* configUSE_QUEUE_SETS */
45            }
46            else
47            {
48                // 隊列被鎖定, 不能修改事件鏈表
49                // 增加計數, 記錄需要接觸幾個任務到就緒
50                // 在解鎖隊列的時候會根據這個計數恢復任務
51                pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );
52            }
53            xReturn = pdPASS;
54        }
55        else
56        {
57            // 隊列滿 直接返回 不阻塞
58            xReturn = errQUEUE_FULL;
59        }
60    }
61
62    // 恢復中斷的優先順序
63    portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
64
65    return xReturn;
66}

消息隊列讀取

消息讀取
任務調用接收函數收取隊列消息, 函數首先判斷當前隊列是否有未讀消息, 如果沒有, 則會判斷參數 xTicksToWait, 決定直接返回函數還是阻塞等待。
如果隊列中有消息未讀, 首先會把待讀的消息複製到傳進來的指針所指內, 然後判斷函數參數 xJustPeeking == pdFALSE的時候, 符合的話, 說明這個函數讀取了數據, 需要把被讀取的數據做出隊處理, 如果不是, 則只是查看一下(peek),只是返回數據,但是不會把數據清除。
對於正常讀取數據的操作, 清除數據後隊列會空出空位, 所以查看隊列中的等待列表中是否有任務等發送數據而被掛起, 有的話恢復一個任務就緒, 並根據優先順序判斷是否需要出進行任務切換。
對於只是查看數據的, 由於沒有清除數據, 所以沒有空間新空出,不需要檢查發送等待鏈表, 但是會檢查接收等待鏈表, 如果有任務掛起會切換其到就緒並判斷是否需要切換。

消息隊列出隊過程分析,其實跟入隊差不多,請看註釋:

 1 /*-----------------------------------------------------------*/
 2 BaseType_t xQueueGenericReceive( QueueHandle_t xQueue,       (1) 
 3                                  void * const pvBuffer,      (2)
 4                                  TickType_t xTicksToWait,    (3) 
 5                                  const BaseType_t xJustPeeking ) (4)
 6 {
 7     BaseType_t xEntryTimeSet = pdFALSE;
 8     TimeOut_t xTimeOut;
 9     int8_t *pcOriginalReadPosition;
10     Queue_t * const pxQueue = ( Queue_t * ) xQueue;
11 
12     /* 已刪除一些斷言 */
13     for ( ;; ) {
14         taskENTER_CRITICAL();                    (5)
15         {
16             const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting; 
17 
18             /* 看看隊列中有沒有消息 */
19             if ( uxMessagesWaiting > ( UBaseType_t ) 0 ) {   (6) 
20                 /*防止僅僅是讀取消息,而不進行消息出隊操作*/
21                 pcOriginalReadPosition = pxQueue->u.pcReadFrom;  (7)
22                 /* 拷貝消息到用戶指定存放區域pvBuffer */
23                 prvCopyDataFromQueue( pxQueue, pvBuffer );   (8)
24 
25                 if ( xJustPeeking == pdFALSE ) {     (9)
26                     /* 讀取消息並且消息出隊 */
27                     traceQUEUE_RECEIVE( pxQueue );   
28 
29                     /* 獲取了消息,當前消息隊列的消息個數需要減一 */
30                     pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;  (10)
31                     /* 判斷一下消息隊列中是否有等待發送消息的任務 */
32                     if ( listLIST_IS_EMPTY(          (11)
33                              &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ) {
34                         /* 將任務從阻塞中恢復 */
35                         if ( xTaskRemoveFromEventList(       (12)
36                                  &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ) {
37                             /* 如果被恢復的任務優先順序比當前任務高,會進行一次任務切換 */
38                             queueYIELD_IF_USING_PREEMPTION();    (13)
39                         } else {
40                             mtCOVERAGE_TEST_MARKER();
41                         }
42                     } else {
43                         mtCOVERAGE_TEST_MARKER();
44                     }
45                 } else {                 (14)
46                     /* 任務只是看一下消息(peek),並不出隊 */   
47                     traceQUEUE_PEEK( pxQueue );
48 
49                     /* 因為是只讀消息 所以還要還原讀消息位置指針 */
50                     pxQueue->u.pcReadFrom = pcOriginalReadPosition; (15)
51 
52                     /* 判斷一下消息隊列中是否還有等待獲取消息的任務 */
53                     if ( listLIST_IS_EMPTY(          (16)
54                              &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ) {
55                         /* 將任務從阻塞中恢復 */
56                         if ( xTaskRemoveFromEventList(           
57                               &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ) {
58                             /* 如果被恢復的任務優先順序比當前任務高,會進行一次任務切換 */
59                             queueYIELD_IF_USING_PREEMPTION();    
60                         } else {
61                             mtCOVERAGE_TEST_MARKER();
62                         }
63                     } else {
64                         mtCOVERAGE_TEST_MARKER();
65                     }
66                 }
67 
68                 taskEXIT_CRITICAL();             (17)
69                 return pdPASS;
70             } else {                     (18)
71                 /* 消息隊列中沒有消息可讀 */
72                 if ( xTicksToWait == ( TickType_t ) 0 ) {    (19)    
73                     /* 不等待,直接返回 */
74                     taskEXIT_CRITICAL();
75                     traceQUEUE_RECEIVE_FAILED( pxQueue );
76                     return errQUEUE_EMPTY;
77                 } else if ( xEntryTimeSet == pdFALSE ) {     
78                     /* 初始化阻塞超時結構體變數,初始化進入
79                     阻塞的時間xTickCount和溢出次數xNumOfOverflows */
80                     vTaskSetTimeOutState( &xTimeOut );       (20)
81                     xEntryTimeSet = pdTRUE;
82                 } else {
83                     mtCOVERAGE_TEST_MARKER();
84                 }
85             }
86         }
87         taskEXIT_CRITICAL();                 
88 
89         vTaskSuspendAll();
90         prvLockQueue( pxQueue );             (21)
91 
92         /* 檢查超時時間是否已經過去了*/
93         if ( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ) {(22)
94             /* 如果隊列還是空的 */
95             if ( prvIsQueueEmpty( pxQueue ) != pdFALSE ) {
96                 traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );   (23)    
97                 /* 將當前任務添加到隊列的等待接收列表中
98                    以及阻塞延時列表,阻塞時間為用戶指定的超時時間xTicksToWait */
99                 vTaskPlaceOnEventList(               
100                     &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
101                 prvUnlockQueue( pxQueue );
102                 if ( xTaskResumeAll() == pdFALSE ) {        
103                     /* 如果有任務優先順序比當前任務高,會進行一次任務切換 */
104                     portYIELD_WITHIN_API();         
105                 } else {
106                     mtCOVERAGE_TEST_MARKER();
107                 }
108             } else {
109                 /* 如果隊列有消息了,就再試一次獲取消息 */
110                 prvUnlockQueue( pxQueue );          (24)
111                 ( void ) xTaskResumeAll();
112             }
113         } else {
114             /* 超時時間已過,退出 */
115             prvUnlockQueue( pxQueue );              (25)
116             ( void ) xTaskResumeAll();
117 
118             if ( prvIsQueueEmpty( pxQueue ) != pdFALSE ) {
119                 /* 如果隊列還是空的,返回錯誤代碼errQUEUE_EMPTY */
120                 traceQUEUE_RECEIVE_FAILED( pxQueue );
121                 return errQUEUE_EMPTY;              (26)
122             } else {
123                 mtCOVERAGE_TEST_MARKER();
124             }
125         }
126     }
127 }
128 /*-----------------------------------------------------------*/

提示

如果隊列存儲的數據較大時,那最好是利用隊列來傳遞數據的指針而不是數據本身,因為傳遞數據的時候是需要CPU一位元組一位元組地將數據拷貝進隊列或從隊列拷貝出來。而傳遞指針無論是在處理速度上還是記憶體空間利用上都更有效。但是,當利用隊列傳遞指針時,一定要十分小心地做到以下兩點:

1.指針指向的記憶體空間的所有權必須明確

當任務間通過指針共用記憶體時,應該從根本上保證所不會有任意兩個任務同時修改共用記憶體中的數據,或是以其它行為方式使得共用記憶體數據無效或產生一致性問題。原則上,共用記憶體在其指針發送到隊列之前,其內容只允許被髮送任務訪問;共用記憶體指針從隊列中被讀出之後,其內容亦只允許被接收任務訪問。

2.指針指向的記憶體空間必須有效

如果指針指向的記憶體空間是動態分配的,只應該有一個任務負責對其進行記憶體釋放。當這段記憶體空間被釋放之後,就不應該有任何一個任務再訪問這段空間。
並且最最最重要的是禁止使用指針訪問任務棧上的空間,也就是局部變數。因為當棧發生改變後,棧上的數據將不再有效。

關註我

歡迎關註我公眾號

更多資料歡迎關註“物聯網IoT開發”公眾號!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 作為一個技術純小白,在Linux伺服器初始化MySQL資料庫的時候遇到了一點小問題: ​ 1、不會使用MySQL圖形工具,幾乎沒玩過 ​ 2、客戶的VPN沒有開放3306埠,沒法用navicat等工具連接資料庫 ​ 3、懶的再打開圖形工具,畢竟命令行接近萬能了…… 所以: 方法一、在初始化腳本文件 ...
  • 對稱加密演算法 非對稱加密演算法 單向散列(hash演算法) CA和證書 證書獲取 安全協議 OpenSSL openssl命令 創建CA和申請證書 ...
  • awk介紹 awk的工作原理 awk基本格式 awk之print格式 awk變數 awk之printf awk操作符 awk PATTERN awk action awk控制語句 awk控制語句if-else awk控制語句 while迴圈 awk控制語句do-while迴圈 awk控制語句 for ...
  • 準備 在移植之前,我們首先要獲取到FreeRTOS的官方的源碼包。這裡我們提供兩個下載鏈接: 一個是官網:http://www.freertos.org/ 另外一個是代碼托管網站:https://sourceforge.net/projects/freertos/files/FreeRTOS/ 這裡 ...
  • 寫在前面:傑傑這個月很忙~所以並沒有時間更新,現在健身房閉館裝修,晚上有空就更新一下!其實在公眾號沒更新的這段日子,每天都有兄弟在來關註我的公眾號,這讓我受寵若驚,在這裡謝謝大家的支持啦!!謝謝^ 在這裡我們就跟著火哥的書來學習一下FreeRTOS的消息隊列,這本書我覺得寫得很好,基本都講解到了,關 ...
  • 寫在前面 主要是為剛接觸 FreeRTOS 的用戶指出那些新手通常容易遇到的問題。這裡把最主要的篇幅放在棧溢出以及棧溢出j檢測上,因為棧相關的問題是初學者遇到最多的問題。 printf stdarg.c 當調用 C 標準庫 的函數時,棧空間使用量可能會急劇上升,特別是 IO 與字元串處理函數,比如 ...
  • 沒研究過操作系統的源碼都不算學過操作系統 FreeRTOS 時間管理 時間管理包括兩個方面:系統節拍以及任務延時管理。 系統節拍: 在前面的文章也講得很多,想要系統正常運行,那麼時鐘節拍是必不可少的, 的時鐘節拍通常由 提供,它周期性的產生定時中斷,所謂的時鐘節拍管理的核心就是這個定時中斷的服務程式 ...
  • FreeRTOS列表&列表項的源碼解讀 第一次看列表與列表項的時候,感覺很像是鏈表,雖然我自己的鏈表也不太會,但是就是感覺很像。 在FreeRTOS中,列表與列表項使用得非常多,是FreeRTOS的一個數據結構,學習過數據結構的同學都知道,數據結構能使我們處理數據更加方便快速,能快速找到數據,在Fr ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...