![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICdzFWRoRXdvN1LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX9MGRNJzZq50drpWTmZEWjZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39zM0ITOzATNxIDOyITM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
吐槽~天啦噜真是累哭了,以前不會想到排版這麼累的。
以下是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;
}