본문 바로가기
DSP, MCU/STM32 (ARM Cortex-M)

STM32 ] TFTP Bootloader

by eteo 2023. 4. 16.

 

 

 

 

 

Board : STM32F429ZI (Nucleo 144)

STM32CubeIDE : version 1.10.1

Firmware Package : FW_F4 V1.27.1

 

 

아래 예제 기반

 

C:\Users\J\STM32Cube\Repository\STM32Cube_FW_F4_V1.27.1\Projects\STM324x9I_EVAL\Applications\LwIP\LwIP_IAP

 

 

일단 위 경로에서 flash_if.h, flash_if.c tftpserver.h, tftpserver.c 파일을 가져온다.

 

 

 

main.h에는 다음과 같이 USER APPLICATION 에서 사용하는 플래시 주소가 선언되 었다. 용어를 어디에선 Page라고 쓰고 어디선 Sector라고 하는데 둘은 같은말이다.

#define USER_FLASH_FIRST_PAGE_ADDRESS 0x08020000
#define USER_FLASH_LAST_PAGE_ADDRESS  0x081E0000
#define USER_FLASH_END_ADDRESS        0x081FFFFF

 

그리고 flash_if.h 에서 이 정보도 참조해야 하고 HAL 함수도 사용해야 하기 때문에 #include "main.h"해주고 있다.

 

 

 

예전 IAP 예제에서는 부트로더가 first 2 sectors만 차지했는데 이건 lwIP가 올라가니까 사용하는 Build Analyzer로 확인해보면 76.33KB를 사용하고 있다.

 

그래서 Sector 0 ~ Sector 4 는 부트로더가 사용하고 Sector 5 부터 User Application이 사용한다.

 

 

 

 

 

 

main.c 는 다음과 같다.

 

USER 버튼이 눌리지 않았으면 바로 User Application으로 점프하고 USER버튼이 눌린 상태면 부트로더 모드로 돌입한다.

 

#include "main.h"
#include "lwip.h"
#include "rtc.h"
#include "usart.h"
#include "gpio.h"

#include "tftpserver.h"
#include "flash_if.h"

typedef  void (*pFunction)(void);

pFunction Jump_To_Application;
uint32_t JumpAddress;

void SystemClock_Config(void);

int main(void) {

	HAL_Init();

	SystemClock_Config();

	MX_GPIO_Init();
	MX_USART3_UART_Init();
	MX_RTC_Init();
	MX_LWIP_Init();

	/* If Key push-button is pressed */
	if (HAL_GPIO_ReadPin(USER_Btn_GPIO_Port, USER_Btn_Pin) != GPIO_PIN_SET) {
		volatile uint32_t msp = (*(__IO uint32_t*) USER_FLASH_FIRST_PAGE_ADDRESS);
		/* Test if user code is programmed starting from address "APPLICATION_ADDRESS" */
		if (msp >= 0x20000000 && msp <= 0x20030000) {
			/* Jump to user application */
			JumpAddress = *(__IO uint32_t*) (USER_FLASH_FIRST_PAGE_ADDRESS + 4);
			Jump_To_Application = (pFunction) JumpAddress;
			/* Initialize user application's Stack Pointer */
			__set_MSP(*(__IO uint32_t*) USER_FLASH_FIRST_PAGE_ADDRESS);
			Jump_To_Application();

		}
	}

	IAP_tftpd_init();

	while (1) {
		MX_LWIP_Process();
	}

}

 

 

 

 

tftpserver.c에서 LCD를 사용하는 부분을 UART로 바꿔줄거다.

 

/* for print message using UART-----------------------------------------------*/
#include "stm32f4xx_hal.h"
#include <stdarg.h>

extern UART_HandleTypeDef huart3;
#define UART &huart3

#define USE_LOG_PRINT

#ifdef USE_LOG_PRINT
#define logPrint(fmt, ...)			printMsg(fmt, ##__VA_ARGS__)
#elif
#define logPrint(fmt, ...)
#endif


void printMsg(char *format, ...)
{
	char str[128];

	va_list args;
	va_start(args, format);
	vsprintf(str, format, args);
	HAL_UART_Transmit(UART, (uint8_t *)str, strlen(str), HAL_MAX_DELAY);
	va_end(args);
}

 

일단 HAL 함수를 쓰기 위해 "stm32f4xx_hal.h"를 포함하고 extern으로 huart3 핸들을 가져온다. 가변인자 출력함수를 만들고 logPrint()를 쓰지않을 경우를 생각해 가변인자 매크로로 선언해뒀는데 그냥 간단하게 아래처럼 해도 된다.

 

void logPrint(char *str)
{
	HAL_UART_Transmit(UART, (uint8_t *)str, strlen(str), HAL_MAX_DELAY);
}

 

 

그리고 TFTP_init()이 성공적으로 끝났을 때를 위해 한줄 추가해준다.

logPrint("TFTP bootloader has been initialized \n");

 

 

 

 

 

 

 

 

 

 

다음 이 부트로더를 위한 USER Application 프로젝트를 만들어보자.

 

예전에 만들었던 프로젝트를 재사용할건데, 유저버튼을 클릭하면 LED가 토글되고, 1초간격으로 터미널에 User Application이 실행중임을 출력하는 프로젝트이다.

 

 

STM32F429ZITX_FLASH.ld 파일로가서 플래시 시작주소와 length를 수정해준다.

 

 

 

그리고 startup 파일에서 SystemInit 함수를 찾아 F3키를 누르고 따라가면 벡터 테이블 위치를 설정하는 부분이 있는데 VECT_TAB_OFFSET을 수정해준다.

 

build output으로 binary 파일을 만든다.

 

 

 

빌드 후 해당 프로젝트 Debug 폴더에 들어가면 .bin 파일이 생겼다.

 

 

 

 

그럼 가장 중요한 IAP_wrq_recv_callback() 함수를 살펴보자. 그중에서도 수신한 데이터를 플래시 메모리에 프로그램하는 부분은 아래와 같다.

 

/* copy packet payload to data_buffer */
pbuf_copy_partial(pkt_buf, data_buffer, pkt_buf->len - TFTP_DATA_PKT_HDR_LEN,
                  TFTP_DATA_PKT_HDR_LEN);

total_count += pkt_buf->len - TFTP_DATA_PKT_HDR_LEN; 

count = (pkt_buf->len - TFTP_DATA_PKT_HDR_LEN)/4;    
if (((pkt_buf->len - TFTP_DATA_PKT_HDR_LEN)%4)!=0) 
count++;

/* Write received data in Flash */
FLASH_If_Write(&Flash_Write_Address, data_buffer ,count);

 

memcpy()가 아니라 굳이 pbuf_copy_partial() 함수를 쓰고 있는데 pbuf의 payload 일부분을 복사하는데 쓰인다. pkt_buf->payload를 지역변수인 data_buffer에 복사하는데, 4번째 매개변수인 src addr 시작위치의 offset이 opcode+block number 필드인 4byte이고, 복사할 길이는 pkt_buf->len - 4이다.

 

그 아래 data 길이를 4로 나눈 몫을 count에 대입하고 나머지가 있는 경우 count를 1 증가하는 것은 밑의 FLASH_IF_Write() 함수의 기본 쓰기 단위가 32bit word이기 때문이다. User manual에도 임베디드 플래시는 Byte, half-word, word and double word write 쓰기가 가능하다고 나와있고 그냥 HAL_FLASH_Program() 함수쓰면 되는데 왜 인터페이스 함수를 이렇게 만들어 놨는지는 모르겠다.

 

그보다 큰 문제는 복사대상인 data_buffer가 지역변수인데 초기화를 안하고 있고, 0으로 패딩하는 코드도 없으니 4로 나눈 나머지가 0이 아닐 때 끝에 쓰레기값이 들어갈 수 있어 보인다. 차라리 data_buffer를 static으로 만들고 복사 전에 0으로 memset하는 것이 나을 것 같다.

 

아무튼 나는 USB를 부트로더 용 뿐만아니라 파일저장 용도로도 쓸거기 때문에 tftp server에서 파일시스템 접근은 isr이 아니라 main routine에서 처리하는 것으로 다시 만들었다.