背景: 在我的畢業設計中需要單片機將採集到的數據上傳到服務器,同時需要接收來自服務器的一些天氣信息,我的單片機的型號是 Stm32F407; ESP8266 刷入了 micropython 的固件,使用python進行開發; 協議是 SPI協議 ESP8266 主機 Stm32F4作爲 從機
單片機側使用的是 Stm32F407 的硬件 SPI + DMA 接收發送。 ESP8266 側使用的也是硬件SPI1 ; 通訊速度應該可以跑滿 單片機這一側的極限速度(42MHz),但是我在測試時候發現數據在40M 就很不穩定了(邏輯分析儀測試,可能是導線有點長) 最終就選擇了4MHz.。
整體的這個實現的機制就是在單片機裏面設置好 SPI的從機模式 + DMA收發 DMA使用循環模式(自動重複覆蓋內存) 然後讓ESP8266 側也是開闢相同大小的 空間,讀取發送同步進行;通過控制 單片機側的開啓時間進而實現 兩側的內存的同步(近似的同步 有點類似於鏡像) 底層的着四塊空間 兩兩相互可以實現單向映射。
程序實現的介紹
ESP8266
import network
#import simple
import time
import json
import machine
from machine import UART,SPI,Pin
machine.freq(160000000) # 提高主頻
#import esps
#esp.osdebug(None)
CS = Pin(16, Pin.OUT) #片選引腳
#spi = SPI(baudrate=10000000, polarity=1, phase=0, sck=Pin(14), mosi=Pin(13), miso=Pin(12)) #軟件模擬
spi = SPI(1, baudrate=4000000, polarity=1, phase=1) #硬件實現
send_buf = bytearray(60) #創建兩個數組大小是 60個byte
recv_buf = bytearray(60)
for i in range(60):
send_buf[i] =i # 賦值 實際應用中我們應該是放自己想要傳遞的數據
cnt =0
print('ok')
while True:
cnt +=1
CS.value(0) #
spi.write_readinto(send_buf,recv_buf)
CS.value(1)
print(recv_buf)
time.sleep_ms(200)
print(cnt)
這個是python的代碼實現 沒啥特殊的 很簡單 就是 發送的時候同步進行讀取 兩個同時進行。
比較難實現的是單片機側的程序 我們需要配置 SPI 然後 SPI 配置兩個DMA的數據流。
聲明: 我下面的代碼是在其他網友的代碼的基礎上修改出來的,在這裏向原作者致敬
uint8_t SPI_RX_BUFFER[RX_LEN]= {0,};
uint8_t SPI_TX_BUFFER[TX_LEN]= {0x1,0x2,0x3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22};
/**
* @breif The spi gpio init function.
* @param None
* @retval None
*/
static void _gpio_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource4, GPIO_AF_SPI1); // CS Òý½Å Èí¼þÄ£Äâ
GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
static void spi_dma_init(void)
{
DMA_InitTypeDef DMA_InitStructure;
/* ??DMA2?? */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
/* DMA RX config */
DMA_InitStructure.DMA_Channel = DMA_Channel_3; // DMA ͨµÀ
DMA_InitStructure.DMA_PeripheralBaseAddr = SPI1_DR_ADDR; // ÍâÉèµØÖ·
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)SPI_RX_BUFFER; // ½ÓÊÕ»º³åÇø£¨ÄÚ´æÖеÄÓÐÒ»¸öÊý×飩
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; //DMA ´«Êä·½Ïò
DMA_InitStructure.DMA_BufferSize = 100; // DMA ´«ÊäµÄÊýÁ¿ Õâ¸öºóÆÚ»¹¿ÉÒÔÔÙ¸Ä
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // ÍâÉèµØÖ·×ÔÔö È¡Ïû
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // ÄÚ´æµØÖ·×ÔÔö ʹÄÜ
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // ´«ÊäµÄ µ¥Î» £¨byte 8bit£©
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;// ´«ÊäµÄ µ¥Î» £¨byte 8bit£©
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // ÆÕͨģʽ ´«ÊäÍê³ÉÒ»´Î¾Í×Ô¶¯½áÊø
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; // ÓÅÏȼ¶ ÖеÈ
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; //²»Ê¹Óà FIFO
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; //
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //
DMA_Init(DMA2_Stream2, &DMA_InitStructure); //³õʼ»¯
//
/* DMA TX Config */ //
DMA_InitStructure.DMA_Channel = DMA_Channel_3; // DMA ͨµÀ
DMA_InitStructure.DMA_PeripheralBaseAddr = SPI1_DR_ADDR; // ÍâÉèµØÖ·
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)SPI_TX_BUFFER; // ½ÓÊÕ»º³åÇø£¨ÄÚ´æÖеÄÓÐÒ»¸öÊý×飩
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; // DMA ´«Êä·½Ïò
DMA_InitStructure.DMA_BufferSize = 100; // DMA ´«ÊäµÄÊýÁ¿ Õâ¸öºóÆÚ»¹¿ÉÒÔÔÙ¸Ä
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // ÍâÉèµØÖ·×ÔÔö È¡Ïû
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // ÄÚ´æµØÖ·×ÔÔö ʹÄÜ
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // ´«ÊäµÄ µ¥Î» £¨byte 8bit£©
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;// ´«ÊäµÄ µ¥Î» £¨byte 8bit£©
// DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // ÆÕͨģʽ ´«ÊäÍê³ÉÒ»´Î¾Í×Ô¶¯½áÊø
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // ÆÕͨģʽ ´«ÊäÍê³ÉÒ»´Î¾Í×Ô¶¯½áÊø
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; // ÓÅÏȼ¶ ÖеÈ
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; //
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; //
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //
DMA_Init(DMA2_Stream3, &DMA_InitStructure); //³õʼ»¯
}
/**
* @breif The spi init function.
* @param None
* @retval None
*/
void bsp_spi_init(void)
{
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
_gpio_init();
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1, ENABLE);
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1, DISABLE);
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // Ë«ÏßÈ«Ë«¹¤
SPI_InitStructure.SPI_Mode = SPI_Mode_Slave; // Ö÷»úģʽ
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //8bit λ¿í
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //SCK ¿ÕÏÐʱÖÓΪ¸ßµçƽ
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; // µÚ¶þ¸öʱÖÓ±ßÔµ ²¶»ñ
SPI_InitStructure.SPI_NSS = SPI_NSS_Hard; //NSSʹÓõÄÊÇÈí¼þÄ£Äâ
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128; // SPI ʱÖӵķÖƵϵÊý
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // MSB £¨ ¸ßλÔÚÇ°£©
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE);
spi_dma_init();
SPI_Cmd(SPI1, ENABLE);
SPI_I2S_ClearITPendingBit(SPI1, SPI_I2S_IT_RXNE);
SPI1_SetSpeed(SPI_BaudRatePrescaler_2); /// µ÷ÕûËÙ¶È ËÙ¶ÈÈ«¿ª ʵ²â´óÔ¼ÊÇ 42M Hz
}
/* ?????DMA?? */
/**
* @breif The spi dma trans function.
* @param rx_buf -- the point of rx buffer
* @param tx_buf -- the point of tx buffer
* @elngth length -- the size of data.
* @retval None
*/
void spi_trans(uint8_t *rx_buf,
uint8_t *tx_buf,
uint16_t length)
{
DMA_Cmd(DMA2_Stream2, DISABLE); // ¹Ø±Õ DMA
DMA_Cmd(DMA2_Stream3, DISABLE); //¹Ø±Õ DMA
//
DMA_SetCurrDataCounter(DMA2_Stream2, (uint16_t)length); // ÉèÖà DMAµÄ´«Êä²ÎÊý
DMA_SetCurrDataCounter(DMA2_Stream3, (uint16_t)length); // ÉèÖà DMAµÄ´«Êä²ÎÊý
//
DMA2_Stream2->M0AR = (uint32_t)rx_buf; // ÉèÖà ÄÚ´æµÄÆðʼµØÖ·
DMA2_Stream3->M0AR = (uint32_t)tx_buf; // ÉèÖà ÄÚ´æµÄÆðʼµØÖ·
//
SPI1->DR; //
// ¼Ù×°¶ÁÈ¡Çå³þ ±ê־λ
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); // µÈ´ýÏÈÇ°µÄ·¢ËÍÍê±Ï
//
DMA_Cmd(DMA2_Stream2, ENABLE); // ´ò¿ª DMA ¿ªÊ¼·¢ËÍ
DMA_Cmd(DMA2_Stream3, ENABLE); // ´ò¿ª DMA ¿ªÊ¼½ÓÊÕ
//
// while( DMA_GetFlagStatus(DMA2_Stream3, DMA_FLAG_TCIF3) == RESET); // ×èÈûʽ ÔÚÕâÀïµÈ´ýÊý¾Ý·¢ËÍÍê
// while( DMA_GetFlagStatus(DMA2_Stream2, DMA_FLAG_TCIF2) == RESET); // ÕâÀï·¢ËÍËٶȿÉÒԷdz£¿é ×èÈûÒ²¿ÉÒÔ
// //
// DMA_Cmd(DMA2_Stream2, DISABLE); // ·¢ËÍÍê ¹Ø±Õ DMA
// DMA_Cmd(DMA2_Stream3, DISABLE); //
// //
// DMA_ClearFlag(DMA2_Stream2, DMA_FLAG_TCIF2); // Çå³þ±ê־λ
// DMA_ClearFlag(DMA2_Stream3, DMA_FLAG_TCIF3); //
}
這個是主體代碼
這一部分是 整體的初始化函數 主要是 SPI協議相關的參數的設置 以及 一些設置。
這裏是一些GPIO的 複用設置 這裏要注意 就是 CS引腳 要選擇硬件 CS引腳
DMA 初始化前半部分 這是 接收DMA的 部分
這一部分是 發送DMA 的程序 相比較於原作者的代碼 我的代碼修改了 DMA的傳輸模式 換成了 循環模式
最後一部分的代碼是 開啓DMA傳輸的函數
再從機模式下 我們設置的時鐘都不起作用 時鐘是主機提供的, 所以這邊可以不管時鐘的設置
在這裏我的程序中使用的 IO 口是 PA4 PA5 PA6 PA7
這是 SPI.h 的頭文件
void bsp_spi_init(void);
void spi_trans_read(uint8_t *rx_buf,
uint8_t *tx_data,
uint16_t length);
void SPI1_SetSpeed(u8 SPI_BaudRatePrescaler);
#define RX_LEN (uint8_t)60
#define TX_LEN (uint8_t)60
#define SPI1_DR_ADDR (uint32_t)0x4001300C
extern uint8_t SPI_RX_BUFFER[RX_LEN];
extern uint8_t SPI_TX_BUFFER[TX_LEN];
還有一點小的心得就是 我們要注意初始化的時機,就是爲了防止出現錯位的情況 我們需要在 CS 引腳 是高電平的時候 開啓傳輸
ESP8266 那一側不是DMA 所以這個的波形不是很緊湊 不過速度也還可以 比串口肯定是快多了
10M的速度也是非常穩定的 不過 用不到那麼快的速度。 而且我是用導線連接的 干擾很大
20 M 的速度時候 CS 引腳就會收到很大的干擾
剩下的就是我們去 自己定義想要傳輸的數據了 準備好的數據放在內存中就可以了 等待 主機發起一次傳輸 雙方就把數據進行了一次的交換。 再次吐槽 ESP8266 的一個半串口太坑了
代碼的下載鏈接在這裏