1.学会看手册
1.手册的了解
关于后三个手册,我们可以简单理解为
- 编程手册——内核,不涉及外设
- 参考手册——外设和寄存器
- 数据手册——引脚功能描述
2.CT117E-M4产品手册
- 开发板的一个使用说明书了。在里面列举了板子上所有的硬件连接情况。
4.数据手册
- P16,时钟树
- P33,定时器
5.每一个手册都要有一定理解
- 注意,比如,13届考题翻转LCD显示,需要查看LCD手册,搜方向,找寄存器,看手册(手册上有
2.GPIO
1.输出
- 从左到右
- 主要由两个MOS管控制,可分为
推挽,开漏,关闭三种模式 - 需要注意蓝桥杯比赛中,
只有I2C需要配置为开漏输出,其他全部为推挽输出。 -
- 开漏输出:P-MOS不工作,具有“线与"功能->外部上拉电阻的大小决定了逻辑电平转换的速度,阻值越小,延时越小,功耗越高,反之,亦然。当延时有要求时,使用下降沿输出
- 优点:可硬件,实现“线与"功能(“线与”允许多个设备通过开漏输出共同驱动一条线,且只有在所有设备都处于低电平状态时,信号线才会被拉低)(可实现5v供电)
- 缺点:在开漏配置下,GPIO 引脚可以拉低(输出低电平),但不能主动拉高(输出高电平)
- 推挽输出:两个MOS管均有效,可输出高低电平
- 优点:负载能力强,速度快
- 缺点:存在短路风险
- 开漏输出:P-MOS不工作,具有“线与"功能->外部上拉电阻的大小决定了逻辑电平转换的速度,阻值越小,延时越小,功耗越高,反之,亦然。当延时有要求时,使用下降沿输出
2.输入
-
从右往左
-
分为4种模式
- 上拉输入: 如果上面导通,下面断开,就是上拉输入模式 ,默认为高电平的输入模式,
-
下拉输入:如果下面导通,上面断开,就是下拉输入模式 ,默认为低电平的输入模式
-
浮空输入:如果两个都断开,就是 浮空输入(极易受外界干扰) (//使用浮空输入的时间,当外部输入信号功率很小,内部上拉电阻可能会影响到这个输入信号)
-
高阻态模拟输入模式:最上面的一个,未通过施密特触发器(本质上,实现的是规范输入的作用),冲用于模拟输入
3.LED模块
-
通过原理图,我们可以发现,LED灯的左侧通过一个电阻接到了VDD,右侧连接到了SN74HC573,然后连接到了PC8-15引脚。那么,如果我们想让LED灯亮,就需要在右侧输出一个低电平,这样电路才会导通。
-
同时需要注意,SN74HC573为锁存器,需要将PD2置低电平,才能实现通路
-
点灯时,注意将数据左移8位(寄存器思想)。
-
& 全1为1 | 有1为1 ^ 相同为0不同为1
-
最好的led模块代码(推荐记忆)
void LED_Disp(uint8_t x)
{
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC,x<<8,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
void LED_Proc(uint8_t num,uint8_t sta)
{
uint8_t postion=0x01 <<(num-1);
uint8_t led_sta =0x10;
led_sta=(led_sta&(~postion))|(postion*sta);
LED_Disp(led_sta);
}
4.按键
-
注意要消抖
-
按键最好一开始配置为
上拉输入,并且使用定时器来配置 -
最基本按键处理函数
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)==0)
{
HAL_Delay(10);
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)==0)
{
//按键按下执行的操作
}
}
- 外部中断法(不推荐PA0和PB0占用了同一个外部中断通道,因此只能用3个按键)
- cubemx配置为Falling edge trigger mode
- Pull_up 默认拉高
- 注意在,nvic中勾选中断
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin==GPIO_PIN_0)
{
//HAL_Delay(10);中断函数中不能有延时函数,如果一定要使用最好在cubemx中将system tick优先级升高,超过中断的优先级
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==0)
{
//按键按下执行的操作
}
}
}
- 定时器法,长按,短按实例代码:
struct keys
{
uchar judge_sta;
bool key_sta;
bool single_sta;//bool short_flag;
bool long_flag;
uint key_time;
};
#include "interrupt.h"
struct keys key[4]={0,0,0};
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM3)//判断
{
key[0].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
key[1].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
key[2].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
key[3].key_sta=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
for(int i=0;i<4;i++)
{
switch(key[i].judge_sta)
{
case 0:
{
if(key[i].key_sta==0)//判断是否被按下
{
key[i].judge_sta=1;
key[i].key_time=0;
}
}
break;
case 1://实现了消抖
{
if(key[i].key_sta==0)
{
key[i].judge_sta=2;
//key[i].single_sta=1;//确实按下了
}
else
key[i].judge_sta=0;
}
break;
case 2:
{
if(key[i].key_sta==1)//当按键松开
{
key[i].judge_sta=0;
if(key[i].key_time<=100)//10ms定时器对按键进行扫描1次
{
key[i].single_sta=1;//确定位短按
}
}
else//当按键没有松开
{
key[i].key_time++;//记录按下时间
if(key[i].key_time>100)//10ms定时器对按键进行扫描1次
{
key[i].long_flag=1;//确定位长按
}
}
}
break;
}
}
}
}
//注意在主函数中加上
extern struct keys key[];
//开启定时器中断
HAL_TIM_Base_Start_IT(&htim6);//button tim
- 定时器第二种(推荐)
struct keys
{
uint8_t age;
uint8_t flag;
uint8_t Long_flag;
uint8_t press;
};
uint8_t key_read()
{
if(HAL_GPIO_ReadPin(GPIOB ,GPIO_PIN_0)==0) return 1;
else if(HAL_GPIO_ReadPin(GPIOB ,GPIO_PIN_1)==0) return 2;
else if(HAL_GPIO_ReadPin(GPIOB ,GPIO_PIN_2)==0) return 3;
else if(HAL_GPIO_ReadPin(GPIOA ,GPIO_PIN_0)==0) return 4;
else return 0;
}
//常用长按函数
void key_serv_long()
{
uint8_t key_value=key_read();
if(key_value!=0)// 当按下
{
M_key[key_value].age++;
if(M_key[key_value].age>5 ) //实现了消抖
M_key[key_value].press=1;
}
else //当松开
{
for(int i=0;i<5;i++)
{
if(key[i].age>100&&key[i].press==1 ) M_key[i].Long_flag=1;//需要知
道当长短按都需要判断时,有必要将长按判断置前
if(M_key[i].press==1&&M_key[i].Long_flag==0)M_key[i].flag=1;//短按
//长按
M_key[i].age=0;
M_key[i].press=0;
//M_key[i].Long_flag=0;//注意题目要求,看放在哪合适
}
}
}
//双击+长按(长按快速增加)+短按
void key_serv_double()
{
uint8_t key_value=key_read();
if(key_value!=0)// 当按下
{
M_key[key_value].age++;
if(M_key[key_value].age>5 ) //实现了消抖
M_key[key_value].press=1;
}
else //当松开
{
for(int i=0;i<5;i++)
{
//`顺序不能变`
if(M_key[i].double_ageEn==1&&M_key[i].press==1)//当双击,必须写在前面
{
M_key[i].double_flag=1;
M_key[i].double_ageEn=0;
M_key[i].double_age=0;
M_key[i].press=0;
}
if(M_key[i].press==1&&M_key[i].Long_flag==0) M_key[i].double_ageEn=1;
if(M_key[i].double_ageEn==1) M_key[i].double_age++;
if(M_key[i].double_ageEn==1&&M_key[i].double_age>20)//当间隔时间过长
{
M_key[i].flag=1;
M_key[i].double_ageEn=0;
M_key[i].double_age=0;
}
M_key[i].age=0;
M_key[i].press=0;
M_key[i].Long_flag=0;
}
}
//在按下的同时判断
if(M_key[key_value].age>100) M_key[key_value].Long_flag=1;//长按
}
//注意在主函数中加上
extern struct keys key[];
//开启定时器中断
HAL_TIM_Base_Start_IT(&htim6);//button tim
5.LCD
-
LCD移植库函数,将LCD提到的GPIO引脚设置为推挽输出,然后调用函数即可
-
常见lcd和按键组合例程(注意将,这两个函数写入循环中)
void key_proc(void)
{
if(key[0].single_sta==1)
{
LCD_Clear(Black);//最好将清屏放入按键判断中,放在外面会导致在循环中不断清屏
view=!view;
key[0].single_sta=0;
}
else if(key[1].single_sta==1)
{
PA6_Duty+=10;
if(PA6_Duty>90)
{
PA6_Duty=10;
}
//__HAL_TIM_SET_COMPARE(&htim16, TIM_CHANNEL_1,PA6_Duty);//设置占空比
TIM16->CCR1=PA6_Duty;
key[1].single_sta=0;
}
else if(key[2].single_sta==1)
{
PA7_Duty+=10;
if(PA7_Duty>90)
{
PA7_Duty=10;
}
//__HAL_TIM_SET_COMPARE(&htim17, TIM_CHANNEL_1,PA7_Duty);//设置占空比
TIM17->CCR1=PA7_Duty;
key[2].single_sta=0;
}
}
void lcd_proc(void)
{
if(view==0)
{
char str[21];
sprintf(str," DATA");
LCD_DisplayStringLine(Line0, ( unsigned char *)str);
}
if(view==1)
{
char str[21];
sprintf(str," Para");
LCD_DisplayStringLine(Line0, ( unsigned char *)str);
sprintf(str," PA6 :%d",PA6_Duty);
LCD_DisplayStringLine(Line2, ( unsigned char *)str);
sprintf(str," PA7 :%d ",PA7_Duty);
LCD_DisplayStringLine(Line4, ( unsigned char *)str);
}
}
- 需要注意LED和LCD共用PC8-PC15,因此控制信号不能同时有效
- 本文是解决 同时在 定时器中点灯 与 LCD屏幕显示 冲突异常的问题
- 我们大家都知道,G431RBT6开发板上led与lcd是冲突的,所以在lcd.c文件中的这三个函数中
void LCD_WriteReg(u8 LCD_Reg, u16 LCD_RegValue)
void LCD_WriteRAM_Prepare(void)
void LCD_WriteRAM(u16 RGB_Code)
的头跟尾分别加这两句话
uint16_t temp = GPIOC->ODR;//头
GPIOC->ODR=temp;//尾
就可以避免冲突。
但是,如果有定时器的参与点灯的话是否也正常呢? 答案是不行!
2进制显示
- 想要二进制显示只能使用
void LCD_DisplayChar(u8 Line, u16 Column, u8 Ascii)函数 - 第一个参数写Linex(0~10),第二个参数写第x列
(360-(16*x )),第三个写ascii码
3.TIM
1.输入捕获
从模式设置为RESET,这样测出来的频率值才不会跳
//回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
c_value1a = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1) + 1; // 总时间
frq1 = (80000000 / 80) / c_value1a;
HAL_TIM_IC_Start(htim, TIM_CHANNEL_1); // 重新启动通道 1 捕获
}
else if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
{
c_value1b = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2) + 1; // 高电平时间
A_Duty = (float)c_value1b / (float)c_value1a; // 计算占空比
HAL_TIM_IC_Start(htim, TIM_CHANNEL_2); // 重新启动通道 2 捕获
}
__HAL_TIM_SetCounter(htim, 0); // 清零计数器
}
if (htim->Instance == TIM3)
{
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
c_value2a = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1) + 1; // 总时间
frq2 = (80000000 / 80) / c_value2a;
HAL_TIM_IC_Start(htim, TIM_CHANNEL_1); // 重新启动通道 1 捕获
__HAL_TIM_SetCounter(htim, 0); // 清零计数器
}
else if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
{
c_value2b = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2) + 1; // 高电平时间
Duty2 = (float)c_value2b / (float)c_value2a; // 计算占空比
HAL_TIM_IC_Start(htim, TIM_CHANNEL_2); // 重新启动通道 2 捕获
}
__HAL_TIM_SetCounter(htim, 0); // 清零计数器
}
}
2.定时中断
- 中断频率=输入频率/PSC+1/ARR+1
- 中断轮询时间=1/中断频率
- 配置定时中断要点:
- 配置时基单元
- 在NVIC中勾选中断使能
- 在主函数中有没有开启中断
HAL_TIM_Base_Start_IT(&htim4);//开启按键中断 - 看看中断函数有没有写错,或者看函数里是否有延时函数(如果有修改system tick timer 优先级,调高)
3.输出比较(PWM产生)
-
PWM产生,除了配置最基本的定时器参数,需要注意的是还要配置最重要的参数CCR(
给CCR配置一个非零的参数,如果为零那么ARR就会和0做比较,最终会导致无法产生PWM波形) -
代码部分只需要在初始化中,开启pwm即可,不需要其他参数
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2);
4.ADC
每步操作的意义
-
HAL_ADC_PollForConversion(&hadc2,HAL_MAX_DELAY);这个代码意义是不断轮询adc转换结束标志位(adc_sr)是否置1,当调用HAL_ADC_GetValue后就会将该标志位置0 -
stm32中存在一个1.2v的参考电压
-
如果想要使用DMA连续获得adc的参数最好调用adc中断回调函数
- 如光找不到中断回调函数,那么可以给DMA开启循环模式,adc配置为持续扫描模式(特别注意一点,对于蓝桥杯板子,还需要开启
DMA continous requests)
- 如光找不到中断回调函数,那么可以给DMA开启循环模式,adc配置为持续扫描模式(特别注意一点,对于蓝桥杯板子,还需要开启
-
同步(Synchronous):
- 同步I/O操作是指在执行I/O操作时,程序必须等待操作完成才能继续执行。在同步操作中,程序提交一个I/O请求后,操作系统会阻塞该程序,直到请求操作完成。此时,程序才能继续执行后续的代码。因此,同步操作会导致程序执行流程暂停,直至I/O操作完成。(总结同步操作会阻塞程序执行)
-
异步(Asynchronous):
- 异步I/O操作是指程序在发起I/O请求后,无需等待操作完成,可以继续执行其他任务。当异步I/O操作完成时,程序会通过某种方式(如回调函数、事件通知、信号等)得到通知。因此,异步操作使程序执行流程得以继续,而不必等待I/O操作完成。(总结异步操作不会阻塞程序执行)
ADC单通道
- differential表示差分输入 和single-ended表示单端输入
- 直接在cubemx中配置好adc,然后生成代码就行
//adc函数
float ADC_Read(ADC_HandleTypeDef *hadc)
{
uint16_t M_adc;//adc输出量为12位
HAL_ADC_Start(hadc);
M_adc=HAL_ADC_GetValue(hadc);
return M_adc*3.3f/4095;
}
* 注意要在 ,初始化函数中加上 校准函数
HAL_ADCEx_Calibration_Start(&hadc2,ADC_SINGLE_ENDED);//adc2 单端模式
ADC双通道
-
cubemx配置,不需要开连续扫描模式(转换完一个通道后立即自动执行下一个通道的转换)
-
代码
HAL_ADC_Start(&hadc2);
HAL_ADC_PollForConversion(&hadc2,50);
adc2_PA4=HAL_ADC_GetValue(&hadc2)*3.30f/4096;
HAL_ADC_Start(&hadc2);
HAL_ADC_PollForConversion(&hadc2,50);
adc2_PA5=HAL_ADC_GetValue(&hadc2)*3.30f/4096;
HAL_ADC_Stop(&hadc2);
ADC新方法(提高精度)(不常用)
- 对于比赛板子可采用,硬件配置来提高adc精度(ADC过采样模式)
- 函数也要做出修改
float ADC_Read_OS(ADC_HandleTypeDef *hadc)
{
uint16_t M_adc;//adc输出量为12位
HAL_ADC_Start(hadc);
M_adc=HAL_ADC_GetValue(hadc);
return M_adc*3.3f/65535;//相当于测了128次平均位1次
5.IIC读写EEPROM
-
值得注意的是,
EEPROM存储的数据不能是小数,eeprom不能连续读取 -
IIC时序基本单元
- 起始条件:SCL高电平期间,SDA由低电平到高电平
- 终止条件:SCL高电平期间,SDA由高电平到低电平
-
总结
- 高位先行
- 数据以字节传输
- 数据在SCL高电平稳定
- 数据发送完,还要有应答信号
-
移植官方库函数,并在官方库函数上加(EEPROM读写),并不需要在CUBEmx上配置(不能硬件iic配置,只能软件IIC)要在初始化函数上加上
I2CInit(void),然后写这两个函数即可
//eeprom读写
uchar eeprom_read(uchar address)
{
uchar m_data;
I2CStart();//开启iic
I2CSendByte(0xa0);//联系芯片
I2CWaitAck();//等待完成
I2CSendByte(address);//写入地址
I2CWaitAck();//等待完成
I2CStop();//停止
I2CStart();
I2CSendByte(0xa1);//发送从机地址
I2CWaitAck();//等待完成
m_data=I2CReceiveByte();//读出
I2CSendNotAck( );//发送应答信号
I2CStop();//停止
return m_data;
}
void eeprom_write(uchar address,uchar mdata)
{
I2CStart();//开启iic
I2CSendByte(0xa0);//联系芯片
I2CWaitAck();//等待完成
I2CSendByte(address);//写入
I2CWaitAck();//等待完成
I2CSendByte(mdata);
I2CWaitAck();//等待完成
I2CStop();//停止
}
- 指定写
- 指定读(随机读+指定写)
- 硬件
- AT24C02是2kb串行EEPROm,内部组织为256Byte,支持8bit页写,写周期内部定时(小于5ms),实现8个器件共用一个接口
- A1 A2 A3对应了E1 E2 E3
IIC和ADC的结合mcp4017t
- 由上图可得,mcp4017t也是IIC通信上的从机,因此可以通过上位机来调整它的阻值,通过IIC读写来调整,他的
从机地址为(0x5e),然后就可以通过ADC来读他的模拟值。
//写mcp
void mcp_write(uint8_t mdata)
{
//指定地址写
I2CStart();//开启iic
I2CSendByte(0x5e);//联系芯片
I2CWaitAck();//等待完成
I2CSendByte(mdata);
I2CWaitAck();//等待完成
I2CStop();//停止
}
第一次不读取eeprom值
- 设备第一次读取时使用代码中的初始化,后续启动使用EEPROM中存储值作为商品信息的初值,那么如何判断设备是否第一次启动就成了商品信息初始化的关键点。这里采用的方法是:使用EEPROM中的两个地址存储特定的值,每次启动时都读取这两个地址,一旦读取出的数值不是设定的特定值就判断是第一次启动设备,再将特定值写入;否则,就不是第一次启动,那么就使用EEPROM中的值作为初值。
//读eeprom(放在初始化函数中)
if(EEPROM_read(111)!=111)//第一次上电
{
X_stock=10;//库存
Y_stock=10;
X_prise=1.0f;//价格1.0-2.0
Y_prise=1.0f;
EEPROM_write(111,111);//第一次上电后置标志位
}
else
{
X_stock=EEPROM_read(0);//eeprom不能连续读取
HAL_Delay(2);
Y_stock=EEPROM_read(1);
HAL_Delay(2);
X_prise=EEPROM_read(2)/10.0;
HAL_Delay(2);
Y_prise=EEPROM_read(3)/10.0;
HAL_Delay(2);
}
eeprom存储小数的方式
- eeprom不能存储小数,可以采用先存
6.串口(uART)
- 比赛考的很有限,不考DMA等等;
- 步骤:在cubemx中开启串口,如果要接受代码,记得开启中断,在初始化中也要开启中断
HAL_UARTEx_ReceiveToIdle_IT然后生成代码, - 发送数据:
//uart
//不开中断,在循环中写
sprintf(str," ADC_value:%.4f \r ", ADC_Read_OS(&hadc2));
HAL_UART_Transmit(&huart1,(const uint8_t*)str,strlen(str),HAL_MAX_DELAY);
//开中断,
HAL_UART_Transmit_IT(&huart1,(const uint8_t*)str,strlen(str));//在初始化函数中开启,
// 在中断函数中,需要在最后将上面函数加上
//中断接收函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
memset(arr,'\0',sizeof(arr));//将arr清零
- 接收数据
//开中断,
HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);//在初始
//化函数中开启,在中断函数中,需要在最后将上面函数加上
//中断函数(最好的),可以接受不定长
HAL_UARTEx_RxEventCallback() //是一个在 STM32 HAL库中定义的回调函数,它是用于处理 UART 接收
//事件的扩展回调函数。这个函数通常在 UART 接收到数据并且数据接收完成时被调用,特别是在使用 DMA
(直接内存访问)或其他非阻塞接收方法时。它是 HAL 库中用于处理 UART 通信的一部分,允许用户在接
收到特定数量的数据后执行自定义操作。
串口应用(密码代换)
- 特别注意将数组长度改成4,不然有编码格式问题,无法比较(因为字符串最后一位需要存储'\0')
- char old_pswd[3]; char new_pswd[3];需要注意,要将这个放到中断函数中,否则会导致password的第一位置0;
char password[3]="123";
static uint16_t count;
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
count++;//记录进入中断的次数
if(huart->Instance==USART1)
{
char old_pswd[3];
char new_pswd[3];
// 确保数据格式为 old_pswd-new_pswd
if (sscanf(rx_data, "%3s-%3s", old_pswd, new_pswd)==2 ) // 确保成功读取两个密码
{
// 判断旧密码是否正确
if (strcmp(password,old_pswd) == 0)
{
// 密码正确,更新密码
for (int i = 0; i < 3; i++)
{
password[i] = new_pswd[i]; // 更新为新密码
}
// 返回成功信息
sprintf(tx_data, " success ");
HAL_UART_Transmit(&huart1, (const uint8_t*)tx_data, strlen(tx_data), HAL_MAX_DELAY);
}
else
{
// 旧密码不匹配
sprintf(tx_data, " fail ");
HAL_UART_Transmit(&huart1, (const uint8_t*)tx_data, strlen(tx_data), HAL_MAX_DELAY);
}
}
else
{
// 数据格式不正确
sprintf(tx_data, " invalid format ");
HAL_UART_Transmit(&huart1, (const uint8_t*)tx_data, strlen(tx_data), HAL_MAX_DELAY);
}
// 重新启动接收中断
HAL_UARTEx_ReceiveToIdle_IT(&huart1,( uint8_t*)rx_data,50);
}
}
- HAL_UARTEx_ReceiveToIdle_IT();//- 该函数用于在中断模式下接收数据,直到 UART 接收缓冲区空闲(即没有数据接收一段时间)。推荐使用
- 不过需要注意,与之对应的 中断回调函数是
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)该函数还有一个参数size可以用来 检测每次传输进来的数据的格式
7.systick
-
HAL_GetTick();获取当前时间,向上计时
-
如果将HAL_Delay()函数放进中断函数中便会卡死,这是因为 HAL_Delay()函数 调用的滴答定时器的中断优先级太低,导致无法打断定时器中断而卡死,只需在nvic中将滴答定时器的优先级提高即可
8. 客观题解
stm32
-
-
1LSB为最小有效位,
-
- 计算公式:
-
- 将这些值代入公式中:
-
-
stm32中,程序可以在
RAM和ROM区域上运行 -
systick属于内核级外设,24位递减计数器,可以通过软件来控制它的开始与停止
-
嵌入式系统不能跨平台移植
-
eeprom,ROM和nor flash属于非易失性存储器,RAM属于易失性存储器
-
在I²C总线协议中,
从器件的地址由7位或10位组成。对于7位地址模式:- 地址位数:7位地址模式使用7位二进制数表示从器件地址。
- 地址范围:7位地址的范围是
0000000到1111111,即 0 到 127(十进制)。 - 可挂载数量:理论上,7位地址可以支持 128 个从器件
数电
- 门电路中输出端可以直接相连实现线与功能的是OD电路和OC电路
模电
- 有源滤波器和无源的区别,是否需要电源,是否有增益
- 放大电路零点漂移的原因
-
- 温度变化:
-
- 元件的不匹配:
-
- 电源电压波动:
-
- 老化效应:
- 输入偏置电流:
- 漏电流:
- 电磁干扰:
-
9. 常见技巧
标志位,实现持续处于状态而不累加
static uint8_t LF=0,LA=0,LT=0;//作为标志位,实现持续处于报警状态不累加
if(frq1>FH)
{
if(LF==0)
{
FN++;
LF=1;
}
}
else LF=0;
//-------------------------------------------------
//本来只用
if(frq1>FH)
{
FN++;
}
长按锁定,短按切换状态
- 本质上是置一个标志位,通过标志位判断
//长按2s就置标志位
if(Mkey[4].flag==1)//短按4
{
if(view==0&&tim_2s_flag==1)
{
ctrl_dutyf=0;//占空比调整解锁
}
Mkey[4].flag=0;
}
if(Mkey[4].Long_flag==1)//长按4
{
if(view==0)
{
ctrl_dutyf=1;//锁定占空比调整功能,占空比不变
}
Mkey[4].Long_flag=0;
}
LED闪烁(使用滴答定时器)
- 详情见
N14
if(uwTick-time100ms>100)//0.1s
{
static uint8_t blink;
blink=!blink;
if(rec_en==1)LED_O(1,blink);
else LED_O(1,0);
if(replay_pwm_en==1)LED_O(2,blink);
else LED_O(2,0);
if(replay_v_en==1)LED_O(3,blink);
else LED_O(3,0);
}
取值取平均值,最大值,最小值方法
- 详见
N13
HAL_ADC_Start(&hadc2);
HAL_ADC_PollForConversion(&hadc2,50);
adc2_PA4=HAL_ADC_GetValue(&hadc2)*3.30f/4096;
HAL_ADC_Start(&hadc2);
HAL_ADC_PollForConversion(&hadc2,50);
adc2_PA5=HAL_ADC_GetValue(&hadc2)*3.30f/4096;
HAL_ADC_Stop(&hadc2);
adc4[N4]=adc2_PA4;
adc5[N5]=adc2_PA5;
if(adc4[N4]>adc4_max)adc4_max=adc4[N4];
if(N4==0)
{
adc4_min=adc4[N4];
}
else if(adc4[N4]<adc4_min)adc4_min=adc4[N4];
adc4_sum+=adc4[N4];
adc4_avrge=adc4_sum/(N4+1);
if(adc5[N5]>adc5_max)adc5_max=adc5[N5];
if(N4==0)
{
adc5_min=adc5[N5];
}
else if(adc5[N5]<adc4_min)adc5_min=adc5[N5];
adc5_sum+=adc5[N5];
adc5_avrge=adc5_sum/(N5+1);
N4++;
N5++;
记录最值
- 常见记录页面,会记录最大值,最小值。最大值好计算,但是最小值如果初始化为0的话,会导致最小值一直为0,出现不存在的数
- 这个代码本质上便是置了一个标志位,如果标志位为0,是第一次就将该次的值赋给最小值,如果为1,就正常计算
uint16_t rate_max;
uint16_t rate_min;
bool rate_min_initialized=0;
if(rate>rate_max) rate_max=rate;
if (!rate_min_initialized || rate<rate_min) {
rate_min = rate;
rate_min_initialized = true; // 标记rate_min已被初始化
}
串口输入值
sscanf(rx_data,"%4s:%4s:%12s",car_type,car_id,car_time)用来处理串口接收的数据- 同时
HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)还有第二个参数Size,可以用来判断接收到数据的字符数 - 这两个函数可以搭配起来使用(P12停车收费系统)
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(huart==&huart1)
{
if(sscanf(rx_data,"%4s:%4s:%12s",car_type,car_id,car_time)==3&&Size==22)//判断格式信息
{
int i=0;//车库的索引
for(;i<car_cmnnum;i++)//先判断车库里有没有这个车的信息
{
if(cars[i].flag==1&&strcmp(car_id,cars[i].id)==0)//如果有
{
carisin_flag=1;//置标志位,证明该车在车库
break;
}
}
if(strcmp("CNBR",car_type)==0)//当类型为1
{
car1_flag=1;
}
else if(strcmp("VNBR",car_type)==0)
{
car2_flag=1;
}
if(carisin_flag==0)//当是进车库
{
//找空车位
int t=0;
for(;t<car_cmnnum;t++)
{
if(cars[t].flag==0)//如果有空车位
{
pksptempty_flag=1;//置标志位,证明该车库空
break;
}
}
if(pksptempty_flag==1)
{
//将信息存入车库信息中
strcpy(cars[t].type,car_type);
strcpy(cars[t].id,car_id);
strcpy(cars[t].time,car_time);
cars[t].flag=1;
idle_paringspt--;//剩余数量--
if(strcmp(car_type,"CNBR")==0)
{
car1++;
}
if(strcmp(car_type,"VNBR")==0)
{
car2++;
}
pksptempty_flag=0;//空车库标志位清除
}
}
else //当出车库
{
char car_outyear[3],car_outmonth[3],car_outday[3],car_outhour[3],car_outmin[3];
sscanf(car_time,"%2s%2s%2s%2s%2s",car_outyear,car_outmonth,car_outday,car_outhour,car_outmin);//存入数据
//处理信息
char car_inyear[3],car_inmonth[3],car_inday[3],car_inhour[3],car_inmin[3];
sscanf(cars[i].time,"%2s%2s%2s%2s%2s",car_inyear,car_inmonth,car_inday,car_inhour,car_inmin);//存入数据
in_time=atoi(car_inyear)*3153600+atoi(car_inmonth)*720+atoi(car_inday)*24+atoi(car_inhour);//时间转化
out_time=atoi(car_outyear)*3153600+atoi(car_outmonth)*720+atoi(car_outday)*24+atoi(car_outhour);//时间转化
sum_time=out_time-in_time;//总的时间
if((atoi(car_outmin)-atoi(car_inmin))>0)
sum_time+=1;
if(strcmp(car_type,"CNBR")==0)
{
car1--;
sum_price=car1_price*sum_time;
}
if(strcmp(car_type,"VNBR")==0)
{
car2--;
sum_price=car2_price*sum_time;
}
//输出信息
printf("%s:%s:%s\r\n",cars[i].type,cars[i].id,cars[i].time);//进场信息
printf("%s:%s:%s\r\n",car_type,car_id,car_time);//出场信息
printf("%s:%s:%d:%.2f\r\n",car_type,car_id,sum_time,sum_price);
idle_paringspt++;//剩余数量++
//将对应车库信息删除
strcpy(cars[i].type,"0");
strcpy(cars[i].id,"0");
strcpy(cars[i].time,"0");
cars[i].flag=0;
carisin_flag=0;//车库无车
}
}
else
{
printf(" ERROR \r\n");
}
memset(rx_data,0,sizeof(rx_data)/sizeof(char));
}
HAL_UARTEx_ReceiveToIdle_IT(&huart1,(uint8_t *)rx_data,sizeof(rx_data)/sizeof(char));//开启中断
}
串口重定向
int fputc(int ch, FILE*F)
{
/*发送一个字节数据到串口DEBUG_USART*/
HAL_UART_Transmit(&huart1 ,(uint8_t * ) &ch,1,1000);
return (ch);
}