文章目录
前言
参考资料:江协科技
参考资料:野火
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();
}