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에서 처리하는 것으로 다시 만들었다.
'임베디드 개발 > STM32 (ARM Cortex-M)' 카테고리의 다른 글
STM32 ] TouchGFX, 하드웨어와 상호작용하기 (0) | 2023.07.18 |
---|---|
STM32 ] TouchGFX 설치하고 여러 Widget과 Interaction 사용 해보기 (0) | 2023.07.17 |
STM32 ] TFTP Server 파일 송수신 + USB Host MSC (9) | 2023.04.15 |
STM32 ] TFTP Server (0) | 2023.04.14 |
STM32 ] USB Host MSC (0) | 2023.04.13 |