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.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);
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库。
用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
用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
//重定义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按键按下向串口发送数据。