四轴飞行器DIY附录
MK的融合算法解释
上面已经让大家足够了解加速度计和陀螺仪做数据融合的必要性和难度了。下面就结合目前比较受大家欢迎的MK(http://www.mikrokopter.com/)开源四轴飞行器来说说老外是如何做的。由于无法找到官方文档,所以这些都是我个人的理解,可能会有不正确的地方。
首先我们使用V0.71h Code Redesign killagreg的代码,这个代码基本都是英文变量名和注释,看起来容易些。所有核心的姿态控制都在fc.c文件里。这个代码可以在MK的CVS中免费下到,位于branches目录...
MK的融合算法解释
上面已经让大家足够了解加速度计和陀螺仪做数据融合的必要性和难度了。下面就结合目前比较受大家欢迎的MK(http://www.mikrokopter.com/)开源四轴飞行器来说说老外是如何做的。由于无法找到官方文档,所以这些都是我个人的理解,可能会有不正确的地方。
首先我们使用V0.71h Code Redesign killagreg的代码,这个代码基本都是英文变量名和注释,看起来容易些。所有核心的姿态控制都在fc.c文件里。这个代码可以在MK的CVS中免费下到,位于branches目录下。
MK的融合代码主要做了3件事:
(1)即时融合:就是实时地根据加速度计的数值反推出陀螺仪积分应有的数值,然后根据当前的陀螺仪积分进行调整。
(2)长期融合:在代码里它用0.5秒的时间采集加速度计的数据,然后到0.5秒时对这些数据进行平均,依此得到一个相对稳定的加速度计数值。根据这个数值来相对准确地知道四轴飞行器这0.5秒的姿态,然后再修整调整量,做到自动稳定到平衡位置。
(3)根据调整量的大小,决定是否需要修改陀螺仪中立点。
下面结合代码来详细解说:
首先是即时融合的代码,位于fc.c的890行开始。
if(!Looping_Nick && !Looping_Roll) // if not lopping in any direction
{
int32_t tmp_long, tmp_long2;
if(FCParam.Kalman_K != -1)
{
// determine the deviation of gyro integral from averaged acceleration sensor
tmp_long = (int32_t)(IntegralNick / ParamSet.GyroAccFactor - (int32_t)Mean_AccNick);
tmp_long = (tmp_long * FCParam.Kalman_K) / (32 * 16);
tmp_long2 = (int32_t)(IntegralRoll / ParamSet.GyroAccFactor - (int32_t)Mean_AccRoll);
tmp_long2 = (tmp_long2 * FCParam.Kalman_K) / (32 * 16);
if((MaxStickNick > 64) || (MaxStickRoll > 64)) // reduce effect during stick commands
{
tmp_long /= 2;
tmp_long2 /= 2;
}
if(abs(PPM_in[ParamSet.ChannelAssignment[CH_YAW]]) > 25) // reduce further if yaw stick is active
{
tmp_long /= 3;
tmp_long2 /= 3;
}
// limit correction effect
if(tmp_long > (int32_t)FCParam.Kalman_MaxFusion) tmp_long = (int32_t)FCParam.Kalman_MaxFusion;
if(tmp_long < -(int32_t)FCParam.Kalman_MaxFusion) tmp_long =-(int32_t)FCParam.Kalman_MaxFusion;
if(tmp_long2 > (int32_t)FCParam.Kalman_MaxFusion) tmp_long2 = (int32_t)FCParam.Kalman_MaxFusion;
if(tmp_long2 <-(int32_t)FCParam.Kalman_MaxFusion) tmp_long2 =-(int32_t)FCParam.Kalman_MaxFusion;
}
else
{
// determine the deviation of gyro integral from averaged acceleration sensor
tmp_long = (int32_t)(IntegralNick / ParamSet.GyroAccFactor - (int32_t)Mean_AccNick);
tmp_long /= 16;
tmp_long2 = (int32_t)(IntegralRoll / ParamSet.GyroAccFactor - (int32_t)Mean_AccRoll);
tmp_long2 /= 16;
if((MaxStickNick > 64) || (MaxStickRoll > 64)) // reduce effect during stick commands
{
tmp_long /= 3;
tmp_long2 /= 3;
}
if(abs(PPM_in[ParamSet.ChannelAssignment[CH_YAW]]) > 25) // reduce further if yaw stick is active
{
tmp_long /= 3;
tmp_long2 /= 3;
}
#define BALANCE 32
// limit correction effect
if(tmp_long > BALANCE) tmp_long = BALANCE;
if(tmp_long < -BALANCE) tmp_long =-BALANCE;
if(tmp_long2 > BALANCE) tmp_long2 = BALANCE;
if(tmp_long2 <-BALANCE) tmp_long2 =-BALANCE;
}
// correct current readings
Reading_IntegralGyroNick -= tmp_long;
Reading_IntegralGyroRoll -= tmp_long2;
}
重点的部分是:
// determine the deviation of gyro integral from averaged acceleration sensor
tmp_long = (int32_t)(IntegralNick / ParamSet.GyroAccFactor - (int32_t)Mean_AccNick);
tmp_long /= 16;
tmp_long2 = (int32_t)(IntegralRoll / ParamSet.GyroAccFactor - (int32_t)Mean_AccRoll);
tmp_long2 /= 16;
if((MaxStickNick > 64) || (MaxStickRoll > 64)) // reduce effect during stick commands
{
tmp_long /= 3;
tmp_long2 /= 3;
}
if(abs(PPM_in[ParamSet.ChannelAssignment[CH_YAW]]) > 25) // reduce further if yaw stick is active
{
tmp_long /= 3;
tmp_long2 /= 3;
}
#define BALANCE 32
// limit correction effect
if(tmp_long > BALANCE) tmp_long = BALANCE;
if(tmp_long < -BALANCE) tmp_long =-BALANCE;
if(tmp_long2 > BALANCE) tmp_long2 = BALANCE;
if(tmp_long2 <-BALANCE) tmp_long2 =-BALANCE;
}
// correct current readings
Reading_IntegralGyroNick -= tmp_long;
Reading_IntegralGyroRoll -= tmp_long2;
首先,MK根据IntegralNick变量(这个变量是当前陀螺仪的积分值),利用ParamSet.GyroAccFactor(这个是一个设置值,用来标定加速度计变化1,陀螺仪积分应该变化多少),得出在目前的陀螺仪积分下,对应的加速度计的偏差量应该是多少。然后用这个值减去现在的加速度计数值,得到的就是当前从加速度计角度“看到”的陀螺仪积分偏差。下面的/16用来对这个值进行衰减。在下面如果有遥控器操作(MaxStickNick > 64)那么还要更进一步衰减,甚至Z轴有操作也要再衰减。最后对这个偏差值进行限幅,避免绝对值超过BALANCE的大小。剩下的就只要把修正值修正到陀螺仪积分里就完成了,Reading_IntegralGyroNick就是陀螺仪的积分。
看起来这个代码比较容易理解,不过这里有两点需要注意的。
一是这个代码是利用加速度计来看陀螺仪积分偏差,得出的调整量是加速度计的数值。也许你会觉得奇怪,怎么能用加速度计的数据来加减陀螺仪积分呢?二者似乎是风马牛不相及。不过这里这二者还是有关系的,ParamSet.GyroAccFactor,MK这么做可以将调整量缩小。
二是这个调整量很小,本来加速度计的变化量就很小了,又除了16,甚至还要再除3。最后还能剩多少啊?实际情况是剩的确实不多,我的四轴数据测量后,这个调整值一般也就是3左右,没有看到超过5的。而陀螺仪积分随便就几百上去了,这点根本不算什么。
这么说起来,大家可能对这个调整量的效率产生疑问。确实,这个调整量是很慢。我在我的四轴上实际测量过,按照400Hz的陀螺仪积分频率,在没有积分的情况下,光靠这个加速度计的调整值,要想让陀螺仪积分恢复到和加速度计的对应状态,需要超过两秒钟。
那么大家肯定又有疑问了,这么慢的调整速度,怎么能实现稳定呢?这就是MK的高明之处。俗话说“四两拨千斤”,用在这里还是很合适的。
整个MK的调整速度,不是标准接收机的50Hz,也不是100Hz,而是高达500Hz。可以在main.c里查找变量UpdateMotor,可以看到注释“reset Flag, is enabled every 2 ms by isr of timer0”,没有错,就是2ms触发一次。在Timer0.c的ISR(TIMER0_OVF_vect)里,还可以看到注释“every 2nd run (976Hz/2 = 488 Hz)”,这就是它的执行频率。
心细的人肯定又会继续产生疑问,为什么要这么快?有必要这么快吗?1000KV的电机,3S电池,一分钟最多也就是12000转,换算成秒一秒最多也就转200圈。500Hz的调整频率难道转0.25圈就换一个转速吗?
实际情况是这样的:
首先大家都明白机械振动对于四轴姿态调整的干扰,如果完全没有机械振动,那么想让四轴飞稳会比现在简单很多。但既然不可能完全避免,那么我们就必须想办法来克服它。于是就有我以前采用的加权移动平均或类似的算法。但这些算法都有一个缺点,如果想要状态准确,就会不可避免地造成响应速度低。反过来响应速度高则抗震动效果就不会好。这是一对矛盾,一对让人头大的矛盾。
MK也有这个问题,于是他们想到了一个非常巧妙的解决方法。既然电机转速不可能无限快,那么完全可以通过提高控制频率来应对震动。与其过滤震动,不如加快速度跟上震动,将震动引起的错误数据也当作正确的来处理,反正到电机端震动会被消除的,因为机械震动大都是往复的。而且I2C电调本身的速度也很快,所以频率就可以轻松达到500Hz或更高。
既然明白了加快调整的原因,那么对于弱化加速度计的调整量也就不难理解了。加速度计对于震动实在太敏感了,对于基本没做处理(实际是加速度计的数据只有一个移动期为2的移动平均,就是保留一个历史数据然后和新数据相加除2)的加速度计数据,如果调整量大了,势必造成陀螺仪积分变化非常大,一个显而易见的结果就是引起四轴振荡。实际测试也是如此。
虽然上面说了这么多,我想大家还是没完全明白有了这部分代码后能做到什么。下面就来说说这段代码的效果。
有了这些代码后,设置好合适的I,关掉P,拿起四轴,让一个轴倾斜,你会在倾斜的同时感觉到这个轴的电机转速快速提升,和你的倾斜动作完全同步。你什么时候倾斜,电机什么时候加速,你倾斜的角度越大,电机转速越快。这就是效果。
正确的融合结果是四轴的每个轴都可以一直保持这种“反抗精神”。不管你倾斜多久,不管你倾斜角度有多大都不会“放弃”。而且当你基本回到平衡位置时,电机们应该都安静下来了,四轴不会因为以前的倾斜发生震荡来“报复”你,即使你反复左右倾斜试图引起震荡也不会。
如果融合有问题,有可能电机反抗一会儿就放弃了,然后按照当前的倾斜位置作为新的平衡位置。或者引发震荡,造成四轴晃动。
从数据的角度,就是能从这样的数据里:
<图片16.jpg>
得到这样的陀螺仪积分:
<图片17.jpg>
为了引出下面的算法,现在请你将四轴小幅度倾斜一点,不要离平衡位置太远,看看电机会有何表示。
试验的结果,是不是在小幅度的时候,电机基本没有加速的动作?
原因并不是你代码弄错了,而是这种平衡策略的天生缺陷。由于这种平衡需要通过陀螺仪积分也就是绝对倾斜角度来调整电机转速,总有一个情况就是当倾斜角度很小的时候,无法让电机产生足够的调整动力来最终把四轴拉平。这就是为什么MK要引入第二种,也就是长期融合。
长期融合的代码位于fc.c文件的949行。
if(MeasurementCounter >= BALANCE_NUMBER) // averaging number has reached
{
static int16_t cnt = 0;
static int8_t last_n_p, last_n_n, last_r_p, last_r_n;
static int32_t MeanIntegralNick_old, MeanIntegralRoll_old;
// if not lopping in any direction (this should be alwais the case,
// because the Measurement counter is reset to 0 if looping in any direction is active.)
if(!Looping_Nick && !Looping_Roll && !FunnelCourse)
{
// Calculate mean value of the gyro integrals
MeanIntegralNick /= BALANCE_NUMBER;
MeanIntegralRoll /= BALANCE_NUMBER;
// Calculate mean of the acceleration values
IntegralAccNick = (ParamSet.GyroAccFactor * IntegralAccNick) / BALANCE_NUMBER;
IntegralAccRoll = (ParamSet.GyroAccFactor * IntegralAccRoll ) / BALANCE_NUMBER;
// Nick ++++++++++++++++++++++++++++++++++++++++++++++++
// Calculate deviation of the averaged gyro integral and the averaged acceleration integral
IntegralErrorNick = (int32_t)(MeanIntegralNick - (int32_t)IntegralAccNick);
CorrectionNick = IntegralErrorNick / ParamSet.GyroAccTrim;
AttitudeCorrectionNick = CorrectionNick / BALANCE_NUMBER;
// Roll ++++++++++++++++++++++++++++++++++++++++++++++++
// Calculate deviation of the averaged gyro integral and the averaged acceleration integral
IntegralErrorRoll = (int32_t)(MeanIntegralRoll - (int32_t)IntegralAccRoll);
CorrectionRoll = IntegralErrorRoll / ParamSet.GyroAccTrim;
AttitudeCorrectionRoll = CorrectionRoll / BALANCE_NUMBER;
if(((MaxStickNick > 64) || (MaxStickRoll > 64) || (abs(PPM_in[ParamSet.ChannelAssignment[CH_YAW]]) > 25)) && (FCParam.Kalman_K == -1) )
{
AttitudeCorrectionNick /= 2;
AttitudeCorrectionRoll /= 2;
}
核心部分:
MeanIntegralNick /= BALANCE_NUMBER;
MeanIntegralRoll /= BALANCE_NUMBER;
// Calculate mean of the acceleration values
IntegralAccNick = (ParamSet.GyroAccFactor * IntegralAccNick) / BALANCE_NUMBER;
IntegralAccRoll = (ParamSet.GyroAccFactor * IntegralAccRoll ) / BALANCE_NUMBER;
// Nick ++++++++++++++++++++++++++++++++++++++++++++++++
// Calculate deviation of the averaged gyro integral and the averaged acceleration integral
IntegralErrorNick = (int32_t)(MeanIntegralNick - (int32_t)IntegralAccNick);
CorrectionNick = IntegralErrorNick / ParamSet.GyroAccTrim;
AttitudeCorrectionNick = CorrectionNick / BALANCE_NUMBER;
// Roll ++++++++++++++++++++++++++++++++++++++++++++++++
// Calculate deviation of the averaged gyro integral and the averaged acceleration integral
IntegralErrorRoll = (int32_t)(MeanIntegralRoll - (int32_t)IntegralAccRoll);
CorrectionRoll = IntegralErrorRoll / ParamSet.GyroAccTrim;
AttitudeCorrectionRoll = CorrectionRoll / BALANCE_NUMBER;
if(((MaxStickNick > 64) || (MaxStickRoll > 64) || (abs(PPM_in[ParamSet.ChannelAssignment[CH_YAW]]) > 25)) && (FCParam.Kalman_K == -1) )
{
AttitudeCorrectionNick /= 2;
AttitudeCorrectionRoll /= 2;
}
这里MeanIntegralNick是陀螺仪积分值,IntegralAccNick是加速度计的积分总合,是经过BALANCE_NUMBER次姿态调整累加下来的。ParamSet.GyroAccFactor依旧是那个关键的系数。
代码首先将陀螺仪积分求平均,得到一个平均值。同样对加速度计的积分也进行了这样的处理,只是代码里MK先将加速度计的数值换算成对应的陀螺仪积分后再求得平均。于是下面就是两个陀螺仪积分的对比了,和上面的算法有些不同。
在下面的代码就更好懂了,先求出这两个积分的差异,然后利用ParamSet.GyroAccTrim缩小,最后再除以BALANCE_NUMBER,小到一点点的地步。后面的代码也对遥控器操作情况下再度进行衰减。在我的四轴上,这个调整量基本小于0.1,有时候甚至都是0.000XXX的级别。为什么要这么小?原因就是变量AttitudeCorrectionNick的应用是每次积分都用到了(fc.c的317行,
Reading_IntegralGyroNick += Reading_GyroNick - AttitudeCorrectionNick;),所以如果大了,那么一下子就大幅度改变了陀螺仪积分,引起麻烦。
这里有个隐含的疑问,既然Reading_IntegralGyroNick每次都有进行过调整,那么和第一种算法会不会冲突呢?实际情况不会。因为第一种调整虽然调整量比第二种大很多,而且调整速度也非常快,加速度计的数据虽然长期看来确实可以反映出四轴的姿态,而在1/500秒看来基本可以算是随机的。所以有很大情况这两种调整量不会互相覆盖。
加入了这个代码以后,再小幅度倾斜四轴看看。四轴会慢慢地自己增加电机转速,直到把自身拉平才会罢休。这就可以大幅度减小漂移的程度。从图表角度看,随着四轴的倾斜,陀螺仪积分也随之水涨船高,转速自然就上去了。
有了这两部分代码以后,四轴基本可以自己做到自主悬停了。
以上分析只是从代码和现象层面进行的理解,MK肯定也采用了一种比较经典的控制算法,只是目前我没有找到相关的资料。欢迎大家继续探索。
附录1:PPM转发代码
http://bbs.5imx.com/bbs/viewthread.php?tid=219838&page=26#pid3319190
花费了将近3天时间,挂掉一片AVR Mega8,终于完成了一个PPM转发的代码。
想要完成舵量输出非常简单。光是输出想做到没有舵机抖动也很容易。但要做到舵量采集后再输出没有抖动就复杂了。
一共2ms的舵量信息,M8内置的8M频率,代码上稍微多执行几行、少执行几行,采集回来的舵量就变了。
时间都花在这上面了,本来第一天就完成了代码,在我的辉盛5克上没有发现抖动的问题。挺开心,心想蛮简单的。可换到辉盛9克上,就开始抖。而且很频繁,基本没不抖的时候。
反复修改软件,调整计时顺序,舵量输出顺序,甚至想用外部12M晶振提高速度。我的那片M8就是为这个挂掉的。熔丝位设置了外部晶振,但是没起来,ISP也进不去了。按照网上说的用外部有源晶振的方法倒是ISP能认了,但认的芯片型号不对。当时也没在意,直接修改熔丝位到内部晶振。结果可能由于数据M8认的有误,RESET脚给关掉了,而且内部频率也不是8M,倒像是1M的。这下彻底没着.... 我的第一片M8就这样交待了。
反复试验了N种方法,最后决定必须使用硬件的定时器来确定舵量。然后修改代码,将定时器0作为舵量输出用,利用定时器2进行舵量采集。
流程是,在main里先完成一次舵量采集,完全根据定时器的计时信息决定舵量,没有使用变量累加计数。然后定时器0启动,输出一次完整舵量。
输出完成后,定时器0暂停计时,再交由main继续下一次采集舵量。就这样一直循环下去。
舵量的输出也采用了新方法,定时器在输出完一部分后,直接修改TIME0的计时器到下一个状态的触发时间点上,取消了软件变量累加的计时方法。
效果还是不错的,舵机已经基本不抖了。为了做到更好,稍微牺牲了一点灵敏度,代码里将舵量和上次的采集结果进行比较,发现差异超过1时再更新。这样最后的效果已经和直接接到接收机上没什么差别了。
下面是代码,希望对想做同样东西的人能有些帮助:
//-------------------------------------------
//
// PPM 解码/转发代码
// Version 1.0
// cnmusic@163.net
// 使用AVR Mega8,使用内部8M晶振
//
//-------------------------------------------
#include
#include
#include
#include
#define FEED_DOG asm("wdr");
#define NOP asm("nop");
#define INT_ON sei();
#define INT_OFF cli();
typedef unsigned char BOOL;
typedef unsigned char BYTE;
typedef unsigned char CHAR;
#define TRUE 1
#define FALSE 0
#define CHANNELCOUNT 1
#define TICK_COUNT_LEAD 70 // 前0.5ms对应的定时器0的64分频的计数数值,虽然不是实际的0.5ms,但这是我这里舵机能
// 承受的最小数值了
typedef struct tagChannelData
{
BYTE nChannelOutValue; // 舵量数据
BYTE nChannelOutStep; // 舵量的输出状态
}CHANNELDATA;
volatile CHANNELDATA g_ChannelData[CHANNELCOUNT] = {{0}};
volatile BOOL g_bOutAllComplete = TRUE;
volatile BYTE g_nOutIndex = 0;
// TIME0定时器中断
// 这个中断用来切换各个舵机端口的状态,包含3个部分:
// 前0.5ms的引导,中间最大2ms的舵量,以及后面的低电平部分
// 为了精确计时,使用TIME0的计数器,而没用软件进行变量累加计数
ISR(TIMER0_OVF_vect)
{
if (g_ChannelData[g_nOutIndex].nChannelOutStep == 0)
{
PORTD |= (1<= CHANNELCOUNT)
{
g_bOutAllComplete = TRUE;
TCCR0 = 0;
}
else
{
TCNT0 = 0xFF - 1;
}
}
}
int main()
{
BYTE nChannelValueFinal = 0;
DDRD = (1< 1)
{
g_ChannelData[0].nChannelOutValue = nChannelValueFinal; // 保存最后的舵量信息
}
g_ChannelData[0].nChannelOutStep = 0; // 设置舵量输出标志
// 重起定时器0
g_nOutIndex = 0; // 从0通道开始输出舵量
TCNT0 = 0xFF - 1; // 延迟1,基本是立即触发中断
TCCR0 = (1<里定义的一个宏。告诉编译器这个函数要放到bootloader段里。最后的实际编译效果可以查看那个.map文件。
这里有一点比较麻烦,就是必须将主函数放到最前面,否则加电后可能先执行某个子程序去了。通过将所有调用的函数前加上static就可以做到这点。
以上步骤都相对容易,设置bootloader的段地址也可以在网上找到相关资料。但下面就郁闷了。
经过这样的过程,Bootloader可以被引导,但有的时候不正常。我在测试中加了一个灯的闪烁代码,结果发现有时候灯闪烁的速度很慢。当时不知道原因,以为是编译器、内置晶振的不稳定的问题,或是加电电压不稳损坏了代码或是芯片损坏等原因。足足查了4天啊,真是痛苦。3个芯片,同样的代码,有一个怎么都好使,另外2个时好时坏。
最后,在网上找到老外的一个BootLoader,也是I2C的,终于发现问题所在。
原来设置成从BootLoader区引导后,必须在进入C的main前设置好堆栈。以前这部编译器帮我完成了,所以我从没注意。但这回就需要自己来写了。于是一段比较简单的汇编代码后,终于我的BootLoader可以正常执行了。
vectors.S 文件:
#include
#if __AVR_MEGA__
#define XJMP jmp
#define XCALL call
#else
#define XJMP rjmp
#define XCALL rcall
#endif
.section .boot_vectors,"ax",@progbits
XJMP start
.word 0 // pad (vector 7)
.word 0 // pad (vector 8)
// 中断向量表,我们没有调用中断,所以都是空。
// 保留这个向量表的意思是,一旦我们需要调用中断了
// 可以加在下面。
//vector __vector_9 // OverFlow0
.word 0
//vector __vector_10
.word 0
//vector __vector_11 // OverFlow0
.word 0
//vector __vector_12
.word 0
//vector __vector_13
.word 0
//vector __vector_14
.word 0
//vector __vector_15
.word 0
//vector __vector_16 // OverFlow0
.word 0
//vector __vector_17 // TWI
.word 0
//vector __vector_18
.word 0
//vector __vector_19 // TWI
.word 0
//vector __vector_20
.word 0
//vector __vector_21
.word 0
//vector __vector_22
.word 0
//vector __vector_23
.word 0
//vector __vector_24 // TWI
.word 0
//vector __vector_25
.word 0
//vector __vector_26
.word 0
//vector __vector_27
.word 0
//vector __vector_28
.word 0
//vector __vector_29
.word 0
//vector __vector_30
.word 0
//vector __vector_31
.word 0
//vector __vector_32
.word 0
//vector __vector_33 // TWI
.word 0
// We don't need any vectors higher than 33, so don't waste the flash
#define __zero_reg__ r1
// 全局的__stack变量
.global __stack
.set __stack, RAMEND
start:
// 设置堆栈,这个非常重要!
// 必须在进入C程序前设置好
clr __zero_reg__
out _SFR_IO_ADDR(SREG), __zero_reg__
ldi r28,lo8(__stack)
ldi r29,hi8(__stack)
out _SFR_IO_ADDR(SPH), r29
out _SFR_IO_ADDR(SPL), r28
ldi r24,lo8(0)
ldi r25,hi8(0)
XJMP main
修改了老外的引导代码,简化了一些。这个是针对M8芯片的。
由于换文件后如果有同名的section,编译器会在后面自动加一个.1。所以这里用了2个section。
.bootloader 0xc25,这个是给C的程序用的。偏移了0x25个字节,因为那个汇编编译后生成了这么多的代码。
.boot_vectors 0xc00,这个是给这个汇编用的。
需要在Project Options的Memory Settings里设置好。
附录3:完整的TWIBootLoader.c的代码
完整的C实现部分代码如下,参考Atmel的TWI例子修改的,执行的功能包括BootLoader签名验证,上传代码的CRC16校验验证,跳转到用户区等功能:
//
// TWI BootLoader.c
//
#include
#include
#include
#include
#include
#include
#include
//--------------------------------------------
// 参数配置,包括地址和亮灯的IO口代码,程序会反复
本文档为【四轴飞行器DIY附录】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑,
图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。