一、成品简介1.实物成品二、项目简介1.功能详解基于单片机的智能电子秤系统语音功能如下主控模块STM32F103C8T6单片机作为主控芯片负责数据处理、外设控制以及交互称重功能采用HX711高精度24位AD模块采集电阻应变式传感器信号量程0~10kg称重精度可达0.1g以内温度补偿通过DS18B20数字温度传感器实时采集环境温度对重量数据进行温度漂移补偿减小环境变化带来的测量误差显示功能0.96寸IIC OLED屏幕清晰显示重量、单价、总价、当前温度、电池电量等信息按键控制独立轻触按键实现去皮、单价增减、总价计算、模式切换等操作语音播报TTS语音模块离线播报称重结果、价格及系统提示支持语音控制指令识别如去皮、调价等电源管理TP4056锂电池充电管理芯片支持恒流恒压充电配合ADC电量采集电路实时监测电池剩余电量保证便携使用。自动计价系统根据设定单价与测得重量自动计算总价并实时更新显示数据保存每次单价自动写入FLASH里面掉电重新启动之后数据还在。2.主要器件STM32F103C8T6单片机DS18B20温度传感器HX711电子秤模块ASR-01语音模块TP4056锂电池充电模块XL3608升压模块0.96OLED模块独立按键碳膜电阻3.原理图设计独立按键用于控制参数修改显示模块用于人机交互显示温度模块用于测量温度(DS18B20模块的单总线协议需要串联一个电阻常用10K或4.7K)使用两个1M欧姆的电阻用于将电池的电压分压因为电池的电压范围为3.7-4.2V远超单片机ADC可以采集的电压TP4056模块可以对锂电池充放电锂电池的电压经过XL3608这个DCDC模块放大之后变为5V可以供给单片机的5V端用于供电。可以在锂电池端加入二极管防止电源反接出现意外有条件可以添加TVS防止浪涌进行浪涌保护。4.PCB设计三、主要模块1.HX711称重模块24位高精度ADC内部集成128倍可编程放大器可直接处理传感器毫伏级信号双速率采样10Hz/80Hz差分输入抗干扰强两线串口通信接口简单功耗极低。在使用过程中需注意对采集到的数据进行滤波重量可能会出现一直浮动影响体验可以对测出重量使用死区滤波函数和一阶低通滤波函数详细滤波函数见5.1.4函数滤波代码。2.DS18B20温度传感器模块单总线通信仅需一根数据线测温范围-55~125℃精度±0.5℃必须接4.7k~10kΩ上拉电阻否则会出现通信失败另外注意正负极。3.TP4056锂电池充电模块专为单节3.7V锂电池设计的线性充电芯片采用恒流/恒压CC/CV三段式充电截至电压4.2V充电电流通过电阻可调最大1A内置热反馈、防倒充等保护带充电状态指示LED。仅适用于单节锂电池严禁用于多节串联或其他类型电池4.语音模块ASR01模块无需联网所有语音识别在本地完成识别率高响应延迟低。可以通过UART串口与单片机通信自带3W功放可直接驱动扬声器同时支持中文界面编程对新手很友好。四、写作内容参考1.摘要对于传统电子秤存在的称重精度受温度影响大、便携性差、功能单一等缺点本文采用一种STM32F103C8T6单片机的智能电子秤系统。系统以STM32F103C8T6为主芯片使用HX711高精度AD模块采集重量信号通过DS18B20实现温度采集与数据补偿搭配0.96寸IICOLED屏显示重量、单价、总价、温度、电量利用TP4056实现锂电池充放电管理并集成按键设置、去皮、计价、语音播报等功能。经测试系统量程0–10kg精度可达0.5g以内具有价格低廉能耗低体积小等特点可满足商场、超市、家庭及小型工业等各方面的使用。本文详细阐述系统总体设计、硬件电路、软件算法、系统测试与结果分析得出了一个完整的方案。2.主要目录五、项目主要源码5.1.单片机源码5.1.1主程序#include bsp.h /* KEY1: 去皮 KEY2 单价 KEY2 单价-- KEY4 确定输出总价 */ //OLED SDA-PB7 SCL-PB6 //KEY1-4 PB12 PB13 PB14 PB15 //HX711 DATA-PA2 ADSK-PA1 /* UART */ uint8_t rx1; #define TX_SIZE 128 uint8_t txbuffer[TX_SIZE]; /* */ //extern uint32_t weight; extern float weight; char oled_buffer[32]{0}; extern uint8_t key_dly; uint16_t price30;//*10单价默认3.0 uint8_t weight_peeled_flag;// float weight_peeled_last;// uint32_t weight_now;// uint8_t sum_price_flag; float sum_price;// uint16_t adc_read; uint32_t adc_sum; uint8_t volt; uint32_t temper; uint8_t flag1; /* */ void uart_send(char* fmt, ...) ; void key_task(); void ttl_send(uint8_t state); /* */ void bsp_init() { HAL_UART_Receive_IT(huart1, rx1, 1); HAL_TIM_Base_Start_IT(htim1); HAL_Delay(10); OLED_Init(); HAL_Delay(10); OLED_NewFrame(); Get_Tare(); HAL_ADC_Start(hadc1); delay_init(); DS18B20_Init(); //初始化DS18B20 priceFlash_ReadByteFromLastPage(); } /* */ void loop() { Get_Weight(); key_task(); //OLED OLED_NewFrame(); sprintf((char *)oled_buffer,单价:%d.%d ,price/10,price%10); OLED_PrintString(0, 0, oled_buffer, font16x16, OLED_COLOR_NORMAL); sprintf((char *)oled_buffer,重量:%.1fg,weight-(float)weight_peeled_last); OLED_PrintString(0, 16,oled_buffer ,font16x16, OLED_COLOR_NORMAL); sprintf((char *)oled_buffer,皮重:%.1fg,weight_peeled_last); OLED_PrintString(0, 32,oled_buffer ,font16x16, OLED_COLOR_NORMAL); sprintf((char *)oled_buffer,总价:%.1f ,sum_price); OLED_PrintString(0, 48,oled_buffer ,font16x16, OLED_COLOR_NORMAL); adc_read0;adc_sum0; for(int i0;i15;i) { if (HAL_ADC_PollForConversion(hadc1, 100) HAL_OK) { adc_readHAL_ADC_GetValue(hadc1); } adc_sumadc_readadc_sum; } adc_readadc_sum/15.0; volt (((float)adc_read * 3.3 / 4095.0 * 2.0) - 3.0) / 1.2 * 100.0; volt filt_del2(volt, 2); sprintf((char *)oled_buffer,电量); OLED_PrintString(97, 0, oled_buffer, font16x16, OLED_COLOR_REVERSED); sprintf((char *)oled_buffer,%d%d,volt/10%10,volt%10); OLED_PrintString(112, 16, oled_buffer, font16x16, OLED_COLOR_NORMAL); temper DS18B20_Get_Temp(); //获取温度 sprintf((char *)oled_buffer,温度); OLED_PrintString(97, 32, oled_buffer, font16x16, OLED_COLOR_REVERSED); sprintf((char *)oled_buffer,%d,temper/10); OLED_PrintString(110, 48, oled_buffer, font16x16, OLED_COLOR_NORMAL); OLED_ShowFrame(); } void ttl_task(uint8_t dat) { switch(dat) { case 1: weight_peeled_flag1; break; case 2: priceprice5;if(price150)price150; break;//price case 3: priceprice-5;if(price240)price0; break;//price- case 4: sum_price_flag1;break; } } void key_task() { uint8_t key0; if(key_dly10) return; key_dly0; keykey_scan(); //KEY1: switch(key) { case 1: weight_peeled_flag1; ttl_send(1); break; case 2: priceprice5;if(price150)price150; ttl_send(2); Flash_WriteByteToLastPage(price);break;//price case 3: priceprice-5;if(price240)price0; ttl_send(3);Flash_WriteByteToLastPage(price);break;//price- case 4: sum_price_flag1;ttl_send(4);break; } if(sum_price_flag1) { if(flag11){sum_price(((float)weight-(float)weight_peeled_last)*price)/10.0;} else {sum_price(((float)weight)*price)/10.0;} sum_price_flag0; } if(weight_peeled_flag1) { weight_peeled_lastweight;flag11; weight_peeled_flag0; } } void ttl_send(uint8_t state) { uint8_t temp; switch(state) { case 1: temp0xAA; break;//去皮 case 2: temp0xBB; break;//增加 case 3: temp0xCC; break;//减少 case 4: temp0xDD; break;//计价 } HAL_UART_Transmit(huart1, temp, 1, 100); } void uart_send(char* fmt, ...) { memset(txbuffer, 0, TX_SIZE); va_list ap; va_start(ap, fmt); vsprintf((char *)txbuffer, fmt, ap); va_end(ap); uint16_t length strlen((char *)txbuffer); HAL_UART_Transmit(huart1, (uint8_t*)txbuffer, length, 1000); }5.1.2 HX711子程序代码#include HX711.h // #define ADSK_H GPIO_SetBits (GPIOA ,GPIO_PIN_1) // #define ADSK_L GPIO_ResetBits(GPIOA ,GPIO_PIN_1) #define ADSK_H HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET) #define ADSK_L HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET) void static Delay(uint32_t del) { delay_us(del); } void GPIO_Configuration_HX711(void) { } //读711 AD数据子程序 //**********************************************************/ uint32_t ReadCount(void) { uint32_t Count; uint16_t ADval; uint8_t i; ADSK_L; Count0; // while(GPIO_ReadInputDataBit(GPIOA ,GPIO_PIN_2)); while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2)); for(i0;i24;i) //读24个bit { ADSK_H; Delay(5); CountCount1; ADSK_L; Delay(5); // if(GPIO_ReadInputDataBit(GPIOA ,GPIO_PIN_2))Count; if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2))Count; } ADSK_H; //发送第25个脉冲表示下次转换使用A通道128db Delay(5); ADSK_L; Delay(5); ADval (int)(Count8); return(ADval); } uint32_t Weighing(void) { uint32_t X1; uint8_t t,t1,count0; uint16_t databuffer[30]; //过采样缓冲区 uint16_t temp0; uint32_t X; do{ //循环读数30次 XReadCount(); if(X100) //如果是在测量在有效区范围的值标示此读数有效 { databuffer[count]X; count; } }while(count30); if(count30) //每次度数一定要读到30次数据,否则丢弃 { do //将数据X升序排列 { t10; for(t0;tcount-1;t) { if(databuffer[t]databuffer[t1])//升序排列 { tempdatabuffer[t1]; databuffer[t1]databuffer[t]; databuffer[t]temp; t11; } } }while(t1); } /* 从排序过的数组里中间抽取连续的10组数据进行取平均值,获得较高的精度 */ X1 0; for(count 10; count 20; count){ X1 X1 databuffer[count]; } X1 X1 / 10; return X1; } void HX711_Data_Out(void) { GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin GPIO_PIN_2; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); } void HX711_Data_In(void) { GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin GPIO_PIN_2; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_NOPULL; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); } #define HX711_DATA HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2) uint32_t Read_HX711(void) { uint8_t i; uint32_t value 0; /** 数据手册写到当数据输出管脚 DOUT 为高电平时表明A/D 转换器还未准备好输出数据此时串口时 钟输入信号 PD_SCK 应为低电平所以下面设置引脚状态。 **/ HX711_Data_Out(); HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET); delay_us(1); HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET); HX711_Data_In(); /** 等待DT引脚变为高电平 **/ while(HX711_DATA); delay_us(1); /** 当 DOUT 从高电平变低电平后PD_SCK 应输入 25 至 27 个不等的时钟脉冲 25个时钟脉冲 --- 通道A 增益128 26个时钟脉冲 --- 通道B 增益32 27个时钟脉冲 --- 通道A 增益64 **/ for(i0; i24; i) //24位输出数据从最高位至最低位逐位输出完成 { HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET); delay_us(1); HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET); if(HX711_DATA 0) { value value 1; value | 0x00; } if(HX711_DATA 1) { value value 1; value | 0x01; } delay_us(1); } //第 25至 27 个时钟脉冲用来选择下一次 A/D 转换的输入通道和增益 HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET); value value^0x800000; delay_us(1); HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET); delay_us(1); return value; } #define MEDIAN_LEN 5 //中值滤波的滤波长度,一般取奇数 #define MEDIAN 3 //中值在滤波数组中的位置 uint32_t buffer[MEDIAN_LEN]; //中值滤波的数据缓存 int medleng 0; //一组中值滤波数据中,进入滤波缓存的数据个数 uint32_t xd,xd1; //数据对比大小中间变量 float weight; //重量值 //uint32_t weight; //重量值 uint32_t pi_weight; //皮重 //uint32_t hx711_xishu31706; //这是一个修正系数例如1000g砝码称出来是934g则HX711_xishu原数据*1000/934; //手机181g 电子秤显示424g //uint32_t hx711_xishu((float)31706.0)*181.0/424.0; //uint32_t hx711_xishu(float)31706.0*943.0/670.0; uint32_t hx711_xishu ((float)31706.0)*200.0/133.3;// 原来200g 显示133.3 void Get_Tare(void)//获取皮重 943 显示670//424.5 { uint32_t hx711_dat; uint8_t i; for(i0;iMEDIAN_LEN;i) { hx711_datRead_HX711(); //HX711AD转换数据处理 if(medleng 0) //缓存的第1个元素,直接放入,不需要排序 { buffer[0] hx711_dat; medleng 1; } else //插入排序算法,按从小到大的顺序排列 { for(i 0; i medleng; i ) { if( buffer[i] hx711_dat) // 轮询到的当前元素AD值,则交换它们的值xd为中间变量存放位置 { xd hx711_dat; hx711_dat buffer[i]; buffer[i] xd; } } buffer[medleng] hx711_dat; //把轮询出较大的数放入缓存的后面. medleng; } if(medleng MEDIAN_LEN) //ADC采样的数据个数达到中值滤波要求的数据个数 { hx711_dat buffer[MEDIAN]; //最终重量取中值滤波数组的中间值 medleng 0; } } pi_weight(uint16_t)(hx711_dat*0.01); } void Get_Weight() //获取被测物体重量 { uint32_t hx711_data,a; uint32_t get,aa; hx711_dataRead_HX711(); //HX711数据采集函数 get(uint16_t)(hx711_data*0.01); //HX711AD转换数据处理数据缩小100倍 if(getpi_weight) { aRead_HX711(); //重新采集HX711数据 aa(uint16_t)(a*0.01)-pi_weight; //测得的重量值减去皮重 weight((float)aa*0.00001*hx711_xishu);//重量转换函数传感器型号不同此函数要适当修改修改HX711_xishu的大小 //新加 if(weight10000)weight0; if(weight0weight2) weight0; if(weight198weight202) weight200; if(weight98weight102) weight100; if(weight298weight302) weight300; weight filt_del(weight, 1.2); } else weight0; } void hx711_init() { GPIO_InitTypeDef GPIO_InitStruct; /*Configure GPIO pin : PA1 */ //SCK GPIO_InitStruct.Pin GPIO_PIN_1; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); /*Configure GPIO pin : PA2 */ //DAT IN GPIO_InitStruct.Pin GPIO_PIN_2; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_NOPULL; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); Get_Tare(); } void hx711_read() { Get_Weight(); if(medleng 0) //缓存的第1个元素,直接放入,不需要排序 { buffer[0] weight; medleng 1; } else //插入排序算法,按从小到大的顺序排列 { for(int i 0; i medleng; i ) { if( buffer[i] weight) // 轮询到的当前元素AD值,则交换它们的值xd为中间变量存放位置 { xd weight; weight buffer[i]; buffer[i] xd; } } buffer[medleng] weight; //把轮询出较大的数放入缓存的后面. medleng; } if(medleng MEDIAN_LEN) //ADC采样的数据个数达到中值滤波要求的数据个数 { weight buffer[MEDIAN]; //最终重量取中值滤波数组的中间值 medleng 0; } }5.1.3 延时函数代码/** * brief 延时函数初始化基于定时器4 * note 定时器4配置PSC71(72-1)Period65535计数频率1MHz1us/次 * param 无 * retval 无 */ void delay_init(void) { HAL_TIM_Base_Stop(htim4); __HAL_TIM_SET_COUNTER(htim4, 0); } /** * brief 微秒级延时函数 * note 最大支持65535us因Period65535超过则截断为65535us * param us: 要延时的微秒数0~65535 * retval 无 */ void delay_us(uint16_t us) { // 边界检查us为0直接返回超过65535则限定为65535 if(us 0) return; if(us 65535) us 65535; __HAL_TIM_SET_COUNTER(htim4, 0); // 2. 启动定时器开始计数 HAL_TIM_Base_Start(htim4); // 3. 等待计数器值达到目标微秒数1us1个计数单位 while(__HAL_TIM_GET_COUNTER(htim4) us); // 4. 停止定时器避免持续计数 HAL_TIM_Base_Stop(htim4); }5.1.4 滤波函数代码float filt_lpf_16(float lpf_input) { // 静态变量保存上一次滤波结果、初始化标志 static float lpf_last 0.0f; static uint8_t first_flag 1; // 首次调用初始化 if (first_flag) { first_flag 0; lpf_last lpf_input; return lpf_last; } lpf_last lpf_last * 0.7f lpf_input * 0.3f; return lpf_last; } /************************************************* 函数名filt_del 功能死区滤波内部自动保存上次值变化小于死区则保持不变 输入 new_val 本次新输入的数值 deadzone 设定的死区阈值变化小于此值不更新 输出滤波后的稳定值 *************************************************/ float filt_del(float new_val, float deadzone) { // 静态变量自动保存上一次的稳定值断电/重启才会重置 static float last_val 0.0f; // 计算绝对差值 float delta new_val - last_val; if (delta 0.0f) { delta -delta; } // 变化小于死区 → 保持不变 if (delta deadzone) { return last_val; } // 变化超过死区 → 更新并返回新值 else { last_val new_val; return last_val; } }5.1.5 内部Flash写入代码/* 定义最后一页起始地址第64页共64KB起始0x0800FC00 */ #define FLASH_LAST_PAGE_START_ADDR 0x0800FC00UL /* 函数声明 */ void Flash_WriteByteToLastPage(uint8_t data); uint8_t Flash_ReadByteFromLastPage(void); /* ------------------------------------------------------------------------ * 函数名称Flash_WriteByteToLastPage * 功能描述向Flash最后一页起始地址写入一个字节实际写入半字0x00XX * 参数说明data - 待写入的字节数据如0x11 * 返回值 无 * 注意事项写入前会擦除整个最后一页所有数据丢失此操作会解锁Flash * 完成后重新锁定。若擦除或写入失败会进入Error_Handler。 * ------------------------------------------------------------------------ */ void Flash_WriteByteToLastPage(uint8_t data) { HAL_StatusTypeDef status; uint32_t PageError 0; /* 1. 解锁Flash */ HAL_FLASH_Unlock(); /* 2. 擦除最后一页 */ FLASH_EraseInitTypeDef eraseInit; eraseInit.TypeErase FLASH_TYPEERASE_PAGES; eraseInit.PageAddress FLASH_LAST_PAGE_START_ADDR; eraseInit.NbPages 1; status HAL_FLASHEx_Erase(eraseInit, PageError); if (status ! HAL_OK) { /* 擦除失败锁定Flash并进入错误处理 */ HAL_FLASH_Lock(); // Error_Handler(); return; } /* 3. 写入半字数据高8位为0低8位为data */ uint16_t half_word (uint16_t)data; // 例如0x0011 status HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, FLASH_LAST_PAGE_START_ADDR, half_word); if (status ! HAL_OK) { /* 写入失败锁定Flash并进入错误处理 */ HAL_FLASH_Lock(); // Error_Handler(); return; } /* 4. 锁定Flash */ HAL_FLASH_Lock(); } /* ------------------------------------------------------------------------ * 函数名称Flash_ReadByteFromLastPage * 功能描述从Flash最后一页起始地址读取一个字节只取低8位 * 参数说明无 * 返回值 读取到的字节数据0~255 * 注意事项直接读取内存无需解锁Flash。 * ------------------------------------------------------------------------ */ uint8_t Flash_ReadByteFromLastPage(void) { /* 读取半字数据然后强制转换为uint8_t只保留低位字节 */ uint16_t half_word *(volatile uint16_t*)FLASH_LAST_PAGE_START_ADDR; return (uint8_t)(half_word 0xFF); }5.1.6 DS18B20温度模块代码// 配置区可根据实际修改 #define DS18B20_GPIO_PORT GPIOA // DS18B20的GPIO端口 #define DS18B20_GPIO_PIN GPIO_PIN_4// DS18B20的GPIO引脚 // /** * brief 设置DS18B20引脚为输出模式 * retval 无 */ static void DS18B20_Set_Output(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin DS18B20_GPIO_PIN; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(DS18B20_GPIO_PORT, GPIO_InitStruct); } /** * brief 设置DS18B20引脚为输入上拉模式 * retval 无 */ static void DS18B20_Set_Input(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin DS18B20_GPIO_PIN; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(DS18B20_GPIO_PORT, GPIO_InitStruct); } // -------------------------- 核心驱动函数 -------------------------- /** * brief 复位DS18B20传感器 * retval 无 */ void DS18B20_Rst(void) { DS18B20_Set_Output(); // 设置为输出模式 HAL_GPIO_WritePin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN, GPIO_PIN_RESET); // 拉低总线 delay_us(750); // 拉低750us符合DS18B20复位时序 HAL_GPIO_WritePin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN, GPIO_PIN_SET); // 释放总线 delay_us(15); // 等待传感器回应 } /** * brief 检测DS18B20是否存在 * retval 1: 未检测到传感器 | 0: 传感器存在 */ uint8_t DS18B20_Check(void) { uint8_t retry 0; DS18B20_Set_Input(); // 设置为输入模式 // 等待传感器拉低总线超时200us while (HAL_GPIO_ReadPin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN) retry 200) { retry; delay_us(1); }; if (retry 200) return 1; // 无回应传感器不存在 else retry 0; // 等待传感器释放总线超时240us while (!HAL_GPIO_ReadPin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN) retry 240) { retry; delay_us(1); }; if (retry 240) return 1; // 回应超时 return 0; // 传感器存在 } /** * brief 从DS18B20读取1个位数据 * retval 0/1: 读取到的位值 */ uint8_t DS18B20_Read_Bit(void) { uint8_t data 0; DS18B20_Set_Output(); // 设置为输出模式 HAL_GPIO_WritePin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN, GPIO_PIN_RESET); // 拉低总线2us delay_us(2); HAL_GPIO_WritePin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN, GPIO_PIN_SET); // 释放总线 DS18B20_Set_Input(); // 设置为输入模式 delay_us(12); // 等待数据稳定 if (HAL_GPIO_ReadPin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN)) data 1; // 读取到1 else data 0; // 读取到0 delay_us(50); // 等待时序结束 return data; } /** * brief 从DS18B20读取1个字节数据已修正低位在前拼接 * retval 读取到的字节值 */ uint8_t DS18B20_Read_Byte(void) { uint8_t i, bit, dat 0; for (i 0; i 8; i) // 循环8次读取8个位 { bit DS18B20_Read_Bit(); dat | (bit i); // 修正低位在前依次拼接原代码核心错误 } return dat; } /** * brief 向DS18B20写入1个字节数据 * param dat: 要写入的字节值 * retval 无 */ void DS18B20_Write_Byte(uint8_t dat) { uint8_t j; uint8_t testb; DS18B20_Set_Output(); // 设置为输出模式 for (j 0; j 8; j) // 逐位写入 { testb dat 0x01; // 取最低位 dat dat 1; // 右移准备下一位 if (testb) // 写1拉低2us释放60us { HAL_GPIO_WritePin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN, GPIO_PIN_RESET); delay_us(2); HAL_GPIO_WritePin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN, GPIO_PIN_SET); delay_us(60); } else // 写0拉低60us释放2us { HAL_GPIO_WritePin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN, GPIO_PIN_RESET); delay_us(60); HAL_GPIO_WritePin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN, GPIO_PIN_SET); delay_us(2); } } } /** * brief 启动DS18B20温度转换 * retval 无 */ void DS18B20_Start(void) { DS18B20_Rst(); DS18B20_Check(); DS18B20_Write_Byte(0xcc); // 跳过ROM指令单传感器时用 DS18B20_Write_Byte(0x44); // 启动温度转换指令 } /** * brief 初始化DS18B20传感器 * retval 1: 初始化失败无传感器 | 0: 初始化成功 */ uint8_t DS18B20_Init(void) { // 先初始化引脚为输出模式 DS18B20_Set_Output(); HAL_GPIO_WritePin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN, GPIO_PIN_SET); DS18B20_Rst(); // 复位传感器 return DS18B20_Check(); // 检测传感器是否存在 } /** * brief 读取DS18B20温度值已修正系数0.0625 * retval 温度值单位0.1℃范围-550~1250 → -55.0℃~125.0℃ */ short DS18B20_Get_Temp(void) { uint8_t TL, TH; short tem; DS18B20_Start(); // 启动温度转换 HAL_Delay(100); // 等待转换完成最大750ms DS18B20_Rst(); DS18B20_Check(); DS18B20_Write_Byte(0xcc); // 跳过ROM DS18B20_Write_Byte(0xbe); // 读取暂存器指令 TL DS18B20_Read_Byte(); // 读取温度低字节 TH DS18B20_Read_Byte(); // 读取温度高字节 // 处理正负温度 if((TH 0xf8) ! 0) // 高5位为1表示负温度 { TL ~TL 1; // 补码转原码 TH ~TH; tem -((TH 8) | TL); } else // 正温度 { tem (TH 8) | TL; } tem (float)tem * 0.0625 * 10; // 修正系数0.0625原代码0.625错误×10转为0.1℃精度 return tem; }六、工程主要内容工程的具体内容包括但不限于原理图、程序代码、流程图等等。完整工程、原理图、APP 源码我已经开源到 Gitee需要的同学可以评论区留言或者私信001基于单片机的智能电子秤系统。开源主要内容