Администрация форума не несёт ответственности за достоверность информации и оставляет за собой право редактировать или в особых случаях даже удалять посты без предупреждения. Спасибо за понимание.

Программирование ATMEL в BASCOM.

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » Программирование ATMEL в BASCOM. » Не пора ли нам попробовать ARM? » STM32F030F4P6 - Encoder и I2C Slave


STM32F030F4P6 - Encoder и I2C Slave

Сообщений 1 страница 16 из 16

1

Натолкнуло меня написать эту статью тема об Xmega и энкодерах. В ней для аппаратной работы с энкодером нужен был 32-ух битный таймер.
Рассмотрим сейчас как STM32 работает с энкодером и будем использовать конечно же 32-ух битный таймер. :) "Подопытным кроликом" станет STM32F030F4P6 о котором писал ранее В этом МК есть недокументированный 32-ух битный таймер Tim2 одна из функций которого - аппаратное декодирование энкодера.
Настройка таймера не сложная.

Код:
void Tim2_Encoder_Config(void)
{
    {
        GPIO_InitTypeDef gpio;

        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);  // Вкл. тактирование GPIOA
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // и TIM2.

        // Настройка порта в/в.
        GPIO_StructInit(&gpio);
        gpio.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; // Настройки относятся к выводам 0 и 1.
        gpio.GPIO_Mode = GPIO_Mode_AF; // Альтернативная функция порта
        gpio.GPIO_PuPd = GPIO_PuPd_UP; // Включены подтягивающие резисторы к плюсу.
        GPIO_Init(GPIOA, &gpio);       // Применение этих настроек к выбранным выводам GPIOA.

        // Альтернативная функция выводов 0 и 1 порта GPIOA - 1 и 2 канал таймера Tim2.
        GPIO_PinAFConfig(GPIOA, GPIO_PinSource0, GPIO_AF_2);
        GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_2);
    }

    {   // Настройки таймера.
        TIM_TimeBaseInitTypeDef timer;
        TIM_TimeBaseStructInit(&timer);
        timer.TIM_Period = 0xFFFFFFFF; // Считать нужно от 0 до 0xFFFFFFFF.
        timer.TIM_CounterMode = TIM_CounterMode_Down | TIM_CounterMode_Up; // Реверсивный счетчик.
        TIM_TimeBaseInit(TIM2, &timer); // Применение этих настроек к TIM2.
    }

    // Настройки энкодера.
    TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
    TIM_Cmd(TIM2, ENABLE); // Запуск таймера.


}

Сперва включается тактирование порта GPIOA и таймера Tim2.
Далее выводы 0 и 1 порта GPIOA настраиваются на работу альтернативной функции. Какой именно зависит от третьего параметра функции GPIO_PinAFConfig. Если посмотреть документацию

Table 12. Alternate functions selected through GPIOA_AFR registers for port A

http://s0.uploads.ru/oJD2E.png

То видно что первый и второй каналы захвата таймера Tim2 соответствуют второй альтернативной функции выводов 0 и 1 порта GPIOA.
Далее по коду производится настройка таймера Tim2. Потом активируется функция энкодера и запускается таймер.

В функции main в цикле регулярно считывается значение счетчика таймера и записывается в переменную xx. Если в режиме отладки наблюдать за переменной то видно что ее содержимое меняется на 4 при каждом щелчке энкодера.

http://s1.uploads.ru/cmvER.png

Схема

Свернутый текст

http://sd.uploads.ru/ViY4r.gif

Файлы. http://pure-basic.narod.ru/forum_files/ … ncoder.zip

+1

2

Продолжим. От просмотра переменной в отладчике толку мало. Решено было предусмотреть возможность считывания значения счетчика таймера Tim2 другим микроконтроллером, например ATmega. Тогда у конструкции появится практический смысл. :) Методов связи микроконтроллеров много. Это USART, I2C, SPI и т. д. Я же выбрал I2C потому что аппаратно поддерживается подключение множества устройств и интерфейс синхронный и его можно реализовать программно (в какой-то тиньке у которой нет аппаратного I2C).
Задача была простая - доработать программу так чтобы микроконтроллер стал I2C Slave.
В код была добавлена функция настройки модуля I2C.

Код:
void I2C_Slave(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); // Вкл. тактирование модуля I2C.
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

    {
        GPIO_InitTypeDef gpio;
        // Настройка порта в/в.
        GPIO_StructInit(&gpio);
        gpio.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; // Настройки относятся к выводам 9 и 10.
        gpio.GPIO_Mode = GPIO_Mode_AF;   // Альтернативная функция порта
        gpio.GPIO_PuPd = GPIO_PuPd_UP;   // Включены подтягивающие резисторы к плюсу.
        gpio.GPIO_OType = GPIO_OType_OD; // Выход с открытым стоком.
        GPIO_Init(GPIOA, &gpio);         // Применение этих настроек к выбранным выводам GPIOA.

        // Альтернативная функция выводов 9 и 10 порта GPIOA - выводы модуля I2C.
        GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_4);  // SCL.
        GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_4); // SDA.

    }

    {
        I2C_InitTypeDef i2c;

        RCC_I2CCLKConfig(RCC_I2C1CLK_HSI); // Модуль I2C тактируется от HSI.

        I2C_StructInit(&i2c);
        i2c.I2C_OwnAddress1 = 0x20; // Адрес устройства.
        i2c.I2C_Ack = I2C_Ack_Enable;
        i2c.I2C_Timing = 0x00100346;
        i2c.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;

        I2C_Init(I2C1, &i2c);
    }

    {
        NVIC_InitTypeDef NVIC_I;

        // Настройка прерывания от I2C.
        NVIC_I.NVIC_IRQChannel = I2C1_IRQn;
        NVIC_I.NVIC_IRQChannelPriority = 0; // Приоритет прерывания (0 - 3. 0 - наивысший, 3 - наименьший).
        NVIC_I.NVIC_IRQChannelCmd = ENABLE; // Разрешаем прерывание.

        NVIC_Init(&NVIC_I); // Разрешение прерываний.

        // Разрешение прерываний при ошибке, обнаружении стоп-бита, совпадении адреса, при приеме и передаче.
        I2C_ITConfig(I2C1, (I2C_IT_ERRI|I2C_IT_STOPI|I2C_IT_ADDRI|I2C_IT_RXI |I2C_IT_TXI), ENABLE);
    }

    I2C_Cmd(I2C1, ENABLE); // Запуск модуля I2C.
}

Как и в предыдущем случае, сперва включается тактирование порта и модуля I2C. Затем выводы 9 и 10 настраиваются на альтернативную функцию связанную с I2C. Картинка и вариантами альтернативных функций выше. Видно что для I2C номер альтернативной функции - 4.
После настраивается модуль I2C. Адрес I2C Slave устанавливается равным 0x20. При обращении к устройству его нужно указывать. Поле I2C_Timing задает тайминги модуля. Текущее значение оптимизировано для скорости обмена 400 КГц, но при необходимости скорость можно поднять до 1000 КГц изменив значение поля I2C_Timing. Где-то видел формулу и рекомендации по расчету этого значения, но я поступил проще - скопировал из STM32CubeMX.
После того как модуль настроен, разрешаются прерывания от него и он включается.
Функция обработки прерывания от модуля I2C.

Код:
void I2C1_IRQHandler(void) // Обработчик прерываний от I2C1.
{
    static uint8_t Cmd=0;          // Текущая команда.
    static uint8_t InFlag = RESET; // Разрешение приема данных.
    static uint8_t Temp;



    if (I2C_GetITStatus(I2C1, I2C_IT_STOPF)) // Обнаружен стоп флаг.
    {
        I2C_ClearITPendingBit (I2C1,I2C_IT_STOPF);

        InFlag = RESET;

        if ((InPack.Pos == 4) && (Cmd == Cmd_SetCounter))
        {
            TIM_SetCounter(TIM2, InPack.Counter);
        }

        InPack.Pos = 0;


       I2C_SoftwareResetCmd(I2C1);
    }


    if (I2C_GetITStatus(I2C1, I2C_IT_RXNE))  // Приняты данные.
    {
        Temp = I2C_ReceiveData(I2C1);

        if (InPack.Pos==-1)
        {
           Cmd = Temp;
           InPack.Pos=0;
        }
        else if ((InPack.Pos>=0) && (InPack.Pos < 4))
        {
           if (InFlag == SET)
           {
               InPack.Byte[InPack.Pos] = Temp;
               InPack.Pos++;
           }
        }
        else InFlag = RESET;

        OutPack.Pos=-1;

    }


    if (I2C_GetITStatus(I2C1, I2C_IT_TXIS)) // Нужно отправить данные.
    {
        I2C_ClearITPendingBit(I2C1, I2C_IT_TXIS);

        if ((Cmd==Cmd_GetCounter) && (OutPack.Pos>=0) && (OutPack.Pos<4))
        {
           I2C_SendData(I2C1, OutPack.Byte[OutPack.Pos]);
           OutPack.Pos++;
        }
        else
        {
            I2C_SendData(I2C1, 0); // Что-то пошло не так.
        }

    }


    if (I2C_GetITStatus(I2C1, I2C_IT_ADDR))  // Совпал адрес.
    {
        I2C_ClearITPendingBit(I2C1, I2C_IT_ADDR);

        uint16_t Received = I2C_GetTransferDirection(I2C1);

        if (Received == I2C_Direction_Transmitter) // Данные передаются мастером в I2C Slave.
        {
            InFlag = SET; // Прием данных разрешен.
            OutPack.Pos = -1;
            InPack.Pos = -1;
        }
        else  // Данные принимаются мастером от I2C Slave.
        {
            OutPack.Pos = 0;
            if ((Cmd != 0) && (InPack.Pos==0))
            {
                switch (Cmd)
                {
                case Cmd_GetCounter:
                    OutPack.Counter = TIM_GetCounter(TIM2); // Текущее значение счетчика таймера.
                    OutPack.Pos = 0;
                    break;
                case Cmd_SetCounter:
                    InPack.Counter = 0;
                    InPack.Pos = 0;
                    break;
                default:
                    OutPack.Pos = -1;
                    break;
                }
            }
        }

    }


    if ((I2C_GetITStatus(I2C1,I2C_IT_BERR)) || (I2C_GetITStatus(I2C1, I2C_IT_ARLO))) // Ошибка на шине I2C.
    {
        I2C_ClearITPendingBit(I2C1, I2C_IT_BERR);
        I2C_ClearITPendingBit(I2C1, I2C_IT_ARLO);

        InFlag = RESET;
        InPack.Pos = 0;
        OutPack.Pos=-1;
    }

}

Детально описывать работу не буду. Скажу лишь что в ней производится обработка прерываний от событий совпадения адреса, приема данных или необходимости их передачи (не забываем это I2C Slave и передавать можно не когда захочется, а когда мастер разрешит, ведь он формирует тактирующие импульсы на линии SCL), а так же при состоянии "стоп" или ошибке.

Чтобы мастеру не пришлось постоянно запрашивать содержимое счетчика, добавлено формирование импульса на одном из выводов STM32 длительностью 20 мс при изменении содержимого счетчика Tim2. Его можно использовать для генерации прерывания для считывания по I2C значения счетчика. Импульсы формируются не чаще 20 раз в секунду.
Пример одиночного импульса при однократном щелке энкодера.

http://s4.uploads.ru/L3GpC.png

Импульсы если хорошенько крутануть энкодер.

http://se.uploads.ru/ZiWA8.png

Этот ряд импульсов на самом деле несколько одиночных. Они формируются аппаратно, точнее запуск формирования выполняется программно, а сам импульс нужной длительности генерируется аппаратно. Была у меня похожая задача на AVR и аппаратно ее не удалось решить.
На STM32 эта задача решается используя режим таймера OnePulse который дословно переводится как "один импульс". Смысл в том что таймер запускается программно. Досчитав до переполнения, таймер аппаратно выключается. Таким образом в режиме ШИМ можно формировать аппаратно импульсы нужной длительности. :) Хотя в STM32 нет той проблемы что была в AVR и можно решить задачу на прерываниях назначив им наивысший приоритет чтобы была возможность прервать все остальные прерывания и выполнить более приоритетное.

Код настройки таймера

Код
Код:
void ConfigTim14(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14, ENABLE); // Включаем тактирование таймера 14.
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

    {
        GPIO_InitTypeDef gpio;
        // Настройка порта в/в.
        GPIO_StructInit(&gpio);
        gpio.GPIO_Pin = GPIO_Pin_4; // Настройки относятся к выводу 4.
        gpio.GPIO_Mode = GPIO_Mode_AF; // Альтернативная функция порта.
        GPIO_Init(GPIOA, &gpio);       // Применение этих настроек к выбранным выводам GPIOA.

        // Альтернативная функция вывода 4 порта GPIOA - выход 1 канала таймера Tim14.
        GPIO_PinAFConfig(GPIOA, GPIO_PinSource4, GPIO_AF_4);
    }

    {
        TIM_TimeBaseInitTypeDef Tim;
        TIM_TimeBaseStructInit(&Tim);

        Tim.TIM_ClockDivision = TIM_CKD_DIV1; // Выключаем предварительный делитель частоты таймера.
        Tim.TIM_CounterMode   = TIM_CounterMode_Up; // Значение в счетном регистре увеличивается.
        Tim.TIM_Period        = 31999; // Значение до которого досчитав таймер обнулится (максимум 65535).
        Tim.TIM_Prescaler     = 9; // На сколько нужно делить частоту (максимум 65535).
        Tim.TIM_RepetitionCounter = 0;

        TIM_TimeBaseInit(TIM14, &Tim); // Инициализация таймера.
    }


    {
        TIM_OCInitTypeDef Pwm;
        // Настройка ШИМ.
        TIM_OCStructInit(&Pwm); // Загрузка в структуру настроек таймера по умолчанию.

        Pwm.TIM_OCMode = TIM_OCMode_PWM1; // Режим ШИМ 1.
        Pwm.TIM_OutputState = TIM_OutputState_Enable;
        Pwm.TIM_OCPolarity = TIM_OCPolarity_Low;
        Pwm.TIM_Pulse = 15999;
        TIM_OC1Init(TIM14, &Pwm);
    }

    TIM_SelectOnePulseMode(TIM14, TIM_OPMode_Single); //Вкл. режим OnePulse.
    //TIM_Cmd(TIM14, ENABLE); // Запуск таймера.
}

почти аналогичен рассмотренному ранее и отличается от него разве что наличием функции TIM_SelectOnePulseMode которая активирует режим OnePulse.

Для тестирования был написана программа для баскома

Код:
$regfile = "m328pdef.dat"                         ' ATmega328P.
$crystal = 16000000                               ' 16 МГц.
$hwstack = 128
$swstack = 128
$framesize = 128

$baud = 9600

Const Read_data = &H21
Const Write_data = &H20

Const Cmd_setcounter = 1                          ' Установка счетчика таймера. Нужно передать I2C Slave 4 байта.
Const Cmd_getcounter = 2                          ' Считывание счетчика таймера. Нужно прочитать из I2C Slave 4 байта.

Config Base = 0                                   ' Индексация массивов начинается с 0.
Config Submode = New
Config Clockdiv = 1

Config Int0 = Rising
On Int0 Int_0
Enable Int0

$lib "i2c_twi.lbx"

Config Sda = Portc.4                              ' we need to provide the SDA pin name
Config Scl = Portc.5                              ' we need to provide the SCL pin name
I2cinit
Config Twi = 400000                               ' wanted clock frequency when using $lib

Dim Count As Long
Dim Count_arr(4) As Byte At Count Overlay
Dim Data_flag As Byte

Count = 100
Data_flag = 0

Enable Interrupts

Sub I2c_write()
  Local I As Byte

  I2cstart

  I2cwbyte Write_data
  I2cwbyte Cmd_setcounter

  For I = 0 To 3
    I2cwbyte Count_arr(i)
  Next

  I2cstop

End Sub


Sub I2c_read()
  Local I As Byte

  Count = 0

  I2cstart

  I2cwbyte Write_data
  I2cwbyte Cmd_getcounter

  I2crepstart

  I2cwbyte Read_data

  I2crbyte Count_arr(0) , Ack
  I2crbyte Count_arr(1) , Ack
  I2crbyte Count_arr(2) , Ack
  I2crbyte Count_arr(3) , Nack

  I2cstop

End Sub


I2c_write

Do

  If Data_flag = 1 Then
    Data_flag = 0
    I2c_read
    Print Count
  End If

  Waitms 10

Loop
End

Int_0:
  Data_flag = 1
Return

Схема подключения. Незабываем что для микроконтроллера нужно 3.3 вольта, а не 5 вольт.

Схема

http://sa.uploads.ru/V6bGd.gif

Результат работы.

Свернутый текст

http://s3.uploads.ru/c3y1g.png

Файлы. http://pure-basic.narod.ru/forum_files/ … _Slave.zip

+1

3

Пётр написал(а):

видно что ее содержимое меняется на 4

Дык смысла нет, 32/2=16 бит всего лишь.)

0

4

RDW написал(а):

32/2=16 бит всего лишь.

Т. е. 4294967295 / 2 = 65535? :D

0

5

Хорошо, уел, будет: 1073741823, но это никак не 32 бит.  :rofl:

0

6

Миллиард мало? :O
С другими настройками получалось изменение на 2 при щелчке энкодера. Если посмотреть на картинку здесь то видно что у энкодера 4 состояния. Не удивлюсь если у XMega тоже изменяется состояние счетчика на 4 при щелчке энкодера.

0

7

Пётр написал(а):

удивлюсь если у XMega тоже изменяется состояние счетчика на 4 при щелчке энкодера

Могу сказать только одно при работе с энкодером: у товарища Ev3658 на видео видно, что цифры имеют шаг "чет/не чет" в разных комбинациях, т.е. считается именно каждый шаг по еденице (ибо двигает он линейки рывками), пропусков не должно быть (иначе кому нужно загрубление - сам это сделает), точность важна (особенно в станках). Никто не будет ставить доп редуктор на энкодер (скажем на шпиндель в 10к об/мин), чтобы сделать полное соответствие оборотам/шагам.

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

Отредактировано RDW (2016-12-26 14:33:07)

0

8

RDW написал(а):

Я сомотрю, тебе эта тема (пиписькаметра) покоя не даёт

Я пример привел.

0

9

Во первых никому силой ничего не навязываю. Во вторых, специально для AVR была разработана конструкция - I2C контроллер энкодера.  Я не отказался от мег и по прежнему пишу для них программы. :)

RDW написал(а):

причем очень старается обогнать ХМеги.

Это не так. :)

RDW написал(а):

Я же тут никому не навязываю DSP или FPGA

Навязывать не нужно, а вот парочка статей с исходниками и объяснениями что к чему не помешала бы. Может кому-то (в т. ч. мне) будет интересно и полезно. :)

0

10

Добрый вечер?
А как отправить данные по I2C?
Пытаюсь подружить STM32F030F4P6 и SSD1306,принцип управления знаю но как отправить-увы...

0

11

Правильная ли настройка?

0

12

bulat943 написал(а):

А как отправить данные по I2C?

Можно посмотреть например здесь https://cxem.net/mc/mc362.php

0

13

Спасибо,посмотрю

0

14

В итоге выяснилось что у меня включена собака! Причем чип новый,из упаковки
Поставил галочку WDG_SW и все заработало!

0

15

В STM32F030F4P6 был включен аппаратный запуск собаки?
По умолчанию должно быть программное включение.
https://upforme.ru/uploads/0000/25/b8/385/t392026.png

+1

16

Спасибо еще раз! у меня вроде вообще не было галочек нигде

0


Вы здесь » Программирование ATMEL в BASCOM. » Не пора ли нам попробовать ARM? » STM32F030F4P6 - Encoder и I2C Slave