你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】FreeRTOS列表和列表项详解

[复制链接]
STMCU小助手 发布时间:2022-3-26 14:00
01. 概述
要想看懂FreeRTOS 源码并学习其原理,有一个东西绝对跑不了,那就是FreeRTOS 的列表和列表项。列表和列表项是FreeRTOS 的一个数据结构,FreeRTOS 大量使用到了列表和列表项,它是FreeRTOS 的基石。要想深入学习并理解FreeRTOS,那么列表和列表项就必须首先掌握,否则后面根本就没法进行。

列表被FreeRTOS调度器使用,用于跟踪任务,处于就绪、挂起、延时的任务,都会被挂接到各自的列表中。用户程序如果有需要,也可以使用列表。

FreeRTOS列表使用指针指向列表项。一个列表(list)下面可能有很多个列表项(list item),每个列表项都有一个指针指向列表。如下图所示。

1K2XUWH(U$[(NNQ]HB2$@_9.png

02. 列表
列表是FreeRTOS 中的一个数据结构,概念上和链表有点类似,列表被用来跟踪FreeRTOS中的任务。与列表相关的全部东西都在文件list.c 和list.h 中。在list.h 中定义了一个叫List_t 的结构体,如下:

  1. typedef struct xLIST
  2. {
  3.      listFIRST_LIST_INTEGRITY_CHECK_VALUE                        /*用于检测列表项数据是否完整*/
  4.      configLIST_VOLATILE UBaseType_t uxNumberOfItems;
  5.      ListItem_t * configLIST_VOLATILE pxIndex;                   /*用于遍历列表*/
  6.      MiniListItem_t xListEnd;                                    /*列表项*/
  7.      listSECOND_LIST_INTEGRITY_CHECK_VALUE                       /*用于检测列表项数据是否完整*/
  8. }List_t;
复制代码

@SXG_R@99Y5(WI_8J~~@H4Q.png

(1) 和(5) 、这两个都是用来检查列表完整性的, 需要将宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 设置为1,开启以后会向这两个地方分别添加一个变量xListIntegrityValue1 和xListIntegrityValue2,在初始化列表的时候会这两个变量中写入一个特殊的值,默认不开启这个功能。
(2)、uxNumberOfItems 用来记录列表中列表项的数量。
(3)、pxIndex 用来记录当前列表项索引号,用于遍历列表。
(4)、列表中最后一个列表项,用来表示列表结束,此变量类型为MiniListItem_t,这是一个迷你列表项。

03. 列表项
列表项就是存放在列表中的项目,FreeRTOS 提供了两种列表项:列表项和迷你列表项。这两个都在文件list.h 中有定义。

xLIST_ITEM列表项

  1. struct xLIST_ITEM
  2. {
  3.      listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE           /*用于检测列表项数据是否完整*/
  4.      configLIST_VOLATILETickType_t xItemValue;           /*列表项值*/
  5.      struct xLIST_ITEM * configLIST_VOLATILE pxNext;      /*指向列表中下一个列表项*/
  6.      struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;  /*指向列表中上一个列表项*/
  7.      void * pvOwner;                                     /*指向一个任务TCB*/
  8.      void * configLIST_VOLATILE pvContainer;             /*指向包含该列表项的列表 */
  9.      listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE          /*用于检测列表项数据是否完整*/
  10. };
  11. typedef struct xLIST_ITEM ListItem_t;
复制代码

N%7G}3){B3XZ1T4SG)FW@VP.png

(1)和(7)、用法和列表一样,用来检查列表项完整性的。以后我们在学习列表项的时候不讨论这个功能!
(2)、xItemValue 为列表项值。
(3)、pxNext 指向下一个列表项。
(4)、pxPrevious 指向前一个列表项,和pxNext 配合起来实现类似双向链表的功能。
(5)、pvOwner 记录此链表项归谁拥有,通常是任务控制块。
(6)、pvContainer 用来记录此列表项归哪个列表。注意和pvOwner 的区别,在前面讲解任务控制块TCB_t 的时候说了在TCB_t 中有两个变量xStateListItem 和xEventListItem,这两个变量的类型就是ListItem_t,也就是说这两个成员变量都是列表项。以xStateListItem 为例,当创建一个任务以后xStateListItem 的pvOwner 变量就指向这个任务的任务控制块,表示xSateListItem属于此任务。当任务就绪态以后xStateListItem 的变量pvContainer 就指向就绪列表,表明此列表项在就绪列表中。举个通俗一点的例子:小王在上二年级,他的父亲是老王。如果把小王比作列表项,那么小王的pvOwner 属性值就是老王,小王的pvContainer 属性值就是二年级。

xMINI_LIST_ITEM列表项

  1. struct xMINI_LIST_ITEM
  2. {
  3.      listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE           /*用于检测列表项数据是否完整*/
  4.      configLIST_VOLATILE TickType_t xItemValue;
  5.      struct xLIST_ITEM * configLIST_VOLATILE pxNext;
  6.      struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
  7. };
  8. typedef struct xMINI_LIST_ITEM MiniListItem_t;
复制代码

$A`B7_1A1`S(1@90`[7OHZX.png

(1)、用于检查迷你列表项的完整性。

(2)、xItemValue 记录列表列表项值。
(3)、pxNext 指向下一个列表项。
(4)、pxPrevious 指向上一个列表项。
可以看出迷你列表项只是比列表项少了几个成员变量,迷你列表项有的成员变量列表项都有的,没感觉有什么本质区别啊?那为什么要弄个迷你列表项出来呢?那是因为有些情况下我们不需要列表项这么全的功能,可能只需要其中的某几个成员变量,如果此时用列表项的话会造成内存浪费!比如上面列表结构体List_t 中表示最后一个列表项的成员变量xListEnd 就是MiniListItem_t 类型的。

04. 列表相关宏
  1. /*
  2. * Access macro to set the owner of a list item.  The owner of a list item
  3. * is the object (usually a TCB) that contains the list item.
  4. *
  5. * \page listSET_LIST_ITEM_OWNER listSET_LIST_ITEM_OWNER
  6. * \ingroup LinkedList
  7. */
  8. #define listSET_LIST_ITEM_OWNER( pxListItem, pxOwner )    ( ( pxListItem )->pvOwner = ( void * ) ( pxOwner ) )

  9. /*
  10. * Access macro to get the owner of a list item.  The owner of a list item
  11. * is the object (usually a TCB) that contains the list item.
  12. *
  13. * \page listGET_LIST_ITEM_OWNER listSET_LIST_ITEM_OWNER
  14. * \ingroup LinkedList
  15. */
  16. #define listGET_LIST_ITEM_OWNER( pxListItem )             ( ( pxListItem )->pvOwner )

  17. /*
  18. * Access macro to set the value of the list item.  In most cases the value is
  19. * used to sort the list in descending order.
  20. *
  21. * \page listSET_LIST_ITEM_VALUE listSET_LIST_ITEM_VALUE
  22. * \ingroup LinkedList
  23. */
  24. #define listSET_LIST_ITEM_VALUE( pxListItem, xValue )     ( ( pxListItem )->xItemValue = ( xValue ) )

  25. /*
  26. * Access macro to retrieve the value of the list item.  The value can
  27. * represent anything - for example the priority of a task, or the time at
  28. * which a task should be unblocked.
  29. *
  30. * \page listGET_LIST_ITEM_VALUE listGET_LIST_ITEM_VALUE
  31. * \ingroup LinkedList
  32. */
  33. #define listGET_LIST_ITEM_VALUE( pxListItem )             ( ( pxListItem )->xItemValue )

  34. /*
  35. * Access macro to retrieve the value of the list item at the head of a given
  36. * list.
  37. *
  38. * \page listGET_LIST_ITEM_VALUE listGET_LIST_ITEM_VALUE
  39. * \ingroup LinkedList
  40. */
  41. #define listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxList )        ( ( ( pxList )->xListEnd ).pxNext->xItemValue )

  42. /*
  43. * Return the list item at the head of the list.
  44. *
  45. * \page listGET_HEAD_ENTRY listGET_HEAD_ENTRY
  46. * \ingroup LinkedList
  47. */
  48. #define listGET_HEAD_ENTRY( pxList )                      ( ( ( pxList )->xListEnd ).pxNext )

  49. /*
  50. * Return the next list item.
  51. *
  52. * \page listGET_NEXT listGET_NEXT
  53. * \ingroup LinkedList
  54. */
  55. #define listGET_NEXT( pxListItem )                        ( ( pxListItem )->pxNext )

  56. /*
  57. * Return the list item that marks the end of the list
  58. *
  59. * \page listGET_END_MARKER listGET_END_MARKER
  60. * \ingroup LinkedList
  61. */
  62. #define listGET_END_MARKER( pxList )                      ( ( ListItem_t const * ) ( &( ( pxList )->xListEnd ) ) )

  63. /*
  64. * Access macro to determine if a list contains any items.  The macro will
  65. * only have the value true if the list is empty.
  66. *
  67. * \page listLIST_IS_EMPTY listLIST_IS_EMPTY
  68. * \ingroup LinkedList
  69. */
  70. #define listLIST_IS_EMPTY( pxList )                       ( ( ( pxList )->uxNumberOfItems == ( UBaseType_t ) 0 ) ? pdTRUE : pdFALSE )

  71. /*
  72. * Access macro to return the number of items in the list.
  73. */
  74. #define listCURRENT_LIST_LENGTH( pxList )                 ( ( pxList )->uxNumberOfItems )

  75. /*
  76. * Access function to obtain the owner of the next entry in a list.
  77. *
  78. * The list member pxIndex is used to walk through a list.  Calling
  79. * listGET_OWNER_OF_NEXT_ENTRY increments pxIndex to the next item in the list
  80. * and returns that entry's pxOwner parameter.  Using multiple calls to this
  81. * function it is therefore possible to move through every item contained in
  82. * a list.
  83. *
  84. * The pxOwner parameter of a list item is a pointer to the object that owns
  85. * the list item.  In the scheduler this is normally a task control block.
  86. * The pxOwner parameter effectively creates a two way link between the list
  87. * item and its owner.
  88. *
  89. * @param pxTCB pxTCB is set to the address of the owner of the next list item.
  90. * @param pxList The list from which the next item owner is to be returned.
  91. *
  92. * \page listGET_OWNER_OF_NEXT_ENTRY listGET_OWNER_OF_NEXT_ENTRY
  93. * \ingroup LinkedList
  94. */
  95. #define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )                                           \
  96.     {                                                                                          \
  97.         List_t * const pxConstList = ( pxList );                                               \
  98.         /* Increment the index to the next item and return the item, ensuring */               \
  99.         /* we don't return the marker used at the end of the list.  */                         \
  100.         ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;                           \
  101.         if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \
  102.         {                                                                                      \
  103.             ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;                       \
  104.         }                                                                                      \
  105.         ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;                                         \
  106.     }


  107. /*
  108. * Access function to obtain the owner of the first entry in a list.  Lists
  109. * are normally sorted in ascending item value order.
  110. *
  111. * This function returns the pxOwner member of the first item in the list.
  112. * The pxOwner parameter of a list item is a pointer to the object that owns
  113. * the list item.  In the scheduler this is normally a task control block.
  114. * The pxOwner parameter effectively creates a two way link between the list
  115. * item and its owner.
  116. *
  117. * @param pxList The list from which the owner of the head item is to be
  118. * returned.
  119. *
  120. * \page listGET_OWNER_OF_HEAD_ENTRY listGET_OWNER_OF_HEAD_ENTRY
  121. * \ingroup LinkedList
  122. */
  123. #define listGET_OWNER_OF_HEAD_ENTRY( pxList )            ( ( &( ( pxList )->xListEnd ) )->pxNext->pvOwner )

  124. /*
  125. * Check to see if a list item is within a list.  The list item maintains a
  126. * "container" pointer that points to the list it is in.  All this macro does
  127. * is check to see if the container and the list match.
  128. *
  129. * @param pxList The list we want to know if the list item is within.
  130. * @param pxListItem The list item we want to know if is in the list.
  131. * @return pdTRUE if the list item is in the list, otherwise pdFALSE.
  132. */
  133. #define listIS_CONTAINED_WITHIN( pxList, pxListItem )    ( ( ( pxListItem )->pxContainer == ( pxList ) ) ? ( pdTRUE ) : ( pdFALSE ) )

  134. /*
  135. * Return the list a list item is contained within (referenced from).
  136. *
  137. * @param pxListItem The list item being queried.
  138. * @return A pointer to the List_t object that references the pxListItem
  139. */
  140. #define listLIST_ITEM_CONTAINER( pxListItem )            ( ( pxListItem )->pxContainer )

  141. /*
  142. * This provides a crude means of knowing if a list has been initialised, as
  143. * pxList->xListEnd.xItemValue is set to portMAX_DELAY by the vListInitialise()
  144. * function.
  145. */
  146. #define listLIST_IS_INITIALISED( pxList )                ( ( pxList )->xListEnd.xItemValue == portMAX_DELAY )
复制代码

05. 列表相关函数
5.1 初始化列表

列表结构体中包含一个列表项成员,主要用于标记列表结束。初始化列表就是把这个列表项插入到列表中。

  1. void vListInitialise( List_t * const pxList )
  2. {
  3.      /*列表索引指向列表项*/
  4.      pxList->pxIndex = ( ListItem_t * )&( pxList->xListEnd );                  
  5.      /* 设置为最大可能值 */
  6.      pxList->xListEnd.xItemValue =portMAX_DELAY;

  7.      /* 列表项xListEnd的pxNext和pxPrevious指针指向了它自己 */
  8.      pxList->xListEnd.pxNext = (ListItem_t * ) &( pxList->xListEnd );
  9.      pxList->xListEnd.pxPrevious= ( ListItem_t * ) &( pxList->xListEnd );
  10.      pxList->uxNumberOfItems = ( UBaseType_t) 0U;

  11.      /* 设置为已知值,用于检测列表数据是否完整*/
  12.      listSET_LIST_INTEGRITY_CHECK_1_VALUE(pxList );
  13.      listSET_LIST_INTEGRITY_CHECK_2_VALUE(pxList );
  14. }
复制代码

如果宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则使能列表项数据完整性检查,则宏listSET_LIST_INTEGRITY_CHECK_1_VALUE()和listSET_LIST_INTEGRITY_CHECK_2_VALUE被一个已知值代替,默认为0x5a5a(16位架构)或者0x5a5a5a5a(32位架构)。

PQ7TE`T~5F]2RUTXY97~6%Q.png

5.2 初始化列表项
列表项的初始比较简单,只要确保列表项不在任何列表中即可。
  1. void vListInitialiseItem( ListItem_t * const pxItem )
  2. {
  3.      pxItem->pvContainer = NULL;

  4.      /*设置为已知值,用于检测列表项数据是否完整*/
  5.      listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE(pxItem );
  6.      listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE(pxItem );
  7. }
复制代码

如果宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则使能列表项数据完整性检查,则宏listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE和listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE会被两个已知的数值代替,默认为0x5a5a(16位架构)或者0x5a5a5a5a(32位架构)。

5.3 列表项插入函数
每个列表项对象都有一个列表项值(xItemValue),通常是一个被跟踪的任务优先级或是一个调度事件的计数器值。调用API函数vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem)可以将pxNewListItem指向的列表项插入到pxList指向的列表中,列表项在列表的位置由pxNewListItem->xItemValue决定,按照升序排列。

  1. void vListInsert( List_t * const pxList,
  2.                   ListItem_t * const pxNewListItem )
  3. {
  4.     ListItem_t * pxIterator;
  5.     const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;

  6.     /* 检查列表和列表项数据的完整性,仅当configASSERT()定义时有效。*/
  7.     /* Only effective when configASSERT() is also defined, these tests may catch
  8.      * the list data structures being overwritten in memory.  They will not catch
  9.      * data errors caused by incorrect configuration or use of FreeRTOS. */
  10.     listTEST_LIST_INTEGRITY( pxList );
  11.     listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );

  12.     /* Insert the new list item into the list, sorted in xItemValue order.
  13.      *
  14.      * If the list already contains a list item with the same item value then the
  15.      * new list item should be placed after it.  This ensures that TCBs which are
  16.      * stored in ready lists (all of which have the same xItemValue value) get a
  17.      * share of the CPU.  However, if the xItemValue is the same as the back marker
  18.      * the iteration loop below will not end.  Therefore the value is checked
  19.      * first, and the algorithm slightly modified if necessary. */
  20.     /*将新的列表项插入到列表,根据xItemValue的值升序插入列表。*/
  21.     if( xValueOfInsertion == portMAX_DELAY )
  22.     {
  23.         pxIterator = pxList->xListEnd.pxPrevious;
  24.     }
  25.     else
  26.     {
  27.         /* *** NOTE ***********************************************************
  28.         *  If you find your application is crashing here then likely causes are
  29.         *  listed below.  In addition see <a href="https://www.FreeRTOS.org/FAQHelp.html" target="_blank">https://www.FreeRTOS.org/FAQHelp.html</a> for
  30.         *  more tips, and ensure configASSERT() is defined!
  31.         *  <a href="https://www.FreeRTOS.org/a00110.html#configASSERT" target="_blank">https://www.FreeRTOS.org/a00110.html#configASSERT</a>
  32.         *
  33.         *   1) Stack overflow -
  34.         *      see <a href="https://www.FreeRTOS.org/Stacks-and-stack-overflow-checking.html" target="_blank">https://www.FreeRTOS.org/Stacks-and-stack-overflow-checking.html</a>
  35.         *   2) Incorrect interrupt priority assignment, especially on Cortex-M
  36.         *      parts where numerically high priority values denote low actual
  37.         *      interrupt priorities, which can seem counter intuitive.  See
  38.         *      and the definition
  39.         *      of configMAX_SYSCALL_INTERRUPT_PRIORITY on
  40.         *      
  41.         *   3) Calling an API function from within a critical section or when
  42.         *      the scheduler is suspended, or calling an API function that does
  43.         *      not end in "FromISR" from an interrupt.
  44.         *   4) Using a queue or semaphore before it has been initialised or
  45.         *      before the scheduler has been started (are interrupts firing
  46.         *      before vTaskStartScheduler() has been called?).
  47.         **********************************************************************/

  48.         //xListEnd表示列表的末尾
  49.         for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) /*lint !e826 !e740 !e9087 The mini list structure is used as the list end to save RAM.  This is checked and valid. *//*lint !e440 The iterator moves to a different value, not xValueOfInsertion. */
  50.         {
  51.             /* There is nothing to do here, just iterating to the wanted
  52.              * insertion position. */
  53.         }
  54.     }

  55.     pxNewListItem->pxNext = pxIterator->pxNext;
  56.     pxNewListItem->pxNext->pxPrevious = pxNewListItem;
  57.     pxNewListItem->pxPrevious = pxIterator;
  58.     pxIterator->pxNext = pxNewListItem;

  59.     /* Remember which list the item is in.  This allows fast removal of the
  60.      * item later. */
  61.     pxNewListItem->pxContainer = pxList;

  62.     ( pxList->uxNumberOfItems )++;
  63. }
复制代码

根据xItemValue的值将新的列表项插入到列表。如果列表中存在与新列表项xItemValue值相同的列表项,则新插入的列表项位于它之后。如果列表项的xItemValue值等于portMAX_DELAY(列表结束标记,我们在讲列表数据结构时,说到每个列表数据结构体中都有一个列表项成员xListEnd,用于标记列表结束。xListEnd.xItemValue被初始化为一个常数,其值与硬件架构相关,为0xFFFF或者0xFFFFFFFF。这个常数在移植层定义,即宏portMAX_DELAY),则表示到达了列表结束位置。

5.4 列表项末尾插入
API函数vListInsertEnd()是简单的将列表项插入到列表的末端。

  1. void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
  2. {
  3. ListItem_t* const pxIndex = pxList->pxIndex;

  4.          /*检查列表和列表项数据的完整性,仅当configASSERT()定义时有效。*/
  5.          listTEST_LIST_INTEGRITY( pxList );
  6.          listTEST_LIST_ITEM_INTEGRITY(pxNewListItem );

  7.          /*向列表中插入新的列表项*/
  8.          pxNewListItem->pxNext = pxIndex;
  9.          pxNewListItem->pxPrevious =pxIndex->pxPrevious;

  10.          mtCOVERAGE_TEST_DELAY();

  11.          pxIndex->pxPrevious->pxNext =pxNewListItem;
  12.          pxIndex->pxPrevious = pxNewListItem;

  13.          pxNewListItem->pvContainer = ( void* ) pxList;

  14.          ( pxList->uxNumberOfItems )++;
  15. }
复制代码

5.5 列表项删除
列表项的删除只是将指定的列表项从列表中删除,并不会将这个列表项的内存给释放掉。

  1. UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
  2. {
  3. /* The list item knows which list it is in.  Obtain the list from the list
  4. * item. */
  5.     List_t * const pxList = pxItemToRemove->pxContainer;

  6.     pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
  7.     pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;

  8.     /* Only used during decision coverage testing. */
  9.     mtCOVERAGE_TEST_DELAY();

  10.     /* Make sure the index is left pointing to a valid item. */
  11.     if( pxList->pxIndex == pxItemToRemove )
  12.     {
  13.         pxList->pxIndex = pxItemToRemove->pxPrevious;
  14.     }
  15.     else
  16.     {
  17.         mtCOVERAGE_TEST_MARKER();
  18.     }

  19.     pxItemToRemove->pxContainer = NULL;
  20.     ( pxList->uxNumberOfItems )--;

  21.     return pxList->uxNumberOfItems;
  22. }
复制代码

5.6 列表遍历
  1. #define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )                                           \
  2.     {                                                                                          \
  3.         List_t * const pxConstList = ( pxList );                                               \
  4.         /* Increment the index to the next item and return the item, ensuring */               \
  5.         /* we don't return the marker used at the end of the list.  */                         \
  6.         ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;                           \
  7.         if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \
  8.         {                                                                                      \
  9.             ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;                       \
  10.         }                                                                                      \
  11.         ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;                                         \
  12.     }

复制代码


收藏 评论0 发布时间:2022-3-26 14:00

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版