Operácie

Jednoduchá kuchynská váha do 1kg

Zo stránky SensorWiki

Záverečný projekt predmetu MIPS / LS2025 - Martin Biacovský

Zadanie

Cieľom tohto projektu je zostrojiť jednoduchú váhu s kapacitou do 1kg za použitia mikrokontroléra (ACROB), HX711 (ADC) a snímača zaťaženia (load cell).

Vývojová doska ACROB
HX711
Snímač zaťaženia

Literatúra:


Analýza a opis riešenia

Nakoľko sa tento predmet nezameriava na Arduino, nie je povolené na projekt používať vývojové prostredie Arduino IDE, ani knižnice tohto prostredia.
Riešenie sa dialo v prostredí AVR studio a využívali sa knižnice z cvičení predmetu (konkrétne knižnica lcd).
Váha je zariadenie, ktoré meria hmotnosť predmetu. Pre naše účely je týmto predmetom sada závaží (200g, 100g, 50g, 20g).

Použité závažia

Úlohou mikrokontroléra bolo spracovávať všetky vstupy a na základe algoritmu produkovať výstupy.
Najvýraznejším výstupom je LCD displej EA-DOGM-163

LCD

Tento displej je v prevedení shield (modul na priame zapojenie na dosku bez nutnosti iného zapájania) a disponuje aj 1x LED dióda a 3x tlačidlo.
Pre naše účely nám postačí len displej (pozor na správne zapojenie jumper konektorov! )

Na meranie hmotnosti sme použili kombo HX711 a snímača zaťaženia.
Táto konfigurácia znamená, že sa nám stačí zaoberať len s HX711. Pre snímač zaťaženia je dôležitá len jeho maximálna nosnosť 1kg a smer záťaže.
HX711 je 24-bitový analógovo-digitálny prevodník priamo určený pre takéto váhy. Na jeho doske nám vychádzajú 4 pripojenia:
GND - ground, teda nulový potenciál
DT - sériový výstup
SCK - signál pre ADC, kedy má vysielať hodnoty
VCC - napájanie pre HX711 a to 5V
Kým VCC a GND majú určené miesto na doske mikrokontroléra, DT a SCK sa pripájajú na piny GPIO (General Purpose Input Output).
My sme použili pin D3 pre DT a D5 pre SCK.

Výsledok vyzerá následne:

Schéma zapojenia

Algoritmus a program

//libraries
#include <avr/io.h>
#include "lcd.h"
#include <util/delay.h>

#define F_CPU 16000000UL
#define DT PD3
#define SCK PD5

//delay function
void delay(int delay)
{
  for (int i=1; i<=delay; i++)
  _delay_ms(1);
}

//function for readout
long hx711_read() {
    long count = 0;

    //wait for DT to go LOW
    while (PIND & (1 << DT));

    //read 24 bits from HX711
    for (uint8_t i = 0; i < 24; i++)
	{
        PORTD |= (1 << SCK);
        _delay_us(1);
        count = count << 1;

        if (PIND & (1 << DT))
		{
            count++;
        }

        PORTD &= ~(1 << SCK);
        _delay_us(1);
    }

    //25th pulse to set gain = 128 (channel A)
    PORTD |= (1 << SCK);
    _delay_us(1);
    PORTD &= ~(1 << SCK);
    _delay_us(1);

    //convert 24-bit to signed long
    if (count & 0x800000)
	{
        count |= ~0xFFFFFF;
    }

    return count;
}

int main(void)
{
	//set input or output (input is default)
	DDRD &= ~(1 << DT);
    DDRD |= (1 << SCK);
	//make SCK LOW
	PORTD &= ~(1 << SCK);
	
	//initialize LCD (with backlight and no coursor)
	lcd_init();
	lcd_bklt(1);
	lcd_command(0x0C);	
	
	//get zeroed out value
	long weight;
	long init = hx711_read();
	
	//infinite cycle	
    while(1)
    {
		//offset = (value with mass - value without mass) / mass of known object in grams
			//(305320 - 220150) / 100 = 851,7 => 851
		//weight = ((current value - zeroed value) / offset) + manual adjustment
			//mass shown was -3g => add 3 as manual adjustment
		weight = ((hx711_read() - init) / 851) + 3;
		//LCD writeout
		lcd_puts("Hmotnost:");
		lcd_setCursor(1,0);
		char text[]= {"                "};	
		sprintf(text,"%ld",weight);
		lcd_puts(text);
		lcd_puts("g");
		_delay_ms(500);
		lcd_command(0x01);
		_delay_ms(2);
    }
}
//end of program
#ifndef LCD_H_
#define LCD_H_

#include <stdlib.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>

#define CHARACTER_BUFFER_BASE_ADDRESS 0x80
#define CHARACTERS_PER_ROW 16

#define DISPLAY_RS_PORT PORTD
#define DISPLAY_RS_DDR DDRD
#define DISPLAY_RS_PIN 2
#define DISPLAY_CSB_PORT PORTD
#define DISPLAY_CSB_DDR DDRD
#define DISPLAY_CSB_PIN 4

#define DISPLAY_BKLT_PORT PORTD
#define DISPLAY_BKLT_DDR DDRD
#define DISPLAY_BKLT_PIN 6

#define DISPLAY_RS_low   (DISPLAY_RS_PORT &= ~(1<<DISPLAY_RS_PIN))
#define DISPLAY_RS_high  (DISPLAY_RS_PORT |= (1<<DISPLAY_RS_PIN))

#define DISPLAY_CSB_low  (DISPLAY_CSB_PORT &= ~(1<<DISPLAY_CSB_PIN))
#define DISPLAY_CSB_high (DISPLAY_CSB_PORT |= (1<<DISPLAY_CSB_PIN))

#define DISPLAY_BKLT_low   (DISPLAY_BKLT_PORT &= ~(1<<DISPLAY_BKLT_PIN))
#define DISPLAY_BKLT_high  (DISPLAY_BKLT_PORT |= (1<<DISPLAY_BKLT_PIN))


//instructions (see the ST7036 instruction set for further information)
#define INSTRUCTION_CLEAR_DISPLAY    		0b00000001
#define INSTRUCTION_FUNCTION_SET_INIT_0  	0b00110011
#define INSTRUCTION_FUNCTION_SET_INIT_1  	0b00110010
#define INSTRUCTION_FUNCTION_SET_INIT_2  	0b00101001
#define INSTRUCTION_INSTRUCTION_SET_0    	0b00101000
#define INSTRUCTION_INSTRUCTION_SET_1 		0b00101001
#define INSTRUCTION_BIAS_SET				0b00010101
#define INSTRUCTION_POWER_CONTROL 			0b01010011
#define INSTRUCTION_FOLLOWER_CONTROL		0b01101100
#define INSTRUCTION_CONTRAST_SET			0b01111111
#define INSTRUCTION_DISPLAY_ON				0b00001100
#define INSTRUCTION_ENTRY_MODE 				0b00000110

void lcd_init(void);
void lcd_write( char data );
void lcd_data( char data );     
void lcd_command( char instruction );

void lcd_putc( char znak );
void lcd_puts( char *string );

void lcd_clear( void );
void lcd_clearline( unsigned char zeile );

void lcd_setCursor(char row, char col);
void lcd_bklt( char OnOff);

void def_znak(unsigned char *ZnakArray, unsigned char Address);

#endif /* LCD_H_ */
#include "lcd.h"

/* Primitivne funkcie, nepredpoklada sa ich vyuzitie uzivatelom */

/* Funkcia zapise jeden bajt po SPI zbernici do zariadenia */
void lcd_write( char data )
{
 signed char index = 8;
        char c_data;

 DISPLAY_CSB_PORT &= ~(1<<DISPLAY_CSB_PIN); 	// Chip-Select do log.0 
 c_data = data;

 do
 {
  _delay_us(6);
  if ( c_data & 0x80 )		// najyssi bit zamaskujeme 
	   PORTB |= (1<<3);		// a posleme ho na zbernicu
  else
	   PORTB &= ~(1<<3);    // cez vodic MOSI (alebo len SI)
  _delay_us(5);			    // a vygenerujeme jeden hodinovy pulz
       PORTB &= ~(1<<5);     // na vyvod CLK
  _delay_us(6);
	   PORTB |= (1<<5);    
	
   c_data = c_data << 1;    // na najvyssie miesto posunieme dalsi bit
   index--;

  } while (index > 0);      // a toto spravime dokola 8x

	                        // a napokon zdvihneme /CS do log.1
	_delay_ms( 2 );
	DISPLAY_CSB_PORT |= (1<<DISPLAY_CSB_PIN);   
}


/* Funkcia zapise jeden byte do riadiaceho (Control) registra */
void lcd_command( char instruction )
{
	DISPLAY_RS_PORT &= ~(1<<DISPLAY_RS_PIN);
	_delay_us( 1 );
	lcd_write( instruction );
}


/* Funkcia zapise jeden byte do datoveho (Data) registra */
void lcd_data( char data )
{
	DISPLAY_RS_PORT |=  1<<DISPLAY_RS_PIN;
	_delay_us( 7 );
	lcd_write( data );
}


/* Uzivatelske funkcie, ktore ma pouzivat uzivatel */



/* Inicializacia displeja podla datasheetu pre radic ST7036 */
void lcd_init(void)
{  
	
              DDRB |= (1<<PB3) | (1<<PB5);	   // MOSI + SCK as OUTPUTs
    DISPLAY_RS_DDR |= 1<<DISPLAY_RS_PIN;       // All signals as OUTPUT
   DISPLAY_CSB_DDR |= 1<<DISPLAY_CSB_PIN;
  DISPLAY_BKLT_DDR |= 1<<DISPLAY_BKLT_PIN;
	
     	
             PORTB |= (1<<PB5);               // set_bit( ST7036_CLK );
  DISPLAY_CSB_PORT |= (1<<DISPLAY_CSB_PIN);   // set_bit( ST7036_CSB );
   DISPLAY_RS_PORT &= ~(1<<DISPLAY_RS_PIN);   // set_bit( ST7036_CSB );
  
  _delay_ms(50);		// pockame viac ako 40ms na ustalenie napajacieho napatia

   lcd_command( 0x39 );	// Function set; 8-bit Datenlänge, 2 Zeilen, Instruction table 1
  _delay_us(50);		// mehr als 26,3µs warten

  lcd_command( 0x1d );	// Bias Set; BS 1/5; 3 zeiliges Display /1d
 _delay_us(50);		// mehr als 26,3µs warten

  lcd_command( 0x50 );	// Booster aus; Kontrast C5, C4 setzen /50
 _delay_us(50);		// mehr als 26,3µs warten

  lcd_command( 0x6c );	// Spannungsfolger und Verstärkung setzen /6c
 _delay_ms( 500 );	// mehr als 200ms warten !!!
 
  lcd_command( 0x7c );	// Kontrast C3, C2, C1 setzen /7c
 _delay_us(50);		// mehr als 26,3µs warten

  lcd_command( 0x38 );	// Display EIN, Cursor EIN, Cursor BLINKEN /0f
 _delay_us(50);		// mehr als 26,3µs warten

  lcd_command( 0x0f );	// Display EIN, Cursor EIN, Cursor BLINKEN /0f
  _delay_us(50);		// mehr als 26,3µs warten

  lcd_command( 0x01 );	// Display löschen, Cursor Home
 _delay_ms(400);		//

  lcd_command( 0x06 );	// Cursor Auto-Increment
 _delay_us(50);		// mehr als 26,3µs warten

}

/* Funkcia zobrazi na pozicii kurzoru jeden znak */
/* Alias funkcia z dovodov kompatibility         */
void lcd_putc( char znak )
{
	lcd_data(znak);
}


/* Funkcia zobrazi na displeji nejaky text*/
void lcd_puts(char *string)
{
  while(*string!='\0')
  {
     lcd_data(*string);
     string++;
  } 
}

/* Funkcia nastavi kurzor na poziciu riadok, stlpec */ 
/*   a text sa samozrejme vypise od kurzora dalej.  */
void lcd_setCursor(char row, char col)
{
	unsigned char address;

    if (row == 0)
        address = 0x00 + col;
    else if (row == 1)
        address = 0x10 + col;
	else if (row == 2)
        address = 0x20 + col;
	else if (row == 3)
        address = 0x30 + col;
    else
        return; // Nepodporovaný riadok

    lcd_command(0x80 | address); // Nastav DDRAM adresu
}

void lcd_clearline( unsigned char riadok )
{
 unsigned char index;
 lcd_setCursor( riadok, 0 );
 for (index=1; index<20; index++) lcd_data( ' ' );
}

void lcd_clear( void )
{
	lcd_clearline( 0 );
	lcd_clearline( 1 );
	lcd_clearline( 2 );
}

/* Funkcia zapne alebo vypne podsvietenie displeja */
void lcd_bklt( char OnOff)
{
  if (OnOff) 
     DISPLAY_BKLT_high;
  else
     DISPLAY_BKLT_low;
}

/* Funkcia ulozi uzivatelom definovany znak do CG RAM na Address */
void def_znak(unsigned char *ZnakArray, unsigned char Address)
{
  lcd_command(0x40|(Address<<3));  //nastavenie adresy znaku v CGRAM
  for(unsigned char i = 0;i<8; i++) lcd_data( *(ZnakArray + i));
  lcd_command(0x80);               // zmena na DD RAM
}

Zdrojový kód: Biacovsky_1kg.zip

Algoritmus je to čo ovláda hardvér. Sú to naše inštrukcie pre zariadenia.
Ako prvé priložíme knižnice pre I/O mikrokontroléra, LCD a oneskorenie (ATmega 328P inak podporuje max. 16ms)
a zadefinujeme frekvenciu procesora, ako aj GPIO pre DT a SCK.
Funkcia delay opakuje oneskornie 1ms dovtedy, kým sa nedosiahne požadované oneskorenie.
Funkcia hx711_read sa stará o prečítanie hodnôt z HX711.
V skratke nastaví požadované podmienky, prečíta 24 bitov, skončí prenos, prevedie hodnoty na kladný long a vráti hodnotu.
Long je nutný z toho dôvodu, že int podporuje len 16 bitov, čo je menej ako 24. Long podporuje 32.
Funkcia main je časť programu, kde nastavujeme úvodné stavy a definujeme premenné.
DT nastavíme ako input, SCK ako output, zaručíme aby bol SCK LOW, inicializujeme a nastavíme LCD s podsvietením bez ukazovateľa a vyžiadame aktuálnu hodnotu.
Cyklus while beží donekonečna a vykonáva načítanie hodnoty, výpočet hmotnosti v gramoch, výpis na LCD, čakanie a obnovenie LCD.
Táto konfigurácia nám umožňuje vykonať vynulovanie váhy s aktuálnou hmotnosťou, čo je vidno aj v priloženej video ukážke. Urobíme to stlačením tlačidla reset.

Dodadok ku riešeniu

Teraz máme aj zapojenie aj program a všetko je funkčné... skoro.
Takéto snímače zaťaženia vyžadujú kalibráciu.
weight = ((hx711_read() - init) / offset) + adjustment;
Tento riadok kódu vykonáva výpočet na gramy. hx711_read a init sú hodnoty z HX711. offset a adjustment sú nami zadané hodnoty.
offset je ten dôležitý. Predstavuje konštantu nášho snímača zaťaženia a treba ju dostať pre náš konkrétny kus.
Upravíme kód tak, aby sme mali vypísanú hodnotu vrátenú z funkcie hx711_read, napr. vykomentovaním riadka weight = ... a nahradením volaním funkcie weight = hx711_read();
Budeme pritom potrebovať predmet so známou hmotnosťou, napr. 100g závažie. Prečítame hodnotu so závažím, odčítame hodnotu bez závažia a výsledok vydelíme hmotnosťou závažia v gramoch. Výsledok zaokrúhlime na celé číslo.
Následne vrátime kód do pôvodného stavu, zadáme našu konštantu a zameníme hodnotu adjustment, aby sa 0g rovnalo 0g.
Úpravy offset a adjustment už ďalej podľa potreby.
Inštrukcie sú v kóde.

Overenie

Overenie funkčosti pozostáva z váženia známych hmotností.
Pre overenie funkcie nulovania stačí stlačiť tlačidlo reset na doske ACROB.
Niekedy môžu byť hodnoty "na hrane", čo spôsobí odchýlku alebo preskakovanie hodnoty na LCD. Toto sa zvyčajne objavuje pri nižších hodnotách.
Funkčné demo je vidno na videu:



Kľúčové slová 'Category', ktoré sú na konci stránky nemeňte.