printf("ho_tari\n");
3일차 본문
2025.03.27
오늘의 학습 목표
1. 과제 review
2. Memory 구조체 access
3. Debugger 사용
4. RTOS 기반 programming
#define PERIPH_BASE 0x40000000UL
#define GPIOB_BASE (AHB1PERIPH_BASE + 0x0400UL)
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000UL)
0x40000000 + 20000 + 400 → 0x40020400 (GPIOB 시작 주소)
typedef struct
{
__IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
__IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */
__IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */
__IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
__IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
__IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
__IO uint32_t BSRR; /*!< GPIO port bit set/reset register, Address offset: 0x18 */
__IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */
__IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */
} GPIO_TypeDef;
__IO uint32_t MODER → 40020400
__IO uint32_t OTYPER → 40020404
__IO uint32_t OSPEEDR → 40020408
__IO uint32_t PUPDR → 4002040C
__IO uint32_t IDR → 40020410
__IO uint32_t ODR → 40020414
...
구조체 pointer 멤버변수
GPIOB → ODR = 0x10;
(*GPIOB).ODR = 0x10;
포인터
- 주소를 저장하는 변수
포인터를 쓰는 이유
- 서로 다른 함수에 위치한 변수를 access하기 위함 (전역 변수 사용을 최소화)
선언할 때 : 주소를 저장하는 공간을 알려줌
사용할 때 : 주소의 내용을 찾아감
구조체 pointer member 사용 LED CONTROL
<led.c>
#include "led.h"
void led_main(void)
{
while(1)
{
// (*GPIOB).ODR |= GPIO_PIN_0; // LED가 ON
// GPIOB->ODR ^= GPIO_PIN_1; // LED1 toggle 반전
// HAL_Delay(500);
// GPIOB->ODR &= ~GPIO_PIN_0; // LED가 OFF
// HAL_Delay(500);
led_all_on();
HAL_Delay(500);
led_all_off();
HAL_Delay(500);
shift_left_led_on();
shift_right_led_on();
shift_left_keep_ledon();
shift_right_keep_ledon();
flower_on();
flower_off();
}
}
void led_all_on(void)
{
#if 1 // 구조체 pointer member
GPIOB->ODR = 0xff;
#endif
#if 0 // DMA
// printf("int %d\n", sizeof(int)); // 4로 찍히는지 확인
*(unsigned int *)GPIOB_ODR = 0xff;
#endif
#if 0 // HAL
// HAL_GPIO_WritePin(GPIOB, 0xff, 1);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 |
GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7, 1);
#endif
}
void led_all_off(void)
{
#if 1 // 구조체 pointer member
GPIOB->ODR = 0x00;
#endif
#if 0 // DMA
// printf("int %d\n", sizeof(int)); // 4로 찍히는지 확인
*(unsigned int *)GPIOB_ODR = 0x00;
#endif
#if 0 // HAL
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 |
GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7, 0);
#endif
}
void shift_left_led_on(void)
{
#if 1 // 구조체 pointer member
for(int i = 0; i < 8; i++)
{
GPIOB->ODR = 0x01 << i;
HAL_Delay(100);
led_all_off();
}
HAL_Delay(100);
#endif
#if 0 // DMA
for(int i = 0; i < 8; i++)
{
*(unsigned int*)GPIOB_ODR = 0x01 << i;
HAL_Delay(100);
led_all_off();
}
HAL_Delay(100);
#endif
#if 0 // HAL
uint16_t ledpins[8] = {
GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3,
GPIO_PIN_4, GPIO_PIN_5, GPIO_PIN_6, GPIO_PIN_7
};
for(int i = 0; i < 8; i++)
{
HAL_GPIO_WritePin(GPIOB, ledpins[i], 1);
HAL_Delay(100);
led_all_off();
}
HAL_Delay(100);
#endif
}
void shift_right_led_on(void)
{
#if 1 // 구조체 pointer member
for(int i = 0; i < 8; i++)
{
GPIOB->ODR = 0x80 >> i;
HAL_Delay(100);
led_all_off();
}
HAL_Delay(100);
#endif
#if 0 // DMA
for(int i = 0; i < 8; i++)
{
*(unsigned int*)GPIOB_ODR = 0x80 >> i;
HAL_Delay(100);
led_all_off();
}
HAL_Delay(100);
#endif
#if 0 // HAL
uint16_t ledpins[8] = {
GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3,
GPIO_PIN_4, GPIO_PIN_5, GPIO_PIN_6, GPIO_PIN_7
};
for(int i = 7; i >= 0; i--)
{
HAL_GPIO_WritePin(GPIOB, ledpins[i], 1);
HAL_Delay(100);
led_all_off();
}
HAL_Delay(100);
#endif
}
void shift_left_keep_ledon(void)
{
#if 1 // 구조체 pointer member
for(int i = 0; i < 8; i++)
{
GPIOB->ODR |= 0x01 << i;
HAL_Delay(100);
}
led_all_off();
HAL_Delay(100);
#endif
#if 0 // DMA
for(int i = 0; i < 8; i++)
{
*(unsigned int*)GPIOB_ODR |= 0x01 << i;
HAL_Delay(100);
}
led_all_off();
HAL_Delay(100);
#endif
#if 0 // HAL
uint16_t ledpins[8] = {
GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3,
GPIO_PIN_4, GPIO_PIN_5, GPIO_PIN_6, GPIO_PIN_7
};
for(int i = 0; i < 8; i++)
{
HAL_GPIO_WritePin(GPIOB, ledpins[i], 1);
HAL_Delay(100);
}
led_all_off();
HAL_Delay(100);
#endif
}
void shift_right_keep_ledon(void)
{
#if 1 // 구조체 pointer member
for(int i = 0; i < 8; i++)
{
GPIOB->ODR |= 0x80 >> i;
HAL_Delay(100);
}
led_all_off();
HAL_Delay(100);
#endif
#if 0 // DMA
for(int i = 0; i < 8; i++)
{
*(unsigned int*)GPIOB_ODR |= 0x80 >> i;
HAL_Delay(100);
}
led_all_off();
HAL_Delay(100);
#endif
#if 0 // HAL
uint16_t ledpins[8] = {
GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3,
GPIO_PIN_4, GPIO_PIN_5, GPIO_PIN_6, GPIO_PIN_7
};
for(int i = 7; i >= 0; i--)
{
HAL_GPIO_WritePin(GPIOB, ledpins[i], 1);
HAL_Delay(100);
}
led_all_off();
HAL_Delay(100);
#endif
}
void flower_on(void)
{
#if 1 // 구조체 pointer member
for(int i = 0; i < 4; i++)
{
GPIOB->ODR |= 0x10 << i | 0x08 >> i;
HAL_Delay(150);
}
led_all_off();
HAL_Delay(100);
#endif
#if 0 // DMA
for(int i = 0; i < 4; i++)
{
*(unsigned int*)GPIOB_ODR |= 0x10 << i | 0x08 >> i;
HAL_Delay(150);
}
led_all_off();
HAL_Delay(100);
#endif
#if 0 // HAL
uint16_t ledpins[8] = {
GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3,
GPIO_PIN_4, GPIO_PIN_5, GPIO_PIN_6, GPIO_PIN_7
};
for(int i = 4; i >= 0; i--)
{
HAL_GPIO_WritePin(GPIOB, ledpins[i] | ledpins[7 - i], 1);
HAL_Delay(150);
}
led_all_off();
HAL_Delay(100);
#endif
}
void flower_off(void)
{
#if 1 // 구조체 pointer member
led_all_on();
HAL_Delay(100);
for(int i = 4; i >= 0; i--)
{
GPIOB->ODR &= ~(0x10 << i | 0x08 >> i);
HAL_Delay(150);
}
led_all_off();
HAL_Delay(100);
#endif
#if 0 // DMA
led_all_on();
HAL_Delay(100);
for(int i = 4; i >= 0; i--)
{
*(unsigned int*)GPIOB_ODR &= ~(0x10 << i | 0x08 >> i);
HAL_Delay(150);
}
led_all_off();
HAL_Delay(100);
#endif
#if 0 // HAL
uint16_t ledpins[8] = {
GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3,
GPIO_PIN_4, GPIO_PIN_5, GPIO_PIN_6, GPIO_PIN_7
};
led_all_on();
HAL_Delay(100);
for(int i = 0; i < 4; i++)
{
HAL_GPIO_WritePin(GPIOB, ledpins[i] | ledpins[7 - i], 0);
HAL_Delay(150);
}
HAL_Delay(100);
#endif
}



#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdint.h>
#include <string.h>
int unicode_to_utf8(uint32_t codepoint, char* utf8)
{
// 1바이트: 0xxxxxxx (ASCII)
if (codepoint <= 0b01111111)
{
utf8[0] = (char)codepoint;
return 1;
}
// 2바이트: 110xxxxx 10xxxxxx
else if (codepoint <= 0b11111111111)
{
utf8[0] = (char)(0b11000000 | ((codepoint >> 6) & 0b00011111));
utf8[1] = (char)(0b10000000 | (codepoint & 0b00011111));
return 2;
}
// 3바이트: 1110xxxx 10xxxxxx 10xxxxxx
else if (codepoint <= 0b1111111111111111)
{
utf8[0] = (char)(0b11100000 | ((codepoint >> 12) & 0b11100000));
utf8[1] = (char)(0b11100000 | ((codepoint >> 6) & 0b11100000));
utf8[2] = (char)(0b11100000 | (codepoint & 0b00111111));
return 3;
}
// 4바이트: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
else if (codepoint <= 0b100001111111111111111)
{
utf8[0] = (char)(0b11110000 | ((codepoint >> 18) & 0b00000111));
utf8[1] = (char)(0b10000000 | ((codepoint >> 12) & 0b00111111));
utf8[2] = (char)(0b10000000 | ((codepoint >> 6) & 0b00111111));
utf8[3] = (char)(0b10000000 | (codepoint & 0b00111111));
return 4;
}
return 0;
}
int main(void)
{
uint32_t name[] = { 0xBC15, 0xC131, 0xD638 };
char utf8_name[20] = { 0 };
int pos = 0;
for (int i = 0; i < 3; i++) {
pos += unicode_to_utf8(name[i], utf8_name + pos);
}
utf8_name[pos] = '\0';
printf("UTF-8 인코딩 결과 : ");
for (int i = 0; i < pos; i++) {
printf("%02X ", (unsigned char)utf8_name[i]);
}
return 0;
}
<실행 결과>

OS
- 운영체제 또는 오퍼레이팅 시스템
- 응용 프로그램이 요청하는 시스템 잦원을 효율적으로 분배하고 관리
- 시스템 자원을 효율적으로 관리하며 사용자가 컴퓨터를 편리하고 효과적으로 사용할 수 있도록 환경을 제공하는 여러 프로그램의 모임
- 사용자와 하드웨어간의 인터페이스로서 동작하는 시스템 소프트웨어
- 대표적인 운영체제로는 Windows, Mac, UNIX, LINUX가 있다.
실시간 운영체제
- 특정한 짧은 시간 내에 이벤트나 데이터의 처리를 보증하는 운영체제
- 싱글태스킹일 수도 있고 멀티태스킹일 수도 있으며 멀티태스킹의 경우 특수한 스케줄링 알고리즘을 사용
RTOS 특징
- 멀티태스킹을 지원 : 여러 개의 task를 동시에 동작시킬 수 있다. 각 task마다 우선 순위를 둘 수 있다.
- 짧은 interrupt latency : interrupt latency는 실제 인터럽트가 발생한 후 인터럽트 핸들러까지 도착하는 시간
- 작은 kernal size
- 유명한 RTOS : VxWorks, pSOS, Nucles
FreeRTOS
- 15년 동안 세계 유수의 칩 회사들과 협력하여 개발된 FreeRTOS는 현재 170초마다 다운로드되는 업계 최고의 마이크로컨트롤러 및 소형 마이크로프로세서용 실시간 운영 체제 (RTOS)
- MIT 오픈 소스 라이선스에 따라 무료로 배포되는 FreeRTOS에는 모든 산업 분야에서 사용할 수 있는 커널과 라이브러리 세트가 포함
- FreeRTOS는 오픈 소스 프로젝트 : https://github.com/FreeRTOS/FreeRTOS 사이트에서 소스코드를 다운로드하거나 변경 또는 개선 사항에 기여하거나 문제를 보고할 수 있다.
- MIT 오픈 소스 라이선스에 따라 FreeRTOS 코드를 릴리스하므로 상업 및 개인 프로젝트에서 사용할 수 있다.
FreeRTOS 소스 코드 다운로드
- www.freertos.org의 페이지에서 최신 FreeRTOS 및 장기 Support (LTS) 패키지 다운로드
시스템 자원 (System Resource)
- 컴퓨터 하드웨어와 같은 개념으로 CPU, 메모리, 입출력 장치, 저장 매체 등 시스템에서 사용할 수 있는 자원을 의미한다. 시스템 자원은 스스로 메모리 확보, 저장 위치, 저장 방법 등을 직접 결정할 수 없기 때문에 반드기 운영체제가 필요하다.
응용 프로그램
- 운영체제를 제외한 나머지 소프트웨어로 엑셀, 파워포인트, 엑세스 등을 사용자가 평소에 사용하는 프로그램을 의미한다.
- 운영체제는 응용 프로그램이 요청하는 메모리를 허가하고 분배
- 운영체제는 응용 프로그램이 요청하는 CPU 시간을 제공
- 운영체제는 응용 프로그램이 요청하는 입출력 장치 사용 여부를 허가 및 제어
- 운영체제에서 실행되는 모든 프로그램은 운영체제에 종속적일 수 밖에 없다.
커널
- 운영체제는 규모가 매우 큰 프로그램이므로 운영체제의 모든 부분을 메모리에 올려놓는 것은 메모리의 측명으로 봤을 때 굉장히 비효율적이다. 따라서, 운영체제는 필요한 부분만을 메모리에 올려서 사용하게 되는데 이 때 메모리에 상주하는 운영체제의 핵심부분을 커널이라고 한다.
- 커널은 메모리에 상주하는 부분이므로 운영체제의 핵심 부분이라고 볼 수 있기 때문에 주로 운영체제 = 커널이라고 한다.
시스템 콜
- 사용자는 운영체제의 기능을 담당하는 커널에 직접 접근할 수가 없다. 따라서 사용자와 커널 사이에 인터페이스 역할이 필요한데 시스템 콜리 바로 이 역할을 하게 된다.
- 사용자가 커널 영역을 사용할 수 있게 즉 응용프로그램이 시스템 자원에 직접 접근하여 필요한 기능을 사용할 수 있게 해주는 함수를 의미한다.
- 하지만 보통은 응용프로그램은 시스템 콜을 직접 사용하지 않고 해당 시스템 콜을 사용하여 만든 언어 별 라이브러리 API를 통해 커널에 접근할 수 있다.
process
- 실행파일을 클릭했을 때 메모리(RAM) 할당이 이루어지고 이 메모리공간으로 코드가 올라간다. 이 순간부터 이 프로그램은 process라 불리게 된다. 실행 중인 program을 process라고 한다.
task
- 일의 실행하는 최소 기본 단위, 프로세스와 쓰레드 의미
프로세스의 스케줄링
- CPU는 하나인데 동시에 여러 프로세스가 실행되어야 한다.
- CPU는 여러 개의 프로세스를 번갈아가면서 실행하는데 매우 고속이기 때문에 우리 눈에는 동시에 실행되는 것처럼 보인다.
- 이러한 멀티프로세스 운영체제에서 프로세스의 CPU 할당 순서 및 방법을 결정짓는 것을 스케줄링이라 한다.
라운드 로빈 스케줄링
- RR 알고리즘은 선점(preemptive)형 알고리즘으로 사분할 시스템을 위해 설계되었다.
- FCFS스케줄링과 비슷하지만 시스템이 프로세스를 사이를 옮겨 다닐 수 있도록 선점이 추가된다.
- 시간 할당량 또는 time slice라는 시간의 단위를 정의하여 구현한다.
- 어떤 프로세스가 이 단위 시간을 지나면 실행을 강제로 다른 프로세스에게 선점시키는 것이다.
- 일반적으로 time quantum은 10에서 100밀리초 동안이다.
선점형 알고리즘과 비선점형 알고리즘의 차이점
- 선점형 알고리즘은 CPU를 강제로 빼앗을 수 있어 시스템의 응답성을 높이는 반면, 컨텍스트 스위칭에 따른 오버헤드와 복잡성이 존재한다.
- 비선점형 알고리즘은 단순하고 오버헤드가 적지만, 한 프로세스가 CPU를 독점하게 되어 응답성이 떨어질 수 있는 단점이 있습니다.
비선점형 스케줄링
- (실행 → 대기), (실행 → 종료)로의 상태전이가 있을 때 적용
선점형 스케줄링
- (실행 → 대기), (대기 → 준비), (수행 → 종료) 모든 상태변화에서 적용
비선점 스케줄링
- 이미 할당된 CPU를 다른 프로세스가 강제로 빼앗아 사용할 수 없는 스케줄링 기법
- 스케줄러 호출 빈도가 낮고 문맥 교환에 의한 오버헤드도 적음
- 일괄 처리 시스템에 적합하고, CPU 사용시간이 긴 하나의 프로세스가 CPU 사용시간이 짧은 여러 프로세스를 오랫동안 대기시킬 수 있으므로 처리율이 떨어질 수 있다.
선점 스케줄링
- 하나의 프로세스가 CPU를 할당 받아 실행하고 있을 때 우선 순위가 높은 다른 프로세스가 CPU를 강제로 빼앗아 사용할 수 있는 기법
- 모든 프로세스에세 CPU 사용시간을 동일하게 부여할 수 있으며 빠른 응답시간을 요하는 대화식 시분할 시스템에 적합하며 긴급한 프로세서를 제어할 수 있다.
컨텍스트 스위칭
- 문맥 교환이란 하나의 프로세스가 CPU를 사용중인 상태에서 다른 프로세스가 CPU를 사용하도록 하기 위해 이전의 프로세스의 상태(문맥)를 보관하고 새로운 프로세스의 상태를 적재하는 작업을 말한다.
컨텍스트 스위칭의 필요 이유
- 여러 프로세스 / 스레드를 동시에 실행시키기 위해서
컨텍스트 스위칭이 발생하는 시점
- 주어진 time slice를 다 사용했거나
- I/O 작업을 해야하거나
- 다른 리소스 (다른 프로세스나 스레드에서 사용하고 있는)를 기다려야 하거나
thread context switching

프로세스와 쓰레드
- 메모리 구조 관점에서 본 프로세스와 쓰레드
- 자식 프로세스가 생성되고 난 다음에는 부모 프로세스가 가진 핸들테이블은 상속되지만 모든 것이 독립적으로 만들어진다. 이런한 메모리 구조를 지녔기에 프로세스 간에 데이터를 메모리 구조를 지녔기에 IPC(Inter Process Communication)가 필요하다.
- 쓰레드를 생성하는 경우 메모리 구조는 다르다.
- 프로세스가 쓰레드 A와 B를 생성한 경우 쓰레드를 생성할 때마다 해당 쓰레드만을 위한 ThreadStack 영역 (실행 흐름의 추가를 위한 최소조건)만이 생성될 뿐 나머지 영역은 부모 프로세스 영역에 공유하고 있다.
- 쓰레드마다 스택을 독립적으로 할당해준다.
쓰레드
<main.c>
/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for myTask02 */
osThreadId_t myTask02Handle;
const osThreadAttr_t myTask02_attributes = {
.name = "myTask02",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityLow,
};
/* Definitions for myTask03 */
osThreadId_t myTask03Handle;
const osThreadAttr_t myTask03_attributes = {
.name = "myTask03",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityLow,
};
void StartDefaultTask(void *argument);
void StartTask02(void *argument);
void StartTask03(void *argument);
/* Create the thread(s) */
/* creation of defaultTask */
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/* creation of myTask02 */
myTask02Handle = osThreadNew(StartTask02, NULL, &myTask02_attributes);
/* creation of myTask03 */
myTask03Handle = osThreadNew(StartTask03, NULL, &myTask03_attributes);
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN 5 */
/* Infinite loop */
for(;;)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
osDelay(50);
}
/* USER CODE END 5 */
}
void StartTask02(void *argument)
{
/* USER CODE BEGIN StartTask02 */
/* Infinite loop */
for(;;)
{
button_led_toggle_test();
osDelay(1);
}
/* USER CODE END StartTask02 */
}
void StartTask03(void *argument)
{
/* USER CODE BEGIN StartTask03 */
/* Infinite loop */
for(;;)
{
osDelay(1);
}
/* USER CODE END StartTask03 */
}
'(Telechips) AI 시스템 반도체 SW 개발자 교육 > STM32CubeIDE' 카테고리의 다른 글
6일차 (0) | 2025.04.01 |
---|---|
5일차 (0) | 2025.03.31 |
4일차 (0) | 2025.03.28 |
2일차 (0) | 2025.03.26 |
1일차 (0) | 2025.03.25 |