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这样慢~

相关推荐
是上好佳佳佳呀2 小时前
【前端(六)】HTML5 新特性笔记总结
前端·笔记·html5
笨笨饿2 小时前
# 52_浅谈为什么工程基本进入复数域?
linux·服务器·c语言·数据结构·人工智能·算法·学习方法
姜太小白2 小时前
【Linux】麒麟V10SP3解决网络设备名不匹配问题
linux·运维·服务器
@小博的博客2 小时前
【Linux探索学习】第六弹:操作系统的概念及冯诺依曼体系结构
linux·学习
青桔柠薯片2 小时前
Linux 设备驱动开发环境构建与系统启动机制剖析
linux·arm开发·驱动开发·imx6ull
三佛科技-134163842122 小时前
无线遥控器开关方案开发 ,无线遥控器开关MCU控制方案设计-基于国产单片机
单片机·嵌入式硬件·物联网·智能家居·pcb工艺
云栖梦泽2 小时前
Linux内核与驱动:8.ioctl驱动基础
linux·c++
talen_hx2962 小时前
《零基础入门Spark》学习笔记 Day 14
大数据·笔记·学习·spark
北城笑笑2 小时前
FPGA 与 市场主流芯片分类详解:SoC/CPU/GPU/DPU 等芯片核心特性与工程应用
前端·单片机·fpga开发·fpga