STM32(更新中)

news2024/11/22 15:04:23

目录

1  时钟(心跳)

1.1  CubeMX基本配置

1.2  外设在时钟上的分配原理

 1.3  时钟树

2  寄存器(地址)

3  GPIO

3.1  GPIO实物

 3.2  GPIO两种结构(推挽/开漏)

3.3  LED

3.4  CUBEMX

3.5  常用函数

3.6  实验1:控制LED交替闪烁

3.7  实验2:按键控制LED的亮灭(1)

3.8  实验2:按键控制LED的亮灭(2)

4  中断

4.1  中断优先级

4.2  实验3:利用外部中断控制LED亮灭(1)(借助其他引脚)

4.3  实验3:利用外部中断控制LED亮灭(2)(按键引脚)

5  UART/USART

5.1  通讯分类

 5.2  USART

5.3  常用函数

5.4  实验4:利用蓝牙模块实现串口通信

5.5  printf/scanf的重定向

6  DMA

6.1  特性及原理

 6.2  FIFO

6.3  传输、工作模式 

6.3.1   传输模式:单次传输与突发传输

6.3.2  工作模式:FIFO模式与直接模式

6.4  实验5:利用空闲中断+串口DMA实现不定长收发

7  HAL库外设驱动原理

7.1  句柄结构体(xx_HandleTypeDef)

7.2  初始化结构体(xx_InitTypeDef)

8  定时器

8.1  基础知识

8.2  PWM

8.3  相关函数

 8.3.1  定时器基本函数

8.3.2  PWM

8.4  实验6:定时器输入捕获实验

8.5  实验7:舵机驱动

9  I2C

9.1  简介

10  SPI

10.1  简介


1  时钟(心跳)

1.1  CubeMX基本配置

不同的时钟源,通过分频或者倍频处理后送到相应的外设单元。
实际应用中根据需要配置外设的时钟控制开关,选择需要的时钟频率,并可关闭不用外设时钟。

这里以STM32F407ZG为例

1.首先根据自己的芯片类型来选择MCU(Microcontroller Unit,微控制器单元)

在这里插入图片描述

 2.SYS 选项卡勾选Serial Wire(不勾选可能会使得无法使用stlink或jlink无法下载)

在这里插入图片描述

 3.在RCC选项卡中将HSE设置为晶振

在这里插入图片描述

4.清板子上晶振原件显示的频率是多少

在这里插入图片描述

1.2  外设在时钟上的分配原理

外设以设定的组别挂在0x0000 0000到0xFFFF FFFF的内存区域中

在这里插入图片描述

以APB1为例,其所附带的外设如下

在这里插入图片描述

 1.3  时钟树

1、HSI:高速内部时钟信号 stm32单片机内带的时钟 (8M频率)精度较差

2、HSE:高速外部时钟信号精度高来源(1)HSE外部晶体/陶瓷谐振器(晶振) (2)HSE用户外部时钟

3、LSE:低速外部晶体 32.768kHz 主要提供一个精确的时钟源 一般作为RTC时钟使用

五大时钟源
LSE:低速外部晶体 32.768kHz 主要提供一个精确的时钟源 一般作为RTC时钟使用

在STM32中,有五个时钟源,为HSI、HSE、LSI、LSE、PLL:

①、HSI是高速内部时钟,RC振荡器,频率为8MHz。

②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。

③、LSI是低速内部时钟,RC振荡器,频率为40kHz。

④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。

⑤、PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。
 

2  寄存器(地址

//寄存器编程 
int main()
{
	volatile unsigned int* pointer=(unsigned int*)0x40028000;//volatile的作用是防止变量被编译器优化 
	*pointer+=1;
	*pointer+=2;	//优化后*pointer+=3 对于寄存器来说先后加1和2和直接加3是不一样的 
}
#define pointer (volatile unsigned int*)0x40028000 //预处理不占用内存 

int main()
{
	*pointer+=1;
	*pointer+=2;	//优化后*pointer+=3 对于寄存器来说先后加1和2和直接加3是不一样的 
}

3  GPIO

GPIO(general porpose intput output):通用输入输出端口的简称。可以通过软件控制其输出和输入。stm32芯片的GPIO引脚与外部设备连接起来,从而实现与外部通信,控制以及数据采集的功能。

3.1  GPIO实物

 3.2  GPIO两种结构(推挽/开漏)

 

推挽输出:当IN是高电平时,上边的三极管处于导通状态,下边的三极管处于断开状态,OUT与VCC相连;当IN是低电平时,上边的三极管处于断开状态,下边的三极管处于导通状态,OUT与GND相连

开漏输出:OUT默认与VCC相连,当IN是高电平时,三极管处于断开状态,OUT与VCC相连;当IN是低电平时,三极管处于导通状态,OUT与GND相连

由此可见:推挽输出的控制能力更强,开漏输出有线与的特点(

两个或两个以上与非门的输出端连接在同一条导线上,将这些与非门上的数据(状态电平)用同一条导线输送出去。

3.3  LED

//基地址+偏移地址 
#define GPIOB_CLK (*(volatile unsigned int*)(0x40021000+0x18))
#define GPIOB_CRL (*(volatile unsigned int*)(0x40010c00+0x00))
#define GPIOB_ODR (*(volatile unsigned int*)(0x40010c00+0x0c))

int main()
{
	//1.使能GPIOB的外设时钟
	GPIOB_CLK|=(1<<3); 
	//2.GPIOB配置推挽输出模式
	GPIOB_CRL&= ~(0xf<<(4*0));//清除低4位寄存器 
	GPIOB_CRL|=(2<<0); 
	
	GPIOB_ODR&= ~(0x1<<(1*0));//清除低1位寄存器
	GPIOB_ODR|=(1<<0);
	 
} 

清除位的步骤解释:0xf是1111 

 按位取反:

3.4  CUBEMX

configuration:配置

GPIO output level: 输出电平

GPIO mode:输出模式(推挽输出/开漏输出)

GPIO Pull-up/Pull-down:上拉/下拉

Maximum output speed:最大输出速度

User Label:用户定义标签

这里说一下GPIO上拉下拉:

GPIO通常有三种状态:高电平、低电平和高阻态。高阻态换句话说就是断开状态或浮空态。因此上拉和下拉其中一个强大的理由就是为了防止输入端悬空,使其有确定的状态。减弱外部电流对芯片的产生的干扰。

上拉就是将不确定的信号通过一个电阻提升为高电平,这个上拉的电阻的选择通常有讲究,通常是驱动能力和功耗的平衡,若GPIO为输出为高电平,一般来说,上拉电阻越小,驱动能力越强,但功耗也就越大,同时还要考虑下级电路对驱动能力的要求,上拉电阻选择的合适才能向下级电路提供足够的电流。另外就是数字电路对高低电平都有一个门槛,以上拉电阻为例,输出高电平自然是被拉高,但输出低电平的时候,通常内部的开关管会被导通到地,这必须确保内部导通到地这一段之间的电阻和上拉电阻的比值足够让其电平处在零电平门槛之下。对频率比较高的时候,上拉电阻和开关管漏源级之间的电容和下级电路之间的输入电容会形成“RC延迟”,电阻越大,延迟越大。下拉电阻的选择原理和上拉电阻是一样的。

3.5  常用函数

HAL_GPIO_Init(GPIO_TypeDef  *GPIOx, GPIO_InitTypeDef *GPIO_Init);//初始化引脚
HAL_GPIO_DeInit(GPIOA,GPIO_PIN_1);//初始化PA1引脚为复位状态
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET);//将PA1置高电平
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET);//将PA1置低电平
HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1);//读取PA1电平状态
HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_1);//取反PA1电平
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);//调用中断回调函数
HAL_GPIO_EXTI_Callback(GPIO_PIN_1);//中断函数具体要响应的动作
HAL_GPIO_LockPin(GPIOA,GPIO_PIN_1);//锁定PA1电平状态

3.6  实验1:控制LED交替闪烁

第一步:基础配置(RCC+时钟树)

第二步:参照数据手册,找到LED对应的引脚,并开启GPIO输出功能:

第三步:生成代码

第四步:写程序

while (1)
  {
    /* USER CODE END WHILE */
		HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_SET);//高电平时灭
		HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_RESET);//低电平时亮
		HAL_Delay(1000);//延时函数
		HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_RESET);//低电平时亮
		HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_SET);//高电平时灭
		HAL_Delay(1000);//延时函数
    /* USER CODE BEGIN 3 */
  }

第五步:编译+写入 

3.7  实验2:按键控制LED的亮灭(1)

第一步:基础配置(RCC+时钟树)

第二步:参照数据手册,找到LED和按键(KEY0,KEY1)对应的引脚,并开启GPIO输出/输入功能:

 

第三步:设置上拉输入

上拉输入相当于接一个VCC端,如果按键没有按下,那么IN端与VCC端相连,是高电平

如果按键按下,那么IN端与GND端相连,是低电平

第四步:生成代码

第五步:写程序

while (1)
  {
    /* USER CODE END WHILE */
		if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3)==GPIO_PIN_RESET)
		{
			HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_RESET);
			HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_SET);
		}
		if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)==GPIO_PIN_RESET)
		{
			HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_SET);
			HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_RESET);
		}
    /* USER CODE BEGIN 3 */
  }

第六步:编译+写入 

3.8  实验2:按键控制LED的亮灭(2)

第一步:基础配置(RCC+时钟树)

第二步:参照数据手册,找到LED和按键(KEY0,KEY1,WK_UP)对应的引脚,并开启GPIO输出/输入功能:

 

第三步:设置上拉/下拉输入

同理,由于WK_UP按键接VCC,所以配置下拉输入

第四步:生成代码

第五步:写程序

while (1)
  {
    /* USER CODE END WHILE */

		if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3)==GPIO_PIN_RESET)
		{
			HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_RESET);
			HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_SET);
		}
		if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)==GPIO_PIN_RESET)
		{
			HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_SET);
			HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_RESET);
		}
		if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_SET)
		{
			HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_RESET);
			HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_RESET);
		}
    /* USER CODE BEGIN 3 */
 
  }

第六步:编译+写入 

4  中断

中断是指CPU在执行当前程序时系统出现了某种状况,使得CPU必须停止当前程序,而去执行另一段程序来处理的出现的紧急事务,处理结束后CPU再返回到原先暂停的程序继续执行,这个过程就称为中断。

4.1  中断优先级

1)中断优先级分为两种,可编程和不可编程,可编程的表示可以自己修改中断优先级,不可编程的就不能修改

(2)对于STM32中断优先级,决定着内核优先响应谁的中断请求

(3)小值优先原则,中断优先级数值越小,中断就会被优先相应

(4)中断优先级按照优先级分组配置

STM32上只使用M3内核支持的8bit优先级中的高4位bit,也就是STM32支持2^4个优先级

bit7bit6bit5bit4bit3bit2bit1bit0
用于表达优先级未使用,读回为0


使用这4个bit,组织成5组优先级分组,每组分为1个抢占组,1个子优先级组

优先级分组    抢占优先级    子优先级    描述
NVIC_PriorityGroup_0    0    0-15    主-0bit,子-4bit
NVIC_PriorityGroup_1    0-1    0-7    主-1bit,子-3bit
NVIC_PriorityGroup_2    0-3    0-3    主-2bit,子-2bit
NVIC_PriorityGroup_3    0-7    0-1    主-3bit,子-1bit
NVIC_PriorityGroup_4    0-15    0    主-4bit,子-0bit


对于组0,抢占优先级为0,表示他没有抢占优先级,4个bit全部用来表示子优先级。对于组1,抢占优先级为0-1,用1个bit表示抢占优先级,其余3个bit表示子优先级...

(1)通过优先级分组,可以管理中断的响应顺序

(2)只有抢占优先级才由抢占中断权限,发生中断嵌套,打断就发生中断嵌套,没有能力打断,那就被挂起

假如事件A抢占优先级为0,B的抢占优先级为10,在B执行过程中,A发出中断请求,则会抢过B中断的使用权,等A执行完毕再继续执行B(ps:如果A并不能打断事件B,A就会被挂起)

(3)如果中断抢占优先级相同,不发生抢占

(4)如果多个挂起的中断具有相同的抢占优先级,则子优先级高的先行,如果子优先级相同,则IRQ(通常指外部中断请求)编号小的先行

所以:主优先级>子优先级>IRQ编号 

4.2  实验3:利用外部中断控制LED亮灭(1)(借助其他引脚)

第一步:基础配置(下同)

第二步:配置

第三步:生成代码

第四步:写程序

中断回调函数重写(用串口收发检验是否成功进入函数,Delay尽量不要用在中断中,容易卡,这里待改进)

char buffer1[100]={"第一个中断"};
char buffer2[100]={"第二个中断"};
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)//重写中断回调函数
{
	if(GPIO_Pin==GPIO_PIN_1)
	{
		HAL_UART_Transmit_DMA(&huart1,(unsigned char*)buffer1,100);//检验是否进入函数
		HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_RESET);
		HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_SET);
		HAL_Delay(1000);//延时一秒
		HAL_NVIC_SystemReset();//复位函数
	}
	if(GPIO_Pin==GPIO_PIN_2)
	{
		HAL_UART_Transmit_DMA(&huart1,(unsigned char*)buffer2,100);
		HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_RESET);
		HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_SET);
		HAL_Delay(1000);
		HAL_NVIC_SystemReset();
	}
}

中断服务函数

while (1)
  {
    /* USER CODE END WHILE */
		if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3)==GPIO_PIN_RESET)
		{
			HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);//中断服务函数
		}
		if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)==GPIO_PIN_RESET)
		{
			HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2);
		}
    /* USER CODE BEGIN 3 */
 
  }

 第五步:编译+写入

4.3  实验3:利用外部中断控制LED亮灭(2)(按键引脚)

第一步

第二步 :配置

这里按键仍然选择上拉输入(原因同上)

中断触发模式选择上升沿/下降沿触发

第三步

第四步:写程序

/* USER CODE BEGIN 0 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)==GPIO_PIN_SET)
	{
		HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_SET);
	}
	if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)==GPIO_PIN_RESET)
	{
		HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_RESET);
	}
}
/* USER CODE END 0 */

while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_RESET);
		HAL_Delay(5000);
		HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_SET);
		HAL_Delay(5000);
  }

第五步 

5  UART/USART

5.1  通讯分类

比特率:单位时间内传输了多少位(比特),单位是bit/s

波特率:单位时间内传输了多少个符号。

波特率与比特率的关系为:比特率=波特率X单个调制状态对应的二进制位数。

              I=S*log2(N)

 5.2  USART

CubeMX:

Baud Rate:波特率

Word Length:字长

Parity:校验位

Stop Bits:停止位

串口调试助手:

 

5.3  常用函数

HAL_UART_Init(&huart1);//初始化串口1函数
HAL_UART_MspInit(&huart1);//串口1回调函数,主要进行硬件部分的初始化
HAL_UART_Transmit(&huart1, uint8_t *pData, uint16_t Size, uint32_t Timeout);//串口1发送数据函数
HAL_UART_Receive(&huart1, uint8_t *pData, uint16_t Size, uint32_t Timeout);//串口1接收数据函数
HAL_UART_Transmit_IT(&huart1, uint8_t *pData, uint16_t Size);//串口1中断方式发送数据
HAL_UART_Receive_IT(&huart1, uint8_t *pData, uint16_t Size);//串口1中断方式接受数据
HAL_UART_Transmit_DMA(&UART1_Handler, uint8_t *pData, uint16_t Size);//DMA方式发送数据
HAL_UART_Receive_DMA(&UART1_Handler, uint8_t *pData, uint16_t Size);//DMA接收数据
HAL_UART_DMAPause(&UART1_Handler);//暂停DMA数据传输
HAL_UART_DMAResume(&UART1_Handler);//从暂停状态中恢复DMA传送
HAL_UART_DMAStop(&UART1_Handler);//停止DMA的传输
HAL_UART_IRQHandler(&huart1);//串口1中断处理函数
HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);//串口发送 数据完毕的回调函数,当串口使用中断模式发送完毕后才能自动调用本函数。
HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);//发送数据一半回调函数
HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);//串口收发数据出错时的回调函数。
HAL_LIN_SendBreak(UART_HandleTypeDef *huart);//LIN总线通信函数,发送一个断开连接的标识。
HAL_MultiProcessor_EnterMuteMode(UART_HandleTypeDef *huart);//多核处理器进入串口静音模式。

5.4  实验4:利用蓝牙模块实现串口通信

CH340模块(USB转TTL)       HC-05模块

VCC(5V)  ——————————  VCC

     GND   ——————————  GND

     RXD   ——————————  TXD

     TXD   ——————————  RXD
 

最开始,用USB转TTL连接HC-05模块(注意此时需要先按住HC-05模块上面的按钮再上电),这时会进入AT模式(此时的灯应该是间隔比较长时间才灭一次),然后打开串口调试助手,波特率设为38400,接下来进行初始化操作

 

 按照步骤

配置蓝牙模块基本信息

     AT+NAME=HC-05   修改蓝牙模块名称为HC-05

    AT+ROLE=0    蓝牙模式为从模式

   AT+CMODE=1   蓝牙连接模式为任意地址连接模式,也就是说 该模块可以被任意蓝牙设备连接

   AT+PSWD=1234   蓝牙配对密码为1234

   AT+UART=9600,0,0  蓝牙通信串口波特率为9600,停止位1位, 无校验位

 

我们直接把蓝牙模块插到原来USB转TTL的位置上(蓝牙模块的用处就是可以无线调试而不用电脑上的虚拟串口调试) (注意不要按按钮)

我们在手机上安装蓝牙串口调试软件,并配对(注意在这之前手机的设置->蓝牙里已经连接成功)

然后这时会发现灯处于长灭状态(偶尔亮一会),就可以正常使用了,我们在手机里输入数据,跟原来用电脑的虚拟串口一模一样

5.5  printf/scanf的重定向

int fputc(int c,FILE *stream)//printf
{
	uint8_t ch[1]={c};
	HAL_UART_Transmit(&huart1,ch,1,0xFFFF);
	return c;
}

int fgetc(FILE *stream)//scanf
{
	uint8_t ch[1];
	HAL_UART_Receive(&huart1,ch,1,0xFFFF);
	return ch[0];
}

6  DMA

6.1  特性及原理

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

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

DMA定义:
DMA用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU的干预,通过DMA数据可以快速地移动。这就节省了CPU的资源来做其他操作。

DMA传输方式
DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节,主要涉及四种情况的数据传输,但本质上是一样的,都是从内存的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)。四种情况的数据传输如下:

外设到内存
内存到外设
内存到内存
外设到外设


总结:DMA就是CPU的帮手

首先需要建立传输通道(存储器->存储器、存储器->外设、外设->外设)

其次确定传输对象(UART(源)->内存(目标)、内存(目标)->UART(源))

最后敲定传输细节(确定由谁来产生DMA请求、通道优先级、确定传输双方的数据格式、确定数据是否需要循环采集(循环模式是否使能)、是否需要传输标志/中断)

 6.2  FIFO

FIFO(First In First Out,即先入先出),是一种数据缓冲器。先被写入的数据会按顺序先被读出。FIFO可看做一个管道,有数据写入端口和 数据读取端口:

如图,数据写入端口从1~10依次写入数据,则数据读取端口也从1~10依次读取数据。输出端口每读出一位数据,FIFO中的后一位数据就向前移一位。如读取端口读出1、2、3后FIFO输出端口的第一位变为4。

6.3  传输、工作模式 

6.3.1   传输模式:单次传输与突发传输

单次传输模式下,一次操作(软件)只能传输一次(一次可以理解为一个节拍,如一个字节)

突发传输模式下,一次操作可以传输多次,如4次,8次,16次(利用到FIFO)

6.3.2  工作模式:FIFO模式与直接模式

FIFO模式下,可以将要传输的多个数据(或字节)累计存储在FIFO缓冲器中,然后在FIFO缓冲器中设置存储阈值,当到达阈值时,FIFO会自动把所有存储的数据一次性的发送到目标地址;

直接模式下,DMA直接进行数据从源地址到目的地址的传输,对于外设的传输,因为外设内部一般也有FIFO,所以传输的数据可以被直接存储在外设的FIFO中。

6.4  实验5:利用空闲中断+串口DMA实现不定长收发

第一步

第二步:配置

第三步

第四步:写程序

while循环外

HAL_UART_Receive_DMA(&huart1,buffer,3);
	__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);

 在it.c文件中找到串口中断服务函数

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
	if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)!=RESET)
	{
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);
		HAL_UART_DMAStop(&huart1);
		len=3-__HAL_DMA_GET_COUNTER(huart1.hdmarx);
		HAL_UART_Transmit_DMA(&huart1,buffer,len);
		HAL_UART_Receive_DMA(&huart1,buffer,3);
	}
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

第五步 

7  HAL库外设驱动原理

7.1  句柄结构体(xx_HandleTypeDef)

Instance:它指向了外设内,一个具体的外设成员。如:ADC里的ADC1、ADC2,UART里的UART1、UART2,DMA里的Channel1、Channel2,实际上他用指针指向一个外设基地址

Init:指向了一个具体外设的初始化结构体用来配置外设的工作参数

7.2  初始化结构体(xx_InitTypeDef)

8  定时器

8.1  基础知识

 递增模式:

递减模式: 

中心对齐模式: 

 

8.2  PWM

8.3  相关函数

 8.3.1  定时器基本函数

/* Time Base functions ********************************************************/
HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim);
HAL_StatusTypeDef HAL_TIM_Base_DeInit(TIM_HandleTypeDef *htim);
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim);
void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef *htim);
/* Blocking mode: Polling */
HAL_StatusTypeDef HAL_TIM_Base_Start(TIM_HandleTypeDef *htim);
HAL_StatusTypeDef HAL_TIM_Base_Stop(TIM_HandleTypeDef *htim);
/* Non-Blocking mode: Interrupt */
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);
HAL_StatusTypeDef HAL_TIM_Base_Stop_IT(TIM_HandleTypeDef *htim);
/* Non-Blocking mode: DMA */
HAL_StatusTypeDef HAL_TIM_Base_Start_DMA(TIM_HandleTypeDef *htim, uint32_t *pData, uint16_t Length);
HAL_StatusTypeDef HAL_TIM_Base_Stop_DMA(TIM_HandleTypeDef *htim);
HAL_TIM_PeriodElapsedCallback();//中断回调函数

8.3.2  PWM

/* Timer PWM functions *********************************************************/
HAL_StatusTypeDef HAL_TIM_PWM_Init(TIM_HandleTypeDef *htim);
HAL_StatusTypeDef HAL_TIM_PWM_DeInit(TIM_HandleTypeDef *htim);
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim);
void HAL_TIM_PWM_MspDeInit(TIM_HandleTypeDef *htim);
/* Blocking mode: Polling */
HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);
HAL_StatusTypeDef HAL_TIM_PWM_Stop(TIM_HandleTypeDef *htim, uint32_t Channel);
/* Non-Blocking mode: Interrupt */
HAL_StatusTypeDef HAL_TIM_PWM_Start_IT(TIM_HandleTypeDef *htim, uint32_t Channel);
HAL_StatusTypeDef HAL_TIM_PWM_Stop_IT(TIM_HandleTypeDef *htim, uint32_t Channel);
/* Non-Blocking mode: DMA */
HAL_StatusTypeDef HAL_TIM_PWM_Start_DMA(TIM_HandleTypeDef *htim, uint32_t Channel, uint32_t *pData, uint16_t Length);
HAL_StatusTypeDef HAL_TIM_PWM_Stop_DMA(TIM_HandleTypeDef *htim, uint32_t Channel);
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);

8.4  实验6:定时器输入捕获实验

第一步

第二步:配置

第三步:生成代码

第四步:写程序

 定义变量

uint8_t CaptureSta=0;//输入捕获状态
uint16_t CaptureVal=0;//输入捕获值

定时器输入捕获中断处理回调函数

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance==TIM2)//确定中断的是TIM2
	{
		if((CaptureSta&0x80)==0)//还没有成功捕获
		{
			if(CaptureSta&0x40)//捕获到一个下降沿
			{
				CaptureSta|=0x80;//标记成功捕获到一次高电平脉冲
				CaptureVal=HAL_TIM_ReadCapturedValue(&htim2,TIM_CHANNEL_1);//获得捕获值
				TIM_RESET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_1);//一定要重新设置
				TIM_SET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_1,TIM_ICPOLARITY_RISING);//
			}
			else
			{
				CaptureSta=0;
				CaptureVal=0;
				CaptureSta|=0x40;
				__HAL_TIM_DISABLE(&htim2);
				__HAL_TIM_SET_COUNTER(&htim2,TIM_CHANNEL_1);
				TIM_RESET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_1);
				TIM_SET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_1,TIM_ICPOLARITY_FALLING);
				__HAL_TIM_ENABLE(&htim2);
			}
		}
	}
}

定时器溢出中断回调函数

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance==TIM2)
	{
		if((CaptureSta&0x80)==0)
		{
			if(CaptureSta&0x40)
			{
				if((CaptureSta&0x3F)==0x3F)
				{
					TIM_RESET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_1);
					TIM_SET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_1,TIM_ICPOLARITY_FALLING);
					CaptureSta|=0X80;
					CaptureVal=0xFFFF;
				}
				else
				{
					CaptureSta++;
				}
			}
		}
	}
}

printf重定向

int fputc(int c,FILE *stream)//printf
{
	uint8_t ch[1]={c};
	HAL_UART_Transmit(&huart1,ch,1,0xFFFF);
	return c;
}

main函数 

while (1)
  {
    /* USER CODE END WHILE */
		if(CaptureSta&0x80)//成功捕获一次高电平
		{
			int temp=CaptureSta&0x3F;
			temp*=1000000;//溢出时间总和
			temp+=CaptureVal;//得到总的高电平时间
			printf("HIGH:%d us\r\n",temp);
			CaptureSta=0;//开启下一次捕获
		}
		int t;
		t++;
		if(t>20)//检验程序正常进行
		{
			t=0;
			HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_9);
		}
		HAL_Delay(10);
    /* USER CODE BEGIN 3 */
  
  /* USER CODE END 3 */
	}

第五步:编译+写入

8.5  实验7:舵机驱动

 我们打开TIM3定时器,这个定时器是挂在APB1总线上的,根据配置好的时钟树可知

频率是84MHz 

然后为了方便计算,我们把分频系数设置成84-1,这样相当于1秒钟点1000000次

由于舵机的驱动信号是50Hz,周期是0.02s,所以在一个周期内会点20000次

舵机的转动角度是按照高电平的占空比来计算的

 

经过手算,我们可以大致推出角度和比较值之间的关系,舵机的难点也就在这了

舵机                  单片机

信号线(黄)-------PA6(根据自己的定时器而定)

电源线(红)-------ST-LINK上的5V(9/10)

地线(棕)----------GND

9  I2C

9.1  简介

10  SPI

10.1  简介

 

 

.. 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1406285.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

最强生产力|卸载并重装Anaconda3

一、Anaconda3卸载 &#xff08;一&#xff09;官方方案一&#xff08;Uninstall-Anaconda3-不能删除配置文件&#xff09; 官方推荐的方案是两种&#xff0c;一种是直接在Anaconda的安装路径下&#xff0c;双击&#xff1a; &#xff08;可以在搜索栏或者使用everything里面搜…

使用css将文字在水平线中显示

方法一&#xff1a; 1.效果图 2.html <!-- <div class"line">第三方登录</div> --> 3.css /* 让文字在水平线中显示 */.line {display: flex;flex-direction: row;color: #ccc;font-size: 18px;font-weight: bolder; }.line:before, .line:aft…

Spring boot3.x 无法向 Nacos2.x进行服务注册的问题

一&#xff1a;问题描述 配置中心都是可用的&#xff0c;但是就是无法向nacos进行服务注册。 二&#xff1a;问题可能出现的原因有如下两种 1.Nacos2.0版本相比1.X新增了gRPC的通信方式&#xff0c;因此需要增加2个端口。除了8848还需要开放9848&#xff0c;9849端口。 官方…

阿里云负载均衡对接

1 、开通负载均衡产品 2 、ALB / NLB / CLB ALB&#xff1a; 应用型负载均衡 &#xff0c; 给定对应服务域名与当前实例DNS绑定之后即可使用 支持&#xff1a; HTTP/HTTPS/QUIC等应用层流量协议 NLB&#xff1a; 网络型负载均衡 支持&#xff1a; TCP / UDP / TCPSSL C…

安装miniconda、tensorflow、libcudnn

目录 安装miniconda 安装tensorflow 安装 libcudnn 安装miniconda wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh && bash Miniconda3-latest-Linux-x86_64.sh 安装tensorflow tensorflow官网&#xff0c;查看版本对应 https:…

阿里云 Flink 原理分析与应用:深入探索 MongoDB Schema Inference

摘要&#xff1a;本文整理自阿里云 Flink 团队归源老师关于 阿里云 Flink 原理分析与应用&#xff1a;深入探索 MongoDB Schema Inference 的研究&#xff0c;内容主要分为以下四部分&#xff1a; 1. MongoDB 简介 2. 社区MongoDB CDC 核心特性 3. MongoDB CDC 在阿里云 Flink …

网络通信(18)-C#TcpClient 和 TcpListener的使用实例

本文演示C#TcpClient 和 TcpListener的使用实例,掌握基本用法。 目录 TcpListener服务器 客户端TcpClient TcpListener服务器 界面 UI代码 namespace TcpListenerDemo {partial class Form1{/// <summary>/// 必需的设计器变量。/// </summary>private System…

【GitHub项目推荐--不错的Rust开源项目】【转载】

01 Rust 即时模式 GUI 库 egui 是一个简单、快速且高度可移植的 Rust 即时模式 GUI 库&#xff0c;可以轻松地将其集成到你选择的游戏引擎中&#xff0c;旨在成为最易于使用的 Rust GUI 库&#xff0c;以及在 Rust 中制作 Web 应用程序的最简单方法。 项目地址&#xff1a;ht…

对齐大型语言模型与人类偏好:通过表示工程实现

1、写作动机&#xff1a; 强化学习表现出相当复杂度、对超参数的敏感性、在训练过程中的不稳定性&#xff0c;并需要在奖励模型和价值网络中进行额外的训练&#xff0c;导致了较大的计算成本。为了解决RL方法带来的上述挑战&#xff0c;提出了几种计算上轻量级的替代方案&…

图像处理算法:白平衡、除法器、乘法器~笔记

参考&#xff1a; 基于FPGA的自动白平衡算法的实现 白平衡初探 (qq.com) FPGA自动白平衡实现步骤详解-CSDN博客 xilinx 除法ip核&#xff08;divider&#xff09; 不同模式结果和资源对比&#xff08;VHDL&ISE&#xff09;_ise除法器ip核-CSDN博客 数…

关于C#中的HashSet<T>与List<T>

HashSet<T> 表示值的集合。这个集合的元素是无须列表&#xff0c;同时元素不能重复。由于这个集合基于散列值&#xff0c;不能通过数组下标访问。 List<T> 表示可通过索引访问的对象的强类型列表。内部是用数组保存数据&#xff0c;不是链表。元素可重复&#xf…

Istio-gateway

一. gateway 在 Kubernetes 环境中&#xff0c;Kubernetes Ingress用于配置需要在集群外部公开的服务。但是在 Istio 服务网格中&#xff0c;更好的方法是使用新的配置模型&#xff0c;即 Istio Gateway&#xff0c;Gateway 允许将 Istio 流量管理的功能应用于进入集群的流量&…

C#基础:用ClosedXML实现Excel写入

直接在控制台输出&#xff0c;确保安装了该第三方库 using ClosedXML.Excel;class DataSource {public int id { get; set; }public string name { get; set; } "";public string classes { get; set; } "";public int score { get; set; } } class Te…

深入浅出hdfs-hadoop基本介绍

一、Hadoop基本介绍 hadoop最开始是起源于Apache Nutch项目&#xff0c;这个是由Doug Cutting开发的开源网络搜索引擎&#xff0c;这个项目刚开始的目标是为了更好的做搜索引擎&#xff0c;后来Google 发表了三篇未来持续影响大数据领域的三架马车论文&#xff1a; Google Fil…

spring Cloud Stream 实战应用深度讲解

springCloudStream 简介 Spring Cloud Stream是一个框架&#xff0c;用于构建与共享消息传递系统连接的高度可扩展的事件驱动微服务。 该框架提供了一个灵活的编程模型&#xff0c;该模型建立在已经建立和熟悉的 Spring 习惯用语和最佳实践之上&#xff0c;包括对持久发布/订…

【Maven】-- 打包添加时间戳的两种方法

一、需求 在执行 mvn clean package -Dmaven.test.skiptrue 后&#xff0c;生成的 jar 包带有自定义系统时间。 二、实现 方法一&#xff1a;使用自带属性&#xff08;不推荐&#xff09; 使用系统时间戳&#xff0c;但有一个问题&#xff0c;就是默认使用 UTC0 的时区。举例…

什么叫特征分解?

特征分解&#xff08;Eigenvalue Decomposition&#xff09;是将一个方阵分解为特征向量和特征值的过程。对于一个 nn 的方阵A&#xff0c;其特征向量&#xff08;Eigenvector&#xff09;v 和特征值&#xff08;Eigenvalue&#xff09; λ 满足以下关系&#xff1a; 这可以写…

Python批量自动处理文件夹

假如在你的电脑硬盘某文件夹里有这样一堆不同格式的文件&#xff0c;看起来非常混乱&#xff0c;详情如图所示&#xff1a; 为了方便自己快速检索到文件&#xff0c;我们需要将这些文件按照不同格式分类整理到不同的文件夹中&#xff0c;应该怎么做呢&#xff1f;源码如下&…

v39.for循环while循环

1.循环引入&#xff08;loops&#xff09; 2.while loop 当括号中表达式expression返回真值时&#xff0c;代码块执行 。之后将再次检查expression &#xff0c;如果仍然返回真值&#xff0c;继续执行......直到expression返回假值。 3.for loop 有初始化、条件、递增/减 步骤。…

Zookeeper集群 + Kafka集群,Filebeat+Kafka+ELK

目录 什么是Zookeeper&#xff1f; Zookeeper 工作机制 Zookeeper 特点 Zookeeper 数据结构 Zookeeper 选举机制 实验 部署 Zookeeper 集群 1.安装前准备 安装 JDK 下载安装包 2.安装 Zookeeper 修改配置文件 拷贝配置好的 Zookeeper 配置文件到其他机器上 在每个节…