STM32与ESP32的硬件SPI通信(个人学习记录)

一、硬件配置

        STM32F103ZET6作为主机,使用SPI2,ESP32S2作为从机,进行SPI双向通信;硬件接线如下:

                                                主机                        从机

CS                                           PB12 ——————  14

MOSI                                        PB6 ——————    2

MISO                                       PB14 ——————  13

CLK                                         PB15 ——————  12

HANDSHAKE                         PB13 ——————   15

GND                                        GND ——————   GND   

(地线一定相连在一起,不然传输的数据会乱码)

二、主机代码

#define SPI2READY	PBin(6)    //读取握手线是否准备好

void SPI2_Init(void)
{
 	GPIO_InitTypeDef GPIO_InitStructure;
    SPI_InitTypeDef  SPI_InitStructure;

	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOB |RCC_APB2Periph_AFIO, ENABLE );
	RCC_APB1PeriphClockCmd(	RCC_APB1Periph_SPI2,  ENABLE );//SPI2时钟使能 	
	
    //CS片选线
	GPIO_InitStructure.GPIO_Pin =GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  //PB12推挽输出 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB
	
    //HANDSHAKE 握手线
	GPIO_InitStructure.GPIO_Pin =GPIO_Pin_6;	     
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;     //PB6下拉输入 
	GPIO_Init(GPIOB, &GPIO_InitStructure);            
	
	GPIO_InitStructure.GPIO_Pin =GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //PB13/14/15复用推挽输出 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB

 	GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);  //PB13/14/15上拉

	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		//设置SPI工作模式:设置为主SPI
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;	//SPI发送接收8位帧结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;		    //串行同步时钟的空闲状态为高电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;	    //第一个跳变沿数据被采样
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		    //软件管理
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//预分频值为256
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//数据传输从MSB位开始
	SPI_InitStructure.SPI_CRCPolynomial = 7;        	//CRC值计算的多项式
	SPI_Init(SPI2, &SPI_InitStructure);                 //初始化外设SPIx寄存器
 
	SPI_Cmd(SPI2, ENABLE);                             //使能SPI外设
}  

//SPIx 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI2_ReadWriteByte(u8 TxData)
{		
	u8 retry=0;	
	while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) 
	{
        retry++;
        if(retry>200) return 0;    //发送上一个数据时间过长,报错
    }			  
	SPI_I2S_SendData(SPI2, TxData); //通过外设SPIx发送一个数据
	retry=0;
	while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET) 
	{
		retry++;
		if(retry>200) return 0;
	}	  						    
	return SPI_I2S_ReceiveData(SPI2); //返回通过SPIx最近接收的数据					    
}

u8 TXBuffer[128];
u8 RXBuffer[128];

 int main(void)
 {	 
	u16 i=0;
	delay_init();	    	 //延时函数初始化	  
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2
	uart_init(115200);	 	//串口初始化为115200
	LED_Init();		  		//初始化与LED连接的硬件接口	
	SPI2_Init();
	
	 for (i = 0; i < 10; i++)    //给发送数组赋初值
    {
        TXBuffer[i] = 0x47+i;
    }
	 
	while(1)
	{
		
		if(SPI2READY==1)        //如果从机准备好接收
		{
			GPIO_ResetBits(GPIOB,GPIO_Pin_12);	    //拉低CS
			for(i=0;i<10;i++)                       //发送数据并接收从机返回数据
			{
				RXBuffer[i]=SPI2_ReadWriteByte(TXBuffer[i]);
			}
			GPIO_SetBits(GPIOB,GPIO_Pin_12);        //拉高CS
			LED0 = ~LED0;                           //指示灯闪烁
		}
		printf("%s\r\n",RXBuffer);                  //串口打印从机接收到的数据
		delay_ms(1000);                                
	}
}




 三、从机代码

根据官方所给例程修改

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#include "esp_log.h"
#include "driver/spi_slave.h"
#include "driver/gpio.h"

#define GPIO_HANDSHAKE 2
#define GPIO_MOSI 12
#define GPIO_MISO 13
#define GPIO_SCLK 15
#define GPIO_CS 14

#ifdef CONFIG_IDF_TARGET_ESP32
#define RCV_HOST    HSPI_HOST
#else
#define RCV_HOST    SPI2_HOST
#endif

在事务进入队列并准备由master提取时调用。我们用这个来设置握手线
void my_post_setup_cb(spi_slave_transaction_t *trans) {
    gpio_set_level(GPIO_HANDSHAKE, 1);
}

//事务发送/接收后调用。我们用这个来降低握手线。
void my_post_trans_cb(spi_slave_transaction_t *trans) {
    gpio_set_level(GPIO_HANDSHAKE, 0);
}

//Main application
void app_main(void)
{
    int n=0;
    esp_err_t ret;

    //Configuration for the SPI bus
    spi_bus_config_t buscfg={
        .mosi_io_num=GPIO_MOSI,
        .miso_io_num=GPIO_MISO,
        .sclk_io_num=GPIO_SCLK,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
    };

    //Configuration for the SPI slave interface
    spi_slave_interface_config_t slvcfg={
        .mode=0,                    //CPOL 0,CPHA 0 (注意模式配置,需要与主机一致) 
        .spics_io_num=GPIO_CS,
        .queue_size=3,              
        .flags=0,
        .post_setup_cb=my_post_setup_cb,
        .post_trans_cb=my_post_trans_cb
    };

    //Configuration for the handshake line
    gpio_config_t io_conf={
        .intr_type=GPIO_INTR_DISABLE,
        .mode=GPIO_MODE_OUTPUT,
        .pin_bit_mask=(1<<GPIO_HANDSHAKE)
    };

    //Configure handshake line as output
    gpio_config(&io_conf);
    //Enable pull-ups on SPI lines so we don't detect rogue pulses when no master is connected.
    gpio_set_pull_mode(GPIO_MOSI, GPIO_PULLUP_ONLY);
    gpio_set_pull_mode(GPIO_SCLK, GPIO_PULLUP_ONLY);
    gpio_set_pull_mode(GPIO_CS, GPIO_PULLUP_ONLY);

    //Initialize SPI slave interface
    ret=spi_slave_initialize(RCV_HOST, &buscfg, &slvcfg, SPI_DMA_CH_AUTO);
    assert(ret==ESP_OK);

    WORD_ALIGNED_ATTR char sendbuf[129]="";
    WORD_ALIGNED_ATTR char recvbuf[129]="";
    memset(recvbuf, 0, 33);     //将前33个字节填充为0     
    spi_slave_transaction_t t;  
    memset(&t, 0, sizeof(t));

    while(1) {
        //Clear receive buffer, set send buffer to something sane
        memset(recvbuf, 0x00, 129);     //将129个字节全部填充为0xA5

        for(int j=0;j<26;j++)    //给从机发送缓存区赋值
        {
            sendbuf[j]=0x41+j;
        }

        //Set up a transaction of 128 bytes to send/receive
        t.length=20*8;             //设置一次接收/发送的最大值,必须比主机一次发送的数据大小要大,否则会乱序
        t.tx_buffer=sendbuf;
        t.rx_buffer=recvbuf;
        
        ret=spi_slave_transmit(RCV_HOST, &t, portMAX_DELAY);
       // spi_slave_queue_trans(RCV_HOST, &t, portMAX_DELAY);    //可以用这个函数,但是没有验证,容易出错

        printf("Received: %s\n", recvbuf);
        n++;
    }
}

 

运行顺序应该是从机接收先复位,再等待主机复位;如果主机先复位,则接收时序可能会不正确。

 四、运行结果

主机接收到从机返回的数据

 

从机接收到主机发送的数据

  • 13
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
### 回答1: 很抱歉,我是AI语言模型,无法提供头歌python实训作业答案。同时,我们也不鼓励学生抄袭作业,建议您自己动手完成作业,这样才能真正掌握知识。如果您有关于列表的问题,可以向我提问,我会尽力帮助您解决。 ### 回答2: 列表是Python中常见的数据类型之一,是一种有序的集合,其中的元素可以是任何数据类型,包括数字、字符串、列表等。头歌Python实训作业中的玩转列表,主要是让我们练习对列表进行增、删、改、查等操作,下面就针对这些操作进行详细讲解。 【添加元素】 向列表中添加元素可以使用append()函数,例如: lst = [1, 2, 3] lst.append(4) print(lst) # [1, 2, 3, 4] 还可以使用extend()函数向列表中添加另一个列表的元素,例如: lst1 = [1, 2, 3] lst2 = [4, 5, 6] lst1.extend(lst2) print(lst1) # [1, 2, 3, 4, 5, 6] 【删除元素】 从列表中删除元素可以使用del、pop()、remove()等方法。del方法可以根据索引删除元素,例如: lst = ['a', 'b', 'c'] del lst[1] print(lst) # ['a', 'c'] pop()方法删除指定索引处的元素,并返回该元素的值,例如: lst = ['a', 'b', 'c'] item = lst.pop(1) print(lst, item) # ['a', 'c'] b remove()方法根据元素值删除元素,例如: lst = ['a', 'b', 'c'] lst.remove('b') print(lst) # ['a', 'c'] 【修改元素】 修改列表中的元素可以直接通过索引进行修改,例如: lst = ['a', 'b', 'c'] lst[1] = 'd' print(lst) # ['a', 'd', 'c'] 也可以使用切片进行修改,例如: lst = ['a', 'b', 'c'] lst[1:3] = ['d', 'e'] print(lst) # ['a', 'd', 'e'] 【访问元素】 访问元素可以通过索引或切片进行,例如: lst = ['a', 'b', 'c'] print(lst[1]) # b print(lst[0:2]) # ['a', 'b'] 【常用函数】 使用len()函数可以得到列表的长度,例如: lst = ['a', 'b', 'c'] print(len(lst)) # 3 使用max()和min()函数可以得到列表中的最大值和最小值,例如: lst = [1, 2, 3] print(max(lst), min(lst)) # 3 1 使用sum()函数可以得到列表中所有元素的和,例如: lst = [1, 2, 3] print(sum(lst)) # 6 使用sorted()函数可以得到按照升序排列的列表,例如: lst = [3, 1, 2] print(sorted(lst)) # [1, 2, 3] 以上就是头歌Python实训作业中玩转列表的主要内容,通过练习这些操作可以更好地掌握Python中的列表操作,提高程序开发的效率和准确性。 ### 回答3: Python的列表占据了Python中重要的位置,列表的灵活性和易用性使得它成为Python中经常被使用的数据结构之一。在实际的开发中,我们经常需要对列表进行操作。包括增加、删除、修改、查找等等,玩转列表的知识可以让我们更加高效地使用Python。 在头歌Python实训中,玩转列表的知识点主要包括以下方面: 1. 列表的基本操作 列表是一种可变的有序序列,支持索引、切片、增加、删除、修改等操作。学习列表的基本操作是玩转列表的重要一步。 2. 列表推导式 列表推导式是Python中非常强大并且常用的一种快捷语法,它可以简化对列表的创建和操作。了解列表推导式可以让开发更加高效。 3. 列表排序 列表排序是对列表中元素进行排序的操作,Python中提供了多种排序算法,例如冒泡排序、选择排序、插入排序等等。在实际开发中,我们经常需要对数据进行排序,掌握列表排序技巧可以有效提高代码的执行效率。 4. 列表的常用函数 Python为列表提供了许多函数,例如append()、extend()、insert()、remove()、pop()等等。掌握这些函数可以让我们更加方便快捷地对列表进行操作。 5. 列表的高级操作 除了基本操作之外,Python还提供了一些高级操作,例如列表的切片、列表的嵌套、列表的比较等等。掌握这些技巧可以让我们更加深入地了解列表的特性。 总之,玩转列表是Python开发中必不可少的一部分,了解列表的基本操作、列表推导式、列表排序、常用函数和高级操作可以让我们更加熟练地使用Python,提高代码的开发效率。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值