【stm32f10x系列学习笔记三】


前言

参考资料:江协科技
参考资料:野火
stm32f10x系列(stm32f103c8t6核心板)USART、DMA、ADC、I2C、SPI学习笔记。


一、USART

USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器,也是我们常说的串口,可以在设备之间传递数据。常用的是UART通用异步收发器,是一种全双工、异步的通信协议。
串口通信一般有两根通信线(发送端TX和接收端RX),并且TX与RX要交叉连接。
当只需单向的数据传输时,可以只接一根通信线。当电平标准不一致时,需要加电平转换芯片。
在这里插入图片描述

1 串口通信参数及时序

波特率:串口通信的速率
起始位:标志一个数据帧的开始,固定为低电平
数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行
校验位:用于数据验证,根据数据位计算得来
停止位:用于数据帧间隔,固定为高电平
在这里插入图片描述
stm32内部集成了USART外设,用来收发数据。具有以下特点:
自带波特率发生器,最高达4.5Mbits/s
可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2)
可选校验位(无校验/奇校验/偶校验)
支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN
USART结构框图
一般来说,计算机内部采用并行数据传输,而在与外部设备进行交互时,一般采用串行传输。
写操作:发送端将数据写入到发送数据寄存器TDR,硬件自动将TDR中的数据并行转移到发送移位寄存器。TDR为空,置TXE为1,硬件在将发送移位寄存器中的数据通过TX引脚串行发送到数据线上的同时,下一个要发送的数据写入到TDR中。
读操作:接收端通过RX引脚从数据线上将数据串行保存到接收移位寄存器,硬件自动将移位寄存器中的数据并行转移到接收寄存器RDR中,置RXNE为1,发送端准备读取数据。
在这里插入图片描述
波特率的计算:波特率 = fPCLK2/1 / (16 * DIV)
如何从USART_BRR寄存器值得到DIV ?
例1: 如果 DIV_Mantissa = 27(0x1B), DIV_Fraction = 12(0x0C)
(USART_BRR=0x1BC),
于是 Mantissa (DIV) = 27
Fraction (DIV) = 12/16 = 0.75
所以 DIV = 27.75
例2: 要求 DIV = 25.62, 就有: DIV_Fraction = 16*0.62 = 9.92
最接近的整数是:10 = 0x0A DIV_Mantissa = mantissa (25.620) = 25 = 0x19
于是,USART_BRR = 0x19A
在这里插入图片描述
注意,USART外设发送和接收数据时,数据低位先行。
在这里插入图片描述
USART初始化结构体
在这里插入图片描述

2 UART收发数据

当使用串口外设传递数据时,需要使用外设指定的引脚。一般,发送数据不使用中断,接收数据使用中断。
实验现象:向电脑上位机发送“hello,world”;电脑上位机发送1,led灯闪烁。
usart.c

#include "usart.h"
#include <stdio.h>

static void nvic_config()
{
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitStruct.NVIC_IRQChannel = USART_IT_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
	
	NVIC_Init(&NVIC_InitStruct);
}

/**
  * @brief  串口配置
  * @param  
  * @retval 
  */
void usart_config()
{
	//串口外设、串口GPIO时钟
	RCC_APB2PeriphClockCmd(USART_PERIP_RCC_CLK, ENABLE);
	RCC_APB2PeriphClockCmd(USART_GPIO_RCC_CLK,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStruct;
	//GPIO初始化,GPIO 复用到 USART
	//将USART Tx的 GPIO 配置为推挽复用模式
	GPIO_InitStruct.GPIO_Mode = USART_TX_GPIO_MODE;
	GPIO_InitStruct.GPIO_Pin = USART_TX_GPIO_PIN;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(USART_GPIO_PERIP, &GPIO_InitStruct);
	
	//将USART Rx的 GPIO 配置为浮空输入模式
	GPIO_InitStruct.GPIO_Mode = USART_RX_GPIO_MODE;
	GPIO_InitStruct.GPIO_Pin = USART_RX_GPIO_PIN;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;	
	GPIO_Init(USART_GPIO_PERIP, &GPIO_InitStruct);
	
	USART_InitTypeDef USART_InitStruct;
	//串口初始化
	USART_InitStruct.USART_BaudRate = USART_BAUDRATE;
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_InitStruct.USART_Parity = USART_Parity_No;
	USART_InitStruct.USART_StopBits = USART_StopBits_1;
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;

	USART_Init(USART_PERIP, &USART_InitStruct);
	
	//中断设置
	USART_ClearFlag(USART_PERIP,USART_FLAG_RXNE);
	nvic_config();
	
	//使能接收缓冲区非空中断
	USART_ITConfig(USART_PERIP, USART_IT_RXNE, ENABLE);
	
	//使能串口
	USART_Cmd(USART_PERIP, ENABLE);
}


/**
  * @brief  发送一个字节到电脑
  * @param  
  * @retval 
  */
void usart_sendByte(uint8_t ch)
{
	USART_SendData(USART_PERIP, ch);
	//USART_FLAG_TXE标志位在下一次发送数据时会被自动清除
	while(USART_GetFlagStatus(USART_PERIP, USART_FLAG_TXE) == RESET);
}

/**
  * @brief  发送数组到电脑
  * @param  
  * @retval 
  */
void usart_sendArray(uint8_t *arr,uint8_t length)
{
	uint8_t i = 0;
	for(;i < length;i++)
	{
		usart_sendByte(*arr++);
	}
	while(USART_GetFlagStatus(USART_PERIP, USART_FLAG_TC) == RESET);
	USART_ClearFlag(USART_PERIP, USART_FLAG_TC);
}

/**
  * @brief  发送字符串到电脑
  * @param  
  * @retval 
  */
void usart_sendStr(uint8_t *str)
{
	while(*str != '\0')
	{
		usart_sendByte(*str++);
	}
	while(USART_GetFlagStatus(USART_PERIP, USART_FLAG_TC) == RESET);
	USART_ClearFlag(USART_PERIP, USART_FLAG_TC);
}

uint32_t my_pow(uint8_t x,uint8_t y)
{
	uint32_t sum = 1;
	while(y--)
	{
		sum *= x;
	}
	return sum;
}
void usart_sendNum(uint32_t Num,uint8_t length)
{
	uint8_t i = 0;
	uint8_t temp = 0;
	for(;i < length;i++)
	{
		temp = Num / my_pow(10,length - 1 - i) % 10;
		usart_sendByte(temp + '0');
	}
	while(USART_GetFlagStatus(USART_PERIP, USART_FLAG_TC) == RESET);
	USART_ClearFlag(USART_PERIP, USART_FLAG_TC);
}

/**
  * @brief  重定向 c 库函数 printf 到串口,重定向后可使用 printf 函数
  * @param  
  * @retval 
  */
int fputc(int ch, FILE *f)
{
	USART_SendData(USART_PERIP,ch);
	while(USART_GetFlagStatus(USART_PERIP, USART_FLAG_TXE) == RESET);
	return ch;
}

/**
  * @brief  重定向 c 库函数 scanf 到串口,重写向后可使用 scanf、getchar 等函数
  * @param  
  * @retval 
  */
int fgetc(FILE *f)
{
	while(USART_GetITStatus(USART_PERIP, USART_IT_RXNE) == RESET) ;
	return (int) USART_ReceiveData(USART_PERIP);
}

usart.h

#ifndef __USART_H
#define __USART_H

#include "stm32f10x.h"  
#define USART_PERIP  		  USART1
#define USART_PERIP_RCC_CLK   RCC_APB2Periph_USART1
#define USART_BAUDRATE        115200

#define USART_GPIO_RCC_CLK    RCC_APB2Periph_GPIOA
#define USART_GPIO_PERIP  	  GPIOA

#define USART_TX_GPIO_PIN     GPIO_Pin_9
#define USART_TX_GPIO_MODE    GPIO_Mode_AF_PP
#define USART_RX_GPIO_PIN     GPIO_Pin_10
#define USART_RX_GPIO_MODE    GPIO_Mode_IN_FLOATING

void usart_config();
void usart_sendByte(uint8_t ch);
void usart_sendArray(uint8_t *arr,uint8_t length);
void usart_sendStr(uint8_t *str);
uint32_t my_pow(uint8_t x,uint8_t y);
void usart_sendNum(uint32_t Num,uint8_t length);
#endif

main.c

#include "stm32f10x.h"                  // Device header
#include "led.h"
#include "usart.h"
#include <stdio.h>
#include "Delay.h"
	
/*
功能需求:
1、开发板上电时通过usart发送数据给电脑,发送可以不使用中断。stm32外设的tx接串口的rxd
2、开发板通过usart接收电脑传输过来的数据,接收需要使用中断,也可以直接查询。stm32外设的rx接串口的txd
*/

int main()
{
	led_config();
	usart_config();
	uint8_t arr[] = {0x51,0x52,0x53,0x53};
	usart_sendByte(0x41);
	usart_sendByte(100);
	usart_sendStr("100");
	usart_sendArray(arr,4);
	usart_sendNum(1000,4);
	
	printf("hello,world!\r\n");
	while(1)
	{}
}

/*使用中断*/
void USART1_IRQHandler(void)
{
	uint8_t temp = 0;
	if(USART_GetITStatus(USART_PERIP, USART_IT_RXNE) != RESET)
	{
		temp = USART_ReceiveData(USART_PERIP);
		USART_SendData(USART_PERIP,temp);
		if(temp == '1')
		{
			LED(ON);
			Delay_ms(500);
			LED(OFF);
			Delay_ms(500);
		}
		USART_ClearITPendingBit(USART_PERIP,USART_IT_RXNE);
	}
}

led.c

#include "led.h"
void led_config()
{
	//设置时钟
	RCC_APB2PeriphClockCmd(LED_GPIO_CLK, ENABLE);
	
	//初始化
	GPIO_InitTypeDef GPIO_InitStruct;	
	GPIO_InitStruct.GPIO_Pin = LED_GPIO_PIN;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;

	GPIO_Init(LED_GPIO_PORT, &GPIO_InitStruct);
	GPIO_SetBits(LED_GPIO_PORT, LED_GPIO_PIN);	
}  

led.h

#ifndef __LED_H
#define __LED_H

#include "stm32f10x.h"
#define LED_GPIO_PIN    GPIO_Pin_0
#define LED_GPIO_PORT   GPIOB
#define LED_GPIO_CLK    RCC_APB2Periph_GPIOB
#define ON    1
#define OFF   0

#define LED(a)   if(a) \
										GPIO_ResetBits(LED_GPIO_PORT, LED_GPIO_PIN); \
								 else \
									  GPIO_SetBits(LED_GPIO_PORT, LED_GPIO_PIN);

void led_config();
#endif

二、DMA

DMA(Direct Memory Access)直接存储器存取,提供了外设到存储器、存储器到存储器之间的高速数据传输。无须CPU干预,节省了CPU资源。
stm32f10x具有12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道),每个通道都支持软件触发和特定的硬件触发(外设的DMA请求)。同一时间,每个通道只能接收一个外设的DMA请求。
当发生多个DMA通道请求时,需要仲裁器处理,分为软件阶段和硬件阶段。软件阶段:仲裁器首先根据DMA_CCRx 寄存器中设置的优先级(非常高、高、中和低四个优先级)确定执行顺序。硬件阶段:如果寄存器中设置的优先级相同,则按照通道编号确定执行顺序,编号越小,优先级越高。

1 DMA结构

图中,APB1、APB2位置是反的。
在这里插入图片描述
在这里插入图片描述
DMA初始化结构体
一般外设只有一个数据寄存器,外设地址保持不变;存储器可以存储多个数据,地址自动递增。
DMA传输数据时,可以以字节(8bit)、半字(16bit)、字(32bit)为单位。传送双方数据单位应保持一致。
DMA模式选择:
正常模式:单次DMA传输。DMA使能后,传输开始。传输数量自动递减,为0时传输结束。
循环模式:DMA使能后,传输开始。传输数量自动递减,为0时,传输数量自动恢复为初始值,传输继续。
在这里插入图片描述
存储器映像
在这里插入图片描述
DMA外设请求
从外设(TIMx[x=1、2、3、4]、ADC1、SPI1、SPI/I2S2、I2Cx[x=1、2]和USARTx[x=1、2、3])产生的7个请求,通过逻辑或输入到DMA1控制器,这意味着同时只能有一个请求有效。
在这里插入图片描述

2 DMA存储器-存储器M2M

实验:将存放在flash中数据,通过DMA拷贝到内部SRAM中。
dma.c

#include "dma_m2m.h"

/**
功能需求:
存储器到存储器模数:静态源数据存放在FLASH中,通过DMA复制到SRAM中
*/

//定义src_buffer数组作为DMA传输数据源
//const表示常量类型,数据存储在内部FLASH中
const uint32_t src_buffer[DMA_BUFFER_SIZE] = 
{
	0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10
};

//定义dest_buffer数组为DMA传输目标内存器,存储在内部SRAM中
uint32_t dest_buffer[DMA_BUFFER_SIZE];

void dma_config()
{
	DMA_InitTypeDef DMA_InitStruct;
	
	RCC_AHBPeriphClockCmd(DMA_PERIPH_RCC_CLK, ENABLE);
	//DMA传输数据数量:0~65535
	DMA_InitStruct.DMA_BufferSize = DMA_BUFFER_SIZE;
	//传输方向:存储器到外设(存储器)
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST;
	//存储器到存储器模式
	DMA_InitStruct.DMA_M2M = DMA_M2M_Enable;
	//存储器地址
	DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)src_buffer;
	//存储器传输数据宽度/单位
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
	//存储器地址自动递增
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
	//单次DMA传输:DMA使能后,传输开始。传输数量自动递减,为0时传输结束。
	DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
	//目标地址
	DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)dest_buffer;
	//目标传输数据宽度/单位
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
	//外设(此处将一存储器作为外设)地址自动递增
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
	//优先级
	DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;
	
	DMA_Init(DMA_PERIPH_Channel, &DMA_InitStruct);
	
	DMA_ClearFlag(DMA_FLAG);
	
	DMA_Cmd(DMA_PERIPH_Channel, ENABLE);
}

uint8_t isEqual(const uint32_t *srcBuffer,uint32_t *destBuffer, uint16_t bufferLen)
{
	while(bufferLen--)
	{
		if(*srcBuffer != *destBuffer)
		{
			return 0;
		}
		srcBuffer++;
		destBuffer++;
	}
	return 1;
}

dma.h

#ifndef __DMA_M2M_H
#define __DMA_M2M_H

#include "stm32f10x.h"
#define DMA_PERIPH_RCC_CLK     RCC_AHBPeriph_DMA1
#define DMA_PERIPH_Channel     DMA1_Channel2
#define DMA_BUFFER_SIZE			   4
#define DMA_FLAG               DMA1_FLAG_TC2

void dma_config();
uint8_t isEqual(const uint32_t *srcBuffer,uint32_t *destBuffer, uint16_t bufferLen);
#endif

main.c

#include "stm32f10x.h"                  // Device header
#include "dma_m2m.h"
#include "OLED.h"
#include "delay.h"
#include "led.h"

extern const uint32_t src_buffer[DMA_BUFFER_SIZE];
extern uint32_t dest_buffer[DMA_BUFFER_SIZE];
	
int main()
{
	led_config();
	dma_config();
	OLED_Init();
	while(DMA_GetFlagStatus(DMA_FLAG) == RESET);
	for(int i =0;i<4;i++)
	{
		OLED_ShowHexNum(1,1,src_buffer[0],8);
		OLED_ShowHexNum(2,1,dest_buffer[0],8);
		Delay_s(5);
	}	
	uint8_t IsEqual = isEqual(src_buffer,dest_buffer,4);
	OLED_ShowNum(3,1,IsEqual,1);
	
	while(1)
	{}
}

三、ADC

ADC(Analog-Digital Converter)模拟-数字转换器,可以将外部连续变化的模拟电压转换成数字量。
stm32f10x内部集成了12位逐次逼近型ADC外设。该外设最多具有16个外部和2个内部通道信号源。各通道的A/D转换以单次、连续、扫描或间断模式执行。ADC结果以左对齐或右对齐方式存储在16位数据寄存器中。
模拟看门狗特性检测输入电压是否超过设定的阈值。ADC输入时钟由PCLK2产生,且不超过14MHz。
逐次逼近型AD原理可参考笔记【51单片机系列笔记四】

1 ADC结构

规则组支持16个通道。规则通道的转换顺序可以通过设置ADC_SQRx寄存器选择,但是规则通道数据寄存器只有一个,也就是说前一次转换的结果会被后一次转换结果覆盖。因此,转换结果需要及时读出。
注入组支持4个通道。诸如组的转换顺序可以通过设置在ADC_JSQR寄存器选择。注入通道数据寄存器由4个,每个寄存器对应一个通道的转换结果。在规则组和注入组同时存在时,注入组可以优先转换。
在这里插入图片描述
单ADC转换模式
单次转换模式:ADC使能后,只进行一次转换。转换结果存储在数据寄存器中,EOC(转换结束标志)置位。下次转换仍需使能ADC。
连续转换模式:ADC使能后,前一次转换结束后立即开始下一次转换。每次转换后,转换结果存储在数据寄存器中,EOC(转换结束标志)置位,由软件清除或读取数据寄存器ADC_DR清除。
扫描模式:ADC使能后,ADC扫描一组选中的通道,在每个通道上执行单次转换。该通道转换结束后,下一通道自动转换。如果设置了DMA位,在每次EOC后,DMA控制器把规则组通道的转换数据传输到SRAM中。而注入通道转换的数据总是存储在ADC_JDRx寄存器中。
此外,还有双ADC转换模式:ADC1主和ADC2从的交替触发或同步触发。
ADC可以通过外部和内部触发。
在这里插入图片描述
ADC校准
ADC内置自校准模式。在每次上电后,建议执行一次校准。启动校准前,ADC必须处于关电状态(ADON=’0’)超过至少两个ADC时钟周期。
ADC转换数据对齐方式有左对齐和右对齐。一般建议右对齐方式。
ADC的通道采样时间
AD转换的步骤:采样,保持,量化,编码。
STM32 ADC的总转换时间为:
TCONV = 采样时间 + 12.5个ADC周期
例如:
当ADCCLK=14MHz,采样时间为1.5周期
TCONV = 1.5 + 12.5 = 14周期 = 1μs
ADC初始化结构体
在这里插入图片描述
在这里插入图片描述

2 单ADC多通道连续扫描+DMA循环模式

实验:PA0、PA1、PA2、PA3分别接电位器、热敏、光敏、反射式传感器的输出口。
adc.c

#include "ADC.h"

uint16_t Desc_Buf[4];

/**
 * @brief  DMA初始化,开启循环模式
 * @param   
 * @return  
 */
void MyADC_DMA_Init()
{
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	
	DMA_InitTypeDef DMA_InitStruct;
	DMA_InitStruct.DMA_BufferSize = 4;
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;
	DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;
	DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)Desc_Buf;
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
	//DMA循环模式
	DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;
	DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
	//ADC数据寄存器16位,DMA数据传输宽度16位
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	//外设地址ADC始终都是ADC1->DR,不自增
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;
	DMA_Init(DMA1_Channel1,&DMA_InitStruct);
	
	DMA_ClearFlag(DMA1_FLAG_TC1);
	DMA_Cmd(DMA1_Channel1,ENABLE);
}

/**
 * @brief  ADC初始化,循环扫描模式
 * @param   
 * @return  
 */
void MyADC_Init()
{
	MyADC_DMA_Init();
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN;
	//ADC通道0~3对应引脚
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
		
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	ADC_InitTypeDef ADC_InitStruct;
	//ADC连续模式
	ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;
	//ADC右对齐
	ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
	//ADC软件触发
	ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
	//单ADC模式
	ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;
	ADC_InitStruct.ADC_NbrOfChannel = 4;
	//ADC扫描模式
	ADC_InitStruct.ADC_ScanConvMode = ENABLE;
	ADC_Init(ADC1,&ADC_InitStruct);
	
	//ADC规则通道配置
	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_3,4,ADC_SampleTime_55Cycles5);
	
	ADC_Cmd(ADC1,ENABLE);
	ADC_DMACmd(ADC1,ENABLE);
	
	//ADC校准复位
	ADC_ResetCalibration(ADC1);
	while(ADC_GetResetCalibrationStatus(ADC1));
	//ADC校准
	ADC_StartCalibration(ADC1);
	while(ADC_GetCalibrationStatus(ADC1));
	
	//数值已经被DMA自动保存到Desc_Buf
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);
}

main.c

#include "stm32f10x.h"                  // Device header
#include "OLED.H"
#include "ADC.H"
#include "Delay.H"

extern uint16_t Desc_Buf[];

int main()
{
	OLED_Init();
	MyADC_Init();	
	while(1)
	{
		OLED_ShowHexNum(1,1,Desc_Buf[0],4);
		OLED_ShowHexNum(2,1,Desc_Buf[1],4);
		OLED_ShowHexNum(3,1,Desc_Buf[2],4);
		OLED_ShowHexNum(4,1,Desc_Buf[3],4);
		
		Delay_ms(500);
	}
}

四、I2C

软件模拟I2C协议:stm32f10x可以通过控制GPIO引脚,作为SCL时钟和SDA数据线。通过翻转电平信号,从而实现I2C时序,发送和接收数据。
硬件I2C协议:stm32f10x内部集成I2C外设。通过配置I2C外设的相关参数,实现I2C时序,发送和接收数据。

1 软件模拟I2C协议-读写at24c02存储器

I2C协议原理可参考笔记【51单片机系列笔记三】
i2c.h

#ifndef __I2C_H
#define __I2C_H

#include "stm32f10x.h"
#define I2C_GPIO_PORT 		  					GPIOB
#define I2C_GPIO_SCL_PIN        			GPIO_Pin_6
#define I2C_GPIO_SDA_PIN							GPIO_Pin_7
#define I2C_GPIO_RCC_APB2Periph_CLK   RCC_APB2Periph_GPIOB
#define GPIO_WRITE_SCL(val)    {GPIO_WriteBit(I2C_GPIO_PORT,I2C_GPIO_SCL_PIN,(BitAction) val);}

void I2C_GPIO_Config();
void I2C_Start();
void I2C_Stop();
void I2C_SendByte(uint8_t Byte);
uint8_t I2C_ReadByte();
void I2C_SendAck(uint8_t AckBit);
uint8_t I2C_ReceiveAck();
#endif

i2c.c

#include "i2c.h"

/**
软件模拟i2c通信协议:
1、除了终止条件,SCL高电平结束,其他情况都假定SCL低电平结束
2、始终以主机的角度进行编写代码。写的是主机的代码,从机不需要管
*/
void I2C_GPIO_Config()
{
	GPIO_InitTypeDef GPIO_InitStruct;
	RCC_APB2PeriphClockCmd(I2C_GPIO_RCC_APB2Periph_CLK,ENABLE);
	
	//开漏输出,直接由gpio控制电平变化
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStruct.GPIO_Pin = I2C_GPIO_SCL_PIN | I2C_GPIO_SDA_PIN;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	
	GPIO_Init(I2C_GPIO_PORT,&GPIO_InitStruct);
	
	//空闲状态,高电平
	GPIO_SetBits(I2C_GPIO_PORT, I2C_GPIO_SCL_PIN | I2C_GPIO_SDA_PIN);
}

void I2C_Start()
{
	GPIO_WriteBit(I2C_GPIO_PORT,I2C_GPIO_SDA_PIN,1);
	GPIO_WriteBit(I2C_GPIO_PORT,I2C_GPIO_SCL_PIN,1);
	
	GPIO_WriteBit(I2C_GPIO_PORT,I2C_GPIO_SDA_PIN,0);
	GPIO_WriteBit(I2C_GPIO_PORT,I2C_GPIO_SCL_PIN,0);
}

void I2C_Stop()
{
	GPIO_WriteBit(I2C_GPIO_PORT,I2C_GPIO_SDA_PIN,0);
	GPIO_WriteBit(I2C_GPIO_PORT,I2C_GPIO_SCL_PIN,1);
	GPIO_WriteBit(I2C_GPIO_PORT,I2C_GPIO_SDA_PIN,1);
}

void I2C_SendByte(uint8_t Byte)
{
	uint8_t index;
	for(index = 0;index < 8;index++)
	{
		//MSB先行。BitAction非0即1
		GPIO_WriteBit(I2C_GPIO_PORT,I2C_GPIO_SDA_PIN,Byte && (0x80 >> index));
		GPIO_WriteBit(I2C_GPIO_PORT,I2C_GPIO_SCL_PIN,1);
		GPIO_WriteBit(I2C_GPIO_PORT,I2C_GPIO_SCL_PIN,0);
	}
}

uint8_t I2C_ReadByte()
{
	uint8_t Byte = 0x00;
	uint8_t index;
	//主机释放SDA
	GPIO_WriteBit(I2C_GPIO_PORT,I2C_GPIO_SDA_PIN,1);
	
	for(index = 0;index < 8;index++)
	{
		GPIO_WriteBit(I2C_GPIO_PORT,I2C_GPIO_SCL_PIN,1);
		if(GPIO_ReadInputDataBit(I2C_GPIO_PORT,I2C_GPIO_SDA_PIN))
		{
			Byte |= (0x80 >> index);
		}
		GPIO_WriteBit(I2C_GPIO_PORT,I2C_GPIO_SCL_PIN,0);
	}
	return Byte;
}

/**
  * @brief  主机接收数据后,发送应答
  * @param  
  * @retval 
  */
void I2C_SendAck(uint8_t AckBit)
{
	GPIO_WriteBit(I2C_GPIO_PORT,I2C_GPIO_SDA_PIN,AckBit);
	GPIO_WriteBit(I2C_GPIO_PORT,I2C_GPIO_SCL_PIN,1);
	GPIO_WriteBit(I2C_GPIO_PORT,I2C_GPIO_SCL_PIN,0);
}

/**
  * @brief 主机发送数据后,读取从机的应答
  * @param  
  * @retval 
  */
uint8_t I2C_ReceiveAck()
{
	uint8_t AckBit;
	//主机释放SDA
	GPIO_WriteBit(I2C_GPIO_PORT,I2C_GPIO_SDA_PIN,1);
	
	GPIO_WriteBit(I2C_GPIO_PORT,I2C_GPIO_SCL_PIN,1);
	AckBit = GPIO_ReadInputDataBit(I2C_GPIO_PORT,I2C_GPIO_SDA_PIN);
	GPIO_WriteBit(I2C_GPIO_PORT,I2C_GPIO_SCL_PIN,0);
	return AckBit;
}

at24c02.h

#ifndef __AT24C02_H
#define __AT24C02_H

#include "stm32f10x.h"
#include "i2c.h"

#define I2C_SLAVE_ADDR        0xa0
void at24c02_Init();
void at24c02_WriteReg(uint8_t RegAddr,uint8_t Data);
uint8_t at24c02_ReadReg(uint8_t RegAddr);
#endif

at24c02.c

#include "at24c02.h"
#include "i2c.h"

void at24c02_Init()
{
	I2C_GPIO_Config();
}

/**
  * @brief  指定地址写
  * @param  
  * @retval 
  */
void at24c02_WriteReg(uint8_t RegAddr,uint8_t Data)
{
	I2C_Start();
	I2C_SendByte(I2C_SLAVE_ADDR);
	//可以不在首行定义变量
//	uint8_t AckBit = I2C_ReceiveAck();
	I2C_ReceiveAck();
	I2C_SendByte(RegAddr);	
	I2C_ReceiveAck();
	I2C_SendByte(Data);
	I2C_ReceiveAck();
	I2C_Stop();
}

/**
  * @brief  指定地址读
  * @param  
  * @retval 
  */
uint8_t at24c02_ReadReg(uint8_t RegAddr)
{
	I2C_Start();
	I2C_SendByte(I2C_SLAVE_ADDR);
	I2C_ReceiveAck();
	I2C_SendByte(RegAddr);	
	I2C_ReceiveAck();
	
	I2C_Start();
	I2C_SendByte(I2C_SLAVE_ADDR | 0x01);
	I2C_ReceiveAck();
	uint8_t Data = I2C_ReadByte();
	I2C_SendAck(1);
	I2C_Stop();
	
	return Data;
}

main.c

#include "stm32f10x.h"                  // Device header
#include "usart.h"
#include "at24c02.h"

int main()
{
	usart_config();
	at24c02_Init();
	
	/*指定地址写*/
	at24c02_WriteReg(0x19,0x28);
	usart_sendByte(0x28);
	uint8_t Data = at24c02_ReadReg(0x19);
	usart_sendByte(Data);
	while(1)
	{
	}
}

2 硬件I2C协议

1 mpu6050-姿态检测

MPU6050功能原理具体描述可参考数据。此处只用来测量角速度和加速度。
MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景。
3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度
3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度
在这里插入图片描述
16位ADC采集传感器的模拟信号,量化范围:-32768~32767
加速度计满量程选择:±2、±4、±8、±16(g)
陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec)
可配置的数字低通滤波器、时钟源、采样分频
I2C从机地址:1101000(AD0=0)
1101001(AD0=1)
MPU6050 模块的硬件原理图
在这里插入图片描述
在这里插入图片描述
mpu6050结构框图
在这里插入图片描述

2 stm32硬件I2C外设

stm32 的 I2C 外设可用作通讯的主机及从机,支持 100Kbit/s 和 400Kbit/s 的速率,支持 7 位、10 位设备地址,支持 DMA 数据传输,并具有数据校验功能。I2C属于同步、半双工协议。
I2C结构
当向外发送数据时,数据寄存器中的数据并行传输到数据移位寄存器中,然后数据移位寄存器将数据按位(高位先行)通过SDA数据线发送出去。
当从外部接收数据时,数据移位寄存器通过SDA数据线将采集的数据按位(高位先行)存储,然后数据移位寄存器将数据并行传输到数据寄存器中。从外部接收数据后,可以通过PCE校验。
当stm32的I2C工作在从机模式时,从外部接收设备地址,并与自身地址寄存器中的地址比较。
一般情况下,stm32的I2C都是用来充当主发送器和主接收器的。
在这里插入图片描述
主发送器
SB=1,起始条件已发送。
ADDR=1,主模式下,表示地址发送结束。
EV8_1事件:TxE=1,移位寄存器空,DR数据寄存器空,此时将数据1写入DR寄存器。
EV8事件:TxE=1,移位寄存器非空,DR数据寄存器空,此时将数据2写入DR寄存器,写入后将清除该事件。从图上可以看出,从机在给数据1响应时数据2已经被写入DR数据寄存器。
BTF=1,字节发送结束。TxE=1,在上一次数据发送结束之前,没有写入新的数据到DR寄存器,BTF将被置位。
ADD10=1,主设备已经将第一个地址字节发送出去。
起始信号后面紧跟设备地址。7位地址模式:7位设备地址+读写位。11位地址模式:11110+2位设备地址+读写位+8位设备地址。
在这里插入图片描述
主接收器
SB=1,起始条件已发送。
ADDR=1,从模式下,表示收到的地址匹配。
EV6_1事件,只适用于接收1个字节。清除了ADDR位后,立即清除响应和停止条件的产生位,之后再接收数据。如何理解?数据和地址按8位/1字节传输,高位在前。每个时钟传输字节的1位,在低9个时钟时,接收器必须回送一个应答位(ACK)给发送器。因此,需要提前清除响应和停止位,表示不再接收数据。
EV7事件,适用于接收多个字节。RXNE=1,DR数据寄存器非空,此时读取数据1,数据2正被接收。如何理解?I2C在接收数据时,数据只要从SDA数据线上转移到移位寄存器中,接收器就可以发送响应。然后移位寄存器将数据并行传输到DR数据寄存器中,并置RXNE=1,产生EV7事件。接着,在下一个数据从SDA数据线上转移到移位寄存器的过程中,DR数据寄存器中的上一个数据被读取,EV7事件被清除。
EV7_1事件,RxNE=1,读DR寄存器清除该事件。设置ACK=0和STOP请求。在读取倒数第二个字节时,最后一个字节已经在被转移到移位寄存器中。因此,需要清除响应和停止位,表示不再接收数据。
在这里插入图片描述
STM32的硬件I2C模拟不稳定,一般采用软件模拟I2C,只通过控制GPIO的输入输出电平变化来通信。
I2C初始化结构体
在这里插入图片描述
在这里插入图片描述
实验:使用I2C外设测量mpu6050姿态传感器x、y、z的角速度、加速度变化量。
mpu6050.h

#ifndef __MPU6050_H
#define __MPU6050_H

#include "stm32f10x.h"
#define MPU6050_Addr   0xd0  //默认写地址
#define	MPU6050_SMPLRT_DIV		0x19
#define	MPU6050_CONFIG			0x1A
#define	MPU6050_GYRO_CONFIG		0x1B
#define	MPU6050_ACCEL_CONFIG	0x1C

#define	MPU6050_ACCEL_XOUT_H	0x3B
#define	MPU6050_ACCEL_XOUT_L	0x3C
#define	MPU6050_ACCEL_YOUT_H	0x3D
#define	MPU6050_ACCEL_YOUT_L	0x3E
#define	MPU6050_ACCEL_ZOUT_H	0x3F
#define	MPU6050_ACCEL_ZOUT_L	0x40
#define	MPU6050_TEMP_OUT_H		0x41
#define	MPU6050_TEMP_OUT_L		0x42
#define	MPU6050_GYRO_XOUT_H		0x43
#define	MPU6050_GYRO_XOUT_L		0x44
#define	MPU6050_GYRO_YOUT_H		0x45
#define	MPU6050_GYRO_YOUT_L		0x46
#define	MPU6050_GYRO_ZOUT_H		0x47
#define	MPU6050_GYRO_ZOUT_L		0x48

#define	MPU6050_PWR_MGMT_1		0x6B
#define	MPU6050_PWR_MGMT_2		0x6C
#define	MPU6050_WHO_AM_I		0x75

typedef struct
{
	int16_t ACCEL_X;
	int16_t ACCEL_Y;
	int16_t ACCEL_Z;
	int16_t GYRO_X;
	int16_t GYRO_Y;
	int16_t GYRO_Z;
}MPU6050_SensorDef;

void MPU6050_I2C_Init();
void MPU6050_Init();
void MPU6050_WriteData(uint8_t RegAddr,uint8_t Data);
uint8_t MPU6050_ReceiveData(uint8_t RegAddr);
void MPU6050_WriteArr(uint8_t RegAddr,uint8_t *Data,uint8_t DataNum);
void MPU6050_ReceiveArr(uint8_t RegAddr,uint8_t *Data,uint8_t DataNum);
void MPU6050_GetSensorData(MPU6050_SensorDef *MPU6050_SensorDefStruct);
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
						int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);
#endif

mpu6050.c

#include "mpu6050.h"

void MPU6050_I2C_Init()
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStruct;
	//硬件I2C。片上外设控制,复用开漏输出模式
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	I2C_InitTypeDef I2C_InitStruct;
	//I2C应答使能,一般都需要开启
	I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;
	//采用7位地址+读写位方式
	I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	//SCL时钟频率,不超过400000
	I2C_InitStruct.I2C_ClockSpeed = 100000;
	//SCL时钟占空比
	I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
	I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
	//stm32的I2C外设自身地址,从模式需要
	I2C_InitStruct.I2C_OwnAddress1 = 0x00;
	I2C_Init(I2C2,&I2C_InitStruct);
	
	I2C_Cmd(I2C2,ENABLE);
}

/**
  * @brief  指定地址写单字节
  * @param  
  * @retval 
  */
void MPU6050_WriteData(uint8_t RegAddr,uint8_t Data)
{
	//产生起始信号
	I2C_GenerateSTART(I2C2,ENABLE);
	//EV5事件,起始条件已发送。I2C_EVENT_MASTER_MODE_SELECT: EV5
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
	
	//发送7位地址+写位
	I2C_Send7bitAddress(I2C2,MPU6050_Addr,I2C_Direction_Transmitter);
	//EV6事件,地址发送结束。I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED: EV6  
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);
	
	//写地址到DR寄存器
	I2C_SendData(I2C2,RegAddr);
	//EV8事件,数据移位寄存器非空,地址正在发送。DR寄存器为空,准备写入下一个数据。I2C_EVENT_MASTER_BYTE_TRANSMITTING: EV8
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);

	//写数据到DR寄存器
	I2C_SendData(I2C2,Data);
	//EV8_2事件,数据移位寄存器非空,数据正在发送。DR寄存器为空,不再写入下一个数据。I2C_EVENT_MASTER_BYTE_TRANSMITTED: EV8_2
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS);

	//产生停止信号
	I2C_GenerateSTOP(I2C2,ENABLE);
}

/**
  * @brief  指定地址读单字节 = 指定地址写 + 当前地址读
  * @param  
  * @retval 
  */
uint8_t MPU6050_ReceiveData(uint8_t RegAddr)
{
	uint8_t Data;
	
	//产生起始信号
	I2C_GenerateSTART(I2C2,ENABLE);
	//EV5事件,起始条件已发送。I2C_EVENT_MASTER_MODE_SELECT: EV5
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
	
	//发送7位地址+写位
	I2C_Send7bitAddress(I2C2,MPU6050_Addr,I2C_Direction_Transmitter);
	//EV6事件,地址发送结束。I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED: EV6  
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);
	
	//写地址到DR寄存器
	I2C_SendData(I2C2,RegAddr);
	//EV8事件,数据移位寄存器非空,地址正在发送。DR寄存器为空,准备写入下一个数据。I2C_EVENT_MASTER_BYTE_TRANSMITTING: EV8
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);
	
	//产生重复起始信号
	I2C_GenerateSTART(I2C2,ENABLE);
	//EV5事件,起始条件已发送。I2C_EVENT_MASTER_MODE_SELECT: EV5
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
	
	//发送7位地址+读位
	I2C_Send7bitAddress(I2C2,MPU6050_Addr,I2C_Direction_Receiver);
	//EV6事件,收到的地址匹配。I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED: EV6 
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS);
	
	//关闭应答
	I2C_AcknowledgeConfig(I2C2,DISABLE);
	//生成停止条件
	I2C_GenerateSTOP(I2C2,ENABLE);
	
	//EV7事件,DR寄存器非空,读取上一个数据。数据移位寄存器非空,下一个数据正在写入。I2C_EVENT_MASTER_BYTE_RECEIVED: EV7
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);
	//接收数据
	Data = I2C_ReceiveData(I2C2);
	
	I2C_AcknowledgeConfig(I2C2,ENABLE);
	return Data;
}

/**
  * @brief  指定地址写多字节
  * @param  
  * @retval 
  */
void MPU6050_WriteArr(uint8_t RegAddr,uint8_t *Data,uint8_t DataNum)
{
	I2C_GenerateSTART(I2C2,ENABLE);
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
	
	I2C_Send7bitAddress(I2C2,MPU6050_Addr,I2C_Direction_Transmitter);
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);
	
	I2C_SendData(I2C2,RegAddr);
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);

	while(DataNum > 1)
	{
		I2C_SendData(I2C2,*Data++);
		while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);
		DataNum--;
	}
	
	I2C_SendData(I2C2,*Data);
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS);
	I2C_GenerateSTOP(I2C2,ENABLE);
}

/**
  * @brief  指定地址读多字节
  * @param  
  * @retval 
  */
void MPU6050_ReceiveArr(uint8_t RegAddr,uint8_t *Data,uint8_t DataNum)
{
	I2C_GenerateSTART(I2C2,ENABLE);
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
	
	I2C_Send7bitAddress(I2C2,MPU6050_Addr,I2C_Direction_Transmitter);
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);
	
	I2C_SendData(I2C2,RegAddr);
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);
	
	I2C_GenerateSTART(I2C2,ENABLE);
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
	
	I2C_Send7bitAddress(I2C2,MPU6050_Addr,I2C_Direction_Receiver);
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS);
	
	I2C_AcknowledgeConfig(I2C2,ENABLE);
	while(DataNum > 1)
	{
		while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);
		*Data++ = I2C_ReceiveData(I2C2);
		DataNum--;
	}
	I2C_AcknowledgeConfig(I2C2,DISABLE);
	I2C_GenerateSTOP(I2C2,ENABLE);
	
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);
	*Data = I2C_ReceiveData(I2C2);
}

/**
  * @brief  MPU6050初始化配置
  * @param  
  * @retval 
  */
void MPU6050_Init()
{
	MPU6050_I2C_Init();
	//电源管理1,配置时钟源:z轴陀螺仪作为参考,提高稳定性
	MPU6050_WriteData(MPU6050_PWR_MGMT_1,0x03);
	//电源管理2,默认
	MPU6050_WriteData(MPU6050_PWR_MGMT_2,0x00);
	//采样频率,随便
	MPU6050_WriteData(MPU6050_SMPLRT_DIV,0x05);
	//外部帧同步(FSYNC)和数字低通滤波器(DLPF)设置,随便
	MPU6050_WriteData(MPU6050_CONFIG,0x06);
	//陀螺仪自检和检测量程,随便
	MPU6050_WriteData(MPU6050_GYRO_CONFIG,0x18);
	//加速度计自检和检测量程、高通滤波,随便
	MPU6050_WriteData(MPU6050_ACCEL_CONFIG,0x18);
}

/**
  * @brief  使用结构体保存MPU6050加速度和加速度测量值
  * @param  
  * @retval 
  */
void MPU6050_GetSensorData(MPU6050_SensorDef* MPU6050_SensorDefStruct)
{
	uint16_t Data_H,Data_L;
	//x轴加速度测量
	Data_H = MPU6050_ReceiveData(MPU6050_ACCEL_XOUT_H);
	Data_L = MPU6050_ReceiveData(MPU6050_ACCEL_XOUT_L);
	MPU6050_SensorDefStruct->ACCEL_X = (Data_H << 8) | Data_L;
	
	//y轴加速度测量
	Data_H = MPU6050_ReceiveData(MPU6050_ACCEL_YOUT_H);
	Data_L = MPU6050_ReceiveData(MPU6050_ACCEL_YOUT_L);
	MPU6050_SensorDefStruct->ACCEL_Y = (Data_H << 8) | Data_L;
	
	//z轴加速度测量
	Data_H = MPU6050_ReceiveData(MPU6050_ACCEL_ZOUT_H);
	Data_L = MPU6050_ReceiveData(MPU6050_ACCEL_ZOUT_L);
	MPU6050_SensorDefStruct->ACCEL_Z = (Data_H << 8) | Data_L;
	
	//x轴角速度测量
	Data_H = MPU6050_ReceiveData(MPU6050_GYRO_XOUT_H);
	Data_L = MPU6050_ReceiveData(MPU6050_GYRO_XOUT_L);
	MPU6050_SensorDefStruct->GYRO_X = (Data_H << 8) | Data_L;
	
	//y轴角速度测量
	Data_H = MPU6050_ReceiveData(MPU6050_GYRO_YOUT_H);
	Data_L = MPU6050_ReceiveData(MPU6050_GYRO_YOUT_L);
	MPU6050_SensorDefStruct->GYRO_Y = (Data_H << 8) | Data_L;
	
	//z轴角速度测量
	Data_H = MPU6050_ReceiveData(MPU6050_GYRO_ZOUT_H);
	Data_L = MPU6050_ReceiveData(MPU6050_GYRO_ZOUT_L);
	MPU6050_SensorDefStruct->GYRO_Z = (Data_H << 8) | Data_L;
}

/**
  * @brief  使用指针保存MPU6050加速度和加速度测量值
  * @param  
  * @retval 
  */
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
						int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
	uint8_t DataH, DataL;
	
	DataH = MPU6050_ReceiveData(MPU6050_ACCEL_XOUT_H);
	DataL = MPU6050_ReceiveData(MPU6050_ACCEL_XOUT_L);
	*AccX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReceiveData(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReceiveData(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReceiveData(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_ReceiveData(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReceiveData(MPU6050_GYRO_XOUT_H);
	DataL = MPU6050_ReceiveData(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReceiveData(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_ReceiveData(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReceiveData(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_ReceiveData(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH << 8) | DataL;
}

main.c

#include "stm32f10x.h"                  // Device header
#include "OLED.H"
#include "MPU6050.H"

/**
功能需求:i2c协议。读写mpu6050姿态传感器的数据。
*/
int16_t AX,AY,AZ,GX,GY,GZ;

int main()
{
	OLED_Init();
	MPU6050_Init();
		
	while(1)
	{
		MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
		OLED_ShowSignedNum(2, 1, AX, 5);
		OLED_ShowSignedNum(3, 1, AY, 5);
		OLED_ShowSignedNum(4, 1, AZ, 5);
		OLED_ShowSignedNum(2, 8, GX, 5);
		OLED_ShowSignedNum(3, 8, GY, 5);
		OLED_ShowSignedNum(4, 8, GZ, 5);
	}
}

五、SPI

SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线。四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slave Output)、SS(Slave Select)。同步,全双工。支持总线挂载多设备(一主多从)。
SPI硬件电路图
所有SPI设备的SCK、MOSI、MISO分别连在一起。主机另外引出多条SS控制线,分别接到各从机的SS引脚。输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入。
在这里插入图片描述
SPI通过CS(SS)线与各个从机之间通讯。当某从机的CS置位1,表示主机与该从机开始通讯。主机通过MOSI(主输出从输入)发送数据到从机,从机接收数据;从机通过MISO(主输入从输出)发送数据到主机,主机接收数据。数据高位先行还是低位先行可以通过SPI_CR1寄存器中的LSBFIRST配置。下图中的数据高位先行。
在这里插入图片描述

1 软件模拟SPI协议-读写W25Q64闪存

1 SPI时序

起始条件:SS从高电平切换到低电平
终止条件:SS从低电平切换到高电平
在这里插入图片描述
交换一个字节
常用模式0:CPOL=0,CPHA=0,低电平,第一个边沿移入数据(采样)。
模式1:CPOL=0,CPHA=1,低电平,第二个边沿移入数据(采样)。
模式2:CPOL=1,CPHA=0,高电平,第一个边沿移入数据(采样)。
模式3:CPOL=1,CPHA=1,高电平,第二个边沿移入数据(采样)。
从机未被选中时,MOSI(主输出,从输入)配置位高阻态。
CPOL时钟极性控制空闲状态下SCK时钟时钟的电平。
CPOL=0:空闲状态时,SCK时钟为低电平;
CPOL=1:空闲状态时,SCK时钟为高电平;
CPHA时钟相位控制在SCK时钟的第一个边沿还是第二个边沿采样数据。
CPHA=0:SCK第一个边沿移入数据(采样),第二个边沿移出数据;
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据(采样);
在这里插入图片描述
图中,当CPOL=0,CPHA=0,SCK时钟空闲状态低电平,第一个边沿移入数据(采样)。主机通过MISO接收从机发送的数据,从机通过MOSI接收主机发送的数据。主、从机数据得到交换。
在这里插入图片描述

2 W25Q64闪存

W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景。
存储介质:Nor Flash(闪存)
时钟频率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)
存储容量(24位地址):
W25Q40: 4Mbit / 512KByte
W25Q80: 8Mbit / 1MByte
W25Q16: 16Mbit / 2MByte
W25Q32: 32Mbit / 4MByte
W25Q64: 64Mbit / 8MByte
W25Q128: 128Mbit / 16MByte
W25Q256: 256Mbit / 32MByte
W25Q64BV支持标准串行外设接口(SPI)、高性能双/四倍输出以及双/四倍I/O SPI:串行时钟、芯片选择、串行数据I/O0(DI)、I/O1(DO)、I/O2(/WP)和I/O3(/HOLD)。
标准SPI:CLK,CS,DO,DI,WP,Hold;
双倍SPI:CLK,CS,I/O0,I/O1,WP,Hold;
四倍SPI:CLK,CS,I/O0,I/O1,I/O2,I/O3。
W25Qxx硬件电路图
在这里插入图片描述
W25Q64BV结构图
W25Q64BV存储容量为8M-Byte,内部被划分为128个Block(64KB),每个Block被划分为16个Sector(4KB),每个Sector被划分为16个Page(256Byte,0xff)。
状态寄存器中保存闪存的写使能/使能、写保护以及四倍SPI设置状态,可以读取和写入。
在这里插入图片描述
页编程
W25Q64BV中的SPI协议支持模式0和模式3。由于存储容量为8M-Byte,需要24位地址表示(2^24/1024/1024=16M-Byte)。
页编程首先执行写使能,然后写入指令码02h(16进制),之后紧跟24位地址和至少一个字节数据。
24位地址的高16位进入页地址锁存/计数器,确定写入的页面;低8位进入字节地址锁存/计数器,确定页面中具体地址。
写入的数据先存入256Byte的页缓冲区(RAM)中,提高写入速度,之后再一次性将缓冲区的数据复制到flash闪存中。
每次页编程数据不宜过多,否则前面的数据可能会被覆盖掉。
在这里插入图片描述
写入操作注意事项:

  • 写入操作前,必须先进行写使能
  • 每个数据位只能由1改写为0,不能由0改写为1
  • 写入数据前必须先擦除,擦除后,所有数据位变为1
  • 擦除必须按最小擦除单元进行
  • 连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入
  • 写入操作结束后,芯片进入忙状态,不响应新的读写操作

读入操作注意事项:直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取。
实验:使用软件模拟SPI协议读写闪存数据
my_spi.h

#ifndef __MY_SPI_H
#define __MY_SPI_H

#include "stm32f10x.h"

#define SPI1_GPIO_PORT   GPIOA
#define SPI_RCC_CLK      RCC_APB2Periph_GPIOA
#define SPI1_CS_PIN      GPIO_Pin_4 
#define SPI1_SCK_PIN     GPIO_Pin_5    
#define SPI1_MISO_PIN    GPIO_Pin_6 
#define SPI1_MOSI_PIN    GPIO_Pin_7 

#define MySPI_W_CS(val)  		{GPIO_WriteBit(SPI1_GPIO_PORT,SPI1_CS_PIN,(BitAction) val);}
#define MySPI_W_CLK(val) 		{GPIO_WriteBit(SPI1_GPIO_PORT,SPI1_CLK_PIN,(BitAction) val);}
#define MySPI_W_MOSI(val)   {GPIO_WriteBit(SPI1_GPIO_PORT,SPI1_MOSI_PIN,(BitAction) val);}
#define MySPI_R_MISO(val)   {GPIO_ReadInputDataBit(SPI1_GPIO_PORT,SPI1_MISO_PIN);}

void MySPI_Init();
void MySPI_Start();
void MySPI_Stop();
uint8_t MySPI_SwapByteByMode0(uint8_t ByteSend);
#endif

my_spi.c

#include "my_spi.h"

void MySPI_Init()
{	
   GPIO_InitTypeDef GPIO_InitStruct;
   RCC_APB2PeriphClockCmd(SPI_RCC_CLK,ENABLE);
   
   /*
   软件模拟SPI。
   控制GPIO口,SCK、CS、MOSI推挽输出模式,
   MISO浮空或上拉输入模式
   */
   GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
   GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
   GPIO_InitStruct.GPIO_Pin = SPI1_SCK_PIN | SPI1_CS_PIN | SPI1_MOSI_PIN;
   GPIO_Init(SPI1_GPIO_PORT,&GPIO_InitStruct);
   
   GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
   GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
   GPIO_InitStruct.GPIO_Pin = SPI1_MISO_PIN;	
   GPIO_Init(SPI1_GPIO_PORT,&GPIO_InitStruct);
   
   //模式0:SCK默认低电,CS默认高电平
   GPIO_WriteBit(SPI1_GPIO_PORT,SPI1_CS_PIN,1);
   GPIO_WriteBit(SPI1_GPIO_PORT,SPI1_SCK_PIN,0);
}

void MySPI_Start()
{
   GPIO_WriteBit(SPI1_GPIO_PORT,SPI1_CS_PIN,0);
}

void MySPI_Stop()
{
   GPIO_WriteBit(SPI1_GPIO_PORT,SPI1_CS_PIN,1);
}

/**
 * @brief  模式0:CPOL=0,CPHA=0。SCK默认低电平,SCK第一个边沿采集数据。最常用
 * @param  
 * @retval 
 */
uint8_t MySPI_SwapByteByMode0(uint8_t ByteSend)
{
   uint8_t index,ByteReceive=0x00;
   
   for(index = 0;index<8;index++)
   {
   	GPIO_WriteBit(SPI1_GPIO_PORT,SPI1_MOSI_PIN,ByteSend & (0x80 >> index));
   	GPIO_WriteBit(SPI1_GPIO_PORT,SPI1_SCK_PIN,1);
   	if(GPIO_ReadInputDataBit(SPI1_GPIO_PORT,SPI1_MISO_PIN))
   	{
   		ByteReceive |= (0x80 >> index);
   	}
   	GPIO_WriteBit(SPI1_GPIO_PORT,SPI1_SCK_PIN,0);
   }
   
   return ByteReceive;
}

/*
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
   uint8_t index;
   
   for(index = 0;index<8;index++)
   {
   	GPIO_WriteBit(SPI1_GPIO_PORT,SPI1_MOSI_PIN,ByteSend & 0x80);
   	ByteSend <<=  1;
   	GPIO_WriteBit(SPI1_GPIO_PORT,SPI1_SCK_PIN,1);
   	if(GPIO_ReadInputDataBit(SPI1_GPIO_PORT,SPI1_MISO_PIN))
   	{
   		ByteSend |= 0x01;
   	}
   	GPIO_WriteBit(SPI1_GPIO_PORT,SPI1_SCK_PIN,0);
   }
   
   return ByteSend;
}
*/

/**
 * @brief  模式1:CPOL=0,CPHA=1。SCK默认低电平,SCK第二个边沿采集数据
 * @param  
 * @retval 
 */
uint8_t MySPI_SwapByteByMode1(uint8_t ByteSend)
{
   uint8_t index,ByteReceive=0x00;
   
   for(index = 0;index<8;index++)
   {
   	GPIO_WriteBit(SPI1_GPIO_PORT,SPI1_SCK_PIN,1);
   	GPIO_WriteBit(SPI1_GPIO_PORT,SPI1_MOSI_PIN,ByteSend & (0x80 >> index));
   	GPIO_WriteBit(SPI1_GPIO_PORT,SPI1_SCK_PIN,0);
   	if(GPIO_ReadInputDataBit(SPI1_GPIO_PORT,SPI1_MISO_PIN))
   	{
   		ByteReceive |= (0x80 >> index);
   	}
   }
   
   return ByteReceive;
}

w25q64.h

#ifndef __W25Q64_H
#define __W25Q64_H

#include "stm32f10x.h"

#define W25Q64_WRITE_ENABLE       0X06
#define W25Q64_READ_STATUS_REG_1  0X05
#define W25Q64_PAGE_PROGRAM       0X02
#define W25Q64_SECTOR_ERASE       0X20
#define W25Q64_JEDEC_ID           0X9F
#define W25Q64_READ_DATA          0X03

#define W25Q64_DUMMY_BYTE         0XFF

void W25Q64_Init();
void W25Q64_ReadID(uint8_t *MID,uint16_t *DID);
void W25Q64_WriteEnable();
void W25Q64_WaitBusy();
void W25Q64_PageProgram(uint32_t Addr,uint8_t *DataArr,uint16_t Count);
void W25Q64_SectorErase(uint32_t Addr);
void W25Q64_ReadData(uint32_t Addr,uint8_t *DataArr,uint32_t Count);
#endif

w25q64.c

#include "w25q64.h"
#include "my_spi.h"

void W25Q64_Init()
{
	MySPI_Init();
}

/**
  * @brief  读取厂商id和设备id。多个返回值,建议使用指针
  * @param  
  * @retval 
  */
void W25Q64_ReadID(uint8_t *MID,uint16_t *DID)
{
	MySPI_Start();
	MySPI_SwapByteByMode0(W25Q64_JEDEC_ID);
	*MID = MySPI_SwapByteByMode0(W25Q64_DUMMY_BYTE);
	*DID = MySPI_SwapByteByMode0(W25Q64_DUMMY_BYTE);
	*DID <<= 8;
	*DID |= MySPI_SwapByteByMode0(W25Q64_DUMMY_BYTE);
	MySPI_Stop();
}

/**
  * @brief  写使能
  * @param  
  * @retval 
  */
void W25Q64_WriteEnable()
{
	MySPI_Start();
	MySPI_SwapByteByMode0(W25Q64_WRITE_ENABLE);
	MySPI_Stop();
}

/**
  * @brief  等待忙状态
  * @param  
  * @retval 
  */
void W25Q64_WaitBusy()
{
	uint32_t Timeout = 10000;
	MySPI_Start();
	MySPI_SwapByteByMode0(W25Q64_READ_STATUS_REG_1);
	while((MySPI_SwapByteByMode0(W25Q64_DUMMY_BYTE) & 0x01) == 1)
	{
		if(Timeout == 0)
		{
			break;
		}
		Timeout--;
	}
	MySPI_Stop();
}

/**
  * @brief  页编程
  * @param  
  * @retval 
  */
void W25Q64_PageProgram(uint32_t Addr,uint8_t *DataArr,uint16_t Count)
{
	uint8_t index;
	//写使能
	W25Q64_WriteEnable();
	
	//SPI起始信号产生
	MySPI_Start();
	//SPI交换数据
	MySPI_SwapByteByMode0(W25Q64_PAGE_PROGRAM);
	for(index = 3;index >0;index--)
	{
		MySPI_SwapByteByMode0(Addr >> (index * 8 - 8));
	}
	while(Count--)
	{
		MySPI_SwapByteByMode0(*DataArr);
		DataArr++;
	}
	//SPI停止信号产生
	MySPI_Stop();

	W25Q64_WaitBusy();
}

/**
  * @brief  区擦除
  * @param  
  * @retval 
  */
void W25Q64_SectorErase(uint32_t Addr)
{
	uint8_t index;
	W25Q64_WriteEnable();
	
	MySPI_Start();
	MySPI_SwapByteByMode0(W25Q64_SECTOR_ERASE);
	for(index = 3;index >0;index--)
	{
		MySPI_SwapByteByMode0(Addr >> (index * 8 - 8));
	}
	MySPI_Stop();
	
	W25Q64_WaitBusy();
}

/**
  * @brief  读数据
  * @param  
  * @retval 
  */
void W25Q64_ReadData(uint32_t Addr,uint8_t *DataArr,uint32_t Count)
{
	uint8_t index;
	MySPI_Start();
	MySPI_SwapByteByMode0(W25Q64_READ_DATA);
	for(index = 3;index >0;index--)
	{
		MySPI_SwapByteByMode0(Addr >> (index * 8 - 8));
	}
	while(Count--)
	{
		*DataArr = MySPI_SwapByteByMode0(W25Q64_DUMMY_BYTE);
		DataArr++;
	}
	MySPI_Stop();
}

main.c

#include "stm32f10x.h"                  // Device header
#include "w25q64.h"
#include "OLED.h"

uint8_t MID;
uint16_t DID;
uint8_t DataArr[4] = {0x14,0x15,0x16,0x17};
uint8_t RxDataArr[4];

int main()
{
	W25Q64_Init();
	OLED_Init();
	
	//显示屏显示
	OLED_ShowString(1,1,"MID:");
	OLED_ShowString(2,1,"DID:");
	
	W25Q64_ReadID(&MID,&DID);
	OLED_ShowHexNum(1,5,MID,2);
	OLED_ShowHexNum(2,5,DID,4);

	W25Q64_SectorErase(0x000000);
	W25Q64_PageProgram(0x000000,DataArr,4);
	W25Q64_ReadData(0x000000,RxDataArr,4);
	OLED_ShowHexNum(3,1,RxDataArr[0],2);
	OLED_ShowHexNum(3,5,RxDataArr[1],2);
	OLED_ShowHexNum(4,1,RxDataArr[2],2);
	OLED_ShowHexNum(4,5,RxDataArr[3],2);
	while(1)
	{
	}
}

2 硬件SPI协议

STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担。
可配置8位/16位数据帧、高位先行/低位先行。时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)。
支持多主机模型、主或从操作。可精简为半双工/单工通信。支持DMA。兼容I2S协议。
SPI外设结构图
当通过MOSI向外部发送数据时,先将数据写入到发送缓冲区,接着并行传输到移位寄存器,并置TXE=1,发送缓冲区为空。
发送数据的同时,也会通过MISO接收外部的数据。先将数据写入到移位寄存器,接着并行传输到接收缓冲区,并置RXNE=1,接收缓冲区为空。
在这里插入图片描述
主模式、全双工模式下连续传输
发送数据时,上一个数据从移位寄存器中发送结束之前,下一个数据已经写入发送缓冲区。
接收数据时,下一个数据被保存到移位寄存器结束之前,上一个数据已经从接收缓冲区读取。
在这里插入图片描述
SPI非连续传输,较简单,最常用
发送数据时,上一个数据从移位寄存器中完全发送出去时,下一个数据才写入发送缓冲区。
接收数据时,下一个数据被完全保存到移位寄存器时,上一个数据才从接收缓冲区读取。
在这里插入图片描述
SPI初始化结构体
在这里插入图片描述
实验:硬件SPI协议读取闪存数据
my_spi.h

#ifndef __MY_SPI_H
#define __MY_SPI_H

#include "stm32f10x.h"

#define SPI_PERIPH_PORT  SPI1
#define SPI_PERIPH_RCC_CLK      RCC_APB2Periph_SPI1
#define SPI1_GPIO_PORT   GPIOA
#define SPI_RCC_CLK      RCC_APB2Periph_GPIOA
#define SPI1_CS_PIN      GPIO_Pin_4 
#define SPI1_SCK_PIN     GPIO_Pin_5    
#define SPI1_MISO_PIN    GPIO_Pin_6 
#define SPI1_MOSI_PIN    GPIO_Pin_7 

void MySPI_Init();
void MySPI_Start();
void MySPI_Stop();
uint8_t MySPI_SwapByteByDiscontinuous(uint8_t ByteSend);
void MySPI_SwapByteByContinuous(uint8_t *ByteSend,uint16_t SendCount,uint8_t *ByteReceive);
#endif

my_spi.c

#include "my_spi.h"

void MySPI_Init()
{	
	GPIO_InitTypeDef GPIO_InitStruct;
	SPI_InitTypeDef SPI_InitStruct;
	RCC_APB2PeriphClockCmd(SPI_RCC_CLK,ENABLE);
	RCC_APB2PeriphClockCmd(SPI_PERIPH_RCC_CLK,ENABLE);
	
	//spi外设复用gpio初始化
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	//复用推挽输出
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = SPI1_SCK_PIN | SPI1_MOSI_PIN;
	GPIO_Init(SPI1_GPIO_PORT,&GPIO_InitStruct);
	
	//上拉输入
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Pin = SPI1_MISO_PIN;
	GPIO_Init(SPI1_GPIO_PORT,&GPIO_InitStruct);
	
	//CS片选,通用推挽输出
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStruct.GPIO_Pin = SPI1_CS_PIN;
	GPIO_Init(SPI1_GPIO_PORT,&GPIO_InitStruct);
	
	//spi初始化
	//波特率分频因子,时钟频率
	SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
	//第一个边沿采集数据
	SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;
	//空闲状态下,SCK低电平
	SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;
	//CRC校验,复位值为0x0007
	SPI_InitStruct.SPI_CRCPolynomial = 7;
	//传输数据宽度:8bit、16bit
	SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
	//SPI双线全双工
	SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
	//高位先行
	SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
	//主模式
	SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
	//软件片选,一般使用软件模拟
	SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;
	
	SPI_Init(SPI_PERIPH_PORT,&SPI_InitStruct);
	
	SPI_Cmd(SPI_PERIPH_PORT,ENABLE);
	GPIO_WriteBit(SPI1_GPIO_PORT,SPI1_CS_PIN,1);
}

void MySPI_Start()
{
	GPIO_WriteBit(SPI1_GPIO_PORT,SPI1_CS_PIN,0);
}

void MySPI_Stop()
{
	GPIO_WriteBit(SPI1_GPIO_PORT,SPI1_CS_PIN,1);
}

/**
  * @brief  模式0:CPOL=0,CPHA=0。SCK默认低电平,SCK第一个边沿采集数据。最常用
  * @param  
  * @retval 
  */
void MySPI_SwapByteByContinuous(uint8_t *ByteSend,uint16_t SendCount,uint8_t *ByteReceive)
{
	while(SPI_I2S_GetFlagStatus(SPI_PERIPH_PORT, SPI_I2S_FLAG_TXE) != SET);
	//发送数据
	SPI_I2S_SendData(SPI_PERIPH_PORT,*ByteSend++);
	SendCount--;
	while(SendCount--)
	{
		//发送缓冲区为空
		while(SPI_I2S_GetFlagStatus(SPI_PERIPH_PORT, SPI_I2S_FLAG_TXE) != SET);
		SPI_I2S_SendData(SPI_PERIPH_PORT,*ByteSend++);
		//接收缓冲区非空
		while(SPI_I2S_GetFlagStatus(SPI_PERIPH_PORT, SPI_I2S_FLAG_RXNE) != SET);
		*ByteReceive++ = SPI_I2S_ReceiveData(SPI_PERIPH_PORT);
	}
	while(SPI_I2S_GetFlagStatus(SPI_PERIPH_PORT, SPI_I2S_FLAG_RXNE) != SET);
	*ByteReceive++ = SPI_I2S_ReceiveData(SPI_PERIPH_PORT);
	
//	while((SPI_I2S_GetFlagStatus(SPI_PERIPH_PORT, SPI_I2S_FLAG_TXE) != SET) || (SPI_I2S_GetFlagStatus(SPI_PERIPH_PORT, SPI_I2S_FLAG_BSY) != SET));	
//	SPI_Cmd(SPI_PERIPH_PORT,DISABLE);
}

/**
  * @brief  模式0:CPOL=0,CPHA=0。SCK默认低电平,SCK第一个边沿采集数据。最常用
						非连续传输。常用
  * @param  
  * @retval 
  */
uint8_t MySPI_SwapByteByDiscontinuous(uint8_t ByteSend)
{
	while(SPI_I2S_GetFlagStatus(SPI_PERIPH_PORT, SPI_I2S_FLAG_TXE) != SET);
	SPI_I2S_SendData(SPI_PERIPH_PORT,ByteSend);
	while(SPI_I2S_GetFlagStatus(SPI_PERIPH_PORT, SPI_I2S_FLAG_RXNE) != SET);
	return SPI_I2S_ReceiveData(SPI_PERIPH_PORT);
}

w25q64.h

#ifndef __W25Q64_H
#define __W25Q64_H

#include "stm32f10x.h"

#define W25Q64_WRITE_ENABLE       0X06
#define W25Q64_READ_STATUS_REG_1  0X05
#define W25Q64_PAGE_PROGRAM       0X02
#define W25Q64_SECTOR_ERASE       0X20
#define W25Q64_JEDEC_ID           0X9F
#define W25Q64_READ_DATA          0X03

#define W25Q64_DUMMY_BYTE         0XFF

void W25Q64_Init();
void W25Q64_ReadID(uint8_t *MID,uint16_t *DID);
void W25Q64_WriteEnable();
void W25Q64_WaitBusy();
void W25Q64_PageProgram(uint32_t Addr,uint8_t *DataArr,uint16_t Count);
void W25Q64_SectorErase(uint32_t Addr);
void W25Q64_ReadData(uint32_t Addr,uint8_t *DataArr,uint32_t Count);
void W25Q64_PageProgramByContinuous(uint32_t Addr,uint8_t *DataArr,uint16_t Count,uint8_t *ByteReceive);
#endif

w25q64.c

#include "w25q64.h"
#include "my_spi.h"

void W25Q64_Init()
{
	MySPI_Init();
}

/**
  * @brief  读取厂商id和设备id。多个返回值,建议使用指针
  * @param  
  * @retval 
  */
void W25Q64_ReadID(uint8_t *MID,uint16_t *DID)
{
	MySPI_Start();
	MySPI_SwapByteByDiscontinuous(W25Q64_JEDEC_ID);
	*MID = MySPI_SwapByteByDiscontinuous(W25Q64_DUMMY_BYTE);
	*DID = MySPI_SwapByteByDiscontinuous(W25Q64_DUMMY_BYTE);
	*DID <<= 8;
	*DID |= MySPI_SwapByteByDiscontinuous(W25Q64_DUMMY_BYTE);
	MySPI_Stop();
}

void W25Q64_WriteEnable()
{
	MySPI_Start();
	MySPI_SwapByteByDiscontinuous(W25Q64_WRITE_ENABLE);
	MySPI_Stop();
}

void W25Q64_WaitBusy()
{
	uint32_t Timeout = 10000;
	MySPI_Start();
	MySPI_SwapByteByDiscontinuous(W25Q64_READ_STATUS_REG_1);
	while((MySPI_SwapByteByDiscontinuous(W25Q64_DUMMY_BYTE) & 0x01) == 1)
	{
		if(Timeout == 0)
		{
			break;
		}
		Timeout--;
	}
	MySPI_Stop();
}

void W25Q64_PageProgram(uint32_t Addr,uint8_t *DataArr,uint16_t Count)
{
	uint8_t index;
	W25Q64_WriteEnable();
	
	MySPI_Start();
	MySPI_SwapByteByDiscontinuous(W25Q64_PAGE_PROGRAM);
	for(index = 3;index >0;index--)
	{
		MySPI_SwapByteByDiscontinuous(Addr >> (index * 8 - 8));
	}
	while(Count--)
	{
		MySPI_SwapByteByDiscontinuous(*DataArr++);
	}
	MySPI_Stop();

	W25Q64_WaitBusy();
}

void W25Q64_SectorErase(uint32_t Addr)
{
	uint8_t index;
	W25Q64_WriteEnable();
	
	MySPI_Start();
	MySPI_SwapByteByDiscontinuous(W25Q64_SECTOR_ERASE);
	for(index = 3;index >0;index--)
	{
		MySPI_SwapByteByDiscontinuous(Addr >> (index * 8 - 8));
	}
	MySPI_Stop();
	
	W25Q64_WaitBusy();
}

void W25Q64_ReadData(uint32_t Addr,uint8_t *DataArr,uint32_t Count)
{
	uint8_t index;
	MySPI_Start();
	MySPI_SwapByteByDiscontinuous(W25Q64_READ_DATA);
	for(index = 3;index >0;index--)
	{
		MySPI_SwapByteByDiscontinuous(Addr >> (index * 8 - 8));
	}
	while(Count--)
	{
		*DataArr++ = MySPI_SwapByteByDiscontinuous(W25Q64_DUMMY_BYTE);
	}
	MySPI_Stop();
}


void W25Q64_PageProgramByContinuous(uint32_t Addr,uint8_t *DataArr,uint16_t Count,uint8_t *ByteReceive)
{
	uint8_t index;
	W25Q64_WriteEnable();
	
	MySPI_Start();
	MySPI_SwapByteByDiscontinuous(W25Q64_PAGE_PROGRAM);
	for(index = 3;index >0;index--)
	{
		MySPI_SwapByteByDiscontinuous(Addr >> (index * 8 - 8));
	}
	MySPI_SwapByteByContinuous(DataArr,Count,ByteReceive);
	
	MySPI_Stop();

	W25Q64_WaitBusy();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值