1.IDE选择

  • keil + STM32CubeMX

  • Clion + STM32CubeMX/PlatformIO(插件)

配置CLion用于STM32开发【优雅の嵌入式开发】 - 知乎 (zhihu.com)

2.时钟RCC

【STM32】系统时钟RCC详解(超详细,超全面)_rcc时钟_Z小旋的博客-CSDN博客

3.STM32-GPIO介绍

STM32-GPIO介绍_stm32gpio_KevinFlyn的博客-CSDN博客

HAL库 GPIO函数库

【STM32】HAL库 STM32CubeMX教程三----外部中断(HAL库GPIO讲解)_HAL_GPIO_EXTI_callback_Z小旋的博客-CSDN博客

单片机引脚的上拉、下拉和三态

**上拉:**上拉是指单片机的引脚通过电阻接VCC,这样可以把这个引脚的电平固定为高电平。

**下拉:**下拉的情况和上拉的相反,是为了把引脚固定为低电平,要有下拉电阻也是为了防止误配置导致烧掉芯片。

**三态:**三态又称为高阻态,简单理解就是电平的高低由这根线上的外部电路决定,当外部电路为高电平的时候,它也是高电平;当外部电路为低电平的时候,它也是低电平;当外部电路为高阻态的时候,它就是高阻态的,状态完全和外部电路一样。

上升沿和下降沿

**电平:**简单理解就是电压,但却不相同。

**上升沿触发:**数字电路中,数字电平从低电平(数字“0”代表低电平)变为高电平(数字“1”代表高电平)的那一瞬间叫作上升沿。上升沿触发是当信号有上升沿时的开关动作,当电位由低变高而触发输出变化的就叫上升沿触发。也就是当测到的信号电位是从低到高也就是上升时就触发,叫做上升沿触发。

**下降沿触发:**数字电路中,数字电平从高电平(数字“1”)变为低电平(数字“0”)的那一瞬间叫作下降沿。 下降沿触发是当信号有下降沿时的开关动作,当电位由高变低而触发输出变化的就叫下降沿触发。也就是当测到的信号电位是从高到低也就是下降时就触发,叫做下降沿触发。

定时器

定时器的介绍

STM32G070RBT6定时器资源

STM32G070RBT6拥有一个PWM高级定时器,五个16位通用定时器,两个基础定时器,两个看门狗定时器,一个SysTick定时器,共有11个定时器。

基本定时器 TIM6 和 TIM7 是一个 16 位的只能向上计数的定时器,只能定时,没有外部 IO。通用定时器 TIM14/15/16/17 是 16 位的只能向上计数的定时器,可以定时,可以输出比较,可以输入捕获,其中TIM15拥有外部输入引脚,和两个输出通道,TIM14/16/17都仅只有一个通道。高级定时器 TIM1 是一个 16 位的可以向上/下计数的定时器,可以定时,可以输出比较,可以输入捕捉,还可以有三相电机互补输出信号,有 8 个外部 IO。

基本定时器功能框图讲解

基本定时器的核心是时基,不仅基本定时器有,通用定时器和高级定时器也有。学习定时器时,我们先从简单的基本定时器学起。

时钟源

定时器时钟 TIMxCLK,即内部时钟 CK_INT,经 APB 预分频器后分频提供,如果APB1 预分频系数等于 1,则频率不变,否则频率乘以 2,库函数中 APB 预分频的系数是 1,即TPCLK=64M,所以定时器TIMxCLK=64MHz。

计数器时钟

定时器时钟经过 PSC 预分频器之后,即计数器时钟 CK_CNT,用来驱动计数器计数。PSC 是一个16 位的预分频器,可以对定时器时钟 TIMxCLK 进行 1~65536 之间的任何一个数进行分频。具体计算方式为:CK_CNT=TIMxCLK/(PSC+1)。

计数器

计数器 CNT 是一个 16 位的计数器,只能往上计数,最大计数值为 65535。当计数达到自动重装载寄存器的时候产生更新事件,并清零从头开始计数。

自动重装载寄存器

自动重装载寄存器 ARR 是一个 16 位的寄存器,这里面装着计数器能计数的最大数值。当计数到这个值的时候,如果使能了中断的话,定时器就产生溢出中断。

定时器时间的计算

定时器的定时时间等于计数器的中断周期乘以中断的次数。计数器在 CK_CNT 的驱动下,计一个数的时间则是 CK_CNT 的倒数,等于:1/(TIMxCLK/(PSC+1)),产生一次中断的时间则等于:(1/CK_CNT) * (ARR+1)。如果在中断服务程序里面设置一个变量 time,用来记录中断的次数,那么就可以计算出我们需要的定时时间等于:(1/CK_CNT) * (ARR+1) * time。

实验内容

Ⅰ.定时器控制小灯每秒闪烁一次

实验原理

定时器溢出时间(us)= (定时器预分频值+1)*(定时器自动重装载值+1)/(定时器输入时钟频率(MHz))

通过配置基础定时器TIM7的 Psc (预分频值)与 Arr (自动重装载值),使得其溢出时间为1 s;在定时器中断回调函数中调用GPIO状态翻转函数控制LED的引脚电平翻转,从而实现小灯每秒闪烁一次。

STM32CubeMX的配置

【STM32】HAL库 STM32CubeMX教程七—PWM输出(呼吸灯)_stm32cubemx pwm呼吸灯_Z小旋的博客-CSDN博客

Keil工程代码讲解

引用或定义的函数

需要引用的函数:
1
2
3
4
5
/* 函数原型 */
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);

/* 函数引用举例 */
HAL_TIM_Base_Start_IT(&htim7); //以中断工作方式启动定时器TIM7,发生UEV事件时产生中断
1
2
3
4
5
/* 函数原型 */
void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);

/* 函数引用举例 */
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); //控制PC13翻转电平

需要定义的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 函数原型 */
__weak void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);

/* 函数重定义举例 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* 判断进入中断定时器是否为TIM7 */
if (htim == &htim7){
/* 控制PC13引脚翻转电平 */
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}
}

拓展:

中断(Interrupt):是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。

UEV事件:全称——update event,中文为更新事件,或者说事件更新发生,是指这个事件发生后,将会将定时器的寄存器进行更新,以使定时器工作在新的配置下,例如当一个定时周期结束(计数器上溢)或者其他事件。

轮询(Polling):是一种CPU决策如何提供周边设备服务的方式,又称“程控输入输出”(Programmed I/O)。轮询法的概念是:由CPU定时发出询问,依序询问每一个周边设备是否需要其服务,有即给予服务,服务结束后再问下一个周边,接着不断周而复始。

控制基础定时器启动的HAL库函数

1
2
3
HAL_TIM_Base_Start(&htimx); // 以轮询工作方式启动定时器,不会产生中断。
HAL_TIM_Base_Start_IT(&htimx); // 以中断工作方式启动定时器,发生UEV事件时产生中断
HAL_TIM_Base_Start_DMA(&htimx); // 以DMA工作方式启动定时器

GPIO引脚相关的部分HAL库函数

1
2
3
4
HAL_GPIO_WritePin(GPIOX, GPIO_PIN_XX, GPIO_PIN_RESET); // 控制PX_XX引脚输出低电平
HAL_GPIO_WritePin(GPIOX, GPIO_PIN_XX, GPIO_PIN_SET); // 控制PX_XX引脚输出高电平
HAL_GPIO_TogglePin(GPIOX, GPIO_PIN_XX); // 控制PX_XX引脚翻转电平
HAL_GPIO_ReadPin(GPIOX, GPIO_PIN_XX); // 读取PX_XX引脚当前状态

__weak修饰符:在 HAL 库中,很多回调函数前面使用__weak 修饰符。例如本次实验所用到的定时器中断回调函数:void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) 其函数原型为:

1
__weak void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);

加上了__weak 修饰符的函数,用户可以在用户文件中重新定义一个同名函数,最终编译器编译的时候,会选择用户定义的函数,如果用户没有重新定义这个函数,那么编译器就会执行 weak 声明的函数,并且编译器不会报错。

在main.c文件中在所指示的位置添加以下代码

1
2
/*以中断模式开启定时器7 */
HAL_TIM_Base_Start_IT(&htim7);
1
2
3
4
5
6
7
8
9
/* 定时器中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* 判断进入中断定时器是否为TIM7 */
if (htim == &htim7){
/* 控制连接LED的引脚翻转电平 */
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}
}

在单片机上观察实验现象

Ⅱ.软件PWM实现控制蜂鸣器响度

实验原理

(1)PWM

脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。

以单片机为例,我们知道,单片机的IO口输出的是数字信号IO口只能输出高电平和低电平

假设高电平为5V 低电平则为0V 那么我们要输出不同的模拟电压,就要用到PWM,通过改变IO口输出的方波的占空比从而获得使用数字信号模拟成的模拟电压信号

我们知道,电压是以一种连接1或断开0的重复脉冲序列被夹到模拟负载上去的(例如LED灯,直流电机等),连接即是直流供电输出,断开即是直流供电断开。通过对连接和断开时间的控制,理论上来讲,可以输出任意不大于最大电压值(即0~5V之间任意大小)的模拟电压

比方说 占空比为50% 那就是高电平时间一半,低电平时间一半,在一定的频率下,就可以得到模拟的2.5V输出电压 那么75%的占空比 得到的电压就是3.75V

pwm的调节作用来源于对“占周期”的宽度控制,“占周期”变宽,输出的能量就会提高,通过阻容变换电路所得到的平均电压值也会上升,“占周期”变窄,输出的电压信号的电压平均值就会降低,通过阻容变换电路所得到的平均电压值也会下降

也就是,在一定的频率下,通过不同的占空比即可得到不同的输出模拟电压

pwm就是通过这种原理实现D/A转换的。

  • 占空比是指在一个脉冲循环内,通电时间相对于总时间所占的比例。 占空比(Duty Ratio)在电信领域中有如下含义:例如:脉冲宽度1μs,信号周期4μs的脉冲序列占空比为0.25
(2)PWM控制蜂鸣器响度的原理

蜂鸣器音量的大小与电压大小成正比,因此通过调节软件pwm的占空比,来得到不同的输出模拟电压,即可控制蜂鸣器的响度。

STM32CubeMX的配置

Step1:配置RCC

Step2:配置SYS

Step3:配置基础定时器TIM7

要实现通过调节软件PWM的占空比模拟不同的输出电压,PWM需有较高频率。

所以此处设Psc = 31, Arr = 99;

Step4:查看开发板原理图查找到蜂鸣器BEEP引脚 -> PC12

如果使用的是其他开发板,则根据所使用开发板的原理图寻找到接BEEP(蜂鸣器)的引脚,若该开发板无蜂鸣器,可寻找一个引出的引脚,准备一个蜂鸣器模块/蜂鸣器,按照蜂鸣器的正负极手动将蜂鸣器的一端与所选引脚相连,另一端与开发板上的GND相连

Step5:配置BEEP引脚PC12为GPIO_Output(输出)模式

Step6:配置时钟树,主频率设为64HMz

Step7:配置工程属性

Step8:生成Keil工程

在main.c文件中在所指示的位置添加以下代码
1
2
3
4
/* 所定义的全局变量 */
uint8_t counter = 0; //计数器
uint8_t pwmrate = 20; //软件PWM的占空比
_Bool sw = 0; //用于控制pwmrate的自增或自减【_Bool为布尔类型变量,只有1位即0和1】
1
2
/* 以中断模式开启定时器7 */
HAL_TIM_Base_Start_IT(&htim7);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* 定时器中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* 软件PWM */
if (counter <= 100)
{
counter++;
/* 当计数值小于等于pwmrate时PC12为1,反之为0 */
/* 占空比为 pwmrate/100%,即pamrate */
if(counter <= pwmrate)
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12, GPIO_PIN_SET);
else
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12, GPIO_PIN_RESET);
}
/* 当counter值超过100归零 */
else{
counter = 0;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 当pwmrate即占空比小于100,且sw为0时,占空比逐渐增加,直至100 */
if (pwmrate < 100 && sw == 0)
pwmrate++;
/* 当pwmrate即占空比大于或等于100时,sw置为1 */
else
sw = 1;

/* 当pwmrate即占空比大于20,且sw为1时,占空比逐渐减小,直至20 */
if (pwmrate > 20 && sw == 1)
pwmrate--;
/* 当pwmrate即占空比小于或等于20时,sw置0 */
else
sw = 0;

/* 延时20ms,使现象可以被人观测到 */
HAL_Delay(20);
在单片机上观察实验现象
拓展:HAL库中的部分重定义变量类型
1
2
3
4
5
6
7
8
9
10
11
>/* 位于“stdint.h”文件中 */
>/* exact-width signed integer types */
>typedef signed char int8_t;
>typedef signed short int int16_t;
>typedef signed int int32_t;
>typedef signed __INT64 int64_t;
>/* exact-width unsigned integer types */
>typedef unsigned char uint8_t;
>typedef unsigned short int uint16_t;
>typedef unsigned int uint32_t;
>typedef unsigned __INT64 uint64_t;

常用的类型有:

1
2
3
4
5
>typedef   signed short     int int16_t;
>typedef signed int int32_t;
>typedef unsigned char uint8_t;
>typedef unsigned short int uint16_t;
>typedef unsigned int uint32_t;

参考资料

【来源于网络】STM32定时器详细篇(基于HAL库)

【来源于网络】PWM原理 PWM频率与占空比详解-z小旋

【来源于网络】【STM32】HAL库 STM32CubeMX教程六----定时器中断-z小旋

【来源于网络】【STM32】HAL库 STM32CubeMX教程七—PWM输出(呼吸灯) -z小旋

4.STM32串口通信、LCD的使用及SPI、IIC

【STM32】HAL库 STM32CubeMX教程四—UART串口通信详解_hal_uart_transmit_Z小旋的博客-CSDN博客

串口(USART)、SPI、IIC的介绍(文末附相关博客)

STM32G070RBT6———串口

1.通信方式

STM32G070RBT6拥有四个串口,每个串口都拥有多种不同的工作式,大致可以按照如下分类:

第一分类为并行通信和串行通信,而由于串行通信的优点以及对缺点的弥补,导致如今基本都采用串行通信

对于串行通信,又按照是否有同步时钟和收发间的数据传输方向分成两大类;

 其中,按照是否有同步时钟分成同步和异步:

同步通信:带时钟同步信号,发送方和接收方在同一时钟的控制下,实现同步传输。

异步通信:不带时钟同步信号,使用各自的时钟控制。 但需要双方相互约定好数据传输速率。

 传输速率的衡量方式——波特率—单位:bps(位/秒)

 按照数据传输方向分成单工、半双工和全工:

单工通信:数据只沿着一个方向传输,发送端和接受端固定,只需要一根数据线。

半双工通信:数据可以沿着两个方向传输,但不能同时进行,需要两根数据线。

全双工通信:数据可以沿着两个方向传输,可以同时进行,需要两根数据线。

大纲图:https://i.postimg.cc/TYmzmtBG/20210720113312701.png

2.UART协议

  • USART-通用同步异步收发器(Universal Synchronous Asynchronous Receiver and Transmitter)是一个串行通信设备,可以灵活地与外部设备进行全双工数据交换。
  • UART-通用异步收发器(Universal Asynchronous Receiver and Transmitter),它是在 USART 基础上裁剪掉了同步通信功能(时钟同步),只有异步通信。简单区分同步和异步就是看通信时需不需要对外提供时钟输出,我们平时用的串口通信基本都是 UART。

UART-全双工

STM32串口通信详解_rivencode的博客-CSDN博客

STM32G070RBT6———SPI通讯协议

SPI-全双工

SPI协议详解(图文并茂+超详细)_小麦大叔的博客-CSDN博客

STM32G070RBT6———IIC(I2C)

IIC-半双工

常用通信协议——IIC详解(全网最全)_iic通信协议_阿波罗啦啦啦啦的博客-CSDN博客

printf的重定向问题:

配置CLion用于STM32开发【优雅の嵌入式开发】 - 知乎 (zhihu.com)

LCD显示

见第四次培训

5.STM32ADC 外设

【STM32】HAL库 STM32CubeMX教程九—ADC_stm32cubemx adc_Z小旋的博客-CSDN博客

ADC 介绍

什么是 ADC?

Analog-to-Digital Converter 的缩写。指模/数转换器或者模拟/数字转换器。是指将连续

变量的模拟信号转换为离散的数字信号的器件。典型的模拟数字转换器将模拟信号转换为

表示一定比例电压值的数字信号。简单地说就是将模拟电压值,转换成对应的肉眼可读数

值 12 位 ADC 是一种逐次逼近型模拟数字转换器。A/D 转换可以单次、连续、扫描或间断

模式执行。ADC 的结果可以左对齐或右对齐方式存储在 16 位数据寄存器中

“输入的模拟量”是输入电压信号,“START”用来控制 ADC 启动转换,“CLOCK”是 ADC 模块的输入时钟,“EOC”是 ADC 转换结束信号,

“OE”是 ADC 转换结果输出允许信号,“VREF”是参考电压。

ADC 的转换模式 (重要,请务必看懂)

单次转换模式:ADC 只执行一次转换;

连续转换模式:转换结束之后马上开始新的转换;

扫描模式:ADC 扫描被规则通道和注入通道选中的所有通道,在每个组的每个通道上执行单次转换。

在每个转换结束时,这一组的下一个通道被自动转换。如果设置了 CONT 位

(开启了连续 转换模式),转换不会在选择组的最后一个通道上停止,而是再次从选择组

的第一个通道继续转换。

间断模式:触发一次,转换一个通道,再触发,再转换。在所选转换通道循环,由触发信号启动新一轮的转换,直到转换完成为止。(扫描模式简单的说是一次对所有所选中的通道进行转换,

比如开了 ch0,ch1,ch4,ch5。 ch0 转换完以后就会自动转换通道 1,4,5

直到转换完这个过程不能被打断。如果开启了连续转换模式,则会在转换完 ch5 之后开始

新一轮的转换。这就引入了间断模式,可以说是对扫描模式的一种补充。它可以把 0,1,4,5

这四个通道进行分组。可以分成 0,1 一组,4,5 一组。也可以每个通道单独配置为一组。

这样每一组转换之前都需要先触发一次。)

ADC 单通道:

只进行一次 ADC 转换:配置为“单次转换模式”,扫描模式关闭。ADC 通道转换一次后,

就停止转换。等待再次使能后才会重新转换

进行连续 ADC 转换:配置为“连续转换模式”,扫描模式关闭。ADC 通道转换一次后,

接着进行下一次转换,不断连续。

ADC 多通道:

只进行一次 ADC 转换:配置为“单次转换模式”,扫描模式使能。ADC 的多个通道,按照配置的顺序依次转换一次后,就停止转换。等待再次使能后才会重新转换

进行连续 ADC 转换:配置为“连续转换模式”,扫描模式使能。ADC 的多个通道,按照

配置的顺序依次转换一次后,接着进行下一次转换,不断连续。

也就是:多通道必须使能扫描模式

左对齐或右对齐

因为 ADC 得到的数据是 12 位精度的,但是数据存储在 16 位,所以 ADC 的存储结果可

以分为左对齐或右对齐两种方式

注入通道,规则通道:

注入通道:

程序正常运行的通道

规则通道:

注入通道可以打断规则通道,如果在规则通道转换过程中,有注入通道进行转换,那么就

要先转换完注入通道,等注入通道转换完成后,再回到规则通道的转换流程(类似于中断)

ADC 时钟

ADC 模块的时钟来源是 ADC 预分频器的 ADCCLK

RCC 控制器为 ADC 时钟提供一个专用的可编程预分频器。 分频因子由 RCC_CFGR 的

ADCPRE[1:0]配置,可配置 2/4/6/8 分频

STM32 的 ADC 最大的转换速率为 1MHz,也就是说最快转换时间为 1us,为了保证 ADC转换结果的准确性,ADC 的时钟最好不超过 14M

ADC 工作流程

ADC 一般用于采集小电压,其输入值不能超过 VDDA,即 ADC 输入范围:VREF- ≤ VIN ≤ VREF+. 一般把VSSA 和VREF-接地,VREF+ 和VDDA 接 3V3,那么ADC的输入范围是0~3.3V!超过会烧坏单片机!!

ADC 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
HAL_ADC_Start(&hadcx); //轮询模式开启 ADC
HAL_ADC_Start_IT(&hadcx); //中断轮询模式开启 ADC
HAL_ADC_Start_DMA(&hadcx); //DMA 模式开启 ADC
HAL_ADC_Stop() //轮询模式关闭 ADC
HAL_ADC_Stop_IT() //中断轮询模式关闭 ADC
HAL_ADC_Stop_DMA() //DMA 模式关闭 ADC
HAL_ADCEx_Calibration_Start(&hadcx); //ADC 校准函数
HAL_ADC_GetValue() //读取 ADC 转换值
HAL_ADC_PollForConversion(&hadc1, 50); //等待转换结束函数,50 为等待时间(ms)
HAL_ADC_ConvCpltCallback() //ADC 中断回调函数,转换完成后回调,DMA 模式下
DMA 传输完成后调用
HAL_ADC_ConfigChannel() //配置规则组通道
•HAL_ADC_AnalogWDGConfig() //看门狗配置

DMA 的基本介绍

什么是 DMA (DMA 的基本定义)

DMA,全称 Direct Memory Access,即直接存储器访问。

DMA 传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者

存储器和存储器之间的高速数据传输。

我们知道 CPU 有转移数据、计算、控制程序转移等很多功能,系统运作的核心就是 CPU,CPU 无时不刻的在处理着大量的事务,但有些事情却没有那么重要,比方说数据的复制和存储数据,如果我们把这部分的 CPU 资源拿出来,让 CPU 去处理其他的复杂计算事务,是不是能够更好的利用 CPU 的资源呢?

**因此:转移数据(尤其是转移大量数据)是可以不需要 CPU 参与。**比如希望外设 A 的数据拷贝到外设 B,只要给两种外设提供一条数据通路,直接让数据由 A 拷贝到 B 不经过CPU 的处理。

数据传输之间有一个独立的DMA通道

ADC 使用 DMA 相关函数:

1
HAL_ADC_Start_DMA(ADC_HandleTypeDef *hadc, uint32_t* pData, uint32_t Length);

实验

1.使用 ADC 查询模式,采集电阻分压值,显示在 LCD 上,同时控制 RGB 灯

2.查看开发板硬件原理图找到 ADC 采集

3.开启 ADC 外设,使能 IN0 端口

4.设置端口

5.设置 GPIO 输出

6.设置 SPI,及 LCD 端口*

7.设置时钟

8.设置储存位置及编译器

9.设置添加头文件,并生成代码

10.代码编写及注释