硬件程序开发流程
-
相关硬件的工作原理
-
理解硬件的工作原理,明确硬件的功能和用途。
-
-
硬件连接
-
将硬件设备正确连接到开发板上。
-
-
编写程序
-
根据硬件功能编写相应的程序代码。
-
-
调试验证
-
通过调试工具验证程序的正确性,确保硬件功能正常。
-
控制LED的步骤
-
找到LED对应的丝印
-
在开发板(PCB)上找到LED对应的丝印标识。
-
-
找到对应的器件
-
在原理图中根据丝印找到对应的LED器件。
-
-
找到连接的处理器引脚
-
确定LED连接到处理器的哪个引脚(如GPB5)。
-
-
控制引脚输出高低电平
-
配置引脚功能为输出,并通过寄存器控制引脚输出高低电平。
-
一、LED灯点亮
寄存器配置
端口B控制寄存器(GPBCON, GPBDAT, GPBUP)
寄存器 | 地址 | R/W | 描述 | 复位值 |
---|---|---|---|---|
GPBCON | 0x56000010 | R/W | 配置端口B的引脚 | 0x0 |
GPBDAT | 0x56000014 | R/W | 端口B的数据寄存器 | - |
GPBUP | 0x56000018 | R/W | 端口B的上拉使能寄存器 | 0x0 |
GPBCON寄存器位配置
位 | 描述 | 初始状态 |
---|---|---|
GPB5[11:10] | 00 = 输入,01 = 输出,10 = nXBACK,11 = 保留 | 0 |
GPBDAT寄存器位配置
位 | 描述 | 初始状态 |
---|---|---|
GPB5 | 当端口配置为输出时,控制引脚输出高低电平 | - |
代码实现
1. 初始化LED
void led1_init(void)
{// 配置GPB5引脚功能为输出gpio_cfg(GPB5, OUT);// 使GPB输出高电平(LED灭)GPBDAT |= (0xF << 5);
}
2. LED亮灯
void led1_on(void)
{// 输出低电平(LED亮)GPBDAT &= ~(0xF << 5);
}
3. LED灭灯
void led1_off(void)
{// 输出高电平(LED灭)GPBDAT |= (0xF << 5);
}
4. 流水灯效果
void led1_flow(void)
{for (int i = 0; i < 4; i++){// 依次点亮不同的LEDGPBDAT |= (0xF << 5); // 所有LED灭GPBDAT &= ~(1 << (5 + i)); // 第i个LED亮delay(100000); // 延时}
}
5. 按键控制LED
void buttons(void)
{int eint4 = GPGDAT & 1; // 检测EINT4按键状态int eint5 = (GPFDAT >> 4) & 0x1; // 检测EINT5按键状态int eint6 = (GPFDAT >> 5) & 0x1; // 检测EINT6按键状态int eint8 = (GPFDAT >> 6) & 0x1; // 检测EINT8按键状态// 根据按键状态控制对应的LEDif (eint4 == 0) GPBDAT &= ~(1 << 5); // EINT4按下,LED5亮else GPBDAT |= (1 << 5); // EINT4松开,LED5灭if (eint5 == 0) GPBDAT &= ~(1 << 6); // EINT5按下,LED6亮else GPBDAT |= (1 << 6); // EINT5松开,LED6灭if (eint6 == 0) GPBDAT &= ~(1 << 7); // EINT6按下,LED7亮else GPBDAT |= (1 << 7); // EINT6松开,LED7灭if (eint8 == 0) GPBDAT &= ~(1 << 8); // EINT8按下,LED8亮else GPBDAT |= (1 << 8); // EINT8松开,LED8灭
}
二、时钟配置
1. 时钟分频寄存器 (CLKDIVN)
-
地址:
0x4C000014
-
功能: 控制HCLK和PCLK的分频比例。
-
位域定义:
-
HCLK分频器: 位1 (
HDIVN
),用于设置HCLK的分频比例。 -
PCLK分频器: 位0 (
PDIVN
),用于设置PCLK的分频比例。
-
-
配置方法:
CLKDIVN = (0x2 << 1) | 0x1; // 设置 HCLK 分频器为 2,PCLK 分频器为 1
-
HCLK 分频器: 2 (HCLK = PCLK * 3)
-
PCLK 分频器: 1 (PCLK = MPLLCLK / 2 / 2)
-
2. 主PLL寄存器 (MPLLCON)
-
地址:
0x4C000004
-
功能: 配置主PLL的输出时钟频率。
-
位域定义:
-
M值: 位15-12 (
MDIV
),用于设置主分频器。 -
P值: 位11-8 (
PDIV
),用于设置预分频器。 -
S值: 位7-4 (
SDIV
),用于设置后分频器。
-
-
计算公式:
-
MPLL输出频率:
(M + 8) * Fin / (P * S)
-
其中:
-
M = MDIV + 8
-
P = PDIV + 2
-
S = SDIV + 2
-
Fin = 外部时钟输入频率 (通常为 12 MHz)
-
-
-
配置方法:
MPLLCON = (127 << 12) | (2 << 4) | 1; // 设置 M=127, P=2, S=1
-
M值: 127 + 8 = 135
-
P值: 2 + 2 = 4
-
S值: 1 + 2 = 3
-
计算结果: MPLL 输出频率 =
(135) * 12 MHz / (4 * 3) = 135 MHz
-
3. 时钟频率计算
-
MPLL输出频率: 135 MHz
-
HCLK频率: 135 MHz / (HCLK分频器 + 1) = 135 MHz / 3 = 45 MHz
-
PCLK频率: 135 MHz / (2 * (PCLK分频器 + 1)) = 135 MHz / (2 * 2) = 33.75 MHz
代码实现
#include "clk.h"
#include <s3c2440.h>// 初始化时钟配置
void clk_init(void)
{// 配置时钟分频寄存器 (CLKDIVN)// 设置 HCLK 分频器为 2,PCLK 分频器为 1CLKDIVN = (0x2 << 1) | 0x1;// 配置主PLL寄存器 (MPLLCON)// 设置 M=127, P=2, S=1MPLLCON = (127 << 12) | (2 << 4) | 1;
}
三、定时器配置
1. 配置 GPIO 引脚为 PWM 模式
GPBCON &= ~(0x03 << 0); // 清除 GPB0 和 GPB1 的配置
GPBCON |= (0x02 << 0); // 将 GPB0 配置为 nPWM0 功能
-
功能:将 GPB0 引脚配置为 PWM 功能(nPWM0)。
-
逻辑:
-
GPBCON
是 GPIO 控制寄存器,低两位控制 GPB0 和 GPB1 的功能。 -
使用位操作清除低两位(
0x03 << 0
),然后设置为0x02 << 0
,将 GPB0 配置为 PWM 功能。
-
2. 配置定时器预分频器和分频器
TCFG0 = 24; // 设置定时器0的预分频值为24
-
功能:设置定时器 0 的预分频值。
-
逻辑:
-
TCFG0
是定时器配置寄存器,Prescaler0
决定定时器 0 和 1 的预分频值。 -
预分频值设置为 24,定时器输入时钟频率计算公式为:
定时器输入时钟频率=(预分频值+1)PCLK -
分频值默认为 1(未显式设置)。
-
3. 配置定时器时钟源选择
TCFG1 &= ~(0xf); // 清除 MUX0 的低4位,选择默认时钟源
-
功能:选择定时器 0 的时钟源。
-
逻辑:
-
TCFG1
是定时器时钟源选择寄存器,MUX0
控制定时器 0 的时钟源。 -
清除
MUX0
的低 4 位(0xf
),选择默认时钟源(内部时钟源,分频值为 1/2 PCLK)。
-
4. 设置定时器计数值和比较值
TCNTB0 = 2000; // 设置定时器0的计数值
TCMPB0 = 1000; // 设置定时器0的比较值(占空比50%)
-
功能:设置定时器 0 的计数值和比较值。
-
逻辑:
-
TCNTB0
是定时器 0 的计数缓冲寄存器,设置计数值为 2000。 -
TCMPB0
是定时器 0 的比较缓冲寄存器,设置比较值为 1000。 -
PWM 周期计算公式:
PWM 周期=(TCNTB0+1)×(预分频值+1)×分频值 -
占空比计算公式:
占空比=TCNTB0TCMPB0×100% -
本例中占空比为 50%(1000/2000 = 0.5)。
-
5. 配置定时器控制寄存器 TCON
TCON &= ~(1 << 4); // 禁用死区功能
TCON |= (1 << 3); // 启用自动重载模式
TCON |= (1 << 2); // 启用输出变相功能
TCON |= (1 << 1); // 手动更新定时器寄存器
TCON &= ~(1 << 1); // 清除手动更新位(必须在下次写操作前清零)
-
功能:配置定时器 0 的控制寄存器。
-
逻辑:
-
死区功能:清除
TCON
的第 4 位(bit4=0
),禁用死区功能。 -
自动重载模式:设置
TCON
的第 3 位(bit3=1
),启用自动重载模式。 -
输出变相功能:设置
TCON
的第 2 位(bit2=1
),启用输出变相功能。 -
手动更新:设置
TCON
的第 1 位(bit1=1
),手动更新定时器寄存器。 -
清除手动更新位:必须在下次写操作前清零
bit1
。
-
6. 启动定时器 0
TCON |= (1 << 0); // 启动定时器0
-
功能:启动定时器 0。
-
逻辑:
-
设置
TCON
的第 0 位(bit0=1
),启动定时器 0。
-
四、ADC配置
1. ADC 初始化函数 adc_init
void adc_init(void)
{ADCCON = (1 << 14) | (49 << 6);
}
-
功能:初始化 ADC 控制寄存器
ADCCON
。 -
逻辑:
-
使能预分频器:
-
设置
ADCCON
的第 14 位(PRSCEN
)为 1,使能预分频器。
-
-
设置预分频值:
-
设置
ADCCON
的第 13:6 位(PRSCVL
)为 49,调整 ADC 频率。 -
注意:ADC 频率应低于 PCLK 的 1/5。
-
-
其他配置:
-
默认选择模拟输入通道为 AIN0(未显式设置)。
-
默认设置为正常工作模式(未显式设置)。
-
-
2. ADC 启动函数 adc_start
void adc_start(unsigned char ch)
{ADCCON &= ~(0x7 << 3); // 清除 SEL_MUX 位ADCCON |= (ch << 3); // 设置 SEL_MUX 位为指定通道// 使能读启动操作ADCCON |= (1 << 0); // ENABLE_START = 1
}
-
功能:启动 ADC 转换。
-
逻辑:
-
选择模拟输入通道:
-
清除
ADCCON
的第 5:3 位(SEL_MUX
),然后设置为指定的通道ch
。 -
ch
的取值范围为 0-7,对应不同的模拟输入通道(AIN0 到 XP)。
-
-
启动 ADC 转换:
-
设置
ADCCON
的第 0 位(ENABLE_START
)为 1,启动 ADC 转换。 -
注意:启动后该位会被硬件清零。
-
-
3. ADC 数据读取函数 adc_read
short adc_read(void)
{short value = 0;int i = 100;// 等待转换结束while (!(ADCCON & (1 << 15)) && i--);if (i <= 0)return -1;// 读取转换结果value = ADCDAT0 & 0x3ff;return value;
}
-
功能:读取 ADC 转换结果。
-
逻辑:
-
等待转换结束:
-
检查
ADCCON
的第 15 位(ECFLG
),等待转换结束标志位为 1。 -
如果超时(
i
减到 0),返回错误值 -1。
-
-
读取转换结果:
-
从
ADCDAT0
寄存器读取转换结果,取低 10 位(0x3ff
)作为有效数据。 -
返回读取的值。
-
-
总结
-
初始化 ADC:
-
使能预分频器。
-
设置预分频值为 49,调整 ADC 频率。
-
默认选择 AIN0 通道和正常工作模式。
-
-
启动 ADC 转换:
-
根据传入的通道号选择模拟输入通道。
-
启动 ADC 转换。
-
-
读取 ADC 数据:
-
等待转换结束。
-
读取转换结果并返回。
-
五、中断配置
1. 外部中断 EINT8 初始化函数 eint8_init
void eint8_init(void)
{ //配置GPIO 引脚为外部中断模式GPGCON &= ~(0x3 << 0);GPGCON |= (0x2 << 0);//配置EINT8的触发方式EXTINT1 &= ~(0x7 << 0);EXTINT1 |= (0x2 << 0); //设置EINT8的触发方式为下降沿触发//使能EINT8中断//EINTMASK 寄存器:这是一个外部中断屏蔽寄存器,用于控制外部中断是否被允许。//INTMSK 寄存器:这是一个中断屏蔽寄存器,用于控制内部中断是否被允许。EINTMASK &= ~(1 << 8); //使能EINT8中断INTMSK &= ~(1 << 5); //使能EINT8_23中断
}
-
功能:初始化外部中断 EINT8。
-
逻辑:
-
配置 GPIO 引脚:
-
清除
GPGCON
的低两位(0x3 << 0
),然后设置为0x2 << 0
,将 GPG0 配置为外部中断模式。
-
-
配置触发方式:
-
清除
EXTINT1
的低三位(0x7 << 0
),然后设置为0x2 << 0
,将 EINT8 的触发方式设置为下降沿触发。
-
-
使能中断:
-
清除
EINTMASK
的第 8 位(1 << 8
),使能 EINT8 中断。 -
清除
INTMSK
的第 5 位(1 << 5
),使能 EINT8_23 中断。
-
-
2. 处理 EINT8 中断的函数 deal_eint8_23
void deal_eint8_23(void)
{//检测EINT8是否发生了中断//EINTPEND 寄存器:这是一个外部中断挂起寄存器,用于指示当前有哪些外部中断请求正在等待处理。if(EINTPEND & (1 << 8)){// 清除EINT8的中断标志位 EINTPEND |= (1 << 8);//通过写1清除EINTPEND的第八位}
}
-
功能:处理 EINT8 中断。
-
逻辑:
-
检测中断:
-
检查
EINTPEND
的第 8 位是否为 1,判断 EINT8 是否发生了中断。
-
-
清除中断标志位:
-
通过写 1 到
EINTPEND
的第 8 位,清除中断标志位。
-
-
3. 中断处理函数 c_deal_irq
void c_deal_irq(void)
{//获取中断源的偏移量unsigned int irq_num = INTOFFSET;// INTOFFSET 寄存器指示当前中断源的偏移量switch (INTPND){case EINT8_23: deal_eint8_23(); break;default:break;} //清除中断标志位SRCPND |= (1 << irq_num);// 通过写 1 清除 SRCPND 中的相应位//INTPND 寄存器:这是一个中断挂起寄存器,用于指示当前有哪些中断请求正在等待处理。//中断标志位:当一个中断发生时,相应的标志位会被硬件自动置为 1。在中断处理完成后,需要手动清除这些标志位,否则中断系统会认为该中断仍然未处理,无法再次触发。//通过读取 INTPND 寄存器的值,并将其重新写回 INTPND 寄存器。//INTPND 里只有挂起的那一位置1,对改为写1,硬件会自动清除其中的中断标志位(即把标志位从 1 变为 0)。INTPND = INTPND;
}
-
功能:处理中断请求。
-
逻辑:
-
获取中断源:
-
读取
INTOFFSET
寄存器,获取当前中断源的偏移量。
-
-
调用处理函数:
-
根据
INTPND
寄存器的值,判断中断源并调用相应的处理函数(如deal_eint8_23
)。
-
-
清除中断标志位:
-
通过写 1 到
SRCPND
的相应位,清除源挂起标志位。 -
通过读取
INTPND
并重新写回,清除中断挂起标志位。
-
-
4.注意事项
-
中断标志位清除:必须在中断处理完成后清除中断标志位,否则中断系统会认为该中断仍然未处理,无法再次触发。
-
中断优先级:
INTOFFSET
寄存器指示当前中断源的偏移量,确保中断处理的优先级正确。 -
中断屏蔽:通过
EINTMASK
和INTMSK
寄存器控制中断的使能和屏蔽。
六、UART配置
什么是UART?
通用异步接收器/发送器,通常称为UART,是一种广泛应用于嵌入式领域的串行、异步、全双工通信协议。
串口连接
UART 通道有两条数据线。每个设备上都有一个 RX 引脚和一个 TX 引脚(RX 用于接收,TX 用于发送)。每个设备的 RX 引脚都连接到另一个设备的 TX 引脚。请注意,没有共享时钟线!这是通用异步接收方发送方的“异步”方面。
1. UART 初始化
1.1 引脚配置
-
将
GPHCON
的第 4 位清零,配置为 GPIO 模式。 -
设置
GPHCON
的第 4 位为0xA
,配置为 UART 模式。
1.2 线路控制寄存器 (ULCON0) 配置
-
清除第 6 位,禁用红外模式。
-
清除第 3-5 位,设置为无奇偶校验。
-
清除第 2 位,设置每帧 1 个停止位。
-
设置第 0-1 位为
0x3
,配置数据位为 8 位。
1.3 控制寄存器 (UCON0) 配置
-
清除第 10-11 位,选择 PCLK 作为时钟源。
-
清除第 0-3 位,设置为普通模式。
-
设置第 0-3 位为
0x5
,配置为中断请求模式。
1.4 FIFO 控制寄存器 (UFCON0) 配置
-
设置第 0 位为 1,使能 FIFO。
-
设置第 1 位为 1,复位 Rx FIFO。
-
清除第 4-5 位,设置 Rx FIFO 触发深度为 8 字节。
-
设置第 7 位为 1,复位 Tx FIFO。
1.5 波特率分频寄存器 (UBRDIV0) 配置
-
设置
UBRDIV0
为 325,配置波特率为 9600。
1.6 中断使能
-
清除
INTMSK
的第 28 位,使能 UART 中断。 -
清除
INTSUBMSK
的第 0 位,使能 UART0 中断。
// UART初始化函数
void uart_init(void)
{// 配置GPH4和GPH5为UART0功能GPHCON &= ~(0xf << 4); // 清除GPH4和GPH5的低4位,确保它们的功能未被其他用途占用GPHCON |= (0xa << 4); // 设置GPH4和GPH5为UART0功能(0xA表示UART0功能)// 配置ULCON0寄存器(UART线路控制寄存器)ULCON0 &= ~(1 << 6); // 清除第6位,确保不是同步模式ULCON0 &= ~(0x7 << 3); // 清除第3-5位,确保不是其他特殊模式ULCON0 &= ~(1 << 2); // 清除第2位,确保无奇偶校验ULCON0 |= (0x3 << 0); // 设置低两位为0x3,表示8位数据,无校验,1位停止位(8N1模式)// 配置UCON0寄存器(UART控制寄存器)UCON0 &= ~(0x3 << 10); // 清除第10-11位,确保无特殊功能UCON0 &= ~(0xf << 0); // 清除低4位,确保无其他特殊功能UCON0 |= (0x5 << 0); // 设置低3位为0x5,表示接收和发送使能// 配置UFCON0寄存器(UART FIFO控制寄存器)UFCON0 |= (1 << 0); // 设置第0位,使能FIFOUFCON0 |= (1 << 1); // 设置第1位,复位接收FIFOUFCON0 &= ~(0x3 << 4); // 清除第4-5位,确保无特殊FIFO深度设置UFCON0 |= (1 << 4); // 设置第4位,设置FIFO深度为16字节UFCON0 |= (1 << 7); // 设置第7位,使能FIFO接收中断// 配置波特率UBRDIV0 = 325; // 设置波特率分频值,此处波特率计算为9600(具体计算公式需根据时钟频率确定)// 配置中断掩码寄存器INTMSK &= ~(1 << 28); // 使能UART0中断INTSUBMSK &= ~(1 << 0); // 使能UART0接收中断
}
2. 数据发送函数
2.1 函数原型
int uart0_send(unsigned char * data, unsigned char len)
2.2 功能描述
-
发送数据到 UART0。
-
循环发送数据,直到所有数据发送完成。
2.3 实现细节
-
检查
UTRSTAT0
的第 2 位,确保发送缓冲区为空。 -
将数据写入
UTXH0
发送缓冲区。 -
返回实际发送的数据长度。
// UART0发送数据函数
int uart0_send(unsigned char * data,unsigned char len)
{int i = 0;// 循环发送数据for(i = 0;i < len;i++){// 等待发送缓冲区为空(UTRSTAT0的第2位为1表示发送缓冲区为空)while(!(UTRSTAT0 & (1 << 2)));UTXH0 = data[i]; // 将数据写入发送缓冲寄存器}return i; // 返回发送的数据长度
}
3. 数据接收函数
3.1 函数原型
unsigned char uart0_recv(unsigned char * data, unsigned char len)
3.2 功能描述
-
从 UART0 接收数据。
-
读取 Rx FIFO 中的数据,直到读取到指定长度或 FIFO 中无数据。
3.3 实现细节
-
读取
UFSTAT0
的第 0-5 位,获取 Rx FIFO 中的数据量。 -
比较数据量和指定长度,确定实际读取长度。
-
从
URXH0
读取数据,存入目标缓冲区。 -
返回实际读取的数据长度。
// UART0接收数据函数
unsigned char uart0_recv(unsigned char * data , unsigned char len)
{int i = 0;// 获取FIFO中数据的数量(UFSTAT0的低6位表示接收FIFO中的数据数量)unsigned char data_cnt = UFSTAT0 & 0x3f;// 计算实际接收的数据长度(取len和data_cnt的较小值)unsigned char len_r = (len < data_cnt) ? len :data_cnt;// 循环读取数据for(i = 0; i < len_r; i++){data[i] = URXH0; // 从接收缓冲寄存器读取数据}return i; // 返回实际接收的数据长度
}