# 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。
引脚参考:

# 设计思路
项目开始时,手头上只有8266,本来想用32,但想想8266也够用,应该没被榨干性能。因为采用IIC通讯,所以使用四根线即可,其余的报警过流针脚可以不管。模块有四个地址,这里只用到两个,分别短接A0-VCC,A0-GND的触点。如果不知道地址,可以执行IIC地址扫描程序。

## 电路设计

**其中: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表

### 第一版
第一版的设计存在几个问题是电压测量的导线太细,航空插头外接应购买直插,8266开发板太占位置,后期会考虑换成贴片的或者是32,以及电阻的封装画错,但也可以选购相同封装的电阻,后期会再修改,先用着先。
预留0.96寸4针OLED屏幕,方便没上位机的情况。

3D预览

加入IKUN的防盗水印,amagi!!!

实物图

### 第二版
改进:
- 增加二极管,使每一路独立测量。
- 替换限流电阻的封装。
- 增加线宽
- 增加按键
第一版由于设计时未考虑到电阻也会回流,使得六个通道只能测一个电压,改进时增加二极管,单向导通。并增加按键,使得可以切换OLED屏幕显示内容。

### 第三版
改进:
- 增加示波器功能
- 校准电流偏差
## 小结
板子基本可以使用无BUG,数据传输为串口通信,延迟最低可达10ms,其中需要注意,在大电阻,小电压的情况下下,若流过电流小于模块最小电流分辨率,则会测量不准,解决方法为提升电压。
# 上位机
此前一直使用的是XCOM这种上位机,但当数据快速的刷新时,此工具就会变得非常难用,所以需要一款较为直观的上位机。
在查阅资料时,发现有串口示波器这种工具,能直观的反应出趋势。经过许多示波器的体验,最终选择个人开发者的 纸飞机串口调试助手,非常好用,且支持白嫖一个月。

对此,我觉得上位机开发成这样非常炫酷,心里暗暗埋下种子。
# 开发上位机
在大学阶段,并未学习过QT,所以无法使用QT开发,但是学过另外一个桌面开发框架C#,于是边重新捡起来,边开发。
## 需求

## 环境
使用VS安装好.net框架后即可。

## 过程创建
选择窗体,然后就可愉快的拉框框了。

## 界面
可能是我的审美接近于0,界面能用就行,哈哈哈。

## 代码
**传统串口功能:**
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. 待续
## 效果
界面好丑

保持目录

# 待续
将来更新
- 更丝滑的曲线
- 保持csv文件
- 写更容易理解的文档
- 等等