Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,139 @@ capture, both watchdogs, progmem, TRNG and CRC), registered as ``/dev/i2c0``,
``/dev/spi0``, ``/dev/adc0``, ``/dev/pwm0``, ``/dev/capture0``,
``/dev/watchdog0`` and ``/dev/watchdog1``. No radio.

sht3x
-----

NSH plus I2C0, the i2ctool and the Sensirion SHT3x temperature/humidity
driver -- a small example to validate the I2C master port against a real
device. Because SPI is *off* here, I2C0 is routed to the J1 header on
**PA2 (SCL)** and **PA3 (SDA)** (adjacent pins on the second row of J1, both
AF4); with SPI on (the ``periph`` case) it falls back to PB0/PB1, whose SDA
pin is not broken out. See the pin-header tables above.

Wire the sensor to J1: SCL -> PA2, SDA -> PA3, VCC -> +3V3 (J2) or the module
3.3 V, GND -> GND. A breakout with its own pull-ups is assumed; the pins are
configured with the internal pull-ups as a fallback.

The bus is registered as ``/dev/i2c0`` and the sensor as ``/dev/temp0``
(``gd32_bringup()`` registers it at address 0x44). Confirm the address with
the i2ctool using a zero-byte write probe (``-z``); the SHT3x NACKs the
default one-byte read probe when it has no measurement pending, so plain
``i2c dev`` would not show it::

nsh> i2c dev -z 0x03 0x77
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- 44 -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

The ``44`` above is the SHT3x acknowledging. Then read it through the
driver::

nsh> sht3x
Converting...
Temperature = 26.530098
Humidity = 47.998001

.. note::
If the sensor sits at 0x45, change the address passed to
``sht3x_register()`` in ``gd32_bringup.c`` (the i2ctool still finds it at
its real address regardless).

pwm
---

NSH plus the PWM driver on TIMER1, registered as ``/dev/pwm0`` and driven by
the ``pwm`` example. Channel 0 (TIMER1_CH0) is routed to **PA0** on the J1
header (AF1), so the waveform can be probed there.

Run the example -- it drives a 5 s pulse train at the configured frequency and
duty (100 Hz, 50 % by default)::

nsh> pwm
pwm_main: starting output with frequency: 100 channel: 1 duty: 00007fff
pwm_main: stopping output

``pwm -f <hz> -d <pct> -t <seconds>`` overrides the frequency, duty and
duration for a single run. Put an LED (with a series resistor) or a scope on
PA0 to see it.

sdcard
------

NSH plus an SD card on SPI0 with a FAT filesystem. The card is registered as
``/dev/mmcsd0`` and ``gd32_bringup()`` mounts it on ``/mnt/sd``.

Wire the card (or a microSD-over-SPI breakout) to the J1 header:

======== ==========
Signal Pin
======== ==========
SCK PA2
MISO PA1
MOSI PA0
CS PA4
======== ==========

.. note::
Power the breakout from **5 V**, not 3.3 V. These breakouts carry their
own 3.3 V regulator, and the SD card draws current bursts while it powers
up; feeding 3.3 V directly leaves the card stuck in the ACMD41 init loop
(it answers CMD0/CMD8 but never leaves the idle state). A decoupling
capacitor close to the card and short leads help too.

The SPI runs its initialisation at ~625 kHz (PCLK2 / 256), slightly above the
400 kHz of the SD spec, which the vast majority of cards tolerate.

The block device shows up as ``/dev/mmcsd0`` and the bringup already mounts it
on ``/mnt/sd``, so no manual ``mount`` is needed::

nsh> ls /dev/
/dev:
console
mmcsd0
null
ttyS0
zero
nsh> mount
/mnt/sd type vfat
nsh> echo "GD32 on NuttX" > /mnt/sd/hello.txt
nsh> cat /mnt/sd/hello.txt
GD32 on NuttX
nsh> ls -l /mnt/sd

adc
---

NSH plus the ADC driver, registered as ``/dev/adc0`` and read by the ``adc``
example. Channel 8 (ADC_IN8) is routed to **PB0** on the J1 header, so an
analog voltage applied there is sampled.

The ADC converts on demand rather than continuously, so the example is built
with ``CONFIG_EXAMPLES_ADC_SWTRIG``: it issues a software trigger and then
reads a group of samples. It is bounded to 20 groups
(``CONFIG_EXAMPLES_ADC_NSAMPLES``) so it returns to the prompt::

nsh> adc
adc_main: g_adcstate.count: 20
adc_main: Hardware initialized. Opening the ADC device: /dev/adc0
Sample:
1: channel: 8 value: 2108
Sample:
1: channel: 8 value: 2115
Sample:
1: channel: 8 value: 2112
...

The reported ``value`` is the 12-bit conversion (0..4095) spanning 0..3.3 V; a
1.65 V input (half of the reference) reads about 2048, and tying PB0 to GND or
3.3 V drives it to the rails.

littlefs
--------

Expand Down
9 changes: 9 additions & 0 deletions arch/risc-v/src/gd32vw55x/gd32vw55x_adc.c
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,15 @@ static int adc_setup(struct adc_dev_s *dev)
return ret;
}

/* Re-enable the ADC clock. adc_shutdown() gates it on close, and the
* one-shot adc_reset() that first turned it on only runs at registration;
* without this a second open would drive a clock-gated peripheral and its
* conversions would never complete. Clock gating preserves the register
* configuration set up by adc_reset(), so nothing has to be re-programmed.
*/

modifyreg32(GD32VW55X_RCU_APB2EN, 0, RCU_APB2EN_ADCEN);

/* Make sure that the interrupts are disabled and power up the ADC */

modifyreg32(GD32VW55X_ADC_CTL0,
Expand Down
94 changes: 72 additions & 22 deletions arch/risc-v/src/gd32vw55x/gd32vw55x_i2c.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,16 @@
# define CONFIG_GD32VW55X_I2C_TIMEOMS (500)
#endif

#ifndef CONFIG_GD32VW55X_I2C_TIMEOTICKS
# define CONFIG_GD32VW55X_I2C_TIMEOTICKS \
/* CONFIG_GD32VW55X_I2C_TIMEOTICKS overrides the seconds/milliseconds pair
* only when non-zero (its Kconfig default is 0); otherwise the timeout is
* derived from the seconds/milliseconds values. Note the Kconfig symbol is
* always defined, so a plain #ifndef would leave it at the useless 0.
*/

#if CONFIG_GD32VW55X_I2C_TIMEOTICKS != 0
# define I2C_TIMEOUT_TICKS CONFIG_GD32VW55X_I2C_TIMEOTICKS
#else
# define I2C_TIMEOUT_TICKS \
(SEC2TICK(CONFIG_GD32VW55X_I2C_TIMEOSEC) + \
MSEC2TICK(CONFIG_GD32VW55X_I2C_TIMEOMS))
#endif
Expand Down Expand Up @@ -113,6 +121,15 @@
#define I2C_SCLDELY_VALUE (4)
#define I2C_SDADELY_VALUE (2)

/* Lower bound for the prescaled clock (4 MHz -> 250 ns period). The
* prescaler is never allowed below this, so the SDADEL/SCLDEL setup and hold
* times stay above the analog-filter minimum of the IP; otherwise the master
* hangs before the first clock. At the 16 MHz kernel clock this forces
* PSC = 3, as in the vendor BSP.
*/

#define I2C_PRESC_FREQ_MAX (4000000)

/****************************************************************************
* Private Types
****************************************************************************/
Expand Down Expand Up @@ -218,7 +235,7 @@ static const struct i2c_ops_s g_i2c_ops =
static const struct gd32_i2c_config_s g_i2c0_config =
{
.i2c_base = GD32VW55X_I2C0_BASE,
.clk_freq = GD32VW55X_PCLK1_FREQ,
.clk_freq = 16000000, /* IRC16M kernel clock */
.scl_pin = GPIO_I2C0_SCL,
.sda_pin = GPIO_I2C0_SDA,
.rcu_en = RCU_APB1EN_I2C0EN,
Expand Down Expand Up @@ -252,7 +269,7 @@ static struct gd32_i2c_priv_s g_i2c0_priv =
static const struct gd32_i2c_config_s g_i2c1_config =
{
.i2c_base = GD32VW55X_I2C1_BASE,
.clk_freq = GD32VW55X_PCLK1_FREQ,
.clk_freq = GD32VW55X_PCLK1_FREQ, /* I2C1 has no clock mux: CK_APB1 */
.scl_pin = GPIO_I2C1_SCL,
.sda_pin = GPIO_I2C1_SDA,
.rcu_en = RCU_APB1EN_I2C1EN,
Expand Down Expand Up @@ -352,23 +369,30 @@ static int gd32_i2c_sem_waitdone(struct gd32_i2c_priv_s *priv)

/* Signal the interrupt handler that we are waiting. Interrupts are
* currently disabled but will be re-enabled while
* nxsem_tickwait_uninterruptible() sleeps.
* nxsem_tickwait_uninterruptible() sleeps. Only arm the wait if the ISR
* has not already completed the transfer between startmsg() and here --
* otherwise WAITING would clobber a DONE that was already posted and the
* wait would block until the timeout.
*/

priv->intstate = INTSTATE_WAITING;

do
ret = OK;
if (priv->intstate != INTSTATE_DONE)
{
ret = nxsem_tickwait_uninterruptible(&priv->sem_isr,
CONFIG_GD32VW55X_I2C_TIMEOTICKS);
if (ret < 0)
priv->intstate = INTSTATE_WAITING;

do
{
/* Break out of the loop on irrecoverable errors */
ret = nxsem_tickwait_uninterruptible(&priv->sem_isr,
I2C_TIMEOUT_TICKS);
if (ret < 0)
{
/* Break out of the loop on irrecoverable errors */

break;
break;
}
}
while (priv->intstate != INTSTATE_DONE);
}
while (priv->intstate != INTSTATE_DONE);

/* Disable the I2C interrupts */

Expand All @@ -386,17 +410,19 @@ static int gd32_i2c_sem_waitdone(struct gd32_i2c_priv_s *priv)
clock_t start;
clock_t elapsed;

timeout = CONFIG_GD32VW55X_I2C_TIMEOTICKS;
timeout = I2C_TIMEOUT_TICKS;
start = clock_systime_ticks();
priv->intstate = INTSTATE_WAITING;

do
{
/* Poll by simply calling the timer interrupt handler with the
* interrupts disabled.
/* Poll by simply calling the interrupt handler. A short pause keeps
* the polling from hammering the status register faster than the bus
* can advance between events.
*/

gd32_i2c_isr_process(priv);
up_udelay(2);

elapsed = clock_systime_ticks() - start;
}
Expand Down Expand Up @@ -479,9 +505,18 @@ static void gd32_i2c_setclock(struct gd32_i2c_priv_s *priv,

i2cclk = priv->config->clk_freq;

/* Find the smallest prescaler that keeps SCLL and SCLH in range */
/* Find the smallest prescaler that keeps SCLL and SCLH in range, but never
* below the one that bounds the prescaled clock to I2C_PRESC_FREQ_MAX (see
* that definition for why).
*/

psc = 0;
if (i2cclk > I2C_PRESC_FREQ_MAX)
{
psc = (i2cclk + I2C_PRESC_FREQ_MAX - 1) / I2C_PRESC_FREQ_MAX - 1;
}

for (psc = 0; psc < 16; psc++)
for (; psc < 16; psc++)
{
cycles = (i2cclk / (psc + 1)) / frequency;

Expand Down Expand Up @@ -742,9 +777,8 @@ static int gd32_i2c_isr_process(struct gd32_i2c_priv_s *priv)

if ((status & I2C_STAT_RBNE) != 0 && priv->dcnt > 0)
{
*priv->ptr++ = (uint8_t)(gd32_i2c_getreg(priv,
GD32VW55X_I2C_RDATA_OFFSET) &
I2C_RDATA_MASK);
*priv->ptr++ = gd32_i2c_getreg(priv, GD32VW55X_I2C_RDATA_OFFSET) &
I2C_RDATA_MASK;
priv->dcnt--;
}

Expand Down Expand Up @@ -857,6 +891,22 @@ static int gd32_i2c_init(struct gd32_i2c_priv_s *priv)

modifyreg32(GD32VW55X_RCU_APB1EN, 0, config->rcu_en);

/* Source the I2C0 kernel clock from IRC16M (16 MHz). The protocol state
* machine is clocked by this kernel clock (not the APB1 bus clock that
* feeds the register interface). With the APB1 default the prescaled
* period is so short that the SDADEL/SCLDEL setup and hold times fall
* below the analog-filter minimum of this I2C v2 IP, and the master hangs
* with the START bit set and BUSY never asserting. IRC16M gives a fixed,
* in-spec 16 MHz reference (matching the vendor BSP), which is why
* clk_freq is 16 MHz.
*/

if (config->i2c_base == GD32VW55X_I2C0_BASE)
{
modifyreg32(GD32VW55X_RCU_CFG1, RCU_CFG1_I2C0SEL_MASK,
RCU_CFG1_I2C0SEL_IRC16M);
}

/* Reset the peripheral to be sure that it is in a known state */

modifyreg32(GD32VW55X_RCU_APB1RST, 0, config->rcu_rst);
Expand Down
55 changes: 55 additions & 0 deletions boards/risc-v/gd32vw55x/gd32vw553k-start/configs/adc/defconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#
# This file is autogenerated: PLEASE DO NOT EDIT IT.
#
# You can use "make menuconfig" to make any modifications to the installed .config file.
# You can then do "make savedefconfig" to generate a new defconfig file that includes your
# modifications.
#
# CONFIG_ARCH_LEDS is not set
CONFIG_ARCH="risc-v"
CONFIG_ARCH_BOARD="gd32vw553k-start"
CONFIG_ARCH_BOARD_GD32VW553K_START=y
CONFIG_ARCH_CHIP="gd32vw55x"
CONFIG_ARCH_CHIP_GD32VW553KM=y
CONFIG_ARCH_CHIP_GD32VW55X=y
CONFIG_ARCH_INTERRUPTSTACK=2048
CONFIG_ARCH_RISCV=y
CONFIG_ARCH_STACKDUMP=y
CONFIG_BOARDCTL_RESET=y
CONFIG_BOARD_LOOPSPERMSEC=16000
CONFIG_BUILTIN=y
CONFIG_DEBUG_FEATURES=y
CONFIG_DEBUG_FULLOPT=y
CONFIG_DEBUG_SYMBOLS=y
CONFIG_EXAMPLES_ADC=y
CONFIG_EXAMPLES_ADC_NSAMPLES=20
CONFIG_EXAMPLES_ADC_SWTRIG=y
CONFIG_EXAMPLES_HELLO=y
CONFIG_FS_PROCFS=y
CONFIG_GD32VW55X_ADC=y
CONFIG_GD32VW55X_UART2=y
CONFIG_IDLETHREAD_STACKSIZE=2048
CONFIG_INIT_ENTRYPOINT="nsh_main"
CONFIG_INIT_STACKSIZE=3072
CONFIG_INTELHEX_BINARY=y
CONFIG_LIBC_PERROR_STDOUT=y
CONFIG_LIBC_STRERROR=y
CONFIG_NFILE_DESCRIPTORS_PER_BLOCK=6
CONFIG_NSH_BUILTIN_APPS=y
CONFIG_NSH_FILEIOSIZE=64
CONFIG_NSH_READLINE=y
CONFIG_NSH_STRERROR=y
CONFIG_RAM_SIZE=294400
CONFIG_RAM_START=0x20000200
CONFIG_RAW_BINARY=y
CONFIG_RR_INTERVAL=200
CONFIG_SCHED_WAITPID=y
CONFIG_STACK_COLORATION=y
CONFIG_START_DAY=13
CONFIG_START_MONTH=7
CONFIG_START_YEAR=2026
CONFIG_SYSTEM_NSH=y
CONFIG_TASK_NAME_SIZE=12
CONFIG_UART2_RXBUFSIZE=128
CONFIG_UART2_SERIAL_CONSOLE=y
CONFIG_UART2_TXBUFSIZE=128
Loading
Loading