day06 链接脚本、shell程序优化、IIC总线

来源:互联网 发布:腾讯红包大数据,归属地 编辑:程序博客网 时间:2024/06/15 00:38
回顾:
1.面试题:谈谈对UART的理解
2.实战:上位机和下位机通过UART进行数据的交互
3.回顾Makefile的编程
**********************************************
4.链接脚本
  4.1.明确:可执行程序基本包含的三大部分
      text段内容
      data段内容
      bss段内容
  4.2.特点
          链接脚本文件以.lds结尾,例如:shell.lds
          链接脚本文件用来指示可执行程序各个段包含的内容以及各个段的起始地址
          链接脚本文件给链接器(arm...ld)使用,链接器根据链接脚本文件的规则进行链接
  4.3.语法
      参考代码:
      mkdir /opt/arm/day06/
      cp /opt/arm/day05/8.0 /opt/arm/day06/1.0 -fr
      cd /opt/arm/day06/1.0
      vim shell.lds    添加如下内容
      /*指定最终的可执行程序shell.bin的入口函数为main函数*/
      ENTRY(main)  
      /*指定最终的可执行程序shell.bin的各个段的内容以及起始地址*/
      SECTIONS
      {
              /*指定所有段的起始地址为0x48000000*/
              . = 0x48000000;  
              
              /*指定最终可执行文件shell.bin中text段的内容*/
              .text :
              {
                      /*将main.o中的text段添加进来*/
                      /*切记:一定要指定链接和运行的第一个文件*/
                      /*切记:此文件包含了程序的入口函数*/
                      main.o(.text)
                      /*将剩余目标文件中所有的text段添加进来*/
                      *(.text)
              }
              /*指定最终可执行文件shell.bin中data段的内容*/
              .data :  
              {
                  *(.data)
              }
              /*指定最终可执行文件shell.bin中bss段的内容*/
              .bss :  
              {
                *(.bss)
              }
      }
      保存退出
    
   4.4.链接脚本文件的使用
       cd /opt/arm/day06/1.0
       vim Makefile
               将:
               LDFLAGS=-nostartfiles -nostdlib -Ttext=0x480000000
               修改:
               LDFLAGS=-nostartfiles -nostdlib -Tshell.lds
               说明:
               -Tshell.lds:告诉arm...ld链接器,将来你链接的规则根据
                                       shell.lds进行链接
       保存退出
       make
       arm-cortex_a9-linux-gnueabi-objdump -D shell.elf > shell.dis
       vim shell.dis //查看main函数对应的地址是否为0x48000000
       cp shell.bin /tftpboot
       下位机测试看代码是否能够运行
        
    4.5.切记:看链接脚本一定要获取一下三个内容
        1.通过链接脚本能够获取程序运行的起始地址
        2.通过链接脚本能够获取程序运行的第一个文件
        3.通过链接脚本能够获取程序运行的第一个入口函数
        4.将来上班看裸板程序,首先要通过链接脚本获取运行的
          第一个文件和入口函数,利用ctags或者sourceinsight
          顺藤摸瓜理顺代码的执行流程即可!
        5.假如没有链接脚本不存在,通过Makeifle文件
          也能够获取运行的第一个文件和入口函数和起始地址
          例如:
          -Ttext=0x48000000:起始地址
          obj=main.o uart.o ....:运行第一个文件main.o
          -emain:运行的入口函数为main
          
      4.6.思考题:
              cd /opt/arm/day06/1.0
              vim Makefile
              将
              LDFLAGS=-nostartfiles -nostdlib -Tshell.lds
              修改为:
              LDFLAGS=-nostartfiles -nostdlib -Ttext=0x40008000 -Tshell.lds
              保存退出
              问:shell.bin最终的链接地址是0x40008000还是0x48000000?
              答:通过反汇编验证
 
5.优化shell程序
  之前目前shell程序缺点:
  if(!my_strcmp(buf, "led on"))
      led_on();
  else if(!my_strcmp(buf, "led off"))
      led_off();
  else if (!my_strcmp(buf, "beep on"))
      beep_on
  ...
  随着硬件操作数量的增多,if ... else将会变得越来越长
   
  回顾C的typedef关键字:
  typdef unsigned char u8;
   
  //描述学生事物
  struct std {
      char *name; //用于描述学生的名称
      int id; //用于描述学生的学号
  };
  //定义A,B两个学生对象
  struct std A;
  struct std B;
   
  优化:
  typedef struct std {
      char *name;
      int id;
  }std_t;
   
  //定义初始化两个学生对象
  std_t std_info[] = {
      {“zhangsan”, 0x100001},
      {"lisi", 0x100002}
  };
   
  //自定义数据类型cb_t,本质就是一个函数指针
  typedef void (*cb_t)(void);
  cb_t cb = led_on; //定义函数指针,指向led_on
  cb(); //本质就是调用led_on函数
   
  根据以上得到shell程序的优化代码:
  shell程序中也有一个事物,这个事物就是命令
  而命令具有两个属性:命令的名称和对应的执行函数
  所以,声明一个描述命令的数据结构:
  typedef void (*cb_t)(void);
  typedef struct _cmd {
          char *name; //命令的名称
          cb_t callback; //命令的执行函数
  }cmd_t;
   
  //定义初始化开关灯命令对象
  cmd_t cmd_tbl[] = {
      {"led on", led_on},
      {"led off", led_off}
  };
   
  所以:一下代码
    if(!my_strcmp(buf, "led on"))
        led_on();
    else if(!my_strcmp(buf, "led off"))
        led_off();
    else if (!my_strcmp(buf, "beep on"))
        beep_on
      ...
  优化成:
  cmd_t *pcmd;
  uart_gets(buf); //从上位机获取命令
  pcmd = find_cmd(buf);//根据上位机的命令找到对应的命令对象
  if(pcmd == NULL)
      uart_puts("命令没有找到\n");
  else
      pcmd->callback();//执行命令对应的执行函数
   
  问:find_cmd如何实现呢?
  cmd_t *find_cmd(char *name)
  {
          //根据name在上面的数组cmd_tbl中进行匹配
          找到对应的命令对象,找到以后返回命令对象的
          首地址
  }  
   
  实施步骤:
  cp /opt/arm/day06/1.0 /opt/arm/day06/2.0 -fr
  cd /opt/arm/day06/2.0
  vim cmd.h
  vim cmd.c
  vim main.c
  vim Makefile 添加cmd.o
  make
  cp shell.bin /tftpboot
   
  下位机测试:
  ping  
  tftp  
  go  
  ...    
*********************************************
6.I2C(IIC)总线  
  面试题:谈谈对I2C总线的理解
  6.1.首先谈谈CPU和外设常用的几种硬件通信方式和接口
  GPIO:LED,蜂鸣器等
  UART:GPS,GPRS,BT等
  I2C总线:触摸屏,重力传感器等
  SPI总线:触摸屏,Norflash闪存等
  1-Wire总线:温度传感器,EEPROM存储器等
  在这里我就讲讲I2C总线,当然还可以讲讲别滴!
   
  6.2.I2C总线的定义
  I2C总线定义就7个字:两线式串行总线
  紧接着对7个字进行详细的阐述说明:
  "两线式":说明CPU和外设的数据传输只需两根信号线
           分别是数据线SDA和时钟控制信号线SCL
  SCL:时钟控制信号线,用来实现同步数据,所以I2C总线
      数据同步采用同步方式,并且SCL时钟控制信号线
      只能由CPU控制,也就是SCL上的高低电平只能由
      CPU发起!
      例如:CPU通过I2C总线给外设发送数据
            那么CPU在SCL为低电平的时候将数据放到数据线SDA上
            那么外设就在同周期的SCL为高电平的时候从数据线上获取数据
      例如:CPU通过I2C总线从外设读取数据
            那么CPU首先将SCL拉低,为了让外设在SCL为低电平
            的时候将数据放到SDA数据线上
            那么CPU就在同周期的SCL为高电平的时候从数据线SDA获取数据
      简称:低放高取
       
  SDA:数据线,顾名思义用于传输数据,数据线可以被CPU
      和外设控制,单同一时刻只能由一方控制
      CPU向外设发送数据,SDA由CPU控制
      外设向CPU发送数据,SDA由外设控制
      控制权相关口诀:
          谁配输出谁控制
          谁配输入谁释放
          可以同时配输入
          不能同时配输出
    
   注意:SCL和SDA分别接一个上拉电阻,也就是SCL和
         SDA的默认电平都是高电平
                
  “串行”:CPU和外设数据传输只需一根信号线SDA
          一个时钟周期传输一个bit位
          在时钟为低时放数
          在时钟为高时取数
          切记:I2C总线数据传输从高位开始!
   顺便侃侃串行的死对头并行!
    
   总线:说明SDA和SCL这两根信号线上可以连接多个外设
         此时此刻务必画出一个简要的硬件连接示意图!
         参见:i2c.bmp
    
   6.3.看图提三个问题:
       1.如果CPU要想访问总线上其中某个外设,那么CPU
         如何一次性定位到要访问的外设呢?
       2.如果CPU找到了,定位到了要访问的外设,那么
         CPU接下来如何通过两根信号线跟这个外设进行数据通信呢?
       3.既然是两个信号线,那么这两根信号线如何搭配使用呢?
       答:以上三个问题的答案在I2C总线协议中!
           切记:
           I2C总线协议就是在I2C外设的芯片手册中!
           重点关注I2C外设芯片手册中的操作时序图!
                    大谈特谈I2C总线协议
    
   6.4.交代I2C总线协议相关的概念
           master=CPU=主设备
           slave=外设=从设备
           MSB:高位
           LSB:低位
           START信号:又称起始信号,此信号只能由CPU发起,
                               每当CPU要访问I2C总线上某个外设,CPU
                               首先向总线上发送一个START信号,类似:同学们,上课了
                               此信号对应的时序为:
                                   SCL为高电平,SDA由高电平向低电平跳变产生START信号
                                   此时此刻画出对应的波形图!
                                   参见:i2c1.bmp
           STOP信号:又称结束信号,此信号同样只能由CPU发起
                    每当CPU结束对I2C总线外设的访问,CPU
                    只需向总线上发送一个STOP信号即可,类似:同学们,下课了
                    此信号对应的时序为:
                       SCL为高电平,SDA由低电平向高电平跳变产生STOP信号
                       此时此刻画出STOP信号的波形图:
                       参见:i2c1.bmp
           读写位(R/W):用于指示CPU到底是读外设还是写外设
                       有效的bit位就是一位
                       如果CPU是读外设,读写位=1
                       如果CPU是写外设,读写为=0
           设备地址:用于指示I2C外设在总线上的唯一性,
                     同一个总线上的每一个I2C外设都有
                     唯一的设备地址,每当CPU要想访问某个
                     I2C外设,那么CPU只需向总线上发送这个
                     外设对应的设备地址即可,类似:喊某个同学的名字
                               问:外设的设备地址如何确定呢?
                               答:有些芯片是通过芯片手册来确定
                                   有些芯片不仅仅需要芯片手册来确定
                                   还需要结合原理图共同来决定!
                               注意:设备地址不包含读写位!
                               
                               以LM77温度传感器为例,LM77的设备地址为:
                               1.打开LM77的芯片手册:lm77.pdf,P8
                               2.得到LM77的设备地址为:
                                 10010A1A0(假设硬件工程师将A1A0都接地)
                                 得到LM77的设备地址为:1001000
                                 软件的表示形式用16进制(高位补0),最终
                                 软件形式的LM77的设备地址=01001000=0x48  
                               
                               再以ADP8860背光灯控制芯片为例,设备地址为:
                               1.打开ADP8860的芯片手册:adp8860.pdf,P26
                               2.得到ADP8860的设备地址为:
                                 0101010x(去掉x读写位),然后高位补0
                                 最终得到软件16进制表示的设备地址为
                                 00101010=0x2A
                               
                               再以MMA8653三轴加速度传感器为例,设备地址:
                               1.打开MMA8653芯片手册:mma8653fcr1.pdf,P17
                               2.最终得到MMA8653的设备地址为0x1D
                                   
           读设备地址=设备地址<<1 | 1                           
           写设备地址=设备地址<<1 | 0
                     总结:读写设备地址不仅仅包含了设备地址还包含了读写位
           
           ACK信号:又称应答信号,反馈信号,低电平有效,1bit有效
           
           至此目前可以回答三个问题的第一个问题:
           CPU要想找到,定位到总线上某个外设,CPU只需
           向总线上发送这个外设的设备地址即可!
           
  6.5.CPU一旦定位到要访问的外设,那么CPU如何通过
      SDA和SCL跟外设进行数据的传输呢?
      答:务必举例子说明即可,边说边画框框圈圈图
      切记:明确具体的访问过程一定要外设芯片手册的时序图!
      以CPU读取LM77两字节数据为例,流程如下:
      1.打开LM77芯片手册P12
      2.CPU首先向总线发送START信号
      3.CPU向总线发送设备地址(1001000)和读写位(1)
      4.如果LM77存在于总线上,LM77会在第九个时钟周期
        给CPU发送一个低电平有效的ACK信号
        注意:前八个时钟周期发送设备地址和读写位
      5.CPU读取两字节数据的高字节
      6.CPU然后在第九个时钟周期给LM77一个ACK信号
      7.CPU继续读取两字节数据的低字节
      8.CPU读取数据以后,并没有在第九个时钟周期给
        LM77一个有效的ACK信号(高电平)
      9.CPU最后向总线发送STOP信号,结束对LM77的读操作
      此时此刻,一定要画框框圈圈图,参见i2c2.bmp
      结论:
      切记:I2C数据传输为:一次一字节,一周期一bit
                                               数据传输从高位开始
       
     以CPU读取MMA8653的ID(身份证号)为例,操作流程如下:
     1.打开MMA8653的芯片手册mma8653fcr1.pdf
     2.掌握MMA8653的内部构造
       MMA8653芯片内部同样集成了各种寄存器,这些寄存器
       也有地址(起始地址从0x00开始),其中有一个寄存器
       基地址为0x0D,用来保存ID,值为0x5A
       接下来的思路就是:CPU利用SDA和SCL只要将
       MMA8653片内0x0D寄存器的数据0x5A读出来即可
       问:既然MMA8653寄存器也有地址,为什么CPU
           不以地址指针的形式访问呢?
           char data = *(volatile unsigned char *)0x0D;
       答:切记:I2C外设的片内寄存器地址不是在CPU直接寻址的4G地址空间中
           CPU不能以地址指针的形式来访问I2C外设的片内寄存器
           要想访问必须利用SCL和SDA两根信号线
     3.在MMA8653芯片手册中找到CPU读取MMA8653相关的
       读操作时序图,P17,单字节的读时序图
       1.CPU首先向总线发送START信号
       2.CPU然后向总线发送MMA8653的设备地址0x1D和读写位0
       3.如果设备存在于总线上,设备会在第九个时钟周期
         给CPU一个有效的低电平的ACK信号
       4.CPU然后向总线发送要访问的片内寄存器地址0x0D
       5.设备一旦知道CPU要访问的地址,设备给CPU一个
         ACK信号
       6.CPU继续发送START信号
       7.CPU继续发送设备地址0x1D和读写位1
       8.设备继续给CPU一个ACK信号
       9.设备将片内寄存器0x0D中的数据0x5A发送给CPU
       10.CPU读取数据以后,没有给设备一个有效的ACK信号
       11.CPU最后发送一个STOP信号
       此时此刻,画出框框圈圈图,参见:mma8653.bmp
        
       以CPU向HMC6352指南针传感器发送休眠命令为例,操作流程:
       1.打开HMC6352的芯片手册,hmc6352.pdf,P4
       2.CPU首先向总线发送START信号
       3.CPU向总线发送HMC6352的设备地址0x21和读写位0
       4.如果设备存在于总线上,设备在第九个时钟周期给
         CPU一个有效的低电平ACK信号
       5.CPU向HMC6352发送休眠命令0x53
       6.设备接受到命令以后给CPU一个ACK信号
       7.CPU最后发送一个STOP信号
       此时此刻画出框框圈圈图!
        
       总结:CPU访问外设的具体操作流程一定要看
             I2C外设的芯片手册,而手册中重点关注时序图!
     
   6.6.SDA和SCL如何搭配使用呢?
   答:就四个字:低放高取
   挑衅放大招:务必举例子画出具体的操作时序图!
   以CPU向HMC6352发送休眠命令0x53为例,画出具体的操作时序图
   具体参见HMC6352.pdf的P4的时序图!
    
   6.7.如果将来CPU访问I2C外设出现问题,只需拿示波器
       抓取SCL和SDA的波形图,然后淡定的分析波形即可
       确定问题,所以务必掌握波形,建议将HMC6352芯片手册
       中其余相关的时序多看看!
       分析波形的思路如下:
       1.首先找到START信号
       2.确定设备地址是否正确
       3.关键是第一个ACK信号是否有
         如果第一个ACK信号没有,说明外设不存在于总线上
         对于这种情况,首先要确认前面的START和设备地址
         是否正确,如果正确,ACK信号还没有,此问题势必
         是硬件问题(芯片烧毁,虚焊,电压,地线有问题)
         此时让硬件工程师协助查!
         如果发现START信号或者设备地址不对,势必是软件问题
         查软件!
         至此:I2C总线相关内容到此结束
 
7.实战    
 
原创粉丝点击