receive/transmit over rs232 with arm lpc2148 on sparkfun logomatic - c

I am trying to program the logomatic by sparkfun, and yes I have used their forum with no responses, and having some issues. I am trying to send characters to the UART0 and I want the logomatic to respond with specific characters and not just an echo. For example, I send 'ID?' over the terminal (using RealTerm), and the logomatic sends back '1'. All it will so now is echo.
I am using c with programmers notepad with the WinARM toolchain. The following snippet is from the main.c file. I only included this, because I am fairly certain that this is where my problem lies
void Initialize(void)
{
rprintf_devopen(putc_serial0);
PINSEL0 = 0xCF351505;
PINSEL1 = 0x15441801;
IODIR0 |= 0x00000884;
IOSET0 = 0x00000080;
S0SPCR = 0x08; // SPI clk to be pclk/8
S0SPCR = 0x30; // master, msb, first clk edge, active high, no ints
}
Notice the rprintf_devopen function, below is from the rprintf.c file, and due to my mediocre skills, I do not understand this bit of code. If I comment out the rprintf_devopen in main, the chip never initializes correctly.
static int (*putcharfunc)(int c);
void rprintf_devopen( int(*put)(int) )
{
putcharfunc = put;
}
static void myputchar(unsigned char c)
{
if(c == '\n') putcharfunc('\r');
putcharfunc(c);
}
Now, below is from the serial.c file. So my thought was that I should be able to just call one of these putchar functions in main.c and that it would work, but it still just echoes.
int putchar_serial0 (int ch)
{
if (ch == '\n')
{
while (!(U0LSR & 0x20));
U0THR = CR; // output CR
}
while (!(U0LSR & 0x20));
return (U0THR = ch);
}
// Write character to Serial Port 0 without \n -> \r\n
int putc_serial0 (int ch)
{
while (!(U0LSR & 0x20));
return (U0THR = ch);
}
// Write character to Serial Port 1 without \n -> \r\n
int putc_serial1 (int ch)
{
while (!(U1LSR & 0x20));
return (U1THR = ch);
}
void putstring_serial0 (const char *string)
{
char ch;
while ((ch = *string))
{
putchar_serial0(ch);
string++;
}
}
I have tried calling the different putchar functions in main, also with the rprintf_devopen. Still just echoes. I have altered the putchar functions and still just echoes. I have tried just writing to the U0THR register in main.c and no luck. Keep in mind that I am still a student and my major is electrical engineering, so the only programming classes that I have taken are intro to c, and an intro to vhdl. I am more of a math and physics guy. I was working on this for an internship I was doing. The internship ended, but it just bugs me that I cannot figure this out. Honestly, working on this program taught me more that the c class that I took. Anyways, I appreciate any help that can be offered, and let me know if you want to see the entire code.
Below is an update to the question. This function is in main.c
static void UART0ISR(void)
{
char temp;
trig = 13; //This is where you set the trigger character in decimal, in this case a carriage return.
temp = U0RBR; //U0RBR is the receive buffer on the chip, refer to datasheet.
if(temp == query1[counter1]) //This segment looks for the characters "ID?" from the U0RBR
{ //query1 is defined at the top of the program
counter1++;
if(counter1 >= 3)
{
flag1 = 1; //This keeps track of whether or not query1 was found
counter1 = 0;
stat(1,ON);
delay_ms(50);
stat(1,OFF);
RX_in = 0;
temp = 0;
//rprintf("\n\rtransmission works\n");
putc_serial1(49);
}
}
if(temp == query2[counter2] && flag1 == 1) //This segment looks for "protov?" from the U0RBR, but only after query1 has been found
{
counter2++;
if(counter2 >= 7)
{
flag2 = 1; //This keeps track of whether or not query2 was found
counter2 = 0;
stat(1,ON);
delay_ms(50);
stat(1,OFF);
RX_in = 0;
temp = 0;
putc_serial1(49);
}
}
if(temp == stop[counter3]) //This if segment looks for certain characters in the receive buffer to stop logging
{
counter3++;
if(counter3 >= 2)
{
flagstop = 1; //This flagstop keeps track of whether or not stop was found. When the stop characters are found,
flag1 = 0; //the query1 and query2 flags will be reset. So, in order to log again these queries must be sent again
flag2 = 0; //this may seem obvious, but deserves mention.
counter3 = 0;
stat(1,ON);
delay_ms(500);
stat(1,OFF);
RX_in = 0;
temp = 0;
}
flagstop = 0; //Reset the stop flag in order to wait once again for the query 1&2
}
if(RX_in == 0)
{
memset (RX_array1, 0, 512); // This clears the RX_array to make way for new data
memset (RX_array2, 0, 512);
}
if(RX_in < 512 && flag1 == 1 && flag2 == 1) //We cannot log data until we see both flags 1 & 2 and after we see these flags,
{ //we must then see the trigger character "carriage return"
RX_array1[RX_in] = temp;
RX_in++;
if(temp == trig)
{
RX_array1[RX_in] = 10; // delimiters
log_array1 = 1;
RX_in = 0;
}
}
else if(RX_in >= 512 && flag1 == 1 && flag2 == 1) //This else if is here in case the RX_in is greater than 512 because the RX_arrays are defined to
{ //be of size 512. If this happens we don't want to lose data, so we must put the overflow into another register.
RX_array2[RX_in - 512] = temp;
RX_in++;
RX_array1[512] = 10; // delimiters
RX_array1[512 + 1] = 13;
log_array1 = 1;
if(RX_in == 1024 || temp == trig)
{
RX_array2[RX_in - 512] = 10; // delimiters
log_array2 = 1;
RX_in = 0;
}
}
temp = U0IIR; // have to read this to clear the interrupt
VICVectAddr = 0;
}

Related

String parsing, turn LED on/off based on received serial data

I am receiving in the serial port from python a string in this format (0,77,88,55).
I have the first number randomized to either be 0 or 1, the rest are fixed.
import random
import serial
import time
last_time = 0
test_list = [0,1]
serialSTM32 = serial.Serial('COM22',9600,writeTimeout=0)
while True:
if (time.time() - last_time) > 2:
random_num = random.choice(test_list)
#string = str(random_num)
string = "(" + str(random_num) + "," + "77" + "," + "88" + "," + "55" + ")"
serialSTM32.write(string.encode('utf-8'))
print(string)
last_time = time.time()
serialSTM32.flushInput()
The STM32 MCU is receiving through the virtual serial COM port the data through a variable in the "usbd_cdc_if.c" file:
uint8_t bufferVariable = 0;
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
bufferVariable = Buf[0];
return (USBD_OK);
}
The bufferVariable is passed to the "main.c" file using:
extern uint8_t bufferVariable;
The "main.c" is then parsing the data, splitting the data and placing the first number which is '0' or '1' in a variable named "first", the second number which is '77' in a variable named "second" etc.
I am taking the "first" variable to see if it is '1' to turn on the LED, but if it is '0' to turn off the LED.
Program waits for start market "(" and keeps reading until it reaches end marker ")", then parses the data in between the two. **Works well on Arduino from parsing tutorial a while back, i converted everything to work on STM32 MCU....
**For this STM32 controller, previously, I tested without the parsing, only receiving '1' or '0' through serial to turn on/off the LED which works.....but now I am using the format (0,77,88,55) which includes parsing the data then getting the value extracted from that format. **This part is not working....LED does not turn on or off.
"main.c" file code:
#define buffer_size 40
char input_buffer[buffer_size];
const char start_marker = '(';
const char end_marker = ')';
uint8_t bytes_received = 0;
uint8_t read_in_progress = 0;
char* grab_value(char *data, char separator, int index) {
int found = 0;
int string_index[] = { 0, -1 };
int maximum_index = strlen(data) - 1;
for (int i = 0; i <= maximum_index && found <= index; i++) {
if (data[i] == separator || i == maximum_index) {
found++;
string_index[0] = string_index[1] + 1;
string_index[1] = (i == maximum_index) ? i + 1 : i;
}
}
if (found > index) {
data[string_index[1]] = '\0';
return &data[string_index[0]];
} else {
return NULL;
}
}
int first, second, third, fourth;
extern uint8_t bufferVariable;
int main(void) {
while (1) {
if (bufferVariable == end_marker) {
read_in_progress = 0;
input_buffer[bytes_received] = '\0';
}
if (read_in_progress) {
input_buffer[bytes_received++] = bufferVariable;
if (bytes_received == buffer_size) {
bytes_received = buffer_size - 1;
}
}
if (bufferVariable == start_marker) {
bytes_received = 0;
read_in_progress = 1;
}
char *str_first = grab_value(input_buffer, ',', 0);
char *str_second = grab_value(input_buffer, ',', 1);
char *str_third = grab_value(input_buffer, ',', 2);
char *str_fourth = grab_value(input_buffer, ',', 3);
if (str_first) {
first = atoi(str_first);
}
if (str_second) {
second = atoi(str_second);
}
if (str_third) {
third = atoi(str_third);
}
if (str_fourth) {
fourth = atoi(str_fourth);
}
printf("first: %d\n", first);
if (first == '0') {
HAL_GPIO_WritePin(GPIOK, GPIO_PIN_3, GPIO_PIN_SET);
} else if (first == '1') {
HAL_GPIO_WritePin(GPIOK, GPIO_PIN_3, GPIO_PIN_RESET);
}
}
}
first == '0'
It is completely wrong. '0' is not zero integer value only 48 in ASCII representing the character '0'.
Do not reinvent the wheel.
After the whole sting reception simply:
if(sscanf(inputBuffer, "(%d,%d,%d,%d)", &first, &second, &third, &fourth) == 4)
{
if(!first) HAL_GPIO_WritePin(GPIOK, GPIO_PIN_3, GPIO_PIN_SET);
else HAL_GPIO_WritePin(GPIOK, GPIO_PIN_3, GPIO_PIN_RESET);
}
STM32 uCs have much more resources than AVR uCs used in Arduino and you do not need any "tricks"
Only make sure that inputBuffer is null character terminated
Disclaimer, this is my personal preference:
For embedded, you should never use blocking I/O, always buffered, interrupt-driven I/O. I cannot say a single thing in favor of blocking I/O except it's simpler.
You say the problem is, that the LED doesn't toggle.
Let's look at the facts.
This check controls whether or not to toggle the LED:
if (first == '0') {
HAL_GPIO_WritePin(GPIOK, GPIO_PIN_3, GPIO_PIN_SET);
} else if (first == '1') {
HAL_GPIO_WritePin(GPIOK, GPIO_PIN_3, GPIO_PIN_RESET);
}
I notice that you're comparing an integer with the char-values for 0 and 1 and not the integer values. You're essentially checking if first == 48/49 instead of 0/1 (see integer values https://www.asciitable.com/)
At the same time, you're converting the received string to an integer by doing
first = atoi(str_first);
Compare first against 0 or 1 instead of '0' and '1' (which are 48 and 49 respectively).

Not Equals Operator not working when interrupts are enabled on PIC32 [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
Improve this question
I am currently trying to parse a string received via UART from a GPS module on a PIC32MZ2048EFG100 micro-controller via a UART receive interrupt protocol. I am using MPLAB X IDE v4.10 and XC32 v2.05 for my IDE and compiler.
When I enabled the UART4 receive interrupt, the != operator stops functioning as it should. I have a block of code within my main function that should never be executed, but yet it is.
I have narrowed it down to enabling interrupts being the problem. If I comment out all working code in my ISR, I still face the issue of myCounter being incremented.
Here is the code I have in my main function.
int myCounter = 0;
void main ( void ){
hal_sys_init();
hal_uart4_init();
gps_init();
//Setup interrupt
asm volatile("di"); //Disable all interrupts
asm volatile("ehb"); //Disable all interrupts
INTCON = 0; //Clear interrupt control register
IEC0 = 0;
IEC1 = 0;
IEC2 = 0;
IEC3 = 0;
IEC4 = 0;
IEC5 = 0;
IEC6 = 0;
INTCONbits.MVEC = 1; //Enable multi-vectored interrupts
IFS5bits.U4RXIF = 0; //Clear interrupt flag
IPC42bits.U4RXIP = 1; //Set priority level to 7
IPC42bits.U4RXIS = 0; //Set sub-priority to 0
IEC5bits.U4RXIE = 1; //Enable interrupt
asm volatile("ei"); //Enable all interrupts
while(1){
int x = 0;
if(x != 0){
myCounter++; //Should never be executed
}
}
}
When running this code on my PIC with interrupts enabled, myCounter gets incremented.
Here is the code for my interrupt service routine.
void __ISR(_UART4_RX_VECTOR, ipl7SRS) UART4_Interrupt(void) {
while (U4STAbits.URXDA) {
char c = U4RXREG;
if (c == '\n') {
currentLine[--lineIndex] = 0; //Overwrite /r as null terminator for string
parseStringFlag = 1;
lineIndex = 0;
if (currentLine == buff_1) {
currentLine = buff_2;
previousLine = buff_1;
} else {
currentLine = buff_1;
previousLine = buff_2;
}
} else if (lineIndex < MAX_LINE_LENGTH) {
currentLine[lineIndex++] = c;
} else {
currentLine[--lineIndex] = c;
}
}
IFS5bits.U4RXIF = 0; //Clear interrupt flag
return;
}
Here is the basic ISR code that still makes myCounter increment.
void __ISR(_UART4_RX_VECTOR, ipl7SRS) UART4_Interrupt(void) {
while (U4STAbits.URXDA) {
char c = U4RXREG;
}
IFS5bits.U4RXIF = 0; //Clear interrupt flag
return;
}
What could be causing the code that should never be executed to execute? If I run the interrupt code in main with interrupts disabled the code works and the code that should never be executed is not executed.
Here:
if (c == '\n') {
currentLine[--lineIndex] = 0; //Overwrite /r as null terminator for string
If the first character received were \n and lineIndex is initialised zero, lineIndex will be decremeted from zero. Assuming it is unsigned, then lineIndex < MAX_LINE_LENGTH will be false and:
} else {
currentLine[--lineIndex] = c;
}
will run repeatedly until lineIndex is eventually decremented to MAX_LINE_LENGTH - 1 - stomping over a large swathe of memory - which is most likely what is happening in this case.
Suggest:
if( lineIndex != 0 && c == '\n' && )
{
currentLine[--lineIndex] = 0; //Overwrite /r as null terminator for
or:
if( c == '\n' )
{
if( lineIndex != 0 )
{
lineindex-- ;
}
currentLine[lineIndex] = 0; //Overwrite /r as null terminator for
depending on the semantics you require. It is not a given that the sending system uses CR+LF pairs for line-ends, and you should not assume that. The code should probably be further modified to check that the preceding character was indeed a CR before decrementing lineindex. An exercise for the reader.
And similarly for the final else:
}
else if( lineIndex != 0 )
{
currentLine[--lineIndex] = c;
}
or
}
else
{
if( lineIndex != 0 )
{
lineindex-- ;
}
currentLine[lineIndex] = c;
}
It is possible that the latter protection is not necessary, but the protection is useful perhaps for clarity and maintenance - it is defensive code - your call.
It may be a safer and more interrupt efficient design to have the ISR simply place any received character into a ring-buffer, and then deal with line-input outside of the interrupt context. You might increment a counter on every received \n and decrement it when \n were unbuffered so that the receiver will know how many lines are currently buffered for processing.

how can I escape from main while loop(C programming)?

I am trying to escape from the main loop if external stop key is pressed.
Currently, communicating AT32UC with ATmega128 through RS485 communication where START and STOP keys are implemented.
RS485 receiver interrupt is called if there is data to be processed in the receiver side where 0x10 = Start and 0x11 = Stop.
My problem is that start and stop keys are recognised well and main loop is continued if start and I would like to terminate the main loop if stop key is pressed.
So I've set the start flag and stop flag accordingly. But, I am struggling with stop(escape) implementation. Below is brief snippets of interrupt routine and main loop.
__attribute__((__interrupt__)) static void rs485RxInterrupt(void)
{
uint32_t data;
static char RxDatalength = 98;
data = AVR32_USART2.RHR.rxchr;
if(data & 0x00000100) // rs485 9 bit check
{
if((data & 0x000000ff) == 0x92) //dsp board address = 0x92
{
rxBuf[0] = data;
addr_flag = true;
rxInCtr = 1;
}
else
{
addr_flag = false;
return;
}
}
else if (addr_flag == true) // if 9 bit is checked
{
rxBuf[rxInCtr++] = data;
if(rxInCtr == 2) // command check
{
if(data < 0x80)
{
if(data==0x10) // start command
{
addr_flag = false; // reset addr flag
start_flag = true;
//RxDatalength = 0;
}
else if(data == 0x11) // stop command
break_flag = true;
}
else if(data >= 0x80)
//gpio_set_pin_high (AVR32_PIN_PA16);
RxDatalength = 3;
}
if ((rxInCtr == RxDatalength) || ((RxDatalength == 98) && (rxInCtr == rxBuf[2]+1))) // end of packet recognition
{
addr_flag = false;
start_flag = true;
}
}
}
int main()
{
......
while(!break_flag)
{
start_flag = false;
while(start_flag == false)
;
gpio_set_pin_high(AVR32_PIN_PA14);
delay_us(40);
gpio_set_pin_low(AVR32_PIN_PA14);
//****** loop stays at this point and I am not giving sync_flag high to
//continue so if I press stop, I want this thing to get out of the main
//while loop!!
// peaksRdy_flag = true;
// SendTx(peaks);
sync_flag = false; // synchronising main with start of the input
while(sync_flag == false)
;
envelopeIndex = 0;
for(uint32_t loop=0; loop<23; loop++) // looping 23 times to cover approx 4.5s
{
//reset counter
sampleCounter = 0;
samplingComplete = false;
//wait for sampling to finish, 256 samples
while (samplingComplete == false)
;
//gpio_set_pin_low(AVR32_PIN_PA15); // main loop indicator
windowing(x);
rms(x); // return ac_rms
//gpio_set_pin_low(AVR32_PIN_PA16); // fft indicator
fft_run(window); // return fft magnitude
//gpio_set_pin_high(AVR32_PIN_PA16);
peak_search(fft_mag);
envelope_output(envelope);
// Function to transmit analysed data through RS485 communication.
//SendTx(peaks);
sprintf(filtResult, "%04d %04d %04d %04d %04d\n", (int)peaks[loop][0], (int)peaks[loop][1], (int)peaks[loop][2], (int)peaks[loop][3],(int)ac_rms);
char *ptr = &filtResult[0];
do
{
c = *ptr;
ptr++;
usart_bw_write_char(&AVR32_USART2, (int)c);
// sendByte(c);
} while (c != '\n');
//gpio_set_pin_high(AVR32_PIN_PA15);
} // outer loop
sprintf(filtResult, "%04d\n", (int)duty);
char *ptr = &filtResult[0];
do
{
c = *ptr;
ptr++;
usart_bw_write_char(&AVR32_USART2, (int)c);
// sendByte(c);
} while (c != '\n');
break;
}//while
}//main
All your flags should be declared volatile.
eg:
volatile int start_flag, sync_flag /*,other_flag ... */;
else the compiler may optimise out checks for their value being changed by code outside the current block.

Confused with output to console, C / USB CDC / PIC18F2550

I have a problem that is probably a simple misunderstanding on my end.
I have a PIC18F2550 device with a USB CDC firmware. I would like to send it a command to output something to the console every second. However, it doesn't seem to work. I put in a loop to iterate for 5 seconds and display an output message every second, but it won't actually output anything. It passes through the 5 second loop until the end where it DOES display the final message after the loop was executed. It won't output anything DURING the loop though.
I included my entire ProcessIO function because I think it's important for this issue, but I commented where I placed the exact command I'm trying to figure out.
Thanks for any suggestions you guys have, I appreciate it. I'm a mechanical engineer trying to learn some embedded stuff.
/********************************************************************
* Function: void ProcessIO(void)
* Overview: This function is a place holder for other user
* routines. It is a mixture of both USB and
* non-USB tasks.
*******************************************************************/
void ProcessIO(void)
{
BYTE numBytesRead;
// User Application USB tasks
if((USBDeviceState < CONFIGURED_STATE)||(USBSuspendControl==1)) return;
if (USBUSARTIsTxTrfReady())
{
if((CDCattached == 0x01) && (CDCattachedcount == 2))
{
putUSBUSART((char*)"Text Message\r\n",49);
CDCTxService();
CDCattachedcount = 0;
CDCattached = 0x00;
}
numBytesRead = getsUSBUSART(USB_Out_Buffer, 64);
if (numBytesRead == 1)
{
if ((USB_Out_Buffer[0] == '\r')) //Received ENTER? Ues-> End of Command
{
if (pos >0)
{
command_recvd = 0x01;
Command[pos++] = '\0';
pos = 0;
}
}
else if ((USB_Out_Buffer[0] == 0x7F) || (USB_Out_Buffer[0] == 0x08))
{
if (pos > 0) pos--;
Command[pos] = '\0';
putUSBUSART ((char*) USB_Out_Buffer, 1);
}
else
{
Command[pos++] = USB_Out_Buffer[0];
putUSBUSART((char*) USB_Out_Buffer, 1);
Command[pos]='\0';
} //No:- Store Character to String
}
else if ((numBytesRead > 1))
{
strncpy(Command,USB_Out_Buffer,numBytesRead);
for(int indx = numBytesRead; indx < 64; indx++)
{
Command[indx]='\0';
}
pos = numBytesRead--;
command_recvd = 0x01;
Command[pos++] = '\0';
// putUSBUSART((char*) USB_Out_Buffer, 1);
pos = 0;
}
if (command_recvd == 0x01)
{
for (int aaa = 0; aaa <= 63; aaa++)
{
output_message[aaa]= '\0';
}
************** THIS IS WHERE MY TEST COMMAND IS ***************
if (strnicmp((char*) Command, (char*) "test", 4) == 0)
{
sprintf(output_message, "\r\nStarting loop...\r\n");
for int bbb = 0; bbb < 5; bbb++)
{
sprintf(output_message, "\r\nLooping...\r\n");
for (delayIndex = 0; delayIndex < 1000; delayIndex++)
{
__delay_ms(1);
}
}
sprintf(output_message, "\r\nLoop finished!\r\n");
}
else
{
invalidCommand:
sprintf(output_message, "\r\nInvalid Command Received. Please Retry.\r\n\0");
}
command_recvd = 0x00;
}
}
CDCTxService();
}
You should call putUSBUSART() and CDCTxService() before overwriting output_message
Also, CDCTxService() needs to be called frequently, so you have to call it during the delay loop.
for int bbb = 0; bbb < 5; bbb++)
{
sprintf(output_message, "\r\nLooping...\r\n");
putsUSBUSART(output_message);
for (delayIndex = 0; delayIndex < 1000; delayIndex++)
{
__delay_ms(1);
if(USBUSARTIsTxTrfReady()) {
sprintf(output_message, "\r\nInside inner loop\r\n");
putsUSBUSART(output_message);
}
CDCTxService();
}
}
Although that kind of bloking delays could work ( __delay_ms() ), a better aproach is to check for an ellapsed timer, or a timestamp. Something like:
for int bbb = 0; bbb < 5; bbb++)
{
sprintf(output_message, "\r\nLooping...\r\n");
putsUSBUSART(output_message);
timestamp = TickGet();
while (TickDiff(timestamp, TickGet()) < TICK_SECOND)
{
if(USBUSARTIsTxTrfReady()) {
sprintf(output_message, "\r\nInside inner loop\r\n");
putsUSBUSART(output_message);
}
CDCTxService();
}
}
TickGet and TickDiff are functions you have to implement yourself, however there are lots of examples on Microchip libraries
Short version: this approach won't work. You'll need to rethink how you're doing this.
USB devices cannot delay while processing events — they must be able to respond promptly to every request sent from the host. Delaying for as long as a second will typically cause the host to assume the device has been disconnected.
It's critical to understand here that the USB device model is based (almost) entirely around the host sending requests to a device, and the device replying. Devices cannot generate unsolicited responses.
Now, the reason you're not seeing the expected results here is because sprintf() doesn't send the results to the host; all it does is put a message into a buffer to prepare it to be sent back. Calling it multiple times overwrites that buffer, rather than sending multiple messages back.

Missing events on ReadConsoleInput in windows shell?

As I proceed in my (possibly vain) attempt to reimplement a curses style library that supports both *nix and windows under an MIT license, I've stumbled onto a problem reading terminal import using the windows api.
Basically, I don't get all the events I expect to, and I don't know why.
First I setup the terminal to be in non-buffering mode:
DWORD mode;
HANDLE hstdin = GetStdHandle( STD_INPUT_HANDLE );
// Save old mode
GetConsoleMode(hstdin, &mode);
// Set to no line-buffering, no echo, no special-key-processing
SetConsoleMode(hstdin, 0);
Then I use PeekConsoleInput and ReadConsoleInput in a loop to have a non blocking key press input; the equivalent of using termios.h and select on stdin in linux:
__EXPORT int sterm_read(void *state) {
DWORD dwRead;
INPUT_RECORD inRecords[1];
PeekConsoleInput(GetStdHandle(STD_INPUT_HANDLE), &inRecords[0], 1, &dwRead);
if (dwRead > 0) {
ReadConsoleInput(GetStdHandle(STD_INPUT_HANDLE), &inRecords[0], 1, &dwRead);
if (inRecords[0].EventType == KEY_EVENT) {
if (inRecords[0].Event.KeyEvent.bKeyDown) {
return inRecords[0].Event.KeyEvent.wVirtualKeyCode;
}
}
}
return -1;
}
Ignore the state variable; that's so the api can accept an arbitrary state struct on various platforms.
Now if I try to use this code:
#include <sterm.h>
#include <stdio.h>
#define assert(v, msg) if (!v) { printf("FAILED! %s", msg); return 1; }
int main(void) {
void *state = sterm_init();
int i;
char c;
for (;;) {
if ((c = sterm_read(state)) == 81) { // ie. press q to exit
break;
}
if (c != -1) {
sterm_write(state, &c, 1); // This is a thin wrapper around _write(1, ...)
}
}
sterm_shutdown(state);
return 0;
}
It almost works. I get the input character I press pushed out to the terminal... mostly.
Probably every 10th character press is recorded. If I type quickly, the API 'loses' events, and I get "HEO WLD" instead of "HELLO WORLD".
What's going on? Does ReadConsoleInput somehow clear the input buffer?
Am I doing something wrong? It seems almost like I'm only getting events based on a race condition which is 'is key pressed when PeekConsoleInput is called'.
...but surely that shouldn't be the case? The point of using these buffered I/O interfaces (instead of GetAsyncKeyState) is that the events should be buffered right?
Help!
I also have discovered that events are not guaranteed to stay around to be read.
This makes sense because otherwise the OS would need to provide lots and lots of buffering space.
The best I can do to deal with this is this code to do my own buffering
but clearly pastes of more than 128 characters will often fail :
static int g_eaten_ct = 0; /* Re-eaten char */
static int g_eaten_ix = -1;
static int g_eaten[128];
void reeat(int c)
{ g_eaten_ct += 1;
g_eaten[g_eaten_ix + g_eaten_ct] = c; /* save the char for later */
}
void flush_typah()
{
g_eaten_ct = 0;
g_eaten_ix = -1;
while (_kbhit())
(void)ttgetc();
}
int ttgetc()
{ if (g_eaten_ct > 0)
{ g_eaten_ct -= 1;
return g_eaten[++g_eaten_ix];
}
{ int totalwait = g_timeout_secs;
int oix = -1;
while (1)
{ int got,need;
const DWORD lim = 1000;
INPUT_RECORD rec[32];
int cc = WaitForSingleObject(g_ConsIn, lim);
switch(cc)
{ case WAIT_OBJECT_0:
need = sizeof(g_eaten)/sizeof(g_eaten[0]) - oix;
if (need > 32)
need = 32;
cc = ReadConsoleInput(g_ConsIn,&rec[0],need,(DWORD*)&got);
if (cc && got > 0)
break;
#if _DEBUG
{ DWORD errn = GetLastError();
if (errn != 6)
mlwrite("%pError %d %d ", cc, errn);
}
#endif
continue;
case WAIT_TIMEOUT:
#if _DEBUG
if (g_got_ctrl)
{ g_got_ctrl = false;
return (int)(CTRL | 'C');
}
#endif
if (--totalwait == 0) // -w opt
exit(2);
// drop through
default:continue;
}
{ int ix = -1;
while (++ix < got)
{ INPUT_RECORD * r = &rec[ix];
if (r->EventType == KEY_EVENT && r->Event.KeyEvent.bKeyDown)
{ int ctrl = 0;
int keystate = r->Event.KeyEvent.dwControlKeyState;
if (keystate & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED))
{ ctrl |= CTRL;
g_chars_since_ctrl = 0;
}
{ int chr = r->Event.KeyEvent.wVirtualKeyCode;
if (in_range(chr, 0x10, 0x12))
continue; /* shifting key only */
if (keystate & (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED))
ctrl |= ALTD;
else
chr = r->Event.KeyEvent.uChar.AsciiChar & 0xff;
if (/*chr != 0x7c && */ (chr | 0x60) != 0x7c) // | BSL < or ^ BSL
{ int vsc = r->Event.KeyEvent.wVirtualScanCode;
if (in_range(vsc, SCANK_STT, 0x58))
{ ctrl |= SPEC;
chr = scantokey[vsc - SCANK_STT];
}
// else if (in_range(vsc, 2, 10) && chr == 0)
// chr = '0' - 1 + vsc;
}
if ((keystate & SHIFT_PRESSED) && ctrl) // exclude e.g. SHIFT 7
ctrl |= SHFT;
g_eaten[++oix] = ctrl | (chr == 0xdd ? 0x7c : chr);
++g_chars_since_ctrl;
}}
else if (r->EventType == MENU_EVENT)
{ /*loglog1("Menu %x", r->Event.MenuEvent.dwCommandId);*/
}
}
if (got == need && oix < sizeof(g_eaten) / sizeof(int))
{ PeekConsoleInput(g_ConsIn, &rec[0], 1, (DWORD*)&got);
if (got > 0)
continue;
}
if (oix >= 0)
{ g_eaten_ct = oix;
g_eaten_ix = 0;
return g_eaten[0];
}
}}
}}

Resources