C语言宏定义使用记录

我在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);
}

参考:

https://www.cnblogs.com/9sheng/archive/2011/03/22/2684247.html

https://blog.csdn.net/huyansoft/article/details/2484297

https://www.mikeash.com/pyblog/friday-qa-2010-12-31-c-macro-tips-and-tricks.html

《完》

如果转载,请注明出处。https://www.cnblogs.com/ssdq/
原文地址:https://www.cnblogs.com/ssdq/p/13071975.html