1. 通用定时器生成PWM波

1.1 功能概述

通用定时器TIM2-TIM5,TIM9-TIM14,计数器位数,通道数不同

1.2 生成PWM波的原理

PWM(Pulse Width Modulation)就是脉冲宽度调制,PWM波就是具有一定占空比的方波信号,通过定时器的设置可以控制方波的频率和占空比。

1.3 与生成PWM波相关的HAL函数

1.4 STM32CUBEMX配置(固定频率PWM)

对于STM32F407ZGT6的TIM14的CH1可以对应引脚到PF9(LED0),用来控制灯的亮度

定时器TIM14各选项的意义:

  • Disable ,禁用通道
  • Input Capture Direct Mode,直接模式输入捕获
  • Output Compare No Output,输出比较,不输出到通道引脚
  • Output Compare CH1,输出比较,输出到通道引脚CH1
  • PWM Generation No Output,生成PWM,不输出到通道引脚
  • PWM Generation CH1,生成PWM,输出到通道引脚CH1
  • Forced Output CH1,强制通道引脚CH1输出某个电平

PWM模式,选项有PWM Mode 1和PWM Mode 2

PWM Mode 1->递增计数模式:CNT<CCR(有效状态),CNT>CCR(无效状态)

PWM Mode 2->递减计数模式:CNT<CCR(无效状态),CNT>CCR(有效状态)

Pulse,PWM脉冲宽度,就是设置16位的捕获/比较寄存器CCR的值。脉冲宽度的值应该小于计数周期的值

eg:设置为50,因为计数器的时钟频率是10kHz(对应定时器APB的值,经过Prescaler分频之后的时钟频率(进入计数器的时钟频率)),所以脉冲宽度为5ms

Output compare preload,输出比较预装载。若Enable,修改CCR值(修改占空比)后立即生效;若Disable,修改CCR之后要等到下一个UEVA事件才会生效。

Fast Mode,是否使用输出比较快速使能

CH Polarity,通道极性,就是CCR与CNT比较输出的有效状态,可以设置为高电平(High)或低电平((Low)

主程序(固定频率PWM):

必须调用函数启动定时器,再启动定时器的PWM输出

1
2
3
HAL_TIM_Base_Start_IT(&htim14); //以中断方式启动TIM14
HAL_TIM_PWM_Start_IT(&htim14,TIM_CHANNEL_1);
//TIM14通道1, 启动生成PWM

主程序(可变频率PWM):

重新实现回调函数HAL_TIM_PWM_PulseFinishedCallback(),在此回调函数里编写代码改变占空比。

1
2
uint8_t pulseWidth=50;//脉宽
uint8_t dirInc=1;//脉宽变化方向,1=递增,0=递减
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance != TIM14)
return;
if(dirInc==1)
{
pulseWidth++;
if(pulseWidth>=195)
{
pulseWidth = 195;
dirInc = 0;
}
}
else
{
pulseWidth--;
if(pulseWidth<=5)
{
pulseWidth = 5;
dirInc=1;
}
}
__HAL_TIM_SET_COMPARE(&htim14,TIM_CHANNEL_1,pulseWidth);//设置CCR的值
}

2. 输入PWM

一个定时器产生PWM波,另一个定时器测量这个PWM波,本次采用TIM14的CH1通道(即PF9)来产生PWM波TIM9_CH1测量pwm波。

2.1 cubemx的配置

使用定时器TIM9的PWM输入功能测量PWM波参数。TIM9有2个通道,在输入PWM模式下,使两个通道都映射到TIM9_CH1的复用引脚上。

由于需要在TIM9的捕获比较中断里读取CCR的值,因此打开TIM9的中断。

3.基于HAL库的按键检测代码

https://wwqe.lanzouo.com/iuE7i0kvztyf

4.CUBEMX配置LCD屏幕

检查FSMC自动分配的GPIO引脚是否与电路图一致,若不一致直接修改即可。

设置背光对应的IO

5.基于HAL库改版正点原子TFTLCD驱动程序(HAL)

基于型号9413,stm32f407zgt6

https://wwqe.lanzouo.com/igS5X0nv67cf

6.基于HAL库改版正点原子HAL库TFTLCD驱动程序(最新版V3)

改良版程序

https://wwqe.lanzouo.com/i3nHP0nvqm8b

基于CUBEIDE的LCD实现程序:

https://wwqe.lanzouo.com/ipmAU0qm5dob

6.1 重点

在最新版的TFTLCD驱动程序中,需要把lcd_ex.c文件在MDK中设置为永不编译的情况

具体说明连接:https://blog.csdn.net/qq_25144391/article/details/118959208

在CUBEIDE的配置中:

7.实时时钟RTC

工程连接https://wwqe.lanzouo.com/iy3Ac0nyb2de

RTC(Real-time Clock,实时时钟)是由时钟信号驱动的日历时钟,提供日期和时间数据。

  • RTC能提供BCD或二进制的秒、分钟、小时(12或24小时制)、星期几、日期、月份、年份数据
  • RTC模块和时钟配置都使用备用存储区域,使用VBAT备用电源,即使主电源断电或系统复位也不影响RTC的工作
  • RTC有两个可编程闹钟中断,可以设定任意组合和重复性的闹钟
  • 有一个可周期性唤醒的中断,唤醒中断可以当做一个普通定时器使用
  • 具有时间戳和入侵检测功能

RTC文档链接

8.串口USART

工程连接:https://wwqe.lanzouo.com/iOpDW0o4r0ri

8.1常用宏函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
__HAL_UART_ENABLE(__HANDLE__),使能一个UART口,如
__HAL_UART_ENABLE(&huart1);

__HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__),使能某
个中断事件源,如
__HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE); //使能接收中断
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能空闲中断

__HAL_UART_DISABLE_IT(__HANDLE__, __INTERRUPT__),禁止
某个中断事件源

__HAL_UART_GET_FLAG(__HANDLE__, __FLAG__),检查一个串口的某个中断标志是否被置位,返回值为宏定义常量SET或
RESET。中断事件标志的宏定义如下:
UART_FLAG_CTS: CTS 信号变化标志
UART_FLAG_LBD: LIN 打断检测标志
UART_FLAG_TXE: 发送数据寄存器空标志
UART_FLAG_TC: 发送完成标志
UART_FLAG_RXNE: 接收数据寄存器非空标志
UART_FLAG_IDLE: 线路空闲标志
UART_FLAG_ORE: 出错误标志
UART_FLAG_NE: 声错误标志
UART_FLAG_FE: 帧错误标志
UART_FLAG_PE: 奇偶校验错误标志

__HAL_UART_CLEAR_FLAG(__HANDLE__, __FLAG__),清除一个UART口的某个事件标志位

注意:注意,HAL_UART_Receive_IT()完成一次数据接收后就关闭了串口接收中断,不会自动进行下一次的接收,需要再次调用HAL_UART_Receive_IT()以启动下一次的接收,但不能在回调函数HAL_UART_RxCpltCallback()里调用HAL_UART_Receive_IT()。

为了能连续进行中断方式的串口接收,程序的处理方法是:在完成一次接收,并且串口状态为空闲,也就是发生UART_IT_IDLE中断时,对接收到的指令数据进行处理,然后再次调用HAL_UART_Receive_IT()以启动下一次的接收。

USART文档链接

9.ADC

  • ADC转换电压的输入范围范围是VREF-≤VIN≤VREF+,由于VREF-必须与VSSA连接,也就是VREF-总是0,所以STM32F407的片上ADC只能转换正电压。
  • 选择的多个模拟输入通道可以分为两组:规则通道和注入通道,每个组的通道构成一个转换序列。
    • 规则转换序列最多可设置16个通道,一个规则转换序列规定了多路复用转换时的顺序。
    • 注入通道就是可以在规则通道转换过程中插入进行转换的通道,类似于中断的现象。每个注入通道还可以设置一个数据偏移量,每次转换结果自动减去这个偏移量,所以转换结果可以是负数。

ADC时钟与转换时间:ADCCLK最高42MHz。一个通道一次ADC转换的总时间是N+12个ADCCLK周期,N是设置的采样次数。

image-20230310195613923

9.1软件启动ADC转换

工程链接:https://wwqe.lanzouo.com/izvuZ0qapo8j

1
2
3
4
5
6
7
8
HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef* hadc); 
//软件启动转换
HAL_StatusTypeDef HAL_ADC_Stop(ADC_HandleTypeDef* hadc);
//停止转换
HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef*
hadc, uint32_t Timeout);
uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef* hadc);
//读取转换结果寄存器的32位数据

其中,参数hadc是ADC外设对象指针,Timeout是超时等待时间,单位是节拍数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在mian()函数的while循环里,每500毫秒以软件触发方式进行一次ADC转换方式
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_ADC_Start(&hadc1); //必须每次启动转换
if (HAL_ADC_PollForConversion(&hadc1,200)==HAL_OK)
{
uint32_t val=HAL_ADC_GetValue(&hadc1); //读取转换结果
LCD_ShowUintX(orgX,orgY,val, 5); //5位显示,前端补空格
uint32_t Volt=3300*val; //以mV为单位
Volt=Volt>>12; //除以2^12
LCD_ShowUintX(voltX,voltY,Volt, 4); //4位显示,前端补空格
}
// HAL_ADC_Stop(&hadc1); //无需每次都停止
HAL_Delay(500);
/* USER CODE END WHILE */
}

9.2定时器触发ADC转换

工程链接:https://wwqe.lanzouo.com/imGIH0qapobc

  • ExternalTriggerConversionSource,设置启动ADC转换的外部触发信号源,这里选择Timer3TriggerOutEvent,也就是定时器TIM3的TRGO信号
  • External Trigger Conversion Edge,设置触发转换的跳变沿,这里选择上跳沿,因为TRGO是一个短时正脉冲信号
  • 开启ADC1的全局中断,在转换完成事件(ADC_IT_EOC)中断里读取转换结果。

TIM3的设置:

  • 使TIM3定时周期为500ms
  • 触发事件选择(Trigger Event Selection)设置为Update Event,也就是以UEV事件信号作为TRGO信号、

回调函数

注:无须开启TIM3的全局中断,TGRO信号也是正常输出的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* USER CODE BEGIN 4 */
/* ADC的转换完成事件(ADC_IT_EOC)中断回调函数 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
if (hadc->Instance == ADC1)
{
uint32_t val=HAL_ADC_GetValue(hadc);//读取转换结果
LCD_ShowUintX(orgX,orgY,val, 5); //5位显示,前端补空格
uint32_t Volt=3300*val; //以mV为单位
Volt=Volt>>12; //除以2^12
LCD_ShowUintX(voltX,voltY,Volt, 4); //4位显示,前端补空格
}
}
/* USER CODE END 4 */

ADC文档链接

9.3多通道和DMA传输

工程链接:https://wwqe.lanzouo.com/iX7ip0qapo4f

这里以ADC1的三个通道为例

  • 当规则转换组有多个通道时,应该使用扫描模式(Scan Conversion Mode)

注:在配置ADC时要使能扫描模式的DMA

设置Rank的输入通道和采样时间——每个通道的采样时间可以不一样,三个Ran里模拟通道出现的顺序就是规则组转换的顺序。

特别注意: 外设使用DMA时是否需要开启外设的全局中断,不同的外设情况不一样。例如,UART 使用DMA时就必须开启UART的全局中断,虽然可以禁止UART的两个主要中断事件源。在外设使用DMA时,建议尽量不开启外设的全局中断,若必须开启,也要禁止外设 的主要事件源产生硬件中断,因为DMA的传输完成事件中断使用外设的回调函数,若开启外 设的中断事件源,则可能导致一个事件发生时回调函数被调用两次。


1
2
#define BATCH_DATA_LEN 3      //  DMA数据缓冲区长度,必须是通道数的整数倍
uint32_t dmaDataBuffer[BATCH_DATA_LEN]; //DMA数据缓冲区
1
2
3
4
lcd_init();
lcd_show_string(10,10,400,20,16,"DEMO:ADC+DMA",GREEN);
HAL_ADC_Start_DMA(&hadc1,dmaDataBuffer,BATCH_DATA_LEN);
HAL_TIM_Base_Start(&htim3);
1
2
3
4
5
6
7
8
9
10
11
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
uint32_t adcValue=0,Volt;
for(uint8_t i=0;i<BATCH_DATA_LEN;i++)
{
adcValue = dmaDataBuffer[i]; //这是缓冲区里3个通道的转换结果
Volt = adcValue * 3300; //以mV为毫伏
Volt = Volt >> 12;
lcd_show_num(10,i*20+50,Volt,sizeof(Volt),16,RED);
}
}

9,4双ADC同步转换

工程链接:https://wwqe.lanzouo.com/ia6qr0qapoij

开启TIM3定时器

双ADC同步转换时CUBEMX的配置

多重ADC模式,只能使用DMA方式传输数据。

具体完整配置:

注:在很多版本的CUBEMX中,当ADC1和ADC2配置为同步规则转换时,ADC2的DMA流不能设置为Enable,这是软件自带的一个小bug,需要在程序中去进行修改。但每次重新生成程序,依然会重置回去,要注意每次都要修改。

程序代码:

.c文件中

1
2
3
#include "lcd.h"
#define BATCH_DATA_LEN 1//双重ADC采集一次存储的32位数据
uint32_t dmaDataBuffer[BATCH_DATA_LEN];//DMA数据缓冲区
1
2
3
4
5
6
7
lcd_init();
lcd_show_string(10,10,400,20,16,"Demo:Shuan ADC",BLUE);

//以多重模式DMA传输方式启动ADC1和ADC2
HAL_ADCEx_MultiModeStart_DMA(&hadc1,dmaDataBuffer,BATCH_DATA_LEN);//启动ADC1
HAL_ADCEx_MultiModeStart_DMA(&hadc2,dmaDataBuffer,BATCH_DATA_LEN);//启动ADC2
HAL_TIM_Base_Start(&htim3);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//DMA传输完成事件中断的回调函数
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
uint32_t Volt;
uint32_t adcValue=dmaDataBuffer[0]; //ADC1 and ADC2 data

uint32_t ADC1_val=adcValue & 0X0000FFFF; //low 16 bit is adc1 data
Volt=3300*ADC1_val;//mv
Volt=Volt>>12; //除以1024
lcd_show_num(10,30,Volt,sizeof(Volt),16,BLACK);

uint32_t ADC2_val=adcValue & 0XFFFF0000; //high 16 bit is adc2 data
ADC2_val=ADC2_val>>16;
Volt=3300*ADC2_val;
Volt=Volt>>12;
lcd_show_num(10,50,Volt,sizeof(Volt),16,BLACK);

}

在双ADC同步模式下,MCU自动将ADC1和ADC2一次转换的数据组合成一个32位数据,高16位是ADC1的数据,低16位是ADC2的数据。

10.DMA

工程链接:https://wwqe.lanzouo.com/ipB8z0qapiej

DMA(Direct Memory Access,直接存储器访问)是实现存储器与外设、存储器与存储器之间高效数据传输的方法

  • DMA控制器
  • DMA流
  • DMA请求
  • 仲裁器

一个DMA流配置一个DMA请求后,就构成一个单方向的DMA数据传输链路,DMA传输属性就由DMA流的参数配置决定

  • DMA流和通道
  • DMA流的优先级别
  • 源和目标的数据宽度
  • 传输数据量的大小
  • 源和目标地址指针是否自增加
  • DMA工作模式:Normal或Circular
  • DMA传输方向
  • 是否使用FIFO

DMA传输模式:

  • 外设到存储器(Peripheral To Memory),例如ADC采集的数据存入内存中的缓存区
  • 存储器到外设(Memory To Peripheral),例如将内存中的数据通过USART接口发出
  • 存储器到存储器(Memory To Memory),例如外部SRAM中的数据复制到内存中,只有DMA2控制器有这种传输模式

数据宽度:

​ 数据宽度(Data width)是源和目标传输的基本数据单元的大小,有字节(Byte)、半字(Half word)和字(word)三种大小。

DMA工作模式:

  • 正常(Normal)模式是指传输完一个缓存区的数据后,DMA传输就停止了。
  • 循环(Circular)模式是指启动一个缓存区的数据传输后,会循环执行这个DMA数据传输任务

例如,HAL_UART_Receive_DMA(),正常模式只传输一次,循环模式就能连续接收

DMA流的优先级别:

​ 每个DMA流有一个可设置的软件优先级别,有4种:

  • Very high(非常高)
  • High(高)
  • Medium(中等)
  • Low(低)

如果两个DMA流的软件优先级别相同,则流编号更小的优先级别更高,流编号就是DMA流的硬件优先级。

DMA流有自己的中断号和ISR函数

USART+DMA主程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main(void)
{
/* USER CODE BEGIN 2 */
TFTLCD_Init();
// 需要打开USART的全局中断,但是可以关闭中断事件
__HAL_UART_DISABLE_IT(&huart1, UART_IT_TC);//关闭USART1的发送完成事件中断
__HAL_UART_DISABLE_IT(&huart1, UART_IT_RXNE);//关闭USART1的接收完成事件中断
uint8_t hello1[]="Hello,DMA transmit\n";
HAL_UART_Transmit_DMA(&huart1,hello1,sizeof(hello1)); //DMA方式发送
HAL_UART_Receive_DMA(&huart1, rxBuffer,RX_CMD_LEN); //DMA方式循环接收
/* USER CODE END 2 */
/* Infinite loop */
while (1)
{
}
}

DMA文档链接

11.SPI接口通信

SPI硬件接口

SPI是串行外设接口(Serial Peripheral Interface)

SPI接口的设备分为主设备(Master)和从设备(Slave),一个主设备可以连接一个或多个从设备

SPI传输协议:

SPI通讯有4种时序模式,由SPI控制寄存器SPI_CR1中的CPOL和CPHA位控制。

  • CPOL(Clock Polority)时钟极性,控制SCK引脚在空闲状态时的电平。如果CPOL=0,则空闲时SCK为低电平;若CPOL=1,则空闲时SCK为高电平。
  • CPHA(Clock Phase)时钟相位,若CPHA=0,则在SCK的第1个边沿对数据采样;如果CPHA=1,则在SCK的第2个边沿对数据采样。

CPHA=0表示在SCK的第1个边沿读取数据,即图中虚线表示的时刻。读取数据的时刻发生在SCK的下跳沿(CPOL=1)时刻或上跳沿(CPOL=0)时刻。MISO、MOSI上的数据变化在读取数据的SCK前一个跳变沿时刻发生变化。

CPHA=1表示在SCK的第2个边沿读取数据,即图中的虚线表示的时刻。读取数据的时刻发生在SCK上跳沿(CPOL=1)时刻或下跳沿(CPOL=0)时刻。MISO、MOSI上的数据在读取数据的SCK前一个跳变沿时刻发生变化。

注:SPI主机和从机必须使用相同的SPI时序。