C8051F120的串口升级程序应用
本人实现该功能的原因
记得是在2018年的8月份,公司放高温假比前两年要晚上一两个星期,媳妇在放高温假的前一个星期就请了产假回老家了。凑巧的是在高温假之后马上就是预产期,所以在一星期的高温假之后,我紧接着又请了一周的陪产假,而过了预产期一周之后还没生,我又请了一周的假,连休了近20天(不上班爽歪歪)。这两个星期就是在老家陪着媳妇散步,等待孩子的到来。那时候最搞笑的是每天小区的人看到总会问“生了没?”,“还没生了?”。
而我请了陪产假而别人正式上班,有个同事(做重要项目的一个部门领导)说甲方要求增加串口升级功能,不能改个程序老得拆开。所以就有了这个事儿,虽然是在将近1年后才做完这个功能。(儿子都1周岁了,时间过得好快,这周末就回老家看你)。
串口升级的原理
串口升级程序的功能的实现,是通过异步通信串口(UART)将新的程序代码下载到单片机中,将旧程序替换掉,从而实现程序更新。而为了实现该功能,就需要一个引导程序来实现代码的更新。
先写一个引导程序BootLoader,下载到单片机中。而正在运行的程序则通过引导程序下载保存到单片机的Flash中。引导程序在Flash中位置如下:
0x1FFFF Flash_MAX Addr
… … … … 引导程序及一些控制参数保存位置
0x0C000 引导程序的起始位置
0x0003~0xBFFF 要升级运行的程序代码
0x00000 程序的起始,头三个字节为跳转命令,跳转到引导程序
其中,位置的设置只是个人应用设置,可根据自己的需要修改。
因为程序的一些中断等在Flash中的位置是固定的,所以引导程序中关于异步串口UART的操作都不是通过中断而是通过查询的方式来实现的。
程序在开始运行时,先进入BootLoader程序中,在开始约两秒时间内检测串口数据,看是否下载程序,如果收到下载程序命令,则进入下载程序流程。如果在这两秒内未检测到下载命令,则程序继续运行下一步骤。
程序中会读取一个固定位置的值,若该值为0x01表示当前程序存储区存储着正确的程序,则读取存储程序起始位置的数据,并跳转到正确程序的起始位置开始运行程序;则表示无效程序,会一直运行BootLoader程序等待下载程序。
整个流程看现象如同89C52等芯片的通过串口下载更新程序。
具体实现
BootLoader的程序代码起始位置设定
要实现BootLoader的程序起始位置为0xC000,通过在Keil工程文件中的设置选项中设置BL51 Locate 如下图:
按此设置编译下载后,Flash中的0x0000~0x0002为跳转命令和地址,跳转到BootLoader程序的运行位置,而0x0003 - 0xBFFF 的值为0xFF,即无程序,后续串口将真正要运行的程序保存到其中。
BootLoader的通信
要通过串口进行程序下载,就需要通过一定的通信协议来进行下载。
通信协议要能够实现进入下载流程、发送程序、对下载的数据校验等功能。
通信协议
在我的应用中,使用的通信协议如下:
帧头 | 命令 | 数据 | 帧尾 | 校验 |
---|---|---|---|---|
0xA5 0x5A | 0xF0开始下载 | 12字节 | 0xAA | CRC校验,同DS18B20的校验相同 |
0xF1~0xFA下载 12字节,前2字节表示数据id(索引地址),后10个字节表示数据,0xF1 – 数据为1字节,0xF2 – 数据为2字节…,0xFA – 数据为10字节 | ||||
0xFB校验 12字节 | 前两字节表示数据长度,第1字节表示代码CRC校验值(最后1字节回报0失败,1校验成功) | |||
0xFC ClearFlash 12字节 |
通信代码
/*
* @Author: xiaoyong yang
* @Date: 2019-04-14 15:10:13
* @Last Modified by: xiaoyong yang
* @Last Modified time: 2019-08-15 21:33:18
*/
#include "Uart.h"
#include "Main.h"
#include "DownLoadCtrl.h"
#include "Communicate.h"
#define FRAME_LENGTH 17
#define FRAME_HEAD1 0xA5
#define FRAME_HEAD2 0x5A
#define FRAME_END 0xAA
#define CMD_DOWNLOAD_START 0xF0 //开始下载
#define CMD_DOWNLOAD_CHECK 0xFB //校验
#define CMD_CLEAR_FLASH 0xFC //清除FLash
static unsigned char frameBuf[FRAME_LENGTH] = {0};
/**
* 名称: PutInBuf
* 功能: 将数据放入到缓存区中
* 参数: buf - 要放入数据的缓冲区
* inData - 要放入缓冲区中的数据
* bufSize - 缓冲数组的大小
* 返回值: void
* */
void PutInBuf(unsigned char *buf,unsigned char inData, unsigned char bufSize)
{
unsigned char i;
for(i=0;i<bufSize-1;i++)
{
buf[i] = buf[i+1];
Idle();
}
buf[bufSize-1] = inData;
}
/**
* 名称: Sum
* 功能: 求和
* 参数: buf - 要进行和运算的缓冲区
* offset - 起始位移
* length - 要进行计算的长度
* 返回值: 计算的和
* */
unsigned char Sum(unsigned char *buf,unsigned char offset,unsigned char length)
{
unsigned char sum = 0,i;
unsigned char sumLength = offset+length;
for(i=offset;i<sumLength;i++)
{
sum += buf[i];
Idle();
}
return sum;
}
/**
* 名称: ScanUart
* 功能: 扫描串口数据
* 参数: void
* 返回值: void
* */
void ScanUart(void)
{
int temp = -1;
unsigned int idx = 0,length = 0;
if(COM0_RxBufLength()>0)
{
temp = COM0_GetByte();
if(temp != -1)
{
PutInBuf(frameBuf,(unsigned char)(temp&0x00FF),FRAME_LENGTH);
if(FRAME_HEAD1 == frameBuf[0] && FRAME_HEAD2 == frameBuf[1] && FRAME_END == frameBuf[15])
{
if(frameBuf[16] == Sum(frameBuf,0,FRAME_LENGTH-1))
{
switch(frameBuf[2])
{
case CMD_DOWNLOAD_START:
flag_dowoLoad = 1;
StartDownload();
UpLoadInfo(CMD_DOWNLOAD_START,&frameBuf[3]);
break;
case 0xF1:
case 0xF2:
case 0xF4:
case 0xF5:
case 0xF6:
case 0xF7:
case 0xF8:
case 0xF9:
case 0xFA:
idx = frameBuf[3]*256+frameBuf[4];
WriteDownLoadBytes(idx,&frameBuf[5],frameBuf[2]&0x0F);
UpLoadInfo(frameBuf[2],&frameBuf[3]);
break;
case 0xF3:
idx = frameBuf[3]*256+frameBuf[4];
if(idx == 0)
{
WriteStartJumpData(&frameBuf[5]);
}
else
{
WriteDownLoadBytes(idx,&frameBuf[5],3);
}
UpLoadInfo(frameBuf[2],&frameBuf[3]);
break;
case CMD_DOWNLOAD_CHECK:
length = frameBuf[3]*256+frameBuf[4];
frameBuf[5] = CheckCode(length,frameBuf[5]);
UpLoadInfo(CMD_DOWNLOAD_CHECK,&frameBuf[3]);
break;
case CMD_CLEAR_FLASH:
ClearFlash();
UpLoadInfo(CMD_CLEAR_FLASH,&frameBuf[3]);
break;
}
}
}
}
}
}
/**
* 名称: UpLoadInfo
* 功能: 上报信息
* 参数: cmd - 命令
* datas - 要上报的数据
* 返回值: void
* */
void UpLoadInfo(unsigned char cmd, unsigned char *datas)
{
unsigned char sendBuf[FRAME_LENGTH] = {0},i;
sendBuf[0] = 0xA5;
sendBuf[1] = 0x5A;
sendBuf[2] = cmd;
for(i=0;i<12;i++)
{
sendBuf[i+3] = datas[i];
}
sendBuf[15] = 0xAA;
for(i=0;i<FRAME_LENGTH-1;i++)
{
sendBuf[16] += sendBuf[i];
COM0_SendByte(sendBuf[i]);
}
COM0_SendByte(sendBuf[16]);
}
/* end of file */
下载控制
程序升级下载的流程控制的代码如下:
/*
* @Author: xiaoyong yang
* @Date: 2019-04-15 08:21:38
* @Last Modified by: xiaoyong yang
* @Last Modified time: 2019-08-15 21:36:40
*/
#include "F120_FlashUtils.h"
#include "C8051F120.h"
#include "CRC_8.h"
// This routine writes <byte> to the linear FLASH address <addr>.
// Linear map is decoded as follows:
// Linear Address Bank Bank Address
// ------------------------------------------------
// 0x00000 - 0x07FFF 0 0x0000 - 0x7FFF
// 0x08000 - 0x0FFFF 1 0x8000 - 0xFFFF
// 0x10000 - 0x17FFF 2 0x8000 - 0xFFFF
// 0x18000 - 0x1FFFF 3 0x8000 - 0xFFFF
#define CODE_SELECT_ADDR 0x14000 //当前的工作flash的模式选择的地址
#define CODE_START_ADDR 0x14001
/**
* 名称:StartDownLoad
* 功能:开始下载
* 参数: void
* 返回值: void
* */
void StartDownload(void)
{
FLASH_PageErase(CODE_SELECT_ADDR,0);
}
/**
* 名称: WriteDownLoadBytes
* 功能: 写下载的数据
* 参数: idx - 要写的数据的索引起始
* writeDatas - 要写入flash中的数据
* length - 要写的数据长度
* 返回值: void
* */
void WriteDownLoadBytes(unsigned int idx,unsigned char *writeDatas,unsigned char length)
{
unsigned char temp[10],i;
FLASH_Read(temp,idx,length,0);
for(i=0;i<length;i++)
{
if(temp[i] != writeDatas[i])
{
FLASH_Write(idx+i,&writeDatas[i],length-i,0);
return;
}
}
}
/**
* 名称: WriteStartJumpData
* 功能: 写开始跳转的地址数据
* 参数: writeDatas - 要写的数据
* 返回值: void
* */
void WriteStartJumpData(unsigned char *writeDatas)
{
unsigned char temp[3],i;
FLASH_Read(temp,CODE_START_ADDR,3,0);
for(i=0;i<3;i++)
{
if(temp[i] != writeDatas[i])
{
FLASH_Write(CODE_START_ADDR,writeDatas,3,0);
return;
}
}
}
/**
* 名称: ClearFlash
* 功能: 清除Flash
* 参数: void
* 返回值: void
* */
void ClearFlash(void)
{
unsigned char i;
FLASH_Clear(3,1024-3,0);
for(i=1;i<48;i++)
{
FLASH_PageErase(0x400*i,0);
}
}
/**
* 名称: CheckCode
* 功能: 校验代码
* 参数: length - 要进行校验的代码长度
* checkWork - 校验的值
* 返回值: 校验的结果,0:校验失败 1:校验成功
* */
unsigned char CheckCode(unsigned int length,unsigned char checkWork)
{
unsigned char crc = 0;
unsigned int i;
for(i=0;i<3;i++)
{
crc =DoCrc8(crc,FLASH_ByteRead(CODE_START_ADDR+i,0));
}
for(i=3;i<length;i++)
{
crc = DoCrc8(crc,FLASH_ByteRead(i,0));
}
if(checkWork == crc)
{
FLASH_ByteWrite(CODE_SELECT_ADDR,1,0);
return 1;
}
return 0;
}
/**
* 名称: JumpWorkSpace
* 功能: 跳转到工作区
* 参数: void
* 返回值: void
* */
void JumpWorkSpace(void)
{
unsigned char state = 0;
unsigned int startAddr = 0;
state = FLASH_ByteRead(CODE_SELECT_ADDR,0);
if(state == 1)
{
startAddr = FLASH_ByteRead(CODE_START_ADDR+1,0);
startAddr *= 256;
startAddr += FLASH_ByteRead(CODE_START_ADDR+2,0);
(*((void(*)(void))startAddr) )();
}
}
/* end of file */
程序的流程(主函数)
/*
* @Author: xiaoyong yang
* @Date: 2019-04-14 14:58:36
* @Last Modified by: xiaoyong yang
* @Last Modified time: 2019-08-15 18:00:24
*/
#include "Main.h"
#include "C8051F120.h"
#include "Uart.h"
#include "DownLoadCtrl.h"
#include "Communicate.h"
#define MAX_WAIT_DOWN 200000
extern void Init_Device(void);
static unsigned long count = 0;
unsigned char flag_dowoLoad = 0;
/**
* 名称: Idle
* 功能: 空闲函数,喂狗
* 参数: void
* 返回值: void
* */
void Idle(void)
{
//WDTCN = 0xA5;//喂狗
// WDTCN = 0xDE;
// WDTCN = 0xAD;
}
/**
* 名称: InitModule
* 功能: 初始化模块
* 参数: void
* 返回值: void
* */
void InitModule(void)
{
ResetAllPin();
}
/**
* 名称: DownLoadCode
* 功能: 下载代码
* 参数: void
* 返回值: void
* */
void DownLoadCode(void)
{
EA = 0;
COM0_Initialize();
while(1)
{
Idle();
COM0_TR();
ScanUart();
}
}
/**
* 名称: WaitDownLoad
* 功能: 等待程序下载,程序开始运行后约2S时间等待下载程序
* 参数: void
* 返回值: void
* */
void WaitDownLoad(void)
{
unsigned long unloadCnt = 0;
EA = 0;
COM0_Initialize();
while(unloadCnt < MAX_WAIT_DOWN || flag_dowoLoad )
{
Idle();
COM0_TR();
ScanUart();
unloadCnt++;
}
}
/**
* 名称: Main
* 功能: 主函数,程序入口
* 参数: void
* 返回值: void
* */
void Main(void)
{
EA = 0;
Init_Device();
EA = 1;
WaitDownLoad();
JumpWorkSpace();
while(1)
{
DownLoadCode();
}
}
/* end of file */
目标代码的下载
目标代码的文件格式
要向MCU中下载更新的程序代码的文件格式为 “.Bin”文件。我们要得到Bin文件,可以通过Keil生成 “.Hex”文件,然后将Hex文件转换成Bin文件。(关于".Bin"文件和".Hex"文件,可自行去了解,本文不进行介绍)。
文件转换的程序可以用“51Hex_Bin.exe”,也可自行搜索相应的软件。
目标代码下载的方式
个人为了实现程序的下载,特写了一个对应的下载程序,界面如下:
贴出部分C#代码:
string binFilePath;
FileStream binFileStream = null;
BinaryReader binReader = null;
byte[] datas = null;
private void button_selectBin_Click(object sender, EventArgs e)
{
OpenFileDialog binFileSelectDialog = new OpenFileDialog();
binFileSelectDialog.Filter = "bin文件(*.bin)|*.bin";
DialogResult result = binFileSelectDialog.ShowDialog();
if(result == DialogResult.OK)
{
binFilePath = binFileSelectDialog.FileName;
textBox_bin.Text = binFilePath;
fileOk = true;
binFileStream = new FileStream(binFilePath, FileMode.Open, FileAccess.Read);
binReader = new BinaryReader(binFileStream);
datas = new byte[binFileStream.Length];
datas = binReader.ReadBytes((int)binFileStream.Length);
}
}
private void button_downLoad_Click(object sender, EventArgs e)
{
if(uartOk && fileOk)
{
DownLoadCode();
}
else
{
if (!uartOk)
MessageBox.Show("请选择串口!","提示");
else
MessageBox.Show("请选择要下载的Bin文件!","提示" );
}
}
bool startOk = false;
bool clearOk = false;
bool downLoadOk = false;
bool checkOk = false;
int downloadOkIdx = -1;
int downFileLength = 0;
byte checkWord = 0x00;
private void DownLoadCode()
{
startOk = false;
int count = 0;
int countSend = 0;
while (!startOk)
{
if(count % 10 == 0)
{
if(!StartDownLoad())
{
return;
}
countSend++;
}
Thread.Sleep(1);
count++;
if(countSend > 200)
{
MessageBox.Show("未能成功下载", "下载失败!");
return;
}
}
int fileLength = (int)binFileStream.Length;
downFileLength = fileLength;
int lastLength = downFileLength;
downLoadProgress.Maximum = (fileLength + 1);
downLoadProgress.Value = 0;
downLoadProgress.Show();
binReader.Read(datas, 0, fileLength);
clearOk = false;
count = 0;
countSend = 0;
while (!clearOk)
{
if (count % 50 == 0)
{
ClearFlash();
countSend++;
}
Thread.Sleep(1);
count++;
if (countSend > 20)
{
MessageBox.Show("未能成功清除Flash", "下载失败!");
return;
}
}
byte crc = 0;
int writeAddr = 0;
downloadOkIdx = -1;
byte[] writeDatas = new byte[10];
for (int i=0; i<3;i++)
{
crc = DoCrc8(crc, datas[i]);
writeDatas[i] = datas[i];
}
downLoadOk = false;
count = 0;
countSend = 0;
while (!downLoadOk || downloadOkIdx != writeAddr)
{
if (count % 10 == 0)
{
DownLoadByte(writeAddr, writeDatas, 3);
downLoadProgress.Value = 3;
countSend++;
}
Thread.Sleep(1);
count++;
if (countSend > 20)
{
MessageBox.Show("下载失败");
return;
}
}
lastLength -= 3;
int n = lastLength / 10;
int mod = lastLength % 10;
for (int i = 0; i < n; i++)
{
writeAddr = 3 + i * 10;
for (int j=0; j <10; j++)
{
crc = DoCrc8(crc, datas[writeAddr+j]);
writeDatas[j] = datas[writeAddr + j];
}
downLoadOk = false;
count = 0;
countSend = 0;
while (!downLoadOk || downloadOkIdx != writeAddr)
{
if (count % 10 == 0)
{
DownLoadByte(writeAddr, writeDatas, 10);
downLoadProgress.Value = 13 + i * 10;
countSend++;
}
Thread.Sleep(1);
count++;
if (countSend > 20)
{
MessageBox.Show("下载失败");
return;
}
}
}
writeAddr = n * 10 + 3;
for (int i = 0;i<mod; i++)
{
crc = DoCrc8(crc, datas[writeAddr + i]);
writeDatas[i] = datas[writeAddr + i];
}
downLoadOk = false;
count = 0;
countSend = 0;
while (!downLoadOk || downloadOkIdx != writeAddr)
{
if (count % 10 == 0)
{
DownLoadByte(writeAddr, writeDatas, (byte)mod);
downLoadProgress.Value = 3 + n * 10 + mod;
countSend++;
}
Thread.Sleep(1);
count++;
if (count > 50)
{
MessageBox.Show("下载失败");
return;
}
}
MessageBox.Show("下载成功,开始校验", "提示");
CheckCode();
}
private bool StartDownLoad()
{
byte[] datas = new byte[12];
return UartSend(CMD_DOWNLOAD_START, datas);
}
public bool ClearFlash()
{
byte[] datas = new byte[12];
return UartSend(CMD_DOWNLOAD_CLEAR, datas);
}
private bool DownLoadByte(int idx,byte[] datas,byte length)
{
byte[] sendDatas = new byte[12];
sendDatas[0] = (byte)((idx & 0xFF00) >> 8);
sendDatas[1] = (byte)(idx & 0xFF);
if (datas.Length <= 10)
{
for (int i = 0; i < datas.Length; i++)
{
sendDatas[2 + i] = datas[i];
}
}
return UartSend((byte)(0xF0|length), sendDatas);
}
private bool DownLoadCheck(int length,byte checkCode)
{
byte[] datas = new byte[12];
datas[0] = (byte)((length & 0xFF00) >> 8);
datas[1] = (byte)(length & 0xFF);
datas[2] = checkCode;
return UartSend(CMD_DOWNLOAD_CHECK, datas );
}
private bool UartSend(byte cmd, byte[] datas)
{
byte[] sendDatas = new byte[17];
sendDatas[0] = FRAME_HEAD1;
sendDatas[1] = FRAME_HEAD2;
sendDatas[2] = cmd;
if(datas.Length <= 12)
{
for(int i=0;i<datas.Length;i++)
{
sendDatas[3 + i] = datas[i];
}
}
sendDatas[15] = FRAME_END;
sendDatas[16] = 0;
for (int i = 0; i < 16; i++)
{
sendDatas[16] += sendDatas[i];
}
if (serialPort1 != null && serialPort1.IsOpen)
{
try
{
serialPort1.Write(sendDatas, 0, sendDatas.Length);
return true;
}
catch (Exception e)
{
MessageBox.Show(e.Message, "串口异常");
CloseUart();
}
}
else
{
MessageBox.Show("请选择串口并打开","提示");
}
return false;
}
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
int n = serialPort1.BytesToRead;
byte[] buf = new byte[n];
serialPort1.Read(buf, 0, n);
for (int i = 0; i < n; i++)
{
ScanUart(buf[i]);
}
}
byte[] frameBuf = new byte[17];
private void ScanUart(byte inData)
{
for(int i=0;i<16;i++)
{
frameBuf[i] = frameBuf[i + 1];
}
frameBuf[16] = inData;
if(FRAME_HEAD1 == frameBuf[0] && FRAME_HEAD2 == frameBuf[1] && FRAME_END == frameBuf[15])
{
byte sum = 0;
for(int i=0;i<16;i++)
{
sum += frameBuf[i];
}
if (sum == frameBuf[16])
{
switch(frameBuf[2])
{
case CMD_DOWNLOAD_START:
startOk = true;
break;
case 0xF1:
case 0xF2:
case 0xF3:
case 0xF4:
case 0xF5:
case 0xF6:
case 0xF7:
case 0xF8:
case 0xF9:
downloadOkIdx = frameBuf[3] * 256 + frameBuf[4];
downLoadOk = true;
break;
case 0xFA:
downloadOkIdx = frameBuf[3] * 256 + frameBuf[4];
downLoadOk = true;
break;
case CMD_DOWNLOAD_CHECK:
int length = frameBuf[3] * 256 + frameBuf[4];
if(length == binFileStream.Length && frameBuf[5] == 0x01)
{
checkOk = true;
}
break;
case CMD_DOWNLOAD_CLEAR:
clearOk = true;
break;
}
}
}
}
private void button_check_Click(object sender, EventArgs e)
{
if (uartOk && fileOk)
{
CheckCode();
}
else
{
if (!uartOk)
MessageBox.Show("请选择串口!", "提示");
else
MessageBox.Show("请选择要下载的Bin文件!", "提示");
}
}
private void CheckCode()
{
byte crcValue = 0;
for(int i=0;i<binFileStream.Length;i++)
{
crcValue = DoCrc8(crcValue, datas[i]);
}
checkOk = false;
int count = 0;
int countSend = 0;
while (!checkOk)
{
if (count % 10 == 0)
{
DownLoadCheck((int)binFileStream.Length, crcValue);
countSend++;
}
Thread.Sleep(1);
count++;
if (countSend > 20)
{
MessageBox.Show("校验失败", "提示");
return;
}
}
MessageBox.Show("校验成功", "提示");
}