前面2节讲述了dsPIC33E的Flash存储结构以及Hex格式,接下来开始讲述如何编写Bootloader下位机和上位机。

本节讲述下位机的设计,考虑到执行效率和烧录时间,我们将主要解析工作放到上位机中,因为PC的执行速度远高于MCU。在上位机中,我们将Hex文件解析成一组一组的地址和数据组合,然后将一个地址和数据打包发送到Bootloader,Bootloader将其写入对应地址内。

我们先将地址空间分割成三段区域:

0x000000-0x0007FF: 第一段为起始跳转、复位和中断向量表

0x000800-0x0071FF: 第二段为Bootloader,此处预留0x6400长度,为了以后扩充,多预留了一点,后面再空一点点。

0x008000-0x02A800: 第三段为User App,注意最后一页(包含配置字)不要使用,擦除时会导致代码保护清零。

分别在Bootloader和AfterBootloader(即User App)项目中添加gld文件(从xc16安装文件夹下复制到项目目录下),修改对应的程序地址(修改program (xr)的起始地址和长度),这样编译时,会限制程序起始和结束地址。

以下我们将使用具体项目来说明,使用的芯片为dsPIC33EP256GP506

示例项目下载:https://download.csdn.net/download/u010875635/10819819

Bootloader中,最重要的就是2个功能,一是通信,二是Flash读写。由于不能使用中断,防止中断向量表与User App的冲突,通信都采用查询模式。开发板正好有现成的串口,本例采用串口通信,考虑到后面可能会使用CAN,因此核心数据长度为8bytes,串口本身校验功能差,我们再在前后分别增加2个字节,总计12个字节。

此例总共设有5中命令,兼容Bootloader和User App,分别为:EntryBootloader(User App中可用)、Reset、Data、DataEnd、CheckBootloader、Erase。

EntryBootloader,Bootloader不适用,User App中可用,由上过机发送过来的命令,用于在User App中复位到Bootloader,以便于进行代码烧写工作;Bootloader启动时会向上位机发送EntryBootloader回馈,表示进入Bootloader。

Reset,Bootloader中可用,上位机发送过来的命令,用于烧写完毕后,重启系统,User App启动时会向上位机发送Reset回馈表示已经于User App。

Data,接收到的为数据,由上位机发送过来,Bootloader将其写入flash。

DataEnd,上位机发送数据完毕,告诉Bootloader系统可以复位了。

CheckBootloader,上位机发送的命令,用于检查是否在Bootloader中,若在则回馈,若不再不回馈,上位机有超时判断。

Erase,上位机发送命令,下位机接收到命令后,会擦除所有用户程序相关的存储区,包括复位和中断向量表。

#include "McuDrivers/uart/uart.h"
#include "McuDrivers/system/system.h"
#include "McuDrivers/flash/DataRecord.h"
#include "McuDrivers/gpio/gpio.h"
#include "McuDrivers/timer/timer.h"
#include "Protocols/Bootloader.h"
#include "Protocols/UserAppFlash.h"
#include "McuDrivers/flash/InnerFlash.h"uint8_t receiveData[12]={0},dataIndex=0;void Uart_Tasks();
uint8_t Time_Tasks();
uint8_t Tick_Tasks();int main(void)
{    uint8_t run=1;System_Initialize_g();Uart_Initial_g(115200);System_Delay_MS_g(100);GPIO_LED_D4_Enable_g();GPIO_LED_D3_Enable_g();while(run){Uart_Tasks(); //接收数据处理run = Tick_Tasks();}while(1){Uart_Tasks(); //接收数据处理}return 0;
}uint16_t countTick = 0;
uint8_t Tick_Tasks()
{countTick++;//晶振为80MHz//每隔800大约1msif(countTick==800){countTick=0;return Time_Tasks();}return 1;
}uint16_t countTimer=0;
uint8_t Time_Tasks()
{countTimer++;if(countTimer==4000){BOOTLOADER_GOTO_8000();return 0;}if(countTimer>4000)return 0;if(countTimer%500==0)GPIO_LED_D3_Toggle_g();return 1;
}//串口接收任务
void Uart_Tasks()
{if(U2STAbits.URXDA==1){uint8_t tmp = Uart_ReadByte();switch(dataIndex){case 0:if(tmp==g_Bootloader_Receive_StartFlag[0]) //起始标识1{receiveData[0] = g_Bootloader_Receive_StartFlag[0];dataIndex = 1;}elsedataIndex = 0;break;case 1:if(tmp==g_Bootloader_Receive_StartFlag[1]) //起始标识2{receiveData[1] = g_Bootloader_Receive_StartFlag[1];dataIndex = 2;}elsedataIndex = 0;break;case 10:if(tmp==g_Bootloader_Receive_EndFlag[0]){receiveData[dataIndex] = g_Bootloader_Receive_EndFlag[0];dataIndex = 11;}elsedataIndex = 0;break;case 11:if(tmp==g_Bootloader_Receive_EndFlag[1]){receiveData[dataIndex] = g_Bootloader_Receive_EndFlag[1];AfterBootloader_CmdType result = Bootloader_DataParse(receiveData,12);//Uart_WriteByte_g(result);switch(result){case EntryBootloader:break;case Reset:BOOTLOADER_SOFTWARE_RESET();break;case Data: //数据countTimer = 4001; //出现数据即不跳转到用户程序,避免烧录用户程序时出现异常//UserFlash_EraseIvtAndUserAppBlock();UserFlash_DataParseAddrData(receiveData,12);Uart_WriteBytes_g(g_Bootloader_DataReponse,12);GPIO_LED_D3_Toggle_g();break;case DataEnd: //数据结束GPIO_LED_D4_Enable_g();GPIO_LED_D3_Enable_g();Uart_WriteBytes_g(g_Bootloader_DataProgramEndReponse,12);BOOTLOADER_SOFTWARE_RESET(); //数据刷新完毕,复位break;case CheckBootloader: //回馈正处于Bootloader中Uart_WriteBytes_g(g_Bootloader_CheckBootloaderReponse,12);break;case Erase:UserFlash_EraseIvtAndUserAppBlock();Uart_WriteBytes_g(g_Bootloader_EraseFlashReponse,12);break;default:break;}}dataIndex = 0;break;default: receiveData[dataIndex] = tmp; dataIndex++;break;}}
}

在数据处理上,由于dsPIC33EP256GP506只支持双指令字编程,所以我们接收到2个数据才会判断然后写入flash,考虑到最后肯定是配置字,而此处配置字是无法写入的,所以不考虑数据个数是否是奇数(奇数会导致最后一个数据漏写)情况了。

另外为保证复位启动时,程序始终跳转到Bootloader,所以用户程序的第一个指令字会被屏蔽,始终指向Bootloader。


FlashAddr_t m_LastAddr={.Uint32Addr=0x000002,}; //要写入的首地址,默认为2,即第一次判断认为上一次是第二个,非第一个,不用填充上一个,防止第一次误判
OneInstruction_t dataForWrite[2]; //存储要写入的2个指令字,每个指令字2个16位组成//解析Hex文件
//0、1表示开头
//2表示类型
//3、4、5表示地址,高字节在前,最高字节无
//6、7、8、9表示数据,低字节在前
void UserFlash_DataParseAddrData(uint8_t *data, uint8_t length)
{FlashAddr_t addr;OneInstruction_t dataTmp;addr.Uint16Addr.HighAddr = data[3];addr.Uint16Addr.LowAddr = data[4]*256 + data[5];dataTmp.HighLowUINT16s.LowWord = data[6]+data[7]*256;dataTmp.HighLowUINT16s.HighWord = data[8] + data[9]*256;if(addr.Uint32Addr==0x00000000)dataTmp.UINT32 = 0x00040800; //发送过来的跳转指令不接受,默认总跳转到bootloader首地址if(addr.Uint16Addr.LowAddr%4==0) //本次地址是组内第一个数据{if(m_LastAddr.Uint16Addr.LowAddr%4==0) //上一次也是第一个,则先填充上一次第二个指令字,写入flash,然后本次重新开始{dataForWrite[1].UINT32 = 0xffffff; //上一组第二个指令字填充InnerFlash_WriteInstructionsToFlash(m_LastAddr,dataForWrite,2); //上一次数据组合后,写入2指令字}//上一次是第1个或者第2个,本次都要重新开始dataForWrite[0].UINT32 = dataTmp.UINT32; //本次为第一个指令字}else //本次地址是第二个{if(m_LastAddr.Uint16Addr.LowAddr%4==0) //上一次是第一个,要继续判断是否连续{if((addr.Uint32Addr-m_LastAddr.Uint32Addr)==2) //与上一次连续,填充到上一次的数据中,并写入2个指令字{dataForWrite[1].UINT32 = dataTmp.UINT32; //本次为第二个指令字InnerFlash_WriteInstructionsToFlash(addr,dataForWrite,2); //本次次数据组合后,写入2指令字}else //与上一次不连续,填充上一次的第二个指令字,然后填充本次的第一个指令字,同时两次共写入4个指令字{dataForWrite[1].UINT32 = 0xffffff; //上组第二个指令字填充InnerFlash_WriteInstructionsToFlash(m_LastAddr,dataForWrite,2); //上一次数据组合后,写入2指令字dataForWrite[0].UINT32 = 0xffffff; //填充本组第一个指令字dataForWrite[1].UINT32 = dataTmp.UINT32; //本组第二个指令字InnerFlash_WriteInstructionsToFlash(addr,dataForWrite,2); //本次次数据组合后,写入2指令字}}else//上一次也是第2个,将本次第一个指令字也填充,写入{dataForWrite[0].UINT32 = 0xffffff; //填充本组第一个指令字dataForWrite[1].UINT32 = dataTmp.UINT32; //本组第二个指令字FlashAddr_t addrTmp;addrTmp.Uint32Addr = addr.Uint32Addr-2; //键入地址要为本组第一个地址InnerFlash_WriteInstructionsToFlash(addrTmp,dataForWrite,2); //本次次数据组合后,写入2指令字,注意地址要减2}}//赋予本次地址m_LastAddr.Uint32Addr = addr.Uint32Addr;//为防止出现奇指令,所以//写入Flash,每4个一组,占2个指令字,第一个指令字-低字节、高字节,第二个指令字-低字节、高字节//Length必须为4的倍数数,双指令字写入模式//例如 data[4]={0xaa05,0x0011,0xaa06,0x0022};,存入0x004000,即0x004000存入0x11aa05,0x004002存入0x22aa06//InnerFlash_WriteTwoWordsFlash(addrHighLow,,m_DataTmp,8);}

Bootloader虽然可以承担烧录工作,但是首次还是需要人工用JTAG将Bootloader写入MCU的Flash中。若是希望第一次直将Bootloader和User App集成,仅烧录一次,可参考一下方式:

合并Hex(Bootloader和Use App)

1、编译好Bootloader.hex和UserApp.hex,并假定Bootloader的首地址为0x800,UserApp的首地址为0x8000。

2、复制一份UserApp.hex,命名为UserAppWithBootloader.hex。

3、使用我们的上位机打开UserAppWithBootloader.hex(由于最后有校验码,不建议文本编辑器修改),修改首行第一个地址0x00048000为0x00040800,并保存。

4、用文本编辑器打开Bootloader.hex,复制:101000....(一般在42行,地址÷2=0x800,包含此地址)之后到:020000040005F5(不包含)之前的,所有的Bootloader程序代码。

5、用文本编辑器打开UserAppWithBootloader.hex,在:020000040001F9(一般在42行,线性地址偏移地址=(0x000001<<16÷2)之前回车,将刚刚复制的Bootloader程序填充到此处。

动图演示:

Hex解析工具源码下载地址:https://download.csdn.net/download/u010875635/10819828

本节到此结束,下一节讲述Bootloader上位机。

【dsPIC33E】Bootloader(三)Bootloader下位机相关推荐

  1. 【飞思卡尔 MC9S12】BootLoader 下位机

    上一篇:[飞思卡尔 MC9S12]内部D-Flash模拟EEPROM 本篇讲述BootLoader下位机的开发. 刚到新公司第三天就接了一个项目,搞到现在才局部完成,更新比较慢了. 先上传源码比较实际 ...

  2. 基于CANoe的Bootloader上位机软件 下位机为飞思卡尔MC9S12G128MLL

    基于CANoe的Bootloader上位机软件 下位机为飞思卡尔MC9S12G128MLL ID:6850668870980676Max_Min

  3. STM32遥控小车下位机及硬件连接部分(Keil MDK5平台的C++编程)

    STM32蓝牙控制小车 简介 一.硬件总体介绍 1. L298N电机驱动模块 2. JDY-31蓝牙模块 3. 电源组成 4. 单片机 二.单片机程序介绍 1. main.c文件 2. bluetoo ...

  4. 【嵌入式开发】监测系统——用QT编写下位机

    [嵌入式开发]监测系统--用QT编写嵌入式下位机界面 一.登陆界面 二.自定义弹出式小键盘 2.1设计思路 2.2具体实现 三.通讯协议 四.检测界面 五.历史信息显示 六.总结 本文为嵌入式课程的课 ...

  5. Cortex-M3单片机的IAP在线升级上位机和下位机

    最近有个项目要做在线升级功能,我也是第一次做,把学习的过程总结下,希望能够帮助到 其他人吧.本篇博客主要介绍两个部分,下位机和上位机. 首先说下要实现功能: 1.上位机能够把APP的bin文件烧写进下 ...

  6. 移动机器人下位机软件

    文章目录 前言 一.串口通信部分 二.PWM调速 三.编码器数据获取 四.PID速度控制 五.里程计数据计算 总结 前言 移动机器人的控制系统软件部分分为上位机软件与下位机软件两部分,二者之间通过串口 ...

  7. 6.PMAC下位机-下位机编程

    PMAC的下位机编程包括三种程序:命令序列程序.运动(Program)程序和PLC程序. 如下图,PMAC中编写程序,直接在File->new新建文件会打开编辑器,所有文件为PMC后缀. 查看当 ...

  8. 打开单片机世界的大门——上位机控制下位机实例详解

    上位机控制下位机实例详解 一.基本概念 上位机与下位机 串口 数据表达 二.下位机程序 三.上位机程序 四.总结 一.基本概念 在开始讲解前,先来看几个基本概念,如果是有基础的大佬,请直接跳到下一节. ...

  9. C#上位机(数据校验发送指令来控制下位机)

    对于"数据校验发送指令来控制下位机"的上位机,该模块主要有三个: (1)串口扫描,获取串口号,通过打开按钮来控制按钮是否打开. (2)发送数据校验的函数,将数据发送个给相应的下位机 ...

最新文章

  1. Spring Boot内容概要
  2. Spring IoC — 基于XML的配置
  3. 图像特效——摩尔纹 moir
  4. 多线程数据下载(akshare)
  5. 个人对响应式布局的理解
  6. win10系统进入监护人模式的方法
  7. cloudera cdh5.13.0 vmware 快速安装
  8. 【英语学习】【医学】Unit 09 The Respiratory System
  9. BZOJ4868: [Shoi2017]期末考试
  10. 标准Android按钮具有不同的颜色
  11. 数据库系统概论 -- 第一章 绪论(知识点)
  12. 装系统弹出计算机丢失,重装系统时提示缺少硬盘驱动怎么解决
  13. 解决word或wps办公软件删除空白页后页面布局变乱问题
  14. u盘启动怎么修复计算机,电脑进不了系统怎么办?u启动u盘启动盘重建主引导记录mbr...
  15. 开启和关闭Windows远程管理(WinRM)
  16. Linux服务之DHCP服务篇(scp)
  17. 海思HI3751HiDPTAndroidV200R001 UNF 接口版本差异说明
  18. oracle rac mpp,DB2 purescale vs Oracle RAC
  19. 用HTML+CSS做一个漂亮简单的个人网页~个人相册介绍个人主页模板(6个页面) ~学生HTML个人网页作业作品~
  20. 无人机领域重大进展,即将实现空中充电

热门文章

  1. b+树时间复杂度_前端大神用的学习笔记:线段树和树状数组
  2. 8大软件供应链攻击事件概述
  3. 用xlwt和xlrd在不修改Excel单元格格式的情况下修改单元格内容
  4. C#获取当前时间毫秒值
  5. windows、linux操作系统下载地址
  6. 程序员修炼之路:算法的力量
  7. ET框架-16 ET框架登录账号请求逻辑编写(1)
  8. java计算机毕业设计进出货管理系统(附源码、数据库)
  9. MTK平台设置GPIO
  10. SQL2000客户端连接不上