[开源硬件] 自制简易电子秤(第一篇——ADC芯片)

[复制链接]
 楼主| Andrew55 发表于 2018-7-23 17:49 | 显示全部楼层 |阅读模式

最近看到山西省电子设计大赛的一个题目——自制悬臂梁式简易电子称。具体题目要求如下:


这个题目好像是前年TI杯的一个题目。网上资料也比较多。

觉得这个题目应该很适合练手,于是就搞了一下。查了一番,觉得这个题目中的难点就是那个悬臂梁的制作与应变片的选择。这个应该属于机械、力学专业的人强项,对于我这个学控制的,还是有点挑战性的。那就先不搞传感器。我先自己买了一个商用的称重传感器(500g精度),以此作为标准先来测试我的程序。等程序方面没问题了,在集中精力去制作一个可以与商用传感器媲美的应变片式传感器。

好了,不懂的地方就不多说了,下面介绍一下我的程序思路:

首先传感器我们有了,就是类似与下面的这个东西:


它内部是一个应变片接成的惠斯通电桥,

之所以把应变片贴成电桥的形式,其实是为了弥补应变片因温度影响而带来的偏差。同样的温度下,四个应变片的阻值受温度影响的变化是一样的 。这个一样的偏差就被桥式给平衡掉。从而避免了温度的影响。

这个东西能把受力转化为电压差,但是这个电压差非常微小,需要一个放大电路,又因为他是模拟电压,后面需要单片机处理的时候需要转化为数字电压。

所以我选用了CS1237  ADC芯片。它是一款高精度、低功耗 Sigma-Delta 模数转换芯片,内置一路 Sigma-Delta ADC ,一路差分输入通道和一路温度传感器, ADC 采用两阶 sigma delta 调制器,通过低噪声仪用放大器结构实现 PGA 放大,放大倍数可选: 1、 2、 64、 128。在 PGA=128 时,有效分辨率可达 20 位 (工作在 5V)

画的PCB小板:


后面就方便了,按照芯片手册给出的通信时序,2线SPI,读取内部转化的24位ADC数值,在按照公式计算出重量即可。

主控:STM32C8T6

LCD触摸屏实时刷新重量

在进行读写CS1237的时候,最重要的又是对其进行功能配置,一旦我们能够成功的配置并读取内部的控制字,那么后面的数据读取就不是问题。

查询芯片手册,可以看到它的功能配置时序图:


这个时序相对来说还是比较复杂的,因为它不是重复的,它分为以下8步:


我们按照这个要求,就可以比较方便的写出它的功能配置函数了。

  1. //先向cs1237中写入一个控制字
  2. //写控制寄存器地址:0X65
  3. //写入默认控制字:0X0C
  4. void CS1237_write_config(unsigned char config)
  5. {
  6.         unsigned char i = 0;
  7.         unsigned char _dat = 0x80;
  8.         unsigned char count_i = 0;//溢出计时器

  9.         SCLK_0;//时钟拉低
  10.        
  11.         mode_IPU();
  12.         while(DOUT_IN() == 1)//芯片准备好数据输出,时钟已经为0,数据也需要等CS1237全部拉低为0才算都准备好
  13.         {
  14.                 CS1237_delay_us(5000);
  15.                 count_i++;
  16.                 if(count_i > 150)
  17.                 {
  18.                         SCLK_1;
  19.                         DOUT_1;
  20.                         return;//超时,则直接退出程序
  21.                 }
  22.         }
  23.        
  24.         for(i=0;i<29;i++)// 1 - 29
  25.         {
  26.                 One_CLK();
  27.         }
  28.         mode_Out_PP();
  29.         //第30-36个时钟周期,输入寄存器的写或读命令字数据(高位先输入),这里是写,应输入0x65
  30.         SCLK_1;CS1237_delay_us(30);DOUT_1;SCLK_0;CS1237_delay_us(30);//30
  31.         SCLK_1;CS1237_delay_us(30);DOUT_1;SCLK_0;CS1237_delay_us(30);//31
  32.         SCLK_1;CS1237_delay_us(30);DOUT_0;SCLK_0;CS1237_delay_us(30);//32
  33.         SCLK_1;CS1237_delay_us(30);DOUT_0;SCLK_0;CS1237_delay_us(30);//33
  34.         SCLK_1;CS1237_delay_us(30);DOUT_1;SCLK_0;CS1237_delay_us(30);//34
  35.         SCLK_1;CS1237_delay_us(30);DOUT_0;SCLK_0;CS1237_delay_us(30);//35
  36.         SCLK_1;CS1237_delay_us(30);DOUT_1;SCLK_0;CS1237_delay_us(30);//36
  37.        
  38.         One_CLK();//37,切换DOUT的方向  
  39.         for(i=0;i<8;i++)// 38 - 45个脉冲,写入寄存器的配置数据(高位先写入)
  40.         {
  41.                 SCLK_1;CS1237_delay_us(40);
  42.                 if((config & _dat) != 0)
  43.                         DOUT_1;
  44.                 else
  45.                         DOUT_0;
  46.                 SCLK_0;CS1237_delay_us(40);
  47.                 _dat >>= 1;
  48.         }
  49.         One_CLK();//46个脉冲,切换DOUT引脚,并且拉高DOUT引脚
  50. }

其中最重要的是DOUT管脚的配置。因为他是双向管脚。

在输出的时候需要配置成推挽输出,读取的时候配成上拉输入。

但是要注意这里不能使用开漏加外部上拉电阻的形式,虽然在之前我用这种方式配置过双向IO口,但是不知道为什么,在这个芯片上面不能用。读不出数据。

我用逻辑分析仪看了波形之后,才发现时序在第38-46个时钟周期内,DOUT引脚没有反应。无法被拉高。



但是上面那个程序是能够与CS1237正常通信的。

建立了通信之后,就是读取内部寄存器存储的ADC数值了:

  1. /*
  2. ** 读取ADC数据,返回一个有符号数据
  3. ** 之前程序中是延时40us,输出数据有点慢,现改为延时10us,测试可以正常读出数据
  4. */
  5. long int CS1237_Read_ADC(void)
  6. {
  7.         unsigned char i = 0;
  8.         long int dat = 0;
  9.         unsigned char count_i = 0;
  10.         SCLK_0;       
  11.         mode_IPU();
  12.         while(DOUT_IN() == 1)
  13.         {
  14.                 CS1237_delay_us(5000);
  15.                 count_i++;
  16.                 if(count_i > 150)
  17.                 {
  18.                         SCLK_1;
  19.                         DOUT_1;
  20.                         return 0;//超时,则直接退出程序
  21.                 }
  22.         }
  23.                
  24.         //获取24位有效转换
  25.         for(i = 0; i<24; i++)
  26.         {
  27.                 SCLK_1;
  28.                 CS1237_delay_us(10);
  29.                 dat <<= 1;
  30.                 if(DOUT_IN() == 1)
  31.                         dat++;
  32.                 SCLK_0;CS1237_delay_us(10);       
  33.         }
  34.         //一共需要输入27个脉冲
  35.         for(i=0; i<3; i++)
  36.                 One_CLK();
  37.        
  38.         mode_Out_PP();
  39.         DOUT_1;
  40.        
  41.         i = 24 - ADC_BIT;
  42.         dat >>= i;                //丢弃多余的位数
  43.        
  44.         return dat;
  45. }

这里读出的是20位的ADC原始数据,离我们想要的重量数据还是有一定的差距。

把读取的数据用波形显示出来,可以发现增加砝码的时候数据的抖动还是非常大的。


所以我选择先对数据进行低通滤波,消抖滤波。让数据能很快的稳定下来。

其中消抖滤波部分:

  1. long int filter_eliminate_dithering(long int value)  
  2. {  
  3.    long int count = 0;  
  4.    long int new_value = 0;  
  5.        
  6.    new_value = CS1237_Read_18bit_ADC();
  7.        
  8.    while (value != new_value)  
  9.    {  
  10.       count++;  
  11.       if (count >= N)   
  12.         return new_value;  
  13.       new_value = CS1237_Read_18bit_ADC();  
  14.    }  
  15.        
  16.    return value;      
  17. }

思想就是对比几次输出的值,在一定的输出个数内,如果是不一样的,就更新数据,否则保持原始数据不变。

得到了稳定的ADC数据之后,就可以将ADC数据转化为重量了。

转化为重量的前提是传感器输出的数据和增加的重量是线性变化的。

那么就可以根据两点直线法,先读出一个没加重物的ADC值,在读出一个加了500g重物的ADC值,根据这两个点就可以确定一个线性关系。后面读出的ADC数据都可以根据这个线性关系进行结算重量。

感觉原理一旦说出来,逼格突然降低了好几个档次。。。。

  1. //根据ADC值计算实际重量
  2. float get_weight(void)
  3. {
  4.         float dat = 0;
  5.         unsigned long int dat_2 = 0;
  6.         float dat_3 = 0;
  7.        
  8.         //读取ADC,并转化为单位:克
  9.         dat = filter_eliminate_dithering(CS1237_Read_18bit_ADC());
  10.         dat *= weight_coe;
  11.         dat -= empty_coe;
  12.        
  13.         if(dat < 0)
  14.                 dat = 0;
  15.        
  16.         //前面K放大了1000倍,这里先除10,为了小数点后面的四舍五入
  17.         dat_2 = dat / 10;
  18.         //dat_2 /= 100;
  19.         if(dat_2 % 100 > 5)
  20.         {
  21.                 dat_2 /= 10;
  22.                 dat_2 += 1;
  23.         }
  24.         else
  25.                 dat_2 /= 10;
  26.         //转换为小数输出
  27.         dat_3 = (float)dat_2 / 10.00f;
  28.         return dat_3;
  29.        
  30. }

上面这段程序在计算重量的时候,同时进行了数据的保留一位小数点和四舍五入。最后把这个数据输出到显示屏上即可。

先放一张最终的显示效果吧:

后面再继续介绍其他的功能实现。


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×

评论

看不了PCB图,最近也在做重量的采集,感觉ADC采到的数值波动很大,想仿一下楼主的电路  发表于 2019-4-16 16:22
期待楼主的完善!!!೭(˵¯̴͒ꇴ¯̴͒˵)౨  发表于 2018-7-23 20:28
小小电子爱好者 发表于 2018-7-23 19:39 | 显示全部楼层
干得漂亮!   加个推荐  大家学习一下!!!
 楼主| Andrew55 发表于 2018-7-23 21:19 | 显示全部楼层
小小电子爱好者 发表于 2018-7-23 19:39
干得漂亮!   加个推荐  大家学习一下!!!

谢谢!后面会继续更新的。
qinlu123 发表于 2018-7-27 16:23 | 显示全部楼层
我不能容忍程序中出现while(某某==某某){}这样的操作,也不能容忍加延时。这种情况一般写成状态机的形式比较好。
caijie001 发表于 2018-7-27 17:41 | 显示全部楼层
图片挂了啊啊,楼主
 楼主| Andrew55 发表于 2018-7-27 19:05 | 显示全部楼层
caijie001 发表于 2018-7-27 17:41
图片挂了啊啊,楼主

我打开看都是好好的啊
 楼主| Andrew55 发表于 2018-7-27 19:09 | 显示全部楼层
qinlu123 发表于 2018-7-27 16:23
我不能容忍程序中出现while(某某==某某){}这样的操作,也不能容忍加延时。这种情况一般写成状态机的形式 ...

还没有具体了解过状态机,这种情况如果用状态机来实现的话,程序该怎么写呢?
caijie001 发表于 2018-7-27 19:17 | 显示全部楼层
Andrew55 发表于 2018-7-27 19:05
我打开看都是好好的啊


难道是我浏览器问题???

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
 楼主| Andrew55 发表于 2018-7-27 20:38 | 显示全部楼层
caijie001 发表于 2018-7-27 19:17
难道是我浏览器问题???

这个我也不清楚了,如果有兴趣的话,可以访问我的CSDN,上面也有关于这个专题的介绍。
https://blog.csdn.net/m0_37655357/article/details/80542307
qinlu123 发表于 2018-7-30 09:08 | 显示全部楼层
Andrew55 发表于 2018-7-27 19:09
还没有具体了解过状态机,这种情况如果用状态机来实现的话,程序该怎么写呢? ...

其实就是用switch case语句把要执行的任务一步一步地实现,这样就避免了死等。
yinhaoyu 发表于 2018-8-21 19:26 | 显示全部楼层
你好。我最近在研究电子秤方面的制作方法,请问你可以把你的这个电子秤的代码发给我看一下嘛,谢谢啦2571231658@ qq .com
sggstg 发表于 2019-4-15 10:25 | 显示全部楼层
楼主,借我资料看下吧,564801004@qq.com,谢了
character 发表于 2020-9-21 08:12 | 显示全部楼层
可以啊
您需要登录后才可以回帖 登录 | 注册

本版积分规则

9

主题

20

帖子

4

粉丝
快速回复 在线客服 返回列表 返回顶部