Appearance
01
芯片基础知识(比赛的话不需要太了解)
外设(需要重点关注)
GPIO
输入输出模式
复用
这一个表是数据手册里面的4.11
引脚的复用, 比如使用PA9作为USART_TX的时候, 把他配置为AF7
这一个是参考手册里面的9.4
使用这一个寄存器配置实际的复用功能
通用定时器
G4的通用定时器有好几种模式, 主要的区别是可以使用的通道的数量
定时功能
使用定时器的时候使用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()
获取这一个时钟的自动重装载值, 使用这一个时候最好打开影子寄存器
__HAL_TIM_SET_PRESCALER()
处理预分频值
PWM
使用这一个模式的时候, 先在对应的引脚选择一个定时器的通道
代码使用的时候需要打开PWM, 使用函数HAL_TIM_PWM_Start();
输入捕获
实际捕获的时候使用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的占空比
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采集。
单通道
c
double get_adc(void){
uint32_t adc;
HAL_ADC_Start(&hadc2);
adc = HAL_ADC_GetValue(&hadc2);
return adc * 3.3 / 4096;
}
多通道
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小时的
这一个时钟不需要特意开启, 只需要直接获取时间以及回调就可以了
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();
}
原理图
使用R39和R40两滑动变阻器进行调节, 使用J9J10进行连接引脚
下边的两个
LED使用的是低电平点亮
MCP4017是一个可变电阻
其内置了7位寄存器,共计127个档位的分辨率。
EEPROM-M24C02
这是一个2Kbit的芯片
通信
实际的地址1010000R/W
写的时候
- 发送一个起始信号
- 发送一个写入的地址
- 等待一个ASK信号
- 发送数据以及等待响应的循环
写的时候
- 发送一个起始信号
- 发送一个写入的地址
- 等待一个ASK信号
- 发送起始信号以及读取的地址
- 读取数据以及发送响应的循环
可变电阻MCP4017
【蓝桥杯】【嵌入式组别】第九节:MCP4017编程设计-CSDN博客
这一个原理图里面的A引脚没有使用, 实际可以控制的是Rbw里面的电阻值
如果使用PB14进行电压的测量, 实际的结果是VDD*Rwb/(Rwb + R17)
内部的原理图
可以通过控制寄存器控制这一个W的位置
这一个N是实际的可以写入的值
开发板使用的这一块的典型电阻是100K欧姆的电阻
通信协议
这一个的地址是固定的
这个玩意只支持两个命令
实际使用的时候写入以及读取只有低7位是有效的, 因为这一个芯片支持的只有127个分辨率
锁存器SN74HC(LED)
这一个芯片使用的时候使能使能这一个芯片的时候, Q和D的电平是一样的, 不然的话Q是使能结束的时候D的状态
LE是高电平的时候这一个芯片是使能的
LCD
这里从官方的文件里面获取这三个文件, 之后添加文件就可以了
使用的时候初始化GPIO为output然后使用LED_Init()函数就可以了
按键
按键有一个上拉电阻, 没有按下的时候这一个按键默认是高电平
使用的时候可以使用一个时钟进行轮询
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函数里面可以轮询获取这一个按键的状态