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

【经验分享】STM32开发,移植修改官方HAL例程,实现串口Printf打印功能

[复制链接]
STMCU小助手 发布时间:2022-5-6 09:39
1 概述
1.1 资源概述

开发板:正点原子STM32F103 Nano开发板
CUBEMX版本:1.3.0
MDK版本:5.23
主控芯片型号:STM32F103RBT6



1.2 实现功能
移植官方例程文件,适当修改,在开发板上实现串口功能,并在电脑上位机上实现输出字符串。

1.3 移植原因
正点原子提供的HAL例程里边自带usart/sys/delay三个由正点原子开发的库函数,但是这几个库函数并非HAL函数,而是用标准函数或者直接操作寄存器实现。因此想完全通过HAL函数实现串口功能,充分了解串口的实现过程。官方例程写的非常好,逻辑结构严谨,有各种错误处理机制,特别适合移植和学习。

2 软件适配工作
2.1 STM芯片的命名规则

首先我们了解下STM32的命名规则,如下图,最后两位是封装和温度信息,如果前面的字母数字相同,那么芯片的架构资源就是完全一样,软件就能通用。正点原子的开发板是STM32F103RBT6,我们只要找到STM32F103RB的软件就能适配。

SY_}H(P1B9Y9_S~01@]H$DH.png

2.2 官方例程下载
ST官方提供非常详细的官方例程,我们登陆ST官网搜索到F103的例程资源,此安装包同时为CUBEMX的资源包。

B@X5980~K@J84`3EFU`}`{U.png

2.3 官方开发板简述
打开文件夹,一级一级进入菜单,可以看到project文件夹,这个文件夹即为官方例程库,我们进入到UART文件夹,在进入选择STM32F103RB-Nucleo。这个Nucleo是ST官方提供的开发板。如下图,官网可以下载到全套的原理图,PCB,BOM等文件。此开发板集成STLINK V2,但是外设资源只有一个LED灯和一个按键,连高速晶振都没有,不能使用HSE时钟。有预留位置,可以自行购买焊接。此开发板淘宝价格大概80元。单板芯片和正点原子的开发板芯片相同,均为STM32F103RB,可以借用官方例程进行修改移植到正点原子开发板上。
CAUM6W`B%U@A2L[[S]Q_RYT.png

我们使用MDK软件进行开发,打开下述文件夹,打开工程文件。en.stm32cubef1(1.8.0)\STM32Cube_FW_F1_V1.8.0\Projects\STM32F103RB-Nucleo\Examples\UART\UART_Printf\MDK-ARM,
打开工程文件后的界面如下:

7T6J`8W_B892QBDJSWA`X{N.png

例程使用的是内部的高速时钟HSI,配置为64MHz。我们也可以修改软件相关时钟配置,从而支持外部高速晶振,同时也可以将频率设置为最高的72MHz。

ZN0%HSIT18[KF5{}`KO~_1F.png


3. 软件修改工作
3.1 文件夹去掉只读属性

在修改前,我们需要将文件夹属性只读去掉,否则打开工程后,很多文件上方会出现一把锁

D5D(7Q@`24X20J1D841Z(G2.png

被锁住的文件将不能进行编辑,代码无法进行修改。

20200330211110417.png

3.2 软件修改
NUCLEO开发板LD2灯对应的GPIO是PB13,正点原子的LED0是PC0,因此我们需要将软件设置里的PB13改为PC0。下图是官方开发板LED部分原理图。当然不改没关系,这里的灯只是用来指示串口收发成功状态用的,如果有错误,LED灯将会亮起。



修改GPIO口的定义

  1. #define LED2_PIN                         GPIO_PIN_0 //这里需要改为端口0,正点原子是PC0
  2. #define LED2_GPIO_PORT                   GPIOC//这里需要改为GPIOC,正点原子是PC0
复制代码

官方例程LED是输出高有效点亮,正点原子开发板是输出低有效点亮,因此我们需要将例程中所有的LED灯的RESET改为SET,这里不一一例出。

  1. /* Reset PIN to switch off the LED */
  2. HAL_GPIO_WritePin(LED_PORT[Led],LED_PIN[Led], GPIO_PIN_SET);//这里需要将设为SET,正点原子开发板是输出低有效
复制代码

同理SET改为RESET,这里不一一例出。

  1. void BSP_LED_On(Led_TypeDef Led)
  2. {
  3.   HAL_GPIO_WritePin(LED_PORT[Led], LED_PIN[Led], GPIO_PIN_RESET); //这里需要将设为RESET,正点原子开发板是输出低有效
  4. }
复制代码

在main主函数中,需要将奇校验改为无校验,否则在PC机上的串口将会出现乱码,即使上位机设置了奇校验。这时候需要将数据位多勾选即可解决,即8位数据位+奇偶校验,上位机数据要选择9位。

  1. UartHandle.Init.Parity     = UART_PARITY_NONE; //需要设为无校验,否则在串口调试助手上显示乱码(即使助手上设置了奇偶校验)
复制代码

3.3程序流程图分析
main.c文件程序流程图如下,每一个函数均有错误处理机制,以及错误后的提醒(将LED2灯点亮)

EWM5X(6C6Y90S@IAH30U7DM.png

4 实验结果
按一次开发板的复位按键,可以在上位机上出现对应的信息,main函数只执行一次。我们也可以将printf函数写在while(1)中,实现多次打印。

I]AII)IHU_N1WJRST{MY7T3.png

这个XCOM串口调试助手在WIN10下容易出现崩溃停止响应,这里推荐WIN10官方商店的一个串口调试助手,稳定不崩溃好用。如下图:

3NJ5DAZ`HSV8FEZNWVQW2KJ.png

5 三种串口发送方式说明

5.1轮询方式printf函数重定义

以串口1为例代码如下,此时使用keil需要勾选microlib

  1. int fputc(int ch, FILE *f)  //轮询方式,超时机制,输出到串口函数重定义
  2. {         
  3.   HAL_UART_Transmit(&huart1, (uint8_t *)&ch, sizeof(ch), 0xFFFF);
  4.   return ch;
  5. }
  6. /*HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)*/
  7. int fgetc(FILE *f)   //轮询方式,超时机制,接收到串口函数重定义
  8. {
  9.         uint8_t ch;        
  10.         HAL_UART_Receive(&huart1, (uint8_t *)&ch, sizeof(ch), 0xFFFF);        
  11.         return ch;
  12. }
复制代码

5.2中断方式方式串口发送数据

以串口1为例代码如下
1,定义要发送的数据。
  1. uint8_t string_it[]="Int:hello world!";
复制代码

2,调用串口发送函数。

  1. HAL_UART_Transmit_IT(&huart2, (uint8_t *)string_it, sizeof(string_it));
复制代码

5.3 DMA方式串口发送数据

以串口1为例代码如下
1,定义要发送的数据。

  1. uint8_t string_dma[]="DMA:hello world!";
复制代码

2,调用串口发送函数。
  1. HAL_UART_Transmit_DMA(&huart2, (uint8_t *)string_dma, sizeof(string_dma));
复制代码

三者可以混用,如下图所示
  1.         HAL_UART_Transmit_DMA(&huart2, (uint8_t *)string_dma, sizeof(string_dma));

  2.         HAL_Delay(1);

  3.         printf("\r\nPoling:hello world!\r\n");

  4.         HAL_UART_Transmit_IT(&huart2, (uint8_t *)string_it, sizeof(string_it));
复制代码

则输出的结果是

49{%~F~KMUW{]61PYY7TRF0.png

如果将Hal_delay(1)函数注释掉,将只会输出DMA信息,如下图

7_%TAS0IP%`G%SHAL8{WXEJ.png


6 NUCLEO_F103RB程序移植

主体程序使用CUBEIDE直接生成,另外将生成的多余注释全部删除,程序进行分层,将底层代码抽象出来,main函数中只保留应用函数。

6.1 程序说明
软件简介
使用STM32CubeIDE自动生成代码。
软件是使用了HAL库,使用了内部集成的CubeMX。
这个软件是用来验证单板是否完整的Demo程序,可以验证LED灯,按键以及串口的收发功能。
串口的数据格式是115200,N,8,1

软件内容
按键控制LED灯。
另外可以通过串口发送数据和接收数据
软件的BSP文件和main文件已经分开,将多余的注释全部删除,看起来更加简洁。

使用说明
首先可以按10次,每次按一下,LED会发生翻转。到达后要求输入一个整数。
输入一个数据后,后续按键允许的次数即为输入的数字;
串口输入输出函数已经重新定向到printf和scanf,这个的重定向和keil中不同,另外,增加了一条清除接收区缓存区的语句,不然scanf不能正常收。

软件备注
此软件已经在Ubuntu上运行编译通过;
下载到单板上已经验证通过。

6.2 源码
main函数如下


  1. #include "BSP.h"

  2. int main(void)
  3. {
  4.         int key=0;
  5.         int ledTimes = 0;
  6.         int inNumber =10;
  7.         HAL_Init();
  8.         SystemClock_Config();
  9.         MX_GPIO_Init();
  10.         MX_USART2_UART_Init();

  11.         setvbuf(stdin, NULL, _IONBF, 0);//清空接收区域缓冲区,否则要到1KB才会刷新
  12. //        setvbuf(stdout, NULL, _IONBF, 0);


  13.         puts("press key to toggle led!\n");
  14.         while (1)
  15.         {
  16.                 if(HAL_GPIO_ReadPin(B1_GPIO_Port,B1_Pin)==0)
  17.                 {
  18.                         while(HAL_GPIO_ReadPin(B1_GPIO_Port,B1_Pin)==0);//等待按键释放
  19.                         key=1;
  20.                 }
  21.                 else
  22.                         key=0;
  23.                 if(key==1)
  24.                 {
  25.                         ledTimes++;
  26.                         printf("LED is toggling! %d times\n",ledTimes);
  27.                         HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);//翻转LED灯
  28.                 }
  29.                 if(ledTimes == inNumber)
  30.                 {
  31.                         puts("intput the end number of LEDs\n");
  32.                         scanf("%d",&inNumber) ;
  33.                         printf("the number you input is %d,press key to toggle led!\n",inNumber);
  34.                         ledTimes = 0;
  35.                 }
  36.                         key = 0;
  37.         }
  38. }
复制代码

BSP函数如下

  1. #include "BSP.h"

  2. UART_HandleTypeDef huart2;

  3. void SystemClock_Config(void)
  4. {
  5.         RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  6.         RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  7.         RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  8.         RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  9.         RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  10.         RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  11.         RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2;
  12.         RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL16;
  13.         if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  14.         {
  15.                 Error_Handler();
  16.         }
  17.         /** Initializes the CPU, AHB and APB buses clocks
  18.          */
  19.         RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
  20.                         |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  21.         RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  22.         RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  23.         RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  24.         RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  25.         if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  26.         {
  27.                 Error_Handler();
  28.         }
  29. }


  30. void MX_USART2_UART_Init(void)
  31. {

  32.         huart2.Instance = USART2;
  33.         huart2.Init.BaudRate = 115200;
  34.         huart2.Init.WordLength = UART_WORDLENGTH_8B;
  35.         huart2.Init.StopBits = UART_STOPBITS_1;
  36.         huart2.Init.Parity = UART_PARITY_NONE;
  37.         huart2.Init.Mode = UART_MODE_TX_RX;
  38.         huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  39.         huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  40.         if (HAL_UART_Init(&huart2) != HAL_OK)
  41.         {
  42.                 Error_Handler();
  43.         }
  44. }

  45. void MX_GPIO_Init(void)
  46. {
  47.         GPIO_InitTypeDef GPIO_InitStruct = {0};

  48.         /* GPIO Ports Clock Enable */
  49.         __HAL_RCC_GPIOC_CLK_ENABLE();
  50.         __HAL_RCC_GPIOD_CLK_ENABLE();
  51.         __HAL_RCC_GPIOA_CLK_ENABLE();
  52.         __HAL_RCC_GPIOB_CLK_ENABLE();

  53.         /*Configure GPIO pin Output Level */
  54.         HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);

  55.         /*Configure GPIO pin : B1_Pin */
  56.         GPIO_InitStruct.Pin = B1_Pin;
  57.         GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
  58.         GPIO_InitStruct.Pull = GPIO_NOPULL;
  59.         HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);

  60.         /*Configure GPIO pin : LD2_Pin */
  61.         GPIO_InitStruct.Pin = LD2_Pin;
  62.         GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  63.         GPIO_InitStruct.Pull = GPIO_NOPULL;
  64.         GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  65.         HAL_GPIO_Init(LD2_GPIO_Port, &GPIO_InitStruct);

  66.         /* EXTI interrupt init*/
  67.         HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);
  68.         HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
  69. }

  70. void Error_Handler(void)
  71. {
  72.         __disable_irq();
  73.         while (1)
  74.         {
  75.                         HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);//翻转LED灯
  76.                         HAL_Delay(250);
  77.         }
  78. }

  79. int __io_putchar(int ch)//printf函数重新定义
  80. {
  81.   HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xFFFF);
  82.   return ch;
  83. }

  84. int __io_getchar()//scanf函数重定向
  85. {
  86.   uint8_t  ch;
  87.   HAL_UART_Receive(&huart2,(uint8_t *)&ch, 1, 0xFFFF);
  88.   return  ch;
  89. }
复制代码

BSP头文件如下


  1. #ifndef INC_BSP_H_
  2. #define INC_BSP_H_

  3. #include "stm32f1xx_hal.h"
  4. #include "stdio.h"

  5. #define B1_Pin GPIO_PIN_13
  6. #define B1_GPIO_Port GPIOC
  7. #define B1_EXTI_IRQn EXTI15_10_IRQn
  8. #define USART_TX_Pin GPIO_PIN_2
  9. #define USART_TX_GPIO_Port GPIOA
  10. #define USART_RX_Pin GPIO_PIN_3
  11. #define USART_RX_GPIO_Port GPIOA
  12. #define LD2_Pin GPIO_PIN_5
  13. #define LD2_GPIO_Port GPIOA

  14. #define TMS_Pin GPIO_PIN_13
  15. #define TMS_GPIO_Port GPIOA
  16. #define TCK_Pin GPIO_PIN_14
  17. #define TCK_GPIO_Port GPIOA
  18. #define SWO_Pin GPIO_PIN_3
  19. #define SWO_GPIO_Port GPIOB

  20. void SystemClock_Config(void);
  21. void MX_GPIO_Init(void);
  22. void MX_USART2_UART_Init(void);
  23. void Error_Handler(void);

  24. #endif /* INC_BSP_H_ */
复制代码

6.3 串口使用效果

`U6LDN3%X9((%9%1H9D{UKY.png

6.4码云上的地址
6.4.1 STM32F103程序源码

程序源码已经进开源,方便自己后续验证自己的单板上的串口,按键以及LED灯是否完好。

6.4.2 STM32F407程序源码

底层驱动使用cubeIDE自动生成,应用代码基本移植于F103,由于底层驱动和应用层进行了很好的分割,所以移植非常方便。



收藏 评论0 发布时间:2022-5-6 09:39

举报

0个回答

所属标签

相似分享

官网相关资源

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