- 2025-12-20
-
发表了主题帖:
【2025 DigiKey 创意大赛】恒温茶杯垫-项目作品提交
本帖最后由 yin_wu_qing 于 2025-12-20 23:59 编辑
一、作品简介
本作品使用DigiKey平台上购得的NXP MIMXRT1020-EVK与自备的外设扩展板、触摸按键板、蜂鸣器,完成了预期的恒温控制功能。该恒温茶杯垫温度可调范围:20℃~80℃,调温步进5℃。使用PID控制PWM的占空比输出,从而控制加热电路中的MOS管导通与截止。温度传感器NSHT30采用硬件IIC驱动,SPI LCD彩屏显示目标温度设定值与当前采集的温度值,加热过程曲线呈现进度。支持随时加减键调节目标温度设定值,加热键控制开启与关闭加热,且伴随声光响应。加热过程中,RGB指示灯亮红色,当温度靠近目标设定值2.8℃,则亮绿灯,表明加热已接近目标设定值或当前温度大于目标设定值。
二、系统框图
系统框图如下图:
秉承“智控万物,改变生活”的主题,让科技真正服务于生活的宗旨,设计源于小家电应用,不依附于网络通讯条件、不依附于220V市电供应,实现可移动便携式恒温控制电器,例如我们常见的暖手宝。如上图的系统结构框图,利用图中的硬件资源,完成相关的功能设计,见下面逻辑处理流程图。
实现该功能的流程:
①、驱动SPI LCD彩屏,优先采用GPIO口模拟SPI方式驱动,一来使用官方参考代码移植、调试更方便,二来满足后续外设的IO口使用。
②、驱动NSHT30温度传感器,同样优先采用GPIO口模拟IIC通信方式驱动,如实时响应、采集精准度有待优化则改用硬件IIC通信方式驱动。
③、先调节PWM输出,使能一路PWM1,使其输出不同占空比,串口打印输出,调试期间可接入一个LED指示灯,让指示灯呈现呼吸灯效果。
④、创建三个按键的中断函数,设置不同标志位,以响应不同按键输入信号,按键输入做消抖处理。
⑤、引入PID算法控制PWM占空比输出,加上按键处理,设定温度可调范围为20℃~80℃,并开机默认处于关闭PWM输出状态。
⑥、完成上述后,再完善扩展板上的加热电路,从EVK引入5V电压,调试PID参数,以达到最佳恒温控制效果。
⑦、优化声光响应,RGB灯状态指示,LCD屏显示等人机交互信息输出。
风险说明:必须确保PWM输出占空比可控时,再引入5V电压,否则会导致加热电路中的MOS管一直处于导通状态,一直给扩展板上的电阻加热,可能会导致PCB烧毁或者NSHT30温湿度传感器损坏!
三、各部分功能说明
根据现有的硬件接口,MIMXRT1020-EVK开发板与外设扩展板、触摸按键板、蜂鸣器的硬件连接关系如下图:
1、LCD显示部分
该TFT-LCD液晶屏显示芯片ST7735S,使用GPIO口模拟SPI通讯方式驱动该显示屏,兼容性好,移植起来也相对简单。这里需要说明的是,由于硬件接口资源有限,最终作品上的硬件接口连接关系如上图所示,并非进度分享贴中的GPIO分布,比如LCD_RES的引脚换用了LPUART1_RX(GPIO_AD_B0_07)接口,只留出LPUART1_TX(GPIO_AD_B0_06)供串口打印调试即可。
该屏附带电阻式触摸功能,这里暂未引入。其相对于扩展板上的电路原理图如下:
LCD屏用来显示开机logo图片,以及恒温控制过程中的GUI信息、曲线图绘制等。
2、NSHT30温湿度传感器
驱动该温度传感器,前期的进度分享贴中使用GPIO口模拟IIC通讯方式驱动,为提高响应速度,后面换成了硬件IIC接口驱动。该传感器感应灵敏,采集温度精准、稳定。其相对于扩展板上对应的电路原理图如下:
开机则初始化NSHT30温湿度传感器,如硬件连接正常,则串口打印输出如下:
如IIC硬件接口连接异常,则开机后报异常,串口输出信息如下:
开机后正常获取当前环境温湿度值以整数形式展示,如下图所示:
温度传感器相关代码见如下:
int I2C_Master_Transmit(uint8_t I2c_ADDR, uint8_t *I2c_txBuff, uint16_t I2c_DATA_LENGTH)
{
status_t reVal = kStatus_Fail;
size_t txCount = 0xFFU;
if (kStatus_Success == LPI2C_MasterStart(EXAMPLE_I2C_MASTER, I2c_ADDR, kLPI2C_Write))
{
/* Check master tx FIFO empty or not */
LPI2C_MasterGetFifoCounts(EXAMPLE_I2C_MASTER, NULL, &txCount);
while (txCount)
{
LPI2C_MasterGetFifoCounts(EXAMPLE_I2C_MASTER, NULL, &txCount);
}
/* Check communicate with slave successful or not */
if (LPI2C_MasterGetStatusFlags(EXAMPLE_I2C_MASTER) & kLPI2C_MasterNackDetectFlag)
{
return kStatus_LPI2C_Nak;
}
/* subAddress = 0x01, data = g_master_txBuff - write to slave.
start + slaveaddress(w) + subAddress + length of data buffer + data buffer + stop*/
reVal = LPI2C_MasterSend(EXAMPLE_I2C_MASTER, (uint8_t *)I2c_txBuff, I2c_DATA_LENGTH);
if (reVal != kStatus_Success)
{
if (reVal == kStatus_LPI2C_Nak)
{
LPI2C_MasterStop(EXAMPLE_I2C_MASTER);
}
return -1;
}
LPI2C_MasterGetFifoCounts(EXAMPLE_I2C_MASTER, NULL, &txCount);
while (txCount)
{
LPI2C_MasterGetFifoCounts(EXAMPLE_I2C_MASTER, NULL, &txCount);
}
reVal = LPI2C_MasterStop(EXAMPLE_I2C_MASTER);
if (reVal != kStatus_Success)
{
PRINTF("Error: uuu,reval=%d\n", reVal);
return -1;
}
PRINTF("INFO: NSHT30 send messurement command success\n");
}
else
{
PRINTF("Error: NSHT30 send messurement command failure\n");
return -1;
}
return 0;
}
int I2C_Master_Receive(uint8_t I2c_ADDR_w, uint8_t I2c_ADDR_r, uint16_t RegAddr, uint8_t *I2c_rxBuff, uint16_t I2c_DATA_LENGTH)
{
status_t reVal = kStatus_Fail;
size_t txCount = 0xFFU;
if (kStatus_Success == LPI2C_MasterStart(EXAMPLE_I2C_MASTER, I2c_ADDR_w, kLPI2C_Write))
{
/* Check master tx FIFO empty or not */
LPI2C_MasterGetFifoCounts(EXAMPLE_I2C_MASTER, NULL, &txCount);
while (txCount)
{
LPI2C_MasterGetFifoCounts(EXAMPLE_I2C_MASTER, NULL, &txCount);
}
/* Check communicate with slave successful or not */
if (LPI2C_MasterGetStatusFlags(EXAMPLE_I2C_MASTER) & kLPI2C_MasterNackDetectFlag)
{
return kStatus_LPI2C_Nak;
}
uint8_t buf[2];
buf[0] = RegAddr >> 8;
buf[1] = RegAddr & 0xFF;
reVal = LPI2C_MasterSend(EXAMPLE_I2C_MASTER, (uint8_t *)buf, 2);
if (reVal != kStatus_Success)
{
if (reVal == kStatus_LPI2C_Nak)
{
LPI2C_MasterStop(EXAMPLE_I2C_MASTER);
}
return -1;
}
reVal = LPI2C_MasterStop(EXAMPLE_I2C_MASTER);
if (reVal != kStatus_Success)
{
PRINTF("Error:Stop,reval=%d\n", reVal);
return -1;
}
reVal = LPI2C_MasterStart(EXAMPLE_I2C_MASTER, I2c_ADDR_r, kLPI2C_Read);
if (reVal != kStatus_Success)
{
PRINTF("Start:ok\r\n");
return -1;
}
reVal = LPI2C_MasterReceive(EXAMPLE_I2C_MASTER, I2c_rxBuff, I2c_DATA_LENGTH);
if (reVal != kStatus_Success)
{
PRINTF("Error: NSHT30 get messurement command failure,reval=%d\n", reVal);
if (reVal == kStatus_LPI2C_Nak)
{
LPI2C_MasterStop(EXAMPLE_I2C_MASTER);
}
else if (reVal == kStatus_LPI2C_FifoError)
{
PRINTF("FIFO under run or overrun\n");
}
return -1;
}
reVal = LPI2C_MasterStop(EXAMPLE_I2C_MASTER);
if (reVal != kStatus_Success)
{
return -1;
}
}
else
{
PRINTF("Error: NSHT30 get messurement command fail\n");
return -1;
}
return 0;
}
bool I2C_ReadBuffer(uint8_t SalveAddr, uint16_t RegAddr, uint8_t *DateByte, uint32_t DataNum)
{
lpi2c_master_transfer_t transfer;
status_t err_flag;
/*
* @data :发送、接受的数据
* @datasize :发送的数据个数
* @direction :读写模式选择
* @flags :传输失败的标志位
* @slaveAaddress:从机地址
* @subaddress :寄存器/内存地址
* @subaddressSize:地址寄存器大小
*/
transfer.data = DateByte;
transfer.dataSize = DataNum;
transfer.direction = kLPI2C_Read;
transfer.flags = kLPI2C_TransferDefaultFlag;
transfer.slaveAddress = SalveAddr;
transfer.subaddress = RegAddr;
transfer.subaddressSize = 0x02;
err_flag = LPI2C_MasterTransferBlocking(LPI2C1, &transfer);
if (err_flag != kStatus_Success)
return false;
return true;
}
static int nsht30_send_cmd(NSHT30_CMD cmd)
{
uint8_t buf[2];
buf[0] = cmd >> 8;
buf[1] = cmd & 0xFF;
return I2C_Master_Transmit(NSHT30_ADDR_WR, (uint8_t *)buf, 2);
}
void NSHT30_Init(void)
{
PID_Init();
GPIO_PinWrite(GPIO1, 27, 0U);
Delay(5);
GPIO_PinWrite(GPIO1, 27, 1U);
Delay(5);
nsht30_send_cmd(CMD_MEAS_High);
}
static int nsht30_single_shot_measurement(uint8_t *buf, uint8_t buf_size)
{
uint16_t cmd = CMD_READ_Data;
if (!buf || buf_size < DATA_SIZE)
{
PRINTF("%s(): Invalid input argument\n", __func__);
return -1;
}
if (I2C_Master_Receive(NSHT30_ADDR, NSHT30_ADDR, cmd, buf, DATA_SIZE) == 0)
{
return 0;
}
else
{
PRINTF("Error: NSHT30 read measurement result fail\n");
Heat_flag = 0;
old_heat_status = 80;
return -1;
}
return 0;
}
static uint8_t nsht30_crc8(const uint8_t *data, int len)
{
const uint8_t POLYNOMIAL = 0x31;
uint8_t crc = 0xFF;
int i, j;
for (i = 0; i < len; ++i)
{
crc ^= *data++;
for (j = 0; j < 8; j++)
{
crc = (crc & 0x80) ? (crc << 1) ^ POLYNOMIAL : (crc << 1);
}
}
return crc;
}
int NSHT30_GetData(void)
{
uint8_t buf[DATA_SIZE];
int data;
uint16_t temp;
uint16_t humd;
uint16_t crc;
data = nsht30_single_shot_measurement(buf, DATA_SIZE);
if (data)
{
PRINTF("NSHT30 Single Short measurement data=%d\n", data);
return -2;
}
crc = nsht30_crc8(buf, 2);
if (crc != buf[2])
{
PRINTF("NSHT30 measurement temperature got CRC Error\n");
Heat_flag = 0;
old_heat_status = 80;
return -3;
}
crc = nsht30_crc8(&buf[3], 2);
if (crc != buf[5])
{
PRINTF("NSHT30 messurement temperature got CRC Error\n");
return -4;
}
temp = (buf[0] << 8) | buf[1];
humd = (buf[3] << 8) | buf[4];
temperature = -45 + 175 * ((float)temp / 65535.0f);
humidity = 100 * ((float)humd / 65535.0f);
return 0;
}
3、加热电路部分
该电路采用5V供电,因此正如视频中所介绍的,必须得从EVK板中引入5V电压,加热电路相对于扩展板中的电路原理图如下所示:
通过原理图可知,通过调节经过R19处的电压幅值从而控制Q2的导通与截止频率,进而实现对四颗功率电阻进行大功率加热控制。
4、PWM与PID控制
PWM初始化接口代码如下:
void PWM1_Init(void)
{
/* Initialize PWM submodule SM0 main configuration */
PWM_Init(BOARD_PWM_BASEADDR, BOARD_PWM_SUBMODULE, &PWM1_SM0_config);
/* Initialize fault input filter configuration */
PWM_SetupFaultInputFilter(BOARD_PWM_BASEADDR, &PWM1_faultInputFilter_config);
/* Initialize fault channel 0 fault Fault0 configuration */
PWM_SetupFaults(BOARD_PWM_BASEADDR, kPWM_Fault_0, &PWM1_Fault0_fault_config);
/* Initialize submodule SM0 channel A output disable mapping to the selected faults */
PWM_SetupFaultDisableMap(BOARD_PWM_BASEADDR, BOARD_PWM_SUBMODULE, BOARD_PWM_CHANNEL, kPWM_faultchannel_0, (kPWM_FaultDisable_0));
/* Initialize deadtime logic input for the channel A */
PWM_SetupForceSignal(BOARD_PWM_BASEADDR, BOARD_PWM_SUBMODULE, BOARD_PWM_CHANNEL, kPWM_UsePwm);
/* Setup PWM output setting for submodule SM0 */
PWM_SetupPwm(BOARD_PWM_BASEADDR, BOARD_PWM_SUBMODULE, PWM1_SM0_pwm_function_config, 1U, kPWM_SignedCenterAligned, PWM_SM0_COUNTER_FREQ_HZ, PWM_SRC_CLK_FREQ);
/* Initialize LDOK for update of the working registers */
PWM_SetPwmLdok(BOARD_PWM_BASEADDR, kPWM_Control_Module_0, true);
/* Start selected counters */
PWM_StartTimer(BOARD_PWM_BASEADDR, kPWM_Control_Module_0);
}
PID算法控制PWM占空比输出相关代码展示如下:
void Control_PWM(int16_t output)
{
if (Heat_flag == 1)
{
if (old_heat_status != Heat_flag)
{
PRINTF("Start Heat\n");
}
PWM_UpdatePwmDutycycle(BOARD_PWM_BASEADDR, BOARD_PWM_SUBMODULE, BOARD_PWM_CHANNEL, kPWM_SignedCenterAligned ,output);
PWM_SetPwmLdok(BOARD_PWM_BASEADDR, BOARD_PWM_CONTROL_SUBMODULE, true);
PRINTF("Coaster DutyCyclePercent: %d\n", output);
}
else
{
if (old_heat_status != Heat_flag)
{
PRINTF("Stop Heat\n");
}
PWM_UpdatePwmDutycycle(BOARD_PWM_BASEADDR, BOARD_PWM_SUBMODULE, BOARD_PWM_CHANNEL, kPWM_SignedCenterAligned ,0);
PWM_SetPwmLdok(BOARD_PWM_BASEADDR, BOARD_PWM_CONTROL_SUBMODULE, true);
}
if(old_heat_status!= Heat_flag){
old_heat_status = Heat_flag;
}
}
void PID_Init(void)
{
PID_Para.pid_set_temp = 40.0;
PID_Para.pid_err_last = 0;
PID_Para.pid_err_sum = 0;
}
void PID_Set_Target(uint32_t target)
{
if (target > 80)
{
target = 80;
}
else if (target < 20)
{
target = 20;
}
PID_Para.pid_set_temp = target;
}
#define Kp 90 // 比例常数 100倍值 90即0.9
#define Ki 98 // 积分常数 100倍值 90即0.9
#define Kd 120 // 微分常数 100倍值 90即0.9
#define I_MAX 4800
#define I_MIN 0
void PID_Calc(void)
{
int16_t P_Value, D_Value;
PID_Para.pid_err = PID_Para.pid_set_temp - PID_Para.pid_now_temp; // 当前误差
P_Value = PID_Para.pid_err * Kp; // 比例量
PID_Para.pid_err_sum += PID_Para.pid_err * Ki; // 积分量
if (PID_Para.pid_err_sum > I_MAX)
PID_Para.pid_err_sum = I_MAX; // 积分限幅
else if (PID_Para.pid_err_sum < I_MIN)
PID_Para.pid_err_sum = I_MIN;
D_Value = (PID_Para.pid_err - PID_Para.pid_err_last) * Kd; // 微分量
PID_Para.pid_err_last = PID_Para.pid_err;
PID_Para.pid_result = (P_Value + PID_Para.pid_err_sum + D_Value) / 100;
if (PID_Para.pid_result > 100)
PID_Para.pid_result = 100; // 范围 0-100
else if (PID_Para.pid_result < 0)
PID_Para.pid_result = 0;
if(PID_Para.pid_err < 2.8){
GREEN_LED_ON();
RED_LED_OFF();
}
else{
GREEN_LED_OFF();
RED_LED_ON();
}
}
void Control_Heating(void)
{
if(Heat_flag == 1)
{
PID_Para.pid_now_temp = ReadTemperature();
PID_Calc();
}
else{
PID_Para.pid_result = 0;
}
Control_PWM(PID_Para.pid_result); // 控制PWM输出
}
5、按键与RGB灯、蜂鸣器
为满足项目需求,这里使用了扩展板上的旋转编码按键开关与外置的触摸开关按键模块,与EVK板上的USER_BUTTON(SW4)构成按键组。扩展板上的旋转编码按键控制加热的开启与关闭;外置的触摸开关调节目标温度值的设定,EVK板上的SW4调节目标温度值的设定。按键程序中都有做消抖处理,但由于旋转编码按钮硬件结构上的差异,需引入另一个标志位,确保奇偶数次被按到点位后,改变加热状态。
其相对于扩展板上的按键与RGB灯的电路原理图如下:
EVK板上的USER_BUTTON硬件原理图如下:
加热键开启与关闭加热时,声光响应同步,且做了区分。加热过程中,RGB指示灯亮红色,当采集的温度与目标设定值低于2.8℃时,则亮绿灯,表明加热已接近目标设定值或当前温度大于目标设定值。
四、作品源码
工程源码是基于MDK平台的,纯单片机前后台控制程序,感兴趣的坛友搭建好基于RT1020的Keil开发环境后,下载该工程源码包,解压后直接编译,EVK板上连接相关硬件资源,即可验证。
工程名称:Constant_Temperature_Coaster
https://download.eeworld.com.cn/detail/yin_wu_qing/637847
五、作品功能演示
开机后系统默认处于关闭加热状态,默认目标温度设定值为40℃,通过外置触摸按键板与EVK板上的SW4按键,以步进5℃进行调节,可调范围为20℃~80℃,加热过程中的曲线绘制也是以20℃~80℃范围呈现。
https://training.eeworld.com.cn/video/43093
六、项目总结
先申明一下,本次项目实现的恒温控制功能相对比较简单,因此进度贴展示比较有限。通过该项目的调试,证明MIMXRT1020-EVK开发板即使只提供“Arduino Interface”接口,也是能满足当前项目硬件需求的。调试过程中遇到的SysTick定时器不能精确到1us,可能跟EVK板的硬件复用有关,正如板上的JTAG调试接口不能直接使用,需要该部分硬件电路。调试按键中断处理时,了解了硬件结构上的差异会带来的中断响应差异,加热按键的弹簧间隙比较大,因此除了做消抖处理外,实现奇偶数次被按到点位后,改变加热状态需引入另一个标志位比较稳妥。
虽然本次项目没有使用实时操作系统,也没有用LVGL的画图组件,更没有涉及网络通信这块,但其中使用PID调试PWM输出的这段经历让我受益匪浅。PID参数的调优,不仅适用于温度控制、电机调速,还能应用在水压控制,流量控制等工业控制场景,增量型PID和位置型PID都值得好好学习。
项目帖子分享链接汇总如下:
创意说明贴:【2025 DigiKey 创意大赛】基于MIMXRT1020-EVK的恒温茶杯垫-创意说明
https://bbs.eeworld.com.cn/thread-1337129-1-1.html
物料展示贴:【2025 DigiKey创意大赛】基于MIMXRT1020-EVK的恒温茶杯垫---开箱上电贴
https://bbs.eeworld.com.cn/thread-1329592-1-1.html
进度分享贴:【2025 DigiKey创意大赛】基于MIMXRT1020-EVK的恒温茶杯垫-环境搭建与呼吸灯
https://bbs.eeworld.com.cn/thread-1330818-1-1.html
【2025 DigiKey创意大赛】基于MIMXRT1020-EVK的恒温茶杯垫-驱动LCD彩屏
https://bbs.eeworld.com.cn/thread-1333994-1-1.html
【2025 DigiKey创意大赛】基于MIMXRT1020-EVK的恒温茶杯垫-驱动NSHT30温湿度传感
https://bbs.eeworld.com.cn/thread-1335152-1-1.html
问题讨论贴:【2025 DigiKey创意大赛】MIMXRT1020-EVK的SysTick定时器
https://bbs.eeworld.com.cn/thread-1335155-1-1.html
项目作品提交文档见如下附件:
-
上传了资料:
基于MIMXRT1020-EVK的恒温茶杯垫之工程源码
- 2025-12-18
-
发表了主题帖:
【2025 DigiKey 创意大赛】基于MIMXRT1020-EVK的恒温茶杯垫-创意说明
一、引言
市面上常见一些小而美的家电,别看功能不怎么复杂,但实用性挺强的。比如下图中的这款恒温加热器,功能简单,接上220V市电即可给杯子加热,但是不能显示当前温度,只用工作指示灯,当盛满牛奶的水杯放在上面,靠压力按住底盘下的按键,从而触发加热,实现恒温控制,水杯一旦挪开则关闭加热。基于该产品引发创意,供电可否换用成5V或12V供电,现在市面上都有智能快充充电宝,可满足大功率输出需求,这样可设计一款便携式茶杯垫、或者防水保温饭盒,户外垂钓也可自行携带饭盒,不依附220V市电供应。
二、简介
依照本次大赛“智控万物,改变生活”的主题,让科技真正服务于生活的宗旨,因此提交了一份使用MIMXRT1020-EVK板实现一个恒温茶杯垫的创意,出乎意料的是居然通过审核了。的确随着物联网的兴起,不少产品设计都依附于网络,由网络推动数据大模型,AI算法等应用,这些高端产品成本自然高,项目落地周期长,需要长期维护网络通讯安全。此次笔者使用MIMXRT1020-EVK板实现的恒温茶杯垫不依附网络控制,就单纯的一个移动小家电。该项目可衍生出暖手宝、保温饭盒、暖手套等实用性产品,功率大点,可设计成便携式烙铁。
项目中使用一路5V的供电,通过加热按键控制加热电路中的MOS管导通与截止。可控制温度范围在20℃~80℃,使用NSHT30温度传感器实时检测当前感应温度值,采用PWM+PID调节的方式,让加热的温度值与用户设定的目标值保持相对稳定,LCD彩屏显示目标温度设定值与当前采集的温度值,加热过程曲线呈现进度。有加减键调节目标温度设定值,操作过程声光响应。加热过程中指示灯亮红色,加热至靠近目标温度值,或者大于目标温度值时亮绿灯。
MIMXRT1020-EVK板引的GPIO口有限,主要应用基于网络端的产品设计开发,这里也有必要评估一下该板卡引出的GPIO是否满足创意需求。
三、功能介绍
项目主要需要实现的功能如下:
1、驱动SPI LCD彩屏显示,用来显示温度传感器感应的当前温度与用户设定的目标温度值,以及加热过程中的曲线展示。
2、驱动NSHT30温湿度传感器,使用硬件IIC接口通信,精准采集环境温湿度值。
3、采用5V供电加热电路,通过PID算法调节PWM的占空比输出,从而控制MOS管的导通与截止,让温度传感器感应的温度值与设定目标值逐渐靠近。
4、RGB灯与蜂鸣器组合成声光响应控件,调节目标温度值或者关闭加热时,亮蓝灯,且同时蜂鸣器“滴”一声;开启加热功能时,亮蓝灯,且同时蜂鸣器“滴滴”两声。
5、加热过程中亮红灯,加热至靠近目标设定值2℃时,指示灯亮绿灯;非加热状态则关闭指示灯。调温按键与加热开关按键均可独立控制。
四、系统结构框图
根据预期设定功能,总结出以下结构框图:
五、实现步骤
从得捷电子商城下单后拿到开发板,进行的项目实施步骤:
A、拿到开发板后,首先得自行焊接好“Arduino Interface”接口的插针或插座,以便后续使用杜邦线连接自备的加热扩展板。
B、驱动SPI LCD彩屏,优先采用GPIO口模拟SPI方式驱动,一来使用官方参考代码移植、调试更方便,二来满足后续外设的IO口使用。
C、驱动NSHT30温度传感器,同样优先采用GPIO口模拟IIC通信方式驱动,如实时响应、采集精准度有待优化则改用硬件IIC通信方式驱动。
D、先调节PWM输出,使能一路PWM1,使其输出不同占空比,串口打印输出,调试期间可接入一个LED指示灯,让指示灯呈现呼吸灯效果。
E、创建三个按键的中断函数,设置不同标志位,以响应不同按键输入信号,按键输入做消抖处理。
F、引入PID算法控制PWM占空比输出,加上按键处理,设定温度可调范围为20℃~80℃,并开机默认处于关闭PWM输出状态。
G、完成上述后,再完善扩展板上的加热电路,从EVK引入5V电压,调试PID参数,以达到最佳恒温控制效果。
H、优化声光响应,RGB灯状态指示,LCD屏显示等人机交互信息输出。
以上是项目的整体实施步骤,值得注意的是第G步骤,必须确保PWM输出占空比可控时,再引入5V电压,否则会导致加热电路中的MOS管一直处于导通状态,一直给板子加热,可能会导致PCB烧毁或者NSHT30温湿度传感器损坏!
- 2025-12-03
-
回复了主题帖:
【2025 DigiKey创意大赛】MIMXRT1020-EVK的SysTick定时器
Jacktang 发表于 2025-12-3 07:34
CPU 资源耗尽,和硬件无关,不需要修改硬件
不是吧,那我用第三方设计的R1021开发板,烧录同样的固件,怎么又能正常启动呢?
- 2025-12-02
-
发表了主题帖:
【2025 DigiKey创意大赛】MIMXRT1020-EVK的SysTick定时器
在驱动NSHT30温湿度传感器的调试过程中,发现在设置systick重载值以生成1us中断时,编译工程后,将代码烧写到EVK板子后,板子直接“挂”了,LCD屏没法点亮。然后debug逐步调试,发现打断点执行到LCD初始化部分,LCD屏是能点亮的。
原本设置重载值以生成1ms中断时,是可以正常点亮LCD屏的,由于驱动NSHT30温湿度传感器需要用到us级的延时,因此为更好得驱动NSHT30,需要设置systick重载值以生成1us中断。为此设置成2us中断,重新更新程序后,EVK板则能正常点亮LCD屏,且NSHT30温湿度传感器采样数据灵敏。
void SysTick_Handler(void)
{
if (g_systickCounter != 0U)
{
g_systickCounter--;
}
}
void SysTick_DelayTicks(uint32_t n)
{
g_systickCounter = n;
while (g_systickCounter != 0U)
{
}
}
... ...
... ...
/* Set systick reload value to generate 2us interrupt */
if (SysTick_Config(SystemCoreClock / 500000U))
{
while (1)
{
}
}
... ...
... ...
有点疑惑,原本EVK的板子应该具备很完善的功能接口,前面帖子提到的JTAG烧录口不能与DAP-Link调试接口共存就算了,现在的延时采用SysTick定时器的方式精确到1us也不能够?硬件不可能又需要做微改吧。有知道原因的坛友吗?欢迎留言讨论!
-
发表了主题帖:
【2025 DigiKey创意大赛】基于MIMXRT1020-EVK的恒温茶杯垫-驱动NSHT30温湿度传感
实现恒温控制,当然离不开温度传感器,日常生活中,温湿度传感器比较多,比如DS18B20、SHT30、DTH11等。今天来介绍纳芯微电子的NSHT30,该传感器灵敏度高,采用IIC通讯接口,支持2V~5.5V的宽电压供电,采用8pin的LGA封装。
一、简介NSHT30
首先来看看该传感器的管脚定义
该温湿度传感器的设备地址分配见如下图说明
这款传感器支持-40℃~125℃的温度范围,0~100%RH的湿度范围采集。IIC的时序图如下:
二、硬件连接
结合MIMXRT1020-EVK上的引出接口定义,在上期驱动LCD彩屏的基础上,更改下RST、BLK两脚的连线,腾出来的两个引脚做为温湿度传感的SCL、SDA。
三、代码编写
这里主要分享驱动NSHT30代码。
i2c_nsht30.h
#ifndef I2C_NSHT30_H
#define I2C_NSHT30_H
#include "board.h"
/* 端口声明 */
#define NSHT30_I2C_Scl_GPIO GPIO3
#define NSHT30_I2C_Scl_PIN (22U)
#define NSHT30_I2C_Sda_GPIO GPIO3
#define NSHT30_I2C_Sda_PIN (23U)
#define NSHT30_I2C_Scl_SET GPIO_PinWrite(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN,1)
#define NSHT30_I2C_Scl_CLR GPIO_PinWrite(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN,0)
#define NSHT30_I2C_Scl_READ GPIO_PinRead(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN)
#define NSHT30_I2C_Sda_SET GPIO_PinWrite(NSHT30_I2C_Sda_GPIO,NSHT30_I2C_Sda_PIN,1)
#define NSHT30_I2C_Sda_CLR GPIO_PinWrite(NSHT30_I2C_Sda_GPIO,NSHT30_I2C_Sda_PIN,0)
#define NSHT30_I2C_Sda_READ GPIO_PinRead(NSHT30_I2C_Sda_GPIO,NSHT30_I2C_Sda_PIN)
// SCL on port 3, bit 22
#define SCL_LOW() NSHT30_I2C_Scl_CLR // set SCL to low
#define SCL_OPEN() NSHT30_I2C_Scl_SET // set SCL to open-drain
#define SCL_READ NSHT30_I2C_Scl_READ // read SCL
// SDA on port 3, bit 23
#define SDA_LOW() NSHT30_I2C_Sda_CLR // set SDA to low
#define SDA_OPEN() NSHT30_I2C_Sda_SET // set SDA to open-drain
#define SDA_READ NSHT30_I2C_Sda_READ // read SDA
void I2c_Init(void);
void IIC_Start(void);
void IIC_Stop(void);
uint8_t IIC_Wait_Ack(void);
void IIC_Ack(void);
void IIC_NAck(void);
void IIC_Send_Byte(uint8_t txd);
uint8_t IIC_Read_Byte();
#endif
i2c_nsht30.c
#include "i2c_nsht30.h"
void SDA_OUT(void)
{
gpio_pin_config_t NSHT30_SDA_config = {
.direction = kGPIO_DigitalOutput,
.outputLogic = 0U,
.interruptMode = kGPIO_NoIntmode
};
GPIO_PinInit(NSHT30_I2C_Sda_GPIO, NSHT30_I2C_Sda_PIN, &NSHT30_SDA_config);
}
void SDA_IN(void)
{
gpio_pin_config_t NSHT30_SDA_config = {
.direction = kGPIO_DigitalInput,
.outputLogic = 0U,
.interruptMode = kGPIO_NoIntmode
};
GPIO_PinInit(NSHT30_I2C_Sda_GPIO, NSHT30_I2C_Sda_PIN, &NSHT30_SDA_config);
}
void I2c_Init(void)
{
gpio_pin_config_t NSHT30_SCL_config = {
.direction = kGPIO_DigitalOutput,
.outputLogic = 0U,
.interruptMode = kGPIO_NoIntmode
};
gpio_pin_config_t NSHT30_SDA_config = {
.direction = kGPIO_DigitalOutput,
.outputLogic = 0U,
.interruptMode = kGPIO_NoIntmode
};
GPIO_PinInit(NSHT30_I2C_Scl_GPIO, NSHT30_I2C_Scl_PIN, &NSHT30_SCL_config);
GPIO_PinInit(NSHT30_I2C_Sda_GPIO, NSHT30_I2C_Sda_PIN, &NSHT30_SDA_config);
GPIO_PinWrite(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN,1);
GPIO_PinWrite(NSHT30_I2C_Sda_GPIO,NSHT30_I2C_Sda_PIN,1);
}
void IIC_Start(void)
{
SDA_OUT();
GPIO_PinWrite(NSHT30_I2C_Sda_GPIO,NSHT30_I2C_Sda_PIN,1);
GPIO_PinWrite(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN,1);
SysTick_DelayTicks(1);
GPIO_PinWrite(NSHT30_I2C_Sda_GPIO,NSHT30_I2C_Sda_PIN,0);
SysTick_DelayTicks(1);
GPIO_PinWrite(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN,0);
}
void IIC_Stop(void)
{
SDA_OUT();
GPIO_PinWrite(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN,0);
GPIO_PinWrite(NSHT30_I2C_Sda_GPIO,NSHT30_I2C_Sda_PIN,0);
SysTick_DelayTicks(1);
GPIO_PinWrite(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN,1);
SysTick_DelayTicks(1);
GPIO_PinWrite(NSHT30_I2C_Sda_GPIO,NSHT30_I2C_Sda_PIN,1);
SysTick_DelayTicks(1);
}
uint8_t IIC_Wait_Ack(void)
{
uint32_t ucErrTime=0;
SDA_IN();
GPIO_PinWrite(NSHT30_I2C_Sda_GPIO,NSHT30_I2C_Sda_PIN,1);
SysTick_DelayTicks(1);
GPIO_PinWrite(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN,1);
SysTick_DelayTicks(1);
while(SDA_READ)
{
ucErrTime++;
if(ucErrTime>1000)
{
IIC_Stop();
return 1;
}
}
GPIO_PinWrite(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN,0);
SysTick_DelayTicks(2);
return 0;
}
void IIC_Ack(void)
{
SDA_OUT();
GPIO_PinWrite(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN,0);
SysTick_DelayTicks(2);
GPIO_PinWrite(NSHT30_I2C_Sda_GPIO,NSHT30_I2C_Sda_PIN,0);
SysTick_DelayTicks(2);
GPIO_PinWrite(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN,1);
SysTick_DelayTicks(2);
GPIO_PinWrite(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN,0);
SysTick_DelayTicks(2);
GPIO_PinWrite(NSHT30_I2C_Sda_GPIO,NSHT30_I2C_Sda_PIN,1);
}
void IIC_NAck(void)
{
SDA_OUT();
GPIO_PinWrite(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN,0);
SysTick_DelayTicks(2);
GPIO_PinWrite(NSHT30_I2C_Sda_GPIO,NSHT30_I2C_Sda_PIN,1);
SysTick_DelayTicks(2);
GPIO_PinWrite(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN,1);
SysTick_DelayTicks(2);
GPIO_PinWrite(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN,0);
SysTick_DelayTicks(2);
}
void IIC_Send_Byte(uint8_t txd)
{
uint8_t t;
SDA_OUT();
GPIO_PinWrite(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN,0);
for(t=0;t<8;t++)
{
GPIO_PinWrite(NSHT30_I2C_Sda_GPIO,NSHT30_I2C_Sda_PIN,(txd&0x80)>>7);
txd<<=1;
SysTick_DelayTicks(2);
GPIO_PinWrite(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN,1);
SysTick_DelayTicks(2);
GPIO_PinWrite(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN,0);
SysTick_DelayTicks(2);
}
}
uint8_t IIC_Read_Byte()
{
unsigned char i,receive=0;
SDA_IN();
for(i=0;i<8;i++ )
{
GPIO_PinWrite(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN,0);
SysTick_DelayTicks(2);
GPIO_PinWrite(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN,1);
receive<<=1;
if(SDA_READ)
receive++;
SysTick_DelayTicks(2);
}
GPIO_PinWrite(NSHT30_I2C_Scl_GPIO,NSHT30_I2C_Scl_PIN,0);
return receive;
}
nsht30.h
#ifndef NSHT30_H
#define NSHT30_H
#include "board.h"
#include "pin_mux.h"
#include "i2c_nsht30.h"
// Measurement Repeatability
typedef enum{
REPEATAB_HIGH, // high repeatability
REPEATAB_MEDIUM, // medium repeatability
REPEATAB_LOW, // low repeatability
}etRepeatability;
// Measurement Mode
typedef enum{
MODE_CLKSTRETCH, // clock stretching
MODE_POLLING, // polling
}etMode;
typedef enum{
FREQUENCY_HZ5, // 0.5 measurements per seconds
FREQUENCY_1HZ, // 1.0 measurements per seconds
FREQUENCY_2HZ, // 2.0 measurements per seconds
FREQUENCY_4HZ, // 4.0 measurements per seconds
FREQUENCY_10HZ, // 10.0 measurements per seconds
}etFrequency;
void NSHT30_set_periodic(uint8_t addr, uint16_t cmd);
void NSHT30_read_raw_periodic(uint8_t addr,uint8_t *buff);
void NSHT30_read_raw_single(uint8_t addr, uint16_t cmd, uint8_t *buff);
int NSHT30_crc_check(uint8_t *data, uint8_t len, uint8_t checksum);
HAL_StatusTypeDef read_temp_rh_1ch(uint8_t addr, double *pout);
#endif
nsht30.c
#include "nsht30.h"
#define CMD_MEAS_SINGLE_H 0x2400 // measurement: SINGLE Mode high repeatability
#define CMD_MEAS_SINGLE_M 0x240B // measurement: SINGLE Mode medium repeatability
#define CMD_MEAS_SINGLE_L 0x2416 // measurement: SINGLE Mode low repeatability
#define CMD_MEAS_PERI_05_H 0x2032 // measurement: periodic Mode 0.5 mps high repeatability
#define CMD_MEAS_PERI_05_M 0x2024 // measurement: periodic Mode 0.5 mps medium repeat-ability
#define CMD_MEAS_PERI_05_L 0x202F // measurement: periodic Mode 0.5 mps low repeatability
#define CMD_MEAS_PERI_1_H 0x2130 // measurement: periodic Mode 1 mps high repeatability
#define CMD_MEAS_PERI_1_M 0x2126 // measurement: periodic Mode 1 mps medium repeatability
#define CMD_MEAS_PERI_1_L 0x212D // measurement: periodic Mode 1 mps low repeatability
#define CMD_MEAS_PERI_2_H 0x2236 // measurement: periodic Mode 2 mps high repeatability
#define CMD_MEAS_PERI_2_M 0x2220 // measurement: periodic Mode 2 mps medium repeatability
#define CMD_MEAS_PERI_2_L 0x222B // measurement: periodic Mode 2 mps low repeatability
#define CMD_MEAS_PERI_4_H 0x2334 // measurement: periodic Mode 4 mps high repeatability
#define CMD_MEAS_PERI_4_M 0x2322 // measurement: periodic Mode 4 mps medium repeatability
#define CMD_MEAS_PERI_4_L 0x2329 // measurement: periodic Mode 4 mps low repeatability
#define CMD_MEAS_PERI_10_H 0x2737 // measurement: periodic Mode 10 mps high repeatability
#define CMD_MEAS_PERI_10_M 0x2721 // measurement: periodic Mode 10 mps medium repeat-ability
#define CMD_MEAS_PERI_10_L 0x272A // measurement: periodic Mode 10 mps low repeatability
void NSHT30_set_periodic(uint8_t addr, uint16_t cmd)
{
IIC_Start();
IIC_Send_Byte(addr<<1);
IIC_Wait_Ack();
IIC_Send_Byte((cmd & 0xFF00) >> 8);
IIC_Wait_Ack();
IIC_Send_Byte(cmd & 0xFF);
IIC_Wait_Ack();
IIC_Stop();
}
//periodic mode
void NSHT30_read_raw_periodic(uint8_t addr,uint8_t *buff)
{
IIC_Start();
IIC_Send_Byte(addr<<1);
IIC_Wait_Ack();
IIC_Send_Byte(0xE0); //Read cmd 0xE000
IIC_Wait_Ack();
IIC_Send_Byte(0x00);
IIC_Wait_Ack();
SysTick_DelayTicks(5000);
IIC_Start();
IIC_Send_Byte((addr<<1)+1);
if(IIC_Wait_Ack()==0)
{
buff[0]=IIC_Read_Byte();
IIC_Ack();
buff[1]=IIC_Read_Byte();
IIC_Ack();
buff[2]=IIC_Read_Byte();
IIC_Ack();
buff[3]=IIC_Read_Byte();
IIC_Ack();
buff[4]=IIC_Read_Byte();
IIC_Ack();
buff[5]=IIC_Read_Byte();
IIC_NAck();
IIC_Stop();
}
}
//Single mode
void NSHT30_read_raw_single(uint8_t addr, uint16_t cmd, uint8_t *buff)
{
IIC_Start();
IIC_Send_Byte(addr<<1);
IIC_Wait_Ack();
IIC_Send_Byte((cmd & 0xFF00) >> 8);
IIC_Wait_Ack();
IIC_Send_Byte(cmd & 0xFF);
IIC_Wait_Ack();
IIC_Stop();
SysTick_DelayTicks(5000);
IIC_Start();
IIC_Send_Byte((addr<<1)+1);
if(IIC_Wait_Ack()==0)
{
buff[0]=IIC_Read_Byte();
IIC_Ack();
buff[1]=IIC_Read_Byte();
IIC_Ack();
buff[2]=IIC_Read_Byte();
IIC_Ack();
buff[3]=IIC_Read_Byte();
IIC_Ack();
buff[4]=IIC_Read_Byte();
IIC_Ack();
buff[5]=IIC_Read_Byte();
IIC_NAck();
IIC_Stop();
}
}
//CRC calculation
int NSHT30_crc_check(uint8_t *data, uint8_t len, uint8_t checksum)
{
uint8_t crc = 0xFF, bit;
uint8_t byteCtr;
//calculates 8-Bit checksum with given polynomial
for (byteCtr = 0; byteCtr < len; ++byteCtr)
{
crc ^= (data[byteCtr]);
for (bit = 8; bit > 0; --bit)
{
if (crc & 0x80)
crc = (crc << 1) ^ 0x131;
else
crc = (crc << 1);
}
}
if (crc == checksum)
return 1;
else
return 0;
}
// Reading and calculating temperature and humidity
HAL_StatusTypeDef read_temp_rh_1ch(uint8_t addr, double *pout)
{
uint8_t dat[6];
uint16_t tem,hum;
NSHT30_read_raw_single(addr,CMD_MEAS_SINGLE_L,dat); // single mode
tem = ((uint16_t)dat[0]<<8) | dat[1];
hum = ((uint16_t)dat[3]<<8) | dat[4];
if((NSHT30_crc_check(dat,2,dat[2])) && (NSHT30_crc_check(dat+3,2,dat[5])))
{
pout[0]= (175.0*(double)tem/65535.0-45.0) ; // T = -45 + 175 * tem / (2^16-1)
pout[1]= (100.0*(double)hum/65535.0); // RH = hum*100 / (2^16-1)
return HAL_OK;
}
else
{
return HAL_ERROR;
}
}
主函数调用接口如下:
#include "fsl_debug_console.h"
#include "board.h"
#include "app.h"
#include "fsl_pwm.h"
#include "lcd_init.h"
#include "lcd.h"
#include "pic.h"
#include "nsht30.h"
#include "i2c_nsht30.h"
/*******************************************************************************
* Variables
******************************************************************************/
volatile uint32_t g_systickCounter;
/* The PIN status */
volatile bool g_pinSet = false;
uint32_t pwmVal = 0;
uint32_t var = 0;
static char Target_Temp = 55;
static char Curr_Temp = 0;
typedef struct
{
unsigned char Index[2];
unsigned char Msk[32];
}typFNT_GB16;
extern const typFNT_GB16 tfont16[];
typedef struct
{
unsigned char Index[2];
unsigned char Msk[24];
}typFNT_GB12;
extern const typFNT_GB12 tfont12[];
/*******************************************************************************
* Code
******************************************************************************/
void SysTick_Handler(void)
{
if (g_systickCounter != 0U)
{
g_systickCounter--;
}
}
void SysTick_DelayTicks(uint32_t n)
{
g_systickCounter = n;
while (g_systickCounter != 0U)
{
}
}
void Display_text(void)
{
LCD_ShowIntNum(8,10,2025,4,BLUE,GREEN,16);
LCD_ShowString(44,10,(uint8_t *)"DigiKey",BLUE,GREEN,16,0);
LCD_ShowChinese(8,30,(uint8_t *)&tfont16[0],BLUE,GREEN,16,0); //创
LCD_ShowChinese(24,30,(uint8_t *)&tfont16[1],BLUE,GREEN,16,0); //意
LCD_ShowChinese(40,30,(uint8_t *)&tfont16[2],BLUE,GREEN,16,0); //大
LCD_ShowChinese(56,30,(uint8_t *)&tfont16[3],BLUE,GREEN,16,0); //赛
LCD_ShowChinese(8,50,(uint8_t *)&tfont16[4],BLUE,GREEN,16,0); //基
LCD_ShowChinese(24,50,(uint8_t *)&tfont16[5],BLUE,GREEN,16,0); //于
LCD_ShowString(8,70,(uint8_t *)"MIMXRT1020-EVK",BLACK,GREEN,16,0);
LCD_ShowChinese(8,90,(uint8_t *)&tfont16[6],RED,GREEN,16,0); //恒
LCD_ShowChinese(24,90,(uint8_t *)&tfont16[7],RED,GREEN,16,0); //温
LCD_ShowChinese(40,90,(uint8_t *)&tfont16[8],RED,GREEN,16,0); //茶
LCD_ShowChinese(56,90,(uint8_t *)&tfont16[9],RED,GREEN,16,0); //杯
LCD_ShowChinese(72,90,(uint8_t *)&tfont16[10],RED,GREEN,16,0); //垫
LCD_ShowString(43,140,(uint8_t *)"2025-12-02",BLUE,GREEN,16,0);
SysTick_DelayTicks(3000000U); // 6s
}
int main(void)
{
double rth[2];
uint8_t addr = 0x44;
uint8_t ret = 0U;
/* Board pin init */
BOARD_InitHardware();
/* Set systick reload value to generate 2us interrupt */
if (SysTick_Config(SystemCoreClock / 500000U))
{
while (1)
{
}
}
I2c_Init();
LCD_Init();
SysTick_DelayTicks(100000U);
LCD_Fill(0,0,LCD_W,LCD_H,WHITE);
LCD_ShowPicture(0,0,128,160,gImage_1);
SysTick_DelayTicks(1500000U); // 3s
LCD_Fill(0,0,LCD_W,LCD_H,YELLOW);
if(read_temp_rh_1ch(addr,rth) == HAL_OK)
{
LCD_ShowChinese(8,112,(uint8_t *)&tfont12[18],RED,WHITE,12,0); //初
LCD_ShowChinese(20,112,(uint8_t *)&tfont12[19],RED,WHITE,12,0); //始
LCD_ShowChinese(32,112,(uint8_t *)&tfont12[7],RED,WHITE,12,0); //温
LCD_ShowChinese(44,112,(uint8_t *)&tfont12[10],RED,WHITE,12,0); //湿
LCD_ShowChinese(56,112,(uint8_t *)&tfont12[8],RED,WHITE,12,0); //度
LCD_ShowChinese(68,112,(uint8_t *)&tfont12[20],RED,WHITE,12,0); //值
LCD_ShowChinese(80,112,(uint8_t *)&tfont12[9],RED,WHITE,12,0); //:
LCD_ShowIntNum(56,128,rth[0],2,BLACK,WHITE,12);
LCD_ShowChinese(68,128,(uint8_t *)&tfont12[11],BLACK,WHITE,12,0);
LCD_ShowIntNum(86,128,rth[1],2,BLACK,WHITE,12);
LCD_ShowString(98,128,(uint8_t *)"%RH",BLACK,WHITE,12,0);
}
else
{
PRINTF("crc error\r\n");
}
Display_text();
LCD_Fill(8,50,128,140,YELLOW);
while (1)
{
if(read_temp_rh_1ch(addr,rth) == HAL_OK){
Curr_Temp = rth[0];
}
else{
PRINTF("crc error\r\n");
}
LCD_ShowChinese(8,50,(uint8_t *)&tfont12[21],RED,WHITE,12,0); //目
LCD_ShowChinese(20,50,(uint8_t *)&tfont12[22],RED,WHITE,12,0); //标
LCD_ShowChinese(32,50,(uint8_t *)&tfont12[7],RED,WHITE,12,0); //温
LCD_ShowChinese(44,50,(uint8_t *)&tfont12[8],RED,WHITE,12,0); //度
LCD_ShowChinese(56,50,(uint8_t *)&tfont12[20],RED,WHITE,12,0); //值
LCD_ShowChinese(68,50,(uint8_t *)&tfont12[9],RED,WHITE,12,0); //:
LCD_ShowString(34,70,(uint8_t *)"Target:",RED,WHITE,12,0);
LCD_ShowIntNum(80,70,(u16)Target_Temp,2,RED,WHITE,12);
LCD_ShowChinese(94,70,(uint8_t *)&tfont12[11],RED,WHITE,12,0);
LCD_ShowChinese(8,90,(uint8_t *)&tfont12[5],BLUE,WHITE,12,0); //当
LCD_ShowChinese(20,90,(uint8_t *)&tfont12[6],BLUE,WHITE,12,0); //前
LCD_ShowChinese(32,90,(uint8_t *)&tfont12[7],BLUE,WHITE,12,0); //温
LCD_ShowChinese(44,90,(uint8_t *)&tfont12[8],BLUE,WHITE,12,0); //度
LCD_ShowChinese(56,90,(uint8_t *)&tfont12[20],BLUE,WHITE,12,0); //值
LCD_ShowChinese(68,90,(uint8_t *)&tfont12[9],BLUE,WHITE,12,0); //:
LCD_ShowString(28,110,(uint8_t *)"Current:",BLUE,WHITE,12,0);
LCD_ShowIntNum(80,110,(u16)Curr_Temp,2,BLUE,WHITE,12);
LCD_ShowChinese(94,110,(uint8_t *)&tfont12[11],BLUE,WHITE,12,0);
}
}
四、温度采集效果
代码编译后,将程序下载到EVK板子,首先显示温湿度传感器采集周围环境的初始状态值,然后用手指紧握NSHT30温湿度传感器,温度迅速升高;挪开手指,温度值缓慢降低,逐渐恢复到室内温度。
[localvideo]8a8a467700d41079075df971f76a8527[/localvideo]
- 2025-11-24
-
发表了主题帖:
【2025 DigiKey创意大赛】基于MIMXRT1020-EVK的恒温茶杯垫-驱动LCD彩屏
前段时间一直比较忙,帖子没来得及更新,今天周末来冒个泡,更新一下进度。分享一下使用MIMXRT1020-EVK驱动1.8寸彩色液晶显示屏。
该屏支持的分辨率为128X160,显示屏接口类型为8位并联接口,屏所采用的驱动芯片为台湾矽创的ST7735S。
一、硬件接口
同样基于上期环境搭建与呼吸灯贴的硬件扩展板,使用了6个GPIO做为信号输出控制脚,外加给扩展板供3.3V的电源,因此需使用到8根杜邦线。综上分析,在原有的呼吸灯功能不变动的基础上,引入6根杜邦线与扩展板上硬件定义接口正确相连,才能正常驱动LCD屏。这款屏支持电阻式触摸功能,这里暂且不考虑触摸接口。
LCD屏的信号脚与MIMXRT1020-EVK板的GPIO口对应关系如下:
LCD_SCLK --- GPIO1_10
LCD_CS --- GPIO1_11
LCD_MOSI --- GPIO1_12
LCD_DC --- GPIO1_15
LCD_RES --- GPIO3_22
LCD_BLK --- GPIO3_23
二、原理图
1、首先是扩展板在原MCU板上的IO定义分布如下:
根据上述的管脚定义,因此可将这使用到的6个信号管脚通过美纹胶标识出来,方便后续连线。
2、再通过MIMXRT1020-EVK板的原理图,找出在J19排针上所对应的信号引脚,官方给出的原理图如下:
三、代码编辑
基于上期的呼吸灯效工程上添加LCD驱动代码,GPIO管脚复用初始化函数如下:
void BOARD_InitPins(void) {
CLOCK_EnableClock(kCLOCK_Iomuxc);
/* GPIO configuration of USER_LED on GPIO_AD_B0_05 (pin 106) */
gpio_pin_config_t USER_LED_config = {
.direction = kGPIO_DigitalOutput,
.outputLogic = 0U,
.interruptMode = kGPIO_NoIntmode
};
/* Initialize GPIO functionality on GPIO_AD_B0_05 (pin 106) */
GPIO_PinInit(GPIO1, 5U, &USER_LED_config);
GPIO_PinInit(GPIO1, 15U, &USER_LED_config);
GPIO_PinInit(GPIO1, 11U, &USER_LED_config);
GPIO_PinInit(GPIO1, 12U, &USER_LED_config);
GPIO_PinInit(GPIO1, 10U, &USER_LED_config);
GPIO_PinInit(GPIO3, 23U, &USER_LED_config);
GPIO_PinInit(GPIO3, 22U, &USER_LED_config);
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B0_05_GPIO1_IO05, 0U);
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B0_11_ARM_CM7_TRACE_SWO, 0U);
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B0_06_LPUART1_TX, 0U);
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B0_07_LPUART1_RX, 0U);
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_06_FLEXPWM1_PWMA00, 0U);
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B0_15_GPIO1_IO15, 0U); // DC
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B0_11_GPIO1_IO11, 0U); // CS
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B0_12_GPIO1_IO12, 0U); // MOSI
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B0_10_GPIO1_IO10, 0U); // SCLK
IOMUXC_SetPinMux(IOMUXC_GPIO_SD_B1_03_GPIO3_IO23, 0U); // BLK
IOMUXC_SetPinMux(IOMUXC_GPIO_SD_B1_02_GPIO3_IO22, 0U); // RES
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B0_05_GPIO1_IO05, 0x10B0U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B0_11_ARM_CM7_TRACE_SWO, 0x10B0U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B0_06_LPUART1_TX, 0x10B0U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B0_07_LPUART1_RX, 0x10B0U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_06_FLEXPWM1_PWMA00, 0x10B0U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B0_15_GPIO1_IO15, 0x10B0U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B0_11_GPIO1_IO11, 0x10B0U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B0_12_GPIO1_IO12, 0x10B0U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B0_10_GPIO1_IO10, 0x10B0U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_SD_B1_03_GPIO3_IO23, 0x10B0U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_SD_B1_02_GPIO3_IO22, 0x10B0U);
}
lcd_init.h中的管脚宏定义见如下:
#ifndef __LCD_INIT_H
#define __LCD_INIT_H
#include "board.h"
#include "pin_mux.h"
#define USE_HORIZONTAL 1 //设置横屏或者竖屏显示 0或1为竖屏 2或3为横屏
#if USE_HORIZONTAL==0||USE_HORIZONTAL==1
#define LCD_W 128
#define LCD_H 160
#else
#define LCD_W 160
#define LCD_H 128
#endif
#define BOARD_LCD_GPIO GPIO1
#define BOARD_LCD_GPIO_SCLK_PIN (10U)
#define BOARD_LCD_GPIO_CS_PIN (11U)
#define BOARD_LCD_GPIO_MOSI_PIN (12U)
#define BOARD_LCD_GPIO_DC_PIN (15U)
#define BOARD_LCD_BLKRES_GPIO GPIO3
#define BOARD_LCD_GPIO_BLK_PIN (23U)
#define BOARD_LCD_GPIO_RES_PIN (22U)
//-----------------LCD端口定义----------------
#define LCD_SCLK_Clr() GPIO_PinWrite(BOARD_LCD_GPIO,BOARD_LCD_GPIO_SCLK_PIN,0)//SCL SCLK
#define LCD_SCLK_Set() GPIO_PinWrite(BOARD_LCD_GPIO,BOARD_LCD_GPIO_SCLK_PIN,1)
#define LCD_CS_Clr() GPIO_PinWrite(BOARD_LCD_GPIO,BOARD_LCD_GPIO_CS_PIN,0)//CS
#define LCD_CS_Set() GPIO_PinWrite(BOARD_LCD_GPIO,BOARD_LCD_GPIO_CS_PIN,1)
#define LCD_MOSI_Clr() GPIO_PinWrite(BOARD_LCD_GPIO,BOARD_LCD_GPIO_MOSI_PIN,0)//SDA MOSI
#define LCD_MOSI_Set() GPIO_PinWrite(BOARD_LCD_GPIO,BOARD_LCD_GPIO_MOSI_PIN,1)
#define LCD_DC_Clr() GPIO_PinWrite(BOARD_LCD_GPIO,BOARD_LCD_GPIO_DC_PIN,0)//DC
#define LCD_DC_Set() GPIO_PinWrite(BOARD_LCD_GPIO,BOARD_LCD_GPIO_DC_PIN,1)
#define LCD_RES_Clr() GPIO_PinWrite(BOARD_LCD_BLKRES_GPIO,BOARD_LCD_GPIO_RES_PIN,0)//RES
#define LCD_RES_Set() GPIO_PinWrite(BOARD_LCD_BLKRES_GPIO,BOARD_LCD_GPIO_RES_PIN,1)
#define LCD_BLK_Clr() GPIO_PinWrite(BOARD_LCD_BLKRES_GPIO,BOARD_LCD_GPIO_BLK_PIN,0)//BLK
#define LCD_BLK_Set() GPIO_PinWrite(BOARD_LCD_BLKRES_GPIO,BOARD_LCD_GPIO_BLK_PIN,1)
void LCD_GPIO_Init(void);//初始化GPIO
void LCD_Writ_Bus(u8 dat);//模拟SPI时序
void LCD_WR_DATA8(u8 dat);//写入一个字节
void LCD_WR_DATA(u16 dat);//写入两个字节
void LCD_WR_REG(u8 dat);//写入一个指令
void LCD_Address_Set(u16 x1,u16 y1,u16 x2,u16 y2);//设置坐标函数
void LCD_Init(void);//LCD初始化
#endif
lcd_init.c主要为LCD屏初始化接口代码,内容如下:
#include "lcd_init.h"
#include "board.h"
/******************************************************************************
函数说明:LCD串行数据写入函数
入口数据:dat 要写入的串行数据
返回值: 无
******************************************************************************/
void LCD_Writ_Bus(u8 dat)
{
u8 i;
LCD_CS_Clr();
for(i=0;i<8;i++)
{
LCD_SCLK_Clr();
if(dat&0x80)
{
LCD_MOSI_Set();
}
else
{
LCD_MOSI_Clr();
}
LCD_SCLK_Set();
dat<<=1;
}
LCD_CS_Set();
}
/******************************************************************************
函数说明:LCD写入数据
入口数据:dat 写入的数据
返回值: 无
******************************************************************************/
void LCD_WR_DATA8(u8 dat)
{
LCD_Writ_Bus(dat);
}
/******************************************************************************
函数说明:LCD写入数据
入口数据:dat 写入的数据
返回值: 无
******************************************************************************/
void LCD_WR_DATA(u16 dat)
{
LCD_Writ_Bus(dat>>8);
LCD_Writ_Bus(dat);
}
/******************************************************************************
函数说明:LCD写入命令
入口数据:dat 写入的命令
返回值: 无
******************************************************************************/
void LCD_WR_REG(u8 dat)
{
LCD_DC_Clr();//写命令
LCD_Writ_Bus(dat);
LCD_DC_Set();//写数据
}
/******************************************************************************
函数说明:设置起始和结束地址
入口数据:x1,x2 设置列的起始和结束地址
y1,y2 设置行的起始和结束地址
返回值: 无
******************************************************************************/
void LCD_Address_Set(u16 x1,u16 y1,u16 x2,u16 y2)
{
if(USE_HORIZONTAL==0)
{
LCD_WR_REG(0x2a);//列地址设置
LCD_WR_DATA(x1+2);
LCD_WR_DATA(x2+2);
LCD_WR_REG(0x2b);//行地址设置
LCD_WR_DATA(y1+1);
LCD_WR_DATA(y2+1);
LCD_WR_REG(0x2c);//储存器写
}
else if(USE_HORIZONTAL==1)
{
LCD_WR_REG(0x2a);//列地址设置
LCD_WR_DATA(x1+2);
LCD_WR_DATA(x2+2);
LCD_WR_REG(0x2b);//行地址设置
LCD_WR_DATA(y1+1);
LCD_WR_DATA(y2+1);
LCD_WR_REG(0x2c);//储存器写
}
else if(USE_HORIZONTAL==2)
{
LCD_WR_REG(0x2a);//列地址设置
LCD_WR_DATA(x1+1);
LCD_WR_DATA(x2+1);
LCD_WR_REG(0x2b);//行地址设置
LCD_WR_DATA(y1+2);
LCD_WR_DATA(y2+2);
LCD_WR_REG(0x2c);//储存器写
}
else
{
LCD_WR_REG(0x2a);//列地址设置
LCD_WR_DATA(x1+1);
LCD_WR_DATA(x2+1);
LCD_WR_REG(0x2b);//行地址设置
LCD_WR_DATA(y1+2);
LCD_WR_DATA(y2+2);
LCD_WR_REG(0x2c);//储存器写
}
}
void LCD_Init(void)
{
LCD_RES_Clr();//复位
SysTick_DelayTicks(1);
LCD_RES_Set();
SysTick_DelayTicks(1);
LCD_BLK_Set();//打开背光
SysTick_DelayTicks(120);
//************* Start Initial Sequence **********//
LCD_WR_REG(0x11); //Sleep out
SysTick_DelayTicks(120); //Delay 120ms
LCD_WR_REG(0xB1);
LCD_WR_DATA8(0x05);
LCD_WR_DATA8(0x3C);
LCD_WR_DATA8(0x3C);
LCD_WR_REG(0xB2);
LCD_WR_DATA8(0x05);
LCD_WR_DATA8(0x3C);
LCD_WR_DATA8(0x3C);
LCD_WR_REG(0xB3);
LCD_WR_DATA8(0x05);
LCD_WR_DATA8(0x3C);
LCD_WR_DATA8(0x3C);
LCD_WR_DATA8(0x05);
LCD_WR_DATA8(0x3C);
LCD_WR_DATA8(0x3C);
LCD_WR_REG(0xB4); //Dot inversion
LCD_WR_DATA8(0x03);
LCD_WR_REG(0xC0);
LCD_WR_DATA8(0x28);
LCD_WR_DATA8(0x08);
LCD_WR_DATA8(0x04);
LCD_WR_REG(0xC1);
LCD_WR_DATA8(0XC0);
LCD_WR_REG(0xC2);
LCD_WR_DATA8(0x0D);
LCD_WR_DATA8(0x00);
LCD_WR_REG(0xC3);
LCD_WR_DATA8(0x8D);
LCD_WR_DATA8(0x2A);
LCD_WR_REG(0xC4);
LCD_WR_DATA8(0x8D);
LCD_WR_DATA8(0xEE);
LCD_WR_REG(0xC5); //VCOM
LCD_WR_DATA8(0x1A);
LCD_WR_REG(0x36); //MX, MY, RGB mode
if(USE_HORIZONTAL==0)LCD_WR_DATA8(0x00);
else if(USE_HORIZONTAL==1)LCD_WR_DATA8(0xC0);
else if(USE_HORIZONTAL==2)LCD_WR_DATA8(0x70);
else LCD_WR_DATA8(0xA0);
LCD_WR_REG(0xE0);
LCD_WR_DATA8(0x04);
LCD_WR_DATA8(0x22);
LCD_WR_DATA8(0x07);
LCD_WR_DATA8(0x0A);
LCD_WR_DATA8(0x2E);
LCD_WR_DATA8(0x30);
LCD_WR_DATA8(0x25);
LCD_WR_DATA8(0x2A);
LCD_WR_DATA8(0x28);
LCD_WR_DATA8(0x26);
LCD_WR_DATA8(0x2E);
LCD_WR_DATA8(0x3A);
LCD_WR_DATA8(0x00);
LCD_WR_DATA8(0x01);
LCD_WR_DATA8(0x03);
LCD_WR_DATA8(0x13);
LCD_WR_REG(0xE1);
LCD_WR_DATA8(0x04);
LCD_WR_DATA8(0x16);
LCD_WR_DATA8(0x06);
LCD_WR_DATA8(0x0D);
LCD_WR_DATA8(0x2D);
LCD_WR_DATA8(0x26);
LCD_WR_DATA8(0x23);
LCD_WR_DATA8(0x27);
LCD_WR_DATA8(0x27);
LCD_WR_DATA8(0x25);
LCD_WR_DATA8(0x2D);
LCD_WR_DATA8(0x3B);
LCD_WR_DATA8(0x00);
LCD_WR_DATA8(0x01);
LCD_WR_DATA8(0x04);
LCD_WR_DATA8(0x13);
LCD_WR_REG(0x3A); //65k mode
LCD_WR_DATA8(0x05);
LCD_WR_REG(0x29); //Display on
}
主函数调用的接口,部分代码如下:
/*******************************************************************************
* Variables
******************************************************************************/
volatile uint32_t g_systickCounter;
/* The PIN status */
volatile bool g_pinSet = false;
uint32_t pwmVal = 0;
uint32_t var = 0;
typedef struct
{
unsigned char Index[2];
unsigned char Msk[32];
}typFNT_GB16;
extern const typFNT_GB16 tfont16[];
void Display_text(void)
{
SysTick_DelayTicks(6000U);
LCD_Fill(0,0,LCD_W,LCD_H,YELLOW);
LCD_ShowIntNum(8,10,2025,4,BLUE,GREEN,16);
LCD_ShowString(44,10,(uint8_t *)"DigiKey",BLUE,GREEN,16,0);
LCD_ShowChinese(8,30,(uint8_t *)&tfont16[0],BLUE,GREEN,16,0); //创
LCD_ShowChinese(24,30,(uint8_t *)&tfont16[1],BLUE,GREEN,16,0); //意
LCD_ShowChinese(40,30,(uint8_t *)&tfont16[2],BLUE,GREEN,16,0); //大
LCD_ShowChinese(56,30,(uint8_t *)&tfont16[3],BLUE,GREEN,16,0); //赛
LCD_ShowChinese(8,60,(uint8_t *)&tfont16[4],BLUE,GREEN,16,0); //基
LCD_ShowChinese(24,60,(uint8_t *)&tfont16[5],BLUE,GREEN,16,0); //于
LCD_ShowString(8,80,(uint8_t *)"MIMXRT1020-EVK",BLACK,GREEN,16,0);
LCD_ShowChinese(8,100,(uint8_t *)&tfont16[6],RED,GREEN,16,0); //恒
LCD_ShowChinese(24,100,(uint8_t *)&tfont16[7],RED,GREEN,16,0); //温
LCD_ShowChinese(40,100,(uint8_t *)&tfont16[8],RED,GREEN,16,0); //茶
LCD_ShowChinese(56,100,(uint8_t *)&tfont16[9],RED,GREEN,16,0); //杯
LCD_ShowChinese(72,100,(uint8_t *)&tfont16[10],RED,GREEN,16,0); //垫
LCD_ShowString(43,140,(uint8_t *)"2025-11-22",BLUE,GREEN,16,0);
SysTick_DelayTicks(100);
}
int main(void)
{
/* Structure of initialize PWM */
pwm_config_t pwmConfig;
pwm_fault_param_t faultConfig;
uint8_t ret = 0U;
/* Board pin init */
BOARD_InitHardware();
/* Set systick reload value to generate 1ms interrupt */
if (SysTick_Config(SystemCoreClock / 1000U))
{
while (1)
{
}
}
LCD_Init();
SysTick_DelayTicks(200U);
LCD_Fill(0,0,LCD_W,LCD_H,WHITE);
LCD_ShowPicture(0,0,128,160,gImage_1);
Display_text();
PWM_GetDefaultConfig(&pwmConfig);
pwmConfig.prescale = kPWM_Prescale_Divide_1;
/* Use full cycle reload */
pwmConfig.reloadLogic = kPWM_ReloadPwmFullCycle;
/* PWM A & PWM B operate as 2 independent channels */
pwmConfig.pairOperation = kPWM_Independent;
pwmConfig.enableDebugMode = true;
/* Initialize submodule 0 */
ret = PWM_Init(BOARD_PWM_BASEADDR, BOARD_PWM_SUBMODULE, &pwmConfig);
if (ret != kStatus_Success)
{
PRINTF("\r\nPWM INIT FAILED");
return 1;
}
PWM_FaultDefaultConfig(&faultConfig);
/* Sets up the PWM fault protection */
PWM_SetupFaults(BOARD_PWM_BASEADDR, kPWM_Fault_0, &faultConfig);
/* Set PWM fault disable mapping for submodule 0 */
PWM_SetupFaultDisableMap(BOARD_PWM_BASEADDR, kPWM_Module_0, kPWM_PwmA, kPWM_faultchannel_0,DEMO_PWM_DISABLE_MAP_OP(kPWM_FaultDisable_0));
PWM_InitPhasePwm();
/* Set the load okay bit for all submodules to load registers from their buffer */
PWM_SetPwmLdok(BOARD_PWM_BASEADDR, kPWM_Control_Module_0, true);
PWM_StartTimer(BOARD_PWM_BASEADDR, BOARD_PWM_CONTROL_SUBMODULE);
while (1)
{
/* Delay 60 ms */
SysTick_DelayTicks(60U);
if(var == 0)
{
pwmVal+=5;
}
else if(var == 1)
{
pwmVal-=5;
}
if(pwmVal > 95)
var = 1;
else if(pwmVal < 5)
var = 0;
/* Update duty cycles for PWM signals */
PWM_UpdatePwmDutycycle(BOARD_PWM_BASEADDR, BOARD_PWM_SUBMODULE, BOARD_PWM_CHANNEL, kPWM_SignedCenterAligned,pwmVal);
/* Set the load okay bit for all submodules to load registers from their buffer */
PWM_SetPwmLdok(BOARD_PWM_BASEADDR, BOARD_PWM_CONTROL_SUBMODULE, true);
if (g_pinSet)
{
GPIO_PinWrite(EXAMPLE_LED_GPIO, EXAMPLE_LED_GPIO_PIN, 0U);
g_pinSet = false;
}
else
{
GPIO_PinWrite(EXAMPLE_LED_GPIO, EXAMPLE_LED_GPIO_PIN, 1U);
g_pinSet = true;
}
}
}
代码中用到的中文字符使用取字模工具“PCtoLCD2002”生成,而图片则由“Image2Lcd”工具生成。
四、显示效果
代码编辑完后,编译下载到EVK板后,正常驱动显示,字符显示效果如下:
视频展示效果如下:
[localvideo]23fbf201b004b71dcba4aaa0ecd3abe8[/localvideo]
录屏中显示字符不怎么清晰,可能是使用GPIO口模拟SPI的方式驱动吧,镜头聚焦,拍照看看如下效果:
此次更新就到这儿啦,后续再完善加热升温控制功能吧,后会有期!
- 2025-10-26
-
发表了主题帖:
【2025 DigiKey创意大赛】基于MIMXRT1020-EVK的恒温茶杯垫-环境搭建与呼吸灯
一、前言
前面分享了开箱贴,今天来分享一下环境的搭建,实验由简单的LED闪烁,扩展到PWM接口实现呼吸灯效果。
二、环境构建
通过链接https://mcuxpresso.nxp.com/zhMCUXpresso SDK 构建工具可在线构建最新的SDK包,当然需要注册会员,然后尽量勾选支持所有工具链,以便后续使用MCUXpresso IDE或Keil工具开发。当然工程示例根据所需的外设进行勾选,然后将生成后的“SDK_25_06_00_EVK-MIMXRT1020.zip”文件下载。
笔者习惯使用Keil工具,因此本节介绍基于Keil开发RT1020的基本应用。既然使用Keil开发,因此需要安装pack支持包。
踩坑:首先笔者跳转至MIMXRT1021DAG5A,完成下载“NXP.MIMXRT1021_DFP.25.06.00.pack”pack支持包。在Keil工具已安装好的电脑中,直接安装该pack文件,然而会报错,弹窗如下:
笔者当前安装的Keil版本如下:
这也不知道是哪里出的问题,系统不兼容?于是乎,还是使用之前用过的“NXP.MIMXRT1021_DFP.18.0.0.pack”版本。
将下载的pack文件,直接点击安装,会默认安装至Keil安装的pack目录下,成功安装如下图所示:
此时再进入到下载好并解压完成的SDK目录下,随意打开一个参考工程示例,点击全局编译,无报异常错误则说明pack包安装成功。
三、工程代码编辑
笔者第一时间使用SDK目录下的“demo_apps”文件中的“led_blinky”工程,验证编译环境是否ok,因此接下来基于该工程,完善输出一路PWM信号,用来控制实现呼吸灯效果。
第1步:添加驱动库文件
第2步:添加IO口复用关系
在“void BOARD_InitPins(void)”函数中完善。工程中使用GPIOB1_06复用成FLEXPWM1,当然这里也一并将串口调试接口复用出来,以便后续串口打印调试。
void BOARD_InitPins(void) {
CLOCK_EnableClock(kCLOCK_Iomuxc);
/* GPIO configuration of USER_LED on GPIO_AD_B0_05 (pin 106) */
gpio_pin_config_t USER_LED_config = {
.direction = kGPIO_DigitalOutput,
.outputLogic = 0U,
.interruptMode = kGPIO_NoIntmode
};
/* Initialize GPIO functionality on GPIO_AD_B0_05 (pin 106) */
GPIO_PinInit(GPIO1, 5U, &USER_LED_config);
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B0_05_GPIO1_IO05, 0U);
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B0_11_ARM_CM7_TRACE_SWO, 0U);
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B0_06_LPUART1_TX, 0U);
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B0_07_LPUART1_RX, 0U);
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_06_FLEXPWM1_PWMA00, 0U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B0_05_GPIO1_IO05, 0x10B0U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B0_11_ARM_CM7_TRACE_SWO, 0x10B0U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B0_06_LPUART1_TX, 0x10B0U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B0_07_LPUART1_RX, 0x10B0U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_06_FLEXPWM1_PWMA00, 0x10B0U);
}
第3步:引入设置PWM Fault输入值
具体在“void BOARD_InitHardware(void)”函数中添加,hardware_init.c文件源码如下:
/*${header:start}*/
#include "pin_mux.h"
#include "clock_config.h"
#include "board.h"
#include "fsl_xbara.h"
/*${header:end}*/
/*${function:start}*/
void BOARD_InitHardware(void)
{
BOARD_ConfigMPU();
BOARD_InitBootPins();
BOARD_InitBootClocks();
BOARD_InitDebugConsole();
/* Set the PWM Fault inputs to a low value */
XBARA_Init(XBARA);
XBARA_SetSignalsConnection(XBARA, kXBARA1_InputLogicHigh, kXBARA1_OutputFlexpwm1Fault0);
}
/*${function:end}*/
第4步:初始化、逻辑代码编写
app.h文件宏定义如下:
#ifndef _APP_H_
#define _APP_H_
/*${header:start}*/
#include "pin_mux.h"
/*${header:end}*/
/***************************************************************
* Definitions
***************************************************************/
/*${macro:start}*/
#define EXAMPLE_LED_GPIO BOARD_USER_LED_GPIO
#define EXAMPLE_LED_GPIO_PIN BOARD_USER_LED_PIN
#define BOARD_PWM_BASEADDR PWM1
#define BOARD_PWM_SUBMODULE kPWM_Module_0
#define BOARD_PWM_CONTROL_SUBMODULE kPWM_Control_Module_0
#define BOARD_PWM_CHANNEL kPWM_PwmA
#define BOARD_DEADTIME_VAL 650U
#define PWM_SRC_CLK_FREQ CLOCK_GetFreq(kCLOCK_IpgClk)
/*${macro:end}*/
/*****************************************************************
* Prototypes
*****************************************************************/
/*${prototype:start}*/
void BOARD_InitHardware(void);
/*${prototype:end}*/
#endif /* _APP_H_ */
led_blinky.c完善逻辑处理代码。
#include "fsl_debug_console.h"
#include "board.h"
#include "app.h"
#include "fsl_pwm.h"
/*******************************************************************************
* Definitions
******************************************************************************/
#ifndef DEMO_PWM_DISABLE_MAP_OP
#define DEMO_PWM_DISABLE_MAP_OP
#endif
/*******************************************************************************
* Prototypes
******************************************************************************/
/* Sets up the PWM signals for a PWM submodule */
static void PWM_InitPhasePwm(void);
/*******************************************************************************
* Variables
******************************************************************************/
volatile uint32_t g_systickCounter;
/* The PIN status */
volatile bool g_pinSet = false;
uint32_t pwmVal = 0;
uint32_t var = 0;
/*******************************************************************************
* Code
******************************************************************************/
void SysTick_Handler(void)
{
if (g_systickCounter != 0U)
{
g_systickCounter--;
}
}
void SysTick_DelayTicks(uint32_t n)
{
g_systickCounter = n;
while (g_systickCounter != 0U)
{
}
}
static void PWM_InitPhasePwm(void)
{
/* Structure of setup PWM */
pwm_signal_param_t pwmSignal;
uint16_t deadTimeVal;
uint32_t pwmSourceClockInHz, pwmFrequencyInHz = 1U;
pwmSourceClockInHz = PWM_SRC_CLK_FREQ;
/* Set deadtime count */
deadTimeVal = ((uint64_t)pwmSourceClockInHz * BOARD_DEADTIME_VAL)/1000000000U;
pwmSignal.pwmChannel = BOARD_PWM_CHANNEL;
pwmSignal.level = kPWM_HighTrue;
pwmSignal.dutyCyclePercent = 0U;
pwmSignal.deadtimeValue = deadTimeVal;
pwmSignal.faultState = kPWM_PwmFaultState0;
pwmSignal.pwmchannelenable = true;
PWM_SetupPwm(BOARD_PWM_BASEADDR,BOARD_PWM_SUBMODULE,&pwmSignal,1U,kPWM_SignedCenterAligned,pwmFrequencyInHz,pwmSourceClockInHz);
/* Set the load okay bit for all submodules to load registers from their buffer */
PWM_SetPwmLdok(BOARD_PWM_BASEADDR, BOARD_PWM_CONTROL_SUBMODULE, true);
}
/*!
* [url=home.php?mod=space&uid=159083]@brief[/url] Main function
*/
int main(void)
{
/* Structure of initialize PWM */
pwm_config_t pwmConfig;
pwm_fault_param_t faultConfig;
uint8_t ret = 0U;
/* Board pin init */
BOARD_InitHardware();
/* Set systick reload value to generate 1ms interrupt */
if (SysTick_Config(SystemCoreClock / 1000U))
{
while (1)
{
}
}
PWM_GetDefaultConfig(&pwmConfig);
pwmConfig.prescale = kPWM_Prescale_Divide_1;
/* Use full cycle reload */
pwmConfig.reloadLogic = kPWM_ReloadPwmFullCycle;
/* PWM A & PWM B operate as 2 independent channels */
pwmConfig.pairOperation = kPWM_Independent;
pwmConfig.enableDebugMode = true;
/* Initialize submodule 0 */
ret = PWM_Init(BOARD_PWM_BASEADDR, BOARD_PWM_SUBMODULE, &pwmConfig);
if (ret != kStatus_Success)
{
PRINTF("\r\nPWM INIT FAILED");
return 1;
}
PWM_FaultDefaultConfig(&faultConfig);
/* Sets up the PWM fault protection */
PWM_SetupFaults(BOARD_PWM_BASEADDR, kPWM_Fault_0, &faultConfig);
/* Set PWM fault disable mapping for submodule 0 */
PWM_SetupFaultDisableMap(BOARD_PWM_BASEADDR,kPWM_Module_0,kPWM_PwmA,kPWM_faultchannel_0,DEMO_PWM_DISABLE_MAP_OP(kPWM_FaultDisable_0));
PWM_InitPhasePwm();
/* Set the load okay bit for all submodules to load registers from their buffer */
PWM_SetPwmLdok(BOARD_PWM_BASEADDR, kPWM_Control_Module_0, true);
PWM_StartTimer(BOARD_PWM_BASEADDR, BOARD_PWM_CONTROL_SUBMODULE);
while (1)
{
/* Delay 60 ms */
SysTick_DelayTicks(60U);
if(var == 0)
{
pwmVal+=5;
}
else if(var == 1)
{
pwmVal-=5;
}
if(pwmVal > 95)
var = 1;
else if(pwmVal < 5)
var = 0;
/* Update duty cycles for PWM signals */
PWM_UpdatePwmDutycycle(BOARD_PWM_BASEADDR,BOARD_PWM_SUBMODULE,BOARD_PWM_CHANNEL,kPWM_SignedCenterAligned,pwmVal);
/* Set the load okay bit for all submodules to load registers from their buffer */
PWM_SetPwmLdok(BOARD_PWM_BASEADDR, BOARD_PWM_CONTROL_SUBMODULE, true);
if (g_pinSet)
{
GPIO_PinWrite(EXAMPLE_LED_GPIO, EXAMPLE_LED_GPIO_PIN, 0U);
g_pinSet = false;
}
else
{
GPIO_PinWrite(EXAMPLE_LED_GPIO, EXAMPLE_LED_GPIO_PIN, 1U);
g_pinSet = true;
}
}
}
四、下载程序
上述工程代码编写完后进行编译,接下来将编译后的程序下载到EVK板中。板上集成DAP-Link调试接口,该CMSIS-DAP接口既支持下载程序,也可当做串口信息输出接口。
说明:另外板上还集成了JLink调试接口,然而这与DAP-Link调试接口是冲突的,如果采用JLink下载程序,不仅需要修改配置文件,而且硬件上也需要稍作修改,断开电阻R116,焊接R425电阻。这比较麻烦,因为电阻物料封装非常小,不易手动焊接,硬件修改完后再换回DAP-Link也费事,因此这里建议直接使用DAP-Link调试接口即可。
五、呼吸灯效
将程序下载到开发板后,引入一个外设扩展底板,给该板通过杜邦线引入3.3V电压,然后将GPIOB1_06脚输出的PWM给到扩展底板的RGB灯任意一位引脚上,呈现的呼吸灯效果如下视频。
[localvideo]c6ac5217f0f90d189519fb69cb29e6f4[/localvideo]
- 2025-10-15
-
发表了主题帖:
【2025 DigiKey创意大赛】基于MIMXRT1020-EVK的恒温茶杯垫---开箱上电贴
一、引言
参加2025 DigiKey“智控万物,改变生活”创意大赛选择的MIMXRT1020-EVK开发板前天收到了,开发板漂洋过海寄过来的确要花点时间。本次活动笔者只有选择了【必选物料】中的MIMXRT1020-EVK板卡,预期实现一个恒温茶杯垫的功能。
二、开箱
收到的包裹有些大,里面存放了多张蜂窝缓冲包装纸,确保运输中的安全运送,核心板块就一个纸盒。
打开纸盒,里面也包装很好,上面附有发货清单。
取出组件,展示如下图所示,MicroUSB的数据线非常坚实,线材真实可靠。
使用15cm的尺子量测,板卡长14cm、宽9cm,正面如下图所示:
板子装有四个定位柱,背面的拨码开关处,附近的蛇形走线显得很是专业,硬件电路设计非常值得借鉴,正面也同样可清晰得看到蛇形走线。
三、上电
收到板卡,第一时间最关心的事,当然是板卡的功能是否正常,上电检测一下出厂设置装载的固件程序。
[localvideo]dd773d0ae1dddeb6cc356f4f72798e43[/localvideo]
四、焊接插针
为了方便后续开发使用IO口,再结合官方提供的“SPF-29856_B1.pdf”原理图中的Arduino Interface说明,焊接好不同颜色的插针,方便加以区分。
背面的焊点展示如下:
焊接完插针后,再次上电,检测焊接时是否对板上其它小元器件造成损伤。
[localvideo]1a67d660e70f38fc39b25332b7902f64[/localvideo]
- 2025-03-20
-
发表了主题帖:
【KW41Z开发板测评】⑥微信小程序控制台灯
本帖最后由 yin_wu_qing 于 2025-3-20 22:26 编辑
根据上期的⑤蓝牙调试工具控制台灯调试结果,今天来分享一下,使用个人微信的小程序来控制台灯。由于是基于蓝牙通讯,因此用户无需打开WiFi或移动数据连接开关。小程序比较流行,用户无需下载安装第三方的应用包,只需要微信扫码或搜索即可使用,易用性强。
一、搭建开发环境
首先要下载“微信开发者工具”,可通过下载之家 免费下载、安装。注册一个账号,或者直接使用微信扫码登录。
微信官方提供了诸多保姆级网页文档教程,开发者可移步:微信官方文档 ● 小程序进行系统性学习了解。
在熟悉了基本的概念后,开发者通过连接硬件能力-蓝牙篇网页,在页面最底下可获取标准模板例程,无需下载,点击完即可加载到“微信开发者工具”的IDE中。然后再将加载进来的工程文件保存到个人创建好的工程目录下。
从示例代码中可进一步了解小程序设计的工程目录结构,用到Javascript与html语言,.js文件是页面逻辑处理部分的源码,.wxml文件是描述页面结构,.json是关于页面配置的,.wxss是关于页面样式表的设置。笔者在此示例工程上,创建一个新的页面,里面展示一个开关按钮、一个显示温度、湿度的控件。
二、代码完善
根据页面设计需求,创建的device.js、device.json、device.wxml、device.wxss源码分享如下:
① device.js
const app = getApp()
function inArray(arr, key, val) {
for (let i = 0; i < arr.length; i++) {
if (arr[i][key] === val) {
return i;
}
}
return -1;
}
// ArrayBuffer转16进度字符串示例
function ab2hex(buffer) {
var hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function (bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('');
}
Page({
data: {
deviceId: null,
name:null,
inputValue: '',
connected: false,
chs: [],
temperature: '20',
humidity: '60',
switchChecked: false, // 开关初始状态为关闭
},
onLoad: function(options)
{
const deviceId = app.globalData.deviceId;
const name = app.globalData.name;
console.log('name:' + name)
console.log('deviceId:' + deviceId)
wx.createBLEConnection({
deviceId,
success: (res) => {
this.setData({
connected: true,
name,
deviceId,
})
this.getBLEDeviceServices(deviceId)
}
})
},
getBLEDeviceServices(deviceId) {
wx.getBLEDeviceServices({
deviceId,
success: (res) => {
for (let i = 0; i < res.services.length; i++) {
if (res.services[i].isPrimary) {
this.getBLEDeviceCharacteristics(deviceId, res.services[i].uuid)
return
}
}
}
})
},
getBLEDeviceCharacteristics(deviceId, serviceId) {
wx.getBLEDeviceCharacteristics({
deviceId,
serviceId,
success: (res) => {
console.log('getBLEDeviceCharacteristics success', res.characteristics)
for (let i = 0; i < res.characteristics.length; i++) {
let item = res.characteristics[i]
if (item.properties.read) {
wx.readBLECharacteristicValue({
deviceId,
serviceId,
characteristicId: item.uuid,
})
}
if (item.properties.write) {
this.setData({
canWrite: true
})
this._deviceId = deviceId
this._serviceId = serviceId
this._characteristicId = item.uuid
this.writeBLECharacteristicValue()
}
if (item.properties.notify || item.properties.indicate) {
wx.notifyBLECharacteristicValueChange({
deviceId,
serviceId,
characteristicId: item.uuid,
state: true,
})
}
}
},
fail(res) {
console.error('getBLEDeviceCharacteristics', res)
}
})
// 操作之前先监听,保证第一时间获取数据
wx.onBLECharacteristicValueChange((characteristic) => {
const idx = inArray(this.data.chs, 'uuid', characteristic.characteristicId)
const data = {}
if (idx === -1) {
data[`chs[${this.data.chs.length}]`] = {
uuid: characteristic.characteristicId,
value: ab2hex(characteristic.value)
}
} else {
data[`chs[${idx}]`] = {
uuid: characteristic.characteristicId,
value: ab2hex(characteristic.value)
}
}
if(characteristic.characteristicId == 'E01C4B5E-1EEB-A15C-EEF4-5EBA0001FF01')
{
let dataView = new DataView(characteristic.value)
const temp = (dataView.getUint8(0))*10 + dataView.getUint8(1);
const hum = dataView.getUint8(2)*10 + dataView.getUint8(3);
this.setData({
humidity: hum,
temperature: temp,
})
}
this.setData(data);
})
},
writeBLECharacteristicValue() {
// 向蓝牙设备发送一个0x00的16进制数据
let buffer = new ArrayBuffer(2)
let dataView = new DataView(buffer)
dataView.setUint8(0, 0);
dataView.setUint8(0, 1);
wx.writeBLECharacteristicValue({
deviceId: this._deviceId,
serviceId: this._serviceId,
characteristicId: this._characteristicId,
value: buffer,
success (res) {
console.log('writeBLECharacteristicValue success', res.errMsg)
},
fail (err) {
console.log('writeBLECharacteristicValue fail', err.errMsg)
}
})
},
switchChange: function (e) {
// 获取开关改变后的状态值
const checked = e.detail.value;
let buffer = new ArrayBuffer(2)
let dataView = new DataView(buffer)
dataView.setUint8(0, 0);
if(checked){
// 向蓝牙设备发送一个0x00的16进制数据
dataView.setUint8(0, 0x31);
}else{
dataView.setUint8(0, 0x30);
}
wx.writeBLECharacteristicValue({
deviceId: this._deviceId,
serviceId: this._serviceId,
characteristicId: this._characteristicId,
value: buffer,
success (res) {
console.log('writeBLECharacteristicValue success', res.errMsg)
},
fail (err) {
console.log('writeBLECharacteristicValue fail', err.errMsg)
}
})
this.setData({
switchChecked: checked
});
},
})
② device.json
{
"usingComponents": {}
}
③ device.wxml
<view class="connected_info" wx:if="{{connected}}">
<view>
<text>已连接到 {{name}}</text>
<view class="operation">
<button wx:if="{{canWrite}}" size="mini" bindtap="writeBLECharacteristicValue">写数据</button>
<button size="mini" bindtap="closeBLEConnection">断开连接</button>
</view>
</view>
</view>
<view class="container">
<view class="display-item">
<text>灯光:</text>
</view>
<switch checked="{{switchChecked}}" bindchange="switchChange" />
<view class="display-item">
<text>----------------------</text>
</view>
<!-- 温度显示控件 -->
<view class="display-item">
<text>温度:</text>
<text>{{temperature}}°C</text>
</view>
<!-- 湿度显示控件 -->
<view class="display-item">
<text>湿度:</text>
<text>{{humidity}}%RH</text>
</view>
</view>
④ device.wxss
.container {
display: flex;
flex-direction: column;
align-items: center;
padding: 20rpx;
}
.switch {
margin-bottom: 30rpx;
}
.display-item {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.display-item text:first-child {
margin-right: 10rpx;
font-weight: bold;
}
slider {
width: 80%;
margin: 30px 0;
}
接下来,需要再修改index.js的Page页中跳转服务与参数保存函数入口,修改如下:
const app = getApp()
function inArray(arr, key, val) {
for (let i = 0; i < arr.length; i++) {
if (arr[i][key] === val) {
return i;
}
}
return -1;
}
// ArrayBuffer转16进度字符串示例
function ab2hex(buffer) {
var hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function (bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('');
}
Page({
data: {
devices: [],
connected: false,
chs: [],
},
openBluetoothAdapter() {
wx.openBluetoothAdapter({
success: (res) => {
console.log('openBluetoothAdapter success', res)
this.startBluetoothDevicesDiscovery()
},
fail: (res) => {
if (res.errCode === 10001) {
wx.onBluetoothAdapterStateChange(function (res) {
console.log('onBluetoothAdapterStateChange', res)
if (res.available) {
this.startBluetoothDevicesDiscovery()
}
})
}
}
})
},
getBluetoothAdapterState() {
wx.getBluetoothAdapterState({
success: (res) => {
console.log('getBluetoothAdapterState', res)
if (res.discovering) {
this.onBluetoothDeviceFound()
} else if (res.available) {
this.startBluetoothDevicesDiscovery()
}
}
})
},
startBluetoothDevicesDiscovery() {
if (this._discoveryStarted) {
return
}
this._discoveryStarted = true
wx.startBluetoothDevicesDiscovery({
allowDuplicatesKey: true,
success: (res) => {
console.log('startBluetoothDevicesDiscovery success', res)
this.onBluetoothDeviceFound()
},
})
},
stopBluetoothDevicesDiscovery() {
wx.stopBluetoothDevicesDiscovery()
},
onBluetoothDeviceFound() {
wx.onBluetoothDeviceFound((res) => {
res.devices.forEach(device => {
if (!device.name && !device.localName) {
return
}
const foundDevices = this.data.devices
const idx = inArray(foundDevices, 'deviceId', device.deviceId)
const data = {}
if (idx === -1) {
data[`devices[${foundDevices.length}]`] = device
} else {
data[`devices[${idx}]`] = device
}
this.setData(data)
})
})
},
createBLEConnection(e) {
const ds = e.currentTarget.dataset
const deviceId = ds.deviceId
const name = ds.name
// 添加跳转函数
app.globalData.deviceId = deviceId;
app.globalData.name = name;
wx.navigateTo({
url: 'device'
})
this.stopBluetoothDevicesDiscovery()
},
closeBLEConnection() {
wx.closeBLEConnection({
deviceId: this.data.deviceId
})
this.setData({
connected: false,
chs: [],
canWrite: false,
})
},
closeBluetoothAdapter() {
wx.closeBluetoothAdapter()
this._discoveryStarted = false
},
})
最后在app.js中添加全局变量用于传递参数:
App({
globalData: {
deviceId: "",
name: "",
chs: [],
},
onLaunch: function () {
}
})
三、编译预览效果与结语
通过“微信开发者工具”编译后,预览上传,生成二维码后,使用手机端的微信扫码,即可更新为当前设计的小程序应用了,效果展示见底部视频,对之前使用蓝牙调试工具控制台灯的固件稍作修改,手机与板卡建立蓝牙连接后,台灯默认状态由原来的被点亮改成关灯状态,这样与小程序默认关闭状态同步。此次对FRDM-KW41Z开发板的评测到此结束,尽管板子有些年头,遇到的问题也比较怪,但体验下来,调试也比较方便,尤其是板卡无需外接JLink调试器,只需一个microUSB数据线就能在MCUXpresso IDE进行调试,NXP的IDE很方便,这点相信使用过的都深有体会。最后,感谢电子工程世界提供的“年终回炉”活动,让闲置的开发板“活”起来。
[localvideo]dbeefe7d656320222c33eb7751a0e6fd[/localvideo]
- 2025-03-19
-
发表了主题帖:
【KW41Z开发板测评】⑤蓝牙调试工具控制台灯
本帖最后由 yin_wu_qing 于 2025-3-20 13:06 编辑
一、前言
前段时间比较忙,最近终于缓过来,而距离规定评测KW41Z开发板的时限越来越近了,今天来分享一下前段时间个人评测时遇到的一些问题与经验。承接上期的④驱动直流电机的帖子,预期是通过该功能的实现,然后将其主干函数接口移入蓝牙串口demo中,然而结合PWM0四路通道、PWM1两路通道、PWM2两路通道分布来看,有两个通道的管脚被复用成串口,还有几个复用管脚没有被引出到两边排座,因此可用来兼顾“bluetooth_wireless_uart”工程的原本功能后,再调试出两路PWM显得比较紧凑,加之调试当中发现,板子两边的排座接口绝大多数是需要自行焊接好0Ω的电阻才能正常使用,这些结合官方提供的原理图可知,如下图标识的红色框。
二、工程配置
根据上述,咱也暂时不考虑将PWM功能接口移入蓝牙串口工程,在原计划上稍作更改,先实现一个蓝牙控制台灯功能。将SDK中的“frdmkw41z_wireless_examples_bluetooth_wireless_uart_bm”工程导出至MCUXpresso IDE中,然后编译下载到开发板中运行,发现即可以通过手机端的“IoT_Toolbox”工具去发送字符,然后在PC端的串口工具上接收打印;也可以通过PC端的串口调试助手给开发板发数据,然后在手机端的App显示接收数据,这样看来,K41Z的“bluetooth_wireless_uart_bm”工程似乎是K41Z相当于一个蓝牙数据透传网络中的“中间桥梁”。因此在此基础上去控制板上的某一个管脚输出高低电平,显得尤为简单。然而万万没想到的是也没那么顺利,听我娓娓道来。
①、也不知道当初这款板是为何设计的,两边的排座是不能直接使用的,因为板上都没焊接相应的贴片电阻。于是本人使用PTC19做为将来通过蓝牙来控制的GPIO口,按照原理图连接关系,需要将SH15短接,这里使用烙铁在SH15处焊接了根导线,正如下图所示。
②、接下来配置PTC19做为GPIOC_19,并初始化。
③、在BleApp_ReceivedUartStream()串口接收函数中添加逻辑处理代码。
三、代码完善
pin_mux.c源文件中,将PTC19添加至LED初始化函数中。
#define PIN0_IDX 0u /*!< Pin number for pin 0 in a port */
#define PIN1_IDX 1u /*!< Pin number for pin 1 in a port */
#define PIN18_IDX 18u /*!< Pin number for pin 18 in a port */
#define PIN19_IDX 19u /*!< Pin number for pin 19 in a port */
/*
* TEXT BELOW IS USED AS SETTING FOR THE PINS TOOL *****************************
BOARD_InitLEDs:
- options: {coreID: singlecore, enableClock: 'true'}
- pin_list:
- {pin_num: '16', peripheral: GPIOB, signal: 'GPIO, 0', pin_signal: PTB0/LLWU_P8/XTAL_OUT_EN/I2C0_SCL/CMP0_OUT/TPM0_CH1/CLKOUT}
- {pin_num: '37', peripheral: GPIOC, signal: 'GPIO, 1', pin_signal: PTC1/ANT_B/I2C0_SDA/UART0_RTS_b/TPM0_CH2/BLE_RF_ACTIVE}
- {pin_num: '7', peripheral: GPIOA, signal: 'GPIO, 19', pin_signal: TSI0_CH13/ADC0_SE5/PTA19/LLWU_P7/SPI1_PCS0/TPM2_CH1}
- {pin_num: '6', peripheral: GPIOA, signal: 'GPIO, 18', pin_signal: TSI0_CH12/PTA18/LLWU_P6/SPI1_SCK/TPM2_CH0}
- {pin_num: '47', peripheral: GPIOC, signal: 'GPIO, 18', pin_signal: TSI0_CH6/PTC18/LLWU_P2/SPI0_SIN/I2C1_SDA/UART0_TX/BSM_DATA/DTM_TX}
* BE CAREFUL MODIFYING THIS COMMENT - IT IS YAML SETTINGS FOR THE PINS TOOL ***
*/
/*FUNCTION**********************************************************************
*
* Function Name : BOARD_InitLEDs
* Description : Configures pin routing and optionally pin electrical features.
*
*END**************************************************************************/
void BOARD_InitLEDs(void) {
CLOCK_EnableClock(kCLOCK_PortA); /* Port A Clock Gate Control: Clock enabled */
CLOCK_EnableClock(kCLOCK_PortB); /* Port B Clock Gate Control: Clock enabled */
CLOCK_EnableClock(kCLOCK_PortC); /* Port C Clock Gate Control: Clock enabled */
PORT_SetPinMux(PORTA, PIN18_IDX, kPORT_MuxAsGpio); /* PORTA18 (pin 6) is configured as PTA18 */
PORT_SetPinMux(PORTA, PIN19_IDX, kPORT_MuxAsGpio); /* PORTA19 (pin 7) is configured as PTA19 */
PORT_SetPinMux(PORTB, PIN0_IDX, kPORT_MuxAsGpio); /* PORTB0 (pin 16) is configured as PTB0 */
PORT_SetPinMux(PORTC, PIN1_IDX, kPORT_MuxAsGpio); /* PORTC1 (pin 37) is configured as PTC1 */
PORT_SetPinMux(PORTC, PIN18_IDX, kPORT_MuxAsGpio); /* PORTC18 (pin 47) is configured as PTC18 */
PORT_SetPinMux(PORTC, PIN19_IDX, kPORT_MuxAsGpio); /* Add use to driver extern LED */
}
LED.c源文件中,初始化函数中添加对PTC19的初始化。
gpioOutputPinConfig_t Externdled =
{
.gpioPort = gpioPort_C_c,
.gpioPin = 19,
.outputLogic = 0,
.slewRate = pinSlewRate_Slow_c,
.driveStrength = pinDriveStrength_High_c
};
/******************************************************************************
* Name: LED_Init
* Description: Initialize the LED module
* Parameters: -
* Return: -
******************************************************************************/
void LED_Init
(
void
)
{
BOARD_InitLEDs();
(void)GpioOutputPinInit(ledPins, gLEDsOnTargetBoardCnt_c);
GpioSetPinOutput(&Externdled);
#if gLedRgbEnabled_d
LED_RgbLedInit();
#endif
/* allocate a timer for use in flashing LEDs */
#if gTMR_Enabled_d
mLEDTimerID = TMR_AllocateTimer();
#endif
#if gLedRgbEnabled_d && gRgbLedDimmingEnabled_d && gTMR_Enabled_d
/* allocate a timer for use in RGB dimming */
mRGBLedTimerID = TMR_AllocateTimer();
mRbgDimInfo.ongoing = FALSE;
mRbgDimInfo.interval = gRgbLedDimDefaultInterval_c;
#endif /* gLedRgbEnabled_d && gRgbLedDimmingEnabled_d && gTMR_Enabled_d */
}
wireless_uart.c源文件中的static void BleApp_ReceivedUartStream(uint8_t *pStream, uint16_t streamLength)函数中进行逻辑处理。
static void BleApp_ReceivedUartStream(uint8_t *pStream, uint16_t streamLength)
{
uint8_t *pBuffer = NULL;
/* Allocate buffer for asynchronous write */
pBuffer = MEM_BufferAlloc(streamLength);
if(pStream[0])
{
if(pStream[0]== 0x31)
{
GPIO_SetPinsOutput(GPIOC,19);
}
else if(pStream[0]==0x30)
{
GPIO_ClearPinsOutput(GPIOC,19);
}
}
if (pBuffer != NULL)
{
Serial_AsyncWrite(gAppSerMgrIf, pStream, streamLength, Uart_TxCallBack, pBuffer);
}
}
为了更好的用户体验,再将断开蓝牙后、再次广播时,将PTC19输出低,添加GPIO_ClearPinsOutput(GPIOC,19);
四、工程编译调试
1、基于以上操作,故将外接的LED连接到PTC19的排针座子上,结果编译后,将程序下载到板子上运行,短按一下板上的SW4,让开发板进入广播状态,然后手机端打开蓝牙、GPS,打开蓝牙调试助手,即可搜索到名为“NXP_WU”的蓝牙设备。连接后发现有创建三个服务,可通过Unknown Service服务发送字符给开发板。结果连接在PTC19管脚处的LED灯没任何反应,反而点亮了RGB灯的第一个管脚,即点亮了红色。
这就有点费解了,而且高低电平与代码中操作PTC19管脚的逻辑刚好相反。再结合pin_mux.c源码中的管脚复用情况,电路原理图的管脚分布,跳线帽设置也是出厂时的状态,一段时间了也没研究出是何原因!
2、既然可以间接控制PTC1管脚的高低电平状态,这里笔者索性将控制台灯的管脚设置在TP24测试点上(见上面RGB灯原理图),将杜邦线焊接在该点上,如下图所示:
3、在某宝上掏了几个模块与5V日光灯,做为实现蓝牙控制台灯的硬件资源。连接的示意图如下:
所对应的实物硬件连接如下:
4、运行代码,使用”蓝牙调试助手“App给K41Z发有效字符,从而实现蓝牙远程控制台灯,效果见底下视频。
五、小结
根据实际调试结果来看,PTC19管脚的信号控制似乎与PTC1(TP24)有关联,因为代码中逻辑处理部分是对PTC19管脚进行高低电平的控制,而实际应用是对PTC1起作用,通过焊接导线短接SH15处的PTC19排针座子并没有预设电平输出。尽管这块板比较陈旧,但想必硬件上也不会有大的问题吧?不然不会搜索一番,也没搜索到遇上类似的问题、现象。不知道咱们坛友有没有遇到类似的问题,欢迎回帖讨论。
[localvideo]e4b922818c55eb75fadc50b98e60bba5[/localvideo]
- 2025-02-26
-
回复了主题帖:
【KW41Z开发板测评】④驱动直流电机
freebsder 发表于 2025-2-25 16:24
这板子出的有些年头了
NXP官方已经没生产了吧,有些年头了,资料都显示是2017年的。
- 2025-02-24
-
发表了主题帖:
【KW41Z开发板测评】④驱动直流电机
前面分享了驱动TFT-LCD屏,本期使用KW41Z驱动12V直流电机。由于KW41Z支持3路TPM,可以通过GPIO口的复用配置成PWM输出管脚。开发板的主体框架如下:
再者开发板支持BLE4.2,Zigbee 3.0,Thread网络协议栈,因此可以做一些远程控制电机类产品,使产品功耗更低,更智能化。前段时间在“bluetooth_wireless_uart”工程上调试,计划通过手机app端发送字符来控制PWM输出占空比,无奈调试后仍然存有bug,此次使用PORTA18、PORTA19管脚复用为TPM2的输出通道,串口调试助手做为上位机,实现对两位直流电机的转速控制。
驱动直流电机,这里不得不引荐一下L298N电机驱动模块,L298N模块不仅价格低廉,而且易于使用。它能驱动2A至4A的大功率直流电机和步进电机,具备H桥结构,支持正反转及调速。通过单片机控制输入端逻辑电平,可实现电机的精确控制。同时,模块内置光耦隔离,确保系统稳定性。模块的正视图如下:
管脚说明:
Output A:接DC电机1或步进电机的A+和A-;
Output B:接DC电机2或步进电机的B+和B-;
12V Enable:如果使用输入电源大于12V的电源,请将跳线帽短接。输入电源小于12V时去除跳线帽可以提供5V电源输出;
+5V Power:当输入电源小于12V时且12V Enable处于断开状态,可以提供+5V电源输出;
Power Gnd:电源地;
+12V Power:连接电机电源,最大35V。输入电压大于12V时,请短接12V Enable针脚上的跳线帽;
A/B Enable:可用于输入PWM脉宽调制信号对电机进行调速控制。输入信号端IN1接高电平输入端IN2接低电平,电机M1正转。(如果信号端IN1接低电平, IN2接高电平,电机M1反转。)控制另一台电机是同样的方式,输入信号端IN3接高电平,输入端IN4接低电平,电机M2正转。(反之则反转),PWM信号端A控制M1调速,PWM信号端B控制M2调速。可参考下图表:
这里笔者暂且不考虑驱动电机的转向,因此只需两路PWM输出即可,实物连接如下:
编写代码,通过键盘输入“0”~“99”任意整数值到上位机串口调试助手中,发送给KW41Z,从而改变这两路PWM输出的占空比。
pin_mux.c
#include "fsl_common.h"
#include "fsl_port.h"
#include "pin_mux.h"
#define PIN6_IDX 6u /*!< Pin number for pin 6 in a port */
#define PIN7_IDX 7u /*!< Pin number for pin 7 in a port */
#define PIN18_IDX 18u /*!< Pin number for pin 18 in a port */
#define PIN19_IDX 19u /*!< Pin number for pin 19 in a port */
#define SOPT4_TPM2CH0SRC_TPM 0x00u /*!< TPM2 Channel 0 Input Capture Source Select: TPM2_CH0 signal */
#define SOPT5_LPUART0RXSRC_LPUART_RX 0x00u /*!< LPUART0 Receive Data Source Select: LPUART_RX pin */
/*FUNCTION**********************************************************************
*
* Function Name : BOARD_InitPins
* Description : Configures pin routing and optionally pin electrical features.
*
*END**************************************************************************/
void BOARD_InitPins(void) {
CLOCK_EnableClock(kCLOCK_PortA); /* Port A Clock Gate Control: Clock enabled */
CLOCK_EnableClock(kCLOCK_PortC); /* Port C Clock Gate Control: Clock enabled */
PORT_SetPinMux(PORTA, PIN18_IDX, kPORT_MuxAlt5); /* PORTA18 (pin 6) is configured as TPM2_CH0 */
PORT_SetPinMux(PORTA, PIN19_IDX, kPORT_MuxAlt5); /* PORTA19 (pin 7) is configured as TPM2_CH1 */
PORT_SetPinMux(PORTC, PIN6_IDX, kPORT_MuxAlt4); /* PORTC6 (pin 42) is configured as UART0_RX */
PORT_SetPinMux(PORTC, PIN7_IDX, kPORT_MuxAlt4); /* PORTC7 (pin 43) is configured as UART0_TX */
SIM->SOPT4 = ((SIM->SOPT4 &
(~(SIM_SOPT4_TPM2CH0SRC_MASK))) /* Mask bits to zero which are setting */
| SIM_SOPT4_TPM2CH0SRC(SOPT4_TPM2CH0SRC_TPM) /* TPM2 Channel 0 Input Capture Source Select: TPM2_CH0 signal */
);
SIM->SOPT5 = ((SIM->SOPT5 &
(~(SIM_SOPT5_LPUART0RXSRC_MASK))) /* Mask bits to zero which are setting */
| SIM_SOPT5_LPUART0RXSRC(SOPT5_LPUART0RXSRC_LPUART_RX) /* LPUART0 Receive Data Source Select: LPUART_RX pin */
);
}
main.c
#include "fsl_debug_console.h"
#include "board.h"
#include "fsl_tpm.h"
#include "fsl_device_registers.h"
#include "pin_mux.h"
#include <stdbool.h>
#include "clock_config.h"
/*******************************************************************************
* Definitions
******************************************************************************/
#define BOARD_TPM_BASEADDR TPM2
#define BOARD_FIRST_TPM_CHANNEL 0U
#define BOARD_SECOND_TPM_CHANNEL 1U
/* Get source clock for TPM driver */
#define TPM_SOURCE_CLOCK CLOCK_GetFreq(kCLOCK_McgFllClk)
/*******************************************************************************
* Variables
******************************************************************************/
volatile uint8_t getDutyValue = 0U;
volatile uint8_t updatedDutycycle = 1U;
/*******************************************************************************
* Code
******************************************************************************/
/*!
* [url=home.php?mod=space&uid=159083]@brief[/url] Main function
*/
int main(void)
{
tpm_config_t tpmInfo;
tpm_chnl_pwm_signal_param_t tpmParam[2];
/* Configure tpm params with frequency 24kHZ */
tpmParam[0].chnlNumber = (tpm_chnl_t)BOARD_FIRST_TPM_CHANNEL;
tpmParam[0].level = kTPM_LowTrue;
tpmParam[0].dutyCyclePercent = updatedDutycycle;
tpmParam[1].chnlNumber = (tpm_chnl_t)BOARD_SECOND_TPM_CHANNEL;
tpmParam[1].level = kTPM_LowTrue;
tpmParam[1].dutyCyclePercent = updatedDutycycle;
/* Board pin, clock, debug console init */
BOARD_InitPins();
BOARD_BootClockRUN();
BOARD_InitDebugConsole();
/* Select the clock source for the TPM counter as MCGPLLCLK */
CLOCK_SetTpmClock(1U);
/* Print a note to terminal */
PRINTF("\r\nTPM example to output PWM on 2 channels\r\n");
PRINTF("\r\nIf an LED is connected to the TPM pin, you will see a change in LED brightness if you enter different values");
PRINTF("\r\nIf no LED is connected to the TPM pin, then probe the signal using an oscilloscope");
TPM_GetDefaultConfig(&tpmInfo);
/* Initialize TPM module */
TPM_Init(BOARD_TPM_BASEADDR, &tpmInfo);
TPM_SetupPwm(BOARD_TPM_BASEADDR, tpmParam, 2U, kTPM_EdgeAlignedPwm, 24000U, TPM_SOURCE_CLOCK);
TPM_StartTimer(BOARD_TPM_BASEADDR, kTPM_SystemClock);
while (1)
{
do
{
PRINTF("\r\nPlease enter a value to update the Duty cycle:\r\n");
PRINTF("Note: The range of value is 0 to 99.\r\n");
PRINTF("For example: If enter '55', the duty cycle will be set to 55 percent.\r\n");
PRINTF("Value:");
SCANF("%d",&getDutyValue);
PRINTF("%d", getDutyValue);
PRINTF("\r\n");
if(getDutyValue > 99)
getDutyValue = 0;
} while (getDutyValue > 99U);
updatedDutycycle = getDutyValue;
/* Start PWM mode with updated duty cycle */
TPM_UpdatePwmDutycycle(BOARD_TPM_BASEADDR, (tpm_chnl_t)BOARD_FIRST_TPM_CHANNEL, kTPM_EdgeAlignedPwm,updatedDutycycle);
TPM_UpdatePwmDutycycle(BOARD_TPM_BASEADDR, (tpm_chnl_t)BOARD_SECOND_TPM_CHANNEL, kTPM_EdgeAlignedPwm,updatedDutycycle);
PRINTF("The duty cycle was successfully updated!\r\n");
}
}
驱动电机的效果展示如下视频,视频中不难发现,相同占空比输出,而电机转速不尽相同。这里是由于两个电机的转轴不同,因而影响电机转速。
[localvideo]770b3e68b211ce6ecdd8e35f3d2457dc[/localvideo]
- 2025-02-15
-
发表了主题帖:
【KW41Z开发板测评】③驱动TFT-LCD彩屏
一、概述
承接上期的【KW41Z开发板测评】②PWM输出呼吸灯帖子,由于春节放假期间事儿比较多,因此有段没更新贴子了,趁周末来补补。
本次是在上期的呼吸灯工程中实现驱动TFT-LCD屏。
前期资源准备
自购一块RGB串口屏,得捷电子商城上架:AFL128160A0-1.77N12NTM-ANO,屏为1.77寸,点像素:128X160,接口为SPI,驱动控制芯片为:ST7735S,这类屏兼容性、性价比非常友好。驱动该屏可使用GPIO口软件模拟SPI通信去驱动,也可采用SPI硬件接口去驱动,当然软件模拟方式易于移植,不受IO口的限制,但刷新数据没有硬件来得快,这是众所周知的,笔者此次采用软件模拟方式驱屏。
接下来寻找板上硬件接口,根据快速入门手册可知开发板正面的硬件资源分布如下图。
再结合《KW41ZData Sheet》可知管脚复用关系,这里笔者采用J1、J2排座上的PTC2、PTC3、PTC16~PTC19、P3V3、GND接口。
二、硬件连接
开发板与TFT-LCD屏的引脚连接对应关系如下:
笔者使用2.54mm杜邦线将管脚间对应连接上,至此硬件接口连接完毕。
三、部分参考代码
pin_mux.c
#include "fsl_common.h"
#include "fsl_port.h"
#include "pin_mux.h"
#include "fsl_gpio.h"
#define PIN6_IDX 6u /*!< Pin number for pin 6 in a port */
#define PIN7_IDX 7u /*!< Pin number for pin 7 in a port */
#define PIN18_IDX 18u /*!< Pin number for pin 18 in a port */
#define SOPT4_TPM2CH0SRC_TPM 0x00u /*!< TPM2 Channel 0 Input Capture Source Select: TPM2_CH0 signal */
#define SOPT5_LPUART0RXSRC_LPUART_RX 0x00u /*!< LPUART0 Receive Data Source Select: LPUART_RX pin */
void BOARD_InitPins(void) {
CLOCK_EnableClock(kCLOCK_PortA); /* Port A Clock Gate Control: Clock enabled */
CLOCK_EnableClock(kCLOCK_PortC); /* Port C Clock Gate Control: Clock enabled */
gpio_pin_config_t OUT_LOW_config = {
.pinDirection = kGPIO_DigitalOutput,
.outputLogic = 0U,
};
gpio_pin_config_t OUT_HIGH_config = {
.pinDirection = kGPIO_DigitalOutput,
.outputLogic = 1U,
};
GPIO_PinInit(GPIOC, 2U, &OUT_LOW_config);
GPIO_PinInit(GPIOC, 3U, &OUT_LOW_config);
GPIO_PinInit(GPIOC, 16U, &OUT_LOW_config);
GPIO_PinInit(GPIOC, 17U, &OUT_LOW_config);
GPIO_PinInit(GPIOC, 18U, &OUT_LOW_config);
GPIO_PinInit(GPIOC, 19U, &OUT_HIGH_config);
PORT_SetPinMux(PORTC,2u,kPORT_MuxAsGpio);
PORT_SetPinMux(PORTC,3u,kPORT_MuxAsGpio);
PORT_SetPinMux(PORTC,16u,kPORT_MuxAsGpio);
PORT_SetPinMux(PORTC,17u,kPORT_MuxAsGpio);
PORT_SetPinMux(PORTC,18u,kPORT_MuxAsGpio);
PORT_SetPinMux(PORTC,19u,kPORT_MuxAsGpio);
PORT_SetPinMux(PORTA, PIN18_IDX, kPORT_MuxAlt5); /* PORTA18 (pin 6) is configured as TPM2_CH0 */
PORT_SetPinMux(PORTC, PIN6_IDX, kPORT_MuxAlt4); /* PORTC6 (pin 42) is configured as UART0_RX */
PORT_SetPinMux(PORTC, PIN7_IDX, kPORT_MuxAlt4); /* PORTC7 (pin 43) is configured as UART0_TX */
SIM->SOPT4 = ((SIM->SOPT4 &
(~(SIM_SOPT4_TPM2CH0SRC_MASK))) /* Mask bits to zero which are setting */
| SIM_SOPT4_TPM2CH0SRC(SOPT4_TPM2CH0SRC_TPM) /* TPM2 Channel 0 Input Capture Source Select: TPM2_CH0 signal */
);
SIM->SOPT5 = ((SIM->SOPT5 &
(~(SIM_SOPT5_LPUART0RXSRC_MASK))) /* Mask bits to zero which are setting */
| SIM_SOPT5_LPUART0RXSRC(SOPT5_LPUART0RXSRC_LPUART_RX) /* LPUART0 Receive Data Source Select: LPUART_RX pin */
);
}
tpm_simple_pwm.c
#include "fsl_debug_console.h"
#include "board.h"
#include "fsl_tpm.h"
#include "pin_mux.h"
#include "lcd_init.h"
#include "lcd.h"
#include "pic.h"
#define BOARD_TPM_BASEADDR TPM2
#define BOARD_TPM_CHANNEL 0U
/* Interrupt to enable and flag to read; depends on the TPM channel used */
#define TPM_CHANNEL_INTERRUPT_ENABLE kTPM_Chnl0InterruptEnable
#define TPM_CHANNEL_FLAG kTPM_Chnl0Flag
/* Interrupt number and interrupt handler for the TPM instance used */
#define TPM_INTERRUPT_NUMBER TPM2_IRQn
#define TPM_LED_HANDLER TPM2_IRQHandler
/* Get source clock for TPM driver */
#define TPM_SOURCE_CLOCK CLOCK_GetFreq(kCLOCK_McgFllClk)
#define Year 2025
volatile bool brightnessUp = true; /* Indicate LED is brighter or dimmer */
volatile uint8_t updatedDutycycle = 10U;
volatile uint8_t getCharValue = 0U;
volatile uint32_t g_systickCounter;
uint8_t dutyCycle = 0;
uint8_t var = 0;
void SysTick_Handler(void)
{
if (g_systickCounter != 0U)
{
g_systickCounter--;
}
}
void SysTick_DelayTicks(uint32_t n)
{
g_systickCounter = n;
while (g_systickCounter != 0U)
{
}
}
void Display_title(void)
{
SysTick_DelayTicks(1U);
LCD_Fill(0,0,LCD_W,LCD_H,WHITE);
LCD_ShowIntNum(8,10,Year,4,RED,BLACK,16);
LcdShow16x16Hz(40,10,0,RED,BLACK);//"蛇"
LcdShow16x16Hz(56,10,1,RED,BLACK);//"年"
LcdShow16x16Hz(72,10,2,RED,BLACK);//"的"
LcdShow16x16Hz(88,10,3,RED,BLACK);//"祝"
LcdShow16x16Hz(104,10,4,RED,BLACK);//"福"
LcdShow16x16Hz(8,35,5,GREEN,BLACK);//"巳"
LcdShow16x16Hz(24,35,6,GREEN,BLACK);//"巳"
LcdShow16x16Hz(40,35,7,GREEN,BLACK);//"如"
LcdShow16x16Hz(56,35,8,GREEN,BLACK);//"意"
LcdShow16x16Hz(8,60,9,GREEN,BLACK);//"生"
LcdShow16x16Hz(24,60,10,GREEN,BLACK);//"生"
LcdShow16x16Hz(40,60,11,GREEN,BLACK);//"不"
LcdShow16x16Hz(56,60,12,GREEN,BLACK);//"息"
LcdShow16x16Hz(8,85,13,GREEN,BLACK);//"工"
LcdShow16x16Hz(24,85,14,GREEN,BLACK);//"程"
LcdShow16x16Hz(40,85,15,GREEN,BLACK);//"世"
LcdShow16x16Hz(56,85,16,GREEN,BLACK);//"界"
LcdShow16x16Hz(8,110,17,GREEN,BLACK);//"再"
LcdShow16x16Hz(24,110,18,GREEN,BLACK);//"创"
LcdShow16x16Hz(40,110,19,GREEN,BLACK);//"佳"
LcdShow16x16Hz(56,110,20,GREEN,BLACK);//"绩"
LCD_ShowString(20,142,"2025-02-14",BLUE,LGRAY,16,0);
SysTick_DelayTicks(3000U);
}
int main(void)
{
tpm_config_t tpmInfo;
tpm_chnl_pwm_signal_param_t tpmParam;
tpm_pwm_level_select_t pwmLevel = kTPM_LowTrue;
/* Configure tpm params with frequency 24kHZ */
tpmParam.chnlNumber = (tpm_chnl_t)BOARD_TPM_CHANNEL;
tpmParam.level = pwmLevel;
tpmParam.dutyCyclePercent = updatedDutycycle;
/* Board pin, clock, debug console init */
BOARD_InitPins();
BOARD_BootClockRUN();
BOARD_InitDebugConsole();
/* Select the clock source for the TPM counter as MCGPLLCLK */
CLOCK_SetTpmClock(1U);
/* Set systick reload value to generate 1ms interrupt */
if (SysTick_Config(SystemCoreClock / 1000U))
{
while (1)
{
}
}
TPM_GetDefaultConfig(&tpmInfo);
/* Initialize TPM module */
TPM_Init(BOARD_TPM_BASEADDR, &tpmInfo);
TPM_SetupPwm(BOARD_TPM_BASEADDR, &tpmParam, 1U, kTPM_CenterAlignedPwm, 24000U, TPM_SOURCE_CLOCK);
TPM_StartTimer(BOARD_TPM_BASEADDR, kTPM_SystemClock);
LCD_Init();
while (1)
{
LCD_Fill(0,0,LCD_W,LCD_H,LIGHTGREEN);
LCD_ShowPicture(0,0,128,160,gImage_1);
SysTick_DelayTicks(1000U);
Display_title();
}
}
lcd_init.c
#include "lcd_init.h"
#include "board.h"
extern void SysTick_DelayTicks(uint32_t n);
/******************************************************************************
函数说明:LCD串行数据写入函数
入口数据:dat 要写入的串行数据
返回值: 无
******************************************************************************/
void LCD_Writ_Bus(u8 dat)
{
u8 i;
LCD_CS_Clr();
for(i=0;i<8;i++)
{
LCD_SCLK_Clr();
if(dat&0x80)
{
LCD_MOSI_Set();
}
else
{
LCD_MOSI_Clr();
}
LCD_SCLK_Set();
dat<<=1;
}
LCD_CS_Set();
}
/******************************************************************************
函数说明:LCD写入数据
入口数据:dat 写入的数据
返回值: 无
******************************************************************************/
void LCD_WR_DATA8(u8 dat)
{
LCD_Writ_Bus(dat);
}
/******************************************************************************
函数说明:LCD写入数据
入口数据:dat 写入的数据
返回值: 无
******************************************************************************/
void LCD_WR_DATA(u16 dat)
{
LCD_Writ_Bus(dat>>8);
LCD_Writ_Bus(dat);
}
/******************************************************************************
函数说明:LCD写入命令
入口数据:dat 写入的命令
返回值: 无
******************************************************************************/
void LCD_WR_REG(u8 dat)
{
LCD_DC_Clr();//写命令
LCD_Writ_Bus(dat);
LCD_DC_Set();//写数据
}
/******************************************************************************
函数说明:设置起始和结束地址
入口数据:x1,x2 设置列的起始和结束地址
y1,y2 设置行的起始和结束地址
返回值: 无
******************************************************************************/
void LCD_Address_Set(u16 x1,u16 y1,u16 x2,u16 y2)
{
if(USE_HORIZONTAL==0)
{
LCD_WR_REG(0x2a);//列地址设置
LCD_WR_DATA(x1+2);
LCD_WR_DATA(x2+2);
LCD_WR_REG(0x2b);//行地址设置
LCD_WR_DATA(y1+1);
LCD_WR_DATA(y2+1);
LCD_WR_REG(0x2c);//储存器写
}
else if(USE_HORIZONTAL==1)
{
LCD_WR_REG(0x2a);//列地址设置
LCD_WR_DATA(x1+2);
LCD_WR_DATA(x2+2);
LCD_WR_REG(0x2b);//行地址设置
LCD_WR_DATA(y1+1);
LCD_WR_DATA(y2+1);
LCD_WR_REG(0x2c);//储存器写
}
else if(USE_HORIZONTAL==2)
{
LCD_WR_REG(0x2a);//列地址设置
LCD_WR_DATA(x1+1);
LCD_WR_DATA(x2+1);
LCD_WR_REG(0x2b);//行地址设置
LCD_WR_DATA(y1+2);
LCD_WR_DATA(y2+2);
LCD_WR_REG(0x2c);//储存器写
}
else
{
LCD_WR_REG(0x2a);//列地址设置
LCD_WR_DATA(x1+1);
LCD_WR_DATA(x2+1);
LCD_WR_REG(0x2b);//行地址设置
LCD_WR_DATA(y1+2);
LCD_WR_DATA(y2+2);
LCD_WR_REG(0x2c);//储存器写
}
}
void LCD_Init(void)
{
LCD_RES_Clr();//复位
SysTick_DelayTicks(1);
LCD_RES_Set();
SysTick_DelayTicks(1);
LCD_BLK_Set();//打开背光
SysTick_DelayTicks(120);
//************* Start Initial Sequence **********//
LCD_WR_REG(0x11); //Sleep out
SysTick_DelayTicks(120); //Delay 120ms
//----------------ST7735S Frame Rate---------------------//
LCD_WR_REG(0xB1);
LCD_WR_DATA8(0x05);
LCD_WR_DATA8(0x3C);
LCD_WR_DATA8(0x3C);
LCD_WR_REG(0xB2);
LCD_WR_DATA8(0x05);
LCD_WR_DATA8(0x3C);
LCD_WR_DATA8(0x3C);
LCD_WR_REG(0xB3);
LCD_WR_DATA8(0x05);
LCD_WR_DATA8(0x3C);
LCD_WR_DATA8(0x3C);
LCD_WR_DATA8(0x05);
LCD_WR_DATA8(0x3C);
LCD_WR_DATA8(0x3C);
//--------------------End ST7735S Frame Rate---------------//
LCD_WR_REG(0xB4); //Dot inversion
LCD_WR_DATA8(0x03);
//--------------------ST7735S Power Sequence---------------//
LCD_WR_REG(0xC0);
LCD_WR_DATA8(0x28);
LCD_WR_DATA8(0x08);
LCD_WR_DATA8(0x04);
LCD_WR_REG(0xC1);
LCD_WR_DATA8(0XC0);
LCD_WR_REG(0xC2);
LCD_WR_DATA8(0x0D);
LCD_WR_DATA8(0x00);
LCD_WR_REG(0xC3);
LCD_WR_DATA8(0x8D);
LCD_WR_DATA8(0x2A);
LCD_WR_REG(0xC4);
LCD_WR_DATA8(0x8D);
LCD_WR_DATA8(0xEE);
//-----------------End ST7735S Power Sequence--------------//
LCD_WR_REG(0xC5); //VCOM
LCD_WR_DATA8(0x1A);
LCD_WR_REG(0x36); //MX, MY, RGB mode
if(USE_HORIZONTAL==0)LCD_WR_DATA8(0x00);
else if(USE_HORIZONTAL==1)LCD_WR_DATA8(0xC0);
else if(USE_HORIZONTAL==2)LCD_WR_DATA8(0x70);
else LCD_WR_DATA8(0xA0);
//-----------------ST7735S Gamma Sequence------------------//
LCD_WR_REG(0xE0);
LCD_WR_DATA8(0x04);
LCD_WR_DATA8(0x22);
LCD_WR_DATA8(0x07);
LCD_WR_DATA8(0x0A);
LCD_WR_DATA8(0x2E);
LCD_WR_DATA8(0x30);
LCD_WR_DATA8(0x25);
LCD_WR_DATA8(0x2A);
LCD_WR_DATA8(0x28);
LCD_WR_DATA8(0x26);
LCD_WR_DATA8(0x2E);
LCD_WR_DATA8(0x3A);
LCD_WR_DATA8(0x00);
LCD_WR_DATA8(0x01);
LCD_WR_DATA8(0x03);
LCD_WR_DATA8(0x13);
LCD_WR_REG(0xE1);
LCD_WR_DATA8(0x04);
LCD_WR_DATA8(0x16);
LCD_WR_DATA8(0x06);
LCD_WR_DATA8(0x0D);
LCD_WR_DATA8(0x2D);
LCD_WR_DATA8(0x26);
LCD_WR_DATA8(0x23);
LCD_WR_DATA8(0x27);
LCD_WR_DATA8(0x27);
LCD_WR_DATA8(0x25);
LCD_WR_DATA8(0x2D);
LCD_WR_DATA8(0x3B);
LCD_WR_DATA8(0x00);
LCD_WR_DATA8(0x01);
LCD_WR_DATA8(0x04);
LCD_WR_DATA8(0x13);
//-------------------End ST7735S Gamma Sequence--------------//
LCD_WR_REG(0x3A); //65k mode
LCD_WR_DATA8(0x05);
LCD_WR_REG(0x29); //Display on
}
lcd_init.h
#ifndef __LCD_INIT_H
#define __LCD_INIT_H
#include "board.h"
#include "pin_mux.h"
#define USE_HORIZONTAL 1 //设置横屏或者竖屏显示 0或1为竖屏 2或3为横屏
#if USE_HORIZONTAL==0||USE_HORIZONTAL==1
#define LCD_W 128
#define LCD_H 160
#else
#define LCD_W 160
#define LCD_H 128
#endif
#define BOARD_LCD_GPIO GPIOC
#define BOARD_LCD_GPIO_SCLK_PIN (2U)
#define BOARD_LCD_GPIO_CS_PIN (3U)
#define BOARD_LCD_GPIO_RES_PIN (16U)
#define BOARD_LCD_GPIO_DC_PIN (17U)
#define BOARD_LCD_GPIO_MOSI_PIN (18U)
#define BOARD_LCD_BLK_GPIO GPIOC
#define BOARD_LCD_GPIO_BLK_PIN (19U)
//-----------------LCD端口定义----------------
#define LCD_SCLK_Clr() GPIO_WritePinOutput(BOARD_LCD_GPIO,BOARD_LCD_GPIO_SCLK_PIN,0)//SCL SCLK
#define LCD_SCLK_Set() GPIO_WritePinOutput(BOARD_LCD_GPIO,BOARD_LCD_GPIO_SCLK_PIN,1)
#define LCD_CS_Clr() GPIO_WritePinOutput(BOARD_LCD_GPIO,BOARD_LCD_GPIO_CS_PIN,0)//CS
#define LCD_CS_Set() GPIO_WritePinOutput(BOARD_LCD_GPIO,BOARD_LCD_GPIO_CS_PIN,1)
#define LCD_MOSI_Clr() GPIO_WritePinOutput(BOARD_LCD_GPIO,BOARD_LCD_GPIO_MOSI_PIN,0)//SDA MOSI
#define LCD_MOSI_Set() GPIO_WritePinOutput(BOARD_LCD_GPIO,BOARD_LCD_GPIO_MOSI_PIN,1)
#define LCD_DC_Clr() GPIO_WritePinOutput(BOARD_LCD_GPIO,BOARD_LCD_GPIO_DC_PIN,0)//DC
#define LCD_DC_Set() GPIO_WritePinOutput(BOARD_LCD_GPIO,BOARD_LCD_GPIO_DC_PIN,1)
#define LCD_RES_Clr() GPIO_WritePinOutput(BOARD_LCD_GPIO,BOARD_LCD_GPIO_RES_PIN,0)//RES
#define LCD_RES_Set() GPIO_WritePinOutput(BOARD_LCD_GPIO,BOARD_LCD_GPIO_RES_PIN,1)
#define LCD_BLK_Clr() GPIO_WritePinOutput(BOARD_LCD_BLK_GPIO,BOARD_LCD_GPIO_BLK_PIN,0)//BLK
#define LCD_BLK_Set() GPIO_WritePinOutput(BOARD_LCD_BLK_GPIO,BOARD_LCD_GPIO_BLK_PIN,1)
void LCD_GPIO_Init(void);//初始化GPIO
void LCD_Writ_Bus(u8 dat);//模拟SPI时序
void LCD_WR_DATA8(u8 dat);//写入一个字节
void LCD_WR_DATA(u16 dat);//写入两个字节
void LCD_WR_REG(u8 dat);//写入一个指令
void LCD_Address_Set(u16 x1,u16 y1,u16 x2,u16 y2);//设置坐标函数
void LCD_Init(void);//LCD初始化
#endif
四、图片字符显示效果
图片转C数组需要借助“Img2Lcd”工具,这里笔者将DIY画的bmp图片转C数组,参数设置详情如下:
显示中文字符,这里需要借助“PCtoLCD2002”工具,工具选项设置参数标记一下:
最终显示的效果呈现如下,使用GPIO口软件模拟SPI通信驱动,刷起图来的确没有硬件方式刷得快。
[localvideo]1472c2d4dc434d75629c53e42b78c157[/localvideo]