1. 項(xiàng)目介紹
稱重計(jì)量是現(xiàn)在社會(huì)活動(dòng)中不可缺少的部分,隨著國際交流的發(fā)展,稱重計(jì)量的國際間的統(tǒng)一顯得越來越重要。
電子稱重技術(shù)是現(xiàn)代稱重計(jì)量和控制系統(tǒng)工程的重要基礎(chǔ)之一。近年來,隨著現(xiàn)代科技進(jìn)步,電子稱重技術(shù)取得了突飛猛進(jìn)的發(fā)展,電子秤在計(jì)量領(lǐng)域中也占有越來越重要的地位。尤其是商用電子衡器,以其準(zhǔn)確度高、反應(yīng)靈敏、性能穩(wěn)定、結(jié)構(gòu)簡單、環(huán)境適應(yīng)性強(qiáng)、便于與電子計(jì)算機(jī)結(jié)合而實(shí)現(xiàn)稱重計(jì)量與過程控制自動(dòng)化等特點(diǎn),而被廣泛用于工商貿(mào)易、能源交通、冶金礦山、輕工食品、醫(yī)藥衛(wèi)生、航空航天等領(lǐng)域。
電子秤的工作原理首先是通過稱重傳感器采集到被測物體的重量并將其轉(zhuǎn)換成電壓信號,輸出電壓信號通常很小,需要通過前端信號處理電路進(jìn)行準(zhǔn)確的線性放大。放大后的模擬電壓信號經(jīng)A/D轉(zhuǎn)換電路轉(zhuǎn)換成數(shù)字量,被送入到主控電路的單片機(jī)中,再經(jīng)過單片機(jī)控制OLED顯示屏,從而顯示出被測物體的重量,在實(shí)際應(yīng)用中為提高數(shù)據(jù)采集的精度,并盡量減少外界電氣干擾還需要在傳感器與A/D芯片之間加上信號調(diào)理電路。
當(dāng)前項(xiàng)目是采用采用STM32+稱重模塊+OLED實(shí)現(xiàn)了簡單的電子秤項(xiàng)目,稱重模塊上采用24位的ADC芯片,精度較高。實(shí)現(xiàn)了稱重,校準(zhǔn)、去皮等功能。
硬件介紹:
MCU:STM32F103ZET6,只要是STM32F1X系列本工程代碼都通用的。
稱重模塊: 淘寶購買的稱重模塊
OLED: SPI接口的0.96寸OLED屏,采用的是中景園電子的OLED屏。
完整工程下載地址: https://download.csdn.net/download/xiaolong1126626497/63993934
視頻演示地址: https://live.csdn.net/v/182608
項(xiàng)目運(yùn)行效果:
2. 項(xiàng)目實(shí)現(xiàn)
2.1 稱重傳感器
稱重傳感器就是一個(gè)壓力傳感器,其又叫做懸臂梁壓力傳感器。安裝時(shí)需要一端固定,另一端受力。其內(nèi)部有四個(gè)應(yīng)變電阻片,共同組成了一個(gè)電橋,當(dāng)受力端施加壓力時(shí),傳感器殼體會(huì)發(fā)生形變,從而影響應(yīng)變電阻片的阻值。
下面是稱重傳感器的原理圖:
稱重傳感器實(shí)物圖:
稱重傳感
器是采用 CS1237 作為轉(zhuǎn)換芯片,用于把微小的電壓信號轉(zhuǎn)換為具有 24 位精度的數(shù)字信號。模塊信號輸入端可以接受差分信號,內(nèi)部具有可編程運(yùn)算放大器用于放大輸入端的弱小信號。模塊內(nèi)置溫度傳感器,可粗略估計(jì)周圍溫度。模塊可用于多種工業(yè)過程控制場合,比如電子秤,血液計(jì),智能變換器等。
CS1237中有1路ADC,集成了1路差分輸入,信號輸入可以是差分輸入信號AINP、AINN,也可以是溫度傳感器的輸出信號,輸入信號的切換由寄存器(ch_sel[1:0])控制。
CS1237是采用2線SPI串行通信,通過SCLK和DRDY/DOUT可以實(shí)現(xiàn)數(shù)據(jù)的接收以及功能配置。
實(shí)現(xiàn)代碼如下:
#include "ADC-CS1237.h"
static long AD_Res_Last=0; //上一輪的ADC數(shù)值保存
/*
定義CS1237使用的GPIO口
CLK PB14 時(shí)鐘線
OUT PB15 數(shù)據(jù)輸出線
*/
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進(jìn)入低功耗模式
void CS1237_Power_Down(void)
{
CLK_HIGH
delay_us(200); //CLK上拉時(shí)間應(yīng)超過100us,恢復(fù)后下拉時(shí)間至少10us
}
//配置CS1237芯片
void Con_CS1237(void)
{
u8 i;
u8 dat;
u8 count_i=0; //溢出計(jì)時(shí)器
dat = CS_CON; // 0100 1000
CS1237_DOUT();
OUT_HIGH
delay_ms(310); //上電建立時(shí)間
CS1237_DRDY(); //配置PB15為輸入
CLK_LOW //時(shí)鐘拉低
while(INT) //芯片準(zhǔn)備好數(shù)據(jù)輸出 時(shí)鐘已經(jīng)為0,數(shù)據(jù)也需要等CS1237全部拉低為0才算都準(zhǔn)備好
{
printf("123\r\n");
delay_ms(100); //10HZ下轉(zhuǎn)換時(shí)間是100ms
count_i++;
if(count_i > 150)
{
CLK_HIGH;
CS1237_DOUT();
OUT_HIGH
return; //超時(shí),則直接退出程序
}
}
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個(gè)脈沖了,寫8位數(shù)據(jù)
{
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個(gè)脈沖拉高數(shù)據(jù)引腳
}
//讀取芯片的配置數(shù)據(jù)
u8 Read_CON(void)
{
u8 i;
u8 dat=0; //讀取到的數(shù)據(jù)
u8 count_i=0; //溢出計(jì)時(shí)器
CS1237_DOUT();
OUT_HIGH
CS1237_DRDY(); //配置PB15為輸入
CLK_LOW //時(shí)鐘拉低
while(INT) //芯片準(zhǔn)備好數(shù)據(jù)輸出 時(shí)鐘已經(jīng)為0,數(shù)據(jù)也需要等CS1237全部拉低為0才算都準(zhǔn)備好
{
delay_ms(100);
count_i++;
if(count_i > 150)
{
CLK_HIGH;
CS1237_DOUT();
OUT_HIGH;
return 1; //超時(shí),則直接退出程序
}
}
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個(gè)脈沖了,讀取數(shù)據(jù)
{
One_CLK;
dat <<= 1;
if(INT)
dat++;
}
One_CLK; //46個(gè)脈沖拉高數(shù)據(jù)引腳
return dat;
}
//讀取ADC數(shù)據(jù),返回的是一個(gè)有符號數(shù)據(jù)
long Read_CS1237(void)
{
u8 i;
long dat=0; //讀取到的數(shù)據(jù)
u16 count_i=0; //溢出計(jì)時(shí)器
CS1237_DOUT();
OUT_HIGH //等待模擬輸入信號建立
CLK_LOW; //時(shí)鐘拉低
CS1237_DRDY();
while(INT) //芯片準(zhǔn)備好數(shù)據(jù)輸出 時(shí)鐘已經(jīng)為0,數(shù)據(jù)也需要等CS1237拉低為0才算都準(zhǔn)備好
{
// printf("等待1\r\n");
delay_ms(10);
count_i++;
if(count_i > 300)
{
CLK_HIGH;
CS1237_DOUT();
OUT_HIGH;
return 0; //超時(shí),則直接退出程序
}
}
CS1237_DOUT();
OUT_HIGH //端口鎖存1,
CS1237_DRDY();
dat=0;
for(i=0;i<24;i++) //獲取24位有效轉(zhuǎn)換
{
CLK_HIGH;
delay_us(6);
dat <<= 1;
if(INT)
dat ++;
CLK_LOW;
delay_us(6);
}
for(i=0;i<3;i++) //一共輸入27個(gè)脈沖
{
One_CLK;
}
CS1237_DOUT();
OUT_HIGH;
//先根據(jù)宏定義里面的有效位,丟棄一些數(shù)據(jù)
i = 24 - ADC_Bit;//i表示將要丟棄的位數(shù)
dat >>= i;//丟棄多余的位數(shù)
return dat;
}
//初始化ADC相關(guān)參數(shù)
int Init_CS1237(void)
{
Con_CS1237();//配置CS1237
if(Read_CON() != CS_CON)//如果讀取的ADC配置出錯(cuò),則重啟
{
printf("讀取錯(cuò)誤\r\n");
return 0;
}
delay_us(10000);
AD_Res_Last = Read_CS1237();
AD_Res_Last = Read_CS1237();
AD_Res_Last = Read_CS1237();
return 0;
}
//數(shù)字一階濾波器 濾波系數(shù)A,小于1。上一次數(shù)值B,本次數(shù)值C out = b*A + C*(1-A)
//下面這個(gè)程序負(fù)責(zé)讀取出最終ADC數(shù)據(jù)
long Read_18Bit_AD(void) //18位的
{
float out,c;
out = AD_Res_Last;
c = Read_CS1237();
if(c!=0) // 讀到正確數(shù)據(jù)
{
out = out*Lv_Bo + c*(1-Lv_Bo);
AD_Res_Last = out;//把這次的計(jì)算結(jié)果放到全局變量里面保護(hù)
}
return AD_Res_Last;
}
2.2 OLED顯示屏
OLED顯示屏是0.96寸 SPI接口顯示屏,采用SSD1306驅(qū)動(dòng),兼容3.3V或5V電源輸入,非常常見,淘寶一搜一大堆,當(dāng)前選擇的是中景園電子的OLED顯示屏。
在調(diào)試設(shè)備或者測試數(shù)據(jù)時(shí),有時(shí)候需要實(shí)時(shí)觀察數(shù)據(jù)的變化,加入顯示屏可以把觀察設(shè)備的運(yùn)行情況,數(shù)據(jù)變化等。在成本和難易程度上,OLED顯示屏是非常適合初學(xué)者去學(xué)習(xí)與應(yīng)用的。
OLED視頻實(shí)物圖:
示例代碼:
#include "OLED.H"
#include "oled_font.h"
/*
定義OLED使用的GPIO口
D0 PA5 時(shí)鐘線
D1 PA1 數(shù)據(jù)輸出線
RES PA2 復(fù)位線
DC PA3 數(shù)據(jù)/命令選擇線
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); //延遲,由于單片機(jī)上電初始化比OLED快,所以必須加上延遲,等待OLED上電初始化完成
OLED_RES_HIGH;
delay_ms(200);
OLED_WR_Byte(0xAE,OLED_CMD); //關(guān)閉顯示
OLED_WR_Byte(0x2e,OLED_CMD); //關(guān)閉滾動(dòng)
OLED_WR_Byte(0x00,OLED_CMD); //設(shè)置低列地址
OLED_WR_Byte(0x10,OLED_CMD); //設(shè)置高列地址
OLED_WR_Byte(0x40,OLED_CMD); //設(shè)置起始行地址
OLED_WR_Byte(0xB0,OLED_CMD); //設(shè)置頁地址
OLED_WR_Byte(0x81,OLED_CMD); // 對比度設(shè)置,可設(shè)置亮度
OLED_WR_Byte(0xFF,OLED_CMD); // 265
OLED_WR_Byte(0xA1,OLED_CMD); //設(shè)置段(SEG)的起始映射地址;column的127地址是SEG0的地址
OLED_WR_Byte(0xA6,OLED_CMD); //正常顯示;0xa7逆顯示
OLED_WR_Byte(0xA8,OLED_CMD); //設(shè)置驅(qū)動(dòng)路數(shù)
OLED_WR_Byte(0x3F,OLED_CMD); //1/64duty
OLED_WR_Byte(0xC8,OLED_CMD); //重映射模式,COM[N-1]~COM0掃描
OLED_WR_Byte(0xD3,OLED_CMD); //設(shè)置顯示偏移
OLED_WR_Byte(0x00,OLED_CMD); //無偏移
OLED_WR_Byte(0xD5,OLED_CMD); //設(shè)置震蕩器分頻(默認(rèn))
OLED_WR_Byte(0x80,OLED_CMD);
OLED_WR_Byte(0xD8,OLED_CMD); //設(shè)置 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); //設(shè)置 Pre-Charge Period(默認(rèn))
OLED_WR_Byte(0xF1,OLED_CMD);
OLED_WR_Byte(0xDA,OLED_CMD); //設(shè)置 com pin configuartion(默認(rèn))
OLED_WR_Byte(0x12,OLED_CMD);
OLED_WR_Byte(0xDB,OLED_CMD); //設(shè)置 Vcomh,可調(diào)節(jié)亮度(默認(rèn))
OLED_WR_Byte(0x30,OLED_CMD);
OLED_WR_Byte(0x8D,OLED_CMD); //設(shè)置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); //畫點(diǎn)
}
void OLED_Write_Byte(u8 data)
{
u8 i; //定義變量
for(i = 0; i < 8; i++) //循環(huán)8次
{
OLED_D0_LOW //將時(shí)鐘線拉低
delay_us(1); //延遲
if(data & 0x80) //數(shù)據(jù)從高位-->低位依次發(fā)送
OLED_D1_HIGH //數(shù)據(jù)為為1
else
OLED_D1_LOW //數(shù)據(jù)位為0
data <<= 1; //數(shù)據(jù)左移1位
OLED_D0_HIGH //時(shí)鐘線拉高,把數(shù)據(jù)發(fā)送出去
delay_us(1); //延遲
}
}
/*
@brief 對OLED寫入一個(gè)字節(jié)
@param dat:數(shù)據(jù)
cmd:1,寫誒數(shù)據(jù);0,寫入命令
@retval 無
*/
void OLED_WR_Byte(u8 dat,u8 cmd)
{
if(cmd) //如果cmd為高,則發(fā)送的是數(shù)據(jù)
OLED_DC_HIGH //將DC拉高
else //如果cmd為低,則發(fā)送的是命令
OLED_DC_LOW //將DC拉低
OLED_CS_LOW; //片選拉低,選通器件
OLED_Write_Byte(dat); //發(fā)送數(shù)據(jù)
OLED_CS_HIGH //片選拉高,關(guān)閉器件
OLED_DC_HIGH //DC拉高,空閑時(shí)為高電平
}
/*
@brief 設(shè)置數(shù)據(jù)寫入的起始行、列
@param x: 列的起始低地址與起始高地址;0x00~0x0f:設(shè)置起始列低地址(在頁尋址模式);
0x10~0x1f:設(shè)置起始列高地址(在頁尋址模式)
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); //寫入列的地址 低半字節(jié)
OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);//寫入列的地址 高半字節(jié)
}
/*
@brief 開顯示
@param 無
@retval 無
*/
void OLED_Display_On(void)
{
OLED_WR_Byte(0X8D,OLED_CMD); //設(shè)置OLED電荷泵
OLED_WR_Byte(0X14,OLED_CMD); //使能,開
OLED_WR_Byte(0XAF,OLED_CMD); //開顯示
}
/*
@brief OLED滾屏函數(shù),范圍0~1頁,水平向左
@param 無
@retval 無
*/
void OLED_Scroll(void)
{
OLED_WR_Byte(0x2E,OLED_CMD); //關(guān)閉滾動(dòng)
OLED_WR_Byte(0x27,OLED_CMD); //水平向左滾動(dòng)
OLED_WR_Byte(0x00,OLED_CMD); //虛擬字節(jié)
OLED_WR_Byte(0x00,OLED_CMD); //起始頁 0
OLED_WR_Byte(0x00,OLED_CMD); //滾動(dòng)時(shí)間間隔
OLED_WR_Byte(0x01,OLED_CMD); //終止頁 1
OLED_WR_Byte(0x00,OLED_CMD); //虛擬字節(jié)
OLED_WR_Byte(0xFF,OLED_CMD); //虛擬字節(jié)
OLED_WR_Byte(0x2F,OLED_CMD); //開啟滾動(dòng)
}
/*
@brief 關(guān)顯示
@param 無
@retval 無
*/
void OLED_Display_Off(void)
{
OLED_WR_Byte(0XAE,OLED_CMD); //關(guān)顯示
OLED_WR_Byte(0X8D,OLED_CMD); //設(shè)置OLED電荷泵
OLED_WR_Byte(0X10,OLED_CMD); //失能,關(guān)
}
/*
@brief 清屏
@param 無
@retval 無
*/
void OLED_Clear(void)
{
u8 i,n; //定義變量
for(i=0;i<8;i++)
{
OLED_WR_Byte (0xb0+i,OLED_CMD); //從0~7頁依次寫入
OLED_WR_Byte (0x00,OLED_CMD); //列低地址
OLED_WR_Byte (0x10,OLED_CMD); //列高地址
for(n=0;n<128;n++)
{
OLED_WR_Byte(0,OLED_DATA); //寫入 0 清屏
}
}
}
/*
@brief 顯示一個(gè)字符
@param x:起始列
y:起始頁,SIZE = 16占兩頁;SIZE = 12占1頁
chr:字符
@retval 無
*/
void OLED_ShowChar(u8 x,u8 y,u8 chr)
{
u8 c=0,i=0;
c=chr-' '; //獲取字符的偏移量
if(x>Max_Column-1){x=0;y=y+2;} //如果列數(shù)超出了范圍,就從下2頁的第0列開始
if(SIZE ==16) //字符大小如果為 16 = 8*16
{
OLED_Set_Pos(x,y); //從x y 開始畫點(diǎn)
for(i=0;i<8;i++) //循環(huán)8次 占8列
OLED_WR_Byte(F8X16[c*16+i],OLED_DATA); //找出字符 c 的數(shù)組位置,先在第一頁把列畫完
OLED_Set_Pos(x,y+1); //頁數(shù)加1
for(i=0;i<8;i++) //循環(huán)8次
OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA); //把第二頁的列數(shù)畫完
}
else //字符大小為 6 = 6*8
{
OLED_Set_Pos(x,y+1); //一頁就可以畫完
for(i=0;i<6;i++) //循環(huán)6次 ,占6列
OLED_WR_Byte(F6x8[c][i],OLED_DATA); //把字符畫完
}
}
/*
@brief 計(jì)算m^n
@param m:輸入的一個(gè)數(shù)
n:輸入數(shù)的次方
@retval result:一個(gè)數(shù)的n次方
*/
u16 oled_pow(u8 m,u8 n)
{
u16 result=1;
while(n--)result*=m;
return result;
}
/*
@brief 在指定的位置,顯示一個(gè)指定長度大小的數(shù)
@param x:起始列
y:起始頁
num:數(shù)字
len:數(shù)字的長度
size:顯示數(shù)字的大小
@retval 無
*/
void OLED_ShowNum(u8 x,u8 y,u16 num,u16 len,u16 size)
{
u8 t,temp; //定義變量
u8 enshow=0; //定義變量
for(t=0;t=128){x=0;y+=2;} //如果x大于等于128,切換頁,從該頁的第一列顯示
j++; //下一個(gè)字符
}
}
/*
@brief 顯示中文
@param x:起始列;一個(gè)字體占16列
y:起始頁;一個(gè)字體占兩頁
no:字體的序號
@retval 無
*/
void OLED_ShowCHinese(u8 x,u8 y,u8 no,u8 w,u8 h)
{
u8 t,k,addr0=(h/8)*no; //定義變量
for(k=0;k;t++)>
審核編輯:湯梓紅
-
STM32
+關(guān)注
關(guān)注
2264文章
10854瀏覽量
354299 -
電子秤
+關(guān)注
關(guān)注
23文章
211瀏覽量
44246
發(fā)布評論請先 登錄
相關(guān)推薦
評論