代码项目地址为stm32综合学习项目。
项目概述(详细可在项目的Project_Structure中查看)
该项目基于STM32F103C8T6芯片,学习I2C,GPIO,ADC,EXTI,PWM等基础模块的使用,并通过模块化的方式使代码具有良好的可读性与健壮性。
准备的模块
- OLED显示屏(I2C)
- 蜂鸣器模块(GPIO)
- 光敏电阻传感器(ADC)
- 热敏电阻传感器(ADC)
- MPU6050陀螺仪(I2C)
- 旋转编码器(EXTI+TIM)
- 舵机(PWM)
- LED指示灯(GPIO)
- 按钮(GPIO+EXTI)
- STM23F103C8T6芯片
文件结构设计
硬件驱动层
Hardware/
├── OLED.c
├── OLED.h
├── OLED_Font.h
├── beeper.c
├── beeper.h
├── light_sensor.c
├── light_sensor.h
├── temp_sensor.c
├── temp_sensor.h
├── mpu6050.c
├── mpu6050.h
├── encoder.c
├── encoder.h
├── servo.c
├── servo.h
├── led.c
├── led.h
├── button.c
└── button.h
中间件层
Middlewares/
├── i2c.c
├── i2c.h
├── adc.c
├── adc.h
├── pwm.c
├── pwm.h
├── exti.c
├── exti.h
├── gpio.c
└── gpio.h
应用层
Application/
├── system.c
├── system.h
├── menu.c
├── menu.h
├── alarm.c
├── alarm.h
├── control.c
├── control.h
├── indicator.c
└── indicator.h
主程序
User/
├── main.c
└── stm32f10x_it.c
各模块所接入的GPIO口(在Hardware/pin_config.h
中)
// OLED 引脚
#define OLED_SCL_PIN GPIO_Pin_6 // PB6
#define OLED_SDA_PIN GPIO_Pin_7 // PB7
#define OLED_PORT GPIOB // OLED引脚所在的端口
//MPU6050引脚
#define MPU6050_SDA_PIN GPIO_Pin_10 // PB10
#define MPU6050_SCL_PIN GPIO_Pin_11 // PB11
#define MPU6050_PORT GPIOB // MPU6050引脚所在的端口
// 光敏传感器引脚
#define LIGHT_SENSOR_PIN GPIO_Pin_0 // PA0
#define LIGHT_SENSOR_PORT GPIOA // 光敏传感器引脚所在的端口
// 温度传感器引脚
#define TEMP_SENSOR_PIN GPIO_Pin_1 // PA1
#define TEMP_SENSOR_PORT GPIOA // 温度传感器引脚所在的端口
// 蜂鸣器引脚
#define BEEPER_PIN GPIO_Pin_2 // PA2
#define BEEPER_PORT GPIOA
// 编码器引脚
#define ENCODER_A_PIN GPIO_Pin_3 // PA3
#define ENCODER_B_PIN GPIO_Pin_4 // PA4
#define ENCODER_PORT GPIOA // 编码器引脚所在的端口
// 舵机引脚
#define SERVO_PWM_PIN GPIO_Pin_8 // PA8
#define SERVO_PORT GPIOA // 舵机引脚所在的端口
// LED引脚
#define LED1_PIN GPIO_Pin_9 // PA9
#define LED2_PIN GPIO_Pin_10 // PA10
#define LED3_PIN GPIO_Pin_11 // PA11
#define LED4_PIN GPIO_Pin_12 // PA12
#define LED_PORT GPIOA // LED引脚所在的端口
// 按钮引脚
#define BTN1_PIN GPIO_Pin_5 // PA5
#define BTN2_PIN GPIO_Pin_7 // PA7
#define BTN3_PIN GPIO_Pin_1 // PB1
#define BTN4_PIN GPIO_Pin_12 // PB12
以下的模块设计均以上方定义的名字为主,若要换成自己的具体引脚只需修改pin_config.h
即可。
硬件驱动层模块设计
LED模块
该模块主要就是使能GPIO口(将LED的引脚正极接电源,负极接芯片引脚),通过芯片输出低电平导通,使LED点亮。
LED_Init()
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 使能LED所在的GPIO端口时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置LED引脚为推挽输出
GPIO_InitStructure.GPIO_Pin = LED1_PIN | LED2_PIN | LED3_PIN | LED4_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(LED_PORT, &GPIO_InitStructure);
// 初始状态,所有LED关闭 该LED_Alloff();是自己写的一个函数,就是将所有引脚输出初始置为高电平,即LED全灭
LED_AllOff();
}
LED_On()
void LED_On(uint16_t LED_PIN)
{
GPIO_ResetBits(LED_PORT, LED_PIN);
}
LED_Off()
void LED_Off(uint16_t LED_PIN)
{
GPIO_SetBits(LED_PORT, LED_PIN);
}
LED_Toggle()
void LED_Toggle(uint16_t LED_PIN)
{
LED_PORT->ODR ^= LED_PIN;
GPIOA->ODR
}
模块所使用到的主要标准库函数:
- GPIO_InitTypeDef:配置 GPIO(通用输入输出)引脚的结构体。
- GPIO_SetBits(GPIOA, GPIO_Pin_11):设置对应GPIO口的引脚为高电平。
- GPIO_ResetBits(GPIOA, GPIO_Pin_11);:设置对应GPIO口的引脚为低电平。
- GPIOA->ODR ^= GPIO_Pin_11;:将GPIO口的引脚电平取反。(ODR(Output Data Register),用于控制GPIO端口的输出状态)
蜂鸣器模块
使用的是有源蜂鸣器,具体如下图。
有源蜂鸣器可以直接通过引脚输出低电平直接引发鸣叫。
Beeper_Init()
void Beeper_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置蜂鸣器引脚为推挽输出
GPIO_InitStructure.GPIO_Pin = BEEPER_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(BEEPER_PORT, &GPIO_InitStructure);
// 初始状态为关闭 关闭的逻辑与LED的一致,高电平关闭。
Beeper_Off();
}
Beeper_Beep()(控制蜂鸣器鸣叫时长)
void Beeper_Beep(uint16_t ms)
{
Beeper_On(); //关闭就是用GPIO_ResetBits(BEEPER_PORT, BEEPER_PIN);
delay_ms(ms); //标准库中延迟函数需要自己写,可以找网上现成的
Beeper_Off(); //开启就是用GPIO_SetBits(BEEPER_PORT, BEEPER_PIN);
}
按钮模块
按钮采用中断的方式触发,需要设置中断向量,判断长按还是短按需要通过SysTick来协同工作,这里我总共接入了4个按钮,对应的引脚接口为A5,A7,B0,B12,所以我在初始化中需要初始两个GPIO口。
button模块的常量定义
// 按键参数定义
#define BUTTON_SCAN_INTERVAL 20 // 扫描间隔(ms)
#define LONG_PRESS_TIME 2000 // 长按时间(ms)
#define DEBOUNCE_TIME 20 // 消抖时间(ms)
// 按键引脚定义
static const uint16_t KEY_PINS[KEY_COUNT] = {
BTN1_PIN, // KEY_MODE (BTN1)
BTN2_PIN, // KEY_CONFIRM (BTN2)
BTN3_PIN, // KEY_ALARM (BTN3)
BTN4_PIN // KEY_RESET (BTN4)
};
static GPIO_TypeDef* const KEY_PORTS[KEY_COUNT] = {
GPIOA, // KEY_MODE (PA5)
GPIOA, // KEY_CONFIRM (PA7)
GPIOB, // KEY_ALARM (PB1)
GPIOB // KEY_RESET (PB12)
};
// 按键逻辑状态定义
typedef enum {
BTN_IDLE = 0, // 空闲状态
BTN_DEBOUNCE, // 消抖状态
BTN_SHORT_PRESS, // 短按状态
BTN_LONG_PRESS // 长按状态
} ButtonState;
// 按键编号定义
typedef enum {
KEY_MODE = 0, // 模式切换按键 (BTN1)
KEY_CONFIRM, // 确认按键 (BTN2)
KEY_ALARM, // 报警确认按键 (BTN3)
KEY_RESET, // 复位按键 (BTN4)
KEY_COUNT // 按键总数
} KeyID;
Button_Init()
void Button_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
uint8_t i;s
// 使能GPIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
// 配置按键引脚为输入上拉
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//逐一配置按键引脚
for(i = 0; i < KEY_COUNT; i++)
{
GPIO_InitStructure.GPIO_Pin = KEY_PINS[i];
GPIO_Init(KEY_PORTS[i], &GPIO_InitStructure);
keyInfo[i].state = BTN_IDLE;
keyInfo[i].isPressed = 0;
}
// 配置EXTI中断
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource5); // KEY_MODE
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource7); // KEY_CONFIRM
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1); // KEY_ALARM
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource12); // KEY_RESET
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //采用中断触发
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Line = EXTI_Line5; // KEY_MODE
EXTI_Init(&EXTI_InitStructure);
EXTI_InitStructure.EXTI_Line = EXTI_Line7; // KEY_CONFIRM
EXTI_Init(&EXTI_InitStructure);
EXTI_InitStructure.EXTI_Line = EXTI_Line1; // KEY_ALARM
EXTI_Init(&EXTI_InitStructure);
EXTI_InitStructure.EXTI_Line = EXTI_Line12; // KEY_RESET
EXTI_Init(&EXTI_InitStructure);
// 配置NVIC
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
// 配置中断通道
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn; // KEY_MODE, KEY_CONFIRM
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; // KEY_ALARM
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; // KEY_RESET
NVIC_Init(&NVIC_InitStructure);
}
Button_Scan()
Button_Scan()
每隔20ms扫描按钮状态,通过状态机的方式来进行消抖。
void Button_Scan(void)
{
static uint32_t lastScanTime = 0;
uint32_t currentTime;
uint8_t i;
currentTime = GetTickCount();
// 按扫描间隔进行处理 小于扫描间隔则不处理
if(currentTime - lastScanTime < BUTTON_SCAN_INTERVAL)
return;
// 更新扫描时间
lastScanTime = currentTime;
for(i = 0; i < KEY_COUNT; i++)
{
// 读取按键状态
uint8_t pinState = GPIO_ReadInputDataBit(KEY_PORTS[i], KEY_PINS[i]);
if(pinState == 0) // 按键按下
{
if(!keyInfo[i].isPressed) //isPressed为0表示按键未按下
{
keyInfo[i].isPressed = 1;
keyInfo[i].pressTime = currentTime;
keyInfo[i].state = BTN_DEBOUNCE;
}
else if(keyInfo[i].state == BTN_DEBOUNCE) // 按键处于消抖状态
{
if(currentTime - keyInfo[i].pressTime >= LONG_PRESS_TIME) // 按键按下时间大于长按时间
{
keyInfo[i].state = BTN_LONG_PRESS; // 按键状态设置为长按状态
}
}
}
else // 按键释放
{
if(keyInfo[i].isPressed)
{
if(currentTime - keyInfo[i].pressTime >= DEBOUNCE_TIME)
{
if(keyInfo[i].state != BTN_LONG_PRESS)
{
keyInfo[i].state = BTN_SHORT_PRESS;
}
}
keyInfo[i].isPressed = 0;
}
}
}
}
光敏传感器模块
以下图是所使用的光敏电阻模块,芯片引脚接AO。
light_sensor常量定义
// 光照强度等级定义
typedef enum {
LIGHT_LEVEL_DARK = 0, // 黑暗
LIGHT_LEVEL_DIM, // 昏暗
LIGHT_LEVEL_NORMAL, // 正常
LIGHT_LEVEL_BRIGHT // 明亮
} LightLevel;
// 光照等级阈值定义(ADC值)
#define LIGHT_THRESHOLD_DARK 3000 // 黑暗阈值
#define LIGHT_THRESHOLD_DIM 1500 // 昏暗阈值
#define LIGHT_THRESHOLD_NORMAL 1000 // 正常阈值
LightSensor_Init()
该模块主要配置GPIO口和ADC初始化。
void LightSensor_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
// 使能ADC1和GPIOA的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
// 配置PA0为模拟输入
GPIO_InitStructure.GPIO_Pin = LIGHT_SENSOR_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(LIGHT_SENSOR_PORT, &GPIO_InitStructure);
// ADC1配置
ADC_DeInit(ADC1);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 单通道模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 单次转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 外部触发模式
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 数据对齐
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
// 配置ADC通道和采样时间
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); // 配置ADC通道和采样时间
// 使能ADC1
ADC_Cmd(ADC1, ENABLE);
// ADC校准
ADC_ResetCalibration(ADC1); // 复位校准
while (ADC_GetResetCalibrationStatus(ADC1))
;
ADC_StartCalibration(ADC1); // 开始校准
while (ADC_GetCalibrationStatus(ADC1))
;
}
LightSensor_GetValue()
获取ADC值主要通过ADC_GetConversionValue(ADC1);
uint16_t LightSensor_GetValue(void)
{
// 启动ADC转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
// 等待转换完成
while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC))
;
// 返回转换结果
return ADC_GetConversionValue(ADC1);
}
MPU6050模块
由MPU6050官方文档可以知道默认寄存器值为0x68