STM32那些事
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
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上追加同步方式的序列信号变换电路的产品被称为USARTUniversal Synchronous Asynchronous Receiver Transmitter。
UART是USART的子集STM32的串口就是USART设备。
STM32的串口发送数据编程有3种编程方法
1基本数据发送
优点代码和逻辑都简单容易理解新手必备
缺点利用while不断检测发送状态+等待挨个字节发出其实很费时如115200波特率发送100个字节至少用时8700us!!而且发送期间中断响应外整个程序、系统不能干其它事如假死一般
2DMA数据发送
优点占用资源最少发送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); //非存储器到存储器模式
3DMA数据发送
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系列包含常规定时器和专用定时器。
1SysTick定时器
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=72MSTM32F103RC的芯片
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个通道。
1ADC1初始化函数
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同时这两个引脚上也不能接上其它设备的电路如上拉电阻等
1DAC1初始化函数
/** 初始化DAC通道1PA4并设置输出模拟电压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
}
2DAC1初始化函数
/** 初始化DAC通道2PA5并设置输出模拟电压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个Page256字节数;
5、SPI读写外部Flash指令注意读数据不要获取读权限写数据需要获取写权限
a发 0x09 0x00 0x00 0x00 读取芯片型号
收2字节高低字节谁先收到看设置SPI_InitTypeDef.SPI_FirstBit
b发 0x03 addressW25Q128 只用3字节, W25Q256用4字节 读取某个地址开始的数据
收通过连续读取寄存器DR可以读取N字节任意数目
c发 0x06 请求写权限
收发送 0x06后循环查询是否获取写权限while (SPI1_RW(0xFF) & 1) {} ,通过读取寄存器DR可以读取N字节任意数目最后一个字节是0x00其它全部是0x01
d发 0x20 addressW25Q128 只用3字节, W25Q256用4字节 擦除某个地址开始的1个扇区4096字节
收发送 0x20 address后循环查询是否擦除操作执行完毕 while (SPI1_RW(0xFF) & 1) {} ,通过读取寄存器DR可以读取N字节任意数目最后一个字节是0x00其它全部是0x01
e发 0x02 addressW25Q128 只用3字节, W25Q256用4字节 开始向某个地址写入1个Page256字节的数据
收发送 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连NSSSPI使能
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存储器
1STM32的内部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
}
您的打赏是我写作的动力