小蜜蜂老师32cubemx学习笔记,对一些实验过程中可能出现的情况做出解释,对老师做补充,重复性工作ctrl+c/v使用,前期略显粗糙,后续随时更新,有错误感谢指正,根据自己板子的情况对视频中的程序做了调整,比如实在懒得加第二个LED灯~还有手头没有光敏,但有一个土壤湿度~很多地方写了注释,说不定你不明白的地方就写在注释里面~
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/90934c9196fd37a640380ba271f0a5ab.png)
这个是参考视频的链接,感谢这位UP我才能学的这么快😄点击即可:小蜜蜂老师B站课程链接
编译一下就有那个打开折叠的加号了~
有IT就对应中断,就要设置NVIC使能,就有回调函数,满足对应条件就会触发回调函数,中断的思维很重要~
要注意你的板载LED灯是不是低电平才亮,接的单片机的哪个引脚捏~
不得不说这软件真的好好用啊~
回调函数里不要用HAL_Delay()
一、按键
如果按键接地的话把设置为输入的脚上拉,否则会一直判断成低电平
while的效果起到暂停的效果,while()里面的判断式为真,就会一直卡在循环里不执行后面的内容。
/* USER CODE BEGIN 0 */
#define key HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_4)
void delay(unsigned int t)
{
while(t--);
}
void button_scan()
{
if(key==0)
{
delay(1000);
if(key==0)
{
while(key==0);
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);//调换while的位置,实现是按钮按下瞬间执行翻转,还是按下后再抬起的瞬间执行反转电平
}
}
}
//这一段发现灯高频闪烁,是因为PB4是悬空状态(没上下拉),PB4应该是低电平,将PB4上拉即可稳定下来
/* USER CODE END 0 */
二、外部中断
在cubemx里面设置好是上升沿下降沿还是上升下降都行,如上升沿,电平上升就会进入回调函数,如下:
/* USER CODE BEGIN 0 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin==GPIO_PIN_10)
{
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
}
}
/* USER CODE END 0 */
三、定时器中断
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM2)
{
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
}
if(htim->Instance==TIM3)
{
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
}
}
//定时器中断回调函数与中断判断,对应定时器中断会触发电平反转
初始化部分:
HAL_TIM_Base_Start_IT(&htim2);//定时器启动函数
原型:
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);
四、PWM
PWM参考视频
通过预分频器调整计数频率为1MHz,每秒(1000ms)计数1000000个(每毫秒1000个数),计数从零达到重装载值,计数清空为一个周期,对舵机而言要调整为一个周期为20ms,也就是计到20000个数清空计数到0,重装载值是20000(填到cubemx里是20000-1),比较值根据你要转动的角度计算占空比,根据占空比计算比较值,如舵机转0°需要20ms周期,0.5ms脉冲,占空比就是0.5/20=0.025,重装载值-重装载值×占空比=比较值
项目 | 数值 |
---|---|
舵机转动角度 | 0° |
占空比 | 0.025 |
重装载值 | 20000 |
比较值 | 19500 |
前提是你的预分频器设置的保证计数频率是1MHz,根据你设置的时钟来定。
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_4);
/* USER CODE END 2 */
while (1)
{
for(uint16_t cnt=0;cnt<1000;cnt++)
{
__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_4,cnt);
HAL_Delay(1);
}
for(uint16_t cnt=1000;cnt>0;cnt--)
{
__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_4,cnt);
HAL_Delay(1);
}
/* USER CODE END WHILE */
五、UART
有的串口助手没有显示文本功能,所以没办法显示中文(中文是乱码,改成同义的英文即可)。
定义与宏:
uint8_t Tx_str1[]="Hello,world!\r\n";
uint8_t Tx_str2[]="LED1_Open!\r\n";
uint8_t Tx_str3[]="LED1_Close!\r\n";
//字符串声明
#define LED1_ON() HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,1);//宏定义的方法,不仅可以定义返回值,还可以定义函数
串口接收中断回调函数:
//回调函数的用法
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance==USART2)//传递过来的实例是usart2的话
{
if(Rx_dat==0xa1)
{
LED1_ON();
LED2_OFF();
HAL_UART_Transmit(&huart2,Tx_str1,sizeof(Tx_str1),10000);
LED2_OFF();
HAL_UART_Receive_IT(&huart2,&Rx_dat,1);//记得重新打开,写到内层的if里就行,继续接收中断请求,写到while(1)里面也可以
}
else if(Rx_dat==0xa2)
{
LED1_OFF();
LED2_ON();
HAL_UART_Transmit(&huart2,Tx_str1,sizeof(Tx_str1),10000);
LED2_OFF();
HAL_UART_Receive_IT(&huart2,&Rx_dat,1);
}
}
}
初始化部分:
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);
HAL_UART_Transmit(&huart2,Tx_str1,sizeof(Tx_str1),10000); //非阻塞式输出,启动提示,最后一位是超时重置
HAL_UART_Receive_IT(&huart2,&Rx_dat,1); //初始化的时候需要写一下,开启接收
//接收和传输
六、定时器串口混合应用
在cubemx里面把预分频器与主计数器设置好数值~
手动引入新的头文件、定义与宏,位置:BEGIN0:
#include "stdio.h"//手动加入在USER CODE BEGIN Includes 中
#define LED_on() HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET)//这个位置不带分号,后边调用该宏就要加上分号,如果这个位置加分号,后面调用时再加分号相当于有两个分号
#define LED_off() HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET)
#define LED_invert() HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13)
uint8_t str1[]="==============Hello!================\r\n";
uint8_t hh=0,mm=0,ss=0,ss05=0;
uint8_t str_buff[64];//64个字节
uint8_t Rx_dat[16];//16个字节
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM2)
{
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
ss05++;
if(ss05==2)
{
ss05=0;
ss++;
if(ss==60)
{
ss=0;
mm++;
if(mm==60)
{
mm=0;
hh++;
}
}
}
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance==USART2)//传递过来的实例是usart2的话
{
if(Rx_dat[0]==0xBF&&Rx_dat[2]==0xFB)//帧头和帧尾
{
switch(Rx_dat[1])
{
case 0xa1:
sprintf((char *)str_buff,"%d:%d:%d LED open!\r\n",hh,mm,ss);//标准化输出
break;
case 0xa2:
sprintf((char *)str_buff,"%d:%d:%d LED close!\r\n",hh,mm,ss);
break;
default://都不是的话
sprintf((char *)str_buff,"%d:%d:%d This is an error!\r\n",hh,mm,ss);
break;
}
HAL_UART_Transmit(&huart2,str_buff,sizeof(str_buff),10000);
HAL_UART_Receive_IT(&huart2,Rx_dat,3);
}
}
}
if(ss05==2),当时设置的是500ms中断一次,所以中断两次秒才能加一
初始化,位置:BEGIN2:
HAL_UART_Transmit(&huart2,str1,sizeof(str1),10000);
HAL_TIM_Base_Start_IT(&htim2);//定时器启动函数
HAL_UART_Receive_IT(&huart2,Rx_dat,3);
//HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);
//HAL_UART_Transmit(&huart2,Tx_str1,sizeof(Tx_str1),10000);
//HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);
//HAL_UART_Receive_IT(&huart2,&Rx_dat,1);
感觉在循环中写电平翻转有点问题??
七、数模转换
若是查询式
#include "stdio.h"
#define LED1_ON() HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET)
#define LED1_OFF() HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET)
uint8_t str1[]="==============Hello!================\r\n";
uint16_t ADC_Val = 0,ADC_Volt=0;
uint8_t str_buff[44];
void UART1_Send_Info()
{
sprintf((char *)str_buff,"Sample_Val:%d,Volt_Val:%d.%d%dV\r\n",ADC_Val,ADC_Volt/100,(ADC_Volt%100)/10,ADC_Volt%10);
HAL_UART_Transmit(&huart1,str_buff,sizeof(str_buff),10000);
}//用到哪个函数就要在用到的位置之前就已经写好,或者在之后再写但前面提前做好声明
void ADC0_Get_Val()
{
HAL_ADC_Start(&hadc1);//用ADC的实例地址启动
LED1_ON();
if(HAL_ADC_PollForConversion(&hadc1,10) == HAL_OK)//10是超时时间
{
ADC_Val = HAL_ADC_GetValue(&hadc1);
ADC_Volt = ADC_Val*330/4096;
}
UART1_Send_Info();
LED1_OFF();
HAL_ADC_Stop(&hadc1);
}
————————————————初始化——————————————————————
HAL_UART_Transmit(&huart1,str1,sizeof(str1),10000);
————————————————主循环——————————————————————
ADC0_Get_Val();
HAL_Delay(500);
若是中断式
#include "stdio.h"
#define LED1_ON() HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET)
#define LED1_OFF() HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET)
uint8_t str1[]="==============Are you ok?================\r\n";
uint16_t ADC_Val = 0,ADC_Volt=0;
uint8_t str_buff[44];
void UART1_Send_Info()
{
sprintf((char *)str_buff,"Sample_Val:%d,Volt_Val:%d.%d%dV\r\n",ADC_Val,ADC_Volt/100,(ADC_Volt%100)/10,ADC_Volt%10);
HAL_UART_Transmit(&huart1,str_buff,sizeof(str_buff),10000);
}//用到哪个函数就要在用到的位置之前就已经写好,或者在之后再写但前面提前做好声明
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
if(hadc->Instance == ADC1)
{
ADC_Val = HAL_ADC_GetValue(&hadc1);
ADC_Volt = ADC_Val*330/4096;
UART1_Send_Info();
LED1_OFF();
}
}
————————————————————————————begin2——————————————————————————
HAL_UART_Transmit(&huart1,str1,sizeof(str1),10000);
————————————————————————————主循环————————————————————————————
LED1_ON();
HAL_ADC_Start_IT(&hadc1);
HAL_Delay(500);
ADC_Volt = ADC_Val*330/4096;
这句话里的4096是212得来的,12位的AD嘛不是,330是3.3V乘100得来的,3.3是参考电压,也是我们插上STlinkV2给板子的输入电压,为什么乘100呢?因为在下面这个句子里,为了让输出的电压单位为V,就不得不用小数去表示电压,为了不让测得的电压显示时损失太多精度,先乘了100后边又除以了一个100,保证了两位小数的电压实际值输出。
sprintf((char *)str_buff,“Sample_Val:%d,Volt_Val:%d.%d%dV\r\n”,ADC_Val,ADC_Volt/100,(ADC_Volt%100)/10,ADC_Volt%10);
好像说的不是很明白。。。。后续再说
八、OLED(SPI)
四脚的是IIC的,七脚的是SPI的,两者差别很大,视频用的后者~
小蜜蜂老师整理的OLED资料与函数讲解
注意:RES脚要接到开发板的RESET脚上(每个板子应该都有的,引出来现成的),这样当你按下32的手动reset按钮时,也会给OLED屏幕复位,这个脚接不好屏幕直接不工作,不是连一个按钮接地这么简单(当时找错误找了好久好久)。
/* USER CODE BEGIN Includes */
#include "XMF_OLED_STM32Cube.h"
/* USER CODE END Includes */
/* USER CODE BEGIN 0 */
extern unsigned char BMP1[];
void OLED_display_pic()
{
OLED_Clear();
OLED_DrawBMP(0,0,128,7,BMP1);
}
void OLED_display_info()
{
OLED_Clear();
//OLED_ShowString(16,0,(uint8_t *)"Hello,world!");//十六个像素是一行字,8个像素是一页,也就是一行字要两页,老师好像是这个意思
//OLED_ShowChinese(16,2,(uint8_t *)"Hello,world!");//占掉第34页
//OLED_ShowString(16,4,(uint8_t *)"Hello,world!");//占掉第56页
//OLED_ShowString(16,6,(uint8_t *)"2022/01/28");//占掉第78页,一列一共64个像素,一共8页,对应编号0—7
OLED_ShowString(16,0,(uint8_t *)"Hello,World!");
OLED_ShowCHinese(10,3,0);
OLED_ShowCHinese(28,3,1);
OLED_ShowCHinese(46,3,2);
OLED_ShowCHinese(64,3,3);
OLED_ShowCHinese(82,3,4);
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
OLED_Init();
OLED_display_pic();
HAL_Delay(500);
OLED_display_info();
/* USER CODE END 2 */
OLED_Init();别忘了初始化
OLED_ShowCHinese(82,3,4);最后一位4是指汉字库(HZK)里的第四个字~
看好了他拼的有点奇怪,不是Chinese而是CHinese~
九、OLED+AD
/* USER CODE BEGIN 0 */
#include "stdio.h"
#define LED_ON() HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET)
#define LED_OFF() HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET)
extern unsigned char BMP1[];
uint16_t ADC_val=0,ADC_volt=0;
uint8_t str_buff[64];
void led_check()
{
LED_ON();
HAL_Delay(300);
LED_OFF();
HAL_Delay(300);
}
void oled_display_pic()
{
OLED_Clear();
OLED_DrawBMP(0,0,128,8,BMP1);
}
void oled_display_info()
{
OLED_Clear();//不清屏就会和前一个图像同时输出在一个屏幕上
OLED_ShowString(0,0,(uint8_t *)"Blog of Z_0_0");//强制类型转换?
OLED_ShowCHinese(0,3,3);
OLED_ShowCHinese(18,3,4);
OLED_ShowCHinese(36,3,8);
OLED_ShowString(54,3,(uint8_t *)":");
OLED_ShowCHinese(0,5,0);
OLED_ShowCHinese(18,5,1);
OLED_ShowCHinese(36,5,8);
OLED_ShowString(54,5,(uint8_t *)":");
OLED_ShowString(100,5,(uint8_t *)"V");
}
void oled_display_data()
{
sprintf((char *)str_buff,"%d",ADC_val);
OLED_ShowString(64,3,str_buff);
sprintf((char *)str_buff,"%d.%d%d",ADC_volt/100,(ADC_volt%100)/10,ADC_volt%10);//把文字与变化的数值结合,下一步再输出
OLED_ShowString(64,5,str_buff);//sprintf可以把数字字母都塞到str_buff里面
}
void GET_adc_val()
{
HAL_ADC_Start(&hadc1);
if(HAL_ADC_PollForConversion(&hadc1,10)== HAL_OK)
{
ADC_val=HAL_ADC_GetValue(&hadc1);
ADC_volt=ADC_val*330/4096;
oled_display_data();
}
if(ADC_volt<200)
{
LED_ON();
}
else
{
LED_OFF();
}
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
OLED_Init();
oled_display_pic();
oled_display_info();
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1)
{
GET_adc_val();
HAL_Delay(300);
/* USER CODE END WHILE */
土壤湿度传感器与沾水的卫生纸配合使用~
电压值最大是3.3V肯定
把土壤湿度传感器摁在湿润的卫生纸上~
使用%d输出时,上一次采样到的模拟值有四位数字这次采样是三位的话,如上一次3978,这次采样得到465,屏幕上会显示4658,这个多出来的8是由于上次3978第四位的8还留在屏幕上没有刷新掉,换成%4d三位数字实际上是465加一个空格,这样把上一次采样第四位8给挤掉了,显示就正常了。
实际上就是显示的问题,数字采样没错误,不用屏幕输出的情况完全不用考虑这个BUG。
测试%d与%4d的区别,%d是按照原型输出,有多少位就有多少位,另外c语言中数据的宽度是从右边开始算起的,比如%4d就表示数据宽度为4,不够的那空格来补,如果超出四位,则应该原型输出。具体测试程序如下,只需粘贴复制到VC6.0上运行就可知道两者的区别。
该段出自:%4d与%d讲解
本文后续再更新~感谢大家支持!