ExternalFlash缝合记

观前提醒:

作为一篇问题随记大概率是又臭又长,而且过程中会有一大堆的弯路错路,对于看到的内容请读者仔细甄别。

问题随记不定时更新,直到问题解决完结(毕竟你不能要求笔者解决了问题再来重新写

问题提出

问题描述:STM32H750VBT6的Flash空间过小,代码稍微多一点就会将其塞爆。

笔者目的:将板载的一块W25Q64通过QSPI内存映射的方式作为InternalFlash的拓展。

实现平台:Clion

解决思路:

  1. 修改OpenOCD:根据有类似应用的Board脚本修改stm32h7xx.cfg文件
  2. 修改ld文件:根据所需的内存布局修改连接脚本文件
  3. 问题解决

问题探索

修改OpenOCD

所找到的参考文件为:...\openocd\scripts\board\stm32l476g-disco.cfg

内容也不过寥寥几十行:

# This is an STM32L476G discovery board with a single STM32L476VGT6 chip.
# https://www.st.com/en/evaluation-tools/32l476gdiscovery.html

# This is for using the onboard STLINK
source [find interface/stlink.cfg]

transport select hla_swd

# increase working area to 96KB
set WORKAREASIZE 0x18000

# enable stmqspi
set QUADSPI 1

source [find target/stm32l4x.cfg]

# QUADSPI initialization
proc qspi_init { } {
    global a
    mmw 0x4002104C 0x000001FF 0                ;# RCC_AHB2ENR |= GPIOAEN-GPIOIEN (enable clocks)
    mmw 0x40021050 0x00000100 0                ;# RCC_AHB3ENR |= QSPIEN (enable clock)
    sleep 1                                    ;# Wait for clock startup

    # PE11: NCS, PE10: CLK, PE15: BK1_IO3, PE14: BK1_IO2, PE13: BK1_IO1, PE12: BK1_IO0

    # PE15:AF10:V, PE14:AF10:V, PE13:AF10:V, PE12:AF10:V, PE11:AF10:V, PE10:AF10:V

    # Port E: PE15:AF10:V, PE14:AF10:V, PE13:AF10:V, PE12:AF10:V, PE11:AF10:V, PE10:AF10:V
    mmw 0x48001000 0xAAA00000 0x55500000    ;# MODER
    mmw 0x48001008 0xFFF00000 0x00000000    ;# OSPEEDR
    mmw 0x48001024 0xAAAAAA00 0x55555500    ;# AFRH

    mww 0xA0001030 0x00001000                ;# QUADSPI_LPTR: deactivate CS after 4096 clocks when FIFO is full
    mww 0xA0001000 0x01500008                ;# QUADSPI_CR: PRESCALER=1, APMS=1, FTHRES=0, FSEL=0, DFM=0, SSHIFT=0, TCEN=1
    mww 0xA0001004 0x00170100                ;# QUADSPI_DCR: FSIZE=0x17, CSHT=0x01, CKMODE=0
    mmw 0xA0001000 0x00000001 0                ;# QUADSPI_CR: EN=1

    # memory-mapped read mode with 3-byte addresses
    mww 0xA0001014 0x0D002503                ;# QUADSPI_CCR: FMODE=0x3, DMODE=0x1, DCYC=0x0, ADSIZE=0x2, ADMODE=0x1, IMODE=0x1, INSTR=READ
}

$_TARGETNAME configure -event reset-init {
    mmw 0x40022000 0x00000004 0x00000003    ;# 4 WS for 72 MHz HCLK
    sleep 1
    mmw 0x40021000 0x00000100 0x00000000    ;# HSI on
    mww 0x4002100C 0x01002432                ;# 72 MHz: PLLREN=1, PLLM=4, PLLN=36, PLLR=2, HSI
    mww 0x40021008 0x00008001                ;# always HSI, APB1: /1, APB2: /1
    mmw 0x40021000 0x01000000 0x00000000    ;# PLL on
    sleep 1
    mmw 0x40021008 0x00000003 0x00000000    ;# switch to PLL
    sleep 1

    adapter speed 4000

    qspi_init
}

本着先看懂再模仿的原则,先对文件语言进行了解:

image-20211021224043056.png

机译:

OpenOCD使用一个称为JimTcl的小型“Tcl解释器”。该编程语言提供了一个简单且可扩展的命令解释器。

本指南中提供的所有命令都是Jim Tcl的扩展。您可以将它们作为简单的命令使用,而无需了解有关Tcl的很多内容。或者,您可以使用它们编写Tcl程序。

你可以在Jim的网站上了解更多关于Jim的信息,https://jim.tcl.tk. 这里有一个积极响应的社区,如果您有任何问题,请加入邮件列表。Jim Tcl维护人员也潜伏在OpenOCD邮件列表中。

文件使用的是Jim-Tcl,无需过多了解细节,直接冲向Tcl Crash Course:

image-20211021224452411.png

浏览了一下,内容只有6页,开始逐句啃......

手册译文

image-20211021224804595.png

Tcl速成班

不是每个人都知道Tcl-这不是为了替代学习 Tcl,本章的目的是让您了解 Tcl 脚本的工作原理。

本章是为两类读者而写的:

(1) OpenOCD 用户需要更多地了解 Jim-Tcl 的工作原理,以便他们可以做一些有用的事情,以及 (2) 那些想要向 OpenOCD 添加新命令的用户。

image-20211021225006109.png

Tcl规则#1

有一个著名的笑话,它是这样说的:

  1. 规则1:妻子总是对的。
  2. 规则2:如果你不这么认为,请参见规则1

Tcl的等效说法如下:

  1. 规则#1:一切都是一个字符串
  2. 规则2:如果你不这么认为,请参见规则1

正如著名的笑话中所说,规则1的后果是深远的。一旦你理解了规则1,你就会理解Tcl。

image-20211022104909141.png

Tcl 规则#1b

还有第二对规则。

  1. 规则#1:控制流不存在。 只有命令

例如:经典的 FOR 循环或 IF 语句不是控制流项,它们是命令,Tcl 中没有控制流这样的东西。

  1. 规则#2:如果您不这么认为,请参阅规则#1

实际上发生的事情是这样的:按照惯例,有些命令的作用类似于其他语言中的控制流关键字。 其中一个命令是单词“for”,另一个命令是“if”。

image-20211022105045074.png

每个规则#1 - 所有结果都是字符串

每个 Tcl 命令都会导致一个字符串。 “导致”这个词是故意使用的。 没有结果只是一个空字符串。 记住:规则#1 - 一切都是字符串

image-20211022105226180.png

image-20211022105239612.png

Tcl 引用操作符

在 Tcl 脚本的生命周期中,有两个重要的时间段,区别很细微。

  1. 解析时间
  2. 评估时间

这里的两个关键项目是“引用的东西”在 Tcl 中的工作方式。 Tcl 有三个主要的引用结构,[方括号]、{花括号}和“双引号”

现在您应该知道 $VARIABLES 总是以 $ 符号开头。顺便说一句:要设置变量,您实际上使用命令“set”,就像在“set VARNAME VALUE”中一样,很像古老的 BASIC 语言“let x = 1”语句,但没有等号。

  • [方括号]

[方括号] 是命令替换。 它的操作很像 Unix Shell 的“反引号”。 [方括号] 操作的结果正好是 1 个字符串。 记住规则#1 - 一切都是一个字符串。 这两个语句大致相同:

# bash example 
X=‘date‘ 
echo "The Date is: $X" 
# Tcl example 
set X [date] 
puts "The Date is: $X"
  • “双引号”

“双引号”只是简单的引用文本。 $VARIABLES 和 [squarebrackets] 扩展到位 - 然而结果正好是 1 个字符串。 记住规则#1 - 一切都是字符串

set x "Dinner" 
puts "It is now [date], $x is in 1 hour"
  • {Curly-Braces}

{Curly-Braces} 很神奇:$VARIABLES 和[方括号] 会被解析,但不会被扩展或执行。 {Curly-Braces} 就像 BASH shell 脚本中的“单引号”运算符,增加了一个特性:{curly-braces} 可以嵌套,单引号不能。

注意:[date] 是一个不好的例子; 在撰写本文时,Jim/OpenOCD 没有日期命令。

image-20211022112515437.png

规则1/2/3/4的后果

规则1的后果是深远的。

标记化和执行。

当然,空格、空行和#注释行是按正常方式处理的。在解析脚本时,脚本文件中的每一行(多行)都会根据引用规则进行标记。标记化后,该行立即执行。

多行语句以一个或多个“仍然打开的”{花括号}结尾,最终在几行之后关闭。

image-20211022112845267.png

image-20211022113050840.png

命令执行

请记住前面的内容:Tcl中没有“控制流”语句。相反,有一些命令的作用与控制流操作符类似。

命令的执行方式如下:

  1. 将下一行解析为(argc)和(argv[])。
  2. 在表中查找(argv[0]),并调用其函数。
  3. 重复此操作,直到文件结束。

它的工作原理是这样的:

for(;;){
    ReadAndParse( &argc, &argv ); 
    cmdPtr = LookupCommand( argv[0] ); 
    (*cmdPtr->Execute)( argc, argv );
}

当解析命令“proc”(创建一个过程函数)时,它在命令行上获得3个参数。

1是 proc (function)的名称,2是参数列表,3是函数体。不是用词的问题: LIST 和 BODY。PROC 命令将这些项目存储在某个表中,以便“LookupCommand ()”可以找到它

image-20211022113444762.png

FOR 命令

最有趣的命令是 FOR 命令。 在 Tcl 中,FOR 命令通常在 C 中实现。记住,FOR 是一个命令,就像任何其他命令一样。 当解析包含 FOR 命令的 ascii 文本时,解析器会生成 5 个参数字符串,(如果有疑问:请参阅规则 #1)它们是:

  1. ascii 文本 \'for\'
  2. 开始文本
  3. 测试表达式
  4. 下一个文本
  5. 正文文本

有点让你想起“main(int argc, char **argv)”,不是吗? 记住规则#1 - 一切都是一个字符串。 关键是:通常许多这些参数都在 {curly-braces} 中 - 因此里面的变量直到以后才被扩展或替换。

请记住,每个 Tcl 命令看起来都像 C 中经典的“main(argc, argv)”函数。在 JimTCL 中 - 它们实际上是这样的:

int
MyCommand( Jim_Interp *interp, int *argc, Jim_Obj * const *argvs );

真正的 Tcl 几乎相同。 虽然较新的版本引入了字节码解析器和解释器,但在核心上,它仍然以相同的基本方式运行。

image-20211022114026054.png

image-20211022114119993-16348741421791.png

FOR 命令执行

要理解 Tcl,查看 FOR 命令可能是最有帮助的。记住,它是一个 COMMAND 而不是一个控制流结构。

在 Tcl 中有两个底层的 c 辅助函数。

记住规则 # 1-你是一个字符串。

第一个助手解析和执行在 ascii 字符串中找到的命令。命令可以用分号或换行符分隔。在解析时,通过引用规则扩展变量。

第二个助手将ascii字符串作为数值表达式进行求值,并返回一个值。

下面是如何实现FOR命令的示例。下面的伪代码不显示错误处理。

void Execute_AsciiString( void *interp, const char *string ); 
int Evaluate_AsciiExpression( void *interp, const char *string ); 
int MyForCommand( void *interp, int argc, char **argv ){
    if( argc != 5 ){ SetResult( interp, "WRONG number of parameters"); 
    return ERROR;
    } 
    // argv[0] = the ascii string just like C
    // Execute the start statement. 
    Execute_AsciiString( interp, argv[1] );
    // Top of loop test 
    for(;;){ 
        i = Evaluate_AsciiExpression(interp, argv[2]); 
        if( i == 0 ) 
            break;
        // Execute the body 
        Execute_AsciiString( interp, argv[3] );
        // Execute the LOOP part 
        Execute_AsciiString( interp, argv[4] );
    }
    // Return no error 
    SetResult( interp, "" ); 
    return SUCCESS;
}

所有其他命令 IF、 WHILE、 FORMAT、 PUTS、 EXPR 都以相同的基本方式工作。

image-20211022115019117.png

image-20211022115031609.png

OpenOCD Tcl的使用
23.6.1源和查找命令

哪里:在许多配置文件中

示例:source [find FILENAME]

记住解析规则

  1. find 命令在方括号中,执行时带参数FILENAME。 它应该找到并返回具有该名称的文件的完整路径; 它使用内部搜索路径。 RESULT 是一个字符串,它被替换到命令行中以代替括号中的 find 命令。 (不要尝试使用包含“#”字符的 FILENAME。该字符开始 Tcl 注释。)
  2. 使用生成的文件名执行source命令; 它读取文件并作为脚本执行。

image-20211022120221601.png

格式化命令

哪里:一般发生在很多地方。

Tcl 没有像 printf() 这样的命令,而是有format,这实际上更像 sprintf()

例子:

set x 6 
set y 7 
puts [format "The answer: %d" [expr $x * $y]]
  1. SET 命令创建 2 个变量,X 和 Y。
  2. double [nested] EXPR 命令执行数学运算

EXPR 命令产生字符串形式的数值结果。

参考规则#1

  1. 执行格式命令,产生单个字符串

参考规则#1。

  1. PUTS 命令输出文本。

image-20211022120551494.png

image-20211022120606033.png

正文或内联文本

哪里:各种 TARGET 脚本。

#1 Good 
    proc someproc {} { 
        ... multiple lines of stuff ...
    } 
    $_TARGETNAME configure -event FOO someproc
#2 Good - no variables 
    $_TARGETNAME configure -event foo "this ; that;"
#3 Good Curly Braces 
    $_TARGETNAME configure -event FOO { 
        puts "Time: [date]"
    }
#4 DANGER DANGER DANGER 
    $_TARGETNAME configure -event foo "puts Time: [date]"
  1. $TARGETNAME是一个OpenOCD变量约定。$TARGETNAME表示上次创建的目标,每次值的修改都会创建一个新的目标。记住解析规则。 当解析 ascii 文本时,$TARGETNAME 变成一个简单的字符串,目标的名称恰好是一个 TARGET(对象)命令。
  2. -event参数的第二个参数是TCLBODY

有4个示例:

  1. TCLBODY是一个简单的字符串,恰好是一个proc名称
  2. TCLBODY 是由分号分隔的几个简单命令
  3. TCLBODY是一个多行{curly brace}带引号的字符串
  4. TCLBODY是一个字符串,其中包含展开的变量。

最后,当目标事件 FOO 发生时,将计算 TCLBODY。方法 # 1和 # 2在功能上是相同的。方法 # 3和 # 4更有趣。

什么是 TCLBODY?

记住解析规则。在案例#3中,{花括号}表示$VARS和[方括号]在事件发生后展开,并对文本进行求值。在第4种情况下,它们在执行“目标对象命令”之前被替换。这在替换$TARGETNAME的同时发生。万一发生这种情况,日期永远不会改变。{BTW:[date]是一个糟糕的例子;在本文中,Jim/OpenOCD没有date命令}

image-20211022121645056.png

全局变量

哪里: 在编写自己的 PROC 时,您可能会发现这一点

简而言之,在 PROC 中,如果您需要访问全局变量,您必须这样说。参见“ upvar”。例子:

proc myproc { } { 
    set y 0 #Local variable Y 
    global x #Global variable X 
    puts [format "X=%d, Y=%d" $x $y]
}

image-20211022132418269.png

image-20211022132428486.png

其他 Tcl 技巧

动态变量创建

# Dynamically create a bunch of variables. 
for { set x 0 } { $x < 32 } { set x [expr $x + 1]} { 
    # Create var name 
    set vn [format "BIT%d" $x] 
    # Make it a global 
    global $vn 
    # Set it. 
    set $vn [expr (1 << $x)]
}

动态过程/命令创建

# One "X" function - 5 uart functions. 
foreach who {A B C D E}
    proc [format "show_uart%c" $who] { } "show_UARTx $who"
}

参考代码解析

阅读过了Tcl速成章节,再结合已知的知识,参考代码的很多部分都可以看懂了。

#选择调试器接口脚本为stlink.cfg
source [find interface/stlink.cfg]
#选择接口为hla_swd
transport select hla_swd
#将QUADSPI标志位置位以便stm32l4x.cfg文件中使用
set QUADSPI 1
#选择目标芯片脚本为stm32l4x.cfg
source [find target/stm32l4x.cfg]

下面的代码定义了一个名为qspi_init的函数,虽然名字上面很明显的说明了是用于QSPI的初始化,不过要想搞懂里面干了什么还需要一点其他的内容配合:ST寄存器边界地址表寄存器地图以及IO复用表

RCC:

image-20211022170714824.png)

image-20211022170756020.png

GPIOE:

image-20211022170935958.png

image-20211022171003635.png

image-20211022171030222.png

image-20211022171057865.png

QUADSPI:

image-20211022172639014.png)

image-20211022172735645.png

image-20211022172758210.png

image-20211022172853316.png

image-20211022172820659.png

PORTE的IO复用表:

image-20211023105744359.png

一些目标指令

mdd,mdw,mdh,mdb:

memory display doublewords(64bit)/words(32bit)/halfwords(16bit)/bytes(8bit)

mwd,mww,mwh,mwb:

memory write doublewords(64bit)/words(32bit)/halfwords(16bit)/bytes(8bit)

两类指令的格式都是:

mdd [phys] addr [count]

mrw,mmw:

memory read/modify word

指令格式:

mrw/mmw reg setbits clearbits

proc qspi_init { } {
    #定义全局变量a(没看出来有啥用
    global a
    #将RCC_AHB2ENR的低9位置位
    #使能GPIOA到GPIOI的时钟
    mmw 0x4002104C 0x000001FF 0
    #将RCC_AHB3ENR的QSPIEN位置位
    #使能QSPI时钟
    mmw 0x40021050 0x00000100 0
    #等待时钟开启
    sleep 1

    #将GPIOE_MODER寄存器的高12位隔位置位
    #将PIN10-15设置为模式10: Alternate function mode
    mmw 0x48001000 0xAAA00000 0x55500000
    #将GPIOE_OSPEED寄存器的高12位置位
    #将PIN10-15设置为速度等级11: Very high speed
    mmw 0x48001008 0xFFF00000 0x00000000
    #将GPIOx_AFRH寄存器的高24位隔位置位
    #将PIN10-15设置为AF10
    mmw 0x48001024 0xAAAAAA00 0x55555500

    #将QUADSPI_LPTR寄存器的第15位置位
    #指示FIFO满后QUADSPI等待4096个时钟周期
    mww 0xA0001030 0x00001000
    #将QUADSPI_CR寄存器的PSC=1,APMS、TOIE、TCEN位置位
    #Qspi时钟2分频、出现匹配项时自动轮询停止、使能超时中断、使能超时计数器
    mww 0xA0001000 0x01500008
    #设置QUADSPI_DCR寄存器的FSIZE位与CSHT位
    #FLASH大小为16M,片选引脚拉高2个时钟周期,SPImode0
    mww 0xA0001004 0x00170100
    #将QUADSPI_CR寄存器的最低位置位
    #使能QSPI
    mmw 0xA0001000 0x00000001 0

    #设置QUADSPI_CCR寄存器:FMODE0b11、DMODE0b01、ADSIZE0b10、
    #ADMODE0b01、IMODE0b01、INSTRUCTION0b00000011
    #内存映射模式、单行数据、24位地址、
    #单行地址、单行指令、发送指令READ
    mww 0xA0001014 0x0D002503
}

接下来再来看看最后的复位初始化干了什么:

$_TARGETNAME configure -event reset-init {
    #设置FLASH_ACR的LATENCY为0b100
    #HCLK与FLASH之间等待4个状态
    mmw 0x40022000 0x00000004 0x00000003
    #等待设置生效
    sleep 1
    #将RCC_CR的HSION位置位
    #使能外部高速晶振
    mmw 0x40021000 0x00000100 0x00000000
    #设置RCC_PLLCFGR寄存器:PLLEN置位、PLLN0b0100100\
    #PLLM0b011、PLLSRC0b10->72MHz
    #PLLSAI3CLK输出使能、锁相倍频因子36、主PLL分频4、高速内部时钟源:PLL
    mww 0x4002100C 0x01002432
    #设置RCC_CFGR寄存器:STOPWUCK置位、SW0b01
    #HSI16振荡器选择为从停止时钟和CSS备份时钟唤醒
    #HSI16作为系统时钟
    mww 0x40021008 0x00008001
    #将RCC_CR的PLLON位置位
    #使能主锁相环
    mmw 0x40021000 0x01000000 0x00000000
    #等待设置生效
    sleep 1
    #设置RCC_CFGR寄存器:SW0b11
    #锁相环作为系统时钟
    mmw 0x40021008 0x00000003 0x00000000
    #等待设置生效
    sleep 1

    #设置仿真器频率
    adapter speed 4000

    #调用QSPI初始化函数
    qspi_init
}

类比参考代码自行撰写

全部看懂了之后,笔者开始模仿这段程序来撰写QSPI初始化函数和复位初始化函数:

source [find interface/jlink.cfg]

transport select swd

# increase working area to 96KB
set WORKAREASIZE 0x18000

# enable stmqspi
set QUADSPI 1

source [find target/stm32h7x.cfg]

# QUADSPI initialization
proc qspi_init { } {
    global a
    #RCC_AHB4ENR GPIOA-GPIOK
    mmw 0x580244E0 0x000007FF 0
    #RCC_AHB3ENR QSPI
    mmw 0x580244D4 0x00004000 0
    #等待
    sleep 1

    #PB3-AF9 PB6-AF10 PD11-AF9 PD12-AF9 PD13-AF9
    #PE2-AF9

    #GPIO模式设置
    mmw 0x58020400 0x00002080 0x00001040
    mmw 0x58020C00 0x0A800000 0x05400000
    mmw 0x58021000 0x00000020 0x00000010
    #GPIO输出速度
    mmw 0x58020408 0x000030C0 0x00000000
    mmw 0x58020C08 0x0FC00000 0x00000000
    mmw 0x58021000 0x00000030 0x00000000
    #GPIO复用功能
    mmw 0x58020420 0x0A009000 0x05006000
    mmw 0x58020C24 0x00999000 0x00666000
    mmw 0x58021020 0x00000900 0x00000600

    #QSPI
    mww 0x52005030 0x00001000
    mww 0x52005000 0x01500008
    mww 0x52005004 0x00170100
    mmw 0x52005000 0x00000001 0

    mww 0x52005014 0x0D002503
}

$_TARGETNAME configure -event reset-init {
    mmw 0x40022000 0x00000004 0x00000003
    sleep 1
    #CR
    mmw 0x58024400 0x00000001 0x00000000
    #PLLCFGR
    mww 0x5802442c 0x01FF0009
    #CFGR
    mww 0x58024410 0x00000019
    #CR enPLLON
    mmw 0x58024400 0x01000000 0x00000000
    sleep 1
    #CFGR
    mmw 0x58024410 0x0000001B 0x00000000
    sleep 1

    adapter speed 2000

    qspi_init
}

修改链接脚本

修改链接脚本的第一步是了解链接脚本,笔者拜读了链接脚本的官方手册,将其中的部分内容进行了翻译并且记录在了这篇文章中。

阅读之后大致了解了应该如何对链接脚本进行修改,于是做出了如下修改:

在MEMORY命令中添加外部FLASH:

/* Specify the memory areas */
MEMORY
{
  FLASH (rx)     : ORIGIN = 0x08000000, LENGTH = 128k
  ExFlash(rx)   : ORIGIN = 0x90000000, LENGTH = 8M
  DTCMRAM (xrw)  : ORIGIN = 0x20000000, LENGTH = 128K
  RAM_D1 (xrw)   : ORIGIN = 0x24000000, LENGTH = 512K
  RAM_D2 (xrw)   : ORIGIN = 0x30000000, LENGTH = 288K
  RAM_D3 (xrw)   : ORIGIN = 0x38000000, LENGTH = 64K
  ITCMRAM (xrw)  : ORIGIN = 0x00000000, LENGTH = 64K
}

然后在内存描述中加入外部FLASH的代码段:

.extext :
  {
    . = ALIGN(4);
    extext_start = .;
    /*files goes into external flash*/

    extext_end = .;
    . = ALIGN(4);
  } >ExFlash

然后只需要将需要放在外部FLASH的代码放到extext段即可。

开心的编译然后烧录,编译是过了,但是烧录报错了。

报出的错误是“Bank is invalid”,猜测是由于QSPI的初始化有问题导致的。

关于猜测的过程:

首先是查阅了ST官方的一本手册:AN5188,大致了解了一下XiP和BootROM两种启动方式,萌生了移植ST官方例程的想法,不过后面想了想决定还是没有移植。

之后向一位有经验的学长请教了一下相关的问题,和学长聊了很多,于是对我现在遇到的问题以及很多之前的疑点都有了一些了解:

  • OpenOCD里面开了QSPI的作用是什么?

OpenOCD烧录片外FLASH的过程是这样的:先向芯片中写入一份程序,作用大概是将单片机作为一个下位机转发数据通过QSPI写入FLASH,因此要开QSPI。

OpenOCD在整个烧录的过程中其实作为一个服务器,我们可以通过telnet或者gdb预期进行通信,因此我们可以开启windows的Telnet服务来连接OpenOCD的服务器对硬件进行命令行方式的调试。

  • 来看看我在调试的时候发现的惊喜:

通过OpenOCD的探针对STM32H750的内部FLASH进行检测得到的FLASH info让然喜出望外:

image-20211221225206729.png

就是说内部FLASH其实有整整2M的大小,而这2M的空间前1M是可以使用的,后面1M是被写保护的,换句话说:H7其实有1M的空间可以用。

上一篇
下一篇