目录
1、环境搭建
需求:
搭建一个STM32F411CEU6工程
分析:
C / C++ 宏定义栏: STM32F411xE
在哪里找? STM32F411xE
2、按键控制灯 & 电机
需求:
按键控制灯和电机的开关
第一次按键:LED_ON
第二次按键:LED_OFF
第三次按键:MI_ON
第四次按键:MI_OFF
LED
创建.c和.h文件
添加文件到工程
看原理图
LED所用到的IO口
低电平点亮 高电平熄灭
输出模式
程序设计
LED初始化函数
/***********************************************
*函数名 :led_init
*函数功能 :对LED灯所用IO口初始化配置
*函数参数 :无
*函数返回值:无
*函数描述 :LED------PA7
************************************************/
void led_init(void)
{
RCC->AHB1ENR |= (1<<0);
GPIOA->MODER &= ~(3<<14);
GPIOA->MODER |= (1<<14);
GPIOA->OTYPER &= ~(1<<7);
GPIOA->OSPEEDR &= ~(3<<14);
GPIOA->PUPDR &= ~(3<<14);
//初始状态 关
GPIOA->ODR |= (1<<7);
}
开关灯的宏定义
#ifndef _LED_H
#define _LED_H
#include "stm32f4xx.h" // Device header
#define LED_OFF (GPIOA->ODR |= (1<<7))
#define LED_ON (GPIOA->ODR &= ~(1<<7))
#define LED_OVERTURN (GPIOA->ODR ^= (1<<7))
void led_init(void);
#endif
电机
创建.c和.h文件
添加文件到工程
看原理图
电机所用到的IO口
高电平转动 低电平停止
输出模式
程序设计
电机初始化函数
/***********************************************
*函数名 :motor_init
*函数功能 :对电机所用IO口初始化配置
*函数参数 :无
*函数返回值:无
*函数描述 :motor------PB10
************************************************/
void motor_init(void)
{
RCC->AHB1ENR |= (1<<1);
GPIOB->MODER &= ~(3<<20);
GPIOB->MODER |= (1<<20);
GPIOB->OTYPER &= ~(1<<10);
GPIOB->OSPEEDR &= ~(3<<20);
GPIOB->PUPDR &= ~(3<<20);
//初始状态 关
GPIOB->ODR &= ~(1<<10);
}
电机转动和停止的宏定义
#ifndef _MOTOR_H
#define _MOTOR_H
#include "stm32f4xx.h" // Device header
#define MI_OFF (GPIOB->ODR &= ~(1<<10))
#define MI_ON (GPIOB->ODR |= (1<<10))
#define MI_OVERTURN (GPIOB->ODR ^= (1<<10))
void motor_init(void);
#endif
垂直按键(机械按键)
创建.c和.h文件
添加文件到工程
看原理图
按键所用到的IO口
高电平按下 低电平抬起
输入模式
程序设计
按键初始化函数
static void delay_ms(u32 ms)
{
u32 i = 100 / 4 * 1000 * ms;
while(i)
{
i--;
}
}
/***********************************************
*函数名 :key_init
*函数功能 :对KEY所用IO口初始化配置
*函数参数 :无
*函数返回值:无
*函数描述 :KEY_OK------PA0
************************************************/
void key_init(void)
{
RCC->AHB1ENR |= (1<<0);
GPIOA->MODER &= ~(3<<0);
GPIOA->PUPDR &= ~(3<<0);
}
按键扫描函数
/***********************************************
*函数名 :key_scan
*函数功能 :按键扫描函数
*函数参数 :无
*函数返回值:u8
*函数描述 :
************************************************/
u8 key_scan(void)
{
u8 key = 0xff;
static u8 key_flag = 1;
//判断按键按下
if(KEY && key_flag)
{
delay_ms(15);
if(KEY)
{
key = KEY_OK;
key_flag = 0;
MOTOR_VAL = 1000;
timer_buff[3]=0;
}
}
//判断按键抬起
if(!KEY)
{
key_flag = 1;
}
return key;
}
3、串口调试功能
程序设计
串口初始化配置
/***********************************************
*函数名 :usart1_init
*函数功能 :串口1初始化配置
*函数参数 :u32 bps
*函数返回值:无
*函数描述 :USART1_Tx ---------PA9
USART1_Rx ---------PA10
************************************************/
void usart1_init(u32 bps)
{
/*IO口控制器配置*/
//端口时钟使能
RCC->AHB1ENR |= (1<<0);
//端口模式配置
GPIOA->MODER &= ~((3<<18) | (3<<20));
GPIOA->MODER |= ((2<<18) | (2<<20));
//端口输出类型
GPIOA->OTYPER &= ~(1<<9);
//端口输出速度
GPIOA->OSPEEDR &= ~(3<<18); //2M
//上下拉配置
GPIOA->PUPDR &= ~((3<<18) | (3<<20));
//复用功能配置
GPIOA->AFR[1] &= ~(15<<4);
GPIOA->AFR[1] |= (7<<4); //PA9配置串口1的发送复用功能
GPIOA->AFR[1] &= ~(15<<8);
GPIOA->AFR[1] |= (7<<8); //PA10配置串口1的接收复用功能
/*串口1控制器配置*/
//串口时钟使能
RCC->APB2ENR |= (1<<4);
//CR1
USART1->CR1 &= ~(1<<15); //16倍过采样
USART1->CR1 &= ~(1<<12); //8位字长
USART1->CR1 |= (1<<3) ; //发送使能
USART1->CR1 |= (1<<2) ; //接收使能
//CR2
USART1->CR2 &= ~(3<<12); //1个停止位
//BRR
USART1->BRR = 100000000/bps;
/*NVIC控制器配置*/
//优先级分组-----------------------------主函数其他初始化上面
//计算优先级编码值
u32 pri = NVIC_EncodePriority (5,1,2);
//设确定具体中断源
NVIC_SetPriority(USART1_IRQn, pri);
//使能NVIC响应通道
NVIC_EnableIRQ(USART1_IRQn);
//串口1空闲中断使能
USART1->CR1 |= (1<<4);
//串口1接收中断使能
USART1->CR1 |= (1<<5);
//串口使能
USART1->CR1 |= (1<<13);
}
发送一个字节函数
/***********************************************
*函数名 :usart1_send_byte
*函数功能 :串口1发送一个字节函数
*函数参数 :u8 data
*函数返回值:无
*函数描述 :
************************************************/
void usart1_send_byte(u8 data)
{
//等待之前的发送完成(等待转态寄存器的发送完成位置1)
while(!(USART1->SR & (1<<6)));
// //把要发送的数据赋值给数据寄存器
USART1->DR = data;
}
发送一个字符串函数
/***********************************************
*函数名 :usart1_send_str
*函数功能 :串口1发送一个字符串函数
*函数参数 :u8 *str
*函数返回值:无
*函数描述 :
************************************************/
void usart1_send_str(u8 *str)
{
while(*str != '\0')
{
//发送字符
usart1_send_byte(*str);
//下一字符的地址
str++;
}
}
中断服务函数
/***********************************************
*函数名 :USART1_IRQHandler
*函数功能 :串口1中断服务函数
*函数参数 :无
*函数返回值:无
*函数描述 :
************************************************/
u8 usart1_buff[10];
void USART1_IRQHandler(void)
{
u8 data;
//判断是接收中断信号触发
if(USART1->SR & (1<<5))
{
//清除中断标志位
//紧急事件
}
//判断是空闲中断信号触发
if(USART1->SR & (1<<4))
{
//清除中断标志位
USART1->SR;
USART1->DR;
//紧急事件
}
}
4、定时器延时和定时器中断
定时器延时用timer11
定时器11没有单次计数配置,只能是循环计数,
等待计数完成后,要关闭定时器。
定时器中断用timer9
注意:
时钟频率
分频
不同的芯片可能没有基本定时器6和7
程序设计
定时器11毫秒延时函数
/***********************************************
*函数名 :tim11_delay_ms
*函数功能 :定时器11延时
*函数参数 :u16 ms
*函数返回值:无
*函数描述 :100MHZ--------------100000/ms
10000分频-----------10/ms
************************************************/
void tim11_delay_ms(u16 ms)
{
//定时器时钟使能
RCC->APB2ENR |= (1<<18);
//CR1
TIM11->CR1 |= (1<<7); //使能影子寄存器
//TIM11->CR1 |= (1<<3); //单次计数模式
TIM11->CR1 &= ~(1<<1); //产生更新事件
//PSC分频寄存器
TIM11->PSC = 10000-1; //10000分频
//ARR重载寄存器
TIM11->ARR = 10 * ms - 1;
//人为产生更新事件UG
TIM11->EGR |= (1<<0);
//清除状态寄存器更新完成位
TIM11->SR &= ~(1<<0);
//使能计数器
TIM11->CR1 |= (1<<0);
//等待计数完成
while(!(TIM11->SR & (1<<0)));
//关闭定时器
TIM11->CR1 &= ~(1<<0);
}
定时器11微秒延时函数
/***********************************************
*函数名 :TIM11_delay_us
*函数功能 :定时器11延时微秒
*函数参数 :u16 us
*函数返回值:无
*函数描述 :100MHZ--------------100/us
50分频-----------2/us
************************************************/
void TIM11_delay_us(u16 us)
{
//定时器时钟使能
RCC->APB2ENR |= (1<<18);
//CR1
TIM11->CR1 |= (1<<7); //使能影子寄存器
//TIM11->CR1 |= (1<<3); //单次计数模式
TIM11->CR1 &= ~(1<<1); //产生更新事件
//PSC分频寄存器
TIM11->PSC = 50-1; //50分频
//ARR重载寄存器
TIM11->ARR = 2 * us - 1;
//人为产生更新事件UG
TIM11->EGR |= (1<<0);
//清除状态寄存器更新完成位
TIM11->SR &= ~(1<<0);
//使能计数器
TIM11->CR1 |= (1<<0);
//等待计数完成
while(!(TIM11->SR & (1<<0)));
//关闭定时器
TIM11->CR1 &= ~(1<<0);
}
定时器9毫秒级定时中断函数
/***********************************************
*函数名 :tim9_interrupt_ms
*函数功能 :定时器9毫秒级定时中断
*函数参数 :u16 ms
*函数返回值:无
*函数描述 :100MHZ--------------100000/ms
10000分频-----------10/ms
************************************************/
void tim9_interrupt_ms(u16 ms)
{
/*定时器控制器配置*/
//定时器时钟使能
RCC->APB2ENR |= (1<<16);
//CR1
TIM9->CR1 |= (1<<7); //使能影子寄存器
TIM9->CR1 &= ~(1<<3); //循环计数模式
TIM9->CR1 &= ~(1<<2);
TIM9->CR1 &= ~(1<<1);
//PSC 分频
TIM9->PSC = 10000-1;
//ARR
TIM9->ARR = 10 * ms - 1;
//EGR UG位,人为产生更新事件 状态寄存器的0号位置1;
TIM9->EGR |= (1<<0);
//清零状态寄存器
TIM9->SR &= ~(1<<0);
/*NVIC控制器配置*/
//优先级分组
//计算优先级编码值
u32 pri = NVIC_EncodePriority (5,1,1);
//设确定具体中断源
NVIC_SetPriority(TIM1_BRK_TIM9_IRQn, pri);
//使能NVIC响应通道
NVIC_EnableIRQ(TIM1_BRK_TIM9_IRQn);
//DIER 定时中断使能
TIM9->DIER |= (1<<0);
//定时器使能
TIM9->CR1 |= (1<<0);
}
5、振动强弱调节
电机变速用PWM
通过查复用表可知:
电机-----PB10--------TIM2_CH3
程序设计
定时器2通道3输出PWM驱动电机函数
/***********************************************
*函数名 :tim2_ch3_pwm_motor
*函数功能 :定时器2通道3驱动PB10,输出PWM
*函数参数 :无
*函数返回值:无
*函数描述 :100MHZ--------------100000/ms-----100/us
100分频-----------1/us
周期定位1ms PB10
************************************************/
void tim2_ch3_pwm_motor(void)
{
/*IO控制器配置*/
//端口时钟使能
RCC->AHB1ENR |= (1<<1);
//端口模式配置-------复用模式
GPIOB->MODER &= ~(3<<20);
GPIOB->MODER |= (2<<20);
//输出类型
GPIOB->OTYPER &= ~(1<<10);
//输出速度
GPIOB->OSPEEDR &= ~(3<<20);
//上下拉
GPIOB->PUPDR &= ~(3<<20);
//复用功能寄存器
GPIOB->AFR[1] &= ~(15<<8);
GPIOB->AFR[1] |= (1<<8);
/*通用定时器控制器配置*/
//定时器时钟使能
RCC->APB1ENR |= (1<<0);
//CR1
TIM2->CR1 |= (1<<7); //重载的影子寄存器
TIM2->CR1 &= ~(3<<5); //边沿对齐计数模式
TIM2->CR1 &= ~(1<<4); //递增计数
TIM2->CR1 &= ~(1<<3); //连续模式
TIM2->CR1 &= ~(1<<2);
TIM2->CR1 &= ~(1<<1);
//SMCR
TIM2->SMCR &= ~(7<<0); //选择内部时钟
//CCMRx
TIM2->CCMR2 &= ~(3<<0); //把通道3定位输出通道
TIM2->CCMR2 |= (1<<3); //比较寄存器影子
TIM2->CCMR2 &= ~(7<<4);
TIM2->CCMR2 |= (6<<4); //PWM1
//CCER
TIM2->CCER &= ~(1<<9); //高电平为有效电平
//PSC
TIM2->PSC = 100-1;
//ARR
TIM2->ARR = 1000-1;
//CCRx
TIM2->CCR3 = 0;
//EGR
TIM2->EGR |= (1<<0); //人为产生更新事件
//通道3打开
TIM2->CCER |= (1<<8);
//定时器使能
TIM2->CR1 |= (1<<0);
}
6、万年历
时钟源 外部低速时钟 32.768KHZ
内部低速时钟 32KHZ
程序设计
RTC初始化配置
/***********************************************
*函数名 :RTC_init
*函数功能 :RTC初始化配置
*函数参数 :无
*函数返回值:无
*函数描述 :
************************************************/
void RTC_init(RTC_t time)
{
/*解除RTC和相关寄存器保护*/
//使能电源控制器时钟
RCC->APB1ENR |= (1<<28);
//PWR->CR 的DBP位写1解除RTC控制器保护
PWR->CR |= (1<<8);
//秘钥寄存器写入值,解除RTC相关寄存器保护
RTC->WPR = 0xca;
RTC->WPR = 0x53;
/*RTC时钟源设置*/
//使能内部低速震荡器
RCC->CSR |= (1<<0);
//等待内部部振荡器就绪
while(!(RCC->CSR & (1<<1)));
//选择内部低速时钟源
RCC->BDCR |= (2<<8);
//使能RTC时钟
RCC->BDCR |= (1<<15);
/*RTC相关寄存器*/
//CR
RTC->CR &= ~(1<<6); //24h
RTC->CR &= ~(1<<5); //开影子寄存器
RTC->WPR = 0xff;
/*设置初始时间*/
if(RTC->BKP0R != 500)
{
RTC->BKP0R = 500;
set_time(time);
}
}
设置时间函数
/***********************************************
*函数名 :in_dec_out_bcd
*函数功能 :将十进制数据转换成BCD码形式
*函数参数 :u8 dec
*函数返回值:u8
*函数描述 :设置时间时候使用
************************************************/
u8 in_dec_out_bcd(u8 dec)
{
return ((dec / 10) << 4) | (dec % 10);
}
/***********************************************
*函数名 :set_time
*函数功能 :设置时间函数
*函数参数 :RTC_t time
*函数返回值:无
*函数描述 :
************************************************/
void set_time(RTC_t time)
{
u32 temp_t = 0;
u32 temp_d = 0;
//解除寄存器保护
RTC->WPR = 0xca;
RTC->WPR = 0x53;
//让日历进入初始化模式
RTC->ISR |= (1<<7);
//等待可以更新日历值
while(!(RTC->ISR & (1<<6)));
//将设置的十进制数据转换成BCD码
temp_t = (in_dec_out_bcd(time.h))<<16 |
(in_dec_out_bcd(time.m))<<8 |
in_dec_out_bcd(time.s);
temp_d =(in_dec_out_bcd(time.year-2000))<<16 |
(in_dec_out_bcd(time.week))<<13 |
(in_dec_out_bcd(time.mon))<<8 |
(in_dec_out_bcd(time.day));
//设置TR 和 DR
RTC->TR = temp_t;
RTC->DR = temp_d;
//退出初始化模式
RTC->ISR &= ~(1<<7);
//激活写保护
RTC->WPR = 0xff;
}
获取时间函数
/***********************************************
*函数名 :in_bcd_out_dec
*函数功能 :将BCD码形式数据转换成十进制
*函数参数 :u8 bcd
*函数返回值:u8
*函数描述 :获取时间使用
************************************************/
u8 in_bcd_out_dec(u8 bcd)
{
return (bcd >> 4) * 10 + (bcd & 0x0f);
}
/***********************************************
*函数名 :get_time
*函数功能 :获取时间函数
*函数参数 :无
*函数返回值:RTC_t
*函数描述 :
************************************************/
RTC_t get_time(void)
{
RTC_t t;
u32 temp_t = 0;
u32 temp_d = 0;
//解除寄存器保护
RTC->WPR = 0xca;
RTC->WPR = 0x53;
//ISR寄存器中RSF位0
RTC->ISR &= ~(1<<5);
//等待同步完成(等待ISR寄存器中RSF位自动变为1)
while(!(RTC->ISR & (1<<5)));
//读出时间寄存器的值(BCD码)
temp_t = RTC->TR;
//ISR寄存器中RSF位0
RTC->ISR &= ~(1<<5);
//等待同步完成(等待ISR寄存器中RSF位自动变为1)
while(!(RTC->ISR & (1<<5)));
//读出日期寄存器的值(BCD码)
temp_d = RTC->DR;
//将读出的BCD码转换成十进制形式
t.year = in_bcd_out_dec((temp_d & 0xff0000) >> 16) + 2000;
t.week = in_bcd_out_dec((temp_d & 0xe000) >> 13);
t.mon = in_bcd_out_dec((temp_d & 0x1f00) >> 8);
t.day = in_bcd_out_dec((temp_d & 0x3f) >> 0);
t.h = in_bcd_out_dec(temp_t >> 16);
t.m = in_bcd_out_dec(temp_t >> 8);
t.s = in_bcd_out_dec(temp_t >> 0);
//激活写保护
RTC->WPR = 0xff;
//返回结构体变量
return t;
}
7、五方向按键
1、原理及分析
需求:
五方向的按键扫描函数
分析:
垂直按键不参与ADC控制
其他四方向的原理:通过ADC转换得到不同的份数,每份份数代表着不同的方向
通过原理图用到的是PA3
通过查表知道用到ADC1的通道3
四个按键方向接在ADC1的3号通道上,不同的方向接了不同的电阻,不同的按键方向就会体现不同的电压值,不同电压值通过AD转换,就会体现出不同的份数.不同的份数就可以识别不同方向的按键.
2、程序设计
设计ADC相关程序
ADC的配置方式有两种:中断的方式获取数据、查询等待的方式获取数据
查询等待的方式:
ADC初始化配置函数
/***********************************************
*函数名 :adc1_ch3_Init
*函数功能 :ADC1通道3
*函数参数 :无
*函数返回值:无
*函数描述 :PA3-----ADC1_CH3------四方向按键
************************************************/
void adc1_ch3_Init(void)
{
/*IO口控制器配置*/
//端口时钟使能
RCC->AHB1ENR |= (1<<0);
GPIOA->MODER &= ~(3<<6);
GPIOA->MODER |= (3<<6); //模拟模式
/*ADC控制器配置*/
//ADC控制器时钟使能
RCC->APB2ENR |= (1<<8);
//CR1
ADC1->CR1 &= ~(3<<24); //精度12位
ADC1->CR1 |= (1<<8); //扫描模式
//CR2
ADC1->CR2 &= ~(1<<11); //右对齐
ADC1->CR2 |= (1<<10); //每个转换完成标志位就置1
ADC1->CR2 &= ~(1<<1); //单次
//SMPR
ADC1->SMPR2 |= (7<<9); //通道3,480个周期
//SQR1
ADC1->SQR1 &= ~(0xf<<20); //一共有一个转换
//SQR3
ADC1->SQR3 &= ~(0x1f<<0);
ADC1->SQR3 |= (3<<0); //第一个转换是3号通道
//CCR
ADC->CCR &= ~(3<<16);
ADC->CCR |= (1<<16); //4分频
//ADC使能
ADC1->CR2 |= (1<<0);
}
获取转换数据函数
/***********************************************
*函数名 :get_adc1_data
*函数功能 :获取adc1的转换数据
*函数参数 :无
*函数返回值:u16
*函数描述 :PA3-----ADC1_CH3------四方向按键
************************************************/
u16 get_adc1_data(void)
{
u16 data;
//开始转换
ADC1->CR2 |= (1<<30);
//等待转换完成
while(!(ADC1->SR & (1<<1)));
//获取转换数据到变量
data = ADC1->DR;
//返回转换值
return data;
}
通过调用ADC的获取函数,再通过测试按键,得到不同方向的份数值
KEY_UP 1000~1100
KEY_Down 1300~1400
KEY_L >=4000
KEY_R 2000~2100
设计四方向按键相关程序
四方向按键扫描函数
/***********************************************
*函数名 :fun_key_init
*函数功能 :五方向按键初始化
*函数参数 :无
*函数返回值:无
*函数描述 :
************************************************/
void fun_key_init(void)
{
key_init();
adc1_ch3_Init();
}
/***********************************************
*函数名 :fun_key_scan
*函数功能 :五方向按键扫描函数
*函数参数 :无
*函数返回值:u8
*函数描述 :
************************************************/
u8 fun_key_scan(void)
{
u16 data;
u8 key_data = 0xff;
static u8 adc_flag = 1;
//四方向按键
data = get_adc1_data();
if(adc_flag && data >= 1000 && data <= 1100 ) //UP
{
adc_flag = 0;
key_data = KEY_UP;
}
else if(adc_flag && data >= 1300 && data <= 1400) //Down
{
adc_flag = 0;
key_data = KEY_Down;
}
else if(adc_flag && data > 4000) //left
{
adc_flag = 0;
key_data = KEY_Left;
}
else if(adc_flag && data >= 2000 && data <= 2100) //right
{
adc_flag = 0;
key_data = KEY_Right;
}
if(data < 20)
{
//解锁标志位
adc_flag = 1;
}
return key_data;
}
结合垂直按键写出五方向按键扫描函数
/***********************************************
*函数名 :fun_key_scan
*函数功能 :五方向按键扫描函数
*函数参数 :无
*函数返回值:u8
*函数描述 :
************************************************/
u8 fun_key_scan(void)
{
u16 data;
u8 key_data = 0xff;
static u8 adc_flag = 1;
//垂直按键
key_data = key_scan();
//四方向按键
data = get_adc1_data();
if(adc_flag && data >= 1000 && data <= 1100 ) //UP
{
adc_flag = 0;
key_data = KEY_UP;
}
else if(adc_flag && data >= 1300 && data <= 1400) //Down
{
adc_flag = 0;
key_data = KEY_Down;
}
else if(adc_flag && data > 4000) //left
{
adc_flag = 0;
key_data = KEY_Left;
}
else if(adc_flag && data >= 2000 && data <= 2100) //right
{
adc_flag = 0;
key_data = KEY_Right;
}
if(data < 20)
{
//解锁标志位
adc_flag = 1;
}
return key_data;
}
若想要实现,按住不抬起能每隔一秒返回一次键值?
利用定时中断,超时检测1S解锁一次标志位
u8 fun_key_scan(void)
{
u16 data;
u8 key_data = 0xff;
static u8 adc_flag = 1;
//垂直按键
key_data = key_scan();
//四方向按键
data = get_adc1_data();
if(adc_flag && data >= 1000 && data <= 1100 ) //UP
{
adc_flag = 0;
key_data = KEY_UP;
timer_buff[3]=0; //开始1s计时
MOTOR_VAL = 1000;
}
else if(adc_flag && data >= 1300 && data <= 1400) //Down
{
adc_flag = 0;
key_data = KEY_Down;
timer_buff[3]=0; //开始1s计时
MOTOR_VAL = 1000;
}
else if(adc_flag && data > 4000) //left
{
adc_flag = 0;
key_data = KEY_Left;
timer_buff[3]=0; //开始1s计时
MOTOR_VAL = 1000;
}
else if(adc_flag && data >= 2000 && data <= 2100) //right
{
adc_flag = 0;
key_data = KEY_Right;
timer_buff[3]=0; //开始1s计时
MOTOR_VAL = 1000;
}
if(data < 20 || timer_buff[3]==1000)
{
//解锁标志位
adc_flag = 1;
}
return key_data;
}