STM32:功能特性与技术详解

OLED驱动函数

NVIC

NVIC中断优先级由优先级寄存器的4位(0-15)决定,这四位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级

抢占优先级高:中断嵌套                响应优先级:优先排队

抢占优先级和响应优先级相同:按照中断号进行排队

EXTI:外部中断

EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序

TIM定时中断

注意总线位置

预分频器:对输入的基准频率进行分频操作:假设寄存器写的是0,那么可以说是1分频(72/1=72),如果寄存器写的是1,那么就是(1+1=2)2分频,那么就是(72/2=36)                    实际分频系数=预分频器的值+1

CNT计数器:++

自动重装载寄存器:到达一个特定值就会归零然后重新计时

ADC

GPIO_Mode_AIN 模拟输入模式,在AIN模式下GPIO口使无效的,防止GPIO口的输入输出对模拟电压造成的干扰

AIN模式就是ADC专属模式(AIN)

校准

while里面两个函数等待校准完 RESET的时候就校准完成,跳出循环

ADC_ResetCalibration(ADC1)//开启校准,将校准位置1
while循环就是如果ADC1是1的时候就一直循环,直到校准完成后,跳出循环

串口发送和接收:

1.开启时钟,将GPIO、USART的时钟打开

2.GPIO和USART初始化

3.配置USART、GPIO,使用结构体将参数配好

4.如果只需要发送,只需要开启USART时钟。如果还需要接收,则需要配置中断,在开启USART时钟之前,加上ITConfig和NVIC代码即可

USART串口数据包

HEX数据包格式

将多个字节打包成一个整体出发(防止串口在接收数据的时候不知道如何对应。防止出现数据错位)

将数据分成一个个的数据包,并且在第一个数据上添加标志位进行分割(比如说在XYZ的数据流中,我们可以在X的数据包上的最高位置1作为标志位,当接收包的最高位是1的时候,就知道后面的数据位YZ)

串口数据包通常使用额外包头包尾的方式进行数据分割打包:规定每个数据包前面的是包头,后面的是包尾。这样就可以自定义数据包格式,这样可以连续不断的分割出想要的数据包

文本数据包格式:

与hex一样,包含固定包长和可变包长。

与HEX的数据包相比:HEX传输更直接且解析数据更加简单。但是包头包尾容易重复

文本数据包的优点就是更直观更利于理解,不易重复

在需要直接传输和简单解析原始数据的情况下,HEX数据包是更好的选择。而在需要输入指令进行人机交互的场合,文本数据包则更为适用。

固定包长:等待包头——接收数据———等待包尾

可变包长:等待包头(判断是否位包头形式,是则进入接收状态)———依次接收数据,在接受的同时还要判断是否位包尾的格式

串口发送代码分析

设置包头包尾

设置中断

分三种情况:012 

下面代码非常重要

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

uint8_t Serial_TxPacket[4];				//定义发送数据包数组,数据包格式:FF 01 02 03 04 FE
uint8_t Serial_RxPacket[4];				//定义接收数据包数组
uint8_t Serial_RxFlag;					//定义接收数据包标志位

/**
  * 函    数:串口初始化
  * 参    数:无
  * 返 回 值:无
  */
void Serial_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);	//开启USART1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA9引脚初始化为复用推挽输出
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA10引脚初始化为上拉输入
	
	/*USART初始化*/
	USART_InitTypeDef USART_InitStructure;					//定义结构体变量
	USART_InitStructure.USART_BaudRate = 9600;				//波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	//硬件流控制,不需要
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;	//模式,发送模式和接收模式均选择
	USART_InitStructure.USART_Parity = USART_Parity_No;		//奇偶校验,不需要
	USART_InitStructure.USART_StopBits = USART_StopBits_1;	//停止位,选择1位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;		//字长,选择8位
	USART_Init(USART1, &USART_InitStructure);				//将结构体变量交给USART_Init,配置USART1
	
	/*中断输出配置*/
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);			//开启串口接收数据的中断
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);			//配置NVIC为分组2
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;					//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;		//选择配置NVIC的USART1线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;		//指定NVIC线路的抢占优先级为1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;		//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);							//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*USART使能*/
	USART_Cmd(USART1, ENABLE);								//使能USART1,串口开始运行
}

/**
  * 函    数:串口发送一个字节
  * 参    数:Byte 要发送的一个字节
  * 返 回 值:无
  */
void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);		//将字节数据写入数据寄存器,写入后USART自动生成时序波形
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);	//等待发送完成
	/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}

/**
  * 函    数:串口发送一个数组
  * 参    数:Array 要发送数组的首地址
  * 参    数:Length 要发送数组的长度
  * 返 回 值:无
  */
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
	uint16_t i;
	for (i = 0; i < Length; i ++)		//遍历数组
	{
		Serial_SendByte(Array[i]);		//依次调用Serial_SendByte发送每个字节数据
	}
}

/**
  * 函    数:串口发送一个字符串
  * 参    数:String 要发送字符串的首地址
  * 返 回 值:无
  */
void Serial_SendString(char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
	{
		Serial_SendByte(String[i]);		//依次调用Serial_SendByte发送每个字节数据
	}
}

/**
  * 函    数:次方函数(内部使用)
  * 返 回 值:返回值等于X的Y次方
  */
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;	//设置结果初值为1
	while (Y --)			//执行Y次
	{
		Result *= X;		//将X累乘到结果
	}
	return Result;
}

/**
  * 函    数:串口发送数字
  * 参    数:Number 要发送的数字,范围:0~4294967295
  * 参    数:Length 要发送数字的长度,范围:0~10
  * 返 回 值:无
  */
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i ++)		//根据数字长度遍历数字的每一位
	{
		Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');	//依次调用Serial_SendByte发送每位数字
	}
}

/**
  * 函    数:使用printf需要重定向的底层函数
  * 参    数:保持原始格式即可,无需变动
  * 返 回 值:保持原始格式即可,无需变动
  */
int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);			//将printf的底层重定向到自己的发送字节函数
	return ch;
}

/**
  * 函    数:自己封装的prinf函数
  * 参    数:format 格式化字符串
  * 参    数:... 可变的参数列表
  * 返 回 值:无
  */
void Serial_Printf(char *format, ...)
{
	char String[100];				//定义字符数组
	va_list arg;					//定义可变参数列表数据类型的变量arg
	va_start(arg, format);			//从format开始,接收参数列表到arg变量
	vsprintf(String, format, arg);	//使用vsprintf打印格式化字符串和参数列表到字符数组中
	va_end(arg);					//结束变量arg
	Serial_SendString(String);		//串口发送字符数组(字符串)
}

/**
  * 函    数:串口发送数据包
  * 参    数:无
  * 返 回 值:无
  * 说    明:调用此函数后,Serial_TxPacket数组的内容将加上包头(FF)包尾(FE)后,作为数据包发送出去
  */
void Serial_SendPacket(void)
{
	Serial_SendByte(0xFF);
	Serial_SendArray(Serial_TxPacket, 4);
	Serial_SendByte(0xFE);
}

/**
  * 函    数:获取串口接收数据包标志位
  * 参    数:无
  * 返 回 值:串口接收数据包标志位,范围:0~1,接收到数据包后,标志位置1,读取后标志位自动清零
  */
uint8_t Serial_GetRxFlag(void)
{
	if (Serial_RxFlag == 1)			//如果标志位为1
	{
		Serial_RxFlag = 0;
		return 1;					//则返回1,并自动清零标志位
	}
	return 0;						//如果标志位为0,则返回0
}

/**
  * 函    数:USART1中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void USART1_IRQHandler(void)
{
	static uint8_t RxState = 0;		//定义表示当前状态机状态的静态变量
	static uint8_t pRxPacket = 0;	//定义表示当前接收数据位置的静态变量
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)		//判断是否是USART1的接收事件触发的中断
	{
		uint8_t RxData = USART_ReceiveData(USART1);				//读取数据寄存器,存放在接收的数据变量
		
		/*使用状态机的思路,依次处理数据包的不同部分*/
		
		/*当前状态为0,接收数据包包头*/
		if (RxState == 0)
		{
			if (RxData == 0xFF)			//如果数据确实是包头
			{
				RxState = 1;			//置下一个状态
				pRxPacket = 0;			//数据包的位置归零
			}
		}
		/*当前状态为1,接收数据包数据*/
		else if (RxState == 1)
		{
			Serial_RxPacket[pRxPacket] = RxData;	//将数据存入数据包数组的指定位置
			pRxPacket ++;				//数据包的位置自增
			if (pRxPacket >= 4)			//如果收够4个数据
			{
				RxState = 2;			//置下一个状态
			}
		}
		/*当前状态为2,接收数据包包尾*/
		else if (RxState == 2)
		{
			if (RxData == 0xFE)			//如果数据确实是包尾部
			{
				RxState = 0;			//状态归0
				Serial_RxFlag = 1;		//接收数据包标志位置1,成功接收一个数据包
			}
		}
		
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);		//清除标志位
	}
}

I2C通信协议

由于引脚少,硬件实施简单,可拓展性强,不需要USART、CAN等通讯协议的外部收发设备,被广泛运用到IC(集成电路)中

I2C总线:用于芯片之间进行通信的串行通信

由两条线组成:串行时钟线(SCL)和串行数据线(SDA)。这种总线允许多个设备在同一条总线上进行通信

I2C通信协议:

1.他是支持设备的总线,在一个I2C通信总线中,可以连接多个I2C通讯总线,支持多个通讯主机和多个通讯从机

2.一个I2C总线只有SCL:数据收发同步  SDA:表示数据

3.总线通过上拉电阻接到电源,空闲:高阻态 

设备的SCL和SDA均要配置成开漏输出模式

SCL和SDA各添加一个上拉电阻,组织一般为4.7k左右

I2C支持总线挂载多设备(一主多从、多主多从

I2C发送一个字节流程

发送一个字节:主机拉低SCL,将数据放在SDL上,主机松开SCL,从机读取SDL数据。在SCL的同步下,依次进行主句发送和从机接收,依次循环8次,发送8个数据(1个字节)

另外这里是高位先行:则第一位是一个字节的最高位B7

I2C时序设计

起始和终止条件

发送一个字节的流程

SCL低电平的时候,主机将数据依次放在SDA上,然后释放SCL

接收一个字节流程

在SCL低电平的时候,从机将数据放在SDL上(高位先行),然后释放SCL,主机将SCL高电平的时候读取数据,

发送应答和接收应答:

发送应答:主机在接受完一个字节之后,在下一个时钟发送一个数据,

                  数据0表示答应,数据1表示非答应

接收应答:主机在发送一个字节后,在下一个时钟接收一个数据,判断是否应答

                数据0表示应答,数据1表示非应答

补充(uint8_t和void函数的区别)

void My_I2C(void) :无返回值,纯粹的初始化函数,仅执行硬件初始化(GPIO的配置、设置IC时钟等等),不返回任何的状态和结果

uint8_t My_I2C(void):返回值类型uint8_t,除了初始化硬件外,还会返回一个状态值,可以用于验证初始化是否成功的场景

SPI通信协议

SPI协议:串行外围设备接口、高速全双工的通信总线

SPI通讯总线有三条总线和片选线:3条总线分别为SCK、MOSI、MISO;片选线SS

SCK:时钟信号线,用于通讯数据同步,作为主从机设备输出,从设备的输入

MOSI:主设备输出引脚/从设备输入引脚。主机的数据从MOSI输出,从机由这条信号线读入从机发送的数据                 ​​​​​​​        主机——>从机

MISO:        ​​​​​​​        ​​​​​​​        ​​​​​​​       从机——>主机

NSS:从设备选择。让主设备可以单独地与特定的从设备通讯,避免产生数据线上的冲突,功能为作为”片选引脚“

时钟信号的的相位和极性

CPOL:时钟极性:控制在没有数据传输时时钟的空闲状态电平

CPOL:0——SCK在空闲状态保持低电平

CPLO:1——SCL在空闲状态保持高电平

CPHA:时钟相位

CPHA:0  数据在 SCLK 的第一个边沿(即第一个跳变沿) 采样,数据输出在 SCLK 的第二个边沿 变化

CPHA:1 数据在 SCLK 的第二个边沿 采样,数据输出在 SCLK 的第一个边沿 变化

状态标志

发送缓存器空闲标志(TXE):”1“表示发送缓存其为空,当写入SPI_DR的时候,TXE标志被清除

接收缓冲器非空(RXNE):”1“表示在接收缓冲器中包含有效的接收数据

忙(Busy):BSY标志由硬件设置与清除,此标志表明SPI通信层的状态

写”1“:SPI忙于通信

以下情况被清0:1)当传输结束(在主模式下,如果是连续通信情况除外)

                           2)关闭SPI

                           3)主模式失效

SPI协议层

1.通讯的起始和终止

NSS信号线从高到低:SPI通讯的起始信号

NSS信号线从低到高:SPI通讯的停止信号

2.数据有效性

SPI使用MOSI和MISO信号线来传输数据,使用SCL信号线进行数据传输

MISO和MOSI数据线在SCL的每个时钟周期传输一位数据

要保证MSB和LSB两个通讯设备之间使用同样的协议,但是采用MSB先行模式

MOSI和MISO的数据在SCK的上升沿期间变化输出,在SCK的下降沿时被采样

3.主模式收发流程及时间说明

1)通讯开始的时候,SCK时钟开始运行,MOSI把发送缓冲区的数据一位一位的传输,MISO将数据一位一位的储存进缓冲区

2)当发送完一帧之后,TXE标志位:置1,表示传输完一帧;发送缓冲区已空

当接收完一帧之后,RXNE标志位:置1,表示传输完一帧,接收缓冲区非空

3)等待TXE标志位为1,如果还要继续发送数据,则再次往”数据寄存器DR“写如即可

输出引脚:推挽输出

输入引脚:浮空或上拉输入

SPI基本结构

作者:Jniip

物联沃分享整理
物联沃-IOTWORD物联网 » STM32:功能特性与技术详解

发表回复