我在STM上,想寫一個利用串口打印LOG的函數trace,根據我手上兩塊板子的硬件設計,他們的串口不一致,但是我想把這個trace函數寫成一個統一的源文件,分別放到兩個不同的項目裏面編譯,需要做的僅僅是修改下頭文件中定義的串口號而已。
於是我就想到了用宏定義來展開相關的代碼。
我定義了一個串口號的宏
// 日誌打印的串口號,可以取值1/2/3 #define TRACE_UART_PORT 1
接着我構思了需要動態修改的幾個地方分別有:
// 串口的API函數操作需要串口號 USART* USART_Init(USARTxN, &usartInitData); USART_ClearFlag(USARTxN, USART_IT_RXNE | USART_IT_ORE); USART_ITConfig(USARTxN, USART_IT_RXNE, ENABLE); USART_Cmd(USARTxN, ENABLE); // 中斷配置的操作 USART*_IRQn nvicInitData.NVIC_IRQChannel = USARTIRQxN; nvicInitData.NVIC_IRQChannelCmd = ENABLE; nvicInitData.NVIC_IRQChannelPreemptionPriority = 2; nvicInitData.NVIC_IRQChannelSubPriority = 0; NVIC_Init(&nvicInitData); // 中斷服務函數 void USART*_IRQHandler(void);
這裏的 USARTxN 可能的值有 USART1,USART2,USART3三者之一,USART*_IRQHandler可能是 USART1_IRQHandler,USART2_IRQHandler,USART3_IRQHandler三者之一,USARTIRQxN也可以是USART1_IRQn,USART2_IRQn,USART3_IRQn三者之一。他們的使用代碼在很多地方都是一樣的,只是具體的串口名稱不一樣,我不想用
#if defined(TRACE_UART_PORT) && (TRACE_UART_PORT == 1) // 串口一的各類初始化操作 #elif defined(TRACE_UART_PORT) && (TRACE_UART_PORT == 2) // 串口二的各類初始化操作 #elif defined(TRACE_UART_PORT) && (TRACE_UART_PORT == 3) // 串口三的各類初始化操作 #else #error "編譯錯誤提示" #endif
這樣的方式去 “複製-黏貼-修改” ,於是便想到了C語言的預處理操作。在C語言裏面只有 # 和 ## 兩個可以這麼做,其中 # 是 字符串化,##是字符拼接。
實現的代碼如下:
#if defined(TRACE_UART_PORT) #define USARTxN2(n) USART##n #define USARTxN1(n) USARTxN2(n) #define USARTxN USARTxN1(TRACE_UART_PORT) #define USARTxN_IRQHandler2(n) USART##n##_IRQHandler #define USARTxN_IRQHandler1(n) USARTxN_IRQHandler2(n) #define USARTxN_IRQHandler USARTxN_IRQHandler1(TRACE_UART_PORT) #define USARTIRQxN2(n) USART##n##_IRQn #define USARTIRQxN1(n) USARTIRQxN2(n) #define USARTIRQxN USARTIRQxN1(TRACE_UART_PORT) #endif
爲什麼要使用三重的宏定義呢?我只知道##是拼接的意思,於是我不斷的修改嘗試,最後試出來了,可惜我那是還是沒搞明白到底是爲什麼。
後來就特地去百度找資料看,直到我看了 https://www.cnblogs.com/9sheng/archive/2011/03/22/2684247.html 中的介紹之後才頓悟。這篇文章中的這一段話十分重要:
“規則可簡單總結如下:在展開當前宏函數時,如果形參有#(字符串化操作)或##(記號連接操作)則不進行宏參數的展開,否則先展開宏參數,再展開當前宏(就像先計算函數中的參數,然後調用函數一樣)。”
關鍵就是這裏了,搞明白這裏才知道爲什麼要多重展開才能達到目的。
初始化代碼如下:
extern void trace_initialize(void) { GPIO_InitTypeDef gpioInitData; USART_InitTypeDef usartInitData; NVIC_InitTypeDef nvicInitData; usartInitData.USART_BaudRate = TRACE_BAUDRATE; usartInitData.USART_WordLength = USART_WordLength_8b; usartInitData.USART_StopBits = USART_StopBits_1; usartInitData.USART_Parity = USART_Parity_No; usartInitData.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; usartInitData.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 如下GPIO初始化是針對STM32F103的,尚未在實物上驗證,編譯已經通過 #if defined(TRACE_UART_PORT) #if (TRACE_UART_PORT == 1) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO | RCC_APB2Periph_USART1, ENABLE); // Alternate: TX=PA9/RX=PA10, default alternate functions. // remap to TX=PB6/RX=PB7 gpioInitData.GPIO_Mode = GPIO_Mode_AF_PP; gpioInitData.GPIO_Speed = GPIO_Speed_50MHz; gpioInitData.GPIO_Pin = GPIO_Pin_9; GPIO_Init(GPIOA, &gpioInitData); gpioInitData.GPIO_Mode = GPIO_Mode_IN_FLOATING; gpioInitData.GPIO_Speed = GPIO_Speed_50MHz; gpioInitData.GPIO_Pin = GPIO_Pin_10; GPIO_Init(GPIOA, &gpioInitData); #elif (TRACE_UART_PORT == 2) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB2Periph_USART2, ENABLE); // Alternate: TX=PA2/RX=PA3. // remap to TX=PD5/RX=PD6 gpioInitData.GPIO_Mode = GPIO_Mode_AF_PP; gpioInitData.GPIO_Speed = GPIO_Speed_50MHz; gpioInitData.GPIO_Pin = GPIO_Pin_2; GPIO_Init(GPIOA, &gpioInitData); gpioInitData.GPIO_Mode = GPIO_Mode_IN_FLOATING; gpioInitData.GPIO_Speed = GPIO_Speed_50MHz; gpioInitData.GPIO_Pin = GPIO_Pin_3; GPIO_Init(GPIOA, &gpioInitData); #elif (TRACE_UART_PORT == 3) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB2Periph_USART3, ENABLE); // Alternate: TX=PB10/RX=PB11. // remap to TX=PD8/RX=PD9 // remap to TX=PC10/RX=PC11 gpioInitData.GPIO_Mode = GPIO_Mode_AF_PP; gpioInitData.GPIO_Speed = GPIO_Speed_50MHz; gpioInitData.GPIO_Pin = GPIO_Pin_10; GPIO_Init(GPIOB, &gpioInitData); gpioInitData.GPIO_Mode = GPIO_Mode_IN_FLOATING; gpioInitData.GPIO_Speed = GPIO_Speed_50MHz; gpioInitData.GPIO_Pin = GPIO_Pin_11; GPIO_Init(GPIOB, &gpioInitData); #else #error "Not supported TRACE_UART_PORT value." #endif #error "You are not define TRACE_UART_PORT." #endif nvicInitData.NVIC_IRQChannel = USARTIRQxN; nvicInitData.NVIC_IRQChannelCmd = ENABLE; //nvicInitData.NVIC_IRQChannelPreemptionPriority = 2; //nvicInitData.NVIC_IRQChannelSubPriority = 0; nvic_load_priority(&nvicInitData); // 專門的函數用來加載各類中斷的優先級 NVIC_Init(&nvicInitData); USART_Init(USARTxN, &usartInitData); USART_ClearFlag(USARTxN, USART_IT_RXNE | USART_IT_ORE); USART_ITConfig(USARTxN, USART_IT_RXNE, ENABLE); //開啓串口接受中斷 USART_Cmd(USARTxN, ENABLE); }