该模块可用于FPGA驱动ESP01-s连接MQTT服务器,同时支持使用HC-05蓝牙模块对ESP01-s完成配网操作,并且接收上位机指令,并发送信息至MQTT服务器。
效果演示:


使用Android APP向FPGA发送控制指令控制LED状态。
ESP-01s:
ESP-01S是由安信可科技开发的一款Wi-Fi模块。其核心处理器是ESP8266,该处理器在较小尺寸的封装中集成了业界领先的Tensilica L106超低功耗32位微型MCU,带有16位精简模式,主频支持80MHz和160MHz,并集成了Wi-Fi MAC/BB/RF/PA/LNA。
ESP-01s(MQTT)固件烧录:
前期准备:
USB转TTL模块或ESP01s下载器。
安信可flash_download_tools:下载地址
固件使用安信可科技MQTT透传AT固件(固件号:1471)下载地址
固件烧录详细步骤
ESP01s初始化模块(ESP01_Init.v):
该模块用于对ESP01s进行上电初始化以及驱动HC-05蓝牙模块向ESP01-s发送SSID及密码信息。其格式为:GetWIFI:AT+CWJAP="SSID","PASS"\n,当WiFi连接成功时,用蓝牙模块发送字符“T”,反之发送字符“F”。
ESP01_Init.
//基于Verilog的ESP32-01s初始化模块
module ESP01_Init (
input clk ,
input rst ,
input bt_rx ,
input esp_rx ,
output test_rx ,
input esp_init_start ,
output bt_tx ,
output test_tx ,
output reg esp_tx ,
output reg esp_init_done ,
output reg get_wifi_done
);
assign test_rx = esp_tx;
assign test_tx = esp_rx;
//状态机
reg [3:0] state; //状态机状态
reg [3:0] next_state;//下一个状态
parameter init_ = 4'b0000;//初始化状态
parameter wait_ = 4'b0001;//等待状态
parameter get_wifi = 4'b0010;//获取WIFI状态
parameter set_wifi = 4'b0100;//设置WIFI状态
parameter if_line = 4'b0101;//判断行数状态
parameter set_done = 4'b0111;//设置完成状态
parameter pw_str1 = 4'b1000;//ESP01s初始化字符串1
parameter pw_str1_tx= 4'b1001;//ESP01s初始化字符串1发送
parameter pw_str2 = 4'b1010;//ESP01s初始化字符串2
parameter pw_str2_tx= 4'b1011;//ESP01s初始化字符串2发送
parameter pw_str3 = 4'b1100;//ESP01s初始化字符串3
parameter pw_str3_tx= 4'b1101;//ESP01s初始化字符串3发送
parameter pw_str4 = 4'b1110;//ESP01s初始化字符串4
parameter pw_str4_tx= 4'b1111;//ESP01s初始化字符串4发送
//AT+MQTTUSERCFG=0,1,"ESP01S","","",0,0,""
parameter esp_pw_str1 = "AT";
parameter esp_pw_str2 = "AT+MQTTUSERCFG=0,1,\"ESP01S\",\"\",\"\",0,0,\"\"";
parameter esp_pw_str3 = "AT+MQTTCONN=0,\"43.143.168.95\",1883,1";
parameter esp_pw_str4 = "AT+MQTTSUB=0,\"FPGA_IoT\",0";
reg timer_en; //定时器使能
reg OK_rst; //OK复位标志位
reg set_wifi_str; //设置WIFI字符串标志位
reg line_flag; //连接成功标志位
reg [7:0]bt_ctrl_data; //蓝牙串口发送数据
reg bt_uart_start; //蓝牙串口发送开始标志位
wire [(8*80)-1:0] WIFI_ID_PW; //WIFI账号密码
reg [(8*80)-1:0] ESP_TX_Str; //WIFI账号密码
wire esp_wifi_tx; //ESP串口发送标志位
always @(posedge clk or negedge rst) begin
if(!rst)begin
state <= init_;
end else begin
state <= next_state;
end
end
reg [2:0] error_cnt; //错误计数
always @(*)begin
case(state)
init_:begin
bt_ctrl_data = "F";
esp_init_done = 1'b0;
OK_rst = 1'b1;
line_flag = 1'b0;
error_cnt = 3'b000;
bt_uart_start = 1'b0;
timer_en = 1'b1;
set_wifi_str = 1'b0;
esp_tx = esp_wifi_tx;
next_state = pw_str1;
end
pw_str1 :begin
esp_init_done = 1'b0;
set_wifi_str = 1'b0;
timer_en = 1'b0;
esp_tx = esp_wifi_tx;
ESP_TX_Str = esp_pw_str1;
if(timer_5s)begin
next_state = pw_str1_tx;
end else begin
next_state = pw_str1;
end
end
pw_str1_tx :begin
esp_init_done = 1'b0;
set_wifi_str = 1'b1;
timer_en = 1'b1;
esp_tx = esp_wifi_tx;
next_state = pw_str2;
end
pw_str2 :begin
esp_init_done = 1'b0;
set_wifi_str = 1'b0;
timer_en = 1'b0;
esp_tx = esp_wifi_tx;
ESP_TX_Str = esp_pw_str2;
if(timer_5s)begin
next_state = pw_str2_tx;
end else begin
next_state = pw_str2;
end
end
pw_str2_tx :begin
esp_init_done = 1'b0;
timer_en = 1'b1;
set_wifi_str = 1'b1;
esp_tx = esp_wifi_tx;
next_state = pw_str3;
end
pw_str3 :begin
esp_init_done = 1'b0;
set_wifi_str = 1'b0;
timer_en = 1'b0;
esp_tx = esp_wifi_tx;
ESP_TX_Str = esp_pw_str3;
if(timer_5s)begin
next_state = pw_str3_tx;
end else begin
next_state = pw_str3;
end
end
pw_str3_tx :begin
esp_init_done = 1'b0;
timer_en = 1'b1;
set_wifi_str = 1'b1;
esp_tx = esp_wifi_tx;
next_state = pw_str4;
end
pw_str4 :begin
esp_init_done = 1'b0;
set_wifi_str = 1'b0;
timer_en = 1'b0;
esp_tx = esp_wifi_tx;
ESP_TX_Str = esp_pw_str4;
if(timer_5s)begin
next_state = pw_str4_tx;
end else begin
next_state = pw_str4;
end
end
pw_str4_tx :begin
esp_init_done = 1'b0;
timer_en = 1'b1;
set_wifi_str = 1'b1;
esp_tx = esp_wifi_tx;
next_state = wait_;
end
wait_:begin
esp_init_done = 1'b1;
bt_uart_start = 1'b0;
bt_ctrl_data = "F";
esp_tx = esp_wifi_tx;
timer_en = 1'b1;
set_wifi_str = 1'b0;
line_flag = 1'b0;
OK_rst = 1'b1;
if(!esp_init_start)begin
next_state = get_wifi;
end else begin
next_state = wait_;
end
end
get_wifi:begin
OK_rst = 1'b1;
bt_uart_start = 1'b0;
timer_en = 1'b1;
line_flag = 1'b0;
set_wifi_str = 1'b0;
esp_tx = esp_wifi_tx;
if(param_rec_done)begin
next_state = set_wifi;
ESP_TX_Str = WIFI_ID_PW;
end else begin
next_state = get_wifi;
end
end
set_wifi:begin
OK_rst = 1'b1;
timer_en = 1'b1;
ESP_TX_Str = WIFI_ID_PW;
set_wifi_str = 1'b1;
next_state = if_line;
line_flag = 1'b0;
esp_tx = esp_wifi_tx;
end
if_line:begin
OK_rst = 1'b0;
timer_en <= 1'b0;
set_wifi_str <= 1'b0;
esp_tx = esp_wifi_tx;
if(equal_flag)begin
line_flag = 1'b1;
next_state = set_done;
end else begin
line_flag = 1'b0;
if(timer_5s)begin
error_cnt = error_cnt + 1;
if(error_cnt == 3'b111)begin
next_state = set_done;
end else begin
next_state = set_wifi;
end
end else begin
next_state = if_line;
end
end
end
set_done:begin
error_cnt = 3'b000;
esp_tx = esp_wifi_tx;
OK_rst = 1'b1;
timer_en <= 1'b1;
next_state = wait_;
if(line_flag)begin
bt_ctrl_data = "T";
bt_uart_start = 1'b1;
end else begin
bt_ctrl_data = "F";
bt_uart_start = 1'b1;
end
end
endcase
end
wire [7:0] bt_rx_data; //蓝牙串口接收数据
//蓝牙串口--->BT串口
//集串口发送和串口接收于一体的串口模块,
//tx与rx端口连接外部串口引脚,rx_data为串口接收到的数据,
//rx_rec_flag为串口接收到数据标志位
//tx_start为串口发送开启信号,tx_done为串口发送完成信号,
uart_const_baud #(.clock_freq(50_000_000),.baud_rate(115200)) Uart_BT(
.rx_data (bt_rx_data ), //串口接收到的数据
.rx_rec_flag(bt_rx_rec_flag ), //串口接收到数据标志位
.tx (bt_tx ), //串口发送引脚
.tx_done (bt_tx_done ), //串口发送完成信号
.tx_idle (bt_tx_idle ), //串口发送空闲信号
.tx_data (bt_ctrl_data ), //串口发送数据
.tx_start (bt_uart_start ), //串口发送开启信号
.rx (bt_rx ), //串口接收引脚
.rx_clr (bt_rx_rec_flag ), //串口接收到数据标志位清零
.clk (clk ), //时钟
.rst (!rst ) //复位
);
//获取对应的字符串参数,通过一对括号"("和")"来界定参数内容
//如LED0(1000),此时会将1000这串字符串筛选出来
//其中,str_buf为顶格,我们直接用排列后的str_rank
string_param #(.byte_num(80),.start_signal(":"),.end_signal(16'h0d)) STR_SAVE0(
.str_buf (),
.str_rank (WIFI_ID_PW ),
.param_rec_done (param_rec_done ),
.str_in (bt_rx_data ),
.rx_flag (bt_rx_rec_flag ),
.clk (clk ),
.rst (!rst )
);
//esp32串口--->ESP串口
wire [7:0] esp_ctrl_data; //ESP串口发送数据
wire [7:0] esp_rx_data; //ESP串口接收数据
//集串口发送和串口接收于一体的串口模块,
//tx与rx端口连接外部串口引脚,rx_data为串口接收到的数据,
//rx_rec_flag为串口接收到数据标志位
//tx_start为串口发送开启信号,tx_done为串口发送完成信号,
uart_const_baud #(.clock_freq(50_000_000),.baud_rate(115200)) UART_esp(
.rx_data (esp_rx_data ),
.rx_rec_flag(esp_rx_rec_flag),
.tx (esp_wifi_tx ),
.tx_done (esp_tx_done ),
.tx_idle (esp_tx_idle ),
.tx_data (esp_ctrl_data ),
.tx_start (esp_start ),
.rx (esp_rx ),
.rx_clr (esp_rx_rec_flag),
.clk (clk ),
.rst (!rst )
);
//定时器,通过给定与时钟频率一致的分频值,生成一秒定时脉冲
//定时脉冲连至串口发送字符串模块启动端,产生每秒发送一次的功能
timer #(.div_value(500_000_000)) TIMER0(
.timeout(timer_5s),
.clk(clk),
.rst(timer_en)
);
//串口发送字符串模块,string_in端口输入要发送的内容
//byte_num计算方法:每个中文文字占两位,每个英文字符占一位,回车换行(16'h0d0a)占两位。
uart_string_send #(.byte_num(82),.skip_end(1)) UART_SEND_esp(
.uart_data (esp_ctrl_data ),
.uart_start (esp_start ),
.str_in ({ESP_TX_Str,16'h0d0a} ),
.uart_tx_done (esp_tx_done ),
.uart_tx_idle (esp_tx_idle ),
.send_start (set_wifi_str ),
.clk (clk ),
.rst (!rst )
);
//字符串保存对比模块,start_signal为开始识别信号,保存内容为开始信号后的内容
//如开始信号为".",则当输入.12345时就会保存内容12345并进行对比
//str_cmp为进行对比的字符串内容
string_save_cmp #(.byte_num(2),.start_signal(16'h0a)) STR_CMP0(
.equal(equal_flag),
.str_cmp("OK"),
.str_in(esp_rx_data),
.rx_flag(esp_rx_rec_flag),
.clk(clk),
.rst(OK_rst)
);
endmodule
ESP01s顶层驱动模块(ESP01_Drive.v):
该模块为顶层模块,用于例化其他子模块以及对其他子模块控制。
ESP01_Drive.v
//基于Verilog的 ESP01 mqtt固件驱动程序
module ESP01_Drive (
input clk,
input rst,
//ESP01端口
input esp_rx,
output esp_tx,
//蓝牙模块端口
input bt_rx,
output bt_tx,
//数据输入输出端口
input [39:0] value1,
input [39:0] value2,
input [39:0] value3,
input [39:0] value4,
//用户输入端口
input get_wifi, //配网状态
//测试端口
output test_rx,
output test_tx
);
assign test_rx = esp_rx;
assign test_tx = esp_tx;
//状态机
reg [3:0] state; //状态机状态
reg [3:0] next_state;//下一个状态
parameter init_ = 4'b0000;//初始化ESP01状态
parameter wait_ = 4'b0001;//等待状态
parameter get_wifi_ = 4'b0010;//配网状态
reg tx_cs; //发送数据标志
assign esp_tx = tx_cs ? mqtt_tx_tmp : init_tx_tmp;
reg [(8*80)-1:0] ESP_TX_Str; //WIFI账号密码
wire init_tx_tmp,mqtt_tx_tmp;
always @(posedge clk or negedge rst) begin
if(!rst)begin
state <= init_;
end else begin
state <= next_state;
end
end
always @(*)begin
case(state)
init_:begin //初始化ESP01
tx_cs = 1'b0;
if(esp_init_done)begin
next_state = wait_;
end else begin
next_state = init_;
end
end
wait_:begin //等待状态
tx_cs = 1'b1;
if(!get_wifi)begin //按下配网键
next_state = get_wifi_;
end else begin
next_state = wait_;
end
end
get_wifi_:begin //配网状态
tx_cs = 1'b0;
if(get_wifi_done)begin
next_state = wait_;
end else begin
next_state = get_wifi_;
end
end
endcase
end
//例化ESP01初始化模块
ESP01_Init ESP01_Init_0(
.clk (clk ),
.rst (rst ),
.bt_rx (bt_rx ),
.esp_rx (esp_rx ),
.test_rx ( ),
.esp_init_start (get_wifi ),
.bt_tx (bt_tx ),
.test_tx ( ),
.esp_tx (init_tx_tmp ),
.esp_init_done (esp_init_done ),
.get_wifi_done (get_wifi_done )
);
ESP01_set ESP01_set0(
.clk (clk ),
.rst (rst ),
.value1 (value1 ),
.value2 (value2 ),
.value3 (value3 ),
.value4 (value4 ),
.esp_tx (mqtt_tx_tmp)
);
endmodule
ESP01sMQTT发送模块(ESP01_set.v):
该模块用于向上位机发送MQTT消息。发送格式为:“ "T1": "30.6",\n"T2": "43.5",\n"T3": "null",\n"T4": "null"”
ESP01_set.v
module ESP01_set (
input clk ,
input rst ,
//数据输入输出端口
input [39:0] value1,
input [39:0] value2,
input [39:0] value3,
input [39:0] value4,
output esp_tx
);
wire[7:0] uart_ctrl_data;
wire [(126*8)-1:0] Set_TX_Str = {
"AT+MQTTPUB=0,\"FPGA_IoT\",\"",
"{",
"\"T1\": \"",value1,"\",",
"\"T2\": \"",value2,"\",",
"\"T3\": \"",value3,"\",",
"\"T4\": \"",value4,"\"",
"}\",0,0"
};
//定时器,通过给定与时钟频率一致的分频值,生成一秒定时脉冲
//定时脉冲连至串口发送字符串模块启动端,产生每秒发送一次的功能
timer #(.div_value(50_000_000)) TIMER0(
.timeout(timer_1s),
.clk(clk),
.rst(!rst)
);
//串口发送驱动,需要给定时钟频率和波特率
uart_const_baud_tx #(.clock_freq(50_000_000),.baud_rate(115200)) UART_TX0(
.tx(esp_tx),
.tx_done(uart_tx_done),
.tx_idle(uart_tx_idle),
.tx_start(uart_start),
.tx_data(uart_ctrl_data),
.clk(clk),
.rst(!rst)
);
//串口发送字符串模块,string_in端口输入要发送的内容
//byte_num计算方法:每个中文文字占两位,每个英文字符占一位,回车换行(16'h0d0a)占两位。
//如果是使用了压缩包内的文件,那么此时编码方式为utf-8,此时一个中文字符占三位
//所以如果是quartus或者gaoyun等utf-8的编码EDA工具,byte_num应改为26
uart_string_send #(.byte_num(128),.skip_end(1)) UART_SEND0(
.uart_data(uart_ctrl_data),
.uart_start(uart_start),
.str_in({Set_TX_Str,16'h0d0a}),
.uart_tx_done(uart_tx_done),
.uart_tx_idle(uart_tx_idle),
.send_start(timer_1s),
.clk(clk),
.rst(!rst)
);
endmodule
ESP01sMQTT接收模块(ESP01_Drive.v):
该模块用于接收上位机发送的MQTT消息。需要上位机按指定格式发送指令,指令格式:“[Switch]": "(00000000)”。
ESP_Str_Get1
//基于Verilog的字符串处理模块
/*
Get
{
"[Switch]": "(00000000)"
}
set
{
"temperature": "30.6",
"humidity": "43.5",
"textView4": "null",
"textView5": "null"
}
*/
module ESP_Str_Get1#(
parameter Set_Str_num = 10 , //发送字符串长度
Get_Str_type = 6 , //接收字符串标签长度
Get_Str_value = 8 //接收字符串值长度
)(
input clk ,
input rst ,
input esp_rx ,
output wire [Get_Str_type*8-1:0] Get_type,
output wire [Get_Str_value*8-1:0] Get_value,
output wire get_done
);
wire [7:0] tx_data;
wire [7:0] rx_data;
//reg [Set_Str_num*8-1:0] Set_Str ;
//wire [Get_Str_type*8-1:0] Get_type ;
//wire [Get_Str_value*8-1:0] Get_value;
//例化Uart收发模块
uart_const_baud #(
.clock_freq (50_000_000 ),
.baud_rate (115200 )
) Uart0(
.rx_data (rx_data ),
.rx_rec_flag (rx_rec_flag ),
.tx ( ),
.tx_idle (tx_idle ),
.tx_done (tx_done ),
.tx_data (tx_data ),
.tx_start (tx_start ),
.rx (esp_rx ),
.rx_clr (rx_clr ),
.clk (clk ),
.rst (!rst )
);
string_param #(.byte_num(Get_Str_type),.start_signal("["),.end_signal("]")) STR_SAVE_type(
.str_buf (Get_type ),
//.str_rank (),
.str_cnt (),
.param_rec_done ( ),
.str_in (esp_rx ),
.rx_flag (rx_rec_flag ),
.clk (clk ),
.rst (!rst )
);
string_param #(.byte_num(Get_Str_type),.start_signal("("),.end_signal(")")) STR_SAVE_view(
.str_buf (Get_value ),
//.str_rank (),
.str_cnt (),
.param_rec_done (get_done ),
.str_in (esp_rx ),
.rx_flag (rx_rec_flag ),
.clk (clk ),
.rst (!rst )
);
//将16位8421bcd码转为12位hex码,
bcd_to_hex #(.bcd_bit(16), .hex_bit(12)) BTH0(
.hex(rx_hex),
.bcd(rx_bcd)
);
endmodule