1 实现效果
2 说明
问题: 在嵌入式开发中,经常遇到一些问题,比如收到一块开发板,没有屏幕,没有串口,需要调试,只能使用网口连接。 可是如果公式内网不是你管理,无法设置固定IP,那么怎么搞?我开发板IP都不知道怎么连接调试或者写代码?
为了解决这个问题,这便是我此次创作的目的!
看了网上很多方法,其中有的使用扬声器开机播报IP,确实也是可以,不过我最终选择的还是使用OLED屏幕显示IP,其中还可以显示cpu温度、MAC地址、时间等其他信息。
这里我是用的开发环境是用qt开发(虽然这个项目没有ui界面),iic驱动使用树莓派的wiringpi IO驱动库。
oled屏幕淘宝买的,8元包邮!!!(图片无卖家信息)
3 设计思路
软件整体包含三个部分,一个是oled的显示驱动,一个是树莓派需要显示的信息获取,最后就是软件开机运行的设置。 整体思路就是,使用一个定时器,1s 驱动一次,每次显示都需要刷新时间,每30s刷新一次cpu温度、IP地址、MAC地址信息。
我是用的oled显示屏是12832,即128 * 32个像素点,显示字符用的8*8大小的,所以能显示4行信息,最长显示16个字符,所以mac地址会将中间的“:”符号去掉显示。 显示的信息排布:
行号 | 显示内容 | 刷新间隔 |
---|
第一行 | 系统时间 | 1s | 第二行 | cpu温度 | 30s | 第三行 | IP地址 | 30s | 第四行 | MAC地址(eth0) | 30s |
4 硬件连接
随便画的,绘制比较简单,树莓派的3、5引脚为IIC_1的SDA(数据)和SCL(时钟)脚,我们使用的wiringpi库也是使用的这个IIC外设,所以我们的iic屏幕也是挂在这上面。
5 代码设计
5.1 信息获取
(1)时间获取 获取时间是最简单的,使用QTime或者QDateTime类即可获取,代码如下:
#include <QTime>
QString timeStr = QTime::currentTime().toString(" hh:mm:ss ");
上面代码为什么只使用QTime获取时间,因为屏幕宽度有线,无法显示日期+时间,所以把日期去掉了。 “hh:mm:ss” 为显示 “ 时:分:秒 ” 如果需要显示日期,则使用QDateTime类,代码如下:
#include <QDateTime>
QString timeStr = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
(2)cpu温度获取
#include <QFile>
#define TEMP_PATH "/sys/class/thermal/thermal_zone0/temp"
QString OledIP::getCpuTemp()
{
char buf[20];
QFile tempFile(TEMP_PATH);
if(tempFile.open(QFile::ReadOnly))
{
tempFile.read(buf,20);
float temp = atoi(buf) / 1000.0;
return QString::number(temp);
}
return "";
}
(3)IP地址获取
QString OledIP::getLocalIp()
{
QString myIp;
QList<QHostAddress> ipList = QNetworkInterface::allAddresses();
for (int i = 0; i < ipList.size(); ++i)
{
if (ipList.at(i) != QHostAddress::LocalHost && ipList.at(i).toIPv4Address())
{
myIp= ipList.at(i).toString();
break;
}
}
if (myIp.isEmpty())
myIp= QHostAddress(QHostAddress::LocalHost).toString();
return myIp;
}
(4)MAC地址获取
QString OledIP::getMAC(Qstring card = "eth0")
{
QString myMAC;
auto interfaces = QNetworkInterface::allInterfaces();
for (int i = 0; i < interfaces.size(); i++)
{
if(interfaces.at(i).name().contains(card))
if (interfaces.at(i).isValid())
{
myMAC= interfaces.at(i).hardwareAddress().replace(":","");
break;
}
}
return myMAC;
}
输入参数为网卡名字,不同的网卡存在不同的MAC地址,该参数一般传入 “eth0”。
5.2 驱动12832屏幕
(1)写入命令和数据
void oled12832::writeCmd(int fd,unsigned char I2C_Command)
{
wiringPiI2CWriteReg8(fd,0x00, I2C_Command);
}
void oled12832::writeData(int fd,unsigned char I2C_Data)
{
wiringPiI2CWriteReg8(fd,0x40, I2C_Data);
}
(2)初始化寄存器
void oled12832::regInit(int fd)
{
writeCmd(fd,0xAE);
writeCmd(fd, 0x20);
writeCmd(fd, 0x10);
writeCmd(fd, 0xb0);
writeCmd(fd, 0xc8);
writeCmd(fd, 0x00);
writeCmd(fd, 0x10);
writeCmd(fd, 0x40);
writeCmd(fd, 0x81);
writeCmd(fd, 0xff);
writeCmd(fd, 0xa1);
writeCmd(fd, 0xa6);
writeCmd(fd, 0xa8);
writeCmd(fd, 0x3F);
writeCmd(fd, 0xa4);
writeCmd(fd, 0xd3);
writeCmd(fd, 0x00);
writeCmd(fd, 0xd5);
writeCmd(fd, 0xf0);
writeCmd(fd, 0xd9);
writeCmd(fd, 0x22);
writeCmd(fd, 0xda);
writeCmd(fd, 0x12);
writeCmd(fd, 0xdb);
writeCmd(fd, 0x20);
writeCmd(fd, 0x8d);
writeCmd(fd, 0x14);
writeCmd(fd, 0xaf);
}
(3)设置写入位置
void oled12832::oledSetPos(int fd,unsigned char x, unsigned char y)
{
writeCmd(fd, 0xb0 + x);
writeCmd(fd,((y & 0x0f) | 0x00));
writeCmd(fd,(((y & 0xf0) >> 4) | 0x10));
}
(4) 屏幕填充和清空
void oled12832::oledFill(unsigned char data)
{
for (unsigned char i = 0; i < 8; i++)
{
oledSetPos(mOledHard, i, 0);
for (int j = 0; j < 128; j++)
writeData(mOledHard, data);
}
}
void oled12832::oledClear()
{
unsigned char i, j;
for (i = 0; i < 8; i++)
{
oledSetPos(mOledHard, i, 0);
for (j = 0; j < 128; j++)
writeData(mOledHard, 0x00);
}
}
(5)清空某行(0-3,一共4行)
void oled12832::clearLine(int row)
{
oledSetPos(mOledHard,row*2, 0);
for (int j = 0; j < 128; j++)
writeData(mOledHard, 0x00);
oledSetPos(mOledHard,row*2+1, 0);
for (int j = 0; j < 128; j++)
writeData(mOledHard, 0x00);
}
(6)显示字符
void oled12832::showASCLL(unsigned char row, unsigned char col, unsigned char ascii_char)
{
unsigned char i,j;
i = ascii_char - ' ';
writeCmd(mOledHard ,0xb0+row);
writeCmd(mOledHard ,col&0x0F);
writeCmd(mOledHard ,((col&0xF0)>>4)|0x10);
for(j=0;j<8;j++)
writeData(mOledHard ,oled_fonts1608[i][j]);
writeCmd(mOledHard ,0xB0+(row&0x07)+1);
writeCmd(mOledHard ,col&0x0F);
writeCmd(mOledHard ,((col&0xF0)>>4)|0x10);
for(j=0;j<8;j++)
writeData(mOledHard ,oled_fonts1608[i][j+8]);
}
(7)显示字符串
void oled12832::showString(unsigned char row, unsigned char col, unsigned char *ascii_string)
{
row = row*2;
while(*ascii_string != '\0')
{
if(col == 128)
{
col = 0;
row += 2;
}
showASCLL(row, col, *ascii_string);
col += 8;
ascii_string++;
}
}
void oled12832::showString(unsigned char row, unsigned char col, QString str)
{
QByteArray buff = str.toLatin1();
unsigned char *ptr = (unsigned char *)buff.data();
showString(row,col,ptr);
}
5.3 开机启动脚本(保证该程序只运行在一个进程)
为什么还需要脚本启动,而不是直接运行oledIP可执行文件,因为我是将启动方式放在profile中,树莓派开机过程中,好像是会多次运行到此文件,所以会导致多个oledIP进程运行,因此运行该进程之前使用pgrep查询一下是否有oledIP进程,如果没有才执行,脚本内容如下:
if [ $(pgrep -f oledIP | wc -l) -eq 0 ];then
cd /home/pi/mApp
./oledIP &
fi
树莓派自定义进程开机启动的方式很多,可参考其他文章,个人做嵌入式linux开发习惯修改profile文件,其实不推荐此方法。
5.4整体代码
整体代码已经上传至gitee并开源,其中包含编译好的可执行文件,树莓派的ubuntu系统可直接运行。 项目链接: https://gitee.com/jiangtao008/raspi-oledip
|