【STM32开发】-基础开发笔记(STM32F103,HAL库开发)
一、软件使用
1.1、MDK5
MDK5 由两个部分组成:MDK Core 和 Software Packs,MDK Core 又分成四个部分:uVision IDE with Editor(编辑器),ARMC/C++ Compiler(编译器),Pack Installer(包安装器),uVision Debugger with Trace(调试跟踪器);Software Packs(包安装器)又分为:Device(芯片支持),CMSIS(ARM Cortex 微控制器软件接口标准)和 Mdidleware(中间库)三个小部分。
安装包网上有很多资源,MDK5安装完后,还需要安装 STM32F1的器件支持包:Keil.STM32F1xx_DFP.1.0.5.pack。
1.2、STM32CubeF1
STM32Cube 是 ST 提供的一套性能强大的免费开发工具和嵌入式软件模块,主要有两部分:
1、图形配置工具 STM32CubeMX;
2、嵌入式软件包(STM32Cube 库)。
经常会有人问,HAL库与标准库有什么区别,实际HAL 库和标准库本质上是一样的,都是提供底层硬件操作 API;
HAL 库在设计的时候更注重软硬件分离,HAL 库的目的应该就是尽量抽离物理层,HAL库的 API 集中关注各个外设的公共函数功能,以便定义通用性更好的 API 函数接口,从而具有更好的可移植性。HAL 库写的代码在不同的 STM32 产品上移植,非常方便,效率得到提升。
目前 HAL 库支持 STM32 各个系列产品。
1.3、新建工程文件说明
在keil新建工程,保存后会生成如下目录:
Template.uvprojx:工程文件
DebugConfig:存储一些调试配置文件
Listing和Obiects:用来存储MDK编译过程的一些中间文件
将STM32CubeF1包里的源码文件复制到工程目录下:
1、复制Src和Inc文件复制到自己的工程目录下
Src存放的是固件库的.c文件,Inc存放的是对应的.h文件。
2、将startup_stm32f103xe.s复制到CORE目录下:
3、四个头文件:cmsis_armcc.h,cmsis_armclang.h,cmsis_compiler.h,core_cm3.h 同样复制到 CORE 目录下面
4、将3 个文件stm32f1xx.h,system_stm32f1xx.h 和 stm32f103xe.h 复制到 USER 目录之下
5、进入目录\STM32Cube_FW_F1_V1.8.0\Projects\STM3210E_EVAL\Templates 目录下,打开Inc目录,将目录下面的3个头文件stm32f1xx_it.h,stm32f1xx_hal_conf.h 和main.h全部复制到 USER 目录下面。然后我们打开 Src 目录,将下面的四个源文件 system_stm32f1xx.c,stm32f1xx_it.c, stm32f1xx_hal_msp.c 和 main.c 同样全部复制到 USER 目录下面。
让后在MDK上新建,文件移动后,项目结构如图:
1.4、关键部分学习
1、startup_stm32f103xe.s 启动文件
STM32 系列所有芯片工程都会有一个.s 启动文件。对于不同型号的 stm32 芯片启动文件也
是不一样的。
2、__weak修饰符
如果函数名称前面加上__weak 修饰符,我们一般称这个函数为“弱函数”。加上了__weak 修饰符的函数,用户可以在用户文件中重新定义一个同名函数,最终编译器编译的时候,会选择用户定义的函数,如果用户没有重新定义这个函数,那么编译器就会执行__weak 声明的函数,并且编译器不会报错。
二、C语言基础知识学习
2.1、位操作
C语言的常规操作:
运算符 | 含义 |
& | 按位与 |
| | 按位或 |
~ | 取反 |
<< | 左移 |
>> | 右移 |
^ | 按位异或 |
在单片机开发中,这些运算符经常使用,下面来看看具体使用:
比如对寄存器的操作:
GPIOA->ODR &= 0XFFOF;//将第4-7位请0
GPIOA->ODR |= 0x0040;//设置对应位置的值
移位操作:
GPIOA->ODR|=1<<5;//将ODR寄存器的第5位设置为1,其他位的值不变
取反操作:
GPIOB->ODR=(uint16_t)~(1<<3);//将ODR寄存器的第3为设为0,其他位为1
2.2、宏定义
1、define宏定义
#define 标识符 字符串
#define IIC_VAL ((uint32_t)9600)//定义标识符IIC_VAL的值为9600
可以在代码中直接使用IIC_VAL标识符,而不用直接使用常量 9600,同时也很方便我们修改 IIC_VAL 的值。
2、#ifdef和if defined条件编译
#ifdef 标识符
程序段 1
#else
程序段 2
#endif
作用:当标识符已经被定义过(一般是用#define 命令定义),则对程序段 1 进行编译,
否则编译程序段 2。 其中#else 部分也可以没有。
3、extern变量声明
extern 可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示
编译器遇到此变量和函数时在其他模块中寻找其定义。
特别注意:extern声明变量可以很多次,但是变量的定义只有一个地方定义。
4、typedef类型声明
typedef 用于为现有类型创建一个新的名字,或称为类型别名,用来简化变量的定义。
struct IIC
{uint32_t addr;uint32_t data;
};//定义结构体IIC//使用typedef
typedef struct
{uint32_t addr;uint32_t data;
}IIC_TypeDef;//Typedef 为结构体定义一个别名IIC_TypeDef,这样我们可以通过IIC_TypeDef来定义结构体
变量.
三、其他基础知识
3.1、关于端口复用
GPIO如果可以复用为内置外设的功能引脚,那么当这个 GPIO 作为内置外设使用的时候,就叫做复用。
复用的一般步骤:
1、打开对应IO时钟和复用功能外设时钟(GPIOA和USART1);
__HAL_RCC_GPIO_CLK_ENABLE();//使能GPIO时钟
__HAL_RCC_USART1_CLK_ENABLE();//使能USART1时钟
__HAL_RCC_AFIO_CLK_ENABLE();//时钟辅助功能IO时钟
2、在GPIOx_MODER寄存器中奖需要的IO口配置为复用功能;
3、配置IO的参数,比如上拉/下拉,输出速度等。
3.2、关于中断优先级
{ void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
/* Check the parameters */
assert_param(IS_NVIC_PRIORITY_GROUP(PriorityGroup));
/* Set the PRIGROUP[10:8] bits according to the PriorityGroup parameter value */
NVIC_SetPriorityGrouping(PriorityGroup);
}__STATIC_INLINE void NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
uint32_t reg_value;
uint32_t PriorityGroupTmp = (PriorityGroup & (uint32_t)0x07UL);
reg_value= SCB->AIRCR; /* read old register configuration */
reg_value&=~((uint32_t)(SCB_AIRCR_VECTKEY_Msk |SCB_AIRCR_PRIGROUP_Msk));
reg_value = (reg_value|((uint32_t)0x5FAUL << SCB_AIRCR_VECTKEY_Pos) |
(PriorityGroupTmp<< SCB_AIRCR_PRIGROUP_Pos) );
SCB->AIRCR = reg_value;
}
第一个是HAL库中中断优先级分组函数,这个函数在系统中只需要被调用一次,一旦分组确定就最好不要更改,否则容易造成程序分组混乱。
第二个是设置 SCB->AIRCR 寄存器的值来设置中断优先级,
四、常规开发使用
4.1、串口使用步骤
1、串口初始化,使能串口
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart);
入口参数是huart,为 UART_HandleTypeDef 结构体指针类型,结构体参数如下:
//串口定义参数结构体
typedef struct
{USART_TypeDef *Instance; //基地址,比如串口1,则为USART1UART_InitTypeDef Init; uint8_t *pTxBuffPtr; //发送的数据缓冲指针uint16_t TxXferSize; //发送的数据量__IO uint16_t TxXferCount; //还剩余的要发送的数据量uint8_t *pRxBuffPtr; //接收的数据缓冲指针uint16_t RxXferSize; //接收的最大数据量__IO uint16_t RxXferCount; //还剩余要接收的数据量DMA_HandleTypeDef *hdmatx;DMA_HandleTypeDef *hdmarx;HAL_LockTypeDef Lock;__IO HAL_UART_StateTypeDef gState;__IO HAL_UART_StateTypeDef RxState;__IO uint32_t ErrorCode;
}UART_HandleTypeDef;//UART初始化结构体
typedef struct
{uint32_t BaudRate; //波特率uint32_t WordLength; //字长uint32_t StopBits; //停止位uint32_t Parity; //奇偶校验uint32_t Mode; //收/发模式设置uint32_t HwFlowCtl;//硬件流设置uint32_t OverSampling; //过采样设置
}UART_InitTypeDef//初始化一般格式,以USART1为例
UART_HandleTypeDef UART1_Handler; //UART 句柄
UART1_Handler.Instance=USART1; //USART1
UART1_Handler.Init.BaudRate=115200;//波特率
UART1_Handler.Init.WordLength=UART_WORDLENGTH_8B; //字长为 8 位格式
UART1_Handler.Init.StopBits=UART_STOPBITS_1;//停止位
UART1_Handler.Init.Parity=UART_PARITY_NONE;//无奇偶校验位
UART1_Handler.Init.HwFlowCtl=UART_HWCONTROL_NONE; //无硬件流控
UART1_Handler.Init.Mode=UART_MODE_TX_RX;//收发模式
HAL_UART_Init(&UART1_Handler); //HAL_UART_Init()会使能 UART1
2、使能串口和时钟
__HAL_RCC_USART1_CLK_ENABLE();//使能 USART1 时钟
__HAL_RCC_GPIOA_CLK_ENABLE();//使能 GPIOA 时钟
3、串口复用配置
复用映射在初始化时完成,一般在设置串口配置是设置,例如:
GPIO_Initure.Pin=GPIO_PIN_4; //PA4
GPIO_Initure.Mode=GPIO_MODE_AF_PP;//复用推挽输出
//......其他配置
4、开启相关中断,配置串口中断的优先级
HAL库中常用的中断配置方法如下:
//使能中断
__HAL_UART_ENABLE_IT(huart,UART_IT_RXNE);//开启接收完成中断
__HAL_UART_DISABLE_IT(huart,UART_IT_RXNE);//关闭接收完成中断
//中断优先级
HAL_NVIC_EnableIRQ(USART1_IRQn);//使能 USART1 中断通道
HAL_NVIC_SetPriority(USART1_IRQn,3,3);//抢占优先级 3,子优先级 3
5、中断服务函数
发生中断时,程序就会执行中断服务函数,比如:
void USART1_IRQHandler(void)
{HAL_UART_IRQHandler(&UART1_Handler); //调用 HAL 库中断处理公用函数…//中断处理完成后的结束工作
}
6、发送和接收和发送
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart,
uint8_t *pData, uint16_t Size, uint32_t Timeout);//发送数据函数
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart,
uint8_t *pData, uint16_t Size, uint32_t Timeout);//接收数据函数
使用HAL库可以直接调用相关函数。
4.2、关于独立看门狗和窗口看门狗
对于STM32,在键值寄存器(IWDG_KR)中写入0xCCCC,开始启用看门狗,对于HAL库,有特定的函数:
1、取消寄存器写保护
HAL_StatusTypeDef HAL_IWDG_Init(IWDG_HandleTypeDef *hiwdg);//对应结构体定义:
typedef struct
{IWDG_TypeDef *Instance; //看门狗寄存器基地址IWDG_InitTypeDef Init;
}IWDG_HandleTypeDef;//初始化结构体
typedef struct
{uint32_t Prescaler; //看门狗预分频系数uint32_t Reload; //重装载值
}IWDG_InitTypeDef;
2、重载计数值喂狗
HAL_StatusTypeDef HAL_IWDG_Refresh(IWDG_HandleTypeDef *hiwdg);
把值0xAAAA写入到IWDG_KR寄存器,从而触发计数器重载,即实现独立看门狗的喂狗操作
3、启动看门狗
__HAL_IWDG_START(hiwdg);
对于窗口看门狗:
窗口看门狗(WWDG)通常被用来监测由外部干扰或不可预见的逻辑条件造成的应用程序
背离正常的运行序列而产生的软件故障。
1、使能WWDG时钟
WWDG 不同于 IWDG,IWDG 有自己独立的 40Khz 时钟,不存在使能问题。而 WWDG
使用的是 PCLK1 的时钟,需要先使能时钟。
__HAL_RCC_WWDG_CLK_ENABLE(); //使能窗口看门狗时钟
2、设置窗口值,分频数和计数器初始值
HAL_StatusTypeDef HAL_WWDG_Init(WWDG_HandleTypeDef *hwwdg);//WWDG_HandleTypeDef 结构体定义
typedef struct
{WWDG_TypeDef *Instance;WWDG_InitTypeDef Init;
}WWDG_HandleTypeDef;//WWDG_InitTypeDef 结构体类型
typedef struct
{uint32_t Prescaler;//预分频系数uint32_t Window;//窗口值uint32_t Counter;//计数器值uint32_t EWIMode ; //是否启用 WWDG 早期唤醒中断
}WWDG_InitTypeDef;
3、使能中断通道并配置优先级
HAL_NVIC_SetPriority(WWDG_IRQn,2,3);//抢占优先级 2,子优先级为 3
HAL_NVIC_EnableIRQ(WWDG_IRQn); //使能窗口看门狗中断
4、写对应的中断服务函数
通过该函数来喂狗,喂狗要快,否则当会引起软复位。
//中断服务函数
void WWDG_IRQHandler(void);
//喂狗
HAL_StatusTypeDef HAL_WWDG_Refresh(WWDG_HandleTypeDef *hwwdg);
5、重写中断处理回调函数
void HAL_WWDG_EarlyWakeupCallback (WWDG_HandleTypeDef* hwwdg);
4.3、定时器中断使用(重要)
1、首先使能要使用的定时器
__HAL_RCC_TIM2_CLK_ENABLE(); //使能 TIM2 时钟
2、初始化定时器
HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim);//TIM_HandleTypeDef 类型结构体
typedef struct
{TIM_TypeDef *Instance; //寄存器基地址TIM_Base_InitTypeDef Init; //初始化HAL_TIM_ActiveChannel Channel; //通道DMA_HandleTypeDef *hdma[7]; //DMA功能HAL_LockTypeDef Lock; //状态标识符__IO HAL_TIM_StateTypeDef State;//状态标识符
}TIM_HandleTypeDef;//TIM_Base_InitTypeDef 结构体
typedef struct
{uint32_t Prescaler;//预分频系数uint32_t CounterMode; //计数方式uint32_t Period;//自动装载值 ARRuint32_t ClockDivision;//时钟分频因子uint32_t RepetitionCounter;
} TIM_Base_InitTypeDef;
3、使能定时器
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);
4、中断优先级设定
中断优先级配置我们都会放在该回调函数内部:
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim);//使用
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{if(htim->Instance==TIM2){__HAL_RCC_TIM2_CLK_ENABLE(); //使能 TIM2 时钟HAL_NVIC_SetPriority(TIM2_IRQn,1,3);//设置中断优先级,抢占优先级 1,子优先级 3HAL_NVIC_EnableIRQ(TIM2_IRQn); //开启 ITM2 中断}
}
5、中断服务函数
void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim);
如需回调函数,则使用需要重写该函数:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);
来自正点原子视频学习总结。