013.定时器之系统Tick实现|千篇笔记实现嵌入式全栈/裸机篇

⚠️裸机仓库: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这样慢~

相关推荐
沸速存储4 分钟前
CPU 和 GPU 核心差别在哪?为什么 AI 训练离不开 GPU
服务器·人工智能·科技·嵌入式硬件·电脑
IT古董16 分钟前
【MES学习笔记系列】MES 厂商与产品对比
笔记·学习
wangyue_msn_8640 分钟前
Agent知识学习笔记——03 RAG
笔记·学习·ai编程
嘶哈哈哈1 小时前
# 异构十电机机架数学建模:从单电机力矩到受约束控制分配
笔记
呱呱巨基1 小时前
Linux 查找命令
linux·笔记·学习·查找命令
basketball6161 小时前
软考中级网络工程师 第4章 无线通信网 备考总结
linux·服务器·网络·网络工程师·软考
我不会JAVA!1 小时前
【无标题】
嵌入式硬件·h.264
芯岭技术郦1 小时前
OM6625A芯片 Timer 定时器计数模式使用说明
stm32·单片机·嵌入式硬件
好评1242 小时前
【Linux】进程信号
linux·运维·服务器
zhangrelay2 小时前
ROS 2 Lyrical 第4章 URDF机器人建模与Gazebo Garden仿真
linux·笔记·学习·ubuntu·机器人·ros2