Cómo leer el sensor de temperatura de la Raspberry Pi Pico

Sensor temperatura Raspberry Pi Pico

Leyendo las especificaciones de la Raspberry Pi Pico me llamó la atención que venía equipada con un sensor de temperatura en el propio chip, así que me puse a buscar información. En este artículo os quiero mostrar cómo acceder a él y de camino cómo utilizar el conversor analógico/digital (ADC).

El siguiente esquema muestra cómo está conectado el ADC a los GPIO y al sensor de temperatura interno.

Multiplexor ADC Raspberry Pi Pico

Es decir, tenemos un multiplexor de cuatro entradas que permite seleccionar la señal de entrada para el ADC. En nuestro caso, la que nos interesa es la 4, que es la fuente de tensión proveniente del sensor. Para seleccionar la entrada en el multiplexor usamos la siguiente función.

adc_select_input(4);

Para hacernos una idea, en la página 574 del datasheet del RP4020 nos dice que esta tensión es de 0.706V a 27 grados centígrados. La fórmula para aproximar la temperatura con respecto a esta tensión es

T = 27 - (ADC_voltage - 0.706)/0.001721

También hay que poner a 1 el registro TS_EN, para conectar el sensor. El SDK nos permite hacer esto fácilmente con la función

adc_set_temp_sensor_enabled(true);

Veamos pues cómo sería el proceso completo con un ejemplo en el que vamos a usar un display LCD 1602 para mostrar la temperatura. El manejo del LCD se hace por I2C y no voy a entrar en mucho detalle aquí, ya que hay bastante documentación al respecto. Además tenéis un ejemplo en la carpeta pico-examples/i2c/lcd_1602_i2c/ que acompaña al SDK.

Ya os conté en otro artículo los pasos para crear un proyecto para la Raspberry Pi Pico, así que ya sabemos que necesitamos como mínimo dos archivos: CMakeLists.txt y el código fuente, que en este caso lo he llamado termometro.c.

cmake_minimum_required(VERSION 3.12)
#enable_language( C ASM )
include(~/tools/pico/pico-sdk/external/pico_sdk_import.cmake)
#include(pico_sdk_import.cmake)

project(termometro C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

pico_sdk_init()
add_executable(termometro termometro.c)
target_link_libraries(termometro pico_stdlib hardware_i2c hardware_adc)
pico_add_extra_outputs(termometro)

Este es el contenido del archivo CMakeLists.txt, que es muy similar al que ya vimos en el anterior artículo. Se diferencia en la siguiente línea, que indica al compilador que debe añadir las librería para la gestión del bus I2C (para el LCD) y del ADC.

target_link_libraries(termometro pico_stdlib hardware_i2c hardware_adc)

Antes de poner el código fuente completo, voy a centrarme en los pasos para leer el sensor de temperatura. El código mínimo para leer el sensor tendría una forma como ésta.

#include "hardware/adc.h"

int main() {
    // 12 bits. Vref=3.3V
    const float conv_factor = 3.3f / (1<<12); 
	
    uint16_t lectura;
    float lect_conv, temperatura;

    adc_init();
    adc_set_temp_sensor_enabled(true);
    adc_select_input(4); // imput 4 - sensor temperatura
	    
    while(1) {
        // Consultar ADC
        lectura = adc_read();
        lect_conv = lectura * conv_factor;
        temperatura = 27-(lect_conv-0.706)/0.001721;

        // mostrar temperatura
	printf("%2.1f", temperatura);
		    
        sleep_ms(20000);
    }
}

A la lectura del ADC hay que aplicarle un factor de conversión. En este caso, la tensión máxima es de 3.3V y nuestro ADC es de 12 bits, por lo que este factor es 3.3f/(1<<12). Al resultado le aplicamos la fórmula que habíamos visto más arriba para aproximar la temperatura.

El programa completo es algo más complicado, ya que incluye el código para el manejo del LCD, y sería el siguiente.

#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "hardware/adc.h"
#include "pico/binary_info.h"

/* panel LCD 16x2 con adaptador I2C PCF8574 
   GPIO 20 (pin 26)-> SDA
   GPIO 21 (pin 27)-> SCL
   VSYS (pin 39) -> VCC
   GND (pin 38)  -> GND
*/
// algunas definiciones para controlar el LCD
const int LCD_CLEARDISPLAY = 0x01;
const int LCD_RETURNHOME = 0x02;
const int LCD_ENTRYMODESET = 0x04;
const int LCD_DISPLAYCONTROL = 0x08;
const int LCD_CURSORSHIFT = 0x10;
const int LCD_FUNCTIONSET = 0x20;
const int LCD_SETCGRAMADDR = 0x40;
const int LCD_SETDDRAMADDR = 0x80;
const int LCD_ENTRYSHIFTINCREMENT = 0x01;
const int LCD_ENTRYLEFT = 0x02;
const int LCD_BLINKON = 0x01;
const int LCD_CURSORON = 0x02;
const int LCD_DISPLAYON = 0x04;
const int LCD_MOVERIGHT = 0x04;
const int LCD_DISPLAYMOVE = 0x08;
const int LCD_5x10DOTS = 0x04;
const int LCD_2LINE = 0x08;
const int LCD_8BITMODE = 0x10;
const int LCD_BACKLIGHT = 0x08;
const int LCD_ENABLE_BIT = 0x04;

// dirección del bus I2C
// podría cambiar según modelos
#define I2C_PORT_LCD i2c0
static int addr_led = 0x27;
#define LCD_CHARACTER  1
#define LCD_COMMAND    0
#define MAX_LINES      2
#define MAX_CHARS      16

// pines I2C en la placa
#define LCD_SDA 20
#define LCD_SCL 21

void i2c_write_byte(uint8_t val) {
    i2c_write_blocking(I2C_PORT_LCD, addr_led, &val, 1, false);
}

void lcd_toggle_enable(uint8_t val) {
#define DELAY_US 600
    sleep_us(DELAY_US);
    i2c_write_byte(val | LCD_ENABLE_BIT);
    sleep_us(DELAY_US);
    i2c_write_byte(val & ~LCD_ENABLE_BIT);
    sleep_us(DELAY_US);
}

void lcd_send_byte(uint8_t val, int mode) {
    uint8_t high = mode | (val & 0xF0) | LCD_BACKLIGHT;
    uint8_t low = mode | ((val << 4) & 0xF0) | LCD_BACKLIGHT;

    i2c_write_byte(high);
    lcd_toggle_enable(high);
    i2c_write_byte(low);
    lcd_toggle_enable(low);
}

void lcd_clear(void) {
    lcd_send_byte(LCD_CLEARDISPLAY, LCD_COMMAND);
}

void lcd_set_cursor(int line, int position) {
    int val = (line == 0) ? 0x80 + position : 0xC0 + position;
    lcd_send_byte(val, LCD_COMMAND);
}

static void inline lcd_char(char val) {
    lcd_send_byte(val, LCD_CHARACTER);
}

void lcd_string(const char *s) {
    while (*s) {
        lcd_char(*s++);
    }
}

void lcd_init() {
    lcd_send_byte(0x03, LCD_COMMAND);
    lcd_send_byte(0x03, LCD_COMMAND);
    lcd_send_byte(0x03, LCD_COMMAND);
    lcd_send_byte(0x02, LCD_COMMAND);

    lcd_send_byte(LCD_ENTRYMODESET | LCD_ENTRYLEFT, LCD_COMMAND);
    lcd_send_byte(LCD_FUNCTIONSET | LCD_2LINE, LCD_COMMAND);
    lcd_send_byte(LCD_DISPLAYCONTROL | LCD_DISPLAYON, LCD_COMMAND);
    lcd_clear();
}

int main() {
	uint16_t lectura;
	float lect_conv, temperatura;
	char s[10];
	const float conv_factor = 3.3f / (1<<12); 

    i2c_init(I2C_PORT_LCD, 100 * 1000);
    gpio_set_function(LCD_SDA, GPIO_FUNC_I2C);
    gpio_set_function(LCD_SCL, GPIO_FUNC_I2C);
    gpio_pull_up(LCD_SDA);
    gpio_pull_up(LCD_SCL);
    bi_decl( bi_2pins_with_func(LCD_SDA, LCD_SCL, GPIO_FUNC_I2C));
	
    lcd_init();

    adc_init();
	adc_set_temp_sensor_enabled(true);
	adc_select_input(4); // imput 4 - sensor temperatura
	
	while(1) {
		// Consultar ADC
		lectura = adc_read();
		lect_conv = lectura * conv_factor;
		temperatura = 27-(lect_conv-0.706)/0.001721;

		// mostrar temperatura en el LCD
		sprintf(s, "%2.1f", temperatura);
		lcd_set_cursor(0,0);
		lcd_string("Temperatura:");
		lcd_set_cursor(1,0);
		lcd_string(s);
		sleep_ms(20000);
		lcd_clear();
	}

    return 0;
}

Las conexiones entre el LCD y la Raspberry Pi Pico son las siguientes.

   GPIO 20 (pin 26)-> SDA en LCD
   GPIO 21 (pin 27)-> SCL en LCD
   VSYS (pin 39) -> VCC en LCD
   GND (pin 38)  -> GND en LCD

Ya vimos que para poder compilar el programa tendríamos que invocar a cmake para que se genere el makefile correspondiente.

cmake .

Y finalmente compilamos con make. El resultado en el siguiente video, donde se observa cómo sube la temperatura al tapar el microcontrolador con el dedo.

Sé el primero en comentar en «Cómo leer el sensor de temperatura de la Raspberry Pi Pico»

Deja un comentario

Tu dirección de correo electrónico no será publicada.


*