STM32
一.看门狗
1.独立看门狗
1.1介绍
在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造
成程序的跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作,会
造成整个系统的陷入停滞状态,发生不可预料的后果,所以出于对单片机运行状态进行实时监测
的考虑,便产生了一种专门用于监测单片机程序运行状态的模块或者芯片,俗称“看门狗”
(watchdog) 。独立看门狗工作在主程序之外,能够完全独立工作,它的时钟是专用的低速时钟(LSI),由
VDD 电压供电, 在停止模式和待机模式下仍能工作。
本质是一个 12 位的递减计数器,当计数器的值从某个值一直减到0的时候,系统就会产生一个复
位信号,即 IWDG_RESET 。
如果在计数没减到0之前,刷新了计数器的值的话,那么就不会产生复位信号,这个动作就是我们
经常说的喂狗。
独立看门狗的时钟由独立的RC振荡器LSI提供,即使主时钟发生故障它仍然有效,非常独立。启用
IWDG后,LSI时钟会自动开启。LSI时钟频率并不精确,F1用40kHz。
LSI经过一个8位的预分频器得到计数器时钟
重装载寄存器是一个12位的寄存器,用于存放重装载值,低12位有效,即最大值为4096,这个值
的大小决定着独立看门狗的溢出时间
键寄存器IWDG_KR可以说是独立看门狗的一个控制寄存器,主要有三种控制方式,往这个寄存器
写入下面三个不同的值有不同的效果。
1.2实验
开启独立看门狗,溢出时间为1秒,使用按键1进行喂狗。
溢出时间计算:
PSC=64,RLR=625
#include "string.h"
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_IWDG_Init();
MX_USART1_UART_Init();
HAL_UART_Transmit(&huart1,(const unsigned char*)"程序启动...\n",strlen("程序启动...\n"),100);
while (1)
{
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){
HAL_IWDG_Refresh(&hiwdg);
HAL_Delay(50);
}
}
}
2.窗口看门狗
2.1介绍
窗口看门狗用于监测单片机程序运行时效是否精准,主要检测软件异常,一般用于需要精准检测
程序运行时间的场合。
窗口看门狗的本质是一个能产生系统复位信号和提前唤醒中断的6位计数器
产生复位条件:
当递减计数器值从 0x40 减到 0x3F 时复位(即T6位跳变到0)
计数器的值大于 W[6:0] 值时喂狗会复位。
产生中断条件:
当递减计数器等于 0x40 时可产生提前唤醒中断 (EWI)。
在窗口期内重装载计数器的值,防止复位,也就是所谓的喂狗。
Tout是WWDG超时时间(没喂狗)
Fwwdg是WWDG的时钟源频率(最大36M)
4096是WWDG固定的预分频系数
2^WDGTB是WWDG_CFR寄存器设置的预分频系数值
T[5:0]是WWDG计数器低6位,最多63
2.2实验
开启窗口看门狗,计数器值设置为 0X7F ,窗口值设置为 0X5F ,预分频系数为 8 。程序启动时点
亮 LED1 ,300ms 后熄灭。在提前唤醒中断服务函数进行喂狗,同时翻转 LED2 状态。
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
{
HAL_WWDG_Refresh(hwwdg);
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_RESET);
HAL_Delay(300);
MX_WWDG_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET);
HAL_Delay(40);;
}
/* USER CODE END 3 */
}
3.总结
二.DMA
1.介绍
数据搬运工-代替cpu搬运数据
代替 CPU 搬运数据,为 CPU 减负。
1. 数据搬运的工作比较耗时间;
2. 数据搬运工作时效要求高(有数据来就要搬走);
3. 没啥技术含量(CPU 节约出来的时间可以处理更重要的事)。
搬运存储器、外设数据
这里的外设指的是spi、usart、iic、adc 等基于APB1 、APB2或AHB时钟的外设,
而这里的存储器包括自身的闪存(flash)或者内存(SRAM)以及外设的存储设备都可以作为访问地源或者目的
三种搬运方式:
存储器→存储器(例如:复制某特别大的数据buf)
存储器→外设 (例如:将某数据buf写入串口TDR寄存器)
外设→存储器 (例如:将串口RDR寄存器写入某数据buf)
STM32F103有2个 DMA 控制器,
DMA1有7个通道,DMA2有5个通道。
一个通道每次只能搬运一个外设的数据!! 如果同时有多个外设的 DMA 请求,则按照优先级进行响应。
DMA及通道优先级
优先级管理采用软件+硬件:
软件: 每个通道的优先级可以在DMA_CCRx寄存器中设置,有4个等级最高级>高级>中级>低级
硬件: 如果2个请求,它们的软件优先级相同,则较低编号的通道比较高编号的通道有较高的优先权。
比如:如果软件优先级相同,通道2优先于通道4
DMA传输方式
DMA_Mode_Normal(正常模式)
一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次
DMA_Mode_Circular(循环传输模式)
当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输模式
指针递增模式
外设和存储器指针在每次传输后可以自动向后递增或保持常量。
当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值。
2.实验
HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
参数一:DMA_HandleTypeDef *hdma,DMA通道句柄
参数二:uint32_t SrcAddress,源内存地址
参数三:uint32_t DstAddress,目标内存地址
参数四:uint32_t DataLength,传输数据长度。注意:需要乘以sizeof(uint32_t)
返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)
__HAL_DMA_GET_FLAG
#define __HAL_DMA_GET_FLAG(__HANDLE__, __FLAG__) (DMA1->ISR & (__FLAG__))
参数一:HANDLE,DMA通道句柄
参数二:FLAG,数据传输标志。DMA_FLAG_TCx表示数据传输完成标志
返回值:FLAG的值(SET/RESET)
2.1内存搬运到内存
实验:
使用DMA的方式将数组A的内容复制到数组B中,搬运完之后将数组B的内容打印到屏幕。
代码:
1. 开启数据传输
2. 等待数据传输完成
3. 打印数组内容
#define BUF_SIZE 16
// 源数组
uint32_t srcBuf[BUF_SIZE] = {
0x00000000,0x11111111,0x22222222,0x33333333,
0x44444444,0x55555555,0x66666666,0x77777777,
0x88888888,0x99999999,0xAAAAAAAA,0xBBBBBBBB,
0xCCCCCCCC,0xDDDDDDDD,0xEEEEEEEE,0xFFFFFFFF
};
// 目标数组
uint32_t desBuf[BUF_SIZE];
int fputc(int ch, FILE *f)
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
main函数里:
// 开启数据传输
HAL_DMA_Start(&hdma_memtomem_dma1_channel1,
(uint32_t)srcBuf, (uint32_t)desBuf, sizeof(uint32_t) * BUF_SIZE);
// 等待数据传输完成
while(__HAL_DMA_GET_FLAG(&hdma_memtomem_dma1_channel1, DMA_FLAG_TC1) == RESET);
// 打印数组内容
for (i = 0; i < BUF_SIZE; i++)
printf("Buf[%d] = %X\r\n", i, desBuf[i]);
2.1内存搬运到外设
使用DMA的方式将内存数据搬运到串口1发送寄存器,同时闪烁LED1。
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData,uint16_t Size)
参数一:UART_HandleTypeDef *huart,串口句柄
参数二:uint8_t *pData,待发送数据首地址
参数三:uint16_t Size,待发送数据长度
返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)
int main(void)
{
/* USER CODE BEGIN 1 */
int i = 0;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
for(i=0;i<BUF_SIZE;i++){
sendBuf[i] = 'A';
}
//将数据发送到串口
HAL_UART_Transmit_DMA(&huart1,sendBuf,BUF_SIZE);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
HAL_Delay(100);
}
/* USER CODE END 3 */
}
2.1外设搬运到内存
使用DMA的方式将串口接收缓存寄存器的值搬运到内存中,同时闪烁LED1。
#define __HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__) ((((__INTERRUPT__) >> 28U)
== UART_CR1_REG_INDEX)? ((__HANDLE__)->Instance->CR1 |= ((__INTERRUPT__) &
UART_IT_MASK)): \
(((__INTERRUPT__) >> 28U)
== UART_CR2_REG_INDEX)? ((__HANDLE__)->Instance->CR2 |= ((__INTERRUPT__) &
UART_IT_MASK)): \
((__HANDLE__)->Instance-
>CR3 |= ((__INTERRUPT__) & UART_IT_MASK)))
参数一:HANDLE,串口句柄
参数二:INTERRUPT,需要使能的中断
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData,uint16_t Size)
参数一:UART_HandleTypeDef *huart,串口句柄
参数二:uint8_t *pData,接收缓存首地址
参数三:uint16_t Size,接收缓存长度
返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)
#define __HAL_UART_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)->Instance->SR &
(__FLAG__)) == (__FLAG__))
参数一:HANDLE,串口句柄
参数二:FLAG,需要查看的FLAG
返回值:FLAG的值
#define __HAL_UART_CLEAR_IDLEFLAG(__HANDLE__) __HAL_UART_CLEAR_PEFLAG(__HANDLE__)
参数一:HANDLE,串口句柄
返回值:无
HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart)
参数一:UART_HandleTypeDef *huart,串口句柄
返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)
#define __HAL_DMA_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNDTR)
参数一:HANDLE,串口句柄
返回值:未传输数据大小
如何判断串口接收是否完成?如何知道串口收到数据的长度?
使用串口空闲中断(IDLE)!
串口空闲时,触发空闲中断;
空闲中断标志位由硬件置1,软件清零
利用串口空闲中断,可以用如下流程实现DMA控制的任意长数据接收:
1. 使能IDLE空闲中断;
2. 使能DMA接收中断;
3. 收到串口接收中断,DMA不断传输数据到缓冲区;
4. 一帧数据接收完毕,串口暂时空闲,触发串口空闲中断;
5. 在中断服务函数中,清除中断标志位,关闭DMA传输(防止干扰);
6. 计算刚才收到了多少个字节的数据。
7. 处理缓冲区数据,开启DMA传输,开始下一帧接收
//main.c
uint8_t rcvBuff[BUF_SIZE]; //接收数据缓存数组
uint8_t rcvLen = 0; //接收一帧数据的长度
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); //使能空闲中断
HAL_UART_Receive_DMA(&huart1,rcvBuff,BUFF_SIZE); //使能DMA接收中断
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
HAL_Delay(100);
}
/* USER CODE END 3 */
}
//stm32f1xx_it.c
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) == SET){ //判断IDLE标志位
__HAL_UART_CLEAR_IDLEFLAG(&huart1); //清除标志位
HAL_UART_DMAStop(&huart1); //停止dma传输防止干扰
uint8_t temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); //获取没有发送完的数据
rcvLen = BUF_SIZE - temp; //计算已经发送的数据
HAL_UART_Transmit_DMA(&huart1,rcvBuff,rcvLen); //发送数据
HAL_UART_Receive_DMA(&huart1,rcvBuff,BUF_SIZE);
}
/* USER CODE END USART1_IRQn 1 */
}
三.ADC
1.介绍
模拟/数字转换器
ADC特性
12位精度下转换速度可高达1MHZ
供电电压:V SSA :0V,V DDA :2.4V~3.6V
ADC输入范围:VREF- ≤ VIN ≤ VREF+
采样时间可配置,采样时间越长, 转换结果相对越准确, 但是转换速度就越慢
ADC 的结果可以左对齐或右对齐方式存储在 16 位数据寄存器中
ADC通道
总共2个ADC(ADC1,ADC2),每个ADC有18个转换通道: 16个外部通道、 2个内部通道(温度传感器、内部参考电压)
外部的16个通道在转换时又分为规则通道和注入通道,其中规则通道最多有16路,注入通道最多有4路。
规则通道:正常排队的人
注入通道:有特权的人
ADC转换顺序
每个ADC只有一个数据寄存器,16个通道一起共用这个寄存器,所以需要指定规则转换通道的转
换顺序。
规则通道中的转换顺序由三个寄存器控制:SQR1、SQR2、SQR3,它们都是32位寄存器。SQR寄
存器控制着转换通道的数目和转换顺序,只要在对应的寄存器位SQx中写入相应的通道,这个通
道就是第x个转换
和规则通道转换顺序的控制一样,注入通道的转换也是通过注入寄存器来控制,只不过只有一个JSQR寄存器来控制,控制关系如下:
注入序列的转换顺序是从JSQx[ 4 : 0 ](x=4-JL[1:0])开始。只有当JL=4的时候,注入通道的转换顺序才会按照JSQ1、JSQ2、JSQ3、JSQ4的顺序执行。
ADC触发方式
1. 通过向控制寄存器ADC-CR2的ADON位写1来开启转换,写0停止转换。
2. 也可以通过外部事件(如定时器)进行转换。
ADC转化时间
ADC是挂载在APB2总线(PCLK2)上的,经过分频器得到ADC时钟(ADCCLK),最高 14 MHz。
转换时间=采样时间+12.5个周期
12.5个周期是固定的,一般我们设置 PCLK2=72M,经过 ADC 预分频器能分频到最大的时钟只能是 12M,采样周期设置为 1.5 个周期,算出最短的转换时间为 1.17us
ADC转换模式
扫描模式
关闭扫描模式:只转换ADC_SQRx或ADC_JSQR选中的第一个通道
打开扫描模式:扫描所有被ADC_SQRx或ADC_JSQR选中的所有通道
单次转换/连续转换
单次转换:只转换一次
连续转换:转换一次之后,立马进行下一次转换
2.实验
使用ADC读取烟雾传感器的值
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_ADC_Start(&hadc1); //启动ADC1
HAL_ADC_PollForConversion(&hadc1,50); //等待ADC转换完成
uint32_t somkeValue = HAL_ADC_GetValue(&hadc1);
//printf("somkeValue = %d\r\n", somkeValue);
printf("smoke_value = %f\r\n", 3.3/4096 * smoke_value);
HAL_Delay(500);
}
三.IIC
HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c,
uint16_t DevAddress,
uint16_t MemAddress,
uint16_t MemAddSize,
uint8_t *pData,
uint16_t Size,
uint32_t Timeout)
参数一:I2C_HandleTypeDef *hi2c,I2C设备句柄
参数二:uint16_t DevAddress,目标器件的地址,七位地址必须左对齐
参数三:uint16_t MemAddress,目标器件的目标寄存器地址
参数四:uint16_t MemAddSize,目标器件内部寄存器地址数据长度
参数五:uint8_t *pData,待写的数据首地址
参数六:uint16_t Size,待写的数据长度
参数七:uint32_t Timeout,超时时间
返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)
向OLED写命令的封装
void Oled_Write_Cmd(uint8_t dataCmd)
{
HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x00, I2C_MEMADD_SIZE_8BIT,&dataCmd, 1, 0xff);
}
向OLED写数据的封装:
void Oled_Write_Data(uint8_t dataData)
{
HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x40, I2C_MEMADD_SIZE_8BIT,&dataData, 1, 0xff);
}
四.SPI
1.介绍
SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,
并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,越来越多的芯片集成了这种通信协议,比如AT91RM9200 。
SPI 包含 4 条总线,SPI 总线包含 4 条总线,分别为SS、SCK、MOSI、MISO。它们的作用介绍如
下 :
(1) MISO – Master Input Slave Output,主设备数据输入,从设备数据输出
(2) MOSI – Master Output Slave Input,主设备数据输出,从设备数据输入
(3) SCK – Serial Clock,时钟信号,由主设备产生
(4) CS – Chip Select,片选信号,由主设备控制
工作原理
SPI工作模式
时钟极性(CPOL):
没有数据传输时时钟线的空闲状态电平
0:SCK在空闲状态保持低电平
1:SCK在空闲状态保持高电平
时钟相位(CPHA):
时钟线在第几个时钟边沿采样数据
0:SCK的第一(奇数)边沿进行数据位采样,数据在第一个时钟边沿被锁存
1:SCK的第二(偶数)边沿进行数据位采样,数据在第二个时钟边沿被锁存
模式0和模式3时序图
模式0:橙色的线处采样
模式3:橙色的线处采样
2.W25Q128
W25Q128 是华邦公司推出的一款 SPI 接口的 NOR Flash 芯片,其存储空间为 128 Mbit,相当于16M 字节。
Flash 是常用的用于储存数据的半导体器件,它具有容量大,可重复擦写、按“扇区/块”擦除、掉电后数据可继续保存的特性。
Flash 是有一个物理特性:只能写 0 ,不能写 1 ,写 1 靠擦除。(里面默认是1)
256个块,一个块16个扇区,一个扇区16页,每页256个字节
一般按扇区(4k)进行擦除。
可以按 章 -- 节 -- 页 -- 字 进行理解。
W24Q128常用命令
写使能 (06H)
执行页写,扇区擦除,块擦除,片擦除,写状态寄存器等指令前,需要写使能。
拉低CS片选 → 发送06H → 拉高CS片选
读状态寄存器(05H)
拉低CS片选 → 发送05H→ 返回SR1的值 → 拉高CS片选
读时序(03H)
拉低CS片选 → 发送03H→ 发送24位地址 → 读取数据(1~n) → 拉高CS片选
页写时序 (02H)
页写命令最多可以向FLASH传输256个字节的数据。
拉低CS片选 → 发送02H→ 发送24位地址 → 发送数据(1~n) → 拉高CS片选
扇区擦除时序(20H)
写入数据前,检查内存空间是否全部都是 0XFF ,不满足需擦除。
拉低CS片选 → 发送20H→ 发送24位地址 → 拉高CS片选
等待空闲:等待写入完成
写操作
无校验的写