蓝桥杯嵌入式开发板结构分析及功能学习笔记
目录
- 板子结构一览
- 时钟源分析
- 74LS573锁存器
- 按键输入
- 滴答定时器 SysTick
- 串口收发
- LCD屏幕
- ADC采样
- AT24C02(EEPROM)
- 可编程电阻
- TIM定时器
- 输入捕获
- DAC
板子结构一览
主控为 **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 0memset(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);#endifGPIOC->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的串口发送非常简单,但是串口接收的方式多种多样
- 使用轮训的方式接收
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
- 使用串口中断的方式接收
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);}}
- 使用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);//地址0xA0I2CWaitAck();/*发送要读出数据的内部寄存器地址信息*/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. StartI2CStart();// 2. 发送设备地址(写模式) + ACK检查(设置内存地址)I2CSendByte(devAddr | 0); // 0xA0if (I2CWaitAck() != SUCCESS) return -1;// 3. 发送内存起始地址I2CSendByte(memAddr);if (I2CWaitAck() != SUCCESS) return -2;// 4. Repeated StartI2CStart();// 5. 发送设备地址(读模式)I2CSendByte(devAddr | 1); // 0xA1if (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(); // 读取并发送 ACKI2CSendAck();}}// 7. StopI2CStop();return 0;
}
AT24C02写多个字节
/*写入多个字节*/
int AT24C02_WriteMultiBytes(uint8_t devAddr, uint8_t memAddr, uint8_t *data, uint8_t len) {// 1. StartI2CStart();// 2. 发送设备地址(写模式) + ACK检查I2CSendByte(devAddr | 0); // 0xA0if (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. StopI2CStop();// 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之间的数 然后知道这个数了和定时器周期去相乘,就能得到时间,然后倒数一下就是频率。
推导如下:
- 定时计数器记一个数的频率等于系统时钟/分频 得到的是记一个数的频率
- 记一个数的时间就等于频率T0的倒数
- 两次捕获到上升沿 (一个周期T) 之间的计数器的数值差为CapValue
- 那么PWM的周期就为上升沿之间的数值差*计数周期即PWM的周期
- PWM的频率就为周期的倒数
- 总的公式
信号发生器的电路图,连接到了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;//32BIThtim2.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 与逻辑分析仪抓到的基本 一致