本帖最后由 阿源玩电子 于 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_InitTypeDef GPIO_InitStruct;
NVIC_InitTypeDef NVIC_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[128];
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[MAX_CMD_LEN];
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[cmdIndex] = '\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[cmdIndex++] = data;
}
while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
}
}
#include "adc_dsp.h"
uint32_t ADC_InterruptFlag;
void ADC_Configure(void)
{
ADC_InitTypeDef ADC_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.实验现象
|
|