# 嵌入式系統設計:目標
目標:
- 學習與 AI 協作,這是每個人都需掌握的新領域。
- 學會以位元運算來思考。
- 獲得通過指標控制硬體的經驗。
嵌入式系統程式設計需要在硬體和資源限制下採取不同的方式。與傳統軟體開發不同,嵌入式系統的記憶體和處理能力有限,因此需要使用較小的資料單位,通常是單個位元。因此,位元運算對於精確控制資料至關重要。
在嵌入式系統中,我們還需要直接控制硬體,通常透過指標來操作記憶體映射的暫存器 (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; | |
} |
思考問題:
- 以下程式碼的值是多少?試著追蹤程式碼。
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk;
- 為什麼以及何時以下的 while 迴圈會終止?
while((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == 0);
- 宏
SysTick
定義了一個指標,但它並未指向任何SysTick_Type
的實例。它是如何運作的? - 為什麼
volatile
關鍵字在嵌入式系統設計中經常被使用?