首页 > 其他分享 >STM32&IIC与SPI详解

STM32&IIC与SPI详解

时间:2024-08-14 11:23:46浏览次数:15  
标签:SCL PIN STM32 SPI Oled SDA IIC GPIO

单片机里的通信协议其实蛮多的,IIC;SPI;MQTT;CAN;包括串口也是一种通信协议。而串口通信虽然实现了全双工,但需要至少三根线,为了节省这一根线的成本,于是IIC诞生了。

目录

一.IIC协议

1.IIC的结构

2.IIC的特点

3.IIC的通信时序

4.具体配置(32HAL库版)

二.SPI协议

1.SPI的结构

2.SPI的特点

3.具体配置

三:联合配置

1.OLED屏幕的配置

OLED的数据格式

OLED的写入模式

Oled的命令

代码:

W25Q128配置

W25Q128的命令

W25Q128的寄存器

代码:

最终配置:

祝你看完就会


一.IIC协议

IIC协议其实就是一种标准外设协议,其实所谓协议,本质上就是各种时序电路的组合。IIC也不例外,它的最大特点就是特别的轻量级。

1.IIC的结构

IIC的简便和轻量级就在于它只有两条线,一条是时钟线SCL,一条是数据线SDA。说白了,就是在SCL的控制下在SDA上传输命令/数据。

在这张图上可以看到同时有很多设备连接在这两条线上,它们之间的关系一般是一主多从。

2.IIC的特点

1.IIC是半双工通信,因为SCL不负责传输数据,只有一条SDA数据线,无法发的同时接收信息。
2.如果你使用的是STM32芯片,那么如果没有配置上拉电阻的话GPIO口必须配置为开漏输出。因为假设时序出现错误两个设备一个讲SDA拉低一个将SDA拉高,那么将导致短路。
3.同步传输:数据的传输是严格按照时钟线来进行的。

3.IIC的通信时序

IIC的通信主要依靠四个信号:起始信号,应答信号,停止信号;读写信号;
START:

  
STOP:


ASK:


读写:

        其中ASK就是在在吧SCL拉高的期间读取SDA的的电平。读取完成后立即拉低。读取到的SDA假设为1则表示有应答,若为0则表示没有应答。
        读写信号则是在起始信号后将SCL拉低的期间向SDA上放或者读取数据。
        START和STOP比较简单不多赘述。 

4.具体配置(32HAL库版)

头文件:

#ifndef __IIC_H__
#define __IIC_H__

#include "sys.h"

#define SDA_PULL_UP()			HAL_GPIO_WritePin(GPIO_POTT, SDA, GPIO_PIN_SET)
#define SDA_PULL_DOWN()			HAL_GPIO_WritePin(GPIO_POTT, SDA, GPIO_PIN_RESET)
#define SCL_PULL_UP()			HAL_GPIO_WritePin(GPIO_POTT, SCL, GPIO_PIN_SET)
#define SCL_PULL_DOWN()			HAL_GPIO_WritePin(GPIO_POTT, SCL, GPIO_PIN_RESET)

void iic_Stop(GPIO_TypeDef *GPIO_POTT,uint16_t SDA,uint16_t SCL);
void iic_Start(GPIO_TypeDef *GPIO_POTT,uint16_t SDA,uint16_t SCL);
void iic_Ask(GPIO_TypeDef *GPIO_POTT,uint16_t SDA,uint16_t SCL);
void iic_Sendbyte(GPIO_TypeDef *GPIO_POTT,uint16_t SDA,uint16_t SCL,uint8_t DATA);

#endif

首先我这里用了大量的宏定义了很多宏函数来方便控制对应线的电平高低。另外如果你看的细致的话会发现我这里宏函数中的参数也是宏,然而你却在这儿找不到这个宏。其实它并不是宏,我用了一种稍微怪的方式来简便我在IIC定义里的工作量,但加大了一点调用的工作量。 
源文件:

#include "IIC.h"

void iic_Stop(GPIO_TypeDef *GPIO_POTT,uint16_t SDA,uint16_t SCL){
	
	SCL_PULL_UP();
	SDA_PULL_DOWN();			//先拉高SCL再拉高SDA
	SDA_PULL_UP();
	
}

void iic_Start(GPIO_TypeDef *GPIO_POTT,uint16_t SDA,uint16_t SCL){
	SCL_PULL_UP();			//SCL高电平期间SDA下降沿
	SDA_PULL_UP();
	SDA_PULL_DOWN();
	SCL_PULL_DOWN();
}

void iic_Ask(GPIO_TypeDef *GPIO_POTT,uint16_t SDA,uint16_t SCL){
	SCL_PULL_UP();		
	SCL_PULL_DOWN();
}

void iic_Sendbyte(GPIO_TypeDef *GPIO_POTT,uint16_t SDA,uint16_t SCL,uint8_t DATA){
	uint8_t i,tmp = DATA;
										//主机在SCL低电平期间在SDA上放数据
	for(i = 0; i<8; i++){
		if((tmp & 0x80) == 0x80)		//DATA &= 1000 0000 
			SDA_PULL_UP();
		else
			SDA_PULL_DOWN();
		
		SCL_PULL_UP();				//从机在SCL高电平期间读取这一位		
		SCL_PULL_DOWN();
		tmp = tmp << 1;
	}
}

想必如果你由上面的疑问看了源文件也就理解 ,我定义的宏函数里的参数并不是宏而是调用它的函数里的参数。所以我增加的调用方的工作量就是传参很多,IIC这里就非常简介易懂了。

另外一个点就是数据传输函数中是一位一位放的,不断地放不断地左移知道8次轮回完成一字节的传输则结束。 

二.SPI协议

SPI的全称是Serial Peripheral Interface。有基础的人估计一眼就看见Serial(串口)了。没错,其实这东西它使用非常像串口。不仅如此,配置起来也非常像串口。

1.SPI的结构

SPI一共由四条线组成:SCK;MISO;MOSI;NSS(CS)

其中,MISO和MOSI分别是:Master Input Slave Output

这里某些同学不要想歪了哈,Master这里就是主机,Slave就是从机,所以这两根线就很好理解了,简单说就是RX和TX一样。

然而:虽然这里也是两条线也是全双工,但是SPI的传输数据方式却是非常的特殊的

图中画圈的地方都是SPI的重点,其中特殊就在于它的位移寄存器 。

2.SPI的特点

1.SPI最大的特点就来自于它的位移寄存器,它每发送一字节的数据就必须收一字节的数据,同样的,它要收一字节的数据就必须发出一字节数据
2.看了特点一你会认为SPI是强制全双工的,但其实不是,它完全可以配置为半双工或者只有一条数据线。
3.SPI的工作模式比较特别,它的工作模式取决于时钟极性时钟相位

第三点展开来说:
时钟极性控制SPI数据线上没有数据时SCL的电平状态:
CPOL为0则空闲时为低电平,反之则为高电平。
CPHA为0则每一个奇数边缘采样数据(第一个),反之则为偶数边缘采样。

你要是看不懂也没关系,总之就是CPOL和CPHA排列组合一共有四种工作模式。

3.具体配置

SPI的配置一般是依靠板子上确定的外设的,因为它不像IIC那样的轻量级。它有很多的东西需要配,比如:分频数;时钟启动;工作模式;是否半双工等等。

#include "SPI.h"
SPI_HandleTypeDef spi_handle = {0};

void SPI_INIT(void){
	
	spi_handle.Instance = SPI1;
	spi_handle.Init.Mode = SPI_MODE_MASTER; 							//配置主从模式
	spi_handle.Init.Direction = SPI_DIRECTION_2LINES;					//半双工全双工选择
	spi_handle.Init.DataSize = SPI_DATASIZE_8BIT;
	spi_handle.Init.CLKPolarity = SPI_POLARITY_LOW;					//低电平有效
	spi_handle.Init.CLKPhase = SPI_PHASE_1EDGE;						//奇数取值
	spi_handle.Init.NSS = SPI_NSS_SOFT;								//软件调控NSS
	spi_handle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;	//分频数选择
	spi_handle.Init.FirstBit = SPI_FIRSTBIT_MSB;	
	spi_handle.Init.TIMode = SPI_TIMODE_DISABLE;
    spi_handle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
    spi_handle.Init.CRCPolynomial = 7;	
	HAL_SPI_Init(&spi_handle);

}

void HAL_SPI_MspInit(SPI_HandleTypeDef *spi_handle){
    if(spi_handle->Instance == SPI1)
    {
        GPIO_InitTypeDef gpio_initstruct;
        __HAL_RCC_GPIOA_CLK_ENABLE();                           
        __HAL_RCC_SPI1_CLK_ENABLE();

        gpio_initstruct.Pin = GPIO_PIN_4;          
        gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP;           
        gpio_initstruct.Pull = GPIO_PULLUP;                    
        gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;          
        HAL_GPIO_Init(GPIOA, &gpio_initstruct);
        
        gpio_initstruct.Pin = GPIO_PIN_5 | GPIO_PIN_7;          
        gpio_initstruct.Mode = GPIO_MODE_AF_PP;           
        HAL_GPIO_Init(GPIOA, &gpio_initstruct);
        
        gpio_initstruct.Pin = GPIO_PIN_6;          
        gpio_initstruct.Mode = GPIO_MODE_INPUT;           
        HAL_GPIO_Init(GPIOA, &gpio_initstruct);
    }
	
}

uint8_t SPI_swap_byte(uint8_t data){
	uint8_t rev_data = 0;
	HAL_SPI_TransmitReceive(&spi_handle,&data,&rev_data,1,1000);
	return rev_data;
}

这里对于Instance的配置还是挺繁琐的,但是也只是繁琐并没有难度。剩下的就是时钟;GPIO配置;然后可以看到在发送的部分它的函数叫HAL_SPI_TransmitReceive,对应上了我们说的接收的同时必须发送。

三:联合配置

两个协议都学会了,如何应用呢?IIC的入门外设还是非常经典的--OLED屏幕。那SPI呢?可以采用一块存储块进行配合读写。我这里就用W25Q128的存储模块。

1.OLED屏幕的配置

Oled的配置核心其实就是显示东西嘛,所以其实总结成一句话就是:

                        告诉屏幕在哪里显示什么东西。

OLED是只可以选择亮或者灭的,所以显示什么东西其实最终说白了是各种点阵,这个东西其实没啥技术含量。另外OLED模块的初始化也是不需要学的,你就照着把一堆命令直接复制过来用就行了。所以主要的配置重点在于:如何告诉他

那么这个时候我们就需要读一下OLED的手册了。

OLED的数据格式


以上为手册的原图,可以看到它的发送全部都是 Control byte + Data byte。那么这是什么意思呢?

说白了,就是Control byte 用来让Oled判断接下来所接收的数据到底是命令还是显示数据。
可以看到,整个数据帧的格式就是:

        Start信号--写入数据模式--ASK--Control byte--Data byte--ASK--Stop信号

那么此时的问题就变成了写入模式是什么?以及Control byte是什么?

Control byte看右下角,D/C的后一位写0则表示接下来的数据是Command,写1则表示接下来的数据Data。

其实自己看手册可绝望了,不信? 给:

OLED的写入模式

一共有四种:

         

总而言之呢,其实你只记得住第一个就行,因为我们用哪一个都ok,第一个在不配置的情况下是被默认选定的。只需要知道它在写完后会自动向右偏移一位。当写道最右边后会返回来这一行的最左边。那么这里就需要稍微知道一下屏幕的大小了:

整个屏幕是128*64的,每一个字节的八位是竖着排列的,逻辑为1的就亮为0就灭。
64 / 8 = 8,这也是为什么是8个page。

Oled的命令


Oled其实内置的是一块芯片,所以它的命令其实还是蛮多的。但是可以看到这里我给你截下来的命令都是关于位置设定和写入模式的设定的。

其实这里主要的就是Page和Column 的设定,Page的很简单B0~B7分别表示Page0到Page7.
主要就是Column需要给两次,因为有 128位嘛,所以需要两个字节。这就在编程的方面稍微有点小麻烦。

代码:
#include "oled.h"
#include "delay.h"
#include "front.h"

void OLED_INIT(){
    GPIO_INIT();
    
    delay_ms(100);    
    Oled_Write_Cmd(0xAE);   
    Oled_Write_Cmd(0xD5);   
    Oled_Write_Cmd(0x80);   
    Oled_Write_Cmd(0xA8);   
    Oled_Write_Cmd(0x3F);   
    Oled_Write_Cmd(0xD3);   
    Oled_Write_Cmd(0x00);   
    Oled_Write_Cmd(0x40);   
    Oled_Write_Cmd(0xA1);   
    Oled_Write_Cmd(0xC8);   
    Oled_Write_Cmd(0xDA);   
    Oled_Write_Cmd(0x12);
    Oled_Write_Cmd(0x81);   
    Oled_Write_Cmd(0xCF);   
    Oled_Write_Cmd(0xD9);   
    Oled_Write_Cmd(0xF1);
    Oled_Write_Cmd(0xDB);   
    Oled_Write_Cmd(0x30);
    Oled_Write_Cmd(0xA4);   
    Oled_Write_Cmd(0xA6);   
    Oled_Write_Cmd(0x8D);   
    Oled_Write_Cmd(0x14);
    Oled_Write_Cmd(0xAF);   
	
}

void GPIO_INIT(){
	GPIO_InitTypeDef gpio_init;
	
	gpio_init.Mode = GPIO_MODE_OUTPUT_PP;
	gpio_init.Pin = SDA_PIN|SCL_PIN;
	gpio_init.Pull = GPIO_PULLUP;
	gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
	
	__HAL_RCC_GPIOB_CLK_ENABLE();
	HAL_GPIO_Init(GPIOB, &gpio_init);
}

void Oled_Write_Cmd(uint8_t cmd){
	
	iic_Start(SDA_PORT,SDA_PIN,SCL_PIN);
	iic_Sendbyte(SDA_PORT,SDA_PIN,SCL_PIN,0x78);
	iic_Ask(SDA_PORT,SDA_PIN,SCL_PIN);
	iic_Sendbyte(SDA_PORT,SDA_PIN,SCL_PIN,0x00);
	iic_Ask(SDA_PORT,SDA_PIN,SCL_PIN);
	iic_Sendbyte(SDA_PORT,SDA_PIN,SCL_PIN,cmd);
	iic_Ask(SDA_PORT,SDA_PIN,SCL_PIN);
	iic_Stop(SDA_PORT,SDA_PIN,SCL_PIN);
}

void Oled_Write_Data(uint8_t data){
	iic_Start(SDA_PORT,SDA_PIN,SCL_PIN);
	iic_Sendbyte(SDA_PORT,SDA_PIN,SCL_PIN,0x78);
	iic_Ask(SDA_PORT,SDA_PIN,SCL_PIN);
	iic_Sendbyte(SDA_PORT,SDA_PIN,SCL_PIN,0x40);
	iic_Ask(SDA_PORT,SDA_PIN,SCL_PIN);
	iic_Sendbyte(SDA_PORT,SDA_PIN,SCL_PIN,data);
	iic_Ask(SDA_PORT,SDA_PIN,SCL_PIN);
	iic_Stop(SDA_PORT,SDA_PIN,SCL_PIN);
}

void Oled_Set_Position(uint8_t page,uint8_t column){
	Oled_Write_Cmd(0xB0 + page);						//选择页数
	Oled_Write_Cmd(column & 0x0F);						//低四位
	Oled_Write_Cmd(((column & 0xF0) >> 4) | 0x10);		//高四位

}

void Oled_clear(void){
	uint8_t i,j;	
	for(i = 0;i < 8;i++){			
		Oled_Set_Position(i,0);
		for(j = 0;j<128;j++){
			Oled_Write_Data(0);
		}	
	}
	
}

void Oled_show_picture(uint8_t hight,uint8_t wide,uint8_t *picture){
	uint8_t page,i,j;
	page = hight / 8;
	
	for(i = 0; i<page; i++){
		Oled_Set_Position(i,0);
		for(j = 0; j<wide; j++){
			Oled_Write_Data(picture[wide * i + j]);
		}
	}
	
}

void Oled_show_char(uint8_t Ocolumn, uint8_t Opage, uint8_t num, uint8_t size)
{
    uint8_t i, j, page;
    
    num = num - ' ';
    page = size / 8;
    if(size % 8)
        page++;
    
    for(j = 0; j < page; j++)
    {
        Oled_Set_Position(Opage + j,Ocolumn);
        for(i = size / 2 * j; i < size /2 * (j + 1); i++)
        {
            if(size == 12)
                Oled_Write_Data(ascii_6X12[num][i]);
            else if(size == 16)
                Oled_Write_Data(ascii_8X16[num][i]);
            else if(size == 24)
                Oled_Write_Data(ascii_12X24[num][i]);
                
        }
    }
}

void Oled_show_string(uint8_t column, uint8_t page, char *p, uint8_t size)
{
    while(*p != '\0')
    {
        Oled_show_char(column, page, *p, size);
        column += size/2;
        p++;
    }
}

W25Q128配置

对于它其实主要就是配置 读;写;等待空闲;这就是一个FLASH储存器。
FLASH的唯一特性就是:它只能写0不能写1.

并且该模块不需要初始化,命令同样很多但是我们用的很少。

W25Q128的命令
0x06写使能写入数据/擦除之前,必须先发送该指令
0x05读 SR1判定 FLASH 是否处于空闲状态,擦除用
0x03读数据读取数据
0x02页写写入数据,最多写256字节
0x20扇区擦除扇区擦除指令,最小擦除单位

这里主要讲一些要点:
1.在做任何通讯的操作之前,必须要拉低CS,也就是拉低片选。这也是它和IIC不同的地方,IIC是进行寻址,而它通过拉低片选。操作完成后必须再拉高
2.在进行读/写操作是需要发送地址,这个地址是三字节的而发送时每次发送的是一个字节,所以就需要位操作。

void send_addr(uint32_t address){
	
	SPI_swap_byte((uint8_t)address >> 16);		//右移的同时进行强转,强转保留低位,所以这里是发送高位
	SPI_swap_byte((uint8_t)address >> 8);		//中间8位
	SPI_swap_byte((uint8_t)address);			//低8位
}

3.任何和写相关的操作比如:马上要写,刚刚写完;必须进行等待空闲。

W25Q128的寄存器

这里就介绍这个一个,就是为了等待空闲操作使用。
在编程过程中,步骤也很简单:

拉低片选----向芯片发送读取Busy的命令----发送的同时接收----while来阻塞知道接收到BusyFlag为0----拉高片选。

void wait_busy(void){
	W25Q128_CS(0);
	SPI_swap_byte(FLASH_ReadStatusReg1);		//读取状态寄存器指令
	SPI_swap_byte(FLASH_DummyByte);				//接收状态寄存器flag	
	while((SPI_swap_byte(0xFF) & 0x01) == 1);	//等待知道寄存器busy位变为0	
	W25Q128_CS(1);
}
代码:
 
#include "w25q128.h"

uint16_t w25q128_config(void){
    uint16_t device_id = 0;
    W25Q128_CS(0);
    
    SPI_swap_byte(FLASH_ManufactDeviceID);
    SPI_swap_byte(0x00);
    SPI_swap_byte(0x00);
    SPI_swap_byte(0x00);
    device_id = SPI_swap_byte(FLASH_DummyByte) << 8;
    device_id |= SPI_swap_byte(FLASH_DummyByte);
    
    W25Q128_CS(1);
    return device_id;	
}

void wait_busy(void){
	W25Q128_CS(0);
	SPI_swap_byte(FLASH_ReadStatusReg1);		//读取状态寄存器指令
	SPI_swap_byte(FLASH_DummyByte);				//接收状态寄存器flag	
	while((SPI_swap_byte(0xFF) & 0x01) == 1);	//等待知道寄存器busy位变为0	
	W25Q128_CS(1);
}

void send_addr(uint32_t address){
	
	SPI_swap_byte((uint8_t)address >> 16);		//右移的同时进行强转,强转保留低位,所以这里是发送高位
	SPI_swap_byte((uint8_t)address >> 8);		//中间8位
	SPI_swap_byte((uint8_t)address);			//低8位
}

void w25q128_writ_enable(void)
{
    W25Q128_CS(0);
    SPI_swap_byte(FLASH_WriteEnable);
    W25Q128_CS(1);
}

void read_data(uint32_t address, uint8_t *rev_data, uint8_t size){
	
	W25Q128_CS(0);
	
	SPI_swap_byte(FLASH_ReadData);
	send_addr(address);
	
	uint8_t i = 0;	
	for(i = 0; i < size; i++){
		rev_data[i] = SPI_swap_byte(0xFF);			//直接根据指针写进去了所以不需要返回值
	}
	W25Q128_CS(1);
}


void write_page_data(uint32_t address, uint8_t *write_data, uint8_t size){	
	
	w25q128_writ_enable();
	wait_busy();
	
	W25Q128_CS(0);
	SPI_swap_byte(FLASH_PageProgram);
	send_addr(address);
	
	uint16_t i = 0;
	for(i = 0; i< size; i++){
		SPI_swap_byte(write_data[i]);
	}
	W25Q128_CS(1);
	wait_busy();
	
}

void erase_page(uint32_t address){

    w25q128_writ_enable();

    wait_busy();

    W25Q128_CS(0);

    SPI_swap_byte(FLASH_SectorErase);

    send_addr(address);

    W25Q128_CS(1);

    wait_busy();

}

最终配置:

把文件合并,接线合并,初始化后在Main函数更改写法让读写操作显示在屏幕上:

#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.h"
#include "SPI.h"
#include "w25q128.h"
#include "oled.h"
#include "IIC.h"
#include "string.h"

uint8_t data_write[4] = {0xAA, 0xBB, 0xCC, 0xDD};
uint8_t data_read[4] = {0};
int main(void)
{
    HAL_Init(); 
	stm32_clock_init(RCC_PLL_MUL9); 	
	SPI_INIT();
	uart1_init(115200);	
	OLED_INIT();
	Oled_clear();	
	
	Oled_show_string(1,1,"HELLO WKX",24);	
	delay_ms(1000);	
	Oled_clear();
	
	char show[8];
	uint16_t number = w25q128_config();
	sprintf(show,"device id :%X",number);
	Oled_show_string(1,1,"Loading...",24);	
	delay_ms(1000);	
	Oled_clear();
	
	Oled_show_string(1,1,show,16);
	delay_ms(1000);
	Oled_clear();	
	
	write_page_data(0x000000, data_write, 4);
	Oled_show_string(1,1,"Writed succes",16);
	delay_ms(500);
	Oled_clear();
	
	Oled_show_string(1,1,"Reading data",16);
	delay_ms(500);
	Oled_clear();
	
    read_data(0x000000, data_read, 4);
	memset(show,0,8);
	sprintf(show,"%X",data_read[0]);
	Oled_show_string(1,1,show,16);
	sprintf(show,"%X",data_read[1]);
	Oled_show_string(40,1,show,16);
	sprintf(show,"%X",data_read[2]);
	Oled_show_string(80,1,show,16);
    while(1)
    { 
		
    }
}


好了给看看效果:

<iframe allowfullscreen="true" data-mediaembed="bilibili" frameborder="0" id="N8KHxlMq-1723171439042" src="https://player.bilibili.com/player.html?aid=112929756152634"></iframe>

OLED&W25&Q128

祝你看完就会
 

标签:SCL,PIN,STM32,SPI,Oled,SDA,IIC,GPIO
From: https://blog.csdn.net/wwwkkkxxx12138/article/details/141029525

相关文章

  • freeRTOS入门学习-基于STM32F103C8T6最小系统板-创建任务_声光色彩
    首先重温一下任务的三大要素:        ·做何事(函数)    ·栈(每个任务都应该有自己独享的栈)    ·优先级(非必要的因素,但是有了优先级可以处理更多的任务)一、如何创建任务:    当一个任务被切换出去之后,要想再找到他,应该去到某个链表里边......
  • STM32 H7系列 全中文HAL&LL库使用手册 中英双语对照 GPT机翻 共4020页、约152万字
    STM32H7系列全中文HAL_LL库使用手册,中英文双语对照阅读。内容、格式对照官方原文,含标签导航及目录跳转。全文GPT机翻,除人工翻译外,相对更加贴合原文原意,双语版防止翻译错误方便对照。全文:4020页,约152万字,2022年12月版本,当前官网最新版。 *******下有更多展示图片********......
  • [STM32]如何正确的安装和配置keil?(详细)
    一、我们为什么需要keil?    对于嵌入式开发的硬件来讲STM32可以说有着不可撼动的地位,它可能是很多人入门嵌入式开发接触到的第一款芯片,其强大的生态和大量开放的源代码也深受开发者的喜爱。对于嵌入式开发的软件来讲,keil绝对是在一届软件中脱颖而出的,它是一款集成了......
  • STM32在Keil5中DeBug界面可以正常运行但是正常运行就失败
    项目场景:提示:这里简述项目相关背景:使用STM32CubeMX建立项目生成基础代码在Keil5MDK中编写STM32F03RCT6程序问题描述提示:这里描述项目中遇到的问题:在DeBug界面可以正常运行但是一旦进行重启自启动芯片就不会正常运行在Debug运行是会卡在一下但是后续运行可继续运行......
  • 我在高职教STM32——I2C通信之读写EEPROM(3)
            大家好,我是老耿,高职青椒一枚,一直从事单片机、嵌入式、物联网等课程的教学。对于高职的学生层次,同行应该都懂的,老师在课堂上教学几乎是没什么成就感的。正是如此,才有了借助CSDN平台寻求认同感和成就感的想法。在这里,我准备陆续把自己花了很多心思设计的教学课件......
  • 问题 IDEA创建Sping项目只能勾选17和21,却无法使用Java8
    想创建一个springboot项目,本地安装jdk版本为1.8,但是在使用SpringInitializr创建项目时,版本只能选择21或17在JDK为1.8的情况下,无论选择Java17版本或者21版本时,都会报错。Java17和Java8(JDK1.8)的区别版本号:Java17是JavaSE17的版本,而JDK1.8是JavaSE8的版本。发......
  • SPI通信协议
    目录一、SPI简介二、寻址方式三、通信过程四、SPI设备时钟五、极性和相位5.1CPOL=0,CPHA=05.2 CPOL=0,CPHA=15.3 CPOL=1,CPHA=0 5.4 CPOL=1,CPHA=1                                               ......
  • 【STM32】SPI通信和RTC实时时钟
    个人主页~SPI通信和RTC实时时钟SPI通信一、简介二、硬件电路三、基本原理四、SPI时序1、时序基本单元2、时序五、FLASH操作注意事项1、写入操作2、读取操作六、SPI外设1、简介2、结构七、传输方式1、主模式全双工连续传输2、非连续传输RTC实时时钟一、Unix时间戳......
  • Aspire8一文通
    Aspire8一文通0、简介微软Aspire是微软今年推出的一个全新的平台无关、语言无关的新框架,它的设计目的是简化云原生应用的开发、部署和管理过程。Aspire的读音是[əˈspaɪər],它的意思是渴望(成就);有志(成为),利用它你可以构建新应用程序或将云原生功能添加到现有的应用程序,或......
  • IIC模拟 && E2PROM
    IIC模拟&&E2PROM IIC_eeprom.h#ifndef__IIC_EEPROM_H__#define__IIC_EEPROM_H__/*****************************************************************************************型号Byte容量页数页内字节数WORD_ADDR位数WORD_ADDR字节......