Bootstrap

#单片机基础 笔记二

SPI+中断

1.SPI总线协议

1.1协议介绍

SPI接口是Motorola (motorola | Smartphones, Accessories & Smart Home Devices)首先提出的全双工三线/四线同步串行外围接口采用主从模式(Master Slave)架构。

时钟由Master控制,在时钟移位脉冲下,数据按位传输,高位在前,低位在后(MSB first);SPI接口有2根单向数据线,为全双工通信。

SPI总线被广泛地使用在FLASH、LCD等设备与MCU间,要求通讯速率较高的场合。

1.2 SPI总线拓扑结构

SPI总共有4根总线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。

  1. SCLK:由主机产生时钟信号。
  2. MOSI:主机输出,从机输入。
  3. MISO:主机输入,从机输出。
  4. /ss:片选线低电平有效。

三线结构:片选接地

三线结构:一主一从

四线结构:一主多从,片选线选择和哪个从机之间进行通信

1.3 SPI总线协议

起始信号:NSS的电平信号由高电平变成了低电平,说明主机选择好了从机进行通信

结束信号:NSS的电平信号由低电平变成了高电平。

数据传输:SPI使用MOSI和MISO信号来传输数据,使用SCK信号进行同步。MOSI及MISO数据线在SCK的每个时钟周期传输一位数据高位在前低位在后,且数据输入输出是同时进行的。SPI每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制

1.4 SPI的四种通信模式

在SPI操作中,时钟信号有两项重要的参数设置就是时钟极性(CPOL)和时钟相位(CPHA)这两项即是主从设备间数据采样的约定方式。

时钟极性(CPOL):设置时钟信号空闲时的电平

CPOL = 0,CPOL = 1

时钟相位(CPHA):时钟的采样边沿。

CPHA = 0,CPHA = 1

SPI四种工作模式

  1. 当CPHA = 0时,时钟线的奇数边沿会被采样。

CPOL = 0,SCK在空闲状态时保持低电平。

CPOL = 1,SCK在空闲状态时保持高电平。

     2. 当CPHA = 1时,时钟线的偶数边沿会被采样。

CPOL = 0,SCK在空闲状态时保持低电平。

CPOL = 1,SCK在空闲状态时保持高电平。

注意:只有在相同模式下的两台机器才能进行通信,主机根据从机的模式进行配置

2. LCD液晶显示屏

2.1 液晶的组成

某些物质在熔融状态或被溶剂溶解之后,尽管失去固态物质的刚性,却获得了液体的易流动性,并保留着部分晶态物质分子的各向异性有序排列,形成一种兼有晶体和液体的部分性质的中间态,这种由固态向液态转化过程中存在的取向有序流体称为液晶

物理特点

当通电时导通,排列变得有秩序,使光线容易通过;不通电时排列混乱,阻止光线通过。

2.2液晶显示屏内部构造

2.3 颜色深度

每一个像素点内都有一个RGB三基色

① R,G,B三基色组合形成各种颜色。

② 能显示的颜色数由RGB的数字信号的位数来决定

例如,以3位数字信号来表示颜色深度

RGB24(24位真彩色)

R:8位 G:8位 B:8位

颜色深度:2^8*2^8*2^8;

STM32G030开发版的显示屏RGB16(16位真彩色)RGB565

R:5位 G:6位 B:5位

颜色深度:2^5*2^6*2^5;

3. 点亮LED屏

实验分析

主机---->芯片STM32G030

从机---->LCD屏幕

屏幕的丝印(J5)

原理与分析

实验过程

在主函数里添加头文件

图片显示

制作图片

图片取模

打开取模软件

保存生成.h的文件

字符显示

汉字显示

汉字显示

4. 中断系统

4.1基本概念

中断相当是对于突发事件的处理过程。当遇到内部/外部的紧急事件需要处理时,暂时中止当前程序,转而去处理紧急事件,待处理完毕后,再返回被打断的程序继续向下运行。

4.2 中断存在的意义

首先中断是一个突发事件,在执行主程序的同时能对突发事件做出实时处理,实现程序的并行化(时间片),进而提高CPU的工作效率。如果等待程序执行完毕再去执行中断,会影响操作系统的实时性。

面试题

时间片了解吗?

时间片轮转调度是一种常见的调度算法,其原理是将所有就绪状态的进程按照顺序分配一个时间片,每个进程只在时间片内执行,时间片到就被挂起,以便给其他进程机会。

4.3 中断的处理过程

  1. 程序正常执行---》正在看电影
  2. 中断发生-----》电话响
  3. 处理器会将程序的状态和参数保存到堆栈空间----》暂停电影
  4. 根据中断向量表跳转到中断服务程序-------》找快递站拿快递
  5. 执行中断服务程序-------》取快递
  6. 从中断服务程序返回,继续执行主程序-------》回到家接着看电影。

sp:指向栈顶的指针

1.

京海旧厂街老默正在杀鱼 (正常的执行程序)

2.

小虎找到老默跟他说强哥想吃鱼了 (产生了一个外部中断的信号)

3.

老默放下手里没杀完的鱼 ,把鱼放进了冰箱里 (保护现场,保存程序的当前状态)

4.

去强哥家领任务 (根据异常向量表进行跳转)

5.

做了李有田 (执行中断程序)

6.

回到市场接着杀没杀完的鱼 (栈恢复现场,继续执行主程序

5. NVIC

(Nested Vectored Interrupt Controller) 嵌套中断向量控制器

管理中断事件

每一个中断事件都有执行和禁止两种状态,由NVIC负责将中断事件标记为清除挂起两种状态。 处理器的中断可以是电平的形式的,也可以是脉冲形式的,这样中断控制器就可以处理任何中断源。

支持中断和异常的向量化处理

当中断或异常发生时,处理器会将PC设定到一个指定地址(中断事件入口函数的地址),进而跳转到指定地址进行执行,这个地址就是所谓的异常向量。因为每一个异常源或者中断事件都会对应一个服务程序的入口地址,将这些地址按照优先级进行排布后,组成的一张表就称为异常向量表。

向量:有大小有方向,指明去哪里处理中断

数越小优先级越高,三个固定优先级,复位最高。

面试题

中断和异常的区别

中断和异常指的是当发生某些事件时,处理器改变当前正常执行的程序,转而去响应该事件的情况。

异常:通常是由处理器内部导致的,源于内部事件,大多数上都是一些软件错误

中断:由外部事件引起,通过中断通道将外部信号送入处理器内部,再由CPU进行处理,大多都是由硬件引起的。

无论是中断还是异常,处理器都有相应的中断/异常服务程序。

支持中断的嵌套

要启用中断嵌套,首先需要在NVIC模块中配置中断优先级。每个中断都有一个相应的优先级,中断嵌套使得某个中断能够打断正在执行的较低优先级中断,从而及时响应更高优先级的中断请求。

注意:3个固定的优先级

可改变的优先级:四个可编程优先级,用两个bit位表示,00,01,10,11

注意:

不同优先级的中断同时发生,优先处理中断优先级编号偏小的那个(中断的优先级)

同样优先级的中断同时发生,中断向量号较小的那个优先相应(响应优先级)

范围(0—255)

6. EXTI( 外部中断和事件控制器)

相当于小助理的小助理

NVIC在内部,EXTI在外部

专门用来管理外部中断和事件的。

面试题:

事件和中断的区别?

中断:中断是一个过程,只要发生就能打断CPU正常工作

事件:送给外部设备执行的信号,本质上是IO的电平信号

6.1 主要功能

1. 产生中断

产生中断的目的:是将信号送入NVIC,进而运行中断服务程序,实现对应功能,是软件级。

2. 产生事件

产生事件的目的:是将采集到的一个脉冲信号送到某个外设,进而驱动某些设备做出动作,是电路级别的传输,是硬件级的。

6.2 外部中断处理流程

  1. 信号输入口:EXTI最多可以产生28个外部事件或者中断请求
  2. 边沿检测电路:用于监测上升沿或下降沿信号。
  3. 它会根据上升沿触发选择寄存(EXTI_RTSR)和下降沿触发选择寄存器(EXTI_FTSR)对应位的设置来控制信号触发。

边沿检测电路以输入线作为信号输入端,监测是否有边沿跳变,检测到有边沿跳变输出有效信号 1 给或门,否则输出无效信号0。

  1. 软件中断事件寄存器:对应的位控制软件中断事件,输出0/1
  2. 或门,或1为1
  3. 两个与门,事件/中断屏蔽寄存器掌管信号让不让你过去
  4. 挂起请求寄存器:挂起产生的中断交由内部的NVIC去处理

7. 外部中断实验(按键中断实验)

实验要求

TM32正常执行LED灯闪烁主程序,当检测到按键按下时处理中断事件,通过串口打印“HelloWorld!!!”

实验过程

MX配置

代码编程思路

  1. 主程序实现LED闪烁
  2. 检测按键
  3. 找到相应的异常服务程序执行串口打印字符串。

代码编写

WEAK弱符号

weak 顾名思义是“弱”的意思,所以如果函数名称前面加上__weak 修饰符,我们一般称这个函数为“弱函数”。

加上了__weak 修饰符的函数,用户可以在用户文件中重新定义一个同名函数,最终编译器编译的时候,会选择用户定义的函数,如果用户没有重新定义这个函数,

那么编译器就会执行__weak 修饰的函数,并且编译器不会报错。

主函数

回调函数

8. 内部中断实验

实验要求

主程序发送一句字符串“HelloWorld”,当这条字符串发送完成时触发中断事件,调用中断服务程序发送“HelloHello

实验过程

MX配置

或者

编程思路

  1. 主函数发送Helloworld
  2. 当主函数发送完毕触发中断
  3. 中断回调函数发送hello hello

函数分析

功能:以中断的模式发送数据

参数:句柄 数据 数据的大小

返回值:发送状态

代码实现

找中断回调函数

重写中断回调函数

练习:尝试实现接收完成中断,当接收到4个字符时,产生接收完成中断事件,并在服务程序中输出“Very OK!”

时钟系统

1. 基本概念

时钟是嵌入式系统的脉搏,在电子系统中,时钟信号可以被理解为一个周期性的信号,它确定了系统的节奏和时间基准)处理器内核在时钟的驱动下完成指令执行、状态切换等动作的。外部设备在时钟驱动下完成各项工作。比如:串口通信(异步通信,每一个设备都有自己单独的时钟系统)、AD转换、定时器计数...

2. 时钟系统的组成

振荡器,唤醒定时器,倍频器,分频器

振荡器:产生信号的源头

唤醒定时器:使能,关闭功能

倍频器:放大信号(频率)

分频器:减小频率

时钟的作用:时钟系统在单片机中起着提供时序控制、计时功能、通信同步等多种重要作用,是单片机正常运行的关键组成部分。

2.1 振荡器(信号源)

振荡器分类:晶体振荡器,RC,LC

晶体振荡器

晶体振荡器是一种电子元件,用于在电路中提供准确的时钟信号。它由一个晶体振荡器和相应的驱动电路组成,通常用于数字电路、微控制器、计算机等系统中。

晶体振荡器使用石英晶体作为振荡元件。石英晶体具有压电效应,当施加电场时,会以固定频率产生机械振动。晶体振荡器利用石英晶体的这种特性,通过电子电路驱动晶体振荡,产生稳定的振荡信号。晶体振荡器的频率稳定性非常高。

优点:产生的信号稳定,精度高,连接方式简单。

缺点:价格高,需要一个较长的启动时间。(起振时间)

RC振荡器

RC振荡器使用电阻和电容器构成一个简单的振荡回路。当电容器充放电达到某个阈值时,会产生周期性的振荡信号。RC振荡器通常用于低频、中频等信号的产生和调制。

优点:便宜,电阻和电容构成,起振时间短。

缺点:精度低,受环境因素影响,不够稳定。

LC振荡器

LC振荡器使用电感和电容器构成一个振荡回路。在LC振荡回路中,电感和电容器通过能量交换产生周期性的振荡信号。通常用于射频、高频等电路的信号产生。

优点:稳定性好

缺点:尺寸大,结构复杂。

总结

RC振荡器和LC振荡器是简单的振荡器,适用于一些低要求的应用场景,而晶体振荡器则具有更高的频率稳定性和精度,适用于对时钟信号要求较高的电子系统中。

2.2 倍频器

cpu需要更高的频率,但是晶体振荡器的制作成本较高(而且频率越高,自身的频率也不稳定),不适合直接生产高振荡器,所以需要用到倍频器对现有的时钟频率进行放大。

2.3 分频器

外设需要不同的频率,为了降低功耗,进行分频,以保证不同的时钟信号。

3. G030的时钟源

(英文参考121页)

  1. HSI:高速内部时钟源,由内部的RC振荡器产生的16MHZ频率的时钟信号
  2. HSE:高速外部时钟源,由外部的晶体/陶瓷谐振器产生的4-48MHZ频率的时钟信号
  3. LSI:低速内部时钟源,由内部的RC振荡器产生的32KHZ频率的时钟信号
  4. LSE:低速外部时钟源,由外部的晶体/陶瓷谐振器产生的32.768kHZ频率的时钟信号

时钟树

MX时钟树

配置高速外部时钟

STM32CubeMX时钟树配置

4. Systick 定时器

4.1 概念

SysTick又称滴答定时器。是一个定时设备,位于Cortex-M0+内核中,和NVIC捆绑(可以产生中断信号),产生SysTick异常(IRQ异常号15)可以对输入的时钟进行计数,系统定时器一般用于操作系统,用于产生时基,维持操作系统的心跳。

补充:

频率的单位 f :HZ KHZ MHZ 1s振荡多少次

1MHZ = 1000KHZ = 1000 000HZ

周期:T 单位:s ms us 振荡一次需要的时间

4.2 原理图

4.3 工作原理

滴答定时器本质上就是一个24位递减计数器,也就是最大计数是2^24 - 1(0xFFFFFF)。 在给Systick设定初值后,每当到来一个时钟信号,计数值则减1,直到计数值减到0时,触发一次异常事件,处理异常服务程序,处理完成后计数器再自动重装初值并继续减一,依次循环。

比如现在输入时钟频率是1MHZ的话,定时1ms该怎么定?

算出1MHZ的周期(计数一次所需的时间)1us,那计时这样的时钟信号1000就是1ms,自动重装载寄存器1000-1。这样一设置计数器就会在999开始计数,每来1个时钟周期计数器就减1,直到减到0再来一次就会触发异常,此时计数器已经计数1000次,1000*1us=1ms,正好计时1ms;同时重载数值寄存器会重新给定时器写入1000-1;如此一来实现了1ms触发一次中断;

所谓的定时器,就是对输入的时钟信号进行计数,当达到计数个数的时候触发中断/异常,并重新开始计数,可以说定时器就是计数器!!!

如果说现在给的频率是16MHZ,那我计1ms该计多少个数,如何配置重载数值寄存器?

16MHZ--》1/16us 1us振荡16次--》1ms振荡16000次--》

自动重装载寄存器16000-1

32KZH 计200ms

32KHZ-》1/32ms -->1MS振荡32次--》200ms--》计6400次 自动重载寄存器 6400-1

滴答定时器系统默认开启

追第一个参数

HAL_SYSTICK_Config(SystemCoreClock / (1000U /(uint32_t)uwTickFreq)

16000000 / 1000 / 1 = 16000

时钟频率:16MHZ

重装载寄存器:16000-1

1ms触发一次中断

中断服务程序的执行

每隔1ms让uwtick自加1

修改成1s

5. 定时器

Systick(滴答定时器)是在内核中(ARM公司设计)、定时器(合作商:ST\GD)

5.1 基本概念

定时器本质上是一个计数器,可对输入的时钟进行计数,并在计数值达到设定值时触发中断,当这个计数器的输入是一个准确可靠的基准时钟时,对基准时钟计数的过程就是计时的过程。

5.2 定时器的分类

定时器的基本结构是通用的,很多模块电路都能用到,所以STM32定时上扩展了非常多的功能,根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型。高级定时器通常提供更多的功能和扩展性,例如可以支持编码器模式、PWM输入输出等。而通用定时器通常可以使用多种计数/工作模式,包括输入捕获、输出比较等。基本定时器相对简单,只能进行单一模式的计数。

STM32G030具有多个定时器,包括基本定时器(TIM6/TIM7)、通用定时器

(TIM3/TIM4/TIM14/TIM15/TIM16/TIM17)和高级控制定时器(TIM1)

互补输出:具有两个输出引脚,分别称为主输出和互补输出。主输出引脚和互补输出引脚是成对使用的,用于产生相位差为180度的两个信号。主输出和互补输出引脚的电平变化是互补的,即当主输出引脚上的电平上升时,互补输出引脚上的电平下降,反之亦然。互补输出还可以用于减小电磁干扰和噪声,并提高系统的可靠性。

5.3 时基单元

PSC预分频器

CNT 计数器

ARR 自动重装载寄存器

预分频器

自动重装寄存器

2^8=256 2^16=65536

计数器

向上计数模式:计数器从0开始计数,当达到自动装载寄存器(TIMx_ARR)里的值时,自动清零且产生一个溢出事件,然后再从0开始向上计数。

向下计数模式:计数器从自动装载寄存器(TIMx_ARR)里的值开始递减计数,当计数值达到0时产生一个定时器溢出事件,并重装初值,继续向下计数。

中央对齐模式:又称为向上/向下计数,计数器从0开始递增达到ARR的值,产生一个定时器溢出事件,再从ARR的值递减到0,产生一个定时器溢出事件。

定时器计数原理

【STM32】第16集 动画告诉你, STM32的定时器到底怎么回事_哔哩哔哩_bilibili

5.4 定时器的配置

定时时间(s为单位) = (预分频器值+1)x (目标值(自动重装载寄存器)+1)/时钟频率

32MHZ 计时10ms 需要配置那些寄存器,如何配置?

预分频器:32000-1----》32000分频--》 计数频率1MHZ-》1us-》10ms--》10000-》ARR-》10-1

输入频率 72MHZ 计时500ms 该如何配置?

预分频器:7200-1----》7200分频--》 计数频率1MHZ-》1us-》500ms--》5000-》ARR-》5000-1

16MHZ初始频率,计时700ms如何配置?

预分频:16000-1 --》16000分频 ---》计数器频率:1KHZ---》1ms----》700ms计数700次

---》ARR:700-1

预分频:1600-1---》1600分频---》计数器计数频率:10KHZ-->计一个数:100us----》700ms计数7000次---》ARR:7000-1

6. 定时器实验

实验一

实验要求

利用定时器中断实现1s打印一个“hellowolrd”

MX配置

配置串口

生成工程后,找到TIM14的入口

HAL函数分析

功能:以中断的模式启动定时器

参数:句柄

返回值:状态

实验二

实验要求

使用两个定时器中断实现蓝灯1s间隔闪烁,绿灯2s间隔闪烁

实验过程

MX配置

代码编写

拓展:看门狗(面试会问)

单片机STM32的看门狗(Watchdog)是一种硬件定时器,用于监控系统的运行状态并在出现故障或死锁时采取措施以恢复正常操作。看门狗的主要功能是定期检查系统是否正常运行,并在系统出现问题时触发复位操作。

STM32系列单片机通常配备了内置的看门狗定时器(通常称为独立看门狗,IWDG)和窗口看门狗定时器(WWDG),以提供更可靠的系统保护。

独立看门狗(IWDG)是STM32中常用的看门狗,它是一个独立的硬件模块,可以在系统内部独立运行。通过配置IWDG定时器的计数器和预分频器,可以设置看门狗的定时时间。当看门狗定时器计数器达到预设的值时,会产生看门狗超时事件,触发系统复位。

功能:喂狗

参数:狗名

返回值:状态

头文件别忘了

ADC +PWM

ADC的全称(Analog Digtail Converter)

Analog:模拟信号

模拟信号是指外界的连续变化的物理量所能表达的信息,如温度、湿度、压力、长度、电流、电压等等,我们通常又把模拟信号称为连续信号,它在一定的时间范围内可以有无限多个不同的取值。模拟信号传输过程中,先把信息信号转换成几乎“一模一样”的波动电信号(因此叫“模拟”)(传感器可以将非电学量转换成电学量)

D(Digital)数字信号

数字信号,是指自变量是离散的、因变量也是离散的信号,这种信号的自变量用整数表示,因变量用有限数字中的一个数字来表示。在计算机中,数字信号的大小常用有限位的

C:Converter(转换器)

转换器(converter)是指将一种信号转换成另一种信号的装置。信号是信息存在的形式或载体。在自动化仪表设备和自动控制系统中,常将一种信号转换成另一种与标准量或参考量比较后的信号,以便将两类仪表联接起来,因此,转换器常常是两个仪表(或装置)间的中间环节。最直观的体现,模拟信号是连续变化的曲线,而数字量是不连续的一个个离散的点。

1. ADC

1.1 基本概念

ADC,全称模数转换器(Analog-to-Digital Converter),模拟数字转换器即A/D转换器,ADC的作用就是将连续变化的信号转换为离散的数字信号。通常的模数转换器是将一个输入电压信号转换为一个输出的数字信号。由于数字信号本身不具有实际意义,仅仅表示一个相对大小。故任何一个模数转换器都需要一个参考模拟量作为转换的标准,比较常见的参考标准为最大的可转换信号大小。而输出的数字量则表示输入信号相对于参考信号的大小

例子:电压表

1.2 转换过程

1.3 ADC介绍

三个内部的通道

1.4 ADC的特性

量程:能测量的电压的范围(0—3.6v),单片机的供电范围(1.8—3.6)

分辨率:ADC的分辨率通常以输出二进制数的位数表示,位数越多,分辨率越高,一般来说分辨率越高,转化时间越长。

可配置的精度:12位、10位、8位、6位 (咱们配置的ADC就是12位的)

转换时间:模拟输入电压在允许的最大变化范围内,从转换开始到获得稳定的数字量输出所需要的时间称为转换时间

这三个量息息相关,也就是说配置的精度越高,转换的分辨率越高,但是对应的所需要的转换时间也就越长。

1.5 ADC的时钟

SYSCLK:系统时钟

HSI:高速内部时钟

PLLP:锁相环倍频器(高速内部/外部)

1.6 ADC的工作模式

EOC:通道转换结束的信号

EOS:序列转换结束的信号

1、单次转换模式:ADC只执行一次转换;(CHx:通道 )

  1. 连续转换模式:转换结束之后马上开始新的转换;
  2. 扫描模式:ADC扫描选中的所有通道,在每个组的每个通道上执行单次转换。在每个转换结束时,这一组的下一个通道被自动转换。如果设置了CONT位(开启了连续转换模式),转换不会在选择组的最后一个通道上停止,而是再次从选择组的第一个通道继续转换。
  3. 间断模式:触发一次,转换一个通道,再触发,再转换。在所选转换通道循环,由触发信号启动新一轮的转换,直到转换完成为止。

ADC单通道:

单次转换:只进行一次ADC转换:配置为“单次转换模式”,扫描模式关闭。ADC通道转换一次后,就停止转换。等待再次使能后才会重新转换

连续转换:进行连续ADC转换:配置为“连续转换模式”,扫描模式关闭。ADC通道转换一次后,接着进行下一次转换,不断连续。(没有STOP)

ADC多通道:

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

续转换:进行连续ADC转换:配置为“连续转换模式”,扫描模式使能。ADC的多个通道,按照配置的顺序依次转换一次后,接着进行下一次转换,不断连续。

2. ADC转换实验

单通道单次转换实验

实验要求

采集光照值,并将数值打印到串口

实验过程

  1. 原理图分析

2. MX配置

3.HAL库函数分析

功能:启动ADC,开始转换

参数:句柄

返回值:状态

功能:等待转换结果

参数:句柄 超时时间

返回值:状态

功能:获取转换结果

参数:句柄

返回值:转换完成的数据

功能:停止转换

参数:句柄

返回值:状态

4.代码实现

头文件别忘记加

int fputc(int ch, FILE * p)
{
    while(!(USART1->ISR & 1<<7));
    USART1->TDR=ch;
}

多通道单次转换实验

实验要求

采集光照值和按键的值打印到串口

实验过程

原理图

MX配置

编程实现

代码实现

练习:简易火灾警报装置

ADC 采集火焰值(光敏传感器)

(每个人采集的值可能不一样,自行调整)

一级 亮绿灯 正常没有火 800

二级 亮黄灯 有小火 2000

三级 亮蓝灯 有大火 4000

开启按键中断 按键按下的时候 串口打印“楼层 + 火势情况”

(楼层用五向按键判断 上下左右中分别代表1-5层)

1 1900 2000

2 300 400

3 1350 1550

4 2800 2900

5 2400 2500

提升:

屏幕显示楼层,火势,时间

3. PWM

3.1 定义

PWM,全称为脉冲宽度调制(Pulse Width Modulation),是一种调节信号的方法。简单来说,PWM 就是通过改变信号的高电平和低电平的时间比例来控制输出的平均电压或功率

3.2 应用

应用于惯性系统。

  1. 电机控制:PWM广泛应用于直流电机和无刷电机的速度控制,通过调节PWM信号的占空比来控制电机的平均电压,从而实现对电机速度的精确控制。
  2. 电源调节:PWM技术在开关电源(如DC-DC转换器和开关稳压器)中用于调节输出电压和电流。通过调节PWM信号的占空比,可以有效地控制电源的输出。
  3. LED灯调光:PWM被广泛用于LED照明的亮度调节。通过快速开关LED并调节开关时间(即占空比),可以实现无闪烁的LED调光效果。
  4. 音频信号处理:在某些音频放大器和数字音频系统中,PWM用于模拟音频信号的调制和解调。它可以将模拟音频信号转换为数字形式进行处理。

3.3 PWM参数

周期:高低电平变化所需的时间。

占空比:在一个脉冲周期中,高电平在这个周期内所占的比例(0-100%)。

频率:在一秒内信号的变化的次数,PWM在一秒内有多少个周期。

占空比:

输入的电压是5v的话

占空比%20,输出平均电压 1V

占空比%50,输出平均电压 2.5V

占空比%75,输出平均电压 3.75v

3.4 PWM的工作原理

过调节信号的占空比(duty cycle)来控制电能输送的技术。它的基本原理是通过快速切换电源的开关状态,使负载获得所需的平均功率。

自动重装载寄存器 (TIMx_ARR)-》目标值

计数器寄存器 (TIMx_CNT)-》通常是向上计数

捕获/比较寄存器(TIMx_CCRx)-》占空比(高电平持续的时间)

设定一个比较值,当计数器的值与这个比较值相等时,触发输出动作。

输入捕获:

输入捕获可以用来捕获外部事件,比如引脚的电平变化(上升沿,下降沿),并记录下变化的时间,通常可以用来测量外部信号的频率或者电平持续的时间

输入捕获的详细原理

  1. 计数器(Timer/Counter)定时器内部有一个计数器,它按设定的频率(由时钟源和预分频器决定)递增。例如,如果计数器以1 MHz的频率递增,则每微秒计数器增加1。
  2. 输入捕获引脚(Capture Input Pin)每个输入捕获通道对应一个输入引脚。当外部信号的特定边沿(上升沿或下降沿)出现在这个引脚上时,就会触发输入捕获事件。
  3. 捕获寄存器(Capture Register)当输入捕获事件发生时,当前计数器的值被保存到捕获寄存器中。这意味着捕获寄存器存储了信号边沿到达时计数器的值。
  4. 边沿检测(Edge Detection)输入捕获模块可以配置为在上升沿、下降沿或双边沿触发。不同的应用可能需要不同的边沿检测。例如,测量信号周期可以使用上升沿触发或下降沿触发,而测量脉宽则可以使用双边沿触发。
输出比较:

本质上那个就是一个定时器

此项功能是用来控制一个输出波形,当计数器与捕获/比较寄存器的内容相同时,输出比较功能做出相应动作,比如电平的翻转。通常用于生成PWM波形

输出的过程

当0_t1这段时间,计数器CNT小于CCR,输出高电平。

当t1_t2这段时间,计数器CNT大于CCR,小于ARR,输出低电平。

当CNT大于ARR时产生溢出事件,自动清零,从头开始计时。

占空比:t1/(t1+t2)

4. 实验

实验要求

通过PWM控制LED灯的亮度。

实验过程

  1. 找引脚
  2. MX配置

功能:启动PWM信号生成

参数:句柄 通道

返回值:状态

代码实现

5. 蜂鸣器

简介

蜂鸣器是采用直流电压供电的一个电子讯响器。

分类

有源蜂鸣器

内部带有震荡源(时钟源),一通电就可以震荡发出响声,驱动较容易。

因为是内部集成好的震荡电路,所以频率是固定的。

无源蜂鸣器

内部没有震荡源,直流电无法驱动,所以用一个方波信号来进行驱动,

价格便宜,且频率可控。需要通过编程控制声调和响度,驱动稍麻烦。

蜂鸣器发送实验

查看原理图

MX配置

利用定时器输出PWM信号控制蜂鸣器

蜂鸣器的声调和响度是由什么决定的?

ARR-》周期--》频率

周期越大,频率越小,声调越低

周期越小,频率越大,声调越高

CCR-》脉冲-》占空比

占空比越高-》响度越大

占空比越低-》响度越小

蜂鸣器播放音乐

DMA+DHT11

1. DMA(数据搬运工)

1.1 DMA介绍

DMA,全称为:Direct Memory Access,即直接存储器访问。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。

1.2 DMA的作用

DMA传输数据不需要CPU,可以直接的传输控制

目的:为了给CPU节省资源,提高CPU的工作效率

1.3 DMA主要特性

  1. 在同一个 DMA 模块上,多个请求间的优先权:共有四级:很高、高、中等和低。
  2. 每个通道都有 3 个事件标志 (DMA 半传输、DMA 传输完成和 DMA 传输出错
  3. 数据源,目标源 数据传输宽度对齐。
  4. 传输的宽度(字节(8位),半字(16位),全字(32位))
  5. 存储器-》存储器,外设-》存储器,存储器-》外设,外设-》外设。
  6. 闪存、SRAMAPB AHB 外设均可作为访问的源和目标
  7. 搬移的最大长度是65536个字节。

1.4 DMA传输流程

举例:外设到外

DMA通常不直接支持外设到外设的直接传输(因为外设本身并不具有像内存那样的直接寻址能力),但我们可以将其理解为数据从一个外设的缓存或寄存器先传输到内存,然后再从内存传输到另一个外设的缓存或寄存器。

请求产生:外设A(源设备)在需要传输数据时,产生一个DMA请求信号。这个请求信号被发送到DMA控制器。

请求仲裁:DMA控制器有一个仲裁器,用于决定各个请求源的优先级。如果同时有多个外设请求DMA服务,仲裁器会根据预设的优先级决定先响应哪个请求。

数据传输开始:一旦外设A的请求被仲裁器接受,DMA控制器会开始从外设A的数据缓存或寄存器中读取数据。DMA控制器会设置源地址(外设A的数据缓存或寄存器的地址)和目的地址(内存中用于暂存数据的地址)。

数据传输:DMA控制器通过DMA通道将数据从外设A传输到内存。这个过程完全由DMA控制器控制,无需CPU的干预。

内存中的数据准备传输到外设B:当数据完全传输到内存后,另一个DMA请求可能会由外设B(目标设备)发起,要求将数据从内存传输到其数据缓存或寄存器。同样地,这个请求会经过仲裁器的处理,并由DMA控制器执行。

数据从内存传输到外设B:DMA控制器设置新的源地址(内存中存储数据的地址)和目的地址(外设B的数据缓存或寄存器的地址)。数据通过DMA通道从内存传输到外设B。

传输结束:当所有数据都成功传输到外设B后,DMA控制器会发送一个中断信号给CPU,通知传输完成。CPU可以处理这个中断,进行必要的后续操作或启动新的任务。

1.5 DMA寄存器

1.6 DMA的增量模式和循环模式

增量:外设搬移到存储器的时候 ,不希望覆盖上一个数据,会将内存设置为增量模式

循环:DMA不停循环的搬移数据,一组的数据传输完成时,计数寄存器将会自动地被恢复成配置该通道时设置的初值(外部设备可以循环的读取同一块地址区域)

1.7 DMA中断

每个 DMA 通道都可以在 DMA 传输过半、传输完成和传输错误时产生中断。为应用的灵活性考虑,通过设置寄存器的不同位来打开这些中断

总结:数据从哪来,数据到哪里去,数据的宽度,数据的大小。

注意DMA传输数据只能传输物理内存地址上连续存储的一块数据。

2. DMA实验

实验一

使用DMA通过串口打印ADC采集的光照值

(外设-》内存)

MX配置

启用串口

启用ADC

启用DMA

代码编写

什么时候搬?

ADC转换完成后再用DMA搬运数据

功能:启动ADC开始转换,并且将结果搬移

参数:句柄 数据存放的地址 数据长度

ADC的启动码函数

注意:此时CPU并未参与ADC转换完成的数据读取工作,节省了CPU的资源。

按键发送

每按下一次按键发送一次采集的值,要求使用高速外部时钟源

MX配置

代码编写

实验二

实验要求

采集按键和光照的值

实验三

DMA不定长接收

不定长接受的含义

DMA(Direct Memory Access,直接存储器访问)不定长接收的含义是指在使用DMA进行数据传输时,接收数据的长度不是预先固定的,而是根据实际接收到的数据量动态调整。这种方式特别适合处理长度不确定的连续数据流,如串口通信、网络数据接收等。

不定长接收依靠串口空闲中断,就是当RX引脚无后续的输入时,串口从忙碌状态转为空闲状态。

寄存器使用

MX配置

可能会用到的函数

1)HAL_UART_Receive_DMA(UART_HandleTypeDef * huart, 
uint8_t * pData, uint16_t Size) //开启DMA通道并设定通道长度
2)__HAL_UART_ENABLE_IT(__HANDLE__,__INTERRUPT__)//开启串口空闲中断
3)__HAL_UART_GET_FLAG(__HANDLE__,__FLAG__)//获得串口空闲中断标志
4)__HAL_UART_CLEAR_FLAG(__HANDLE__,__FLAG__) //清除串口空闲中断
5)HAL_UART_DMAStop(UART_HandleTypeDef * huart) //关闭串口DMA通道
6)设定的传输长度-剩余传输数量(DMA_CNDTRx)=实际长度
7)HAL_UART_Transmit_DMA(UART_HandleTypeDef * huart, uint8_t * pData, uint16_t Size)  //使用DMA通道发送指定长度的字符到串口中
8)HAL_UART_Receive_DMA(UART_HandleTypeDef * huart, uint8_t * pData, uint16_t Size)  //再次开启DMA通道并设定通道长度

代码编写

3. DHT11

3.1 DHT11概述

DHT11是一款有已校准数字信号输出的温湿度传感器。 其精度湿度±5%RH, 温度±2℃,量程湿度20-90%RH, 温度0~50℃。

DHT11温湿度传感器类似于DS18B20采用一线制通信协议(单总线),所谓“一线制”顾名思义,设备与上位控制器通信使用1根线,这根线同时承担了时钟和数据线的角色。   

硬件上的简单势必会带来软件上的复杂,像一线制通信协议,一般都是上位CPU先发开始、复位等电平信号,然后DHT11发送回应信号,然后再发送对应数据,上位CPU接收电平脉冲信号,连续接收固定的字节,然后再进行解析数据。

3.2 协议分析

主机先要发送一个至少18ms的低电平,在这个过程中,DHT11内部完成AD转换等操作,当主机拉高后,有20-40us时间,这个时间用于主机做输入输出切换,当主机释放总线控制权(此时主机为输入状态,总线被上拉电阻拉高),DHT11尝试将总线拉低,成功拉低后就开始准备发送数据了,再拉高一次就开始传输数据了。

40位:8湿度整 8湿度小 8温度整 8温度小 8校验和

  1. 读取一位 (函数)
  2. 循环8次(读取一位)
  3. 循环5次第二步的函数
  4. 检验和 = 8湿度整+8湿度小+8温度整+8温度小

3.3 原理图分析

3.4 MX配置

3.5 源码分析

//设置IO为输入模式
static void DHT11_IO_IN(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    GPIO_InitStruct.Pin = DHT11_GPIO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStruct);
}
//设置IO为输出模式
static void DHT11_IO_OUT(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    GPIO_InitStruct.Pin = DHT11_GPIO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStruct);
}

//复位DHT11      \\起始信号
void DHT11_Rst(void)
{
    DHT11_IO_OUT(); 	//SET OUTPUT  转换成输出模式
    HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_RESET); 	
     //拉低DQ
    HAL_Delay(20);    	//拉低至少18ms
    HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_SET);		      
     //拉高DQ
    delay_us(30);     	//主机拉高20~40us
}

//等待DHT11的回应
//返回1:未检测到DHT11的存在
//返回0:存在
uint8_t DHT11_Check(void)
{
    uint8_t retry=0;
    DHT11_IO_IN();//SET INPUT
    while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us
    {
        retry++;
        delay_us(1);
    };
    if(retry>=100)return 1;
    else retry=0;
    while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us
    {
        retry++;
        delay_us(1);
    };
    if(retry>=100)return 1;
    return 0;
}

//从DHT11读取一个位
//返回值:1/0
uint8_t DHT11_Read_Bit(void)
{
    uint8_t retry=0;
    while(DHT11_DQ_IN&&retry<100)//等待变为低电平
    {
        retry++;
        delay_us(1);
    }//延时100
    retry=0;
    while(!DHT11_DQ_IN&&retry<100)//等待变高电平
    {
        retry++;
        delay_us(1);
    }
    delay_us(40);//等待40us
    if(DHT11_DQ_IN)return 1;
    else return 0;
}

//从DHT11读取一个字节
//返回值:读到的数据
uint8_t DHT11_Read_Byte(void)
{
    uint8_t i,dat;
    dat=0;
    for (i=0; i<8; i++)
    {
        dat<<=1;
        dat|=DHT11_Read_Bit();
    }
    return dat;
}

//从DHT11读取一次数据
//temp:温度值(范围:0~50°)
//humi:湿度值(范围:20%~90%)
//返回值:HAL_OK,正常;1,读取失败
uint8_t DHT11_Read_Data(uint8_t *humiH,uint8_t *humiL,uint8_t *tempH,uint8_t *tempL)
{
    uint8_t buf[5];
    uint8_t i;
    DHT11_Rst();
    if(DHT11_Check()==0)
    {
        for(i=0; i<5; i++) //读取40位数据
        {
            buf[i]=DHT11_Read_Byte();
        }
        if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])
        {
            *humiH=buf[0];     //湿度高八位,整数位
            *humiL=buf[1];      //湿度低八位,小数位
            *tempH=buf[2];     //温度高八位,整数位
            *tempL=buf[3];      //温度低八位,小数位
        }
    } else
        return HAL_ERROR;
    return HAL_OK;
}

//初始化DHT11的IO口 DQ 同时检测DHT11的存在
//返回1:不存在
//返回0:存在
uint8_t FS_DHT11_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    GPIO_InitStruct.Pin = DHT11_GPIO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStruct);
    HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_SET);	
    // 输出高电平
    DHT11_Rst();  //复位DHT11
    return DHT11_Check();//等待DHT11的回应
}

3.6 延时函数分析

void delay_us(uint32_t nus)  // 延时函数,延时的时间以微秒为单位
{
  uint32_t ticks;            // 需要的节拍数
  uint32_t told, tnow, tcnt = 0;  // told: 进入函数时的SysTick值; tnow: 当前的SysTick值; tcnt: 累计的节拍数
  uint32_t reload = SysTick->LOAD; // 记录SysTick计数器的重装载值

  ticks = nus * fac_us;  // 计算需要的节拍数,fac_us是系统时钟的频率因子,具体值由初始化函数delay_init设置
  told = SysTick->VAL;  // 记录当前SysTick计数器的值(SysTick是一个递减计数器)

  while (1)
  {
    tnow = SysTick->VAL;  // 获取当前SysTick计数器的值
    if (tnow != told)     // 如果当前SysTick值与上一次记录的值不同
    {
      if (tnow < told)    // 如果当前值小于之前记录的值,说明没有发生重装载
        tcnt += told - tnow;  // 计算这一段时间内经过的节拍数
      else                // 如果当前值大于之前记录的值,说明发生了重装载
        tcnt += reload - tnow + told;  // 计算重装载期间经过的节拍数
      told = tnow;  // 更新记录的SysTick值
      if (tcnt >= ticks)  // 如果累计的节拍数达到或超过需要的节拍数
        break;  // 退出循环,延时结束
    }
  };
}


 

;