/**
******************************************************************************
* @file 8f_5aw_display.c
* @author Motor Control SDK Team, Yuwell Software XiangenWang
* @brief Voice Recognition Module Initialization Section,
including peripheral initialization and message node insertion, etc.
* @version 1.0
* @changelog version 1.0 初始版本 2026.1.8
******************************************************************************
* @attention
*
*
© Copyright (c) 2025 Yuwell Software Danyang.Jiangsu.China.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted, provided that the following conditions are met:
*
* 1. Redistribution of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of Yuwell Software nor the names of other
* contributors to this software may be used to endorse or promote products
* derived from this software without specific written permission.
* 4. This software, including modifications and/or derivative works of this
* software, must execute solely and exclusively on microcontroller or
* microprocessor devices manufactured by or for Yuwell Software.
* 5. Redistribution and use of this software other than as permitted under
* this license is void and will automatically terminate your rights under
* this license.
*
* THIS SOFTWARE IS PROVIDED BY Yuwell Software AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NON-INFRINGEMENT OF THIRD PARTY INTELLECTUAL PROPERTY
* RIGHTS ARE DISCLAIMED TO THE FULLEST EXTENT PERMITTED BY LAW. IN NO EVENT
* SHALL Yuwell Software OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
******************************************************************************
*/
/* USER CODE END Header */
#include "8f_5aw_display.h"
uint8_t display_buffer[BL55072A_DISP_BUF_SIZE];
// 面板显示信息结构体
volatile PanelDisplayInfo panel_display_info;
#define ENABLE_PRECISE_I2C_DELAY (1)
#define configCPU_CLOCK_HZ (8000000)
/************************************************** 外部映射API **************************************************/
#define SDA_SET BL55072A_SDA_SET
#define SCL_SET BL55072A_SCL_SET
#define SDA_CLEAR BL55072A_SDA_CLEAR
#define SCL_CLEAR BL55072A_SCL_CLEAR
/************************************************** 静态子函数定义 **************************************************/
/**
* @brief I2C专用us级延时函数(仅用volatile,无编译器属性)
* @param us: 要延时的微秒数
* @note 循环体填充无用指令,进一步防止编译器优化
*/
static void I2C_Delay_us(uint32_t us)
{
#if ENABLE_PRECISE_I2C_DELAY
const uint32_t cycles_per_us = configCPU_CLOCK_HZ / 1000000UL;
const uint32_t Calib_Num = 8;
const uint32_t Calib_Den = 10;
uint32_t total_cycles = (us * cycles_per_us * Calib_Num) / Calib_Den;
if (total_cycles == 0 && us > 0)
{
total_cycles = 1;
}
volatile uint32_t i, j = 0;
for (i = 0; i < total_cycles; i++)
{
j++;
j--;
__NOP();
}
#else
// 主频过高时不适用
for(uint32_t i = 0;i < us;i++)
{
__NOP();
}
#endif
}
/**
* @brief I2C起始条件:SCL高电平时,SDA从高变低
* @param NONE
* @retval NONE
* @note 根据飞利浦I2C官方文档编写
* @author jarvis
* @data 2025.11.25
*/
static void I2C_Start(void)
{
SDA_SET();
SCL_SET();
I2C_Delay_us(1);
SDA_CLEAR();
I2C_Delay_us(1);
SCL_CLEAR();
}
/**
* @brief I2C停止条件:SCL高电平时,SDA从低变高
* @param NONE
* @retval NONE
* @note 根据飞利浦I2C官方文档编写,停止后释放I2C总线控制权
* @author jarvis
* @date 2025.11.25
*/
static void I2C_Stop(void)
{
SDA_CLEAR();
SCL_SET();
I2C_Delay_us(1);
SDA_SET();
I2C_Delay_us(1);
}
/**
* @brief I2C接收应答信号
* @param NONE
* @retval uint8_t - 应答检测结果:0表示接收到应答(ACK),1表示未接收到应答(NACK)
* @note SDA引脚需配置为输入模式,通过检测PC11引脚电平判断应答状态,遵循I2C协议时序
* @author jarvis
* @date 2025.11.25
*/
static uint8_t I2C_ReceiveAck(void)
{
uint8_t ack = 1;
SCL_CLEAR();
SDA_SET(); // 释放SDA总线
I2C_Delay_us(1);
SCL_SET();
I2C_Delay_us(1);
if((FL_GPIO_ReadInputPort(BL55072A_SDA_GPIO_PORT)&BL55072A_SDA_GPIO_PIN) == 0)
{
ack = 0; // 检测到应答
}
SCL_CLEAR();
return ack;
}
/**
* @brief I2C发送一个字节数据
* @param data - 待发送的8位数据
* @retval NONE
* @note 高位先行(MSB first),遵循I2C协议时序,SCL高电平时数据有效
* @author jarvis
* @date 2025.11.25
*/
static void I2C_SendByte(uint8_t data)
{
uint8_t i;
SCL_CLEAR();
for(i = 0; i < 8; i++)
{
if(data & 0x80)
{
SDA_SET(); // 发送最高位
}else{
SDA_CLEAR();
}
data <<= 1;
I2C_Delay_us(1);
SCL_SET();
I2C_Delay_us(1);
SCL_CLEAR();
}
}
/************************** 常量定义 **************************/
static const PanelDisplayInfo panel_display_default =
{
.timer =
{
.hours = 88,
.minutes = 88,
.is_light = LIGHT_ON
},
.total_time =
{
.time_mode = MODE_TOTAL_TIME,
.hours = 88888,
.first8_segtable_index = 10,
.second8_segtable_index = 10,
.third8_segtable_index = 10,
.fourth8_segtable_index = 10,
.fifth8_segtable_index = 10,
.is_light = LIGHT_ON
},
.error =
{
.first8_segtable_index = 8,
.second8_segtable_index = 8,
.is_light = LIGHT_ON
},
.oxg =
{
.value = 888,
.is_light = LIGHT_ON
},
};
#define SEG_TABLE_SIZE (20) // 断码表大小
const uint8_t SEG_TAB[SEG_TABLE_SIZE] =
{
//--0--,--1--,--2--,--3--,--4--,--5--,--6--,--7--,--8--,--9--,
0x5F, 0x50, 0x6B, 0x79, 0x74, 0x3D, 0x3F, 0x58, 0x7F, 0x7D,
//'-', ' ', 'E', 'L', 'P', 'H', 'C', 'F', 'U', 'A',
0x20, 0x00, 0x2F, 0x07, 0x6E, 0x76, 0x0F, 0x2E, 0x57, 0x7E,
};
/************************** 供外部调用的API函数 **************************/
/**
* @brief BL55072A液晶显示芯片初始化函数
* @note 完成GPIO初始化、I2C总线初始化、芯片基础配置及显存清空
* 初始化后屏幕处于关闭状态,需调用user_display_refresh开启显示
* @param 无
* @retval 无
* @author jarvis
* @date 2026.01.09
*/
void user_display_init(void)
{
// 初始化I2C引脚GPIO配置
FL_GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.pin = BL55072A_SDA_GPIO_PIN;
GPIO_InitStruct.mode = FL_GPIO_MODE_OUTPUT;
GPIO_InitStruct.pull = FL_DISABLE;
GPIO_InitStruct.remapPin = FL_DISABLE;
GPIO_InitStruct.analogSwitch = FL_DISABLE;
GPIO_InitStruct.outputType = FL_GPIO_OUTPUT_OPENDRAIN;
FL_GPIO_Init(BL55072A_SDA_GPIO_PORT, &GPIO_InitStruct); // 初始化SDA引脚
GPIO_InitStruct.pin = BL55072A_SCL_GPIO_PIN;
GPIO_InitStruct.pull = FL_ENABLE;
GPIO_InitStruct.outputType = FL_GPIO_OUTPUT_PUSHPULL;
FL_GPIO_Init(BL55072A_SCL_GPIO_PORT, &GPIO_InitStruct); // 初始化SCL引脚
// 初始化显示缓冲区为全1
memset(display_buffer, 0xFF, sizeof(display_buffer));
// 初始化屏幕配置结构体
panel_display_info = panel_display_default;
// ------------------- I2C总线及芯片初始化流程 -------------------
// 确保I2C总线处于停止状态,避免总线异常
I2C_Stop();
I2C_Delay_us(100); // 总线稳定延时(100us)
// 发送I2C起始条件
I2C_Start();
// 发送芯片写地址(0x7C),忽略ACK检测(仅显示场景)
I2C_SendByte(BL55072A_I2C_ADDR_W);
(void)I2C_ReceiveAck(); // 显式忽略返回值,避免编译器警告
// 发送ICSET命令:芯片基础配置(内部振荡器、不复位)
I2C_SendByte(BL55072A_CMD_ICSET);
(void)I2C_ReceiveAck();
// 发送DISCTL命令:显示驱动参数配置(帧频、驱动模式)
I2C_SendByte(BL55072A_CMD_DISCTL);
(void)I2C_ReceiveAck();
// 发送MODESET命令:关闭显示(1/3偏置)
I2C_SendByte(MODESET_BASIC | MODESET_DISP_OFF | MODESET_BIAS_1_3);
(void)I2C_ReceiveAck();
// 发送ADSET命令:设置显示寄存器起始地址为SEG0
I2C_SendByte(BL55072A_CMD_ADSET);
(void)I2C_ReceiveAck();
// 清空显存(0x00~0x23地址),防止脏数据导致显示异常
for(uint8_t i = 0; i < BL55072A_DISP_BUF_SIZE; i++)
{
I2C_SendByte(0x00);
(void)I2C_ReceiveAck();
}
// 发送I2C停止条件,确认数据写入生效
I2C_Stop();
// 显示稳定延时(100us)
I2C_Delay_us(100);
}
/**
* @brief BL55072A液晶显示刷新函数
* @note 加载显示缓冲区数据到芯片显存,并开启显示
* 需在初始化完成后调用,或更新display_buffer后调用
* @param 无
* @retval 无
* @author jarvis
* @date 2026.01.09
*/
void user_display_refresh(void)
{
uint8_t timer_hour_ten; // 小时十位(0-9)
uint8_t timer_hour_unit; // 小时个位(0-9)
uint8_t timer_minute_ten; // 分钟十位(0-9)
uint8_t timer_minute_unit; // 分钟个位(0-9)
uint8_t total_time_unit; // 存储累时的个、十、百、千、万位,定义变量
uint8_t total_time_ten;
uint8_t total_time_hundred;
uint8_t total_time_thousand;
uint8_t total_time_ten_thousand;
uint8_t oxg_ten; // 存储浓度的个、十、小数点
uint8_t oxg_unit;
uint8_t oxg_decimal;
// ------------------- 根据配置内容更改显存 -------------------
memset(display_buffer, 0x00, sizeof(display_buffer));
// 确定定时部分显存数据
if(panel_display_info.timer.is_light == LIGHT_ON)
{
timer_hour_ten = (panel_display_info.timer.hours / 10) % 10; // 十位:先除10再取余10
timer_hour_unit = panel_display_info.timer.hours % 10;
timer_minute_ten = (panel_display_info.timer.minutes / 10) % 10; // 十位:先除10再取余10
timer_minute_unit = panel_display_info.timer.minutes % 10;
display_buffer[TIMER_FIRST8_AFED_SEG_NUM/2] |= (SEG_TAB[timer_hour_ten]&0x0F)<<4;
display_buffer[TIMER_FIRST8_BGC_SEG_NUM/2] |= (SEG_TAB[timer_hour_ten]&0xF0)>>4;
display_buffer[TIMER_SECOND8_AFED_SEG_NUM/2] |= (SEG_TAB[timer_hour_unit]&0x0F)<<4;
display_buffer[TIMER_SECOND8_BGC_SEG_NUM/2] |= (SEG_TAB[timer_hour_unit]&0xF0)>>4;
display_buffer[TIMER_THIRD8_AFED_SEG_NUM/2] |= (SEG_TAB[timer_minute_ten]&0x0F)<<4;
display_buffer[TIMER_THIRD8_BGC_SEG_NUM/2] |= (SEG_TAB[timer_minute_ten]&0xF0)>>4;
display_buffer[TIMER_FOURTH8_AFED_SEG_NUM/2] |= (SEG_TAB[timer_minute_unit]&0x0F)<<4;
display_buffer[TIMER_FOURTH8_BGC_SEG_NUM/2] |= (SEG_TAB[timer_minute_unit]&0xF0)>>4;
display_buffer[TIMER_COL_ARRAY_INDEX] |= ADDITION_SEG_POS;
display_buffer[TIMER_H1_ARRAY_INDEX] |= ADDITION_SEG_POS;
}
// 确定累时部分显存数据
if(panel_display_info.total_time.is_light == LIGHT_ON)
{
if(panel_display_info.total_time.time_mode == MODE_TOTAL_TIME)
{
// 永远只取前五位 不用防止溢出
total_time_unit = panel_display_info.total_time.hours % 10;
total_time_ten = (panel_display_info.total_time.hours / 10) % 10; // 十位:先除10再取余10
total_time_hundred = (panel_display_info.total_time.hours / 100) % 10; // 百位:先除100再取余10
total_time_thousand = (panel_display_info.total_time.hours / 1000) % 10; // 千位:先除1000再取余10
total_time_ten_thousand = (panel_display_info.total_time.hours / 10000) % 10; // 万位:先除10000再取余10
display_buffer[TIME_FIRST8_AFED_SEG_NUM/2] |= (SEG_TAB[total_time_ten_thousand]&0x0F)<<4;
display_buffer[TIME_FIRST8_BGC_SEG_NUM/2] |= (SEG_TAB[total_time_ten_thousand]&0xF0)>>4;
display_buffer[TIME_SECOND8_AFED_SEG_NUM/2] |= (SEG_TAB[total_time_thousand]&0x0F)<<4;
display_buffer[TIME_SECOND8_BGC_SEG_NUM/2] |= (SEG_TAB[total_time_thousand]&0xF0)>>4;
display_buffer[TIME_THIRD8_AFED_SEG_NUM/2] |= (SEG_TAB[total_time_hundred]&0x0F)<<4;
display_buffer[TIME_THIRD8_BGC_SG_NUM/2] |= (SEG_TAB[total_time_hundred]&0xF0)>>4;
display_buffer[TIME_FOURTH8_AFED_SEG_NUM/2] |= (SEG_TAB[total_time_ten]&0x0F)<<4;
display_buffer[TIME_FOURTH8_BGC_SEG_NUM/2] |= (SEG_TAB[total_time_ten]&0xF0)>>4;
display_buffer[TIME_FIFTH8_AFED_SEG_NUM/2] |= (SEG_TAB[total_time_unit]&0x0F)<<4;
display_buffer[TIME_FIFTH8_BGC_SEG_NUM/2] |= (SEG_TAB[total_time_unit]&0xF0)>>4;
display_buffer[TOTAL_TIME_H2_ARRAY_INDEX] |= ADDITION_SEG_POS;
}else if(panel_display_info.total_time.time_mode == MODE_INDEX)
{
// 中间累时部分按照索引显示
display_buffer[TIME_FIRST8_AFED_SEG_NUM/2] |= (SEG_TAB[panel_display_info.total_time.first8_segtable_index]&0x0F)<<4;
display_buffer[TIME_FIRST8_BGC_SEG_NUM/2] |= (SEG_TAB[panel_display_info.total_time.first8_segtable_index]&0xF0)>>4;
display_buffer[TIME_SECOND8_AFED_SEG_NUM/2] |= (SEG_TAB[panel_display_info.total_time.second8_segtable_index]&0x0F)<<4;
display_buffer[TIME_SECOND8_BGC_SEG_NUM/2] |= (SEG_TAB[panel_display_info.total_time.second8_segtable_index]&0xF0)>>4;
display_buffer[TIME_THIRD8_AFED_SEG_NUM/2] |= (SEG_TAB[panel_display_info.total_time.third8_segtable_index]&0x0F)<<4;
display_buffer[TIME_THIRD8_BGC_SG_NUM/2] |= (SEG_TAB[panel_display_info.total_time.third8_segtable_index]&0xF0)>>4;
display_buffer[TIME_FOURTH8_AFED_SEG_NUM/2] |= (SEG_TAB[panel_display_info.total_time.fourth8_segtable_index]&0x0F)<<4;
display_buffer[TIME_FOURTH8_BGC_SEG_NUM/2] |= (SEG_TAB[panel_display_info.total_time.fourth8_segtable_index]&0xF0)>>4;
display_buffer[TIME_FIFTH8_AFED_SEG_NUM/2] |= (SEG_TAB[panel_display_info.total_time.fifth8_segtable_index]&0x0F)<<4;
display_buffer[TIME_FIFTH8_BGC_SEG_NUM/2] |= (SEG_TAB[panel_display_info.total_time.fifth8_segtable_index]&0xF0)>>4;
}
}
// 故障码显示
if(panel_display_info.error.is_light == LIGHT_ON)
{
// 防止超出断码表范围
panel_display_info.error.first8_segtable_index = panel_display_info.error.first8_segtable_index >= SEG_TABLE_SIZE?\
(SEG_TABLE_SIZE-1):panel_display_info.error.first8_segtable_index;
panel_display_info.error.second8_segtable_index = panel_display_info.error.second8_segtable_index >= SEG_TABLE_SIZE?\
(SEG_TABLE_SIZE-1):panel_display_info.error.second8_segtable_index;
display_buffer[ERROR_FIRT8_AFED_SEG_NUM/2] |= (SEG_TAB[panel_display_info.error.first8_segtable_index]&0x0F);
display_buffer[ERROR_FIRT8_BGC_SEG_NUM/2] |= (SEG_TAB[panel_display_info.error.first8_segtable_index]&0xF0);
display_buffer[ERROR_SECOND8_AFED_SEG_NUM/2] |= (SEG_TAB[panel_display_info.error.second8_segtable_index]&0x0F);
display_buffer[ERROR_SECOND8_BGC_SEG_NUM/2] |= (SEG_TAB[panel_display_info.error.second8_segtable_index]&0xF0);
}
// 氧浓度显示
if(panel_display_info.oxg.is_light == LIGHT_ON)
{
oxg_decimal = panel_display_info.oxg.value % 10;
oxg_unit = (panel_display_info.oxg.value / 10) % 10;
oxg_ten = (panel_display_info.oxg.value / 100) % 10;
display_buffer[OXG_FIRST8_AFED_SEG_NUM/2] |= (SEG_TAB[oxg_ten]&0x0F);
display_buffer[OXG_FIRST8_BGC_SEG_NUM/2] |= (SEG_TAB[oxg_ten]&0xF0);
display_buffer[OXG_SECOND8_AFED_SEG_NUM/2] |= (SEG_TAB[oxg_unit]&0x0F);
display_buffer[OXG_SECOND8_BGC_SEG_NUM/2] |= (SEG_TAB[oxg_unit]&0xF0);
display_buffer[OXG_THIRD8_AFED_SEG_NUM/2] |= (SEG_TAB[oxg_decimal]&0x0F);
display_buffer[OXG_THIRD8_BGC_SG_NUM/2] |= (SEG_TAB[oxg_decimal]&0xF0);
display_buffer[OXG_S1_ARRAY_INDEX] |= ADDITION_SEG_POS<<4;
display_buffer[OXG_POINT_ARRAY_INDEX] |= ADDITION_SEG_POS<<4;
display_buffer[OXG_PERCEENET_ARRAY_INDEX] |= ADDITION_SEG_POS<<4;
}
// ------------------- 加载显示缓冲区数据 -------------------
// 发送I2C起始条件
I2C_Start();
// 发送芯片写地址
I2C_SendByte(BL55072A_I2C_ADDR_W);
(void)I2C_ReceiveAck(); // 显式忽略返回值,避免编译器警告
// 发送BLKCTL命令:关闭闪烁功能
I2C_SendByte(BL55072A_CMD_BLKCTL);
(void)I2C_ReceiveAck();
// 发送DISCTL命令:配置显示驱动参数
I2C_SendByte(BL55072A_CMD_DISCTL);
(void)I2C_ReceiveAck();
// 发送ICSET命令:芯片基础参数配置
I2C_SendByte(BL55072A_CMD_ICSET);
(void)I2C_ReceiveAck();
// 发送ADSET命令:设置显示寄存器起始地址
I2C_SendByte(BL55072A_CMD_ADSET);
(void)I2C_ReceiveAck();
// 将显示缓冲区数据写入芯片显存(0x00~0x23地址)
for(uint8_t i = 0; i < BL55072A_DISP_BUF_SIZE; i++)
{
I2C_SendByte(display_buffer[i]);
(void)I2C_ReceiveAck();
}
// 停止I2C传输,确认数据写入
I2C_Stop();
// ------------------- 开启显示 -------------------
I2C_Delay_us(100); // 总线稳定延时(100us)
// 发送I2C起始条件
I2C_Start();
// 发送芯片写地址(0x7C)
I2C_SendByte(BL55072A_I2C_ADDR_W);
(void)I2C_ReceiveAck(); // 显式忽略返回值,避免编译器警告
// 发送MODESET命令:开启显示(核心步骤)
I2C_SendByte(BL55072A_CMD_MODESET);
(void)I2C_ReceiveAck();
// 停止I2C传输,完成显示开启
I2C_Stop();
}
/************************ (C) COPYRIGHT YUWELL *****END OF FILE****/