dirty123 发表于 2024-4-14 12:20:21

【树莓派5 测评】--6.OLED显示

本帖最后由 dirty123 于 2024-4-15 09:14 编辑

      树莓派5有硬件I2C接口,这里准备了128*64分辨率ssd1306 OLED屏,本篇讲述使用wringpi库,C语言代码驱动OLED做显示。
一.准备工作
1.安装wringpi库。前面以讲述不赘述。
2.打开树莓派5的I2C接口。sudo raspi-config命令 或开始菜单路径下配置,也不赘述。
3.查询引脚后,oled与开发板连接好。
图1:查询I2C引脚
oled模块   树莓派5
VCC      3.3V
GND      0V
SCL      SCL.1
SDA      SDA.1
      这里把前面实现过的DHT11也接在开发板上,oled上做温湿度显示。
4.安装i2c工具
sudo apt-get install i2c-toolssudo apt-get install i2c-tools
5.测试oled连接情况,如连接好可看到设备地址
sudo i2cdetect -y 1
图2:OLED屏i2c连接情况

二.代码编写,界面设计
1.ssd1306写命令函数
void ssd1306_command(unsigned int c)
{
      // I2C
      unsigned int control = 0x00;    // Co = 0, D/C = 0
      wiringPiI2CWriteReg8(i2cd, control, c);
}2.ssd1306初始化
void ssd1306_begin(unsigned int vccstate, unsigned int i2caddr)
{
      // I2C Init

      _vccstate = vccstate;

      i2cd = wiringPiI2CSetup(i2caddr);
      if (i2cd < 0) {
                fprintf(stderr, "ssd1306_i2c : Unable to initialise I2C:\n");
                return;
      }
      // Init sequence
      ssd1306_command(SSD1306_DISPLAYOFF);    // 0xAE
      ssd1306_command(SSD1306_SETDISPLAYCLOCKDIV);    // 0xD5
      ssd1306_command(0x80);// the suggested ratio 0x80

      ssd1306_command(SSD1306_SETMULTIPLEX);// 0xA8
      ssd1306_command(SSD1306_LCDHEIGHT - 1);

      ssd1306_command(SSD1306_SETDISPLAYOFFSET);      // 0xD3
      ssd1306_command(0x0);   // no offset
      ssd1306_command(SSD1306_SETSTARTLINE | 0x0);    // line #0
      ssd1306_command(SSD1306_CHARGEPUMP);    // 0x8D
      if (vccstate == SSD1306_EXTERNALVCC) {
                ssd1306_command(0x10);
      } else {
                ssd1306_command(0x14);
      }
      ssd1306_command(SSD1306_MEMORYMODE);    // 0x20
      ssd1306_command(0x00);// 0x0 act like ks0108
      ssd1306_command(SSD1306_SEGREMAP | 0x1);
      ssd1306_command(SSD1306_COMSCANDEC);

#if defined SSD1306_128_32
      ssd1306_command(SSD1306_SETCOMPINS);    // 0xDA
      ssd1306_command(0x02);
      ssd1306_command(SSD1306_SETCONTRAST);   // 0x81
      ssd1306_command(0x8F);

#elif defined SSD1306_128_64
      ssd1306_command(SSD1306_SETCOMPINS);    // 0xDA
      ssd1306_command(0x12);
      ssd1306_command(SSD1306_SETCONTRAST);   // 0x81
      if (vccstate == SSD1306_EXTERNALVCC) {
                ssd1306_command(0x9F);
      } else {
                ssd1306_command(0xCF);
      }

#elif defined SSD1306_96_16
      ssd1306_command(SSD1306_SETCOMPINS);    // 0xDA
      ssd1306_command(0x2);   // ada x12
      ssd1306_command(SSD1306_SETCONTRAST);   // 0x81
      if (vccstate == SSD1306_EXTERNALVCC) {
                ssd1306_command(0x10);
      } else {
                ssd1306_command(0xAF);
      }

#endif
      ssd1306_command(SSD1306_SETPRECHARGE);// 0xd9
      if (vccstate == SSD1306_EXTERNALVCC) {
                ssd1306_command(0x22);
      } else {
                ssd1306_command(0xF1);
      }
      ssd1306_command(SSD1306_SETVCOMDETECT); // 0xDB
      ssd1306_command(0x40);
      ssd1306_command(SSD1306_DISPLAYALLON_RESUME);   // 0xA4
      ssd1306_command(SSD1306_NORMALDISPLAY); // 0xA6

      ssd1306_command(SSD1306_DEACTIVATE_SCROLL);

      ssd1306_command(SSD1306_DISPLAYON);   // --turn on oled panel
}      oled驱动基本是在ssd1306_command函数配合寄存器命令构建。
3.关于界面显示数据源函数,这里选取部分
//获取时间
time(&now);
timenow = localtime(&now);//获取IP
char* GetLocalIp(void)
{
    int MAXINTERFACES=16;
    char *ip;
    int fd, intrface, retn = 0;
    struct ifreq buf;
    struct ifconf ifc;

    if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) >= 0)
    {
      ifc.ifc_len = sizeof(buf);
      ifc.ifc_buf = (caddr_t)buf;
      if (!ioctl(fd, SIOCGIFCONF, (char *)&ifc))
      {
            intrface = ifc.ifc_len / sizeof(struct ifreq);

            while (intrface-- > 0)
            {
                if (!(ioctl (fd, SIOCGIFADDR, (char *) &buf)))
                {
                  ip=(inet_ntoa(((struct sockaddr_in*)(&buf.ifr_addr))->sin_addr));
                  break;
                }
            }
      }
      close (fd);
      return ip;
    }
}
//获取CPU温度
int get_cpu_temp()
{
    FILE *fd;
    int temp;
    char buff;

    fd = fopen("/sys/class/thermal/thermal_zone0/temp","r");
    fgets(buff,sizeof(buff),fd);
    sscanf(buff, "%d", &temp);

    fclose(fd);

    return temp/1000;
}//获取内存占用
MEM_PACK get_memocupy()
{
    FILE *fd;
    int n;
    double mem_total,mem_used_rate;
    char buff;
    MEM_OCCUPY *m = (MEM_OCCUPY*)malloc(sizeof(MEM_OCCUPY));
    MEM_PACK p;

    fd = fopen("/proc/meminfo", "r");

    fgets(buff,sizeof (buff), fd);
    sscanf(buff, "%s %lu %s\n", m->name, &m->total, m->name2);
    mem_total = m->total;
    fgets(buff,sizeof (buff),fd);
    sscanf(buff, "%s %lu %s\n", m->name, &m->total,m->name2);
    mem_used_rate = (1 - m->total/mem_total)*100;
    mem_total = mem_total/(1024*1024);
    p.total = mem_total;
    p.used_rate = mem_used_rate;
    fclose(fd);
    free(m);
    return p;
}4.界面设计
      设计思想是在main函数初始化与参数轮询更新,在Display定时刷新显示。工程目录如下。
图3:工程目录
①main函数
void main()
{
    #if DHT11_USE
    printf("PIN:%d\n", pinNumber);
      
      if (-1 == wiringPiSetup()) {
                printf("Setup wiringPi failed!");
                return;
      }
      pinMode(pinNumber, OUTPUT); // set mode to output
      digitalWrite(pinNumber, 1); // output a high level
   
    #endif
   
    printf("Run OLED\n");
    //屏幕初始化
    ssd1306_begin(SSD1306_SWITCHCAPVCC, SSD1306_I2C_ADDRESS);
    //绑定定时器执行函数
    signal(***ALRM, Display);
    //定时器启用中断 每0.5s执行一次,就是2帧
    set_timer();
    while (1)
    {
      #if DHT11_USE
      pinMode(pinNumber, OUTPUT); // set mode to output
                digitalWrite(pinNumber, 1); // output a high level
                //delay(3000);
                if (readSensorData())
                {
                        printf("Sensor data read ok!\n");
                        printf("RH:%d.%d\n", (databuf >> 24) & 0xff, (databuf >> 16) & 0xff);
                        printf("TMP:%d.%d\n", (databuf >> 8) & 0xff, databuf & 0xff);
                        //databuf = 0;
                }
                else
                {
                        printf("Sensor dosent ans!\n");
                        databuf = 0;
                }
      #endif
      
      //获取时间
      time(&now);
      timenow = localtime(&now);

      //获取IP
      IP_address=GetLocalIp();

      //获取CPU温度
      temp = get_cpu_temp();

      //获取CPU占用
      get_cpuoccupy((CPU_OCCUPY*)&cpu_stat1);
      sleep(10);
      get_cpuoccupy((CPU_OCCUPY*)&cpu_stat2);
      cpu = cal_cpuoccupy((CPU_OCCUPY *)&cpu_stat1,(CPU_OCCUPY*)&cpu_stat2);

      //获取内存占用
      mem = get_memocupy();

      //获取实时流量
      getCurrentDownloadRates(&start_download_rates);    //获取当前流量,并保存在start_download_rates
      sleep(3);   //休眠多少秒,这个值根据宏定义中的WAIT_SECOND的值来确定
      getCurrentDownloadRates(&end_download_rates);    //获取当前流量,并保存在end_download_rates里
                Byte=(end_download_rates - start_download_rates) / 3;


      //获取当前磁盘的使用情况
      get_disk_occupy(&used);

    }
}
②界面设计刷新
oid Display(int m)
{
    //清屏
    ssd1306_clearDisplay();
   
    ssd1306_drawString("Hi,Raspberry_Pi5 !");
    ssd1306_drawString("\r\n");
   
   
    //显示时间
    ssd1306_drawString("    --       ::");
    OLED_ShowNum(0,8,timenow->tm_year+1900,4,1);
    OLED_ShowNum(30,8,timenow->tm_mon,2,1);
    OLED_ShowNum(48,8,timenow->tm_mday,2,1);
    OLED_ShowNum(66+12,8,timenow->tm_hour,2,1);
    OLED_ShowNum(82+12,8,timenow->tm_min,2,1);
    OLED_ShowNum(100+12,8,timenow->tm_sec,2,1);
    ssd1306_drawString("\r\n");

   
    //显示IP
    ssd1306_drawString("IP=");
    ssd1306_drawString(IP_address);
    ssd1306_drawString("\r\n");

    //显示CPU温度
    ssd1306_drawString("CPU Temperature=   C");
    OLED_ShowNum(96,24,temp,2,0);
    ssd1306_drawString("\r\n");

    //显示CPU占用
    ssd1306_drawString("CPU usage=   %");
    OLED_ShowNum(61,32,cpu,2,0);
    ssd1306_drawString("\r\n");

    //显示运存占用
    ssd1306_drawString("mem use rate=%");
    OLED_ShowNum(78,40,mem.used_rate,2,0);
    ssd1306_drawString("\r\n");

    #if DHT11_USE
    //DHT11 温湿度显示
    ssd1306_drawString("RH=.,TEMP=.C");
    OLED_ShowNum(20,48,(databuf >> 24) & 0xff,2,1);
    OLED_ShowNum(35,48,(databuf >> 16) & 0xff,1,1);
    OLED_ShowNum(85,48,(databuf >> 8) & 0xff,2,1);
    OLED_ShowNum(105,48,databuf & 0xff,1,1);
    ssd1306_drawString("\r\n");
    //databuf = 0;
    #else
    //显示适时网速   
    ssd1306_drawString("Download=       B/s");
    OLED_ShowNum(54,48,Byte,6,1);
    ssd1306_drawString("\r\n");
    #endif

    //显示适时磁盘读写
    ssd1306_drawString("Disk used=");
    ssd1306_drawString(used);

    ssd1306_display();
}

5.编译运行
①编译
gcc oled.c ssd1306_i2c.c dht11.c-lwiringPi -o oled
这里将oled.c,ssd1306.c和dht11.c 分别编译后链接成oled可执行文件,-l用来指定依赖库
②运行
./oled
图4:编译运行

三.OLED显示效果与开机固化程序
1.OLED显示效果
      执行上述,OLED开始显示运行,效果如下
图5:OLED显示效果

2.将程序固化到开机启动
①pwd获取执行文件所在路径
图6:获取可执行文件路径
②添加开机自启
sudo vim /etc/rc.local

③在exit()之前添加,格式nohup (编译好oled文件的全名--带路径的) &
nohup /home/cool/project/code/oled_combine/oled &
④关机断电后上电,查看效果
sudo reboot
断电后开机,可以看到图5所示效果,说明固化开机程序生效。


      至此,实现了树莓派5驱动OLED显示,且应用丰富。






页: [1]
查看完整版本: 【树莓派5 测评】--6.OLED显示

Baidu
map