/** * Updates the temperature sample of this instance of MicroBitThermometer * only if isSampleNeeded() indicates that an update is required. * * This call also will add the thermometer to fiber components to receive * periodic callbacks. * * @return MICROBIT_OK on success. */ int MicroBitThermometer::updateSample() { if(!(status & MICROBIT_THERMOMETER_ADDED_TO_IDLE)) { // If we're running under a fiber scheduer, register ourselves for a periodic callback to keep our data up to date. // Otherwise, we do just do this on demand, when polled through our read() interface. fiber_add_idle_component(this); status |= MICROBIT_THERMOMETER_ADDED_TO_IDLE; } // check if we need to update our sample... if(isSampleNeeded()) { int32_t processorTemperature; uint8_t sd_enabled; // For now, we just rely on the nrf senesor to be the most accurate. // The compass module also has a temperature sensor, and has the lowest power consumption, so will run the cooler... // ...however it isn't trimmed for accuracy during manufacture, so requires calibration. sd_softdevice_is_enabled(&sd_enabled); if (sd_enabled) { // If Bluetooth is enabled, we need to go through the Nordic software to safely do this sd_temp_get(&processorTemperature); } else { // Othwerwise, we access the information directly... uint32_t *TEMP = (uint32_t *)0x4000C508; NRF_TEMP->TASKS_START = 1; while (NRF_TEMP->EVENTS_DATARDY == 0); NRF_TEMP->EVENTS_DATARDY = 0; processorTemperature = *TEMP; NRF_TEMP->TASKS_STOP = 1; } // Record our reading... temperature = processorTemperature / 4; // Schedule our next sample. sampleTime = system_timer_current_time() + samplePeriod; // Send an event to indicate that we'e updated our temperature. MicroBitEvent e(id, MICROBIT_THERMOMETER_EVT_UPDATE); } return MICROBIT_OK; };
/** * A pairing request has been sucessfully completed. * If we're in pairing mode, display a success or failure message. * * @note for internal use only. */ void MicroBitBLEManager::pairingComplete(bool success) { this->pairingStatus = MICROBIT_BLE_PAIR_COMPLETE; if(success) { this->pairingStatus |= MICROBIT_BLE_PAIR_SUCCESSFUL; fiber_add_idle_component(this); } }
/** * Reads the acceleration data from the accelerometer, and stores it in our buffer. * This only happens if the accelerometer indicates that it has new data via int1. * * On first use, this member function will attempt to add this component to the * list of fiber components in order to constantly update the values stored * by this object. * * This technique is called lazy instantiation, and it means that we do not * obtain the overhead from non-chalantly adding this component to fiber components. * * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR if the read request fails. */ int MicroBitAccelerometer::updateSample() { if(!(status & MICROBIT_ACCEL_ADDED_TO_IDLE)) { fiber_add_idle_component(this); status |= MICROBIT_ACCEL_ADDED_TO_IDLE; } // Poll interrupt line from accelerometer. // n.b. Default is Active LO. Interrupt is cleared in data read. if(!int1) { int8_t data[6]; int result; result = readCommand(MMA8653_OUT_X_MSB, (uint8_t *)data, 6); if (result !=0) return MICROBIT_I2C_ERROR; // read MSB values... sample.x = data[0]; sample.y = data[2]; sample.z = data[4]; // Normalize the data in the 0..1024 range. sample.x *= 8; sample.y *= 8; sample.z *= 8; #if CONFIG_ENABLED(USE_ACCEL_LSB) // Add in LSB values. sample.x += (data[1] / 64); sample.y += (data[3] / 64); sample.z += (data[5] / 64); #endif // Scale into millig (approx!) sample.x *= this->sampleRange; sample.y *= this->sampleRange; sample.z *= this->sampleRange; // Indicate that pitch and roll data is now stale, and needs to be recalculated if needed. status &= ~MICROBIT_ACCEL_PITCH_ROLL_VALID; // Update gesture tracking updateGesture(); // Indicate that a new sample is available MicroBitEvent e(id, MICROBIT_ACCELEROMETER_EVT_DATA_UPDATE); } return MICROBIT_OK; };
/** * Default constructor. * * Adds itself as a fiber component, and also configures itself to be the * default EventModel if defaultEventBus is NULL. */ MicroBitMessageBus::MicroBitMessageBus() { this->listeners = NULL; this->evt_queue_head = NULL; this->evt_queue_tail = NULL; this->queueLength = 0; fiber_add_idle_component(this); if(EventModel::defaultEventBus == NULL) EventModel::defaultEventBus = this; }
/** * Constructor. * Create a representation of the EventService * @param _ble The instance of a BLE device that we're running on. * @param _messageBus An instance of an EventModel which events will be mirrored from. */ MicroBitEventService::MicroBitEventService(BLEDevice &_ble, EventModel &_messageBus) : ble(_ble),messageBus(_messageBus) { GattCharacteristic microBitEventCharacteristic(MicroBitEventServiceMicroBitEventCharacteristicUUID, (uint8_t *)µBitEventBuffer, 0, sizeof(EventServiceEvent), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY); GattCharacteristic clientEventCharacteristic(MicroBitEventServiceClientEventCharacteristicUUID, (uint8_t *)&clientEventBuffer, 0, sizeof(EventServiceEvent), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE); GattCharacteristic clientRequirementsCharacteristic(MicroBitEventServiceClientRequirementsCharacteristicUUID, (uint8_t *)&clientRequirementsBuffer, 0, sizeof(EventServiceEvent), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE); microBitRequirementsCharacteristic = new GattCharacteristic(MicroBitEventServiceMicroBitRequirementsCharacteristicUUID, (uint8_t *)µBitRequirementsBuffer, 0, sizeof(EventServiceEvent), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY); microBitRequirementsCharacteristic->setReadAuthorizationCallback(this, &MicroBitEventService::onRequirementsRead); clientEventBuffer.type = 0x00; clientEventBuffer.reason = 0x00; microBitEventBuffer = microBitRequirementsBuffer = clientRequirementsBuffer = clientEventBuffer; messageBusListenerOffset = 0; // Set default security requirements microBitEventCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); clientEventCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); clientRequirementsCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); microBitRequirementsCharacteristic->requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); GattCharacteristic *characteristics[] = {µBitEventCharacteristic, &clientEventCharacteristic, &clientRequirementsCharacteristic, microBitRequirementsCharacteristic}; GattService service(MicroBitEventServiceUUID, characteristics, sizeof(characteristics) / sizeof(GattCharacteristic *)); ble.addService(service); microBitEventCharacteristicHandle = microBitEventCharacteristic.getValueHandle(); clientEventCharacteristicHandle = clientEventCharacteristic.getValueHandle(); clientRequirementsCharacteristicHandle = clientRequirementsCharacteristic.getValueHandle(); ble.onDataWritten(this, &MicroBitEventService::onDataWritten); fiber_add_idle_component(this); }
/** * Constructor. * Create a representation of the IOPinService * @param _ble The instance of a BLE device that we're running on. * @param _io An instance of MicroBitIO that this service will use to perform * I/O operations. */ MicroBitIOPinService::MicroBitIOPinService(BLEDevice &_ble, MicroBitIO &_io) : ble(_ble), io(_io) { // Create the AD characteristic, that defines whether each pin is treated as analogue or digital GattCharacteristic ioPinServiceADCharacteristic(MicroBitIOPinServiceADConfigurationUUID, (uint8_t *)&ioPinServiceADCharacteristicBuffer, 0, sizeof(ioPinServiceADCharacteristicBuffer), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE); // Create the IO characteristic, that defines whether each pin is treated as input or output GattCharacteristic ioPinServiceIOCharacteristic(MicroBitIOPinServiceIOConfigurationUUID, (uint8_t *)&ioPinServiceIOCharacteristicBuffer, 0, sizeof(ioPinServiceIOCharacteristicBuffer), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE); // Create the Data characteristic, that allows the actual read and write operations. ioPinServiceDataCharacteristic = new GattCharacteristic(MicroBitIOPinServiceDataUUID, (uint8_t *)ioPinServiceDataCharacteristicBuffer, 0, sizeof(ioPinServiceDataCharacteristicBuffer), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY); ioPinServiceDataCharacteristic->setReadAuthorizationCallback(this, &MicroBitIOPinService::onDataRead); ioPinServiceADCharacteristicBuffer = 0; ioPinServiceIOCharacteristicBuffer = 0; memset(ioPinServiceIOData, 0, sizeof(ioPinServiceIOData)); // Set default security requirements ioPinServiceADCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); ioPinServiceIOCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); ioPinServiceDataCharacteristic->requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); GattCharacteristic *characteristics[] = {&ioPinServiceADCharacteristic, &ioPinServiceIOCharacteristic, ioPinServiceDataCharacteristic}; GattService service(MicroBitIOPinServiceUUID, characteristics, sizeof(characteristics) / sizeof(GattCharacteristic *)); ble.addService(service); ioPinServiceADCharacteristicHandle = ioPinServiceADCharacteristic.getValueHandle(); ioPinServiceIOCharacteristicHandle = ioPinServiceIOCharacteristic.getValueHandle(); ble.gattServer().write(ioPinServiceADCharacteristicHandle, (const uint8_t *)&ioPinServiceADCharacteristicBuffer, sizeof(ioPinServiceADCharacteristicBuffer)); ble.gattServer().write(ioPinServiceIOCharacteristicHandle, (const uint8_t *)&ioPinServiceIOCharacteristicBuffer, sizeof(ioPinServiceIOCharacteristicBuffer)); ble.onDataWritten(this, &MicroBitIOPinService::onDataWritten); fiber_add_idle_component(this); }
/** * Poll to see if new data is available from the hardware. If so, update it. * n.b. it is not necessary to explicitly call this funciton to update data * (it normally happens in the background when the scheduler is idle), but a check is performed * if the user explicitly requests up to date data. * * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR if the update fails. * * @note This method should be overidden by the hardware driver to implement the requested * changes in hardware. */ int FXOS8700::requestUpdate() { // Ensure we're scheduled to update the data periodically if(!(MicroBitAccelerometer::status & MICROBIT_ACCEL_ADDED_TO_IDLE)) { fiber_add_idle_component((MicroBitAccelerometer *)this); MicroBitAccelerometer::status |= MICROBIT_ACCEL_ADDED_TO_IDLE; } // Poll interrupt line from device (ACTIVE LOW) if(int1.getDigitalValue() == 0) { uint8_t data[12]; int16_t s; uint8_t *lsb = (uint8_t *) &s; uint8_t *msb = lsb + 1; Sample3D accelerometerSample; Sample3D compassSample; int result; // Read the combined accelerometer and magnetometer data. result = i2c.readRegister(address, FXOS8700_OUT_X_MSB, data, 12); if (result !=0) return MICROBIT_I2C_ERROR; // read sensor data (and translate into signed little endian) *msb = data[0]; *lsb = data[1]; accelerometerSample.x = s; *msb = data[2]; *lsb = data[3]; accelerometerSample.y = s; *msb = data[4]; *lsb = data[5]; accelerometerSample.z = s; *msb = data[6]; *lsb = data[7]; compassSample.x = s; *msb = data[8]; *lsb = data[9]; compassSample.y = s; *msb = data[10]; *lsb = data[11]; compassSample.z = s; // scale the 14 bit accelerometer data (packed into 16 bits) into SI units (milli-g), and translate to ENU coordinate system MicroBitAccelerometer::sampleENU.x = (-accelerometerSample.y * MicroBitAccelerometer::sampleRange) / 32; MicroBitAccelerometer::sampleENU.y = (accelerometerSample.x * MicroBitAccelerometer::sampleRange) / 32; MicroBitAccelerometer::sampleENU.z = (accelerometerSample.z * MicroBitAccelerometer::sampleRange) / 32; // translate magnetometer data into ENU coordinate system and normalise into nano-teslas MicroBitCompass::sampleENU.x = FXOS8700_NORMALIZE_SAMPLE(-compassSample.y); MicroBitCompass::sampleENU.y = FXOS8700_NORMALIZE_SAMPLE(compassSample.x); MicroBitCompass::sampleENU.z = FXOS8700_NORMALIZE_SAMPLE(compassSample.z); MicroBitAccelerometer::update(); MicroBitCompass::update(); } return MICROBIT_OK; }
/** * Initialises the radio for use as a multipoint sender/receiver * * @return MICROBIT_OK on success, MICROBIT_NOT_SUPPORTED if the BLE stack is running. */ int MicroBitRadio::enable() { // If the device is already initialised, then there's nothing to do. if (status & MICROBIT_RADIO_STATUS_INITIALISED) return MICROBIT_OK; // Only attempt to enable this radio mode if BLE is disabled. if (ble_running()) return MICROBIT_NOT_SUPPORTED; // If this is the first time we've been enable, allocate out receive buffers. if (rxBuf == NULL) rxBuf = new FrameBuffer(); if (rxBuf == NULL) return MICROBIT_NO_RESOURCES; // Enable the High Frequency clock on the processor. This is a pre-requisite for // the RADIO module. Without this clock, no communication is possible. NRF_CLOCK->EVENTS_HFCLKSTARTED = 0; NRF_CLOCK->TASKS_HFCLKSTART = 1; while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0); // Bring up the nrf51822 RADIO module in Nordic's proprietary 1MBps packet radio mode. setTransmitPower(MICROBIT_RADIO_DEFAULT_TX_POWER); setFrequencyBand(MICROBIT_RADIO_DEFAULT_FREQUENCY); // Configure for 1Mbps throughput. // This may sound excessive, but running a high data rates reduces the chances of collisions... NRF_RADIO->MODE = RADIO_MODE_MODE_Nrf_1Mbit; // Configure the addresses we use for this protocol. We run ANONYMOUSLY at the core. // A 40 bit addresses is used. The first 32 bits match the ASCII character code for "uBit". // Statistically, this provides assurance to avoid other similar 2.4GHz protocols that may be in the vicinity. // We also map the assigned 8-bit GROUP id into the PREFIX field. This allows the RADIO hardware to perform // address matching for us, and only generate an interrupt when a packet matching our group is received. NRF_RADIO->BASE0 = MICROBIT_RADIO_BASE_ADDRESS; // Join the default group. This will configure the remaining byte in the RADIO hardware module. setGroup(this->group); // The RADIO hardware module supports the use of multiple addresses, but as we're running anonymously, we only need one. // Configure the RADIO module to use the default address (address 0) for both send and receive operations. NRF_RADIO->TXADDRESS = 0; NRF_RADIO->RXADDRESSES = 1; // Packet layout configuration. The nrf51822 has a highly capable and flexible RADIO module that, in addition to transmission // and reception of data, also contains a LENGTH field, two optional additional 1 byte fields (S0 and S1) and a CRC calculation. // Configure the packet format for a simple 8 bit length field and no additional fields. NRF_RADIO->PCNF0 = 0x00000008; NRF_RADIO->PCNF1 = 0x02040000 | MICROBIT_RADIO_MAX_PACKET_SIZE; // Most communication channels contain some form of checksum - a mathematical calculation taken based on all the data // in a packet, that is also sent as part of the packet. When received, this calculation can be repeated, and the results // from the sender and receiver compared. If they are different, then some corruption of the data ahas happened in transit, // and we know we can't trust it. The nrf51822 RADIO uses a CRC for this - a very effective checksum calculation. // // Enable automatic 16bit CRC generation and checking, and configure how the CRC is calculated. NRF_RADIO->CRCCNF = RADIO_CRCCNF_LEN_Two; NRF_RADIO->CRCINIT = 0xFFFF; NRF_RADIO->CRCPOLY = 0x11021; // Set the start random value of the data whitening algorithm. This can be any non zero number. NRF_RADIO->DATAWHITEIV = 0x18; // Set up the RADIO module to read and write from our internal buffer. NRF_RADIO->PACKETPTR = (uint32_t)rxBuf; // Configure the hardware to issue an interrupt whenever a task is complete (e.g. send/receive). NRF_RADIO->INTENSET = 0x00000008; NVIC_ClearPendingIRQ(RADIO_IRQn); NVIC_EnableIRQ(RADIO_IRQn); NRF_RADIO->SHORTS |= RADIO_SHORTS_ADDRESS_RSSISTART_Msk; // Start listening for the next packet NRF_RADIO->EVENTS_READY = 0; NRF_RADIO->TASKS_RXEN = 1; while(NRF_RADIO->EVENTS_READY == 0); NRF_RADIO->EVENTS_END = 0; NRF_RADIO->TASKS_START = 1; // register ourselves for a callback event, in order to empty the receive queue. fiber_add_idle_component(this); // Done. Record that our RADIO is configured. status |= MICROBIT_RADIO_STATUS_INITIALISED; return MICROBIT_OK; }