PicoCalc/Code/picocalc_keyboard/picocalc_keyboard.ino
shtirlic f58e85e10e
Fix i2c slave scl freeze if read address first recieved
If i2c master performs discovery via address read, stm32 has no data to
send for the masterin requestEvent. Send the zero byte if we got
requestEvent early, for example on i2c address read.
2025-04-19 14:17:17 +07:00

572 lines
16 KiB
C++

#include <Arduino.h>
#define XPOWERS_CHIP_AXP2101
#include <Wire.h>
#include "XPowersLib.h"
//---------------------------------------
#include "backlight.h"
#include "conf_app.h"
#include "fifo.h"
#include "keyboard.h"
#include "pins.h"
#include "port.h"
#include "reg.h"
#include "battery.h"
#define DEBUG_UART
TwoWire Wire2 = TwoWire(CONFIG_PMU_SDA, CONFIG_PMU_SCL);
bool pmu_flag = 0;
bool pmu_online = 0;
uint8_t pmu_status = 0;
uint8_t keycb_start = 0;
uint8_t head_phone_status=LOW;
XPowersPMU PMU;
const uint8_t i2c_sda = CONFIG_PMU_SDA;
const uint8_t i2c_scl = CONFIG_PMU_SCL;
const uint8_t pmu_irq_pin = CONFIG_PMU_IRQ;
unsigned long run_time;
void set_pmu_flag(void) { pmu_flag = true; }
HardwareSerial Serial1(PA10, PA9);
uint8_t write_buffer[10] = {0};
uint8_t write_buffer_len = 0;
uint8_t io_matrix[9];//for IO matrix,last bytye is the restore key(c64 only)
uint8_t js_bits=0xff;// c64 joystick bits
static int current_bat_pcnt = 0;
unsigned long time_uptime_ms() { return millis(); }
void lock_cb(bool caps_changed, bool num_changed) {
bool do_int = false;
if (caps_changed && reg_is_bit_set(REG_ID_CFG, CFG_CAPSLOCK_INT)) {
reg_set_bit(REG_ID_INT, INT_CAPSLOCK);
do_int = true;
}
if (num_changed && reg_is_bit_set(REG_ID_CFG, CFG_NUMLOCK_INT)) {
reg_set_bit(REG_ID_INT, INT_NUMLOCK);
do_int = true;
}
/* // int_pin can be a LED
if (do_int) {
port_pin_set_output_level(int_pin, 0);
delay_ms(INT_DURATION_MS);
port_pin_set_output_level(int_pin, 1);
}*/
}
static void key_cb(char key, enum key_state state) {
bool do_int = false;
if(keycb_start == 0){
fifo_flush();
return;
}
if (reg_is_bit_set(REG_ID_CFG, CFG_KEY_INT)) {
reg_set_bit(REG_ID_INT, INT_KEY);
do_int = true;
}
#ifdef DEBUG
// Serial1.println("key: 0x%02X/%d/%c, state: %d, blk: %d\r\n", key, key, key,
// state, reg_get_value(REG_ID_BKL));
#endif
const struct fifo_item item = {key, state};
if (!fifo_enqueue(item)) {
if (reg_is_bit_set(REG_ID_CFG, CFG_OVERFLOW_INT)) {
reg_set_bit(REG_ID_INT, INT_OVERFLOW); // INT_OVERFLOW The interrupt was
// generated by FIFO overflow.
do_int = true;
}
if (reg_is_bit_set(REG_ID_CFG, CFG_OVERFLOW_ON)) fifo_enqueue_force(item);
}
//Serial1.println(key);
}
void receiveEvent(int howMany) {
uint8_t rcv_data[2]; // max size 2, protocol defined
uint8_t rcv_idx;
if (Wire.available() < 1) return;
rcv_idx = 0;
while (Wire.available()) // loop through all but the last
{
uint8_t c = Wire.read(); // receive byte as a character
rcv_data[rcv_idx] = c;
rcv_idx++;
if (rcv_idx >= 2) {
rcv_idx = 0;
}
}
const bool is_write = (rcv_data[0] & WRITE_MASK);
const uint8_t reg = (rcv_data[0] & ~WRITE_MASK);
write_buffer[0] = 0;
write_buffer[1] = 0;
write_buffer_len = 2;
switch (reg) {
case REG_ID_FIF: {
const struct fifo_item item = fifo_dequeue();
write_buffer[0] = (uint8_t)item.state;
write_buffer[1] = (uint8_t)item.key;
} break;
case REG_ID_BKL: {
if (is_write) {
reg_set_value(REG_ID_BKL, rcv_data[1]);
lcd_backlight_update_reg();
}
write_buffer[0] = reg;
write_buffer[1] = reg_get_value(REG_ID_BKL);
} break;
case REG_ID_BK2: {
if (is_write) {
reg_set_value(REG_ID_BK2, rcv_data[1]);
// kbd_backlight_update will add 0 to the reg
// value that we just set, and apply the result
// to the backlight
kbd_backlight_update(0);
}
write_buffer[0] = reg;
write_buffer[1] = reg_get_value(REG_ID_BK2);
} break;
case REG_ID_BAT:{
//Serial1.print("REG_ID_BAT getBatteryPercent:");Serial1.print(current_bat_pcnt);Serial1.println("%");
write_buffer[0] = reg;
write_buffer[1] = (uint8_t)current_bat_pcnt;
}break;
case REG_ID_KEY: {
write_buffer[0] = fifo_count();
write_buffer[0] |= keyboard_get_numlock() ? KEY_NUMLOCK : 0x00;
write_buffer[0] |= keyboard_get_capslock() ? KEY_CAPSLOCK : 0x00;
}break;
case REG_ID_C64_MTX:{
write_buffer[0] = reg;
memcpy(write_buffer + 1, io_matrix, sizeof(io_matrix));
write_buffer_len = 10;
}break;
case REG_ID_C64_JS:{
write_buffer[0] = reg;
write_buffer[1] = js_bits;
write_buffer_len = 2;
}break;
default: {
write_buffer[0] = 0;
write_buffer[1] = 0;
write_buffer_len = 2;
} break;
}
}
//-this is after receiveEvent-------------------------------
void requestEvent() {
if (write_buffer_len > 0 && write_buffer_len <= sizeof(write_buffer)) {
Wire.write(write_buffer,write_buffer_len );
} else {
Wire.write((uint8_t)0); // Send something minimal to avoid stalling
}
}
void report_bat(){
if (PMU.isBatteryConnect()) {
write_buffer[0] = REG_ID_BAT;
write_buffer[1] = PMU.getBatteryPercent();
write_buffer_len = 2;
requestEvent();
}
}
void printPMU() {
Serial1.print("isCharging:");
Serial1.println(PMU.isCharging() ? "YES" : "NO");
Serial1.print("isDischarge:");
Serial1.println(PMU.isDischarge() ? "YES" : "NO");
Serial1.print("isStandby:");
Serial1.println(PMU.isStandby() ? "YES" : "NO");
Serial1.print("isVbusIn:");
Serial1.println(PMU.isVbusIn() ? "YES" : "NO");
Serial1.print("isVbusGood:");
Serial1.println(PMU.isVbusGood() ? "YES" : "NO");
Serial1.print("getChargerStatus:");
uint8_t charge_status = PMU.getChargerStatus();
if (charge_status == XPOWERS_AXP2101_CHG_TRI_STATE) {
Serial1.println("tri_charge");
} else if (charge_status == XPOWERS_AXP2101_CHG_PRE_STATE) {
Serial1.println("pre_charge");
} else if (charge_status == XPOWERS_AXP2101_CHG_CC_STATE) {
Serial1.println("constant charge");
} else if (charge_status == XPOWERS_AXP2101_CHG_CV_STATE) {
Serial1.println("constant voltage");
} else if (charge_status == XPOWERS_AXP2101_CHG_DONE_STATE) {
Serial1.println("charge done");
} else if (charge_status == XPOWERS_AXP2101_CHG_STOP_STATE) {
Serial1.println("not chargin");
}
Serial1.print("getBattVoltage:");
Serial1.print(PMU.getBattVoltage());
Serial1.println("mV");
Serial1.print("getVbusVoltage:");
Serial1.print(PMU.getVbusVoltage());
Serial1.println("mV");
Serial1.print("getSystemVoltage:");
Serial1.print(PMU.getSystemVoltage());
Serial1.println("mV");
// The battery percentage may be inaccurate at first use, the PMU will
// automatically learn the battery curve and will automatically calibrate the
// battery percentage after a charge and discharge cycle
if (PMU.isBatteryConnect()) {
Serial1.print("getBatteryPercent:");
Serial1.print(PMU.getBatteryPercent());
Serial1.println("%");
}
Serial1.println();
}
void check_pmu_int() {
int pcnt;
if (!pmu_online) return;
if (time_uptime_ms() - run_time > 20000) {
run_time = millis(); // reset time
pcnt = PMU.getBatteryPercent();
//Serial1.print("check_pmu_int: ");Serial1.print(pcnt);Serial1.println();
if (pcnt < 0) { // disconnect
pcnt = 0;
pmu_status = 0xff;
current_bat_pcnt = pcnt;
} else { // battery connected
current_bat_pcnt = pcnt;
if (PMU.isCharging()) {
pmu_status = bitSet(pcnt, 7);
} else {
pmu_status = pcnt;
}
low_bat();
}
}
if (pmu_flag) {
pmu_flag = false;
// Get PMU Interrupt Status Register
uint32_t status = PMU.getIrqStatus();
Serial1.print("STATUS => HEX:");
Serial1.print(status, HEX);
Serial1.print(" BIN:");
Serial1.println(status, BIN);
// When the set low-voltage battery percentage warning threshold is reached,
// set the threshold through getLowBatWarnThreshold( 5% ~ 20% )
if (PMU.isDropWarningLevel2Irq()) {
Serial1.println("isDropWarningLevel2");
report_bat();
}
// When the set low-voltage battery percentage shutdown threshold is reached
// set the threshold through setLowBatShutdownThreshold()
//This is related to the battery charging and discharging logic. If you're not sure what you're doing, please don't modify it, as it could damage the battery.
if (PMU.isDropWarningLevel1Irq()) {
report_bat();
//
PMU.shutdown();
}
if (PMU.isGaugeWdtTimeoutIrq()) {
Serial1.println("isWdtTimeout");
}
if (PMU.isBatChargerOverTemperatureIrq()) {
Serial1.println("isBatChargeOverTemperature");
}
if (PMU.isBatWorkOverTemperatureIrq()) {
Serial1.println("isBatWorkOverTemperature");
}
if (PMU.isBatWorkUnderTemperatureIrq()) {
Serial1.println("isBatWorkUnderTemperature");
}
if (PMU.isVbusInsertIrq()) {
Serial1.println("isVbusInsert");
}
if (PMU.isVbusRemoveIrq()) {
Serial1.println("isVbusRemove");
stop_chg();
}
if (PMU.isBatInsertIrq()) {
pcnt = PMU.getBatteryPercent();
if (pcnt < 0) { // disconnect
pcnt = 0;
pmu_status = 0xff;
} else {
pmu_status = pcnt;
}
current_bat_pcnt = pcnt;
Serial1.println("isBatInsert");
}
if (PMU.isBatRemoveIrq()) {
pmu_status = 0xff;
current_bat_pcnt = 0;
Serial1.println("isBatRemove");
stop_chg();
}
if (PMU.isPekeyShortPressIrq()) {
Serial1.println("isPekeyShortPress");
// enterPmuSleep();
Serial1.print("Read pmu data buffer .");
uint8_t data[4] = {0};
PMU.readDataBuffer(data, XPOWERS_AXP2101_DATA_BUFFER_SIZE);
for (int i = 0; i < 4; ++i) {
Serial1.print(data[i]);
Serial1.print(",");
}
Serial1.println();
printPMU();
}
if (PMU.isPekeyLongPressIrq()) {
Serial1.println("isPekeyLongPress");
//Serial1.println("write pmu data buffer .");
//uint8_t data[4] = {1, 2, 3, 4};
//PMU.writeDataBuffer(data, XPOWERS_AXP2101_DATA_BUFFER_SIZE);
digitalWrite(PA13, LOW);
digitalWrite(PA14, LOW);
PMU.setChargingLedMode(XPOWERS_CHG_LED_CTRL_CHG);
PMU.shutdown();
}
if (PMU.isPekeyNegativeIrq()) {
Serial1.println("isPekeyNegative");
}
if (PMU.isPekeyPositiveIrq()) {
Serial1.println("isPekeyPositive");
}
if (PMU.isLdoOverCurrentIrq()) {
Serial1.println("isLdoOverCurrentIrq");
}
if (PMU.isBatfetOverCurrentIrq()) {
Serial1.println("isBatfetOverCurrentIrq");
}
if (PMU.isBatChagerDoneIrq()) {
pcnt = PMU.getBatteryPercent();
if (pcnt < 0) { // disconnect
pcnt = 0;
pmu_status = 0xff;
}
current_bat_pcnt = pcnt;
pmu_status = bitClear(pcnt, 7);
Serial1.println("isBatChagerDone");
stop_chg();
}
if (PMU.isBatChagerStartIrq()) {
pcnt = PMU.getBatteryPercent();
if (pcnt < 0) { // disconnect
pcnt = 0;
pmu_status = 0xff;
}
current_bat_pcnt = pcnt;
pmu_status = bitSet(pcnt, 7);
Serial1.println("isBatChagerStart");
if(PMU.isBatteryConnect()) {
start_chg();
}
}
if (PMU.isBatDieOverTemperatureIrq()) {
Serial1.println("isBatDieOverTemperature");
}
if (PMU.isChagerOverTimeoutIrq()) {
Serial1.println("isChagerOverTimeout");
}
if (PMU.isBatOverVoltageIrq()) {
Serial1.println("isBatOverVoltage");
}
// Clear PMU Interrupt Status Register
PMU.clearIrqStatus();
}
reg_set_value(REG_ID_BAT, (uint8_t)pmu_status);
}
/*
* PA8 lcd_bl
* PC8 keyboard_bl
*/
void setup() {
pinMode(PA13, OUTPUT); // pico enable
digitalWrite(PA13, LOW);
reg_init();
delay(10);
Serial1.begin(115200);
Wire.setSDA(PB9);
Wire.setSCL(PB8);
Wire.begin(SLAVE_ADDRESS);
Wire.setClock(10000);//It is important to set to 10Khz
Wire.onReceive(receiveEvent); // register event
Wire.onRequest(requestEvent);
// no delay here
bool result = PMU.begin(Wire2, AXP2101_SLAVE_ADDRESS, i2c_sda, i2c_scl);
if (result == false) {
Serial1.println("PMU is not online...");
} else {
pmu_online = 1;
Serial1.printf("getID:0x%x\n", PMU.getChipID());
}
pinMode(PC12, INPUT); // HP_DET
pinMode(PC13, OUTPUT); // indicator led
digitalWrite(PC13, LOW);
pinMode(PA14, OUTPUT); // PA_EN
digitalWrite(PA14, HIGH);
int pin = PC8;
/*
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
// 重映射Timer3的部分映射到PC6-PC9
AFIO->MAPR &= ~AFIO_MAPR_TIM3_REMAP;
AFIO->MAPR |= AFIO_MAPR_TIM3_REMAP_PARTIALREMAP;
*/
/*
pinMode(pin,OUTPUT);
TIM_TypeDef *Instance = (TIM_TypeDef
*)pinmap_peripheral(digitalPinToPinName(pin), PinMap_PWM); uint32_t channel =
STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(pin), PinMap_PWM));
// Instantiate HardwareTimer object. Thanks to 'new' instantiation,
HardwareTimer is not destructed when setup() function is finished.
HardwareTimer *MyTim = new HardwareTimer(Instance);
// Configure and start PWM
// MyTim->setPWM(channel, pin, 5, 10, NULL, NULL); // No callback required, we
can simplify the function call MyTim->setPWM(channel, pin, 80000, 1); //
Hertz, dutycycle
*/
/*
* data records:
* 500,10 === nightlight watch level
*/
/*
analogWriteFrequency(80000);
analogWrite(pin, 10);
*/
pin = PA8;
analogWriteFrequency(10000);
analogWrite(pin, 100);
keyboard_init();
keyboard_set_key_callback(key_cb);
lcd_backlight_update(-223);
digitalWrite(PA13, HIGH);
// It is necessary to disable the detection function of the TS pin on the
// board without the battery temperature detection function, otherwise it will
// cause abnormal charging
PMU.setSysPowerDownVoltage(2800);
PMU.disableTSPinMeasure();
// PMU.enableTemperatureMeasure();
PMU.enableBattDetection();
PMU.enableVbusVoltageMeasure();
PMU.enableBattVoltageMeasure();
PMU.enableSystemVoltageMeasure();
PMU.setChargingLedMode(XPOWERS_CHG_LED_CTRL_CHG);
pinMode(pmu_irq_pin, INPUT_PULLUP);
attachInterrupt(pmu_irq_pin, set_pmu_flag, FALLING);
PMU.disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
PMU.clearIrqStatus();
PMU.enableIRQ(XPOWERS_AXP2101_BAT_INSERT_IRQ |
XPOWERS_AXP2101_BAT_REMOVE_IRQ | // BATTERY
XPOWERS_AXP2101_VBUS_INSERT_IRQ |
XPOWERS_AXP2101_VBUS_REMOVE_IRQ | // VBUS
XPOWERS_AXP2101_PKEY_SHORT_IRQ |
XPOWERS_AXP2101_PKEY_LONG_IRQ | // POWER KEY
XPOWERS_AXP2101_BAT_CHG_DONE_IRQ |
XPOWERS_AXP2101_BAT_CHG_START_IRQ // CHARGE
// XPOWERS_AXP2101_PKEY_NEGATIVE_IRQ |
// XPOWERS_AXP2101_PKEY_POSITIVE_IRQ | //POWER KEY
);
// setLowBatWarnThreshold Range: 5% ~ 20%
// The following data is obtained from actual testing , Please see the description below for the test method.
// 20% ~= 3.7v
// 15% ~= 3.6v
// 10% ~= 3.55V
// 5% ~= 3.5V
// 1% ~= 3.4V
PMU.setLowBatWarnThreshold(5); // Set to trigger interrupt when reaching 5%
// setLowBatShutdownThreshold Range: 0% ~ 15%
// The following data is obtained from actual testing , Please see the description below for the test method.
// 15% ~= 3.6v
// 10% ~= 3.55V
// 5% ~= 3.5V
// 1% ~= 3.4V
PMU.setLowBatShutdownThreshold(1); //This is related to the battery charging and discharging logic. If you're not sure what you're doing, please don't modify it, as it could damage the battery.
run_time = 0;
keycb_start = 1;
low_bat();
//printf("Start pico");
}
//hp headphone
void check_hp_det(){
int v = digitalRead(PC12);
if(v == HIGH) {
if( head_phone_status != v ) {
Serial1.println("HeadPhone detected");
}
digitalWrite(PA14,LOW);
}else{
digitalWrite(PA14,HIGH);
}
head_phone_status = v;
}
void loop() {
check_pmu_int();
keyboard_process();
check_hp_det();
delay(10);
}