Operácie

Senzor teploty a vlhkosti SHT31: Rozdiel medzi revíziami

Zo stránky SensorWiki

StudentMIPS (diskusia | príspevky)
Bez shrnutí editace
StudentMIPS (diskusia | príspevky)
Bez shrnutí editace
Riadok 31: Riadok 31:
Ak sa nám už správne podarilo pripojiť senzor aj displej, hardvérová časť nášho zadania je hotová.
Ak sa nám už správne podarilo pripojiť senzor aj displej, hardvérová časť nášho zadania je hotová.
Ďalej nasleduje programová časť. Pri tejto časti budeme používať datasheety, priložené vyššie. Ak chceme aby náš senzor vypisoval informácie na displej, budeme potrebovať knižnicu pre LCD displej. Pre výpis cez sériový kanál na obrazovku budeme potrebovať knižnicu UART. A pre použitie samotného senzora budeme ešte potrebovať používať knižnicu pre I2C zbernicu, ktorá slúži na prenos dát zo senzoru a na ich čítanie.
Ďalej nasleduje programová časť. Pri tejto časti budeme používať datasheety, priložené vyššie. Ak chceme aby náš senzor vypisoval informácie na displej, budeme potrebovať knižnicu pre LCD displej. Pre výpis cez sériový kanál na obrazovku budeme potrebovať knižnicu UART. A pre použitie samotného senzora budeme ešte potrebovať používať knižnicu pre I2C zbernicu, ktorá slúži na prenos dát zo senzoru a na ich čítanie.
Samotný program budeme písať podľa datasheetu, kde si naštudujeme ako náš senzor komunikuje s mikroprocesorom a ako z neho čítať dáta. Na to nám slúži tento obrázok nižšie z datasheetu, ktorý opisuje ako správne komunikovať a vyčítavať zo senzoru informácie.  
Samotný program budeme písať podľa datasheetu, kde si naštudujeme ako náš senzor komunikuje s mikroprocesorom a ako z neho čítať dáta. Na to nám slúži obrázok priloženy nižšie z datasheetu, ktorý opisuje ako správne komunikovať a vyčítavať informácie zo senzoru.  


[[Súbor:CITANIE_DAT_SHT31.png|400px|thumb|center|Algoritmus čítania dát zo senzoru.]]
[[Súbor:CITANIE_DAT_SHT31.png|400px|thumb|center|Algoritmus čítania dát zo senzoru.]]


Na začiatku začneme posielať I2C adresu a hodnotu, ktorou nastavíme, že chceme zapisovať. Po prijatí ACK (acknowledge), nastavíme MSB (most significant bit) a s ním si pre jednoduchosť vypneme Clock stretching. Ďalej nastavíme LSB (least significant bit), ktorým si volíme repeatebility. Ďalej pošleme informáciu o adrese a hodnote a počkáme na ACK, inak posielanie opakujeme v cykle až kým nedostaneme odpoveď. Keď odpoveď dostaneme, môžeme si do bufferu vyčítať dáta ktoré nám prišli zo senzoru. Tieto dáta nám prídu v dvoch častiach. Jedna nám hovorí o teplote a druhá o vlhkosti. Pomocou vzorčekov, ktoré si nájdeme v datasheete si údaje zo senzoru prepočítame a za pomoci nami vytvorenej funkcie na výpočet a výpis ich môžeme vypísať na displej ale aj cez sériovú linku na obrazovku.
Na začiatku začneme posielať I2C adresu a hodnotu, ktorou nastavíme, že chceme zapisovať dáta. Po prijatí ACK (acknowledge), nastavíme MSB (most significant bit) a s ním si pre jednoduchosť vypneme Clock stretching. Ďalej nastavíme LSB (least significant bit), ktorým si volíme repeatebility. Ďalej pošleme informáciu o adrese a hodnote a počkáme na ACK, inak posielanie opakujeme v cykle až kým nedostaneme odpoveď. Keď odpoveď dostaneme, môžeme si do bufferu vyčítať dáta ktoré nám prišli zo senzoru. Tieto dáta nám prídu v dvoch častiach. Jedna nám hovorí o teplote a druhá o vlhkosti. Pomocou vzorčekov, ktoré si nájdeme v datasheete si údaje zo senzoru prepočítame a za pomoci nami vytvorenej funkcie na výpočet a výpis ich môžeme vypísať na displej ale aj cez sériovú linku na obrazovku.


=== Algoritmus a program ===
=== Algoritmus a program ===
Riadok 138: Riadok 138:
return (0);
return (0);
}
}
</source></tab>
<tab name="lcd_ch.c"><source lang="c++" style="background: LightYellow;">
#include "lcd_ch.h"
void lcd_data(unsigned char);
void lcd_command(unsigned char);
void ini_ports(void);
void En_imp(void);
void wr_data(unsigned char);
unsigned char busy_flag(void);
int8_t lcd_read_AC(void);
void zob_text(char *);
void def_Clear_spec_znaky(void);
void def_znak(unsigned char *, unsigned char);
void def_spec_znaky(void);
unsigned char Znak_OO[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0}; // Prazdny znak dole su tri bodky
unsigned char Znak_SC[8] = {0x1A, 0x1D, 0x04, 0x04, 0x04, 0x05, 0x02, 0}; // st.C
unsigned char Znak_SM[8] = {0x0A, 0x04, 0x0E, 0x10, 0x0E, 0x01, 0x1E, 0}; //  s + mekcen
unsigned char Znak_CM[8] = {0x0A, 0x04, 0x0E, 0x10, 0x10, 0x11, 0x0C, 0}; //  c + mekcen
// Vynulovanie CGRAM
void def_Clear_spec_znaky(void)
{
// zapis specialneho znaku do CGRAM  na poziciu 0, a 7
for (char i = 0; i < 8; i++)
def_znak(Znak_OO, i);
lcd_command(0x80);
}
void def_spec_znaky(void)
{
// zapis vlastnych znakkov do CGRAM  na poziciu 4, a 0 (8)
def_znak(Znak_SC, 4); // stupen Celzia
def_znak(Znak_SM, 0); // s + makcen
// obnovenie obsahu AC. AC nastvime na 1. riadok, 1. znak
lcd_command(0x80);
}
void def_spec_znaky_AC(void)
{
// zapis specialneho znaku do CGRAM  na poziciu 7
unsigned char obsah_AC = 0;
obsah_AC = lcd_read_AC(); //
// kon_vyp = obsah_AC;
// sem dame nove vlastne znaky
def_znak(Znak_CM, 7); // c + makcen
  // obnovenie obsahu AC. AC
lcd_command(0x80 | obsah_AC);
}
void ini_ports(void)
{
/* inicializacia portov - vstupy / vystupy
oba typy displaja */
// nasledovna # riadky su spolocne pre oba typy LCD
LCD_CTRL_DDR |= (1 << LCD_EN_pin);   //  (Enable)    output
LCD_CTRL_DDR |= (1 << LCD_RS_pin);   //  (RS)        output
LCD_DATA_DDR |= (1 << LCD_D4_pin) | (1 << LCD_D5_pin) | (1 << LCD_D6_pin) | (1 << LCD_D7_pin); // Datove piny ako Output (Data pre display)
// klasicky LCD vyuziva aj RW pin
LCD_CTRL_DDR |= (1 << LCD_RW_pin); // (RW)        output
}
void EnOn(void)
{
LCD_CTRL_PORT |= (1 << LCD_EN_pin); // -> "log.1"
LCD_DELAY; // cca 400 ns
}
void EnOff(void)
{
LCD_CTRL_PORT &= ~(1 << LCD_EN_pin); // -> "log.0"
LCD_DELAY;
}
void En_imp(void)
{
LCD_CTRL_PORT |= (1 << LCD_EN_pin); // -> "log.1"
LCD_DELAY; // cca 400 ns
LCD_CTRL_PORT &= ~(1 << LCD_EN_pin); // -> "log.0"  spolu cca 500 ns
}
void lcd_init(void)
{ // 4 bitove pripojenie display-a
ini_ports(); // inicializacia porov
_delay_ms(15);
// 1. -------------------
LCD_CTRL_PORT &= ~(1 << LCD_RS_pin); // set RS  = to "log. 0"  Instruction Register (IR)
LCD_CTRL_PORT &= ~(1 << LCD_RW_pin); // set R/W = to "log. 0" - Write
// RS R/W  DB7 DB6 DB5 DB4  DB3 DB2 DB1 DB0
// 0  0    0  0  1  1    x  x  x  x    = 0x30
PORT_DATA_WR_H(0x30); // 8-bitove pripojenie
En_imp();
// 2. -------------------
// zopakujem  8-bitove pripojenie
_delay_ms(5);
En_imp();
// 3. -------------------
// zopakujem  8-bitove pripojenie
_delay_ms(1);
En_imp(); // -  stacilo by delay 0.1ms, momentalne najkratsie nastavitelne je 1ms
// 4. -------------------
// zmenim na 4-bitove pripojenie
// RS R/W    DB7 DB6 DB5 DB4    DB3 DB2 DB1 DB0
// 0  0      0  0  1  0      x  x  x  x    = 0x20
PORT_DATA_WR_H(0x20); // 4-bitove pripojenie
_delay_ms(1);
En_imp(); // -  stacilo by delay 0.04ms
// -------------------
// LCD function set      mod: DL = 0 - 4-bitove data, N = 1 - 2 riadky, F = 0 - 5x7 dots
// RS R/W    DB7 DB6 DB5 DB4    DB3 DB2 DB1 DB0
// 0  0      0  0  1  DL    N  F  x  x    = 0x28
// Mozeme nasledujuci prikaz pre LCD vynechat?
_delay_ms(2);
lcd_command(0x28);
// RS R/W    DB7 DB6 DB5 DB4    DB3 DB2 DB1 DB0
// 0  0      0  0  0  0     0  0  0  1    = 0x01 , Display clear
// Mozeme nasledujuci prikaz pre LCD vynechat?
_delay_ms(2);
lcd_command(0x01);
// RS R/W    DB7 DB6 DB5 DB4    DB3 DB2 DB1 DB0
// 0  0      0  0  0  0     0  1  I/D S      = 0x06, I/D = 1 - inkrement
// Mozeme nasledujuci prikaz pre LCD vynechat?
_delay_ms(2);
lcd_command(0x02);
// RS R/W    DB7 DB6 DB5 DB4    DB3 DB2 DB1 DB0
// 0  0      0  0  0  0     1  D  C  B      = 0x0E, D = C = 1 - Display a kurzor zapnute
// Mozeme nasledujuci prikaz pre LCD vynechat?
_delay_ms(2);
lcd_command(0x0E); // B = 0 - blikanie vypnute
}
// zapis data do Data Register
void lcd_data(unsigned char data)
{
while (busy_flag() & 0x80)
;
LCD_CTRL_PORT |= (1 << LCD_RS_pin); //    (RS = High)
LCD_CTRL_PORT &= ~(1 << LCD_RW_pin); //    (RW = Low, write)
wr_data(data);
}
// zapis commandu do Instruction Register
void lcd_command(unsigned char command)
{
while (busy_flag() & 0x80)
;
// while(rd_BF()); // test BF sa da realizovat pre klasicky LCD, nie Shield
LCD_CTRL_PORT &= ~(1 << LCD_RS_pin); //  (RS = Low)
LCD_CTRL_PORT &= ~(1 << LCD_RW_pin); //  (RW = Low, write)
wr_data(command);
}
void wr_data(unsigned char data)
{
PORT_DATA_WR_H(data); // data High nibble
En_imp();
PORT_DATA_WR_L(data); // data Low nibble
En_imp();
}
// klasicke pripojenie LCD umozni aj test BF
unsigned char busy_flag(void)
{ //  rd_BF
unsigned char pom = 0;
LCD_CTRL_PORT &= ~(1 << LCD_RS_pin);   //  (RS = Low)
LCD_CTRL_PORT |= (1 << LCD_RW_pin);   //    (RW = High, read)
  // port B, datove bity budu teraz input
LCD_DATA_DDR &= ~((1 << LCD_D4_pin) | (1 << LCD_D5_pin) | (1 << LCD_D6_pin) | (1 << LCD_D7_pin)); //  Datove piny nastavime  na input, (Data pre disp)
// Spravne by sa malo vycitavaj pred dobeznou hranou EN impulzu. Je tam urcity presah. Mozeme vycitat aj po dobeznej hrane.
// Spravne vycitavanie pocas EN impulzu.
EnOn();
pom = PORT_DATA_RD_H; // vycitam High nibble AC
EnOff();
EnOn();
pom |= PORT_DATA_RD_L; // vycitam Low nibble AC
EnOff();
// datove bity zase output
LCD_DATA_DDR |= (1 << LCD_D4_pin) | (1 << LCD_D5_pin) | (1 << LCD_D6_pin) | (1 << LCD_D7_pin); // Datove piny nastavime na output (Data pre disp)
// if(pom & 0x80) return 1; // display je busy
// else
return pom; // display je not busy
}
void zob_text(char *s)
{
register unsigned char c;
while ((c = *s++))
lcd_data(c); // retazec konci "nulou"
}
void def_znak(unsigned char *ZnakArray, unsigned char kam)
{
lcd_command(0x40 | (kam << 3)); // nastavenie adresy znaku v CGRAM
for (unsigned char i = 0; i < 8; i++)
lcd_data(*(ZnakArray + i));
}
void lcd_putc(char ch)
{
switch (ch)
{
case '\r':
lcd_command(0xC0); // Presun na za?iatok 2. riadka (0x80 + 0x40)
break;
case '\f':
lcd_command(0X80); // Presun na zaciatok 1. riadku
break;
default:
lcd_data(ch);
}
}
/* ******************************************************** */
/* vypis retazca na poziciu, resp. podla nasledovnych */
/* formatovacich prikazov                                      */
/*  : \r - prechod na novy riadok                              */
/*  : \f - prechod na zaciatok displeja                        */
/* ********************************************************    */
void lcd_puts(char *str)
{ /* definicia funkcie */
while (*str)
{
lcd_putc(*str++);
}
}
int8_t lcd_read_AC(void)
{ //  rd_BF
char pom_AC;
while ((pom_AC = busy_flag()) & 0x80)
;
// kedze po BF = 0 este cca 4us sa nezmenil obsah AC
// treba vycitat este raz
pom_AC = busy_flag();
return pom_AC; // display not busy
}
</source></tab>
<tab name="lcd_ch.h"><source lang="c++" style="background: LightYellow;">
/*
* lcd_ch.h
*
* Created: 3/10/2021 7:05:39 PM
*  Author: Admin
*/
#ifndef F_CPU
#define F_CPU 16000000UL /* Define CPU frequency here 16MHz */
#endif
#ifndef LCD_CH_H_
#define LCD_CH_H_
#include <avr/io.h>
#include <util/delay.h>
// LCD  klasik zapojenie vid. MIPS
//  - http://senzor.robotika.sk/sensorwiki/index.php/LCD_displej_s_radi%C4%8Dom_HD44780
#define LCD_CTRL_DDR DDRD
#define LCD_CTRL_PORT PORTD
#define LCD_DATA_DDR DDRB
#define LCD_DATA_PORT PORTB
#define LCD_DATA_PIN PINB
#define LCD_RS_pin 2
#define LCD_RW_pin 3
#define LCD_EN_pin 4
#define LCD_D4_pin 1
#define LCD_D5_pin 2
#define LCD_D6_pin 3
#define LCD_D7_pin 4
// Oneskorenie 6 SC
#define NOP() asm("nop")
#define LCD_DELAY \
    NOP();        \
    NOP();        \
    NOP();        \
    NOP();        \
    NOP();        \
    NOP();
#define PORT_DATA_WR_H(x)        \
    LCD_DATA_PORT &= 0b11100001; \
    LCD_DATA_PORT |= (x & 0xF0) >> 3
#define PORT_DATA_WR_L(x)        \
    LCD_DATA_PORT &= 0b11100001; \
    LCD_DATA_PORT |= (x & 0x0F) << 1
#define PORT_DATA_RD_H ((LCD_DATA_PIN & ((1 << LCD_D4_pin) | (1 << LCD_D5_pin) | (1 << LCD_D6_pin) | (1 << LCD_D7_pin))) << 3)
#define PORT_DATA_RD_L ((LCD_DATA_PIN & ((1 << LCD_D4_pin) | (1 << LCD_D5_pin) | (1 << LCD_D6_pin) | (1 << LCD_D7_pin))) >> 1)
void lcd_init(void);
void lcd_putc(char ch);
void lcd_puts(char *str);
#endif /* LCD_CH_H_ */
</source></tab>
<tab name="uart.c"><source lang="c++" style="background: LightYellow;">
/* ************************************************************************* */
/* FileName:            uart.c                                              */
/* ************************************************************************* */
#include <avr/io.h>
#include <util/delay.h>
#include "uart.h"
void hw_init( void )
{
  DDRB |= (1<<LED);    // PORTB.5 kde je LED ma byt OUTPUT
  /* sem si mozete dopisat svoje vlastne inicializacne prikazy */
}
void uart_init( void )
{
//  for different BAUD rate change the project settings, or uncomment
//  following two lines:
// #undef  BAUD          // avoid compiler warning
//  #define BAUD 115200
  #include <util/setbaud.h>  // requires defined BAUD
 
  UBRR0H = UBRRH_VALUE;
  UBRR0L = UBRRL_VALUE;
  #if USE_2X                // defined in setbaud.h
    UCSR0A |= (1 << U2X0);
  #else
    UCSR0A &= ~(1 << U2X0);
  #endif
    UCSR0C = _BV(UCSZ01) | _BV(UCSZ00); /* 8-bit data */
    UCSR0B = _BV(RXEN0) | _BV(TXEN0);  /* Enable RX and TX */
}
int uart_putc( char c, FILE *stream )
{
  if (c == '\n')
      uart_putc('\r',stream);
 
  loop_until_bit_is_set(UCSR0A, UDRE0); /* Wait until data register empty. */
  UDR0 = c;
  return 0;
}
void uart_puts(const char *s, FILE *stream)
{
while(*s)
{
uart_putc(*s++, stream);
}
}
char uart_getc(void)
{
    loop_until_bit_is_set(UCSR0A, RXC0); /* Wait until data exists. */
    return UDR0;
}
void delay(int delay)      // vlastna funkcia pre dlhsie casy
{
  for (int i=1; i<=delay; i++)
  _delay_ms(1);
}
</source></tab>
<tab name="uart.h"><source lang="c++" style="background: LightYellow;">
/* ************************************************************************* */
/* FileName:            uart.h                                              */
/* ************************************************************************* */
#define LED PB5  // internal on-board LED
/* na testovanie su uz zadefinovane */
// bit_is_set(PINB, SW1)
// bit_is_clear(PINB, SW1)
/* na cakanie su preddefinovane slucky */
// loop_until_bit_is_set(PINB, SW1);    // cakanie na uvolnenie tlacitka
// loop_until_bit_is_clear(PINB, SW1);  // cakanie na stlacenie tlacitka
#define set_bit(ADDRESS,BIT) (ADDRESS |= (1<<BIT))
#define clear_bit(ADDRESS,BIT) (ADDRESS &= ~(1<<BIT))
#ifndef UART_H_
#define UART_H_
#include <stdio.h>
#define BAUD_PRESCALE  (((F_CPU / (BAUDRATE * 16UL))) - 1)  // vzor?ek z datasheetu
void hw_init( void );
void uart_init( void );
   
/* Following definition is compatible with STDIO.H, for more
* information see https://www.appelsiini.net/2011/simple-usart-with-avr-libc/
*/
int uart_putc( char c, FILE *stream );
void uart_puts( const char *s, FILE *stream);
char uart_getc( void );
void delay(int delay);
#endif /* UART_H_ */
</source></tab>
<tab name="i2cmaster.c"><source lang="c++" style="background: LightYellow;">
/*************************************************************************
* Title:    I2C master library using hardware TWI interface
* Author:  Peter Fleury <pfleury@gmx.ch>  http://jump.to/fleury
* File:    $Id: twimaster.c,v 1.4 2015/01/17 12:16:05 peter Exp $
* Software: AVR-GCC 3.4.3 / avr-libc 1.2.3
* Target:  any AVR device with hardware TWI
* Usage:    API compatible with I2C Software Library i2cmaster.h
**************************************************************************/
#include <inttypes.h>
#include <compat/twi.h>
#include "i2cmaster.h"
/* define CPU frequency in hz here if not defined in Makefile */
#ifndef F_CPU
#define F_CPU 16000000UL
#endif
/* I2C clock in Hz */
#define SCL_CLOCK  100000L
/*************************************************************************
Initialization of the I2C bus interface. Need to be called only once
*************************************************************************/
void i2c_init(void)
{
/* initialize TWI clock: 100 kHz clock, TWPS = 0 => prescaler = 1 */
TWSR = 0;                        /* no prescaler */
TWBR = ((F_CPU / SCL_CLOCK) - 16) / 2;  /* must be > 10 for stable operation */
}/* i2c_init */
/*************************************************************************
  Issues a start condition and sends address and transfer direction.
  return 0 = device accessible, 1= failed to access device
*************************************************************************/
unsigned char i2c_start(unsigned char address)
{
uint8_t  twst;
// send START condition
TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
// wait until transmission completed
while (!(TWCR & (1 << TWINT)));
// check value of TWI Status Register. Mask prescaler bits.
twst = TW_STATUS & 0xF8;
if ((twst != TW_START) && (twst != TW_REP_START)) return 1;
// send device address
TWDR = address;
TWCR = (1 << TWINT) | (1 << TWEN);
// wail until transmission completed and ACK/NACK has been received
while (!(TWCR & (1 << TWINT)));
// check value of TWI Status Register. Mask prescaler bits.
twst = TW_STATUS & 0xF8;
if ((twst != TW_MT_SLA_ACK) && (twst != TW_MR_SLA_ACK)) return 1;
return 0;
}/* i2c_start */
/*************************************************************************
Issues a start condition and sends address and transfer direction.
If device is busy, use ack polling to wait until device is ready
Input:  address and transfer direction of I2C device
*************************************************************************/
void i2c_start_wait(unsigned char address)
{
uint8_t  twst;
while (1)
{
// send START condition
TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
// wait until transmission completed
while (!(TWCR & (1 << TWINT)));
// check value of TWI Status Register. Mask prescaler bits.
twst = TW_STATUS & 0xF8;
if ((twst != TW_START) && (twst != TW_REP_START)) continue;
// send device address
TWDR = address;
TWCR = (1 << TWINT) | (1 << TWEN);
// wail until transmission completed
while (!(TWCR & (1 << TWINT)));
// check value of TWI Status Register. Mask prescaler bits.
twst = TW_STATUS & 0xF8;
if ((twst == TW_MT_SLA_NACK) || (twst == TW_MR_DATA_NACK))
{
/* device busy, send stop condition to terminate write operation */
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
// wait until stop condition is executed and bus released
while (TWCR & (1 << TWSTO));
continue;
}
//if( twst != TW_MT_SLA_ACK) return 1;
break;
}
}/* i2c_start_wait */
/*************************************************************************
Issues a repeated start condition and sends address and transfer direction
Input:  address and transfer direction of I2C device
Return:  0 device accessible
  1 failed to access device
*************************************************************************/
unsigned char i2c_rep_start(unsigned char address)
{
return i2c_start(address);
}/* i2c_rep_start */
/*************************************************************************
Terminates the data transfer and releases the I2C bus
*************************************************************************/
void i2c_stop(void)
{
/* send stop condition */
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
// wait until stop condition is executed and bus released
while (TWCR & (1 << TWSTO));
}/* i2c_stop */
/*************************************************************************
  Send one byte to I2C device
  Input:    byte to be transfered
  Return:  0 write successful
1 write failed
*************************************************************************/
unsigned char i2c_write(unsigned char data)
{
uint8_t  twst;
// send data to the previously addressed device
TWDR = data;
TWCR = (1 << TWINT) | (1 << TWEN);
// wait until transmission completed
while (!(TWCR & (1 << TWINT)));
// check value of TWI Status Register. Mask prescaler bits
twst = TW_STATUS & 0xF8;
if (twst != TW_MT_DATA_ACK) return 1;
return 0;
}/* i2c_write */
/*************************************************************************
Read one byte from the I2C device, request more data from device
Return:  byte read from I2C device
*************************************************************************/
unsigned char i2c_readAck(void)
{
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWEA);
while (!(TWCR & (1 << TWINT)));
return TWDR;
}/* i2c_readAck */
/*************************************************************************
Read one byte from the I2C device, read is followed by a stop condition
Return:  byte read from I2C device
*************************************************************************/
unsigned char i2c_readNak(void)
{
TWCR = (1 << TWINT) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)));
return TWDR;
}/* i2c_readNak */
</source></tab>
<tab name="i2cmaster.h"><source lang="c++" style="background: LightYellow;">
#ifndef _I2CMASTER_H
#define _I2CMASTER_H
/*************************************************************************
* Title:    C include file for the I2C master interface
*          (i2cmaster.S or twimaster.c)
* Author:  Peter Fleury <pfleury@gmx.ch>
* File:    $Id: i2cmaster.h,v 1.12 2015/09/16 09:27:58 peter Exp $
* Software: AVR-GCC 4.x
* Target:  any AVR device
* Usage:    see Doxygen manual
**************************************************************************/
/**
@file
@defgroup pfleury_ic2master I2C Master library
@code #include <i2cmaster.h> @endcode
@brief I2C (TWI) Master Software Library
Basic routines for communicating with I2C slave devices. This single master
implementation is limited to one bus master on the I2C bus.
This I2c library is implemented as a compact assembler software implementation of the I2C protocol
which runs on any AVR (i2cmaster.S) and as a TWI hardware interface for all AVR with built-in TWI hardware (twimaster.c).
Since the API for these two implementations is exactly the same, an application can be linked either against the
software I2C implementation or the hardware I2C implementation.
Use 4.7k pull-up resistor on the SDA and SCL pin.
Adapt the SCL and SDA port and pin definitions and eventually the delay routine in the module
i2cmaster.S to your target when using the software I2C implementation !
Adjust the  CPU clock frequence F_CPU in twimaster.c or in the Makfile when using the TWI hardware implementaion.
@note
    The module i2cmaster.S is based on the Atmel Application Note AVR300, corrected and adapted
    to GNU assembler and AVR-GCC C call interface.
    Replaced the incorrect quarter period delays found in AVR300 with
    half period delays.
@author Peter Fleury pfleury@gmx.ch  http://tinyurl.com/peterfleury
@copyright (C) 2015 Peter Fleury, GNU General Public License Version 3
@par API Usage Example
  The following code shows typical usage of this library, see example test_i2cmaster.c
@code
#include <i2cmaster.h>
#define Dev24C02  0xA2      // device address of EEPROM 24C02, see datasheet
int main(void)
{
    unsigned char ret;
    i2c_init();                            // initialize I2C library
    // write 0x75 to EEPROM address 5 (Byte Write)
    i2c_start_wait(Dev24C02+I2C_WRITE);    // set device address and write mode
    i2c_write(0x05);                        // write address = 5
    i2c_write(0x75);                        // write value 0x75 to EEPROM
    i2c_stop();                            // set stop conditon = release bus
    // read previously written value back from EEPROM address 5
    i2c_start_wait(Dev24C02+I2C_WRITE);    // set device address and write mode
    i2c_write(0x05);                        // write address = 5
    i2c_rep_start(Dev24C02+I2C_READ);      // set device address and read mode
    ret = i2c_readNak();                    // read one byte from EEPROM
    i2c_stop();
    for(;;);
}
@endcode
*/
/**@{*/
#if (__GNUC__ * 100 + __GNUC_MINOR__) < 304
#error "This library requires AVR-GCC 3.4 or later, update to newer AVR-GCC compiler !"
#endif
#include <avr/io.h>
/** defines the data direction (reading from I2C device) in i2c_start(),i2c_rep_start() */
#define I2C_READ    1
/** defines the data direction (writing to I2C device) in i2c_start(),i2c_rep_start() */
#define I2C_WRITE  0
/**
@brief initialize the I2C master interace. Need to be called only once
@return none
*/
extern void i2c_init(void);
/**
@brief Terminates the data transfer and releases the I2C bus
@return none
*/
extern void i2c_stop(void);
/**
@brief Issues a start condition and sends address and transfer direction
@param    addr address and transfer direction of I2C device
@retval  0  device accessible
@retval  1  failed to access device
*/
extern unsigned char i2c_start(unsigned char addr);
/**
@brief Issues a repeated start condition and sends address and transfer direction
@param  addr address and transfer direction of I2C device
@retval  0 device accessible
@retval  1 failed to access device
*/
extern unsigned char i2c_rep_start(unsigned char addr);
/**
@brief Issues a start condition and sends address and transfer direction
If device is busy, use ack polling to wait until device ready
@param    addr address and transfer direction of I2C device
@return  none
*/
extern void i2c_start_wait(unsigned char addr);
/**
@brief Send one byte to I2C device
@param    data  byte to be transfered
@retval  0 write successful
@retval  1 write failed
*/
extern unsigned char i2c_write(unsigned char data);
/**
@brief    read one byte from the I2C device, request more data from device
@return  byte read from I2C device
*/
extern unsigned char i2c_readAck(void);
/**
@brief    read one byte from the I2C device, read is followed by a stop condition
@return  byte read from I2C device
*/
extern unsigned char i2c_readNak(void);
/**
@brief    read one byte from the I2C device
Implemented as a macro, which calls either @ref i2c_readAck or @ref i2c_readNak
@param    ack 1 send ack, request more data from device<br>
              0 send nak, read is followed by a stop condition
@return  byte read from I2C device
*/
extern unsigned char i2c_read(unsigned char ack);
#define i2c_read(ack)  (ack) ? i2c_readAck() : i2c_readNak();
/**@}*/
#endif
</source></tab>
</tabs>
</tabs>



Verzia z 19:20, 22. apríl 2024

Záverečný projekt predmetu MIPS / LS2024 - Marián Sušina


Zadanie

Sem príde text zadania, ak bolo len voľne formulované, rozpíšte ho podrobnejšie

Mojou úlohou v tomto zadaní bolo zapojiť, naprogramovať a overiť funkčnosť senzoru teploty a vlhkosti SHT31. Výpis informácii v o teplote a vlhkosti som pri mojom riešení realizoval pomocou LCD displeja a aj pomocou výpisu na obrazovku.

Arduino NANO.
Senzor teploty a vlhkosti SHT31.

Literatúra:

Analýza a opis riešenia

Na začiatok si môžeme podľa priložených schém pripojiť senzor SHT31 k mikroprocesoru. Na jeho správne používanie musíme použiť pri SCL (časovom kanále) a SDA (dátovom kanále) pull up rezistory.

Schéma zapojenia sensoru SHT31.

Po pripojení senzoru si môžeme pripojiť k mikroprocesoru aj LCD displej, ktorý zapojíme podľa priloženej schémy zapojenia.

Schéma zapojenia LCD displeja.

Ak sa nám už správne podarilo pripojiť senzor aj displej, hardvérová časť nášho zadania je hotová. Ďalej nasleduje programová časť. Pri tejto časti budeme používať datasheety, priložené vyššie. Ak chceme aby náš senzor vypisoval informácie na displej, budeme potrebovať knižnicu pre LCD displej. Pre výpis cez sériový kanál na obrazovku budeme potrebovať knižnicu UART. A pre použitie samotného senzora budeme ešte potrebovať používať knižnicu pre I2C zbernicu, ktorá slúži na prenos dát zo senzoru a na ich čítanie. Samotný program budeme písať podľa datasheetu, kde si naštudujeme ako náš senzor komunikuje s mikroprocesorom a ako z neho čítať dáta. Na to nám slúži obrázok priloženy nižšie z datasheetu, ktorý opisuje ako správne komunikovať a vyčítavať informácie zo senzoru.

Algoritmus čítania dát zo senzoru.

Na začiatku začneme posielať I2C adresu a hodnotu, ktorou nastavíme, že chceme zapisovať dáta. Po prijatí ACK (acknowledge), nastavíme MSB (most significant bit) a s ním si pre jednoduchosť vypneme Clock stretching. Ďalej nastavíme LSB (least significant bit), ktorým si volíme repeatebility. Ďalej pošleme informáciu o adrese a hodnote a počkáme na ACK, inak posielanie opakujeme v cykle až kým nedostaneme odpoveď. Keď odpoveď dostaneme, môžeme si do bufferu vyčítať dáta ktoré nám prišli zo senzoru. Tieto dáta nám prídu v dvoch častiach. Jedna nám hovorí o teplote a druhá o vlhkosti. Pomocou vzorčekov, ktoré si nájdeme v datasheete si údaje zo senzoru prepočítame a za pomoci nami vytvorenej funkcie na výpočet a výpis ich môžeme vypísať na displej ale aj cez sériovú linku na obrazovku.

Algoritmus a program

Algoritmus programu spočíva v prijímaní informácii zo senzoru, ktoré prekonvertujeme na požadované veličiny (Pre nás stupne Celzia pre teplotu a % pre vlhkosť), a následne ich vypíšeme aby sme si mohli pozrieť, čo sa nám podarilo odmerať a či nám všetko správne funguje.


<tab name="SHT31D_projekt.c"><source lang="c++" style="background: LightYellow;"> /* SHT31D_projekt.c Autor: Marián Sušina

  • /

/* hlavickove subory pre komunikaciu s UART, LCD displejom a I2C zbernicou */

  1. include <avr/io.h>
  2. include <stdio.h>
  3. include <util/delay.h>
  4. include "uart.h"
  5. include "lcd_ch.h"
  6. include "i2cmaster.h"

/* Inicializacia funkcie printf() cez UART */ FILE mystdout = FDEV_SETUP_STREAM(uart_putc, NULL, _FDEV_SETUP_WRITE);

/* Adresa senzora v I2C zbernici */

  1. define SHT31 0x44

/* Funkcia na zobrazenie teploty a vlhkosti na LCD displeji a do Terminalu. Teplota a vlhkost sa formatuje s jednym desatinnym miestom */ void display_temperature_humidity(float temperature, float humidity) { char str[20];

int temp_int = (int)(temperature * 10); // Prevod na cele cislo s jednym desatinnym miestom sprintf(str, "\fTeplota:%d.%d C\r", temp_int / 10, temp_int % 10); // Formatovanie teploty s jednym desatinnym miestom lcd_puts(str); // Vypis teploty na displej printf("Teplota: %d.%d\r\n", temp_int / 10, temp_int % 10);

int humi_int = (int)(humidity * 10); // Prevod na cele cislo s jednym desatinnym miestom sprintf(str, "Vlhkost:%d.%d %%", humi_int / 10, humi_int % 10); // Formatovanie vlhkosti s jednym desatinnym miestom lcd_puts(str); // Vypis vlhkosti na displej printf("Vlhkost: %d.%d\r\n", humi_int / 10, humi_int % 10); }

/* Funkcia na meranie teploty a vlhkosti */ void measure() { uint8_t buf[6]; i2c_start_wait(SHT31 << 1 | I2C_WRITE); // posiela I2C adresu a hodnotu na zapis a caka na potvrdenie ACK i2c_write(0x24); // nastavenie MSB -> vypneme s nim Clock stretching i2c_write(0x0B); // nastavenie LSB -> nastavyme si repeatebility na medium i2c_stop();

_delay_ms(1); // pauza 1ms po prikaze (podla datasheetu)

while (i2c_start(SHT31 << 1 | I2C_READ)) // posleme adresu a hodnotu na citanie a pockame na ACK { i2c_stop(); // ak prisiel NAK posleme STOP }

for (uint8_t i = 0; i < 6; i++) // precitame 6 bajtov zo snimaca { buf[i] = i2c_read(i != 5); // zapiseme hodnoty zo snimaca do bufferu } i2c_stop();

uint16_t T_RAW = buf[0] << 8 | buf[1]; // hodnota teploty uint16_t RH_RAW = buf[3] << 8 | buf[4]; // hodnota vlhkosti float T = 175.0 * T_RAW / 0xFFFF - 45; // prepocet na stupne C float RH = RH_RAW * 100.0 / 0xFFFF; // prepocet na %

display_temperature_humidity(T, RH); // spustenie funkcie na vypis teploty a vlhkosti na displeji a v terminale }

int main(void) {

/* inicializacia portov - vstupy / vystupy */

DDRD |= (1 << LCD_EN_pin); // Pin D4 (Enable) PORTD output DDRD |= (1 << LCD_RW_pin); // Pin D3 (RW) PORTD output DDRD |= (1 << LCD_RS_pin); // Pin D2 (RS) PORTD output

LCD_DATA_PORT |= (1 << LCD_D4_pin) | (1 << LCD_D5_pin) | (1 << LCD_D6_pin) | (1 << LCD_D7_pin); // Piny 1,2,3,4, PORTB ako output (Data pre display)

/* inicializacia I2C, UARTu a LCD displeja */ i2c_init(); uart_init(); lcd_init();

stdout = &mystdout; // funkcia printf();

lcd_puts("Start"); _delay_ms(500);

while (1) { measure(); _delay_ms(1000); }

return (0); }

Pridajte sem aj zbalený kompletný projekt, napríklad takto (použite jednoznačné pomenovanie, nemôžeme mať na serveri 10x zdrojaky.zip:

Zdrojový kód: zdrojaky.zip


Overenie

Po pripojení senzora SHT31 a LCD displeja si vieme overiť funkčnosť programu. Po jeho spustení sa nám začne na LCD displeji ale aj na obrazovke zobrazovať teplota a vlhkosť v intervaloch jendej sekundy.

Zapojenie v realite.

Video: overenie funkčnosti

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