STM32F030R8Tx自定義協議實現串口升級詳解

  • 基於STM32F030R8Tx爲例來剖析串口升級,本例程分爲三個部分

  1. STM32應用程序部分
  2. STM32 bootloader部分
  3. 上位機串口通信分發升級包部分
  • 上位機與STM32之間的串口數據通信協議約定 

包頭 包長度 命令 0~n字節數據 校驗和
0xff      0x**    0x**    0x**....0x**    0x**
  •  校驗和計算方法

       從包頭開始到校驗和之前的所有數據累加和,取低8位數據作爲整個包的檢驗和

   例如 0xff 0x4 0x01,無數據部分,則檢驗和爲

            0xff + 0x04 + 0x01 = 0x104

  取低8位數據作爲整包的檢驗和,則校驗和是0x04,那麼完整的數據包是 0xff 0x04 0x01 0x04

  • STM32用戶應用程序部分

   1. 整個程序由bootloader引導程序和用戶應用程序兩部分組成,這裏分配12KB的flash空間給bootloader使用,那麼用戶應用

       程序flash地址則要從0x8000000的基礎上再偏移0x3000,則flash的起始地址爲0x8003000,由於STM32F030R8Tx的flash

       的大小爲 64KB,那麼應用程序可用的flash空間爲0x10000減去bootloader的大小0x3000等於0xd000

    2.應用程序的RAM起始地址計算,由於bootloader跳轉後要複製啓動文件中的向量表到RAM中,所以至少要分配的RAM大小

      根據啓動文件中的向量表決定,stm32f030R8Tx啓動文件startup_stm32f030x8.s中的向量部分內部如下,共有45個DCD

      定義,每個佔4個字節,則複製向量表總共所需佔用的RAM爲45*4=180個字節(0xb4),那麼應用程序的起始RAM要從

     0x20000000加上0xb4,即0x200000B4,如果stm32f030R8Tx的RAM大小爲8KB,那麼應用程序可用RAM大小

      爲0x2000-0xb4=0x1F4C

__Vectors       DCD     __initial_sp                   ; Top of Stack       1
                DCD     Reset_Handler                  ; Reset Handler      
                DCD     NMI_Handler                    ; NMI Handler
                DCD     HardFault_Handler              ; Hard Fault Handler
                DCD     0                              ; Reserved
                DCD     0                              ; Reserved
                DCD     0                              ; Reserved
                DCD     0                              ; Reserved
                DCD     0                              ; Reserved
                DCD     0                              ; Reserved
                DCD     0                              ; Reserved
                DCD     SVC_Handler                    ; SVCall Handler
                DCD     0                              ; Reserved
                DCD     0                              ; Reserved
                DCD     PendSV_Handler                 ; PendSV Handler
                DCD     SysTick_Handler                ; SysTick Handler     16

                ; External Interrupts
                DCD     WWDG_IRQHandler                ; Window Watchdog
                DCD     0                              ; Reserved
                DCD     RTC_IRQHandler                 ; RTC through EXTI Line
                DCD     FLASH_IRQHandler               ; FLASH
                DCD     RCC_IRQHandler                 ; RCC
                DCD     EXTI0_1_IRQHandler             ; EXTI Line 0 and 1
                DCD     EXTI2_3_IRQHandler             ; EXTI Line 2 and 3
                DCD     EXTI4_15_IRQHandler            ; EXTI Line 4 to 15
                DCD     0                              ; Reserved
                DCD     DMA1_Channel1_IRQHandler       ; DMA1 Channel 1
                DCD     DMA1_Channel2_3_IRQHandler     ; DMA1 Channel 2 and Channel 3
                DCD     DMA1_Channel4_5_IRQHandler     ; DMA1 Channel 4 and Channel 5
                DCD     ADC1_IRQHandler                ; ADC1 
                DCD     TIM1_BRK_UP_TRG_COM_IRQHandler ; TIM1 Break, Update, Trigger and Commutation     30
                DCD     TIM1_CC_IRQHandler             ; TIM1 Capture Compare
                DCD     0                              ; Reserved
                DCD     TIM3_IRQHandler                ; TIM3
                DCD     TIM6_IRQHandler                ; TIM6
                DCD     0                              ; Reserved
                DCD     TIM14_IRQHandler               ; TIM14            36
                DCD     TIM15_IRQHandler               ; TIM15
                DCD     TIM16_IRQHandler               ; TIM16
                DCD     TIM17_IRQHandler               ; TIM17
                DCD     I2C1_IRQHandler                ; I2C1
                DCD     I2C2_IRQHandler                ; I2C2           41
                DCD     SPI1_IRQHandler                ; SPI1
                DCD     SPI2_IRQHandler                ; SPI2
                DCD     USART1_IRQHandler              ; USART1
                DCD     USART2_IRQHandler              ; USART2     45

__Vectors_End

3.根據上述計算,在keil中配置flash和RAM如下

4. STM32F030R8Tx從bootloader跳轉到應用程序,需要在進入main函數第一時間將0x8003000起始地址開始複製向量表

    到0x20000000起始的RAM中,複製內容的大小是上面計算出的0xb4,那麼添加以下代碼實現

 5.串口通信校驗和計算

/**
* @brief 計算checksum
* @param [in] uint8_t const*
*  - 待計算的數據
* @param [in] uint8_t 
*  - len 計算數據的長度
* @return uint8_t
*  - checksum計算結果
*
*/
static uint8_t get_checksum(uint8_t const*dat, uint8_t len)
{
	uint8_t chksum = 0;
 
	for (uint8_t i = 0; i < len; i++)
	    chksum += dat[i];

	return chksum;
}

6. 通信實現2個命令,一個是獲取硬件版本命令,另一個是進入OTA升級命令,定義如下


typedef enum
{
	
	UART_GET_VERSION = 0x01,
	STM32_ENTER_DFU = 0X02,
	UART_HEAD = 0xff,
}E_UART_COMMAND;

7.本例程版本使用4個字節的字符串格式,格式爲Vx.x,其中V和.爲固定格式,x爲任意字符,版本在sdk_config.h中定義,

定義如下

  

8.上位機發送UART_GET_VERSION到stm32,收到此命令後會回覆硬件版本給上位機


/**
* @brief 回覆版本
*/
void reply_stm32_version(void)
{
	
		uint8_t buf[5];
		uint8_t version_buf[] = FIRMWARE_VERSION_DEF;
		for(uint8_t i=0; i<4; i++)
		{
			buf[i] = version_buf[i];
		}
		buf[4] = enter_stm32_dfu;
		USART_Send_Data(UART_GET_VERSION,buf,5);
	 
}

9. 上位機發送STM32_ENTER_DFU命令到stm32,收到此命令後會在flash最後一個扇區將升級包的版本和升級標誌寫入到

  flash中,然後啓動軟件復位進入bootloader引導程序,寫入flash實現如下


/**
* @brief 寫入版本
*/
void write_version_to_flash(uint8_t *dfu_version)
{
	HAL_FLASH_Unlock();
	EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
  EraseInitStruct.PageAddress = FLASH_USER_START_ADDR;
  EraseInitStruct.NbPages = (FLASH_USER_END_ADDR - FLASH_USER_START_ADDR) / FLASH_PAGE_SIZE;
  uint32_t PageError;
  HAL_StatusTypeDef err_code = HAL_FLASHEx_Erase(&EraseInitStruct, &PageError);
	APP_ERROR_CHECK(err_code);
	
	 Address = FLASH_USER_START_ADDR;

	  uint8_t version_buf[] = FIRMWARE_VERSION_DEF;
    uint32_t content = *(uint32_t*)version_buf;
    err_code = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, Address, content);
    APP_ERROR_CHECK(err_code);
    Address += 4;
	
	  if(dfu_version)
			content = *(uint32_t*)dfu_version;
		else
			content = U32_MAX_VALUE;
	  err_code = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, Address, content);
    APP_ERROR_CHECK(err_code);
  

  /* Lock the Flash to disable the flash control register access (recommended
     to protect the FLASH memory against possible unwanted operation) *********/
  HAL_FLASH_Lock();
		
}



/**
* @brief 進入DFU
*/
static void enter_dfu(void)
{
		if(enter_stm32_dfu)
		{
			enter_stm32_dfu = 0;
			write_version_to_flash(from_app_stm32_version);
			NRF_LOG_INFO("STM32_ENTER_DFU");
			delay_ms(100);
			NVIC_SystemReset();
		}
}
  • STM32 bootloader部分

    1. 由於bootloader打算分配12KB空間,則flash和RAM配置如下

2. bootloader程序會先從最後一個扇區讀取OTA升級標誌

#define FLASH_USER_START_ADDR   ADDR_FLASH_PAGE_63   /* Start @ of user Flash area */
#define FLASH_USER_END_ADDR     ADDR_FLASH_PAGE_63 + FLASH_PAGE_SIZE   /* End @ of user Flash area */

uint32_t JumpAddress;
pFunction Jump_To_Application;

uint32_t Address = 0;
__IO uint32_t read_flash_value = 0;

/*Variable used for Erase procedure*/
static FLASH_EraseInitTypeDef EraseInitStruct;



uint8_t version_buffer[4];
uint8_t dfu_flag;

 3.如果dfu_flag爲0表示無需升級,由以下代碼進行跳轉到0x08003000地址

if (((*(__IO uint32_t*)ApplicationAddress) & 0x2FFE0000 ) == 0x20000000)
{
	NRF_LOG_INFO("enter app"); 
	//跳轉至用戶代碼
	EXTI->PR = 0x1fffff;
	JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4);
	Jump_To_Application = (pFunction) JumpAddress;

	//初始化用戶程序的堆棧指針
	__set_MSP(*(__IO uint32_t*) ApplicationAddress);
	Jump_To_Application();
}
else
{
	   NRF_LOG_INFO("no user Program");
}

  while(1); 

4.如果dfu_flag爲1表示需要升級,會先初時化LCD並顯示0%,然後擦除從0x08003000地址到最後一個扇區前的所有空間

 5. bootloader串口命令定義

typedef enum
{
	
	UART_GET_VERSION = 0x01,
	UART_DFU_FILE = 0XD9,
	UART_DFU_DOWNLOAD = 0XDA,
	UART_HEAD = 0xff,
}E_UART_COMMAND;

6.初時化串口,併發送硬件版本和OTA標誌到上位機,請求上位機發送升級包內容

7.上位機收到OTA標誌,將升級包的文件名稱和文件長度發給stm32,例如 spi.bin升級文件,假如文件長度爲0x7294,則發送的數據如下              

包頭 包長 命令   s  p  i   . b  i  n  \0         升級包文件長度29332 校驗和
0xff 0x10 0xd9 0x73 0x70 0x69 0x2e 0x62 0x69 0x6e 0x00 0x00 0x00 0x72 0x94 0xa1

8.stm收到UART_DFU_FILE命令會對文件名和長度的有效性等進行判斷

 

9.stm32回覆UART_DFU_DOWNLOAD命令給上位機,上位機收到此命令後開始傳輸升級包內容,升級包內容格式內如下

                  數據長度超過128,則按(128+8)的包長髮送,不足128的數據,則發送(餘下的數據+8)的包長髮送

包頭 包長 命令   包序號從0開始遞增            升級包數據(1~128Byte) 校驗和
0xff 0x10 0xda 0x00 0x00 0x00 0x00 0x** 0x** 0x** 0x** 0x** 0x** 0x** 0x** 0x**

 10.stm32收到升級包後會對包序號是否連續進行判斷,如果不連續則說明丟包,升級會超時重啓

/**
* @brief 升級超時
*/
void dfu_timeout_handler(void)
{
	if(dfu_timeout)
	{
		if(--dfu_timeout == 0)
		{
			NVIC_SystemReset();
		}
	}
}

11.如果接收的包連續,則會一直寫入到flash中

12.升級包百分比計算 

    當前所寫入的總長度與升級包文件長度的比,得出升級進度百分比

  calcPercent = readTotalLen;     
  calcPercent *= 100;                     
  dfu_percent = calcPercent / fileLength; 

13.升級完成後,dfu_flag清0,result設成0表示升級完成

 if(readTotalLen == fileLength)
{
    dfu_flag = 0;
    result = 0;
 }

14.dfu_flag爲0,退出循環

15.擦除OTA標誌,發送OTA完成標誌給上位機,最後stm32復位重啓

  • 上位機串口通信分發升級包部分

    1.基於github上的例程進行修改,使用VS2019打開解決方案

2.定義串口類對象

	CSerialPort m_SerialPort;//About CSerialPort 

3.獲取已插入的有效串口號


	//獲取串口號
	vector<SerialPortInfo> m_portsList = CSerialPortInfo::availablePortInfos();
	TCHAR m_regKeyValue[255];
	for (unsigned int i = 0; i < m_portsList.size(); i++)
	{
#ifdef UNICODE
		int iLength;
		const char * _char = m_portsList[i].portName.c_str();
		iLength = MultiByteToWideChar(CP_ACP, 0, _char, strlen(_char) + 1, NULL, 0);
		MultiByteToWideChar(CP_ACP, 0, _char, strlen(_char) + 1, m_regKeyValue, iLength);
#else
		strcpy_s(m_regKeyValue, 255, m_portsList[i].portName.c_str());
#endif
		m_PortNr.AddString(m_regKeyValue);
		m_PortNr2.AddString(m_regKeyValue);
	}
	m_PortNr.SetCurSel(0);
	m_PortNr2.SetCurSel(0);

3. 準備串口連接

m_SerialPort.readReady.connect(this, &CCommDlg::OnReceive);

4.打開和關閉串口,波特率固定爲115200,無奇偶校驗,8位數據長度,1位停止位

void CCommDlg::OnBnClickedButtonOpenClose()
{
	// TODO:  在此添加控件通知處理程序代碼
	//GetDlgItem(IDC_SendEdit)->SetFocus();
	CString temp;
	m_OpenCloseCtrl.GetWindowText(temp);///獲取按鈕的文本
	UpdateData(true);
	if (temp == _T("關閉串口"))///表示點擊後是"關閉串口",也就是已經關閉了串口
	{
		m_SerialPort.close();
		m_OpenCloseCtrl.SetWindowText(_T("打開串口"));///設置按鈕文字爲"打開串口"
	}
	///打開串口操作
	else if (m_PortNr.GetCount() > 0)///當前列表的內容個數
	{
		string portName;
		int SelBaudRate;
		int SelParity;
		int SelDataBits;
		int SelStop;

		UpdateData(true);
		m_PortNr.GetWindowText(temp);///CString temp
#ifdef UNICODE
		portName = CW2A(temp.GetString());
#else
		portName = temp.GetBuffer();
#endif	

		SelBaudRate = 115200;	

		SelParity = 0;

		SelDataBits = 8;

		SelStop = 0;

		m_SerialPort.init(portName, SelBaudRate, itas109::Parity(SelParity), itas109::DataBits(SelDataBits), itas109::StopBits(SelStop));
		m_SerialPort.open();

		if (m_SerialPort.isOpened())
		{
			m_OpenCloseCtrl.SetWindowText(_T("關閉串口"));
		}
		else
		{
			AfxMessageBox(_T("串口已被佔用!"));
		}
	}
	else
	{
		AfxMessageBox(_T("沒有發現串口!"));
	}
}

5.關閉窗口釋放串口資源


void CCommDlg::OnClose()
{
	// TODO:  在此添加消息處理程序代碼和/或調用默認值
	m_SerialPort.close();
	m_SerialPort2.close();
        if (pOTA_file_buf != NULL) {
		delete pOTA_file_buf;
		pOTA_file_buf = NULL;
	}
	CDialogEx::OnClose();
}

  6. 獲取升級包文件名和長度


void CCommDlg::OnBnClickedButtonOpenClose4()
{
	// TODO: 在此添加控件通知處理程序代碼
	  // 設置過濾器   
	TCHAR szFilter[] = _T("文本文件(*.bin)|*.bin|所有文件(*.*)|*.*||");

	// 構造打開文件對話框
	CFileDialog fileDlg(TRUE, _T("txt"), NULL, 0, szFilter, this);
	CString strFilePath;
	if (IDOK == fileDlg.DoModal())
	{
		// 如果點擊了文件對話框上的“打開”按鈕,則將選擇的文件路徑顯示到編輯框裏   
		strFilePath = fileDlg.GetPathName();
		SetDlgItemText(IDC_EDIT2, strFilePath);
		CFile file;
		file.Open(strFilePath, CFile::typeBinary, NULL);
		file_len = (DWORD)file.GetLength();
		char buf[200];
		sprintf(buf, "%d", file_len);
		CString str_temp((char*)buf);
		m_file_size.SetWindowTextW(str_temp);
		m_ota_file_name = file.GetFileName();  //獲取升級包文件名
	
		if (pOTA_file_buf)   //如果指針爲非空,先釋放內存
		{
			delete pOTA_file_buf;
			pOTA_file_buf = NULL;
		}
		if (pOTA_file_buf == NULL) // 分配內存
		{
			pOTA_file_buf = new char[file_len];
			file.Read(pOTA_file_buf, file_len);  // 讀取升級包所有內容

		}
		file.Close();

	}

}

7.串口接收監聽OnReceive,

void CCommDlg::OnReceive()
{

	int iRet = m_SerialPort.readAllData(uart_receive_buf);

	if (iRet > 0)
	{
		

		if (uart_receive_buf[0] != (char)0xff)  //包頭檢查
			return;
		BYTE len = uart_receive_buf[1];
		if (len < 4 || len > 150)        // 長度限制檢查
			return;
		len -= 1;
		char checksum = calc_checksum((BYTE*)uart_receive_buf, len);
		if (checksum != uart_receive_buf[len])  // 校驗和檢查
			return;
		switch ((BYTE)uart_receive_buf[2])
		{
		case 0x01:  // 獲取版本及OTA標誌
		{
			if (uart_receive_buf[3 + 4])  // 升級標誌
			{
				m_dfu_status.SetWindowTextW(_T("升級中..."));
				if ((pOTA_file_buf != NULL) && (file_len > 0)) {
				  m_dfu_index = 0;
				  package_index = 0;
				  BYTE len = m_ota_file_name.GetLength(); 
				  char send_buf[128];
				  BYTE index = 0;
				  send_buf[index++] = (char)0xff;
				  send_buf[index++] = len + 9;
				  send_buf[index++] = (char)0XD9;  //文件名和長度發送命令
				  for (DWORD i = 0; i < len; i++)
				  {
					  send_buf[index++] = (char)m_ota_file_name.GetAt(i);
				  }
				  send_buf[index++] = '\0';
				  send_buf[index++] = (file_len >> 24) & 0xff;
				  send_buf[index++] = (file_len >> 16) & 0xff;
				  send_buf[index++] = (file_len >> 8) & 0xff;
				  send_buf[index++] =  file_len & 0xff;
				  send_buf[index] = calc_checksum((BYTE*)send_buf, index);
				  m_SerialPort.writeData(send_buf, index+1);
				}
				else
				{
					m_dfu_status.SetWindowTextW(_T("請選擇升級bin文件"));
				}
			}
			else
			{
				m_dfu_status.SetWindowTextW(_T("應用程序"));
			}
			uart_receive_buf[3 + 4] = '\0';
			CString str((char*)&uart_receive_buf[3]);
			m_version_name.SetWindowTextW(str);
			
		}
			break;

		case 0XDA:   // 發送升級包內容
			if ((pOTA_file_buf != NULL) && (file_len > 0)) {
				switch (uart_receive_buf[4])
				{
				case 0:
				case 1:
				{
					char buf[200];
					sprintf(buf, "升級完成 %d%%", uart_receive_buf[3]);
					CString temp((char*)buf);
					m_dfu_status.SetWindowTextW(temp); //升級進度
					if (m_dfu_index >= file_len)
						break;
					DWORD len = 128;
					if ((m_dfu_index + 128) > file_len) // 不夠128
					{
						len = file_len - m_dfu_index;
					}
					
					buf[0] = (char)0xff;
					buf[1] = (char)(len+8);
					buf[2] = (char)0xda;
					buf[3] = (package_index >> 24) & 0xff;
					buf[4] = (package_index >> 16) & 0xff;
					buf[5] = (package_index >> 8) & 0xff;
					buf[6] = package_index & 0xff;
					for (DWORD i = 0; i < len; i++) {
						buf[7 + i] = pOTA_file_buf[m_dfu_index + i];
					}
					m_dfu_index += len;
					package_index++;  //包序號遞增
					len += 7;
					buf[len] = (char)calc_checksum((BYTE*)buf, (BYTE)len);
					
					m_SerialPort.writeData(buf, len+1);  //發送升級包


				}
					break;
				case 2:
					m_dfu_status.SetWindowTextW(_T("升級失敗!"));
					break;
				default:
					break;
				}
			}
			else
			{
				m_dfu_status.SetWindowTextW(_T("請選擇升級bin文件"));
			}
			
			break;
		default:
			break;
		}
		CString str1;
		if (m_nHexCheck)
		{
			char buf[2048];
			hex_disp_buffer(uart_receive_buf, buf, iRet);
			 str1 = ((char*)buf);
			 str1 += "\n";
		}
		else
		{
			str1 = (char*)uart_receive_buf;
		}
		
		m_ReceiveCtrl.SetSel(-1, -1);
		m_ReceiveCtrl.ReplaceSel(str1);
	}
	
}
  • 運行結果:

  • DEMO下載地址:

https://download.csdn.net/download/mygod2008ok/12579383 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章