1. 项目介绍
称重计量是当今社会活动中不可缺少的一部分,随着国际交流的发展,称重计量的国际统一越来越重要。
电子称重技术是现代称重测量和控制系统工程的重要基础之一。近年来,随着现代科学技术的进步,电子称重技术取得了突飞猛进的发展,电子秤在计量领域也发挥着越来越重要的作用。特别是商用电子衡器以其精度高、反应灵敏、性能稳定、结构简单、环境适应性强、与电子计算机结合方便、称重计量和过程控制自动化等特点,广泛应用于工商贸易、能源交通、冶金矿山、轻工食品、医疗卫生、航空航天等领域。
电子秤的工作原理是通过称重传感器收集被测物体的重量,并将其转换为电压信号。输出电压信号通常很小,需要通过前端信号处理电路准确线性放大。放大后的模拟电压信号通过A/D转换电路转换成数字量,送入主控电路的单片机,然后由单片机控制OLED显示屏,从而显示出被测物体的重量,在实际应用中为提高数据采集的精度,并尽量减少外界电气干扰还需要在传感器与A/D在芯片之间添加信号调节电路。
采用当前项目STM32 称重模块 OLED实现了简单的电子秤项目,称重模块采用24位ADC芯片,精度高。实现称重、校准、去皮等功能。
MCU:STM32F103ZET6,只要是STM32F1X本系列工程代码通用。
称重模块: 淘宝购买的称重模块
OLED: SPI接口的0.96寸OLED屏,采用的是中景园电子的OLED屏。
https://download.csdn.net/download/xiaolong1126626497/63993934
https://live.csdn.net/v/182608
2. 项目实现
2.1 称重传感器
称重传感器是一种压力传感器,也称为悬臂梁压力传感器。安装时需要一端固定,另一端受力。内部有四个应变电阻片,共同形成电桥。当受力端施加压力时,传感器外壳会变形,从而影响应变电阻片的电阻值。
采用称重传感器 CS1237 将微小的电压信号转换为转换芯片 24 位精度数字信号。模块信号输入端可接受差分信号,内部有可编程操作放大器放大输入端的弱信号。模块内置温度传感器,可粗略估计周围温度。模块可用于电子秤、血液计、智能变换器等各种工业过程控制场合。
CS1237中有1路ADC,集成1路差分输入,信号输入可以是差分输入信号AINP、AINN,它也可以是温度传感器的输出信号,由寄存器切换输入信号(ch_sel控制。
CS1237是采用2线SPI串行通信,通过SCLK和DRDY/DOUT可实现数据接收和功能配置。
#include "ADC-CS1237.h" static long AD_Res_Last=0; //上一轮ADC数值保存 /* 定义CS1237使用的GPIO口 CLK PB14 时钟线 OUT PB15 数据输出线 */ void CS1237_GPIO_INIT(void) {
RCC->APB2ENR |= 0x01 << 3; //打开PB口 GPIOB->CRH &= 0xF0FFFFFF; ///清零寄存器 GPIOB->CRH |= 0x03000000; //通用推挽输出 50MHz GPIOB->ODR |= 1<< 14; //拉高CLK电平 } void CS1237_DRDY(void) //配置PB15为输入 {
GPIOB->CRH &= 0x0FFFFFFF; ///清零寄存器 GPIOB->CRH |= 0x80000000; //上下拉输入模式 } void CS1237_DOUT
(
void
)
//配置PB15为输出
{
GPIOB
->CRH
&=
0x0FFFFFFF
;
//寄存器清零 GPIOB
->CRH
|=
0x30000000
;
//通用推挽输出 50MHz
}
//CS1237进入低功耗模式
void
CS1237_Power_Down
(
void
)
{
CLK_HIGH
delay_us
(
200
)
;
//CLK上拉时间应超过100us,恢复后下拉时间至少10us
}
//配置CS1237芯片
void
Con_CS1237
(
void
)
{
u8 i
; u8 dat
; u8 count_i
=
0
;
//溢出计时器 dat
= CS_CON
;
// 0100 1000
CS1237_DOUT
(
)
; OUT_HIGH
delay_ms
(
310
)
;
//上电建立时间
CS1237_DRDY
(
)
;
//配置PB15为输入 CLK_LOW
//时钟拉低
while
(INT
)
//芯片准备好数据输出 时钟已经为0,数据也需要等CS1237全部拉低为0才算都准备好
{
printf
(
"123\r\n"
)
;
delay_ms
(
100
)
;
//10HZ下转换时间是100ms count_i
++
;
if
(count_i
>
150
)
{
CLK_HIGH
;
CS1237_DOUT
(
)
; OUT_HIGH
return
;
//超时,则直接退出程序
}
}
for
(i
=
0
;i
<
29
;i
++
)
// 1 - 29
{
One_CLK
;
}
CS1237_DOUT
(
)
; CLK_HIGH
;
delay_us
(
6
)
;OUT_HIGH
;CLK_LOW
;
delay_us
(
6
)
;
//30 CLK_HIGH
;
delay_us
(
6
)
;OUT_HIGH
;CLK_LOW
;
delay_us
(
6
)
;
//31 CLK_HIGH
;
delay_us
(
6
)
;OUT_LOW
;CLK_LOW
;
delay_us
(
6
)
;
//32 CLK_HIGH
;
delay_us
(
6
)
;OUT_LOW
;CLK_LOW
;
delay_us
(
6
)
;
//33 CLK_HIGH
;
delay_us
(
6
)
;OUT_HIGH
;CLK_LOW
;
delay_us
(
6
)
;
//34 CLK_HIGH
;
delay_us
(
6
)
;OUT_LOW
;CLK_LOW
;
delay_us
(
6
)
;
//35 CLK_HIGH
;
delay_us
(
6
)
;OUT_HIGH
;CLK_LOW
;
delay_us
(
6
)
;
//36 One_CLK
;
//37 写入了0x65
for
(i
=
0
;i
<
8
;i
++
)
// 38 - 45个脉冲了,写8位数据
{
CLK_HIGH
;
delay_us
(
6
)
;
if
(dat
&
0x80
) OUT_HIGH
else OUT_LOW dat
<<=
1
; CLK_LOW
;
delay_us
(
6
)
;
}
CS1237_DRDY
(
)
; One_CLK
;
//46个脉冲拉高数据引脚
}
//读取芯片的配置数据 u8
Read_CON
(
void
)
{
u8 i
; u8 dat
=
0
;
//读取到的数据 u8 count_i
=
0
;
//溢出计时器
CS1237_DOUT
(
)
; OUT_HIGH
CS1237_DRDY
(
)
;
//配置PB15为输入 CLK_LOW
//时钟拉低
while
(INT
)
//芯片准备好数据输出 时钟已经为0,数据也需要等CS1237全部拉低为0才算都准备好
{
delay_ms
(
100
)
; count_i
++
;
if
(count_i
>
150
)
{
CLK_HIGH
;
CS1237_DOUT
(
)
; OUT_HIGH
;
return
1
;
//超时,则直接退出程序
}
}
for
(i
=
0
;i
<
29
;i
++
)
// 1 - 29
{
One_CLK
;
}
CS1237_DOUT
(
)
; CLK_HIGH
;
delay_us
(
6
)
;OUT_HIGH
;CLK_LOW
;
delay_us
(
6
)
;
//30 CLK_HIGH
;
delay_us
(
6
)
;OUT_LOW
;CLK_LOW
;
delay_us
(
6
)
;
//31 CLK_HIGH
;
delay_us
(
6
)
;OUT_HIGH
;CLK_LOW
;
delay_us
(
6
)
;
//32 CLK_HIGH
;
delay_us
(
6
)
;OUT_LOW
;CLK_LOW
;
delay_us
(
6
)
;
//33 CLK_HIGH
;
delay_us
(
6
)
;OUT_HIGH
;CLK_LOW
;
delay_us
(
6
)
;
//34 CLK_HIGH
;
delay_us
(
6
)
;OUT_HIGH
;CLK_LOW
;
delay_us
(
6
)
;
//35 CLK_HIGH
;
delay_us
(
6
)
;OUT_LOW
;CLK_LOW
;
delay_us
(
6
)
;
//36 One_CLK
;
//37 写入了0x56
CS1237_DRDY
(
)
; dat
=
0
;
for
(i
=
0
;i
<
8
;i
++
)
// 38 - 45个脉冲了,读取数据
{
One_CLK
; dat
<<=
1
;
if
(INT
) dat
++
;
} One_CLK
;
//46个脉冲拉高数据引脚
return dat
;
}
//读取ADC数据,返回的是一个有符号数据
long
Read_CS1237
(
void
)
{
u8 i
;
long dat
=
0
;
//读取到的数据 u16 count_i
=
0
;
//溢出计时器
CS1237_DOUT
(
)
; OUT_HIGH
//等待模拟输入信号建立 CLK_LOW
;
//时钟拉低
CS1237_DRDY
(
)
;
while
(INT
)
//芯片准备好数据输出 时钟已经为0,数据也需要等CS1237拉低为0才算都准备好
{
// printf("等待1\r\n");
delay_ms
(
10
)
; count_i
++
;
if
(count_i
>
300
)
{
CLK_HIGH
;
CS1237_DOUT
(
)
; OUT_HIGH
;
return
0
;
//超时,则直接退出程序
}
}
CS1237_DOUT
(
)
; OUT_HIGH
//端口锁存1,
CS1237_DRDY
(
)
; dat
=
0
;
for
(i
=
0
;i
<
24
;i
++
)
//获取24位有效转换
{
CLK_HIGH
;
delay_us
(
6
)
; dat
<<=
1
;
if
(INT
) dat
++
; CLK_LOW
;
delay_us
(
6
)
;
}
for
(i
=
0
;i
<
3
;i
++
)
//一共输入27个脉冲
{
One_CLK
;
}
CS1237_DOUT
(
)
; OUT_HIGH
;
//先根据宏定义里面的有效位,丢弃一些数据 i
=
24
- ADC_Bit
;
//i表示将要丢弃的位数 dat
>>= i
;
//丢弃多余的位数
return dat
;
}
//初始化ADC相关参数
int
Init_CS1237
(
void
)
{
Con_CS1237
(
)
;
//配置CS1237
if
(
Read_CON
(
)
!= CS_CON
)
//如果读取的ADC配置出错,则重启
{
printf
(
"读取错误\r\n"
)
;
return
0
;
}
delay_us
(
10000
)
; AD_Res_Last
=
Read_CS1237
(
)
; AD_Res_Last
=
Read_CS1237
(
)
; AD_Res_Last
=
Read_CS1237
(
)
;
return
0
;
}
//数字一阶滤波器 滤波系数A,小于1。上一次数值B,本次数值C out = b*A + C*(1-A)
//下面这个程序负责读取出最终ADC数据
long
Read_18Bit_AD
(
void
)
//18位的
{
float out
,c
; out
= AD_Res_Last
; c
=
Read_CS1237
(
)
;
if
(c
!=
0
)
// 读到正确数据
{
out
= out
*Lv_Bo
+ c
*
(
1
-Lv_Bo
)
; AD_Res_Last
= out
;
//把这次的计算结果放到全局变量里面保护
}
return AD_Res_Last
;
}
2.2 OLED显示屏
OLED显示屏是0.96寸 SPI接口显示屏,采用SSD1306驱动,兼容3.3V或5V电源输入,非常常见,淘宝一搜一大堆,当前选择的是中景园电子的OLED显示屏。
在调试设备或者测试数据时,有时候需要实时观察数据的变化,加入显示屏可以把观察设备的运行情况,数据变化等。在成本和难易程度上,OLED显示屏是非常适合初学者去学习与应用的。
#include "OLED.H"
#include "oled_font.h"
/* 定义OLED使用的GPIO口 D0 PA5 时钟线 D1 PA1 数据输出线 RES PA2 复位线 DC PA3 数据/命令选择线 CS PA4 片选线 */
void OLED_GPIO_INIT(void)
{
RCC->APB2ENR |= 1<<2; //打开PA口
GPIOA->CRL &= 0xFF00000F; //寄存器清零
GPIOA->CRL |= 0x00333330; //通用推挽输出 50MHz
GPIOA->ODR |=0x003E;
}
void OLED_Init(void)
{
OLED_GPIO_INIT(); //GPIO口初始化
OLED_RES_HIGH;
delay_ms(100);
OLED_RES_LOW;
delay_ms(200); //延迟,由于单片机上电初始化比OLED快,所以必须加上延迟,等待OLED上电初始化完成
OLED_RES_HIGH;
delay_ms(200);
OLED_WR_Byte(0xAE,OLED_CMD); //关闭显示
OLED_WR_Byte(0x2e,OLED_CMD); //关闭滚动
OLED_WR_Byte(0x00,OLED_CMD); //设置低列地址
OLED_WR_Byte(0x10,OLED_CMD); //设置高列地址
OLED_WR_Byte(0x40,OLED_CMD); //设置起始行地址
OLED_WR_Byte(0xB0,OLED_CMD); //设置页地址
OLED_WR_Byte(0x81,OLED_CMD); // 对比度设置,可设置亮度
OLED_WR_Byte(0xFF,OLED_CMD); // 265
OLED_WR_Byte(0xA1,OLED_CMD); //设置段(SEG)的起始映射地址;column的127地址是SEG0的地址
OLED_WR_Byte(0xA6,OLED_CMD); //正常显示;0xa7逆显示
OLED_WR_Byte(0xA8,OLED_CMD); //设置驱动路数
OLED_WR_Byte(0x3F,OLED_CMD); //1/64duty
OLED_WR_Byte(0xC8,OLED_CMD); //重映射模式,COM[N-1]~COM0扫描
OLED_WR_Byte(0xD3,OLED_CMD); //设置显示偏移
OLED_WR_Byte(0x00,OLED_CMD); //无偏移
OLED_WR_Byte(0xD5,OLED_CMD); //设置震荡器分频(默认)
OLED_WR_Byte(0x80,OLED_CMD);
OLED_WR_Byte(0xD8,OLED_CMD); //设置 area color mode off(没有)
OLED_WR_Byte(0x05,OLED_CMD);
OLED_WR_Byte(0xD6,OLED_CMD); //放大显示
OLED_WR_Byte(0x00,OLED_CMD);
OLED_WR_Byte(0xD9,OLED_CMD); //设置 Pre-Charge Period(默认)
OLED_WR_Byte(0xF1,OLED_CMD);
OLED_WR_Byte(0xDA,OLED_CMD); //设置 com pin configuartion(默认)
OLED_WR_Byte(0x12,OLED_CMD);
OLED_WR_Byte(0xDB,OLED_CMD); //设置 Vcomh,可调节亮度(默认)
OLED_WR_Byte(0x30,OLED_CMD);
OLED_WR_Byte(0x8D,OLED_CMD); //设置OLED电荷泵
OLED_WR_Byte(0x14,OLED_CMD); //开显示
OLED_WR_Byte(0xA4,OLED_CMD); // Disable Entire Display On (0xa4/0xa5)
OLED_WR_Byte(0xA6,OLED_CMD); // Disable Inverse Display On (0xa6/a7)
OLED_WR_Byte(0xAF,OLED_CMD); //开启OLED面板显示
OLED_Clear(); //清屏
OLED_Set_Pos(0,0); //画点
}
void OLED_Write_Byte(u8 data)
{
u8 i; //定义变量
for(i = 0; i < 8; i++) //循环8次
{
OLED_D0_LOW //将时钟线拉低
delay_us(1); //延迟
if(data & 0x80) //数据从高位-->低位依次发送
OLED_D1_HIGH //数据为为1
else
OLED_D1_LOW //数据位为0
data <<= 1; //数据左移1位
OLED_D0_HIGH //时钟线拉高,把数据发送出去
delay_us(1); //延迟
}
}
/* @brief 对OLED写入一个字节 @param dat:数据 cmd:1,写诶数据;0,写入命令 @retval 无 */
void OLED_WR_Byte(u8 dat,u8 cmd)
{
if(cmd) //如果cmd为高,则发送的是数据
OLED_DC_HIGH //将DC拉高
else //如果cmd为低,则发送的是命令
OLED_DC_LOW //将DC拉低
OLED_CS_LOW; //片选拉低,选通器件
OLED_Write_Byte(dat); //发送数据
OLED_CS_HIGH //片选拉高,关闭器件
OLED_DC_HIGH //DC拉高,空闲时为高电平
}
/* @brief 设置数据写入的起始行、列 @param x: 列的起始低地址与起始高地址;0x00~0x0f:设置起始列低地址(在页寻址模式); 0x10~0x1f:设置起始列高地址(在页寻址模式) y:起始页地址 0~7 @retval 无 */
void OLED_Set_Pos(u8 x, u8 y)
{
OLED_WR_Byte(0xb0+y,OLED_CMD);//写入页地址
OLED_WR_Byte((x&0x0f),OLED_CMD); //写入列的地址 低半字节
OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);//写入列的地址 高半字节
}
/* @brief 开显示 @param 无 @retval 无 */
void OLED_Display_On(void)
{
OLED_WR_Byte(0X8D,OLED_CMD); //设置OLED电荷泵
OLED_WR_Byte(0X14,OLED_CMD); //使能,开
OLED_WR_Byte(0XAF,OLED_CMD); //开显示
}
/* @br