开发环境
软件:RT-Thread Studio
硬件:NK-980IOT开发板
I2C介绍
I2C是一种双线、双向串行总线,为设备之间的数据交换提供了一种简单有效的方法。I2C标准是一种真正的多主总线,包括冲突检测和仲裁,当两个或多个主总线试图同时控制总线时,可防止数据损坏。
共有四套I2C控制器,支持断电唤醒功能。
特性
I2C总线使用两根导线(SDA和SCL)在连接到总线的设备之间传输信息。I2C总线的主要功能包括:
最多支持三个I2C端口
主/从模式
主设备和从设备之间的双向数据传输
多主机模式(无中心主机)
支持标准模式(100kbps)、快速模式(400kbps)和快速模式plus(1Mbps)
在不损坏总线上串行数据的情况下,同时传输主机之间的仲裁
内置14位超时计数器,在I2C总线挂起且定时器超时计数器溢出时请求I2C中断
可编程时钟允许多种速率控制
支持7位寻址和10位寻址模式
支持多地址识别(带掩码选项的四个从地址)
支持断电唤醒功能
支持设置/保持时间可编程
功能框图
基本配置
在测试用使用的I2C2,下面主要介绍I2C2的配置。
时钟源配置:在I2CKEN中启用I2C2外设时钟(CLK_APBCLK1[2])
复位配置:在I2C2RST中重置I2C2控制器(SYS_APBIPRST1[2])
管脚配置:
测试中使用的是PB5/PB7:
功能描述
在I2C总线上,数据在主设备和从设备之间传输。SCL和SDA线路上的数据位以字节为单位同步传输。每个数据字节的长度为8位。每个数据位有一个SCL时钟脉冲,MSB先传输,每个传输的字节后有一个确认位。
在SCL的高位期间对每个位进行采样;因此,SDA线可能仅在SCL的低期发生变化,并且在SCL的高期必须保持稳定。当SCL处于高位时,SDA线上的转换被解释为命令(启动或停止)。有关I2C总线定时的更多详细信息,请参考图6.16-2。
I2C时序图
以上内容均来自芯片的数据手册,数据手册里内容非常全,建议大家在需要时一定要去翻阅。新唐是国产IC里少有的能把手册做那么全的,很给力。
外部电路
测试使用了GY-68模块,模块原理图:
与开发板的连接
工程代码部分
工程配置
先使能I2C2
在软件包里有传感器驱动库可以使用
这个库写的不错,里面有对设备抽象、添加自己的设备等代码,对提高编程能力很有启发和学习价值。
初始化
int rt_hw_bmp180_init(const char name, struct rt_sensor_config cfg)
{
rt_err_t ret = RT_EOK;
rt_sensor_t sensor_baro = RT_NULL, sensor_temp = RT_NULL;
struct rt_sensor_module module = RT_NULL;
struct bmp180_dev bmp180 = RT_NULL;
uint8_t bmbuf[22] = {0};
bmp180 = rt_calloc(1, sizeof(struct bmp180_dev));
if(bmp180 == RT_NULL)
{
LOG_E("malloc memory failed\r\n");
ret = -RT_ERROR;
goto __exit;
}
bmp180->i2c_bus = rt_i2c_bus_device_find(cfg->intf.dev_name);
if(bmp180->i2c_bus == RT_NULL)
{
LOG_E("i2c bus device %s not found!\r\n", cfg->intf.dev_name);
ret = -RT_ERROR;
goto __exit;
}
module = rt_calloc(1, sizeof(struct rt_sensor_device));
if(module == RT_NULL)
{
LOG_E("malloc memory failed\r\n");
ret = -RT_ERROR;
goto __exit;
}
module->sen[0] = sensor_baro;
module->sen[1] = sensor_temp;
module->sen_num = 2;
/* barometric pressure sensor register */
{
sensor_baro = rt_calloc(1, sizeof(struct rt_sensor_device));
if (sensor_baro == RT_NULL)
{
goto __exit;
}
rt_memset(sensor_baro, 0x0, sizeof(struct rt_sensor_device));
sensor_baro->info.type = RT_SENSOR_CLASS_BARO;
sensor_baro->info.vendor = RT_SENSOR_VENDOR_BOSCH;
sensor_baro->info.model = "bmp180_baro";
sensor_baro->info.unit = RT_SENSOR_UNIT_PA;
sensor_baro->info.intf_type = RT_SENSOR_INTF_I2C;
sensor_baro->info.range_max = 110000; /* 1Pa */
sensor_baro->info.range_min = 30000;
sensor_baro->info.period_min = 100; /* read ten times in 1 second */
rt_memcpy(&sensor_baro->config, cfg, sizeof(struct rt_sensor_config));
sensor_baro->ops = &bmp180_ops;
sensor_baro->module = module;
ret = rt_hw_sensor_register(sensor_baro, name, RT_DEVICE_FLAG_RDWR, (void*)bmp180);
if (ret != RT_EOK)
{
LOG_E("device register err code: %d", ret);
goto __exit;
}
}
/* temperature sensor register */
{
sensor_temp = rt_calloc(1, sizeof(struct rt_sensor_device));
if (sensor_temp == RT_NULL)
{
goto __exit;
}
rt_memset(sensor_temp, 0x0, sizeof(struct rt_sensor_device));
sensor_temp->info.type = RT_SENSOR_CLASS_TEMP;
sensor_temp->info.vendor = RT_SENSOR_VENDOR_BOSCH;
sensor_temp->info.model = "bmp180_temp";
sensor_temp->info.unit = RT_SENSOR_UNIT_DCELSIUS;
sensor_temp->info.intf_type = RT_SENSOR_INTF_I2C;
sensor_baro->info.range_max = 850; /* 0.1C */
sensor_baro->info.range_min = -400;
sensor_temp->info.period_min = 100; /* read ten times in 1 second */
rt_memcpy(&sensor_temp->config, cfg, sizeof(struct rt_sensor_config));
sensor_temp->ops = &bmp180_ops;
sensor_temp->module = module;
ret = rt_hw_sensor_register(sensor_temp, name, RT_DEVICE_FLAG_RDWR, (void*)bmp180);
if (ret != RT_EOK)
{
LOG_E("device register err code: %d", ret);
goto __exit;
}
}
/* bmp180 read calc param */
ret = bmp180_read_regs(sensor_baro, BMS_CAL_AC1, bmbuf, 22);
if(ret == RT_EOK)
{
bmp180->calc_param.ac1 = (bmbuf[0]<<8)|bmbuf[1];
bmp180->calc_param.ac2 = (bmbuf[2]<<8)|bmbuf[3];
bmp180->calc_param.ac3 = (bmbuf[4]<<8)|bmbuf[5];
bmp180->calc_param.ac4 = (bmbuf[6]<<8)|bmbuf[7];
bmp180->calc_param.ac5 = (bmbuf[8]<<8)|bmbuf[9];
bmp180->calc_param.ac6 = (bmbuf[10]<<8)|bmbuf[11];
bmp180->calc_param.b1 = (bmbuf[12]<<8)|bmbuf[13];
bmp180->calc_param.b2 = (bmbuf[14]<<8)|bmbuf[15];
bmp180->calc_param.mb = (bmbuf[16]<<8)|bmbuf[17];
bmp180->calc_param.mc = (bmbuf[18]<<8)|bmbuf[19];
bmp180->calc_param.md = (bmbuf[20]<<8)|bmbuf[21];
}
else
{
LOG_E("bmp180 read calc param failed\r\n");
goto __exit;
}
return RT_EOK;
__exit:
if(sensor_baro)
{
rt_free(sensor_baro);
}
if(sensor_temp)
{
rt_free(sensor_temp);
}
if(module)
{
rt_free(module);
}
if (bmp180)
{
rt_free(bmp180);
}
return ret;
}
### 功能实现
```Plain Text
static void read_baro_entry(void *parameter)
{
rt_device_t baro_dev = RT_NULL, temp_dev = RT_NULL;
struct rt_sensor_data baro_data,temp_data;
rt_size_t res0 = 0, res1 = 1;
rt_uint8_t chip_id;
baro_dev = rt_device_find("baro_bmp180");
if (baro_dev == RT_NULL)
{
rt_kprintf("not found baro_bmp180 device\r\n");
return;
}
if (rt_device_open(baro_dev, RT_DEVICE_FLAG_RDONLY) != RT_EOK)
{
rt_kprintf("open baro_180 failed\r\n");
return;
}
temp_dev = rt_device_find("temp_bmp180");
if (temp_dev == RT_NULL)
{
rt_kprintf("not found temp_bmp180 device\r\n");
return;
}
if (rt_device_open(temp_dev, RT_DEVICE_FLAG_RDONLY) != RT_EOK)
{
rt_kprintf("open temp_bmp180 failed\r\n");
return;
}
rt_device_control(baro_dev, RT_SENSOR_CTRL_SET_ODR, (void *)(1));/* 1Hz read */
rt_device_control(temp_dev, RT_SENSOR_CTRL_SET_ODR, (void *)(1));/* 1Hz read */
rt_device_control(temp_dev, RT_SENSOR_CTRL_GET_ID, (void*)&chip_id);
rt_kprintf("bmp180 chip ID [0x%X]\n", chip_id);
while (1)
{
res0 = rt_device_read(baro_dev, 0, &baro_data, 1);
res0 = rt_device_read(temp_dev, 0, &temp_data, 1);
if (res0==0 || res1==0)
{
rt_kprintf("read data failed! result is %d,%d\n", res0, res1);
rt_device_close(baro_dev);
rt_device_close(temp_dev);
return;
}
else
{
rt_kprintf("baro[%dPa],temp[%d.%dC],timestamp[%d]\r\n", baro_data.data.baro,
temp_data.data.temp/10, temp_data.data.temp%10,
temp_data.timestamp);
}
rt_thread_delay(500);
}
}
}
测试结果
总结
RT-THREAD现在支持的开发板和传感器越来越全了,点一点就能完成工程搭建和测试。非常适合快速出产品的项目开发。
新唐的NK-980IOT开发板除了开始上手时不熟悉烧写过程,其他都很容易使用,提供的手册资料非常全,堪比ti、adi等大厂,遇到问题建议大家多翻翻手册。
原作者:sunxh_rk