硬件环境
OLED
OLED用这个,4pin的I2C驱动,尺寸为0.像素为1286英寸x64。
AHT20
程序配置
I2C部分
这里就不用介绍太多了,可以参考我之前写的另一个笔记: STM32F103C6T6 | 模拟IIC主机读取AHT20温湿度传感器数据 虽然这种用法只有16个M主频的HT66F3195
效率不是很高,但考虑到后续的开发和可读性,首先选择这种方法,如果你必须追求效率,换回直接操作寄存器的方法。
OLED部分
所有操作都是基于模拟I2C过去发送数据给主机OLED。 OLED.c
发送数据
void OLED_Write_Data(unsigned char data) {
I2C_Start(&I2C_Dev[I2C_OLED]); I2C_Write_Byte(&I2C_Dev[I2C_OLED], (( (OLED_Dev_Address)<<1 ) | (OLED_In_Rx) )); if(I2C_Check_ACK(&I2C_Dev[I2C_OLED]) == I2C_NACK) {
I2C_Stop(&I2C_Dev[I2C_OLED]); return ; } GCC_DELAY(I2C_Delay); /*------------------------------------------------------------*/ I2C_Write_Byte(&I2C_Dev[I2C_OLED], 0x40); if(I2C_Check_ACK(&I2C_Dev[I2C_OLED]) == I2C_NACK) {
I2C_Stop(&I2C_Dev[I2C_OLED]); return ;
}
GCC_DELAY(I2C_Delay);
/*------------------------------------------------------------*/
I2C_Write_Byte(&I2C_Dev[I2C_OLED], data);
if(I2C_Check_ACK(&I2C_Dev[I2C_OLED]) == I2C_NACK)
{
I2C_Stop(&I2C_Dev[I2C_OLED]);
return ;
}
GCC_DELAY(I2C_Delay);
/*------------------------------------------------------------*/
I2C_Stop(&I2C_Dev[I2C_OLED]);
return ;
}
发送命令
void OLED_Write_Command(unsigned char com)
{
I2C_Start(&I2C_Dev[I2C_OLED]);
I2C_Write_Byte(&I2C_Dev[I2C_OLED], (( (OLED_Dev_Address)<<1 ) | (OLED_In_Rx) ));
if(I2C_Check_ACK(&I2C_Dev[I2C_OLED]) == I2C_NACK)
{
I2C_Stop(&I2C_Dev[I2C_OLED]);
return ;
}
GCC_DELAY(I2C_Delay);
/*------------------------------------------------------------*/
I2C_Write_Byte(&I2C_Dev[I2C_OLED], 0x00);
if(I2C_Check_ACK(&I2C_Dev[I2C_OLED]) == I2C_NACK)
{
I2C_Stop(&I2C_Dev[I2C_OLED]);
return ;
}
GCC_DELAY(I2C_Delay);
/*------------------------------------------------------------*/
I2C_Write_Byte(&I2C_Dev[I2C_OLED], com);
if(I2C_Check_ACK(&I2C_Dev[I2C_OLED]) == I2C_NACK)
{
I2C_Stop(&I2C_Dev[I2C_OLED]);
return ;
}
GCC_DELAY(I2C_Delay);
/*------------------------------------------------------------*/
I2C_Stop(&I2C_Dev[I2C_OLED]);
return ;
}
初始化
这一段初始化程序是参考了别人的数据,但是具体是哪一篇我也不记得了,因为有很多都是一样的。
void OLED_Init() { OLED_Write_Command(0xAE); //关闭显示 OLED_Write_Command(0x00);//设置低列地址 OLED_Write_Command(0x10);//设置高列地址 OLED_Write_Command(0x40);//设置起始行地址,集映射RAM显示起始行(0x00~0x3F) OLED_Write_Command(0x81);//设置对比度控制寄存器 OLED_Write_Command(0xCF);//设置SEG输出电流亮度 OLED_Write_Command(0xA1);//段重定义设置,bit0:0,0->0;1,0->127; 0xa0左右反置 0xa1正常 OLED_Write_Command(0xC8);//设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数 0xc0上下反置 0xc8正常 OLED_Write_Command(0xA6);//设置正常显示(设置显示方式;bit0:1,反相显示;0,正常显示 ) OLED_Write_Command(0xA8);//设置驱动路数 设置多路复用比(1比64) OLED_Write_Command(0x3F);//1/64 duty(默认0X3F(1/64)) OLED_Write_Command(0xD3);//设置显示偏移位移映射RAM计数器(0x00~0x3F) OLED_Write_Command(0x00);//-not offset OLED_Write_Command(0xD5);//设置显示时钟分频比/
振荡器频率 OLED_Write_Command(0x80);//设置分频比,设置时钟为100帧/秒 OLED_Write_Command(0xD9);//设置预充电周期 OLED_Write_Command(0xF1);//设置预充15个时钟,放电1个时钟([3:0],PHASE 1;[7:4],PHASE 2;) OLED_Write_Command(0xDA);//设置COM硬件引脚配置 OLED_Write_Command(0x12);//[5:4]配置 OLED_Write_Command(0xDB);//设置VCOMH 电压倍率 OLED_Write_Command(0x40);//Set VCOM 释放电压([6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc;) OLED_Write_Command(0x20);//设置页面寻址模式(0x00/0x01/0x02) OLED_Write_Command(0x00);//[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10; OLED_Write_Command(0x8D);//设置充电泵启用/禁用 OLED_Write_Command(0x14);//设置(0x10禁用,0x14启用) OLED_Write_Command(0xA4);// 全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏) (0xa4/0xa5) OLED_Write_Command(0xA6);// 设置显示方式;bit0:1,反相显示;0,正常显示 (0xa6/a7) OLED_Write_Command(0xAF);//开启显示 OLED_Set_Position(0, 0); OLED_Display_All(0x00); }
设定坐标
void OLED_Set_Position(unsigned char x, unsigned char y)
{
OLED_Write_Command(0xB0 + y);
OLED_Write_Command((x >> 4) + 0x10);
OLED_Write_Command(x & 0x0F);
}
全屏显示或者熄灭
这个主要是配合自己测试用的,自己去改需要写的数据为0还是1去刷整个屏幕。
void OLED_Display_All(unsigned char data)
{
unsigned char i, j;
for(i = 0; i < 8; i++)
{
Feed_Dog();
for(j = 0; j < 128; j++)
{
OLED_Write_Data(data);
}
}
}
清除屏幕指定区域
void OLED_Clear_Area(unsigned char x_start, unsigned char x_end, unsigned char y_start, unsigned char y_end)
{
unsigned char i, j;
OLED_Set_Position(x_start, y_start);
for(i = y_start; i < y_end + 1; i++)
{
OLED_Set_Position(x_start, i);
for(j = x_start; j < x_end + 1; j++)
{
OLED_Write_Data(0x00);
}
}
}
判断是否为特殊符号
如果返回一个0xFF证明不是符号,因为目前这个阶段也不会有这么多符号,只列举一些常见常用的,后续添加的话直接加到尾部。
const char OLED_Symbol_Table[] =
{
',',
'.',
'!',
'~',
':',
';',
'/',
'?',
'*',
' ',
'%',
};
const unsigned char OLED_Symbol_Table_Length = sizeof(OLED_Symbol_Table);
for循环会在已经列举出的符号表内遍历,如果有配对的符号则返回对应的索引号。
unsigned char OLED_Is_Symbol(char sym)
{
unsigned char flag = 0xFF;
unsigned char i;
for(i = 0; i < OLED_Symbol_Table_Length; i++)
{
if(sym == OLED_Symbol_Table[i])
flag = i;
}
return flag;
}
显示单个英文字符
因为后续有一个输出英文字符串的操作,一般是要配合标点符号输出,所以在这里面加了一个标点符号的判断和输出。
void OLED_Set_En_Char(unsigned char x, unsigned char y, char n)
{
unsigned char i;
unsigned char index;
unsigned char flag = 0;
flag = OLED_Is_Symbol(n);
if(flag != 0xFF) /* 如果为标点符号 */
{
index = flag;
}
else /* 如果不是标点符号,即为英文字符 */
{
if(n < 90)
index = (unsigned char)n - 65;
else
index = (unsigned char)n - 71;
}
OLED_Set_Position(x, y);
for(i = 0; i < 8; i++)
{
if(flag != 0xFF)
OLED_Write_Data(OLED_Symbol_Font[index][i]);
else
OLED_Write_Data(OLED_En_Font[index][i]);
}
OLED_Set_Position(x, y + 1);
for(i = 8; i < 16; i++)
{
if(flag != 0xFF)
OLED_Write_Data(OLED_Symbol_Font[index][i]);
else
OLED_Write_Data(OLED_En_Font[index][i]);
}
Feed_Dog();
}
显示英文字符串
这里面有一个自动换行的操作,就是先一步判断,输出下一个字符的空间,够不够放一个字符,如果空间够就放,不够的话就切换到下一行再输出,这里的一行对于屏幕来说其实是两行,因为一个字符的高度本身就要占掉屏幕的两行。
void OLED_Set_En_String(unsigned char x, unsigned char y, char *p)
{
unsigned char x_temp = x, y_temp = y;
char *tp = p;
OLED_Set_Position(x, y);
while(*tp != '\0')
{
OLED_Set_En_Char(x_temp, y_temp, *tp);
if((x_temp + 8) < 120) /* 判断是否达到行末端 */
{
x_temp += 8; /* 未到达继续步进单个字符 */
}
else
{
x_temp = 0; /* 如果到达x轴边界,进行一个字符的换行 */
if((y_temp + 2) < 7)
y_temp += 2;
else /* y轴边界暂时不处理,理论上应该会回到左上角起点的位置 */
y_temp += 0;
}
tp += 1;
}
}
显示单个数字
这个跟显示单个英文字符差不多是一样的,但是因为不存在大小写映射关系,所以简化了一些。
void OLED_Set_Num_Char(unsigned char x, unsigned char y, unsigned char n)
{
unsigned char i;
unsigned char index;
unsigned char flag = 0;
flag = OLED_Is_Symbol(n);
if(flag != 0xFF) /* 如果为标点符号 */
{
index = flag;
}
else
{
index = (unsigned char)n - 48;
}
OLED_Set_Position(x, y);
for(i = 0; i < 8; i++)
{
if(flag != 0xFF)
OLED_Write_Data(OLED_Symbol_Font[index][i]);
else
OLED_Write_Data(OLED_Num_Font[index][i]);
}
OLED_Set_Position(x, y + 1);
for(i = 8; i < 16; i++)
{
if(flag != 0xFF)
OLED_Write_Data(OLED_Symbol_Font[index][i]);
else
OLED_Write_Data(OLED_Num_Font[index][i]);
}
Feed_Dog();
}
显示数字串
输出字符串的这个几乎也是一模一样,但是如果我要将两个合并在一起的话,我还要额外加形参输入,函数里面一大片分支判断,个人又不是很喜欢这种形式。
void OLED_Set_Num_String(unsigned char x, unsigned char y, char *p)
{
unsigned char x_temp = x, y_temp = y;
char *tp = p;
OLED_Set_Position(x, y);
while(*tp != '\0')
{
OLED_Set_Num_Char(x_temp, y_temp, *tp);
if((x_temp + 8) < 120) /* 判断是否达到行末端 */
{
x_temp += 8; /* 未到达继续步进单个字符 */
}
else
{
x_temp = 0; /* 如果到达x轴边界,进行一个字符的换行 */
if((y_temp + 2) < 7)
y_temp += 2;
else /* y轴边界暂时不处理,理论上应该会回到左上角起点的位置 */
y_temp += 0;
}
tp += 1;
}
}
显示单个符号
void OLED_Set_Symbol_Char(unsigned char x, unsigned char y, unsigned char n)
{
unsigned char i;
OLED_Set_Position(x, y);
for(i = 0; i < 8; i++)
{
OLED_Write_Data(OLED_Symbol_Font[n][i]);
}
OLED_Set_Position(x, y + 1);
for(i = 8; i < 16; i++)
{
OLED_Write_Data(OLED_Symbol_Font[n][i]);
}
}
显示汉字
void OLED_Set_Cn_Char(unsigned char x, unsigned char y, unsigned char n)
{
unsigned char i;
OLED_Set_Position(x, y);
for(i = 0; i < 16; i++)
{
OLED_Write_Data(OLED_Cn_Font[n][i]);
}
Feed_Dog();
OLED_Set_Position(x, y + 1);
for(i = 16; i < 32; i++)
{
OLED_Write_Data(OLED_Cn_Font[n][i]);
}
Feed_Dog();
}
显示图片
忘记那个图片的数组是怎样的了,如果是二维数组的话可能还有点麻烦。 不记得二维数组跟指针之间的关系了,但如果是一个128x64的超大一维数组反而还容易处理。 连续往里面写数据,因为目前采用的是页地址,遇到边界自己进行跳转,不用自己额外控制坐标。
这个是全局的图片,所以尺寸固定是128x64这种。
void OLED_Set_Pic(const unsigned char *p)
{
unsigned char i, j;
OLED_Display_All(0x00);
OLED_Set_Position(0, 0);
for(i = 0; i < 8; i++)
{
Feed_Dog();
for(j = 0; j < 128; j++)
{
OLED_Write_Data(*(p + 128*i + j));
}
}
}
显示信息
这个是配合那个温湿度的数据来显示,