本帖最后由 L-MCU 于 2024-6-21 14:51 编辑
1、后备寄存器(BKP) 关于BKP的介绍,在CH32L103应用手册第4章 后备寄存器(BKP)有详细介绍,在此不再赘述。 BKP在MCU应用中主要用于以下几个方面: (1)VDD掉电保存。当VDD掉电后,由VBAT供电可存储20字节的用户数据; (2)侵入检测功能。当检测到侵入事件,会清除后备数据寄存器保存的用户数据; (3)RTC校准功能。可通过配合修改CAL[6:0]位来调整时钟对RTC 进行校准; (4)RTC脉冲输出。 可将侵入检测引脚作为普通IO使用,用于输出秒脉冲或者闹钟脉冲。 CH32L103 EVT提供了BKP例程,演示数据保存以及侵入检测清除数据,例程打印信息如下: 由于EVT例程演示了数据保存以及侵入检测的应用,下面主要介绍一下RTC校准功能以及RTC脉冲输出的应用。关于RTC校准功能以及RTC脉冲输出的应用,基于使用开启RTC的前提下。 RTC校准功能: 启用RTC校准功能需要配置RTC校准寄存器(BKP_OCTLR)的位7为1,配置TEMPER引脚(即PC13引脚)输出经64分频的RTC时钟。可直接调用BKP_RTCOutputConfig()函数进行配置。BKP_RTCOutputConfig()函数代码如下: - /*********************************************************************
- * @fn BKP_RTCOutputConfig
- *
- * [url=home.php?mod=space&uid=247401]@brief[/url] Select the RTC output source to output on the Tamper pin.
- *
- * @param BKP_RTCOutputSource - specifies the RTC output source.
- * BKP_RTCOutputSource_None - no RTC output on the Tamper pin.
- * BKP_RTCOutputSource_CalibClock - output the RTC clock with
- * frequency divided by 64 on the Tamper pin.
- * BKP_RTCOutputSource_Alarm - output the RTC Alarm pulse signal
- * on the Tamper pin.
- * BKP_RTCOutputSource_Second - output the RTC Second pulse
- * signal on the Tamper pin.
- *
- * [url=home.php?mod=space&uid=266161]@return[/url] none
- */
- void BKP_RTCOutputConfig(uint16_t BKP_RTCOutputSource)
- {
- uint16_t tmpreg = 0;
- tmpreg = BKP->OCTLR;
- tmpreg &= OCTLR_MASK;
- tmpreg |= BKP_RTCOutputSource;
- BKP->OCTLR = tmpreg;
- }
该函数主要是对RTC校准寄存器(BKP_OCTLR)进行配置,可配置TEMPER引脚输出经64分频的RTC时钟或秒脉冲或闹钟输出。此处配置TEMPER引脚输出经64分频的RTC时钟。 在RTC初始化完成后调用该函数就可以得到TEMPER引脚输出经64分频的RTC时钟,波形如下: 此处RTC的时钟源为LSE,大小为32.768KHz,经过64分频后,大小为512Hz,与截图逻辑分析仪采样得到的频率差不多。 关于校准,注意RTC只能对RTC运行快了进行减慢,无法对RTC运行慢了进行加快。根据手册RTC校准寄存器(BKP_OCTLR)位[6:0]校准值寄存器的介绍,RTC时钟可以被减慢0-121ppm。此处单位ppm表示频率误差,定义为百万分之一,ppm误差的计算公式为:ppm误差=偏差/基准值*10的6次方。具体解释可参考百度百科解释,链接如下: https://baike.baidu.com/item/ppm/14922800?fr=aladdin 关于校准值的计算:一般LSE的典型值为32.768KHz,但实际可能会偏小或偏大。比如我手上L103板子上LSE实测大小为32.765KHz,以该值为例,32765Hz经过64分频后输出大小应该为511.953Hz,但用逻辑分析仪测量的值为511.980Hz,那么根据ppm的误差计算公式,ppm误差大小为:(511.980-511.953)/511.953*1000000=52.739ppm 那么RTC的校准值应该是 52.739/1000000*2^20=55 根据该值,就可以调用BKP_SetRTCCalibrationValue函数对RTC进行校准,BKP_SetRTCCalibrationValue函数具体如下: - /*********************************************************************
- * @fn BKP_SetRTCCalibrationValue
- *
- * [url=home.php?mod=space&uid=247401]@brief[/url] Sets RTC Clock Calibration value.
- *
- * @param CalibrationValue - specifies the RTC Clock Calibration value.
- * This parameter must be a number between 0 and 0x1F.
- *
- * [url=home.php?mod=space&uid=266161]@return[/url] none
- */
- void BKP_SetRTCCalibrationValue(uint8_t CalibrationValue)
- {
- uint16_t tmpreg = 0;
- tmpreg = BKP->OCTLR;
- tmpreg &= OCTLR_CAL_MASK;
- tmpreg |= CalibrationValue;
- BKP->OCTLR = tmpreg;
- }
该函数主要是对RTC校准寄存器(BKP_OCTLR)的位[6:0]校准值寄存器进行配置。 注意该种校准方式也是存在一定误差的。 RTC秒脉冲/闹钟脉冲 脉冲输出通过配置RTC校准寄存器(BKP_OCTLR)的AOSE位,开启RTC脉冲输出。通过设置ASOS位,可以选择输出秒脉冲或者闹钟脉冲,如下图。 秒脉冲: 直接调用BKP_RTCOutputConfig(BKP_RTCOutputSource_Second);函数即可输出秒脉冲,逻辑分析仪采样如下图。由脉宽可知,大约1s左右输出一次脉冲。 闹钟脉冲: 直接调用BKP_RTCOutputConfig(BKP_RTCOutputSource_Alarm);函数即可输出闹钟脉冲,逻辑分析仪采样如下图。注意输出闹钟脉冲要设置一下闹钟。
2、循环冗余校验(CRC) 关于CRC的介绍,在CH32L103应用手册第5章 循环冗余校验(CRC)有详细介绍,在此不再赘述。 CH32L103提供硬件CRC计算单元,其使用CRC32多项式(0x4C11DB7),可节省CPU和RAM资源,提高效率。 CH32L103 EVT提供了CRC例程,代码如下: - /********************************** (C) COPYRIGHT *******************************
- * File Name : main.c
- * Author : WCH
- * Version : V1.0.0
- * Date : 2024/02/21
- * Description : Main program body.
- *********************************************************************************
- * Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
- * Attention: This software (modified or not) and binary are used for
- * microcontroller manufactured by Nanjing Qinheng Microelectronics.
- *******************************************************************************/
- /*
- *@Note
- CRC routine:
- Use CRC-32 polynomial 0x4C11DB7.
- */
- #include "debug.h"
- /* Global define */
- #define Buf_Size 32
- /* Global Variable */
- u32 SRC_BUF[Buf_Size] = {0x01020304, 0x05060708, 0x090A0B0C, 0x0D0E0F10,
- 0x11121314, 0x15161718, 0x191A1B1C, 0x1D1E1F20, 0x21222324, 0x25262728,
- 0x292A2B2C, 0x2D2E2F30, 0x31323334, 0x35363738, 0x393A3B3C, 0x3D3E3F40,
- 0x41424344, 0x45464748, 0x494A4B4C, 0x4D4E4F50, 0x51525354, 0x55565758,
- 0x595A5B5C, 0x5D5E5F60, 0x61626364, 0x65666768, 0x696A6B6C, 0x6D6E6F70,
- 0x71727374, 0x75767778, 0x797A7B7C, 0x7D7E7F80};
- u32 CRCValue = 0;
- /*********************************************************************
- * @fn RecalculateCRC
- *
- * [url=home.php?mod=space&uid=247401]@brief[/url] The function RecalculateCRC calculates the CRC value of a given buffer and sets the ID register to a
- * specific value.
- *
- * @param SRC_BUF SRC_BUF is the source buffer, which is a pointer to the start of the data that needs
- * to be used for CRC calculation.
- * suze The parameter "suze" is likely a typo and should be "size". It represents the size of
- * the source buffer in bytes.
- *
- * [url=home.php?mod=space&uid=266161]@return[/url] the calculated CRC value as a uint32_t.
- */
- uint32_t RecalculateCRC(uint32_t *SRC_BUF, uint32_t suze)
- {
- uint32_t temp;
- if (CRC_GetIDRegister() == 0xaa)
- CRC_ResetDR();
- temp = CRC_CalcBlockCRC((u32 *)SRC_BUF, suze);
- CRC_SetIDRegister(0xaa);
- return temp;
- }
- /**
- * @fn CRC_Is_Used
- *
- * [url=home.php?mod=space&uid=247401]@brief[/url] The function checks if the CRC module is being used by comparing the ID register value to 0xaa and
- * returns 1 if it is being used, otherwise it returns 0.
- *
- * [url=home.php?mod=space&uid=266161]@return[/url] a value of type uint8_t, which is an 8-bit unsigned integer. The function is checking if the
- * value returned by the CRC_GetIDRegister() function is equal to 0xaa. If it is, the function returns
- * 1. If it is not, the function returns 0.
- */
- uint8_t CRC_Is_Used()
- {
- if (CRC_GetIDRegister() == 0xaa)
- return 1;
- else
- return 0;
- }
- /*********************************************************************
- * @fn main
- *
- * @brief Main program.
- *
- * @return none
- */
- int main(void)
- {
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
- SystemCoreClockUpdate();
- Delay_Init();
- USART_Printf_Init(115200);
- printf("SystemClk:%d\r\n", SystemCoreClock);
- printf("ChipID:%08x\r\n", DBGMCU_GetCHIPID());
- printf("CRC TEST\r\n");
- RCC_HBPeriphClockCmd(RCC_HBPeriph_CRC, ENABLE);
- CRC_Is_Used() ? printf("CRC is not clear\r\n") : printf("CRC is clear\r\n");
- /* The code is calling the function `RecalculateCRC` with the arguments `SRC_BUF` and `Buf_Size`. */
- CRCValue = RecalculateCRC(SRC_BUF, Buf_Size);
- printf("CRCValue: 0x%08X\r\n", CRCValue); /* CRCValue should be 0x199AC3CA in this example */
- CRC_Is_Used() ? printf("CRC is not clear\r\n") : printf("CRC is clear\r\n");
- while (1)
- ;
- }
程序中,使用CRC首先注意要开启CRC时钟,CRC是挂载在HB总线上的,如下图,调用RCC_HBPeriphClockCmd(RCC_HBPeriph_CRC, ENABLE);开启CRC时钟。 程序中,CRC_Is_Used()函数主要用于判断CRC是否在使用中,该函数主要通过读取独立数据缓冲寄存器(CRC_IDATAR)的值判断CRC是否有被使用,若该寄存器值为0xAA,说明被使用。独立数据缓冲寄存器(CRC_IDATAR)是一个8位通用寄存器,具体介绍如下: 程序中,RecalculateCRC()函数主要用于CRC计算,计算给定数组的CRC值,并往独立数据缓冲寄存器(CRC_IDATAR)写值0xAA,因此可以调用CRC_Is_Used()函数判断CRC是否在使用中。CRC计算主要往数据寄存器(CRC_DATAR)写入给定数组的值,会自动得到CRC计算结果。数据寄存器(CRC_DATAR)具体介绍如下:
附件例程为RTC校准以及秒脉冲、闹钟脉冲输出例程,可下载简单参考一下。
|