为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

基于STM8的在线升级系统

2018-01-16 50页 doc 420KB 44阅读

用户头像

is_418164

暂无简介

举报
基于STM8的在线升级系统基于STM8的在线升级系统 摘 要 在嵌入式应用中,微控制器作为逻辑控制和数值计算的核心,在软件和硬件的支持下完成产品功能。产品硬件是通常不变的,它的更新频率非常低。产品的软件操作产品硬件来完成逻辑功能,它可以通过软件更新来增强产品功能和修复功能缺陷,它的更新频率相对于硬件而言高的多。 软件升级常用的方式有在线编程方式(ICP)和在应用编程(IAP),在线编程需要单独将MCU插入编程器,而在实际的嵌入式应用中,MCU已经嵌入产品硬件,单独拆除MCU是非常困难的,因此通常采用在在线编程的方式,经过MCU的外部通信接口进行软...
基于STM8的在线升级系统
基于STM8的在线升级系统 摘 要 在嵌入式应用中,微控制器作为逻辑控制和数值计算的核心,在软件和硬件的支持下完成产品功能。产品硬件是通常不变的,它的更新频率非常低。产品的软件操作产品硬件来完成逻辑功能,它可以通过软件更新来增强产品功能和修复功能缺陷,它的更新频率相对于硬件而言高的多。 软件升级常用的方式有在线编程方式(ICP)和在应用编程(IAP),在线编程需要单独将MCU插入编程器,而在实际的嵌入式应用中,MCU已经嵌入产品硬件,单独拆除MCU是非常困难的,因此通常采用在在线编程的方式,经过MCU的外部通信接口进行软件升级。 本文针对汽车车身网络系统的研究,设计一种基于CAN总线的在线升级系统,实现对汽车各种电控单元(ECU)进行在线升级。本文通过在STM8的ECU中实现了基于CAN总线通信的在线升级代码bootloader,通过IAP编程的字节编程实现对应用程序代码数据的更新。在实现在线升级底层代码过程中,将升级代码bootloader存放在用户启动代码区域(UBC)中,防止意外的升级代码破坏,通过固定应用程序的中断向量表位置,并修改位于UBC空间的原始中断向量表的入口地址来解决更新后应用程序中断的使用。 通过对FLASH的IAP编程和中断向量的修改实现了STM8单片机的在线更新系统,本系统对STM8嵌入产品的软件升级提供了完整的支持。 【关键字】ECU 在线升级系统 STM8 IAP CAN - I - 目 录 前 言................................................................................................................................................... 1 第一章 在线升级简介 ...................................................................................................................... 2 第二章 系统总体设计 ...................................................................................................................... 4 第一节 软件总体设计................................................................................................................... 4 第二节 在线升级过程................................................................................................................... 7 第三节 下载 ........................................................................................................................ 10 第四节 下载程序数据解析 ......................................................................................................... 13 第三章 基于STM8的在线升级系统 ............................................................................................... 15 第一节 在线升级入口................................................................................................................. 15 第二节 升级流程控制................................................................................................................. 16 第三节 下载协议支持................................................................................................................. 18 第四节 FLASH编程接口.............................................................................................................. 19 第五节 CAN通信 ........................................................................................................................ 21 第六节 中断向量表搬移 ................................................................................................................. 22 第四章 系统实现及测试 ................................................................................................................ 25 第一节 编译器设置 .................................................................................................................... 25 第二节Bootloader项目实现............................................................................................................ 26 第三节 应用程序测试项目.............................................................................................................. 33 第五节 在线升级测试................................................................................................................. 36 结 论.................................................................................................................... 错误~未定义书签。 致 谢.................................................................................................................... 错误~未定义书签。 参考文献 ................................................................................................................ 错误~未定义书签。 附录 ....................................................................................................................... 错误~未定义书签。 - II - 前 言 随着电子技术、计算机技术和通信技术的迅猛发展,嵌入式系统已经广泛应用于工业、通信、信息家电及汽车电子等领域。但是面对新技术的不断涌现和对系统功能、性能以及规模要求的不断提高,开发者必须能够针对客户的需求及时对系统进行升级和维护,以延长系统的使用周期,改善系统性能,增强系统适应性。 传统的嵌入式系统升级通常需要维护人员到达设备现场,开箱重写或者更换FLASH存储部件,当设备数目庞大并且分布范围广泛时,这种升级维护方式的工作量非常大,而且耗费的时间长、成本高。特别是在汽车电控产品中,由于汽车的封闭性,拆卸这些电控设备进行升级是一件非常麻烦的事,因此需要设计一种快捷方便的在线升级方法和系统,便于汽车各种电控单元的软件更新与维护。 本文在此背景下,提出了基于CAN总线的在线升级系统设计方法,利用CAN总线高等级的分布实时控制、报文的优先权、设置灵活等特点,只要终端接入CAN网络就可以实现远程数据的传输;为了实现对升级数据的可靠性和安全性要求,采用了应答机制和安全验证技术;通过对嵌入式芯片内部FLASH存储器的存储空间进行划分和升级文件传输格式进行定义,在通过IAP技术对系统软件进行在线升级,从而解决了数量庞大并且不适合开箱升级设备的在线升级或维护问题。 - 1 - 第一章 在线升级简介 随着科技技术的不断进步,人们的生活水平日益提高。与此同时,人们对物质与精神生活的要求也越来越高。而汽车作为人类的代步工具也随着 随着人类对物质的追求和第三次工业革命的浪潮发生了巨大的变化,传统的汽车已经不能满足人类、环境、能源的需求。特别是现在城市化的加快,越来越多的汽车出现在我们身边,同时越来越多的交通事故使得汽车对人们的生存带来威胁。在这种背景下各种新能源新技术尤其是计算机技术在汽车上的应用也应运而生。 在科技飞速发展的今天,哪个汽车拥有越先进的电子技术,那么就拥有了更高的性能。要是汽车有优异的性能,必须与用户形成互动的关系,利用用户的反馈信息对产品不断完善。另外,产品长时间使用后,软件程序设计上的缺陷也就慢慢浮出水面,这就要求对软件系统程序进行升级。 软件升级的方法包括现场升级和在线升级。现场升级需要操作人员到设备现场,将微控制器拆除下来放入单独的编程器,然后烧写升级程序。如果对软件进行现场升级,那么不但浪费大量的人力、财力,更重要的是浪费了宝贵的时间。在线升级通过MCU提供的外部通信接口(比如UART/SPI/CAN等),采用在应用编程(IAP, In Application Programming)的方式更新应用程序代码。这种方式特别适用于已经嵌入产品的微控制器升级,在线升级系统为嵌入式应用的软件更新提供了一个方便、可靠、高效的途径。 汽车电子软件在升级过程中可能遇到以下问题: 第一、改装升级可增强性能输出,之所以原车出厂不如此设计,是因为国际顶级汽车生产厂商均为国际跨国企业,生产产品均销售至世界各地使用。因每个国家汽油品质、温度、大气压力、湿度、引擎形式上的差异使得软件设定上须符合不同的条件来使用,故在设定上保留很多的空间可供改装. 第二、改装升系统的性能,不会对行车电脑造成损伤。那是由于改装升级只是对系统中的程序芯片的重新改写,只是将系统控制计算的更精准,及加速芯片的运算速度,只会提高系统的运行的效率,因此,对整个电脑不会有任何伤害,另外,改装升级的系统仅仅是在特定的芯片上进行升级和调校,没有对系统中任何的其他相关部件及芯片进行任何改动,所以,不会对行车电脑有任何伤害. 第三、改装升级采用车辆型号量身订做的方式,其拥有专业改装升级设备,可将原车程序读下后传送至国外程序生产厂商总部,总部会根据您的车况将改装程序传回安装,故不影响您 - 2 - 原车功能设定上的改变,此方法为改装,而不是去欺骗SENSOR给错误信息,所以原厂诊断计算机不会有无法消除的故障码出现,相应的安全防盗系统也会保留. 第四、同类车型改装升级ECU程序是每个车型不只有一个。以BORA1.8为例,目前为止,已经发现此车型的六种电脑的版本,电脑ECU的产地的不同是出现这个问题的主要原因,车辆生产厂商的行车电脑OEM采购和安装来自各个不同的代理生产厂商的生产地点;改装升级程序,会根据车辆电脑上的标号来进行准确的程序改装与升级,这样的改装方法非常精准,能够最大限度的发挥升级系统的效果! 第五、在遇到以下种情况时需作软件升级与更新: A、当爱车做了结构上的改装时,需要新的软件来匹配。 B、生产厂家发布了新的软件时,升级更新以提高车的整体性能。 C、当你的居住环境发生变化时,通过软件的改装来保持车的最佳性能的输出。 D、原有的软件遭到病毒攻击时,及时的更新杀毒以保证行车的安全。 因此,针对汽车电控单元在线升级的要求,设计一种基于汽车车身网络的在线升级系统,实现汽车电控单元软件的快速、方便的程序下载和升级。 - 3 - 第二章 系统总体设计 一个在线升级系统的最终目的是下载升级程序代码,通过微控制器自身的操作更新FLASH中的应用程序代码,但一个性能可靠、机制完善的在线升级系统还应该具有以下功能: 1、 确认升级过程是安全的。该要求可以通过一个升级请求用户的身份认证来完成: 2、 确认接收数据的完整和正确。在完成一个单位的程序数据接收后,对单位数据进行验 证,然后返回一个验证结果,用以确认接收数据的正确性。 3、 保持应用程序空间的清洁。该操作的目的是使应用程序代码空间在完成一次在线升级 后只具有一个版本的应用软件,防止多个软件版本代码的混合,导致程序的意外执行 错误。该要求可以通过代码下载前的应用程序空间擦除操作来实现。 4、 重启微控制器。在完成代码的在线升级后,需要对微控制器进行复位。 针对以上提出的在线升级机制要求,下面的小节将对实现该机制的软件架构和程序执行流程进行详细描述。 第一节 软件总体设计 本系统通过CAN总线进行数据传输,因此本系统需要以下基本模块:CAN驱动、FALSH驱动、协议解析、引导转载。本节将对各个模块的功能范围和各个模块之间的架构进行描述。下图描述了各个模块在bootloader存储空间中的分布。 图2.1 bootloader程序空间分布 - 4 - 一、模块组成及功能划分 通过对在线升级过程的了解我们可以知道,整个系统涉及两个方面的单片机硬件资源使用,即CAN模块和FLASH编程模块。软件上的逻辑模块需要有对协议的解析和升级流程的控制。因此我们将bootloader的功能划分为四个模块,分别是:控制装载、协议解析、CAN驱动和FLASH驱动。 控制装载完成对升级流程的控制,各个模块间的协调调度;协议解析是对接收数据的类型解析并生成响应报文,同时还要对下载协议要求的一些过程进行支持,CAN驱动及完成数据的发送和接收;FLASH驱动是完成应用程序代码空间内容的擦除和更新。 下面的小节对各个模块的作用及功能范围作出详细介绍。模块间的结构关系如下图: 控制装载协议分析 CAN驱动FLASH驱动 图 2.2 模块架构图 二、CAN数据通信 CAN是控制器局域网络(Controller Area Network,CAN)的简称,它是串口通信协议,能够有效地支持具有很高安全等级的分布实时控制。它的传输速度可以达到1Mbit/s,非常广泛的应用于汽车电子行业中。 CAN总线具有很多有用的特点。很高的系统灵活性,它允许在网络中直接添加节点,而不需要对硬件或任何节点的应用层软件作改变;报文的内容由识别符命名,识别符只解释数据含义,而不指出报文目的地,因此网络上所有的节点对数据是否做出反应是通过报文滤波来完成的;网络中任何数目的节点都可以接收报文,并对该报文做出反应。 数据帧是CAN网络传输中常用的格式。数据帧携带数据从发送器到接收器,一个数据帧包含了7个不同的位场:帧起始、仲裁场、控制场、数据场、CRC场、应答场、帧结尾。数据场的长度可以为0。 - 5 - 1、CAN使用 本系统采用使用11标识符的帧类型,下位机通过自身的CAN过滤器组来获取自身需要的数据。CAN数据帧长度始终设置为8,如果不足8位,需要使用特定的值进行填充。 CAN数据帧中的8字节数据包含一个字节的帧类型和7个字节的数据。帧类型字节描述了当前数据帧携带的什么数据,它是下位机协议解析的重要依据。数据字节是在线升级系统需要使用到的数据。 2、CAN模块的功能 该模块完成的工作是对MCU的CAN模块的初始化、CAN数据帧的接收和发送。 初始化工作包括了对CAN的中断、标识符、波特率、过滤器组等的设置,由于MCU的CAN模块在上电复位后进入SLEEP模式,为了方便操作,需要提供一个模式转换函数。 三、FLASH在应用编程 STM8的EEPROM以32位字长为基础组织起来,本系统实现中使用的微控制器STM8S 208具有128K的FLASH程序存储器,每页512字节;2K的数据EEPROM,每页512字节。同时提供在应用编程(IAP)和在线编程(ICP)能力。 提供的保护特性有:存储器读保护(ROP)、基于存储器存取安全系统(MASS密钥)的程序存储器写保护、基于存储器存取安全系统(MASS密钥)的数据存储器写保护、可编程的用户启动代码区域(UBC)写保护。 提供的编程模式有:字节编程和自动快速字节编程(没有擦除操作)、字编程、块编程和快速块编程、在编程/擦除操作结束时和发生非法编程操作时产生中断。 1、FLASH使用规范 本系统采用字节编程。字节编程不需要将编程代码拷贝到RAM区执行,使得系统结构更加简单。 由于系统使用S19文件格式传输更新的程序代码,因此FLASH编程操作是针对一条S记录进行的字节编程,即解锁后完成一条S记录编程后,再对FLASH存储器进行上锁。 考虑到在线升级系统需要保存当前程序空间中的软件版本信息,因此在数据EEPROM中使用一定空间来保存该信息。 - 6 - 2、FLASH模块功能范围 FLASH模块需要提供以下操作:初始化FLASH、擦除应用程序空间、写入S记录到应用程序空间、写入软件版本到数据EEPROM。 四、协议解析 协议解析主要是对上位机和下位机通信协议的支持,通过在线升级过程的介绍可以知道,在下载协议中包含了不同的命令,各个命令需要bootloader采用不同的操作,根据命令和操作结果的不同响应不同的报文。同时该下载协议还涉及了软件版本和MCU型号的比较、S19文件的拆分等。 该模块首先需要提供两个基本的功能:解析报文命令的类型、生成响应报文。其次,它还应该提供自身模块需要使用的函数,比如软件版本比较、S19记录的校验码计算等。 五、控制装载 控制装载是在线升级系统最高层的应用,它调用CAN驱动来接收数据报文,如果接收到数据,则调用协议解析模块来分析接收到的数据,根据数据解析的结果调用响应的操作,然后根据操作的结果返回响应报文。在下载过程中,当完成一条有效的S记录接收后,调用FALSH驱动将数据写入应用程序存储空间。 从以上过程可以看出,控制装载需要具有以下功能:对系统状态的转换、升级流程的控制、数据结构的定义、CAN数据帧和S记录数据的转换。 第二节 在线升级过程 系统上电复位后,首先进入bootloader区的main函数,该函数中通过轮询的方式查看是否接收到了升级请求。如果没有升级请求,等到超时后就转入用户应用程序执行;如果bootloader接收到升级报文,程序转入引导装载执行。 引导装载包括5个阶段,每个阶段都对应着通信协议的一个会话过程,它们分别是:握手会话、身份验证、擦除应用代码区、下载应用代码、ECU复位。下图描述了整个在线升级系统的各个运行环节和顺序: - 7 - 变量及模块初始化 是否超时 初始化状态, 增加超时计数升级控制装载 延时 跳转执行应用程序 图2.3 在线升级整体流程图 一、握手会话 MCU在完成上电复位后等待一定时间,查询上位机是否发送了升级请求报文。握手会话需要确认是否需要对自身的软件进行升级。 该报文中包含了软件的版本号和当前升级软件对应的MCU型号。bootloader收到请求报文后,首先将系统状态改为握手状态,然后对报文中的数据和MCU本身的版本号和MCU型号信息进行比较。如果通过验证,则将系统状态改为身份验证状态,并返回肯定响应报文;如果没有通过验证,MCU返回否定响应报文,然后MCU进行复位。 二、身份验证 - 8 - 本系统采用置换密码的方法进行身份验证,即下位机发送种子数据,上位机在获取种子后通过一定算法计算密钥,然后传送给下位机MCU进行验证。种子和密钥都是7位的16进制数据,密钥为种子循环右移5位。 在通过握手过程后,MCU进入等待状态。上位机发送身份认证请求报文,下位机在收到该身份验证的请求报文后,将自身的种子数据发送给上位机,上位机经过计算后将密钥发送给下位机,如果MCU收到的密钥正确,则发送肯定响应报文,并将系统状态改变为擦除应用代码状态;如果MCU收到的密钥错误,则返回否定响应报文,系统状态仍然停留在身份验证状态,等待上位机重新完成身份验证。 身份验证过程描述如下图: 图 2.4 身份验证过程 三、擦除应用代码区 本阶段的功能是将应用代码区中原来的应用程序全部擦除,这样可以防止新应用代码比原来应用代码小的情况下,应用代码空间混乱的情况;同时它还可以减少IAP编程时间。 上位机发送擦除请求报文,当MCU收到该报文后,调用FALSH驱动的相关进行应用代码区进行擦除。 四、下载应用代码 - 9 - 本阶段完成升级代码数据的传输,升级代码采用S19文件格式。首先bootloader需要将接收到的数据进行报文合并,即传输一条S记录的多数据帧。然后根据当前报文中的数据进行校验,如果校验正确,则返回肯定的校验和正确报文,并通过IAP编程的方式将升级数据写入FALSH程序代码区;如果校验错误,则返回否定响应报文,等待上位机重传。 当MCU收到上位机传输的退出请求报文时,MCU将系统状态改变为复位状态,并返回肯定响应报文。 五、MCU复位 上位机在接收到MCU的传输退出响应报文后,发送MCU复位请求报文,下位机收到该报文后,返回肯定响应报文,然后跳转到系统复位向量执行。 第三节 下载协议 在一个CAN网络中包含了不同的MCU节点,PC机通过USB-CAN接口卡接入CAN总线网络。当系统处于在线编程状态时,PC机可以通过CAN总线网络方便的与MCU节点通信,实现数据下载,在应用编程。下图描述了下载协议使用的网络环境: 图2.5 在线升级系统网络架构 一、在线升级系统服务 通过前面的在线升级过程一节我们可以获得以下在线升级系统需要提供的服务: 表 2. 1 在线升级系统服务 - 10 - 服务 功能描述 握手会话控制 请求MCU进入在线升级状态 身份验证 进行升级请求发出用户的身份验证 请求MCU擦除 请求MCU擦除用户应用代码空间 数据下载 下载升级的程序代码数据 请求退出传输 声明升级数据传输完成 请求MCU复位 完成升级后请MCU进行复位 握手会话是升级的请求信息,同时该请求数据中还包含了软件版本和MCU型号信息;身份验证采用了置换密码的方法,首先上位机发送请求种子报文,由MCU返回本节的种子数据,上位机经过计算后,发送密钥请求下位机解锁;数据下载服务请求上位机发送升级程序代码;在完成数据传输后,上位机请求MCU进行复位,进入应用程序执行。 - 11 - 二、下载协议数据定义 本下载协议数据定义的主要依据是本章开始提出的机制要求,提供一个安全、可靠的下 载协议。本节中描述了各个阶段传输的数据内容及意义。 表 2. 2 下载协议数据定义 服务类型 数据位 说明 握手会话 FF 软件版本、MCU型号 请求MCU进入升级状态。 01 01… 肯定相应 10 10 否定相应 身份验证 FE 填充字节 请求MCU发送 FE 种子数据 返回上位机的种子数据 FD 密钥字节 解密数据,上位机对种子加密后的数据 02 02… MCU的肯定响应 11 11 MCU否定响应报文,意味着上位机的密钥错误 请求MCU擦除 FC 填充字节 请求MCU擦除用户应用代码区 03 03… 肯定响应报文 下载数据 FB 程序数据字节 接受程序代码数据 04 04… 校验正确响应 12 12 校验错误响应 请求退出传输 FA 填充字节 请求下位机MCU退出数据传输 05 05… 肯定响应报文 请求MCU复位 F0 填充字节 请求MCU复位 06 06… 肯定响应报文 - 12 - 第四节 下载程序数据解析 在本在线升级系统中,上位机发送的程序数据采用S19格式的文件。S19文件是摩托罗拉定义的一种文件格式,它的内容是一段可以直接烧写进MCU的ASCII码。而在CAN总线通信中,数据的传输是通过携带8个数据字节的数据帧来实现。本机将描述S19文件的格式和如何通过CAN数据帧来传输程序代码。 一、S19文件格式 S格式文件中每一行称为一个S记录,每个S记录由5个部分组成,分别是:记录类型、记录长度、存储地址、代码/数据、校验和。 表 2.3 S19文件格式 记录类型 记录长度 存储地址 代码/数据 校验和 S记录类型场的有:S0类型表示S格式文件的开始;S1/S2/S3分别描述了当前记录中存储地址的长度为2/3/4个字节;S7/S8/S9表示程序的结束,它们对应的存储地址长度分别为4/3/2字节。 记录长度场用一个字节(2个字符)表示,它的值计算方法为: 记录长度=存储地址字节数+代码/数据字节数+校验和字节数。 存储地址场的值表示代码/数据应该装载的起始地址,它的长度有记录类型决定,可以是2/3/4个字节 代码/数据场表示需要下载到MCU的数据,它可以包含0-32个字节(0-64个字符)。 校验和场为一个字节(2个字符),它的计算方法为: 校验和=0Xff-(记录长度+存储地址+代码/数据) 下面是一个S记录的分析:S123C000CF1400790011CC09395800C01BCB7317340027 表 2.4 S19文件解析例子 Type count addr Data checksum S1 23 C000 CF1400790011CC09395800C01BCB73173400 27 二、使用CAN传输程序数据 本系统中采用的CAN数据帧的类型为标准数据帧,一个代码数据传输单位是一条S记录。 一条S记录被封装成为CAN的多重帧报文,一个报文由多个CAN数据帧组成。一条S - 13 - 记录按7个字节划分,如果最后的程序代码数据不足7个字节,采用特定的字节值填充,因此一个多重报文包含整数个CAN数据帧。由于S记录的长度不定,所以一个多重帧报文所包含的数据帧数目也是不定的。 下位机MCU在接收到报文的第一个CAN数据帧后,通过该帧中的记录长度来判断当前S记录的报文是否传输结束。 - 14 - 第三章 基于STM8的在线升级系统 本系统实现基于STM8的CAN总线的在线升级,文中具体采用的单片机型号是STMS208,需要使用到的单片机特点包括: 1、 控制局域网(CAN)模块,它支持CAN协议2.0A和2.0B; 2、 存储器,最大128KB的程序存储器空间,最大2KB的数据EEPROM空间; 3、 用户启动区域,它包含有复位和中断向量表,可用于存储IAP及通讯程序。 下面将根据STM单片机的具体资源描述本系统的原理及设计,具体内容包括在线升级系统的入口、升级流程控制、下载协议支持、FLASH编程接口和CAN通信,同时本章还介绍了中断向量表搬移的具体实现方法。 第一节 在线升级入口 Bootloader的主函数通过轮询调用控制装载函数来判断是否进入升级模式。在主函数中规定一个超时范围,如果在超时前系统接收到升级请求数据,那么改变系统的模式为升级模式,如果超时没有接收到升级请求,那么系统转入应用程序代码执行,具体流程图如下: 变量及模块初始化 是否超时 初始化状态, 增加超时计数升级控制装载 延时 跳转执行应用程序 图3.1 主函数流程图 - 15 - 第二节 升级流程控制 在线升级的流程控制由控制装载函数来实现,它首先调用协议解析模块函数对接收到的数据进行分析,获取数据类型,根据不同的数据采取不同的操作。该模块的两大功能是:子函数模块的调用和CAN数据帧到S19文件的转换。 一、流程控制 在前面的概述章节中,我们可以知道整个在线升级过程的各个步骤都是顺序执行的,各个阶段步骤是不可以任意调换的,如果跳回原来执行过的步骤执行,那么又需要重新执行后面的步骤。比如在没有通过安全认证时,系统不能进行擦除操作,同样如果没有完成代码下载阶段,系统不能进行复位重启,这样可能因此以外的严重后果。因此在流程控制中采用了系统状态这个变量来控制各个阶段步骤的顺序执行。 在下面的流程图中可以看出,一部分的升级阶段并没有在流程控制中出现,那是因为一些协议阶段不需要其他模块操作,只需要在模块内部进行处理完成后返回状态给控制装载函数就可以了,并不需要单独在控制装载函数中作对应的处理。比如握手会话,它只需要控制装载给出接收到的数据,协议解析模块就可以在模块内部生成响应报文,而不需要控制装载函数的参与。 控制装载通过本模块的UpdateProcess()函数完成,该函数的流程图如下: - 16 - 调用解析协议A获取数据类型 调用FLASH驱动是否为MCU擦除请求是擦除应用代码区 调用协议解析模块 生成响应报文否 是否为下载代码是调用CAN模块 发送响应数据帧 将CAN数据添加到否S记录数据中 是否请求复位是否请求退出传输是 是校验错误S记录接收状态调用FALSH模块 修改软件版本跳转至复位向量 否正确接收否 未完成接收调用FALSH驱动 写入S记录数据 返回A 图 3.2 控制装置的UpdateProcess函数流程图 二、CAN数据转换为S记录 将CAN数据装入S记录数据结构的函数同时应该具有以下功能:将CAN数据复制到S 记录的数据结构中;根据当前的S记录数据判断是否完成一个S记录报文的接收;完成一条 S记录报文接收后,计算完整报文的校验和,并返回状态。 CAN数据帧到S记录的转换功能由msgAddSRecord()函数实现,该函数的原理流程图为: - 17 - 追加数据到S记录 结构 当前S报文是否完成 是 计算S报文校验和否 返回S报文接收状 态 图 3.3 msgAddSRecord函数流程图 第三节 下载协议支持 由升级流程控制可以看出,协议解析模块需要对外提供的功能函数有:解析数据类型、生成响应报文;由于部分下载协议过程是在协议解析模块内部完成的,因此该模块还需要提供以下完成协议支持功能的函数:版本比较、安全认证密钥比较、响应报文的快速填充。 一、 解析数据类型 本节所指的数据类型是指CAN数据在下载协议中的类型,比如升级请求数据、种子请求数据或擦除请求数据,数据类型解析的主要依据是CAN数据的第一个字节值。 根据协议规定可以知道,协议数据可以分为两类,一类是不需要认证的数据,另一类是需要认证的数据,因此数据类型解析函数将分为两个阶段来完成,不需要认证的数据直接与分支语句中的各个值进行逐个比较,如果获取结果这返回,如果没有对应的值,这返回未定 - 18 - 义类型;要求认证的数据类型需要首先判断系统当前的状态十分已经认证,如果没有认证,则将当前收到的数据返回未定义类型,如果当前系统为已经认证状态,则进行比较获取数据类型。 二、生成响应报文 该功能模块的主要功能根据报文类型和控制装载中的操作结果来生成响应报文,同时它还要完成一些协议内部的功能,比如版本比较和安全认证。该功能由GetAnswer()函数来实现,它的流程图如下: 数据类型 比较软件版本和计算种子密钥并与MCU型号CAN数据比较 将种子赋给响应报...文 是否通过版本比较是否通过密钥比较 生成肯定响应报文生成否定响应报文生成肯定响应报文生成否定响应报文 返回响应报文 图 3.4 生成响应报文GetAnswer函数流程图 第四节 FLASH编程接口 在本模块中,首先需要对FLASH进行初始化,在初始化中完成UBC区域大小的配置。同时需要为上层提供FLASH的操作接口函数,该模块应该包括的操作有:应用程序空间擦除、S记录写入的字节编程、版本信息的数据EEPROM字节编程。 一、应用程序空间擦除 该擦除操作根据控制装置传入的参数对相应地址的FLASH写入0X00完成擦除操作,该程序需要两个参数:应用程序基地址、擦除空间的长度。该功能由FlashErase()函数实现,流 - 19 - 程图如下: 写入第一密钥 写入第二密钥 i<长度 是 在当前地址写入0X00 否 重新给FLASH程序 空间上锁 图 3.5 应用程序空间擦除FlashErase函数流程图 二、将S记录写入FLASH 本系统中,一次FLASH编程的单位是一个S记录。传入该函数的S记录数据都是完成校 验的正确数据,校验操作在控制装载模块的数据格式转换中进行。 采用字节编程的方式,将S记录的有效数据写入完成后,重新对FLASH空间上锁。该功 能由FLASH模块的FlashSrecordWrite()函数实现,它的流程图如下: - 20 - 写入第一密钥 写入第二密钥 计算数据长度和地 址 是否完成写入 否 数据写入FLASH空 间 是 FLASH重新上锁 图3.6 FlashSrecordWrite函数流程图 三、数据EEPROM字节编程 该编程操作在数据EEPROM空间的指定地址写入软件的版本号。存储版本信息的地址是通过宏定义的方式完成,当完成软件程序数据更新后,系统将存储的升级版本信息写入宏定义的数据EEPROM地址。 数据EEPROM的写入方式和FLASH字节编程的方式相同,即首先解锁MASS,然后写入数据到指定地址,完成数据写入后,重新对数据EEPROM上锁。 第五节 CAN通信 在该模块中需要使用到4个功能函数:CAN的初始化、模式转换、发送CAN数据、接收CAN数据。 CAN的接收需要检测CAN数据帧的类型为标准数据帧,通过检测接收FIFO中的报文数量来完成数据的接收,并不使用CAN中断。 - 21 - 由于初始化涉及的操作比较多,具体实现在Can_InitController()函数中实现,下面给出初始化的流程图: 退出SLLEP模式 进入初始化模式 等待确认进入初始 化模式 设置波特率 退出初始化模式 等待确认 设置复用CAN的GPIO 进入初始化模式 设置过滤器组 切换到正常模式 图3.7 CAN模块初始化Can_InitController流程图 由于采用查询的方式,而STM8的CAN模块操作由比较方便、简单,因此在此不做详细阐述,CAN数据的接收和发送简述如下:首先查询相应的状态寄存器,如果接收FIFO的接收的报文数据不为零,则将相应寄存器中的数据赋值给数据结构。发送数据的操作类似。 第六节 中断向量表搬移 中断向量表存储着用户定义的中断服务函数(ISR)的入口地址。但硬件中断源产生一个中断时,程序跳到中断向量表中相应的中断向量读取中断服务函数的入口地址,根据获取的函数入口地址执行相应的中断服务函数。 由于在整个在线升级过程中,bootloader无法修改位于用户启动区域(UBC)的中断向量表内容,意味着完成在线升级后,MCU无法找到位于用户应用代码区中的中断服务函数,这样 - 22 - 用户应用程序就无法使用MCU中断,因此我们需要对中断向量表进行搬移。 一、中断向量表存储结构 中断向量表有32个4字节的入口地址:复位中断,trap中断,NMI中断和29个常规用户中断。每个入口包含了保留操作码82h和24位的中断服务程序地址值,24位值分别为:PCE、PCH、PCL。主函数和中断服务程序可以位于16M存储空间中的任何地址。 二、中断向量表汇编文件 在新建项目文件后会有一个中断向量表的C文件,里面包含一个结构体,它存储着操作码和中断服务函数。通过命令行汇编后可以得到它的汇编文件,下面的代码是一个中断服务函数入口地址的定义: dc.b 130 .dcall "__vectab.T:f_NonHandledInterrupt" dc.b page(f_NonHandledInterrupt) dc.w f_NonHandledInterrupt 通过对COSMIC的手册可以知道上面的各个汇编指令意义,dc.b为后面的表达式定义了 Page()获取表达式的页码数。 一个字节,dc.w为后面的表达式定义了一个字(即2字节)。 130的十六进制是82h,即中断向量表中的保留操作码。NonHandledInterrupt是中断函数服务程序的函数名称,汇编调用C语言函数通过在函数名前加f_来实现,因此f_NonHandledInterrupt就是用户定义的中断服务程序。 三、中断向量表的搬移 通过上面的介绍,我们可以看到程序空间中存储中断向量表结构和汇编语言的指令完全符合,我们只需要将汇编语言中的中断向量的中断服务函数入口地址修改为相应的搬移后的中断向量地址。而该地址的内容存放着用户中断服务函数的入口地址。 比如串口中断向量的搬移实现如下:通过数据手册的查询可以知道,串口接收中断的中断序号是18,向量地址是8050h,如果我们将中断向量表搬运到开始地址为A000h,那么搬移后的串口接收中断地址为A050h,原始中断向量表内容为: dc.b 130 .dcall "__vectab.T:f_NonHandledInterrupt" - 23 - dc.b page(f_NonHandledInterrupt) dc.w f_NonHandledInterrupt 修改后的中断向量内容为: dc.b 130 dc.b 0x00 dc.w 0xa050 - 24 - 第四章 系统实现及测试 第一节 编译器设置 本系统中使用8KB的bootloader空间,即0x8000-0x9fff的空间。应用程序空间从0xa000开始至FLASH最后的地址。同时bootloader项目使用的中断向量表文件是经过汇编后的汇编文件,名称为stm8_interrupt_vector.s,它的起始地址是0x8000,程序代码的其实地址是0x8080。与之相对应的地址分配需要对编译器进行如下设置: Bootloader项目编译器设置 图4.1 bootloader项目连接地址设置 应用程序项目编译器设置 - 25 - 图 4.2 应用程序连接设置 第二节Bootloader项目实现 通过系统的总体设计可以知道系统总共分为四大模块,由此我们可以达到该项目的文件组织结构,如下图: 图 4.3 bootloader的文件组织结构 一、程序入口分支(main.c) - 26 - 该段程序位于main函数中,它首先需要进行各个模块的初始化,它还是进入应用程序代码段的唯一入口。系统的初始状态是初始化状态,如果该程序检测到CAN模块收到数据,则调用升级系统的控制装置进行操作,并改变系统状态;同时程序通过一个计数器来记录延时,如果系统为初始化状态,则增加计数,如果计数超过延时限制,则跳转到应用程序代码区,如果系统不在初始化状态,则停止延时计数。 以下程序是位于死循环中的功能代码: if(CAN->RFR&0x03) { CAN_Receive(&tmpMsg); UpdateProcess(tmpMsg); } if(pStage == PS_INIT) /* 进入延时 */ { tmp++; PC_ODR = tmp; delay(); } if(tmp > timeOut) { _asm("JPF $0Xa000"); } 二、控制装载(control.h/control.c) 由于控制装载需要调用其他功能模块来实现整个升级流程控制,因此常用的宏定义和数据结构都在control.h中进行定义。该模块的主要功能是完成在线升级流程控制,同还需要进行CAN数据到S记录数据结构的转换。 1、宏和数据结构定义 #define VERTION_ADDR 0x4000 #define SET_MCUH 0X01 #define SET_MCUL 0X01 以上宏定义设定了软件版本号的写入地址和单片机型号。 - 27 - /* 该数据结构用于模拟CAN数据帧的8字节数据 */ typedef struct { u16 CanID; // 帧ID u8 IDE; // ID类型 u8 RTR; // 帧类型 u8 LENGTH; // 帧中数据长度: bytes to transmit w/o null u8 byte[8]; }msgCan; /* 该数据结构用于存储多针报文的数据,即一个S记录 */ typedef struct { u8 byte[80]; }SRecord; /* 定义了在线升级所处的阶段 */ typedef enum { PS_INIT, PS_REQUEST, PS_AUTH, PS_ERASE, PS_DOWNLOAD, PS_RESET }ProcessStage; /* 定义CAN数据帧类型 */ typedef enum { CMD_UNDEFINED, CMD_UPDATE, CMD_AUTH_REQUREST, CMD_AUTH_ANSWER, CMD_ERASE, - 28 - CMD_DOWNLOAD, CMD_DOWNLOAD_FINISH, CMD_MCU_RESET } msgType; 2、升级流程控制 该部分对协议的支持主要有两个函数完成,一个是parseMsg(),它通过CAN数据的第一个字节来判断当前数据的类型。另一个函数是GetAnswer(),该函数主要的功能是通过当前数据的类型和操作结果来返回不同的报文。其次它还完成一些协议支持的独立功能,比如握手阶段的版本信息比较、安全认证阶段的种子获取和密钥计算比较。 流程控制还需要根据获取的命令来对单片机资源进行操作,比如FLASH擦除、发送CAN数据,主要代码如下: if(ctrlMsgType == CMD_ERASE) { FlashErase(0xa000,0x800); /* 调用FALSH擦除操作 */ } else if(ctrlMsgType == CMD_DOWNLOAD) /* 处理下载数据 */ { opRes = msgAddSRecord(&saveSRecord, canFrom); if(opRes == 1) { FlashSrecordWrite(&saveSRecord); } else if(opRes == 2) /* 接收未完成 */ { return; } } else if(ctrlMsgType == CMD_DOWNLOAD_FINISH) { EepromWriteVersion(VERTION_ADDR,currentVersion); } 最后,如果上位机请求复位,它还应该执行复位跳转。 if(ctrlMsgType == CMD_MCU_RESET) - 29 - { _asm("JPF $0X8000"); } 3、CAN数据到S记录的转换 这个功能实现的是将代码下载中接收到的数据添加到S记录的数据结构中。由于协议规定多帧报文传输S记录时,如果最后的数据不足,将采用填充,因此S记录必定为整数个CAN数据帧。比较当前接收到的数据长度与S记录中的记录长度比较来判断是否完成一条S记录的接收,如果完成一条S记录多重帧报文的接收,则调用校验和计算校验和,如果接收的数据正确,则会在控制装载函数中调用FLASH驱动将数据写入FLASH中。具体代码如下: for(i = 1; i < 8; i++) { (*pSRec).byte[cntPtr] = data.byte[i]; cntPtr++; } len = (*pSRec).byte[0]; /* 一条S记录的长度 */ if(len < cntPtr) /* 根据S记录长度,已经完成接收 */ { cntPtr = 0; correctReceive = SrecordCheckSum(pSRec); } 三、协议解析 该模块需要实现控制装载中的两个函数:数据类型解析、获取响应报文。获取响应报文是该模块的主要函数,它不仅会生成响应报文返回给控制装载函数,同时它还需要单独对协议要求的一些过程进行验证和计算。同时该函数完成系统状态的转换。以下是部分实现代码 if(M_msgType == CMD_UPDATE) /* 握手会话状态 */ { switch (VersionCompare(*from)) - 30 - { case 1 : DataFastFill(response,0x01,8,0xff); pStage = PS_AUTH; break; case 0 : DataFastFill(response,0x10,2,0xff); } } else if(M_msgType == CMD_AUTH_REQUREST) /* 安全认证状态 */ { (*response).byte[0] = SEED.byte[0] = 0xfe; (*response).byte[1] = SEED.byte[1] = 0x01; (*response).byte[2] = SEED.byte[2] = 0x02; (*response).byte[3] = SEED.byte[3] = 0x03; (*response).byte[4] = SEED.byte[4] = 0x04; (*response).byte[5] = SEED.byte[5] = 0x05; (*response).byte[6] = SEED.byte[6] = 0x06; (*response).byte[7] = SEED.byte[7] = 0x07; } else if(M_msgType == CMD_AUTH_ANSWER) { switch (CodeCompare(*from)) { case 1 : DataFastFill(response,0x02,8,0xff); pStage = PS_ERASE; break; case 0 : DataFastFill(response,0x02,8,0xff); } } else if(M_msgType == CMD_DOWNLOAD) /* 下载状态 */ { /* 在没有下载完成一条S记录,返回错误响应,是否发送由控制装载来控制 */ switch (bValue) { case 1 : DataFastFill(response,0x04,8,0xff); break; case 0 : DataFastFill(response,0x12,2,0xff); break; } } - 31 - 四、FLASH驱动 FLASH驱动的主要功能是将S记录写入应用程序空间,根据本系统的策略特点,FLASH编程的单位是一个S记录的数据。 FLASH_PUKR = 0x56; FLASH_PUKR = 0xae; /* len = 地址字节(2)+数据+校验和(1) */ len = (*pSRec).byte[0]; addr = ((*pSRec).byte[2] << 8) | (*pSRec).byte[3]; /* 长度(1)+地址(2)+数据+校验和(1)=len+1 */ for(i = 4; i <= len; i++) { *((@far u8 *)addr++) = (*pSRec).byte[i]; } FLASH_IAPSR &= 0xfd; 五、CAN驱动 CAN驱动的接收主要是对FLASH模块的寄存器读取。 if((CAN->RFR&0x03) == 1) { CAN_Page = CAN->PSR; //返回当前页面选择寄存器中页面映射内容。 CAN->PSR |= CAN_Page_RxFIFO; Rx_PduInfo->IDE=CAN->Page.RxFIFO.MIDR1&0x40; if((!(Rx_PduInfo->IDE&0x40))&&(!(Rx_PduInfo->RTR&0x20))) { Rx_PduInfo->CanID = ((((u16)CAN->Page.RxFIFO.MIDR1) << 6 ) | ((u16)CAN->Page.RxFIFO.MIDR2>>2)); Rx_PduInfo->RTR = CAN->Page.RxFIFO.MIDR1 & 0x20; Rx_PduInfo->LENGTH = CAN->Page.RxFIFO.MDLC&0x0F; for(i=0;i<8;i++) - 32 - { Rx_PduInfo->byte[i] = (u8)CAN->Page.RxFIFO.MDAR[i]; } CAN->RFR |= CAN_RFR_RFOM;//释放接收邮箱 } CAN->PSR = CAN_Page; // 恢复设置前的页面映射关系 } 六、中断向量表 根据前面对中断向量表的分析,对中断向量表的搬运,只需要将地址修改为搬运后的地址即可。以下是串口中断的搬运部分。 dc.b 130 ;操作码82h dc.b 0x00 dc.w 0xa050 ;搬运后的中断向量地址 第三节 应用程序测试项目 该应用代码完成的测试有两个:GPIO和串口中断。该项目中的串口中断作用是用于测试应用代码能够正常使用进行中断转移后的中断向量表。文件目录结构如下: 图 4.4 应用程序目录结构 一、主函数 主函数中完成了对串口模块的初始化和通用输入输出口的初始化。并编写中断服务函数。具体代码如下: #include void Init_UART(void) { - 33 - /* 波特率为9600 */ UART1_BRR2 = 0x2; UART1_BRR1 = 0x34; UART1_CR1 = 0x0; UART1_CR2 = 0x2c; } void UART_SendChar(unsigned char ch) { while((UART1_SR & 0x80) == 0x00); // 若发送寄存器不空,则等待 UART1_DR = ch; // 将要发送的字符送到数据寄存器 } main() { int count = 0; PC_DDR = 0XFF; PC_CR1 = 0XFF; Init_UART(); while (1) { PC_ODR = count; count++; count = count%4; delay(10); } } @far @interrupt void ISR_UART1_Receive(void) { - 34 - unsigned char ch; ch = UART1_DR; // 读入接收到的字符 UART_SendChar('a'); } 二、中断向量表文件 应用程序的中断向量表和普通项目一样,只是起始地址需要设置到0xa000,部分代码如下: extern void _stext(); /* startup routine */ extern @far @interrupt void ISR_UART1_Receive(void); {0x82, ISR_UART1_Receive}, /* irq18 */ - 35 - 第五节 在线升级测试 下面的图按照执行顺序列出了一次在线更新过程,本次测试使用了USB-CAN接口卡将PC与微控制器进行连接。 图 4.5 上位机初始化 - 36 - 图 4.6 发送升级请求 图 4.7 身份验证 图 4.8 擦除应用程序区 - 37 - 图 4.9 打开下载文件 图 4.10 下载文件 - 38 - 图 4.11 MCU复位 至此完成一次在线升级过程,通过现场观察看出MCU升级过后的应用程序工作正常,串口中断测试正常,串口中断测试的中断函数如下: @far @interrupt void ISR_UART1_Receive(void) { unsigned char ch; ch = UART1_DR; // 读入接收到的字符 UART_SendChar('a'); } - 39 - 图4.12 串口中断测试 - 40 - 三、源程序 Main.c /******************** 文件名:main.c************/ #include "control.h" #include "flash.h" #include "can.h" /********************* 全局变量定义 *********************/ extern ProcessStage pStage; /** 功能:初始化GPIO **/ void InitGPIO(void) { PC_DDR = 0XFF; PC_CR1 = 0XFF; } /*** 功能:实现延时 **/ void delay(void) { int j; for(j = 0; j < 400; j++) { _asm("NOP"); } } main() { unsigned int tmp = 0, timeOut = 5000; msgCan tmpMsg; - 41 - InitGPIO(); //Init_FLASH(); Can_InitController(); _asm("rim"); while(1) { if(CAN->RFR&0x03) { CAN_Receive(&tmpMsg); UpdateProcess(tmpMsg); } if(pStage == PS_INIT) /* 进入延时 */ { tmp++; PC_ODR = tmp; delay(); } if(tmp > timeOut) { _asm("JPF $0Xa000"); } } } Control.h /**************** 文件名:control.h*****************/ #ifndef _CONTROL_H #define _CONTROL_H - 42 - /**************** 包含头文件 *********************/ #include #include #include /************** 宏定义 ********************/ #define VERTION_ADDR 0x4000 #define SET_MCUH 0X01 #define SET_MCUL 0X01 #define CAN_ID_STANDARD 0x00 // 标准ID #define CAN_RTR_DATA 0x00 //数据帧 /*********************** 数据结构定义 *******************/ /* 该数据结构用于模拟CAN数据帧的8字节数据 */ typedef struct { u16 CanID; // 帧ID u8 IDE; // ID类型 u8 RTR; // 帧类型 u8 LENGTH; // 帧中数据长度: bytes to transmit w/o null u8 byte[8]; }msgCan; /* 该数据结构用于存储多针报文的数据,即一个S记录 */ typedef struct { u8 byte[80]; }SRecord; /* 定义了在线升级所处的阶段 */ - 43 - typedef enum { PS_INIT, PS_REQUEST, PS_AUTH, PS_ERASE, PS_DOWNLOAD, PS_RESET }ProcessStage; /* 定义CAN数据帧类型 */ typedef enum { CMD_UNDEFINED, CMD_UPDATE, CMD_AUTH_REQUREST, CMD_AUTH_ANSWER, CMD_ERASE, CMD_DOWNLOAD, CMD_DOWNLOAD_FINISH, CMD_MCU_RESET } msgType; extern void UpdateProcess(msgCan canFrom); extern u8 msgAddSRecord(SRecord *pSRec, msgCan data); extern u8 SrecordCheckSum(SRecord *pSRec); #endif Control.c /****************** 文件名:control.c ******************/ #include "control.h" #include "can.h" - 44 - #include "flash.h" #include "protocol.h" /*************** 全局变量定义 **************************/ extern u16 currentVersion; SRecord saveSRecord; u8 cntPtr = 0; /* 该变量描述下一添加到S记录的位置 */ /* 功能:控制在线升级流程 */ void UpdateProcess(msgCan canFrom) { u8 tmp, opRes = 0; msgType ctrlMsgType; msgCan canResponse; ctrlMsgType = parseMsg(canFrom.byte[0]); DataFastFill(&canResponse,0xff,8,0xff); if(ctrlMsgType == CMD_ERASE) { FlashErase(0xa000,0x800); /* 调用FALSH擦除操作 */ } else if(ctrlMsgType == CMD_DOWNLOAD) /* 处理下载数据 */ { opRes = msgAddSRecord(&saveSRecord, canFrom); if(opRes == 1) { FlashSrecordWrite(&saveSRecord); } else if(opRes == 2) /* 接收未完成 */ { return; - 45 - } } else if(ctrlMsgType == CMD_DOWNLOAD_FINISH) { EepromWriteVersion(VERTION_ADDR,currentVersion); } GetAnswer(&canFrom, &canResponse, opRes); CAN_Transmit(&canResponse); if(ctrlMsgType == CMD_MCU_RESET) { _asm("JPF $0X8000"); } } /* * 功能:将单帧CAN数据存储到报文MSG中 * 输入:多帧CAN报文指针 CAN数据帧 * 输出:是否正确完成一条S记录的接收,没有完成和错误都是返回0 */ u8 msgAddSRecord(SRecord *pSRec, msgCan data) { u8 i, len = 0, correctReceive = 2; for(i = 1; i < 8; i++) { (*pSRec).byte[cntPtr] = data.byte[i]; cntPtr++; } len = (*pSRec).byte[0]; /* 一条S记录的长度 */ - 46 - if(len < cntPtr) /* 根据S记录长度,已经完成接收 */ { cntPtr = 0; correctReceive = SrecordCheckSum(pSRec); } return correctReceive; } /* * 功能:计算一个S记录的校验和 * 输出:返回校验和计算结果,正确返回1s */ u8 SrecordCheckSum(SRecord *pSRec) { u8 i, len = 0, checkSum = 0, addSum = 0; len = (*pSRec).byte[0]; checkSum = (*pSRec).byte[len]; for(i = 0; i < len; i++) { addSum = addSum + (*pSRec).byte[i]; } addSum = 0xff - addSum; return (checkSum == addSum) ? 1 : 0; } Protocol.h /******************** 文件名:protocol.h ***********/ #ifndef _PROTOCOL_H #define _PROTOCOL_H - 47 - /*************************** 函数声明 ****************/ extern msgType parseMsg(u8 cmd); extern void GetAnswer(msgCan *from, msgCan *response, u8 bValue); extern u8 VersionCompare(msgCan verMsg); extern u8 CodeCompare(msgCan authCode); extern void DataFastFill(msgCan *pData,u8 value, u8 len,u8 defaultValue); #endif Protocol.c /************ 文件名:protocol.c *****************/ #include "control.h" #include "protocol.h" /********************* 全局变量定义 ********************/ msgCan SEED; /* 种子数据 */ msgType M_msgType; /* M_意味着是模块全局变量 */ ProcessStage pStage = PS_INIT; u16 currentVersion = 0; /* * 功能:根据当前的系统状态获取接收信息的类型 * 输入:CAN数据帧的命令字节 * 系统状态 * 输出:CAN数据帧类型 */ msgType parseMsg(u8 cmd) { M_msgType = CMD_UNDEFINED; /* 默认返回不识别数据类型 */ switch (cmd) { - 48 - case 0xFF : M_msgType = CMD_UPDATE; break; case 0xFE : M_msgType = CMD_AUTH_REQUREST; break; case 0xFD : M_msgType = CMD_AUTH_ANSWER; break; } /* 通过身份验证后才能识别以下数据 */ if(pStage == PS_ERASE || pStage == PS_DOWNLOAD || pStage == PS_RESET) { switch (cmd) { case0xFC : M_msgType = CMD_ERASE; break; case0xFB : M_msgType = CMD_DOWNLOAD; break; case0xFA : M_msgType = CMD_DOWNLOAD_FINISH; break; case0xF0 : M_msgType = CMD_MCU_RESET; break; } } return M_msgType; } /* * 功能:根据系统状态和响应类型生成响应CAN数据帧 * 输入:系统状态 * 响应类型 bValue -> 0(否定响应) 1(肯定响应) * 2...(其他响应) * 输出:响应的CAN数据帧 */ void GetAnswer(msgCan *from, msgCan *response, u8 bValue) { if(M_msgType == CMD_UPDATE) /* 握手会话状态 */ { switch (VersionCompare(*from)) { case 1 : DataFastFill(response,0x01,8,0xff); - 49 - pStage = PS_AUTH; break; case 0 : DataFastFill(response,0x10,2,0xff); } } else if(M_msgType == CMD_AUTH_REQUREST) /* 安全认证状态 */ { (*response).byte[0] = SEED.byte[0] = 0xfe; (*response).byte[1] = SEED.byte[1] = 0x01; (*response).byte[2] = SEED.byte[2] = 0x02; (*response).byte[3] = SEED.byte[3] = 0x03; (*response).byte[4] = SEED.byte[4] = 0x04; (*response).byte[5] = SEED.byte[5] = 0x05; (*response).byte[6] = SEED.byte[6] = 0x06; (*response).byte[7] = SEED.byte[7] = 0x07; } else if(M_msgType == CMD_AUTH_ANSWER) { switch (CodeCompare(*from)) { case 1 : DataFastFill(response,0x02,8,0xff); pStage = PS_ERASE; break; case 0 : DataFastFill(response,0x02,8,0xff); } } else if(M_msgType == CMD_DOWNLOAD) /* 下载状态 */ { /* 在没有下载完成一条S记录,返回错误响应,是否发送由控制装载来控制 */ switch (bValue) { case 1 : DataFastFill(response,0x04,8,0xff); break; case 0 : DataFastFill(response,0x12,2,0xff); break; } } else /* 其他只有肯定响应的情况 */ { - 50 - switch (M_msgType) { case CMD_ERASE : DataFastFill(response,0x03,8,0xff); pStage = PS_DOWNLOAD; break; case CMD_DOWNLOAD_FINISH : DataFastFill(response,0x05,8,0xff); pStage = PS_RESET; break; case CMD_MCU_RESET: DataFastFill(response,0x06,8,0xff); } } } /* * 功能:完成软件版本和MCU型号比较 * 输入:握手会话请求数据帧,该帧中包含有版本信息 */ u8 VersionCompare(msgCan verMsg) { u16 installedVersion = 0, pass = 0; installedVersion = *((@far u16 *)VERTION_ADDR); currentVersion = (verMsg.byte[1] << 8) | verMsg.byte[2]; pass = (currentVersion > installedVersion) && (SET_MCUH == verMsg.byte[3]) && (SET_MCUL == verMsg.byte[4]); return pass; } /* * 功能:完成密钥的验证 * 输入:上位机传送的密钥CAN数据帧 * 输出:密钥不匹配的个数 */ u8 CodeCompare(msgCan authCode) - 51 - { u8 i, tmp = 0, kByte = 0, error = 0; for(i = 1; i < 8; i++) { tmp = SEED.byte[i] << 3; kByte = (SEED.byte[i] >> 5) | tmp; if(authCode.byte[i] != kByte) /* 对应的密钥错误 */ { error++; } } return (error == 0) ? 1 : 0; } /* * 功能:快速填充一个CAN数据帧 * 输入:pCAN 需要填充的CAN数据帧指针 * value 该数据帧的有效值 * len 有效值的长度 *defaultValue 默认的填充值 */ void DataFastFill(msgCan *pData,u8 value, u8 len,u8 defaultValue) { u8 i; /*** 还需要填充CAN数据结构中的其他成员 ***/ (*pData).CanID = 0x01; (*pData).IDE = CAN_ID_STANDARD; (*pData).RTR = CAN_RTR_DATA; (*pData).LENGTH = 8; - 52 - for(i = 0; i < 8; i++) { if(i < len) { (*pData).byte[i] = value; } else { (*pData).byte[i] = defaultValue; } } } Can.h /************** can.h ****************/ #ifndef _CAN_H #define _CAN_H /*--------------------------常量定义--------------------------------*/ #define CAN_NormalMODE_MASK (u8)0x03 #define CAN_SLTestMODE_MASK (u8)0x03 #define CAN_EXTID_SIZE (u32)0x1FFFFFFF #define CAN_STDID_SIZE (u16)0x07FF /*-------------CAN页面映射选项定义-----------------------------------*/ typedef enum { CAN_Page_TxMailBox0 = 0x00, //CAN页面中是TX mailbox 0 reg CAN_Page_TxMailBox1 = 0x01, //CAN页面中是TX mailbox 1 reg CAN_Page_TxMailBox2 = 0x05, //CAN页面中是TX mailbox 1 reg,其中 需要CAN_DGR.TxM2E=1 CAN_Page_Filter01 = 0x02, //CAN页面中是Filters 0 & 1 reg - 53 - CAN_Page_Filter23 = 0x03, //CAN页面中是Filters 2 &3 reg CAN_Page_Filter45 = 0x04, //CAN页面中是Filters 4 & 5 reg CAN_Page_Config = 0x06, //CAN页面中是Configuration control/status reg CAN_Page_RxFIFO = 0x07 //CAN页面中是RX FIFO registers }CAN_Page_tag; /*---------------邮箱状态定义----------------------------------------*/ typedef enum { CAN_TxStatus_NoMailBox = 0xF4, /*!< CAN cell did not provide an empty mailbox */ CAN_TxStatus_MailBoxEmpty = 0xF5, /*!< CAN Tx mailbox is Empty */ CAN_TxStatus_MailBox0Ok = 0x00, /*!< CAN transmission succeeded by mail box 1*/ CAN_TxStatus_MailBox1Ok = 0x01, /*!< CAN transmission succeeded by mail box 2*/ CAN_TxStatus_MailBox2Ok = 0x05 /*!< CAN transmission succeeded by mail box 3*/ }CAN_TxStatus_tag; /*----------------------CAN模式类型定义----------------------------------*/ typedef enum { CAN_OperatingMode_Initialization = 0x00, //初始化模式 CAN_OperatingMode_Normal = 0x01 //正常工作模式 }Can_StateTransitionType; // CAN模式定义 /*-----------------------API原型声明------------------------------------*/ void Can_InitController(void ); static void Can_SetControllerMode(Can_StateTransitionType Transition ); void CAN_Transmit(msgCan *PduInfo); void CAN_Receive(msgCan *Rx_PduInfo); #endif Can.c /*********** can.c ***************/ #include "control.h" - 54 - #include "can.h" CAN_Page_tag CAN_Page; //邮箱设置 void Can_InitController(void) { u16 WaitAck = 0x0000; u16 timeout = 0xFFFF; CAN_Page = CAN->PSR; // 获取当前页面映射 选择寄存器中的页面内容 /*使能Can模块,并使其进入初始化模式*/ CAN->MCR &= ~CAN_MCR_SLEEP; CAN->MCR |= CAN_MCR_INRQ; /*等待确认进入初始化模式*/ while((WaitAck != timeout)&&((CAN->MSR & CAN_MSR_INAK)!=CAN_MSR_INAK)) { WaitAck++; } if(WaitAck != timeout) { CAN->MCR |=0x6C; /*-------------配置为时间特性------------------------------*/ CAN->PSR = CAN_Page_Config; //0x06 把设置/诊断寄存器映射到内存中 CAN->Page.Config.BTR1 &= 0x00; //清零 CAN->Page.Config.BTR2 &= 0x00; //清零 CAN->Page.Config.BTR1 |=0x00; CAN->Page.Config.BTR2 |=0x23; //250Kbs /*--------------退出初始化模式------------------------------*/ CAN->MCR &= (~CAN_MCR_INRQ); //退出初始化 WaitAck=0x0000; while((WaitAck!=timeout)&&((CAN->MSR & CAN_MSR_INAK)==CAN_MSR_INAK)) { WaitAck++; } - 55 - WaitAck=0x0000; if(WaitAck!=timeout) { WaitAck++; } /*Restore Last Page*/ CAN->PSR = CAN_Page; // 恢复设置前的页面映射关系 //CAN_SelectPage(CAN_Page); // 恢复设置前的页面映射关系 /*--------------输入输出引脚配置----------------------------*/ GPIOG->DDR |= 0x01; //PG0引脚用于发送 GPIOG->DDR &= 0xFD; //PG1引脚用于接收 GPIOG->CR1 &= 0xFD; //浮空输入 GPIOG->CR2 &= 0xFC; //PG1输入口禁止外部中断,输出速度最大为2MHZ GPIOG->CR1 |= 0x01; // 推挽输出 GPIOG->ODR &= 0x00; } // 初始化过滤器 CAN_Page = CAN->PSR; Can_SetControllerMode(CAN_OperatingMode_Initialization); CAN->PSR = CAN_Page_Config; //页面映射内容配置和诊断 CAN->Page.Config.FCR1 &= ~(CAN_FCR1_FACT0 | CAN_FCR1_FSC00 | CAN_FCR1_FSC01); //清位 CAN->Page.Config.FCR1 |= 0x00; //过滤器组0 CAN->Page.Config.FMR1 &= ~(CAN_FMR1_FML0 | CAN_FMR1_FMH0); //清位 CAN->Page.Config.FMR1 |= 0x00;//高低位寄存器工作在屏蔽位模式 CAN->PSR = CAN_Page_Filter01; //页面映射内容过滤器组01 //32位过滤器组 CAN->Page.Filter.FR[0] = 0x00; CAN->Page.Filter.FR[1] = 0x20; CAN->Page.Filter.FR[2] = 0x00; CAN->Page.Filter.FR[3] = 0x00; CAN->Page.Filter.FR[4] = 0xFF; CAN->Page.Filter.FR[5] = 0xFF; CAN->Page.Filter.FR[6] = 0xFF; - 56 - CAN->Page.Filter.FR[7] = 0xFF; CAN->PSR = CAN_Page_Config; // 进入配置页面 CAN->Page.Config.FCR1 |= CAN_FCR1_FACT0;// 激活过滤器组0 CAN->PSR = CAN_Page; // 恢复设置前的页面映射关系 Can_SetControllerMode(CAN_OperatingMode_Normal); } static void Can_SetControllerMode(Can_StateTransitionType Transition ) { u16 timeout = 0xFFFF; u16 WaitAck = 0X0000; if(Transition==CAN_OperatingMode_Initialization) { CAN->MCR |= CAN_MCR_INRQ; /*使能Can模块,并使其进入初始化模式*/ /*等待确认进入初始化模式*/ while((WaitAck!=timeout)&&((CAN->MSR & CAN_MSR_INAK)!=CAN_MSR_INAK)) { WaitAck++; } } else if(Transition==CAN_OperatingMode_Normal) { CAN->MCR &= ~(CAN_MCR_INRQ | CAN_MCR_SLEEP); // 退出初始化和睡眠模式 /*等待确认进入正常工作模式*/ while((WaitAck!=timeout)&&((CAN->MSR & CAN_NormalMODE_MASK)!=0)) { WaitAck++; } //其中CAN_NormalMODE_MASK是CAN_MSR_SLAK和CAN_MSR_INAK的整体别名。 } else { } - 57 - CAN->MCR &= (~CAN_MCR_INRQ); //退出初始化 } void CAN_Transmit(msgCan *PduInfo) { int i; CAN_TxStatus_tag CAN_TxStatus; CAN_TxStatus = CAN_TxStatus_NoMailBox; CAN_Page = CAN->PSR; //返回当前页面选择寄存器中页面映射内容。 /*----------------准备ID------------------------------*/ CAN->PSR = CAN_TxStatus_MailBox0Ok; // 把相应邮箱寄存器映射到内存中 if((PduInfo->IDE ==CAN_ID_STANDARD)&&(PduInfo->RTR==CAN_RTR_DATA)) { PduInfo->CanID &= CAN_STDID_SIZE; // 获取11位的ID值 CAN->Page.TxMailbox.MIDR2 &= 0x00; // 清位 CAN->Page.TxMailbox.MIDR2 |= (u8)(PduInfo->CanID<< 2); //取0-5位 CAN->Page.TxMailbox.MIDR1 &= 0x00; //清位 CAN->Page.TxMailbox.MIDR1 |=((u8)(PduInfo->CanID>>6)|PduInfo->IDE|PduInfo->RTR); CAN->Page.TxMailbox.MDLCR &= (u8)0xF0; //清楚以前的数据长度 CAN->Page.TxMailbox.MDLCR |= PduInfo->LENGTH;//数据长度 for(i=0;iLENGTH;i++ ) { CAN->Page.TxMailbox.MDAR[i]= (u8)PduInfo->byte[i];//准备待发数据 } CAN->Page.TxMailbox.MCSR |= CAN_TMIDxR_TXRQ;//请求发送数据 } CAN->PSR = CAN_Page; // 恢复设置前的页面映射关系 } void CAN_Receive(msgCan *Rx_PduInfo) { - 58 - u8 i=0; if((CAN->RFR&0x03) == 1) { CAN_Page = CAN->PSR; //返回当前页面选择寄存器中页面映射内容。 CAN->PSR |= CAN_Page_RxFIFO; Rx_PduInfo->IDE=CAN->Page.RxFIFO.MIDR1&0x40; if((!(Rx_PduInfo->IDE&0x40))&&(!(Rx_PduInfo->RTR&0x20))) { Rx_PduInfo->CanID = ((((u16)CAN->Page.RxFIFO.MIDR1) << 6 ) | ((u16)CAN->Page.RxFIFO.MIDR2>>2)); Rx_PduInfo->RTR = CAN->Page.RxFIFO.MIDR1 & 0x20; Rx_PduInfo->LENGTH = CAN->Page.RxFIFO.MDLC&0x0F; for(i=0;i<8;i++) { Rx_PduInfo->byte[i] = (u8)CAN->Page.RxFIFO.MDAR[i]; } CAN->RFR |= CAN_RFR_RFOM;//释放接收邮箱 } CAN->PSR = CAN_Page; // 恢复设置前的页面映射关系 } } Flash.h /* * 文件名:flash.h */ #ifndef _FLASH_H #define _FLASH_H extern void Init_FLASH(void); extern void FlashErase(u16 addrBase, u16 addrLen); extern void FlashSrecordWrite(SRecord *pSRec); extern void EepromWriteVersion(u16 addr, u16 version); - 59 - #endif Flash.c /********************** 文件名:flash.c **********/ #include "control.h" #include "flash.h" void Init_FLASH(void) { FLASH_CR1 = 0X01; FLASH_CR2 = 0x80; FLASH_NCR2 = 0x7f; UBC = 0X80; NUBC = 0X7F; FLASH_CR2 = 0x00; FLASH_NCR2 = 0xff; } void FlashErase(u16 addrBase, u16 addrLen) { u16 i; PC_ODR = 0xff; FLASH_PUKR = 0x56; FLASH_PUKR = 0xae; for(i = 0; i <= addrLen; i++) { - 60 - *((@far u8 *)addrBase++) = 0x00; } FLASH_IAPSR &= 0xfd; PC_ODR = 0x00; } void FlashSrecordWrite(SRecord *pSRec) { u8 i,len; u16 addr; FLASH_PUKR = 0x56; FLASH_PUKR = 0xae; /* len = 地址字节(2)+数据+校验和(1) */ len = (*pSRec).byte[0]; addr = ((*pSRec).byte[2] << 8) | (*pSRec).byte[3]; /* 长度(1)+地址(2)+数据+校验和(1)=len+1 */ for(i = 4; i <= len; i++) { *((@far u8 *)addr++) = (*pSRec).byte[i]; } FLASH_IAPSR &= 0xfd; } void EepromWriteVersion(u16 addr, u16 version) { FLASH_DUKR = 0xae; FLASH_DUKR = 0x56; *((@far u16 *)addr++) = version; - 61 - FLASH_IAPSR = 0xff; } stm8_interrupt_vector.s的串口中断部分 dc.b 130 dc.b 0x00 dc.w 0xa050 测试应用程序的main.c /* MAIN.C file * * Copyright (c) 2002-2005 STMicroelectronics */ #include void Init_UART(void) { /* 波特率为9600 */ UART1_BRR2 = 0x2; UART1_BRR1 = 0x34; UART1_CR1 = 0x0; UART1_CR2 = 0x2c; } void UART_SendChar(unsigned char ch) { while((UART1_SR & 0x80) == 0x00); // 若发送寄存器不空,则等待 UART1_DR = ch; // 将要发送的字符送到数据寄存器 } void delay(int ms) - 62 - { int i,j; for(i = 0; i< ms; i++) { for(j = 0; j< 1000; j++) ; } } main() { int count = 0; PC_DDR = 0XFF; PC_CR1 = 0XFF; Init_UART(); while (1) { PC_ODR = count; count++; count = count%4; delay(10); } } @far @interrupt void ISR_UART1_Receive(void) { unsigned char ch; ch = UART1_DR; // 读入接收到的字符 UART_SendChar('a'); } - 63 -
/
本文档为【基于STM8的在线升级系统】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索