GPIO

GPIO引脚分布

彩色引脚具有特定的功能,用户无法通过编程来控制它们

VDD接电源正极VSS接地

VBAT用来接备用电池,STM32通常使用纽扣电池作为芯片的备用电源,当主电源断电之后,备用电池可以继续为芯片的Backup-Domain供电,所以芯片的一部分功能仍然可以正常运转

NRST是芯片的复位引脚,一般我们会在这个引脚上接一枚按钮,按下这个按钮,芯片复位

BOOT0启动模式选择,通过这个引脚选择芯片的启动模式

剩下蓝灰色的引脚都是普通IO引脚,这些引脚都是可编程的,给这些引脚分组,按照字母命名组号,GPIOA GPIOB GPIOC GPIOD4个组,每组最多有16个引脚,编号从0开始

IO复用和复用功能重映射

IO复用:同一个IO引脚具备多个不同的功能,既可以被用户直接编程,也可以给芯片上的其他模块使用

以PA9为例,向其写0,输出低电平;向其写1,输出高电平,这种直接控制引脚的方式叫做通用功能。

USART1是芯片内部的串口模块,通过PA9向外发送数据,比如想通过USART1发送数字100,那么我们把100写入到USART1,然后它就会通过PA9发送一段特定的波形从而把数据发送出去,此时PA9被USART1托管了,这时就不能直接控制PA9输出高电平和低电平了,我们把这种使用IO的方式叫做复用功能。

复用功能重映射:一般单个引脚可以同时兼具多种复用功能,复用功能重映射就是把冲突的复用功能移动到备用引脚上去

定时器1和串口1都是芯片内部的模块,它们所占用的引脚有一些是重复的,如果我们想同时使用定时器1和串口1,可以把USART1重映射到PB6和PB7

芯片上给大多数模块都留有一组或多组备用引脚,USART1的备用引脚在PB6和PB7

4种输出模式

这两个MOS管可以等效为一对开关

推挽模式下,如果向IO写0,那么P-MOS关断,N-MOS 导通,IO引脚通过下方的N-MOS与下方的低电压VSS相连,引脚上输出的就是低电压

IO最大输出速度

把电压升高所消耗的时间称为上升时间,把电压降落所消耗的时间称为下降时间,中间输出有效电平的时间称为保持时间,加快电压的切换速度,发现上升沿和下降沿的斜坡越靠越近,中间的保持时间越来越短,如果再加快切换速度,会发现上升沿和下降沿完全重合,此时已经不能输出有效的高低电压了。如果使用更快的上升时间和下降时间,也就是让两个斜坡更陡,这样就可以继续输出有效的高低电压了。可以看出,上升时间和下降时间限制了IO的最大输出速度,上升时间和下降时间越短,最大输出速度就越大。

三种模式下IO的最大输出速度

2MHZ------每秒钟切换200万次   10MHZ-----每秒钟切换1000万次

 50MHZ-----每秒钟切换5000万次(注:M,即百万,10^6)

实际编程过程中,如何选择最大输出速度?原则是选取满足要求的最小值,过快的上升沿和下降沿会增加芯片损耗,同时会使数字信号的带宽增加,从而对电路板上的其他元器件产生电磁干扰。

1>使用单片机的IO引脚去驱动发光二极管,因为人眼能够分辨的闪烁频率大概是每秒钟10次,所以驱动发光二极管的频率也不宜太高,一般不会超过1000HZ,所以选择低速,也就是最大2MHZ的输出速度

2>英飞凌的TLE5012B是一款GMR编码器,它使用SPI总线跟单片机进行通信,官通标称的最大通信速率是8Mbps(b:bit比特,ps:per second每秒,描述数据传输的快慢),所以选择中速,也就是最大10MHZ的输出速度

3>单片机上有一个USB2.0的全速接口,它的通信速率是12Mbbps,所以只能选择高速

闪灯实验

使用单片机的IO引脚去驱动板载的LED和一颗外接的LED

除了向单片机下载程序之外,我们还可以使用ST-Link对代码进行调试,在调试之前,首先先关闭掉代码的优化,如下图

使用bootloader清除程序

如果忘记了这个步骤,芯片的调试接口将会被锁死,烧录程序时会发现烧录不进去,可以通过bootloader把里面的程序清除掉,程序被清除掉之后,单片机的调试接口将被重新打开

BOOT0接1,BOOT接0

然后点击disconnected,断开连接

4种输入模式

当IO引脚工作在输入模式下时,它的作用就是去测量外部输入信号的电压,相当于一块电压表,因此它的内阻也应当是无穷大,无穷大的电阻相当于开路,所以我们把这个位置断开,同时如果我们不接上拉电阻和下拉电阻,那么这个时候IO引脚完全悬空了,悬空的引脚就像一根天线,他会接收空间的电磁波,我们会读到随机的0和1。现在我们加入上拉电阻,同样不接外部输入信号,这时候电路由两个电阻分压,因为下面电阻的阻值是无穷大,所以它会分得整个Vdd,而上面电阻分得的电压是0V,接了上拉电阻之后,即使没有外部信号,IO引脚上也会出现一个稳定的高电压,而不再是一个随机值。

按钮实验

使用一枚按钮,去控制最小系统板上的板载LED,按下按钮时,LED点亮;松开按钮时,LED熄灭。

把IO引脚配置为输入上拉模式,按钮松开时,IO引脚悬空,此时在上拉电阻的作用下 ,IO引脚商呈现高电平,此时读到的IO引脚的值为1;当按钮按下时,IO引脚通过这条线接地,所以引脚上是低电平,此时读到的IO引脚的值为0

UART

UART

基础知识

串口是以数据帧为单位传输数据的

十进制85转化为二进制是01010101(补足8位,高位补0)

串口发送时低位先行,即10101010

一位代表传输一个高低电压所需要的时间

波特率越高,数据传输的速率就越快,注意收发双方应该选择相同的波特率

简单的数据发送实验

选择异步模式,观察右边的引脚分配图,发现CubeMX自动分配好了UART的引脚,并且CubeMX

自动配置好了引脚的参数,但是由于串口的数据接收引脚可能会意外断开,最好配置一个上拉电阻

将调试器和USB转TTL模块都接到电脑上

本次实验选择最常用的,如下

CubeMX自动帮我们生成了串口1的句柄,即huart1

设超时时间是10ms,如果数据发送在10ms内没有完成,那么发送过程就会停止,并且返回一个错误。一般不使用这个超时时间,填写HAL_MAX_DELAY表示无限期等待下去,直到发送完成为止

返回值只有返回HAL_OK才表示发送成功,其他返回值都表示发送出错


传送数据的一些解释

1>0x5A 的十进制值是:5 × 16 + 10 = 90

2>uint8_t byteArray[] = {1,2,3,4,5};

byteArray 是一个 uint8_t 数组,每个元素占 1 字节,数组长度自动推断为 5

1 个 uint8_t = 1 个字节,数组里每个元素正好对应 内存中的 1 个字节

HAL_UART_Transmit(&huart1, byteArray, 5, HAL_MAX_DELAY);
这里,byteArray 本质上是指向数组首元素的指针(即 &byteArray[0])。

3>char ch = 'a';     内存中实际是:ch = 97 (0x61),串口发送0110 0001   // 97 → 'a'

实际发生的是:把数字 97 放进 1 字节的内存中,'a' 不是字符对象,它是一个 值为 97 的整数

如果char ch = 97;      printf("%c\n", ch);  // a,把 97 当 ASCII 码解释
printf("%d\n", ch);  // 97,直接当数字 → 97

4>"Hello world" 是 字符串常量,本质是一个 以 \0 结尾的字符数组,内存结构为

H e l l o   w o r l d \0,str 实际上是指向数组的首元素(即 'H')的指针。

char *str是一个 指针,指向字符串首地址,等价写法:char str[] = "Hello world";

转换后的指针仍然指向相同的内存位置,但数据的类型解释变成了无符号 8 位整数,这样 HAL_UART_Transmit 就可以正确地处理这些字节数据,进行 UART 传输。

&ch 返回的是一个指向 ch 的指针,类型是 char *(即指向一个字符的指针)。强制类型转换 (uint8_t *)&ch 的目的是将 ch 的地址从 char * 转换为 uint8_t *,使得指针类型符合 HAL_UART_Transmit 函数的要求。

简单的数据接收实验

通过串口想单片机发送命令,发送字符1,板载LED点亮,发送字符0,板载LED熄灭

如果第四个参数填10,即超时时间是10ms,如果在10ms之内没有接受到足够数量的数据,那么接收就会停止并返回错误,一般不使用这个超时时间,这时参数应当填HAL_MAX_DELAY,表示超时时间是无限大,如果没有接收到足够数量的数据,就会一直等待下去

该编程接口还有一个返回值,用来返回程序执行的结果,只有返回HAL_OK才表示接收成功 

dataRcvd表示接收到的数据,Rcvd是Rceived的缩写

GBK、UTF-8 和 ASCII 字符编码标准的解释

IIC

基础知识

串口可以实现一对一的数据传输,单片机有三个串口,通过串口,最多让单片机和三个外部设备连接。

如果想要连接更多设备,就需要用到总线,IIC就是一种总线,可以让单片机跟大量设备进行连接,单片机一般作为IIC总线的主机,其他设备作为从机,

SCL(Serial Clock,串行时钟线),负责传输时钟信号,即这种高低变化的方波信号,每个时钟周期传输一位,所以时钟的频率越高,传输数据的速率就越快.

SDA(Serial Data,串行数据线),负责传输数据,高电压表示1,低电压表示0。

主机和从机都有SCL和SDA引脚,需要将它们分别连接在SCL和SDA线上,IIC总线还需要两颗上拉电阻,阻值一般取4.7K,一颗接在SCL上,另一颗接在SDA上,分别对时钟线和数据线进行上拉,从机和主机的SCL和SDA引脚都应该使用开漏输出(P-MOS断开,只控制N-MOS的通断,N-MOS导通时输出低电压,N-MOS关断时,引脚悬空输出高阻抗)。

SCL和SDA两条线实现逻辑线与的原理是一样的,所以我们只分析其中的一条线。向所有的引脚都写1,开漏模式下向IO引脚写1,输出高阻抗,相当于开路,这样总线就悬空了,但是由于上拉电阻的存在,总线上的电压会被拉高,也就是输出高电压1;向任意一个引脚写0,剩下的引脚保持1,写1的引脚输出高阻抗,写0的引脚输出低电压,总线通过写0的引脚接地,虽然有上拉电阻的存在,但是它已经不起作用了,总线输出低电压0。这样就通过硬件电路实现了逻辑线与。

弱上拉,强下拉

由于IIC是总线结构,所以主机和从机会共用SCL和SDA这两条线

起始位

在数据传输开始之前,总线上处于空闲状态,由于上拉电阻的存在,所以SCL和SDA上都是高电压,此时,主机只要把SDA线拉低,就可以发送一个起始位。

寻址

在上一个阶段,我们已将发送了起始位,现在开始寻址,首先发送从机的七位地址,比如要寻址的设备是第一个从机0x78,把它的地址转换为二进制01111000,把最后一位丢掉,遇到0就发送低电压,遇到1就发送高电压,最后还要发送一个RW位用来填写数据传输的方向,R/W#=0时,代表向从机写数据,R/W#=1时,代表从从机读数据。发送完从机的地址之后,主机释放SDA线,等待从应答。从机通过把 SDA拉低来发送一个应答信号来告诉主机寻址成功,我们把这个应答信号称为ACK(Acknowlege),如果没有发送应答信号叫NAK(Not Acknowlege)。

寻址阶段NAK的原因:

地址填错,要寻址的从机不存在;要寻址的从机正忙,没来得及回复ACK;从机故障

不管是哪种原因,只要收到了NAK,就表示寻址失败

数据传输

根据上一阶段填写的RW值,数据传输有两种不同的方向

写:主机发送,从机接收 。主机首先向从机发送8个比特,也就是一个字节的数据,然后释放掉SDA线,等待从机确认接收,从机通过把SDA拉低来发送一个ACK,表示数据收到;然后主机再发送第二个字节,以此类推,每次可以发送多个字节。

 读:从机发送,主机接收。读数据和写数据类似,但是方向相反,从机发送数据,主机回复ACK,每读一个字节,回复一个ACK,

停止位

在数据传输结束之后,主机需要向总线上发送一个停止位,当SCL是高电压时,向SDA上发送一个上升沿。

练习:主机向从机0x78写0x5a,0x33,然后再读一个字节

数据传输开始之前,总线上处于空闲状态;主机通过把SDA拉低来发送一个起始位,开始寻址,主机发送0x78,RW=0,表示写数据;然后主机释放SDA等待从机应答,从机0x78通过把SDA拉低来发送一个ACK,此时寻址成功;主机发送第一个字节0x5a(01011010),然后释放SDA等待从机确认接收数据,从机通过把SDA拉低,发送ACK,第一个字节传输完成;紧接着主机发送第二个字节0x33(00110011),释放SDA等待从机应答,从机发送ACK,第二个字节发送成功。两个字节都发完了,此时主机发送停止位,数据传输结束,总线重新进入空闲状态。下一步主机需要从从机读取一个字节的数据,它先发送起始位,然后发送0x78(0111100),RW=1,表示方向是读,主机释放SDA,从机发送ACK表示寻址成功,主机从从机读取一个字节的数据,然后从机释放SDA,等待主机应答,因为不想接收更多的数据,所以发送一个NAK,然后发送一个停止位,数据传输结束。

IIC以高低变化的电压来传输数据,每个时钟周期传输一位,把每秒钟传输位的数量叫做波特率,波特率越大,通信速率越快。根据通信速率的快慢IIC总线可以分成多种模式,STM32F105C8T6只支持标准模式和快速模式,也就是最大通信速度不超过400kbps

IIC总线里,主机可以通过SCL向外发送这种方波信号,即时钟信号,在快速模式下,它的占空比是可以设置的,如果没有特殊说明,我们一般选择2:1的占空比。

简单的数据收发实验

我们可以使用一块0.96寸的OLED显示屏显示字母、汉字、图像、动画等各种各样的信息。

我们一般用不到SMBus,直接选IIC

STM32既可以作为主机来连接大量的外部设备,也可以作为从机挂载在IIC总线上,当STM32作为IIC从机的时候,需要另一块单片机作为主机。STM32作从机的现象比较少见,只有在大型的项目中才会用到。当STM32作主机的时候,设置上面的参数,当STM32作从机时,设置下面的参数。

IIC1的SCL位于PB6引脚,SDA位于PB7引脚

注意两颗电阻之间不要短路,这块屏幕使用了一颗型号为SSD1306的驱动芯片

同型号OLED默认地址一致



 

uint8_t commands[] = 
{
    0x00,        // 命令流标识(IIC通信中区分命令/数据)
    0x8d, 0x14,  // 使能电荷泵
    0xaf,        // 开启OLED显示
    0xa5         // 屏幕全亮测试
};

IIC通信中,向OLED发送0x00表示后续字节为命令,0x40则为显示数据

0x8d是设置电荷泵的指令码,0x14为参数(0x10关,0x14开启),OLED的驱动电压需要高于电源电压,电荷泵负责升压,是屏幕点亮的前提

af为Diasplay ON显示,对应ae为关闭显示,执行后OLED从休眠状态进入工作状态,驱动开始刷新显示内容到屏幕

a5为Entire Display ON指令,强制让所有像素点点亮(忽略显示数据),用于检测屏幕硬件是否正常;若要回复显示,需发0xa4

sizeof(commands) :计算整个数组的字节数,这里 commands 是 uint8_t 类型数组,包含5个元素,总字节数为 5×1=5 。

 sizeof(commands[0]) :计算数组单个元素的字节数, uint8_t 占1字节。

两者相除结果为 5 ,作为 HAL_I2C_Master_Transmit 的数据长度参数,告诉IIC总线要发送5个字节的指令。

按位与运算是对两个二进制数的每一位进行比较:

  • 如果两个对应位都是 1,结果为 1。

  • 如果两个对应位有一个是 0,结果为 0。

 假设dataRcvd = 0xC3  // 二进制表示为 11000011

   11000011
& 01000000
    -----------
    01000000

时钟系统

基础知识

单片机内部有许多模块,每个模块都包含记忆性元件,都属于时序逻辑电路,因此单片机需要给每一个模块都提供时钟信号,并且这些模块所需要的时钟的频率不同

LSI和HSI都是芯片内部的RC振荡器,不需要外接任何电子元件,硬件成本低,精度低

HSE和LSE都需要外接一颗晶振,精度高,但会略微增加硬件成本

SYSCLK有三种来源,既可以来自芯片内部的高速RC振荡器,即HSI;也可以来自芯片外部的高速晶振;还可以来自锁相环,即PLL(一种倍频器,可以对时钟信号的频率做乘法),PLL的输入信号又三种来源,1/2HSI,1/2HSE,HSE

 

时钟配置实验

Cortex-M3内核,即单片机的CPU,我们所写的所有代码,都是通过Cortex-M3内核执行的,HCLK的频率就决定了代码的执行速度,可以通过板载LED闪灯的快慢来间接观察代码的执行速度,

8MHZ=800万次每秒,这就意味着单片机最快每秒钟可以执行800万条指令,如果考虑到内核的指令流水线,实际执行的速度比这稍快。

for循环每次消耗八个指令周期,100万次for循环,大概要执行800万条指令,单片机每秒可以执行800万条指令,所以这行代码的耗时大概就是1S钟左右。

SPI

总线结构

比如摄像头模块,因为要传输图像,所以数据通信的量比较大,一般都会选择使用SPI总线跟单片机建立连接;在项目开发过程中,经常会存储一些数据,并且要求这些数据掉电后不会丢失,这时就需要使用外接的flash模块,它就像一个移动硬盘,即使断电,里边的数据也不会受到影响,因为我们既要向这个flash模块写入数据,又要从它里面读取数据,所以数据通信的方向是双向的,而且传输的速率也比较快,所以适合使用SPI总线跟单片机进行通信。

在传输数据的过程中,时钟信号由主机产生,并通过SCK发送给从机,而且每个时钟周期发送一个位,时钟频率决定了通信速度的快慢,时钟频率越高,数据传输的速度就越快。

每一个从机有一条NSS,所以有多少个从机就有多少条NSS线,主机想要和哪个从机通信,就像对应的NSS引脚输出一个低电压。

SPI的5个参数

比如使用面包板和杜邦线搭建的电路,电气性能不怎么好,根据经验,对于这种电路来说,最高选择10Mbps的波特率,如果再高的话,就会产生比较严重的干扰。

外部Flash实验

使用按钮去切换板载LED的亮灭状态,每按一次按钮,LED的亮灭状态切换一次,在每次按按钮时,把LED的亮灭状态保存在一颗外部的Flash芯片当中,这样即使断电,LED的状态也不会丢失。比如点亮这颗LED,断电,再上电,可以发现LED还是点亮的;然后熄灭这颗LED,断电,再上电,可以发现LED还是熄灭的。

当我们按下按钮时,LED没有反应;只有松开按钮时,LED的亮灭状态才会发生变化,因此我们需要捕捉的是按钮状态变化的瞬间

我们可以声明两个变量,previous(保存按钮上一次的值)和current(保存按钮当前的值),使用一条蓝色的虚线来表示当前时刻,不断的更新previous和current的值,当previous和current值相等时,表示按钮状态没有发生变化;当previous不等于current时,表示捕捉到了按钮动作的瞬间。然后需要进一步去判断,捕捉到的是按年下的动作还是按钮抬起的动作。如果current的值等于0,表示捕捉到的是按钮按下的动作;如果current的值等于1,表示捕捉到的是按钮抬起的动作。

实际上按钮存在抖动,这些抖动会造成按钮的多次触发,这就是按钮失灵的原因。按钮的抖动时间一般不会超过10ms,因此当我们检测到按钮动作之后,延迟10ms就可以了。

主机通过SCK向总线上发送16个周期的时钟脉冲,主机通过MOSI吧接收缓冲区的初始值发送出去,因为我们已经给接收缓冲区的所有元素都赋了初值0xff,所以它发送的全是高电压;若没有给接收缓冲区赋初始值,那么他就会发送一些随机值。记得给接收缓冲区的元素赋初值0xff

而主机接收到的数据会被保存到我们声明的接收缓冲区里面。

向Flash写入数据之前,需要对写入数据的位置进行擦除,Flash擦除的最小单元是扇区,每次至少要擦除4K字节,擦除需要一定的时间,所以延时100ms,等待擦除完成。

数据写入的最小单元是页,页编程需要一定的时间,所以延时10ms,等待编程完成。

在每次进行擦除和编程之前,都需要对芯片进行解锁,把解锁的步骤叫写使能。

 中断

基础知识

中断编程能够更快地响应突发事件。

 

NVIC

单片机的内部有一个flash,这个flash就相当于单片机的硬盘,我们所写的所有代码都存储在这个flash里面,我们的中断响应函数其实也是一段代码,所以它也存储在flash里面。在单片机的flash里,不光存储了中断响应函数的代码,还存储了一些其他的代码,单片机是如何从这些代码里面找到中断响应函数的呢?为了找到中断响应函数,我们需要在flash的一开头放置一个目录,这个目录的名字就叫做中断向量表,单片机想要调用中断响应函数的时候,先去查中断向量表,顺着中断向量表,我们就可以找到对应的中断响应函数了。

串口中断实验

通过串口来控制LED的闪灯速度,发送1是满闪,发送2是速度正常,发送3是快闪。

通过控制Delay()的时间控制闪灯速度,可以声明一个变量blinkInterval用来控制闪灯的间隔,把它作为延迟函数的参数传递进去。

可以看到,现在LED慢闪

之后会弹出一个监视窗口,在里面填入要监视的变量

把Hexadecimal Display勾选掉,然后就以十进制显示

值改为300,闪灯的速度加快

改为50,LED以更快的频率闪烁

这样我们就可以通过修改blinkInterval的值来控制闪灯的速度。

定时器

时基单元

只有高级定时器才会有RCR

输出比较

通过定时器,向外输出精确定时的方波信号

输入捕获:把外部信号输入到定时器的内部,然后使用定时器对时间参数进行测量

信号强度越强的地方,PWM的占空比越大;信号强度越弱的的地方,PWM的占空比越小,这样就可以使用PWM信号来表示任意的波形。       

CCR:捕获比较寄存器Capture/Compare Register

 输出比较的8种模式

互补输出:有些情况下,我们要使用定时器的PWM信号来驱动MOS管,如下图是一个H桥电路,它由4个MOS管组成,只看左边的两个这两个MOS管需要交替工作,如果上面的导通,下面的就断开,下面的导通,上面的就断开,但是两个MOS管不能同时导通,因为同时导通的话,电路就短路了。因此对于这两个MOS管来说,需要使用一对互补的PWM进行驱动。如上面的MOS管,我们需要使用正常的PWM波去驱动,而下面的MOS管我们需要对上面的的信号取反。

呼吸灯实验

 

为了验证高级定时器的互补输出,准备了两颗LED,一颗蓝色的接正常输出,一颗红色的接互补输出,因为正常输出和互补输出是相反的,所以两颗LED一明一暗交替点亮

当我们调用其中任意一个编程接口后,它首先会闭合最左边的这个开关,从而去启动定时器,然后会闭合右边的开关来使能输出。

Ex是extended的缩写

定时器是有很多定时器组成的,下图这组编程接口的作用就是直接去读写这些寄存器

输入捕获                     

超声波测距实验

随便选择一个引脚,如PA0,设置成推挽模式,设置为默认低电压

把计数器CNT每跳一次所用的时间叫分辨率。分辨率决定了测量的最大精度。

定时器的定时周期应大于最大脉宽。

如果再测量过程中发生了溢出,那测量的结果就没有意义。echo引脚上脉冲的最大宽度是38ms,所以我们应当保证周期大于38ms。假如把自动重装寄存器的ARR的值设置为它的最大值65535,那么定时周期就等于(65535+1)*定时器的分辨率1us,即65.536ms,这个值比38ms要大,所以它满足要求。

实验目标:不断去检测传感器前方20cm范围处有没有障碍物,如果有障碍物,就点亮板载LED;如果没有障碍物,就熄灭板载LED。因为要不断地去检测,所以需要把代码写在main方法的while循环里。

 Trig和Echo分别代表传感器的Trig和Echo引脚,CNT代表计数器,随时间递增

可以使用CC1和CC2这两个标志位来判断测量是否结束

上次测量结束后,CNT的值很可能不是0,应手动让CNT的值归0,即向计数器CNT写0。

定时器中有很多标志位,如update cc1 c2 cc3 cc4,我们可以使用这些标志位来获取定时器的一些信息,如当计数器CNT发生溢出的时候,会触发一个uopdate事件,当uppdate发生的时候,单片机会自动让update标志位从0变成1,所以只需要查一下update标志位就知道CNT有没有溢出;当我们把定时器的通道设置为输入捕获模式,并且这个通道捕捉到了对应的信号变化的时候,就会产生一个CCs事件,当CCs事件发生的时候,单片机会自动让CCs标志位从0变为1,我们只需要查询一下CCs标志位,就可以知道这个通道当前有没有捕捉到对应的信号变化了。在使用这些标志位之前,我们还需要对这些标志位进行清0。

在使用标志位之前,还需要对标志位清0,。

IC:Input Cpture输入捕获    第一个编程接口的作用是首先闭合时基单元的开关从而启动定时器,然后再闭合输入捕获对应通道的开关,从而让这个通道能够捕捉到信号的变化;第二个编程接口会断开刚才闭合的两个开关。

需要向Trig引脚发送一个宽度至少为10us的脉冲,传感器的Trig引脚连接的是单片机的PA0引脚,所以我们首先向PA0写1,延迟一段时间,再向PA0写0。这里的脉冲宽度至少是10us,而我们使用HAL_Delay进行延迟,延迟的最小时间是1ms,也就是1000us,比10ms大的太多了,所以改用for循环进行延迟,通常认为,for循环每执行一次,需要消耗8个指令周期。

观察时钟树,当前状态下,Cortex-M3内核的时钟频率是8MHZ,因此for循环每执行一次消耗的时间就是1us,所以要延时10us,只需要让for循环执行10次。

因为Echo的最大宽度是38ms,所以即使加上其他损耗,最长的等待时间也不会超过50ms。

expire失效,终止,到期

需要在while循环里判断当前是否超时,如果超时,就代表测量失败了

如果我们检测到了CC1标志位的值从0变成了1,就意味着捕获到了上升沿;如果我们检测到了CC2标志位的值从0变成了1,就意味着我们检测到了下降沿,意味着整个测量结束。

当我们检测到Echo引脚出现下降沿时,就没有必要让CNT的值继续增加了,所以在这个位置关闭定时器。

会同时断开下面的两个开关

计算距离的前提是测量已经成功,所以我们需要在外边加一个if语句用来判断测量是否成功

从模式控制器

作为主机

在复位模式下,只需要向TRGI输入一个脉冲,脉冲有一个上升沿,当上升沿发生的时候,计数器CNT的值也会清0,同时也会残生一个update事件,跟正常溢出是非常相似的

在这种模式下,输入到RTGI的信号可以用来控制时基单元的开关,比如一开始的时候给TRGI输入高电压,此时开关导通,CNT的值随时间递增,然后输入低电压,断开开关,此时CNT的值保持不变,最后我们恢复高电压,此时开关导通,CNT的值继续增加。

在这种模式下,只需向TRGI输入一个上升沿,就能闭合时基单元的开关,从而启动定时器。如假设一开始时时基单元的开关是断开的,CNT的值保持不变,现在通过TRGI输入一个脉冲,脉冲包含上升沿,所以会启动会定时器,然后计数器CNT的值随时间递增。

通过TRGI输入一个时钟信号,然后把这个是时钟信号当成时基单元的时钟来源,计数器CNT的值随时钟信号递增,这和直接从时钟树输入时钟信号是一样的。时基单元的时钟来源有三种,TRGI就是指从模式控制器的触发输入。

作为主机

时基单元这个位置的开关负责定时器的启动和停止,在这种使能模式下,开关的状态会通过从模式控制器的TRGO向外输出出去,当开关导通的时候,输出的是高电压;当开关断开的时候,输出的是低电压。

时基单元每产生一个update事件就向TRGO向外输出一个脉冲。如每当计数器CNT溢出的时候,就会产生一个update事件,对应TRGO就会向外输出一个脉冲

占空比测量实验

1.首先测试串口的功能是否正常

接收设置选ASCII,即以字符形式接收数据

每个中文字符占两个字节,4个字符正好是8个字节

strlen(),本质是库函数,需包含<string.h>,仅适用于以\0结尾的字符串,计算字符串有效字符长度(不包含末尾的\0)

串口应和编写代码是使用的字符集编码格式一样 

按下单片机的复位按钮,这时串口调试助手就显示了字符串你好世界 ,说明串口工作是正常的

2.产生PWM信号

和高级定时器相比,通用定时器缺少了重复计数器RCR和互补输出等这些功能

如果不对时钟树进行任何配置,默认状态下输入到定时器三的时钟信号的频率就是8MHZ

按下复位按钮,指示灯发出微弱的亮光,说明我们已经成功的向外输出占空比为20%的PWM信号了。

3.测量PWM信号的参数(周期和占空比)

TIxFPY:timer input x(从哪个通道来)   filtered polarized(经过极性选择之后的,上升沿捕获or下降沿捕获)    y(信号要使用到哪个通道去)

由于通道1捕获的是上升沿,所以每当输入的PWM信号出现一个上升沿时,TI1FP1上就会出现一个脉冲,CNT的值就会保存到CCR1中,因为已经把TI1FP1选作从模式控制器的触发输入TRGI了,所以TRGI出现一个上升沿时,CNT的值就会被清0,从而产生一个update事件。使用通道2捕获下降沿,所以当遇到下降沿时,CNT的值会保存到CCR2中。

__HAL:最底层的寄存器操作函数,直接操作硬件寄存器(底层硬件操作)

HAL:上层的API函数(上层功能封装)

 

编码器实验

除了编码器之外,下面还有一块黑色的电路板,为编码器提供一些简单的外围电路;右边还有一个5pin的排针,通过排针把编码器的引脚引出去,方便接线。

不同旋转方向时A、B相谁在前、谁在后可能根据型号的不同反过来。

增量式旋转编码器的应用场景有很多,如旋钮输出A、B两相信号来告知单片机用户对旋钮的正反转以及旋转角度;许多电机上也自带旋转编码器,想单片机反馈当前电机的旋转角度与旋转方向,并且单片机还可以通过单位时间电机的旋转角度,计算得到电机的旋转速度。

使用思路:1.将A、B相信号接入到GPIO口,将A相GPIO口设置为上升沿(或下降沿)触发中断,然后在中断回调中读取B相GPIO口的电平来判断旋转方向,并且根据旋转方向对计数值加1或减1来记录脉冲数量。但如果是处理转得非常快的电机的旋转编码器,就会因为频繁触发中断,占用太多CPU软件计算资源,导致其他任务无法正常执行。而且太快的话,很有可能软件处理跟不上导致丢步问题。2.通用和高级定时器为增量式编码器提供了专门的编码器接口,只要将A、B两相信号同时输入进去,就可以实现正转时计数器自增,反转时计数器自减,编码器接口对上下边沿都敏感,所以对于A、B相上的一组脉冲,会计数两次,比如A相是下降沿时,B相是低电平,计数器加1;A相是上升沿时,B相是高电平,计数器又加1。

不管是这三种模式的哪一种,都是A相在前表示正传,CNT递增;B相在前表示反转,CNT递减

                    

      

编码器的模式是让我们选择在哪一通道输入信号的边沿上进行计数,选择TI1就是在通道1的边沿上,选择TI2就是在通道2的边沿上,也可以选择通道1和通道2的边沿都计数,如果选择都计数,那每次脉冲就会计数4次。

编码器接口就像是一个时钟源,最终计数的还是定时器的计数器

所以顺时针拧编码器时,CNT的值是减少的

极性设置:设置为下降沿有效会将此通道的波形翻转,

一个用来以编码器模式启动定时器,一个用来停止定时器,通过控制编码器的开关来控制定时器

顺时针拧编码器,每拧一次,计数器CNT的值就增加1;逆时针拧编码器,每拧一次,计数器CNT的值就减少1

为每个外设生成单独的.c .h文件

将TI2的电平信号反向,也就调换了正反转的效果

ADC

逐次逼近型ADC

在STM32F103C8T6单片机中,集成了两个逐次逼近型ADC。

逐次逼近型ADC类似砝码称重,我们需要不断的调整砝码的组合,来一次一次逼近被测物体的实际质量。

闭合采样保持电路的开关,输入电压就会对电容充电,当电容充满电之后,断开采样保持电路的开关,它两端的电压就等于输入信号的电压,即2.21V;然后再把采样保持电路的开关断开,输出到右边比较器的电流应当恒为0,所以存储在电容里的电能既不可能通过左边流出去,也不可能通过右边流出去,因此刚才冲进去的点就只能被锁定在电容当中,所以电容两端的电压在后续转换过程中不会发生变化,始终维持2.21V,而且这个电容跟比较器的正端直接相连,所以在后续的转换过程中,比较器的正输入端也会维持2.21V不变,即采样保持电路的作用就是先把输入电压采集进来,然后再通过电容储能的形式,保证输入的电压在后续转换过程中保持不变。(相当于把待测物体放在了天平的左侧)

向b3这个权重最大的比特位写1,电压发生器输出的电压就是1.76V,这个电压比2.21V要小;向b2这个比特位写1,此时电压发生器输出的电压就是1.76V+0.88V=2.64V,比2.21V要大,需要向b2比特位写0,;向b1这个比特位写1,此时电压发生器输出的电压就是1.76V+0.44V=2.2V,这个电压比2.21V要小;;向b0这个比特位写1,此时电压发生器输出的电压就是1.76V+0.44V+0.22V=2.42V,这个电压比2.21V要大,需要向b0比特位写0。最终结果为1011

模块的基本原理

采样时间和转换时间

ADC模块的时钟

转换时间就类似于调整砝码所需要的时间,在称重的过程中每一种砝码都需要调整一次,因此转化时间的长短取决于砝码的数量

12个周期用于逐次逼近,0.5个周期用于结果锁存

采样时间就相当于把重物放在天平上所消耗的时间,即把模拟信号输入到ADC里所花的时间

采样时间非常短,待转换的模拟信号可看作是一条直线

ADC的精度本就有限

现实世界中并不存在理想的信号源,在现实的世界中所有的信号源都是带有内阻的

                    

单通道转换

使用单片机的ADC去测量模块的输出电压,当模块的输出电压大于1.5V时,熄灭LED;当它小于1.5V时,就点亮LED。

R1和R2串联分压,分压的结果通过AO这个引脚输出出去

0 7.5 代表第一个被转化的通道是模拟输入0,它对应的采样时间是7.5倍的周期

软件启动就是通过编程的方式向外部触发信号发送一个脉冲从而去启动常规序列,也是最简单的一种方式


定时器触发

把光敏传感器模块接入到单片机的ADC上,使用单片机ADC把它输出的模拟信号转换为数字信号,然后通过单片机的串口,把转换的结果发送给电脑,横轴表示时间,纵轴表示光敏传感器输出的电压,当光线昏暗的时候,光敏传感器输出的电压反而越高。

定时器每隔1ms会发送一个脉冲,所以常规序列每隔1毫秒启动一次,在常规序列里只包含一个通道,就是连接光敏传感器的通道,即模拟输入0,模拟输入的转换需要消耗20个周期,换算成时间就是5us(接入ADC的时钟是标准的4MHZ),

把单片机串口发来的程序以波形的形式显示在电脑上,需要使用一种叫串口示波器的软件,如VOFA+

stdio.h  是 C 语言标准库中的标准输入输出头文件,全称是 Standard Input & Output Header。它提供了一系列用于格式化输入、输出和文件操作的函数,是嵌入式开发(如STM32)和桌面程序中非常常用的头文件。

string.h  是 C 语言标准库中的字符串处理头文件,提供了一系列用于操作字符串和内存块的函数,是嵌入式开发中处理字符串、缓冲区时必不可少的工具。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐