STM32 综合温控风扇控制系统

目录

  1. 项目总体概述
  2. 硬件资源与引脚分配
  3. 系统功能需求
  4. 软件总体架构
  5. 各模块详细原理说明
  6. 通信指令协议
  7. 控制逻辑说明(温控回差、滤波、模式切换)
  8. OLED 分页显示机制
  9. 主循环任务调度机制
  10. 程序全局变量与宏定义说明
  11. 运行流程
  12. 注意事项与硬件要求
  13. 系统优缺点与扩展方案
  14. 核心代码

1. 项目总体概述

本系统基于 STM32F103 单片机,实现手动蓝牙遥控模式自动温度控制模式双模式运行。 外设整合:LED 指示灯、HC-05 蓝牙串口、DS18B20 单总线温度传感器、TIM3 PWM 直流风扇驱动、I2C SSD1306(128×64)OLED 显示屏。

核心功能:

  1. 通过蓝牙串口下发单字符指令,远程控制 LED 开关、风扇转速;
  2. DS18B20 实时采集环境温度,滑动均值滤波抑制采样噪声;
  3. 自动模式下根据温度阈值 + 滞环回差自动调节风扇 PWM 转速,防止频繁启停抖动;
  4. OLED 双页面自动轮询显示系统运行状态;
  5. 主循环分时任务调度,无阻塞长延时,系统实时性稳定。

应用场景:智能温控散热系统、小型机箱温控风扇、实训综合嵌入式项目。

2. 硬件资源与引脚分配

引脚定义以头文件内宏为准,可根据硬件修改 | 外设 | 功能 | 引脚 | | ---- | ---- | ---- | | USART1(HC-05 蓝牙) | 串口收发,蓝牙通信 | PA9 (TX)、PA10 (RX) | | TIM3_CH1 PWM 输出 | 风扇调速信号 | PA6 | | OLED SSD1306(软件 I2C) | SDA、SCL、RST、DC | PA4、PA5 等(lcd9696.h 定义) | | DS18B20 单总线 | 温度数据 DQ 引脚 | 自定义 GPIO(ds18b20.h 宏) | | LED 指示灯 | 系统心跳灯、状态指示 | LED_PORT(led.h 定义) |

电气硬件说明

  1. PA6 PWM 信号不能直接驱动直流电机,必须外接 MOS 管 / 三极管驱动电路;
  2. DS18B20 建议外接 4.7K 上拉电阻;
  3. HC-05 蓝牙模块电平 3.3V,与 STM32 直接兼容,无需电平转换;
  4. OLED 为 I2C 接口,注意硬件上拉电阻。

3. 系统功能需求

3.1 模式需求

  • 自动模式 MODE_AUTO:系统根据采集温度自动调节风扇档位,不受蓝牙手动指令干预;
  • 手动模式 MODE_MANUAL:蓝牙指令直接控制风扇转速,脱离自动温控逻辑。

规则:任意风扇调速指令(0/2/3/4)会自动切入手动模式;发送字符A切换回自动模式。

3.2 控制对象需求

  1. LED:支持翻转开关;
  2. 风扇 4 档控制:停止、低速 30%、中速 60%、全速 100%;
  3. 温度实时采集、滤波处理;
  4. OLED 双页面循环显示状态;
  5. 蓝牙指令实时响应。

4. 软件总体架构

软件采用分层设计:

  1. 底层驱动层 led.h、usart.h、pwm.h、ds18b20.h、lcd9696.h、delay.h、SysTick.h 提供硬件初始化、读写、时序基础接口,业务无关。
  2. 业务逻辑层(主文件) 温度滤波、风扇温控算法、蓝牙命令解析、OLED 显示刷新、任务调度。
  3. 中断服务层 USART1 接收中断,仅负责接收字符、设置标志位,中断内禁止复杂运算

数据流流程: 蓝牙中断接收字符 → 设置全局标志 → 主循环检测标志 → 命令解析 → 更新系统状态 → PWM/LED执行 → OLED刷新状态 DS18B20采集温度 → 滑动均值滤波 → 自动模式下执行温控算法 → 更新风扇档位

5. 各模块详细原理说明

5.1 串口蓝牙模块(usart.c)

  1. 通信参数:USART1,波特率 9600,8N1;
  2. 开启接收中断USART1_IRQHandler
  3. 中断逻辑:
    • 接收字节,过滤非打印控制字符(0x20~0x7E 有效);
    • 若当前无未处理命令,将接收字符存入全局rec_flag
    • 中断只做数据捕获,所有业务逻辑放到主循环;
  4. fputc重定向,支持 printf 调试输出;

局限性:仅缓存单条指令,高速连续发送指令存在丢包,适合单字符简易指令场景。

5.2 PWM 风扇驱动(pwm.c)

定时器 TIM3,通道 1 PA6 复用推挽输出 参数配置: PSC=71,ARR=999

\(F_{PWM}=\frac{72000000}{(PSC+1)(ARR+1)}=\frac{72000000}{72\times1000}=10\mathrm{kHz}\)

档位占空比定义:

  • DUTY_STOP 0 → 0%
  • DUTY_SLOW 300 → 30%
  • DUTY_MID 600 → 60%
  • DUTY_FULL 999 → 100% 接口SetPWM_Duty(u16 duty)修改 CCR 寄存器实时调整占空比。

5.3 DS18B20 温度驱动(ds18b20.c)

标准单总线时序:复位脉冲、存在脉冲、读写位、读写字节。 工作流程:

  1. 发送 0x44 启动温度转换;
  2. 发送 0xBE 读取寄存器高低字节;
  3. 判断正负温度;
  4. 原始数据 ×0.0625 转换为摄氏度浮点;

依赖精准微秒延时delay_us(),SysTick 必须正确初始化。

5.4 OLED 显示驱动(lcd9696.c)

软件 I2C 驱动 SSD1306 128×64 OLED; 采用显存缓存机制 OLED_GRAM 1448 : 所有绘图、文字操作先修改内存显存,调用OLED_Refresh()一次性批量输出到屏幕,大幅降低屏幕闪烁。 内置接口:显示字符、字符串、数字、画点、清屏、屏幕反显、屏幕翻转。

5.5 LED 驱动(led.h)

普通 GPIO 推挽输出初始化,用于系统运行指示灯以及蓝牙可控 LED。

6. 蓝牙通信指令协议

上位机(蓝牙串口助手)发送单个 ASCII 字符:

表格

指令字符 功能说明
'1' LED 状态翻转
'2' 风扇低速 30%,自动切手动模式
'3' 风扇中速 60%,自动切手动模式
'4' 风扇全速 100%,自动切手动模式
'0' 风扇停止,自动切手动模式
'A' 切换至自动温控模式
'M' 切换至手动模式

7. 核心控制算法说明

7.1 温度滑动均值滤波 Get_Filter_Temp ()

采用 5 阶循环缓冲区滑动平均:

  1. 新温度存入循环数组,索引循环归零;
  2. 读取 5 组数据求和取平均值; 作用:滤除 DS18B20 瞬时跳变噪声,温度曲线平滑。

7.2 温控分段 + 滞环回差 Fan_PWM_Ctrl ()

温度阈值:

c

运行

复制代码
#define TEMP_STOP    22.0f   // 停止阈值
#define TEMP_LOW     25.0f   // 低速阈值
#define TEMP_MID     28.0f   // 中速阈值
#define TEMP_HYSTER  2.0f    // 回差2℃

逻辑原理: 普通阈值控制会出现温度在临界点上下小幅波动,风扇频繁切换档位; 引入2℃回差:升温到达阈值升档,降温必须低于「阈值 - 回差」才降档,消除振荡。

档位划分:

  1. T < 22.0℃ → 风扇停止
  2. 22.0℃ ≤ T <25.0℃ → 低速
  3. 25.0℃ ≤ T <28.0℃ → 中速
  4. T ≥28.0℃ → 全速

8. OLED 分页显示机制

设置自动翻页周期:PAGE_INTERVAL=30,配合 200ms 刷新周期,页面 6 秒自动切换。

  • 页面 A(disp_page=0) 当前温度、运行模式(AUTO/MANU)、风扇工作档位(STOP/SLOW/MID/FULL)
  • 页面 B(disp_page=1) LED 状态(ON/OFF)、蓝牙接收状态(Recv/Wait)、固定日期标识

每次刷新执行OLED_Clear()清空显存,重新绘制整页内容,避免文字残留遮挡。

9. 主循环分时任务调度机制

基础时基:10ms 循环节拍 delay_ms(10) 通过 tick_cnt 计数器实现分时任务,无长时间阻塞延时:

  1. 100ms 任务(tick_cnt%10):系统心跳 LED 闪烁,指示程序正常运行;
  2. 500ms 任务(tick_cnt%50):DS18B20 温度采集、滤波;自动模式执行温控算法;
  3. 200ms 任务(tick_cnt%20):OLED 页面刷新、翻页计时;
  4. 实时轮询:持续检测蓝牙接收标志,收到命令立即处理。

优势:所有任务并行调度,新增功能只需增加判断条件,拓展性强。

10. 全局变量与宏定义说明

温度相关

temp_buf[]:温度采样缓存数组 temp_filter:滤波后有效温度 last_temp:上一次温度,用于回差判断

系统状态

run_mode:运行模式 0 自动 /1 手动 manual_duty:手动模式下风扇占空比 led_state:LED 开关状态 fan_state:风扇档位标识 0~3

OLED 显示

disp_page:当前页面编号 page_timer:页面切换计时器 disp_buf[]:字符串格式化缓冲区

通信

rec_flag:蓝牙接收标志,存储收到的命令字符

11. 系统整体运行流程

  1. 上电执行System_Init():时钟、串口、LED、PWM、OLED、DS18B20 初始化;
  2. OLED 初始化显示页面;
  3. 进入 while (1) 主循环分时调度;
  4. 每 500ms 采集一次温度并滤波;
    • 自动模式:根据温度自动调整风扇 PWM;
  5. 持续监听蓝牙中断:
    • 收到指令 → 解析命令,修改 LED、风扇状态、切换运行模式;
  6. 每 200ms 刷新 OLED 画面,6s 自动切换显示页面;
  7. 循环往复。

12. 硬件与程序注意事项

  1. DS18B20 对时序敏感,SysTick 延时必须精准;
  2. 电机严禁直接用 IO 驱动,必须增加驱动电路;
  3. 蓝牙连续快速发送指令会丢包,适合人工手动控制场景;
  4. OLED 软件 I2C 占用 CPU,若系统卡顿可更换硬件 I2C;
  5. 串口中断服务函数尽量精简,禁止调用 delay、复杂打印;
  6. 全局状态变量使用 volatile 修饰,保证中断与主循环数据同步

13. 系统优化扩展方案

  1. 增加 FIFO 指令缓冲区,解决蓝牙高速指令丢包问题;
  2. 增加独立按键,脱离蓝牙实现本地模式切换;
  3. 增加片内 Flash 存储温度阈值参数,支持掉电保存;
  4. OLED 增加温度曲线实时绘图;
  5. 增加风扇转速检测脉冲采集,实现闭环调速;
  6. 增加温度超限报警 LED / 蜂鸣器;
  7. 支持多字符指令协议,扩展更多控制功能。

14. 核心代码

复制代码
/*******************************************************************************
 * 整合代码:蓝牙控制 + LED + 电机(PWM) + 温度检测 + OLED显示
 * 功能:
 *   - 蓝牙串口接收命令控制LED和电机
 *   - DS18B20温度检测并自动调速
 *   - OLED分页显示,解决文字遮挡溢出
 *   - 命令:'1'=LED翻转, '2'=电机低速, '3'=电机中速, '4'=电机全速, '0'=停止
 ******************************************************************************/

#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "ds18b20.h"
#include "pwm.h"
#include "lcd9696.h"
#include "delay.h"
#include <stdio.h>
#include <string.h>
#include <math.h>

/* ============================================================
 *  宏定义
 * ============================================================ */

// 温度分段阈值
#define TEMP_STOP    22.0f
#define TEMP_LOW     25.0f
#define TEMP_MID     28.0f
#define TEMP_HYSTER  2.0f   // 回差2℃防抖动

// PWM配置:10KHz
#define PWM_PSC      71
#define PWM_ARR      999

// 各档位占空比
#define DUTY_STOP    0
#define DUTY_SLOW    300    // 30%
#define DUTY_MID     600    // 60%
#define DUTY_FULL    999    // 100%

// 蓝牙命令定义
#define CMD_LED_TOGGLE   '1'
#define CMD_FAN_SLOW     '2'
#define CMD_FAN_MID      '3'
#define CMD_FAN_FULL     '4'
#define CMD_FAN_STOP     '0'
#define CMD_AUTO_MODE    'A'   // 自动温控模式
#define CMD_MANUAL_MODE  'M'   // 手动模式

// 运行模式
#define MODE_AUTO        0
#define MODE_MANUAL      1

// OLED分页配置
#define PAGE_INTERVAL    30    // 30*200ms = 6s自动翻页

/* ============================================================
 *  全局变量
 * ============================================================ */

// 温度滤波缓存
float temp_buf[5];
u8    temp_buf_idx = 0;
float temp_filter = 0.0f;
float last_temp = 0.0f;

// 系统状态
//volatile u8  rec_flag = REC_FLAG_NONE;     // 蓝牙接收标志
volatile u8  run_mode = MODE_AUTO;          // 运行模式
volatile u16 manual_duty = DUTY_STOP;         // 手动模式占空比
volatile u8  led_state = 0;                  // LED状态
volatile u8  fan_state = 0;                  // 0停 1低速 2中速 3全速

// OLED分页变量
u8 disp_page = 0;       // 0=页面A,1=页面B
u16 page_timer = 0;

// 显示缓冲区
char disp_buf[32];

/* ============================================================
 *  函数声明
 * ============================================================ */
float Get_Filter_Temp(float new_temp);
void  Fan_PWM_Ctrl(float temp);
void  Update_Display(void);
void  Process_Bluetooth_Cmd(u8 cmd);
void  System_Init(void);

/* ============================================================
 *  温度滤波
 * ============================================================ */
float Get_Filter_Temp(float new_temp)
{
    u8 i;
    float sum = 0;

    temp_buf[temp_buf_idx++] = new_temp;
    if(temp_buf_idx >= 5) temp_buf_idx = 0;

    for(i = 0; i < 5; i++) sum += temp_buf[i];
    return sum / 5.0f;
}

/* ============================================================
 *  自动温控风扇控制
 * ============================================================ */
void Fan_PWM_Ctrl(float temp)
{
    static u8 last_fan_state = 0;

    if(temp < TEMP_STOP)
    {
        SetPWM_Duty(DUTY_STOP);
        fan_state = 0;
    }
    else if(temp >= TEMP_MID)
    {
        SetPWM_Duty(DUTY_FULL);
        fan_state = 3;
    }
    else if(temp >= TEMP_LOW)
    {
        // 回差判断:从高速降速需要低于阈值-回差
        if(last_temp >= TEMP_MID && temp < TEMP_MID - TEMP_HYSTER)
        {
            SetPWM_Duty(DUTY_MID);
            fan_state = 2;
        }
        else if(last_temp < TEMP_LOW)
        {
            SetPWM_Duty(DUTY_MID);
            fan_state = 2;
        }
    }
    else // TEMP_STOP ~ TEMP_LOW
    {
        if(last_temp >= TEMP_LOW && temp < TEMP_LOW - TEMP_HYSTER)
        {
            SetPWM_Duty(DUTY_SLOW);
            fan_state = 1;
        }
        else if(last_temp < TEMP_STOP)
        {
            SetPWM_Duty(DUTY_SLOW);
            fan_state = 1;
        }
    }

    last_temp = temp;
    last_fan_state = fan_state;
}

/* ============================================================
 *  处理蓝牙命令
 * ============================================================ */
void Process_Bluetooth_Cmd(u8 cmd)
{
    switch(cmd)
    {
        case CMD_LED_TOGGLE:
            led2 = !led2;
            led_state = led2;
           // printf("CMD: LED toggle -> %d\r\n", led_state);
            break;

        case CMD_FAN_STOP:
            run_mode = MODE_MANUAL;
            manual_duty = DUTY_STOP;
            SetPWM_Duty(DUTY_STOP);
            fan_state = 0;
            //printf("CMD: Fan STOP\r\n");
            break;

        case CMD_FAN_SLOW:
            run_mode = MODE_MANUAL;
            manual_duty = DUTY_SLOW;
            SetPWM_Duty(DUTY_SLOW);
            fan_state = 1;
            //printf("CMD: Fan SLOW 30%%\r\n");
            break;

        case CMD_FAN_MID:
            run_mode = MODE_MANUAL;
            manual_duty = DUTY_MID;
            SetPWM_Duty(DUTY_MID);
            fan_state = 2;
            //printf("CMD: Fan MID 60%%\r\n");
            break;

        case CMD_FAN_FULL:
            run_mode = MODE_MANUAL;
            manual_duty = DUTY_FULL;
            SetPWM_Duty(DUTY_FULL);
            fan_state = 3;
            //printf("CMD: Fan FULL 100%%\r\n");
            break;

        case CMD_AUTO_MODE:
            run_mode = MODE_AUTO;
            printf("CMD: Auto Mode\r\n");
            break;

        case CMD_MANUAL_MODE:
            run_mode = MODE_MANUAL;
            printf("CMD: Manual Mode\r\n");
            break;

        default:
            printf("CMD: Unknown 0x%02X\r\n", cmd);
            break;
    }
}

/* ============================================================
 *  更新OLED显示【分页版,解决文字遮挡】
 *  PageA:温度 + 模式 + 风扇状态
 *  PageB:LED状态 + 蓝牙状态 + 日期页码
 * ============================================================ */
void Update_Display(void)
{
    OLED_Clear();

    if(disp_page == 0)
    {
        /* Page A:温控信息 */
        OLED_ShowString(0,   0, (u8 *)"Temp:", 12);
        sprintf(disp_buf, "%.1fC", temp_filter);
        OLED_ShowString(48,  0, (u8 *)disp_buf, 12);

        OLED_ShowString(0,  18, (u8 *)"Mode:", 12);
        if(run_mode == MODE_AUTO)
        {
            OLED_ShowString(48, 18, (u8 *)"AUTO", 12);
        }
        else
        {
            OLED_ShowString(48, 18, (u8 *)"MANU", 12);
        }

        OLED_ShowString(0,  36, (u8 *)"Fan:", 12);
        switch(fan_state)
        {
            case 0:  OLED_ShowString(48, 36, (u8 *)"STOP", 12); break;
            case 1:  OLED_ShowString(48, 36, (u8 *)"SLOW", 12); break;
            case 2:  OLED_ShowString(48, 36, (u8 *)"MID", 12);  break;
            case 3:  OLED_ShowString(48, 36, (u8 *)"FULL", 12); break;
            default: OLED_ShowString(48, 36, (u8 *)"UNK", 12);  break;
        }

        OLED_ShowString(0, 72, (u8 *)"<Page A>", 12);
    }
    else
    {
        /* Page B:LED / 蓝牙 / 日期 */
        OLED_ShowString(0,   0, (u8 *)"LED:", 12);
        sprintf(disp_buf, "%s", led_state ? "ON" : "OFF");
        OLED_ShowString(48,  0, (u8 *)disp_buf, 12);

        OLED_ShowString(0,  18, (u8 *)"Cmd:", 12);
        if(rec_flag != REC_FLAG_NONE)
        {
            OLED_ShowString(48, 18, (u8 *)"Recv", 12);
        }
        else
        {
            OLED_ShowString(48, 18, (u8 *)"Wait", 12);
        }

        OLED_ShowString(0,  36, (u8 *)"Date:", 12);
        OLED_ShowString(48, 36, (u8 *)"26/07/22", 12);

        OLED_ShowString(0, 72, (u8 *)"<Page B>", 12);
    }

    OLED_Refresh();
}
/* ============================================================
 *  系统初始化
 * ============================================================ */
void System_Init(void)
{
    SysTick_Init(72);
    uart_init(9600);
    LED_Init();

    // PWM初始化
    TIM3_CH1_PWM_Init(PWM_ARR, PWM_PSC);
    SetPWM_Duty(0);

    // OLED初始化
    OLED_Init();
    OLED_ColorTurn(0);
    OLED_DisplayTurn(0);
    OLED_Clear();
    OLED_Refresh();

    // DS18B20检测
    printf("System Init...\r\n");
    if(DS18B20_Init() != 0)
    {
       // printf("DS18B20 ERROR!\r\n");
        OLED_ShowString(0, 0, (u8 *)"DS18B20 ERR", 12);
        OLED_Refresh();
    }
    else
    {
      //  printf("DS18B20 OK\r\n");
    }

    delay_ms(500);
}

/* ============================================================
 *  主函数
 * ============================================================ */
int main(void)
{
    u16 tick_cnt = 0;
    float temp_val = 0.0f;

    System_Init();

    // 初始显示
    Update_Display();

    while(1)
    {
        tick_cnt++;
        delay_ms(10);   // 10ms 主循环周期

        // ========== 100ms 任务:LED心跳 ==========
        if(tick_cnt % 10 == 0)
        {
            led2 = !led2;   // LED1 作为系统运行指示灯
        }

        // ========== 500ms 任务:温度采集 + 自动调速 ==========
        if(tick_cnt % 50 == 0)
        {
            temp_val = DS18B20_GetTemperture();
            temp_filter = Get_Filter_Temp(temp_val);

            // 自动模式下才进行温控
            if(run_mode == MODE_AUTO)
            {
                Fan_PWM_Ctrl(temp_filter);
            }

            // 串口打印温度
            //printf("Temp:%.2fC Fan:%d Mode:%s\r\n",
             //      temp_filter, fan_state,
             //      run_mode == MODE_AUTO ? "AUTO" : "MANU");
        }

        // ========== 200ms 任务:OLED刷新 + 翻页计时 ==========
        if(tick_cnt % 20 == 0)
        {
            page_timer++;
            // 定时切换页面
            if(page_timer >= PAGE_INTERVAL)
            {
                page_timer = 0;
                disp_page = !disp_page;
            }
            Update_Display();
        }

        // ========== 蓝牙命令处理 ==========
        if(rec_flag != REC_FLAG_NONE)
        {
            u8 cmd = rec_flag;
            Process_Bluetooth_Cmd(cmd);
            rec_flag = REC_FLAG_NONE;
        }

        // tick_cnt 防溢出
        if(tick_cnt >= 1000) tick_cnt = 0;
    }
}
cpp 复制代码
#include "lcd9696.h"
#include "stdlib.h"
#include "oledfont.h"  
u8 OLED_GRAM[144][8];

//反显函数
void OLED_ColorTurn(u8 i)
{
	if(i==0)
		{
			OLED_WR_Byte(0xA6,OLED_CMD);//正常显示
		}
	if(i==1)
		{
			OLED_WR_Byte(0xA7,OLED_CMD);//反色显示
		}
}

//屏幕旋转180度
void OLED_DisplayTurn(u8 i)
{
	if(i==0)
		{
			OLED_WR_Byte(0xC8,OLED_CMD);//正常显示
			OLED_WR_Byte(0xA1,OLED_CMD);
		}
	if(i==1)
		{
			OLED_WR_Byte(0xC0,OLED_CMD);//反转显示
			OLED_WR_Byte(0xA0,OLED_CMD);
		}
}

//起始信号
void I2C_Start(void)
{
	OLED_SDIN_Set();
	OLED_SCLK_Set();
	OLED_SDIN_Clr();
	OLED_SCLK_Clr();
}

//结束信号
void I2C_Stop(void)
{
	OLED_SCLK_Set();
	OLED_SDIN_Clr();
	OLED_SDIN_Set();
}

//等待信号响应
void I2C_WaitAck(void) //测数据信号的电平
{
	OLED_SCLK_Set();
	OLED_SCLK_Clr();
}

//写入一个字节
void Send_Byte(u8 dat)
{
	u8 i;
	for(i=0;i<8;i++)
	{
		OLED_SCLK_Clr();//将时钟信号设置为低电平
		if(dat&0x80)//将dat的8位从最高位依次写入
		{
			OLED_SDIN_Set();
    }
		else
		{
			OLED_SDIN_Clr();
    }
		OLED_SCLK_Set();//将时钟信号设置为高电平
		OLED_SCLK_Clr();//将时钟信号设置为低电平
		dat<<=1;
  }
}

//发送一个字节
//向SSD1306写入一个字节。
//mode:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(u8 dat,u8 mode)
{
	I2C_Start();
	Send_Byte(0x78);
	I2C_WaitAck();
	if(mode){Send_Byte(0x40);}
  else{Send_Byte(0x00);}
	I2C_WaitAck();
	Send_Byte(dat);
	I2C_WaitAck();
	I2C_Stop();
}


//开启OLED显示 
void OLED_DisPlay_On(void)
{
	OLED_WR_Byte(0x8D,OLED_CMD);//电荷泵使能
	OLED_WR_Byte(0x14,OLED_CMD);//开启电荷泵
	OLED_WR_Byte(0xAF,OLED_CMD);//点亮屏幕
}

//关闭OLED显示 
void OLED_DisPlay_Off(void)
{
	OLED_WR_Byte(0x8D,OLED_CMD);//电荷泵使能
	OLED_WR_Byte(0x10,OLED_CMD);//关闭电荷泵
	OLED_WR_Byte(0xAF,OLED_CMD);//关闭屏幕
}

//更新显存到OLED	
void OLED_Refresh(void)
{
	u8 i,n;
	for(i=0;i<8;i++)
	{
	   OLED_WR_Byte(0xb0+i,OLED_CMD); //设置行起始地址
	   OLED_WR_Byte(0x00,OLED_CMD);   //设置低列起始地址
	   OLED_WR_Byte(0x10,OLED_CMD);   //设置高列起始地址
	   for(n=0;n<128;n++)
		 OLED_WR_Byte(OLED_GRAM[n][i],OLED_DATA);
  }
}
//清屏函数
void OLED_Clear(void)
{
	u8 i,n;
	for(i=0;i<8;i++)
	{
	   for(n=0;n<128;n++)
			{
			 OLED_GRAM[n][i]=0;//清除所有数据
			}
  }
	OLED_Refresh();//更新显示
}

//画点 
//x:0~127
//y:0~63
void OLED_DrawPoint(u8 x,u8 y)
{
	u8 i,m,n;
	i=y/8;
	m=y%8;
	n=1<<m;
	OLED_GRAM[x][i]|=n;
}

//清除一个点
//x:0~127
//y:0~63
void OLED_ClearPoint(u8 x,u8 y)
{
	u8 i,m,n;
	i=y/8;
	m=y%8;
	n=1<<m;
	OLED_GRAM[x][i]=~OLED_GRAM[x][i];
	OLED_GRAM[x][i]|=n;
	OLED_GRAM[x][i]=~OLED_GRAM[x][i];
}


//画线
//x:0~128
//y:0~64
void OLED_DrawLine(u8 x1,u8 y1,u8 x2,u8 y2)
{
	u8 i,k,k1,k2,y0;
	if((x1<0)||(x2>128)||(y1<0)||(y2>64)||(x1>x2)||(y1>y2))return;
	if(x1==x2)    //画竖线
	{
			for(i=0;i<(y2-y1);i++)
			{
				OLED_DrawPoint(x1,y1+i);
			}
  }
	else if(y1==y2)   //画横线
	{
			for(i=0;i<(x2-x1);i++)
			{
				OLED_DrawPoint(x1+i,y1);
			}
  }
	else      //画斜线
	{
		k1=y2-y1;
		k2=x2-x1;
		k=k1*10/k2;
		for(i=0;i<(x2-x1);i++)
			{
			  OLED_DrawPoint(x1+i,y1+i*k/10);
			}
	}
}
//x,y:圆心坐标
//r:圆的半径
void OLED_DrawCircle(u8 x,u8 y,u8 r)
{
	int a, b,num;
    a = 0;
    b = r;
    while(2 * b * b >= r * r)      
    {
        OLED_DrawPoint(x + a, y - b);
        OLED_DrawPoint(x - a, y - b);
        OLED_DrawPoint(x - a, y + b);
        OLED_DrawPoint(x + a, y + b);
 
        OLED_DrawPoint(x + b, y + a);
        OLED_DrawPoint(x + b, y - a);
        OLED_DrawPoint(x - b, y - a);
        OLED_DrawPoint(x - b, y + a);
        
        a++;
        num = (a * a + b * b) - r*r;//计算画的点离圆心的距离
        if(num > 0)
        {
            b--;
            a--;
        }
    }
}



//在指定位置显示一个字符,包括部分字符
//x:0~127
//y:0~63
//size:选择字体 12/16/24
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size1)
{
	u8 i,m,temp,size2,chr1;
	u8 y0=y;
	size2=(size1/8+((size1%8)?1:0))*(size1/2);  //得到字体一个字符对应点阵集所占的字节数
	chr1=chr-' ';  //计算偏移后的值
	for(i=0;i<size2;i++)
	{
		if(size1==12)
        {temp=asc2_1206[chr1][i];} //调用1206字体
		else if(size1==16)
        {temp=asc2_1608[chr1][i];} //调用1608字体
		else if(size1==24)
        {temp=asc2_2412[chr1][i];} //调用2412字体
		else return;
				for(m=0;m<8;m++)           //写入数据
				{
					if(temp&0x80)OLED_DrawPoint(x,y);
					else OLED_ClearPoint(x,y);
					temp<<=1;
					y++;
					if((y-y0)==size1)
					{
						y=y0;
						x++;
						break;
          }
				}
  }
}


//显示字符串
//x,y:起点坐标  
//size1:字体大小 
//*chr:字符串起始地址 
void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 size1)
{
	while((*chr>=' ')&&(*chr<='~'))//判断是不是非法字符!
	{
		OLED_ShowChar(x,y,*chr,size1);
		x+=size1/2;
		if(x>128-size1)  //换行
		{
			x=0;
			y+=2;
    }
		chr++;
  }
}

//m^n
u32 OLED_Pow(u8 m,u8 n)
{
	u32 result=1;
	while(n--)
	{
	  result*=m;
	}
	return result;
}

////显示2个数字
////x,y :起点坐标	 
////len :数字的位数
////size:字体大小
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size1)
{
	u8 t,temp;
	for(t=0;t<len;t++)
	{
		temp=(num/OLED_Pow(10,len-t-1))%10;
			if(temp==0)
			{
				OLED_ShowChar(x+(size1/2)*t,y,'0',size1);
      }
			else 
			{
			  OLED_ShowChar(x+(size1/2)*t,y,temp+'0',size1);
			}
  }
}

//显示汉字
//x,y:起点坐标
//num:汉字对应的序号
void OLED_ShowChinese(u8 x,u8 y,u8 num,u8 size1)
{
	u8 i,m,n=0,temp,chr1;
	u8 x0=x,y0=y;
	u8 size3=size1/8;
	while(size3--)
	{
		chr1=num*size1/8+n;
		n++;
			for(i=0;i<size1;i++)
			{
				if(size1==16)
						{temp=Hzk1[chr1][i];}//调用16*16字体
				else if(size1==24)
						{temp=Hzk2[chr1][i];}//调用24*24字体
				else if(size1==32)       
						{temp=Hzk3[chr1][i];}//调用32*32字体
				else if(size1==64)
						{temp=Hzk4[chr1][i];}//调用64*64字体
				else return;
							
						for(m=0;m<8;m++)
							{
								if(temp&0x01)OLED_DrawPoint(x,y);
								else OLED_ClearPoint(x,y);
								temp>>=1;
								y++;
							}
							x++;
							if((x-x0)==size1)
							{x=x0;y0=y0+8;}
							y=y0;
			 }
	}
}

//num 显示汉字的个数
//space 每一遍显示的间隔
void OLED_ScrollDisplay(u8 num,u8 space)
{
	u8 i,n,t=0,m=0,r;
	while(1)
	{
		if(m==0)
		{
	    OLED_ShowChinese(128,24,t,16); //写入一个汉字保存在OLED_GRAM[][]数组中
			t++;
		}
		if(t==num)
			{
				for(r=0;r<16*space;r++)      //显示间隔
				 {
					for(i=0;i<144;i++)
						{
							for(n=0;n<8;n++)
							{
								OLED_GRAM[i-1][n]=OLED_GRAM[i][n];
							}
						}
           OLED_Refresh();
				 }
        t=0;
      }
		m++;
		if(m==16){m=0;}
		for(i=0;i<144;i++)   //实现左移
		{
			for(n=0;n<8;n++)
			{
				OLED_GRAM[i-1][n]=OLED_GRAM[i][n];
			}
		}
		OLED_Refresh();
	}
}

//配置写入数据的起始位置
void OLED_WR_BP(u8 x,u8 y)
{
	OLED_WR_Byte(0xb0+y,OLED_CMD);//设置行起始地址
	OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);
	OLED_WR_Byte((x&0x0f)|0x01,OLED_CMD);
}

//x0,y0:起点坐标
//x1,y1:终点坐标
//BMP[]:要写入的图片数组
void OLED_ShowPicture(u8 x0,u8 y0,u8 x1,u8 y1,u8 BMP[])
{
	u32 j=0;
	u8 x=0,y=0;
	if(y%8==0)y=0;
	else y+=1;
	for(y=y0;y<y1;y++)
	 {
		 OLED_WR_BP(x0,y);
		 for(x=x0;x<x1;x++)
		 {
			 OLED_WR_Byte(BMP[j],OLED_DATA);
			 j++;
     }
	 }
}
//OLED的初始化
void OLED_Init(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	 //使能A端口时钟
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_7;	 
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
 	GPIO_Init(GPIOA, &GPIO_InitStructure);	  //初始化GPIOD3,6
 	GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_7|GPIO_Pin_4);	
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	 //使能A端口时钟
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_8;	 
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
 	GPIO_Init(GPIOB, &GPIO_InitStructure);	  //初始化GPIOD3,6
 	GPIO_SetBits(GPIOB,GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_8);	
	
	OLED_RST_Set();
	delay_ms(100);
	OLED_RST_Clr();//复位
	delay_ms(200);
	OLED_RST_Set();
	OLED_DC_Clr();
	
	OLED_WR_Byte(0xAE,OLED_CMD);//--turn off oled panel
	OLED_WR_Byte(0x00,OLED_CMD);//---set low column address
	OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
	OLED_WR_Byte(0x40,OLED_CMD);//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
	OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control register
	OLED_WR_Byte(0xCF,OLED_CMD);// Set SEG Output Current Brightness
	OLED_WR_Byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
	OLED_WR_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
	OLED_WR_Byte(0xA6,OLED_CMD);//--set normal display
	OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
	OLED_WR_Byte(0x3f,OLED_CMD);//--1/64 duty
	OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset	Shift Mapping RAM Counter (0x00~0x3F)
	OLED_WR_Byte(0x00,OLED_CMD);//-not offset
	OLED_WR_Byte(0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequency
	OLED_WR_Byte(0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec
	OLED_WR_Byte(0xD9,OLED_CMD);//--set pre-charge period
	OLED_WR_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
	OLED_WR_Byte(0xDA,OLED_CMD);//--set com pins hardware configuration
	OLED_WR_Byte(0x12,OLED_CMD);
	OLED_WR_Byte(0xDB,OLED_CMD);//--set vcomh
	OLED_WR_Byte(0x40,OLED_CMD);//Set VCOM Deselect Level
	OLED_WR_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)
	OLED_WR_Byte(0x02,OLED_CMD);//
	OLED_WR_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disable
	OLED_WR_Byte(0x14,OLED_CMD);//--set(0x10) disable
	OLED_WR_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)
	OLED_WR_Byte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7) 
	OLED_WR_Byte(0xAF,OLED_CMD);
	OLED_Clear();
}
cpp 复制代码
#include "sys.h"
#include "usart.h"
#include "led.h"
#include "delay.h"

#if 1
#pragma import(__use_no_semihosting)
struct __FILE { int handle; };
FILE __stdout;
_sys_exit(int x) { x = x; }
int fputc(int ch, FILE *f)
{
    while ((USART1->SR & 0x40) == 0);
    USART1->DR = (u8)ch;
    return ch;
}
#endif

#if EN_USART1_RX

volatile u8  USART_RX_BUF[USART_REC_LEN];
volatile u16 USART_RX_STA = 0;
volatile u8  rec_flag = REC_FLAG_NONE;

static void USART_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

static void USART_NVIC_Init(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel                   = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority         = 3;
    NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

static void USART_Periph_Init(u32 baudrate)
{
    USART_InitTypeDef USART_InitStructure;
    USART_InitStructure.USART_BaudRate            = baudrate;
    USART_InitStructure.USART_WordLength          = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits            = USART_StopBits_1;
    USART_InitStructure.USART_Parity              = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStructure);
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    USART_Cmd(USART1, ENABLE);
}

void uart_init(u32 bound)
{
    USART_GPIO_Init();
    USART_NVIC_Init();
    USART_Periph_Init(bound);
}

static u8 IsPrintable(u8 ch)
{
    return (ch >= 0x20 && ch <= 0x7E);
}
void USART1_IRQHandler(void)
{
    u8 Res;
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    {
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
        Res = USART_ReceiveData(USART1);
        
        // ===== 调试打印 =====
        printf("IRQ: Res=0x%02X dec=%d char='%c' rec_flag=%d\r\n", 
               Res, Res, Res, rec_flag);
        
        // 过滤控制字符
        if(Res < 0x20 || Res > 0x7E)
        {
            printf("IRQ: filtered control char\r\n");
            return;
        }
        
        USART_SendData(USART1, Res);
        //led1 = 0;
        while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
        //led1 = 1;
        
        if (rec_flag == REC_FLAG_NONE)
        {
            rec_flag = Res;   // 直接保存命令字符
        }
    }
    
}
/*void USART1_IRQHandler111(void)
{
    u8 Res;

    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    {
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
        Res = USART_ReceiveData(USART1);

        // 过滤控制字符
        if (!IsPrintable(Res))
        {
            return;
        }

        // 回显
        USART_SendData(USART1, Res);
        while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);

        // 设置标志(防止覆盖)
        if (rec_flag == REC_FLAG_NONE)
        {
            rec_flag = Res;   // 直接保存命令字符
        }
    }
}*/

#endif
相关推荐
科芯创展5 小时前
3.5A,60VIN,异步降压芯片,XZ1815
单片机·嵌入式硬件
茯苓gao5 小时前
嵌入式开发笔记:Qt信号槽机制深度解析——从原理到实战的全方位指南
开发语言·笔记·嵌入式硬件·qt·学习
浅止菌6 小时前
海思回归第二弹:瑞芯微的隐忧与对手的反击
嵌入式硬件
Kuakewei8886 小时前
PW2609A在2.6A负载下压差0.14V,温升较3A工况低11°C
单片机·嵌入式硬件
Zyed6 小时前
[FreeRTOS]Day7项目介绍
stm32·单片机·嵌入式硬件
xuxie998 小时前
start7 电机闭环控制
单片机·嵌入式硬件
嵌入式-老费8 小时前
esp32开发与应用(播放wav音频文件)
嵌入式硬件
LCG元8 小时前
嵌入式开发踩坑排查实战:从 STM32 外设初始化到 Linux SPI 驱动的 15 类硬件疑难故障深度复盘
linux·stm32·单片机
piaoyiren9 小时前
基于 STM32F103 蓝牙遥控云台超声波避障小车
stm32·单片机·嵌入式硬件·蓝牙·超声波测距·直流电机
Industio_触觉智能9 小时前
RK3576开发板UART驱动实战教程(下),基于触觉智能开发板演示
嵌入式硬件·硬件架构·串口·uart·智能硬件·rs232·rs485