03:TIM定时器

news2024/11/14 21:29:58

目录

一:TIM

1:介绍

2:定时器的分类

3:基本定时器

4:通用定时器

 5:高级定时器

6:定时器的基本结构

二:定时中断功能

A:定时器定时器中断

1:连接图

​编辑 

2:步骤

3:函数介绍

4:代码

三:外部时钟功能

A:定时器外部时钟

1:连接图

2:函数介绍 

3:外部时钟代码


一:TIM

1:介绍

        TIM(Timer)定时器

        定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断

        16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时 (计数器、预分频器、自动重装寄存器构成时基单元)

        不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能

         根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型

2:定时器的分类

 STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4

3:基本定时器

1 :  预分频器+CNT计数器+自动重装载寄存器=时基单元

2 : 基本定时器只能选择内部时钟;他们都为16位的

预分频器 : 可以对72MHZ的计数时钟进行预分频处理; 对输入的基准频率提前进行一个分频的操作

eg : 预分频器写0,那就是不分频,或者说是1分频 ; 输出频率=输入频率=72MHz

预分频器写1,那就是2分频,输出频率(实际分频系数)=输入频率/2=36MHz

预分频器写2,那就是3分频,输出频率(实际分频系数)=输入频率/3= 24MHz

所以预分频器的值和实际的分频系数相差了1;  实际分频系数=预分频器的值+1

计数器 : 计数器可以对预分频后的计数时钟进行计数,预分频器每来一个上升沿计数器就+1

        所以计数器的值在计时过程中会不断地自增运行,直到达到目标值(自动重装载寄存器)然后产生中断,然后在重新开始计数

自动重装载寄存器(固定值) : 储存的是我们的计数目标,产生中断的目标值,(当计数器达到目标值就产生中断)

流程: 基准时钟------->预分频器------>计数器<--------->自动重装载计数器

计数器不断自增,会和自动重装载寄存器比较,当两个的值相同时,产生更新中断和更新事件;

cpu会响应更新中断

4:通用定时器

 5:高级定时器

6:定时器的基本结构

二:定时中断功能

A:定时器定时器中断

我们使用的是通用定时器TIM2在案列中(内部时钟)

1:连接图

 

2:步骤

1: 开启时钟 (RCC)

2: 选择时基单元的时钟 (TIM_InternalClockConfig--选择内部时钟)

3: 配置时基单元  (TIM_TimeBaseInit)

4 : 使能更新中断( TIM_ITConfig中断时钟控制)

5: NICV的配置   (见 02: STM32)

6: 启动定时器  (TIM_Cmd)

3:函数介绍

在stm32f10x tim.h文件中的函数-----时钟源选择函数   (选择时基单元的时钟)

void TIM_InternalClockConfig(TIM_TypeDef* TIMx);


void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);


void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_TIM_TIxExternalCLKSource,
uint16_t TIM_ICPolarity, uint16_t ICFilter);


void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);


void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, 
uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);


void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);

TIM_InternalClockConfig : 选择内部时钟

TIM_ITRxExternalClockConfig : 选择TIR其他定时器的时钟

TIM_TIxExternalClockConfig : 选择TIx捕获通道的时钟

TIM_ETRClockMode1Config : 选择ETR通过外部时钟模式1输入的时钟

TIM_ETRClockMode2Config : 选择ETR通过外部时钟模式2输入的时钟

TIM_ETRConfig : 单独用来配置ETR引脚的预分频器、极性、滤波器这些参数的

 在stm32f10x tim.h文件中的函数-------时基单元函数

void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);

TIM_TimeBaseInit : 时基单元初始化,;TIMX选择某个定时器;  TIM_TimeBaseInitStruct:结构体包含了TIM配置的一些参数;

 在stm32f10x tim.h文件中的函数-------中断输出控制函数

void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);

TIM_ITConfig : 使能中断输出信号

 在stm32f10x tim.h文件中的函数-------运行控制函数函数

void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);

 TIM_Cmd : 选择启动那个定时器, 选择使能还有失能

 在stm32f10x tim.h文件中的函数-------单独修改初始化函数中的重要参数

不能为了某一个参数.直接重新初始化,太关于麻烦,直接更改某一个参数即可

void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);


void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);

void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);

void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);
 

void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);

uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);
 

uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);
 

TIM_PrescalerConfig :  单独写预分频值

TIM_CounterModeConfig : 改变计数器的计数模式

TIM_ARRPreloadConfig : 自动重装器预装功能配置

TIM_SetCounter : 给计数器写入一个值

TIM_SetAutoreload : 给自动重装器写入一个值

TIM_GetCounter : 获取当前计数器的值

TIM_GetPrescaler :获取当前的预分频器的值

在stm32f10x tim.h文件中的函数--其他函数

void TIM_DeInit(TIM_TypeDef* TIMx);

void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);

TIM_DeInit : 恢复缺省配置

TIM_TimeBaseStructInit : 结构体变量赋一个默认值

4:代码

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Timer.h"
int16_t Num;

extern int16_t Num;
void Timer_init(void){
//第一步是开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//第二步,选择时基单元的时钟 (stm23上电默认使用的是内部时钟,这一行代码可以省略)
TIM_InternalClockConfig(TIM2);
//第三步,配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数
/*计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
					       = CK_PSC / (PSC + 1) / (ARR + 1
	定时频率=72M/(PSC+1)/(ARR+1)
	72MHZ=72000KHZ
	72000KHZ/7200=10KHZ=10000HZ
	T=1/F   T=1/10000hz=0.0001s=0.1ms
	然后以0.1ms的周期计10 000个数,所以就是1s
	
	*/
TIM_TimeBaseInitStructure.TIM_Period=10000-1;			//自动重装载寄存器ARR
TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1;  //预分频器PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;//高级定时器特有的(重复寄存器)
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	
//第四使能更新中断

TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
	
	
TIM_ClearFlag(TIM2, TIM_FLAG_Update);  //手动清除更新中断标志位
	
//第五步NICV的配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	
	
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
//第六步启动定时器
TIM_Cmd(TIM2,ENABLE);
}

void TIM2_IRQHandler(){
	//检查中断标志位
	if (	TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
	{
		//清除标志位
		Num++;
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
	}
}


int main(void)
{
	OLED_Init();
	Timer_init();
	OLED_ShowString(1, 1, "Num:");
	
	while (1)
	{
		OLED_ShowNum(1,5,Num,5);
	}
}

为什么要清除中断标志位

响应中断条件是:中断使能中断标志同时成立

单片机要靠查询中断标志来判断是否要进入中断,如果你不清除中断标志,本次中断退出,单片机又会检测到中断标志,因此重复进入中断

在STM32微控制器中,中断是一种重要的机制,用于响应外部事件或内部条件的变化。当一个中断事件发生时,相应的中断标志位会被置位(1),以表示中断事件已经发生。但是,在处理完中断之后,必须清除中断标志位(0),以确保下一次中断事件的正确触发。

清除中断标志位的主要目的有以下几个方面:

1. 防止重复触发:如果不清除中断标志位,当中断处理程序退出后,如果中断标志位仍然保持置位状态,可能会导致重复触发中断。这样会导致中断处理程序不停地执行,影响系统正常运行。

2. 确保正确的中断优先级:在STM32微控制器中,不同的外设和中断源具有不同的优先级。当多个中断源同时触发时,只有优先级最高的中断源会被处理。如果不清除中断标志位,可能会导致错误的中断源被处理,影响系统的功能和性能。

3. 确保正确的中断嵌套:STM32微控制器支持中断的嵌套执行。当一个高优先级的中断正在执行时,如果有一个更高优先级的中断进来,系统会自动挂起当前中断,转而执行更高优先级的中断。在挂起期间,中断标志位可能会保持置位状态。当更高优先级的中断执行完毕后,必须清除该中断的标志位,以便继续执行之前挂起的中断。

因此,为了确保中断系统的正确运行,必须在中断处理程序中清除相应的中断标志位。这可以通过写入相应的寄存器或调用相应的函数来实现。

时间的计算

计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
                           = CK_PSC / (PSC + 1) / (ARR + 1
    定时频率=72M/(PSC+1)/(ARR+1)

首先72M进行7200分频,得到是10K的计数频率

在10K频率下,记10000个数,就是1s的时间

Arr是自动重装 psc预分频

方表明最大值是0-65535。-1表示有偏差

频率的单位是Hz; 周期的单位是s;

Hz<KHz<MHz; 都是千进的

s秒,ms毫秒,us微秒,ns纳秒;  都是千进的

72M进行7200分频,得到10KHz的计数频率。

T=1/F 所以T=1/10 000hz = 0.0001s = 0.1ms

然后以0.1ms的周期计10 000个数,所以就是1s
 

    72MHZ=72000KHZ
    72000KHZ/7200=10KHZ=10000HZ
    T=1/F   T=1/10000hz=0.0001s=0.1ms
    然后以0.1ms的周期计10 000个数,所以就是1s

三:外部时钟功能

A:定时器外部时钟

1:连接图

2:函数介绍 

uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);

void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, 
uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);

TIM_GetCounter :   它用于获取定时器计数器的当前值

TIM_ETRClockMode2Config : 选择ETR通过外部时钟模式2输入的时钟


3:外部时钟代码


#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
extern int16_t Num;
void Timer_init(void){
//第一步是开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
GPIO_InitTypeDef A;
A.GPIO_Mode=GPIO_Mode_IPU;  //上拉
A.GPIO_Pin=GPIO_Pin_0;
A.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&A);
//第二步,选择时基单元的时钟 (stm23上电默认使用的是内部时钟,这一行代码可以省略)
	//TIM_ExtTRGPolarity_NonInverted 高电平或者上升沿有效
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x0f);//选择ETR通过外部时钟模式2输入的时钟

	
//第三步,配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数
/*计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
					       = CK_PSC / (PSC + 1) / (ARR + 1
	定时频率=72M/(PSC+1)/(ARR+1)
	72MHZ=72000KHZ
	72000KHZ/7200=10KHZ=10000HZ
	T=1/F   T=1/10000hz=0.0001s=0.1ms
	然后以0.1ms的周期计10 000个数,所以就是1s
	
	*/
TIM_TimeBaseInitStructure.TIM_Period=10-1;			//自动重装载寄存器ARR
TIM_TimeBaseInitStructure.TIM_Prescaler=1-1;  //预分频器PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;//高级定时器特有的(重复寄存器)
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	
//第四使能更新中断

TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
	
	
TIM_ClearFlag(TIM2, TIM_FLAG_Update);//手动清除更新中断标志位
	
//第五步NICV的配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	
	
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
//第六步启动定时器
TIM_Cmd(TIM2,ENABLE);
}

void TIM2_IRQHandler(){
	//检查中断标志位
	if (	TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
	{
		//清除标志位
		Num++;
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
	}

}



uint16_t  Timer_GetCounter(void){
	//TIM_GetCounter。它用于获取定时器计数器的当前值。
	return TIM_GetCounter(TIM2);  
}



uint16_t Num;

int main(void)
{
	OLED_Init();
	Timer_init();
	
	OLED_ShowString(1, 1, "Num:");
	OLED_ShowString(2, 1, "CNT:");
	
	while (1)
	{
		OLED_ShowNum(1, 5, Num, 5);
		OLED_ShowNum(2, 5, Timer_GetCounter(), 5);
	}
}

本实验实验了外部时钟(对外式红外传感计数器),当对外式红外传感计数器达到了某个数值是触发定时器

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【FreeRTOS】【应用篇】任务创建

前言 从本篇开始&#xff0c;将不再太过于关心 FreeRTOS 的内核细节&#xff0c;把重心转移到对 FreeRTOS 的应用上来。 本篇代码大部分参考野火的 FreeRTOS 教程。 一、静态任务和动态任务创建的区别 1. 概念解析 在 FreeRTOS 中&#xff0c;我们可以选择两个不同的函数进…

微信小程序,封装身高体重选择器组件

wxml代码&#xff1a; // 微信小程序的插值语法不支持直接使用Math <wxs src"./ruler.wxs" module"math"></wxs> <view class"ruler-container"><scroll-view scroll-left"{{scrollLeft}}" enhanced"{{tru…

探索软件项目管理的本质及概念

什么是软件项目管理&#xff1f; 软件项目管理是指对软件项目从规划、组织、指挥、控制到最终交付的全过程进行有效管理的一种方法。它通过合理的资源分配、有效的沟通和高效的协作&#xff0c;确保软件项目能够按照预定的目标、时间和质量要求完成。在现代信息技术逐渐普及和…

互联网企业内部FAQ的设计指南:帮助企业用户快速解决常见问题!

在互联网企业中&#xff0c;内部FAQ&#xff08;Frequently Asked Questions&#xff09;是帮助企业用户快速解决常见问题的重要工具。一个优秀的内部FAQ不仅可以提高用户满意度&#xff0c;还可以减轻客服团队的负担。本文将介绍互联网企业内部FAQ的设计指南&#xff0c;帮助企…

知识储备--基础算法篇-动态规划

1.前言 第一次接触动态规划&#xff0c;不知道具体什么意思&#xff0c;做了题才发现动态规划就是把大问题变成小问题&#xff0c;并解决了小问题重复计算的方法称为动态规划。比如上楼梯&#xff0c;一次上一阶或二阶&#xff0c;求有多少种算法&#xff0c;就可以拆成最后一…

Flutter 小技巧之 3.13 全新生命周期 AppLifecycleListener

Flutter 3.13 在 Framework 里添加了 AppLifecycleListener 用于监听应用生命周期变化&#xff0c;并响应退出应用的请求等支持&#xff0c;那它有什么特殊之处&#xff1f;和老的相比又有什么不同&#xff1f; 简单说&#xff0c;在 Flutter 3.13 之前&#xff0c;我们一般都…

计网第三章(数据链路层)(五)

目录 一、以太网交换机自学习和转发帧的过程 1.两层交换机和三层交换机 2.以太网交换机的基本原理 3.具体实现过程 一、以太网交换机自学习和转发帧的过程 1.两层交换机和三层交换机 大家可能注意到平常做题时有叫两层交换机&#xff0c;或者三层交换机的。 两层交换机就…

为什么需要websocket?

一、为什么需要websocket&#xff1f; 前端和后端的交互模式最常见的就是前端发数据请求&#xff0c;从后端拿到数据后展示到页面中。如果前端不做操作&#xff0c;后端不能主动向前端推送数据&#xff0c;这也是http协议的缺陷。 因此&#xff0c;一种新的通信协议应运而生---…

使用kabeja库解析DXF格式文件

DXF格式是一种开源的CAD文件格式&#xff0c;如何实现Java代码对齐的解析&#xff0c;在网上找了很久&#xff0c;也没有找到非常成熟的库。很奇怪&#xff0c;开源的格式&#xff0c;正常应该会有很多的库来支持。只找到了一个kabeja库&#xff0c;最新版本还是2008年出的0.4版…

GTK3实现自定义列表

使用gtk,如果想自己定义列表,思路可以将每个列表项作为一个hbox,整个列表是一个vbox。通过对容器动态的添加删除,实现列表操作,同时添加任何自己所需要的控件。 下面的例子是实现一个显示图片、按钮和进度条的列表,并且进行上移下移,具有添加和删除列表项功能但没有演示…

网站巡查与SEO:爱校对如何确保内容的最佳质量?

随着互联网的飞速发展&#xff0c;企业和个人正在寻找优化他们网站内容的最佳方式。在这个数字化时代&#xff0c;网站巡查和SEO已成为维持网站高度相关性和可见性的关键。此时&#xff0c;工具如“爱校对”不仅帮助检查文本的质量&#xff0c;还确保内容对搜索引擎优化&#x…

STM32使用定时器实现微秒(us)级延时

STM32使用定时器实现微秒&#xff08;us&#xff09;级延时 引言前期准备介绍系统时钟定时器时钟 项目项目介绍STM32CubeMX程序 引言 目前开发STM32普遍使用HAL库&#xff0c;但 HAL 库封装的延时函数目前仅支持 ms 级别的延时&#xff0c;日常很多情况下会用到 us 延时&#…

IC封装——从基本概念到TSV

一、IC封装 在之前文章中有大致提过封装&#xff0c;这里展开讲讲 芯片生产流程_沧海一升的博客-CSDN博客每个半导体产品的制造都需要数百个工艺&#xff0c;泛林集团将整个制造过程分为八个步骤&#xff1a;晶圆加工-氧化-光刻-刻蚀-薄膜沉积-互连-测试-封装。_芯片生产流程h…

spring异步框架使用教程

背景 在需求开发过程中&#xff0c;为了提升效率&#xff0c;很容易就会遇到需要使用多线程的场景。这个时候一般都会选择建一个线程池去专门用来进行某一类动作&#xff0c;这种任务到来的时候往往伴随着大量的线程被创建调用。而还有另外一种场景是整个任务的执行耗时比较长…

ElasticSearch 7.4学习记录(DSL语法)

上文和大家一起初次了解了很多ES相关的基础知识&#xff0c;本文的内容将会是实际企业中所需要的吗&#xff0c;也是我们需要熟练应用的内容。 面对ES&#xff0c;我们最多使用的就是查询&#xff0c;当我负责这个业务时&#xff0c;现不需要我去考虑如何创建索引&#xff0c;添…

ubuntu18.04安装keil5(踩坑)看完再享用,别直接上手

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、安装winewine的总结 二、安装Keil5总结 前言 切记看完再享用&#xff0c;别直接上手&#xff0c;不然安装的时候会和我一样踩坑的&#xff08;走了很多弯路…

Unity之用Transform 数组加多个空物体-->简单地控制物体按照指定路线自动行驶

文章目录 **原理解释**&#xff1a;**带注释的代码**&#xff1a;实际运用 当你需要实现物体按照指定路线行驶时&#xff0c;你可以通过以下步骤来实现&#xff1a; 原理解释&#xff1a; 路径点&#xff1a;你需要定义一系列路径点&#xff0c;这些点将构成物体行驶的路线。每…

[软件工具]精灵标注助手目标检测数据集格式转VOC或者yolo

有时候我们拿到一个数据集发现是xml文件格式如下&#xff1a; <?xml version"1.0" ?> <doc><path>C:\Users\Administrator\Desktop\test\000000000074.jpg</path><outputs><object><item><name>dog</name>…

纷享销客连接型CRM助力中国企业全球业务增长

近年来&#xff0c;中国企业出海热度越来越高&#xff0c;中国企业出海之路也越走越宽&#xff0c;全球化步伐明显加速。2023年&#xff0c;中国企业业务出海正进入快车道和分水岭阶段&#xff0c;中国也正在从一个世界工厂变成全球资源的整合者。 0 中国企业出海&#xff0c…

精简体积的OLED 基础驱动库 - OLED_BASIC

打算用一个存储空间不大的Arduino 芯片做点简单的文字和图形显示&#xff0c;屏幕芯片SSD1316&#xff0c;感觉u8g2 占用还是太大&#xff0c;想裁剪别人的现成代码又感觉无从下手&#xff0c;所以就基本上重写了一个OLED 显示库&#xff0c;仓库地址&#xff1a;gitee.com/etb…