/************* 功能说明 **************
本例程基于AI8051U为主控芯片的实验箱V1.1版本进行编写测试。
使用Keil C251编译器,Memory Model推荐设置XSmall模式,默认定义变量在edata,单时钟存取访问速度快。
edata建议保留1K给堆栈使用,空间不够时可将大数组、不常用变量加xdata关键字定义到xdata空间。
单色OLED12864显示屏驱动程序,驱动IC为SSD1306,I2C接口,通过I2C将1024字节的图片数据送到屏幕,传送时不占用CPU时间。
显示图形,汉字,英文,数字.
其中图形显示发送命令和图片数据使用I2C操作,传输数据时不占用CPU时间。做GUI最方便了,可以先操作定义于xdata的1024字节缓存,然后触发SPI DMA即可,523us或943us即可自己动刷完。
本例运行于40MHz, SPI速度为主频4分频(10MHz),每次SPI DMA传输总时间943us,SPI速度为主频2分频(20MHz),每次SPI DMA传输总时间523us。
将要显示的内容放在1024字节的显存中,启动DMA传输即可。
下载时, 选择时钟 40MHz (用户可自行修改频率后重新编译即可).
******************************************/
#include "AI8051U.h"
#include "ASCII6x8.h"
#include "HZK16.h"
#include "ASCII-10x24.h"
#include "picture1.h"
#include "picture2.h"
#include "stdio.h"
#include "intrins.h"
//************************************************************************************************
/****************************** 用户定义宏 ***********************************/
#define MAIN_Fosc 40000000UL //定义主时钟
#define Baudrate 115200L
#define TM (65536 -(MAIN_Fosc/Baudrate/4))
#define PrintUart 1 //1:printf 使用 UART1; 2:printf 使用 UART2
#define Timer0_Reload (65536UL -(MAIN_Fosc / 1000)) //Timer 0 中断频率, 1000次/秒
/****************************** IO定义 ***********************************/
/* 定义接口 */
//GND AI8051U实验箱 V1.1
//VCC 3~5V
sbit P_OLED_SCL = P3^2; // II2 的时钟脚
sbit P_OLED_SDA = P3^3; // II2 的数据脚
/*****************************************************************************/
/************* 本地常量声明 **************/
#define SLAW (0x3C<<1)
#define SLAR (SLAW +1)
/************* 本地变量声明 **************/
/************* 本地函数声明 **************/
void UartInit(void);
void LCD_delay_ms(u16 ms) // 1~65535
{
u16 i;
do
{
i = MAIN_Fosc / 6000;
while(--i) ;
}while(--ms);
}
/********************** I2C函数 ************************/
void Wait()
{
while (!(I2CMSST & 0x40));
I2CMSST &= ~0x40;
}
void Start()
{
I2CMSCR = 0x01; //发送START命令
Wait();
}
void SendData(char dat)
{
I2CTXD = dat; //写数据到数据缓冲区
I2CMSCR = 0x02; //发送SEND命令
Wait();
}
void RecvACK()
{
I2CMSCR = 0x03; //发送读ACK命令
Wait();
}
/*
char RecvData()
{
I2CMSCR = 0x04; //发送RECV命令
Wait();
return I2CRXD;
}
void SendACK()
{
I2CMSST = 0x00; //设置ACK信号
I2CMSCR = 0x05; //发送ACK命令
Wait();
}
void SendNAK()
{
I2CMSST = 0x01; //设置NAK信号
I2CMSCR = 0x05; //发送ACK命令
Wait();
}
*/
void Stop()
{
I2CMSCR = 0x06; //发送STOP命令
Wait();
}
//******************************************
void OLED_WriteData(u8 dat) //write display data to LCD
{
Start(); //发送起始命令
SendData(SLAW); //发送设备地址+写命令
RecvACK();
SendData(0x40); //设置D/C为1,为数据模式
RecvACK();
SendData(dat);
RecvACK();
Stop(); //发送停止命令
}
//******************************************
void OLED_WriteCMD(u8 cmd)
{
Start(); //发送起始命令
SendData(SLAW); //发送设备地址+写命令
RecvACK();
SendData(0x00); //设置D/C为0,为指令模式
RecvACK();
SendData(cmd);
RecvACK();
Stop(); //发送停止命令
}
//========================================================================
// 函数: void Set_Dot_Addr_LCD(int x,int y)
// 描述: 设置在LCD的真实坐标系上的X、Y点对应的RAM地址
// 参数: x X轴坐标
// y Y轴坐标
// 返回: 无
// 备注: 仅设置当前操作地址,为后面的连续操作作好准备
// 版本:
// 2007/04/10 First version
//========================================================================
void Set_Dot_Addr(u8 x,u8 y)
{
OLED_WriteCMD((u8)(0xb0 + y)); //设置页0~7
OLED_WriteCMD((x >> 4) | 0x10); //设置列0~127 高nibble
OLED_WriteCMD(x & 0x0f); //设置列0~127 低nibble
}
//******************************************
void FillPage(u8 y,u8 color) //Clear Page LCD RAM
{
u8 j;
Set_Dot_Addr(0,y);
for(j=0; j<128; j++) OLED_WriteData(color);
}
//******************************************
void FillAll(u8 color) //Clear CSn LCD RAM
{
u8 i;
for(i=0; i<8; i++) FillPage(i,color);
}
//******************************************
void Initialize_OLED(void) //initialize OLED
{
LCD_delay_ms(100);
OLED_WriteCMD(0xAE); //Set Display Off
OLED_WriteCMD(0xd5); //display divide ratio/osc. freq. mode
OLED_WriteCMD(0x80); //
OLED_WriteCMD(0xA8); //multiplex ration mode:63
OLED_WriteCMD(0x3F);
OLED_WriteCMD(0xD3); //Set Display Offset
OLED_WriteCMD(0x00);
OLED_WriteCMD(0x40); //Set Display Start Line
OLED_WriteCMD(0x8D); //Set Display Offset
OLED_WriteCMD(0x14);
OLED_WriteCMD(0xA1); //Segment Remap
OLED_WriteCMD(0xC8); //Sst COM Output Scan Direction
OLED_WriteCMD(0xDA); //common pads hardware: alternative
OLED_WriteCMD(0x12);
OLED_WriteCMD(0x81); //contrast control
OLED_WriteCMD(0xCF);
OLED_WriteCMD(0xD9); //set pre-charge period
OLED_WriteCMD(0xF1);
OLED_WriteCMD(0xDB); //VCOM deselect level mode
OLED_WriteCMD(0x40); //set Vvcomh=0.83*Vcc
OLED_WriteCMD(0xA4); //Set Entire Display On/Off
OLED_WriteCMD(0xA6); //Set Normal Display
OLED_WriteCMD(0xAF); //Set Display On
FillAll(0);
}
//******************************************
void WriteAscii6x8(u8 x,u8 y, u8 number)
{
u8 const *p;
u8 i;
if(x > (128-5)) return;
if(y >= 8) return;
p = (u16)number * 6 + ASCII6x8;
Set_Dot_Addr(x,y);
for(i=0; i<6; i++)
{
OLED_WriteData(*p);
p++;
}
}
//=====================================================
void WriteHZ16(u8 x, u8 y, u16 hz) //向指定位置写一个汉字, x为横向的点0~127, y为纵向的页0~7, hz为要写的汉字.
{
u8 const *p;
u8 i;
if(x > (128-16)) return;
if(y > 6) return;
p = hz * 32 + HZK16;
Set_Dot_Addr(x, y);
for(i=0; i<16; i++) { OLED_WriteData(*p); p++;}
Set_Dot_Addr(x, (u8)(y+1));
for(i=0; i<16; i++) { OLED_WriteData(*p); p++;}
}
void printf_ASCII_text(u8 x, u8 y, u8 *ptr) //最快写入10个ASCII码(10*6+9=69个字节)耗时430us@24MHZ
{
u8 c;
for (;;)
{
c = *ptr;
if(c == 0) return; //遇到停止符0结束
if(c < 0x80) //ASCII码
{
WriteAscii6x8(x,y,c);
x += 6;
}
ptr++;
}
}
//******************************************
void WriteAscii_10x24(u8 x, u8 y, u8 chr) //向指定位置写一个ASCII码字符, x为横向的点0~127, y为纵向的页0~7, chr为要写的字符
{
u8 const *p;
u8 i;
if(x > (128-10)) return;
if(y >= 6) return;
p = (u16)chr * 30 + ASCII10x24;
Set_Dot_Addr(x, y);
for(i=0; i<10; i++) { OLED_WriteData(*p); p++; }
Set_Dot_Addr(x, (u8)(y+1));
for(i=0; i<10; i++) { OLED_WriteData(*p); p++; }
Set_Dot_Addr(x, (u8)(y+2));
for(i=0; i<10; i++) { OLED_WriteData(*p); p++; }
}
//******************************************
void WriteDot_3x3(u8 x, u8 y) //向指定位置写一个小数点, x为横向的点0~127, y为纵向的页0~7
{
if(x > (128-3)) return;
if(y >= 8) return;
Set_Dot_Addr(x, y);
OLED_WriteData(0x38);
OLED_WriteData(0x38);
OLED_WriteData(0x38);
}
//************ 打印ASCII 10x24英文字符串 *************************
void printf_ascii_10x24(u8 x, u8 y, u8 const *ptr) //x为横向的点0~127, y为纵向的页0~7, *ptr为要打印的字符串指针, 间隔2点.
{
u8 c;
for (;;)
{
if(x > (128-10)) return;
if(y > 5) return;
c = *ptr;
if(c == 0) return; //遇到停止符0结束
if((c >= '0') && (c <= '9')) //ASCII码
{
WriteAscii_10x24(x,y,(u8)(c-'0'));
x += 12;
}
else if(c == '.')
{
WriteDot_3x3(x,(u8)(y+2));
x += 6;
}
else if(c == ' ') //显示空格
{
WriteAscii_10x24(x,y,11);
x += 12;
}
else if(c == '-') //显示空格
{
WriteAscii_10x24(x,y,10);
x += 12;
}
ptr++;
}
}
//====================================================================================
u8 xdata DisTmp[1024]; //显示缓冲,将要显示的内容放在显存里,启动DMA即可. 由于LCM DMA有4字节对齐问题,所以这里定位对地址为4的倍数
//******************************************
void main(void)
{
u16 i;
EAXFR = 1; //允许访问扩展寄存器
WTST = 0;
CKCON = 0;
P0M1 = 0x00; P0M0 = 0x00; //设置为准双向口
P1M1 = 0x00; P1M0 = 0x00; //设置为准双向口
P2M1 = 0x00; P2M0 = 0x00; //设置为准双向口
P3M1 = 0x00; P3M0 = 0x00; //设置为准双向口
P4M1 = 0x00; P4M0 = 0x00; //设置为准双向口
P5M1 = 0x00; P5M0 = 0x00; //设置为准双向口
P6M1 = 0x00; P6M0 = 0x00; //设置为准双向口
P7M1 = 0x00; P7M0 = 0x00; //设置为准双向口
UartInit();
I2C_S1 =1; //I2C功能脚选择,00:P2.4,P2.3; 01:P1.5,P1.4; 11:P3.2,P3.3
I2C_S0 =1;
I2CCFG = 0xc2; //使能I2C主机模式
I2CPSCR = 0x00; //MSSPEED[13:6]
I2CMSST = 0x00;
EA = 1;
Initialize_OLED();
printf("SSD1306 OLED 128×64 \r\n"); //串口打印测试
while(1)
{
for(i=0; i<1024; i++) DisTmp[i] = 0; //清除显存
printf_ASCII_text(0, 0, " OLED12864 SSD1306");
for(i=0; i<8; i++) WriteHZ16((u8)(i*16),2,i);
printf_ascii_10x24(0,5,"-12.345 678");
LCD_delay_ms(3000);
for(i=0; i<1024; i++) DisTmp[i] = gImage_picture1[i]; //将图片装载到显存
LCD_delay_ms(3000);
for(i=0; i<1024; i++) DisTmp[i] = gImage_picture2[i]; //将图片装载到显存
LCD_delay_ms(3000);
}
}
/******************** 串口打印函数 ********************/
void UartInit(void)
{
#if(PrintUart == 1)
S1_S1 = 0; //UART1 switch to, 0x00: P3.0 P3.1, 0x40: P3.6 P3.7, 0x80: P1.6 P1.7, 0xC0: P4.3 P4.4
S1_S0 = 0;
SCON = (SCON & 0x3f) | 0x40;
T1x12 = 1; //定时器时钟1T模式
S1BRT = 0; //串口1选择定时器1为波特率发生器
TL1 = TM;
TH1 = TM>>8;
TR1 = 1; //定时器1开始计时
// SCON = (SCON & 0x3f) | 0x40;
// T2L = TM;
// T2H = TM>>8;
// AUXR |= 0x15; //串口1选择定时器2为波特率发生器
#else
S2_S = 1; //UART2 switch to: 0: P1.2 P1.3, 1: P4.2 P4.3
S2CFG |= 0x01; //使用串口2时,W1位必需设置为1,否则可能会产生不可预期的错误
S2CON = (S2CON & 0x3f) | 0x40;
T2L = TM;
T2H = TM>>8;
AUXR |= 0x14; //定时器2时钟1T模式,开始计时
#endif
}
void UartPutc(unsigned char dat)
{
#if(PrintUart == 1)
SBUF = dat;
while(TI==0);
TI = 0;
#else
S2BUF = dat;
while(S2TI == 0);
S2TI = 0; //Clear Tx flag
#endif
}
char putchar(char c)
{
UartPutc(c);
return c;
}