目录
板子结构一览
主控为 **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的串口发送非常简单,但是串口接收的方式多种多样
- 使用轮训的方式接收
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);//地址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之间的数 然后知道这个数了和定时器周期去相乘,就能得到时间,然后倒数一下就是频率。
推导如下:
- 定时计数器记一个数的频率等于系统时钟/分频 得到的是记一个数的频率
- 记一个数的时间就等于频率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;//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 与逻辑分析仪抓到的基本 一致