STM32F103C8T6实时时钟RTC

目录

前言

一、RTC基本硬件结构

二、Unix时间戳

2.1 unix时间戳定义

2.2 时间戳与日历日期时间的转换

2.3 指针函数使用注意事项

​三、RTC和BKP硬件结构

 四、驱动代码解析


前言

        STM32F103C8T6外部低速时钟LSE(一般为32.768KHz)用的引脚是PC14和PC15,所以这两个引脚一定不要再外接其它的电路,比如按键、LED灯之类的,会导致LSE时钟频率出错甚至不起振。

        附上完整代码压缩包链接,包含所用到的用户手册和STM32实战手册:

https://jcnwdt8hb184.feishu.cn/wiki/AUXQwtZ6AipW16kUAn0cIi0anMe?form_wx_login=1

一、RTC基本硬件结构

        具体的框图可以查看用户手册309页的图154。RTC的时钟输入源有三种选择,外部高速时钟(8MHz)128分频、外部低速时钟LSE(32.768KHz)、内部低速时钟LSI(40KHz)。只有选择LSE做时钟输入源,才能实现主电源掉电后,由电池(给VBAT供电)供电继续工作。

        假设选择LSE做时钟输入源,预分频器系数PSC可以选择32767。DIV是一个向下递减的计数器,装载32767这个数值,每来一个时钟脉冲就减1,递减到0又重新装载32767,产生溢出事件,这样输出的时钟频率就是1Hz,对应周期1S。这个1S的时钟信号可以用来提供给32位的计数器CNT,每来一个1代表1S。所以CNT计数的数值就代表多少秒。

        用户手册上的框图。RTCCLK就是上面说的三种时钟源之一,主要用来给CNT计数器计数用的;而PCLK1是用来,通过APB1接口获取以及写入寄存器数据用的。

        以前用过的RTC时钟芯片DS1302,它是可以设置日历时间年月日等等的,而STM32F103C8T6的RTC却只有一个32位的CNT计数器。其实这个CNT计数器也可以理解为定时器,不过它和普通的定时器不同的是,当主电源掉电以后,它还能通过电池(给VBAT供电)工作。 

        下图是STM32F4系列的RTC框图,它就可以设置日历时间。

        虽然STM32F103的RTC没有设置日历的功能,但是它便宜,那么,只有一个CNT计数器,如何把它转换为我们日常生活中需要的日历时间:年月日时分秒。

二、Unix时间戳

2.1 unix时间戳定义

        RTC内的CNT计数器就可以用来存储时间戳,然后在软件内将时间戳转换为日历时间。

2.2 时间戳与日历日期时间的转换

        时间戳转换为日历时间并不需要手撕代码,下面是C标准库提供的转换函数。划线的函数是裸机开发RTC常用的函数。

        time_t是对uint32_t类型的重定义,struct tm是time.h头文件中定义的一个结构体,成员见下图,注意其中月的范围是0~11,所以写代码的时候要加1,年是从1900起的数值,所以年要加1900。

        第一个函数time都是用在操作系统里面的,裸机开发用不了。

        函数localtime能将时间戳也就是CNT秒计数器存的数值转换为日历时间,在内部已经自动写了闰年,大小月等等的判断。

2.3 指针函数使用注意事项

        struct tm *localtime(const time_t *)是一个指针函数,对于指针函数,使用的时候要格外的注意,它返回的地址可能有下图中说明的三种情况,当然,基于高内聚低耦合原则,不会使用全局变量,那就只能是静态变量,或者用malloc,calloc在堆上申请的 内存空间。

        如果这个函数用的是malloc申请的地址,那么在使用之后就必须使用free,否则会造成内存泄露。

        如何知道函数使用的究竟是那种方法呢?由于没办法点开源文件,所以我们只能自己设法写代码验证。 可以看到,两次返回的地址都是一样的,说明函数使用的是静态变量。

         也可以自己设计一种用malloc申请内存的方法,看看不同之处。可以看到,没有用free释放内存,导致两次打印的结果是不同的。

        以后写代码越来越多,肯定会接触到很多指针函数,使用的时候都要小心,看看头文件里有没有说明,使用后需要用free释放内存,比如下面这个例子,这是ESP32的HAL开源库中的一个函数,他就使用了calloc申请地址,然后返回,这个时候就需要我们手动是否内存,否则就会造成内存泄露,而且这样的错误可能比较难排查(至少对我这种水平来说是这样)。

        这种带creat的函数,要注意一般都是成对出现的,用delete就可以释放掉内存。

三、RTC和BKP硬件结构

        下面是RTC硬件结构框图,可以看到,预分频器、计数器和闹钟都是位于后备区域,待机时维持供电。

        下图是PN学堂GD32F303ZET6开发板上的RTC电路,当主电源3V3供电时,BAT54C内的2号二极管导通,1号截止,由3V3给VBAT供电;当主电源掉电时,1号导通2号截止,由3V的纽扣电池BT1给VBAT供电。

         

        什么是后备区域呢 ?这就涉及到另一个片上外设BKP,在后备区域内,除了有之前提到的RTC的那些寄存器,还有42个2字节的寄存器用于存储并保护用户数据,比如说一些配置参数、系数可以放到这里面。

        注意,这42个寄存器和内存一样是掉电丢失的,所以如果没有主电源供电了,那必须要有纽扣电池之类的给VBAT供电,它才可以工作。

         该图是GD32F303ZET6的图。RTC信号输出和RTC校准:可以配置RTC信号输出寄存器,通过一个引脚(GD32F303ZET6是PC16)将RTC的时钟输出,然后去检测这个时钟信号,如果发现偏差较大,可以配置校准寄存器用来校准。

        侵入检测寄存器作用,假如产品安全要求较高,不想让别人去拆、分析,就可以使用侵入检测。

 四、驱动代码解析

        就只有一个驱动函数,在rtc_drv.c文件中。我将基于寄存器,逐行分析。

#define MAGIC_CODE	0x5a5a//模码

/**
***********************************************************
* @brief	RTC驱动初始化
* @param
* @return 
***********************************************************
*/
void RtcDrvInit(void)

 (1)这个函数内部就两行代码,其实不写这个函数也行。

  RCC_APB1PeriphResetCmd(RCC_APB1Periph_PWR, ENABLE);
  RCC_APB1PeriphResetCmd(RCC_APB1Periph_PWR, DISABLE);

/*- - - - - - - -复位后备寄存器- - - - - - - - */

PWR_DeInit();

(2)开启时钟,就去用户手册找RCC_APB1ENR,就是把7.3.8 APB1 外设时钟使能寄存器(RCC_APB1ENR)的位28、27置1。

/*- - - - - - - -设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备接口时钟- - - - - - - - */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);

(3) 这个函数内部使用了位带操作,关于这部分的内容,在STM32实战手册中有详细说明,能看懂就行。

/*- - - - - - - -设置寄存器PWR_CR的DBP位,使能对后备寄存器和RTC的访问。- - - - - - - - */
PWR_BackupAccessCmd(ENABLE);

/__________________下面是这个函数在库中的具体内容___________________________/

/**
  * @brief  Enables or disables access to the RTC and backup registers.
  * @param  NewState: new state of the access to the RTC and backup registers.
  *   This parameter can be: ENABLE or DISABLE.
  * @retval None
  */
void PWR_BackupAccessCmd(FunctionalState NewState)
{
  /* Check the parameters */
  assert_param(IS_FUNCTIONAL_STATE(NewState));
  *(__IO uint32_t *) CR_DBP_BB = (uint32_t)NewState;
}

/__________________下面是这个函数中的某些变量具体内容_______________________/

//你必须要自己在keil中去查看才能看明白,这些内容只能用于辅助你理解

#define CR_DBP_BB                (PERIPH_BB_BASE + (CR_OFFSET * 32) + (DBP_BitNumber * 4))


        PERIPH_BB_BASE 就是位带别名区的首地址0x42000000,CR_OFFSET 就是我要配置的这个外设寄存器相当于位带区基地址的偏移量,DBP_BitNumber 就是我要配置外设寄存器中的第几位。
        #define CR_OFFSET                (PWR_OFFSET + 0x00)
        #define PWR_OFFSET               (PWR_BASE - PERIPH_BASE)
        其中PWR_BASE就是要配置的外设寄存器的地址,去查看,地址是0x40007000,之后通过这个地址,在用户手册2.3存储器映像中去找对应的外设,发现是电源控制PWR。
        通过#define DBP_BitNumber            0x08        可以知道配置的是第八位。也就是4.4.1 电源控制寄存器(PWR_CR)的第八位。

(4)打开LSE时钟,是配置用户手册7.3.9 备份域控制寄存器(RCC_BDCR)位0;

        等待LSE稳定这部分库函数的代码写的很巧妙,值得仔细分析。

        /*- - - - - - - -打开外部低速时钟LSE,并等待其稳定- - - - - - - - */
        RCC_LSEConfig(RCC_LSE_ON);
        while ( RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET );

/__________________下面是等待LSE稳定这个函数在库中的具体内容________________/

/**
  * @brief  Checks whether the specified RCC flag is set or not.
  * @param  RCC_FLAG: specifies the flag to check.
  *   
  *   For @b STM32_Connectivity_line_devices, this parameter can be one of the
  *   following values:
  *     @arg RCC_FLAG_HSIRDY: HSI oscillator clock ready
  *     @arg RCC_FLAG_HSERDY: HSE oscillator clock ready
  *     @arg RCC_FLAG_PLLRDY: PLL clock ready
  *     @arg RCC_FLAG_PLL2RDY: PLL2 clock ready      
  *     @arg RCC_FLAG_PLL3RDY: PLL3 clock ready                           
  *     @arg RCC_FLAG_LSERDY: LSE oscillator clock ready
  *     @arg RCC_FLAG_LSIRDY: LSI oscillator clock ready
  *     @arg RCC_FLAG_PINRST: Pin reset
  *     @arg RCC_FLAG_PORRST: POR/PDR reset
  *     @arg RCC_FLAG_SFTRST: Software reset
  *     @arg RCC_FLAG_IWDGRST: Independent Watchdog reset
  *     @arg RCC_FLAG_WWDGRST: Window Watchdog reset
  *     @arg RCC_FLAG_LPWRRST: Low Power reset
  * 
  *   For @b other_STM32_devices, this parameter can be one of the following values:        
  *     @arg RCC_FLAG_HSIRDY: HSI oscillator clock ready
  *     @arg RCC_FLAG_HSERDY: HSE oscillator clock ready
  *     @arg RCC_FLAG_PLLRDY: PLL clock ready
  *     @arg RCC_FLAG_LSERDY: LSE oscillator clock ready
  *     @arg RCC_FLAG_LSIRDY: LSI oscillator clock ready
  *     @arg RCC_FLAG_PINRST: Pin reset
  *     @arg RCC_FLAG_PORRST: POR/PDR reset
  *     @arg RCC_FLAG_SFTRST: Software reset
  *     @arg RCC_FLAG_IWDGRST: Independent Watchdog reset
  *     @arg RCC_FLAG_WWDGRST: Window Watchdog reset
  *     @arg RCC_FLAG_LPWRRST: Low Power reset
  *   
  * @retval The new state of RCC_FLAG (SET or RESET).
  */
FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG)
{
  uint32_t tmp = 0;
  uint32_t statusreg = 0;
  FlagStatus bitstatus = RESET;
  /* Check the parameters */
  assert_param(IS_RCC_FLAG(RCC_FLAG));

  /* Get the RCC register index */
  tmp = RCC_FLAG >> 5;
  if (tmp == 1)               /* The flag to check is in CR register */
  {
    statusreg = RCC->CR;
  }
  else if (tmp == 2)          /* The flag to check is in BDCR register */
  {
    statusreg = RCC->BDCR;
  }
  else                       /* The flag to check is in CSR register */
  {
    statusreg = RCC->CSR;
  }

  /* Get the flag position */
  tmp = RCC_FLAG & FLAG_Mask;
  if ((statusreg & ((uint32_t)1 << tmp)) != (uint32_t)RESET)
  {
    bitstatus = SET;
  }
  else
  {
    bitstatus = RESET;
  }

  /* Return the flag status */
  return bitstatus;
}

/_____________下面是分析,只是用于辅助理解,必须自己动手查看________________/

/** @defgroup RCC_Flag 
  * @{
  */

#define RCC_FLAG_HSIRDY                  ((uint8_t)0x21)
#define RCC_FLAG_HSERDY                  ((uint8_t)0x31)
#define RCC_FLAG_PLLRDY                  ((uint8_t)0x39)

#define RCC_FLAG_LSERDY                  ((uint8_t)0x41)
#define RCC_FLAG_LSIRDY                  ((uint8_t)0x61)
#define RCC_FLAG_PINRST                  ((uint8_t)0x7A)
#define RCC_FLAG_PORRST                  ((uint8_t)0x7B)
#define RCC_FLAG_SFTRST                  ((uint8_t)0x7C)
#define RCC_FLAG_IWDGRST                 ((uint8_t)0x7D)
#define RCC_FLAG_WWDGRST                 ((uint8_t)0x7E)
#define RCC_FLAG_LPWRRST                 ((uint8_t)0x7F)


 


//分析
        这些数字设计的十分巧妙,高3位用于区分要配置哪个寄存器,低五位用于识别是配置寄存器中的第几位。根据高三位,黄色部分配置RCC_CR寄存器、绿色配置RCC_BDCR寄存器、蓝色配置RCC_CSR寄存器。

        我们带入参数((uint8_t)0x41)分析,也就是说RCC_FLAG = ((uint8_t)0x41);

        那么tmp = RCC_FLAG >> 5;结果是0x02,下面这个条件成立。

          else if (tmp == 2)          /* The flag to check is in BDCR register */
          {
                    statusreg = RCC->BDCR;
          }
         语句 tmp = RCC_FLAG & FLAG_Mask;用于获取RCC_FLAG的低5位;用来识别是要配置寄存器中的第几位。也就是查看用户手册,7.3.9 备份域控制寄存器(RCC_BDCR)中的第1位是否被硬件置一了。

          if ((statusreg & ((uint32_t)1 << tmp)) != (uint32_t)RESET)
          {
            bitstatus = SET;
          }

(5)设置时钟源为LSE就是配置用户手册,7.3.9 备份域控制寄存器(RCC_BDCR)的bit9:8

        使能时钟这个函数,里面也是用了位带操作,用同样的方法可以知道是对 7.3.9 备份域控制寄存器(RCC_BDCR)的bit15进行配置。

        /*- - - - - - - -设置RTC时钟源为外部低速时钟LSE,并使能- - - - - - - - */
        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
        RCC_RTCCLKCmd(ENABLE);

(6) 用户手册,16.4.2 RTC控制寄存器低位(RTC_CRL),查看位3是否被置1。

        /*- - - - - - - -等待APB1接口时钟和RTC时钟同步- - - - - - - - */
        RTC_WaitForSynchro();

(7)用户手册,16.4.2 RTC控制寄存器低位(RTC_CRL),查看位5是否被置1。

        /*- - - - - - - -等待上次对 RTC 寄存器写操作完成- - - - - - - - */
        RTC_WaitForLastTask(); 

(8)用户手册,16.4.3 RTC预分频装载寄存器(RTC_PRLH/RTC_PRLL),写入数值。

        /*- - - - - - - -设置分频值32767- - - - - - - - */
        RTC_SetPrescaler(32767);//32768-1 

、、、、、、、、、函数具体内容
/**
  * @brief  Sets the RTC prescaler value.
  * @param  PrescalerValue: RTC prescaler new value.
  * @retval None
  */
void RTC_SetPrescaler(uint32_t PrescalerValue)
{
  /* Check the parameters */
  assert_param(IS_RTC_PRESCALER(PrescalerValue));
  
  RTC_EnterConfigMode();//16.4.2 RTC控制寄存器低位(RTC_CRL),位4置1

  /* Set RTC PRESCALER MSB word */
  RTC->PRLH = (PrescalerValue & PRLH_MSB_MASK) >> 16;//高16位写入0
  /* Set RTC PRESCALER LSB word */
  RTC->PRLL = (PrescalerValue & RTC_LSB_MASK);//低16位写入0x7fff
  RTC_ExitConfigMode();//16.4.2 RTC控制寄存器低位(RTC_CRL),位4置0
}

(9)同之前

        /*- - - - - - - -等待上次对 RTC 寄存器写操作完成- - - - - - - - */
        RTC_WaitForLastTask(); 

(10)用户手册,16.4.5 RTC计数器寄存器 (RTC_CNTH / RTC_CNTL),写入数值。

        /*- - - - - - - -设置时间1970-01-01 00:00:00- - - - - - - - */
        RTC_SetCounter(0); 

(11)向后备区域BKP的BKP_DR1寄存器中写入模码MAGIC_CODE,只要主电源供电或者VBAT有纽扣电池供电,那么即使复位,BKP寄存器中的内容也不会丢失。

BKP_WriteBackupRegister(BKP_DR1, MAGIC_CODE); 

        写入这个模码的作用是什么?在代码中,只有读取BKP_DR1中的内容与模码MAGIC_CODE相同时,才会执行上面讲述的所有代码。当设备第一次上电时,BKP_DR1中的内容肯定不是这个模码,就会执行这些初始化代码,而设备复位之后,由于BKP_DR1中已经有模码了,就不会再执行这些代码了。

        有一个好处,如果复位后不执行这些代码,那么也就不会再初始化时间戳为0,我们CNT计数器中的时间戳就还是一直在计数的值。

	if ( BKP_ReadBackupRegister(BKP_DR1) != MAGIC_CODE )
	{
		/*- - - - - - - -复位后备寄存器- - - - - - - - */
		PWR_DeInit();
		/*- - - - - - - -设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备接口时钟- - - - - - - - */
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
		/*- - - - - - - -设置寄存器PWR_CR的DBP位,使能对后备寄存器和RTC的访问。- - - - - - - - */
		PWR_BackupAccessCmd(ENABLE);
		/*- - - - - - - -打开外部低速时钟LSE,并等待其稳定- - - - - - - - */
		RCC_LSEConfig(RCC_LSE_ON);
		while ( RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET );
		/*- - - - - - - -设置RTC时钟源为外部低速时钟LSE,并使能- - - - - - - - */
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
		RCC_RTCCLKCmd(ENABLE);
		/*- - - - - - - -等待APB1接口时钟和RTC时钟同步- - - - - - - - */
		RTC_WaitForSynchro();
		/*- - - - - - - -等待上次对 RTC 寄存器写操作完成- - - - - - - - */
		RTC_WaitForLastTask();
		/*- - - - - - - -设置分频值32767- - - - - - - - */
		RTC_SetPrescaler(32767);//32768-1
		/*- - - - - - - -等待上次对 RTC 寄存器写操作完成- - - - - - - - */
		RTC_WaitForLastTask();
		/*- - - - - - - -设置时间1970-01-01 00:00:00- - - - - - - - */
		RTC_SetCounter(0);
		BKP_WriteBackupRegister(BKP_DR1, MAGIC_CODE);
		return;
	}

(12) 那么,复位后要执行的初始化代码是哪些呢?为什么是这些代码需要执行呢?

    /*- - - - - - - -设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备接口时钟- - - - - - - - */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
    /*- - - - - - - -设置寄存器PWR_CR的DBP位,使能对后备寄存器和RTC的访问。- - - - - - - - */
    PWR_BackupAccessCmd(ENABLE);
    /*- - - - - - - -等待APB1接口时钟和RTC时钟同步- - - - - - - - */
    RTC_WaitForSynchro();
    /*- - - - - - - -等待上次对 RTC 寄存器写操作完成- - - - - - - - */
    RTC_WaitForLastTask();

其它代码不用执行的原因可以参考下图

五、其它代码部分解析

/**
***********************************************************
* @brief 设置时间
* @param time,输入,日历时间
* @return 
***********************************************************
*/
void SetRtcTime(RtcTime_t *time)
{
	time_t timeStamp;//时间戳
	struct tm timeInfo;
	memset(&timeInfo, 0, sizeof(timeInfo));//结构体初始化
	
	timeInfo.tm_year = time->year - 1900;
	timeInfo.tm_mon = time->month - 1;
	timeInfo.tm_mday = time->date;
	timeInfo.tm_hour = time->hour;
	timeInfo.tm_min = time->minute;
	timeInfo.tm_sec = time->second;
	
	timeStamp = mktime(&timeInfo) - 8 * 60 * 60;
	/*等待上次对 RTC 寄存器写操作完成*/
	RTC_WaitForLastTask();
	/*设置时间*/
	RTC_SetCounter(timeStamp);//因为这里面是基于零时区实现的,要想得到东八区即北京时间,时间戳就要减8*60*60S
}


/**
***********************************************************
* @brief 获取时间
* @param time,输出,日历时间
* @return 
***********************************************************
*/
void GetRtcTime(RtcTime_t *time)
{
	time_t timeStamp;
	struct tm* timeInfo;
	
	timeStamp = RTC_GetCounter() + 8 * 60 * 60;
	timeInfo = localtime(&timeStamp);
	
	time->year = timeInfo->tm_year + 1900;
	time->month = timeInfo->tm_mon + 1;
	time->date = timeInfo->tm_mday;
	time->hour = timeInfo->tm_hour;
	time->minute = timeInfo->tm_min;
	time->second = timeInfo->tm_sec;
}

        在函数void SetRtcTime(RtcTime_t *time)中,有这样一行代码:

timeStamp = mktime(&timeInfo) - 8 * 60 * 60;

        而在函数void GetRtcTime(RtcTime_t *time)中,却是这样一行代码:

timeStamp = RTC_GetCounter() + 8 * 60 * 60; 

         在函数void SetRtcTime(RtcTime_t *time)中,假如RtcTime_t *time成员的值为2001-9-9 9:46:40,那么通过mktime(&timeInfo)获得的零时区时间戳就是B,因为这些函数都是基于零时区的。我们希望东八区即北京时间的日历时间是通过零时区添加偏移得到的,那么时间戳B减去8个小时的偏移,就得到2001-9-9 9:46:40的东八区时间戳1000000000。

         假设,我们已经在主函数中写了如下代码。

int main(void)
{
	DrvInit();
	AppInit();
	
	RtcTime_t time = {2001, 9, 9, 9, 46, 40};
	SetRtcTime(&time);

	while(1)
	{
		TaskHandler();
	}
}

        那么,在函数void GetRtcTime(RtcTime_t *time)中,RTC_GetCounter()得到的零时区时间戳就是1000000000。而localtime(&timeStamp);也是基于零时区进行转换的,如果timeStamp就是1000000000的话,转换的日历时间就是2001-9-9 1:46:40。但如果RTC_GetCounter()得到的时间戳加上8个小时的时区偏移量,那么得到的时间戳就是零时区时间戳B,timeInfo = localtime(&timeStamp);就得到零时区日历时间2001-9-9 9:46:40。

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

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

相关文章

AI社媒引流工具:解锁智能化营销的新未来

在数字化浪潮的推动下&#xff0c;社交媒体成为品牌营销的主战场。然而&#xff0c;面对海量的用户数据和日益复杂的运营需求&#xff0c;传统营销方法显得力不从心。AI社媒引流王应运而生&#xff0c;帮助企业在多平台中精准触达目标用户&#xff0c;提升营销效率和效果。 1.…

知识中台:提升企业知识管理的智能化水平

在数字化转型的浪潮中&#xff0c;企业知识管理的智能化水平成为提升竞争力的关键。HelpLook知识中台通过集成先进的AI技术&#xff0c;为企业提供了一个智能化的知识管理平台。 一、知识管理智能化的重要性 智能化的知识管理不仅能够提高信息检索的效率&#xff0c;还能通过…

Unreal5从入门到精通之EnhancedInput增强输入系统详解

前言 从Unreal5开始,老版的输入系统,正式替换为EnhancedInput增强型输入系统,他们之间有什么区别呢? 如果有使用过Unity的同学,大概也知道,Unity也在2020版本之后逐渐把输入系统也升级成了新版输入系统,为什么Unreal和Unity都热衷于升级输入系统呢?这之间又有什么联系…

C语言数据结构与算法--简单实现队列的入队和出队

&#xff08;一&#xff09;队列的基本概念 和栈相反&#xff0c;队列(Queue)是一种先进先出&#xff08;First In First Out&#xff09;的线性表。只 允许在表的一端进行插入&#xff0c;而在另一端删除元素&#xff0c;如日常生活中的排队现象。队列中 允许插入的一端叫队尾…

docker搭建私有仓库,实现镜像的推送和拉取

1.拉取docker仓库镜像 docker pull registry 2.启动registry容器 docker run -d registry 3.查看当前仓库中存在的镜像&#xff08;一&#xff09; curl -XGET http://192.168.111.162: 5000/v2/_catalog 192.168.111.162 部署docker仓库宿主机的ip 5000 部署docker仓库映射到宿…

算法学习笔记(九):网格图DFS、图论算法DFS、动态规划DP、贪心

一.网格图DFS 适用于需要计算连通块个数、大小的题目 1.岛屿数量 给你一个由 1(陆地) 和 0&#xff08;水&#xff09;组成的二维网格&#xff0c;请你计算网格中岛屿的数量 岛屿总是被水包围&#xff0c;并且每座岛屿只能由水平方向和\或竖直方向上相邻的陆地连接形成 此外&…

Cmakelist.txt之Linux-redis配置

1.cmakelist.txt cmake_minimum_required(VERSION 3.16) ​ project(redis_linux_test LANGUAGES C) ​ ​ ​ add_executable(redis_linux_test main.c) ​ # 设置hiredis库的头文件路径和库文件路径 set(Hiredis_INCLUDE_DIR /usr/local/include/hiredis) set(Hiredis_LIBRA…

【Node.js】Node.js 和浏览器之间的差异

Node.js 是一个强大的运行时环境&#xff0c;它在现代 JavaScript 开发中扮演着重要角色。然而&#xff0c;许多开发者在使用 Node.js 时常常会感到困惑&#xff0c;尤其是与浏览器环境的对比。本文将深入探讨 Node.js 和浏览器之间的差异&#xff0c;帮助你全面理解两者的设计…

【物联网原理与应用】实验二:红外传感实验

目录 一、实验目的 二、实验原理 三、实验内容及步骤 四、实验结果 五、核心代码 一、实验目的 学习试验模块上线路的连接操作理解掌握红外传感器的工作原理实现对红外传感器数据的接收和处理 二、实验原理 1、将红外辐射能转换成电能的光敏元件称为红外传感器&#…

PAL(Program-Aided Language Model)

PAL&#xff08;Program-Aided Language Model&#xff09;是一种结合生成式语言模型&#xff08;如 GPT&#xff09;和程序执行能力的技术框架。它的核心思想是通过让语言模型生成代码或程序来解决复杂任务&#xff0c;程序执行的结果反过来增强语言模型的输出准确性和逻辑性。…

java基础概念36:正则表达式1

一、正则表达式的作用 作用一&#xff1a;校验字符串是否满足规则&#xff1b;作用二&#xff1a;在一段文本中查找满足要求的内容。——爬虫 二、正则表达式 2-1、字符类 示例&#xff1a; public static void main(String[] args) {System.out.println("a".matc…

VsCode 插件推荐(个人常用)

VsCode 插件推荐&#xff08;个人常用&#xff09;

工业储能柜的大小该如何选择,工商储能系统设备哪家好?

在能源转型和可持续发展的大潮中&#xff0c;工商业储能系统因其提升清洁能源利用率、降低电能损耗、实现“双碳”目标等优势而备受青睐。它们不仅增强了电力系统的可靠性和灵活性&#xff0c;还帮助企业降低成本、提高经济效益。储能系统通过负荷管理适应电价波动&#xff0c;…

人工智能之数学基础:线性代数在人工智能中的地位

本文重点 从本文开始&#xff0c;我们将开启线性代数的学习&#xff0c;在线性代数中有向量、矩阵&#xff0c;以及各种性质&#xff0c;那么这些数学知识究竟和人工智能有什么关系呢&#xff1f; 重要性 机器学习和深度学习的本质就是训练模型&#xff0c;要想训练模型需要使…

数字IC后端实现时钟树综合系列教程 | Clock Tree,Clock Skew Group之间的区别和联系

Q: Clock&#xff0c;Clock Tree和Skew Group有何区别&#xff1f;Innovus CCOPT引擎是如何使用这些的&#xff1f; Clock是时序约束SDC中的时钟定义点。 create_clock -name clk_osc -period $period_24m [get_ports xin_osc0_func] 时钟树综合(Clock Tree Synthesis)之前应…

飞桨大模型PaddleOCR

一、新建项目PaddleOCRProject 二、查看开源 pip install paddlepaddle pip install paddleocr指定镜像源下载才快&#xff1a; pip install paddlepaddle -i https://pypi.tuna.tsinghua.edu.cn/simple pip install paddleocr -i https://pypi.tuna.tsinghua.edu.cn/simple 三…

31、js中日期操作

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>日期</title> </head> <body></body> <script>// js中日期操作 var datenew Date();document.write("日期时间&am…

【大数据学习 | Spark】Spark中的join原理

join是两个结果集之间的链接&#xff0c;需要进行数据的匹配。 演示一下join是否存在shuffle。 1. 如果两个rdd没有分区器&#xff0c;分区个数一致 &#xff0c;会发生shuffle。但分区数量不变。 scala> val arr Array(("zhangsan",300),("lisi",…

NLP论文速读(CVPR 2024)|使用DPO进行diffusion模型对齐

论文速读|Diffusion Model Alignment Using Direct Preference Optimization 论文信息&#xff1a; 简介&#xff1a; 本文探讨的背景是大型语言模型&#xff08;LLMs&#xff09;通过人类比较数据和从人类反馈中学习&#xff08;RLHF&#xff09;的方法进行微调&#xff0c;以…

小车AI视觉识别--9.目标检测

一、目标检测概述 本节主要解决的问题是如何使用OpenCV中的dnn模块&#xff0c;用来导入一个实现训练好的目标检测网络。但是对opencv的版本是有要求的。目前用深度学习进行目标检测&#xff0c;主要有三种方法&#xff1a; Faster R-CNNsYou Only Look Once(YOLO)Single Shot…