江协科技STM32学习笔记(第11章 RTC实时时钟)

第11章 RTC实时时钟

实时时钟本质上是一个定时器,但是这个定时器是专门用来产生年月日时分秒,这种日期和时间信息的。学会了RTC实时时钟,就可以在STM32内部拥有一个独立运行的钟表。想要记录或读取日期和时间,就可以通过操作RTC来实现。RTC这个外设比较特殊,它和备份寄存器BKP、电源控制PWR、这两章关联性比较强,在RTC这一章,BKP和PWR会经常来串门。

11.1 Unix时间戳

11.1.1 Unix时间戳

Unix 时间戳(Unix Timestamp)定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数,不考虑闰秒;

时间戳存储在一个秒计数器中,秒计数器为32位/64位的整型变量;

世界上所有时区的秒计数器相同,不同时区通过添加偏移来得到当地时间。

11.1.2 UTC/GMT

GMT(Greenwich Mean Time)格林尼治标准时间是一种以地球自转为基础的时间计量系统。它将地球自转一周的时间间隔等分为24小时,以此确定计时标准;

GMT是以前的时间标准,这是因为GMT有一个棘手的问题,就是地球自转一周的时间,其实不是固定的,由于潮汐力、地球活动等原因,地球目前是越来越慢的,这时再根据一天的时间来定义时间基准,这个时间基准就是在不断变化的。

UTC(Universal Time Coordinated)协调世界时是一种以原子钟为基础的时间计量系统。它规定铯133原子基态的两个超精细能级间在零磁场下跃迁辐射9,192,631,770周所持续的时间为1秒。当原子钟计时一天的时间与地球自转一周的时间相差超过0.9秒时,UTC会执行闰秒来保证其计时与地球自转的协调一致.

11.1.3 时间戳转换

C 标准库 -- | 菜鸟教程 (runoob.com)https://www.runoob.com/cprogramming/c-standard-library-time-h.html

C语言的time.h模块提供了时间获取和时间戳转换的相关函数,可以方便地进行秒计数器、日期时间和字符串之间的转换。

|-------------------------------------------------------------------|---------------------|
| 函数 | 作用 |
| time_t time(time_t*); | 获取系统时钟 |
| struct tm* gmtime(const time_t*); | 秒计数器转换为日期时间(格林尼治时间) |
| struct tm* localtime(const time_t*); | 秒计数器转换为日期时间(当地时间) |
| time_t mktime(struct tm*); | 日期时间转换为秒计数器(当地时间) |
| char* ctime(const time_t*); | 秒计数器转换为字符串(默认格式) |
| char* asctime(const struct tm*); | 日期时间转换为字符串(默认格式) |
| size_t strftime(char*, size_t, const char*, const struct tm*); | 日期时间转换为字符串(自定义格式) |

11.2 BKP备份寄存器和RTC实时时钟

11.2.1 BKP备份寄存器

11.2.1.1 BKP简介

BKP(Backup Registers)备份寄存器;

BKP可用于存储用户应用程序数据。当VDD(2.0~3.6V)电源被切断,他们仍然由VBAT(1.8~3.6V)维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位;

VBAT(V Battery):备用电池电源;

上图中VSS_i和VDD_i(i=1~3)是数字部分电路的供电, VSSA和VDDA是内部模拟部分电路的供电。这四组以VDD开头的供电,都是系统的主电源。在正常使用STM32时,这四组供电,全部都需要接到3.3V的电源上。

VBAT:就是备用电池供电引脚,如果要使用STM32内部的BKP和RTC实时时钟,这个引脚就必须接备用电池,用来维持BKP和RTC,在VDD主电源掉电后的供电。这里备用电池只有一根正极的供电引脚,接电池时,电池正极接到VBAT,电池负极和主电源的负极接在一起,共地就行了。如果VDD断电,VBAT也没电,那BKP里的数据就会清零,因为BKP本质上就是RAM存储器,没有掉电不丢失的能力。

TAMPER引脚产生的侵入事件将所有备份寄存器内容清除;

TAMPER是一个接到STM32外部的引脚,是一个安全保障设计,比如做一个安全系数非常高的设备,设备需要有防拆功能,然后BKP里也存储了一些敏感数据,这些数据不能被别人窃取或篡改,那就可以使用这个TAMPER引脚的侵入检测功能。设计电路时,TAMPER引脚可以先加一个默认的上拉或下拉电阻,然后引一根线,到设备外壳的防拆开关或触点,别人一拆开设备,触发开关,就会在TAMPER引脚产生上升沿或者下降沿,这样STM32就检测到侵入事件了,这时BKP的数据就会自动清零,并且申请中断,在中断里,还可以继续保护设备,比如清除其它存储器数据,然后设备锁死,这样来保证设备的安全。另外,主电源断电后,侵入检测任然有效,这样即使设备关机,也能防拆,这就是TAMPER侵入检测的功能。

RTC引脚输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲;

存储RTC时钟校准寄存器;

用户数据存储容量:

20字节(中容量和小容量)/ 84字节(大容量和互联型)。

我们使用的芯片BKP是20字节,BKP一般只能用来存储少量的参数。

11.2.1.2 BKP基本结构

图中橙色部分我们可以叫做后备区域, BKP处于后备区域,但后备区域不止有BKP、还有RTC的相关电路,也位于后备区域,STM32后备区域的特性就是,当VDD主电源掉电时,后备区域仍然可以由VBAT的备用电池供电,当VDD主电源上电时,后备区域供电会由VBAT切换到VDD,也即是主电源有电时,VBAT不会用到,这样可以节省电池电量。BKP是位于后备区域的,BKP主要有数据寄存器、控制寄存器、状态寄存器和RTC实时时钟校准寄存器这些东西。其中数据寄存器是主要部分,用来存储数据的,每个数据寄存器都是16位的,也就是,一个数据寄存器可以存2个字节,那对于中容量和小容量的设备,里面有DR1、DR2,一直到DR10,总共10个数据寄存器。那一个寄存器存2个字节,所以容量是20个字节。对于大容量和互联型设备,里面除了DR1到DR10,还有DR11到DR12,一直到DR42,总共42个数据寄存器,容量是84个字节。

侵入检测,可以从PC13位置的TAMPER引脚引入一个检测信号,当TAMPER产生上升沿或下降沿时,清除BKP所有的内容,以保证安全。

时钟输出可以把RTC的相关时钟,从PC13位置的RTC引脚输出出去,供外部使用,其中输出校准时钟时,再配合校准寄存器,可以对RTC的误差进行校准。

11.2.2 RTC实时时钟

11.2.2.1 RTC简介

RTC(Real Time Clock)实时时钟;

RTC是一个独立的定时器,可为系统提供时钟和日历的功能;

RTC实时时钟,一般就指提供年月日时分秒这种日期时间信息的计时装置。在51单片机时有DS1302这个芯片,DS1302是外置的RTC芯片,这个芯片可以独立计时,我们需要设置时间或读取时间,就通过通信协议向它发送或接收数据来完成。在我们STM32内部,有这个RTC的外设,所以STM32可以在内部直接实现RTC的功能,这样就不用外挂RTC芯片了,当然RTC芯片所必要的元件,比如备用电池、RTC晶振这些东西就要接到STM32上了。

RTC和时钟配置系统处于后备区域,系统复位时数据不清零,VDD(2.0~3.6V)断电后可借助VBAT(1.8~3.6V)供电继续走时;

为了保持时钟能一直连续运行不出错,在主电源断电后,RTC走时肯定不能停下来,在系统复位时,RTC时间值肯定也不能复位。为了实现这些功能,VBAT接上备用电池就是必须的了。主电源断电后,VBAT的电池可以继续维持BKP和RTC的运行。

32位的可编程计数器,可对应Unix时间戳的秒计数器;

这个32位可编程计数器就是对应的是时间戳里的秒计数器,在读取时间时,我们先得到这个秒数,然后使用time.h模块里的localtime函数,就能立刻知道年月日时分秒的信息了。在写入时间时,我们先填充年月日时分秒信息到struct tm结构体,然后用mktime函数,得到秒数,再先写入到32位计数器即可。这样,操作这个秒计数器的思路就不是很清晰了。得益于时间戳的设计,这个硬件电路就得到了极大的简化,要想实现年月日时分秒的计时,只需要一个32位的秒计数器即可。

20位的可编程预分频器,可适配不同频率的输入时钟;

32位的秒计数器,1秒需要自增一次,所以这个地方驱动计数器的时钟,需要是一个1Hz的信号,但是实际提供给RTC模块的时钟,也就是RTCCLK,一般频率都比较高,所以,显然,我们需要在这之间,加一个分频器,给RTCCLK降一下频率,保证分频器输出给计数器的频率位1Hz。为了适配各种频率的RTCCLK,这里就加了一个20位的分频器,可以选择对输入时钟进行1~2^20这么大范围的分频,这样就可以适配不同频率的输入时钟。

可选择三种RTC时钟源:

HSE时钟除以128(通常为8MHz/128)

LSE振荡器时钟(通常为32.768KHz)

LSI振荡器时钟(40KHz)

这三个时钟可以选择其中一个接入到RTCCLK。

高速时钟,一般供内部程序运行和主要外设使用, 低速时钟,一般供RTC、看门狗这些东西使用。我们最常用的是LSE OSC这一路外部32.768KHz的晶振,提供RTCCLK的时钟。第一个原因就是,中间这一路32.768KHz的晶振,本身就是专供RTC使用的,上面这两路其实是有各自的任务,上面这一路,主要作为系统主时钟,下面这一路主要作为看门狗时钟。它们只是顺带备选当作RTC的时钟。另外是只有这一路的时钟,可以通过VBAT备用电池供电,上下两路时钟,在主电源断电后,是停止运行的。所以要想实现RTC主电源掉电继续走时的功能,必须得选择中间这一路的RTC专用时钟。如果选择上下两路时钟,主电源断电后,时=时钟就暂停了,这样显然会出错。

11.2.2.2 RTC框图
11.2.2.3 RTC基本结构

最左边是RTCCLK时钟来源,这一块需要在RCC里配置, 3个时钟,选择一个当作RTCCLK,之后RTCCLK通过预分频器,对时钟进行分频。余数寄存器是一个自减计数器,存储当前的计数值,重装寄存器是计数目标,决定分频值,分频之后得到1Hz的秒计数信号,通向32位计数器,1秒自增1次,下面还有一个32位的闹钟值,可以设定闹钟,如果不需要的话,下面可以不用管。右边有三个信号可以触发中断,分别是秒信号,计数器溢出信号和闹钟信号,三个信号先通过中断输出控制,进行中断使能,使能的中断才能通向NVIC,然后向CPU申请中断。在程序中,我们可以配置数据选择器选择时钟来源,配置重装寄存器,可以选择分频系数,配置32位计数器,可以进行日期时间的读写。需要闹钟的话,配置32位闹钟值即可,需要中断的话,先允许中断,再配置NVIC。最后写对应的中断函数即可。

11.2.2.4 硬件电路

在最小电路上,外部电路还要再额外加两部分,第一部分就是备用电池供电, 第二部分就是外部低速晶振。

11.2.2.5 RTC操作注意事项

执行以下操作将使能对BKP和RTC的访问:

设置RCC_APB1ENR的PWREN和BKPEN,使能PWR和BKP时钟

设置PWR_CR的DBP,使能对BKP和RTC的访问

若在读取RTC寄存器时,RTC的APB1接口曾经处于禁止状态,则软件首先必须等待RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置1;

必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器;

对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是1时,才可以写入RTC寄存器。

11.3 读写备份寄存器

11.3.1 硬件电路

11.3.2 软件部分

(1)复制《OLED显示屏》并改为《读写备份寄存器》

(2)BKP库函数

cpp 复制代码
void BKP_DeInit(void);             //恢复缺省配置,手动清空BKP所有的数据寄存器
void BKP_TamperPinLevelConfig(uint16_t BKP_TamperPinLevel);    //用于配置TAMPER侵入检测功能,高电平还是低电平触发
void BKP_TamperPinCmd(FunctionalState NewState);               //是否开启侵入检测功能
void BKP_ITConfig(FunctionalState NewState);                   //中断配置,是否开启中断
void BKP_RTCOutputConfig(uint16_t BKP_RTCOutputSource);        //时钟输出功能配置,可以选择在RTC引脚上输出时钟信号,输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲
void BKP_SetRTCCalibrationValue(uint8_t CalibrationValue);     //设置RTC校准值,其实就是写入RTC校准寄存器
void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);  //写备份寄存器,第一参数指定卸载哪个DR里,第二个参数指定要写入的数据
uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);              //读备份寄存器
FlagStatus BKP_GetFlagStatus(void);
void BKP_ClearFlag(void);
ITStatus BKP_GetITStatus(void);
void BKP_ClearITPendingBit(void);

(3) PWR库函数

cpp 复制代码
void PWR_DeInit(void);
void PWR_BackupAccessCmd(FunctionalState NewState);         // 备份寄存器访问使能
void PWR_PVDCmd(FunctionalState NewState);
void PWR_PVDLevelConfig(uint32_t PWR_PVDLevel);
void PWR_WakeUpPinCmd(FunctionalState NewState);
void PWR_EnterSTOPMode(uint32_t PWR_Regulator, uint8_t PWR_STOPEntry);
void PWR_EnterSTANDBYMode(void);
FlagStatus PWR_GetFlagStatus(uint32_t PWR_FLAG);
void PWR_ClearFlag(uint32_t PWR_FLAG);

(4)main.c

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"                      // 调用延时头文件
#include "OLED.h"
#include "Key.h"

uint8_t KeyNum;
uint16_t ArrayWrite[] = {0x1234,0x5678};
uint16_t ArrayRead[2];

int main(void)
{
	OLED_Init();                                 // 初始化OLED屏幕
	Key_Init();
	OLED_ShowString(1,1,"W:");
	OLED_ShowString(2,1,"R:");
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);   //使能PWR时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);   //使能BKP时钟
	PWR_BackupAccessCmd(ENABLE);                         //使能对BKP和RTC的访问
//	BKP_WriteBackupRegister(BKP_DR1,0x1234);
//	OLED_ShowHexNum(1,1,BKP_ReadBackupRegister(BKP_DR1),4);
	while(1)
	{	
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			ArrayWrite[0]++;
			ArrayWrite[1]++;
			BKP_WriteBackupRegister(BKP_DR1,ArrayWrite[0]);
			BKP_WriteBackupRegister(BKP_DR2,ArrayWrite[1]);
			OLED_ShowHexNum(1,3,ArrayWrite[0],4);
			OLED_ShowHexNum(1,8,ArrayWrite[1],4);
		}
        ArrayRead[0] = BKP_ReadBackupRegister(BKP_DR1);   
		ArrayRead[1] = BKP_ReadBackupRegister(BKP_DR2); 
		OLED_ShowHexNum(2,3,ArrayRead[0],4);
		OLED_ShowHexNum(2,8,ArrayRead[1],4);
	}
}

11.4 实时时钟

11.4.1 硬件电路

11.4.2 软件部分

(1)复制复制《OLED显示屏》并改为《实时时钟》

(2)添加驱动文件

(3)使用的RCC库函数

cpp 复制代码
void RCC_LSEConfig(uint8_t RCC_LSE);                   //配置LSE外部低速时钟
void RCC_LSICmd(FunctionalState NewState);             //配置LSI内部低速时钟
void RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource);      //选择RTCCLK的时钟源
void RCC_RTCCLKCmd(FunctionalState NewState);          //启动RTCCLK


FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG);    //获取标志位,LSE时钟不是说让它启动就能立刻启动,还需要1等待标志位LSEREADY置1

(4)RTC库函数

cpp 复制代码
void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);   //配置中断输出
void RTC_EnterConfigMode(void);                                 //进入配置模式,置CRL的CNF位为1,进入配置模式
void RTC_ExitConfigMode(void);                                  //退出配置模式,把CNF位清零
uint32_t  RTC_GetCounter(void);                                 //获取CNT计数器的值,读取时钟,就靠这个函数
void RTC_SetCounter(uint32_t CounterValue);                     //写入CNT计数器的值,设置时间,就靠这个函数    
void RTC_SetPrescaler(uint32_t PrescalerValue);                 //写入预分频器,这个值会写入的预分频器的PRL重装寄存器中,用来配置预分频器的分频系数
void RTC_SetAlarm(uint32_t AlarmValue);                         //写入闹钟值
uint32_t  RTC_GetDivider(void);                                 //读取预分频器中的DIV余数寄存器,余数寄存器是一个自减计数器,一般是为了得到更细致的时间
void RTC_WaitForLastTask(void);                                 //等待上次操作完成
void RTC_WaitForSynchro(void);                                  //等待同步,等待RSF位置1
FlagStatus RTC_GetFlagStatus(uint16_t RTC_FLAG);
void RTC_ClearFlag(uint16_t RTC_FLAG);
ITStatus RTC_GetITStatus(uint16_t RTC_IT);
void RTC_ClearITPendingBit(uint16_t RTC_IT);

(5)MyRTC.c

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include <time.h>
void MyRTC_SetTime(void);

uint16_t MyRTC_Time[]= {2024,8,15,11,13,55};            //十进制前面千万不要补0,因为8进制以0开头

/*RTC初始化函数*/
void MyRTC_Init(void)
{
	/*第1步:开启PWR和BKP的时钟,使能BKP和RTC的访问*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
	PWR_BackupAccessCmd(ENABLE);
	if(BKP_ReadBackupRegister(BKP_DR1 != 0xA5A5))
	{
		/*第2步:开启LSE时钟,并等待LSE时钟启动完成*/
		RCC_LSEConfig(RCC_LSE_ON);                        //启动LSE晶振
		while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)!=SET);   //等待LSE状态标志位置1
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);           //选择RTCCLK时钟源
		RCC_RTCCLKCmd(ENABLE);                            //使能时钟                        
		
		RTC_WaitForSynchro();                             //等待同步 
		RTC_WaitForLastTask();                            //等待上一次操作完成	
		
		RTC_SetPrescaler(32768-1);                        //配置分频系数得到1Hz
		RTC_WaitForLastTask();                            //等待上一次操作完成
	//	RTC_SetCounter(1723690874);	
		MyRTC_SetTime();
		
		/*有的板子RTC晶振起振不了,RTC晶振不起振,就会卡死在RCC_GetFlagStatus(RCC_FLAG_LSERDY)这里
		,这时侯只能备选LSI,进行以下修改*/
	//	RCC_LSICmd(ENABLE);                        //启动LSI晶振
	//	while(RCC_GetFlagStatus(RCC_FLAG_LSIRDY)!=SET);   //等待LSI状态标志位置1
	//	RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);           //选择RTCCLK时钟源
	//	RCC_RTCCLKCmd(ENABLE);                            //使能时钟                        
	//	
	//	RTC_WaitForSynchro();                             //等待同步 
	//	RTC_WaitForLastTask();                            //等待上一次操作完成	
	//	
	//	RTC_SetPrescaler(40000-1);                        //配置分频系数得到1Hz
	//	RTC_WaitForLastTask();                            //等待上一次操作完成
	//	RTC_SetCounter(1723690874);
		BKP_WriteBackupRegister(BKP_DR1,0xA5A5);
	}
	else
		{
			RTC_WaitForSynchro();                             //等待同步 
			RTC_WaitForLastTask();                            //等待上一次操作完成	
		}
}

/*将时间转换位Unix时间戳形式函数*/
void MyRTC_SetTime(void)
{
	time_t time_cnt;
	struct tm time_date;
	time_date.tm_year = MyRTC_Time[0]-1900;
	time_date.tm_mon= MyRTC_Time[1]-1;
	time_date.tm_mday = MyRTC_Time[2];
	time_date.tm_hour = MyRTC_Time[3];
	time_date.tm_min = MyRTC_Time[4];
	time_date.tm_sec = MyRTC_Time[5];
	time_cnt = mktime(&time_date)-8*60*60;
	RTC_SetCounter(time_cnt);
	RTC_WaitForLastTask();
}

/*读取时间函数*/
void MyRTC_ReadTime(void)
{
	time_t time_cnt;
	struct tm time_date;
	time_cnt = RTC_GetCounter() + 8*60*60;
	time_date = *localtime(&time_cnt);
	MyRTC_Time[0]=time_date.tm_year + 1900;
	MyRTC_Time[1]=time_date.tm_mon + 1;
	MyRTC_Time[2]=time_date.tm_mday;
	MyRTC_Time[3]=time_date.tm_hour ;
	MyRTC_Time[4]=time_date.tm_min;
	MyRTC_Time[5]=time_date.tm_sec;
}

(6)MyRTC.h

cpp 复制代码
#ifndef __MYRTC_
#define __MYRTC_
extern uint16_t MyRTC_Time[];  
void MyRTC_Init(void);
void MyRTC_SetTime(void);
void MyRTC_ReadTime(void);
#endif
 

(7)main.c

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"                      // 调用延时头文件
#include "OLED.h"
#include "MyRTC.h"

uint8_t KeyNum;

int main(void)
{
	OLED_Init();                                 // 初始化OLED屏幕
	MyRTC_Init();
	OLED_ShowString(1,1,"Date:XXXX-XX-XX");
	OLED_ShowString(2,1,"Time:XX:XX:XX");
	OLED_ShowString(3,1,"CNT:");
	OLED_ShowString(4,1,"DIV:");
	
	while(1)
	{
		MyRTC_ReadTime();
		OLED_ShowNum(1,6,MyRTC_Time[0],4);
		OLED_ShowNum(1,11,MyRTC_Time[1],2);
		OLED_ShowNum(1,14,MyRTC_Time[2],2);
		OLED_ShowNum(2,6,MyRTC_Time[3],2);
		OLED_ShowNum(2,9,MyRTC_Time[4],2);
		OLED_ShowNum(2,12,MyRTC_Time[5],2);
		OLED_ShowNum(3,6,RTC_GetCounter(),10);
		OLED_ShowNum(2,12,MyRTC_Time[5],2);
		OLED_ShowNum(3,6,RTC_GetCounter(),10);
		OLED_ShowNum(4,6,(32767-RTC_GetDivider())/32767.0*999,10);
	}
}
相关推荐
潮汐退涨月冷风霜1 小时前
机器学习之非监督学习(四)K-means 聚类算法
学习·算法·机器学习
GoppViper1 小时前
golang学习笔记29——golang 中如何将 GitHub 最新提交的版本设置为 v1.0.0
笔记·git·后端·学习·golang·github·源代码管理
羊小猪~~1 小时前
深度学习基础案例5--VGG16人脸识别(体验学习的痛苦与乐趣)
人工智能·python·深度学习·学习·算法·机器学习·cnn
Charles Ray2 小时前
C++学习笔记 —— 内存分配 new
c++·笔记·学习
我要吐泡泡了哦3 小时前
GAMES104:15 游戏引擎的玩法系统基础-学习笔记
笔记·学习·游戏引擎
骑鱼过海的猫1233 小时前
【tomcat】tomcat学习笔记
笔记·学习·tomcat
贾saisai5 小时前
Xilinx系FPGA学习笔记(九)DDR3学习
笔记·学习·fpga开发
北岛寒沫5 小时前
JavaScript(JS)学习笔记 1(简单介绍 注释和输入输出语句 变量 数据类型 运算符 流程控制 数组)
javascript·笔记·学习
铁匠匠匠7 小时前
从零开始学数据结构系列之第六章《排序简介》
c语言·数据结构·经验分享·笔记·学习·开源·课程设计
架构文摘JGWZ8 小时前
Java 23 的12 个新特性!!
java·开发语言·学习