单片机 STM32F103C8T6 cubeMX HAL库 从环境到开发 正点原子工程移植 freeRTOS

0 准备材料

1、需要正点原子的HAL介绍书籍可以翻阅
http://www.openedv.com/docs/boards/stm32/zdyz_stm32f103_mini.html
2、下载安装cubeMX,cubeMX是初始化代码生成器,能很快地生成硬件基础配置,基于HAL库。版本越新越好,我的是en.stm32cubemx_v6-0-0。
https://www.st.com/zh/development-tools/stm32cubemx.html
正常安装cubeMX后点击软件的这里添加包支持,我需要开发STM32F103C8T6。
在这里插入图片描述
3、需要keil ARM软件,用习惯了。

1 闪烁PC13的小灯

1.1 新建工程
在这里插入图片描述
1.2 找到芯片,开始配置。顺便一提,这里可以看到芯片的的特性、资源图、手册,真是不要太perfect。
在这里插入图片描述
1.3 熟悉配置界面
RCC时钟外部输入打开,实物板子的外部时钟有8MHZ和32768HZ,外部8MHZ可以使得时钟达到最高的72MHZ,没有外部8MHZ内部时钟最高能调到64MHZ。没有外部32768HZ,内部的RTC模块工作不正常。下图是打开了的。一些情况下,设置外部时钟输入这不是必须的。
在这里插入图片描述
一定要做的事情,设置debug方式,我用的STLINK 2线下载,SWDIO SWCLK下载,设置为这个。
在这里插入图片描述

配置PC13端口为输出。左键点击引脚设置即可。
在这里插入图片描述
时钟配置中有芯片时钟的详细配置,非常详细。我没开外部晶振输入,用的内部的,图里有颜色的就是线路,可见RTC没打开,内部40KHZ工作不工作无所谓。点击HCLK可以自己设置一个时钟,我设置为16MHZ。
在这里插入图片描述

在这里插入图片描述
工程名字写好后,创建代码。
在这里插入图片描述
直接打开。
在这里插入图片描述
加逻辑代码:

HAL_Delay(1000);
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);

在这里插入图片描述
设置debug
在这里插入图片描述
下载后即可运行。

2 正点原子工程移植 注意点以及细节

cubeMX缺点就是没printf、没delay_us,而且实际开发需要一些传感器,自己去搞就是重复造轮子,所以需要正点原子的例子支持。

过程简单粗暴,RCC中打开2个外部时钟输入,打开SYS中debug(!!注意,如果关了调试端口后不然下次只能串口下载了,很烦人),SYS中使用sysTick,使能USART1(这样cubeMX能把h啥的HAL依赖加进来,后面就不用自己加了,比较舒服,cubeMX把这些初始化,我们一样可以在keil里把初始化删了,不影响骚操作。其他的资源同理,比如需要ADC1就cube里加了再说,后面不想要就自己keil里删除)。
在这里插入图片描述

得到的keil工程,直接把原子的下面6个文件复制到.\Core\Src(main.c也在这里)中。
在这里插入图片描述
keil中添加上c文件,会得到下面这样的结构。
在这里插入图片描述
在main.c中添加包含

#include "sys.h"
#include "delay.h"
#include "usart.h"

然后修改main(),删除UART初始化函数(声明、定义、调用都删除),删除时钟初始化函数(声明、定义、调用都删除),全部用原子的。

Stm32_Clock_Init( RCC_PLL_MUL9 );                                                               /* 设置时钟,72M */
	delay_init( 72 );                                                                               /* 初始化延时函数 */
	uart_init( 115200 );                                                                            /* 初始化串口 */

在这里插入图片描述
点编译会出现下面这结果,就是说重复定义了,因为开始的时候用了cubeMX生成了UART1的东西和院子的重复了。

Build target 'DDDDD'
compiling main.c...
linking...
DDDDD\DDDDD.axf: Error: L6200E: Symbol USART1_IRQHandler multiply defined (by stm32f1xx_it.o and usart.o).
DDDDD\DDDDD.axf: Error: L6200E: Symbol HAL_UART_MspInit multiply defined (by stm32f1xx_hal_msp.o and usart.o).
Not enough information to list image symbols.
Not enough information to list the image map.
Finished: 2 information, 0 warning and 2 error messages.
"DDDDD\DDDDD.axf" - 2 Error(s), 0 Warning(s).
Target not created.
Build Time Elapsed:  00:00:01

打开.\stm32f1xx_hal_msp.c找到void HAL_UART_MspInit(UART_HandleTypeDef* huart),然后可以直接把这个函数删除掉,或者像下图这样添加__weak关键字,keil发现有2处相同定义时优先用没带__weak关键字的定义,自然就用原子的定义了。
在这里插入图片描述
打开…/Core/Src/stm32f1xx_it.c,同样修饰一下定义void USART1_IRQHandler(void)
打开…/Core/Src/stm32f1xx_it.h去修饰一下声明__weak void USART1_IRQHandler(void);这个操作无所谓,声明而已,可以不修饰。

在这里插入图片描述

__weak void USART1_IRQHandler(void);

再次编译就不会有错了。
一些细节:
细节1:原子的CORE文件和cubeMX的CMSIS效果一样的。
图1 原子的CORE文件
在这里插入图片描述
cubeMX的CMSIS
在这里插入图片描述
细节2:
stm32f1xx_it.c/stm32f1xx_it.h里面是中断服务函数的定义和声明,除了SysTick_Handler和USART1_IRQHandler,其他的基本都是单片机发生异常后进入中断就再也出不去了(死机),比如发生数值溢出,keil编译的时候是不可预知这种错误的,单片机靠这个机制让自己”死掉“。

void NMI_Handler(void);
void HardFault_Handler(void);
void MemManage_Handler(void);
void BusFault_Handler(void);
void UsageFault_Handler(void);
void SVC_Handler(void);
void DebugMon_Handler(void);
void PendSV_Handler(void);
void SysTick_Handler(void);
__weak void USART1_IRQHandler(void);

细节3:
stm32f1xx_hal_msp.c 文件,这文件是ST公司推出HAL的主要目的,弱化硬件配置难度,下面一段话引用自原子书籍。

MSP,全称为 MCU support package,关于怎么理解 MSP,我们后面在讲解程序运行流程的时
候会给大家举例详细讲解,这里大家只需要知道,函数名字中带有 MspInit 的函数,它们
的作用是进行 MCU 级别硬件初始化设置,并且它们通常会被上一层的初始化函数所调用,
这样做的目的是为了把MCU相关的硬件初始化剥夺出来,方便用户代码在不同型号的MCU
上移植
。stm32f1xx_hal_msp.c 文件定义了两个函数 HAL_MspInit 和 HAL_MspDeInit。这两个
函数分别被文件 stm32f1xx_hal.c 中的 HAL_Init 和 HAL_DeInit 所调用。HAL_MspInit 函数的
主要作用是进行 MCU 相关的硬件初始化操作。例如我们要初始化某些硬件,我们可以硬
件相关的初始化配置写在 HAL_MspDeinit 函数中。这样的话,在系统启动后调用了 HAL_Init
之 后 , 会 自 动 调 用 硬 件 初 始 化 函 数 。 实 际 上 , 我 们 在 工 程 模 板 中 直 接 删 掉
stm32f1xx_hal_msp.c 文件也不会对程序运行产生任何影响。对于这个文件存在的意义,我
们在后面讲解完程序运行流程之后,大家会有更加清晰的理解。

细节4:
什么是回调函数?
参看https://blog.csdn.net/x1131230123/article/details/106507086,回调函数就是一个通过函数指针调用的函数:编写一个函数,另一个函数可以用这个函数当参数。

原子有另外的解释:STM32 不完全手册( ( H AL 库 版) Msp 回调函数执行过程解读

在 STM32 的 HAL 驱动中HAL_PPP_MspInit()作为回调,被 HAL_PPP_Init()函数所调用。当我们需要移植程序到 STM32F1平台的时候,我们只需要修改 HAL_PPP_MspInit 函数内容而不需要修改 HAL_PPP_Init 入口参数内容。
下面这个函数或许更应该称为Callback函数。但毋庸置疑的是,HAL实现都用句柄操作,大量用__weak修饰一些回调函数,用户需要自己编写逻辑实现而不用过多在意硬件底层。

void HAL_UART_RxCpltCallback( UART_HandleTypeDef *huart )
{
	if ( huart->Instance == USART1 )                        /* 如果是串口1 */
	{
	}
}

细节5:
在目录ALIENTEK MiniSTM32 V3.0开发板资料\6,软件资料\3,EMWIN学习资料\stm32cubef1\STM32Cube_FW_F1_V1.0.0中打开Release_Notes。包里面资料一应俱全,我建议我自己反复翻阅一下,能够更快熟悉HAL库。
在这里插入图片描述
在这里插入图片描述

3 freeRTOS

用cubeMX肯定图谋freeRTOS,尽情玩起来。
有下面注意点:
1 单片机是单核的,freeRTOS是为了让其CPU充分利用,任务切换。
2 任务切换依靠freeRTOS里的任务就绪状态表执行,对就绪任务进行分优先级切换。
3 freeRTOS是可抢断的。
4 临界区代码是指关闭所有中断后执行的一段代码,执行完后记得打开全局中断,不然任务无法切换。
5 任务的状态与切换。
在这里插入图片描述
6 任务之间的通信依靠信号量、消息队列、消息邮箱等机制。
7 内存管理的一些事情。
8 内核裁剪的一些事情。
9 cubeMX把freeRTOS的方法再次封装了,在cmsis_os.c文件中。这使得程序更简单了。
https://www.keil.com/pack/doc/CMSIS/RTOS/html/usingOS.html
https://www.keil.com/pack/doc/CMSIS/RTOS/html/functionOverview.html

3.1 LED KEY 串口1 的一个实验

用cubeMX打开RCC的2个外部时钟输入
打开PC13作为LED输出
打开PB7 PB6 PB5 PB4作为按键输入
打开SYS中的serial Wire,选择TIM1作为Timebase Source(避免和freeRTOS的心跳冲突)
打开USART1并打开中断
打开中间件FREERTOS并添加2个任务进去,LED和KEY任务。
在clock configuration中选择72MHZ时钟。
生成工程。
在这里插入图片描述
添加下面代码在main.c中使得串口1支持printf

#include <stdio.h>
//重定义fputc函数 
int fputc(int ch, FILE *f)
{      
	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
    USART1->DR = (unsigned char) ch;      
	return ch;
}

编写任务函数:

/* USER CODE BEGIN Header_StartTask02 */
/**
* @brief Function implementing the LED thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask02 */
void StartTask02(void const * argument)
{
  /* USER CODE BEGIN StartTask02 */
  /* Infinite loop */
  for(;;)
  {
    HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);//取反
    osDelay(1000);//延时1秒钟
  }
  /* USER CODE END StartTask02 */
}
/* USER CODE BEGIN Header_StartTask03 */
/**
* @brief Function implementing the KEY thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask03 */
void StartTask03(void const * argument)
{
  /* USER CODE BEGIN StartTask03 */
  /* Infinite loop */
  for(;;)
  {
		if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_7)==0)
		{
			while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_7)==0);
			printf("  press key1 \r\n");
		}
    osDelay(10);
  }
  /* USER CODE END StartTask03 */
}

任务StartTask02每隔1000ms取反小灯,任务StartTask03每次PB7按键按下向串口发送数据。

猜你喜欢

转载自blog.csdn.net/x1131230123/article/details/107874503