基于Python和opencv图像处理(3)——图片的膨胀与腐蚀
图片的膨胀与腐蚀是非常常见的图像处理技巧(现在就见到了)需要注意的是,这里的腐蚀和膨胀时对于白色部分而言的,而不是黑色部分!!字体的大小就是常见的图像的腐蚀和膨胀。腐蚀和膨胀可以很好的去除掉图像中的噪声点,消除物体边界附近的像素。代码编写首先是导入相关的库import cv2 import numpy as np import matplotlib.pyplot as plt 之后利用opencv的函数来读取我们的图像。# 读取图像 img = cv2.imread('C:/Users/13256/Desktop/11.tif', cv2.IMREAD_GRAYSCALE) 定义结构元素来确定我们的卷积运算大小# 定义结构元素(核) kernel = np.ones((5, 5), np.uint8) 利用opencv的内置函数来进行膨胀运算# 膨胀操作 dilated_img = cv2.dilate(img, kernel, iterations=1) 最后显示出我们的图像# 显示原始图像、膨胀后的图像 plt.subplot(1, 2, 1), plt.imshow(img, cmap='gray'), plt.title('原始图像') plt.subplot(1, 2, 2), plt.imshow(dilated_img, cmap='gray'), plt.title('膨胀操作后的图像') plt.show()可以看到膨胀操作更凸显出图片的轮廓和边界将膨胀函数方法换成腐蚀,之后再运行我们的代码。# 腐蚀操作 dilated_img = cv2.erode(img, kernel, iterations=1) 以下附上全部代码import cv2 import numpy as np import matplotlib.pyplot as plt # 读取图像 img = cv2.imread('C:/Users/13256/Desktop/11.tif', cv2.IMREAD_GRAYSCALE) # 定义结构元素(核) kernel = np.ones((5, 5), np.uint8) # 膨胀操作 dilated_img = cv2.erode(img, kernel, iterations=1) # 显示原始图像、膨胀后的图像 plt.subplot(1, 2, 1), plt.imshow(img, cmap='gray'), plt.title('原始图像') plt.subplot(1, 2, 2), plt.imshow(dilated_img, cmap='gray'), plt.title('膨胀操作后的图像') plt.show()
实在太懒于是不想取名
2 9 嘉立创PCB
基于C#的软件大杂烩(4.1)—— 和ESP32_cam搭配的TCP协议解析图像
前几期公众号(上一期)中有介绍如何使用ESP32_Cam获取图像利用TCP协议上传,之后利用利用Python解析收到的代码并且显示出来。那么本期我们在之前的C#软件大合集中添加TCP服务器端并完成在C#中的图像显示的功能。在上一期说过,由于HTTP协议需要等待响应,因此可以做到非常好的数据传输校验。而TCP协议不需要服务器响应,因此虽然在数据传输的可靠性上得到了下降,但是在数据传输的速度上却得到了质的飞跃。首先简简单单地做个界面,右边是选择地址和端口的界面,左边是分开文本接收模式和图片显示模式。有一个件方便我们选择IP地址,由端口号来选择我们的端口号,需要注意的是通常端口都是开放的,如果我们遇到了打开端口报错的问题,我们需要及时调节我们的端口或者开启管理员模式。虽然说是1024及以下的低序列端口才需要开启管理员模式才能打开,但是在实际的使用中发现如果需要使用端口还是需要打开管理员模式的。IPV6地址由四组组成,我们获得的地址包括我们的公网地址以及映射地址。值得一提的事,如果设置地址是即默认监听所有可用地址。private void LoadLocalIPv4Addresses() { try { // 获取本地主机名 string hostName = Dns.GetHostName(); // 获取主机名对应的IP地址列表 IPAddress[] ipAddresses = Dns.GetHostAddresses(hostName); // 筛选IPv4地址并添加到 foreach (IPAddress ipAddress in ipAddresses.Where(ip => ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)) { IPAddressChoose.Items.Add(ipAddress.ToString()); } // 设置默认选择第一个IPv4地址 if (IPAddressChoose.Items.Count > 0) { IPAddressChoose.SelectedIndex = 0; } } catch (Exception ex) { MessageB.Show($"错误:{ex.Message}", "错误", MessageBButtons.OK, MessageBIcon.Error); } } 首先是加载的时候筛选出本机可以用的IP地址,把IP地址加入我们的控件中,方便我们使用时候的选择。主要代码之后编写创建监听的代码。创建监听的步骤主要是建立一个TCP监听句柄,之后使用异步的方法创建一个回调函数来处理我们接收到的图像。需要调节关闭时候的任务状态,防止调用过程中关闭监听,导致监听继续从而发生代码中断。 private void Connect_Click(object sender, EventArgs e) {        if (Connect.Text == "创建监听")        {             DisableAll();             StartListening();             Connect.Text = "结束监听";        }        else        {            EnableAll();            StopListening();            Connect.Text = "创建监听";        } } 创建监听的按钮按下后首先是禁用其他控件,之后是创建监听。private void StartListening() { try { // 获取选定的IP地址和端口 IPAddress ipAddress = IPAddress.Parse(IPAddressChoose.SelectedItem.ToString()); int port = int.Parse(PortChoose.Text); // 启动TCP监听 tcpListener = new TcpListener(ipAddress, port); tcpListener.Start(); cancellationTokenSource = new CancellationTokenSource(); isListening = true;                 //开辟线程实现异步监听 listenerThread = new Thread(() => ListenForImages(cancellationTokenSource.Token)); listenerThread.Start(); } catch (Exception ex) { MessageB.Show($"错误:{ex.Message}", "错误", MessageBButtons.OK, MessageBIcon.Error); } } private void StopListening() { // 停止TCP监听 isListening = false; if (cancellationTokenSource != null) {                 cancellationTokenSource.Cancel();//取消进程防止出错 cancellationTokenSource.Dispose(); cancellationTokenSource = null; } if (tcpListener != null) { tcpListener.Stop(); } } 创建监听的时候,我们根据所选择的IP地址和端口开启TCP监听,并且使用一个isListening标志位来判断是否处于监听状态。需要注意的是这里的cancellationTokenSource.Dispose()是不可少的,如果缺少这行代码就会导致异步程序进行会导致程序错误。 并且开始一个线程来实现异步监听。 之后分别是异步监听的函数,将我们收到的TCP数据转化为图片信息进行保存,并且把图片显示到PictureBox控件上。需要注意的是,在异步监听的函数中,我们使用Console.WriteLine来打印错误信息,防止阻塞UI进程。 效果展示
实在太懒于是不想取名
0 1 嘉立创PCB
基于ESP32_Cam和Python的实时图像显示
很早一段时间之前入手了一个ESP32_Cam即Camera摄像头。但是一直没有时间去研究这个模块,难得最近空下来了所以本期决定使用ESP32_Cam去实现一个实时视频传输的效果。ESP32_Cam如上图所示,淘宝上加上烧录器(一个串口转CH340)总计30+,使用的是经典的OV2640摄像头。官方Demo使用组装好之后,我们打开Arduino IDE,烧入ESP32的摄像头例程。并且修改相应的设置。使用该实例的代码,需要注意的是,我们需要注释掉第17行,解开第24行的注释,之后烧入我们的代码。修改WIFI连接的账号还有密码,之后开启电脑热点(或者自己家的WIFI)保证处于同一个局域网中。我们打开Start Stream来开启我们的流式传输,效果如下打开串口,输入这个浏览器地址就可以使用ESP32的官方Demo啦。代码优化为了更好的帧率以及拓展,我们决定使用Python来实现数据的接收以及显示。首先我们需要修改我们的代码。将官方例程中的这两个函数注释掉,我们在Loop循环中添加我们的逻辑。void loop() {    camera_fb_t *fb = esp_camera_fb_get();//获取图像 if (fb == nullptr) { Serial.println("Camera capture failed"); } else { Serial.println("Camera capture OK");     //使用TCP协议上传 WiFiClient client;     client.connect("192.168.137.1", 8081);//上传地址的端口号     client.write(fb->buf, fb->len);//上传字节数组和长度 }   esp_camera_fb_return(fb);//返回帧 } 为此ESP32的代码编写完成。之后我们需要编辑Python部分的代码,关于Python的环境安装可以看往期的公众号。 需要导入Opencv库来显示我们的图像。#该线程用于图像显示 def display_image(): global shared_image while True:         if shared_image is not None: for (x, y, w, h) in faces: cv2.rectangle(shared_image, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.imshow('Received Image with Face Detection', shared_image) cv2.waitKey(1) # 服务器地址和端口 host = '0.0.0.0' #0.0.0.0则是设置监听所有可用地址 port = 8081 # 创建一个TCP套接字 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 将套接字绑定到指定地址和端口 server_socket.bind((host, port)) # 开始监听连接,允许最大连接数为1 server_socket.listen(1) print(f"Listening on {host}:{port}...") # 创建一个线程用于图像显示 display_thread = threading.Thread(target=display_image) display_thread.daemon = True # 设置为守护线程,主程序退出时线程也会退出 display_thread.start() while True: # 接受客户端连接 client_socket, client_address = server_socket.accept() print(f"Accepted connection from {client_address}") # 接收数据并保存到内存中 data = b"" while True: packet = client_socket.recv(4096) if not packet: break data += packet # 将接收到的二进制数据转换为NumPy数组 image_array = np.frombuffer(data, dtype=np.uint8) # 解码图像 with image_lock: shared_image = cv2.imdecode(image_array, cv2.IMREAD_COLOR) print("Image received and sent to display.") # 关闭连接 client_socket.close() 之后运行以下我们的代码,可以看到虽然运行起来还不是很流畅,但是也可以勉强接受,相比官方的几秒一帧已经有很大的改善了。
实在太懒于是不想取名
2 15 嘉立创PCB
基于Python和opencv图像处理(1)——相关库导入完成简单的图像处理
关于Python的环境安装还有库导入可以参考这篇公众号 人工智能导论(1)——Python环境安装与基础编程 本期我们要实现的内容是: (1)使用canny边缘检测算法提取花朵轮廓; (2)计算图像直方图,使用阈值分割法对花朵区域进行分割,并提取花朵轮廓; (3)计算出花朵轮廓图的Hu矩; 打开PyCharm在安装库的界面搜索opencv-python,安装Python的Opencv库。import cv2 import numpy as np 导入opencv库以及运算工具库。 我们利用opencv的自带库来进行Canny算法。import cv2 import numpy as np # 读取图像 img = cv2.imread('C:/Users/13256/Desktop/1.bmp', 0) #读取灰度图像# 使用Canny算法进行边缘检测 edges = cv2.Canny(img, 50, 150) #50和150是Canny算法的低阈值和高阈值# 显示原始图像和边缘检测结果 cv2.imshow('原始 Image', img) cv2.imshow('Canny Edges', edges) cv2.waitKey(0) #任意键推出 cv2.destroyAllWindows() 运行代码可以看效果 可以看到效果显著。 (2)计算图像直方图,使用阈值分割法对花朵区域进行分割,并提取花朵轮廓; import cv2 import numpy as np # 读取图像 img = cv2.imread('C:/Users/13256/Desktop/5.jpg') # 转换为灰度图像 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 计算直方图 hist = cv2.calcHist([gray], [0], None, [256], [0, 256]) # 选择适当的阈值进行分割 _, thresholded = cv2.threshold(gray, 70, 256, cv2.THRESH_BINARY) # 查找轮廓 contours, _ = cv2.findContours(thresholded, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 绘制轮廓 result = img.copy() cv2.drawContours(result, contours, -1, (0, 255, 0), 2) # 显示图像和结果 cv2.imshow('yuanshi Image', img) cv2.imshow('yuzhi Image', thresholded) cv2.imshow('Contours', result) cv2.waitKey(0) cv2.destroyAllWindows() (3)计算出花朵轮廓图的Hu矩; Hu矩(Hu Moments)是一组用于描述图像形状的特征矩。这些矩是从图像的原始矩(Moments)计算而来的,通过对图像的几何形状进行建模,可以用于表征图像的整体形状特征。 Hu矩的主要优点是它们对于图像的旋转、缩放和平移具有不变性,这使得它们在图像识别和模式匹配中非常有用。 共有七个Hu矩,称为Hu1至Hu7。这些矩可以通过计算图像的中心矩(central moments)和规范化中心矩(normalized central moments)得到。Hu矩的计算公式较为复杂,但它们的主要思想是通过对图像的几何形状特征进行标准化,使得这些特征对于缩放、旋转和平移不敏感。 在OpenCV中,可以使用cv2.HuMoments()函数来计算图像的Hu矩。这些矩以一维数组的形式返回,其中包含Hu1至Hu7的值。 在绘制Hu矩的时候 需要我们多导入一个库: matplotlib来绘制我们的Hu图像。import cv2 import numpy as np import matplotlib.pyplot as plt # 读取图像 img = cv2.imread('C:/Users/13256/Desktop/5.jpg', cv2.IMREAD_GRAYSCALE) # 选择适当的阈值进行分割 _, thresholded = cv2.threshold(img, 70, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, _ = cv2.findContours(thresholded, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 筛选出面积较大的轮廓 filtered_contours = [cnt for cnt in contours if cv2.contourArea(cnt) > 100] # 初始化一个数组来保存Hu矩的值 hu_values = [] # 遍历筛选后的轮廓并计算Hu矩 for contour in filtered_contours: hu_moments = cv2.HuMoments(cv2.moments(contour)).flatten() hu_values.append(hu_moments) # 将Hu值打印成直方图 hu_values = np.array(hu_values) num_hu_moments = hu_values.shape[1] plt.figure(figsize=(10, 6)) for i in range(num_hu_moments): plt.subplot(2, 4, i + 1) plt.hist(hu_values[:, i], bins=20, color='blue', alpha=0.7) plt.title(f'Hu{i + 1}') plt.grid(True) plt.tight_layout() plt.show()
实在太懒于是不想取名
2 5 嘉立创PCB
电容测量之NE555
电容传感器在我们的生活中用处广泛,如何测量电容,电容测量电路也是一个讨论甚广的电路。本期我们分享一种利用NE555测量电路的方法。NE555有着电子小甲虫之称,其使用非常非常的广泛,并且稳定性经过了时间的考验。在NE555的基础上有着许多的电路设计例如施密特触发器、单稳态触发器等等,本期我们使用NE555构成无稳态振荡器来实现电容的测量。上图这种利用NE555的无稳态电路被广泛的使用,利用R1和R2以及C1构成的充电放电电路在这个特定的电路之下使得3脚输出一个频率的方波利用这个公式我们可以推算出频率和电容电阻的关系我们利用Multisim进行仿真,可以看到理论频率为4800HZ,我们利用频谱分析仪测出的频率则是在4877HZ,1.6%的误差(应该可以纠正, 而且是频谱分析仪,而且是仿真。)02测量电路与数据处理本期我们想要测量10pf到100pf的电容,事实上在测量这种小电容的时候会受到电路分布电容的影响。由于两个引脚相距很近,所以事实上这两个IO自己就会构成一个电容,因此当我们测微小电容的时候引脚之间的电容是不能忽视的。简单的用洞洞板做了个电路,R1和R2分别选用328K电阻(可能也有点误差)。我们发现,电路空载的时候存在一个168.4KHZ的方波,这个信号就是由电路的分布电容引起的振荡。接着我们将168.4KHZ代入公式。1.44/(328+2*328)/168.4 = 0.0000086uF,即约等于8pf的电容。之后我们测量多组数据,将实际电容值和理论电容值记录下来利用拟合工具进行拟合。、程序运行程序部分本来打算使用51单片机的(主打一个丐版),因为频率在168KHZ,51单片机完全可以采集到168KHZ的信号,因此但是手上并没有合适的51单片机和屏幕,于是就打算使用STM32单片机来进行测试,我们使用外部中断来获取频率数,利用定时器定时触发来计算频率数量,之后转换成电容值进行数据处理后打印在LCD屏幕上。使用PB2作为外部中断,定时器2作为时间计数。定义一个数字变量,在外部中断的中断回调函数中进行频率计数,定时器每秒统计频率计数用一个变量来存储。 之后再把我们获得的频率利用公式计算出理论电容,之后把理论电容通过拟合曲线转换为实际电容。 效果展示
实在太懒于是不想取名
3 7 嘉立创PCB
基于C#和ESP32的远程示波器(7)——外部ADC设计提高采样率和数据精度
上期我们暂时的解决了数据连续性问题,但是我们发现我们的ADC采样似乎有非常大的噪声,因此我决定利用外部ADC芯片来替换到噪声非常大的板载ADC的噪声非常大。 我们对ADC信号进行傅里叶变换,可以看到ADC的噪声还是非常大的。 因此我们决定使用外部ADC来提高精度。 ADC选型 MAX11606 是美信 公司推出的低噪声累加型ADC芯片,并且其四通道的设计符合我们的需求,87KHZ的采样率也是满足我们的使用要求,支持IIC通讯。 所以我决定使用这块ADC芯片来制作我的外部ADC模块。我们参考芯片手册可以了解这块芯片的相关 查看芯片手册,其中MAX11606的供电电源可以到达5V,参考电平也可以到达5V(纠正,这个是外部参考,我们使用内部参考为4.096V)满足我们的0~3.3V的使用需求。 DAC选型 DAC芯片我们选择微芯公司的MCP4822,MCP4822具有12位精度,SPI通讯双通道芯片 MCP4822的快速沉降时间为4.5us,压摆率为0.55V/us,足够满足我们的要求(我的前置运放LM324是0.5V/us的压摆率二者差不多) 并且,其信噪比也很低,非常适合于我们的项目。 (这两块芯片是我找了好久好久的电路比较简单,速度快,分辨率高以及最最最最最重要的价格便宜并且嘉立创上面有库存的器件) 电路设计 绘制我们的原理图部分,其中红色方框为数字地与模拟地,数字电源和模拟电源的隔离。 蓝色方框为IIC和SPI的上拉电阻。 我们利用ESP32的SPI,CS连D5,SCK连D18,DIN连D23,LDNC选择一个(我选择D17)。 IIC部分,SCL连D22,SDA连D21。 MCP4822驱动编写 我们查看MCP4822的手册。 MCP4822的命令由:4个配置位+12位数据组成。 从高到低分别是:通道选择,无关位,增益控制位以及输出控制。 例如我们想要使用通道A,增益为2,并且可输出,那么设置为0001,对应的十六进制是0x1;void SPI_Init() { // 初始化对应VSPI接口,得到SPI对象 vspi = new SPIClass(VSPI); vspi->begin(VSPI_SCLK, VSPI_MISO, VSPI_MOSI, VSPI_SS); //SCLK, MISO, MOSI, SS pinMode(vspi->pinSS(), OUTPUT); //VSPI SS pinMode(LDAC, OUTPUT); } 初始化我们的SPI,begin参数中传入我们的SPI引脚。 接着我们编写我们的写命令函数。void spiCommand(SPIClass *spi, uint8_t cmd,uint16_t value) { // 设置SPI对应模式 uint32_t data = cmd<[removed]beginTransaction(SPISettings(spiClk, MSBFIRST, SPI_MODE0)); digitalWrite(spi->pinSS(), LOW); // NSS低电平,数据开始传输 spi->write16(data); digitalWrite(spi->pinSS(), HIGH); // NSS高电平,数据截止传输 digitalWrite(LDAC,LOW); digitalWrite(LDAC,HIGH); spi->endTransaction(); } 我们将cmd命令左移12位,接着加上我们的值就构成了我们要发送的命令,接着我们开始SPI的传输,并且拉低SS片选信号。接着传入我们拼接好的数据,接着拉低LDAC使的数据写入寄存器开始工作,关闭SPI传输。 //use the SPI buses for(float i = 0;i<2*3.14;i+=0.01) {     spiCommand(vspi, 0x1,((sin(i))+1)*2096/4); // junk data to illustrate usage } 在主函数中我们设置一个For循环让DAC输出一个正弦波。 可以看到DAC可以正常地工作。 MAX11606驱动编写 MAX11606是使用IIC驱动,首先我们需要在头文件中引入ESP32 Arduino的IIC库:#include [removed] 之后启动IIC,由于MPU作为主机,因此begin函数不需要传入参数,如果作为从机和其他主机通讯就需要输入地址。Wire.begin(); 查阅MAX11606手册得知,该芯片的从机地址是0x34 上述的两张表用来配置启动设置,我们需要的是:选择内部Vref,外部时钟,单极输入,不重置。扫描方式为连续扫描,通道选择1,单端输入。Wire.beginTransmission(0x34);   Wire.write(0xC);//一个字节一个字节的发 Wire.write(0xA); Wire.endTransmission(true); int result = Wire.endTransmission(); Wire.beginTransmission(0x34); Wire.write(0x6); Wire.write(0x7); result = Wire.endTransmission(); if (result == 0) { Serial.println("命令发送成功"); } else { Serial.println("命令发送失败"); } 之后定义读取的函数。void IIC_Read() { int deviceAddress = 0x34; uint16_t result = 0; // 请求读取数据,读取2个字节 Wire.requestFrom(deviceAddress, 2); // 等待数据可用 while (Wire.available() < 2) { // 等待数据 } // 读取数据并输出到串口 int TT = Wire.read(); //result = result<<8; result = Wire.read(); TT=(TT-252); result = (TT<[removed]
实在太懒于是不想取名
7 13 嘉立创PCB
人工智能导论(3.1)—— 决策树和信息增益
决策树是一种基于树状结构来进行决策的模型,被广泛用于分类和回归问题。它通过对输入数据集进行逐步的划分,从而在叶节点上做出决策。每个内部节点表示一个属性或特征,每个分支代表该属性或特征的一个可能的取值,而每个叶节点表示一个类别标签或回归值。 决策树的优点是简单易懂,可以直观地表示决策过程。缺点是决策树可能过于复杂,难以解释。 信息增益(information gain)是决策树算法中的一个重要概念,它用于选择最能划分数据集的属性。信息增益是指划分数据集后,数据集的熵(不确定性)减少的程度。 熵是信息论中的一个概念,它用来度量随机变量的不确定性。熵越高,表示随机变量的不确定性越大。 信息增益的公式如下: 信息增益 = 原始熵 - 条件熵 其中,原始熵是指未划分数据集的熵,条件熵是指根据属性划分数据集后,每个子集的熵。 例如,假设我们有以下数据集: 该数据集共有两种属性:年龄和类别。年龄有两个值:小于30岁和大于30岁。类别有两种值:感冒和流感。 原始熵可以计算为: 原始熵 = -(1/2 * log2(1/2) + 1/2 * log2(1/2)) = 1 根据年龄将数据集划分为两个子集: 其中,小于30岁子集包含一个数据点,类别均为感冒。大于30岁子集包含一个数据点,类别均为流感。 条件熵可以计算为: 条件熵 = -(1 * log2(1) + 1 * log2(1)) = 0 因此,信息增益可以计算为: 信息增益 = 原始熵 - 条件熵 = 1 - 0 = 1 信息增益越大,表示属性对数据集的划分越有效。因此,在决策树算法中,我们会选择信息增益最大的属性作为划分数据集的依据。 信息增益的优点是简单易懂,计算方便。缺点是容易受到数据集分布的影响。例如,如果数据集中的某个类别占比过高,那么信息增益可能会被夸大。 ID3算法是决策树算法的一种,它使用信息增益来选择最能划分数据集的属性。信息增益是指划分数据集后数据集的熵(不确定性)减少的程度。 信息增益的公式如下: 信息增益 = 原始熵 - 条件熵 其中,原始熵是指未划分数据集的熵,条件熵是指根据属性划分数据集后,每个子集的熵。 在本例中,原始熵可以计算为: 原始熵 = -(11/14 * log2(11/14) + 3/14 * log2(3/14)) = 0.940 根据天气将数据集划分为两个子集:晴天和非晴天。 晴天子集包含 5 个数据点,其中 3 个数据点的类别是“否”,2 个数据点的类别是“是”。因此,晴天子集的条件熵可以计算为: 条件熵 = -(3/5 * log2(3/5) + 2/5 * log2(2/5)) = 0.609 因此,根据天气划分数据集的信息增益可以计算为 信息增益 = 0.940  - 0.609  = 0.331 同样,我们可以计算出其他属性的信息增益。 因此,我们选择天气作为第一个分类属性,因为它的信息增益最大。属性信息增益天气0.6931471806温度0.4138067639湿度0.3258092328是否有风0.1921568627 根据信息增益的值顺序,我们可以列出我们的分类图 天气-->温度-->湿度-->风力 例如: 非晴   凉爽   正常   无风  去打球
实在太懒于是不想取名
1 4 嘉立创PCB
基于C#的软件大杂烩(3.1)—— 线性拟合工具
前面人工智能导论(2)——基本算法程序编写——回归介绍了Python中使用线性拟合工具,我就想在C#中能不能做一个线性拟合工具,方便我们进行线性拟合。界面布局和基础准备我们简单的绘制一个窗体,其中可以进行选择数据,显示原始数据以及进行数据拟合。其中的存放数据的容器使用的是DataGripView,其他的应该都很熟悉。之前在设计串口示波器的时候,我们引入了MathNet数学库,本期我们也是利用MathNet库中的方法进行数据拟合。using MathNet.Numerics.LinearAlgebra; using MathNet.Numerics.LinearAlgebra.Double; using MathNet.Numerics.LinearRegression; 导入我们的MathNet相关库(在NuGet中获取)除此之外我们还需要安装其他的几个库,也在NuGet管理库中添加。CsvHelper,EPPlus这两个库可以帮助我们解析CSV文件和EXCEL文件。功能编写private void DataChoose_Click(object sender, EventArgs e) {   using (OpenFileDialog openFileDialog = new OpenFileDialog())   {      openFileDialog.Filter = "CSV Files (*.csv)|*.csv|Excel Files (*.xls;*.xlsx)|*.xls;*.xlsx|All Files (*.*)|*.*"; DialogResult result = openFileDialog.ShowDialog(); if (result == DialogResult.OK)      {          string selectedFilePath = openFileDialog.FileName; LoadDataFromSelectedFile(selectedFilePath); } } } 当我们点击按钮的时候,唤醒资源管理器,选择我们的CSV文件或者Excel文件,并且将文件路劲作为变量传递给LoadDataFromSelectedFile函数。 当我们点击数据显示按钮的时候,先检查我们的XY轴是否已经选择,如果没有选择我们则进行报错,并退出函数。如果选择没有问题的话,我们就先检测数据是否有异常(这里应该添加更多的判断逻辑)之后根据我们选择的XY轴来将数据打印到Chart上作为原始数据。当我们点击拟合按钮,我们从波形图中获取各个点的数据保存到列表中,之后调用数学库中的线性拟合函数,将XY作为参数传入。效果展示regression 的两个成员item1和item2分别是截距和斜率,根据我们的截距和斜率来画出我们的线性拟合曲线。
实在太懒于是不想取名
1 2 嘉立创PCB
人工智能导论(2)——基本算法程序编写——回归
回归是统计学和机器学习中的一种分析方法,用于研究变量之间的关系。具体而言,回归分析用于建立一个模型来描述自变量(特征)与因变量(目标)之间的关系,并且该关系通常用一个函数来表示。在回归中,我们通常考虑的是连续型的因变量。回归分析的目标是找到一个适当的模型,以最好地拟合已知数据,并用于对未知数据的预测。回归模型的形式可以是线性的或非线性的,具体选择取决于问题的性质和数据的特点。以下是一些常见的回归模型:线性回归(Linear Regression):假设自变量与因变量之间的关系是线性的。多项式回归(Polynomial Regression):考虑自变量的高次项,使得模型可以拟合非线性关系。岭回归(Ridge Regression)和Lasso回归(Lasso Regression):用于处理多重共线性问题,同时对参数进行正则化。逻辑回归(Logistic Regression):用于处理二分类问题,输出是概率值,并通过一个逻辑函数将概率映射到0和1之间。支持向量回归(Support Vector Regression,SVR):适用于处理非线性关系,利用支持向量机的思想。..............本期我们使用线性回归实现对数据的预测(数据来源:文件 (lanzoui.com))我们将我们的样本复制到我们的工程目录接着我们导入我们的相关库包括:pandas库用来读取数据,线性回归模型以及画图库。import pandas as pd from sklearn.linear_model import LinearRegression import matplotlib.pyplot as plt 读取csv文件的第一列和第二列,用X和Y存储。df = pd.read_csv('data.csv') X = df.iloc[:, 0:1]  # 第一列y=df.iloc:,1# 第二列 接着创建一个线性回归模型,对我们的X和Y进行线性回归拟合model = LinearRegression() #训练模型model.fit(X, y)# 获取模型参数 slope = model.coef_[0] intercept = model.intercept_ 绘制出我们的线性回归模型:# 绘制原始数据和线性回归模型的拟合曲线 plt.scatter(X, y, color='black') plt.plot(X, model.predict(X), color='blue', linewidth=3, label='函数: y = {:.2f}X + {:.2f}'.format(slope, intercept)) plt.xlabel('X') plt.ylabel('y') plt.title('xian xing hui guiw') plt.legend() plt.show() 可以看到我们的线性回归曲线被打印了出来、我们也可以用R-sqared来评估我们的线性回归曲线。r_squared = model.score(X, y) print('R-squared:', r_squared) 可以看到我们的R-squared为0.9618,非常的接近1,说明我们的模型非常的成功。 最后我们也可以调用我们的模型进行预测predicted_y = model.predict([[0.5]])#因为是二维 print('Predicted y:', predicted_y)
实在太懒于是不想取名
0 3 嘉立创PCB
人工智能导论(1.2)——ROC曲线介绍以及绘制ROC曲线
上期我们介绍了Python的环境配置,本期我们介绍ROC曲线以及如何绘制ROC曲线。 ROC(Receiver Operating Characteristic)曲线是用于评估二分类模型性能的一种工具。它显示了在不同阈值下,真正例率(True Positive Rate,也称为灵敏度或召回率)与假正例率(False Positive Rate)之间的权衡关系。当我们讨论ROC曲线时,可以想象一个二分类模型,例如医学诊断中的癌症检测。我们用“正例”表示病患患有癌症,而“负例”表示患者没有癌症。真正例率 (TPR,也叫灵敏度或召回率): 假设我们的模型通过某个阈值,成功地找出了患有癌症的病患。 这就是一个真正例。 TPR告诉我们在所有实际患有癌症的病患中,有多少被成功地找出来了。 假正例率 (FPR): 然而,模型可能会犯错误,将一些实际上是健康的人错误地判定为患有癌症。 这就是一个假正例。 FPR告诉我们在所有实际健康的人中,有多少被错误地认为患有癌症。 现在 ,我们可以考虑不同的 阈值。 如果我们把阈值设置得很低,模型可能会把很多人都判定为患有癌症,这会增加TPR,但同时也增加了FPR。 反之,如果我们把阈值设置得很高,模型只会把那些非常确信患有癌症的人判定为患有癌症,这样会减少FPR,但可能也减少了TPR。ROC曲线就是在不同阈值下,TPR和FPR之间的权衡。如果你能画出ROC曲线,你可以看到在整个权衡范围内的模型表现情况。比如,一条理想的ROC曲线可能如下图所示: 这个图形展示了在不同阈值下,TPR和FPR的变化。曲线越靠近左上角,说明模型在保持高召回率的同时,控制假正例率的能力越好。ROC曲线正是通过不断移动分类器的“阈值”来生成曲线上的一组关键点 (也可以参考老师给的这篇博客:机器学习基础(1)- ROC曲线理解 - 简书 (jianshu.com)可能讲的更清晰一点) import numpy as np import matplotlib.pyplot as plt from sklearn.metrics import roc_curve, auc 我们导入我们需要使用的库,我们使用import导入特定的库,使用from从特定关键字导入,使用as作为库的别称。y_true = np.random.randint(2, size=100) y_scores = np.random.rand(100) 我们使用random生成随机的一百组数据。fpr, tpr, thresholds = roc_curve(y_true, y_scores) 接着调用roc_curve代入我们生成的数 据 计算 ROC曲线的参数。roc_auc = auc(fpr, tpr) 计算ROC曲线的面积。plt.figure(figsize=(8, 8)) plt.plot(fpr, tpr, label='ROC曲线(AUC = {:.2f})'.format(roc_auc)) plt.plot([0, 1], [0, 1], linestyle='--', label='数据') plt.xlabel('FPR') plt.ylabel('TPR') plt.legend(loc='lower right') plt.show() 最后利用matplotlib生成我们的图像并显示就完成了我们的ROC曲线的绘制。
实在太懒于是不想取名
2 5 嘉立创PCB
人工智能导论(1)——Python环境安装与基础编程
人工智能是科技领域冉冉升起的一颗新星,年初的ChatGPT,智能驾驶等无不预示着AI时代的到来,而人工智能导论正是开始学习AI的开篇。本期我们以Python为工具之后逐步实现人工智能的四个实验(主要这门课就给我布置了四个实验)我们的语言使用Python,原因在于Python拥有丰富的深度学习框架,大量的开源工具以及库,丰富的拓展性以及使用人群,最重要的是简单易上手。环境安装首先是Python的安装与相关编程工具的下载。Python环境的安装有两个方式,一种方式是使用Anaconda安装Python全家桶第二种方法则是直接前往Python的官网下载Python环境。我推荐是使用第二种方法,前往Python的官网(因为那个Anaconda可能是我使用的原因,我之前在使用Anaconda的时候下了好几个版本,包括Mini Anaconda反正啰嗦的要死,我之前去Python官网的配置环境倒是简单的不要再简单了)某度搜索Python官网,进入官网之后,在下载界面找到我们的Python最新稳定版 现在最新的稳定版是3.12,我当时下的比较早是3.7(后来更新到3.10)不过只要大版本相同,区别都不大。接着我们下滑,找到我们的下载链接,点击下载源文件。在最下面找到对应我们系统的版本,我们的系统一般是Win 64(Win32 和 Linux的自行切换)不知是因为我在绿皮火车上的原因还是网站本身的原因(因为一般下载这种资源需要在镜像网站下载否则会非常非常慢)当然我这里提供懒人链接啦,公众号私信(Python.exe)千万别傻乎乎的把括号也加上去。有时候作者看到你们的私信都有那种想顺着网线过去打你们的冲动,记住就是Python.exe   10个字母,Python.exe !!!下载好Python.exe之后,我们运行这个文件。安装的时候这边的五个选项全部打勾(总没人傻乎乎的安装到C盘吧)(我为此还把正版的卸了装了盗版)安装完PyChram,我们的环境配置也就完成了,接下来介绍如何在PyCharm中创建我们相关的工程以及添加我们的各种各样的库。新建工程首先打开我们的PyCharm,点击File下拉框,点击New Project创建我们的新工程、红色方框处选择我们的工程位置,蓝色方框处选择我们的版本(我这里比较乱,大家选择能选的就行)点击This Window就不打开新的窗体了,在本窗体。main.py就是我们的主要文件,右键空白(黑)处,Run "main.py"就可以运行我们的程序。库安装接着我们为我们工程安装我们相关的工具包。在File中选择Settings选择Python Interpreter点击加号,添加我们的库。我们分别添加numpy、scipy、pandas、matplotlib、scikit-learn、sklearn等工具包;其中Numpy库可以用来处理多维数组和矩阵,神经网络的本质就是复杂的矩阵运算(不亏我线代考了五次)Scipy则是在Numpy的基础上提供了许多模块,例如方程求解,图像处理,插值算法,信号处理,统计等等。Pandas提供了两种额外的数据结构,可以帮助我们更好的处理数据。Matplotlib学过Matlab的朋友可能觉得很熟悉,因为在Matlab中就是使用Plot函数进行画图的,所以这个库顾名思义就是用来画二维图像的库。scikit-learn,这个直译,机器学习~~sklearn 这个,额奇怪,Python里面好像没有这个库,这个应该是scikit-learn的简称,我怀疑是我老师写错了。from sklearn.datasets import load_iris iris_dataset = load_iris() print("Keys of iris_dataset: \n{}".format(iris_dataset.keys())) print("Shape of data: {}".format(iris_dataset['data'].shape)) 我们运行我老师给的代码,在下方的终端可以看见运行结果(右键空白处点击Run "main")上述代码其实是导入模块:从这里也可以看出sklearn其实是简称from sklearn.datasets import load_iris 加载某个数据集:iris_dataset = load_iris() 打印其中的iris_dataset对应的值print("Keys of iris_dataset: \n{}".format(iris_dataset.keys())) 打印其中的样本数和特征数print("Shape of data: {}".format(iris_dataset['data'].shape)) 下期预告下期介绍Python的语法规范以及如何绘制ROC曲线
实在太懒于是不想取名
0 2 嘉立创PCB
基于C#的软件大杂烩(2.1)——MQTT助手
简单介绍 MQTT(消息队列遥测传输)是ISO 标准(ISO/IEC PRF 20922)下基于发布/订阅范式的消息协议。在之前的文章中我们也介绍过好几次使用MQTT:ESP32物联网教程之MQTT(Ardunio IDE)      百度云物联网之MQTT协议创建设备及获取信息....... 本期我们使用C#制作一个MQTT助手,可以实现指定MQTT设备的连接,主题的订阅与取消,消息的接收与发布。 我们简单的设置一下我们的界面,设置了四个文本框用来获取我们的连接信息。 接着我们在NuGet管理包中下载我们的M2MQTT库。 基础部分 在我们的类的顶部使用using 包含我们的库。using uPLibrary.Networking.M2Mqtt; using uPLibrary.Networking.M2Mqtt.Messages; 接着我们在点击连接的按钮中使用Client的Connect方法连接我们的MQTT服务端 private void Conect_Click(object sender, EventArgs e) { if (Conect.Text == "连接") { string username = UserText.Text; string password = PassText.Text; string clientName = NameText.Text; string address = AddressText.Text; client = new MqttClient(address); client.Connect(clientName, username, password); Console.WriteLine("Connected to MQTT as " + clientName); Conect_Click_Connect_Change(); } else { Conect_Click_Closed_Change(); } } private void Conect_Click_Connect_Change() { UserText.Enabled = false; NameText.Enabled = false; AddressText.Enabled = false; PassText.Enabled = false; Conect.Text = "断开连接"; } private void Conect_Click_Closed_Change() { UserText.Enabled = true; NameText.Enabled = true; AddressText.Enabled = true; PassText.Enabled = true; Conect.Text = "连接"; client.Disconnect(); } 并且点击连接之后禁用我们的其他控件。 关于MQTT连接信息可以参考公众号的这篇文章: 百度云物联网之MQTT协议创建设备及获取信 接着我们运行一下我们的程序: 我们可以看到我们的MQTT成功进行了连接。 优化 接着我们优化完善一下我们的布局: 我们添加几个文本框还有按钮用来设置我们的订阅信息包括主题还有安全性等等(实际上我们这个库比较简单,这个安全性没啥子用)并且我们在成功连接之前我们禁用这些控件。 private void AddTopic_Click(object sender, EventArgs e) { //弹出输入框输入主题 var userInput = GetUserTopicInput(); string topic = userInput.topic; byte qos = userInput.qos; // 首先检查用户输入是否为空 if (string.IsNullOrEmpty(topic)) { MessageBox.Show("主题不能为空"); return; } // 判断TopicBox中是否已包含该主题 if (TopicBoxtems.Contains(topic)) { MessageBox.Show("主题 " + topic + " 已经存在"); return; } // 如果不存在就订阅并添加到TopicBox中 //client.Subscribe(new[] { topic }); byte[] byteArray = Encoding.UTF8.GetBytes(SubQOS.Text); try { string[] topics = { topic }; client.Subscribe(topics, byteArray); client.MqttMsgPublishReceived += Client_MqttMsgPublishReceived; MessageBox.Show("订阅成功"); } catch (Exception ex) { MessageBox.Show("订阅失败"); } TopicBox.tems.Add(topic); TopicBox.SelectedIndex = 0; TransTopic.Items.Add(topic); } private void UpdateReciveText(string message) { if (ReciveText.InvokeRequired) { ReciveText.Invoke(new Action[removed](UpdateReciveText), message); } else { // 在 UI 线程上更新 ReciveText 控件 ReciveText.AppendText(message + "\r\n"); } } // 在其他地方调用这个方法,传递要显示的消息 private void Client_MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e) { // 收到发布的消息时执行 string topic = e.Topic; // 主题 byte[] payload = e.Message; // 消息负载 // 处理消息... UpdateReciveText("收到主题: " + topic + "\r\n" + "消息: " + Encoding.UTF8.GetString(payload)); Console.WriteLine("收到主题: " + topic); Console.WriteLine("消息: " + Encoding.UTF8.GetString(payload)); } private (string topic, byte qos) GetUserTopicInput() { string topic = Interaction.InputBox("请输入Topic:", "输入", ""); byte qos = 0; return (topic, qos); } 这部分代码包括弹出提示框用来输入我们的主题,判断这个主题是否已经被订阅以及订阅我们的主题。 之后设置我们的回调函数用来处理MQTT接收到的信息。 效果展示 运行程序,输入用户名以及密码,之后使用百度云的MQTT服务器端我们是可以接收到数据(图片欠着)。
实在太懒于是不想取名
0 4 嘉立创PCB
基于C#的软件大杂烩(1.3)——串口助手的优化
前端时间做的串口示波器做了许多优化,其代码量也到达了将近2000行 在原本的基础上做了很多内容的增加,私信回复《串口助手》即可获取发布链接(还有很多内容尚未完善),欢迎朋友们提出改进意见以及点赞分享。 包括但不限于: 控件提示:将鼠标悬停于控件上可以提示相关控件信息。 接收设置优化: 利用定时器更新UI控件防止当串口收发速度过快的时候导致UI进程被占用无法使用。 串口详细信息显示: 显示串口的详细信息包括蓝牙,CH340等详细内容的显示,方便我们在连接蓝牙设备,CH340设备的多串口选择。 热拔插刷新:在使用串口的过程中拔插串口或者在软件启动后加入新的串口时会刷新串口列表以及对状态进行修改。 布局优化:使用Panel容器优化界面布局,是的波形显示和接收显示界面分开可以自由来回切换。 波形显示逻辑: 修改波形显示逻辑,我们可以在使用串口的同时选择是否开启波形显示(之前是在打开串口前进行选择) 滚轮控制显示区域:增添了利用滚轮控制波形的显示范围,之前只能利用滑条来控制区域。 傅里叶变换:增加了对指定区域的傅里叶变换功能,将时域图转换成频域图。 保存图表: 可以保存我们的波形图到我们的文件夹中。 数据统计: 增加了统计波形显示区域内的最大值与最小值(后续可能更新更多的统计数据) 此外还有许多地方尚未完成,例如自定义数据帧解析,多行发送,高速模式下时间戳的使用等等~欢迎大家提出建议,会多多吸取意见并修改。 以下展示部分源码: private void Chart1_MouseEnter(object sender, EventArgs e) { chart.MouseWheel += Chart1_MouseWheel; } private void Chart1_MouseLeave(object sender, EventArgs e) { chart.MouseWheel -= Chart1_MouseWheel; } private void Chart1_MouseWheel(object sender, MouseEventArgs e) { try { if (e.Delta [removed] 0) // 向上滚动 { // 减小 chartLength 的值,可以根据需要调整减小的步长 ChartLenth -= 100; if (ChartLenth [removed] 0) { using (SaveFileDialog saveFileDialog = new SaveFileDialog()) { saveFileDialog.Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*"; saveFileDialog.DefaultExt = "png"; saveFileDialog.Title = "Save Chart Image"; if (saveFileDialog.ShowDialog() == DialogResult.OK) { SaveChartImage(chart, saveFileDialog.FileName); MessageBox.Show("图表已成功保存为图片!", "成功", MessageBoxButtons.OK, MessageBoxcon.Information); } } } } private void SaveChartImage(Chart chart, string filePath) { try { // 使用 SaveImage 方法保存图表为图片 chart.SaveImage(filePath, ChartImageFormat.Png); } catch (Exception ex) { // 处理保存异常 MessageBox.Show("保存图片时发生错误:" + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxcon.Error); } } 图片保存部分的代码,启用资源管理器来获取我们的保存信息,之后利用chart控件自带保存控件的方法来保存我们的控件。 private void FuChange_Click(object sender, EventArgs e) { int Xmax = int.Parse(MAXArea.Text); int Xmin = int.Parse(MinArea.Text); if (Xmax < Xmin || Xmin < 0) { MessageBox.Show("请输入正确的范围"); return; } if (chart.Series.Count == 0) { MessageBox.Show("图表中没有可用的系列"); return; } // 获取选定范围内的数据点 List[removed] selectedDataPoints = new List[removed](); foreach (DataPoint dataPoint in chart.Series[0].Points) { if (dataPoint.XValue >= Xmin && dataPoint.XValue <= Xmax) { selectedDataPoints.Add(dataPoint); } } // 将数据点转换为复数数组 Complex[] complexData = new Complex[selectedDataPoints.Count]; for (int i = 0; i < selectedDataPoints.Count; i++) { complexData[i] = new Complex(selectedDataPoints[i].YValues[0], 0); } // 执行傅里叶变换 Fourier.Forward(complexData, FourierOptions.Matlab); // 将结果显示在 FuChart 上 FuChart.Series.Clear(); WindowsFormsSeries fuSerial = new WindowsFormsSeries("傅里叶变换结果"); fuSerial.ChartType = SeriesChartType.FastLine; fuSerial.BorderWidth = 3; FuChart.Series.Add(fuSerial); // 计算频率间隔 double frequencyInterval = 1.0 / (selectedDataPoints[1].XValue - selectedDataPoints[0].XValue); // 显示正频率部分 for (int i = 0; i [removed]
实在太懒于是不想取名
1 13 嘉立创PCB
基于C#和ESP32的远程示波器(6)—— 合理的利用数据缓存区来消除数据丢失
啊~我的天!好久没更新这个系列了,主要是在琢磨怎么彻底的消除数据不连续的问题。 这个星期从硬件到软件到通讯协议,试过了很多方法来解决数据,不连续的问题。光通讯协议就用了HTTP,UDP,WebSockerts以及TCP还为每一个通讯协议专门写了数据处理的代码和示波器(每天写到吐了、无数的BUG和Error) 最后我们解决的方法还是:定时器控制采样率、HTTP将数据打包上传、利用标志位来交替写入缓存区。 上期问题阐述与解决 我简单的阐述一下上一期的代码出现的问题: 首先是我们在任务中运行ADC采集,这样子会导致我们无法控制我们的采样率,这样子对于信号采集来说是不应该出现的。 其次我们的数据在Data = PostData中会有一个数据传递的过程,又可以造成不必要的耗时。 因此我们选择的方法是使用定时器来定时触发我们的ADC采样。 并且使用两个数据缓存区PostData1和PostData2,定时器交替向这两个缓存区写入数据。 主要代码 我们首先加入定时器的头文件(利用的也是FreeRTOS)以及声明其句柄。#include [removed] TimerHandle_t timer; 接着在主函数中加入定时器的配置timer = xTimerCreate("DataTimer", pdMS_TO_TICKS(2), pdTRUE, NULL, onTimer); xTimerStart(timer, 0); 该定时器的名字命名为:DataTimer,2ms触发一次,自动装填,回调函数为onTimer 接着我们定义我们的回调函数的内容。int QueueFlag = 1; void IRAM_ATTR onTimer(TimerHandle_t xTimer) { static int j = 0;   int ADCVlaue[4]; for (int i = 0; i [removed]
实在太懒于是不想取名
0 2 嘉立创PCB
基于C#的软件大杂烩(1.2)——串口示波器
上一期我们实现了串口助手收发数据之后,这期我们为我们的串口助手加上我们的波形图功能。 我们在前面有一期中介绍如何在C#中使用chart画图库 本期我们利用我们上一期制作的串口助手来实现我们的串口示波器。 准备工作 首先我们在上一期的基础上加上我们的chart库using System.Windows.Forms.DataVisualization.Charting; 其次,简单的布置一下我们的界面。 其中图表和按钮的默认状态是隐藏的,当我们勾选波形显示时显示。 private void ChartShow_CheckedChanged(object sender, EventArgs e) { if (ChartShow.Checked == true) { Receive.Height = 680/2; chart.Show(); ClearChart.Show(); } else { Receive.Height = 680; chart.Hide(); ClearChart.Hide(); } } 我们勾选按钮的时候,将接收区的高度减少一半,让按键和图表显形。 数据解析与存放 我们希望我们的数据格式是类似“a:xxx,b:xxx”等等。因此,我们需要解析我们的数据private Dictionary[removed] ParseData(string input) {             Dictionary[removed] result = new Dictionary[removed](); // 按逗号分割字符串             string[] pairs = input.Split(','); foreach (string pair in pairs) { // 再按冒号分割键值对                 string[] keyValue = pair.Split(':'); if (keyValue.Length == 2) { string key = keyValue[0].Trim();                     string value = keyValue[1].Trim(); result[key] = value; } } return result; } 我们定义一个解析键值对函数,利用逗号分隔字符串,冒号分隔将解析的结果返回,这样子我们就可以获取我们的键值对。 // 添加解析数据的代码 string[] lines = data.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); lines = lines.Skip(1).Take(lines.Length - 2).ToArray(); 由于我们的数据可能有很多行,因此我们在更新图表的代码中需要将数据按照换行符分开,并且由于第一行和最后一行的数据可能不完整,我们需要删除第一行和最后一行的数据。foreach (string line in lines) {     // 对每一行进行解析     Dictionary[removed] parsedData = ParseData(line);     foreach (var kvp in parsedData) {        // 继续处理解析后的数据       int existingIndex = chart.Series.IndexOf(kvp.Key);       if (existingIndex != -1)       {          //如果该系列的图表存在           chart.Series[existingIndex].Points.AddXY(chart.Series[existingIndex].Points.Count, double.Parse(kvp.Value));      } else { //如果该系列的图表不存在 string seriesName = new string(kvp.Key.TakeWhile(char.IsLetter).ToArray()); // 获取字符串中的所有字母前缀 Series newSeries = new Series(seriesName); newSeries.ChartType = SeriesChartType.Spline; // 设置图表类型为 Spline newSeries.Points.AddXY(0, double.Parse(kvp.Value)); chart.Series.Add(newSeries); } } } 我们将我们解析的数据放入图表,按照数据的前缀来划分数据。 判断图表是否存在,如果不存在就添加相应的系列。 效果展示 接着让我们看看效果
实在太懒于是不想取名
1 9 嘉立创PCB
基于C#的软件大杂烩——手搓串口助手
作为一个有着伟大理想,崇高信念21青年,在我平时用着串口助手、计算器的时候。我总是寻思着,我能不能自己去实现做一个软件,能实现我日常想要做的功能,例如串口助手啦,图片助手啦,文件转换啦等等。 于是打算从0.5开始手搓一个大杂烩软件。 首先的工具我就想到了串口助手,那么本期我们就介绍如何使用C#制作串口助手。相关库安装 首先,我们需要在NuGet包管理中,找到我们的关于端口的包 我们搜索这个包并且安装这个包,这样子我们就可以在C#中调用我们的端口类了。 页面布局 通常我们的串口助手最基本的数据接收和发送,打开和关闭串口以及选择端口设置波特率等等,所以我们需要若干个TextBox文本框,Label标签以及Button按钮来实现我们的基本功能最后还需要有一个下拉式的ComboBo来选择我们的串口 我们在左边的空间库中寻找我们需要的控件,放置到我们的设计器中。 基础编写private void LoadAvailablePorts() { // 获取可用端口 string[] ports = SerialPort.GetPortNames(); // 将端口添加到ComboBox中 COMChoose.Items.AddRange(ports); // 如果需要,默认选择第一个端口 if (COMChoose.Items.Count > 0) { COMChoose.SelectedIndex = 0; } } 我们首先编写获取端口的代码,并且在FromLoad函数中调用这个代码,这样子我们就可以加载端口显示在下拉框中CheckBit.SelectedIndex = 0; 我们在这些选项框中使用默认第一个数据。private void Connect_Click(object sender, EventArgs e) { if (serialPort.IsOpen) { // 串口已经打开,关闭串口 serialPort.Close();         Connect.Text = "打开串口"; // 启用控件 COMChoose.Enabled = true; PortText.Enabled = true; DataBit.Enabled = true; StopBit.Enabled = true; CheckBit.Enabled = true; } else { // 串口未打开,尝试打开串口 try { // 配置串口 serialPort.PortName = COMChoose.SelectedItem.ToString(); serialPort.BaudRate = int.Parse(PortText.Text); // 获取波特率 serialPort.DataBits = int.Parse(DataBit.SelectedItem.ToString()); // 获取数据位 // 设置停止位 switch (StopBit.SelectedItem.ToString()) { case "1": serialPort.StopBits = StopBits.One; break; case "1.5": serialPort.StopBits = StopBits.OnePointFive; break; case "2": serialPort.StopBits = StopBits.Two; break; default: serialPort.StopBits = StopBits.One;              break;      } // 设置校验位 switch (CheckBit.SelectedItem.ToString()) {      case "None": serialPort.Parity = Parity.None;             break; case "Odd": serialPort.Parity = Parity.Odd; break; case "Even": serialPort.Parity = Parity.Even; break; default: serialPort.Parity = Parity.None; break;      }      // 打开串口 serialPort.Open();      Connect.Text = "关闭串口";      // 禁用控件 COMChoose.Enabled = false; PortText.Enabled = false; DataBit.Enabled = false; StopBit.Enabled = false;      CheckBit.Enabled = false; serialPort.DataReceived += DataReceivedHandler; } catch (Exception ex) {        // 打开串口失败,显示错误消息 MessageBox.Show("串口打开失败" + ex.Message); } } } 我们在点击按钮的时候设置的代码是:点击按钮改变按钮的文本,并且配置按钮的各项数据,禁用/开启端口选择..等等的使用以及创建新的回调函数(DataReceivedHandler)在这个函数中编写我们收到的代码信息。private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e) { try   { // 从串口读取数据 string data = serialPort.ReadExisting(); // 将数据追加到 Receive 文本框中 UpdateReceiveTextBox(data); } catch (Exception ex) { MessageBox.Show("接收失败" + ex.Message); } } private void UpdateReceiveTextBox(string data) { if (Receive.InvokeRequired) { // 如果不在 UI 线程上,通过 Invoke 在 UI 线程上更新文本框 Receive.Invoke(new Action[removed](UpdateReceiveTextBox), data); } else { // 在 UI 线程上更新文本框 Receive.AppendText(data); } } 我们在串口回调函数中简单的添加一些内容,我们将收到的串口打印到我们的TextBox上(UpdateReceiveTextBox这个函数也是我们自己定义的)作用是用来区分当前程序是不是在UI线程上,如果不在UI线程上,我们需要用另一种方式来更新我们的控件。 接收串口数据成功,我们也添加了时间戳的功能,用来为我们的代码接收添加时间。 // 在 UI 线程上更新文本框  if (TimeTik.Checked)  {   // 如果 TimeTik 被勾选,加上时间戳 data = $"{DateTime.Now:HH:mm:ss} -> {data}"; } Receive.AppendText(data); 接着我们来编写发送部分的代码private void TransBotton_Click(object sender, EventArgs e) { try    { // 获取要发送的文本    string dataToSend = TransText.Text;    // 判断是否勾选了 NewLine 复选框 if (NewLine.Checked) { // 添加新行      dataToSend += Environment.NewLine;  ‍‍          ‍ ‍ ‍‍‍‍‍ }‍‍‍ ‍ // 发送数据 serialPort.Write(dataToSend); } catch (Exception ex) {     MessageBox.Show("串口发送失败");     } } 其实这个东西运行起来还是有不少BUG的,需要我们不断的优化,感兴趣的朋友可以后台私信联系。
实在太懒于是不想取名
3 8 嘉立创PCB
基于C#和ESP32的远程示波器(5)—— 利用FreeRTOS双核运行减少数据丢失
在我们上一期代码中,我们利用HTTP将我们的数据上传到局域网,但是由于我们的程序是串行程序,即ADC采集完需要等待数据上传成功才能继续运行我们的代码,因此我们的数据就会出现很严重的数据丢失。 而我们在之前学习过STM32中的FreeRTOS即实时操作系统,将不同的任务按照不同的优先级分别运行。因此我们尝试使用FreeRTOS将ADC采集和数据上传的任务按照不同级别的优先级进行划分。 关于FreeRTOS的介绍可以查看前几期的内容基于STM32F407的FreeRTOS学习笔记(13)—— 阶段性的总结与后续 FreeRTOS库准备 FreeRTOS在ESP32中的环境配置比在STM32中简单许多,我们只需要在库管理中搜索FreeRTOS即可安装我们的FreeRTOS 我们直接安装最新版本的FreeRTOS#include[removed]#include [removed] #include[removed]#include [removed]//FreeRTOS头文件 #include [removed]//任务头文件 #include [removed]//信号量头文件 之后我们放入相关头文件包含信号量和任务头文件还有FreeRTOS的基本头文件。 任务创建 任务创建可以参考我们的公众号文章基于STM32F407的FreeRTOS学习笔记(2)——任务的创建与删除TaskHandle_t Task1, Task2; xTaskCreatePinnedToCore( Task1Code, // 任务函数 "Task1", // 任务名称 10000, // 任务堆栈大小 NULL, // 任务参数 1, // 任务优先级 &Task1, // 任务句柄 0); // 核心编号 // 创建任务2,绑定到核心1 xTaskCreatePinnedToCore( Task2Code, // 任务函数 "Task2", // 任务名称 10000, // 任务堆栈大小 NULL, // 任务参数 2, // 任务优先级 &Task2, // 任务句柄 1); // 核心编号 我们创建任务1和任务2 的句柄,并且由于ESP32是双核的MPU,比起STM32我们可以将不同的任务绑定到不同的核心进行使用,我们设置两个任务,并且任务1的优先级大于任务2的优先级。 我们将任务1用于ADC采集,任务2用于数据上传。String postData = ""; String Data; int dataArray[1000]; // 任务1的函 void Task1Code(void *parameter) { for (;;) { for (int i = 0; i < 1000; i++) { dataArray[i] = analogRead(34); postData += String(dataArray[i]); if (i [removed] 0) { Serial.print("HTTP Response code: "); Serial.println(httpResponseCode); String response = http.getString(); Serial.println(response); } else { Serial.print("HTTP Request failed. Error code: "); Serial.println(httpResponseCode); } } delay(1000); } } 接着我们定义我们的任务函数,关于HTTP的部分参考上一期公众号内容。 我们每次采集好数据之后释放信号量,任务2等待信号量的释放,当任务1释放信号量之后我们上传我们的数据,需要注意的是,此时任务2上传数据时postData依旧在进行改变,所以我们需要用一个新的变量来接受PostData。 效果展示 我们来看看我们的运行效果
实在太懒于是不想取名
1 5 嘉立创PCB
基于C#和ESP32的远程示波器(4)—— ESP32在局域网中利用HTTP协议代替MQTT协议
上一期我们使用MQTT协议连接到百度云并且向C#的客户端上传了我们的数据。 我之后也成功使用ESP32上传数据到MQTT服务器,在C#中显现折线图,但是随之而来的我们发现了新的问题:这消息量好像有点庞大了 短短的测试环节我就发送了将近20w条消息(幸亏我关的早),于是当机立断选择放弃使用MQTT协议,改用其他协议来传输数据。 MQTT协议适合小型数据量的传输,那么对于我们这么庞大的数据量来说确实不太适合,于是我们选择常用的超文本传输协议:HTTP来传输我们的数据。 HTTP简单介绍 HTTP由三个部分组成: 起始行(start line):描述请求或响应的基本信息; 头部字段集合(header):使用 key-value 形式更详细地说报文; 消息正文(entity):实际传输的数据,它不一定是纯文本,可以是图片、视频等二进制数据。 起始行中我们需要确定我们的HTTP是进行请求还是发送。 这里不细细赘述如何具体使用HTTP报文的详细,我们简单的介绍一下在ESP32中如何使用HTTP。 HTTP发送 首先,我们在Arduino IDE中安装我们的HTTP相关的库,我们在库管理中搜索HttpClient安装相关库。 其次按win+R调出命令窗口,输入cmd并运行调出终端面板,之后输入ipconfig查询我们的电脑IP 在IPv4的地址中(任意一个)查看我们的局域网IP地址 我们在ESP32中保存我们的Wifi用户名,Wifi密码还有我们刚才的IP地址,注意加上http://还有确定我们使用的端口号,我们使用8081端口,最后一定一定记得加上斜杠void WIFIConnect() { WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi..."); } Serial.print("IP Address: "); Serial.println(WiFi.localIP()); Serial.println("Connected to WiFi"); } 我们定义一个函数来连接我们的Wifi,因为我们使用的ESP32的定时器2,使用Wifi的时候不能 使用ESP32的ADC2,因此我们使用ADC2的时候我们需要关闭我们的Wifi。 String postData = ""; for (int i = 0; i < 1000; i++) { dataArray[i] = analogRead(13); postData += String(dataArray[i]); if (i [removed] ListenForHttpRequests()); } 接着我们定义我们的监听函数,我们在监听函数中解析我们的数据。 private void HandleHttpRequest(HttpListenerContext context) { try { // 从请求中获取数据 Stream body = context.Request.InputStream; Encoding encoding = context.Request.ContentEncoding; StreamReader reader = new StreamReader(body, encoding); if (context.Request.HasEntityBody) { string postData = reader.ReadToEnd(); Console.WriteLine($"Received data from ESP32: {postData}"); // 将以逗号分隔的多个数值解析为数组 string[] values = postData.Split(','); Invoke((MethodInvoker)delegate { for (int i = 0; i [removed]
实在太懒于是不想取名
3 7 嘉立创PCB
基于C#和ESP32的远程示波器(3)—— 使用C#连接MQTT百度云服务器
上期我们介绍了C#中的折线工具,本期我们介绍C#中利用MQTT协议连接baidu服务器。在使用MQTT之前,我们需要获得我们的MQTT服务器地址和相关信息,往期有介绍关于如何使用百度云创建MQTT服务器并获取连接信息的文章,可以参考百度云物联网之MQTT协议创建设备及获取信息。关于ESP32连接MQTT的部分可以参考下的(哥们能不水就不水)ESP32物联网教程之MQTT(Ardunio IDE)但是有一点要注意的是,WIFI的使用会占用ADC2导致ADC2不能用,所以不能使用ADC2,或者先采集数据,再连接WIFI,再上传数据,之后再断开连接·相关库在C#中有许多库是可以使用MQTT的,使用较多的有MQTTnet,M2Mqtt等,我们选择的是M2Mqtt库(MQTTnet我没用明白)我们打开NuGet程序包管理。在其中搜索M2Mqtt安装最新版即可。连接MQTT服务器接着我们在代码中连接我们的MQTT服务器。首在全局中创建我们的MQTT句柄,用以连接我们的MQTT服务器。接着加入如下代码,连接MQTT服务器并且配置我们的回调函数 private void InitializeMqttClient() { // 服务器地址 string brokerAddress = "你的服务器地址"; // 你的用户名和密码 string username = "你的用户名"; string password = "你的密码"; // 创建MqttClient实例 mqttClient = new MqttClient(brokerAddress); // 设置用户名和密码 mqttClient.Connect(Guid.NewGuid().ToString(), username, password); // 订阅主题 string topic = "你的主题"; mqttClient.Subscribe(new string[] { topic }, new byte[] { MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE }); // 设置接收消息的回调函数 //这个Client_MqttMesgPublishReceived是我自己定义的 mqttClient.MqttMsgPublishReceived += Client_MqttMsgPublishReceived; Console.WriteLine($"Connected to {brokerAddress} with username {username}"); } 服务器的地址用户名和密码,参考开头的那期有讲解如何获取相关信息的。接着我们在Form函数中调用这个函数,这样子我们在窗口加载的时候就可以连接到我们的MQTT服务器 public Form1() { InitializeComponent(); InitializeMqttClient(); } 接着我们点击运行,前往百度云的后台查看是否有设备连接并运行。首先我们可以看到我们的控制台提示:我们已经连接到了MQTT服务器,并且打印了服务器地址以及用户名。之后在运维管理中,我们也可以看到我们当前有一个设备正在使用。接收MQTT消息并显示连接好设备之后,我们需要编写我们的相关回调函数,我们之前定义了我们的回调函数这里要对其做具体定义。我们在上一期的时候介绍了如何使用chart控件显示折线图,我们在消息回调函数中,利用chart控件,将我们的信打印出来。 private void Client_MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e) { // 处理接收到的消息 string message = Encoding.UTF8.GetString(e.Message); number++; Console.WriteLine(message); // 在图表中添加新数据点 this.Invoke((MethodInvoker)delegate { Series series = chart1.Series[0]; series.Points.AddXY(number,int.Parse(message)); // 如果超过一定数量的数据点,删除最旧的数据点,保持图表显示的数据量有限 if (series.Points.Count > 50) { series.Points.RemoveAt(0); } }); } 这里我还定义了一个number变量,用以确定折线图的下标。需要注意的是,我们收到的数据应该使用Encoding.UTF8.GetString方法转化成字符串否则会报错。接着我们在百度云中,创建一个新的实例,然后模拟设备,进入MQTT服务端我们需要订阅和我们程序中连接的服务端一样的主题,之后设置我们的消息,点击发布(Publish)可以看到我们成功的发布了消息。同样的我们C#中的服务器也接收到了消息并且在折线图中打印了出来。这样子我们就成功的让我们的C#服务器连接到MQTT云平台啦。
实在太懒于是不想取名
0 6 嘉立创PCB
基于STM32系列的气压传感器MS5611——获取温度数据和气压数据
 MS5611是一款由瑞士公司MEAS推出的高分辨率气压传感器。它具有以下特点:接口:支持SPI和I²C总线接口。(通过PS脚来选择)内部结构:包括高线性度的压力传感器和超低功耗的24位累加模数转换器(工厂校准系数)。功能:提供精确的24位数字压力值和温度值,适用于高度计、温度计等功能。尺寸:仅有5.0毫米×3.0毫米×1.0毫米的小尺寸,适合集成在移动设备中。    这款传感器采用领先的MEMS技术,具有高稳定性和非常低的压力信号滞后。它在移动高度计、气压计系统、自行车电脑、智能手机等领域有广泛的应用准备工作由于芯片上支持IIC通讯,因此我们打算使用STM32的硬件IIC,我的STM32为STM32F407。我们打开CUBEMX,选择好我们的时钟还有其他设置之后,我们打开我们的硬件IIC并且我们打开串口来进行调试,我们选择IIC1,可以发现是PB6/PB7。并且我们在USART.C文件中加入我们的串口重定向代码,方便我们使用printf函数struct __FILE { int handle; }; struct __FILE __stdout; //定义_sys_exit()以避免使用半主机模式 void _sys_exit(int x) { x = x; } //重定义fputc函数 int fputc(int ch, struct __FILE *f) { while((USART1->SR&0X40)==0);//循环发送,直到发送完毕 USART1->DR = (unsigned char) ch; return ch; } 利用这串代码,我们就可以把我们的串口发送函数重定向到我们的printf函数上。之后我们测试一下,我们的串口发送。我们在MS5611的官方手册中可以看到,MS5611的数据主要由C1~C6这六个校准系数以及D1,D2两个数据组成。我们在向MS5611发送数据转换命令的时候,模块会把对应的信号转换好之后存入PROM寄存器中,我们需要读取PROM的数据来读取这些数据,PROM的地址从0XA0~0XAE 代码编写#define MS5611_I2C_ADDR 0xEE //PS拉高的地址 #define CMD_ADC_READ    0x00 //ADC转换开始 #define CMD_ADC_CONV_D1 0x40 //D1读取命令 #define CMD_ADC_CONV_D2 0x50 //D2读取命令 #define CMD_PROM_RD 0xA0 //PROM基地址 typedef struct {     double temperature;//温度     double pressure;//气压 uint32_t D1; uint32_t D2; uint16_t C1; uint16_t C2; uint16_t C3; uint16_t C5; uint16_t C6; } MS5611Data; 首先我们定义模块的命令宏定义以及我们的结构体用来存放我们的数据。extern I2C_HandleTypeDef hi2c1; void ReadMS5611Data(MS5611Data *ms5611Data); 其次定义我们的hi2c1句柄,由于这个句柄的实际定义是在i2c.hd文件中,因此我们需要加上extern来修饰我们的变量接着我们来定义我们的ReadMS5611Data函数,我们分步讲解。 HAL_I2C_Mem_Write(&hi2c1, MS5611_I2C_ADDR, CMD_ADC_CONV_D1, I2C_MEMADD_SIZE_8BIT, NULL, 0, 100); HAL_Delay(10); uint8_t buffer[3]; HAL_I2C_Mem_Read(&hi2c1, MS5611_I2C_ADDR, CMD_ADC_READ, I2C_MEMADD_SIZE_8BIT, buffer, 3, 100); ms5611Data->D1 = ((uint32_t)buffer[0] << 16) | ((uint32_t)buffer[1] <[removed]D2 = ((uint32_t)buffer[0] << 16) | ((uint32_t)buffer[1] << 8) | (uint32_t)buffer[2]; 这部分也是同理,我们用这个方式获取D2的值。uint8_t coef_data[2]; for (int i = 1; i [removed]C1 = (coef_data[0] <[removed]C2 = (coef_data[0] <[removed]C3 = (coef_data[0] <[removed]C5 = (coef_data[0] <[removed]C6 = (coef_data[0] <[removed]D2 - ((int32_t)ms5611Data->C5 *256); double temp = 2000+((double)dt/ 8388608)*ms5611Data->C6; 接着我们按照手册中的方法计算我们的数据,需要注意的是,temp的类型最好选择是double型,否则dt/8388608(1<[removed]C2 * 65536) + ((int64_t)ms5611Data-> * dt) / 128; int64_t sens = ((int64_t)ms5611Data->C1 <[removed]C3 * dt) / (1 <[removed]D1 * sens/2097152 - off) / (1 <[removed]
实在太懒于是不想取名
2 11 嘉立创PCB
基于C#和ESP32的远程示波器(1)—— C#的简单介绍与使用
最近在做一个项目,关于远程采集数据并上传的一套装置, 为此也决定做一期合集用以记录制作流程。 关于硬件方面我选择了ESP32,ESP32有双核240MHZ的主频,并且自带ADC以及DAC,非常方便我们的数据采集与云端上传。 关于上位机方面,我选择了C#,C#作为基于C语言的图形化编程语言,可以有效的帮助我们编写相关的上位机。我也是曾经使用C#完成过一个远程LED点阵控制系统的制作。 本期我们的主要内容是介绍一下C#的简单使用。C#环境 我所使用的C#是在Visual Studio中安装的, 在创建项目中,我们在右边选项中拉到底,点击安装多个工具和功能既可安装我们的C#环境 选择通用Windows平台开发,安装即可。 简单使用 我们创建工程时,选择Windows窗体应用。 进入工程界面,有我们的窗体,左边的工具栏,右边的项目资源管理器。 我们可以从左边的工具栏选择常用的工具到我们的窗体上。 右下角则是关于这些控件的属性,例如大小,位置,字体大小,名称等等。 我们放置一个标签和一个按钮,并修改他们的字体大小。 接着我们双击我们的控件就可以进入相应的编写代码,例如我们双击我们的按钮。 我们可以看到这时候我们创建了一个函数,函数的名称是button1_Click,我们可以理解为这个函数是我们按下按钮后调用的函数,我们在这个函数中编写,标签的值改变。 再点击上方的运行按钮。 这时候就会跳出我们的窗口了。 然后我们点击Hello按钮。 我们可以发现标签的文本发生了改变。 这样子就简单的使用了C#,C#中有很多强力的工具和控件,我们合理的使用这些控件可以很好的帮助我们实现上位机软件的编程。
实在太懒于是不想取名
2 8 嘉立创PCB
手机也能电路仿真?PE端的仿真软件推荐
电路仿真是我们在学习电路中起到非常大的帮助,好用的电路仿真软件不少例如Multisim,Protues。但是这些软件都是在电脑中使用,那么有没有什么软件能在手机上很好的使用呢? 方法就是:把手机连上电脑 电路模拟器就是很好的一款能在手机上使用的电路仿真软件(私信 "电路模拟器软件 "获取APK仅安卓用户) 打开电路模拟器,我们在工作区中创建新的电路图就可以使用我们的电路模拟器啦。 在电路图界面有许多元器件我为他们一一做了标识以及左下角的启动按钮。 接着我们简单的测试一下,使用信号源还有电阻构成一个分压电路并运行。 连接好电路之后我们点击左下角运行按钮。 之后选择电路结点,左下角会出现“眼睛”符号,我们点击👀,即可观察改点的波形图 接着我们测试其他的电路,例如RC滤波电路。 可以看到,信号中的交流分量在进过RC低通滤波器之后明显的衰减了。 我们再用这个电路模拟一下运算放大器的使用,构建一个同向比例运算放大器。 可以看到,利用同相比例放大电路我们将峰峰值为2V的正弦信号转化成峰峰值为4V的正弦信号。 除此之外,在主页面中软件也给我们提供了许多事例。这些事例展示了相关器件的使用以及一些经典电路图。 例如电流镜就是一个现实中非常有用的一种电路,可以为我们的电路提供精准稳定的恒流源。 以上就是本期我们分享的软件啦,喜欢的朋友记得👍加关注
实在太懒于是不想取名
2 5 嘉立创PCB
友友们有什么开源项目推荐嘛
最近放假回家想做点简单的开源项目玩玩
实在太懒于是不想取名
1 2 嘉立创PCB
CCS下载与安装及MSP432的SDK环境配置
全国大学生电子设计大赛中必然有一道题指定为TI公司的单片机,去年2022年浙江省大学生电子设计大赛中控制题中的双车跟随系统就是指定TI公司的单片机。 我们去年选择的是MSP430单片机,额.....MSP430,不好评价懂得都懂,1MHZ的主频,只能说去年车子能跑完正常的圈子实属奇迹了。 TI公司除了MSP430之外还有许多的单片机,而MSP432单片机更是号称对标32,性能指标超过STM32单片机的一款,因此今年为了预防在仪器仪表题上对我们限制单片机,我们决定采用MSP432P401R型号的单片机,去年我也是接触过这个单片机,因此在编程上也有经验。 编程MSP432我们选择的是CCS,定速巡航系统,Code Composer Studio™ 集成式开发环境 (IDE)。 CCS是官方推荐的编译工具,也可以对芯片一键烧录和运行,MSP432上有板载DAP,所以只需要一根数据线即可调试和运行。 CCS的下载可以去Ti公司的官网(CCSTUDIO IDE、配置、编译器或调试器 | TI.com.cn),在设计和开发中选择CCS,之后选择第二个选项,下载对应的系统版本即可。 下载完成后找到对应的安装文件,双击进行安装,其他步骤与正常软件安装时无异,但是注意的是在选择产品中,一定要勾选MSP432这个选项后再NEXT。 安装完CCS,我们还需要安装MSP432SDK,这样子我们就可以使用官方的例程和库文件了。 在这个链接中SIMPLELINK-MSP432-SDK Software development kit (SDK) | TI.com打开MSP432SDK下载地址(打不开的话去CSDN搜一下相关教程)之后安装MSP432SDK,安装完成后,在安装目录下就会有这些文件,比如example就是官方的例程,source是一些驱动文件等等。 有了这个MSP432SDK,我们就可以借此打开例程,选择example文件,由于我们是裸机开发,因此打开nortos,driverlib文件夹中即是我们可能需要的例程,其中有Keil的例程,也有CCS的例程。 我们打开CCS,在Project中选择import CCS Project ,导入CCS例程。选择我们的例程文件夹,点击finished即可导入工作区完成,之后就可以使用我们的例程了。 可以看到资源管理器中,工作区主要包含着MSP432的系统头文件,启动文件,以及主函数,其中例程的主函数均以该例程的名称命名。
实在太懒于是不想取名
2 9 嘉立创PCB
百度云物联网之MQTT协议创建设备及获取信息
随着科技的不断进步,智能家居的广泛应用与发展,物联网技术在生活及生产中越来越重要。 物联网(Internet of Things,简称IoT)是指通过各种信息传感器、射频识别技术、全球定位系统、红外感应器、激光扫描器等各种装置与技术,实时采集任何需要监控、 连接、互动的物体或过程,采集其声、光、热、电、力学、化学、生物、位置等各种需要的信息,通过各类可能的网络接入,实现物与物、物与人的泛在连接,实现对物品和过程的智能化感知、识别和管理。物联网是一个基于互联网、传统电信网等的信息承载体,它让所有能够被独立寻址的普通物理对象形成互联互通的网络(以上转载自百度百科) 而如何去做物联网,目前网上的教程并不是很多,并且教程时间也都是好几年前的,百度云也更新换代了好多,因此当时的自己摸索了好多,总结了一个使用方法。 MQTT是一种简单常用方便的消息协议,云端设备和云下设备可以在物联网中发布和订阅主题,在主题中发布消息,使得所有订阅该主题的设备均可以收到消息,是一种快捷方便的传输协议。 首先,我们登录百度智能云,在左上角产品目录中,选择物联网 进入界面后选择立即使用,第一次使用物联网核心套件时可能会需要询问付费模式,由于百度智能云每个月前一百万条消息是不会收费的,因此可以选择预收费模式。 之后,我们进入界面后,选择创建IoT Care,创建新的云端设备。 设置好名称,选择按需付费,之后购买产品(只要消息数量不超限制,就不会产生费用)。 点击右边模板,我们选择新建模板来生成我们的设备所需要的协议。 创建好模板后,进入我们创建的模板,除了默认的响应和遗嘱主题之外,我们创建一个新的One(名称任意)主题。模式我们选择高级模式。 返回设备列表,选择新建设备,输入名称后,认证方式选择 密钥认证 ,添加我们刚刚创建的模板后点击创建设备,就会给我们设备的密钥信息。 进入设备后,我们也可以看到我们的设备所有的信息。 下一步我们去生成MQTT服务器所需要的密码和地址。 在百度云的文档中,找到物联网的部分。选择物联网核心套件的文档。在快速入门中,选择“获取连接信息”。 点击蓝色字体,MQTT连接生成器。时间戳填入0,输入我们的设备密钥,一定一定一定注意检查有没有空格,可能我们复制粘贴前,前面会多一个空格,之后就会自动生成MQTT的服务器地址和用户名,我们可以用服务器地址和用户名来链接我们的云上设备,使得设备之间可以通讯。
实在太懒于是不想取名
2 7 嘉立创PCB
HC-05蓝牙模块实现双机通讯详细指南
蓝牙技术在我们的生活中应用广泛,在嵌入式的领域中蓝牙技术也可以为我们的通讯,调试起到巨大的作用,因此学会调试和使用蓝牙模块是不可或缺的一项技能。 HC-05是一种双向全工蓝牙串口通讯模块,可以为用户提供蓝牙通讯的功能。 HC-05具有两种工作模式:命令响应工作模式和自动模式。在自动模式下又可以分为主模式(M),从模式(S)以及回环模式(L),在自动模式下,模块将以提前设定好的设置进行工作。 在命令响应工作模式下,模块可以执行所有的AT指令,用户可以利用AT指令向模块发出命令获得模块各式信息以及修改模块各项参数。 如下图连接蓝牙模块,供电选择3.3V,防止电压过高烧坏芯片。 按住蓝牙模块上的EN按钮上电,进入AT指令模式,如果蓝牙模块上的灯慢闪,则进入AT模式成功,此时将串口波特率调制38400,发送“AT”,并且勾选“发送新行”,其他型号的蓝牙模块有些可能不需要勾选发送新行,具体应当查看手册。 正确进入AT模式后,我们发送AT,串口会接收到OK。即AT模式进入成功,我们此时对可以查看AT指令表对蓝牙模块进行配置。 根据AT指令表,我们修改蓝牙模块的名称以及连接密钥,可以看到模块返回的是OK,查询模块波特率,发现其波特率是9600。 此时我们在手机上下载蓝牙串口助手(应用商店搜索即可),下载完蓝牙串口助手后,连接蓝牙并发送数据。注意的是此时我们应该重新对模块上电,使其进入工作模式。 我们利用串口助手,发送ASCLL码为30 31 32 33 34(对应字符为“01234”),观察PC端上蓝牙接收的数据。 当使用两个蓝牙模块互相连接时,我们需要设置主机与从机,我们让蓝牙模块进入AT模式,输入AT+ROLE?此模块当前主从模式,发现当前模式属于从模式(1:主模式,0:从模式,2:回环模式)。 为了两个蓝牙可以互相通讯,我们需要将一个蓝牙设置为主模式,另一个蓝牙设置为从模式,我们发送AT+ROLE=1,将此蓝牙设置为主模式。 我们按照上述的步骤,将另一块蓝牙模块设置为从机。 利用蓝牙模块连接蓝牙模块时,我们采用的是地址链接,在其中一块蓝牙模块上,我们输入AT+ADDR?查询从机蓝牙的地址。 在主机蓝牙中,利用AT+BIND=xxxx,xx,xxxxxx 注意的是利用逗号分开,设置主机蓝牙需要绑定的地址,并使用AT+CMODE=0,将蓝牙设置为指定地址链接模式。 此时断开电源,重新上电,两块蓝牙几秒后进入慢闪,即表示两块蓝牙配对成功,此时我们利用串口助手分别发送数据即可实现两块蓝牙之间的互相通讯。 双机通讯成功。
实在太懒于是不想取名
2 10 嘉立创PCB
AD9854数字频率合成器
DDS信号发生器在电子设计大赛中应用十分广泛,而AD9854则是一块功能强大,用途广泛的数字频率合成器。 AD9854内部包括一个具有48位相位累加器、一个可编程时钟倍频器、一个反sinc滤波器、两个12位300MHz DAC,一个高速模拟比较器以及接口逻辑电路。 如图可以看出,AD9854支持100MHZ并行编程和10MHZ串行编程,由于我们使用STM32单片机是利用模拟SPI串行写入数据,因此需要注意的是我们查看的寄存器应当是串行寄存器。 手册中提到,我们需要将SP引脚接到GND,使得芯片工作在串行编程模式。并且A0,A1作为SPI的SDIO和SDO。 我们将A0,A1,A2以及其他控制引脚接入单片机,并将IO口设置成输出模式,利用软件模拟SPI,以向AD9854写入命令。 查阅手册寄存器列表,注意的是列表第一列地址是并行模式寄存器地址,列表第二列是串行模式寄存器地址。 观察寄存器列表,其中串行地址7是配置相关启动参数,我们设置成0x00,即打开比较,默认模式后。配置时钟为倍频。 利用 多位相乘算法 ,将十进制的频率转换为4字节方便后续写入频率。 根据芯片手册,将二进制频率数据写入地址0x02频率字段,并将幅度二进制数据分别写入0x08,0x09 ,I通道以及Q通道幅度字段,使得AD9854可以输出频率幅度可调的正弦信号。 之后DDS就可以正常工作了,同样的,需要配置FM,AM,FSK信号也可以按照芯片手册进行相关配置,这里就不作展示了。
实在太懒于是不想取名
1 10 嘉立创PCB
C语言的门牌号——指针
C语言可以说是工科学生必不可少的编程语言了,尤其是嵌入式的应用中C语言更是大厦之基,甚至在我的评价里他相比于数学对于工科生的作用等价。 在学习C语言之初就有一个说法是:学明白了指针,就学明白了C语言。那么什么是指针? 在 计算机 中,所有的数据都是存放在存储器中的,不同的数据类型占有的内存空间的大小各不相同。内存是以字节为单位的连续编址空间,每一个字节单元对应着一个独一的编号,这个编号被称为内存单元的地址。 如果说把存放数据的空间当作我们家的小区,那么地址就像是门牌号,每家每户的地址都是唯一且固定不变的。 此时如果有一名访客来找你,你可以告诉这位访客 你家的地址,也就是门牌号。访客可以直接通过门牌号来找到你。 在C语言中我们可以通过 & 来获得门牌号也就是变量的地址。并且可以通过 * 去访问这个地址,并进行修改,这叫做引用 与 解引用。 当然也有除了直接寻找门牌号之外,还有一种方式找到你家——问路当你到你朋友的小区时,你不知道朋友住哪里,但是朋友刚刚玩完钢铁雄心没注意时间现在睡着了,那么你可能会询问门口保安:NT的家住哪?保安会说:NT住某某幢某零某。此时这个NT的家起到了和一个门牌号一样的作用或者说NT从某种意义上来说他和NT家的地址是等价的。 而C语言中,NT的家等价于NT家的地址,这叫做指针。指针是一种类型,他的作用就是指向变量的地址并可以读取和修改。 首先,使用指针的前提是变量已经存在(还有一种空指针NULL),就像如果NT不在小区买房,那么门口保安肯定是不知道NT的家在那的甚至会不让你进小区(系统报错)。 此时int * NT = &home 就像是小区业主在登记信息,向大伙说明,这个地址是NT的家,你可以直接问NT的家在哪来访问我。 使用 变量类型 + * + 指针名 = & 变量名  的方式来定义和声明变量,注意的是,指针类型必须进行初始化,哪怕是你说不进去找人,只是空逛逛小区,也就是空指针 如 int * NT = NULL; 既然可以通过NT这个别称来寻找到NT的家,那么同理一个地址可以被多个指针所指向(可能是NT老婆的名字,NT儿女的名字) 此时int * NT = &home 就像是小区业主在登记信息,向大伙说明,这个地址是NT的家,你可以直接问NT的家在哪来访问我。 使用 变量类型 + * + 指针名 = & 变量名  的方式来定义和声明变量,注意的是,指针类型必须进行初始化,哪怕是你说不进去找人,只是空逛逛小区,也就是空指针 如 int * NT = NULL; 既然可以通过NT这个别称来寻找到NT的家,那么同理一个地址可以被多个指针所指向(可能是NT老婆的名字,NT儿女的名字) 那么指针的作用是什么呢? 指针最基本的一个作用是值传递和地址传递。通常我们在定义一个函数的时候,其变量类型是数据变量,如下图所示,x和y的类型都是int,这个a和b叫做行参,即我们在定义函数的时候写的参数,而当我们调用这个函数的时候,传入的参数x和y叫做实参。 那么a,b,x,y到底是什么关系呢?其实我们可以观察他们的地址,我们发现,实际上我们将x,y传入函数的时候,函数新创建了两个变量a 和b 来存放我们x和y的数据并进行运算,等到这个函数运行完毕再释放掉a和b这两个数据(拆迁) 而当我们在函数中改变a和b的值的时候, x和y 的值并没有发生改变(抓周树人,管我鲁迅什么事? ) 然鹅,当我们把函数的数据类型换成指针时,我们就可以在函数中改变外部实参的值。事实上函数中也创建了一个形参,不过这个形参的是一个指针类型的(和实参类型相同)而他和我们传入的实参所指向的地址是同一个地址,因此我们通过解引用行参来修改实参的值。我们把这种叫做地址传递,意思是将指针(变量的地址)作为数据传入函数。
实在太懒于是不想取名
6 8 嘉立创PCB
运放输出电阻探究(1)
探究完WL01涡流传感器,上午刚准备去退货,中午模块被旁边的同学玩坏了。工作电压5V,工作电流达到了0.4A。发热量严重。 我仔细询问了其使用情况。发现了几个问题: 第一:其模块供电是由电源供电,输出端接STM32单片机PA0,PA0设置为模拟,无上下拉电。 第二:其PA0口空置存在1.2V左右空置电压。 第三:存在电源还未上电但输出端接PA0的情况。 如图是去年自己设计的一块运放(LCZ-2201)与现今许多运放的结构相似。其输出端是推挽输出的三极管。并且在此电路外部也一般会串联两个二极管至正负电源已起到保护电路的作用,防止电压过大,烧毁芯片。 当学生电源接入电路而不开启时,我们可以用万用表的通断档进行测量,发现其内部是导通。 因此,当我们连接上电源,但不但不打开时,其内部结构等效如下: 此时如果使用者将输出端接入单片机,而单片机IO上存在高于导通压降的电压,就会导致二极管导通,造成过高的电压,也可能使内部输出端电流过大导致内部结构损坏。 所以在使用过程中我们一定要符合规范,需要先供电,再将其接入信号或者输出否则很可能导致芯片因为这个问题导致烧毁。 那么有什么办法可以避免这样子的问题呢? 这时候我们就可以为运放添加输入电阻与输出电阻。 在输入输出端加入两个电阻(跟随器为相同电阻,放大器为接地端电阻等于反馈电阻并联的值)可以有效抑制输入电流,还可以对运算放大器进行电源保护,在不正规操作下,保护运算放大器不被外部电压窜入或者静电烧毁。 中午批评了实验室的学弟一通,无论是使用模块还是芯片还是单片机,出现问题并不可怕,可怕的是出现问题后逃避而不去解决问题,对问题寻根问底以后才能避免这样子的错误再次发生而不是将问题抛给电路抛给模块,从自身出发寻找问题的解决方法才是正确的道路。
实在太懒于是不想取名
3 14 嘉立创PCB
多位数据的分组存储与计算与其中的计算机原理探究——补码的意义
今日在某一技术群里水群的时候,看到群友问了一个很有意思的问题如下所示:如果xx和yy是48位变量,将xx和yy分别用XX[]和YY[]来将这些数据分为六段进行保存我们该如何运算其差值。 强调一下仅限于人家是八位运算,不考虑用什么long来运算这些~~ 那么最简单的想法也许就是 aa[i] = xx[i] - yy[i]了 但是这就会遇到一个最大的问题就是—— 假如xx[i] - yy[i] 是负数呢,而aa ,xx,yy都是u8即unsigned char 无符号字符类型的数据,那么假如xx[i]-yy[i]小于零必然会导致程序崩溃。 于是我提出了用一个额外的数组来专门存储这个符号位,众所周知,有符号的八位数据类型,其最高位是符号位,1代表负数,0代表正数,其数据范围是-128~127,而无符号字符型其最高位也用于计数,其范围为   0~256。 看似这个问题解决了,但是实际是不行的,在后续的计算中我们发现蕴含着巨大的问题。 如果我们想要计算石榴煎酒(16-9),6-9不够借1当十,16减9,6减9不够借1当十,16减9..... 好吧言归正传,以16-9举例,我们将数据四个字节四个字节运算,那么高四位就是0001 - 0000 就是 0001 ,低四位是0000-1001 那么我们把符号置1,用高四位的结果减符号,就是0000,低四位是1001-0000是1001就是十进制的9。那么最后的结果就是9啦~~ 很显然,0111才是正确的结果,那么0111和1001是什么关系呢。哦!!!1001是0111的补码。突然间恍然大悟(死去的计算机原理正在攻击我)。 其实在计算机中,负数并不是简单的将数字最高位加上1 来表示的,而是用补码的形式表示的,补码的所有位翻转后+1就是原码了。 哦又想到刚才我们的操作,其实我们将6-9后得到的-3是一个负数,或者说0000-1001得到的负的1001是一个负数,而负数在计算机中的存储方式应该是以其补码的方式存在的。那么我们判断正负之后,如果是正数,那么应该用他的原码格式,如果是一个负数,那么我们就应该保存它的补码。 那么如此我们就可以完美的解决这个问题了。! 但这就结束了嘛(可恶字数才800还能水) 但是,为什么会有补码这么看起来反人类的东西呢??? 这才是这期内容的主题——全减器 几乎所有编程里面的书都会向你介绍补码,反码,原码的概念,但是我几乎没有看到这些书对其做出解释(虽然好像也不重要)。但是当你了解到补码的意义时才会感慨道人类的智慧竟是如此伟大。 这是一个全加器,结构非常简单,Ai和Bi分别是加数作为输入端,  Ci-1是上一个全加器的进位,Si作为本位,Ci作为给下一个全加器的进位,第一个全加器的Ci-1是0,再加上七个这样子的全加器就构成了一个八位加法器。结构非常的巧妙,被大量的运用在了计算机的底层,本质上计算机就是再做复杂的运算。 那么如何做减法呢??? 我们可以利用138译码器来实现全减器,亦或者其他的结构。但是总不能我八位减法器就得用八个138译码器吧?? 我们再来看回减法,减法的本质也是加法,比方说3-5其实就是3+-5那么我们能不能用全加器来实现加法呢? 当我们的数据采用补码时,我们即可实现将减法运算利用加法来实现,用补码来表示计算机的负数,使得硬件上可以节省减法器的开发,而我们前面提到,将 补码转换成原码的方式是取反加1 ,那么同样的我们需要计算减法时,只需要对其中的一个加数将其转换成补码,即 减一再取反 ,并且通过逻辑运算单元ALU来控制就可以实现减法运算了。 写到这不由感慨这些知识本是这学期初想和mlm手搓一块CPU而学习来的,可是这个学期事情繁多最终并未付诸实践。几个月前两个少年再研究CPU架构的时候,命运的齿轮就开始转动,也许教育和学习就是有延时性的,并不能当时获得满足,可是当这一切积累起来的时候,总有一个时刻总会为之前了解过的东西而感到惊讶。 这也让我悟了,其实本来没指望在大学的课堂中学到什么,但是没学明白和不了解是两码事,课堂上我所学的不仅仅是知识,而是新的视野。
实在太懒于是不想取名
3 7 嘉立创PCB