# 嵌入式系統設計:目標

目標:

  1. 學習與 AI 協作,這是每個人都需掌握的新領域。
  2. 學會以位元運算來思考。
  3. 獲得通過指標控制硬體的經驗。

嵌入式系統程式設計需要在硬體和資源限制下採取不同的方式。與傳統軟體開發不同,嵌入式系統的記憶體和處理能力有限,因此需要使用較小的資料單位,通常是單個位元。因此,位元運算對於精確控制資料至關重要。

在嵌入式系統中,我們還需要直接控制硬體,通常透過指標來操作記憶體映射的暫存器 (memory-mapped registers)。指標是 C 語言中最難的概念之一,但對於硬體管理至關重要。有效的記憶體使用和資源管理是嵌入式系統設計的關鍵,學會在這些限制下優化程式碼對於開發者來說至關重要。


# 嵌入式系統設計:實驗 1

這是一個讓 LED 閃爍的範例程式:

#include <stdio.h>
#include "NUC100Series.h"
#include "MCU_init.h"
#include "SYS_init.h"
int main(void)
{
    SYS_Init(); 
    GPIO_SetMode(PC, BIT12, GPIO_MODE_OUTPUT);
	
    while(1) {
        PC12 = 0;                    /* 打開 LED */
        CLK_SysTickDelay(100000);    /* 延遲 */
        PC12 = 1;                    /* 關閉 LED */
        CLK_SysTickDelay(100000);    /* 延遲 */		
    }
}

在這個範例中,你會發現我們經常使用 CLK_SysTickDelay() 函數。這個函數的目的是停止計時器,讓我們觀察延遲。那它是如何運作的呢?

以下是一些可能有幫助的提示:

  • 讀取源代碼中的註解。
  • 向 ChatGPT 或 AI 請教。
  • 它是硬體解決方案還是軟體解決方案?
  • 指標如何與記憶體映射的硬體互動?

# 關於 SysTick 的定義與設置

#define    __I    volatile const    /*!< 定義 ' 只讀 ' 權限  */
#define    __O    volatile          /*!< 定義 ' 只寫 ' 權限 */
#define    __IO   volatile          /*!< 定義 ' 讀寫 ' 權限 */
/* @brief  訪問系統計時器 (SysTick) 的結構類型 */
typedef struct
{
  __IO uint32_t CTRL;        /*!< 偏移量: 0x000 (R/W)  SysTick 控制和狀態寄存器 */
  __IO uint32_t LOAD;        /*!< 偏移量: 0x004 (R/W)  SysTick 重載值寄存器 */
  __IO uint32_t VAL;         /*!< 偏移量: 0x008 (R/W)  SysTick 當前值寄存器 */
  __I  uint32_t CALIB;       /*!< 偏移量: 0x00C (R/)  SysTick 校準寄存器 */
} SysTick_Type;
/* Cortex-M0 硬體的記憶體映射 */
#define SCS_BASE        (0xE000E000UL)                    /*!< 系統控制空間基址 */
#define SysTick_BASE    (SCS_BASE +  0x0010UL)            /*!< SysTick 基址 */
#define SysTick         ((SysTick_Type *) SysTick_BASE)   /*!< SysTick 配置結構 */
/* SysTick 控制 / 狀態寄存器定義 */
/*!< SysTick CTRL: COUNTFLAG 位置,遮罩 */
#define SysTick_CTRL_COUNTFLAG_Pos    16    
#define SysTick_CTRL_COUNTFLAG_Msk    (1UL << SysTick_CTRL_COUNTFLAG_Pos)
/*!< SysTick CTRL: CLKSOURCE 位置,遮罩 */
#define SysTick_CTRL_CLKSOURCE_Pos    2    
#define SysTick_CTRL_CLKSOURCE_Msk    (1UL << SysTick_CTRL_CLKSOURCE_Pos)
/*!< SysTick CTRL: ENABLE 位置,遮罩 */
#define SysTick_CTRL_ENABLE_Pos       0    
#define SysTick_CTRL_ENABLE_Msk       (1UL << SysTick_CTRL_ENABLE_Pos)
/* 時鐘變數定義 */
uint32_t SystemCoreClock = __HSI;               /*!< 系統時鐘頻率 (核心時鐘) */
uint32_t CyclesPerUs     = (__HSI / 1000000);   /*!< 每微秒的週期數 */
/**
  * @brief      該函數執行延遲功能。
  * @param [in]  us (微秒) 延遲時間。最大值為 2^24 / CPU 時鐘 (MHz)。
  *              例如: 50MHz => 335544us, 48MHz => 349525us, 28MHz => 699050us ...
  * @return     無
  * @details    使用 SysTick 生成延遲時間,單位為微秒。
  *             SysTick 時鐘源來自 HCLK,即與系統核心時鐘相同。
  *             用戶可以在使用該函數前使用 SystemCoreClockUpdate () 來自動計算 CyclesPerUs。
  */
__STATIC_INLINE void CLK_SysTickDelay(uint32_t us)
{
    SysTick->LOAD = us * CyclesPerUs;
    SysTick->VAL  = (0x00);
    SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk;
    /* 等待倒數到零 */
    while((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == 0);
    
    /* 禁用 SysTick 計數器 */
    SysTick->CTRL = 0;    
}

思考問題:

  1. 以下程式碼的值是多少?試著追蹤程式碼。
    SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk;
  2. 為什麼以及何時以下的 while 迴圈會終止?
    while((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == 0);
  3. SysTick 定義了一個指標,但它並未指向任何 SysTick_Type 的實例。它是如何運作的?
  4. 為什麼 volatile 關鍵字在嵌入式系統設計中經常被使用?