目录
[1 系统框架](#1 系统框架)
[1.1 模块介绍](#1.1 模块介绍)
[1.1 Version-Board 开发板](#1.1 Version-Board 开发板)
[1.1.1 Vision-Board简介](#1.1.1 Vision-Board简介)
[1.1.2 Vision-Board的资源](#1.1.2 Vision-Board的资源)
[1.2 框架介绍](#1.2 框架介绍)
[2 上位机App](#2 上位机App)
[2.1 UI设计](#2.1 UI设计)
[2.2 代码实现](#2.2 代码实现)
[3 功能测试](#3 功能测试)
[3.1 网络连接](#3.1 网络连接)
[3.2 功能测试](#3.2 功能测试)
概述
本文主要Renesa Version Board开发RT-Thread 之Client,使用C#语言开发一个上位机App实现Server功能。Renesa Version Board将Sensor数据(温度和湿度数据,环境光照数据, SR04 测量距离)通过WIFI上传给上位机。上位机将这些数据通过UI呈现出来。
1 系统框架
硬件实物图:
软件运行UI图:
1.1 模块介绍
- 温湿度传感器: SHT20 采集环境温度和湿度数据
2)光照传感器: ISL2003S 采集环境光照数据
3)超声波测距传感器:SR04 测量距离
4) OLED 显示屏: 实时显示温度、湿度、光照和距离参数
关于这些模块的应用程序设计,参考如下文章:
Renesa Version Board开发RT-Thread 之I2C驱动应用(ISL29035)_rt-thread i2c-CSDN博客
Renesa Version Board开发RT-Thread 之超声波测距模块(HC-SR04)-CSDN博客
Renesa Version Board开发RT-Thread 之I2C驱动应用(OLED)_rt-thread软件实现i2c-CSDN博客
1.1 Version-Board 开发板
1.1.1 Vision-Board简介
Vision-Board 开发板是 RT-Thread 推出基于瑞萨 Cortex-M85 架构 RA8D1 芯片,为工程师们提供了一个灵活、全面的开发平台,助力开发者在机器视觉领域获得更深层次的体验。
Vision Board搭载全球首颗 480 MHz Arm Cortex-M85芯片,该芯片拥有Helium和TrustZone技术的加持。官方提供的SDK包里集成了OpenMV机器视觉例程,配合MicroPython 解释器,使其可以流畅地开发机器视觉应用。
1.1.2 Vision-Board的资源
Vision-Board基于瑞萨R7FA8D1BH MCU设计,存储器:板卡搭载8M Bytes容量的Flash, 32M Bytes RAM。还支持摄像头接口,LCD接口。通信接口主要是WIFI。
该板块自带DAP-LINK,无需外接调试器,使用一条USB线即可开发,这一点对于开发者非常友好。
其具体资源列表如下:
板卡正面资源信息:
板卡反面资源信息:
1.2 框架介绍
如下三个模块挂载到Renesa Version Board的I2C接口上
- 温湿度传感器
2)光照传感器
3) OLED 显示屏
IO通用接口:
SR04超声波测模块挂载到IO接口上,通过MCU内部的定时器和IO的 外部中断测出距离数据
WIFI接口
Renesa Version Board 自带WIFI通信模块,这里只需使能该模块,然后建立Socket通信Client, 上位机实现Server功能。
Renesa Version Board WIFI模块使用方法,可参考文章:
2 上位机App
2.1 UI设计
上位机实现功能如下:
1) 上位机实现Server功能,Renesa Version Board 实现 Client功能,二者之间通过WIFI网络通信
2) 上位机实时显示Renesa Version Board上传的Sensor数据,包括温度,湿度,光照,和距离
3)上位机可以配置小车的操作模块:手动模式/自动模式
4)在手动模式下,可以通过button控制小车的运行状态。
上位机使用C#语言编写,其UI框架如下:
2.2 代码实现
1) 使用C# WINFrom框架设计UI
2)功能代码实现
cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace RT_ThreadApp
{
public partial class MianWindows : Form
{
Socket sokWatch = null; //负责监听 客户端段 连接请求的 套接字
Thread threadWatch = null; //负责 调用套接字, 执行 监听请求的线程
ManualResetEvent allDone = new ManualResetEvent(false);
Boolean b_watchCilent = false; //使能监听
Boolean b_connectSuccess = false;
Boolean b_ClosingTCP = false;
Boolean b_ListeningTCP = false;
String uid = "RT_Thread";
public MianWindows()
{
InitializeComponent();
}
private void MianWindows_Load(object sender, EventArgs e)
{
Control.CheckForIllegalCrossThreadCalls = false;
var array = LOCALIpAddress.GetSpecifiedIpAddress(AddressFamily.InterNetwork);
cbx_SerIP.Items.AddRange(array);
if (cbx_SerIP.Items.Count >= 1)
{
cbx_SerIP.SelectedIndex = 0;
}
radioButton_manual.Checked = true;
}
private void btn_Connect_Click(object sender, EventArgs e)
{
//实例化 套接字 (ip4寻址协议,流式传输,TCP协议)
sokWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//创建 ip对象
IPAddress address = IPAddress.Parse(cbx_SerIP.Text.Trim());
//创建网络节点对象 包含 ip和port
IPEndPoint endpoint = new IPEndPoint(address, int.Parse(tbx_ServerPort.Text.Trim()));
cbx_SerIP.Enabled = false;
tbx_ServerPort.Enabled = false;
try
{
//将 监听套接字 绑定到 对应的IP和端口
sokWatch.Bind(endpoint);
//设置 监听队列 长度为10(同时能够处理 10个连接请求)
sokWatch.Listen(10);
threadWatch = new Thread(StartWatch);
threadWatch.IsBackground = true;
threadWatch.Start();
b_watchCilent = true;
rtb_RecMonitor.AppendText("启动服务器成功......\r\n");
btn_Connect.Enabled = false;
btn_disConnect.Enabled = true;
b_ListeningTCP = true;
b_ClosingTCP = false;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
/// <summary>
/// 被线程调用 监听连接端口
/// </summary>
void StartWatch()
{
while (b_watchCilent)
{
try
{
allDone.Reset();
sokWatch.BeginAccept(new AsyncCallback(AcceptCallback), sokWatch);
allDone.WaitOne();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
public Dictionary<string, StateObject> dictConnL = new Dictionary<string, StateObject>();
public void AcceptCallback(IAsyncResult ar)
{
try
{
allDone.Set();
Socket listener = (Socket)ar.AsyncState;
Socket handler = listener.EndAccept(ar);
StateObject state = new StateObject();
state.workSocket = handler;
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
MonitorShowMsg(handler.RemoteEndPoint.ToString() + ":" + "连接成功", true);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
void MonitorShowMsg(string str, Boolean flag)
{
string dStr;
DateTime day = DateTime.Now;
dStr = "【" + day.Date.ToShortDateString() + " " + day.ToLongTimeString() + "】" + " " + str + "\r\n";
if (flag) //接受区监控
{
rtb_RecMonitor.Text += dStr;
}
else
{
rtb_RecMonitor.Text += dStr;
}
}
public void ShowMsg(string msgStr)
{
rtb_RecMonitor.AppendText(msgStr + "\r\n");
}
public void EndAcceptCallBack(IAsyncResult ar)
{
threadWatch.Abort();
Socket lenter = (Socket)ar.AsyncState;
Socket handler = lenter.EndAccept(ar);
handler.Dispose();
lenter.Dispose();
sokWatch.Dispose();
sokWatch = null;
}
public void ReadCallback(IAsyncResult ar)
{
try
{
String content = String.Empty;
StateObject state = (StateObject)ar.AsyncState;
Socket handler = state.workSocket;
int DataLength = handler.EndReceive(ar);
byte[] TempBuff = new byte[DataLength];
string str_recv = "";
string[] str_Msg;
Buffer.BlockCopy(state.buffer, 0, TempBuff, 0, TempBuff.Length);
if (DataLength > 0)
{
if (!dictConnL.ContainsKey(uid))
{
dictConnL.Add(uid, state);
}
else
{
dictConnL[uid] = state;
}
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReadCallback), state);
// 显示log
String str = DateTime.Now.ToString();
str_recv = System.Text.Encoding.ASCII.GetString(state.buffer);
ShowMsg(str + " " + str_recv);
// 显示数据
str_Msg = str_recv.Split(',');
textBox_humidity.Text = str_Msg[0];
textBox_temp.Text = str_Msg[1];
textBox_lux.Text = str_Msg[2];
textBox_SR40.Text = str_Msg[3];
Console.WriteLine(str_Msg);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private void send_wifi_Cmd(byte cmd )
{
byte mode = 0;
byte[] cmdList = { cmd ,0};
if (radioButton_manual.Checked)
{
mode = 0x02;
}
else if (radioButton_auto.Checked)
{
mode = 0x01;
}
cmdList[1] = mode;
SendCmdBuf(dictConnL, uid, cmdList, cmdList.Length);
}
private void btn_disConnect_Click(object sender, EventArgs e)
{
cbx_SerIP.Enabled = true;
tbx_ServerPort.Enabled = true;
Net_Disconnect();
dictConnL.Clear();
}
private void button_up_Click(object sender, EventArgs e)
{
byte cmd;
cmd = 0x01;
send_wifi_Cmd( cmd );
}
private void button_left_Click(object sender, EventArgs e)
{
byte cmd;
cmd = 0x03;
send_wifi_Cmd(cmd);
}
private void button_stop_Click(object sender, EventArgs e)
{
byte cmd;
cmd = 0x05;
send_wifi_Cmd(cmd);
}
private void button_right_Click(object sender, EventArgs e)
{
byte cmd;
cmd = 0x04;
send_wifi_Cmd(cmd);
}
private void button_down_Click(object sender, EventArgs e)
{
byte cmd;
cmd = 0x02;
send_wifi_Cmd(cmd);
}
private void Net_Disconnect()
{
try
{
if (b_watchCilent)
{
if (sokWatch != null)
{
b_ListeningTCP = false;
b_connectSuccess = false;
b_watchCilent = false;
btn_Connect.Enabled = true;
btn_disConnect.Enabled = false;
sokWatch.Dispose();
sokWatch.Close();
GC.Collect();
foreach (var v in dictConnL)
{
v.Value.workSocket.Close();
}
}
}
rtb_RecMonitor.Clear();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
public void SendCmdBuf(Dictionary<string, StateObject> dic, string UID, byte[] sendbyte, int length)
{
try
{
if (b_ListeningTCP)
{
Send(dic[UID].workSocket, sendbyte, length);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private void Send(Socket handler, byte[] data, int length)
{
try
{
if (b_ListeningTCP)
handler.BeginSend(data, 0, length, 0, new AsyncCallback(SendCallback), handler);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private void SendCallback(IAsyncResult ar)
{
try
{
Socket handler = (Socket)ar.AsyncState;
int bytesSent = handler.EndSend(ar);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private void radioButton_auto_CheckedChanged(object sender, EventArgs e)
{
if (radioButton_auto.Enabled)
{
button_up.Enabled = false;
button_down.Enabled = false;
button_left.Enabled = false;
button_right.Enabled = false;
button_stop.Enabled = false;
send_wifi_Cmd(0);
}
}
private void radioButton_manual_CheckedChanged(object sender, EventArgs e)
{
button_up.Enabled = true;
button_down.Enabled = true;
button_left.Enabled = true;
button_right.Enabled = true;
button_stop.Enabled = true;
}
}
public class LOCALIpAddress
{
/// <summary>
/// 获取指定地址族的ip地址
/// </summary>
/// <param name="addressFamily"></param>
/// <returns></returns>
public static IPAddress[] GetSpecifiedIpAddress(AddressFamily addressFamily)
{
var addressList = GetIpAddresses();
var ipv4Addresses = addressList.Where(x => x.AddressFamily == addressFamily).ToArray();
return ipv4Addresses;
}
/// <summary>
/// 获取本机的所有ip地址
/// </summary>
/// <returns></returns>
public static IPAddress[] GetIpAddresses()
{
var hostName = Dns.GetHostName();
var hostEntry = Dns.GetHostEntry(hostName);
return hostEntry.AddressList;
}
/// <summary>
/// 获取指定地址族的第一个ip地址
/// </summary>
/// <param name="addressFamily"></param>
/// <returns></returns>
public static IPAddress GetFirstSpecifiedIpAddress(AddressFamily addressFamily)
{
var addressList = GetIpAddresses();
var ipv4Address = addressList.First(x => x.AddressFamily == addressFamily);
return ipv4Address;
}
}
public class StateObject
{
// Client socket.
public Socket workSocket = null;
// Size of receive buffer.
public const int BufferSize = 1024 * 1024 * 5;
// Receive buffer.
public byte[] buffer = new byte[BufferSize];
// Received data string.
public StringBuilder sb = new StringBuilder();
}
}
3 功能测试
3.1 网络连接
1)编译和运行上位机,点击监听按钮,等待Client连接
2)启动Version board下位机, 连接网络
当出现如下信息,说明Version board client已经连接上Server
3.2 功能测试
1)数据上传和UI展示
2)App发送控制命令给Version Board
测试项一: 选择手动模式后,控制按键不能操作
测试项二:
**选择手动模式后,可通过发送控制命令给下位机,**Version Board也能正确的接收到这些命令