标签:
嵌入式系统实验c语言开发adc模数转换mini2440armj-linkkeil-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)编译程序, 使用仿真器在目标板上调试运行程序, 使用单步、 设置断点,观察程序执行的流程,观察函数调用过程中栈指针寄存器(R13,SP)的值的变化。
(5)编程并观察 C 语言语句编译后对应的 ARM 汇编程序。
(二)模数转换器(ADC)实验:
(1) 本实验使用 S3C2440 片内模数转换器。从结构图和芯片手册可以知道, 该 ADC 模块总共有 8 个通道可以进行模拟信号的输入, 分别是 AIN0、AIN1、AIN2、AIN3、YM、YP、 XM、XP。模拟信号从任一通道输入,然后设定寄存器中预分频器的值来确定 AD 转换器频率,最后 ADC 将模拟信号转换为数字信号保存到 ADC 数据寄存器 0 中(ADCDAT0),然后 ADCDAT0 中的数据可以通过中断或查询的方式来访问。对于 ADC 的各寄存器的操作和注意事项请参阅数据芯片手册。本实验使用通道 0 采集实验板上的滑动变阻器的分压电压值.
(2)启动 Keil uVision,新建一个工程 ex04-2。不需要系统提供的 Startup文件。建立汇编源文件 ex04-2.s,编写实验程序,然后添加到工程中。设置工程选项,存储器映射。设置工程调试选项。建立仿真初始化文件 RAM.ini。
(5) 编译链接工程。连接实验板电源、J-link 仿真器,进行仿真调试。单步运行程序, 并改变板上滑动变阻器 W1 的阻值, 观察采集到的电压数值的变化。
六、实验程序
C 语言实验程序见程序清单 4.1、4.2。模数转换器实验程序见程序清单 4.3。
程序清单 4.1 C 语言实验程序(汇编部分)
Stack_Top
Undef b Undef
SWI
Pabort b Pabort
Dabort b Dabort
IRQ
FIQ
Reset
END
程序清单 4.2 C 语言实验参考程序(C 语言部分)
int compare( int a, int b)
{
tmp =
}
int max( int a, int b)
{
if( compare( a, b) >= 0)
}
main()
{
v = max( v, 4);
{
}
程序清单 4.3 模数转换器实验参考程序(C 语言部分)
#define rADCCON
#define rADCTSC
#define rADCDLY
#define rADCDAT0
#define rADCDAT1
#define rADCUPDN
#define LOOP 10000
#define ADC_FREQ 1000000
volatile unsigned preScaler;
int ReadAdc(int ch)
{
rADCCON|=0x1;
}
main()
{
}
七、实验现象
1.
2.
八、思考题
(1)实验程序中 main()函数名是否可以改为其它名字?
答:实验中main()函数名可以改为其他名字。
相比于C语言程序,main()函数作为整个程序的入口,提供了很重要的信息:系统应该从main()函数处执行。所以把C语言中的main()改为其他名字后,可以通过编译,但是链接时就会出错,应为链接器不知道整个程序的入口在哪。
而实验程序中,实验的开发环境虽然用C的编译器,但却不用链接目标文件,因为最终生成的目标文件是通过汇编语言导入的:
IMPORT
实际导入的不是函数的.c文件,而是目标文件,所以只要目标文件的名字与上述指令中被导入的目标文件相同即可,也就没有了C语言中必须有main()的限制。例如将main()函数改为example(),则上述三条指令变为:
IMPORT
用汇编语言写的启动代码执行时,会将上述目标文件导入,然后同过BX指令跳转到函数头部开始执行,这一分支指令相当于链接程序时生成的程序入口信息。
(2)如何校准模数转换器测量电压的精度?
答:假设模数转换的精度为n位(mini2440最大为10),在测量精度准确的情况下,输入的电压U和转换后的数值VALUE应该满足如下关系:
http://s7/middle/7bf0c30f4a66bba7d7066&690
如果测量出现了误差,比如电阻阻值发生变化而导致滑动变阻器调到最小值时输出的VALUE不是0,跳到最大值时输出的VALUE不是1111111111,此时可以通过如下办法校准:
首先把滑动变阻器阻值调到最大,A/D转换器的输出VALUE为MAX;然后把滑动变阻器阻值调到最小,A/D转换器的输出VALUE为MIN。
然后对A/D转换器输入电压U,A/D转换器输出结果为n位的VALUE,对应于电压U的真值为TRUEVALUE,则应有如下等式:
http://s8/middle/7bf0c30f4a66bba8df2c7&690
http://s14/middle/7bf0c30f4a66bbab301bd&690
化简则有:
http://s12/middle/7bf0c30f4a66bbb0eb25b&690
即真正的转换值应该是:
http://s10/middle/7bf0c30f4a66bbb173a79&690
当校正以后,只需记住MAX和MIN的值,然后在程序中实现上述等式即可。
九、选做题
(2) 用板上滑动变阻器控制指示灯的闪烁速度。
滑动变阻器控制的模拟信号经A/D转换器转换为制定精度的数字信号,实验中用C语言完成A/D转换器的相关控制部分,C子函数中用return()语句返回A/D转换器转换后的信号,根据C语言汇编后的特点,一个函数返回值会用寄存器R0保存返回值。因此A/D转换器的转换结果在寄存器R1中。
要用实验开发板上的滑动变阻器控制指示灯的闪烁速度,即用模拟信号控制指示灯的闪烁速度。灯需要加入额外的代码实现GPIO功能,灯的闪烁可以用循环方式,每隔一段时间改变一次灯的状态。而控制其闪烁时间可以再嵌套循环,在内层循环中以A/D转换器的输出结果R0作为延时变量。
这样就实现了用滑动变阻器控制指示灯的闪烁速度,完整的实验程序如下:
汇编部分
Stack_Top EQU 0x1000 - 4
AREA RESET, CODE, READONLY
ENTRY
CODE32
b Reset
Undef b
Undef
SWI b
SWI
Pabort
b Pabort
Dabort
b Dabort
b .
IRQ b
IRQ
FIQ b
FIQ
Reset
LDR R0,
=Stack_Top
MOV SP, R0
IMPORT main
LDR R0, =main
BX R0
b .
END
C语言部分
#define
rGPBCON (*(volatile unsigned *)0x56000010
#define
rGPBDAT (*(volatile unsigned *)0x56000014
#define rADCCON (*(volatile unsigned *)0x58000000) //ADC control
#define
rADCTSC (*(volatile unsigned *)0x58000004)
#define
rADCDLY (*(volatile unsigned *)0x58000008)
#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);
if(prevCh!=ch)
{
rADCCON = (1<<14)|(preScaler<<6)|(ch<<3); //setup channel
for(i=0;i<LOOP;i++);
prevCh=ch;
}
rADCCON|=0x1;
while(rADCCON & 0x1);
while(!(rADCCON & 0x8000));
return ( (int)rADCDAT0 & 0x3ff );
}
main()
{
int v = 0;
int i=0;
int flag=0;
preScaler = 12000000/ADC_FREQ - 1;
rGPBCON=0x15400;
while(1)
{
v = ReadAdc(0);
if(flag==1)
flag=0;
else
flag=1;
rGPBDAT=flag&0x1e0;
for(i=0;i<v;i++)
{
}
}
}

加载中…