最近有个项目需要用STM32F103C8做个显示板,和主板通过串口通讯,同时完成按键扫描、OLED显示和电灯功能。同时要求带GB2312字库。于是开始设计硬件,原理图就不贴了,总之就是:SP1口连接OLED和FLASH,其中OLED的控制器是SSD1305,FLASH型号是W25Q16。其他点灯啊、按键啊之类的就不说了,主要说说SSD1305的驱动。
首先是初始化系统,我使用了STM32CubeMX,设置如下图:
时钟配置如下图:
重点是SPI口配置如下:
速度配置为9M是因为SSD1305的SPI模式要求时钟周期不小于250ns,也就是频率不大于4M,但经过测试9M的速度也是没问题的。由于和W25Q16共用SPI口,为了减少读取SPI时间因此采用了高速方式。
先来基础的宏定义、写命令和写数据函数(我由于用了STM32CubeMX用的HAL库):
#define OLED_CS_E HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET)//使能OLED
然后是显示区域设置函数:
/*******************************************
工作窗口设置;
a,b 列窗口始末; (点为单位) c,d 页窗口始末;
(8 点为单位)
*******************************************/
void SET_AW(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
wr_com(0x21);
wr_com(a + 4);
wr_com(b + 4);
wr_com(0x22);
wr_com(c);
wr_com(d);
}
然后是清屏函数:
//132*64 整个 RAM 区清屏;
void all_screen(uint8_t b) {
uint8_t i, j;
wr_com(0x21);
wr_com(0);
wr_com(131);
wr_com(0x22);
wr_com(0);
wr_com(7);
for (j = 0; j < 8; j++)
for (i = 0; i < 132; i++)
wr_data(b);
}
//128*64 显示界面的操作,b=0清屏;b=55H 隔行;b=FFH 全屏;
void fill(uint8_t b) {
uint8_t i, j;
SET_AW(0, 127, 0, 3);
for (j = 0; j < 4; j++)
for (i = 0; i < 128; i++)
wr_data(b);
}
本次使用的是清达光电的HGS128322 模块,其初始化方式如下,各位可根据自己的模块调节初始化时候命令内容,具体内容查看附件的SSD1306数据手册(带中文注释)
/*******************************************
初始化函数;
*******************************************/
void spioled_init(void) {
HAL_Delay(50);
Spi_Oled_rest();
// 设置列首地址低半字节;
wr_com(0x00);
// 设置列地址的高半字节;
wr_com(0x10);
// 设置存储区的地址模式;
wr_com(0x20);
wr_com(0x00); /* 选水平地址模式 */
// 设置列窗口;
wr_com(0x21);
wr_com(0x00);
wr_com(0x7f); /* 128 宽度的窗口 */
// 设置页窗口;
wr_com(0x22);
wr_com(0x00);
wr_com(0x07); /* 8 页的窗口 */
// 设置显示起始行;
wr_com(0x40); /* 0*/
// 设置 bank0 对比度;
wr_com(0x81);
wr_com(0x80); /* 共 256 级 */
// 设置彩色亮度;
wr_com(0x82);
wr_com(0x80); /* 共 256 级 */
// 设置 SEG 可逆;
wr_com(0xA1); /* 改变 SEG 与地址
的对应关系 */
// 设置全显;
wr_com(0xA4); /* 不全显 */
// 设置反显;
wr_com(0xA6); /* 不反显 */
// 设置显示行;
wr_com(0xA8);
wr_com(0x1f); //32行
/* 64 行, 改变的是可显示的 row*/
// Vcc 来源;
wr_com(0xAD);
wr_com(0x8E); /* 8E=外部 */
// 设置页首地址;
wr_com(0xB0); /* 第 0 页 */
// 设置 COM 扫描方向;
wr_com(0xC8); /* COM 反向 */
// 设置显示分支;
wr_com(0xD3);
wr_com(0x00); /* 0 */
// 设置 D 和 Fosc;
wr_com(0xD5);
wr_com(0xD1); /* F=D D=2 */
// 彩色与省电模式选择;
wr_com(0xD8);
wr_com(0); /* 48 为彩色, 5 为省电 */
// 设置 P1 P2;
wr_com(0xD9);
wr_com(0x22); /* P1=2 P2=2 */
//设置 COM 的硬件接法;
wr_com(0xDA);
wr_com(0x12);
//设置 Vcomh;
wr_com(0xDB);
wr_com(0x00);
all_screen(0x00);
// 显示开关;
wr_com(0xAF);
/* AF=ON, AE=Sleep Mode, AC=Dim */
fill(0xff); //清屏准备显示
}
开头加了个延时是为了系统稳定,去掉也没影响。
重点来了,由于这次的模组采用从上到下,从左到右,低字节在前高字节在后方式描点,因此字库取模需要设置为这个方式(软件分别是hzdotreaderv3、PCtoLCD2002和ts3):
这是不同软件的设置方式,可是我一点一点试出来的啊,厂家你给我出来,为什么不写清楚啊(好吧,英文我实在看不懂的过)
然后,
重点来了!从W25Q16里面取字模然后显示!
先来看看字库是如何存储的,之前用hzdotreaderv3生成的16*16字库用UG打开看看
嗯……………………看不出来,再生个带注释的看看吧
通过对比可以看到,纯二进制文件就是按照GB2312字符内码顺序从A1A1开始存储的,每个16*16的字符占32个字节。OK这就好说了,下面就是从W25Q16里面取字模的程序:
void show_HZ_16(uint8_t x, uint8_t y,uint8_t *HZ_word)
{
uint32_t HZ_addr;
uint8_t HZ_code;
uint8_t HZ_data;
HZ_code=*HZ_word; //内码高8位
HZ_code=*(HZ_word+1);//内码第8位
HZ_addr=(HZ_code-0xA1)*94+(HZ_code-0xa1);
W25QXX_Read(HZ_data,HZ_addr*32,32);//从FLASH中读取字模,读取第HZ_addr个汉字的字模,读取32个字节存储到HZ_data.
word_1616_1(x,y,HZ_data,0);
}
字符串显示函数考多次调用上面这个函数实现:
/*显示字符串自动换行
*参数:x起始x坐标;y起始y行数,*ASC_str字符串首地址指针,len长度
*注意:1、x值为像素位置,y值为行号,每行8个像素
*注意:2、每个汉字长度为2
*/
void show_str_16(uint8_t x, uint8_t y,uint8_t *ASC_str,uint16_t len)
{
uint16_t i=0;
uint8_t shouw_y=y;
uint8_t shouw_x=x;
for(i=0;i=128)
{
shouw_y=y+2;
shouw_x=shouw_x-128;
}
if(*(ASC_str+i)==0x20) //如果是空格
{
//不处理
}
else if(*(ASC_str+i)<0x7f) //如果是ASCII字符
{
show_ASC_16(shouw_x,shouw_y,(ASC_str+i));
}
else if(*(ASC_str+i)>0xa0) //如果是GB2312字符
{
show_HZ_16(shouw_x,shouw_y,(ASC_str+i));
i++;
}
}
}
这里面我判断的如果说ASCII字符就调用汉字显示部分了,毕竟8*16就能显示一个ASCII字符,占用一个16*16位置太浪费了,ASCII字符显示函数如下
void show_ASC_16(uint8_t x, uint8_t y,uint8_t *ASC_word)
{
uint16_t i;
uint8_t ASC_addr;
uint8_t ASC_data;
ASC_addr=*ASC_word-0x21;
for(i=0;i<16;i++)
ASC_data=ASC_16;
word_1616_2(x,y,ASC_data);
}
这里的ASC_16数组是我自己生成的ASCII字符字模,存在片上FLASH里,比较STM32F103C8有64kB的片上FLASH呢,不用白不用。
最后测试程序:
void spi_oled_test()
{
static uint8_t zf_show_data={0xa1,0xa1};
uint8_t i;
if(Oled_Delay_time!=0)return;
uint8_t show_data="显示任意字符";
fill(0x00);
show_str_16(0,0,show_data,sizeof(show_data)-1);
for(i=0;i<8;i++)
{
show_str_16(i*16,2,zf_show_data,2);
zf_show_data++;
if(zf_show_data>=0xff)
{
zf_show_data++;
zf_show_data=0xa1;
if(zf_show_data>0xf7)
{
zf_show_data=0xa1;
zf_show_data=0xa1;
}
}
}
HAL_Delay(1000);
}
效果如下是第一行显示:显示任意字符,第二行则把字库中所有字符轮流显示,每次8个,可以看到GB2312包含常用的全角符号、英文字母、希腊字母和汉字(据说是六千多个)。
最近有个项目需要用STM32F103C8做个显示板,和主板通过串口通讯,同时完成按键扫描、OLED显示和电灯功能。同时要求带GB2312字库。于是开始设计硬件,原理图就不贴了,总之就是:SP1口连接OLED和FLASH,其中OLED的控制器是SSD1305,FLASH型号是W25Q16。其他点灯啊、按键啊之类的就不说了,主要说说SSD1305的驱动。
首先是初始化系统,我使用了STM32CubeMX,设置如下图:
时钟配置如下图:
重点是SPI口配置如下:
速度配置为9M是因为SSD1305的SPI模式要求时钟周期不小于250ns,也就是频率不大于4M,但经过测试9M的速度也是没问题的。由于和W25Q16共用SPI口,为了减少读取SPI时间因此采用了高速方式。
先来基础的宏定义、写命令和写数据函数(我由于用了STM32CubeMX用的HAL库):
#define OLED_CS_E HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET)//使能OLED
然后是显示区域设置函数:
/*******************************************
工作窗口设置;
a,b 列窗口始末; (点为单位) c,d 页窗口始末;
(8 点为单位)
*******************************************/
void SET_AW(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
wr_com(0x21);
wr_com(a + 4);
wr_com(b + 4);
wr_com(0x22);
wr_com(c);
wr_com(d);
}
然后是清屏函数:
//132*64 整个 RAM 区清屏;
void all_screen(uint8_t b) {
uint8_t i, j;
wr_com(0x21);
wr_com(0);
wr_com(131);
wr_com(0x22);
wr_com(0);
wr_com(7);
for (j = 0; j < 8; j++)
for (i = 0; i < 132; i++)
wr_data(b);
}
//128*64 显示界面的操作,b=0清屏;b=55H 隔行;b=FFH 全屏;
void fill(uint8_t b) {
uint8_t i, j;
SET_AW(0, 127, 0, 3);
for (j = 0; j < 4; j++)
for (i = 0; i < 128; i++)
wr_data(b);
}
本次使用的是清达光电的HGS128322 模块,其初始化方式如下,各位可根据自己的模块调节初始化时候命令内容,具体内容查看附件的SSD1306数据手册(带中文注释)
/*******************************************
初始化函数;
*******************************************/
void spioled_init(void) {
HAL_Delay(50);
Spi_Oled_rest();
// 设置列首地址低半字节;
wr_com(0x00);
// 设置列地址的高半字节;
wr_com(0x10);
// 设置存储区的地址模式;
wr_com(0x20);
wr_com(0x00); /* 选水平地址模式 */
// 设置列窗口;
wr_com(0x21);
wr_com(0x00);
wr_com(0x7f); /* 128 宽度的窗口 */
// 设置页窗口;
wr_com(0x22);
wr_com(0x00);
wr_com(0x07); /* 8 页的窗口 */
// 设置显示起始行;
wr_com(0x40); /* 0*/
// 设置 bank0 对比度;
wr_com(0x81);
wr_com(0x80); /* 共 256 级 */
// 设置彩色亮度;
wr_com(0x82);
wr_com(0x80); /* 共 256 级 */
// 设置 SEG 可逆;
wr_com(0xA1); /* 改变 SEG 与地址
的对应关系 */
// 设置全显;
wr_com(0xA4); /* 不全显 */
// 设置反显;
wr_com(0xA6); /* 不反显 */
// 设置显示行;
wr_com(0xA8);
wr_com(0x1f); //32行
/* 64 行, 改变的是可显示的 row*/
// Vcc 来源;
wr_com(0xAD);
wr_com(0x8E); /* 8E=外部 */
// 设置页首地址;
wr_com(0xB0); /* 第 0 页 */
// 设置 COM 扫描方向;
wr_com(0xC8); /* COM 反向 */
// 设置显示分支;
wr_com(0xD3);
wr_com(0x00); /* 0 */
// 设置 D 和 Fosc;
wr_com(0xD5);
wr_com(0xD1); /* F=D D=2 */
// 彩色与省电模式选择;
wr_com(0xD8);
wr_com(0); /* 48 为彩色, 5 为省电 */
// 设置 P1 P2;
wr_com(0xD9);
wr_com(0x22); /* P1=2 P2=2 */
//设置 COM 的硬件接法;
wr_com(0xDA);
wr_com(0x12);
//设置 Vcomh;
wr_com(0xDB);
wr_com(0x00);
all_screen(0x00);
// 显示开关;
wr_com(0xAF);
/* AF=ON, AE=Sleep Mode, AC=Dim */
fill(0xff); //清屏准备显示
}
开头加了个延时是为了系统稳定,去掉也没影响。
重点来了,由于这次的模组采用从上到下,从左到右,低字节在前高字节在后方式描点,因此字库取模需要设置为这个方式(软件分别是hzdotreaderv3、PCtoLCD2002和ts3):
这是不同软件的设置方式,可是我一点一点试出来的啊,厂家你给我出来,为什么不写清楚啊(好吧,英文我实在看不懂的过)
然后,
重点来了!从W25Q16里面取字模然后显示!
先来看看字库是如何存储的,之前用hzdotreaderv3生成的16*16字库用UG打开看看
嗯……………………看不出来,再生个带注释的看看吧
通过对比可以看到,纯二进制文件就是按照GB2312字符内码顺序从A1A1开始存储的,每个16*16的字符占32个字节。OK这就好说了,下面就是从W25Q16里面取字模的程序:
void show_HZ_16(uint8_t x, uint8_t y,uint8_t *HZ_word)
{
uint32_t HZ_addr;
uint8_t HZ_code;
uint8_t HZ_data;
HZ_code=*HZ_word; //内码高8位
HZ_code=*(HZ_word+1);//内码第8位
HZ_addr=(HZ_code-0xA1)*94+(HZ_code-0xa1);
W25QXX_Read(HZ_data,HZ_addr*32,32);//从FLASH中读取字模,读取第HZ_addr个汉字的字模,读取32个字节存储到HZ_data.
word_1616_1(x,y,HZ_data,0);
}
字符串显示函数考多次调用上面这个函数实现:
/*显示字符串自动换行
*参数:x起始x坐标;y起始y行数,*ASC_str字符串首地址指针,len长度
*注意:1、x值为像素位置,y值为行号,每行8个像素
*注意:2、每个汉字长度为2
*/
void show_str_16(uint8_t x, uint8_t y,uint8_t *ASC_str,uint16_t len)
{
uint16_t i=0;
uint8_t shouw_y=y;
uint8_t shouw_x=x;
for(i=0;i=128)
{
shouw_y=y+2;
shouw_x=shouw_x-128;
}
if(*(ASC_str+i)==0x20) //如果是空格
{
//不处理
}
else if(*(ASC_str+i)<0x7f) //如果是ASCII字符
{
show_ASC_16(shouw_x,shouw_y,(ASC_str+i));
}
else if(*(ASC_str+i)>0xa0) //如果是GB2312字符
{
show_HZ_16(shouw_x,shouw_y,(ASC_str+i));
i++;
}
}
}
这里面我判断的如果说ASCII字符就调用汉字显示部分了,毕竟8*16就能显示一个ASCII字符,占用一个16*16位置太浪费了,ASCII字符显示函数如下
void show_ASC_16(uint8_t x, uint8_t y,uint8_t *ASC_word)
{
uint16_t i;
uint8_t ASC_addr;
uint8_t ASC_data;
ASC_addr=*ASC_word-0x21;
for(i=0;i<16;i++)
ASC_data=ASC_16;
word_1616_2(x,y,ASC_data);
}
这里的ASC_16数组是我自己生成的ASCII字符字模,存在片上FLASH里,比较STM32F103C8有64kB的片上FLASH呢,不用白不用。
最后测试程序:
void spi_oled_test()
{
static uint8_t zf_show_data={0xa1,0xa1};
uint8_t i;
if(Oled_Delay_time!=0)return;
uint8_t show_data="显示任意字符";
fill(0x00);
show_str_16(0,0,show_data,sizeof(show_data)-1);
for(i=0;i<8;i++)
{
show_str_16(i*16,2,zf_show_data,2);
zf_show_data++;
if(zf_show_data>=0xff)
{
zf_show_data++;
zf_show_data=0xa1;
if(zf_show_data>0xf7)
{
zf_show_data=0xa1;
zf_show_data=0xa1;
}
}
}
HAL_Delay(1000);
}
效果如下是第一行显示:显示任意字符,第二行则把字库中所有字符轮流显示,每次8个,可以看到GB2312包含常用的全角符号、英文字母、希腊字母和汉字(据说是六千多个)。