STM32F103C8T6综合学习项目的具体功能实现详解


代码项目地址为stm32综合学习项目

项目概述(详细可在项目的Project_Structure中查看)

该项目基于STM32F103C8T6芯片,学习I2C,GPIO,ADC,EXTI,PWM等基础模块的使用,并通过模块化的方式使代码具有良好的可读性与健壮性。

准备的模块

  1. OLED显示屏(I2C)
  2. 蜂鸣器模块(GPIO)
  3. 光敏电阻传感器(ADC)
  4. 热敏电阻传感器(ADC)
  5. MPU6050陀螺仪(I2C)
  6. 旋转编码器(EXTI+TIM)
  7. 舵机(PWM)
  8. LED指示灯(GPIO)
  9. 按钮(GPIO+EXTI)
  10. 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


文章作者: LsWorld
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 LsWorld !
评论
  目录