阿源玩电子 发表于 2025-7-10 23:20

【灵动微电子MM32F0121测评】9.ESP32-WEB控制

本帖最后由 阿源玩电子 于 2025-7-12 11:02 编辑

#申请原创# 基于MM32F0121与ESP32的无线Web控制技术实现
1.项目概述
通过ESP32创建WiFi热点,搭建Web服务器,实现手机浏览器远程控制MM32F0121开发板:

[*]手机网页控制板载LED
[*]实时显示板载电位器电压值
[*] 双向串口通信(ESP32 UART2 ↔ MM32F0121 UART2)


2.硬件搭建
ESP32 (UART2)         Mini_F0121-OB (UART2)   
────────────────────────────         
GPIO17 (TX2)    →      RX: PB8                     
GPIO16 (RX2)    ←      TX: PA8                     
                   GND            →      GND                                       

3.无线通信
使用Arduino开发环境,以下为ESP32部分:

[*]WiFi名称:ESP32-Control
[*]WiFi密码:12345678
[*]IP地址:192.168.4.1
[*]使用一颗板载蓝色LED,来显示运行状态
[*]长时间没有收到电压数据,会显示无数据字样
[*]同时能监控连接WIFI设备的数量

#include <WiFi.h>
#include <WebServer.h>

// 热点配置
const char* ap_ssid = "ESP32-Control";
const char* ap_password = "12345678";
const int statusLed = 2;// ESP32 板载 LED 引脚用于状态指示

// 串口配置 (UART2: RX=16, TX=17)
#define SERIAL2_RX 16
#define SERIAL2_TX 17
HardwareSerial SerialPort(2);// 使用 UART2

WebServer server(80);

// 全局变量
String voltageValue = "0.00V";// 存储电压值
unsigned long lastVoltageUpdate = 0;
bool externalLEDState = false;
unsigned long lastBlinkTime = 0;
bool ledState = LOW;

void handleRoot() {
String html = R"rawliteral(
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="UTF-8">
      <title>ESP32 设备控制</title>
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <style>
      body {
          font-family: "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
          max-width: 600px;
          margin: 0 auto;
          padding: 20px;
          background-color: #f5f5f5;
      }
      .card {
          background: white;
          border-radius: 10px;
          box-shadow: 0 4px 8px rgba(0,0,0,0.1);
          padding: 20px;
          margin-bottom: 20px;
      }
      h1 { color: #2c3e50; text-align: center; }
      .btn {
          display: block;
          width: 100%;
          padding: 15px;
          margin: 10px 0;
          background-color: #3498db;
          color: white;
          border: none;
          border-radius: 5px;
          font-size: 18px;
          cursor: pointer;
          transition: background-color 0.3s;
      }
      .btn-on { background-color: #27ae60; }
      .btn-off { background-color: #e74c3c; }
      .btn:hover { opacity: 0.9; }
      .status {
          padding: 15px;
          text-align: center;
          font-size: 20px;
          margin: 15px 0;
          border-radius: 5px;
      }
      .voltage { background-color: #f1c40f; color: #2c3e50; }
      .external-led { background-color: %external_color%; }
      .info {
          text-align: center;
          color: #7f8c8d;
          margin-top: 30px;
          font-size: 14px;
      }
      </style>
      <script>
      function updateVoltage() {
          fetch('/voltage')
            .then(response => response.text())
            .then(data => {
            document.getElementById("voltage-value").innerHTML = data;
            setTimeout(updateVoltage, 2000);
            });
      }
      
      function controlLED(state) {
          fetch('/external/' + state)
            .then(response => response.text())
            .then(data => {
            const statusElem = document.getElementById("external-status");
            statusElem.innerHTML = "状态: " + data.toUpperCase();
            statusElem.className = "status external-led";
            statusElem.style.backgroundColor = state === "on" ? "#27ae60" : "#e74c3c";
            });
      }
      
      window.onload = function() {
          updateVoltage();
      };
      </script>
    </head>
    <body>
      <div class="card">
      <h1>ESP32 设备</h1>
      
      <div class="card">
          <h2>系统状态</h2>
          <div class="status" style="background-color: #3498db; color: white;">
            ESP32(正常运行)
          </div>
      </div>
      
      <div class="card">
          <h2>Mini_F0121-OB</h2>
          <button class="btn btn-on" onclick="controlLED('on')">打开 LED</button>
          <button class="btn btn-off" onclick="controlLED('off')">关闭 LED</button>
          <div id="external-status" class="status external-led">状态: OFF</div>
      </div>
      
      <div class="card">
          <h2>电压监测</h2>
          <div id="voltage-value" class="status voltage">读取中...</div>
      </div>
      </div>
      
      <div class="info">
      IP: 192.168.4.1 | 连接设备: %client_count%
      </div>
    </body>
    </html>
)rawliteral";

// 动态替换内容
html.replace("%external_color%", externalLEDState ? "#27ae60" : "#e74c3c");
html.replace("%client_count%", String(WiFi.softAPgetStationNum()));

server.send(200, "text/html; charset=utf-8", html);
}

void handleExternalLED(String state) {
if (state == "on") {
    SerialPort.println("LED_ON");// 发送控制指令
    externalLEDState = true;
    server.send(200, "text/plain", "on");
    Serial.println("Sent: LED_ON");
} else if (state == "off") {
    SerialPort.println("LED_OFF"); // 发送控制指令
    externalLEDState = false;
    server.send(200, "text/plain", "off");
    Serial.println("Sent: LED_OFF");
}
}

// 获取电压值
void handleVoltage() {
server.send(200, "text/plain", voltageValue);
}

// 处理串口数据
void processSerialData() {
if (SerialPort.available()) {
    String data = SerialPort.readStringUntil('\n');
    data.trim();
   
    // 电压数据格式: "VOLT:3.25"
    if (data.startsWith("VOLT:")) {
      voltageValue = data.substring(5) + "V";
      lastVoltageUpdate = millis();
      Serial.println("Received voltage: " + voltageValue);
    }
    // 外部 LED 状态反馈: "EXT_LED:ON"
    else if (data.startsWith("EXT_LED:")) {
      externalLEDState = (data.substring(8) == "ON");
      Serial.println("External LED: " + String(externalLEDState ? "ON" : "OFF"));
    }
}
}


void blinkStatusLED() {
if (millis() - lastBlinkTime > 1000) {
    lastBlinkTime = millis();
    ledState = !ledState;
    digitalWrite(statusLed, ledState);
}
}

void setup() {
// 初始化串口
Serial.begin(115200);
SerialPort.begin(9600, SERIAL_8N1, SERIAL2_RX, SERIAL2_TX);

// 初始化状态指示灯
pinMode(statusLed, OUTPUT);
digitalWrite(statusLed, LOW);

// 创建热点
WiFi.softAP(ap_ssid, ap_password);
delay(100);

Serial.println("\nAccess Point Created!");
Serial.println("SSID: " + String(ap_ssid));
Serial.println("Password: " + String(ap_password));
Serial.println("IP: " + WiFi.softAPIP().toString());

// 设置路由
server.on("/", handleRoot);
server.on("/external/on", []() { handleExternalLED("on"); });
server.on("/external/off", []() { handleExternalLED("off"); });
server.on("/voltage", handleVoltage);

server.begin();
Serial.println("HTTP server started");

// 初始闪烁
digitalWrite(statusLed, HIGH);
delay(500);
digitalWrite(statusLed, LOW);
}

void loop() {
server.handleClient();// 处理网页请求
processSerialData();    // 处理串口数据
blinkStatusLED();       // 更新LED闪烁状态

// 电压数据超时处理
if (millis() - lastVoltageUpdate > 5000) {
    voltageValue = "无数据";
}
}
4.MM32F0121部分

[*]使用板载的蓝色LED作为运行指示灯
[*]使用板载的绿色LED作为控制的对象
[*]使用RTOS,TASK1为运行指示灯任务,TASK2为电压检测任务
[*]USART2部分配置代码如下
void USART2_Configure(uint32_t Baudrate)
{
    GPIO_InitTypeDefGPIO_InitStruct;
    NVIC_InitTypeDefNVIC_InitStruct;
    USART_InitTypeDef USART_InitStruct;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

    USART_StructInit(&USART_InitStruct);
    USART_InitStruct.USART_BaudRate   = Baudrate;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_InitStruct.USART_StopBits   = USART_StopBits_1;
    USART_InitStruct.USART_Parity   = USART_Parity_No;
    USART_InitStruct.USART_Mode       = USART_Mode_Rx | USART_Mode_Tx;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_Init(USART2, &USART_InitStruct);

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_3);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_4);

    GPIO_StructInit(&GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_8;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
    GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_StructInit(&GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_8;
    GPIO_InitStruct.GPIO_Mode= GPIO_Mode_FLOATING;
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    NVIC_InitStruct.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);

    USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);

    USART_Cmd(USART2, ENABLE);
}
void USART_SendString(USART_TypeDef* USARTx, const char* str) {
    while (*str) {
      USART_SendData(USARTx, (uint8_t)(*str++));
      while (USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET);
    }
}

void USART_Printf(USART_TypeDef* USARTx, const char* fmt, ...) {
    char buffer;
    va_list args;
    va_start(args, fmt);
    vsnprintf(buffer, sizeof(buffer), fmt, args);
    va_end(args);
    USART_SendString(USARTx, buffer);
}

#define MAX_CMD_LEN 32

char receivedCmd;
uint8_t cmdIndex = 0;         

void USART2_IRQHandler(void)
{
    if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
    {
      uint8_t data = USART_ReceiveData(USART2);
      
      if(data == '\r' || data == '\n' || cmdIndex >= MAX_CMD_LEN-1)
      {
            receivedCmd = '\0';
         
            if(strcmp(receivedCmd, "LED_ON") == 0) {
                                                                GPIO_WriteBit(GPIOB,GPIO_Pin_15,0);
                printf("ACK: LED OFF\r\n");
            }
            else if(strcmp(receivedCmd, "LED_OFF") == 0) {
                                                                GPIO_WriteBit(GPIOB,GPIO_Pin_15,1);
                printf("ACK: LED ON\r\n");
            }
            cmdIndex = 0;
      }
      else
      {
            receivedCmd = data;
      }
      
      while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
    }
}

[*]ADC部分循环采集,循环上报
#include "adc_dsp.h"

uint32_t ADC_InterruptFlag;

void ADC_Configure(void)
{
    ADC_InitTypeDefADC_InitStruct;
    GPIO_InitTypeDef GPIO_InitStruct;
    NVIC_InitTypeDef NVIC_InitStruct;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC, ENABLE);

    ADC_StructInit(&ADC_InitStruct);
    ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b;
    ADC_InitStruct.ADC_Prescaler= ADC_Prescaler_16;
    ADC_InitStruct.ADC_Mode       = ADC_Mode_Continue;
    ADC_InitStruct.ADC_DataAlign= ADC_DataAlign_Right;
    ADC_Init(ADC1, &ADC_InitStruct);

    ADC_SampleTimeConfig(ADC1, ADC_Channel_3, ADC_SampleTime_240_5);

    ADC_AnyChannelNumCfg(ADC1, 0);
    ADC_AnyChannelSelect(ADC1, ADC_AnyChannel_0, ADC_Channel_3);
    ADC_AnyChannelCmd(ADC1, ENABLE);

    ADC_ClearFlag(ADC1, ADC_FLAG_EOC);
    ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

    /* PA3(POT) */
    GPIO_StructInit(&GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_3;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
    GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    NVIC_InitStruct.NVIC_IRQChannel = ADC_COMP_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPriority = 0x00;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);

    ADC_Cmd(ADC1, ENABLE);

}


void ADC_InternalVoltageSensor_Sample(void)
{
          float    Voltage;
    uint16_t ConversionValue = 0;
      ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    while (1)
    {

      if (0 != ADC_InterruptFlag)
      {
      ConversionValue = (float)ADC_GetChannelConvertedValue(ADC1, ADC_Channel_3);
                                 Voltage = (float)ConversionValue *(float)3.3 / (float)4096.0;
                              printf("\r\nVDDA = %0.2fV", Voltage);
                              USART_Printf(USART2,"\r\nVOLT:%0.1f",Voltage);
                              vTaskDelay(1);                                       
      }
    }
}

void ADC_COMP_IRQHandler(void)
{
    if (RESET != ADC_GetITStatus(ADC1, ADC_IT_EOC))
    {
      ADC_InterruptFlag = 1;

      ADC_ClearITPendingBit(ADC1, ADC_IT_EOC);
    }
}
5.实验现象



页: [1]
查看完整版本: 【灵动微电子MM32F0121测评】9.ESP32-WEB控制