文章目录
前言
本程序设计是基于嵌入式开发板CT117E,stm32f103RBT6。如果对哪个模块的代码不理解可以点开我的博客查看各个模块的编写思路。
一、试题
二、需要用到的模块
1.LED
代码如下:led.c:
#include "led.h"
void led_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOC, ENABLE);
/* Configure PD0 and PD2 in output pushpull mode */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = 0xff00;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIOC->ODR |=0xff<<8;
GPIOD->ODR |=1<<2;
GPIOD->ODR &=~(1<<2);
}
void led_ctrl(u8 ledx,u8 status) //控制led的亮灭,ledx取值范围:8-15
{
if(status)
{
GPIOC->ODR &=~(1<<ledx);
GPIOD->ODR |=1<<2;
GPIOD->ODR &=~(1<<2);
}
else
{
GPIOC->ODR |=1<<ledx;
GPIOD->ODR |=1<<2;
GPIOD->ODR &=~(1<<2);
}
}
led.h:
#ifndef LED_H
#define LED_H
#include "stm32f10x.h"
void led_init(void);
void led_ctrl(u8 ledx,u8 status);
#endif
2.按键
代码如下:key.c:
#include "key.h"
void key_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
/* Configure PD0 and PD2 in output pushpull mode */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
key.h:
#include "key.h"
#ifndef KEY_H
#define KEY_H
#include "stm32f10x.h"
#define key1 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) //读取按键的状态
#define key2 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_8)
#define key3 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)
#define key4 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_8)
void key_init(void);
#endif
3.ADC
代码如下:adc.c:
#include "adc.h"
void adc_init(void)
{
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
/* Enable ADC1, ADC2 and GPIOC clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOB, ENABLE);
/* Configure PC.01, PC.02 and PC.04 (ADC Channel11, Channel12 and Channel14)
as analog input ----------------------------------------------------------*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOB, &GPIO_InitStructure);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
/* ADC2 regular channels configuration */
ADC_RegularChannelConfig(ADC1, ADC_Channel_8, 1, ADC_SampleTime_239Cycles5);
ADC_Cmd(ADC1, ENABLE);
/* Enable ADC1 reset calibration register */
ADC_ResetCalibration(ADC1);
/* Check the end of ADC1 reset calibration register */
while(ADC_GetResetCalibrationStatus(ADC1));
/* Start ADC1 calibration */
ADC_StartCalibration(ADC1);
/* Check the end of ADC1 calibration */
while(ADC_GetCalibrationStatus(ADC1));
}
u16 get_adc(void) //简单的滤波,求平均值,获取12位的adc值
{
int i;
u16 adc_buff[10];
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
for(i=0;i<10;i++)
{
adc_buff[i]=ADC_GetConversionValue(ADC1);
}
for(i=1;i<10;i++)
{
adc_buff[0] += adc_buff[i];
}
ADC_SoftwareStartConvCmd(ADC1, DISABLE);
return adc_buff[0]/10;
}
adc.h:
#ifndef ADC_H
#define ADC_H
#include "stm32f10x.h"
void adc_init(void);
u16 get_adc(void);
#endif
4.定时器3 产生pwm
代码如下:pwm.c:
#include "pwm.h"
u32 CH1_VAL,CH1_DUTY,CH2_VAL,CH2_DUTY;
void pwm_time3_init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
/* GPIOA clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* Enable the TIM3 global Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_TimeBaseStructure.TIM_Period = 65535;
TIM_TimeBaseStructure.TIM_Prescaler = 71;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
/* Output Compare Toggle Mode configuration: Channel1 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 65534;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
TIM_OC2Init(TIM3, &TIM_OCInitStructure);
TIM_Cmd(TIM3, ENABLE);
/* TIM IT enable */
TIM_ITConfig(TIM3, TIM_IT_CC1 | TIM_IT_CC2 , ENABLE);
}
void set_pwm(u32 ch1_val,u32 ch1_duty,u32 ch2_val,u32 ch2_duty)
{
CH1_VAL=1000000/ch1_val;
CH2_VAL=1000000/ch2_val;
CH1_DUTY=CH1_VAL*ch1_duty/100;
CH2_DUTY=CH2_VAL*ch2_duty/100;
TIM_SetCounter(TIM3,0);
TIM_SetCompare1(TIM3,0);
TIM_SetCompare2(TIM3,0);
}
u32 capture;
u8 ch1_flag=0;
u8 ch2_flag=0;
void TIM3_IRQHandler(void)
{
/* TIM3_CH1 toggling with frequency = 183.1 Hz */
if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_CC1 );
capture = TIM_GetCapture1(TIM3);
if(ch1_flag)
{
TIM_SetCompare1(TIM3, capture + CH1_DUTY );
}
else
{
TIM_SetCompare1(TIM3, capture + CH1_VAL - CH1_DUTY );
}
ch1_flag ^=1;
}
/* TIM3_CH2 toggling with frequency = 366.2 Hz */
if (TIM_GetITStatus(TIM3, TIM_IT_CC2) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_CC2);
capture = TIM_GetCapture2(TIM3);
if(ch2_flag)
{
TIM_SetCompare2(TIM3, capture + CH2_DUTY);
}
else
{
TIM_SetCompare2(TIM3, capture + CH2_VAL - CH2_DUTY );
}
ch2_flag ^=1;
}
}
pwm.h:
#ifndef PWM_H
#define PWM_H
#include "stm32f10x.h"
void pwm_time3_init(void);
void set_pwm(u32 ch1_val,u32 ch1_duty,u32 ch2_val,u32 ch2_duty);
#endif
5.定时器2 捕获pwm(不需要)
(手上没有测pwm的工具,用定时器2编写捕获程序进行捕获观察验证程序的正确性)
代码如下:capture.c:
#include "capture.h"
void capture_time2_init(void)
{
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
/* GPIOA clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = 65535;
TIM_TimeBaseStructure.TIM_Prescaler = 71;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x0;
TIM_ICInit(TIM2, &TIM_ICInitStructure);
TIM_Cmd(TIM2, ENABLE);
/* Enable the CC2 Interrupt Request */
TIM_ITConfig(TIM2, TIM_IT_CC2, ENABLE);
}
u8 ch2_mode=0;
u32 capture_val,capture_duty;
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_CC2) == SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_CC2);
switch(ch2_mode)
{
case 0: capture_val=0;
capture_duty=0;
TIM_SetCounter (TIM2,0);
TIM_OC2PolarityConfig (TIM2,TIM_OCPolarity_Low);
ch2_mode=1;
break;
case 1: capture_duty=TIM_GetCounter (TIM2);
TIM_OC2PolarityConfig (TIM2,TIM_OCPolarity_High);
ch2_mode=2;
break;
case 2: capture_val=TIM_GetCounter (TIM2);
TIM_OC2PolarityConfig (TIM2,TIM_OCPolarity_High);
ch2_mode=3;
break;
default :break;
}
}
}
capture.h:
#ifndef CAPTURE_H
#define CAPTURE_H
#include "stm32f10x.h"
extern u8 ch2_mode;
extern u32 capture_val,capture_duty;
void capture_time2_init(void);
#endif
三、主函数逻辑设计
思路流程图:
stm32f10x_it.c:
#include "stm32f10x_it.h"
#include "pwm.h"
extern u32 TimingDelay;
extern u8 key_flag;
extern u8 a6;
extern u8 a7;
extern u16 pwm_num;
void SysTick_Handler(void)
{
static u8 key_num=0;
TimingDelay--;
key_num++;
pwm_num--;
if(pwm_num==0)
{
set_pwm(100,a6,200,a7); //每500ms更新一下pwm波的参数,也就是0.5秒,题目要求是小于1秒响应
pwm_num=500;
}
if(key_num==50)
{
key_flag=1;
key_num=0;
}
}
main.c:
#include "stm32f10x.h"
#include "stdio.h"
#include "lcd.h"
#include "led.h"
#include "key.h"
#include "adc.h"
#include "pwm.h"
#include "capture.h"
u32 TimingDelay = 0;
u8 key_flag; //50ms按键扫描标志
u8 key1_num; //按键1的计数值,用于判断长按和短按,这个试题目前用不到
u8 key2_num; //按键2的计数值,用于判断长按和短按,这个试题目前用不到
u8 key3_num; //按键3的计数值,用于判断长按和短按,这个试题目前用不到
u8 key4_num; //按键4的计数值,用于判断长按和短按,这个试题目前用不到
u8 key1_flag=1; //按键1按下的标志 1:显示数据界面 0:显示参数界面
u8 key4_flag=1; //按键4按下的标志 1:自动模式 0:手动模式
u8 a6=10; //PA6占空比值, 范围: 1-100
u8 a7=10; //PA7占空比值, 范围: 1-100
float adc_val; //获取adc的12数据值
u8 buff[20]; //用于拷贝数据,进行显示
u16 pwm_num=500; //在定时器中重新修改pwm的占空比,达到分时复用的效果
void Delay_Ms(u32 nTime);
void key_read(void); //按键扫描函数
void led_show(void); //显示函数
//Main Body
int main(void)
{
SysTick_Config(SystemCoreClock/1000);
Delay_Ms(200);
STM3210B_LCD_Init();
LCD_Clear(Black);
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
led_init();
led_ctrl(8,1);
key_init();
adc_init();
pwm_time3_init();
set_pwm(100,a6,200,a7); //初始化pwm波
capture_time2_init();
while(1)
{
if(key_flag)
{
key_read();
adc_val=get_adc();
key_flag=0;
if(key4_flag)
{
a6=adc_val/0xfff*100; //占空比为adc的电压值/3.3 也就是12位的数据值除以0xfff
a7=adc_val/0xfff*100;
}
}
if(ch2_mode==3) //本试题没有要求,这里为的是验证我的程序是否正确所编写的pwm捕获程序
{
sprintf((char *)buff," VAL:%d ",1000000/capture_val);
LCD_DisplayStringLine(Line7,buff);
sprintf((char *)buff," DUTY:%d ",capture_duty*100/capture_val);
LCD_DisplayStringLine(Line8,buff);
ch2_mode=0;
}
led_show(); //界面显示
}
}
//
void Delay_Ms(u32 nTime)
{
TimingDelay = nTime;
while(TimingDelay != 0);
}
void key_read(void)
{
if(key1==0) //按键1
{
key1_num++;
if(key1_num==20) 按键长按1秒,试题没有要求所以这里没有什么执行代码
{
}
}
else
{
if(key1_num>1 && key1_num<10) //短按
{
key1_flag ^=1; //按键1的按下标志位取反 1:数据显示界面 0:参数显示界面
}
key1_num=0;
}
if(key2==0 && key1_flag==0) //按键2只能在参数显示界面下才有效
{
key2_num++;
if(key2_num==20)
{
}
}
else
{
if(key2_num>1 && key2_num<10)
{
if(key4_flag==0) //参数的增10只能在手动模式下才有效
{
a6=a6+10;
if(a6==100 || a6>90) //参数设置的范围 10-100
{
a6=10;
}
}
}
key2_num=0;
}
if(key3==0 && key1_flag==0) //同按键2
{
key3_num++;
if(key3_num==20)
{
}
}
else
{
if(key3_num>1 && key3_num<10)
{
if(key4_flag==0)
{
a7=a7+10;
if(a7==100 || a7>90)
{
a7=10;
}
}
}
key3_num=0;
}
if(key4==0)
{
key4_num++;
if(key4_num==20)
{
}
}
else
{
if(key4_num>1 && key4_num<10) //按键4
{
key4_flag ^=1; //按键4按下的标志位取反 1:自动模式 0:手动模式
if(key4_flag) //只有在手动模式下才会亮灯
{
led_ctrl(8,1);
}
else
{
led_ctrl(8,0);
}
}
key4_num=0;
}
}
void led_show(void)
{
if(key1_flag) //按键1的标志位 1:数据界面 0:参数界面
{
led_ctrl(9,1); //数据模式点亮led2
LCD_DisplayStringLine(Line0," Data");
sprintf((char *)buff," V:%0.2fV ",adc_val/0xfff*3.3);
LCD_DisplayStringLine(Line2,buff);
if(key4_flag) //按键4标志位 1:自动模式 0:手动模式
{
LCD_DisplayStringLine(Line4," Mode:AUTO ");
}
else
{
LCD_DisplayStringLine(Line4," Mode:MANU ");
}
}
else //参数界面的显示
{
led_ctrl(9,0);
LCD_DisplayStringLine(Line0," Para");
sprintf((char *)buff," PA6:%d%% ",a6);
LCD_DisplayStringLine(Line2,buff);
sprintf((char *)buff," PA7:%d%% ",a7);
LCD_DisplayStringLine(Line4,buff);
}
}
四、 总结
本试题的难点主要有:
①两路不同的pwm占空比
- 为了输出两路不同占空比的pwm波,可以采用的输出比较模式,采用其进去中断函数会自动反转输出极性相反的电平,我们在中断里面设置比较的计数值,达到我们需要的波形,这种属于手动人工输出pwm波。
- 实现的思路:这个编程的思路需要对CCR1,CCR2,CNT,这三个寄存器的功能有足够的了解,CCR寄存器会不断地与CNT寄存器里的值做比较,两者相等的话,就产生中断,第一次产生中断的极性是在初始化配置的时候配置的,程序一开始就先让CCR和CNT都为0,一使能定时器中断就发生中断,在中断中判断是哪个通道出现中断,捕获当前CCR寄存器对的值,然后再用一个标志位判断目前是高电平还是低电平,然后再重新设置当前的CCR的值,达到我们所要求的频率和占空比。
②PWM占空比的不断变化
- pwm的占空比为当前的电压值除以3.3。
- 如果在主循环中,直接不断地修改占空比,会出现pwm混乱。
- 解决方法:在滴答定时器中断服务程序中,调用pwm波的设置函数,每500ms进行一次更新。
③手上没有测pwm的工具
- 编写定时器2的通道2,进行pwm的捕获。
- 如下图: