一、项目功能介绍
i.MX RT1021开发板上集成了环境亮度传感器,与屏幕搭配实现亮度测量与控制。实现将读取到的环境光亮度及控制屏幕背光灯的PWM波的占空比显示到屏幕上,并能够根据当前亮度,依照人眼对亮度的感知曲线,自动调整屏幕亮度。
二、设计思路
利用lpspi通信(硬件实现)实现与lcd屏幕通讯,实现简易的ui;
利用自己编写的简易的ui,实现屏幕上绘制相关信息并实时更新;
利用i2c通信(软件模拟)实现与环境光传感器通讯,读取环境光信息;
利用pwm波控制屏幕背光灯,通过调节pwm波的占空比以实现对屏幕亮度的调节;
根据人眼对亮度的感知曲线,利用伽马修正将获取的环境光数值转换为控制屏幕背光灯的pwm波的占空比;
利用FreeRTOS,实现任务的调度;
使用公有变量,实现FreeRTOS各任务中数据的同步。
三、硬件框图
四、软件流程图
五、硬件介绍
1.核心板
本次使用的RT1021核心板是基于恩智浦i.MX RT1020系列MIMXRT1021CAG4A芯片设计的综合开发板,i.MX RT1020跨界MCU基于Arm®Cortex®-M7内核,运行频率高达500MHz, 256 KB 片内 RAM,可灵活配置为 TCM 或通用片内 RAM。i.MX RT1020 集成了先进的电源管理模块、DCDC和 LDO,可降低外部电源的复杂性并简化上电时序。i.MX RT1020 还提供各类存储器接口,包括 SDRAM、RAW NAND、闪存、NOR 闪存、SD/eMMC、四通道 SPI;以及各种连接接口,包括 UART、SPI、I2C、USB 和 CAN,可用于连接外围设备,包括 WLAN、bluetooth™和GPS。i.MX Rt1020 还提供丰富的音频功能,包括SPDIF 和 I2S 音频接口。集成各类模拟 IP,包括ADC、模拟比较器、温度传感器等。
采用的是144pin脚的MIMXRT1021DAG5A芯片,外扩8MB FLASH用于存储程序,核心板上集成了四个按键,一个电源指示灯,一个用户LED灯,两组2x20 Pin的排针将调试接口和可用IO口基本全部引出,方便扩展与测试。核心板采用TYPE-C接口进行供电和数据传输,此外该接口还支持480M高速通信模式。核心板卡上预留有Micro SD卡槽,可用作数据存储与回放。
2.扩展板概述
扩展版上搭配的四款传感器,包括用于实时气象监测的温湿度传感器、为导航和定位提供精准数据的三轴磁场传感器、高精度姿态感知的加速度角速度传感器以及自动调节背光的环境光传感器,均为常见的通信接口,方便用户更加熟悉各类传感器的使用。在用户交互方面,主板引入软件可编程的RGB彩灯,一块128*160分辨率的电阻式触摸屏提供直观的触摸操作、旋钮、双向电位计摇杆增加用户的操作空间。同时搭配DAPlink硬件调试助手,直接插入主板上预留的调试器接口中,不需要用户额外使用杜邦线连接,调试过程更加方便快捷。
3.BH1730FVC环境光传感器
(此条为对2中提到的“环境光传感器”的具体介绍)
BH1730FVC为数字输出型的环境光亮度传感器IC,由作为基本电路的光电二极管、电流电压转换电路、A/D转换器、控制逻辑电路以及接口电路等构成,与机器内的I²C BUS连接使用。支持I²C总线接口(支持 f/s-mode , 从机地址为“0101001");具有两种输出,在可视光领域(Type0)和红外光领域有各种波长灵敏度峰值;用数字值输出相应的亮度值;具有高灵敏度模式,支持宽范围的输入光(相当于0.008 - 65535 lx);根据掉电功能可实现低电流化;根据50Hz / 60Hz 光噪去除功能实现放心测量;支持1.8V逻辑输入接口;根据两种输出的计算,灵敏度对光源的依赖性小(对白炽灯、荧光灯、卤素灯、白色LED、太阳光等光源的依赖);通过中断功能,可以检测照度值的变化;根据灵敏度调整功能,可以校正光学窗引起的光的衰减(通过使用这个功能可以检测min 0.001lx、max 100,000lx);测量偏差小(± 15%);内置上电复位电路。
4.调试器
本次使用的配套调试器为硬禾学堂的12指神探调试助手。12指神探是一款基于树莓派基金会推出的微控制器RP2040制作的多功能硬件调试助手。
六、实现的功能及图片展示
本次实现的功能为根据当前环境光的亮度调整屏幕亮度,并将环境光亮度及控制屏幕背光灯亮度的pwm波占空比实时显示在屏幕上。
下面为图片展示及简要说明:
【图 1】
上图为高环境光强度下屏幕亮度被设置为较高的数值
图中”+Lux“栏为当前环境光强度,单位为勒克斯(lx);
图中”+Scr Light“栏为当前控制屏幕背光灯亮度的pwm波的占空比,单位为百分比(%)
如图所示当前环境光亮度为172lx,与使用Lux Light Meter测量得到的173lx误差较小
【图 2】
上图为高环境光强度下用手挡住环境光传感器模拟较低光照强度
如图所示环境光传感器读取的值较低,屏幕的亮度也被设置为较低水平(因为相机的原因显示的不太明显)
【图 3】
【图 4】
上两张图片为较低环境光亮度下屏幕亮度对于环境光改变变化得较为明显,这也符合人眼对亮度的感知曲线
【图 5】
上图为板载传感器测量值与实际值(使用Lux Light Meter测量)对比,可见测量值与实际值误差较小
七、主要代码片段及说明
1.基于lpspi的lcd显示屏驱动(定义于lcd.c)
①向屏幕发送命令或数据
此部分通过利用中断式lpspi实现,通过操作直接GPIO引脚实现数据与命令的片选
void lcd_WriteData(uint8_t data){
/*
* @brief 向屏幕控制器写数据
* @param <uint8_t>要发送的数据
* @return 无
*/
uint8_t *txData = &data;
GPIO_PinWrite(GPIO3, 1, 0U);//CS low : enable
GPIO_PinWrite(GPIO3, 4, 1U);//DC high : data mode
spi_send(txData);
GPIO_PinWrite(GPIO3, 1, 1U);//CS high : disable
}
void lcd_WriteCmd(uint8_t command){
/*
* @brief 向屏幕控制器写命令
* @param <uint8_t>要发送的命令
* @return 无
*/
uint8_t *data = &command;
GPIO_PinWrite(GPIO3, 1, 0U);//CS low : enable
GPIO_PinWrite(GPIO3, 4, 0U);//DC low : command mode
spi_send(data);
GPIO_PinWrite(GPIO3, 1, 1U);//CS high : disable
}
void lcd_WriteData_u16(uint16_t data){
/*
* @brief 向屏幕控制器写数据(16位)
* @param <uint16_t>要发送的数据
* @return 无
*/
GPIO_PinWrite(GPIO3, 1, 0U);//CS low : enable
GPIO_PinWrite(GPIO3, 4, 1U);//DC high : data mode
uint8_t high8 = (uint8_t)(data >> 8);
uint8_t low8 = (uint8_t)(data &0x00FF);
uint8_t *p_high8 = &high8;
uint8_t *p_low8 = &low8;
spi_send(p_high8);
spi_send(p_low8);
GPIO_PinWrite(GPIO3, 1, 1U);//CS high : disable
}
②复位屏幕(注意启动时要先复位屏幕)
注:Sleep为自定义阻塞式延时函数,单位为毫秒,定义于(led.c)
void lcd_reset(void){
/*
* @brief 重置屏幕
* @param 无
* @return 无
*/
GPIO_PinWrite(GPIO3, 7, 0U);
Sleep(5);
GPIO_PinWrite(GPIO3, 7, 1U);
Sleep(120);
}
③初始化屏幕(参考官方提供的例程写出)
注:屏幕方向的取值由宏定义,VERTICAL表示纵向,取值为1;HORIZONTAL为横向,取值为2
void lcd_init(int direction){
/*
* @brief 初始化屏幕
* @param <int>屏幕显示方向
* @return 无
*/
lcd_reset();
lcd_WriteCmd(0x11);
Sleep(120);
lcd_WriteCmd(0xB1);
lcd_WriteData(0x01);
lcd_WriteData(0x2C);
lcd_WriteData(0x2D);
lcd_WriteCmd(0xB2);
lcd_WriteData(0x01);
lcd_WriteData(0x2C);
lcd_WriteData(0x2D);
lcd_WriteCmd(0xB3);
lcd_WriteData(0x01);
lcd_WriteData(0x2C);
lcd_WriteData(0x2D);
lcd_WriteData(0x01);
lcd_WriteData(0x2C);
lcd_WriteData(0x2D);
lcd_WriteCmd(0xB4); //Column inversion
lcd_WriteData(0x07);
//ST7735R Power Sequence
lcd_WriteCmd(0xC0);
lcd_WriteData(0xA2);
lcd_WriteData(0x02);
lcd_WriteData(0x84);
lcd_WriteCmd(0xC1);
lcd_WriteData(0xC5);
lcd_WriteCmd(0xC2);
lcd_WriteData(0x0A);
lcd_WriteData(0x00);
lcd_WriteCmd(0xC3);
lcd_WriteData(0x8A);
lcd_WriteData(0x2A);
lcd_WriteCmd(0xC4);
lcd_WriteData(0x8A);
lcd_WriteData(0xEE);
lcd_WriteCmd(0xC5); //VCOM
lcd_WriteData(0x0E);
lcd_WriteCmd(0x36); //MX, MY, RGB mode
if(direction == HORIZONTAL){
lcd_WriteData(0xA0);//横屏
}else{
lcd_WriteData(0xC8);//竖屏
}
lcd_WriteCmd(0xE0);
lcd_WriteData(0x0F);
lcd_WriteData(0x1A);
lcd_WriteData(0x0F);
lcd_WriteData(0x18);
lcd_WriteData(0x2F);
lcd_WriteData(0x28);
lcd_WriteData(0x20);
lcd_WriteData(0x22);
lcd_WriteData(0x1F);
lcd_WriteData(0x1B);
lcd_WriteData(0x23);
lcd_WriteData(0x37);
lcd_WriteData(0x00);
lcd_WriteData(0x07);
lcd_WriteData(0x02);
lcd_WriteData(0x10);
lcd_WriteCmd(0xE1);
lcd_WriteData(0x0F);
lcd_WriteData(0x1B);
lcd_WriteData(0x0F);
lcd_WriteData(0x17);
lcd_WriteData(0x33);
lcd_WriteData(0x2C);
lcd_WriteData(0x29);
lcd_WriteData(0x2E);
lcd_WriteData(0x30);
lcd_WriteData(0x30);
lcd_WriteData(0x39);
lcd_WriteData(0x3F);
lcd_WriteData(0x00);
lcd_WriteData(0x07);
lcd_WriteData(0x03);
lcd_WriteData(0x10);
lcd_WriteCmd(0x2A);
lcd_WriteData(0x00);
lcd_WriteData(0x00+2);
lcd_WriteData(0x00);
lcd_WriteData(0x80+2);
lcd_WriteCmd(0x2B);
lcd_WriteData(0x00);
lcd_WriteData(0x00+3);
lcd_WriteData(0x00);
lcd_WriteData(0x80+3);
lcd_WriteCmd(0xF0); //Enable test command
lcd_WriteData(0x01);
lcd_WriteCmd(0xF6); //Disable ram power save mode
lcd_WriteData(0x00);
lcd_WriteCmd(0x3A); //65k mode
lcd_WriteData(0x05);
lcd_WriteCmd(0x29);//Display on
}
④填充屏幕区域与描点
void lcd_setAreaColor(uint8_t x_start,uint8_t y_start,uint8_t x_end,uint8_t y_end,uint16_t color){
/*
* @brief 将屏幕指定区域设置为指定颜色
* @param <uint8_t>x起始坐标 <uint8_t>y起始坐标 <uint8_t>x结束坐标 <uint8_t>y结束坐标 <uint16_t>rgb565格式颜色
* @return 无
*/
lcd_writeAddr(x_start,y_start,x_end,y_end);
//计算填充区域的长度和宽度,终点坐标减起点坐标+1
uint8_t x_len = x_end - x_start + 1;//计算x坐标的长度
uint8_t y_len = y_end - y_start + 1;//计算y坐标的长度
for(int i = 0; i < x_len; i++){
for(int j = 0; j < y_len; j++){
lcd_WriteData_u16(convert(color));
}
}
}
void lcd_setPointColor(uint8_t x, uint8_t y, uint16_t color){
/*
* @brief 将屏幕指定像素点设置为指定颜色
* @param <uint8_t>x坐标 <uint8_t>y坐标 <uint16_t>rgb565格式颜色
* @return 无
*/
lcd_writeAddr(x,y,x,y);
lcd_WriteData_u16(convert(color));
}
void lcd_screen(uint16_t color){
/*
* @brief 将屏幕设置为指定颜色
* @param <uint16_t>rgb565格式颜色
* @return 无
*/
lcd_writeAddr(0, 0, 160, 160);
for(int i = 0; i < 161; i++){
for(int j = 0; j < 161; j++){
lcd_WriteData_u16(convert(color));
}
}
}
2.根据上面实现的lcd驱动完成简易的ui(定义于ui.c)
①封装屏幕相关的初始化代码
void ui_init(void){
/*
* @brief 初始化lpspi通讯并配置好屏幕
* @param 无
* @return 无
*/
spi_init();//初始化lpspi通讯
lcd_init(VERTICAL);//初始化屏幕并设置为竖屏
lcd_start();//打开屏幕背光灯
ui_solidScreen(0x0000);//把屏幕设置为全黑等待显示
}
②软件模拟pwm通过占空比控制屏幕背光灯的亮度
void ui_luminance(int percentage){
/*
* @brief 调整屏幕亮度(应该被放入一个无限循环中)
* @param <int>屏幕亮度(取值0到100)
* @return 无
*/
if(percentage > 100){
percentage = 100;
}else if(percentage < 1){
percentage = 1;
}
if(percentage > 35){
lcd_close();
SDK_DelayAtLeastUs((100*(100-percentage)), SystemCoreClock);
lcd_start();
SDK_DelayAtLeastUs((100*percentage), SystemCoreClock);
}else{
lcd_start();
SDK_DelayAtLeastUs((100*percentage), SystemCoreClock);
lcd_close();
SDK_DelayAtLeastUs((100*(100-percentage)), SystemCoreClock);
}
}
③实现文本的定义与显示
注:这里使用8x8的点阵字库,使用const unsigned char[]存储,支持ascii字符显示,字库名为font,定义于font.h
Text ui_label(char* txt, uint16_t color, uint16_t bg, bool hasBg){
/*
* @brief 得到一个赋值完成的Text结构体
* @param <char *>指向文本数组的指针 <uint16_t>文本颜色(rgb565格式) <uint16_t>背景颜色(rgb565格式,若hasBg为false则无效) <bool>是否启用背景颜色
* @return 赋值完成的Text结构体
*/
Text newText;
newText.txt = txt;
newText.color = color;
newText.bgColor = bg;
newText.hasBg = hasBg;
return newText;
}
void ui_displayText(Text* ui_label, int xpos, int ypos, bool flag){
/*
* @brief 在屏幕上显示文本,使用\n换行
* @param <Text *>指向文本结构体的指针 <int>起始x坐标 <int>起始y坐标 <bool>是否允许延时(设为true如果放在FreeRTOS的任务调度中)
* @return 无
*/
int length = strlen(ui_label->txt);
int thisX = xpos;
int thisY = ypos;
int initX = xpos;
int count = 0;
for(int i=0; i<length; i++){
//遍历每一个文字
if(ui_label->txt[i] == 0x0A){
//如果遇到换行符(\n)
thisY += 8;
ypos += 8;
thisX = initX;
xpos = initX;
count = 0;
continue;
}
if(count == 16){
//如果一行占满
count = 0;
thisY += 8;
ypos += 8;
thisX = initX;
xpos = initX;
if(ui_label->txt[i] == 0x20){
//如果行首是空格
continue;
}
}
for(int j=0; j<8; j++){
//遍历单个文字的每一排(8排)
for(int k=0; k<8; k++){
//遍历每排的每个像素点(8个)
if((font[(ui_label->txt[i]*8+j)]&(1 << k)) == 0){
//背景点
if(ui_label->hasBg){
lcd_setPointColor(thisX, thisY, ui_label->bgColor);
}
}else{
//有效文本点
lcd_setPointColor(thisX, thisY, ui_label->color);
}
thisX += 1;
if(flag == true && (k == 3)){
vTaskDelay(pdMS_TO_TICKS(1));
}
}
thisX = xpos;
thisY += 1;
}
thisY = ypos;
thisX += 8;
xpos += 8;
count++;
}
}
④实现图片的定义与显示
Image ui_image(uint16_t *img, int xsize, int ysize){
/*
* @brief 得到一个赋值完成的Image结构体
* @param <uint16_t *>指向图片数组的指针 <int>图片的宽 <int>图片的高
* @return 赋值完成的Image结构体
*/
Image newImg;
newImg.img = img;
newImg.xsize = xsize;
newImg.ysize = ysize;
return newImg;
}
void ui_displayImage(Image* img, int xpos, int ypos){
/*
* @brief 在屏幕上显示图片
* @param <Image *>指向图片组件的指针 <int>起始x坐标 <int>起始y坐标
* @return 无
*/
lcd_writeAddr(xpos, ypos, (xpos+img->xsize-1), (ypos+img->ysize-1));
for(int i = 0; i < (img->xsize*img->ysize); i++){
lcd_WriteData_u16(img->img[i]);
}
}
3.软件模拟实现i2c通信并完成读取环境光传感器的值(定义于lightSensor.c)
(注:由于本人的开发板的硬件实现的lpi2c通讯出现了一些问题导致其api无法正常工作,故这里采用软件模拟i2c实现与环境光传感器的通讯)
①定义对i2c时钟线的控制与对数据线的控制与读取
void scl(uint8_t value){
/*
* @brief 设置i2c的scl时钟线为指定电平
* @param <uint8_t>要设置的时钟线的电平,取值0(低电平)或1(高电平)
* @return 无
*/
GPIO2->GDIR |= (1UL << 2);
GPIO_PinWrite(GPIO2, 2, value);
}
void sda(uint8_t value){
/*
* @brief 设置i2c的sda数据线为指定电平
* @param <uint8_t>要设置的数据线的电平,取值0(低电平)或1(高电平)
* @return 无
*/
GPIO2->GDIR |= (1UL << 3);
GPIO_PinWrite(GPIO2, 3, value);
}
uint8_t sda_read(void){
/*
* @brief 读取当前i2c的sda数据线的电平
* @param 无
* @return <uint8_t>当前数据线的电平,取值0(低电平)或1(高电平)
*/
GPIO2->GDIR |= (1UL << 3);
GPIO_PinWrite(GPIO2, 3, 0);
SDK_DelayAtLeastUs(2, SystemCoreClock);
GPIO2->GDIR &= ~(1UL << 3);
return GPIO_PinRead(GPIO2, 3);
}
②模拟i2c的起始(Start)与结束(Stop)信号
注:DELAY为宏定义,取值为5,下同
void i2c_start(void){
/*
* @brief 主机发送i2c起始(Start)信号
* @param 无
* @return 无
*/
scl(1);
sda(1);
SDK_DelayAtLeastUs(DELAY, SystemCoreClock);
sda(0);
SDK_DelayAtLeastUs(DELAY, SystemCoreClock);
}
void i2c_stop(void){
/*
* @brief 主机发送i2c停止(Stop)信号
* @param 无
* @return 无
*/
scl(1);
sda(0);
SDK_DelayAtLeastUs(DELAY, SystemCoreClock);
sda(1);
SDK_DelayAtLeastUs(DELAY, SystemCoreClock);
}
③实现软件模拟i2c的数据发送与读取
void i2c_write(uint8_t data){
/*
* @brief 主机通过i2c发送数据
* @param <uint8_t>要发送的数据
* @return 无
*/
printf("Prepare to send byte : 0x%02X \n", data);
scl(0);
SDK_DelayAtLeastUs(DELAY, SystemCoreClock);
for(uint8_t bit=0x80; bit>0; bit >>= 1){
//for循环实现每一位数据的发送
if(data & bit){
sda(1);
}else{
sda(0);
}
//发送时钟脉冲
SDK_DelayAtLeastUs(DELAY, SystemCoreClock);
scl(1);
SDK_DelayAtLeastUs(DELAY, SystemCoreClock);
scl(0);
SDK_DelayAtLeastUs(DELAY, SystemCoreClock);
}
int idd = 0;
while(sda_read()){
//等待从机Ack应答
if(idd >= 300){
//超时未收到Ack应答的处理,这里为显示错误信息
printf("Software I2C Error : Acknowledgment Error \n");
break;
}
idd++;
}
scl(1);
SDK_DelayAtLeastUs(DELAY, SystemCoreClock);
}
uint8_t i2c_read(bool hasAck){
/*
* @brief 主机通过i2c读取数据
* @param <bool>是否发送ack信号,为true发送ack,为false发送nack
* @return 读取的数据
*/
uint8_t data = 0;
GPIO2->GDIR |= (1UL << 3);
GPIO_PinWrite(GPIO2, 3, 0);
SDK_DelayAtLeastUs(1, SystemCoreClock);
GPIO2->GDIR &= ~(1UL << 3);
SDK_DelayAtLeastUs(3, SystemCoreClock);
for(int i=0; i<8; i++){
//用for循环实现每一位数据的读取
data <<= 1;
scl(0);
int idd = 0;
uint8_t i0 = 0;
uint8_t i1 = 1;
i0 = GPIO_PinRead(GPIO2, 3);
while(idd<5){
i1 = GPIO_PinRead(GPIO2, 3);
if(i0 == 1 && i1 == 0){
//检测到下降沿,判定为0
data |= 0;
break;
}else if(i0 == 0 && i1 == 1){
//检测到上升沿,判定为1
data |= 1;
break;
}
i0 = i1;
idd++;
}
scl(1);
SDK_DelayAtLeastUs(DELAY, SystemCoreClock);
}
SDK_DelayAtLeastUs(1, SystemCoreClock);
scl(0);
sda(1);
SDK_DelayAtLeastUs(DELAY, SystemCoreClock);
if(!hasAck){
//如果为Nack则拉低sda数据线的电平
sda(0);
}
SDK_DelayAtLeastUs(DELAY, SystemCoreClock);
scl(1);
SDK_DelayAtLeastUs(DELAY, SystemCoreClock);
return data;
}
④数据的处理与计算
uint16_t calculate(uint16_t d0, uint16_t d1){
/*
* @brief 将读取到的数据通过计算转化为环境光的值,单位为lux
* 计算公式来自BH1730FVC-TR环境光传感器Datasheet第13面
* @param <uint16_t>DATA0 <uint16_t>DATA1
* @return 计算所得的环境光的值
*/
float Gain = 1.0;//默认的Gain,可参考BH1730FVC-TR环境光传感器Datasheet
float Tint = 2.8;//默认的Tint,可参考BH1730FVC-TR环境光传感器Datasheet
uint8_t ITIME = 0xda;//默认的ITIME,可参考BH1730FVC-TR环境光传感器Datasheet
float ITIME_ms = (Tint * 964 * (0xff - ITIME)) / 1000;//根据BH1730FVC-TR环境光传感器Datasheet计算得到积分时间
float Lx = 0;
//计算当前环境光的值,计算公式来自BH1730FVC-TR环境光传感器Datasheet第13面
if(d1/d0 < 0.26){
Lx = (1.290 * d0 - 2.733 * d1) / Gain * 102.6 / ITIME_ms;
}else if(d1/d0 < 0.55){
Lx = (0.795 * d0 - 0.859 * d1) / Gain * 102.6 / ITIME_ms;
}else if(d1/d0 < 1.09){
Lx = (0.510 * d0 - 0.345 * d1) / Gain * 102.6 / ITIME_ms;
}else if(d1/d0 < 2.13){
Lx = (0.276 * d0 - 0.130 * d1) / Gain * 102.6 / ITIME_ms;
}else{
Lx = 0;
}
return (uint16_t)Lx;
}
int convert2pwm(uint16_t lum){
/*
* @brief 根据人眼对亮度的感知曲线,应用伽马修正得到对应的操作屏幕背光灯的pwm波的占空比
* @param <uint16_t>环境光强度
* @return <int>占空比
*/
double b = pow((lum/180.0), (1/2.2));//伽马修正计算
if(b > 1){
b = 1;
}else if(b < 0.01){
b = 0.01;
}
return ceil(b * 100);
}
⑤读取环境光传感器的信息
uint16_t read(void){
/*
* @brief 打开环境光传感器并读取一次当前的环境光的值,单位为lux
* @param 无
* @return 当前检测到的环境光的值
*/
i2c_start();
i2c_write((0x29 << 1) | 0);//发送从机地址+写标志位
i2c_write(0x80);//发送要写入的寄存器
i2c_write(0x03);//发送要写入的命令
i2c_stop();
vTaskDelay(pdMS_TO_TICKS(1000));//使用FreeRTOS提供的延时便于任务调度
i2c_start();
i2c_write((0x29 << 1) | 0);//发送从机地址+写标志位
i2c_write(0x94);//发送接下来要读取的寄存器
i2c_stop();
vTaskDelay(pdMS_TO_TICKS(1));//使用FreeRTOS提供的延时便于任务调度
i2c_start();
i2c_write((0x29 << 1) | 1);//发送从机地址+读标志位
uint8_t getVal[4];
int icc = 0;
for(uint8_t regAddr=0x94; regAddr<=0x97; regAddr++){
uint8_t ans = i2c_read(regAddr != 0x97);
printf("Register 0x%02X : 0x%02X \n", regAddr, ans);
getVal[icc] = ans;
icc++;
}
//对读取到的数值实现处理与计算
uint16_t d0 = (((uint16_t)getVal[1] << 8) | getVal[0]);
uint16_t d1 = (((uint16_t)getVal[3] << 8) | getVal[2]);
uint16_t cal = calculate(d0, d1);
printf("Luminance : %d \n", cal);
i2c_stop();
vTaskDelay(pdMS_TO_TICKS(1));//使用FreeRTOS提供的延时便于任务调度
return cal;
}
4.主程序(MIMXRT1021_Project.c)
①定义公有变量实现数据同步
int pwm = 100;//公有变量,记录屏幕的占空比
uint16_t lux = 180;//公有变量,记录当前环境光的值
②定义FreeRTOS相关任务
void T_Read(void *pvParameters){
/*
* @brief 读取环境光传感器任务
*/
while(1){
lux = read();//读取环境光传感器数值并同步到公有变量lux
pwm = convert2pwm(lux);//根据伽马修正计算得对应的控制屏幕背光灯亮度的pwm占空比数值并同步到公有变量pwm
vTaskDelay(pdMS_TO_TICKS(100));//延迟确保任务能被正确调度
}
}
void T_Luminance(void *pvParameters){
/*
* @brief 设置屏幕亮度任务
*/
while(1){
ui_luminance(pwm);//根据公有变量pwm设置屏幕亮度
vTaskDelay(pdMS_TO_TICKS(1));//延迟1ms确保任务能被正确调度
}
}
void T_UpdateUI(void *pvParameters){
/*
* @brief 更新ui任务
*/
while(1){
char str[6];
char str_pwm[4];
//将数值转换为文本以供ui显示
snprintf(str, sizeof(str), "%5u", lux);
snprintf(str_pwm, sizeof(str_pwm), "%3u", pwm);
//定义转换后的文本组件
Text txtF = ui_label((char*)&str, S_BLUE, S_BLACK, true);
Text txtG = ui_label((char*)&str_pwm, S_BLUE, S_BLACK, true);
//显示文本
ui_displayText(&txtF, 66, 145, true);
vTaskDelay(pdMS_TO_TICKS(1));//延迟1ms确保任务能被正确调度
ui_displayText(&txtG, 90, 153, true);
vTaskDelay(pdMS_TO_TICKS(1));//延迟1ms确保任务能被正确调度
}
}
③主函数
注:这里使用的图片为用自定义python脚本完成格式转换的图片,使用const uint16_t[]存储,图片名为img0,定义于images.h
int main(void) {
/* Init board hardware. */
BOARD_ConfigMPU();
BOARD_InitBootPins();
BOARD_InitBootClocks();
BOARD_InitBootPeripherals();
#ifndef BOARD_INIT_DEBUG_CONSOLE_PERIPHERAL
/* Init FSL debug console. */
BOARD_InitDebugConsole();
#endif
ui_init();//初始化ui
//定义要显示的图片与文字
Image imgA = ui_image((uint16_t *)&img0, 128, 160);
Text txtA = ui_label(" By Mr.Wolf ", S_YELLOW, S_BLACK, true);
Text txtB = ui_label("-Luminance Test-", S_PURPLE, S_BLACK, true);
Text txtC = ui_label("===Statistics===", S_RED, S_BLACK, true);
Text txtD = ui_label("+Lux: lx", S_GREEN, S_BLACK, true);
Text txtE = ui_label("+Scr Light: %", S_GREEN, S_BLACK, true);
//将图片和文字显示在屏幕上
ui_displayImage(&imgA, 1, 1);
ui_displayText(&txtA, 2, 121, false);
ui_displayText(&txtB, 2, 129, false);
ui_displayText(&txtC, 2, 137, false);
ui_displayText(&txtD, 2, 145, false);
ui_displayText(&txtE, 2, 153, false);
xMutex = xSemaphoreCreateMutex();
//设置FreeRTOS要运行的任务
xTaskCreate(T_Read, "T_Read", configMINIMAL_STACK_SIZE, (void *)xMutex, tskIDLE_PRIORITY + 1, NULL);//读取环境光传感器的任务
xTaskCreate(T_Luminance, "T_Luminance", configMINIMAL_STACK_SIZE, (void *)xMutex, tskIDLE_PRIORITY + 1, NULL);//设置屏幕亮度的任务
xTaskCreate(T_UpdateUI, "T_UpdateUI", configMINIMAL_STACK_SIZE, (void *)xMutex, tskIDLE_PRIORITY + 1, NULL);//更新ui的任务
vTaskStartScheduler();//启动FreeRTOS的任务调度
while(1) {
light_time(RED, 500);
Sleep(500);
}
return 0 ;
}
5.用于完成图片转换的python3脚本
编写的python脚本,可以转换同级的子目录./image/下的所有jpg和png图片并将图像名作为数组名,生成images.h头文件供工程调用。与Image2Lcd等工具不同,此脚本可以直接生成格式为const uint16_t[]的数组。
import os
from PIL import Image
"""
这里可以转换同级的子目录./image/下的所有jpg和png图片并将图像名作为数组名,生成images.h头文件供工程调用
"""
# 子目录/image路径
image_dir = os.path.join(os.path.dirname(__file__), 'image')
# 输出的头文件名
header_file = 'images.h'
def convert_to_bgr565(image):
# 调整图片尺寸
resized_image = image.resize((128, 160))#这里可以手工指定为其他尺寸
# 转换为bgr565格式
bgr565_data = []
pixels = resized_image.load()
for y in range(resized_image.height):
for x in range(resized_image.width):
r, g, b = pixels[x, y]
bgr565 = ((b & 0xF8) << 8) | ((g & 0xFC) << 3) | (r >> 3)
bgr565_data.append(bgr565)
return bgr565_data
# 遍历子目录/image下的图片
images = []
for filename in os.listdir(image_dir):
if filename.endswith('.png') or filename.endswith('.jpg'):
# 读取图片
image_path = os.path.join(image_dir, filename)
image = Image.open(image_path)
# 图片转换为bgr565格式
bgr565_data = convert_to_bgr565(image)
# 添加到图片列表中
images.append((filename, bgr565_data))
# 生成头文件
with open(header_file, 'w') as f:
f.write('#ifndef IMAGES_H_\n')
f.write('#define IMAGES_H_\n\n')
f.write('#include <stdio.h>\n')
f.write('#include "board.h"\n')
f.write('#include "peripherals.h"\n')
f.write('#include "pin_mux.h"\n')
f.write('#include "clock_config.h"\n')
f.write('#include "MIMXRT1021.h"\n')
f.write('#include "fsl_debug_console.h"\n\n')
for filename, bgr565_data in images:
# 图片名作为变量名
var_name = os.path.splitext(filename)[0]
var_name = var_name.replace('-', '_').replace('.', '_')
# 写入图片数组
f.write('const uint16_t {}[] = {{\n'.format(var_name))
for data in bgr565_data:
f.write(' 0x{:04X},\n'.format(data))
f.write('};\n\n')
f.write('#endif // IMAGES_H_\n')
print('头文件生成完成:{}'.format(header_file))
八、遇到的主要困难及解决方法
1.硬件实现的lpi2c的api出现问题
困难描述:
由于某些问题,本人的开发板的lpi2c通讯所提供的api无法正常工作——使用中断模式无法收到任何消息,使用轮询模式则无法发送停止信号(Stop)。
解决方法:
使用软件模拟i2c通信的时序,通过拉低拉高对应的GPIO引脚电平来发送信息;通过GPIO2->GDIR &= ~(1UL << 3)改变引脚的方向为输入通过读取引脚的电平进而得到数据线的信息;通过循环读取电平的上升沿/下降沿来确定数据位是1还是0。
2.频繁改变GPIO方向造成的潜在死机风险
困难描述:
由于使用软件模拟i2c通讯,需要频繁改变GPIO的输入输出方向,造成系统存在潜在的不定时死机问题。
解决方法(临时):
按开发板的核心板上的RESET按键进行软件复位即可重新正常工作。
九、未来的计划
该项目已经成功实现了亮度测量与控制的功能,并达到了预期指标。然而还有许多可以提升与扩展的地方:
①排查并解决硬件lpi2c通讯的问题,实现用硬件lpi2c实现的传感器控制
②实现旋钮控制的自动屏幕亮度调节的开与关和手动辅助亮度调节
③实现靠eFlexPWM实现的pwm以更好的控制屏幕亮度