RM67162 1.2英寸AMOLED调试 390*390
0赞X120BLN02这款屏可以用SPI、MIPI、SPI+MIPI这三款通讯方式,但为了省主控的成本,不得不用SPI来驱动,如果主控的SPI时钟太慢,势必会影响到刷新效果。
先看硬件,接口定义如下,从接口可以看出,FPC上自带POWER IC(BGA的)
//以下代码是4W-SPI
//IC:RM67162
//Module:H120BLN02
//interface:4W-SPI,
//主控SPI时钟32MHZ
//色深:RGB332,之前我一直以为方案会用RGB565,为是没有减少数据,那只能降色深,说真的RGB332效果还
//不赖,毕竟是Amoled,手表又不像手机,眼睛一直盯着看
//但是有个问题无法接触:SPI速率太低,同时平台在刷每帧都是全屏刷,所以在切换画面时会有延迟感
//我有建议方案改UI,即只有第一幅画面全屏刷,其余的都是写窗口,只要窗口像素点在肉眼能识别的时间内刷完
//不影响用户体验,还有个办法就是换高配置的主控
//方案驱动工程师有建议采用Vertical Scrolling刷屏,把IC规格书翻烂了,都没有找到这个寄存器
//所以只能放弃滚屏,话说ST7789V可以滚屏,不过我还没有试过,有机会领教一下,滚屏用来规避画面刷新
//延迟还是蛮不错的
#include "nrf_drv_spi.h"
#include "nrf_gpio.h"
#include "dxc_hal.h"
#include "dxc_halLcd.h"
#include "drv_rm67162.h"
// @brief 设置命令模式
static __INLINE void rm6716x_set_command_mode(void)
{
nrf_gpio_pin_clear(RM6716X_CMD_DATA_PIN);
delay_us(22);
}
// @brief 设置数据模式
static __INLINE void rm6716x_set_data_mode(void)
{
nrf_gpio_pin_set(RM6716X_CMD_DATA_PIN);
delay_us(22);
}
// @brief 发送数据
void rm6716x_write_data(const uint8_t* data, uint16_t dataLen)
{
rm6716x_spi_csn_clr();
rm6716x_set_data_mode();
rm6716x_transfer(data, dataLen, NULL, 0);
rm6716x_spi_csn_set();
}
// @brief 发送命令和数据
void rm6716x_send_command_data(uint8_t cmd, const uint8_t* data, uint16_t dataLen)
{
rm6716x_spi_csn_clr();
rm6716x_set_command_mode();
rm6716x_transfer(&cmd, 1, NULL, 0);
if (data && dataLen > 0)
{
rm6716x_set_data_mode();
rm6716x_transfer(data, dataLen, NULL, 0);
}
rm6716x_spi_csn_set();
}
// @brief 发送命令和字节
static __INLINE void rm6716x_send_command_byte(uint8_t cmd, uint8_t byte)
{
rm6716x_send_command_data(cmd, &byte, 1);
}
// @brief 发送命令
static __INLINE void rm6716x_write_command(uint8_t cmd)
{
rm6716x_send_command_data(cmd, NULL, 0);
}
uint16_t mem_start_address; // 垂直滚动地址
int16_t mem_length = 390; // 内存长(左右长度)
int16_t mem_width = 390; // 内存宽(上下高度)
// @brief 接口调试
void rm6716x_who_am_i(void)
{
uint8_t buff[8];
memset(buff, 0, sizeof(buff));
buff[0] = 0x04;
rm6716x_transfer(buff, 1, &buff[1], 3);
Debug("buff: %02x-%02x-%02x-%02x-%02x-%02x", buff[0], buff[1], buff[2], buff[3], buff[4], buff[5]);
}
void rm6716x_x120bln_init(void)
{
uint8_t buff[32];
uint8_t offset;
// 引脚初始化
hal_gpio_cfg_output(RM6716X_POWER_ENABLE_PIN);
hal_gpio_cfg_output(RM6716X_SPI_RESET_PIN);
hal_gpio_cfg_output(RM6716X_CMD_DATA_PIN);
// power enable
hal_gpio_pin_set(RM6716X_POWER_ENABLE_PIN);
delay_ms(15);
// reset
rm6716x_reset();
// 初始化
rm6716x_spi_init();
delay_ms(2);
rm6716x_send_command_byte(0xfe, 0x01); // page 0
rm6716x_send_command_byte(0x6c, 0x0a); // mipi turn off
rm6716x_send_command_byte(0x04, 0xa0);// 开启 spi 写 ram,此04必需写0xA0才能打开SPIR
rm6716x_send_command_byte(0xfe, 0x05);//Page4
rm6716x_send_command_byte(0x05, 0x00);
rm6716x_send_command_byte(0xfe, 0x00);//User Command
rm6716x_send_command_byte(0x35, 0x00);
rm6716x_send_command_byte(0x36, RM6716X_MADCTL_MY|RM6716X_MADCTL_MX|RM6716X_MADCTL_RGB);
rm6716x_send_command_byte(RM6716X_CMD_SET_BRIGHTNESS, 200);
rm6716x_send_command_byte(0x53, 0x20);//关dimming
rm6716x_send_command_byte(0xc4, 0x80);
rm6716x_send_command_byte(0x3a, RM6716X_RGB_FORMAT_565);
offset = 0;
buff[offset++] = 0x00;
buff[offset++] = 0x00;
buff[offset++] = 0x01; // 0x00ef = 239; 319 = 0x013f
buff[offset++] = 0x85;
rm6716x_send_command_data(RM6716X_CMD_COLUMN_ADDR_SET, buff, offset);
offset = 0;
buff[offset++] = 0x00;
buff[offset++] = 0x00;
buff[offset++] = 0x01; // 0x00ef = 239; 319 = 0x013f
buff[offset++] = 0x85;
rm6716x_send_command_data(RM6716X_CMD_ROW_ADDR_SET, buff, offset);
rm6716x_write_command(0x11); // sleep out
delay_ms(120);
rm6716x_write_command(0x29);//display on
delay_ms(20);
rm6716x_write_command(0x2C);//write Gram
delay_ms(20);
rm6716x_show_block_color(0, 0, 389, 389, WHITE);
}
// @brief 寄存器初始化
void rm6716x_init(void)
{
// gpio 初始化
hal_gpio_cfg_output(RM6716X_POWER_ENABLE_PIN);
hal_gpio_cfg_output(RM6716X_SPI_RESET_PIN);
hal_gpio_cfg_output(RM6716X_CMD_DATA_PIN);
rm6716x_spi_init();
delay_ms(1);
nrf_gpio_pin_clear(RM6716X_SPI_RESET_PIN);
delay_ms(20);
nrf_gpio_pin_set(RM6716X_SPI_RESET_PIN);
delay_ms(120);
// reg config
rm6716x_write_command(RM6716X_CMD_SLEEP_OUT);
delay_ms(10);
}
// @brief 使能电源
void rm6716x_power_enable(void)
{
// 复位引脚拉低
hal_gpio_cfg_output(RM6716X_SPI_RESET_PIN);
hal_gpio_pin_clear(RM6716X_SPI_RESET_PIN);
hal_gpio_cfg_output(RM6716X_CMD_DATA_PIN);
// 电源芯片引脚拉高
hal_gpio_cfg_output(RM6716X_POWER_ENABLE_PIN);
hal_gpio_pin_set(RM6716X_POWER_ENABLE_PIN);
}
// 初始化 spi
void rm6716x_spi_enable(void)
{
rm6716x_spi_init();
}
void rm6716x_reset(void)
{
hal_gpio_cfg_output(RM6716X_SPI_RESET_PIN);
delay_us(20);
nrf_gpio_pin_set(RM6716X_SPI_RESET_PIN);
delay_ms(4);
nrf_gpio_pin_clear(RM6716X_SPI_RESET_PIN);
delay_ms(4);
nrf_gpio_pin_set(RM6716X_SPI_RESET_PIN);
delay_ms(12);
}
// @brief 进入休眠
void rm6716x_enter_sleep(void)
{
rm6716x_write_command(RM6716X_CMD_DISPLAY_OFF);
rm6716x_write_command(RM6716X_CMD_SLEEP_IN);
}
// @brief 退出休眠
void rm6716x_exit_sleep(void)
{
rm6716x_write_command(RM6716X_CMD_SLEEP_OUT);
delay_ms(5);
rm6716x_write_command(RM6716X_CMD_DISPLAY_ON);
}
// @brief 设置窗口地址
// @param xs,xe 设置列区域(左右),ys,ye 设置行区域(上下);
void rm6716x_set_addr_window(uint16_t xs, uint16_t ys, uint16_t xe, uint16_t ye)
{
uint8_t buff[4];
buff[0] = xs >> 8;
buff[1] = xs;
buff[2] = xe >> 8;
buff[3] = xe;
rm6716x_send_command_data(RM6716X_CMD_COLUMN_ADDR_SET, buff, 4);
buff[0] = ys >> 8;
buff[1] = ys;
buff[2] = ye >> 8;
buff[3] = ye;
rm6716x_send_command_data(RM6716X_CMD_ROW_ADDR_SET, buff, 4);
rm6716x_write_command(RM6716X_CMD_MEM_WRITE);
}
// @brief 显示一个区块的颜色
bool rm6716x_show_block_color(int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color)
{
uint16_t i;
uint16_t length, weight;
uint8_t buff[256];
if (x2 > mem_length - 1)
x2 = mem_length - 1;
if (y2 > mem_length - 1)
y2 = mem_length - 1;
if (x1 < 0)
x1 = 0;
if (y1 < 0)
y1 = 0;
if ((x1 > x2) || (y1 > y2))
{
return FALSE;
}
length = x2-x1+1;
weight = y2-y1+1;
for (i=0; i<=256;)
{
buff[i++] = color >> 8;
buff[i++] = color;
}
// 对某个区域内存进行写
rm6716x_set_addr_window(x1, y1, x2, y2);
for(i=0; i<weight; i++)
{
if (length < 128)
{
rm6716x_write_data(buff, (length << 1));
}
else
{
rm6716x_write_data(buff, length);
rm6716x_write_data(buff, length);
}
}
rm6716x_write_command(0x2C);
delay_ms(20);
return TRUE;
}
// @brief 显示一张图片的一部分
// @param x1,y1 显示坐标
// @param xoft,yoft 图片显示偏移量
// @return true 成功显示图片;false 失败,没有显示图片
bool rm6716x_show_picture(int16_t x1, int16_t y1, int16_t xoft, int16_t yoft, const uint8_t *data)
{
int16_t i;
int16_t length, width;
uint8_t buf[256];
uint32_t offset = 8;
int16_t xLen, yLen;
// 如果坐标小于0,需要做偏移
if (x1 < 0)
{
xoft += (-x1);
x1 = 0;
}
if (y1 < 0)
{
yoft += (-y1);
y1 = 0;
}
length = ((data[2] << 8) | (data[3] & 0xFF));
width = ((data[4] << 8) | (data[5] & 0xFF));
if (xoft > length - 1 || yoft > width - 1) // 偏移超出图片大小
{
Log("The error picture position. xoft=%d, yoft=%d, width=%d, length=%d", xoft, yoft, width, length);
return FALSE;
}
xLen = length - xoft;
yLen = width - yoft;
xoft = 0;
if (x1 + xLen > mem_length - 1)
xLen = mem_length - x1;
else
xoft = (length - xLen) * 2;
length <<= 1;
if (y1 + yLen > mem_width - 1)
yLen = mem_width - y1;
else
offset += yoft * length;
rm6716x_set_addr_window(x1, y1, x1 + xLen - 1, y1 + yLen - 1);
for (i=0; i<yLen; i++)
{
if (xLen < 128)
{
memcpy(buf, &data[offset + xoft], (xLen << 1));
rm6716x_write_data(buf, (xLen << 1));
}
else
{
memcpy(buf, &data[offset + xoft], xLen);
rm6716x_write_data(buf, xLen);
memcpy(buf, &data[offset+xLen + xoft], xLen);
rm6716x_write_data(buf, xLen);
}
offset += length;
}
return TRUE;
}
// @brief 垂直滚动定义命令
// @param tfix 顶部固定区域;scoll 滚动区域;bfix 底部固定区域
// @note: tfix + scoll + bfix = 320
void rm6716x_vertical_scrolling_defined(uint16_t tfix, uint16_t scoll, uint16_t bfix)
{
uint8_t data[6];
data[0] = tfix >> 8;
data[1] = tfix;
data[2] = scoll >> 8;
data[3] = scoll;
data[4] = bfix >> 8;
data[5] = bfix;
LCD_SendCmdData(RM6716X_CMD_VERTICAL_SCROLLING, data, 6);
}
// @brief设置垂直滚动起始地址
void rm6716x_set_mem_start_address(uint16_t addr)
{
uint8_t data[2];
if (addr == mem_start_address)
return;
mem_start_address = addr;
data[0] = addr >> 8;
data[1] = addr;
LCD_SendCmdData(RM6716X_CMD_VER_SCRO_START_ADDR, data, 2);
}