From 2fdbf0f236034b4d011365f79f409c5e9ddd0ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Izzo?= Date: Thu, 23 Dec 2021 14:14:09 +0100 Subject: [PATCH] Initial support for I2C soft pots on Module17 Initial support for I2C soft pots on Module17, ADC1 driver for input voltage reading. Cherry-picked from Mathis DB9MAT repo. TG-398 --- meson.build | 4 +- openrtx/src/battery.c | 3 + platform/drivers/ADC/ADC1_Mod17.c | 92 +++++++++ platform/drivers/ADC/ADC1_Mod17.h | 74 +++++++ platform/drivers/baseband/MCP4551.h | 56 ++++++ platform/drivers/baseband/MCP4551_Mod17.cpp | 203 ++++++++++++++++++++ platform/drivers/display/SSD1306_Mod17.c | 103 +++++----- platform/targets/Module17/hwconfig.h | 10 +- platform/targets/Module17/platform.c | 17 +- 9 files changed, 505 insertions(+), 57 deletions(-) create mode 100644 platform/drivers/ADC/ADC1_Mod17.c create mode 100644 platform/drivers/ADC/ADC1_Mod17.h create mode 100644 platform/drivers/baseband/MCP4551.h create mode 100644 platform/drivers/baseband/MCP4551_Mod17.cpp diff --git a/meson.build b/meson.build index a2f7c013..876eca31 100644 --- a/meson.build +++ b/meson.build @@ -310,12 +310,14 @@ dm1801_def = def + mk22fn512_def + {'PLATFORM_DM1801': ''} ## mod17_src = src + stm32f405_src + ['platform/targets/Module17/platform.c', 'platform/drivers/display/SH110x_Mod17.c', + 'platform/drivers/ADC/ADC1_Mod17.c', 'platform/drivers/keyboard/keyboard_Mod17.c', 'platform/drivers/NVM/nvmem_Mod17.c', 'platform/drivers/baseband/radio_Mod17.cpp', 'platform/drivers/audio/inputStream_Mod17.cpp', 'platform/drivers/audio/outputStream_Mod17.c', - 'platform/drivers/audio/audio_Mod17.c'] + 'platform/drivers/audio/audio_Mod17.c', + 'platform/drivers/baseband/MCP4551_Mod17.cpp'] mod17_inc = inc + stm32f405_inc + ['platform/targets/Module17'] mod17_def = def + stm32f405_def + {'PLATFORM_MOD17': ''} diff --git a/openrtx/src/battery.c b/openrtx/src/battery.c index f350c760..5105a88f 100644 --- a/openrtx/src/battery.c +++ b/openrtx/src/battery.c @@ -35,6 +35,9 @@ static const uint16_t bat_v_max = 0x0819; // 8.10V #elif defined BAT_LIPO_3S static const uint16_t bat_v_min = 0x0AD4; // 10.83V static const uint16_t bat_v_max = 0x0C73; // 12.45V +#elif defined BAT_MOD17 +static const uint16_t bat_v_min = 0x0600; // 6.00V +static const uint16_t bat_v_max = 0x0DCD; // 13.8V #elif defined BAT_NONE static const uint16_t bat_v_min = 0; static const uint16_t bat_v_max = 0; diff --git a/platform/drivers/ADC/ADC1_Mod17.c b/platform/drivers/ADC/ADC1_Mod17.c new file mode 100644 index 00000000..4e361972 --- /dev/null +++ b/platform/drivers/ADC/ADC1_Mod17.c @@ -0,0 +1,92 @@ +/*************************************************************************** + * Copyright (C) 2020 by Silvano Seva IU2KWO and Niccolò Izzo IU2KIN * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include +#include +#include +#include +#include "ADC1_Mod17.h" + +pthread_mutex_t adcMutex; + +void adc1_init() +{ + pthread_mutex_init(&adcMutex, NULL); + + RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; + __DSB(); + + /* + * Configure GPIOs to analog input mode: + */ + gpio_setMode(AIN_VBAT, INPUT_ANALOG); + + /* + * ADC clock is APB2 frequency divided by 8, giving 10.5MHz. + * We set the sample time of each channel to 84 ADC cycles and we have that + * a conversion takes 12 cycles: total conversion time is then of ~9us. + */ + ADC->CCR |= ADC_CCR_ADCPRE; + ADC1->SMPR2 = ADC_SMPR2_SMP3_2; + + /* + * Convert one channel, no overrun interrupt, 12-bit resolution, + * no analog watchdog, discontinuous mode, no end of conversion interrupts, + * turn on ADC. + */ + ADC1->SQR1 = 0; + ADC1->CR2 = ADC_CR2_ADON; +} + +void adc1_terminate() +{ + pthread_mutex_destroy(&adcMutex); + + ADC1->CR2 &= ~ADC_CR2_ADON; + RCC->APB2ENR &= ~RCC_APB2ENR_ADC1EN; + __DSB(); +} + +uint16_t adc1_getRawSample(uint8_t ch) +{ + if(ch > 15) return 0; + + pthread_mutex_lock(&adcMutex); + + ADC1->SQR3 = ch; + ADC1->CR2 |= ADC_CR2_SWSTART; + while((ADC1->SR & ADC_SR_EOC) == 0) ; + uint16_t value = ADC1->DR; + + pthread_mutex_unlock(&adcMutex); + + return value; +} + +uint16_t adc1_getMeasurement(uint8_t ch) +{ + /* + * To avoid using floats, we convert the raw ADC sample to mV using 16.16 + * fixed point math. The equation for conversion is (sample * 3300)/4096 but, + * since converting the raw ADC sample to 16.16 notation requires a left + * shift by 16 and dividing by 4096 is equivalent to shifting right by 12, + * we just shift left by four and then multiply by 3300. + * With respect to using floats, maximum error is -1mV. + */ + uint32_t sample = (adc1_getRawSample(ch) << 4) * 3300; + return ((uint16_t) (sample >> 16)); +} diff --git a/platform/drivers/ADC/ADC1_Mod17.h b/platform/drivers/ADC/ADC1_Mod17.h new file mode 100644 index 00000000..924497f5 --- /dev/null +++ b/platform/drivers/ADC/ADC1_Mod17.h @@ -0,0 +1,74 @@ +/*************************************************************************** + * Copyright (C) 2020 by Silvano Seva IU2KWO and Niccolò Izzo IU2KIN * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef ADC1_H +#define ADC1_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Driver for ADC1, used on Module 17 to sample input voltage + * + * NOTE: values inside the enum are the channel numbers of STM32 ADC1 peripheral. + */ + +enum adcCh +{ + ADC_VBAT_CH = 3, +}; + +/** + * Initialise ADC1. + */ +void adc1_init(); + +/** + * Turn off ADC1. + */ +void adc1_terminate(); + +/** + * Get current measurement of a given channel returning the raw ADC value. + * + * NOTE: the mapping provided in enum adcCh DOES NOT correspond to the physical + * ADC channel mapping! + * + * @param ch: channel number. + * @return current value of the specified channel, in ADC counts. + */ +uint16_t adc1_getRawSample(uint8_t ch); + +/** + * Get current measurement of a given channel. + * + * NOTE: the mapping provided in enum adcCh DOES NOT correspond to the physical + * ADC channel mapping! + * + * @param ch: channel number. + * @return current value of the specified channel in mV. + */ +uint16_t adc1_getMeasurement(uint8_t ch); + +#ifdef __cplusplus +} +#endif + +#endif /* ADC1_H */ diff --git a/platform/drivers/baseband/MCP4551.h b/platform/drivers/baseband/MCP4551.h new file mode 100644 index 00000000..22c22d05 --- /dev/null +++ b/platform/drivers/baseband/MCP4551.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * Copyright (C) 2021 by Federico Amedeo Izzo IU2NUO, * + * Niccolò Izzo IU2KIN * + * Frederik Saraci IU2NRO * + * Silvano Seva IU2KWO * + * Mathis Schmieder DB9MAT * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef MCP4551_H +#define MCP4551_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Common WIPER values +#define MCP4551_WIPER_MID 0x080 +#define MCP4551_WIPER_A 0x100 +#define MCP4551_WIPER_B 0x000 + +// Command definitions (sent to WIPER register) +#define MCP4551_CMD_WRITE 0x00 +#define MCP4551_CMD_INC 0x04 +#define MCP4551_CMD_DEC 0x08 +#define MCP4551_CMD_READ 0x0C + +/** + * Initialise I2C. + */ +void i2c_init(); + +void mcp4551_init(uint8_t addr); +void mcp4551_setWiper(uint8_t devAddr, uint16_t value); + +#ifdef __cplusplus +} +#endif + +#endif /* MCP4551_H */ \ No newline at end of file diff --git a/platform/drivers/baseband/MCP4551_Mod17.cpp b/platform/drivers/baseband/MCP4551_Mod17.cpp new file mode 100644 index 00000000..81287f1e --- /dev/null +++ b/platform/drivers/baseband/MCP4551_Mod17.cpp @@ -0,0 +1,203 @@ +/*************************************************************************** + * Copyright (C) 2021 by Federico Amedeo Izzo IU2NUO, * + * Niccolò Izzo IU2KIN * + * Frederik Saraci IU2NRO * + * Silvano Seva IU2KWO * + * Mathis Schmieder DB9MAT * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include +#include +#include +#include "MCP4551.h" + +/* + * Implementation of MCP4551 I2C interface. + * + * Hardware I2C is not yet implemented. Bit-bang, baby! + */ + +void _i2c_start(); +void _i2c_stop(); +void _i2c_write(uint8_t val); +uint8_t _i2c_read(bool ack); + +void i2c_init() +{ + gpio_setMode(I2C_SDA, INPUT); + gpio_setMode(I2C_SCL, OUTPUT); + gpio_clearPin(I2C_SCL); +} + +void mcp4551_init(uint8_t addr) +{ + mcp4551_setWiper(addr, MCP4551_WIPER_MID); +} + +void mcp4551_setWiper(uint8_t devAddr, uint16_t value) +{ + _i2c_start(); + _i2c_write(devAddr << 1); + uint8_t temp = ((value >> 8 & 0x01) | MCP4551_CMD_WRITE); + _i2c_write(temp); + temp = (value & 0xFF); + _i2c_write(temp); + _i2c_stop(); +} + +/*uint16_t i2c_readReg16(uint8_t devAddr, uint8_t reg) +{ + _i2c_start(); + _i2c_write(devAddr << 1); + _i2c_write(reg); + _i2c_start(); + _i2c_write(devAddr | 0x01); + uint8_t valHi = _i2c_read(true); + uint8_t valLo = _i2c_read(false); + _i2c_stop(); + + return (valHi << 8) | valLo; +} */ + +/* + * Software I2C routine + */ + +void _i2c_start() +{ + gpio_setMode(I2C_SDA, OUTPUT); + + /* + * Lines commented to keep SCL high when idle + * + gpio_clearPin(I2C_SCL); + delayUs(2); + */ + + gpio_setPin(I2C_SDA); + delayUs(5); + + gpio_setPin(I2C_SCL); + delayUs(5); + + gpio_clearPin(I2C_SDA); + delayUs(5); + + gpio_clearPin(I2C_SCL); + delayUs(6); +} + +void _i2c_stop() +{ + gpio_setMode(I2C_SDA, OUTPUT); + + gpio_clearPin(I2C_SCL); + delayUs(5); + + gpio_clearPin(I2C_SDA); + delayUs(5); + + gpio_setPin(I2C_SCL); + delayUs(5); + + gpio_setPin(I2C_SDA); + delayUs(5); + + /* + * Lines commented to keep SCL high when idle + * + gpio_clearPin(I2C_SCL); + delayUs(5); + */ +} + +void _i2c_write(uint8_t val) +{ + gpio_setMode(I2C_SDA, OUTPUT); + + for(uint8_t i = 0; i < 8; i++) + { + gpio_clearPin(I2C_SCL); + delayUs(1); + + if(val & 0x80) + { + gpio_setPin(I2C_SDA); + } + else + { + gpio_clearPin(I2C_SDA); + } + + val <<= 1; + delayUs(1); + gpio_setPin(I2C_SCL); + delayUs(5); + } + + /* Ensure SCL is low before releasing SDA */ + gpio_clearPin(I2C_SCL); + + /* Clock cycle for slave ACK/NACK */ + gpio_setMode(I2C_SDA, INPUT_PULL_UP); + delayUs(5); + gpio_setPin(I2C_SCL); + delayUs(5); + gpio_clearPin(I2C_SCL); + delayUs(1); + + /* Asserting SDA pin allows to fastly bring the line to idle state */ + gpio_setPin(I2C_SDA); + gpio_setMode(I2C_SDA, OUTPUT); + delayUs(6); +} + +uint8_t _i2c_read(bool ack) +{ + gpio_setMode(I2C_SDA, INPUT_PULL_UP); + gpio_clearPin(I2C_SCL); + + uint8_t value = 0; + for(uint8_t i = 0; i < 8; i++) + { + delayUs(5); + gpio_setPin(I2C_SCL); + delayUs(5); + + value <<= 1; + value |= gpio_readPin(I2C_SDA); + + gpio_clearPin(I2C_SCL); + } + + /* + * Set ACK/NACK state BEFORE putting SDA gpio to output mode. + * This avoids spurious spikes which can be interpreted as NACKs + */ + gpio_clearPin(I2C_SDA); + gpio_setMode(I2C_SDA, OUTPUT); + delayUs(5); + if(!ack) gpio_setPin(I2C_SDA); + + /* Clock cycle for ACK/NACK */ + delayUs(5); + gpio_setPin(I2C_SCL); + delayUs(5); + gpio_clearPin(I2C_SCL); + delayUs(5); + + return value; +} \ No newline at end of file diff --git a/platform/drivers/display/SSD1306_Mod17.c b/platform/drivers/display/SSD1306_Mod17.c index 534599b7..bc8a176d 100644 --- a/platform/drivers/display/SSD1306_Mod17.c +++ b/platform/drivers/display/SSD1306_Mod17.c @@ -15,6 +15,16 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see * ***************************************************************************/ @@ -80,41 +90,36 @@ void display_init() gpio_clearPin(LCD_RS); gpio_clearPin(LCD_RST); /* Reset controller */ - delayMs(1); + delayMs(50); gpio_setPin(LCD_RST); - delayMs(5); + delayMs(50); gpio_clearPin(LCD_CS); gpio_clearPin(LCD_RS); /* RS low -> command mode */ - spi2_sendRecv(0xAE); /* Disable Display */ - spi2_sendRecv(0x20); /* Set Memory Addressing Mode to horizontal addressing Mode */ + spi2_sendRecv(0xAE); // SH110X_DISPLAYOFF, + spi2_sendRecv(0xd5); // SH110X_SETDISPLAYCLOCKDIV, 0x51, + spi2_sendRecv(0x51); + //spi2_sendRecv(0x20); // SH110X_MEMORYMODE, + spi2_sendRecv(0x81); // SH110X_SETCONTRAST, 0x4F, + spi2_sendRecv(0x4F); + spi2_sendRecv(0xAD); // SH110X_DCDC, 0x8A, + spi2_sendRecv(0x8A); + spi2_sendRecv(0xA0); // SH110X_SEGREMAP, + spi2_sendRecv(0xC0); // SH110X_COMSCANINC, + spi2_sendRecv(0xDC); // SH110X_SETDISPSTARTLINE, 0x0, spi2_sendRecv(0x00); - spi2_sendRecv(0xB0); /* Set Page Start Address for Page Addressing Mode */ - spi2_sendRecv(0xC8); /* Set COM Output Scan Direction */ - spi2_sendRecv(0x00); /* Set low column address */ - spi2_sendRecv(0x10); /* Set high column address */ - spi2_sendRecv(0x40); /* Set start line address */ - spi2_sendRecv(0x81); /* Set contrast */ - spi2_sendRecv(0xCF); - spi2_sendRecv(0xA1); /* Set segment re-map 0 to 127 */ - spi2_sendRecv(0xA6); /* Set normal color */ - spi2_sendRecv(0xA8); /* Set multiplex ratio (1 to 64) */ - spi2_sendRecv(0x3F); - spi2_sendRecv(0xA4); /* Output follows RAM content */ - spi2_sendRecv(0xD3); /* Set display offset */ - spi2_sendRecv(0x00); /* Not offset */ - spi2_sendRecv(0xD5); /* Set display clock divide ratio/oscillator frequency */ - spi2_sendRecv(0xF0); /* Set divide ratio */ - spi2_sendRecv(0xD9); /* Set pre-charge period */ + spi2_sendRecv(0xd3); // SH110X_SETDISPLAYOFFSET, 0x60, + spi2_sendRecv(0x60); + spi2_sendRecv(0xd9); // SH110X_SETPRECHARGE, 0x22, spi2_sendRecv(0x22); - spi2_sendRecv(0xDA); /* Set com pins hardware configuration */ - spi2_sendRecv(0x12); - spi2_sendRecv(0xDB); /* Set vcomh */ - spi2_sendRecv(0x40); - spi2_sendRecv(0x8D); /* Set DC-DC enable */ - spi2_sendRecv(0x14); - spi2_sendRecv(0xAF); /* Enable Display */ + spi2_sendRecv(0xdb); // SH110X_SETVCOMDETECT, 0x35, + spi2_sendRecv(0x35); + spi2_sendRecv(0xa8); // SH110X_SETMULTIPLEX, 0x3F, + spi2_sendRecv(0x3f); + spi2_sendRecv(0xa4); // SH110X_DISPLAYALLON_RESUME, + spi2_sendRecv(0xa6); // SH110X_NORMALDISPLAY, + spi2_sendRecv(0xAF); // SH110x_DISPLAYON gpio_setPin(LCD_CS); } @@ -130,36 +135,26 @@ void display_terminate() void display_renderRow(uint8_t row) { - /* magic stuff */ - uint8_t *buf = (frameBuffer + 128 * row); - for (uint8_t i = 0; i<16; i++) - { - uint8_t tmp[8] = {0}; - for (uint8_t j = 0; j < 8; j++) - { - uint8_t tmp_buf = buf[j*16 + i]; - int count = __builtin_popcount(tmp_buf); - while (count > 0) - { - int pos = __builtin_ctz(tmp_buf); - tmp[pos] |= 1UL << j; - tmp_buf &= ~(1 << pos); - count--; - } - } + uint8_t *buf = (frameBuffer); - for (uint8_t s = 0; s < 8; s++) - { - (void) spi2_sendRecv(tmp[s]); - } - } + for(uint16_t i=0; i<64; i++) + { + uint8_t out=0, tmp=buf[i*16 + 15-row]; + + for(uint8_t j=0; j<8; j++) + { + out|=((tmp>>(7-j))&1)< command mode */ (void) spi2_sendRecv(0xB0 | row); /* Set Y position */ @@ -168,13 +163,13 @@ void display_renderRows(uint8_t startRow, uint8_t endRow) gpio_setPin(LCD_RS); /* RS high -> data mode */ display_renderRow(row); } - + gpio_setPin(LCD_CS); } void display_render() { - display_renderRows(0, SCREEN_HEIGHT / 8); + display_renderRows(0, SCREEN_WIDTH / 8 - 1); } bool display_renderingInProgress() diff --git a/platform/targets/Module17/hwconfig.h b/platform/targets/Module17/hwconfig.h index ec65f34a..e92102e3 100644 --- a/platform/targets/Module17/hwconfig.h +++ b/platform/targets/Module17/hwconfig.h @@ -39,7 +39,7 @@ #define PIX_FMT_BW /* Device has no battery */ -#define BAT_NONE +#define BAT_MOD17 /* Signalling LEDs */ #define PTT_LED GPIOC,8 @@ -75,4 +75,12 @@ #define MIC_MUTE GPIOC,4 #define MIC_GAIN GPIOC,5 +#define AIN_VBAT GPIOA,3 + +/* I2C for MCP4551 */ +#define I2C_SDA GPIOB,7 +#define I2C_SCL GPIOB,6 +#define SOFTPOT_RX 0x2E +#define SOFTPOT_TX 0x2F + #endif diff --git a/platform/targets/Module17/platform.c b/platform/targets/Module17/platform.c index a4c07f01..e088884c 100644 --- a/platform/targets/Module17/platform.c +++ b/platform/targets/Module17/platform.c @@ -27,6 +27,9 @@ #include #include #include +#include +#include +#include void platform_init() { @@ -39,6 +42,14 @@ void platform_init() gpio_setMode(PTT_OUT, OUTPUT); gpio_clearPin(PTT_OUT); + nvm_init(); + adc1_init(); + i2c_init(); + mcp4551_init(SOFTPOT_RX); + mcp4551_init(SOFTPOT_TX); + //mcp4551_setWiper(SOFTPOT_RX, MCP4551_WIPER_A); + //mcp4551_setWiper(SOFTPOT_TX, MCP4551_WIPER_A); + audio_init(); } @@ -48,11 +59,15 @@ void platform_terminate() gpio_clearPin(PTT_LED); gpio_clearPin(SYNC_LED); gpio_clearPin(ERR_LED); + + adc1_terminate(); + nvm_terminate(); + audio_terminate(); } uint16_t platform_getVbat() { - return 0; + return adc1_getMeasurement(ADC_VBAT_CH)*5; } uint8_t platform_getMicLevel()