Skip to content

01

芯片基础知识(比赛的话不需要太了解)

image-20240310185552935

外设(需要重点关注)

GPIO

输入输出模式

image-20240310192643548

复用

image-20240310191433902

这一个表是数据手册里面的4.11

引脚的复用, 比如使用PA9作为USART_TX的时候, 把他配置为AF7

image-20240310191637675

这一个是参考手册里面的9.4

使用这一个寄存器配置实际的复用功能

image-20240310191823289

通用定时器

G4的通用定时器有好几种模式, 主要的区别是可以使用的通道的数量

image-20240311165053944

image-20240311165022510

image-20240311164957100

定时功能

使用定时器的时候使用HAL_TIM_Base_Start();HAL_TIM_Base_Start_IT();HAL_TIM_Base_Start_DMA();进行开启

可以使用函数HAL_TIM_Base_GetState();获取时钟的状态

使用__HAL_TIM_GET_COUNTER()获取计数器的值, __HAL_TIM_SET_COUNTER()进行设置

使用__HAL_TIM_GET_AUTORELOAD()获取这一个时钟的自动重装载值, 使用这一个时候最好打开影子寄存器

image-20240312230937476

__HAL_TIM_SET_PRESCALER()处理预分频值

PWM

使用这一个模式的时候, 先在对应的引脚选择一个定时器的通道

image-20240311173239988

image-20240311173308458

image-20240311174210763

代码使用的时候需要打开PWM, 使用函数HAL_TIM_PWM_Start();

输入捕获

image-20240311194546528

image-20240311194442648

实际捕获的时候使用HAL_TIM_IC_Start_IT()开启这一个捕获

c
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){
	if(htim->Instance == TIM17){
		curr_val = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
		__HAL_TIM_SET_COUNTER(htim, 0);
		frq = (80000000 / 80) / curr_val;
		HAL_TIM_IC_Start_IT(htim, TIM_CHANNEL_1);
	}
}

使用这一个计算PWM的频率

如果要捕获占空比, 可以使用一个定时器的两个通道, 这两个通道捕获不同的边沿, 之后通过捕获时间的比例获取这一个PWM的占空比

image-20240311202141971

c
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){
	if(htim->Instance == TIM17){
		curr_val = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
		__HAL_TIM_SET_COUNTER(htim, 0);
		frq = (80000000 / 80) / curr_val;
		HAL_TIM_IC_Start_IT(htim, TIM_CHANNEL_1);
	}
	if(htim->Instance == TIM8){
		if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1){
			rise = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
			fall = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2);
			__HAL_TIM_SET_COUNTER(htim, 0);
			duty = 100 * fall / rise;
			HAL_TIM_IC_Start_IT(htim, TIM_CHANNEL_1);
			HAL_TIM_IC_Start_IT(htim, TIM_CHANNEL_2);
		}
	}
}

ADC

  • 扫描模式: 使用STM32CUBEMX配置了多通道后,这一项默认开启且无法设置成关闭。这个模式就是自动扫描你开启的所有通道进行转换,直至转换完。例如你开启了CH0、CH1、CH2、CH3这四个通道,启动转换后ADC会自动将这4个通道全部转换完,但是这种连续性是可以被打断的,所以就引出了间断模式。

  • 连续模式: 在CUBE中选中ENABLE就是连续模式,DISABLE就是单次模式。开启连续模式后,ADC的转换不由其他控制。例如将ADC设置为了定时器的TGRO触发采样,如果开启连续模式,ADC将忽略定时器的触发采样。(连续转换模式开启后其实就是满频率的采样)。

  • 间断模式: 可以将多个通道进行分组采集,例如你开启了CH0~3这4个通道,假如你设置了间断次数为4,就相当于将4个通道分成了4组,每组1个通道,那么要想采集完这4个通道就需要手动触发4次ADC采集;如果设置了间断次数为2,那么采集完4个通道就需要手动触发2次ADC采集。

单通道

image-20240311212818189

c
double get_adc(void){
	uint32_t adc;
	HAL_ADC_Start(&hadc2);
	adc = HAL_ADC_GetValue(&hadc2);
	return adc * 3.3 / 4096;
}

多通道

image-20240314123429962

c
void read_adc(void){
	//依次获取两个通道的数据
    HAL_ADC_Start(&hadc2);
	HAL_ADC_PollForConversion(&hadc2, 50);
	adcs[0].adc_original =  HAL_ADC_GetValue(&hadc2);
	HAL_ADC_Start(&hadc2);
	HAL_ADC_PollForConversion(&hadc2, 50);
	adcs[1].adc_original = HAL_ADC_GetValue(&hadc2);
	
	//计算实际的电压
	adcs[0].adc_voltage = adcs[0].adc_original * 3.3 /4096;
	adcs[1].adc_voltage = adcs[1].adc_original * 3.3 /4096;
}

串口

这一个使用的时候需要注意, usb转串口使用的引脚不是默认的引脚, 需要自己调整一下, 使用PA10, PA9

RTC

HAL_RTC_GetData()获取日期

HAL_RTC_SetData()设置日期

HAL_RTC_GetTime()获取当前时间

IS_RTC_HOUR_FORMAT(FROMAT)看一看这一个时间是24小时的还是12小时的

image-20240317104250750

image-20240317101614571

image-20240317104333386

image-20240317104902730

这一个时钟不需要特意开启, 只需要直接获取时间以及回调就可以了

I2C

使用官方的I2C示例

这一个直接使用开漏输出就可以了

c
uint8_t eeprom_read(uint8_t addr){
	uint8_t data;
	I2CStart();
	I2CSendByte(EEPROM_ADDRESS_WRITE);
	I2CWaitAck();
	I2CSendByte(addr);
	I2CWaitAck();
	
	I2CStart();
	I2CSendByte(EEPROM_ADDRESS_READ);
	I2CWaitAck();
	data = I2CReceiveByte();
	I2CSendNotAck();
	I2CStop();
	return data;
}

void eeprom_write(uint8_t addr, uint8_t data){

	I2CStart();
	I2CSendByte(EEPROM_ADDRESS_WRITE);
	I2CWaitAck();
	I2CSendByte(addr);
	I2CWaitAck();
	I2CSendByte(data);
	I2CWaitAck();	
	I2CStop();
}

原理图

image-20240310095731925

image-20240310100012205

使用R39和R40两滑动变阻器进行调节, 使用J9J10进行连接引脚

image-20240310100415304

下边的两个

image-20240310100538745

LED使用的是低电平点亮

image-20240310100619729

image-20240310100730807

image-20240310100745415

image-20240310101025084

MCP4017是一个可变电阻

其内置了7位寄存器,共计127个档位的分辨率。

EEPROM-M24C02

image-20240310111745699

这是一个2Kbit的芯片

通信

image-20240310112858203

实际的地址1010000R/W

image-20240310113324349

写的时候

  1. 发送一个起始信号
  2. 发送一个写入的地址
  3. 等待一个ASK信号
  4. 发送数据以及等待响应的循环

image-20240310113924984

写的时候

  1. 发送一个起始信号
  2. 发送一个写入的地址
  3. 等待一个ASK信号
  4. 发送起始信号以及读取的地址
  5. 读取数据以及发送响应的循环

可变电阻MCP4017

【蓝桥杯】【嵌入式组别】第九节:MCP4017编程设计-CSDN博客

image-20240310101522872

image-20240310101514378

这一个原理图里面的A引脚没有使用, 实际可以控制的是Rbw里面的电阻值

如果使用PB14进行电压的测量, 实际的结果是VDD*Rwb/(Rwb + R17)

image-20240310102410374

image-20240310101919163

内部的原理图

image-20240310103037720

可以通过控制寄存器控制这一个W的位置

image-20240310110353522

这一个N是实际的可以写入的值

image-20240310110455988

开发板使用的这一块的典型电阻是100K欧姆的电阻

通信协议

image-20240310102018565

这一个的地址是固定的

image-20240310104052761

这个玩意只支持两个命令

image-20240310103540741

实际使用的时候写入以及读取只有低7位是有效的, 因为这一个芯片支持的只有127个分辨率

锁存器SN74HC(LED)

image-20240310202118817

这一个芯片使用的时候使能使能这一个芯片的时候, Q和D的电平是一样的, 不然的话Q是使能结束的时候D的状态

image-20240310202345193

LE是高电平的时候这一个芯片是使能的

LCD

image-20240310212415740

image-20240310213014309

这里从官方的文件里面获取这三个文件, 之后添加文件就可以了

使用的时候初始化GPIO为output然后使用LED_Init()函数就可以了

按键

按键有一个上拉电阻, 没有按下的时候这一个按键默认是高电平

使用的时候可以使用一个时钟进行轮询

image-20240310214449383

image-20240310214506324

c
  ==============================================================================
                        ##### TIM Callbacks functions #####
  ==============================================================================
 [..]
   This section provides TIM callback functions:
   (+) TIM Period elapsed callback
   (+) TIM Output Compare callback
   (+) TIM Input capture callback
   (+) TIM Trigger callback
   (+) TIM Error callback
   (+) TIM Index callback
   (+) TIM Direction change callback
   (+) TIM Index error callback
   (+) TIM Transition error callback

这里使用的是溢出中断回调函数HAL_TIM_PeriodElapsedCallback

使用时钟之前需要使用 HAL_TIM_Base_Start_IT(&htim6);开启这一个时钟

实际实现

这里使用一个状态机进行实现

c
#ifndef KEY_H
#define KEY_H
#include "main.h"


#define KEY1_Press			(1<<0)
#define KEY2_Press			(1<<1)
#define KEY3_Press			(1<<2)
#define KEY4_Press			(1<<3)

#define KEY1_LONG_Press			(1<<4)
#define KEY2_LONG_Press			(1<<5)
#define KEY3_LONG_Press			(1<<6)
#define KEY4_LONG_Press			(1<<7)
typedef struct _key_t{
	GPIO_TypeDef *GPIOx;
	uint16_t GPIO_Pinx;
	enum{
		KEY_NO_PRESS,
		KEY_PRESS_UNAFFIRM,
		KEY_PRESS_AFFIRM,
	}state;//这是一个状态机用于按键的消抖
	GPIO_PinState input;//记录当前的按键状态
	int time;//一个按下的时间, 用于处理长按
	unsigned char long_press;//长按的标志
}key_t;

void KEY_Init(void);
uint8_t key_get_state(void);
#endif /*KEY_H*/
c
#include "key/key.h"
#include "gpio.h"

key_t keys[4];
//按键的初始化, 这一个需要在时钟开启之前执行
void KEY_Init(void){
	keys[0].GPIOx = GPIOB;
	keys[0].GPIO_Pinx = GPIO_PIN_0;
	keys[1].GPIOx = GPIOB;
	keys[1].GPIO_Pinx = GPIO_PIN_1;
	keys[2].GPIOx = GPIOB;
	keys[2].GPIO_Pinx = GPIO_PIN_2;
	keys[3].GPIOx = GPIOA;
	keys[3].GPIO_Pinx = GPIO_PIN_0;
	for(int i = 0;i < 4; i++){
		
	}
}

//使用一个时钟进行轮询
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
	if(htim->Instance == TIM6)
	{
		for(int i = 0;i < 4; i++){
			keys[i].input = HAL_GPIO_ReadPin(keys[i].GPIOx, keys[i].GPIO_Pinx);
			if(keys[i].input == GPIO_PIN_RESET){
				switch(keys[i].state){
					case KEY_NO_PRESS:
                          //这一个按键第一次按下, 这时候需要进行消抖
						keys[i].state = KEY_PRESS_UNAFFIRM;
						keys[i].time++;
						break;
					case KEY_PRESS_UNAFFIRM:
                          //消抖结束, 确定这一个按键是按下的
						keys[i].state = KEY_PRESS_AFFIRM;
						keys[i].time++;
						break;
					case KEY_PRESS_AFFIRM:
                          //这时候处理一下这一个按键是不是长按
						if(keys[i].time++ > 10){
							keys[i].long_press = 1;
						}
						break;
				}
			}else{
				keys[i].state = KEY_NO_PRESS;
				keys[i].time = 0;
				keys[i].long_press = 0;
			}
			
		}
	}
}

//获取当前的按键状态
//这里使用返回的值和宏定义&可以获取按键的状态
uint8_t key_get_state(void){
	uint8_t key_state = 0;
	for(int i = 0;i < 4; i++){
		if(keys[i].state == KEY_PRESS_AFFIRM)
		{
			key_state |= 1 << i;
		}
		if(keys[i].long_press)
		{
			key_state |= 1 << i + 4;
		}
	}
	return key_state;
}
c
key_state = key_get_state();  
if(key_state & KEY1_Press){
    LCD_DisplayStringLine(Line0, "press");
}else{
    LCD_DisplayStringLine(Line0, "no press");
}

main函数里面可以轮询获取这一个按键的状态