本文内容结合个人开发经验和其他博主的见解,供个人学习使用
1.设置使用外部高速时钟源(8M晶振)
如果使用RTC时钟,要使能LSE。
说明:
BYPASS Clock Source(旁路时钟源)
Crystal/Ceramic Resonator(石英/陶瓷 晶振)
2.设置时钟树为72M
修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置
NVIC即嵌套向量中断控制器,用于配置各种中断源的使能和失能,响应优先级。
除了在此处配置,在具体中断功能中的NVICsetting选项卡中也可以设置并自动保持同步。
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子优先级 |
多个中断同时响应时,抢占优先级高的会先于抢占优先级低的执行;如果抢占优先级相同,就比较响应优先级;如果响应优先级也相同,就比较它们的硬件中断编号,编号越小优先级越高。
如果在此之前还有一个响应优先级低的还在进行,那么高的响应优先级必须等待低的先进行。
1.工程名称,路径,编译器设置
注意工程名和路径要避免使用中文
2.代码生成设置
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.配置定时器
中断间隔T = (arr+1)(psc+1) / 72M
其中72M 为时钟树设置的主频
2.开启中断
高级定时器勾选update interrupt,普通定时器勾选global interrupt
这里在系统NVIC中配置也是可以的
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)
{
}
}
完成!
频率 = 定时器时钟 / (psc预分频 + 1)/ (arr计数值 + 1)Hz
占空比 = ccr ( 对比值) / (arr计数值)%
驱动电机的频率一般10K左右,太低,电机有噪音;太高,功率管无法承受且给电路板带来高频干扰。占空比控制精度一般1000合适。
PWM模式1,有效电平为高,即直观上的ccr体现占空比
2.业务代码
// 使能tim8的通道
HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_1);
// 修改tim8的通道1的pwm比较值为pwmval,即修改占空比
__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, pwmval);
完成!
编码器为AB相,用两路输入,编码模式设置为 Encoder Mode TI1 and TI2 则会默认检测AB相的上升沿与下降沿。每一个上升沿和下降沿都触发计数,所以每转一格计数器就会+4/-4。可以将PSC的值改为4-1,就是原脉冲数
配置定时器两个GPIO引脚模式,全部改成Pull-Up,即上拉模式,用于没有外部上拉的编码器读取时,可以确定引脚电平,防止出错。
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);//视情况加正负号
}
完成!
单通道、多通道
轮询、中断、DMA
轮询方式是阻塞式采集
开启连续模式(Continuous Conversion Mode)后,每次转换完成要调用HAL_ADC_Stop来关闭ADC,好处是可以开启一次ADC,进行多次重复采集。
例如,如果是单通道,非连续模式在完成一次ADC转换后就停止了,而连续模式会一直转换,如果是多通道AN1 ,AN2,AN3,单次转换模式会在三个通道转换完一次后停止,而连续转换模式在转换完AN3后,会从新再次去转换。
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);//若关闭连续模式,可去掉此句
}
多个通道时必须开启间断模式(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);
}
完成!
业务代码:
//发送数据,使用超时管理机制
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勾选微库选项(必须勾选,但可能导致运行速度变慢)
然后就可以使用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码