浦江一水 发表于 2023-11-1 13:17:59


上面使用STC32G做实验键盘、LCD1602显示驱动以及串口通信的同时, 已经将LCD1602做成了一个简单的“智能串口屏”了, 由此也引发一个思考。。。

关于LCD1602的驱动转接板的思考:


众所周知, LCD1602显示屏是一种经典而简单的显示设备, 几乎所有C51单片机学习开发板,都将它作为基本的外设配置, 或者加上对LCD1602的接口.
然而,驱动LCD1602需要8位数据 口,3位控制口, 共11位IO口,占用IO口比较多, 就算是采用四线制数据口方式, 也要占用7位IO口.
那么能不能占用较少的比如2位IO口来驱动呢? 答案是有的。

市面有一种被称之为“LCD1602_IIC驱动转接板”, 就是通过Vcc,GND,SCL,SDA一共四线, 实际是2根数据线,2根电源线.
如图所示.....




我原以为这个方案是可以用最少的接口、更方便地驱动LCD1602了, 于是就买了一套来学习实验了。
如图所示....



但是, 实验结果却令我大失所望.....

原来这个方案是通过一片PCF8574串转并的芯片, 用IIC数据通讯的方式, 模拟LCD1602的控制时序,来达到对LCD1602的8位数据口和3位控制口的控制驱动的.
从硬件经济成本方面来说,LCD1602 ,RMB > 3.5元, 转接板 > RMB 5元,(还不算运费...),并不便宜....
从编程软件上来说,要驱动LCD6102的话, 就要编程用IIC的通讯方式语句,来模拟实现原来的8位数据口和3位控制口的控制时序, 完全是隔山打牛, 编程复杂,语句逻辑变得很古怪, 语言效率非常低下, 移植麻烦....

而如果使用STC单片机(小封装的芯片)来做这个"转接板"的话, 无论是从硬件成本还是从软件编程来说, 都是大大胜出的了...
在STC单片机小电路板方面, 只要连接LCD1602的原来接口,按原来的LCD1602驱动接口时序编程,
再增加一个简单的串口通讯协议, 将串口指令(自定义)转换为LCD1602显示指令, 就能实现对LCD1602的全面控制。
这是充分利用了STC单片机外围元件少而功能强大的特点, 实现最少的接口、最低的成本、最简便的语句来驱动LCD1602显示屏。转接板同样可以做的很小,经济成本更低.
从理论上讲, 对于常用的128*64,320*240等小面积单色或彩色显示屏, 都可以做成“智能串口屏”。这一点应该引起开发“IIC转接板”的开发者和经销商的注意了。

浦江一水 发表于 2023-11-3 21:07:23


学用STC32G12K128的实践和思考在继续进行中……

实验IIC单色OLED显示屏

人们总是希望用最少的IO、最简单的接口来控制最基本的输出设备、控制使用显示屏。我当然也是这样想的。

市面上有一种四针接口的单色显示屏,除了电源线以外,只用两根IO口即可驱动,一共是四根线。那就是淘宝上看到的,0.96寸的IIC接口的OLED显示屏。如图。。。

这种显示屏,屏幕显示面积很小,实际应用到项目中去的可能性较小,但是分辨率128*64比较常用,倒是用做单片机学习,实验显示屏编程是比较经济实惠的,价格便宜,仅RMB 7.4元,就淘了一块来做实验。
在自制的实验板中,原先LCD1602接口,最末尾四个引脚是:D6,D7,A+,K-,这块显示屏引脚是GND、VCC,SCL、SDA 。 倒过来插入, 正好,无需做任何改动,即可实验。
为此,编写了一段简单的程序,点亮了显示屏。实现了STC32G12K128对OLED单色128*64显示屏的控制输出。如图所示……


在这个实践过程中,学习编程两线IIC协议的通讯,实现了部分简单的显示功能:6*8点阵西文字符串显示、8*16点阵西文字符串显示、16点阵中西文混合显示。

源代码稍后整理贴出…… 供有兴趣的同学们参考……

浦江一水 发表于 2023-11-3 21:50:58

主程序


//========================================================================
// 实验STC32G驱动OLED单色显示屏(128*64)。。。
// MCU:STC32G12K128 4KEY 实验板
// 特点: 中西文混合显示,增强源码可读性...
// 编程: 浦晓明(浦江一水) 2023-10 ...
//=====================================================


#include <STC32G.H>
#include <stdio.h>
#include <string.h>
#include "OLED_IIC.h"


//延时函数...//@11.0592MHz
void Delayms(unsigned int xms)
{ unsigned long i;
while(xms--)
{ _nop_(); _nop_();
    i = 2763UL;
    while (i) i--;
}
}
//主程序入口
void main()
{
    EAXFR = 1;   //这里使能XFR
    CKCON = 0x00;//设置外部数据总线时钟速度最快
    WTST= 0x00;//这里是CPU等待每条指令的时间
       
   P2M1 = 0x00;
   P2M0 = 0x00;


   OLED_Init();          //显示屏初始化
   OLED_ON();          //开显示
   OLED_CLS();         //清屏
   OLED_A8(6,0,"-* TestOLED_IIC *-",18);
   OLED_A8(0,1,"--------------------",20);
   OLED_P16(20,2,"STC-单片机");   //中西文混合编写,增强源码可读性...
   OLED_A8(30,4, "2023-10-17",10);
   OLED_A8(30,5,"----------",10);
   OLED_A16(12,6,"PuXiaoMingOK!",13);

    while(1)
    { //LED指示程序正在运行中...
       P20=0;P21=0;P22=0;P23=0;
       Delayms(100);
       P20=1;P21=1;P22=1;P23=1;
      Delayms(900);
    }
}


浦江一水 发表于 2023-11-3 21:57:03

OLED显示屏驱动头文件

//=====================================================
// 文件: OLED_IIC.H
// 功能: OLED_IIC 4针接口单色显示屏 驱动程序
// MCU:STC32G12K128 4KEY 实验板
// 接口: P0.7 -- SCL 时钟   P0.6 -- SDA 数据
// 整理: 浦晓明(浦江一水) 2023-10 ...
//=====================================================
#ifndef _OLED_IIC_H
#define _OLED_IIC_H

#include "STC32G.H"                //基于: STC32G系列

//根据实验电路板实际情况定义端口...
//同时 在这里通过宏定义体现IIC协议的相关端口电平状态变化   */
#define IIC_SDA_H         P06=1   //数据端高电平
#define IIC_SDA_L          P06=0   //数据端低电平
#define IIC_SCL_H          P07=1   //时钟端高电平
#define IIC_SCL_L         P07=0   //时钟端低电平
#define IIC_SDA_READ        P06       //读数据端电平

void OLED_Init(void);             //OLED初始化
void OLED_ON(void);             //开显示
void OLED_OFF(void);            //关显示

void OLED_CLS(void);            //清除屏幕
void OLED_SetXY(unsigned char X,unsigned char Y);                                                       //定位坐标 X:水平列(0..127) Y:垂直行(0..7)
void OLED_A8( unsigned char X,unsigned char Y,unsigned char *str,unsigned char len);//显示6*8点阵ASCII字符串
void OLED_A16(unsigned char X,unsigned char Y,unsigned char *str,unsigned char len); //显示8*16点阵ASCII字符串
void OLED_P16(unsigned char X,unsigned char Y,unsigned char *str);                           //中西文混合显示16点阵汉字和8*16点阵ASC字符串

#endif


浦江一水 发表于 2023-11-3 22:02:27

本帖最后由 浦江一水 于 2023-11-3 22:04 编辑

OLED驱动函数的具体实现....

//=====================================================
// 文件:OLED_IIC.C4针接口单色显示屏 驱动程序
// MCU:STC32G12K128 4KEY 实验板
// 接口: P0.7 -- SCK 时钟   P0.6 -- SDA 数据
// 编程整理: 浦晓明(浦江一水) 2023-10 ...
//=====================================================
#include <STC32G.H>
#include <stdio.h>
#include <string.h>
#include "OLED_IIC.H"
#include "OLED_Font.H"      //字库:包括ASCII字符和汉字

//延时函数...//@11.0592MHz
void Delay_ms(unsigned int xms)
{ unsigned long i;
while(xms--)
{ _nop_(); _nop_();
    i = 2763UL;
    while (i) i--;
}
}
//==关于IIC内部基础函数=========
// IIC 初始化,把2条端口电平拉高(为总线释放状态)
void IIC_Init(void)
{
    P0M1 = 0x00;P0M0 = 0x00;
    IIC_SDA_H;IIC_SCL_H; _nop_();_nop_();_nop_();_nop_();
}
//开始信号函数 //开始条件: 时钟端高电平时,数据端下降沿跳变.
void IIC_Start(void)
{
    IIC_SDA_H;_nop_();_nop_();_nop_();_nop_();_nop_();
    IIC_SCL_H;_nop_();_nop_();_nop_();_nop_();_nop_();
    IIC_SDA_L;_nop_();_nop_();_nop_();_nop_();_nop_();
    IIC_SCL_L;//开始后时钟端保持低电平....
}
//结束信号函数
//结束条件: 时钟端高电平时,数据端上升沿跳变.
void IIC_Stop(void)
{
    IIC_SCL_L;
    IIC_SDA_L; _nop_();_nop_();_nop_();_nop_();
    IIC_SCL_H;
    IIC_SDA_H; _nop_();_nop_();_nop_();_nop_();_nop_();
}
//应答信号函数
//数据端为低电平时,发一个时钟脉冲...
void IIC_Ack(void)
{
    IIC_SCL_L;
    IIC_SDA_L; _nop_();_nop_();
    IIC_SCL_H; _nop_();_nop_();_nop_();_nop_();_nop_();
    IIC_SCL_L;
}
//非应答信号函数
//数据端为高电平时,发一个时钟脉冲...
void IIC_NAck(void)
{
IIC_SCL_L;
IIC_SDA_H;_nop_();_nop_();
IIC_SCL_H;_nop_();_nop_();_nop_();_nop_();_nop_();
IIC_SCL_L;
}
// 等待从机应答信号函数:
unsigned char IIC_Wait_Ack(void)
{ unsigned char ACK = 0;
IIC_SDA_H;_nop_();
IIC_SCL_H;_nop_();
if(IIC_SDA_READ)ACK = 1;
else            ACK = 0;
IIC_SCL_L;_nop_();_nop_();
return ACK;
}
// 发送一个字节数据函数
void IIC_SendByte(unsigned char dat)
{ unsigned char i;
for(i = 0;i < 8;i++)
{ if(dat & 0x80)IIC_SDA_H;
    else          IIC_SDA_L;
    dat<<=1;      _nop_();_nop_();
    IIC_SCL_H;    _nop_();_nop_();
    IIC_SCL_L;    _nop_();_nop_();
}
}
//读取一个字节数据函数
unsigned char IIC_ReaByte(void)
{ unsigned char i,receive = 0;
for(i=0;i<8;i++)
{ IIC_SCL_L;   _nop_();_nop_();
    IIC_SCL_H;
    receive<<=1;
    if(IIC_SDA_READ)receive++;
    _nop_();
} _nop_();_nop_();_nop_();
IIC_SCL_L;
return receive;
}
// 向OLED发送指令函数
void IIC_SendCMD(unsigned char IIC_Command)
{
IIC_Start();                //开始信号
IIC_SendByte(0x78); //发送设备地址
IIC_Wait_Ack();         //等待应答,这里应答可以不用处理,一般会给一个应答信号
IIC_SendByte(0x00); //设置D/C为0,为指令模式
IIC_Wait_Ack();         //等待应答
IIC_SendByte(IIC_Command); //发送指令
IIC_Wait_Ack();         //等待应答
IIC_Stop();                //结束信号
}
// 向OLED发送数据函数
void IIC_SendDAT(unsigned char IIC_Data)
{
IIC_Start();                //开始信号
IIC_SendByte(0x78); //发送设备地址
IIC_Wait_Ack();         //等待应答,这里应答可以不用处理,一般会给一个应答信号
IIC_SendByte(0x40); //设置D/C为1,为数据模式
IIC_Wait_Ack();         //等待应答
IIC_SendByte(IIC_Data);//发送数据
IIC_Wait_Ack();         //等待应答
IIC_Stop();                //结束信号
}
//==外部函数==屏幕初始化====

void OLED_Init(void)
{
IIC_Init();                   //IIC协议初始化
IIC_Stop();                //IIC协议初始化
Delay_ms(200);      //上电后延迟,等待复位。
IIC_SendCMD(0xAE);//.关闭显示
IIC_SendCMD(0xD5);//.设置时钟分频因子,震荡频率
IIC_SendCMD(0x80);//.低4位设置分频因子,高4位设置震荡频率
IIC_SendCMD(0xA8);//.设置驱动路数
IIC_SendCMD(0x3F);//.默认0x3F(1/64)
IIC_SendCMD(0xD3);//.设置显示偏移
IIC_SendCMD(0x00);//.默认为0
IIC_SendCMD(0x00);//.设置显示位置-列低地址
IIC_SendCMD(0x10);//.设置显示位置-列高地址
IIC_SendCMD(0x40);//.设置显示开始行,行数
IIC_SendCMD(0x8D);//.电荷泵设置
IIC_SendCMD(0x14);//.bit2:开启/关闭
IIC_SendCMD(0x20);//设置内存寻址模式
IIC_SendCMD(0x02);//设置成为页面寻址模式
IIC_SendCMD(0xA1);//.段重定义设置,bit0:0,0->1;1:0->127;这里是设置成1,是将127映射到SEG0
IIC_SendCMD(0xC8);//.设置COM扫描方向,bit3:0:普通模式;1:重定义模式(COM->COM0,N:驱动路数);
IIC_SendCMD(0xDA);//.设置COM引脚的硬件配置
IIC_SendCMD(0x12);//.A,这里只配置了第5位,启动了COM的左右映射,顺序COM引脚配置
IIC_SendCMD(0x81);//.设置对比度
IIC_SendCMD(0xEF);//.这里设置成EF,默认是7F,数据越大对比度越大
IIC_SendCMD(0xD9);//.设置预充电周期
IIC_SendCMD(0xF1);//.第一阶段为1个时钟周期,第二阶段为15个时间周期
IIC_SendCMD(0xDB);//.设置VCOMH电压倍率
IIC_SendCMD(0x30);//.000:0.65*vcc;001:0.77*vcc;011:0.83*vcc;
IIC_SendCMD(0xA4);//恢复到RAM内容显示
IIC_SendCMD(0xA6);//.设置显示方式;bit0:1反向显示;0正常显示;
IIC_SendCMD(0xAF);//.开启显示
//OLED_CLS();
}
//==外部函数==清屏函数 ========================
void OLED_CLS(void)
{ unsigned char i,j;
for(i = 0;i < 8;i++)            //由于SSD1306分了8页(行):0-7
{ IIC_SendCMD(0xB0+i);//按页分配总共8页:0xB0-0xB7
    IIC_SendCMD(0x00);   //设置低4位地址,这里重新配置会被置为0000
    IIC_SendCMD(0x10);   //设置高4位地址,这里重新配置会被置为0000
    for(j = 0;j < 128;j++)    //每一行的长度是128点
    { IIC_SendDAT(0x00); }//每次都写0x00
}
}
//OLED开显示函数
void OLED_ON(void)
{
IIC_SendCMD(0x8D);//电荷泵设置指令
IIC_SendCMD(0x14);//打开电荷泵
IIC_SendCMD(0xAF);//打开显示
}
//OLED关显示函数
void OLED_OFF(void)
{
IIC_SendCMD(0x8D);//电荷泵设置指令
IIC_SendCMD(0x10);//关闭电荷泵
IIC_SendCMD(0xAE);//关闭显示
}
// 设置行列光标坐标函数: X:0..127 Y:0..7
void OLED_SetXY(unsigned char X,unsigned char Y)
{
IIC_SendCMD(0xB0+Y);                  //确定页面开始
IIC_SendCMD((X&0xF0)>>4|0x10);//设置高4位的列
IIC_SendCMD((X&0x0F));                  //设置低4位的列
}
//显示6*8点阵ASCII字符串
//入口参数: X,Y坐标,str字符串,len长度
void OLED_A8( unsigned char X,unsigned char Y,unsigned char *str,unsigned char len)
{unsigned int l,i=0;
   unsigned char ch;
   for(l=0;l<len;l++)
   { ch=str-0x20;
   OLED_SetXY(X+l*6,Y);
   for(i=0;i<6;i++)IIC_SendDAT(ASC8);
   }
}
// ******OLED_IIC.C END******************************************************************

浦江一水 发表于 2023-11-3 22:14:37

本帖最后由 浦江一水 于 2023-11-15 19:37 编辑

字库文件(简略表达)

#ifndef _OLED_FONT_H
#define _OLED_FONT_H

/*****6*8点阵ASCII字符点阵模 ************************************/
unsigned char code ASC8[] =
{
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,// sp
    0x00, 0x00, 0x00, 0x2f, 0x00, 0x00,   // !
    0x00, 0x00, 0x07, 0x00, 0x07, 0x00,// "
    0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14,    // #
    0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12,   // $
    0x00, 0x62, 0x64, 0x08, 0x13, 0x23,// %
    0x00, 0x36, 0x49, 0x55, 0x22, 0x50,// &

    .........

    0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C,// y
    0x00, 0x44, 0x64, 0x54, 0x4C, 0x44,// z
    0x14, 0x14, 0x14, 0x14, 0x14, 0x14,// horiz lines
};

/************ 8*16点阵ASCII字符点阵模 ************************************/
unsigned char code ASC16[]=      
{
      0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,   // - -
      0x00,0x00,0x38,0xFC,0xFC,0x38,0x00,0x00,0x00,0x00,0x00,0x0D,0x0D,0x00,0x00,0x00,// -!-
      0x00,0x0E,0x1E,0x00,0x00,0x1E,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// -"-
      0x20,0xF8,0xF8,0x20,0xF8,0xF8,0x20,0x00,0x02,0x0F,0x0F,0x02,0x0F,0x0F,0x02,0x00,    // -#-
      0x38,0x7C,0x44,0x47,0x47,0xCC,0x98,0x00,0x03,0x06,0x04,0x1C,0x1C,0x07,0x03,0x00, // -$-
      0x30,0x30,0x00,0x80,0xC0,0x60,0x30,0x00,0x0C,0x06,0x03,0x01,0x00,0x0C,0x0C,0x00, // -%-
      0x80,0xD8,0x7C,0xE4,0xBC,0xD8,0x40,0x00,0x07,0x0F,0x08,0x08,0x07,0x0F,0x08,0x00, // -&-

       .........

      0x00,0x00,0x00,0xBC,0xBC,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x0F,0x00,0x00,0x00, // -|-
      0x00,0x04,0x04,0xBC,0xF8,0x40,0x40,0x00,0x00,0x08,0x08,0x0F,0x07,0x00,0x00,0x00, // -}-
      0x08,0x0C,0x04,0x0C,0x08,0x0C,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // -~-
      0x80,0xC0,0x60,0x30,0x60,0xC0,0x80,0x00,0x07,0x07,0x04,0x04,0x04,0x07,0x07,0x00, // --
};

/* **************** 自定义中文小字库,16*16点阵*********************************************/
#defineTotalHZ163                                                 //说明一下汉字总数(根据需要扩展...)
unsigned char code HZ16M[]={ "单","片","机",};   //汉字索引列表(根据需要扩展...)

//具体点阵模数组... ( 取模方式: 纵向8点下高位 )      //数组顺序与汉字索引列表排列一致
unsigned char code HZ16[]={
/* 单0 */{
0x00,0x00,0xF8,0x28,0x29,0x2E,0x2A,0xF8,0x28,0x2C,0x2B,0x2A,0xF8,0x00,0x00,0x00,
0x08,0x08,0x0B,0x09,0x09,0x09,0x09,0xFF,0x09,0x09,0x09,0x09,0x0B,0x08,0x08,0x00,},
/* 片1 */{
0x00,0x00,0x00,0xFE,0x10,0x10,0x10,0x10,0x10,0x1F,0x10,0x10,0x10,0x18,0x10,0x00,
0x80,0x40,0x30,0x0F,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0xFF,0x00,0x00,0x00,0x00,},
/* 机2 */{
0x08,0x08,0xC8,0xFF,0x48,0x88,0x08,0x00,0xFE,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,
0x04,0x03,0x00,0xFF,0x00,0x41,0x30,0x0C,0x03,0x00,0x00,0x00,0x3F,0x40,0x78,0x00,},
};

//****** 字库文件 END ************************************************************************





浦江一水 发表于 2023-11-6 11:46:38

实践与思考: 为什么在驱动OLED_IIC单色显示屏时, 没用 STC32G12K128 的硬件IIC接口功能?

有两个原因:

1, 在自制的实验电路板上, 已经设有LCD1602的IO接口, 在驱动0.96寸OLED_IIC单色显示屏时, 是利用了现有的接口:P0.7 -- SCK 时钟, P0.6 -- SDA 数据. 在这两个端口上没有硬件IIC功能,只好用普通IO口来模拟IIC口;

2, 思想懒惰,急功近利: 没有静心去研究STC3212K128有关IIC的13个寄存器各个位的定义和功能。 而是个人固执地认为:只要理解了IIC通讯协议的时序规则,用普通IO口模拟IIC通讯,也能完成对IIC接口外设的通讯控制。只要这个CPU的IO口具有输入输出切换功能、编程时注意一下延时等待,就没有必要再去研究弄懂此CPU的硬件IIC寄存器定义了。并且,用普通IO口模拟IIC通讯的方法,适用于各种CPU的应用需要,源码的可移植性非常强,几乎是可以“以不变应万变”了。当然,这样的思想是片面的,学习态度是不认真的,是要接受批评的。待以后有空余时间时再慢慢细细阅读STC32G手册有关章节, 再深入学习和理解了……


神农鼎 发表于 2023-11-6 13:31:58


浦江一水 发表于 2023-11-6 15:05:02

非常感谢"神农鼎"管理员的指点,有您的关照和指点,学习STC32G则更有信心和底气了。
也很感叹STC的不断努力和进取,已经注意到了,STC-ISP(V6.92D)软件又更新到V6.92E版本了,速度很快。
新增的“功能脚切换”标签页,很好,很实用。真是想的很周到,让人很方便地查询到哪些引脚具有哪些可能的功能切换,并且立即给出参考代码。
STC-ISP这款天天不离手的软件功能越来越强大了。也期待着“范例程序”页中,能出现STC32G系列的相关例程(猜想有这个计划可能吧?)。

神农鼎 发表于 2023-11-6 15:18:35



后续依靠实验箱例程包和核心功能实验板例程包


页: 1 [2] 3 4 5 6
查看完整版本: 新手学用STC32G12K128, 实践与思考(都是干货,盖楼砖瓦,实用基础)