目标制作宽度 x 高度 为16 x 16大小的中文字库 和 8 x 16大小英文字库,且在LCD显示。
第一个值小于127的解析为英文字符,否则取连续两个值解析为GB2313.
中文编码格式为GB2312,需要用2个字节表示一个汉字。
全文需要注意的是开发软件的编码格式是否是GB2312,若不是请用其他编码字库,编程思路一致。
由于大家使用的显示屏不同,但是画点的函数一定会有,如LCD_DrawPoint(uint16_t x, uint16_t y, uint32_t color)。
编程的时候使用printf打印debug信息,经常用到ASCII。这真的是再熟悉不过了。
ASCII只用到了指定的7 bit二进制数,最大到0 ~ 127。由于ASCII只能显示英文字母,
但是很多国家的文字并不是由英文字母构成的。故127后面的编码显示其他国家文字了。
GB 2312标准共收录6763个汉字,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率。
对于人名、古汉语等方面出现的罕用字,GB 2312不能处理,这导致了后来GBK及GB 18030汉字字符集的出现。
在使用GB2312的程序中,通常采用EUC储存方法,以便兼容于ASCII。浏览器编码表上的“GB2312”,通常都是指“EUC-CN”表示法。每个汉字及符号以两个字节来表示。第一个字节称为“高位字节”(也称“区字节)”,第二个字节称为“低位字节”(也称“位字节”)。“高位字节”使用了0xA1-0xF7(把01-87区的区号加上0xA0),“低位字节”使用了0xA1-0xFE(把01-94加上 0xA0)。 由于一级汉字从16区起始,汉字区的“高位字节”的范围是0xB0-0xF7,“低位字节”的范围是0xA1-0xFE,占用的码位是 72*94=6768。其中有5个空位是D7FA-D7FE。
例如“啊”字在大多数程序中,会以两个字节,0xB0(第一个字节) 0xA1(第二个字节)储存。区位码=区字节+位字节(与区位码对比:0xB0=0xA0+16,0xA1=0xA0+1)。
6763个汉字一般够我们嵌入式系统使用了, GB2312编码范围是0xA1A1 ~ 0xFEFE。
制作字库的工具很多,例如:PCtoLCD2002.exe
下图是设置生成字体数据的规则。
生成16 x 16大小的字库。
阴码:用1表示LCD该像素点点亮。
顺向:表示高字节在前。
逐行式:逐行刷写。
生成字库保存完成后,文件尾缀是.fon文件。把尾缀改成.bin,如:GB2312.bin,文件大小是255 KB。
使用ST烧写工具,打开GB2312.bin。把GB2312.bin烧录进芯片内部flash。
我用的是F407ZG有1MB空间,烧录在最后两个分区,即起始地址是:0x80C0000
现在要把字库里面的数据读取出来,看看是否数据摆放位置和读取是否正确。
现在我要读取一个GB2312 '好' 字的数据出来,编码是BAC3.
https://www.qqxiuzi.cn/bianma/guobiaoma.php
对应的十六进制数据是:
{0x10,0x00,0x10,0xFC,0x10,0x04,0x10,0x08,0xFC,0x10,0x24,0x20,0x24,0x20,0x25,0xFE},
{0x24,0x20,0x48,0x20,0x28,0x20,0x10,0x20,0x28,0x20,0x44,0x20,0x84,0xA0,0x00,0x40},/*"好",0*/
首先知道一个16 x 16汉字占 多少个字节: size = 16 * 16 / 8 = 32byte
现在有了GB2312字符编码,用 下面公式即可定位该字符在字库位置。
Address = (((CodeH-0xA0-1)*94) +(CodeL-0xA0-1)) * size ;
测试代码:虽然拿到了字符在字库的偏移,但是不要忘了字库在flash的偏移是0x80C0000。
void GB2312_FontDataRead(uint16_t code)
{
uint32_t i = 0;
uint8_t CodeL = code & 0xFF;
uint8_t CodeH = (code >> 8) & 0xFF;
uint32_t Addr = (((CodeH - 0xA0 - 1) * 94) + (CodeL - 0xA0 - 1)) * 32;
Addr = Addr + 0x80C0000;
for(i = 0; i < 32; i++)
{
printf("0x%02X ", *(uint8_t *)(Addr + i));
}
printf("\r\n");
}
执行结果:
数据一致,好评。
既然拿到的数据也是正确的,那么就在拿到数据判读哪位bit位1,即画点。
由于大家使用的显示屏不同,但是画点的函数一定会有,如LCD_DrawPoint(uint16_t x, uint16_t y, uint32_t color)。
void LCD_ShowGB2312_16X16(uint16_t x, uint16_t y, uint16_t color, uint16_t code)
{
uint8_t CodeL = code & 0xFF;
uint8_t CodeH = (code >> 8) & 0xFF;
uint8_t i = 0, j = 0, z = 0, temp = 0;
uint32_t Addr = 0;
if(code < 0xA1A1 || code > 0xFEFE) return;
Addr = (((CodeH - 0xA0 - 1) * 94) + (CodeL - 0xA0 - 1)) * 16 * 16 / 8;
Addr += 0x80C0000;
for(i = 0; i < 16; i++)
{
for(j = 0; j < 2; j++)
{
temp = *(uint8_t*)(Addr + (i*2)+j);
for(z = 0; z < 8; z++)
{
if(temp & (0x80 >> z))
LCD_Fast_DrawPoint(x + (j * 8) + z, y, color);
}
}
y++;
}
}
现象:
注意:由于英文在16x16矩阵中实际是只用了8 x 16,故计算字符偏移: size = 8 * 16 / 8 = 16byte
后面步骤一样,改名bin,烧录到flash地址0x80A0000,然后读取数据对比。
显示代码:
void LCD_ShowASCII_8x16(uint16_t x, uint16_t y, uint16_t color, uint8_t ch)
{
uint32_t Addr = 0x80A0000;
uint8_t i = 0, j = 0, temp = 0;
if(ch > 127) return;
Addr += ch * 16;
printf("ch = %X, a = %lX\r\n", ch, Addr);
for(i = 0; i < 16; i++)
{
temp = *(uint8_t*)(Addr + i);
for(j = 0; j < 8; j++)
{
if(temp & (0x80 >> j))
LCD_Fast_DrawPoint(x + j, y, color);
}
y++;
}
}
显示英文字符' S ',效果:
显示中英混合字符串函数:
ASCII码是用1个字节表示,而GB2312是用2个字节表示。
但是ASCII最大是127,故凡是大于127的编码都按照GB2312解析,取两个字节。
void LCD_ShowString(uint16_t x, uint16_t y, uint16_t color, uint8_t* pData)
{
uint16_t temp = 0;
uint16_t len = strlen((char*)pData);
while(len > 0)
{
temp = *pData;
if(temp <= 127) // ASCII
{
LCD_ShowASCII_8x16(x, y, color, temp);
x += 8;
len -= 1;
pData += 1;
}
else // GB2312
{
if(len < 2) return;
temp = ((*pData) << 8) + (*(pData + 1));
LCD_ShowGB2312_16X16(x, y, color, temp);
x += 16;
pData += 2;
len -= 2;
}
}
}
支持类库函数printf打印输出在屏幕上
void LCD_Printf(uint16_t x, uint16_t y, uint16_t color, const char *format, ...)
{
uint16_t length = 0;
va_list args;
uint8_t buf[100] = {0};
va_start(args, format);
length = vsnprintf((char *)buf, sizeof(buf), (char *)format, args);
va_end(args);
if(length == 0) return;
LCD_ShowString(x, y, color, buf);
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_FSMC_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("Sudaroot\r\n");
LCD_Init();
GB2312_FontDataRead(0xBAC3);
LCD_ShowGB2312_16X16(100, 100, RED, 0xBAC3);
LCD_ShowASCII_8x16(100, 200, BLUE, 'S');
LCD_ShowString(100, 200, RED, (uint8_t*)"你好吗?Sudaroot");
LCD_Printf(100, 300, RED, "%s = %d分", "Sudaroot", 59);
/* USER CODE END 2 */
while (1)
{
}
}
效果:
全篇完。
本人是一个嵌入式未入门小白,博客仅仅代表我个人主观见解方便记录成长笔记。 若有与大神大大见解有冲突,我坚信大神大大见解是对的,我的是错的。 若无法下载源码,可私聊私发。 感谢~!