У меня есть общие знания об указателях и их использовании, но я придумал код, использующий приведение указателей (я думаю), которого я никогда не видел и не уверен, что понимаю.
Код представляет собой функцию на языке C (написанную для микроконтроллера STM32F091), которая считывает данные из EEPROM (93c66).
Насколько я понимаю, функция принимает адрес для чтения в качестве аргумента. Значение адреса используется как указатель и отправляется по SPI.
Если связь завершена правильно, она начинает прослушивание фактического значения, отправленного из EEPROM, а затем функция возвращает данные.
unsigned short rd_word(unsigned short add93)
{
unsigned char *pt = (unsigned char *)(&add93);
unsigned char error = 0;
CS_HIGH;
add93 &= 0x00ff;
add93 |= 0x0600;
while (HAL_SPI_Transmit(&hspi2, pt, 1, 1000) != HAL_OK){
__iar_builtin_no_operation();
}
SET_BIT((&hspi2)->Instance->CR1, SPI_CR1_CPHA);
while (HAL_SPI_Receive(&hspi2, pt, 1, 1000) != HAL_OK){
__iar_builtin_no_operation();
}
CLEAR_BIT((&hspi2)->Instance->CR1, SPI_CR1_CPHA);
CS_LOW;
return add93;
}
Мой вопрос: что делает код unsigned char *pt = (unsigned char *)(&add93);
? Насколько я понимаю, он приводит add93
к указателю unsigned char
?
Теперь, если unsigned char *pt
является указателем и его значением является фактическое значение памяти, в которой хранится add93
, то почему он передается pt
, а не add93
?
Код
add93 &= 0x00ff;
add93 |= 0x0600;
введите add93
в беззнаковый символ (add93 &= 0x00ff;
) и прикрепите начальный код чтения для EEPROM (add93 |= 0x0600;
) [0x6 = 110b]
/* SPI2 init function */
void MX_SPI2_Init(unsigned int dataSize)
{
/* SPI2 parameter configuration*/
hspi2.Instance = SPI2;
hspi2.Init.Mode = SPI_MODE_MASTER;
hspi2.Init.Direction = SPI_DIRECTION_2LINES;
hspi2.Init.DataSize = SPI_DATASIZE_16BIT;
hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi2.Init.NSS = SPI_NSS_SOFT;
hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128;
hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi2.Init.CRCPolynomial = 7;
hspi2.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
hspi2.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
if (HAL_SPI_Init(&hspi2) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
}
Кроме того, оператор типа add93 &= 0x00ff;
изменяет значение самой переменной add93
, а не ячейку памяти, на которую она «указывает». Для этого используйте *pt &= 0x00ff;
Так почему бы не передать переменную add93
напрямую, а не использовать указатель pt
?
Да, и если вы хотите читать и писать unsigned short
, вам нужно изменить pt
на unsigned short *
.
Переменная add93
содержит короткое значение без знака, которое является адресом памяти (я предполагаю), но не является указателем на этот адрес памяти. Возьмем, к примеру, return add93;
, который возвращает текущее значение самого add93
, а не значение, хранящееся в ячейке памяти.
@Someprogrammerdude, только что выяснилось, что HAL_SPI_Transmit
и HAL_SPI_Receive
на самом деле нужен указатель, а не переменная в качестве входных данных. Теперь это имеет смысл
@NicoCaldo: Что еще более важно, HAL_SPI_Transmit
и HAL_SPI_Receive
на самом деле нужен char*
, а не unsigned short
В первом вопросе
что делает код
unsigned char *pt = (unsigned char *)(&add93);
? Насколько я понимаю, он приводитadd93
к указателюunsigned char
?
Да, строка unsigned char *pt = (unsigned char *)(&add93)
передает адрес add93
в указатель на unsigned char
В секунду:
add93 &= 0x00ff;
add93 |= 0x0600;
Они выполняют побитовые операции И и ИЛИ, чтобы подготовить add93 с правильными битами команды и адреса для операции чтения EEPROM.
ОП спросил, «приводит ли это add93
к указателю unsigned char
, и ответ — НЕТ. Он приводит адрес значения add93 к указателю unsigned char
.
Указатели в большинстве случаев эквивалентны адресу. Но во встроенных системах это обычно локальные адреса внутри самого микроконтроллера. Здесь вы имеете дело с какой-то внешней памятью.
rd_word
— одна из функций драйвера, взаимодействующего с этой памятью. Видимо они решили, что у него должен быть параметр unsigned short add93
, соответствующий адресу в этой памяти, но внутри вашего микроконтроллера он выражается в виде простого числа.
Здесь мы можем отметить, что использование unsigned short
и подобных «примитивных типов данных», которые по умолчанию входят в состав C, является плохой практикой, особенно во встроенных системах. Потому что мы не знаем, насколько велик этот тип — возможно, он 16 бит. Лучшей практикой, как это делается в профессиональном программном обеспечении, является использование типов из stdint.h
, например uint16_t
.
Код хочет преобразовать адрес add93
во что-то значимое для оборудования памяти и отправить его через SPI. А SPI почти всегда работает побайтно, по 8 бит за раз. Поэтому нам нужно сериализовать add93
на байтовые фрагменты, чтобы иметь возможность отправить его.
В языке C есть специальное правило, которое позволяет нам побайтно проверять любой тип переменной. Мы можем сделать это, приведя адрес любой переменной к указателю на символьный тип, байтовый тип C. Чаще всего выражается как unsigned char
или uint8_t
, потому что при работе с необработанными байтами мы не хотим, чтобы какая-либо знаковость испортила ситуацию.
&add93
для этой цели берет адрес локальной переменной add93
. Эта переменная находится в локальном стеке микроконтроллера. Приведя адрес к указателю на символ (unsigned char *)(&add93)
, мы получаем адрес первого байта. Здесь следует отметить некоторые вещи:
(&add93)
не обязательна.unsigned short
. Соответствует ли это старшему или наименее значимому байту, зависит от порядка байтов ЦП (Что такое порядок байтов ЦП?). Cortex M и STM32 в значительной степени имеют прямой порядок байтов, поэтому здесь у нас есть младший байт.Это всего лишь побитовая арифметика:
add93 &= 0x00ff;
add93 |= 0x0600;
Первая строка, выполняющая побитовое И против маски, где все единицы установлены в 0xFF..., называется «маскированием», что означает, что в этом случае младший байт сохраняется, а старший байт очищается. Затем они добавляют значение 0x6 к старшему байте. Напомним, что pt
указывает на младший байт, поэтому значение, на которое он указывает, соответствует той части, которая была сохранена с помощью битовой маскировки.
Функция отправки SPI HAL_SPI_Transmit(&hspi2, pt, 1, 1000)
предположительно отправляет сюда 1 одиночный байт, тот, на который мы указывали.
SET_BIT((&hspi2)->Instance->CR1, SPI_CR1_CPHA);
меняет тактовую фазу связи SPI на лету по какой-либо причине. Делать это в середине передачи — экзотика, но существует множество странных интерфейсов SPI, которые плохо стандартизированы.
Затем он получает 1 байт, который предположительно перезаписывает данные, на которые указывает pt
. Это тоже немного странно - мы можем вспомнить, что SPI является полнодуплексным, всегда отправляя во время приема. То есть, если мы сначала что-то отправляем, а затем что-то получаем, на самом деле мы отправляем 2 байта и получаем 2 байта. Я не знаю подробностей о драйвере SPI и почему нам (не) хотелось бы отправлять 2 байта.
Однако мы можем отметить, что старший байт, установленный add93 |= 0x0600
, не был отправлен. Что странно. Скорее, функция вернет смесь 0x06 и всего, что было получено от SPI.
Настоятельно рекомендуется просматривать фактически отправляемые данные с помощью осциллографа. Запустите сигнал выбора микросхемы и просмотрите MOSI и/или MISO.
Чтобы ответить на ваши конкретные вопросы:
Мой вопрос: что такое код unsigned char *pt = (unsigned char *)(&add93); делает? Насколько я понимаю, он приводит add93 к указателю на беззнаковый символ?
Нет, как упоминалось выше, он преобразует адрес add93
в байтовый указатель, чтобы мы могли получить доступ к unsigned short
побайтно. Это адрес вашего микроконтроллера, не путать с адресом любой части EEPROM, с которой вы имеете дело.
Теперь, если беззнаковый символ *pt является указателем, поэтому его значение является значением фактической памяти, в которой хранится add93, почему передается pt, а не add93?
Потому что функции драйвера SPI, такие как HAL_SPI_Transmit
, ожидают в качестве входных данных байтовый указатель на выделенный буфер. Он должен работать побайтно. Они также могли бы передать (unsigned char*)&add93
в функцию, и это было бы эквивалентно. Но они не могли просто пройти &add93
, потому что в результате получился бы unsigned short*
, тип которого несовместим с unsigned char*
.
Вопреки распространенному мнению, дикие и сумасшедшие преобразования указателей на самом деле не разрешены в C, и добиться их корректности сложно. Общий совет новичкам: вообще никогда не делайте заброс, потому что с этим очень легко ошибиться.
приведите add93 к беззнаковому символу (add93 &= 0x00ff;
Нет, нигде не было приведения, это работало непосредственно с беззнаковой короткой переменной.
прикрепите исходный код чтения EEPROM (add93 |= 0x0600;
Кажется, так и было задумано, но не похоже, что эта часть вообще когда-либо отправлялась через SPI. Этот код написан не очень хорошо и, возможно, содержит ошибки. Возможно, в какой-то момент они намеревались увеличить pt
, чтобы указать на следующий байт, но это не похоже на это. Я бы сразу сказал, что код просто сломан, но мне придется поближе взглянуть на драйвер SPI, чтобы сказать наверняка.
Большое спасибо за обширное объяснение. Я добавил настройки микроконтроллера и SPI в исходный вопрос. Кроме того, что касается add93 |= 0x0600
, это странно, поскольку, согласно таблице данных 93c66
, это код OP для команды чтения, за которым следует 0, как показано на рисунке 9-2.
unsigned char *pt = (unsigned char *)(&add93);
дать вам указатель на переменнуюadd93
, а не на то, куда она «указывает». Для этого используйтеunsigned char *pt = (unsigned char *)(add93);
, то есть отбросьте оператор-указатель на&
.