0%

oled-am2320驱动

0、说明

在linux平台上,开发两个驱动:OLED与AM2320,在OLED上显示传感器的温湿度信息。

开发板使用正点原子的i.MX6ULL Linux阿尔法开发板。

y00mKP.png

1、OLED

OLED(OrganicLight-Emitting Diode),又称为有机电激光显示、有机发光半导体(OrganicElectroluminesence Display,OLED)。OLED属于一种电流型的有机发光器件,是通过载流子的注入和复合而致发光的现象,发光强度与注入的电流成正比【百度百科】。与LCD不同,OLED不需要背光。

OLED显示器依驱动方式的不同又可分为被动式(Passive Matrix,PMOLED)与主动式(Active Matrix,AMOLED),具体可参考https://www.zhihu.com/question/25669200

本文所使用的的OLED是0.96寸,如下图:

y00iUe.png

该显示屏可以选择IIC或者SPI的方式通信,这里使用SPI的方式,可以参考https://wenku.baidu.com/view/42efcb877cd184254a353584

(1)引脚

由于开发板接口的问题,OLED的驱动使用模拟SPI的方式,使用的引脚如设备树中的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pinctrl_oled: oledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0x10B0
MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0x10B0
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0
MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0x10B0
>;
};

oled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "lxl-oled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_oled>;
oled-gpio1 = <&gpio1 1 GPIO_ACTIVE_LOW>;
oled-gpio2 = <&gpio1 2 GPIO_ACTIVE_LOW>;
oled-gpio3 = <&gpio1 3 GPIO_ACTIVE_LOW>;
oled-gpio4 = <&gpio1 4 GPIO_ACTIVE_LOW>;
status = "okay";
};

当然,其它功能不应该再使用这几个引脚了。

(2)OLED指令

使用0.96寸OLED,操作命令如下图。

y00MVS.png

(3)图像显示

这里仅仅是作为一个展示,因为显示图片还需要做一些转换:将图像内容二值化(若彩色图片)后,按照OLED显示顺序来处理,重新组合为适合于OLED的数据格式。比如下面的二维码显示。

6dEAYT.jpg

一个需要注意的问题,因为是OLED,如果某个点需要亮,那么将该点写1,否则就是暗的,所以二维码处理的时候还需要反转一下,即黑变为白,白变为黑,也就是如下图所示,经过点阵处理工具处理后,在OLED上显示正常的图像。上图就是反转之后在OLED上的显示图像。

但是,也发现一个有意思的现象,即微信支付宝均可以识别反转的图像。

y0088s.jpg

(4)驱动源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
#include "oledfont.h"

#define SIZE 16
#define XLEVEL_L 0x00
#define XLEVEL_H 0x10
#define MAX_COLUMN 128
#define MAX_ROW 64
#define BRIGHTNESS 0xFF
#define X_WIDTH 128
#define Y_WIDTH 64

#define OLED_DATA 1 //写数据
#define OLED_CMD 0 //写命令

void oled0_switch(u8 bit, u8 sta);

#define OLED_RST_CLR() oled0_switch(3,0) //RES RES => 接RES引脚
#define OLED_RST_SET() oled0_switch(3,1)

#define OLED_DC_CLR() oled0_switch(4,0) //DC DC => 接DC引脚
#define OLED_DC_SET() oled0_switch(4,1)

/* 使用4线串行接口时使用 */
#define OLED_SCLK_CLR() oled0_switch(1,0)//CLK D0 => 接D0引脚
#define OLED_SCLK_SET() oled0_switch(1,1)

#define OLED_SDIN_CLR() oled0_switch(2,0)//PIN D1 => 接D1引脚
#define OLED_SDIN_SET() oled0_switch(2,1)

/*
* @brief 写入一个字节
* @param dat:要写入的数据/命令, cmd:0,表示命令;1,表示数据;
* @retval None
*/
void oled_write_byte(uint8_t dat, uint8_t cmd)
{
uint8_t i;

if(cmd == OLED_DATA)
OLED_DC_SET();
else if(cmd == OLED_CMD)
OLED_DC_CLR();
else
return;

for(i = 0; i < 8; i++)
{
OLED_SCLK_CLR();
if(dat & 0x80)
OLED_SDIN_SET();
else
OLED_SDIN_CLR();
OLED_SCLK_SET();
dat <<= 1;
}
OLED_DC_SET();
}

/*
* @brief
* @param
* @retval None
*/
void oled_set_pos(unsigned char x, unsigned char y)
{
oled_write_byte(0xb0 + y, OLED_CMD);
oled_write_byte(((x & 0xf0) >> 4) | 0x10, OLED_CMD);
oled_write_byte(x & 0x0f, OLED_CMD);
}

/*
* @brief 开启OLED显示
* @param None
* @retval None
*/
void oled_display_on(void)
{
oled_write_byte(0X8D, OLED_CMD);
oled_write_byte(0X14, OLED_CMD);
oled_write_byte(0XAF, OLED_CMD);
}

/*
* @brief 关闭OLED显示
* @param None
* @retval None
*/
void oled_display_off(void)
{
oled_write_byte(0X8D, OLED_CMD);
oled_write_byte(0X10, OLED_CMD);
oled_write_byte(0XAE, OLED_CMD);
}

/*
* @brief 清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样
* @param None
* @retval None
*/
void oled_clean(void)
{
uint8_t i, n;

for(i = 0; i < 8; i++)
{
oled_write_byte(0xb0 + i, OLED_CMD); //设置页地址(0~7)
oled_write_byte(0x00, OLED_CMD); //设置显示位置—列低地址
oled_write_byte(0x10, OLED_CMD); //设置显示位置—列高地址
for(n = 0; n < 128; n++)
oled_write_byte(0, OLED_DATA);
} //更新显示
}

/*
* @brief 在指定位置显示一个字符,包括部分字符
* @param x:0~127 y:0~63 chr:字符
* @retval None
*/
void oled_show_char(uint8_t x, uint8_t y, uint8_t 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_write_byte(D8X16[c * 16 + i], OLED_DATA);
oled_set_pos(x, y + 1);
for(i = 0; i < 8; i++)
oled_write_byte(D8X16[c * 16 + i + 8], OLED_DATA);
}
else
{
oled_set_pos(x, y + 1);
for(i = 0; i < 6; i++)
{
oled_write_byte(D6X8[c][i], OLED_DATA);
}
}
}

/*
* @brief m^n函数
* @param
* @retval None
*/
uint32_t oled_pow(uint8_t m, uint8_t n)
{
uint32_t result = 1;

while(n--)
result *= m;
return result;
}

/*
* @brief 显示2个数字
* @param x,y :起点坐标
* len :数字的位数,即显示几位有效数字
* size:字体大小
* num:数值(0~4294967295);
* @retval None
*/
void oled_show_num(uint8_t x, uint8_t y, uint32_t num, uint8_t len, uint8_t size)
{
uint8_t t, temp;
uint8_t enshow = 0;

for(t = 0; t < len; t++)
{
temp = (num / oled_pow(10, len - t - 1)) % 10;
if(enshow == 0 && t < (len - 1))
{
if(temp == 0)
{
oled_show_char(x + (size / 2)*t, y, ' ');
continue;
}
else enshow = 1;

}
oled_show_char(x + (size / 2)*t, y, temp + '0');
}
}

/*
* @brief 显示一个字符号串
* @param
* @retval None
*/
void oled_show_string(uint8_t x, uint8_t y, char* chr)
{
unsigned char j = 0;

while(chr[j] != '\0')
{
oled_show_char(x, y, chr[j]);
x += 8;
if(x > 120)
{
x = 0;
y += 2;
}
j++;
}
}

/*
* @brief 显示汉字
* @param
* @retval None
*/
void oled_show_chinese(uint8_t x, uint8_t y, uint8_t no)
{
uint8_t t, adder = 0;

oled_set_pos(x, y);
for(t = 0; t < 32; t++)
{
oled_write_byte(SHOW[2 * no][t], OLED_DATA);
adder += 1;
}
oled_set_pos(x, y + 1);
for(t = 0; t < 32; t++)
{
oled_write_byte(SHOW[2 * no + 1][t], OLED_DATA);
adder += 1;
}
}

/*
* @brief 显示显示图片
* @param 显示显示BMP图片128×64起始点坐标(x,y),x的范围0~127,y为页的范围0~7
* x1,y1:对角坐标点
* @retval None
*/
void oled_show_pic(unsigned char x0, unsigned char y0, unsigned char x1, unsigned char y1, unsigned char *pic)
{
unsigned int j = 0;
unsigned char x, y;

for(y = y0; y < y1; y++)
{
oled_set_pos(x0, y);
for(x = x0; x < x1; x++)
{
oled_write_byte(pic[j++], OLED_DATA);
}
}
}

/*
* @brief 初始化SSD1306
* @param None
* @retval None
*/
void oled_init(void)
{
OLED_RST_SET();
msleep(100);
OLED_RST_CLR();
msleep(100);
OLED_RST_SET();

oled_write_byte(0xAE, OLED_CMD); //--turn off oled panel
oled_write_byte(0x00, OLED_CMD); //---set low column address
oled_write_byte(0x10, OLED_CMD); //---set high column address
oled_write_byte(0x40, OLED_CMD); //--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
oled_write_byte(0x81, OLED_CMD); //--set contrast control register
oled_write_byte(0xCF, OLED_CMD); // Set SEG Output Current BRIGHTNESS
oled_write_byte(0xA1, OLED_CMD); //--Set SEG/Column Mapping 0xa0左右反置 0xa1正常
oled_write_byte(0xC8, OLED_CMD); //Set COM/Row Scan Direction 0xc0上下反置 0xc8正常
oled_write_byte(0xA6, OLED_CMD); //--set normal display
oled_write_byte(0xA8, OLED_CMD); //--set multiplex ratio(1 to 64)
oled_write_byte(0x3f, OLED_CMD); //--1/64 duty
oled_write_byte(0xD3, OLED_CMD); //-set display offset Shift Mapping RAM Counter (0x00~0x3F)
oled_write_byte(0x00, OLED_CMD); //-not offset
oled_write_byte(0xd5, OLED_CMD); //--set display clock divide ratio/oscillator frequency
oled_write_byte(0x80, OLED_CMD); //--set divide ratio, Set Clock as 100 Frames/Sec
oled_write_byte(0xD9, OLED_CMD); //--set pre-charge period
oled_write_byte(0xF1, OLED_CMD); //Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
oled_write_byte(0xDA, OLED_CMD); //--set com pins hardware configuration
oled_write_byte(0x12, OLED_CMD);
oled_write_byte(0xDB, OLED_CMD); //--set vcomh
oled_write_byte(0x40, OLED_CMD); //Set VCOM Deselect Level
oled_write_byte(0x20, OLED_CMD); //-Set Page Addressing Mode (0x00/0x01/0x02)
oled_write_byte(0x02, OLED_CMD); //
oled_write_byte(0x8D, OLED_CMD); //--set Charge Pump enable/disable
oled_write_byte(0x14, OLED_CMD); //--set(0x10) disable
oled_write_byte(0xA4, OLED_CMD); // Disable Entire Display On (0xa4/0xa5)
oled_write_byte(0xA6, OLED_CMD); // Disable Inverse Display On (0xa6/a7)
oled_write_byte(0xAF, OLED_CMD); //--turn on oled panel

oled_write_byte(0xAF, OLED_CMD); /*display ON*/
oled_clean();
oled_set_pos(0, 0);
}

头文件oledfont.h是字库、图形等的声明等,在这里无需过分关注。

上述代码是OLED的驱动,可以在单片机中使用,可参考https://www.cnblogs.com/ningmeng484/p/10406590.html。因为这里是在linux系统上的驱动,所以需要做一些额外的工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include<linux/delay.h>

#define OLEDDEV_CNT 1 /* 设备号长度 */
#define OLEDDEV_NAME "oled" /* 设备名字 */
#define OLEDOFF 0
#define OLEDON 1

/* oleddev设备结构体 */
struct oleddev_dev
{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class* class; /* 类 */
struct device* device; /* 设备 */
int major; /* 主设备号 */
struct device_node* node; /* OLED设备节点 */
int oled1, oled2, oled3, oled4; /* OLED灯GPIO标号 */
};

struct oleddev_dev oleddev; /* oled设备 */

/*
* @description : OLED控制引脚操作
* @param : bit,引脚;sta,状态
* @return : 无
*/
void oled0_switch(u8 bit, u8 sta)
{
u8 pin;

switch(bit)
{
case 1:
pin = oleddev.oled1;
break;
case 2:
pin = oleddev.oled2;
break;
case 3:
pin = oleddev.oled3;
break;
case 4:
pin = oleddev.oled4;
break;
default:
return;
}
if(sta == OLEDON)
gpio_set_value(bit, 1);
else if(sta == OLEDOFF)
gpio_set_value(bit, 0);
}

/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int oled_open(struct inode* inode, struct file* filp)
{
filp->private_data = &oleddev; /* 设置私有数据 */
return 0;
}

/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t oled_write(struct file* filp, const char __user* buf, size_t cnt, loff_t* offt)
{
int retvalue;
unsigned char databuf[32];

retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0)
{
printk("kernel write faioled!\r\n");
return -EFAULT;
}

oled_clean();
oled_show_string(0, 1, databuf);
return cnt;
}

/* 设备操作函数 */
static struct file_operations oled_fops =
{
.owner = THIS_MODULE,
.open = oled_open,
.write = oled_write,
};

/*
* @description : flatform驱动的probe函数,当驱动与设备匹配以后此函数就会执行
* @param - dev : platform设备
* @return : 0,成功;其他负值,失败
*/
static int oled_probe(struct platform_device* dev)
{
printk("oled driver and device was matched!\r\n");

/* 1、设置设备号 */
if(oleddev.major)
{
oleddev.devid = MKDEV(oleddev.major, 0);
register_chrdev_region(oleddev.devid, OLEDDEV_CNT, OLEDDEV_NAME);
}
else
{
alloc_chrdev_region(&oleddev.devid, 0, OLEDDEV_CNT, OLEDDEV_NAME);
oleddev.major = MAJOR(oleddev.devid);
}

/* 2、注册设备 */
cdev_init(&oleddev.cdev, &oled_fops);
cdev_add(&oleddev.cdev, oleddev.devid, OLEDDEV_CNT);

/* 3、创建类 */
oleddev.class = class_create(THIS_MODULE, OLEDDEV_NAME);
if(IS_ERR(oleddev.class))
{
return PTR_ERR(oleddev.class);
}

/* 4、创建设备 */
oleddev.device = device_create(oleddev.class, NULL, oleddev.devid, NULL, OLEDDEV_NAME);
if(IS_ERR(oleddev.device))
{
return PTR_ERR(oleddev.device);
}

/* 5、初始化IO */
oleddev.node = of_find_node_by_path("/oled");
if(oleddev.node == NULL)
{
printk("ooled node nost find!\r\n");
return -EINVAL;
}

oleddev.oled1 = of_get_named_gpio(oleddev.node, "oled-gpio1", 0);
if(oleddev.oled1 < 0)
{
printk("can't get oled-gpio1\r\n");
return -EINVAL;
}
oleddev.oled2 = of_get_named_gpio(oleddev.node, "oled-gpio2", 0);
if(oleddev.oled2 < 0)
{
printk("can't get oled-gpio2\r\n");
return -EINVAL;
}
oleddev.oled3 = of_get_named_gpio(oleddev.node, "oled-gpio3", 0);
if(oleddev.oled3 < 0)
{
printk("can't get oled-gpio3\r\n");
return -EINVAL;
}
oleddev.oled4 = of_get_named_gpio(oleddev.node, "oled-gpio4", 0);
if(oleddev.oled4 < 0)
{
printk("can't get oled-gpio4\r\n");
return -EINVAL;
}

gpio_request(oleddev.oled1, "oled-d0");
gpio_direction_output(oleddev.oled1, 1);/*d0 IO设置为输出,默认高电平*/
gpio_request(oleddev.oled2, "oled-d1");
gpio_direction_output(oleddev.oled2, 1);/*d1 IO设置为输出,默认高电平*/
gpio_request(oleddev.oled3, "oled-res");
gpio_direction_output(oleddev.oled3, 1);/*res IO设置为输出,默认高电平*/
gpio_request(oleddev.oled4, "oled-dc");
gpio_direction_output(oleddev.oled4, 1);/*dc IO设置为输出,默认高电平*/

printk("ooled pin init ok\nstart to config ooled...\n");

oled_init();
oled_clean();
printk("showing...\n");
//oled_show_string(72, 0, "hello");
//oled_show_string(72, 6, "hello");

oled_show_pic(0,0,64,8,(unsigned char *)gImage_mail_er);

return 0;
}

/*
* @description : platform驱动的remove函数,移除platform驱动的时候此函数会执行
* @param - dev : platform设备
* @return : 0,成功;其它负值,失败
*/
static int oled_remove(struct platform_device* dev)
{
oled_clean();

gpio_set_value(oleddev.oled1, 1); /* 卸载驱动的时候关闭OLED */
gpio_set_value(oleddev.oled2, 1);
gpio_set_value(oleddev.oled3, 1);
gpio_set_value(oleddev.oled4, 1);

cdev_del(&oleddev.cdev); /* 删除cdev */
unregister_chrdev_region(oleddev.devid, OLEDDEV_CNT); /* 注销设备号 */
device_destroy(oleddev.class, oleddev.devid);
class_destroy(oleddev.class);
printk("%s\n", __func__);
return 0;
}

/* 匹配列表 */
static const struct of_device_id oled_of_match[] =
{
{ .compatible = "lxl-oled" },
{ /* Sentinel */ }
};

/* platform驱动结构体 */
static struct platform_driver oled_driver =
{
.driver = {
.name = "imx6ul-oled", /* 驱动名字,用于和设备匹配 */
.of_match_table = oled_of_match, /* 设备树匹配表 */
},
.probe = oled_probe,
.remove = oled_remove,
};

/*
* @description : 驱动模块加载函数
* @param : 无
* @return : 无
*/
static int __init oleddriver_init(void)
{
return platform_driver_register(&oled_driver);
}

/*
* @description : 驱动模块卸载函数
* @param : 无
* @return : 无
*/
static void __exit oleddriver_exit(void)
{
platform_driver_unregister(&oled_driver);
}

module_init(oleddriver_init);
module_exit(oleddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lxl");

驱动与设备树匹配(lxl-oled)成功后,会调用oled_probe(),进行设备的注册与创建,之后在设备树中查找/oled节点,获取模拟SPI的引脚并进行设置,最后初始化OLED。之后调用的oled_show_pic()函数是为了图片显示功能而调用的。

2、AM2320

(1)简介

AM2320 数字温湿度传感器,是一款含有己校准数字信号输出的温湿度复合型传感器。传感器包括一个电容式感湿元件和一个高精度集成测温元件,并与一个高性能微处理器相连接。AM2320 通信方式有单总线、标准 I2C 两种通信方式。

我们这里就是采用I2C的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
&iomuxc {
pinctrl-names = "default";
imx6ul-evk {
pinctrl_i2c2: i2c2grp {
fsl,pins = <
MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0
MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0
>;
};
};

&i2c2 {
clock_frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c2>;
status = "okay";

//lxl:AM2320 5c
am2320@5c {
compatible = "lxl,am2320";
reg = <0x5c>;
};
};

AM2320传感器的 I2C 的地址(SLAVE ADDRESS)为 0xB8,但是因为linux I2C在处理的时候,会将地址左移1位,所以在这里需要提前将地址右移1位,即0xB8变为0x5C,也就是上面设备树中am2320地址0x5c.

表 1:AM2320 数据寄存器表

寄存器信息 地址 寄存器信息 地址 寄存器信息 地址 寄存器信息 地址
湿度高位 0x00 设备型号高位 0x08 用户寄存器 1 高位 0x10 保留 0x18
湿度低位 0x01 设备型号低位 0x09 用户寄存器 1 低位 0x11 保留 0x19
温度高位 0x02 版本号 0x0A 用户寄存器 2 高位 0x12 保留 0x1A
温度低位 0x03 设备 ID(24-31) Bit 0x0B 用户寄存器 2 低位 0x13 保留 0x1B
保留 0x04 设备 ID(16-23) Bit 0x0C 保留 0x14 保留 0x1C
保留 0x05 设备 ID(8 - 15) Bit 0x0D 保留 0x15 保留 0x1D
保留 0x06 设备 ID(0 - 7 ) Bit 0x0E 保留 0x16 保留 0x1E
保留 0x07 状态寄存器 0x0F 保留 0x17 保留 0x1F

读取数据的方式

y00NrV.png

(2)驱动

I.MX6U 的 I2C 适配器驱动 NXP 已经编写好了 ,这里直接使用即可,在总线驱动之上还有一个设备驱动,这是本驱动的重点。

AM2320的读写按照上面的说明操作即可。

(3)源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
/*
2020-10-30 20:41
test ok
*/

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include<linux/delay.h>

#define am2320_CNT 1
#define am2320_NAME "am2320"

struct am2320_dev
{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int major;
void *private_data;
unsigned short hum,tem;
};

#define HUM_H 0
#define HUM_L 1
#define TEM_H 2
#define TEM_L 3

static struct am2320_dev am2320dev;

static int am2320_read_regs(struct am2320_dev *dev, u8 reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->private_data;

msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].buf = &reg;
msg[0].len = 1;

msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].buf = val;
msg[1].len = len;

ret = i2c_transfer(client->adapter,msg, 2);
if (ret == 2)
{
ret = 0;
}
else
{
printk("error @ %s,%d\n",__func__,__LINE__);
printk("i2c rd failed =%d reg=%06d len=%d\n", ret, reg, len);
ret = -EREMOTEIO;
}
return ret;
}

static int am2320_read_regs_recv(struct am2320_dev *dev, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->private_data;

msg[0].addr = client->addr;
msg[0].flags = I2C_M_RD;
msg[0].buf = val;
msg[0].len = len;

ret = i2c_transfer(client->adapter,msg, 1);
if (ret == 1)
{
ret = 0;
}
else
{
printk("error @ %s,%d\n",__func__,__LINE__);
ret = -EREMOTEIO;
}
return ret;
}

static s32 am2320_write_regs(struct am2320_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->private_data;

b[0] = reg;
memcpy(&b[1], buf, len);

msg.addr = client->addr;
msg.flags = 0; //write data

msg.buf = b;
msg.len = len + 1;

return i2c_transfer(client->adapter, &msg, 1);
}

static void am2320_readdata(struct am2320_dev *dev)
{
unsigned char buf[8];

//am2320_write_regs(dev,0,buf,0);//唤醒

buf[0]=0;
buf[1]=4;
am2320_write_regs(dev,0x03,buf,2);
mdelay(2);
//am2320_read_regs(dev,0x03,buf,8);//lxl:!!!
if(EREMOTEIO==am2320_read_regs_recv(dev,buf,8))
{
dev->hum = 0;
dev->tem = 0;
return;
}

dev->hum = (buf[2]<<8) + buf[3];
dev->tem = (buf[4]<<8) + buf[5];
}

static int am2320_open(struct inode *inode, struct file *filp)
{
filp->private_data = &am2320dev;

printk("%s\n",__func__);
return 0;
}

static ssize_t am2320_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
short data[2];
long err = 0;

struct am2320_dev *dev = (struct am2320_dev *)filp->private_data;

//printk("%s,%d\n",__func__,__LINE__);

am2320_readdata(dev);

data[0] = dev->hum;
data[1] = dev->tem;
err = copy_to_user(buf, data, sizeof(data));
return err;
}

static int am2320_release(struct inode *inode, struct file *filp)
{
printk("%s\n",__func__);
return 0;
}

static const struct file_operations am2320_ops = {
.owner = THIS_MODULE,
.open = am2320_open,
.read = am2320_read,
.release = am2320_release,
};

static int am2320_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
printk("%s\n",__func__);

if (am2320dev.major)
{
am2320dev.devid = MKDEV(am2320dev.major, 0);
register_chrdev_region(am2320dev.devid, am2320_CNT, am2320_NAME);
}
else
{
alloc_chrdev_region(&am2320dev.devid, 0, am2320_CNT, am2320_NAME);
am2320dev.major = MAJOR(am2320dev.devid);
}

cdev_init(&am2320dev.cdev, &am2320_ops);
cdev_add(&am2320dev.cdev, am2320dev.devid, am2320_CNT);

am2320dev.class = class_create(THIS_MODULE, am2320_NAME);
if (IS_ERR(am2320dev.class))
{
printk("%s,%d\n",__func__,__LINE__);
return PTR_ERR(am2320dev.class);
}

am2320dev.device = device_create(am2320dev.class, NULL, am2320dev.devid, NULL, am2320_NAME);
if (IS_ERR(am2320dev.device))
{
printk("%s,%d\n",__func__,__LINE__);
return PTR_ERR(am2320dev.device);
}
am2320dev.private_data = client;

printk("%s,%d\n",__func__,__LINE__);
return 0;
}

static int am2320_remove(struct i2c_client *client)
{
cdev_del(&am2320dev.cdev);
unregister_chrdev_region(am2320dev.devid, am2320_CNT);

device_destroy(am2320dev.class, am2320dev.devid);
class_destroy(am2320dev.class);
return 0;
}

static const struct i2c_device_id am2320_id[] =
{
{"lxl,am2320", 0},
{},
};

static const struct of_device_id am2320_of_match[] =
{
{.compatible = "lxl,am2320"},
{},
};

static struct i2c_driver am2320_driver =
{
.probe = am2320_probe,
.remove = am2320_remove,
.driver =
{
.owner = THIS_MODULE,
.name = "lxl,am2320",
.of_match_table = am2320_of_match,
},
.id_table = am2320_id,
};

static int __init am2320_init(void)
{
int ret = 0;

printk("%s,%d\n",__func__,__LINE__);
ret = i2c_add_driver(&am2320_driver);
if(ret==0)
printk("i2c_add_driver ok!\n");
else
printk("i2c_add_driver error!\n");

return ret;
}

static void __exit am2320_exit(void)
{
printk("%s\n",__func__);
i2c_del_driver(&am2320_driver);
}

module_init(am2320_init);
module_exit(am2320_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lXl");

I2C 设备和驱动的匹配过程是由 I2C 核心(drivers/i2c/i2c-core.c)来完成的,设备和驱动的匹配过程也是由 I2C 总线完成的 。匹配成功后am2320_probe函数就会调用,在其中处理字符设备的那套东西。

3、应用层

(1)设备查看

加载am2320与OLED驱动

1
2
root@ALIENTEK-IMX6U:~/nfs# insmod oled.ko
root@ALIENTEK-IMX6U:~/nfs# insmod am2320.ko

刚才安装的驱动可以在/dev下查看,如下图所示。

6dEPwq.png

(2)应用程序

驱动层只是将设备运行起来,具体如何使用这些设备,要看应用层。

可以对每个设备单独写应用程序,以便测试设备是否正常工作。下面的程序首先读取am2320传感器的数据,之后将数据写入OLED之中。原理很简单,直接看代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <stdio.h>
#include <unistd.h>
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
#include <time.h>

/*
* @description : main主程序
* @param - argc : argc数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其它 失败
*/
int main(int argc, char *argv[])
{
int fd_am2320,fd_oled;
unsigned short databuf[2];
unsigned short hum,tmp;
int ret = 0;
time_t t;
struct tm *tmp_ptr = NULL;
unsigned char str[16]="H:12.3, T:34.5";

fd_am2320 = open("/dev/am2320", O_RDWR);
if(fd_am2320 < 0)
{
printf("can't open file %s\r\n", "/dev/am2320");
return -1;
}

fd_oled = open("/dev/oled", O_RDWR);
if(fd_oled < 0)
{
printf("can't open file %s\r\n", "/dev/oled");
close(fd_am2320);
return -1;
}

while(1)
{
ret = read(fd_am2320, databuf, sizeof(databuf));
if(ret == 0) /* 数据读取成功 */
{
hum = databuf[0]; /* 湿度 */
tmp = databuf[1]; /* 温度 */
snprintf(str,15,"H:%2.1f, T:%2.1f", 1.0*hum/10,1.0*tmp/10);

time (&t);
tmp_ptr = localtime(&t);
printf("%02d:%02d:%02d --> ", tmp_ptr->tm_hour, tmp_ptr->tm_min, tmp_ptr->tm_sec);
printf("%s\n",str);//lxl:!,'\n'
ret = write(fd_oled, str, 15);
}
sleep(1);//usleep(1000000);
}
close(fd_oled);
close(fd_am2320);
}
(3)运行

上述两步成功后运行应用层APP

1
root@ALIENTEK-IMX6U:~/nfs#./oledapp
(4)运行结果
  • 串口终端

    6dAyLR.png

  • OLED

    6dAUoV.jpg

4 、总结

本文中,使用GPIO模拟SPI来与OLED通信,使用linux之I2C总线与AM2320通信,在应用层读取传感器数据后写入OLED。

使用到了设备树、GPIO、linux I2C,同时在应用层编程,实现数据的获取与显示。

5、主要参考

正点原子资料

https://wenku.baidu.com/view/42efcb877cd184254a353584

https://www.cnblogs.com/ningmeng484/p/10406590.html