Основы языка СИ для микроконтроллеров

страница в разработке

Cистемы счисления, использованные для программирования микроконтроллеров:

Для работы с портами:

DDRD = 0b11111110; //DDRD - регистр направляния данных порта D, все порты порта D настроены на выход, кроме нулевого, который на вход

Далее, например, необходимо установить только 2-й бит порта D, т.е. чтоб на втором бите порта D был высокий уровень (логическая единица):

PORTD = 0b00000100;//2-й разряд установили в единицу, а все остальные сбрасили в ноль

Установка бита(ов)

Команда PORTD = 0b00000100; может быть критична, если другие порты нельзя трогать. Для этого применяется побитовая операция, т.е. затрагивающая один бит:

PORTD |= (1<<2);//установить второй бит порта D

Такая короткая запись равна записи:

PORTD = PORTD | (1<<2); //установить второй бит порта D

Разберём подробней эту запись, зная что существует шесть логических побитовых операций (см. алгебру Буля) и команды выполняются слева на права и сначала в скобках:

Чтобы установить сразу несколько битов:

PORTD |= (1<<2)|(1<<6)|(1<<7); //установить второй, шестой и седьмой биты порта D

Сброс (обнуление) бита(ов)

Например, необходимо обнулить 4-ый бит порта С:

PORTC = 0b00000000;//Обнулить 4-ый бит порта С и соответственно все остальные

Как рассмотрели ранее, эта команда может быть критична, если другие биты участвуют в других операция. Для сброса отдельного бита применяются выражение:

PORTC = 0b00010100;
PORTC = PORTC & ~(1<<4);//сбросить 4-ый бит порта С
PORTC &= ~(1<<4);//короткая запись, сбросить 4-ый бит порта С

Разберём команду PORTC &= ~(1<<4); слева на право:

Сбросим 4-й и 2-ой разряд регистра PORTC и оставим без изменений остальные:

PORTC &= ~((1<<2)|(1<<4));//сбросить 2-ой и 4-ый бит регистра С

Переключение бита(ов)

Переключение бита на противоположное состояние: единицу в ноль и наоборот. Рассмотрим на примере порта A:

PORTA ^= (1<<2);//изменим состояние второго бита на пробивоположное
PORTA ^= (1<<0)|(1<<2)|(1<<6);//0-ой, 2-ой и 6-ой биты изменить на противоположное

Проверка разряда на наличие логического нуля

if (0==(PIND & (1<<3))){/*код какой-то*/}//eсли третий разряд порта D сброшен, то...
//аналогичное условие:
if (~PIND & (1<<3)){/*код какой-то*/}//если 3-ий пин регистра D не равен 1, то... (Пин - ножка микроконтроллера, соответствующая биту порта)
//Ожидание сброса бита, оператор while:
while (PIND & (1<<3)){/*код какой-то*/}//выполнять условие пока 3-ий пин = 1

Проверка разряда на наличие логической единицы

if (0 != (PIND & (1<<3))){/*код какой-то*/}//если 3-ий пин не равен нулю
//Аналогично:
if (PIND & (1<<3)){/*код какой-то*/}//если третий пин равен единице
//Ожидание установки бита, оператор while:
while (~PIND & (1<<3)){/*код какой-то*/}//Цикл выполняеться пока 3-й разряд порта D сброшен
//или
while (! (PIND & (1<<3))){/*код какой-то*/}//аналогично

Случайные числа

volatile int Temp;
Temp = rand(); //случайное число от 0 до 32768, но при каждом включении они будут одинаковы. Чтобы этого не было нужно менять стартовый аргумент:

srand(5); // стартовое число. Его тоже надо менять, чтоб при следующих запусках программы не было повторений
Temp = rand();

Temp = rand()%10; // генерация от 0 до 9 (%- остаток от деления)
Temp = rand()%5; // генерация от 0 до 4

Temp = 10 + rand()%41; // генерация в диапазоне от 10 до 40
Temp = -10 + rand()%10; // генерация в диапазоне от -10 до 0
Temp = -20 + rand()%10; // генерация в диапазоне от -20 до -10
Temp = -20 + rand()%41; // генерация в диапазоне от -20 до 20

volatile float Temp;
Temp = 1.0 + (float) rand() / 32768 + 2.4; // генерация в диапазоне от 1.0 до 2.4

Типы данных С

Тип байт Диапазон принимаемых значений Пример

логический тип данных

bool 1 0 / 255 true (истина) или 1...255 / false (ложь) или 0

символьный тип данных

char 1 0 / 255 char symbol ="a"; или char string[] = "energodar.net"; и т.д.
unsigned char 1 0 / 255
signed char 1 -128 / 127

целочисленный: int. Варианты: short - укороченный, long - удлинённый, unsigned - без знака.

int 2 -32 768 / 32 767
short int 1 -128 / 127
unsigned short int 1 0 / 255
unsigned int 2 0 / 65 535
long int 4 -2 147 483 648 / 2 147 483 647
unsigned long int 4 0 / 4 294 967 295
int8_t 1 -128 / 127 Т.к. в AVR и ESP есть разница,
то лучше использовать эти переменные
uint8_t 1 0 / 255
int16_t 2 -32 768 / 32 767
uint16_t 2 0 / 65 535
int32_t 4 -2 147 483 648 / 2 147 483 647
uint32_t 4 0 / 4 294 967 295

типы данных с плавающей точкой

float 4 -2 147 483 648.0 / 2 147 483 647.0
long float 8 -9 223 372 036 854 775 808 .0 / 9 223 372 036 854 775 807.0
double 8 -9 223 372 036 854 775 808 .0 / 9 223 372 036 854 775 807.0

Подключение библиотек:
#include ... - подключаем СИшный код (библиотеку), написанный до нас для нужных нам функций, например:
#include <avr/io.h> // библиотека ввода вывода
#include <avr/delay.h> // функция задержки: _delay_ms(1000) - задержка 1 секунда
Функции
void main(void){} // main - главная функция микроконтроллеров AVR, которую МК обработает при подаче на него питания. В ней пишем все наши команды
Пользовательские функции
Функция паузы
void pause (unsigned int a) { unsigned int i;  for (i=a;i>0;i--); }
Вызываем:
pause(1000);//подождать 1000 тактов?
Циклы
while (1){ //безконечный цикл, т.к. 1 (или любое другое число, отличное от нуля) - это истина
  _delay_ms(100); //задержка 100 мс
}
Порты
  DDRA - определить порт A на выход (1) или на вход (0), пример:
    DDRB=0b00100011; // порт B, ножка PB5 (5-ый бит), PB1 (1-ый бит), PB0 (0-ый бит) будут работать на выход (т.к. = 1), остальные на вход (= 0)
  PORTB - управление состоянием порта B:
    PORTB=0b11111111; // на всех ножках порта B логическая 1, в двоичной системе счисления
    PORTB=0xFF; // на всех ножках порта B логическая 1, в 16-тиричной системе счисления
    PORTB=255; // на всех ножках порта B логическая 1, 10-тиричной системе счисления
    PORTB=(1<<2); // второй бит выставить в 1, остальные в 0
    PORTC |=(1<<3); // меняем в порту С третий бит в 1, остальные не изменятся
    PORTA &=~(1<<5); // сбросить пятый бит порта А, не затрагивая остальные
    PORTA = PORTA<<1; // сдвинуть 1 бит, т.е. было, например: 00b00000010, станет 0b00000100
  PORTD5 - управление 5-ым битом порта D (ножка PD5)
    PORTD |= (1<<(PORTD5)); //5-ый бит порта D в единицу // Это константа определена в файле io.h. Аналогична запись: PORTD |= (1<<5); или 
    PORTD |= _BV(PD5);      //5-ый бит порта D в единицу //_BV - функция побитового сдвига
    PORTD &= ~_BV(PD4);     //4-ый бит порта D сбросить в 0. Аналогична запись PORTD &= ~(1<<4); или PORTD ^=(1<<PD4);
  Проверить пин порта, например, что на RD2 логическая 1:
    while (PORTD &(1<<2)) { ... }
    if (PORTD &(1<<PD2)) { ... }
  Проверить пин порта, например, что на RD2 логический 0:
    while (!(PORTD &(1<<2))) { ... } 
    if (!(PORTD &(1<<PD2))) { ... }
// Пример зажигания светодиода, подключенного к порту RD, третий бит (RD3):
# define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void){
	DDRD |=(1<<3); // RD3 на выход, остальные не трогаем
	PORTD &=~(1<<3); // RD3 определяем в 0, остальные не трогаем
	while (1) { //вечный цикл
		_delay_ms(100); //задержка 100 мс - _delay_ms конечно лучше не использовать, т.е. тратиться машинное время в пустую		//PORTD = 0b00000000;
		PORTD &=~(1<<3); // RD3 определяем в 0, остальные не трогаем
		_delay_ms(100); //задержка 100 мс
		PORTD |=(1<<3); // RD3 определяем в 1, остальные не трогаем в отличии, если бы написали так: PORTD = 0b000001000;
	}
}
// Пример бегущей точки из светодиодов, зажигаем по очереди порта с 0 по 7
# define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
	DDRD = 255; // RD все биты на выход
	PORTD = 0b00000000; // RD все биты определяем в 0
	int8_t i; // задаём переменную i
	i=0; // присваиваем переменной i ноль
	while (1) { // вечный цикл
		PORTD |=(1<<i); // RD i определяем в 1 (зажигаем)
		_delay_ms(1000); //задержка 1000 мс
		PORTD &=~(1<<i); // RD i определяем в 0 (т.е. тушим через секунду)
		i  ; // увеличиваем переменную i на единицу, т.е. переходим к следующему биту порта
		if (i==8) i=0; // если i равно 8, то обнуляем, т.к. бита с номером 8 нет (от 0 до 7)
	}
}
/* генератор на 8-bit Timer2, CTC режим для AVR ATmega8 */
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

register unsigned char myTimer asm("r28");

ISR(TIMER2_COMP_vect){
  myTimer--;
  if (!myTimer)  {
    PORTB ^= (1<<PB5);
    myTimer=0x80;
  }
}

int main(void){
  DDRB |= (1<<PB5); //  pinMode(13,OUTPUT); в Wiring
  // timer2 setup
  TIMSK = (1<<OCIE2);  // timer2: compare interrup is enable
  TCCR2 = (1<<WGM21);  // set CTC mode 
  TCCR2|= (1<<CS22) |(1<<CS21)| (1<<CS20); // prescaler 1/1024
  OCR2  = 0x40;

  myTimer=0x80;
  sei();
  for (;;) {}

  return 0;
}
char buf[3];
Temp=543;

buf[2] = Temp % 1000 / 100;	// сотни
buf[1] = Temp % 100 / 10;	// десятки
buf[0] = Temp % 10;		// единицы

// или так:

char buf[3];
Temp=543;
while(Temp>=100){Temp=Temp-100; buf[0]++;} // вычисляем сотни   buf[0]==5
while(Temp>=10 ){Temp=Temp-10;  buf[1]++;} // вычисляем десятки buf[1]==4
                                buf[2] = Temp; // единицы       buf[2]==3

// для часов:

unsigned long int Temp=10000;//секунды
char buf[4];
while(Temp>=3600){Temp=Temp-3600; buf[0]++;} // вычисляем часы
while(Temp>=600 ){Temp=Temp-600; buf[1]++;} // вычисляем десятки минут
while(Temp>=60 ){Temp=Temp-60; buf[2]++;} // вычисляем единицы минут
while(Temp>=10 ){Temp=Temp-10; buf[3]++;} // вычисляем десятки секунд
			buf[4] = Temp; // единицы секунд
sprintf (str, "%s %d %c", "one", 2, '3');
&data; // & - вывести символ по коду, который в переменной data