/* ----------------------------------------------------- v003_serial.cpp Rudimentaere UART-Funktionen erweitert um Stream-Vererbung MCU : CH32V003 Takt : 48 MHz 30.11.2025 R. Seelig ------------------------------------------------------ */ #include "v003serial.h" // -------------------------------------------------------------------- // V003serial (Konstruktor) // -------------------------------------------------------------------- V003serial::V003serial() { } /* -------------------------------------------------------------------- begin initialisiert serielle Schnittstelle mit Protokoll 8N1 TxD an Pin PD5, RxD an Pin PD6 Uebergabe: baudrate : einzustellende Baudrate ---------------------------------------------------------- */ void V003serial::begin(uint32_t brate) { #define pinnr 6 // PD6 baudrate= brate; // Enable UART and GPIOD RCC->APB2PCENR |= RCC_APB2Periph_GPIOD | RCC_APB2Periph_USART1; // Push-Pull, 10MHz Output on D5, alternate function (TxD) GPIOD->CFGLR = (GPIOD->CFGLR & ~(0x0f << (4*5))) | ((GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF)<<( 4*5 )); // PD6 Pull-Up Widerstand an GPIOD->BSHR |= (1 << pinnr); // PD6 als Input GPIOD->CFGLR &= ~(0x0f << (4 * pinnr)); GPIOD->CFGLR |= ((GPIO_CNF_IN_PUPD) << (4 * pinnr)); GPIOD->BSHR |= (1 << pinnr); // Setup UART for 8n1 USART1->CTLR1 = USART_WordLength_8b | USART_Parity_No | USART_Mode_Tx | USART_Mode_Rx; USART1->CTLR2 = USART_StopBits_1; // Set baud rate and enable UART USART1->BRR = ((FUNCONF_SYSTEM_CORE_CLOCK) + (baudrate)/2) / (baudrate); USART1->CTLR1 |= CTLR1_UE_Set; } /* ---------------------------------------------------------- putchar sendet ein Zeichen auf der seriellen Schnittstelle Uebergabe: ch : zu sendendes Zeichen ---------------------------------------------------------- */ void V003serial::putchar(uint8_t ch) { while(!(USART1->STATR & USART_FLAG_TXE)); USART1->DATAR = ch; } /* ---------------------------------------------------------- getchar wartet, bis auf der seriellen Schnittstelle ein Zeichen eingegangen ist und liest dieses ein. Rueckgabe: gelesenes Zeichen ---------------------------------------------------------- */ uint8_t V003serial::getchar(void) { while(!(USART1->STATR & USART_FLAG_RXNE)); return USART1->DATAR; } /* ---------------------------------------------------------- ischar testet, ob auf der seriellen Schnittstelle ein Zeichen eingegangen ist, liest dieses aber nicht ein Rueckgabe: 1 : es ist ein Zeichen eingetroffen 0 : kein Zeichen verfuegbar ---------------------------------------------------------- */ uint8_t V003serial::ischar(void) { return (USART1->STATR & USART_FLAG_RXNE); } /* -------------------------------------------------- strdelch "String delete Char" loescht ein Zeichen aus einer Zeichenkette Uebergabe: ptrstr => Ursprungstring pos => Loeschposition ( erstes Zeichen entspr. Position 0 ) -------------------------------------------------- */ void V003serial::strdelch(char *ptrstr, int pos) { int l,i; char *hptr; l= strlen(ptrstr); hptr= ptrstr+pos; for (i= pos+1; i!= l+1; i++) { *hptr= *(hptr+1); hptr++; } } /* -------------------------------------------------- upperch "upper char" konvertiert Kleinbuchstaben a..z zu Grossbuch- staben A..Z Entspricht der Funktion "toupper" aus die mit riscv-Compiler in diesem Framework nicht funktioniert Uebergabe: ch: zu konvertierendes Zeichen -------------------------------------------------- */ char V003serial::upperch(char ch) { if ((ch> 'a'-1) && (ch< 'z'+1)) { return (ch-32); } else { return ch; } } /* ---------------------------------------------------------- strfindch sucht in einem String ein Zeichen. Liefert einen Zeiger auf das erste Vorkommen von ch in str bzw. den Wert NULL, wenn ch in str nicht enthalten ist. ==> Ersatz fuer strfindch aus welches aus irgendwelchen Gruenden augenscheinlich nicht richtig funktioniert (liefert keinen NULL-Zeiger) ---------------------------------------------------------- */ char *V003serial::strfindch(const char *str, char ch) { while(*str) { if (*str == ch) { return (char *)str; } str++; } if ((*str == 0) && (ch== '\0')) return (char *)str; return NULL; } /* -------------------------------------------------- strhicase "String high case" konvertiert alle Kleinbuchstaben eines Strings zu Grossbuchstaben Uebergabe: ptrstr => Ursprungstring -------------------------------------------------- */ void V003serial::strhicase(char *ptrstr) { uint8_t i; for(i = 0; ptrstr[i]; i++) { ptrstr[i] = upperch(ptrstr[i]); } } /* ---------------------------------------------------------- prints gibt einen AsciiZ String auf dem UART aus ---------------------------------------------------------- */ void V003serial::prints(char *txbuf) { while(*txbuf) { putchar(*txbuf); txbuf++; } } /* ---------------------------------------------------------- my_pow10 einfache Funktion 10 hoch x ---------------------------------------------------------- */ int V003serial::my_pow10(unsigned int x) { #define my_powbase 10 int i; int result = my_powbase; if (x== 0) { return 1; } else { for (i = 1; i < x; i++) { result *= my_powbase; } return result; } } /* ---------------------------------------------------------- my_atoi my ascii to integer einfache Konvertierung eines Strings zu einem Integer- wert in Ermangelung eines atoi Uebergabe: *str: Zeiger, der einen im String enthaltenen Integerzahlenwert repraesentiert Rueckgabe: konvertierter Integerwert ---------------------------------------------------------- */ int V003serial::my_atoi(char *str) { int i, j; int res; int sign; int len; i = 0; res = 0; sign = 1; // Vorzeichenflag, 1 = positiv, -1 = negativ len= strlen(str); while(str[i] == ' ' || (str[i] >= 9 && str[i] <= 13)) { i++; } if(str[i] == '-') { sign = -1; i++; } else if(str[i] == '+') { sign = 1; i++; } j = i-1; // Index auf Stringanfang i = len; while (str[i] < '0' || str[i] > '9') // Suche nach der letzten gueltigen Ziffer { i--; } int num_end = i; // Konvertierung mit der niederwertigsten Stelle beginnen while(i > j) { if (str[i] >= '0' && str[i] <= '9') { res += my_pow10(num_end-i) * (str[i] - '0'); } else { num_end--; // ueberspringen einer ungueltigen Ziffer } i--; } return(res * sign); } /* ---------------------------------------------------------- my_ahtoi my ascii hex to integer einfache Konvertierung eines Hex-Strings zu einem Integerwert Uebergabe: *str: Zeiger, der einen im String enthaltenen Hexadezimalwert repraesentiert Rueckgabe: konvertierter Integerwert ---------------------------------------------------------- */ int V003serial::my_ahtoi(char *str) { char ch; uint32_t result; strhicase(str); result= 0; do { ch= *str; if ((ch < '0') || ((ch > '9') && (ch < 'A')) || (ch > 'F') ) { return 0xffffffff; } if (ch< 'A') { ch -= '0'; } else { ch -= 55; } result = (result << 4) + ch; str++; } while (*str); return result; } /* ------------------------------------------------------------ printint gibt einen Integer dezimal aus. Ist Uebergabe "komma" != 0 wird ein "Kommapunkt" mit ausgegeben. Groesste darstellbare Zahl ist 99.999.999 Bsp.: 12345 wird als 123.45 ausgegeben. (ermoeglicht Pseudofloatausgaben im Bereich) ------------------------------------------------------------ */ void V003serial::printint(int i, char komma) { typedef enum boolean { FALSE, TRUE }bool_t; static int zz[] = { 10000000, 1000000, 100000, 10000, 1000, 100, 10 }; bool_t not_first = FALSE; uint8_t zi; komma= 8-komma; if (!i) { putchar('0'); } else { if(i < 0) { putchar('-'); i = -i; } int z, b; for(zi = 0; zi < 7; zi++) { z = 0; b = 0; while(z + zz[zi] <= i) { b++; z += zz[zi]; } if(b || not_first) { putchar('0' + b); not_first = TRUE; } if (zi+1 == komma) { if (!not_first) putchar('0'); putchar('.'); not_first= TRUE; } i -= z; } putchar('0' + i); } } /* ---------------------------------------------------------- readln Stream-basierte Version, liest Zeichen bis Return oder Maxlen und gibt sie in txbuffer zurück. Echo wird ebenfalls gesendet. ---------------------------------------------------------- */ void V003serial::readln(char *txbuffer, int maxlen) { int index = 0; while(index < maxlen - 1) { while(available() == 0); int ch = read(); if(ch == '\r' || ch == '\n') break; txbuffer[index++] = (char)ch; write((uint8_t)ch); // Echo } txbuffer[index] = 0; } /* -------------------------------------------------- locase konvertier alle Grossbuchstaben eines Strings in Kleinbuchstaben Uebergabe: str => konvertierter Ursprungstring -------------------------------------------------- */ void V003serial::locase(char *str) { while(*str) { if ((*str >= 'A') && (*str <= 'Z')) *str += 'a' - 'A'; str++; } } /* -------------------------------------------------- lineinput Eingabe eines Strings in den Buffer *string Beim Start der Funktion wird der in *string ab- gelegte Text angezeigt und der Cursor an das Ende dieses Textes gesetzt. Einschraenkungen: Im Integer- und Kommamodus ist die Eingabe von Ziffern auf max. 8 begrenzt (da es sonst zu einem Werteueberlauf kommen kann). Uebergabe: *string : Zeiger auf String, der zum einen angezeigt wird, zum anderen wird in diesen Speicher- bereich auf den der Zeiger zeigt der String abgelegt mode : Eingabemodus 0 => Stringeingabe 1 => Integereingabe, nur Ziffern von 0..9,'-' 2 => Kommazahleingabe wie Mode 1 mit zusaetzlich erlaubtem '.' Zeichen 3 => Hexadezimaleingabe, Eingabe von 0..9, A..F anz : max. Anzahl Zeichen, die eingegeben werden koennen Rueckgabe: Zeiger auf den verarbeiteten String -------------------------------------------------- */ char *V003serial::lineinput(char *string, uint8_t mode, char anz) { uint8_t cnt; char ch; char *ptr; char *kptr; char dp, tc; char zifanz; dp= 0; if (strlen(string)> 0) { kptr = strfindch(string, '.'); if (kptr) dp= 1; } if (mode== 3) { strhicase(string); } // Ziffernanzahl fuer Integer und Komma auf 8 begrenzen, daher feststellen // wieviele Ziffern im Vorgabestring enthalten sind if ((mode== 1) || (mode== 2)) { zifanz= strlen(string); if (zifanz > 0) { if (dp) { zifanz--; } if (*string == '-') { zifanz--; } } // printf("\n\rZifanz: %d\n\r",zifanz); } cnt= strlen(string); prints(&string[0]); ptr= string+cnt; do { ch= getchar(); if ((ch!= 0x0a) && (ch != 0x0d)) // Enter Taste ? { if ((ch== 8) || (ch== 127)) // Backspace { if (cnt> 0) // ist ueberhaupt ein Zeichen vorhanden { tc= *(ptr-1); if (tc == '.') { dp= 0; } if ( !((tc == '.') || (tc == '-'))) { zifanz--; } ptr--; cnt--; *ptr= 0; // neues Stringende // letzes Zeichen im Terminal loeschen putchar(0x08); putchar(' '); putchar(0x08); } } else { // Zeicheneingabe, erlaubte Zeichen in den entsprechenden // Modi pruefen if (mode==3) { if ((ch>= 'a') && (ch<= 'f')) { ch -= 32; } // bei hexadezimaler Eingabe Grossbuchstaben verwenden } if ( ((mode== 0) && (ch >= 32) && (ch <= 126)) // anhaengen Ascii-Zeichen (Stringmode) || (( (mode== 1) && (ch >= '0') && (ch <= '9') ) || (ch== '-')) // anhaengen Dezimalziffer und '-' Zeichen (Integermode) || ((mode== 2) && (((ch >= '0') && (ch <= '9')) || // anhaengen Kommazahl (Floatmode) (ch=='.') || (ch=='-'))) || ((mode== 3) && ( ((ch >= '0') && (ch <= '9')) || // anhaengen Hexadezimalzifer (Hexmode) ((ch >= 'A') && (ch <= 'F'))) ) ) { if ((cnt< anz) && (!((mode==3) && (ch== '-'))) ) { // '-' Zeichen nur erlauben im Integer- und Floatmode und auch nur, wenn noch kein // Minuszeichen angegeben wurde und nur, wenn der Cursor ganz am Anfang steht if (!( ((mode == 2) || (mode == 1)) && (ch == '-') && (cnt>0))) { if ( !( (mode == 2) && (ch == '.') && (dp) ) ) { if (!( ((mode == 1) || (mode == 2)) && (zifanz> 7) )) // Integer und Kommazahlen auf max. 8 Ziffern beschraenken { *ptr= ch; ptr++; cnt++; putchar(ch); if (ch== '.') { dp= 1 ; } if ((ch != '.') && (ch != '-')) { zifanz++; } } } } } } } } } while ((ch != 0x0a) && (ch != 0x0d)); *ptr= 0; return string; } /* -------------------------------------------------- readint Eingabe eines Integerwertes Uebergabe: anz : max. Anzahl Zeichen, die eingegeben werden koennen Rueckgabe: auf UART eingegebener Integerwert -------------------------------------------------- */ int V003serial::readint(uint8_t anz) { char txbuf[11]; if (anz> 10) { anz= 10; } txbuf[0]= NULL; lineinput(txbuf, 1, anz); return my_atoi(txbuf); } /* -------------------------------------------------- readhex Eingabe eines hexadezimalen Zahlenwertes Uebergabe: anz : max. Anzahl Zeichen, die eingegeben werden koennen Rueckgabe: auf UART eingegebener Integerwert -------------------------------------------------- */ int V003serial::readhex(uint8_t anz) { char txbuf[9]; if (anz> 8) { anz= 8; } txbuf[0]= NULL; lineinput(txbuf, 3, anz); return my_ahtoi(txbuf); } /* ------------------------------------------------------------ hexnibbleout gibt die unteren 4 Bits eines chars als Hexaziffer aus. Eine Pruefung ob die oberen vier Bits geloescht sind erfolgt NICHT ! ------------------------------------------------------------- */ void V003serial::hexnibbleout(uint8_t b) { if (b< 10) b+= '0'; else b+= 55; putchar(b); } /* ------------------------------------------------------------ printhex Uebergabe: h : auszugebender Wert out16 : 0 => ist Wert <= 0xff wird der Wert 2-stellig ausgegeben, ansonsten 4-stellig 1 => Wert wird immer 4-stellig ausgegeben ------------------------------------------------------------ */ void V003serial::printhex(uint16_t h, char out16) { uint8_t b; if ((h> 0xff) || out16) { b= (h >> 12); hexnibbleout(b); b= (h >> 8) & 0x0f; hexnibbleout(b); } b= h; b= (h >> 4) & 0x0f; hexnibbleout(b); b= h & 0x0f; hexnibbleout(b); } /* ------------------------------------------------------------ Stream Methoden ------------------------------------------------------------ */ int V003serial::available() { return ischar() ? 1 : 0; } int V003serial::read() { return getchar(); } int V003serial::peek() { if(ischar()) return USART1->DATAR; return -1; } size_t V003serial::write(uint8_t ch) { putchar(ch); return 1; }