2024年寒假练 - 基于TI MSPM0L1306的综合开发平台的智能温度调节展示系统
一、初始要求:
- 设计一个基于温度变化的展示系统。
- 热敏电阻检测温度变化。
- 彩色LCD显示当前温度和设定阈值。
- LED和RGB LED根据温度高低展示不同颜色。
- 按键用于设置温度阈值。
- 电位计调节LED显示的模式和强度。
二、改进要求:
基于硬件的实际情况,在原要求的基础上进行改善:
- 热敏电阻检测温度变化。
- 彩色LCD显示当前温度和设定的阈值,以及加热情况,并实现了开屏动画和刷新功能,同时数据颜色也会根据情况进行变化。
- RGBLED根据温度高低显示不同颜色。
- 旋转式编码器用于调整温度最大阈值。
- 由于旋转式编码器的ADC获取值存在一定误差,彩色LCD输出ADC获取值以供参考。
- 摇杆用于控制RGBLED显示状态以及刷新。
- 由于仅有一个按键可供使用,设定其用于加热开关。
三、功能介绍:
开屏动画:
按下复位键后,LCD屏中会显示硬禾学堂的LOGO并延时一段时间,之后刷新显示相应的数据,分别为当前温度,设定的温度最大阈值和最小阈值,当前加热状态以及ADC获取值。
温度显示:
在当前温度低于最低阈值时,RGBLED会显示蓝色,同时LCD中当前温度的数值也会显示蓝色;
在当前温度高于最低阈值、低于最高阈值时,RGBLED会显示绿色,同时LCD中当前温度的数值也会显示黑色;
在当前温度高于最高阈值时,RGBLED会显示红色,同时LCD中当前温度的数值也会显示红色。
阈值调整:
在使用旋转式编码器调整温度阈值时,默认处于增加模式,此时最大阈值显示蓝色,旋转编码器即可增加温度阈值,但该阈值不能超过程序内部设定的最大阈值;
按下旋转式编码器,即可在增加模式和降低模式里切换,在降低模式下,最大阈值显示红色,旋转编码器即可降低温度阈值,但该阈值不能超过程序内部设定的最小阈值。
LED模式:
摇杆可用于控制RGBLED显示状态和刷新功能:
将摇杆向下拨动,RGBLED调整至呼吸灯模式;将摇杆向右拨动,RGBLED调整至闪烁模式;将摇杆向左拨动,RGBLED调整至默认模式,即恒亮模式。
屏幕刷新:
将摇杆向上拨动,将刷新LCD屏,重新出现硬禾学堂的LOGO,并将阈值调整至初始状态。
加热控制:
右下角按键用于控制加热开关,该开关的启用需要连接5V电压,否则无法加热。按下按键后,LCD中显示“Heating”,同时温度迅速上升;在达到最大阈值后,会自动停止加热,LCD中显示默认“Colding”即停止加热,或者也可以在超过最大阈值前手动按下按钮,关闭加热模式。
以上三功能均不宜用图片展示,请通过视频观察结果。
四、实现思路:
通过SPI协议实现LCD屏的显示,其中SPI通过DMA实现传输。
通过读取ADC获取值,判断是否按下按键、是否按下编码器、是否旋转编码器并在LCD屏上显示。
通过获取PWM波的频率和占空比,判断摇杆拨向方向。
通过IIC协议获取温度传感芯片的温度值,并在LCD屏上显示。
通过调整三个PWM波的占空比,实现RGBLED的恒亮、闪烁、呼吸模式转换。
五、实现流程:
以下为流程图以及相应函数名:
六、重要代码介绍(仅展示部分函数,具体函数请下载代码包):
LCD显示:
本次寒假练的扩展板所使用的LCD屏是128*128的ST7735型号,通过淘宝搜索得到相应LCD屏的启动代码。之后,根据TFT的传输协议,修改各个引脚配置、基于此代码进行相关移植,即可实现LCD屏显示有关数据。之后,根据TI-L1306所给样例例程中的SPI-DMA中断传输函数,并加以修改,实现了SPI-DMA的LCD显示。
void SPI_WriteData(u8 Data)
{
u8 gTxPacket[1];
gTxPacket[0] = Data;
LCD_CS_CLR;
delay_us(1);
DL_DMA_setSrcAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t) &gTxPacket[0]);
DL_DMA_setDestAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t)(&SPI_INST->TXDATA));
DL_DMA_setTransferSize(DMA, DMA_CH0_CHAN_ID, 1);
DL_DMA_enableChannel(DMA, DMA_CH0_CHAN_ID);
// DL_SPI_transmitData8(SPI_INST, Data);
// while(DL_SPI_isBusy(SPI_INST));
delay_us(1);
LCD_CS_SET;
}
void SPI_INST_IRQHandler(void)
{
switch (DL_SPI_getPendingInterrupt(SPI_INST)) {
case DL_SPI_IIDX_DMA_DONE_TX:
/* DMA is done transferring data from gTxPacket to TXFIFO */
//gDMATXDataTransferred = true;
break;
case DL_SPI_IIDX_TX_EMPTY:
/* SPI is done transmitting data and TXFIFO is empty */
//gSPIDataTransmitted = true;
break;
default:
break;
}
}
温度IIC获取:
本扩展板所使用的温度传感器的型号是NST1112-DSTR,该传感器通过IIC协议,输出两个字节的数据。相对于如何将数据转化为温度,更有难度的是如何使用L1306与其进行通信。我参照所给样例例程中的IIC中断传输函数、NST1112-DSTR技术手册和IIC通信协议,对传输数组和传输地址进行修改,最终实现读取到传感器传输值,并根据手册中的转化标准进行处理得到实际温度。
#include "ti_msp_dl_config.h"
#include "string.h"
#include "IIC.h"
#include "GUI.h"
#include "stdio.h"
float Temperature;
/* Data sent to the Target */
uint8_t gTxPacket[I2C_TX_PACKET_SIZE] = {0x00};
/* Data received from Target */
volatile uint8_t gRxPacket[I2C_RX_PACKET_SIZE];
char num_str_now[12] = {'0', '0', '0', '.', '0', '0', '0', '0', '\0'};
float GetTemp(uint8_t volatile* buff){ //基于NST1112-DSTR技术手册,温度转化函数
uint8_t high, low;
uint16_t temp, num;
float temperature = 0;
high = buff[0];
low = buff[1];
if(high & 0x80){
temp = (high << 8) | low;
num = (((~temp) >> 4) + 1) & 0x0fff;
temperature = -0.0625 * num;
}else{
temp = (high << 8) | low;
num = temp >> 4;
temperature = 0.0625 * num;
}
return temperature;
}
void TempGet(){
DL_I2C_fillControllerTXFIFO(I2C_INST, &gTxPacket[0], I2C_TX_PACKET_SIZE);
/* Wait for I2C to be Idle */
while (!(
DL_I2C_getControllerStatus(I2C_INST) & DL_I2C_CONTROLLER_STATUS_IDLE))
;
/* Send the packet to the controller.
* This function will send Start + Stop automatically.
*/
DL_I2C_startControllerTransfer(I2C_INST, I2C_TARGET_ADDRESS,
DL_I2C_CONTROLLER_DIRECTION_TX, I2C_TX_PACKET_SIZE);
/* Poll until the Controller writes all bytes */
while (DL_I2C_getControllerStatus(I2C_INST) &
DL_I2C_CONTROLLER_STATUS_BUSY_BUS)
;
/* Trap if there was an error */
if (DL_I2C_getControllerStatus(I2C_INST) &
DL_I2C_CONTROLLER_STATUS_ERROR) {
/* LED will remain high if there is an error */
__BKPT(0);
}
/* Wait for I2C to be Idle */
while (!(
DL_I2C_getControllerStatus(I2C_INST) & DL_I2C_CONTROLLER_STATUS_IDLE))
;
/* Add delay between transfers */
delay_cycles(1000);
/* Send a read request to Target */
DL_I2C_startControllerTransfer(I2C_INST, I2C_TARGET_ADDRESS,
DL_I2C_CONTROLLER_DIRECTION_RX, I2C_RX_PACKET_SIZE);
/*
* Receive all bytes from target. LED will remain high if not all bytes
* are received
*/
for (uint8_t i = 0; i < I2C_RX_PACKET_SIZE; i++) {
while (DL_I2C_isControllerRXFIFOEmpty(I2C_INST))
;
gRxPacket[i] = DL_I2C_receiveControllerData(I2C_INST);
}
Temperature = GetTemp(gRxPacket);
snprintf(num_str_now, 12, "%08.04f℃", Temperature);
}
ADC获取:
ADC获取相对来说十分简单,因此在结合按键和编码器的电阻分压网络中,可以很容易的判断操作。可以通过LCD屏或者UART串口进行ADC显示,并得到各个操作下的ADC值,进行分段处理,实现相应功能。
#include "keyADC.h"
#include "GUI.h"
#include "stdio.h"
volatile bool gCheckADC;
volatile uint16_t gAdcResult;
volatile uint16_t gAdc;
unsigned char valtage[5] = {'0', '0', '0', '0', '\0'};
void ADCinit(void)
{
NVIC_EnableIRQ(ADC12_INST_INT_IRQN);
gCheckADC = false;
}
void ADCget(void)
{
DL_ADC12_startConversion(ADC12_INST);
while(gCheckADC == false){
__WFE();
};
gCheckADC = false;
gAdc = DL_ADC12_getMemResult(ADC12_INST, DL_ADC12_MEM_IDX_0);
DL_ADC12_enableConversions(ADC12_INST);
snprintf(valtage, 5, "%04d", gAdc);
}
void ADC12_INST_IRQHandler(void)
{
switch (DL_ADC12_getPendingInterrupt(ADC12_INST)) {
case DL_ADC12_IIDX_MEM0_RESULT_LOADED:
gCheckADC = true;
break;
default:
break;
}
}
PWM波频率、占空比获取:
TI-L1306所给样例例程中有PWM捕获函数,我们仅需要稍作修改,就可以使用。
#include "ti_msp_dl_config.h"
#include "PwmCapture.h"
#include "stdio.h"
#include "GUI.h"
volatile uint32_t gCaptureCnt = 0;
volatile bool gSynced;
volatile bool gCheckCaptures;
uint32_t gLoadValue;
volatile static uint32_t pwmPeriod;
__attribute__((unused)) volatile static uint32_t pwmDuty;
int MidPeriod = 410;
int MaxPeriod = 525;
int MinPeriod = 285;
char ErrorPeriod = 20;
char MidDuty = 55;
char MaxDuty = 80;
char MinDuty = 35;
char ErrorDuty = 5;
char str[10] = "hello";
void PwmCaptureInit(void)
{
/*
* This value is used to reload timer manually. Due to timer capture
* limitation
*/
gLoadValue = DL_TimerG_getLoadValue(CAPTURE_0_INST);
/* Initialize capture global states */
gSynced = false;
gCheckCaptures = false;
/*
* Forcing timers to halt immediately to prevent timers getting out of sync
* when code is halted
*/
DL_TimerG_setCoreHaltBehavior(CAPTURE_0_INST, DL_TIMER_CORE_HALT_IMMEDIATE);
NVIC_EnableIRQ(CAPTURE_0_INST_INT_IRQN);
DL_TimerG_startCounter(CAPTURE_0_INST);
}
int PwmCapture(void)
{
while (false == gCheckCaptures) {
__WFE();
}
gCheckCaptures = false;
/*
* Calculate PWM period and PWM duty cycle. IMPORTANT: These calculation
* assume timer is running in DOWN counting mode
*/
pwmPeriod = gLoadValue - gCaptureCnt;
pwmDuty = ((gLoadValue - DL_TimerG_getCaptureCompareValue(CAPTURE_0_INST, DL_TIMER_CC_0_INDEX)) * 100) / pwmPeriod;
if(pwmPeriod >= MaxPeriod - ErrorPeriod && pwmPeriod <= MaxPeriod + ErrorPeriod){//左
return 1;
}else if(pwmPeriod >= MinPeriod - ErrorPeriod && pwmPeriod <= MinPeriod + ErrorPeriod){//右
return 2;
}else if(pwmDuty >= MaxDuty - ErrorDuty && pwmDuty <= MaxDuty + ErrorDuty){//下
return 3;
}else if(pwmDuty >= MinDuty - ErrorDuty && pwmDuty <= MinDuty + ErrorDuty){//上
return 4;
}
return 0;
}
void CAPTURE_0_INST_IRQHandler(void)
{
switch (DL_TimerG_getPendingInterrupt(CAPTURE_0_INST)) {
case DL_TIMERG_IIDX_CC1_DN:
if (gSynced == true) {
gCaptureCnt = DL_TimerG_getCaptureCompareValue(CAPTURE_0_INST, DL_TIMER_CC_1_INDEX);
gCheckCaptures = true;
} else {
gSynced = true;
}
/* Manual reload is needed to workaround timer capture limitation */
DL_TimerG_setTimerCount(CAPTURE_0_INST, gLoadValue);
break;
case DL_TIMERG_IIDX_ZERO:
/* If Timer reaches zero then no PWM signal is detected and it
* requires re-synchronization
*/
gSynced = false;
break;
default:
break;
}
}
RGBLED模式改变:
添加三个PWM通道并开启中断,通过上述PWM波获取判断RGBLED显示模式后,结合定时器和PWM占空比设置函数,实现呼吸灯、闪烁和恒亮。
#include "LED.h"
char tick = 0;
static float Duty = 0;
static bool flag = 1;
void LEDInit(){
DL_TimerG_startCounter(PWM_B1_INST);
DL_TimerG_startCounter(PWM_G2_INST);
DL_TimerG_startCounter(PWM_R3_INST);
DL_TimerG_setCaptureCompareValue(PWM_B1_INST, 0, GPIO_PWM_B1_C1_IDX);
DL_TimerG_setCaptureCompareValue(PWM_G2_INST, 0, GPIO_PWM_G2_C0_IDX);
DL_TimerG_setCaptureCompareValue(PWM_R3_INST, 0, GPIO_PWM_R3_C0_IDX);
}
void ModeLed(char color, char temp){
if(color == 1){
DL_TimerG_setCaptureCompareValue(PWM_G2_INST, 0, GPIO_PWM_G2_C0_IDX);
DL_TimerG_setCaptureCompareValue(PWM_R3_INST, 0, GPIO_PWM_R3_C0_IDX);
switch(temp){
case 1:
DL_TimerG_setCaptureCompareValue(PWM_B1_INST, 100, GPIO_PWM_B1_C1_IDX);
break;
case 2:
if(tick < 100){
DL_TimerG_setCaptureCompareValue(PWM_B1_INST, 100, GPIO_PWM_B1_C1_IDX);
}else{
DL_TimerG_setCaptureCompareValue(PWM_B1_INST, 0, GPIO_PWM_B1_C1_IDX);
}
break;
case 3:
if(flag){
Duty += 0.5;
}else{
Duty -= 0.5;
}
if(Duty == 100){
flag = 0;
}else if(Duty == 0){
flag = 1;
}
DL_TimerG_setCaptureCompareValue(PWM_B1_INST, Duty, GPIO_PWM_B1_C1_IDX);
break;
default:
break;
}
}else if (color == 2){
DL_TimerG_setCaptureCompareValue(PWM_B1_INST, 0, GPIO_PWM_B1_C1_IDX);
DL_TimerG_setCaptureCompareValue(PWM_R3_INST, 0, GPIO_PWM_R3_C0_IDX);
switch(temp){
case 1:
DL_TimerG_setCaptureCompareValue(PWM_G2_INST, 100, GPIO_PWM_G2_C0_IDX);
break;
case 2:
if(tick < 100){
DL_TimerG_setCaptureCompareValue(PWM_G2_INST, 100, GPIO_PWM_G2_C0_IDX);
}else{
DL_TimerG_setCaptureCompareValue(PWM_G2_INST, 0, GPIO_PWM_G2_C0_IDX);
}
break;
case 3:
if(flag){
Duty += 0.5;
}else{
Duty -= 0.5;
}
if(Duty == 100){
flag = 0;
}else if(Duty == 0){
flag = 1;
}
DL_TimerG_setCaptureCompareValue(PWM_G2_INST, Duty, GPIO_PWM_G2_C0_IDX);
break;
default:
break;
}
}else if(color == 3){
DL_TimerG_setCaptureCompareValue(PWM_B1_INST, 0, GPIO_PWM_B1_C1_IDX);
DL_TimerG_setCaptureCompareValue(PWM_G2_INST, 0, GPIO_PWM_G2_C0_IDX);
switch(temp){
case 1:
DL_TimerG_setCaptureCompareValue(PWM_R3_INST, 100, GPIO_PWM_R3_C0_IDX);
break;
case 2:
if(tick < 100){
DL_TimerG_setCaptureCompareValue(PWM_R3_INST, 100, GPIO_PWM_R3_C0_IDX);
}else{
DL_TimerG_setCaptureCompareValue(PWM_R3_INST, 0, GPIO_PWM_R3_C0_IDX);
}
break;
case 3:
if(flag){
Duty += 0.5;
}else{
Duty -= 0.5;
}
if(Duty == 100){
flag = 0;
}else if(Duty == 0){
flag = 1;
}
DL_TimerG_setCaptureCompareValue(PWM_R3_INST, Duty, GPIO_PWM_R3_C0_IDX);
break;
default:
break;
}
}
}
void SysTick_Handler(void)
{
tick++;
if(tick % 10 == 0){
flagIIC = true; //温度获取标志
}
if(tick == 200){
tick = 0;
}
ModeLed(Color, SetMode[0]); //中断中判断RGBLED模式和颜色
}
七、主要难点及解决方案:
在处理LCD显示时,起初是直接使用SPI进行数据传输,在后续使用定时器中断后,发现其与SPI传输存在冲突,使得SPI传输错误、LCD花屏或串行、定时器运行过慢。因此,后续根据TI-L1306所给样例例程中的SPI-DMA中断传输函数进行修改,解决了问题。
在获取PWM的占空比和频率时,我并未直接下载,而是通过调试进行监视,发现在对外部产生的PWM进行测量时无法正确测量,而对内部产生的PWM可以正确测量。后续发现是在停止调试时,仅是程序停止,时钟不会停止,故会对测量结果产生影响。将程序直接下载使用后问题得到解决。
八、不足之处及改进:
由于ADC是对按键和编码器的电阻分压网络的获取,编码器的旋转存在一定的误差,导致旋转编码器调整阈值可能会一下跳动多次。同时,该状态下的ADC获取难以用简单的延时来减小误差。
ADC数据并未使用DMA传输,由于时间问题,暂未改进。
程序存在可以优化的地方,由于时间问题,暂未修改。
可以尝试使用更多的中断。
可以尝试使用NST1112-DSTR中的蜂鸣器。
最后,感谢硬禾学堂给了我这次机会去学习和使用TI-L1306,提升了自己的学习能力。