MM32F013x

01、RTT Viewerd简介

SEGGER的实时传输(Real Time Transfer, RTT)是嵌入式应用中用户I/O交互的一种新技术。J-Link RTT Viewer是在调试主机上使用RTT功能的Windows GUI应用程序,它结合了SWO和半主机semihosting的优点,具有很高的性能。使用RTT,可以从目标微控制器输出信息,并以非常高的速度向应用程序发送输入,而不会影响目标的实时性。

在没有多余串口printf输出的情况下,而且需要输出少量的Debug状态信息时,可以使用SEGGER-RTT开发调试。

1.1、特性

  • 与目标应用程序进行双向通信

  • 非常高的传输速度,不影响实时行为

  • 使用调试通道进行通信

  • 目标上不需要额外的硬件或引脚

  • 支持任何J-Link

  • 支持Arm Cortex-M0/M0+/M1/M3/M4/M7/M23/M33

  • 提供功能和自由的完整实现代码

1.2、RTT Viewer主要功能

  • 通道0上的终端输出

  • 将文本输入发送到通道0

  • 最多16个虚拟终端,只有一个目标通道

  • 控制文本输出:彩色文本,擦除控制台

  • 在通道1上记录数据

RTT支持两个方向上的多个通道,向上到主机,向下到目标板,可以用于不同的目标,并为用户提供尽可能多的自由选择。默认实现每个方向使用一个通道,这意味着多个可打印的终端输入和输出。有了J-Link RTT查看器,这个通道可以用于多个“虚拟”终端,只需要一个目标缓冲区就可以打印到多个窗口(例如,一个用于标准输出,一个用于错误输出,一个用于调试输出)。例如,可以使用另一个up (to host)通道发送分析或事件跟踪数据。

02、资料准备

2.1、下载并安装

下载地址:

https://www.segger.com/downloads/jlink/#JLinkSoftwareAndDocumentationPack

全家桶即安装J-Flash相关的软件,相关的下载以及安装方式就不做过多的介绍。

2.2、获取RTT Viewer源码

在安装完成J-Link全家桶以后,在电脑安装路径下的C:\Program Files (x86)\SEGGER\JLink\Samples\RTT文件夹下面存放的就是RTT_Viewer的源代码。将此文件夹下面的SEGGER_RTT_Vxxxx.zip文件解压到我们的工程文件夹下面。

“”

“”

至此可以看到解压完成,打开RTT的文件夹可以看到三个文件夹,其中RTT文件夹下面存放的即为RTT Viewer的源码,将RTT Viewer整个文件夹拷贝到我们的工程路径下面。

“”

03、工程配置

3.1、RTT Viewer加入Keil工程

在模板工程中将文件夹下面的SEGGER_RTT.c,SEGGER_RTT_printf.c加入到我们的工程文件中,并包含头文件SEGGER_RTT.h,SEGGER_RTT_printf.h相关路径。

“”

在main.c文件中加入RTT Viewer相关的头文件。

#include "SEGGER_RTT.h"
#include "SEGGER_RTT_Conf.h"

“”

并在main函数中加入SEGGER_RTT_ConfigUpBuffer函数来初始化RTT Viewer。

SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_SKIP);

“”

至此RTT Viewer加入到工程并进行相关的初始化已经完成。

3.2、RTT Viewer log打印

调用LOG打印函数SEGGER_RTT_printf打印上电LOG:

SEGGER_RTT_printf(0, "SEGGER RTT Sample. Uptime: %.10dms.", /*OS_Time*/ 890912);
// Formatted output on channel 0: SEGGER RTT Sample. Uptime: 890912ms.

“”

连接上位机并在上位机端查看log打印是否成功。

“”

3.3、控制台发送指令到MCU

RTT_Viewer还可以作为控制台发送指令到MCU,具体调用的函数为SEGGER_RTT_WaitKey();下面来做一个测试,RTT_Viewer上位机发送一个数据到MCU。具体的测试代码如下,将下面的代码加入主函数的初始化后面即可。

 do
    {
        c = SEGGER_RTT_WaitKey();//获取RTT_Viewer上位机发送的数据
    }
    while (c != 'c');

“”

3.4、MCU控制上位机打印彩色log

MCU控制RTT_Viewer打印彩色log可以调用SEGGER_RTT_TerminalOut函数来实现,以下为具体的实现方式。

SEGGER_RTT_WriteString(0, "Hello World from your target.\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_BG_BLACK"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_TEXT_RED"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_TEXT_GREEN"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_TEXT_YELLOW"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_TEXT_BLUE"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1,RTT_CTRL_TEXT_MAGENTA"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_TEXT_CYAN"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_TEXT_WHITE"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_TEXT_BRIGHT_RED"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_TEXT_BRIGHT_GREEN"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_TEXT_BRIGHT_YELLOW"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_TEXT_BRIGHT_BLUE"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_TEXT_BRIGHT_MAGENTA"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_TEXT_BRIGHT_CYAN"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_TEXT_BRIGHT_WHITE"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_TEXT_BLUE"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_BG_BLACK"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_BG_RED"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_BG_GREEN"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_BG_YELLOW"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_BG_BLUE"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_BG_MAGENTA"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_BG_CYAN"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_BG_WHITE"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_BG_BRIGHT_BLACK"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_BG_BRIGHT_RED"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_BG_BRIGHT_GREEN"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_BG_BRIGHT_YELLOW"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_BG_BRIGHT_BLUE"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_BG_BRIGHT_MAGENTA"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_BG_BRIGHT_CYAN"Counter overflow!\r\n\r\n");
SEGGER_RTT_TerminalOut(1, RTT_CTRL_BG_BRIGHT_WHITE"Counter overflow!\r\n\r\n");

“”

3.5、RTT Viewer打印浮点数

在SEGGER_RTT.c文件中加入如下函数:

/*********************************************************************
*
*       rtt_printf()
*
*  Function description
*    print a formatted string using RTT and standard library formatting.
**********************************************************************/
int rtt_printf(const char *fmt,...) 
{
  int     n;
  char    aBuffer[256]; //根据应用需求调整大小
  va_list args;

  va_start (args, fmt);
  n = vsnprintf(aBuffer, sizeof(aBuffer), fmt, args);
  if (n > (int)sizeof(aBuffer)) {
    SEGGER_RTT_Write(0, aBuffer, sizeof(aBuffer));
  } else if (n > 0) {
    SEGGER_RTT_Write(0, aBuffer, n);
  }
  va_end(args);
  return n;
}

“”

在主函数的循环中加入如下测试代码:

Cnt++;
        SEGGER_RTT_printf(0, "%sCounter: %s%d\n",
                          RTT_CTRL_BG_CYAN,
                          RTT_CTRL_TEXT_BRIGHT_GREEN,
                          Cnt);
            fa += 0.0001f;
            fb -= 0.0002f;
            rtt_printf("floating test:\tfa = %f, fb = %f\r\n", fa, fb);//此函数用来打印浮点

“”

至此相关的代码已经完成,然后进行编译下载并查看相关的现象。

04、功能验证

4.1、MCU控制上位机打印彩色log

与RTT Viewer的上位机进行连接并可以看到打印的log数据,可以看到通道0与通道1有不同的数据打印。

“”

“”

4.2、控制台发送指令到MCU

输入小写字符c以后可以看到控制台输出LOG: Sent 1 byte.说明数据发送成功,然后可以看到counter的数据在递增,程序运行正确,浮点数的数据变化正常,至此验证代码无误。

“”

参考代码链接:

https://github.com/Samplecode-MM32/MM32MCU_Code

来源:灵动微电子
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 188

在上周的《基于Embedded Studio搭建MM32开发环境》的章节中,我们使用了Ozone这款工具进行调试,今天我们来向大家详细地介绍如何使用Ozone调试MM32 MCU,以及Ozone能给大家带来哪些便利性,体验这款工具的强大之处。

01、Ozone简介

Ozone 是 SEGGER公司开发的一个调试工具,用于J-Link和J-Trace的跨平台调试器和性能分析器,具有所有常见的视图(源代码、内存、控制台/终端、寄存器、反汇编、调用堆栈等等)。

主要特点

- 独立图形调试器
- 调试任何工具链和IDE的输出
- C / C ++源代码级调试和汇编指令调试
- 用于任何目的的调试信息窗口:反汇编,内存,全局和本地,(实时)监视,CPU和外围设备寄存器
- 源代码编辑器可立即修复错误
- 将应用程序高速编程到目标中
- 直接使用J-Link内置功能(无限的Flash断点,Flash下载,实时终端,指令跟踪)
- 可编写脚本的项目文件可自动设置所有内容
- 新项目向导可简化新项目的基本配置
Ozone已支持的编译器:Embedded Studio,GCC,Clang,MDK,IAR。

02、Ozone环境搭建

2.1、软件下载

在SEGGER的官网(https://www.segger.com/)下载最新的Ozone软件。

“”

“”

2.2、软件安装

双击Ozone软件安装包进行安装,完成安装如下图所示,同时会在桌面生成快捷方式。

“”

03、工程创建及调试

Ozone调试的方式有两种:

1、 在集成IDE环境中直接调用Ozone来进行调试。

2、 通过创建Ozone工程来进行调试。

第一种方式可以参照《基于Embedded Studio搭建MM32开发环境》,在这里就不过多的讲解说明,下面对通过创建Ozone工程来进行MCU调试进行讲解说明。

3.1、Ozone工程创建

打开Ozone软件,如下图所示:

“”

点击菜单栏File->New->New Project Wizard..来新建工程。

“”

然后在弹出的New Project Wizard对话框中,Device选项根据芯片的具体型号来选择实际的内核版本,例如此次使用的芯片为MM2F013x,为Cortex-M0内核,因此在Device选项中选择M0,Register Set对话框中的型号在Device对话框选择完成以后会自动选择,在Peripheral对话框中选择芯片的svd文件(目前为止Ozone安装包中包含的svd很不全面,如果我们使用的芯片的svd文件在Ozone的Periphaeral文件夹中没有包含,我们可以到MDK-Keil的安装路径下面找到PACK文件夹,并在此文件夹中找到相应芯片的svd文件,并拷贝到Ozone安装路径下面的Peripheral文件夹中即可,然后并选择芯片的svd文件)。

“”

然后点击Next选项进入到Connection Settings选项卡来选择调试接口以及通讯速率。MM32F013x支持SWD接口,因此我们在Target Interface按钮中选择SWD接口,并在Target Interface Speed选项中选择通讯速率为4MHz。

“”

点击Next选项,然后进入到Program File选项卡来选择需要Debug的.elf文件。

“”

后面的配置选择默认即可。

3.2、下载并进行调试

选择Debug选项卡的Download & Reset Program选项来下载程序到MCU并进入到调试界面,用户可以在View选项卡中选择各种窗口来帮助调试。

“”

3.2.1 Memory窗口

Ozone可以直接通过在寄存器或者变量窗口直接右键Show Data就可以看到变量甚至寄存器的Memory状态。

“”

“”

在Memory中用户可以选择保存指定地址区间的数据。

“”

“”

3.2.2 Watch窗口

用户可以在View选项卡中选择Watch Data选项中的New Watch Data Window来调出Watch窗口显示变量的数值。

“”

“”

当用户需要查看某一个变量的数值的时候,直接选中此变量然后右击弹出相关的选项卡,然后选择Watch选项来将变量添加到Watch Data窗口。

“”

3.2.3 Disassembly窗口

用户可以在View选项卡中选择Disassembly选项来显示汇编窗口,熟悉汇编的用户可以在此窗口中查看汇编指令,指令跟踪显示已执行的指令,并且与源代码视图同步。

“”

3.2.4 Registers窗口

用户可以在View选项卡中选择Registers选项来查看寄存的数据,其中CPU为内核相关的寄存器,Peripheral为外设相关的寄存器。

“”

如果用户在创建工程的时候没有选择svd文件,那么在进行调试的时候将无法查看外设寄存的数据。

3.2.5 Source File窗口

用户可以在View选项卡中选择Source File选项来查看相关的源文件,包含被编译的c文件、头文件,其中有程序大小,指令数,位置和状态(编译、包含、外部调用)。

“”

3.2.6 Data Sampling窗口

选择Data Sampling选项来定时查看某一个变量的数据变化情况。如下图所示,Index为打印的序号,Times为打印的时间戳,sTimingDelay为变量名称。

“”

使用Timeline来查看变量的变化曲线:

“”

3.2.7 断点

用户可以在IDE中间源文件的左侧添加断点,并同步到Break & Tracepoints窗口,在Break & Tracepoints窗口展示了断点的数量,断点所在的文件以及所在的行等基本信息。

“”

视图可以移动和放在彼此以具有“标签”视图,可以将视图移出主窗口,例如放置在单独的监视器显示器上。

3.2.8 调用窗口

可以直观的看到编译后工程之间的函数调用关系,和调用深度等相关内容。这是一个静态的程序图框,用于描述函数、子函数之间的相互引用关系以及所占用的堆栈量、代码总量、调用深度等有点类似于keil中的htm(Obj过程文件中)文件的描述。

“”

3.2.9 脚本

Ozone有一个很好的脚本引擎,几乎一切都是可脚本的:

“”

实际上Ozone项目文件是用脚本语言编写的C文件,这样我可以很容易地更改调试环境,在脚本中我们只需要按照Ozone的Console的命令格式来编写脚本命令即可,比如我们需要打开Global Data窗口,在Console窗口输入命令Window.Show ("Global Data");即可调出Global Data窗口,如果关闭此窗口输入Window.Close ("Global Data");即可。

“”

来源:灵动微电子
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 390

mbedded Studio是用于嵌入式系统的多合一集成开发环境(IDE),用于管理,构建,测试和部署嵌入式应用程序。Embedded Studio还提供了功能强大的项目管理器和源代码编辑器,以及随附的C / C ++编译器和具有高级调试信息窗口的集成调试器,还提供用于自动部署应用程序的直接J-Link集成和版本控制功能,项目生成器支持常见的微控制器。

主要特点

  • 免费用于非商业用途,例如教育和评估目的,没有任何限制
  • 跨平台:可在Windows,macOS和Linux上运行
  • 多线程构建可最大程度地减少构建时间
  • 高度优化的运行时库,可实现最佳性能和最小代码量
  • 具有无缝J-Link集成的功能丰富的调试器
  • 强大的项目管理,能够处理非常大的项目
  • 基于软件包的项目生成器,适用于所有常见的微控制器
  • IAR,MDK(AC5),MDK(AC6)和Eclipse创建的工程都可以转换成到 Embedded Studio平台使用
  • 适用于嵌入式C / C ++编程的专业IDE解决方案,包括Clang / LLVM&GCC&SEGGER C / C ++工具链

MM32系列MCU也是早期就得到SEGGER官方支持的MCU厂商之一,因此MM32系列MCU也可以完美在Embedded Studio平台进行开发、调试,今天我们将介绍在Embedded Studio平台开发、调试MM32 MCU。

环境搭建

01、 软件下载

登录SEGGER的官网下载最新的Embedded Studio软件(https://www.segger.com/)。

根据电脑的不同版本选择不同版本的软件进行下载,由于本次教程使用的电脑是win10、64位的,因此选择64位win10版本的软件进行下载。

02、软件安装

软件安装可以一直Next,基本上选择默认的配置即可,操作比较简单。

“”

完成以后,双击打开Embedded Studio IDE,会弹出一个预警对话框,我们直接点击CONTINUE按钮跳过即可,然后会进入到系统默认的工程页面,则说明我们的环境配置成功。

“”

03、安装PACK

打开Embedded Studio上位机软件,打开选项卡Tools->Package Manager进入pack管理选项卡。

“”

在SearchPackages搜索框中输入MM32查找pack包进行安装,pack根据自己的工程需求来进行安装即可(MM32最新系列的MCU的pack正在得到SEGGER支持过程中,用户也可以选择相同的型号pack)。

点击我们选中的pack包我们就会看到IDE弹出Next按钮。

“”

点击Next按钮进入下载安装选项卡。

04、查看安装完成的pack包

点击Display Installed选项卡就会弹出已经安装完成的pack包,并可以查看已经安装完成pack包的相关信息。

“”

IAR,MDK(AC5),MDK(AC6)和Eclipse创建的工程都可以转换成到Embedded Studio平台使用,也可以基于Embedded Studio平台创建新的工程,本章将实验两种方式创建MM32F013X工程环境流程。

Embedded Studio创建MM32工程

具体的操作如下:

01、新建工程

选择File->New Project选项卡。

“”

选择MM32的芯片型号。

“”

并配置工程名,将默认的工程名修改为MM32。

“”

选择相关的工程配置,点击Target Processor来选择芯片的具体型号。

“”

02、加载文件

“”

移植MM32F013x的库到我们的工程,首先我们从MM32官网下载最新的MM32F013x的SDK包到我们的电脑并解压缩。

“”

并将Device下面的HAL_lib复制到我们的工程下面,并在工程中新建一个文件夹并将HAL_lib中的文件添加到工程。具体的操作如下:

“”

“”

并将MM32F013x工程中的IOtoggle的main.c替换工程中的main.c文件。

“”

添加led的驱动文件到工程中:在工程中新建BSP文件夹并将IOtoggle文件夹下面的HARDWARE文件夹下面的LED.c复制到BSP文件夹下面,并添加到工程中。

“”

工程中添加SYSTEM文件夹并添加文件,具体的操作就是将IOtoggle文件夹下面的SYSTEM文件夹复制到我们的工程中,并添加到工程项目中,具体的操作如下:

“”

在SYSTEM文件夹下面新建一个inc文件夹,并将从官方库SYSTEM移植过来的.h文件放在此文件夹下面。

“”

“”

添加.C文件到工程中。

“”

添加MM32F013x的库的头文件,具体的操作如下:

将Device文件夹下面CMSIS文件夹中的文件复制到工程文件夹下面的CMSIS_5->CMSIS文件夹下面的Include文件夹下面。

“”

03、添加路径

右击Project->Options。

“”

在Code目录下的Preprocessor中点击User Include Directories选中添加路径即可。

“”

04、 编译

“”

我们会发现很多的错误,在delay.c \ uart.c中都需要添加#include "HAL_conf.h"头文件,并在uart中屏蔽掉FILE __stdout这行代码。

出现Build complete则说明我们的文件编译成功了,接下来进行验证,我们将代码下载进我们的板子测试OK,说明我们工程搭建成功。

在debug的时候我们既可以选择软件自带的调试方式也可以选择Ozone进行调试。

将KEIL工程导入Embedded Studio编译器

01、导入MDK工程

将KEIL工程导入到SEGGER Embedded Studio编译器去编译文件具体的操作如下:

选择File->Import Project选项卡来添加MDK工程,并选择导入mdk工程类型,目前支持的类型有MDK、MDK-ARM6、IAR、GCC。

“”

选择MDK工程文件。

“”

选择内核型号:

“”

选择编译配置,则选择外部编译工具。

“”

“”

到此我们就可以看到我们文件导入成功了。

02、编译

“”

出现Build complete则说明我们的文件编译成功了。

03、下载调试

选择Debug->Debug with Ozone选项卡来进入Debug模式。

“”

下载并开始调试:

“”

今天主要讲解Embedded Studio的环境搭建及新建MM32F013x工程文件,在后续的教程中将继续讲解基于MM32F013x使用SEGGER相关工具的方法。

来源:灵动微电子
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 202

前面一章节介绍了在Windows下搭建Eclipse开发环境,本章节将介绍在Windows环境下基于Eclipse开发、调试MM32。

01、GCC创建工程

打开File->New->Project,选择C Project,然后点击NEXT,在Project name选项框中设置GCC工程的名字,在Project type选项框中选择Empty Project,在toolchains中选择Cross ARM GCC。

“图1"
图1

“图2"
图2

“图3"
图3

“图4"
图4

“图5"
图5

在Cross GNU ARM Toolchain界面用户需要选择GNU Tools for ARM Embedded Processors (arm-none-eabi-gcc)作为A工程的编译工具,并选择编译工具的路径。后面点击Finish按钮,至此工程模板的创建已经完成。

02、添加MM32相关库函数

在上面的模板中添加与MM32F013x相关的文件:Source文件夹中存放的为F013x的相关代码,在Source文件夹中Device文件夹中存放的是MM32F013x的Library以及启动文件。BSP文件夹中存放的为外设相关的驱动文件, SYS文件夹下面存放的为芯片UART,Delay的配置文件。APP中存放main.c文件。

“图6"
图6

“”

“
图7

在添加所要操作的文件以后,为了防止文件没有加载出来,需要刷新一下(直接按快捷键F5刷新工程)。

“图8"
图8

03、工程配置

在GCC中添加以上文件的头文件所在的路径,点击在工程浏览器中选中该工程,然后点击project->properties 选择项。

“图9"
图9

“图10"
图10

设置需要打开的宏定义:

“图11"
图11

给工程添加flash.ld文件。

“图12"
图12

如果在我们的工程中有lib文件,比如MM32的W系列使用eclipse编程的时候据需要向工程中添加lib库文件,在进行lib文件添加的时候要注意lib文件的格式,例如:蓝牙的lib文件为libmg_BLE-gcc.a,lib为lib文件前缀.a为文件的类型,添加名字的时候直接添加为mg_BLE-gcc即可。

“图13"
图13

04、编译工程

选择CDT编译。

“图14"
图14

然后选择Build Project进行工程的编译,看到有hex文件生成并且无错误、无警告,则说明我们的工程编译成功。

“图15"
图15

05、DEBUG配置

编译完成后, 我们就要进行下载和调试了,首先进行调试的配置。

“图16"
图16

在GDB SEGGER J-LINK Debugging上面双击创建debug工程,新建了一个选项卡,设置 debug 的名称,调试的工程和源文件。

“图17"
图17

选择相关的工程以及对应的elf文件。

“图18"
图18

由于debug依靠的GDB框架,所以选择JLinkGDBServerCL.exe来进行调试。

“图19"
图19

“图20"
图20

到此我们的GCC的debug已经配置完成了,打开Debug->MM32TEST DEBUG进入debug界面。点击 Debug 开始调试,我们就可以看到我们的软件进入了调试模式,并且停留在了main 函数的第一个有效行上面,点击RUN。

“图21"
图21

调试界面基本都是一样的,watch窗口也能调出来设置断点、全速运行等功能。

如果想结束调试,只需要点击上方的红色方块即可,此时 Jlink 的 GDB 会自动关闭,然后点击右侧的 C/C++选项卡即可回到工程的编辑视图了,到此debug配置验证完成。

本次实验参考代码:

https://github.com/Samplecode-MM32/MM32MCU_Code

来源:灵动微电子

围观 105

如何在Windows环境下基于Eclipse开发、调试MM32 MCU,经过尝试,现将环境搭建分享给大家。

使用 Eclipse 与 GCC 优点:两者均为开源软件,可以自由使用,并支持 Windows,Linux等多个平台,同时还可以通过各种插件拓展其功能,例如 EGit。

环境需要工具

搭建过程中用到了以下工具:

  • Eclipse for C/C++ IDE(需要JAVA开发环境)

  • CDT-8.3.0

  • GNUARM for Eclipse plugin

环境简介

Eclipse

一款开源的集成开发环境(Integrated Development Environment),配合众多插件,可以用于Java应用程序开发、Android应用程序开发等。最精简的Eclipse只是一个框架,开发不同应用程序时需要安装对应的插件才能进行,不像微软的VS已经集成了众多的编译工具。

CDT

开发ARM内核的应用程序主要使用的是C/C++,所以Eclipse需要安装CDT(C/C++ Development Tooling)插件。

GNUARM Eclipse

包含一套Eclipse插件和用于跨平台嵌入式ARM程序开发工具的开源项目。有过stm32库函数开发经历的朋友可能知道,开发前工程师一般都会找一套工程模板,包含了对各个库文件的引用,我们只需要关注核心的应用即可,换一个项目时我们就拷贝一份模板,重新进行开发。Eclipse装了GNU ARM Eclipse工具簇后,新建项目时我们只需要选择对应模板的项目,插件就会自动帮我们配置好工程。

Windowsbuild tools

程序由代码变为可执行文件需要进过编译和链接的过程。Windows下的IDE无论是VisualStudio还是KEIL,编译工具都集成到IDE中了,且有一套自己的管理项目文件的方式。

Eclipse创建的工程会自带makefile文件,该文件的解析需要make工具。Linux下自带make工具,Windows下需要使用Windows build tools作为make工具使用。

GNUARM Embedded Toolchain

ARM交叉编译链,被编译的程序运行于基于ARM架构的处理器上。

由于Eclipse是基于JAVA,在安装之前首先要确定机器是否有JAVA环境,需要在甲骨文的官网http://www.oracle.com/index.html下载。

环境搭建

01、Eclipse Download

安装完JAVA环境后就可以进行安装Eclipse,下载最新的Eclipse in C/C++版本。

URL:https://www.eclipse.org/downloads/packages/

“图1
图1 Eclipse下载地址

我们下载的Eclipse为免安装版本的,下载完成以后直接解压运行即可。

02、CDT Download

URL:https://www.eclipse.org/cdt/downloads.php

在此网址下面下载适合开发者的CDT版本,Eclipse的不同版本会有不同版本的CDT来做适配,由于上面我们安装的为Eclipse的2020-12版本,所以本次教程选择与2020-12 Eclipse版本适配的CDT 10.1.0版本来进行安装。

“图2
图2 CDT下载地址

03、安装CDT

首先我们打开已经下载完成的CDT安装包来安装CDT,具体的安装步骤如下:

“图3
图3 安装CDT

打开Eclipse->Help->Install New software,点击Add按钮来添加插件,在弹出来的对话框中输入插件的名字以及下载的位置或者网址,点击Add按钮,并勾选我们所要添加的插件的类型,按Next进行安装,最后等待即可。

“图4
图4 安装插件

“图5
图5 安装插件

04、安装Eclipse插件 – 编译工具

在Eclipse的Help->install new software里面添加如下信息:

Name: GNU ARM Eclipse Plug-ins
URL: http://gnuarmeclipse.sourceforge.net/updates

“图6
图6 安装编译工具

“图7
图7 安装插件

05、下载并安装GNU Arm Embedded Toolchain

安装方式与CDT插件安装方式相同,目前已经支持基于 GDB SEGGER JLINK 以及 GDB OpenOCD的调试。

URL: https://developer.arm.com/tools-and-software/open-source-software/develo...

“图8
图8 下载并安装GNU

在上面的链接中用户可以下载exe文件,也可以下载zip文件,下载exe文件的时候需要进行安装,安装生成的文件与zip压缩文件解压缩的文件是一样的,使用哪一种方式用户可以根据自己的喜好来选择。

查看安装结果

通过上面的安装步骤我们已经完成安装,下面通过查看我们是否成功安装插件。

“图9
图9 Eclipse Marketplace

“图10
图10 查看CDT安装结果

至此关于Eclipse编译的插件都已经安装完毕,在下一章节我们将讲解如何使用Eclipse新建MM32工程文件。

来源:灵动微电子

围观 177

在上篇文章中我们介绍了EasyFlash组件三大功能中的ENV功能及使用,本篇通过移植开源的EasyLogger组件结合EasyFlash,使用MM32F013x内置空闲的FLASH存储空间来实现LOG日志的存储记录,使用芯片自带的RTC功能使日志在存储的时候带有日期和时间信息。

EasyLogger介绍

EasyLogger是一款超轻量级、高性能的C/C++日志库,非常适合对资源敏感的软件项目。相比于log4c、zlog这些知名的C/C++日志库,EasyLogger的功能更加简单,提供给用户的接口更少,但上手会很快,更多实用功能支持以插件(Flash、File等)形式进行动态扩展。

EasyLogger主要特性

  • 支持用户自定义输出方式(例如:终端、文件、数据库、串口、RS-485、Flash等等)

  • 日志内容可包含级别、时间戳、线程信息、进程信息等

  • 日志输出被设计为线程安全的方式,并支持异步输出和缓冲输出模式

  • 支持多种操作系统(例如:RT-Thread、uCOS、Linux、Windows等等),也支持裸机平台

  • 日志支持RAW格式(未经过格式化的原始日志)、支持HEXDUMP

  • 支持按标签、级别、关键词进行动态过滤

  • 各级别日志支持不同颜色显示,用户也可以根据自己的喜好,在 elog_cfg.h 对各个级别日志的颜色及字体风格进行单独设置

  • 扩展性强,支持以插件的形式扩展新功能

EasyLogger资源占用

最低要求:ROM < 1.6KB,RAM < 0.3KB。

EasyLogger移植说明

下载最新的EasyFlash源代码:
https://github.com/armink/EasyLogger

01、添加EasyLogger源文件

将\easylogger\目录下的inc、src、port及plugins文件夹拷贝到项目中:

“”

02、添加工程文件

添加\ easylogger \src\、\ easylogger \port\、\ easylogger \plugins\flash文件夹下的源文件到项目工程目录中:

“”

03、添加路径

根据项目需求,选择性添加\ easylogger \src\中的其他源码文件,\easylogger\inc\和\ easylogger \plugins\flash文件夹到编译的头文件目录列表中;

“”

EasyLogger接口移植

01、初始化和配置

easyflash_init初始化EasyLogger移植所需的资源等等。在easyflash_init成功后,我们再来进行easylogger的初始化和配置操作。

/*******************************************************************************
 * @brief       
 * @param       
 * @retval      
 * @attention   
*******************************************************************************/
int main(void)
{
    InitSystem();

    if(easyflash_init() == EF_NO_ERR)
    {
        EasyFlash_ENV_Demo();

        if(elog_init() == EF_NO_ERR)
        {
            elog_set_fmt(ELOG_LVL_ASSERT,  ELOG_FMT_ALL & ~ELOG_FMT_P_INFO);
            elog_set_fmt(ELOG_LVL_ERROR,   ELOG_FMT_LVL |  (ELOG_FMT_TAG  | ELOG_FMT_TIME));
            elog_set_fmt(ELOG_LVL_WARN,    ELOG_FMT_LVL |  (ELOG_FMT_TAG  | ELOG_FMT_TIME));
            elog_set_fmt(ELOG_LVL_INFO,    ELOG_FMT_LVL |  (ELOG_FMT_TAG  | ELOG_FMT_TIME));
            elog_set_fmt(ELOG_LVL_DEBUG,   ELOG_FMT_ALL & ~(ELOG_FMT_FUNC | ELOG_FMT_P_INFO));
            elog_set_fmt(ELOG_LVL_VERBOSE, ELOG_FMT_ALL & ~(ELOG_FMT_FUNC | ELOG_FMT_P_INFO));

            /* set EasyLogger assert hook */
            elog_assert_set_hook(elog_user_assert_hook);

            /* initialize EasyLogger Flash plugin */
            elog_flash_init();

            /* start EasyLogger */
            elog_start();
        }
    }

    while(1)
    {
        TASK_Scheduling();
    }
}

02、elog接口输出

日志最终输出的末端接口,可以在里面增加输出到终端、输出到文件、输出到Flash等方法。

/**
 * output log port interface
 *
 * @param log output of log
 * @param size log size
 */
void elog_port_output(const char *log, size_t size)
{
    /* add your code here */
    printf("%.*s", size, log);
    elog_flash_write(log, size);
} 

03、elog获取时间信息

返回当前时间,将会显示在日志中。

/**
 * get current time interface
 *
 * @return current time
 */
const char *elog_port_get_time(void)
{
    /* add your code here */
    memset(elog_time, 0, sizeof(elog_time));
    sprintf(elog_time, "%d-%02d-%02d %02d:%02d:%02d", 
        RTC_Calendar.year, RTC_Calendar.month,  RTC_Calendar.day,
        RTC_Calendar.hour, RTC_Calendar.minute, RTC_Calendar.second);
    return elog_time;
}

参数设置

配置时需要修改项目中的elog_cfg.h文件,开启、关闭、修改对应的宏即可。

可以配置内容参数有输出开关、输出级别、断言开关、每行日志缓冲大小、行号最大长度、过滤标签最大长度、过滤关键字最大长度、标签+级别过滤器的最大数目、换行符、颜色和异步输出模式等,具体的配置参数方式可以参考链接。

开启缓冲输出模式后,如果缓冲区不满,用户线程在进行日志输出时,无需等待日志彻底输出完成,即可直接返回。但当日志缓冲区满以后,将会占用用户线程,自动将缓冲区中的日志全部输出干净。同时用户也可以在非日志输出线程,通过定时等机制使用 void elog_flush(void) 将缓冲区中的日志输出干净。

操作方法:
开启、关闭ELOG_BUFF_OUTPUT_ENABLE宏即可

默认大小:
(ELOG_LINE_BUF_SIZE * 10) ,不定义此宏,将会自动按照默认值设置

操作方法:
修改ELOG_BUF_OUTPUT_BUF_SIZE宏对应值即可

/* EasyLogger flash log plugin's RAM buffer size */
#define ELOG_FLASH_BUF_SIZE        1024   /* @note you must define it for a value */

测试验证

我们使用了芯片内部的RTC功能,在日志存储的时候记录了当前系统的日期和时间;结合EasyFlash我们将EasyLogger的日志记录存储到片内FLASH空间,这样在芯片重启后仍能查询到之前日志信息。同时我们将需要测试的函数注册到Shell命令中,通过调用Shell命令可以便捷的进行调试过程。

01、编写elog测试记录

/*******************************************************************************
 * @brief       
 * @param       
 * @retval      
 * @attention   
*******************************************************************************/
void EasyLogger_SHELL_elog_test(void)
{
    log_a("Hello EasyLogger!"); /* 断言Assert  */
    log_e("Hello EasyLogger!"); /* 错误Error   */
    log_w("Hello EasyLogger!"); /* 警告Warn    */
    log_i("Hello EasyLogger!"); /* 信息Info    */
    log_d("Hello EasyLogger!"); /* 调试Debug   */
    log_v("Hello EasyLogger!"); /* 详细Verbose */
}
SHELL_EXPORT_CMD(elog_test, EasyLogger_SHELL_elog_test, EasyLogger test);

02、编写elog操作Flash插件的函数

/*******************************************************************************
 * @brief       
 * @param       
 * @retval      
 * @attention   
*******************************************************************************/
void EasyLogger_SHELL_elog_flash(char *argv)
{
    if(!strcmp(argv, "read"))
    {
        printf("\r\nelog_flash read \r\n"); elog_flash_output_all();
    }
    else if(!strcmp(argv, "clean"))
    {
        printf("\r\nelog_flash clean\r\n"); elog_flash_clean();
    }
    else if(!strcmp(argv, "flush"))
    {
        printf("\r\nelog_flash flush\r\n"); elog_flash_flush();
    }
    else
    {
        printf("\r\nelog_flash error\r\n");
    }
}
SHELL_EXPORT_CMD(elog_flash, EasyLogger_SHELL_elog_flash, EasyLogger read/clean/flush flash log);

运行测试

01、下载程序后

待EasyFlash成功初始化完成后,再进行EasyLogger的初始化和配置,并记录Info类型日志信息,如下图所示:

“”

02、elog功能测试

在命令行窗口中,我们输入TAB按键可以查看当前程序支持的SHELL命令;我们通过elog_test命令来进行log信息的更新和记录,通过elog_flash flush命令来将当前的log日志信息存储到用户自定义规划的MM32F013x芯片片内Flash空间,使用elog_flash clean命令来清除存储在Flash内的所有log日志信息,使用elog_flash read命令可以将存储在Flash内的所有log日志信息读取出来;测试过程如下图所示:

“”

本次实验参考代码:
https://github.com/Samplecode-MM32/MM32MCU_Code

来源:灵动微电子

围观 343

一. MM32F013x片内FLASH

MM32F013x芯片内嵌高达64KB的程序FLASH存储空间,由64页组成,每页大小为1KB;用户的可执行程序从FLASH的起始地址0x08000000开始存放,支持读、写操作,页擦除,整片擦除,可通过 16 位(半字)方式编程写入闪存,其擦写寿命可达 20000 次。闪存控制器在读取数据时,支持带预取缓冲器的数据接口,以支持 MCU 运行在更高的主频。FLASH的每页都可以独立的设置写保护功能,以防止芯片内部可执行程序被复制,增强产品的安全性。

如果用户的可执行程序没有占满FLASH的存储空间,那我们就可以利用剩余的FLASH空间来当作存储器使用,可以存储一些用户配置、运行日志等记录,同时对于存储数据量不大的情况,可以省去外置的存储芯片,节省BOM成本。

本篇通过移植开源的EasyFlash组件,使用MM32F013x内置的空闲的FLASH存储空间来实现用户数据存储记录的功能。

二. EasyFlash介绍

EasyFlash是一款开源的轻量级嵌入式FLASH存储器库,方便开发者更加轻松的实现基于FLASH存储器的常见应用开发,非常适合智能家居、可穿戴、工控、医疗、物联网等需要断电存储功能的产品,资源占用极低,支持各种MCU片上存储器。该库主要包括三大实用功能:

ENV

快速保存产品参数,支持写平衡(磨损平衡)及掉电保护功能

EasyFlash不仅能够实现对产品的设定参数或运行日志等信息的掉电保存功能,还封装了简洁的增加、删除、修改及查询方法,降低了开发者对产品参数的处理难度,也保证了产品在后期升级时拥有更好的扩展性。让Flash变为NoSQL(非关系型数据库)模型的小型键值(Key-Value)存储数据库。

IAP

在线升级再也不是难事儿

该程序库封装了IAP(In-Application Programming)功能常用的接口,支持CRC32校验,同时支持Bootloader及Application的升级。

Log

无需文件系统,日志可直接存储在FLASH上

非常适合应用在小型的不带文件系统的产品中,方便开发人员快速定位、查找系统发生崩溃或死机的原因。同时配合EasyLogger一起使用,轻松实现日志的FLASH存储功能。

本篇章我们需要通过MM32F013x来实现ENV环境变量的存取功能,也可以叫做KV数据库模式;目前ENV功能有两种主要模式,一种为V4.0版本带来的NG(Next Generation)模式,还有一种为延续V3.0版本的Legacy模式。

对于NG模式相比较于Legacy模式具有以下新特性:

  • 更小的资源占用,内存占用几乎为0;V4.0以前版本会使用额外的RAM空间进行缓存;
  • ENV的值类型支持任意类型、任意长度,相当于直接memcpy变量至Flash;V4.0 之前只支持存储字符串;
  • ENV操作效率比以前的模式高,充分利用剩余空闲区域,擦除次数及操作时间显著降低;
  • 原生支持磨损平衡、掉电保护功能;V4.0之前需要占用额外的Flash扇区;
  • ENV支持增量升级,固件升级后ENV也支持升级;

三. EasyFlash ENV模式对比


四. EasyFlash资源占用

最低要求:ROM:6KB,RAM:0.1KB。

五. EasyFlash移植说明

下载最新的EasyFlash源代码:
https://github.com/armink/EasyFlash

01、目录结构

先解压下载好的源码包,文件的目录结构大致如下:


02、拷贝port文件

将\easyflash\目录下的inc、src及port文件夹拷贝到项目中:


03、添加工程文件

添加\easyflash\src\及\easyflash\port\文件夹下的源文件到项目工程目录中:


04、添加路径

根据项目需求,选择性添加\easyflash\src\中的其他源码文件,\easyflash\inc\文件夹到编译的头文件目录列表中:


六. EasyFlash接口移植

01、移植初始化

EasyFlash移植初始化。可以传递默认环境变量,初始化EasyFlash移植所需的资源等等。

/**
 * Flash port for hardware initialize.
 *
 * @param default_env default ENV set for user
 * @param default_env_size default ENV size
 *
 * @return result
 */
EfErrCode ef_port_init(ef_env const **default_env, size_t *default_env_size)
{
    EfErrCode result = EF_NO_ERR;

    *default_env = default_env_set;
    *default_env_size = sizeof(default_env_set) / sizeof(default_env_set[0]);
    return result;
}

02、读取FLASH

/**
 * Read data from flash.
 * @note This operation's units is word.
 *
 * @param addr flash address
 * @param buf buffer to store read data
 * @param size read bytes size
 *
 * @return result
 */
EfErrCode ef_port_read(uint32_t addr, uint32_t *buf, size_t size)
{
    EfErrCode result = EF_NO_ERR;
    /* You can add your code under here. */
    uint8_t *Data = (uint8_t *)buf;
    for(size_t i = 0; i < size; i++, addr++, Data++)
    {
        *Data = *(uint8_t *)addr;
    }
    return result;
}

03、擦除FLASH

/**
 * Erase data on flash.
 * @note This operation is irreversible.
 * @note This operation's units is different which on many chips.
 *
 * @param addr flash address
 * @param size erase bytes size
 *
 * @return result
 */
EfErrCode ef_port_erase(uint32_t addr, size_t size)
{
    EfErrCode result = EF_NO_ERR;

    /* make sure the start address is a multiple of EF_ERASE_MIN_SIZE */
    EF_ASSERT(addr % EF_ERASE_MIN_SIZE == 0);
    /* You can add your code under here. */
    FLASH_Status Status;
    size_t Number;
    Number = size / 1024;
    if((size % 1024) != 0) Number++;
    FLASH_Unlock();
    FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
    for(size_t i = 0; i < Number; i++)
    {
        Status = FLASH_ErasePage(addr + 1024 * i);
        FLASH_ClearFlag(FLASH_FLAG_EOP);
        if(Status != FLASH_COMPLETE)
        {
            printf("\r\nErase Error!!!");
            result = EF_ERASE_ERR; break;
        }
    }
    FLASH_Lock();
    return result;
}

04、写入FLASH

/**
 * Write data to flash.
 * @note This operation's units is word.
 * @note This operation must after erase. @see flash_erase.
 *
 * @param addr flash address
 * @param buf the write data buffer
 * @param size write bytes size
 *
 * @return result
 */
EfErrCode ef_port_write(uint32_t addr, const uint32_t *buf, size_t size)
{
    EfErrCode result = EF_NO_ERR;
    EF_ASSERT(size % 4 == 0);
    /* You can add your code under here. */
    FLASH_Unlock();
    FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
    for(size_t i = 0; i < size; i+=4, buf++, addr+=4)
    {
        FLASH_ProgramWord(addr,   *buf);
        FLASH_ClearFlag(FLASH_FLAG_EOP);
        uint32_t Data = *(uint32_t *)addr;
        if(Data != *buf)
        {
            printf("\r\nWrite Error!!!");
            result = EF_WRITE_ERR; break;
        }
    }
    FLASH_Lock();
    return result;
}

05、对环境变量缓冲区加锁

/**
 * lock the ENV ram cache
 */
void ef_port_env_lock(void)
{
    /* You can add your code under here. */
    __disable_irq();
}

06、对环境变量缓冲区解锁

/**
 * unlock the ENV ram cache
 */
void ef_port_env_unlock(void)
{
    /* You can add your code under here. */
    __enable_irq();
}

07、打印调试日志信息

static char log_buf[128];

/**
 * This function is print flash debug info.
 *
 * @param file the file which has call this function
 * @param line the line number which has call this function
 * @param format output format
 * @param ... args
 *
 */
void ef_log_debug(const char *file, const long line, const char *format, ...)
{
#ifdef PRINT_DEBUG
    va_list args;
    /* args point to the first variable parameter */
    va_start(args, format);
    /* You can add your code under here. */
    ef_print("\r\n[Debug](%s:%ld) ", file, line);
    vsprintf(log_buf, format, args);
    ef_print("%s", log_buf);
    printf("\r\n");
    va_end(args);
#endif
}

08、打印普通日志信息

/**
 * This function is print flash routine info.
 *
 * @param format output format
 * @param ... args
 */
void ef_log_info(const char *format, ...)
{
    va_list args;
    /* args point to the first variable parameter */
    va_start(args, format);
    /* You can add your code under here. */
    ef_print("\r\n[LogInfo]");
    /* must use vprintf to print */
    vsprintf(log_buf, format, args);
    ef_print("%s", log_buf);
    printf("\r\n");
    va_end(args);
}

09、无格式打印信息

/**
 * This function is print flash non-package info.
 *
 * @param format output format
 * @param ... args
 */
void ef_print(const char *format, ...)
{
    va_list args;
    /* args point to the first variable parameter */
    va_start(args, format);
    /* You can add your code under here. */
    vsprintf(log_buf, format, args);
    printf("%s", log_buf);
    va_end(args);
}

10、默认环境变量集合

/* default environment variables set for user */
static const ef_env default_env_set[] =
{
    {"startup_times", "0"},
    {"pressed_times", "0"},
};

七. 设置参数

配置时需要修改项目中的ef_cfg.h文件,开启、关闭、修改对应的宏即可。

01、FLASH最小擦除单元

操作方法:修改EF_ERASE_MIN_SIZE宏对应值即可,单位:byte

/* The minimum size of flash erasure. May be a flash sector size. */
#define EF_ERASE_MIN_SIZE      1024  /* @note you must define it for a value */

02、FLASH写入粒度

操作方法:修改EF_WRITE_GRAN宏对应值即可,单位:bit,仅支持:1/8/32

/* the flash write granularity, unit: bit. only support 1(nor flash)/ 8/ 32 */    
#define EF_WRITE_GRAN          32    /* @note you must define it for a value */

03、备份区起始地址

操作方法:修改EF_START_ADDR宏对应值即可

/* backup area start address */
#define EF_START_ADDR    (0x08000000 + 50 * 1024)  /* @note you must define it for a value */

04、环境变量区总容量

操作方法:修改ENV_AREA_SIZE宏对应值即可

/* ENV area size. It's at least one empty sector for GC. So it's definition must
more then or equal 2 flash sector size. */
#define ENV_AREA_SIZE    (2 * 1024)    /* @note you must define it for a value if you used ENV */

在配置时需要注意以下几点:

1、所有的区域必须按照EF_ERASE_MIN_SIZE对齐;

2、环境变量分区大少至少为两倍以上EF_ERASE_MIN_SIZE;

3、从V4.0开始ENV的模式命名为NG模式,V4.0之前的称之为LEGACY遗留模式;遗留模式已经被废弃,不再建议继续使用;如果需要继续使用遗留模式,请EasyFlash的V3.X版本。

八. 测试验证

每次使用前,务必先执行easyflash_init()方法对EasyFlash库及所使用的FLASH进行初始化,保证初始化没问题后,再使用各功能的API方法。如果出现错误或断言,需根据提示信息检查移植配置及接口。

01、编写系统启动次数记录

/*******************************************************************************
 * @brief       
 * @param       
 * @retval      
 * @attention   
*******************************************************************************/
void EasyFlash_Demo(void)
{
    uint32_t startup_times = 0;
    char *old_startup_times, new_startup_times[30] = {0};
    old_startup_times = ef_get_env("startup_times");
    startup_times = atol(old_startup_times);
    startup_times++;
    sprintf(new_startup_times, "%d", startup_times);
    printf("\r\nThe system now startup %d times\r\n\r\n", startup_times);
    ef_set_env("startup_times", new_startup_times);
    ef_save_env();
}

02、编写按键次数记录

/*******************************************************************************
 * @brief       
 * @param       
 * @retval      
 * @attention   
*******************************************************************************/
void EasyFlash_Demo(void)
{
    uint32_t startup_times = 0;
    char *old_startup_times, new_startup_times[30] = {0};
    old_startup_times = ef_get_env("startup_times");
    startup_times = atol(old_startup_times);
    startup_times++;
    sprintf(new_startup_times, "%d", startup_times);
    printf("\r\nThe system now startup %d times\r\n\r\n", startup_times);
    ef_set_env("startup_times", new_startup_times);
    ef_save_env();
}

九. 运行测试

01、下载程序后,第一次运行

检测到EasyFlash没有默认配置或者说存储的数据CRC校验错误时,EasyFlash都会将存储恢复到默认值;随后将当前系统启动的次数记录到片机FLASH中,按键2次按键后,对按键的次数进行存储记录,如下图所示:


02、开发板重新上电运行

此时片内FLASH已经存在记录数据,等EasyFlash初始化成功后,取出当前的启动次数记录,进行操作后,更新启动次数存储记录;接着按键用户按键,我们会发现按键的次数接在上次的记录数据后面继续累加了,如下图所示:


参考源码:
https://github.com/Samplecode-MM32/MM32MCU_Code

从 EasyFlash V4.1 后,基于 EasyFlash 全新设计开发的 FlashDB 开源项目也正式上线,新集成了时序数据库、多分区管理,多数据库实例等功能,也从一定程度上提升了整体性能。

来源:灵动微电子

围观 399

嵌入式应用中经常会遇到需要保存一些数据,比如配置信息等等,为了在设备关机或掉电情况下数据不丢失,我们通常做法是会将数据保存在存储区,可以在FLASH主存储区、备份寄存器和选项字节等存储用户数据。

MM32F013x系列芯片的嵌入式闪存高达 64K 字节 ,整个片内 FLASH由两部分组成:一部分是主存储块,另一部分是信息存储块。主存储块除了被用于存储用户代码,也可被模拟成EEPROM来存储用户数据;在信息存储块中,包括了保护字节、保密空间、系统存储器 ISP 和选项字节四部分,其中除了系统存储器 ISP区域用户不可使用外,用户可以通过对应的操作流程对其它区域进行读写操作,于用户而言,选项字节用户数据区也可以被用来存储2个字节长度的有效数据。

本文将重点介绍如何在MM32F013x上实现用FLASH 选项字节存储用户数据以及使能硬件独立看门狗模式的功能。

选项字节介绍

MM32F013x 系列MCU 的选项字节主要用于存储用户对芯片的配置信息及用户关键数据,主要有写保护使能、看门狗模式切换等等不同配置内容,FLASH 控制器可以通过对这些值的设置来选择不同的系统功能选项。选项字节区块的前16字节,每两个字节组成一个正反对,其中用户只需要设置低位的字节,高位由系统自动填充为其反码。

选项字节的组成如下表所示(位 15 ∼ 8 中的值为位 7 ∼ 0 中选项字节 0 的反码):


注意:在写保护值中,一个比特位对应四页,即 4096 字节,其它详情请参见UM手册。

USER:字节2,用户字节,配置看门狗模式、停机复位模式、待机复位模式以及BOOT1。

DATA0:字节4,数据字节0,由用户存储数据。

DATA1:字节6,数据字节1,由用户存储数据。

WRP0:字节8,写保护字节0,存储对主存储块的写保护设置。

WRP1:字节10,写保护字节1,存储对主存储块的写保护设置。

01、相关寄存器

除了之前熟悉的FLASH_ACR 、FLASH_KEYR、FLASH_CR 以及FLASH_AR 等FLASH 基础寄存器外,主要还需要关注以下相关的寄存器:



02、擦除流程

选项字节区块擦除操作流程的具体步骤如下:


1. 检查FLASH_CR寄存器的LOCK位,如未被解锁则执行解锁操作。

if((FLASH->CR & FLASH_CR_LOCK) == SET )
{
    FLASH->KEYR = FLASH_KEY1 ;
    FLASH->KEYR = FLASH_KEY2 ;
}

2. 检查FLASH_CR寄存器的OPTWRE位,如未置位则执行解锁OPTWRE操作。

if((FLASH->CR & FLASH_CR_OPTER) == RESET)
{
    FLASH->OPTKEYR = FLASH_KEY1 ;
    FLASH->OPTKEYR = FLASH_KEY2 ;
}

3. 将闪存选项字节块基地址写入FLASH_AR寄存器。

FLASH->AR = OB_BASE ;

4. 设置FLASH_CR寄存器的OPTER位为1,选择选项字节擦除操作。

FLASH->CR |= FLASH_CR_OPTER ;

5. 设置FLASH_CR寄存器的STRT位为1。

FLASH->CR |= FLASH_CR_STRT ;

6. 等待FLASH_SR寄存器的BSY位变为0,再读出选项字节寄存器中所有地址来验证擦除操作,直到流程结束。

do{               
       ret = (((FLASH->SR & FLASH_FLAG_BSY)) ? FLASH_BUSY:            \
              ((FLASH->SR & FLASH_FLAG_PGERR) ? FLASH_ERROR_PG:         \
              ((FLASH->SR & FLASH_FLAG_WRPRTERR) ? 
                FLASH_ERROR_WRP : FLASH_COMPLETE))) ;
       EraseTimeout-- ;
       for (i = 0xFF; i != 0; i--) ;
  }while((ret == FLASH_BUSY) && (EraseTimeout!= 0x00)) ;
  FLASH->CR = 0 ;
  FLASH->SR = FLASH_SR_EOP | FLASH_SR_WRPRTERR | FLASH_SR_PGERR ;        
  return (FLASH_Status)((EraseTimeout== 0x00) ? FLASH_TIMEOUT : ret) ;

由于选项字节只有16字节,因此,擦除时是整个选项字节都将被擦除,此时需要注意,将有效的用户数据及配置信息提前保存到内存中再进行擦除。

03、编程流程

选项字节区块编程操作流程的具体步骤如下:


1. 检查FLASH_CR寄存器的LOCK位,如未被解锁则执行解锁操作。

if((FLASH->CR & FLASH_CR_LOCK) == SET )
    {
        FLASH->KEYR = FLASH_KEY1 ;
        FLASH->KEYR = FLASH_KEY2 ;
    }

2. 检查FLASH_CR寄存器的OPTWRE位,如未置位则执行解锁OPTWRE操作。

if((FLASH->CR & FLASH_CR_OPTER) == RESET)
{
    FLASH->OPTKEYR = FLASH_KEY1 ;
    FLASH->OPTKEYR = FLASH_KEY2 ;
}

3. 设置FLASH_CR寄存器的OPTPG位为1,选择编程操作。

FLASH->CR |= FLASH_CR_OPTPG ;

4. 写入要编程的半字到指定的地址,启动编程操作。

__IO u16 temp ;
temp = (u16)(~data) ;
temp = (temp << 8) & 0xFF00 ;
temp = temp | (u16)data ;
*(__IO u16*)address = temp ;

5. 等待FLASH_SR寄存器的BSY位变为0,可选读目标地址数据,以确保半字编程成功,直到所有编程流程结束。

do{               
       ret = (((FLASH->SR & FLASH_FLAG_BSY)) ? FLASH_BUSY:            \
              ((FLASH->SR & FLASH_FLAG_PGERR) ? FLASH_ERROR_PG:         \
              ((FLASH->SR & FLASH_FLAG_WRPRTERR) ? 
                FLASH_ERROR_WRP : FLASH_COMPLETE))) ;
       ProgramTimeout -- ;
       for (i = 0xFF; i != 0; i--) ;
  }while((ret == FLASH_BUSY) && (ProgramTimeout != 0x00)) ;
   FLASH->CR = 0 ;
   FLASH->SR = FLASH_SR_EOP | FLASH_SR_WRPRTERR | FLASH_SR_PGERR ;        
   return (FLASH_Status)((ProgramTimeout == 0x00) ? FLASH_TIMEOUT : ret) ;

软件实现步骤

下面列出配置代码,实现开启硬件IWDG独立看门狗以及将用户数据存储在DATA0和DATA1。

01、主程序初始化

s32 main(void)
{
    /* Systick delay init */
    DELAY_Init();
    /* Uart1 init */
    CONSOLE_Init(115200) ;
    printf("\r\nMM32F013x OptionByte Demo %s %s\r\n", __DATE__, __TIME__);

    /* OptionByte test entry */
    FLASH_OptionByte_Entry() ;

    /* Set the HWIWDG parameters,no need to turn on LSI or IWDG_Enable */
    Set_IWDG(IWDG_Prescaler_64, 999);
    printf("\r\n IWDG reload value is set to 1.6s !\r\n ");

    while (1)
    {
        /* Feed the HW_IWDG if needed*/
        Feed_IWDG() ;
        DELAY_Ms(1000) ;
        printf("\r\n System is going on. Feed the IWDG...\r\n "); 
    }
}

以上为整个软件工程的入口主函数,平台相关的堆栈设置、时钟设置以及FLASH延迟设置等等都在STARTUP_MM32F013X_KEIL.S和SYSTEM_MM32f013x.C文件中已经完成,默认主频为内部时钟HSI倍频到72M,上电后程序会跳转到主函数。初始化好延时和串口后,进入到测试选项字节的功能函数中,并且接下来的初始化中还配置了IWDG的重装载值以及分频系数,在while(1)循环里选择是否喂狗看不同现象。

02、OptionByte 实验函数

void FLASH_OptionByte_Entry(void)
{
    volatile uint32_t TempOptionByteValue = 0;
    uint8_t WriteUserData[2] ={0x01,0x02} ;

    /* Unlock The FLASH Controller */
    FLASH_Unlock();

    if( FLASH_GetFlagStatus(FLASH_FLAG_OPTERR) == RESET )
    {      
        TempOptionByteValue = FLASH_GetUserOptionByte();
        printf("\r\nTempOptionByteValue=0x%2x\r\n ", TempOptionByteValue);

        if(((uint8_t)(TempOptionByteValue >> 10) != WriteUserData[0]) &&           \
            ((uint8_t)(TempOptionByteValue >> 18) != WriteUserData[1]) )
        {/* User Data0 != 0x01 , User Data1 != 0x02*/           
            /* Erase Option Bytes */
            FLASH_Status status = FLASH_EraseOptionBytes();

            /* Write User Data0 */
            status = FLASH_ProgramOptionByteData(0x1FFFF804, WriteUserData[0]) ;
            /* Write User Data1 */
            status = FLASH_ProgramOptionByteData(0x1FFFF806, WriteUserData[1]) ;
            /* Write User Byte to enable HW mode of IWDG */
            status = FLASH_ProgramOptionByteData(0x1FFFF802,0xFE) ;
            printf("\r\n UserDatas and UserByte are written!\r\n ");       

            printf("\r\n INPORTANT! Need to reset the system ……\r\n ");
            /* Lock The Flash Program Erase Controller */
            FLASH_Lock();
            /* System Reset */
            NVIC_SystemReset();
        }
        else
        {                    
            if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET)
            {
                printf("\r\n RCC_FLAG_IWDGRST is set!\r\n ");
                /* Clear reset flags */
                RCC_ClearFlag();
                printf("HW IWDG was set successfully,need to RL_IWDG!\r\n ") ;
            }
            else
            {
                printf("\r\n Reboot ok.\r\n "); 
                printf("\r\n UserDatas were updated successfully!\r\n ");  
            }
        }
    }
    /* Lock The Flash Controller */
    FLASH_Lock();
}

由于每次系统复位后,选项字节才会被重新加载,并保存在选项字节寄存器 (FLASH_OBR)中,每个选择位都在信息块中有它的反码位,在加载选择位时,反码位用于验证选择位是否正确,如果有任何的差别,将产生一个选项字节错误标志 (OPTERR),所以建议操作选项字节空间时先判断FLASH标志位。

按照流程对FLASH的选项字节空间进行数据读取和写入操作,且设置独立看门狗为硬件模式,做完所有的操作后,一定要将系统进行软复位或者上电复位,这样才能使之前的写入操作正式生效。

最后,通过RCC的复位标志判断此次复位是否由看门狗引发,搭配串口信息输出来进行所有功能验证。

测试验证

本次实验同样是基于eMiniboard MB-025硬件资源完成的,前述完整代码中,开启了在主循环的喂狗操作,每隔1s钟进行喂狗动作且通过串口打印运行状态,串口助手完整显示如下:


可以发现已经正常将用户数据0x01和0x02分别写入到了Data0和Data1中,且独立看门狗的硬件模式也有设置,为了验证看门狗已经生效,屏蔽喂狗语句后重新编译工程,擦除全片后重新烧录程序,此刻,串口助手完整显示如下:


通过以上打印信息,可以确认硬件看门狗的硬件模式已经生效,无需通过用户代码开启LSI时钟和开启独立看门狗,只要芯片正常上电即可完成对用户程序异常情况发生的防护,从而提高系统整体安全性能。

转自:灵动微电子

围观 97

RTC可用于周期性从低功耗模式下唤醒MCU,RTC可选三个时钟源:

  • 低功耗 32.768kHz 外部低速晶振 (LSE):该时钟源提供了一个低功耗且精确的时间基准。
  • 低功耗内部低速振荡器 (LSI):使用该时钟源,节省了一个 32.768kHz 晶振的成本,但是精度没有外部晶振的精度高。
  • 外部高速晶振(HSE)128分频:在某些低功耗模式中HSE被关闭会导致RTC无时钟源。

为了用 RTC 闹钟事件将系统从停机模式下唤醒,必须进行如下操作:

  • 配置外部中断线 17 为上升沿触发。
  • 配置 RTC 使其可产生 RTC 闹钟事件。

如果要从待机模式中唤醒, 不必配置外部中断线 17。

MM32F013x有三种低功耗模式:睡眠模式(Sleep Mode)、停机模式(Stop Mode)和待机模式(Standby Mode),三种低功耗模式的对比如下表所示:

MM32F013x的三种低功耗模式

从上表中可以看出,当MCU工作在停机模式时,可以通过任一外部中断事件来唤醒。MM32F013x低功耗模式下的功耗列表如下图所示:

MM32F013x低功耗模式下的功耗列表

MM32F013x有22个外部中断源,其中EXTI 17对应的是RTC闹钟事件,所以结合RTC闹钟的功能,本文将重点介绍如何在MM32F013x上通过内部RTC模块的闹钟事件来唤醒处于停机模式下的MCU。

01、实现功能

通过内部RTC模块的闹钟事件(对应的是外部中断EXTI 17)来唤醒处于停机模式下的MCU。系统在进入停机模式时拉高GPIO端口,在恢复到正常运行状态时拉低GPIO端口。

02、配置顺序

1)使能PWR和BKP时钟:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

2)使能后备寄存器访问:

PWR_BackupAccessCmd(ENABLE);

3)配置RTC时钟源,使能RTC时钟,如果使用LSE,要打开LSE:

RCC_LSEConfig(RCC_LSE_ON);
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
RCC_RTCCLKCmd(ENABLE);

4)设置RTC预分频系数:

RTC_SetPrescaler();

5)开启相关中断:

RTC_ITConfig(RTC_IT_ALR, ENABLE);

6)配置外部中断线:

EXTI_StructInit(&EXTI_InitStructure);
EXTI_InitStructure.EXTI_Line    = EXTI_Line17;
EXTI_InitStructure.EXTI_Mode    = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);

7)配置中断服务函数:

NVIC_InitStructure.NVIC_IRQChannel  = RTC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

8)部分操作要等待写操作完成和同步。

03、参考代码

3.1 RTC初始化配置:使用外部32.768kHz的晶振源

void RTC_Configure(void)
{
    uint16_t BKP_Value = 0x5A5A;
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    /* Enable PWR Clock */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

    /* Enable Access To The RTC & Backup Registers */
    PWR_BackupAccessCmd(ENABLE);

    if(BKP_ReadBackupRegister(BKP_DR1) != BKP_Value)
    {
        BKP_DeInit();

        /* Enable LSE Clock Source */
        RCC_LSEConfig(RCC_LSE_ON);

        /* Wait LSI Clock Source Ready */
        while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);

        /* Config RTC Clock Source : LSE */
        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);

        /* Enable RTC Clock */
        RCC_RTCCLKCmd(ENABLE);

        /* Wait For Synchronization */
        RTC_WaitForSynchro();
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();

        /* Set The RTC Prescaler Value */
        RTC_SetPrescaler(32767);
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();
        /* Enable RTC Alarm Interrupt */
        RTC_ITConfig(RTC_IT_ALR, ENABLE);
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();
        /* Exit From The RTC Configuration Mode */
        RTC_ExitConfigMode();
        BKP_WriteBackupRegister(BKP_DR1, BKP_Value);
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();
    }
    else
    {
        /* Wait For Synchronization */
        RTC_WaitForSynchro();
        /* Enable RTC Alarm Interrupt */
        RTC_ITConfig(RTC_IT_ALR, ENABLE);
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();
    }
    /* Clear EXTI Line17 Flag */
    EXTI_ClearITPendingBit(EXTI_Line17);

   /* Configure EXTI Line17(RTC Alarm) To Generate An Interrupt On Rising Edge */
    EXTI_StructInit(&EXTI_InitStructure);
    EXTI_InitStructure.EXTI_Line    = EXTI_Line17;
    EXTI_InitStructure.EXTI_Mode    = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);

    /* Config RTC NVIC */
    NVIC_InitStructure.NVIC_IRQChannel  = RTC_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

3.2 RTC闹钟中断函数

void RTC_BKP_IRQHandler(void)
{
    if(RTC_GetITStatus(RTC_IT_ALR) != RESET)
    {
        GPIO_WriteBit(LED_GPIO, LED_PIN, Bit_RESET);

        /* Clear EXTI Line17 Flag */
        EXTI_ClearITPendingBit(EXTI_Line17);

        /* Check If The Wake-Up Flag Is Set */
        if(PWR_GetFlagStatus(PWR_FLAG_WU) != RESET)
        {
            /* Clear Wake Up Flag */
            PWR_ClearFlag(PWR_FLAG_WU);
        }
        /* Clear Alarm Flag */
        RTC_ClearITPendingBit(RTC_IT_ALR);

        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();
    }
}

3.3 设置RTC闹钟时间,系统进入STOP模式

void RTC_EntryStopMode(void)
{
    /* Wait till RTC Second event occurs */
    RTC_ClearFlag(RTC_FLAG_SEC);
    while(RTC_GetFlagStatus(RTC_FLAG_SEC) == RESET);

    /* Set The RTC Alarm Value */
    RTC_SetAlarm(RTC_GetCounter() + 3);

    /* Wait Until Last Write Operation On RTC REG Has Finished */
    RTC_WaitForLastTask();

    GPIO_WriteBit(LED_GPIO, LED_PIN, Bit_SET);

    /* Enter Stop Mode */
    PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFI);
}

04、运行结果

编译软件工程无误后下载代码,调用RTC_EntryStopMode()函数使系统进入到停机模式,等待RTC闹钟事件3S后唤醒停机模式下的MCU,唤醒后继续执行程序,通过观察GPIO的电平状态来查看运行结果:


用户可以结合上一篇万年历功能设置在某年某月某时某刻定时唤醒MCU功能,万年历的具体实方式可以参考上一篇《MM32F013x——万年历》

转自:灵动微电子

围观 211

MM32F013x内部的RTC是一个独立的定时器单元,它拥有一组连续计数的计数器,配置相应的寄存器参数,可以实现闹钟、秒中断、毫秒中断、MCU定时唤醒、万年历等功能。

主要特征

① 可编程的预分频系数:分频系数最高为 220

② 32 位的可编程计数器,用于较长时间段的测量

③ 2 个分离的时钟:用于 APB1 接口的 PCLK1 和 RTC 时钟 (RTC 时钟的频率必须小于PCLK1 时钟频率的四分之一以上)

④ 可以选择以下三种 RTC 的时钟源
– HSE 时钟除以 128
– LSE 振荡器时钟
– LSI 振荡器时钟

⑤ 2 个独立的复位类型
– APB1 接口由系统复位
– RTC 核心 (预分频器、闹钟、计数器和分频器) 只能由后备域复位

⑥ 3 个专门的屏蔽中断
– 闹钟中断,用来产生一个软件可编程的闹钟中断
– 秒 / 毫秒中断,用来产生一个可编程的周期性中断信号 (最长可达 1 秒)
– 溢出中断,指示内部可编程计数器溢出并返回为 0 的状态

本文将重点介绍如何在MM32F013x上通过内部RTC模块实现万年历的功能。

实现功能

通过修改RTC计数器的初始值来设置系统当前的时间和日期,使能RTC秒中断功能;在RTC产生秒中断后,通过获取当前RTC的计数值,将其转换为对应的年月日信息,再通过蔡勒公式计算出星期,将最终的结果通过串口的形式输出显示。

RTC模块的电源域处在VDD数字电源域,只要MCU供电就可以使用RTC,没有独立的VBAT供电引脚,所以无法使用纽扣电池类的应用。

参考代码

01、结构体定义及全局变量

typedef struct
{
    uint16_t year;
    uint8_t  month;
    uint8_t  day;
    uint8_t  week;
    uint8_t  hour;
    uint8_t  minute;
    uint8_t  second;
} CALENDAR_t;
const uint8_t RTC_DayOfMonth[12] =
{
    31,28,31,30,31,30,31,31,30,31,30,31
};
CALENDAR_t    RTC_Calendar;

02、RTC初始化配置:使用外部32.768kHz的晶振源

void RTC_Configure(void)
{
    uint16_t BKP_Value = 0x5A5A;
    NVIC_InitTypeDef NVIC_InitStructure;
    /* Enable PWR Clock */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
    /* Enable WKUP pin */
    PWR_WakeUpPinCmd(ENABLE);
    /* Enable Access To The RTC & Backup Registers */
    PWR_BackupAccessCmd(ENABLE);
    if(BKP_ReadBackupRegister(BKP_DR1) != BKP_Value)
    {
        BKP_DeInit();
        /* Enable LSE Clock Source */
        RCC_LSEConfig(RCC_LSE_ON);

        /* Wait LSI Clock Source Ready */
        while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);

        /* Config RTC Clock Source : LSE */
        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);

        /* Enable RTC Clock */
        RCC_RTCCLKCmd(ENABLE);

        /* Wait For Synchronization */
        RTC_WaitForSynchro();
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();

        /* Set The RTC Prescaler Value */
        RTC_SetPrescaler(32767);
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();

        /* Enable RTC Second Interrupt */
        RTC_ITConfig(RTC_IT_SEC, ENABLE);
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();

        /* Exit From The RTC Configuration Mode */
        RTC_ExitConfigMode();

        BKP_WriteBackupRegister(BKP_DR1, BKP_Value);
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();

        /* Set initial time */
        RTC_SetDateTime(2021, 1, 12, 14, 48, 0);
    }
    else
    {
        /* Wait For Synchronization */
        RTC_WaitForSynchro();

        /* Enable RTC Second Interrupt */
        RTC_ITConfig(RTC_IT_SEC, ENABLE);
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();
    }

    /* Config RTC NVIC */
    NVIC_InitStructure.NVIC_IRQChannel  = RTC_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

03、RTC秒中断函数

void RTC_BKP_IRQHandler(void)
{
    if(RTC_GetITStatus(RTC_IT_SEC) != RESET)
    {
        /* Update Date and Time */
        RTC_UpdateCalendar();

        /* Print current Date and Time */
        RTC_PrintDateTime();

        /* Clear Alarm Flag */
        RTC_ClearITPendingBit(RTC_IT_SEC);
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();
    }
}

04、将RTC计数值转换为日期信息

void RTC_UpdateCalendar(void)
{
    static uint32_t PreTotalDay = 0;

    uint32_t TotalSecond = 0;
    uint32_t TotalDay    = 0;

    uint16_t Year  = 1970;
    uint8_t  Month = 0;

    /* Get The RTC Counter Value */
    TotalSecond = RTC_GetCounter();
    TotalDay    = TotalSecond / 86400;

    if(PreTotalDay != TotalDay)
    {
        PreTotalDay = TotalDay;

        while(TotalDay >= 365)
        {
            if(RTC_LeapYear(Year) == 1)
            {
                if(TotalDay >= 366)
                {
                    TotalDay -= 366;
                }
                else
                {
                    break;
                }
            }
            else
            {
                TotalDay -= 365;
            }

            Year++;
        }
        RTC_Calendar.year = Year;
        while(TotalDay >= 28)
        {
            if((Month == 1) && (RTC_LeapYear(RTC_Calendar.year) == 1))
            {
                if(TotalDay >= 29)
                {
                    TotalDay -= 29;
                }
                else
                {
                    break;
                }
            }
            else
            {
                if(TotalDay >= RTC_DayOfMonth[Month])
                {
                    TotalDay -= RTC_DayOfMonth[Month];
                }
                else
                {
                    break;
                }
            }

            Month++;
        }

        RTC_Calendar.month = Month    + 1;
        RTC_Calendar.day   = TotalDay + 1;

        RTC_Calendar.week  = RTC_GetWeek(RTC_Calendar.year, RTC_Calendar.month, RTC_Calendar.day);
    }

    RTC_Calendar.hour   =  (TotalSecond % 86400) / 3600;
    RTC_Calendar.minute = ((TotalSecond % 86400) % 3600) / 60;
    RTC_Calendar.second = ((TotalSecond % 86400) % 3600) % 60;
}

05、将日期信息转换为RTC计数值

void RTC_SetDateTime(uint16_t Year, uint8_t Month, uint8_t Day,
                     uint8_t  Hour, uint8_t Min,   uint8_t Sec)
{
    uint32_t TotalSecond = 0;
    uint16_t y = 0;
    uint8_t  m = 0;

    if((Year >= 1970) && (Year <= 2099))
    {
        for(y = 1970;  y < Year; y++)
        {
            if(RTC_LeapYear(y) == 1)
            {
                TotalSecond += 31622400;    /* Total Seconds Of Leap   Year */
            }
            else
            {
                TotalSecond += 31536000;    /* Total Seconds Of Normal Year */
            }
        }

        for(m = 0; m < (Month - 1); m++)
        {
            TotalSecond += RTC_DayOfMonth[m] * 86400; /*Total Seconds Of Month */
            if((RTC_LeapYear(Year) == 1) && (m == 1))
            {
                TotalSecond += 86400;
            }
        }
        TotalSecond += (uint32_t)(Day - 1) * 86400; /* Total Seconds Of Day    */
        TotalSecond += (uint32_t)Hour      * 3600;  /* Total Seconds Of Hour   */
        TotalSecond += (uint32_t)Min       * 60;    /* Total Seconds Of Minute */
        TotalSecond += Sec;

        /* Enable PWR Clock */
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

        /* Enable Access To The RTC & Backup Registers */
        PWR_BackupAccessCmd(ENABLE);

        /* Set The RTC Counter Value */
        RTC_SetCounter(TotalSecond);

        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();

        RTC_UpdateCalendar();
    }
    else
    {
        printf("\r\nError Date & Time!!!\r\n");
    }
}

06、RTC信息打印

void RTC_PrintDateTime(void)
{
    printf("\r\n%04d-%02d-%02d", RTC_Calendar.year, RTC_Calendar.month,  RTC_Calendar.day);

    switch(RTC_Calendar.week)
    {
     case 0 :
        printf(" SUN ");
        break;

     case 1 :
        printf(" MON ");
        break;

     case 2 :
        printf(" TUE ");
        break;

     case 3 :
        printf(" WED ");
        break;

     case 4 :
        printf(" THU ");
        break;

     case 5 :
        printf(" FRI ");
        break;

     case 6 :
        printf(" SAT ");
        break;

     default:
        break;
    }

    printf("%02d:%02d:%02d\r\n", RTC_Calendar.hour, RTC_Calendar.minute, RTC_Calendar.second);
}

07、RTC功能函数:判断闰年、蔡勒公式计算星期

uint8_t RTC_LeapYear(uint16_t Year)
{
    if(
        (((Year % 400) == 0)                     ) ||   /* Century Leap Year */
        (((Year % 100) != 0) && ((Year % 4) == 0))      /* Normal  Leay Year */
    )
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

uint8_t RTC_GetWeek(uint16_t Year, uint8_t Month, uint8_t Day)
{
    int w, c, y;
    /* Month 1 Or 2 of This Year Must Be As Last Month 13 Or 14 */
    if((Month == 1) || (Month == 2))
    {
        Month += 12;
        Year  -= 1;
    }

    w = 0;          /* Weekday */
    c = Year / 100; /* Century */
    y = Year % 100; /* Year    */

    w = y + (y / 4) + (c / 4) - (2 * c) + (26 * (Month + 1) / 10) + Day - 1;

    while(w < 0) w += 7;

    w %= 7;

    return w;
}

运行结果

编译软件工程无误后下载代码,在串口终端工具中我们可以看到每间隔1秒钟,RTC产生一次中断,在中断中我们将当前的日期信息通过串口打印在显示终端软件上:

转自:灵动微电子

围观 76

页面

订阅 RSS - MM32F013x