STM32F103C8T6学习笔记——使用STM32CubeMX编程
开发板原理图
LED1 PB8 LED2 PB9
KET1 PA0 KEY2 PA1
TXD1 PA9 RXD1 PA10
TXD2 PA2 RXD2 PA3
TXD3 B10 RXD3 B11
SCL PA6 SDA PA7
CubeMX创建工程的步骤
这里有一篇好文章,感谢博主,讲的很详细。
——————————————————————————————————————————
STM32 CubeMx教程 — 基础知识及配置使用教程_stm32cubemx教程_Dir_xr的博客-CSDN博客
——————————————————————————————————————————
下面我们以轮询法点灯来演示一下,首先看原理图,知道LED和KEY的接线
LED1 PB8 LED2 PB9 低电平亮
KET1 PA0 KEY2 PA1 低电平代表被按下
1、打开软件点击箭头所指的按钮
2、在箭头所指的地方输入自己的芯片,然后双击第二个箭头自己的芯片型号
3、配置SYS参数
GPIO口的设置
4、配置引脚参数,直接点上面的引脚选择不同的功能,PB8、PB9配置成输出模式,PA0、PA1配置为输入模式(检测这两个引脚的值来判断按键是否被按下)。
5、在外设的详细配置界面配置下PB8、PB9的初始值为高电平(这一步可要可不要),区别就是上电以后LED默认是亮还是不亮的问题。
6、生成工程文件,填好自己的项目名字和保存位置,IDE选择MDK-ARM,然后按照下面打勾,最后生成文件即可
7、此时已经自动打开了keil软件了,就可以进行代码的编写了
8、轮询法点亮LED代码
把下面的代码放到主函数的while(1)循环中即可
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==RESET){
HAL_Delay(20);
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==RESET);
HAL_Delay(20);
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
}
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1)==RESET){
HAL_Delay(20);
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1)==RESET);
HAL_Delay(20);
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_9);
练习:外部中断点灯
LED1 PB8 LED2 PB9 低电平亮
KEY1 PA0 KEY2 PA1 低电平代表被按下
按照上面的步骤配置,区别是要配置RCC,选择外部晶振,时钟树配置成72按确定
然后在GPIO配置的时候PA0、PA1选择中断模式,然后配置成下降沿触发中断
在NVIC打开中断
然后配置项目文件打开Keil编程即可。
要使用中断我们要知道怎么去找中断服务函数,找到以后重写虚函数。
接下来我们重写虚函数,把虚函数的定义复制到主函数前重新写
外部中断虚函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
HAL_Delay()的注意点
注意:中断里面尽量不要用HAL_Delay这个函数,应为这这个函数本身也是靠Systick定时器中断来完成的,而且这个中断的优先级一般是最低的,在GPIO的中断回调函数中得不到响应额,就会锁死在这个函数这里。
这里不能把延时函数写在case语句下面,程序会卡死。(原因不知道)。
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
switch(GPIO_Pin){
HAL_Delay(20);
case GPIO_PIN_0:
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1)==GPIO_PIN_RESET);
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
break;
case GPIO_PIN_1:
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1)==GPIO_PIN_RESET);
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);
break;
}
}
定时器
定时器的分类
基本定时器 TIM6~TIM7
通用定时器 TIM2~TIM5
高级定时器 TIM1和TIM8
STM32F103C8T6有四个定时器
一个高级定时器TIM1 三个通用定时器TIM2 TIM3 TIM4
计数模式:
向上计数,向下计数,中心对齐计数
定时器溢出时间计算公式
练习:使用TIM2定时器中断点灯
要求:使用定时器中断的方法,每500ms翻转一次LED1的状态
我们使用TIM2来完成
1、打开CubeMX配置gpio口PB8、PB9为输出模式
2、配置RCC为外部高速时钟并且时钟树配置为72MHz
3、配置TIM2和使能定时器中断
4、上述做完以后配置好文件名等打开KEIL软件写代码
同样先去中断文件里面找定时器中断的回调函数,重写该函数,函数嵌套的的比较多,这里复制出来了,就不用去自己找了,复制以后,重写即可
定时器中断虚函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
5、代码
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)回调函数的形参是一个结构体
在写回调函数的时候判断htim->Instance的值来确定是那个定时器中断调用的中断回调函数
定时器中断启动函数
在主函数中定时器初始化函数以后,需要添加HAL_TIM_Base_Start_IT(&htim2);函数启动定时器中断
重写中断回调函数
写好以后编译下载即可。
PWM
在51中是没有硬件支持的,需要我们用软件模拟,在32中资源丰富,有硬件电路支持。
PWM的周期和频率、占空比
实操
练习:PWM实现呼吸灯
需求:使用PWM点亮LED1实现呼吸灯。
首先看原理图和STM32使用手册
LED接到PB8的,同时PB8又跟TIM4的CH3复用一根线。所以我们待会配置PB8的时候要配置成TIM4_CH3。
打开CubeMX开始配置
第五步开始配置TIM4(因为PB8接到TIM4的CH3的)
根据上图配置完以后,完成后面的项目配置(项目名称之类的)打开Keil。
首先介绍两个函数
PWM启动函数
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_3);
参数:1、启动的那个定时器 2、启用的哪一路PWM
在主函数初始化PWM以后要调用这个函数
修改比较值的函数(修改CCRx的值)
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, pwmVal);
完整代码如下:
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
uint16_t pwmVal=0;
uint8_t flag=1;//状态标志位 1 越来越亮 0 越来越暗
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM4_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_3);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_Delay(1);
/* USER CODE END WHILE */
if(pwmVal>500){
flag=0;
}
if(pwmVal==0){
flag=1;
}
if(flag==1){
pwmVal++;
}
else if(flag==0){
pwmVal--;
}
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, pwmVal);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
练习:PWM控制舵机
需求:每隔1s,转动一个角度:0度 –> 45度 –> 90度 –> 135度 –> 180度 –> 0度
原理:舵机由三根线,vcc、gnd、信号线、向黄色的信号线输入不同的PWM波形就可以控制舵机的转向了。
向黄色信号线“灌入”PWM信号
PWM波的频率不能太高,大约50HZ,即周期=1/频率=1/50=0.02s,20ms左右
确定周期/频率
如果周期为20ms,则 PSC=7199,ARR=199
角度控制
0.5ms————-0度; 2.5% 对应函数中CCRx为5
1.0ms————45度; 5.0% 对应函数中CCRx为10
1.5ms————90度; 7.5% 对应函数中CCRx为15
2.0ms———–135度; 10.0% 对应函数中CCRx为20
2.5ms———–180度; 12.5% 对应函数中CCRx为25
本次选用TIM3_CH1来产生PWM波形,查看手册是PA6引脚。
1、打开CubeMx开始配置
2、接着配置工程文件,这里就不演示了,配置完成打开keil开始写代码
老规律,这么配置完,主函数里面只有对TIM3的初始化,要想使用PWM还得添加启动函数
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
代码如下:
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM3_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);//PWM启动函数
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
//__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, pwmVal);
uint16_t pwmVal=5;
while (1)
{
/* USER CODE END WHILE */
__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, pwmVal);//改变CCRx的值
HAL_Delay(1000);
pwmVal=pwmVal+5;
if(pwmVal>=30){
pwmVal=5;
}
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
练习:PWM控制超声波
需求:
使用超声波测距,当手离传感器距离小于5cm时,LED1点亮,否则保持不亮状态。
超声波模块的原理如下:
怎么让它发送波
Trig ,给Trig端口至少10us的高电平
怎么知道它开始发了
Echo信号,由低电平跳转到高电平,表示开始发送波
怎么知道接收了返回波
Echo,由高电平跳转回低电平,表示波回来了
怎么算时间
Echo引脚维持高电平的时间!
波发出去的那一下,开始启动定时器
波回来的拿一下,我们开始停止定时器,计算出中间经过多少时间
怎么算距离
距离 = 速度 (340m/s)* 时间/2
这个练习需要自己手写一个延时10us的函数,因为STM32的HAL_Delay函数最低延时就是1ms。
这里使用TIM2来完成这个延时的函数
定时器配置:
使用 TIM2 ,只用作计数功能,不用作定时。
将 PSC 配置为71,则计数 1 次代表 1us 。
编写微秒级函数:
//使用TIM2来做us级延时函数
void TIM2_Delay_us(uint16_t n_us)
{
/* 使能定时器2计数 */
__HAL_TIM_ENABLE(&htim2);
__HAL_TIM_SetCounter(&htim2, 0);
while(__HAL_TIM_GetCounter(&htim2) < ((1 * n_us)-1) );
/* 关闭定时器2计数 */
__HAL_TIM_DISABLE(&htim2);
}
接线:
1、打开CubeMx开始配置
SYS RCC 时钟树 配置定时器 GPIO口 PB6输出 PB7输入
2、代码
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM2_Init();
/* USER CODE BEGIN 2 */
int cnt;
float distance;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
// 怎么让它发送波
//Trig ,给Trig端口至少10us的高电平
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_SET);
TIM2_Delay_us(15);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_RESET);
//怎么知道它开始发了
//Echo信号,由低电平跳转到高电平,表示开始发送波
//波发出去的那一下,开始启动定时器
while( HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_7)==GPIO_PIN_RESET);
HAL_TIM_Base_Start(&htim2);
__HAL_TIM_SetCounter(&htim2,0);//让定时器从0开始计数
//怎么知道接收了返回波
//Echo,由高电平跳转回低电平,表示波回来了
//波回来的拿一下,我们开始停止定时器,计算出中间经过多少时间
while( HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_7)==GPIO_PIN_SET);
HAL_TIM_Base_Stop(&htim2);
//怎么算时间
//Echo引脚维持高电平的时间!
cnt=__HAL_TIM_GetCounter(&htim2);//读取定时的数值
//怎么算距离
//距离 = 速度 (340m/s)* 时间/2
distance=cnt*340/2*0.000001*100;
if(distance<5){
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_RESET);
}
else{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET);
}
HAL_Delay(100);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
串口
串口发送/接收函数:
HAL_UART_Transmit(); 串口发送数据,使用超时管理机制
HAL_UART_Receive(); 串口接收数据,使用超时管理机制
HAL_UART_Transmit_IT(); 串口中断模式发送
HAL_UART_Receive_IT(); 串口中断模式接收
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart,
uint8_t *pData, uint16_t Size, uint32_t Timeout)
作用:以阻塞的方式发送指定字节的数据
形参 1 :UART_HandleTypeDef 结构体类型指针变量
形参 2:指向要发送的数据地址
形参 3:要发送的数据大小,以字节为单位
形参 4:设置的超时时间,以ms单位
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart,
uint8_t *pData, uint16_t Size)
作用:以中断的方式接收指定字节的数据
形参 1 是 UART_HandleTypeDef 结构体类型指针变量
形参 2 是指向接收数据缓冲区
形参 3 是要接收的数据大小,以字节为单位
此函数执行完后将清除中断,需要再次调用以重新开启中断。
串口中断回调函数:
HAL_UART_IRQHandler(UART_HandleTypeDef *huart); //串口中断处理函数
HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); //发送中断回调函数
HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); //接收中断回调函数
状态标记变量:
USART_RX_STA(这个变量的名字可以随便取)可以定义为16位的也可以定义位32位的,看自己的需求。
从0开始,串口中断接收到一个数据(一个字节)就自增1。当数据读取全部OK时候(回车和换行
符号来的时候),那么 USART_RX_STA的最高位置1,表示串口数据接收全部完毕了,然后main
函数里面可以处理数据了
串口中断流程
练习:(非中断)接受串口工具发送的字符串,并将其发送回串口工具
1、配置CubeMX,把串口工具接到开发板查到电脑上
2、打开keil开始写代码
printf重定向
在main函数前需要重写一下fputc函数,然后打开魔术棒勾选MicroLIB。
int fputc(int ch,FILE *f)
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
然后开始写代码
int main(void)
{
/* USER CODE BEGIN 1 */
unsigned char str[20]={'\0'};
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
// HAL_UART_Transmit(&huart1,"hello world\n",strlen("hello world\n"),100);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
HAL_UART_Receive(&huart1,str,19,100);
printf("%s",str);//要使用printf打印到串口 需要重写fputc函数,然后勾选魔术棒的MicroLIB
memset(str,'\0',strlen(str));
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
练习:(中断方式)通过中断的方法接受串口工具发送的字符串,并将其发送回串口工具。
接线方式同上,配置同上,这里需要打开中断
代码
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include <stdio.h>
#include <string.h>
uint8_t buf=0; //串口接收缓存 一字节
#define UART_REC_LEN 200 //最大接收字节数
uint8_t UART1_REC_BUFF[UART_REC_LEN]; //接收缓冲
int fputc(int ch,FILE *f) //printf重定向
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
void SystemClock_Config(void);
uint16_t UART1_RX_STA=0; //状态标记变量
// 接收完成回调函数,收到一个数据后,在这里处理
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
// 判断中断是由哪个串口触发的
if(huart->Instance == USART1)
{
// 判断接收是否完成(UART1_RX_STA bit15 位是否为1)
if((UART1_RX_STA & 0x8000) == 0)
{
// 如果已经收到了 0x0d (回车),
if(UART1_RX_STA & 0x4000)
{
// 则接着判断是否收到 0x0a (换行)
if(buf == 0x0a)
// 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1
UART1_RX_STA |= 0x8000;
else
// 否则认为接收错误,重新开始
UART1_RX_STA = 0;
}
else // 如果没有收到了 0x0d (回车)
{
//则先判断收到的这个字符是否是 0x0d (回车)
if(buf == 0x0d)
{
// 是的话则将 bit14 位置为1
UART1_RX_STA |= 0x4000;
}
else
{
// 否则将接收到的数据保存在缓存数组里
UART1_REC_BUFF[UART1_RX_STA & 0X3FFF] = buf;
UART1_RX_STA++;// 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
if(UART1_RX_STA > UART_REC_LEN - 1)
UART1_RX_STA = 0;
}
}
}// 重新开启中断
HAL_UART_Receive_IT(&huart1, &buf, 1);
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
HAL_UART_Receive_IT(&huart1, &buf, 1);
while (1)
{
if(UART1_RX_STA & 0x8000)
{
printf("收到数据:");
// 将收到的数据发送到串口
HAL_UART_Transmit(&huart1, UART1_REC_BUFF, UART1_RX_STA & 0x3fff, 0xffff);
// 等待发送完成
while(huart1.gState != HAL_UART_STATE_READY);
printf("\r\n");
// 重新开始下一次接收
UART1_RX_STA = 0;
}
}
}
独立看门狗 IWDG
简介:
在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造成程序的跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作,会造成整个系统的陷入停滞状态,发生不可预料的后果,所以出于对单片机运行状态进行实时监测的考虑,便产生了一种专门用于监测单片机程序运行状态的模块或者芯片,俗称“看门狗(watchdog) 。
独立看门狗工作在主程序之外,能够完全独立工作,它的时钟是专用的低速时钟(LSI),由
VDD 电压供电, 在停止模式和待机模式下仍能工作。
独立看门狗本质
本质是一个 12 位的递减计数器,当计数器的值从某个值一直减到0的时候,系统就会产生一个复位信号,即 IWDG_RESET 。
如果在计数没减到0之前,刷新了计数器的值的话,那么就不会产生复位信号,这个动作就是我们经常说的喂狗
独立看门狗框图
独立看门狗时钟
独立看门狗的时钟由独立的RC振荡器LSI提供,即使主时钟发生故障它仍然有效,非常独立。启用IWDG后,LSI时钟会自动开启。LSI时钟频率并不精确,F1用40kHz。
LSI经过一个8位的预分频器得到计数器时钟。
预分频寄存器(IWDG_PR)
重装载寄存器
重装载寄存器是一个12位的寄存器,用于存放重装载值,低12位有效,即最大值为4096,这个值的大小决定着独立看门狗的溢出时间。
键寄存器
键寄存器IWDG_KR可以说是独立看门狗的一个控制寄存器,主要有三种控制方式,往这个寄存器写入下面三个不同的值有不同的效果。
练习:开启独立看门狗,溢出时间为1秒,使用按键1进行喂狗。
1、打开CubeMX开始配置
2、开始写代码
喂狗函数
HAL_IWDG_Refresh(&hiwdg); //喂狗
没有按下KEY1喂狗的现象,每隔一秒程序会重新从头开始运行,LED会闪烁。
#include "main.h"
#include "iwdg.h"
#include "usart.h"
#include "gpio.h"
void SystemClock_Config(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_IWDG_Init();
MX_USART1_UART_Init();
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_RESET);
HAL_Delay(100);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET);
while (1)
{
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET){
HAL_Delay(20);
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET);
HAL_Delay(20);
HAL_IWDG_Refresh(&hiwdg); //喂狗
}
}
}
窗口看门狗WWDG
简介:
窗口看门狗用于监测单片机程序运行时效是否精准,主要检测软件异常,一般用于需要精准检测程序运行时间的场合。
窗口看门狗的本质
是一个能产生系统复位信号和提前唤醒中断的6位计数器
产生复位条件:
当递减计数器值从 0x40 减到 0x3F 时复位(即T6位跳变到0)
计数器的值大于 W[6:0] 值时喂狗会复位。
产生中断条件:
当递减计数器等于 0x40 时可产生提前唤醒中断 (EWI)。
在窗口期内重装载计数器的值,防止复位,也就是所谓的喂狗。
WWDG工作原理
WWDG框图
控制寄存器WWDG_CR
配置寄存器WWDG_CFR
状态寄存器WWDG_SR
超时时间计算
练习:开启窗口看门狗,计数器值设置为 0X7F ,窗口值设置为 0X5F ,预分频系数为 8 。程序启动时点亮 LED1 ,300ms 后熄灭。在提前唤醒中断服务函数进行喂狗,同时翻转 LED2 状态
1、配置CubeMX
2、写代码
提前中断回调函数
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
喂狗函数
HAL_WWDG_Refresh(hwwdg);
代码
#include "main.h"
#include "wwdg.h"
#include "gpio.h"
void SystemClock_Config(void);
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)//提前中断回调函数
{
HAL_WWDG_Refresh(hwwdg);//喂狗
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_RESET);
HAL_Delay(300);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET);
MX_WWDG_Init();
while (1)
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET);
HAL_Delay(40);
}
}
独立看门狗和窗口看门狗的异同点
DMA
简介:
DMA(Direct Memory Access,直接存储器访问) 提供在外设与内存、存储器和存储器、外设
与外设之间的高速数据传输使用。它允许不同速度的硬件装置来沟通,而不需要依赖于CPU,在这个时间中,CPU对于内存的工作来说就无法使用。简单描述,就是一个数据搬运工
DMA的意义
代替 CPU 搬运数据,为 CPU 减负。
1. 数据搬运的工作比较耗时间;
2. 数据搬运工作时效要求高(有数据来就要搬走);
3. 没啥技术含量(CPU 节约出来的时间可以处理更重要的事)。
搬运什么数据?
存储器、外设
这里的外设指的是spi、usart、iic、adc 等基于APB1 、APB2或AHB时钟的外设,而这里的存
储器包括自身的闪存(flash)或者内存(SRAM)以及外设的存储设备都可以作为访问地源或者目
的。
三种搬运方式:
存储器→存储器(例如:复制某特别大的数据buf)
存储器→外设 (例如:将某数据buf写入串口TDR寄存器)
外设→存储器 (例如:将串口RDR寄存器写入某数据buf)
DMA 控制器
STM32F103有2个 DMA 控制器,DMA1有7个通道,DMA2有5个通道。
一个通道每次只能搬运一个外设的数据!! 如果同时有多个外设的 DMA 请求,则按照优先级进
行响应。
DMA1有7个通道:
DMA2有5个通道:
DMA及通道的优先级
优先级管理采用软件+硬件:
软件: 每个通道的优先级可以在DMA_CCRx寄存器中设置,有4个等级
最高级>高级>中级>低级
硬件: 如果2个请求,它们的软件优先级相同,则较低编号的通道比较高编号的通道有较高
的优先权。
比如:如果软件优先级相同,通道2优先于通道4
DMA传输方式
DMA_Mode_Normal(正常模式)
一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次
DMA_Mode_Circular(循环传输模式)
当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是
多次传输模式
指针递增模式
外设和存储器指针在每次传输后可以自动向后递增或保持常量。当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值
练习1:内存到内存的搬运
需求:使用DMA的方式将数组A的内容复制到数组B中,搬运完之后将数组B的内容打印到屏幕。
1、配置CubeMX
2、编写代码
DMA启动函数
HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t
DstAddress, uint32_t DataLength)
作用:用于启动一个DMA(Direct Memory Access)传输
参数一:DMA_HandleTypeDef *hdma,DMA通道句柄
参数二:uint32_t SrcAddress,源内存地址
参数三:uint32_t DstAddress,目标内存地址
参数四:uint32_t DataLength,传输数据长度。注意:需要乘以sizeof(uint32_t)
返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)
检测是否传输完成函数
#define __HAL_DMA_GET_FLAG(__HANDLE__, __FLAG__) (DMA1->ISR & (__FLAG__)
作用:用于获取DMA(Direct Memory Access)控制器中的特定标志位状态。
参数一:HANDLE,DMA通道句柄
参数二:FLAG,数据传输标志。DMA_FLAG_TCx表示数据传输完成标志
返回值:FLAG的值(SET/RESET)
代码实现
1、开启数据传输
2、等待数据传输完成
3、打印数组内容
#include "main.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"
#include <stdio.h>
#define BUF_SIZE 16
uint32_t srcBuf[BUF_SIZE]={
0x00000000,0x11111111,0x22222222,0x33333333,
0x44444444,0x55555555,0x66666666,0x77777777,
0x88888888,0x99999999,0xAAAAAAAA,0xBBBBBBBB,
0xCCCCCCCC,0xDDDDDDDD,0xEEEEEEEE,0xFFFFFFFF
};
uint32_t desBuf[BUF_SIZE];
void SystemClock_Config(void);
int fputc(int ch, FILE *f)
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
int main(void)
{
int i;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
HAL_DMA_Start(&hdma_memtomem_dma1_channel1,(uint32_t)srcBuf, (uint32_t)desBuf, sizeof(uint32_t) * BUF_SIZE);//开启数据传输
while(__HAL_DMA_GET_FLAG(&hdma_memtomem_dma1_channel1, DMA_FLAG_TC1) == RESET);//检测是传输完成
for (i = 0; i < BUF_SIZE; i++)
{
printf("Buf[%d] = %X\r\n", i, desBuf[i]);
}
while (1)
{
}
}
练习2:内存到外设搬运
需求:使用DMA的方式将内存数据搬运到串口1发送寄存器,同时闪烁LED1。
1、配置CubeMX
2、写代码
启动MDA串口发送函数
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData,
uint16_t Size)
参数一:UART_HandleTypeDef *huart,串口句柄
参数二:uint8_t *pData,待发送数据首地址
参数三:uint16_t Size,待发送数据长度
返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)
代码实现
1. 准备数据
2. 将数据通过串口DMA发送
#include "main.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"
#define BUF_SIZE 1000
unsigned char BUF[BUF_SIZE];
void SystemClock_Config(void);
int main(void)
{
int i;
for(i=0;i<1000;i++){
BUF[i]='A';
}
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
HAL_UART_Transmit_DMA(&huart1,BUF,BUF_SIZE);
while (1)
{
}
}
练习3:外设到内存搬运
需求:使用DMA的方式将串口接收缓存寄存器的值搬运到内存中,在从内存发送到串口,同时闪烁LED1。
常用的函数
使能UART中断函数
__HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__)
参数1:指向UART设备的指针
参数2:中断类型
在STM32的HAL库中,UART设备可以使用多种中断,例如:
UART_IT_TX
: 发送中断UART_IT_RX
: 接收中断UART_IT_TC
: 发送完成中断UART_IT_RCF
: 接收完成中断UART_IT_IDLE
: 空闲中断DMA接收函数
HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData,uint16_t Size)
参数一:UART_HandleTypeDef *huart,串口句柄
参数二:uint8_t *pData,接收缓存首地址
参数三:uint16_t Size,接收缓存长度
返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)
获取DMA通道的计数值函数
__HAL_DMA_GET_COUNTER
#define __HAL_DMA_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNDTR
作用:获取DMA通道的计数值。具体而言,它访问了DMA通道的CNDTR
寄存器,该寄存器存储了当前传输的字节数。通过这个宏定义,可以方便地获取DMA通道的计数值,以便在DMA传输过程中进行监控和管理。
参数一:HANDLE,串口句柄
返回值:未传输数据大小
UART标志位检测函数
__HAL_UART_GET_FLAG(__HANDLE__, __FLAG__)
参数一:HANDLE,串口句柄
参数二:FLAG,需要查看的FLAG
在STM32的HAL库中,UART设备的标志位包括:
UART_FLAG_TXE
:发送寄存器空标志UART_FLAG_TC
:发送完成标志UART_FLAG_RXNE
:接收寄存器非空标志UART_FLAG_IDLE
:空闲线标志UART_FLAG_ERR
:错误标志返回值:返回值是一个布尔值(bool),表示指定的标志位是否被设置。如果指定的标志位被设置,那么返回值为真(true);否则,返回值为假(false)。
清除UART设备的空闲线标志(IDLE flag)函数
__HAL_UART_CLEAR_IDLEFLAG(__HANDLE__)
参数一:HANDLE,串口句柄
返回值:无
UART DMA传输的停止函数
HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart)
参数:huart,串口指针。
功能:停止DMA的传输。
1、打开CubeMX函数配置
需要注意的是,DMA2支持支内存到内存的传输,所以练习1用的是DMA2,而练习2,练习3是内存到外设和外设到内存,所以这两个练习使用的是DMA1。
2、写代码
main.c
#include "main.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"
#define BUF_SIZE 100
uint8_t rcvBuf[BUF_SIZE]; // 接收数据缓存数组
uint8_t rcvLen = 0; // 接收一帧数据的长度
void SystemClock_Config(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 使能IDLE空闲中断 //串口空线的时候会产生一个中断
HAL_UART_Receive_DMA(&huart1,rcvBuf,100); // 使能DMA接收中断
while (1)
{
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
HAL_Delay(300);
}
}
stm32f1xx_it.c
#define BUF_SIZE 100
extern uint8_t rcvBuf[BUF_SIZE];
extern uint8_t rcvLen;
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);
if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) == SET)) // 判断IDLE标志位是否被置位
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);// 清除标志位
HAL_UART_DMAStop(&huart1); // 停止DMA传输,防止干扰
uint8_t temp=__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
rcvLen = BUF_SIZE - temp; //计算数据长度
HAL_UART_Transmit_DMA(&huart1, rcvBuf, rcvLen);//发送数据
HAL_UART_Receive_DMA(&huart1, rcvBuf, BUF_SIZE);//开启DMA
}
}
ADC
ADC是什么?
全称:Analog-to-Digital Converter,指模拟/数字转换器
ADC的性能指标
量程:能测量的电压范围
分辨率:ADC能辨别的最小模拟量,通常以输出二进制数的位数表示,比如:8、10、12、16位等;位数越多,分辨率越高,一般来说分辨率越高,转化时间越长
转化时间:从转换开始到获得稳定的数字量输出所需要的时间称为转换时间
ADC特性
12位精度下转换速度可高达1MHZ
供电电压:V SSA :0V,V DDA :2.4V~3.6V
ADC输入范围:VREF- ≤ VIN ≤ VREF+
采样时间可配置,采样时间越长, 转换结果相对越准确, 但是转换速度就越慢
ADC 的结果可以左对齐或右对齐方式存储在 16 位数据寄存器中
ADC通道
总共2个ADC(ADC1,ADC2),每个ADC有18个转换通道: 16个外部通道、 2个内部通道(温度
传感器、内部参考电压)
外部的16个通道在转换时又分为规则通道和注入通道,其中规则通道最多有16路,注入通道最多
有4路。
规则组:正常排队的人;
注入组:有特权的人(军人、孕妇)
ADC转换顺序
每个ADC只有一个数据寄存器,16个通道一起共用这个寄存器,所以需要指定规则转换通道的转换顺序。
规则通道中的转换顺序由三个寄存器控制:SQR1、SQR2、SQR3,它们都是32位寄存器。SQR寄存器控制着转换通道的数目和转换顺序,只要在对应的寄存器位SQx中写入相应的通道,这个通道就是第x个转换。
和规则通道转换顺序的控制一样,注入通道的转换也是通过注入寄存器来控制,只不过只有一个
JSQR寄存器来控制,控制关系如下:
注入序列的转换顺序是从JSQx[ 4 : 0 ](x=4-JL[1:0])开始。只有当JL=4的时候,注入通道的转换
顺序才会按照JSQ1、JSQ2、JSQ3、JSQ4的顺序执行。
ADC触发方式
1. 通过向控制寄存器ADC-CR2的ADON位写1来开启转换,写0停止转换。
2. 也可以通过外部事件(如定时器)进行转换。
ADC转化时间
ADC是挂载在APB2总线(PCLK2)上的,经过分频器得到ADC时钟(ADCCLK),最高 14 MHz。
转换时间=采样时间+12.5个周期
12.5个周期是固定的,一般我们设置 PCLK2=72M,经过 ADC 预分频器能分频到最大的时钟只能
是 12M,采样周期设置为 1.5 个周期,算出最短的转换时间为 1.17us。
ADC转化模式
扫描模式
关闭扫描模式:只转换ADC_SQRx或ADC_JSQR选中的第一个通道
打开扫描模式:扫描所有被ADC_SQRx或ADC_JSQR选中的所有通道
单次转换/连续转换
单次转换:只转换一次
连续转换:转换一次之后,立马进行下一次转换
练习:使用ADC读取烟雾传感器的值
烟雾报警器接到PA0口
代码
while (1)
{
HAL_ADC_Start(&hadc1); //启动ADC单次转换
HAL_ADC_PollForConversion(&hadc1, 50); //等待ADC转换完成
smoke_value = HAL_ADC_GetValue(&hadc1); //读取ADC转换数据
printf("smoke_value = %f\r\n", 3.3/4096 * smoke_value);
//printf("smoke_value = %d \r\n", smoke_value);
HAL_Delay(500);
}
IIC
SPI
作者:init_hello