段码LCD研究 | HT1621B/STC8H4K64TLCD驱动 | IO直驱
【本文发布地址https://blog.csdn.net/Stack_/article/details/117532877,未经许可不得转载,转载须注明出处】
一、探索
这是一个从乐心血压计上拆下来的屏幕,有40个引脚,其中1和40、2和39、3和38、4和37在PCB上是连起来的,所以实际上是36个引脚。
在其引脚上加电压,得到的不是数码管的效果。

查询了一番之后方得知这种屏是段码屏,引脚上有小黑点的是COM口,其它是SEG口。看这块屏上有5个脚上有黑点,但实测其中一个有黑点的是SEG脚。
所以基本可以确定这块屏是4COM * 32SEG的屏。
用数码管的思维驱动是没法驱动的,因为在任意两点加电压都会同时亮起至少4处。
网上都说MCU驱动可以是可以,但会很复杂,而且效果不好,会有类似数码管的鬼影出现。(文章章节五已更新MCU IO直驱)
而且网上对控制原理的描述太少,不知道如何使其只亮起某一段,所以选择使用专用的驱动芯片 — HT1621B, tb上才9毛钱一块,因为是当玩具来玩,怎么简单怎么来,何必为难自己。
二、电路设计


三、焊接调试



四、程序(附件提供)


写命令时,数据格式为ID + 命令(b100 + 9bit命令。命令最低位任意);
写数据时,数据格式为ID + 地址 + 数据(b101 + 6bit地址 + 4bit数据)。在这里要注意地址为高位在前,数据为低位在前。
底层驱动
@ CSDN Tyrion.Mon
/*
所有字节只有低四位有效
array_RAM[0] :
bit3-bit0: SEG0 - D3:D0
array_RAM[1] :
bit3-bit0: SEG1 - D3:D0
array_RAM[2] :
bit3-bit0: SEG2 - D3:D0
.......
*/
uint8_t array_RAM[32] =
{
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
/**
* @brief 将N个bit命令发送给HT1621B
* @note
* @param
* @retval None
* @author PWH @ CSDN Tyrion.Mon
* @date 2021/6
*/
static void ht1621b_write_command_bits(uint8_t command, uint8_t num_of_bits)
{
int8_t i;
for (i = 0; i < num_of_bits; i++)
{
GPIO_WriteLow(HT1621B_WR_GPIO, HT1621B_WR_PIN);
if (command & 0x80)
{
GPIO_WriteHigh(HT1621B_DATA_GPIO, HT1621B_DATA_PIN);
}
else
{
GPIO_WriteLow(HT1621B_DATA_GPIO, HT1621B_DATA_PIN);
}
GPIO_WriteHigh(HT1621B_WR_GPIO, HT1621B_WR_PIN);
command <<= 1;
}
}
/**
* @brief 将N个bit数据发送给HT1621B
* @note
* @param
* @retval None
* @author PWH @ CSDN Tyrion.Mon
* @date 2021/6
*/
static void ht1621b_write_data_bits(uint8_t data, uint8_t num_of_bits)
{
int8_t i;
for (i = 0; i < num_of_bits; i++)
{
GPIO_WriteLow(HT1621B_WR_GPIO, HT1621B_WR_PIN);
if (data & 0x01)
{
GPIO_WriteHigh(HT1621B_DATA_GPIO, HT1621B_DATA_PIN);
}
else
{
GPIO_WriteLow(HT1621B_DATA_GPIO, HT1621B_DATA_PIN);
}
GPIO_WriteHigh(HT1621B_WR_GPIO, HT1621B_WR_PIN);
data >>= 1;
}
}
/**
* @brief 向HT1621B发送一个命令
* @note
* @param
* @retval None
* @author PWH @ CSDN Tyrion.Mon
* @date 2021/6
*/
static void ht1621b_write_one_command(uint8_t command)
{
GPIO_WriteLow(HT1621B_CS_GPIO, HT1621B_CS_PIN);
ht1621b_write_command_bits(0x80, 3); //发送标识码 100
ht1621b_write_command_bits(command, 9); //发送9位命令,前8位为命令,最后1位任意
GPIO_WriteHigh(HT1621B_CS_GPIO, HT1621B_CS_PIN);
}
/**
* @brief 将一个数据写入HT1621B某一个地址
* @note
* @param
* @retval None
* @author PWH @ CSDN Tyrion.Mon
* @date 2021/6
*/
static void ht1621b_write_one_addr_data(uint8_t address, uint8_t data)
{
GPIO_WriteLow(HT1621B_CS_GPIO, HT1621B_CS_PIN);
ht1621b_write_command_bits(0xA0, 3); //发送标识码 101
ht1621b_write_command_bits(address << 2, 6); //发送6位地址
ht1621b_write_data_bits(data, 4);
GPIO_WriteHigh(HT1621B_CS_GPIO, HT1621B_CS_PIN);
}
/**
* @brief 连续写入多个数据到HT1621B
* @note
* @param
* @retval None
* @author PWH @ CSDN Tyrion.Mon
* @date 2021/6
*/
static void ht1621b_write_N_addr_data(uint8_t address, uint8_t *array, uint8_t len)
{
uint8_t i;
GPIO_WriteLow(HT1621B_CS_GPIO, HT1621B_CS_PIN);
ht1621b_write_command_bits(0xA0, 3); //发送标识码 101
ht1621b_write_command_bits(address << 2, 6); //发送6位地址
for (i = 0; i < len; i++)
{
ht1621b_write_data_bits(*array++, 4);
}
GPIO_WriteHigh(HT1621B_CS_GPIO, HT1621B_CS_PIN);
}
/**
* @brief
* @note
* @param
* @retval None
* @author PWH @ CSDN Tyrion.Mon
* @date 2021/6
*/
void ht1621b_init(void)
{
GPIO_Init(HT1621B_DATA_GPIO, HT1621B_DATA_PIN, GPIO_MODE_OUT_PP_HIGH_FAST);
GPIO_Init(HT1621B_WR_GPIO, HT1621B_WR_PIN, GPIO_MODE_OUT_PP_HIGH_FAST);
GPIO_Init(HT1621B_CS_GPIO, HT1621B_CS_PIN, GPIO_MODE_OUT_PP_HIGH_FAST);
GPIO_Init(HT1621B_RD_GPIO, HT1621B_RD_PIN, GPIO_MODE_IN_PU_NO_IT);
ht1621b_write_one_command(0x29); //1/3 bias,4commons
ht1621b_write_one_command(0x01); //SYS EN
ht1621b_write_one_command(0x03); //LCD ON
}
/**
* @brief 将缓冲区的数据写入HT1621B,在while中不断循环
* @note
* @param
* @retval None
* @author PWH @ CSDN Tyrion.Mon
* @date 2021/6
*/
void ht1621b_scan(void)
{
ht1621b_write_N_addr_data(0, array_RAM, 32);
}
因为不知道屏引脚对应的段,所以需要用程序测出对应关系(附件有测试demo),得

/*
bit7 - bit0: f g e d a b c x (x为任意,这里强制为0)
*/
const uint8_t array_num[11] =
{
// 0 1 2 3 4 5 6 7 8 9 NULL
0xbe, 0x06, 0x7c, 0x5e, 0xc6, 0xda, 0xfa, 0x0e, 0xfe, 0xde, 0x00
};
const uint8_t array_char[2] =
{
// A P
0xee, 0xec
};
驱动程序
/**
* @brief 6个大数字
* @note
* @param
* @retval None
* @author PWH @ CSDN Tyrion.Mon
* @date 2021/6
*/
void ht1621b_display_big_8(uint8_t which, uint8_t num)
{
if (num > 10) return;
switch(which)
{
case big_num_1:
array_RAM[6] = array_num[num] >> 4; //只有低四位有效,数字的f g e d
array_RAM[5] &= 0x01; //清零a b c 的位置
array_RAM[5] |= array_num[num]; //数字的a b c
break;
case big_num_2:
array_RAM[4] = array_num[num] >> 4;
array_RAM[3] &= 0x01;
array_RAM[3] |= array_num[num];
break;
case big_num_3:
array_RAM[2] = array_num[num] >> 4;
array_RAM[1] &= 0x01;
array_RAM[1] |= array_num[num];
break;
case big_num_4:
array_RAM[0] = array_num[num] >> 4;
array_RAM[8] &= 0x01;
array_RAM[8] |= array_num[num];
break;
case big_num_5:
array_RAM[9] = array_num[num] >> 4;
array_RAM[10] &= 0x01;
array_RAM[10] |= array_num[num];
break;
case big_num_6:
array_RAM[11] = array_num[num] >> 4;
array_RAM[12] &= 0x01;
array_RAM[12] |= array_num[num];
break;
}
}
/**
* @brief 蓝牙
* @note
* @param
* @retval None
* @author PWH @ CSDN Tyrion.Mon
* @date 2021/6
*/
void ht1621b_display_bluetooth(uint8_t sta)
{
array_RAM[27] &= ~0x01;
if (sta)
{
array_RAM[27] |= 0x01;
}
}
附、测试代码
该附件代码还包含另一款段码屏的驱动(分别使用STM8+HT1621B驱动 、 STC8H4K64TLCD内置驱动器)。
同时提供了测试出段码屏真值表的方法





五、单片机IO直接驱动
根据【参考设计】
得到如下电路和程序,由于该屏是1/3BIAS的,电路却是1/2BIAS的,所以显示效果会略差(有残影+对比度稍低),对于显示要求不高、成本优先的产品来说够用了,任意MCU均可,不依赖LCD驱动芯片或LCD驱动器,但是占用IO数量多。因为是DIY研究非产品用途,暂不继续优化了。
建议IO直驱优先选用3.3V 1/2BIAS的屏。

const uint8_t num_code[13] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x40, 0x79, 0x00}; //0-9,- ,E, ' '(A在最低位)
//const uint8_t num_code[13] = {0xfc, 0x60, 0xda, 0xf2, 0x66, 0xb6, 0xbe, 0xe0, 0xfe, 0xf6, 0x02, 0x9E, 0x00}; //0-9,-,E, ' '(A在最高位)
uint8_t flush[4] = {0};
void LCD8888_Display(int16_t num, bool SSD, bool HSD)
{//需要更改显示内容时调用
uint8_t i = 0;
for (i = 0; i < sizeof(flush); i++)
flush[i] = 0x00;
if (num > 9999 || num < (-999)) //超出显示范围
{
//错误码
flush[1] = num_code[11]; //E
flush[2] = num_code[1]; //1
return;
}
if (num < 0)
{
num *= (-1);
if (num > 99) flush[0] = num_code[10];
else if (num > 9) flush[1] = num_code[10];
else flush[2] = num_code[10];
}
flush[3] = num_code[num % 10];
if (num > 9) flush[2] = num_code[num / 10 % 10] ;
if (num > 99) flush[1] = num_code[num / 100 % 10] ;
if (num > 999) flush[0] = num_code[num / 1000 % 10] ;
}
void LCD8888_LOOP(void)
{ //定时器1ms中断调用
static int8_t step = -1;
static uint8_t fflush[4] = {0};
uint8_t i = 0;
static uint8_t count = 0;
//COM和SEG设置为输入,由外部电阻分压为1/2VCC,全灭
COM_1_INPUT(); COM_2_INPUT(); COM_3_INPUT(); COM_4_INPUT();
SEG_0_INPUT(); SEG_1_INPUT(); SEG_2_INPUT(); SEG_3_INPUT(); SEG_4_INPUT(); SEG_5_INPUT(); SEG_6_INPUT(); SEG_7_INPUT();
if (++count < 2) return;
count = 0;
switch (step)
{
case 0: for (i = 0; i < sizeof(fflush); i++) fflush[i] = flush[i]; //在完整的一个扫描周期后才更新显示数据,确保一个扫描周期内电流都正反流一次
COM_1_SET(); COM_1_OUTPUT(); //COM1 输出模式+拉高, 电流流向COM -> SEG,根据需要拉低SEG以激活段显示
if (0x01U & fflush[0]) SEG_0_RESET(); else SEG_0_SET();
if (0x02U & fflush[0]) SEG_1_RESET(); else SEG_1_SET();
if (0x01U & fflush[1]) SEG_2_RESET(); else SEG_2_SET();
if (0x02U & fflush[1]) SEG_3_RESET(); else SEG_3_SET();
if (0x01U & fflush[2]) SEG_4_RESET(); else SEG_4_SET();
if (0x02U & fflush[2]) SEG_5_RESET(); else SEG_5_SET();
if (0x01U & fflush[3]) SEG_6_RESET(); else SEG_6_SET();
if (0x02U & fflush[3]) SEG_7_RESET(); else SEG_7_SET();
break;
case 1: COM_1_RESET(); COM_1_OUTPUT(); //COM1 输出模式+拉低, 电流流向SEG -> COM,根据需要拉高SEG以激活段显示
if (0 == (0x01U & fflush[0])) SEG_0_RESET(); else SEG_0_SET();
if (0 == (0x02U & fflush[0])) SEG_1_RESET(); else SEG_1_SET();
if (0 == (0x01U & fflush[1])) SEG_2_RESET(); else SEG_2_SET();
if (0 == (0x02U & fflush[1])) SEG_3_RESET(); else SEG_3_SET();
if (0 == (0x01U & fflush[2])) SEG_4_RESET(); else SEG_4_SET();
if (0 == (0x02U & fflush[2])) SEG_5_RESET(); else SEG_5_SET();
if (0 == (0x01U & fflush[3])) SEG_6_RESET(); else SEG_6_SET();
if (0 == (0x02U & fflush[3])) SEG_7_RESET(); else SEG_7_SET();
break;
//以上是COM1电流正反向流动,避免液晶极化
case 2: COM_2_SET(); COM_2_OUTPUT(); //COM2 输出模式+拉高, 电流流向COM -> SEG,根据需要拉低SEG以激活段显示
if (0x20U & fflush[0]) SEG_0_RESET(); else SEG_0_SET();
if (0x40U & fflush[0]) SEG_1_RESET(); else SEG_1_SET();
if (0x20U & fflush[1]) SEG_2_RESET(); else SEG_2_SET();
if (0x40U & fflush[1]) SEG_3_RESET(); else SEG_3_SET();
if (0x20U & fflush[2]) SEG_4_RESET(); else SEG_4_SET();
if (0x40U & fflush[2]) SEG_5_RESET(); else SEG_5_SET();
if (0x20U & fflush[3]) SEG_6_RESET(); else SEG_6_SET();
if (0x40U & fflush[3]) SEG_7_RESET(); else SEG_7_SET();
break;
case 3: COM_2_RESET(); COM_2_OUTPUT(); //COM2 输出模式+拉低, 电流流向SEG -> COM,根据需要拉高SEG以激活段显示
if (0 == (0x20U & fflush[0])) SEG_0_RESET(); else SEG_0_SET();
if (0 == (0x40U & fflush[0])) SEG_1_RESET(); else SEG_1_SET();
if (0 == (0x20U & fflush[1])) SEG_2_RESET(); else SEG_2_SET();
if (0 == (0x40U & fflush[1])) SEG_3_RESET(); else SEG_3_SET();
if (0 == (0x20U & fflush[2])) SEG_4_RESET(); else SEG_4_SET();
if (0 == (0x40U & fflush[2])) SEG_5_RESET(); else SEG_5_SET();
if (0 == (0x20U & fflush[3])) SEG_6_RESET(); else SEG_6_SET();
if (0 == (0x40U & fflush[3])) SEG_7_RESET(); else SEG_7_SET();
break;
case 4: COM_3_SET(); COM_3_OUTPUT();
if (0x10U & fflush[0]) SEG_0_RESET(); else SEG_0_SET();
if (0x04U & fflush[0]) SEG_1_RESET(); else SEG_1_SET();
if (0x10U & fflush[1]) SEG_2_RESET(); else SEG_2_SET();
if (0x04U & fflush[1]) SEG_3_RESET(); else SEG_3_SET();
if (0x10U & fflush[2]) SEG_4_RESET(); else SEG_4_SET();
if (0x04U & fflush[2]) SEG_5_RESET(); else SEG_5_SET();
if (0x10U & fflush[3]) SEG_6_RESET(); else SEG_6_SET();
if (0x04U & fflush[3]) SEG_7_RESET(); else SEG_7_SET();
break;
case 5: COM_3_RESET(); COM_3_OUTPUT();
if (0 == (0x10U & fflush[0])) SEG_0_RESET(); else SEG_0_SET();
if (0 == (0x04U & fflush[0])) SEG_1_RESET(); else SEG_1_SET();
if (0 == (0x10U & fflush[1])) SEG_2_RESET(); else SEG_2_SET();
if (0 == (0x04U & fflush[1])) SEG_3_RESET(); else SEG_3_SET();
if (0 == (0x10U & fflush[2])) SEG_4_RESET(); else SEG_4_SET();
if (0 == (0x04U & fflush[2])) SEG_5_RESET(); else SEG_5_SET();
if (0 == (0x10U & fflush[3])) SEG_6_RESET(); else SEG_6_SET();
if (0 == (0x04U & fflush[3])) SEG_7_RESET(); else SEG_7_SET();
break;
case 6: COM_4_SET(); COM_4_OUTPUT();
if (0x80U & fflush[0]) SEG_0_RESET(); else SEG_0_SET();
if (0x08U & fflush[0]) SEG_1_RESET(); else SEG_1_SET();
if (0x80U & fflush[1]) SEG_2_RESET(); else SEG_2_SET();
if (0x08U & fflush[1]) SEG_3_RESET(); else SEG_3_SET();
if (0x80U & fflush[2]) SEG_4_RESET(); else SEG_4_SET();
if (0x08U & fflush[2]) SEG_5_RESET(); else SEG_5_SET();
if (0x80U & fflush[3]) SEG_6_RESET(); else SEG_6_SET();
if (0x08U & fflush[3]) SEG_7_RESET(); else SEG_7_SET();
break;
case 7: COM_4_RESET(); COM_4_OUTPUT();
if (0 == (0x80U & fflush[0])) SEG_0_RESET(); else SEG_0_SET();
if (0 == (0x08U & fflush[0])) SEG_1_RESET(); else SEG_1_SET();
if (0 == (0x80U & fflush[1])) SEG_2_RESET(); else SEG_2_SET();
if (0 == (0x08U & fflush[1])) SEG_3_RESET(); else SEG_3_SET();
if (0 == (0x80U & fflush[2])) SEG_4_RESET(); else SEG_4_SET();
if (0 == (0x08U & fflush[2])) SEG_5_RESET(); else SEG_5_SET();
if (0 == (0x80U & fflush[3])) SEG_6_RESET(); else SEG_6_SET();
if (0 == (0x08U & fflush[3])) SEG_7_RESET(); else SEG_7_SET();
break;
default:
break;
}
//开启SEG输出 H/L
SEG_0_OUTPUT(); SEG_1_OUTPUT(); SEG_2_OUTPUT(); SEG_3_OUTPUT(); SEG_4_OUTPUT(); SEG_5_OUTPUT(); SEG_6_OUTPUT(); SEG_7_OUTPUT();
if (++step > 7)
step = 0;
}

更多推荐


所有评论(0)