加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

嵌入式系统实验—C 语言开发/模数转换(ADC)实验

(2011-06-24 10:12:20)
标签:

嵌入式系统实验

c语言开发

adc

模数转换

mini2440

arm

j-link

keil-uvis

分类: 实验报告

实验四:C 语言开发/模数转换(ADC)实验


 

一、实验目的

1、掌握 C 语言嵌入式开发的基本步骤和原理。

2、学习使用 C 语言操作外设的方法。

3、掌握模数转换器外设的操作原理和编程。

二、实验设备

1、硬件:PC 机一台 Mini2440 ARM 实验板一套 J-link 仿真器一套

2、软件:WindowsXP 系统,Keil uVision 4.0 集成开发环境

三、实验内容

1)建立汇编-C 程序框架。

2)使用 ADC 读取 Mini2440 实验板上的滑动变阻器产生的电压值。

四、实验预习要求

(1)学习 ARM 子程序调用相关的指令;

(2)查阅 S3C2440 芯片手册,了解模数转换器系统的结构和原理。

五、实验步骤

(一)C 语言编程实验:

(1)实验程序分为汇编语言和 C 语言两部分。汇编语言程序是处理器上电复位后执行的第一段程序,负责最基础的硬件初始化,在本实验中主要是初始化栈指针(SP)寄存器,为调用 C 语言函数做准备,然后跳转到 C 语言 main 函数执行;从 main 函数开始,程序的编写结构和执行原理基本上与 PC 机上典型的 C语言相同。

(2)启动 Keil uVision,新建一个工程 ex04-1。不需要系统提供的 Startup文件。建立汇编源文件 ex04-1.s,编写实验程序,然后添加到工程中。设置工程选项,存储器映射。设置工程调试选项。建立仿真初始化文件 RAM.ini 具体步骤参考实验二。

(3) 建立 C 语言源文件 main.c,编写实验程序,然后添加到工程中。

(4)编译程序, 使用仿真器在目标板上调试运行程序, 使用单步、 设置断点,观察程序执行的流程,观察函数调用过程中栈指针寄存器(R13SP)的值的变化。

(5)编程并观察 C 语言语句编译后对应的 ARM 汇编程序。

(二)模数转换器(ADC)实验:

(1) 本实验使用 S3C2440 片内模数转换器。从结构图和芯片手册可以知道, ADC 模块总共有 8 个通道可以进行模拟信号的输入, 分别是 AIN0AIN1AIN2AIN3YMYP XMXP。模拟信号从任一通道输入,然后设定寄存器中预分频器的值来确定 AD 转换器频率,最后 ADC 将模拟信号转换为数字信号保存到 ADC 数据寄存器 0 (ADCDAT0),然后 ADCDAT0 中的数据可以通过中断或查询的方式来访问。对于 ADC 的各寄存器的操作和注意事项请参阅数据芯片手册。本实验使用通道 0 采集实验板上的滑动变阻器的分压电压值.

(2)启动 Keil uVision,新建一个工程 ex04-2。不需要系统提供的 Startup文件。建立汇编源文件 ex04-2.s,编写实验程序,然后添加到工程中。设置工程选项,存储器映射。设置工程调试选项。建立仿真初始化文件 RAM.ini

 (3)实验程序实现了一个用于控制读取 ADC 指定通道的值的函数: int ReadAdc(int ch) 在对 preScaler 变量初始化之后,即可通过调用该函数来多次采集连接在ADC 上的模拟信号值。

(5) 编译链接工程。连接实验板电源、J-link 仿真器,进行仿真调试。单步运行程序, 并改变板上滑动变阻器 W1 的阻值, 观察采集到的电压数值的变化。

六、实验程序

C 语言实验程序见程序清单 4.14.2。模数转换器实验程序见程序清单 4.3

程序清单 4.1 C 语言实验程序(汇编部分)

Stack_Top  EQU  0x1000 - 4   ;end of ram

  AREA RESET, CODE, READONLY    ;声明代码段RESET

  ENTRY          ;表示程序入口

  CODE32         ;声明32ARM指令

  b Reset

Undef b Undef     ;handler for Undefined mode

SWI  b SWI       ;handler for SWI interrupt

Pabort b Pabort     ;handler for PAbort

Dabort b Dabort     ;handler for DAbort

  b .      ;reserved

IRQ  b IRQ       ;handler for IRQ interrupt

FIQ  b FIQ       ;handler for FIQ interrupt

Reset

  ;initialize stack pointer

  LDR     R0, =Stack_Top

  MOV     SP, R0

        IMPORT  main

        LDR     R0, =main

        BX      R0

  b .    ;should never get here

END

程序清单 4.2 C 语言实验参考程序(C 语言部分)

int compare( int a, int b)

{

 int tmp, ret;

tmp =  a - b;

 if( tmp > 0)

  ret = 1;

 else if( tmp == 0)

  ret = 0;

 else

  ret = -1;

     return ret;

}

int max( int a, int b)

{

 int tmp1;

if( compare( a, b) >= 0)

  tmp1 = a;

 else

  tmp1 = b;

 return tmp1;

}

main()

{

 int v = 0;

v = max( v, 4);

 while(1)

{ 

 }

}

程序清单 4.3 模数转换器实验参考程序(C 语言部分)

#define rADCCON    (*(volatile unsigned *)0x58000000) //ADC control

#define rADCTSC    (*(volatile unsigned *)0x58000004) //ADC touch screen control

#define rADCDLY    (*(volatile unsigned *)0x58000008) //ADC start or Interval Delay

#define rADCDAT0   (*(volatile unsigned *)0x5800000c) //ADC conversion data 0

#define rADCDAT1   (*(volatile unsigned *)0x58000010) //ADC conversion data 1

#define rADCUPDN   (*(volatile unsigned *)0x58000014) //Stylus  Up/Down interrupt status

#define LOOP 10000

#define ADC_FREQ 1000000

volatile unsigned preScaler;

int ReadAdc(int ch)

{

    int i;

    static int prevCh=-1;

 

    rADCCON = (1<<14)|(preScaler<<6)|(ch<<3); //setup channel

 

    if(prevCh!=ch)

    {

  rADCCON = (1<<14)|(preScaler<<6)|(ch<<3);   //setup channel

  for(i=0;i<LOOP;i++); //delay to set up the next channel

   prevCh=ch;  

    }

rADCCON|=0x1;   //start ADC

    while(rADCCON & 0x1); //check if Enable_start is low

    while(!(rADCCON & 0x8000)); //check if EC(End of Conversion) flag is high

    return ( (int)rADCDAT0 & 0x3ff );

}

main()

{

 int v = 0;

 preScaler = 12000000/ADC_FREQ - 1;               //PCLK:12MHz

  while(1)

 { 

  v = ReadAdc(0); //对应开发板上W1可调电阻;

 }

}

 

七、实验现象

1.          C语言指令被编译成一系列的汇编指令,在运行子函数和声明变量的时候,SP会发生变化。

2.          全速运行,转动可调电阻器,暂停程序,观察局部变量,v的值发生变化,变化范围0-0x3ff

 

八、思考题

(1)实验程序中 main()函数名是否可以改为其它名字?

答:实验中main()函数名可以改为其他名字。

相比于C语言程序,main()函数作为整个程序的入口,提供了很重要的信息:系统应该从main()函数处执行。所以把C语言中的main()改为其他名字后,可以通过编译,但是链接时就会出错,应为链接器不知道整个程序的入口在哪。

而实验程序中,实验的开发环境虽然用C的编译器,但却不用链接目标文件,因为最终生成的目标文件是通过汇编语言导入的:

IMPORT  main

        LDR     R0, =main

        BX      R0

实际导入的不是函数的.c文件,而是目标文件,所以只要目标文件的名字与上述指令中被导入的目标文件相同即可,也就没有了C语言中必须有main()的限制。例如将main()函数改为example(),则上述三条指令变为:

IMPORT  example

        LDR     R0, =example

        BX      R0

用汇编语言写的启动代码执行时,会将上述目标文件导入,然后同过BX指令跳转到函数头部开始执行,这一分支指令相当于链接程序时生成的程序入口信息。

 

(2)如何校准模数转换器测量电压的精度?

答:假设模数转换的精度为n位(mini2440最大为10),在测量精度准确的情况下,输入的电压U和转换后的数值VALUE应该满足如下关系:

http://s7/middle/7bf0c30f4a66bba7d7066&amp;690

如果测量出现了误差,比如电阻阻值发生变化而导致滑动变阻器调到最小值时输出的VALUE不是0,跳到最大值时输出的VALUE不是1111111111,此时可以通过如下办法校准:

首先把滑动变阻器阻值调到最大,A/D转换器的输出VALUEMAX;然后把滑动变阻器阻值调到最小,A/D转换器的输出VALUEMIN

然后对A/D转换器输入电压UA/D转换器输出结果为n位的VALUE,对应于电压U的真值为TRUEVALUE,则应有如下等式:

http://s8/middle/7bf0c30f4a66bba8df2c7&amp;690

http://s14/middle/7bf0c30f4a66bbab301bd&amp;690

化简则有:

http://s12/middle/7bf0c30f4a66bbb0eb25b&amp;690

即真正的转换值应该是:

http://s10/middle/7bf0c30f4a66bbb173a79&amp;690

当校正以后,只需记住MAXMIN的值,然后在程序中实现上述等式即可。

九、选做题

(2) 用板上滑动变阻器控制指示灯的闪烁速度。

滑动变阻器控制的模拟信号经A/D转换器转换为制定精度的数字信号,实验中用C语言完成A/D转换器的相关控制部分,C子函数中用return()语句返回A/D转换器转换后的信号,根据C语言汇编后的特点,一个函数返回值会用寄存器R0保存返回值。因此A/D转换器的转换结果在寄存器R1中。

要用实验开发板上的滑动变阻器控制指示灯的闪烁速度,即用模拟信号控制指示灯的闪烁速度。灯需要加入额外的代码实现GPIO功能,灯的闪烁可以用循环方式,每隔一段时间改变一次灯的状态。而控制其闪烁时间可以再嵌套循环,在内层循环中以A/D转换器的输出结果R0作为延时变量。

这样就实现了用滑动变阻器控制指示灯的闪烁速度,完整的实验程序如下:

 

汇编部分

Stack_Top EQU 0x1000 - 4                                  ;end of ram

AREA RESET, CODE, READONLY              ;声明代码段RESET

ENTRY                                                             ;表示程序入口

CODE32                                                          ;声明32ARM指令

b Reset

Undef b Undef                                              ;handler for Undefined mode

SWI b SWI                                                      ;handler for SWI interrupt

Pabort b Pabort                                            ;handler for PAbort

Dabort b Dabort                                          ;handler for DAbort

b .                                                                     ;reserved

IRQ b IRQ                                                                ;handler for IRQ interrupt

FIQ b FIQ                                                        ;handler for FIQ interrupt

 

Reset

LDR R0, =Stack_Top                                               ;initialize stack pointer

MOV SP, R0

IMPORT main

LDR R0, =main

BX R0

b .                                                                     ;should never get here

END

 

C语言部分

#define rGPBCON (*(volatile unsigned *)0x56000010   //Port B control register

#define rGPBDAT (*(volatile unsigned *)0x56000014    //Port B data register

 

#define rADCCON (*(volatile unsigned *)0x58000000) //ADC control

#define rADCTSC (*(volatile unsigned *)0x58000004)   //ADC touch screen control

#define rADCDLY (*(volatile unsigned *)0x58000008)   //ADC start or Interval Delay

#define rADCDAT0 (*(volatile unsigned *)0x5800000c) //ADC conversion data 0

#define rADCDAT1 (*(volatile unsigned *)0x58000010) //ADC conversion data 1

#define rADCUPDN (*(volatile unsigned *)0x58000014) //Stylus Up/Down interrupt status

#define LOOP 10000

#define ADC_FREQ 1000000

 

volatile unsigned preScaler;

 

int ReadAdc(int ch)

{

int i;

static int prevCh=-1;

rADCCON = (1<<14)|(preScaler<<6)|(ch<<3);        //setup channel

if(prevCh!=ch)

{

rADCCON = (1<<14)|(preScaler<<6)|(ch<<3); //setup channel

for(i=0;i<LOOP;i++);                                              //delay to set up the next channel

prevCh=ch;

}

rADCCON|=0x1;                                                             //start ADC

while(rADCCON & 0x1);                                               //check if Enable_start is low

while(!(rADCCON & 0x8000));                           //check if EC(End of Conversion) flag is high

return ( (int)rADCDAT0 & 0x3ff );

}

 

main()

{

int v = 0;

int i=0;                                                                   //灯的延时变量

int flag=0;                                                              //控制灯的掩码

preScaler = 12000000/ADC_FREQ - 1;             //PCLK:12MHz

 

rGPBCON=0x15400;                                           //set GPIO portB(5,6,7,8) as output

 

while(1)

{

v = ReadAdc(0);                                                     //对应开发板上W1 可调电阻;

 

if(flag==1)

flag=0;

else

flag=1;

 

rGPBDAT=flag&0x1e0;                             //写入灯的数据为00x1e0,即灯的亮灭

 

for(i=0;i<v;i++)                                                      //灯的状态延时

{

                   while(i<10000);

}

 

}

}

 

 

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有