
第十、COMP 实验 实验目的:掌握和熟悉 G474 内部的模拟比较器用法,包括触发方式以及与内部 DAC 级联使用等。 1、软件读取 COMP 结果实验 CubeMX 配置如下,保存后生成对应的配置代码: ![]() ▲ CubeMX 进行 COMP 配置 本实验使用软件读取 COMP 比较结果,不需要配置触发,为了使比较结果更加直观,开启外部比较结果输出。 相关操作函数说明: HAL_StatusTypeDef HAL_COMP_Start(COMP_HandleTypeDef *hcomp) 功能:开启比较器; 参数 1:比较器句柄,根据需要填写; 返回:操作结果,HAL_OK 或 HAL_ERROR; 示例:HAL_COMP_Start(&hcomp3);// 开启 COMP3HAL_StatusTypeDef HAL_COMP_Stop(COMP_HandleTypeDef *hcomp) 功能:关闭比较器; 参数 1:比较器句柄,根据需要填写; 返回:操作结果,HAL_OK 或 HAL_ERROR; uint32_t HAL_COMP_GetOutputLevel(const COMP_HandleTypeDef *hcomp) 功能:读取比较器输出电平; 参数 1:比较器句柄,根据需要填写; 返回:比较结果,COMP_OUTPUT_LEVEL_LOW 或 COMP_OUTPUT_LEVEL_HIGH; 示例:result = HAL_COMP_GetOutputLevel(&hcomp3); //软件读取比较结果 核心代码: if(HAL_COMP_Start(&hcomp3) != HAL_OK) //开启比较器 { Error_Handler(); } while (1) { result = HAL_COMP_GetOutputLevel(&hcomp3); //软件读取比较结果 if(result == COMP_OUTPUT_LEVEL_HIGH)//比较结果为 1,即 INP 大于 INM { HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_11);//翻转 LED2 } HAL_Delay(100); } 以上为 main 函数中外设初始化结束后的部分首先开启比较器,然后在主循环中每 100ms读取一次比较结果,如果结果为 1,即正相输入大于反相输入,则进行 LED2 翻转。 实验现象: 下载烧录后可以观察到拨动拨盘电位器,或者遮蔽光敏电阻时可以改变比较结果,CMP 亮时,LED2 状态不变,CMP 灭时,LED2 闪烁。 2、中断读取 COMP 结果实验 CubeMX 配置如下,保存后生成对应的配置代码: ![]() ▲ CubeMX 进行 COMP 配置 本实验使用中断读取 COMP 比较结果,在比较结果变化的上升沿和下降沿都触发中断,为了使比较结果更加直观,开启外部比较结果输出。 核心代码: if(HAL_COMP_Start(&hcomp3) != HAL_OK) //开启比较器 { Error_Handler(); } 以上为 main 函数中外设初始化结束后的部分,这里只需要开启比较器即可,使用函数与上例相同。 void HAL_COMP_TriggerCallback(COMP_HandleTypeDef* hcomp) { if(hcomp->Instance==COMP3) { uint8_t temp; temp = HAL_COMP_GetOutputLevel(&hcomp3);//读取比较结果 if(temp == COMP_OUTPUT_LEVEL_HIGH)//结果为 1,上升沿触发 { HAL_GPIO_WritePin(GPIOD,GPIO_PIN_10,GPIO_PIN_RESET);//点亮 LED1 HAL_GPIO_WritePin(GPIOD,GPIO_PIN_11,GPIO_PIN_SET);//熄灭 LED2 } else { HAL_GPIO_WritePin(GPIOD,GPIO_PIN_10,GPIO_PIN_SET);//熄灭 LED1 HAL_GPIO_WritePin(GPIOD,GPIO_PIN_11,GPIO_PIN_RESET);//点亮 LED2 } } } 以上为 COMP 触发中断回调函数,该回调函数为比较器共用,需要判断中断源,如果开启了多个边沿,还需要判断具体边沿。实质上,COMP 中断触发与 EXTI 类似,实际就是将COMP 比较输出作为一个 IO 映射到 EXTI 进行中断。 实验现象: 下载烧录后可以观察到拨动拨盘电位器,或者遮蔽光敏电阻时可以改变比较结果,CMP 亮时,LED1 灭,LED2 亮,CMP 灭时,LED1 亮,LED2 灭。 3、内部 DAC 级联比较实验 CubeMX 配置如下,保存后生成对应的配置代码: ![]() ▲ CubeMX 进行 COMP 配置 ![]() ▲ CubeMX 进行 DAC 配置 本实验使用软件读取 COMP 比较结果,正相输入为光敏电阻,反相输入使用 DAC3 的 OUT1作为比较电压,为了使比较结果更加直观,开启外部比较结果输出。 核心代码: void DAC3_CH1_Set_Vol(uint16_t vol) { double temp=vol; temp/=1000; temp=temp*4096/3.3; HAL_DAC_SetValue(&hdac3,DAC_CHANNEL_1,DAC_ALIGN_12B_R,temp);//12 位右对齐数据格式设置 DAC 值 } 以上为 DAC 设置函数,通过该函数可以将输入的电压快速转换为 DAC 所需的寄存器数据。 DAC3_CH1_Set_Vol(500);//DAC 输出设置为 500mv HAL_DAC_Start(&hdac3,DAC_CHANNEL_1);//生效 DAC 输出 if(HAL_COMP_Start(&hcomp3) != HAL_OK) //开启比较器 { Error_Handler(); } while (1) { result = HAL_COMP_GetOutputLevel(&hcomp3); //软件读取比较结果 if(result == COMP_OUTPUT_LEVEL_HIGH)//比较结果为 1,即 INP 大于 INM { HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_11);//翻转 LED2 } HAL_Delay(100); } 以上为 main 函数中外设初始化结束后的部分,首先设置好 DAC 输出电压,开启 DAC,然后开启比较器,最后在主循环中每 100ms 读取一次比较结果,如果结果为 1,即正相输入大于反相输入,则进行 LED2 翻转。 实验现象: 下载烧录后可以观察到遮蔽光敏电阻时可以改变比较结果,CMP 亮时,LED2 状态不变,CMP 灭时,LED2 闪烁。 第十一、FLASH 实验 实验目的:掌握和熟悉 G474 内部的 FLASH 用法,包括 FLASH 读写应用等。FLASH 读取无需进行配置。 核心代码: /* 函数名:FLASH_Write * 描述 :flash 写入数据 * 输入 :data 写入数据地址 addr flash 地址 size 写入字节数 * 输出 :无 * 调用 :FLASH_Write(data,FLASH_PAGE_127,8);//8 字节 */ uint8_t FLASH_Write(uint8_t* data,uint32_t addr,uint16_t SIZE) { FLASH_EraseInitTypeDef EraseInitStruct;//定义擦写操作结构体 uint32_t SECTORError = 0; uint64_t write_buff[10];//写入缓冲数组 uint8_t i = 0; uint8_t size = SIZE/8;//8 字节写入次数 memcpy(write_buff,data,SIZE);//将输入的数组移动到写入缓冲区 HAL_FLASH_Unlock();//解锁 EraseInitStruct.Banks = FLASH_BANK_1; //存储区 1 EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;//页擦除 EraseInitStruct.Page = (addr-0x08000000)/FLASH_PAGE_SIZE;//擦除页 EraseInitStruct.NbPages = 1;//擦除页数 if(HAL_FLASHEx_Erase(&EraseInitStruct,&SECTORError) != HAL_OK) //擦除页 { return 1;//擦除失败 } while(size) { if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,addr,write_buff)!= HAL_OK)//双字写入 { return 2;//写入失败 } addr = addr+8; i++; size--; } HAL_FLASH_Lock(); //锁定 FLASH return 0; } /* 函数名:FLASH_Read * 描述 :flash 读取数据 * 输入 :data 读取数据地址 addr flash 地址 size 读取字节数 * 输出 :无 * 调用 :FLASH_Read(data,FLASH_PAGE_127,8);//8 字节 */ uint8_t FLASH_Read(uint8_t* data,uint32_t addr,uint16_t SIZE) { uint32_t read_buff[10]; //接收缓冲数组 uint8_t i = 0; uint16_t size = SIZE/4; //接收次数 while(size) { read_buff = *(__IO uint32_t*)addr; //从 FLASH 读取数据到接收缓冲数组 addr = addr+4; i++; size--; } memcpy(data,read_buff,SIZE); //将数据从接收缓冲数组转移到接收数组 return 0; } 主要代码: uint8_t data[8]={0,0,0,0,0,0,0,0}; char buf[30]=""; FLASH_Read(data,FLASH_PAGE_127,8);//8 字节 data[0]++;FLASH_Write(data,FLASH_PAGE_127,8);//8 字节 snprintf(buf,10,"times:%d",data[0]); LCD_PutString(10,30,buf,Red,White,0); uint32_t UID1=READ_REG(*((uint32_t *)UID_BASE)); uint32_t UID2=READ_REG(*((uint32_t *)(UID_BASE+4U))); uint32_t UID3=READ_REG(*((uint32_t *)(UID_BASE+8U))); snprintf(buf,30,"UID:%0x-%0x-%0x",UID1,UID2,UID3); LCD_PutString(10,60,buf,Red,White,0); 实验现象: 下载烧录后可以观察到 LCD 显示 FLASH 测试次数以及芯片的 ID。 ![]() ▲ 实验现象 第十二、单总线实验 实验目的:掌握和熟悉常用的单总线通信,包括 DS18B20,DHT11 读写应用。 CubeMX 配置如下,保存后生成对应的配置代码: ![]() ▲ 图 3.12.1 CubeMX 进行 GPIO 输出配置 本实验进行单总线读取 DS18B20 和 DHT11。使用 CUBEMX 配置 IO 为输出模式。 读取 DS18B20 核心代码: /* 函数名:DS18B20_IO_OUT * 功能:初始化 DS18B20 的 GPIO 为输出模式 * 输入:无 * 输出:无 * 备注:无 */ void DS18B20_IO_OUT(void ) { GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_9; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); } /* 函数名:DS18B20_IO_OUT * 功能:初始化 DS18B20 的 GPIO 为输入模式 * 输入:无 * 输出:无 * 备注:无 */void DS18B20_IO_IN(void ) { GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_9; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); } /* 函数名:DS18B20_Rst * 功能:复位 DS18B20 * 输入:无 * 输出:无 * 备注:无 */ void DS18B20_Rst(void) { DS18B20_IO_OUT(); //SET PG11 OUTPUT DQ_OUT_LOW(); //拉低 DQ delay_us(750); //拉低 750us DQ_OUT_HIGH(); //DQ=1 delay_us(15); //15US } /* 函数名:DS18B20_Check * 功能:等待 DS18B20 的回应 * 输入:无 * 输出:返回 1:未检测到 DS18B20 的存在 返回 0:存在 * 备注:无 */ uint8_t DS18B20_Check(void) { uint8_t retry=0; DS18B20_IO_IN(); //SET PG11 INPUT while (DQ_GET_IN()&&retry<200) { retry++;delay_us(1); }; if(retry>=200)return 1; else retry=0; while (!DQ_GET_IN()&&retry<240) { retry++; delay_us(1); }; if(retry>=240)return 1; return 0; } /* 函数名:DS18B20_Read_Bit * 功能:从 DS18B20 读取一个位 * 输入:无 * 输出:返回值:1/0 * 备注:无 */ uint8_t DS18B20_Read_Bit(void) { uint8_t data; DS18B20_IO_OUT(); //SET PG11 OUTPUT DQ_OUT_LOW();delay_us(2); DQ_OUT_HIGH(); DS18B20_IO_IN(); //SET PG11 INPUT delay_us(12); if(DQ_GET_IN())data=1; else data=0; delay_us(50); return data; } /* 函数名:DS18B20_Read_Byte * 功能:从 DS18B20 读取一个字节 * 输入:无 * 输出:返回值:读到的数据 * 备注:无 */ uint8_t DS18B20_Read_Byte(void) { uint8_t i,j,dat; dat=0; for (i=1;i<=8;i++) { j=DS18B20_Read_Bit(); dat=(j<<7)|(dat>>1); } return dat; } /* 函数名:DS18B20_Write_Byte * 功能:写一个字节到 DS18B20 * 输入:dat:要写入的字节 * 输出:无 * 备注:无 */ void DS18B20_Write_Byte(uint8_t dat) { uint8_t j; uint8_t testb; DS18B20_IO_OUT(); //SET PG11 OUTPUT; for (j=1;j<=8;j++) { testb=dat&0x01; dat=dat>>1; if (testb) { DQ_OUT_LOW(); // Write 1 delay_us(2); DQ_OUT_HIGH(); delay_us(60); } else { DQ_OUT_LOW(); // Write 0 delay_us(60); DQ_OUT_HIGH(); delay_us(2); } } } /* 函数名:DS18B20_Start * 功能:开始温度转换 * 输入:无 * 输出:无 * 备注:无 */ // void DS18B20_Start(void) { DS18B20_Rst(); DS18B20_Check(); DS18B20_Write_Byte(0xcc);// skip rom DS18B20_Write_Byte(0x44);// convert } /* 函数名:DS18B20_Start * 功能:从 ds18b20 得到温度值 精度:0.1C * 输入:无 * 输出:返回值:温度值 (-55.0~125.0) * 备注:无 */ short DS18B20_Get_Temp(void) { uint8_t temp; uint8_t TL,TH; short tem;DS18B20_Start (); // ds1820 start convertDS18B20_Rst(); 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) { TH=~TH; TL=~TL; temp=0; //温度为负 }else temp=1; //温度为正 tem=TH; //获得高八位 tem<<=8;tem+=TL; //获得底八位 tem=(float)tem*0.625; //转换 char buf[20]=""; if(temp)snprintf(buf,10,"Temp:%.1f",tem/10.0); else snprintf(buf,10,"Temp:%.1f",tem/10.0); LCD_PutString(10, 10, buf, White,Blue,1); if(temp)return tem; //返回温度值 else return -tem; } 读取 DHT11 核心代码: /* 函数名:delay_us * 功能:微秒延时 * 输入:delay 延时多少微秒 * 输出:无 * 备注:无 */ #define CPU_FREQUENCY_MHZ 170 // STM32 时钟主频 void delay_us(__IO uint32_t delay) { int last, curr, val; int temp; while (delay != 0) { temp = delay > 900 ? 900 : delay; last = SysTick->VAL; curr = last - CPU_FREQUENCY_MHZ * temp; if (curr >= 0) { do { val = SysTick->VAL; } while ((val < last) && (val >= curr)); } else { curr += CPU_FREQUENCY_MHZ * 1000; do { val = SysTick->VAL; } while ((val <= last) || (val > curr)); } delay -= temp; } } unsigned int rec_data[4]; /* 函数名:DH11_GPIO_Init_OUT * 功能:初始化 DHT11 的 GPIO 为输出模式 * 输入:无 * 输出:无 * 备注:无 */ void DH11_GPIO_Init_OUT(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_9; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); } // 对于 M0 来说,是输入 /* 函数名:DH11_GPIO_Init_IN * 功能:初始化 DHT11 的 GPIO 为输入模式 * 输入:无 * 输出:无 * 备注:无 */ void DH11_GPIO_Init_IN(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_9; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); // DL_GPIO_initDigitalInput(GPIO_DQ_dht11_and_ds18b20_IOMUX);//配置为上拉输入 } // 主机发送开始信号 /* 函数名:DHT11_Start * 功能:主机发送开始信号 * 输入:无 * 输出:无 * 备注:无 */ void DHT11_Start(void) { DH11_GPIO_Init_OUT(); // 输出模式 dht11_high; // 先拉高 delay_us(30); dht11_low; // 拉低电平至少 18ms HAL_Delay(20); dht11_high; // 拉高电平 20~40us DH11_GPIO_Init_IN(); // 输入模式 delay_us(30); } // 获取一个字节 /* 函数名:DHT11_Rec_Byte * 功能:获取一个字节 * 输入:无 * 输出:读取的字符 * 备注:无 */ char DHT11_Rec_Byte(void) { unsigned char i = 0; unsigned char data; for (i = 0; i < 8; i++) // 1 个数据就是 1 个字节 byte,1 个字节 byte 有 8 位 bit { while (dht11_read == 0) ; // 从 1bit 开始,低电平变高电平,等待低电平结束 delay_us(30); // 延迟 30us 是为了区别数据 0 和数据 1,0 只有 26~28us data <<= 1; // 左移 if (dht11_read == GPIO_PIN_SET) // 如果过了 30us 还是高电平的话就是数据 1 { data |= 1; // 数据+1 } while (dht11_read == GPIO_PIN_SET) ; // 高电平变低电平,等待高电平结束 } return data; } // 获取数据 /* 函数名:DHT11_REC_Data * 功能:获取 DHT11 数据并打印 * 输入:无 * 输出:无 * 备注:无 */ void DHT11_REC_Data(void) { unsigned int R_H, R_L, T_H, T_L; unsigned char RH, RL, TH, TL, CHECK; DHT11_Start(); // 主机发送信号 //dht11_high; // 拉高电平 if (dht11_read == 0) // 判断 DHT11 是否响应 { while (dht11_read == 0); // 低电平变高电平,等待低电平结束 while (dht11_read == GPIO_PIN_SET); // 高电平变低电平,等待高电平结束 R_H = DHT11_Rec_Byte(); R_L = DHT11_Rec_Byte(); T_H = DHT11_Rec_Byte(); T_L = DHT11_Rec_Byte(); CHECK = DHT11_Rec_Byte(); // 接收 5 个数据 //DH11_GPIO_Init_OUT(); //dht11_low; // 当最后一 bit 数据传送完毕后,DHT11 拉低总线 50us delay_us(55); // 这里延时 55us //dht11_high; // 随后总线由上拉电阻拉高进入空闲状态。 if (R_H + R_L + T_H + T_L == CHECK) // 和检验位对比,判断校验接收到的数据是否正确 { RH = R_H; RL = R_L; TH = T_H; TL = T_L; } } rec_data[0] = RH; rec_data[1] = RL; rec_data[2] = TH; rec_data[3] = TL; char buf[20]=""; snprintf(buf,10,"Temp:%d.%d",TH,TL); LCD_PutString(10, 10, buf, White,Blue,1); snprintf(buf,10,"Hum:%d.%d",RH,RL); LCD_PutString(10, 30, buf, White,Blue,1);} 主要代码: while(1){ DHT11_REC_Data();//DHT11 读取 //DS18B20_Get_Temp();//18B20 读取 HAL_Delay(1000); } 实验现象: 下载烧录后可以观察到 LCD 显示 DS18B20 测试的温度,或者 DHT11 测量的温度和湿度。 ![]() ▲ 图 3.12.2 实验现象 第十三、独立看门狗 实验目的:掌握和熟悉独立看门狗用法,包括喂狗操作等。 CubeMX 配置如下,保存后生成对应的配置代码: ![]() ▲ 图 3.13.1 CubeMX 进行独立看门狗配置 本实验进行独立看门狗配置。使用 CUBEMX 配置 IO 为输出输入模式实现 LED 指示和按键读取,GPIO 配置参考上文。程序中使用按键喂狗,如果没在 1s 内喂狗,系统将会自动复位。 主要代码: while (1) { if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_4)==0) { HAL_Delay(5); if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_4)==0) { HAL_IWDG_Refresh(&hiwdg); HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_10); while(!HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_4)); } } /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } 实验现象: 下载烧录后可以观察到如果在一定时间内进行喂狗操作,系统会复位,LCD 重新加载。 ![]() ▲ 图 3.13.2 实验现象 转载自: AI电堂 如有侵权请联系删除 |