STM8S + SDCC: Программирование БЕЗ SPL. Интерфейсы: UART в режиме передатчика, АЦП в режиме однократного замера, I2C в режиме мастера

разделы: STM8 , UART , I2C , дата: 5 мая 2018г.

В этот раз разговор пойдёт про аппаратные интерфейсы STM8S: UART, АЦП и I2C. Каждый их этих интерфейсов поддерживает несколько режимов работы, но сейчас мне хотелось бы сфокусироваться на наиболее типовых, на мой взгляд, примерах их использования: а)организация передатчика на UART, б) режим однократного замера АЦП, в) использование I2C в режиме мастера. Напомню, что вариант использования SPI в режиме мастера я приводил на примере драйвера для 4-x разрядного семисегментного индикатора .

Документация которая понадобится для прочтения статьи: Reference Manual STM8S - RM0016, главы: 22 (UART), 24 (АЦП), 21 (I2C). В качестве целевого микроконтроллера я буду использовать 20-пиновый STM8S103F3P6.

Так же как и в прошлый раз, упор будет делаться на "чистом" программировании на Си и Ассемблере без использования сторонних библиотек. В качестве компилятора используется open-source SDCC версии 3.7. Справедливости ради замечу, что я ввёл макросы для прямого доступа к битовым инструкциям, что бы хоть как-то оптимизировать код.

Скачать полные исходники со сборочными файлам и скомпилированными прошивками, можно по ссылке в конце статьи.

В статье используются формулы в формате MathML который поддерживается браузером Firefox, для браузеров Chrome и Opera потребуется установить одноимённое расширение MathML.

    Содержание:
  1. UART1 передатчик со скоростью 921600 baud;
  2. АЦП в режиме однократного замера;
  3. I2C в режиме мастера, на примере RTC DS1307/DS3231. Инициализация.
  4. I2C в режиме мастера, на примере RTC DS1307/DS3231. Функция передачи адреса и данных.
  5. I2C в режиме мастера, на примере RTC DS1307/DS3231. Функция чтения данных.

1. UART1 передатчик со скоростью 921600 baud

Документация на чип STM8S103f3 обещает нам скорость UART протокола ~1 Mbit/sec при частоте fMASTER=16МГц. От себя замечу, что заявленная скорость доступна и без внешнего кварца или генератора.

Блок-схема модуля UART1 представлена на картинке ниже:

В первую очередь здесь бросается в глаза регистр данных - UART1_DR. Как видно, он состоит из двух независимых регистров на чтение и на запись и так же из двух независимых внутренних сдвиговых регистров. Т.е. записывая в UART1_DR и читая с него, фактически обращаешься в разным регистрам.

UART1 включает в себя один флаговый регистр UART1_SR1 и пять конфигурационных: UART1_CR1, UART1_CR2, UART1_CR3, UART1_CR4, UART1_CR5. Кроме регистра данных UART1_DR имеются также пара регистров для указания битрейта: UART1_BRR1 и UART1_BRR2, регистр предделителя: UART1_PSCR, и защитный регистр UART1_GTR для Smartcard режима.

Общая карта регистров с предустановленными значениями показана в следующей табличке:

Значения регистров я рассматривал в статье STM8S+SDCC+SPL: использование модуля UART1 на примере функции printf(). Для того чтобы сконфигурировать и запустить UART1 передатчик нужно иметь в виду всего три флага.

В UART1_CR1 это флаг UARTD который включает и выключает модуль UART1. Обращаю внимание, что в сброшенном состоянии флага, модуль UART1 включён:

В регистре UART1_CR2 нас будет интересовать флаг TEN который включает передатчик:

И во флаговом регистре UART1_SR1 нас будет интересовать бит TXE, который устанавливается в момент передачи содержимого UART1_DR в сдвиговый регистр. Т.е. в момент, когда UART1_DR освободится. Очищается этот флаг путём записи в UART1_DR.

В качестве шаблона проекта я взял пример из предыдущей статьи: Передача параметров из Си в ассемблерную функцию

В результате у меня получился такой исходник:
#include <stdint.h>
#include "stm8s103f.h"
#include "uart1.h"

#define LED 5

extern void delay(uint16_t value);

int main( void ) {
    uint16_t i=0;
    //----------- Setup Clock ----------------------
    // fCPU =16MHz
    CLK_CKDIVR=0;
    // enable UART and turn off other  peripherals
    CLK_PCKENR1=0;
    CLK_PCKENR2=0;
    clk_pckenr1_bset(#3);   // enable UART1
    //----------- Setup GPIO -----------------------
    // GPIO setup
    pb_ddr_bset(#LED);
    pb_cr1_bset(#LED);
    //----------- Setup UART1 ----------------------
    // Clear
    UART1_CR1=0;
    UART1_CR2=0;
    UART1_CR3=0;
    UART1_CR4=0;
    UART1_CR5=0;
    UART1_GTR=0;
    UART1_PSCR=0;
    // setup UART1
    uart1_cr1_bset(#5);     // set UARTD, UART1 disable
    // 9600 Baud Rate, when fMASTER=16MHz
    //UART1_BRR2=0x03
    //UART1_BRR1=0x68
    // 115200 Baud Rate, when fMASTER=16MHz
    //UART1_BRR2=0x0b
    //UART1_BRR1=0x08
    // 230400 Baud Rate, when fMASTER=16MHz
    //UART1_BRR2=0x05;
    //UART1_BRR1=0x04;
    // 460800 Baud Rate, when fMASTER=16MHz
    //UART1_BRR2=0x03;
    //UART1_BRR1=0x02;
    // 921600 Baud Rate, when fMASTER=16MHz
    UART1_BRR2=0x01;
    UART1_BRR1=0x01;
    // Trasmission Enable
    uart1_cr2_bset(#3);      // set TEN, Transmission Enable
    // enable UART1
    uart1_cr1_bres(#5);      // clear UARTD, UART1 enable
    // main loop
    for(;;)
    {
        static uint16_t i=0;
        pb_odr_bcpl(#LED);
        uart1_print_string("count: ");
        uart1_print_number(i++);
        uart1_send_char('\n');
        delay(1000);
    }
}

Макросы: регистр_bset(), регистр_bres(), регистр_bcpl() определены в заголовочном файле stm8s103f.h. Они заменяются ассеблерными инструкциями:

;   main.c: 51: uart1_cr2_bset(#3);      // set TEN, Transmission Enable
    bset    0x5235,#3
;   main.c: 53: uart1_cr1_bres(#5);      // clear UARTD, UART1 enable
    bres    0x5234,#5
00102$:
;   main.c: 58: pb_odr_bcpl(#LED);
    bcpl    0x5005,#5

Функцией printf() я в этот раз не пользовался, они слишком тяжёлая. Поэтому ее пришлось заменить несколькими самописными функциями для передачи данных через UART:

#include "uart1.h"

#define uart1_sr1_btjf(X,Y) __asm Y: btjf 0x5230,X,Y __endasm
#define len 6

void uart1_send_char(uint8_t ch) {
    UART1_DR=ch;
    uart1_sr1_btjf(#7,l0);      // while (TXE bit == 0);
}

void uart1_print_string(char *str) {
    while (*str)
    {
        uart1_send_char(*str++);
    }
}

void uart1_print_number(uint16_t num){
    uint8_t n[len];
    char *s=n+(len-1);
    *s=0;           // EOL
    do {
        *(--s)=(num%10 + 0x30);
        num=num/10;
    } while (num>0);

    uart1_print_string(s);
}

void uart1_print_bcd(uint8_t num) {
    UART1_DR=(num>>4) + 0x30;
    uart1_sr1_btjf(#7,l1);      // while (TXE bit == 0);
    UART1_DR=(num & 0x0f) + 0x30;
    uart1_sr1_btjf(#7,l2);      // while (TXE bit == 0);
}

Прошивка весит 284 байта. Посмотреть вывод в можно в любой терминальной программе, например в Linux c помощью minicom:

$ minicom -D /dev/ttyUSB0 -b 921600

или через Arduino IDE, вызвав монитор последовательного порта.

2. АЦП в режиме однократного замера

В STM8S имеется 10-битный АЦП, и так как сам микроконтроллер является 8-битным, имеются варианты выравнивания результата, для получения однобайтного 8-битного числа или двухбайтного 10-битного. Варианты выравнивания представлены на картинке ниже:

Нетрудно догадаться, что выравнивание по левому краю удобно для получения 8-битного значения АЦП, а выравнивание по правому удобно для получения 10-битного значения.

Если говорить о режиме однократного замера, то кроме вышеупомянутой регистровой пары ADC_DR, нас будут интересовать ещё несколько РВВ:

1. Флагово - конфигурационный регистр ADC_CSR:

Интересующие нас здесь поля: EOC - завершение преобразования, EOCIE - разрешение прерывания по завершению преобразования и CH[3:0] - выбор аналогового канала.

2. Первый конфигурационный регистр АЦП - ADC_CR1:

Здесь через SPSEL[2:0] устанавливается частота дискретизации АПЦ. Через бит CONT устанавливается или сбрасывается непрерывный режим работы АЦП. ADON включает или выключает АЦП.

3. Второй конфигурационный регистр АЦП - ADC_CR2:

Здесь нас будет интересовать только ALIGN - флаг выравнивания.

Также не стоит забывать про регистры отключающие триггеры Шмитта:

Здесь каждый бит соответствует своему каналу. Отключение триггера делает невозможным использование соответствующего пина в качестве пина цифрового ввода-вывода, а оставление его включённым значительно снижает точность измерения АЦП. Триггер отключается записью единицы.

Вариант программы для получения 8-значения с АЦП и вывода его через UART у меня получился таким:

#include <stdint.h>
#include "stm8s103f.h"
#include "uart1.h"

#define LED 5

extern void delay(uint16_t value);
volatile uint8_t adc_value;

void adc_irq(void) __interrupt(22) {
    adc_value=ADC_DRH;
    adc_csr_bres(#7);       // Clear EOC bit
}

void awu_irq(void) __interrupt(1) {
    awu_csr_bres(#5);       //Clear AWUF bit for AWU_CSR
}

int main( void ) {
    //----------- Setup Clock ----------------------
    // fCPU =16MHz
    CLK_CKDIVR=0;
    // enable UART and turn off other  peripherals
    CLK_PCKENR1=0;
    CLK_PCKENR2=0;
    clk_pckenr1_bset(#3);   // enable UART1
    clk_pckenr2_bset(#2);   // enable AWU
    clk_pckenr2_bset(#3);   // enable ADC
    //----------- Setup GPIO -----------------------
    // GPIO setup
    pb_ddr_bset(#LED);
    pb_cr1_bset(#LED);
    // ------- Setup AWU & Buzzer ------------------------
    AWU_TBR = 12;
    AWU_APR = 62;       // =1 sec
    AWU_CSR = 0x10;     // set AWUEN bit for AWUCSR

    BEEP_CSR=0x1e;      // buuuuuuuuuzzzzz (low tone)
    beep_csr_bset(#5);  // Enable BEEP
    delay(100);
    beep_csr_bres(#5);  // Disable BEEP
    //----------- Setup UART1 ----------------------
    // Clear
    UART1_CR1=0;
    UART1_CR2=0;
    UART1_CR3=0;
    UART1_CR4=0;
    UART1_CR5=0;
    UART1_GTR=0;
    UART1_PSCR=0;
    // setup UART1
    uart1_cr1_bset(#5);     // set UARTD, UART1 disable
    // 9600 Baud Rate, when fMASTER=16MHz
    //UART1_BRR2=0x03
    //UART1_BRR1=0x68
    // 115200 Baud Rate, when fMASTER=16MHz
    //UART1_BRR2=0x0b
    //UART1_BRR1=0x08
    // 230400 Baud Rate, when fMASTER=16MHz
    //UART1_BRR2=0x05;
    //UART1_BRR1=0x04;
    // 460800 Baud Rate, when fMASTER=16MHz
    //UART1_BRR2=0x03;
    //UART1_BRR1=0x02;
    // 921600 Baud Rate, when fMASTER=16MHz
    UART1_BRR2=0x01;
    UART1_BRR1=0x01;
    // Trasmission Enable
    uart1_cr2_bset(#3);      // set TEN, Transmission Enable
    // enable UART1
    uart1_cr1_bres(#5);      // clear UARTD, UART1 enable
    //  ------------- ADC Setup --------------------
    adc_cr2_bres(#3);       // Clear ALIGN, Left  Align
    adc_cr1_bres(#1);       // Clear CONT, Single Conersion Mode
    ADC_CSR &= 0xf0;        // Clear CH[3:0] bits
    adc_csr_bset(#1);       // Select AIN2 Channel
    ADC_CR1 |= 0x70;        // Prescaler = fMASTER/18
    adc_cr2_bres(#6);       // Clear EXTTRIG, Disable External Trigger
    ADC_TDRL=0x04;          // Diable Schmitt Trigger
    adc_csr_bset(#5);       // Set EOCIE, enable ADC Interrupt
    // ------------ END SETUP ----------------------
    // main loop
    rim();                  // Enable Interrupts
    for(;;)
    {
        adc_cr1_bset(#0);   // set ADON, Start Of Conversion
        wfi();              // sleep
        pb_odr_bcpl(#LED);
        uart1_print_string("adc: ");
        uart1_print_number((uint16_t)adc_value);
        uart1_send_char('\n');
        delay(1000);
    }
}

Я подключил на пин РС3 потенциометр и пару раз покрутив ручкой получил такой результат:

Как видно, работает вполне нормально, не шумит, однако в программе есть "подводный камень". Если закомментировать строку: AWU_CSR = 0x10, пересобрать и перезалить прошивку, то она работать не будет. Микроконтроллер не будет выходить из режима сна после инструкции wfi. Попытка заменить инструкцию wfi на цикл ожидания установки флага EOC не поможет. При этом если закомментировать на этот раз инструкцию wfi, то программа вроде как работает. Какая взаимосвязь между AWU и АЦП, я честное слово не знаю. Возможно где-то каким-то образом задевается аналоговый watchdog. Могу только уверенно сказать что дело не в микроконтроллере. Аналогичная программа на ассемблере на том же микроконтроллере работает без всякого AWU, но вот при переносе алгоритма на Си, возникла такая проблема. Так что я предполагаю что дело в компиляторе SDCC, что весьма печально.

Вариант программы для 10-битного АЦП, представлен ниже:

#include <stdint.h>
#include "stm8s103f.h"
#include "uart1.h"

#define LED 5

extern void delay(uint16_t value);
volatile uint16_t adc_value;

void adc_irq(void) __interrupt(22) {
    adc_value=ADC_DRL;
    adc_value|=(ADC_DRH<<8);
    adc_value &= 0x03ff;
    adc_csr_bres(#7);       // Clear EOC bit
}

void awu_irq(void) __interrupt(1) {
    awu_csr_bres(#5);       //Clear AWUF bit for AWU_CSR
}

int main( void ) {
    //----------- Setup Clock ----------------------
    // fCPU =16MHz
    CLK_CKDIVR=0;
    // enable UART and turn off other  peripherals
    CLK_PCKENR1=0;
    CLK_PCKENR2=0;
    clk_pckenr1_bset(#3);   // enable UART1
    clk_pckenr2_bset(#2);   // enable AWU
    clk_pckenr2_bset(#3);   // enable ADC
    //----------- Setup GPIO -----------------------
    // GPIO setup
    pb_ddr_bset(#LED);
    pb_cr1_bset(#LED);
    // ------- Setup AWU & Buzzer ------------------------
    AWU_TBR = 12;
    AWU_APR = 62;       // =1 sec
    AWU_CSR = 0x10;     // set AWUEN bit for AWUCSR

    BEEP_CSR=0x1e;      // buuuuuuuuuzzzzz (low tone)
    beep_csr_bset(#5);  // Enable BEEP
    delay(100);
    beep_csr_bres(#5);  // Disable BEEP
    //----------- Setup UART1 ----------------------
    // Clear
    UART1_CR1=0;
    UART1_CR2=0;
    UART1_CR3=0;
    UART1_CR4=0;
    UART1_CR5=0;
    UART1_GTR=0;
    UART1_PSCR=0;
    // setup UART1
    uart1_cr1_bset(#5);     // set UARTD, UART1 disable
    // 9600 Baud Rate, when fMASTER=16MHz
    //UART1_BRR2=0x03
    //UART1_BRR1=0x68
    // 115200 Baud Rate, when fMASTER=16MHz
    //UART1_BRR2=0x0b
    //UART1_BRR1=0x08
    // 230400 Baud Rate, when fMASTER=16MHz
    //UART1_BRR2=0x05;
    //UART1_BRR1=0x04;
    // 460800 Baud Rate, when fMASTER=16MHz
    //UART1_BRR2=0x03;
    //UART1_BRR1=0x02;
    // 921600 Baud Rate, when fMASTER=16MHz
    UART1_BRR2=0x01;
    UART1_BRR1=0x01;
    // Trasmission Enable
    uart1_cr2_bset(#3);      // set TEN, Transmission Enable
    // enable UART1
    uart1_cr1_bres(#5);      // clear UARTD, UART1 enable
    //  ------------- ADC Setup --------------------
    adc_cr2_bset(#3);       // Set ALIGN, Right  Align
    adc_cr1_bres(#1);       // Clear CONT, Single Conersion Mode
    ADC_CSR &= 0xf0;        // Clear CH[3:0] bits
    adc_csr_bset(#1);       // Select AIN2 Channel
    ADC_CR1 |= 0x70;        // Prescaler = fMASTER/18
    adc_cr2_bres(#6);       // Clear EXTTRIG, Disable External Trigger
    ADC_TDRL=0x04;          // Diable Schmitt Trigger
    adc_csr_bset(#5);       // Set EOCIE, enable ADC Interrupt
    // ------------ END SETUP ----------------------
    // main loop
    rim();                  // Enable Interrupts
    for(;;)
    {
        adc_cr1_bset(#0);   // set ADON, Start Of Conversion
        wfi();              // sleep
        pb_odr_bcpl(#LED);
        uart1_print_string("adc: ");
        uart1_print_number((uint16_t)adc_value);
        uart1_send_char('\n');
        delay(1000);
    }
}

Результат работы с тем же потенциометром:

На мой взгляд, для 10-битного АЦП это вполне достойный результат. Шумит только последний бит, да и то, слегка.

3. I2C в режиме мастера, на примере RTC DS1307/DS3231. Инициализация

    Аппаратный I2C интерфейс в STM8:
  • обеспечивает работу шины на скоростях 100 кГц (standart) и 400 кГц (fast);
  • может работать как с 7-битными I2C адресами, так и с 10-битными.

Чтобы, составить полное мнение о возможностях I2C модуля, нужно будет ознакомиться с его конфигурационными регистрами. Мне лично модуль понравился, это мощная и гибкая штука. Сейчас мне хотелось бы рассмотреть самый простой режим его работы: режим мастера на стандартной скорости 100кГц и 7-битным I2C адресом, без использования прерываний (работа в режиме опроса). В качестве слейва я выбрал RTC DS3231, с которым наверное уже все знакомы.

Порядок работы с I2C я бы разделил на инициализацию и рабочий цикл. Думаю, что сначала имеет смысл рассмотреть регистры которые отвечают за инициализацию.

В данном случае, инициализация занимает 8 строчек кода и затрагивает 5 регистров:

    //----------- Setup I2C ------------------------
    i2c_cr1_bres(#0);       // PE=0, disable I2C before setup
    I2C_FREQR= 16;          // peripheral frequence =16MHz
    I2C_CCRH = 0;           // =0
    I2C_CCRL = 80;          // 100kHz for I2C
    i2c_ccrh_bres(#7);      // set standart mode(100кHz)
    i2c_oarh_bres(#7);      // 7-bit address mode
    i2c_oarh_bset(#6);      // see reference manual
    i2c_cr1_bset(#0);       // PE=1, enable I2C
    //------------- End Setup ---------------------

Инициализация I2C модуля состоит из задания скорости работы и переключения в нужный режим: стандартная скорость шины, 7-битный адрес. Переключение в режим мастера и обратно в слейв происходит автоматически, после команд START и STOP.

    Регистры модуля I2C:
  1. Регистр I2C_FREQR служит для указания частоты fMASTER микроконтроллера. Не забывайте, что микроконтроллер может тактироваться от внешнего кварца или генератора, частоту которого сам микроконтроллер не может знать. Здесь ее следует указать. При этом, частота fMASTER для стандартной скорости шины должна быть не менее 1 МГц, и не менее 4 МГц для 400 кГц "быстрого" режима.
  2. Через число СCR в регистровой паре I2C_CCRH:I2C_CCRL, задаётся рабочая частота I2C шины:

    где значением CCR является:

    P e r i o d I 2 C = 2 * C C R * T M A S T E R

    и следовательно:

    C C R = P e r i o d I 2 C 2 * T M A S T E R

    К примеру для частоты I2C шины =100 кГц, и частоты fMASTER = 16 МГц, имеем значение CCR=80:

    C C R = 1 1 0 5 2 * 1 16 * 1 0 6 = 1 0 -5 2 / 16 * 1 0 -6 = 16 * 10 2 = 80

    Проще конечно вычислять CCR как отношение частоты fMASTER к частоте I2C шины:

    C C R = f M A S T E R 2 * f I 2 C

    Кроме значения CCR, в регистре I2C_CCRH содержится флаг F/S который должен быть сброшен в ноль для стандартного режима 100 кГц.
  1. Третий регистр, который понадобится для конфигурации I2C интерфейса, выставляет 7-и или 10-битный адресный режим I2C:
    Для стандартного 7-битного режима, флаг ADDMODE должен быть сброшен, а ADDCONF установлен.
  2. Последний регистр который необходим для конфигурации I2C - это I2C_CR1:

    Здесь сейчас интересен только флаг PE, который включает/выключает работу модуля. Вся конфигурация должна происходить при сброшенном PE флаге!

4. I2C в режиме мастера, на примере RTC DS1307/DS3231. Функция передачи адреса и данных

Функция передачи I2C адреса и записи одного числа у меня получилась такая (параметры функции читаются из стека):

_init_i2c:
    ;----------- Begin  I2C routine ------------------
    ;   Send Address
    bset I2C_CR2,#2         ; set ACK bit
    bset I2C_CR2,#0         ; START
wait_start_tx:              ; wait SB in I2C_SR1
    btjf I2C_SR1, #0, wait_start_tx
    ld a,I2C_SR1            ; Clear SB bit
    ld a, (03,sp)
    ld I2C_DR,a             ; I2C address
wait_adr_tx:
    btjf I2C_SR1,#1, wait_adr_tx
    ld a,I2C_SR1            ; clear ADDR bit
    ld a,I2C_SR3            ; clear ADDR bit
    ld a,(04,sp)
    ld I2C_DR,a             ; DS1307 set address =0 
wait_zero_tx:               ; wait set TXE bit
    btjf I2C_SR1,#7, wait_zero_tx
    bset I2C_CR2,#1         ; STOP
;---------------------------------------------------
    bres I2C_CR2,#7         ; set SWRST
;--------------------------------------------------
    ret

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

Сначала посылается сигнал START, затем адрес устройства, затем один байт данных. После чего посылается СТОП и сброс линии. Функция годится для проверки I2C модуля. Три цикла обеспечивают гарантированное зависание микроконтроллера, если у вас что-то работает не так.

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

    Регистры которые используются в функции:
  1. Второй конфигурационный регистр I2C_CR2:

    здесь флаг START при установке посылает на линию сигнал соответствующий сигнал, после чего I2C модуль переключается к режим мастера. Установка этого флага в режиме мастера, посылает сигнал RESTART, это позволяет заменить последовательности STOP - START, когда переключаются режимы для слейва: запись на чтение и наоборот. DS1307 сигнал RESTART не поддерживает, но другие устройства, например LM75, поддерживают его. Флаг сбрасывается автоматически при установке состояния СТАРТ или при отключении модуля (PE=0).

    Флаг STOP. Установка его посылает на линию соответствующий сигнал, после чего I2C модуль переводится в режим слейва.

    Установка флага SWRST сбрасывает, отпускает линию.

    Флаги POS и ACK используются в режиме чтения. ACK - определяет: посылать ли в ответ на принятый байт ACK или NACK (если флаг ACK установлен, значит посылать, а если флаг ACK сброшен, значит посылать NACK). POS определяет: посылать ACK/NACK текущим или следующим байтом.

  2. Первый флаговый регистр I2C_SR1:

    Здесь, флаг SB устанавливается при сигнале START на линии. Флаг очищается программно чтением регистра I2C_SR1. Флаг очищается аппаратно при отключении модуля (PE=0).

    Флаг ADDR устанавливается при передаче I2C адреса, включая получение отклика ACK. Флаг ADDR НЕ устанавливается при неполучении отклика ACK от слейва! Флаг сбрасывается чтением регистра I2C_SR1 с последующим чтением I2C_SR3. Флаг очищается аппаратно при отключении модуля (PE=0).

    Флаг BTF - завершение операции обмена. Устанавливается в режиме чтения, кода новый байт был принят, но ещё не считан с регистра данных DR. Также устанавливается при передаче, когда был отправлен байт, а регистр данных DR ещё не записан новым значением. Флаг очищается: программно при чтении SR1 регистра, аппаратно при записи или чтении регистра данных DR, или при отключении модуля (PE=0).

    Флаг STOPF - устанавливается аппаратно при обнаружении сигнала стоп на линии. Флаг очищается программно чтением регистра I2C_SR1 с последующим чтением I2C_SR2. Флаг очищается аппаратно при отключении модуля (PE=0).

    Флаг RxNE (регистр данных не пуст) - устанавливается аппаратно при получении (приёме) данных в регистре DR. Флаг очищается программно чтением или записью в регистр DR. Флаг очищается аппаратно при отключении модуля (PE=0).

    Флаг TxE (регистр данных пуст) - устанавливается аппаратно при перемещении байта из регистра данных DR в сдвиговый регистр для передачи на линию. Флаг очищается программно записью в регистр DR. Флаг очищается аппаратно после сигналов START или STOP на линии или при отключении модуля (PE=0).

5. I2C в режиме мастера, на примере RTC DS1307/DS3231. Функция чтения данных

Мощной возможностью STM8 является то, что данные со слейва можно читать порциями по одному, двум, трём байтам или каким-то массивом. Часто при работе по I2C требуется прочитать лишь один или два байта устройства и здесь мы имеем возможность использовать упрощённый алгоритм чтения без циклов.

Отличительной чертой, модуля I2C является то, что сигналы ACK, NACK, STOP передаются "пакетом" вместе с читаемым байтом, и они должны быть сконфигурированы ДО(!) чтения этого байта из регистра DR.

Т.к. сессию чтения мастер завершает передачей сигналов NACK и STOP, порядком выставления флагов для формирования этих сигналов и отличаются алгоритмы с завершением сессии чтении в один, два или три байта.

Завершение сессии в один байт, мне показался наиболее универсальным. С его помощью можно читать как один байт, так и массив байтов. Функция чтения массива из семи байт представлена ниже:

_read_i2c:
;--------------------------------------------------
    bset I2C_CR2,#2         ; set ACK bit
    bset I2C_CR2,#0         ; START
wait_start_rx:              ; wait SB in I2C_SR1
    btjf I2C_SR1, #0, wait_start_rx
    ld a,I2C_SR1            ; Clear SB bit
    ld a,(03,sp)
    inc a
    ld  I2C_DR, a           ; DS1307 address RAED Mode
wait_adr_rx:
    btjf I2C_SR1,#1, wait_adr_rx
    ; --------- READ 7 BYTES --------------------
    bset I2C_CR2,#2         ; send ACK
    ld a,I2C_SR1            ; clear ADDR bit
    ld a,I2C_SR3            ; clear ADDR bit
    clrw y
    ldw x,(04,sp)
    ; --------- READ LOOP ----------------------
wait_read:
    btjf I2C_SR1,#6,wait_read
    ld a,I2C_DR
    ld (x),a
    incw x
    incw y
    cpw y,#06
    jrne wait_read
    ; get last byte
    bres I2C_CR2,#2         ; NACK
    bset I2C_CR2,#1         ; STOP
wait_last:                      ; wait RXNE bit
    btjf I2C_SR1,#6, wait_last
    ld a,I2C_DR             ; get data from DS1307 and clear RXNE bit
    ld (x), a               ; save seconds in RAM

    bres I2C_CR2,#7         ; set SWRST

    ret

Здесь адрес устройства и указатель на массив передаются через стек.

Если из функции убрать цикл чтения шести байт, останется функция чтения одного байта. Как видно, установка флага STOP и сброс флага ACK происходит до чтения последнего байта из регистра DR.

Порядок чтения одного байта в документации представлен следующей диаграммой:


Один, два или три байта можно читать без использования циклов. Например чтобы прочитать текущее время из DS3231 нужно прочитать три байта. Тогда функция чтения будет выглядеть так:

.globl _read_i2c
_read_i2c:
;--------------------------------------------------
    bset I2C_CR2,#2         ; set ACK bit
    bset I2C_CR2,#0         ; START
wait_start_rx:              ; wait SB in I2C_SR1
    btjf I2C_SR1, #0, wait_start_rx
    ld a,I2C_SR1            ; Clear SB bit
    ld a,(03,sp)
    inc a
    ld  I2C_DR, a      ; DS1307 address RAED Mode
    ldw x, (04,sp)
w0:
    btjf I2C_SR1,#1, w0
    ; --------- READ THREE BYTES --------------------
    bset I2C_CR2,#2         ; send ACK
    ld a,I2C_SR1            ; clear ADDR bit
    ld a,I2C_SR3            ; clear ADDR bit
w2:
    btjf I2C_SR1,#2,w2
    bres I2C_CR2,#2         ; NACK
    ld a,I2C_DR
    ld (x),a
    bset I2C_CR2,#1         ; STOP
    ld a,I2C_DR
    incw x
    ld (x),a
w4:
    btjf I2C_SR1,#6,w4
    ld a,I2C_DR
    incw x
    ld (x),a

    bres I2C_CR2,#7         ; set SWRST


    ret

Полная Си-часть программы чтения данных с DS3231:

#include <stdint.h>
#include "stm8s103f.h"
#include "uart1.h"

#define LED 3
#define DS1307_ADR  0x68

uint8_t data[7];

extern void delay(uint16_t value);
extern void init_i2c(uint8_t adr, uint8_t value);
extern void read_i2c(uint8_t adr, uint8_t *values);

int main( void ) {
    //----------- Setup Clock ----------------------
    // fCPU =16MHz
    CLK_CKDIVR=0;
    // enable UART and turn off other  peripherals
    CLK_PCKENR1=0;
    CLK_PCKENR2=0;
    clk_pckenr1_bset(#3);   // enable UART1
    clk_pckenr1_bset(#0);   // enable I2C
    clk_pckenr2_bset(#2);   // enable AWU
    //----------- Setup GPIO -----------------------
    // GPIO setup
    pc_ddr_bset(#LED);      //  PC_DDR|=(1<<LED)
    pc_cr1_bset(#LED);      //  PC_CR1|=(1<<LED)
    // ------- Setup AWU & Buzzer ------------------------
    BEEP_CSR=0x1e;      // buuuuuuuuuzzzzz (low tone)
    beep_csr_bset(#5);  // Enable BEEP
    delay(100);
    beep_csr_bres(#5);  // Disable BEEP
    //----------- Setup UART1 ----------------------
    // Clear
    UART1_CR1=0;
    UART1_CR2=0;
    UART1_CR3=0;
    UART1_CR4=0;
    UART1_CR5=0;
    UART1_GTR=0;
    UART1_PSCR=0;
    // setup UART1
    uart1_cr1_bset(#5);     // set UARTD, UART1 disable
    // 9600 Baud Rate, when fMASTER=16MHz
    //UART1_BRR2=0x03
    //UART1_BRR1=0x68
    // 115200 Baud Rate, when fMASTER=16MHz
    //UART1_BRR2=0x0b
    //UART1_BRR1=0x08
    // 230400 Baud Rate, when fMASTER=16MHz
    //UART1_BRR2=0x05;
    //UART1_BRR1=0x04;
    // 460800 Baud Rate, when fMASTER=16MHz
    //UART1_BRR2=0x03;
    //UART1_BRR1=0x02;
    // 921600 Baud Rate, when fMASTER=16MHz
    UART1_BRR2=0x01;
    UART1_BRR1=0x01;
    // Trasmission Enable
    uart1_cr2_bset(#3);      // set TEN, Transmission Enable
    // enable UART1
    uart1_cr1_bres(#5);      // clear UARTD, UART1 enable
    //----------- Setup I2C ------------------------
    i2c_cr1_bres(#0);       // PE=0, disable I2C before setup
    I2C_FREQR= 16;          // peripheral frequence =16MHz
    I2C_CCRH = 0;           // =0
    I2C_CCRL = 80;          // 100kHz for I2C
    i2c_ccrh_bres(#7);      // set standart mode(100кHz)
    i2c_oarh_bres(#7);      // 7-bit address mode
    i2c_oarh_bset(#6);      // see reference manual
    i2c_cr1_bset(#0);       // PE=1, enable I2C
    //------------- End Setup ---------------------

    // main loop
    for(;;)
    {
        init_i2c((DS1307_ADR<<1), 0x0);
        read_i2c((DS1307_ADR<<1), data);
        pc_odr_bcpl(#LED);
        uart1_print_string("time: ");
        uart1_print_bcd(data[2]);
        uart1_send_char(':');
        uart1_print_bcd(data[1]);
        uart1_send_char(':');
        uart1_print_bcd(data[0]);
        uart1_send_char('\n');
        delay(800);
    }
}

Хочу обратить внимание, что светодиод для индикации рабочего цикла был перенесён с PB5 на PC3, т.к. пин PB5 занят I2C линией.

Текст функций init_i2c() и read_i2c() был представлен выше.

Результат работы в окне программы minicom:


Скачать полные исходники со сборочными файлам и скомпилированными прошивками, можно по этой ссылке .

поделиться: