-
一、项目介绍当前文章介绍基于51单片机的热敏电阻测温系统的设计过程,用于实时监测环境温度,并在温度超过预设阈值时进行报警。由于采用的是热敏电阻测温技术,无需外置温度传感器,使得系统具有结构简单、成本较低等优点。主控芯片采用STC89C52,具有良好的稳定性和可靠性,适应于工业控制等领域的应用需要。ADC采集模块采用PCF8591模块,可方便地实现对热敏电阻温度数据的转换和采集,提高了系统的准确度和实用性。系统通过4位数码管显示出温度值,同时通过按键设置温度上限阀值,当温度超过阀值时,会通过蜂鸣器报警,提醒用户注意环境温度的变化情况。在项目中主要是用到了热敏电阻和PCF8591模块。(1)热敏电阻介绍热敏电阻(Thermistor)是一种基于材料的电阻元件,其电阻值随温度的变化而发生相应的变化。通常情况下,热敏电阻的电阻值随温度升高而降低,反之则随温度降低而升高,这种特性被称为负温度系数(NTC)或正温度系数(PTC)。热敏电阻的工作原理是基于材料的温度敏感性质。在热敏电阻中,存在许多导电粒子,当温度升高时,导电粒子与材料中的离子激发程度增强,导致导电粒子的数量变多,因此电阻值降低;反之,当温度降低时,导电粒子的数量变少,电阻值增加。(2)PCF8591PCF8591是一款4通道、8位模数转换器(ADC)和1通道、8位数模转换器(DAC)的集成电路芯片。可以通过I2C总线与微控制器进行通信,实现模拟信号的输入和输出。PCF8591的输入电压范围为0V~VCC(通常为5V),可以通过外部电阻进行放大或缩小。它还有一个内部参考电压源,可以通过软件控制选择使用。PCF8591的输出电压范围也是0V~VCC,可以用于控制模拟信号的输出,比如控制电机的转速、LED的亮度等。在热敏电阻测温系统中,使用PCF8591模块来采集热敏电阻的电压信号,并将其转换为数字信号,进而计算出温度值。二、设计思路2.1 系统结构系统采用单片机作为主控芯片,热敏电阻用于测量环境温度,PCF8591模块采集热敏电阻的温度数据并将其转换为正常温度值,通过数码管进行显示。同时,系统设置上限阀值,当温度超过该值时,系统会通过蜂鸣器报警。2.2 硬件设计(1)主控芯片本系统采用STC89C52单片机作为主控芯片,具有强大的计算能力、稳定的性能和较低的功耗,支持多种外设接口,适合于工业控制等领域的应用需求。(2)温度传感器本系统采用热敏电阻作为温度传感器,其结构简单、价格便宜,且无需额外的电源供应,可直接通过PC8591模块的输入端口进行检测。(3)ADC采集模块系统采用PCF8591模块进行ADC采集,具有4路模拟输入通道和一个模拟输出通道,采样精度高达8位,能够满足本系统对温度信号的准确采集需求。(4)数码管显示模块系统采用4位共阳数码管进行数据显示,其显示范围为-999~+9999,可满足本系统对温度数据的实时显示需求。(5)蜂鸣器报警模块系统采用蜂鸣器进行报警提示,当温度超过预设阈值时,蜂鸣器会发出持续声响,提醒用户注意环境温度的变化情况。(6)按键模块按键模块,方便用户进行阀值的设置和调整操作。2.3 软件设计(1)温度采集与转换系统使用ADC采集热敏电阻的温度信号,并将采集到的数字信号转换成温度值进行显示。转换公式为: T=(adc_value/255.0)*330,其中adc_value为AD转换器输出的数字值,330是热敏电阻的参考电阻值。(2)温度上限阀值设置系统通过按键实现温度上限阀值的设置和调整操作,用户可以根据自己的需求进行设定。(3)报警功能设计系统在采集到温度超过预设阈值时,蜂鸣器会发出声响进行提醒,并且LED指示灯会亮起。2.4 总体流程(1)初始化各个模块,包括单片机、PCF8591、数码管、蜂鸣器和按键等。(2)采集热敏电阻的温度信号,并将数字信号转换为温度值。(3)将温度值通过数码管进行显示。(4)检测当前温度是否超过预设阈值,若超过,则触发报警并点亮LED指示灯。(5)用户可以通过按键设置温度上限阀值,系统会保存设置的阈值并进行下一次温度比较。三、代码实现以下是基于51单片机设计的热敏电阻测温系统的实现代码。 #include <reg52.h> #include <intrins.h> typedef unsigned char u8; typedef unsigned int u16; #define PCF8591_address_write 0x90 #define PCF8591_address_read 0x91 sbit SCLK = P1^0; //PCF8591模块时钟线 sbit DOUT = P1^1; //PCF8591模块数据线 sbit DIN = P1^2; //PCF8591模块数据线 sbit CS = P1^3; //PCF8591模块片选线 sbit LATCH1 = P3^4; //锁存器1 sbit LATCH2 = P3^5; //锁存器2 sbit KEY1 = P2^0; //按键1 sbit KEY2 = P2^1; //按键2 sbit BUZZ = P2^3; //蜂鸣器 u16 ADC_value; //采集到的ADC值 float temperature; //计算得到的温度值 u8 table[] = { //共阳数码管段码表 0xc0, //0 0xf9, //1 0xa4, //2 0xb0, //3 0x99, //4 0x92, //5 0x82, //6 0xf8, //7 0x80, //8 0x90, //9 0xbf, //- }; void delay(u16 i){ while(i--); } void delay_ms(u16 ms){ u16 i, j; for(i=0; i<ms; i++){ for(j=0; j<110; j++); } } void write_PCF8591(u8 data){ u8 i; DIN = 1; SCLK = 0; CS = 0; for(i=0; i<8; i++){ DOUT = (data & 0x80) >> 7; data <<= 1; SCLK = 1; SCLK = 0; } CS = 1; } u16 read_ADC(){ u16 value; CS = 0; DIN = 1; SCLK = 0; DIN = 0; _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); SCLK = 1; _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); value = P1; SCLK = 0; value <<= 8; _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); SCLK = 1; _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); value |= P1; CS = 1; return value; } void display(u8 addr, u8 dat){ LATCH1 = 0; LATCH2 = 0; P0 = addr; LATCH1 = 1; LATCH1 = 0; P0 = table[dat]; LATCH2 = 1; LATCH2 = 0; } void main(){ u8 set_value = 40; //温度上限阀值 u8 temp; write_PCF8591(0x40); while(1){ ADC_value = read_ADC(); temperature = (float)ADC_value * 330 / 255; temperature -= 50; if(temperature > set_value){ //温度超过上限阀值,触发报警 BUZZ = 1; } else{ BUZZ = 0; } if(temperature < -99){ temp = '-'; display(0x00, temp); display(0x01, 10); display(0x02, 10); display(0x03, 10); } else if(temperature < 0){ temp = '-'; display(0x00, temp); temp = ~(int)temperature + 1; display(0x01, temp/10); display(0x02, temp%10); display(0x03, 11); //小数点 } else if(temperature > 999){ display(0x00, 10); display(0x01, 9); display(0x02, 9); display(0x03, 9); } else{ display(0x00, temperature/100); display(0x01, temperature/10%10); display(0x02, temperature%10); display(0x03, 11); //小数点 } if(KEY1 == 0){ //按键1按下,增加上限阀值 delay_ms(10); if(KEY1 == 0){ set_value++; while(!KEY1); } } if(KEY2 == 0){ //按键2按下,减小上限阀值 delay_ms(10); if(KEY2 == 0){ set_value--; while(!KEY2); } } delay_ms(10); } }代码中采用了共阳数码管,通过采集热敏电阻产生的电压值,计算得到环境温度值,再通过数码管进行显示;当温度超过设定的上限值时,会触发蜂鸣器报警。可以通过按键对温度上限阀值进行设置和调整操作。
-
一、项目介绍井下瓦斯监控系统是煤矿安全生产中非常重要的一部分,防止井下瓦斯爆炸事故的发生,保障煤矿工人的人身安全。由于地下环境特殊,需要特殊的监测系统来实时监测瓦斯浓度等关键指标,并及时报警以便采取措施进行处理。瓦斯气体,又称沼气,是一种轻质烃类气体,主要成分是甲烷(CH4),也包含少量的乙烷、丙烷等。它是在地下煤炭层与泥岩等岩石中通过微生物作用或者煤炭化学反应形成的。在煤矿等地下工程中,瓦斯常常是一种具有危险性的气体,如果采取不当的措施,就有可能发生瓦斯爆炸事故。基于51单片机的井下瓦斯监控系统,可以通过传感器检测瓦斯气体浓度,将检测到的数据通过AD转换后送入单片机处理,再通过LCD显示器显示出来。如果瓦斯浓度超过了预设阈值,系统会自动启动报警装置进行警示。同时,这种系统具有适用面广、成本低、可靠性高等特点。在目前环保意识提高的背景下,煤炭企业和政府对于井下瓦斯监控系统的需求越来越大,系统的市场潜力巨大。二、设计原理2.1 传感器选型(1)瓦斯气体检测MQ2传感器是一种常用于气体检测的半导体传感器,主要用于检测多种易燃、易爆气体,如瓦斯、丙烷、液化气等。它采用了半导体氧化物层敏感元件技术,当检测到目标气体时,其电阻值会发生变化,从而可以通过测量电阻值的变化来检测目标气体的浓度。MQ2传感器具有灵敏度高、响应速度快、使用方便等特点,因此在气体检测领域广泛应用。MQ2传感器包括热敏电阻、电化学传感器、半导体敏感元件等部分,其中半导体敏感元件是其核心部件,也是影响传感器性能的关键因素。在使用前需要进行预热处理,一般预热时间为1-2分钟,然后将待测气体与传感器接触,即可读取传感器的输出信号并进行浓度计算。(2)ADC采集模块PCF8591模块是一种集成了AD转换器和DA转换器的模块,通过I2C总线可以连接到单片机或其他电子设备上,用于模拟信号的输入和输出。其主要特点是集成度高、精度高、使用方便、成本低廉等。模块由PCF8591芯片和相关外围电路组成,其中PCF8591芯片是一个具有4个模拟输入通道和1个模拟输出通道的集成电路,内部集成了128级AD转换器和8位DA转换器,并且支持外部基准电压输入。同时,该模块还包括4个可变电阻,可以通过调节来改变模拟输入通道的电阻值,从而实现对信号的增益和衰减。通过I2C总线,可以方便地读取和输出模拟信号。在实际应用中,PCF8591模块广泛用于传感器信号的采集和处理,例如温度、光强、声音等信号的转换和传输。2.2 设计思路基于51单片机设计的井下瓦斯监控系统的原理如下:(1)传感器检测瓦斯浓度:使用瓦斯传感器检测井下瓦斯浓度,并将检测结果转换为电信号输出。(2)单片机采集数据:使用ADC模块将传感器输出的电信号转换为数字信号,并将其存储到单片机内部的RAM中。(3)数据处理:单片机通过对采集到的数据进行处理,可以实现瓦斯浓度的实时监测,并根据预设阈值进行报警处理。(4)报警处理:当瓦斯浓度超过预设阈值时,单片机会触发报警器进行报警。同时,可以通过OLED显示屏实时显示瓦斯浓度,并通过蜂鸣器发出警报声音。(5)数据存储:单片机还可以将采集到的数据存储到外部存储器中,以便后续的数据分析和处理。基于51单片机设计的井下瓦斯监控系统通过传感器检测瓦斯浓度,单片机采集数据并进行处理,实现了对瓦斯浓度的实时监测和报警处理,同时还可以将数据存储到外部存储器中,方便后续的数据分析和处理。三、代码实现3.1 采集MQ2浓度打印到串口以下是基于STC89C52通过PCF8591采集MQ2烟雾传感器的值,并转为浓度打印到串口的详细代码。 #include <reg52.h> #include <intrins.h> #define uchar unsigned char #define uint unsigned int sbit SCL=P1^0; // I2C时钟线 sbit SDA=P1^1; // I2C数据线 sbit MQ2=P3^2; // MQ2烟雾传感器 // 函数声明 void delay(uint n); void I2C_Start(); void I2C_Stop(); void I2C_SendACK(bit ack); bit I2C_RecvACK(); void I2C_SendByte(uchar dat); uchar I2C_RecvByte(); void PCF8591_Write(uchar addr, uchar dat); uchar PCF8591_Read(uchar addr); void InitPCF8591(); uint ReadMQ2(); // 主函数 void main() { InitPCF8591(); // 初始化PCF8591 while(1) { uint mq2val = ReadMQ2(); // 读取MQ2传感器的值 float mq2con = (mq2val / 255.0) * 100.0; // 将传感器的值转换为浓度 printf("MQ2烟雾浓度:%f%%\n", mq2con); // 打印浓度到串口 delay(1000); // 延时1秒 } } // 延时函数 void delay(uint n) { uint i, j; for(i = 0; i < n; i++) for(j = 0; j < 125; j++); } // I2C总线函数 // I2C起始信号 void I2C_Start() { SDA = 1; SCL = 1; _nop_(); _nop_(); _nop_(); SDA = 0; _nop_(); _nop_(); _nop_(); SCL = 0; } // I2C停止信号 void I2C_Stop() { SDA = 0; SCL = 1; _nop_(); _nop_(); _nop_(); SDA = 1; _nop_(); _nop_(); _nop_(); } // I2C发送应答信号 void I2C_SendACK(bit ack) { SDA = ack; SCL = 1; _nop_(); _nop_(); _nop_(); SCL = 0; } // I2C接收应答信号 bit I2C_RecvACK() { SCL = 1; _nop_(); _nop_(); _nop_(); bit ack = SDA; SCL = 0; return ack; } // I2C发送一个字节 void I2C_SendByte(uchar dat) { uchar i; for(i = 0; i < 8; i++) { SDA = (dat & 0x80) >> 7; dat <<= 1; SCL = 1; _nop_(); _nop_(); _nop_(); SCL = 0; } I2C_RecvACK(); } // I2C接收一个字节 uchar I2C_RecvByte() { uchar i, dat = 0; for(i = 0; i < 8; i++) { dat <<= 1; SCL = 1; _nop_(); _nop_(); _nop_(); dat |= SDA; SCL = 0; } I2C_SendACK(1); return dat; } // PCF8591函数 // 初始化PCF8591 void InitPCF8591() { PCF8591_Write(0x40, 0x00); // 设置PCF8591控制字节,模拟输入通道为0 } // 向PCF8591写入一个字节 void PCF8591_Write(uchar addr, uchar dat) { I2C_Start(); // 发送起始信号 I2C_SendByte(0x90); // 发送设备地址,并写入模式 I2C_RecvACK(); I2C_SendByte(addr); // 发送寄存器地址 I2C_RecvACK(); I2C_SendByte(dat); // 发送数据 I2C_RecvACK(); I2C_Stop(); // 发送停止信号 } // 从PCF8591读取一个字节 uchar PCF8591_Read(uchar addr) { uchar dat; I2C_Start(); // 发送起始信号 I2C_SendByte(0x90); // 发送设备地址,并写入模式 I2C_RecvACK(); I2C_SendByte(addr); // 发送寄存器地址 I2C_RecvACK(); I2C_Start(); // 发送起始信号 I2C_SendByte(0x91); // 发送设备地址,并读取数据 I2C_RecvACK(); dat = I2C_RecvByte(); // 读取数据 I2C_SendACK(1); I2C_Stop(); // 发送停止信号 return dat; } // 读取MQ2传感器的值 uint ReadMQ2() { uchar val = PCF8591_Read(0x40); // 读取PCF8591的模拟输入值 if(MQ2 == 0) // 如果MQ2传感器检测到烟雾 return (uint)(val * 2.55); // 返回模拟输入值的百分比 else return 0; // 否则返回0 }上面代码里,主要包括了I2C总线函数和PCF8591函数,用于与PCF8591芯片进行通信。其中,InitPCF8591()函数用于初始化PCF8591芯片,PCF8591_Write()函数用于向PCF8591芯片写入数据,PCF8591_Read()函数用于从PCF8591芯片读取数据。另外,ReadMQ2()函数用于读取MQ2传感器的值,并将其转换为浓度值。最后,在主函数中,通过调用ReadMQ2()函数读取MQ2传感器的值,并将其转换为浓度值,然后通过printf()函数将浓度值打印到串口。3.2 采用烟雾浓度显示到OLED下面代码是STC89C52通过PCF8591采集MQ2烟雾传感器的值,并转为浓度显示到IIC接口的OLED显示屏上。 #include <reg52.h> #include <intrins.h> #define uchar unsigned char #define uint unsigned int typedef enum { false = 0, true = !false } bool; sbit SCL=P3^6; //IIC总线时钟引脚定义 sbit SDA=P3^7; //IIC总线数据引脚定义 sbit LED = P1^0; // 测试用LED灯,可选 uchar code tabel[]="Smog: "; //OLED屏幕显示内容 uchar code dis[]="%"; /*------------------函数声明------------------*/ void delayms(uint); //毫秒延时函数 void IIC_Start(); //IIC起始信号 void IIC_Stop(); //IIC停止信号 void IIC_Send_Byte(uchar); //发送一个字节 uchar IIC_Read_Byte(bool); //读取一个字节 void LCD_SetPos(uchar,uchar); //设置位置 void LCD_WriteCmd(uchar); //写命令 void LCD_WriteData(uchar); //写数据 void LCD_Init(); //初始化 void MQ2_Init(); //MQ2传感器初始化 int MQ2_Read(); //读取MQ2传感器的值并返回浓度值 /*------------------主函数------------------*/ void main() { uchar i=0,j=0,k=0; int result = 0; //存储MQ2传感器读取的值 uchar buf[5] = {0}; //存储MQ2浓度值字符串 uchar MQ2_data[10]={0}; //存储OLED屏幕显示数据 EA = 1; //开放总中断 IIC_Init(); LCD_Init(); MQ2_Init(); while(1) { result = MQ2_Read(); //读取MQ2传感器的值 if(result >= 0) //读取成功 { itoa(result,buf,10); //将读取结果转为字符串格式 for(i=0;i<6;i++) //将OLED显示内容清空,准备写入新数据 MQ2_data[i]=0; for(i=0;i<6;i++) //拼接OLED显示内容 { if(tabel[i]!=0) //判断是否有显示内容 MQ2_data[i]=tabel[i]; else break; } j=0; //记录MQ2字符串长度 while(buf[j]!=0 && i+j<7) //拼接MQ2浓度值字符串,最多显示5位数 { MQ2_data[i+j] = buf[j]; j++; } if(j<5) //MQ2浓度值不足5位数字,换行再拼接“%”字符 { MQ2_data[i+j] = '\n'; MQ2_data[i+j+1] = dis[0]; } for(k=0;k<i+j+2;k++) //将OLED屏幕显示MQ2浓度值 { LCD_WriteData(MQ2_data[k]); } delayms(1000); //延时1秒(可根据实际需求调整) } } } /*------------------IIC总线控制函数------------------*/ void IIC_Init() { SCL = 1; //初始化,总线空闲状态时SCL和SDA都为高电平 SDA = 1; } void IIC_Start() { SDA = 1; _nop_(); SCL = 1; _nop_(); SDA = 0; //起始信号的形成:在SCL高电平期间,SDA从高电平转为低电平 _nop_(); SCL = 0; } void IIC_Stop() { SDA = 0; _nop_(); SCL = 1; _nop_(); SDA = 1; //停止信号的形成:在SCL高电平期间,SDA从低电平转到高电平 _nop_(); } void IIC_Send_Byte(uchar dat) { uchar i; for(i=0;i<8;i++) { SDA = dat & 0x80; dat <<= 1; SCL = 1; _nop_(); SCL = 0; _nop_(); } } uchar IIC_Read_Byte(bool ack) { uchar i,dat=0; for(i=0;i<8;i++) { SCL = 1; _nop_(); dat <<= 1; dat |= SDA; SCL = 0; _nop_(); } if(ack) SDA = 0; //发送ACK else SDA = 1; //不发送ACK SCL = 1; _nop_(); SCL = 0; _nop_(); SDA = 1; return dat; } /*------------------OLED屏幕控制函数------------------*/ void LCD_SetPos(uchar x,uchar y) { LCD_WriteCmd(0xb0+y); LCD_WriteCmd(((x&0xf0)>>4)|0x10); LCD_WriteCmd((x&0x0f)|0x00); } void LCD_WriteCmd(uchar cmd) { IIC_Start(); IIC_Send_Byte(0x78); IIC_Send_Byte(0x00); //写命令 IIC_Send_Byte(cmd); IIC_Stop(); } void LCD_WriteData(uchar dat) { IIC_Start(); IIC_Send_Byte(0x78); IIC_Send_Byte(0x40); //写数据 IIC_Send_Byte(dat); IIC_Stop(); } void LCD_Init() { LCD_WriteCmd(0xae); LCD_WriteCmd(0x00); LCD_WriteCmd(0x10); LCD_WriteCmd(0x40); LCD_WriteCmd(0xb0); LCD_WriteCmd(0x81); LCD_WriteCmd(0xcf); LCD_WriteCmd(0xa1); LCD_WriteCmd(0xa6); LCD_WriteCmd(0xa8); LCD_WriteCmd(0x3f); LCD_WriteCmd(0xc8); LCD_WriteCmd(0xd3); LCD_WriteCmd(0x00); LCD_WriteCmd(0xd5); LCD_WriteCmd(0x80); LCD_WriteCmd(0xd9); LCD_WriteCmd(0xf1); LCD_WriteCmd(0xda); LCD_WriteCmd(0x12); LCD_WriteCmd(0xdb); LCD_WriteCmd(0x40); LCD_WriteCmd(0x20); LCD_WriteCmd(0x02); LCD_WriteCmd(0xaf); LCD_WriteCmd(0xff); } /*------------------MQ2传感器控制函数------------------*/ void MQ2_Init() { IIC_Start(); IIC_Send_Byte(0x90); //写入设备地址 1001A2A1A0(0) R/W = 0(PCF8591 写操作) IIC_Send_Byte(0x40); //写入控制字节,选择通道0,并开启模拟转换器 IIC_Stop(); } int MQ2_Read() { int result=0; uchar buf[10]={0}; IIC_Start(); IIC_Send_Byte(0x90); //写入设备地址 IIC_Send_Byte(0x41); //读取数据 result = IIC_Read_Byte(true)*256; //读取高位数据 result += IIC_Read_Byte(true); //读取低位数据 IIC_Stop(); if(result < 0) return -1; //读取失败 else return result; //返回读取的值 } /*------------------辅助函数------------------*/ void delayms(uint n) { uint i, j; for(i=0;i<n;i++) for(j=0;j<114;j++); }
-
一、项目介绍花样流水灯是一种常见的LED灯效果,被广泛应用于舞台表演、节日庆典、晚会演出等场合。在现代智能家居、电子产品中,花样流水灯也被广泛使用,通过调整亮灭顺序和时间,可以实现各种炫酷的灯光效果,增强用户体验。而51单片机作为一种常见的嵌入式开发平台,具有体积小、功耗低、可编程性强等优点,非常适合用于开发花样流水灯及其他嵌入式应用。以下场景中流水灯得到了广泛的应用:舞台表演:花样流水灯可用于舞台背景、音乐MV等场合,配合音乐和舞蹈,营造出炫酷、动感的视觉效果。节日庆典:在传统节日如春节、中秋节等场合,花样流水灯可以用于灯笼、彩灯等装饰,为节日增添喜庆氛围。晚会演出:在各种晚会、派对、聚会等场合,花样流水灯可以用于舞台效果、音乐灯光秀等,增强整个活动的氛围和趣味性。智能家居:花样流水灯可以使用在居家灯光控制中,实现远程控制、定时开关、自动调节等功能,提升居住环境的科技感和人性化。二、设计原理2.1 基本原理花样流水灯是一种常见的LED灯效果,通常由多个LED灯组成,通过控制每个LED灯的亮灭顺序和时间,实现花样流水灯的效果。在51单片机中,可以使用定时器和端口控制来实现这一效果。2.2 硬件搭建为了实现花样流水灯,需要将多个LED灯按照一定的顺序连接到51单片机的IO引脚上。电路设计上,为每个LED灯配备一个电阻,并将它们连接到5V电源引脚和地线上。具体硬件搭建方法如下:(1)将多个LED灯依次连接起来组成一个电路链,将第一个LED的正极接到P1.0口,第二个LED的正极接到P1.1口,第三个LED的正极接到P1.2口,以此类推,一共连接7个LED灯。(2)为每个LED灯配备一个适当的电阻,用来限制电流,防止损坏LED。(3)将每个LED的负极连接到5V电源引脚附近的地线上,形成一个完整的电路。2.3 软件实现在软件实现上,使用51单片机的定时器和端口控制来控制LED灯的亮灭顺序和时间。具体方法如下:(1)设置一个计数器变量count,用来保存当前亮起的LED灯的编号(从0开始)。(2)在定时器中断处理函数中,每次计数器溢出时,将当前亮起的LED灯熄灭,并将count加1;当count等于LED灯总数时,将count重置为0。(3)然后,再将下一个LED灯亮起,以此类推。(4)通过控制定时器的计数周期和每个灯亮起的时间,可以调整花样流水灯的效果。三、代码实现3.1 流水灯代码实现1 #include <reg52.h> #define LED_NUM 8 // LED灯总数 #define TIMER_TICK 500 // 定时器计数初值,控制亮灭时间 #define HIGH 0 // 高电平 #define LOW 1 // 低电平 unsigned int count = 0; // 定时器中断处理函数 void TimerInterrupt() interrupt 1 { static unsigned long tick = 0; tick++; if (tick >= TIMER_TICK) { P1 &= ~(1 << count); // 熄灭当前LED count++; // 切换到下一个LED if (count >= LED_NUM) { count = 0; // 重置计数器 } P1 |= (1 << count); // 亮起下一个LED tick = 0; // 重置计时器 } } // 主函数 void main() { unsigned int i; P1 = 0xFF; // 所有IO口初始化为高电平 TMOD |= 0x01; // 定时器0,模式1,16位自动重载 TH0 = (65536 - TIMER_TICK) / 256; TL0 = (65536 - TIMER_TICK) % 256; ET0 = 1; // 定时器中断允许 EA = 1; // 总中断允许 TR0 = 1; // 定时器开始计数 while (1) { // 等待中断事件 } }3.2 流水灯实现效果2【1】逐个点亮 #include <reg52.h> void Delay(unsigned int t) // 延时函数 { unsigned int i, j; for (i = 0; i < t; i++) for (j = 0; j < 125; j++); } void main() { while (1) { unsigned char i; // 定义计数器i for (i = 0; i < 8; i++) // 循环8次,依次点亮LED灯 { P0 = ~(1 << i); // 通过位运算生成控制信号,输出到P0口,控制LED灯点亮 Delay(500); // 延时500ms } } }【2】逐个熄灭 #include <reg52.h> void Delay(unsigned int t) // 延时函数 { unsigned int i, j; for (i = 0; i < t; i++) for (j = 0; j < 125; j++); } void main() { while (1) { unsigned char i; // 定义计数器i for (i = 7; i < 8; i--) // 循环8次,依次熄灭LED灯 { P0 = ~(1 << i); // 通过位运算生成控制信号,输出到P0口,控制LED灯熄灭 Delay(500); // 延时500ms } } }【3】来回流动 #include <reg52.h> void Delay(unsigned int t) // 延时函数 { unsigned int i, j; for (i = 0; i < t; i++) for (j = 0; j < 125; j++); } void main() { while (1) { unsigned char i; // 定义计数器i for (i = 0; i < 8; i++) // 循环8次,依次点亮LED灯 { P0 = ~(1 << i); // 通过位运算生成控制信号,输出到P0口,控制LED灯点亮 Delay(500); // 延时500ms } for (i = 6; i > 0; i--) // 循环6次,依次熄灭LED灯 { P0 = ~(1 << i); // 通过位运算生成控制信号,输出到P0口,控制LED灯熄灭 Delay(500); // 延时500ms } } }3.3 闪光灯的实现下面是三个不同的闪光灯效果的代码,分别为常亮、快闪和慢闪。【1】常亮闪光灯 #include <reg52.h> sbit LED = P1 ^ 0; void main() { while (1) { LED = 0; // LED常亮 } }【2】快闪闪光灯 #include <reg52.h> sbit LED = P1 ^ 0; void delay(unsigned int i) { while (i--); } void main() { while (1) { LED = 0; // LED亮 delay(50000); // 延时一段时间 LED = 1; // LED灭 delay(50000); // 延时一段时间 } }【2】慢闪闪光灯 #include <reg52.h> sbit LED = P1 ^ 0; void delay(unsigned int i) { while (i--); } void main() { while (1) { LED = 0; // LED亮 delay(100000); // 延时一段时间 LED = 1; // LED灭 delay(100000); // 延时一段时间 } }以上三个代码中,都使用了P1口的第0位来控制LED灯的亮灭。其中,第一个代码是常亮闪光灯,只需要将LED置为0。第二个代码是快闪闪光灯,使用了一个delay函数来实现延时,每次延时50000个时钟周期,即约为500ms。第三个代码是慢闪闪光灯,与第二个代码类似,只是将延时时间改为了100000个时钟周期,即约为1s。
-
一、项目介绍呼吸灯是一种常见的LED灯光效果,它可以模拟人类呼吸的变化,使灯光看起来更加柔和和自然。51单片机是一种广泛使用的微控制器,具有体积小、功耗低、成本低等优点,非常适合用于控制LED呼吸灯。本项目的呼吸灯将使用PWM(脉冲宽度调制)技术控制LED亮度,从而实现呼吸灯的效果。在本项目中,将使用51单片机作为主控制器,通过编程实现呼吸灯的控制。将使用C语言编写代码,并使用Keil C51集成开发环境进行编译和调试。使用Proteus仿真软件进行电路设计和仿真,确保电路的正确性和稳定性。二、设计原理2.1 PWM技术PWM是脉冲宽度调制(Pulse Width Modulation)的缩写,是一种通过改变脉冲宽度来控制电路的技术。在数字电路中,PWM是一种非常常见的技术,它可以用来控制电机、LED灯等电子设备的亮度、速度等参数。PWM技术的基本原理是通过控制脉冲的宽度和周期来控制电路的输出。在一个PWM周期内,电路会以一定的频率(也就是PWM频率)产生一系列脉冲,每个脉冲的宽度和高电平时间占整个周期的比例是由控制器根据需要设定的。通过这种方式,可以实现对电路输出的精确控制。在LED呼吸灯项目中,使用定时器模拟PWM技术可以实现呼吸灯效果。具体来说,就是通过定时器产生一定频率的脉冲信号,然后通过改变脉冲的占空比来控制LED灯的亮度。当脉冲的占空比逐渐增大时,LED灯的亮度也会逐渐增强,直到达到最大亮度;当脉冲的占空比逐渐减小时,LED灯的亮度也会逐渐减弱,直到最终熄灭。这样就可以实现类似于人类呼吸的渐变效果。2.2 呼吸灯原理呼吸灯是一种将 LED 灯光做成渐变效果的技术,可以让 LED 的亮度在一定时间内慢慢地增加和减小,使得 LED 的亮度变化更加自然和柔和,适合用于需要渐变效果的场景,如灯光调节、音响节拍等。呼吸灯的原理是通过改变 LED 的 PWM 信号的占空比来控制 LED 的亮度。PWM(Pulse Width Modulation,脉宽调制)是一种调节模拟信号幅度的常用技术,它通过改变信号的脉冲宽度来实现对信号幅度的调节。在呼吸灯中,PWM 信号的频率较高,而占空比则会随着时间的推移而逐渐变化,从而实现 LED 亮度的渐变效果。呼吸灯的实现通常需要使用一个定时器和一个 PWM 模块。定时器用来定时触发中断事件,在中断处理函数中改变 PWM 信号的占空比,从而控制 LED 的亮度。在定时器中断处理函数中,可以通过数学函数(如正弦、余弦等)或者简单的数值计算来得到不同的 PWM 占空比,实现不同的呼吸灯效果。2.3 51单片机51单片机是一种广泛使用的微控制器,具有体积小、功耗低、成本低等优点,非常适合用于控制LED呼吸灯。STC89C52是一种基于MCS-51内核的8位单片机,由中国的STC公司生产。具有高性价比、易于编程、广泛应用等特点,在工业控制、通信、家电控制等领域得到了广泛应用。STC89C52单片机的主要特点如下:采用MCS-51内核,具有8位数据总线和16位地址总线,可以访问64KB的程序存储器和64KB的数据存储器。内置12MHz的晶振,可以通过软件设置分频系数来获得不同的系统时钟频率。具有多种外设接口,包括UART、SPI、I2C、定时器、中断等,可以方便地实现各种应用。支持ISP(In-System Programming)编程方式,可以通过串口或并口进行在线编程,方便快捷。具有低功耗模式,可以通过软件设置进入不同的睡眠模式,以节省系统能耗。STC89C52单片机可以使用C语言或汇编语言进行编程,编写的程序可以通过编译器生成HEX文件,然后通过编程器烧录到芯片中。由于STC89C52单片机的广泛应用和丰富的资料,因此学习和使用它相对来说比较容易。三、代码实现3.1 自动呼吸灯因为STC89C52单片机没有PWM输出功能,只能使用延时函数实现,以下是基于STC89C52单片机实现呼吸灯效果的完整代码: #include <reg52.h> #define LED P1 void delay(unsigned int xms) { unsigned int i, j; for (i = xms; i > 0; i--) for (j = 110; j > 0; j--); } void main() { unsigned char i; while (1) { for (i = 0; i < 255; i++) { LED = i; delay(10); } for (i = 255; i > 0; i--) { LED = i; delay(10); } } }在这个代码中,使用了STC89C52单片机的P1口来控制LED灯的亮度。通过一个循环,让LED灯的亮度从0到255逐渐增加,再从255到0逐渐减小,这样就实现了呼吸灯的效果。在代码中,使用了一个delay函数来控制循环的速度。这个函数可以让程序延时一定的时间,从而控制LED灯的亮度变化速度。在这个代码中,设置了每次延时10毫秒,可以根据需要调整这个值来改变呼吸灯的效果。3.2 按键控制灯光亮度以下是基于STC89C52单片机的LED灯亮度控制完整代码,其中使用了两个按键分别控制LED的亮度和灭度。 #include <reg52.h> #define LED P1 sbit KEY_UP = P3 ^ 2; sbit KEY_DOWN = P3 ^ 3; unsigned char pwm = 0; void delay(unsigned int i) { while (i--); } void key_scan() { if (KEY_UP == 0) { delay(1000); if (KEY_UP == 0) { pwm += 10; if (pwm >= 100) { pwm = 100; } } } if (KEY_DOWN == 0) { delay(1000); if (KEY_DOWN == 0) { pwm -= 10; if (pwm <= 0) { pwm = 0; } } } } void main() { TMOD = 0x01; // 设置定时器0为模式1 TH0 = 0xFC; // 定时器初值,用于产生PWM信号的频率为50Hz TL0 = 0x67; TR0 = 1; // 启动定时器0 ET0 = 1; // 允许定时器0中断 EA = 1; // 开启总中断 while (1) { key_scan(); } } void timer0() interrupt 1 { static unsigned char cnt = 0; if (cnt >= 100) { cnt = 0; } if (cnt < pwm) { LED = 0; } else { LED = 1; } cnt++; }以上代码中,使用了定时器0来产生PWM信号,控制LED的亮度。使用了两个按键来调整LED的亮度和灭度。其中,KEY_UP按键用于增加LED的亮度,KEY_DOWN按键用于减小LED的亮度。在每次定时器中断时,根据pwm的值来控制LED的亮度。当cnt小于pwm时,LED为低电平,LED亮度较高;当cnt大于等于pwm时,LED为高电平,LED亮度较低。
-
一、项目介绍遥控器是现代生活中必不可少的电子产品之一,目前市面上的遥控器种类繁多,应用范围广泛。而 NEC 红外遥控器协议则是目前应用最为广泛的一种协议之一,几乎所有的电视、空调等家用电器都支持该协议。本项目是基于 51 单片机设计支持 NEC 协议的红外遥控器,实现接收解码和发送功能。用户通过按下相应按键进行信号的发射,红外发射二极管向外发射红外信号,被控制设备通过红外接收头接收到这个信号,然后解码执行相应的操作。二、硬件设计本项目所需的硬件器件主要包括:(1)5STC89C52单片机(2)红外发射管(3)红外接收头(4)OLED显示屏(5)按键开关三、软件设计本项目的程序代码采用 C 语言编写,主要分为三个部分:初始化部分、接收解码部分和发送数据部分。(1)初始化部分初始化函数主要完成各个端口的初始化和定时器的配置,以及红外接收头和红外发射管的引脚的配置。(2)接收解码部分接收解码函数主要采用计数器方式对红外遥控器发送的信号进行捕获,并将捕获到的信号转换成 NEC 码。然后根据 NEC 码的规定,解码出用户所输入的指令,最终实现控制设备的功能。(3)发送数据部分发送数据函数主要将单片机中存放的指令码进行编码,并通过红外发射管发送给被控制的设备。在该函数中,需要通过计时器的方式来调节发送信号的时间和频率,以保证信号能够正确传输。四、代码实现4.1 NEC协议解码代码下面是基于 51 单片机实现 NEC 协议解码的代码: #define IRIN P1_0 //红外接收管 unsigned char code Remote6[] = {0x06, 0x09, 0x08, 0x0a, 0x0c, 0x0d, 0x0f}; //遥控器按键对应的命令码 unsigned char read_IR() //读取红外信号 { int k = 0; unsigned char data = 0; while (IRIN) //等待低电平出现 { k++; if (k > 1000) return 0; //超时返回 } k = 0; while (!IRIN) //等待高电平出现 { k++; if (k > 1000) return 0; //超时返回 } k = 0; while (IRIN) //等待低电平出现 { k++; if (k > 1000) return 0; //超时返回 } for (int i = 0; i < 8; i++) //解码 8 个 bit { k = 0; while (!IRIN) //等待高电平出现 { k++; if (k > 1000) return 0; //超时返回 } delay_us(650); if (IRIN) //判断 bit 的值 data |= (1 << i); k = 0; while (IRIN) //等待低电平出现 { k++; if (k > 1000) return 0; //超时返回 } } return data; //返回解码结果 } unsigned char decode_IR(unsigned char code_val) //NEC 码转换为命令码 { for (int i = 0; i < 7; i++) { if (Remote6[i] == code_val) return (i + 1); } return 0; } void main() { unsigned char data = 0; unsigned char code_val = 0; while (1) { data = read_IR(); //读取红外信号 if (data == 0) //判断是否读取成功 continue; code_val = decode_IR(data); //将 NEC 码转换为命令码 switch (code_val) //根据命令码控制设备 { case 1: //控制设备执行命令 1 break; case 2: //控制设备执行命令 2 break; case 3: //控制设备执行命令 3 break; case 4: //控制设备执行命令 4 break; case 5: //控制设备执行命令 5 break; case 6: //控制设备执行命令 6 break; default: break; } } }以上代码主要实现了读取红外信号和将 NEC 码转换为命令码的功能,并且可以根据不同的命令码控制设备执行不同的指令。4.2 NEC协议发送代码以下是基于 51 单片机实现 NEC 协议发送的代码,可以根据需要修改指令码来控制不同的设备: #define IRLED P1_1 //红外发射管 unsigned char code Remote6[] = {0x06, 0x09, 0x08, 0x0a, 0x0c, 0x0d, 0x0f}; //遥控器按键对应的命令码 void delay_us(unsigned int us) //延时函数,单位为微秒 { while (us--) _nop_(); } void send_IR(unsigned char data) //发送红外信号 { unsigned char mask = 0x01; //bit 掩码 for (int i = 0; i < 8; i++) //发送 8 个 bit { if (data & mask) { IRLED = 0; delay_us(600); IRLED = 1; delay_us(1600); } else { IRLED = 0; delay_us(600); IRLED = 1; delay_us(600); } mask <<= 1; //移位更新掩码 } IRLED = 0; //发送结束,将红外发射管关闭 } void send_command(unsigned char code_val) //将命令码转换为 NEC 码并发送 { unsigned char nec_val = 0; if (code_val > 6) //判断是否超出范围 return; nec_val = 0x80 | (code_val << 4) | ((~code_val) & 0x0F); //计算 NEC 码 for (int i = 0; i < 2; i++) //发送两遍,以提高成功率 { send_IR(nec_val); delay_ms(50); } } void main() { while (1) { //向电视发送命令码为 1 的指令 send_command(1); delay_ms(1000); //延时 1s } }以上代码主要实现了将命令码转换为 NEC 码并发送的功能,可以根据需要修改指令码来控制不同的设备。红外发射管发射的红外信号有一定的传输范围和传输角度限制,需要根据具体情况调整发射管的位置和方向。4.3 按键检测代码以下是支持控制发送不同控制码的代码,可以根据需要修改指令码和按键设置: #define IRLED P1_1 //红外发射管 #define IRIN P1_0 //红外接收管 unsigned char code Remote6[] = {0x06, 0x09, 0x08, 0x0a, 0x0c, 0x0d, 0x0f}; //遥控器按键对应的命令码 unsigned char read_IR() //读取红外信号 { int k = 0; unsigned char data = 0; while (IRIN) //等待低电平出现 { k++; if (k > 1000) return 0; //超时返回 } k = 0; while (!IRIN) //等待高电平出现 { k++; if (k > 1000) return 0; //超时返回 } k = 0; while (IRIN) //等待低电平出现 { k++; if (k > 1000) return 0; //超时返回 } for (int i = 0; i < 8; i++) //解码 8 个 bit { k = 0; while (!IRIN) //等待高电平出现 { k++; if (k > 1000) return 0; //超时返回 } delay_us(650); if (IRIN) //判断 bit 的值 data |= (1 << i); k = 0; while (IRIN) //等待低电平出现 { k++; if (k > 1000) return 0; //超时返回 } } return data; //返回解码结果 } void delay_ms(unsigned int ms) //延时函数,单位为毫秒 { while (ms--) for (int i = 0; i < 120; i++) _nop_(); } void delay_us(unsigned int us) //延时函数,单位为微秒 { while (us--) _nop_(); } void send_IR(unsigned char data) //发送红外信号 { unsigned char mask = 0x01; //bit 掩码 for (int i = 0; i < 8; i++) //发送 8 个 bit { if (data & mask) { IRLED = 0; delay_us(600); IRLED = 1; delay_us(1600); } else { IRLED = 0; delay_us(600); IRLED = 1; delay_us(600); } mask <<= 1; //移位更新掩码 } IRLED = 0; //发送结束,将红外发射管关闭 } void send_command(unsigned char code_val) //将命令码转换为 NEC 码并发送 { unsigned char nec_val = 0; if (code_val > 6) //判断是否超出范围 return; nec_val = 0x80 | (code_val << 4) | ((~code_val) & 0x0F); //计算 NEC 码 for (int i = 0; i < 2; i++) //发送两遍,以提高成功率 { send_IR(nec_val); delay_ms(50); } } void main() { unsigned char data = 0; unsigned char code_val = 0; while (1) { data = read_IR(); //读取红外信号 if (data > 0) //判断是否有按键按下 { code_val = Remote6[data - 1]; //根据按键编号获取命令码 send_command(code_val); //将命令码转换为 NEC 码并发送 delay_ms(500); //延时一段时间,防止频繁发送 } } }以上代码主要实现了支持控制发送不同控制码的功能,可以根据需要修改指令码和按键设置。当用户按下遥控器上的按键时,程序会根据按键编号获取到对应的命令码,并将其转换为 NEC 码进行发送。
-
一、项目介绍随着社会经济的快速发展,人们对节能环保的要求越来越高,电动车因其无污染、噪音小、使用成本低等优点逐渐成为了市场关注的焦点。同时,随着科技的不断进步和应用,电动车的技术水平也在不断提高。为了更好地满足市场需求和科技进步的要求,本项目基于51单片机设计了一款电动车控制器。主要包括电动车控制和驱动两个关键部分。其中,控制部分采用51单片机作为控制核心,通过编程实现电动车前后行驶、左右转向、加速等操作。而驱动部分则采用L298N驱动芯片驱动直流电机。当前设计的电动车,支持锂电池供电、支持按键实现电动车前后行驶、左右转向和加速等操作,电机采用直流电机,驱动芯片采用L298N。二、系统架构本系统由控制器、电机、驱动芯片、锂电池和按键等组成,其功能、特点如下:(1)控制器:采用AT89S52微控制器,作为整个系统的核心控制部分。控制器接收来自按键的信号,控制驱动芯片输出电机控制信号,从而实现对电动车的前后行驶、左右转向、加速等控制功能。(2)电机:采用直流电机,其转速和转向可通过驱动芯片控制信号进行调节。(3)驱动芯片:采用L298N驱动芯片,为电机提供驱动电流,并控制电机转速和转向。L298N驱动芯片具有功率大、稳定性好等特点。(4)锂电池:为电动车提供动力,具有体积小、能量密度高、充电效率高、自放电率低等优点。(5)按键:用于控制和调节电动车的运行状态,包括前后行驶、左右转向、加速等操作。三、系统设计3.1 控制器设计本项目采用STC89C52为主控芯片,主要功能是接收来自按键的信号,并通过控制L298N驱动芯片输出驱动电流,从而控制电机的转速和转向。控制器还需要实现锂电池充电管理、限位保护等的功能。设计流程:(1)编写单片机的逻辑程序,实现对按键信号的捕获和处理,以及对L298N驱动芯片的控制。(2)为了实现锂电池充电和保护,采用锂电池充电模块和充电管理芯片。3.2 电机和驱动芯片设计本项目电机采用直流电机,驱动芯片采用L298N。设计流程:(1)根据电机型号和参数,确定合适的电机供电电压和控制电路。(2)根据实际需要,确定L298N驱动芯片的工作模式和参数,设计驱动电路。(3)为提高电机的效率和寿命,添加电机驱动电阻、反电动势抑制电路电路。3.3 锂电池设计本项目采用锂电池供电。设计流程:(1)根据需要,选择适当的锂电池型号和容量。(2)设计电池充电管理电路,实现对锂电池的充电和保护。(3)结合其他电路的设计,完成对锂电池的供电和相应的充电管理。3.4 按键设计按键是控制电动车运行状态的关键部分。设计流程:(1)根据实际需要,确定需要添加的按键类型和数量。(2)设计按键接口电路,实现按键信号的捕获和处理。(3)结合控制器设计,实现对电动车的前后行驶、左右转向、加速等操作控制。四、代码实现4.1 按键检测程序设计本项目用到了9个按键,按键按下是低电平。 实现了前后行驶切换控制、左右转向灯控制、加速控制、喇叭控制、前后刹车灯控制、一个开机键。以下是按键的完整逻辑代码: #include <reg52.h> sbit key1 = P1^0; // 按键1 sbit key2 = P1^1; // 按键2 sbit key3 = P1^2; // 按键3 sbit key4 = P1^3; // 按键4 sbit key5 = P1^4; // 按键5 sbit key6 = P1^5; // 按键6 sbit key7 = P1^6; // 按键7 sbit key8 = P1^7; // 按键8 sbit key9 = P2^0; // 按键9 sbit forward = P3^0; // 前进 sbit backward = P3^1; // 后退 sbit left = P3^2; // 左转灯 sbit right = P3^3; // 右转灯 sbit accelerate = P3^4; // 加速器 sbit horn = P3^5; // 喇叭 sbit stoplight1 = P3^6; // 前刹车灯 sbit stoplight2 = P3^7; // 后刹车灯 void main() { while(1) { if(key1 == 0) { // 按键1按下 forward = 1; backward = 0; } if(key2 == 0) { // 按键2按下 forward = 0; backward = 1; } if(key3 == 0) { // 按键3按下 left = 1; } else { left = 0; } if(key4 == 0) { // 按键4按下 right = 1; } else { right = 0; } if(key5 == 0) { // 按键5按下 accelerate = 1; } else { accelerate = 0; } if(key6 == 0) { // 按键6按下 horn = 1; } else { horn = 0; } if(key7 == 0) { // 按键7按下 stoplight1 = 1; } else { stoplight1 = 0; } if(key8 == 0) { // 按键8按下 stoplight2 = 1; } else { stoplight2 = 0; } if(key9 == 0) { // 按键9按下 forward = 0; backward = 0; left = 0; right = 0; accelerate = 0; horn = 0; stoplight1 = 0; stoplight2 = 0; } } }代码通过不断检测按键的电平状态,实现了对电动车的前后行驶、左右转向灯控制、加速、喇叭以及前后刹车灯控制等操作。当按键被按下时,对应的功能就会被执行,否则就会停止执行。其中,第9个按键为开机键,当按下时将所有功能都清零。4.2 L298芯片控制电机代码下面是 L298N 驱动模块控制电机正反转的代码: #include <reg52.h> sbit ena = P2^0; // 使能A端口 sbit in1 = P2^1; // A+控制信号 sbit in2 = P2^2; // A-控制信号 sbit enb = P2^3; // 使能B端口 sbit in3 = P2^4; // B+控制信号 sbit in4 = P2^5; // B-控制信号 void delay(int time) { // 延时函数 int i, j; for(i = 0; i < time; i++) { for(j = 0; j < 120; j++); } } void main() { ena = 1; // 使能A端口 enb = 1; // 使能B端口 while(1) { in1 = 1; // A+ 电流正向 in2 = 0; // A- 电流反向 in3 = 1; // B+ 电流正向 in4 = 0; // B- 电流反向 delay(1000); // 延时一段时间 in1 = 0; // A+ 电流反向 in2 = 1; // A- 电流正向 in3 = 0; // B+ 电流反向 in4 = 1; // B- 电流正向 delay(1000); // 延时一段时间 } }L298N 驱动模块可以控制电机的正反转,其中 in1、in2 控制 A 相电流的方向,in3、in4 控制 B 相电流的方向,ena、enb 是使能端口,需要设置为高电平才能控制电机。在例子中,先将 ena 和 enb 设置为高电平,然后让电机正向运转一段时间,再让电机反向运转一段时间,不断循环实现正反转。
-
一、OpenCV介绍OpenCV 是基于开源许可证的跨平台计算机视觉库,提供了一组丰富、广泛的图像处理和计算机视觉算法。OpenCV 支持多种编程语言,包括 C++、Python、Java 等,可以运行在 Linux、Windows、Mac OS 等平台上。OpenCV 能够在图像上绘制各种几何形状、文本和曲线,以及对图像进行调整、裁剪和旋转等操作,这些功能都为图像的分析和处理提供了很大的帮助。以下是 OpenCV 可以绘制图像的一些应用:(1)图像标注:在图像上添加标注或者注释,例如在目标检测或者图像分类任务中,通过在图像上绘制框、标签等信息来标记检测到的目标。(2)处理后显示:例如在图像处理过程中,可以在处理前和处理后的图像上绘制对比图,直观地显示图像处理的效果。(3)实时显示:通过持续不断地在屏幕上绘画来实现实时显示效果,例如在视频处理中输出处理后的视频流并将其实时渲染在屏幕上。二、绘制图形【1】绘制图形弹窗显示下面代码实现的功能:使用 OpenCV(C++) 新建一张透明图片,在图片里绘制一个矩形、一条直线、一段文字、一个圆。 // 创建一张大小为 512x512,具有 alpha 通道的透明图片 cv::Mat img(512, 512, CV_8UC4, cv::Scalar(0, 0, 0, 0)); // 在图片上绘制一个矩形 cv::rectangle(img, cv::Point(50, 50), cv::Point(200, 150), cv::Scalar(255, 0, 0, 255), -1); // 在图片上绘制一条直线 cv::line(img, cv::Point(300, 100), cv::Point(450, 100), cv::Scalar(0, 255, 0, 255), 3); // 在图片上绘制一段文本 std::string text = "Hello, OpenCV!"; cv::putText(img, text, cv::Point(50, 300), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255, 255), 2); // 在图片上绘制一个圆 cv::circle(img, cv::Point(400, 350), 50, cv::Scalar(255, 255, 0, 255), -1); // 显示图片 cv::imshow("image", img); cv::waitKey(0);cv::Mat 是 OpenCV 中表示图像的数据结构,它可以存储多通道的图像。 cv::Scalar 是一个四通道的实数向量,用于表示像素点的颜色和 alpha 值。 cv::Point 是一个二维整型向量,用于表示像素点的坐标。 cv::rectangle 函数用于在图片上绘制矩形。 cv::line 函数用于在图片上绘制直线。 cv::putText 函数用于在图片上绘制文本。 cv::circle 函数用于在图片上绘制圆。【2】绘制图形保存到本地 // 创建一张大小为 512x512,具有 alpha 通道的透明图片 cv::Mat img(512, 512, CV_8UC4, cv::Scalar(0, 0, 0, 0)); // 在图片上绘制一个矩形 cv::rectangle(img, cv::Point(50, 50), cv::Point(200, 150), cv::Scalar(255, 0, 0, 255), -1); // 在图片上绘制一条直线 cv::line(img, cv::Point(300, 100), cv::Point(450, 100), cv::Scalar(0, 255, 0, 255), 3); // 在图片上绘制一段文本 std::string text = "Hello, OpenCV!"; cv::putText(img, text, cv::Point(50, 300), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255, 255), 2); // 在图片上绘制一个圆 cv::circle(img, cv::Point(400, 350), 50, cv::Scalar(255, 255, 0, 255), -1); // 保存图片到本地 cv::imwrite("output.png", img);三、函数功能介绍【1】绘制直线cv::line 函数用于在图像上绘制一条直线,其参数如下: void cv::line(InputOutputArray img, Point pt1, Point pt2, const Scalar& color, int thickness = 1, LineTypes lineType = LINE_8, int shift = 0); img: 输入输出参数,表示待绘制的目标图像。pt1: 输入参数,表示的是直线的起点坐标,是一个 cv::Point 类型的对象。pt2: 输入参数,表示的是直线的终点坐标,也是一个 cv::Point 类型的对象。color: 输入参数,表示绘制直线的颜色以及透明度,是一个 cv::Scalar 类型的对象。常见的颜色有:红色(0, 0, 255)、绿色(0, 255, 0)、蓝色(255, 0, 0)等。thickness: 可选参数,表示绘制直线的宽度。默认值为 1 表示绘制一个像素宽度的直线,如果设置为负值,则表示绘制一条填充直线。 lineType : 可选参数,表示直线的类型,可以取以下几个值:cv::LINE_4: 表示绘制一条 4 连通的直线,默认值。cv::LINE_8: 表示绘制一条 8 连通的直线。cv::LINE_AA: 表示绘制一条抗锯齿的直线。shift: 可选参数,表示坐标点像素值所占用的位数,默认为 0。【2】绘制圆cv::circle 函数用于在图像上绘制一个圆,其参数如下: void cv::circle(InputOutputArray img, Point center, int radius, const Scalar& color, int thickness = 1, LineTypes lineType = LINE_8, int shift = 0); img: 输入输出参数,表示待绘制的目标图像。center: 输入参数,表示圆心坐标,是一个 cv::Point 类型的对象。radius: 输入参数,表示圆的半径。color: 输入参数,表示绘制圆的颜色以及透明度,是一个 cv::Scalar 类型的对象。thickness: 可选参数,表示圆线条的宽度。默认值为 1 表示绘制一个像素宽度的圆,如果设置为负值,则表示绘制一条填充的圆。 lineType : 可选参数,表示圆边界的类型,可以取以下几个值:cv::LINE_4: 表示绘制四个相邻的点的圆边界,默认值。cv::LINE_8: 表示绘制八个相邻的点的圆边界。cv::LINE_AA: 表示绘制抗锯齿的圆边界。shift: 可选参数,表示坐标点像素值所占用的位数,默认值为 0。【3】绘制矩形cv::rectangle 函数用于在图像上绘制一个矩形,其参数如下: void cv::rectangle(InputOutputArray img, Rect rect, const Scalar& color, int thickness = 1, LineTypes lineType = LINE_8, int shift = 0); img: 输入输出参数,表示待绘制的目标图像。rect: 输入参数,表示矩形,是一个 cv::Rect 类型的对象,可以通过传递左上角和右下角坐标的方式来定义一个矩形。color: 输入参数,表示绘制矩形的颜色以及透明度,是一个 cv::Scalar 类型的对象。thickness: 可选参数,表示矩形边框的宽度。默认值为 1 表示绘制一个像素宽度的矩形,如果设置为负值,则表示绘制一条填充的矩形。 lineType : 可选参数,表示矩形边框的类型,可以取以下几个值:cv::LINE_4: 表示绘制四个相邻的点的矩形边框,默认值。cv::LINE_8: 表示绘制八个相邻的点的矩形边框。cv::LINE_AA: 表示绘制抗锯齿的矩形边框。shift: 可选参数,表示坐标点像素值所占用的位数,默认值为 0。【4】绘制文本cv::putText 函数用于在图像上绘制文本,其参数如下:void cv::putText(InputOutputArray img, const String& text, Point org, int fontFace, double fontScale, Scalar color, int thickness = 1, int lineType = LINE_8, bool bottomLeftOrigin = false); img: 输入输出参数,表示待绘制的目标图像。text: 输入参数,表示要绘制的文本字符串。org: 输入参数,表示文本框左下角的坐标点,是一个 cv::Point 类型的对象。 fontFace : 输入参数,表示字体类型,可以取以下几个值:cv::FONT_HERSHEY_COMPLEX: 复杂风格字体。cv::FONT_HERSHEY_COMPLEX_SMALL: 小字号复杂风格字体。cv::FONT_HERSHEY_DUPLEX: 双线条字体。cv::FONT_HERSHEY_PLAIN: 单线条字体。cv::FONT_HERSHEY_SIMPLEX: 正常大小的字体。cv::FONT_HERSHEY_TRIPLEX: 三线条字体。fontScale: 输入参数,表示字体大小缩放比例。color: 输入参数,表示绘制文本的颜色以及透明度,是一个 cv::Scalar 类型的对象。thickness: 可选参数,表示文本轮廓线条的宽度。默认值为 1 表示绘制一个像素宽度的文本,如果设置为负值,则表示绘制一条填充的文本。lineType : 可选参数,表示文本边界的类型,可以取以下几个值:cv::LINE_4: 表示绘制四个相邻的点的文本边界,默认值。cv::LINE_8: 表示绘制八个相邻的点的文本边界。cv::LINE_AA: 表示绘制抗锯齿的文本边界。bottomLeftOrigin: 可选参数,表示坐标点是否为文本框左下角的坐标点,默认值为 false,表示坐标点为文本框左上角的坐标点。
-
一、项目介绍随着智能物联网技术的不断发展,人们的生活方式和消费习惯也正在发生改变。如今越来越多的人习惯于在线购物、自助购物等新型消费模式,因此智能零售自助柜应运而生。本项目设计开发一款基于STM32主控芯片的智能零售自助柜,通过重力传感器监测货柜内商品重量变化,并通过WiFi通信模块与手机端实现交互。用户可以通过输入账号密码,柜门自动打开,用户自取商品后关闭柜门,柜门锁定,系统根据重量变化判断用户拿取的商品并从账户自动扣费。同时,用户也可以通过手机端查看消费流水、商品库存,并进行补货和充值等操作。智能零售自助柜的应用场景非常广泛,可以应用于商场、超市、酒店、机场、车站等各类场景。通过自助购物,可以提高消费者的消费体验和购物效率,同时也降低了商家的人力成本和物流成本。二、设计思路【1】功能细节总结(1)ESP8266配置成AP+TCP服务器模式与手机APP连接。(2)手机APP可以完成用户的注册,充值功能,然后通过连接货柜将数据同步到货柜的存储芯片上(W25Q64-FLASH保存数据)。(3)手机APP连接货柜之后,可以拉取数据显示,了解货柜现在的物品哪些已经售卖出去,哪些还没有售卖。,每个物品是放在一个货柜格子里,透明玻璃可以查看到物品。【2】硬件选型主控芯片:STM32F103RCT6是一款主流的32位ARM Cortex-M系列微控制器,具有高性能、低功耗和易于开发等特点,因此被选择作为该系统的主控芯片。重力传感器:HX711重力传感器模块采用24位高精度芯片,能够精确测量重量,适用于该系统中货柜内商品的重量监测。SG90舵机:该系统需要控制柜门的打开和关闭,因此使用舵机来实现柜门控制。矩阵键盘:用户需要输入账号密码进行登录,因此使用矩阵键盘作为输入设备。显示屏:OLED显示屏具有低功耗、高对比度、快速响应等特点,适用于该系统中的桌面显示界面。WiFi模块:ESP8266-WIFI模块是一款成本低、体积小、性能稳定的WiFi通信模块,适合在该系统中与手机APP进行无线通信。【2】程序设计思路初始化系统,包括各个外设的初始化,如WiFi模块、重力传感器HX711模块、矩阵键盘等;用户输入账号密码,判断是否为有效用户;根据重力传感器读取货柜内商品重量,判断用户拿取的商品并从账户自动扣费;控制柜门打开和关闭,同时显示屏上显示相关提示信息;同步数据到手机APP。【3】设备操作流程用户输入账号密码,系统进行验证,判断是否为有效用户;如果验证通过,屏幕上显示“登录成功”,并显示货柜内商品列表和对应价格;用户选择需要购买的商品,系统根据重力传感器读取货柜内商品重量,并判断用户拿取的商品并从账户自动扣费;系统控制电磁锁或舵机将柜门打开,用户自取商品后关闭柜门;重力传感器监测到货柜内重量变化,系统自动判断用户拿取的商品种类和数量,并在显示屏上显示相关提示信息,如显示扣费金额;控制柜门锁定,确保商品安全,同时在显示屏上显示“门已锁定”等相关提示信息;同步扣费记录和商品库存信息到手机APP,以便用户查看消费流水和进行补货等操作。如需要充值,用户可以在手机APP上进行余额充值操作。三、代码实现【1】OLED显示屏驱动代码下面是OLED显示屏的测试代码。使用的SPI接口的OLED显示屏。 #include "stm32f10x.h" #include "OLED.h" // OLED驱动库头文件 void OLED_ShowString(uint8_t x, uint8_t y, uint8_t *str) { uint8_t i = 0; while(str[i] != '\0'){ if(x > OLED_WIDTH - 8){ // 满行自动换行 x = 0; y++; } OLED_ShowChar(x, y, str[i]); // 显示单个字符 x += 8; // 水平方向上的下一个字符 i++; } } void OLED_SPI_SendByte(uint8_t data) { while(SPI_I2S_GetFlagStatus(OLED_SPI_PORT, SPI_I2S_FLAG_TXE) == RESET); // 等待发送缓冲区空 SPI_I2S_SendData(OLED_SPI_PORT, data); // 通过SPI发送数据 } void OLED_WriteCmd(uint8_t cmd) { OLED_DC_Clr(); // 将DC置为0,表示发送命令 OLED_CS_Clr(); // 将CS置为0,选中OLED芯片 OLED_SPI_SendByte(cmd); // 发送命令 OLED_CS_Set(); // 将CS置为1,取消OLED芯片选中 } void OLED_WriteData(uint8_t data) { OLED_DC_Set(); // 将DC置为1,表示发送数据 OLED_CS_Clr(); // 将CS置为0,选中OLED芯片 OLED_SPI_SendByte(data); // 发送数据 OLED_CS_Set(); // 将CS置为1,取消OLED芯片选中 } int main(void) { uint32_t i; // 初始化SPI接口 SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); // 打开SPI1时钟 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // 设置SPI工作模式 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 数据位宽8bit SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // 时钟极性为低电平 SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // 时钟第一个边沿采样 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 软件控制CS信号 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; // 预分频系数为256 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // MSB先行 SPI_InitStructure.SPI_CRCPolynomial = 7; // CRC校验值 SPI_Init(SPI1, &SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); // 使能SPI1 // 初始化OLED显示屏 OLED_Init(); // OLED初始化 // 显示数字 char str[] = "1234567890"; OLED_ShowString(0, 0, (uint8_t *)str); // 在(0,0)坐标处显示字符串 while(1){ for(i = 0; i < 10000000; i++); // 延时等待 } } OLED_WriteCmd 函数用于向 OLED 显示屏发送命令,而 OLED_WriteData 函数用于向 OLED 显示屏发送数据。OLED_SPI_SendByte 函数是底层SPI数据传输的关键代码部分。【2】HX711称重传感器代码 #include "stm32f10x.h" #include <stdio.h> #include "usart.h" #define HX711_SCK_GPIO_RCC RCC_APB2Periph_GPIOB #define HX711_SCK_GPIO_PORT GPIOB #define HX711_SCK_GPIO_PIN GPIO_Pin_13 #define HX711_DOUT_GPIO_RCC RCC_APB2Periph_GPIOB #define HX711_DOUT_GPIO_PORT GPIOB #define HX711_DOUT_GPIO_PIN GPIO_Pin_15 uint32_t read_HX711_data(void); void init_GPIO(void); void init_USART1(void); void USART1_SendChar(char ch); int main(void) { uint32_t hx711_value; init_GPIO(); init_USART1(); while(1){ hx711_value = read_HX711_data(); // 读取 HX711 传感器数据 printf("The weight is: %d g\r\n", hx711_value); // 通过串口打印 HX711 传感器读取的数据 } } // 从 HX711 传感器读取数据 uint32_t read_HX711_data(void) { uint32_t weight = 0; uint8_t i; GPIO_SetBits(HX711_SCK_GPIO_PORT, HX711_SCK_GPIO_PIN); // 拉高 SCK 管脚 GPIO_ResetBits(HX711_DOUT_GPIO_PORT, HX711_DOUT_GPIO_PIN); // 拉低 DOUT 管脚 for(i = 0; i < 24; i++){ GPIO_ResetBits(HX711_SCK_GPIO_PORT, HX711_SCK_GPIO_PIN); // 拉低 SCK 管脚,使得 HX711 将数据推入 DOUT 管脚 weight <<= 1; // 左移一位,为下一次读取做准备 if(GPIO_ReadInputDataBit(HX711_DOUT_GPIO_PORT, HX711_DOUT_GPIO_PIN)) weight++; // 如果 DOUT 管脚为高电平,那么就在 weight 中保存 "1" GPIO_SetBits(HX711_SCK_GPIO_PORT, HX711_SCK_GPIO_PIN); // 拉高 SCK 管脚,为下一次读取做准备 } GPIO_ResetBits(HX711_SCK_GPIO_PORT, HX711_SCK_GPIO_PIN); // 最后时刻需要拉低 SCK 管脚一次 weight = (weight ^ 0x800000) - 0x800000; // 将读出的24位二进制重量值转化为带符号数,这里我们只考虑单通道读取的情况(如有多个物理传感器需进行一定的计算处理) return weight; } // 初始化 GPIO 管脚 void init_GPIO(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(HX711_SCK_GPIO_RCC | HX711_DOUT_GPIO_RCC, ENABLE); // 打开 SCK 和 DOUT 管脚时钟 GPIO_InitStructure.GPIO_Pin = HX711_SCK_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(HX711_SCK_GPIO_PORT, &GPIO_InitStructure); // 初始化 SCK 管脚 GPIO_InitStructure.GPIO_Pin = HX711_DOUT_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(HX711_DOUT_GPIO_PORT, &GPIO_InitStructure); // 初始化 DOUT 管脚 } // 初始化 USART1 void init_USART1(void) { USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // 打开 USART1 时钟 USART_InitStructure.USART_BaudRate = 115200; // 波特率 115200 USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 数据位 8 位 USART_InitStructure.USART_StopBits = USART_StopBits_1; // 停止位 1 位 USART_InitStructure.USART_Parity = USART_Parity_No; // 无奇偶校验 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件流控制 USART_InitStructure.USART_Mode = USART_Mode_Tx; // 只启用串口发送 USART_Init(USART1, &USART_InitStructure); // 初始化 USART1 USART_Cmd(USART1, ENABLE); // 使能 USART1 } // 通过 USART1 发送字符 void USART1_SendChar(char ch) { while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 等待发送缓冲区为空 USART_SendData(USART1, (uint8_t)ch); // 发送数据 } 代码执行流程说明:(1)通过 init_GPIO() 函数初始化 SCK 和 DOUT 两个 GPIO 管脚,并通过 init_USART1() 函数初始化 USART1 串口。其中,初始化 SCK 管脚为输出模式,DOUT 管脚为输入模式,USART1 算是串口助手,用于将数据打印输出。(2)read_HX711_data() 函数用于向 HX711 传感器发出读取数据的指令,并将返回的数据进行处理(将24位二进制重量值转化为带符号数)后返回。(3)在主函数的 while 循环中,不断调用 read_HX711_data() 函数读取 HX711 传感器的数据,并通过串口打印出来。【3】SG90舵机控制代码下面是SG90舵机的控制代码,可以按照指定的角度旋转。 #include "stm32f10x.h" #include "delay.h" #define GPIO_PORT GPIOA #define GPIO_PIN GPIO_Pin_1 #define RCC_APB2Periph_GPIO RCC_APB2Periph_GPIOA #define PWM_FREQ 50 void servoInit(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIO, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIO_PORT, &GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period = 9999; //计数器最大值 TIM_TimeBaseStructure.TIM_Prescaler = (72 * 2) - 1; //时钟分频,72是系统时钟频率,2是倍频 TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 0; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM2, &TIM_OCInitStructure); TIM_Cmd(TIM2, ENABLE); } void servoSetAngle(uint8_t angle) { uint16_t pwmVal = (uint16_t)(500 + angle * 10.0 / 9.0); TIM_SetCompare1(TIM2, pwmVal); delay_ms(100); } int main(void) { SystemInit(); delay_init(); servoInit(); while(1) { servoSetAngle(0); delay_ms(1000); servoSetAngle(90); delay_ms(1000); servoSetAngle(180); delay_ms(1000); } }
-
一、设计介绍当前基于STC89C52单片机和PCF8591、PulseSensor心率传感器、SSD1306 OLED显示屏等元件实现了一个心率检测仪。检测仪可以通过采集心率传感器输出的模拟信号,并经过AD转换后计算出实时的心率值,然后将心率值通过IIC协议传输到OLED显示屏上进行展示。用户只需要将心率传感器固定在身体上,启动心率检测仪,就能够方便地实时监测自己的心率。本项目的应用范围广泛,可以用于健康管理、健身锻炼、医疗等领域。在家庭中,人们可以使用该心率检测仪,及时监测自己的心率,对身体健康进行有效管理和控制;在健身房或健身教练中心,教练可以利用该心率检测仪来监测运动员的心率变化,以便针对性地调整训练计划,提高训练效果;在医疗机构中,医护人员可以使用该心率检测仪,监测患者的心率情况,及时发现异常情况,为患者的治疗提供有力的依据和参考。二、硬件选型本项目需要用到的硬件:STC89C52单片机:作为主控芯片,负责读取PulseSensor心率传感器的模拟信号、进行AD转换、计算心率值,并将心率值通过IIC协议传输到OLED显示屏上进行展示。PCF8591模块:用于实现STC89C52单片机通过IIC总线对PulseSensor心率传感器进行数据采集和AD转换。PulseSensor心率传感器:用于采集人体的微弱心跳信号,并将信号输出到PCF8591模块。SSD1306 OLED显示屏:用于显示心率检测结果,包括心率值及单位。杜邦线、面包板:用于连接各个硬件模块和搭建电路原型。三、实现代码下面是项目核心代码,通过PCF8591接PulseSensor心率传感器采集心率,并通过IIC协议的0.96寸OLED显示屏显示出来:#include <reg52.h> #include <intrins.h> #define uchar unsigned char #define uint unsigned int sbit SCL = P1^0; sbit SDA = P1^1; sbit LED = P2^0; #define ADDR_PCF8591 0x90 // PCF8591的IIC地址:1001 0000 #define CMD_PCF8591_WR 0x40 // PCF8591写数据命令字:0100 CCCC,CCCC为通道选择 #define CMD_PCF8591_RD 0x41 // PCF8591读数据命令字:0100 CCCC,CCCC为通道选择 #define ADDR_OLED 0x78 // SSD1306 OLED显示屏的IIC地址:0111 1000 uchar heartRate[3]; // 存储心率值的字符串 /** * 延时函数,控制IIC通信速度 */ void Delay() { uint i, j; for(i=0; i<50; i++) for(j=0; j<500; j++); } /** * IIC启动信号 */ void IIC_Start() { SCL = 1; SDA = 1; Delay(); SDA = 0; Delay(); SCL = 0; } /** * IIC停止信号 */ void IIC_Stop() { SCL = 0; SDA = 0; Delay(); SCL = 1; SDA = 1; Delay(); } /** * IIC发送一个字节的数据 * @param byte 发送的字节 * @return 接收到的应答位 */ uchar IIC_SendByte(uchar byte) { uchar i, ack; for(i=0; i<8; i++) { SDA = (bit)(byte & 0x80); byte <<= 1; Delay(); SCL = 1; Delay(); SCL = 0; } SDA = 1; Delay(); SCL = 1; Delay(); ack = SDA; SCL = 0; return ack; } /** * 初始化PCF8591模块 */ void Init_PCF8591() { IIC_Start(); IIC_SendByte(ADDR_PCF8591); IIC_SendByte(CMD_PCF8591_WR | 0); IIC_Stop(); } /** * 读取PCF8591的AD值 * @param ch 选择的通道编号 * @return AD转换后的数值 */ uchar Read_PCF8591(uchar ch) { uchar value; IIC_Start(); IIC_SendByte(ADDR_PCF8591); IIC_SendByte(CMD_PCF8591_WR | ch); IIC_Stop(); IIC_Start(); IIC_SendByte(ADDR_PCF8591 | 0x01); value = IIC_SendByte(0xFF); IIC_Stop(); return value; } /** * 初始化SSD1306 OLED显示屏 */ void Init_OLED() { IIC_Start(); IIC_SendByte(ADDR_OLED); IIC_SendByte(0xAE); // 关闭显示 IIC_SendByte(0x00); // 列地址低4位 IIC_SendByte(0x10); // 列地址高4位 IIC_SendByte(0x40); // 起始行地址 IIC_SendByte(0xB0); // 设置页地址 IIC_SendByte(0x81); // 对比度设置命令 IIC_SendByte(0xCF); // 对比度值 IIC_SendByte(0xA1); // 段复用设置 IIC_SendByte(0xA6); // 常规显示模式 IIC_SendByte(0xA8); // 多路复用设置 IIC_SendByte(0x3F); // 页面数-1 IIC_SendByte(0xC8); // 扫描方式设置 IIC_SendByte(0xD3); // 设置显示偏移 IIC_SendByte(0x00); IIC_SendByte(0xD5); // 频率设置命令 IIC_SendByte(0x80); // 分频系数 IIC_SendByte(0xD9); // 设置预充电周期 IIC_SendByte(0xF1); IIC_SendByte(0xDA); // 设置COM硬件连接方式 IIC_SendByte(0x12); IIC_SendByte(0xDB); // VCOMH设置 IIC_SendByte(0x40); IIC_SendByte(0xA4); // 全部点亮/正常显示 IIC_SendByte(0xA6); // 正常/反显示控制 IIC_SendByte(0xAF); // 开启显示 IIC_Stop(); } /** * 在OLED上显示字符串 * @param x 开始列地址 * @param y 开始页地址 * @param str 需要显示的字符串 */ void ShowString_OLED(uchar x, uchar y, uchar *str) { uchar i = 0; IIC_Start(); IIC_SendByte(ADDR_OLED); IIC_SendByte(0x00); // 列地址低4位 IIC_SendByte(0x10); // 列地址高4位 IIC_SendByte(0xB0 + y);// 设置页地址 for(i=0; str[i]!='\0'; i++) { IIC_SendByte(0xB0 + y); IIC_SendByte((x + 8*i) & 0x0F); IIC_SendByte(((x + 8*i) >> 4) | 0x10); IIC_SendByte(str[i]); } IIC_Stop(); } /** * 主函数,心率计算和显示 */ void main() { Init_PCF8591(); // 初始化PCF8591模块 Init_OLED(); // 初始化OLED显示屏 while(1) { uchar adValue = Read_PCF8591(0); // 读取PCF8591的AD值 uint timeInterval = 100; // 设定采集心率的时间间隔,单位为毫秒 uint count = 0; // 统计脉搏跳动次数的计数器 uint heartRateValue = 0; // 计算得出的心率值 for (uint i=0; i<timeInterval; i++) // 在一定时间内采集数据 { if (adValue > 200) // 当AD值高于阈值时,统计脉搏跳动次数 { count++; while(adValue > 100) // 等待一段时间,避免同一次脉搏被重复计数 { adValue = Read_PCF8591(0); } } adValue = Read_PCF8591(0); // 读取下一个AD值 } heartRateValue = (uint)(count * 60.0 / timeInterval); // 计算心率值 sprintf(heartRate, "%d", heartRateValue); // 将心率值转换为字符串 ShowString_OLED(0, 0, "Heart Rate:"); // 在OLED上显示标题 ShowString_OLED(80, 0, heartRate); // 在OLED上显示心率值 ShowString_OLED(96, 0, "bpm"); // 在OLED上显示单位 } }
-
一、项目介绍温度检测是工业自动化、生产线等众多领域中常见的应用场景之一,能及时准确地监测温度对于保障生产安全和提高生产效率有着非常重要的作用。而在现代的电子制造行业中,使用单片机和传感器等电子元器件进行温度检测已经成为了一个比较成熟的技术方案。本项目选择STC89C52单片机和DS18B20数字温度传感器,通过读取传感器输出的温度值,经过计算和处理后,并将结果显示在数码管上,实现环境温度的实时监测和显示。其中,STC89C52单片机为主控芯片,负责接收和处理数字温度传感器的数据,并通过数码管将温度值进行显示。二、整体设计【1】设计思路使用 STC89C52 单片机和 DS18B20 数字温度传感器,通过 I/O 口进行连接,读取传感器输出的温度值。通过计算和处理后,将温度值在数码管上进行显示。其中,STC89C52 单片机为主控芯片,负责接收和处理数字温度传感器的数据,并通过数码管将温度值进行显示。【2】硬件连接硬件方面,需要使用 STC89C52 单片机和 DS18B20 数字温度传感器。其中,STC89C52 单片机通过 P1 口连接4位数码管的动态扫描信号线,并与 DS18B20 传感器的 DQ 线相连。DS18B20需要使用一个2.2K欧姆上拉电阻和一个10K欧姆下拉电阻。【3】软件设计在软件方面,主要进行以下操作:(1)初始化函数初始化串行总线,设置为推挽输出,并将数码管段选端口初始化为高电平输出,数码管位选端口初始化为低电平输出。(2)读取温度值函数通过发送读取命令,从 DS18B20 数字温度传感器中读取温度值。(3)温度值计算函数根据 DS18B20 数字温度传感器的温度值计算方法,将读取到的数值进行转换,得到实际温度值。(4)数码管显示函数将温度值分离出整数和小数部分,然后经过数码管驱动程序,通过数码管进行显示。三、具体代码实现【1】DS18B20温度读取DS18B20 是一种数字温度传感器,采用单总线接口进行通讯。它可以在较长的距离内实现温度值的准确测量,并且不需要调零或校准,被广泛应用于各种计算机控制系统、电子设备和温度控制应用中。其分辨率为 12 位,温度范围为 -55 度 Celsius 到 +125 度 Celsius。下面代码实现的功能是:读取DS18B20温度再通过串口打印出来。 #include <reg51.h> #define uchar unsigned char #define uint unsigned int sbit DQ = P1^0; // DS18B20 数字温度传感器数据线连接到 P1.0 引脚 // DS18B20 数据传输函数 void DS18B20_WriteByte(uchar dat); uchar DS18B20_ReadByte(); void DS18B20_Start(); void DS18B20_End(); void DS18B20_Delay(uint i); // 初始化函数 void init(); // 串口初始化函数 void uart_init(); // 串口发送函数 void send_string(char *s); void main() { uchar temp_h, temp_l; uint temp; init(); uart_init(); // 串口初始化 while(1) { DS18B20_Start(); // 启动传输 DS18B20_WriteByte(0xCC); // 忽略 ROM 指令 DS18B20_WriteByte(0x44); // 发送温度转换指令 DS18B20_End(); // 结束传输 DS18B20_Start(); // 启动传输 DS18B20_WriteByte(0xCC); // 忽略 ROM 指令 DS18B20_WriteByte(0xBE); // 发送读取指令 // 读取温度值 temp_l = DS18B20_ReadByte(); // 读取低位温度值 temp_h = DS18B20_ReadByte(); // 读取高位温度值 // 计算温度值 temp = (temp_h << 8) + temp_l; temp = (float)temp / 16; send_string("The temperature is: "); send_string(temp); send_string("\r\n"); DS18B20_End(); // 结束传输 } } // DS18B20 数据传输函数 void DS18B20_WriteByte(uchar dat) { uchar i; for (i = 0; i < 8; i++) { DQ = 0; // 写时序开始 DQ = dat & 0x01; // 写数据 DS18B20_Delay(1); // 延时 1us DQ = 1; // 写时序结束 dat >>= 1; } } uchar DS18B20_ReadByte() { uchar i, dat = 0; for (i = 0; i < 8; i++) { DQ = 0; // 读时序开始 DS18B20_Delay(1); // 延时 1us dat >>= 1; if(DQ) dat |= 0x80; DS18B20_Delay(5); // 延时 5us DQ = 1; // 读时序结束 } return dat; } void DS18B20_Start() { DQ = 1; DS18B20_Delay(1); DQ = 0; DS18B20_Delay(480); DQ = 1; DS18B20_Delay(60); } void DS18B20_End() { DQ = 1; DS18B20_Delay(1); } void DS18B20_Delay(uint i) { while(i--); } // 初始化函数 void init() { TMOD |= 0x20; // 定时器 1 工作在模式 2 TH1 = 0xfd; // 设置波特率,4800 bps TL1 = 0xfd; TR1 = 1; // 启动定时器 } // 串口初始化函数 void uart_init() { SCON = 0x50; // 8 位数据,可变波特率,允许接收 ES = 1; // 允许串口中断 EA = 1; // 允许总中断 } // 串口发送函数 void send_string(char *s) { while(*s) { SBUF = *s; while(!TI); TI = 0; s++; } }【2】读取温度数码管显示数码管是一种数码显示装置,通常由一个数码管的阵列组成,可以用来显示数字、字母和一些特殊符号。广泛应用于各种电子装置中,如计算器、时钟、温度计、电压表、档位指示器等。数码管通常可分为共阳极和共阴极两种类型,其中共阳极的数码管是将阳极连接在一起,通过控制对应的阴极接口以实现显示数字,而共阴极则是将阴极连接在一起,通过控制对应的阳极接口以实现显示。数码管的显示原理是通过不同的电信号按照一定的逻辑在数码管内部的小灯泡上点亮不同的线段,从而形成所需的数字、字母或符号。要实现数码管的显示控制,需要使用微控制器或其他数字电路实现对数码管各个位的控制,在采集到数据后将其转换为可显示的信息,并将其显示在相应的数码管上。下面是通过DS18B20 温度传感器读取温度再通过数码管显示温度: #include <reg51.h> #define uchar unsigned char #define uint unsigned int sbit DQ = P1^0; // DS18B20 数字温度传感器数据线连接到 P1.0 引脚 sbit DIO = P2^0; // 数码管数据总线 DIO 连接到 P2.0 引脚 sbit RCLK = P2^1; // 数码管存储总线 RCLK 连接到 P2.1 引脚 sbit SRCLK = P2^2; // 数码管移位总线 SRCLK 连接到 P2.2 引脚 // DS18B20 数据传输函数 void DS18B20_WriteByte(uchar dat); uchar DS18B20_ReadByte(); void DS18B20_Start(); void DS18B20_End(); void DS18B20_Delay(uint i); // 初始化函数 void init(); // 串口初始化函数 void uart_init(); // 串口发送函数 void send_string(char *s); // 数码管显示函数 void display(uchar num); void main() { uchar temp_h, temp_l; uint temp; init(); uart_init(); // 串口初始化 while(1) { DS18B20_Start(); // 启动传输 DS18B20_WriteByte(0xCC); // 忽略 ROM 指令 DS18B20_WriteByte(0x44); // 发送温度转换指令 DS18B20_End(); // 结束传输 DS18B20_Start(); // 启动传输 DS18B20_WriteByte(0xCC); // 忽略 ROM 指令 DS18B20_WriteByte(0xBE); // 发送读取指令 // 读取温度值 temp_l = DS18B20_ReadByte(); // 读取低位温度值 temp_h = DS18B20_ReadByte(); // 读取高位温度值 // 计算温度值 temp = (temp_h << 8) + temp_l; temp = (float)temp / 16; // 数码管显示温度 display(temp); send_string("The temperature is: "); send_string(temp); send_string("\r\n"); DS18B20_End(); // 结束传输 } } // DS18B20 数据传输函数 void DS18B20_WriteByte(uchar dat) { uchar i; for (i = 0; i < 8; i++) { DQ = 0; // 写时序开始 DQ = dat & 0x01; // 写数据 DS18B20_Delay(1); // 延时 1us DQ = 1; // 写时序结束 dat >>= 1; } } uchar DS18B20_ReadByte() { uchar i, dat = 0; for (i = 0; i < 8; i++) { DQ = 0; // 读时序开始 DS18B20_Delay(1); // 延时 1us dat >>= 1; if(DQ) dat |= 0x80; DS18B20_Delay(5); // 延时 5us DQ = 1; // 读时序结束 } return dat; } void DS18B20_Start() { DQ = 1; DS18B20_Delay(1); DQ = 0; DS18B20_Delay(480); DQ = 1; DS18B20_Delay(60); } void DS18B20_End() { DQ = 1; DS18B20_Delay(1); } void DS18B20_Delay(uint i) { while(i--); } // 初始化函数 void init() { TMOD |= 0x20; // 定时器 1 工作在模式 2 TH1 = 0xfd; // 设置波特率,4800 bps TL1 = 0xfd; TR1 = 1; // 启动定时器 } // 串口初始化函数 void uart_init() { SCON = 0x50; // 8 位数据,可变波特率,允许接收 ES = 1; // 允许串口中断 EA = 1; // 允许总中断 } // 串口发送函数 void send_string(char *s) { while(*s) { SBUF = *s; while(!TI); TI = 0; s++; } } // 数码管显示函数 void display(uchar num) { uchar code table[] = { 0x3f, // '0' 0x06, // '1' 0x5b, // '2' 0x4f, // '3' 0x66, // '4' 0x6d, // '5' 0x7d, // '6' 0x07, // '7' 0x7f, // '8' 0x6f, // '9' }; uchar i; for (i = 0; i < 8; i++) { RCLK = 0; DS = table[num % 10]; // 取出个位数码 num /= 10; // 取下一位数 SRCLK = 1; SRCLK = 0; } RCLK = 1; RCLK = 0; }
-
一、项目背景随着粮食质量要求的提高和储存方式的改变,对于粮仓环境的监测和控制也愈发重要。在过去的传统管理中,通风、防潮等操作需要定期人工进行,精度和效率都较低。而利用嵌入式技术和智能控制算法进行监测和控制,不仅能够实时掌握环境变化,还可以快速做出响应。本项目选择STM32F103RCT6作为主控芯片,采用DHT11温湿度传感器和MQ9可燃气体检测模块进行数据采集,在本地利用显示屏实时显示出来。WiFi模块则用于与手机端实现数据通信和远程控制,方便用户随时了解粮仓环境状况并进行相应的操作。同时,通过连接继电器控制通风风扇和蜂鸣器报警,实现了智能化的温湿度检测和可燃气体浓度检测。 二、硬件选型【1】主控芯片:STM32F103RCT6,这款芯片具有较高性能、低功耗等特点。【2】温湿度传感器:DHT11,DHT11是一种数字温湿度传感器,价格便宜。【3】可燃气体检测模块:MQ9模块,MQ9模块对多种可燃气体具有敏感性,可以精确检测可燃气体浓度。【4】通风风扇:选择直流电机作为通风风扇,使用继电器进行控制。【5】WiFi模块:ESP8266,ESP8266是一种低成本的高性能WiFi模块,支持TCP/UDP协议。【6】显示屏:采用7针引脚的OLED显示屏,SPI接口,分辨率128x64,用于显示当前温度、湿度、可燃气体浓度。三、设计思路【1】硬件层通过STM32F103RCT6控制DHT11和MQ9等模块进行数据采集。在采集到温湿度和可燃气体浓度数据之后,对其进行处理,并判断是否超过了设定的阈值范围。如果超过了阈值,就控制继电器打开风扇,并通过蜂鸣器声音报警。ESP8266 WiFi模块用于与手机端进行通信。ESP8266被配置成AP+TCP服务器模式,通过向服务器发送指令,实现远程控制风扇及设置相应阈值等操作,并能实时接收粮仓环境状况信息。【2】软件层STM32的控制程序使用C语言编写,采用keil软件进行整体项目开发,对外设进行控制并实现数据采集和智能控制。主要分为采集数据、处理数据、数据显示、控制继电器和蜂鸣器等功能模块。手机APP采用Qt框架开发,实现对应数据界面显示和逻辑操作,能够实时显示和控制粮仓内部的温湿度和可燃气体浓度,并能够对风扇进行控制。同时,APP界面提供了设置选项,允许用户设置报警阈值参数。四、代码设计【1】DHT11采集温湿度DHT11是一种数字温湿度传感器,能够通过单总线接口输出当前环境下的温度和相对湿度。它由测量模块及处理电路组成,具有体积小、成本低、响应时间快等特点,被广泛应用于各种环境监测和自动控制系统中。下面代码是通过STM32F103RCT6采集DHT11温湿度数据通过串口打印输出(使用HAL库):#include "main.h" #include "dht11.h" UART_HandleTypeDef huart1; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_USART1_UART_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); char temp[20]; char humi[20]; while (1) { DHT11_Read_Data(temp, humi); // 读取DHT11数据 printf("Temperature: %s C, Humidity: %s %%\r\n", temp, humi); // 打印温湿度数据 HAL_Delay(2000); // 延时2秒 } } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct; RCC_ClkInitTypeDef RCC_ClkInitStruct; RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState = RCC_HSI_OFF; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } static void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } } void Error_Handler(void) { __disable_irq(); while (1) { } } static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); /*Configure GPIO pin : PC13 */ GPIO_InitStruct.Pin = GPIO_PIN_13; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); } void HAL_UART_MspInit(UART_HandleTypeDef *uartHandle) { GPIO_InitTypeDef GPIO_InitStruct; if (uartHandle->Instance == USART1) { /* Peripheral clock enable */ __HAL_RCC_USART1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /**USART1 GPIO Configuration PA9 ------> USART1_TX PA10 ------> USART1_RX */ GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } } void HAL_UART_MspDeInit(UART_HandleTypeDef *uartHandle) { if (uartHandle->Instance == USART1) { /* Peripheral clock disable */ __HAL_RCC_USART1_CLK_DISABLE(); /**USART1 GPIO Configuration PA9 ------> USART1_TX PA10 ------> USART1_RX */ HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9 | GPIO_PIN_10); } }s上面代码里,使用了DHT11读取函数DHT11_Read_Data(),该函数返回温度值和湿度值,并将其转换为字符串形式。通过串口与电脑连接后,可以使用串口调试软件来查看STM32采集到的温湿度数据。【2】采集MQ9有毒气气体MQ9是一种可燃气体传感器,可以检测空气中的多种可燃气体,例如甲烷、丙烷、丁烷等。它的工作原理是通过加热敏感元件,使其产生一个电阻变化,从而实现检测目标气体的浓度。MQ9具有高灵敏度、快速响应和稳定性好等特点,广泛应用于火灾报警、室内空气质量监测、工业生产等领域。需要注意的是,MQ9只能检测可燃气体,不能检测其他气体,如二氧化碳、氧气等。下面代码是通过STM32F103RCT6采集MQ9可燃气体转为浓度通过串口打印(使用HAL库):#include "main.h" UART_HandleTypeDef huart1; ADC_HandleTypeDef hadc1; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_USART1_UART_Init(void); static void MX_ADC1_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); MX_ADC1_Init(); uint16_t adc_value; float voltage; float concentration; char buffer[20]; while (1) { HAL_ADC_Start(&hadc1); // 启动ADC转换 if (HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK) // 等待转换完成 { adc_value = HAL_ADC_GetValue(&hadc1); // 获取原始ADC值 voltage = (float)adc_value * 3.3f / 4096.0f; // 转换为电压值 concentration = (float)(2.5f - voltage) / 0.2f; // 根据MQ9传感器曲线计算浓度值 sprintf(buffer, "Concentration: %.2f %%\r\n", concentration); // 将浓度值转换为字符串 printf("%s", buffer); // 通过串口打印浓度值 } HAL_ADC_Stop(&hadc1); // 停止ADC转换 HAL_Delay(2000); // 延时2秒 } } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct; RCC_ClkInitTypeDef RCC_ClkInitStruct; RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState = RCC_HSI_OFF; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } static void MX_ADC1_Init(void) { ADC_ChannelConfTypeDef sConfig; hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = DISABLE; hadc1.Init.ContinuousConvMode = DISABLE; hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 1; if (HAL_ADC_Init(&hadc1) != HAL_OK) { Error_Handler(); } sConfig.Channel = ADC_CHANNEL_5; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES5; if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { Error_Handler(); } } static void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } } void Error_Handler(void) { __disable_irq(); while (1) { } } static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); /*Configure GPIO pin : PC13 */ GPIO_InitStruct.Pin = GPIO_PIN_13; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); } void HAL_UART_MspInit(UART_HandleTypeDef *uartHandle) { GPIO_InitTypeDef GPIO_InitStruct; if (uartHandle->Instance == USART1) { /* Peripheral clock enable */ __HAL_RCC_USART1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /**USART1 GPIO Configuration PA9 ------> USART1_TX PA10 ------> USART1_RX */ GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } } void HAL_UART_MspDeInit(UART_HandleTypeDef *uartHandle) { if (uartHandle->Instance == USART1) { /* Peripheral clock disable */ __HAL_RCC_USART1_CLK_DISABLE(); /**USART1 GPIO Configuration PA9 ------> USART1_TX PA10 ------> USART1_RX */ HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9 | GPIO_PIN_10); } }上面代码里,通过ADC采集MQ9可燃气体浓度。由于MQ9传感器的输出信号与浓度值之间不是线性关系,需要根据其曲线进行计算,将电压转换为浓度值。在这里,采用了简单的公式:Concentration=(2.5−V)/0.2其中V为MQ9传感器输出的电压值,Concentration为可燃气体浓度。在主函数里,先调用MX_ADC1_Init()函数中初始化ADC,将输入通道设置为PA5(也就是ADC_CHANNEL_5)。
-
一、项目介绍本项目实现了一个基于GTK和libvlc的视频播放器。使用GTK创建GUI界面,使用libvlc播放视频。用户可以通过选择视频文件,然后启动播放器来观看视频。二、VLC介绍VLC是一款自由、开放源代码的跨平台媒体播放器,支持播放几乎所有常见的音频和视频格式。最初于2001年由法国学生开发,现在已经成为了一个非常受欢迎的媒体播放器,在Windows、macOS、Linux等多个操作系统上都可用。libvlc是VLC media player使用的核心库之一。提供了一组应用程序接口(API),可以让开发人员轻松地将类似于VLC的媒体播放功能嵌入到他们自己的应用程序中。libvlc可以与多种编程语言和框架(如C、C++、Python、Java、.NET等)集成,因此被广泛应用于各种媒体相关的项目中。VLC是一个独立的媒体播放器软件,而libvlc是VLC media player使用的核心库之一,可以方便地嵌入到其他应用程序中,以实现类似于VLC的媒体播放功能。VLC软件下载(3.X) :cid:link_1libVLC最新4.0官网: cid:link_0三、GTK介绍文档学习地址:cid:link_2官网地址:cid:link_3GTK是一种开源的跨平台图形用户界面(GUI)工具包。最初是为GNU计划设计的,现在被广泛地用于Linux和其他Unix-like操作系统的各种应用程序中。GTK提供了一组用于创建图形用户界面的功能库,包括窗口、按钮、标签、文本输入框等控件,以及用于渲染这些控件的绘图引擎。GTK还支持国际化和主题定制,可以让开发者创建符合用户期望和风格的应用程序界面。在 Ubuntu 中安装最新的 GTK 依赖库的命令: sudo apt-get update sudo apt-get install libgtk-3-dev这个命令将会安装 GTK3 库的开发文件和依赖库。如果需要在程序中使用 GTK2 库,则需要安装 libgtk2.0-dev 包。除此之外,还可以安装一些其他的GTK扩展包,如GStreamer、WebKit 等。如果要编译一个基于 GTK 的程序,可以使用 gcc 或 g++ 来进行编译,同时需要链接 GTK 库。假设源代码文件为 example.c,编译命令可以如下: gcc -o example example.c `pkg-config --cflags --libs gtk+-3.0`其中,pkg-config 是一个用来管理编译时的依赖库的工具。--cflags 和 --libs 分别是输出 GTK 库的头文件路径和链接库路径,包含了命令行返回的路径。pkg-config --cflags --libs gtk+-3.0` 就是获取编译 GTK 程序时需要的参数。四、设计思路通过GTK调用libvlc来实现视频播放器,需要执行以下步骤:(1)下载和安装libvlc和相关依赖库,可以使用apt-get或者源码编译的方式安装。 sudo apt-get update sudo apt-get install libvlc-dev libgtk-3-devlibvlc-dev 是 libvlc 的开发库,包括头文件和链接库;libgtk-3-dev 是 GTK 库的开发库,也包括头文件和链接库。通过安装这两个开发库,就可以在 Ubuntu 下进行开发基于 GTK 和 libvlc 的视频播放器了。(2)在GTK程序中引入libvlc的头文件和库文件,以及GTK的头文件和库文件: #include <gtk/gtk.h> #include <vlc/vlc.h>(3)创建GTK窗口和控件: GtkWidget *window; GtkWidget *video_widget; GtkBuilder *builder;GtkBuilder用于动态加载UI文件,可以通过glade工具创建UI文件,然后在程序中使用GtkBuilder加载UI文件。video_widget是用于显示视频的GTK控件。(4)初始化libvlc,并创建libvlc_media_player对象和libvlc_media对象: libvlc_instance_t *vlc_instance; libvlc_media_t *media; libvlc_media_player_t *media_player; // ... vlc_instance = libvlc_new(0, NULL); media = libvlc_media_new_path(vlc_instance, "/path/to/video.mp4"); media_player = libvlc_media_player_new_from_media(media); libvlc_media_player_set_xwindow(media_player, GDK_WINDOW_XID(gtk_widget_get_window(video_widget))); libvlc_media_player_play(media_player); // ...第三行代码使用libvlc_media_new_path()函数创建一个libvlc_media对象,用于表示要播放的视频文件。第四行代码使用libvlc_media_player_new_from_media()函数创建一个libvlc_media_player对象,用于播放视频。第五行代码使用libvlc_media_player_set_xwindow()函数将video_widget的XID绑定到libvlc_media_player对象中,从而能够将视频显示在video_widget中。第六行代码使用libvlc_media_player_play()函数开始播放视频。(5)在GTK窗口中添加视频控件,并启动GTK主循环: builder = gtk_builder_new_from_file("ui.glade"); window = GTK_WIDGET(gtk_builder_get_object(builder, "main_window")); video_widget = GTK_WIDGET(gtk_builder_get_object(builder, "video_widget")); // ... gtk_container_add(GTK_CONTAINER(window), video_widget); gtk_widget_show_all(window); gtk_main();第一行代码通过GtkBuilder加载UI文件,并获取main_window和video_widget对象。第三行代码将video_widget添加到window中,第四行代码显示窗口和控件,最后一行代码启动GTK主循环。五、完整的设计代码【1】main.c代码 #include <gtk/gtk.h> #include <vlc/vlc.h> int main(int argc, char *argv[]) { GtkWidget *window; GtkWidget *video_widget; GtkBuilder *builder; libvlc_instance_t *vlc_instance; libvlc_media_t *media; libvlc_media_player_t *media_player; gtk_init(&argc, &argv); builder = gtk_builder_new_from_file("ui.glade"); window = GTK_WIDGET(gtk_builder_get_object(builder, "main_window")); video_widget = GTK_WIDGET(gtk_builder_get_object(builder, "video_widget")); vlc_instance = libvlc_new(0, NULL); media = libvlc_media_new_path(vlc_instance, "/path/to/video.mp4"); media_player = libvlc_media_player_new_from_media(media); libvlc_media_player_set_xwindow(media_player, GDK_WINDOW_XID(gtk_widget_get_window(video_widget))); libvlc_media_player_play(media_player); gtk_container_add(GTK_CONTAINER(window), video_widget); gtk_widget_show_all(window); gtk_main(); libvlc_media_player_stop(media_player); libvlc_media_player_release(media_player); libvlc_release(vlc_instance); return 0; }【2】ui.glade代码使用glade工具创建UI文件:<?xml version="1.0" encoding="UTF-8"?><!-- Generated with glade 3.22.1 --><interface> <requires lib="gtk+" version="3.20"/> <object class="GtkWindow" id="main_window"> <property name="can_focus">False</property> <child> <object class="GtkDrawingArea" id="video_widget"> <property name="can_focus">False</property> </object> </child> </object></interface>
-
希望支持Risc-v单片机的嵌入式开发IDE环境。国产化是个趋势,在这个大环境中risc-v芯片不错,可惜跟arm比起来生态太弱。比如国产risc-v界碎片化忒严重,没有一款通用好用的IDE。各个芯片厂家各显神通,重复造轮子,然而基于eclipse 的IDE体积大且不太好用。CodeArts编码体验不错,希望未来能也拿来作为单片机IDE的一种来用。至少考虑下这一可能性和选项,这样也更利于生态的扩大。另外还有就是希望别跟自家的华为云绑定太紧了,虽然这是特色。但是其他家不用这云呢或者用不到呢,能以插件形式提供最好。更开放些更好。
-
基于51单片机的智能营养秤系统设计与实现cid:link_2随着人们生活水平和健康意识的提高,越来越多的人开始注重自己的饮食健康。在此背景下,智能营养秤系统应运而生,成为了一种非常实用的工具。本项目基于51单片机设计和实现一种智能营养秤系统,通过该系统可准确地测量食物的重量并计算其热量、蛋白质、脂肪、碳水化合物等营养成分含量。当前系统采用了STC89C52单片机作为主控芯片,预置了多种食材的营养成分数据。用户只需要使用矩阵键盘输入食材编号,将需要称重的食材放置在重力传感器上进行依次称重,系统就可以自动计算出所有食材的各类营养含量总值,并通过液晶屏显示出来。同时,系统根据预设的营养指标,对不达标或超标的食材进行对应的声光提示,提醒用户注意饮食健康。当前系统还配备了无线WIFI模块,可以将当前营养数据上传到手机端实时显示,并给出营养建议。这使得用户可以随时1了解自己的饮食情况,及时进行调整,从而达到更好的健康效果。本项目的设计和实现是为了满足人们对于饮食健康的需求,帮助人们更好地控制自己的饮食,达到健康瘦身的目的。同时,由于采用了51单片机的设计方案,具有成本低、易于制作、易于维护等优点,具有广泛的应用前景。基于STM32的铁路自动围栏系统设计cid:link_3随着城市规模的不断扩大和交通运输方式的日益发展,铁路与公路的交叉口已经成为常见的场景。然而,这些交叉口往往存在一定的安全隐患,因为有时不易发现列车行进的情况,导致公路上的车辆或行人可能会无意中闯入铁路区域,从而引发重大交通事故。为了解决这个问题,当前开发了一款基于STM32的铁路自动围栏系统。该系统采用了STM32F103RCT6作为主控芯片,并使用步进电机来控制铁路围栏的开启和闭合。同时,系统还配备了红外感应器,以便能够及时监测到列车的通过情况。当系统监测到有列车即将通过铁路交叉口时,公路信号灯会立刻变为红灯,蜂鸣器也会发出警报声音,以提醒行人和车辆注意安全。同时,铁路两侧的围栏也会自动关闭,在列车通过后再次打开。这样,就能有效地防止公路车辆和行人误闯铁路区域,保障了路人的安全。基于STM32的无人售货机系统设计cid:link_4随着科技的发展和生活水平的提高,人们对于购物体验的要求越来越高。传统的商场、超市购物方式已经无法满足消费者的需求,因此无人售货机应运而生。本文针对现有售货机存在的缺陷,设计了一款基于STM32的无人售货机系统。该系统采用STM32作为主控芯片,使用液晶屏显示各种商品库存与售价,用户按下对应按键选择购买指定商品,在矩阵键盘输入账号密码付款。若付款成功,对应电机旋转一定角度使商品出库,同时修改库存;若余额不足,则进行声光提示。手机端还可查看消费流水、商品库存情况,并进行补货和充值操作。QML加载模块 WebView 与C++代码通信控制WebView模块的隐藏与显示cid:link_5在Qt Quick中,WebView模块可以用来显示网页内容。它提供了一个基于WebKit引擎的web浏览器组件,可以显示本地html文件或者加载外部网页。基于STM32的智能饮水机系统设计cid:link_0随着智能化的迅速发展,人们对于生活中的各类设备也越来越有智能化的需求,其中智能饮水机是一种比较常见的设备。智能饮水机不仅可以提供饮用水,还可以通过智能化的技术满足人们对于水质、水温、出水量等方面的需求。因此,当前设计了一种基于STM32的智能饮水机系统,以满足人们对智能化饮水机的需求。CC2530采用ESP8266与手机APP通信cid:link_6项目实现通过CC2530控制ESP8266将其配置成AP+TCP服务器模式,并通过手机APP连接到TCP服务器并完成数据传输。ESP8266将作为一个热点(AP)来工作,其WiFi模块被配置为建立一个TCP服务器并监听端口号。CC2530将使用其串口与ESP8266进行通信,并通过AT指令控制ESP8266的WiFi模块设置和数据传输。QDir拼接路径解决各种\//斜杠问题cid:link_1一般在项目中经常需要组合路径,与其他程序进行相互调用传递消息通信。 经常可能因为多加斜杠、少加斜杠等问题导致很多问题。 为了解决这些问题,我们可以使用QDir来完成路径的拼接,不要直接拼接字符串。基于CC2530设计智慧农业控制系统cid:link_7智慧农业是近年来发展迅速的领域,其目的是利用先进的传感技术、物联网技术和云计算技术等,实现自动化、智能化的农业生产管理,并提高农业生产效率和质量。本文基于CC2530设计了一种智慧农业控制系统,采用DHT11模块、BH1750模块和土壤湿度传感器等传感器,通过串口协议将采集的数据上传给上位机显示。基于Linux设计的倒车雷达系统cid:link_8随着社会的不断发展,人们对于汽车的安全性要求越来越高,而倒车雷达系统就是为了增强汽车驾驶者的安全性而被广泛使用。在这种情况下,我们开发了一个基于Linux设计的倒车雷达系统,该系统可以采用迅为4412主控板,运行Linux3.5内核,使用USB摄像头、TFT真彩显示屏、超声波测距模块和蜂鸣器等硬件。STM32采集传感器数据通过冒泡排序取稳定值cid:link_9在物联网、单片机开发中,经常需要采集各种传感器的数据。比如:温度、湿度、MQ2、MQ3、MQ4等等传感器数据。这些数据采集过程中可能有波动,偶尔不稳定,为了得到稳定的值,我们可以对数据多次采集,进行排序,去掉最大和最小的值,然后取平均值返回。C语言实现单链表-增删改查cid:link_10链表是由一连串节点组成的数据结构,每个节点包含一个数据值和一个指向下一个节点的指针。链表可以在头部和尾部插入和删除节点,因此可以在任何地方插入和删除节点,从而使其变得灵活和易于实现。链表通常用于实现有序集合,例如队列和双向链表。链表的优点是可以快速随机访问节点,而缺点是插入和删除操作相对慢一些,因为需要移动节点。此外,链表的长度通常受限于内存空间,因此当链表变得很长时,可能需要通过分页或链表分段等方式来管理其内存。STC89C52+DHT20设计的环境温湿度检测仪cid:link_11本项目基于STC89C52单片机和DHT20温湿度传感器,实现了一款环境温湿度检测仪。通过传感器采集环境的温度和湿度数据,利用IIC接口的OLED显示屏显示出来,便于用户实时监测环境温湿度状态。在现代社会,人们对环境温湿度的要求越来越高。无论是工作场所还是居住环境,都需要维持一个舒适的温湿度状态,以保证身体的健康和工作效率的提高。随着科技的不断进步和物联网技术的广泛应用,环境温湿度检测仪被广泛运用于各种领域,如制造业、医疗、农业等等,成为了一种重要的环境检测设备。而本项目所涉及的STC89C52单片机和DHT20温湿度传感器作为传统的嵌入式开发技术,在实现物联网设备方面有着广泛的应用前景。通过本项目的学习和实践,可以深入了解传感器技术的原理和应用,并掌握基于单片机的嵌入式开发技术,为实现更多物联网设备的开发和应用打下基础。STC89C52+AT24C02实现设备开机次数记录cid:link_12在一些设备的使用过程中,需要对设备的使用次数进行统计和记录。这可以用于评估设备的实际使用寿命、确定维护周期、预测故障风险等方面,对于提高设备的稳定性和可靠性具有重要意义。当前项目采用STC89C52作为主控芯片,AT24C02作为存储芯片,实现了设备的开机次数记录功能。每次设备上电启动时,程序会从AT24C02中读取之前的记录值并加1,然后再将新的记录值写入AT24C02中,从而完成一次开机次数的记录。通过这种方式,可以实时、准确地记录设备的使用次数,并且不受断电影响,数据可靠性高。基于STM32设计的炉温温度检测仪cid:link_13炉温检测在现代工业生产中十分重要,因为炉温过高或过低都会对产品质量产生影响,甚至影响工厂的正常运作。因此,设计一款能够精准测量炉温并显示结果的检测仪器具有很大的实用价值。 本项目采用了STM32F103C8T6作为主控芯片,该芯片拥有丰富的外设和性能较好的计算能力,能够满足该项目对计算和控制的需求。同时,铂电阻PT100作为测温传感器,能够提供更加精准的温度测量结果。
-
炉温检测在现代工业生产中十分重要,因为炉温过高或过低都会对产品质量产生影响,甚至影响工厂的正常运作。因此,设计一款能够精准测量炉温并显示结果的检测仪器具有很大的实用价值。 本项目采用了STM32F103C8T6作为主控芯片,该芯片拥有丰富的外设和性能较好的计算能力,能够满足该项目对计算和控制的需求。同时,铂电阻PT100作为测温传感器,能够提供更加精准的温度测量结果。一、项目背景随着工业生产的发展,炉温检测在现代化工、钢铁、电子、玻璃等行业中变得越来越重要。对于这些行业,稳定的生产环境和品质稳定的产品是必须的,而炉温是影响产品品质的重要因素之一。如果炉温过高或过低,都有可能导致产品结构改变、硬度变化、强度下降等质量问题,使得产品不能达到预期的性能指标。此外,炉温不仅会影响产品质量,还会影响设备的使用寿命和工作效率,有时甚至会对整个工厂的正常生产造成影响。为了防止这些问题的发生,现代化工、钢铁、电子、玻璃等行业需要精准测量炉温并实时地监测炉温变化情况。而本项目即是为了满足这些需求而设计的。采用STM32F103C8T6作为主控芯片,它是一款基于ARM Cortex-M3内核的微控制器,具有丰富的外设和良好的计算能力,并且易于控制和集成到系统中。同时,铂电阻PT100是一种高精度、稳定性好、线性度高的温度传感器,能够提供更加准确的温度测量结果。采用0.96寸IIC接口的OLED屏幕进行显示,操作简便、节省成本,并且具有较好的兼容性和可移植性。二、设计思路【1】硬件设计主控芯片采用STM32F103C8T6,其内置有多种外设,可满足该项目的需求。铂电阻PT100作为测温传感器,能够提供更加准确的温度测量结果。0.96寸IIC接口的OLED显示屏幕是本项目的显示工具,能够直观地显示测量结果。【2】软件设计软件设计分为数据采集、数据处理和数据显示三个部分。采用STM32的ADC进行数据采集,通过PT100将温度信号转换为电阻信号,再通过AD转换器转换成数字信号进行处理。在数据处理中,对ADC采样值进行数据校准、滤波处理和算法计算,得到准确的温度值。最后,通过IIC总线协议将温度值发送给OLED屏幕进行显示,实现实时显示检测结果的功能。三、代码实现【1】OLED显示屏代码以下是基于STM32F103C8T6主控芯片,通过IIC接口控制0.96寸OLED显示屏显示数字的代码: #include "stm32f10x.h" #include "i2c.h" #define OLED_ADDRESS 0x78 // OLED IIC地址 void oled_init(void) { OLED_Write_Command(0xAE); // 关闭显示 OLED_Write_Command(0xD5); // 设置时钟分频因子 OLED_Write_Command(0x80); // 重要参数,必须设置,不然屏幕无法上电 OLED_Write_Command(0xA8); // 设置驱动路数 OLED_Write_Command(0x3F); // 默认值 OLED_Write_Command(0xD3); // 设置显示偏移 OLED_Write_Command(0x00); // 默认值 OLED_Write_Command(0x40); // 设置起始行 OLED_Write_Command(0x8D); // 电荷泵设置 OLED_Write_Command(0x14); // 开启电荷泵 OLED_Write_Command(0x20); // 设置内存地址模式 OLED_Write_Command(0x00); // 水平模式 OLED_Write_Command(0xA1); // 段重新映射设置 OLED_Write_Command(0xC0); // 设置COM扫描方向 OLED_Write_Command(0xDA); // 设置COM引脚硬件配置 OLED_Write_Command(0x12); // 默认值 OLED_Write_Command(0x81); // 对比度设置 OLED_Write_Command(0xCF); // 默认值 OLED_Write_Command(0xd9); // 设置预充电周期 OLED_Write_Command(0xF1); // 默认值 OLED_Write_Command(0xDB); // 设置VCOMH OLED_Write_Command(0x40); // 默认值 OLED_Write_Command(0xA4); // 关闭全屏点亮 OLED_Write_Command(0xA6); // 设置显示方式 OLED_Write_Command(0xAF); // 开启屏幕显示 } void OLED_Write_Command(uint8_t cmd) { // 写命令 I2C1_Start(); I2C1_SendByte(OLED_ADDRESS); I2C1_SendByte(0x00); I2C1_SendByte(cmd); I2C1_Stop(); } void OLED_Write_Data(uint8_t data) { // 写数据 I2C1_Start(); I2C1_SendByte(OLED_ADDRESS); I2C1_SendByte(0x40); I2C1_SendByte(data); I2C1_Stop(); } void OLED_Set_Pos(uint8_t x, uint8_t y) { // 设置光标位置 OLED_Write_Command(0xb0+y); OLED_Write_Command(((x&0xf0)>>4)|0x10); OLED_Write_Command(x&0x0f); } void OLED_Show_Number(uint8_t x, uint8_t y, uint32_t num) { // 在指定位置显示数字 OLED_Set_Pos(x, y); while (num) { uint8_t temp = num % 10; OLED_Write_Data(temp + '0'); num /= 10; } } int main(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); I2C1_Init(); oled_init(); OLED_Show_Number(0, 0, 12345); //在第1行第1列显示数字12345 while (1) { } }首先,通过oled_init()函数初始化OLED屏幕,在函数中依次写入了一系列命令,来设置OLED的各种参数,例如驱动路数、扫描方向、预充电周期、对比度等。接着,在OLED_Show_Number()函数中,调用了OLED_Set_Pos()函数来设置数字显示的位置,然后通过循环取余数的方法将数字逐位分离,再将其转换为字符型并通过OLED_Write_Data()函数输出到OLED屏幕上,最终实现在屏幕上显示指定数字的功能。【2】温度测量代码以下是基于STM32F103C8T6主控芯片,通过IIC接口控制0.96寸OLED显示屏显示温度,并通过串口打印温度的代码: #include "stm32f10x.h" #include "i2c.h" #include "usart.h" #define OLED_ADDRESS 0x78 // OLED IIC地址 // PT100温度转换函数 float RTD2Temperature(float R) { float temperature = 0; float RTD_A = 3.9083e-003f; float RTD_B = -5.775e-007f; temperature = (-RTD_A + sqrtf(RTD_A * RTD_A - 4 * RTD_B * (1 - R / 100))) / (2 * RTD_B); return temperature; } void oled_init(void) { OLED_Write_Command(0xAE); // 关闭显示 OLED_Write_Command(0xD5); // 设置时钟分频因子 OLED_Write_Command(0x80); // 重要参数,必须设置,不然屏幕无法上电 OLED_Write_Command(0xA8); // 设置驱动路数 OLED_Write_Command(0x3F); // 默认值 OLED_Write_Command(0xD3); // 设置显示偏移 OLED_Write_Command(0x00); // 默认值 OLED_Write_Command(0x40); // 设置起始行 OLED_Write_Command(0x8D); // 电荷泵设置 OLED_Write_Command(0x14); // 开启电荷泵 OLED_Write_Command(0x20); // 设置内存地址模式 OLED_Write_Command(0x00); // 水平模式 OLED_Write_Command(0xA1); // 段重新映射设置 OLED_Write_Command(0xC0); // 设置COM扫描方向 OLED_Write_Command(0xDA); // 设置COM引脚硬件配置 OLED_Write_Command(0x12); // 默认值 OLED_Write_Command(0x81); // 对比度设置 OLED_Write_Command(0xCF); // 默认值 OLED_Write_Command(0xd9); // 设置预充电周期 OLED_Write_Command(0xF1); // 默认值 OLED_Write_Command(0xDB); // 设置VCOMH OLED_Write_Command(0x40); // 默认值 OLED_Write_Command(0xA4); // 关闭全屏点亮 OLED_Write_Command(0xA6); // 设置显示方式 OLED_Write_Command(0xAF); // 开启屏幕显示 } void OLED_Write_Command(uint8_t cmd) { // 写命令 I2C1_Start(); I2C1_SendByte(OLED_ADDRESS); I2C1_SendByte(0x00); I2C1_SendByte(cmd); I2C1_Stop(); } void OLED_Write_Data(uint8_t data) { // 写数据 I2C1_Start(); I2C1_SendByte(OLED_ADDRESS); I2C1_SendByte(0x40); I2C1_SendByte(data); I2C1_Stop(); } void OLED_Set_Pos(uint8_t x, uint8_t y) { // 设置光标位置 OLED_Write_Command(0xb0+y); OLED_Write_Command(((x&0xf0)>>4)|0x10); OLED_Write_Command(x&0x0f); } void OLED_Show_Temperature(uint8_t x, uint8_t y, float temperature) { // 在指定位置显示温度 OLED_Set_Pos(x, y); int temp = (int)(temperature * 10); for (int i = 0; i < 5; i++) { if (i == 2) { OLED_Write_Data('.'); } else { OLED_Write_Data(temp % 10 + '0'); temp /= 10; } } OLED_Write_Data('C'); } int main(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); I2C1_Init(); oled_init(); USART1_Init(); while (1) { float resistance = 100; // 铂电阻的电阻值 float temperature = RTD2Temperature(resistance); // 算出温度值 // OLED显示温度 OLED_Show_Temperature(0, 0, temperature); // 串口输出温度 char str[32]; sprintf(str, "Temperature: %.1f C\r\n", temperature); USART1_SendString(str); delay_ms(1000); // 延时1s } }首先,利用RTD2Temperature()函数将铂电阻的电阻值转换为温度值。接着,在OLED_Show_Temperature()函数中,调用了OLED_Set_Pos()函数来设置温度显示的位置,并将温度值逐位分离,通过OLED_Write_Data()函数输出到OLED屏幕上,最终实现在屏幕上显示测量的温度的功能。同时,也通过串口输出温度值。在主函数main()中,不断循环读取铂电阻的电阻值,并通过RTD2Temperature()函数转换为温度值。然后,调用OLED_Show_Temperature()函数将温度显示在OLED屏幕上,并调用USART1_SendString()函数通过串口输出温度值。最后,通过delay_ms()函数延时1秒,等待下一次测量。
上滑加载中
推荐直播
-
空中宣讲会 2025年华为软件精英挑战赛
2025/03/10 周一 18:00-19:00
宸睿 华为云存储技术专家、ACM-ICPC WorldFinal经验 晖哥
2025华为软挑赛空中宣讲会重磅来袭!完整赛程首曝+命题天团硬核拆题+三轮幸运抽奖赢参赛助力礼包,与全国优秀高校开发者同台竞技,直通顶尖赛事起跑线!
即将直播
热门标签