circuitpython/ports/espressif/common-hal/ps2io/Ps2.c

394 lines
10 KiB
C

/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2020 microDev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "common-hal/ps2io/Ps2.h"
#include "shared-bindings/ps2io/Ps2.h"
#include "supervisor/port.h"
#include "shared-bindings/microcontroller/__init__.h"
#define STATE_IDLE 0
#define STATE_RECV 1
#define STATE_RECV_PARITY 2
#define STATE_RECV_STOP 3
#define STATE_RECV_ERR 10
#define ERROR_STARTBIT 0x01
#define ERROR_TIMEOUT 0x02
#define ERROR_PARITY 0x04
#define ERROR_STOPBIT 0x08
#define ERROR_BUFFER 0x10
#define ERROR_TX_CLKLO 0x100
#define ERROR_TX_CLKHI 0x200
#define ERROR_TX_ACKDATA 0x400
#define ERROR_TX_ACKCLK 0x800
#define ERROR_TX_RTS 0x1000
#define ERROR_TX_NORESP 0x2000
void ps2_reset(void) {
gpio_uninstall_isr_service();
}
static void delay_us(uint32_t t) {
common_hal_mcu_delay_us(t);
}
/* interrupt handling */
static void IRAM_ATTR ps2_interrupt_handler(void *self_in) {
// Grab the current time first.
uint64_t current_tick = port_get_raw_ticks(NULL);
ps2io_ps2_obj_t *self = self_in;
int data_bit = gpio_get_level(self->data_pin) ? 1 : 0;
// test for timeout
if (self->state != STATE_IDLE) {
int64_t diff_ms = current_tick - self->last_raw_ticks;
if (diff_ms > 1) { // a.k.a. > 1.001ms
self->last_errors |= ERROR_TIMEOUT;
self->state = STATE_IDLE;
}
}
self->last_raw_ticks = current_tick;
if (self->state == STATE_IDLE) {
self->bits = 0;
self->parity = false;
self->bitcount = 0;
self->state = STATE_RECV;
if (data_bit) {
// start bit should be 0
self->last_errors |= ERROR_STARTBIT;
self->state = STATE_RECV_ERR;
} else {
self->state = STATE_RECV;
}
} else if (self->state == STATE_RECV) {
if (data_bit) {
self->bits |= data_bit << self->bitcount;
self->parity = !self->parity;
}
++self->bitcount;
if (self->bitcount >= 8) {
self->state = STATE_RECV_PARITY;
}
} else if (self->state == STATE_RECV_PARITY) {
++self->bitcount;
if (data_bit) {
self->parity = !self->parity;
}
if (!self->parity) {
self->last_errors |= ERROR_PARITY;
self->state = STATE_RECV_ERR;
} else {
self->state = STATE_RECV_STOP;
}
} else if (self->state == STATE_RECV_STOP) {
++self->bitcount;
if (!data_bit) {
self->last_errors |= ERROR_STOPBIT;
} else if (self->waiting_cmd_response) {
self->cmd_response = self->bits;
self->waiting_cmd_response = false;
} else if (self->bufcount >= sizeof(self->buffer)) {
self->last_errors |= ERROR_BUFFER;
} else {
self->buffer[self->bufposw] = self->bits;
self->bufposw = (self->bufposw + 1) % sizeof(self->buffer);
self->bufcount++;
}
self->state = STATE_IDLE;
} else if (self->state == STATE_RECV_ERR) {
// just count the bits until idle
if (++self->bitcount >= 10) {
self->state = STATE_IDLE;
}
}
}
static void enable_interrupt(ps2io_ps2_obj_t *self) {
// turn on falling edge interrupt
gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
gpio_set_intr_type(self->clk_pin, GPIO_INTR_NEGEDGE);
gpio_isr_handler_add(self->clk_pin, ps2_interrupt_handler, (void *)self);
}
static void disable_interrupt(ps2io_ps2_obj_t *self) {
// turn off fallling edge interrupt
gpio_isr_handler_remove(self->clk_pin);
}
static void resume_interrupt(ps2io_ps2_obj_t *self) {
self->state = STATE_IDLE;
gpio_isr_handler_add(self->clk_pin, ps2_interrupt_handler, (void *)self);
}
/* gpio handling */
static void clk_hi(ps2io_ps2_obj_t *self) {
// external pull-up
gpio_set_direction(self->clk_pin, GPIO_MODE_INPUT);
}
static bool wait_clk_lo(ps2io_ps2_obj_t *self, uint32_t us) {
clk_hi(self);
delay_us(1);
while (gpio_get_level(self->clk_pin) && us) {
--us;
delay_us(1);
}
return us;
}
static bool wait_clk_hi(ps2io_ps2_obj_t *self, uint32_t us) {
clk_hi(self);
delay_us(1);
while (!gpio_get_level(self->clk_pin) && us) {
--us;
delay_us(1);
}
return us;
}
static void clk_lo(ps2io_ps2_obj_t *self) {
gpio_set_direction(self->clk_pin, GPIO_MODE_OUTPUT);
gpio_set_level(self->clk_pin, 0);
}
static void data_hi(ps2io_ps2_obj_t *self) {
// external pull-up
gpio_set_direction(self->data_pin, GPIO_MODE_INPUT);
}
static bool wait_data_lo(ps2io_ps2_obj_t *self, uint32_t us) {
data_hi(self);
delay_us(1);
while (gpio_get_level(self->data_pin) && us) {
--us;
delay_us(1);
}
return us;
}
static bool wait_data_hi(ps2io_ps2_obj_t *self, uint32_t us) {
data_hi(self);
delay_us(1);
while (!gpio_get_level(self->data_pin) && us) {
--us;
delay_us(1);
}
return us;
}
static void data_lo(ps2io_ps2_obj_t *self) {
gpio_set_direction(self->data_pin, GPIO_MODE_OUTPUT);
gpio_set_level(self->data_pin, 0);
}
static void idle(ps2io_ps2_obj_t *self) {
clk_hi(self);
data_hi(self);
}
static void inhibit(ps2io_ps2_obj_t *self) {
clk_lo(self);
data_hi(self);
}
/* ps2io module functions */
void common_hal_ps2io_ps2_construct(ps2io_ps2_obj_t *self,
const mcu_pin_obj_t *data_pin, const mcu_pin_obj_t *clk_pin) {
self->clk_pin = (gpio_num_t)clk_pin->number;
self->data_pin = (gpio_num_t)data_pin->number;
self->state = STATE_IDLE;
self->bufcount = 0;
self->bufposr = 0;
self->bufposw = 0;
self->waiting_cmd_response = false;
idle(self);
enable_interrupt(self);
claim_pin(clk_pin);
claim_pin(data_pin);
}
bool common_hal_ps2io_ps2_deinited(ps2io_ps2_obj_t *self) {
return self->clk_pin == GPIO_NUM_MAX;
}
void common_hal_ps2io_ps2_deinit(ps2io_ps2_obj_t *self) {
if (common_hal_ps2io_ps2_deinited(self)) {
return;
}
gpio_uninstall_isr_service();
reset_pin_number(self->clk_pin);
reset_pin_number(self->data_pin);
self->clk_pin = GPIO_NUM_MAX;
self->data_pin = GPIO_NUM_MAX;
}
uint16_t common_hal_ps2io_ps2_get_len(ps2io_ps2_obj_t *self) {
return self->bufcount;
}
int16_t common_hal_ps2io_ps2_popleft(ps2io_ps2_obj_t *self) {
common_hal_mcu_disable_interrupts();
if (self->bufcount <= 0) {
common_hal_mcu_enable_interrupts();
return -1;
}
uint8_t b = self->buffer[self->bufposr];
self->bufposr = (self->bufposr + 1) % sizeof(self->buffer);
self->bufcount -= 1;
common_hal_mcu_enable_interrupts();
return b;
}
uint16_t common_hal_ps2io_ps2_clear_errors(ps2io_ps2_obj_t *self) {
common_hal_mcu_disable_interrupts();
uint16_t errors = self->last_errors;
self->last_errors = 0;
common_hal_mcu_enable_interrupts();
return errors;
}
// Based upon TMK implementation of PS/2 protocol
// https://github.com/tmk/tmk_keyboard/blob/master/tmk_core/protocol/ps2_interrupt.c
int16_t common_hal_ps2io_ps2_sendcmd(ps2io_ps2_obj_t *self, uint8_t b) {
disable_interrupt(self);
/* terminate a transmission if we have */
inhibit(self);
delay_us(100);
/* RTS and start bit */
data_lo(self);
clk_hi(self);
if (!wait_clk_lo(self, 10000)) {
self->last_errors |= ERROR_TX_RTS;
goto ERROR;
}
bool parity = true;
for (uint8_t i = 0; i < 8; i++) {
delay_us(15);
if (b & (1 << i)) {
parity = !parity;
data_hi(self);
} else {
data_lo(self);
}
if (!wait_clk_hi(self, 50)) {
self->last_errors |= ERROR_TX_CLKHI;
goto ERROR;
}
if (!wait_clk_lo(self, 50)) {
self->last_errors |= ERROR_TX_CLKLO;
goto ERROR;
}
}
/* Parity bit */
delay_us(15);
if (parity) {
data_hi(self);
} else {
data_lo(self);
}
if (!wait_clk_hi(self, 50)) {
self->last_errors |= ERROR_TX_CLKHI;
goto ERROR;
}
if (!wait_clk_lo(self, 50)) {
self->last_errors |= ERROR_TX_CLKLO;
goto ERROR;
}
/* Stop bit */
delay_us(15);
data_hi(self);
/* Ack */
if (!wait_data_lo(self, 50)) {
self->last_errors |= ERROR_TX_ACKDATA;
goto ERROR;
}
if (!wait_clk_lo(self, 50)) {
self->last_errors |= ERROR_TX_ACKCLK;
goto ERROR;
}
/* wait for idle state */
if (!wait_clk_hi(self, 50)) {
self->last_errors |= ERROR_TX_ACKCLK;
goto ERROR;
}
if (!wait_data_hi(self, 50)) {
self->last_errors |= ERROR_TX_ACKDATA;
goto ERROR;
}
/* Wait for response byte */
self->waiting_cmd_response = true;
idle(self);
resume_interrupt(self);
for (int i = 0; i < 25; ++i) {
delay_us(1000);
common_hal_mcu_disable_interrupts();
bool has_response = !self->waiting_cmd_response;
uint8_t response = self->cmd_response;
common_hal_mcu_enable_interrupts();
if (has_response) {
return response;
}
}
/* No response */
common_hal_mcu_disable_interrupts();
self->waiting_cmd_response = false;
self->last_errors |= ERROR_TX_NORESP;
common_hal_mcu_enable_interrupts();
return -1;
/* Other errors */
ERROR:
idle(self);
resume_interrupt(self);
return -1;
}