天天看點

【筆記】ARM裸機程式開發_part5

【筆記】ARM裸機程式開發_part5

吐槽~天啦噜真是累哭了,以前不會想到排版這麼累的。

以下是ARM裸機的最後一部分

12.1 I2C

I2C: 實體結構非常簡單,隻有兩根線。SCL+SDA

SCL是時鐘線,傳輸CLK信号,一般是I2C的主裝置向從屬裝置提供時鐘的通道

(serial clock 串行時鐘)

SDA是資料線,通信資料通過SDA傳輸

(serial data 串行資料)

通信特征:串行、同步、非差分、低速率

同步:工作在同一個時鐘。一般是方通過一根CLK信号線傳輸A自己的時鐘給B,B在A傳輸的時鐘下工作

同步通信的顯著特征就是有CLK線

非差分:即是電平信号。隻有高速的通信(如USB)才會使用差分信号抗幹擾。通信雙方距離近,幹擾本來就少

低速率:I2C一般是用一個闆子上兩個IC之間的通信,用來傳輸的資料量不大,一般隻有幾百K/s

每個I2C裝置都有一個從位址,該位址是唯一的,是裝置本身固有的裝置,在通信中通過位址來甄别各個I2C裝置。

電平通信:絕對的電壓是沒有意的,電壓差是真正的意義。電平信号的傳輸線中有一個參考電平線(一般是GND)

容易受到幹擾,屆時傳輸失敗

8位二進制并行通信時:需要9根線(1參考GND+8資料線)

差分信号:也是兩條線,但是沒有1和0,通過高電平減去其對稱的低電平(詳情1.7.1 37分鐘),可能有9V-0.6V

最顯著的特征就是抗幹擾能力比較強,傳輸品質比較穩定,現代通信一般使用差分信号,電平信号很少。

而且電壓整體上移或者下移沒有影響。

8位二進制并行通信時:需要16根線(8x2資料線)

串行通信和并行通信:看起來并行通信快一些,但是串行通信才是王道,因為省信号線,速度可以由通訊提高。

經過發展,最終勝出的通信:異步、串行、差分(USB和網絡)

I2C的時序

I2C總線:就是時鐘線+資料線

時鐘線是非正常律的。

I2C的總線空閑狀态、起始位、結束位

I2C總線上有一個主裝置,n個從裝置。同時隻能有一個從裝置與主裝置 通信。

是以I2C總線上有2種狀态:

空閑态 所有從裝置未通信

忙态 其中一個從裝置在和主裝置通信,總線被占用

時序看什麼?就看每一個時間點CLK和SDA處在高電平還是低電平

空閑态: SDA高電平,SCL高電平持續好幾個周期。通信未開始。

起始位: (一個時間段内)SCL保持高電平,SDA從高到低跳變 [下降沿]

結束位: (一個時間段内)SCL保持高電平,SDA從低到高跳變 [上升沿]

【重要】 資料傳輸:在起始之後,每一個SCL的上升沿發生1bit的資料傳輸!

也就是在SCL上升沿前SDA要鎖存資料,上升沿結束即錄入。

主裝置有收發的權利,從裝置隻有被動的響應。

寫模式

start 發送從裝置7bit位址 R/W 從裝置響應 主發送DATA1Byte 從裝置響應 stop

讀模式

start 發送從裝置7bit位址 R/W 從裝置響應 從發送DATA1Byte 主裝置響應 stop

傳輸過程

1主裝置開始發送7位位址和1位讀寫标志,然後以廣播 形式發送。總線上的其他裝置全部都能接收到,總線上的從裝置都到位址後,要和自己的位址進行比較看是否相等。如果相等說明我們的主裝置就是給“我”說話。如果不相等就說明這次通信與我無關,不用管了。

2每當主裝置發送起始位,從裝置都豎起耳朵仔細聽,聽到位址來判斷是不是給自己說話。

3主裝置發送一段資料後,從裝置回應一個ACK,拉低總線,本身隻占1個bit位。主裝置先釋放總線,然後從裝置回應ACK。如果從裝置沒有拉低總線,則主裝置看到的現象是總線在一直高,沒有收到ACK,主裝置就認為剛剛發送的8bit接收失敗

釋放總線:I2CCON上的某一位有相關操作

主裝置等待ACK釋放總線時SDA會拉低。我們的空閑狀态是高電平

如果我們不釋放又不發,SDA就會一直高着,也就沒有ACK

這種時序規則不隻是I2C,DDR和NAND都是用的類似的規則,隻不過線有好幾根。搞FPGA、搞DSP的關注時序這種東西,我們知道了有好處。

整個通信分多個周期。

兩個相鄰的通信周期之間是空閑态。

每一個通信周期由1個“起始位”開始,以1個“結束位”結束。中間是通信資料

I2C是用控制器自動做的,但是單片機是可以用GPIO模拟I2C的,因為不複雜。

在某一個通信時刻,主從裝置隻能有一個在發[寫總線],另一個在收[讀總線]。絕對不可以同時寫操作總線哦!

S5PV210 的 I2C控制器

I2C框圖不複雜,比較簡單

通信雙方本質上是通過時序在工作,但時序複雜不利于軟體完成,于是産生了I2C控制器。

PCLK_PSYS似乎是經過分頻進來了。

I2C總線控制背後是一坨電路,我們的接口就是I2CCON I2CSTAT,負責産生I2C通信時序。實際程式設計中要用的起始位、停止位、ACK就靠他們。

此外還有一個shift register,移位寄存器,将寄存器的待發送資料1位1位推入SDA。

位址比較器+比較器(未驗證)

I2C控制器做從裝置時用

I2C的時鐘

一般要求速率不多。

時鐘源是PCLK_PSYS

2級分頻

第一級分頻是I2CCON的bit6,可以得到一個中間時鐘I2CCLK

第二級得到工作的最終時鐘。(Transmit clk value,1-16分)

eg:65000 /512/ 4 = 31KHz

主要寄存器

I2CCON 【重要】

I2CSTAT 【重要】

I2CADD 用來寫自己的slave addr,一般用不到。

I2CDS 發送/接收的資料都放在這個

I2C中斷

I2C中斷是順應CPU設計需要,可以不用他。有他的好處是cpu比起I2C速度快太多,I2C可以每裝載1byte發生一個中斷,提醒cpu裝載,不必讓cpu一直等着。

12.3 X210的gsensor介紹

我們的最終目的:通過I2C讀寫作為從裝置的gsensor的寄存器

原理圖分解

講gsensor傳感器是因為他是I2C從裝置,重力傳感器。G-sensor的原理圖在底闆圖上KXTF9-2050.

左邊是一個LDO開關,用來供電的,可以讓Gsensor不必一上電就接通。EN1=1子產品導通。

PWMTOUT3輸出高電平時,gsensor工作。

重力傳感器

即重力加速度傳感器。測試的是實體的加速度 。

典型的應用:計步器。

發展應用:重力傳感器、地磁傳感器(電子羅盤)、陀螺儀,9軸傳感器。

一般的傳感器接口有2種

模拟接口 以接口電平變化作為輸出。如壓力,壓力不同時輸出電平在0~3.3V變化

需要用AD接口對接傳感器,得到一個數字電壓值,再用數字電壓量化成壓力值

數字接口 是模拟接口的sensor基礎上,内部內建了AD轉換,直接輸出一個數字值的參數

輸出的參數通過一定的總線協定,一般用的 就是I2C

感覺已經成為主流了

I2C從裝置的裝置位址

我們的I2C接口一共有4個,這裡使用了I2C0。

KXTE9 7位位址是:0001111 0x0f

每一個晶片廠商出廠的同一個晶片都配備一個I2C從裝置位址,這個位址是統一的。

發送給gsensor消息時,SAD(Slave addr)應該是:

前7位:從裝置位址

第8位:R/W, 0代表主寫從讀

1代表主讀從寫

因為有R/W,是以KXTE9主寫從讀是的位址是0X1E,要注意

注意:I2C連兩個同樣的晶片就真的會出事的!會用不了。

e2prom設計了針對這個問題出現的解決方案,在位址後面加了3碼。

注意:一般來說sensor的本身I2C速率偏低,如KXTE9最高支援400KHz頻率,我們确定通信頻率的時候要比主從兩邊都小。

I2C從裝置接收的指令長度是不一定的,我們發多長的要看從裝置

以主裝置寫一個位元組資料給gsensor為例:

1 master發S , SAD+W,等待ACK

2 master發Register Addr,等待ACK

3 master發DATA,等待ACK

4 接收得到ACK,stop

這裡就一共發送了24個bit資料。8位選從裝置,8位選擇寄存器,8位配置從裝置寄存器

甚至可以多個DATA,發送多位元組資料

這裡有一個隐情:gsensor中發送多個DATA資料不會寫到一個寄存器裡,而是gsensor有邏輯,每當發送一個額外的DATA,寄存器位址都會向後移動。

I2C通訊中有的地方不發ACK,我們要把他空過去而不是省略他。否則後面的stop失效

看了驅動的代碼,看出來驅動和裸機相比

邏輯結構上基本是一樣的

語言上驅動使用了大量宏定義子函數,更為複雜

驅動将實體層(時序)和應用層分開

13 ADC

AD轉換的原理:多是二分法出來的(有點神奇0.0)

ADC的幾個重要特征

量程(轉前範圍)

精度(轉後範圍)

轉換速率 //AD轉換是需要時間的,不同晶片不一樣。通常機關:MSPS

//MSPS:mega Sample(采樣) per second.每秒轉出nM數字值

//AD工作需要一個時鐘,有一定範圍,我們配給他。

//S5PV210中,MSPS = 時鐘頻率 / 5

DAC的原理是通過一些RC元件,配合濾波實作的。單純的cpu是不夠的,cpu是個數字化裝置。

x210的ADC

10路ADC,2路觸摸屏

s5PV210是支援2路觸摸屏的,觸摸屏是ADC的一個功能實作

adc的主要控制寄存器

TSADCCON0

TSCON0 暫時不用

TSDAT 存放轉換出來的資料X TS:TouchScreen

CLRINTADC0 清中斷

ADCMUX 選擇AD通道

等待觸摸屏轉換結束的方法

1 檢查标志位

輪詢檢查标志位是否為1

2 中斷

設定中斷isr讀取ad轉換資料

AD轉換是時刻反複進行 的,這裡使用了一種機制叫“stand by read”,讀取AD值之後硬體立刻自動開啟下一次AD轉換。

程式設計聽完,實驗偷個懶不做了~~

困死了而且腹痛,記完這句話一定要去躺=.=

1.14.2.LCD的接口技術、

1.14.2.1、從電平角度來講本質上都是TTL信号

(1)什麼是TTL接口。

+5V表示邏輯1,0V表示邏輯0.這種就叫TTL電平,和CMOS電平相對比。

(2)SoC的LCD控制器硬體接口是TTL電平的,LCD這邊硬體接口也是TTL電平的。是以他們倆本來是可以直接對接的,手機、平闆、開發闆都是這樣直接對接的(一般用軟排線連接配接)。

(3)TTL電平的缺陷就是不能傳遞太遠,如果LCD螢幕和主機闆控制器太遠(1米甚至更遠)就不能直接TTL連接配接了,要進行轉換。

是以我們使用的轉換方式:

主機SoC(TTL) ->VGA-> LCD螢幕(TTL)
           

1.14.2.2、各種接口(TTL、LVDS、EDP、MIPI、)在傳輸速率、距離、适配性方面不同

(參考資料:http://blog.csdn.net/wocao1226/article/details/23870149)

1.14.2.3、RGB接口詳解(參考資料手冊P1207頁時序圖)

(1)VD[23:0]:24根資料線,用來傳輸圖像資訊。可見LCD是并行接口,速率才夠快。

(2)HSYNC(水準同步信号)

(3)VSYNC(垂直同步信号): 時序信号線,為了讓LCD能夠正常顯示給的控制信号

(4)VCLK(像素時鐘): LCD工作時需要主機闆控制器給LCD模組一個工作時鐘信号,就是VCLK

(5)VDEN(資料有效标志):時序信号,和HSYNC、VSYNC結合使用。

(6)LEND(行結束标志,不是必須的):時序信号,非必須,譬如X210接口就沒有。

14.3 LCD如何顯示圖像

幾個重要器件:顯示記憶體,SoC,LCD控制器,LCD驅動器,LCD面闆

像素

像素pixel,像素是圖檔的基本組成元素

每個像素可以被單獨控制。有的是控制亮或滅,有的是控制亮度,有的顯示不同顔色

掃描

圖像不是一下子出來的,而是一個點一個點出來的

最早是由CRT大屁股顯示器遺留下來的。到LCD年代已經失去意義了,但習慣上還這麼叫。

掃描頻率(重新整理率)不能太慢,太慢就能看到閃動(高于60Hz不會感覺到)

驅動器&控制器

LCD顯示需要幾個部件:

SoC —顯示圖像資料依次填充到—> LCD像素

LCD由兩部分組成:

LCD本體,以及LCD驅動器。驅動器負責産生複雜的模拟電信号

LCD驅動器發送模拟信号,驅動LCD内部的液晶分子流動,形成各種顔色和圖像

LCD驅動器的信号從LCD控制器來

SOC+LCD控制器-----(DIGITAL,RGB)---LCD驅動器----(ANALOGE)--LCD

LCD驅動器往往和LCD面闆內建在一起。
LCD控制器往往和SoC內建在一起,他按照一定時序和驅動器通信。    
           

顯示記憶體:簡稱顯存

LCD控制器要給驅動器發一張圖檔。開始可能在FLASH裡面,由SoC讀到顯示記憶體中。記憶體中進行解碼(如jpeg),于是就有了一幅像素資料了。

SoC在記憶體中挑一段記憶體(一般為程式員對齊挑選),作為顯示記憶體。然後配置LCD控制器,将LCD控制器和顯存連接配接起來構成一個映射關系,随後就自動可以讀取到LCD驅動器。

這個流程隻要配置好,就是全部自動的了。

顯示的過程是不需要cpu參與的,但是cpu要做的有搬運記憶體,解碼等等…做完這些之後,以後cpu隻關心顯存即可。【cpu要把像素丢到顯存,硬體就會自動響應,螢幕即可看到】

【再強調一般:cpu的工作重點,就是關注顯存】

總結: 1 建立顯示體系

CPU初始化LCD控制器,建立映射

2 填充顯存

要顯示的圖像丢到顯存中去

16、shell原理和問答機制

本節課shell是模仿的uboot寫的,但是做了簡化

通過寫這個項目對uboot有一個架構性的認識。

1 從零開始

2 移植進開發闆

3 定義标準指令集

4 添加指令

5 實作開機倒計時執行指令

6 記憶體中實作環境變量

7 在flash中儲存環境變量

做這樣的事情做的比較複雜之後就形成了自己的bootloader,工作中不會要求這麼做,但是可以通過學習搞一個這個出來。

shell:使用者操作接口。shell是計算機内部複雜系統的封裝。

計算機程式本身很複雜,裡面的實作和外部的調用必須分開。操作者不需要明白内部實作

作業系統必須提供shell

shell程式設計就是腳本程式設計,就是在shell層次上程式設計

例如

linux中腳本、windows中的批處理

uboot:本課程中是裸機程式構成的shell

幾種shell:GUI圖形使用者界面 cmdline指令行 路由器webshell

展望:未來的shell應該是聲音圖像接口的

shell的運作原理:是一個死循環

loop

{

指令接收

指令解析

指令執行

}

指令行有一個标準指令集,使用者在操作時候必須知道指令,不能随便輸入。不聽話會提示不合法輸入

16.2 從零開始寫shell

第一步:使用printf和scanf輸入回顯

第二步:定義簡單指令集

【main.c】

//宏定義一些标準指令
#define pwd "pwm"
#define lcd "lcd"

main{
char str[MAX_LINE_LENGTH];
char cmdset[MAX_CMD_NUM] [MAX_LINE_LENGTH];

    init_cmd_set();     //初始化指令清單
    loop
    {
        printf("[email protected]# ");
        scanf("%s" , &str)

        //解析指令
        for(i=0; i<CMD_NUM; i++)
        {
            if(strcmp(str, g_cmdset[i]) == 0)
            {
                //相等,找到了指令
                ...
                break;
            }
            if(i >= CMD_NUM)
            {
                printf("找遍了,%s不是一個内部合法指令,str");
            }   
        }
        //處理指令  
    }
}

void init_cmd_set(void)
{
    memset(g_cmdset, 0,sizeof(g_cmdset));
    strcpy(g_cmdset[0], lcd);
    strcpy(g_cmdset[1], pwm);
}           

16.3 移植到開發闆上

項目的工程是從裸機7章,序列槽ver1.1開始的

bl main

這個工程的lds和makefile都是改寫的比較嚴謹的,注意一下

不使用标準庫!我們的putchar、puts都是重定義的。

void main()
{
    char buf[100];

    uart-init()

    puts("root#");
    memset();       //清buf,沒有使用标準庫,是以這個memset是我們自己的
                    //我們的puts等函數标準輸出都定義的 是序列槽,如果使用标準庫,
                    //這些函數就會産生沖突,也就随之不能使用了
    gets(buf);
    puts("機醬輸入的是:");
    puts(buf);
    puts("\n");
}

void puts(const char *p)
{
    while (*p != '\0')
    {
        putchar(*p);    //putchar()函數就是用uart發送1個char
        p++;
    }
}           

gets和getchar就更麻煩了

這兩個是從WIN下 的SecureCRT輸入到裸機程式中,有兩個問題

1 使用者輸入回顯問題

按一個p就出一個”p”,但是輸入的時候不設定,才不會有實時顯示呢

2 使用者按Enter鍵問題

出現’\r’,要跟着傳回一個’\n’,避免換行不回車問題

3 使用者按倒退鍵問題

if( getchar() == '\b' )
        {
            putchar('\b');
            putchar(' ');
            putchar('\b');
            p--;        
            *p = '\0';      //指針指向了要删除的格子,填充\0
                            //最後一行,其實也是沒有必要的
        }           

16.4 添加指令:led 實作功能是控制闆載LED亮滅

擴充:led 1 on 表示點亮led1亮

int main(argc, *argv[])
{
    ?   
}           

指令解析:就是把一個類似led on這種指令解析成led 和 on,放在一個字元串數組中

定義:

g_cmdset[CMD_NUM] [MAX_LINE_LENGTH] //全局,指令集
    char cmd[5][20];        //目前解析的指令。最多由5部分構成,最長為20.

void init_cmd_set()         //初始化指令清單
{
    memset((char *)g_cmdset, 0 ,sizeof(g_cmdset));      //先全部清零
    strcpy(g_cmdset[0], led);
    strcpy(g_cmdset[1], lcd);

    memset((char *)cmd, 0 ,sizeof(cmd));    
}           

但是目前不可以使用led on,led off,led flash等指令,因為沒有指令的第二部分

我們管“led”稱為主指令,on和off稱為次指令。

指令集g_cmdset裡面存的是主指令。

//第一步,先将使用者輸入的次指令分割放入cmd中    
    cmdsplit()
//第二步将cmd中的次指令第一個字元串和g_cmdset去對比
    for(i=0;i<CMD_NUM;i++;)
    {
        if(!strcmp(cmd[0]) , g_cmdset[i])
        { cmd_index = i;}
    }
eg:第一步執行主指令,然後看次指令裡面到底要幹什麼。
           

分割的任務即是寫一個字元串分割函數

void cmdsplit(char **cmd, const char *str)  //将使用者輸入的字元串指令按空格分割成多個
                                            //然後依次放入cmd二維數組中
{
    int m=0,n=0;                            //m是第一維,n是第二維

    while(*str != '\0')
    {
        if(*str != ' ')                     //如果目前不是空格
        {
            cmd[m][n] = *str;   
            n++;
        }
        else                                //如果是空格 
        {
            cmd[m][n] = '\0';
            n = 0;
            m++;
        }   
        str++;
    }

}           

二維數組:

n 0 1 2 3

m

1

2

3

執行指令

void cmd_exec(void)
{
    switch (cmd_index)      //cmd_index是目前指令号,第n個 指令,全局變量
    case 0:
    case 1:
    default:
    break;
}           

繼續閱讀