你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32F103自定义的printf函数的实现

[复制链接]
STMCU小助手 发布时间:2022-3-18 22:21
 在单片机中使用最多的通信接口基本就是串口了,说起串口就不得不提串口中最常用的一个函数就是打印函数printf()函数,通常使用这个函数都是直接调用库函数来实现的,在单片机中如何要使用printf()函数一般都是在串口中进行重映射。如要在串口1中使用printf()函数,可以使用下面的代码进行重映射。

  1. //加入以下代码,支持printf函数,而不需要选择use MicroLIB
  2. #if 1
  3. #pragma import(__use_no_semihosting)
  4. //标准库需要的支持函数
  5. struct __FILE
  6. {
  7.     int handle;
  8. };
  9. FILE __stdout;
  10. //定义_sys_exit()以避免使用半主机模式
  11. _sys_exit(int x)
  12. {
  13.     x = x;
  14. }
  15. //重定义fputc函数
  16. int fputc(int ch, FILE *f)
  17. {
  18.     while((USART1->SR & 0X40) == 0); //循环发送,直到发送完毕
  19.     USART1->DR = (u8) ch;
  20.     return ch;
  21. }
  22. #endif
复制代码

  在串口1的c文件中,添加上面的代码后,使用printf()函数时,就可以通过串口1来打印了。那么不通过库函数的话,自己能不能实现printf()函数的功能呢?当然是可以的,下面就通过串口2来演示,如何在串口2上直接实现printf()函数的功能。

  1. void UART2_Init(u32 bound)
  2. {
  3.     GPIO_InitTypeDef GPIO_InitStructure;
  4.     USART_InitTypeDef USART_InitStructure;
  5.     NVIC_InitTypeDef NVIC_InitStructure;

  6.     // 1、串口时钟使能 GPIO时钟使能
  7.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
  8.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

  9.     // 2、串口复位
  10.     USART_DeInit(USART2);

  11.     // 3、GPIO端口设置
  12.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;                         //PA2 TX
  13.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;          //复用推挽输出
  14.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  15.     GPIO_Init(GPIOA, &GPIO_InitStructure);

  16.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;                               //PA3 RX
  17.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
  18.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  19.     GPIO_Init(GPIOA, &GPIO_InitStructure);

  20.     // 4、串口参数初始化
  21.     USART_InitStructure.USART_BaudRate = bound;
  22.     USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  23.     USART_InitStructure.USART_StopBits = USART_StopBits_1;
  24.     USART_InitStructure.USART_Parity = USART_Parity_No;
  25.     USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  26.     USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  27.     USART_Init(USART2, &USART_InitStructure);

  28.     // 5、初始化NVIC
  29.     NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
  30.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
  31.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
  32.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  33.     NVIC_Init(&NVIC_InitStructure);

  34.     // 6、开启中断
  35.     USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);

  36.     // 7、使能串口
  37.     USART_Cmd(USART2, ENABLE);

  38. }
  39. void USART2_IRQHandler(void)
  40. {
  41.     u8 res;
  42.     if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
  43.     {
  44.         res = USART_ReceiveData(USART2);
  45.         USART_SendData(USART2, res);                        //把接收到的数据发送出去
  46.     }
  47. }
复制代码

  首先初始化串口2,初始化方式和正常情况下一样,初始化完成之后开始自定义一个函数来实现printf()函数的功能。

  1. //自定义串口2 的printf 函数
  2. char UART2_TX_BUF[200];
  3. void u2_printf(char* fmt, ...)    //无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表
  4. {
  5.     u16 i, j;
  6.     va_list ap;          //va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。
  7.     va_start(ap, fmt);   //va_start函数来获取参数列表中的参数,使用完毕后调用va_end()结束
  8.     vsprintf((char*)UART2_TX_BUF, fmt, ap);        // 把生成的格式化的字符串存放在这里
  9.     va_end(ap);
  10.     i = strlen((const char*)UART2_TX_BUF);              //此次发送数据的长度
  11.     for(j = 0; j < i; j++)                                                    //循环发送数据
  12.     {
  13.         while((USART2->SR & 0X40) == 0);                    //循环发送,直到发送完毕
  14.         USART2->DR = UART2_TX_BUF[j];
  15.     }
  16. }
复制代码

  这个函数名定义为 :u2_printf(char* fmt, …) 使用省略号表示当前参数为可变参数。接下里就可以直接使用这个函数来打印数据了。在主函数中通过一段代码来测试串口1和串口2 printf函数的功能。

  1. int main(void)
  2. {
  3.     u8 t;
  4.     u8 len;
  5.     u16 times = 0;
  6.     delay_init();       //延时函数初始化
  7.     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  8.     uart_init(115200);
  9.     LED_Init();
  10.     UART2_Init(115200);
  11.     printf("USART1 AND USART2 TEST!!!");
  12.     while(1)
  13.     {
  14.         if(USART_RX_STA & 0x8000)
  15.         {
  16.             LED1 = !LED1;
  17.             len = USART_RX_STA & 0x3fff;                //获取本次接收数据长度
  18.             printf("\r\n您发送的消息为:\r\n");
  19.             for(t = 0; t < len; t++)
  20.             {
  21.                 USART_SendData(USART1, USART_RX_BUF[t]); //向串口1发送数据
  22.                 while(USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET); //等待发送完成
  23.             }
  24.             printf("\r\n");
  25.             USART_RX_STA = 0;
  26.         }
  27.         else
  28.         {
  29.             times++;
  30.             if(times % 5000 == 0)
  31.             {
  32.                 printf("串口实验\r\n");
  33.                 u2_printf("串口实验\r\n");                                                                        //调用串口2 printf 函数
  34.             }
  35.             if(times % 300 == 0)
  36.             {
  37.                 printf("请输入数据,以回车键结束\r\n");
  38.                 u2_printf("请输入数据,以回车键结束\r\n");                //调用串口2 printf 函数
  39.             }
  40.             if(times % 30 == 0)
  41.                 LED0 = !LED0;
  42.             delay_ms(10);

  43.         }

  44.     }
  45. }
复制代码

  通过串口1和串口2输出同样的提示信息,然后用串口助手分别给串口1和串口2发送数据,当串口1和串口2接收到数据后就会通过串口打印出来。串口1和串口2输出的提示信息都是用printf函数输出的。

D5QCO6U0BN45)`L8H2)O0PQ.png

  可以看出串口2的printf函数和功能和串口1的printf函数功能一样,可以正常输出字符串。也可以使用串口2的printf函数打印变量值,比如在代码中增加一句变量打印的功能。

A_X8ZPYP8FCJ}{ZV0Q3C6%6.png

  每次输出提示信息的时候,顺便打印一下变量times的值。

DKY43H1Y}ZZ`{BJH__9V8)M.png

  可以看到变量times的值也可以正常打印。


收藏 评论0 发布时间:2022-3-18 22:21

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版