Bitbang not reliable for uart

hustenhabas
Posts: 18
Joined: Tue Sep 14, 2021 2:18 pm

Bitbang not reliable for uart

Postby hustenhabas » Wed Oct 27, 2021 5:12 pm

Hello, i have all 3 HW uarts already allocated and i need a 4th.

My code works but sometimes the hardware timer interrupt event queue gets delayed and the message is not sent or read correctly. Im using xQueueSendFromISR and xQueueReceive.

The images attached shows the bitbanged singal when its correct and when it gets delayed and the information is missed.

How can i make sure the interrupt get called reliably? here's the code:

Code: Select all

/* General Purpose Timer example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/timer.h"
#include "driver/gpio.h"
#define TEST_INTR              1
#define GPIO_OUTPUT            15
#define TIMER_DIVIDER         (2)  //  Hardware timer clock divider
#define TIMER_SCALE           (TIMER_BASE_CLK / TIMER_DIVIDER)  // convert counter value to seconds
#ifdef TEST_INTR
static int count = 0;
#endif
typedef struct {
    int timer_group;
    int timer_idx;
    int alarm_interval;
    bool auto_reload;
} example_timer_info_t;

/**
 * @brief A sample structure to pass events from the timer ISR to task
 *
 */
typedef struct {
    example_timer_info_t info;
    uint64_t timer_counter_value;
} example_timer_event_t;

static xQueueHandle s_timer_queue;
static void (* user_hw_timer_cb)(void) = NULL;
/*
 * A simple helper function to print the raw timer counter value
 * and the counter value converted to seconds
 */

static void IRAM_ATTR hw_timer_task(void* arg) {
    while (1) {
        example_timer_event_t evt;
        xQueueReceive(s_timer_queue, &evt, NULL);
        
        if (user_hw_timer_cb != NULL) {
            (*(user_hw_timer_cb))();
        }
#ifdef TEST_INTR
        gpio_set_level(GPIO_OUTPUT, count & 1);
        count++;
#endif
    }
}
static bool IRAM_ATTR timer_group_isr_callback(void *args)
{
    BaseType_t high_task_awoken = pdFALSE;
    example_timer_info_t *info = (example_timer_info_t *) args;

    uint64_t timer_counter_value = timer_group_get_counter_value_in_isr(info->timer_group, info->timer_idx);

    /* Prepare basic event data that will be then sent back to task */
    example_timer_event_t evt = {
        .info.timer_group = info->timer_group,
        .info.timer_idx = info->timer_idx,
        .info.auto_reload = info->auto_reload,
        .info.alarm_interval = info->alarm_interval,
        .timer_counter_value = timer_counter_value
    };

    if (!info->auto_reload) {
        timer_counter_value += info->alarm_interval * TIMER_SCALE;
        timer_group_set_alarm_value_in_isr(info->timer_group, info->timer_idx, timer_counter_value);
    }

    /* Now just send the event data back to the main program task */
    xQueueSendFromISR(s_timer_queue, &evt, &high_task_awoken);

    return high_task_awoken == pdTRUE; // return whether we need to yield at the end of ISR
}

/**
 * @brief Initialize selected timer of timer group
 *
 * @param group Timer Group number, index from 0
 * @param timer timer ID, index from 0
 * @param auto_reload whether auto-reload on alarm event
 * @param timer_interval_sec interval of alarm
 */
static void example_tg_timer_init(int group, int timer, bool auto_reload, int timer_interval_usec)
{
    /* Select and initialize basic parameters of the timer */
    timer_config_t config = {
        .divider = TIMER_DIVIDER,
        .counter_dir = TIMER_COUNT_UP,
        .counter_en = TIMER_PAUSE,
        .alarm_en = TIMER_ALARM_EN,
        .auto_reload = auto_reload,
    }; // default clock source is APB
    timer_init(group, timer, &config);

    /* Timer's counter will initially start from value below.
       Also, if auto_reload is set, this value will be automatically reload on alarm */
    timer_set_counter_value(group, timer, 0);

    /* Configure the alarm value and the interrupt on alarm. */
    timer_set_alarm_value(group, timer, timer_interval_usec * TIMER_SCALE/1000000);
    timer_enable_intr(group, timer);

    example_timer_info_t *timer_info = calloc(1, sizeof(example_timer_info_t));
    timer_info->timer_group = group;
    timer_info->timer_idx = timer;
    timer_info->auto_reload = auto_reload;
    timer_info->alarm_interval = timer_interval_usec;
    timer_isr_callback_add(group, timer, timer_group_isr_callback, timer_info, 0);

    // timer_start(group, timer);
}

void hw_timer_init(int us)
{
#ifdef TEST_INTR
    gpio_config_t config = {
        .intr_type = GPIO_PIN_INTR_DISABLE,
        .pull_down_en = 0,
        .pull_up_en = 0,
        .pin_bit_mask = (1ULL<<GPIO_OUTPUT),
        .mode = GPIO_MODE_OUTPUT
    };
    gpio_config(&config);
#endif
    s_timer_queue = xQueueCreate(10, sizeof(example_timer_event_t));

    example_tg_timer_init(TIMER_GROUP_0, TIMER_0, true, us);
    static TaskHandle_t xHandle;
    // example_tg_timer_init(TIMER_GROUP_1, TIMER_0, false, 5);
    xTaskCreate(hw_timer_task, "hw_timer_task", 8192, NULL, 10, &xHandle);
    
}
void  hw_timer_set_func(void (* user_hw_timer_cb_set)(void))
{
    user_hw_timer_cb = user_hw_timer_cb_set;
}
void hw_timer_disarm() {
    timer_pause(TIMER_GROUP_0, TIMER_0);
}
void hw_timer_arm() {
    timer_start(TIMER_GROUP_0, TIMER_0);
}

Code: Select all

#include <stdio.h>
#include "tty.h"
#include "esp_log.h"
#include "hw_timer.h"
#include "esp_timer.h"
#include "gpio_drv.h"
#include "driver/timer.h"
#include "uart_drv.h"
#include "soc/soc.h"
#include "soc/uart_periph.h"
#define TTY_BSIZE           512
#define TTY_BITBANG_BITS    10
#define TTY_TIMEOUT         100000

#define UART0               0
#define UART0_RX_PIN        8
#define UART0_TX_PIN        9

#define UART1               1
#define UART1_RX_PIN        -1
#define UART1_TX_PIN        -1

#define UART2               2
#define UART2_RX_PIN        35
#define UART2_TX_PIN        15

#define UART3               3
#define UART3_RX_PIN        14
#define UART3_TX_PIN        12
#define UART3_RX_INTR       3

#define GPIO_INPUT_PIN_SEL (1ULL<<UART3_RX_PIN) | (1ULL<<UART2_RX_PIN) | (1ULL<<UART1_RX_PIN)
#define GPIO_OUTPUT_PIN_SEL (1ULL<<UART3_TX_PIN) | (1ULL<<UART2_TX_PIN) | (1ULL<<UART1_TX_PIN) 


#define TTY_FIFO_CNT(head,tail,size) \
    ((tail >= head) ? (tail - head) : (size - (head - tail)))
#define TTY_FIFO_SPACE(head,tail,size) \
    ((tail >= head) ? (size - (tail - head) - 1) : (head - tail - 1))

typedef struct _tty_dev {
    int tty;
    tty_func_t func;
    void *user_data;
    uint8_t recv_buf[TTY_BSIZE];
    uint16_t recv_head;
    uint16_t recv_tail;
    uint32_t recv_xsr;
    uint8_t recv_bits;
    uint8_t xmit_buf[TTY_BSIZE];
    uint16_t xmit_head;
    uint16_t xmit_tail;
    uint32_t xmit_xsr;
    uint8_t xmit_bits;
} tty_dev_t;

static portMUX_TYPE my_mutex = portMUX_INITIALIZER_UNLOCKED;
static int cnt = 0;
static tty_dev_t tty_dev[TTY_NUM_DEV] = { 
    {
        .tty = 0,
        .func = NULL,
        .user_data = NULL,
    },
    {   
        .tty = 1,
        .func = NULL,
        .user_data = NULL,
    },
    {   
        .tty = 2,
        .func = NULL,
        .user_data = NULL,
    },
    {   
        .tty = 3,
        .func = NULL,
        .user_data = NULL,
    }
};

static bool hw_timer_enabled = false;
static uint32_t hw_timer_counter = 0;
static esp_timer_handle_t tty_task_timer;
//static xQueueHandle sw_timer_queue;

void tty_hw_timer_enable(void)
{
    if (!hw_timer_enabled) {
        hw_timer_enabled = true;
        hw_timer_counter = 0;
        hw_timer_arm();
    }
}

void tty_hw_timer_disable(void)
{
    if (hw_timer_enabled) {
        hw_timer_enabled = false;
        hw_timer_counter = 0;
        hw_timer_disarm();
    }
}

static void tty_gpio_interrupt(int intr, void *user_data)
{
    tty_dev_t *p = user_data;
    //ESP_LOGI("TTY", "iterrupto via intr: %d", intr);
    taskENTER_CRITICAL(&my_mutex);

    if (p->tty == UART3) {
        p->recv_bits = TTY_BITBANG_BITS;
        tty_hw_timer_enable();
    }

    taskEXIT_CRITICAL(&my_mutex);
}

static void tty_hw_timeout(void)
{
    // ets_printf("hwtiem\n");
    
    
    tty_dev_t *p;
    uint32_t bit;
    int rc = 0;
    // ets_printf("hwtime\n");
    taskENTER_CRITICAL(&my_mutex);
    
    /* Only receive */
    // p = &tty_dev[UART1];
    // if (p->func) {
        
    // }
    //     if (!(hw_timer_counter & 1)) {
    //         if (p->recv_bits > 0) {
    //             p->recv_bits--;
    //             /* Read GPIO */
    //             bit = GPIO_INPUT_GET(UART1_RX_PIN);
    //             p->recv_xsr >>= 1;
    //             p->recv_xsr |= (bit << (TTY_BITBANG_BITS - 1));
    //             if (!p->recv_bits) {
    //                 /* Check stop bit */
    //                 if (bit) {
    //                     p->recv_xsr >>= 1;
    //                     p->recv_buf[p->recv_tail] = p->recv_xsr & 0xFF;
    //                     p->recv_tail = ++p->recv_tail & (TTY_BSIZE - 1);
    //                 }
    //                 p->recv_xsr = 0;
    //                 /* Enable interrupt */
    //                 gpio_pin_intr_state_set(UART1_RX_PIN, GPIO_PIN_INTR_NEGEDGE);
    //             }
    //         }
    //     }
    //     /* Work pending */
    //     if (p->recv_bits) {
    //         rc++;
    //     }
    // }

    /* Transmit and Receive */
    p = &tty_dev[UART3];
    if (p->func) {
        if (!(hw_timer_counter & 1)) {
            if (p->recv_bits > 0) {
                p->recv_bits--;
                /* Read GPIO */
                bit = gpio_get_level(UART3_RX_PIN);
                //ESP_LOGI("tty","got bit %d", bit);
                p->recv_xsr >>= 1;
                p->recv_xsr |= (bit << (TTY_BITBANG_BITS - 1));
                if (!p->recv_bits) {
                    /* Check stop bit */
                    if (bit) {
                        p->recv_xsr >>= 1;
                        p->recv_buf[p->recv_tail] = p->recv_xsr & 0xFF;
                        p->recv_tail = (p->recv_tail+1) & (TTY_BSIZE - 1);
                    }
                    p->recv_xsr = 0;
                    /* Enable interrupt */
                    gpio_set_intr_type(UART3_RX_PIN, GPIO_PIN_INTR_NEGEDGE);
                }
            }
            if (p->xmit_head != p->xmit_tail) {
                if (!p->xmit_bits) {
                    p->xmit_xsr = p->xmit_buf[p->xmit_head];
                    /* Put start and stop bit */
                    p->xmit_xsr <<= 1;
                    p->xmit_xsr |= (1 << (TTY_BITBANG_BITS - 1));
                    p->xmit_bits = TTY_BITBANG_BITS;
                }
                /* Write GPIO */
                bit = (p->xmit_xsr >> (TTY_BITBANG_BITS - p->xmit_bits)) & 1;
                // gpio_set_level(12, cnt & 1);
                // cnt++;
                // ets_printf("a\n");
                gpio_set_level(UART3_TX_PIN, bit);
                p->xmit_bits--;
                if (!p->xmit_bits)
                    p->xmit_head = (p->xmit_head + 1) & (TTY_BSIZE - 1);
            }
        }
        /* Work pending */
        if (p->recv_bits || p->xmit_bits || p->xmit_head != p->xmit_tail) {
            rc++;
        }
    }

    hw_timer_counter++;

    if (!rc)
        tty_hw_timer_disable();

    taskEXIT_CRITICAL(&my_mutex);
    
}


static int 
tty_read_fifo(int tty, char *buf, int len)
{
    tty_dev_t *p;
    int size;
    int i;

    p = &tty_dev[tty];

    size = TTY_FIFO_CNT(p->recv_head, p->recv_tail, TTY_BSIZE);
    // printf("fifo size: %d\n", size);
    if (size) {
        if (size > len) size = len;
        for (i = 0; i < size; i++) {
            buf[i] = p->recv_buf[p->recv_head];
            p->recv_head = (p->recv_head+1) & (TTY_BSIZE - 1);
        }
    }

    return size;
}


static int tty_write_fifo(int tty, unsigned char *buf, int len)
{
    tty_dev_t *p;
    int rc = -1;
    int size;
    int i;

    p = &tty_dev[tty];

    size = TTY_FIFO_SPACE(p->xmit_head, p->xmit_tail, TTY_BSIZE);
    if (size >= len) {
        for (i = 0; i < len; i++) {
            p->xmit_buf[p->xmit_tail] = buf[i];
            p->xmit_tail = (p->xmit_tail + 1) & (TTY_BSIZE - 1);
        }
        rc = OK;
    }

    /* Enable UART3 bitbang */
    if (tty == UART3)
        tty_hw_timer_enable();

    return rc;
}

static void tty_task(void)
{
    char buf[TTY_BSIZE];
    tty_dev_t *p;
    int size;
    int len;
    //int i;
    // ESP_LOGD("TTY", "tty_task");
    p = &tty_dev[UART0];
    if (p->func) {
        ESP_ERROR_CHECK(uart_get_buffered_data_len(UART0, (size_t*)&len));
        size = uart_read_bytes(UART0, buf, len, 50);
        if (size) {
            // ESP_LOGI("TTY", "read %d bytes from UART %d ", size, UART1);
            // ESP_LOG_BUFFER_HEX("TTY", buf, len);
            p->func(UART0, buf, len, p->user_data);
        }
    }
    p = &tty_dev[UART1];
    if (p->func) {
        ESP_ERROR_CHECK(uart_get_buffered_data_len(UART1, (size_t*)&len));
        size = uart_read_bytes(UART1, buf, len, 50);
        if (size) {
            ESP_LOGI("TTY", "read %d bytes from UART %d ", size, UART1);
            // ESP_LOG_BUFFER_HEX("TTY", buf, len);
            p->func(UART1, buf, len, p->user_data);
        }
    }
    p = &tty_dev[UART2];
    if (p->func) {
        ESP_ERROR_CHECK(uart_get_buffered_data_len(UART2, (size_t*)&len));
        size = uart_read_bytes(UART2, buf, len, 50);
        if (size) {
            ESP_LOGI("TTY", "read %d bytes from UART%d", size, UART2);

            p->func(UART2, buf, len, p->user_data);
        }
    }

    p = &tty_dev[UART3];
    if (p->func) {
        //printf("reading fifo\n");
        size = tty_read_fifo(UART3, buf, sizeof(buf));
        // ESP_LOGI("TTY", "reading from uart3 size %d", size);
        if (size)
            p->func(UART3, buf, size, p->user_data);
    }
    
}




int tty_init(void)
{
    // ESP_LOGI("TTY", "TTY init");

    gpio_config_t io_conf;
    io_conf.intr_type = GPIO_INTR_DISABLE;
    io_conf.mode = GPIO_MODE_OUTPUT;
    io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL;
    io_conf.pull_down_en = 0;
    io_conf.pull_up_en = 0;
    gpio_config(&io_conf);

    gpio_config_t io_conf2;
    io_conf2.intr_type = GPIO_INTR_DISABLE;
    io_conf2.mode = GPIO_MODE_INPUT;
    io_conf2.pin_bit_mask = GPIO_INPUT_PIN_SEL;
    io_conf2.pull_down_en = 0;
    io_conf2.pull_up_en = 0;
    gpio_config(&io_conf2);

    ESP_LOGI("tty", "initialized gpios");
    uart_dev uart0_dev = {
        .baud_rate = BIT_RATE_9600,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bit = UART_STOP_BITS_1,
        .flow_control = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_APB,
        .uart_id = UART0,
        .tx_pin = UART0_TX_PIN,
        .rx_pin = UART0_RX_PIN,
        .cts = -1,
        .rts = -1,
    };
    uart_dev uart1_dev = {
        .baud_rate = BIT_RATE_9600,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bit = UART_STOP_BITS_1,
        .flow_control = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_APB,
        .uart_id = UART1,
        .tx_pin = UART1_TX_PIN,
        .rx_pin = UART1_RX_PIN,
        .cts = -1,
        .rts = -1,
    };

    uart_dev uart2_dev = {
        .baud_rate = BIT_RATE_9600,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bit = UART_STOP_BITS_1,
        .flow_control = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_APB,
        .uart_id = UART2,
        .tx_pin = UART2_TX_PIN,
        .rx_pin = UART2_RX_PIN,
        .cts = -1,
        .rts = -1,
    };
    // uart_initialize(&uart0_dev);
    uart_initialize(&uart1_dev);
    uart_initialize(&uart2_dev);

    ESP_LOGI("tty", "initialized uarts");
    // tty_event.sig = 1;
    // tty_event.par = 0;
    // os_timer_setfn(&tty_timer, (os_timer_func_t *)tty_task, &tty_event);
    // os_timer_arm(&tty_timer, TTY_TIMEOUT, TRUE);

    gpio_set_level(UART3_TX_PIN, 1);
    gpio_drv_init();
    // ESP_LOGI("tty", "configed gpio");

    hw_timer_set_func(tty_hw_timeout);

    hw_timer_init(52);
    ESP_LOGI("tty", "hw timer init 0");

    const esp_timer_create_args_t tty_task_timer_args = {
            .callback = &tty_task,
            /* name is optional, but may help identify the timer when debugging */
            .name = "tty task"
    };
    
    ESP_ERROR_CHECK(esp_timer_create(&tty_task_timer_args, &tty_task_timer));
    ESP_ERROR_CHECK(esp_timer_start_periodic(tty_task_timer, TTY_TIMEOUT));
    // sw_timer_set_func(tty_task);
    // ESP_LOGI("tty", "sw timer setfunc");
    // hw_timer_arm(TTY_TIMEOUT,true, TIMER_GROUP_1);
    // ESP_LOGI("tty", "armed sw timer");
    return 1;
}


void tty_release(void)
{
    ESP_LOGI("tty", "releasing");

    tty_close(UART0);
    tty_close(UART1);
    tty_close(UART2);
    esp_timer_delete(tty_task_timer);
    ESP_LOGI("tty", "closed uarts");
    // tty_close(UART3);
    // hw_timer_release();
    ESP_LOGI("tty", "timers disarmed");
    gpio_drv_release();
    ESP_LOGI("tty", "released");
}


int tty_open(int tty, tty_func_t func,
             void *user_data)
{
    tty_dev_t *p;

    if (tty >= TTY_NUM_DEV) return -1;

    ESP_LOGI("TTY", "%d open", tty);

    if (func) {
        p = &tty_dev[tty];
        switch (tty) {
            case UART0:
                uart_open(UART0);
                break;
            case UART1:
                uart_open(UART1);
                break;
            case UART2:
                uart_open(UART2);
                break;
            case UART3:
                gpio_interrupt_open(UART3_RX_INTR, UART3_RX_PIN,
                                    GPIO_PIN_INTR_NEGEDGE, GPIO_INTR_DISABLED,
                                    tty_gpio_interrupt, p); 
                break;
        }
        p->func = func;
        p->user_data = user_data;
        p->recv_head = p->recv_tail = 0;
        p->recv_xsr = 0;
        p->recv_bits = 0;
        p->xmit_head = p->xmit_tail = 0;
        p->xmit_xsr = 0;
        p->xmit_bits = 0;
    }

    return 0;
}


int tty_close(int tty)
{
    tty_dev_t *p;

    if (tty >= TTY_NUM_DEV) return -1;

    ESP_LOGI("TTY", "TTY: %d close", tty);

    p = &tty_dev[tty];
    switch (tty) {
        case UART0:
            uart_close(tty);
            break;
        case UART1:
            uart_close(tty);
            break;
        case UART2:
            uart_close(tty);
            break;
        case UART3:
            gpio_interrupt_close(UART3);
            break;
    }
    p->func = NULL;
    p->user_data = NULL;
    p->recv_head = p->recv_tail = 0;
    p->recv_xsr = 0;
    p->recv_bits = 0;
    p->xmit_head = p->xmit_tail = 0;
    p->xmit_xsr = 0;
    p->xmit_bits = 0;

    return 0;
}


int tty_write(int tty, unsigned char *data, int len)
{
    
    tty_dev_t *p;
    int rc = -1;

    // int i;

    if (tty >= TTY_NUM_DEV) return -1;


    ESP_LOGD("TTY", "TTY: %d write %d bytes", tty, len);


    p = &tty_dev[tty];
    //ESP_LOGI("TTY", "tty = %d and uart is %d", tty, UART3);
    switch (tty) {
        case UART0:
            rc = uart_write_bytes(tty, data, len);
            // for (i = 0; i < len; i++)
            //     rc = uart_tx_one_char(tty, data[i]);
            break;
        case UART1:
            ESP_LOG_BUFFER_HEX("TTY", data, len);
            rc = uart_write_bytes(tty, data, len);
            break;
        case UART2:
            // ESP_LOG_BUFFER_HEX("TTY", data, len);
            rc = uart_write_bytes(tty, data, len);
            break;
        case UART3:
            // ESP_LOGI("TTY", "writing command to UART3: ");
            // ESP_LOG_BUFFER_HEX("TTY", data, len);
            // ESP_LOGE("TTY", "writing command to UART3: ");
            //printf("writing to fifo\n");
            rc = tty_write_fifo(tty, data, len);
            break;
    }

    return rc;
}


int tty_tx_fifo_size(int tty)
{
    tty_dev_t *p;
    int size = 0;

    if (tty >= UART0 && tty <= UART2) {
        // /* UART FIFO */
        size = ((READ_PERI_REG(UART_STATUS_REG(tty)) >> UART_TXFIFO_CNT_S) & UART_TXFIFO_CNT);
        return size;
    } else if (tty == UART3) {
        p = &tty_dev[tty];
        size = TTY_FIFO_CNT(p->xmit_head, p->xmit_tail, TTY_BSIZE);
        return size;
    }

    return 0;
}


void tty_set_baudrate(int tty, int baudrate)
{
    if (tty >= 0 && tty < 3) {
        uart_set_baudrate(tty, baudrate);
    }
}
uint32_t tty_get_baudrate(int tty)
{
    uint32_t baud;
    if (tty >= 0 && tty < 3) {
        uart_get_baudrate(tty, &baud);
        return baud;
    }
    return 0;
}


void tty_set_parity(int tty, int mode)
{
   if (tty >= 0 && tty < 3) {
        uart_set_parity(tty, mode);
    }
}
Attachments
WhatsApp Image 2021-10-27 at 14.03.30 (1).jpeg
WhatsApp Image 2021-10-27 at 14.03.30 (1).jpeg (190.8 KiB) Viewed 1573 times
WhatsApp Image 2021-10-27 at 14.03.30.jpeg
WhatsApp Image 2021-10-27 at 14.03.30.jpeg (178.85 KiB) Viewed 1573 times

Who is online

Users browsing this forum: Bing [Bot] and 125 guests