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[1:0])控制。
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顯示屏是非常適合初學者去學習與應用的。
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); //開顯示
}
/*
@brief OLED滾屏函數,范圍0~1頁,水平向左
@param 無
@retval 無
*/
void OLED_Scroll(void)
{
OLED_WR_Byte(0x2E,OLED_CMD); //關閉滾動
OLED_WR_Byte(0x27,OLED_CMD); //水平向左滾動
OLED_WR_Byte(0x00,OLED_CMD); //虛擬字節
OLED_WR_Byte(0x00,OLED_CMD); //起始頁 0
OLED_WR_Byte(0x00,OLED_CMD); //滾動時間間隔
OLED_WR_Byte(0x01,OLED_CMD); //終止頁 1
OLED_WR_Byte(0x00,OLED_CMD); //虛擬字節
OLED_WR_Byte(0xFF,OLED_CMD); //虛擬字節
OLED_WR_Byte(0x2F,OLED_CMD); //開啟滾動
}
/*
@brief 關顯示
@param 無
@retval 無
*/
void OLED_Display_Off(void)
{
OLED_WR_Byte(0XAE,OLED_CMD); //關顯示
OLED_WR_Byte(0X8D,OLED_CMD); //設置OLED電荷泵
OLED_WR_Byte(0X10,OLED_CMD); //失能,關
}
/*
@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 顯示一個字符
@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;} //如果列數超出了范圍,就從下2頁的第0列開始
if(SIZE ==16) //字符大小如果為 16 = 8*16
{
OLED_Set_Pos(x,y); //從x y 開始畫點
for(i=0;i<8;i++) //循環8次 占8列
OLED_WR_Byte(F8X16[c*16+i],OLED_DATA); //找出字符 c 的數組位置,先在第一頁把列畫完
OLED_Set_Pos(x,y+1); //頁數加1
for(i=0;i<8;i++) //循環8次
OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA); //把第二頁的列數畫完
}
else //字符大小為 6 = 6*8
{
OLED_Set_Pos(x,y+1); //一頁就可以畫完
for(i=0;i<6;i++) //循環6次 ,占6列
OLED_WR_Byte(F6x8[c][i],OLED_DATA); //把字符畫完
}
}
/*
@brief 計算m^n
@param m:輸入的一個數
n:輸入數的次方
@retval result:一個數的n次方
*/
u16 oled_pow(u8 m,u8 n)
{
u16 result=1;
while(n--)result*=m;
return result;
}
/*
@brief 在指定的位置,顯示一個指定長度大小的數
@param x:起始列
y:起始頁
num:數字
len:數字的長度
size:顯示數字的大小
@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++; //下一個字符
}
}
/*
@brief 顯示中文
@param x:起始列;一個字體占16列
y:起始頁;一個字體占兩頁
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
+關注
關注
2270文章
10896瀏覽量
355774 -
電子秤
+關注
關注
23文章
214瀏覽量
44313
發布評論請先 登錄
相關推薦
評論