嵌入式學習筆記(綜合提高篇 第二章) -- FreeRTOS的移植和應用


1.1    資料准備和分析

  上章節通過實現雙機通訊,了解如何設計和實現自定義協議,不過對於嵌入式系統來說,當然不僅僅包含協議,還有其它很多需要深入學習了解的知識,下面將列出我在工作和學習上遇到的嵌入式方向及知識點,雖然不一定全面,也基本上覆蓋大部分嵌入式應用。

  • 嵌入式RTOS(包括不限於uCos,FreeRTOS,RT-Thread)的移植和應用,以及配合的文件系統,協議棧等的移植
  • 圖像/攝像頭,音/視頻流和GUI/觸摸等,以及依托之上的菜單管理,圖像識別,視頻流壓縮等功能實現
  • 通訊協議/射頻,如支持以太網的TCP/IP協議, 2.4GHz無線的BLE協議,基於無線網絡的WIFI協議和常見的USB協議等,另外支持大部分雙機之間通過不同物理接口(如RS485,I2C,SPI, USART)通訊的自定義用戶協議,目前流行的物聯網設備大都基於此開發。
  • 低功耗設計,這里包含芯片選型,硬件的設計/調試,應用的整體設計配合,這些需要經驗和對芯片的深入了解

  這些都是我踏入行業后接觸到的嵌入式大方向的知識,有些有豐富的經驗,如RTOS操作系統的移植和應用,攝像頭驅動調試/圖像采集,TCP/IP協議(lwip協議移植,web,snmp,telnet服務器), 有些則淺嘗輒止,只是因為項目和開發需求,有過開發經驗但沒有深入了解,如低功耗設備開發,BLE/WIFI模組開發/應用,圖像算法的使用,GUI移植、菜單界面開發和RFID連接開發調試。說到這里,只是提醒這些並不需要全都需要深入掌握,之所以總結出來,也是希望能夠對未來發展方向選擇提供指導。

  這里我也根據開發板硬件和外部設備的限制,規划后續的深入了解,后續將結合嵌入式RTOS,菜單界面GUI開發,TCP/IP網絡協議(LWIP),USB協議開發,WIFI模塊(WIFI轉串口開發)來實踐,包含FreeRTOS的移植和應用開發,文件系統移植,GUI移植和開發,LWIP移植,USB協議移植以及相應的應用開發,對於低功耗,CMOS,視頻流則不在涉及,如果后續有機會將單獨整理這部分的經驗,下面開始本章的重點,FreeRTOS的移植或應用,具體要求要求如下:

  • 使用FreeRTOS實現uart點亮LED燈
  • 增加協議實現串口轉發功能
  • 支持串口遠程復位
  • 系統出現錯誤后能夠自我恢復,優化串口發送

  資料/設置(在之前基礎上添加):

  • FreeRTOSv10.2.0源代碼
  • FreeRTOS_Reference_Manual_V10.0.0文檔

  根據設計要求,在結合上述文檔,首先就可以確定需要掌握的知識點包含,上位機和協議部分需要在上章基礎上進行擴充,在主循環中的處理代碼可以由FreeRTOS的任務單獨完成,接收數據標志位由信號量替代,另外因為系統的復雜度提升,為了保證系統在出錯后能夠自恢復,需要硬件、軟件或者窗口看門狗的引入。經過這些分析,就可以將整個軟件的實現分解成以下任務:

  • FreeRTOS系統的移植 ,實現測試程序並運行正常(一般以點亮LED或者打印為准)。
  • 移植上章實現的Uart通訊協議,將主函數內循環改由任務完成,標志位由任務間通訊完成,並在上章協議的基礎上完善添加串口轉發和系統復位命令。
  • 代碼改進提高,引入看門狗任務,保證系統自恢復,串口發送改由DMA完成,提高代碼的處理效率

1.2     FreeRTOS系統的移植

  FreeRTOS的移植並不困難,從官網下載https://www.freertos.org/a00104.html最新源代碼壓縮包,解壓后復制FreeRTOSv10.2.0/FreeRTOS下的文件夾到到當前工程下(DEMO文件夾是提供的例程,可作為參考,不需要全部添加)。

  在工程界面目錄下添加FreeRTOS文件夾,將FreeRTOS/Source下的所有.c文件導入到目錄下,並添加portable/MemMang下的heap_4.c以及portable/RVDS/ARM_CM7/r0p1下的port.c文件;添加portable/RVDS/ARM_CM7/r0p1和Source/include到頭文件路徑,如下圖所示:

 

圖 1  FreeRTOS文件/路徑添加

  完成后編譯,此時會報大量error:  #5: cannot open source input file "FreeRTOSConfig.h": No such file or directory, 這表示缺少與FreeRTOS跨平台相關系統配置文件,此時不用太過擔心,只要去FreeRTOS/Demo/CORTEX_M7_STM32F7_STM32756G-EVAL_IAR_Keil下復制FreeRTOSConfig.h到當前目錄可訪問路徑下,但此時配置不能直接使用,需要注釋掉與芯片/代碼不相符的配置,修改后文件如下:

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
/*-----------------------------------------------------------
 * Application specific definitions.
 *
 * These definitions should be adjusted for your particular hardware and
 * application requirements.
 *
 * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
 * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
 *
 * See http://www.freertos.org/a00110.html
 *----------------------------------------------------------*/ 

//board.h不存在,修改為stm32f746xx.h
/* For definition of BOARD_MCK. */
#ifndef __IAR_SYSTEMS_ASM__
         /* Prevent chip.h being included when this file is included from the IAR
         port layer assembly file. */
         #include  “board.h”
         #include  "stm32f746xx.h"
#endif

#define configUSE_PREEMPTION                                                 1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION     1
#define configUSE_QUEUE_SETS                                                 1
#define configUSE_IDLE_HOOK                            0
#define configUSE_TICK_HOOK                                                    0        //時鍾鈎子函數暫不需要
#define configCPU_CLOCK_HZ                            ( ( unsigned long ) 216000000 )  //系統時鍾216Mhz
#define configTICK_RATE_HZ                             ( 1000 )
#define configMAX_PRIORITIES                                                   ( 6 )     //最高優先級改大
#define configMINIMAL_STACK_SIZE                                         ( ( unsigned short ) 128 )     
#define configTOTAL_HEAP_SIZE                                                ( ( size_t ) ( 18 * 1024 ) )    //系統堆改小
#define configMAX_TASK_NAME_LEN                                       ( 16 )                    //任務名變大
#define configUSE_TRACE_FACILITY                                         1
#define configUSE_16_BIT_TICKS                                                 0
#define configIDLE_SHOULD_YIELD                                          1
#define configUSE_MUTEXES                                                       1
#define configQUEUE_REGISTRY_SIZE                                       8
#define configCHECK_FOR_STACK_OVERFLOW                      0            //棧溢出檢查暫不實現
#define configUSE_RECURSIVE_MUTEXES                               1
#define configUSE_MALLOC_FAILED_HOOK                            0            //堆溢出檢查暫不實現
#define configUSE_APPLICATION_TASK_TAG                          0
#define configUSE_COUNTING_SEMAPHORES                        1

/* The full demo always has tasks to run so the tick will never be turned off.
The blinky demo will use the default tickless idle implementation to turn the
tick off. */
#define configUSE_TICKLESS_IDLE                                         0

/* Run time stats gathering definitions. */
#define configGENERATE_RUN_TIME_STATS             0

/* This demo makes use of one or more example stats formatting functions.  These
format the raw data provided by the uxTaskGetSystemState() function in to human
readable ASCII form.  See the notes in the implementation of vTaskList() within
FreeRTOS/Source/tasks.c for limitations. */
#define configUSE_STATS_FORMATTING_FUNCTIONS     1 

/* Co-routine definitions. */
#define configUSE_CO_ROUTINES                       0
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )

/* Software timer definitions. */
#define configUSE_TIMERS                                     1                          
#define configTIMER_TASK_PRIORITY                ( configMAX_PRIORITIES - 1 )
#define configTIMER_QUEUE_LENGTH               5
#define configTIMER_TASK_STACK_DEPTH       ( configMINIMAL_STACK_SIZE * 2 )

/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#define INCLUDE_vTaskPrioritySet                     1
#define INCLUDE_uxTaskPriorityGet                  1
#define INCLUDE_vTaskDelete                                1
#define INCLUDE_vTaskCleanUpResources        1
#define INCLUDE_vTaskSuspend                        1
#define INCLUDE_vTaskDelayUntil                         1
#define INCLUDE_vTaskDelay                                 1
#define INCLUDE_eTaskGetState                         1
#define INCLUDE_xTimerPendFunctionCall        1
/* Cortex-M specific definitions. */ #ifdef __NVIC_PRIO_BITS /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */ #define configPRIO_BITS __NVIC_PRIO_BITS #else #define configPRIO_BITS 3 /* 7 priority levels */ #endif /* The lowest interrupt priority that can be used in a call to a "set priority" function. */ #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0xF //Priority最低值為所有位為1即0xF /* The highest interrupt priority that can be used by any interrupt service routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER PRIORITY THAN THIS! (higher priorities are lower numeric values. */ #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 4 /* Interrupt priorities used by the kernel port layer itself. These are generic to all Cortex-M ports, and do not rely on any particular library functions. */ #define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!! See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */ #define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) /* Normal assert() semantics without relying on the provision of an assert.h header file. */ #define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); } /* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS standard names. */ #define xPortPendSVHandler PendSV_Handler #define vPortSVCHandler SVC_Handler #define xPortSysTickHandler SysTick_Handler #endif /* FREERTOS_CONFIG_H */
 然后在任務創建前添加代碼SCB->AIRCR = (VECTKEYSTAT | NVIC_PriorityGroup_4);在主函數中添加用於測試的LED閃爍任務和創建即可
static void vLEDTask( void *pvParameters )
{
    while(1)
    {
        vTaskDelay(  pdMS_TO_TICKS(500) );
        led_gpio_on();
        vTaskDelay(  pdMS_TO_TICKS(500) );
        led_gpio_off();
    }
}

void TestTaskCreate(void)
{
    //任務創建
    xTaskCreate( vLEDTask, "LED", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL );

    /* Start the scheduler. */
    vTaskStartScheduler();
}

  編譯后下載運行后,就可以看到LED閃爍,此時FreeRTOS系統就成功運行了。對於引入的FreeRTOSConfig.h文件修改,其中替換包含頭文件,時鍾頻率,鈎子函數和堆棧檢測標志位等,這些項的修改比較容易理解,看說明注釋即可,這里主要講為什么在初始化時添加SCB->AIRCR = (VECTKEYSTAT | NVIC_PriorityGroup_4);以及修改優先級,因為STM32F746支持最大4位優先級,也就是最大值(優先級最低)就是0xF, 所以configLIBRARY_LOWEST_INTERRUPT_PRIORITY的值同樣需要要保持一致;另外FreeRTOS為了保證系統的實時性,要求所有被管理中斷都配置為可搶占優先級中斷,參考下面這段話:

If you are using an STM32 with the STM32 driver library then ensure all the priority bits are assigned to be preempt priority

  這里給出來源參考http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html以及《Cortex-M3權威指南》7.2節優先級的定義部分, 至此,我們完成了任務的第一步,FreeRTOS的移植。

1.3     串口協議移植和功能添加

  串口通訊協議因為在開發時已經考慮到結構化,所以修改起來工作量不大,主要包含協議處理任務的實現,並將之前通過狀態標志位傳遞修改為消息實現,具體實現如下。

消息的創建和管理

//信號量創建UsartProtocolInit函數
USART_STATUS.rxsem = xSemaphoreCreateBinary();

//信號量等待ReceiveCheckData函數
rtype = xSemaphoreTake(USART_STATUS.rxsem, 1000);

//信號量的釋放USART6_IRQHandler函數
xSemaphoreGiveFromISR( USART_STATUS.rxsem, &xHigherPriorityTaskWoken );
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);

同時保留USART_STATUS.reflag = 0; 用於保證串口接收緩存在代碼處理完成后不會被后續接收覆蓋導致數據出錯。上述修改完成后,編譯下載后測試,此時通過協議修改LED能夠正常工作,如此便完成了協議的移植。

       至於功能說明的添加到ProtoCmdDone函數中,在上章的將0x01定義為USART設備,則可以添加如下代碼:

 case CMD_USART:
            sendbuffer(USART1, (char *)&pdata[1], len-1);
            size = CreateOutBuf(RT_OK, NULL, 0);
            break;

  添加新的代碼后,就可通過串口發送數據,測試結構如圖 3‑2所示

 圖 2  串口數據轉發示意圖

  串口實現軟件復位的實現就更簡單些,參考資料文件夾下的《嵌入式協議》內,添加軟件復位指令即可,代碼實現如下。

    if(pFrame->type == TYPE_DEVCMD)
  { size
= ProtoCmdDone(USART_STATUS.rxdata, REVERSAL16(pFrame->length));   } else if (pFrame->type == TYPE_DEVRST)
  { NVIC_SystemReset();   }
else size = CreateOutBuf(RT_FAIL, NULL, 0); //sendbuffer(USART6, (char *)USART_STATUS.txdata, size); sendbuffer_dma((char *)USART_STATUS.txdata, size); return RT_OK;

       如此便完成了串口協議的移植以及新增加了串口轉發及軟件復位功能。至於發送部分的改進則要結合Uart驅動和DMA部分,首先要確認Uart對應的DMA模塊和通道。

 

圖 3 DMA通道表

這里表示數據流6和數據流7都可以選擇,這里選擇數據流7,就可實現如下代碼。

void uart_dma_module_init(void)
{

    DMA_InitTypeDef DMA_InitStruct;
 
    RCC_ENABLE_CLK(RCC->AHB1ENR, RCC_CLK_AHB1_DMA2);

    //FLASH => USART
    DMA_InitStruct.Channel = DMA_CHANNEL_5;                  
    DMA_InitStruct.Direction = DMA_MEM_DIR_PER;         
    DMA_InitStruct.PeriphInc = DMA_PINC_DISABLE;              
    DMA_InitStruct.MemInc = DMA_MINC_ENABLE;                 
    DMA_InitStruct.PeriphDataAlignment = DMA_MSIZE_1;
    DMA_InitStruct.MemDataAlignment = DMA_MSIZE_1;  
    DMA_InitStruct.Mode = DMA_NORMAL;                       
    DMA_InitStruct.Priority = DMA_PL_HIGH;             
    DMA_InitStruct.FIFOMode = DMA_FIFOMODE_DISABLE;       
    DMA_InitStruct.FIFOThreshold = DMA_FIFOMODE_THRESHOLD_4_4;
    DMA_InitStruct.MemBurst = DMA_PBURST_1;          
    DMA_InitStruct.PeriphBurst = DMA_PBURST_1;     
    DMA_Init(DMA2_Stream7, &DMA_InitStruct);

    //啟動USART6的DMA發送
    USART6->CR3 |= (1<<7);
}

至於發送代碼則如下。

void sendbuffer_dma(char *ptr, int len)
{
#if CACHE_OPEN == 1
    //發送首地址必須為4的整倍數(設計要求)
    SCB_InvalidateDCache_by_Addr((uint32_t *)ptr, len/4+1);
#endif

    //DMA再次啟動時需要清除狀態位,否則只能發送一次
    //模塊使能位狀態注釋說明
    DMA2->HIFCR |= (0xf<<24) | (0x1<<22);
    DMA_Start(DMA2_Stream7,(uint32_t)ptr,(uint32_t)(&(USART6->TDR)), len);

    if(DMA_CheckDone(DMA2_Stream7, 1) != RT_OK)
    {
        vTaskDelay(  pdMS_TO_TICKS(1) );
    }
    while((USART6->ISR&(1<<6)) == 0);
}

1.4     項目程序改進

  結合上位機軟件的開發,我們對於本次應用的功能需求基本上已經全部完成,不過因為設計時要求系統異常時能夠自復位,這里介紹下什么是自復位,為什么需要自復位。

  所謂自復位很好理解,當芯片工作不正常時,能夠自己進行軟件復位,保證系統能夠恢復初始狀態繼續運行。不過正常情況下運行一段時間出錯是不允許的,這時一般是軟件有邏輯錯誤或者代碼錯誤,有可能是數組/指針越界,任務分支異常,這些是軟件測試時要解決和避免的問題,不能夠依靠自復位解決。不過有的情況下如突然的電流/電壓異常,外部溫度的急劇變化等外部環境會引起芯片不正常反應,進入正常情況不可能達到的分支,從而觸發異常,這時自復位就可以保證芯片在重啟后能夠繼續實現功能,而不是直接死機,可以一定程度上提高產品的可靠性。這里要深刻記住一點,自復位是功能的限制補充,而不是開發運行的手段,正常情況下復位首先需要的是定位和解決。

  對於大部分芯片,都提供看門狗來實現芯片自復位,當看門狗配置定時時鍾,啟動后,就需要在指定時間寫入寄存器保證內部定時器計數的刷新,否則就會軟件復位,如此當芯片進入異常分支時,便能夠自動復位。具體實現如下,當然在FreeRTOS下我們就可以創建任務單獨管理看門狗功能。

void vIdleTask(void *pvParameters)
{
    TickType_t delaytick = 2000;
#if SYSTEM_NODEBUG == 1
    iwdg_module_init();
#endif

while(1) { iwdg_module_reload(); vTaskDelay(delaytick); } } xTaskCreate(vIdleTask, "vIdleTaskk", configMINIMAL_STACK_SIZE, NULL, vIdleTaskPriority, NULL );

硬件部分的操作參考手冊與IWDG相關的說明,就可以輕松的實現。

void iwdg_module_init(void)
{
   IWDG->KR = 0x5555;
//獨立看門狗時鍾由內部32Khz時鍾提供 //則復位時間為 計數/(時鍾頻率/分頻系數),暫定8s //RLR值為8*(32000/64)= 4000 IWDG->PR = 0x4; IWDG->RLR = 0xFA0; IWDG->KR = 0xcccc; }

  至此,自復位的功能也已經實現完畢。

1.5     總結和知識點

  在本章節中,我們在上章的串口點亮LED任務代碼基礎上移植了FreeRTOS,擴展了串口實現,並實現了相應的上位機功能,同時增加了自復位功能,進一步優化了產品的可靠性,仔細思考全部流程,就會發現無論FreeRTOS的移植,還是串口的擴展功能實現,架構部分基本保持不變,這正是協議制定后開發的優勢。此外我們也了解了自復位和看門狗存在的意義,進一步理解產品開發中細節的重要性。事實上到這步,產品開發的框架已經走向了正軌,后續在這基礎上擴展功能就可以一步步實現心目中的作品,當然這並不是結束,恰恰是真正的開始,下章我們將暫時遠離自定義協議,從LCD顯示和GUI部分在講訴另外的功能實現。

  本章節涉及的部分細節知識點:

  1. FreeRTOS移植
  2. FreeRTOS任務創建/管理,消息通訊
  3. USART發送DMA模式
  4. 獨立看門狗(IWDG)

本項目開發相關資料和代碼實現見附件:

鏈接:https://pan.baidu.com/s/1pXkKG3y8ItXWqXLC4ODuaw 

提取碼:kzol


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM