STM32 Keil C 软件调试 TIM3 重映射 PB5 AFIO复用功能模块 PWM 输出高低电平控制

文章介绍了在STM32开发中,如何使用魔术棒进行调试设置,包括选择Simulator,修改DialogDLL参数为DARMSTM.DLL及-pSTM32F103ZE。接着,文章详细阐述了使能GPIO外设和AFIO复用功能模块的时钟,并进行了TIMER3的部分重映射,将TIM3_CH2映射到PB5,同时提到了PWM模式的配置。

1.  点击魔术棒

2. Debug-> Use Simulator

修改 Dialog DLL:    "DARMSTM.DLL"

修改Parameter:          "-pSTM32F103ZE" 

3. 回到主程序, 点击debug

4. 点击 Logic Analyzer

 

GPIO外设   和   AFIO复用功能模块  时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB  | RCC_APB2Periph_AFIO, ENABLE);  //使能GPIO外设和AFIO复用功能模块时钟

TIMER3 重映射:

 GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射  TIM3_CH2->PB5    

TIM_OCMode_PWM1

TIM_OCMode_PWM2   

<think> 我们需要完成一个复杂的嵌入式项目,涉及多个外设:TIM3TIM4、USART1、SysTick、GPIO(按键和LED)以及中断。 根据要求,我们需要完成以下任务: 1. 使用TIM3的CH2重映射PB5输出PWM(呼吸灯效果,占空比按0.5+0.5*sin(2*pi*f*t)变化)。 2. TIM4的CH1映射到PB6,用于输入捕获,测量PB5输出PWM占空比,并通过USART1发送到PC。 3. 正弦函数的频率f由串口接收,定时由TIM6/TIM7/SysTick中断实现(这里选择TIM6,因为要求用中断方式)。 4. 按键KEY0(PE4)控制呼吸灯的启动和停止(toggle)。 5. 按键KEY1(PE3)控制PE5引脚LED的状态切换(常亮和闪烁),闪烁频率由串口设定,使用SysTick控制(注意:SysTick已经用于系统时钟,我们可以用其作为延时,但闪烁控制我们可以用状态机在SysTick中断中处理,或者用另一个定时器。但题目要求闪烁频率由串口设定,且由SysTick控制,因此我们可以在SysTick中断中计数控制闪烁。然而,题目要求使用TIM6/TIM7/SysTick定时中断,并且我们选择了TIM6用于正弦波的定时,所以LED闪烁我们可以用SysTick的中断来计数。注意:SysTick中断频率较高,通常为1ms一次,我们可以通过软件计数实现不同频率的闪烁)。 6. 串口调试软件发送频率值(两个频率:一个是呼吸灯正弦波的频率,一个是LED闪烁的频率?注意题目要求:函数0.5+0.5sine(2*pi*f*t)的频率值由串口发送,以及LED闪烁频率由串口发送。这里有两个不同的频率需要接收)。 具体实施步骤: 一、初始化部分(使用寄存器编程): 1. GPIO初始化: - PB5: TIM3_CH2重映射(需要查看重映射表,TIM3_CH2默认在PA7,部分重映射PB5,所以需要部分重映射)-> 复用推挽输出 - PB6: TIM4_CH1(默认是PB6,不用重映射)-> 浮空输入或复用输入(因为要捕获) - PE4, PE3: 按键输入(设置为输入,上拉或下拉,根据原理图,正点原子精英板KEY0和KEY1是低电平有效,所以设置为上拉输入) - PE5: LED(推挽输出) - USART1_TX(PA9), USART1_RX(PA10): 复用推挽输出和浮空输入 2. 定时器初始化: a. TIM3: 用于输出PWM(呼吸灯) - 时基初始化:ARR和PSC,根据系统时钟设置,比如72MHz,PSC=71,则计数器频率为1MHz,这样ARR可以设置为999,则PWM频率为1kHz。 - 通道2配置为PWM模式1,使能预装载,设置CCR2初始值,使能输出。 - 使能TIM3。 b. TIM4: 用于输入捕获(测量PWM占空比) - 时基初始化:同样1MHz计数频率,ARR设置为0xFFFF(最大)。 - 通道1配置为输入捕获模式,上升沿捕获,开启捕获中断。 - 使能TIM4和中断。 c. TIM6: 用于产生定时中断,更新正弦波函数的参数t(即定时中断周期就是我们的时间步长dt)。注意:正弦波的频率由串口给定,所以我们需要在定时中断中更新角度(2*pi*f*t),然后计算占空比,并更新TIM3->CCR2。 - 时基初始化:比如我们希望中断频率为100Hz(即每10ms中断一次),则如果系统时钟72MHz,PSC=7199,则计数器频率为10kHz,ARR=99,则每100个计数周期中断一次(即10ms中断一次,100Hz)。 - 开启更新中断,使能TIM6。 3. USART1初始化: - 波特率设置(如115200),数据位8,停止位1,无校验。 - 使能接收中断(用于接收串口数据)。 - 使能USART1。 4. SysTick初始化: - 系统滴答定时器通常用于操作系统的心跳,这里我们也可以用来做LED闪烁的定时。但注意,题目要求LED闪烁由SysTick控制,而正弦波的定时由TIM6(我们选择了TIM6)完成。所以SysTick我们配置为1ms中断一次,然后在其中断服务函数中,我们可以对LED闪烁进行计时(如果LED处于闪烁状态)。 5. NVIC初始化: - 设置中断优先级:TIM6中断、TIM4捕获中断、USART1中断、SysTick中断、外部中断(按键)或者用扫描方式?题目要求按键控制,我们可以用外部中断,也可以用定时扫描。这里为了简化,我们使用外部中断(EXTI)来处理按键。 - 注意:按键KEY0(PE4)和KEY1(PE3)分别连接到EXTI4和EXTI3。需要配置EXTI和AFIO。 6. 按键外部中断初始化: - 配置EXTI3和EXTI4为下降沿触发(因为按键按下时低电平,所以下降沿表示按下)。 - 配置NVIC,使能EXTI3和EXTI4的中断。 二、程序流程: 1. 主循环:可以空循环,或者处理一些其他任务,主要工作都在中断中。 2. 中断服务函数: a. TIM6中断:用于更新PWM占空比(呼吸灯) - 计算当前时间t(可以用一个全局变量累加定时中断周期,比如每次中断t增加0.01s,如果中断周期是10ms的话) - 计算占空比:duty = 0.5 + 0.5 * sin(2 * pi * f * t); 其中f是正弦波的频率(由串口接收) - 将占空比转换为CCR值:CCR = duty * (TIM3_ARR+1) (注意:TIM3_ARR我们设置为999,所以占空比范围0~100%,CCR范围0~999) - 更新TIM3->CCR2 = CCR; b. TIM4捕获中断:测量PB5输出PWM占空比 - 在捕获中断中,记录上升沿和下降沿的时间,计算高电平时间,从而得到占空比。 - 将占空比通过串口发送(可以每捕获一个周期发送一次)。 c. USART1中断:接收PC发来的数据 - 接收数据,解析出两个频率(需要定义协议,例如:两个频率用逗号分隔,或者分两次发送,或者用特定格式)。这里我们简单处理:假设串口发送的数据格式为“f1,f2”,其中f1是呼吸灯正弦波频率(Hz),f2是LED闪烁频率(Hz)。 - 将f1赋值给全局变量sine_freq,用于TIM6中断中计算正弦波。 - 将f2赋值给全局变量blink_freq,用于控制LED闪烁。注意:LED闪烁频率为f2,那么闪烁周期为1/f2秒,即亮0.5/f2秒,灭0.5/f2秒(或者按占空比50%)。我们可以在SysTick中断中计数实现。 d. SysTick中断(1ms一次): - 如果LED处于闪烁模式,则使用一个计数器,根据blink_freq计算半周期(半周期=500/f2 毫秒),然后计数,达到半周期则翻转LED。 e. 按键外部中断(EXTI3和EXTI4): - EXTI4(KEY0):控制呼吸灯的启动和停止。设置一个全局标志pwm_enable,每次按下取反。如果pwm_enable为真,则使能TIM3TIM6;否则关闭TIM3TIM6(或者停止TIM6,同时将TIM3的CCR设为0,让灯灭)。 - EXTI3(KEY1):控制LED(PE5)的状态。设置一个全局变量led_mode,每次按下切换:0表示常亮,1表示闪烁。在中断中改变这个状态,然后在主循环或SysTick中断中根据这个状态设置LED。注意:如果切换到常亮,则直接点亮LED;如果切换到闪烁,则开始闪烁(由SysTick中断控制)。 三、注意事项: 1. 重映射TIM3_CH2到PB5:需要查看重映射寄存器,TIM3的CH2部分重映射PB5AFIO_MAPR寄存器,设置TIM3_REMAP[1:0]=10,即部分重映射)。 2. 输入捕获的配置:TIM4_CH1用于捕获PB6的PWM信号,需要配置为输入捕获模式,并开启中断。 3. 串口接收数据:需要设计简单的协议,这里我们假设串口发送的是两个浮点数(用逗号分隔),然后我们解析它们。注意:串口接收的是字符串,需要转换成浮点数。 4. 浮点数运算:在中断中做浮点运算可能会占用较多时间,所以尽量简化。也可以考虑使用查表法或者定点数运算。 由于代码量较大,我们分模块实现。这里只给出关键部分的代码,并尽量按照要求:初始化部分用寄存器,其他用库函数(但题目要求初始化函数用寄存器,其他位置用标准库。所以初始化部分我们将用寄存器操作,而中断服务函数等使用库函数来操作寄存器,或者混合使用)。 由于我们使用标准外设库(虽然题目要求初始化用寄存器,但其他部分用库函数,所以我们可以使用库函数来操作外设),但初始化部分我们按照要求用寄存器写。 注意:正点原子精英板的系统时钟通常是72MHz。 下面我们开始编写代码: 步骤1:初始化系统时钟(使用库函数或寄存器,但通常用库函数配置,这里我们假设系统时钟已经配置为72MHz) 步骤2:初始化各个外设(用寄存器) 由于寄存器初始化代码比较繁琐,我们只给出关键寄存器的配置。 一、GPIO初始化(寄存器): 1. 使能GPIO和AFIO时钟:RCC->APB2ENR |= (1<<0) | (1<<2) | (1<<3) | (1<<5); // AFIO, GPIOA, GPIOB, GPIOE 2. 配置PB5复用推挽输出50MHz): GPIOB->CRL &= ~(0xF << 20); // 清除原来的设置(PB5在CRL的20-23位) GPIOB->CRL |= (0xB << 20); // 复用推挽输出,最大速度50MHz 3. 配置PB6为浮空输入(因为TIM4_CH1默认是PB6,不需要重映射): GPIOB->CRL &= ~(0xF << 24); GPIOB->CRL |= (0x4 << 24); // 浮空输入 4. 配置PE3、PE4为上拉输入: // PE3, PE4 GPIOE->CRL &= ~(0xFF << 12); // 清除PE3(12-15), PE4(16-19) GPIOE->CRL |= (0x8 << 12) | (0x8 << 16); // 输入模式,上拉/下拉 GPIOE->ODR |= (1<<3) | (1<<4); // 上拉 5. 配置PE5为推挽输出50MHz): GPIOE->CRL &= ~(0xF << 20); GPIOE->CRL |= (0x3 << 20); // 推挽输出,最大速度50MHz 6. 配置PA9(USART1_TX)复用推挽输出,PA10(USART1_RX)浮空输入: // PA9: 复用推挽输出 GPIOA->CRH &= ~(0xF << 4); GPIOA->CRH |= (0xB << 4); // PA10: 浮空输入 GPIOA->CRH &= ~(0xF << 8); GPIOA->CRH |= (0x4 << 8); 二、重映射TIM3_CH2到PB5(部分重映射): // 部分重映射TIM3_CH2->PB5, 查表得知:TIM3_REMAP[1:0] = 10 是部分重映射 AFIO->MAPR &= ~(0x3 << 10); // 清除TIM3_REMAP位 AFIO->MAPR |= (0x2 << 10); // 部分重映射 三、TIM3初始化(寄存器): 1. 使能TIM3时钟:RCC->APB1ENR |= 1<<1; 2. 设置TIM3的ARR和PSC: TIM3->ARR = 999; // 自动重装载值 TIM3->PSC = 71; // 预分频,72MHz/(71+1)=1MHz,计数周期1us,则PWM周期为1000us=1ms,频率1kHz 3. 设置通道2为PWM模式1: // 捕获/比较模式寄存器1(CCMR1),因为通道2是CCR2,所以使用CCMR1的8-15TIM3->CCMR1 &= ~(0x7 << 12); // 清除OC2M位 TIM3->CCMR1 |= (0x6 << 12); // PWM模式1 TIM3->CCMR1 |= (1<<11); // 输出比较2预装载使能 // 使能输出 TIM3->CCER |= (1<<4); // 使能CC2输出 // 设置初始占空比 TIM3->CCR2 = 0; // 初始为0 // 使能定时器 TIM3->CR1 |= (1<<0); // 使能计数器 四、TIM4初始化(用于输入捕获,寄存器): 1. 使能TIM4时钟:RCC->APB1ENR |= 1<<2; 2. 设置TIM4的ARR和PSC: TIM4->ARR = 0xFFFF; // 最大 TIM4->PSC = 71; // 计数频率1MHz,计数周期1us 3. 设置通道1为输入捕获模式: // CC1S=01,通道1配置为输入,IC1映射到TI1上(默认) TIM4->CCMR1 &= ~(0x3 << 0); TIM4->CCMR1 |= (0x1 << 0); // 设置输入捕获分频(不分频),滤波(不滤波) TIM4->CCMR1 &= ~(0xF << 4); // 不分频,不滤波 // 设置捕获边沿:上升沿 TIM4->CCER &= ~(0x2 << 0); // 捕获边沿:0表示上升沿,1表示下降沿(CC1P位) TIM4->CCER |= (1<<0); // 使能捕获 // 使能捕获中断 TIM4->DIER |= (1<<1); // 使能CC1IE 4. 使能定时器: TIM4->CR1 |= (1<<0); 五、TIM6初始化(寄存器): 1. 使能TIM6时钟:RCC->APB1ENR |= 1<<4; 2. 设置TIM6的ARR和PSC: TIM6->ARR = 99; // 100个计数周期中断一次 TIM6->PSC = 7199; // 72MHz/(7199+1)=10kHz,计数周期0.1ms,ARR=99,则中断周期10ms(100Hz) 3. 使能更新中断: TIM6->DIER |= (1<<0); // 使能更新中断 // 设置中断优先级(在NVIC中设置,后面统一设置) 4. 使能定时器: TIM6->CR1 |= (1<<0); // 使能计数器,但先不开启(由按键控制) 六、USART1初始化(寄存器): 1. 使能USART1时钟:RCC->APB2ENR |= 1<<14; 2. 设置波特率115200: // 波特率=72MHz/(16*USARTDIV) = 115200 -> USARTDIV=39.0625 // 所以BRR=39.0625*16=625,即0x271 USART1->BRR = 0x271; 3. 设置数据格式:8位数据,1位停止位,无校验: USART1->CR1 &= ~(1<<12); // 8位数据 USART1->CR2 &= ~(3<<12); // 1位停止位 4. 使能发送和接收,以及接收中断: USART1->CR1 |= (1<<2) | (1<<3) | (1<<5); // 使能发送、接收、接收中断 5. 使能USART1: USART1->CR1 |= (1<<13); 七、SysTick初始化(使用库函数,因为通常用库函数初始化,但题目要求初始化用寄存器,我们这里用寄存器): SysTick是内核外设,寄存器定义在core_cm3.h中,我们直接使用。 配置为1ms中断一次: SysTick->LOAD = 72000 - 1; // 72MHz/1000=72kHz,所以计数72000次为1ms,但实际是72000/72MHz=0.001s SysTick->VAL = 0; // 清空计数器 SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; // 使用内核时钟,使能中断,使能计数器 八、按键外部中断(EXTI)初始化(寄存器): 1. 配置EXTI3和EXTI4为下降沿触发: EXTI->FTSR |= (1<<3) | (1<<4); // 下降沿触发 2. 配置EXTI3和EXTI4的中断屏蔽: EXTI->IMR |= (1<<3) | (1<<4); 3. 配置AFIO,将EXTI3和EXTI4连接到PE3和PE4: // 配置EXTI3和EXTI4的映射 // 每个EXTI线需要配置4个位,在AFIO_EXTICR1和AFIO_EXTICR2中 // EXTI3: 配置AFIO_EXTICR1[15:12] = 0100 (PE3) AFIO->EXTICR[0] &= ~(0xF << 12); // 清除 AFIO->EXTICR[0] |= (0x4 << 12); // PE3 // EXTI4: 配置AFIO_EXTICR1[19:16] = 0100 (PE4) AFIO->EXTICR[1] &= ~(0xF << 0); // 注意:EXTI4在AFIO_EXTICR1的[19:16],但该寄存器是32位,我们按数组索引,EXTICR[1]对应EXTI4~7 AFIO->EXTICR[1] |= (0x4 << 0); // PE4 4. 配置NVIC,使能EXTI3和EXTI4中断: // 在NVIC中,EXTI3的中断号是9,EXTI4的中断号是10 NVIC_EnableIRQ(EXTI3_IRQn); NVIC_EnableIRQ(EXTI4_IRQn); // 设置优先级(这里默认优先级,可以省略) 九、配置其他中断的优先级(在NVIC中): TIM6_DAC_IRQn(TIM6中断)、TIM4_IRQn(TIM4中断)、USART1_IRQn(串口中断)等。 由于代码量很大,我们这里只给出框架,具体实现请根据实际情况调整。 下面给出部分代码,注意:由于要求初始化用寄存器,而其他部分用库函数,所以中断服务函数中我们使用库函数来操作寄存器(实际上就是混合编程)。 由于全部代码太长,这里只给出关键部分的中断服务函数: 1. TIM6中断服务函数(更新中断): ```c void TIM6_DAC_IRQHandler(void) { if (TIM6->SR & TIM_SR_UIF) { // 检查中断标志 TIM6->SR &= ~TIM_SR_UIF; // 清除中断标志 // 更新时间t,每次增加0.01秒(因为10ms中断一次) static float t = 0.0f; t += 0.01f; // 计算占空比 float duty = 0.5f + 0.5f * sin(2 * M_PI * sine_freq * t); // 设置CCR2,注意TIM3的ARR是999 uint16_t ccr = (uint16_t)(duty * 999); TIM3->CCR2 = ccr; } } ``` 2. TIM4捕获中断服务函数: ```c // 全局变量用于捕获 volatile uint16_t tim4_rising_val = 0; volatile uint16_t tim4_falling_val = 0; volatile uint8_t tim4_capture_state = 0; // 0:等待上升沿,1:已捕获上升沿,等待下降沿 void TIM4_IRQHandler(void) { if (TIM4->SR & TIM_SR_CC1IF) { // 捕获1事件 if (tim4_capture_state == 0) { // 上升沿 tim4_rising_val = TIM4->CCR1; tim4_capture_state = 1; // 改变为下降沿捕获 TIM4->CCER &= ~TIM_CCER_CC1P; // 设置为下降沿捕获 } else if (tim4_capture_state == 1) { // 下降沿 tim4_falling_val = TIM4->CCR1; tim4_capture_state = 0; // 计算占空比 uint32_t high_time = tim4_falling_val - tim4_rising_val; // 高电平时间(us) // 周期:由于PWM频率1kHz,周期1000us,所以占空比=high_time/1000.0 float duty = high_time / 1000.0f; // 通过串口发送占空比 char buffer[50]; sprintf(buffer, "Duty: %.2f%%\r\n", duty*100); USART_SendString(USART1, buffer); // 改回上升沿捕获 TIM4->CCER |= TIM_CCER_CC1P; // 设置为上升沿捕获 } } } ``` 3. 串口中断服务函数: ```c // 假设接收协议:发送两个浮点数,用逗号分隔,以换行结束(例如 "1.0,2.0\r\n") #define MAX_RX_LEN 50 char rx_buffer[MAX_RX_LEN]; uint8_t rx_index = 0; void USART1_IRQHandler(void) { if (USART1->SR & USART_SR_RXNE) { char c = USART1->DR; if (c == &#39;\r&#39; || c == &#39;\n&#39;) { if (rx_index > 0) { rx_buffer[rx_index] = &#39;\0&#39;; // 解析字符串,格式为"f1,f2" float f1, f2; if (sscanf(rx_buffer, "%f,%f", &f1, &f2) == 2) { sine_freq = f1; blink_freq = f2; // 计算半周期(毫秒)用于LED闪烁,注意:闪烁频率f2,则半周期=500/f2 毫秒 // 我们在SysTick中每毫秒计数,所以半周期计数 = 500/f2 // 注意:如果f2为0,则不闪烁(但我们有常亮和闪烁模式切换,所以这里只更新全局变量) } rx_index = 0; } } else { if (rx_index < MAX_RX_LEN-1) { rx_buffer[rx_index++] = c; } else { // 缓冲区满,丢弃 rx_index = 0; } } } } ``` 4. SysTick中断服务函数(在core_cm3.h中已经定义,但我们需要重写): 注意:SysTick中断服务函数名为SysTick_Handler(标准名称) ```c // 全局变量: volatile uint32_t systick_count = 0; volatile uint32_t blink_half_period = 0; // 半周期(毫秒) volatile uint32_t blink_counter = 0; volatile uint8_t blink_state = 0; // 0:灭,1:亮 void SysTick_Handler(void) { systick_count++; // 如果LED处于闪烁模式 if (led_mode == 1) { // 1表示闪烁模式 if (blink_half_period > 0) { blink_counter++; if (blink_counter >= blink_half_period) { blink_counter = 0; blink_state = !blink_state; GPIO_WriteBit(GPIOE, GPIO_Pin_5, (BitAction)blink_state); } } } } ``` 5. 按键外部中断服务函数: ```c // KEY0: PE4 -> EXTI4 // KEY1: PE3 -> EXTI3 void EXTI3_IRQHandler(void) { if (EXTI->PR & (1<<3)) { EXTI->PR = (1<<3); // 清除中断标志 // 按键KEY1,切换LED模式 led_mode = 1 - led_mode; // 0和1切换 if (led_mode == 0) { // 常亮模式,直接点亮 GPIO_SetBits(GPIOE, GPIO_Pin_5); } else { // 闪烁模式,初始化计数器 blink_counter = 0; blink_state = 0; // 初始为灭 blink_half_period = (uint32_t)(500.0f / blink_freq); // 半周期(毫秒),注意:blink_freq由串口设置,如果为0则不会闪烁 // 如果blink_freq为0,则blink_half_period会很大(除0问题,需要避免) if (blink_freq == 0) blink_freq = 1; // 防止除0,在串口接收时处理 } } } void EXTI4_IRQHandler(void) { if (EXTI->PR & (1<<4)) { EXTI->PR = (1<<4); // 清除中断标志 // 按键KEY0,切换呼吸灯使能 pwm_enable = !pwm_enable; if (pwm_enable) { // 使能TIM6和TIM3 TIM6->CR1 |= TIM_CR1_CEN; TIM3->CR1 |= TIM_CR1_CEN; } else { TIM6->CR1 &= ~TIM_CR1_CEN; TIM3->CR1 &= ~TIM_CR1_CEN; // 同时把灯灭掉 TIM3->CCR2 = 0; } } } ``` 注意:以上代码只是示例,实际应用中需要考虑临界区保护(全局变量的访问),以及错误处理。 另外,全局变量定义: ```c float sine_freq = 1.0f; // 正弦波频率,默认1Hz float blink_freq = 1.0f; // 闪烁频率,默认1Hz uint8_t pwm_enable = 0; // 呼吸灯使能标志,0:停止,1:启动 uint8_t led_mode = 0; // 0:常亮,1:闪烁 ``` 由于使用了浮点数和sine函数,需要在工程设置中勾选使用FPU(如果芯片有FPU),并且包含math.h。对于STM32F103,没有FPU,所以浮点运算会慢,可以考虑用查表法或整数运算近似。 最后,初始化代码(寄存器)和中断服务函数需要放在相应的文件中(如stm32f10x_it.c等)。 由于篇幅限制,无法给出完整代码,但以上框架应该可以指导实现。 注意:在串口接收解析时,如果接收到的字符串无法解析,需要做错误处理。另外,在设置blink_half_period时,如果blink_freq太小(接近0)会导致半周期很大,同样如果blink_freq很大(例如1000Hz),则半周期为0.5ms,而SysTick是1ms一次,这样精度不够。所以当blink_freq很大时,闪烁可能不准确。可以调整SysTick中断频率(例如10kHz)来获得更高精度,但这样会增加系统负担。或者使用另一个定时器专门控制LED闪烁。 根据题目要求,LED闪烁由SysTick控制,所以我们只能接受这个精度限制。 以上代码仅供参考,实际调试中可能需要调整。 由于要求使用正点原子精英板,请根据实际板子的连接调整(例如LED和按键的引脚)。 最后,提出相关问题: </think>### 解决方案 以下是基于STM32F103ZE正点原子精英板的完整实现方案,满足所有要求。代码分为初始化(寄存器编程)和主逻辑(标准库函数)两部分。 #### 关键全局变量定义 ```c #include "stm32f10x.h" #include <math.h> #include <stdio.h> // 全局变量 volatile float sine_freq = 1.0f; // 正弦波频率 (Hz) volatile float blink_freq = 1.0f; // LED闪烁频率 (Hz) volatile uint8_t pwm_enable = 0; // PWM输出使能标志 volatile uint8_t led_mode = 0; // LED模式 (0: 常亮, 1: 闪烁) volatile float t = 0.0f; // 时间累积量 volatile uint32_t systick_count = 0; // SysTick计数器 volatile uint32_t blink_half_period = 500; // 闪烁半周期(ms) ``` #### 1. 初始化函数(寄存器编程) ```c void GPIO_Init(void) { // 使能时钟 RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPEEN | RCC_APB2ENR_AFIOEN; // PB5 (TIM3_CH2重映射) - 复用推挽输出 GPIOB->CRL &= ~(0xF << (5*4)); // 清除设置 GPIOB->CRL |= (0xB << (5*4)); // 50MHz 复用推挽 // PB6 (TIM4_CH1) - 浮空输入 GPIOB->CRL &= ~(0xF << (6*4)); GPIOB->CRL |= (0x04 << (6*4)); // 浮空输入 // PE4 (KEY0), PE3 (KEY1) - 上拉输入 GPIOE->CRL &= ~(0xFF << (3*4)); GPIOE->CRL |= (0x88 << (3*4)); // 输入模式 GPIOE->ODR |= (1<<3) | (1<<4); // 上拉 // PE5 (LED) - 推挽输出 GPIOE->CRL &= ~(0xF << (5*4)); GPIOE->CRL |= (0x03 << (5*4)); // 50MHz 推挽 // PA9 (USART1_TX) - 复用推挽输出 GPIOA->CRH &= ~(0xF << (9-8)*4); GPIOA->CRH |= (0x0B << (9-8)*4); // PA10 (USART1_RX) - 浮空输入 GPIOA->CRH &= ~(0xF << (10-8)*4); GPIOA->CRH |= (0x04 << (10-8)*4); } void TIM3_Init(void) { // 使能TIM3时钟 RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; // 重映射TIM3_CH2到PB5 AFIO->MAPR &= ~AFIO_MAPR_TIM3_REMAP; AFIO->MAPR |= AFIO_MAPR_TIM3_REMAP_PARTIALREMAP; // 时基配置: 1kHz PWM TIM3->PSC = 72 - 1; // 72MHz/72 = 1MHz TIM3->ARR = 1000 - 1; // 1MHz/1000 = 1kHz // PWM模式 (通道2) TIM3->CCMR1 |= TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2M_2; // PWM模式1 TIM3->CCMR1 |= TIM_CCMR1_OC2PE; // 预装载使能 TIM3->CCER |= TIM_CCER_CC2E; // 输出使能 TIM3->CR1 |= TIM_CR1_ARPE; // ARR预装载 TIM3->EGR |= TIM_EGR_UG; // 更新寄存器 } void TIM4_Init(void) { // 使能TIM4时钟 RCC->APB1ENR |= RCC_APB1ENR_TIM4EN; // 时基配置 TIM4->PSC = 72 - 1; // 72MHz/72 = 1MHz TIM4->ARR = 0xFFFF; // 最大周期值 // 输入捕获配置 (通道1) TIM4->CCMR1 |= TIM_CCMR1_CC1S_0; // CC1通道配置为输入 TIM4->CCER |= TIM_CCER_CC1E; // 捕获使能 TIM4->DIER |= TIM_DIER_CC1IE; // 捕获中断使能 } void TIM6_Init(void) { // 使能TIM6时钟 RCC->APB1ENR |= RCC_APB1ENR_TIM6EN; // 时基配置: 100Hz中断 (10ms) TIM6->PSC = 7200 - 1; // 72MHz/7200 = 10kHz TIM6->ARR = 100 - 1; // 10kHz/100 = 100Hz // 中断使能 TIM6->DIER |= TIM_DIER_UIE; } void USART1_Init(void) { // 使能USART1时钟 RCC->APB2ENR |= RCC_APB2ENR_USART1EN; // 波特率115200 (72MHz时钟) USART1->BRR = 0x271; // 72MHz / (16 * 115200) ≈ 39.0625 // 配置: 8位数据, 1停止位, 无校验 USART1->CR1 &= ~USART_CR1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值