#c #embedded #stm32 #spi
#c #встроенный #stm32 #spi
Вопрос:
Уважаемые пользователи stack overflow, я создал устройство с главным устройством и сетью из 10 подчиненных устройств. Все они обмениваются данными через 4-проводной SPI. Прямо сейчас я пишу программу для обеих плат, и они, похоже, не работают, я не получаю ожидаемых ответов.
У меня есть главная плата и 10 идентичных подчиненных плат. Протокол прост — как и в SPI, любая транзакция инициируется ведущим устройством и отправляется команда. Затем выбранное ведомое устройство получает вышеупомянутую команду, устанавливает высокий PIN-код флага занятости и проверяет, действителен ли он. После синтаксического анализа команды освобождается занятая ячейка, и, если команда действительна, ведущему устройству отправляется тот же байт, что и полученный, в противном случае отправляется маркер ошибки. После этого выполняются все необходимые обмены данными. Я попытался настроить IO как обычный portf и их альтернативные функции, также я попытался сбросить периферию SPI после каждой транзакции, и, похоже, ничего не работает.
Вот что я получаю: https://imgur.com/a/MICEx2f Каналы расположены сверху, соответственно: MOSI, MISO, CLK и флаг занятости. Я не получаю ответа от ведомого устройства, несмотря ни на что. Команда интерпретируется правильно (отладочные данные из UART), однако обратно ничего не отправляется.
Это часть кода SPI для ВЕДОМОГО устройства:
uint8_t spi_sendrecv(uint8_t byte)
{
// poczekaj az bufor nadawczy bedzie wolny
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPI1, byte);
// poczekaj na dane w buforze odbiorczym
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
return SPI_I2S_ReceiveData(SPI1);
}
uint8_t SPI_get_cmd_ack(void)
{
uint8_t cmd;
uint8_t valid_flag;
//In cas if the BF pin was left high
BF_OUT_low();
//Let's wait for some data
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
cmd = SPI_I2S_ReceiveData(SPI1);
//cmd = SPI_get_command();
//Check the cmd
BF_OUT_high();
valid_flag = SPI_check_for_valid_cmd(cmd);
//SPI_reset_flush();
BF_OUT_low();
if(valid_flag == CMD_RET_STATUS_VALID)
{
spi_sendrecv(cmd);
return cmd;
}
else
{
spi_sendrecv(CMD_ERROR);
return CMD_ERROR;
}
}
И это ОСНОВНАЯ часть:
//Sends a command to a slave device
//Param1: slave device no, from 0 to 9
//Param2: command to send
//Retval: command send success or failure:
//DATA_TRANSFER_OK or DATA_TRANSFER_ERR
uint8_t SPI_send_command(uint8_t slave_no, uint8_t cmd)
{
uint8_t cnt = 0;
uint8_t rx_cmd;
//SPI_reset();
//Select the correct slave
SPI_select_slave(0);
delay_ms(0);
SPI_select_slave(slave_no);
delay_ms(0);
//Transmit the cmd
SPI_sendrecv(cmd);
//SPI_reset();
//Wait for the busy flag indication
while(SPI_get_busy_flag(slave_no) == Bit_RESET)
{
if(cnt < SPI_RETRY_COUNT)
{
cnt;
delay_ms(1);
}
else
{
SPI_select_slave(0);
return DATA_TRANSFER_ERR;
}
}
//Same for the busy flag on:
while (SPI_get_busy_flag(slave_no) == Bit_SET)
{
if(cnt < SPI_RETRY_COUNT)
{
cnt;
delay_ms(1);
}
else
{
SPI_select_slave(0);
return DATA_TRANSFER_ERR;
}
}
rx_cmd = SPI_sendrecv(0);
//SPI_reset();
if(rx_cmd == cmd) return DATA_TRANSFER_OK;
else return DATA_TRANSFER_ERR;
}
And here are the initialization parts of the code, slave and master respectively:
void SPI_init(void)
{
GPIO_InitTypeDef SPI_GPIO;
SPI_InitTypeDef SPI;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB | RCC_AHBPeriph_GPIOC, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
//GPIOA5 SCK
//GPIOA6 MISO
//GPIOA7 MOSI
SPI_GPIO.GPIO_Mode = GPIO_Mode_AF;
SPI_GPIO.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
SPI_GPIO.GPIO_PuPd = GPIO_PuPd_DOWN;
SPI_GPIO.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, amp;SPI_GPIO);
SPI_GPIO.GPIO_Pin = GPIO_Pin_15;
SPI_GPIO.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, amp;SPI_GPIO);
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_PinAFConfig(GPIOA, GPIO_PinSource15, GPIO_AF_SPI1);
//Busy flag
SPI_GPIO.GPIO_Mode = GPIO_Mode_OUT;
SPI_GPIO.GPIO_OType = GPIO_OType_PP;
SPI_GPIO.GPIO_Pin = GPIO_Pin_5;
GPIO_Init(GPIOC, amp;SPI_GPIO);
/*SPI_GPIO.GPIO_Mode = GPIO_Mode_IN;
SPI_GPIO.GPIO_PuPd = GPIO_PuPd_UP;
SPI_GPIO.GPIO_Pin = GPIO_Pin_15;
GPIO_Init(GPIOA, amp;SPI_GPIO);*/
SPI.SPI_CPHA = SPI_CPHA_1Edge;
SPI.SPI_CPOL = SPI_CPOL_Low;
SPI.SPI_DataSize = SPI_DataSize_8b;
SPI.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI.SPI_FirstBit = SPI_FirstBit_MSB;
SPI.SPI_Mode = SPI_Mode_Slave;
SPI.SPI_NSS = SPI_NSS_Hard;
SPI_Init(SPI1, amp;SPI);
SPI_Cmd(SPI1, ENABLE);
SPI_aux_tim_conf();
}
static void SPI_IO_conf(void)
{
//Struct
GPIO_InitTypeDef SPI_IO;
//CLK
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOE, ENABLE);
//Conf
SPI_IO.GPIO_Mode = GPIO_Mode_AF;
//5 - SCK, 6 - MISO, 7- MOSI
SPI_IO.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7 | GPIO_Pin_6;
SPI_IO.GPIO_PuPd = GPIO_PuPd_DOWN;
SPI_IO.GPIO_OType = GPIO_OType_PP;
SPI_IO.GPIO_Speed = GPIO_Speed_25MHz;
//Init
GPIO_Init(GPIOA, amp;SPI_IO);
//Connect to SPI periph
GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);
//For busy flag checking
SPI_IO.GPIO_Mode = GPIO_Mode_IN;
SPI_IO.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 |GPIO_Pin_12 |GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
SPI_IO.GPIO_PuPd = GPIO_PuPd_DOWN;
SPI_IO.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOE, amp;SPI_IO);
SPI_IO.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOB, amp;SPI_IO);
}
static void SPI_periph_conf(void)
{
//Struct
SPI_InitTypeDef SPI_conf;
//CLK
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
//Conf
//SysClk = 84000000
//84/64 = 1,3125MHz
SPI_conf.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
SPI_conf.SPI_CPHA = SPI_CPHA_1Edge;
SPI_conf.SPI_CPOL = SPI_CPOL_Low;
//SPI_conf.SPI_CRCPolynomial =
SPI_conf.SPI_DataSize = SPI_DataSize_8b;
SPI_conf.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_conf.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_conf.SPI_Mode = SPI_Mode_Master;
SPI_conf.SPI_NSS = SPI_NSS_Soft;
//Conf, enable
SPI_Init(SPI1, amp;SPI_conf);
SPI_Cmd(SPI1, ENABLE);
//SPI_Cmd(SPI1, DISABLE);
}
Как вы можете видеть на осциллограмме, ответа от ведомого устройства нет, ожидаемый ответ — та же команда, которая была отправлена в предыдущем цикле ведущим устройством. Например, я отправляю команду присутствия 0x01, и ведомое устройство должно ответить тем же байтом, после этого должны произойти любые другие обмены, которые еще не реализованы.
С наилучшими пожеланиями, Марек
Комментарии:
1. Сигналы на pic выглядят как случайный мусор, так что заставляет вас думать, что это проблема программного обеспечения? Похоже, вы либо измерили без заземления зонда, либо у вас возникла проблема с оборудованием.
2. Как я вижу, нет сигнала CLK или CS … ведомое устройство не будет отвечать, если не выбрано. Можете ли вы попробовать с 1 ведомым 1 ведущим и совместно использовать сигналы CLK и CS?
3. Отличный пост. Можете ли вы отключить все подчиненные устройства, подключить осциллограмму только к ведущим линиям SPI и линиям выбора ведомого устройства и подтвердить, что ведущее устройство правильно отправляет данные? Таким образом, вы будете знать, что, по крайней мере, ведущее устройство работает правильно.
4. Спасибо за ответы. Конечно, есть сигналы Clock и CS, хотя последний не виден, часы можно увидеть после увеличения масштаба на осциллографе: ! Осциллограмма CS — обычный активный низкий сигнал, я не стал его изображать, поскольку знаю, что он работает правильно. Я также пробовал с одним из каждого устройства, похоже, оно работает одинаково. Master также отправляет правильные данные, я проверил, как вы предложили, @KamilCuk. С наилучшими пожеланиями
5. Итак, что на самом деле показывает pic? MOSI на самом деле не MOSI? MISO — это часы… где данные? «2» — это какой-то случайный шум? «1» — это какой-то несвязанный сигнал?
Ответ №1:
Из ваших изображений кажется, что CLK остается низким после отправки данных. В SPI ведущее устройство является единственным регулятором синхронизации.
Из справочного руководства STM32F411xC / E, стр. 578:
Флаг ЗАНЯТОСТИ
Этот флаг BSY устанавливается и очищается аппаратным обеспечением (запись в этот флаг не имеет никакого эффекта). Флаг BSY указывает состояние уровня связи SPI.
Когда установлен BSY, это указывает на то, что SPI занят обменом данными. Существует одно исключение в режиме ведущего / двунаправленного приема (MSTR = 1 и BDM = 1 и BDOE = 0), когда флаг BSY остается низким во время приема.
Флаг BSY полезен для определения окончания передачи, если программное обеспечение хочет отключить SPI и перейти в режим остановки (или отключить периферийные часы). Это позволяет избежать повреждения последней передачи. Для этого необходимо строго соблюдать процедуру, описанную ниже.
Флаг BSY также полезен для предотвращения конфликтов записи в системе с несколькими мастерами.
Флаг BSY устанавливается при запуске передачи, за исключением режима master / режима двунаправленного приема (MSTR = 1 и BDM = 1 и BDOE = 0).
Он очищен:
- когда передача завершена (за исключением режима master, если связь непрерывна)
- когда SPI отключен при возникновении ошибки в главном режиме (MODF = 1)
Когда связь не является непрерывной, флаг BSY является низким между каждым сообщением.
Когда связь непрерывна:
- в режиме master флаг BSY остается высоким во время всех передач
- в подчиненном режиме флаг BSY становится низким на один такт SPI между каждой передачей
Примечание: Не используйте флаг BSY для обработки каждой передачи или приема данных. Вместо этого лучше использовать флаги TXE и RXNE
Поэтому я думаю, что ваше ожидание флага занятости в главном после отправки данных может заблокироваться на неопределенный срок. Попробуйте это (код использует обычный CMSI, но он должен быть понятным):
GPIOB->BSRR |= GPIO_BSRR_BR6; //slave select
while(! (SPI1->SR amp; SPI_SR_TXE)); //wait for Tx buffer empty
SPI1->DR = 0x01; //send 0x01
while(! (SPI1->SR amp; SPI_SR_RXNE)); //wait for Rx buffer not empty (receive 0x0 sent by the slave during our sending 0x01 since it's 4-wire SPI)
uint8_t tmp = SPI1->DR; //we don't need that value, but need to read DR in order to reset RXNE flag
SPI1->DR = 0x0; //we need to trigger send in order to receive
while(! (SPI1->SR amp; SPI_SR_RXNE)); //wait for Rx buffer not empty (our response)
response = SPI1->DR;
while(SPI1->SR amp; SPI_SR_BSY); //now we can wait for SPI to end communications
GPIOB->BSRR |= GPIO_BSRR_BS6; //slave deselect
Ответ №2:
Спасибо за помощь. После долгих часов мне удалось заставить его работать, сбрасывая периферийное устройство SPI на ведомом устройстве после каждой транзакции:
void SPI_reset_flush(void)
{
//Reset the periph and registers
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1, ENABLE);
SPI_aux_tim_wait();
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1, DISABLE);
SPI_aux_tim_wait();
SPI_Cmd(SPI1, ENABLE);
}
12.04.2019
На самом деле, я думаю, что упомянутое решение не самое лучшее. Проблема заключалась в том, что я не ждал, пока буферы SPI опустеют, это привело к отправке случайных данных, и я потерял синхронизацию между устройствами. Я с тех пор переписал код и придерживался процедур TX / RX в справочном руководстве.
С наилучшими пожеланиями, Марек