【笔记】ARM裸机程序开发_part5

【笔记】ARM裸机程序开发_part5_第1张图片

吐槽~天啦噜真是累哭了,以前不会想到排版这么累的。
以下是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("root@KimonoYan# ");
        scanf("%s" , &str)

        //解析命令
        for(i=0; iif(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 用户按回车键问题
出现’\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

分割的任务即是写一个字符串分割函数

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
0
1
2
3

执行命令

void cmd_exec(void)
{
    switch (cmd_index)      //cmd_index是当前命令号,第n个 命令,全局变量
    case 0:
    case 1:
    default:
    break;
}

你可能感兴趣的:(纯真的自学笔记喵)