本帖最后由 _Prtemly 于 2025-6-21 22:12 编辑

1. 准备工作

1.1 硬件准备

  • 凌鸥创芯LKS EVB MC453 V2.0开发板一张。
  • 调试器(如J-Link、ST-Link等)
  • 必要的连接线缆,如图:

Snipaste_2025-06-21_21-30-02.png

电路原理图下载链接:LKS_EVB_LKS32MC453RCT8_Core

Snipaste_2025-06-21_21-36-05.png

串口接入图上的P0_1和P0_2;LED灯接入LED1(即P2_4),见原理图上红圈位置。

(注:这个开发板应该是配合电机开发底板一起使用的,板载LED都没有,只能手焊一个接入,上图白灯)



1.2 软件准备

  • Keil MDK或IAR Embedded Workbench开发环境
       Keil MDK需要安装芯片支持包,下载地址:LKS32MC45x_Keil5
  • RT-Thread Nano源码包(可从RT-Thread官网下载)
  • 目标MCU的BSP(板级支持包),下载地址:LKS32MC45x_DevDriver


2. 创建基础工程

  • 在开发环境中创建一个新的工程,或者直接copy一份sdk的测试工程也可以。
  • 添加必要的lib库文件。


3. 添加RT-Thread Nano到工程
3.1 复制RT-Thread Nano文件将RT-Thread Nano的以下目录复制到你的工程目录中:

<pre>rtthread-nano/
  • ├── include
  • ├── libcpu
  • ├── src
  • └── components</pre>
  • 复制代码

    3.2 添加文件到工程

    在开发环境中添加以下文件到工程:

    • rtthread-nano/src/*.c
    • rtthread-nano/libcpu/arm/cortex-m4/*.c

    3.3 包含头文件路径

    添加以下头文件路径到工程设置:

    • rtthread-nano/include
    • rtthread-nano/libcpu/arm/cortex-m4

    配置好的工程目录如下图:

    Snipaste_2025-06-21_21-46-17.png


    4. 配置RT-Thread Nano

    4.1 修改rtconfig.h创建或修改rtconfig.h文件,

    配置RT-Thread Nano的基本参数,使用默认参数即可,由于我们的Demo需要用到串口日志输出,打开RT_USING_CONSOLE宏定义即可:

    Snipaste_2025-06-21_21-49-15.png

    如上图红圈位置,取消注释即可。


    4.2 板级硬件配置

    board.c中配置内存堆:

    Snipaste_2025-06-21_21-50-55.png

    LKS32MC453芯片内置40K RAM,相对来说还是很丰富的。


    配置systick中断函数:

    Snipaste_2025-06-21_21-52-58.png

    配置Systick中断基本上是默认套路,不过这里要注意的是,由于官方SystemInit函数里边有Reset操作,这里不用额外调用,参见图上注释掉的部分。


    配置日志串口初始化及日志输出函数

    日志输出串口选择uart0,见第一小节电路图,配置P0_1和P0_2分别为uart0的rx和tx,代码如下:

    Snipaste_2025-06-21_21-58-09.png


    5. 配置MCU工作时钟

    LKS32MC453最高工作频率196M,代码如下,使用官方的时钟初始化代码:

    Snipaste_2025-06-21_21-59-58.png


    6. 创建示例应用

    6.1 修改main函数

    我们这里只开了一个点灯的任务:

    Snipaste_2025-06-21_22-01-28.png

    其中点灯任务使用了板LED1插针,对应管脚为P2_4,代码如下:

    Snipaste_2025-06-21_22-02-45.png


    7. 编译与调试

    • 编译工程,确保没有错误
    • 连接调试器,下载程序到目标板
    • 通过串口调试工具查看输出,确认RT-Thread正常运行


    Snipaste_2025-06-21_22-04-56.png


    8.一点疑惑

    官方SDK代码中关于TRIM的操作,提供了汇编代码,在MDK中直接使用会编译报错。

    不得已只好将其改为C语言代码,不知实际效果如何,Demo中未实际使用TRIM的相关功能,只能留待之后的使用中观察了。

    代码如下:

    <pre>#include "lks32mc45x_trim.h"

  • // 快速擦除计数器
  • #define FSMC_ICFG_RETRY_Pos         (2U)
  • #define FSMC_ICFG_RETRY_Msk         (0x3UL << FSMC_ICFG_RETRY_Pos)
  • // 快速擦除使能 0:关闭; 1:使能
  • #define FSMC_ICFG_RETRY_EN_Pos      (7U)
  • #define FSMC_ICFG_RETRY_EN_Msk      (0x1UL << FSMC_ICFG_RETRY_EN_Pos)
  • // 访问 On-Chip FLASH 区域选择。默认为 0。0:MAIN/ROM(根据地址不同选择不同区域) 1:NVR
  • #define FSMC_ICFG_REGION_Pos        (11U)
  • #define FSMC_ICFG_REGION_Msk        (0x1UL << FSMC_ICFG_REGION_Pos)
  • // On-Chip FLAS 保护字选择  0:选择 On-Chip FLASH 存储体整体保护策略的保护字; 1:选择 ROM 区域独有保护策略的保护字
  • #define FSMC_ICFG_SEL_PROT_Pos      (19U)
  • #define FSMC_ICFG_SEL_PROT_Msk      (0x1UL << FSMC_ICFG_SEL_PROT_Pos)
  • // On-Chip FLASH 地址递增使能。默认为 0。 0:关闭递增使能;  1:开启递增使能
  • // 当执行 On-Chip FLASH 连续读写访问时,开启此功能,可以减少对地址的操作
  • #define FSMC_ICFG_ADR_INC_Pos       (23U)
  • #define FSMC_ICFG_ADR_INC_Msk       (0x1UL << FSMC_ICFG_ADR_INC_Pos)
  • // On-Chip FLASH 编程使能。默认为 0。0:关闭编程使能; 1:开启编程使能
  • #define FSMC_ICFG_PRG_EN_Pos        (27U)
  • #define FSMC_ICFG_PRG_EN_Msk        (0x1UL << FSMC_ICFG_PRG_EN_Pos)
  • // On-Chip FLASH 擦除使能。默认为 0。0:关闭擦除使能; 1:开启擦除使能
  • #define FSMC_ICFG_ERS_EN_Pos        (31U)
  • #define FSMC_ICFG_ERS_EN_Msk        (0x1UL << FSMC_ICFG_ERS_EN_Pos)

  • // On-Chip FLASH 存储体整体保护策略的保护状态
  • #define FSMC_PORT_PROT_STS_Pos      (0U)
  • #define FSMC_PORT_PROT_STS_Msk      (0x1UL << FSMC_PORT_PROT_STS_Pos)
  • // ROM 区域独有保护策略的保护状态
  • #define FSMC_PORT_RPROT_STS_Pos     (16U)
  • #define FSMC_PORT_RPROT_STS_Msk     (0x1UL << FSMC_PORT_RPROT_STS_Pos)

  • // I/D总线就绪
  • #define FSMC_REDY_READY_Pos         (0U)
  • #define FSMC_REDY_READY_Msk         (0x1UL << FSMC_REDY_READY_Pos)      
  • // S总线就绪
  • #define FSMC_REDY_IREADY_Pos        (8U)
  • #define FSMC_REDY_IREADY_Msk        (0x1UL << FSMC_REDY_IREADY_Pos)

  • // On-Chip FLASH 在读取操作时,内部基准时钟高电平的持续时间,这个值
  • // 影响了读取操作中时钟信号高电平保持的周期数。该值仅可为 0x1 和 0x2。
  • // 0x1:仅当系统频率低于 96M,可使用的值,高电平保持 2 个系统周期。
  • // 0x2:默认值,当系统大于等于 96M 时,高电平保持 3 个系统时钟周期
  • #define FSMC_IDIV_CKH_Pos           (0U)
  • #define FSMC_IDIV_CKH_Msk           (0xFUL << FSMC_IDIV_CKH_Pos)
  • // On-Chip FLASH 在读取操作时,内部基准时钟低电平的持续时间,这个值
  • // 影响了读取操作中时钟信号低电平保持的周期数。该值仅可为 0x1 和 0x2。
  • // 0x1:仅当系统频率低于 96M,可使用的值,低电平保持 2 个系统周期。
  • // 0x2:默认值,当系统大于等于 96M 时,低电平保持 3 个系统时钟周期
  • #define FSMC_IDIV_CKL_Pos           (4U)
  • #define FSMC_IDIV_CKL_Msk           (0xFUL << FSMC_IDIV_CKL_Pos)
  • // On-Chip FLASH 编程擦除时基寄存器 0。默认值为 0x40,不同时钟频率下
  • // 该寄存器值不需要更改
  • #define FSMC_IDIV_BCNT0_Pos         (8U)
  • #define FSMC_IDIV_BCNT0_Msk         (0x3FUL << FSMC_IDIV_BCNT0_Pos)
  • // On-Chip FLASH 编程擦除时基寄存器 1。不同系统频率,值可以微调。默认
  • // 为 0x5400,对应 192M 系统时钟。
  • // 192M:0x5400
  • // 096M:0x2A00
  • // 048M:0x1500
  • // 024M:0xA80
  • #define FSMC_IDIV_BCNT1_Pos         (16U)
  • #define FSMC_IDIV_BCNT1_Msk         (0x3FFFUL << FSMC_IDIV_BCNT1_Pos)

  • // 触发编程和擦除操作的代码值
  • #define FSMC_WDAT_TRIGGER_CODE      0x7654DCBA
  • #define FSMC_ERAS_TRIGGER_CODE      0x7654DCBA

  • typedef struct
  • {
  •     __IO uint32_t ICFG;     // 配置寄存器   80
  •     __IO uint32_t ADDR;     // 地址寄存器   84
  •     __IO uint32_t WDAT;     // 编程操作触发寄存器   88
  •     __IO uint32_t RDAT;     // 数据寄存器   8C
  •     __IO uint32_t ERAS;     // 擦除操作触发寄存器   90
  •     __IO uint32_t PORT;     // 保护状态寄存器   94
  •     __IO uint32_t REDY;     // 状态寄存器   98
  •     __IO uint32_t IDIV;     // 时基寄存器   9C
  •     __IO uint32_t RA0;      // 未知寄存器   A0
  •     __IO uint32_t RA4;      // 未知寄存器   A4
  •     __IO uint32_t RA8;      // 未知寄存器   A8
  •     __IO uint32_t RAC;      // 未知寄存器   AC
  • }FSMC_TypeDef;

  • #define FSMC_BASE               0x4001A080
  • #define FSMC                    ((FSMC_TypeDef *) FSMC_BASE)

  • // 操作码定义
  • #define FSMC_OPT_UNLOCK         0x59000000
  • #define FSMC_OPT_LOCK           0x95000000

  • #define UID_BASE_ADDR           0x0003FC30
  • #define UID_ALT_ADDR            0x0003FF20

  • // TRIM 值读取函数
  • uint32_t TRIM_Read(uint32_t address)
  • {
  •     uint32_t primask = __get_PRIMASK();
  •     __asm volatile ("CPSID i");  // 禁用中断

  •     // 确保地址4字节对齐
  •     address &= ~0x03;

  •     // 启用TRIM接口
  •     uint32_t reg = FSMC->ICFG;
  •     reg |= FSMC_ICFG_REGION_Msk;  // 选择NVR区域
  •     FSMC->ICFG = reg;

  •     // 发送读取命令
  •     FSMC->ADDR = FSMC_OPT_UNLOCK;
  •     FSMC->ADDR = address;
  •    
  •     // 读取结果
  •     uint32_t result = FSMC->RDAT;
  •    
  •     // 禁用TRIM接口
  •     FSMC->ADDR = FSMC_OPT_LOCK;
  •     reg = FSMC->ICFG;
  •     reg &= ~FSMC_ICFG_REGION_Msk;  // 取消选择NVR区域
  •     FSMC->ICFG = reg;

  •     __set_PRIMASK(primask);  // 恢复中断状态
  •     return result;
  • }

  • // UID 读取函数
  • void TRIM_ReadUid(uint32_t *uid)
  • {
  •     uint32_t primask = __get_PRIMASK();
  •     __asm volatile ("CPSID i");  // 禁用中断

  •     // 初始化TRIM接口
  •     FSMC->ADDR = FSMC_OPT_UNLOCK;
  •     FSMC->RA8 = 0xC3;  // 设置控制寄存器
  •    
  •     // 使能RETRY_EN,并选择NVR区域
  •     uint32_t reg = FSMC->ICFG;
  •     reg |= (FSMC_ICFG_RETRY_EN_Msk | FSMC_ICFG_REGION_Msk);
  •     FSMC->ICFG = reg;

  •     for(uint8_t i=0; i<4; i++) {
  •         // 读取UID的7个部分
  •         FSMC->ADDR = UID_BASE_ADDR + i*4;
  •         // 等待总线就绪
  •         while((FSMC->REDY & FSMC_REDY_IREADY_Msk) != FSMC_REDY_IREADY_Msk);   
  •         uid[i] = FSMC->RDAT;
  •     }
  •    
  •     FSMC->ADDR = UID_ALT_ADDR;
  •     while((FSMC->REDY & FSMC_REDY_IREADY_Msk) != FSMC_REDY_IREADY_Msk);   
  •     uid[4] = FSMC->RDAT;
  •    
  •     FSMC->ADDR = UID_ALT_ADDR - 0x10;
  •     while((FSMC->REDY & FSMC_REDY_IREADY_Msk) != FSMC_REDY_IREADY_Msk);
  •     uid[5] = FSMC->RDAT;
  •    
  •     FSMC->ADDR = UID_ALT_ADDR - 0x20;
  •     while((FSMC->REDY & FSMC_REDY_IREADY_Msk) != FSMC_REDY_IREADY_Msk);
  •     uid[6] = FSMC->RDAT;

  •     // 禁用RETRY_EN,并取消选择NVR区域
  •     reg = FSMC->ICFG;
  •     reg &= ~(FSMC_ICFG_RETRY_EN_Msk | FSMC_ICFG_REGION_Msk);  
  •     FSMC->ICFG = reg;
  •    
  •     // 清除控制寄存器设置
  •     reg = FSMC->RA8;
  •     reg &= ~0xC3;
  •     FSMC->RA8 = reg;
  •    
  •     // 发送禁用命令
  •     FSMC->ADDR = FSMC_OPT_LOCK;

  •     __set_PRIMASK(primask);  // 恢复中断状态
  • }
  • </pre>
  • 复制代码