【TI MSPM0 MCU 焕新大作战】+课程3任务1:实现温度报警器
这次的任务是实现温度报警,关键点在于实际检测环境温度。活动提供了经典的Dallas18B20传感器。但凡搞单片机的,恐怕没几个人不知道Dallas18B20。一个原因是Dallas18B20是很经典的一个温度传感器、例子多,另外一个原因可能还和18B20的特殊通讯协议有关。在我的单片机学习过程中,大概也是唯一一次采用单总线通讯方式收发数据的经历。
18B20的资料,不多讲,网上可以找到的例子一大把。本次实验提供的是模组方式,已经配置好了外围电路,只需要用杜邦线简单连接到开发板上就行。本次实验为了方便显示调试信息和测试结果,除了之前用到的数码管模块,还加入了OLED显示。
OLED显示屏耗电量低,需要显示什么数据一次传递便好,不需要不停扫描。
开发板的电路原理图:
整个电路的简易结构图如下:
DS18B20的一些资料:
对于报警处理,我用的方式是:在采集到温度后,显示到OLED和数码管模块上,然后和警戒线比较、判断,超出报警线,驱动蜂鸣器报警。和18B20的通讯过程,要注意通讯时序的问题。
所有和18B20有关的处理代码,都放在主文件中了。18B20数据通讯接口,初始化时配置为输出,但在运行过程中根据通讯需要动态设置为输入或者输出口模式。
让DS18B20进行一次温度的转换,那具体的操作就是:
1、主机先作个复位操作,
2、主机再写跳过ROM的操作(0xCC)命令,
3、然后主机接着写个转换温度的操作命令(0x44),后面释放总线至少一秒,让DS18B20完成转换的操作。在这里要注意的是每个命令字节在写的时候都是低字节先写,例如CCH的二进制为11001100,在写到总线上时要从低位开始写,写的顺序是“0、0、1、1、0、0、1、1”。整个转换过程大概需要125ms的样子。
转换完成后,立即读取转换结果,这个处理同样需要三个步骤:
1、主机发出复位操作并接收DS18B20的应答(存在)脉冲。
2、主机发出跳过对ROM操作的命令(0xCC)。
4、主机发出读取RAM的命令(0xBE),随后主机依次读取DS18B20发出的数据。如果只想读取温度数据,那在读完第0和第1个数据后就不再理会后面DS18B20发出的数据即可。同样读取数据也是低位在前的。这两个数据,低8位在前,高八位在后,处理的时候要注意。从功能上讲,18B20读出来的数据,是包括负温度数据的。但测试室温场合,不可能有负数,所以在数据处理上可以更简单一些。
程序的处理逻辑如下:
1、定时器处理完成时间计数,
2、主程序的循环中检测定时周期信号,收到信号后,启动温度检测。将检测到的数据进行转换,显示到OLED上,同时转换为数码管模块可以显示的形式。
3、主程序的循环中不断显示温度数据到数码管上。
主处理代码:
int main(void) { uint32_t pos=0, i=0, cnt=0, flag=0; char temp_char[20]={'\0'}; char alert_flag=0; /* Power on GPIO, initialize pins as digital outputs */ SYSCFG_DL_init(); // 设置定时器0中断优先级 NVIC_SetPriority(TIMER_0_INST_INT_IRQN, 1); // 允许定时器0中断 NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN); // 启动定时器计数 DL_TimerG_startCounter(TIMER_0_INST); //DL_GPIO_setPins(GPIOA_PORT, GPIOA_USER_LED_1_PIN | GPIOA_USER_LED_2_PIN | GPIOA_USER_LED_3_PIN | GPIOA_USER_TEST_PIN); closeBeep(); OLED_Init(); OLED_Clear(0); GUI_ShowCHinese(0, 16, (uint8_t *)"温度:", 16, 1); cnt_1s_flag=0; while (1) { // 每秒读取一次温度 if (cnt_1s_flag==1) { // 设置标志,表示读取传感器的处理进行中 cnt_1s_flag = 2; // 获取温度 t=Ds18b20GetTemp(); // 如果温度大于报警线,则报警;恢复正常后,停止报警 if (t > TEMP_ALARM_H1) { openBeep(); } else { closeBeep(); } if (old_t != t) { old_t = t; // 将温度转换为字符串 if (t > 0) { sprintf(temp_char, "%3.1f", t); } else { sprintf(temp_char, "%-3.1f", t); } // 在OLED上显示温度数据 GUI_ShowString(48, 16, (uint8_t *)" ", 16, 1); GUI_ShowString(48, 16, (uint8_t *)temp_char, 16, 1); // 利用数码管模块显示温度。显示之前,先做转换处理, // 1、将小数点和前面数字结合 // 2、所有要显示的数据,转换为数码管上显示用的笔段码 pos=0; cnt=0; // 显示数据位数,为了调整显示数码管的位置用,忽略的话,是从最前面开始显示 for (i=0; i<8; i++) { if (temp_char[i]=='\0') { disp_segdat[pos]='\0'; break; } if (temp_char[i]=='-') { // 负号对应的笔段码 disp_segdat[pos]=0xBF; // 调整保存位置为下一个数码管的 pos++; cnt++; } else if (temp_char[i]=='.') { // 前一个字符的段位码数据加上小数点位 disp_segdat[pos-1]=disp_segdat[pos-1] & 0x7F; } else if (temp_char[i]>='0' && temp_char[i]<='9') { // 数字0-9,取得字段码数据 disp_segdat[pos]=TAB_SEG[temp_char[i]-48]; // 调整保存位置为下一个数码管的 pos++; cnt++; } } } // 清除1秒标志 cnt_1s_flag = 0; // } // 发送字段码数据给数码管显示模块 // 方式1:循环一次只显示一个数码管,显示有闪烁 smpos=(smpos+1)%8; if (disp_segdat[smpos]=='\0') { smpos=-1; } else { // 显示的位置,可以根据cnt的值(显示数据的总位数)调整到任意位置 DispSegData(smpos, disp_segdat[smpos]); delay_cycles(ms1*SMSEG_DISP_TIMES); } DL_GPIO_togglePins(GPIOA_PORT, GPIOA_USER_LED_3_PIN); } }
定时器的中断处理代码:
// Timer0的中断处理,按照配置,每10ms完成一次中断,累计100次,得到1S的周期 void TIMER_0_INST_IRQHandler(void) { switch (DL_TimerG_getPendingInterrupt(TIMER_0_INST)) { case DL_TIMERG_IIDX_ZERO: // 定时器0完成一次中断 if (cnt_1s_flag == 2) { // 读取温度数据时,停止计算秒的计数 cnt_10ms=0; } else if (cnt_1s_flag == 0) { // 累计50次,500mS cnt_10ms=(cnt_10ms+1)%50; if (cnt_10ms==0) { // 达到1秒钟 if (cnt_1s_flag == 0) { cnt_1s_flag=1; } //DL_GPIO_togglePins(GPIOA_PORT, GPIOA_USER_LED_3_PIN); } } break; case DL_TIMERG_IIDX_LOAD: case DL_TIMERG_IIDX_CC0_DN: case DL_TIMERG_IIDX_CC1_DN: case DL_TIMERG_IIDX_CC0_UP: case DL_TIMERG_IIDX_CC1_UP: case DL_TIMERG_IIDX_OVERFLOW: default: break; } }
程序编好后,下载到开发板,接好各个部件,测试效果如下:
数码管的显示处理不好,闪烁明显,但不影响正确显示温度。
DS18B20处理部分的代码:
void Ds18b20Rst(void) { // 设置TEMP_IO口为输出模式 DL_GPIO_initDigitalOutputFeatures(GPIOA_DS18B20_DIO_IOMUX, DL_GPIO_INVERSION_DISABLE, DL_GPIO_RESISTOR_PULL_UP, DL_GPIO_DRIVE_STRENGTH_LOW, DL_GPIO_HIZ_DISABLE); DL_GPIO_clearPins(GPIOA_PORT, GPIOA_DS18B20_DIO_PIN); DL_GPIO_enableOutput(GPIOA_PORT, GPIOA_DS18B20_DIO_PIN); // 主机发送复位脉冲480us-960us delay_us(750); DL_GPIO_setPins(GPIOA_PORT, GPIOA_DS18B20_DIO_PIN); delay_us(15); } /*等待18b20响应 * 返回1:未检测到18b20 * 返回0:存在 */ char Ds18b20Check(void) { char retry=0; // DL_GPIO_disableOutput(GPIOA, GPIOA_DS18B20_DIO_PIN); // DL_GPIO_initDigitalInputFeatures(GPIOA_DS18B20_DIO_IOMUX, // DL_GPIO_INVERSION_DISABLE, DL_GPIO_RESISTOR_PULL_UP, // DL_GPIO_HYSTERESIS_DISABLE, DL_GPIO_WAKEUP_DISABLE); // // while(DL_GPIO_readPins(GPIOA_PORT, GPIOA_DS18B20_DIO_PIN)>0 && retry<200) { // retry++; // delay_us(1); // } // if(retry>=200) { // return 1; // } // retry=0; // // while(DL_GPIO_readPins(GPIOA_PORT, GPIOA_DS18B20_DIO_PIN)==0 && retry<240) { // retry++; // delay_us(1); // } // if(retry>=240) return 1; delay_us(125); return 0; } /*从18b20读取一个位 * 返回值1/0 */ char Ds18b20ReadBit(void) { char data; // 设置TEMP_IO口为输出模式 DL_GPIO_initDigitalOutputFeatures(GPIOA_DS18B20_DIO_IOMUX, DL_GPIO_INVERSION_DISABLE, DL_GPIO_RESISTOR_PULL_UP, DL_GPIO_DRIVE_STRENGTH_LOW, DL_GPIO_HIZ_DISABLE); DL_GPIO_enableOutput(GPIOA_PORT, GPIOA_DS18B20_DIO_PIN); DL_GPIO_clearPins(GPIOA_PORT, GPIOA_DS18B20_DIO_PIN); delay_us(2); DL_GPIO_setPins(GPIOA_PORT, GPIOA_DS18B20_DIO_PIN); // 设置TEMP_IO口为输入模式 DL_GPIO_disableOutput(GPIOA, GPIOA_DS18B20_DIO_PIN); DL_GPIO_initDigitalInputFeatures(GPIOA_DS18B20_DIO_IOMUX, DL_GPIO_INVERSION_DISABLE, DL_GPIO_RESISTOR_PULL_UP, DL_GPIO_HYSTERESIS_DISABLE, DL_GPIO_WAKEUP_DISABLE); delay_us(12); if(DL_GPIO_readPins(GPIOA_PORT, GPIOA_DS18B20_DIO_PIN)>0) data=1; else data=0; delay_us(50); return data; } /*从18b20读取一个字节 * 返回值:读到的数据 */ char Ds18b20ReadByte(void) { char i,j,dat; dat=0; for(i=1;i<=8;i++) { j=Ds18b20ReadBit(); dat=(j<<7)|(dat>>1);//低位在前 } return dat; } /*写一个字节到Ds18b20 * dat:要写入的字节 */ void Ds18b20WriteByte(char dat) { char i; char temp; DL_GPIO_initDigitalOutputFeatures(GPIOA_DS18B20_DIO_IOMUX, DL_GPIO_INVERSION_DISABLE, DL_GPIO_RESISTOR_PULL_UP, DL_GPIO_DRIVE_STRENGTH_LOW, DL_GPIO_HIZ_DISABLE); DL_GPIO_enableOutput(GPIOA_PORT, GPIOA_DS18B20_DIO_PIN); for(i=1;i<=8;i++) { temp=dat&0x01; dat=dat>>1; //write 1 if (temp>0) { DL_GPIO_clearPins(GPIOA_PORT, GPIOA_DS18B20_DIO_PIN); delay_us(2); DL_GPIO_setPins(GPIOA_PORT, GPIOA_DS18B20_DIO_PIN); delay_us(60); } else { DL_GPIO_clearPins(GPIOA_PORT, GPIOA_DS18B20_DIO_PIN); delay_us(60); DL_GPIO_setPins(GPIOA_PORT, GPIOA_DS18B20_DIO_PIN); delay_us(2); } } } /*开始温度转换 * */ void Ds18b20Start(void) { Ds18b20Rst(); Ds18b20Check(); // if (Ds18b20Check()>0) { // GUI_ShowString(0, 0, (uint8_t *)"Start NG", 8, 1); // return; // } Ds18b20WriteByte(0XCC); Ds18b20WriteByte(0X44); } /*从Ds18b20得到温度值 * 精度0.1c * 返回值:温度值(-550-1250) */ float Ds18b20GetTemp() { char temp; char TH=0,TL=0; short tem; float t; Ds18b20Start(); Ds18b20Rst(); // if (Ds18b20Check()==0) { // GUI_ShowString(0, 0, (uint8_t *)"GetTemp NG", 8, 1); // return 999; // } Ds18b20Check(); Ds18b20WriteByte(0XCC); Ds18b20WriteByte(0XBE); TL=Ds18b20ReadByte();//LSB TH=Ds18b20ReadByte();//MSB GUI_ShowNum(20, 8, TH, 3, 8, 1); GUI_ShowNum(50, 8, TL, 3, 8, 1); if(TH>7) { TH=~TH; TL=~TL; temp=0;//the temperature is negative } else temp=1;// the temperature is positive tem=TH;//高八位 tem<<=8; tem+=TL;//低八位 t=((float)tem*0.0625); if(temp) return t; else return -t; }