cnlohr / ch32v003fun

Open source minimal stack for the ch32 line of WCH processors, including the ch32v003, a 10¢ 48 MHz RISC-V Microcontroller - as well as many other chips within the ch32v/x line.
MIT License
938 stars 144 forks source link

The serial interface does not work in parallel with the I2C mode #151

Closed panciunio closed 1 year ago

panciunio commented 1 year ago

Hi Cnlohr,

At first - many thanks for your effort and for all contributors! It's very useful project and I'm very happy using it! I hope it will be available for CH32V103 too :-)

I tried to use an example I2C_OLED with partially success... OLED working as expected but serial output doesn't work - no one message appeared on serial console. To confirm that any connection and configuration is ok, I used other example: UARTDEMO where everything works fine and each message are visible on my console: image

I compared both examples and I can't find any difference between into serial initialization, so I suppose the issue coming somewhere else in rest of code. Could you please advise where I shall to look?

Best Regards, pancio

maxgerhardt commented 1 year ago

If you want the debug printf to go out via serial / UART, then you need to activate the macro responsible for it, by default it's commented out

https://github.com/cnlohr/ch32v003fun/blob/b199584bd2d586b91c5a117ff9ec758d092f39d2/examples/i2c_oled/Makefile#L3-L6

Otherwise it tries to push out the debug messages via the flashing interface (SDI), readable with the minichlink program.

https://github.com/cnlohr/ch32v003fun/blob/b199584bd2d586b91c5a117ff9ec758d092f39d2/examples/i2c_oled/i2c_oled.c#L27-L33

panciunio commented 1 year ago

Just minute before I found this #CFLAGS+=-DSTDOUT_UART definition in Makefile :-) But many thanks for your advice. Could you please explain how to do it with minichlink? Should be data available on /dev/ttyACM0 (my WCH-link) after flashing process?

maxgerhardt commented 1 year ago

No in the not STDOUT_UART case the data is passed via the debug registers in the debug connection, they don't show up on the serial device (/dev/tty*). As the readme says per

https://github.com/cnlohr/ch32v003fun#a-fast-printf-debug-over-the-programming-interface

you need to use ./minichlink -T to get those prints.

panciunio commented 1 year ago

Now everything is clear and works fine. There is no possibility to see data from ACM0 device directly on terminal e.g. minicom app but ./minichlink -T is very usefull. Is there any example how to catch data from serial port by UART?

maxgerhardt commented 1 year ago

Hm I didn't see a UART RX example, but if you look at the _write implementation and UART init

https://github.com/cnlohr/ch32v003fun/blob/b199584bd2d586b91c5a117ff9ec758d092f39d2/ch32v003fun/ch32v003fun.c#L879-L906

you could come up with an extension of that, that activates the RX mode and implements the _read function so that standard-IO functions like scanf() or getchar() just work, see e.g. https://shawnhymel.com/1873/how-to-use-printf-on-stm32/ for reference.

Let me just test this for myself :)

maxgerhardt commented 1 year ago

Hmm I got it working in my PlatformIO enviornment but I had to comment out some functions in ch32v003fun.c because otherwise it would generate duplicate definitions for standard-C functions.. need to investigate it more. But when I comment those out (as would be mentioned in the compile errors), this program works nicely:

(serial terminal settings: 115200 baud, local echo turned on, WCH-LinkE's "TX" connected to PD6, "RX" connected to PD5)

grafik

#define SYSTEM_CORE_CLOCK 48000000
#define APB_CLOCK SYSTEM_CORE_CLOCK

#include "ch32v003fun.h"
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int _read(int fd, char *ptr, int len)
{
  if (fd == STDIN_FILENO) {
    // wait for a character to be received
    while(!(USART1->STATR & USART_STATR_RXNE)) {}
    uint8_t rxData = USART1->DATAR;
    *ptr = rxData;
    //always only return 1 byte
    return 1;
  }
  return -1;
}

int main() {
  SystemInit48HSI();
  SetupUART(UART_BRR);
  // we need to additional turn on RX mode
  USART1->CTLR1 |= USART_Mode_Rx;
  Delay_Ms(1000); // startup delay

  while (1) {
    printf("Please enter some text: ");
    // read from stdin FILE* / stream
    char buf[128];
    fgets(buf, sizeof(buf), stdin);
    // will return after hitting newline, but newline chracter is included.
    // beauty massage: overwrite it with NUL character here
    char *pos;
    if ((pos=strchr(buf, '\n')) != NULL)
      *pos = '\0';
    if ((pos=strchr(buf, '\r')) != NULL)
      *pos = '\0';
    printf("You entered: \"%s\"\n", buf);
  }
}
panciunio commented 1 year ago

Of course... I'm rather noob, more concentrated on hardware than software but I see big potential with CH32V003 - so small but so powerfully :-) Let me check your example and confirm that working fine... suppose shall be added as additional example...

EDIT: I have no idea how to find functions definitions you mentioned... maybe my environment is so different than your?

pancio@desktop:/sandbox/ch32v003fun/examples/uartdemo$ make clean
rm -rf uartdemo.elf uartdemo.bin uartdemo.hex uartdemo.lst uartdemo.map uartdemo.hex || true
pancio@desktop:/sandbox/ch32v003fun/examples/uartdemo$ make
riscv64-unknown-elf-gcc -o uartdemo.elf ../../ch32v003fun/ch32v003fun.c uartdemo.c -DSTDOUT_UART -g -Os -flto -ffunction-sections -static-libgcc -march=rv32ec -mabi=ilp32e -I/usr/include/newlib -I../../ch32v003fun/../extralibs -I../../ch32v003fun -nostdlib -I. -Wall  -T ../../ch32v003fun/ch32v003fun.ld -Wl,--gc-sections -L../../ch32v003fun/../misc -lgcc
/usr/lib/riscv64-unknown-elf/bin/ld: /tmp/uartdemo.elf.5bCtq0.ltrans0.ltrans.o: in function `.L0 ':
/sandbox/ch32v003fun/examples/uartdemo/uartdemo.c:30: undefined reference to `_impure_ptr'
/usr/lib/riscv64-unknown-elf/bin/ld: /tmp/uartdemo.elf.5bCtq0.ltrans0.ltrans.o:/sandbox/ch32v003fun/examples/uartdemo/uartdemo.c:30: undefined reference to `_impure_ptr'
/usr/lib/riscv64-unknown-elf/bin/ld: /tmp/uartdemo.elf.5bCtq0.ltrans0.ltrans.o:/sandbox/ch32v003fun/examples/uartdemo/uartdemo.c:33: undefined reference to `fgets'
collect2: error: ld returned 1 exit status
make: *** [../../ch32v003fun/ch32v003fun.mk:27: uartdemo.elf] Error 1
pancio@desktop:/sandbox/ch32v003fun/examples/uartdemo$ 

if I comment fgets(buf, sizeof(buf), stdin); program is compiled without any warning and putting many lines on terminal: image

maxgerhardt commented 1 year ago

Actually I had a hard time getting it to compile with this makefile / standard toolchain because it doesn't link / have the standard nosys.specs and nano.specs that I'm linking with.

As an intermediate solution I rewrote it to not rely on Standard-IO functions, but do this instead:

#define SYSTEM_CORE_CLOCK 48000000
#define APB_CLOCK SYSTEM_CORE_CLOCK

#include "ch32v003fun.h"
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>

int _read(int fd, char *ptr, int len) {
  if (fd == STDIN_FILENO)  {
    // wait for a character to be received
    while (!(USART1->STATR & USART_STATR_RXNE)) { }
    uint8_t rxData = USART1->DATAR;
    *ptr = rxData;
    // always only return 1 byte
    return 1;
  }
  return -1;
}
// user input buffer and position in buffer
static char buf[128];
static unsigned pos = 0;
bool get_user_input() {
  char c;
  if (pos < sizeof(buf) && _read(STDIN_FILENO, &c, 1) == 1) {
    // assume terminal program is set to either \r\n or \n as line ending, thus \n is always end of line
    if (c == '\n') {
      return true;
    }
    // swallow \r (line ending normalization basically)
    if (c == '\r')
      return false;
    buf[pos++] = c;
    // we don't have a full line yet
    return false;
  }
  else {
    // something bad happened, reset
    pos = 0;
    memset(buf, 0, sizeof(buf));
    return false;
  }
}

int main() {
  SystemInit48HSI();
  SetupUART(UART_BRR);
  // we need to additional turn on RX mode
  USART1->CTLR1 |= USART_Mode_Rx;
  Delay_Ms(1000); // startup delay

  while (1) {
    printf("Please enter some text: ");
    // read from user until newline (blocking)
    while (!get_user_input()) {    }
    printf("You entered: \"%s\"\n", buf);
    // reset for next run
    pos = 0;
    memset(buf, 0, sizeof(buf));
  }
}

this should compile without problems and still works, and is a significantly smaller firmware as a side-effect.

grafik

panciunio commented 1 year ago

Good job Max,

Working fine: image

So maybe some commit to branch?

Thanks a lot for help!

Best Regards,

maxgerhardt commented 1 year ago

(You can turn on local echo mode in miniterm with Ctrl + T followed by Ctrl + E, in _read(), write back rxData into the UART1->DATAR register for an actual echo)

Great that it works, but let's get this issue back on track. The original question was about printf() not showing up as UART output in the I2C OLED demo, but we resolved that with the unactivated STDOUT_UART macro.

Adding a proper UART RX is another point, I've opened #152 for that.

panciunio commented 1 year ago

As investigated by @maxgerhardt there is no issue with STDOUT_UART. By accident new feature regarding UART RX were discovered. Thank you @maxgerhardt