硬件协议开发篇------OTA协议

本文探讨了OTA(Over-The-Air)技术在物联网和汽车行业的应用,介绍了ymodem协议的原理、帧格式以及Bootloader引导程序设计,包括CRC校验和数据传输优化。重点讲解了如何通过ymodem进行固件远程升级的过程和相关代码示例。
摘要由CSDN通过智能技术生成

系列文章目录

1.OTA是什么?
2.解析什么是ymode协议
3.Bootloader引导程序设计


前言

随着物联网的快速发展,越来越多的设备需要进行固件更新和升级,以提供新功能、修复漏洞或改善性能。而传统的手动升级方式往往不仅耗时耗力,还存在一定的风险。为了解决这些问题,提供一种安全、高效、灵活的远程升级解决方案变得至关重要。至此,对于一名嵌入式工程师来说学习OTA技术是有必要的。


一、OTA是什么?

OTA全称“Over-The-Air”,即空中下载技术,早期被广泛应用在手机行业中,终结了手机软件升级需要连接电脑、下载软件、再安装更新的繁复操作。近年来,随着汽车网联技术不断发展,汽车OTA也成为了行业热词。
在这里插入图片描述
通过OTA技术对汽车进行远程升级,不仅可以持续为车辆改善终端功能和服务,让车主拥有更便捷、更智能的用车体验,而且还可以被用于快速修复漏洞,帮助实施汽车召回。正在逐渐成为企业解决软、硬件系统问题的重要措施。业内公认的汽车OTA最早出现是在2012年,特斯拉推出的ModesS首次采用OTA技术,更新范围涉及人机交互、自动驾驶、动力电池系统等模块,当时特斯拉可以通过OTA完成钥匙卡漏洞、提升续航里程、提高最高速度、提升乘坐舒适度等,让车的功能迭代更加灵活和便捷。后来,OTA技术开始被丰田、福特、大众、宝马等传统车企所尝试。期间,国内的蔚来、理想、小鹏、上汽、比亚迪等也陆续推出了可以实现部分功能或整车OTA的车型。

具体详细的内容请自行问度娘。过于细节的内容我在此不再赘述。

二、ymodem是什么?

首先要想学习OTA更新的技术内容,我们首先来了解一个技术协议------ymodem

2.1 ymodem协议简介

ymodem协议是一个文件传输协议,由ChuckForsberg于上世纪90年代开发完成,通常用于资源受限的设备。
xmodem、ymodem和zmodem协议是最常用的三种通信协议。ymodem协议是由xmodem协议演变而来的,是一种发送并等待的协议,即发送方发送一个数据包以后,都要等待接收方的确认。如果是ACK信号,则可以发送新的包。如果是NAK信号,则重发或者错误退出。ymodem-1k用1024字节信息块传输取代标准的128字节传输,每包数据可以达到1024字节,是一个非常高效的文件传输协议,所用到的符号如下。

#define MODEM_SOH 0x01 //数据块起始字符
#define MODEM_STX 0x02 //1028字节开始
#define MODEM_EOT 0x04 //文件传输结束
#define MODEM_ACK 0x06 //确认应答
#define MODEM_NAK 0x15 //出现错误
#define MODEM_CAN 0x18 //取消传输
#define MODEM_C   0x43 //大写字母

2.2 ymodem帧格式

2.2.1 ymodem协议的传输过程

如下图所示:
截取官方文档
开启是由接收方开启传输,它发一个大写字母C(0x43)开启传输。然后进入等待SOH(0x01)状态,如果没有回应,就会超时退出。发送方开始时处于等待过程中,等待C。收到C以后,发送(SOH)数据包开始信号,发送序号(00),补码(FF),“文件名”,“\0”“文件大小”“除去序号外,补满128字节”,16位CRC校验两个字节,高字节在前,低字节在后。进入等待(ACK)状态。

内容示例:SOH 00 FF Foo.bin NUL[123] CRC CRC

接收方收到以后,CRC校验满足,则发送ACK。发送方接收到ACK,又进入等待“文件传输开启”信号,即重进入等待“C”的状态。发送接收到“C”以后,发送第一个数据包,(SOH) + (01序号) + (FE补码) + (128位数据) +(CRC校验),或者(STX) + (01序号) + (FE补码) + (1024位数据) + (CRC校验),不满128或者1024,用0x00补齐,等待接收方“ACK”。

内容示例:STX 01 FE data[1024] CRC CRC

文件发送完以后,发送方发出一个“EOT”信号,接收方也以“ACK”回应。然后接收方会再次发出“C”开启另一次传输,若接着发送方会发出一个“全0数据包”,接收方“ACK”以后,本次通信正式结束。

2.2.2 起始帧的数据格式ymodem的起始帧并不直接传输文件的数据,而是将文件名与文件的大小放在数据帧中传输,它的帧长=3字节数据首部+128字节数据+2字节CRC16校验码=133字节。
它的数据结构如下:

SOH 00 FF foo.c 3232 NUL[118] CRCH CRCL
  • SOH:表示本帧数据块大小为128字节
  • 00: 表示数据帧序号,初始是0,依次向下递增,FF是帧序号的取反
  • foo.c:是要传输的文件名,是ASCII字符串(以空字符结尾)
  • 3232:表示文件的大小,是ASCII字符串(以空字符结尾)
  • NUL[118]:剩余部分用空字符填充
  • CRCH/L: 表示16位CRC校验码的高8位与低8位

2.3 数据帧的数据格式

ymodem的数据帧的数据块大小可以是128字节或者1024字节。

// 128字节的数据块
SOH 01 FE data[128] CRCH CRCL
// 1024字节的数据块
STX 01 FE data[1024] CRCH CRCL

一般会使用1024字节的数据块进行传输,这样可以加快传输速度,如果最后文件数据不足1024字节,则将其拆分为128字节的数据块进行传输,如果拆分后有不足128字节的数据依然按照128字节的数据块进行传输,但是剩余空间全部用0x1A填充,以表示文件结束。

2.4 结束帧数据结构

当文件传输结束时,除了发送EOT传输结束指令外,还需要发送一个结束
帧。ymodem的结束帧与起始帧的数据格式相同,数据块大小为128字节,但是
结束帧的数据块要全用空字符填充。

SOH 3A C5 NUL[128] CRCH CRCL

2.5 crc16函数

CRC即循环冗余校验码:是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查(CRC)是一种数据传输检错功能,对数据进行多项式计算,并将得到的结果附在帧的后面,接收设备也执行类似的算法,以保证数据传输的正确性和完整性。

/**
 * @bieaf CRC-16 校验
 *
 * @param addr 开始地址
 * @param num   长度
 * @param num   CRC
 * @return crc  返回CRC的值
 */
#define POLY        0x1021  
uint16_t crc16(uint8_t *addr, int32_t num, uint16_t crc)  
{  
    int32_t i;  
    for (; num > 0; num--)					/* Step through bytes in memory */  
    {  
        crc = crc ^ (*addr++ << 8);			/* Fetch byte from memory, XOR into CRC top byte*/  
        for (i = 0; i < 8; i++)				/* Prepare to rotate 8 bits */  
        {
            if (crc & 0x8000)				/* b15 is set... */  
                crc = (crc << 1) ^ POLY;  	/* rotate and XOR with polynomic */  
            else                          	/* b15 is clear... */  
                crc <<= 1;					/* just rotate */  
        }									/* Loop for 8 bits */  
        crc &= 0xFFFF;						/* Ensure CRC remains 16-bit value */  
    }										/* Loop until num=0 */  
    return(crc);							/* Return updated CRC */  
}

2.6 ymodem传输大小选择

在SecureCRT中,ymodem默认为128字节数据包大小,如下图。当然亦可选择为1024字节(Xmodem-1k/Ymodem-1k)
默认字节数据大小选择

3.流程代码

#include "stm32f4xx.h"
#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "beep.h"
#include "flash.h"
#include "key.h"
#include "ymodem.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>


/**
 * @bieaf CRC-16 校验
 *
 * @param addr 开始地址
 * @param num   长度
 * @param num   CRC
 * @return crc  返回CRC的值
 */
#define POLY        0x1021  
uint16_t crc16(uint8_t *addr, int32_t num, uint16_t crc)  
{  
    int32_t i;  
    for (; num > 0; num--)					/* Step through bytes in memory */  
    {  
        crc = crc ^ (*addr++ << 8);			/* Fetch byte from memory, XOR into CRC top byte*/  
        for (i = 0; i < 8; i++)				/* Prepare to rotate 8 bits */  
        {
            if (crc & 0x8000)				/* b15 is set... */  
                crc = (crc << 1) ^ POLY;  	/* rotate and XOR with polynomic */  
            else                          	/* b15 is clear... */  
                crc <<= 1;					/* just rotate */  
        }									/* Loop for 8 bits */  
        crc &= 0xFFFF;						/* Ensure CRC remains 16-bit value */  
    }										/* Loop until num=0 */  
    return(crc);							/* Return updated CRC */  
}



/* 设置升级的步骤 */
static enum UPDATE_STATE update_state = TO_START;

void ymodem_set_state(enum UPDATE_STATE state)
{
	update_state = state;
}


/* 查询升级的步骤 */
uint8_t ymodem_get_state(void)
{
	return update_state;
}




/* 发送指令 */
void ymodem_send_cmd(uint8_t command)
{

	USART_SendData(USART1,command);

	//等待数据发送成功
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
	USART_ClearFlag(USART1,USART_FLAG_TXE);
	
	delay_ms(10);
}

/* 标记升级完成 */
void update_set_down(void)
{
	uint32_t update_flag = 0xAAAAAAAA;				// 对应bootloader的启动步骤
	
	flash_program((APPLICATION_2_ADDR + APPLICATION_2_SIZE - 4), &update_flag,1 );
}


/**
 * @bieaf ymodem下载
 *
 * @param none
 * @return none
 */
void ymodem_download(void)
{
	uint16_t crc = 0;

static 
	uint8_t data_state = 0;

	if(ymodem_get_state()==TO_START)
	{
		ymodem_send_cmd(CCC);
		
		delay_ms(1000);
	}
	
	/* 串口1接收完一个数据包 */
	if(g_usart1_rx_end)    	
	{

		
		/* 清空接收完成标志位、接收计数值 */
		g_usart1_rx_end=0;
		g_usart1_rx_cnt=0;
		
		switch(g_usart1_rx_buf[0])
		{
			case SOH://数据包开始
			{
					crc = 0;
				
					/* 计算crc16 */
					crc = crc16((uint8_t *)&g_usart1_rx_buf[3], 128, crc);
						
					if(crc != (g_usart1_rx_buf[131]<<8|g_usart1_rx_buf[132]))
						return;
					
					if((ymodem_get_state()==TO_START)&&(g_usart1_rx_buf[1] == 0x00)&&(g_usart1_rx_buf[2] == (uint8_t)(~g_usart1_rx_buf[1])))// 开始
					{

						ymodem_set_state(TO_RECEIVE_DATA);
						
						/* 若ymodem_send_cmd执行在sector_erase之前,则导致串口数据丢包,因为擦除会关闭所有中断 */
						/* 擦除应用程序2的扇区 */
						sector_erase(APPLICATION_2_SECTOR);						
						
						data_state = 0x01;						
						ymodem_send_cmd(ACK);
						ymodem_send_cmd(CCC);

	
					}
					else if((ymodem_get_state()==TO_RECEIVE_END)&&(g_usart1_rx_buf[1] == 0x00)&&(g_usart1_rx_buf[2] == (uint8_t)(~g_usart1_rx_buf[1])))// 结束
					{
						update_set_down();						
						ymodem_set_state(TO_START);
						ymodem_send_cmd(ACK);
						
						/* 嘀一声示,表示下载完成 */
						beep_on();delay_ms(80);beep_off();
						
						/* 复位 */
						NVIC_SystemReset();
					}					
					else if((ymodem_get_state()==TO_RECEIVE_DATA)&&(g_usart1_rx_buf[1] == data_state)&&(g_usart1_rx_buf[2] == (uint8_t)(~g_usart1_rx_buf[1])))// 接收数据
					{

						/* 烧录程序 */
						flash_program((APPLICATION_2_ADDR + (data_state-1) * 128), (uint32_t *)(&g_usart1_rx_buf[3]), 32);
						data_state++;
						
						ymodem_send_cmd(ACK);		
					}
			}break;
			
			case EOT://数据包传输结束
			{
				if(ymodem_get_state()==TO_RECEIVE_DATA)
				{

					ymodem_set_state(TO_RECEIVE_EOT2);					
					ymodem_send_cmd(NACK);
				}
				else if(ymodem_get_state()==TO_RECEIVE_EOT2)
				{
	
					
					ymodem_set_state(TO_RECEIVE_END);					
					ymodem_send_cmd(ACK);
					ymodem_send_cmd(CCC);
				}
	
			}break;	
			
			default:break;
		}

	}
}




三、Bootloader引导程序设计

1.编写代码

/**
 * @bieaf 进行程序的覆盖
 * @detail 1.擦除目的地址
 *         2.源地址的代码拷贝到目的地址
 *         3.擦除源地址
 *
 * @param  搬运的源地址
 * @param  搬运的目的地址
 * @return 搬运的程序大小
 */
void move_code(uint32_t dest_addr, uint32_t src_addr,uint32_t word_size)
{
	uint32_t temp[256];
	uint32_t i;
	
	/*1.擦除目的地址*/
	printf("> start erase application 1 sector......\r\n");
	
	//擦除
	sector_erase(APPLICATION_1_SECTOR);
	
	printf("> erase application 1 success......\r\n");
	
	/*2.开始拷贝*/	

	printf("> start copy......\r\n");
	
	for(i = 0; i <word_size/1024; i++)
	{
		flash_read((src_addr + i*1024), temp, 256);
		flash_program((dest_addr + i*1024), temp, 256);
	}
	
	printf("> copy finish......\r\n");
	
	/*3.擦除源地址*/
	
	printf("> start erase application 2 sector......\r\n");
	
	//擦除
	sector_erase(APPLICATION_2_SECTOR);
	
	printf("> erase application 2 success......\r\n");
	
}

2.程序跳转

2.1程序跳转前需要用汇编进行重新设置MSP(main stack pointer)。

/* 采用汇编设置栈的值 */
__asm void MSR_MSP (uint32_t ulAddr) 
{
    MSR MSP, r0 			                   //set Main Stack value
    BX r14
}

函数体中的__asm表示这是一段内嵌汇编代码。MSR MSP, r0将r0寄存器中的值设置为主栈指针(MSP)的值,即将栈地址设置为ulAddr。BX r14则是一个分支指令,将控制权返回到调用该函数的地址。
通过使用这段汇编代码,可以在ARM Cortex-M系列处理器上设置主栈值该处使用的url网络请求的数据。


2.2程序跳转需要使用函数指针,预先提前定义。

typedef void(*jump_func)(void);

2.3.执行程序覆盖

void iap_execute_app (uint32_t app_addr)
{
	jump_func jump_to_app; 
	
	//printf("* ( __IO uint32_t * ) app_addr  =%08X ,app_addr=%08X\r\n",* ( __IO uint32_t * ) app_addr,app_addr );
    
	//if ( ( ( * ( __IO uint32_t * ) app_addr ) & 0x2FFE0000 ) == 0x200006B0 )	//检查栈顶地址是否合法.
	//{ 
		//printf("stack is legal\r\n");
		
		jump_to_app = (jump_func) * ( __IO uint32_t *)(app_addr + 4);			//用户代码区第二个字为程序开始地址(复位地址)		
		
		MSR_MSP( * ( __IO uint32_t * ) app_addr );								//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
		
		jump_to_app();															//跳转到APP.
	//}
	
	//printf("stack is illegal\r\n");
}

栈顶:栈顶=IRAM起始地址+RW-data大小+ZI-data大小,当程序成功编译后,
输出显示以下信息:
在这里插入图片描述
若IRAM起始地址为0x20000000,则栈顶地址
= 0x20000000+464 + 72 =0x200001D0。

3 应用程序

3.1 工程配置
在【Target】标签页,重新配置IROM的起始地址和大小,目前该应用代码存储在扇区5,则起始地址为0x08020000,大小为128KB,即0x20000,如下图。
在这里插入图片描述
3.2 在【Linker】标签页,R/O Base设置为扇区5的起始地址0x08020000。

在这里插入图片描述
3.3 在【User】标签页中,在“After Build/Rebuild”中勾选口Run #1,并增加“fromelf -
-bin --output .\Objects\demo.bin .\Objects*.axf”,生成bin文件,可用于ymodem下
载。
在这里插入图片描述
详细代码过程可以上本人githup上自行借鉴:https://github.com/201697suxi/OTA

### 回答1: nrf52810开发指南-上册.pdf是一本非常详细的指南,可以帮助人们更好地了解和学习nrf52810无线芯片的开发和使用方法。该指南主要介绍了nrf52810的基本特性、硬件架构、软件开发环境、开发工具、编程语言、开发流程等方面的内容。 该指南以实例讲解的方式,详细介绍了如何使用nrf52810从事无线应用程序的开发。具体而言,该指南介绍了如何使用Keil MDK、IAR Embedded Workbench、SEGGER Embedded Studio等IDE工具,以及nrf5 SDK和nrf5x Command Line Tools等开发工具进行开发。 另外,该指南还分析了nrf52810在无线通讯中的应用场景,包括基于Bluetooth Low Energy(BLE)的智能家居、可穿戴设备等领域。通过实践和案例演示,读者能够深入了解nrf52810芯片的广泛应用前景。 总之,nrf52810开发指南-上册.pdf是一份非常有价值的指南,对于无线产品开发者和爱好者都有非常大的帮助,值得一读。 ### 回答2: nrf52810是Nordic公司最新推出的低功耗蓝牙芯片,它采用ARM Cortex-M4处理器,拥有256KB的闪存和24KB的RAM,非常适合开发低功耗蓝牙设备。该芯片还支持Nordic公司自主开发的S132 SoftDevice协议栈,可以实现BLE4.2和BLE5.0的各种特性,如长包、扫描窗口等。 该开发指南共分为六个章节,第一章介绍了nrf52810芯片的主要特点,第二章讲解了如何使用开发板进行测试与调试,第三章介绍了如何使用nRF5 SDK和nRF5 SoftDevice进行开发,第四章讲解了如何使用Nordic公司的开发工具nRF Connect和Segger J-Link进行开发,第五章讲解了如何进行定制化开发,第六章则是常见问题解答与扩展阅读。 通过阅读该开发指南,开发者可以快速了解nrf52810芯片的各项特性,掌握开发与调试技巧,并且学会如何利用Nordic公司的各种开发工具进行开发。此外,该指南还提供了大量的编程示例和实验框图,可以帮助开发者快速上手并深入理解nrf52810的开发流程。 总之,nrf52810开发指南为开发低功耗蓝牙设备的开发者提供了全面且实用的开发指导,是一本非常优秀的开发书籍,值得大家阅读。 ### 回答3: nRF52810开发指南-上册是针对nRF52810芯片的开发指南,该芯片是由北欧半导体公司推出的低功耗蓝牙SoC芯片。本开发指南针对该芯片的主要功能特点和应用场景,提供了全面的技术说明和具体实践操作指引。 一方面,本开发指南详细介绍了nRF52810芯片的硬件架构和软件框架,包括CPU核心、内存、外设、通信接口等方面的详细说明,同时还介绍了nRF52810芯片的功耗特点和低功耗技术实现方法。这些内容对于硬件工程师和软件工程师来说都是非常重要的,可以帮助他们在设计和开发过程中更好地了解和掌握nRF52810芯片的内部结构和工作原理。 另一方面,本开发指南还提供了丰富的样例程序和实践案例,包括基于SDK的应用程序设计、BLE服务设计、无线OTA升级、传感器数据采集和处理等方面的实现方法和具体代码。这些实践案例不仅可以帮助读者更好地理解nRF52810芯片的使用方法和应用场景,还可以为实际应用开发提供有参考价值的实践经验。 总之,nRF52810开发指南-上册是一份非常优秀的技术文档,对于从事低功耗蓝牙应用开发的工程师和科研人员来说都是非常有价值的。它详细介绍了nRF52810芯片的设计和开发方法、低功耗技术实现方案,并提供了多个实践案例和具体的应用程序设计,为读者提供了全面且实用的技术指导。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值