Stm32CubeMX学习笔记 -- GPIO、定时器中断、PWM、ADC、串口

本文内容结合个人开发经验和其他博主的见解,供个人学习使用

----待更新----

文章目录

  • ----待更新----
  • 系统
    • 选择芯片
    • 配置时钟、调试模式√
    • 中断优先级NVIC√
    • 生成code√
  • GPIO -
  • 外部中断
  • 定时器
    • 定时器中断√
    • PWM模式√
    • 编码器模式√
  • ADC
    • 单通道轮询√
    • 多通道轮询√
  • 串口√
    • 串口发送
    • 串口接收中断

系统

选择芯片

配置时钟、调试模式√

1.设置使用外部高速时钟源(8M晶振)
Stm32CubeMX学习笔记 -- GPIO、定时器中断、PWM、ADC、串口_第1张图片如果使用RTC时钟,要使能LSE。
说明:
BYPASS Clock Source(旁路时钟源)
Crystal/Ceramic Resonator(石英/陶瓷 晶振)

2.设置时钟树为72M
修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置
Stm32CubeMX学习笔记 -- GPIO、定时器中断、PWM、ADC、串口_第2张图片

3.调试模式为SW模式
Stm32CubeMX学习笔记 -- GPIO、定时器中断、PWM、ADC、串口_第3张图片

中断优先级NVIC√

NVIC即嵌套向量中断控制器,用于配置各种中断源的使能和失能,响应优先级。
除了在此处配置,在具体中断功能中的NVICsetting选项卡中也可以设置并自动保持同步。
Stm32CubeMX学习笔记 -- GPIO、定时器中断、PWM、ADC、串口_第4张图片

Preemption Priority:抢占优先级
Sub Priority:子优先级(响应优先级)
数字越小表示优先级越高

在CubeMX中默认只有抢占优先级可以进行0-15的设置,响应优先级只能为0,可以自行设置中断优先级控制位分组,分配如下:

编号 分配情况 说明
0/1/2/3 4:0 16抢占优先级,0 子优先级
4 3:1 8 抢占优先级,2 子优先级
5 2:2 4 抢占优先级,4 子优先级
6 1:3 2 抢占优先级,8 子优先级
7 0:4 0 抢占优先级,16子优先级

多个中断同时响应时,抢占优先级高的会先于抢占优先级低的执行;如果抢占优先级相同,就比较响应优先级;如果响应优先级也相同,就比较它们的硬件中断编号,编号越小优先级越高。
如果在此之前还有一个响应优先级低的还在进行,那么高的响应优先级必须等待低的先进行。

生成code√

1.工程名称,路径,编译器设置
注意工程名和路径要避免使用中文
Stm32CubeMX学习笔记 -- GPIO、定时器中断、PWM、ADC、串口_第5张图片2.代码生成设置
Stm32CubeMX学习笔记 -- GPIO、定时器中断、PWM、ADC、串口_第6张图片

GPIO -

GPIO 8 种工作模式

GPIO_Mode_AIN 模拟输入
GPIO_Mode_IN_FLOATING 浮空输入
GPIO_Mode_IPD 下拉输入
GPIO_Mode_IPU 上拉输入
GPIO_Mode_Out_OD 开漏输出
GPIO_Mode_Out_PP 推挽输出
GPIO_Mode_AF_OD 复用开漏输出
GPIO_Mode_AF_PP 复用推挽输出

1、上拉输入、下拉输入可以用来检测外部信号;例如,按键等;
2、浮空输入模式,由于输入阻抗较大,一般把这种模式用于标准通信协议的I2C、USART 的接收端;
3、普通推挽输出模式一般应用在输出电平为 0 和 3.3V 的场合。
普通开漏输出模式一般应用在电平不匹配的场合,如需要输出 5V 的高电平,就需要在外部一个上拉电阻,电源为 5V,把 GPIO 设置为开漏模式,当输出高阻态时,由上拉电阻和电源向外输出 5V 电平。
4、对于相应的复用模式(复用输出来源片上外设),则是根据 GPIO 的复用功能来选择,如 GPIO 的引脚用作串口的输出(USART/SPI/CAN),则使用复用推挽输出模式。如果用在 I2C、SMBUS 这些需要线与功能的复用场合,就使用复用开漏模式。
5、在使用任何一种开漏模式时,都需要接上拉电阻。

业务代码

//读取电平状态
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
//设置引脚状态
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState
PinState);
//转换引脚状态
void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
//锁定引脚状态
HAL_StatusTypeDef HAL_GPIO_LockPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);


//举例:
//读取PA0引脚状态
 HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
 //将PA0引脚状态改为低电平
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);

user lable位于main.h

外部中断

定时器

定时器中断√

1.配置定时器
Stm32CubeMX学习笔记 -- GPIO、定时器中断、PWM、ADC、串口_第7张图片中断间隔T = (arr+1)(psc+1) / 72M
其中72M 为时钟树设置的主频

2.开启中断
高级定时器勾选update interrupt,普通定时器勾选global interrupt
这里在系统NVIC中配置也是可以的
Stm32CubeMX学习笔记 -- GPIO、定时器中断、PWM、ADC、串口_第8张图片

3.业务代码
在主函数添加初始化代码

//__HAL_TIM_CLEAR_FLAG(&htim1,TIM_FLAG_UPDATE);//如果不想使能中断后立即进入中断,加上这一句
HAL_TIM_Base_Start_IT(&htim1);//开启定时器1中断

重写中断回调函数

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == htim1.Instance) 
	{	
	
	}
}

完成!

PWM模式√

频率 = 定时器时钟 / (psc预分频 + 1)/ (arr计数值 + 1)Hz
占空比 = ccr ( 对比值) / (arr计数值)%
驱动电机的频率一般10K左右,太低,电机有噪音;太高,功率管无法承受且给电路板带来高频干扰。占空比控制精度一般1000合适。
PWM模式1,有效电平为高,即直观上的ccr体现占空比

1.PWM配置
Stm32CubeMX学习笔记 -- GPIO、定时器中断、PWM、ADC、串口_第9张图片无需中断,不需要配置中断优先级

2.业务代码

// 使能tim8的通道
HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_1);
// 修改tim8的通道1的pwm比较值为pwmval,即修改占空比
__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, pwmval);

完成!

编码器模式√

Stm32CubeMX学习笔记 -- GPIO、定时器中断、PWM、ADC、串口_第10张图片编码器为AB相,用两路输入,编码模式设置为 Encoder Mode TI1 and TI2 则会默认检测AB相的上升沿与下降沿。每一个上升沿和下降沿都触发计数,所以每转一格计数器就会+4/-4。可以将PSC的值改为4-1,就是原脉冲数
Stm32CubeMX学习笔记 -- GPIO、定时器中断、PWM、ADC、串口_第11张图片
配置定时器两个GPIO引脚模式,全部改成Pull-Up,即上拉模式,用于没有外部上拉的编码器读取时,可以确定引脚电平,防止出错。

Stm32CubeMX学习笔记 -- GPIO、定时器中断、PWM、ADC、串口_第12张图片
业务代码:
主函数初始化代码,两个通道都要使能

HAL_TIM_Encoder_Start(&htim2,TIM_CHANNEL_1);
HAL_TIM_Encoder_Start(&htim2,TIM_CHANNEL_2);

测速

//__HAL_TIM_IS_TIM_COUNTING_DOWN(htim);   //读取电机转动方向,用不到

//测速函数,short强制转换用于转换负值
int encoder_get(TIM_HandleTypeDef *htim)
{
  int sp=0;
  sp =(short)__HAL_TIM_GET_COUNTER(htim);
  __HAL_TIM_SET_COUNTER(htim,0);
  return sp;
}

//定时器中断50ms调用
tim_it_50ms()
{
	speed = encoder_get(&htim2);//视情况加正负号
}

完成!

ADC

单通道、多通道
轮询、中断、DMA
轮询方式是阻塞式采集

开启连续模式(Continuous Conversion Mode)后,每次转换完成要调用HAL_ADC_Stop来关闭ADC,好处是可以开启一次ADC,进行多次重复采集。
例如,如果是单通道,非连续模式在完成一次ADC转换后就停止了,而连续模式会一直转换,如果是多通道AN1 ,AN2,AN3,单次转换模式会在三个通道转换完一次后停止,而连续转换模式在转换完AN3后,会从新再次去转换。

单通道轮询√

Stm32CubeMX学习笔记 -- GPIO、定时器中断、PWM、ADC、串口_第13张图片
业务代码

void ADC_Get()
{
	HAL_ADC_Start(&hadc1);
	if(HAL_ADC_PollForConversion(&hadc1,50)==HAL_OK)//等待转换完成,第二个参数表示超时时间,单位ms
	{
		ADC_Value = HAL_ADC_GetValue(&hadc1);
		ADC_Volt = ADC_Value * 3.3 / 4096.0;
	}
	HAL_ADC_Stop(&hadc1);//若关闭连续模式,可去掉此句
}

多通道轮询√

Stm32CubeMX学习笔记 -- GPIO、定时器中断、PWM、ADC、串口_第14张图片多个通道时必须开启间断模式(Discontinuous Conversion Mode),并且每个间断组中只有一个通道,否则每次只能读取到每组最后一个通道的值。后面还需设置设置通道转换顺序。

在多通道里面,第一此启用一次ADC的转换是采的第一个通道的(这里第一个通道是channel0);然后是第二个通道(channel1);第三次采又回到了第一个通道(channel0),依次类推。HAL_ADC_Start必须放在for循环中,否则只能采集第一个通道的ADC值。

uint16_t adc1_value[30];

//ADC多通道读取函数,num为通道数,阻塞式读取
void ADC_Get(uint8_t num)
{
	int i=0;
	for(i=0;i<num;i++)
	{
		HAL_ADC_Start(&hadc1);
		HAL_ADC_PollForConversion(&hadc1,50);
		adc1_value[i]=HAL_ADC_GetValue(&hadc1);
	}
	HAL_ADC_Stop(&hadc1);
}

完成!

串口√

配置协议
Stm32CubeMX学习笔记 -- GPIO、定时器中断、PWM、ADC、串口_第15张图片配置中断

Stm32CubeMX学习笔记 -- GPIO、定时器中断、PWM、ADC、串口_第16张图片

业务代码:

//发送数据,使用超时管理机制 
HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
//接收数据,使用超时管理机制 
HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
//中断模式发送
HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
//中断模式接收
HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

串口发送

//串口发送3个字节数据,最大传输时间0xffff
//字节数一定要核实,不能超过最大字节数!
HAL_UART_Transmit(&huart1, (uint8_t *)data, 3, 0xffff);

//字符串发送函数
void Usart1_String(char *str)
{ 
	int k=0;
	do
	{
		HAL_UART_Transmit(&huart1,(uint8_t * )(str+k),1,0xffff);
		k++;
	}
	while(*(str+k)!='\0');
	return;
}

注意发送函数的字节数一定要核实!过大可能导致修改其他地址的数据!!!

或者重新定义printf函数来发送:

//包含头文件
#include 


//在 USER CODE BEGIN 0 添加以下代码

//函数功能: 重定向c库函数printf到DEBUG_USARTx
int fputc(int ch, FILE *f)
{
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
  return ch;
}
//函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx
int fgetc(FILE *f)
{
  uint8_t ch = 0;
  while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) == RESET);//等待串口输入数据
  HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
  return ch;
}

在keil勾选微库选项(必须勾选,但可能导致运行速度变慢)
Stm32CubeMX学习笔记 -- GPIO、定时器中断、PWM、ADC、串口_第17张图片然后就可以使用Printf函数和scanf,getchar函数

串口接收中断

函数流程:
HAL_UART_Receive_IT(中断接收函数) -> USART2_IRQHandler(void)(中断服务函数) -> HAL_UART_IRQHandler(UART_HandleTypeDef *huart)(中断处理函数) -> UART_Receive_IT(UART_HandleTypeDef *huart) (接收函数) -> HAL_UART_RxCpltCallback(huart);(中断回调函数)

1.添加定义

//添加头文件
#include 
//添加定义
#define RXSTRSIZE  256    //最大接收字节数
uint8_t rx_string[RXSTRSIZE];    //接收字符串数组
uint8_t rx_cnt=0;         //接收字符串计数
uint8_t rx_buff;          //接收缓存

2.在主函数初始化处,调用一次接收中断函数

//中断接收1个字符,存储到rx_buff中
HAL_UART_Receive_IT(&huart1, (uint8_t *)&rx_buff, 1);   //开启接收中断

3.重写中断回调函数
回调函数中要调用一次HAL_UART_Receive_IT函数,使得程序可以重新触发接收中断

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart -> Instance == huart1.Instance)
	{
		//以下是串口接收中断业务代码
		if(rx_cnt >= RXSTRSIZE - 1)  //溢出判断
		{
			rx_cnt = 0;
			memset(rx_string,0x00,sizeof(rx_string));
			HAL_UART_Transmit(&huart1, (uint8_t *)"overfull!", 10,0xFFFF); 	
		}
	    else
	    {
			rx_string[rx_cnt++] = rx_buff;  //接收数据转存
			if(rx_string[rx_cnt-1] == 0x0A || rx_string[rx_cnt-2] == 0x0D) //判断结束位,0x0D是回车/r,0x0A是换行\n
			{
				HAL_UART_Transmit(&huart1, (uint8_t *)&rx_string, rx_cnt,0xFFFF); //将收到的信息发送出去
				while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX); //判断发送是否完毕
				memset(rx_string,0x00,sizeof(rx_string)); //清空接收字符串
				rx_cnt = 0; //清空计数器
			}
		}
		HAL_UART_Receive_IT(&huart1, (uint8_t *)&rx_buff, 1);   //再开启接收中断,若去掉只能接收一次
	}
}

完成!

附ascii码


你可能感兴趣的:(嵌入式,stm32,单片机)