【STM32】软件I2C控制频率

在上一篇文章中,我们已经介绍了整个软件I2C的实现原理,但是也遗留了一个问题,那就是I2C速率的控制,其实就是控制SCL信号的频率。

微秒级延时

在上篇文章中,我们使用了SysTick进行延时,具体如下:

    typedef enum
    {
        Standard_Mode = 100 * 1000,
        Fast_Mode = 400 * 1000,
        Fast_Mode_Plus = 1000 * 1000,
    } I2C_SW_Speed_Mode_e; //i2c速率 unit Hz

static void i2c_delay(I2C_SW_Speed_Mode_e speed_mode)
{
    uint32_t temp;
    SysTick->LOAD = SystemCoreClock / 8 / (speed_mode * 2);
    SysTick->VAL = 0X00; //
    SysTick->CTRL = 0X01; //
    do
    {
        temp = SysTick->CTRL; //
    }
    while((temp & 0x01) && (!(temp & (1 << 16)))); //
    SysTick->CTRL = 0x00; //
    SysTick->VAL = 0X00; //
}

关于SysTick延时的原理,可以参考这篇文章

HAL库下的systick 底层配置 HAL_Delay实现原理 微秒级延时(非中断)以及一些重写延时的小坑 关于HAL_Delay的使用问题_hal_inctick配置__zs_dawn的博客-CSDN博客

STM32入门:Systick(嘀嗒定时器)学习_我是混子我怕谁的博客-CSDN博客

在这里就说明一下Load的值怎么来的。

首先,Systick使用的是系统时钟的8分频,例如系统时钟为400MHz,8分频后为50MHz。

那么1/50000000秒即1/50微秒产生一个周期的振动。

所以,需要计数50次才会产生1微秒的时钟。也就是计数1次产生0.02微秒,精度就是0.02us。

延时验证

那么这个延时准确吗?我们怎么验证呢?网上一般都会有两种方式:

  • 使用示波器观察(电平翻转延时)
  • keil中使用st-link单步调试

在这里,我推荐使用keil中使用st-link单步调试的方案,至于为什么,我们下文再做解释。

首先,我们重写了一个函数

void delay_us(uint32_t delay)
{
    uint32_t temp;
    SysTick->LOAD = SystemCoreClock / 8 / 1000000 * delay;
    SysTick->VAL = 0X00; //
    SysTick->CTRL = 0X01; //
    do
    {
        temp = SysTick->CTRL; //
    }
    while((temp & 0x01) && (!(temp & (1 << 16)))); //
    SysTick->CTRL = 0x00; //
    SysTick->VAL = 0X00; //
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dQr8E86k-1687864519727)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20230627114808333.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u34unSy4-1687864519728)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20230627114814233.png)]

Core Clock中配置mcu的主时钟频率,本人使用的H7 mcu,在时钟树中配置的主频为400MHz。然后打开Trace Enable。

然后打断点调试(注意,如果有些行不能打断点,可能需要调整代码编译优化等级)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JZWhqYLr-1687864519729)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20230627142100990.png)]

记录两个端点的时刻,然后相减就是delay_us(1);语句运行的耗时。

0.02621103s - 0.02620955s = 0.00000148s = 1.48us
0.02623537s - 0.02623389s = 0.00000148s = 1.48us
0.02620512s - 0.02620364s = 0.00000148s = 1.48us

实测了3次,平均耗时为1.48us。那么与实际值还是有一定偏差呢,对于这个现象个人觉得是正常的,因为delay_us函数中除了延时代码,还有其它代码也有需要耗时的。那是不是其他耗时就是1.48us-1.00us=0.48us呢?

当我们delay_us(2);时,测试结果如下;

0.02617987s - 0.02617736s = 0.00000251s = 2.51us
0.02619555s - 0.02619303s = 0.00000252s = 2.52us
0.02618346s - 0.02618095s = 0.00000251s = 2.51us

好像也不是固定的。。。。所以除了一个固定计数延时,还有一个不固定的其他代码耗时。但是如果只关注微秒级别的值,那么0.xxus确实可以忽略不计。

但是如果是对于1MHz的时钟频率,半周期为0.50us,那这个代码本身运行耗时是没有办法忽略不计的。这也是为什么配置时钟频率越高的时候,实际偏差越大。

高频率时钟延时

上文说到,1MHz频率的时钟,半周期为0.50us,而我们1us计数的偏差都有0.48us,所以我们使用微秒级别的延时是远远不够的。那么不能直接使用上文的delay_us函数。

又由于其他代码自身有延时,所以我们的实际计数时间得减去一个偏置值,这个偏置值是一个经验值,和编译优化等级强相关。

#define CODE_BASE_DELAY_US  (0.40f)
#define I2C_SPEED_HZ_TO_DELAY_TIME_US(speed) (1000000.0/(speed*2)-CODE_BASE_DELAY_US)

    typedef enum
    {
        Standard_Mode = 100 * 1000,
        Fast_Mode = 400 * 1000,
        Fast_Mode_Plus = 1000 * 1000,
    } I2C_SW_Speed_Mode_e; //i2c速率 unit Hz

static void i2c_delay(I2C_SW_Speed_Mode_e speed_mode)
{
    float delay = I2C_SPEED_HZ_TO_DELAY_TIME_US(speed_mode);
	
    uint32_t temp;
    SysTick->LOAD = SystemCoreClock / 8 / 1000000 * delay;
    SysTick->VAL = 0X00; //
    SysTick->CTRL = 0X01; //
    do
    {
        temp = SysTick->CTRL; //
    }
    while((temp & 0x01) && (!(temp & (1 << 16)))); //
    SysTick->CTRL = 0x00; //
    SysTick->VAL = 0X00; //
    //printf("delay %0.3f us\r\n",delay);
}

结果

本人的编译优化等级选择的是-Ofast,然后#define CODE_BASE_DELAY_US (0.40f)

最终测得波形比较准确。
在这里插入图片描述
在这里插入图片描述

总结

1、当制作一个延时函数的时候(无论什么方式),当延时足够小的时候,我们就很有必要考虑这个延时函数内部代码本身运行的耗时。
2、很多地方计算SysTick->LOAD的时候会最终减1,这个应该是参考了HAL库函数里的写法。但是如果是上文中的mcu,1个计数就是0.02us,可根据实际情况决定是否减1。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/34105.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu下编译VTK

1.先安装QT&#xff0c;不知道不装行不行&#xff0c;我们项目需要。 2.去VTK官网下载VTK源码。 3.解压源码。 4.编译需要用cmake-gui&#xff0c;装QT的一般都有&#xff0c;但需要把路径添加到PATH才能用。 5.打开cmake-gui&#xff0c;设置源码路径&#xff0c;编译输出路…

Java开发 - Canal的基本用法

前言 今天给大家带来的是Canal的基本用法&#xff0c;Canal在Java中常被我们用来做数据的同步&#xff0c;当然不是MySQL与MySQL&#xff0c;Redis与Redis之间了&#xff0c;如果是他们&#xff0c;那就好办了&#xff0c;我们可以直接通过配置来完成他们之间的主从、主主&…

【SpringBoot】一、SpringBoot3新特性与改变详细分析

前言 本文适合具有springboot的基础的同学。 SpringBoot3改变&新特性 一、前置条件二、自动配置包位置变化1、Springboot2.X2、Springboot3.X 三、jakata api迁移1、Springboot2.X2、Springboot3.X3、SpringBoot3使用druid有问题&#xff0c;因为它引用的是旧的包 四 新特…

MySQL数据库--------简单理解文件的相关信息

作者前言 欢迎小可爱们前来借鉴我的gtiee秦老大大 (qin-laoda) - Gitee.com ———————————————————————————————————— 目录 文件的信息 文件的权限 权限的赋予 —————————————————————————————— 插播一些…

MySQL 备份与恢复

MySQL 备份与恢复 一、数据库备份的分类1.1 数据备份的重要性1.2 数据库备份的分类1.2.1 从物理与逻辑的角度&#xff0c;分为物理备份和逻辑备份1.2.2 从数据库的备份策略角度&#xff0c;分为完全备份&#xff0c;差异备份和增量备份1.2.3 常见的备份方法 二、MySQL完全备份与…

爬虫入门指南(1):学习爬虫的基础知识和技巧

文章目录 爬虫基础知识什么是爬虫&#xff1f;爬虫的工作原理爬虫的应用领域 爬虫准备工作安装Python安装必要的库和工具 网页解析与XPath网页结构与标签CSS选择器与XPathXpath 语法XPath的基本表达式&#xff1a;XPath的谓语&#xff08;Predicate&#xff09;&#xff1a;XPa…

【K8S系列】深入解析K8S存储

序言 做一件事并不难&#xff0c;难的是在于坚持。坚持一下也不难&#xff0c;难的是坚持到底。 文章标记颜色说明&#xff1a; 黄色&#xff1a;重要标题红色&#xff1a;用来标记结论绿色&#xff1a;用来标记一级论点蓝色&#xff1a;用来标记二级论点 Kubernetes (k8s) 是一…

elementui el-table-column表头换行,自定义表头以及排序图标的位置放置

目录 1、普通表头换行⭐️想实现以下效果 2、表头换行时调整文字和排序图标的位置⭐️想实现以下效果遇到问题 效果如下遇到问题 效果如下⭐️最终成功实现以下效果 &#x1f44d;写在最后 1、普通表头换行 https://www.jb51.net/article/228935.htm // 在需要换行的地方加入换…

开源虚拟化工具VirtualBox安装部署

什么是Virtualbox VirtualBox是一款由Oracle开发和维护的免费开源虚拟化软件&#xff0c;用于在一台计算机上创建和管理多个虚拟机。它允许用户在单个物理计算机上运行多个操作系统&#xff0c;例如Windows、Linux、macOS等。VirtualBox提供了一个虚拟化环境&#xff0c;使用户…

云原生(第二篇)k8s-二进制搭建

准备五台机器&#xff1a; master01&#xff1a;192.168.169.10 node01&#xff1a;192.168.169.40 node02&#xff1a;192.168.169.50 master02&#xff1a;192.168.169.60 负载均衡nginxkeepalive01&#xff08;master&#xff09;&#xff1a;192.168.169.20 负载均衡…

9.用python写网络爬虫,完结

前言 这是python网络爬虫的最后一篇给大家做个总结&#xff0c;且看且珍惜把&#xff01; 截止到目前&#xff0c; 前几章本书介绍的爬虫技术都应用于一个定制网站&#xff0c;这样可以帮助我们更加专注于学习特定技巧。而在本章中&#xff0c;我们将分析几个真实网站&#xff…

azure databricks因为notebook 日志打多或者打印图片太多,往下拉卡死怎么处理

1、同事碰到个问题&#xff0c;databricks 页面卡死不动了 2、我。。。。。。。。测试了下搞不定&#xff0c;找azure的工程师&#xff0c;特此笔记如下图 !](https://img-blog.csdnimg.cn/5db9756d0e224d15a9a607561b47591f.png)

2014年全国硕士研究生入学统一考试管理类专业学位联考逻辑试题——纯享题目版

&#x1f3e0;个人主页&#xff1a;fo安方的博客✨ &#x1f482;个人简历&#xff1a;大家好&#xff0c;我是fo安方&#xff0c;考取过HCIE Cloud Computing、CCIE Security、CISP、RHCE、CCNP RS、PEST 3等证书。&#x1f433; &#x1f495;兴趣爱好&#xff1a;b站天天刷&…

QT ObjectThread moveToThread多线程操作

QT多线程专栏共有15篇文章&#xff0c;从初识线程到、QMutex锁、QSemaphore信号量、Emit、Sgnals、Slot主线程子线程互相传值同步变量、QWaitCondition、事件循环、QObjects、线程安全、线程同步、线程异步、QThreadPool线程池、ObjectThread多线程操作、 moveToThread等线程操…

LeetCode 打卡day46-- 单词拆分和多重背包问题

一个人的朝圣 — LeetCode打卡第46天 知识总结 Leetcode 139. 单词拆分题目说明代码说明 知识总结 今天写了一道题目, 但是还挺难的, 而且是面试高频题目 还过了一遍多重背包问题. 多重背包与01背包的区别在于多重背包限制了物品的个数, 某些物品的个数可能不为1, 可以使用两…

c++day3

#include <iostream> using namespace std;class Person { private:int age;int *p; public://无参构造Person():p(new int(89)){age 18;cout << "无参构造函数" <<endl;}//有参构造Person(int age,int num){this->age age;this->pnew int…

【Docker】Docker中 AUFS、BTRFS、ZFS、存储池概念的详细讲解

前言 Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux或Windows操作系统的机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。 &#x1f4d5;作者简介&#xff1a;热…

基于SpringCloud微服务毕业论文管理系统设计与实现

一、概述 1.1 课题背景及意义 随着学校不断扩大和学生人数的猛增,关于各类教学信息也越来越多。毕业论文的管理也成为了不可避免的一道关卡,学生需要及时获取论文相关进度,学校的管理者要求能方便对论文进行处理。基于这些需求,开发一个实用的微服务管理系统,以满足双方…

GELU激活函数

GELU是一种常见的激活函数&#xff0c;全称为“Gaussian Error Linear Unit”&#xff0c;其图像与ReLU、ELU对比如下&#xff1a; 文章链接&#xff1a;https://arxiv.org/pdf/1606.08415.pdf https://pytorch.org/docs/master/generated/torch.nn.GELU.html 公式为&#xff1…

Spring Cloud - HTTP 客户端 Feign 、自定义配置、优化、最佳实践

目录 一、Feign 是什么&#xff0c;有什么用呢&#xff1f; 二、Feign 客户端的使用 2.1、远程调用 1.引入依赖 2.在order-service&#xff08;发起远程调用的微服务&#xff09;的启动类添加注解开启Feign的功能 3.编写 Feign 客户端 4.通过 Feign 客户端发起远程调用 …