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

这篇具有很好参考价值的文章主要介绍了51内核单片机实现Bootloader跳转到用户程序,要求两个程序都要支持中断。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Flash空间规划

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

起始地址 结束地址 用途
0x0000 0x3fff

Bootloader程序

0x4000 0xefff

用户程序(APP程序)

0xf000

0xffff

存放设备配置信息

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

Keil工程配置

第一步:
设置bootloader程序使用的Flash空间范围为0x0000-0x3fff,XDATA(也就是SRAM)空间范围为0x0001-0x1fff
设置app程序使用的Flash空间范围为0x4000-0xefff,XDATA SRAM空间范围为0x0001-0x1fff

bootloader跳转到app,汇编,单片机,嵌入式硬件,51单片机

bootloader跳转到app,汇编,单片机,嵌入式硬件,51单片机

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

bootloader跳转到app,汇编,单片机,嵌入式硬件,51单片机

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

bootloader跳转到app,汇编,单片机,嵌入式硬件,51单片机

bootloader跳转到app,汇编,单片机,嵌入式硬件,51单片机

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

bootloader跳转到app,汇编,单片机,嵌入式硬件,51单片机

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

#include <A9129F6.h>
#include <stdio.h>
#include "macros.h"
#include "systick.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;

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

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

        printf("i=%d, time=%lu\n", i, sys_now());
        i++;
        delay_ms(500);
    }
}

汇编实现Bootloader中断向量表

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

    CSEG AT 0x0003
    LJMP 0x4003 ; INT0_ISR (重定向到APP程序相应的中断向量表上, 下同)
    
    CSEG AT 0x000b
    LJMP 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                ; 自定义代码块
TIMER0_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,APP_TIMER0_ISR   ; 如果A的值不等于0,则跳转到APP_TIMER0_ISR标签上;否则不跳转,继续往下执行
    
    ; 恢复现场
    POP PSW                       ; 恢复PSW寄存器的原有内容
    POP DPL                       ; 恢复DPTR寄存器的原有内容
    POP DPH
    POP ACC                       ; 恢复A寄存器的原有内容
EXTRN CODE(BOOTLOADER_TIMER0_ISR) ; 引用bootloader程序中的C语言函数
    LJMP BOOTLOADER_TIMER0_ISR    ; 执行bootloader程序中的C语言函数,然后不返回了
APP_TIMER0_ISR:
    POP PSW
    POP DPL
    POP DPH
    POP ACC
    LJMP 0x400b                   ; 执行APP程序中的C语言函数,然后不返回了
    
UART_ISR:
    PUSH ACC
    PUSH DPH
    PUSH DPL
    PUSH PSW
    MOV PSW,#0x00
    
    MOV DPTR,#0x0000
    MOVX A,@DPTR
    CJNE A,#0x00,APP_UART_ISR
    
    POP PSW
    POP DPL
    POP DPH
    POP ACC
EXTRN CODE(BOOTLOADER_UART_ISR)
    LJMP BOOTLOADER_UART_ISR
APP_UART_ISR:
    POP PSW
    POP DPL
    POP DPH
    POP ACC
    LJMP 0x4023
END

Bootloader中没有用到的中断,直接用CSEG AT和LJMP语句重定向到APP程序,不用管APP程序用没用到。
Bootloader中用到了的中断,那就需要判断一下xdata 0x0000处的标志变量,再决定是执行bootloader的ISR,还是app的ISR。同样也不用管APP程序用没用到。
如果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关键字):

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

/* 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关键字):

/* 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();
}

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

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

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++;
        }
    }
}

有的人会有这样的疑问:C语言函数加了interrupt关键字之后会自动生成保护现场的代码,那汇编里面保护现场不就多余了吗?
其实并不是。仔细看汇编代码,中断发生时先进入的是汇编函数,在汇编函数里面先保护现场,再进行寄存器操作和条件判断,然后恢复现场后再跳转到C语言的带interrupt关键字的函数,然后C语言函数里面再保护现场和恢复现场。现场的确是保护和恢复了两次,但是这两次并不是重叠的,而是分开的。具体来说就是“汇编保护、汇编操作、汇编恢复、C保护、C操作、C恢复”的过程,并不是先保护两次再恢复两次,所以结论是汇编里面的保护现场并不多余。文章来源地址https://www.toymoban.com/news/detail-604837.html

到了这里,关于51内核单片机实现Bootloader跳转到用户程序,要求两个程序都要支持中断的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • AT89C51单片机实现单片机串口互动(中断方式,单片机--单片机,应答)

     说一下功能:客户机发送0x01到服务机 2服务单片机应答0xf2到客户机 3客户机接收到0xf2,发送信息153432这6个数字到服务机 4client发送完信息后发送0xaa结束通信 5server接收到0xaa后回复0xaa结束通信,从此老死不相往来 看代码: 服务端代码:    

    2024年02月13日
    浏览(53)
  • 51单片机实现流水灯

    我们之前说过可以控制管脚口电平来控制LED的亮灭,低电平灯亮,高电平灯灭,具体可以看我之前的博客。 P20~P27可以用八位二进制数来表示每一位的电平,第八位是P20,第一位是P27,那么11111110就代表P20亮,其他灭,11101111就代表P24亮,其他灭。流水灯的代码就可以写成这样

    2024年02月11日
    浏览(44)
  • 单片机设计基于51单片机的智能风扇控制系统设计与实现

      我们常见的电风扇一般只有四、五个风速档,用的是人工开关,而且并不是每个人家里都会有空调,或者在一些小型的工厂或者一些小型加工厂,这些地方都可能没有配备大型的中央空调系统这些东西,所以这些东西往往都会采用风扇这种小成本的东西来代替,但是不清楚

    2024年02月03日
    浏览(65)
  • C51单片机实现呼吸灯

         呼吸灯的效果是灯的亮度由暗缓慢变亮再缓慢变暗。      实现思路是改变小灯亮与暗在单位周期的占空比。如下图 具体代码如下:

    2024年02月11日
    浏览(48)
  • 51单片机实现矩阵键盘密码锁

    使用51单片机的矩阵键盘模块以及led1602显示屏,实现模拟密码锁。 当程序烧录到单片机中后,led1602屏幕会显示文字。 第一行会显示单词“PASSWORD”,第二行显示4个0,表示我们要写入的四位密码,每位默认为0。 矩阵键盘前两行与第三行的前两个分别代表输入1-9与0,第三行第

    2024年02月03日
    浏览(55)
  • 通过51单片机实现直流电机调速

    随着各种工业生产设备和机械设备的广泛使用,直流电机调速技术的研究和应用越来越受到人们的重视,具有广泛的应用前景。本项目通过51单片机实现直流电机调速功能,为实际工程应用提供一个可靠和有效的调速方案。 如果需要下载项目工程,可以去这里: https://blog.c

    2024年02月14日
    浏览(43)
  • 【【51单片机蜂鸣器实现起风了】】

    前面的有两个非常关键的点希望兄弟们明白 我一开始也失算了 这里兄弟们注意务必改成int 不然会超出 就会出现播放一半从头开始的情况 下面是两份起风了代码直接附上main.c 因为另外的其实和我之前说的模板都一样复制粘贴就行 为什么是2份因为什么ABCD大调我不懂啊 我看乐

    2024年02月16日
    浏览(116)
  • 51单片机点阵屏实现俄罗斯方块

    前言 一、关于点阵屏 二、俄罗斯方块代码部分 1.main主函数 2.按键扫描 3.方块生成 4.方块显示  5.方块下降   6.固定的方块显示 7.方块的左右移动 8.键值的判断与执行  9.方块的旋转   10.总代码 总结 这里采用的是清翔51单片机,通过独立键盘实现左右移动和旋转,通过8×8点

    2024年02月11日
    浏览(31)
  • 【51单片机】矩阵按键实现数码管显示

    闲的没事看一看玩一玩········· 矩阵按键 数码管是一种导体发光器件,其基本单元是发光二极管。按发光二极管单元连接方式可分为共阳极数码管和共阴极数码管 共阴极数码管是指将所有发光二极管的阴极接到一起形成共阴极的数码管,共阴极的数码管在应用时将公共

    2024年02月02日
    浏览(57)
  • 基于51单片机实现时间显示及闹钟设置

    本次为大学中的一次创新实验,当时老师叫我自己拿个单片机去玩,为了赶时间就做了个简单的时间显示和闹钟设置,因为比较简单所以也把代码附上了。 1.单片机 单片机内部内部资源:Flash——程序存储空间;RAM——数据存储空间;SFR——特殊功能寄存器。51单片机指的是

    2024年02月07日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包