GT30L32S4W字库芯片调试
测试平台:STM32F103RFT6
库版本:官方标准库3.5.0版本
字库芯片:GT30L32S4W
字库标准:GB2312国标汉字,ASCII字符
点阵排列方式:字节横置横排
字库套数:4套(12x12,16x16,24x24,32x32点阵)
驱动方式:SPI
在使用单片机+LCD屏幕进行显示控制时,文字显示是一个重要的内容,在比较小的屏幕使用中,例如0.96寸OLED的单色黑白屏幕等,通常是使用文字取模软件进行取模,然后存储在程序数组里,再拿去显示,当只显示一些特定的字符或文字时,这是一个不错的方式
但是当需要显示的字符比较多时,这种方法就不太适合了,这时候字库芯片的作用就体现出来了
这是高通的芯片,GT官网,在官网下载到这款芯片的数据手册,但是GT30L32S4W的数据手册的版本号是【V 1.0I_B 】,不知道是处于商业目的还是啥原因,这个版本的数据手册删除了关于编程指导的章节
GT30L32S4W (V1.0I_A)这是在百度文库找到的A版本,A版的第三章和第四章是B版本里缺少的,也是如何使用该芯片的关键内容,可惜下载不到这份数据手册,只能在线看了
注: 文中首次出现的代码块会标注[xxx.c]或[xxx.h],表明该代码是属于对应的文件,未标注的即为重复出现的
1、电路设计
电路设计方面没有啥讲究的,按照【datasheet_B版】里的来就行了,采用SPI通讯,注意不同封装对应的管脚不同就是了,这个SOP8封装
2、程序设计
2.1、SPI驱动
按照常规的SPI配置就行,注意时钟极性CPOL和时钟相位CPHA
【datasheet_B版】第2.2章节
数据在下降沿移出,实际测试中串行时钟稳态设置成高电平或低电平似乎都是可以的,这就有点迷惑了,或许是我的理解有问题
[spi.c]
SPI_InitStructure.SPI_BaudRatePrescaler = speed;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //数据捕获于第一个时钟沿
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //串行时钟稳态
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //每次收发数据大小
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向/双向
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI模式,主/从模式
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)控制
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
由于是双向是通讯,需要一个SPI的读写函数▼
[spi.c]
u8 SPI2_ReadWriteByte(u8 data)
{
u8 temp = 0;
//等待发送数据寄存器清空,temp防止程序卡死
while(!(SPI2->SR & SPI_I2S_FLAG_TXE)){
temp++;
if (temp > 200){return 0xFF;}
}
//发送数据
SPI2->DR = data;
temp = 0;
//等待接收数据寄存器非空,temp防止程序卡死
while(!(SPI2->SR & SPI_I2S_FLAG_RXNE)){
temp++;
if (temp > 200){return 0xFF;}
}
return SPI2->DR;
}
2.1、GT30L32S4W配置
芯片配置只有片选IO口配置+SPI
[GT30L32S4W.c]
/********************************************************************************
* @brief 配置GT30L32S4W的SPI以及片选端口
* @param 无
* @retval 无
*******************************************************************************/
void GT30L32S4W_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GT_CS_1;
//配置SPI2
SPI2_Config(SPI_BaudRatePrescaler_2);
}
2.3、操作指令
从这里开始,编程就需要参考【datasheet_A版】了
第二章【指令操作】里提供了两种数据读取方式
【一般读取】
【快速读取点阵数据】
但是数据手册里的描述很迷惑,【一般读取】读取指定地址的数据后,如果片选CS不拉高,可以继续读取往后的地址上的数据,那么实际上和【快速读取点阵数据】实现是功能是一致的,而且使用【快速读取点阵数据】的话,前期需要发送5byte数据,【一般读取】则只需要发送4byte数据,详见datasheet
所以这里只介绍【一般读取】
看回上边2.2章节的【一般读取】的三个步骤
【1】片选置低 -> 发送0x03命令字+3字节地址(4byte)
【2】读取1byte数据
【3】片选置高,结束读取,片选保存,可以继续读取下一地址数据
由于【1】在各种规格的点阵读取里都会用到,这里单独编写一个发送字符地址函数▼
[GT30L32S4W.c]
/********************************************************************************
* @brief 发送字符地址
* @param Address 地址
* @retval none
*******************************************************************************/
void GenitopZk_Address(u32 Address)
{
u8 AddH, AddM, AddL;
AddH = Address >> 16;
AddM = Address >> 8;
AddL = Address;
SPI2_ReadWriteByte(0x03); //一般读取
SPI2_ReadWriteByte(AddH);
SPI2_ReadWriteByte(AddM);
SPI2_ReadWriteByte(AddL);
// SPI2_ReadWriteByte(0x0B); //快速读取
// SPI2_ReadWriteByte(AddH);
// SPI2_ReadWriteByte(AddM);
// SPI2_ReadWriteByte(AddL);
// SPI2_ReadWriteByte(0xff);
}
2.4、ASCII字符获取
在字库芯片中,每一个文字或数字都存放在一个地址里边,需要找到这一个字符,就需要获取其首地址,通过上边的发送地址函数发送给字库芯片,再读取一定数量的点阵数据
【datasheet_A】里边的第三章给出了每套字库的起始地址▼
通过起始地址加上参考算法算出来的地址,就可以知道所需的字符的首地址
这里先对一些常用的基地址进行一个宏定义▼
[GT30L32S4W.c]
/************** GB2312字库字符基地址 *****************/
#define BaseAdd_12X12 0x00000
#define BaseAdd_16X16 0x2C9D0
#define BaseAdd_24X24 0x68190
#define BaseAdd_32X32 0xEDF00
/************** ASCII字符基地址 *****************/
#define ASCIIAdd_5X7 0x1DDF80
#define ASCIIAdd_7X8 0x1DE280
#define ASCIIAdd_6X12 0x1DBE00
#define ASCIIAdd_8X16 0x1DD780
#define ASCIIAdd_12X24 0x1DFF00
#define ASCIIAdd_16X32 0x1E5A50
在【datasheet_B】的第七章给出了8x16的字母“A”(横置横排)点阵数据▼
7 点阵数据验证(客户参考用)
客户将芯片内“A”的数据调出与以下进行对比。若一致,表示 SPI 驱动正常工作;若不一致,请重 新编写驱动。
排置:Y(竖置横排)点阵大小 8X16 字母"A" 点阵数据:00 E0 9C 82 9C E0 00 00 0F 00 00 00 00 00 0F 00
排置:W(横置横排)点阵大小 8X16 字母"A" 点阵数据:00 10 28 28 28 44 44 7C 82 82 82 82 00 00 00 00
那么先编写一个8x16点阵数据的看看数据是否能对应上▼
[GT30L32S4W.c]
/********************************************************************************
* @brief ASCII_8X16 ASCII字符读取
* @param *ASCIICode ASCII字符码
* @param *BUF 缓存数组
* @retval none
*******************************************************************************/
void ASCII_8X16(char *ASCIICode, u8 *BUF)
{
u8 i;
u32 BaseAdd;
u32 Address;
BaseAdd = ASCIIAdd_8X16;
if(*ASCIICode >= 0x20 && *ASCIICode <= 0x7E){
Address = (*ASCIICode - 0x20) * 16 + BaseAdd;
}
GT_CS_0;
GenitopZk_Address(Address);
for(i = 0; i < 16 ; i++){
BUF[i] = SPI2_ReadWriteByte(0xFF);
// printf("%c", BUF[i]);
}
GT_CS_1;
}
Address就是字符的首地址,该算法是【datasheet_A】里给出的▼
Address = (*ASCIICode - 0x20) * 16 + BaseAdd;
这里的【16】是每两个字符之间的偏置地址
1bit的0/1对应一个像素点的灭/亮,8x16的点阵就需要【(8x16)/8bit = 16byte】的数据大小来存储,对于其他大小的点阵也是同理推算
计算出字符地址后,就可以发送出去,然后读取相应数量(这里是16byte)的数据,使用数组存储起来就可以了
在main函数写个数组传递进去即可检验该函数是否可行▼
[main.c]
u8 BUF[16];
ASCII_8X16("A", BUF);
通过仿真可以查看各个变量的数值▼
"A"实际上是把其对应的ASCII码(0x41)传递进去计算,仿真里可以看见指针【*ASCIICode】指向的值正是【0x41】
而且BUF数组能对应上参考数据▼
排置:W(横置横排)点阵大小 8X16 字母"A" 点阵数据:00 10 28 28 28 44 44 7C 82 82 82 82 00 00 00 00
这就说明这个点阵读取函数是正确有效的,其他大小的ASCII点阵参考其写法就行
这里给一个16x32点阵的写法,可以看看如何改写,其中的【64】是根据上边给的公式算出来的▼
[GT30L32S4W.c]
/********************************************************************************
* @brief ASCII_16X32 ASCII字符读取
* @param *ASCIICode ASCII字符码
* @param *BUF 缓存数组
* @retval none
*******************************************************************************/
void ASCII_16X32(char *ASCIICode, u8 *BUF)
{
u8 i;
u32 BaseAdd;
u32 Address;
BaseAdd = ASCIIAdd_16X32;
if(*ASCIICode >= 0x20 && *ASCIICode <= 0x7E){
Address = (*ASCIICode - 0x20) * 64 + BaseAdd;
}
GT_CS_0;
GenitopZk_Address(Address);
for(i = 0; i < 64 ; i++){
BUF[i] = SPI2_ReadWriteByte(0xFF);
// printf("%c", BUF[i]);
}
GT_CS_1;
}
2.5、GB2312字符获取
ASCII码有数字,字母,英文字符等对应编码号,对于中文,则是由汉字国标码(GB2312)来表示的,同样是把编码(汉字机内码)传递进去,再获取点阵数据即可
由于汉字数量众多,汉字的机器内码不再是1byte,而是2byte,内码高八位和低八位都需要判断
【datasheet_A】里给出12x12的算法如下▼
根据这个算法就可以编写函数▼
[GT30L32S4W.c]
/********************************************************************************
* @brief GB2312_12X12汉字字符读取
* @param *GBCode 汉字内码
* @param *BUF 缓存数组
* @retval none
*******************************************************************************/
void GB2312_12X12(char *GBCode, u8 *BUF)
{
u8 i;
u8 MSB; //汉字内码高八位
u8 LSB; //汉字内码低八位
u32 BaseAdd; //字库基地址
u32 Address; //字符在芯片中的地址
BaseAdd = BaseAdd_12X12;
MSB = *GBCode;
LSB = *(++GBCode);
//计算字符地址值
if(MSB >= 0xA1 && MSB <= 0xA9 && LSB >= 0xA1){
Address = ((MSB - 0xA1) * 94 + (LSB - 0xA1)) * 24 + BaseAdd;
}
else if(MSB >= 0xB0 && MSB <= 0xF7 && LSB >= 0xA1){
Address = ((MSB - 0xB0) * 94 + (LSB - 0xA1) + 846) * 24 + BaseAdd;
}
//获取字符点阵数据
GT_CS_0;
GenitopZk_Address(Address);
for(i = 0; i < 24; i++){
BUF[i] = SPI2_ReadWriteByte(0xff);
// printf("%c", BUF[i]);
}
GT_CS_1;
}
这里最关键的是正确获取MSB和LSB的值,指针的操作要注意
还有就是【24】这个值
如果按照ASCII里边的计算是【(12x12) / 8bit = 18byte】
但这里并不是这样的,为了数据对齐,每两个字符之间的偏置地址是【(12x16) / 8bit = 24byte】
注意: 在ASCII码里也有这种数据对齐的现象
ASCII_5X7,实际地址偏置是【(8x8) / 8bit = 8byte】
ASCII_7X8,实际地址偏置是【(8x8) / 8bit = 8byte】
ASCII_6X12,实际地址偏置是【(6x16) / 8bit = 12byte】
因此在屏幕显示时框选的显示区域就得是对应的比例
算出地址之后的操作和ASCII是一样的
同样,在main函数测试下
[main.c]
u8 BUF[24]
GB2312_12X12("中", BUF);
这里需要了解下如何获取汉字机内码,一些【在线网站】可以输入文字查询机器内码
对于单片机,“中”通过编译器编译,就会编译成机器内码,大小2byte,可以仿真查看▼
在网站里查询的“中”字内码▼
可见内码是正确获取到了,至于BUF[24]里存储的数据是否正确,这里没法对比
可以自行手动对数据进行描点确认,注意点阵是【12x16】(宽度x高度)
其他大小的汉字点阵参考其写法就行,这里给一个32x32点阵的改写
其中的【128】是根据上边给的公式算出来的【(32x32) / 8bit = 128byte】▼
[GT30L32S4W.c]
/********************************************************************************
* @brief GB2312_32X32汉字字符读取
* @param *GBCode 汉字内码
* @param *BUF 缓存数组
* @retval none
*******************************************************************************/
void GB2312_32X32(char *GBCode, u8 *BUF)
{
u8 i;
u8 MSB; //汉字内码高八位
u8 LSB; //汉字内码低八位
u32 BaseAdd; //字库基地址
u32 Address; //字符在芯片中的地址
BaseAdd = BaseAdd_32X32;
MSB = *GBCode;
LSB = *(++GBCode);
//计算字符地址值
if(MSB >= 0xA1 && MSB <= 0xA9 && LSB >= 0xA1){
Address = ((MSB - 0xA1) * 94 + (LSB - 0xA1)) * 128 + BaseAdd;
}
else if(MSB >= 0xB0 && MSB <= 0xF7 && LSB >= 0xA1){
Address = ((MSB - 0xB0) * 94 + (LSB - 0xA1) + 846) * 128 + BaseAdd;
}
//获取字符点阵数据
GT_CS_0;
GenitopZk_Address(Address);
for(i = 0; i < 128; i++){
BUF[i] = SPI2_ReadWriteByte(0xff);
// printf("%c", BUF[i]);
}
GT_CS_1;
}
3、总结
GT30L32S4W字库芯片调试大概就是这些了,关键点是:
【1】芯片内每套字库的基地址
【2】汉字机内码 / ASCII码
【3】每套字库的字符地址算法
【4】字库的点阵数据对齐
对于其他的字库芯片,这四点也是需要注意的
高通官方的数据手册不同版本内容大幅修改的问题不知道在其他型号的字库芯片上会不会是同样的情况,如果也是这样的话就得找找早期的版本了
这里分享一个结合LCD屏幕(240x240彩色)显示汉字的函数和调试照片【关于LCD屏幕见往期文章】
[gui.c]
/********************************************************************************
* @brief LCD显示32x32汉字字符
* @param *string 汉字字符串
* @param (x,y) 显示位置
* @param color_1 字体主体颜色
* @param color_2 字体背景颜色
* @retval none
*******************************************************************************/
void LCD_Chinese_32X32(char *string, u32 x, u32 y, u16 f_color, u16 b_color)
{
u32 i, j;
u8 BUF[128];
while(*string != 0){ //判断MSB
GB2312_32X32(string, BUF); //读取一个字符点阵数据
LCD_SetRegion_Image(x, y, 32, 32); //设定显示区域
LCD_DC_1;
for(i = 0; i < 128; i++){ //分解一帧点阵数据
for(j = 0; j < 8; j++){ //1byte分解成8byte
if(BUF[i] & 0x80){
LCD_WR_Data16(f_color);
}else{
LCD_WR_Data16(b_color);
}
BUF[i] <<= 1;
}
}
x += 32;
string += 2;
}
}
调试结果▼