【1】STM32简介
-
命名规范 STM32F051K8U6 ST - 公司 M - micro eletronics 32 - 32位处理器 STM8 F - 基础版 L - 低功耗 G - 电源 051 - 入门级别 103 - 主流级别 407 - 高性能 K - 32个管脚 8 - 64K U - UQFN 封装类型 6 - 工作温度范围 -
STM32 和ARM 的关系? STM32 的CPU 是采用的ARM-CortexM0 架构的
ARM 处理器命名规范 ARM7\9\11 Cortex - A : 开放式操作系统 手机 智能电视 Cortex - R : 实时系统 汽车电子 Cortex - M : 低成本的优化解决方案
实时操作系统:一般用于单片机 分时操作系统:通过时间片轮转进行调度工作 Linux典型的分时操作系统 最大区别就是响应速度。
处理器和架构? 三星S5P6818 ARM-cortexA53 v8 麒麟990 4ARM-cortexA55+4ARM-cortexA76 v8
【2】Cortex-M0架构
Cortex-M0 主要功耗和性能的平衡 Cortex-M0 微处理器主要包括处理器内核、嵌套向量中断控制器(NVIC )、调试子系统、内部总线系统构成,通过高性能总线(AHB-LITE )与外部进行通信。
两种工作模式:
- 线程模式(Thread Mode):芯片复位后,即进入线程模式,执行用户程序;
- 处理模式(Handler Mode):当处理器发生了异常或者中断,则进入处理模式进行处理、处理完成后返回线程模式
两种工作状态:
Thumb 状态:正常运行时处理器的状态- 调试状态:调试程序时处理器的状态
相关寄存器:
R0-R12 13个通用寄存器R13 (SP 栈指针):Cortex-M0 在不同物理位置上存在两个栈指针,主栈指针 MSP ,进程栈指针PSP 。在处理模式下,只能使用主堆栈,在线程模式下,可以使用主堆栈也可以使用进程堆栈,这主要是由 CONTROL 寄存器控制完成。系统上电的默认栈指针是MSP 。R14 (LR 链接寄存器):程序跳转时保存当前正在执行的下一条指令地址。R15 (PC 程序计数器):存储下一条将要执行的指令的地址。- 特殊寄存器:
xPSR :组合程序状态寄存器,该寄存器由三个程序状态寄存器组成- 应用
PSR (APSR ):包含前一条指令执行后的条件标志 - 中断
PSR (IPSR ):包含当前ISR 的异常编号 - 执行
PSR (EPSR ):包含Thumb 状态位
CortexM0支持的异常和中断:
Cortex-M0 处理器最多支持 32 个外部中断(通常称为 IRQ)和一个不可屏蔽中断(NMI), 另外 Cortex-M0 还支持许多系统异常(Reset、HardFault、SVCall、PendSV、SysTick )
指令集:
-
ARM 指令集 32位精简指令集; 指令长度固定; 降低编码数量产生的耗费,减轻解码和流水线的负担; -
Thumb 指令集 Thumb 指令集是ARM 指令集的一个子集; 指令宽度16位; 与32位指令集相比,大大节省了系统的存储空间; Thumb 指令集不完整,所以必须配合ARM 指令集一同使用。
【3】CortexM0的寄存器映射
- 寻址空间 0-4G
32位 2^32次方 4G = 4 * 1024 * 1024 * 1024 = 4,294,967,296 - 寄存器地址映射
在编程手册中找到GPIO相关寄存器,GPIOA的起始地址为0x28000000 MODER 0x00 OTYPER 0x04
#define GPIOA_BASE ((usigned int)0x28000000)
#define GPIOA_OTYPER *(unisgned int *)(GPIOA_BASE+0x04)
封装成结构体:
typedef struct
{
__IO uint32_t MODER;
__IO uint32_t OTYPER;
__IO uint32_t OSPEEDR;
__IO uint32_t PUPDR;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t LCKR;
__IO uint32_t AFR[2];
__IO uint32_t BRR;
} GPIO_TypeDef;
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
GPIOA->OTYPER = 0X20;
位操作赋值:
- 清零用与,置一用或。
将GPIOA->OTYPER 的第6位置1,第7位置0。 先清零后置位:
GPIOA->OTYPER = GPIOA->OTYPER & ~(0x3 << 6) | (1<<6)
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOA_BASE (AHB2PERIPH_BASE + 0x00000000UL)
#define AHB2PERIPH_BASE (PERIPH_BASE + 0x08000000UL)
#define PERIPH_BASE 0x40000000UL
- 实际GPIOA的基地址为0x40000000+ 0x08000000 = 0x48000000
【4】启动文件
Stack_Size EQU 0x400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
Heap_Size EQU 0x200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
?
【实验】通过寄存器点亮LED灯
- ? 点亮
D10 绿灯 - LED4 - 核心板PB0 ? 让PB0 这个管脚输出低电平即可
- 使能
GPIOB 的时钟 ? 将RCCAHBENR 的第18位置1 ? RCC->AHBER |= 1<<18; - 配置
PB0 为输出模式 ? 将GPIOBMODER 的第0位置1,第1位清零。 ? GPIOB->MODER &= ~(0x3); ? GPIOB->MODER |= 0x1; - 配置
PB0 为推挽输出 ? 将GPIOBOTYPER 的第0位清零; ? GPIOB-> OTYPER &= ~0x1 - ? 将
GPIOBODR 的第0位清零 ? GPIOB->ODR &= ~0x1 ?
【5】GPIO 通用输入输出口
1.输出类型
三极管(流控) 放大状态:发射结(BE)正偏,集电结(BC)反偏。 饱和状态:发射结正偏,集电结正偏。 截止状态:发射结反偏,集电结反偏。
推挽输出:具备输出高低电平的能力。 开漏输出:具备输出低电平的能力,可通过外加上拉电阻输出高电平。
2.输入类型
浮空输入 :IO -> 施密特触发器 -> 输入寄存器 -> 读 模拟输入 : IO -> 输入寄存器 -> 读 上拉输入 : IO -> 上拉电阻 -> 施密特触发器 -> 输入寄存器 -> 读 下拉输入 :IO -> 下拉电阻 -> 施密特触发器 -> 输入寄存器 -> 读
3.GPIO相关寄存器
- 4个32位配置寄存器
GPIOx_MODER 模式寄存器GPIOx_OTYPER 输出类型寄存器GPIOx_OSPEEDR 输出速度寄存器GPIOx_PUPDR 上拉下拉寄存器 浮空取决于外设的电平 上拉 - 高 下拉 - 低 - 2个32位数据寄存器
GPIOx_IDR 输入数据寄存器GPIOx_ODR 输出数据寄存器 - 1个32位 置位/复位寄存器
GPIOx_BSRR - 2个32位 复用功能配置寄存器
GPIOx_AFRH 复用功能高位寄存器GPIOx_AFRL 复用功能低位寄存器
【实验】点亮一个LED灯
- 查看电路原理图
在底板原理图上找到对应的LED灯 LED2 LED3 LED4 在核心板原理图上找到对应控制管脚 PB2 PB1 PB0 - 查看芯片手册找到对应的控制寄存器
-
开启GPIOB的时钟 RCC_AHBENR 位 18 IOPBEN: GPIOB 时钟使能 由软件置 1 或清 0. 0: GPIOB 时钟关闭 1: GPIOB 时钟开启 RCC->AHBENR |= 1<<18 -
选择输出模式 GPIOB_MODER 010101 MODERy[1:0]: 端口 x 配置位 (y = 0…15) 这些位可由软件写来配置 I/O 口模式。 00: 输入模式 ( 复位状态 ) 01: 通用输出模式 10: 复用功能模式 11: 模拟模式 GPIOB->MODER |= (1<<0)|(1<<2)|(1<<4) -
选择输出类型 GPIOx_OTYPER OTy[1:0]: 端口 x 的配置位 (y = 0…15) 这些位可由软件写来配置 I/O 口的输出类型。 0: 推挽输出 ( 复位状态 ) 1: 开漏输出 GPIOB->OTYPER = 0x0; -
配置输出数据 GPIOB_BSRR 端口置位 / 复位寄存器 位 31:16 BRy: 端口 x 复位位 y(y = 0…15) 这些位只写。读这些位时返回 0x0000 数值。 0: 对相应的 ODRx 位无影响 1: 复位相应的 ODRx 位 GPIOB -> BSRR = (0x7 << 16);
void LED_Init()
{
RCC->AHBENR |= 1<<18;
GPIOB->MODER |= (1<<0)|(1<<2)|(1<<4);
GPIOB->OTYPER = 0x0;
}
void main()
{
while (1)
{
GPIOB -> BSRR = (0x7 << 16);
HAL_Delay(500);
GPIOB -> BSRR = 0x7;
HAL_Delay(500);
}
}
【6】HAL库编程版本
- GPIO写函数
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState) 功能:给定GPIO引脚写入指定数据 参数:
GPIO_TypeDef* GPIOx 端口 A...F
uint16_t GPIO_Pin 引脚编号 0 - 15
GPIO_PinState PinState
GPIO_PIN_RESET: 清0 低电平
GPIO_PIN_SET: 置1 高电平
返回值:空
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2, GPIO_PIN_RESET);
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0|GPIO_PIN_2,GPIO_PIN_SET);
HAL_Delay(200);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_2,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0|GPIO_PIN_1,GPIO_PIN_SET);
HAL_Delay(200);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1|GPIO_PIN_2,GPIO_PIN_SET);
HAL_Delay(200);
【实验】 五向按键控制实验
- 五向按键通过一个或门由
D3&KEY 输出 只要有一个方向按键按下,则能够从D3&KEY 这个管脚读到高电平 - 核心板
PA8 管脚连接D3&KEY 读PA8 管脚输入的信号即可判断按键是否被按下 - 读
PA8 管脚的值
HAL_GPIO_ReadPin GPIO_PinState HAL_GPIO_ReadPin (GPIO_TypeDef * GPIOx,uint16_t GPIO_Pin) 功能:读管脚的电平值 参数:GPIOx 端口号 GPIO_Pin 管脚编号 返回值:GPIO_PinState 管脚实际的电平值
?
if(HAL_GPIO_ReadPin(KEY_GPIO_Port,KEY_Pin) == 1)
{
HAL_GPIO_WritePin(LED4_GPIO_Port,LED4_Pin,0);
}else
{
HAL_GPIO_WritePin(LED4_GPIO_Port,LED4_Pin,1);
}
HAL_Delay(100);
void HAL_GPIO_TogglePin (GPIO_TypeDef * GPIOx, uint16_tGPIO_Pin) 功能:电平翻转 参数:GPIOx 端口号 GPIO_Pin 管脚编号 返回值:空
if(HAL_GPIO_ReadPin(KEY_GPIO_Port,KEY_Pin) == 1)
{
while(HAL_GPIO_ReadPin(KEY_GPIO_Port,KEY_Pin) == 1);
HAL_GPIO_TogglePin(LED4_GPIO_Port,LED4_Pin);
}
【7】通信相关基础知识
- 通信对象最少要有两个
-
根据时钟源可以区分为:
- 同步通信:通信双方共用一个时钟信号。
- 异步通信:通信双方各自都有一个独立时钟源,双方约定好通信速度。
-
通过通信方式区分为:
- 串行通信:用一根数据线进行通信,同一时刻只接收一个bit位数据
优点:节省资源,占用管脚少 缺点:通信效率低 - 并行通信:用多根信号线同时进行同时,同一时刻能够接收多个bit位数据
优点:通信效率高 缺点:占用管脚资源多 -
根据传输方向区分为: 单工:只能作为接收设备或者发送设备,要么收要么发。 广播、收音机 半双工:可以接收也可以发送,但是同一时间只能发送或接收。对讲机 全双工:同一时间既可作为发送端又可作为接收端。手机
【8】串口USART – 通用同步异步收发器
UART – 异步通信 串行 全双工 USART – 同步通信 (5v)TTL 电平 : 逻辑1 :2.4v ~ 5v 逻辑0: 0 ~ 0.5v
EIA电平(RS232): 逻辑1 : -3v ~ -15v 逻辑0:+3v ~ +15v 15m RS485 :双绞线 差分信号 1300m 逻辑1 : 2v ~ 6v 逻辑0:-2v ~ -6v 单片机 — 串口线 — 电脑 CH340 USB转串口芯片
【9】串口通信协议
起始位(1位):低电平 数据位(8位): 校验位(1位):奇偶校验 停止位(1位):高电平 奇校验:数据位中1的个数+校验位上1的个数为奇数 偶校验:数据位中1的个数+校验位上1的个数为偶数 例如: 发送01010101采用奇校验,校验位应该为1
数据接收过程 : RX -> 接收移位寄存器 -> 接收数据寄存器 -> CPU or DMA 数据发送过程 : CPU or DMA -> 发送数据寄存器 -> 发送移位寄存器 —> TX
【10】串口相关寄存器
配置寄存器: 控制寄存器 USART_CR1 CR2 波特率寄存器 USART_BRR
发送数据寄存器: 中断和状态寄存器USART_ISR 接收数据寄存器 USART_RDR 发送数据寄存器USART_TDR
【11】串口发送实验
- 分析原理图
PA9 – USART1_TX PA10 – USART1_RX - 配置
CubeMX 使能USART1 - 异步通信Asynchronous 配置USART1 参数 波特率 – 115200 数据位 –8位 校验位 – NONE 停止位 – 1位 - 编写发送函数
void My_Putchar(uint8_t ch)
{
while(!(USART1->ISR & (1<<7)));
USART1->TDR = ch;
}
【12】串口接收实验
完成串口接收函数 实现大小写转换 例如:通过串口给单片机发送大写字母A,单片机返回小写字母a。 先实现串口接收函数:
uint8_t My_Getchar(void)
{
uint8_t ch;
while(!(USART1->ISR & (1<<5)));
ch = USART1->RDR;
return ch;
}
在main函数里实现:
while(1){
ch = My_Getchar();
if(ch>='A'&&ch<='Z')
{
My_Putchar(ch + 32);
}else if(ch>='a'&&ch<='z')
{
My_Putchar(ch - 32);
}else
{
My_Putchar('?');
}
}
【13】HAL库函数版本
- HAL库版本发送数据:
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout) UART_HandleTypeDef *huart : 句柄 (注意取地址) uint8_t *pData : 发送的字符数组首地址 uint16_t Size : 发送数据长度 uint32_t Timeout : 超时时间(规定时间内没完成发送返回HAL_TIMEOUT
HAL_UART_Transmit(&huart1, "hello test\n", strlen("hello test\n"), 100);
-
HAL库版本接收数据: HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout) -
通过HAL库函数实现串口收发:
uint8_t str[32] = {0};
while(1){
HAL_UART_Receive(&huart1, str,sizeof(str), 100);
HAL_UART_Transmit(&huart1, str, strlen(str), 100);
memset(str,0,sizeof(str));
}
【13】printf重定向到串口工具实验
a想要发送一个不定长度的到串口 将printf打印的内容输出到串口中,因为printf使用标准库fputc 那么我们将fputc 重写即可 添加stdio.h 头文件,编译后查找fputc 函数 将函数原型找出来 extern _ARMABI int fputc(int /*c*/, FILE * /*stream*/) __attribute__((__nonnull__(2))); 重写fputc函数:
int fputc(int ch, FILE *file)
{
while(!((huart1.Instance->ISR) & (1<<7)));
huart1.Instance->TDR = ch;
return ch;
}
007A1200 02DC6C00 在main.c里测试printf: printf(“hello world\n”);
【13】scanf输入重定向实验
重写fgetc 函数
extern _ARMABI int fgetc(FILE * ) __attribute__((__nonnull__(1)));
int fgetc(FILE * file)
{
int ch;
while(!((huart1.Instance->ISR) & (1<<5)));
ch=huart1.Instance->RDR;
return ch;
}
在main 函数里通过scanf和printf实现输入输出: scanf("%s",buf); printf("%s",buf); 注意: 在串口工具里发送字符串时记得添加/r/n结束标志 (可参考群里图片设置自动发送附加位)
【14】中断介绍
-
概念:当处理器在正常执行程序时,突然有外部/内部中断事件发生, 此时需要暂停当前正在执行的程序,并进行备份,转而去处理中断事件, 处理完成后,返回当时程序执行位置继续执行。 -
举例 老师正在讲课 (正常执行的程序) 老徐叫我去开会 (外部中断信号) 暂停讲课保存课堂笔记 (压栈保护现场) 跳转到中断处理程序 (根据异常向量表进行跳转) 开会 (执行中断处理程序) 会后回来继续上课 (出栈恢复现场) -
意义:提高突发事件的处理效率 -
NVIC 嵌套向量中断控制器
-
管理中断 ? 每一个外部中断进来都可能被执行或禁止,并可以设置为挂起状态或清除状态。 ? 挂起:当处理器正在处理高优先级事件时,低优先级中断事件会先被设置为挂起状态。 ? 清除:当处理完中断处理程序时,可将该中断事件设置为清除状态。 -
支持中断或异常的向量化处理 ? 当异常或中断发生时,给定PC一个特定的地址,对应各个中断或异常处理程序的执行地址,这个按照优先级排列组成的地址列表称为中断向量表。 异常:来自于处理器内部的异常事件 ? 中断:来自于处理器外部的中断事件 -
支持中断嵌套
- 有3个固定优先级,都是负值,不可改变。
- 优先级数越小,优先级越高。
- 由两个bit位控制的可配置优先级 00 01 10 11
- 抢占式优先级
? 抢占,高优先级中断事件可以打断正在处理的低优先级中断事件 ? 响应式优先级 ? 两个中断事件同时到达时,处理响应优先级高的事件。 ?
【实验】按键中断
? 按键按下,触发中断控制串口打印“interrupt”
-
查看芯片原理图 ? 按键 – PA8 ? 使能UART1为异步通信模式 – PA9 PA10 -
在CubeMX中进行配置
- uart1配置为ASY异步模式
- PA8管脚配置为GPIO_EXTI8外部中断模式
- 在NVIC详细配置里使能外部中断EXTI4to15
-
打开工程 ? 启动文件中找到对应的中断向量 ? DCD EXTI0_1_IRQHandler ; EXTI Line 0 and 1 ? DCD EXTI2_3_IRQHandler ; EXTI Line 2 and 3 ? DCD EXTI4_15_IRQHandler ; EXTI Line 4 to 15 ? -
找到中断处理函数
void EXTI4_15_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_8);
}
- 继续跳转
外部中断处理程序
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
HAL_GPIO_EXTI_Callback(GPIO_Pin);
}
}
- 进入中断回调函数
__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
UNUSED(GPIO_Pin);
}
在gpio.c里重写中断回调函数:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == GPIO_PIN_8)
{
printf("interrupt!");
}
}
【15】串口中断
-
串口发送完成中断 串口发送“hello”完成之后触发中断事件,在中断中发送“world”。
- 在CubeMX中将UART1设置为异步模式
- 在NVIC中使能串口全局中断
USART1 global interrupt I USART1 wake-up interrupt through EXTI line 25 - 生成工程后编译,在启动文件中找到对应中断向量:
DCD USART1_IRQHandler ; USART1 void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);
}
- 找到串口中断处理函数
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart) 发送完成中断 UART_EndTransmit_IT(huart); - 发送完成中断回调函数
static void UART_EndTransmit_IT(UART_HandleTypeDef *huart) { ATOMIC_CLEAR_BIT(huart->Instance->CR1, USART_CR1_TCIE); huart->gState = HAL_UART_STATE_READY; huart->TxISR = NULL; #if (USE_HAL_UART_REGISTER_CALLBACKS == 1) huart->TxCpltCallback(huart); #else HAL_UART_TxCpltCallback(huart); #endif } - 重新发送完成中断回调函数
__weak void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { UNUSED(huart); } 在uart.c中重写 void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { HAL_UART_Transmit(&huart1,"world",sizeof("world"),100); } - 在main.c中触发发送完成中断
HAL_UART_Transmit_IT(&huart1,"hello",sizeof("hello")); -
串口接收完成中断 //重写接收完成中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Transmit(&huart1,"Recive 4 char",sizeof("Recive 4 char"),100);
}
while (1)
{
HAL_UART_Receive_IT(&huart1,str,4);
HAL_Delay(1000);
printf("%s",str);
memset(str,0,sizeof(str));
}
【实验】五向按键电压采集实验
- 查看电路原理图
找到按键ADC监测管脚 – PA0 - 配置CubeMX
配置PA0管脚为ADC_CH0 使能串口为异步通信模式 使能RCC时钟配置时钟选择高速外部时钟48Mhz - 生成工程
需要使用的函数: HAL_ADC_Start 开始转换 HAL_ADC_PollForConversion 等待转换 HAL_ADC_GetValue 获取转换结果 HAL_ADC_Stop 停止转换 - 代码实现:
while(1)
{
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_8))
{
HAL_Delay(100);
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_8))
{
HAL_ADC_Start(&hadc);
HAL_ADC_PollForConversion(&hadc,100);
value = HAL_ADC_GetValue(&hadc);
HAL_ADC_Stop(&hadc);
printf("key:%d",value);
}
}
【16】STM32的时钟系统
- 时钟系统是由振荡器(信号源),定时唤醒器,分频器等组成的电路。
常用的信号源:石英晶体 RC振荡器 - 震荡不稳 停振 不起振
- 倍频:CPU需要更高的频率,石英晶体成本高,震荡频率高不稳定,所以需要把低频率倍频到高频率
分频: CPU频率很高但是外设不需要这么高的频率 - STM32四个时钟源
- HSI 高速内部时钟 RC振荡器 8MHZ
- HSE 高速外部时钟 石英晶体/陶瓷 8MHZ
- LSI 低速内部时钟 RC振荡器 40KHZ 看门狗用的时钟源只能是这个,有时候也给RTC时钟源
- LSE 低速外部时钟 32.768KHZ的使用晶体 给RTC做时钟源
【17】systick定时器
- 定时器: 能够计数和定时的器件称为定时器,systick又叫滴答定时器,是一个定时设备,(定时的本质是计数-保证时钟信号是周期性的),systick位于Contex_M0的内核当中,和NVIC是捆绑的,产生一个systick的异常,IRQ异常号是15
- 滴答定时器 是一个24位的递减定时器,最多能计数2^24(0xFFFFFF),减到0的时候出发中断,再次重装值继续减1
【实验】 追Systick - 8M的时基
- 配置CubeMx 时钟树-高速内部时钟-8M(使用默认)
- DCD SysTick_Handler
void SysTick_Handler(void)
{
HAL_IncTick();
}
__weak void HAL_IncTick(void)
{
uwTick += uwTickFreq;
}
__IO uint32_t uwTick;
HAL_TickFreqTypeDef uwTickFreq = HAL_TICK_FREQ_DEFAULT;
HAL_TICK_FREQ_1KHZ = 1U
HAL_TICK_FREQ_DEFAULT = HAL_TICK_FREQ_1KHZ
- main.c
/* Reset of all peripherals, Initializes the Flash interface and the Systick. / 初始化外设 flash和Systick HAL_Init(); / Use systick as time base source and configure 1ms tick (default clock after Reset is HSI) */ 使用高速内部时钟配置systick时基是1ms (就算配置了48M在复位之后第一次也是HSI-8M) HAL_InitTick(TICK_INT_PRIORITY); Care must be taken: 外设中断调用HAL_Dela的时候,要注意中断优先级的问题,必须要提高systick的优先级(数字越小越高),否则会遇到阻塞/屏蔽的情况
- 1.中断处理函数为什么要延时? 2.NVIC捆绑内核组件,两个中断优先级会有影响。
/Configure the SysTick to have interrupt in 1ms time basis/ if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U) { return HAL_ERROR; } uint32_t SystemCoreClock = 8000000; HSI - 8MHZ 1/8M秒可以计一个数,计了8M/1000个数 8M/(1000/1) == 1/8M8M/1000=1/1000s = 1ms uint32_t HAL_SYSTICK_Config(uint32_t TicksNumb) { return SysTick_Config(TicksNumb); } SysTick->LOAD = (uint32_t)(ticks - 1UL); / set reload register 配置重装寄存器*/ NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); //设置优先级
【实验】 HAL_Delay函数
__weak void HAL_Delay(uint32_t Delay)
{
uint32_t tickstart = HAL_GetTick();
uint32_t wait = Delay;
if (wait < HAL_MAX_DELAY)
{
}
while((HAL_GetTick() - tickstart) <= 10*wait)
{
1-1 < 11
2-1 < 11 1-11 --- 10ms
12 - 1= 11
}
}
__weak uint32_t HAL_GetTick(void)
{
return uwTick;
}
【实验】 48M高速外部时钟 - 查看重装值
上电之后还是8M 一次过后会改变成我们设置的值
- debug
SysTick->LOAD = (uint32_t)(ticks - 1UL); - 搜索
/* Update the SystemCoreClock global variable */ SystemCoreClock = HAL_RCC_GetSysClockFreq() >> AHBPrescTable[(RCC->CFGR & RCC_CFGR_HPRE)>> RCC_CFGR_HPRE_BITNUMBER];
/* Configure the source of time base considering new system clocks settings*/ HAL_InitTick (TICK_INT_PRIORITY);
【18】通用定时器
- F051有6个通用定时器,一个基本定时器,一个高级定时器
- 通用定时器: TIM2 3 14 15 16 17
定时器的定时和计数 输入捕获 输出比较 – 输出PWM波形 - 基本定时器 TIM6
定时器的定时和计数 - 高级定时器
带死区的PWM和刹车功能 - 定时器计数模式
向上计数 计数器从0开始向上加到加载值(TIMx_ARR)触发一个计数器向上计数溢出中断,然后从0开始重新计数 向下计数 计数器从重装值(TIMx_ARR)开始向下减,减到0触发向下计数溢出中断,然后重装初始值 中央对齐(向上向下) 计数器从0开始向上加到加载值(TIMx_ARR), 计数器从重装值(TIMx_ARR)开始向下减
- 假设现在频率是48M 用定时器实现1S的定时
48 / 48(0-47) = 1MHZ * 1M 个 = 1S 频率HZ 周期S 计数个
-
输入捕获和输出比较 1)输入捕获 :用来获取外部事件的 2)输出比较: 定时器从0开始到CCR的值输出低电平,从CCR到ARR的值输出高电平,循环此过程 CCRX:占空比 ARR:周期 PWM:脉冲宽度调制 -
寄存器 TIMx_CNT 计数器当前的值 TIMx_ARR 自动重装值 TIMx_CCRx 捕获/比较寄存器
【实验】 用通用定时器TIM2实现定时1S中断在中断处理函数里打印字符
- CubeMx配置
使用HSI 写48M USART - 115200 Precasler=48-1 //分频 conter = 1000000-1 //重装值1M个 NVIC里中断打开 - DCD TIM2_IRQHandler
void TIM2_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim2);
}
定时器数值更新
if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_UPDATE) != RESET)
{
if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_UPDATE) != RESET)
{
__HAL_TIM_CLEAR_IT(htim, TIM_IT_UPDATE);
HAL_TIM_PeriodElapsedCallback(htim);
}
}
【实验】 软件定时器 实现上述功能 模拟HAL_Delay函数
-
设计思想:
-
设计一个定时器 (软件层次 结构体:开始时间 想要延时的时间) -
函数: 设定定时时间的函数 比较定时时间是否达到 -
CubeMX 新建一个工程 48M 开两个LED -
新建文件 file -new softtimer.c --> core/src file -new softtimer.h --> core/inc 在Application user上右键选择Add existing file将.c文件添加进来(.h不需要这个操作) -
softtimer.h 里 #ifndef __SOFTTIMER_H__ #define __SOFTTIMER_H__ #include "stm32f0xx.h" —>是在左边Driver/CMSIS 的文件夹下的system_stm32f0xx.c 下找到的头文件 #endif -
softtimer.c 里 setTimer compareTimer 两个函数 -
main.c 文件 测试功能
【19】ADC模数转化
-
ADC逐次逼近形的模拟-数字转化器(采样 量化 编码) *模拟信号和数字信号的区别?是否连续 *dht11-温湿度传感器-直接出数字信号 -
ADC特性 量程:能测量的电压的范围 分辨率: ADC的分辨率通常以二进制数表示,位数越多分辨率越高,转化时间越长 转化时间:从转化开始到获得稳定的数字信号的时间 19个通道:16个外部(对应16个引脚),3个内部(芯片的温度 定时器的信息 内部参考电压) ADC的结果以左对齐或者右对齐的方式存在16位的寄存器当中 ADC最高的频率14M(时钟树查看) -
转化方式 单通道单单次转化 单通道连续转化 多通道扫描单次转化 多通道扫描连续转化 间隔转化 存在AD的中断和状态寄存器ADC_ISR EOC:通道转化结束 EOS:序列转化结束
【实验】 基于五项按键的AD采集实验(判断方向,打印上下左右)
-
查看电路图 ADC_KEY - PA0 (GPIO引脚复用映射) - AD_IN0 D3&KEY - PA8 - input clock pre 时钟分频 resolution 精度 Data Ali 对齐方式 - 左右 scan 扫描方式 - 前后 continue 连续转化 discontinue 间隔 DMA 是否以DMA方式开启AD End of单通道转化结束的标志 Sampling time 采样时间 -
编程步骤
- 开启ADC HAL_ADC_Start
- 等待ADC转化 HAL_ADC_PollForConversion
- 获取ADC的值 HAL_ADC_GetValue
- 关闭ADC HAL_ADC_Stop
【实验】单通道数据检测 - 光照/火焰传感器 ADC转化完成的中断里得到数据
- 光敏电阻:随着光强的增大电阻减小
- A1 - PA4 — AD_IN4
RCC - 48M NVIC - AD中断 - DCD ADC1_COMP_IRQHandler
void ADC1_COMP_IRQHandler(void)
{
HAL_ADC_IRQHandler(&hadc);
}
#if (USE_HAL_ADC_REGISTER_CALLBACKS == 1)
hadc->ConvCpltCallback(hadc);
#else
HAL_ADC_ConvCpltCallback(hadc);
#endif
中断回调函数
__weak void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
}
【实验】多通道数据检测 - 按键按下中断里检测按键的数值和光强的数值
- PA0 - ADC_IN0 检测按键的数值
PA4 - ADC_IN0 检测传感器的数值 PA8 - EXTI8 开启按键中断NVIC USART - Asy - 115200 RCC - 48M(如果是8M可能有问题) - 重写按键中断回调函数
【20】DMA 直接存储器访问
- DMA无需CPU的参与,直接控制传输数据,提高CPU的效率
- 传输宽度:字节 半字 全字
向DMA请求优先级:低 中等 高 很高 目标和源传输的数据宽度对齐 每个通道都有三个事件标志位:DMA半传输 传输完成 传输出错 存储器->外设 外设->存储器 存储器->存储器 外设->外设 闪存(flash) SRAM APB AHB都可以做目标或者是源 搬移的最大长度是65535字节 - DMA寄存器
DMA_CPARx 外设寄存器的地址 DMA_CMARx 存储器地址设置 DMA_CCRx 配置数据的传输方向 - DMA增量设置 一般会把存储器设为增量模式
循环模式
【实验】DMA-ADC的多路采集 按键按下触发按键中断 在按键中断里开启ADC请求DMA的方式 采集火焰传感器和按键的数值
- 实验思路
按键按下 触发一个中断 在中断里开启了ADC ADC处理结束的时候自动发起DMA请求 DMA请求控制总线 搬移数据 搬移数据结束之后 产生一个搬移完成的DMA中断 - RCC - 48M
USART - Asy - 115200 PA8 - EXTI8 - NVIC PA0 - ADC_IN0 PA4 - ADC_IN4 ADC: DMA request 开启DMA请求 DMA setting - ADD - select - ADC 可以看到 配置好使用通道1 方向是外设到内存 优先级低 MODE-normal(正常模式) 如果ADC是连续转化我们要选择circle搬移 不然会溢出 data width选择半字(32位单片机半字是16 ADC-DR也是16位) Increase address 内存要选择地址增加 NVIC看到DMA默认就开了中断 - DCD DMA1_Channel1_IRQHandler
void DMA1_Channel1_IRQHandler(void)
{
HAL_DMA_IRQHandler(&hdma_adc);
}
if(hdma->XferCpltCallback != NULL)
{
hdma->XferCpltCallback(hdma);
}
void (* XferCpltCallback)(struct __DMA_HandleTypeDef * hdma);
hadc->DMA_Handle->XferCpltCallback = ADC_DMAConvCplt;
static void ADC_DMAConvCplt(DMA_HandleTypeDef *hdma)
{
HAL_ADC_ConvCpltCallback(hadc);
}
重写的回调函数
__weak void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
}
【实验】 利用串口空闲中断,使用DMA来搬移串口的数据,实现数据的接收和显示
- 串口空闲的概念 - USART_ISR:第4位 IDLE
串口在接收数据之后,接收一帧空闲帧,线路会被认为空闲状态,如果串口没有数据接收的情况不会产生串口空闲中断,只有在接收第一个数据之后才能触发中断,一旦数据断流没有接收到数据,就会产生串口空闲中断。 IDLE: 0: 没有检测到线路空闲 1: 检测到线路空闲 - 逻辑
- main:
- 开串口接收的DMA(设置通道长度 即每次搬移的数据)
- 开串口空闲中断
- 中断函数:
- 判断是否是串口空闲中断
- 关闭DMA通道 空闲中断清除
- 计算DMA传输的实际长度(通道长度 - 剩余数量 = 实际传输的数量) DMA_CNDTRx:DMA通道传输量寄存器
- 开启串口DMA发送数据 ,将数据发送到串口工具
- 开启DMA接收通道(设置通道长度)
- 需要的函数
HAL_UART_Receive_DMA 开启DMA __HAL_UART_ENABLE_IT 开启串口空闲中断 __HAL_UART_GET_FLAG 获得串口空闲中断标志 __HAL_UART_CLEAR_FLAG 清除串口中断标志 - 函数编写
int fputc(int ch,FILE *file)
{
while (!(USART1->ISR & 1<<7));
USART1->TDR = ch;
return ch;
}
|