STM32芯片型号命名方式
STM32开发板的GPIO编程
GPIO的函数调用顺序:
(1)使能GPIO时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
第一个参数是GPIO对象,第二个参数是枚举使能
(2)初始化GPIO:该步骤设置某个GPIO的某个Pin的工作模式(输入4种,输出4种)和速度
GPIO_InitTypeDef gpio_init;
gpio_init.GPIO_Pin = GPIO_Pin_5; //初始化GPIO的哪个Pin
gpio_init.GPIO_Mode = GPIO_Mode_Out_PP; //Pin的工作模式(输入4种,输出4种)
gpio_init.GPIO_Speed = GPIO_Speed_50MHz; //速度
GPIO_Init(GPIOC, &gpio_init); //第一个参数是要初始化的GPIO对象,第二个参数是初始化参数
(3)操作GPIO(以控制LED和键盘KEY监测为例子):
(3.1)控制LED:
以GPIOC的Pin5上连接LED为例,
第一步:初始数据, GPIOC->BSRR = GPIO_Pin_5;
第二步:点亮LED, GPIOC->BSRR |= GPIO_Pin_5 << 16;
熄灭LED, GPIOC->BSRR |= GPIO_Pin_5;
反转LED(亮变灭,灭变亮), GPIOC->ODR ^= GPIO_Pin_5;
(3.2)键盘KEY监测(以查询的方式,还可以用中断方式,中断方式见后文):
第一步:键盘KEY连接的GPIO的Pin需要设置成输入模式的“上拉”或者“下拉”模式:
gpio_init.GPIO_Pin = GPIO_Pin_1;
gpio_init.GPIO_Mode = GPIO_Mode_IPU;
gpio_init.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA, &gpio_init);
第二步:在循环中不断调用 GPIO_ReadInputDataBit 函数读取该Pin的输入值:
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == status) //上拉输入工作模式:status==0,下拉输入工作模式:status==1
以中断方式进行键盘KEY监测
(1)使能GPIO时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
(2)使能AFIO时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
(3)初始化GPIO:
键盘KEY连接的GPIO的Pin需要设置成输入模式的“上拉”或者“下拉”模式:
gpio_init.GPIO_Pin = GPIO_Pin_1;
gpio_init.GPIO_Mode = GPIO_Mode_IPU; //上拉GPIO_Mode_IPU,下拉GPIO_Mode_IPD
gpio_init.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA, &gpio_init);
(4)配置按键的中断优先级:
NVIC_InitTypeDef nvic;
nvic.NVIC_IRQChannel = EXTI0_IRQn; //对应固定对应中断响应函数EXTI0_IRQHandlernvic.NVIC_IRQChannelPreemptionPriority = 1;
nvic.NVIC_IRQChannelSubPriority = 0;
nvic.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic);
(5)配置按键的中断方式:
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);//指定GPIO对象和Pin
exti.EXTI_Line = EXTI_Line0;
exti.EXTI_Mode = EXTI_Mode_Interrupt;
exti.EXTI_Trigger = EXTI_Trigger_Rising; //上升沿触发EXTI_Trigger_Rising,下降沿触发EXTI_Trigger_Falling
exti.EXTI_LineCmd = ENABLE;
EXTI_Init(&exti);
(6)在中断函数中编写业务逻辑:
void EXTI0_IRQHandler(void)
{
//业务逻辑
}
中断的优先级分组
STM32(Cortex-M3)中有两个优先级的概念——抢占式优先级和响应优先级,有人把响应优先级称作'亚优先级'或'副优先级',每个中断源都需要被指定这两种优先级。
具有高抢占式优先级的中断可以在具有低抢占式优先级的中断处理过程中被响应,即中断嵌套,或者说高抢占式优先级的中断可以嵌套在低抢占式优先级的中断中。
当两个中断源的抢占式优先级相同时,这两个中断将没有嵌套关系,当一个中断到来后,如果正在处理另一个中断,这个后到来的中断就要等到前一个中断处理完之后才能被处理。如果这两个中断同时到达,则中断控制器根据他们的响应优先级高低来决定先处理哪一个;如果他们的抢占式优先级和响应优先级都相等,则根据他们在中断表中的排位顺序决定先处理哪一个。
看了上面的介绍后,相信大家都明白了这里面的关系了,总结下便是:抢占式优先级>响应优先级>中断表中的排位顺序(其中“>”理解为比较的方向)。
Cortex-M3中定义了8个比特位用于设置中断源的优先级,但是Cortex-M3允许具有较少中断源时使用较少的寄存器位指定中断源的优先级。于是,STM32把指定中断优先级的寄存器位减少到4位,这4个寄存器位的分组方式如下:
第0组:所有4位用于指定响应优先级;
第1组:最高1位用于指定抢占式优先级,最低3位用于指定响应优先级;
第2组:最高2位用于指定抢占式优先级,最低2位用于指定响应优先级;
第3组:最高3位用于指定抢占式优先级,最低1位用于指定响应优先级;
第4组:所有4位用于指定抢占式优先级。
优先级分组函数 NVIC_PriorityGroupConfig(u32 NVIC_PriorityGroup),该函数的参数共有5种:
NVIC_PriorityGroup_0 => 选择第0组
NVIC_PriorityGroup_1 => 选择第1组
NVIC_PriorityGroup_2 => 选择第2组
NVIC_PriorityGroup_3 => 选择第3组
NVIC_PriorityGroup_4 => 选择第4组
在STM32中使用C语言的库函数
在STM32中使用C语言的库函数(例如 memcpy、memset等),要勾选Use MicroLib选项。
MicroLib是缺省c库的备选库,它可装入少量内存中,与嵌入式应用程序配合使用,且这些应用程序不在操作系统中运行。 MicroLib进行了高度优化以使代码变得很小,功能比缺省c库少,不具备某些ISO c特性,部分库函数的运行速度也比较慢,如内存拷贝函数memcpy()。
(1)MicroLib 不符合 ISO C 库标准。 不支持某些 ISO 特性,并且其他特性具有的功能也较少。
(2)MicroLib 不符合 IEEE 754 二进制浮点算法标准。
(3)MicroLib 进行了高度优化以使代码变得很小。
(4)无法对区域设置进行配置。 缺省 C 区域设置是唯一可用的区域设置。
(5)不能将 main() 声明为使用参数,并且不能返回内容。
(6)不支持 stdio,但未缓冲的 stdin、stdout 和 stderr 除外。
(7)MicroLib对 C99 函数提供有限的支持。
(8)MicroLib不支持操作系统函数。
(9)MicroLib不支持与位置无关的代码。
(10)MicroLib不提供互斥锁来防止非线程安全的代码。
(11)MicroLib不支持宽字符或多字节字符串。
(12)与stdlib不同,MicroLib不支持可选择的单或双区内存模型。MicroLib只提供双区内存模型,即单独的堆栈和堆区。
STM32开发过程中打印调试信息
MicroLib提供了一个有限的stdio子系统,它仅支持未缓冲的stdin、stdout和stderr,那么也就是说勾选了Use MicroLib选项后,在代码工程中就可以使用printf()函数来打印调试信息了吗?
答案是“否”,这样直接使用printf()函数,其打印的字符串最终不知道打印到何处。我们要做的是将调试信息打印到USART1中,所以需要对printf()函数所依赖的打印输出函数fputc()重定向(MicroLib中的printf()函数打印操作依赖fputc())。
在MicroLib的stdio.h中,fputc()函数的原型为:
int fputc(int ch, FILE* stream)
此函数原本是将字符ch打印到文件指针stream所指向的文件流去的。
现在我们不需要打印到文件流,而是打印到串口(串口的输出数据被连接串口的上位机接收,通过上位机的串口调试软件显示打印信息)。
重新编写fputc函数,就实现了重定向:
#include <stdio.h>
int fputc(int ch, FILE* stream) {
USART_SendData(USART1, (uint8_t)ch);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
return ch;
}
注意,需要包含头文件stdio.h,否则FILE类型未定义,并且还要勾选Use MicroLib选项。
至此,重定向fputc()函数后,我们就可以在工程代码中使用printf()函数打印调试信息,并在上位机上查看了。
STM32的串口(UART/USART)编程
UART:通用异步收发传输器(Universal Asynchronous Receiver/Transmitter);
USART:在UART上追加同步方式的序列信号变换电路的产品,被称为USART(Universal Synchronous Asynchronous Receiver Transmitter)。
UART是USART的子集,STM32的串口就是USART设备。
STM32的串口发送数据编程有3种编程方法:
(1)基本数据发送:
优点:代码和逻辑都简单,容易理解!新手必备!
缺点:利用while不断检测发送状态+等待,挨个字节发出,其实很费时;如115200波特率,发送100个字节,至少用时8700us!!而且发送期间,中断响应外,整个程序、系统不能干其它事,如假死一般;
(2)DMA数据发送:
优点:占用资源最少,发送100字节(用"处理"这个词更好理解), 只需大约30us, 比方式1发送快290倍!!
发送工作都在后台,调试时特省工夫。别看代码好像很复杂,其实过程会很简单,只要配置好DMA基本就没啥事了。
缺点:代码长,易出错。 这里指的是使用标准库,或HAL库编写的代码, 调试时, 发量直线下降
发送处理的时间确实很短, 但后台中还得按设定的波特率进行发送, 115200, 100字节, 后台工作时长还是要8600us左右, 如果在这8600us期间, 又有新的数据要发送出去, 要么等待上次的发送完成,要么停止上次未完的发送。
(3)中断数据发送
优点:占用资源少,没准确测量时间,如100字节,估计总用时100us左右吧,但分100次中断分摊。
发送期间,如果有新的数据要发送,交追加到发送区的尾部,不会像DMA那样被抹掉。
缺点:逻辑比DMA要多两个弯弯, 调试时有点麻烦;
综上所述,中断数据发送是UART数据发送的最优解。
STM32的串口编程 - 基本数据发送
串口基本数据发送的编程函数调用顺序为:
(1)初始化:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //第一步,初始化中断优先级。因为数据接收是通过中断,因此先初始化中断,优先级全局只设置一次
//第二步,使能GPIO的时钟和USART的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //因为USART使用的是GPIO的Pin,因此需要使能GPIO的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //使能USART1的时钟
//配置Tx引脚
GPIO_InitTypeDef gpio;
gpio.GPIO_Pin = GPIO_Pin_9;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
gpio.GPIO_Mode = GPIO_Mode_AF_PP; //输出、开漏复用模式
GPIO_Init(GPIOA, &gpio);
//配置Rx引脚
gpio.GPIO_Pin = GPIO_Pin_10;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING; //输入、浮空模式
GPIO_Init(GPIOA, &gpio);
//中断配置
NVIC_InitTypeDef nvic;
nvic.NVIC_IRQChannel = USART1_IRQn;//该中断对应固定中断函数
nvic.NVIC_IRQChannelPreemptionPriority = 2;
nvic.NVIC_IRQChannelSubPriority = 2;
nvic.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic);
//USART配置(串口参数配置)
USART_InitTypeDef usart;
USART_DeInit(USART1);
usart.USART_BaudRate = 115200;
usart.USART_WordLength = USART_WordLength_8b;
usart.USART_StopBits = USART_StopBits_1;
usart.USART_Parity = USART_Parity_No;
usart.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
usart.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &usart);
//中断使能
USART_ITConfig(USART1, USART_IT_TXE, DISABLE);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //使能接收中断
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); //使能空闲中断
//使能串口USART1,串口开始工作
USART_Cmd(USART1, ENABLE);
//清理中断
USART1->SR = ~(0x00F0);
//(2)发送数据:普通函数
void send_data(uint8_t ch) {
USART_SendData(USART1, (uint8_t)ch);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
//(3)接收数据:中断函数中
void USART1_IRQHandler(void) {
static uint16_t cnt = 0; static uint8_t RxTemp[U1_RX_BUF_SIZE];
if(USART1->SR & (1 << 5)) {
RxTemp[cnt++] = USART1->DR;
}
STM32的串口编程 - DMA数据发送
DMA:直接存储器存取。
DMA用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。
STM32F10x系列芯片有两个DMA控制器,共有12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权。
串口DMA数据发送的编程函数调用顺序为:
(1)初始化:
同“基本数据发送”的初始化
(2)初始化DMA:
USART1->CR3 |= (1 << 7); //使能USART1的DMA发送
RCC->AHBENR |= (1 << 0); //开启DMA1时钟(0-DMA1 1-DMA2)
DMA1_Channel4->CCR = 0; //使能关闭,以便设置参数
DMA1_Channel4->CPAR = (u32)&USART1->DR;//DMA使用的外设地址
DMA1_Channel4->CCR |= (1 << 4); //数据传输方向 0-从外设读;1-从存储器读
DMA1_Channel4->CCR |= (0 << 5); //循环模式 0-不循环;1-循环
DMA1_Channel4->CCR |= (0 << 6); //外设地址非增量模式
DMA1_Channel4->CCR |= (1 << 7); //存储器增量模式
DMA1_Channel4->CCR |= (0 << 8); //外设数据宽度为8位
DMA1_Channel4->CCR |= (0 << 10); //存储器数据宽度为8位
DMA1_Channel4->CCR |= (0 << 12); //中等优先级
DMA1_Channel4->CCR |= (0 << 14); //非存储器到存储器模式
(3)DMA数据发送
void MyDMASend(const char * data, uint32_t num) {
DMA1_Channel4->CCR &= ~((u32)(1 << 0)); //使能关闭,以便设置参数
DMA1_Channel4->CMAR = (u32)data;
DMA1_Channel4->CNDTR = num;
DMA1_Channel4->CCR |= (1 << 0); //使能打开
//等待DMA发送完成
while(DMA1_Channel4->CNDTR > 0) {
uint32_t n = DMA1_Channel4->CNDTR;
++n;
}
}
//(4)接收数据:中断函数中
void USART1_IRQHandler(void) {
static uint16_t cnt = 0; static uint8_t RxTemp[U1_RX_BUF_SIZE];
if(USART1->SR & (1 << 5)) {
RxTemp[cnt++] = USART1->DR;
}
STM32的串口编程 - 中断数据发送
函数调用顺序为:
(1)初始化:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //第一步,初始化中断优先级。因为数据接收是通过中断,因此先初始化中断,优先级全局只设置一次
//第二步,使能GPIO的时钟和USART的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //因为USART使用的是GPIO的Pin,因此需要使能GPIO的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //使能USART1的时钟
//配置Tx引脚
GPIO_InitTypeDef gpio;
gpio.GPIO_Pin = GPIO_Pin_9;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
gpio.GPIO_Mode = GPIO_Mode_AF_PP; //输出、开漏复用模式
GPIO_Init(GPIOA, &gpio);
//配置Rx引脚
gpio.GPIO_Pin = GPIO_Pin_10;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING; //输入、浮空模式
GPIO_Init(GPIOA, &gpio);
//中断配置
NVIC_InitTypeDef nvic;
nvic.NVIC_IRQChannel = USART1_IRQn;//该中断对应固定中断函数
nvic.NVIC_IRQChannelPreemptionPriority = 2;
nvic.NVIC_IRQChannelSubPriority = 2;
nvic.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic);
//USART配置(串口参数配置)
USART_InitTypeDef usart;
USART_DeInit(USART1);
usart.USART_BaudRate = 115200;
usart.USART_WordLength = USART_WordLength_8b;
usart.USART_StopBits = USART_StopBits_1;
usart.USART_Parity = USART_Parity_No;
usart.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
usart.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &usart);
//中断使能
USART_ITConfig(USART1, USART_IT_TXE, DISABLE);//初始化时,中断发送不使能,有数据发送时再使能
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //使能接收中断
USART_ITConfig(USART1, USART_IT_IDLE, DISABLE); //不使能空闲中断
//使能串口USART1,串口开始工作
USART_Cmd(USART1, ENABLE);
//清理中断
USART1->SR = ~(0x00F0);
(3)任意需要发送数据的时候,开启发送中断 USART_ITConfig(USART1, USART_IT_TXE, ENABLE);
(4)接收数据:中断函数中
void USART1_IRQHandler(void) {
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)} unsigned char cmd = USART_ReceiveData(USART1); //接收中断中接收数据
if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET) {
USART_SendData(USART1, buf_send[buf_sended_len++]); //发送中断中发送数据
if(buf_sended_len == buf_send_len) USART_ITConfig(USART1, USART_IT_TXE, DISABLE); //数据发送完了就关闭发送中断
}
STM32编程-获取实时的日期时间
注意:STM32编程中,如果要获取实时的日期和时间,不能使用C语言的库函数time获取,而需要使用STM32的RTC模块获取
(1)相关结构体定义和函数申明
typedef struct {
uint16_t Year;
uint8_t Month;
uint8_t Day;
uint8_t Hour;
uint8_t Minute;
uint8_t Second;
uint8_t Week;
char * WeekString;
}MyRTC_TypeDef;
#define WEEK_0 "日"
#define WEEK_1 "一"
#define WEEK_2 "二"
#define WEEK_3 "三"
#define WEEK_4 "四"
#define WEEK_5 "五"
#define WEEK_6 "六"
uint8_t const table_week[12] = {0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5}; //月修正数据表
const uint8_t mon_table[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; //平年的月份日期表
uint8_t isLeapYear(uint16_t year);
uint8_t getWeek(uint16_t year, uint8_t month, uint8_t day);
u8 RTC_Init(void);
u8 RTC_SetDate(const MyRTC_TypeDef * rtc);
u8 RTC_GetDate(MyRTC_TypeDef * rtc);
(2)函数定义(因为篇幅限制,只对函数功能进行简要说明)
/** 该年份是否是闰年 - 返回值 1-是;0-不是。*/
uint8_t isLeapYear(uint16_t year) { ... }
/** 获取某日是星期几 - 输入:年月日 - 返回:星期(0-周日;1-周一;...*/
uint8_t getWeek(uint16_t year, uint8_t month, uint8_t day) { ... }
/** 检查并配置RTC(程序启动时调用一次) - 返回值:0-成功;1-失败 */
uint8_t RTC_Init(void) { ... }
/** 设置板卡的日期和时间 - 返回值:0-成功;1-失败 */
uint8_t RTC_SetDate(const MyRTC_TypeDef * rtc) { ... }
/** 获取板卡的日期和时间 返回值:0-成功;1-失败 */
uint8_t RTC_GetDate(MyRTC_TypeDef * rtc) {... }
(3)调用函数,获取/设置板卡的日期时间
int main(void) {
RTC_Init(); //只在程序启动时调用一次
//设置板卡的日期时间
MyRTC_TypeDef dt;
dt.Year = 2022; dt.Month = 11; dt.Day = 28; dt.Hour = 17; dt.Minute = 59; dt.Second = 12;
RTC_SetDate(&dt);
//获取板卡的日期时间
MyRTC_TypeDef dt2;
RTC_GetDate(&dt2);
}
STM32编程-定时器的类别
STM32中有众多定时器,按所处的位置可分为核内定时器和外设定时器。
核内定时器就是SysTick定时器,该定时器位于Cortex-M3内核中。
外设定时器由芯片半导体厂商设计,如STM32系列,包含常规定时器和专用定时器。
(1)SysTick定时器
SysTick定时器主要用于系统精确延时,不占用其它定时器。在多任务操作系统中,为系统提供时间基准。
(2)看门狗定时器
看门狗定时器主要用于监控系统的运行状态,当系统受外界干扰,程序脱离正常的执行流程时,看门狗将复位系统,尝试恢复正常状态。
看门狗也是定时器,启动后便开始计数,达到计数阈值则复位系统。STM32内置了两个看门狗定时器,即独立看门狗(IWDG)和窗口看门狗(WWDG)。
(3)常规定时器
STM32F1系列共用8个定时器,2个基本定时器(TIM6、TIM7)、4个通用定时器(TIM2、TIM3、TIM4、 TIM5)、2个高级定时器(TIM1、TIM8),三者区别如表 25.1.1 所示。
STM32编程-定时器的编程
STM32中有众多定时器,按所处的位置可分为核内定时器和外设定时器。
核内定时器就是SysTick定时器,该定时器位于Cortex-M3内核中。
外设定时器由芯片半导体厂商设计,如STM32系列,包含常规定时器和专用定时器。
(1)使用核内定时器
#include <system_stm32f10x.h>
//定义初始化核内定时器函数
int sysTickCnt = 0; //定时器计数,1毫秒递增1
void System_SysTickInit(void)
{
SystemCoreClock = 5201314; // 用于存放系统时钟频率,先随便设个值
SystemCoreClockUpdate(); // 获取当前时钟频率, 更新全局变量 SystemCoreClock值
uint32_t msTick= SystemCoreClock /1000; // 计算重载值,全局变量SystemCoreClock的值
SysTick -> LOAD = msTick -1; // 自动重载
SysTick -> VAL = 0; // 清空计数器
SysTick -> CTRL = 0; // 清0
SysTick -> CTRL |= 1<<2; // 0: 时钟=HCLK/8, 1:时钟=HCLK
SysTick -> CTRL |= 1<<1; // 使能中断
SysTick -> CTRL |= 1<<0; // 使能SysTick
}
int main(void) {
//第一步,设置中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//第二步,调用初始化函数
System_SysTickInit(); //初始化只在程序开始调用一次就。
while(1) {
//第三步,随时获取定时器运行时间
uint32_t ms = sysTickCnt; //毫秒
uint32_t us = (float)ms *1000 + (SysTick ->LOAD - SysTick ->VAL )*1000/SysTick->LOAD ; //微妙
}
}
(2)使用外设定时器Tim
#include <stm32f10x_tim.h>
//定义Tim6的中断函数
int tim6Cnt = 0; //定时器计数,1毫秒递增1
void TIM6_IRQHandler(void)
{
TIM6->SR &= (uint16_t)~0x01;//清0更新中断标志位
tim6Cnt++;//计数+1
}
int main(void) {
//第一步,设置中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//第二步,初始化TIM6
//为Tim6设置中断
NVIC_InitTypeDef nvic_tim6;
nvic_tim6.NVIC_IRQChannel = TIM6_IRQn;
nvic_tim6.NVIC_IRQChannelPreemptionPriority = 1;
nvic_tim6.NVIC_IRQChannelSubPriority = 2;
nvic_tim6.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic_tim6);
//配置Tim6
TIM_TimeBaseInitTypeDef tim_init;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); //开启TIM6时钟,即内部时钟CK_INT=72M(STM32F103RC的芯片)
TIM_Cmd(TIM6, DISABLE); //配置前,先关闭计数器
tim_init.TIM_Prescaler = 72 -1;// 预分频器数值,16位; 计数器的时钟频率CK_CNT等于f CK_PSC /(PSC[15:0]+1)。在每一次更新事件时,PSC的数值被传送到实际的预分频寄存器中。
tim_init.TIM_Period = 1000;// 自动重装载数值,16位; 即多少个脉冲产生一个更新或中断(1周期)。如果自动重装载数值为0,则计数器停止。
TIM_TimeBaseInit(TIM6, &tim_init); // 初始化定时器
TIM_ClearFlag(TIM6, TIM_FLAG_Update);// 清除计数器中断标志位
TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE); // 开启更新中断(只是开启功能,未工作)
TIM_Cmd(TIM6, ENABLE);// 是否使能计数器(使能了就会立即开始工作)
while(1) {
//第三步,随时获取定时器运行时间
uint32_t ms = tim6Cnt ; //毫秒
}
}
STM32编程-ADC_模数转换_获取芯片内部温度
1、使能内部温度传感器:ADC_TempSensorVrefintCmd(ENABLE); //开启内部温度传感器
2、读出传感器的采样值:float temp = cpu_val * (float)(3.3)/(float)4095;//采样值转电压值
3、采样值转为电压值:ADC读出来的是电压值,要按公式来转换T(℃) ={(V25-Vsense) /Avg_Slope}+25
V25=Vsense 在 25 度时的数值(典型值为: 1.43)。
Avg_Slope=温度与 Vsense 曲线的平均斜率(单位为 mv/℃或 uv/℃)(典型值为4.3Mv/℃)。
4、电压值转为温度值:temp=((1.43-temp)/0.0043)+25;//电压值转温度值
STM32有3个ADC,其中2个ADC有16个通道,另1个ADC有8个通道。
(1)ADC1初始化函数
void ADC_InitCpuTem(void) {
GPIO_InitTypeDef gpio;
ADC_InitTypeDef adc;
//(1)开启PA口时钟和ADC1时钟,设置PA1为模拟输入
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
//(2)复位ADC1,同时设置ADC1分频因子
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
//(3)初始化ADC1参数,设置ADC1的工作模式以及规则序列的相关信息
adc.ADC_Mode = ADC_Mode_Independent;//独立模式
adc.ADC_ScanConvMode = DISABLE;//AD单通道模式
adc.ADC_ContinuousConvMode = DISABLE;//AD单次转换模式
adc.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//转换由软件而不是外部触发启动
adc.ADC_DataAlign = ADC_DataAlign_Right;//ADC数据右对齐
adc.ADC_NbrOfChannel = 1;//顺序进行规则转换的ADC通道的数目
ADC_Init(ADC1, &adc);
ADC_TempSensorVrefintCmd(ENABLE);//开启内部温度传感器
//(4)使能ADC并校准
//执行复位校准的AD校准,注意这两部是必须的!不校准将导致结果很不准确
ADC_Cmd(ADC1, ENABLE);//使能ADC
ADC_ResetCalibration(ADC1);//复位校准
ADC_StartCalibration(ADC1);//AD校准
//记住,每次进行校准之后要等待校准结束,这里是通过校准状态来判断是否校准结束
//一旦校准结束,CAL位被硬件复位 校准状态复位完成的返回值是0
while(ADC_GetResetCalibrationStatus(ADC1));
while(ADC_GetCalibrationStatus(ADC1));
}
(2)读取传感器的采样值
uint16_t ADC_GetCpuTem(uint8_t ch) {
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);//使能指定的ADC1的软件转换启动功能
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));//等待转换结束
return ADC_GetConversionValue(ADC1); //获取转换ADC转换结果数据
}
(3)读取多次采样的平均值
uint16_t ADC_GetCpuTemAgv(uint8_t ch, uint8_t time) {
uint8_t i = 0;
uint16_t adc_value = 0;
for(i = 0; i < time; i++) {
adc_value += ADC_GetCpuTem(ch);
delay_ms(10);
}
return (adc_value / time);
}
(4)读取CPU温度值
int main(void) {
ADC_InitCpuTem();
while(1) {
uint16_t cpu_val = ADC_GetCpuTemAgv(ADC_Channel_16, 10);//通道16,计算10次
float temp = cpu_val * (float)(3.3)/(float)4095;//采样值转电压值
temp = ((1.43 - temp) / 0.0043) + 25;//电压值转温度值
printf("CPU temperature : %.2f ℃ !\n", temp);
delay_ms(250);
}
STM32编程-DAC_数模转换_输出模拟电压
1: STM32F103系列,需RC规格以上芯片,才有DAC功能,只有两个通道
2: STM32系列,最多只有两个DAC,每个DAC只有1个独立通道;STM32F103系列,通道1为PA4, 通道2为PA5
3:如果要使用这两个通道,就不能与其它设备共用引脚
4: 如PA5为SPI1的SCK引脚,如果两个外设同时使用,会有冲突;
5:同时,这两个引脚上,也不能接上其它设备的电路,如上拉电阻等;
(1)DAC1初始化函数
/** 初始化DAC通道1:PA4,并设置输出模拟电压:0mV */
static uint8_t g_DAC_chan1_inited = 0;
void DAC_Chan1Init(void) {
GPIO_InitTypeDef gpio;
DAC_InitTypeDef dac;
//时钟使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//引脚配置
gpio.GPIO_Pin = GPIO_Pin_4;//引脚编号
gpio.GPIO_Mode = GPIO_Mode_AIN;//引脚工作模式:模拟输入
GPIO_Init(GPIOA, &gpio);//初始化
GPIO_SetBits(GPIOA, GPIO_Pin_4);//打开上拉电阻
//DAC配置
dac.DAC_Trigger = DAC_Trigger_None;
dac.DAC_WaveGeneration = DAC_WaveGeneration_None;
dac.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;
dac.DAC_OutputBuffer = DAC_OutputBuffer_Disable;
DAC_Init(DAC_Channel_1, &dac);//初始化DAC通道1
DAC_Cmd(DAC_Channel_1, ENABLE);//标记通道1初始化标志
g_DAC_chan1_inited = 1;
DAC_PA4SetVoltage(0);//设置DAC通道1输出电压,mV
}
(2)DAC1初始化函数
/** 初始化DAC通道2:PA5,并设置输出模拟电压:0mV */
static uint8_t g_DAC_chan2_inited = 0;
void DAC_Chan2Init(void) {
GPIO_InitTypeDef gpio;
DAC_InitTypeDef dac;
//时钟使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
gpio.GPIO_Pin = GPIO_Pin_5;
gpio.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &gpio);
GPIO_SetBits(GPIOA, GPIO_Pin_5);
//DAC配置
dac.DAC_Trigger = DAC_Trigger_None;//不使用触发使能功能 TEN1=0
dac.DAC_WaveGeneration = DAC_WaveGeneration_None;dac.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;dac.DAC_OutputBuffer = DAC_OutputBuffer_Disable;
DAC_Init(DAC_Channel_2, &dac);
DAC_Cmd(DAC_Channel_2, ENABLE);
g_DAC_chan2_inited = 1;
DAC_PA5SetVoltage(0);//设置DAC通道1输出电压,mV
}
(3)设置DAC输出电压函数
/** 设置DAC通道1电压值,单位:mV,值域:0~3300 */
void DAC_PA4SetVoltage(uint16_t mV) {
if(g_DAC_chan1_inited != 1)
DAC_Chan1Init();
uint16_t dac_val = (uint16_t)((float)mV / 1000 / 3.3 * 4096);
DAC_SetChannel1Data(DAC_Align_12b_R, dac_val);
}
/** 设置DAC通道2电压值,单位:mV,值域:0~3300 */
void DAC_PA5SetVoltage(uint16_t mV) {
if(g_DAC_chan2_inited != 1)
DAC_Chan2Init();
uint16_t dac_val = (uint16_t)((float)mV / 1000 / 3.3 * 4096);
DAC_SetChannel2Data(DAC_Align_12b_R, dac_val);
}
(4)输出DAC电压
int main(void) {
DAC_PA4SetVoltage(1536); //PA4引脚输出1.536V
DAC_PA5SetVoltage(2815);//PA5引脚输出2.815V
}
STM32编程-SPI_数据存储_外部FLASH存储器(1)
1: STM32 有多个 SPI 外设,STM32F10x 系列就拥有 3 个 SPI。
2: 实际应用中,一般不使用 SPI 外设的标准 NSS 信号线,而是更简单地使用普通的 GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号。
3:注意 SPI3 中有些引脚与 SWD 下载引脚重合了,这对我们使用和学习有影响,因此在做 SPI 实验时应避免使用 SPI3。3:如果要使用这两个通道,就不能与其它设备共用引脚。
4、以W25Qxx系列Flash芯片为例,W25Qxx每个读命令可以读出任意字节数,但是,W25Qxx每个写命令最大写入1个Page(256字节数);
5、SPI读写外部Flash指令(注意,读数据不要获取读权限,写数据需要获取写权限):
(a)发: 0x09 0x00 0x00 0x00 读取芯片型号
收:2字节(高低字节谁先收到看设置SPI_InitTypeDef.SPI_FirstBit)
(b)发: 0x03 address(W25Q128 只用3字节, W25Q256用4字节) 读取某个地址开始的数据
收:通过连续读取寄存器DR,可以读取N字节(任意数目)
(c)发: 0x06 请求写权限
收:发送 0x06后,循环查询是否获取写权限while (SPI1_RW(0xFF) & 1) {} ,通过读取寄存器DR,可以读取N字节(任意数目,最后一个字节是0x00,其它全部是0x01)
(d)发: 0x20 address(W25Q128 只用3字节, W25Q256用4字节) 擦除某个地址开始的1个扇区(4096字节)
收:发送 0x20 address后,循环查询是否擦除操作执行完毕 while (SPI1_RW(0xFF) & 1) {} ,通过读取寄存器DR,可以读取N字节(任意数目,最后一个字节是0x00,其它全部是0x01)
(e)发: 0x02 address(W25Q128 只用3字节, W25Q256用4字节) 开始向某个地址写入1个Page(256字节)的数据
收:发送 0x02 address后,循环查询是否写入操作执行完毕 while (SPI1_RW(0xFF) & 1) {} ,通过读取寄存器DR,可以读取N字节(任意数目,最后一个字节是0x00,其它全部是0x01)
STM32编程-SPI_数据存储_外部FLASH存储器(2)
1: 第一步,初始化SPI1时钟和引脚
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//注意:SPI2和SPI3是APB1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitTypeDef gpio;
gpio.GPIO_Pin = GPIO_Pin_13; //GPIOC-Pin-13连NSS(SPI使能)
gpio.GPIO_Mode = GPIO_Mode_Out_PP;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &gpio);
gpio.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;//GPIOA-Pin5/6/7分别连CLK/MISO/MOSI
gpio.GPIO_Mode = GPIO_Mode_AF_PP;
//gpio.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio);
2:第二步,初始化SPI(寄存器方式和标准库函数,这2种方式都可以)
//寄存器编程方式
/* SPI1 -> CR1 = 0x1 << 0; // CPHA:时钟相位,0x1=在第2个时钟边沿进行数据采样
SPI1 -> CR1 |= 0x1 << 1; // CPOL:时钟极性,0x1=空闲状态时,SCK保持高电平
SPI1 -> CR1 |= 0x1 << 2; // 主从模式: 1 = 主配置
SPI1 -> CR1 |= 0x0 << 3; // 波特率控制[5:3]: 0 = fPCLK /2
SPI1 -> CR1 |= 0x0 << 7; // 帧格式: 0 = 先发送MSB
SPI1 -> CR1 |= 0x1 << 9; // 软件从器件管理 : 1 = 使能软件从器件管理(软件NSS)
SPI1 -> CR1 |= 0x1 << 8; // 内部从器件选择,根据9位设置(失能内部NSS)
SPI1 -> CR1 |= 0x0 << 11; // 数据帧格式, 0 = 8位
SPI1 -> CR1 |= 0x1 << 6; // SPI使能 1 = 使能外设*/
//标准库函数编程方式
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);
SPI1读写函数定义
uint8_t SPI1_RW(uint8_t d) {
uint8_t retry = 0;
while ((SPI1->SR & 2) == 0) { // 等待发送区为空
retry++;
if (retry > 250) return 0;
}
SPI1->DR = d;
retry = 0;
while ((SPI1->SR & 1) == 0) { // 等待接收完数据
retry++;
if (retry > 250) return 0;
}
uint8_t DD = (uint8_t)SPI1->DR;
return DD;
}
STM32编程-数据存储_内部FLASH存储器
(1)STM32的内部Flash可以理解为PC机上的硬盘/U盘等存储设备;
(2)读数据:可以跟读取内存类似(*v = *addr),使用*v = *(__IO addr),不同点就是,Flash存储器地址前要加上 “__IO “,而内存地址不需要加上这个符号;
(3)写数据注意:
(a)写入数据必须是偶数字节数(Flash寄存器每次是16位写入);
(b)存于芯片内部flash的数据量,只适合少量数据(例如1K),不适合几十K的数据;
(c)写入数据存储地址必须在“0x80000000+代码字节数”之后,0x80000000是基地址;
(d)写数据前要解锁Flash:
FLASH->KEYR = ((uint32_t)0x45670123);
FLASH->KEYR = ((uint32_t)0xCDEF89AB);
(e)写数据要一个扇区一个扇区写:
第一步,等Flash空闲,读取扇区,并将要写入的数据写入指定位置;
if(FlashWaitForBSY(0x00888888)) return 2;
FlashInnerRead(sec_pos * STM32_FLASH_SECTOR_SIZE + STM32_FLASH_ADDR_BASE, g_sectorbuffer_temp, STM32_FLASH_SECTOR_SIZE);
for(uint16_t i = 0; i < sec_remain; i++) {
g_sectorbuffer_temp[sec_off + i] = data[i];
}
第二步,等Flash空闲,擦除扇区;
//擦除指定页(扇区)
if(FlashWaitForBSY(0x00888888)) return 2;
FLASH->CR |= 1<<1; //页擦除
FLASH->AR = sec_pos * STM32_FLASH_SECTOR_SIZE + STM32_FLASH_ADDR_BASE; //要擦除的页地址
FLASH->CR |= 0x40;//触发擦除动作(该位写1时触发擦除动作)
if(FlashWaitForBSY(0x00888888)) return 2;
FLASH->CR &= ((uint32_t)0x00001FFD);//关闭页擦除功能
第三步,写入(编程)Flash,每次写入2个字节(Flash寄存器是16位)
for(uint16_t i = 0; i < STM32_FLASH_SECTOR_SIZE / 2; i++) {
if(FlashWaitForBSY(0x00888888)) return 2;
FLASH->CR |= 0X01 << 0; //编程
*(uint16_t*)(STM32_FLASH_ADDR_BASE + sec_pos*STM32_FLASH_SECTOR_SIZE +i*2) = (data[i*2+1]<<8) | data[i*2] ;
if(FlashWaitForBSY(0x00888888)) return 2;
FLASH->CR &= ((uint32_t)0x00001FFE) ; //关闭编程
}
等待内部Flash空闲函数
static uint8_t FlashWaitForBSY(uint32_t timeOut) {
while((FLASH->SR & 0x01) && (timeOut-- != 0x00)) ; // 等待BSY标志空闲
if(timeOut ==0)
return 1; // 失败,返回1, 等待超时;
return 0; // 正常,返回0
}
您的打赏是我写作的动力!