# AD模块+C井上位机 **Repository Path**: rise2629/ad-module ## Basic Information - **Project Name**: AD模块+C井上位机 - **Description**: 自制AD模块,精度高,自定义高。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2023-06-26 - **Last Updated**: 2023-09-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 背景 ​ 之前用过INA219以及INA226的AD模块,精度虽然很高,但是只有单通道,且占用一个IIC地址,在偶然间发现还有一个INA系列的模块,不仅可以测电流电压,而且还是三通道,且有四个地址,意味着能同时存在12通道的AD检测,斯巴拉西。且价格还比单通道的219和226偏移好几倍,唯一缺点是精度0.4mA,空载最小存在0.4mA。 img 引脚参考: ![1](https://s2.loli.net/2023/04/24/tlE9dsAThrvSugQ.png) # 设计思路 ​ 项目开始时,手头上只有8266,本来想用32,但想想8266也够用,应该没被榨干性能。因为采用IIC通讯,所以使用四根线即可,其余的报警过流针脚可以不管。模块有四个地址,这里只用到两个,分别短接A0-VCC,A0-GND的触点。如果不知道地址,可以执行IIC地址扫描程序。 ![image-20230423155340875](https://s2.loli.net/2023/04/24/M8OYdxCZcDVpnh3.png) ## 电路设计 ![image-20230423155657760](https://s2.loli.net/2023/04/24/elPYsobVFAR8nXC.png) **其中:R为上拉限流电阻,防止在不小心短接时烧坏模块,电阻的选择应该为电阻尽量小,功率尽量大。** | MCU脚 | INA3221脚 | 注释 | | :----: | :-------: | ---- | | D1/SCL | SCL | | | D2/SDA | SDA | | | VCC | VCC | 3V3 | | GND | GND | GND | 元件说明: | 元件 | 说明 | | :----: | :----: | | 二极管 | 防反接 | | 电容 | 滤波 | | | | ## 软件设计 采用的是Arduino框架,原因是开发简单,快速。 ### IIC地址扫描 若不知道地址,可以执行以下程序 ```c #include void setup() { Wire.begin(); Serial.begin(115200); while (!Serial); // Leonardo: wait for serial monitor Serial.println("\nI2C Scanner"); } void loop() { byte error, address; int nDevices; Serial.println("Scanning..."); nDevices = 0; for(address = 1; address < 127; address++ ) { // The i2c_scanner uses the return value of // the Write.endTransmisstion to see if // a device did acknowledge to the address. Wire.beginTransmission(address);//从指定的地址开始向I2C从设备进行传输 error = Wire.endTransmission();//停止与从机的数据传输 /* * error返回结果: * 0: 成功 1: 数据量超过传送缓存容纳限制 2: 传送地址时收到 NACK 3: 传送数据时收到 NACK 4: 其它错误 */ if (error == 0) { Serial.print("I2C device found at address 0x"); if (address<16) Serial.print("0"); Serial.print(address,HEX); Serial.println(" !"); nDevices++; } else if (error==4) { Serial.print("Unknown error at address 0x"); if (address<16) Serial.print("0"); Serial.println(address,HEX); } } if (nDevices == 0) Serial.println("No I2C devices found\n"); else Serial.println("done\n"); delay(5000); // wait 5 seconds for next scan } ``` ### INA322使用步骤 ```C #include #include //............创建对象 INA3221 ina_0(INA3221_ADDR40_GND);//0x40 INA3221 ina_1(INA3221_ADDR41_VCC);//0x41 //............初始化函数 void current_measure_init() { ina_0.begin(&Wire); ina_0.reset(); ina_0.setShuntRes(10, 10, 10); ina_1.begin(&Wire); ina_1.reset(); ina_1.setShuntRes(10, 10, 10); } //............主函数初始化 void setup() { Serial.begin(SERIAL_SPEED);//波特率 current_measure_init(); while (!Serial) { delay(1); } // Set shunt resistors to 10 mOhm for all channels } //...........loop void loop() { //----------模块1通道1的电流电压 temp_v[t]=ina_0.getVoltage(INA3221_CH1); temp_i[t]=ina_0.getCurrent(INA3221_CH1)*100; //----------以此类推采集模块1-2的1-3通道 } ``` 编译可以看到内存使用还是挺高,榨干了。 ``` . Variables and constants in RAM (global, static), used 29144 / 80192 bytes (36%) ║ SEGMENT BYTES DESCRIPTION ╠══ DATA 1508 initialized variables ╠══ RODATA 1324 constants ╚══ BSS 26312 zeroed variables . Instruction RAM (IRAM_ATTR, ICACHE_RAM_ATTR), used 60411 / 65536 bytes (92%) ║ SEGMENT BYTES DESCRIPTION ╠══ ICACHE 32768 reserved space for flash instruction cache ╚══ IRAM 27643 code in IRAM . Code in flash (default, ICACHE_FLASH_ATTR), used 242132 / 1048576 bytes (23%) ║ SEGMENT BYTES DESCRIPTION ╚══ IROM 242132 code in flash ``` ## 板子设计 ## BOM表 ![image-20230616084915451](https://s2.loli.net/2023/06/16/ySJXmGFBYL4QUcs.png) ### 第一版 ​ 第一版的设计存在几个问题是电压测量的导线太细,航空插头外接应购买直插,8266开发板太占位置,后期会考虑换成贴片的或者是32,以及电阻的封装画错,但也可以选购相同封装的电阻,后期会再修改,先用着先。 预留0.96寸4针OLED屏幕,方便没上位机的情况。 ![image-20230423162533811](https://s2.loli.net/2023/04/24/N14CDFWuTAHetOL.png) 3D预览 ![image-20230423162911918](https://s2.loli.net/2023/04/24/brhJAW9TcQy26NI.png) 加入IKUN的防盗水印,amagi!!! ![image-20230423163158541](https://s2.loli.net/2023/04/24/uRb5KANhSF9HDWo.png) 实物图 ![image-20230509084255781](https://s2.loli.net/2023/05/09/HZOQzKmvwtATclG.png) ### 第二版 改进: - 增加二极管,使每一路独立测量。 - 替换限流电阻的封装。 - 增加线宽 - 增加按键 ​ 第一版由于设计时未考虑到电阻也会回流,使得六个通道只能测一个电压,改进时增加二极管,单向导通。并增加按键,使得可以切换OLED屏幕显示内容。 ![image-20230509085033274](https://s2.loli.net/2023/05/09/9bnGMZXvE5qjCTI.png) ### 第三版 改进: - 增加示波器功能 - 校准电流偏差 ## 小结 ​ 板子基本可以使用无BUG,数据传输为串口通信,延迟最低可达10ms,其中需要注意,在大电阻,小电压的情况下下,若流过电流小于模块最小电流分辨率,则会测量不准,解决方法为提升电压。 # 上位机 ​ 此前一直使用的是XCOM这种上位机,但当数据快速的刷新时,此工具就会变得非常难用,所以需要一款较为直观的上位机。 ​ 在查阅资料时,发现有串口示波器这种工具,能直观的反应出趋势。经过许多示波器的体验,最终选择个人开发者的 纸飞机串口调试助手,非常好用,且支持白嫖一个月。 ![image-20230616085637656](https://s2.loli.net/2023/06/16/q58TcDSCE1unN7m.png) ​ 对此,我觉得上位机开发成这样非常炫酷,心里暗暗埋下种子。 # 开发上位机 ​ 在大学阶段,并未学习过QT,所以无法使用QT开发,但是学过另外一个桌面开发框架C#,于是边重新捡起来,边开发。 ## 需求 ![LIGHT-COM助手](https://s2.loli.net/2023/06/16/ev3RdcbuilfVrsZ.png) ## 环境 ​ 使用VS安装好.net框架后即可。 ![image-20230616090102012](https://s2.loli.net/2023/06/16/JcwTUaNukEFd7R2.png) ## 过程创建 ​ 选择窗体,然后就可愉快的拉框框了。 ![image-20230616090227612](https://s2.loli.net/2023/06/16/NP9cCxQj5gJGmdA.png) ## 界面 ​ 可能是我的审美接近于0,界面能用就行,哈哈哈。 ![image-20230616090503921](https://s2.loli.net/2023/06/16/7rfZq2uN3htmDeJ.png) ## 代码 **传统串口功能:** 1. 串口扫描 2. 波特率选择 3. 打开/关闭串口 4. 接收数据,发送数据 5. 清除接收区/发送区 **附加功能** 1. 保持数据 2. 打开保存目录 3. K值(斜率计算) 4. AD采样通道选择 5. 数据分窗显示 6. 一次函数y=kx+b计算功能 7. 趋势表(15s更新) ## 实现原理 串口实现我是参考此贴 https://blog.csdn.net/weixin_38743772/article/details/125666832 1. 不同行数据读取 ```c# receive_data1 = serialPort1.ReadLine(); receive_data2 = serialPort1.ReadLine(); receive_data3 = serialPort1.ReadLine(); //DateTime.Now.ToString("HH:mm:ss.fff"); ``` 2. 保存数据 ``` if (button2.Text == "关闭串口" && button11.Text == "保存数据") { get_time_to_name = DateTime.Now.ToString("MM-dd HH.mm"); button11.BackColor = System.Drawing.Color.Red; button11.Text = "保存中"; save_cmd = true; } else if (button2.Text == "打开串口") { MessageBox.Show("请先打开串口!"); } else { if (button2.Text == "关闭串口" && button11.Text == "保存中") { button11.BackColor = System.Drawing.Color.Empty; button11.Text = "保存数据"; save_cmd = false; MessageBox.Show("保存成功!"); } } ``` 3. 文件流 ``` /// /// 保存txt文档 /// /// 保存内容 /// 通道 public static void Txt(string zhi, string tongdao, string name) { string path = ".\\log\\";//日志文件路径&配置到Config文件中直接获取 string filename = name + ".txt";// DateTime.Now.ToString("MM-dd HH.mm") + ".txt";//文件名 //string year = DateTime.Now.ToString("yyyy-MM");//年月日文件夹 string passageway = tongdao;//通道文件夹 //判断log文件路径是否存在,不存在则创建文件夹 if (!System.IO.Directory.Exists(path)) { System.IO.Directory.CreateDirectory(path);//不存在就创建目录 } path += passageway + "\\"; //判断通道文件夹是否存在,不存在则创建文件夹 if (!System.IO.Directory.Exists(path)) { System.IO.Directory.CreateDirectory(path);//不存在就创建目录 } path += "\\"; //判断年月文件夹是否存在,不存在则创建文件夹 if (!System.IO.Directory.Exists(path)) { System.IO.Directory.CreateDirectory(path);//不存在就创建目录 } //拼接完整文件路径 path += filename; if (!File.Exists(path)) { //文件不存在,新建文件 FileStream fs = new FileStream(path, FileMode.OpenOrCreate); StreamWriter sw = new StreamWriter(fs); sw.Close(); } using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write)) { using (StreamWriter sw = new StreamWriter(fs)) { sw.BaseStream.Seek(0, SeekOrigin.End); sw.Write(DateTime.Now.ToString("HH:mm:ss.fff") + " :{0}\n", zhi, DateTime.Now); sw.Flush(); } } } ``` 4. 线程 ```c# Thread sel_thread = new Thread(() => { select(); }); select_cmd = true; sel_thread.Start(); ``` ```c# public void select()//标志位控制线程函数 { while (select_cmd) { Thread.Sleep(110); select_r(receive_data1); select_v(receive_data2); select_i(receive_data3); //label12.Text = receive_data2.Length.ToString(); //label12.Text = str; } } ``` ``` if (data.IndexOf("R:") != -1)//过滤,类似strstr函数 { for (int i = data.IndexOf(":") + 1; i < j; i++) { if (data[i] == '}') break; data_r[m] = data[i]; m++; if (data[i] == ',') d++; } } ``` ```c# //追加文本 textBox1.AppendText(receive_data1 + "\n"); //R textBox25.AppendText(receive_data2 + "\n");//V textBox26.AppendText(receive_data3 + "\n");//I ``` 5. 零碎功能k值计算 ```c# private void button9_Click(object sender, EventArgs e) { try { float K = Convert.ToSingle(textBox29.Text) / (Convert.ToSingle(textBox27.Text) - Convert.ToSingle(textBox28.Text)); float B = Convert.ToSingle(textBox29.Text) - K * Convert.ToSingle(textBox27.Text); //label14.Text=B.ToString(); //select_cmd = false; textBox10.Text = K.ToString("0.000"); textBox23.Text = B.ToString("0.00"); } catch { } } ``` 6. 待续 ## 效果 界面好丑 ![image-20230616143911855](https://s2.loli.net/2023/06/16/XIflRyGUqaPZTeC.png) 保持目录 ![image-20230616143946340](https://s2.loli.net/2023/06/16/ukrD5VK2aPbBipN.png) # 待续 将来更新 - 更丝滑的曲线 - 保持csv文件 - 写更容易理解的文档 - 等等