인하대학교 공과대학에서 전자공학 학사 학위를 받았으며 임베디드 시스템용 소프트웨어 개발자로써 수년간의 경력을 쌓았습니다. 임베디드 시스템 및 프로그래밍을 위한 전문 강사로도 활동 중입니다. 아이폰 3GS 등장과 같은 시기에 맥(북)에 입문하였고, 그때부터 맥(북) 자동화에 관심을 갖게 되었습니다. '맥(북)에서 사용할 수 있는 시리 리모트', '키보드마에스트로를 이용한 구글번역기' 같은 오픈 소스를 깃허브(https://github.com/guileschool) 에 두고 개발 및 유지보수하고 있습니다. 오픈 소스 하드웨어 '비글본블랙' 에도 많은 관심을 가지고 있습니다. 맥(북) 자동화를 이용하여 생산성을 높일 수 있는 컴퓨터를 사용하는 모든 분야에 관심을 가지고 있고, 이를 필요로 하는 사람들을 돕고 있습니다.
강의 요청 및 기타 문의사항은 guileschool@gmail.com으로 보내주세요 :)
강의
수강평
- FreeRTOS 프로그래밍
- FreeRTOS 프로그래밍
- ARM Cortex-M 프로세서 프로그래밍
게시글
- 질문&답변 - 재진입가능여부에 관한 질문 - 안녕하세요. 박상우님!아래와 같이 사용하시면 void swap(int x, int y) 은 재진입에 대응할 수 있는 함수로서의 기능을 잘 수행해줍니다.// 태스크 1int a = 10, b = 20;swap(&a, &b); // temp1은 태스크1의 스택// 태스크 2 (동시 실행)int c = 30, d = 40;swap(&c, &d); // temp2는 태스크2의 스택하지만, 아래와 같은 사용 예시에서는 재진입이 안된다는 점 양지하시기 바랍니다.이건 swap 함수가 재진입 불가능해서가 아니라, 호출자가 같은 데이터에 동시 접근했기 때문입니다.// 태스크 1swap(&shared_x, &shared_y);// 태스크 2 (동시 실행)swap(&shared_x, &shared_y);- 끝 - - 1
- 1
- 19
 
- 질문&답변 - 01_TASKMAN프로젝트 디버깅 모드 실패 - 안녕하세요. 학습자님!STM32CubeIDE 최신 버전에서 GCC 컴파일러가 업데이트되면서 발생하는 문제가 의심되어 chatGPT 에 확인한 결과 예상대로 그 문제가 맞았습니다.__FILENAME__ 매크로가 더 이상 기본 제공되지 않습니다.__FILE_NAME__ 을 사용하시면 되겠습니다. 아래 내용 참고해주세요.컴파일러 옵션으로 매크로 정의 (가장 깔끔)1. 프로젝트 우클릭 → Properties2. C/C++ Build → Settings → Tool Settings3. MCU GCC Compiler → Preprocessor4. Defined symbols (-D) 에 추가:```FILENAME=__FILE_NAME__``` - 1
- 2
- 26
 
- 질문&답변 - 그러면 malloc/free가 아닌 동적할당자를 써서 메모리를 할당했기떄문에 - 안녕하세요. 박상우님!필요하다면 main 함수 시작 직후 c runtime heap 영역에 malloc 이나 new 로 큰 메모리 블럭을 할당하시고 그 영역을 bss 에 할당하고 있는 것처럼 freertos 용 동적 메모리 풀을 선언하여 사용하실 수 있습니다. // FreeRTOSConfig.h #define configAPPLICATION_ALLOCATED_HEAP 1 #define configTOTAL_HEAP_SIZE (100 * 1024) // main.c uint8_t *ucHeap; // extern으로 선언된 ucHeap을 위해 int main(void) { // C 런타임 heap에서 할당 ucHeap = (uint8_t*)malloc(configTOTAL_HEAP_SIZE); if (ucHeap == NULL) { // 할당 실패 처리 while(1); } // 이제 FreeRTOS는 malloc된 영역을 ucHeap으로 사용 xTaskCreate(...); vTaskStartScheduler(); return 0; } - 1
- 2
- 23
 
- 질문&답변 - 실행순서 - 안녕하세요. 박상우님!멀티태스킹 환경에서의 printf 디버깅은 자칫하면 태스크의 동작 상황에 대해 잘못 이해하는 오류를 범하기 쉬운데요. 그런 의미에서 지금 하신 질문과 궁금증은 그 의미가 크다고 할 수 있습니다.답변 드리기 앞서 제가 수행한 테스트와 그 결과를 지금부터 봐주시죠.테스트에 사용한 코드는 다음과 같습니다. 박상우님이 궁금해 하시는 그 조건이죠.저는 로직분석기를 이용해서 태스크 동작을 면밀히 보기 위해 필요한 코드를 추가하였습니다.HAL_GPIO_WritePin(GPIOB, T1_Pin, GPIO_PIN_SET); 바로 이거입니다.static void Task1( void const *pvParameters ) { const char *pcTaskName = "Task1"; pvParameters = pvParameters; // for compiler warning /* Print out the name of this task. */ printf( "%s is running\n", pcTaskName ); printf("\n------- Task1 information -------\n"); printf("task1 name = %s \n",pcTaskGetName( xHandle1 )); printf("task1 priority = %d \n",(int)uxTaskPriorityGet( xHandle1 )); // printf("task1 status = %d \n",eTaskGetState( xHandle1 )); printf("----------------------------------\n"); //vTaskDelay(1); while(1) { HAL_GPIO_WritePin(GPIOB, T1_Pin, GPIO_PIN_SET); /* TODO #3: 코드를 실행 하여 보고 vTaskDelay() 코드를 주석 처리한 후 그 결과를 설명한다 */ #if 1 // No comment //vTaskDelay (pdMS_TO_TICKS (1000)); printf("a"); fflush(stdout); // 문자 'a' 출력 #endif // TODO #3 HAL_GPIO_WritePin(GPIOB, T1_Pin, GPIO_PIN_SET); task1timer++; } } static void Task2( const struct Param_types *Param ) { const char *pcTaskName = "Task2"; /* Print out the name of this task. */ printf( "%s is running\n", pcTaskName ); printf("\n------- Task2 parameter passed from main --------\n"); printf("task2 first parameter = %d \n",Param->P1); printf("task2 second parameter = %d \n",Param->P2); printf("--------------------------------------------------\n"); while(1) { HAL_GPIO_WritePin(GPIOB, T2_Pin, GPIO_PIN_SET); int i; /* TODO #3: 코드를 실행 하여 보고 vTaskDelay() 코드를 주석 처리한 후 그 결과를 설명한다 */ #if 1 // No comment //vTaskDelay (pdMS_TO_TICKS (1000)); printf("b"); fflush(stdout); // 문자 'b' 출력 #endif // TODO #3 HAL_GPIO_WritePin(GPIOB, T2_Pin, GPIO_PIN_SET); task2timer++; } } 아래 실행결과를 보시죠. 정확하게 우선순위를 동일하게 부여한 Task1 와 Task2 은 printf, fflush, HAL_UART_Transmit 가 실행되고 있는지 여부와 관계없이 정확하게 1밀리 초 간격으로 라운드로빈이 수행되는 것을 볼 수 있습니다.(사진) (사진)또한 아래와 같은 테스트 결과도 보시겠습니다. 단지 171 라인의 실행 유무에 따라 아래와 같이 printf 출력 결과는 크게 달라집니다.(사진)(사진)(사진) 이 증상은 어떻게 설명할 수 있는가 하면 규칙적으로 발생하는 tick 인터럽트와 주기적(일정한 간격)으로 실행되는 for 루프를 고려하면 어떤 태스크는 운이좋아 콘솔 출력버퍼에 글자를 더 많이 입력할 수 있게 되고 또 다른 어떤 태스크는 콘솔 출력버퍼에 글자를 덜 입력하게 되는 상황으로 이해할 수 있을 겁니다. 아래 함수 HAL_UART_Transmit 내에서는 UART 전송을 위해 버퍼가 비워지는 때까지 CPU 시간을 소모하면서 기다리는 코드(UART_WaitOnFlagUntilTimeout) 가 사용되고 있는데, 이 함수가 바로 지금 이러한 이상한 일이 일어나는 주 원인일 것입니다. 절묘한 타이밍 차이에 따라 task1 이나 task2 둘 중 하나는 이 쓸모없는? 일만 수행하게 되었을 겁니다. 어떤 태스크는 a (혹은 b) 글자를 버퍼에 넣는 생산적인 일을 위주로 실행하는가 하면, 어떤 태스크는 이 기다리는 일을 위주로 수행 하는 거죠.HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout) { uint8_t *pdata8bits; uint16_t *pdata16bits; uint32_t tickstart = 0U; /* Check that a Tx process is not already ongoing */ if (huart->gState == HAL_UART_STATE_READY) { if ((pData == NULL) || (Size == 0U)) { return HAL_ERROR; } /* Process Locked */ __HAL_LOCK(huart); huart->ErrorCode = HAL_UART_ERROR_NONE; huart->gState = HAL_UART_STATE_BUSY_TX; /* Init tickstart for timeout management */ tickstart = HAL_GetTick(); huart->TxXferSize = Size; huart->TxXferCount = Size; /* In case of 9bits/No Parity transfer, pData needs to be handled as a uint16_t pointer */ if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE)) { pdata8bits = NULL; pdata16bits = (uint16_t *) pData; } else { pdata8bits = pData; pdata16bits = NULL; } /* Process Unlocked */ __HAL_UNLOCK(huart); while (huart->TxXferCount > 0U) { if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK) { return HAL_TIMEOUT; } if (pdata8bits == NULL) { huart->Instance->DR = (uint16_t)(*pdata16bits & 0x01FFU); pdata16bits++; } else { huart->Instance->DR = (uint8_t)(*pdata8bits & 0xFFU); pdata8bits++; } huart->TxXferCount--; } if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK) { return HAL_TIMEOUT; } /* At end of Tx process, restore huart->gState to Ready */ huart->gState = HAL_UART_STATE_READY; return HAL_OK; } else { return HAL_BUSY; } } 따라서, 개발과 디버깅에 있어 언제든 이런 상황이 일어나고 있다는 점을 고려해야 할겁니다.그리고, 추가적으로 아래 소스도 참고해보시기 바랍니다.### 1. printf.chttps://github.com/lattera/glibc/blob/master/stdio-common/printf.c### 2. vfprintf.chttps://github.com/lattera/glibc/blob/master/stdio-common/vfprintf.c### 3. fileops.c (new_do_write, IOnew_file_write 등)https://github.com/lattera/glibc/blob/master/libio/fileops.chttps://github.com/bminor/glibc/blob/master/libio/fileops.c### 4. syscall.S (x86_64 어셈블리)https://github.com/lattera/glibc/blob/master/sysdeps/unix/sysv/linux/x86_64/syscall.S### 5. iofflush.chttps://github.com/lattera/glibc/blob/master/libio/iofflush.c - 1
- 2
- 23
 
- 질문&답변 - uart 전송중에는 스위칭이 금지되나요? - 안녕하세요. 박상우님!freertos 수업중에 사용한 예제 소스 기준으로 답변드립니다. 현재 우리 소스 코드에서는 stm32cubeide 에서 기본으로 제공하는 HAL API UART 드라이버를 이용하여 printf 을 수행합니다.결국 질문 내용은 관련 핵심 함수인 HAL_UART_Transmit 을 살펴보아야 할텐데요. 해당 함수내에서 tick 인터럽트를 불활성하거나 작동을 방해할 만한 요소는 발견되지 않습니다. 따라서, 간단히 답변드리자면 'fflush 나 HAL_UART_Transmit 함수가 실행중이더라도 tick 인터럽트 이벤트가 발생한다면 인터럽트는 즉시 처리된다. 그리고 문맥전환도 필요하다면 실행된다' 입니다.HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout) { uint8_t *pdata8bits; uint16_t *pdata16bits; uint32_t tickstart = 0U; /* Check that a Tx process is not already ongoing */ if (huart->gState == HAL_UART_STATE_READY) { if ((pData == NULL) || (Size == 0U)) { return HAL_ERROR; } /* Process Locked */ __HAL_LOCK(huart); huart->ErrorCode = HAL_UART_ERROR_NONE; huart->gState = HAL_UART_STATE_BUSY_TX; /* Init tickstart for timeout management */ tickstart = HAL_GetTick(); huart->TxXferSize = Size; huart->TxXferCount = Size; /* In case of 9bits/No Parity transfer, pData needs to be handled as a uint16_t pointer */ if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE)) { pdata8bits = NULL; pdata16bits = (uint16_t *) pData; } else { pdata8bits = pData; pdata16bits = NULL; } /* Process Unlocked */ __HAL_UNLOCK(huart); while (huart->TxXferCount > 0U) { if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK) { return HAL_TIMEOUT; } if (pdata8bits == NULL) { huart->Instance->DR = (uint16_t)(*pdata16bits & 0x01FFU); pdata16bits++; } else { huart->Instance->DR = (uint8_t)(*pdata8bits & 0xFFU); pdata8bits++; } huart->TxXferCount--; } if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK) { return HAL_TIMEOUT; } /* At end of Tx process, restore huart->gState to Ready */ huart->gState = HAL_UART_STATE_READY; return HAL_OK; } else { return HAL_BUSY; } } - 1
- 2
- 16
 
- 질문&답변 - 그림들도 해석 할 수 있어야 하나요? - 안녕하세요. 이윤주님!CORTEX 패밀리 각각의 특징을 요약해드린것이고요, '꼭 이해하고 가야 하는 내용은 아니다' 라고 말씀드립니다. 더불어 이번 수업은 CORTEX-M 기반의 STM32 칩을 중심으로 진행됩니다.그러니 해당 주제에만 집중하셔도 좋습니다. 🙂 - 1
- 2
- 39
 
- 질문&답변 - 스택오버플로우 실습 중 stack size 설정 질문 - 안녕하세요, SeongJin 님!영상 촬영 당시에는 스택 사이즈가 128 이었고, 이후 예제 튜토리얼이 버젼업되면서 스택 크기 여유분 증가의 필요성 때문에 수정하게 되었고 현재는 256 이 맞습니다. 추가적으로 궁금한 점 있으시면 다시 질문 남겨주세요. 감사합니다 🙂 - 0
- 2
- 25
 
- 질문&답변 - no-stlink - 글쎄요.. 처음에 설치하시지 않았을까요?(사진) - 1
- 2
- 37
 
- 질문&답변 - 포팅 원합니다. - taegyu224님이 말씀하신 포팅 자료는 아래 링크에서 다운로드 하실 수 있습니다.https://inf.run/45f6Y혹여 사용하시는 컴파일러 버젼과 이 자료가 호환성 문제가 있을 수 있으니 그때는 아래 글을 참고해주세요.https://inf.run/9mVm4 - 2
- 2
- 43
 
- 질문&답변 - 4개의 CPU 사이클이 필요한 이유 - 안녕하세요. 이명운님!STM32 플래시 메모리는 CPU 성능에 보조를 맞출수 있도록 128비트 가속기 인터페이스를 채택하고 있습니다. 데이터 시트에는 플래시 메모리의 128비트를 버퍼에 채우는데 버스클럭 기준 1클럭이 필요한지 4클럭이 필요한지에서 대해서는 명확한 설명은 없습니다. 다만 아래 그림을 참고해 유추해보면 4클럭 단위로 버퍼를 채우는 것으로 생각해도 좋을 것입니다.CORE 는 이 버퍼에서 명령어들을 페치합니다. 플래시 가속기는 버퍼가 비워지면 준비한 다음 128비트를 버퍼에 채우는 식으로 동작하는 것을 아래 그림에서 확인해주세요.(사진) - 1
- 2
- 52
 






