Commit of new class abstraction APIs for all device demos other than the MIDI demo - not documented yet.
Removed scheduler and memory allocation libraries. Added new EVENT_USB_StartOfFrame event in the library to indicate the start of each USB frame (when generated). Removed Tx interrupt from the USBtoSerial demo; now sends characters via polling to ensure more time for the Rx interrupt.
This commit is contained in:
parent
2440ca268a
commit
d1e5266036
106 changed files with 3072 additions and 5760 deletions
|
@ -28,60 +28,37 @@
|
|||
arising out of or in connection with the use or performance of
|
||||
this software.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Main source file for the MagStripe application. This file contains the code which drives
|
||||
* the USB keyboard interface from the magnetic card stripe reader device.
|
||||
*/
|
||||
|
||||
#include "Magstripe.h"
|
||||
|
||||
/* Scheduler Task List */
|
||||
TASK_LIST
|
||||
{
|
||||
{ .Task = USB_USBTask , .TaskStatus = TASK_STOP },
|
||||
{ .Task = USB_Keyboard_Report , .TaskStatus = TASK_STOP },
|
||||
{ .Task = Magstripe_Read , .TaskStatus = TASK_STOP },
|
||||
};
|
||||
BitBuffer_t TrackDataBuffers[3];
|
||||
|
||||
/* Global Variables */
|
||||
/** Indicates if the device is using Report Protocol mode, instead of Boot Protocol mode. Boot Protocol mode
|
||||
* is a special reporting mode used by compatible PC BIOS to support USB keyboards before a full OS and USB
|
||||
* driver has been loaded, by using predefined report structures indicated in the USB HID standard.
|
||||
*/
|
||||
bool UsingReportProtocol = true;
|
||||
USB_ClassInfo_HID_t Keyboard_HID_Interface =
|
||||
{
|
||||
.InterfaceNumber = 0,
|
||||
|
||||
/** Total idle period in milliseconds set by the host via a SetIdle request, used to silence the report endpoint
|
||||
* until the report data changes or the idle period elapsed. Generally used to implement hardware key repeats, or
|
||||
* by some BIOS to reduce the number of reports when in Boot Protocol mode.
|
||||
*/
|
||||
uint8_t IdleCount = 0;
|
||||
.ReportINEndpointNumber = KEYBOARD_EPNUM,
|
||||
.ReportINEndpointSize = KEYBOARD_EPSIZE,
|
||||
};
|
||||
|
||||
/** Milliseconds remaining counter for the HID class SetIdle and GetIdle requests, used to silence the report
|
||||
* endpoint for an amount of time indicated by the host or until the report changes.
|
||||
*/
|
||||
uint16_t IdleMSRemaining = 0;
|
||||
|
||||
/** Circular buffer to hold the read bits from track 1 of the inserted magnetic card. */
|
||||
BitBuffer_t Track1Data;
|
||||
|
||||
/** Circular buffer to hold the read bits from track 2 of the inserted magnetic card. */
|
||||
BitBuffer_t Track2Data;
|
||||
|
||||
/** Circular buffer to hold the read bits from track 3 of the inserted magnetic card. */
|
||||
BitBuffer_t Track3Data;
|
||||
|
||||
/** Delay counter between successive key strokes. This is to prevent the OS from ignoring multiple keys in a short
|
||||
* period of time due to key repeats. Two milliseconds works for most OSes.
|
||||
*/
|
||||
uint8_t KeyDelayRemaining;
|
||||
|
||||
|
||||
/** Main program entry point. This routine configures the hardware required by the application, then
|
||||
* starts the scheduler to run the application tasks.
|
||||
*/
|
||||
int main(void)
|
||||
{
|
||||
SetupHardware();
|
||||
|
||||
for (uint8_t Buffer = 0; Buffer < 3; Buffer++)
|
||||
BitBuffer_Init(&TrackDataBuffers[Buffer]);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (Magstripe_GetStatus() & MAG_CARDPRESENT)
|
||||
ReadMagstripeData();
|
||||
|
||||
USB_HID_USBTask(&Keyboard_HID_Interface);
|
||||
USB_USBTask();
|
||||
}
|
||||
}
|
||||
|
||||
void SetupHardware(void)
|
||||
{
|
||||
/* Disable watchdog if enabled by bootloader/fuses */
|
||||
MCUSR &= ~(1 << WDRF);
|
||||
|
@ -92,330 +69,97 @@ int main(void)
|
|||
|
||||
/* Hardware Initialization */
|
||||
Magstripe_Init();
|
||||
|
||||
/* Buffer Initialization */
|
||||
BitBuffer_Init(&Track1Data);
|
||||
BitBuffer_Init(&Track2Data);
|
||||
BitBuffer_Init(&Track3Data);
|
||||
|
||||
/* Millisecond timer initialization, with output compare interrupt enabled for the idle timing */
|
||||
OCR0A = 0xFA;
|
||||
TCCR0A = (1 << WGM01);
|
||||
TCCR0B = ((1 << CS01) | (1 << CS00));
|
||||
TIMSK0 = (1 << OCIE0A);
|
||||
|
||||
/* Initialize Scheduler so that it can be used */
|
||||
Scheduler_Init();
|
||||
|
||||
/* Initialize USB Subsystem */
|
||||
USB_Init();
|
||||
|
||||
/* Scheduling - routine never returns, so put this last in the main function */
|
||||
Scheduler_Start();
|
||||
}
|
||||
|
||||
/** Event handler for the USB_Connect event. This starts the USB task. */
|
||||
void EVENT_USB_Connect(void)
|
||||
{
|
||||
/* Start USB management task */
|
||||
Scheduler_SetTaskMode(USB_USBTask, TASK_RUN);
|
||||
}
|
||||
|
||||
/** Event handler for the USB_Disconnect event. This stops the USB and keyboard report tasks. */
|
||||
void EVENT_USB_Disconnect(void)
|
||||
{
|
||||
/* Stop running keyboard reporting, card reading and USB management tasks */
|
||||
Scheduler_SetTaskMode(USB_Keyboard_Report, TASK_STOP);
|
||||
Scheduler_SetTaskMode(USB_USBTask, TASK_STOP);
|
||||
Scheduler_SetTaskMode(Magstripe_Read, TASK_STOP);
|
||||
}
|
||||
|
||||
/** Event handler for the USB_ConfigurationChanged event. This configures the device's endpoints ready
|
||||
* to relay reports to the host, and starts the keyboard report task.
|
||||
*/
|
||||
void EVENT_USB_ConfigurationChanged(void)
|
||||
{
|
||||
/* Setup Keyboard Keycode Report Endpoint */
|
||||
Endpoint_ConfigureEndpoint(KEYBOARD_EPNUM, EP_TYPE_INTERRUPT,
|
||||
ENDPOINT_DIR_IN, KEYBOARD_EPSIZE,
|
||||
ENDPOINT_BANK_SINGLE);
|
||||
|
||||
/* Default to report protocol on connect */
|
||||
UsingReportProtocol = true;
|
||||
|
||||
/* Start Keyboard reporting and card reading tasks */
|
||||
Scheduler_SetTaskMode(USB_Keyboard_Report, TASK_RUN);
|
||||
Scheduler_SetTaskMode(Magstripe_Read, TASK_RUN);
|
||||
}
|
||||
|
||||
/** Event handler for the USB_UnhandledControlPacket event. This is used to catch standard and class specific
|
||||
* control requests that are not handled internally by the USB library, so that they can be handled appropriately
|
||||
* for the application.
|
||||
*/
|
||||
void EVENT_USB_UnhandledControlPacket(void)
|
||||
{
|
||||
/* Handle HID Class specific requests */
|
||||
switch (USB_ControlRequest.bRequest)
|
||||
{
|
||||
case REQ_GetReport:
|
||||
if (USB_ControlRequest.bmRequestType == (REQDIR_DEVICETOHOST | REQTYPE_CLASS | REQREC_INTERFACE))
|
||||
{
|
||||
USB_KeyboardReport_Data_t KeyboardReportData;
|
||||
|
||||
/* Create the next keyboard report for transmission to the host */
|
||||
GetNextReport(&KeyboardReportData);
|
||||
|
||||
Endpoint_ClearSETUP();
|
||||
|
||||
/* Write the report data to the control endpoint */
|
||||
Endpoint_Write_Control_Stream_LE(&KeyboardReportData, sizeof(KeyboardReportData));
|
||||
|
||||
/* Finalize the stream transfer to send the last packet or clear the host abort */
|
||||
Endpoint_ClearOUT();
|
||||
}
|
||||
|
||||
break;
|
||||
case REQ_GetProtocol:
|
||||
if (USB_ControlRequest.bmRequestType == (REQDIR_DEVICETOHOST | REQTYPE_CLASS | REQREC_INTERFACE))
|
||||
{
|
||||
Endpoint_ClearSETUP();
|
||||
|
||||
/* Write the current protocol flag to the host */
|
||||
Endpoint_Write_Byte(UsingReportProtocol);
|
||||
|
||||
/* Send the flag to the host */
|
||||
Endpoint_ClearIN();
|
||||
|
||||
/* Acknowledge status stage */
|
||||
while (!(Endpoint_IsOUTReceived()));
|
||||
Endpoint_ClearOUT();
|
||||
}
|
||||
|
||||
break;
|
||||
case REQ_SetProtocol:
|
||||
if (USB_ControlRequest.bmRequestType == (REQDIR_HOSTTODEVICE | REQTYPE_CLASS | REQREC_INTERFACE))
|
||||
{
|
||||
Endpoint_ClearSETUP();
|
||||
|
||||
/* Set or clear the flag depending on what the host indicates that the current Protocol should be */
|
||||
UsingReportProtocol = (USB_ControlRequest.wValue != 0x0000);
|
||||
|
||||
/* Acknowledge status stage */
|
||||
while (!(Endpoint_IsINReady()));
|
||||
Endpoint_ClearIN();
|
||||
}
|
||||
|
||||
break;
|
||||
case REQ_SetIdle:
|
||||
if (USB_ControlRequest.bmRequestType == (REQDIR_HOSTTODEVICE | REQTYPE_CLASS | REQREC_INTERFACE))
|
||||
{
|
||||
Endpoint_ClearSETUP();
|
||||
|
||||
/* Get idle period in MSB */
|
||||
IdleCount = (USB_ControlRequest.wValue >> 8);
|
||||
|
||||
/* Acknowledge status stage */
|
||||
while (!(Endpoint_IsINReady()));
|
||||
Endpoint_ClearIN();
|
||||
}
|
||||
|
||||
break;
|
||||
case REQ_GetIdle:
|
||||
if (USB_ControlRequest.bmRequestType == (REQDIR_DEVICETOHOST | REQTYPE_CLASS | REQREC_INTERFACE))
|
||||
{
|
||||
Endpoint_ClearSETUP();
|
||||
|
||||
/* Write the current idle duration to the host */
|
||||
Endpoint_Write_Byte(IdleCount);
|
||||
|
||||
/* Send the flag to the host */
|
||||
Endpoint_ClearIN();
|
||||
|
||||
/* Acknowledge status stage */
|
||||
while (!(Endpoint_IsOUTReceived()));
|
||||
Endpoint_ClearOUT();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** ISR for the timer 0 compare vector. This ISR fires once each millisecond, and decrements the counter indicating
|
||||
* the number of milliseconds left to idle (not send the host reports) if the device has been instructed to idle
|
||||
* by the host via a SetIdle class specific request.
|
||||
*/
|
||||
ISR(TIMER0_COMPA_vect, ISR_BLOCK)
|
||||
{
|
||||
/* One millisecond has elapsed, decrement the idle time remaining counter if it has not already elapsed */
|
||||
if (IdleMSRemaining)
|
||||
IdleMSRemaining--;
|
||||
|
||||
if (KeyDelayRemaining)
|
||||
KeyDelayRemaining--;
|
||||
}
|
||||
|
||||
/** Constructs a keyboard report indicating the currently pressed keyboard keys to the host.
|
||||
*
|
||||
* \param ReportData Pointer to a USB_KeyboardReport_Data_t report structure where the resulting report should
|
||||
* be stored
|
||||
*
|
||||
* \return Boolean true if the current report is different to the previous report, false otherwise
|
||||
*/
|
||||
bool GetNextReport(USB_KeyboardReport_Data_t* ReportData)
|
||||
{
|
||||
static bool OddReport = false;
|
||||
static bool MustRelease = false;
|
||||
|
||||
BitBuffer_t* Buffer = NULL;
|
||||
|
||||
/* Clear the report contents */
|
||||
memset(ReportData, 0, sizeof(USB_KeyboardReport_Data_t));
|
||||
|
||||
/* Get the next non-empty track data buffer */
|
||||
if (Track1Data.Elements)
|
||||
Buffer = &Track1Data;
|
||||
else if (Track2Data.Elements)
|
||||
Buffer = &Track2Data;
|
||||
else if (Track3Data.Elements)
|
||||
Buffer = &Track3Data;
|
||||
|
||||
if (Buffer != NULL)
|
||||
{
|
||||
/* Toggle the odd report number indicator */
|
||||
OddReport = !OddReport;
|
||||
|
||||
/* Set the flag indicating that a null report must eventually be sent to release all pressed keys */
|
||||
MustRelease = true;
|
||||
|
||||
/* Only send the next key on odd reports, so that they are interspersed with null reports to release keys */
|
||||
if (OddReport)
|
||||
{
|
||||
/* Set the report key code to the key code for the next data bit */
|
||||
ReportData->KeyCode = BitBuffer_GetNextBit(Buffer) ? KEY_1 : KEY_0;
|
||||
|
||||
/* If buffer is now empty, a new line must be sent instead of the terminating bit */
|
||||
if (!(Buffer->Elements))
|
||||
{
|
||||
/* Set the keycode to the code for an enter key press */
|
||||
ReportData->KeyCode = KEY_ENTER;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MustRelease)
|
||||
{
|
||||
/* Leave key code to null (0), to release all pressed keys */
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Task to read out data from inserted magnetic cards and place the separate track data into their respective
|
||||
* data buffers for later sending to the host as keyboard key presses.
|
||||
*/
|
||||
TASK(Magstripe_Read)
|
||||
void ReadMagstripeData(void)
|
||||
{
|
||||
/* Arrays to hold the buffer pointers, clock and data bit masks for the separate card tracks */
|
||||
const struct
|
||||
{
|
||||
BitBuffer_t* Buffer;
|
||||
uint8_t ClockMask;
|
||||
uint8_t DataMask;
|
||||
} TrackInfo[] = {{&Track1Data, MAG_T1_CLOCK, MAG_T1_DATA},
|
||||
{&Track2Data, MAG_T2_CLOCK, MAG_T2_DATA},
|
||||
{&Track3Data, MAG_T3_CLOCK, MAG_T3_DATA}};
|
||||
uint8_t ClockMask;
|
||||
uint8_t DataMask;
|
||||
} TrackInfo[] = {{MAG_T1_CLOCK, MAG_T1_DATA},
|
||||
{MAG_T2_CLOCK, MAG_T2_DATA},
|
||||
{MAG_T3_CLOCK, MAG_T3_DATA}};
|
||||
|
||||
/* Previous magnetic card control line' status, for later comparison */
|
||||
uint8_t Magstripe_Prev = 0;
|
||||
|
||||
/* Buffered current card reader control line' status */
|
||||
uint8_t Magstripe_LCL = Magstripe_GetStatus();
|
||||
|
||||
/* Exit the task early if no card is present in the reader */
|
||||
if (!(Magstripe_LCL & MAG_CARDPRESENT))
|
||||
return;
|
||||
|
||||
/* Read out card data while a card is present */
|
||||
while (Magstripe_LCL & MAG_CARDPRESENT)
|
||||
{
|
||||
/* Read out the next bit for each track of the card */
|
||||
for (uint8_t Track = 0; Track < 3; Track++)
|
||||
{
|
||||
/* Current data line status for the current card track */
|
||||
bool DataLevel = ((Magstripe_LCL & TrackInfo[Track].DataMask) != 0);
|
||||
|
||||
/* Current clock line status for the current card track */
|
||||
bool ClockLevel = ((Magstripe_LCL & TrackInfo[Track].ClockMask) != 0);
|
||||
|
||||
/* Current track clock transition check */
|
||||
bool ClockChanged = (((Magstripe_LCL ^ Magstripe_Prev) & TrackInfo[Track].ClockMask) != 0);
|
||||
bool DataPinLevel = ((Magstripe_LCL & TrackInfo[Track].DataMask) != 0);
|
||||
bool ClockPinLevel = ((Magstripe_LCL & TrackInfo[Track].ClockMask) != 0);
|
||||
bool ClockLevelChanged = (((Magstripe_LCL ^ Magstripe_Prev) & TrackInfo[Track].ClockMask) != 0);
|
||||
|
||||
/* Sample the next bit on the falling edge of the track's clock line, store key code into the track's buffer */
|
||||
if (ClockLevel && ClockChanged)
|
||||
BitBuffer_StoreNextBit(TrackInfo[Track].Buffer, DataLevel);
|
||||
if (ClockPinLevel && ClockLevelChanged)
|
||||
BitBuffer_StoreNextBit(&TrackDataBuffers[Track], DataPinLevel);
|
||||
}
|
||||
|
||||
/* Retain the current card reader control line states for later edge detection */
|
||||
Magstripe_Prev = Magstripe_LCL;
|
||||
|
||||
/* Retrieve the new card reader control line states */
|
||||
Magstripe_LCL = Magstripe_GetStatus();
|
||||
}
|
||||
|
||||
/* Add terminators to the end of each track buffer */
|
||||
BitBuffer_StoreNextBit(&Track1Data, 0);
|
||||
BitBuffer_StoreNextBit(&Track2Data, 0);
|
||||
BitBuffer_StoreNextBit(&Track3Data, 0);
|
||||
}
|
||||
|
||||
/** Task for the magnetic card reading and keyboard report generation. This task waits until a card is inserted,
|
||||
* then reads off the card data and sends it to the host as a series of keyboard key presses via keyboard reports.
|
||||
*/
|
||||
TASK(USB_Keyboard_Report)
|
||||
void EVENT_USB_ConfigurationChanged(void)
|
||||
{
|
||||
USB_KeyboardReport_Data_t KeyboardReportData;
|
||||
bool SendReport = false;
|
||||
|
||||
/* Check if the USB system is connected to a host */
|
||||
if (USB_IsConnected)
|
||||
{
|
||||
/* Select the Keyboard Report Endpoint */
|
||||
Endpoint_SelectEndpoint(KEYBOARD_EPNUM);
|
||||
|
||||
/* Check if Keyboard Endpoint Ready for Read/Write */
|
||||
if (Endpoint_IsReadWriteAllowed())
|
||||
{
|
||||
/* Only fetch the next key to send once the period between key presses has elapsed */
|
||||
if (!(KeyDelayRemaining))
|
||||
{
|
||||
/* Create the next keyboard report for transmission to the host */
|
||||
SendReport = GetNextReport(&KeyboardReportData);
|
||||
}
|
||||
|
||||
/* Check if the idle period is set and has elapsed */
|
||||
if (IdleCount && !(IdleMSRemaining))
|
||||
{
|
||||
/* Idle period elapsed, indicate that a report must be sent */
|
||||
SendReport = true;
|
||||
|
||||
/* Reset the idle time remaining counter, must multiply by 4 to get the duration in milliseconds */
|
||||
IdleMSRemaining = (IdleCount << 2);
|
||||
}
|
||||
|
||||
/* Write the keyboard report if a report is to be sent to the host */
|
||||
if (SendReport)
|
||||
{
|
||||
/* Write Keyboard Report Data */
|
||||
Endpoint_Write_Stream_LE(&KeyboardReportData, sizeof(USB_KeyboardReport_Data_t));
|
||||
|
||||
/* Finalize the stream transfer to send the last packet */
|
||||
Endpoint_ClearIN();
|
||||
|
||||
/* Reset the key delay period counter */
|
||||
KeyDelayRemaining = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
USB_HID_ConfigureEndpoints(&Keyboard_HID_Interface);
|
||||
}
|
||||
|
||||
void EVENT_USB_UnhandledControlPacket(void)
|
||||
{
|
||||
USB_HID_ProcessControlPacket(&Keyboard_HID_Interface);
|
||||
}
|
||||
|
||||
void EVENT_USB_StartOfFrame(void)
|
||||
{
|
||||
USB_HID_RegisterStartOfFrame(&Keyboard_HID_Interface);
|
||||
}
|
||||
|
||||
uint16_t CALLBACK_USB_HID_CreateNextHIDReport(USB_ClassInfo_HID_t* HIDInterfaceInfo, void* ReportData)
|
||||
{
|
||||
static bool IsKeyReleaseReport;
|
||||
static bool IsNewlineReport;
|
||||
|
||||
BitBuffer_t* Buffer = NULL;
|
||||
USB_KeyboardReport_Data_t* KeyboardReport = (USB_KeyboardReport_Data_t*)ReportData;
|
||||
|
||||
/* Key reports must be interleaved with 0 Key Code reports to release the keys, or repeated keys will be ignored */
|
||||
IsKeyReleaseReport = !IsKeyReleaseReport;
|
||||
|
||||
if (IsKeyReleaseReport)
|
||||
{
|
||||
KeyboardReport->KeyCode = 0;
|
||||
}
|
||||
else if (IsNewlineReport)
|
||||
{
|
||||
IsNewlineReport = false;
|
||||
KeyboardReport->KeyCode = KEY_ENTER;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TrackDataBuffers[0].Elements)
|
||||
Buffer = &TrackDataBuffers[0];
|
||||
else if (TrackDataBuffers[1].Elements)
|
||||
Buffer = &TrackDataBuffers[1];
|
||||
else if (TrackDataBuffers[2].Elements)
|
||||
Buffer = &TrackDataBuffers[2];
|
||||
else
|
||||
return 0;
|
||||
|
||||
KeyboardReport->KeyCode = BitBuffer_GetNextBit(Buffer) ? KEY_1 : KEY_0;
|
||||
|
||||
/* If buffer now empty, next report must be a newline to seperate track data */
|
||||
if (!(Buffer->Elements))
|
||||
IsNewlineReport = true;
|
||||
}
|
||||
|
||||
return sizeof(USB_KeyboardReport_Data_t);
|
||||
}
|
||||
|
||||
void CALLBACK_USB_HID_ProcessReceivedHIDReport(USB_ClassInfo_HID_t* HIDInterfaceInfo, void* ReportData, uint16_t ReportSize)
|
||||
{
|
||||
// Unused (but mandatory for the HID class driver) in this demo, since there are no Host->Device reports
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue