51内核单片机实现Bootloader跳转到用户程序,要求两个程序都要支持中断

news2025/2/24 21:19:20

Flash空间规划

本文使用的单片机为笙科的A9129F6,Flash大小为64KB,其空间规划如下。

起始地址结束地址用途
0x00000x3fff

Bootloader程序

0x40000xefff

用户程序(APP程序)

0xf000

0xffff

存放设备配置信息

程序间跳转实现起来很简单,只需要使用函数指针就行了。
但是难点在于51
单片机的中断向量表不支持重定向,中断发生时只能固定从(0x0003+8n)处开始执行。
bootloaderapp都有自己的中断向量表,而中断发生时进入的始终是bootloader的向量表。
程序需要有一个标志变量(定义到xdata0地址处),用于判断当前执行的是bootloader还是APP程序。如果当前是在执行APP程序,那么中断发生后先是运行bootloader程序的向量表,判断这个标志变量后,再主动跳转到APP程序的中断向量表中执行。

Keil工程配置

第一步:
设置bootloader程序使用的Flash空间范围为0x0000-0x3fff,XDATA RAM空间范围为0x0001-0xffff
设置app程序使用的Flash空间范围为0x4000-0xefff,XDATA RAM空间范围为0x0001-0xffff

第二步:在app程序的启动文件中设置Reset Vector和Startup段的地址

第三步:设置编译bootloader程序时不自动产生中断向量表,设置编译app程序时产生的中断向量表的保存位置为0x4000

第四步:设置烧写bootloader程序和app程序时只擦除需要的扇区,而不是全片擦除(但是这样设置后烧完app程序,0号扇区仍然会被覆盖)

实现从Bootloader程序跳转到主程序

#include <A9129F6.h>
#include <LIB_Uti.h>
#include <stdio.h>
#include "macros.h"
#include "uart.h"

char uart_data;

#define APP_FLASH_ADDR 0x4000
#define VECTOR_TABLE (*(uint8_t xdata *)0x0000)

typedef void (code *Runnable)(void);

static void jump_to_application(void)
{
    Runnable run = (Runnable)APP_FLASH_ADDR;

    printf("Jump to application...\n");
    EA = 0;
    ES = 0;
    VECTOR_TABLE = 1;
    run();
}

int main(void)
{
    int i;

    VECTOR_TABLE = 0;
    EA = 1;

    uart_init();
    printf("Bootloader\n");

    while (1)
    {
        if (uart_data)
        {
            printf("Interrupt occurred\n");
            if (uart_data == '\r')
            {
                jump_to_application();
            }
            uart_data = 0;
        }

        printf("i=%d\n", i);
        i++;
        Delay10ms(50);
    }
}

汇编实现Bootloader中断向量表

中断向量表必须用汇编语言实现,不能用C语言实现。因为这涉及到保护现场和恢复现场。
假设bootloader和app程序里面都用到了UART中断,而其他中断(如TIMER0、RFINT和KEYINT)只有app程序在用。
新建一个名叫interrupt.a51的汇编文件,添加到工程中。内容如下:

    CSEG AT 0x0003
    LJMP 0x4003 ; INT0_ISR (重定向到APP程序相应的中断向量表上,下同)
    
    CSEG AT 0x000b
    LJMP 0x400b ; TIMER0_ISR
    
    CSEG AT 0x0013
    LJMP 0x4013 ; INT1_ISR
    
    CSEG AT 0x001b
    LJMP 0x401b ; TIMER1_ISR

    CSEG AT 0x0023
    LJMP UART_ISR
    
    CSEG AT 0x002b
    LJMP 0x402b ; TIMER2_ISR
    
    CSEG AT 0x003b
    LJMP 0x403b ; INT2_ISR
    
    CSEG AT 0x0043
    LJMP 0x4043 ; USBINT_ISR
    
    CSEG AT 0x004b
    LJMP 0x404b ; I2SINT_ISR

    CSEG AT 0x0053
    LJMP 0x4053 ; RFINT_ISR
    
    CSEG AT 0x005b
    LJMP 0x405b ; KEYINT_ISR

    CSEG AT 0x0063
    LJMP 0x4063 ; WATCHDOG_ISR
    
    CSEG AT 0x006b
    LJMP 0x406b ; I2C_ISR
    
    CSEG AT 0x0073
    LJMP 0x4073 ; SPI_ISR
    
    CSEG AT 0x0100              ; 自定义代码块
UART_ISR:
    ; 保护现场
    PUSH ACC                    ; 保存A寄存器的原有内容
    PUSH DPH                    ; 保存DPTR寄存器(高字节)的原有内容
    PUSH DPL                    ; 保存DPTR寄存器(低字节)的原有内容
    PUSH PSW                    ; 保存PSW(程序状态)寄存器的原有内容
    MOV PSW,#0x00               ; 清除PSW程序状态值
    
    MOV DPTR,#0x0000            ; DPTR寄存器赋值为0
    MOVX A,@DPTR                ; 从XDATA 0x0000地址处读取一个字节,存到A寄存器中
    CJNE A,#0x00,UART_ISR_APP   ; 如果A的值不等于0,则跳转到UART_ISR_APP标签上;否则不跳转,继续往下执行
    
    ; 恢复现场
    POP PSW                     ; 恢复PSW寄存器的原有内容
    POP DPL                     ; 恢复DPTR寄存器的原有内容
    POP DPH
    POP ACC                     ; 恢复A寄存器的原有内容
EXTRN CODE(BOOTLOADER_UART_ISR) ; 引用bootloader程序中的C语言函数
    LJMP BOOTLOADER_UART_ISR    ; 执行bootloader程序中的C语言函数,然后不返回了
UART_ISR_APP:
    POP PSW
    POP DPL
    POP DPH
    POP ACC
    LJMP 0x4023                 ; 执行APP程序中的C语言函数,然后不返回了
END

Bootloader中没有用到的中断,直接用CSEG AT和LJMP语句重定向到APP程序。无论APP程序用没用到,最好都写上。
Bootloader中用到了的中断,那就需要判断一下xdata 0x0000处的标志变量,再决定是执行bootloader的ISR,还是app的ISR。
如果xdata 0x0000=0,就执行bootloader的ISR,如果xdata 0x0000!=0,就执行APP的ISR。

根据A9129F6的芯片手册,0x0003是INT0中断的向量地址,0x000b是TIMER0中断的向量地址,……,0x0073是SPI中断的向量地址。
这些都属于bootloader的中断向量表空间,代码空间有限,应该只写一条LJMP跳转指令。
其他复杂的代码块要放在一个专门的区域内,在本文中是CSEG AT 0x0100。这个地址可以随意指定,在这个区域下可以放置多种中断的程序,要新添加其他中断的话不用再自己新建CSEG数据段了,直接复制UART_ISR:到LJMP 0x4023这段代码,然后再作相应修改,放到END语句前就行。

Bootloader程序中的中断服务函数(必须都要加上interrupt关键字):

/* UART interrupt handler */
void BOOTLOADER_UART_ISR(void) interrupt 4
{
    char c;
    extern char uart_data;

    if (RI)
    {
        c = SBUF;
        RI = 0;
        uart_data = c;
    }
}

APP程序中的中断服务函数(必须都要加上interrupt关键字):

/* UART interrupt handler */
void UART_ISR(void) interrupt 4
{
    char c;

    if (RI)
    {
        c = SBUF;
        RI = 0;
        console_receive(c);
    }
}

/* Timer 0 interrupt handler */
void TIMER0_ISR(void) interrupt 1
{
    TL0 = 0xd5;        // reload timer 0
    TH0 = 0xfb;
    TF0 = 0;           // clear timer 0 overflow flag
    systick_counter++; // increment microsecond counter

    led_process();
}

void RF_ISR(void) interrupt 10
{
    EIF = EIF_RFINTF; // RFINTF->0
    rf_flag = 1;
}

void KEYINT_ISR(void) interrupt 11
{
    EIF = EIF_KEYINTF; // clear interrupt flag
}

加上interrupt关键字的目的是为了保证函数代码以RETI汇编指令结尾。

特别注意:以后如果修改了APP程序的代码并重新编译,必须在烧写完APP程序后,再烧写一下bootloader程序,才能保证APP程序正常运行。也就是说改一次代码要烧写两次。

APP程序里面虽然也有自己的中断向量表,但是在中断发生时是先进入bootloader程序的中断向量表,然后再跳转到APP程序的中断向量表。
如果只烧写了APP程序,没有烧写bootloader程序,那么APP里面所有的中断服务函数都无法执行!
所以APP程序要判断一下bootloader程序到底有没有烧写,如果没有烧写,应该在串口中给出错误提示,然后停止执行程序。
判断的方法是看APP的中断服务函数到底能不能得到执行。见下面的systick_test函数。

static uint32_t systick_counter;

/* Get the microsecond counter */
uint32_t sys_now(void)
{
    return systick_counter;
}

/* Verify if this APP was started from the bootloader */
void systick_test(void) large
{
    int i = 0;
    uint32_t start;

    start = sys_now();
    while (sys_now() == start)
    {
        if (i == 30000)
        {
            printf("Please download the bootloader program before running this APP\n");
            i = -1;
        }
        else if (i >= 0)
        {
            i++;
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/362009.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【算法基础】一维前缀和 + 二维前缀和

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;【C/C】算法 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵 希望大佬指点一二 如果文章对你有…

大A社群丨全球宽基ETF轮动(GP02)

量化策略开发&#xff0c;高质量社群&#xff0c;交易思路分享等相关内容 『正文』 ˇ 大家好&#xff0c;今天我们分享股票社群第2期量化策略——ETF轮动。 根据我们在12月份预售投票情况看&#xff0c;大家还是比较倾向于技术多因子和ETF轮动&#xff0c;如下图所示&#…

googletest 笔记

什么是一个好的测试 1 测试应该是独立的和可重复的。调试一个由于其他测试而成功或 失败的测试是一件痛苦的事情。googletest 通过在不同的对象上 运行测试来隔离测试。当测试失败时&#xff0c;googletest 允许您单独运 行它以快速调试。 2 测试应该很好地“组织”&#xff0c…

循环、函数、对象——js基础练习

目录 一、循环练习 1.1 取款机案例 1.2 九九乘法表 1.3 根据数据生成柱形图 1.4 冒泡排序 1.6综合大练习 二、函数 2.1 转换时间案例 三、对象 1. 遍历数组对象 2. 猜数字游戏 3. 生成随机颜色 4. 学成在线页面渲染案例 一、循环练习 1.1 取款机案例 // 准备一个…

多 态

1多态的基本概念多态是C面向对象三大特性之一多态分为两类静态多态: 函数重载和运算符重载属于静态多态&#xff0c;复用函数名动态多态: 派生类和虚函数实现运行时多态静态多态和动态多态区别:静态多态的函数地址早绑定–--编译阶段确定函数地址动态多态的函数地址晚绑定–--运…

操作系统(day13)-- 虚拟内存;页面分配策略

虚拟内存管理 虚拟内存的基本概念 传统存储管理方式的特征、缺点 一次性&#xff1a; 作业必须一次性全部装入内存后才能开始运行。驻留性&#xff1a;作业一旦被装入内存&#xff0c;就会一直驻留在内存中&#xff0c;直至作业运行结束。事实上&#xff0c;在一个时间段内&…

usb闪存驱动器数据恢复该怎么进行?3个方法总结

“怎么办&#xff1f;我的USB驱动器不知道因为什么原因&#xff0c;里面的数据、文件都消失了。有没有什么方法在没有进行备份的情况下恢复从U盘丢失的数据&#xff1f;” USB驱动器作为最常用的存储移动设备&#xff0c;里面保存着各种文件数据。但是有时会出现损坏而导致数据…

麦克风分类汇总

1.麦克风分类汇总 1)按声电转换原理分为&#xff1a;电动式&#xff08;动圈式、铝带式&#xff09;&#xff0c;电容式&#xff08;直流极化式&#xff09;、压电式&#xff08;晶体式、陶瓷式&#xff09;、以及电磁式、碳粒式、半导体式等。 2)按声场作用力分为&#xff1a…

广域网技术(PAP和CHAP)

第十六章&#xff1a;广域网技术 随着经济全球化与数字化变革加速&#xff0c;企业规模不断扩大&#xff0c;越来越多的分支机构出现在不同的地域。每个分支的网络被认为一个LAN&#xff08;Local Area Network&#xff0c;局域网&#xff09;&#xff0c;总部和各分支机构之间…

Tr0ll1靶机训练

信息收集 主机探测 端口扫描 21,22,80端口开放通过浏览器访问并进行指纹识别&#xff0c;并没没有发现什么有用信息 测试 观察发现21端口开放&#xff08;ftp&#xff09;尝试进行匿名登录发现其中存在一个流量文件将其下载 并将文件用wirwshark打开&#xff0c;追踪其TCP流(…

451个PyPI包安装Chrome扩展以窃取加密信息

发现有超过450个恶意的PyPI Python软件包会安装恶意浏览器扩展&#xff0c;以劫持基于浏览器的加密钱包和网站进行的加密货币交易。自2022年11月首次启动后&#xff0c;至今仍在延续&#xff0c;从最初只有27个恶意的PyPI软件包&#xff0c;在过去几个月中到现在正大幅扩张。这…

RocketMQ事务消息

RocketMQ事务消息 RocketMq提供的一种高级消息类型&#xff0c;支持在分布式场景下面保障消息生产和本地事务的一致性 生产者将消息发送到服务端服务端将消息持久化成功后&#xff0c;向生产者返回ACK确认消息发送成功&#xff0c;此时消息状态为待投递,这种状态下的消息称之为…

2、MySQL5.7安装

前言&#xff1a;工具下载地址阿里云盘&#xff1a;MySQL&#xff1a;https://www.aliyundrive.com/s/o37N4pWdzyz提取码: xs12一、MySQL安装包下载MySQL官方网站下载速度太慢&#xff0c;这里推荐使国内的开源镜像站。推荐清华大学镜像站&#xff1a;https://mirrors.tuna.tsi…

如何通过一台 iPhone 申请一个 icloud 邮箱账号 后缀为 @icloud.com

总目录 iOS开发笔记目录 从一无所知到入门 文章目录需求关键步骤步骤后续需求 在 iPhone 自带的邮箱软件中添加账号&#xff0c;排第一位的是 iCloud 邮箱&#xff1a; 选 iCloud 之后&#xff1a; 提示信息是exampleicloud.com&#xff0c;也就是说是有icloud.com为域的邮箱…

前端学习第二阶段-第1、2章

01第一章 移动web网页开发课前导学 1-1 移动web网页开发课前导学 02第二章 H5C3进阶【v6.5】 2-1 vscode编辑器基本使用 01vscode 基本使用 02vscode插件安装 2-2 HTML5新增标签【复习】 01-什么是HTML5 02-HTML5新增标签 03-多媒体音频标签 04-多媒体视频标签 05-HTML5新增i…

智能小车PWM调速原理

电机驱动电路智能小车电机的驱动芯片采用L293D。L293D是一款单片集成的高电压、高电流、4通道电机驱动&#xff0c;设计用于连接标准DTL或TTL逻辑电平&#xff0c;驱动电感负载&#xff08;诸如继电线圈、DC和步进电机&#xff09;和开关功率晶体管等等。L293D有4个通道&#x…

APB总线详解及手撕代码

本文的参考资料为官方文档AMBA™3 APB Protocol specification文档下载地址&#xff1a; https://pan.baidu.com/s/1Vsj4RdyCLan6jE-quAsEuw?pwdw5bi 提取码&#xff1a;w5bi APB端口介绍介绍总线具体握手规则之前&#xff0c;需要先熟悉一下APB总线端口&#xff0c;APB的端口…

【跟我一起读《视觉惯性SLAM理论与源码解析》】第二章 编程及编译工具

23.2.21终于拿到六哥的新书 感觉很是不错&#xff0c;打算近期写一写心得之类的 废话不多说&#xff0c;直接开啃 PS&#xff1a;我的建议是阅读完十四讲后再来看这本书&#xff0c;效果应该会很不错。 因为第一章都是介绍之类的我觉得没什么整理的必要&#xff0c;所以直接来…

Netty高级应用之:编解码器与群聊天室开发

Netty高级应用之&#xff1a;编解码器与群聊天室开发 文章目录Netty高级应用之&#xff1a;编解码器与群聊天室开发Netty编解码器Java的编解码Netty编解码器概念解码器(Decoder)编码器(Encoder)编码解码器CodecNetty案例-群聊天室聊天室服务端编写聊天室客户端编写Netty编解码器…

ChatGPT爆火背后的真相:学编程已经成为必选项

这一阵最热门的话题&#xff0c;莫过于人工智能新选手——ChatGPT&#xff0c;在推出后只用了两个月就积累了1亿用户&#xff01;它的出现在科技圈掀起了一阵“惊涛骇浪”&#xff0c;有人称ChatGPT的意义&#xff0c;堪比当年蒸汽机的出现&#xff0c;它足以爆发新一轮的“工业…