【任务目标】
1、驱动数码管,可以显示000.0-999.9的数值。
2、驱动DS_18B20数字温度计
3、驱动有源蜂鸣器,在采集到温度值达到规定高度时,这现蜂鸣器报警,低于规定值时关闭报警。
【实现的思路】
1、在任务2时我已经实现的驱动数码管,这里继续沿用程序,不过要加入小数点的支持。如果要显示小数点,由于我们使用的是共阳,那么就需要对第二个位的第8位进行清零即与上0x7f。
2、DS_18B20为单总线驱动,他有着严格的时序,这里我选取PA0为总线驱动IO。在这个GPIO上需要实现对温度传感器写数据时,配置为输出模式,在读数据时配置为输入模式。其代码我用宏定义进行封装:
#define DS18B20_OutPut_Mode() {DL_GPIO_initDigitalOutput(HC595_DS18B20_IOMUX);} #define DS18B20_InPut_Mode() {DL_GPIO_initDigitalInput(HC595_DS18B20_IOMUX);}
在往总线进行写高低电平时,宏定义了输出高低电平的函数如下:
#define DS18B20_DQ_OUT(x) ((x)?(DL_GPIO_setPins(HC595_PORT, HC595_DS18B20_PIN)) : (DL_GPIO_clearPins(HC595_PORT, HC595_DS18B20_PIN)))
在读取总线时的宏定义如下:
#define DS18B20_DQ_IN DL_GPIO_readPins(HC595_PORT,HC595_DS18B20_PIN)
由于总线延时需要用到微秒级的延时,而且在读、阶段是不能被打断的,所以采用的系统的延时函数delay_cycles()进行延时,由于我们的总线是32M,所以延时一微秒需要乘以32也就是delay_cycles(32*us)。
由以上的基础,再结合ds_18B20的初始化时序图:
读写时序图:
结合以上的资料,我这里封了ds_18B20的函数库,其源码如下:
/* * ds_18b20.h * * Created on: 2024年4月24日 * Author: liujianhua */ #ifndef DS_18B20_H_ #define DS_18B20_H_ #include "ti_msp_dl_config.h" #define DS18B20_OutPut_Mode() {DL_GPIO_initDigitalOutput(HC595_DS18B20_IOMUX);} #define DS18B20_InPut_Mode() {DL_GPIO_initDigitalInput(HC595_DS18B20_IOMUX);} #define DS18B20_DQ_IN DL_GPIO_readPins(HC595_PORT,HC595_DS18B20_PIN) #define DS18B20_DQ_OUT(x) ((x)?(DL_GPIO_setPins(HC595_PORT, HC595_DS18B20_PIN)) : (DL_GPIO_clearPins(HC595_PORT, HC595_DS18B20_PIN))) uint8_t ds18b20_init(void); /* 初始化DS18B20 */ uint8_t ds18b20_check(void); /* 检测是否存在DS18B20 */ short ds18b20_get_temperature(void);/* 获取温度 */ #endif /* DS_18B20_H_ */
/* * ds_18b20.c * * Created on: 2024年4月24日 * Author: liujianhua */ #include "ds_18b20.h" /** * @brief 复位DS18B20 * @param data: 要写入的数据 * @retval 无 */ static void ds18b20_reset(void) { DS18B20_OutPut_Mode(); DS18B20_DQ_OUT(0); /* 拉低DQ,复位 */ delay_cycles(32*750); /* 拉低750us */ DS18B20_DQ_OUT(1); /* DQ=1, 释放复位 */ delay_cycles(32*15); /* 延迟15US */ } /** * @brief 等待DS18B20的回应 * @param 无 * @retval 0, DS18B20正常 * 1, DS18B20异常/不存在 */ uint8_t ds18b20_check(void) { uint8_t retry = 0; uint8_t rval = 0; while ((DS18B20_DQ_IN>0) && (retry < 200)) /* 等待DQ变低, 等待200us */ { retry++; delay_cycles(32*1); } if (retry >= 200) { rval = 1; } else { retry = 0; while (!(DS18B20_DQ_IN>0) && (retry < 240)) /* 等待DQ变高, 等待240us */ { retry++; delay_cycles(32*1); } if (retry >= 240) rval = 1; } return rval; } /** * @brief 从DS18B20读取一个位 * @param 无 * @retval 读取到的位值: 0 / 1 */ static uint8_t ds18b20_read_bit(void) { uint8_t data = 0; DS18B20_DQ_OUT(0); delay_cycles(32*2); DS18B20_DQ_OUT(1); delay_cycles(32*12); DS18B20_InPut_Mode(); if (DS18B20_DQ_IN>0) { data = 1; } delay_cycles(32*50); return data; } /** * @brief 从DS18B20读取一个字节 * @param 无 * @retval 读到的数据 */ static uint8_t ds18b20_read_byte(void) { uint8_t i, b, data = 0; for (i = 0; i < 8; i++) { b = ds18b20_read_bit(); /* DS18B20先输出低位数据 ,高位数据后输出 */ data |= b << i; /* 填充data的每一位 */ } return data; } /** * @brief 写一个字节到DS18B20 * @param data: 要写入的字节 * @retval 无 */ static void ds18b20_write_byte(uint8_t data) { uint8_t j; for (j = 1; j <= 8; j++) { if (data & 0x01) { DS18B20_DQ_OUT(0); /* Write 1 */ delay_cycles(32*2); DS18B20_DQ_OUT(1); delay_cycles(32*50); } else { DS18B20_DQ_OUT(0); /* Write 0 */ delay_cycles(32*50); DS18B20_DQ_OUT(1); delay_cycles(32*2); } data >>= 1; /* 右移,获取高一位数据 */ } } /** * @brief 开始温度转换 * @param 无 * @retval 无 */ static void ds18b20_start(void) { ds18b20_reset(); ds18b20_check(); ds18b20_write_byte(0xcc); /* skip rom */ ds18b20_write_byte(0x44); /* convert */ } /** * @brief 初始化DS18B20的IO口 DQ 同时检测DS18B20的存在 * @param 无 * @retval 0, 正常 * 1, 不存在/不正常 */ uint8_t ds18b20_init(void) { ds18b20_reset(); return ds18b20_check(); } /** * @brief 从ds18b20得到温度值(精度:0.1C) * @param 无 * @retval 温度值 (-550~1250) * @note 返回的温度值放大了10倍. * 实际使用的时候,要除以10才是实际温度. */ short ds18b20_get_temperature(void) { uint8_t flag = 1; /* 默认温度为正数 */ uint8_t TL, TH; short temp; ds18b20_start(); /* ds1820 start convert */ ds18b20_reset(); ds18b20_check(); ds18b20_write_byte(0xcc); /* skip rom */ ds18b20_write_byte(0xbe); /* convert */ TL = ds18b20_read_byte(); /* LSB */ TH = ds18b20_read_byte(); /* MSB */ if (TH > 7) {/* 温度为负,查看DS18B20的温度表示法与计算机存储正负数据的原理一致: 正数补码为寄存器存储的数据自身,负数补码为寄存器存储值按位取反后+1 所以我们直接取它实际的负数部分,但负数的补码为取反后加一,但考虑到低位可能+1后有进位和代码冗余, 我们这里先暂时没有作+1的处理,这里需要留意 */ TH = ~TH; TL = ~TL; flag = 0; /* 温度为负 */ } temp = TH; /* 获得高八位 */ temp <<= 8; temp += TL; /* 获得底八位 */ /* 转换成实际温度 */ if (flag == 0) { /* 将温度转换成负温度,这里的+1参考前面的说明 */ temp = (double)(temp+1) * 0.625; temp = -temp; } else { temp = (double)temp * 0.625; } return temp; }
3、报警功能,报警给出的题目是使用有源蜂鸣器来实现。实现蜂鸣器的驱动,我这里采用了PWM来实现,因为数码官+DS18B20都是阻塞式的驱动,如果再增加其他的任务,那么写的状态机就非常复杂。我这里设计生成1个1.56Hz的PWM信号,占空比设计为50%,那么就可以实现0.8秒鸣响与0.8秒停止的报警声,这样听起来就不刺耳,而且还可以通过改变不同的占空比,可以实现不同的报警提示。为此我配置syscfg如下:
生成代码以后,我通过DL_TimerG_setCaptureCompareValue(PWM_0_INST, 0, DL_TIMER_CC_0_INDEX);来设置他的占空比为100%,实现持继输出高电平来关闭蜂鸣器器。
如果到达了温度报警值时使用DL_TimerG_setCaptureCompareValue(PWM_0_INST, 2500, DL_TIMER_CC_0_INDEX);来设置占空比为50%实现报警功能。
综以上面的设计思路,主函数的代码如下:
/* * Copyright (c) 2021, Texas Instruments Incorporated * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Texas Instruments Incorporated nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "ti_msp_dl_config.h" #include "ds_18b20.h" #include "stdio.h" // 16进制数字显示信史段码(0-9,A-F),段码位=1时,数码管的对应的笔段不会被点亮 uint8_t Disp_DX[] = { 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90, 0x77, 0x83, 0xC6, 0xA1, 0x86, 0x8E, 0xFF, 0xBF }; // 数码管模块的显示位置,自左向右 uint8_t Disp_PX[] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}; // 发送数据给数码管模块用的串行时钟,时钟上升沿有效 #define HC595_DAT(x) ((x)?(DL_GPIO_setPins(HC595_PORT, HC595_DIO_PIN)) : (DL_GPIO_clearPins(HC595_PORT, HC595_DIO_PIN))) #define HC595_CLK(x) ((x)?(DL_GPIO_setPins(HC595_PORT, HC595_SCLK_PIN)) : (DL_GPIO_clearPins(HC595_PORT, HC595_SCLK_PIN))) #define HC595_RCK(x) ((x)?(DL_GPIO_setPins(HC595_PORT, HC595_RCLK_PIN)) : (DL_GPIO_clearPins(HC595_PORT, HC595_RCLK_PIN))) #define SCLK_1 DL_GPIO_setPins(HC595_PORT, HC595_SCLK_PIN) uint8_t TimerCnt; uint32_t TmpVal; void Display_Out() { HC595_RCK(0); HC595_RCK(1); } void HC595_WriteData(uint8_t data) { uint8_t i; for(i = 0; i < 8; i++) { if((data & 0x80)>0){ HC595_DAT(1); } else { HC595_DAT(0); } data <<= 1; HC595_CLK(0); HC595_CLK(1); } } void HC595_SEND_DATA(uint8_t disp_num, uint8_t disp_bit) { HC595_WriteData(disp_num); HC595_WriteData(1<<disp_bit); Display_Out(); } void Disp_Data(uint16_t dataH, uint16_t dataL) { uint16_t tempH,tempL; uint8_t num_q, num_b,num_s,num_g; tempL = dataL; num_q = tempL/1000; num_b = tempL/100%10; num_s = tempL/10%10; num_g = tempL%10; HC595_SEND_DATA(Disp_DX[num_q],3); HC595_SEND_DATA(Disp_DX[num_b],2); HC595_SEND_DATA(Disp_DX[num_s],1); HC595_SEND_DATA(Disp_DX[num_g],0); tempH = dataH; num_q = tempH/1000; num_b = tempH/100%10; num_s = tempH/10%10 ; num_g = tempH%10; HC595_SEND_DATA(Disp_DX[num_q],7); HC595_SEND_DATA(Disp_DX[num_b],6); HC595_SEND_DATA(Disp_DX[num_s] ,5); HC595_SEND_DATA(Disp_DX[num_g],4); } void Disp_Temp(uint16_t dataL) { uint16_t tempH,tempL; uint8_t num_q, num_b,num_s,num_g; tempL = dataL; num_q = tempL/1000; num_b = tempL/100%10; num_s = tempL/10%10; num_g = tempL%10; HC595_SEND_DATA(Disp_DX[num_q],3); HC595_SEND_DATA(Disp_DX[num_b],2); HC595_SEND_DATA(Disp_DX[num_s] & 0x7F,1); //显示小数点 HC595_SEND_DATA(Disp_DX[num_g],0); } int main(void) { short temperature; TmpVal = 400; SYSCFG_DL_init(); //NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN); //DL_TimerG_enableClock(PWM_0_INST); //DL_TimerG_disableClock(PWM_0_INST); while (1) { TmpVal++; if(TmpVal >= 80000) { temperature = ds18b20_get_temperature(); TmpVal = 0; if(temperature>230){ DL_TimerG_setCaptureCompareValue(PWM_0_INST, 2500, DL_TIMER_CC_0_INDEX); } else{ DL_TimerG_setCaptureCompareValue(PWM_0_INST, 0, DL_TIMER_CC_0_INDEX); } } Disp_Temp(temperature); } } void TIMER_0_INST_IRQHandler(void) { switch(DL_Timer_getPendingInterrupt(TIMER_0_INST)){ case DL_TIMER_IIDX_ZERO: { TmpVal --; if(TmpVal == 0) { TmpVal = 4; } } break; default: break; } }
【实现的功能】
开机后数码管显示实际温度值,超过23度,他就会报警,低于23度,关闭报警。