Получение ошибки при настройке регистра режима порта GPIO

Я изучаю программирование микроконтроллера STM32 с помощью платы STM32F4DISC-1. Я пытаюсь мигать светодиодом, подключенным к выводу PD14 контроллера STM32F407vg. В рамках упражнения я должен установить регистр режима порта GPIO, где 28-й бит должен быть установлен как выход, чтобы я мог поставить 1 для включения светодиода.

Переменная-указатель для регистра режима порта определяется, как показано ниже, она инициализируется базовым адресом + смещением.

uint32_t * pPortDModeReg = (uint32_t*)0x40020C00;

чтобы установить 28-й бит в 1 , я использую следующий оператор для очистки 28-го и 29-го битов перед установкой 28-го бита в 1.

* 2. настроить режим ввода-вывода в качестве выхода

очистить 28-й и 29-й биты, инвертировав & ing со сдвигом 011 влево на 28 бит*

*pPortDModeReg &= ~(3 << 28);

Я получаю следующую ошибку в IDE STM32CubeIDE на основе gcc.

../Src/main.c:36:4: **ошибка: недопустимые операнды для двоичного * (имеют 'int' и 'uint32_t ' {он же 'long unsigned int '}) 36 |
*pPortDModeReg &= ~(3 << 28);

Я даже пытался использовать выражение RHS как (uint32_t*), как показано ниже, но ошибка сохраняется.

pPortDModeReg &= (uint32_t *) (~(3 << 28));

**

Но для инструктора обучающего видео это утверждение строится без ошибок.

Играет ли со мной какая-либо настройка компилятора? , пожалуйста помоги.

Я даю ниже файл main.c для справки

 #include <stdint.h>
 
 // This program has been modified to blink the RED LED connected to
 PD14 pin and with bitwise operators
 
 int main(void) {

     uint32_t *pClkCtrlReg = (uint32_t*)0x40023830;
     uint32_t *pPortDModeReg = (uint32_t*)0x40020C00;
     uint32_t *pPortDOutReg = (uint32_t*)0x40020C14;

//1. Enable clock for GPIOD peripheral in AHB1ENR ( SET the 3rd bit position)
   

  *pClkCtrlReg |= (1<<3) /* Surprisingly , this line doesn't cause error */

/*configure the mode to set the GPIO pin 14 as output
// clear the 28th and 29th bits  

 
 *pPortDModeReg  &= ~(3  << 28);  /*This line causes error*/

 // set the 28th bit   

  *pPortDModeReg |= (1> << 28);
Не могли бы вы опубликовать минимальный файл main.c, который вызывает эту ошибку?
Вам нужно понять ошибки вашего компилятора, он говорит, что у него недопустимые операнды для двоичной операции, являющиеся int и uint32_t. Ваш гипс беспорядок и не нужен. Вместо этого вы должны сообщить компилятору, что ваши литералы также беззнаковые, поэтому используйте 3UL<<28 - это делает ваш литерал беззнаковым длинным, и у вас есть беззнаковый и беззнаковый для оператора, и он должен работать.
Пробовал 3UL<<28 , ошибка не исчезает. Я даже объявил новую переменную var1 как uint32_t var1 =3; а затем сделал *pPortDModeReg &= ~( var1 << 28); , все равно ошибка не исчезает, я привожу ниже файл main.c для справки.
В строке с комментарием «удивительно, но эта строка не вызывает ошибки» отсутствует символ ;, что сбивает синтаксический анализатор и вызывает ошибку.
Я думаю, что вы следуете неправильному руководству и начинаете не с того конца. Никто не должен писать переменные, указывающие на правильные адреса памяти. Обычно вы просто используете заголовок, предоставленный производителем, и просто используете указанные там имена регистров. Также вы не говорите, какой компилятор вы используете, и если вы не используете точно такую ​​же версию компилятора с такими же настройками, вы можете получить ошибки.
Арсенал и Мэт, вы оба были правы, *pPortDModeReg &= ~( 3UL << 28); работал и был ; отсутствует после *pClkCtrlReg |= (1<<3) , проект построен успешно, проверим его на оборудовании - большое спасибо.
@Justme Есть много причин, по которым можно было бы объявить регистры вручную. Переносимость между разными компиляторами для одной и той же цели является основной. Другой причиной может быть совместимость с MISRA-C, поскольку большинство карт регистров, предоставляемых поставщиками микросхем, не соответствуют MISRA-C или даже стандарту C. Обычно можно увидеть полностью нефункциональные, непереносимые карты регистров, реализованные с битовыми полями, что является полностью сломанная, недоопределенная функция C. Если бы у среднего поставщика микросхем действительно были наняты компетентные программисты на C, у нас не было бы этих проблем.
@Lundin Я понял. Однако OP использует микроконтроллеры ST с инструментами ST IDE, которые используют компилятор GCC. Используя эти инструменты, вы сможете мигать светодиодами в RTOS через 15 минут после их установки. Заголовки, предоставленные ST, уже включены для вас.
Привет @Justme, пожалуйста, дайте мне пример исходного кода для мигания LD4 в stm32f4disc с использованием предоставленных заголовков st, чтобы я мог попробовать, просто main.c должно быть достаточно - спасибо.
@MadavanViswanathan Примеры ST для вашей платы Discovery, поставляемые с установленным вами пакетом прошивки CubeF4, уже должны содержать такую ​​демонстрацию мигания светодиода.

Ответы (1)

Начните с выяснения того, что компилятор пытается вам сказать. Что означает «ошибка: недопустимые операнды для двоичного *»? В программировании на C мы говорим об унарных, бинарных и троичных операндах, в зависимости от того, принимают ли они 1, 2 или 3 операнда. Таким образом, бинарный оператор * будет *с двумя операндами, также известный как умножение. Теперь, почему компилятор думает, что вы делаете умножение?

pClkCtrlReg |= (1<<3) / Удивительно, но эта строка не вызывает ошибки */

Оно делает. Отсутствие точки с запятой заставляет компилятор думать, что вы пишете одно выражение в нескольких строках:

*pClkCtrlReg |= (1<<3)*pPortDModeReg &= ~(3 << 28); // this is what the compiler sees

И поэтому он думает, что *in (1<<3)*pPortDModeReg— это умножение, бинарный оператор * вместо унарного * для косвенности.


Кроме того, вот набор других ошибок:

uint32_t *pClkCtrlReg = (uint32_t*)0x40023830;

Всякий раз, когда вы используете аппаратные регистры, вы обязательно должны добавлять volatileквалификаторы, иначе вы можете получить всевозможные странные ошибки оптимизации. См. Как получить доступ к аппаратному регистру из прошивки? Из этой ссылки мы также можем узнать, что все целочисленные константы должны быть написаны с Uили uв конце, чтобы предотвратить случайное получение целочисленных типов со знаком.

Это приводит нас к другому источнику ошибок 1 << 28. Эта линия работает, но в основном не повезло. 1является целочисленной константой и имеет знаковый тип int. Пока он положительный, мы можем сдвинуться до 30-го бита. Если вместо этого мы попытаемся что-то вроде 1 << 31установки MSB, мы вызовем ошибки неопределенного поведения, потому что теперь мы сдвигаем данные в знаковый бит безымянного, intобразованный 1. Опять же, мы должны писать 1u << 31, чтобы избежать ошибок.

Почему только левый операнд? Что ж, в написании нет ничего плохого, 1u << 31uно операторы сдвига — это особый случай в C, поскольку они всегда возвращают результат того же типа, что и левый операнд. Обычные выражения C будут использовать неявное повышение типа, чтобы сделать оба операнда одного и того же типа, и тогда результат будет этого типа.

int main (void)не имеет смысла в приложениях «голого железа» и RTOS MCU. Это так называемые автономные системы, и они никогда не возвращаются из функции main(). Потому что к кому они вернутся? Обычно все автономные системы void main (void)вместо этого используют форму, определяемую реализацией. (Если вы используете gcc, компилируйте его -ffreestandingпри кодировании встроенных систем.)

Спасибо @Lundin за подробный ответ, действительно очень поучительный.