⚠️裸机仓库:https://gitee.com/simonchina_carel_li/mini2440-bare-metal.git
⚠️Tag:
13-sys-tick
1. 为什么要系统Tick?
在前面的SDRAM测试程序中(),
我们有这样的部分,
C
// -- TODO: 如果你有定时器驱动,在这里记录 start_time --
// uint32_t start_time = timer_get_ticks();
for (i = 0; i < words_1MB; i++) {
pDst[i] = pSrc[i];
}
// -- TODO: 在这里记录 end_time,计算 MB/s --
// uint32_t end_time = timer_get_ticks();
// printf(" -> 耗时: %d ticks\r\n", end_time - start_time);
我们需要有一个后台运行的准确的系统石基定时器,
然后通过获取每个时刻的Tick值做类似间隔判断的操作,
后面随便程序越来越复杂,这种需求越发迫切,
因此我们先实现这个系统Tick的框架功能
2. 方案分析
先看下框图,

定时器是先通过预分频器,然后通过分频器,进行了两次分频,
而且,Timer4是个纯内部定时器,没有外部IO,
因此我们选择Timer4作为系统Tick定时器,
查阅数据手册,整理具体方案项,
| 项 | 怎么做 |
|---|---|
| 定时器输入时钟 | 定时器输入时钟频率 = P C L K / 预分频值 + 1 / 分频值 定时器输入时钟频率 = PCLK / {预分频值+1} / {分频值} 定时器输入时钟频率=PCLK/预分频值+1/分频值 |
| 我们不进行预分频,然后再继续8分频好了, | |
| 那么, 定时器输入时钟频率 = 50.625 M H z / 1 / 8 = 6.328125 M H z 定时器输入时钟频率 = 50.625MHz / 1 / 8 = 6.328125MHz 定时器输入时钟频率=50.625MHz/1/8=6.328125MHz | |
| 预分频值 = 0 | |
| 分频值 = 8 | |
| Tick的粒度设计为10ms | 那么每次定时器装填值, |
| 在6.328125MHz的输入时钟基础上, | |
| 6.328125 M H z / 1000 ∗ 10 − 1 = 63280.25 = > 63280 6.328125MHz / 1000 * 10 - 1 = 63280.25 => 63280 6.328125MHz/1000∗10−1=63280.25=>63280 | |
| 这个值没有溢出uint16~ | |
| 要实现自动装载 | 查阅手册得知,要实现自动装载,必须先手动装载一次, |
| 将初始值扔进去, | |
| 就是要:手动装载一次 > 切换到自动装载 | |
| 实现Tick自增 | 利用中断框架,实现irq_int_timer4_handler, |
| 然后在此函数中做Tick递增即可 | |
| 然后实现一个Tick的获取接口,供应用层使用 |
3. 代码实现
3.1 Tick代码
创建common/timer.c,
查阅手册,具体寄存器配置见注释,
C
#include "s3c2440a.h"
#include <stdbool.h>
static volatile unsigned long sys_tick_count_10ms = 0;
/// @brief 定时器初始化
/// PCLK = 50.625MHz
void timer_init()
{
// 目标: 定时器输入时钟频率 = 50.625MHz / 1 / 8 = 6.328125MHz
// 定时器输入时钟频率 = PCLK / {预分频值+1} / {分频值}
// 预分频全部是0
TCFG0 = (0 << 8) | (0 << 0);
// 分频值是8
TCFG1 = (0b0010 << 16);
// 每个tick 10ms
// 设置装填值
// 6.328125MHz / 1000 * 10 = 63281.25
TCNTB4 = 63281;
// 先要手动装填一次
// [22]自动重载=0, [21]手动更新=1, [20]启动=0
// 此时缓冲寄存器的值被装载到了内部倒数器中
TCON = (1 << 21);
// 启动定时器,并开启自动重载
// [22]自动重载=1, [21]手动更新=0, [20]启动=1
// 注意:手动更新位必须清零!
TCON = (1 << 22) | (1 << 20);
// 开启Timer4中断
irq_src_enable(IRQ_INT_TIMER4, true);
}
void irq_int_timer4_handler(void)
{
sys_tick_count_10ms++;
}
unsigned long sys_tick_get_ms()
{
return sys_tick_count_10ms * 10;
}
将初始化函数放到hal_init()中调用,调整Makefile等,
3.2 测试程序
创建Tick测试程序tick/main.c,
C
#include "s3c2440a.h"
int main()
{
unsigned long cnt_bef = 0;
while (1) {
easy_delay_ms(100);
unsigned long cnt = sys_tick_get_ms();
if (cnt - cnt_bef >= 1000) {
cnt_bef = cnt;
uart0_printf("sys_tick_get_ms(): %d\n", cnt);
}
}
return 0;
}
功能就是1S周期打印tick值,判断定时器是否准确~
编译烧录运行,

3.3 完善SDRAM测试程序
我们也把之前的SDRAM的测试程序给做完善,
将原来TODO注释的部分给实现,
C
// -- 记录 start_time --
uint32_t start_time = sys_tick_get_ms();
for (i = 0; i < words_1MB; i++) {
pDst[i] = pSrc[i];
}
// -- 记录 end_time,计算 MB/s --
uint32_t end_time = sys_tick_get_ms();
printf(" -> 耗时: %d ms\r\n", end_time - start_time);
编译、运行,

程序卡死了~!
这是为啥?
别急,还记得之前我们将程序的栈空间移植到了SDRAM中了吗?
对应的.data段和.bss段也放倒了SDRAM的开头,
而SDRAM的测试程序,第一步是要从SDRAM的开头 写值一直写到SDRAM的结尾,
那么,.data和.bss段直接就被覆盖了,破坏了C程序的运行环境,所以导致卡死不意外了~
要怎么解决?
答案就是要跳过.data和.bss段,并且减小总测试区域,比如64MB→62MB,防止把顶部栈空间也覆盖了
C
// 宏定义 SDRAM 的基地址和容量
extern char __bss_end[];
#define SDRAM_BASE ((unsigned long)__bss_end) //0x30000000
#define SDRAM_SIZE_BYTES (62 * 1024 * 1024) // 62MB
再次测试,

可以看到,1MB的读写耗时也打印出来了,原来真的有2S这样慢~