Бегущие огни - встроенный «Hello World»

Я мочу ноги во встроенной разработке, и, как и все, что было до меня, моя первая задача состояла в том, чтобы сделать светодиод мигающим. Я пошел немного дальше этого и сделал «бегун», который загораживает каждый светодиод от 0 до 7 в последовательности. Последовательность начинается, как только плата включается. Вы можете увидеть это здесь .

Я использую чип ATxMegaA1 с компилятором avr-gcc и цепочкой инструментов. Документ, который я консультировал больше всего, был Использование контактов IO и внешних прерываний doc.

Я решил, что создание концепции «Led» было хорошей абстракцией, чтобы обернуть вокруг порта, к которому они прикреплены на доске. Я также представил макрос, чтобы изменить задержку, когда я отлаживаю, потому что симулятор является порядком величин медленнее, чем реальный чип.

Это мой первый , так что пожалуйста быть жестоким.

led.h

#include <avr/io.h>

#define LEDPORT PORTE_OUT
#define LEDPORT_DIR PORTE_DIR

void init(void);
void toggleLights(int ledPosition);

led.c

#include "led.h"

void init(void)
{
    LEDPORT_DIR = 0b11111111; //Configure LED port for output
    LEDPORT = 0b11111111; //LEDs are active low, this makes sure they're off on start up
}

//Turns light at provided port pin on and all others off.
void toggleLights(int ledPosition)
{
    LEDPORT = ~(1 << ledPosition);
}

Blink.c

#include <stdint.h>
#include "led.h"

#ifdef DEBUG
    #define DELAYITERATIONS 0
#else
    #define DELAYITERATIONS 10000
#endif 

void delay(volatile uint32_t d)
{
    while (d-- != 0)     // loops while non-0 and decrements
    ;
}

int main(void)
{
    init();

    while(1)
    {
        for (int i = 0; i < 8; i++)
        {
            toggleLights(i);
            delay(DELAYITERATIONS);
        }
    }
}
36 голосов | спросил RubberDuck 26 J000000Sunday15 2015, 23:08:15

5 ответов


26

Переключение? Или настройка?

Наша функция toggleLights имеет странное имя и не делает того, что я ожидаю. (В частности, переключатель отключает вещи и выключает их.)

В C, и особенно во встроенном C, мы не должны быть боятся использовать целые числа без знака как битовые массивы .

Итак, возможно, нам нужна функция setLights, которая выглядит примерно так:

void setLights(uint8_t lights) {
    LEDPORT = lights;
}

И чтобы включить каждый из восьми огней, по одному за раз, мы можем перебросить их в наш цикл for:

for (uint8_t lights = 1; lights != 0; lights <<= 1) {
    setLights(lights);
    // delay
}

Итак, теперь мы эффективно делаем то, что ваш код пытается сделать, но функция setLights еще более гибкая. Он просто устанавливает огни точно, что мы говорим, чтобы установить их (возможно, функция может вообще не существовать), но она ограничивает наш ввод правильным типом и дает нам некоторое чувство безопасности).


Использование typedef

Во-первых, мы должны использовать uint8_t, а не int, потому что мы никогда не будем использовать какие-либо значения, которые мы не можем представить с 8 битами и это спасет нас 24 бита.

Но как насчет использования нашего имени для типа?

typedef uint8_t light_positions;

Итак, теперь наша функция setLights становится:

void setLights(light_positions lights);

И наш цикл for будет выглядеть следующим образом:

for (light_positions lights = 1; lights != 0; lights <<= 1) { // ...

Этот тип помогает добавить ясность того, что эта переменная должна представлять и использовать. Это может быть особенно полезно в встроенном C, где вы, возможно, склонны иметь lots переменных типа uint8_t (или других размеров целых чисел без знака).


lights <<= 1

<<= представляет собой соединение оператора присваивания . Все знают и знакомы с +=, правильно? Он является наиболее распространенным из следующих операторов ten .

Между тем << является побитовым оператором сдвига влево (вы использовали его в функции toggleLights).

Итак, вот что происходит в нашем цикле for.

Мы инициализируем lights до 0x00000001. Запустите тело цикла. Оператор обновления сдвигает lights влево по 1 (lights <<= 1), и мы получаем: 0x00000010.

Повторяем это несколько раз, пока не получим 0x10000000, а следующий оператор обновления сдвинет все биты. Мы закончили с 0x00000000 (просто 0) и цикл for завершен, потому что lights != 0 возвращает false.


О макросах препроцессора ...

Макросы препроцессора иногда необходимы, но обычно приводят к более сложному отладке кода.

Вместо вашей версии макроса, я думаю, что код более понятен, если мы просто введем его в цикл:

false

Конечно, если мы используем toggleLights(lights); #ifdef DEBUG delay(1000); #endif в нескольких местах, то ваша версия в порядке - мы не хотим писать лишний код обязательно. Но если мы используем его только в одном месте, напишите его более как это.


Используйте DELAYITERATIONS s ...

Есть несколько способов использования bool /true в C (что помогает сделать код более понятным для человека ). Выберите один и используйте его.


Шестнадцатеричный и двоичный

Использование шестнадцатеричной нотации, возможно, более читаемо, чем подсчет числа 1s в двоичной нотации. Я думаю, что предпочитаю видеть false, а не 0xFF.

Вы даже заметили, что там всего семь?

ответил nhgrif 26 J000000Sunday15 2015, 23:29:57
10

Ваш текущий код задержки оживлен, это означает, что для того, чтобы задержать требуемое количество времени, вы просто тратите впустую столько циклов процессора, сколько необходимо для того, чтобы это время прошло. Если я правильно помню _delay_ms - это функция библиотеки, которая делает что-то похожее на то, что у вас уже есть. Он работает, вычисляя количество нулевых операций, необходимых для ожидания на основе требуемых мс для задержки с учетом тактовой частоты. _delay_ms, тем не менее, остается оживленной функцией ожидания, и вы должны относиться к ее использованию с подозрением, если время задержки больше, чем минимальное.

Поскольку мощность процессора ограничена, а отсрочка в течение более чем мгновенного времени является особенно распространенной проблемой, которую люди должны решать с помощью своей встроенной работы, разработчики чипов включили множество таймеров с прерываниями для решения этой точной проблемы. Таким образом, лучший способ добиться этой задержки - использовать внутренние таймеры, которые предоставляются вам. Вам нужно будет создать код, который устанавливает требуемое значение счетчика для таймера с учетом необходимости предварительного масштабирования и задержки. Затем создайте прерывание, которое срабатывает при сравнении таймера, когда вы хотите отложить, вы должны включить это прерывание. Затем в ISR для прерывания таймера вы можете переключаться с вывода, который управляет светодиодом оттуда. Это сэкономит вам мощность от мощности процессора и познакомит вас с важной концепцией при одновременном выполнении нескольких операций, когда вам нужен таймер в вашем коде.

ответил shuttle87 27 J000000Monday15 2015, 17:25:28
10

Магические числа

В вашем коде есть волшебные числа. Создайте для них константы, чтобы сделать код более читабельным и поддерживаемым. Использование неназванного enum - это трюк для создания значений констант int. Использование в этом случае квалифицированных переменных const, но не для таких вещей, как в операторах switch и для размера массивов. См. этот ответ для получения дополнительной информации об объявлении констант в C.

enum {
    LIGHTS_COUNT = 8,
    LEDPORT_INIT_VALUE = 0b11111111
};

Задержка

Для задержки процесса выполняется цикл занятости. Это не позволяет ОС планировать другие задачи вместо использования ресурсов для no-ops. См. Что такое компромиссы для «оживленного ожидания» против «сна» ? . Я рекомендую вместо этого использовать sleep - это это обычный способ ожидания.

Используйте _delay_ms, как рекомендует @RubberDuck в своем ответе.

Бесконечная петля

Обычный бесконечный цикл в C использует for вместо while.

for (;;) {

}

Исходные файлы

Для проекта с четырьмя функциями действительно ли необходимо разбить его на 3 разных файла? Использование большего количества исходных файлов становится полезным только тогда, когда модули больше по моему мнению.

ответил jacwah 26 J000000Sunday15 2015, 23:36:13
6

Это небольшая точка по сравнению с другими ответами, но стоит отметить для новичка. Это заголовочный файл светодиода.

#include <avr/io.h>

#define LEDPORT PORTE_OUT
#define LEDPORT_DIR PORTE_DIR

void init(void);
void toggleLights(int ledPosition);

Все содержимое заголовка доступно для внешнего кода. Нет никаких оснований для доступа к LEDPORT_DIR. Мы хотим, чтобы этот порт настроен для вывода, и все. Не давайте себе возможность отключить эту настройку после ее инициализации. Минимально переместите это в файл led.c, но практически, это не делает много и может быть полностью удалено.

Конечно, вы все равно можете получить доступ к нему через PORTE_DIR, но точка абстракции состоит в том, что этот модуль должен быть единственным, кто сможет получить доступ к PORTE по его реальному имени. Все, что должно работать с LEDPORT и ничего , должно быть установлено для этого порта для ввода .


Еще одно замечание: ваш компилятор предлагает некоторую встроенную задержку delay методы . Добавьте #include <util/delay.h> и замените свой hack на _delay_ms.

ответил RubberDuck 27 J000000Monday15 2015, 04:08:29
6

Это действительно придирчивое, придирчивое, придирчивое, и я это понимаю.

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

  1. В заголовке, led.h нет ничего вредного, но вы не обернули его, чтобы он включался только один раз. Вы можете увидеть эту практику во всех файлах заголовков (например, stdio.h), и это делается для того, чтобы вы не компилировали ошибки, когда оба файла .h и .c содержали один и тот же заголовочный файл.

    #include <avr/io.h>
    
    #ifndef _LED_H_
    #define _LED_H_
    
    #define LEDPORT PORTE_OUT
    #define LEDPORT_DIR PORTE_DIR
    
    void init(void);
    void toggleLights(int ledPosition);
    
    #endif
    
  2. Вот придирчивая часть:

    Булевы тесты на языке C странно обрабатываются. Мы склонны думать об одном как об истинном, а ноль - как о ложном, но это не совсем правильно. Ноль - false, а не ноль - true.

    Итак, ваш цикл while:

    while(1)
    

    будет нормально работать, и вы можете подумать, что это удивительно понятно. Я это понимаю. Но так будет

    while( -666 )
    

    или

    while(8675309)
    

    , и это значительно менее понятно.

    Действительно, то, что вы хотите выразить, это TRUE или FALSE.

    Вы можете найти такое определение во всех местах в заголовках C-языков:

    #define FALSE 0
    #define TRUE !FALSE
    

    или иногда:

    #define TRUE 1
    

    , который мне нравится еще больше, потому что он устраняет двусмысленность.

    И, как правило, я согласен с тем, что было сказано выше.

ответил John 28 J000000Tuesday15 2015, 00:24:21

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132