STM32蓝牙连接Android实现云端数据通信(电机控制-开源)

news2025/4/19 10:02:39

引言

        基于 STM32F103C8T6 最小系统板完成电机控制。这个小项目采用 HAL 库方法实现,通过 CubeMAX 配置相关引脚,步进电机使用 28BYJ-48 (四相五线式步进电机),程序通过蓝牙连接手机 APP 端进行数据收发, OLED 显示当前步进电机角度,并且手机收到数据的同时返回云端保存,记录下当前电机角度。

        对于 28BYJ-48 步进电机,其工作原理基于电磁感应。当定子的某一相绕组通电时,会产生磁场,吸引转子转动。通过按照一定的顺序依次给各相绕组通电,就可以使转子按照预定的方向逐步转动。常见的通电方式有单四拍、双四拍和八拍等。

 一、STM32F103C8T6

芯片对应引脚信息如下:(我这里使用的是 普中-精灵1 开发板)

1. 硬件传感器连接

OLED:0.96寸OLED,四针。

对应引脚:VCC --------> VCC

                  GND --------> GND

                  SCL ---------> PB6

                  SDA ---------> PB7

蓝牙:HC-08,低功耗蓝牙(BLE),六针。

对应引脚:VCC --------> VCC

                  GND --------> GND

                  RXD --------> TXD(PA9)

                  TXD --------> RXD(PA10)

(PS:我这里蓝牙使用的是串口1---USART1

步进电机:28BYJ-48

驱动板 ULN2003D :IN1 --------> MOTO IN1(PB8)

                                  IN2 --------> MOTO IN2(PB9)

                                  IN3 --------> MOTO IN3(PB12)

                                  IN4 --------> MOTO IN4(PB13)

2. CubeMAX配置

开放电机对应的四个引脚

开启时钟,使用的是晶振。

 有线(SW调试接口)---- 我使用 ST-link 下载器

开启定时器2,内部时钟,开启中断,配置PSC和ARR。

打开 OLED 需要的I2C1

 蓝牙使用的串口1(串口波特率默认为 115200,一般蓝牙默认也是如此,若蓝牙和串口1的不相同,更改其一使他们相同即可)

 引脚设置完成后,芯片大致如下:

配置时钟频率

选择路径,勾选 Keil 代码显示 .c .h文件

点击 GENERATE CODE 生成相关代码

3. Keil5代码相关

        自动生成相关 Keil 5 代码函数,如下:

 OLED相关

        添加oled相关函数(就是市面上通用的oled例程--SSD1306驱动器)

#include "oled.h" 
#include "oledfont.h"
#include "main.h"
#include <stdio.h>
#include <stdarg.h>

extern I2C_HandleTypeDef hi2c1;

static uint8_t OLED_GRAM[128][8];



/**
  * @brief          write data/command to OLED, if you use spi, please rewrite the function
  * @param[in]      dat: the data ready to write
  * @param[in]      cmd: OLED_CMD means command; OLED_DATA means data
  * @retval         none
  */
/**
  * @brief          写数据或者指令到OLED, 如果使用的是SPI,请重写这个函数
  * @param[in]      dat: 要写入的字节
  * @param[in]      cmd: OLED_CMD 代表写入的字节是指令; OLED_DATA 代表写入的字节是数据
  * @retval         none
  */
void oled_write_byte(uint8_t dat, uint8_t cmd)
{
    static uint8_t cmd_data[2];
    if(cmd == OLED_CMD)
    {
        cmd_data[0] = 0x00;
    }
    else
    {
        cmd_data[0] = 0x40;
    }
    cmd_data[1] = dat;
    HAL_I2C_Master_Transmit(&hi2c1, OLED_I2C_ADDRESS, cmd_data, 2, 10);
}


/**
  * @brief          initialize the oled device
  * @param[in]      none
  * @retval         none
  */
/**
  * @brief          初始化OLED模块,
  * @param[in]      none
  * @retval         none
  */
void OLED_init(void)
{
    oled_write_byte(0xAE, OLED_CMD);    //display off
    oled_write_byte(0x20, OLED_CMD);    //Set Memory Addressing Mode	
    oled_write_byte(0x10, OLED_CMD);    //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
    oled_write_byte(0xb0, OLED_CMD);    //Set Page Start Address for Page Addressing Mode,0-7
    oled_write_byte(0xc8, OLED_CMD);    //Set COM Output Scan Direction
    oled_write_byte(0x00, OLED_CMD);    //---set low column address
    oled_write_byte(0x10, OLED_CMD);    //---set high column address
    oled_write_byte(0x40, OLED_CMD);    //--set start line address
    oled_write_byte(0x81, OLED_CMD);    //--set contrast control register
    oled_write_byte(0xff, OLED_CMD);    //brightness 0x00~0xff
    oled_write_byte(0xa1, OLED_CMD);    //--set segment re-map 0 to 127
    oled_write_byte(0xa6, OLED_CMD);    //--set normal display
    oled_write_byte(0xa8, OLED_CMD);    //--set multiplex ratio(1 to 64)
    oled_write_byte(0x3F, OLED_CMD);    //
    oled_write_byte(0xa4, OLED_CMD);    //0xa4,Output follows RAM content;0xa5,Output ignores RAM content
    oled_write_byte(0xd3, OLED_CMD);    //-set display offset
    oled_write_byte(0x00, OLED_CMD);    //-not offset
    oled_write_byte(0xd5, OLED_CMD);    //--set display clock divide ratio/oscillator frequency
    oled_write_byte(0xf0, OLED_CMD);    //--set divide ratio
    oled_write_byte(0xd9, OLED_CMD);    //--set pre-charge period
    oled_write_byte(0x22, OLED_CMD);    //
    oled_write_byte(0xda, OLED_CMD);    //--set com pins hardware configuration
    oled_write_byte(0x12, OLED_CMD);
    oled_write_byte(0xdb, OLED_CMD);    //--set vcomh
    oled_write_byte(0x20, OLED_CMD);    //0x20,0.77xVcc
    oled_write_byte(0x8d, OLED_CMD);    //--set DC-DC enable
    oled_write_byte(0x14, OLED_CMD);    //
    oled_write_byte(0xaf, OLED_CMD);    //--turn on oled panel
}

/**
  * @brief          turn on OLED display
  * @param[in]      none
  * @retval         none
  */
/**
  * @brief          打开OLED显示
  * @param[in]      none
  * @retval         none
  */
void OLED_display_on(void)
{
    oled_write_byte(0x8d, OLED_CMD);
    oled_write_byte(0x14, OLED_CMD);
    oled_write_byte(0xaf, OLED_CMD);
}

/**
  * @brief          turn off OLED display
  * @param[in]      none
  * @retval         none
  */
/**
  * @brief          关闭OLED显示
  * @param[in]      none
  * @retval         none
  */
void OLED_display_off(void)
{
    oled_write_byte(0x8d, OLED_CMD);
    oled_write_byte(0x10, OLED_CMD);
    oled_write_byte(0xae, OLED_CMD);
}

/**
  * @brief          operate the graphic ram(size: 128*8 char)
  * @param[in]      pen: the type of operate.
                    PEN_CLEAR: set ram to 0x00
                    PEN_WRITE: set ram to 0xff
                    PEN_INVERSION: bit inversion 
  * @retval         none
  */
/**
  * @brief          操作GRAM内存(128*8char数组)
  * @param[in]      pen: 操作类型.
                    PEN_CLEAR: 设置为0x00
                    PEN_WRITE: 设置为0xff
                    PEN_INVERSION: 按位取反
  * @retval         none
  */
void OLED_operate_gram(pen_typedef pen)
{
    uint8_t i, n;

    for (i = 0; i < 8; i++)
    {
        for (n = 0; n < 128; n++)
        {
            if (pen == PEN_WRITE)
            {
                OLED_GRAM[n][i] = 0xff;
            }
            else if (pen == PEN_CLEAR)
            {
                OLED_GRAM[n][i] = 0x00;
            }
            else
            {
                OLED_GRAM[n][i] = 0xff - OLED_GRAM[n][i];
            }
        }
    }
}

/**
  * @brief          cursor set to (x,y) point
  * @param[in]      x:X-axis, from 0 to 127
  * @param[in]      y:Y-axis, from 0 to 7
  * @retval         none
  */
/**
  * @brief          设置光标起点(x,y)
  * @param[in]      x:x轴, 从 0 到 127
  * @param[in]      y:y轴, 从 0 到 7
  * @retval         none
  */
void OLED_set_pos(uint8_t x, uint8_t y)
{
    oled_write_byte((0xb0 + y), OLED_CMD);              //set page address y
    oled_write_byte(((x&0xf0)>>4)|0x10, OLED_CMD);      //set column high address
    oled_write_byte((x&0x0f), OLED_CMD);                //set column low address
}


/**
  * @brief          draw one bit of graphic raw, operate one point of screan(128*64)
  * @param[in]      x: x-axis, [0, X_WIDTH-1]
  * @param[in]      y: y-axis, [0, Y_WIDTH-1]
  * @param[in]      pen: type of operation,
                        PEN_CLEAR: set (x,y) to 0
                        PEN_WRITE: set (x,y) to 1
                        PEN_INVERSION: (x,y) value inversion 
  * @retval         none
  */
/**
  * @brief          操作GRAM中的一个位,相当于操作屏幕的一个点
  * @param[in]      x:x轴,  [0,X_WIDTH-1]
  * @param[in]      y:y轴,  [0,Y_WIDTH-1]
  * @param[in]      pen: 操作类型,
                        PEN_CLEAR: 设置 (x,y) 点为 0
                        PEN_WRITE: 设置 (x,y) 点为 1
                        PEN_INVERSION: (x,y) 值反转
  * @retval         none
  */
void OLED_draw_point(int8_t x, int8_t y, pen_typedef pen)
{
    uint8_t page = 0, row = 0;

    /* check the corrdinate */
    if ((x < 0) || (x > (X_WIDTH - 1)) || (y < 0) || (y > (Y_WIDTH - 1)))
    {
        return;
    }
    page = y / 8;
    row = y % 8;

    if (pen == PEN_WRITE)
    {
        OLED_GRAM[x][page] |= 1 << row;
    }
    else if (pen == PEN_INVERSION)
    {
        OLED_GRAM[x][page] ^= 1 << row;
    }
    else
    {
        OLED_GRAM[x][page] &= ~(1 << row);
    }
}




/**
  * @brief          draw a line from (x1, y1) to (x2, y2)
  * @param[in]      x1: the start point of line
  * @param[in]      y1: the start point of line
  * @param[in]      x2: the end point of line
  * @param[in]      y2: the end point of line
  * @param[in]      pen: type of operation,PEN_CLEAR,PEN_WRITE,PEN_INVERSION.
  * @retval         none
  */
/**
  * @brief          画一条直线,从(x1,y1)到(x2,y2)
  * @param[in]      x1: 起点
  * @param[in]      y1: 起点
  * @param[in]      x2: 终点
  * @param[in]      y2: 终点
  * @param[in]      pen: 操作类型,PEN_CLEAR,PEN_WRITE,PEN_INVERSION.
  * @retval         none
  */
  
void OLED_draw_line(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, pen_typedef pen)
{
    uint8_t col = 0, row = 0;
    uint8_t x_st = 0, x_ed = 0, y_st = 0, y_ed = 0;
    float k = 0.0f, b = 0.0f;

    if (y1 == y2)
    {
        (x1 <= x2) ? (x_st = x1):(x_st = x2);
        (x1 <= x2) ? (x_ed = x2):(x_ed = x1);

        for (col = x_st; col <= x_ed; col++)
        {
            OLED_draw_point(col, y1, pen);
        }
    }
    else if (x1 == x2)
    {
        (y1 <= y2) ? (y_st = y1):(y_st = y2);
        (y1 <= y2) ? (y_ed = y2):(y_ed = y1);

        for (row = y_st; row <= y_ed; row++)
        {
            OLED_draw_point(x1, row, pen);
        }
    }
    else
    {
        k = ((float)(y2 - y1)) / (x2 - x1);
        b = (float)y1 - k * x1;

        (x1 <= x2) ? (x_st = x1):(x_st = x2);
        (x1 <= x2) ? (x_ed = x2):(x_ed = x2);

        for (col = x_st; col <= x_ed; col++)
        {
            OLED_draw_point(col, (uint8_t)(col * k + b), pen);
        }
    }
}


/**
  * @brief          show a character
  * @param[in]      row: start row of character
  * @param[in]      col: start column of character
  * @param[in]      chr: the character ready to show
  * @retval         none
  */
/**
  * @brief          显示一个字符
  * @param[in]      row: 字符的开始行
  * @param[in]      col: 字符的开始列
  * @param[in]      chr: 字符
  * @retval         none
  */
void OLED_show_char(uint8_t row, uint8_t col, uint8_t chr)
{
    uint8_t x = col * 6;
    uint8_t y = row * 12;
    uint8_t temp, t, t1;
    uint8_t y0 = y;
    chr = chr - ' ';

    for (t = 0; t < 12; t++)
    {
        temp = asc2_1206[chr][t];

        for (t1 = 0; t1 < 8; t1++)
        {
            if (temp&0x80)
                OLED_draw_point(x, y, PEN_WRITE);
            else
                OLED_draw_point(x, y, PEN_CLEAR);

            temp <<= 1;
            y++;
            if ((y - y0) == 12)
            {
                y = y0;
                x++;
                break;
            }
        }
    }
}


/**
  * @brief          show a character string
  * @param[in]      row: row of character string begin
  * @param[in]      col: column of character string begin
  * @param[in]      chr: the pointer to character string
  * @retval         none
  */
/**
  * @brief          显示一个字符串
  * @param[in]      row: 字符串的开始行
  * @param[in]      col: 字符串的开始列
  * @param[in]      chr: 字符串
  * @retval         none
  */
void OLED_show_string(uint8_t row, uint8_t col, uint8_t *chr)
{
    uint8_t n =0;
	
	  uint8_t max_len = 21 - col; // 每行最多显示21个字符(128/6≈21)
	
	  // 先填充空格覆盖旧内容
    for (n = 0; n < max_len; n++) {
        OLED_show_char(row, col + n, ' ');
    }
		
		// 再显示新内容
    n = 0;

    while (chr[n] != '\0')
    {
        OLED_show_char(row, col, chr[n]);
        col++;

        if (col > 20)
        {
            col = 0;
            row += 1;
        }
        n++;
    }
		OLED_refresh_gram();
}


/**
  * @brief          formatted output in oled 128*64
  * @param[in]      row: row of character string begin, 0 <= row <= 4;
  * @param[in]      col: column of character string begin, 0 <= col <= 20;
  * @param          *fmt: the pointer to format character string
  * @note           if the character length is more than one row at a time, the extra characters will be truncated
  * @retval         none
  */
/**
  * @brief          格式输出
  * @param[in]      row: 开始列,0 <= row <= 4;
  * @param[in]      col: 开始行, 0 <= col <= 20;
  * @param[in]      *fmt:格式化输出字符串
  * @note           如果字符串长度大于一行,额外的字符会换行
  * @retval         none
  */
void OLED_printf(uint8_t row, uint8_t col, const char *fmt,...)
{
    static uint8_t LCD_BUF[128] = {0};
    static va_list ap;
    uint8_t remain_size = 0;

    if ((row > 4) || (col > 20) )
    {
        return;
    }
    va_start(ap, fmt);

    vsprintf((char *)LCD_BUF, fmt, ap);

    va_end(ap);

    remain_size = 21 - col;

    LCD_BUF[remain_size] = '\0';

    OLED_show_string(row, col, LCD_BUF);
}

/**
  * @brief          send the data of gram to oled sreen
  * @param[in]      none
  * @retval         none
  */
/**
  * @brief          发送数据到OLED的GRAM
  * @param[in]      none
  * @retval         none
  */
void OLED_refresh_gram(void)
{
    uint8_t i, n;

    for (i = 0; i < 8; i++)
    {
        OLED_set_pos(0, i);
        for (n = 0; n < 128; n++)
        {
            oled_write_byte(OLED_GRAM[n][i], OLED_DATA);
        }
    }
}



/**
  * @brief          show the logo of RoboMaster
  * @param[in]      none
  * @retval         none
  */
/**
  * @brief          显示RM的LOGO
  * @param[in]      none
  * @retval         none
  */
void OLED_LOGO(void)
{
    uint8_t temp_char = 0;
    uint8_t x = 0, y = 0;
    uint8_t i = 0;
    OLED_operate_gram(PEN_CLEAR);


    for(; y < 64; y += 8)
    {
        for(x = 0; x < 128; x++)
        {
            temp_char = LOGO_BMP[x][y/8];
            for(i = 0; i < 8; i++)
            {
                if(temp_char & 0x80)
                {
                    OLED_draw_point(x, y + i,PEN_WRITE);
                }
                else
                {
                    OLED_draw_point(x,y + i,PEN_CLEAR);
                }
                temp_char <<= 1;
            }
        }
    }
    OLED_refresh_gram();
}

oled.h

/**
  ****************************(C) COPYRIGHT 2019 DJI****************************
  * @file       oled.c/h
  * @brief      0.96 inch oled use SSD1306 driver. the file includes oled initialization function,
  *             and some OLED setting function, GRAM operate function, oled show num ,char and string function,
  *             show RoboMaster LOGO function.
  *             0.96OLED使用SSD1306驱动器,本文件包括初始化函数以及其他OLED设置函数, GRAM操作函数,oled显示数字,字符,字符串函数
  *             以及显示RoboMaster LOGO函数
  * @note       
  * @history
  *  Version    Date            Author          Modification
  *  V1.0.0     Dec-26-2018     RM              1. done
  *
  @verbatim
  ==============================================================================

  ==============================================================================
  @endverbatim
  ****************************(C) COPYRIGHT 2019 DJI****************************
  */

#ifndef _OLED_H_
#define _OLED_H_

#include "stm32f1xx_hal.h"
#include <stdint.h>


// the I2C address of oled
#define OLED_I2C_ADDRESS    0x78

//the resolution of oled   128*64
#define MAX_COLUMN      128
#define MAX_ROW         64

#define X_WIDTH         MAX_COLUMN
#define Y_WIDTH         MAX_ROW

#define OLED_CMD        0x00
#define OLED_DATA       0x01

#define CHAR_SIZE_WIDTH 6
#define CHAR_SIZE_HIGHT 12


typedef enum
{
    PEN_CLEAR = 0x00,
    PEN_WRITE = 0x01,
    PEN_INVERSION= 0x02,
}pen_typedef;



/**
  * @brief          initialize the oled device
  * @param[in]      none
  * @retval         none
  */
/**
  * @brief          初始化OLED模块,
  * @param[in]      none
  * @retval         none
  */
extern void OLED_init(void);


/**
  * @brief          turn on OLED display
  * @param[in]      none
  * @retval         none
  */
/**
  * @brief          打开OLED显示
  * @param[in]      none
  * @retval         none
  */
extern void OLED_display_on(void);


/**
  * @brief          turn off OLED display
  * @param[in]      none
  * @retval         none
  */
/**
  * @brief          关闭OLED显示
  * @param[in]      none
  * @retval         none
  */
extern void OLED_display_off(void);


/**
  * @brief          operate the graphic ram(size: 128*8 char)
  * @param[in]      pen: the type of operate.
                    PEN_CLEAR: set ram to 0x00
                    PEN_WRITE: set ram to 0xff
                    PEN_INVERSION: bit inversion 
  * @retval         none
  */
/**
  * @brief          操作GRAM内存(128*8char数组)
  * @param[in]      pen: 操作类型.
                    PEN_CLEAR: 设置为0x00
                    PEN_WRITE: 设置为0xff
                    PEN_INVERSION: 按位取反
  * @retval         none
  */
extern void OLED_operate_gram(pen_typedef pen);


/**
  * @brief          cursor set to (x,y) point
  * @param[in]      x:X-axis, from 0 to 127
  * @param[in]      y:Y-axis, from 0 to 7
  * @retval         none
  */
/**
  * @brief          设置光标起点(x,y)
  * @param[in]      x:x轴, 从 0 到 127
  * @param[in]      y:y轴, 从 0 到 7
  * @retval         none
  */
extern void OLED_set_pos(uint8_t x, uint8_t y);


/**
  * @brief          draw one bit of graphic raw, operate one point of screan(128*64)
  * @param[in]      x: x-axis, [0, X_WIDTH-1]
  * @param[in]      y: y-axis, [0, Y_WIDTH-1]
  * @param[in]      pen: type of operation,
                        PEN_CLEAR: set (x,y) to 0
                        PEN_WRITE: set (x,y) to 1
                        PEN_INVERSION: (x,y) value inversion 
  * @retval         none
  */
/**
  * @brief          操作GRAM中的一个位,相当于操作屏幕的一个点
  * @param[in]      x:x轴,  [0,X_WIDTH-1]
  * @param[in]      y:y轴,  [0,Y_WIDTH-1]
  * @param[in]      pen: 操作类型,
                        PEN_CLEAR: 设置 (x,y) 点为 0
                        PEN_WRITE: 设置 (x,y) 点为 1
                        PEN_INVERSION: (x,y) 值反转
  * @retval         none
  */
extern void OLED_draw_point(int8_t x, int8_t y, pen_typedef pen);


/**
  * @brief          draw a line from (x1, y1) to (x2, y2)
  * @param[in]      x1: the start point of line
  * @param[in]      y1: the start point of line
  * @param[in]      x2: the end point of line
  * @param[in]      y2: the end point of line
  * @param[in]      pen: type of operation,PEN_CLEAR,PEN_WRITE,PEN_INVERSION.
  * @retval         none
  */
/**
  * @brief          画一条直线,从(x1,y1)到(x2,y2)
  * @param[in]      x1: 起点
  * @param[in]      y1: 起点
  * @param[in]      x2: 终点
  * @param[in]      y2: 终点
  * @param[in]      pen: 操作类型,PEN_CLEAR,PEN_WRITE,PEN_INVERSION.
  * @retval         none
  */
extern void OLED_draw_line(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, pen_typedef pen);


/**
  * @brief          show a character
  * @param[in]      row: start row of character
  * @param[in]      col: start column of character
  * @param[in]      chr: the character ready to show
  * @retval         none
  */
/**
  * @brief          显示一个字符
  * @param[in]      row: 字符的开始行
  * @param[in]      col: 字符的开始列
  * @param[in]      chr: 字符
  * @retval         none
  */
extern void OLED_show_char(uint8_t row, uint8_t col, uint8_t chr);

/**
  * @brief          show a character string
  * @param[in]      row: row of character string begin
  * @param[in]      col: column of character string begin
  * @param[in]      chr: the pointer to character string
  * @retval         none
  */
/**
  * @brief          显示一个字符串
  * @param[in]      row: 字符串的开始行
  * @param[in]      col: 字符串的开始列
  * @param[in]      chr: 字符串
  * @retval         none
  */
extern void OLED_show_string(uint8_t row, uint8_t col, uint8_t *chr);

/**
  * @brief          formatted output in oled 128*64
  * @param[in]      row: row of character string begin, 0 <= row <= 4;
  * @param[in]      col: column of character string begin, 0 <= col <= 20;
  * @param          *fmt: the pointer to format character string
  * @note           if the character length is more than one row at a time, the extra characters will be truncated
  * @retval         none
  */
/**
  * @brief          格式输出
  * @param[in]      row: 开始列,0 <= row <= 4;
  * @param[in]      col: 开始行, 0 <= col <= 20;
  * @param[in]      *fmt:格式化输出字符串
  * @note           如果字符串长度大于一行,额外的字符会换行
  * @retval         none
  */
extern void OLED_printf(uint8_t row, uint8_t col, const char *fmt,...);

/**
  * @brief          send the data of gram to oled sreen
  * @param[in]      none
  * @retval         none
  */
/**
  * @brief          发送数据到OLED的GRAM
  * @param[in]      none
  * @retval         none
  */
extern void OLED_refresh_gram(void);


/**
  * @brief          show the logo of RoboMaster
  * @param[in]      none
  * @retval         none
  */
/**
  * @brief          显示RM的LOGO
  * @param[in]      none
  * @retval         none
  */
extern void OLED_LOGO(void);
#endif

oledfont.h 

#ifndef __OLED__FONT__H
#define __OLED__FONT__H
//the common ascii character
const unsigned char asc2_1206[95][12]={
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",0*/
{0x00,0x00,0x00,0x00,0x3F,0x40,0x00,0x00,0x00,0x00,0x00,0x00},/*"!",1*/
{0x00,0x00,0x30,0x00,0x40,0x00,0x30,0x00,0x40,0x00,0x00,0x00},/*""",2*/
{0x09,0x00,0x0B,0xC0,0x3D,0x00,0x0B,0xC0,0x3D,0x00,0x09,0x00},/*"#",3*/
{0x18,0xC0,0x24,0x40,0x7F,0xE0,0x22,0x40,0x31,0x80,0x00,0x00},/*"$",4*/
{0x18,0x00,0x24,0xC0,0x1B,0x00,0x0D,0x80,0x32,0x40,0x01,0x80},/*"%",5*/
{0x03,0x80,0x1C,0x40,0x27,0x40,0x1C,0x80,0x07,0x40,0x00,0x40},/*"&",6*/
{0x10,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"'",7*/
{0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x80,0x20,0x40,0x40,0x20},/*"(",8*/
{0x00,0x00,0x40,0x20,0x20,0x40,0x1F,0x80,0x00,0x00,0x00,0x00},/*")",9*/
{0x09,0x00,0x06,0x00,0x1F,0x80,0x06,0x00,0x09,0x00,0x00,0x00},/*"*",10*/
{0x04,0x00,0x04,0x00,0x3F,0x80,0x04,0x00,0x04,0x00,0x00,0x00},/*"+",11*/
{0x00,0x10,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*",",12*/
{0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00,0x00,0x00},/*"-",13*/
{0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*".",14*/
{0x00,0x20,0x01,0xC0,0x06,0x00,0x38,0x00,0x40,0x00,0x00,0x00},/*"/",15*/
{0x1F,0x80,0x20,0x40,0x20,0x40,0x20,0x40,0x1F,0x80,0x00,0x00},/*"0",16*/
{0x00,0x00,0x10,0x40,0x3F,0xC0,0x00,0x40,0x00,0x00,0x00,0x00},/*"1",17*/
{0x18,0xC0,0x21,0x40,0x22,0x40,0x24,0x40,0x18,0x40,0x00,0x00},/*"2",18*/
{0x10,0x80,0x20,0x40,0x24,0x40,0x24,0x40,0x1B,0x80,0x00,0x00},/*"3",19*/
{0x02,0x00,0x0D,0x00,0x11,0x00,0x3F,0xC0,0x01,0x40,0x00,0x00},/*"4",20*/
{0x3C,0x80,0x24,0x40,0x24,0x40,0x24,0x40,0x23,0x80,0x00,0x00},/*"5",21*/
{0x1F,0x80,0x24,0x40,0x24,0x40,0x34,0x40,0x03,0x80,0x00,0x00},/*"6",22*/
{0x30,0x00,0x20,0x00,0x27,0xC0,0x38,0x00,0x20,0x00,0x00,0x00},/*"7",23*/
{0x1B,0x80,0x24,0x40,0x24,0x40,0x24,0x40,0x1B,0x80,0x00,0x00},/*"8",24*/
{0x1C,0x00,0x22,0xC0,0x22,0x40,0x22,0x40,0x1F,0x80,0x00,0x00},/*"9",25*/
{0x00,0x00,0x00,0x00,0x08,0x40,0x00,0x00,0x00,0x00,0x00,0x00},/*":",26*/
{0x00,0x00,0x00,0x00,0x04,0x60,0x00,0x00,0x00,0x00,0x00,0x00},/*";",27*/
{0x00,0x00,0x04,0x00,0x0A,0x00,0x11,0x00,0x20,0x80,0x40,0x40},/*"<",28*/
{0x09,0x00,0x09,0x00,0x09,0x00,0x09,0x00,0x09,0x00,0x00,0x00},/*"=",29*/
{0x00,0x00,0x40,0x40,0x20,0x80,0x11,0x00,0x0A,0x00,0x04,0x00},/*">",30*/
{0x18,0x00,0x20,0x00,0x23,0x40,0x24,0x00,0x18,0x00,0x00,0x00},/*"?",31*/
{0x1F,0x80,0x20,0x40,0x27,0x40,0x29,0x40,0x1F,0x40,0x00,0x00},/*"@",32*/
{0x00,0x40,0x07,0xC0,0x39,0x00,0x0F,0x00,0x01,0xC0,0x00,0x40},/*"A",33*/
{0x20,0x40,0x3F,0xC0,0x24,0x40,0x24,0x40,0x1B,0x80,0x00,0x00},/*"B",34*/
{0x1F,0x80,0x20,0x40,0x20,0x40,0x20,0x40,0x30,0x80,0x00,0x00},/*"C",35*/
{0x20,0x40,0x3F,0xC0,0x20,0x40,0x20,0x40,0x1F,0x80,0x00,0x00},/*"D",36*/
{0x20,0x40,0x3F,0xC0,0x24,0x40,0x2E,0x40,0x30,0xC0,0x00,0x00},/*"E",37*/
{0x20,0x40,0x3F,0xC0,0x24,0x40,0x2E,0x00,0x30,0x00,0x00,0x00},/*"F",38*/
{0x0F,0x00,0x10,0x80,0x20,0x40,0x22,0x40,0x33,0x80,0x02,0x00},/*"G",39*/
{0x20,0x40,0x3F,0xC0,0x04,0x00,0x04,0x00,0x3F,0xC0,0x20,0x40},/*"H",40*/
{0x20,0x40,0x20,0x40,0x3F,0xC0,0x20,0x40,0x20,0x40,0x00,0x00},/*"I",41*/
{0x00,0x60,0x20,0x20,0x20,0x20,0x3F,0xC0,0x20,0x00,0x20,0x00},/*"J",42*/
{0x20,0x40,0x3F,0xC0,0x24,0x40,0x0B,0x00,0x30,0xC0,0x20,0x40},/*"K",43*/
{0x20,0x40,0x3F,0xC0,0x20,0x40,0x00,0x40,0x00,0x40,0x00,0xC0},/*"L",44*/
{0x3F,0xC0,0x3C,0x00,0x03,0xC0,0x3C,0x00,0x3F,0xC0,0x00,0x00},/*"M",45*/
{0x20,0x40,0x3F,0xC0,0x0C,0x40,0x23,0x00,0x3F,0xC0,0x20,0x00},/*"N",46*/
{0x1F,0x80,0x20,0x40,0x20,0x40,0x20,0x40,0x1F,0x80,0x00,0x00},/*"O",47*/
{0x20,0x40,0x3F,0xC0,0x24,0x40,0x24,0x00,0x18,0x00,0x00,0x00},/*"P",48*/
{0x1F,0x80,0x21,0x40,0x21,0x40,0x20,0xE0,0x1F,0xA0,0x00,0x00},/*"Q",49*/
{0x20,0x40,0x3F,0xC0,0x24,0x40,0x26,0x00,0x19,0xC0,0x00,0x40},/*"R",50*/
{0x18,0xC0,0x24,0x40,0x24,0x40,0x22,0x40,0x31,0x80,0x00,0x00},/*"S",51*/
{0x30,0x00,0x20,0x40,0x3F,0xC0,0x20,0x40,0x30,0x00,0x00,0x00},/*"T",52*/
{0x20,0x00,0x3F,0x80,0x00,0x40,0x00,0x40,0x3F,0x80,0x20,0x00},/*"U",53*/
{0x20,0x00,0x3E,0x00,0x01,0xC0,0x07,0x00,0x38,0x00,0x20,0x00},/*"V",54*/
{0x38,0x00,0x07,0xC0,0x3C,0x00,0x07,0xC0,0x38,0x00,0x00,0x00},/*"W",55*/
{0x20,0x40,0x39,0xC0,0x06,0x00,0x39,0xC0,0x20,0x40,0x00,0x00},/*"X",56*/
{0x20,0x00,0x38,0x40,0x07,0xC0,0x38,0x40,0x20,0x00,0x00,0x00},/*"Y",57*/
{0x30,0x40,0x21,0xC0,0x26,0x40,0x38,0x40,0x20,0xC0,0x00,0x00},/*"Z",58*/
{0x00,0x00,0x00,0x00,0x7F,0xE0,0x40,0x20,0x40,0x20,0x00,0x00},/*"[",59*/
{0x00,0x00,0x70,0x00,0x0C,0x00,0x03,0x80,0x00,0x40,0x00,0x00},/*"\",60*/
{0x00,0x00,0x40,0x20,0x40,0x20,0x7F,0xE0,0x00,0x00,0x00,0x00},/*"]",61*/
{0x00,0x00,0x20,0x00,0x40,0x00,0x20,0x00,0x00,0x00,0x00,0x00},/*"^",62*/
{0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10},/*"_",63*/
{0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"`",64*/
{0x00,0x00,0x02,0x80,0x05,0x40,0x05,0x40,0x03,0xC0,0x00,0x40},/*"a",65*/
{0x20,0x00,0x3F,0xC0,0x04,0x40,0x04,0x40,0x03,0x80,0x00,0x00},/*"b",66*/
{0x00,0x00,0x03,0x80,0x04,0x40,0x04,0x40,0x06,0x40,0x00,0x00},/*"c",67*/
{0x00,0x00,0x03,0x80,0x04,0x40,0x24,0x40,0x3F,0xC0,0x00,0x40},/*"d",68*/
{0x00,0x00,0x03,0x80,0x05,0x40,0x05,0x40,0x03,0x40,0x00,0x00},/*"e",69*/
{0x00,0x00,0x04,0x40,0x1F,0xC0,0x24,0x40,0x24,0x40,0x20,0x00},/*"f",70*/
{0x00,0x00,0x02,0xE0,0x05,0x50,0x05,0x50,0x06,0x50,0x04,0x20},/*"g",71*/
{0x20,0x40,0x3F,0xC0,0x04,0x40,0x04,0x00,0x03,0xC0,0x00,0x40},/*"h",72*/
{0x00,0x00,0x04,0x40,0x27,0xC0,0x00,0x40,0x00,0x00,0x00,0x00},/*"i",73*/
{0x00,0x10,0x00,0x10,0x04,0x10,0x27,0xE0,0x00,0x00,0x00,0x00},/*"j",74*/
{0x20,0x40,0x3F,0xC0,0x01,0x40,0x07,0x00,0x04,0xC0,0x04,0x40},/*"k",75*/
{0x20,0x40,0x20,0x40,0x3F,0xC0,0x00,0x40,0x00,0x40,0x00,0x00},/*"l",76*/
{0x07,0xC0,0x04,0x00,0x07,0xC0,0x04,0x00,0x03,0xC0,0x00,0x00},/*"m",77*/
{0x04,0x40,0x07,0xC0,0x04,0x40,0x04,0x00,0x03,0xC0,0x00,0x40},/*"n",78*/
{0x00,0x00,0x03,0x80,0x04,0x40,0x04,0x40,0x03,0x80,0x00,0x00},/*"o",79*/
{0x04,0x10,0x07,0xF0,0x04,0x50,0x04,0x40,0x03,0x80,0x00,0x00},/*"p",80*/
{0x00,0x00,0x03,0x80,0x04,0x40,0x04,0x50,0x07,0xF0,0x00,0x10},/*"q",81*/
{0x04,0x40,0x07,0xC0,0x02,0x40,0x04,0x00,0x04,0x00,0x00,0x00},/*"r",82*/
{0x00,0x00,0x06,0x40,0x05,0x40,0x05,0x40,0x04,0xC0,0x00,0x00},/*"s",83*/
{0x00,0x00,0x04,0x00,0x1F,0x80,0x04,0x40,0x00,0x40,0x00,0x00},/*"t",84*/
{0x04,0x00,0x07,0x80,0x00,0x40,0x04,0x40,0x07,0xC0,0x00,0x40},/*"u",85*/
{0x04,0x00,0x07,0x00,0x04,0xC0,0x01,0x80,0x06,0x00,0x04,0x00},/*"v",86*/
{0x06,0x00,0x01,0xC0,0x07,0x00,0x01,0xC0,0x06,0x00,0x00,0x00},/*"w",87*/
{0x04,0x40,0x06,0xC0,0x01,0x00,0x06,0xC0,0x04,0x40,0x00,0x00},/*"x",88*/
{0x04,0x10,0x07,0x10,0x04,0xE0,0x01,0x80,0x06,0x00,0x04,0x00},/*"y",89*/
{0x00,0x00,0x04,0x40,0x05,0xC0,0x06,0x40,0x04,0x40,0x00,0x00},/*"z",90*/
{0x00,0x00,0x00,0x00,0x04,0x00,0x7B,0xE0,0x40,0x20,0x00,0x00},/*"{",91*/
{0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xF0,0x00,0x00,0x00,0x00},/*"|",92*/
{0x00,0x00,0x40,0x20,0x7B,0xE0,0x04,0x00,0x00,0x00,0x00,0x00},/*"}",93*/
{0x40,0x00,0x80,0x00,0x40,0x00,0x20,0x00,0x20,0x00,0x40,0x00},/*"~",94*/
};


//the logo of robomaster
const unsigned char LOGO_BMP[128][8] = {
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1E},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xDE},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD8},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD8},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD8},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xDE},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF6},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x72},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC6},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC6},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC6},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1E},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xDE},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD6},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD6},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD6},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD6},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xDE},
{0x40,0x00,0x00,0x00,0x00,0x00,0x00,0xFE},
{0x60,0x00,0x00,0x00,0x00,0x10,0x00,0x6C},
{0x70,0x00,0x00,0x00,0x00,0x30,0x00,0x00},
{0x78,0x00,0x00,0x00,0x00,0xF0,0x00,0x7C},
{0x7C,0x00,0x00,0x00,0x07,0xF0,0x00,0xFE},
{0x7E,0x00,0x00,0x00,0x3F,0xF0,0x00,0xC6},
{0x7F,0x00,0x00,0x01,0xFF,0xF0,0x00,0xC6},
{0x7F,0x80,0x00,0x0F,0xFF,0xF0,0x00,0xC6},
{0x7F,0xC0,0x00,0x7F,0xFF,0xF0,0x00,0xFE},
{0x7F,0xE0,0x03,0xFF,0xFF,0xF0,0x00,0x7C},
{0x7F,0xF0,0x3F,0xFF,0xFF,0xF0,0x00,0x02},
{0x7F,0xF8,0x3F,0xFF,0xFF,0xF0,0x00,0x06},
{0x7F,0xFC,0x3F,0xFF,0xFF,0xF0,0x00,0x1E},
{0x7F,0xFE,0x3F,0xFF,0xFF,0xF0,0x00,0xBC},
{0x7F,0xFF,0x3F,0xFF,0xFF,0xF0,0x00,0xE0},
{0x7F,0xFF,0xBF,0xFF,0xFF,0x80,0x00,0xF8},
{0x7F,0xFF,0xFF,0xFF,0xFC,0x00,0x00,0x3E},
{0x7F,0xFF,0xFF,0xFF,0xE0,0x00,0x00,0x0E},
{0x7F,0xFF,0xFF,0xFF,0x00,0x00,0x00,0xB8},
{0x7F,0xFF,0xFF,0xF8,0x00,0x00,0x00,0xE0},
{0x7F,0xFF,0xFF,0xF0,0x00,0x00,0x00,0xFE},
{0x7F,0xFF,0xFF,0xF0,0x00,0x00,0x00,0x1E},
{0x7F,0xFF,0xFF,0xF0,0x00,0x00,0x00,0x02},
{0x7F,0xFF,0xFF,0xF0,0x00,0x00,0x00,0x00},
{0x7F,0xEF,0xFF,0xF0,0x02,0x00,0x00,0x06},
{0x7F,0xE7,0xFF,0xF0,0x02,0x00,0x00,0x0E},
{0x7F,0xE3,0xFF,0xF0,0x02,0x00,0x00,0x1C},
{0x7F,0xE1,0xFF,0xF8,0x03,0x00,0x00,0xBA},
{0x7F,0xE0,0xFF,0xFC,0x03,0x00,0x00,0xF6},
{0x7F,0xE0,0x7F,0xFE,0x03,0x80,0x00,0xE6},
{0x7F,0xE0,0x3F,0xFF,0x03,0x80,0x00,0xF6},
{0x7F,0xE0,0x3F,0xFF,0x83,0xC0,0x00,0x3E},
{0x7F,0xE0,0x3F,0xFF,0xC3,0xC0,0x00,0x0E},
{0x7F,0xE0,0x3F,0xFF,0xE3,0xE0,0x00,0x02},
{0x7F,0xE0,0x3F,0xFF,0xF3,0xE0,0x00,0x00},
{0x7F,0xE0,0x3F,0xFF,0xFB,0xF0,0x00,0x02},
{0x7F,0xE0,0x3F,0xFF,0xFF,0xF0,0x00,0x66},
{0x7F,0xE0,0x3F,0xFF,0xFF,0xF8,0x00,0xF6},
{0x7F,0xE0,0x3F,0xFF,0xFF,0xF8,0x00,0xD6},
{0x7F,0xE0,0x3F,0xFF,0xFF,0xFC,0x00,0xD6},
{0x7F,0xF0,0x7F,0xFF,0xFF,0xFC,0x00,0xD6},
{0x7F,0xF8,0xFF,0xF7,0xFF,0xFE,0x00,0xD6},
{0x7F,0xFF,0xFF,0xF3,0xFF,0xFE,0x00,0xDE},
{0x3F,0xFF,0xFF,0xE1,0xFF,0xFF,0x00,0x8C},
{0x3F,0xFF,0xFF,0xE0,0xFF,0xCF,0x00,0x40},
{0x1F,0xFF,0xFF,0xC0,0x7F,0xC7,0x80,0xC0},
{0x1F,0xFF,0xFF,0xC0,0x3F,0xC3,0x80,0xC0},
{0x0F,0xFF,0xFF,0x80,0x1F,0xC1,0xC0,0xFE},
{0x07,0xFF,0xFF,0x00,0x0F,0xC0,0xC0,0xFE},
{0x03,0xFF,0xFE,0x00,0x07,0xC0,0x60,0xC0},
{0x01,0xFF,0xFC,0x00,0x03,0xC0,0x20,0xC0},
{0x00,0x7F,0xF0,0x00,0x01,0xC0,0x00,0x86},
{0x00,0x0F,0x80,0x00,0x00,0xC0,0x00,0x16},
{0x00,0x00,0x00,0x00,0x00,0x40,0x00,0xD6},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD6},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD6},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD6},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD6},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD0},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1E},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xDE},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD8},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD8},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD8},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xDE},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xDE},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF6},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x72},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x62},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF6},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF6},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD6},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD6},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD6},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xDE},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x8C},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
};

#endif

 usart串口相关

        main函数中添加串口重定向,方便串口打印,对应库记得加上。

        定义回调函数 --- HAL_UART_RxCpltCallback --- 用于在使用 HAL 库进行串口接收时处理接收完成事件。(连接蓝牙收发数据相关)

 28BYJ-48 电机相关

        定义电机半步驱动序列

        定义回调函数 --- HAL_TIM_PeriodElapsedCallback --- 处理定时器的周期性中断事件。(驱动电机旋转相关)

 主函数内部实现

        main() 函数里启用中断模式来接收 UART 数据,通过串口打印调试信息(printf),添加 oled 初始化函数,启动定时器的中断功能。

 

        while()循环,在 oled 上显示信息,串口发送对应角度信息。如下图示例:

        结束后重新启用串口接收中断。

4. 控制电机(方法扩展)

        我们前面介绍了直接计算步长来实现对应角度旋转,现在以 PWM 频率为节拍,即用 PWM 方式来计时。28BYJ-48 是一种步进电机,而不是直流电机,不能直接用 PWM 占空比来控制转速或转向,需要控制四个线圈的步进顺序。

        首先,对应 CubeMAX 引脚相关都不需要改动,需要改动的配置是 定时器2 ,我们将定时器2通道1设置为PWM输出,然后生成代码文件(这里注意,代码一定写在注释里,不然重新生成会覆盖掉)。

        代码部分,前面串口重定向和串口回调函数都不需要变动,主要修改处为 中断回调函数。相关代码如下,我这里还添加了电机速度调控函数。

变量定义:

  • volatile uint8_t current_step = 0;:当前步序号,使用volatile关键字确保在中断中也能正确访问和修改。
  • volatile int8_t direction = 1;:电机旋转方向,1表示正转,-1表示反转。
  • volatile int32_t steps_remaining = 0;:剩余步数。
  • volatile uint8_t continuous_mode = 0;:连续旋转模式标志,1表示持续旋转,0表示停止。
  • uint32_t current_speed = 100;:当前速度,单位为步/秒。
  • volatile uint32_t steps_divider = 1;:步进分频系数,用于调整实际速度。
  • volatile uint32_t step_counter = 0;:步进计数器,用于计算何时执行下一步。 

速度设置函数 Set_Stepper_Speed:

  • 接收一个速度参数speed_steps,表示每秒的步数。
  • 限制速度在10到1000步/秒之间。
  • 根据速度计算新的分频系数steps_divider,以调整定时器中断频率,从而控制步进电机的速度。
  • 更新当前速度并在OLED显示屏上显示。

中断回调 HAL_TIM_PeriodElapsedCallback:

  • 当定时器(TIM2)周期结束时调用。
  • 增加步进计数器step_counter。
  • 如果计数器达到分频系数steps_divider且剩余步数大于0或处于连续旋转模式,则执行步进操作:
    • 清除所有线圈(GPIO引脚)的输出。
    • 根据当前步序号current_step和旋转方向direction设置新的线圈状态。
    • 更新当前步序号。
    • 如果不是连续旋转模式,则减少剩余步数。
    • 如果剩余步数为0且不是连续旋转模式,则停止PWM输出并清除所有线圈状态。

         main 函数中定义相关控制,我们在初始化这段加入 pwm 启动,同时定义电机初始转速为 400步/s 。

然后在 while 循环中加入控制代码:

命令处理:

  • 启动/停止:如果接收到 'm',电机将以连续模式启动并顺时针旋转;如果接收到 'n',电机将停止。
  • 加减速:如果接收到 'j',电机将加速;如果接收到 'k',电机将减速。加减速是通过改变current_speed变量实现的,然后调用Set_Stepper_Speed(current_speed);来设置新的速度。
  • 角度控制:如果接收到 'a' 到 'd' 或 'x' 到 'w' 中的任何字符,电机将旋转到指定的角度。这些字符分别代表不同的角度(例如,'a' 代表90度,'b' 代表180度,'x' 代表-90度等)。角度转换为步数,然后电机根据计算出的步数和方向旋转。
  while (1)
  {
    OLED_show_string(1,1,"28BYJ-48: ");
		// OLED_show_string(2,1,"Angle: ");
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		
		if(rflag == 1) {
    rflag = 0;

    // 持续旋转命令
    if(rdata == 'm') {
        continuous_mode = 1;
        direction = 1;
        HAL_TIM_PWM_Start_IT(&htim2, TIM_CHANNEL_1);
        printf("START\n");
        OLED_show_string(3,1,"START ");
    }
    // 停止命令
    else if(rdata == 'n') {
        continuous_mode = 0;
        steps_remaining = 0;
        HAL_TIM_PWM_Stop_IT(&htim2, TIM_CHANNEL_1);
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_12|GPIO_PIN_13, GPIO_PIN_RESET);
        printf("STOP\n");
        OLED_show_string(3,1,"STOP ");
    }
		
		// 加减速
    else if(rdata == 'j' || rdata == 'k') 
	  {
			 // 加速
			 if(rdata == 'j'){
				 current_speed += SPEED_STEP;
			 }
			 // 减速
			 else{
					 if (current_speed > SPEED_STEP) {
							current_speed -= SPEED_STEP;
					} else {
							current_speed = 10;  // 不能低于10
					}
			 }
			 Set_Stepper_Speed(current_speed);
	  }
		
    // 角度控制
    else {
        int16_t angle = 0;
        switch(rdata) {
            case 'a': angle = 90; break;
            case 'b': angle = 180; break;
            case 'c': angle = 270; break;
            case 'd': angle = 360; break;
            case 'x': angle = -90; break;
            case 'y': angle = -180; break;
            case 'z': angle = -270; break;
            case 'w': angle = -360; break;
        }
        
        if(angle != 0) {
            steps_remaining = abs(angle) * 4096 / 360;
            direction = (angle > 0) ? 1 : -1;
            HAL_TIM_PWM_Start_IT(&htim2, TIM_CHANNEL_1);
            
            // 显示角度
            uint8_t angle_str[5];
            snprintf((char*)angle_str, sizeof(angle_str), "%4d", angle);
            OLED_show_string(3,7, angle_str);
            printf("%d\n", angle);
        }
			}
			HAL_UART_Receive_IT(&huart1, &rdata, 1);
		}
	}

二、AndroidStudio 蓝牙开发

        使用 Android Studio 编写一个蓝牙 app ,主要语言为 Java,实现与 stm32 的数据收发。

        

Bluetooth相关

        权限问题 --- AndroidManifest.xml

        首先建立 Bluetooth 包,专门存放蓝牙相关的函数。

 BluetoothConstants.java
  • 定义 BLE 服务和特性的 UUID 常量(如服务 UUID、TX/RX 特性 UUID)。
package com.example.myapplication.Bluetooth;

import java.util.UUID;

public class BluetoothConstants {
    public static final UUID SERVICE_UUID = UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb");
    public static final UUID TX_CHARACTERISTIC_UUID = UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb");
    public static final UUID RX_CHARACTERISTIC_UUID = UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb");
}
DeviceListAdapter.java
  • getView() 自定义列表项视图,显示设备名称和 MAC 地址。 
package com.example.myapplication.Bluetooth;

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import androidx.annotation.NonNull;
import java.util.ArrayList;

public class DeviceListAdapter extends ArrayAdapter<BluetoothDevice> {
    private final int resourceLayout;

    public DeviceListAdapter(Context context, int resource, ArrayList<BluetoothDevice> devices) {
        super(context, resource, devices);
        this.resourceLayout = resource;
    }

    @SuppressLint("MissingPermission")
    @NonNull
    @Override
    public View getView(int position, View convertView, @NonNull ViewGroup parent) {
        if (convertView == null) {
            convertView = LayoutInflater.from(getContext()).inflate(resourceLayout, parent, false);
        }

        BluetoothDevice device = getItem(position);
        TextView deviceName = convertView.findViewById(android.R.id.text1);
        TextView deviceAddress = convertView.findViewById(android.R.id.text2);

        if (device != null) {
            deviceName.setText(device.getName());
            deviceAddress.setText(device.getAddress());
        }
        return convertView;
    }
}
BluetoothManager.java  
  • startDiscovery() 启动蓝牙设备扫描。  
  • registerReceivers() / unregisterReceivers() 注册/注销广播接收器,监听设备发现事件。
  • requestPermissions() 请求用户启用蓝牙功能。  
  • notifyConnectionStatus() / notifyDataReceived() 通知监听器连接状态或数据接收事件。 
package com.example.myapplication.Bluetooth;

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;

public class BluetoothManager {
    private final BluetoothAdapter bluetoothAdapter;
    private final Context context;
    private BluetoothStatusListener statusListener;

    private final BroadcastReceiver discoveryReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (BluetoothDevice.ACTION_FOUND.equals(intent.getAction())) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                if (statusListener != null) {
                    statusListener.onDeviceDiscovered(device);
                }
            }
        }
    };

    public BluetoothManager(Context context) {
        this.context = context;
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    }

    public void setBluetoothStatusListener(BluetoothStatusListener listener) {
        this.statusListener = listener;
    }

    @SuppressLint("MissingPermission")
    public void startDiscovery() {
        bluetoothAdapter.startDiscovery();
    }

    public void registerReceivers() {
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        context.registerReceiver(discoveryReceiver, filter);
    }

    public void unregisterReceivers() {
        context.unregisterReceiver(discoveryReceiver);
    }

    public boolean checkPermissions() {
        return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
    }

    @SuppressLint("MissingPermission")
    public void requestPermissions() {
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        context.startActivity(enableBtIntent);
    }

    public void notifyConnectionStatus(String status) {
        if (statusListener != null) statusListener.onConnectionStatusChanged(status);
    }

    public void notifyDataReceived(String data) {
        if (statusListener != null) statusListener.onDataReceived(data);
    }

    public void notifyError(String errorMessage) {
        if (statusListener != null) statusListener.onError(errorMessage);
    }

    public interface BluetoothStatusListener {
        void onDeviceDiscovered(BluetoothDevice device);
        void onConnectionStatusChanged(String status);
        void onDataReceived(String data);
        void onError(String errorMessage);
    }
}
 BLEConnection.java
  • connect(BluetoothDevice device) 连接指定的 BLE 设备,并初始化 GATT 通信。  
  • sendData(String data) 通过 RX 特性向 BLE 设备发送数据。  
  • setOnDataReceivedListener() 设置监听器,用于接收从 BLE 设备返回的数据。  
  • setOnConnectionStateChangeListener() 设置监听器,处理连接成功或断开事件。  
  • close() 关闭 GATT 连接并释放资源。  
  • gattCallback(内部类)处理连接状态变化(如连接成功、断开)、服务发现、特性值变化(如接收数据)等回调。
package com.example.myapplication.Bluetooth;

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.util.Log;

public class BLEConnection {
    private static final String TAG = "BLEConnection";

    private final Context context;
    private BluetoothGatt bluetoothGatt;
    private BluetoothGattCharacteristic txCharacteristic;
    private BluetoothGattCharacteristic rxCharacteristic;
    private OnDataReceivedListener dataReceivedListener;
    private OnConnectionStateChangeListener connectionStateChangeListener;

    public BLEConnection(Context context) {
        this.context = context;
    }

    @SuppressLint("MissingPermission")
    public void connect(BluetoothDevice device) {
        Log.d(TAG, "正在连接 BLE 设备: " + device.getName());
        bluetoothGatt = device.connectGatt(context, false, gattCallback);
    }

    private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
        @SuppressLint("MissingPermission")
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                Log.d(TAG, "BLE 设备已连接");
                bluetoothGatt.discoverServices();

                if (connectionStateChangeListener != null) {
                    connectionStateChangeListener.onConnected();
                }
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                Log.d(TAG, "BLE 设备已断开连接");

                if (connectionStateChangeListener != null) {
                    connectionStateChangeListener.onDisconnected();
                }
                close();
            }
        }

        @SuppressLint("MissingPermission")
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                BluetoothGattService service = bluetoothGatt.getService(BluetoothConstants.SERVICE_UUID);
                if (service != null) {
                    txCharacteristic = service.getCharacteristic(BluetoothConstants.TX_CHARACTERISTIC_UUID);
                    rxCharacteristic = service.getCharacteristic(BluetoothConstants.RX_CHARACTERISTIC_UUID);
                    Log.d(TAG, "发现 BLE 透传服务!");

                    bluetoothGatt.setCharacteristicNotification(txCharacteristic, true);
                }
            }
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            if (characteristic.getUuid().equals(BluetoothConstants.TX_CHARACTERISTIC_UUID)) {
                String receivedData = new String(characteristic.getValue());
                Log.d(TAG, "收到 BLE 数据: " + receivedData);

                if (dataReceivedListener != null) {
                    dataReceivedListener.onDataReceived(receivedData);
                }
            }
        }
    };

    @SuppressLint("MissingPermission")
    public void sendData(String data) {
        if (rxCharacteristic != null) {
            rxCharacteristic.setValue(data.getBytes());
            bluetoothGatt.writeCharacteristic(rxCharacteristic);
            Log.d(TAG, "发送数据: " + data);
        }
    }

    public void setOnDataReceivedListener(OnDataReceivedListener listener) {
        this.dataReceivedListener = listener;
    }

    public void setOnConnectionStateChangeListener(OnConnectionStateChangeListener listener) {
        this.connectionStateChangeListener = listener;
    }

    @SuppressLint("MissingPermission")
    public void close() {
        if (bluetoothGatt != null) {
            bluetoothGatt.close();
            bluetoothGatt = null;
        }
    }

    public interface OnDataReceivedListener {
        void onDataReceived(String data);
    }

    public interface OnConnectionStateChangeListener {
        void onConnected();
        void onDisconnected();
    }
}
 BluetoothConnectionActivity.java
  • requestBluetoothPermissions() 动态请求 Android 12+ 的蓝牙扫描和连接权限。  
  • initializeUI() 初始化界面组件(列表、按钮、状态栏),并绑定点击事件。  
  • setupBluetoothManager() 配置蓝牙管理器,绑定设备发现、连接状态、数据接收等事件的监听器。  
  • startBluetoothDiscovery() 启动蓝牙设备扫描,更新界面状态。  
  • updateDeviceList(BluetoothDevice device) 将新发现的设备添加到列表并更新 UI。
  • connectToDevice(BluetoothDevice device) 连接选中的设备,成功后跳转到数据传输界面。
package com.example.myapplication.Bluetooth;

import static android.content.ContentValues.TAG;

import android.Manifest;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothProfile;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.example.myapplication.R;
import java.util.ArrayList;

public class BluetoothConnectionActivity extends AppCompatActivity {
    private ListView devicesListView;
    private TextView statusText;
    private Button scanButton;
    private BluetoothManager bluetoothManager;
    private ArrayAdapter<String> devicesAdapter;
    private ArrayList<BluetoothDevice> discoveredDevices = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bluetooth);

        initializeUI();
        setupBluetoothManager();
    }

    // **动态请求 Android 12+ 的蓝牙权限**
    private void requestBluetoothPermissions() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED ||
                    ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {

                ActivityCompat.requestPermissions(this, new String[]{
                        Manifest.permission.BLUETOOTH_CONNECT,
                        Manifest.permission.BLUETOOTH_SCAN
                }, 1);
            }
        }
    }

    private void initializeUI() {
        devicesListView = findViewById(R.id.devicesListView);
        statusText = findViewById(R.id.statusText);
        scanButton = findViewById(R.id.scanButton);

        devicesAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new ArrayList<>());
        devicesListView.setAdapter(devicesAdapter);

        scanButton.setOnClickListener(v -> startBluetoothDiscovery());

        devicesListView.setOnItemClickListener((parent, view, position, id) -> {
            BluetoothDevice selectedDevice = discoveredDevices.get(position);
            connectToDevice(selectedDevice);
        });
    }

    private void setupBluetoothManager() {
        bluetoothManager = new BluetoothManager(this);
        bluetoothManager.setBluetoothStatusListener(new BluetoothManager.BluetoothStatusListener() {
            @Override
            public void onDeviceDiscovered(BluetoothDevice device) {
                runOnUiThread(() -> updateDeviceList(device));
            }

            @Override
            public void onConnectionStatusChanged(String status) {
                runOnUiThread(() -> statusText.setText(status));
            }

            @Override
            public void onDataReceived(String data) {
                runOnUiThread(() -> showToast("收到数据: " + data));
            }

            @Override
            public void onError(String errorMessage) {
                runOnUiThread(() -> showToast("错误: " + errorMessage));
            }
        });
    }

    private void startBluetoothDiscovery() {
        if (bluetoothManager.checkPermissions()) {
            devicesAdapter.clear();
            discoveredDevices.clear();
            statusText.setText("扫描中...");
            bluetoothManager.startDiscovery();
        } else {
            bluetoothManager.requestPermissions();
        }
    }

    private void updateDeviceList(BluetoothDevice device) {
        if (!containsDevice(device)) {
            discoveredDevices.add(device);
            @SuppressLint("MissingPermission")
            String deviceInfo = device.getName() + "\n" + device.getAddress();
            devicesAdapter.add(deviceInfo);
            devicesAdapter.notifyDataSetChanged();
        }
    }

    private boolean containsDevice(BluetoothDevice newDevice) {
        for (BluetoothDevice existingDevice : discoveredDevices) {
            if (existingDevice.getAddress().equals(newDevice.getAddress())) {
                return true;
            }
        }
        return false;
    }

    @SuppressLint("MissingPermission")
    private void connectToDevice(BluetoothDevice device) {
        statusText.setText("连接到:" + device.getName());

        BLEConnection bleConnection = new BLEConnection(this);
        bleConnection.setOnConnectionStateChangeListener(new BLEConnection.OnConnectionStateChangeListener() {
            @Override
            public void onConnected() {
                runOnUiThread(() -> {
                    Intent intent = new Intent(BluetoothConnectionActivity.this, BluetoothDataTransferActivity.class);
                    intent.putExtra("device_name", device.getName());
                    intent.putExtra("device_address", device.getAddress());
                    startActivity(intent);
                });
            }

            @Override
            public void onDisconnected() {
                runOnUiThread(() -> statusText.setText("设备已断开"));
            }
        });

        bleConnection.connect(device);
    }



    private void showToast(String message) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onResume() {
        super.onResume();
        bluetoothManager.registerReceivers();
    }

    @Override
    protected void onPause() {
        super.onPause();
        bluetoothManager.unregisterReceivers();
    }
}
BluetoothDataTransferActivity.java
  • setupBLEListener() 绑定 BLE 数据接收监听器,将收到的数据通过 MQTT 发送到云端。  发送按钮点击事件 将用户输入的数据通过 BLE 发送到设备。  (云端详情请看第四部分MQTT相关)
  • onDestroy() 关闭 BLE 连接,释放资源。
package com.example.myapplication.Bluetooth;

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.example.myapplication.MQTT.MqttHelper;
import com.example.myapplication.R;


public class BluetoothDataTransferActivity extends AppCompatActivity {
    private BLEConnection bleConnection;
    private EditText sendDataEdit;
    private Button sendButton;
    private TextView receivedDataText;
    private TextView deviceInfoText;

    private String deviceName;
    private String deviceAddress;

    // MQTT 连接对象
    private MqttHelper mqttHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bluetooth_data_transfer);

        sendDataEdit = findViewById(R.id.sendDataEdit);
        sendButton = findViewById(R.id.sendButton);
        receivedDataText = findViewById(R.id.receivedDataText);
        deviceInfoText = findViewById(R.id.deviceInfoText);

        // **获取设备信息**
        deviceName = getIntent().getStringExtra("device_name");
        deviceAddress = getIntent().getStringExtra("device_address");

        if (deviceAddress == null || deviceAddress.isEmpty()) {
            showToast("设备地址无效");
            finish();
            return;
        }

        deviceInfoText.setText("已连接到: " + deviceName + "\n" + deviceAddress);

        // **创建 BLE 连接**
        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(deviceAddress);
        bleConnection = new BLEConnection(this);
        bleConnection.connect(device);

        // **启动 MQTT 连接**
        mqttHelper = new MqttHelper(this);
        mqttHelper.connect();

        // **设置监听器,接收数据**
        setupBLEListener();

        // **点击按钮发送数据**
        sendButton.setOnClickListener(v -> {
            String data = sendDataEdit.getText().toString();
            if (!data.isEmpty()) {
                bleConnection.sendData(data);
            } else {
                showToast("请输入要发送的数据");
            }
        });
    }

    // **监听 BLE 设备的数据**
    private void setupBLEListener() {
        bleConnection.setOnDataReceivedListener(data -> runOnUiThread(() -> {
            receivedDataText.setText("收到数据: " + data);
            showToast("收到数据: " + data);

            // 将收到数据通过mqtt发送到云端 (同步)
            if (mqttHelper.isConnected()) {
                mqttHelper.publishMessage("mqtt", data); // 发送到 MQTT 主题 "mqtt"
            } else {
                showToast("MQTT 未连接,无法同步数据");
            }
        }));

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (bleConnection != null) {
            bleConnection.close();
        }
    }

    private void showToast(String message) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
    }
}

三、MQTT连接阿里云配置

选择试用云服务器ESC

 

重置完成后,点击实例进入服务器详情页面,远程连接登录进入终端。

 打开阿里云控制台页面,在安全组中开放 8888、1883、8083、18083、8084、443端口。

 右侧管理规则,进入后手动添加开放对应端口。

1. 终端配置(主要演示终端操作)

EMQX配置,下载对应centos版本的emqx

wget https://www.emqx.com/en/downloads/broker/3.1.2/emqx-centos7-v3.1.2.zip

解压

unzip emqx-centos7-v3.1.2.zip

 在emqx目录下启动

./bin/emqx console

./bin/emqx start

打开emqx,公网ip+18083。(你自己申请的云平台公网ip)

118.178.234.179:18083

若出现连接不上和以下错误可能是版本不适配问题,重新下载emqx

 还是无法连接,也可能是安全组没有配置成功,手动检查对应配置

 检查本地是否开放:

        若服务器本地访问 127.0.0.1:18083 返回了 HTTP 200,说明 EMQX Dashboard 运行正常,问题应该出在 远程访问(本地电脑无法访问服务器的 18083 端口)。

curl -I http://127.0.0.1:18083

 检查云端地址能否访问:

        长时间没有反应说明端口开放有问题,查询防火墙开放端口。

curl -I http://118.178.234.179:18083

sudo firewall-cmd --list-ports

这里可以看出18083端口并没有被放通(博主也不知道为什么,安全组明明已经配置了)

手动配置18083号端口,配置完成后重新加载

sudo firewall-cmd --add-port=18083/tcp --permanent

sudo firewall-cmd --reload

 登录,初始账号为:admin 密码:public

在打开的网页EMQX中进行Websocket连接

订阅主题(该主题为 mqtt 发送信息的主题名)

本机电脑MQTT创建连接

 添加订阅主题(该主题为 emqx 发送信息的主题名)

 2. 宝塔面板(较简略,请主要看终端)

 安装宝塔面板(Linux-Centos)

url=https://download.bt.cn/install/install_panel.sh;if [ -f /usr/bin/curl ];then curl -sSO $url;else wget -O install_panel.sh $url;fi;bash install_panel.sh ed8484bec

 如下,版本安装完成

 打开阿里云控制台页面,在安全组中开放 8888(宝塔面板)、1883(TCP、UDP)、8083、18083、8084、443(TCP)端口。

 右侧管理规则,进入后手动添加开放对应端口。

复制连接打开宝塔面板,(链接就是刚刚下载完成的信息)

输入账号密码登录。 

 点击安全,添加刚刚对应的端口号。

 安装nginx

四、Android与阿里云通信

        在已经完成的蓝牙app中继续添加功能,即MQTT通信功能,完成与阿里云emqx的数据通信。

MQTT相关

        创建MQTT包,存放对应 mqtt 功能的函数。(只看MqttHelper即可,MqttActivity是我用于测试的函数)

        MqttHelper用于实现 MQTT 协议的客户端功能,包括连接代理服务器、订阅主题、发布消息,并通过回调机制处理消息接收和连接状态。

  • 构造函数 MqttHelper(Context context) 初始化 MQTT 客户端,配置连接参数(服务器地址、用户名、密码、超时等),设置回调监听(连接丢失、消息送达完成、消息接收)。
  • init() 创建 MqttClient 实例,使用内存持久化(MemoryPersistence),配置连接选项(清理会话、心跳间隔等),定义回调逻辑,初始化 Handler。
  • connect() 在新线程中发起异步 MQTT 连接。连接成功时通过 Handler 发送成功消息(显示 Toast 并自动订阅主题),连接失败时发送失败提示。
  • isConnected() 检查客户端当前是否已连接到 MQTT 服务器。
  • publishMessage(String topic, String message) 向指定主题发布消息,消息内容需为字符串。若未连接则记录错误日志。
  • disconnect() 断开 MQTT 连接,释放资源。
package com.example.myapplication.MQTT;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;

import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class MqttHelper {
    private static final String TAG = "MQTT";
    private static final String HOST = "tcp://118.178.234.179:1883";
    private static final String USERNAME = "aliyun";
    private static final String PASSWORD = "123456";
    private static final String CLIENT_ID = "mqttx_0bdbad30";

    private MqttClient client;
    private MqttConnectOptions options;
    private ScheduledExecutorService scheduler;
    private Context context;
    private Handler handler;

    public MqttHelper(Context context) {
        this.context = context;
        init();
    }

    private void init() {
        try {
            client = new MqttClient(HOST, CLIENT_ID, new MemoryPersistence());
            options = new MqttConnectOptions();
            options.setCleanSession(true);
            options.setUserName(USERNAME);
            options.setPassword(PASSWORD.toCharArray());
            options.setConnectionTimeout(10);
            options.setKeepAliveInterval(20);

            client.setCallback(new MqttCallback() {
                @Override
                public void connectionLost(Throwable cause) {
                    Log.e(TAG, "MQTT 连接丢失", cause);
                }

                @Override
                public void deliveryComplete(IMqttDeliveryToken token) {
                    Log.d(TAG, "消息发送完成: " + token.isComplete());
                }

                @Override
                public void messageArrived(String topic, MqttMessage message) {
                    Log.d(TAG, "MQTT 接收消息: " + topic + " - " + message.toString());
                    Message msg = new Message();
                    msg.what = 3;
                    msg.obj = topic + "---" + message.toString();
                    handler.sendMessage(msg);
                }
            });

            handler = new Handler(msg -> {
                if (msg.what == 30) {
                    Toast.makeText(context, "MQTT 连接失败", Toast.LENGTH_SHORT).show();
                } else if (msg.what == 31) {
                    Toast.makeText(context, "MQTT 连接成功", Toast.LENGTH_SHORT).show();
                    try {
                        client.subscribe("ali_emqx", 1);
                    } catch (MqttException e) {
                        Log.e(TAG, "订阅失败", e);
                    }
                }
                return false;
            });

        } catch (MqttException e) {
            Log.e(TAG, "MQTT 初始化失败", e);
        }
    }

    public void connect() {
        new Thread(() -> {
            try {
                if (!client.isConnected()) {
                    client.connect(options);
                    handler.sendEmptyMessage(31);
                }
            } catch (MqttException e) {
                handler.sendEmptyMessage(30);
            }
        }).start();
    }

    public boolean isConnected() {
        return client != null && client.isConnected();
    }

    public void publishMessage(String topic, String message) {
        if (client == null || !client.isConnected()) {
            Log.e(TAG, "MQTT 未连接,无法发送消息");
            return;
        }
        try {
            MqttMessage mqttMessage = new MqttMessage(message.getBytes());
            client.publish(topic, mqttMessage);
            Log.d(TAG, "消息发送到: " + topic);
        } catch (MqttException e) {
            Log.e(TAG, "消息发送失败", e);
        }
    }

    public void disconnect() {
        try {
            if (client != null) {
                client.disconnect();
            }
        } catch (MqttException e) {
            Log.e(TAG, "MQTT 断开连接失败", e);
        }
    }
}

         在 BluetoothDataTransferActivity.java 中添加相关 MQTT 代码。首先创建一个 MQTT 连接对象。

        启动 MQTT 连接

 将收到的数据利用 MQTT 传入云端,结果显示在 EMQX 上。

五、项目完整代码链接(开源)

【免费】stm32f103c8t6蓝牙通信驱动电机资源-CSDN文库

拓展--基于FreeRTOS操作系统实现电机控制功能

STM32F103C8T6-基于FreeRTOS系统实现步进电机控制-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2336362.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第一个Qt开发的OpenCV程序

OpenCV计算机视觉开发实践&#xff1a;基于Qt C - 商品搜索 - 京东 下载安装Qt&#xff1a;https://download.qt.io/archive/qt/5.14/5.14.2/qt-opensource-windows-x86-5.14.2.exe 下载安装OpenCV&#xff1a;https://opencv.org/releases/ 下载安装CMake&#xff1a;Downl…

TCP 如何在网络 “江湖” 立威建交?

一、特点&#xff1a; &#xff08;一&#xff09;面向连接 在进行数据传输之前&#xff0c;TCP 需要在发送方和接收方之间建立一条逻辑连接。这一过程类似于打电话&#xff0c;双方在通话前需要先拨号建立连接。建立连接的过程通过三次握手来完成&#xff0c;确保通信双方都…

【小白训练日记——2025/4/15】

变化检测常用的性能指标 变化检测&#xff08;Change Detection&#xff09;的性能评估依赖于多种指标&#xff0c;每种指标从不同角度衡量模型的准确性。以下是常用的性能指标及其含义&#xff1a; 1. 混淆矩阵&#xff08;Confusion Matrix&#xff09; 定义&#xff1a;统…

数据结构——二叉树(中)

接上一篇&#xff0c;上一篇主要讲解了关于二叉树的基本知识&#xff0c;也是为了接下来讲解关于堆结构和链式二叉树结构打基础&#xff0c;其实无论是堆结构还是链式二叉树结构&#xff0c;都是二叉树的存储结构&#xff0c;那么今天这一篇主要讲解关于堆结构的实现与应用 堆…

02-MySQL 面试题-mk

文章目录 1.mysql 有哪些存储引擎、区别是什么?1.如何定位慢查询?2.SQL语句执行很慢,如何分析?3.索引概念以及索引底层的数据结构4.什么是聚簇索引什么是非聚簇索引?5.知道什么叫覆盖索引嘛 ?6.索引创建原则有哪些?7.什么情况下索引会失效 ?8.谈一谈你对sql的优化的经验…

#include<bits/stdc++.h>

#include<bits/stdc.h> 是 C 中一个特殊的头文件&#xff0c;其作用如下&#xff1a; 核心作用 ​​包含所有标准库头文件​​ 该头文件会自动引入 C 标准库中的几乎全部头文件&#xff08;如 <iostream>、<vector>、<algorithm> 等&#xff09;&…

在企业级部署中如何优化NVIDIA GPU和容器环境配置:最佳实践与常见误区20250414

在企业级部署中如何优化NVIDIA GPU和容器环境配置&#xff1a;最佳实践与常见误区 引言 随着AI和深度学习技术的迅速发展&#xff0c;企业对GPU加速计算的需求愈加迫切。在此过程中&#xff0c;如何高效地配置宿主机与容器化环境&#xff0c;特别是利用NVIDIA GPU和相关工具&…

Spring Boot 项目三种打印日志的方法详解。Logger,log,logger 解读。

目录 一. 打印日志的常见三种方法&#xff1f; 1.1 手动创建 Logger 对象&#xff08;基于SLF4J API&#xff09; 1.2 使用 Lombok 插件的 Slf4j 注解 1.3 使用 Spring 的 Log 接口&#xff08;使用频率较低&#xff09; 二. 常见的 Logger&#xff0c;logger&#xff0c;…

[react]Next.js之自适应布局和高清屏幕适配解决方案

序言 阅读前首先了解即将要用到的两个包的作用 1.postcss-pxtorem 自动将 CSS 中的 px 单位转换为 rem 单位按照设计稿尺寸直接写 px 值&#xff0c;由插件自动计算 rem 值 2.amfe-flexible 动态设置根元素的 font-size&#xff08;即 1rem 的值&#xff09;根据设备屏幕宽度和…

STM32H503CB升级BootLoader

首先&#xff0c;使用SWD接口&#xff0c;ST-LINK连接电脑和板子。 安装SetupSTM32CubeProgrammer_win64 版本2.19。 以下是接线和软件操作截图。

在Apple Silicon上部署Spark-TTS:四大核心库的技术魔法解析!!!

在Apple Silicon上部署Spark-TTS&#xff1a;四大核心库的技术魔法解析 &#x1f680; &#xff08;M2芯片实测&#xff5c;Python 3.12.9PyTorch 2.6.0全流程解析&#xff09; 一、核心库功能全景图 &#x1f50d; 在Spark-TTS的部署过程中&#xff0c;pip install numpy li…

VMWare 16 PRO 安装 Rocky8 并部署 MySQL8

VMWare 16 PRO 安装 Rocky8 并部署 MySQL8 一.Rocky OS 下载1.官网二.配置 Rocky1.创建新的虚拟机2.稍后安装系统3.选择系统模板4.设置名字和位置5.设置大小6.自定义硬件设置核心、运存和系统镜像7.完成三.启动安装1.上下键直接选择安装2.回车安装3.设置分区(默认即可)和 roo…

cursor如何回退一键回退多个文件的修改

当我们使用 Cursor 写代码时&#xff0c;起初可能操作得很顺利&#xff0c;但某次更改或许会让代码变得面目全非。这时候如果没有使用 Git 该怎么办呢&#xff1f;别担心&#xff0c;Cursor 已经为我们考虑到了。 具体的操作如下&#xff1a; 当我们要取消某次操作时&#xf…

基于RV1126开发板的口罩识别算法开发

1. 口罩识别简介 口罩识别是一种基于深度学习的判断人员有没有戴口罩的分类算法&#xff0c;能广泛的用于安防、生产安全等多种场景。本算法先基于人脸检测和人脸标准化获取的标准人脸&#xff0c;然后输入到口罩识别分类算法进行识别。 本人脸检测算法在数据集表现如下所示&am…

PyCharm显示主菜单和工具栏

显示主菜单 新版 PyCharm 是不显示主菜单的&#xff0c;要想显示主菜单和工具栏&#xff0c;则通过 “视图” → “外观” &#xff0c;勾选 “在单独的工具栏中显示主菜单” 和 “工具栏” 即可。 设置工具栏 此时工具栏里并没有什么工具&#xff0c;因此我们需要自定义工具…

Java工程行业管理软件源码 - 全面的项目管理工具 - 工程项目模块与功能一览

工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离构建工程项目管理系统 项目背景 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性&#xff0c;公司对内部工程管理的提升提…

Redis 高可用集群搭建与优化实践

在分布式系统中,缓存技术用于提升性能和响应速度。 Redis 作为一款高性能的键值存储系统,广泛应用于缓存、消息队列和会话管理等场景。随着业务规模的扩大,单机 Redis 的性能和可用性逐渐无法满足需求。 因此,搭建高可用的 Redis 集群可以解决这一问题。我将详细介绍 Red…

【AI大模型】基于阿里百炼大模型进行调用

目录 一、认识阿里云百炼 模型广场 创建自己的模型 二、AI扩图示例 1、开头服务、设置秘钥 2、选择HTTP方式调用流程 3、创建任务请求示例 4、发送http请求提交任务 5、查看任务进度的流程设计 6、后端查看任务进度代码 三、总结 大家好&#xff0c;我是jstart千语…

【神经网络结构的组成】深入理解 转置卷积与转置卷积核

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;《深度学习理论直觉三十讲》_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 …

MyBatis-plus笔记 (上)

简介 [MyBatis-Plus]&#xff08;简称 MP&#xff09;是一个 [MyBatis]的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 mybatis-plus总结&#xff1a; 注意&#xff1a;mybatis-puls仅局限于单表操作。 自动生成单表的C…