实验目的
使用PSoc62™开发板驱动OLED模块,实时监控室内的光照强度、温度信息
实验准备
- PSoc62™开发板
- SSD1309 OLED模块
- DS18B20温度传感器
- BH1750光照传感器
模块电路
SSD1309
OLED模块的电路连接和模块配置教程请参考之前的文章,这里不详细展开描述
BH1750
模块SCL、SDA、GND引脚分别接CON5的P8.0、P8.1、GND引脚,VCC则接CON6的3.3V引脚
实物连接图
i2c引脚配置,需要在RT-Thread Studio里边配置使用模拟i2c引脚P8.0和P8.1(分别对应编号64、65)
有需要的可以使能i2c-tools这个包
模块的设备地址用i2c-tools查看是0x23
DS18B20
模块out引脚连接CON8处的P10.5引脚,VCC、GND可以连接CON6处的3.3V、GND,模块已经接了上拉电阻,这里不需要额外配置
实物连接图
程序设计
SSD1309
OLED模块的程序设计请参考之前的文章
DS18B20
传感器驱动代码
#define DS18B20_PIN GET_PIN(10, 5)
#define DS18B20_DQ_OUT(x) rt_pin_write(DS18B20_PIN, x)
#define DS18B20_DQ_IN rt_pin_read(DS18B20_PIN)// 复位DS18B20
static void ds18b20_reset(void)
{DS18B20_DQ_OUT(0); rt_hw_us_delay(750); DS18B20_DQ_OUT(1); rt_hw_us_delay(15);
}// 等待DS18B20的回应
uint8_t ds18b20_check(void)
{uint8_t retry = 0;uint8_t rval = 0;while (DS18B20_DQ_IN && retry < 200) {retry++;rt_hw_us_delay(1);}if (retry >= 200){rval = 1;}else{retry = 0;while (!DS18B20_DQ_IN && retry < 240) {retry++;rt_hw_us_delay(1);}if (retry >= 240) rval = 1;}return rval;
}// 从DS18B20读取一个位
static uint8_t ds18b20_read_bit(void)
{uint8_t data = 0;DS18B20_DQ_OUT(0);rt_hw_us_delay(2);DS18B20_DQ_OUT(1);rt_hw_us_delay(12);if (DS18B20_DQ_IN){data = 1;}rt_hw_us_delay(50);return data;
}// 从DS18B20读取一个字节
static uint8_t ds18b20_read_byte(void)
{uint8_t i, b, data = 0;for (i = 0; i < 8; i++){b = ds18b20_read_bit(); data |= b << i; }return data;
}// 写一个字节到DS18B20
static void ds18b20_write_byte(uint8_t data)
{uint8_t j;for (j = 1; j <= 8; j++){if (data & 0x01){DS18B20_DQ_OUT(0); rt_hw_us_delay(2);DS18B20_DQ_OUT(1);rt_hw_us_delay(60);}else{DS18B20_DQ_OUT(0); rt_hw_us_delay(60);DS18B20_DQ_OUT(1);rt_hw_us_delay(2);}data >>= 1; }
}// 开始温度转换
static void ds18b20_start(void)
{ds18b20_reset();ds18b20_check();ds18b20_write_byte(0xcc); /* skip rom */ds18b20_write_byte(0x44); /* convert */
}// 初始化DS18B20的IO口 DQ 同时检测DS18B20的存在
uint8_t ds18b20_init(void)
{rt_pin_mode(DS18B20_PIN, PIN_MODE_OUTPUT_OD);
// rt_pin_write(DS18B20_PIN, PIN_HIGH);ds18b20_reset();return ds18b20_check();
}// 从ds18b20得到温度值(精度:0.1C)
short ds18b20_get_temperature(void)
{uint8_t flag = 1; /* 默认温度为正数 */uint8_t TL, TH;short temp;ds18b20_start(); /* ds1820 start convert */ds18b20_reset();ds18b20_check();ds18b20_write_byte(0xcc); /* skip rom */ds18b20_write_byte(0xbe); /* convert */TL = ds18b20_read_byte(); /* LSB */TH = ds18b20_read_byte(); /* MSB */if (TH > 7){TH = ~TH;TL = ~TL;flag = 0; /* 温度为负 */}temp = TH; /* 获得高八位 */temp <<= 8;temp += TL; /* 获得底八位 */temp = (double)temp * 0.625; /* 转换 */if (flag == 0){temp = -temp; /* 将温度转换成负温度 */}return temp;
}
读取温度值
// ds18b20
ds18b20_init();// ssd1309
OLED_Init();
OLED_Clear();for (;;)
{// ds18b20int val = ds18b20_get_temperature();rt_memset(buff, 20, 0);rt_sprintf(buff, "temp : %d.%d'C ", val / 10, val % 10);OLED_ShowString(10, 2, buff);rt_thread_mdelay(200);
}
用逻辑分析仪抓一下单总线的读写时序,读取的数据(0x0185,大概是室温20来度)是OK的
BH1750
模块驱动代码,用的是i2c1总线,模块的设备地址用i2c-tools查看是0x23
#define BH1750_I2C_BUS_NAME "i2c1"
#define BH1750_ADDR 0x23static rt_uint8_t buffer[2];
static uint16_t light = 0;
static struct rt_i2c_bus_device *i2c_bus;static int read_i2c_reg(rt_uint8_t reg_addr, rt_uint8_t *data, rt_uint8_t len)
{struct rt_i2c_msg msgs[2];msgs[0].addr = BH1750_ADDR;msgs[0].flags = RT_I2C_WR;msgs[0].buf = ®_addr;msgs[0].len = 1;msgs[1].addr = BH1750_ADDR;msgs[1].flags = RT_I2C_RD;msgs[1].buf = data;msgs[1].len = len;if (rt_i2c_transfer(i2c_bus, msgs, 2) == 2){return RT_EOK;}elsereturn -RT_ERROR;
}static int8_t write_i2c_reg(uint8_t data)
{struct rt_i2c_msg msgs[2];msgs[0].addr = BH1750_ADDR;msgs[0].flags = RT_I2C_WR;msgs[0].buf = NULL;msgs[0].len = 0;msgs[1].addr = BH1750_ADDR;msgs[1].flags = RT_I2C_WR | RT_I2C_NO_START;msgs[1].buf = &data;msgs[1].len = 1;if (rt_i2c_transfer(i2c_bus, msgs, 2) != 2){return -RT_ERROR;}return RT_EOK;
}void BH1750_Init()
{write_i2c_reg(0x10);
}
读取光照强度值
// bh1750
i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(BH1750_I2C_BUS_NAME);
if (i2c_bus == RT_NULL)
{rt_kprintf("can't find %s device!\n", BH1750_I2C_BUS_NAME);return RT_ERROR;
}BH1750_Init();
rt_thread_mdelay(500);// ssd1309
OLED_Init();
OLED_Clear();for (;;)
{// bh1750BH1750_Init();rt_thread_mdelay(200);read_i2c_reg(0, buffer, 2);light = ((buffer[0] << 8) | buffer[1]) / 1.2;rt_memset(buff, 20, 0);rt_sprintf(buff, "light : %d lx ", light);OLED_ShowString(10, 5, buff);rt_thread_mdelay(150);
}
整合代码
以下代码用于读取DS18B20的温度值、BH1750的光照强度
#include <rtthread.h>
#include <rtdevice.h>
#include "drv_gpio.h"#define DS18B20_PIN GET_PIN(10, 5)
#define DS18B20_DQ_OUT(x) rt_pin_write(DS18B20_PIN, x)
#define DS18B20_DQ_IN rt_pin_read(DS18B20_PIN)// 复位DS18B20
static void ds18b20_reset(void)
{DS18B20_DQ_OUT(0); /* 拉低DQ,复位 */rt_hw_us_delay(750); /* 拉低750us */DS18B20_DQ_OUT(1); /* DQ=1, 释放复位 */rt_hw_us_delay(15); /* 延迟15US */
}// 等待DS18B20的回应
uint8_t ds18b20_check(void)
{uint8_t retry = 0;uint8_t rval = 0;while (DS18B20_DQ_IN && retry < 200) /* 等待DQ变低, 等待200us */{retry++;rt_hw_us_delay(1);}if (retry >= 200){rval = 1;}else{retry = 0;while (!DS18B20_DQ_IN && retry < 240) /* 等待DQ变高, 等待240us */{retry++;rt_hw_us_delay(1);}if (retry >= 240) rval = 1;}return rval;
}// 从DS18B20读取一个位
static uint8_t ds18b20_read_bit(void)
{uint8_t data = 0;DS18B20_DQ_OUT(0);rt_hw_us_delay(2);DS18B20_DQ_OUT(1);rt_hw_us_delay(12);if (DS18B20_DQ_IN){data = 1;}rt_hw_us_delay(50);return data;
}// 从DS18B20读取一个字节
static uint8_t ds18b20_read_byte(void)
{uint8_t i, b, data = 0;for (i = 0; i < 8; i++){b = ds18b20_read_bit(); /* DS18B20先输出低位数据 ,高位数据后输出 */data |= b << i; /* 填充data的每一位 */}return data;
}// 写一个字节到DS18B20
static void ds18b20_write_byte(uint8_t data)
{uint8_t j;for (j = 1; j <= 8; j++){if (data & 0x01){DS18B20_DQ_OUT(0); /* Write 1 */rt_hw_us_delay(2);DS18B20_DQ_OUT(1);rt_hw_us_delay(60);}else{DS18B20_DQ_OUT(0); /* Write 0 */rt_hw_us_delay(60);DS18B20_DQ_OUT(1);rt_hw_us_delay(2);}data >>= 1; /* 右移,获取高一位数据 */}
}// 开始温度转换
static void ds18b20_start(void)
{ds18b20_reset();ds18b20_check();ds18b20_write_byte(0xcc); /* skip rom */ds18b20_write_byte(0x44); /* convert */
}// 初始化DS18B20的IO口 DQ 同时检测DS18B20的存在
uint8_t ds18b20_init(void)
{rt_pin_mode(DS18B20_PIN, PIN_MODE_OUTPUT_OD);
// rt_pin_write(DS18B20_PIN, PIN_HIGH);ds18b20_reset();return ds18b20_check();
}// 从ds18b20得到温度值(精度:0.1C)
short ds18b20_get_temperature(void)
{uint8_t flag = 1; /* 默认温度为正数 */uint8_t TL, TH;short temp;ds18b20_start(); /* ds1820 start convert */ds18b20_reset();ds18b20_check();ds18b20_write_byte(0xcc); /* skip rom */ds18b20_write_byte(0xbe); /* convert */TL = ds18b20_read_byte(); /* LSB */TH = ds18b20_read_byte(); /* MSB */if (TH > 7){TH = ~TH;TL = ~TL;flag = 0; /* 温度为负 */}temp = TH; /* 获得高八位 */temp <<= 8;temp += TL; /* 获得底八位 */temp = (double)temp * 0.625; /* 转换 */if (flag == 0){temp = -temp; /* 将温度转换成负温度 */}return temp;
}///#include "stdlib.h"
#include "oledfont.h"// 绑定PSoc62板子的GPIO
#define CS_PIN GET_PIN(10, 0)
#define DC_PIN GET_PIN(10, 1)
#define RES_PIN GET_PIN(10, 2)
#define SDA_PIN GET_PIN(10, 3)
#define SCL_PIN GET_PIN(10, 4)
#define WR_PIN GET_PIN(0, 1)
#define RD_PIN GET_PIN(0, 1)// OLED模式设置
#define OLED_MODE 0
#define SIZE 16
#define XLevelL 0x00
#define XLevelH 0x10
#define Max_Column 128
#define Max_Row 64
#define Brightness 0xFF
#define X_WIDTH 128
#define Y_WIDTH 64// OLED端口定义
#define OLED_CS_Clr() rt_pin_write(CS_PIN, PIN_LOW)
#define OLED_CS_Set() rt_pin_write(CS_PIN, PIN_HIGH)
#define OLED_RST_Clr() rt_pin_write(RES_PIN, PIN_LOW)
#define OLED_RST_Set() rt_pin_write(RES_PIN, PIN_HIGH)
#define OLED_DC_Clr() rt_pin_write(DC_PIN, PIN_LOW)
#define OLED_DC_Set() rt_pin_write(DC_PIN, PIN_HIGH)
#define OLED_SDIN_Clr() rt_pin_write(SDA_PIN, PIN_LOW)
#define OLED_SDIN_Set() rt_pin_write(SDA_PIN, PIN_HIGH)
#define OLED_SCLK_Clr() rt_pin_write(SCL_PIN, PIN_LOW)
#define OLED_SCLK_Set() rt_pin_write(SCL_PIN, PIN_HIGH)
#define OLED_WR_Clr() rt_pin_write(WR_PIN, PIN_LOW)
#define OLED_WR_Set() rt_pin_write(WR_PIN, PIN_HIGH)
#define OLED_RD_Clr() rt_pin_write(RD_PIN, PIN_LOW)
#define OLED_RD_Set() rt_pin_write(RD_PIN, PIN_HIGH)#define OLED_CMD 0 // 写命令
#define OLED_DATA 1 // 写数据#define u8 unsigned char
#define u32 unsigned intstatic u8 buff[20];// 向SSD1309写入一个字节数据:dat -> 要写入的数据 or命令,cmd -> 数据or命令
void OLED_WR_Byte(u8 dat, u8 cmd)
{u8 i;if(cmd)OLED_DC_Set();elseOLED_DC_Clr();OLED_CS_Clr();for(i=0; i < 8; i++){OLED_SCLK_Clr();if(dat & 0x80)OLED_SDIN_Set();elseOLED_SDIN_Clr();OLED_SCLK_Set();dat <<= 1;}OLED_CS_Set();OLED_DC_Set();
}// 设置绘制的坐标
void OLED_Set_Pos(unsigned char x, unsigned char y)
{OLED_WR_Byte(0xb0 + y, OLED_CMD);OLED_WR_Byte(((x & 0xf0) >> 4) | 0x10, OLED_CMD);OLED_WR_Byte((x & 0x0f) | 0x01, OLED_CMD);
}// 开启OLED显示
void OLED_Display_On(void)
{OLED_WR_Byte(0X8D, OLED_CMD);OLED_WR_Byte(0X14, OLED_CMD);OLED_WR_Byte(0XAF, OLED_CMD);
}// 关闭OLED显示
void OLED_Display_Off(void)
{OLED_WR_Byte(0X8D, OLED_CMD);OLED_WR_Byte(0X10, OLED_CMD);OLED_WR_Byte(0XAE, OLED_CMD);
}// 清屏函数,屏幕会置为黑色
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);}
}// 绘制字符
void OLED_ShowChar(u8 x,u8 y,u8 chr)
{unsigned char c=0,i=0;c=chr-' ';//得到偏移后的值if(x>Max_Column-1){x=0;y=y+2;}if(SIZE ==16){OLED_Set_Pos(x,y);for(i=0;i<8;i++)OLED_WR_Byte(F8X16[c*16+i],OLED_DATA);OLED_Set_Pos(x,y+1);for(i=0;i<8;i++)OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA);}else {OLED_Set_Pos(x,y+1);for(i=0;i<6;i++)OLED_WR_Byte(F6x8[c][i],OLED_DATA);}
}// 显示一个字符号串
void OLED_ShowString(u8 x,u8 y,u8 *chr)
{unsigned char j=0;while (chr[j]!='\0'){OLED_ShowChar(x,y,chr[j]);x+=8;if(x>120){x=0;y+=2;}j++;}
}// 显示数字
void OLED_ShowNum(u8 x,u8 y,u32 num)
{rt_memset(buff, 20, 0);rt_sprintf(buff, "%d", num);OLED_ShowString(x, y, buff);
}// 显示汉字
void OLED_ShowCHinese(u8 x,u8 y,u8 no)
{u8 t,adder=0;OLED_Set_Pos(x,y);for(t=0;t<16;t++){OLED_WR_Byte(Hzk[2*no][t],OLED_DATA);adder+=1;}OLED_Set_Pos(x,y+1);for(t=0;t<16;t++){OLED_WR_Byte(Hzk[2*no+1][t],OLED_DATA);adder+=1;}
}// 初始化SSD1309
void OLED_Init(void)
{// init gpiosrt_pin_mode(CS_PIN, PIN_MODE_OUTPUT);rt_pin_mode(DC_PIN, PIN_MODE_OUTPUT);rt_pin_mode(RES_PIN, PIN_MODE_OUTPUT);rt_pin_mode(SDA_PIN, PIN_MODE_OUTPUT);rt_pin_mode(SCL_PIN, PIN_MODE_OUTPUT);OLED_RST_Set();rt_thread_mdelay(100);OLED_RST_Clr();rt_thread_mdelay(100);OLED_RST_Set();OLED_WR_Byte(0xAE,OLED_CMD); //--turn off oled panelOLED_WR_Byte(0x00,OLED_CMD); //---set low column addressOLED_WR_Byte(0x10,OLED_CMD); //---set high column addressOLED_WR_Byte(0x40,OLED_CMD); //--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)OLED_WR_Byte(0x81,OLED_CMD); //--set contrast control registerOLED_WR_Byte(0xCF,OLED_CMD); // Set SEG Output Current BrightnessOLED_WR_Byte(0xA1,OLED_CMD); //--Set SEG/Column Mapping 0xa0左右反置 0xa1正常OLED_WR_Byte(0xC8,OLED_CMD); //Set COM/Row Scan Direction 0xc0上下反置 0xc8正常OLED_WR_Byte(0xA6,OLED_CMD); //--set normal displayOLED_WR_Byte(0xA8,OLED_CMD); //--set multiplex ratio(1 to 64)OLED_WR_Byte(0x3f,OLED_CMD); //--1/64 dutyOLED_WR_Byte(0xD3,OLED_CMD); //-set display offset Shift Mapping RAM Counter (0x00~0x3F)OLED_WR_Byte(0x00,OLED_CMD); //-not offsetOLED_WR_Byte(0xd5,OLED_CMD); //--set display clock divide ratio/oscillator frequencyOLED_WR_Byte(0x80,OLED_CMD); //--set divide ratio, Set Clock as 100 Frames/SecOLED_WR_Byte(0xD9,OLED_CMD); //--set pre-charge periodOLED_WR_Byte(0xF1,OLED_CMD); //Set Pre-Charge as 15 Clocks & Discharge as 1 ClockOLED_WR_Byte(0xDA,OLED_CMD); //--set com pins hardware configurationOLED_WR_Byte(0x12,OLED_CMD);OLED_WR_Byte(0xDB,OLED_CMD); //--set vcomhOLED_WR_Byte(0x40,OLED_CMD); //Set VCOM Deselect LevelOLED_WR_Byte(0x20,OLED_CMD); //-Set Page Addressing Mode (0x00/0x01/0x02)OLED_WR_Byte(0x02,OLED_CMD); //OLED_WR_Byte(0x8D,OLED_CMD); //--set Charge Pump enable/disableOLED_WR_Byte(0x14,OLED_CMD); //--set(0x10) disableOLED_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); //--turn on oled panelOLED_WR_Byte(0xAF,OLED_CMD); /*display ON*/OLED_Clear();OLED_Set_Pos(0,0);
}//
#define BH1750_I2C_BUS_NAME "i2c1"
#define BH1750_ADDR 0x23static rt_uint8_t buffer[2];
static uint16_t light = 0;
static struct rt_i2c_bus_device *i2c_bus;static int read_i2c_reg(rt_uint8_t reg_addr, rt_uint8_t *data, rt_uint8_t len)
{struct rt_i2c_msg msgs[2];msgs[0].addr = BH1750_ADDR;msgs[0].flags = RT_I2C_WR;msgs[0].buf = ®_addr;msgs[0].len = 1;msgs[1].addr = BH1750_ADDR;msgs[1].flags = RT_I2C_RD;msgs[1].buf = data;msgs[1].len = len;if (rt_i2c_transfer(i2c_bus, msgs, 2) == 2){return RT_EOK;}elsereturn -RT_ERROR;
}static int8_t write_i2c_reg(uint8_t data)
{struct rt_i2c_msg msgs[2];msgs[0].addr = BH1750_ADDR;msgs[0].flags = RT_I2C_WR;msgs[0].buf = NULL;msgs[0].len = 0;msgs[1].addr = BH1750_ADDR;msgs[1].flags = RT_I2C_WR | RT_I2C_NO_START;msgs[1].buf = &data;msgs[1].len = 1;if (rt_i2c_transfer(i2c_bus, msgs, 2) != 2){return -RT_ERROR;}return RT_EOK;
}void BH1750_Init()
{write_i2c_reg(0x10);
}int main(void)
{// ds18b20ds18b20_init();// bh1750i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(BH1750_I2C_BUS_NAME);if (i2c_bus == RT_NULL){rt_kprintf("can't find %s device!\n", BH1750_I2C_BUS_NAME);return RT_ERROR;}BH1750_Init();rt_thread_mdelay(500);// ssd1309OLED_Init();OLED_Clear();for (;;){// ds18b20int val = ds18b20_get_temperature();rt_memset(buff, 20, 0);rt_sprintf(buff, "temp : %d.%d'C ", val / 10, val % 10);OLED_ShowString(10, 2, buff);rt_thread_mdelay(200);// bh1750BH1750_Init();rt_thread_mdelay(200);read_i2c_reg(0, buffer, 2);light = ((buffer[0] << 8) | buffer[1]) / 1.2;rt_memset(buff, 20, 0);rt_sprintf(buff, "light : %d lx ", light);OLED_ShowString(10, 5, buff);rt_thread_mdelay(150);}
}
实验效果
实时读取环境的温度和光照强度,正值夜晚光照强度在0-100 lx范围内
注意事项
这里有三个关键的模块,连接线比较复杂
- 千万不要接错电源和地
- i2c不能接错数据线和时钟线,spi接线也不例外
- 读取不到数据可以借助一些工具排查一下,比如i2c-tools、逻辑分析仪