FreeRTOS#
FreeRTOS 是一个开源的 RTOS(实时操作系统)。
FreeRTOS 开源社区包括很多功能组件,我们一般说的 FreeRTOS 指的是 FreeRTOS-Kernel。
这里我们需要的文件如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| .
├── include # 里面的所有头文件 (.h)
├── portable
│ ├── GCC # 适用于 armclang 和 gcc 编译器
│ │ └── ARM_CM4F
│ │ ├── port.c
│ │ └── portmacro.h
│ └── MemMang # 内存管理
│ └── heap_4.c
├── croutine.c
├── event_groups.c
├── list.c
├── queue.c
├── stream_buffer.c
├── tasks.c
└── timers.c
|
动态内存分配#
我们使用的 heap_4 算法,其内部实现的策略如下:
1
2
3
4
5
6
7
8
| #if ( configAPPLICATION_ALLOCATED_HEAP == 1 )
/* The application writer has already defined the array used for the RTOS
* heap - probably so it can be placed in a special segment or address. */
extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#else
PRIVILEGED_DATA static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#endif /* configAPPLICATION_ALLOCATED_HEAP */
|
在前面的第一章中,我们已经为动态分配预留了堆大小,所以我们需要将其告诉 FreeRTOS。
修改链接脚本
armclang
1
2
3
4
5
6
| #if __HEAP_SIZE > 0
ARM_LIB_HEAP AlignExpr(+0, 8) __HEAP_SIZE
{
*(.freertos_heap)
}
#endif
|
gcc
1
2
3
4
5
6
7
8
9
10
11
12
13
| .heap (NOLOAD) :
{
. = ALIGN(8);
__end__ = .;
PROVIDE(end = .);
KEEP(*(.freertos_heap))
. = ALIGN(8);
__HeapLimit = .;
} > RAM
|
创建 FreeRTOSHeap.c 文件。
1
2
3
4
5
6
7
8
9
10
11
12
| /**
* FreeRTOSHeap.c
*
* SPDX-License-Identifier: Apache-2.0
* SPDX-FileCopyrightText: 2026 Zeepunt
*/
#include <stdint.h>
#include "FreeRTOSConfig.h"
#if ( configAPPLICATION_ALLOCATED_HEAP == 1 )
__attribute__((section(".freertos_heap"), aligned(8), used)) uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#endif
|
配置文件#
FreeRTOS 配置文件模板位于 examples/template_configuration/FreeRTOSConfig.h。
参考这个模板,我们要创建一个配置文件 FreeRTOSConfig.h。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
| /**
* FreeRTOSConfig.h (FreeRTOS Kernel V11.3.1)
*
* SPDX-License-Identifier: Apache-2.0
* SPDX-FileCopyrightText: 2026 Zeepunt
*/
#ifndef __FREERTOS_CONFIG_H__
#define __FREERTOS_CONFIG_H__
#include "region.h"
/* Ensure stdint is only used by the compiler, and not the assembler. */
#if defined(__ICCARM__) || defined(__ARMCC_VERSION) || defined(__GNUC__)
#include <stdint.h>
extern uint32_t SystemCoreClock;
#endif
#ifndef CMSIS_device_header
#define CMSIS_device_header "stm32l4xx.h"
#endif
/* If No secure feature is used the configENABLE_TRUSTZONE should be set to 0 */
#define configENABLE_TRUSTZONE 0
#define configENABLE_FPU 1
#define configENABLE_MPU 0
#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configMAX_PRIORITIES (56)
#define configCPU_CLOCK_HZ (SystemCoreClock)
#define configTICK_RATE_HZ (1000)
#define configMINIMAL_STACK_SIZE (128)
#define configMAX_TASK_NAME_LEN (16)
#define configAPPLICATION_ALLOCATED_HEAP 1
#define configTOTAL_HEAP_SIZE __HEAP_SIZE
#define configUSE_TRACE_FACILITY 1
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
#define configUSE_MUTEXES 1
#define configQUEUE_REGISTRY_SIZE 8
#define configCHECK_FOR_STACK_OVERFLOW 0
#define configUSE_RECURSIVE_MUTEXES 1
#define configUSE_MALLOC_FAILED_HOOK 0
#define configUSE_APPLICATION_TASK_TAG 0
#define configUSE_COUNTING_SEMAPHORES 1
#define configGENERATE_RUN_TIME_STATS 0
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0
#define configSUPPORT_STATIC_ALLOCATION 0
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#define configUSE_TICKLESS_IDLE 1
#if (configUSE_TICKLESS_IDLE != 0)
#define configPRE_SLEEP_PROCESSING(x)
#define configPOST_SLEEP_PROCESSING(x)
#endif
/* Co-routine definitions. */
#define configUSE_CO_ROUTINES 0
#define configMAX_CO_ROUTINE_PRIORITIES (2)
/* Software timer definitions. */
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY (2)
#define configTIMER_QUEUE_LENGTH 10
#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 2)
/* ARMv8-M secure side port related definitions. */
/* #define secureconfigMAX_SECURE_CONTEXTS 5 */
/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskCleanUpResources 1
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_xTaskDelayUntil 1
#define INCLUDE_vTaskDelay 1
#define INCLUDE_xTaskGetSchedulerState 1
/* Optional functions - most linkers will remove unused functions anyway. */
#define INCLUDE_xQueueGetMutexHolder 1
#define INCLUDE_eTaskGetState 1
#define INCLUDE_uxTaskGetStackHighWaterMark 1
#define INCLUDE_xTimerPendFunctionCall 1
#define INCLUDE_xTaskGetCurrentTaskHandle 1
/* Cortex-M specific definitions. */
#ifdef __NVIC_PRIO_BITS
/* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4 /* 15 priority levels */
#endif
/* The lowest interrupt priority that can be used in a call to a "set priority"
function. */
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0xf
/* The highest interrupt priority that can be used by any interrupt service
routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL
INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
PRIORITY THAN THIS! (higher priorities are lower numeric values. */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
/* Interrupt priorities used by the kernel port layer itself. These are generic
to all Cortex-M ports, and do not rely on any particular library functions. */
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* Normal assert() semantics without relying on the provision of an assert.h
header file. */
#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler
#endif /* __FREERTOS_CONFIG_H__ */
|
Tick#
FreeRTOS 已经接管了 SysTick_Handler 中断处理函数,stm32 的 HAL 也会用到 tick,所以需要使用 FreeRTOS 提供的钩子函数 vApplicationTickHook。
1
2
3
4
5
6
7
8
| #include "stm32l4xx.h"
#include "FreeRTOS.h"
#include "task.h"
void vApplicationTickHook(void)
{
HAL_IncTick();
}
|
C 库适配#
我们可以创建 syscall_port.c 文件,将适配内容放在这个文件。
内存管理#
对于 armclang 来说:
可以直接通过实现 malloc/free 的同名函数来覆盖 C 库提供的函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| #include "FreeRTOS.h"
void *malloc(size_t size)
{
return pvPortMalloc(size);
}
void *calloc(size_t nmemb, size_t size)
{
return pvPortCalloc(nmemb, size);
}
void free(void *ptr)
{
vPortFree(ptr);
}
|
对于 gcc 来说:
在链接的时候需要添加标志位:-Wl,--wrap=malloc -Wl,--wrap=free -Wl,--wrap=_malloc_r -Wl,--wrap=_free_r。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| #include "FreeRTOS.h"
#include <reent.h>
void *__wrap_malloc(size_t size)
{
return pvPortMalloc(size);
}
void *__wrap__malloc_r(struct _reent *reent, size_t size)
{
(void)reent;
return pvPortMalloc(size);
}
void *__wrap_calloc(size_t nmemb, size_t size)
{
return pvPortCalloc(nmemb, size);
}
void *__wrap__calloc_r(struct _reent *reent, size_t nmemb, size_t size)
{
(void)reent;
return pvPortCalloc(nmemb, size);
}
void __wrap_free(void *ptr)
{
vPortFree(ptr);
}
void __wrap__free_r(struct _reent *reent, void *ptr)
{
(void)reent;
vPortFree(ptr);
}
|
注意:
- 在 heap_4 内部已经实现了
vTaskSuspendAll/xTaskResumeAll,所以无需加锁。 - 不允许在中断处理函数中调用 malloc 分配内存。
IO 重定向#
我们要先实现串口的初始化函数和数据发送函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
| #include <stdbool.h>
#include "stm32l4xx.h"
static UART_HandleTypeDef s_uart1_handler = {0};
static bool s_uart1_init = false;
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&s_uart1_handler);
}
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
GPIO_InitTypeDef gpio_init;
if (huart->Instance == USART1) {
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_USART1_CLK_ENABLE();
gpio_init.Pin = GPIO_PIN_9 | GPIO_PIN_10;
gpio_init.Mode = GPIO_MODE_AF_PP;
gpio_init.Pull = GPIO_PULLUP;
gpio_init.Speed = GPIO_SPEED_FAST;
gpio_init.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &gpio_init);
}
}
int bsp_uart_init(void)
{
HAL_StatusTypeDef status = HAL_OK;
s_uart1_handler.Instance = USART1;
s_uart1_handler.Init.BaudRate = 115200;
s_uart1_handler.Init.WordLength = UART_WORDLENGTH_8B;
s_uart1_handler.Init.StopBits = UART_STOPBITS_1;
s_uart1_handler.Init.Parity = UART_PARITY_NONE;
s_uart1_handler.Init.HwFlowCtl = UART_HWCONTROL_NONE;
s_uart1_handler.Init.Mode = UART_MODE_TX_RX;
status = HAL_UART_Init(&s_uart1_handler);
if (status != HAL_OK) {
return -1;
}
__HAL_UART_ENABLE_IT(&s_uart1_handler, UART_IT_RXNE);
HAL_NVIC_EnableIRQ(USART1_IRQn);
HAL_NVIC_SetPriority(USART1_IRQn, 3, 3);
s_uart1_init = true;
return 0;
}
int bsp_uart_write(const char *buf, int len)
{
if (!s_uart1_init) {
return -1;
}
return HAL_UART_Transmit(&s_uart1_handler, buf, len, HAL_MAX_DELAY);
}
|
对于 armclang 来说:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| FILE __stdout;
int fputc(int ch, FILE *stream)
{
(void)stream;
uint8_t c = (uint8_t)ch;
bsp_uart_write((const uint8_t *)&c, 1);
return ch;
}
int ferror(FILE *f)
{
return 0;
}
|
对于 gcc 来说:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| #include "FreeRTOS.h"
#include "semphr.h"
static SemaphoreHandle_t s_uart_mutex = NULL;
static void priv_uart_mutex_lock(void)
{
if (xPortIsInsideInterrupt() == pdTRUE) {
return;
}
if (s_uart_mutex == NULL) {
s_uart_mutex = xSemaphoreCreateRecursiveMutex();
}
if (s_uart_mutex != NULL) {
xSemaphoreTakeRecursive(s_uart_mutex, portMAX_DELAY);
}
}
static void priv_uart_mutex_unlock(void)
{
if (xPortIsInsideInterrupt() == pdTRUE) {
return;
}
if (s_uart_mutex != NULL) {
xSemaphoreGiveRecursive(s_uart_mutex);
}
}
int _write(int file, char *ptr, int len)
{
(void)file;
priv_uart_mutex_lock();
bsp_uart_write((const uint8_t *)ptr, len);
priv_uart_mutex_unlock();
return len;
}
|
注意:
对于 armclang 编译器来说,fputc 是单个字符输出,是由 printf 调用的,所以无需再次加锁。
如果我们提供了 ``mutex*` 函数 ,那么 C 库调用 printf 时会自行加锁。
对于 gcc 编译器来说,_write 是字符串输出,为了保留数据完整性,需要加锁。
不允许在中断处理函数中调用 printf 打印信息。
这里的测试是在前面的第一章基础上进行的。
修改 main.c 文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
| /**
* main.c
*
* SPDX-License-Identifier: Apache-2.0
* SPDX-FileCopyrightText: 2026 Zeepunt
*/
#include <stdio.h>
#include <string.h>
#include "stm32l4xx.h"
#include "bsp_uart.h"
#include "FreeRTOS.h"
#include "task.h"
static TaskHandle_t s_app_task_tid = NULL;
void vApplicationTickHook(void)
{
HAL_IncTick();
}
static void priv_app_task(void *args)
{
uint32_t cnt = 0;
__HAL_RCC_GPIOE_CLK_ENABLE();
GPIO_InitTypeDef gpio_init = {0};
gpio_init.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9;
gpio_init.Mode = GPIO_MODE_OUTPUT_PP;
gpio_init.Pull = GPIO_NOPULL;
gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9, GPIO_PIN_SET);
HAL_GPIO_Init(GPIOE, &gpio_init);
printf("Heap free: %d, min_ever: %d\n", xPortGetFreeHeapSize(), xPortGetMinimumEverFreeHeapSize());
while (1) {
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_7);
vTaskDelay(pdMS_TO_TICKS(500));
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_8);
vTaskDelay(pdMS_TO_TICKS(500));
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_9);
vTaskDelay(pdMS_TO_TICKS(500));
printf("led flash: %d\n", cnt++);
}
vTaskDelete(NULL);
}
int main(int argc, char *argv[])
{
BaseType_t ret = pdPASS;
HAL_Init();
bsp_uart_init();
ret = xTaskCreate(priv_app_task,
"app",
512 * sizeof(StackType_t),
NULL,
15,
&s_app_task_tid);
vTaskStartScheduler();
/* should not be here */
while (1);
return 0;
}
|
Keil#
将 FreeRTOS 的头文件目录和源代码添加进来。


GCC#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
| #
# Makefile
#
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2026 Zeepunt
Q := @
ifeq ($(V),1)
quiet =
cmd = $(cmd_$(1))
else
quiet = quiet_
cmd = $(Q)$(if $(quiet_cmd_$(1)),echo ' $(quiet_cmd_$(1))' &&) $(cmd_$(1))
endif
BUILD_DIR := build
TARGET := main
CC_PREFIX := arm-none-eabi-
AS := $(CC_PREFIX)as
CC := $(CC_PREFIX)gcc
LD := $(CC_PREFIX)ld
NM := $(CC_PREFIX)nm
AR := $(CC_PREFIX)ar
OBJCOPY := $(CC_PREFIX)objcopy
OBJDUMP := $(CC_PREFIX)objdump
SIZE := $(CC_PREFIX)size
STRIP := $(CC_PREFIX)strip
LD_SCRIPT_IN := link.ld.in
LD_SCRIPT := $(BUILD_DIR)/link.ld
ELF := $(BUILD_DIR)/$(TARGET).elf
BIN := $(BUILD_DIR)/$(TARGET).bin
MAP := $(BUILD_DIR)/$(TARGET).map
CPU := \
-mcpu=cortex-m4 \
-mthumb \
-mfpu=fpv4-sp-d16 \
-mfloat-abi=hard
CFLAGS := \
$(CPU) \
-std=c11
CFLAGS += \
-fdata-sections \
-ffunction-sections
CFLAGS += \
-DUSE_HAL_DRIVER \
-DSTM32L475xx
LDFLAGS := \
$(CPU) \
-T$(LD_SCRIPT) \
--specs=nano.specs \
--specs=nosys.specs \
-Wl,-Map=$(MAP) \
-Wl,--wrap=malloc \
-Wl,--wrap=calloc \
-Wl,--wrap=free \
-Wl,--wrap=_malloc_r \
-Wl,--wrap=_calloc_r \
-Wl,--wrap=_free_r \
-Wl,--gc-sections \
-Wl,--print-memory-usage
LDLIBS :=
INCLUDES := \
-Icmsis \
-Icmsis/m-profile \
-Icmsis-device-l4/include \
-Istm32l4xx-hal-driver/include \
-Istm32l4xx-hal-driver/include/Legacy \
-Ifreertos \
-Ifreertos/include \
-Ifreertos/portable/GCC/ARM_CM4F \
-Ibsp/include \
-I.
SOURCES := \
cmsis-device-l4/source/system_stm32l4xx.c \
stm32l4xx-hal-driver/source/stm32l4xx_hal.c \
stm32l4xx-hal-driver/source/stm32l4xx_hal_cortex.c \
stm32l4xx-hal-driver/source/stm32l4xx_hal_dma.c \
stm32l4xx-hal-driver/source/stm32l4xx_hal_dma_ex.c \
stm32l4xx-hal-driver/source/stm32l4xx_hal_flash.c \
stm32l4xx-hal-driver/source/stm32l4xx_hal_flash_ex.c \
stm32l4xx-hal-driver/source/stm32l4xx_hal_flash_ramfunc.c \
stm32l4xx-hal-driver/source/stm32l4xx_hal_gpio.c \
stm32l4xx-hal-driver/source/stm32l4xx_hal_pwr.c \
stm32l4xx-hal-driver/source/stm32l4xx_hal_pwr_ex.c \
stm32l4xx-hal-driver/source/stm32l4xx_hal_rcc.c \
stm32l4xx-hal-driver/source/stm32l4xx_hal_rcc_ex.c \
stm32l4xx-hal-driver/source/stm32l4xx_hal_uart.c \
stm32l4xx-hal-driver/source/stm32l4xx_hal_uart_ex.c \
stm32l4xx-hal-driver/source/stm32l4xx_hal_usart.c \
stm32l4xx-hal-driver/source/stm32l4xx_hal_usart_ex.c \
freertos/FreeRTOSHeap.c \
freertos/source/croutine.c \
freertos/source/event_groups.c \
freertos/source/list.c \
freertos/source/queue.c \
freertos/source/stream_buffer.c \
freertos/source/tasks.c \
freertos/source/timers.c \
freertos/portable/GCC/ARM_CM4F/port.c \
freertos/portable/GCC/MemMang/heap_4.c \
bsp/source/bsp_uart.c \
startup.c \
retarget_syscall.c \
main.c
OBJS := $(patsubst %.c,$(BUILD_DIR)/%.o,$(SOURCES))
quiet_cmd_cc = CC $<
cmd_cc = $(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<
quiet_cmd_ld = LD $(TARGET)
cmd_ld = $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)
quiet_cmd_bin = OBJCOPY $(BIN)
cmd_bin = $(OBJCOPY) -O binary $< $@
quiet_cmd_size = SIZE $(TARGET)
cmd_size = $(SIZE) $@
PHONY :=
PHONY += all
all: $(ELF) $(BIN)
$(LD_SCRIPT): $(LD_SCRIPT_IN) | $(BUILD_DIR)
$(Q)$(CC) -E -P -x c -I. $(LD_SCRIPT_IN) -o $@
$(ELF): $(OBJS) $(LD_SCRIPT)
$(call cmd,ld)
$(BIN): $(ELF)
$(call cmd,bin)
$(BUILD_DIR)/%.o: %.c | $(BUILD_DIR)
$(Q)mkdir -p $(dir $@)
$(call cmd,cc)
$(BUILD_DIR):
$(Q)mkdir -p $(BUILD_DIR)
PHONY += clean
clean:
$(Q)rm -rf $(BUILD_DIR)
PHONY += run
run: $(BINARY)
$(Q)$(BINARY)
PHONY += size
size: $(ELF)
$(SIZE) $(ELF)
.PHONY: $(PHONY)
|
参考工程