工程模板文件的如何进行初始化
从cubemx软件找到本地保存包的地址。



本视频中作者对于STM32CubeMX的态度,只用来负责外设的初始化配置,将配置完成确认可以运行的代码再复制进工程中,这感觉也是不错的。
-
在文件浏览器中确定要移植的模板文件
文件名称 |
文件地址 |
CMSIS文件 |
Drivers文件夹下的Device和include |
HAL驱动库 |
STM32WBxx_HAL_Driver文件夹下的inc和src |
工程模板文件 |
\Projects\P-NUCLEO-WB55.Nucleo\Templates文件夹下的inc和src |
-
复制到自己新建的模板文件夹下面,确定的格式是这样的
建立三个文件夹,命名如下Libraries,Project,Source
文件夹名称 |
文件名称 |
文件含义 |
Libraries |
CMSIS |
官方库的CMSIS |
Libraries |
FWLIB |
官方库的HAL部分 |
Project |
|
用于创建keil的地方 |
Source |
App |
保留 |
Source |
Dev |
保留 |
Source |
Main |
官方库的template |
Source |
Packages |
保留 |
注:
- 官方库的HAL部分,ll库可以不保留,初学阶段很少用到。
- 官方库的HAL部分,”...template“ 不保留,后续自己定义定时器的方法
- 官方模板文件中的”system_stm32wbxx.c“在CMSIS中有一份重复的了,放在template或者CMSIS都可以说得过去,只保留一份即可。
-
在keil中建立工程目录**

-
注释,重新编译,0错误0警告,完成。

-
替换掉main.h 加入新的system.h system.c 和 config.h
其中config.h的作用相当于新的main.h,包含所有用到的头文件,这样调试起来比较方便,system.h和system.c用来抽离main.c的代码,使main.c工程看起来更加清爽,模板文件中有main.c,xxx_it.c,xxx_msp_init.c文件用到了main.h头文件,把这些地方全都换成config.h,编译,完成。
-
记得执行一次save all,否则可能要收获一次血的教训。
GPIO外设 点亮LED灯
在参考资料UM2435中,可以看到套件中stm32WB55-Nucleo的大致介绍,

板载有三颗LED灯珠,从原理图中可以看到,分别接在PB5、PB0、PB1中。

用STM32CubeMX工具初始化外设驱动。开发板选择wb55-nucleo选项。

官方的开发板,默认的配置方案中就已经有了LED的脚位配置。


在外设的时钟可以看到,CPU1(M4内核)的时钟最高只有64MHz.

选定一个空的文件夹方便管理生成代码,选择对应的编译工具链,生成.c和.h的文件等

生成代码,用keil编译工具链先编译一趟看看有没有错误。

用hal库的接口编写最简单的闪灯程序,
HAL_GPIO_WritePin(LD1_GPIO_Port,LD1_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(LD2_GPIO_Port,LD2_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(LD3_GPIO_Port,LD3_Pin,GPIO_PIN_SET);
HAL_Delay(500);
HAL_GPIO_WritePin(LD1_GPIO_Port,LD1_Pin,GPIO_PIN_RESET);
HAL_GPIO_WritePin(LD2_GPIO_Port,LD2_Pin,GPIO_PIN_RESET);
HAL_GPIO_WritePin(LD3_GPIO_Port,LD3_Pin,GPIO_PIN_RESET);
HAL_Delay(500);
烧录,复位测试,程序能够正常工作。
USART外设
实现printf功能
回到STM32CubeMX的配置界面,wb55-nucleo板子自带的usb串口连接在PB7和PB6上

串口外设和io口初始化由cubemx完成了,要实现一个printf功能用于后续程序调试,只需要实现fputc接口,重定向到串口上就行,
引入头文件
#include "stdio.h"
fputc实现
int fputc(int ch, FILE* file)
{
HAL_UART_Transmit(&huart1,(uint8_t*) &ch,1,200);
return ch;
}
//main.c中做测试:
printf(" hello world uart1 config ok!\r\n");
HAL_Delay(1000);
keil编译器中,需要选择use MicroLIB选项,否则串口正常运行时不会有输出,只有在debug模式下,调试时才有串口打印。

实现完成后,使用串口可以看到输出,默认配置是数据位7位。

实现Rx接收中断功能
这里用的是标准库的实现思路简单验证。
STM32CubeMX的修改如下:
- 在STM32CubeMx中使能USART1外设的global interrupt

- 修改中断的优先级,位置是在SystemCore的NVIC,可以先设置组优先级,再设置抢占和子优先级,stm32的优先级越大数字越小。

- 完成,重新生成代码。
在KeilMDK软件中,stm32主要在 HAL_UART_MspInit
底层初始化的函数中新增了以下的代码:
/* USART1 interrupt Init */
HAL_NVIC_SetPriority(USART1_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
代码设置了优先级并且使能了串口的中断。
要实现串口的接收,我们主要需要修改串口中断服务函数,纯中断而且比较简单的实现是:使能两个标志位,RX空闲(IDLE)和RX非空(RXNE)标志位。
在外设数据寄存器非空标志位中,把数据从Rx外设搬运进数组。
在外设空闲标志位中,我们把数组的数据直接用printf打印出来。
-
使能两个标志位,位置可以是在MX_USART1_UART_Init
函数中,cubemx并不帮我们生成这两个标志位。
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);
__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
-
在串口的中断服务函数USART1_IRQHandler
中,实现rx数据接收和回显。
uint8_t g_usart1_rxbuf[240]; // global
uint8_t g_usart1_rxidx = 0; // global
//...
uint8_t receive_char;
if((__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET))
{
__HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE);
HAL_UART_Receive(&huart1,&receive_char,1,200);
g_usart1_rxbuf[g_usart1_rxidx ++] = receive_char;
}
if(( __HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET))
{
g_usart1_rxbuf[g_usart1_rxidx] = '\0';//c字符串的终止符号
printf("usart1 receive:%s\n",g_usart1_rxbuf);
g_usart1_rxidx = 0;
__HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_IDLE);
}
-
回显现象
裸机用定时器实现简单的任务框架
在很多人介绍RTOS的时候,会笼统的把裸机说成是必须要延时等待长时间任务的,以此来说明其分时高效,不浪费cpu资源的特点。
裸机实现这样的特点是可行的,核心就是用定时器的定时功能。例如按键扫描,完全是可以放在1ms中断中通过软件的计数值进行按键的消抖,长短按的识别等等。但是没有框架,纯靠堆代码,会导致各种计数值和标志位飘散在程序中,看的凌乱而且难以管理。框架的引入可以大大节省各种命名的烦恼,保证程序的可靠性。
这个任务框架有点RTOS的味道主要由****task**和**multiTimer**两个文件组成,其中multiTimer是一个底层实现,有点类似用软件实现IIC,实际用户程序不应该使用这个底层。multiTimer实现了一个定时器链表,时钟滴答计时器可以放在systick中自增,每个while循环,将轮询定时器的超时时间,定时时间到了,执行回调函数,并且自动更新下一个超时时间。**
如何使用?
-
定义一个任务数组,这些任务统一一个时钟,如1ms,任务数组将在函数中进行初始化。
#define TASK_TIMER_CNT 4 //任务定时器个数,增加任务时须修改该宏值
struct Timer task_timer[TASK_TIMER_CNT];
-
声明回调函数
void led0_task_timer_callback(void);//任务回调函数声明
void led1_task_timer_callback(void);//任务回调函数声明
void led2_task_timer_callback(void);//任务回调函数声明
void uart_task_timer_callback(void);//任务回调函数声明
-
初始化定时器数组,其大小和任务数组相同且一一对应。
task_timer_parameter task_timer_para[TASK_TIMER_CNT] =
{
/*{timeout_cb, timeout(ms), repeat(ms)}*/
{led0_task_timer_callback, 1000, 1000},
{led1_task_timer_callback, 500, 500},
{led2_task_timer_callback, 200, 200},
{uart_task_timer_callback, 1000, 1000},
/*{timeout_cb, timeout(ms), repeat(ms)}*/
};
-
任务定时器回调函数实现
void led0_task_timer_callback(void)//任务回调函数实现
{
led0_toggle();
}
void led1_task_timer_callback(void)//任务回调函数实现
{
led1_toggle();
}
void led2_task_timer_callback(void)//任务回调函数声明
{
led2_toggle();
}
void uart_task_timer_callback(void)
{
printf("hello world\r\n");
}
-
任务初始化函数
int task_init(void)
{
uint8_t task_index;
for(task_index = 0;task_index < TASK_TIMER_CNT;task_index++)
{
timer_init(&task_timer[task_index],
task_timer_para[task_index].timeout_cb,
task_timer_para[task_index].timeout,
task_timer_para[task_index].repeat);
timer_start(&task_timer[task_index]);
}
return 0;
}
-
任务正常循环调用
int task_run(void)
{
timer_loop();
return 0;
}
要添加任务,仅需要修改任务数的宏,声明和定义一个新的函数,然后设定多少ms执行一次即可。
小结
主要用wb55实现了最基础的外设驱动和功能。wb55的外设驱动起来还是比较舒服的,没有碰到太多的坑。
由于不熟悉hal库,有参考一些视频资料,
本文的代码和思路很大部分参考的一位b站大佬:hezhijie157,视频地址:
STM32-HAL库速讲**哔哩哔哩bilibili**。
代码资料:链接:https://pan.baidu.com/s/1AnxLqKJnwHRSpT6cIMzV-A提取码:3mgo