分享一下我对官方ISP DEMO的理解
<p><a href="forum.php?mod=attachment&aid=112794" title="attachment"><img src="/source/plugin/zhanmishu_markdown/template/editor/images/upload.svg" alt="upload" /> 附件:Release.zip</a></p><h2><a href="forum.php?mod=attachment&aid=112795" title="attachment"><img src="/source/plugin/zhanmishu_markdown/template/editor/images/upload.svg" alt="upload" /> 附件:stc_isp_demo-0.0.1.zip</a></h2>
<h2>-1. github仓库</h2>
<p>stc_isp_demo</p>
<h2>0. STC CPU 启动过程</h2>
<p>上电后,执行STC自带内部ISP程序,没有检测到上位机要求下载的串口指令时,转入用户程序.</p>
<p><code>PC=0000H</code>,即从 <code>ROM</code>空间第 <code>0</code>字节取指令执行(也可以类比为 <code>stm32</code> 的 <code>Reset_Handler</code> 即复位中断入口),一般这里是 <code>02 addr16</code> 即 <code>LJMP addr16</code>长跳转指令,<code>addr16</code> 处一般是 <code>STARTUP.A51</code> 的内容,<br />
如初始化 <code>idata</code>, 设置 <code>sp</code> 等,然后执行 <code>LJMP ?C_START</code> 跳转到用户的 <code>main</code> 函数.</p>
<p>此时我们可以接管 <code>CPU</code>了,可以编写一个专用的 <code>BOOTLOADER</code> 程序来进行 <code>UART/USB/CAN</code> 等通信, 以 <code>IAP</code> 更新 <code>USER_APP</code>——真正的业务程序,以设置标记、检测某个 <code>PIN</code>、检测 <code>USER_APP</code> 是否合法等方式来决定是要跳转到 <code>USER_APP</code> 还是继续停留在 <code>BOOTLOADER</code> 等待上位机更新.</p>
<p>有两处重点:</p>
<ol>
<li>合理划分 <code>BOOTLOADER/USER_APP</code> 的空间</li>
<li>将中断重定向到 <code>USER_APP</code> 空间</li>
</ol>
<p>其中1可以使用 <code>STC</code> 的官方下载软件 <code>AiCube-ISP</code> 来设置用户 <code>EEPROM</code> 大小,</p>
<p>2可以用汇编在原中断地址入口(中断入口地址是芯片厂家决定的,不过一般都是在8051中断地址的扩展和小改)处写一条 <code>LJMP #USER_ISR_ADDR</code> 来跳转到 <code>USER_APP</code> 的中断函数.</p>
<p>下面我将在 <code>STC</code> 官方示例 <code>STC-Official-user-UART-ISP-bootloader-demo-STC8H8K64U-series</code> 的基础上逆向其串口协议、修改代码风格、增添一些功能.</p>
<h2>1. common.h</h2>
<pre><code class="language-c">/**
* chip: STC8H8K64U
* ram: 256B idata, 8KB xdata
* flash: 64KB
* - 4KB for bootloader
* - 60KB for application
*/
#ifndef __COMMON_H__
#define __COMMON_H__
// #define MAIN_Fosc 11520000UL
// #define MAIN_Fosc 12000000UL
// #define MAIN_Fosc 22118400UL
#define MAIN_Fosc 24000000UL
#define STC_RAM_SIZE 0x2000// STC8H8K64U has 8KB xdata
#define LDR_SIZE 0x1000 // bootloader flash space = 4KB
#define LDR_VERSION 0x0100// bootloader version 1.0
#define DFU_TAG 0x12ABCD34UL// force DFU mode
#define DFU_ADDR (STC_RAM_SIZE - sizeof(DFU_TAG))
#endif /* __COMMON_H__ */
</code></pre>
<p>由于 <code>8H8K64U</code>的主频是 <code>AiCube-ISP</code> 设置的,所以 <code>BOOTLOADER</code> 和 <code>USER_APP</code> 只能使用相同的主频.</p>
<p><code>FLASH</code> 地址划分也是 <code>AiCube-ISP</code> 设置的,所以这里先定义好 <code>LDR_SIZE</code> 即 <code>BOOTLOADER</code> 占用的 <code>FLASH</code> 空间大小,方便 <code>BOOTLOADER</code> 更新 <code>isr</code> 重映射计算和 <code>USER_APP</code> 的 <code>INTVECTOR/CLASSES</code> 计算.</p>
<p>由于使用了 <code>KEIL</code>自带的 <code>STARTUP.A51</code>, <code>XDATALEN EQU 0</code>,所以芯片软复位时不会初始化 <code>XDATA</code>,即可以在 <code>8KB RAM</code> 的结尾即 <code>DFU_ADDR</code>处设置一个 <code>dfuflag</code>,一旦 <code>USER_APP</code> 检测到了某个引脚变化、收到了串口指令等任何要触发更新 <code>USER_APP</code>的动作,<code>USER_APP</code> 即可设置 <code>dfuflag = DFU_TAG</code>并复位,<code>BOOTLOADER</code> 检测到 <code>dfuflag == DFU_TAG</code> 则停留在 <code>BOOTLOADER</code> 等待上位机下载指令而暂时不跳转到 <code>USER_APP</code>.</p>
<h2>2. BOOTLOADER</h2>
<p><code>BOOTLOADER</code> 要实现:</p>
<ol>
<li>重映射 <code>ISR</code></li>
<li>检测是否要跳转到 <code>USER_APP</code></li>
<li>接收上位机下载指令更新 <code>USER_APP</code></li>
</ol>
<h3>2.1 ISR 重映射</h3>
<p>为了方便调整 <code>FLASH</code> 空间划分,编写了 <code>update_isr.sh</code> :</p>
<pre><code class="language-bash">#!/bin/bash
set -e
CURRENT_DIR=$( cd "$(dirname "${BASH_SOURCE}")" ; pwd -P )
cd ${CURRENT_DIR}
COMMON_H=${CURRENT_DIR}/../common.h
ISR_ASM=${CURRENT_DIR}/src/isr.asm
# parse LDR_SIZE from COMMON_H like `#define LDR_SIZE 0x1000// bootloader flash space`
LDR_SIZE=$(grep -oP '#define LDR_SIZE \K+' ${COMMON_H})
echo "LDR_SIZE=${LDR_SIZE}"
# convert the LDR_SIZE from `0xXXXX` to `XXXXH`
LDR_SIZE_HEX=$(printf "%04XH" ${LDR_SIZE})
echo "LDR_SIZE_HEX=${LDR_SIZE_HEX}"
# replace LDR_SIZE in ISR_ASM like `LDR_SIZE EQU 1000H`
sed -i "s/LDR_SIZE EQU .*/LDR_SIZE EQU ${LDR_SIZE_HEX}/" ${ISR_ASM}
</code></pre>
<p>可以使用 <code>git-bash</code> 调用,来从 <code>common.h</code> 处定义的 <code>LDR_SIZE</code> 计算重映射 <code>ISR</code> 的基地址并更新 <code>isr.asm</code>:</p>
<pre><code class="language-ASM">LDR_SIZE EQU 1000H
MAPISR MACRO ADDR
CSEG AT ADDR
LJMP LDR_SIZE + $
ENDM
MAPISR0003H
MAPISR000BH
MAPISR0013H
...
END
</code></pre>
<h3>2.2 跳转 USER_APP</h3>
<p>应跳转时执行:</p>
<pre><code class="language-c">((void(code *)())(LDR_SIZE))(); // LJMP #LDR_SIZE, from here the CPU is running application code
</code></pre>
<p>即可</p>
<h3>2.3 串口 IAP</h3>
<p>协议参考 protocol.h 和 protocol.txt</p>
<h2>3. USER_APP</h2>
<p>重点是在编译时设置 <code>INTVECTOR</code> 即中断向量基地址为 <code>LDR_SIZE</code>,在链接时设置 <code>CLASSES</code>,将生成的代码放在 <code>LDR_SIZE</code>之后.</p>
<p>来看一下 <code>DEMO_APP.HEX</code><br />
<img src="data/attachment/forum/202508/22/173237cokglo1md0vm1oh0.png" alt="DEMO_APP.HEX" title="image.png" /></p>
<p>生成的第一条指令位于 <code>0000H</code>, 机器码为 <code>02 100EH</code>,所以我前面说可以类比为 <code>STM32</code> 的 <code>Reset_Handler</code>,但51并不把 <code>0000H</code>认为是复位中断入口地址,仅仅是上电第一条指令的取指地址.</p>
<p>可以看到 <code>1003H</code> 为 <code>INT0</code> 中断,由于 <code>DEMO_APP</code>没写该中断,指向了错误的地址 <code>0800H</code>,不知道 <code>C51</code> 是怎么处理的.<br />
<code>100BH</code> 为 <code>TIMER 0</code> 中断,指向 <code>111B</code> 是正确的.</p>
<p>这种偏移量分布的 <code>HEX</code>是不能直接交给 <code>BOOTLOADER</code> 来烧写的,<code>BOOTLOADER</code> 的 <code>IAP</code> 操作基地址是 <code>0</code>,会被映射到真实 <code>FLASH</code>空间的 <code>LDR_SIZE</code>处,因此要先将 <code>HEX</code>转换为 <code>bin</code>格式,且将第一行的位于 <code>0000H</code> 的 <code>02 10 0E</code> 代码移动到 <code>1000H</code> 处,与后面的组合起来,再整体前移 <code>1000H</code>,就得到了可以用来烧写的 <code>bin</code> 数据.</p>
<h2>4. PROGRAMMER</h2>
<p>有了理论+协议,就可以编写一个自己的烧录软件了,当然只能用于已经烧录好 <code>BOOTLOADER</code> 程序的情况.</p>
<p>点击 <code>Open</code> 打开 <code>DEMO_APP.HEX</code>, 转换为 <code>bin</code> 后是如下分布:</p>
<p><img src="data/attachment/forum/202508/24/225713ig08dthdjct60t8s.png" alt="image2.png" title="image2.png" /><br />
<img src="data/attachment/forum/202508/24/225712wirzk1ridkk7ztod.png" alt="image3.png" title="image3.png" /></p>
<p>再点击 <code>Patch</code> 将 <code>1003H</code> 处的代码整体前移 <code>1000H</code> ,就得到了可以直接烧写的数据:</p>
<p><img src="data/attachment/forum/202508/24/225713pamne6e6nwf6ws6j.png" alt="image4.png" title="image4.png" /></p>
<p>连接串口,<code>Erase All</code> 再 <code>Program</code>, <code>Reboot</code>,<code>USER_APP</code> 可以正常运行.</p>
页:
[1]