主頁 > 軟體設計 > 嵌入式 C 語言(下)

嵌入式 C 語言(下)

2021-04-02 09:10:50 軟體設計

目錄

    • 條件編譯
    • 指標用法
    • 回呼函式
    • 位運算

條件編譯

可以使用預處理指令創建條件編譯,即可以使用這些指令告訴編譯器根據編譯時的條件執行或忽略代碼塊,

  1. #ifdef、#else和#endif指令
    我們用一個示例來看這幾個指令:
#ifdef HI /* 如果用#define 定義了符號 HI,則執行下面的陳述句 */
#include <stdio.h>
#define STR "Hello world"
#else /* 如果沒有用#define 定義符號 HI,則執行下面的陳述句 */
#include "mychar.h"
#define STR "Hello China"
#endif

#ifdef指令說明,如果前處理器已定義了后面的識別符號,則執行#else或#endif指令之前的所有指令并編譯所有C代碼,如果未定義且有#elif指令,則執行#else和#endif指令之間的代碼,

#ifdef、#else和C和if else很像,兩者的主要區別在于前處理器不識別用于標記塊的花括號{},因此它使用#else(如果需要的話)和#endif(必須存在)來標記指令塊,

  1. #ifndef指令
    #ifndef指令與#ifdef指令的用法類似,也可以和#else、#endif一起使用,但是它的邏輯和#ifdef指令相反,
  2. #if和#elif
    #if指令很想C語言中的if,#if后面緊跟整型常量運算式,如果運算式為非零,則運算式為真,可以在指令中使用C的關系運算子和邏輯運算子:
#if MAX==1
printf("1");
#endif
可以按照 if else 的形式使用#if #elif:
#if MAX==1
printf("1");
#elif MAX==2
printf("2");
#endif

條件編譯還有一個用途是讓程式更容易移植,改變檔案開頭部分的幾個關鍵的定義即可根據不同的系統設定不同的值和包含不同的檔案,

指標用法

什么是指標?從根本上看,指標是一個值為記憶體地址的變數,正如char型別變數的值是字符,int型別變數的值是整數,指標變數的值是地址,

因為計算機或者嵌入式設備的硬體指令非常依賴地址,指標在某種程度上把程式員想要表達的指令以更接近機器的方式表達,因此,使用指標的程式更有效率,尤其是指標能夠有效的處理陣列,而陣串列示法其實是在變相的使用指標,比如:陣列名是陣列首元素的地址,

要創建指標變數,首先要宣告指標變數的型別,加入想把ptr宣告為儲存int型別變數地址的指標,就要使用間接運算子*來宣告,

假設已知ptr指向bah,如下表示:

ptr = &bah;

然后使用間接運算子*找出儲存在bah中的值:value = *ptr;此運算子有時也被稱為解參考運算子,陳述句ptr=&bah;value=*ptr;放在一起的效果等效于:value=bah;

那么該如何宣告一個指標變數呢?是這樣嗎:

pointer ptr; // 不能這樣宣告一個指標變數

為什么不能這樣宣告一個指標變數呢?因為宣告指標變數時必須指定指標所指向變數的型別,不同的變數型別所占據的儲存空間是不同的,一些指標操作需要知道操作物件的大小,另外程式必須知道儲存在指定地址的資料型別,例如:

int *pi; // pi 是指向 int 型別變數的指標
char *str; // str 是指向 char 型別變數的指標
float *pf, *pg; // pf, pg 都是只想 float 型別變數的指標

型別說明符表明了指標所指向物件的型別,解參考符號*表明宣告的變數是一個指標,int *pi宣告的意思是pi是一個指標,*pi是int型別,如圖 5.3.4 所示,
在這里插入圖片描述
這僅僅是指標的簡單使用,實際指標的世界千變萬化,豐富多彩,縱使多年C語言開發的老手,有時在面對指標的使用也會出錯,后繼者更應謹慎求索,后面將會對指標常見的應用和注意事項進行介紹,

  1. 指標與陣列
    前面提到可以使用地址運算子&獲取變數所在的地址,而在陣列中同樣可以使用取地址運算子獲取陣列成員中任意成員的地址,例如:
int week[7] = {1, 2, 3, 4, 5, 6, 7};
int *pw;
pw = &week[2];
printf("week is: %d", *pw);

輸出的結果是:week is 3,對這段代碼的釋義參照上圖 5.3.3,

  1. 指標與函式
    指標在函式中的使用最簡單的是作為函式的形參,比如:
int sum(int *pdata)
{
int i = 0;
int temp = 0;
for(i=0;i<10;i++) {
temp = temp + (*pdata);
pdata++; }
return temp;
}

這個例子有幾點值得講解的地方,第1點指標pdata是作為函式的形參存在,指向一個儲存int型別變數的地址;第2點指標pdata++;陳述句執行后,pdata只想的地址自增的不是1,而是int型別所占的大小,加入pdata最初的值是0,int型別占2個位元組,那么pdata++;陳述句執行后,pdata的值就變成了2,而不是1,而*pdata的值是地址2所在的值不是地址1所在的值;第3點這個函式有個危險,即函式實作的是從pdata最初指向的地址開始往后的10個int型別變數的和,假如我們這樣使用:

int data[5] = {1, 2, 3, -1, -2};
int x = sum(data);

可以看到陣列data的陣列名即陣列的首地址作為引數輸入到函式sum里,而陣列的大小只有5個int,函式sum計算的卻是10個數的和,因而就會出現地址溢位,得不到正確的結果甚至于程式跑飛,為了避免這個問題,通常的解決方法是加一個數量形參:

int sum(int *pdata, int length)
{
int i = 0;
int temp = 0;
for(i=0;i<length;i++) {
temp = temp + (*pdata);
pdata++; }
return temp;
}x = sum(data, 5);

或者給出指標范圍:

int sum(int *pStart, int *pEnd)
{
int i = 0;
int temp = 0;
int length = (pEnd - pStart)/2; // 假設一個 int 占 2 個位元組
for(i=0;i<length;i++) {
temp = temp + (*pdata);
pdata++; }
return temp;
}x = sum(data, &data[4]);

指標與函式的關系除了指標作為函式形參外還有另一個重要的應用,那邊是函式指標,比如再typedef用法章節的那個例子:

typedef void (*pFunction)(void);

在這個例子中,首先*表明pFunction是一個指標變數,其次前面的void表示這個指標變數回傳一個void型別的值,最后括號里面的void表明這個函式指標的形參是void型別的,如何使用函式指標呼叫函式呢?

看下面這個例子:

int max(int a, int b)
{
return ((a>b)?a:b);
}
int main(void) {
int (*pfun)(int, int);
int a=-1, b=2, c=0;
pfun = max;
c=pfun(a, b);
printf("max: %d", c);
return 0; }

輸出的結果是:2,

  1. 指標與硬體地址
    指標與硬體地址的聯系在volatile用法章節的例子中驚鴻一現,沒有詳細介紹,下面做詳細說明,比如在STM32F103ZET6中內部SRAM的基地址是0x20000000,我們想對這片空間的前256個位元組寫入資料,就可以使用指標指向這個基地址,然后開始寫:
volatile unsigned char *pData = (volatile unsigned char *)(0x20000000);
int main(void) {
int i = 0;
for(i=0; i<256; i++) {
pData[i] = i+10; }
return 0; }

除了記憶體地址,還可以指向硬體外設的暫存器地址,操作方式與上述例子類似,
指標應用的基本原則:

  • 首先必須要指定指標的型別;
  • 如果是普通指標變數,非函式形參或者函式指標,必須要給指標變數指定地址,避免成為一個“野指標”;

回呼函式

在C語言中回呼函式是函式指標的高級應用,所謂回呼函式,一個籠統簡單的介紹就是一個被作為引數傳遞的函式,從字面上看,回呼函式的意思是:一個回去呼叫的函式,如何理解這句話呢?從邏輯上分析,要“回去”,必然存在著一個已知的目的地,然后在某一個時刻去訪問;那么回呼函式就是存在一個已知的函式體A,將這個函式體A的地址即函式名“A”(函式名即是這個函式體的函式指標,指向這個函式的地址)告知給另外某個函式B,當那個函式B執行到某一步的時候就會去執行函式A,

回呼函式的應用有很多,因之后的程式都是在STM32的HAL庫下撰寫的,因而此處我們僅從HAL庫出發來看其中的回呼函式,

我們僅以GPIO的HAL庫函式來看,檔案名“stm32f1xx_hal_gpio.c”,我們用逆分析的方法來看這個回呼函式,

首先是GPIO的回呼函式宣告:

__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)

可以看到其函式名是:HAL_GPIO_EXTI_Callback,形參是GPIO_Pin表示引腳號(Px0~Px15, x=A,B,C,D,E,F,G),從這個函式的名稱出發,可以大致明確這是一個引腳的外部中斷(EXTI)的回呼函式,然后大家看到前面還有個“__weak”,這是虛函式的修飾符,告訴編譯器如果用戶在其它地方用void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)重新定義了此回呼函式那么優先呼叫用戶定義的,否則呼叫這個虛函式修飾的回呼函式,

緊接著我們來看此回呼函式是在哪里被呼叫的:

void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
/* EXTI line interrupt detected */
if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u) {
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
HAL_GPIO_EXTI_Callback(GPIO_Pin); } }

可以看到是在GPIO的外部中斷服務函式中被呼叫的,與前面所說的這是一個外部引腳中斷回呼函式印證一致了,

GPIO的回呼函式到此就說完了,其實STM32的HAL庫中其它大多數的外設的回呼函式基本都是如此,用戶如果設計需求,就自己重定義需求的回呼函式,然后在中斷中被呼叫,

位運算

位運算是指二進制位之間的運算,在嵌入式系統設計中,常常要處理二進制的問題,例如將某個暫存器中的某一個位置1或者值0,將資料左移5位等,常用的位運算子如表 5.3.1 所示,
在這里插入圖片描述

  1. 按位與運算子(&)
    參與運算的兩個運算元,每個二進制位進行“與”運算,若兩個都為1,結果為1,否者為0,例如,1011&1001,第一位都為1,結果為1;第二位都為0,結果為0;第三位一個為1,一個為0,結果為0;第四位都為1,結果為1,最后結果為1001,

  2. 按位或運算子(|)
    參與運算的兩個運算元,每個二進制位進行“或”運算,若兩個都為0,結果為1,否者為1,例如,1011 | 1001,第一位都為1,結果為1;第二位都為0,結果為0;第三位一個為1,一個為0,結果為1;第四位都為1,結果為1,最后結果為1011,

  3. 按位取反運算子(~)
    按位取反運算子用于對一個二進制數按位取反,
    例如,~1011,第一位為1,取反為0;第二位為0,取反為1;第三位為1,取反為0,結果為1;第四位為1,取反為0,最后結果為0100,

  4. 左移(<<)和右移(>>)運算子
    左移(<<)運算子用于將一個數左移若干位,右移(>>)運算子用于將一個數右移若干位,例如,假設val為unsigned char型資料,對應的二進制數為10111001,若val=va<<3,表示val左移3位,然后賦值給val,左移程序中,高位移出去后被丟棄,低位補0,最后val結果為1100100;若val=val>>3,表示val右移3位,然后賦值給val,右移程序中,低位移出去后被丟棄,高位補0,最后val結果為00010111,

  5. 清零或置1
    在嵌入式中,經常使用位預算符實作清零或置1,
    例如,MCU的ODR暫存器控制引腳的輸出電平高低,暫存器為32位,每位控制一個引腳的電平,假設需要控制GPIOB的1號引腳輸出電平的高低,設定該暫存器第1位為1,輸出高電平,設定該暫存器第1位為0,輸出低電平,

#define GPIOB_ODR (*(volatile unsigned int *)(0x40010C0C))
GPIOB_ODR &= ~(1<<0);
GPIOB_ODR |= (1<<0);

第一行:使用#define定義了GPIOB_ODR 對應的記憶體地址為0x40010C0C,該地址為MCU的ODR暫存器地址,

第三行:GPIOB_ODR &= ~(1<<0)實際是GPIOB_ODR = GPIOB_ODR & (1<<0),先將GPIOB_ODR和(1<<0)的進行與運算,運算結果賦值給GPIOB_ODR,1<<0的值為00000000 00000000 00000000 00000010,
再取反為11111111 11111111 11111111 11111101,則GPIO_ODR的第1位和0與運算,結果必為0,其它位和1運算,由GPIO_ODR原來的值決定結果,這就實作了,只將GPIO_ODR的第1位清0,其它位保持不變的效果,
實作了單獨控制對應引腳電平輸出低,

第四行:GPIOB_ODR |= (1<<0)實際是GPIOB_ODR = GPIOB_ODR | (1<<0),先將GPIOB_ODR和(1<<0)的進行或運算,運算結果賦值給GPIOB_ODR,1<<0的值為00000000 00000000 00000000 00000010,則GPIO_ODR的第1位和0或運算,結果必為1,其它位和0運算,由GPIO_ODR原來的值決定結果,這就實作了,只將GPIO_ODR的第1位置1,其它位保持不變的效果,實作了單獨控制對應引腳電平輸出高,


百問網技術論壇:
http://bbs.100ask.net/

百問網嵌入式視頻官網:
https://www.100ask.net/index

百問網開發板:
淘寶:https://100ask.taobao.com/
天貓:https://weidongshan.tmall.com/

技術交流群(鴻蒙開發/Linux/嵌入式/驅動/資料下載)
QQ群:869222007

單片機-嵌入式Linux交流群:
QQ群:536785813

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/271414.html

標籤:其他

上一篇:Linux不講武德——開機無法進入登錄界面 卡在進度條就不動了

下一篇:C++中的繼承之菱形繼承 【超詳細 圖文+代碼】

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more