РЕДАКТИРОВАТЬ / РЕШЕНО: В итоге проблема заключалась в моих заявлениях Serial.print в коде Arduino, которые замедлялись до фактического выполнения кода связи. По сути, Arduino получал байты после того, как плата STM закончила отправлять их, поэтому регистрировала только 'd' / 100.
В настоящее время я записался на онлайн-курс и столкнулся с проблемой одного из упражнения. Упражнение кажется довольно простым, но я не могу заставить все работать. Упражнение состоит в том, чтобы отправить строку с главного MCU STM32F407 на плату Arduino Uno, которая затем должна напечатать сообщение и его длину.
Кажется, плата Arduino зарегистрировала SS, но ничего не печатала, не печатала неполное / неправильное сообщение (например, «hello vorl») или печатала gibberi sh. После некоторой обнаруженной отладки я неправильно установил регистры CPOL и CPHA, однако теперь он не печатает ничего похожего на «Hello world». После дополнительной отладки кажется, что Arduino застревает в своей функции SPI_SlaveReceive. Код SPI для STM предназначен для первой отправки длины сообщения, а затем для отправки фактического сообщения. Из того, что я могу сказать, Arduino, похоже, не регистрирует правильную длину для первой передачи, что приводит к путанице в l oop для вызовов SPI_SlaveReceive, которые должны захватывать фактическое сообщение.
Я новичок во встроенном и пытался изучить его в основном самостоятельно. Я чувствую, что, возможно, есть что-то, что мне не хватает, но я не знаю. Мне также кажется, что код плохо спроектирован, но я стараюсь придерживаться курса (код STM «написан» в соответствии с инструкциями, и код arduino был только что предоставлен). Я опубликую код ниже, но даже оценил бы идеи о том, что может пойти не так / как я могу отлаживать. Ниже всего я также опубликую некоторые сообщения об ошибках.
Это также моя первая публикация stackoverflow, поэтому, пожалуйста, дайте мне знать, если есть что-то, что мне нужно добавить (или удалить). Спасибо!
Обновление: я думаю, что это может быть что-то с тактовой частотой. Я попытался снова выполнить код в отладчике и обнаружил, что смог успешно отправить данные, если я установил точку останова в коде, который изменяет регистр данных SPI. Похоже, что SPI может отправлять данные быстрее, чем Arduino может обработать. Если я перешагиваю код, который отправляет длину сообщения «Hello world», то я вижу, что Arduino успешно получил 11. Если я продолжу перебирать pSPIx-> DR = * pTxBuffer; Инструкция второго вызова SPI_SendData, я могу успешно отправить сообщение. Однако, если после отправки длины я пытаюсь выполнить весь второй вызов SPI_SendData за один шаг. Arduino не регистрирует всю строку. Я попытался изменить предскаляр SPI, чтобы получить более высокие значения, и это, похоже, помогло. то есть arduino был способен выводить больше допустимых символов (в результате "hellod"). Это меня смущает, так как APB1, управляющий SPI2, имеет максимальную частоту 42 МГц. Предскалярное значение, которое я нашел, дало мне лучшие результаты, было 7, что означает, что PCLK делится на 256, что приводит, если я правильно об этом думаю, к частоте максимум 164 МГц. Основываясь на спецификациях Ardunio, у него не должно возникнуть проблем при обработке этого сообщения. Я думаю, у меня может быть проблема в том, как часто отправляются биты? Может быть, моя функция SPI_SendData работает неправильно?
Другие детали:
- 4 провода, соединяющих платы: GND, CLK (STM: PB13 <-> UNO: 13), MOSI (STM: PB15 <-> A: 11) и NSS (STM: PB12 <-> A: 10)
- У меня нет анализатора logi c, поэтому я не смог чтобы увидеть, что на самом деле отправляется (что отстой)
- Я понимаю, что может быть трудно получить полный контекст без публикации всего кода, но я подумал, что это будет слишком много, поэтому попытался просто опубликовать соответствующий части.
Регистрация:
16:01:42.461 -> Slave Initialized
16:01:42.494 -> Slave waiting for ss to go low
16:01:45.500 -> slave selected
16:01:45.500 -> Waiting to receive length
16:01:45.534 -> Length: 68
16:01:47.520 -> Received:
16:01:47.554 -> Received: d
16:01:48.968 -> Received:
16:01:48.968 -> Received: d
16:01:49.858 -> Received:
16:01:49.858 -> Received: d
16:01:50.499 -> Received:
16:01:50.499 -> Received: d
16:01:51.137 -> Received:
16:01:51.137 -> Received: d
16:01:51.707 -> Received:
16:01:51.741 -> Received: d
16:01:52.244 -> Received:
16:01:52.277 -> Received: d
16:01:52.782 -> Received:
16:01:52.782 -> Received: d
16:01:53.360 -> Received:
16:01:53.393 -> Received: d
16:01:53.866 -> Received:
16:01:53.899 -> Received: d
16:01:54.441 -> Received:
16:01:54.441 -> Received: d
16:01:54.946 -> Received:
16:01:54.980 -> Received: d
16:01:55.484 -> Received:
16:01:55.518 -> Received: d
16:01:55.994 -> Received:
16:01:56.027 -> Received: d
16:01:56.599 -> Received:
16:01:56.599 -> Received: d
16:01:57.105 -> Received:
16:01:57.138 -> Received: `
16:01:57.609 -> Received:
16:01:57.644 -> Received: d
16:01:58.085 -> Received:
16:01:58.118 -> Received: d
16:01:58.622 -> Received:
16:01:58.622 -> Received: d
16:01:59.128 -> Received:
16:01:59.161 -> Received: d
16:01:59.676 -> Received:
16:01:59.676 -> Received: d
16:02:00.146 -> Received:
16:02:00.180 -> Received: d
16:02:00.654 -> Received:
16:02:00.654 -> Received: d
16:02:01.127 -> Received:
16:02:01.127 -> Received: d
16:02:01.634 -> Received:
16:02:01.668 -> Received: d
16:02:02.105 -> Received:
16:02:02.139 -> Received: d
16:02:02.582 -> Received:
16:02:02.582 -> Received: d
16:02:03.052 -> Received:
16:02:03.052 -> Received: d
16:02:03.729 -> Received:
16:02:03.729 -> Received: d
16:02:04.236 -> Received:
16:02:04.236 -> Received: d
16:02:04.774 -> Received:
16:02:04.774 -> Received: d
16:02:05.336 -> Received:
16:02:05.336 -> Received: d
16:02:05.947 -> Received:
16:02:05.980 -> Received: d
16:02:06.485 -> Received:
16:02:06.485 -> Received: d
16:02:06.518 -> done receiving word
16:02:06.518 -> Rcvd:
16:02:06.553 -> ddddddddddddddd`dddddddddddddddddd
16:02:06.620 -> Length:68
16:02:06.620 -> Slave deselected
16:02:06.654 -> Slave waiting for ss to go low
16:02:07.025 -> slave selected
16:02:07.058 -> Waiting to receive length
16:02:07.092 -> Length: 100
Код Arduino:
/* SPI Slave Demo
*
* SPI pin numbers:
* SCK 13 // Serial Clock.
* MISO 12 // Master In Slave Out.
* MOSI 11 // Master Out Slave In.
* SS 10 // Slave Select . Arduino SPI pins respond only if SS pulled low by the master
*
*/
#include <SPI.h>
#include<stdint.h>
#define SPI_SCK 13
#define SPI_MISO 12
#define SPI_MOSI 11
#define SPI_SS 10
char dataBuff[500];
//Initialize SPI slave.
void SPI_SlaveInit(void)
{
#if 0
// Initialize SPI pins.
pinMode(SPI_SCK, INPUT);
pinMode(SPI_MOSI, INPUT);
pinMode(SPI_MISO, OUTPUT);
pinMode(SPI_SS, INPUT);
// Enable SPI as slave.
SPCR = (1 << SPE);
#endif
// Initialize SPI pins.
pinMode(SCK, INPUT);
pinMode(MOSI, INPUT);
pinMode(MISO, OUTPUT);
pinMode(SS, INPUT);
//make SPI as slave
// Enable SPI as slave.
SPCR = (1 << SPE);
}
//This function returns SPDR Contents
uint8_t SPI_SlaveReceive(void)
{
/* Wait for reception complete */
while(!(SPSR & (1<<SPIF)));
/* Return Data Register */
return SPDR;
}
//sends one byte of data
void SPI_SlaveTransmit(char data)
{
/* Start transmission */
SPDR = data;
/* Wait for transmission complete */
while(!(SPSR & (1<<SPIF)));
}
// The setup() function runs right after reset.
void setup()
{
// Initialize serial communication
Serial.begin(9600);
// Initialize SPI Slave.
SPI_SlaveInit();
Serial.println("Slave Initialized");
}
uint16_t dataLen = 0;
uint32_t i = 0;
// The loop function runs continuously after setup().
void loop()
{
Serial.println("Slave waiting for ss to go low");
while(digitalRead(SS) );
Serial.println("slave selected");
// Serial.println("start");
//1. read the length
// dataLen = (uint16_t)( SPI_SlaveReceive() | (SPI_SlaveReceive() << 8) );
//Serial.println(String(dataLen,HEX));
i = 0;
Serial. println("Waiting to receive length");
dataLen = SPI_SlaveReceive();
for(i = 0 ; i < dataLen ; i++ )
{
dataBuff[i] = SPI_SlaveReceive();
Serial.print("Received: ");
Serial.println(dataBuff[i]);
}
Serial.println("done receiving word");
// Serial.println(String(i,HEX));
dataBuff[i] = '\0';
Serial.println("Rcvd:");
Serial.println(dataBuff);
Serial.print("Length:");
Serial.println(dataLen);
while(!digitalRead(SS));
Serial.println("Slave deselected");
}
Основной код STM:
#include <string.h>
#include "stm32f407xx.h"
/*
* PB15 --> SPI2_MOSI
* PB14 --> SPI2_MISO
* PB13 --> SPI2_SCLK
* PB12 --> SPI2_NSS
* ALT Function mode: 5
*/
void SPI_GPIO_Setup(void);
void GPIO_ButtonInit(void);
void SPI_Setup(void);
void delay(int time){
for(uint32_t i=0; i<time; i++);
}
int main(void) {
char userData[] = "Hello world";
//initizalize button
GPIO_ButtonInit();
//This function is used to initialize the gpio pins to behave as spi pins
SPI_GPIO_Setup();
//This function is used to initialize the SPI peripheral
SPI_Setup();
//this makes NSS signal internally high and avoids MODF error
//NOTE: This should be taken care of in my SPI_Init function
// SPI_SSIConfig(SPI2, ENABLE);
//SSOE to 1 does NSS output enable
//I don't want to enable this in this way, but it is more simple due to the code design
//i.e. (using shifts instead of masks for registers)
SPI_SSOEConfig(SPI2, ENABLE);
while(1){
//wait for button to be pressed
while(! GPIO_ReadFromInputPin(GPIOA, GPIO_PIN_0));
delay(250000); //used for button debouncing
//enable the SPI2 peripheral --- according to generated code, this seems to be done in the transmit function
// I a SET_BIT macro in the transmit function if SPI isn't enabled, make sure to test this
SPI_PeripheralControl(SPI2, ENABLE);
//first send the length of data (Arduino expects this as 1 byte)
uint8_t dataLen = strlen(userData);
SPI_SendData(SPI2, &dataLen, 1);
//send data
SPI_SendData(SPI2, (uint8_t*)userData, dataLen);
//confirm the SPI is not busy
while(SPI_GetFlagStatus(SPI2, SPI_BSY_FLAG));
//disable the SPI2 peripheral --- according to Generated code, this seems to be done in abort functions
SPI_PeripheralControl(SPI2, DISABLE);
}
return 0;
}
void SPI_GPIO_Setup(void) {
GPIO_Handle_t SPIpins;
SPIpins.pGPIOx = GPIOB;
SPIpins.GPIO_PinConfig.GPIO_PinMode = GPIO_MODE_ALTFN;
SPIpins.GPIO_PinConfig.GPIO_PinAltFunMode = 5;
SPIpins.GPIO_PinConfig.GPIO_PinOPType = GPIO_OPTYPE_PP;
SPIpins.GPIO_PinConfig.GPIO_PinPuPdControl = GPIO_PU;
SPIpins.GPIO_PinConfig.GPIO_PinSpeed = GPIO_SPEED_FAST;
//MOSI
SPIpins.GPIO_PinConfig.GPIO_PinNumber = GPIO_PIN_15;
GPIO_Init(&SPIpins);
//MISO
// SPIpins.GPIO_PinConfig.GPIO_PinNumber = GPIO_PIN_14;
// GPIO_Init(&SPIpins);
//SCLK
SPIpins.GPIO_PinConfig.GPIO_PinNumber = GPIO_PIN_13;
GPIO_Init(&SPIpins);
//NSS
SPIpins.GPIO_PinConfig.GPIO_PinNumber = GPIO_PIN_12;
GPIO_Init(&SPIpins);
}
void GPIO_ButtonInit(void){
GPIO_Handle_t GpioBtn;
// configure button
//Set up PA0 to be an input for the button press
GpioBtn.pGPIOx = GPIOA;
GpioBtn.GPIO_PinConfig.GPIO_PinNumber = GPIO_PIN_0;
GpioBtn.GPIO_PinConfig.GPIO_PinMode = GPIO_MODE_IN;
GpioBtn.GPIO_PinConfig.GPIO_PinSpeed = GPIO_SPEED_FAST;
GpioBtn.GPIO_PinConfig.GPIO_PinPuPdControl = GPIO_NO_PUPD;
GPIO_Init(&GpioBtn);
}
void SPI_Setup(void){
SPI_Handle_t hSPI;
hSPI.SPIx = SPI2;
hSPI.SPIConfig.SPI_BusConfig = SPI_BUS_CONFIG_FD;
hSPI.SPIConfig.SPI_DeviceMode = SPI_DEVICE_MODE_MASTER;
hSPI.SPIConfig.SPI_SclkSpeed = SPI_SCLK_SPEED_DIV8; //generate SCLK of 2 MHz
hSPI.SPIConfig.SPI_DFF = SPI_DFF_8BITS;
hSPI.SPIConfig.SPI_CPOL = SPI_CPOL_LOW;
hSPI.SPIConfig.SPI_CPHA = SPI_CPHA_LOW;
hSPI.SPIConfig.SPI_SSM = SPI_SSM_DI; //Hardware slave management disabled for NSS pin
SPI_Init(&hSPI);
}
Код драйвера SPI:
/*
* stm32f407xx_spi_driver.c
*
* Created on: Oct 17, 2019
* Author: pigmo
*/
#include "stm32f407xx_spi_driver.h"
/**************************************************************************
* APIs supported by this driver
* For more information about these APIs check the function description
**************************************************************************/
/*
* Peripheral clock setup
*/
/******************************************************
* @fn - SPI_PeriClockControl
*
* @brief - Enables or disables peripheral clock for the given SPI port
*
* @param[in] - base address of SPI peripheral
* @param[in] - ENABLE or DISABLE macros
*
* @return - none
*
* @Note - SPI4 is reserved and won't be affected by this function
*/
void SPI_PeriClockControl(SPI_RegDef_t* pSPIx, uint8_t enOrDi){
if(enOrDi == ENABLE){
if(pSPIx == SPI1){
SPI1_PCLK_EN();
} else if (pSPIx == SPI2){
SPI2_PCLK_EN();
} else if (pSPIx == SPI3){
SPI3_PCLK_EN();
}
} else {
if(pSPIx == SPI1){
SPI1_PCLK_DI();
} else if (pSPIx == SPI2){
SPI2_PCLK_DI();
} else if (pSPIx == SPI3){
SPI3_PCLK_DI();
}
}
}
/*
* Init and De-init
*/
/******************************************************
* @fn - SPI_Init
*
* @brief - Initializes a given GPIO peripheral with given configurations
*
* @param[in] - Struct holding base address and desired configurations of SPI peripheral
*
* @return - none
*
* @Note - I don't like the way this is done, but I'm doing it to be consistent with the course
* Instead, I think the SPIConfig should hold masks, not enum type values
*/
void SPI_Init(SPI_Handle_t *pSPIHandle){
//enable the spi clock
SPI_PeriClockControl(pSPIHandle->SPIx, ENABLE);
//disable the SPI (concept taken from STM generated code)
SPI_PeripheralControl(pSPIHandle->SPIx, DISABLE);
//first configure the SPI CR1 register
uint32_t tempReg = 0;
//1. configure the device mode
if(pSPIHandle->SPIConfig.SPI_DeviceMode == SPI_DEVICE_MODE_MASTER){
// if the device is master mode, then the SSI must also be 1 to avoid an error (unless Multi master?)
// this is only true if SSM is enabled, can we keep this functionality even is SSM is disabled?
// based on stm generated code, the SSI stays enabled for SSM enabled and disabled
tempReg |= pSPIHandle->SPIConfig.SPI_DeviceMode << SPI_CR1_MSTR;
tempReg |= (1 << SPI_CR1_SSI); // this should probably be done in another way
} else {
tempReg |= pSPIHandle->SPIConfig.SPI_DeviceMode << SPI_CR1_MSTR;
}
//2. configure the bus config
if(pSPIHandle->SPIConfig.SPI_BusConfig == SPI_BUS_CONFIG_FD){
//bdi should be cleared
tempReg &= ~(1 << SPI_CR1_BIDIMODE);
} else if(pSPIHandle->SPIConfig.SPI_BusConfig == SPI_BUS_CONFIG_HD){
//bdi should be set
tempReg |= ~(1 << SPI_CR1_BIDIMODE);
} else {
//bdi should be cleared
tempReg &= ~(1 << SPI_CR1_BIDIMODE);
//RXONLY should be set
tempReg |= (1 << SPI_CR1_RXONLY);
}
//3. configure the clock speed
tempReg |= (pSPIHandle->SPIConfig.SPI_SclkSpeed << SPI_CR1_BR);
//4. configure the Data frame format
tempReg |= (pSPIHandle->SPIConfig.SPI_DFF << SPI_CR1_DFF);
//5. configure the clock polarity
tempReg |= (pSPIHandle->SPIConfig.SPI_CPOL << SPI_CR1_CPOL);
//6. configure the clock phase
tempReg |= (pSPIHandle->SPIConfig.SPI_CPHA << SPI_CR1_CPHA);
//7. determine hardware or software slave management
tempReg |= (pSPIHandle->SPIConfig.SPI_SSM << SPI_CR1_SSM);
pSPIHandle->SPIx->CR1 = tempReg;
}
/******************************************************
* @fn - SPI_DeInit
*
* @brief - Deinitializes a given SPI periphal and resets register
*
* @param[in] - base address of SPI peripheral
*
* @return - none
*
* @Note - none
*/
void SPI_DeInit(SPI_RegDef_t* pSPIx){
if(pSPIx == SPI1){
SPI1_REG_RESET();
} else if (pSPIx == SPI2){
SPI2_REG_RESET();
} else if (pSPIx == SPI3){
SPI3_REG_RESET();
}
}
/*
* Data send and receive
*/
uint8_t SPI_GetFlagStatus(SPI_RegDef_t* pSPIx, uint32_t flag){
if(pSPIx->SR & flag){
return SET;
} else {
return RESET;
}
}
/******************************************************
* @fn - SPI_SendData
*
* @brief - sends data of length len to transmit buffer
*
* @param[in] - base address of SPI peripheral
* @param[in] - address of Tx buffer
* @param[in] - length of data to send
*
* @return - none
*
* @Note - This is a blocking call
*/
//actual code seems much more complex, check that out
void SPI_SendData(SPI_RegDef_t* pSPIx, uint8_t *pTxBuffer, uint32_t len){
//if SPi is not enabled, then enable it
if((pSPIx->CR1 & (1 << SPI_CR1_SPE)) != (1 << SPI_CR1_SPE)){
SET_BIT(pSPIx->CR1, (1 << SPI_CR1_SPE)); // this is repetitive, should just have a mask,
}
while(len > 0) {
//1. Wait until Tx is empty
while(SPI_GetFlagStatus(pSPIx, SPI_TXE_FLAG) == RESET);
//2. Check DFF in CR1
if (pSPIx->CR1 & (1 << SPI_CR1_DFF) ) {
//16 bit dff
//3. load data into DR
pSPIx->DR = *((uint16_t*)pTxBuffer);
len--;
len--;
(uint16_t*)pTxBuffer++;
} else {
//8 bit dff
//3. load data into DR
pSPIx->DR = *pTxBuffer;
len--;
pTxBuffer++;
}
}
}
/******************************************************
* @fn - SPI_ReceiveData
*
* @brief - Receives data of length len to Receive buffer
*
* @param[in] - base address of SPI peripheral
* @param[in] - address of Rx buffer
* @param[in] - length of data to Receive
*
* @return - none
*
* @Note - none
*/
void SPI_ReceiveData(SPI_RegDef_t* pSPIx, uint8_t *pRxBuffer, uint32_t len){
}
/*
* IRQ configuration and handling
*/
/******************************************************
* @fn - SPI_IRQITConfig
*
* @brief - enables or disables a SPI peripherals IRQ functionality
*
* @param[in] - IRQ number of SPI peripheral
* @param[in] - ENABLE or DISABLE macros
*
* @return - none
*
* @Note - none
*/
void SPI_IRQInterruptConfig(uint8_t IRQNumber, uint8_t enOrDi){
}
/******************************************************
* @fn - SPI_IRQPriorityConfig
*
* @brief - sets the priority for a given IRQ number
*
* @param[in] - IRQ priority of SPI peripheral
* @param[in] - IRQ number of SPI peripheral
*
* @return - none
*
* @Note - modifies the NVIC peripheral
*/
void SPI_IRQPriorityConfig(uint8_t IRQPriority, uint8_t IRQNumber){
}
/******************************************************
* @fn - SPI_IRQHandling
*
* @brief - deals with a interrupt triggered by a SPI pin
*
* @param[in] - Struct holding base address and desired configurations of SPI peripheral
*
* @return - none
*
* @Note - none
*/
void SPI_IRQHandling(SPI_Handle_t *pHandle){
}
/******************************************************
* @fn - SPI_PeripheralControl
*
* @brief - Enables or Disables SPI
*
* @param[in] - Pointer to a SPI register
*
* @return - none
*
* @Note - I'm implementing this in the transmit function
*/
void SPI_PeripheralControl(SPI_RegDef_t *pSPIx, uint8_t enOrDi){
if(enOrDi == ENABLE){
pSPIx->CR1 |= (1 << SPI_CR1_SPE);
} else {
pSPIx->CR1 &= ~(1 << SPI_CR1_SPE);
}
}
/******************************************************
* @fn - SPI_SSIConfig
*
* @brief - Sets or Resets SSI bit in SPI CR1 reg
*
* @param[in] - Pointer to a SPI register
*
* @return - none
*
* @Note - This code is used in the course I'm following,
* but I think it is redundant and bad practice. It will not be used.
* Instead, I implemented a check in the SPI init function to enable SSI w/ master
*/
void SPI_SSIConfig(SPI_RegDef_t *pSPIx, uint8_t enOrDi){
if(enOrDi == ENABLE){
pSPIx->CR1 |= (1 << SPI_CR1_SSI);
} else {
pSPIx->CR1 &= ~(1 << SPI_CR1_SSI);
}
}
/******************************************************
* @fn - SPI_SSOEConfig
*
* @brief - Sets or Resets SSOE bit in SPI CR2 reg
*
* @param[in] - Pointer to a SPI register
*
* @return - none
*
* @Note - This code is used in the course I'm following,
* but I think it is redundant and bad practice. I will use it bec of consistency
* Otherwise, in the STM generated code, this bit is determine by a NSS value in the init struct
* See: WRITE_REG(hspi->Instance->CR2, (((hspi->Init.NSS >> 16U) & SPI_CR2_SSOE) | hspi->Init.TIMode));
*/
void SPI_SSOEConfig(SPI_RegDef_t *pSPIx, uint8_t enOrDi){
if(enOrDi == ENABLE){
pSPIx->CR2 |= (1 << SPI_CR2_SSOE);
} else {
pSPIx->CR2 &= ~(1 << SPI_CR2_SSOE);
}
}