最近开始学ST的cubeMX工具开发stm32,然后之前又是跟着原子哥的教程学习的,所以在cubeMX生成的工程后,自然而然地想使用原子哥的system库,毕竟原子的很多例程和硬件驱动都有用到这个库里面的功能,尤其是delay的功能。那如何比较好地移植使用到cubeMX生成的工程中呢?我捣鼓了一天大概整明白了,也对这个库有了更好的理解,之前都是直接用,确实不太好。这里分享一下,希望大家可以提出一些错误的地方。
这里先写裸机下的移植
笔者使用的是STM32F103C8T6,目前只做了这个的移植
后期写跑FreeRTOS的再贴链接
打开原子哥的例程文件内部都有SYSTEM这个文件,里面包含了以下3个文件。
3个文件的具体功能这里就不赘述了,可以看视频或者原子的资料。
笔者使用的是STM32F103C8T6
使能一个GPIO口驱动LED,使能串口1,打开串口中断。
时钟设置
复制一份原子F1系列下的HAL库版本例程中的system文件,粘贴到新建工程文件夹中
打开工程后添加system文件到工程目录中,同时记得加入.h文件的路径
然后在main.c中加入三个 .h文件
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "delay.h"
#include "sys.h"
#include "usart.h"
/* USER CODE END Includes */
这里会发现usart.h和生成文件的usart.h重复了,这个在下面解决。
这里需要清楚的是,原子这些文件里面的函数实现的功能一些已经在cubeMX里面就设置好了,并且生成了代码,所以移植这个库后就会出现功能重复定义或者配置重复的问题,这也是我们需要解决的问题。那为什么还要移植呢,直接用cubeMX配置不就好了,因为这些库内部有一些封装好的功能可以使用,我们就是要使用这些功能,又不能出现上述的问题。
首先是sys.c文件
这个文件需要注意的就是里面的时钟配置函数
代码如下
void Stm32_Clock_Init(u32 PLL)
{
HAL_StatusTypeDef ret = HAL_OK;
RCC_OscInitTypeDef RCC_OscInitStructure;
RCC_ClkInitTypeDef RCC_ClkInitStructure;
RCC_OscInitStructure.OscillatorType=RCC_OSCILLATORTYPE_HSE; //时钟源为HSE
RCC_OscInitStructure.HSEState=RCC_HSE_ON; //打开HSE
RCC_OscInitStructure.HSEPredivValue=RCC_HSE_PREDIV_DIV1; //HSE预分频
RCC_OscInitStructure.PLL.PLLState=RCC_PLL_ON; //打开PLL
RCC_OscInitStructure.PLL.PLLSource=RCC_PLLSOURCE_HSE; //PLL时钟源选择HSE
RCC_OscInitStructure.PLL.PLLMUL=PLL; //主PLL倍频因子
ret=HAL_RCC_OscConfig(&RCC_OscInitStructure);//初始化
if(ret!=HAL_OK) while(1);
//选中PLL作为系统时钟源并且配置HCLK,PCLK1和PCLK2
RCC_ClkInitStructure.ClockType=(RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2);
RCC_ClkInitStructure.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK; //设置系统时钟时钟源为PLL
RCC_ClkInitStructure.AHBCLKDivider=RCC_SYSCLK_DIV1; //AHB分频系数为1
RCC_ClkInitStructure.APB1CLKDivider=RCC_HCLK_DIV2; //APB1分频系数为2
RCC_ClkInitStructure.APB2CLKDivider=RCC_HCLK_DIV1; //APB2分频系数为1
ret=HAL_RCC_ClockConfig(&RCC_ClkInitStructure,FLASH_LATENCY_2); //同时设置FLASH延时周期为2WS,也就是3个CPU周期。
if(ret!=HAL_OK) while(1);
}
其实在使用cubeMX的时候已经自动生成的相关的代码,代码的内容就是我们在配置时钟树的时候的一些参数选择。自动生成的代码在main.c文件中。
代码如下
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
因为笔者在使用cubeMX新建工程时,时钟配置就和sys.c里的函数配置基本一致,所以可以看到这两端代码其实基本一致。所以我们在使用时,只需要调用其中一个即可。这里不需要做大改动,只是选择的问题。
然后就是delay.c文件
这个文件里面的延时函数是比较好用的,因为可以精确延时us,这也是HAL_Delay()的一个补充。
原文件不需要改动什么,就是需要注意延时需要初始化。
//初始化延迟函数
//当使用ucos的时候,此函数会初始化ucos的时钟节拍
//SYSTICK的时钟固定为AHB时钟
//SYSCLK:系统时钟频率
void delay_init(u8 SYSCLK)
参数为系统时钟频率,这个就和前面的sys.c的时钟配置选择有关系了,如以上新建的工程,这里的系统时钟频率为72M。
初始化代码如下
/* USER CODE BEGIN SysInit */
delay_init(72);
/* USER CODE END SysInit */
最后就是比较麻烦的usart.c文件
system里面的usart主要是做了usart的初始化和接收发的功能,但是我们在cubeMX已经初始化了串口,会自动生成一个usart.c和.h文件,这个时候就出现了重复文件名的情况。
显然做法有两种,要不不使用原子哥写的函数,要不就不在cubeMX里初始化串口。
这里尝试使用cubeMX里初始化串口,也使用原子哥写的接收发的功能。
先解决文件名的问题,这里把原子哥的文件改为yuanzi_usart.c和yuanzi_usart.h
修改后在yuanzi_usart.c文件上方也改为yuanzi_usart.h
#include "yuanzi_usart.h"
删除串口初始化的相关函数
void uart_init(u32 bound)
...
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
...
这个时候编译会出现
yuanzi\yuanzi.axf: Error: L6200E: Symbol USART1_IRQHandler multiply defined (by yuanzi_usart.o and stm32f1xx_it.o).
原因是串口的中断服务函数重复定义了
打开stm32f1xx_it.c文件,找到USART1_IRQHandler函数,在函数前添加__weak
__weak void USART1_IRQHandler(void)
这样子就可以使用yuanzi_usart.c的中断服务函数
再编译一次
0 Error(s), 0 Warning(s).
舒服。
这里还需要注意!!!
这里还需要注意!!!
这里还需要注意!!!
1.cubeMX自动生成的UART句柄和原子哥定义的不同,需要全部改为huart1
UART_HandleTypeDef huart1; //UART句柄
可以yuanzi_usart文件下的句柄定义删除,然后在include “usart.h”
2.需要在串口初始化之后开启接收中断
HAL_UART_Receive_IT(&huart1 , (u8 *)aRxBuffer, RXBUFFERSIZE);//该函数会开启接收中断
功能:使用system库,移植实现使用原子哥的串口例程
main函数代码如下
(这里为了代码整洁容易看,把cubeMX自动生成的注释代码删了)
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "delay.h"
#include "sys.h"
#include "yuanzi_usart.h"
void SystemClock_Config(void);
int main(void)
{
u8 len;
u16 times=0;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
HAL_UART_Receive_IT(&huart1 , (u8 *)aRxBuffer, RXBUFFERSIZE);//该函数会开启接收中断
delay_init(72);
while (1)
{
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\n您发送的消息为:\r\n");
HAL_UART_Transmit(&huart1,(uint8_t*)USART_RX_BUF,len,1000); //发送接收到的数据
while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC)!=SET); //等待发送结束
printf("\r\n\r\n");//插入换行
USART_RX_STA=0;
}else
{
times++;
if(times%5000==0)
{
printf("\r\nALIENTEK MiniSTM32开发板 串口实验\r\n");
printf("正点原子@ALIENTEK\r\n\r\n\r\n");
}
if(times%200==0)
{
printf("请输入数据,以回车键结束\r\n");
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);//闪烁LED,提示系统正在运行.
}
delay_ms(10);
}
}
}
至此,我们把system的文件都过了一遍,可以将更改后的文件另存为,下次新建文件就可以直接使用了。
尝试移植新的文件
a. 新建一个一样的工程
b. 将文件复制粘贴到工程文件中
yuanzi这个文件夹里有
c. 打开工程,导入c文件和h文件
d. 打开stm32f1xx_it.c,在void USART1_IRQHandler(void)前添加__weak
__weak void USART1_IRQHandler(void)
e. 在main函数加入实验的代码测试
注意串口初始化后需要添加打开接收中断的函数,这样子才能实现库原本的接收功能。
中间的操作说明如果出现什么错误或者说不明白的地方还请大家帮忙指正批评。
链接里面有一个成功的工程文件和yuanzi的那个库文件夹。
文件下载链接.