c program
Agent.h
#ifndef AGENT_H #define AGENT_H #include <stdint.h> #include "Message.h" #include "BattleBoats.h" /** * Defines the various states used within the agent state machines. All states should be used * within a valid agent implementation. Additionally there is no need for states outside of * those defined here. */ typedef enum { AGENT_STATE_START, //0 AGENT_STATE_CHALLENGING, //1 AGENT_STATE_ACCEPTING, //2 AGENT_STATE_ATTACKING, //3 AGENT_STATE_DEFENDING, //4 AGENT_STATE_WAITING_TO_SEND, //5 AGENT_STATE_END_SCREEN, //6 //If implementing a human agent, you will need a state to setup boats: AGENT_STATE_SETUP_BOATS, //7 } AgentState; /** * The Init() function for an Agent sets up everything necessary for an agent before the game * starts. At a minimum, this requires: * -setting the start state of the Agent SM. * -setting turn counter to 0 * If you are using any other persistent data in Agent SM, that should be reset as well. * * It is not advised to call srand() inside of AgentInit. * */ void AgentInit(void); /** * AgentRun evolves the Agent state machine in response to an event. * * @param The most recently detected event * @return Message, a Message struct to send to the opponent. * * If the returned Message struct is a valid message * (that is, not of type MESSAGE_NONE), then it will be * passed to the transmission module and sent via UART. * This is handled at the top level! AgentRun is ONLY responsible * for generating the Message struct, not for encoding or sending it. */ Message AgentRun(BB_Event event); /** * * @return Returns the current state that AgentGetState is in. * * This function is very useful for testing AgentRun. */ AgentState AgentGetState(void); /** * * @param Force the agent into the state given by AgentState * * This function is very useful for testing AgentRun. */ void AgentSetState(AgentState newState); #endif // AGENT_H
Field.h
#ifndef FIELD_H #define FIELD_H #include <stdint.h> /** * Define the dimensions of the game field. They can be overridden by compile-time specifications. * All references to the dimensions of the field should use these constants instead of hard-coding * a numeric value so that the field dimensions can be changed with minimal coding changes. */ #ifndef FIELD_COLS #define FIELD_COLS 10 #endif #ifndef FIELD_ROWS #define FIELD_ROWS 6 #endif /** * Set different constants used for conveying different information about the different locations * of the field. These values should be used for the actual storage of the field state, which is * almost every usage. For displaying the field using FieldPrint(), the SquareStatus Display enum * values should be used instead. */ typedef enum { /// These denote field positions useful for representing the local board FIELD_SQUARE_EMPTY = 0, // An empty field position. FIELD_SQUARE_SMALL_BOAT, // This position contains part of the small boat. FIELD_SQUARE_MEDIUM_BOAT, // This position contains part of the medium boat. FIELD_SQUARE_LARGE_BOAT, // This position contains part of the large boat. FIELD_SQUARE_HUGE_BOAT, // This position contains part of the huge boat. /// These denote field positions useful for representing the enemy's board FIELD_SQUARE_UNKNOWN, // It is unknown what is here. Useful for denoting a position on the // enemy's board that hasn't been checked. ///these statuses may be used on either field: FIELD_SQUARE_HIT, // A field position that was attacked and contained part of a boat. FIELD_SQUARE_MISS, // This position was attacked by the enemy, but was empty. /// This may be useful for implementing extra-credit features: FIELD_SQUARE_CURSOR, // This is used merely for use in FieldOled.c for indicating the current // cursor when selecting a position to attack. /// Occasionally, it may be necessary to indicate an error using a square status: FIELD_SQUARE_INVALID, } SquareStatus; /** * These are the possible results of shots: */ typedef enum { RESULT_MISS, //0 RESULT_HIT, //1 RESULT_SMALL_BOAT_SUNK, //2 RESULT_MEDIUM_BOAT_SUNK, //3 RESULT_LARGE_BOAT_SUNK, //4 RESULT_HUGE_BOAT_SUNK, //5 } ShotResult; /** * GuessData is used for exchanging coordinate data along with information about of coordinate. */ typedef struct { uint8_t row; // Row of the coordinate that was guessed uint8_t col; // Column of the coordinate guessed ShotResult result; // result of a shot at this coordinate } GuessData; /** * A struct for tracking all of the necessary data for an agent's field. */ typedef struct { uint8_t grid[FIELD_ROWS][FIELD_COLS]; uint8_t smallBoatLives; uint8_t mediumBoatLives; uint8_t largeBoatLives; uint8_t hugeBoatLives; } Field; /** * Specify how many boats there exist on the field. There is 1 boat of each of the 4 types, so 4 * total. */ #define FIELD_NUM_BOATS 4 /** * Declares direction constants for use with FieldAddShip. */ typedef enum { FIELD_DIR_SOUTH, FIELD_DIR_EAST, } BoatDirection; /** * Constants for specifying which boat the current operation refers to. This is independent of the * SquareStatus enum. */ typedef enum { FIELD_BOAT_TYPE_SMALL, FIELD_BOAT_TYPE_MEDIUM, FIELD_BOAT_TYPE_LARGE, FIELD_BOAT_TYPE_HUGE } BoatType; /** * Track the alive state of the boats. They are arranged as as mutually-exclusive bits so that they * can be bitwise ORed together. Used for checking the return value of `FieldGetBoatStates()`. * * For example, if a field has a MEDIUM boat and a HUGE boat, but the other two boats * have been sunk, then its status flag should be: * FIELD_BOAT_STATUS_MEDIUM | FIELD_BOAT_STATUS_HUGE, or 0b1010 * */ typedef enum { FIELD_BOAT_STATUS_SMALL = 0x01, FIELD_BOAT_STATUS_MEDIUM = 0x02, FIELD_BOAT_STATUS_LARGE = 0x04, FIELD_BOAT_STATUS_HUGE = 0x08, } BoatStatusFlag; /** * This enum lists the number of squares, each boat occupies (and therefore, the number of * lives) that each boat has. */ typedef enum { FIELD_BOAT_SIZE_SMALL = 3, FIELD_BOAT_SIZE_MEDIUM = 4, FIELD_BOAT_SIZE_LARGE = 5, FIELD_BOAT_SIZE_HUGE = 6 } BoatSize; /** * This function is optional, but recommended. It prints a representation of both * fields, similar to the OLED display. * @param f The field to initialize. * @param p The data to initialize the entire field to, should be a member of enum * SquareStatus. */ void FieldPrint_UART(Field *own_field, Field * opp_field); /** * FieldInit() will initialize two passed field structs for the beginning of play. * Each field's grid should be filled with the appropriate SquareStatus ( * FIELD_SQUARE_EMPTY for your own field, FIELD_SQUARE_UNKNOWN for opponent's). * Additionally, your opponent's field's boatLives parameters should be filled * (your own field's boatLives will be filled when boats are added) * * FieldAI_PlaceAllBoats() should NOT be called in this function. * * @param own_field //A field representing the agents own ships * @param opp_field //A field representing the opponent's ships */ void FieldInit(Field *own_field, Field * opp_field); /** * Retrieves the value at the specified field position. * @param f //The Field being referenced * @param row //The row-component of the location to retrieve * @param col //The column-component of the location to retrieve * @return FIELD_SQUARE_INVALID if row and col are not valid field locations * Otherwise, return the status of the referenced square */ SquareStatus FieldGetSquareStatus(const Field *f, uint8_t row, uint8_t col); /** * This function provides an interface for setting individual locations within a Field struct. This * is useful when FieldAddBoat() doesn't do exactly what you need. For example, if you'd like to use * FIELD_SQUARE_CURSOR, this is the function to use. * * @param f The Field to modify. * @param row The row-component of the location to modify * @param col The column-component of the location to modify * @param p The new value of the field location * @return The old value at that field location */ SquareStatus FieldSetSquareStatus(Field *f, uint8_t row, uint8_t col, SquareStatus p); /** * FieldAddBoat() places a single ship on the player's field based on arguments 2-5. Arguments 2, 3 * represent the x, y coordinates of the pivot point of the ship. Argument 4 represents the * direction of the ship, and argument 5 is the length of the ship being placed. * * All spaces that * the boat would occupy are checked to be clear before the field is modified so that if the boat * can fit in the desired position, the field is modified as SUCCESS is returned. Otherwise the * field is unmodified and STANDARD_ERROR is returned. There is no hard-coded limit to how many * times a boat can be added to a field within this function. * * In addition, this function should update the appropriate boatLives parameter of the field. * * So this is valid test code: * { * Field myField; * FieldInit(&myField,FIELD_SQUARE_EMPTY); * FieldAddBoat(&myField, 0, 0, FIELD_BOAT_DIRECTION_EAST, FIELD_BOAT_TYPE_SMALL); * FieldAddBoat(&myField, 1, 0, FIELD_BOAT_DIRECTION_EAST, FIELD_BOAT_TYPE_MEDIUM); * FieldAddBoat(&myField, 1, 0, FIELD_BOAT_DIRECTION_EAST, FIELD_BOAT_TYPE_HUGE); * FieldAddBoat(&myField, 0, 6, FIELD_BOAT_DIRECTION_SOUTH, FIELD_BOAT_TYPE_SMALL); * } * * should result in a field like: * 0 1 2 3 4 5 6 7 8 9 * --------------------- * 0 [ 3 3 3 . . . 3 . . . ] * 1 [ 4 4 4 4 . . 3 . . . ] * 2 [ . . . . . . 3 . . . ] * 3 [ . . . . . . . . . . ] * 4 [ . . . . . . . . . . ] * 5 [ . . . . . . . . . . ] * * @param f The field to grab data from. * @param row The row that the boat will start from, valid range is from 0 and to FIELD_ROWS - 1. * @param col The column that the boat will start from, valid range is from 0 and to FIELD_COLS - 1. * @param dir The direction that the boat will face once places, from the BoatDirection enum. * @param boatType The type of boat to place. Relies on the FIELD_SQUARE_*_BOAT values from the * SquareStatus enum. * @return SUCCESS for success, STANDARD_ERROR for failure */ uint8_t FieldAddBoat(Field *own_field, uint8_t row, uint8_t col, BoatDirection dir, BoatType boat_type); /** * This function registers an attack at the gData coordinates on the provided field. This means that * 'f' is updated with a FIELD_SQUARE_HIT or FIELD_SQUARE_MISS depending on what was at the * coordinates indicated in 'gData'. 'gData' is also updated with the proper HitStatus value * depending on what happened AND the value of that field position BEFORE it was attacked. Finally * this function also reduces the lives for any boat that was hit from this attack. * @param f The field to check against and update. * @param gData The coordinates that were guessed. The result is stored in gData->result as an * output. The result can be a RESULT_HIT, RESULT_MISS, or RESULT_***_SUNK. * @return The data that was stored at the field position indicated by gData before this attack. */ SquareStatus FieldRegisterEnemyAttack(Field *own_field, GuessData *opp_guess); /** * This function updates the FieldState representing the opponent's game board with whether the * guess indicated within gData was a hit or not. If it was a hit, then the field is updated with a * FIELD_SQUARE_HIT at that position. If it was a miss, display a FIELD_SQUARE_EMPTY instead, as * it is now known that there was no boat there. The FieldState struct also contains data on how * many lives each ship has. Each hit only reports if it was a hit on any boat or if a specific boat * was sunk, this function also clears a boats lives if it detects that the hit was a * RESULT_*_BOAT_SUNK. * @param f The field to grab data from. * @param gData The coordinates that were guessed along with their HitStatus. * @return The previous value of that coordinate position in the field before the hit/miss was * registered. */ SquareStatus FieldUpdateKnowledge(Field *opp_field, const GuessData *own_guess); /** * This function returns the alive states of all 4 boats as a 4-bit bitfield (stored as a uint8). * The boats are ordered from smallest to largest starting at the least-significant bit. So that: * 0b00001010 indicates that the small boat and large boat are sunk, while the medium and huge boat * are still alive. See the BoatStatus enum for the bit arrangement. * @param f The field to grab data from. * @return A 4-bit value with each bit corresponding to whether each ship is alive or not. */ uint8_t FieldGetBoatStates(const Field *f); /** * This function is responsible for placing all four of the boats on a field. * * @param f //agent's own field, to be modified in place. * @return SUCCESS if all boats could be placed, STANDARD_ERROR otherwise. * * This function should never fail when passed a properly initialized field! */ uint8_t FieldAIPlaceAllBoats(Field *own_field); /** * Given a field, decide the next guess. * * This function should not attempt to shoot a square which has already been guessed. * * You may wish to give this function static variables. If so, that data should be * reset when FieldInit() is called. * * @param f an opponent's field. * @return a GuessData struct whose row and col parameters are the coordinates of the guess. The * result parameter is irrelevant. */ GuessData FieldAIDecideGuess(const Field *opp_field); /** * For Extra Credit: Make the two "AI" functions above * smart enough to beat our AI in more than 55% of games. */ #endif // FIELD_H
Negotiation.h
#ifndef NEGOTIATION_H #define NEGOTIATION_H #include <stdint.h> /** * The outcome of the negotiation is a coin flip, and can be either HEADS or TAILS: */ typedef enum{ HEADS, TAILS, } NegotiationOutcome; /** * All negotiation data occurs on uniformly-sized data packets. */ typedef uint16_t NegotiationData; /** * The commitment packet is generated using a hash that utilizes a public key, which is defined here: */ #define PUBLIC_KEY 0xBEEF /** * This function implements a one-way hash. It maps its input, A, * into an image, #a, in a way that is hard to reverse, but easy * to reproduce. * @param secret //A number that a challenger commits to * @return hash //the hashed value of the secret commitment. * * This function implements the "Beef Hash," a variant of a Rabin hash. * The result is ((the square of the input) modulo the constant key 0xBEEF). * So, for example, * * NegotiationHash(3) == 9 * NegotiationHash(12345) == 43182 */ NegotiationData NegotiationHash(NegotiationData secret); /** * Detect cheating. An accepting agent will receive both a commitment hash * and a secret number from the challenging agent. This function * verifies that the secret and the commitment hash agree, hopefully * detecting cheating by the challenging agent. * * @param secret //the previously secret number that the challenging agent has revealed * @param commitment //the hash of the secret number * @return TRUE if the commitment validates the revealed secret, FALSE otherwise */ int NegotiationVerify(NegotiationData secret, NegotiationData commitment); /** * The coin-flip protocol uses random numbers generated by both * agents to determine the outcome of the coin flip. * * The parity of a bitstring is 1 if there are an odd number of one bits, * and 0 otherwise. * So, for example, the number 0b01101011 has 5 ones. If the parity of * A XOR B is 1, then the outcome is HEADS. Otherwise, the outcome is TAILS. */ NegotiationOutcome NegotiateCoinFlip(NegotiationData A, NegotiationData B); /** * Extra credit: * Use either or both of these two functions if you want to generate a "cheating" agent. * * To get extra credit, define these functions * and use these functions in agent.c to generate A and/or B * * Your agent only needs to be able to cheat at one role for extra credit. They must result in * your agent going first more than 75% of the time in that role when * competing against a fair agent (that is, an agent that uses purely random A and B). * * You must state that you did this at the top of your README, and describe your * strategy thoroughly. */ NegotiationData NegotiateGenerateBGivenHash(NegotiationData hash_a); NegotiationData NegotiateGenerateAGivenB(NegotiationData B); #endif // NEGOTIATION_H
Message.h
#ifndef MESSAGE_H #define MESSAGE_H #include <stdint.h> #include "BattleBoats.h" /** According to the NMEA standard, messages cannot be longer than 82, * including the delimiters $, *, and \n. */ #define MESSAGE_MAX_LEN 82 /** The maximum payload length is the maximum message length, * -1 start delimiter ($) * -1 checksum delimiter (*), * -2 checksum characters, * -1 end delimiter (\n) */ #define MESSAGE_MAX_PAYLOAD_LEN (MESSAGE_MAX_LEN - 1 - 1 - 2 - 1) /*NMEA also defines a specific checksum length*/ #define MESSAGE_CHECKSUM_LEN 2 /** * The types of messages that can be sent or received: */ typedef enum { MESSAGE_NONE, //used if no message is to be sent MESSAGE_CHA, MESSAGE_ACC, MESSAGE_REV, MESSAGE_SHO, MESSAGE_RES, //while not required, an error message can be a useful debugging tool: MESSAGE_ERROR = -1, } MessageType; /** * Messages follow this struct: */ typedef struct { MessageType type; unsigned int param0; unsigned int param1; unsigned int param2; } Message; /** Message payloads will have the following syntax. * Each (almost*) follows the NMEA0183 syntax for message payloads: * The first three characters describe the message type * Zero or more comma-separated fields follow, containing various kinds of data * * (* true NEMA0183 payloads begin with two characters that describe the "talker", which we * omit from the BattleBoats protocol) */ #define PAYLOAD_TEMPLATE_CHA "CHA,%u" // Challenge message: hash_a (see protocol) #define PAYLOAD_TEMPLATE_ACC "ACC,%u" // Accept message: B (see protocol) #define PAYLOAD_TEMPLATE_REV "REV,%u" // Reveal message: A (see protocol) #define PAYLOAD_TEMPLATE_SHO "SHO,%d,%d" // Shot (guess) message: row, col #define PAYLOAD_TEMPLATE_RES "RES,%u,%u,%u" // Result message: row, col, GuessResult /** * NEMA0183 messages wrap the payload with a start delimiter, * a checksum to verify the contents of * the message in case of transmission errors, and an end delimiter. * This template defines the wrapper. * Note that it uses printf-style tokens so that it can be used with sprintf(). * * Here is an example message: * * 1 start delimiter (a literal $) * 2 payload (any string, represented by %s in the template) * 3 checksum delimiter (a literal *) * 4 checksum (two ascii characters representing hex digits, %02x in the template) * 5 end delimiter (a literal \n) * * example message: 1 3 5 * v v vv * $SHO,2,9*5F\n * ^^^^^^^ ^^ * 2 4 * * Note that 2 and 4 correspond to %s and %02x in the template. * * Also note that valid BattleBoats messages use * strictly upper-case letters, so $SHO,2,3*5f\n is an invalid message. */ #define MESSAGE_TEMPLATE "$%s*%02X\n" /** * Given a payload string, calculate its checksum * * @param payload //the string whose checksum we wish to calculate * @return //The resulting 8-bit checksum */ uint8_t Message_CalculateChecksum(const char* payload); /** * ParseMessage() converts a message string into a BB_Event. The payload and * checksum of a message are passed into ParseMessage(), and it modifies a * BB_Event struct in place to reflect the contents of the message. * * @param payload //the payload of a message * @param checksum //the checksum (in string form) of a message, * should be exactly 2 chars long, plus a null char * @param message_event //A BB_Event which will be modified by this function. * //If the message could be parsed successfully, * message_event's type will correspond to the message type and * its parameters will match the message's data fields. * //If the message could not be parsed, * message_events type will be BB_EVENT_ERROR * * @return STANDARD_ERROR if: * the payload does not match the checksum * the checksum string is not two characters long * the message does not match any message template * SUCCESS otherwise * * Please note! sscanf() has a couple compiler bugs that make it a very * unreliable tool for implementing this function. * */ int Message_ParseMessage(const char* payload, const char* checksum_string, BB_Event * message_event); /** * Encodes the coordinate data for a guess into the string `message`. This string must be big * enough to contain all of the necessary data. The format is specified in PAYLOAD_TEMPLATE_COO, * which is then wrapped within the message as defined by MESSAGE_TEMPLATE. * * The final length of this * message is then returned. There is no failure mode for this function as there is no checking * for NULL pointers. * * @param message The character array used for storing the output. * Must be long enough to store the entire string, * see MESSAGE_MAX_LEN. * @param message_to_encode A message to encode * @return The length of the string stored into 'message_string'. Return 0 if message type is MESSAGE_NONE. */ int Message_Encode(char *message_string, Message message_to_encode); /** * Message_Decode reads one character at a time. If it detects a full NMEA message, * it translates that message into a BB_Event struct, which can be passed to other * services. * * @param char_in - The next character in the NMEA0183 message to be decoded. * @param decoded_message - a pointer to a message struct, used to "return" a message * if char_in is the last character of a valid message, * then decoded_message * should have the appropriate message type. * if char_in is the last character of an invalid message, * then decoded_message should have an ERROR type. * otherwise, it should have type NO_EVENT. * @return SUCCESS if no error was detected * STANDARD_ERROR if an error was detected * * note that ANY call to Message_Decode may modify decoded_message. */ int Message_Decode(unsigned char char_in, BB_Event * decoded_message_event); #endif // MESSAGE_H
BattleBoats.h
#ifndef BATTLEBOATS_H #define BATTLEBOATS_H #include <stdint.h> /** This file contains standard structures that are used */ typedef enum { BB_EVENT_NO_EVENT, //0 BB_EVENT_START_BUTTON, //1 BB_EVENT_RESET_BUTTON, //2 BB_EVENT_CHA_RECEIVED, //3 BB_EVENT_ACC_RECEIVED, //4 BB_EVENT_REV_RECEIVED, //5 BB_EVENT_SHO_RECEIVED, //6 BB_EVENT_RES_RECEIVED, //7 BB_EVENT_MESSAGE_SENT, //8 BB_EVENT_ERROR, //9 //the following events are only used for human agents: BB_EVENT_SOUTH_BUTTON, //10 BB_EVENT_EAST_BUTTON, //11 } BB_EventType; /** All BB events use this struct: */ typedef struct { BB_EventType type; uint16_t param0; //defined in Message.h uint16_t param1; uint16_t param2; } BB_Event; /** * Used to signal different types of errors as the param0 * of a BattleBoat Error event. You are not required to utilize these, * but they can make error checking much more effective. * */ typedef enum { BB_SUCCESS = 0, //0 BB_ERROR_BAD_CHECKSUM, //1 BB_ERROR_PAYLOAD_LEN_EXCEEDED, //2 BB_ERROR_CHECKSUM_LEN_EXCEEDED, //3 BB_ERROR_CHECKSUM_LEN_INSUFFICIENT, //3 BB_ERROR_INVALID_MESSAGE_TYPE, //4 BB_ERROR_MESSAGE_PARSE_FAILURE, } BB_Error; #endif // BATTLEBOATS_H
Lab09_main.c
// **** Include libraries here **** // Standard libraries #include <stdio.h> #include <string.h> #include <stdint.h> //CMPE13 Support Library #include "BOARD.h" // Microchip libraries #include <xc.h> #include <sys/attribs.h> //CE13 standard libraries: #include "Buttons.h" #include "Uart1.h" #include "Oled.h" // Battleboats Libraries: #include "BattleBoats.h" #include "Agent.h" #include "Negotiation.h" #include "Message.h" #include "Field.h" //The following Macro switches provide useful debugging tools: //Trace Mode: Print a trace of events as they are detected: //#define TRACE_MODE //Unseeded Mode: Do not reseed rand, and seed with switches (useful for creating repeatable tests): //#define UNSEEDED_MODE // <editor-fold defaultstate="collapsed" desc="macros for trace mode"> #ifndef TRACE_MODE #define debug_printf(...) #else #define debug_printf printf #endif // </editor-fold> // <editor-fold defaultstate="collapsed" desc="macros for unseeded mode"> #ifdef UNSEEDED_MODE #define seed_rand(x) #define AgentInit() {srand(SWITCH_STATES()); AgentInit();} #else #define seed_rand(x) srand(x) #endif // </editor-fold> //The amount of time between UART updates (in 100ths of a second) #define TRANSMIT_PERIOD 10 /** * Static data for BattleBoats top level: */ //This is the top-level event flag: static BB_Event battleboatEvent; //A freerunning timer is used to inject randomness using external events, //and to throttle the outgoing transmission speed: static uint32_t freerunning_timer = 0; /* * The Transmission Outgoing submodule has two states. It can only send one message at a time, * so new outgoing messages can only be started when it is in IDLE mode. * * An indexed buffer stores the message until it is completely sent. */ enum { SENDING, IDLE } transmission_state = IDLE; static char outgoing_message_buffer[MESSAGE_MAX_LEN + 1]; static int outgoing_index = 0; /** * This function copies a message into the Transmission outgoing message buffer and begins * the sending process. Once this function is called, the Transmission module * switches into the SENDING state. * * This function should not be called if it is already in the SENDING state! */ void Transmission_StartSendingMessage(const Message * message_to_send) { //this should only be called if sender is in IDLE. switch (transmission_state) { case SENDING: //The Agent state machine should not allow a new message to be started while //another message is in the buffer: OledClear(OLED_COLOR_BLACK); OledDrawString("Fatal Transmission Error!"); OledUpdate(); FATAL_ERROR(); case IDLE: //copy message into sending buffer: Message_Encode(outgoing_message_buffer, *message_to_send); outgoing_index = 0; //switch into sending mode: transmission_state = SENDING; } } /** * If an outgoing message is in the buffer, this module sends one character each * time it is called. When the message is sent, the module switches to IDLE mode. * */ void Transmission_SendChar(void) { //if in SENDING, send one char. Otherwise, do nothing. if (transmission_state != SENDING) return; //First send our current char: char to_send = outgoing_message_buffer[outgoing_index]; if (to_send == '\0') { //this means our message is fully transmitted. battleboatEvent.type = BB_EVENT_MESSAGE_SENT; outgoing_index = 0; transmission_state = IDLE; return; } else { Uart1WriteByte(to_send); outgoing_index++; } } /** * Check for incoming messages. This module uses Message_Decode to parse messages * in the UART input stream, and generates events if any messages are detected. **/ void Transmission_ReceiveChar(void) { unsigned char incoming_char; //read from the UART (if there is anything to read: if (!Uart1HasData()) return; Uart1ReadByte(&incoming_char); // the commented line below is very handy for debugging Message_Decode debug_printf("%c | %02x\n", incoming_char, incoming_char); //react to incoming char: if (incoming_char != '\0') { Message_Decode(incoming_char, &battleboatEvent); } //also, re-seed our random number using the time: seed_rand(rand() + freerunning_timer); } //Functions that stringify state names and event names for display. // <editor-fold defaultstate="collapsed" desc="Trace Mode Functions"> #ifdef TRACE_MODE #define printcase(x) case x: strcat(tracestr,#x); break; void TraceState(void) { char tracestr[100] = "---TRACE: Current state="; switch (AgentGetState()) { printcase(AGENT_STATE_START); printcase(AGENT_STATE_CHALLENGING); printcase(AGENT_STATE_ACCEPTING); printcase(AGENT_STATE_ATTACKING); printcase(AGENT_STATE_DEFENDING); printcase(AGENT_STATE_WAITING_TO_SEND); printcase(AGENT_STATE_END_SCREEN); default: return; } strcat(tracestr, "\n"); Uart1WriteData(tracestr, strlen(tracestr)); } void TraceEvent(void) { char tracestr[100] = "---TRACE: Detected Event: "; switch (battleboatEvent.type) { printcase(BB_EVENT_NO_EVENT); printcase(BB_EVENT_START_BUTTON); printcase(BB_EVENT_RESET_BUTTON); printcase(BB_EVENT_SOUTH_BUTTON); printcase(BB_EVENT_EAST_BUTTON); printcase(BB_EVENT_CHA_RECEIVED); printcase(BB_EVENT_ACC_RECEIVED); printcase(BB_EVENT_REV_RECEIVED); printcase(BB_EVENT_SHO_RECEIVED); printcase(BB_EVENT_RES_RECEIVED); printcase(BB_EVENT_MESSAGE_SENT); printcase(BB_EVENT_ERROR); } sprintf(tracestr, "%s - %d,%d,%d\n", tracestr, battleboatEvent.param0, battleboatEvent.param1, battleboatEvent.param2); Uart1WriteData(tracestr, strlen(tracestr)); } #else #define TraceEvent() #define TraceState() #endif // </editor-fold> int main() { BOARD_Init(); // Set up UART1 for output. // <editor-fold defaultstate="collapsed" desc="Configure Timers and UART"> Uart1Init(UART_BAUD_RATE); // Configure Timer 2 using PBCLK as input. We configure it using a 1:16 prescalar, so each timer // tick is actually at F_PB / 16 Hz, so setting PR2 to F_PB / 16 / 100 yields a 10ms timer. // Configure Timer 2 using PBCLK as input. We configure it using a 1:16 prescalar, so each timer // tick is actually at F_PB / 16 Hz, so setting PR2 to F_PB / 16 / 100 yields a .01s timer. T2CON = 0; // everything should be off T2CONbits.TCKPS = 0b100; // 1:16 prescaler PR2 = BOARD_GetPBClock() / 16 / 100; // interrupt at .5s intervals T2CONbits.ON = 1; // turn the timer on // Set up the timer interrupt with a priority of 4. IFS0bits.T2IF = 0; //clear the interrupt flag before configuring IPC2bits.T2IP = 4; // priority of 4 IPC2bits.T2IS = 0; // subpriority of 0 arbitrarily IEC0bits.T2IE = 1; // turn the interrupt on // Disable buffering on stdout setbuf(stdout, NULL); // </editor-fold> //Set up LEDs: TRISE = 0; LATE = 0; //initialize CE13 libraries: ButtonsInit(); OledInit(); //Print a greeting: OledDrawString("This is BattleBoats!\nPress BTN4 to\nchallenge, or wait\nfor opponent."); OledUpdate(); //Initialize Agent module: AgentInit(); TraceState(); //Main loop: while (TRUE) { //if there is a top-level event, the Agent module should respond to it: if (battleboatEvent.type != BB_EVENT_NO_EVENT) { TraceEvent(); Message message_to_send = AgentRun(battleboatEvent); TraceState(); //send a message, if there is one to send: if (message_to_send.type != MESSAGE_NONE) { Transmission_StartSendingMessage(&message_to_send); } //consume the event: battleboatEvent.type = BB_EVENT_NO_EVENT; } //update the LEDs to show the agent's current state: LATE = (1 << AgentGetState()); //this is very fast so we can do it directly in while(1) loop } } /** * This is the interrupt for the Timer2 peripheral. It just keeps incrementing a counter used to * track the time until the first user input. */ void __ISR(_TIMER_2_VECTOR, ipl4auto) TimerInterrupt100Hz(void) { // Clear the interrupt flag. IFS0CLR = 1 << 8; // Increment the timer freerunning_timer++; // Check for any button events uint8_t buttonEvent = ButtonsCheckEvents(); if (buttonEvent & BUTTON_EVENT_4DOWN) battleboatEvent.type = BB_EVENT_START_BUTTON; if (buttonEvent & BUTTON_EVENT_3DOWN) battleboatEvent.type = BB_EVENT_EAST_BUTTON; if (buttonEvent & BUTTON_EVENT_2DOWN) battleboatEvent.type = BB_EVENT_SOUTH_BUTTON; if (buttonEvent & BUTTON_EVENT_1DOWN) battleboatEvent.type = BB_EVENT_RESET_BUTTON; //also, re-seed our random number using the time: if (buttonEvent) seed_rand(rand() + freerunning_timer); //every TRANSMIT_PERIOD cycles, attempt to run the transmission module. if (freerunning_timer % TRANSMIT_PERIOD == 0) { Transmission_SendChar(); if (battleboatEvent.type == BB_EVENT_MESSAGE_SENT) return; Transmission_ReceiveChar(); } }
FieldOled.h
#ifndef FIELD_OLED_H #define FIELD_OLED_H #include "Field.h" #include "BOARD.h" #include "Oled.h" #include "FieldOled.h" /** * Define a tri-state variable for indicating which agent's turn it is. Before and after the game, * the turn should always be `*_NONE`. This means that until turn negotiation is completed by an agent, * it should use `*_NONE`. Once the game is completed because the agent knows someone has one, it should * once again be specified as `*_NONE`. */ typedef enum { FIELD_OLED_TURN_NONE, FIELD_OLED_TURN_MINE, FIELD_OLED_TURN_THEIRS } FieldOledTurn; /** * Draw both player's fields to the screen, along with a current turn indicator. * @param myField The field representing this agent's field. * @param theirField The field representing the enemy agent's field. * @param playerTurn Which agent currently has the turn. * * Optionally, theirField may be null, in which case only ownField is shown. * This is useful during a HumanAgent's boat setup phase. */ void FieldOledDrawScreen(const Field *myField, const Field *theirField, FieldOledTurn playerTurn, uint8_t turn_number); #endif // FIELD_OLED_H
FieldOled.c
#include "Oled.h" #include "OledDriver.h" #include "Field.h" #include "FieldOled.h" #include "Ascii.h" #define FIELD_SYMBOL_WIDTH 3 #define FIELD_SYMBOL_HEIGHT 4 const uint8_t gridSymbols[10][FIELD_SYMBOL_WIDTH] = { [FIELD_SQUARE_EMPTY] = { 0b0000, 0b0000, 0b0000, }, [FIELD_SQUARE_SMALL_BOAT] = { 0b1001, 0b1011, 0b1111, }, [FIELD_SQUARE_MEDIUM_BOAT] = { // 0b0111, 0b0100, 0b1111, }, [FIELD_SQUARE_LARGE_BOAT] = { // 0b1011, 0b1011, 0b1101, }, [FIELD_SQUARE_HUGE_BOAT] = { // 0b1111, 0b1101, 0b1101, }, [FIELD_SQUARE_UNKNOWN] = { // 0b1111, 0b1111, 0b1111, }, [FIELD_SQUARE_HIT] = { // 0b1001, 0b0110, 0b1001, }, [FIELD_SQUARE_MISS] = { // 0b0000, 0b0110, 0b0000, }, [FIELD_SQUARE_CURSOR] = { // 0b1111, 0b1001, 0b1111, }, [FIELD_SQUARE_INVALID] = { // 0b1111, 0b1111, 0b1111, } }; uint8_t _FieldOledDrawSymbol(int x, int y, SquareStatus s); void _FieldOledDrawField(const Field *f, int xOffset); void FieldOledDrawScreen(const Field *myField, const Field *theirField, FieldOledTurn playerTurn, uint8_t turn_number) { #ifndef __MPLAB_DEBUGGER_SIMULATOR OledClear(OLED_COLOR_BLACK); _FieldOledDrawField(myField, 0); if (theirField) { _FieldOledDrawField(theirField, 76); } else { OledUpdate(); return; } //draw inner artwork OledDrawChar(53, 1, 'P'); OledDrawChar(76 - ASCII_FONT_WIDTH - 1, 1, 'O'); if (playerTurn == FIELD_OLED_TURN_MINE) { OledDrawChar(53, ASCII_FONT_HEIGHT + 1, '<'); } else if (playerTurn == FIELD_OLED_TURN_THEIRS) { OledDrawChar(76 - ASCII_FONT_WIDTH - 1, ASCII_FONT_HEIGHT + 1, '>'); } //draw turn number: int x; x = 76 - ASCII_FONT_WIDTH * 2; OledDrawChar(x, ASCII_FONT_HEIGHT * 3, turn_number % 10 + '0'); x -= ASCII_FONT_WIDTH; turn_number /= 10; OledDrawChar(x, ASCII_FONT_HEIGHT * 3, turn_number % 10 + '0'); OledUpdate(); #endif } /** * Draw the given player's grid at the given x-coordinate. */ void _FieldOledDrawField(const Field *f, int xOffset) { int i; int finalCol = 10 * 5 + 2; int finalRowOffset = (OLED_DRIVER_PIXEL_ROWS / OLED_DRIVER_BUFFER_LINE_HEIGHT - 1) * OLED_DRIVER_PIXEL_COLUMNS; // Draw the horizontal grid borders. for (i = 0; i < finalCol; ++i) { rgbOledBmp[xOffset + i] |= 1; rgbOledBmp[finalRowOffset + xOffset + i] |= 0x80; } // Draw the vertical grid borders. for (i = 0; i < OLED_DRIVER_PIXEL_ROWS / OLED_DRIVER_BUFFER_LINE_HEIGHT; ++i) { rgbOledBmp[i * OLED_DRIVER_PIXEL_COLUMNS + xOffset + 0] = 0xFF; rgbOledBmp[i * OLED_DRIVER_PIXEL_COLUMNS + xOffset + finalCol - 1] = 0xFF; } // Draw each item in the grid. int yOffset = 2; xOffset += 1; for (i = 0; i < FIELD_COLS; ++i) { int j; for (j = 0; j < FIELD_ROWS; ++j) { _FieldOledDrawSymbol(xOffset + 1 + 5 * i, yOffset + 5 * j, f->grid[j][i]); } } } /** * Draw the desired symbol at the given x/y coordinates. */ uint8_t _FieldOledDrawSymbol(int x, int y, SquareStatus s) { if (x < OLED_DRIVER_PIXEL_COLUMNS - FIELD_SYMBOL_WIDTH && y < OLED_DRIVER_PIXEL_ROWS - FIELD_SYMBOL_HEIGHT) { // Now first determine the columns and rows of the OLED bits that need to be modified int rowMin, rowMax, colMin, colMax; rowMin = y / OLED_DRIVER_BUFFER_LINE_HEIGHT; int rowY = y % OLED_DRIVER_BUFFER_LINE_HEIGHT; rowMax = (y + FIELD_SYMBOL_HEIGHT) / OLED_DRIVER_BUFFER_LINE_HEIGHT; colMin = x; colMax = x + FIELD_SYMBOL_WIDTH; { // Generate a positive mask for where in the column the new symbol will be drawn. int colMask = ((1 << FIELD_SYMBOL_HEIGHT) - 1) << rowY; int j; for (j = 0; j < colMax - colMin; ++j) { int oledCol = colMin + j; uint8_t newCharCol = rgbOledBmp[rowMin * OLED_DRIVER_PIXEL_COLUMNS + oledCol] & ~colMask; // Make sure we always grab from the top part of the character. newCharCol |= (gridSymbols[(int) s][j] & (colMask >> rowY)) << rowY; rgbOledBmp[rowMin * OLED_DRIVER_PIXEL_COLUMNS + oledCol] = newCharCol; } } if (rowMax > rowMin) { // Generate a positive mask for where in the column the new symbol will be drawn. // Since we need the lower portion of the symbol, we recalculate its height. int colMask = ((1 << FIELD_SYMBOL_HEIGHT) - 1) >> (OLED_DRIVER_BUFFER_LINE_HEIGHT - rowY); int j; for (j = 0; j < colMax - colMin; ++j) { int oledCol = colMin + j; uint8_t newCharCol = rgbOledBmp[rowMax * OLED_DRIVER_PIXEL_COLUMNS + oledCol] & ~colMask; // Make sure we grab the proper part of the character from the font. newCharCol |= (gridSymbols[(int) s][j] & (colMask << (OLED_DRIVER_BUFFER_LINE_HEIGHT - rowY))) >> (OLED_DRIVER_BUFFER_LINE_HEIGHT - rowY); rgbOledBmp[rowMax * OLED_DRIVER_PIXEL_COLUMNS + oledCol] = newCharCol; } } } return FALSE; }
Lab9SupportLib.a
autotest_tools.o
FieldOled.o
Ascii.o
BOARD.o
Buttons.o
CircularBuffer.o
Oled.o
OledDriver.o
Uart1.o
Lab09_main.o
Negotiation.o
NegotiationTest.o
Lab9SupportLib.a
Ascii.o
Buttons.o
Oled.o
OledDriver.o
HumanAgent.o
Agent_correct.o
Field_correct.o
Negotiation_correct.o
Message_correct.o
staff_ai_gauntlet.o
Oled.h
#ifndef OLED_H #define OLED_H /** * This file provides a complete interface for interacting with the OLED on Digilent's I/O Shield. * It works by storing a backend array of pixel data for storing modifications and then flushing * that to the screen to update it when OledUpdate() is called. Since OledUpdate() is very slow, * this allows for batching together a lot of drawing operations before finally updating the screen * at the end in one go. * * This OLED is a monochrome display, offering black and white as your color options. These can be * specified as arguments to OledDrawChar() and OledClear(). Note that this coloring can be inverted * on the display itself by calling OledSetDisplayInverted(). This doesn't actually modify the data * stored in the pixels, but merely switches what colors each bit value represents. * * The OLED offers both a pixel interface and a text interface for drawing. Individual pixels can be * read and changed with the Oled*Pixel() functions. * * Higher-level text operations can be done through OledDrawChar() and OledDrawString, with the * latter function being the easier one to use. It allows for writing text across all OLED_NUM_LINES * lines on the display where each line can hold up to OLED_CHARS_PER_LINE complete characters. * * The font (defined in Ascii.h) used for drawing characters is a custom monospaced font. It * provides glyphs for most of the basic ASCII character set, but is incomplete. Additionally some * non-printing characters have been repurposed for custom characters for specific uses * (@see Ascii.h). */ #include "Ascii.h" #include "OledDriver.h" #include "BOARD.h" /** * Define constants for available colors for the OLED: either white or black. */ typedef enum { OLED_COLOR_BLACK = 0, OLED_COLOR_WHITE = 1 } OledColor; // Define how many lines of text the display can show. #define OLED_NUM_LINES (OLED_DRIVER_PIXEL_ROWS / ASCII_FONT_HEIGHT) // Define how many complete characters can be displayed on a row of text. #define OLED_CHARS_PER_LINE (OLED_DRIVER_PIXEL_COLUMNS / ASCII_FONT_WIDTH) /** * Initializes the OLED, turns it on, and clears the screen. */ void OledInit(void); /** * Sets a specific pixel in the frame buffer, available colors are black or white. * @note OledUpdate() must be called before the OLED will actually display these changes. * @param x The X position (left is zero) * @param y The Y position (top is zero) * @param color OLED_COLOR_WHITE or OLED_COLOR_BLACK */ void OledSetPixel(int x, int y, OledColor color); /** * Reads a pixel from the frame buffer. * @param x The X position (left is zero) * @param y The Y position (top is zero) * @return OLED_COLOR_WHITE or OLED_COLOR_BLACK */ int OledGetPixel(int x, int y); /** * Draws the specified character at the specified position, using Ascii.h as the font. * @note OledUpdate() must be called before the OLED will actually display these changes. * @param x The x-position to use as the left-most value for the character. * @param y The y-position to use as the top-most value for the character * @param c The character to write. Uses the character array defined in Ascii.h * @return True if the write succeeded. Fails on invalid inputs. */ uint8_t OledDrawChar(int x, int y, char c); /** * Draws a string to the screen buffer, starting on the top line. OLED_CHARS_PER_LINE characters fit * on each of the OLED_NUM_LINES lines on the screen. A newline in the string will start the * subsequent characters on the next line, otherwise once a line has run out of room no more * characters will display on the screen. There is no other special processing of the input string * besides this newline functionality, for example backspace characters just render as blank * characters. * * For example, the following code example shows Hello World I'm Working! on the OLED with each word * on its own line: * OledInit(); * OledDrawString("Hello\nWorldI'm\nWorking!\n"); * OledUpdate(); * * @note OledUpdate() must be called before the OLED will actually display these changes. * @param string A null-terminated string to print. */ void OledDrawString(const char *string); /** * Writes the specified color pixels to the entire frame buffer. * @note OledUpdate() must be called before the OLED will actually display these changes. * @param p The color to write all pixels in the OLED to. */ void OledClear(OledColor p); /** * Sets the display to display pixels the opposite color than what was intended. This does not * change the stored value for any pixel. * @see OledSetDisplayNormal */ void OledSetDisplayInverted(void); /** * Displays all pixels as they are stored, where a 0 indicates black and a 1 indicates white. This * is the default setting for the OLED on startup. This undoes `OledSetDisplayInverted()`. * @see OledSetDisplayInverted */ void OledSetDisplayNormal(void); /** * Turns on the OLED display. * @note This is not required as part of initialization, as `OledInit()` already does this. */ void OledOn(void); /** * Turns off the OLED display. * @note This is not required as part of initialization, as `OledInit()` already does this. */ void OledOff(void); /** * Refreshes the OLED display to reflect any changes. Should be called after any operation that * changes the display: OledSetPixel(), OledDrawChar(), OledDrawString(), and OledClear(). * * This function is very slow and so shouldn't be called too often or the OLED might look dim or * even show no data at all. This is because it uses a blocking SPI interface to push out the entire * screen of pixel data every time it's called. Like I said, very slow function! * * For example, the following code example shows Hello World I'm Workin! on the OLED with each word * on its own line: * OledInit(); * OledDrawString("Hello\nWorldI'm\nWorking!\n"); * OledUpdate(); */ void OledUpdate(void); #endif
Oled.c
#include <stddef.h> #ifdef __MPLAB_DEBUGGER_SIMULATOR #include <stdio.h> #endif #include "OledDriver.h" #include "Oled.h" #include "Ascii.h" // in simulator we do nothing with the hardware, printing instead void OledInit(void) { #ifndef __MPLAB_DEBUGGER_SIMULATOR // First initialize the PIC32 to be able to talk over SPI to the OLED. OledHostInit(); // Now send initialization commands to the OLED. OledDriverInitDisplay(); // Clear the frame buffer by filling it with black pixels. OledClear(OLED_COLOR_BLACK); // Finally update the screen, triggering a write of all black pixels to the screen. OledUpdate(); #endif } void OledSetPixel(int x, int y, OledColor color) { #ifndef __MPLAB_DEBUGGER_SIMULATOR // Check for valid inputs before doing anything. if (x >= OLED_DRIVER_PIXEL_COLUMNS || y >= OLED_DRIVER_PIXEL_ROWS || x < 0 || y < 0) { return; } // Map the x/y coordinates into a byte/bit index. unsigned int index = ((y & 0xFFF8) << 4) + x; unsigned int shift = y & 0x0007; // Now set the pixel to the proper color, doing nothing if an invalid color was specified. if (color == OLED_COLOR_WHITE) { rgbOledBmp[index] = rgbOledBmp[index] | (1 << shift); } else if (color == OLED_COLOR_BLACK) { rgbOledBmp[index] = rgbOledBmp[index] & ~(1 << shift); } else { return; } #endif } int OledGetPixel(int x, int y) { #ifndef __MPLAB_DEBUGGER_SIMULATOR // Check for valid inputs before doing anything. if (x >= OLED_DRIVER_PIXEL_COLUMNS || y >= OLED_DRIVER_PIXEL_ROWS || x < 0 || y < 0) { return OLED_COLOR_BLACK; } // Map the x/y coordinates into a byte/bit index. unsigned int index = ((y & 0xFFF8) << 4) + x; unsigned int shift = y & 0x0007; // Now return the desired bit. return (rgbOledBmp[index] >> shift) & 0x01; #else return OLED_COLOR_BLACK; #endif } //in simulator this is the same as putchar uint8_t OledDrawChar(int x, int y, char c) { #ifndef __MPLAB_DEBUGGER_SIMULATOR if (x <= OLED_DRIVER_PIXEL_COLUMNS - ASCII_FONT_WIDTH && y <= OLED_DRIVER_PIXEL_ROWS - ASCII_FONT_HEIGHT) { // We need to convert our signed char into an unsigned value to index into the ascii[] array. int charIndex = (int) (unsigned char) c; // Now first determine the columns and rows of the OLED bits that need to be modified int rowMin, rowMax, colMin, colMax; rowMin = y / ASCII_FONT_HEIGHT; int rowY = y % ASCII_FONT_HEIGHT; rowMax = (y + ASCII_FONT_HEIGHT) / OLED_DRIVER_BUFFER_LINE_HEIGHT; colMin = x; colMax = x + ASCII_FONT_WIDTH; { // Generate a positive mask for where in the column the new symbol will be drawn. int colMask = ((1 << ASCII_FONT_HEIGHT) - 1) << rowY; int j; for (j = 0; j < colMax - colMin; ++j) { int oledCol = colMin + j; uint8_t newCharCol = rgbOledBmp[rowMin * OLED_DRIVER_PIXEL_COLUMNS + oledCol] & ~colMask; // Make sure we always grab from the top part of the character. newCharCol |= (ascii[charIndex][j] & (colMask >> rowY)) << rowY; rgbOledBmp[rowMin * OLED_DRIVER_PIXEL_COLUMNS + oledCol] = newCharCol; } } if (rowMax > rowMin) { // Generate a positive mask for where in the column the new symbol will be drawn. // Since we need the lower portion of the symbol, we recalculate its height. int colMask = ((1 << ASCII_FONT_HEIGHT) - 1) >> (OLED_DRIVER_BUFFER_LINE_HEIGHT - rowY); int j; for (j = 0; j < colMax - colMin; ++j) { int oledCol = colMin + j; uint8_t newCharCol = rgbOledBmp[rowMax * OLED_DRIVER_PIXEL_COLUMNS + oledCol] & ~colMask; // Make sure we grab the proper part of the character from the font. newCharCol |= (ascii[charIndex][j] & (colMask << (OLED_DRIVER_BUFFER_LINE_HEIGHT - rowY))) >> (OLED_DRIVER_BUFFER_LINE_HEIGHT - rowY); rgbOledBmp[rowMax * OLED_DRIVER_PIXEL_COLUMNS + oledCol] = newCharCol; } } } #else putchar(c); #endif return FALSE; } void OledDrawString(const char *string) { #ifndef __MPLAB_DEBUGGER_SIMULATOR if (string == NULL) { return; } // Track the current line number we're in on the OLED. Valid values are [0, OLED_NUM_LINES). int line = 0; // Track the current character we're writing to the OLED. Valid values are // [0, OLED_CHARS_PER_LINE). int column = 0; // Run through all characters. The maximum length can be the number of lines times the number // of characters per line + three newlines. int i; for (i = 0; string[i] != '\0' && i < (OLED_NUM_LINES * OLED_CHARS_PER_LINE + 3); ++i) { // Move the cursor to the next line if a newline character is encountered. This allows for // early line ending. if (string[i] == '\n') { ++line; column = 0; continue; } else { // Reset to the start of the next line if we've hit the character limit of this line // without seeing a newline. if (column == OLED_CHARS_PER_LINE) { ++line; column = 0; } // Now if we're < OLED_NUM_LINES and < OLED_CHARS_PER_LINE we can proceed. The second // check is implicitly handled by the above if-statement that forces a newline after // encountering a full line of characters. if (line == OLED_NUM_LINES) { break; } // Finally at this point, we can write characters to the OLED. OledDrawChar(column * ASCII_FONT_WIDTH, line * ASCII_FONT_HEIGHT, string[i]); ++column; } } #else printf("%s",string); #endif } void OledClear(OledColor p) { int i; for (i = 0; i < OLED_DRIVER_BUFFER_SIZE; i++) { if (p == OLED_COLOR_WHITE) { rgbOledBmp[i] = 0xFF; } else { rgbOledBmp[i] = 0; } } } void OledSetDisplayInverted(void) { #ifndef __MPLAB_DEBUGGER_SIMULATOR OledDriverSetDisplayInverted(); #endif } void OledSetDisplayNormal(void) { #ifndef __MPLAB_DEBUGGER_SIMULATOR OledDriverSetDisplayNormal(); #endif } void OledOn(void) { #ifndef __MPLAB_DEBUGGER_SIMULATOR OledDriverInitDisplay(); #endif } void OledOff(void) { #ifndef __MPLAB_DEBUGGER_SIMULATOR OledDriverDisableDisplay(); #endif } void OledUpdate(void) { #ifndef __MPLAB_DEBUGGER_SIMULATOR OledDriverUpdateDisplay(); #endif }
OledDriver.h
#ifndef OLED_DRIVER_H #define OLED_DRIVER_H // Include standard C libraries. #include <stdint.h> // Include Microchip C libraries. #include <xc.h> /** * Configure the port and pins for each of the 4 control signals used with the OLED: * * F6: Controls the power to the controller logic. Active-low, so a 0 powers it on. * * F5: Controls the power to the OLED display. Active-low, so a 0 powers it on. * * F4: Sets the input mode of the controller logic. High indicates incoming data is display * data, while low indicates they're commands. * * G9: Reset pin connected to the display controller. Active-low, so a 0 holds the logic in * reset. */ #define OLED_DRIVER_CONTROLLER_POWER_PORT PORTFbits.RF6 #define OLED_DRIVER_CONTROLLER_POWER_TRIS TRISFbits.TRISF6 #define OLED_DRIVER_OLED_POWER_PORT PORTFbits.RF5 #define OLED_DRIVER_OLED_POWER_TRIS TRISFbits.TRISF5 #define OLED_DRIVER_MODE_PORT PORTFbits.RF4 #define OLED_DRIVER_MODE_TRIS TRISFbits.TRISF4 #define OLED_DRIVER_RESET_PORT PORTGbits.RG9 #define OLED_DRIVER_RESET_TRIS TRISGbits.TRISG9 // The number of pixel columns in the OLED display. #define OLED_DRIVER_PIXEL_COLUMNS 128 // The number of pixel rows in the OLED display. #define OLED_DRIVER_PIXEL_ROWS 32 // Store how high each column is for the OLED in bits in terms of data structure storage. #define OLED_DRIVER_BUFFER_LINE_HEIGHT 8 // The number of bytes required to store all the data for the whole display. 1 bit / pixel. #define OLED_DRIVER_BUFFER_SIZE ((OLED_DRIVER_PIXEL_COLUMNS * OLED_DRIVER_PIXEL_ROWS) / 8) /** * This array is the off-screen frame buffer used for rendering. It isn't possible to read back from * the OLED display device, so display data is rendered into this off-screen buffer and then copied * to the display. The high-order bits equate to the lower pixel rows. * @note Any time this is updated, An `OledDriverUpdateDisplay()` call must be performed. */ extern uint8_t rgbOledBmp[OLED_DRIVER_BUFFER_SIZE]; /** * Initialize the PIC32MX to communicate with the UG-23832HSWEG04 OLED display through the SSD1306 * display controller. */ void OledHostInit(void); /** * Initialize the OLED display and driver circuitry. */ void OledDriverInitDisplay(void); /** * Disable the Oled display before power-off. This means powering it up, sending the display off * command, and finally disabling Vbat. */ void OledDriverDisableDisplay(void); /** * Update the display with the contents of rgb0ledBmp. */ void OledDriverUpdateDisplay(void); /** * Set the LCD to display pixel values as the opposite of how they are actually stored in NVRAM. So * pixels set to black (0) will display as white, and pixels set to white (1) will display as black. */ void OledDriverSetDisplayInverted(void); /** * Set the LCD to display pixel values normally, where a 1 indicates white and a 0 indicates black. * This is the default operating mode of the LCD and the mode it starts up in. */ void OledDriverSetDisplayNormal(void); #endif // OLED_DRIVER_H
OledDriver.c
#include <stdint.h> //CSE13E Support Library #include "BOARD.h" #include <xc.h> #include "OledDriver.h" #define SPI_CHANNEL SPI_CHANNEL2 /** * Constants for the various command values that can be sent to the OLED driver. */ typedef enum { OLED_COMMAND_SET_DISPLAY_LOWER_COLUMN_0 = 0x00, OLED_COMMAND_SET_DISPLAY_UPPER_COLUMN_0 = 0x10, OLED_COMMAND_SET_PAGE = 0x22, OLED_COMMAND_SET_CHARGE_PUMP = 0x8D, OLED_COMMAND_SET_SEGMENT_REMAP = 0xA1, OLED_COMMAND_DISPLAY_NORMAL = 0xA6, OLED_COMMAND_DISPLAY_INVERTED = 0xA7, OLED_COMMAND_DISPLAY_OFF = 0xAE, OLED_COMMAND_DISPLAY_ON = 0xAF, OLED_COMMAND_SET_PRECHARGE_PERIOD = 0xD9, OLED_COMMAND_SET_COM_PINS_CONFIG = 0xDA } OledCommand; /** * Store constants for all settings used with the OLED driver. */ typedef enum { OLED_SETTING_ENABLE_CHARGE_PUMP = 0x14, OLED_SETTING_MAXIMUM_PRECHARGE = 0xF1, OLED_SETTING_SEQUENTIAL_COM_NON_INTERLEAVED = 0x20, OLED_SETTING_REVERSE_ROW_ORDERING = 0xC8 } OledSetting; #define OLED_DRIVER_PAGES 4 /** * This array is the off-screen frame buffer used for rendering. * It isn't possible to read back from the OLED display device, * so display data is rendered into this off-screen buffer and then * copied to the display. * @note Any time this is updated, An `OledDriverUpdateDisplay()` call must be performed. */ uint8_t rgbOledBmp[OLED_DRIVER_BUFFER_SIZE]; // Function prototypes for internal-use functions. void OledPutBuffer(int size, uint8_t *buffer); uint8_t Spi2Put(uint8_t bVal); void DelayMs(unsigned int msec); /** * Initialize the PIC32MX to communicate with the UG-23832HSWEG04 OLED display through the SSD1306 * display controller. */ void OledHostInit(void) { // Open SPI2 as a master in 1-byte mode running at 10MHz. // The peripheral bus is running at 10Mhz, and we want a 10MHz SPI bus clock. int pbClkDiv = 20000000 / 10000000; SPI2CON = 0; // reset and clear the SPI config register SPI2CONbits.MSTEN = 1; // We are a Master SPI2CONbits.CKP = 1; // Idle is high level SPI2BRG = (pbClkDiv >> 1) - 1; // set the baud rate to the correct setting. SPI2CONbits.ON = 1; // turn it on // Set RF4-6 as digital outputs for controlling data/command selection, logic power, and display // power. They're all initialized high beforehand, because that disables power. OLED_DRIVER_CONTROLLER_POWER_PORT = 1; OLED_DRIVER_OLED_POWER_PORT = 1; OLED_DRIVER_MODE_PORT = 1; OLED_DRIVER_MODE_TRIS = 0; OLED_DRIVER_CONTROLLER_POWER_TRIS = 0; OLED_DRIVER_OLED_POWER_TRIS = 0; // Set RG9 as a digital output, tied to the reset pin on the SG1306 controller, low => reset. OLED_DRIVER_RESET_PORT = 1; OLED_DRIVER_RESET_TRIS = 0; } /** * Initialize the OLED display and driver circuitry. */ void OledDriverInitDisplay(void) { // Set the OLED into command mode. OLED_DRIVER_MODE_PORT = 0; // Power on the display logic, waiting 1ms for it to start up. OLED_DRIVER_CONTROLLER_POWER_PORT = 0; DelayMs(1); // Turn off the display. Spi2Put(OLED_COMMAND_DISPLAY_OFF); // Toggle the reset pin. OLED_DRIVER_RESET_PORT = 0; DelayMs(1); OLED_DRIVER_RESET_PORT = 1; // Enable the charge pump and Spi2Put(OLED_COMMAND_SET_CHARGE_PUMP); Spi2Put(OLED_SETTING_ENABLE_CHARGE_PUMP); Spi2Put(OLED_COMMAND_SET_PRECHARGE_PERIOD); Spi2Put(OLED_SETTING_MAXIMUM_PRECHARGE); // Power on the display, giving it 100ms to start up. OLED_DRIVER_OLED_POWER_PORT = 0; DelayMs(100); // Invert row numbering so that (0,0) is upper-right. Spi2Put(OLED_COMMAND_SET_SEGMENT_REMAP); Spi2Put(OLED_SETTING_REVERSE_ROW_ORDERING); // Set sequential COM configuration with non-interleaved memory. Spi2Put(OLED_COMMAND_SET_COM_PINS_CONFIG); Spi2Put(OLED_SETTING_SEQUENTIAL_COM_NON_INTERLEAVED); // And turn on the display. Spi2Put(OLED_COMMAND_DISPLAY_ON); } /** * Set the LCD to display pixel values as the opposite of how they are actually stored in NVRAM. So * pixels set to black (0) will display as white, and pixels set to white (1) will display as black. */ void OledDriverSetDisplayInverted(void) { // Set the OLED into command mode. OLED_DRIVER_MODE_PORT = 0; Spi2Put(OLED_COMMAND_DISPLAY_INVERTED); } /** * Set the LCD to display pixel values normally, where a 1 indicates white and a 0 indicates black. * This is the default operating mode of the LCD and the mode it starts up in. */ void OledDriverSetDisplayNormal(void) { // Set the OLED into command mode. OLED_DRIVER_MODE_PORT = 0; Spi2Put(OLED_COMMAND_DISPLAY_NORMAL); } /** * Disable the Oled display before power-off. This means powering it up, sending the display off * command, and finally disabling Vbat. */ void OledDriverDisableDisplay(void) { // Set the OLED into command mode. OLED_DRIVER_MODE_PORT = 0; // Power on the OLED display logic, waiting for 1ms for it to start up. OLED_DRIVER_CONTROLLER_POWER_PORT = 0; DelayMs(1); // Send the display off command. Spi2Put(OLED_COMMAND_DISPLAY_OFF); // And finally power off the display, giving it 100ms to do so. OLED_DRIVER_CONTROLLER_POWER_PORT = 1; DelayMs(100); } /** * Update the display with the contents of rgb0ledBmp. */ void OledDriverUpdateDisplay(void) { uint8_t *pb = rgbOledBmp; int page; for (page = 0; page < OLED_DRIVER_PAGES; page++) { // Set the LCD into command mode. // PORTClearBits(OLED_DRIVER_MODE_PORT, OLED_DRIVER_MODE_BIT); OLED_DRIVER_MODE_PORT = 0; // Set the desired page. Spi2Put(OLED_COMMAND_SET_PAGE); Spi2Put(page); // Set the starting column back to the origin. Spi2Put(OLED_COMMAND_SET_DISPLAY_LOWER_COLUMN_0); Spi2Put(OLED_COMMAND_SET_DISPLAY_UPPER_COLUMN_0); // Return the LCD to data mode. // PORTSetBits(OLED_DRIVER_MODE_PORT, OLED_DRIVER_MODE_BIT); OLED_DRIVER_MODE_PORT = 1; // Finally write this entire column to the OLED. // SpiChnPutS() OledPutBuffer(OLED_DRIVER_PIXEL_COLUMNS, pb); pb += OLED_DRIVER_PIXEL_COLUMNS; } } /** * Write an entire array of uint8_ts over SPI2. * @param size The number of uint8_ts to write. * @param buffer The start of the uint8_t array to write. */ void OledPutBuffer(int size, uint8_t *buffer) { uint8_t bTmp = 0; int i = bTmp; //non ideal way of forcing Wall error to go away for (i = 0; i < size; ++i) { // Make sure the transmitter is ready while (SPI2STATbits.SPITBE == 0); // Then transmit the desired uint8_t. SPI2BUF = *buffer++; // And wait for a response. It's ignored, but we read it out of the buffer anyways to keep // the buffer clear. while (SPI2STATbits.SPIRBF == 0); bTmp = SPI2BUF; } } /** * Performs a blocking write of a single uint8_t over SPI2. The response uint8_t is returned. * @param bVal The uint8_t to write over SPI. * @return The response to the transmission. */ uint8_t Spi2Put(uint8_t bVal) { // Make sure the transmitter is ready while (SPI2STATbits.SPITBE == 0); // Then transmit the desired uint8_t. SPI2BUF = bVal; // And wait for a response. while (SPI2STATbits.SPIRBF == 0); // Before returning it. uint8_t bRx = SPI2BUF; return bRx; } /** * Block the processor for the desired number of milliseconds. * @note Assumes processor frequency of 80Mhz. * @param msec The number of milliseconds to block for. */ void DelayMs(uint32_t msec) { uint32_t tWait, tStart, tCurrent; // Calculate the amount of wait time in terms of core processor frequency. tWait = (80000000L / 2000) * msec; asm volatile("mfc0 %0, $9" : "=r"(tStart)); tCurrent = tStart; while ((tCurrent - tStart) < tWait) { asm volatile("mfc0 %0, $9" : "=r"(tCurrent)); }// wait for the time to pass }
Ascii.h
#ifndef ASCII_H #define ASCII_H #include <stdint.h> // Specify the height and width of the characters defined in this library. #define ASCII_FONT_HEIGHT 8 #define ASCII_FONT_WIDTH 6 /** * Pick a font for most of the standard ASCII characters into a byte array. Each character is stored * as ASCII_FONT_WIDTH number of bytes which each byte corresponding to a vertical line of 8 pixels * on the display. * */ extern const uint8_t ascii[256][ASCII_FONT_WIDTH]; /** * In the Toaster Oven lab, some special "artwork" characters are used to represent * elements of the toaster oven. These characters are #defined below as string literals * with escape characters, and can be included in strings using string literal concatenation like so: * * char *example_string = "Top of oven looks like" OVEN_TOP_ON "when on" * * or alternatively, they can be inserted in a string using sprintf format specifiers: * * sprintf(dest_str, "Top of oven looks like %s when on", OVEN_TOP_ON); * */ #define OVEN_TOP_ON "\x01" #define OVEN_TOP_OFF "\x02" #define OVEN_BOTTOM_ON "\x03" #define OVEN_BOTTOM_OFF "\x04" #define DEGREE_SYMBOL "\xF8" #endif // ASCII_H
Ascii.c
#include "Ascii.h" /* * This file defines a bitmap font corresponding to the standard ASCII character set (0-7F). */ const unsigned char ascii[256][6] = { // Non-printing characters 0x00 - 0x1F {0,0,0,0,0,0}, { // top of oven, on (0x01) 0b00000101, 0b11110011, 0b00000101, 0b11110011, 0b00000101, 0b11110011 }, { // top of oven, off (0x02) 0b00000101, 0b00000011, 0b00000101, 0b00000011, 0b00000101, 0b00000011 }, { // bottom of oven, on (0x03) 0b10100000, 0b11001111, 0b10100000, 0b11001111, 0b10100000, 0b11001111 }, { // bottom of oven, off (0x04) 0b10100000, 0b11000000, 0b10100000, 0b11000000, 0b10100000, 0b11000000 },{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, { // (space) 0x20 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000 }, { // ! 0x21 0b00000000, 0b00000000, 0b01011110, 0b00000000, 0b00000000, 0b00000000 }, { // " 0x22 0b00001100, 0b00000010, 0b00000000, 0b00001100, 0b00000010, 0b00000000 }, { // # 0x23 0b00010100, 0b01111111, 0b00010100, 0b01111111, 0b00010100, 0b00000000 }, { // $ 0x24 0b00100100, 0b00101010, 0b01111111, 0b00101010, 0b00010010, 0b00000000 }, { // % 0x25 0b00100011, 0b00010011, 0b00001000, 0b01100100, 0b01100010, 0b00000000 }, { // & 0x26 0b00110110, 0b01001001, 0b01010001, 0b00100010, 0b01010000, 0b00000000 }, { // ' 0x27 0b00000000, 0b00000000, 0b00001100, 0b00000010, 0b00000000, 0b00000000 }, { // ( 0x28 0b00000000, 0b00000000, 0b00111110, 0b01000001, 0b00000000, 0b00000000 }, { // ) 0x29 0b00000000, 0b01000001, 0b00111110, 0b00000000, 0b00000000, 0b00000000 }, { // * 0x2A 0b00001010, 0b00000100, 0b00011111, 0b00000100, 0b00001010, 0b00000000 }, { // + 0x2B 0b00001000, 0b00001000, 0b00111110, 0b00001000, 0b00001000, 0b00000000 }, { // , 0x2C 0b00000000, 0b00000000, 0b01010000, 0b00110000, 0b00000000, 0b00000000 }, { // - 0x2D 0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b00000000 }, { // . 0x2E 0b00000000, 0b01100000, 0b01100000, 0b00000000, 0b00000000, 0b00000000 }, { // / 0x2F 0b01000000, 0b00110000, 0b00001000, 0b00000110, 0b00000001, 0b00000000 }, { // 0 0x30 0b00111110, 0b01010001, 0b01001001, 0b01000101, 0b00111110, 0b00000000 }, { // 1 0x31 0b01000010, 0b01000010, 0b01111111, 0b01000000, 0b01000000, 0b00000000 }, { // 2 0x32 0b01000110, 0b01100001, 0b01010001, 0b01001001, 0b01000110, 0b00000000 }, { // 3 0x33 0b00100010, 0b01000001, 0b01001001, 0b01001001, 0b00110110, 0b00000000 }, { // 4 0x34 0b00011000, 0b00010100, 0b00010010, 0b01111111, 0b00010000, 0b00000000 }, { // 5 0x35 0b01001111, 0b01001001, 0b01001001, 0b01001001, 0b00110001, 0b00000000 }, { // 6 0x36 0b00111100, 0b01001010, 0b01001001, 0b01001001, 0b00110000, 0b00000000 }, { // 7 0x37 0b00000001, 0b01110001, 0b00001001, 0b00000101, 0b00000011, 0b00000000 }, { // 8 0x38 0b00110110, 0b01001001, 0b01001001, 0b01001001, 0b00110110, 0b00000000 }, { // 9 0x39 0b00000110, 0b01001001, 0b01001001, 0b00101001, 0b00011110, 0b00000000 }, { // : 0x3A 0b00000000, 0b00000000, 0b00110110, 0b00110110, 0b00000000, 0b00000000 }, { // ; 0x3B 0b00000000, 0b00000000, 0b01010110, 0b00110110, 0b00000000, 0b00000000 }, { // < 0x3C 0b00001000, 0b00010100, 0b00100010, 0b01000001, 0b00000000, 0b00000000 }, { // = 0x3D 0b00010100, 0b00010100, 0b00010100, 0b00010100, 0b00010100, 0b00000000 }, { // > 0x3E 0b00000000, 0b01000001, 0b00100010, 0b00010100, 0b00001000, 0b00000000 }, { // ? 0x3F 0b00000110, 0b00000001, 0b01010001, 0b00001001, 0b00000110, 0b00000000 }, { // @ 0x40 0b00111110, 0b01000001, 0b01011101, 0b01010101, 0b00111110, 0b00000000 }, { // A 0x41 0b01111110, 0b00001001, 0b00001001, 0b00001001, 0b01111110, 0b00000000 }, { // B 0x42 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b00110110, 0b00000000 }, { // C 0x43 0b00111110, 0b01000001, 0b01000001, 0b01000001, 0b01000001, 0b00000000 }, { // D 0x44 0b01111111, 0b01000001, 0b01000001, 0b01000001, 0b00111110, 0b00000000 }, { // E 0x45 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b01000001, 0b00000000 }, { // F 0x46 0b01111111, 0b00001001, 0b00001001, 0b00001001, 0b00000001, 0b00000000 }, { // G 0x47 0b00111110, 0b01000001, 0b01000001, 0b01001001, 0b00111001, 0b00000000 }, { // H 0x48 0b01111111, 0b00001000, 0b00001000, 0b00001000, 0b01111111, 0b00000000 }, { // I 0x49 0b01000001, 0b01000001, 0b01111111, 0b01000001, 0b01000001, 0b00000000 }, { // J 0x4A 0b00110001, 0b01000001, 0b01000001, 0b00111111, 0b00000001, 0b00000000 }, { // K 0x4B 0b01111111, 0b00001000, 0b00001000, 0b00010100, 0b01100011, 0b00000000 }, { // L 0x4C 0b01111111, 0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b00000000 }, { // M 0x4D 0b01111111, 0b00000010, 0b00001100, 0b00000010, 0b01111111, 0b00000000 }, { // N 0x4E 0b01111111, 0b00000100, 0b00001000, 0b00010000, 0b01111111, 0b00000000 }, { // O 0x4F 0b00111110, 0b01000001, 0b01000001, 0b01000001, 0b00111110, 0b00000000 }, { // P 0x50 0b01111111, 0b00001001, 0b00001001, 0b00001001, 0b00000110, 0b00000000 }, { // Q 0x51 0b00111110, 0b01000001, 0b01010001, 0b00100001, 0b01011110, 0b00000000 }, { // R 0x52 0b01111111, 0b00001001, 0b00001001, 0b00001001, 0b01110110, 0b00000000 }, { // S 0x53 0b01000110, 0b01001001, 0b01001001, 0b01001001, 0b00110001, 0b00000000 }, { // T 0x54 0b00000001, 0b00000001, 0b01111111, 0b00000001, 0b00000001, 0b00000000 }, { // U 0x55 0b00111111, 0b01000000, 0b01000000, 0b01000000, 0b00111111, 0b00000000 }, { // V 0x56 0b00011111, 0b00100000, 0b01000000, 0b00100000, 0b00011111, 0b00000000 }, { // W 0x57 0b00111111, 0b01000000, 0b00110000, 0b01000000, 0b00111111, 0b00000000 }, { // X 0x58 0b01100011, 0b00010100, 0b00001000, 0b00010100, 0b01100011, 0b00000000 }, { // Y 0x59 0b00000111, 0b00001000, 0b01110000, 0b00001000, 0b00000111, 0b00000000 }, { // Z 0x5A 0b01100001, 0b01010001, 0b01001001, 0b01000101, 0b01000011, 0b00000000 }, { // [ 0x5B 0b00000000, 0b01111111, 0b01000001, 0b01000001, 0b00000000, 0b00000000 }, { // \ 0x5C 0b00000001, 0b00000110, 0b00001000, 0b00110000, 0b01000000, 0b00000000 }, { // ] 0x5D 0b00000000, 0b01000001, 0b01000001, 0b01111111, 0b00000000, 0b00000000 }, { // ^ 0x5E 0b00000100, 0b00000010, 0b00000001, 0b00000010, 0b00000100, 0b00000000 }, { // _ 0x5F 0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b00000000 }, { // ` 0x60 0b00000000, 0b00000001, 0b00000010, 0b00000100, 0b00000000, 0b00000000 }, { // a 0x61 0b00100000, 0b01010100, 0b01010100, 0b01010100, 0b01111000, 0b00000000 }, { // b 0x62 0b01111111, 0b01000100, 0b01000100, 0b01000100, 0b00111000, 0b00000000 }, { // c 0x63 0b00111000, 0b01000100, 0b01000100, 0b01000100, 0b01000100, 0b00000000 }, { // d 0x64 0b00111000, 0b01000100, 0b01000100, 0b01000100, 0b01111111, 0b00000000 }, { // e 0x65 0b00111000, 0b01010100, 0b01010100, 0b01010100, 0b01011000, 0b00000000 }, { // f 0x66 0b00001000, 0b01111110, 0b00001001, 0b00001001, 0b00000010, 0b00000000 }, { // g 0x67 0b00001000, 0b01010100, 0b01010100, 0b01010100, 0b00111000, 0b00000000 }, { // h 0x68 0b01111111, 0b00000100, 0b00000100, 0b00000100, 0b01111000, 0b00000000 }, { // i 0x69 0b00000000, 0b01001000, 0b01111010, 0b01000000, 0b00000000, 0b00000000 }, { // j 0x6A 0b00100000, 0b01000000, 0b01000000, 0b00111010, 0b00000000, 0b00000000, }, { // k 0x6B 0b01111111, 0b00010000, 0b00010000, 0b00101000, 0b01000100, 0b00000000 }, { // l 0x6C 0b00000000, 0b00000001, 0b01111111, 0b01000000, 0b00000000, 0b00000000 }, { // m 0x6D 0b01111100, 0b00000100, 0b01111000, 0b00000100, 0b01111100, 0b00000000 }, { // n 0x6E 0b01111100, 0b00001000, 0b00000100, 0b00000100, 0b01111000, 0b00000000 }, { // o 0x6F 0b00111000, 0b01000100, 0b01000100, 0b01000100, 0b00111000, 0b00000000 }, { // p 0x70 0b11111100, 0b00100100, 0b00100100, 0b00100100, 0b00011000, 0b00000000 }, { // q 0x71 0b00011000, 0b00100100, 0b00100100, 0b00100100, 0b11111100, 0b00000000 }, { // r 0x72 0b01111100, 0b00001000, 0b00000100, 0b00000100, 0b00000100, 0b00000000 }, { // s 0x73 0b01001000, 0b01010100, 0b01010100, 0b01010100, 0b00100100, 0b00000000 }, { // t 0x74 0b00000100, 0b00111110, 0b01000100, 0b01000100, 0b00000000, 0b00000000 }, { // u 0x75 0b00111100, 0b01000000, 0b01000000, 0b01000000, 0b00111100, 0b00000000 }, { // v 0x76 0b00011100, 0b00100000, 0b01000000, 0b00100000, 0b00011100, 0b00000000 }, { // w 0x77 0b00111100, 0b01000000, 0b00100000, 0b01000000, 0b00111100, 0b00000000 }, { // x 0x78 0b01000100, 0b00101000, 0b00010000, 0b00101000, 0b01000100, 0b00000000 }, { // y 0x79 0b00001100, 0b01010000, 0b01010000, 0b01010000, 0b00111100, 0b00000000 }, { // z 0x7A 0b01000100, 0b01100100, 0b01010100, 0b01001100, 0b01000100, 0b00000000 }, { // { 0x7B 0b00001000, 0b00001000, 0b00110110, 0b01000001, 0b00000000, 0b00000000 }, { // | 0x7C 0b00000000, 0b00000000, 0b01111111, 0b00000000, 0b00000000, 0b00000000 }, { // } 0x7D 0b00000000, 0b01000001, 0b00110110, 0b00001000, 0b00001000, 0b00000000 }, { // ~ 0x7E 0b00001000, 0b00000100, 0b00001000, 0b00001000, 0b00000100, 0b00000000 }, { // DEL 0x7F 0b00000000, 0b00010000, 0b00111000, 0b00010000, 0b00000000, 0b00000000 }, {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 131 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 135 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 139 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 143 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 147 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 151 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 155 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 159 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 163 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 167 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 171 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 175 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 179 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 183 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 187 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 191 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 195 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 199 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 203 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 207 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 211 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 215 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 219 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 223 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 227 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 231 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 235 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 239 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 243 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 247 { // degree 0xF8 (248) 0b00000000, 0b00000110, 0b00001001, 0b00001001, 0b00000110, 0b00000000 }, {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0}, // 251 {0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0},{0,0,0,0,0,0} // 255 };
Uart1.h
#ifndef UART1_H #define UART1_H // USAGE: // Add Uart1Init() to an initialization sequence called once on startup. // Use Uart1Write*Data() to push appropriately-sized data chunks into the queue and begin transmission. // Use Uart1ReadByte() to read bytes out of the buffer #include <stddef.h> #include <stdint.h> #include "CircularBuffer.h" /** * Initializes the UART1 peripheral according to the BRG SFR value passed to it. * @param brgRegister The value to be placed in the BRG register. */ void Uart1Init(uint32_t brgRegister); /** * Alters the baud rate of the UART1 peripheral to that dictated by brgRegister. * @param brgRegister The new baud rate. */ void Uart1ChangeBaudRate(uint16_t brgRegister); /** * Returns whether UART1 has data available for reading. * @return True if there is data in the RX buffer for UART1. */ uint8_t Uart1HasData(void); /** * This function reads a byte out of the received data buffer for UART1. * @param datum The data received from the buffer. If no data was there it's unmodified. * @return A boolean value of whether valid data was returned. */ int Uart1ReadByte(uint8_t *datum); /** * This function starts a transmission sequence after enqueuing a single byte into * the buffer. */ void Uart1WriteByte(uint8_t datum); /** * This function augments the uart1EnqueueByte() function by providing an interface * that enqueues multiple bytes. */ int Uart1WriteData(const void *data, size_t length); #endif // UART1_H
Uart1.c
#include "CircularBuffer.h" #include "Uart1.h" //CSE13E Support Library #include "BOARD.h" #include <xc.h> #include <sys/attribs.h> static CircularBuffer uart1RxBuffer; static uint8_t u1RxBuf[1024]; static CircularBuffer uart1TxBuffer; static uint8_t u1TxBuf[1024]; /* * Private functions. */ void Uart1StartTransmission(void); /** * Initialization function for the UART_USED peripheral. * Should be called in initialization code for the * model. This function configures the UART * for whatever baud rate is specified. It also configures two circular buffers * for transmission and reception. */ void Uart1Init(uint32_t baudRate) { // First initialize the necessary circular buffers. CB_Init(&uart1RxBuffer, u1RxBuf, sizeof (u1RxBuf)); CB_Init(&uart1TxBuffer, u1TxBuf, sizeof (u1TxBuf)); #ifdef PIC32MX //the next few lines below are redundant with actions performed in BOARD_Init(): U1MODEbits.ON = 1; //turn on UART U1STAbits.UTXEN = 1; //enable TX pin U1STAbits.URXEN = 1; //enable RX oun // The FIFO mode here for transmission is not set to `*_TX_BUFFER_EMPTY` as that seems to fail // with some characters dropped. This method, waiting until transmission is finished, is // technically slower, but works quite nicely. U1STAbits.UTXISEL = 0b01; //interrupt when transmission is complete U1STAbits.URXISEL = 0b00; //interrupt when RX is not empty (has at least 1 character) // Configure UART interrupt for both RX and TX IEC0bits.U1RXIE = 1; //enable RX interrupt IEC0bits.U1TXIE = 1; //enable TX interrupt IPC6bits.U1IP = 6; //set UART interrupt priority to 6 IPC6bits.U1IS = 0; //set interrupt subpriority to 0 #endif } void Uart1ChangeBaudRate(uint16_t brgRegister) { uint8_t utxen = U1STAbits.UTXEN; // Disable the port; U1MODEbits.UARTEN = 0; // Change the BRG register to set the new baud rate U1BRG = brgRegister; // Enable the port restoring the previous transmission settings U1MODEbits.UARTEN = 1; U1STAbits.UTXEN = utxen; } uint8_t Uart1HasData(void) { return (uart1RxBuffer.dataSize > 0); } /** * This function actually initiates transmission. It * attempts to start transmission with the first element * in the queue if transmission isn't already proceeding. * Once transmission starts the interrupt handler will * keep things moving from there. The buffer is checked * for new data and the transmission buffer is checked that * it has room for new data before attempting to transmit. */ void Uart1StartTransmission(void) { while (uart1TxBuffer.dataSize > 0 && !U1STAbits.UTXBF) { // A temporary variable is used here because writing directly into U1TXREG causes some weird issues. uint8_t c; CB_ReadByte(&uart1TxBuffer, &c); U1TXREG = c; } } int Uart1ReadByte(uint8_t *datum) { return CB_ReadByte(&uart1RxBuffer, datum); } /** * This function supplements the uart1EnqueueData() function by also * providing an interface that only enqueues a single byte. */ void Uart1WriteByte(uint8_t datum) { CB_WriteByte(&uart1TxBuffer, datum); Uart1StartTransmission(); } /** * This function enqueues all bytes in the passed data character array according to the passed * length. */ int Uart1WriteData(const void *data, size_t length) { int success = CB_WriteMany(&uart1TxBuffer, data, length, FALSE); Uart1StartTransmission(); return success; } #ifdef PIC32MX void __ISR(_UART_1_VECTOR, ipl6auto) Uart1Interrupt(void) { // if receive flag is set, handle received character input if (IFS0bits.U1RXIF) { // Keep receiving new bytes while the buffer has data. while (U1STAbits.URXDA == 1) { CB_WriteByte(&uart1RxBuffer, (uint8_t) U1RXREG); } // Clear buffer overflow bit if triggered if (U1STAbits.OERR == 1) { U1STAbits.OERR = 0; } // Clear the interrupt flag IFS0bits.U1RXIF = 0; } // Handle transmission interrupt if (IFS0bits.U1TXIF) { Uart1StartTransmission(); // Clear the interrupt flag IFS0bits.U1TXIF = 0; } } #endif
CircularBuffer.h
/* * Copyright Bar Smith, Bryant Mairs 2012 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses. */ /** * @file CircularBuffer.h * @author Bar Smith * @author Bryant Mairs * @date August, 2012 * @brief Provides a circular buffer implementation for bytes and non-primitive datatypes. * * This circular buffer provides a single buffer interface for almost any situation necessary. It * has been written for use with the dsPIC33f, but has been tested on x86. * * Unit testing has been completed on x86 by compiling with the UNIT_TEST_CIRCULAR_BUFFER macro. * With gcc: `gcc CircularBuffer.c -DUNIT_TEST_CIRCULAR_BUFFER` */ #ifndef CIRCULAR_BUFFER_H #define CIRCULAR_BUFFER_H #include <stdint.h> /** * @brief A structure which holds information about the circular buffer. * * This struct contains all of the metadata necessary to implemented a circular buffer using the * memory space pointed to by `data`. * * The useful properties are dataSize and overflowCount. Most of the other properties you probably * don't care about and shouldn't touch. */ typedef struct { uint16_t readIndex; //!< Holds the index of the tail of the list. Always points to valid data when empty is FALSE. uint16_t writeIndex; //!< Holds the index of the head of the list. Always points to empty space except when buffer is full. uint16_t staticSize; //!< Stores the static size of the buffer. The actual number of data bytes stored can be retrieved by CB_LENGTH() or CB_GetLength(). uint16_t dataSize; //!< The actual number of unread bytes in the buffer. uint8_t overflowCount; //!< Tracks how many bytes have been attempted to be written while the buffer was full. uint8_t *data; //!< A pointer to the actual data managed by this buffer. } CircularBuffer; /** * @brief CB_Init initializes the buffer. * * Initializes the passed CircularBuffer to the proper values. If either buffer pointer is NULL or * null or the size is <= 1 this function returns STANDARD_ERROR, otherwise SUCCESS is returned. * * This function is idempotent and can also be used to re-initialize a CircularBuffer struct. This * will effectively reset a buffer is used with the original buffer pointer and size. Otherwise it * can change a buffer to use another buffer pointer and size. * * @param b A pointer to a circular buffer struct * @param data A pointer to where the data will be stored. * @param size The length of the buffer. */ int CB_Init(CircularBuffer *b, uint8_t *data, const uint16_t size); /** * @brief CB_ReadByte() reads a byte from the buffer. * * CB_ReadByte() is the inverse of CB_WriteByte(), it reads a single value from `b` and stores it in * data. It returns STANDARD_ERROR if b was NULL or had no data to return. * * @see CB_ReadMany() * * @param b A pointer to the CircularBuffer struct. * @param outData A pointer to where the value will be saved. */ int CB_ReadByte(CircularBuffer *b, uint8_t *outData); /** * @brief CB_ReadMany() reads multiple bytes from the buffer. * * CB_ReadMany reads the top size number of bytes from the buffer `b` to the memory pointed to by * `data`. If there are not `size` number of elements currently in the buffer the function will * write nothing to memory, remove nothing from the buffer, and return STANDARD_ERROR. If all * elements are successfully removed from the buffer the function will return SUCCESS. CB_ReadMany * can work easily with any non-primitives. * * Example use with an array: * unsigned char readresults[30]; * CB_ReadMany(&b, readresults, 30); * * Example use with a struct: * struct d { * unsigned char c; * int64 a; * } myStruct; * CircularBuffer b; * CB_ReadMany(&b, &myStruct, sizeof(d)); * * @see CB_ReadByte() * * @param b A pointer to the CircularBuffer struct. * @param outData A pointer to where the data will be stored. * @param size The number of bytes to be read. */ int CB_ReadMany(CircularBuffer *b, void *outData, uint16_t size); /** * @brief CB_WriteByte writes a byte into the buffer. * * CB_WriteByte() writes the new uint8_t data into CircularBuffer b. SUCCESS is * returned if that value was successfully added. STANDARD_ERROR is returned if * the buffer overflows or b was NULL. If the buffer overflows the new item is * not inserted. * * @param b A pointer to the CircularBuffer struct. * @param outData The value to be written to the buffer. * * @see CB_WriteMany() */ int CB_WriteByte(CircularBuffer *b, uint8_t outData); /** * @brief CB_WriteMany() writes multiple bytes into the buffer. * * CB_WriteMany() writes the first `size` number of elements from the array `data` to the * CircularBuffer b. If the boolean value failEarly is TRUE the function will return STANDARD_ERROR * if there is not enough space in the buffer. If `failEarly` is FALSE then as many elements as * will fit will be written to the buffer. When the buffer is full the function will do nothing and * return STANDARD_ERROR. * * CB_WriteMany can also be used to write structures to the buffer. When writing structures it is * recommended that failEarly be set to TRUE so that partial structures won't be written. * * @param b A pointer to the CircularBuffer struct. * @param data A pointer to the data to be written to the buffer. * @param size The number of bytes to be written. * @param failEarly A flag to switch failure modes. */ int CB_WriteMany(CircularBuffer *b, const void *inData, uint16_t size, uint8_t failEarly); /** * @brief CB_Peek retrieves a byte from the buffer without removing it. * * CB_Peek reads the top element from the buffer `b` and writes it to `data`. If the * buffer is empty or the pointer to buffer is void the function returns STANDARD_ERROR. When * the function succeeds it returns SUCCESS. * * The only difference between this function and CB_ReadByte() is that CB_ReadByte() removes the * elements from the buffer as it reads them, while this does not. This function is idempotent. * * @param b A pointer to the CircularBuffer struct. * @param outData A pointer to the byte that will be recorded */ int CB_Peek(const CircularBuffer *b, uint8_t *outData); /** * @brief CB_PeekMany() copies the top size number of elements to the array pointed to by data * * CB_PeekMany() is an extension of Peek() to multi-byte data structures. Given a desired number of * bytes, CB_PeekMany() will copy those bytes into the passed byte-array. If there aren't enough bytes * in the buffer, then STANDARD_ERROR is returned. This is also the case if the CircularBuffer * pointer is NULL. CB_PeakMany can also be used to peak a structure off the buffer. * * Example use with a struct: * ``` * CircularBuffer b; * // Code that puts data in the buffer. * struct d { * unsigned char c; * int64 a; * } myStruct; * CB_PeekMany(&b, &myStruct, sizeof(d)); * ``` * * @see CB_Peek() * * @param b A pointer to the CircularBuffer struct. * @param outData A pointer to where the data will be stored. * @param size The number of bytes to peek. */ int CB_PeekMany(const CircularBuffer *b, void *outData, uint16_t size); /** * @brief CB_Remove Removes data from the buffer. * * The function CB_Remove removes size number of items from the passed in circular buffer. * b is a pointer to the CircularBuffer struct and size is the number of elements to be removed. * If there are not size elements currently in the buffer, the buffer is emptied. The function * will always return SUCCESS. * * This function is useful for removing data that has already been CB_Peek()d at. An example is with * the ECAN peripheral on the dsPICs where I CB_PeekMany() off entire CAN message structs: * ``` * CanMessage cmsg; * CB_PeekMany(&b, &cmsg, sizeof(CanMessage)); * Ecan1Transmit(&cmsg); * ``` * * Then when the interrupt triggers, which happens only after a successful transmission do I remove * the data: * ``` * interrupt() { * CB_Remove(&b, sizeof(CanMessage)); * } * ``` * * @see CB_Peek() * @see CB_PeekMany() * * @param b A pointer to the circularbuffer structure. * @param size The number of elements to be removed from the buffer. */ int CB_Remove(CircularBuffer *b, uint16_t size); #endif /* CIRCULAR_BUFFER_H */
CircularBuffer.c
/* * Copyright Bar Smith, Bryant Mairs 2012 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses. */ /** * @file CircularBuffer.c * @author Bar Smith * @author Bryant Mairs * @date August, 2012 * @brief Provides a circular buffer implementation for bytes and non-primitive datatypes. * * This circular buffer provides a single buffer interface for almost any situation necessary. It * has been written for use with the dsPIC33f, but has been tested on x86. * * Unit testing has been completed on x86 by compiling with the UNIT_TEST_CIRCULAR_BUFFER macro. * With gcc: `gcc CircularBuffer.c -DUNIT_TEST_CIRCULAR_BUFFER -Wall -g` */ #include "CircularBuffer.h" #include "BOARD.h" #include <stddef.h> #include <stdint.h> #include <stdlib.h> #include <stdio.h> int CB_Init(CircularBuffer *b, uint8_t *buffer, const uint16_t size) { // Check the validity of pointers. if (!buffer || !b) { return FALSE; } // Checks that the size is valid. if (size <= 1) { return FALSE; } // Store the buffer pointer and initialize it all to zero. // This is not necessary, but makes debugging easier. b->data = buffer; uint16_t i; for (i = 0; i < size; ++i) { b->data[i] = 0; } // Initialize all variables. The only one of note is `empty`, which is initialized to TRUE. b->readIndex = 0; b->writeIndex = 0; b->staticSize = size; b->dataSize = 0; b->overflowCount = 0; return TRUE; } int CB_ReadByte(CircularBuffer *b, uint8_t *outData) { if (b) { if (b->dataSize) { //copys the last element from the buffer to data *outData = b->data[b->readIndex]; //sets the buffer empty if there was only one element in it if (b->dataSize == 1) { //checks for wrap around b->readIndex = b->readIndex < (b->staticSize - 1)?b->readIndex + 1:0; } else { //checks for wrap around and moves indicies b->readIndex = b->readIndex < (b->staticSize - 1)?b->readIndex + 1:0; } --b->dataSize; return TRUE; } } return FALSE; } int CB_ReadMany(CircularBuffer *b, void *outData, uint16_t size) { int16_t i; if (b && outData) { //cast data so that it can be used to ready bytes uint8_t *data_u = (uint8_t*)outData; //check if there are enough items in the buffer to read if (b->dataSize >= size) { // And read the data. for (i = 0; i < size; ++i) { data_u[i] = b->data[b->readIndex]; // Update the readIndex taking into account wrap-around. if (b->readIndex < b->staticSize - 1) { ++b->readIndex; } else { b->readIndex = 0; } } b->dataSize -= size; return TRUE; } } return FALSE; } int CB_WriteByte(CircularBuffer *b, uint8_t inData) { if (b) { // If the buffer is full the overflow count is incremented and no data is written. if (b->dataSize == b->staticSize) { ++b->overflowCount; return FALSE; } else { b->data[b->writeIndex] = inData; // Now update the writeIndex taking into account wrap-around. b->writeIndex = b->writeIndex < (b->staticSize - 1) ? b->writeIndex + 1: 0; ++b->dataSize; return TRUE; } } return FALSE; } int CB_WriteMany(CircularBuffer *b, const void *inData, uint16_t size, uint8_t failEarly) { if (b && inData) { uint8_t *data_u = (uint8_t*)inData; //if the fail early value is set if (failEarly) { //Checks to make sure there is enough space if (b->staticSize - b->dataSize < size) { return FALSE; } else { int i = 0; //runs size times while (i < size) { //writes to the buffer b->data[b->writeIndex] = data_u[i]; ++i; //checks for wrap around and moves the indicies b->writeIndex = b->writeIndex < (b->staticSize - 1) ? b->writeIndex + 1: 0; } b->dataSize += i; return TRUE; } } // Otherwise we try and write as much data as we can. else { int i = 0; while (i < size) { //if the buffer is full the overflow count is increased and FALSE is returned if (b->dataSize == b->staticSize) { b->overflowCount += (size - i); return FALSE; } //reads an element from the buffer to data b->data[b->writeIndex] = data_u[i]; ++i; ++b->dataSize; //move the indicies and check for wrap around b->writeIndex = (b->writeIndex < (b->staticSize - 1)) ? b->writeIndex + 1: 0; } return TRUE; } } return FALSE; } int CB_Peek(const CircularBuffer *b, uint8_t *outData) { if (b) { if (b->dataSize > 0) { *outData = b->data[b->readIndex]; return TRUE; } } return FALSE; } int CB_PeekMany (const CircularBuffer *b, void *outData, uint16_t size) { uint16_t i; int tmpHead; if (b) { uint8_t *data_u = (uint8_t*)outData; // Make sure there's enough data to read off and read them off one-by-one. if (b->dataSize >= size) { tmpHead = b->readIndex; for (i = 0; i < size; ++i) { data_u[i] = b->data[tmpHead]; // Handle wrapping around the buffer. if (tmpHead < b->staticSize - 1) { ++tmpHead; } else { tmpHead = 0; } } return TRUE; } } return FALSE; } int CB_Remove(CircularBuffer *b, uint16_t size){ // If there are more elements in the buffer. if (b->dataSize > size) { // Checks to see if the buffer will wrap around. if ((b->staticSize - b->readIndex) < size) { b-> readIndex = size - (b->staticSize - b->readIndex); } else { // If the buffer will not wrap around size is added to read index. b->readIndex = b->readIndex + size; } b->dataSize -= size; return TRUE; } // If one is trying to remove more elements than are in the buffer, the buffer is made empty. else { b->readIndex = b->writeIndex; b->dataSize = 0; return TRUE; } } /** * This begins the unit testing code. Directions for compilation are at the top of the header file. */ #ifdef UNIT_TEST_CIRCULAR_BUFFER #include <string.h> #include <stdio.h> #include <assert.h> /** * @brief A struct used for testing. */ typedef struct { uint8_t hey; int foo; float bar; } TestStruct; /** * @brief Returns whether two circular buffers are equal in their metadata. * * This does not do an exact comparison of their data arrays, just their metadata. */ int TestStructEqual(const TestStruct *a, const TestStruct *b) { return (a->hey == b->hey && a->foo == b->foo && a->bar == b->bar); } /** * @brief Run various unit tests confirming proper operation of the CircularBuffer. * * To run (assuming all files in the same directory and that's your current directory): * ``` * $ gcc CircularBuffer.c -DUNIT_TEST_CIRCULAR_BUFFER -Wall -g * $ a.out * Running unit tests. * All tests passed. * $ */ int main() { printf("Running unit tests.\n"); // These tests check the ability of the circular buffer to write a single item and // then read it back. { // Create a new circular buffer. CircularBuffer b; uint16_t size = 256; uint8_t *buffer = (uint8_t*)malloc(256*sizeof(uint8_t)); CB_Init(&b, buffer, size); assert(!b.dataSize); // Add a single item and check. CB_WriteByte(&b, 0x56); assert(b.dataSize == 1); uint8_t peekval; assert(CB_Peek(&b, &peekval)); assert(peekval == 0x56); // Remove that item and check. uint8_t d; assert(CB_ReadByte(&b, &d) && d == 0x56); assert(b.dataSize == 0); assert(CB_Peek(&b, &peekval) == 0); free(buffer); } /* This tests the ability of the buffer to read and write many items. This code also tests writing two and reading from a buffer which has been wrapped around. Deeppeek is tested. */ { // Create a new circular buffer. CircularBuffer b; uint16_t size = 256; uint8_t *buffer = (uint8_t*)malloc(256*sizeof(uint8_t)); CB_Init(&b, buffer, size); assert(!b.dataSize); // Here we make a 1016 int8_t long string for testing. Testing with the library with BUFFER_SIZE // set to larger than 1016 will produce errors. int8_t testString[] = "Copyright (C) <year> <copyright holders> Permission is hereby granted, free of int8_tge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."; // Fill the buffer to SIZE+1 and check. uint16_t i; for (i = 0; i < b.staticSize; ++i) { assert(b.dataSize == i); assert(CB_WriteByte(&b, testString[i])); assert(b.dataSize == i + 1); } assert(!CB_WriteByte(&b, 0x89)); assert(b.overflowCount == 1); // Run a deepPeek on the now-full buffer. uint8_t tmpString[b.staticSize]; assert(CB_PeekMany(&b, tmpString, b.staticSize)); assert(b.dataSize == b.staticSize); i = 0; while(i < 256){ ++i; } assert(memcmp(testString, tmpString, b.staticSize) == 0); assert(b.dataSize); // Verify reading of an entire circular-buffer uint8_t d; i = b.dataSize; while (i > 0) { assert(CB_ReadByte(&b, &d)); assert(d == testString[b.staticSize - i]); i--; } assert(b.dataSize == 0); d = 0x77; int8_t d2 = d; assert(!CB_ReadByte(&b, &d)); assert(d == d2); //nothing has been read // Test element insertion when the buffer wraps around. uint8_t peekval; assert(CB_WriteByte(&b, 91)); assert(b.overflowCount == 1); // Overflow is triggered on an earlier test assert(b.dataSize == 1); CB_Peek(&b, &peekval); assert(peekval == 91); assert(CB_WriteByte(&b, 92)); assert(b.dataSize == 2); assert(CB_WriteByte(&b, 93)); assert(b.dataSize == 3); assert(CB_WriteByte(&b, 94)); assert(b.dataSize == 4); // Test DeepPeek on wrapped-around buffers uint8_t peekData[4]; assert(CB_PeekMany(&b, peekData, 4)); assert(peekData[0] == 91); assert(peekData[1] == 92); assert(peekData[2] == 93); assert(peekData[3] == 94); // Test reading now. assert(CB_ReadByte(&b, &d) && d == 91); assert(CB_ReadByte(&b, &d) && d == 92); assert(CB_ReadByte(&b, &d) && d == 93); assert(CB_ReadByte(&b, &d) && d == 94); assert(!CB_ReadByte(&b, &d) && d == 94); free(buffer); } /* This section of test code checks that CB_Init will not initialize a buffer if its arguments are not valid. */ { //Test initialization with invalid arguments CircularBuffer b; uint8_t *buffer = (uint8_t*)malloc(256*sizeof(uint8_t)); assert(CB_Init(&b, buffer, 0) == FALSE); //checks the invalid argument size = 0 assert(CB_Init(&b, buffer, 1) == FALSE); //checks the invalid argument size = 1 assert(CB_Init(&b, buffer, 128) == TRUE); //checks that the function returns TRUE upon TRUE buffer = NULL; assert(CB_Init(&b, buffer, 16) == FALSE); //tests the invalid argument where buffer is a null pointer free(buffer); } /* This code tests the buffer at the edge case size is two. */ { //Test functionality at edge case size is 2 uint8_t CBtestbuf[2]; CircularBuffer b; CB_Init(&b, CBtestbuf, 2); //creates a new buffer of length two assert(!b.dataSize); // Add a single item and check. uint8_t peekval; CB_WriteByte(&b, 0x56); assert(b.dataSize == 1); CB_Peek(&b, &peekval); assert(peekval == 0x56); // Remove that item and check. uint8_t e; assert(CB_ReadByte(&b, &e) && e == 0x56); assert(b.dataSize == 0); assert(CB_Peek(&b, &peekval) == 0); //Now write two characters to the buffer assert(CB_WriteByte(&b, 0x56)); assert(CB_WriteByte(&b, 0x58)); assert(b.dataSize == 2); //Check to see if the length is correct CB_WriteByte(&b, 0x59); //Write a third element to the two bit buffer assert(b.overflowCount == 1); //Check that overflow has occurred assert(CB_ReadByte(&b, &e) && e == 0x56); //Check Reading an element assert(b.dataSize == 1); //Check the length of the buffer assert(CB_ReadByte(&b, &e) && e == 0x58); //Check Reading an element assert(b.dataSize == 0); //Check that the buffer is now empty assert(CB_ReadByte(&b, &e) == FALSE); //checks that the empty buffer cannot be read from } /* This code tests the edge case where the buffer is at the maximum size. The size is limited by the maximum value which can be held in a uint16_t. */ { //Test functionality at edge case size is UINT16_MAX CircularBuffer b; uint8_t *buffertwo = (uint8_t*)malloc(UINT16_MAX*sizeof(uint8_t)); CB_Init(&b, buffertwo, UINT16_MAX); assert(!b.dataSize); assert(b.staticSize == UINT16_MAX); // Here we use the same UINT16_MAX int8_character long string for testing. Testing with the library with BUFFER_SIZE // set to larger than UINT16_MAX will produce errors. uint8_t testStringtwo[b.staticSize+1]; int i; for (i = 0; i < b.staticSize; ++i) { testStringtwo[i] = i; } // Fill the buffer to SIZE+1 and check for (i = 0; i < b.staticSize; ++i) { assert(b.dataSize == i); assert(CB_WriteByte(&b, i)); assert(b.dataSize == i + 1); } assert(!CB_WriteByte(&b, 0x89)); assert(b.overflowCount == 1); // Run a deepPeek on the now-full buffer. uint8_t tmpStringtwo[b.staticSize]; assert(CB_PeekMany(&b, tmpStringtwo, b.staticSize)); assert(b.dataSize == b.staticSize); assert(memcmp(testStringtwo, tmpStringtwo, b.staticSize) == 0); // Verify reading of an entire circular-buffer uint8_t d; for (i = b.dataSize; i > 0; --i) { assert(CB_ReadByte(&b, &d)); assert(d == testStringtwo[b.staticSize - i]); } assert(b.dataSize == 0); d = 0x77; int8_t d3 = d; assert(!CB_ReadByte(&b, &d)); assert(d == d3); assert(!b.dataSize); // Test element insertion when the buffer wraps around. uint8_t peekval; assert(CB_WriteByte(&b, 91)); assert(b.overflowCount == 1); // Overflow is triggered on an earlier test assert(b.dataSize == 1); CB_Peek(&b, &peekval); assert(peekval == 91); assert(CB_WriteByte(&b, 92)); assert(b.dataSize == 2); assert(CB_WriteByte(&b, 93)); assert(b.dataSize == 3); assert(CB_WriteByte(&b, 94)); assert(b.dataSize == 4); // Test DeepPeek on wrapped-around buffers uint8_t peekDatatwo[4]; assert(CB_PeekMany(&b, peekDatatwo, 4)); assert(peekDatatwo[0] == 91); assert(peekDatatwo[1] == 92); assert(peekDatatwo[2] == 93); assert(peekDatatwo[3] == 94); // Test reading now. assert(CB_ReadByte(&b, &d) && d == 91); assert(CB_ReadByte(&b, &d) && d == 92); assert(CB_ReadByte(&b, &d) && d == 93); assert(CB_ReadByte(&b, &d) && d == 94); assert(!CB_ReadByte(&b, &d) && d == 94); free(buffertwo); } /* This tests the remove function */ { /**Test Remove Function*/ CircularBuffer b; uint8_t CBtestbuften[10]; CB_Init(&b, CBtestbuften, 10); //creates a new buffer of length ten assert(!b.dataSize); int i; i = 0; while(i < 9){ CB_WriteByte(&b, i); ++i; } //Test removing a valid number of items assert(b.dataSize == 9); assert(CB_Remove(&b, 4)); assert(b.dataSize == 5); uint8_t d; CB_ReadByte(&b, &d); assert(d == 4); //Test removing more items than are in the buffer assert(b.dataSize == 4); CB_Remove(&b, 10); assert(b.dataSize == 0); //The buffer is now empty } /* This tests using the CB_ReadMany function to read a buffer. */ { /**Test reading multiple values from the buffer using CB_ReadMany */ CircularBuffer b; uint8_t CBtestbufthirty[30]; CB_Init(&b, CBtestbufthirty, 30); //creates a new buffer of length thirty assert(!b.dataSize); int i = 0; while(i < 30){ CB_WriteByte(&b, i); ++i; } assert(b.dataSize == 30); uint8_t readresults[30]; assert(CB_ReadMany(&b,readresults, 15)); i = 0; while(i < 15){ assert(readresults[i] == i); ++i; } assert(b.dataSize == 15); assert(CB_ReadMany(&b,readresults, 15)); assert(b.dataSize == 0); //Checks that buffer is now empty assert(b.readIndex == b.writeIndex); //checks that the pointers are equal } /* This tests using CB_WriteMany to write to a buffer. */ { /**Testing the WriteMany function*/ CircularBuffer b; uint8_t CBtestbufthirty[30]; CB_Init(&b, CBtestbufthirty, 30); //re-initializes buffer of length thirty assert(!b.dataSize); uint8_t readresults[100]; int i = 0; while (i < 100) { readresults[i] = i; ++i; } assert(CB_WriteMany(&b, readresults, 22, TRUE)); //write 22 values from readresults to the buffer assert(b.dataSize == 22); uint8_t d; i = 0; while (i < 22) { CB_ReadByte(&b, &d); assert(d == i); ++i; } CB_Init(&b, CBtestbufthirty, 30); //re-initializes buffer of length thirty assert(!b.dataSize); assert(b.readIndex == b.writeIndex); i = 0; while (i < 30) { readresults[i] = i; ++i; } CB_WriteMany(&b, readresults, 22, FALSE); //write 22 values from readresults to the buffer assert(b.dataSize == 22); i = 0; while (i < 22) { CB_ReadByte(&b, &d); assert(d == i); ++i; } /**Now test the failure criteria specified in failEarly*/ CB_Init(&b, CBtestbufthirty, 30); //re-initializes buffer of length thirty CB_WriteMany(&b, readresults, 18, TRUE); assert(b.dataSize == 18); assert(CB_WriteMany(&b, readresults, 50, TRUE) == FALSE); //Writing more than the buffer can hold returns an error assert(b.dataSize == 18); // Checks that nothing was written assert(!CB_WriteMany(&b, readresults, 100, FALSE)); // Now without the size check assert(b.dataSize == 30); //Checks that buffer is now full assert(b.overflowCount == 88); //100-(30-18) = 88 elements have overflowed i = 0; while (i < 18) { CB_ReadByte(&b, &d); assert(i == d); ++i; } while (i < 12) { CB_ReadByte(&b, &d); assert(i == d); ++i; } } /* This code tests using the CB_WriteMany and CB_ReadMany functions to read and write structures to a buffer. CB_PeekMany is also tested. */ { // The test circular buffer CircularBuffer c; //Testing writing of a structure to the buffer and reading it back // Create a new circular buffer. TestStruct t1 = {6, 42, 1.5}; uint16_t sizetwo = 256*sizeof(TestStruct); uint8_t structbuff[sizetwo]; assert(CB_Init(&c, structbuff, sizetwo)); //Creates a buffer to hold 256 TestStruct structures assert(!c.dataSize); CB_WriteMany(&c, &t1, sizeof(TestStruct), TRUE); assert(c.dataSize == sizeof(TestStruct)); TestStruct f; CB_ReadMany(&c, &f, sizeof(TestStruct)); //read the structure back from the buffer assert(!c.dataSize); //the buffer is now empty assert(TestStructEqual(&f, &t1)); TestStruct t2 = {56, 700, 5.75}; //filled with arbitrary values //Write a single structure to the buffer and then read it back assert(c.dataSize == 0); assert(CB_WriteMany(&c, &t2, sizeof(TestStruct), TRUE)); assert(c.dataSize == sizeof(TestStruct)); assert(CB_ReadMany(&c, &f, sizeof(TestStruct))); //read the structure back from the buffer assert(TestStructEqual(&f, &t2)); //Write two structures to the buffer and then read them back CB_WriteMany(&c, &t1, sizeof(TestStruct), TRUE); CB_WriteMany(&c, &t2, sizeof(TestStruct), TRUE); assert(c.dataSize == 2*sizeof(TestStruct)); CB_ReadMany(&c, &f, sizeof(TestStruct)); //read first the structure back from the buffer assert(TestStructEqual(&f, &t1)); CB_ReadMany(&c, &f, sizeof(TestStruct)); //read the second structure back from the buffer assert(TestStructEqual(&f, &t2)); assert(c.readIndex == c.writeIndex); //The buffer is now empty. // Write four structures to the buffer using a loop and read them back. CB_WriteMany(&c, &t1, sizeof(TestStruct), TRUE); CB_WriteMany(&c, &t2, sizeof(TestStruct), TRUE); CB_WriteMany(&c, &t1, sizeof(TestStruct), TRUE); CB_WriteMany(&c, &t2, sizeof(TestStruct), TRUE); assert(c.dataSize == 4*sizeof(TestStruct)); CB_ReadMany(&c, &f, sizeof(TestStruct)); //read first the structure back from the buffer assert(TestStructEqual(&f, &t1)); assert(c.dataSize == 3*sizeof(TestStruct)); CB_ReadMany(&c, &f, sizeof(TestStruct)); //read the second structure back from the buffer assert(TestStructEqual(&f, &t2)); assert(c.dataSize == 2*sizeof(TestStruct)); CB_ReadMany(&c, &f, sizeof(TestStruct)); //read third the structure back from the buffer assert(TestStructEqual(&f, &t1)); assert(c.dataSize == 1*sizeof(TestStruct)); CB_ReadMany(&c, &f, sizeof(TestStruct)); //read the fourth structure back from the buffer assert(TestStructEqual(&f, &t2)); assert(c.dataSize == 0); //the buffer is now empty // Now write six structures and then read them off using a loop. CB_WriteMany(&c, &t1, sizeof(TestStruct), TRUE); CB_WriteMany(&c, &t2, sizeof(TestStruct), TRUE); CB_WriteMany(&c, &t1, sizeof(TestStruct), TRUE); CB_WriteMany(&c, &t2, sizeof(TestStruct), TRUE); CB_WriteMany(&c, &t1, sizeof(TestStruct), TRUE); CB_WriteMany(&c, &t2, sizeof(TestStruct), TRUE); int i = 0; while (i < 3) { CB_ReadMany(&c, &f, sizeof(TestStruct)); //read first the structure back from the buffer assert(TestStructEqual(&f, &t1)); CB_ReadMany(&c, &f, sizeof(TestStruct)); //read the second structure back from the buffer assert(TestStructEqual(&f, &t2)); ++i; } //Now write from a loop and read from a loop i = 0; while (i < 3) { CB_WriteMany(&c, &t1, sizeof(TestStruct), TRUE); CB_WriteMany(&c, &t2, sizeof(TestStruct), TRUE); ++i; } i = 0; while (i < 3) { CB_ReadMany(&c, &f, sizeof(TestStruct)); //read first the structure back from the buffer assert(TestStructEqual(&f, &t1)); CB_ReadMany(&c, &f, sizeof(TestStruct)); //read the second structure back from the buffer assert(TestStructEqual(&f, &t2)); ++i; } assert(c.readIndex == c.writeIndex); //the buffer is empty //Now write 40 elements from a loop and read 40 elements from a loop i = 0; while (i < 20) { CB_WriteMany(&c, &t1, sizeof(TestStruct), TRUE); CB_WriteMany(&c, &t2, sizeof(TestStruct), TRUE); ++i; } i = 0; while (i < 20) { CB_ReadMany(&c, &f, sizeof(TestStruct)); //read first the structure back from the buffer assert(TestStructEqual(&f, &t1)); CB_ReadMany(&c, &f, sizeof(TestStruct)); //read the second structure back from the buffer assert(TestStructEqual(&f, &t2)); ++i; } //Now try to overfill a buffer and then read the structures back //C is length 256*sizeof(TestStruct) so we will try to write 260 structures assert(c.readIndex == c.writeIndex); //the buffer is empty assert(c.dataSize == 0); i = 0; while (i < 130) { //writes 260 structures to the buffer CB_WriteMany(&c, &t1, sizeof(TestStruct), TRUE); CB_WriteMany(&c, &t2, sizeof(TestStruct), TRUE); ++i; } i = 0; while (i < 128) { CB_ReadMany(&c, &f, sizeof(TestStruct)); //read first the structure back from the buffer assert(TestStructEqual(&f, &t1)); CB_ReadMany(&c, &f, sizeof(TestStruct)); //read the second structure back from the buffer assert(TestStructEqual(&f, &t2)); ++i; } assert(c.readIndex == c.writeIndex); //the buffer is empty assert(c.dataSize == 0); //Testing CB_PeekMany on structures TestStruct peekTest; CB_WriteMany(&c, &t1, sizeof(TestStruct), TRUE); CB_PeekMany(&c, (uint8_t*)&peekTest, sizeof(TestStruct)); assert(TestStructEqual(&t1, &peekTest)); } { // Test writing and reading from the circular buffer. Showed a failure mode of the CircularBuffer with regards to its read index. CircularBuffer circBuf; unsigned char data[20]; unsigned char testIn[20] = "Hey There This Test"; unsigned char testOut[20]; // Initialize the circular buffer assert(CB_Init(&circBuf, data, 20)); assert(circBuf.readIndex == circBuf.writeIndex); assert(circBuf.readIndex == 0); assert(circBuf.writeIndex == 0); // Write and read to the buffer multiple times CB_WriteMany(&circBuf, testIn, 20, TRUE); assert(circBuf.readIndex == circBuf.writeIndex); assert(circBuf.readIndex == 0); assert(circBuf.writeIndex == 0); CB_PeekMany(&circBuf, testOut, 20); assert(circBuf.readIndex == circBuf.writeIndex); assert(circBuf.readIndex == 0); assert(circBuf.writeIndex == 0); CB_Remove(&circBuf, 20); assert(circBuf.readIndex == circBuf.writeIndex); assert(circBuf.readIndex == 0); assert(circBuf.writeIndex == 0); assert(!memcmp(testIn, testOut, 20)); CB_WriteMany(&circBuf, testIn, 20, TRUE); assert(circBuf.readIndex == circBuf.writeIndex); assert(circBuf.readIndex == 0); assert(circBuf.writeIndex == 0); CB_PeekMany(&circBuf, testOut, 20); assert(circBuf.readIndex == circBuf.writeIndex); assert(circBuf.readIndex == 0); assert(circBuf.writeIndex == 0); CB_Remove(&circBuf, 20); assert(circBuf.readIndex == circBuf.writeIndex); assert(circBuf.readIndex == 0); assert(circBuf.writeIndex == 0); assert(!memcmp(testIn, testOut, 20)); //Test uneven data CB_WriteMany(&circBuf, testIn, 7, TRUE); assert(circBuf.readIndex == 0); assert(circBuf.writeIndex == 7); CB_PeekMany(&circBuf, testOut, 7); assert(circBuf.readIndex == 0); assert(circBuf.writeIndex == 7); CB_Remove(&circBuf, 7); assert(circBuf.readIndex == circBuf.writeIndex); assert(circBuf.readIndex == 7); assert(circBuf.writeIndex == 7); //Test the full data again CB_WriteMany(&circBuf, testIn, 20, TRUE); assert(circBuf.readIndex == circBuf.writeIndex); assert(circBuf.readIndex == 7); assert(circBuf.writeIndex == 7); CB_PeekMany(&circBuf, testOut, 20); assert(circBuf.readIndex == circBuf.writeIndex); assert(circBuf.readIndex == 7); assert(circBuf.writeIndex == 7); CB_Remove(&circBuf, 20); assert(circBuf.readIndex == circBuf.writeIndex); assert(circBuf.readIndex == 7); assert(circBuf.writeIndex == 7); assert(!memcmp(testIn, testOut, 20)); CB_WriteMany(&circBuf, testIn, 20, TRUE); assert(circBuf.readIndex == circBuf.writeIndex); assert(circBuf.readIndex == 7); assert(circBuf.writeIndex == 7); CB_PeekMany(&circBuf, testOut, 20); assert(circBuf.readIndex == circBuf.writeIndex); assert(circBuf.readIndex == 7); assert(circBuf.writeIndex == 7); CB_Remove(&circBuf, 20); assert(circBuf.readIndex == circBuf.writeIndex); assert(circBuf.readIndex == 7); assert(circBuf.writeIndex == 7); assert(!memcmp(testIn, testOut, 20)); } printf("All tests passed.\n"); return 0; } #endif // UNIT_TEST_CIRCULAR_BUFFER
Buttons.h
#ifndef BUTTONS_H #define BUTTONS_H /** * @file * This library provides an interface to the 4 pushbuttons on the Digilent Basic IO shield for their * Uno32 development board platform.. * * This library relies on continuously polling the pins connected to the pushbuttons. It then * provides an event-based interface for use. The resultant output are either UpEvents or DownEvents * corresponding to whether the button has been pushed-down or released. * * Be aware that the ButtonsInit() function exists for configuring the appropriate pins on the PIC * processor and must be done before ButtonsCheckEvents() will work. */ #include <stdint.h> // We rely on this file to define various macros for working with the hardware buttons. #include "BOARD.h" /** * Specify the number of samples that must be the same to be for a change in button state to be * recognized. */ #define BUTTONS_DEBOUNCE_PERIOD 4 /** * This enum{} lists all of the possible button events that could occur. Each event constants were * chosen so that multiple button events can be recorded in a single call to ButtonsCheckEvents(). * All possible event flags will also fit into a char datatype. * * In order to check for events occuring use bit-masking as the following code demonstrates: * * uint8_t bEvent = ButtonsCheckEvents(); * if (bEvent & BUTTON_EVENT_1DOWN) { * // Button 1 was pressed * } */ typedef enum { BUTTON_EVENT_NONE = 0x00, BUTTON_EVENT_1UP = 0x01, BUTTON_EVENT_1DOWN = 0x02, BUTTON_EVENT_2UP = 0x04, BUTTON_EVENT_2DOWN = 0x08, BUTTON_EVENT_3UP = 0x10, BUTTON_EVENT_3DOWN = 0x20, BUTTON_EVENT_4UP = 0x40, BUTTON_EVENT_4DOWN = 0x80 } ButtonEventFlags; /** * This function initializes the proper pins such that the buttons 1-4 may be used by modifying * the necessary bits in TRISD/TRISF. Only the bits necessary to enable the 1-4 buttons are * modified so that this library does not interfere with other libraries. */ void ButtonsInit(void); /** * This function checks the button states and returns any events that have occured since the last * call. In the case of the first call to ButtonsCheckEvents() after ButtonsInit(), the function * should assume that the buttons start in an off state with value 0. Therefore if no buttons are * pressed when ButtonsCheckEvents() is first called, BUTTONS_EVENT_NONE should be returned. The * events are listed in the ButtonEventFlags enum at the top of this file. This function should be * called repeatedly. * * This function also performs debouncing of the buttons. Every time ButtonsCheckEvents() is called, * all button values are saved, up to the 4th sample in the past, so 4 past samples and the present * values. A button event is triggered if the newest 4 samples are the same and different from the * oldest sample. For example, if button 1 was originally down, button 1 must appear to be up for 4 * samples and the last BTN3 event was BUTTON_EVENT_3DOWN before a BUTTON_EVENT_1UP is triggered. * This eliminates button bounce, where the button may quickly oscillate between the ON and OFF state * as it's being pressed or released. * * NOTE: This will not work properly without ButtonsInit() being called beforehand. * @return A bitwise-ORing of the constants in the ButtonEventFlags enum or BUTTON_EVENT_NONE if no * event has occurred. */ uint8_t ButtonsCheckEvents(void); #endif // BUTTONS_H
BOARD.h
/* * File: BOARD.h * Author: Max Dunne * * Created on December 19, 2012, 2:08 PM */ #ifndef BOARD_H #define BOARD_H /******************************************************************************* * PUBLIC #INCLUDES * ******************************************************************************/ #ifdef PIC32 #include <GenericTypeDefs.h> #include <xc.h> #else #include <stdlib.h> #endif #include <stdint.h> /******************************************************************************* * PUBLIC #DEFINES * ******************************************************************************/ #ifndef PIC32 typedef enum _BOOL { FALSE = 0, TRUE } BOOL; #endif //suppresses various warnings that we don't need to worry about for CSE13E #ifndef _SUPPRESS_PLIB_WARNING #define _SUPPRESS_PLIB_WARNING #endif #ifndef _DISABLE_OPENADC10_CONFIGPORT_WARNING #define _DISABLE_OPENADC10_CONFIGPORT_WARNING #endif // Define some standard error codes. enum { SIZE_ERROR = -1, STANDARD_ERROR, SUCCESS }; // Specify the default UART to use. UART1 is selected the Uno32, as it's the one on the USB port. #define UART_USED UART1 // Set the baud rate for use with the UART. This is chosen as it's the default baud rate for Tera // Term. #define UART_BAUD_RATE 115200 /* * Set some helper macros for interfacing with the switches */ // Define macros for referring to the single-bit values of the switches. #define SW1 PORTDbits.RD8 #define SW2 PORTDbits.RD9 #define SW3 PORTDbits.RD10 #define SW4 PORTDbits.RD11 /** * Provides a way to quickly get the status of all 4 switches as a nibble, where a bit is 1 if * the button is being pressed and 0 if it's not. The buttons are ordered such that bit 3 is switch * 4 and bit 0 is switch 1. * @see enum ButtonStateFlags */ #define SWITCH_STATES() ((PORTD >> 8) & 0x0F) /** * The SwitchStateFlags enum provides a bitmask for use with the SWITCH_STATES macro. By bitwise- * ANDing any of these enum values with the return value from SWITCH_STATES, the current state of * the switches can be tested. * * For example: * * uint8_t switchesState = SWITCH_STATES(); * if (switchesState & SWITCH_STATE_SW3) { * // Switch 3 is on. * } * * @see SWITCH_STATES() */ enum SwitchStateFlags { SWITCH_STATE_SW1 = 0x1, SWITCH_STATE_SW2 = 0x2, SWITCH_STATE_SW3 = 0x4, SWITCH_STATE_SW4 = 0x8 }; /* * Set some helper macros for interfacing with the buttons */ // Define macros for referring to the single-bit values of the buttons. #define BTN1 PORTFbits.RF1 #define BTN2 PORTDbits.RD5 #define BTN3 PORTDbits.RD6 #define BTN4 PORTDbits.RD7 /** * Provides a way to quickly get the status of all 4 pushbuttons in to 4-bits, where a bit is 1 if * the button is being pressed and 0 if it's not. The buttons are ordered such that bit 3 is button * 4 and bit 0 is button 1. * @see enum ButtonStateFlags */ #define BUTTON_STATES() (((PORTD >> 4) & 0x0E) | ((PORTF >> 1) & 0x01)) /** * The ButtonStateFlags enum provides a bitmask for use with the BUTTON_STATES macro. By bitwise- * ANDing any of these enum values with the return value from BUTTON_STATES, the current state of * the buttons can be tested. * * For example: * * uint8_t buttonsState = BUTTON_STATES(); * if (buttonsState & BUTTON_STATE_3) { * // Buttons 3 is pressed down. * } * * @see BUTTON_STATES() */ enum ButtonStateFlags { BUTTON_STATE_1 = 0x1, BUTTON_STATE_2 = 0x2, BUTTON_STATE_3 = 0x4, BUTTON_STATE_4 = 0x8 }; /** * Enter an infinite loop and flash one of the status LEDs. This should be used when there is an * unrecoverable error onboard, like when a subsystem fails to initialize. */ #ifdef PIC32 #define FATAL_ERROR() do { \ TRISFCLR = 1; \ LATFCLR = 1; \ while (1) { \ unsigned long int i; \ for (i = 0; i < 600000; ++i); \ LATFINV = 1; \ } \ } while (0); #else #define FATAL_ERROR() exit(EXIT_FAILURE) #endif /******************************************************************************* * PUBLIC FUNCTIONS * ******************************************************************************/ /** * Function: BOARD_Init(void) * @param None * @return None * @brief Set the clocks up for the board, initializes the serial port, and turns * on the A/D subsystem for battery monitoring * @author Max Dunne, 2013.09.15 */ void BOARD_Init(); /** * Function: BOARD_End(void) * @param None * @return None * @brief shuts down all peripherals except for serial and A/D. Turns all pins * into input * @author Max Dunne, 2013.09.20 */ void BOARD_End(); /** * Function: BOARD_GetPBClock(void) * @param None * @return PB_CLOCK - speed the peripheral clock is running in hertz * @brief returns the speed of the peripheral clock. Nominally at 20Mhz * @author Max Dunne, 2013.09.01 */ unsigned int BOARD_GetPBClock(); /** * Function: BOARD_GetSysClock(void) * @param None * @return SYS_CLOCK - speed the main clock is running at */ unsigned int BOARD_GetSysClock(); #endif /* BOARD_H */
BOARD.c
/* * File: BOARD.h * Author: Max Dunne * * Created on December 19, 2012, 2:08 PM * * Much of the odder code come directly from the microchip peripheral library as reinventing the wheel seemed * not necessary */ #include "BOARD.h" // Microchip Libraries #ifdef PIC32 #include <xc.h> #endif #include <stdint.h> #include <stdio.h> #include <stdlib.h> // no legacy libc #ifdef PIC32 #ifdef __C32_LEGACY_LIBC__ #error CSE13E Can not be done with legacy libc. Ensure project properties -> XC32 (Global Options) -> Use legacy libc is UN checked. #endif #endif /******************************************************************************* * PRAGMAS * ******************************************************************************/ // Configuration Bits // SYSCLK = 80MHz // PBCLK = 20MHz // using POSC w/ PLL, XT mode #ifdef PIC32 #pragma config FPBDIV = DIV_4 #pragma config FPLLIDIV = DIV_2 // Set the PLL input divider to 2, seems to #pragma config IESO = OFF // Internal/External Switch #pragma config POSCMOD = XT // Primary Oscillator Configuration for XT osc mode #pragma config OSCIOFNC = OFF // Disable clock signal output #pragma config FCKSM = CSECMD // Clock Switching and Monitor Selection #pragma config WDTPS = PS1 // Specify the watchdog timer interval (unused) #pragma config FWDTEN = OFF // Disable the watchdog timer #pragma config ICESEL = ICS_PGx2 // Allow for debugging with the Uno32 #pragma config PWP = OFF // Keep the program flash writeable #pragma config BWP = OFF // Keep the boot flash writeable #pragma config CP = OFF // Disable code protect #pragma config FNOSC = PRIPLL //Oscillator Selection Bits #pragma config FSOSCEN = OFF //Secondary Oscillator Enable #pragma config FPLLMUL = MUL_20 //PLL Multiplier #pragma config FPLLODIV = DIV_1 //System PLL Output Clock Divid #endif /******************************************************************************* * PRIVATE #DEFINES * ******************************************************************************/ #define SYSTEM_CLOCK 80000000L #define PB_CLOCK (SYSTEM_CLOCK / 4) #define QUEUESIZE 512 //#define TurnOffAndClearInterrupt(Name) INTEnable(Name,INT_DISABLED); INTClearFlag(Name) #define TurnPortToInput(Tris) Tris=0xFFFF //#define LAB10_READ_OVERWRITE /******************************************************************************* * PRIVATE DATATYPES * ******************************************************************************/ /******************************************************************************* * PRIVATE FUNCTION PROTOTYPES * ******************************************************************************/ void SERIAL_Init(void); /******************************************************************************* * PRIVATE VARIABLES * ******************************************************************************/ /******************************************************************************* * PUBLIC FUNCTIONS * ******************************************************************************/ /** * Function: BOARD_Init(void) * @param None * @return None * @brief Initializes the board for 80MHz SYSCLK and 20MHz PBCLK. */ void BOARD_Init() { #ifdef PIC32 //seeds the random number generator with the time char seed1[] = __TIME__; unsigned int seed2 = (((unsigned int) (seed1[7] ^ seed1[2])) << 8) | ((unsigned int) (seed1[4] ^ seed1[6])); srand(seed2); //enables the interrupt system in the new style //INTConfigureSystem(INT_SYSTEM_CONFIG_MULT_VECTOR); unsigned int val; // set the CP0 cause IV bit high asm volatile("mfc0 %0,$13" : "=r"(val)); val |= 0x00800000; asm volatile("mtc0 %0,$13" : "+r"(val)); INTCONSET = _INTCON_MVEC_MASK; //INTEnableInterrupts(); int status; asm volatile("ei %0" : "=r"(status)); // Initialize for serial SERIAL_Init(); #endif } /** * Function: BOARD_End(void) * @param None * @return None * @brief shuts down all peripherals except for serial and A/D. Turns all pins * into input * @author Max Dunne, 2013.09.20 */ void BOARD_End() { #ifdef PIC32 // set all interrupt enable flags to zero IEC0 = 0; IEC1 = 0; //set all flags to zero IFS0 = 0; IFS1 = 0; // disable timer interrupts, clear flags and turn off module T1CON = 0; T2CON = 0; T3CON = 0; T4CON = 0; T5CON = 0; // disable input capture interrupts, clear flags and turn off module IC1CONCLR = _IC1CON_ICM_MASK; IC2CONCLR = _IC2CON_ICM_MASK; IC3CONCLR = _IC3CON_ICM_MASK; IC4CONCLR = _IC4CON_ICM_MASK; IC5CONCLR = _IC5CON_ICM_MASK; // disable output compare interrupts, clear flags and turn off module OC1CONCLR = _OC1CON_ON_MASK; OC2CONCLR = _OC2CON_ON_MASK; OC3CONCLR = _OC3CON_ON_MASK; OC4CONCLR = _OC4CON_ON_MASK; OC5CONCLR = _OC5CON_ON_MASK; // disable I2C interrupts, clear flags and turn off module I2C1CONCLR = _I2C1CON_ON_MASK; I2C2CONCLR = _I2C2CON_ON_MASK; //disable spi interrupts, clear flags and turn off module SPI1CONCLR = _SPI1CON_ON_MASK; SPI2CONCLR = _SPI2CON_ON_MASK; // disable external interrupts, clear flags and turn off module // set all ports to be digital inputs TurnPortToInput(TRISB); TurnPortToInput(TRISC); TurnPortToInput(TRISD); TurnPortToInput(TRISE); TurnPortToInput(TRISF); TurnPortToInput(TRISG); #else exit(0); #endif } /** * Function: BOARD_GetPBClock(void) * @param None * @return */ unsigned int BOARD_GetPBClock() { return PB_CLOCK; } /** * Function: BOARD_GetSysClock(void) * @param None * @return */ unsigned int BOARD_GetSysClock() { return SYSTEM_CLOCK; } /******************************************************************************* * PRIVATE FUNCTIONS * ******************************************************************************/ /** * @Function SERIAL_Init(void) * @param none * @return none * @brief Initializes the UART subsystem to 115200 and sets up the circular buffer * @author Max Dunne, 2011.11.10 */ void SERIAL_Init(void) { #ifdef PIC32 // we first clear the Configs Register to ensure a blank state and peripheral is off. U1MODE = 0; U1STA = 0; //UARTConfigure(UART1, 0x00); //we then calculate the required frequency, note that this comes from plib source to avoid rounding errors int sourceClock = BOARD_GetPBClock() >> 3; int brg = sourceClock / 115200; brg++; brg >>= 1; brg--; U1BRG = brg; //UARTSetDataRate(UART1, PB_CLOCK, 115200); //UARTSetFifoMode(UART1, UART_INTERRUPT_ON_RX_NOT_EMPTY | UART_INTERRUPT_ON_RX_NOT_EMPTY); //we now enable the device U1STAbits.UTXEN = 1; U1STAbits.URXEN = 1; U1MODEbits.UARTEN = 1; //UARTEnable(UART1, UART_ENABLE_FLAGS(UART_PERIPHERAL | UART_TX | UART_RX)); __XC_UART = 1; //printf("\r\n%d\t%d",U1BRG,brg); #endif } /******************************************************************************* * OVERRIDE FUNCTIONS * ******************************************************************************/ /** * @Function read(int handle, void *buffer, unsigned int len) * @param handle * @param buffer * @param len * @return Returns the number of characters read into buffer * @brief Overrides the built-in function called for scanf() to ensure proper functionality */ #ifdef PIC32 #ifndef LAB10_READ_OVERWRITE int read(int handle, char *buffer, unsigned int len) { int i; if (handle == 0) { while (!U1STAbits.URXDA) { if (U1STAbits.OERR) { U1STAbits.OERR = 0; } continue; } i = 0; while (U1STAbits.URXDA) { char tmp = U1RXREG; if (tmp == '\r') { tmp = '\n'; } *(char*) buffer++ = tmp; //WriteUART1(tmp); U1TXREG = tmp; i++; } return i; } return 0; } #endif #endif #ifdef BOARD_TEST int main(void) { BOARD_Init(); printf("\r\nThis stub tests SERIAL Functionality with scanf"); printf("\r\nIt will intake integers and divide by 2"); printf("\r\n Peripheral Clock: %d", BOARD_GetPBClock()); printf("\r\n Peripheral Clock: %d\r\n", BOARD_GetSysClock()); char trash; int input; while (1) { scanf("%d%c", &input, &trash); printf("\r\nEntered: %d\t/2: %d\r\n", input, input / 2); } while (1); return 0; } #endif
agent.py
# -*- coding: utf-8 -*- """ Created on Wed Aug 22 12:43:24 2018 @author: AutonomousSystemsLab """ help_text = """ WELCOME TO AGENT.PY! Author Max Lichtenstein, created Aug 2018 This is a tool to assist in grading for UCSC CMPE13's BattleBoats lab. It provides wrappers to allow humans to efficiently play as BattleBoats agents for the purposes of testing and grading student projects. >>> If you want to use the serial functionality of this tool, you will probably need to install the python package 'pySerial' Following is a brief description of the elements of agent.py's UI: SERIAL CONNECTION PANEL: You can connect via serial to a pic32 agent. In theory, the correct port will already be detected, but in practice that's rarely the case. Use the text box to manually enter in the port of your Pic(you can use CoolTerm or something to scan for the correct port, it's usually something like "COM4"). You may also find it useful to modify the "default_serial" parameter in the code. Once the correct port is entered, you can press "open serial" button to connect! SERIAL COMMUNICATIONS PANEL: The "incoming" and "outgoing" fields are pretty self-explanitory, they maintain a record of the serial i/o with the pic. Note that the outgoing serial is throttled to aid visibility. The "prepare outgoing message" fields below can be used to send custom messages. Write a payload and wrap it in "$...*XX", or write a full message. MESSAGE ANALYSIS PANEL: Sometimes, it is helpful to analyze a message in a way that is challenging for a human. For example, it is hard to know if a secret and an A token agree during the negotiation phase, or what the parity of a given A would be. This window shows that information, and it can be used to "cheat" during the negotiation process to go first or second as you please. This is helpful for testing all 4 entrances to the agent turn cycle. NEGOTIATION TOOLS PANEL: These windows keep a record of the negotiation data, and can also be used to do calculations with negotiation data. As the game goes on, the fields fill up with hash, secret (A), and B as the agents reveal these things. The "Calculate Hash" button will take whatever value is in the "secret" field and fill the "hash" field with its 0xbeef hash. The "Calculate Parity" button will calculate the parity of whatever is in the Secret and B fields. These fields are used when the python agent sends negotiation messages (see below) PLAY PANEL: Negotiation settings (FAIR/FIRST_MOVE/SECOND_MOVE): When generating hashes/secrets/Bs for negotiation, the python agent can attempt to cheat as either accepting or challenging agent. This is helpful for testing all 4 entrances to the agent turn cycle! Select a first-move or second-move option to attempt to go first or second. Note that this isn't guarenteed to work 100% of the time, depending on the value of the secret. But it works for most secrets. The send buttons: These are probably where you will spend most of your time! "Send CHA" sends a challenge message. It uses the value in the "hash" field. If no value is present, it uses the secret to generate a hash. If no secret is present, it generates one. "Send ACC" sends an accept message. It uses the value in the B field. If no value is present, it generates one. "Send REV" sends a reveal message. It uses the value in the secret field. If no value is present, it generates one. "Send SHO" randomly picks a square and sends a shot message at that square. "Send RES" sends a result for the last square. It picks randomly between hits and misses. Have fun battling! """ import tkinter as tk import serial import serial.tools.list_ports #import time import random import sys #from difflib import SequenceMatcher #import traceback import re #config: refresh_period = 100 #ms readout_width= 30 testing = False student_release_mode = False #serial config: default_port = "COM6" #right now we only support 1 baud= 115200 timeout = 0.25 #%% First, the basic hash functions: def beefHash(A): return (A*A) % 0xBEEF def parity(A): return sum([ 1 for i in range(16) if (A>>i) & 1]) & 1 def encode_message(message): checksum = 0; for letter in message: checksum = checksum ^ ord(letter) return "$%s*%02X" % (message, checksum) def crack_hash(hash_a): assert(type(hash_a) == int) valid_sources = [A for A in range(0xFFFF) if beefHash(A) == hash_a] return valid_sources if testing: pass # print("Parity of 0b0001101 = ",parity(0b001101)) # print("Parity of 0b1001101 = ",parity(0b1001101)) # # print("Beefhash of 3 (9) = ", beefHash(3)) # print("Beefhash of 221 (48841) = ", beefHash(221)) # print("Beefhash of 0xDE (405) = ", beefHash(0xDE)) # print("Beefhash of 0xBEEF (405) = ", beefHash(0xBEEF)) # # print("Beefhash of 12345 = ", beefHash(12345)) # # print("Crackhash of 9 = ", crack_hash(9)) #%% #%% class PyAgent: def __init__(self, master): self.master = master self.master.title("CSE013 PyAgent BattleBoats testing tool") serial_status = tk.Frame(master,padx=10,pady=10) serial_status.grid(row=0, column=0) misc = tk.Frame(master, padx=10, pady=10) misc.grid(row=0, column=1) io_viewer = tk.Frame(master,padx=10,pady=10) io_viewer.grid(row=1, column=0) m_prep = tk.Frame(master,padx=10,pady=10) m_prep.grid(row=2, column=0) tk.Button(misc, text="RESET ALL FIELDS", command = self.reset).grid(row=0,column=0) tk.Button(misc, text="HELP", command = self.show_help).grid(row=0,column=1) analysis_panel = tk.Frame(master,padx=10,pady=10) analysis_panel.grid(row=1, column=1) autoplay_panel = tk.Frame(master,padx=10,pady=10) if not student_release_mode: autoplay_panel.grid(row=2, column=1) #SERIAL STATUS CONTROLS: self.serial_status_label = tk.Label(serial_status, text="SERIAL CLOSED") self.serial_status_label.grid(row=0, column=0) self.ser_open_button = tk.Button(serial_status, text="Open Serial", command=self.openSer) self.ser_open_button.grid(row=0, column=1) self.ser_close_button = tk.Button(serial_status, text="Close Serial", command=self.closeSer) self.ser_close_button.grid(row=0, column=2) self.ser_port_box = tk.Entry(serial_status) self.ser_port_box.insert(0, default_port) self.ser_port_box.grid(row=0, column=3) #INPUT AND OUTPUT VIEWERS: tk.Label(io_viewer, text="OUTGOING").grid(row=0, column=0) self.raw_serial_out = tk.Text(io_viewer, width=30) self.raw_serial_out.grid(row=1,column=0) self.raw_out_scroller = tk.Scrollbar(io_viewer, command=self.raw_serial_out.yview) self.raw_out_scroller.grid(row=1,column=1, sticky='nsew') tk.Label(io_viewer, text="INCOMING").grid(row=0, column=2) self.raw_serial_in = tk.Text(io_viewer, width=readout_width) self.raw_serial_in.grid(row=1,column=2) self.raw_in_scroller = tk.Scrollbar(io_viewer, command=self.raw_serial_in.yview) self.raw_in_scroller.grid(row=1,column=3, sticky='nsew') #MESSAGE PREP PANEL: self.payload_to_send = tk.StringVar() self.raw_message_to_send = tk.StringVar() tk.Label(m_prep, text="PREPARE OUTGOING MESSAGE:").grid(row=0, columnspan=2) tk.Label(m_prep, text="FULL MESSAGE:").grid(row=1, column=0) tk.Entry(m_prep, width=30,textvariable=self.raw_message_to_send).grid(row=1, column=1) self.send_button = tk.Button(m_prep, text="SEND MESSAGE", command = self.sendRaw) self.send_button.grid(row=1, column=2) tk.Label(m_prep, text="PAYLOAD:").grid(row=2, column=0) tk.Entry(m_prep,width=30,textvariable=self.payload_to_send).grid(row=2, column=1) self.wrap_button = tk.Button(m_prep, text="WRAP PAYLOAD", command = self.sendPayload) self.wrap_button.grid(row=2, column=2) self.quicksend = tk.IntVar() tk.Checkbutton(m_prep, text="QUICK SEND (send message immediately upon wrapping)", variable=self.quicksend).grid(row=3,column = 1) #ANALYSIS PANEL: tk.Label(analysis_panel, text="MESSAGE ANALYSIS").grid(row=0, column=0) self.readout = tk.Text(analysis_panel,width=50,height=15) self.readout.grid(row=1, column=0) #NEG_TOOLS_PANEL: neg_tools_panel = tk.Frame(analysis_panel,padx=10,pady=10) neg_tools_panel.grid(row=4,column=0) tk.Label(neg_tools_panel, text="NEGOTIATION TOOLS").grid(row=0, column=0, columnspan=4) tk.Label(neg_tools_panel, text="hash").grid(row=1, column=0) tk.Label(neg_tools_panel, text="secret").grid(row=1, column=1) tk.Label(neg_tools_panel, text="B").grid(row=1, column=2) tk.Label(neg_tools_panel, text="parity").grid(row=1, column=3) self.hash = tk.StringVar() self.B = tk.StringVar() self.secret = tk.StringVar() self.parity = tk.StringVar() tk.Entry(neg_tools_panel, width=10, textvariable=self.hash).grid(row=3,column=0) tk.Entry(neg_tools_panel, textvariable=self.secret).grid(row=3,column=1) tk.Entry(neg_tools_panel, textvariable=self.B).grid(row=3,column=2) tk.Entry(neg_tools_panel, width=10, textvariable=self.parity).grid(row=3,column=3) tk.Button(neg_tools_panel, text="calculate hash", command = self.calculateHash).grid(row=4,column=0,columnspan=2) tk.Button(neg_tools_panel, text="calculate parity", command = self.calculateParity).grid(row=5,column=1,columnspan=2) #AUTOPLAY_PANEL tk.Label(autoplay_panel, text="PLAY").grid() self.ships_to_sink = [2,3,4,5] #used for losing autoplay self.next_row = 0 self.next_col = 0 self.autoplay = tk.StringVar() self.autoplay.set("FAIR") tk.Radiobutton(autoplay_panel, text="FAIR", variable=self.autoplay, value="FAIR").grid(row=1, column=0) tk.Radiobutton(autoplay_panel, text="FIRST_MOVE", variable=self.autoplay, value="FIRST_MOVE").grid(row=2, column=0) tk.Radiobutton(autoplay_panel, text="SECOND_MOVE", variable=self.autoplay, value="SECOND_MOVE").grid(row=3, column=0) """ #Autoplay is not functional, but maybe someday.... self.state = tk.StringVar() self.state.set("start") tk.Radiobutton(autoplay_panel, text="start", variable=self.state, value="start").grid(row=1, column=1) tk.Radiobutton(autoplay_panel, text="challenging", variable=self.state, value="challenging").grid(row=2, column=1) tk.Radiobutton(autoplay_panel, text="accepting", variable=self.state, value="accepting").grid(row=3, column=1) tk.Radiobutton(autoplay_panel, text="attacking", variable=self.state, value="attacking").grid(row=4, column=1) tk.Radiobutton(autoplay_panel, text="waiting", variable=self.state, value="waiting").grid(row=5, column=1) tk.Radiobutton(autoplay_panel, text="defending", variable=self.state, value="defending").grid(row=6, column=1) """ tk.Button(autoplay_panel, text="send cha", command = self.sendCHA).grid(row=1,column=2) tk.Button(autoplay_panel, text="send acc", command = self.sendACC).grid(row=2,column=2) tk.Button(autoplay_panel, text="send rev", command = self.sendREV).grid(row=3,column=2) tk.Button(autoplay_panel, text="send sho", command = self.sendSHO).grid(row=4,column=2) tk.Button(autoplay_panel, text="send res", command = self.sendRES).grid(row=5,column=2) #Generic state variables: self.ser_write_buffer ="" self.ser_read_buffer = "" self.port = self.ser_port_box.get() self.last_shot = (0,0) try: self.ser = serial.Serial(port=self.port, baudrate=baud, timeout=timeout) except serial.SerialException: print("Visible ports: ",list(serial.tools.list_ports.comports())) self.ser = serial.Serial() # self.ser_port_box.delete(0,tk.END) def sendCHA(self): if not self.hash.get(): self.calculateHash() self.sendSer(encode_message("CHA,"+self.hash.get())) def sendACC(self): #get best guess about challenger's secret parity: cracks = [parity(i) for i in crack_hash(int(self.hash.get()))] avg_parity = 1.0*sum(cracks)/len(cracks) self.disp("expected parity of opponent's secret is %d" %(avg_parity)) if self.autoplay.get() == "FIRST_MOVE": self.B.set(avg_parity > 0.5) if self.autoplay.get() == "SECOND_MOVE": self.B.set(avg_parity < 0.5) if not self.B.get(): self.B.set(random.randint(0,0xFFFF)) self.disp("PyAgent generated random B (%s)" % self.B.get()) self.sendSer(encode_message("ACC,"+self.B.get())) def sendREV(self): if not self.secret.get(): if not self.hash.get(): self.calculateHash() else: self.secret.set(crack_hash(int(self.hash.get))[0]) if not self.B.get(): self.B.set("0") possible_secrets = crack_hash(int(self.hash.get())) tails = [i for i in possible_secrets if parity(i ^ int(self.B.get())) == 0] heads = [i for i in possible_secrets if parity(i ^ int(self.B.get())) == 1] if self.autoplay.get()=="SECOND_MOVE" and len(tails)>0: self.disp("PyAgent picked losing B") secret = tails[0] elif self.autoplay.get()=="FIRST_MOVE" and len(heads)>0: self.disp("PyAgent picked winning B") secret = heads[0] else: secret = self.secret.get() self.sendSer(encode_message("REV,"+str(secret))) self.calculateParity() def sendSHO(self): sho_mess = "SHO,%s,%s" %(self.next_row, self.next_col) self.next_row = random.randint(0,5) self.next_col = random.randint(0,9) #self.next_row +=1 #if self.next_row==6: # self.next_col+=1 # self.next_row=0 self.sendSer(encode_message(sho_mess)) def sendRES(self): print(self.last_shot) print(self.ships_to_sink) if(self.autoplay.get()=="SECOND_MOVE"): res_mess = "RES,%s,%s,%d" %( self.last_shot[0],self.last_shot[1],self.ships_to_sink[0]) self.ships_to_sink = self.ships_to_sink[1:] + [self.ships_to_sink[0]] if(self.autoplay.get()=="FIRST_MOVE"): res_mess = "RES,%s,%s,%d" %( self.last_shot[0],self.last_shot[1],0); if(self.autoplay.get()=="FAIR"): res_mess = "RES,%s,%s,%d" %( self.last_shot[0],self.last_shot[1],random.randint(0,1)); self.sendSer(encode_message(res_mess)) def disp(self,string): self.readout.insert(tk.INSERT, "\n"+string) self.readout.see(tk.END) def reset(self): self.readout.delete("1.0",tk.END) self.raw_serial_out.delete("1.0",tk.END) self.raw_serial_in.delete("1.0",tk.END) self.secret.set("") self.hash.set("") self.B.set("") self.parity.set("") # self.quicksend.set(False) # self.autoplay.set("FAIR") # self.state.set("START") # self.payload.delete(0,tk.END) # self.raw_message_to_send.delete(0,tk.END) def runSM(self,message_in): pass # if self.state == "start": # if re.match("$CHA", message_in): # self.B = random.randint(0xFFFF) # self.sendSer(encode_message("$ACC,%d"%(self.B))) #self.state = "accepting" # if re.search("SHO",message_in): # print("\nReceived SHO") # payload=message.split(",") # # # self.raw_message_to_send.set(encode_message(sho_mess)) # self.sendSer() def decodeCharIn(self, char_in): if char_in == "\n": message = self.ser_read_buffer analysis = self.analyze_message(message) print("\n"+analysis) self.runSM(message) self.readout.insert(tk.INSERT, "\n\nRECEIVED MESSAGE: '%s'\n%s"%(message,analysis)) self.readout.see(tk.END) self.ser_read_buffer = "" else: self.ser_read_buffer+= char_in def calculateParity(self): A = int(self.secret.get()) B = int(self.B.get()) self.parity.set(parity(A ^ B)) def calculateHash(self): if not self.secret.get(): self.secret.set(random.randint(0,0xFFFF)) self.readout.insert(tk.INSERT, "\nPyAgent Generated random secret (%s)"%(self.secret.get())) self.hash.set(beefHash(int(self.secret.get()))) def analyze_message(self,message): """This updates the analysis window. This function is agnostic about who sent/received messages!""" message=message.rstrip("\n") ret="----message analysis------\nMESSAGE = '%s'"%(message) #first, get payload and checksum: match=re.match("\$(.*)\*[A-F0-9][A-F0-9]", message) if not match: ret += "\nDoes not appear to be valid NMEA format" return ret #now calculate payload: payload = match.group(1) if encode_message(payload) != message: ret += "\nPayload and checksum do not match" ret += "\n Should be: " + encode_message(payload) #ok, now we can analyze individual messages: payload = payload.split(",") if payload[0]=="CHA": self.hash.set(int(payload[1])) if not student_release_mode: ret+= "\nValid secrets for this hash are: " ret+= ",".join([" %d (%d)" % (i,parity(i)) for i in crack_hash(int(self.hash.get()))]) elif payload[0]=="ACC": self.B.set(int(payload[1])) ret+= "\nParity = %d" %(parity(int(self.B.get()))) elif payload[0]=="REV": self.secret.set(payload[1]) self.calculateParity() elif payload[0]=="SHO": self.last_shot = (int(payload[1]), int(payload[2])) elif payload[0]=="RES": pass else: ret+= "\nDoes not appear to have a valid message type" return ret+"\n------------------------" def sendSer(self, message): """Send a message""" message = message.rstrip("\n") + "\n" #there may or may not be a \n at the end of the input arg self.ser_write_buffer = self.ser_write_buffer + message #analyze message: analysis = self.analyze_message(message) print(analysis) self.readout.insert(tk.INSERT, """\n\nSENT: "%s\n%s""" %(message, analysis)) self.readout.see(tk.END) def sendRaw(self): message = self.raw_message_to_send.get() self.sendSer(message) def sendPayload(self): #basically, just move the wrap payload = self.payload_to_send.get().upper() message = encode_message(payload) self.raw_message_to_send.set(message) if self.quicksend.get(): self.sendRaw() def updateSer(self): if not self.ser.is_open: self.serial_status_label.configure(text="SERIAL CLOSED") self.master.update() return #next, handle incoming chars: self.serial_status_label.configure(text="SERIAL OPEN") if (self.ser.in_waiting>0): serIn = self.ser.read(1).decode("ascii")[0] self.decodeCharIn(serIn) if(serIn == "\n"): serIn = "\\n\n" self.raw_serial_out.insert(tk.INSERT,"\n") elif (serIn< " " or serIn > "~"): serIn = "(0x%02X)"%(ord(serIn)) self.raw_serial_in.insert(tk.INSERT,serIn) print(serIn,end="") self.raw_serial_out.see(tk.END) self.raw_serial_in.see(tk.END) #and now, outgoing chars: if len(self.ser_write_buffer) >0: serOut = self.ser_write_buffer[0] self.ser_write_buffer = self.ser_write_buffer[1:] #first, send whatever we got: self.ser.write(bytes(serOut, 'utf-8')) #and now print it on our screen if(serOut == "\n"): serOut = "\\n\n" self.raw_serial_in.insert(tk.INSERT, "\n") self.raw_serial_out.insert(tk.INSERT, serOut) self.raw_serial_out.see(tk.END) self.raw_serial_in.see(tk.END) def openSer(self): print("opening serial...") self.port = self.ser_port_box.get() self.ser = serial.Serial(port=self.port, baudrate=baud, timeout=timeout) def closeSer(self): try: self.ser.close() print("Closed serial...") except AttributeError: pass except serial.SerialException as e: print(e) def refresh(self): try: sys.stdout.flush() self.updateSer() root.after(refresh_period, self.refresh) except Exception as e: print(e) raise(e) def show_help(self): # self.hide() otherFrame = tk.Toplevel() # otherFrame.geometry("400x300") otherFrame.title("agent.py Help") self.help_text_element = tk.Text(otherFrame) self.help_text_element.insert(tk.END, help_text) self.help_text_element.grid(row=0, column=0) self.help_scroller = tk.Scrollbar(otherFrame, command=self.help_text_element.yview) self.help_scroller.grid(row=0, column=1, sticky='nsew') # handler = lambda: self.onCloseOtherFrame(otherFrame) # btn = Tk.Button(otherFrame, text="Close", command=handler) # btn.pack() #%% if __name__=="__main__": print("starting...") sys.stdout.flush() root = tk.Tk() # root.after(10000, lambda: root.destroy()) #add an autoclose to keep these things from cluttering up my desktop pyAgent = PyAgent(root) root.protocol("WM_DELETE_WINDOW", pyAgent.closeSer) root.protocol("WM_DELETE_WINDOW", lambda: root.destroy()) root.after(100, pyAgent.refresh) root.mainloop()