蓝桥杯嵌入式开发板结构分析及功能学习笔记

发布于:2025-04-16 ⋅ 阅读:(33) ⋅ 点赞:(0)

板子结构一览

主控为 **STM32G431RBT6**** 外部晶振频率为 **24MHz

IAP下载为** GD32F350C8T6**

时钟源分析

自己配置的STM32CubeMX选择的是168M的高频时钟 (Max 170MHz),并且用的是外部时钟源,晶振频率为24MHz(来自原理图),

当我解析了工程中的配置文件,时钟树配置参数如下,使用内部时钟(16MHz),并且时钟频率为80MHz

官方历程 中有一个频率校准函数,通过查询资料得知,只有在使用内部时钟的时候采用,需要将其设置为0x40,F103设置的是0x16应该不同的芯片这个值不同,是温度等环境参数动态调整的

/** @defgroup RCC_HSI_Config HSI Config
  * @{
  */
#define RCC_HSI_OFF                    0x00000000U            /*!< HSI clock deactivation */
#define RCC_HSI_ON                     RCC_CR_HSION           /*!< HSI clock activation */
#define RCC_HSICALIBRATION_DEFAULT     0x40U                  /* Default HSI calibration trimming value */

RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
**<font style="color:#DF2A3F;background-color:#FBDE28;">使用内部时钟的时候需要增加校准值,并且将其校准为0x40(STM32G4)</font>**

74LS573锁存器

74LS573为锁存器,用于复用GPIO,似的GPIO可以作为输入,同时又可以作为输出,芯片管脚图如左图所示,真值表如右图所示,大概意思是有一个管脚,如果使能后输出的结果就会和 输入同步变化,如果没有使能,就维持上一次的状态不变

OE:输出使能引脚,低电平有效

LE:寄存器使能引脚,高电平有效,为高电平时,输出为输入的状态,为低电平时保持上一次的状态

使用程序编程实现LED流水灯操作,代码如下

  HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,1);//激活74HC573的LE管脚
  /* USER CODE END 2 */
	GPIOC->ODR |= 0xff00;
	GPIOC->ODR &= led_sta;
	HAL_UART_Transmit(&huart1,(uint8_t *)hello_msg,sizeof(hello_msg),0xff);
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
//	  HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_8);
	led_sta=0x7F00;
	for(uint8_t i=0;i<8;i++){
		led_cmd= led_sta>>i;
		GPIOC->ODR &= led_cmd;
		HAL_Delay(200);
	}
	GPIOC->ODR |= 0xff00;
	led_sta = 0x100;
	record_last=led_sta;
	GPIOC->ODR &=led_sta;
	HAL_Delay(200);
	for(uint8_t i=0;i<8;i++){
	   
		led_cmd= led_sta<<i;
		led_cmd = (led_cmd | record_last);
		#if 0
		memset(data_msg,0,sizeof(data_msg));
		sprintf((char *)data_msg,"sta:0x%x record_last:0x%x cmd:0x%x\r\n",led_sta,record_last,led_cmd);
		HAL_UART_Transmit(&huart1,(uint8_t *)data_msg,strlen(data_msg),0xff);
		#endif
		GPIOC->ODR |= 0xff00;
		GPIOC->ODR &= led_cmd;
		record_last = led_cmd;
		HAL_Delay(200);
		
	}
	count++;
	if(count %3 != 0)
	{
		memset(data_msg,0,sizeof(data_msg));
		sprintf((char *)data_msg,"74LS573 IS ACTIVE!\r\n");
		HAL_UART_Transmit(&huart1,(uint8_t *)data_msg,strlen(data_msg),0xff);
		HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,1);//激活74HC573的LE管脚
	}
	else{
		memset(data_msg,0,sizeof(data_msg));
		sprintf((char *)data_msg,"74LS573 IS Not ACTIVE!\r\n");
		HAL_UART_Transmit(&huart1,(uint8_t *)data_msg,strlen(data_msg),0xff);
		HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,0);//取消激活74HC573的LE管脚
	}

	
//	  HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_9);
    /* USER CODE BEGIN 3 */
  }

实验现象

按键输入

按键输入部分的原理图如图所示,默认上拉,按下后GPIO接地,可以 通过循环 读取GPIO的状态,或者中断的方式去检查按键状态。

在实验程序中 因为公用的EXIT0 中断线 我设置了两种触发方式,一种是中断触发,需要配置其中断优先级,另外一种是通过循环扫描+消抖去读取按键的状态

/*中断的GPIO配置*/
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

/*扫描按键的GPIO配置*/
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);


/*设置中断优先级*/
HAL_NVIC_SetPriority(EXTI0_IRQn, 4, 2);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);

HAL_NVIC_SetPriority(EXTI1_IRQn, 4, 3);
HAL_NVIC_EnableIRQ(EXTI1_IRQn);

HAL_NVIC_SetPriority(EXTI2_IRQn, 4, 4);
HAL_NVIC_EnableIRQ(EXTI2_IRQn);


/*中断线的触发函数*/
/**
  * @brief This function handles EXTI line0 interrupt.
  */
void EXTI0_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI0_IRQn 0 */

  /* USER CODE END EXTI0_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
  /* USER CODE BEGIN EXTI0_IRQn 1 */

  /* USER CODE END EXTI0_IRQn 1 */
}



/*GPIO中断的回调函数*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if(GPIO_Pin == GPIO_PIN_0)
	{
		memset(send_str,0,sizeof(send_str));
		sprintf((char  *)send_str,"key0 is  press!\r\n");
		HAL_UART_Transmit(&huart1,(uint8_t *)send_str,strlen(send_str),0xff);
		
	}
		if(GPIO_Pin == GPIO_PIN_1)
	{
		memset(send_str,0,sizeof(send_str));
		sprintf((char  *)send_str,"key1 is  press!\r\n");
		HAL_UART_Transmit(&huart1,(uint8_t *)send_str,strlen(send_str),0xff);
		
	}
	
		if(GPIO_Pin == GPIO_PIN_2)
	{
		memset(send_str,0,sizeof(send_str));
		sprintf((char  *)send_str,"key2 is  press!\r\n");
		HAL_UART_Transmit(&huart1,(uint8_t *)send_str,strlen(send_str),0xff);
		
	}
}

实验现象:

滴答定时器 SysTick

串口收发

STM32的串口发送非常简单,但是串口接收的方式多种多样

  1. 使用轮训的方式接收
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
  1. 使用串口中断的方式接收
  HAL_NVIC_EnableIRQ(USART1_IRQn);
  HAL_NVIC_SetPriority(USART1_IRQn,4,1);

uint8_t rx_dat[10]; 
HAL_UART_Receive_IT(&huart1,rx_dat,2);
/**
  * @brief This function handles USART1 global interrupt / USART1 wake-up interrupt through EXTI line 25.
  */
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
  HAL_UART_Receive_IT(&huart1,rx_dat, 2);
  /* USER CODE END USART1_IRQn 1 */
}

/* USER CODE BEGIN 1 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if(huart->Instance == USART1){
    uint16_t led_cmd = rx_dat[1] & 0x0100;
	 
	  HAL_UART_Transmit(&huart1,rx_dat,2,0xff);    // GPIOC->ODR |= 0xFF00;
    // GPIOC->ODR &= led_cmd;

    HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_8);
  }

}
  1. 使用IDLE接收不定长的数据
	//下方为自己添加的代码
	 __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);
	/*打开idle 接收中断*/
	HAL_UARTEx_ReceiveToIdle_IT(&huart1,rec_buf,64);

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
	if(huart->Instance  == USART1)
	{
		
		HAL_UART_Transmit(&huart1,rec_buf,Size,0xff);
		 HAL_UARTEx_ReceiveToIdle_IT(&huart1,rec_buf,64);
	}
}

自定义串口打印函数,模仿printf

void MyPrintf(const char *__format, ...)
{
  va_list ap;
  va_start(ap, __format);
  
  /* 清空发送缓冲区 */
  memset(TxBuf, 0x0, TX_BUF_LEN);
  
  /* 填充发送缓冲区 */
  vsnprintf((char*)TxBuf, TX_BUF_LEN, (const char *)__format, ap);
  va_end(ap);
  int len = strlen((const char*)TxBuf);
  
  /*  */
  HAL_UART_Transmit(&huart2, (uint8_t*)&TxBuf, len, 0xFFFF);
}

LCD屏幕

LCD的移植程序非常简单 只需要将**lcd.c lcd.h,** 放置在代码下,在主程序中初始化LCD_Init即可,然后就可以开始调用函数去显示字符。

原理图

LCD_Init();
LCD_Clear(Blue);
LCD_SetBackColor(Green);
LCD_SetTextColor(Blue);
LCD_DisplayStringLine(Line1,(uint8_t *)"Hello,Harbin!");
LCD_DisplayStringLine(Line0,(uint8_t *)"LCD Test");
LCD_DisplayStringLine(Line3,(uint8_t *)"Designed By NS");
实现效果

提供的库函数如下

void LCD_Init(void);/*初始化*/
void LCD_SetTextColor(vu16 Color);/*设置文字颜色*/
void LCD_SetBackColor(vu16 Color);/*设置背景颜色*/
void LCD_ClearLine(u8 Line);/*清除某一行*/
void LCD_Clear(u16 Color);/*设置为某一种颜色*/
void LCD_SetCursor(u8 Xpos, u16 Ypos);/*设置坐标*/
void LCD_DrawChar(u8 Xpos, u16 Ypos, uc16 *c);/*显示一个字符*/
void LCD_DisplayChar(u8 Line, u16 Column, u8 Ascii);/*显示字符??*/
void LCD_DisplayStringLine(u8 Line, u8 *ptr);/*按行显示字符串*/
void LCD_SetDisplayWindow(u8 Xpos, u16 Ypos, u8 Height, u16 Width);/*设置显示区域*/
void LCD_WindowModeDisable(void);
void LCD_DrawLine(u8 Xpos, u16 Ypos, u16 Length, u8 Direction);/*画线*/
void LCD_DrawRect(u8 Xpos, u16 Ypos, u8 Height, u16 Width);/*画矩形*/
void LCD_DrawCircle(u8 Xpos, u16 Ypos, u16 Radius);/*画圆*/
void LCD_DrawMonoPict(uc32 *Pict);
void LCD_WriteBMP(u32 BmpAddress);
void LCD_DrawBMP(u32 BmpAddress);//画bmp图像
void LCD_DrawPicture(const u8* picture);//画图像

ADC采样

蓝桥杯板子的ADC接在了PB15 和PB12上,其中PB12对用的是ADC1->IN11 PB15对应的是ADC2->IN15,如果同时采样这两个ADC必须得同时使用ADC1和ADC2

/*获取ADC1 的值*/
uint16_t GetADC1(void)
{
	uint16_t adcVal = 0;
	HAL_ADC_Start(&hadc1);
	adcVal = HAL_ADC_GetValue(&hadc1);
	return adcVal;
}

/*获取ADC2 的值*/
uint16_t GetADC2(void)
{
	uint16_t adcVal = 0;
	HAL_ADC_Start(&hadc2);
	adcVal = HAL_ADC_GetValue(&hadc2);
	return adcVal;
}

在主程序中需要初始化LCD并且控制了一下LED灯 因为LCD和LED共用了GPIOC的口 所以通过锁存器 提前 配置好LED的灯的状态

    LCD_Init();
    /*熄灭LED灯 */
    HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);//激活锁存器
    GPIOC->ODR |= 0xFF00;
    GPIOC->ODR &= 0xaa00;
    
    HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
    
    
    LCD_Clear(White);
    LCD_SetBackColor(White);
    
    LCD_SetTextColor(Red);
    
    
    LCD_DisplayStringLine(Line0,(u8 *)"ADC Test!");
    
    
    LCD_SetBackColor(Blue2);
    LCD_SetTextColor(Black);
    
    LCD_DisplayStringLine(Line8,(u8 *)"Designed By NS.");
    
    /* USER CODE END 2 */
    LCD_SetBackColor(Green);

/*while循环*/
  while (1)
  {
    /* USER CODE END WHILE */
	  memset(adc_buf,0,sizeof(adc_buf));
	  sprintf(adc_buf,"ADC1_VAL:%.2f V",(GetADC1()*3.3)/4095);
		LCD_DisplayStringLine(Line5,(u8 *)adc_buf);
    
	  	  memset(adc_buf,0,sizeof(adc_buf));
	  sprintf(adc_buf,"ADC2_VAL:%.2f V",(GetADC2()*3.3) / 4096);
		LCD_DisplayStringLine(Line6 ,(u8 *)adc_buf);
		HAL_Delay(200);
	  /* USER CODE BEGIN 3 */
  }

效果展示

AT24C02(EEPROM)

AT24C02是存储单元 通过I2C读取数据

AT24C02的 地址构成 ,高四位固定位**0xa **,底三位的A0 A1 A2决定了其后缀地址,当A0 A1 A2都为0时,设备地址为0xa0 ,同时I2C为7为地址模式,第0位决定了读还是写 0-写 1-读

读写的流程

读写的函数

/*AT24C02 Read a Byte data*/
uint8_t AT24C02_ReadByte(uint8_t addr)
{
    /*1.发送起始信号*/
    uint8_t val;
    I2CStart();
    I2CSendByte(0xA0);//地址0xA0
    I2CWaitAck();


    /*发送要读出数据的内部寄存器地址信息*/
    I2CSendByte(addr);
    I2CWaitAck();


    /*开始读数据*/
    I2CStart();
    I2CSendByte(0xA1);//读数据的地址
    I2CWaitAck();
    val=I2CReceiveByte();
    I2CWaitAck();
    I2CStop();

    return val;

}

/*AT24C02 Write a byte data*/
void AT24C02_WriteByte(uint8_t addr,uint8_t data)
{

    /*发送起始信号*/
    I2CStart();
    I2CSendByte(0xA0);
    I2CWaitAck();

    /*发送写数据的内部寄存器的 地址*/
    I2CSendByte(addr);
    I2CWaitAck();
    /*发送要写入的数据*/
    I2CSendByte(data);
    I2CWaitAck();
    I2CStop();

}

AT24C02读多个字节


/*读取读个字节*/
int AT24C02_ReadMultiBytes(uint8_t devAddr, uint8_t memAddr, uint8_t *buf, uint8_t len) {
    // 1. Start
    I2CStart();
    
    // 2. 发送设备地址(写模式) + ACK检查(设置内存地址)
    I2CSendByte(devAddr | 0);  // 0xA0
    if (I2CWaitAck() != SUCCESS) return -1;
    
    // 3. 发送内存起始地址
    I2CSendByte(memAddr);
    if (I2CWaitAck() != SUCCESS) return -2;
    
    // 4. Repeated Start
    I2CStart();
    
    // 5. 发送设备地址(读模式)
    I2CSendByte(devAddr | 1);  // 0xA1
    if (I2CWaitAck() != SUCCESS) return -3;
    
    // 6. 循环读取数据
    for (uint8_t i = 0; i < len; i++) {
        if (i == len - 1) {
            buf[i] = I2CReceiveByte();  // 读取并发送 NACK(结束读取)
            I2CSendNotAck();
        } else {
            buf[i] = I2CReceiveByte();  // 读取并发送 ACK
            I2CSendAck();
        }
    }
    
    // 7. Stop
    I2CStop();
	 
	 return 0;
}


AT24C02写多个字节

/*写入多个字节*/
int AT24C02_WriteMultiBytes(uint8_t devAddr, uint8_t memAddr, uint8_t *data, uint8_t len) {
    // 1. Start
    I2CStart();
    
    // 2. 发送设备地址(写模式) + ACK检查
    I2CSendByte(devAddr | 0);  // 0xA0
    if (I2CWaitAck() != SUCCESS) {
        // 处理错误(如无应答)
        return -1;
    }
    
    // 3. 发送内存起始地址
    I2CSendByte(memAddr);
    if (I2CWaitAck() != SUCCESS) return -2;
    
    // 4. 循环发送数据字节
    for (uint8_t i = 0; i < len; i++) {
        I2CSendByte(data[i]);
        if (I2CWaitAck() != SUCCESS) break;  // 发送失败则终止
    }
    
    // 5. Stop
    I2CStop();
    
    // 6. 等待 EEPROM 完成内部写入(重要!)
    HAL_Delay(5);
	 
	 return 0;
}

实验效果

可编程电阻

蓝桥杯板子上可编程电阻使用的是MCP4017,其内部结构如下图所示,内部相当于一个滑动变阻器,通过I2C写入数据改变滑子的位置,从而改变电阻器的电阻值,由于其内部B点已经接地,A点有些芯片不会被引出,所以我们能读到的接口只能为RBW之间的阻值。

在看原理图,W点接了10k电阻到VCC(3.3V),B点通过跳线帽接GND,所以我们可以测得W点对地的电压,然后经过推导,算出W的电阻值。

:::color1
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

:::

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们通过I2C去 写数据和读数据,其I2C的地址为0x5E 那么写的地址 位0x5E 读的地址为0x5F

蓝桥板子提供的读取和 写的代码如下

void write_resistor(uint8_t value)
{   
	I2CStart();
	I2CSendByte(0x5E);  
	I2CWaitAck();
	
	I2CSendByte(value);  
	I2CWaitAck();
	I2CStop();
}

uint8_t read_resistor(void)
{   
	uint8_t value; 
	I2CStart();
	I2CSendByte(0x5F);  
	I2CWaitAck();
	
	value = I2CReceiveByte();
	I2CSendNotAck();
	I2CStop();

	return value;
}

通过下面的图我们可以看出 写入的数据只有7位 最上面固为A 所以写入的范围为0-127 即0x00-0x7F,MCP4017的可编程电阻值为100kΩ,而调整范围 位0-127所以这个比例关系为,其中Wvalue为写入的值

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

好接下来我们开始实战,接线如下 还是使用U2B调试工具和万用表测量电压来验证

写入值推测电阻

我们写入数据值为0x50

根据理论公式RWB=80/127*100k=63k

测量的电压为2.803V 算出的实际值为 (10*2.803) / (3.3-2.803)=56k

理论值和实际值有7K的误差 可能由于10K电阻有误差,以及可编程电阻有误差 但是还是很接近的

测试2

写入最大值0x7f(127)

根据理论公式 RWB=127/127100k = 100k 算出理论电压为 3.3(100/(10+110))=3V

现在我们测量 实际 电压值为2.958还是很接近理论值的

编程实验

#include "mcp4017.h"

#include "i2c_hal.h"

void MCP_WriteVal(uint8_t Val)
{
    /*判断写入值的范围  ,不合适直接退出*/
    if(Val<=0 || Val>0x7f){
        return;
    }

    I2CStart();
    I2CSendByte(0x5E);
	 I2CWaitAck();

    
    I2CSendByte(Val);
	 I2CWaitAck();
    I2CStop();
}



uint8_t MCP_ReadVal(void)
{
    I2CStart();
    I2CSendByte(0x5F);
    I2CWaitAck();

    uint8_t val =  I2CReceiveByte();
    I2CSendNotAck();
    I2CStop();

    return val;

}

#ifndef __MCP4017_H
#define __MCP4017_H
#include "main.h"

uint8_t MCP_ReadVal(void);
void MCP_WriteVal(uint8_t Val);
#endif

 /*写入电阻值*/
  MCP_WriteVal(20);

while (1)
  {
    /* USER CODE END WHILE */

    mc_val  =    MCP_ReadVal();      
    resist = (mc_val*100)/127;
    vlotage = 3.3*(resist/(10+resist));
    memset(displayBuf,0,sizeof(displayBuf));
	  sprintf(displayBuf,"val:%d RES:%.2f K",mc_val,resist);
	  LCD_DisplayStringLine(Line7,(u8 *)displayBuf);

    memset(displayBuf,0,sizeof(displayBuf));
	  sprintf(displayBuf,"Voltage:%.2f V", vlotage);
    LCD_DisplayStringLine(Line8,(u8 *)displayBuf);
  /* Infinite loop */
    HAL_Delay(200);
    /* USER CODE BEGIN 3 */
  }

TIM定时器

void MX_TIM2_Init(void)
{

  /* USER CODE BEGIN TIM2_Init 0 */

  /* USER CODE END TIM2_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE BEGIN TIM2_Init 1 */

  /* USER CODE END TIM2_Init 1 */
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 1000-1;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 79;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;/*选择自动重装载*/
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM2_Init 2 */

    HAL_TIM_Base_Start_IT(&htim2);/*启动带中断的定时器*/
  /* USER CODE END TIM2_Init 2 */

}
uint16_t i=0;
/* USER CODE BEGIN 1 */

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if(htim->Instance == TIM2){
    i++;
    if(i>=  100){
      i=0;
      HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_9);
    }
    
  }
}

输入捕获

输入捕获的PWM波如下,其主要思想是捕获两个上升沿(下降沿 )记录这两个捕获到的CNT0 CNT1之间的数 然后知道这个数了和定时器周期去相乘,就能得到时间,然后倒数一下就是频率。

推导如下:

  1. 定时计数器记一个数的频率等于系统时钟/分频 得到的是记一个数的频率

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 记一个数的时间就等于频率T0的倒数

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 两次捕获到上升沿 (一个周期T) 之间的计数器的数值差为CapValue

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 那么PWM的周期就为上升沿之间的数值差*计数周期即PWM的周期

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. PWM的频率就为周期的倒数

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 总的公式

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

信号发生器的电路图,连接到了A15 B4引脚,其频率通过滑动变阻器R39 R40控制

实验1 生成PWM信号并测试

我们通过定时器2 的CH2 CH3 即A1 A2管脚生成PWM信号,然后通过A7 TIM17->CH1的输入捕获模式去捕获生成的PWM信号 然后去进行计算 先算出周期 后面再算出占空比

// TIM2_Init
htim2.Instance = TIM2;
htim2.Init.Prescaler = 40-1;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 1000-1;

//TIM17_Init
htim17.Instance = TIM17;
htim17.Init.Prescaler = 80-1;
htim17.Init.CounterMode = TIM_COUNTERMODE_UP;
htim17.Init.Period = 65530-1;

//main.c
/*打开PWM输出 */
	HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);
	HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_3);
  __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 500);
  __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, 800);
	/*开启定时器*/
	HAL_TIM_IC_Start_IT(&htim17,TIM_CHANNEL_1);
在TIM2初始化中我们选择为PWM模式,其中分频为40-1 其频率为2000000HZ(2000kh 2MHZ) 自动重装载值为1000-1 那么其周期为2000kHZ/1000=2000hz 2khz 在下面我们设置了PWM的比较值,那么其占空比为CH2 50% CH3 80%

在TIM17初始化中我们设置了分频为80-1 即 1000000(1000khz 1Mhz) 计数上限为65530,那么他溢出的时间为65530/1Mhz = 6.553*10^-2s ≈65ms 上面我们的周期为2000HZ = 0.5ms 也就是说我们的采样在65ms后才溢出 但是我们需要捕获的信号在5ms就已经溢出了,所以我们的捕获频率可以捕获到信号

在设置好输出的PWM和输入捕获的定时器 后,我们就可以调用中断去实现捕获

extern TIM_HandleTypeDef htim17;
uint32_t fre,CapValue;

/*输入捕获的回调函数 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
  if(htim->Instance == TIM17){
    CapValue = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);

    TIM17->CNT = 0;
    /*计算频率 */
    fre = 1000000/(CapValue+1);  
    
  }
}
我们在初始化函数中设置的是高电平捕获,那么捕获到高电平时就会进入这个回调函数,在回调函数中读取当前的计数值,然后将其清零,那么从定时器独到的数据值再乘以计数周期就是PWM信号的周期
  while (1)
  {
    /* USER CODE END WHILE */
	HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_10);
		HAL_Delay(500);
    memset(lcd_string,0,sizeof(lcd_string));
    sprintf(lcd_string,(char *)"fre:%d K\r\n",fre);
    HAL_UART_Transmit(&huart1,lcd_string,strlen(lcd_string),0xff);
    // LCD_DisplayStringLine(Line5,lcd_string);
    /* USER CODE BEGIN 3 */
  }
在主函数中我们打印获取并计算后得到的周期值

实验结果如下

通过逻辑分析仪抓到的PWM信号周期为2khz占空比为80% 符合设置

串口的打印也为200kHZ

捕获占空比

捕获占空比的示意图如下所示,定义一个计数器去记录当前捕获的次数 ,首先设置捕获上升沿,在捕获到第一个上升沿时记录其值为Cap1,然后立马设置其捕获下降沿,捕获到下降沿是cnt=2 记录当前的值为cap2,此时cnt++ cnt=3 设置捕获上升沿,捕获到上升沿时记录其值为cap3,此时cnt++ cnt=4 然后将cnt=0 开启下一轮

这里我们需要记录捕获的值CapVlue1 CapVlue2 CapVlue3 记录进入捕获的次数 cnt和记录第几次捕获上升沿的RaiseCnt;

代码如下

extern  uint32_t  fre;
extern uint8_t cap_cnt;
extern uint32_t CapValue1;
extern uint32_t CapValue2;
extern uint32_t CapValue3;
extern uint8_t capRaise_cnt;
float high_duty=0.0;

while(1)
{
    
    switch (cap_cnt){
    case 0:
    cap_cnt++;
      __HAL_TIM_SET_CAPTUREPOLARITY(&htim17, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
      HAL_TIM_IC_Start_IT(&htim17, TIM_CHANNEL_1);	//启动输入捕获       或者: __HAL_TIM_ENABLE(&htim5);
      break;
    case 4:
      capRaise_cnt = 0;
      cap_cnt = 0;  //清空标志位
     
    break;

    }
    if(CapValue2-CapValue1 < 0){

    }
    else{
        /*计算周期 */
        fre = 1000000/(CapValue3-CapValue1);
        high_duty=(float)(CapValue2-CapValue1)/(CapValue3-CapValue1);
        high_duty *= 100;
        memset(lcd_string,0,sizeof(lcd_string));
        sprintf(lcd_string,(char *)"fre:%d high_duty:%.2f cap1:%d cap2:%d  \r\n",fre,high_duty,CapValue2-CapValue1,CapValue3-CapValue1);
        HAL_UART_Transmit(&huart1,lcd_string,strlen(lcd_string),0xff);
    }
   
    HAL_Delay(100);
}
extern TIM_HandleTypeDef htim17;
uint32_t fre,CapValue1,CapValue2,CapValue3;
uint8_t cap_cnt=0;
uint8_t capRaise_cnt=0;

/*输入捕获的回调函数 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
  if(htim->Instance == TIM17){
    switch(cap_cnt){
    case 1:
    if(capRaise_cnt==1){
      //第二次上升沿捕获
      CapValue3 = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);//获取当前的捕获值.
      TIM17->CNT = 0;
      
    }
    else{
      CapValue1 = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);//获取当前的捕获值.
    }
      __HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1,TIM_ICPOLARITY_FALLING);  //设置为下降沿捕获
      cap_cnt++;
      capRaise_cnt++;
      break;
    case 2:
      CapValue2 = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);//获取当前的捕获值.
      __HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1,TIM_ICPOLARITY_RISING);  //设置为上升沿捕获
      cap_cnt++;

      break;
    case 3:
      CapValue3 = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);//获取当前的捕获值.
      __HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1,TIM_ICPOLARITY_RISING);  //设置为上升沿捕获
      cap_cnt++;

      break;
    }
    

    
    /*计算频率 */
    // fre = 1000000/(CapValue+1);  
    
  }
}
/* USE
效果演示

存在一个bug 记录的值为负值,可能是中间取值不规范 待解决

XL555定时器捕获 其接在了A15(TIM2->CH1) B4(TIM3->CH1)上面 通过配置定时器 的值来捕获,其中TIM2定时器计数值为32位 最大为0xffffffff TIM3为16位最大为0xffff


TIM_HandleTypeDef htim2;
TIM_HandleTypeDef htim3;

/* TIM2 init function */
void MX_TIM2_Init(void)
{

  /* USER CODE BEGIN TIM2_Init 0 */

  /* USER CODE END TIM2_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_IC_InitTypeDef sConfigIC = {0};

  /* USER CODE BEGIN TIM2_Init 1 */

  /* USER CODE END TIM2_Init 1 */
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 8-1;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 0xffffffff-1;//32BIT
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_IC_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
  sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
  sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
  sConfigIC.ICFilter = 0;
  if (HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM2_Init 2 */

  /* USER CODE END TIM2_Init 2 */

}
/* TIM3 init function */
void MX_TIM3_Init(void)
{

  /* USER CODE BEGIN TIM3_Init 0 */

  /* USER CODE END TIM3_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_IC_InitTypeDef sConfigIC = {0};

  /* USER CODE BEGIN TIM3_Init 1 */

  /* USER CODE END TIM3_Init 1 */
  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 8-1;
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 0xffff-1;
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_IC_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
  sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
  sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
  sConfigIC.ICFilter = 0;
  if (HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM3_Init 2 */

  /* USER CODE END TIM3_Init 2 */

}

uint32_t cap1,fre1;
uint32_t cap2,fre2;
/* USER CODE BEGIN 1 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == TIM2)
	{
		cap1 = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
		htim->Instance->CNT = 0;
		fre1  = 1000000/cap1;
		HAL_TIM_IC_Start_IT(htim,TIM_CHANNEL_1);
	}
	if(htim->Instance == TIM3){
		
		cap2 = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
		htim->Instance->CNT = 0;
		fre2  = 1000000/cap2;
		HAL_TIM_IC_Start_IT(htim,TIM_CHANNEL_1);
	}
	
}
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1);

 while (1)
  {
    /* USER CODE END WHILE */
	HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_10);
	
		  memset(sendbuf,0,sizeof(sendbuf));
	  sprintf(sendbuf,"fre1:%d fre2:%d\r\n",fre1,fre2);
	  HAL_UART_Transmit(&huart1,sendbuf,strlen(sendbuf),0xff);
	  HAL_Delay(100);
    /* USER CODE BEGIN 3 */
  }

实验结果

通过串口打印其捕获到的值为4255HZ 5780HZ 与逻辑分析仪抓到的基本 一致

DAC


网站公告

今日签到

点亮在社区的每一天
去签到