<span style="font-family: SimSun; background-color: rgb(255, 255, 255);">一、I/O端口的相关寄存器</span>
1. 端口配置寄存器(GPACON至GPJCON)
S3C2440A中,大多数端口为复用端口。因此要决定每个引脚选择哪项功能。PnCON(引脚控制寄存器)决定了引脚使用哪项功能。
2. 端口数据寄存器(GPADAT至GPJDAT)
如果端口配置为输出端口,可以写入数据到PnDAT的相应位。如果端口配置为输入端口,可以从PnDAT的相应位读取数据。
3. 以端口B为例
如表1.1为端口B的配置寄存器和数据寄存器的描述。
表4.1 端口B配置寄存器和数据寄存器
寄存器 | 地址 | R/W | 描述 | 复位值 |
GPBCON | 0x56000010 | R/W | 配置端口B的引脚 | 0x0 |
GPBDAT | 0x56000014 | R/W | 端口B的数据寄存器 | - |
对于端口B配置寄存器的各位的描述如下表1.2所示。
表1.2 端口B配置寄存器各位描述
GPBCON | 位 | 描述 | 初始状态 |
GPB10 | [21:20] | 00=输入 01=输出 10=nXDREQ0 11=保留 | 0 |
GPB9 | [19:18] | 00=输入 01=输出 10=nXDACK0 11=保留 | 0 |
GPB8 | [17:16] | 00=输入 01=输出 10=nXDREQ1 11=保留 | 0 |
GPB7 | [15:14] | 00=输入 01=输出 10=nXDACK1 11=保留 | 0 |
GPB6 | [13:12] | 00=输入 01=输出 10=nXBREQ 11=保留 | 0 |
GPB5 | [11:10] | 00=输入 01=输出 10=nXBACK 11=保留 | 0 |
GPB4 | [9:8] | 00=输入 01=输出 10=TCLK[0] 11=保留 | 0 |
GPB3 | [7:6] | 00=输入 01=输出 10=TOUT3 11=保留 | 0 |
GPB2 | [5:4] | 00=输入 01=输出 10=TOUT2 11=保留 | 0 |
GPB1 | [3:2] | 00=输入 01=输出 10=TOUT1 11=保留 | 0 |
GPB0 | [1:0] | 00=输入 01=输出 10=TOUT0 11=保留 | 0 |
对于端口B数据寄存器的各位的描述如下表1.3所示
表1.3 端口B数据寄存器各位描述
GPBCON | 位 | 描述 | 初始状态 |
GPB[10:0] | [10:0] | 当端口配置为输入端口时,相应位为引脚状态。当端口配置为输出端口时,引脚状态将与相应位相同。当端口配置为功能引脚,将读取到未定义值 | - |
4. 端口的配置函数s3c2410_gpio_cfgpin
下面函数是根据新的功能需求配置一个GPIO引脚。其中参数pin是要配置的GPIO引脚,function是要配置的功能。
voids3c2410_gpio_cfgpin(unsigned int pin, unsigned int function)
{
/* S3C2410_GPIO_BASE是内核代码中定义的宏,定义如下:
#defineS3C2410_GPIO_BASE(pin) ((((pin) & ~31) >> 1) + S3C24XX_VA_GPIO)
其中S3C24XX_VA_GPIO= ((S3C2410_PA_GPIO - S3C24XX_PA_UART) +S3C24XX_VA_UART)= 0x56000000-0x50000000+0xF5000000= 0xF9000000,即通过S3C2410_GPIO_BASE宏可获得物理地址到虚拟地址的转化。 */
void__iomem *base = S3C24XX_GPIO_BASE(pin);
unsignedlong mask;
unsignedlong con;
unsignedlong flags;
//计算寄存器掩码位,当引脚为GPIO的A端口时,它的掩码位为1位,引脚A之外的端口掩码位均为2位
if(pin < S3C2410_GPIO_BANKB) {
mask= 1 << S3C2410_GPIO_OFFSET(pin);
//宏定义为S3C2410_GPIO_OFFSET(pin)((pin) & 31),获得引脚的偏移位
}
else{
mask= 3 << S3C2410_GPIO_OFFSET(pin)*2;
}
//修改function的值
switch(function) {
caseS3C2410_GPIO_LEAVE:
mask= 0;
function= 0;
break;
caseS3C2410_GPIO_INPUT:
caseS3C2410_GPIO_OUTPUT:
caseS3C2410_GPIO_SFN2:
caseS3C2410_GPIO_SFN3:
if(pin< S3C2410_GPIO_BANKB) {
function-= 1;
function&= 1;
function<<= S3C2410_GPIO_OFFSET(pin);
}
else{
function&= 3;
function<<= S3C2410_GPIO_OFFSET(pin)*2;
}
}
local_irq_save(flags);
con = __raw_readl(base + 0x00);//读取寄存器原有的值
con&= ~mask;
con|= function;
//根据新的需求修改原有的寄存器特定位的值(确保其他位保持不变)
__raw_writel(con,base + 0x00);//调用__raw_writel将修改后的寄存器值重新写入相应的寄存器
local_irq_restore(flags);
}
5. 端口的输出设置函数s3c2410_gpio_setpin
下面函数用来设置GPIO引脚的输出值。其中参数pin是要配置的GPIO引脚,to是要设置的值0或者1。代码实现类似于端口的配置函数,不再赘述。
voids3c2410_gpio_cfgpin(unsigned int pin, unsigned int function)
{
/* S3C2410_GPIO_BASE是内核代码中定义的宏,定义如下:
#defineS3C2410_GPIO_BASE(pin) ((((pin) & ~31) >> 1) + S3C24XX_VA_GPIO)
其中S3C24XX_VA_GPIO= ((S3C2410_PA_GPIO - S3C24XX_PA_UART) +S3C24XX_VA_UART)= 0x56000000-0x50000000+0xF5000000= 0xF9000000,即通过S3C2410_GPIO_BASE宏可获得物理地址到虚拟地址的转化。 */
void__iomem *base = S3C24XX_GPIO_BASE(pin);
unsignedlong mask;
unsignedlong con;
unsignedlong flags;
//计算寄存器掩码位,当引脚为GPIO的A端口时,它的掩码位为1位,引脚A之外的端口掩码位均为2位
if(pin < S3C2410_GPIO_BANKB) {
mask= 1 << S3C2410_GPIO_OFFSET(pin);
//宏定义为S3C2410_GPIO_OFFSET(pin)((pin) & 31),获得引脚的偏移位
}
else{
mask= 3 << S3C2410_GPIO_OFFSET(pin)*2;
}
//修改function的值
switch(function) {
caseS3C2410_GPIO_LEAVE:
mask= 0;
function= 0;
break;
caseS3C2410_GPIO_INPUT:
caseS3C2410_GPIO_OUTPUT:
caseS3C2410_GPIO_SFN2:
caseS3C2410_GPIO_SFN3:
if(pin< S3C2410_GPIO_BANKB) {
function-= 1;
function&= 1;
function<<= S3C2410_GPIO_OFFSET(pin);
}
else{
function&= 3;
function<<= S3C2410_GPIO_OFFSET(pin)*2;
}
}
local_irq_save(flags);
con = __raw_readl(base + 0x00);//读取寄存器原有的值
con&= ~mask;
con|= function;
//根据新的需求修改原有的寄存器特定位的值(确保其他位保持不变)
__raw_writel(con,base + 0x00);//调用__raw_writel将修改后的寄存器值重新写入相应的寄存器
local_irq_restore(flags);
}
1.2 PWM定时器的相关寄存器
1. 定时器配制寄存器0(TCFG0)
定时器输入时钟频率 = PCLK / {预分频值+1} / {分频值},其中,预分频值= 0~255,分频值= 2, 4, 8, 16。具体描述如表4.4和4.5所示。
表1.4 定时器配置寄存器0
寄存器 | 地址 | R/W | 描述 | 复位值 |
TCFG0 | 0x51000000 | R/W | 配制两个8位预分频器 | 0x0 0000000 |
表1.5 定时器配置寄存器0各位描述
TCFG0 | 位 | 描述 | 初始状态 |
保留 | [31:24] |
| 0x00 |
死区长度 | [23:16] | 该8位决定了死区段。死区段持续为1的时间等于定时器0持续为1的时间 | 0x00 |
Prescaler 1 | [15:8] | 该8位决定了定时器2,3和4的预分频值 | 0x00 |
Prescaler 0 | [7:0] | 该8位决定了定时器0和1的预分频值 | 0x00 |
2. 定时器配制寄存器1(TCFG1)
表1.6 定时器配置寄存器1
寄存器 | 地址 | R/W | 描述 | 复位值 |
TCFG1 | 0x51000004 | R/W | 5路多路选择器和DMA模式选择寄存器 | 0x0 0000000 |
表1.7 定时器配置寄存器1各位描述
TCFG1 | 位 | 描述 | 初始状态 |
保留 | [21:20] |
| 00000000 |
DMA模式 | [19:18] | 选择DMA请求通道 0000 = 未选择(所有中断) 0001 = 定时器0 0010 = 定时器1 0011 = 定时器2 0100 = 定时器3 0101 = 定时器4 0110 = 保留 | 0000 |
MUX 4 | [17:16] | 选择PWM定时器4的选通输入 0000 = 1/2 0001 = 1/4 0010 = 1/8 0011 = 1/16 01xx = 外部TCLK1 | 0000 |
MUX 3 | [15:14] | 选择PWM定时器3的选通输入 0000 = 1/2 0001 = 1/4 0010 = 1/8 0011 = 1/16 01xx = 外部TCLK1 | 0000 |
MUX 2 | [13:12] | 选择PWM定时器2的选通输入 0000 = 1/2 0001 = 1/4 0010 = 1/8 0011 = 1/16 01xx = 外部TCLK1 | 0000 |
MUX 1 | [11:10] | 选择PWM定时器1的选通输入 0000 = 1/2 0001 = 1/4 0010 = 1/8 0011 = 1/16 01xx = 外部TCLK1 | 0000 |
MUX 0 | [9:8] | 选择PWM定时器0的选通输入 0000 = 1/2 0001 = 1/4 0010 = 1/8 0011 = 1/16 01xx = 外部TCLK1 | 0000 |
4.4.3 LED电路原理图
在实验平台的核心板上有四个LED灯可用作此次实验。核心板上的四个LED都是直接与处理器相连,连接方式简单。其电路图如图4.1所示:
图1.1 LED电路图
上面左侧图为处理器引脚,右侧图为LED部分的电路,线路上的标签相同,表示这两条线的电气属性直接相连,例如左侧图中的nLED_1这条线与右侧图中nLED_1这条线直接相连。四个LED(LED1、LED2、LED3、LED4)采用共阳极连接方式,以LED1为例,当LED1左侧的电位为低电平时(即处理器的相应引脚输出低电平),在LED1上存在从右至左的电流流过,LED1处于导通状态,因而被点亮。当LED1左侧的电位为高电平时(即处理器的相应引脚输出高电平),在LED1上没有足够大的电流流过,LED1处于截止状态,因而未被点亮。因此,通过控制处理器相关引脚输出电平的高低即可达到控制对应LED亮灭的目的。
1.4 LED驱动程序分析
static unsigned long led_table[]={//使用Linux内核中相关的宏定义LED控制引脚
S3C2410_GPB(5), //LED1的控制引脚
S3C2410_GPB(6), //LED2的控制引脚
S3C2410_GPB(7), //LED3的控制引脚
S3C2410_GPB(8)//LED4的控制引脚
};
static unsigned int led_cfg_table []= { //将处理器的相应引脚设置为输出状态
S3C2410_GPIO_OUTPUT,
S3C2410_GPIO_OUTPUT,
S3C2410_GPIO_OUTPUT,
S3C2410_GPIO_OUTPUT,
};
//四个LED的控制函数
static int sbc2440_leds_ioctl(
struct inode *inode,
struct file *file,
unsigned int cmd,
unsigned long arg)
{
switch(cmd) { //用cmd来控制LED灯的亮和灭
case 0:
case 1:
if (arg > 4) { //用arg表示LED的编号
return -EINVAL;
}
s3c2410_gpio_setpin(led_table[arg], !cmd);//设定处理器相应引脚电平
return 0;
default:
return -EINVAL;
}
}
staticstruct file_operations dev_fops = { // 初始化设备的文件操作的结构体
.owner = THIS_MODULE,
.ioctl = sbc2440_leds_ioctl,
};
staticstruct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
};
staticint __init dev_init(void) //LED驱动的初始化函数
{
int ret;
int i;
//将四个LED对应的处理器引脚设为输出状态,并且四个LED熄灭
for (i = 0; i < 4; i++) {
s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);
s3c2410_gpio_setpin(led_table[i], 0); //设置引脚为低电平
}
ret = misc_register(&misc); //注册设备
printk(DEVICE_NAME"\tinitialized\n");
return ret;
}
staticvoid __exit dev_exit(void)
{
misc_deregister(&misc); //卸载设备
}
1.5 LED测试代码分析
void delay(long time) //延时函数
{
long tmp = 5000;
while( time-- )
{
while(tmp--);
tmp = 5000;
}
}
int main(void) //主函数
{
int i = 0, j=0;
int fd;
fd = open("/dev/leds"); //打开设备
if(fd<0){ //打开设备失败
perror("open device leds failed!");
exit(1);
}
//先将所有LED关闭
ioctl(fd,0,0);
ioctl(fd,0,1);
ioctl(fd,0,2);
ioctl(fd,0,3);
delay(2000);
for(j=0;j<10;j++){ //先逐个点亮四个LED,然后逐个熄灭四个LED,循环10次
for(i=0;i<4;i++){
ioctl(fd,1,i);
delay(2000);
}
for(i=0;i<4;i++){
ioctl(fd,0,i);
delay(2000);
}
}
close(fd);
return 0;
}
1.6 蜂鸣器简介及电路原理图
微处理器驱动蜂鸣器的方式有两种:一种是PWM 输出口直接驱动,另一种是利用I/O 定时翻转电平产生驱动波形对蜂鸣器进行驱动,在本系统中,我们使用PWM输出口直接驱动方式。PWM 输出口直接驱动是利用PWM 输出口本身可以输出一定的方波来直接驱动蜂鸣器。在单片机的软件设置中有几个系统寄存器是用来设置PWM 口的输出的,可以设置占空比、周期等等,通过设置这些寄存器产生符合蜂鸣器要求的频率的波形之后,只要打开PWM 输出,PWM 输出口就能输出该频率的方波,这个时候利用这个波形就可以驱动蜂鸣器了。比如频率为2000Hz 的蜂鸣器的驱动,可以知道周期为500μs,这样只需要把PWM 的周期设置为500μs,占空比电平设置为250μs,就能产生一个频率为2000Hz 的方波,通过这个方波再利用三极管就可以去驱动这个蜂鸣器了。
PWM是指脉冲宽度调制,它是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。
脉冲宽度调制是一种模拟控制方脉冲宽度调制是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中脉冲宽度调制是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。
随着电子技术的发展,出现了多种PWM技术,其中包括:相电压控制PWM、脉宽PWM法、随机PWM、SPWM法、线电压控制PWM等,而在镍氢电池智能充电器中采用的脉宽PWM法,它是把每一脉冲宽度均相等的脉冲列作为PWM波形,通过改变脉冲列的周期可以调频,改变脉冲的宽度或占空比可以调压,采用适当控制方法即可使电压与频率协调变化。可以通过调整PWM的周期、PWM的占空比而达到控制充电电流的目的。
操控PWM主要分为以下四步:
1、PWM是通过引脚TOUT0输出的,而这个引脚是与GPB0复用的,因此要实现PWM功能首先要把相应的引脚配置成TOUT输出。
2、再设置定时器的输出时钟频率,它是以PCLK为基准,再除以用寄存器TCFG0配置的prescaler参数,和用寄存器TCFG1配置的divider参数。
3、然后设置脉冲的具体宽度,它的基本原理是通过寄存器TCNTBn来对寄存器TCNTn(内部寄存器)进行配置计数,TCNTn是递减的,如果减到零,则它又会重新装载TCNTBn里的数,重新开始计数,而寄存器TCMPBn作为比较寄存器与计数值进行比较,当TCNTn等于TCMPBn时,TOUTn输出的电平会翻转,而当TCNTn减为零时,电平会又翻转过来,就这样周而复始。因此这一步的关键是设置寄存器TCNTBn和TCMPBn,前者可以确定一个计数周期的时间长度,而后者可以确定方波的占空比。由于s3c2410的定时器具有双缓存,因此可以在定时器运行的状态下,改变这两个寄存器的值,它会在下个周期开始有效。
4、最后就是对PWM的控制,它是通过寄存器TCON来实现的,一般来说每个定时器主要有4个位要配置(定时器0多一个死区位):启动/终止位,用于启动和终止定时器;手动更新位,用于手动更新TCNTBn和TCMPBn,这里要注意的是在开始定时时,一定要把这位清零,否则是不能开启定时器的;输出反转位,用于改变输出的电平方向,使原先是高电平输出的变为低电平,而低电平的变为高电平;自动重载位,用于TCNTn减为零后重载TCNTBn里的值,当不想计数了,可以使自动重载无效,这样在TCNTn减为零后,不会有新的数加载给它,那么TOUTn输出会始终保持一个电平(输出反转位为0时,是高电平输出;输出反转位为1时,是低电平输出),这样就没有PWM功能了,因此这一位可以用于停止PWM。